]> icculus.org git repositories - taylor/freespace2.git/blob - src/network/stand_server.cpp
make first pass at websocket-based standalone server
[taylor/freespace2.git] / src / network / stand_server.cpp
1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell
5  * or otherwise commercially exploit the source or things you created based on the
6  * source.
7  *
8 */
9
10
11 #include "pstypes.h"
12 #include "osregistry.h"
13 #include "multi_options.h"
14 #include "gamesequence.h"
15 #include "timer.h"
16 #include "version.h"
17 #include "multi.h"
18 #include "stand_gui.h"
19 #include "multi_pmsg.h"
20 #include "multi_endgame.h"
21 #include "multimsgs.h"
22 #include "multiutil.h"
23
24 #include <libwebsockets.h>
25 #include <string>
26 #include <vector>
27
28
29
30 static std::string Standalone_debug_state;
31 static std::string Standalone_ping_str;
32 static std::string Standalone_player_info;
33 static std::string Standalone_message;
34
35 #define STANDALONE_MAX_BAN              50
36 static std::vector<std::string> Standalone_ban_list;
37
38 #define STD_STATS_UPDATE_TIME           500             // ms between updating player stats
39 #define STD_NG_UPDATE_TIME                      100             // ms between updating netgame information
40 #define STD_PING_UPDATE_TIME            1000    // ms between updating pings
41
42 static int Standalone_stats_stamp = -1;
43 static int Standalone_ng_stamp = -1;
44 static int Standalone_ping_stamp = -1;
45
46 static int Standalone_update_flags = 0;
47
48 #define STD_UFLAG_DEBUG_STATE           (1<<0)
49 #define STD_UFLAG_TITLE                         (1<<1)
50 #define STD_UFLAG_CONN                          (1<<2)
51
52 #define STD_UFLAG_SERVER_NAME           (1<<3)
53 #define STD_UFLAG_HOST_PASS                     (1<<4)
54 #define STD_UFLAG_SET_PING                      (1<<5)
55
56 #define STD_UFLAG_PLAYER_INFO           (1<<6)
57
58 #define STD_UFLAG_S_MESSAGE                     (1<<7)
59
60 #define STD_UFLAG_GENERAL                       (STD_UFLAG_DEBUG_STATE|STD_UFLAG_TITLE)
61 #define STD_UFLAG_TAB_SERVER            (STD_UFLAG_SERVER_NAME|STD_UFLAG_HOST_PASS|STD_UFLAG_CONN|STD_UFLAG_SET_PING)
62 #define STD_UFLAG_TAB_PLAYER            (STD_UFLAG_PLAYER_INFO)
63 #define STD_UFLAG_TAB_GS                        (STD_UFLAG_S_MESSAGE)
64
65 #define STD_UFLAG_ALL                           (STD_UFLAG_GENERAL|STD_UFLAG_TAB_SERVER|STD_UFLAG_TAB_PLAYER)
66
67
68 static lws_context *stand_context = NULL;
69
70
71 static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
72 {
73         bool try_reuse = false;
74
75         switch (reason) {
76                 case LWS_CALLBACK_HTTP: {
77                         if (len < 1) {
78                                 lws_return_http_status(wsi, HTTP_STATUS_BAD_REQUEST, NULL);
79                                 try_reuse = true;
80
81                                 break;
82                         }
83
84                         int     ret = lws_serve_http_file(wsi, "./standalone.html", "text/html", NULL, 0);
85
86                         if ( (ret < 0) || ((ret > 0) && lws_http_transaction_completed(wsi)) ) {
87                                 // error or can't reuse connection, close the socket
88                                 return -1;
89                         }
90
91                         break;
92                 }
93
94                 case LWS_CALLBACK_HTTP_BODY_COMPLETION: {
95                         lws_return_http_status(wsi, HTTP_STATUS_OK, NULL);
96                         try_reuse = true;
97
98                         break;
99                 }
100
101                 case LWS_CALLBACK_HTTP_FILE_COMPLETION: {
102                         try_reuse = true;
103
104                         break;
105                 }
106
107                 default:
108                         break;
109         }
110
111         if (try_reuse) {
112                 if (lws_http_transaction_completed(wsi)) {
113                         return -1;
114                 }
115         }
116
117         return 0;
118 }
119
120 std::string conn_test[] = {
121         "Appolo 13,127.0.0.1,13 ms;Gemini,10.1.1.1,;HelloKitty,192.168.0.3,> 1 sec;"
122 };
123 std::string ping_test[] = {
124         "127.0.0.1,37 ms;10.1.1.1,999 ms;"
125 };
126
127 static int callback_standalone(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
128 {
129         #define MAX_BUF_SIZE    1050
130         unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + MAX_BUF_SIZE + LWS_SEND_BUFFER_POST_PADDING];
131         unsigned char *p = &buf[LWS_SEND_BUFFER_PRE_PADDING];
132         int rval;
133         int size;
134
135         switch (reason) {
136                 case LWS_CALLBACK_ESTABLISHED: {
137                         Standalone_stats_stamp = -1;
138                         Standalone_ng_stamp = -1;
139
140                         Standalone_update_flags = STD_UFLAG_ALL;
141
142                         break;
143                 }
144
145                 case LWS_CALLBACK_SERVER_WRITEABLE: {
146                         if (Standalone_update_flags & STD_UFLAG_TITLE) {
147                                 size = SDL_snprintf((char *)p, 64, "T:%s %d.%02d.%02d", XSTR("FreeSpace Standalone", 935), FS_VERSION_MAJOR, FS_VERSION_MINOR, FS_VERSION_BUILD);
148
149                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
150
151                                 if (rval < size) {
152                                         lwsl_err("ERROR sending title string!\n");
153                                         return -1;
154                                 }
155
156                                 Standalone_update_flags &= ~STD_UFLAG_TITLE;
157                         }
158
159                         if (Standalone_update_flags & STD_UFLAG_DEBUG_STATE) {
160                                 size = SDL_snprintf((char *)p, 32, "D:%s", Standalone_debug_state.c_str());
161
162                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
163
164                                 if (rval < size) {
165                                         lwsl_err("ERROR sending debug state!\n");
166                                         return -1;
167                                 }
168
169                                 Standalone_update_flags &= ~STD_UFLAG_DEBUG_STATE;
170                         }
171
172                         if (Standalone_update_flags & STD_UFLAG_SERVER_NAME) {
173                                 size = SDL_snprintf((char *)p, MAX_GAMENAME_LEN, "S:name %s", Netgame.name);
174
175                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
176
177                                 if (rval < size) {
178                                         lwsl_err("ERROR sending debug state!\n");
179                                         return -1;
180                                 }
181
182                                 Standalone_update_flags &= ~STD_UFLAG_SERVER_NAME;
183                         }
184
185                         if (Standalone_update_flags & STD_UFLAG_HOST_PASS) {
186                                 size = SDL_snprintf((char *)p, STD_PASSWD_LEN, "S:pass %s", Multi_options_g.std_passwd);
187
188                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
189
190                                 if (rval < size) {
191                                         lwsl_err("ERROR sending debug state!\n");
192                                         return -1;
193                                 }
194
195                                 Standalone_update_flags &= ~STD_UFLAG_HOST_PASS;
196                         }
197
198                         if (Standalone_update_flags & STD_UFLAG_CONN) {
199                                 std::string conn_str;
200                                 char ip_address[60];
201
202                                 conn_str.reserve(1024);
203
204                                 for (int i = 0; i < MAX_PLAYERS; i++) {
205                                         net_player *np = &Net_players[i];
206
207                                         if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
208                                                 conn_str.append(np->player->callsign);
209                                                 conn_str.append(",");
210
211                                                 psnet_addr_to_string(ip_address, SDL_arraysize(ip_address), &np->p_info.addr);
212                                                 conn_str.append(ip_address);
213                                                 conn_str.append(",");
214
215                                                 if (np->s_info.ping.ping_avg > -1) {
216                                                         if (np->s_info.ping.ping_avg >= 1000) {
217                                                                 SDL_snprintf(ip_address, SDL_arraysize(ip_address), "%s", XSTR("> 1 sec", 914));
218                                                         } else {
219                                                                 SDL_snprintf(ip_address, SDL_arraysize(ip_address), "%d%s", np->s_info.ping.ping_avg, XSTR(" ms", 915));
220                                                         }
221                                                 }
222
223                                                 conn_str.append(";");
224                                         }
225                                 }
226
227                                 SDL_assert(conn_str.length() < 1024);
228
229                                 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "S:conn %s", conn_str.c_str());
230
231                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
232
233                                 if (rval < size) {
234                                         lwsl_err("ERROR sending debug state!\n");
235                                         return -1;
236                                 }
237
238                                 Standalone_update_flags &= ~STD_UFLAG_CONN;
239                         }
240
241                         if ( (Standalone_update_flags & STD_UFLAG_SET_PING) && !Standalone_ping_str.empty() ) {
242                                 SDL_assert(Standalone_ping_str.length() < 1024);
243
244                                 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "S:ping %s", Standalone_ping_str.c_str());
245
246                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
247
248                                 if (rval < size) {
249                                         lwsl_err("ERROR sending debug state!\n");
250                                         return -1;
251                                 }
252
253                                 Standalone_ping_str.clear();
254                                 Standalone_update_flags &= ~ STD_UFLAG_SET_PING;
255                         }
256
257                         if ( (Standalone_update_flags & STD_UFLAG_PLAYER_INFO) && !Standalone_player_info.empty() ) {
258                                 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "P:info %s", Standalone_player_info.c_str());
259
260                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
261
262                                 if (rval < size) {
263                                         lwsl_err("ERROR sending debug state!\n");
264                                         return -1;
265                                 }
266
267                                 Standalone_player_info.clear();
268                                 Standalone_update_flags &= ~STD_UFLAG_PLAYER_INFO;
269                         }
270
271                         if ( (Standalone_update_flags & STD_UFLAG_S_MESSAGE) && !Standalone_message.empty() ) {
272                                 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "P:info %s", Standalone_message.c_str());
273
274                                 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
275
276                                 if (rval < size) {
277                                         lwsl_err("ERROR sending debug state!\n");
278                                         return -1;
279                                 }
280
281                                 Standalone_message.clear();
282                                 Standalone_update_flags &= ~STD_UFLAG_S_MESSAGE;
283                         }
284
285                         break;
286                 }
287
288                 case LWS_CALLBACK_RECEIVE: {
289                         if (in != NULL && len > 0) {
290                                 const char *msg = (const char *)in;
291                                 char mtype = msg[0];
292
293                                 if ( !SDL_strcmp(msg, "shutdown") ) {
294                                         gameseq_post_event(GS_EVENT_QUIT_GAME);
295                                         return -1;
296                                 }
297
298                                 // server tab
299                                 if (mtype == 'S') {
300                                         if (len >= 7) {
301                                                 if ( !SDL_strncmp(msg+2, "name ", 5) ) {
302                                                         SDL_strlcpy(Netgame.name, msg+7, SDL_arraysize(Netgame.name));
303                                                         SDL_strlcpy(Multi_options_g.std_pname, Netgame.name, SDL_arraysize(Multi_options_g.std_pname));
304                                                 } else if ( !SDL_strncmp(msg+2, "pass ", 5) ) {
305                                                         SDL_strlcpy(Multi_options_g.std_passwd, msg+7, SDL_arraysize(Multi_options_g.std_passwd));
306                                                 }
307                                         }
308                                 }
309                                 // multi-player tab
310                                 else if (mtype == 'M') {
311
312                                 }
313                                 // player info tab
314                                 else if (mtype == 'P') {
315                                         if (len >= 7) {
316                                                 if ( !SDL_strncmp(msg+2, "info ", 5) ) {
317                                                         for (int i = 0; i < MAX_PLAYERS; i++) {
318                                                                 net_player *np = &Net_players[i];
319
320                                                                 if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
321                                                                         if ( !SDL_strcmp(msg+7, np->player->callsign) ) {
322                                                                                 std_pinfo_display_player_info(np);
323
324                                                                                 break;
325                                                                         }
326                                                                 }
327                                                         }
328                                                 }
329                                         }
330                                 }
331                                 // god stuff tab
332                                 else if (mtype == 'G') {
333                                         if (len >= 7) {
334                                                 if ( !SDL_strncmp(msg+2, "smsg ", 5) ) {
335                                                         char txt[256];
336
337                                                         SDL_strlcpy(txt, msg+7, SDL_arraysize(txt));
338
339                                                         if (SDL_strlen(txt) > 0) {
340                                                                 send_game_chat_packet(Net_player, txt, MULTI_MSG_ALL, NULL);
341
342                                                                 std_add_chat_text(txt, MY_NET_PLAYER_NUM, 1);
343                                                         }
344                                                 } else if ( !SDL_strcmp(msg+2, "mrefresh") ) {
345                                                         if (MULTI_IS_TRACKER_GAME) {
346                                                                 cf_delete(MULTI_VALID_MISSION_FILE, CF_TYPE_DATA);
347
348                                                                 multi_update_valid_missions();
349                                                         }
350                                                 }
351                                         }
352                                 } else if (mtype == 'T') {
353
354                                 }
355                         }
356
357                         break;
358                 }
359
360                 default:
361                         break;
362         }
363
364         return 0;
365 }
366
367 static struct lws_protocols stand_protocols[] = {
368         {
369                 "http-only",
370                 callback_http,
371                 0,
372                 0
373         },
374         {
375                 "standalone",
376                 callback_standalone,
377                 0,
378                 0
379         },
380         // terminator
381         {
382                 NULL,
383                 NULL,
384                 0,
385                 0
386         }
387 };
388
389
390
391
392 void std_deinit_standalone()
393 {
394         if (stand_context) {
395                 lws_cancel_service(stand_context);
396                 lws_context_destroy(stand_context);
397                 stand_context = NULL;
398         }
399 }
400
401 void std_init_standalone()
402 {
403         struct lws_context_creation_info info;
404
405         if (stand_context) {
406                 return;
407         }
408
409         SDL_zero(info);
410
411         info.port = Multi_options_g.port;
412
413         info.protocols = stand_protocols;
414
415         info.gid = -1;
416         info.uid = -1;
417
418         info.ka_time = 0;
419         info.ka_probes = 0;
420         info.ka_interval = 0;
421
422         stand_context = lws_create_context(&info);
423
424         if (stand_context == NULL) {
425                 SDL_assert_always(1);
426         }
427
428         Standalone_stats_stamp = -1;
429         Standalone_ng_stamp = -1;
430         Standalone_ping_stamp = -1;
431
432         Standalone_update_flags = STD_UFLAG_ALL;
433 }
434
435 void std_do_gui_frame()
436 {
437         // maybe update selected player stats
438         if ( (Standalone_stats_stamp == -1) || timestamp_elapsed(Standalone_stats_stamp) ) {
439                 Standalone_stats_stamp = timestamp(STD_STATS_UPDATE_TIME);
440         }
441
442         // maybe update netgame info
443         if ( (Standalone_ng_stamp == -1) || timestamp_elapsed(Standalone_ng_stamp) ) {
444                 Standalone_ng_stamp = timestamp(STD_NG_UPDATE_TIME);
445         }
446
447         // update connection ping times
448         if ( !Standalone_ping_str.empty() && ((Standalone_ping_stamp == -1) || timestamp_elapsed(Standalone_ping_stamp)) ) {
449                 Standalone_ping_stamp = timestamp(STD_PING_UPDATE_TIME);
450                 Standalone_update_flags |= STD_UFLAG_SET_PING;
451         }
452
453         if (Standalone_update_flags) {
454                 lws_callback_on_writable_all_protocol(stand_context, &stand_protocols[1]);
455         }
456
457         lws_service(stand_context, 0);
458 }
459
460 void std_debug_set_standalone_state_string(const char *str)
461 {
462         Standalone_debug_state = str;
463
464         Standalone_update_flags |= STD_UFLAG_DEBUG_STATE;
465 }
466
467 void std_connect_set_gamename(const char *name)
468 {
469         if (name == NULL) {
470                 // if a permanent name exists, use that instead of the default
471                 if ( SDL_strlen(Multi_options_g.std_pname) ) {
472                         SDL_strlcpy(Netgame.name, Multi_options_g.std_pname, SDL_arraysize(Netgame.name));
473                 } else {
474                         SDL_strlcpy(Netgame.name, XSTR("Standalone Server", 916), SDL_arraysize(Netgame.name));
475                 }
476         } else {
477                 SDL_strlcpy(Netgame.name, name, SDL_arraysize(Netgame.name));
478         }
479
480         Standalone_update_flags |= STD_UFLAG_SERVER_NAME;
481 }
482
483 int std_connect_set_connect_count()
484 {
485         int count = 0;
486
487         for (int i = 0; i < MAX_PLAYERS; i++) {
488                 if (MULTI_CONNECTED(Net_players[i]) && (Net_player != &Net_players[i]) ) {
489                         count++;
490                 }
491         }
492
493         return count;
494 }
495
496 void std_add_player(net_player *p)
497 {
498         Standalone_update_flags |= STD_UFLAG_CONN;
499
500         // check to see if this guy is the host
501         std_connect_set_host_connect_status();
502 }
503
504 int std_remove_player(net_player *p)
505 {
506         int count;
507
508         Standalone_update_flags |= STD_UFLAG_CONN;
509
510         // update the host connect count
511         std_connect_set_host_connect_status();
512
513         // update the currently connected players
514         count = std_connect_set_connect_count();
515
516         if (count == 0) {
517                 multi_quit_game(PROMPT_NONE);
518                 return 1;
519         }
520
521         return 0;
522 }
523
524 void std_update_player_ping(net_player *p)
525 {
526         char ip_address[60];
527
528         if (p->s_info.ping.ping_avg > -1) {
529                 psnet_addr_to_string(ip_address, SDL_arraysize(ip_address), &p->p_info.addr);
530
531                 Standalone_ping_str.append(ip_address);
532
533                 if (p->s_info.ping.ping_avg > 1000) {
534                         SDL_snprintf(ip_address, SDL_arraysize(ip_address), ",%s;", XSTR("> 1 sec", 914));
535                 } else {
536                         SDL_snprintf(ip_address, SDL_arraysize(ip_address), ",%d%s;", p->s_info.ping.ping_avg, XSTR(" ms", 915));
537                 }
538
539                 Standalone_ping_str.append(ip_address);
540         }
541 }
542
543 void std_pinfo_display_player_info(net_player *p)
544 {
545         char sml_ping[30];
546
547         Standalone_player_info.clear();
548         Standalone_player_info.reserve(256);
549
550         // ship type
551         Standalone_player_info.append(Ship_info[p->p_info.ship_class].name);
552         Standalone_player_info.append(";");
553
554         // avg ping time
555         if (p->s_info.ping.ping_avg > 1000) {
556                 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%s", XSTR("> 1 sec", 914));
557         } else {
558                 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d%s", p->s_info.ping.ping_avg, XSTR(" ms", 915));
559         }
560
561         Standalone_player_info.append(sml_ping);
562         Standalone_player_info.append(";");
563
564         scoring_struct *ptr = &p->player->stats;
565
566         // all-time stats
567         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_shots_fired);
568         Standalone_player_info.append(sml_ping);
569         Standalone_player_info.append(",");
570         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_shots_hit);
571         Standalone_player_info.append(sml_ping);
572         Standalone_player_info.append(",");
573         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_bonehead_hits);
574         Standalone_player_info.append(sml_ping);
575         Standalone_player_info.append(",");
576         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", (int)(100.0f * ((float)ptr->p_shots_hit / (float)ptr->p_shots_fired)));
577         Standalone_player_info.append(sml_ping);
578         Standalone_player_info.append(",");
579         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", (int)(100.0f * ((float)ptr->p_bonehead_hits / (float)ptr->p_shots_fired)));
580         Standalone_player_info.append(sml_ping);
581         Standalone_player_info.append(",");
582         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_shots_fired);
583         Standalone_player_info.append(sml_ping);
584         Standalone_player_info.append(",");
585         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_shots_hit);
586         Standalone_player_info.append(sml_ping);
587         Standalone_player_info.append(",");
588         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_bonehead_hits);
589         Standalone_player_info.append(sml_ping);
590         Standalone_player_info.append(",");
591         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", (int)(100.0f * ((float)ptr->s_shots_hit / (float)ptr->s_shots_fired)));
592         Standalone_player_info.append(sml_ping);
593         Standalone_player_info.append(",");
594         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", (int)(100.0f * ((float)ptr->s_bonehead_hits / (float)ptr->s_shots_fired)));
595         Standalone_player_info.append(sml_ping);
596         Standalone_player_info.append(",");
597         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->assists);
598         Standalone_player_info.append(sml_ping);
599         Standalone_player_info.append(";");     // <- end of block
600
601         // mission stats
602         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_shots_fired);
603         Standalone_player_info.append(sml_ping);
604         Standalone_player_info.append(",");
605         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_shots_hit);
606         Standalone_player_info.append(sml_ping);
607         Standalone_player_info.append(",");
608         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_bonehead_hits);
609         Standalone_player_info.append(sml_ping);
610         Standalone_player_info.append(",");
611         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", (int)(100.0f * ((float)ptr->mp_shots_hit / (float)ptr->mp_shots_fired)));
612         Standalone_player_info.append(sml_ping);
613         Standalone_player_info.append(",");
614         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", (int)(100.0f * ((float)ptr->mp_bonehead_hits / (float)ptr->mp_shots_fired)));
615         Standalone_player_info.append(sml_ping);
616         Standalone_player_info.append(",");
617         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_shots_fired);
618         Standalone_player_info.append(sml_ping);
619         Standalone_player_info.append(",");
620         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_shots_hit);
621         Standalone_player_info.append(sml_ping);
622         Standalone_player_info.append(",");
623         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_bonehead_hits);
624         Standalone_player_info.append(sml_ping);
625         Standalone_player_info.append(",");
626         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", (int)(100.0f * ((float)ptr->ms_shots_hit / (float)ptr->ms_shots_fired)));
627         Standalone_player_info.append(sml_ping);
628         Standalone_player_info.append(",");
629         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", (int)(100.0f * ((float)ptr->ms_bonehead_hits / (float)ptr->ms_shots_fired)));
630         Standalone_player_info.append(sml_ping);
631         Standalone_player_info.append(",");
632         SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->m_assists);
633         Standalone_player_info.append(sml_ping);
634
635         Standalone_update_flags |= STD_UFLAG_PLAYER_INFO;
636 }
637
638 void std_add_chat_text(const char *text, int player_index, int add_id)
639 {
640         char id[32];
641
642         if ( (player_index < 0) || (player_index >= MAX_PLAYERS) ) {
643                 return;
644         }
645
646         // format the chat text nicely
647         if (add_id) {
648                 if ( MULTI_STANDALONE(Net_players[player_index]) ) {
649                         SDL_snprintf(id, SDL_arraysize(id), XSTR("<SERVER> %s", 924), "");
650                 } else {
651                         SDL_snprintf(id, SDL_arraysize(id), "%s: ", Net_players[player_index].player->callsign);
652                 }
653
654                 Standalone_message.append(id);
655         }
656
657         Standalone_message.append(text);
658         Standalone_message.append("\n");
659
660         Standalone_update_flags |= STD_UFLAG_S_MESSAGE;
661 }
662
663 void std_reset_timestamps()
664 {
665         // reset the stats update stamp
666         Standalone_stats_stamp = timestamp(STD_STATS_UPDATE_TIME);
667
668         // reset the netgame controls update timestamp
669         Standalone_ng_stamp = timestamp(STD_NG_UPDATE_TIME);
670
671         // reset the ping update stamp
672         Standalone_ping_stamp = timestamp(STD_PING_UPDATE_TIME);
673 }
674
675 void std_add_ban(const char *name)
676 {
677         if ( (name == NULL) || !SDL_strlen(name) ) {
678                 return;
679         }
680
681         if (Standalone_ban_list.size() >= STANDALONE_MAX_BAN) {
682                 return;
683         }
684
685         Standalone_ban_list.push_back(name);
686 }
687
688 int std_player_is_banned(const char *name)
689 {
690         if ( Standalone_ban_list.empty() ) {
691                 return 0;
692         }
693
694         for (size_t i = 0; i < Standalone_ban_list.size(); i++) {
695                 if ( !SDL_strcasecmp(name, Standalone_ban_list[i].c_str()) ) {
696                         return 1;
697                 }
698         }
699
700         return 0;
701 }
702
703 int std_is_host_passwd()
704 {
705         return (SDL_strlen(Multi_options_g.std_passwd) > 0) ? 1 : 0;
706 }
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724 int std_pinfo_maybe_update_player_info(net_player *p)
725 {
726         STUB_FUNCTION;
727
728         return 0;
729 }
730
731 void std_connect_set_host_connect_status()
732 {
733         STUB_FUNCTION;
734 }
735
736 void std_create_gen_dialog(const char *title)
737 {
738         STUB_FUNCTION;
739 }
740
741 void std_destroy_gen_dialog()
742 {
743         STUB_FUNCTION;
744 }
745
746 void std_gen_set_text(const char *str, int field_num)
747 {
748         STUB_FUNCTION;
749 }
750
751 void std_multi_add_goals()
752 {
753         STUB_FUNCTION;
754 }
755
756 void std_multi_set_standalone_mission_name(const char *mission_name)
757 {
758         STUB_FUNCTION;
759 }
760
761 void std_multi_set_standalone_missiontime(float mission_time)
762 {
763         STUB_FUNCTION;
764 }
765
766 void std_multi_setup_goal_tree()
767 {
768         STUB_FUNCTION;
769 }
770
771 void std_multi_update_goals()
772 {
773         STUB_FUNCTION;
774 }
775
776 void std_multi_update_netgame_info_controls()
777 {
778         STUB_FUNCTION;
779 }
780
781 void std_reset_standalone_gui()
782 {
783         STUB_FUNCTION;
784 }
785
786 void std_set_standalone_fps(float fps)
787 {
788         STUB_FUNCTION;
789 }