Files
Cesarus85--ringfall-mjolnir-vr/source/networking/network_server_manager.c
T
Stefan MaierandClaude Opus 5.5 fc2b6551ca Merge upstream (OpenCE 13c14df9): online co-op campaign, online split screen, network version 16
Upstream's online co-op (network_actors.c, network_coop.c) ticks beside the
VR port's VR relay and bots; its message types end at 77, below the VR
port's from 240. The look and move flags the action test reads moved into
player_control_action_test_note: the head's turning goes there too. The
vertex shader's screen-space undo is upstream's on every platform now, with
the multiview eye offset after it on Android. The APK carries upstream's
brokers.txt beside the FDM layer stub; the updaters keep the VR port's own
repository (upstream's now points at OpenCommunityEdition/OpenCE).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:22:05 +02:00

5072 lines
159 KiB
C

/*
NETWORK_SERVER_MANAGER.C
symbols in this file:
0011B5D0 0030:
_countdown_timer_update (0000)
0011B600 0060:
_countdown_timer_get_time_remaining (0000)
0011B660 0090:
_countdown_timer_increment (0000)
0011B6F0 0090:
_countdown_timer_decrement (0000)
0011B780 0040:
_countdown_timer_set_time_remaining (0000)
0011B7C0 0070:
_network_game_server_set_game_name (0000)
0011B830 0040:
_network_game_server_get_game_name (0000)
0011B870 0040:
_network_game_server_get_state (0000)
0011B8B0 0050:
_network_game_server_open_game (0000)
0011B950 0060:
_network_game_server_game_is_open (0000)
0011B9B0 0060:
_network_game_server_game_is_valid (0000)
0011BA10 00d0:
_network_game_server_send_player_quit_messages_ingame (0000)
0011BAE0 00e0:
_network_game_server_start_network_game (0000)
0011BBC0 00a0:
_network_game_server_switch_to_postgame (0000)
0011BC60 00f0:
_network_game_server_graceful_shutdown (0000)
0011BD50 0060:
_network_game_server_client_machine_is_joined_to_game (0000)
0011BDB0 0130:
_network_game_server_accept_client_machine_into_game (0000)
0011BEE0 0040:
_player_name_is_unique (0000)
0011BF20 0080:
_get_unique_random_name (0000)
0011BFA0 0090:
_get_unique_random_color (0000)
0011C030 0140:
_network_game_server_add_player_to_game (0000)
0011C170 00e0:
_network_game_server_remove_player_from_game (0000)
0011C250 00a0:
_network_game_server_adjust_machine_settings (0000)
0011C2F0 0080:
_network_game_server_all_machines_have_loaded (0000)
0011C370 00c0:
_network_game_server_client_machine_game_loading_complete (0000)
0011C430 0030:
_network_game_server_client_machine_is_precached (0000)
0011C460 0150:
_network_game_server_handle_client_update_packet (0000)
0011C5B0 0050:
_network_game_server_switch_machine_from_postgame_to_pregame (0000)
0011C600 01b0:
_network_game_server_update_ticks (0000)
0011C7B0 0070:
_network_game_server_queue_player_for_addition (0000)
0011C820 0070:
_network_game_server_begin_game_start_countdown (0000)
0011C890 0080:
_server_needs_more_teams (0000)
0011C910 0090:
_server_has_a_player_on_each_machine (0000)
0011C9A0 0070:
_server_has_enough_machines (0000)
0011CA10 0050:
_server_ok_to_countdown (0000)
0011CA60 0040:
_network_game_server_invalidate_network_machine (0000)
0011CAA0 00a0:
_network_game_generate_join_game_token (0000)
0011CB40 0090:
_network_game_server_get_client_machine (0000)
0011CBD0 0030:
_network_game_server_get_connection (0000)
0011CC00 0020:
_network_game_server_get_client_connection (0000)
0011CC20 0080:
_network_game_server_get_machine_connection (0000)
0011CCA0 0050:
_network_game_server_get_client_machine_at_index (0000)
0011CCF0 00d0:
_network_game_server_get_client_machine_at_address (0000)
0011CDC0 0040:
_network_game_server_get_game (0000)
0011CE90 0050:
_network_game_server_game_can_start (0000)
0011CEE0 0060:
_network_game_server_pause_countdown (0000)
0011CF40 0100:
_network_game_server_change_map_name (0000)
0011D040 0090:
_network_game_server_change_game_variant (0000)
0011D0D0 0170:
_code_0011d0d0 (0000)
0011D240 00a0:
_code_0011d240 (0000)
0011D2E0 00a0:
_network_game_server_send_rejection_message (0000)
0011D380 0030:
_network_game_server_reject_connection_game_is_full (0000)
0011D3B0 0050:
_code_0011d3b0 (0000)
0011D400 0070:
_code_0011d400 (0000)
0011D470 00d0:
_code_0011d470 (0000)
0011D540 0030:
_network_game_server_get_client_machine_count (0000)
0011D570 0100:
_dump_network_game_data (0000)
0011D670 0130:
_network_game_server_dump (0000)
0011D7A0 0140:
_network_game_server_remove_client_machine_from_game (0000)
0011D8E0 0140:
_network_game_server_remove_machine_from_game (0000)
0011DBF0 01e0:
_network_game_server_update_countdown (0000)
0011DDD0 01d0:
_code_0011ddd0 (0000)
0011DFA0 02b0:
_code_0011dfa0 (0000)
0011E250 0120:
_network_game_server_dispose (0000)
0011E370 0180:
_network_game_server_idle (0000)
0011E4F0 0250:
_network_game_server_reset_to_pregame (0000)
0011E740 0150:
_network_game_server_create (0000)
00285104 001b:
??_C@_0BL@DEKPBLAE@timer?9?$DOtime_remaining?5?$DO?$DN?50?$AA@ (0000)
00285120 0033:
??_C@_0DD@CFCGIJJL@c?3?2halo?2SOURCE?2networking?2networ@ (0000)
00285154 0010:
??_C@_0BA@LLGNDOEI@adjustment?5?$DO?$DN?50?$AA@ (0000)
00285164 0007:
??_C@_06HJHJCKIO@server?$AA@ (0000)
0028516C 000d:
??_C@_0N@JPPODHNK@opening?5game?$AA@ (0000)
0028517C 000d:
??_C@_0N@DMKJHMIA@closing?5game?$AA@ (0000)
0028518C 0032:
??_C@_0DC@GCAPAPEJ@?$CITRUE?5?$DN?$DN?5game_is_open?$CJ?5?$HM?$HM?5?$CIFALSE@ (0000)
002851C0 0034:
??_C@_0DE@CFAOOLOB@?$CITRUE?5?$DN?$DN?5game_is_valid?$CJ?5?$HM?$HM?5?$CIFALS@ (0000)
002851F8 0086:
??_C@_0IG@MNBBLADP@network_game_server_send_message@ (0000)
00285280 0024:
??_C@_0CE@IKCKJLGP@sending?5quit?5out?5of?5game?0?5time?5?$DN@ (0000)
002852A4 0033:
??_C@_0DD@OJCFEAMH@_network_game_server_state_ingam@ (0000)
002852D8 0043:
??_C@_0ED@MDKIICLM@failed?5to?5signal?5client?5machines@ (0000)
0028531C 003d:
??_C@_0DN@JMGGBBEG@signalling?5client?5machines?5to?5be@ (0000)
0028535C 003a:
??_C@_0DK@JICNHAFB@failed?5to?5create?5a?5_message_type@ (0000)
00285398 003b:
??_C@_0DL@ICMNGBFO@failed?5to?5signal?5all?5client?5mach@ (0000)
002853D4 002d:
??_C@_0CN@DJJDFOFP@server?5sent?5message_game_over?5to@ (0000)
00285408 0045:
??_C@_0EF@JHLALKAD@server?5going?5down?0?5but?5failed?5to@ (0000)
00285450 0040:
??_C@_0EA@EJEKFFFL@server?5closing?5down?$DL?5all?5client?5@ (0000)
00285490 003d:
??_C@_0DN@KLHIBLCI@failed?5to?5create?5a?5message_serve@ (0000)
002854D0 003e:
??_C@_0DO@IAODKJJI@failed?5to?5create?5a?5message_serve@ (0000)
00285510 0008:
??_C@_07MHDNFCJE@machine?$AA@ (0000)
00285518 006e:
??_C@_0GO@BGKBFJIE@network_game_server_accept_clien@ (0000)
00285588 005b:
??_C@_0FL@EIIOKBIG@network_game_add_machine?$CI?$CJ?5faile@ (0000)
002855E4 003b:
??_C@_0DL@MGNGFOBG@server?5added?5machine?5?$EA?5?$CFs?5to?5the@ (0000)
00285620 004c:
??_C@_0EM@PJMBGAIG@client?5machine?5tried?5to?5add?5a?5pl@ (0000)
00285670 004d:
??_C@_0EN@DNDECHNF@network_game_add_player?$CI?$CJ?5failed@ (0000)
002856C0 0049:
??_C@_0EJ@LCGCJMBE@server?5added?5player?5from?5machine@ (0000)
00285710 004f:
??_C@_0EP@COKGKJNJ@client?5machine?5tried?5to?5remove?5a@ (0000)
00285760 0055:
??_C@_0FF@BKKCLGKI@network_game_remove_player?$CI?$CJ?5fai@ (0000)
002857B8 004d:
??_C@_0EN@LDDPDGNO@server?5removed?5player?5from?5machi@ (0000)
00285808 004d:
??_C@_0EN@KMKKCDOM@client?5machine?5tried?5to?5update?5i@ (0000)
00285858 0056:
??_C@_0FG@IPMBAPCA@network_game_update_machine?$CI?$CJ?5fa@ (0000)
002858B0 0024:
??_C@_0CE@NCDPADPN@server?5updated?5machine?5?$CD?$CFd?5setti@ (0000)
002858D4 0029:
??_C@_0CJ@ECFINOOJ@server?5?$CG?$CG?5machine?5?$CG?$CG?5machine_des@ (0000)
00285900 001b:
??_C@_0BL@PFJPBEPF@local?5game?5data?5not?5loaded?$AA@ (0000)
0028591C 0026:
??_C@_0CG@JHLMGKLE@all?5machines?5have?5successfully?5l@ (0000)
00285944 002f:
??_C@_0CP@JBNFJJAE@still?5waiting?5on?5machine?5?$CD?$CFd?5to?5@ (0000)
00285978 0047:
??_C@_0EH@NKIEAGCA@client?5update?5packet?5from?5machin@ (0000)
002859C0 0040:
??_C@_0EA@BIPOHPNI@received?5an?5outdated?5client?5upda@ (0000)
00285A00 004b:
??_C@_0EL@GBABNBCE@client?5machine?5?$CD?$CFd?5is?5out?5of?5syn@ (0000)
00285A4C 000f:
??_C@_0P@OKCBNOPP@message_packet?$AA@ (0000)
00285A5C 0031:
??_C@_0DB@OKJCLNBN@machine?5?$CD?$CFd?5has?5successfully?5swi@ (0000)
00285A90 0012:
??_C@_0BC@HJLNGPOP@server?5?$CG?$CG?5machine?$AA@ (0000)
00285AA8 0085:
??_C@_0IF@CCOGGKBF@network_game_server_send_player_@ (0000)
00285B30 002e:
??_C@_0CO@BNMPLHFO@server?5failed?5to?5add?5a?5network?5p@ (0000)
00285B60 005d:
??_C@_0FN@BDPHLOBF@server?5failed?5to?5send?5game?5updat@ (0000)
00285BC0 0011:
??_C@_0BB@JLJAPHKN@server?5?$CG?$CG?5player?$AA@ (0000)
00285BD4 0024:
??_C@_0CE@KMCEHK@server?5game?5start?5countdown?5star@ (0000)
00285BF8 000b:
??_C@_0L@DEEHFDNB@join_token?$AA@ (0000)
00285C04 003c:
??_C@_0DM@GFDOGMMN@client_machine?9?$DOmachine_index?$DMMA@ (0000)
00285C40 0019:
??_C@_0BJ@PDBLHLMI@server?5?$CG?$CG?5client_machine?$AA@ (0000)
00285C5C 002c:
??_C@_0CM@HFLLHELF@server?5?$CG?$CG?5network_machine_is_val@ (0000)
00285C88 0030:
??_C@_0DA@BAJANDFN@server?5?$CG?$CG?5?$CIindex?$DMMAXIMUM_NETWORK@ (0000)
00285CB8 001b:
??_C@_0BL@OEHAOKDP@no?5machine?5found?5?$EA?5ip?5?$CD?$CFlX?$AA@ (0000)
00285CD4 0026:
??_C@_0CG@LADAADEM@server?9?$DOclient_machines?$FLi?$FN?4conne@ (0000)
00285CFC 0015:
??_C@_0BF@BGCFGMDN@server?5?$CG?$CG?5ip_address?$AA@ (0000)
00285D18 0056:
??_C@_0FG@ENHBPECG@network_game_server_change_map_n@ (0000)
00285D70 0034:
??_C@_0DE@OPKCBBPP@server?9?$DOstate?5?$DN?$DN?5_network_game_s@ (0000)
00285DA4 0022:
??_C@_0CC@DFOMMGAI@server?5?$CG?$CG?5map_name?5?$CG?$CG?5map_name?$FL0@ (0000)
00285DC8 005a:
??_C@_0FK@HEDIBIPF@network_game_server_change_game_@ (0000)
00285E24 0012:
??_C@_0BC@DLOLLBOD@server?5?$CG?$CG?5variant?$AA@ (0000)
00285E38 0047:
??_C@_0EH@GICIMKGF@network_game_server_add_new_clie@ (0000)
00285E80 0051:
??_C@_0FB@EIEHAMPG@failed?5to?5find?5an?5available?5mach@ (0000)
00285ED8 0067:
??_C@_0GH@CKJGDANO@network_connection_get_address?$CI?$CJ@ (0000)
00285F40 0027:
??_C@_0CH@GMPLHAIM@new?5remote?5connection?5accepted?5f@ (0000)
00285F68 0062:
??_C@_0GC@GMAADHDI@remote?5system?5tried?5to?5join?5our?5@ (0000)
00285FCC 0019:
??_C@_0BJ@BMKILIN@server?5?$CG?$CG?5new_connection?$AA@ (0000)
00285FE8 005d:
??_C@_0FN@HKDNEOAD@network_game_server_handle_datag@ (0000)
00286048 0069:
??_C@_0GJ@NFGOFIEO@failed?5to?5create?5a?5message_serve@ (0000)
002860B8 0041:
??_C@_0EB@ILPMAAHI@error?5sending?5rejection?5message?5@ (0000)
002860FC 0038:
??_C@_0DI@LLMEOPEK@endpoint?5?$CG?$CG?5?$CIreason?5?$DM?5NUMBER_OF_@ (0000)
00286134 0028:
??_C@_0CI@NMINAOIB@client?5connection?5refused?$DL?5game?5@ (0000)
00286160 0061:
??_C@_0GB@DCECGEGP@?$CBall_machines_have_precached?5?$HM?$HM?5@ (0000)
002861C4 003e:
??_C@_0DO@MCCLFBCA@network?5game?5setup?5failed?$DL?5proba@ (0000)
00286204 0016:
??_C@_0BG@OLFNBGNL@setting?5up?5a?5net?5game?$AA@ (0000)
0028621C 001c:
??_C@_0BM@JFGLKNKN@?$CFsnumber_of_games_played?5?$CFd?$AA@ (0000)
00286238 001e:
??_C@_0BO@JOAPPGLE@?$CFsnetwork_game_random_seed?5?$CFx?$AA@ (0000)
00286258 0018:
??_C@_0BI@BAJBJCMP@?$CFs?7player_list_index?5?$CFx?$AA@ (0000)
00286270 0011:
??_C@_0BB@EOOHLDJC@?$CFs?7team_index?5?$CFx?$AA@ (0000)
00286284 0017:
??_C@_0BH@LNCHICCP@?$CFs?7controller_index?5?$CFx?$AA@ (0000)
0028629C 0014:
??_C@_0BE@HLHKAFCI@?$CFs?7machine_index?5?$CFx?$AA@ (0000)
002862B0 000c:
??_C@_0M@ONJMLKP@?$CFsplayer?5?$CFd?$AA@ (0000)
002862BC 0012:
??_C@_0BC@MPDEELBJ@?$CFsplayer_count?5?$CFd?$AA@ (0000)
002862D0 0011:
??_C@_0BB@NMBMNGAB@?7?$CFsmachine?5?$CFd?5?$CFx?$AA@ (0000)
002862E4 0013:
??_C@_0BD@FGMHAHMN@?$CFsmachine_count?5?$CFd?$AA@ (0000)
002862F8 0014:
??_C@_0BE@JAKJKGBL@?$CFsnetwork_game_data?$AA@ (0000)
0028630C 001e:
??_C@_0BO@KBMOCMCI@?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CKEND?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$AA@ (0000)
0028632C 002c:
??_C@_0CM@KBELBPJF@?7time_of_first_client_loading_co@ (0000)
00286358 001c:
??_C@_0BM@EADPCOLB@?7time_of_last_keep_alive?5?$CFd?$AA@ (0000)
00286374 0017:
??_C@_0BH@BNHPAHB@?7next_update_number?5?$CFd?$AA@ (0000)
0028638C 000b:
??_C@_0L@EEANCMOB@?7?7flags?5?$CFx?$AA@ (0000)
00286398 0013:
??_C@_0BD@JKAKGNJM@?7?7machine_index?5?$CFx?$AA@ (0000)
002863AC 0016:
??_C@_0BG@PPFEBDPA@?7?7stall_start_time?5?$CFd?$AA@ (0000)
002863C4 002a:
??_C@_0CK@BMPEFDDJ@?7?7last_received_update_sequence_@ (0000)
002863F0 0013:
??_C@_0BD@NOBCPADN@?7?7connection?5?$CFx?5?$CFs?$AA@ (0000)
00286404 000b:
??_C@_0L@NIBBMIDE@?7client?5?$CFd?$AA@ (0000)
00286410 0007:
??_C@_06NEHEIOHK@?$CIdead?$CJ?$AA@ (0000)
00286418 0009:
??_C@_08MGFPAODM@?$CIactive?$CJ?$AA@ (0000)
00286424 000e:
??_C@_0O@EFMNOFHN@no?5connection?$AA@ (0000)
00286434 0011:
??_C@_0BB@HDACINMK@client_machines?3?$AA@ (0000)
00286448 0002:
??_C@_01GPOEFGEJ@?7?$AA@ (0000)
0028644C 000a:
??_C@_09LHENMLPO@?7flags?5?$CFx?$AA@ (0000)
00286458 000a:
??_C@_09JEKGPEEJ@?7state?5?$CFx?$AA@ (0000)
00286464 000f:
??_C@_0P@IGEGAEKH@?7connection?5?$CFx?$AA@ (0000)
00286474 0020:
??_C@_0CA@JIMNGMN@?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CKBEGIN?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$CK?$AA@ (0000)
00286494 002d:
??_C@_0CN@BOOLPEKD@server?5failed?5to?5close?5a?5client?8@ (0000)
002864C8 005b:
??_C@_0FL@JJMLLPJG@network_game_server_remove_clien@ (0000)
00286528 0080:
??_C@_0IA@HCKJGBDF@network_game_server_remove_clien@ (0000)
002865A8 0013:
??_C@_0BD@OGPILLHP@machine?5index?5?$DN?5?$CFx?$AA@ (0000)
002865BC 0021:
??_C@_0CB@BNBDJEGI@machine?5name?5?$DN?5?$DMnot?5implemented?$DO@ (0000)
002865E0 0067:
??_C@_0GH@MMONEEJC@attempted?5to?5remove?5an?5invalid?5m@ (0000)
00286648 0069:
??_C@_0GJ@MMCLLNAF@network_game_server_remove_machi@ (0000)
002866B8 0057:
??_C@_0FH@BABKECJC@network_game_remove_machine?$CI?$CJ?5fa@ (0000)
00286710 0054:
??_C@_0FE@BFPCFLEJ@network_game_server_remove_machi@ (0000)
00286768 006f:
??_C@_0GP@EIJIPAGB@network_game_server_remove_clien@ (0000)
002867D8 0051:
??_C@_0FB@JOJEBAMD@network_game_server_remove_machi@ (0000)
0028682C 0008:
??_C@_07DPHHBAJ@removed?$AA@ (0000)
00286834 003c:
??_C@_0DM@CCJHIKEB@forcibly?5removing?5client?5system?5@ (0000)
00286870 000f:
??_C@_0P@IOAOPNJI@?$DMunknown?5name?$DO?$AA@ (0000)
00286880 0010:
??_C@_0BA@JMALINEK@culprit?5?$CB?$DN?5NONE?$AA@ (0000)
00286890 003e:
??_C@_0DO@BHFBBCGG@server?5?$CG?$CG?5server?9?$DOstate?5?$DN?$DN?5_netw@ (0000)
002868D0 002a:
??_C@_0CK@CDKFKGHB@failed?5to?5remove?5client?5machine?5@ (0000)
002868FC 0021:
??_C@_0CB@MJKIPMIF@client?5machine?5removed?5from?5game@ (0000)
00286920 0063:
??_C@_0GD@ILCKCFLA@network_game_server_handle_clien@ (0000)
00286984 002d:
??_C@_0CN@GAKEIIC@failed?5to?5remove?5client?5machine?5@ (0000)
002869B4 0024:
??_C@_0CE@CPAGILJL@client?5machine?5?$CFx?5removed?5from?5g@ (0000)
002869D8 004b:
??_C@_0EL@DJHOLFOA@forcibly?5removing?5client?5system?5@ (0000)
00286A28 0041:
??_C@_0EB@FNLOGEMK@failed?5to?5send?5a?5message_server_@ (0000)
00286A6C 0030:
??_C@_0DA@IPEDOIIA@network_game_server_start_networ@ (0000)
00286A9C 001f:
??_C@_0BP@CKGEOJEP@booting?5dead?5client?5machine?5?$CFd?$AA@ (0000)
00286ABC 0018:
??_C@_0BI@BNPOAEPH@network?5server?5disposed?$AA@ (0000)
00286AD4 0036:
??_C@_0DG@DABJFLIB@network_game_server_memory_do_no@ (0000)
00286B10 0059:
??_C@_0FJ@KJNIDGHK@network_game_server_handle_clien@ (0000)
00286B70 004b:
??_C@_0EL@BMJJENN@failed?5to?5create?5a?5_message_type@ (0000)
00286BBC 0034:
??_C@_0DE@CJDOLHKA@failed?5to?5notify?5all?5clients?5tha@ (0000)
00286BF0 002c:
??_C@_0CM@DJIIBLPE@notified?5all?5clients?5that?5we?5are@ (0000)
00286C20 004c:
??_C@_0EM@NAIJHAGO@failed?5to?5create?5a?5_message_type@ (0000)
00286C6C 001d:
??_C@_0BN@CJOEAMG@the?5server?8s?5game?5is?5invalid?$AA@ (0000)
00286C8C 0021:
??_C@_0CB@OBPDACNL@network_connection_idle?$CI?$CJ?5failed@ (0000)
00286CB0 0034:
??_C@_0DE@MJEBDOOH@network_game_server_handle_publi@ (0000)
00286CE4 0034:
??_C@_0DE@ECHDPAEM@network_game_server_handle_clien@ (0000)
00286D18 0015:
??_C@_0BF@HDEDCFJD@unknown?5server?5state?$AA@ (0000)
00286D30 0030:
??_C@_0DA@GGCMIGDD@failed?5to?5add?5new?5client?5connect@ (0000)
00286D60 0035:
??_C@_0DF@CAHJJMAP@new?5client?5connected?5from?5ip?5?$CFs?5@ (0000)
00286D98 003a:
??_C@_0DK@OICDNHOL@failed?5to?5signal?5all?5client?5mach@ (0000)
00286DD8 0050:
??_C@_0FA@CGIOIDCF@the?5playlist?5has?5ended?5?9?5server?5@ (0000)
00286E28 002b:
??_C@_0CL@GEFANPJI@the?5playlist?5has?5ended?5?9?5server?5@ (0000)
00286E54 001c:
??_C@_0BM@DEOHMBDO@server?5resetting?5to?5pregame?$AA@ (0000)
00286E70 0027:
??_C@_0CH@LGNFBGFC@failed?5to?5create?5the?5server?5conn@ (0000)
00286E98 002d:
??_C@_0CN@LEAKMMND@failed?5to?5initialize?5server?5preg@ (0000)
00286EC8 0037:
??_C@_0DH@KNNCDAKL@?$CBnetwork_game_server_memory_do_n@ (0000)
00456CF4 0008:
_network_game_server_memory_do_not_use_directly_in_use (0000)
*/
/* ---------- headers */
#include "cseries.h"
#include "bungie_net/common/message_header.h"
#include "bungie_net/network/transport.h"
#include "bungie_net/network/transport_address_constants.h"
#include "bungie_net/network/transport_endpoint_winsock.h"
#include "cseries/cseries_windows.h"
#include "cseries/errors.h"
#include "game/game.h"
#include "game/game_engine.h"
#include "game/player_queues_new.h"
#include "game/players.h"
#include "interface/ui_widget.h"
#include "main/main.h"
#include "math/real_math.h"
#include "networking/network_client_manager.h"
#include "networking/network_connection.h"
#include "networking/network_game_globals.h"
#include "networking/network_game_manager.h"
#include "networking/network_game_protocol.h"
#include "networking/network_game_ui.h"
#include "networking/network_messages.h"
#include "networking/network_server_manager.h"
#include "networking/network_server_manager_internal.h"
#include "networking/network_server_message_handler.h"
#include "saved games/player_profile.h"
#include "text/unicode.h"
#include "cache/cache_files.h"
#include "interface/player_ui.h"
#include "tag_files/tag_files.h"
/* port: internet play's Discord presence (port/linux/src/p2p.c), and the
server browser's listing of a public game (p2p_lobby.c) */
void p2p_set_game_player_counts(int count, int maximum);
#ifdef HALO_ANDROID
void p2p_set_game_description(char const *name, char const *map, char const *mode, int players,
int maximum_players);
void p2p_set_hosting_public(int public_game);
int p2p_public_game(void);
#endif
void p2p_set_game_listing(const char *name, const char *map, const char *gametype, int engine_type, int open,
int in_progress, int has_teams);
/* ---------- constants */
#define NETWORK_SERVER_MANAGER_FILE "c:\\halo\\SOURCE\\networking\\network_server_manager.c"
enum
{
/* the native builds' session limits (port/linux/include/halo_port_limits.h) */
MAXIMUM_NETWORK_MACHINE_COUNT = HALO_PORT_MAXIMUM_NETWORK_MACHINES,
MAXIMUM_NETWORK_PLAYER_COUNT = HALO_PORT_MAXIMUM_NETWORK_PLAYERS,
NETWORK_GAME_NAME_LENGTH = 16,
NETWORK_GAME_MAP_NAME_LENGTH = 0x80,
NETWORK_PLAYER_NAME_LENGTH = 12,
MAXIMUM_MACHINE_NAME_LENGTH = 32,
NUMBER_OF_MULTIPLAYER_TEAMS = 2,
NETWORK_GAME_PLAYER_QUIT_DELAY = 33,
/* the time the other machines have to load the map once the first has
finished, allowing for many machines of mixed speed */
NETWORK_GAME_SERVER_MAXIMUM_WAIT_TIME_FOR_LEVEL_LOADING =
60 * MILLISECONDS_PER_SECOND,
MAXIMUM_PLAYERS_PER_MACHINE = MAXIMUM_LOCAL_PLAYERS,
PLAYER_UPDATE_SIZE = 0x20,
MAXIMUM_GOOD_COLOR_ATTEMPTS = 10,
/* with more players than random names or colours, the pickers settle for a
numbered name or a shared colour after this many tries */
MAXIMUM_UNIQUE_NAME_ATTEMPTS = 64,
MAXIMUM_UNIQUE_COLOR_ATTEMPTS = 64,
/* (the top bit was the lockstep netcode's out of sync, which clients no
longer set) */
CLIENT_UPDATE_SEQUENCE_NUMBER_MASK = 0x7FFFFFFF,
NETWORK_GAME_COUNTDOWN_TIME = 30999,
NETWORK_GAME_SPLITSCREEN_COUNTDOWN_TIME = 10999,
NETWORK_GAME_COUNTDOWN_ADJUSTMENT = 5000,
NETWORK_GAME_MINIMUM_COUNTDOWN_TIME = 999,
_network_client_machine_connected_bit = 0,
_network_client_machine_validated_bit,
_network_client_machine_level_loaded_bit,
_network_client_machine_precached_bit,
/* started in the game in progress (network_game_server_start_late_joiner) */
_network_client_machine_started_late_bit,
NUMBER_OF_NETWORK_CLIENT_MACHINE_FLAGS,
/* the protocol's largest message (a client's hits come near it) */
MAXIMUM_NETWORK_MESSAGE_SIZE = HALO_PORT_MAXIMUM_NETWORK_MESSAGE_SIZE,
/* a machine that connects must join within this, and one in the game
not go silent for longer (it sends ten times a second) */
NETWORK_GAME_SERVER_JOIN_TIMEOUT = 10 * MILLISECONDS_PER_SECOND,
NETWORK_GAME_SERVER_CLIENT_TIMEOUT = 15 * MILLISECONDS_PER_SECOND,
/* a machine joining the game in progress, silent while it loads */
NETWORK_GAME_SERVER_LATE_JOINER_TIMEOUT = 120 * MILLISECONDS_PER_SECOND,
};
enum
{
_network_game_server_game_open_bit = 0,
_network_game_server_game_valid_bit,
};
enum
{
_network_game_server_countdown_event_player_left,
_network_game_server_countdown_event_player_joined,
_network_game_server_countdown_event_stop,
_network_game_server_countdown_event_start_immediately,
NUMBER_OF_NETWORK_GAME_SERVER_COUNTDOWN_EVENTS
};
enum
{
_network_game_server_state_pregame,
_network_game_server_state_ingame,
_network_game_server_state_postgame,
NUMBER_OF_NETWORK_GAME_SERVER_STATES
};
/* ---------- macros */
#define network_machine_is_valid(machine) \
((machine) && (machine)->machine_index >= 0 && \
(machine)->machine_index < MAXIMUM_NETWORK_MACHINE_COUNT)
/* ---------- structures */
struct countdown_timer
{
long time_remaining;
unsigned long last_update_time;
};
struct message_server_game_over
{
long unused;
};
struct message_server_begin_game
{
long unused;
};
struct message_server_remove_player_ingame
{
struct network_player player;
long reason;
};
struct message_server_graceful_game_exit_pregame
{
long unused;
};
struct message_server_switch_to_pregame
{
long unused;
};
struct message_server_graceful_game_exit_postgame
{
long unused;
};
struct message_server_machine_rejected
{
word reason;
};
struct message_server_pregame_countdown
{
short seconds_to_start;
};
struct message_server_pregame_keep_alive
{
short unused;
};
struct message_server_postgame_keep_alive
{
short unused;
};
struct message_client_game_update
{
unsigned long update_number;
short unknown04;
short player_count;
struct player_action actions[MAXIMUM_PLAYERS_PER_MACHINE];
};
struct message_server_game_update
{
long update_number;
long random_seed;
long game_time;
word unknown0C;
word player_count;
byte player_updates[MAXIMUM_NETWORK_PLAYER_COUNT * PLAYER_UPDATE_SIZE];
};
typedef char network_machine_size_assert[
sizeof(struct network_machine) == 0x44 ? 1 : -1];
struct network_game_server_client_machine
{
struct network_connection *connection;
unsigned long last_received_update_sequence_number;
/* when it was last heard from (the lockstep netcode's stall_start_time) */
unsigned long last_heard_time;
short machine_index;
word flags;
};
struct network_game_server_countdown_state
{
struct countdown_timer timer;
long last_countdown_message_time;
boolean active;
boolean paused;
boolean adjusted_time_this_tick;
byte padding0F;
};
struct network_game_server
{
struct network_connection *connection;
word state;
word flags;
struct network_game game;
struct network_game_server_client_machine client_machines[MAXIMUM_NETWORK_MACHINE_COUNT];
long next_update_number;
long time_of_last_keep_alive;
unsigned long time_of_first_client_loading_completion;
struct network_game_server_countdown_state countdown_state;
struct network_player queued_player;
boolean queued_player_valid;
boolean sent_start_game_message;
byte padding4BA[2];
/* in-game joins waiting behind queued_player (the Xbox game keeps one
and drops any other that arrives meanwhile) */
struct network_player waiting_players[MAXIMUM_NETWORK_PLAYER_COUNT];
long waiting_player_count;
};
/* the layout follows the session limits (port/linux/include/halo_port_limits.h) */
typedef char network_game_players_offset_assert[
offsetof(struct network_game, players) == HALO_PORT_NETWORK_GAME_PLAYERS_OFFSET ? 1 : -1];
typedef char network_game_variant_has_teams_offset_assert[
offsetof(struct network_game, variant) +
offsetof(struct game_variant, universal_variant.teams) == 0xC0 ? 1 : -1];
typedef char network_game_size_assert[
sizeof(struct network_game) == HALO_PORT_NETWORK_GAME_SIZE ? 1 : -1];
typedef char network_game_server_client_machines_offset_assert[
offsetof(struct network_game_server, client_machines) == 8 + HALO_PORT_NETWORK_GAME_SIZE ? 1 : -1];
typedef char network_game_server_countdown_state_offset_assert[
offsetof(struct network_game_server, countdown_state) ==
8 + HALO_PORT_NETWORK_GAME_SIZE + MAXIMUM_NETWORK_MACHINE_COUNT * 0x10 + 0xC ? 1 : -1];
/* bots' machines (the VR port's bots, port/linux/game/bots.c; on the
desktop none is ever marked, the writes live behind the Android guard) */
static boolean network_game_server_bot_machines[MAXIMUM_NETWORK_MACHINE_COUNT];
#ifdef HALO_ANDROID
static short network_game_server_bot_machine_count(struct network_game_server *server);
#endif
/* ---------- prototypes */
void countdown_timer_increment(
struct countdown_timer *timer,
long adjustment,
long maximum);
static boolean network_game_server_setup_game_from_playlist(
struct network_game_server *server);
static boolean network_game_server_machine_has_waiting_players(
struct network_game_server *server,
long machine_index);
static boolean network_game_server_machine_has_players(
struct network_game_server *server,
long machine_index);
static void network_game_server_refuse_late_joiner(
struct network_game_server *server,
struct network_game_server_client_machine *machine,
word reason);
static boolean network_game_server_drop_client_machine(
struct network_game_server *server,
struct network_game_server_client_machine *client);
static void network_game_server_start_late_joiner(
struct network_game_server *server,
struct network_game_server_client_machine *machine);
static void network_game_server_keep_late_joiners_alive(
struct network_game_server *server);
static boolean network_game_server_add_new_client(
struct network_game_server *server,
struct network_connection *new_connection);
static boolean network_game_server_handle_public_endpoint(
struct network_game_server *server);
static boolean network_game_server_handle_client_machines(
struct network_game_server *server);
static boolean network_game_server_idle_postgame_tasks(
struct network_game_server *server);
static boolean network_game_server_have_all_machines_have_precached(
struct network_game_server *server);
static boolean network_game_server_idle_pregame_tasks(
struct network_game_server *server);
static void network_game_server_send_rejection_message(
struct transport_endpoint *endpoint,
word reason);
static void network_game_server_reject_connection_game_is_full(
struct transport_endpoint *endpoint);
static short network_game_server_get_client_machine_count(
struct network_game_server *server);
void get_unique_random_name(
struct network_game_server *server,
struct network_player *player);
void get_unique_random_color(
struct network_game_server *server,
struct network_player *player);
static boolean player_name_is_unique(
struct network_game_server *server,
wchar_t const *name);
static void network_game_server_dump(
struct network_game_server *server);
static void network_game_server_countdown_started(
struct network_game_server *server);
static void network_game_server_variant_options(
struct game_variant const *variant,
struct game_variant_options *options);
static void network_game_server_remove_players_gone_while_loading(
struct network_game_server *server);
/* ---------- globals */
/* port: the map for the next co-op round after a win
(network_game_server_port_cooperative_won); empty when there is none */
static char network_game_server_cooperative_next_map[sizeof(((struct network_game *)NULL)->map.name)];
struct network_game_server network_game_server_memory_do_not_use_directly;
boolean network_game_server_memory_do_not_use_directly_in_use = FALSE;
/* port: the players in the settings the game started with: every machine
spawned them, and those whose machines left while the game loaded (whom no
quit message reached, the server being in the pregame) are removed from the
game once it has loaded (network_game_server_all_machines_have_loaded) */
static struct network_player network_game_server_start_players[MAXIMUM_NETWORK_PLAYER_COUNT];
/* port: each joined client machine's IPv4 address (0: none), by slot, which
a datagram is matched to without asking every connection for its address
(network_game_server_get_client_machine_at_address) */
static unsigned long network_game_server_client_machine_addresses[MAXIMUM_NETWORK_MACHINE_COUNT];
/* port: the update number up to which a machine that has loaded (at the
start, or joining the game in progress) gets the host's game update every
tick; after that once a second (network_game_server_update_ticks) */
static long network_game_server_frequent_updates_until[MAXIMUM_NETWORK_MACHINE_COUNT];
/* port: the client machines that have slowed this countdown (once each:
network_game_server_client_machine_may_slow_countdown) */
static boolean network_game_server_countdown_slowed[MAXIMUM_NETWORK_MACHINE_COUNT];
/* port/linux/game/network_distributed.c's (the host's bans: bans.txt) */
boolean network_distributed_banned(unsigned long address, char const *hardware_id);
void network_distributed_ban(long machine_index, unsigned long address, char const *names);
void network_distributed_kick(char const *names);
/* port/linux/src/p2p.c's */
enum
{
P2P_HARDWARE_ID_SIZE = 33,
};
void p2p_hardware_id_sanitize(char *destination, int size, const char *source);
/* console.c's */
void console_warning(const char *format, ...);
/* port: the client machines to drop, as this server next looks at its
machines: those the distributed netcode asked to (their games sped up:
network_game_server_kick_machine) and those the host banned, whose
addresses are kept out of this server's games while it lasts; and those the
host kicked, which may join again at once */
enum
{
MAXIMUM_KICKED_ADDRESSES = 64,
};
enum
{
_kick_none = 0,
_kick_kept_out,
_kick_rejoinable,
};
static byte network_game_server_kick_pending[MAXIMUM_NETWORK_MACHINE_COUNT];
/* port: each client machine's hardware id as it told it joining, hex only
(p2p_hardware_id_sanitize), by slot */
static char network_game_server_hardware_ids[MAXIMUM_NETWORK_MACHINE_COUNT][P2P_HARDWARE_ID_SIZE];
static unsigned long network_game_server_kicked_addresses[MAXIMUM_KICKED_ADDRESSES];
static long network_game_server_kicked_address_next;
/* port: when each client machine joined (system_milliseconds): one that has
added no player this long after holds the lobby's countdown (a machine's
player is asked for as it joins) */
static unsigned long network_game_server_client_machine_join_times[MAXIMUM_NETWORK_MACHINE_COUNT];
enum
{
NETWORK_GAME_SERVER_PLAYERLESS_MACHINE_TIMEOUT = 15 * MILLISECONDS_PER_SECOND,
/* the connections of one address that have not joined yet a server
takes (network_game_server_add_new_client) */
MAXIMUM_WAITING_CONNECTIONS_PER_ADDRESS = 2,
};
/* port: the players added to the game in progress from each client
machine's address (a machine joins from one address, and joining again
drops the old: network_game_server_accept_client_machine_into_game), no
more than MAXIMUM_INGAME_ADDITIONS_PER_ADDRESS a game, so that players
added and removed, or a machine joining again and again, do not take
every player of the game (a player gone stays for its scores) */
enum
{
MAXIMUM_INGAME_ADDITIONS_PER_ADDRESS = 2 * MAXIMUM_PLAYERS_PER_MACHINE,
};
static struct
{
unsigned long address;
short count;
} network_game_server_ingame_additions[MAXIMUM_NETWORK_PLAYER_COUNT];
static short *network_game_server_ingame_addition_count(
unsigned long address,
boolean create)
{
long index;
if (!address)
return NULL;
for (index = 0; index < (long)NUMBEROF(network_game_server_ingame_additions); index++)
{
if (network_game_server_ingame_additions[index].address == address)
return &network_game_server_ingame_additions[index].count;
}
/* (more addresses than players: the game is full anyway) */
for (index = 0; create && index < (long)NUMBEROF(network_game_server_ingame_additions); index++)
{
if (!network_game_server_ingame_additions[index].address)
{
network_game_server_ingame_additions[index].address = address;
network_game_server_ingame_additions[index].count = 0;
return &network_game_server_ingame_additions[index].count;
}
}
return NULL;
}
/* port: a player's name in ASCII (player_name_character_ascii: a letter
with a mark its plain one, what is not ASCII else "?"), for the host's ban
command */
static void network_game_server_player_name_text(
struct network_player const *player,
char *text,
long size)
{
long index;
for (index = 0; index < (long)NUMBEROF(player->name) && player->name[index] && index < size - 1; index++)
text[index] = player_name_character_ascii(player->name[index]);
text[index] = 0;
}
/* port: whether a player's name (in ASCII) begins with the text, in either
case */
static boolean network_game_server_name_begins_with(
char const *name,
char const *text)
{
for (; *text; name++, text++)
{
char a = *name >= 'A' && *name <= 'Z' ? *name - 'A' + 'a' : *name;
char b = *text >= 'A' && *text <= 'Z' ? *text - 'A' + 'a' : *text;
if (!*name || a != b)
return FALSE;
}
return TRUE;
}
/* port: the names of the players of the game's other machines beginning with
the text (the host's ban command's completion: console.c); how many */
short network_game_server_matching_player_names(
char const *text,
char (*names)[NETWORK_GAME_SERVER_NAME_TEXT_SIZE],
short maximum_count)
{
struct network_game_server *server = global_network_game_server_get();
short count = 0;
long index;
if (!server)
return 0;
for (index = 0; index < MAXIMUM_NETWORK_PLAYER_COUNT && count < maximum_count; index++)
{
struct network_player const *player = &server->game.players[index];
if (!network_player_is_valid(player) || !VALID_INDEX(player->machine_index, MAXIMUM_NETWORK_MACHINE_COUNT) ||
network_game_server_client_machine_is_local(server, &server->client_machines[player->machine_index]))
{
continue;
}
network_game_server_player_name_text(player, names[count], NETWORK_GAME_SERVER_NAME_TEXT_SIZE);
if (network_game_server_name_begins_with(names[count], text))
count++;
}
return count;
}
/* port: the host's ban and kick commands' player: the other machine of the
player of the name (in either case; else the one player whose name begins
with it), and the names of that machine's players (each command's warnings
start with its own name); FALSE (said) if there is none */
static boolean network_game_server_named_machine(
char const *command,
char const *text,
long *machine_index,
char *names,
long names_size)
{
struct network_game_server *server = global_network_game_server_get();
long found_index = NONE;
long match_count = 0;
long exact_index = NONE;
long exact_count = 0;
long index;
names[0] = 0;
if (!server)
{
console_warning("%s: only the host of a game does this", command);
return FALSE;
}
for (index = 0; index < MAXIMUM_NETWORK_PLAYER_COUNT; index++)
{
struct network_player const *player = &server->game.players[index];
char name[NETWORK_GAME_SERVER_NAME_TEXT_SIZE];
if (!network_player_is_valid(player))
continue;
network_game_server_player_name_text(player, name, sizeof(name));
/* (the whole name first) */
if (network_game_server_name_begins_with(name, text) && csstrlen(name) == csstrlen(text))
{
exact_index = index;
exact_count++;
}
if (network_game_server_name_begins_with(name, text))
{
found_index = index;
match_count++;
}
}
if (exact_count > 1)
{
/* (the host numbers players of the same name: network_server_message_handler.c) */
console_warning("%s: %ld players are named \"%s\"", command, exact_count, text);
return FALSE;
}
if (exact_count == 1)
{
found_index = exact_index;
match_count = 1;
}
if (!text[0])
{
console_warning("%s: give a player's name (Tab completes it)", command);
return FALSE;
}
if (match_count > 1)
{
console_warning("%s: %ld players' names begin with \"%s\": give more of it", command, match_count, text);
return FALSE;
}
if (match_count == 0)
{
console_warning("%s: no player's name begins with \"%s\"", command, text);
return FALSE;
}
*machine_index = server->game.players[found_index].machine_index;
if (!VALID_INDEX(*machine_index, MAXIMUM_NETWORK_MACHINE_COUNT) ||
!network_game_server_client_machine_is_joined_to_game(server, &server->client_machines[*machine_index]) ||
network_game_server_client_machine_is_local(server, &server->client_machines[*machine_index]))
{
console_warning("%s: not a player of the host's own machine, nor one not joined", command);
return FALSE;
}
/* (every player of that machine, named) */
for (index = 0; index < MAXIMUM_NETWORK_PLAYER_COUNT; index++)
{
struct network_player const *player = &server->game.players[index];
char name[NETWORK_GAME_SERVER_NAME_TEXT_SIZE];
if (!network_player_is_valid(player) || player->machine_index != *machine_index)
continue;
network_game_server_player_name_text(player, name, sizeof(name));
if (names[0] && csstrlen(names) + 2 < names_size)
csstrcat(names, ", ");
if (csstrlen(names) + csstrlen(name) < names_size)
csstrcat(names, name);
}
return TRUE;
}
/* port: the host's ban command: the player's machine (as
network_game_server_named_machine finds it) dropped, its address in
bans.txt (network_distributed_ban), and kept out */
boolean network_game_server_ban_player(
char const *text)
{
long machine_index;
char names[96];
if (!network_game_server_named_machine("ban", text, &machine_index, names, sizeof(names)))
return FALSE;
network_distributed_ban(machine_index, network_game_server_client_machine_addresses[machine_index], names);
network_game_server_kick_pending[machine_index] = _kick_kept_out;
return TRUE;
}
/* port: the host's kick command: the player's machine (as the ban
command's) dropped, every machine told, and nothing kept: it may join again
at once */
boolean network_game_server_kick_player(
char const *text)
{
long machine_index;
char names[96];
if (!network_game_server_named_machine("kick", text, &machine_index, names, sizeof(names)))
return FALSE;
network_distributed_kick(names);
network_game_server_kick_pending[machine_index] = _kick_rejoinable;
return TRUE;
}
/* port: a client machine's hardware id as it told it joining (its join
request: network_server_message_handler.c), kept as hex only */
void network_game_server_set_machine_hardware_id(
struct network_game_server_client_machine *machine,
char const *hardware_id)
{
if (machine && VALID_INDEX(machine->machine_index, MAXIMUM_NETWORK_MACHINE_COUNT))
{
p2p_hardware_id_sanitize(network_game_server_hardware_ids[machine->machine_index],
P2P_HARDWARE_ID_SIZE, hardware_id);
}
}
/* port: ... and as the distributed netcode logs it (empty if none told) */
char const *network_game_server_machine_hardware_id(
long machine_index)
{
return VALID_INDEX(machine_index, MAXIMUM_NETWORK_MACHINE_COUNT) ?
network_game_server_hardware_ids[machine_index] : "";
}
/* port: a joined client machine's IPv4 address (host byte order; 0 if
none), for the distributed netcode's log of cheaters */
unsigned long network_game_server_machine_address(
long machine_index)
{
return VALID_INDEX(machine_index, MAXIMUM_NETWORK_MACHINE_COUNT) ?
network_game_server_client_machine_addresses[machine_index] : 0;
}
/* port: the distributed netcode asks that a client machine be dropped (its
game ran faster than this one's: network_distributed.c); it is, once this
server next looks at its machines, not while its messages are read */
void network_game_server_kick_machine(
long machine_index)
{
struct network_game_server *server = global_network_game_server_get();
if (!server || !VALID_INDEX(machine_index, MAXIMUM_NETWORK_MACHINE_COUNT) ||
!network_game_server_client_machine_is_joined_to_game(server, &server->client_machines[machine_index]) ||
network_game_server_client_machine_is_local(server, &server->client_machines[machine_index]))
{
return;
}
network_game_server_kick_pending[machine_index] = _kick_kept_out;
}
/* (a client machine's player queued to add in game: one refused is as one
the game has no room for) */
static boolean network_game_server_machine_may_add_player_ingame(
struct network_game_server *server,
long machine_index)
{
short *count;
if (!VALID_INDEX(machine_index, MAXIMUM_NETWORK_MACHINE_COUNT) ||
network_game_server_client_machine_is_local(server, &server->client_machines[machine_index]))
{
return TRUE;
}
count = network_game_server_ingame_addition_count(network_game_server_client_machine_addresses[machine_index], FALSE);
if (count && *count >= MAXIMUM_INGAME_ADDITIONS_PER_ADDRESS)
{
network_event("client machine #%ld added too many players in game", machine_index);
return FALSE;
}
return TRUE;
}
/* ---------- public code */
struct network_game_server *network_game_server_create(
void)
{
struct network_game_server *server =
&network_game_server_memory_do_not_use_directly;
match_assert(
NETWORK_SERVER_MANAGER_FILE,
0xE0,
!network_game_server_memory_do_not_use_directly_in_use);
network_game_server_memory_do_not_use_directly_in_use = TRUE;
csmemset(server, 0, sizeof(*server));
if (server != NULL)
{
server->connection = network_connection_new(
FLAG(_connection_create_server_bit),
NETWORK_GAME_SERVER_PORT);
if (server->connection != NULL)
{
int i;
#ifdef xbox
transport_server_initialize();
#endif
server->state = _network_game_server_state_pregame;
server->flags = FLAG(_network_game_server_game_valid_bit);
csmemset(&server->game, 0, sizeof(server->game));
network_connection_set_connection_rejection_procedure(
server->connection,
network_game_server_reject_connection_game_is_full);
network_game_invalidate(&server->game);
server->game.difficulty = main_get_difficulty();
server->game.number_of_games_played = -1;
for (i = 0; i < MAXIMUM_NETWORK_MACHINE_COUNT; i++)
{
server->client_machines[i].connection = NULL;
server->client_machines[i].last_received_update_sequence_number = 0;
server->client_machines[i].last_heard_time = 0;
server->client_machines[i].machine_index = NONE;
server->client_machines[i].flags = 0;
network_game_server_client_machine_addresses[i] = 0;
network_game_invalidate_machine(&server->game, i);
}
server->sent_start_game_message = FALSE;
server->time_of_first_client_loading_completion = 0;
csmemset(network_game_server_start_players, NONE, sizeof(network_game_server_start_players));
if (!network_game_server_reset_to_pregame(server))
{
error(
_error_silent,
"failed to initialize server pregame settings");
network_game_server_dispose(server);
server = NULL;
}
}
else
{
error(
_error_silent,
"failed to create the server connection");
/* port: nothing to dispose of (the Xbox game disposed of it: its
client machines, all zero, have machine 0 with no connection,
which the dispose's machine check reads; the port in use, by
another copy of the game, gets here) */
network_game_server_memory_do_not_use_directly_in_use = FALSE;
server = NULL;
}
}
return server;
}
void network_game_server_dispose(
struct network_game_server *server)
{
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x120, server);
/* port: a won co-op round's next level belongs to this server alone */
network_game_server_cooperative_next_map[0] = 0;
switch (server->state)
{
case _network_game_server_state_pregame:
{
/* (the Xbox game sent it uninitialised: stack bytes to every machine) */
struct message_server_graceful_game_exit_pregame message_packet = { 0 };
struct network_message *message;
message = create_network_game_message(
_message_server_graceful_game_exit_pregame,
&message_packet,
sizeof(message_packet));
if (message != NULL)
{
if (network_game_server_send_message_to_all_machines(server, message))
network_event("notified all clients that we are going down");
else
network_event("failed to notify all clients that we are going down");
}
else
{
network_event(
"failed to create a _message_type_server_graceful_game_exit_pregame message");
}
break;
}
case _network_game_server_state_ingame:
break;
case _network_game_server_state_postgame:
{
struct message_server_graceful_game_exit_postgame message_packet = { 0 };
struct network_message *message;
message = create_network_game_message(
_message_server_graceful_game_exit_postgame,
&message_packet,
sizeof(message_packet));
if (message != NULL)
{
if (network_game_server_send_message_to_all_machines(server, message))
network_event("notified all clients that we are going down");
else
network_event("failed to notify all clients that we are going down");
}
else
{
network_event(
"failed to create a _message_type_server_graceful_game_exit_postgame message");
}
break;
}
}
/* port: no longer waiting for the machines to load: a machine dropped
below, the last the others waited for, or the last to say it has loaded,
would start the game, whose local client (and its loaded game) is gone;
and no longer open */
server->time_of_first_client_loading_completion = 0;
server->sent_start_game_message = FALSE;
SET_FLAG(server->flags, _network_game_server_game_open_bit, FALSE);
if (!network_game_server_handle_client_machines(server))
{
error(
_error_silent,
"network_game_server_handle_client_machines() failed inside network_game_server_dispose()");
}
if (server->connection)
network_connection_delete(server->connection);
#ifdef xbox
SleepEx(MILLISECONDS_PER_SECOND, FALSE);
transport_server_terminate();
#endif
csmemset(server, 0, sizeof(*server));
match_assert(
NETWORK_SERVER_MANAGER_FILE,
0x171,
network_game_server_memory_do_not_use_directly_in_use);
network_game_server_memory_do_not_use_directly_in_use = FALSE;
p2p_set_game_player_counts(0, 0);
network_event("network server disposed");
return;
}
/* port: whether the host's network is gone: at once but in a game, where a
link that comes back within NETWORK_GAME_SERVER_CLIENT_TIMEOUT (an address
renewed, a wifi drop) goes on (a check more than two seconds after the last
starts the count over) */
static boolean network_game_server_network_lost(
struct network_game_server *server)
{
static unsigned long down_time;
static unsigned long checked_time;
static boolean asked;
static boolean available_asked;
static unsigned long asked_time;
unsigned long now = system_milliseconds();
boolean in_game = server->state == _network_game_server_state_ingame ||
server->state == _network_game_server_state_postgame;
boolean recent = checked_time && now - checked_time <= 2000;
boolean available;
/* (asked once a second: each asking lists the machine's interfaces) */
if (!asked || now - asked_time >= 1000)
{
available_asked = transport_network_available();
asked_time = now;
asked = TRUE;
}
available = available_asked;
checked_time = now;
if (available)
{
down_time = 0;
return FALSE;
}
if (!in_game)
return TRUE;
if (!down_time || !recent)
down_time = now;
return now - down_time > NETWORK_GAME_SERVER_CLIENT_TIMEOUT;
}
/* port: wide text as the listing has it: ASCII, a Latin letter with a mark
its plain letter ('?' for the rest: player_name_character_ascii, as the
server browser validates the name as a player's) */
static void listing_text(char *text, int size, wchar_t const *wide, int length)
{
int index;
for (index = 0; index < size - 1 && index < length && wide[index]; index++)
text[index] = player_name_character_ascii(wide[index]);
text[index] = 0;
}
/* port: the server browser's listing of the game (shown only if hosted
for the internet and public: p2p_lobby.c), as its advertisement has it
(network_server_message_handler.c) */
static void network_game_server_list(
struct network_game_server *server)
{
struct network_game *game = &server->game;
short state = network_game_server_get_state(server, NULL);
char name[NUMBEROF(game->name) + 1];
char gametype[NUMBEROF(game->variant.human_readable_game_description) + 1];
boolean in_progress = state != _network_game_server_state_pregame || network_game_server_game_is_loading(server);
boolean open = state == _network_game_server_state_ingame ? network_game_server_accepts_late_joins(server) :
network_game_server_game_is_open(server) && network_game_has_free_player_slot(game);
listing_text(name, sizeof(name), game->name, NUMBEROF(game->name));
listing_text(gametype, sizeof(gametype), game->variant.human_readable_game_description,
NUMBEROF(game->variant.human_readable_game_description));
/* co-op: add the difficulty */
if (!game->variant.game_engine_index && game->difficulty >= 0 && game->difficulty < 4)
{
static char const *const difficulty_names[] = { "Easy", "Normal", "Heroic", "Legendary" };
size_t length = strlen(gametype);
snprintf(gametype + length, sizeof(gametype) - length, " %s", difficulty_names[game->difficulty]);
}
/* (the scenario's name, not its path: the listing has 32 characters) */
p2p_set_game_listing(name, tag_name_strip_path(game->map.name), gametype, game->variant.game_engine_index, open,
in_progress, game->variant.universal_variant.teams);
}
boolean network_game_server_idle(
struct network_game_server *server)
{
boolean success = TRUE;
if (network_game_server_network_lost(server))
{
if (!network_game_is_splitscreen_local())
{
display_error_when_main_menu_loaded(_error_network_connection_lost);
error(_error_silent, "network connection went down!");
success = FALSE;
goto exit;
}
}
/* (what Discord shows of a game hosted for internet play, and the
server browser's listing) */
p2p_set_game_player_counts(server->game.player_count, server->game.maximum_players);
#ifdef HALO_ANDROID
/* (and what a public game's listing says: port/linux/src/p2p_list.c) */
{
char name[16], map[32], mode[24];
char const *map_name = server->game.map.name;
char const *separator;
short index;
for (index = 0; index < (short)(NUMBEROF(name) - 1) && server->game.name[index]; index++)
name[index] = server->game.name[index] < 128 ? (char)server->game.name[index] : '?';
name[index] = 0;
/* (the scenario's name, the last of its path) */
while ((separator = strpbrk(map_name, "\\/")) != NULL)
map_name = separator + 1;
csstrncpy(map, map_name, sizeof(map) - 1);
map[sizeof(map) - 1] = 0;
for (index = 0; index < (short)(NUMBEROF(mode) - 1) && index < 12 &&
server->game.variant.human_readable_game_description[index]; index++)
{
wchar_t character = server->game.variant.human_readable_game_description[index];
mode[index] = character < 128 ? (char)character : '?';
}
mode[index] = 0;
/* (a game type without a name of its own: its kind's) */
if (!mode[0])
{
static char const *const kinds[] = { "", "CTF", "Slayer", "Oddball", "King of the Hill", "Race" };
long kind = server->game.variant.game_engine_index;
csstrncpy(mode, kind > 0 && kind < (long)NUMBEROF(kinds) ? kinds[kind] : "Custom", sizeof(mode) - 1);
mode[sizeof(mode) - 1] = 0;
}
p2p_set_game_description(name, map, mode, server->game.player_count, server->game.maximum_players);
/* (listed in the server browser too, as public as the VR port's list
has it) */
p2p_set_hosting_public(p2p_public_game());
}
#endif
network_game_server_list(server);
if (network_game_server_game_is_valid(server))
{
struct network_connection *new_client_connection = NULL;
success = network_connection_idle(
server->connection,
_connection_dont_timeout,
&new_client_connection);
if (success == TRUE)
{
if (new_client_connection)
{
success = network_game_server_add_new_client(
server,
new_client_connection);
if (success == TRUE)
{
struct transport_address client_address;
network_connection_get_address(
new_client_connection,
&client_address,
NULL);
network_event(
"new client connected from ip %s (validation pending)",
transport_address_to_string(&client_address));
}
else
{
network_event(
"failed to add new client connection to the game");
success = network_server_close_client_connection(
server->connection,
new_client_connection);
}
}
success = network_game_server_handle_public_endpoint(server);
if (success)
{
success = network_game_server_handle_client_machines(server);
if (success)
{
switch (server->state)
{
case _network_game_server_state_pregame:
success = network_game_server_idle_pregame_tasks(server);
break;
case _network_game_server_state_ingame:
network_game_server_keep_late_joiners_alive(server);
break;
case _network_game_server_state_postgame:
success = network_game_server_idle_postgame_tasks(server);
break;
default:
network_event("unknown server state");
success = FALSE;
break;
}
}
else
{
network_event(
"network_game_server_handle_client_machines() failed");
}
}
else
{
network_event(
"network_game_server_handle_public_endpoint() failed");
}
}
else
{
network_event("network_connection_idle() failed");
}
}
else
{
network_event("the server's game is invalid");
}
exit:
return success;
}
void countdown_timer_update(
struct countdown_timer *timer)
{
unsigned long update_time = system_milliseconds();
/* (the time since, which the milliseconds' wrap leaves right: not
which time is greater) */
unsigned long elapsed_time = update_time - timer->last_update_time;
if (elapsed_time < (unsigned long)timer->time_remaining)
timer->time_remaining -= (long)elapsed_time;
else
timer->time_remaining = 0;
timer->last_update_time = update_time;
return;
}
long countdown_timer_get_time_remaining(
struct countdown_timer *timer)
{
long time_remaining;
unsigned long update_time = system_milliseconds();
unsigned long elapsed_time = update_time - timer->last_update_time;
if (elapsed_time < (unsigned long)timer->time_remaining)
timer->time_remaining -= (long)elapsed_time;
else
timer->time_remaining = 0;
time_remaining = timer->time_remaining;
timer->last_update_time = update_time;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x5F,
timer->time_remaining >= 0);
return time_remaining;
}
void countdown_timer_increment(
struct countdown_timer *timer,
long adjustment,
long maximum)
{
countdown_timer_update(timer);
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x68, adjustment >= 0);
if (timer->time_remaining + adjustment < adjustment)
{
timer->time_remaining = maximum;
}
else
{
timer->time_remaining += adjustment;
timer->time_remaining = MIN(timer->time_remaining, maximum);
}
match_assert(
NETWORK_SERVER_MANAGER_FILE,
0x75,
timer->time_remaining >= 0);
return;
}
void countdown_timer_decrement(
struct countdown_timer *timer,
long adjustment)
{
unsigned long update_time = system_milliseconds();
unsigned long elapsed_time = update_time - timer->last_update_time;
if (elapsed_time < (unsigned long)timer->time_remaining)
timer->time_remaining -= (long)elapsed_time;
else
timer->time_remaining = 0;
timer->last_update_time = update_time;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x7E, adjustment >= 0);
if (timer->time_remaining > adjustment)
timer->time_remaining -= adjustment;
else
timer->time_remaining = 0;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x89,
timer->time_remaining >= 0);
return;
}
struct network_connection *network_game_server_get_client_connection(
struct network_game_server_client_machine *client_machine)
{
if (client_machine)
return client_machine->connection;
return NULL;
}
struct network_connection *network_game_server_get_connection(
struct network_game_server *server)
{
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x712, server);
return server->connection;
}
struct network_game *network_game_server_get_game(
struct network_game_server *server)
{
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x769, server);
return &server->game;
}
wchar_t *network_game_server_get_game_name(
struct network_game_server *server)
{
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x1E9, server);
return server->game.name;
}
boolean network_game_server_set_game_name(
struct network_game_server *server,
wchar_t const *name)
{
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x1DD, server);
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x1DE, name);
ustrncpy(server->game.name, name, NETWORK_GAME_NAME_LENGTH - 1);
server->game.name[NETWORK_GAME_NAME_LENGTH - 1] = 0;
return FALSE;
}
word network_game_server_get_state(
struct network_game_server *server,
short *substate)
{
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x1F2, server);
if (substate)
*substate = 0;
return server->state;
}
void network_game_server_open_game(
struct network_game_server *server)
{
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x1FC, server);
SET_FLAG(server->flags, _network_game_server_game_open_bit, TRUE);
network_server_allow_client_connections(server->connection, TRUE);
network_event("opening game");
return;
}
boolean network_game_server_start_network_game(
struct network_game_server *server)
{
boolean success = TRUE;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x2DE, server);
if (server->sent_start_game_message == FALSE)
{
struct message_server_begin_game begin_game = { 0 };
void *message;
/* the settings record goes out in pieces */
/* port: once the settings are out, the start is sent once: a machine
that missed it (its connection failed, and is closed) is dropped, and
sent again, the start reached those that loaded it already (which
refuse a second start) and the wait for the others never began */
if (network_game_server_send_game_settings_to_all_machines(server, &server->game, sizeof(server->game)) &&
((message = create_network_game_message(
_message_server_begin_game,
&begin_game,
sizeof(begin_game))) != NULL))
{
if (network_game_server_send_message_to_all_machines(server, message))
network_event("signalling client machines to begin loading for network game");
else
network_event("signalling client machines to begin loading for network game (some machines missed it)");
server->sent_start_game_message = TRUE;
csmemcpy(network_game_server_start_players, server->game.players,
sizeof(network_game_server_start_players));
success = TRUE;
}
else
{
network_event("failed to signal client machines to begin loading for network game");
}
}
server->next_update_number = 0;
return success;
}
static void network_game_server_send_player_quit_messages_ingame(
struct network_game_server *server,
struct network_game_server_client_machine *machine)
{
long player_index;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x267,
_network_game_server_state_ingame == server->state);
for (player_index = 0;
player_index < MAXIMUM_NETWORK_PLAYER_COUNT;
player_index++)
{
struct network_player *player = &server->game.players[player_index];
if (network_player_is_valid(player) &&
player->machine_index == machine->machine_index)
{
struct message_server_remove_player_ingame remove_player;
void *message;
remove_player.player = *player;
remove_player.reason = game_time_get() + NETWORK_GAME_PLAYER_QUIT_DELAY;
error(_error_silent, "sending quit out of game, time = %x", remove_player.reason);
message = create_network_game_message(
_message_server_remove_player_ingame,
&remove_player,
sizeof(remove_player));
if (message &&
!network_game_server_send_message_to_all_machines(server, message))
{
network_event(
"network_game_server_send_message_to_all_machines() failed in network_game_server_handle_message_client_remove_player_request_ingame()");
}
}
}
return;
}
void network_game_server_switch_to_postgame(
struct network_game_server *server)
{
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x2FF, server);
if (server->state == _network_game_server_state_ingame)
{
struct message_server_game_over game_over = { 0 };
void *message;
server->state = _network_game_server_state_postgame;
message = create_network_game_message(
_message_server_game_over,
&game_over,
sizeof(game_over));
if (message)
{
if (network_game_server_send_message_to_all_machines(server, message))
network_event("server sent message_game_over to all clients");
else
network_event("failed to signal all client machines to switch to postgame");
}
else
{
network_event("failed to create a _message_type_server_game_over message");
}
}
return;
}
boolean network_game_server_graceful_shutdown(
struct network_game_server *server)
{
boolean success = FALSE;
void *message = NULL;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x39F, server);
switch (network_game_server_get_state(server, NULL))
{
case _network_game_server_state_pregame:
{
struct message_server_graceful_game_exit_pregame exit_pregame = { 0 };
message = create_network_game_message(
_message_server_graceful_game_exit_pregame,
&exit_pregame,
sizeof(exit_pregame));
if (!message)
network_event("failed to create a message_server_graceful_game_exit_pregame");
}
break;
case _network_game_server_state_postgame:
{
struct message_server_graceful_game_exit_postgame exit_postgame = { 0 };
message = create_network_game_message(
_message_server_graceful_game_exit_postgame,
&exit_postgame,
sizeof(exit_postgame));
if (!message)
network_event("failed to create a message_server_graceful_game_exit_postgame");
}
break;
}
if (message)
{
success = network_game_server_send_message_to_all_machines(server, message);
if (success == TRUE)
{
network_event(
"server closing down; all client machines were properly informed");
}
else
{
network_event(
"server going down, but failed to properly inform all client machines");
}
}
return success;
}
void countdown_timer_set_time_remaining(
struct countdown_timer *timer,
long time_remaining)
{
unsigned long update_time = system_milliseconds();
timer->time_remaining = time_remaining;
timer->last_update_time = update_time;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x95, timer->time_remaining >= 0);
return;
}
void network_game_server_client_machine_is_precached(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
char const *map_name)
{
char const *multiplayer_map_name = main_get_multiplayer_map_name();
if (!csstrcmp(multiplayer_map_name, map_name))
SET_FLAG(client_machine->flags, _network_client_machine_precached_bit, TRUE);
return;
}
boolean network_game_server_game_is_open(
struct network_game_server *server)
{
boolean game_is_open;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x214, server);
game_is_open = TEST_FLAG(server->flags, _network_game_server_game_open_bit);
/* port: the game stays open in progress (a machine may join it), but
not while the machines load it, nor once it is over */
if ((server->state == _network_game_server_state_pregame && server->sent_start_game_message) ||
server->state == _network_game_server_state_postgame)
{
game_is_open = FALSE;
}
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x217,
(TRUE == game_is_open) || (FALSE == game_is_open));
return game_is_open;
}
/* port: whether the host's game is being played (not its lobby, before or
after one) */
boolean network_game_server_playing(
struct network_game_server *server)
{
return server->state == _network_game_server_state_ingame;
}
/* port: whether the machines are loading the game (its start sent, still
in the pregame) */
boolean network_game_server_game_is_loading(
struct network_game_server *server)
{
return server->state == _network_game_server_state_pregame && server->sent_start_game_message;
}
boolean network_game_server_game_is_valid(
struct network_game_server *server)
{
boolean game_is_valid;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x220, server);
game_is_valid = TEST_FLAG(server->flags, _network_game_server_game_valid_bit);
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x223,
(TRUE == game_is_valid) || (FALSE == game_is_valid));
return game_is_valid;
}
boolean network_game_server_accept_client_machine_into_game(
struct network_game_server *server,
struct network_game_server_client_machine *machine)
{
boolean success = FALSE;
long machine_index;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x3DA, server);
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x3DB, machine);
/* port: into its own slot (network_game_server_add_new_client numbered
it by it), not the first free one: another connection's slot gave two
machines one index */
machine_index = machine->machine_index;
/* port: not a machine of an address dropped for cheating */
{
struct transport_address address = { { { 0 } } };
long index;
network_connection_get_address(machine->connection, &address, FALSE);
for (index = 0; index < MAXIMUM_KICKED_ADDRESSES && address.address.long_words[0]; index++)
{
if (network_game_server_kicked_addresses[index] == address.address.long_words[0])
{
network_event("refusing a machine @ %s: dropped from this game for cheating",
transport_address_to_string(&address));
return FALSE;
}
}
/* (and not one the host banned: its address or hardware id in
bans.txt) */
/* (the host's own client, which connects from 127.0.0.1, never) */
if (!network_game_server_client_machine_is_local(server, machine) &&
address.address.long_words[0] != IPV4_LOOPBACK_ADDRESS &&
network_distributed_banned(address.address.long_words[0],
VALID_INDEX(machine_index, MAXIMUM_NETWORK_MACHINE_COUNT) ?
network_game_server_hardware_ids[machine_index] : ""))
{
network_event("refusing a machine @ %s: banned (bans.txt)", transport_address_to_string(&address));
return FALSE;
}
}
/* (a kick asked for the slot's machine before is not this one's) */
if (VALID_INDEX(machine_index, MAXIMUM_NETWORK_MACHINE_COUNT))
network_game_server_kick_pending[machine_index] = _kick_none;
if (VALID_INDEX(machine_index, MAXIMUM_NETWORK_MACHINE_COUNT) &&
machine == &server->client_machines[machine_index])
{
if (!network_machine_is_valid(&server->game.machines[machine_index]))
{
struct network_machine new_machine;
csmemcpy(
&new_machine,
&server->game.machines[machine_index],
sizeof(new_machine));
new_machine.machine_index = (char)machine_index;
success = network_game_add_machine(&server->game, &new_machine);
if (success == TRUE)
{
struct transport_address address = { { { 0 } } };
network_connection_get_address(
machine->connection,
&address,
FALSE);
network_event(
"server added machine @ %s to the game at machine index #%d",
transport_address_to_string(&address),
machine_index);
SET_FLAG(
machine->flags,
_network_client_machine_validated_bit,
TRUE);
machine->machine_index = (short)machine_index;
/* port: a machine that joins again from its address before
its old connection is found dead: the old one goes (the
datagrams from the address are the new one's, and were taken
for the old slot's). Two machines of one address are not in a
game: the client's port is fixed, and internet play gives each
machine its own address. */
{
long other_index;
for (other_index = 0; other_index < MAXIMUM_NETWORK_MACHINE_COUNT; other_index++)
{
struct network_game_server_client_machine *other = &server->client_machines[other_index];
if (other != machine &&
network_game_server_client_machine_is_joined_to_game(server, other) &&
network_game_server_client_machine_addresses[other_index] == address.address.long_words[0] &&
!network_game_server_client_machine_is_local(server, other))
{
network_event("machine #%ld joined from the address of machine #%ld, which is dropped",
machine_index, other_index);
network_game_server_drop_client_machine(server, other);
}
}
}
network_game_server_client_machine_addresses[machine_index] = address.address.long_words[0];
network_game_server_client_machine_join_times[machine_index] = system_milliseconds();
}
else
{
network_event(
"network_game_add_machine() failed in network_game_server_accept_client_machine_into_game()");
}
}
}
if (!success)
{
network_event(
"network_game_server_accept_client_machine_into_game() failed to find an available opening for the new machine");
}
return success;
}
boolean network_game_server_client_machine_is_joined_to_game(
struct network_game_server *server,
struct network_game_server_client_machine *machine)
{
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x3CD, server);
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x3CE, machine);
return TEST_FLAG(machine->flags, _network_client_machine_validated_bit);
}
boolean network_game_server_switch_machine_from_postgame_to_pregame(
struct network_game_server *server,
struct network_game_server_client_machine *machine)
{
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x547, server && machine);
network_event(
"machine #%d has successfully switched to pregame",
machine->machine_index);
SET_FLAG(machine->flags, _network_client_machine_level_loaded_bit, FALSE);
return TRUE;
}
void network_game_server_all_machines_have_loaded(
struct network_game_server *server)
{
long client_machine_index;
network_event("all machines have successfully loaded");
server->state = _network_game_server_state_ingame;
server->time_of_first_client_loading_completion = 0;
/* (the machines that loaded first waited in silence: their timeouts
start now) */
for (client_machine_index = 0;
client_machine_index < MAXIMUM_NETWORK_MACHINE_COUNT;
client_machine_index++)
{
struct network_game_server_client_machine *client_machine =
&server->client_machines[client_machine_index];
if (network_game_server_client_machine_is_joined_to_game(server, client_machine))
network_game_server_client_machine_heard(server, client_machine);
/* (the host's game update every tick for the game's first second) */
network_game_server_frequent_updates_until[client_machine_index] =
server->next_update_number + TICKS_PER_SECOND;
}
network_game_server_remove_players_gone_while_loading(server);
server->game.local_data.game_objects_loaded = global_network_game_client_get()
? network_game_client_get_game(global_network_game_client_get())->local_data.game_objects_loaded
: FALSE;
match_vassert(NETWORK_SERVER_MANAGER_FILE, 0x4E0, server->game.local_data.game_objects_loaded,
"local game data not loaded");
return;
}
void network_game_server_client_machine_game_loading_complete(
struct network_game_server *server,
struct network_game_server_client_machine *machine)
{
boolean all_machines_loaded = TRUE;
long client_machine_index;
long loading_machine_count = 0;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x4ED, server);
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x4EE, machine);
/* port: not in the lobby, before the game starts: the wait for the
others would have run out by the start */
if (!server->sent_start_game_message)
{
network_event("ignoring machine #%d's load before the game started", machine->machine_index);
return;
}
SET_FLAG(machine->flags, _network_client_machine_level_loaded_bit, TRUE);
if (!server->time_of_first_client_loading_completion)
server->time_of_first_client_loading_completion = system_milliseconds();
for (client_machine_index = 0;
client_machine_index < MAXIMUM_NETWORK_MACHINE_COUNT;
client_machine_index++)
{
struct network_game_server_client_machine *client_machine =
&server->client_machines[client_machine_index];
/* (the machines in the game: not a connection that has not joined) */
if (network_game_server_client_machine_is_joined_to_game(server, client_machine) &&
!TEST_FLAG(client_machine->flags, _network_client_machine_level_loaded_bit))
{
loading_machine_count++;
all_machines_loaded = FALSE;
}
}
/* a line per machine still loading, each time one finishes, is 8,000
lines as 128 machines load, and the host writes its log a line at a time */
if (loading_machine_count)
{
network_event("still waiting for machines to finish loading (%ld left)", loading_machine_count);
}
if (all_machines_loaded == TRUE)
network_game_server_all_machines_have_loaded(server);
return;
}
/* port: the machines still loading the game have gone (a machine that left,
the last it waited for, left it waiting out the wait for the others) */
static void network_game_server_check_loading_complete(
struct network_game_server *server)
{
long client_machine_index;
if (server->state != _network_game_server_state_pregame ||
!server->sent_start_game_message ||
!server->time_of_first_client_loading_completion)
{
return;
}
for (client_machine_index = 0;
client_machine_index < MAXIMUM_NETWORK_MACHINE_COUNT;
client_machine_index++)
{
struct network_game_server_client_machine *client_machine =
&server->client_machines[client_machine_index];
if (network_game_server_client_machine_is_joined_to_game(server, client_machine) &&
!TEST_FLAG(client_machine->flags, _network_client_machine_level_loaded_bit))
{
return;
}
}
network_game_server_all_machines_have_loaded(server);
}
static boolean network_game_server_same_player(
struct network_player const *player0,
struct network_player const *player1);
static boolean network_game_server_player_among(
struct network_player const *player,
struct network_player *players,
long count);
/* port: the players of the machines that left while the game loaded: every
machine spawned them from the settings the game started with, and none was
told they quit (the server was in the pregame, which sends no quit messages):
they quit now */
static void network_game_server_remove_players_gone_while_loading(
struct network_game_server *server)
{
long count = (long)NUMBEROF(server->game.players);
long index;
for (index = 0; index < count; index++)
{
struct network_player *player = &network_game_server_start_players[index];
struct message_server_remove_player_ingame remove_player;
void *message;
if (!network_player_is_valid(player) || network_game_server_player_among(player, server->game.players, count))
continue;
remove_player.player = *player;
remove_player.reason = game_time_get() + NETWORK_GAME_PLAYER_QUIT_DELAY;
message = create_network_game_message(_message_server_remove_player_ingame, &remove_player,
sizeof(remove_player));
if (message && !network_game_server_send_message_to_all_machines(server, message))
network_event("some machines missed the quit of a player gone while the game loaded");
network_event("a player gone while the game loaded quits (machine #%d / controller #%d)",
player->machine_index, player->controller_index);
}
csmemset(network_game_server_start_players, NONE, sizeof(network_game_server_start_players));
}
void network_game_server_handle_client_update_packet(
struct network_game_server *server,
struct network_game_server_client_machine *machine,
struct message_client_game_update *message_packet)
{
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x51E, server);
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x51F, machine);
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x520, message_packet);
/* (a client's out of sync, the update number's top bit, was the
lockstep netcode's, and ended the game: the distributed netcode's machines
are never in sync, and the host's game is the game) */
if ((message_packet->update_number & CLIENT_UPDATE_SEQUENCE_NUMBER_MASK) <
machine->last_received_update_sequence_number)
{
/* (a datagram overtaken by a newer: dropped without a line in the
log for each, ten times a second from every machine) */
}
else if (message_packet->player_count < 0 ||
message_packet->player_count > MAXIMUM_PLAYERS_PER_MACHINE)
{
network_event(
"client update packet from machine #%d had a bad player count; ignoring",
machine->machine_index);
}
else
{
struct player_action actions[MAXIMUM_PLAYERS_PER_MACHINE] = { 0 };
long player_index;
for (player_index = 0;
player_index < message_packet->player_count;
player_index++)
{
actions[player_index] = message_packet->actions[player_index];
}
update_server_handle_client_update(machine->machine_index, actions);
machine->last_received_update_sequence_number =
message_packet->update_number & CLIENT_UPDATE_SEQUENCE_NUMBER_MASK;
}
return;
}
boolean network_game_server_add_player_to_game(
struct network_game_server *server,
struct network_game_server_client_machine *machine,
struct network_player *player)
{
boolean success;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x46C, server);
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x46D, machine);
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x46E, player);
if (machine->machine_index == player->machine_index)
{
/* port: the smaller team (the Xbox game took turns with a counter of
its own, which a failed add or an earlier game moved on) */
{
long player_count_by_team[NUMBER_OF_MULTIPLAYER_TEAMS] = { 0, 0 };
long player_index;
for (player_index = 0; player_index < MAXIMUM_NETWORK_PLAYER_COUNT; player_index++)
{
struct network_player *other_player = &server->game.players[player_index];
if (network_player_is_valid(other_player) &&
VALID_INDEX(other_player->team_index, NUMBER_OF_MULTIPLAYER_TEAMS))
{
player_count_by_team[other_player->team_index]++;
}
}
player->team_index = player_count_by_team[1] < player_count_by_team[0] ? 1 : 0;
}
/* (the name comes from the wire) */
player->name[NETWORK_PLAYER_NAME_LENGTH - 1] = 0;
if (!player->name[0])
get_unique_random_name(server, player);
if (!player_name_is_unique(server, player->name))
get_unique_random_name(server, player);
if (player->primary_color_index == NONE)
get_unique_random_color(server, player);
/* (the host chooses the player's slot, which is its datum on every
machine: network_game_add_player) */
player->player_list_index = NONE;
success = network_game_add_player(&server->game, player);
if (success == TRUE)
{
network_event(
"server added player from machine #%d at controller index #%d to the game",
player->machine_index,
player->controller_index);
}
else
{
network_event(
"network_game_add_player() failed in network_game_server_add_player_to_game()");
}
}
else
{
network_event(
"client machine tried to add a player with a non-matching machine identifier");
success = FALSE;
}
return success;
}
void network_game_server_update_ticks(
struct network_game_server *server,
short tick_count)
{
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x553, server);
switch (network_game_server_get_state(server, NULL))
{
case _network_game_server_state_ingame:
{
struct network_game_server_client_machine *client_machine = NULL;
long client_machine_index;
short tick_index;
for (tick_index = 0; tick_index < tick_count; tick_index++)
{
struct message_server_game_update game_update;
long update_number = server->next_update_number++;
void *message;
update_server_next_update();
/* (the distributed netcode relays the actions unreliably, each
tick's buttons with the next ticks', network_distributed.c: this
update only keeps the clients' count of the host's ticks and
gives them the host's time, which is all they read of it; with
no players, none of its actions is sent or read) */
game_update.update_number = update_number;
game_update.random_seed = 0;
game_update.game_time = game_time_get();
game_update.unknown0C = 0;
game_update.player_count = 0;
message = create_network_game_message(
_message_server_game_update,
&game_update,
sizeof(game_update));
/* port: a machine takes only the update's number and time (its
clock starts with the first, and follows the host's time), not a
reliable message and its acknowledgement every tick: every tick
for a machine's first second in the game, then once a second (a
machine still loading the game in progress gets none, as it gets
none of the game's messages) */
if (message)
{
boolean sent = TRUE;
for (client_machine_index = 0;
client_machine_index < MAXIMUM_NETWORK_MACHINE_COUNT;
client_machine_index++)
{
struct network_game_server_client_machine *machine =
&server->client_machines[client_machine_index];
if (network_game_server_client_machine_is_joined_to_game(server, machine) &&
TEST_FLAG(machine->flags, _network_client_machine_level_loaded_bit) &&
(update_number < network_game_server_frequent_updates_until[client_machine_index] ||
update_number % TICKS_PER_SECOND == 0) &&
network_connection_active(machine->connection) &&
!network_game_server_send_message_to_client_machine(server, machine, message))
{
sent = FALSE;
}
}
if (!sent)
{
network_event(
"server failed to send game update message to all machines; client machine may be out of sync");
}
}
}
if (!server->queued_player_valid && server->waiting_player_count > 0)
{
csmemcpy(&server->queued_player, &server->waiting_players[0], sizeof(server->queued_player));
server->waiting_player_count--;
csmemmove(
&server->waiting_players[0],
&server->waiting_players[1],
server->waiting_player_count * sizeof(struct network_player));
server->queued_player_valid = TRUE;
}
if (server->queued_player_valid)
{
/* (a machine's slot is its index; the handlers queued only a
machine's own players) */
client_machine_index = server->queued_player.machine_index;
if (VALID_INDEX(client_machine_index, MAXIMUM_NETWORK_MACHINE_COUNT) &&
network_game_server_client_machine_is_joined_to_game(server,
&server->client_machines[client_machine_index]))
{
client_machine = &server->client_machines[client_machine_index];
}
if (client_machine &&
network_game_server_machine_may_add_player_ingame(server, client_machine_index) &&
network_game_server_add_player_to_game(
server,
client_machine,
&server->queued_player))
{
if (!network_game_server_client_machine_is_local(server, client_machine))
{
short *count = network_game_server_ingame_addition_count(
network_game_server_client_machine_addresses[client_machine_index], TRUE);
if (count)
(*count)++;
}
if (!network_game_server_send_player_joined_info_ingame(
server,
&server->queued_player))
{
network_event(
"network_game_server_send_player_joined_info_ingame() failed in network_game_server_handle_message_client_add_player_request_ingame()");
}
/* a machine joining the game in progress, its players all in:
it loads the game now */
if (!TEST_FLAG(client_machine->flags, _network_client_machine_level_loaded_bit) &&
!TEST_FLAG(client_machine->flags, _network_client_machine_started_late_bit) &&
!network_game_server_machine_has_waiting_players(server, client_machine->machine_index))
{
network_game_server_start_late_joiner(server, client_machine);
}
}
else
{
network_event("server failed to add a network player in-game");
/* port: a machine joining the game in progress whose last
player could not join (the game is full): started with those
that did, or refused, not left waiting for ever */
if (client_machine &&
!TEST_FLAG(client_machine->flags, _network_client_machine_level_loaded_bit) &&
!TEST_FLAG(client_machine->flags, _network_client_machine_started_late_bit) &&
!network_game_server_machine_has_waiting_players(server, client_machine->machine_index))
{
if (network_game_server_machine_has_players(server, client_machine->machine_index))
network_game_server_start_late_joiner(server, client_machine);
else
network_game_server_refuse_late_joiner(server, client_machine, _rejection_code_game_is_full);
}
}
server->queued_player_valid = FALSE;
}
}
break;
case _network_game_server_state_postgame:
game_engine_update();
break;
}
return;
}
/* ---------- joining a game in progress (the distributed netcode's)
A machine may join a distributed game in progress: the host keeps the game
open, accepts the machine's join as in the pregame, and adds its players as
it adds a player in game (every machine in the game spawns them, told by
_message_server_add_player_ingame). Then the host sends the machine alone
the settings and the start, with its game time, and the machine loads the
game and runs its clock from there; the distributed netcode gives it the
host's objects when it has loaded (network_objects.c). Until then the
machine hears none of the game's messages (network_game_server_send_message_to_all_machines),
which a machine in the pregame would refuse. */
boolean network_game_server_accepts_late_joins(
struct network_game_server *server)
{
/* (and a player can join it: a machine none of whose players can is
refused, network_game_server_refuse_late_joiner) */
return server->state == _network_game_server_state_ingame &&
network_game_server_game_is_open(server) &&
network_game_has_free_player_slot(&server->game);
}
boolean network_game_server_client_machine_is_loaded(
struct network_game_server *server,
struct network_game_server_client_machine *machine)
{
(void)server;
return TEST_FLAG(machine->flags, _network_client_machine_level_loaded_bit);
}
/* whether players of the machine are still waiting to be added (after the
one being added) */
static boolean network_game_server_machine_has_waiting_players(
struct network_game_server *server,
long machine_index)
{
long index;
for (index = 0; index < server->waiting_player_count; index++)
{
if (server->waiting_players[index].machine_index == machine_index)
return TRUE;
}
return FALSE;
}
/* whether players of the machine are in the game */
static boolean network_game_server_machine_has_players(
struct network_game_server *server,
long machine_index)
{
long index;
for (index = 0; index < MAXIMUM_NETWORK_PLAYER_COUNT; index++)
{
if (network_player_is_valid(&server->game.players[index]) &&
server->game.players[index].machine_index == machine_index)
{
return TRUE;
}
}
return FALSE;
}
/* a machine joining the game in progress none of whose players could join
(the game filled up): told the game is full, and let go */
static void network_game_server_refuse_late_joiner(
struct network_game_server *server,
struct network_game_server_client_machine *machine,
word reason)
{
struct message_server_machine_rejected rejection = { reason };
struct network_message *message;
network_event("refusing machine #%d: no player of it can join the game", machine->machine_index);
message = create_network_game_message(_message_server_machine_rejected, &rejection, sizeof(rejection));
if (message)
network_game_server_send_message_to_client_machine(server, machine, message);
network_game_server_drop_client_machine(server, machine);
}
/* the players in the settings each machine joining the game in progress
was started with (by machine index): those added and gone while it loaded
it, whose messages it did not hear, it is told of once it has */
static struct network_player late_joiner_players[MAXIMUM_NETWORK_MACHINE_COUNT][NUMBEROF(((struct network_game *)0)->players)];
static boolean network_game_server_same_player(
struct network_player const *player0,
struct network_player const *player1)
{
return player0->machine_index == player1->machine_index &&
player0->controller_index == player1->controller_index &&
player0->player_list_index == player1->player_list_index;
}
/* whether the player is valid and one of the players */
static boolean network_game_server_player_among(
struct network_player const *player,
struct network_player *players,
long count)
{
long index;
for (index = 0; index < count; index++)
{
if (network_player_is_valid(&players[index]) && network_game_server_same_player(&players[index], player))
return TRUE;
}
return FALSE;
}
/* the settings (with the machine's players) and the start, to the machine
alone, the start with the host's game time */
static void network_game_server_start_late_joiner(
struct network_game_server *server,
struct network_game_server_client_machine *machine)
{
struct message_server_begin_game begin_game = { 0 };
struct network_message *message;
/* (the message carries 16 bits: the client has the rest from its first
game update; never 0, which is a game starting) */
begin_game.unused = MAX(game_time_get() & 0xFFFF, 1);
if (!network_game_server_send_game_settings_to_client_machine(server, machine, &server->game, sizeof(server->game)) ||
!(message = create_network_game_message(_message_server_begin_game, &begin_game, sizeof(begin_game))) ||
!network_game_server_send_message_to_client_machine(server, machine, message))
{
network_event("failed to start machine #%d in the game in progress", machine->machine_index);
return;
}
/* (its players at the start are told apart from those of an earlier
machine at its index by this: network_game_server_late_joiner_loaded) */
SET_FLAG(machine->flags, _network_client_machine_started_late_bit, TRUE);
machine->last_heard_time = system_milliseconds();
if (machine->machine_index >= 0 && machine->machine_index < MAXIMUM_NETWORK_MACHINE_COUNT)
{
csmemcpy(late_joiner_players[machine->machine_index], server->game.players,
sizeof(late_joiner_players[machine->machine_index]));
}
network_event("machine #%d joins the game in progress at game tick #%ld", machine->machine_index,
begin_game.unused);
}
/* the machines joining the game in progress (in the pregame, or loading)
hear none of the game's messages, and drop a connection they hear nothing on
for 15 seconds: a pregame keep-alive every 5 */
static void network_game_server_keep_late_joiners_alive(
struct network_game_server *server)
{
unsigned long now = system_milliseconds();
struct message_server_pregame_keep_alive message_packet = { 0 };
long client_machine_index;
if (now - server->time_of_last_keep_alive <= 5UL * MILLISECONDS_PER_SECOND)
return;
server->time_of_last_keep_alive = now;
for (client_machine_index = 0; client_machine_index < MAXIMUM_NETWORK_MACHINE_COUNT; client_machine_index++)
{
struct network_game_server_client_machine *machine = &server->client_machines[client_machine_index];
struct network_message *message;
if (!network_game_server_client_machine_is_joined_to_game(server, machine) ||
TEST_FLAG(machine->flags, _network_client_machine_level_loaded_bit))
{
continue;
}
message = create_network_game_message(_message_server_pregame_keep_alive, &message_packet,
sizeof(message_packet));
if (message)
network_game_server_send_message_to_client_machine(server, machine, message);
}
}
/* a machine that joined the game in progress has loaded it (or it loaded
it as the game ended: in the postgame, it is only loaded) */
void network_game_server_late_joiner_loaded(
struct network_game_server *server,
struct network_game_server_client_machine *machine)
{
if (server->state == _network_game_server_state_ingame &&
!TEST_FLAG(machine->flags, _network_client_machine_started_late_bit))
{
network_event("ignoring machine #%d's load of a game it was not started in", machine->machine_index);
return;
}
SET_FLAG(machine->flags, _network_client_machine_level_loaded_bit, TRUE);
machine->last_heard_time = system_milliseconds();
network_event("machine #%d has loaded the game in progress", machine->machine_index);
/* (the host's game update every tick for its first second, which sets
its clock) */
if (machine->machine_index >= 0 && machine->machine_index < MAXIMUM_NETWORK_MACHINE_COUNT)
{
network_game_server_frequent_updates_until[machine->machine_index] =
server->next_update_number + TICKS_PER_SECOND;
}
if (server->state == _network_game_server_state_ingame &&
machine->machine_index >= 0 && machine->machine_index < MAXIMUM_NETWORK_MACHINE_COUNT)
{
struct network_player *started = late_joiner_players[machine->machine_index];
long count = (long)NUMBEROF(server->game.players);
long index;
/* the players gone while it loaded (first: a player's machine index
may be a new machine's, which a player added is told apart from by
that only) */
for (index = 0; index < count; index++)
{
struct network_player *player = &started[index];
struct message_server_remove_player_ingame remove_player;
void *message;
if (!network_player_is_valid(player) || network_game_server_player_among(player, server->game.players, count))
continue;
remove_player.player = *player;
remove_player.reason = game_time_get();
message = create_network_game_message(_message_server_remove_player_ingame, &remove_player,
sizeof(remove_player));
if (message)
network_game_server_send_message_to_client_machine(server, machine, message);
network_event("told machine #%d of a player gone while it loaded (machine #%d / controller #%d)",
machine->machine_index, player->machine_index, player->controller_index);
}
/* and those added */
for (index = 0; index < count; index++)
{
struct network_player *player = &server->game.players[index];
struct network_player message_packet;
void *message;
if (!network_player_is_valid(player) || network_game_server_player_among(player, started, count))
continue;
message_packet = *player;
message = create_network_game_message(_message_server_add_player_ingame, &message_packet,
sizeof(message_packet));
if (message)
network_game_server_send_message_to_client_machine(server, machine, message);
network_event("told machine #%d of a player added while it loaded (machine #%d / controller #%d)",
machine->machine_index, player->machine_index, player->controller_index);
}
}
}
void network_game_server_queue_player_for_addition(
struct network_game_server *server,
struct network_player *player)
{
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x5DE, server && player);
/* port: a player already in the game or queued to join it, asked for
again: the pregame screen asks every frame until its player is in the
settings, which a machine joining the game in progress has only once
started, and that waits for the machine's queued players (the repeats
among them, which fail to join) */
{
long index;
if (server->queued_player_valid &&
server->queued_player.machine_index == player->machine_index &&
server->queued_player.controller_index == player->controller_index)
{
return;
}
for (index = 0; index < server->waiting_player_count; index++)
{
if (server->waiting_players[index].machine_index == player->machine_index &&
server->waiting_players[index].controller_index == player->controller_index)
{
return;
}
}
for (index = 0; index < (long)NUMBEROF(server->game.players); index++)
{
if (network_player_is_valid(&server->game.players[index]) &&
server->game.players[index].machine_index == player->machine_index &&
server->game.players[index].controller_index == player->controller_index)
{
return;
}
}
}
if (!server->queued_player_valid && network_player_is_valid(player))
{
csmemcpy(&server->queued_player, player, sizeof(server->queued_player));
server->queued_player_valid = TRUE;
}
else if (network_player_is_valid(player) &&
server->waiting_player_count < MAXIMUM_NETWORK_PLAYER_COUNT)
{
csmemcpy(&server->waiting_players[server->waiting_player_count++], player, sizeof(struct network_player));
}
return;
}
boolean network_game_server_remove_player_from_game(
struct network_game_server *server,
struct network_game_server_client_machine *machine,
struct network_player *player)
{
boolean success;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x4A0, server);
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x4A1, machine);
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x4A2, player);
if (machine->machine_index == player->machine_index)
{
success = network_game_remove_player(&server->game, player);
if (success == TRUE)
{
network_event(
"server removed player from machine #%d at controller index #%d from the game",
player->machine_index,
player->controller_index);
}
else
{
network_event(
"network_game_remove_player() failed in network_game_server_remove_player_from_game()");
}
}
else
{
network_event(
"client machine tried to remove a player with a non-matching machine identifier");
success = FALSE;
}
return success;
}
boolean network_game_server_adjust_machine_settings(
struct network_game_server *server,
struct network_game_server_client_machine *machine,
struct network_machine *machine_description)
{
boolean success = FALSE;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x4BF,
server && machine && machine_description);
if (machine->machine_index == machine_description->machine_index)
{
success = network_game_update_machine(&server->game, machine_description);
if (success == TRUE)
{
network_event(
"server updated machine #%d settings",
machine_description->machine_index);
}
else
{
network_event(
"network_game_update_machine() failed in network_game_server_adjust_machine_settings()");
}
}
else
{
network_event(
"client machine tried to update itself with a non-matching machine identifier");
}
return success;
}
void network_game_server_begin_game_start_countdown(
struct network_game_server *server,
long time_remaining)
{
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x5ED, server);
if (!server->countdown_state.active && !server->countdown_state.paused)
{
countdown_timer_set_time_remaining(
&server->countdown_state.timer,
time_remaining);
server->countdown_state.adjusted_time_this_tick = FALSE;
server->countdown_state.active = TRUE;
network_game_server_countdown_started(server);
network_event("server game start countdown started");
}
return;
}
boolean server_needs_more_teams(
struct network_game_server *server)
{
boolean needs_more_teams = FALSE;
if (server->game.variant.universal_variant.teams)
{
short player_count_by_team[NUMBER_OF_MULTIPLAYER_TEAMS] = { 0, 0 };
long player_index;
long team_index;
for (player_index = 0;
player_index < MAXIMUM_NETWORK_PLAYER_COUNT;
player_index++)
{
struct network_player *player = &server->game.players[player_index];
if (network_player_is_valid(player) &&
player->team_index >= 0 &&
player->team_index < NUMBER_OF_MULTIPLAYER_TEAMS)
{
player_count_by_team[player->team_index]++;
}
}
for (team_index = 0;
team_index < NUMBER_OF_MULTIPLAYER_TEAMS;
team_index++)
{
if (player_count_by_team[team_index] == 0)
{
needs_more_teams = TRUE;
break;
}
}
}
return needs_more_teams;
}
boolean server_has_a_player_on_each_machine(
struct network_game_server *server)
{
long client_machine_index;
for (client_machine_index = 0;
client_machine_index < MAXIMUM_NETWORK_MACHINE_COUNT;
client_machine_index++)
{
struct network_game_server_client_machine *client_machine =
&server->client_machines[client_machine_index];
/* (the machines in the game: not a connection that has not joined) */
if (network_game_server_client_machine_is_joined_to_game(server, client_machine))
{
boolean has_a_player = FALSE;
long player_index;
for (player_index = 0;
player_index < MAXIMUM_NETWORK_PLAYER_COUNT;
player_index++)
{
if (network_player_is_valid(&server->game.players[player_index]) &&
server->game.players[player_index].machine_index ==
client_machine->machine_index)
{
has_a_player = TRUE;
}
}
if (!has_a_player)
return FALSE;
}
}
return TRUE;
}
boolean server_has_enough_machines(
struct network_game_server *server)
{
boolean has_enough_machines;
long minimum_machine_count =
network_game_is_splitscreen_local() ? 1 : 2;
long machine_count = 0;
long client_machine_index;
for (client_machine_index = 0;
client_machine_index < MAXIMUM_NETWORK_MACHINE_COUNT;
client_machine_index++)
{
struct network_game_server_client_machine *client_machine =
&server->client_machines[client_machine_index];
if (network_game_server_client_machine_is_joined_to_game(server, client_machine))
{
machine_count++;
}
}
#ifdef HALO_ANDROID
/* (bots' machines count: the VR port's) */
machine_count += network_game_server_bot_machine_count(server);
#endif
has_enough_machines = machine_count >= minimum_machine_count;
return has_enough_machines;
}
boolean server_ok_to_countdown(
struct network_game_server *server)
{
if (server_has_enough_machines(server) &&
server_has_a_player_on_each_machine(server) &&
!server_needs_more_teams(server) &&
server->game.player_count >= server->game.minimum_players)
{
return TRUE;
}
return FALSE;
}
void network_game_server_invalidate_network_machine(
struct network_machine *machine)
{
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x6C9, machine);
csmemset(machine, 0, sizeof(*machine));
machine->machine_index = NONE;
return;
}
void network_game_generate_join_game_token(
byte join_token[NETWORK_JOIN_GAME_TOKEN_SIZE])
{
byte join_token_initializer[] =
{
0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20,
0x69, 0x6E, 0x20, 0x61, 0x20, 0x62, 0x6F, 0x74,
0x74, 0x6C, 0x65
};
match_assert(NETWORK_SERVER_MANAGER_FILE, 1754, join_token);
/* January and the supplied source both clear the decayed pointer's size. */
memset(join_token, 0, sizeof(join_token));
memcpy(join_token, join_token_initializer,
MIN(NETWORK_JOIN_GAME_TOKEN_SIZE, sizeof(join_token_initializer)));
#ifndef DEBUG
{
char *build_timestamp = __DATE__ __TIME__;
int i, j, length = strlen(build_timestamp);
unsigned long tags_checksum = tag_groups_checksum();
/* Release builds also incorporate compilation time and tag checksum. */
for (i = 0; i < sizeof(join_token); i++)
{
for (j = 0; j < length; j++)
{
join_token[i] ^= build_timestamp[j];
}
}
for (i = j = 0; i < sizeof(join_token); i++)
{
join_token[i] ^= ((byte *)&tags_checksum)[j++];
if (j == sizeof(tags_checksum))
{
j = 0;
}
}
}
#endif
return;
}
struct network_machine *network_game_server_get_client_machine(
struct network_game_server *server,
struct network_game_server_client_machine *client_machine,
long *machine_index)
{
struct network_machine *machine;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x701, server && client_machine);
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x702,
client_machine->machine_index<MAXIMUM_NETWORK_MACHINE_COUNT);
if (machine_index)
*machine_index = NONE;
machine = &server->game.machines[client_machine->machine_index];
if (machine_index)
*machine_index = machine->machine_index;
return machine;
}
struct network_connection *network_game_server_get_machine_connection(
struct network_game_server *server,
struct network_machine *machine)
{
struct network_connection *connection = NULL;
long index;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x72F,
server && network_machine_is_valid(machine));
for (index = 0; index < MAXIMUM_NETWORK_MACHINE_COUNT; index++)
{
if (server->client_machines[index].machine_index == machine->machine_index)
{
connection = server->client_machines[index].connection;
break;
}
}
return connection;
}
struct network_game_server_client_machine *network_game_server_get_client_machine_at_index(
struct network_game_server *server,
long index)
{
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x741,
server && (index<MAXIMUM_NETWORK_MACHINE_COUNT));
return &server->client_machines[index];
}
struct network_game_server_client_machine *network_game_server_get_client_machine_at_address(
struct network_game_server *server,
unsigned long ip_address)
{
struct network_game_server_client_machine *client_machine = NULL;
long i;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x74D, server && ip_address);
/* port: from the addresses the machines joined from (every datagram
asked each machine's connection for its address) */
for (i = 0; i < MAXIMUM_NETWORK_MACHINE_COUNT; i++)
{
/* (a machine in the game: not a connection that has not joined) */
if (network_game_server_client_machine_addresses[i] == ip_address &&
network_game_server_client_machine_is_joined_to_game(server, &server->client_machines[i]))
{
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x755,
server->client_machines[i].connection);
client_machine = &server->client_machines[i];
break;
}
}
/* (none: a datagram from a machine not in the game, which is dropped
without a line in the log for each) */
return client_machine;
}
boolean network_game_server_game_can_start(
struct network_game_server *server)
{
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x782, server);
return server->state == 0 &&
server->game.player_count >= server->game.minimum_players;
}
void network_game_server_pause_countdown(
struct network_game_server *server,
boolean pause_countdown)
{
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x78C, server);
if (pause_countdown == TRUE)
csmemset(&server->countdown_state, 0, sizeof(server->countdown_state));
server->countdown_state.paused = pause_countdown;
return;
}
void network_game_server_change_map_name(
struct network_game_server *server,
char const *map_name)
{
long client_machine_index;
match_assert(
NETWORK_SERVER_MANAGER_FILE,
0x79B,
server && map_name && map_name[0]);
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x79C,
server->state == _network_game_server_state_pregame);
for (client_machine_index = 0;
client_machine_index < MAXIMUM_NETWORK_MACHINE_COUNT;
client_machine_index++)
{
struct network_game_server_client_machine *client_machine =
&server->client_machines[client_machine_index];
if (client_machine->machine_index >= 0 &&
client_machine->machine_index < MAXIMUM_NETWORK_MACHINE_COUNT)
{
SET_FLAG(
client_machine->flags,
_network_client_machine_precached_bit,
FALSE);
}
}
csstrncpy(
server->game.map.name,
map_name,
NETWORK_GAME_MAP_NAME_LENGTH - 1);
server->game.map.name[NETWORK_GAME_MAP_NAME_LENGTH - 1] = 0;
if (!network_game_server_send_game_data_pregame(server))
{
network_event(
"network_game_server_change_map_name() failed to send updated game settings to clients");
}
return;
}
void network_game_server_change_game_variant(
struct network_game_server *server,
struct game_variant *variant)
{
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x7BE, server && variant);
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x7BF,
server->state == _network_game_server_state_pregame);
csmemcpy(&server->game.variant, variant, sizeof(server->game.variant));
network_game_server_variant_options(&server->game.variant, &server->game.variant_options);
if (!network_game_server_send_game_data_pregame(server))
{
network_event(
"network_game_server_change_game_variant() failed to send updated game settings to clients");
}
return;
}
boolean network_game_server_remove_client_machine_from_game(
struct network_game_server *server,
struct network_game_server_client_machine *client)
{
boolean success = FALSE;
int i;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x22F, server);
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x230, client);
if (_network_game_server_state_ingame == server->state)
{
network_game_server_send_player_quit_messages_ingame(server, client);
}
for (i = 0; i < MAXIMUM_NETWORK_MACHINE_COUNT; i++)
{
/* (not a slot no machine holds, for a client already removed) */
if (client->machine_index != NONE &&
server->game.machines[i].machine_index == client->machine_index)
{
if (!network_game_remove_machine(
&server->game,
&server->game.machines[i]))
{
error(
_error_silent,
"network_game_server_remove_client_machine_from_game() failed to remove the offending machine from the server's copy of the game");
}
break;
}
}
for (i = 0; i < MAXIMUM_NETWORK_MACHINE_COUNT; i++)
{
if (&server->client_machines[i] == client)
{
if (server->client_machines[i].connection != NULL)
{
if (!network_server_close_client_connection(
server->connection,
server->client_machines[i].connection))
{
network_event("server failed to close a client's connection");
}
}
{
/* players this machine queued to join in game go with it: a
machine that joins later may get its index */
long waiting_index = 0;
if (server->queued_player_valid &&
server->queued_player.machine_index == client->machine_index)
{
server->queued_player_valid = FALSE;
}
while (waiting_index < server->waiting_player_count)
{
if (server->waiting_players[waiting_index].machine_index == client->machine_index)
{
server->waiting_player_count--;
csmemmove(
&server->waiting_players[waiting_index],
&server->waiting_players[waiting_index + 1],
(server->waiting_player_count - waiting_index) * sizeof(struct network_player));
}
else
{
waiting_index++;
}
}
}
server->client_machines[i].connection = NULL;
server->client_machines[i].last_received_update_sequence_number = 0;
server->client_machines[i].last_heard_time = 0;
server->client_machines[i].machine_index = NONE;
server->client_machines[i].flags = 0;
network_game_server_client_machine_addresses[i] = 0;
success = TRUE;
break;
}
}
if (!success)
{
network_event(
"network_game_server_remove_client_machine_from_game() failed to find the specified machine");
}
else
{
/* the machine may have been the last the others waited for */
network_game_server_check_loading_complete(server);
}
return success;
}
/* a client machine gone: out of the game if it joined it, else (a
connection that never joined, whose machine the game does not have) only
its connection */
static boolean network_game_server_drop_client_machine(
struct network_game_server *server,
struct network_game_server_client_machine *client)
{
if (network_game_server_client_machine_is_joined_to_game(server, client) &&
network_game_server_remove_machine_from_game(server, &server->game.machines[client->machine_index]))
{
return TRUE;
}
if (client->machine_index == NONE)
{
return TRUE;
}
return network_game_server_remove_client_machine_from_game(server, client);
}
void network_game_server_client_machine_heard(
struct network_game_server *server,
struct network_game_server_client_machine *machine)
{
(void)server;
machine->last_heard_time = system_milliseconds();
}
boolean network_game_server_client_machine_is_local(
struct network_game_server *server,
struct network_game_server_client_machine *machine)
{
struct network_game_client *client = global_network_game_client_get();
return client &&
network_game_server_client_machine_is_joined_to_game(server, machine) &&
network_game_client_get_machine_index(client) == machine->machine_index;
}
/* port: a connection that has not joined in time, or a machine in the game
gone silent (the lockstep netcode's stall timed it out; the distributed
netcode waits for no machine) */
static boolean network_game_server_client_machine_timed_out(
struct network_game_server *server,
struct network_game_server_client_machine *machine)
{
unsigned long silence = system_milliseconds() - machine->last_heard_time;
if (!network_game_server_client_machine_is_joined_to_game(server, machine))
return silence > NETWORK_GAME_SERVER_JOIN_TIMEOUT;
if (server->state != _network_game_server_state_ingame ||
network_game_server_client_machine_is_local(server, machine))
{
return FALSE;
}
if (TEST_FLAG(machine->flags, _network_client_machine_level_loaded_bit))
return silence > NETWORK_GAME_SERVER_CLIENT_TIMEOUT;
/* (joining the game in progress: waiting for its players to be added,
or loading) */
return silence > NETWORK_GAME_SERVER_LATE_JOINER_TIMEOUT;
}
boolean network_game_server_remove_machine_from_game(
struct network_game_server *server,
struct network_machine *machine)
{
boolean success = FALSE;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x299, server);
if (NONE == machine->machine_index)
{
network_event(
"network_game_server_remove_machine_from_game called with a machine_index of NONE");
}
if (network_machine_is_valid(machine))
{
int i;
for (i = 0; i < MAXIMUM_NETWORK_MACHINE_COUNT; i++)
{
if (server->client_machines[i].machine_index == machine->machine_index)
{
success = network_game_server_remove_client_machine_from_game(
server,
&server->client_machines[i]);
if (!success)
{
network_event(
"network_game_server_remove_client_machine_from_game() failed in network_game_server_remove_machine_from_game()");
}
break;
}
}
if (i == MAXIMUM_NETWORK_MACHINE_COUNT)
{
network_event(
"network_game_server_remove_machine_from_game() failed to find the specified machine");
}
if (machine->machine_index != NONE)
{
success = network_game_remove_machine(&server->game, machine);
if (!success)
{
network_event(
"network_game_remove_machine() failed in network_game_server_remove_machine_from_game()");
}
}
/* (the lobby's: not to machines loading the game, which have the
settings it started with, and are told of a player gone once it has
loaded, network_game_server_remove_players_gone_while_loading) */
if (network_game_server_lobby_is_open(server))
{
boolean sent_game_settings =
network_game_server_send_game_data_pregame(server);
if (!sent_game_settings)
{
network_event(
"network_game_server_remove_machine_from_game() failed to send updated game settings to remaining clients");
}
}
}
else
{
network_event(
"attempted to remove an invalid machine from the game in network_game_server_remove_machine_from_game()");
network_event("machine name = <not implemented>");
network_event("machine index = %x", machine->machine_index);
network_game_server_dump(server);
}
return success;
}
static void dump_network_game_data(
char *prefix,
struct network_game *network_game_data)
{
#ifdef DEBUG
network_event("%snetwork_game_data", prefix);
network_event("%smachine_count %d", prefix, network_game_data->machine_count);
{
long itr;
for (itr = 0; itr < MAXIMUM_NETWORK_MACHINE_COUNT; itr++)
{
/* the native builds log only the slots in use, a line each: each
line reopens the log, and 128 empty slots took seconds */
if (network_game_data->machines[itr].machine_index == NONE)
{
continue;
}
network_event(
"\t%smachine %d %x",
prefix,
itr,
network_game_data->machines[itr].machine_index);
}
}
network_event("%splayer_count %d", prefix, network_game_data->player_count);
{
long itr;
for (itr = 0; itr < MAXIMUM_NETWORK_PLAYER_COUNT; itr++)
{
if (network_game_data->players[itr].machine_index == NONE)
{
continue;
}
network_event("%splayer %d: machine_index %x controller_index %x team_index %x player_list_index %x",
prefix,
itr,
network_game_data->players[itr].machine_index,
network_game_data->players[itr].controller_index,
network_game_data->players[itr].team_index,
network_game_data->players[itr].player_list_index);
}
}
network_event("%snetwork_game_random_seed %x", prefix,
network_game_data->random_seed);
network_event("%snumber_of_games_played %d", prefix,
network_game_data->number_of_games_played);
#endif
return;
}
static void network_game_server_dump(
struct network_game_server *server)
{
#ifdef DEBUG
long itr;
network_event("*************BEGIN*************");
network_event("\tconnection %x", server->connection);
network_event("\tstate %x", server->state);
network_event("\tflags %x", server->flags);
dump_network_game_data("\t", &server->game);
network_event("client_machines:");
for (itr = 0; itr < MAXIMUM_NETWORK_MACHINE_COUNT; itr++)
{
struct network_game_server_client_machine *client_machine =
server->client_machines + itr;
char *connection_status = "no connection";
if (client_machine->connection != NULL)
{
connection_status = network_connection_active(client_machine->connection)
? "(active)" : "(dead)";
}
if (client_machine->connection == NULL && client_machine->machine_index == NONE)
{
continue;
}
network_event("\tclient %d: connection %x %s last_received_update_sequence_number %d last_heard_time %d machine_index %x flags %x",
itr,
client_machine->connection,
connection_status,
client_machine->last_received_update_sequence_number,
client_machine->last_heard_time,
client_machine->machine_index,
client_machine->flags);
}
network_event("\tnext_update_number %d", server->next_update_number);
network_event("\ttime_of_last_keep_alive %d", server->time_of_last_keep_alive);
network_event("\ttime_of_first_client_loading_completion %d",
server->time_of_first_client_loading_completion);
network_event("*************END*************");
#endif
return;
}
void network_game_server_update_countdown(
struct network_game_server *server,
short countdown_event)
{
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x66E,
server && server->state == _network_game_server_state_pregame);
if (server->countdown_state.paused == FALSE)
{
boolean ok_to_countdown = server_ok_to_countdown(server);
if (ok_to_countdown ||
countdown_event == _network_game_server_countdown_event_stop)
{
if (server->countdown_state.active == TRUE)
{
if (!server->countdown_state.adjusted_time_this_tick)
{
switch (countdown_event)
{
case _network_game_server_countdown_event_player_left:
server->countdown_state.adjusted_time_this_tick = TRUE;
countdown_timer_increment(
&server->countdown_state.timer,
NETWORK_GAME_COUNTDOWN_ADJUSTMENT,
NETWORK_GAME_COUNTDOWN_TIME);
break;
case _network_game_server_countdown_event_player_joined:
server->countdown_state.adjusted_time_this_tick = TRUE;
if (countdown_timer_get_time_remaining(&server->countdown_state.timer) >
NETWORK_GAME_MINIMUM_COUNTDOWN_TIME)
{
countdown_timer_decrement(
&server->countdown_state.timer,
NETWORK_GAME_COUNTDOWN_ADJUSTMENT);
if (countdown_timer_get_time_remaining(&server->countdown_state.timer) <
NETWORK_GAME_MINIMUM_COUNTDOWN_TIME)
{
countdown_timer_set_time_remaining(
&server->countdown_state.timer,
NETWORK_GAME_MINIMUM_COUNTDOWN_TIME);
}
}
break;
case _network_game_server_countdown_event_stop:
server->countdown_state.active = FALSE;
server->countdown_state.adjusted_time_this_tick = TRUE;
break;
case _network_game_server_countdown_event_start_immediately:
server->countdown_state.adjusted_time_this_tick = TRUE;
countdown_timer_set_time_remaining(&server->countdown_state.timer, 0);
break;
}
}
}
else
{
unsigned long now = system_milliseconds();
if (countdown_event == _network_game_server_countdown_event_start_immediately)
{
countdown_timer_set_time_remaining(&server->countdown_state.timer, 0);
server->countdown_state.active = TRUE;
server->countdown_state.adjusted_time_this_tick = FALSE;
network_game_server_countdown_started(server);
}
else
{
if (network_game_should_accept_remote_connections() == FALSE ||
network_game_server_get_client_machine_count(server)
#ifdef HALO_ANDROID
+ network_game_server_bot_machine_count(server)
#endif
> 1)
{
unsigned long countdown;
if (network_game_is_splitscreen_local())
countdown = NETWORK_GAME_SPLITSCREEN_COUNTDOWN_TIME;
else
countdown = NETWORK_GAME_COUNTDOWN_TIME;
server->countdown_state.active = TRUE;
countdown_timer_set_time_remaining(
&server->countdown_state.timer,
countdown);
server->countdown_state.adjusted_time_this_tick = FALSE;
server->countdown_state.last_countdown_message_time = 0;
network_game_server_countdown_started(server);
}
}
}
}
}
return;
}
/* port: the lobby takes changes (players, machines' and players' settings):
not once the game has started, which every machine loads from the settings
sent with its start */
boolean network_game_server_lobby_is_open(
struct network_game_server *server)
{
return server->state == _network_game_server_state_pregame && !server->sent_start_game_message;
}
/* port: a client machine's slower (the pregame screen's, adding time to the
countdown): once each countdown, and not in its last ten seconds (the Xbox
game took any number from any machine, which could hold the lobby for ever);
the host's own as often as it likes */
boolean network_game_server_client_machine_may_slow_countdown(
struct network_game_server *server,
struct network_game_server_client_machine *machine)
{
if (!server->countdown_state.active ||
network_game_server_client_machine_is_local(server, machine))
{
return TRUE;
}
if (!VALID_INDEX(machine->machine_index, MAXIMUM_NETWORK_MACHINE_COUNT) ||
network_game_server_countdown_slowed[machine->machine_index] ||
countdown_timer_get_time_remaining(&server->countdown_state.timer) < 10 * MILLISECONDS_PER_SECOND)
{
return FALSE;
}
network_game_server_countdown_slowed[machine->machine_index] = TRUE;
return TRUE;
}
/* ---------- private code */
static void network_game_server_countdown_started(
struct network_game_server *server)
{
(void)server;
csmemset(network_game_server_countdown_slowed, 0, sizeof(network_game_server_countdown_slowed));
}
void get_unique_random_name(
struct network_game_server *server,
struct network_player *player)
{
wchar_t const *name;
long duplicate_count;
long attempt_count = 0;
do
{
long player_index;
name = network_game_get_random_player_name();
duplicate_count = 0;
for (player_index = 0;
player_index < MAXIMUM_NETWORK_PLAYER_COUNT;
player_index++)
{
struct network_player *existing_player =
&server->game.players[player_index];
if (network_player_is_valid(existing_player) &&
!ustrcmp(existing_player->name, name))
{
duplicate_count++;
}
}
}
while (duplicate_count != 0 && ++attempt_count < MAXIMUM_UNIQUE_NAME_ATTEMPTS);
ustrncpy(player->name, name, NETWORK_PLAYER_NAME_LENGTH - 1);
player->name[NETWORK_PLAYER_NAME_LENGTH - 1] = 0;
/* every random name is taken: number this one ("Name2", "Name3", ...)
until it is unique */
if (duplicate_count != 0)
{
wchar_t base_name[NETWORK_PLAYER_NAME_LENGTH];
long number;
csmemcpy(base_name, player->name, sizeof(base_name));
for (number = 2; number < 1000 && !player_name_is_unique(server, player->name); number++)
{
wchar_t digits[4];
long digit_count = 0;
long base_length = (long)ustrlen(base_name);
long value;
for (value = number; value; value /= 10)
{
digits[digit_count++] = (wchar_t)(L'0' + value % 10);
}
base_length = MIN(base_length, NETWORK_PLAYER_NAME_LENGTH - 1 - digit_count);
csmemcpy(player->name, base_name, base_length * sizeof(wchar_t));
while (digit_count > 0)
{
player->name[base_length++] = digits[--digit_count];
}
player->name[base_length] = 0;
}
}
return;
}
void get_unique_random_color(
struct network_game_server *server,
struct network_player *player)
{
long attempt_count = 0;
long color_index;
boolean unique;
do
{
long player_index;
color_index = attempt_count < MAXIMUM_GOOD_COLOR_ATTEMPTS
? player_profile_get_random_good_color()
: player_profile_get_random_color();
unique = TRUE;
for (player_index = 0;
player_index < MAXIMUM_NETWORK_PLAYER_COUNT;
player_index++)
{
if (network_player_is_valid(&server->game.players[player_index]) &&
server->game.players[player_index].primary_color_index == color_index)
{
unique = FALSE;
break;
}
}
attempt_count++;
}
while (!unique && attempt_count < MAXIMUM_UNIQUE_COLOR_ATTEMPTS);
player->primary_color_index = (short)color_index;
return;
}
static boolean player_name_is_unique(
struct network_game_server *server,
wchar_t const *name)
{
long player_index;
for (player_index = 0;
player_index < MAXIMUM_NETWORK_PLAYER_COUNT;
player_index++)
{
struct network_player *player = &server->game.players[player_index];
if (network_player_is_valid(player) && !ustrcmp(player->name, name))
return FALSE;
}
return TRUE;
}
static short network_game_server_get_client_machine_count(
struct network_game_server *server)
{
short client_machine_count = 0;
short client_machine_index;
for (client_machine_index = 0;
client_machine_index < MAXIMUM_NETWORK_MACHINE_COUNT;
client_machine_index++)
{
if (server->client_machines[client_machine_index].connection &&
network_game_server_client_machine_is_joined_to_game(server,
&server->client_machines[client_machine_index]))
{
client_machine_count++;
}
}
return client_machine_count;
}
/* port: the PC menus' server settings (port/linux/game/menu_functions.c):
the game's name and the most players it takes, every game the server sets
up; none (empty, 0) keeps the Xbox's (the machine's name, every player the
native builds hold) */
static struct
{
wchar_t name[NETWORK_GAME_NAME_LENGTH];
long maximum_players;
} network_game_server_port_settings;
static void network_game_server_port_settings_apply(
struct network_game_server *server)
{
if (network_game_server_port_settings.name[0])
{
ustrncpy(server->game.name, network_game_server_port_settings.name, NETWORK_GAME_NAME_LENGTH - 1);
server->game.name[NETWORK_GAME_NAME_LENGTH - 1] = 0;
}
if (network_game_server_port_settings.maximum_players > 0)
{
server->game.maximum_players = (byte)PIN(network_game_server_port_settings.maximum_players, 2,
MAXIMUM_NETWORK_PLAYER_COUNT);
}
}
void network_game_server_port_set_settings(
wchar_t const *name,
long maximum_players)
{
struct network_game_server *server = global_network_game_server_get();
ustrncpy(network_game_server_port_settings.name, name ? name : L"", NETWORK_GAME_NAME_LENGTH - 1);
network_game_server_port_settings.name[NETWORK_GAME_NAME_LENGTH - 1] = 0;
network_game_server_port_settings.maximum_players = maximum_players;
if (server)
network_game_server_port_settings_apply(server);
}
void network_game_server_port_cooperative_won(
char const *next_map)
{
struct network_game_server *server = global_network_game_server_get();
if (!server)
return;
csstrncpy(network_game_server_cooperative_next_map, next_map ? next_map : server->game.map.name,
sizeof(network_game_server_cooperative_next_map) - 1);
network_game_server_cooperative_next_map[sizeof(network_game_server_cooperative_next_map) - 1] = 0;
network_game_server_switch_to_postgame(server);
/* Back to the lobby at once, with the next level set up there to start
or change. Multiplayer leaves the postgame when the host presses a button
on its scoreboard (game_engine.c); co-op has neither, and every machine
was left on the level's last screen. */
network_game_server_reset_to_pregame(server);
}
/* port: reapply the co-op settings after a won round, since the playlist
the next round is set up from (network_game_server_setup_game_from_playlist)
has a multiplayer gametype and map */
static void network_game_server_cooperative_round(
struct network_game_server *server)
{
struct game_variant variant;
short friendly_fire = server->game.variant_options.friendly_fire;
if (!network_game_server_cooperative_next_map[0])
return;
csmemset(&variant, 0, sizeof(variant));
ustrncpy(variant.human_readable_game_description, L"Co-op",
NUMBEROF(variant.human_readable_game_description) - 1);
csmemcpy(&server->game.variant, &variant, sizeof(server->game.variant));
network_game_server_variant_options(&server->game.variant, &server->game.variant_options);
/* (Server Setup's FRIENDLY FIRE, for every level of the game) */
server->game.variant_options.friendly_fire = friendly_fire;
csstrncpy(server->game.map.name, network_game_server_cooperative_next_map, sizeof(server->game.map.name) - 1);
server->game.map.name[sizeof(server->game.map.name) - 1] = 0;
main_set_multiplayer_map_name(server->game.map.name);
server->game.maximum_teams = 1;
network_game_server_cooperative_next_map[0] = 0;
}
void network_game_server_port_set_cooperative(
struct network_game_server *server,
short difficulty)
{
if (!server || server->state != _network_game_server_state_pregame)
return;
server->game.difficulty = difficulty;
/* (Server Setup's default for co-op, menu_functions.c's
COOPERATIVE_DEFAULT_PLAYERS, until it sets its own) */
server->game.maximum_players = MIN(16, MAXIMUM_NETWORK_PLAYER_COUNT);
if (!network_game_server_send_game_data_pregame(server))
network_event("network_game_server_port_set_cooperative() failed to send updated game settings to clients");
return;
}
void network_game_server_port_set_cooperative_friendly_fire(
short friendly_fire)
{
struct network_game_server *server = global_network_game_server_get();
if (!server || friendly_fire < 0 || friendly_fire >= NUMBER_OF_FRIENDLY_FIRE_MODES)
return;
server->game.variant_options.friendly_fire = friendly_fire;
if (server->state == _network_game_server_state_pregame && !network_game_server_send_game_data_pregame(server))
network_event("network_game_server_port_set_cooperative_friendly_fire() failed to send updated game settings to clients");
}
/* port: a gametype's PC options: the menus' (player_ui_set_game_variant_options)
when it is the menus' gametype, else its defaults */
static void network_game_server_variant_options(
struct game_variant const *variant,
struct game_variant_options *options)
{
struct game_variant chosen;
if (player_ui_game_variant_specified(&chosen) && !csmemcmp(&chosen, variant, sizeof(chosen)))
*options = *player_ui_get_game_variant_options();
else
game_variant_options_default(variant, options);
}
static boolean network_game_server_setup_game_from_playlist(
struct network_game_server *server)
{
boolean success = FALSE;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x961, server);
network_event("setting up a net game");
if (game_engine_get_current_stage(&server->game.variant, server->game.map.name))
{
wchar_t machine_name[MAXIMUM_MACHINE_NAME_LENGTH] = L"<unknown>";
network_game_generate_local_machine_name(machine_name);
ustrncpy(server->game.name, machine_name, NETWORK_GAME_NAME_LENGTH - 1);
server->game.name[NETWORK_GAME_NAME_LENGTH - 1] = L'\0';
server->game.map.version = 0;
server->game.minimum_players = 2;
server->game.maximum_players = MAXIMUM_NETWORK_PLAYER_COUNT;
network_game_server_port_settings_apply(server);
network_game_server_variant_options(&server->game.variant, &server->game.variant_options);
if (server->game.variant.universal_variant.teams)
{
server->game.maximum_teams = 2;
}
else
{
server->game.maximum_teams = 1;
}
network_game_server_open_game(server);
success = TRUE;
}
else
{
error(
_error_silent,
"network game setup failed; probably due to a missing playlist");
}
return success;
}
static boolean network_game_server_add_new_client(
struct network_game_server *server,
struct network_connection *new_connection)
{
boolean success = FALSE;
int i;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x7D4, server && new_connection);
/* (open, if not to joins now: a machine that joins while the others
load the game is told so, network_game_server_handle_message_client_join_game_request) */
if (TEST_FLAG(server->flags, _network_game_server_game_open_bit))
{
for (i = 0; i < MAXIMUM_NETWORK_MACHINE_COUNT; i++)
{
#ifdef HALO_ANDROID
/* (not a bot machine's slot) */
if (network_game_server_bot_machines[i] && server->game.machines[i].machine_index == i)
continue;
#endif
if (server->client_machines[i].machine_index == NONE)
{
struct transport_address client_address = { 0 };
network_connection_get_address(
new_connection,
&client_address,
NULL);
if (client_address.address.ipv4_address)
{
/* port: the connections of an address that have not joined
yet (each holds a machine's slot until the join timeout):
a few, so that one machine does not take them all */
long waiting_count = 0;
long other_index;
for (other_index = 0;
other_index < MAXIMUM_NETWORK_MACHINE_COUNT &&
client_address.address.ipv4_address != IPV4_LOOPBACK_ADDRESS;
other_index++)
{
struct network_game_server_client_machine *other = &server->client_machines[other_index];
struct transport_address other_address = { 0 };
if (!other->connection || network_game_server_client_machine_is_joined_to_game(server, other))
continue;
network_connection_get_address(other->connection, &other_address, NULL);
if (other_address.address.ipv4_address == client_address.address.ipv4_address)
waiting_count++;
}
if (!network_game_should_accept_remote_connections() &&
client_address.address.ipv4_address != IPV4_LOOPBACK_ADDRESS)
{
network_event(
"remote system tried to join our server but we are not accepting remote connections: address= '%s'",
transport_address_to_string(&client_address));
}
else if (waiting_count >= MAXIMUM_WAITING_CONNECTIONS_PER_ADDRESS)
{
network_event(
"refusing another connection from %s, which has not joined with those it has",
transport_address_to_string(&client_address));
}
else
{
server->client_machines[i].connection = new_connection;
network_game_invalidate_machine(&server->game, i);
server->client_machines[i].machine_index = (short)i;
server->client_machines[i].flags =
FLAG(_network_client_machine_connected_bit);
/* (it has until the join timeout to join) */
server->client_machines[i].last_heard_time = system_milliseconds();
success = network_connection_server_accept_client_connection(
server->connection,
new_connection);
if (success == TRUE)
{
network_event(
"new remote connection accepted from %s",
transport_address_to_string(&client_address));
}
else
{
/* port: the caller closes the connection: the slot
must not keep it */
server->client_machines[i].connection = NULL;
server->client_machines[i].last_heard_time = 0;
server->client_machines[i].machine_index = NONE;
server->client_machines[i].flags = 0;
}
}
}
else
{
network_event(
"network_connection_get_address() failed to get a valid address in network_game_server_add_new_client()");
}
break;
}
}
if (i == MAXIMUM_NETWORK_MACHINE_COUNT)
{
network_event(
"failed to find an available machine slot in network_game_server_add_new_client()");
}
}
else
{
network_event(
"network_game_server_add_new_client() failed because the game is closed");
}
return success;
}
static boolean network_game_server_handle_public_endpoint(
struct network_game_server *server)
{
boolean success = TRUE;
word datagram_buffer[DATAGRAM_MAXIMUM_SIZE / sizeof(word)];
word *message = datagram_buffer;
word datagram_size = sizeof(datagram_buffer);
struct transport_address source_address;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x810, server);
while (success && network_connection_read(
server->connection,
message,
&datagram_size,
&source_address))
{
if ((success = network_game_server_handle_datagram(
server,
message,
datagram_size,
&source_address)) == FALSE)
{
network_event(
"network_game_server_handle_datagram() failed in network_game_server_handle_public_endpoint()");
}
datagram_size = sizeof(datagram_buffer);
}
return success;
}
static boolean network_game_server_handle_client_machines(
struct network_game_server *server)
{
boolean success = TRUE;
int i;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x827, server);
for (i = 0; success && i < MAXIMUM_NETWORK_MACHINE_COUNT; i++)
{
struct network_game_server_client_machine *client_machine = &server->client_machines[i];
if (client_machine->machine_index == NONE)
continue;
if (!network_connection_active(client_machine->connection))
{
short machine_index = client_machine->machine_index;
/* (a connection that never joined goes too: it held its slot, and
the lobby's start, for ever) */
if (network_game_server_drop_client_machine(server, client_machine))
{
network_event("client machine %x removed from game", machine_index);
/* the native builds skip the dump here: it lists every machine
and player, and when many machines leave at once the dumps
keep the host writing its log for minutes */
}
else
{
network_event("failed to remove client machine %x from game", machine_index);
network_game_server_dump(server);
}
}
else if (network_connection_idle(
client_machine->connection,
_connection_dont_timeout,
NULL) &&
network_connection_connected(client_machine->connection))
{
word message_buffer[MAXIMUM_NETWORK_MESSAGE_SIZE / sizeof(word)];
word *message = message_buffer;
word message_buffer_size = sizeof(message_buffer);
while (success && network_connection_read(
client_machine->connection,
message,
&message_buffer_size,
NULL))
{
if (network_game_server_handle_client_message(
server,
client_machine,
message,
message_buffer_size))
{
/* the message may have taken the machine out of the game
(its graceful exit): its connection is gone */
if (client_machine->machine_index == NONE || client_machine->connection == NULL)
break;
/* (a connection that has not joined is timed from when it
connected: what it sends before its join does not count) */
if (network_game_server_client_machine_is_joined_to_game(server, client_machine))
network_game_server_client_machine_heard(server, client_machine);
message_buffer_size = sizeof(message_buffer);
}
else
{
short machine_index = client_machine->machine_index;
network_event(
"network_game_server_handle_client_message() failed in network_game_server_handle_client_machines()");
if (client_machine->machine_index != NONE &&
!network_game_server_drop_client_machine(server, client_machine))
{
network_event("failed to remove client machine %x from game", machine_index);
}
else
{
network_event("client machine %x removed from game", machine_index);
}
break;
}
}
if (client_machine->machine_index != NONE &&
network_game_server_client_machine_timed_out(server, client_machine))
{
short machine_index = client_machine->machine_index;
network_event("client machine %x timed out", machine_index);
if (!network_game_server_drop_client_machine(server, client_machine))
network_event("failed to remove client machine %x from game", machine_index);
}
/* port: one the distributed netcode found cheating, or the host
banned or kicked: told, and dropped; its address kept out but
for a kick's */
else if (VALID_INDEX(client_machine->machine_index, MAXIMUM_NETWORK_MACHINE_COUNT) &&
network_game_server_kick_pending[client_machine->machine_index] != _kick_none)
{
short machine_index = client_machine->machine_index;
struct message_server_machine_rejected rejection = { _rejection_code_blacklisted_machine };
struct network_message *message;
unsigned long address = network_game_server_client_machine_addresses[machine_index];
boolean kept_out = network_game_server_kick_pending[machine_index] == _kick_kept_out;
network_game_server_kick_pending[machine_index] = _kick_none;
if (kept_out && address && !network_game_server_client_machine_is_local(server, client_machine))
{
network_game_server_kicked_addresses[network_game_server_kicked_address_next++ %
MAXIMUM_KICKED_ADDRESSES] = address;
}
message = create_network_game_message(_message_server_machine_rejected, &rejection, sizeof(rejection));
if (message)
network_game_server_send_message_to_client_machine(server, client_machine, message);
if (!network_game_server_drop_client_machine(server, client_machine))
network_event("failed to remove client machine %x from game", machine_index);
}
}
else
{
short machine_index = client_machine->machine_index;
if (network_game_server_drop_client_machine(server, client_machine))
network_event("client machine %x removed from game", machine_index);
else
network_event("failed to remove client machine %x from game", machine_index);
}
}
return success;
}
static void network_game_server_send_rejection_message(
struct transport_endpoint *endpoint,
word reason)
{
struct message_server_machine_rejected farewell_message = { reason };
message_header *message;
match_assert(
NETWORK_SERVER_MANAGER_FILE,
0x878,
endpoint && (reason < NUMBER_OF_SERVER_REJECTION_CODES));
message = create_network_game_message(
_message_server_machine_rejected,
&farewell_message,
sizeof(farewell_message));
if (message != NULL)
{
int length = GET_MESSAGE_SIZE(*message);
int bytes_written;
byte_swap_message_header(message, _byte_order_network);
bytes_written = write_endpoint(endpoint, message, length);
if (bytes_written != length)
{
network_event(
"error sending rejection message to client; transport error= '%s'",
transport_error_to_string(bytes_written));
}
}
else
{
network_event(
"failed to create a message_server_machine_rejected message in network_game_server_send_rejection_message");
}
return;
}
static void network_game_server_reject_connection_game_is_full(
struct transport_endpoint *endpoint)
{
network_event("client connection refused; game is full");
network_game_server_send_rejection_message(
endpoint,
_rejection_code_game_is_full);
return;
}
static boolean network_game_server_idle_postgame_tasks(
struct network_game_server *server)
{
unsigned long now = system_milliseconds();
boolean success = TRUE;
/* (the time since, which the milliseconds' wrap leaves right) */
if (now - (unsigned long)server->time_of_last_keep_alive > 5UL * MILLISECONDS_PER_SECOND)
{
struct message_server_postgame_keep_alive message_packet = { 0 };
struct network_message *message;
message = create_network_game_message(
_message_server_postgame_keep_alive,
&message_packet,
sizeof(message_packet));
network_game_server_send_message_to_all_machines(server, message);
server->time_of_last_keep_alive = (long)now;
}
return success;
}
static boolean network_game_server_have_all_machines_have_precached(
struct network_game_server *server)
{
long i;
boolean all_machines_have_precached = TRUE;
for (i = 0; i < MAXIMUM_NETWORK_MACHINE_COUNT; i++)
{
struct network_game_server_client_machine *machine =
server->client_machines + i;
if (network_game_server_client_machine_is_joined_to_game(server, machine))
{
boolean client_has_precached = TEST_FLAG(
server->client_machines[i].flags,
_network_client_machine_precached_bit);
if (!client_has_precached)
{
all_machines_have_precached = FALSE;
break;
}
}
}
match_assert(
NETWORK_SERVER_MANAGER_FILE,
0x8C8,
!all_machines_have_precached ||
cache_files_precache_map_loaded(main_get_multiplayer_map_name()));
return all_machines_have_precached;
}
/* whether the machine at this index is one of the host's bots (the VR
port's bots.c asks, on every platform: on the desktop no bot machine is
ever marked, so this is always false there) */
boolean network_game_server_is_bot_machine(
long machine_index)
{
struct network_game_server *server = global_network_game_server_get();
return server && VALID_INDEX(machine_index, MAXIMUM_NETWORK_MACHINE_COUNT) &&
network_game_server_bot_machines[machine_index] &&
server->game.machines[machine_index].machine_index == machine_index;
}
#ifdef HALO_ANDROID
/* ---------- bots (the VR port's: port/linux/game/bots.c)
Bots are the players of machines of the host's own that nothing connects
to: from the top slot down, four players each (a machine's most). The host
drives them (bots_tick); every other machine sees them as any other
machine's players. A new connection never takes a bot's slot, and in the
lobby they count as machines, so that a game of the host and bots starts. */
long config_integer(char const *name);
static void network_game_server_bot_copy_name(
wchar_t *destination,
wchar_t const *source,
short size)
{
short index;
for (index = 0; index < size - 1 && source[index]; index++)
destination[index] = source[index];
destination[index] = 0;
}
static short network_game_server_bot_machine_count(
struct network_game_server *server)
{
short count = 0;
long machine_index;
for (machine_index = 0; machine_index < MAXIMUM_NETWORK_MACHINE_COUNT; machine_index++)
{
if (network_game_server_bot_machines[machine_index] &&
server->game.machines[machine_index].machine_index == machine_index &&
network_game_server_machine_has_players(server, (short)machine_index))
{
count++;
}
}
return count;
}
static short network_game_server_bot_count(
struct network_game_server *server,
struct network_player **last)
{
short count = 0;
long player_index;
*last = NULL;
for (player_index = 0; player_index < MAXIMUM_NETWORK_PLAYER_COUNT; player_index++)
{
struct network_player *player = &server->game.players[player_index];
if (network_player_is_valid(player) && VALID_INDEX(player->machine_index, MAXIMUM_NETWORK_MACHINE_COUNT) &&
network_game_server_bot_machines[(long)player->machine_index])
{
count++;
*last = player;
}
}
return count;
}
static boolean network_game_server_add_bot(
struct network_game_server *server,
short number)
{
struct network_player bot;
long machine_index;
short controller_index;
if (!network_game_has_free_player_slot(&server->game))
return FALSE;
/* (a bot machine with room, else a new one from the top) */
for (machine_index = MAXIMUM_NETWORK_MACHINE_COUNT - 1; machine_index >= 0; machine_index--)
{
if (network_game_server_bot_machines[machine_index] &&
server->game.machines[machine_index].machine_index == machine_index)
{
for (controller_index = 0; controller_index < MAXIMUM_LOCAL_PLAYERS; controller_index++)
{
long player_index;
boolean taken = FALSE;
for (player_index = 0; player_index < MAXIMUM_NETWORK_PLAYER_COUNT && !taken; player_index++)
{
struct network_player *player = &server->game.players[player_index];
taken = network_player_is_valid(player) && player->machine_index == machine_index &&
player->controller_index == controller_index;
}
if (!taken)
goto found;
}
}
}
for (machine_index = MAXIMUM_NETWORK_MACHINE_COUNT - 1; machine_index >= 0; machine_index--)
{
if (!server->client_machines[machine_index].connection &&
server->client_machines[machine_index].machine_index == NONE &&
server->game.machines[machine_index].machine_index == NONE)
{
struct network_machine machine;
csmemset(&machine, 0, sizeof(machine));
network_game_server_bot_copy_name(machine.name, L"Bots", NUMBEROF(machine.name));
machine.machine_index = (char)machine_index;
if (!network_game_add_machine(&server->game, &machine))
return FALSE;
network_game_server_bot_machines[machine_index] = TRUE;
controller_index = 0;
goto found;
}
}
return FALSE;
found:
csmemset(&bot, 0, sizeof(bot));
{
wchar_t name[NETWORK_PLAYER_NAME_LENGTH];
short length = 0;
short digits = number < 10 ? 1 : number < 100 ? 2 : 3;
short digit;
network_game_server_bot_copy_name(name, L"Bot ", NUMBEROF(name));
while (name[length])
length++;
for (digit = digits - 1; digit >= 0 && length + digit < NETWORK_PLAYER_NAME_LENGTH - 1; digit--)
{
name[length + digit] = (wchar_t)(L'0' + number % 10);
number /= 10;
}
name[length + digits] = 0;
network_game_server_bot_copy_name(bot.name, name, NUMBEROF(bot.name));
}
bot.machine_index = (char)machine_index;
bot.controller_index = (char)controller_index;
bot.primary_color_index = NONE;
bot.icon_index = 0;
{
long player_count_by_team[NUMBER_OF_MULTIPLAYER_TEAMS] = { 0, 0 };
long player_index;
for (player_index = 0; player_index < MAXIMUM_NETWORK_PLAYER_COUNT; player_index++)
{
struct network_player *other_player = &server->game.players[player_index];
if (network_player_is_valid(other_player) && VALID_INDEX(other_player->team_index, NUMBER_OF_MULTIPLAYER_TEAMS))
player_count_by_team[other_player->team_index]++;
}
bot.team_index = player_count_by_team[1] < player_count_by_team[0] ? 1 : 0;
}
if (!player_name_is_unique(server, bot.name))
get_unique_random_name(server, &bot);
get_unique_random_color(server, &bot);
bot.player_list_index = NONE;
if (!network_game_add_player(&server->game, &bot))
return FALSE;
network_event("added a bot on machine %ld", machine_index);
return TRUE;
}
/* (the lobby) as many bots as bots.count asks for */
static void network_game_server_update_bots(
struct network_game_server *server)
{
long wanted = config_integer("bots.count");
struct network_player *last;
short count = network_game_server_bot_count(server, &last);
boolean changed = FALSE;
long machine_index;
/* (bot machines a new game no longer has) */
for (machine_index = 0; machine_index < MAXIMUM_NETWORK_MACHINE_COUNT; machine_index++)
{
if (network_game_server_bot_machines[machine_index] &&
server->game.machines[machine_index].machine_index != machine_index)
{
network_game_server_bot_machines[machine_index] = FALSE;
}
}
wanted = PIN(wanted, 0, 15);
while (count < wanted && network_game_server_add_bot(server, (short)(count + 1)))
{
count++;
changed = TRUE;
}
while (count > wanted && network_game_server_bot_count(server, &last) > 0 && last)
{
long bot_machine = last->machine_index;
network_game_remove_player(&server->game, last);
if (!network_game_server_machine_has_players(server, (short)bot_machine))
{
network_game_remove_machine(&server->game, &server->game.machines[bot_machine]);
network_game_server_bot_machines[bot_machine] = FALSE;
}
count--;
changed = TRUE;
}
if (changed)
network_game_server_send_game_data_pregame(server);
}
#endif
static boolean network_game_server_idle_pregame_tasks(
struct network_game_server *server)
{
long now = (long)system_milliseconds();
boolean success = TRUE;
if (server->sent_start_game_message == FALSE)
{
long itr;
#ifdef HALO_ANDROID
network_game_server_update_bots(server);
#endif
/* port: the gametype's auto team balance (game_variant_options):
the lobby's teams kept within a player of each other, the bigger
team's last player moved */
if (server->game.variant.universal_variant.teams && server->game.variant_options.auto_team_balance)
{
for (;;)
{
short count[2] = { 0, 0 };
long last[2] = { NONE, NONE };
short bigger;
for (itr = 0; itr < MAXIMUM_NETWORK_PLAYER_COUNT; itr++)
{
struct network_player *player = &server->game.players[itr];
if (network_player_is_valid(player) && VALID_INDEX(player->team_index, 2))
{
count[player->team_index]++;
last[player->team_index] = itr;
}
}
if (ABS(count[0] - count[1]) <= 1)
break;
bigger = count[0] > count[1] ? 0 : 1;
server->game.players[last[bigger]].team_index = 1 - bigger;
network_game_server_send_game_data_pregame(server);
}
}
/* send the lobby changes collected since the last settings update */
network_game_server_flush_game_data_pregame(server);
for (itr = 0; itr < MAXIMUM_NETWORK_MACHINE_COUNT; itr++)
{
struct network_game_server_client_machine *client_machine =
server->client_machines + itr;
if (client_machine->connection != NULL &&
!network_connection_active(client_machine->connection))
{
network_event("booting dead client machine %d", itr);
network_game_server_remove_client_machine_from_game(
server,
client_machine);
}
/* port: a machine that joined and can add no player (the lobby
filled after it joined), or adds none, holds the countdown for
ever: refused */
else if (network_game_server_client_machine_is_joined_to_game(server, client_machine) &&
!network_game_server_client_machine_is_local(server, client_machine) &&
!network_game_server_machine_has_players(server, client_machine->machine_index) &&
(!network_game_has_free_player_slot(&server->game) ||
system_milliseconds() - network_game_server_client_machine_join_times[client_machine->machine_index] >
NETWORK_GAME_SERVER_PLAYERLESS_MACHINE_TIMEOUT))
{
network_game_server_refuse_late_joiner(server, client_machine,
network_game_has_free_player_slot(&server->game) ? _rejection_code_game_is_closed :
_rejection_code_game_is_full);
}
}
if (server->countdown_state.active == TRUE)
{
boolean send_countdown_update = FALSE;
boolean ok_to_countdown = server_ok_to_countdown(server);
if (!ok_to_countdown)
{
csmemset(
&server->countdown_state,
0,
sizeof(server->countdown_state));
send_countdown_update = TRUE;
}
else if (countdown_timer_get_time_remaining(
&server->countdown_state.timer) == 0 &&
network_game_server_have_all_machines_have_precached(server) &&
server->countdown_state.paused == FALSE)
{
/* (the game stays open: a machine may join it in progress,
network_game_server_start_late_joiner) */
if ((success = network_game_server_start_network_game(server)) != TRUE)
network_event("network_game_server_start_network_game() failed");
}
else if ((long)((unsigned long)now -
(unsigned long)server->countdown_state.last_countdown_message_time) >
MILLISECONDS_PER_SECOND)
{
send_countdown_update = TRUE;
}
if (send_countdown_update == TRUE)
{
struct message_server_pregame_countdown message_packet;
struct network_message *message;
server->countdown_state.adjusted_time_this_tick = FALSE;
if (ok_to_countdown)
{
long time_remaining = countdown_timer_get_time_remaining(
&server->countdown_state.timer);
message_packet.seconds_to_start =
(short)(time_remaining / MILLISECONDS_PER_SECOND);
}
else
{
message_packet.seconds_to_start = NONE;
}
message = create_network_game_message(
_message_server_pregame_countdown,
&message_packet,
sizeof(message_packet));
if (message != NULL)
{
if (network_game_server_send_message_to_all_machines(server, message))
{
server->countdown_state.last_countdown_message_time = now;
}
else
{
network_event(
"failed to send a message_server_pregame_countdown to all clients");
}
}
}
}
else
{
if ((unsigned long)now - (unsigned long)server->time_of_last_keep_alive >
5UL * MILLISECONDS_PER_SECOND)
{
struct message_server_pregame_keep_alive message_packet = { 0 };
struct network_message *message;
message = create_network_game_message(
_message_server_pregame_keep_alive,
&message_packet,
sizeof(message_packet));
network_game_server_send_message_to_all_machines(server, message);
server->time_of_last_keep_alive = now;
}
}
}
else if (server->time_of_first_client_loading_completion)
{
/* machines that have loaded wait for the others in silence, and a
client drops a connection it hears nothing on for 15 seconds, less than
the wait for the others: keep their connections alive (a client in game
ignores a pregame keep-alive) */
if ((unsigned long)now - (unsigned long)server->time_of_last_keep_alive >
5UL * MILLISECONDS_PER_SECOND)
{
struct message_server_pregame_keep_alive message_packet = { 0 };
struct network_message *message;
message = create_network_game_message(
_message_server_pregame_keep_alive,
&message_packet,
sizeof(message_packet));
if (message)
{
network_game_server_send_message_to_all_machines(server, message);
}
server->time_of_last_keep_alive = now;
}
if (system_milliseconds() - server->time_of_first_client_loading_completion >=
NETWORK_GAME_SERVER_MAXIMUM_WAIT_TIME_FOR_LEVEL_LOADING)
{
int i;
for (i = 0; i < MAXIMUM_NETWORK_MACHINE_COUNT; i++)
{
if (TEST_FLAG(
server->client_machines[i].flags,
_network_client_machine_connected_bit) &&
!TEST_FLAG(
server->client_machines[i].flags,
_network_client_machine_level_loaded_bit))
{
char ascii_name[MAXIMUM_MACHINE_NAME_LENGTH];
boolean removed;
network_event(
"forcibly removing client system '%s' due to timeout while loading for game",
wide_to_ascii(
server->game.machines[
server->client_machines[i].machine_index].name,
ascii_name,
sizeof(ascii_name))
? ascii_name
: "<unknown name>");
removed = network_game_server_remove_client_machine_from_game(
server,
&server->client_machines[i]);
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x94E, removed);
}
}
/* (the last removal may have started it) */
if (server->state == _network_game_server_state_pregame)
network_game_server_all_machines_have_loaded(server);
}
}
return success;
}
boolean network_game_server_reset_to_pregame(
struct network_game_server *server)
{
boolean success = FALSE;
struct message_server_switch_to_pregame message_packet = { 0 };
struct network_message *message;
int i;
match_assert(NETWORK_SERVER_MANAGER_FILE, 0x324, server);
csmemset(&server->countdown_state, 0, sizeof(server->countdown_state));
server->next_update_number = 0;
server->time_of_first_client_loading_completion = 0;
server->sent_start_game_message = FALSE;
server->queued_player_valid = FALSE;
server->waiting_player_count = 0;
csmemset(network_game_server_ingame_additions, 0, sizeof(network_game_server_ingame_additions));
/* Preserve January's 32-bit wrap without overflowing signed arithmetic.
* VC7 converts the unsigned result back to the same signed bit pattern.
*/
server->game.number_of_games_played =
(long)((unsigned long)server->game.number_of_games_played + 1);
if (server->state == _network_game_server_state_postgame)
{
message = create_network_game_message(
_message_server_switch_to_pregame,
&message_packet,
sizeof(message_packet));
/* port: the machines are switched whether or not the message reached
every one: those it did are in the pregame already (the host's own
too), and one it did not (its connection failed, and is closed) is
dropped; the server kept to the postgame for good, and a second try
switched the others again, which refuse it. Only the machines that
were in the game: one joining it in progress and not yet started is
in the pregame already (it refuses the switch), and has the settings
below. */
if (message)
{
boolean sent = TRUE;
for (i = 0; i < MAXIMUM_NETWORK_MACHINE_COUNT; i++)
{
struct network_game_server_client_machine *machine = &server->client_machines[i];
if (network_game_server_client_machine_is_joined_to_game(server, machine) &&
(TEST_FLAG(machine->flags, _network_client_machine_level_loaded_bit) ||
TEST_FLAG(machine->flags, _network_client_machine_started_late_bit)) &&
network_connection_active(machine->connection) &&
!network_game_server_send_message_to_client_machine(server, machine, message))
{
sent = FALSE;
}
}
if (sent)
network_event("server resetting to pregame");
else
network_event("server resetting to pregame (some machines missed it)");
if (server->game.variant.universal_variant.teams)
{
for (i = 0; i < MAXIMUM_NETWORK_PLAYER_COUNT; i++)
{
if (network_player_is_valid(&server->game.players[i]))
{
switch (server->game.players[i].team_index)
{
case _team_red:
server->game.players[i].team_index = _team_blue;
break;
case _team_blue:
server->game.players[i].team_index = _team_red;
break;
}
}
}
}
for (i = 0; i < MAXIMUM_NETWORK_MACHINE_COUNT; i++)
{
SET_FLAG(
server->client_machines[i].flags,
_network_client_machine_level_loaded_bit,
FALSE);
SET_FLAG(
server->client_machines[i].flags,
_network_client_machine_started_late_bit,
FALSE);
server->client_machines[i].last_received_update_sequence_number = 0;
}
network_game_reset_for_next_round(&server->game, FALSE);
if (network_game_server_setup_game_from_playlist(server))
{
network_game_server_cooperative_round(server);
/* the settings record goes out in pieces */
/* (the pregame whatever a machine missed: the machines are in it,
and the pregame's flush sends the settings again) */
if (!network_game_server_send_game_settings_to_all_machines(server, &server->game, sizeof(server->game)))
network_event("some machines missed the game settings; they are sent again in the pregame");
server->state = _network_game_server_state_pregame;
success = TRUE;
}
else
{
struct message_server_graceful_game_exit_pregame shutdown_message = { 0 };
message = create_network_game_message(
_message_server_graceful_game_exit_pregame,
&shutdown_message,
sizeof(shutdown_message));
if (message &&
network_game_server_send_message_to_all_machines(server, message) &&
network_game_server_handle_client_machines(server))
{
network_event("the playlist has ended - server going down");
}
else
{
network_event(
"the playlist has ended - server going down, but failed to alert client machines");
}
}
}
else
{
network_event("failed to create a _message_type_server_switch_to_pregame message");
}
}
else
{
success = network_game_server_setup_game_from_playlist(server);
if (server->game.variant.universal_variant.teams)
{
for (i = 0; i < MAXIMUM_NETWORK_PLAYER_COUNT; i++)
{
if (network_player_is_valid(&server->game.players[i]))
{
switch (server->game.players[i].team_index)
{
case _team_red:
server->game.players[i].team_index = _team_blue;
break;
case _team_blue:
server->game.players[i].team_index = _team_red;
break;
}
}
}
}
}
return success;
}