]> icculus.org git repositories - taylor/freespace2.git/blob - src/network/stand_server.cpp
limit ws write calls to once per callback to fix crash
[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
28 #include <libwebsockets.h>
29 #include <string>
30 #include <vector>
31
32
33
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;
47
48 #define STANDALONE_MAX_BAN              50
49 static std::vector<std::string> Standalone_ban_list;
50
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
55
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;
60
61 static int Standalone_update_flags = 0;
62
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)
67
68 #define STD_UFLAG_SERVER_NAME           (1<<4)
69 #define STD_UFLAG_HOST_PASS                     (1<<5)
70 #define STD_UFLAG_SET_PING                      (1<<6)
71
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)
77
78 #define STD_UFLAG_PLAYER_INFO           (1<<12)
79
80 #define STD_UFLAG_S_MESSAGE                     (1<<13)
81
82 #define STD_UFLAG_POPUP                         (1<<14)         // DO NOT INCLUDE IN STD_UFLAG_ALL!!
83
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)
89
90 #define STD_UFLAG_ALL                           (STD_UFLAG_GENERAL|STD_UFLAG_TAB_SERVER|STD_UFLAG_TAB_MULTI|STD_UFLAG_TAB_PLAYER|STD_UFLAG_TAB_GS)
91
92
93 static lws_context *stand_context = NULL;
94
95
96 static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
97 {
98         bool try_reuse = false;
99
100         switch (reason) {
101                 case LWS_CALLBACK_HTTP: {
102                         if (len < 1) {
103                                 lws_return_http_status(wsi, HTTP_STATUS_BAD_REQUEST, NULL);
104                                 try_reuse = true;
105
106                                 break;
107                         }
108
109                         int     ret = lws_serve_http_file(wsi, "./standalone.html", "text/html", NULL, 0);
110
111                         if ( (ret < 0) || ((ret > 0) && lws_http_transaction_completed(wsi)) ) {
112                                 // error or can't reuse connection, close the socket
113                                 return -1;
114                         }
115
116                         break;
117                 }
118
119                 case LWS_CALLBACK_HTTP_BODY_COMPLETION: {
120                         lws_return_http_status(wsi, HTTP_STATUS_OK, NULL);
121                         try_reuse = true;
122
123                         break;
124                 }
125
126                 case LWS_CALLBACK_HTTP_FILE_COMPLETION: {
127                         try_reuse = true;
128
129                         break;
130                 }
131
132                 default:
133                         break;
134         }
135
136         if (try_reuse) {
137                 if (lws_http_transaction_completed(wsi)) {
138                         return -1;
139                 }
140         }
141
142         return 0;
143 }
144
145 static int callback_standalone(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
146 {
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];
150         int rval;
151         int size;
152
153         switch (reason) {
154                 case LWS_CALLBACK_ESTABLISHED: {
155                         std_reset_standalone_gui();
156
157                         break;
158                 }
159
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");
164
165                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
166
167                                 if (rval < size) {
168                                         lwsl_err("ERROR sending reset command!\n");
169                                         return -1;
170                                 }
171
172                                 Standalone_update_flags &= ~STD_UFLAG_RESET;
173
174                                 lws_callback_on_writable(wsi);
175
176                                 break;
177                         }
178
179                         // general messages
180                         if (Standalone_update_flags & STD_UFLAG_TITLE) {
181                                 size = SDL_snprintf((char *)p, 64, "T:%s %d.%02d.%02d", XSTR("FreeSpace Standalone", 935), FS_VERSION_MAJOR, FS_VERSION_MINOR, FS_VERSION_BUILD);
182
183                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
184
185                                 if (rval < size) {
186                                         lwsl_err("ERROR sending title string!\n");
187                                         return -1;
188                                 }
189
190                                 Standalone_update_flags &= ~STD_UFLAG_TITLE;
191
192                                 lws_callback_on_writable(wsi);
193
194                                 break;
195                         }
196
197                         if (Standalone_update_flags & STD_UFLAG_DEBUG_STATE) {
198                                 size = SDL_snprintf((char *)p, 32, "D:%s", Standalone_debug_state.c_str());
199
200                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
201
202                                 if (rval < size) {
203                                         lwsl_err("ERROR sending debug state!\n");
204                                         return -1;
205                                 }
206
207                                 Standalone_update_flags &= ~STD_UFLAG_DEBUG_STATE;
208
209                                 lws_callback_on_writable(wsi);
210
211                                 break;
212                         }
213
214                         if (Standalone_update_flags & STD_UFLAG_POPUP) {
215                                 if ( !Standalone_popup_title.empty() ) {
216                                         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());
217                                 } else {
218                                         size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "popup ");
219                                 }
220
221                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
222
223                                 if (rval < size) {
224                                         lwsl_err("ERROR sending popup!\n");
225                                         return -1;
226                                 }
227
228                                 Standalone_update_flags &= ~STD_UFLAG_POPUP;
229
230                                 lws_callback_on_writable(wsi);
231
232                                 break;
233                         }
234
235                         // server tab
236                         if (Standalone_update_flags & STD_UFLAG_SERVER_NAME) {
237                                 size = SDL_snprintf((char *)p, MAX_GAMENAME_LEN, "S:name %s", Netgame.name);
238
239                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
240
241                                 if (rval < size) {
242                                         lwsl_err("ERROR sending server name!\n");
243                                         return -1;
244                                 }
245
246                                 Standalone_update_flags &= ~STD_UFLAG_SERVER_NAME;
247
248                                 lws_callback_on_writable(wsi);
249
250                                 break;
251                         }
252
253                         if (Standalone_update_flags & STD_UFLAG_HOST_PASS) {
254                                 size = SDL_snprintf((char *)p, STD_PASSWD_LEN, "S:pass %s", Multi_options_g.std_passwd);
255
256                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
257
258                                 if (rval < size) {
259                                         lwsl_err("ERROR sending host password!\n");
260                                         return -1;
261                                 }
262
263                                 Standalone_update_flags &= ~STD_UFLAG_HOST_PASS;
264
265                                 lws_callback_on_writable(wsi);
266
267                                 break;
268                         }
269
270                         if (Standalone_update_flags & STD_UFLAG_CONN) {
271                                 std::string conn_str;
272                                 char ip_address[60];
273
274                                 conn_str.reserve(1024);
275
276                                 for (int i = 0; i < MAX_PLAYERS; i++) {
277                                         net_player *np = &Net_players[i];
278
279                                         if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
280                                                 conn_str.append(np->player->callsign);
281                                                 conn_str.append(",");
282
283                                                 psnet_addr_to_string(ip_address, SDL_arraysize(ip_address), &np->p_info.addr);
284                                                 conn_str.append(ip_address);
285                                                 conn_str.append(",");
286
287                                                 if (np->s_info.ping.ping_avg > -1) {
288                                                         if (np->s_info.ping.ping_avg >= 1000) {
289                                                                 SDL_snprintf(ip_address, SDL_arraysize(ip_address), "%s", XSTR("> 1 sec", 914));
290                                                         } else {
291                                                                 SDL_snprintf(ip_address, SDL_arraysize(ip_address), "%d%s", np->s_info.ping.ping_avg, XSTR(" ms", 915));
292                                                         }
293                                                 }
294
295                                                 conn_str.append(";");
296                                         }
297                                 }
298
299                                 SDL_assert(conn_str.length() < 1024);
300
301                                 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "S:conn %s", conn_str.c_str());
302
303                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
304
305                                 if (rval < size) {
306                                         lwsl_err("ERROR sending connetions!\n");
307                                         return -1;
308                                 }
309
310                                 Standalone_update_flags &= ~STD_UFLAG_CONN;
311
312                                 lws_callback_on_writable(wsi);
313
314                                 break;
315                         }
316
317                         if ( (Standalone_update_flags & STD_UFLAG_SET_PING) && !Standalone_ping_str.empty() ) {
318                                 SDL_assert(Standalone_ping_str.length() < 1024);
319
320                                 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "S:ping %s", Standalone_ping_str.c_str());
321
322                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
323
324                                 if (rval < size) {
325                                         lwsl_err("ERROR sending conn ping!\n");
326                                         return -1;
327                                 }
328
329                                 Standalone_ping_str.clear();
330                                 Standalone_update_flags &= ~ STD_UFLAG_SET_PING;
331
332                                 lws_callback_on_writable(wsi);
333
334                                 break;
335                         }
336
337                         // multi-player tab
338                         if (Standalone_update_flags & STD_UFLAG_MISSION_NAME) {
339                                 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "M:name %s", Standalone_mission_name.c_str());
340
341                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
342
343                                 if (rval < size) {
344                                         lwsl_err("ERROR sending mission name!\n");
345                                         return -1;
346                                 }
347
348                                 Standalone_update_flags &= ~STD_UFLAG_MISSION_NAME;
349
350                                 lws_callback_on_writable(wsi);
351
352                                 break;
353                         }
354
355                         if (Standalone_update_flags & STD_UFLAG_MISSION_TIME) {
356                                 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "M:time %s", Standalone_mission_time.c_str());
357
358                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
359
360                                 if (rval < size) {
361                                         lwsl_err("ERROR sending mission time!\n");
362                                         return -1;
363                                 }
364
365                                 Standalone_update_flags &= ~STD_UFLAG_MISSION_TIME;
366
367                                 lws_callback_on_writable(wsi);
368
369                                 break;
370                         }
371
372                         if (Standalone_update_flags & STD_UFLAG_NETGAME_INFO) {
373                                 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "M:info %s", Standalone_netgame_info.c_str());
374
375                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
376
377                                 if (rval < size) {
378                                         lwsl_err("ERROR sending netgame info!\n");
379                                         return -1;
380                                 }
381
382                                 Standalone_update_flags &= ~STD_UFLAG_NETGAME_INFO;
383
384                                 lws_callback_on_writable(wsi);
385
386                                 break;
387                         }
388
389                         if (Standalone_update_flags & STD_UFLAG_FPS) {
390                                 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "M:fps %.1f", Standalone_fps);
391
392                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
393
394                                 if (rval < size) {
395                                         lwsl_err("ERROR sending fps!\n");
396                                         return -1;
397                                 }
398
399                                 Standalone_update_flags &= ~STD_UFLAG_FPS;
400
401                                 lws_callback_on_writable(wsi);
402
403                                 break;
404                         }
405
406                         if ( (Standalone_update_flags & STD_UFLAG_MISSION_GOALS) && !Standalone_mission_goals.empty() ) {
407                                 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "M:goal %s", Standalone_mission_goals.c_str());
408
409                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
410
411                                 if (rval < size) {
412                                         lwsl_err("ERROR sending mission goals!\n");
413                                         return -1;
414                                 }
415
416                                 Standalone_mission_goals.clear();
417                                 Standalone_update_flags &= ~STD_UFLAG_MISSION_GOALS;
418
419                                 lws_callback_on_writable(wsi);
420
421                                 break;
422                         }
423
424                         // player tab
425                         if ( (Standalone_update_flags & STD_UFLAG_PLAYER_INFO) && !Standalone_player_info.empty() ) {
426                                 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "P:info %s", Standalone_player_info.c_str());
427
428                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
429
430                                 if (rval < size) {
431                                         lwsl_err("ERROR sending player info!\n");
432                                         return -1;
433                                 }
434
435                                 Standalone_player_info.clear();
436                                 Standalone_update_flags &= ~STD_UFLAG_PLAYER_INFO;
437
438                                 lws_callback_on_writable(wsi);
439
440                                 break;
441                         }
442
443                         // god stuff tab
444                         if ( (Standalone_update_flags & STD_UFLAG_S_MESSAGE) && !Standalone_message.empty() ) {
445                                 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "G:mesg %s", Standalone_message.c_str());
446
447                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
448
449                                 if (rval < size) {
450                                         lwsl_err("ERROR sending chat message!\n");
451                                         return -1;
452                                 }
453
454                                 Standalone_message.clear();
455                                 Standalone_update_flags &= ~STD_UFLAG_S_MESSAGE;
456
457                                 lws_callback_on_writable(wsi);
458
459                                 break;
460                         }
461
462                         break;
463                 }
464
465                 case LWS_CALLBACK_RECEIVE: {
466                         if (in != NULL && len > 0) {
467                                 const char *msg = (const char *)in;
468                                 char mtype = msg[0];
469
470                                 if ( !SDL_strcmp(msg, "shutdown") ) {
471                                         gameseq_post_event(GS_EVENT_QUIT_GAME);
472                                         return -1;
473                                 }
474
475                                 // server tab
476                                 if (mtype == 'S') {
477                                         if (len >= 7) {
478                                                 if ( !SDL_strncmp(msg+2, "name ", 5) ) {
479                                                         SDL_strlcpy(Netgame.name, msg+7, SDL_arraysize(Netgame.name));
480                                                         SDL_strlcpy(Multi_options_g.std_pname, Netgame.name, SDL_arraysize(Multi_options_g.std_pname));
481                                                 } else if ( !SDL_strncmp(msg+2, "pass ", 5) ) {
482                                                         SDL_strlcpy(Multi_options_g.std_passwd, msg+7, SDL_arraysize(Multi_options_g.std_passwd));
483                                                 } else if ( !SDL_strncmp(msg+2, "kick ", 5) ) {
484                                                         char ip_string[60];
485
486                                                         for (int i = 0; i < MAX_PLAYERS; i++) {
487                                                                 if ( MULTI_CONNECTED(Net_players[i]) ) {
488                                                                         psnet_addr_to_string(ip_string, SDL_arraysize(ip_string), &Net_players[i].p_info.addr);
489
490                                                                         if ( !SDL_strcmp(msg+7, ip_string) ) {
491                                                                                 multi_kick_player(i, 0);
492
493                                                                                 break;
494                                                                         }
495                                                                 }
496                                                         }
497                                                 }
498                                         }
499                                 }
500                                 // multi-player tab
501                                 else if (mtype == 'M') {
502                                         if (len >= 6) {
503                                                 if ( !SDL_strncmp(msg+2, "fps ", 4) ) {
504                                                         int fps = SDL_atoi(msg+6);
505
506                                                         Multi_options_g.std_framecap = fps;
507                                                 }
508                                         }
509                                 }
510                                 // player info tab
511                                 else if (mtype == 'P') {
512                                         if (len >= 7) {
513                                                 if ( !SDL_strncmp(msg+2, "info ", 5) ) {
514                                                         int i;
515
516                                                         for (i = 0; i < MAX_PLAYERS; i++) {
517                                                                 net_player *np = &Net_players[i];
518
519                                                                 if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
520                                                                         if ( !SDL_strcmp(msg+7, np->player->callsign) ) {
521                                                                                 Standalone_pinfo_active_player = msg+7;
522                                                                                 std_pinfo_display_player_info(np);
523
524                                                                                 break;
525                                                                         }
526                                                                 }
527                                                         }
528
529                                                         if (i == MAX_PLAYERS) {
530                                                                 Standalone_pinfo_active_player.clear();
531                                                         }
532                                                 }
533                                         }
534                                 }
535                                 // god stuff tab
536                                 else if (mtype == 'G') {
537                                         if (len >= 7) {
538                                                 if ( !SDL_strncmp(msg+2, "smsg ", 5) ) {
539                                                         char txt[256];
540
541                                                         SDL_strlcpy(txt, msg+7, SDL_arraysize(txt));
542
543                                                         if (SDL_strlen(txt) > 0) {
544                                                                 send_game_chat_packet(Net_player, txt, MULTI_MSG_ALL, NULL);
545
546                                                                 std_add_chat_text(txt, MY_NET_PLAYER_NUM, 1);
547                                                         }
548                                                 } else if ( !SDL_strcmp(msg+2, "mrefresh") ) {
549                                                         if (MULTI_IS_TRACKER_GAME) {
550                                                                 cf_delete(MULTI_VALID_MISSION_FILE, CF_TYPE_DATA);
551
552                                                                 multi_update_valid_missions();
553                                                         }
554                                                 }
555                                         }
556                                 }
557                         }
558
559                         break;
560                 }
561
562                 default:
563                         break;
564         }
565
566         return 0;
567 }
568
569 static struct lws_protocols stand_protocols[] = {
570         {
571                 "http-only",
572                 callback_http,
573                 0,
574                 0
575         },
576         {
577                 "standalone",
578                 callback_standalone,
579                 0,
580                 0
581         },
582         // terminator
583         {
584                 NULL,
585                 NULL,
586                 0,
587                 0
588         }
589 };
590
591 static void std_lws_logger(int level, const char *line)
592 {
593         if (level & (LLL_WARN|LLL_ERR)) {
594                 mprintf(("STD: %s\n", line));
595         } else if (level & LLL_NOTICE) {
596                 nprintf(("lws", "STD: %s\n", line));
597         }
598 }
599
600
601
602
603 void std_deinit_standalone()
604 {
605         if (stand_context) {
606                 lws_cancel_service(stand_context);
607                 lws_context_destroy(stand_context);
608                 stand_context = NULL;
609         }
610 }
611
612 void std_init_standalone()
613 {
614         struct lws_context_creation_info info;
615
616         if (stand_context) {
617                 return;
618         }
619
620         SDL_zero(info);
621
622         info.port = Multi_options_g.port;
623
624         info.protocols = stand_protocols;
625
626         info.gid = -1;
627         info.uid = -1;
628
629         info.ka_time = 0;
630         info.ka_probes = 0;
631         info.ka_interval = 0;
632
633         lws_set_log_level(LLL_ERR|LLL_WARN|LLL_NOTICE, std_lws_logger);
634
635         stand_context = lws_create_context(&info);
636
637         if (stand_context == NULL) {
638                 Error(LOCATION, "Unable to initialize standalone server!");
639         }
640
641         atexit(std_deinit_standalone);
642
643         // turn off all sound and music
644         Cmdline_freespace_no_sound = 1;
645         Cmdline_freespace_no_music = 1;
646
647         std_reset_standalone_gui();
648
649         std_multi_update_netgame_info_controls();
650 }
651
652 void std_do_gui_frame()
653 {
654         // maybe update selected player stats
655         if ( ((Standalone_stats_stamp == -1) || timestamp_elapsed(Standalone_stats_stamp)) && !Standalone_pinfo_active_player.empty() ) {
656                 Standalone_stats_stamp = timestamp(STD_STATS_UPDATE_TIME);
657
658                 for (int i = 0; i < MAX_PLAYERS; i++) {
659                         net_player *np = &Net_players[i];
660
661                         if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
662                                 if ( !SDL_strcmp(Standalone_pinfo_active_player.c_str(), np->player->callsign) ) {
663                                         std_pinfo_display_player_info(np);
664
665                                         break;
666                                 }
667                         }
668                 }
669         }
670
671         // maybe update netgame info
672         if ( (Standalone_ng_stamp == -1) || timestamp_elapsed(Standalone_ng_stamp) ) {
673                 Standalone_ng_stamp = timestamp(STD_NG_UPDATE_TIME);
674
675                 std_multi_update_netgame_info_controls();
676         }
677
678         // update connection ping times
679         if ( ((Standalone_ping_stamp == -1) || timestamp_elapsed(Standalone_ping_stamp)) && !Standalone_ping_str.empty() ) {
680                 Standalone_ping_stamp = timestamp(STD_PING_UPDATE_TIME);
681                 Standalone_update_flags |= STD_UFLAG_SET_PING;
682         }
683
684         if (Standalone_update_flags) {
685                 lws_callback_on_writable_all_protocol(stand_context, &stand_protocols[1]);
686         }
687
688         lws_service(stand_context, 0);
689 }
690
691 void std_debug_set_standalone_state_string(const char *str)
692 {
693         Standalone_debug_state = str;
694
695         Standalone_update_flags |= STD_UFLAG_DEBUG_STATE;
696 }
697
698 void std_connect_set_gamename(const char *name)
699 {
700         if (name == NULL) {
701                 // if a permanent name exists, use that instead of the default
702                 if ( SDL_strlen(Multi_options_g.std_pname) ) {
703                         SDL_strlcpy(Netgame.name, Multi_options_g.std_pname, SDL_arraysize(Netgame.name));
704                 } else {
705                         SDL_strlcpy(Netgame.name, XSTR("Standalone Server", 916), SDL_arraysize(Netgame.name));
706                 }
707         } else {
708                 SDL_strlcpy(Netgame.name, name, SDL_arraysize(Netgame.name));
709         }
710
711         Standalone_update_flags |= STD_UFLAG_SERVER_NAME;
712 }
713
714 int std_connect_set_connect_count()
715 {
716         int count = 0;
717
718         for (int i = 0; i < MAX_PLAYERS; i++) {
719                 if (MULTI_CONNECTED(Net_players[i]) && (Net_player != &Net_players[i]) ) {
720                         count++;
721                 }
722         }
723
724         return count;
725 }
726
727 void std_add_player(net_player *p)
728 {
729         Standalone_update_flags |= STD_UFLAG_CONN;
730
731         // check to see if this guy is the host
732         std_connect_set_host_connect_status();
733 }
734
735 int std_remove_player(net_player *p)
736 {
737         int count;
738
739         Standalone_update_flags |= STD_UFLAG_CONN;
740
741         // update the host connect count
742         std_connect_set_host_connect_status();
743
744         // update the currently connected players
745         count = std_connect_set_connect_count();
746
747         if (count == 0) {
748                 multi_quit_game(PROMPT_NONE);
749                 return 1;
750         }
751
752         return 0;
753 }
754
755 void std_update_player_ping(net_player *p)
756 {
757         char ip_address[60];
758
759         if (p->s_info.ping.ping_avg > -1) {
760                 psnet_addr_to_string(ip_address, SDL_arraysize(ip_address), &p->p_info.addr);
761
762                 // only add it if address isn't already queued up
763                 if (Standalone_ping_str.find(ip_address) == std::string::npos) {
764                         Standalone_ping_str.append(ip_address);
765
766                         if (p->s_info.ping.ping_avg > 1000) {
767                                 SDL_snprintf(ip_address, SDL_arraysize(ip_address), ",%s;", XSTR("> 1 sec", 914));
768                         } else {
769                                 SDL_snprintf(ip_address, SDL_arraysize(ip_address), ",%d%s;", p->s_info.ping.ping_avg, XSTR(" ms", 915));
770                         }
771
772                         Standalone_ping_str.append(ip_address);
773                 }
774         }
775 }
776
777 void std_pinfo_display_player_info(net_player *p)
778 {
779         char sml_ping[30];
780
781         Standalone_player_info.clear();
782         Standalone_player_info.reserve(256);
783
784         // ship type
785         Standalone_player_info.append(Ship_info[p->p_info.ship_class].name);
786         Standalone_player_info.append(";");
787
788         // avg ping time
789         if (p->s_info.ping.ping_avg > 1000) {
790                 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%s", XSTR("> 1 sec", 914));
791         } else {
792                 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d%s", p->s_info.ping.ping_avg, XSTR(" ms", 915));
793         }
794
795         Standalone_player_info.append(sml_ping);
796         Standalone_player_info.append(";");
797
798         scoring_struct *ptr = &p->player->stats;
799
800         // all-time stats
801         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_shots_fired);
802         Standalone_player_info.append(sml_ping);
803         Standalone_player_info.append(",");
804         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_shots_hit);
805         Standalone_player_info.append(sml_ping);
806         Standalone_player_info.append(",");
807         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_bonehead_hits);
808         Standalone_player_info.append(sml_ping);
809         Standalone_player_info.append(",");
810         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);
811         Standalone_player_info.append(sml_ping);
812         Standalone_player_info.append(",");
813         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);
814         Standalone_player_info.append(sml_ping);
815         Standalone_player_info.append(",");
816         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_shots_fired);
817         Standalone_player_info.append(sml_ping);
818         Standalone_player_info.append(",");
819         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_shots_hit);
820         Standalone_player_info.append(sml_ping);
821         Standalone_player_info.append(",");
822         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_bonehead_hits);
823         Standalone_player_info.append(sml_ping);
824         Standalone_player_info.append(",");
825         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);
826         Standalone_player_info.append(sml_ping);
827         Standalone_player_info.append(",");
828         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);
829         Standalone_player_info.append(sml_ping);
830         Standalone_player_info.append(",");
831         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->assists);
832         Standalone_player_info.append(sml_ping);
833         Standalone_player_info.append(";");     // <- end of block
834
835         // mission stats
836         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_shots_fired);
837         Standalone_player_info.append(sml_ping);
838         Standalone_player_info.append(",");
839         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_shots_hit);
840         Standalone_player_info.append(sml_ping);
841         Standalone_player_info.append(",");
842         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_bonehead_hits);
843         Standalone_player_info.append(sml_ping);
844         Standalone_player_info.append(",");
845         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);
846         Standalone_player_info.append(sml_ping);
847         Standalone_player_info.append(",");
848         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);
849         Standalone_player_info.append(sml_ping);
850         Standalone_player_info.append(",");
851         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_shots_fired);
852         Standalone_player_info.append(sml_ping);
853         Standalone_player_info.append(",");
854         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_shots_hit);
855         Standalone_player_info.append(sml_ping);
856         Standalone_player_info.append(",");
857         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_bonehead_hits);
858         Standalone_player_info.append(sml_ping);
859         Standalone_player_info.append(",");
860         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);
861         Standalone_player_info.append(sml_ping);
862         Standalone_player_info.append(",");
863         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);
864         Standalone_player_info.append(sml_ping);
865         Standalone_player_info.append(",");
866         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->m_assists);
867         Standalone_player_info.append(sml_ping);
868
869         Standalone_update_flags |= STD_UFLAG_PLAYER_INFO;
870 }
871
872 void std_add_chat_text(const char *text, int player_index, int add_id)
873 {
874         char id[32];
875
876         if ( (player_index < 0) || (player_index >= MAX_PLAYERS) ) {
877                 return;
878         }
879
880         // format the chat text nicely
881         if (add_id) {
882                 if ( MULTI_STANDALONE(Net_players[player_index]) ) {
883                         SDL_snprintf(id, SDL_arraysize(id), XSTR("<SERVER> %s", 924), "");
884                 } else {
885                         SDL_snprintf(id, SDL_arraysize(id), "%s: ", Net_players[player_index].player->callsign);
886                 }
887
888                 Standalone_message.append(id);
889         }
890
891         Standalone_message.append(text);
892         Standalone_message.append("\n");
893
894         Standalone_update_flags |= STD_UFLAG_S_MESSAGE;
895 }
896
897 void std_reset_timestamps()
898 {
899         // reset the stats update stamp
900         Standalone_stats_stamp = timestamp(STD_STATS_UPDATE_TIME);
901
902         // reset the netgame controls update timestamp
903         Standalone_ng_stamp = timestamp(STD_NG_UPDATE_TIME);
904
905         // reset the ping update stamp
906         Standalone_ping_stamp = timestamp(STD_PING_UPDATE_TIME);
907
908         // reset fps update stamp
909         Standalone_fps_stamp = timestamp(STD_FPS_UPDATE_TIME);
910 }
911
912 void std_add_ban(const char *name)
913 {
914         if ( (name == NULL) || !SDL_strlen(name) ) {
915                 return;
916         }
917
918         if (Standalone_ban_list.size() >= STANDALONE_MAX_BAN) {
919                 return;
920         }
921
922         Standalone_ban_list.push_back(name);
923 }
924
925 int std_player_is_banned(const char *name)
926 {
927         if ( Standalone_ban_list.empty() ) {
928                 return 0;
929         }
930
931         for (size_t i = 0; i < Standalone_ban_list.size(); i++) {
932                 if ( !SDL_strcasecmp(name, Standalone_ban_list[i].c_str()) ) {
933                         return 1;
934                 }
935         }
936
937         return 0;
938 }
939
940 int std_is_host_passwd()
941 {
942         return (SDL_strlen(Multi_options_g.std_passwd) > 0) ? 1 : 0;
943 }
944
945 void std_multi_set_standalone_mission_name(const char *mission_name)
946 {
947         Standalone_mission_name = mission_name;
948         Standalone_update_flags |= STD_UFLAG_MISSION_NAME;
949 }
950
951 void std_multi_set_standalone_missiontime(float mission_time)
952 {
953         char txt[80];
954         char timestr[50];
955         fix m_time = fl2f(mission_time);
956
957         // format the time string and set the text
958         game_format_time(m_time, timestr, SDL_arraysize(timestr));
959         SDL_snprintf(txt, SDL_arraysize(txt), "%s  :  %.1f", timestr, mission_time);
960
961         Standalone_mission_time = txt;
962         Standalone_update_flags |= STD_UFLAG_MISSION_TIME;
963 }
964
965 void std_multi_update_netgame_info_controls()
966 {
967         char nginfo[50];
968
969         SDL_snprintf(nginfo, SDL_arraysize(nginfo), "%d,%d,%d,%d", Netgame.max_players, Netgame.options.max_observers, Netgame.security, Netgame.respawn);
970
971         Standalone_netgame_info = nginfo;
972         Standalone_update_flags |= STD_UFLAG_NETGAME_INFO;
973 }
974
975 void std_set_standalone_fps(float fps)
976 {
977         if ( (Standalone_fps_stamp == -1) || timestamp_elapsed(Standalone_fps_stamp) ) {
978                 Standalone_fps_stamp = timestamp(STD_FPS_UPDATE_TIME);
979
980                 Standalone_fps = fps;
981                 Standalone_update_flags |= STD_UFLAG_FPS;
982         }
983 }
984
985 void std_multi_setup_goal_tree()
986 {
987         std::string primary;
988         std::string secondary;
989         std::string bonus;
990         std::string status;
991
992         Standalone_mission_goals.clear();
993
994         for (int i = 0; i < Num_goals; i++) {
995                 switch (Mission_goals[i].satisfied) {
996                         case GOAL_FAILED: {
997                                 status = "f ";
998                                 break;
999                         }
1000
1001                         case GOAL_COMPLETE: {
1002                                 status = "c ";
1003                                 break;
1004                         }
1005
1006                         case GOAL_INCOMPLETE:
1007                         default: {
1008                                 status = "i ";
1009                                 break;
1010                         }
1011                 }
1012
1013                 switch (Mission_goals[i].type & GOAL_TYPE_MASK) {
1014                         case PRIMARY_GOAL: {
1015                                 primary.append(status);
1016                                 primary.append(Mission_goals[i].name);
1017                                 primary.append(",");
1018
1019                                 break;
1020                         }
1021
1022                         case SECONDARY_GOAL: {
1023                                 secondary.append(status);
1024                                 secondary.append(Mission_goals[i].name);
1025                                 secondary.append(",");
1026
1027                                 break;
1028                         }
1029
1030                         case BONUS_GOAL: {
1031                                 bonus.append(status);
1032                                 bonus.append(Mission_goals[i].name);
1033                                 bonus.append(",");
1034
1035                                 break;
1036                         }
1037
1038                         default:
1039                                 break;
1040                 }
1041         }
1042
1043         if ( primary.empty() ) {
1044                 Standalone_mission_goals.append("i none");
1045         } else {
1046                 Standalone_mission_goals.append(primary.substr(0, primary.size()-1));
1047         }
1048
1049         Standalone_mission_goals.append(";");
1050
1051         if ( secondary.empty() ) {
1052                 Standalone_mission_goals.append("i none");
1053         } else {
1054                 Standalone_mission_goals.append(secondary.substr(0, secondary.size()-1));
1055         }
1056
1057         Standalone_mission_goals.append(";");
1058
1059         if ( bonus.empty() ) {
1060                 Standalone_mission_goals.append("i none");
1061         } else {
1062                 Standalone_mission_goals.append(bonus.substr(0, bonus.size()-1));
1063         }
1064
1065         Standalone_update_flags |= STD_UFLAG_MISSION_GOALS;
1066 }
1067
1068 void std_multi_add_goals()
1069 {
1070         std_multi_setup_goal_tree();
1071 }
1072
1073 void std_multi_update_goals()
1074 {
1075         std_multi_setup_goal_tree();
1076 }
1077
1078 void std_reset_standalone_gui()
1079 {
1080         Standalone_stats_stamp = -1;
1081         Standalone_ng_stamp = -1;
1082         Standalone_ping_stamp = -1;
1083         Standalone_fps_stamp = -1;
1084
1085         Standalone_ping_str.clear();
1086         Standalone_player_info.clear();
1087         Standalone_message.clear();
1088         Standalone_pinfo_active_player.clear();
1089         Standalone_mission_name = "";
1090         Standalone_mission_time = "";
1091         Standalone_netgame_info.clear();
1092         Standalone_popup_title.clear();
1093         Standalone_popup_field1 = "";
1094         Standalone_popup_field2 = "";
1095
1096         std_set_standalone_fps(0.0f);
1097         std_multi_set_standalone_missiontime(0.0f);
1098
1099         Standalone_update_flags |= STD_UFLAG_ALL;
1100 }
1101
1102 void std_create_gen_dialog(const char *title)
1103 {
1104         Standalone_popup_title = title;
1105 }
1106
1107 void std_destroy_gen_dialog()
1108 {
1109         Standalone_popup_title.clear();
1110
1111         Standalone_update_flags |= STD_UFLAG_POPUP;
1112 }
1113
1114 void std_gen_set_text(const char *str, int field_num)
1115 {
1116         switch (field_num) {
1117                 case 0:
1118                         Standalone_popup_title = str;
1119                         break;
1120
1121                 case 1:
1122                         Standalone_popup_field1 = str;
1123                         break;
1124
1125                 case 2:
1126                         Standalone_popup_field2 = str;
1127                         break;
1128
1129                 default:
1130                         break;
1131         }
1132
1133         Standalone_update_flags |= STD_UFLAG_POPUP;
1134 }
1135
1136 void std_connect_set_host_connect_status()
1137 {
1138 }