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