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