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 static int callback_standalone(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
122 #define MAX_BUF_SIZE 1050
123 unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + MAX_BUF_SIZE + LWS_SEND_BUFFER_POST_PADDING];
124 unsigned char *p = &buf[LWS_SEND_BUFFER_PRE_PADDING];
129 case LWS_CALLBACK_ESTABLISHED: {
130 Standalone_stats_stamp = -1;
131 Standalone_ng_stamp = -1;
133 Standalone_update_flags = STD_UFLAG_ALL;
138 case LWS_CALLBACK_SERVER_WRITEABLE: {
139 if (Standalone_update_flags & STD_UFLAG_TITLE) {
140 size = SDL_snprintf((char *)p, 64, "T:%s %d.%02d.%02d", XSTR("FreeSpace Standalone", 935), FS_VERSION_MAJOR, FS_VERSION_MINOR, FS_VERSION_BUILD);
142 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
145 lwsl_err("ERROR sending title string!\n");
149 Standalone_update_flags &= ~STD_UFLAG_TITLE;
152 if (Standalone_update_flags & STD_UFLAG_DEBUG_STATE) {
153 size = SDL_snprintf((char *)p, 32, "D:%s", Standalone_debug_state.c_str());
155 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
158 lwsl_err("ERROR sending debug state!\n");
162 Standalone_update_flags &= ~STD_UFLAG_DEBUG_STATE;
165 if (Standalone_update_flags & STD_UFLAG_SERVER_NAME) {
166 size = SDL_snprintf((char *)p, MAX_GAMENAME_LEN, "S:name %s", Netgame.name);
168 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
171 lwsl_err("ERROR sending debug state!\n");
175 Standalone_update_flags &= ~STD_UFLAG_SERVER_NAME;
178 if (Standalone_update_flags & STD_UFLAG_HOST_PASS) {
179 size = SDL_snprintf((char *)p, STD_PASSWD_LEN, "S:pass %s", Multi_options_g.std_passwd);
181 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
184 lwsl_err("ERROR sending debug state!\n");
188 Standalone_update_flags &= ~STD_UFLAG_HOST_PASS;
191 if (Standalone_update_flags & STD_UFLAG_CONN) {
192 std::string conn_str;
195 conn_str.reserve(1024);
197 for (int i = 0; i < MAX_PLAYERS; i++) {
198 net_player *np = &Net_players[i];
200 if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
201 conn_str.append(np->player->callsign);
202 conn_str.append(",");
204 psnet_addr_to_string(ip_address, SDL_arraysize(ip_address), &np->p_info.addr);
205 conn_str.append(ip_address);
206 conn_str.append(",");
208 if (np->s_info.ping.ping_avg > -1) {
209 if (np->s_info.ping.ping_avg >= 1000) {
210 SDL_snprintf(ip_address, SDL_arraysize(ip_address), "%s", XSTR("> 1 sec", 914));
212 SDL_snprintf(ip_address, SDL_arraysize(ip_address), "%d%s", np->s_info.ping.ping_avg, XSTR(" ms", 915));
216 conn_str.append(";");
220 SDL_assert(conn_str.length() < 1024);
222 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "S:conn %s", conn_str.c_str());
224 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
227 lwsl_err("ERROR sending debug state!\n");
231 Standalone_update_flags &= ~STD_UFLAG_CONN;
234 if ( (Standalone_update_flags & STD_UFLAG_SET_PING) && !Standalone_ping_str.empty() ) {
235 SDL_assert(Standalone_ping_str.length() < 1024);
237 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "S:ping %s", Standalone_ping_str.c_str());
239 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
242 lwsl_err("ERROR sending debug state!\n");
246 Standalone_ping_str.clear();
247 Standalone_update_flags &= ~ STD_UFLAG_SET_PING;
250 if ( (Standalone_update_flags & STD_UFLAG_PLAYER_INFO) && !Standalone_player_info.empty() ) {
251 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "P:info %s", Standalone_player_info.c_str());
253 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
256 lwsl_err("ERROR sending debug state!\n");
260 Standalone_player_info.clear();
261 Standalone_update_flags &= ~STD_UFLAG_PLAYER_INFO;
264 if ( (Standalone_update_flags & STD_UFLAG_S_MESSAGE) && !Standalone_message.empty() ) {
265 size = SDL_snprintf((char *)p, MAX_BUF_SIZE, "P:info %s", Standalone_message.c_str());
267 rval = lws_write(wsi, p, size, LWS_WRITE_TEXT);
270 lwsl_err("ERROR sending debug state!\n");
274 Standalone_message.clear();
275 Standalone_update_flags &= ~STD_UFLAG_S_MESSAGE;
281 case LWS_CALLBACK_RECEIVE: {
282 if (in != NULL && len > 0) {
283 const char *msg = (const char *)in;
286 if ( !SDL_strcmp(msg, "shutdown") ) {
287 gameseq_post_event(GS_EVENT_QUIT_GAME);
294 if ( !SDL_strncmp(msg+2, "name ", 5) ) {
295 SDL_strlcpy(Netgame.name, msg+7, SDL_arraysize(Netgame.name));
296 SDL_strlcpy(Multi_options_g.std_pname, Netgame.name, SDL_arraysize(Multi_options_g.std_pname));
297 } else if ( !SDL_strncmp(msg+2, "pass ", 5) ) {
298 SDL_strlcpy(Multi_options_g.std_passwd, msg+7, SDL_arraysize(Multi_options_g.std_passwd));
303 else if (mtype == 'M') {
307 else if (mtype == 'P') {
309 if ( !SDL_strncmp(msg+2, "info ", 5) ) {
310 for (int i = 0; i < MAX_PLAYERS; i++) {
311 net_player *np = &Net_players[i];
313 if ( MULTI_CONNECTED((*np)) && (Net_player != np) ) {
314 if ( !SDL_strcmp(msg+7, np->player->callsign) ) {
315 std_pinfo_display_player_info(np);
325 else if (mtype == 'G') {
327 if ( !SDL_strncmp(msg+2, "smsg ", 5) ) {
330 SDL_strlcpy(txt, msg+7, SDL_arraysize(txt));
332 if (SDL_strlen(txt) > 0) {
333 send_game_chat_packet(Net_player, txt, MULTI_MSG_ALL, NULL);
335 std_add_chat_text(txt, MY_NET_PLAYER_NUM, 1);
337 } else if ( !SDL_strcmp(msg+2, "mrefresh") ) {
338 if (MULTI_IS_TRACKER_GAME) {
339 cf_delete(MULTI_VALID_MISSION_FILE, CF_TYPE_DATA);
341 multi_update_valid_missions();
358 static struct lws_protocols stand_protocols[] = {
383 void std_deinit_standalone()
386 lws_cancel_service(stand_context);
387 lws_context_destroy(stand_context);
388 stand_context = NULL;
392 void std_init_standalone()
394 struct lws_context_creation_info info;
402 info.port = Multi_options_g.port;
404 info.protocols = stand_protocols;
411 info.ka_interval = 0;
413 stand_context = lws_create_context(&info);
415 if (stand_context == NULL) {
416 SDL_assert_always(1);
419 Standalone_stats_stamp = -1;
420 Standalone_ng_stamp = -1;
421 Standalone_ping_stamp = -1;
423 Standalone_update_flags = STD_UFLAG_ALL;
426 void std_do_gui_frame()
428 // maybe update selected player stats
429 if ( (Standalone_stats_stamp == -1) || timestamp_elapsed(Standalone_stats_stamp) ) {
430 Standalone_stats_stamp = timestamp(STD_STATS_UPDATE_TIME);
433 // maybe update netgame info
434 if ( (Standalone_ng_stamp == -1) || timestamp_elapsed(Standalone_ng_stamp) ) {
435 Standalone_ng_stamp = timestamp(STD_NG_UPDATE_TIME);
438 // update connection ping times
439 if ( !Standalone_ping_str.empty() && ((Standalone_ping_stamp == -1) || timestamp_elapsed(Standalone_ping_stamp)) ) {
440 Standalone_ping_stamp = timestamp(STD_PING_UPDATE_TIME);
441 Standalone_update_flags |= STD_UFLAG_SET_PING;
444 if (Standalone_update_flags) {
445 lws_callback_on_writable_all_protocol(stand_context, &stand_protocols[1]);
448 lws_service(stand_context, 0);
451 void std_debug_set_standalone_state_string(const char *str)
453 Standalone_debug_state = str;
455 Standalone_update_flags |= STD_UFLAG_DEBUG_STATE;
458 void std_connect_set_gamename(const char *name)
461 // if a permanent name exists, use that instead of the default
462 if ( SDL_strlen(Multi_options_g.std_pname) ) {
463 SDL_strlcpy(Netgame.name, Multi_options_g.std_pname, SDL_arraysize(Netgame.name));
465 SDL_strlcpy(Netgame.name, XSTR("Standalone Server", 916), SDL_arraysize(Netgame.name));
468 SDL_strlcpy(Netgame.name, name, SDL_arraysize(Netgame.name));
471 Standalone_update_flags |= STD_UFLAG_SERVER_NAME;
474 int std_connect_set_connect_count()
478 for (int i = 0; i < MAX_PLAYERS; i++) {
479 if (MULTI_CONNECTED(Net_players[i]) && (Net_player != &Net_players[i]) ) {
487 void std_add_player(net_player *p)
489 Standalone_update_flags |= STD_UFLAG_CONN;
491 // check to see if this guy is the host
492 std_connect_set_host_connect_status();
495 int std_remove_player(net_player *p)
499 Standalone_update_flags |= STD_UFLAG_CONN;
501 // update the host connect count
502 std_connect_set_host_connect_status();
504 // update the currently connected players
505 count = std_connect_set_connect_count();
508 multi_quit_game(PROMPT_NONE);
515 void std_update_player_ping(net_player *p)
519 if (p->s_info.ping.ping_avg > -1) {
520 psnet_addr_to_string(ip_address, SDL_arraysize(ip_address), &p->p_info.addr);
522 Standalone_ping_str.append(ip_address);
524 if (p->s_info.ping.ping_avg > 1000) {
525 SDL_snprintf(ip_address, SDL_arraysize(ip_address), ",%s;", XSTR("> 1 sec", 914));
527 SDL_snprintf(ip_address, SDL_arraysize(ip_address), ",%d%s;", p->s_info.ping.ping_avg, XSTR(" ms", 915));
530 Standalone_ping_str.append(ip_address);
534 void std_pinfo_display_player_info(net_player *p)
538 Standalone_player_info.clear();
539 Standalone_player_info.reserve(256);
542 Standalone_player_info.append(Ship_info[p->p_info.ship_class].name);
543 Standalone_player_info.append(";");
546 if (p->s_info.ping.ping_avg > 1000) {
547 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%s", XSTR("> 1 sec", 914));
549 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d%s", p->s_info.ping.ping_avg, XSTR(" ms", 915));
552 Standalone_player_info.append(sml_ping);
553 Standalone_player_info.append(";");
555 scoring_struct *ptr = &p->player->stats;
558 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_shots_fired);
559 Standalone_player_info.append(sml_ping);
560 Standalone_player_info.append(",");
561 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_shots_hit);
562 Standalone_player_info.append(sml_ping);
563 Standalone_player_info.append(",");
564 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->p_bonehead_hits);
565 Standalone_player_info.append(sml_ping);
566 Standalone_player_info.append(",");
567 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", (int)(100.0f * ((float)ptr->p_shots_hit / (float)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", (int)(100.0f * ((float)ptr->p_bonehead_hits / (float)ptr->p_shots_fired)));
571 Standalone_player_info.append(sml_ping);
572 Standalone_player_info.append(",");
573 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_shots_fired);
574 Standalone_player_info.append(sml_ping);
575 Standalone_player_info.append(",");
576 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_shots_hit);
577 Standalone_player_info.append(sml_ping);
578 Standalone_player_info.append(",");
579 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->s_bonehead_hits);
580 Standalone_player_info.append(sml_ping);
581 Standalone_player_info.append(",");
582 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", (int)(100.0f * ((float)ptr->s_shots_hit / (float)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", (int)(100.0f * ((float)ptr->s_bonehead_hits / (float)ptr->s_shots_fired)));
586 Standalone_player_info.append(sml_ping);
587 Standalone_player_info.append(",");
588 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->assists);
589 Standalone_player_info.append(sml_ping);
590 Standalone_player_info.append(";"); // <- end of block
593 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_shots_fired);
594 Standalone_player_info.append(sml_ping);
595 Standalone_player_info.append(",");
596 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_shots_hit);
597 Standalone_player_info.append(sml_ping);
598 Standalone_player_info.append(",");
599 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->mp_bonehead_hits);
600 Standalone_player_info.append(sml_ping);
601 Standalone_player_info.append(",");
602 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", (int)(100.0f * ((float)ptr->mp_shots_hit / (float)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", (int)(100.0f * ((float)ptr->mp_bonehead_hits / (float)ptr->mp_shots_fired)));
606 Standalone_player_info.append(sml_ping);
607 Standalone_player_info.append(",");
608 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_shots_fired);
609 Standalone_player_info.append(sml_ping);
610 Standalone_player_info.append(",");
611 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_shots_hit);
612 Standalone_player_info.append(sml_ping);
613 Standalone_player_info.append(",");
614 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->ms_bonehead_hits);
615 Standalone_player_info.append(sml_ping);
616 Standalone_player_info.append(",");
617 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", (int)(100.0f * ((float)ptr->ms_shots_hit / (float)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", (int)(100.0f * ((float)ptr->ms_bonehead_hits / (float)ptr->ms_shots_fired)));
621 Standalone_player_info.append(sml_ping);
622 Standalone_player_info.append(",");
623 SDL_snprintf(sml_ping, SDL_arraysize(sml_ping), "%d", ptr->m_assists);
624 Standalone_player_info.append(sml_ping);
626 Standalone_update_flags |= STD_UFLAG_PLAYER_INFO;
629 void std_add_chat_text(const char *text, int player_index, int add_id)
633 if ( (player_index < 0) || (player_index >= MAX_PLAYERS) ) {
637 // format the chat text nicely
639 if ( MULTI_STANDALONE(Net_players[player_index]) ) {
640 SDL_snprintf(id, SDL_arraysize(id), XSTR("<SERVER> %s", 924), "");
642 SDL_snprintf(id, SDL_arraysize(id), "%s: ", Net_players[player_index].player->callsign);
645 Standalone_message.append(id);
648 Standalone_message.append(text);
649 Standalone_message.append("\n");
651 Standalone_update_flags |= STD_UFLAG_S_MESSAGE;
654 void std_reset_timestamps()
656 // reset the stats update stamp
657 Standalone_stats_stamp = timestamp(STD_STATS_UPDATE_TIME);
659 // reset the netgame controls update timestamp
660 Standalone_ng_stamp = timestamp(STD_NG_UPDATE_TIME);
662 // reset the ping update stamp
663 Standalone_ping_stamp = timestamp(STD_PING_UPDATE_TIME);
666 void std_add_ban(const char *name)
668 if ( (name == NULL) || !SDL_strlen(name) ) {
672 if (Standalone_ban_list.size() >= STANDALONE_MAX_BAN) {
676 Standalone_ban_list.push_back(name);
679 int std_player_is_banned(const char *name)
681 if ( Standalone_ban_list.empty() ) {
685 for (size_t i = 0; i < Standalone_ban_list.size(); i++) {
686 if ( !SDL_strcasecmp(name, Standalone_ban_list[i].c_str()) ) {
694 int std_is_host_passwd()
696 return (SDL_strlen(Multi_options_g.std_passwd) > 0) ? 1 : 0;
715 int std_pinfo_maybe_update_player_info(net_player *p)
722 void std_connect_set_host_connect_status()
727 void std_create_gen_dialog(const char *title)
732 void std_destroy_gen_dialog()
737 void std_gen_set_text(const char *str, int field_num)
742 void std_multi_add_goals()
747 void std_multi_set_standalone_mission_name(const char *mission_name)
752 void std_multi_set_standalone_missiontime(float mission_time)
757 void std_multi_setup_goal_tree()
762 void std_multi_update_goals()
767 void std_multi_update_netgame_info_controls()
772 void std_reset_standalone_gui()
777 void std_set_standalone_fps(float fps)