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_gui.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"
28 #include <libwebsockets.h>
34 static std::string Standalone_debug_state = "";
35 static std::string Standalone_ping_str;
36 static std::string Standalone_player_info;
37 static std::string Standalone_message;
38 static std::string Standalone_pinfo_active_player;
39 static std::string Standalone_mission_name = "";
40 static std::string Standalone_mission_time = "";
41 static std::string Standalone_netgame_info;
42 static std::string Standalone_mission_goals = "";
43 static std::string Standalone_popup_title;
44 static std::string Standalone_popup_field1 = "";
45 static std::string Standalone_popup_field2 = "";
46 static float Standalone_fps = 0.0f;
48 #define STANDALONE_MAX_BAN 50
49 static std::vector<std::string> Standalone_ban_list;
51 #define STD_STATS_UPDATE_TIME 500 // ms between updating player stats
52 #define STD_NG_UPDATE_TIME 1500 // ms between updating netgame information
53 #define STD_PING_UPDATE_TIME 1000 // ms between updating pings
54 #define STD_FPS_UPDATE_TIME 250 // ms between fps updates
56 static int Standalone_stats_stamp = -1;
57 static int Standalone_ng_stamp = -1;
58 static int Standalone_ping_stamp = -1;
59 static int Standalone_fps_stamp = -1;
61 static int Standalone_update_flags = 0;
63 #define STD_UFLAG_DEBUG_STATE (1<<0)
64 #define STD_UFLAG_TITLE (1<<1)
65 #define STD_UFLAG_CONN (1<<2)
66 #define STD_UFLAG_RESET (1<<3)
68 #define STD_UFLAG_SERVER_NAME (1<<4)
69 #define STD_UFLAG_HOST_PASS (1<<5)
70 #define STD_UFLAG_SET_PING (1<<6)
72 #define STD_UFLAG_FPS (1<<7)
73 #define STD_UFLAG_MISSION_NAME (1<<8)
74 #define STD_UFLAG_MISSION_TIME (1<<9)
75 #define STD_UFLAG_NETGAME_INFO (1<<10)
76 #define STD_UFLAG_MISSION_GOALS (1<<11)
78 #define STD_UFLAG_PLAYER_INFO (1<<12)
80 #define STD_UFLAG_S_MESSAGE (1<<13)
82 #define STD_UFLAG_POPUP (1<<14) // DO NOT INCLUDE IN STD_UFLAG_ALL!!
84 #define STD_UFLAG_GENERAL (STD_UFLAG_DEBUG_STATE|STD_UFLAG_TITLE|STD_UFLAG_RESET)
85 #define STD_UFLAG_TAB_SERVER (STD_UFLAG_SERVER_NAME|STD_UFLAG_HOST_PASS|STD_UFLAG_CONN|STD_UFLAG_SET_PING)
86 #define STD_UFLAG_TAB_MULTI (STD_UFLAG_FPS|STD_UFLAG_MISSION_NAME|STD_UFLAG_MISSION_TIME|STD_UFLAG_NETGAME_INFO|STD_UFLAG_MISSION_GOALS)
87 #define STD_UFLAG_TAB_PLAYER (STD_UFLAG_PLAYER_INFO)
88 #define STD_UFLAG_TAB_GS (STD_UFLAG_S_MESSAGE)
90 #define STD_UFLAG_ALL (STD_UFLAG_GENERAL|STD_UFLAG_TAB_SERVER|STD_UFLAG_TAB_MULTI|STD_UFLAG_TAB_PLAYER|STD_UFLAG_TAB_GS)
93 static lws_context *stand_context = NULL;
96 static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
98 bool try_reuse = false;
101 case LWS_CALLBACK_HTTP: {
103 lws_return_http_status(wsi, HTTP_STATUS_BAD_REQUEST, NULL);
109 int ret = lws_serve_http_file(wsi, "./standalone.html", "text/html", NULL, 0);
111 if ( (ret < 0) || ((ret > 0) && lws_http_transaction_completed(wsi)) ) {
112 // error or can't reuse connection, close the socket
119 case LWS_CALLBACK_HTTP_BODY_COMPLETION: {
120 lws_return_http_status(wsi, HTTP_STATUS_OK, NULL);
126 case LWS_CALLBACK_HTTP_FILE_COMPLETION: {
137 if (lws_http_transaction_completed(wsi)) {
145 static int callback_standalone(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
147 #define MAX_BUF_SIZE 1050
148 unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + MAX_BUF_SIZE + LWS_SEND_BUFFER_POST_PADDING];
149 unsigned char *p = &buf[LWS_SEND_BUFFER_PRE_PADDING];
154 case LWS_CALLBACK_ESTABLISHED: {
155 std_reset_standalone_gui();
160 case LWS_CALLBACK_SERVER_WRITEABLE: {
161 // RESET: *must* come first
162 if (Standalone_update_flags & STD_UFLAG_RESET) {
163 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "reset");
165 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
168 lwsl_err("ERROR sending reset command!\n");
172 Standalone_update_flags &= ~STD_UFLAG_RESET;
176 if (Standalone_update_flags & STD_UFLAG_TITLE) {
177 size = SDL_snprintf((char *)p, 64, "T:%s %d.%02d.%02d", XSTR("FreeSpace Standalone", 935), FS_VERSION_MAJOR, FS_VERSION_MINOR, FS_VERSION_BUILD);
179 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
182 lwsl_err("ERROR sending title string!\n");
186 Standalone_update_flags &= ~STD_UFLAG_TITLE;
189 if (Standalone_update_flags & STD_UFLAG_DEBUG_STATE) {
190 size = SDL_snprintf((char *)p, 32, "D:%s", Standalone_debug_state.c_str());
192 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
195 lwsl_err("ERROR sending debug state!\n");
199 Standalone_update_flags &= ~STD_UFLAG_DEBUG_STATE;
202 if (Standalone_update_flags & STD_UFLAG_POPUP) {
203 if ( !Standalone_popup_title.empty() ) {
204 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());
206 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "popup ");
209 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
212 lwsl_err("ERROR sending popup!\n");
216 Standalone_update_flags &= ~STD_UFLAG_POPUP;
220 if (Standalone_update_flags & STD_UFLAG_SERVER_NAME) {
221 size = SDL_snprintf((char *)p, MAX_GAMENAME_LEN, "S:name %s", Netgame.name);
223 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
226 lwsl_err("ERROR sending server name!\n");
230 Standalone_update_flags &= ~STD_UFLAG_SERVER_NAME;
233 if (Standalone_update_flags & STD_UFLAG_HOST_PASS) {
234 size = SDL_snprintf((char *)p, STD_PASSWD_LEN, "S:pass %s", Multi_options_g.std_passwd);
236 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
239 lwsl_err("ERROR sending host password!\n");
243 Standalone_update_flags &= ~STD_UFLAG_HOST_PASS;
246 if (Standalone_update_flags & STD_UFLAG_CONN) {
247 std::string conn_str;
250 conn_str.reserve(1024);
252 for (int i = 0; i < MAX_PLAYERS; i++) {
253 net_player *np = &Net_players[i];
255 if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
256 conn_str.append(np->player->callsign);
257 conn_str.append(",");
259 psnet_addr_to_string(ip_address, SDL_arraysize(ip_address), &np->p_info.addr);
260 conn_str.append(ip_address);
261 conn_str.append(",");
263 if (np->s_info.ping.ping_avg > -1) {
264 if (np->s_info.ping.ping_avg >= 1000) {
265 SDL_snprintf(ip_address, SDL_arraysize(ip_address), "%s", XSTR("> 1 sec", 914));
267 SDL_snprintf(ip_address, SDL_arraysize(ip_address), "%d%s", np->s_info.ping.ping_avg, XSTR(" ms", 915));
271 conn_str.append(";");
275 SDL_assert(conn_str.length() < 1024);
277 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "S:conn %s", conn_str.c_str());
279 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
282 lwsl_err("ERROR sending connetions!\n");
286 Standalone_update_flags &= ~STD_UFLAG_CONN;
289 if ( (Standalone_update_flags & STD_UFLAG_SET_PING) && !Standalone_ping_str.empty() ) {
290 SDL_assert(Standalone_ping_str.length() < 1024);
292 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "S:ping %s", Standalone_ping_str.c_str());
294 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
297 lwsl_err("ERROR sending conn ping!\n");
301 Standalone_ping_str.clear();
302 Standalone_update_flags &= ~ STD_UFLAG_SET_PING;
306 if (Standalone_update_flags & STD_UFLAG_MISSION_NAME) {
307 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "M:name %s", Standalone_mission_name.c_str());
309 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
312 lwsl_err("ERROR sending mission name!\n");
316 Standalone_update_flags &= ~STD_UFLAG_MISSION_NAME;
319 if (Standalone_update_flags & STD_UFLAG_MISSION_TIME) {
320 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "M:time %s", Standalone_mission_time.c_str());
322 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
325 lwsl_err("ERROR sending mission time!\n");
329 Standalone_update_flags &= ~STD_UFLAG_MISSION_TIME;
332 if (Standalone_update_flags & STD_UFLAG_NETGAME_INFO) {
333 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "M:info %s", Standalone_netgame_info.c_str());
335 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
338 lwsl_err("ERROR sending netgame info!\n");
342 Standalone_update_flags &= ~STD_UFLAG_NETGAME_INFO;
345 if (Standalone_update_flags & STD_UFLAG_FPS) {
346 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "M:fps %.1f", Standalone_fps);
348 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
351 lwsl_err("ERROR sending fps!\n");
355 Standalone_update_flags &= ~STD_UFLAG_FPS;
358 if ( (Standalone_update_flags & STD_UFLAG_MISSION_GOALS) && !Standalone_mission_goals.empty() ) {
359 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "M:goal %s", Standalone_mission_goals.c_str());
361 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
364 lwsl_err("ERROR sending mission goals!\n");
368 Standalone_mission_goals.clear();
369 Standalone_update_flags &= ~STD_UFLAG_MISSION_GOALS;
373 if ( (Standalone_update_flags & STD_UFLAG_PLAYER_INFO) && !Standalone_player_info.empty() ) {
374 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "P:info %s", Standalone_player_info.c_str());
376 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
379 lwsl_err("ERROR sending player info!\n");
383 Standalone_player_info.clear();
384 Standalone_update_flags &= ~STD_UFLAG_PLAYER_INFO;
388 if ( (Standalone_update_flags & STD_UFLAG_S_MESSAGE) && !Standalone_message.empty() ) {
389 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "G:mesg %s", Standalone_message.c_str());
391 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
394 lwsl_err("ERROR sending chat message!\n");
398 Standalone_message.clear();
399 Standalone_update_flags &= ~STD_UFLAG_S_MESSAGE;
405 case LWS_CALLBACK_RECEIVE: {
406 if (in != NULL && len > 0) {
407 const char *msg = (const char *)in;
410 if ( !SDL_strcmp(msg, "shutdown") ) {
411 gameseq_post_event(GS_EVENT_QUIT_GAME);
418 if ( !SDL_strncmp(msg+2, "name ", 5) ) {
419 SDL_strlcpy(Netgame.name, msg+7, SDL_arraysize(Netgame.name));
420 SDL_strlcpy(Multi_options_g.std_pname, Netgame.name, SDL_arraysize(Multi_options_g.std_pname));
421 } else if ( !SDL_strncmp(msg+2, "pass ", 5) ) {
422 SDL_strlcpy(Multi_options_g.std_passwd, msg+7, SDL_arraysize(Multi_options_g.std_passwd));
423 } else if ( !SDL_strncmp(msg+2, "kick ", 5) ) {
426 for (int i = 0; i < MAX_PLAYERS; i++) {
427 if ( MULTI_CONNECTED(Net_players[i]) ) {
428 psnet_addr_to_string(ip_string, SDL_arraysize(ip_string), &Net_players[i].p_info.addr);
430 if ( !SDL_strcmp(msg+7, ip_string) ) {
431 multi_kick_player(i, 0);
441 else if (mtype == 'M') {
443 if ( !SDL_strncmp(msg+2, "fps ", 4) ) {
444 int fps = SDL_atoi(msg+6);
446 Multi_options_g.std_framecap = fps;
451 else if (mtype == 'P') {
453 if ( !SDL_strncmp(msg+2, "info ", 5) ) {
456 for (i = 0; i < MAX_PLAYERS; i++) {
457 net_player *np = &Net_players[i];
459 if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
460 if ( !SDL_strcmp(msg+7, np->player->callsign) ) {
461 Standalone_pinfo_active_player = msg+7;
462 std_pinfo_display_player_info(np);
469 if (i == MAX_PLAYERS) {
470 Standalone_pinfo_active_player.clear();
476 else if (mtype == 'G') {
478 if ( !SDL_strncmp(msg+2, "smsg ", 5) ) {
481 SDL_strlcpy(txt, msg+7, SDL_arraysize(txt));
483 if (SDL_strlen(txt) > 0) {
484 send_game_chat_packet(Net_player, txt, MULTI_MSG_ALL, NULL);
486 std_add_chat_text(txt, MY_NET_PLAYER_NUM, 1);
488 } else if ( !SDL_strcmp(msg+2, "mrefresh") ) {
489 if (MULTI_IS_TRACKER_GAME) {
490 cf_delete(MULTI_VALID_MISSION_FILE, CF_TYPE_DATA);
492 multi_update_valid_missions();
509 static struct lws_protocols stand_protocols[] = {
534 void std_deinit_standalone()
537 lws_cancel_service(stand_context);
538 lws_context_destroy(stand_context);
539 stand_context = NULL;
543 void std_init_standalone()
545 struct lws_context_creation_info info;
553 info.port = Multi_options_g.port;
555 info.protocols = stand_protocols;
562 info.ka_interval = 0;
564 stand_context = lws_create_context(&info);
566 if (stand_context == NULL) {
567 Error(LOCATION, "Unable to initialize standalone server!");
570 atexit(std_deinit_standalone);
572 // turn off all sound and music
573 Cmdline_freespace_no_sound = 1;
574 Cmdline_freespace_no_music = 1;
576 std_reset_standalone_gui();
578 std_multi_update_netgame_info_controls();
581 void std_do_gui_frame()
583 // maybe update selected player stats
584 if ( ((Standalone_stats_stamp == -1) || timestamp_elapsed(Standalone_stats_stamp)) && !Standalone_pinfo_active_player.empty() ) {
585 Standalone_stats_stamp = timestamp(STD_STATS_UPDATE_TIME);
587 for (int i = 0; i < MAX_PLAYERS; i++) {
588 net_player *np = &Net_players[i];
590 if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
591 if ( !SDL_strcmp(Standalone_pinfo_active_player.c_str(), np->player->callsign) ) {
592 std_pinfo_display_player_info(np);
600 // maybe update netgame info
601 if ( (Standalone_ng_stamp == -1) || timestamp_elapsed(Standalone_ng_stamp) ) {
602 Standalone_ng_stamp = timestamp(STD_NG_UPDATE_TIME);
604 std_multi_update_netgame_info_controls();
607 // update connection ping times
608 if ( ((Standalone_ping_stamp == -1) || timestamp_elapsed(Standalone_ping_stamp)) && !Standalone_ping_str.empty() ) {
609 Standalone_ping_stamp = timestamp(STD_PING_UPDATE_TIME);
610 Standalone_update_flags |= STD_UFLAG_SET_PING;
613 if (Standalone_update_flags) {
614 lws_callback_on_writable_all_protocol(stand_context, &stand_protocols[1]);
617 lws_service(stand_context, 0);
620 void std_debug_set_standalone_state_string(const char *str)
622 Standalone_debug_state = str;
624 Standalone_update_flags |= STD_UFLAG_DEBUG_STATE;
627 void std_connect_set_gamename(const char *name)
630 // if a permanent name exists, use that instead of the default
631 if ( SDL_strlen(Multi_options_g.std_pname) ) {
632 SDL_strlcpy(Netgame.name, Multi_options_g.std_pname, SDL_arraysize(Netgame.name));
634 SDL_strlcpy(Netgame.name, XSTR("Standalone Server", 916), SDL_arraysize(Netgame.name));
637 SDL_strlcpy(Netgame.name, name, SDL_arraysize(Netgame.name));
640 Standalone_update_flags |= STD_UFLAG_SERVER_NAME;
643 int std_connect_set_connect_count()
647 for (int i = 0; i < MAX_PLAYERS; i++) {
648 if (MULTI_CONNECTED(Net_players[i]) && (Net_player != &Net_players[i]) ) {
656 void std_add_player(net_player *p)
658 Standalone_update_flags |= STD_UFLAG_CONN;
660 // check to see if this guy is the host
661 std_connect_set_host_connect_status();
664 int std_remove_player(net_player *p)
668 Standalone_update_flags |= STD_UFLAG_CONN;
670 // update the host connect count
671 std_connect_set_host_connect_status();
673 // update the currently connected players
674 count = std_connect_set_connect_count();
677 multi_quit_game(PROMPT_NONE);
684 void std_update_player_ping(net_player *p)
688 if (p->s_info.ping.ping_avg > -1) {
689 psnet_addr_to_string(ip_address, SDL_arraysize(ip_address), &p->p_info.addr);
691 // only add it if address isn't already queued up
692 if (Standalone_ping_str.find(ip_address) == std::string::npos) {
693 Standalone_ping_str.append(ip_address);
695 if (p->s_info.ping.ping_avg > 1000) {
696 SDL_snprintf(ip_address, SDL_arraysize(ip_address), ",%s;", XSTR("> 1 sec", 914));
698 SDL_snprintf(ip_address, SDL_arraysize(ip_address), ",%d%s;", p->s_info.ping.ping_avg, XSTR(" ms", 915));
701 Standalone_ping_str.append(ip_address);
706 void std_pinfo_display_player_info(net_player *p)
710 Standalone_player_info.clear();
711 Standalone_player_info.reserve(256);
714 Standalone_player_info.append(Ship_info[p->p_info.ship_class].name);
715 Standalone_player_info.append(";");
718 if (p->s_info.ping.ping_avg > 1000) {
719 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%s", XSTR("> 1 sec", 914));
721 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d%s", p->s_info.ping.ping_avg, XSTR(" ms", 915));
724 Standalone_player_info.append(sml_ping);
725 Standalone_player_info.append(";");
727 scoring_struct *ptr = &p->player->stats;
730 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_shots_fired);
731 Standalone_player_info.append(sml_ping);
732 Standalone_player_info.append(",");
733 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_shots_hit);
734 Standalone_player_info.append(sml_ping);
735 Standalone_player_info.append(",");
736 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_bonehead_hits);
737 Standalone_player_info.append(sml_ping);
738 Standalone_player_info.append(",");
739 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);
740 Standalone_player_info.append(sml_ping);
741 Standalone_player_info.append(",");
742 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);
743 Standalone_player_info.append(sml_ping);
744 Standalone_player_info.append(",");
745 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_shots_fired);
746 Standalone_player_info.append(sml_ping);
747 Standalone_player_info.append(",");
748 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_shots_hit);
749 Standalone_player_info.append(sml_ping);
750 Standalone_player_info.append(",");
751 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_bonehead_hits);
752 Standalone_player_info.append(sml_ping);
753 Standalone_player_info.append(",");
754 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);
755 Standalone_player_info.append(sml_ping);
756 Standalone_player_info.append(",");
757 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);
758 Standalone_player_info.append(sml_ping);
759 Standalone_player_info.append(",");
760 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->assists);
761 Standalone_player_info.append(sml_ping);
762 Standalone_player_info.append(";"); // <- end of block
765 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_shots_fired);
766 Standalone_player_info.append(sml_ping);
767 Standalone_player_info.append(",");
768 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_shots_hit);
769 Standalone_player_info.append(sml_ping);
770 Standalone_player_info.append(",");
771 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_bonehead_hits);
772 Standalone_player_info.append(sml_ping);
773 Standalone_player_info.append(",");
774 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);
775 Standalone_player_info.append(sml_ping);
776 Standalone_player_info.append(",");
777 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);
778 Standalone_player_info.append(sml_ping);
779 Standalone_player_info.append(",");
780 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_shots_fired);
781 Standalone_player_info.append(sml_ping);
782 Standalone_player_info.append(",");
783 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_shots_hit);
784 Standalone_player_info.append(sml_ping);
785 Standalone_player_info.append(",");
786 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_bonehead_hits);
787 Standalone_player_info.append(sml_ping);
788 Standalone_player_info.append(",");
789 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);
790 Standalone_player_info.append(sml_ping);
791 Standalone_player_info.append(",");
792 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);
793 Standalone_player_info.append(sml_ping);
794 Standalone_player_info.append(",");
795 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->m_assists);
796 Standalone_player_info.append(sml_ping);
798 Standalone_update_flags |= STD_UFLAG_PLAYER_INFO;
801 void std_add_chat_text(const char *text, int player_index, int add_id)
805 if ( (player_index < 0) || (player_index >= MAX_PLAYERS) ) {
809 // format the chat text nicely
811 if ( MULTI_STANDALONE(Net_players[player_index]) ) {
812 SDL_snprintf(id, SDL_arraysize(id), XSTR("<SERVER> %s", 924), "");
814 SDL_snprintf(id, SDL_arraysize(id), "%s: ", Net_players[player_index].player->callsign);
817 Standalone_message.append(id);
820 Standalone_message.append(text);
821 Standalone_message.append("\n");
823 Standalone_update_flags |= STD_UFLAG_S_MESSAGE;
826 void std_reset_timestamps()
828 // reset the stats update stamp
829 Standalone_stats_stamp = timestamp(STD_STATS_UPDATE_TIME);
831 // reset the netgame controls update timestamp
832 Standalone_ng_stamp = timestamp(STD_NG_UPDATE_TIME);
834 // reset the ping update stamp
835 Standalone_ping_stamp = timestamp(STD_PING_UPDATE_TIME);
837 // reset fps update stamp
838 Standalone_fps_stamp = timestamp(STD_FPS_UPDATE_TIME);
841 void std_add_ban(const char *name)
843 if ( (name == NULL) || !SDL_strlen(name) ) {
847 if (Standalone_ban_list.size() >= STANDALONE_MAX_BAN) {
851 Standalone_ban_list.push_back(name);
854 int std_player_is_banned(const char *name)
856 if ( Standalone_ban_list.empty() ) {
860 for (size_t i = 0; i < Standalone_ban_list.size(); i++) {
861 if ( !SDL_strcasecmp(name, Standalone_ban_list[i].c_str()) ) {
869 int std_is_host_passwd()
871 return (SDL_strlen(Multi_options_g.std_passwd) > 0) ? 1 : 0;
874 void std_multi_set_standalone_mission_name(const char *mission_name)
876 Standalone_mission_name = mission_name;
877 Standalone_update_flags |= STD_UFLAG_MISSION_NAME;
880 void std_multi_set_standalone_missiontime(float mission_time)
884 fix m_time = fl2f(mission_time);
886 // format the time string and set the text
887 game_format_time(m_time, timestr, SDL_arraysize(timestr));
888 SDL_snprintf(txt, SDL_arraysize(txt), "%s : %.1f", timestr, mission_time);
890 Standalone_mission_time = txt;
891 Standalone_update_flags |= STD_UFLAG_MISSION_TIME;
894 void std_multi_update_netgame_info_controls()
898 SDL_snprintf(nginfo, SDL_arraysize(nginfo), "%d,%d,%d,%d", Netgame.max_players, Netgame.options.max_observers, Netgame.security, Netgame.respawn);
900 Standalone_netgame_info = nginfo;
901 Standalone_update_flags |= STD_UFLAG_NETGAME_INFO;
904 void std_set_standalone_fps(float fps)
906 if ( (Standalone_fps_stamp == -1) || timestamp_elapsed(Standalone_fps_stamp) ) {
907 Standalone_fps_stamp = timestamp(STD_FPS_UPDATE_TIME);
909 Standalone_fps = fps;
910 Standalone_update_flags |= STD_UFLAG_FPS;
914 void std_multi_setup_goal_tree()
917 std::string secondary;
921 Standalone_mission_goals.clear();
923 for (int i = 0; i < Num_goals; i++) {
924 switch (Mission_goals[i].satisfied) {
930 case GOAL_COMPLETE: {
935 case GOAL_INCOMPLETE:
942 switch (Mission_goals[i].type & GOAL_TYPE_MASK) {
944 primary.append(status);
945 primary.append(Mission_goals[i].name);
951 case SECONDARY_GOAL: {
952 secondary.append(status);
953 secondary.append(Mission_goals[i].name);
954 secondary.append(",");
960 bonus.append(status);
961 bonus.append(Mission_goals[i].name);
972 if ( primary.empty() ) {
973 Standalone_mission_goals.append("i none");
975 Standalone_mission_goals.append(primary.substr(0, primary.size()-1));
978 Standalone_mission_goals.append(";");
980 if ( secondary.empty() ) {
981 Standalone_mission_goals.append("i none");
983 Standalone_mission_goals.append(secondary.substr(0, secondary.size()-1));
986 Standalone_mission_goals.append(";");
988 if ( bonus.empty() ) {
989 Standalone_mission_goals.append("i none");
991 Standalone_mission_goals.append(bonus.substr(0, bonus.size()-1));
994 Standalone_update_flags |= STD_UFLAG_MISSION_GOALS;
997 void std_multi_add_goals()
999 std_multi_setup_goal_tree();
1002 void std_multi_update_goals()
1004 std_multi_setup_goal_tree();
1007 void std_reset_standalone_gui()
1009 Standalone_stats_stamp = -1;
1010 Standalone_ng_stamp = -1;
1011 Standalone_ping_stamp = -1;
1012 Standalone_fps_stamp = -1;
1014 Standalone_ping_str.clear();
1015 Standalone_player_info.clear();
1016 Standalone_message.clear();
1017 Standalone_pinfo_active_player.clear();
1018 Standalone_mission_name = "";
1019 Standalone_mission_time = "";
1020 Standalone_netgame_info.clear();
1021 Standalone_popup_title.clear();
1022 Standalone_popup_field1 = "";
1023 Standalone_popup_field2 = "";
1025 std_set_standalone_fps(0.0f);
1026 std_multi_set_standalone_missiontime(0.0f);
1028 Standalone_update_flags |= STD_UFLAG_ALL;
1031 void std_create_gen_dialog(const char *title)
1033 Standalone_popup_title = title;
1036 void std_destroy_gen_dialog()
1038 Standalone_popup_title.clear();
1040 Standalone_update_flags |= STD_UFLAG_POPUP;
1043 void std_gen_set_text(const char *str, int field_num)
1045 switch (field_num) {
1047 Standalone_popup_title = str;
1051 Standalone_popup_field1 = str;
1055 Standalone_popup_field2 = str;
1062 Standalone_update_flags |= STD_UFLAG_POPUP;
1065 void std_connect_set_host_connect_status()