2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
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
12 #include "osregistry.h"
13 #include "multi_options.h"
14 #include "gamesequence.h"
18 #include "stand_gui.h"
19 #include "multi_pmsg.h"
20 #include "multi_endgame.h"
21 #include "multimsgs.h"
22 #include "multiutil.h"
24 #include <libwebsockets.h>
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;
35 #define STANDALONE_MAX_BAN 50
36 static std::vector<std::string> Standalone_ban_list;
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
42 static int Standalone_stats_stamp = -1;
43 static int Standalone_ng_stamp = -1;
44 static int Standalone_ping_stamp = -1;
46 static int Standalone_update_flags = 0;
48 #define STD_UFLAG_DEBUG_STATE (1<<0)
49 #define STD_UFLAG_TITLE (1<<1)
50 #define STD_UFLAG_CONN (1<<2)
52 #define STD_UFLAG_SERVER_NAME (1<<3)
53 #define STD_UFLAG_HOST_PASS (1<<4)
54 #define STD_UFLAG_SET_PING (1<<5)
56 #define STD_UFLAG_PLAYER_INFO (1<<6)
58 #define STD_UFLAG_S_MESSAGE (1<<7)
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)
65 #define STD_UFLAG_ALL (STD_UFLAG_GENERAL|STD_UFLAG_TAB_SERVER|STD_UFLAG_TAB_PLAYER)
68 static lws_context *stand_context = NULL;
71 static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
73 bool try_reuse = false;
76 case LWS_CALLBACK_HTTP: {
78 lws_return_http_status(wsi, HTTP_STATUS_BAD_REQUEST, NULL);
84 int ret = lws_serve_http_file(wsi, "./standalone.html", "text/html", NULL, 0);
86 if ( (ret < 0) || ((ret > 0) && lws_http_transaction_completed(wsi)) ) {
87 // error or can't reuse connection, close the socket
94 case LWS_CALLBACK_HTTP_BODY_COMPLETION: {
95 lws_return_http_status(wsi, HTTP_STATUS_OK, NULL);
101 case LWS_CALLBACK_HTTP_FILE_COMPLETION: {
112 if (lws_http_transaction_completed(wsi)) {
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;"
123 std::string ping_test[] = {
124 "127.0.0.1,37 ms;10.1.1.1,999 ms;"
127 static int callback_standalone(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
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];
136 case LWS_CALLBACK_ESTABLISHED: {
137 Standalone_stats_stamp = -1;
138 Standalone_ng_stamp = -1;
140 Standalone_update_flags = STD_UFLAG_ALL;
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);
149 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
152 lwsl_err("ERROR sending title string!\n");
156 Standalone_update_flags &= ~STD_UFLAG_TITLE;
159 if (Standalone_update_flags & STD_UFLAG_DEBUG_STATE) {
160 size = SDL_snprintf((char *)p, 32, "D:%s", Standalone_debug_state.c_str());
162 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
165 lwsl_err("ERROR sending debug state!\n");
169 Standalone_update_flags &= ~STD_UFLAG_DEBUG_STATE;
172 if (Standalone_update_flags & STD_UFLAG_SERVER_NAME) {
173 size = SDL_snprintf((char *)p, MAX_GAMENAME_LEN, "S:name %s", Netgame.name);
175 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
178 lwsl_err("ERROR sending debug state!\n");
182 Standalone_update_flags &= ~STD_UFLAG_SERVER_NAME;
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);
188 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
191 lwsl_err("ERROR sending debug state!\n");
195 Standalone_update_flags &= ~STD_UFLAG_HOST_PASS;
198 if (Standalone_update_flags & STD_UFLAG_CONN) {
199 std::string conn_str;
202 conn_str.reserve(1024);
204 for (int i = 0; i < MAX_PLAYERS; i++) {
205 net_player *np = &Net_players[i];
207 if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
208 conn_str.append(np->player->callsign);
209 conn_str.append(",");
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(",");
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));
219 SDL_snprintf(ip_address, SDL_arraysize(ip_address), "%d%s", np->s_info.ping.ping_avg, XSTR(" ms", 915));
223 conn_str.append(";");
227 SDL_assert(conn_str.length() < 1024);
229 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "S:conn %s", conn_str.c_str());
231 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
234 lwsl_err("ERROR sending debug state!\n");
238 Standalone_update_flags &= ~STD_UFLAG_CONN;
241 if ( (Standalone_update_flags & STD_UFLAG_SET_PING) && !Standalone_ping_str.empty() ) {
242 SDL_assert(Standalone_ping_str.length() < 1024);
244 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "S:ping %s", Standalone_ping_str.c_str());
246 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
249 lwsl_err("ERROR sending debug state!\n");
253 Standalone_ping_str.clear();
254 Standalone_update_flags &= ~ STD_UFLAG_SET_PING;
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());
260 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
263 lwsl_err("ERROR sending debug state!\n");
267 Standalone_player_info.clear();
268 Standalone_update_flags &= ~STD_UFLAG_PLAYER_INFO;
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());
274 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
277 lwsl_err("ERROR sending debug state!\n");
281 Standalone_message.clear();
282 Standalone_update_flags &= ~STD_UFLAG_S_MESSAGE;
288 case LWS_CALLBACK_RECEIVE: {
289 if (in != NULL && len > 0) {
290 const char *msg = (const char *)in;
293 if ( !SDL_strcmp(msg, "shutdown") ) {
294 gameseq_post_event(GS_EVENT_QUIT_GAME);
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));
310 else if (mtype == 'M') {
314 else if (mtype == 'P') {
316 if ( !SDL_strncmp(msg+2, "info ", 5) ) {
317 for (int i = 0; i < MAX_PLAYERS; i++) {
318 net_player *np = &Net_players[i];
320 if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
321 if ( !SDL_strcmp(msg+7, np->player->callsign) ) {
322 std_pinfo_display_player_info(np);
332 else if (mtype == 'G') {
334 if ( !SDL_strncmp(msg+2, "smsg ", 5) ) {
337 SDL_strlcpy(txt, msg+7, SDL_arraysize(txt));
339 if (SDL_strlen(txt) > 0) {
340 send_game_chat_packet(Net_player, txt, MULTI_MSG_ALL, NULL);
342 std_add_chat_text(txt, MY_NET_PLAYER_NUM, 1);
344 } else if ( !SDL_strcmp(msg+2, "mrefresh") ) {
345 if (MULTI_IS_TRACKER_GAME) {
346 cf_delete(MULTI_VALID_MISSION_FILE, CF_TYPE_DATA);
348 multi_update_valid_missions();
352 } else if (mtype == 'T') {
367 static struct lws_protocols stand_protocols[] = {
392 void std_deinit_standalone()
395 lws_cancel_service(stand_context);
396 lws_context_destroy(stand_context);
397 stand_context = NULL;
401 void std_init_standalone()
403 struct lws_context_creation_info info;
411 info.port = Multi_options_g.port;
413 info.protocols = stand_protocols;
420 info.ka_interval = 0;
422 stand_context = lws_create_context(&info);
424 if (stand_context == NULL) {
425 SDL_assert_always(1);
428 Standalone_stats_stamp = -1;
429 Standalone_ng_stamp = -1;
430 Standalone_ping_stamp = -1;
432 Standalone_update_flags = STD_UFLAG_ALL;
435 void std_do_gui_frame()
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);
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);
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;
453 if (Standalone_update_flags) {
454 lws_callback_on_writable_all_protocol(stand_context, &stand_protocols[1]);
457 lws_service(stand_context, 0);
460 void std_debug_set_standalone_state_string(const char *str)
462 Standalone_debug_state = str;
464 Standalone_update_flags |= STD_UFLAG_DEBUG_STATE;
467 void std_connect_set_gamename(const char *name)
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));
474 SDL_strlcpy(Netgame.name, XSTR("Standalone Server", 916), SDL_arraysize(Netgame.name));
477 SDL_strlcpy(Netgame.name, name, SDL_arraysize(Netgame.name));
480 Standalone_update_flags |= STD_UFLAG_SERVER_NAME;
483 int std_connect_set_connect_count()
487 for (int i = 0; i < MAX_PLAYERS; i++) {
488 if (MULTI_CONNECTED(Net_players[i]) && (Net_player != &Net_players[i]) ) {
496 void std_add_player(net_player *p)
498 Standalone_update_flags |= STD_UFLAG_CONN;
500 // check to see if this guy is the host
501 std_connect_set_host_connect_status();
504 int std_remove_player(net_player *p)
508 Standalone_update_flags |= STD_UFLAG_CONN;
510 // update the host connect count
511 std_connect_set_host_connect_status();
513 // update the currently connected players
514 count = std_connect_set_connect_count();
517 multi_quit_game(PROMPT_NONE);
524 void std_update_player_ping(net_player *p)
528 if (p->s_info.ping.ping_avg > -1) {
529 psnet_addr_to_string(ip_address, SDL_arraysize(ip_address), &p->p_info.addr);
531 Standalone_ping_str.append(ip_address);
533 if (p->s_info.ping.ping_avg > 1000) {
534 SDL_snprintf(ip_address, SDL_arraysize(ip_address), ",%s;", XSTR("> 1 sec", 914));
536 SDL_snprintf(ip_address, SDL_arraysize(ip_address), ",%d%s;", p->s_info.ping.ping_avg, XSTR(" ms", 915));
539 Standalone_ping_str.append(ip_address);
543 void std_pinfo_display_player_info(net_player *p)
547 Standalone_player_info.clear();
548 Standalone_player_info.reserve(256);
551 Standalone_player_info.append(Ship_info[p->p_info.ship_class].name);
552 Standalone_player_info.append(";");
555 if (p->s_info.ping.ping_avg > 1000) {
556 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%s", XSTR("> 1 sec", 914));
558 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d%s", p->s_info.ping.ping_avg, XSTR(" ms", 915));
561 Standalone_player_info.append(sml_ping);
562 Standalone_player_info.append(";");
564 scoring_struct *ptr = &p->player->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
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);
635 Standalone_update_flags |= STD_UFLAG_PLAYER_INFO;
638 void std_add_chat_text(const char *text, int player_index, int add_id)
642 if ( (player_index < 0) || (player_index >= MAX_PLAYERS) ) {
646 // format the chat text nicely
648 if ( MULTI_STANDALONE(Net_players[player_index]) ) {
649 SDL_snprintf(id, SDL_arraysize(id), XSTR("<SERVER> %s", 924), "");
651 SDL_snprintf(id, SDL_arraysize(id), "%s: ", Net_players[player_index].player->callsign);
654 Standalone_message.append(id);
657 Standalone_message.append(text);
658 Standalone_message.append("\n");
660 Standalone_update_flags |= STD_UFLAG_S_MESSAGE;
663 void std_reset_timestamps()
665 // reset the stats update stamp
666 Standalone_stats_stamp = timestamp(STD_STATS_UPDATE_TIME);
668 // reset the netgame controls update timestamp
669 Standalone_ng_stamp = timestamp(STD_NG_UPDATE_TIME);
671 // reset the ping update stamp
672 Standalone_ping_stamp = timestamp(STD_PING_UPDATE_TIME);
675 void std_add_ban(const char *name)
677 if ( (name == NULL) || !SDL_strlen(name) ) {
681 if (Standalone_ban_list.size() >= STANDALONE_MAX_BAN) {
685 Standalone_ban_list.push_back(name);
688 int std_player_is_banned(const char *name)
690 if ( Standalone_ban_list.empty() ) {
694 for (size_t i = 0; i < Standalone_ban_list.size(); i++) {
695 if ( !SDL_strcasecmp(name, Standalone_ban_list[i].c_str()) ) {
703 int std_is_host_passwd()
705 return (SDL_strlen(Multi_options_g.std_passwd) > 0) ? 1 : 0;
724 int std_pinfo_maybe_update_player_info(net_player *p)
731 void std_connect_set_host_connect_status()
736 void std_create_gen_dialog(const char *title)
741 void std_destroy_gen_dialog()
746 void std_gen_set_text(const char *str, int field_num)
751 void std_multi_add_goals()
756 void std_multi_set_standalone_mission_name(const char *mission_name)
761 void std_multi_set_standalone_missiontime(float mission_time)
766 void std_multi_setup_goal_tree()
771 void std_multi_update_goals()
776 void std_multi_update_netgame_info_controls()
781 void std_reset_standalone_gui()
786 void std_set_standalone_fps(float fps)