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