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