2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on the
12 #include "osregistry.h"
13 #include "multi_options.h"
14 #include "gamesequence.h"
18 #include "stand_server.h"
19 #include "multi_pmsg.h"
20 #include "multi_endgame.h"
21 #include "multimsgs.h"
22 #include "multiutil.h"
23 #include "freespace.h"
24 #include "missiongoals.h"
26 #include "multi_kick.h"
27 #include "multi_fstracker.h"
28 #include "osregistry.h"
30 #include <libwebsockets.h>
36 static std::string Standalone_debug_state = "";
37 static std::string Standalone_ping_str;
38 static std::string Standalone_player_info;
39 static std::string Standalone_message;
40 static std::string Standalone_pinfo_active_player;
41 static std::string Standalone_mission_name = "";
42 static std::string Standalone_mission_time = "";
43 static std::string Standalone_netgame_info;
44 static std::string Standalone_mission_goals = "";
45 static std::string Standalone_popup_title;
46 static std::string Standalone_popup_field1 = "";
47 static std::string Standalone_popup_field2 = "";
48 static float Standalone_fps = 0.0f;
50 #define STANDALONE_MAX_BAN 50
51 static std::vector<std::string> Standalone_ban_list;
53 #define STD_STATS_UPDATE_TIME 500 // ms between updating player stats
54 #define STD_NG_UPDATE_TIME 1500 // ms between updating netgame information
55 #define STD_PING_UPDATE_TIME 1000 // ms between updating pings
56 #define STD_FPS_UPDATE_TIME 250 // ms between fps updates
58 static int Standalone_stats_stamp = -1;
59 static int Standalone_ng_stamp = -1;
60 static int Standalone_ping_stamp = -1;
61 static int Standalone_fps_stamp = -1;
63 static int Standalone_update_flags = 0;
65 #define STD_UFLAG_DEBUG_STATE (1<<0)
66 #define STD_UFLAG_TITLE (1<<1)
67 #define STD_UFLAG_CONN (1<<2)
68 #define STD_UFLAG_RESET (1<<3)
70 #define STD_UFLAG_SERVER_NAME (1<<4)
71 #define STD_UFLAG_HOST_PASS (1<<5)
72 #define STD_UFLAG_SET_PING (1<<6)
74 #define STD_UFLAG_FPS (1<<7)
75 #define STD_UFLAG_MISSION_NAME (1<<8)
76 #define STD_UFLAG_MISSION_TIME (1<<9)
77 #define STD_UFLAG_NETGAME_INFO (1<<10)
78 #define STD_UFLAG_MISSION_GOALS (1<<11)
80 #define STD_UFLAG_PLAYER_INFO (1<<12)
82 #define STD_UFLAG_S_MESSAGE (1<<13)
84 #define STD_UFLAG_POPUP (1<<14) // DO NOT INCLUDE IN STD_UFLAG_ALL!!
86 #define STD_UFLAG_GENERAL (STD_UFLAG_DEBUG_STATE|STD_UFLAG_TITLE|STD_UFLAG_RESET)
87 #define STD_UFLAG_TAB_SERVER (STD_UFLAG_SERVER_NAME|STD_UFLAG_HOST_PASS|STD_UFLAG_CONN|STD_UFLAG_SET_PING)
88 #define STD_UFLAG_TAB_MULTI (STD_UFLAG_FPS|STD_UFLAG_MISSION_NAME|STD_UFLAG_MISSION_TIME|STD_UFLAG_NETGAME_INFO|STD_UFLAG_MISSION_GOALS)
89 #define STD_UFLAG_TAB_PLAYER (STD_UFLAG_PLAYER_INFO)
90 #define STD_UFLAG_TAB_GS (STD_UFLAG_S_MESSAGE)
92 #define STD_UFLAG_ALL (STD_UFLAG_GENERAL|STD_UFLAG_TAB_SERVER|STD_UFLAG_TAB_MULTI|STD_UFLAG_TAB_PLAYER|STD_UFLAG_TAB_GS)
95 static lws_context *stand_context = NULL;
98 static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
100 bool try_reuse = false;
103 case LWS_CALLBACK_HTTP: {
105 lws_return_http_status(wsi, HTTP_STATUS_BAD_REQUEST, NULL);
111 int ret = lws_serve_http_file(wsi, "./standalone.html", "text/html", NULL, 0);
113 if ( (ret < 0) || ((ret > 0) && lws_http_transaction_completed(wsi)) ) {
114 // error or can't reuse connection, close the socket
121 case LWS_CALLBACK_HTTP_BODY_COMPLETION: {
122 lws_return_http_status(wsi, HTTP_STATUS_OK, NULL);
128 case LWS_CALLBACK_HTTP_FILE_COMPLETION: {
139 if (lws_http_transaction_completed(wsi)) {
147 static int callback_standalone(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
149 #define MAX_BUF_SIZE 1050
150 unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + MAX_BUF_SIZE + LWS_SEND_BUFFER_POST_PADDING];
151 unsigned char *p = &buf[LWS_SEND_BUFFER_PRE_PADDING];
156 case LWS_CALLBACK_ESTABLISHED: {
157 std_reset_standalone_gui();
162 case LWS_CALLBACK_SERVER_WRITEABLE: {
163 // RESET: *must* come first
164 if (Standalone_update_flags & STD_UFLAG_RESET) {
165 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "reset");
167 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
170 lwsl_err("ERROR sending reset command!\n");
174 Standalone_update_flags &= ~STD_UFLAG_RESET;
176 lws_callback_on_writable(wsi);
182 if (Standalone_update_flags & STD_UFLAG_TITLE) {
183 size = SDL_snprintf((char *)p, 64, "T:%s %d.%02d.%02d", XSTR("FreeSpace Standalone", 935), FS_VERSION_MAJOR, FS_VERSION_MINOR, FS_VERSION_BUILD);
185 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
188 lwsl_err("ERROR sending title string!\n");
192 Standalone_update_flags &= ~STD_UFLAG_TITLE;
194 lws_callback_on_writable(wsi);
199 if (Standalone_update_flags & STD_UFLAG_DEBUG_STATE) {
200 size = SDL_snprintf((char *)p, 32, "D:%s", Standalone_debug_state.c_str());
202 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
205 lwsl_err("ERROR sending debug state!\n");
209 Standalone_update_flags &= ~STD_UFLAG_DEBUG_STATE;
211 lws_callback_on_writable(wsi);
216 if (Standalone_update_flags & STD_UFLAG_POPUP) {
217 if ( !Standalone_popup_title.empty() ) {
218 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "popup %s;%s;%s", Standalone_popup_title.c_str(), Standalone_popup_field1.c_str(), Standalone_popup_field2.c_str());
220 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "popup ");
223 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
226 lwsl_err("ERROR sending popup!\n");
230 Standalone_update_flags &= ~STD_UFLAG_POPUP;
232 lws_callback_on_writable(wsi);
238 if (Standalone_update_flags & STD_UFLAG_SERVER_NAME) {
239 size = SDL_snprintf((char *)p, MAX_GAMENAME_LEN, "S:name %s", Netgame.name);
241 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
244 lwsl_err("ERROR sending server name!\n");
248 Standalone_update_flags &= ~STD_UFLAG_SERVER_NAME;
250 lws_callback_on_writable(wsi);
255 if (Standalone_update_flags & STD_UFLAG_HOST_PASS) {
256 size = SDL_snprintf((char *)p, STD_PASSWD_LEN, "S:pass %s", Multi_options_g.std_passwd);
258 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
261 lwsl_err("ERROR sending host password!\n");
265 Standalone_update_flags &= ~STD_UFLAG_HOST_PASS;
267 lws_callback_on_writable(wsi);
272 if (Standalone_update_flags & STD_UFLAG_CONN) {
273 std::string conn_str;
276 conn_str.reserve(1024);
278 for (int i = 0; i < MAX_PLAYERS; i++) {
279 net_player *np = &Net_players[i];
281 if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
282 conn_str.append(np->player->callsign);
283 conn_str.append(",");
285 psnet_addr_to_string(ip_address, SDL_arraysize(ip_address), &np->p_info.addr);
286 conn_str.append(ip_address);
287 conn_str.append(",");
289 if (np->s_info.ping.ping_avg > -1) {
290 if (np->s_info.ping.ping_avg >= 1000) {
291 SDL_snprintf(ip_address, SDL_arraysize(ip_address), "%s", XSTR("> 1 sec", 914));
293 SDL_snprintf(ip_address, SDL_arraysize(ip_address), "%d%s", np->s_info.ping.ping_avg, XSTR(" ms", 915));
297 conn_str.append(";");
301 SDL_assert(conn_str.length() < 1024);
303 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "S:conn %s", conn_str.c_str());
305 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
308 lwsl_err("ERROR sending connetions!\n");
312 Standalone_update_flags &= ~STD_UFLAG_CONN;
314 lws_callback_on_writable(wsi);
319 if ( (Standalone_update_flags & STD_UFLAG_SET_PING) && !Standalone_ping_str.empty() ) {
320 SDL_assert(Standalone_ping_str.length() < 1024);
322 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "S:ping %s", Standalone_ping_str.c_str());
324 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
327 lwsl_err("ERROR sending conn ping!\n");
331 Standalone_ping_str.clear();
332 Standalone_update_flags &= ~ STD_UFLAG_SET_PING;
334 lws_callback_on_writable(wsi);
340 if (Standalone_update_flags & STD_UFLAG_MISSION_NAME) {
341 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "M:name %s", Standalone_mission_name.c_str());
343 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
346 lwsl_err("ERROR sending mission name!\n");
350 Standalone_update_flags &= ~STD_UFLAG_MISSION_NAME;
352 lws_callback_on_writable(wsi);
357 if (Standalone_update_flags & STD_UFLAG_MISSION_TIME) {
358 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "M:time %s", Standalone_mission_time.c_str());
360 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
363 lwsl_err("ERROR sending mission time!\n");
367 Standalone_update_flags &= ~STD_UFLAG_MISSION_TIME;
369 lws_callback_on_writable(wsi);
374 if (Standalone_update_flags & STD_UFLAG_NETGAME_INFO) {
375 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "M:info %s", Standalone_netgame_info.c_str());
377 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
380 lwsl_err("ERROR sending netgame info!\n");
384 Standalone_update_flags &= ~STD_UFLAG_NETGAME_INFO;
386 lws_callback_on_writable(wsi);
391 if (Standalone_update_flags & STD_UFLAG_FPS) {
392 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "M:fps %.1f", Standalone_fps);
394 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
397 lwsl_err("ERROR sending fps!\n");
401 Standalone_update_flags &= ~STD_UFLAG_FPS;
403 lws_callback_on_writable(wsi);
408 if ( (Standalone_update_flags & STD_UFLAG_MISSION_GOALS) && !Standalone_mission_goals.empty() ) {
409 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "M:goal %s", Standalone_mission_goals.c_str());
411 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
414 lwsl_err("ERROR sending mission goals!\n");
418 Standalone_mission_goals.clear();
419 Standalone_update_flags &= ~STD_UFLAG_MISSION_GOALS;
421 lws_callback_on_writable(wsi);
427 if ( (Standalone_update_flags & STD_UFLAG_PLAYER_INFO) && !Standalone_player_info.empty() ) {
428 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "P:info %s", Standalone_player_info.c_str());
430 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
433 lwsl_err("ERROR sending player info!\n");
437 Standalone_player_info.clear();
438 Standalone_update_flags &= ~STD_UFLAG_PLAYER_INFO;
440 lws_callback_on_writable(wsi);
446 if ( (Standalone_update_flags & STD_UFLAG_S_MESSAGE) && !Standalone_message.empty() ) {
447 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "G:mesg %s", Standalone_message.c_str());
449 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
452 lwsl_err("ERROR sending chat message!\n");
456 Standalone_message.clear();
457 Standalone_update_flags &= ~STD_UFLAG_S_MESSAGE;
459 lws_callback_on_writable(wsi);
467 case LWS_CALLBACK_RECEIVE: {
468 if (in != NULL && len > 0) {
469 const char *msg = (const char *)in;
472 if ( !SDL_strcmp(msg, "shutdown") ) {
473 gameseq_post_event(GS_EVENT_QUIT_GAME);
477 if ( !SDL_strcmp(msg, "reset") ) {
478 multi_quit_game(PROMPT_NONE);
479 std_reset_standalone_gui();
487 if ( !SDL_strncmp(msg+2, "name ", 5) ) {
488 SDL_strlcpy(Netgame.name, msg+7, SDL_arraysize(Netgame.name));
489 SDL_strlcpy(Multi_options_g.std_pname, Netgame.name, SDL_arraysize(Multi_options_g.std_pname));
490 } else if ( !SDL_strncmp(msg+2, "pass ", 5) ) {
491 SDL_strlcpy(Multi_options_g.std_passwd, msg+7, SDL_arraysize(Multi_options_g.std_passwd));
492 } else if ( !SDL_strncmp(msg+2, "kick ", 5) ) {
495 for (int i = 0; i < MAX_PLAYERS; i++) {
496 if ( MULTI_CONNECTED(Net_players[i]) ) {
497 psnet_addr_to_string(ip_string, SDL_arraysize(ip_string), &Net_players[i].p_info.addr);
499 if ( !SDL_strcmp(msg+7, ip_string) ) {
500 multi_kick_player(i, 0);
510 else if (mtype == 'M') {
512 if ( !SDL_strncmp(msg+2, "fps ", 4) ) {
513 int fps = SDL_atoi(msg+6);
515 Multi_options_g.std_framecap = fps;
520 else if (mtype == 'P') {
522 if ( !SDL_strncmp(msg+2, "info ", 5) ) {
525 for (i = 0; i < MAX_PLAYERS; i++) {
526 net_player *np = &Net_players[i];
528 if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
529 if ( !SDL_strcmp(msg+7, np->player->callsign) ) {
530 Standalone_pinfo_active_player = msg+7;
531 std_pinfo_display_player_info(np);
538 if (i == MAX_PLAYERS) {
539 Standalone_pinfo_active_player.clear();
545 else if (mtype == 'G') {
547 if ( !SDL_strncmp(msg+2, "smsg ", 5) ) {
550 SDL_strlcpy(txt, msg+7, SDL_arraysize(txt));
552 if (SDL_strlen(txt) > 0) {
553 send_game_chat_packet(Net_player, txt, MULTI_MSG_ALL, NULL);
555 std_add_chat_text(txt, MY_NET_PLAYER_NUM, 1);
557 } else if ( !SDL_strcmp(msg+2, "mrefresh") ) {
558 if (MULTI_IS_TRACKER_GAME) {
559 cf_delete(MULTI_VALID_MISSION_FILE, CF_TYPE_DATA);
561 multi_update_valid_missions();
578 static struct lws_protocols stand_protocols[] = {
600 static void std_lws_logger(int level, const char *line)
602 if (level & (LLL_WARN|LLL_ERR)) {
603 mprintf(("STD: %s", line));
604 } else if (level & LLL_NOTICE) {
605 nprintf(("lws", "STD: %s", line));
612 void std_deinit_standalone()
615 lws_cancel_service(stand_context);
616 lws_context_destroy(stand_context);
617 stand_context = NULL;
621 void std_init_standalone()
623 struct lws_context_creation_info info;
631 // basic security measure for admin interface
632 // "1" bind to loopback iface only *default*
633 // "0" bind to any/all
634 // any other value should be ip or iface name to bind
635 // (invalid values will trigger error)
637 const char *stand_iface = os_config_read_string("Network", "RestrictStandAdmin", "1");
639 if ( !SDL_strcmp(stand_iface, "1") ) {
640 info.iface = "127.0.0.1";
641 } else if ( !SDL_strcmp(stand_iface, "0") ) {
644 info.iface = stand_iface;
647 info.port = Multi_options_g.port;
649 info.protocols = stand_protocols;
656 info.ka_interval = 0;
658 lws_set_log_level(LLL_ERR|LLL_WARN|LLL_NOTICE, std_lws_logger);
660 stand_context = lws_create_context(&info);
662 if (stand_context == NULL) {
663 Error(LOCATION, "Unable to initialize standalone server!");
666 atexit(std_deinit_standalone);
668 // turn off all sound and music
669 Cmdline_freespace_no_sound = 1;
670 Cmdline_freespace_no_music = 1;
672 std_reset_standalone_gui();
674 std_multi_update_netgame_info_controls();
677 void std_do_gui_frame()
679 // maybe update selected player stats
680 if ( ((Standalone_stats_stamp == -1) || timestamp_elapsed(Standalone_stats_stamp)) && !Standalone_pinfo_active_player.empty() ) {
681 Standalone_stats_stamp = timestamp(STD_STATS_UPDATE_TIME);
683 for (int i = 0; i < MAX_PLAYERS; i++) {
684 net_player *np = &Net_players[i];
686 if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
687 if ( !SDL_strcmp(Standalone_pinfo_active_player.c_str(), np->player->callsign) ) {
688 std_pinfo_display_player_info(np);
696 // maybe update netgame info
697 if ( (Standalone_ng_stamp == -1) || timestamp_elapsed(Standalone_ng_stamp) ) {
698 Standalone_ng_stamp = timestamp(STD_NG_UPDATE_TIME);
700 std_multi_update_netgame_info_controls();
703 // update connection ping times
704 if ( ((Standalone_ping_stamp == -1) || timestamp_elapsed(Standalone_ping_stamp)) && !Standalone_ping_str.empty() ) {
705 Standalone_ping_stamp = timestamp(STD_PING_UPDATE_TIME);
706 Standalone_update_flags |= STD_UFLAG_SET_PING;
709 if (Standalone_update_flags) {
710 lws_callback_on_writable_all_protocol(stand_context, &stand_protocols[1]);
713 lws_service(stand_context, 0);
716 void std_debug_set_standalone_state_string(const char *str)
718 Standalone_debug_state = str;
720 Standalone_update_flags |= STD_UFLAG_DEBUG_STATE;
723 void std_connect_set_gamename(const char *name)
726 // if a permanent name exists, use that instead of the default
727 if ( SDL_strlen(Multi_options_g.std_pname) ) {
728 SDL_strlcpy(Netgame.name, Multi_options_g.std_pname, SDL_arraysize(Netgame.name));
730 SDL_strlcpy(Netgame.name, XSTR("Standalone Server", 916), SDL_arraysize(Netgame.name));
733 SDL_strlcpy(Netgame.name, name, SDL_arraysize(Netgame.name));
736 Standalone_update_flags |= STD_UFLAG_SERVER_NAME;
739 int std_connect_set_connect_count()
743 for (int i = 0; i < MAX_PLAYERS; i++) {
744 if (MULTI_CONNECTED(Net_players[i]) && (Net_player != &Net_players[i]) ) {
752 void std_add_player(net_player *p)
754 Standalone_update_flags |= STD_UFLAG_CONN;
756 // check to see if this guy is the host
757 std_connect_set_host_connect_status();
760 int std_remove_player(net_player *p)
764 Standalone_update_flags |= STD_UFLAG_CONN;
766 // update the host connect count
767 std_connect_set_host_connect_status();
769 // update the currently connected players
770 count = std_connect_set_connect_count();
773 multi_quit_game(PROMPT_NONE);
780 void std_update_player_ping(net_player *p)
784 if (p->s_info.ping.ping_avg > -1) {
785 psnet_addr_to_string(ip_address, SDL_arraysize(ip_address), &p->p_info.addr);
787 // only add it if address isn't already queued up
788 if (Standalone_ping_str.find(ip_address) == std::string::npos) {
789 Standalone_ping_str.append(ip_address);
791 if (p->s_info.ping.ping_avg > 1000) {
792 SDL_snprintf(ip_address, SDL_arraysize(ip_address), ",%s;", XSTR("> 1 sec", 914));
794 SDL_snprintf(ip_address, SDL_arraysize(ip_address), ",%d%s;", p->s_info.ping.ping_avg, XSTR(" ms", 915));
797 Standalone_ping_str.append(ip_address);
802 void std_pinfo_display_player_info(net_player *p)
806 Standalone_player_info.clear();
807 Standalone_player_info.reserve(256);
810 Standalone_player_info.append(Ship_info[p->p_info.ship_class].name);
811 Standalone_player_info.append(";");
814 if (p->s_info.ping.ping_avg > 1000) {
815 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%s", XSTR("> 1 sec", 914));
817 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d%s", p->s_info.ping.ping_avg, XSTR(" ms", 915));
820 Standalone_player_info.append(sml_ping);
821 Standalone_player_info.append(";");
823 scoring_struct *ptr = &p->player->stats;
826 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_shots_fired);
827 Standalone_player_info.append(sml_ping);
828 Standalone_player_info.append(",");
829 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_shots_hit);
830 Standalone_player_info.append(sml_ping);
831 Standalone_player_info.append(",");
832 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_bonehead_hits);
833 Standalone_player_info.append(sml_ping);
834 Standalone_player_info.append(",");
835 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_shots_fired ? (int)(100.0f * ((float)ptr->p_shots_hit / (float)ptr->p_shots_fired)) : 0);
836 Standalone_player_info.append(sml_ping);
837 Standalone_player_info.append(",");
838 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_shots_fired ? (int)(100.0f * ((float)ptr->p_bonehead_hits / (float)ptr->p_shots_fired)) : 0);
839 Standalone_player_info.append(sml_ping);
840 Standalone_player_info.append(",");
841 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_shots_fired);
842 Standalone_player_info.append(sml_ping);
843 Standalone_player_info.append(",");
844 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_shots_hit);
845 Standalone_player_info.append(sml_ping);
846 Standalone_player_info.append(",");
847 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_bonehead_hits);
848 Standalone_player_info.append(sml_ping);
849 Standalone_player_info.append(",");
850 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_shots_fired ? (int)(100.0f * ((float)ptr->s_shots_hit / (float)ptr->s_shots_fired)) : 0);
851 Standalone_player_info.append(sml_ping);
852 Standalone_player_info.append(",");
853 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_shots_fired ? (int)(100.0f * ((float)ptr->s_bonehead_hits / (float)ptr->s_shots_fired)) : 0);
854 Standalone_player_info.append(sml_ping);
855 Standalone_player_info.append(",");
856 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->assists);
857 Standalone_player_info.append(sml_ping);
858 Standalone_player_info.append(";"); // <- end of block
861 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_shots_fired);
862 Standalone_player_info.append(sml_ping);
863 Standalone_player_info.append(",");
864 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_shots_hit);
865 Standalone_player_info.append(sml_ping);
866 Standalone_player_info.append(",");
867 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_bonehead_hits);
868 Standalone_player_info.append(sml_ping);
869 Standalone_player_info.append(",");
870 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_shots_fired ? (int)(100.0f * ((float)ptr->mp_shots_hit / (float)ptr->mp_shots_fired)) : 0);
871 Standalone_player_info.append(sml_ping);
872 Standalone_player_info.append(",");
873 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_shots_fired ? (int)(100.0f * ((float)ptr->mp_bonehead_hits / (float)ptr->mp_shots_fired)) : 0);
874 Standalone_player_info.append(sml_ping);
875 Standalone_player_info.append(",");
876 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_shots_fired);
877 Standalone_player_info.append(sml_ping);
878 Standalone_player_info.append(",");
879 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_shots_hit);
880 Standalone_player_info.append(sml_ping);
881 Standalone_player_info.append(",");
882 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_bonehead_hits);
883 Standalone_player_info.append(sml_ping);
884 Standalone_player_info.append(",");
885 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_shots_fired ? (int)(100.0f * ((float)ptr->ms_shots_hit / (float)ptr->ms_shots_fired)) : 0);
886 Standalone_player_info.append(sml_ping);
887 Standalone_player_info.append(",");
888 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_shots_fired ? (int)(100.0f * ((float)ptr->ms_bonehead_hits / (float)ptr->ms_shots_fired)) : 0);
889 Standalone_player_info.append(sml_ping);
890 Standalone_player_info.append(",");
891 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->m_assists);
892 Standalone_player_info.append(sml_ping);
894 Standalone_update_flags |= STD_UFLAG_PLAYER_INFO;
897 void std_add_chat_text(const char *text, int player_index, int add_id)
901 if ( (player_index < 0) || (player_index >= MAX_PLAYERS) ) {
905 // format the chat text nicely
907 if ( MULTI_STANDALONE(Net_players[player_index]) ) {
908 SDL_snprintf(id, SDL_arraysize(id), XSTR("<SERVER> %s", 924), "");
910 SDL_snprintf(id, SDL_arraysize(id), "%s: ", Net_players[player_index].player->callsign);
913 Standalone_message.append(id);
916 Standalone_message.append(text);
917 Standalone_message.append("\n");
919 Standalone_update_flags |= STD_UFLAG_S_MESSAGE;
922 void std_reset_timestamps()
924 // reset the stats update stamp
925 Standalone_stats_stamp = timestamp(STD_STATS_UPDATE_TIME);
927 // reset the netgame controls update timestamp
928 Standalone_ng_stamp = timestamp(STD_NG_UPDATE_TIME);
930 // reset the ping update stamp
931 Standalone_ping_stamp = timestamp(STD_PING_UPDATE_TIME);
933 // reset fps update stamp
934 Standalone_fps_stamp = timestamp(STD_FPS_UPDATE_TIME);
937 void std_add_ban(const char *name)
939 if ( (name == NULL) || !SDL_strlen(name) ) {
943 if (Standalone_ban_list.size() >= STANDALONE_MAX_BAN) {
947 Standalone_ban_list.push_back(name);
950 int std_player_is_banned(const char *name)
952 if ( Standalone_ban_list.empty() ) {
956 for (size_t i = 0; i < Standalone_ban_list.size(); i++) {
957 if ( !SDL_strcasecmp(name, Standalone_ban_list[i].c_str()) ) {
965 int std_is_host_passwd()
967 return (SDL_strlen(Multi_options_g.std_passwd) > 0) ? 1 : 0;
970 void std_multi_set_standalone_mission_name(const char *mission_name)
972 Standalone_mission_name = mission_name;
973 Standalone_update_flags |= STD_UFLAG_MISSION_NAME;
976 void std_multi_set_standalone_missiontime(float mission_time)
980 fix m_time = fl2f(mission_time);
982 // format the time string and set the text
983 game_format_time(m_time, timestr, SDL_arraysize(timestr));
984 SDL_snprintf(txt, SDL_arraysize(txt), "%s : %.1f", timestr, mission_time);
986 Standalone_mission_time = txt;
987 Standalone_update_flags |= STD_UFLAG_MISSION_TIME;
990 void std_multi_update_netgame_info_controls()
994 SDL_snprintf(nginfo, SDL_arraysize(nginfo), "%d,%d,%d,%d", Netgame.max_players, Netgame.options.max_observers, Netgame.security, Netgame.respawn);
996 Standalone_netgame_info = nginfo;
997 Standalone_update_flags |= STD_UFLAG_NETGAME_INFO;
1000 void std_set_standalone_fps(float fps)
1002 if ( (Standalone_fps_stamp == -1) || timestamp_elapsed(Standalone_fps_stamp) ) {
1003 Standalone_fps_stamp = timestamp(STD_FPS_UPDATE_TIME);
1005 Standalone_fps = fps;
1006 Standalone_update_flags |= STD_UFLAG_FPS;
1010 void std_multi_setup_goal_tree()
1012 std::string primary;
1013 std::string secondary;
1017 Standalone_mission_goals.clear();
1019 for (int i = 0; i < Num_goals; i++) {
1020 switch (Mission_goals[i].satisfied) {
1026 case GOAL_COMPLETE: {
1031 case GOAL_INCOMPLETE:
1038 switch (Mission_goals[i].type & GOAL_TYPE_MASK) {
1039 case PRIMARY_GOAL: {
1040 primary.append(status);
1041 primary.append(Mission_goals[i].name);
1042 primary.append(",");
1047 case SECONDARY_GOAL: {
1048 secondary.append(status);
1049 secondary.append(Mission_goals[i].name);
1050 secondary.append(",");
1056 bonus.append(status);
1057 bonus.append(Mission_goals[i].name);
1068 if ( primary.empty() ) {
1069 Standalone_mission_goals.append("i none");
1071 Standalone_mission_goals.append(primary.substr(0, primary.size()-1));
1074 Standalone_mission_goals.append(";");
1076 if ( secondary.empty() ) {
1077 Standalone_mission_goals.append("i none");
1079 Standalone_mission_goals.append(secondary.substr(0, secondary.size()-1));
1082 Standalone_mission_goals.append(";");
1084 if ( bonus.empty() ) {
1085 Standalone_mission_goals.append("i none");
1087 Standalone_mission_goals.append(bonus.substr(0, bonus.size()-1));
1090 Standalone_update_flags |= STD_UFLAG_MISSION_GOALS;
1093 void std_multi_add_goals()
1095 std_multi_setup_goal_tree();
1098 void std_multi_update_goals()
1100 std_multi_setup_goal_tree();
1103 void std_reset_standalone_gui()
1105 Standalone_stats_stamp = -1;
1106 Standalone_ng_stamp = -1;
1107 Standalone_ping_stamp = -1;
1108 Standalone_fps_stamp = -1;
1110 Standalone_ping_str.clear();
1111 Standalone_player_info.clear();
1112 Standalone_message.clear();
1113 Standalone_pinfo_active_player.clear();
1114 Standalone_mission_name = "";
1115 Standalone_mission_time = "";
1116 Standalone_popup_title.clear();
1117 Standalone_popup_field1 = "";
1118 Standalone_popup_field2 = "";
1120 std_set_standalone_fps(0.0f);
1121 std_multi_set_standalone_missiontime(0.0f);
1122 std_multi_update_netgame_info_controls();
1124 Standalone_update_flags |= STD_UFLAG_ALL;
1127 void std_create_gen_dialog(const char *title)
1129 Standalone_popup_title = title;
1132 void std_destroy_gen_dialog()
1134 Standalone_popup_title.clear();
1136 Standalone_update_flags |= STD_UFLAG_POPUP;
1139 void std_gen_set_text(const char *str, int field_num)
1141 switch (field_num) {
1143 Standalone_popup_title = str;
1147 Standalone_popup_field1 = str;
1151 Standalone_popup_field2 = str;
1158 Standalone_update_flags |= STD_UFLAG_POPUP;
1160 // force ws write since do_frame() may not happen until popup is done
1161 lws_callback_on_writable_all_protocol(stand_context, &stand_protocols[1]);
1162 lws_service(stand_context, 0);
1165 void std_tracker_notify_login_fail()
1170 void std_tracker_login()
1172 if ( !Multi_options_g.pxo ) {
1176 multi_fs_tracker_init();
1178 if ( !multi_fs_tracker_inited() ) {
1179 std_tracker_notify_login_fail();
1183 multi_fs_tracker_login_freespace();
1186 void std_connect_set_host_connect_status()