]> icculus.org git repositories - taylor/freespace2.git/blob - src/network/stand_server.cpp
fix ui reset
[taylor/freespace2.git] / src / network / stand_server.cpp
1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
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
6  * source.
7  *
8 */
9
10
11 #include "pstypes.h"
12 #include "osregistry.h"
13 #include "multi_options.h"
14 #include "gamesequence.h"
15 #include "timer.h"
16 #include "version.h"
17 #include "multi.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"
25 #include "cmdline.h"
26 #include "multi_kick.h"
27 #include "multi_fstracker.h"
28 #include "osregistry.h"
29
30 #include <libwebsockets.h>
31 #include <string>
32 #include <vector>
33 #include <list>
34
35
36 struct std_state {
37         std::string title;
38         std::string debug_txt;
39         std::string popup_title;
40         std::string popup_field1;
41         std::string popup_field2;
42
43         std::string active_player;
44 };
45
46 static std_state Standalone_state;
47
48 static std::list<std::string> Standalone_send_buf;
49
50
51 #define STANDALONE_MAX_BAN              50
52 static std::vector<std::string> Standalone_ban_list;
53
54 #define STD_STATS_UPDATE_TIME           500             // ms between updating player stats
55 #define STD_NG_UPDATE_TIME                      1500    // ms between updating netgame information
56 #define STD_PING_UPDATE_TIME            1000    // ms between updating pings
57 #define STD_FPS_UPDATE_TIME                     250             // ms between fps updates
58
59 static int Standalone_stats_stamp = -1;
60 static int Standalone_ng_stamp = -1;
61 static int Standalone_ping_stamp = -1;
62 static int Standalone_fps_stamp = -1;
63
64 static lws_context *stand_context = NULL;
65
66 static int startup_reset_stamp;
67 static struct lws *active_wsi = NULL;
68
69
70 static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
71 {
72         bool try_reuse = false;
73
74         switch (reason) {
75                 case LWS_CALLBACK_HTTP: {
76                         if (len < 1) {
77                                 lws_return_http_status(wsi, HTTP_STATUS_BAD_REQUEST, NULL);
78                                 try_reuse = true;
79
80                                 break;
81                         }
82
83                         int     ret = lws_serve_http_file(wsi, "./standalone.html", "text/html", NULL, 0);
84
85                         if ( (ret < 0) || ((ret > 0) && lws_http_transaction_completed(wsi)) ) {
86                                 // error or can't reuse connection, close the socket
87                                 return -1;
88                         }
89
90                         break;
91                 }
92
93                 case LWS_CALLBACK_HTTP_BODY_COMPLETION: {
94                         lws_return_http_status(wsi, HTTP_STATUS_OK, NULL);
95                         try_reuse = true;
96
97                         break;
98                 }
99
100                 case LWS_CALLBACK_HTTP_FILE_COMPLETION: {
101                         try_reuse = true;
102
103                         break;
104                 }
105
106                 default:
107                         break;
108         }
109
110         if (try_reuse) {
111                 if (lws_http_transaction_completed(wsi)) {
112                         return -1;
113                 }
114         }
115
116         return 0;
117 }
118
119 static int callback_standalone(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
120 {
121         #define MAX_BUF_SIZE    1024
122         unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + MAX_BUF_SIZE + LWS_SEND_BUFFER_POST_PADDING];
123         unsigned char *p = &buf[LWS_SEND_BUFFER_PRE_PADDING];
124         int rval;
125         int size;
126
127         switch (reason) {
128                 case LWS_CALLBACK_ESTABLISHED: {
129                         active_wsi = wsi;
130
131                         if ( timestamp_elapsed(startup_reset_stamp) ) {
132                                 std_reset_standalone_gui();
133                         }
134
135                         break;
136                 }
137
138                 case LWS_CALLBACK_CLOSED: {
139                         active_wsi = NULL;
140
141                         break;
142                 }
143
144                 case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: {
145                         if (active_wsi) {
146                                 return -1;
147                         }
148
149                         break;
150                 }
151
152                 case LWS_CALLBACK_SERVER_WRITEABLE: {
153                         while ( !Standalone_send_buf.empty() ) {
154                                 size = SDL_strlcpy((char *)p, Standalone_send_buf.front().c_str(), MAX_BUF_SIZE);
155
156                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
157
158                                 if (rval < size) {
159                                         lwsl_err("ERROR sending buffer!\n");
160                                         lws_close_reason(wsi, LWS_CLOSE_STATUS_UNEXPECTED_CONDITION, (unsigned char *)"write error", 11);
161
162                                         return -1;
163                                 }
164
165                                 Standalone_send_buf.pop_front();
166
167                                 if ( lws_send_pipe_choked(wsi) ) {
168                                         lws_callback_on_writable(wsi);
169
170                                         break;
171                                 }
172                         }
173
174                         break;
175                 }
176
177                 case LWS_CALLBACK_RECEIVE: {
178                         if (in != NULL && len > 0) {
179                                 const char *msg = (const char *)in;
180                                 char mtype = msg[0];
181
182                                 if ( !SDL_strcmp(msg, "shutdown") ) {
183                                         gameseq_post_event(GS_EVENT_QUIT_GAME);
184                                         lws_close_reason(wsi, LWS_CLOSE_STATUS_GOINGAWAY, (unsigned char *)"shutdown", 8);
185
186                                         return -1;
187                                 }
188
189                                 if ( !SDL_strcmp(msg, "reset") ) {
190                                         multi_quit_game(PROMPT_NONE);
191                                         std_reset_standalone_gui();
192
193                                         break;
194                                 }
195
196                                 // server tab
197                                 if (mtype == 'S') {
198                                         if (len >= 7) {
199                                                 if ( !SDL_strncmp(msg+2, "name ", 5) ) {
200                                                         SDL_strlcpy(Netgame.name, msg+7, SDL_arraysize(Netgame.name));
201                                                         SDL_strlcpy(Multi_options_g.std_pname, Netgame.name, SDL_arraysize(Multi_options_g.std_pname));
202                                                 } else if ( !SDL_strncmp(msg+2, "pass ", 5) ) {
203                                                         SDL_strlcpy(Multi_options_g.std_passwd, msg+7, SDL_arraysize(Multi_options_g.std_passwd));
204                                                 } else if ( !SDL_strncmp(msg+2, "kick ", 5) ) {
205                                                         char ip_string[60];
206
207                                                         for (int i = 0; i < MAX_PLAYERS; i++) {
208                                                                 if ( MULTI_CONNECTED(Net_players[i]) ) {
209                                                                         psnet_addr_to_string(ip_string, SDL_arraysize(ip_string), &Net_players[i].p_info.addr);
210
211                                                                         if ( !SDL_strcmp(msg+7, ip_string) ) {
212                                                                                 multi_kick_player(i, 0);
213
214                                                                                 break;
215                                                                         }
216                                                                 }
217                                                         }
218                                                 }
219                                         }
220                                 }
221                                 // multi-player tab
222                                 else if (mtype == 'M') {
223                                         if (len >= 6) {
224                                                 if ( !SDL_strncmp(msg+2, "fps ", 4) ) {
225                                                         int fps = SDL_atoi(msg+6);
226
227                                                         Multi_options_g.std_framecap = fps;
228                                                 }
229                                         }
230                                 }
231                                 // player info tab
232                                 else if (mtype == 'P') {
233                                         if (len >= 7) {
234                                                 if ( !SDL_strncmp(msg+2, "info ", 5) ) {
235                                                         int i;
236
237                                                         for (i = 0; i < MAX_PLAYERS; i++) {
238                                                                 net_player *np = &Net_players[i];
239
240                                                                 if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
241                                                                         if ( !SDL_strcmp(msg+7, np->player->callsign) ) {
242                                                                                 Standalone_state.active_player = msg+7;
243                                                                                 std_pinfo_display_player_info(np);
244
245                                                                                 break;
246                                                                         }
247                                                                 }
248                                                         }
249
250                                                         if (i == MAX_PLAYERS) {
251                                                                 Standalone_state.active_player.clear();
252                                                         }
253                                                 }
254                                         }
255                                 }
256                                 // god stuff tab
257                                 else if (mtype == 'G') {
258                                         if (len >= 7) {
259                                                 if ( !SDL_strncmp(msg+2, "smsg ", 5) ) {
260                                                         char txt[256];
261
262                                                         SDL_strlcpy(txt, msg+7, SDL_arraysize(txt));
263
264                                                         if (SDL_strlen(txt) > 0) {
265                                                                 send_game_chat_packet(Net_player, txt, MULTI_MSG_ALL, NULL);
266
267                                                                 std_add_chat_text(txt, MY_NET_PLAYER_NUM, 1);
268                                                         }
269                                                 } else if ( !SDL_strcmp(msg+2, "mrefresh") ) {
270                                                         if (MULTI_IS_TRACKER_GAME) {
271                                                                 cf_delete(MULTI_VALID_MISSION_FILE, CF_TYPE_DATA);
272
273                                                                 multi_update_valid_missions();
274                                                         }
275                                                 }
276                                         }
277                                 }
278                         }
279
280                         break;
281                 }
282
283                 default:
284                         break;
285         }
286
287         return 0;
288 }
289
290 static struct lws_protocols stand_protocols[] = {
291         {
292                 "http-only",
293                 callback_http,
294                 0,
295                 0
296         },
297         {
298                 "standalone",
299                 callback_standalone,
300                 0,
301                 0
302         },
303         // terminator
304         {
305                 NULL,
306                 NULL,
307                 0,
308                 0
309         }
310 };
311
312 static void std_lws_logger(int level, const char *line)
313 {
314         if (level & (LLL_WARN|LLL_ERR)) {
315                 mprintf(("STD: %s", line));
316         } else if (level & LLL_NOTICE) {
317                 nprintf(("lws", "STD: %s", line));
318         }
319 }
320
321
322
323 static void std_add_ws_message(const char *id, const char *val)
324 {
325         std::string msg;
326
327         // if no client, and startup stamp elapsed, then don't add more messages
328         if ( (active_wsi == NULL) && timestamp_elapsed(startup_reset_stamp) ) {
329                 return;
330         }
331
332         msg.assign(id);
333
334         if (val) {
335                 msg.append(val);
336         }
337
338         Standalone_send_buf.push_back(msg);
339 }
340
341 void std_deinit_standalone()
342 {
343         if (stand_context) {
344                 lws_cancel_service(stand_context);
345                 lws_context_destroy(stand_context);
346                 stand_context = NULL;
347         }
348 }
349
350 void std_init_standalone()
351 {
352         struct lws_context_creation_info info;
353
354         if (stand_context) {
355                 return;
356         }
357
358         SDL_zero(info);
359
360         // basic security measure for admin interface
361         //   "1" bind to loopback iface only *default*
362         //   "0" bind to any/all
363         //   any other value should be ip or iface name to bind
364         //   (invalid values will trigger error)
365
366         const char *stand_iface = os_config_read_string("Network", "RestrictStandAdmin", "1");
367
368         if ( !SDL_strcmp(stand_iface, "1") ) {
369                 info.iface = "127.0.0.1";
370         } else if ( !SDL_strcmp(stand_iface, "0") ) {
371                 info.iface = NULL;
372         } else {
373                 info.iface = stand_iface;
374         }
375
376         info.port = Multi_options_g.port;
377
378         info.protocols = stand_protocols;
379
380         info.gid = -1;
381         info.uid = -1;
382
383         lws_set_log_level(LLL_ERR|LLL_WARN|LLL_NOTICE, std_lws_logger);
384
385         stand_context = lws_create_context(&info);
386
387         if (stand_context == NULL) {
388                 Error(LOCATION, "Unable to initialize standalone server!");
389         }
390
391         atexit(std_deinit_standalone);
392
393         // turn off all sound and music
394         Cmdline_freespace_no_sound = 1;
395         Cmdline_freespace_no_music = 1;
396
397         char title[64];
398         SDL_snprintf(title, SDL_arraysize(title), "%s %d.%02d.%02d", XSTR("FreeSpace Standalone", 935), FS_VERSION_MAJOR, FS_VERSION_MINOR, FS_VERSION_BUILD);
399         Standalone_state.title = title;
400
401         // connections > 5 sec after startup should get gui reset
402         startup_reset_stamp = timestamp(5000);
403
404         std_reset_standalone_gui();
405
406         std_multi_update_netgame_info_controls();
407 }
408
409 static void std_update_ping_all()
410 {
411         std::string ping_upd;
412         char ping_str[10];
413
414         for (int i = 0, idx = 0; i < MAX_PLAYERS; i++) {
415                 net_player *np = &Net_players[i];
416
417                 if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
418                         if (np->s_info.ping.ping_avg > -1) {
419                                 if (np->s_info.ping.ping_avg >= 1000) {
420                                         SDL_snprintf(ping_str, SDL_arraysize(ping_str), "%s", XSTR("> 1 sec", 914));
421                                 } else {
422                                         SDL_snprintf(ping_str, SDL_arraysize(ping_str), "%d%s", np->s_info.ping.ping_avg, XSTR(" ms", 915));
423                                 }
424                         } else {
425                                 SDL_zero(ping_str);
426                         }
427
428                         // append separator if not first
429                         if (idx++) {
430                                 ping_upd.append(",");
431                         }
432
433                         ping_upd.append(ping_str);
434                 }
435         }
436
437         if ( !ping_upd.empty() ) {
438                 std_add_ws_message("S:ping ", ping_upd.c_str());
439         }
440 }
441
442 static void std_update_connections()
443 {
444         std::string conn_str;
445         char ip_address[60];
446
447         conn_str.reserve(1024);
448
449         for (int i = 0, idx = 0; i < MAX_PLAYERS; i++) {
450                 net_player *np = &Net_players[i];
451
452                 if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
453                         // append seperator if not first
454                         if (idx++) {
455                                 conn_str.append(";");
456                         }
457
458                         conn_str.append(np->player->callsign);
459                         conn_str.append(",");
460
461                         psnet_addr_to_string(ip_address, SDL_arraysize(ip_address), &np->p_info.addr);
462                         conn_str.append(ip_address);
463                 }
464         }
465
466         SDL_assert(conn_str.length() < 1024);
467
468         if ( !conn_str.empty() ) {
469                 std_add_ws_message("S:conn ", conn_str.c_str());
470         }
471 }
472
473 void std_do_gui_frame()
474 {
475         // maybe update selected player stats
476         if ( ((Standalone_stats_stamp == -1) || timestamp_elapsed(Standalone_stats_stamp)) && !Standalone_state.active_player.empty() ) {
477                 Standalone_stats_stamp = timestamp(STD_STATS_UPDATE_TIME);
478
479                 for (int i = 0; i < MAX_PLAYERS; i++) {
480                         net_player *np = &Net_players[i];
481
482                         if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
483                                 if ( !SDL_strcmp(Standalone_state.active_player.c_str(), np->player->callsign) ) {
484                                         std_pinfo_display_player_info(np);
485
486                                         break;
487                                 }
488                         }
489                 }
490         }
491
492         // maybe update netgame info
493         if ( (Standalone_ng_stamp == -1) || timestamp_elapsed(Standalone_ng_stamp) ) {
494                 Standalone_ng_stamp = timestamp(STD_NG_UPDATE_TIME);
495
496                 std_multi_update_netgame_info_controls();
497         }
498
499         // update connection ping times
500         if ( ((Standalone_ping_stamp == -1) || timestamp_elapsed(Standalone_ping_stamp)) ) {
501                 Standalone_ping_stamp = timestamp(STD_PING_UPDATE_TIME);
502
503                 std_update_ping_all();
504         }
505
506         if ( !Standalone_send_buf.empty() ) {
507                 lws_callback_on_writable_all_protocol(stand_context, &stand_protocols[1]);
508         }
509
510         lws_service(stand_context, 0);
511 }
512
513 void std_debug_set_standalone_state_string(const char *str)
514 {
515         Standalone_state.debug_txt = str;
516
517         std_add_ws_message("D:", str);
518 }
519
520 void std_connect_set_gamename(const char *name)
521 {
522         if (name == NULL) {
523                 // if a permanent name exists, use that instead of the default
524                 if ( SDL_strlen(Multi_options_g.std_pname) ) {
525                         SDL_strlcpy(Netgame.name, Multi_options_g.std_pname, SDL_arraysize(Netgame.name));
526                 } else {
527                         SDL_strlcpy(Netgame.name, XSTR("Standalone Server", 916), SDL_arraysize(Netgame.name));
528                 }
529         } else {
530                 SDL_strlcpy(Netgame.name, name, SDL_arraysize(Netgame.name));
531         }
532
533         std_add_ws_message("S:name ", Netgame.name);
534 }
535
536 int std_connect_set_connect_count()
537 {
538         int count = 0;
539
540         for (int i = 0; i < MAX_PLAYERS; i++) {
541                 if (MULTI_CONNECTED(Net_players[i]) && (Net_player != &Net_players[i]) ) {
542                         count++;
543                 }
544         }
545
546         return count;
547 }
548
549 void std_add_player(net_player *p)
550 {
551         std_update_connections();
552
553         // check to see if this guy is the host
554         std_connect_set_host_connect_status();
555 }
556
557 int std_remove_player(net_player *p)
558 {
559         int count;
560
561         std_update_connections();
562
563         // update the host connect count
564         std_connect_set_host_connect_status();
565
566         // update the currently connected players
567         count = std_connect_set_connect_count();
568
569         if (count == 0) {
570                 multi_quit_game(PROMPT_NONE);
571                 return 1;
572         }
573
574         return 0;
575 }
576
577 void std_update_player_ping(net_player *p)
578 {
579 }
580
581 void std_pinfo_display_player_info(net_player *p)
582 {
583         char sml_ping[30];
584         std::string pinfo;
585
586         pinfo.reserve(256);
587
588         // ship type
589         pinfo.append(Ship_info[p->p_info.ship_class].name);
590         pinfo.append(";");
591
592         // avg ping time
593         if (p->s_info.ping.ping_avg > 1000) {
594                 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%s", XSTR("> 1 sec", 914));
595         } else {
596                 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d%s", p->s_info.ping.ping_avg, XSTR(" ms", 915));
597         }
598
599         pinfo.append(sml_ping);
600         pinfo.append(";");
601
602         scoring_struct *ptr = &p->player->stats;
603
604         // all-time stats
605         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_shots_fired);
606         pinfo.append(sml_ping);
607         pinfo.append(",");
608         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_shots_hit);
609         pinfo.append(sml_ping);
610         pinfo.append(",");
611         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_bonehead_hits);
612         pinfo.append(sml_ping);
613         pinfo.append(",");
614         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);
615         pinfo.append(sml_ping);
616         pinfo.append(",");
617         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);
618         pinfo.append(sml_ping);
619         pinfo.append(",");
620         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_shots_fired);
621         pinfo.append(sml_ping);
622         pinfo.append(",");
623         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_shots_hit);
624         pinfo.append(sml_ping);
625         pinfo.append(",");
626         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_bonehead_hits);
627         pinfo.append(sml_ping);
628         pinfo.append(",");
629         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);
630         pinfo.append(sml_ping);
631         pinfo.append(",");
632         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);
633         pinfo.append(sml_ping);
634         pinfo.append(",");
635         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->assists);
636         pinfo.append(sml_ping);
637         pinfo.append(";");      // <- end of block
638
639         // mission stats
640         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_shots_fired);
641         pinfo.append(sml_ping);
642         pinfo.append(",");
643         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_shots_hit);
644         pinfo.append(sml_ping);
645         pinfo.append(",");
646         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_bonehead_hits);
647         pinfo.append(sml_ping);
648         pinfo.append(",");
649         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);
650         pinfo.append(sml_ping);
651         pinfo.append(",");
652         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);
653         pinfo.append(sml_ping);
654         pinfo.append(",");
655         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_shots_fired);
656         pinfo.append(sml_ping);
657         pinfo.append(",");
658         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_shots_hit);
659         pinfo.append(sml_ping);
660         pinfo.append(",");
661         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_bonehead_hits);
662         pinfo.append(sml_ping);
663         pinfo.append(",");
664         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);
665         pinfo.append(sml_ping);
666         pinfo.append(",");
667         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);
668         pinfo.append(sml_ping);
669         pinfo.append(",");
670         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->m_assists);
671         pinfo.append(sml_ping);
672
673         std_add_ws_message("P:info ", pinfo.c_str());
674 }
675
676 void std_add_chat_text(const char *text, int player_index, int add_id)
677 {
678         char id[32];
679         std::string msg;
680
681         if ( (player_index < 0) || (player_index >= MAX_PLAYERS) ) {
682                 return;
683         }
684
685         // format the chat text nicely
686         if (add_id) {
687                 if ( MULTI_STANDALONE(Net_players[player_index]) ) {
688                         SDL_snprintf(id, SDL_arraysize(id), XSTR("<SERVER> %s", 924), "");
689                 } else {
690                         SDL_snprintf(id, SDL_arraysize(id), "%s: ", Net_players[player_index].player->callsign);
691                 }
692
693                 msg.append(id);
694         }
695
696         msg.append(text);
697         msg.append("\n");
698
699         std_add_ws_message("G:mesg ", msg.c_str());
700 }
701
702 void std_reset_timestamps()
703 {
704         // reset the stats update stamp
705         Standalone_stats_stamp = timestamp(STD_STATS_UPDATE_TIME);
706
707         // reset the netgame controls update timestamp
708         Standalone_ng_stamp = timestamp(STD_NG_UPDATE_TIME);
709
710         // reset the ping update stamp
711         Standalone_ping_stamp = timestamp(STD_PING_UPDATE_TIME);
712
713         // reset fps update stamp
714         Standalone_fps_stamp = timestamp(STD_FPS_UPDATE_TIME);
715 }
716
717 void std_add_ban(const char *name)
718 {
719         if ( (name == NULL) || !SDL_strlen(name) ) {
720                 return;
721         }
722
723         if (Standalone_ban_list.size() >= STANDALONE_MAX_BAN) {
724                 return;
725         }
726
727         Standalone_ban_list.push_back(name);
728 }
729
730 int std_player_is_banned(const char *name)
731 {
732         if ( Standalone_ban_list.empty() ) {
733                 return 0;
734         }
735
736         for (size_t i = 0; i < Standalone_ban_list.size(); i++) {
737                 if ( !SDL_strcasecmp(name, Standalone_ban_list[i].c_str()) ) {
738                         return 1;
739                 }
740         }
741
742         return 0;
743 }
744
745 int std_is_host_passwd()
746 {
747         return (SDL_strlen(Multi_options_g.std_passwd) > 0) ? 1 : 0;
748 }
749
750 void std_multi_set_standalone_mission_name(const char *mission_name)
751 {
752         std_add_ws_message("M:name ", mission_name);
753 }
754
755 void std_multi_set_standalone_missiontime(float mission_time)
756 {
757         char txt[80];
758         char timestr[50];
759         fix m_time = fl2f(mission_time);
760
761         // format the time string and set the text
762         game_format_time(m_time, timestr, SDL_arraysize(timestr));
763         SDL_snprintf(txt, SDL_arraysize(txt), "%s  :  %.1f", timestr, mission_time);
764
765         std_add_ws_message("M:time ", txt);
766 }
767
768 void std_multi_update_netgame_info_controls()
769 {
770         char nginfo[50];
771
772         SDL_snprintf(nginfo, SDL_arraysize(nginfo), "%d,%d,%d,%d", Netgame.max_players, Netgame.options.max_observers, Netgame.security, Netgame.respawn);
773
774         std_add_ws_message("M:info ", nginfo);
775 }
776
777 void std_set_standalone_fps(float fps)
778 {
779         if ( (Standalone_fps_stamp == -1) || timestamp_elapsed(Standalone_fps_stamp) ) {
780                 Standalone_fps_stamp = timestamp(STD_FPS_UPDATE_TIME);
781
782                 char rfps[10];
783
784                 SDL_snprintf(rfps, SDL_arraysize(rfps), "%.1f", fps);
785
786                 std_add_ws_message("M:rfps ", rfps);
787         }
788 }
789
790 void std_multi_setup_goal_tree()
791 {
792         std::string mission_goals;
793         std::string primary;
794         std::string secondary;
795         std::string bonus;
796         std::string status;
797
798         for (int i = 0; i < Num_goals; i++) {
799                 switch (Mission_goals[i].satisfied) {
800                         case GOAL_FAILED: {
801                                 status = "f ";
802                                 break;
803                         }
804
805                         case GOAL_COMPLETE: {
806                                 status = "c ";
807                                 break;
808                         }
809
810                         case GOAL_INCOMPLETE:
811                         default: {
812                                 status = "i ";
813                                 break;
814                         }
815                 }
816
817                 switch (Mission_goals[i].type & GOAL_TYPE_MASK) {
818                         case PRIMARY_GOAL: {
819                                 primary.append(status);
820                                 primary.append(Mission_goals[i].name);
821                                 primary.append(",");
822
823                                 break;
824                         }
825
826                         case SECONDARY_GOAL: {
827                                 secondary.append(status);
828                                 secondary.append(Mission_goals[i].name);
829                                 secondary.append(",");
830
831                                 break;
832                         }
833
834                         case BONUS_GOAL: {
835                                 bonus.append(status);
836                                 bonus.append(Mission_goals[i].name);
837                                 bonus.append(",");
838
839                                 break;
840                         }
841
842                         default:
843                                 break;
844                 }
845         }
846
847         if ( primary.empty() ) {
848                 mission_goals.append("i none");
849         } else {
850                 mission_goals.append(primary.substr(0, primary.size()-1));
851         }
852
853         mission_goals.append(";");
854
855         if ( secondary.empty() ) {
856                 mission_goals.append("i none");
857         } else {
858                 mission_goals.append(secondary.substr(0, secondary.size()-1));
859         }
860
861         mission_goals.append(";");
862
863         if ( bonus.empty() ) {
864                 mission_goals.append("i none");
865         } else {
866                 mission_goals.append(bonus.substr(0, bonus.size()-1));
867         }
868
869         std_add_ws_message("M:goal ", mission_goals.c_str());
870 }
871
872 void std_multi_add_goals()
873 {
874         std_multi_setup_goal_tree();
875 }
876
877 void std_multi_update_goals()
878 {
879         std_multi_setup_goal_tree();
880 }
881
882 void std_reset_standalone_gui()
883 {
884         Standalone_send_buf.clear();
885
886         std_add_ws_message("reset", NULL);
887
888         std_add_ws_message("T:", Standalone_state.title.c_str());
889         std_add_ws_message("D:", Standalone_state.debug_txt.c_str());
890
891         std_add_ws_message("S:name ", Netgame.name);
892         std_add_ws_message("S:pass ", Multi_options_g.std_passwd);
893
894         std_update_connections();
895         std_set_standalone_fps(0.0f);
896         std_multi_set_standalone_missiontime(0.0f);
897         std_multi_update_netgame_info_controls();
898
899         Standalone_fps_stamp = -1;
900         Standalone_ng_stamp = -1;
901         Standalone_ping_stamp = -1;
902         Standalone_stats_stamp = -1;
903
904         Standalone_state.active_player.clear();
905 }
906
907
908 void std_create_gen_dialog(const char *title)
909 {
910         Standalone_state.popup_title = title;
911
912         Standalone_state.popup_field1 = "";
913         Standalone_state.popup_field2 = "";
914 }
915
916 void std_destroy_gen_dialog()
917 {
918         std_add_ws_message("popup ", NULL);
919 }
920
921 void std_gen_set_text(const char *str, int field_num)
922 {
923         std::string popup_str;
924
925         switch (field_num) {
926                 case 0:
927                         Standalone_state.popup_title = str;
928                         break;
929
930                 case 1:
931                         Standalone_state.popup_field1 = str;
932                         break;
933
934                 case 2:
935                         Standalone_state.popup_field2 = str;
936                         break;
937
938                 default:
939                         break;
940         }
941
942         popup_str.append(Standalone_state.popup_title);
943         popup_str.append(";");
944         popup_str.append(Standalone_state.popup_field1);
945         popup_str.append(";");
946         popup_str.append(Standalone_state.popup_field2);
947
948         std_add_ws_message("popup ", popup_str.c_str());
949
950         // force ws write since do_frame() may not happen until popup is done
951         lws_callback_on_writable_all_protocol(stand_context, &stand_protocols[1]);
952         lws_service(stand_context, 0);
953 }
954
955 void std_tracker_notify_login_fail()
956 {
957
958 }
959
960 void std_tracker_login()
961 {
962         if ( !Multi_options_g.pxo ) {
963                 return;
964         }
965
966         multi_fs_tracker_init();
967
968         if ( !multi_fs_tracker_inited() ) {
969                 std_tracker_notify_login_fail();
970                 return;
971         }
972
973         multi_fs_tracker_login_freespace();
974 }
975
976 void std_connect_set_host_connect_status()
977 {
978 }