/* GAME_ENGINE.C symbols in this file: 00096780 0020: _game_globals_get_weapon (0000) 000967A0 0070: _game_engine_get_team_score (0000) 00096810 0020: _linear_to_non_linear_alpha (0000) 00096830 0020: _game_engine_dispose (0000) 00096850 0040: _initialize_player_multiplayer_data (0000) 00096890 0120: _game_engine_build_lighting (0000) 000969B0 0020: _game_engine_dispose_from_old_map (0000) 000969D0 0020: _game_engine_game_ending (0000) 000969F0 0020: _game_engine_game_starting (0000) 00096A10 0040: _players_in_game (0000) 00096A50 0020: _game_engine_statistics_append (0000) 00096A70 0020: _game_engine_handle_client_message (0000) 00096A90 0020: _game_engine_handle_server_message (0000) 00096AB0 0030: _sort_statistic_buffer (0000) 00096AE0 0050: _sort_statistic_buffer_ranking (0000) 00096B30 0010: _is_place_tied (0000) 00096B40 0010: _place_get_position (0000) 00096B50 0050: _get_place_string (0000) 00096BA0 0070: _drawline (0000) 00096C10 0020: _get_selected_color (0000) 00096C30 0050: _get_postgame_hilite_colors (0000) 00096C80 0060: _find_closest_player_callback (0000) 00096CE0 0020: _game_engine_post_rasterize_objects (0000) 00096D00 0030: _can_delete_item (0000) 00096D30 0100: _game_engine_update_purge (0000) 00096E30 00a0: _update_weapon_inventory (0000) 00096ED0 0150: _game_engine_update_weapons (0000) 00097020 00b0: _item_collection_get_total (0000) 000970D0 0050: _game_engine_load_stage (0000) 00097120 0030: _game_engine_playlist_begin (0000) 00097150 0060: _game_engine_get_current_stage (0000) 000971B0 0030: _game_engine_end_game (0000) 000971E0 0020: _game_engine_allow_pick_up (0000) 00097200 0050: _game_engine_player_damaged_player (0000) 00097250 00a0: _game_engine_player_is_odd_man_out (0000) 000972F0 0040: _game_engine_player_is_out_of_lives (0000) 00097330 0100: _game_engine_should_spawn_player (0000) 00097430 0070: _game_engine_player_get_team_index (0000) 000974A0 0040: _game_engine_prespawn_player_update (0000) 000974E0 0010: _game_engine_running (0000) 000974F0 0020: _game_engine_can_score (0000) 00097510 0020: _game_engine_force_single_screen (0000) 00097530 0030: _get_blink_alpha (0000) 00097560 0010: _game_engine_press_start_to_begin (0000) 00097570 00f0: _nearby_vehicle (0000) 00097660 0100: _game_engine_rasterize_message (0000) 00097760 00e0: _game_engine_picking_up (0000) 00097840 0080: _goal_matches_player (0000) 000978C0 00a0: _game_engine_player_get_custom_motion_sensor_positions (0000) 00097960 0030: _get_flag_definition_index (0000) 00097990 0030: _get_ball_definition_index (0000) 000979C0 0040: _game_engine_switch_to_postgame (0000) 00097A00 0010: _game_engine_get_variant (0000) 00097A10 0020: _game_engine_get_goal_in_use (0000) 00097A30 0060: _game_engine_get_goal_position (0000) 00097A90 0080: _game_engine_set_goal_position (0000) 00097B10 0020: _game_engine_clear_goal_position (0000) 00097B30 00d0: _game_engine_render_nav_points (0000) 00097C00 0020: _game_engine_infinite_grenades_internal (0000) 00097C20 0030: _game_engine_infinite_grenades (0000) 00097C50 0020: _game_engine_has_teams (0000) 00097C70 0030: _game_engine_display_team_indicators (0000) 00097CA0 0030: _game_engine_has_shield (0000) 00097CD0 0060: _weapon_definition_index_to_list_index (0000) 00097D30 0030: _list_index_to_weapon_definition_index (0000) 00097D60 00c0: _game_engine_remap_vehicle (0000) 00097E20 0190: _game_engine_remap_weapon (0000) 00097FB0 0070: _game_engine_man_out (0000) 00098020 0030: _game_engine_state_message (0000) 00098050 0090: _game_engine_player_get_change_color (0000) 000980E0 0070: _game_engine_player_has_flag (0000) 00098150 0050: _game_engine_player_depower_active_camo (0000) 000981A0 00e0: _get_place_name (0000) 00098280 0170: _game_engine_get_place (0000) 000983F0 0040: _game_engine_hud_draw_motion_sensor (0000) 00098430 0020: _game_engine_test_flag (0000) 00098450 0020: _game_engine_test_trait (0000) 00098470 00a0: _netgame_flag_verify_no_team_duplicates (0000) 00098510 0070: _netgame_flag_verify_team_range (0000) 00098580 0070: _game_engine_playlist_next (0000) 000985F0 0090: _build_game_variant_slayer (0000) 00098680 0090: _build_game_variant_slayer_pro (0000) 00098710 0090: _build_game_variant_elimination (0000) 000987A0 0090: _build_game_variant_phantoms (0000) 00098830 0090: _build_game_variant_endurance (0000) 000988C0 0090: _build_game_variant_rockets (0000) 00098950 0090: _build_game_variant_snipers (0000) 000989E0 0090: _build_game_variant_team_slayer (0000) 00098A70 0090: _build_game_variant_oddball (0000) 00098B00 0090: _build_game_variant_team_oddball (0000) 00098B90 0090: _build_game_variant_reverse_tag (0000) 00098C20 00a0: _build_game_variant_accumulation (0000) 00098CC0 00a0: _build_game_variant_juggernaut (0000) 00098D60 00a0: _build_game_variant_stalker (0000) 00098E00 0080: _build_game_variant_king (0000) 00098E80 0090: _build_game_variant_king_pro (0000) 00098F10 0080: _build_game_variant_crazy_king (0000) 00098F90 0090: _build_game_variant_team_king (0000) 00099020 0090: _build_game_variant_ctf (0000) 000990B0 0090: _build_game_variant_ctf_pro (0000) 00099140 0090: _build_game_variant_invasion (0000) 000991D0 0090: _build_game_variant_iron_ctf (0000) 00099260 0090: _build_game_variant_race (0000) 000992F0 0090: _build_game_variant_rally (0000) 00099380 0090: _build_game_variant_team_race (0000) 00099410 0090: _build_game_variant_team_rally (0000) 000994A0 0030: _game_engine_override_map_name (0000) 000994D0 0020: _game_engine_override_game_variant (0000) 000994F0 01a0: _rasterize_in_game_score_draw_line (0000) 00099690 0060: _game_engine_hud_draw_messages (0000) 000996F0 0080: _game_engine_player_has_stealth_weapon (0000) 00099770 0110: _game_engine_weapon_fired (0000) 00099880 0030: _test_any_gamepad_button (0000) 000998B0 00c0: _ticks_to_unicode_time_string (0000) 00099970 0060: _game_engine_flag_reset (0000) 000999D0 01b0: _game_engine_variant_cleanup (0000) 00099B80 0010: _game_engine_allow_pause (0000) 00099B90 0270: _game_engine_predict_resources (0000) 00099E00 0020: _game_engine_draw_object_in_motion_sensor (0000) 00099E20 0020: _game_engine_allow_dynamic_lighting (0000) 00099E40 0020: _game_engine_allow_integrated_lights (0000) 00099E60 0030: _game_engine_force_autopickup (0000) 00099E90 0060: _game_engine_initialize (0000) 00099EF0 0100: _multiple_teams_alive (0000) 00099FF0 00f0: _team_has_players (0000) 0009A0E0 0020: _game_engine_should_end_game (0000) 0009A100 0080: _adjust_score_for_ranking (0000) 0009A180 0230: _populate_statistic_buffer (0000) 0009A3B0 0080: _game_engine_get_player_place (0000) 0009A430 0060: _postgame_statistic_get_rating (0000) 0009A490 01f0: _select_players_to_display (0000) 0009A680 01c0: _find_closest_player_index (0000) 0009A840 0100: _internal_rasterize_target_name (0000) 0009A940 0920: _internal_rasterize_score (0000) 0009B260 0080: _random_item (0000) 0009B2E0 0020: _game_engine_get_type (0000) 0009B300 00a0: _match_game_type (0000) 0009B3A0 0150: _game_engine_update_item_spawn (0000) 0009B4F0 0070: _game_engine_update_player_no_shield (0000) 0009B560 0060: _game_engine_update_player_always_invis (0000) 0009B5C0 00e0: _game_engine_update_non_deterministic (0000) 0009B6A0 0040: _multiplayer_message_internal (0000) 0009B6E0 0090: _multiplayer_message (0000) 0009B770 0010: _game_show_score_one_player (0000) 0009B780 0060: _game_show_score_team (0000) 0009B7E0 00d0: _game_show_score_you_ally_enemy (0000) 0009B8B0 0080: _game_show_score_extended (0000) 0009B930 0020: _game_show_score (0000) 0009B950 0110: _find_netgame_flags (0000) 0009BA60 0040: _find_netgame_flag (0000) 0009BAA0 0130: _handle_custom_starting_equipment (0000) 0009BBD0 0150: _game_engine_postspawn_player_update (0000) 0009BD20 00d0: _game_engine_get_damage_multiplier (0000) 0009BDF0 03e0: _game_engine_update_teleporter (0000) 0009C1D0 0170: _game_engine_get_distance_rating_for_spawn (0000) 0009C340 0120: _game_engine_get_friendly_bonus (0000) 0009C460 00b0: _default_starting_location_rate_function (0000) 0009C510 0060: _game_engine_get_starting_location_rating (0000) 0009C570 0220: _game_engine_get_variant_by_name (0000) 0009C790 0130: _game_engine_remap_equipment (0000) 0009C8C0 0070: _game_engine_remap_object_definition (0000) 0009C930 0140: _game_engine_get_state_message (0000) 0009CA70 00c0: _game_engine_did_player_win_default (0000) 0009CB30 0030: _game_engine_did_player_win (0000) 0009CB60 0080: _game_engine_did_team_win (0000) 0009CBE0 0040: _netgame_flag_verify_team_exists (0000) 0009CC20 0060: _netgame_verify_spawn_points (0000) 0009CC80 0070: _netgame_verify_equipment (0000) 0009CCF0 0280: _game_engine_verify_current_map (0000) 0009CF70 0010: _game_engine_playlist_initialize (0000) 0009CF80 0080: _game_engine_initialize_for_new_map (0000) 0009D000 0140: _game_engine_player_added (0000) 0009D140 0480: _game_engine_generate_title_string (0000) 0009D5C0 09e0: _game_engine_post_rasterize_post_game (0000) 0009DFA0 02f0: _game_engine_update (0000) 0009E290 0340: _game_engine_player_killed (0000) 0009E5D0 00a0: _game_engine_nonplayer_post_rasterize (0000) 0009E670 0350: _game_engine_rasterize_in_game_score (0000) 0009E9C0 0140: _game_engine_post_rasterize_in_game (0000) 0009EB00 0060: _game_engine_post_rasterize (0000) 0025B190 0008: __real@3ffe666660000000 (0000) 0025B198 0002: ??_C@_11LOCGONAA@?$AA?$AA@ (0000) 0025B19C 0019: ??_C@_0BJ@IAIEOKNM@ui?2multiplayer_game_text?$AA@ (0000) 0025B1B8 001d: ??_C@_0BN@BLHEDFPC@weapon_is_flag?$CIweapon_index?$CJ?$AA@ (0000) 0025B1D8 0022: ??_C@_0CC@HKAICMNO@c?3?2halo?2SOURCE?2game?2game_engine?4@ (0000) 0025B1FC 003c: ??_C@_0DM@INOFLLBD@?$CIitem?9?$DOobject?4scale?5?$DO?$DN?50?45f?$CJ?5?$CG?$CG?5@ (0000) 0025B238 0014: ??_C@_0BE@PEGHDBAP@NULL?5?$CB?$DN?5game_engine?$AA@ (0000) 0025B24C 0014: ??_C@_0BE@CKCLKIJA@variant?5?$CG?$CG?5map_name?$AA@ (0000) 0025B260 001a: ??_C@_0BK@EKPIBKAB@dead_player_index?5?$CB?$DN?5NONE?$AA@ (0000) 0025B27C 000c: ??_C@_0M@CLNODINK@game_engine?$AA@ (0000) 0025B288 0008: __real@3f53104b57cf969e (0000) 0025B290 0007: ??_C@_06IEOJBDIK@object?$AA@ (0000) 0025B298 0097: ??_C@_0JH@PNHLNBOL@?$CBallow_pick_up?5?$HM?$HM?5?$CBTEST_FLAG?$CIwea@ (0000) 0025B330 001a: ??_C@_0BK@LOIEOAAE@global_goal?$FLindex?$FN?4in_use?$AA@ (0000) 0025B34C 0004: __real@3f2147ae (0000) 0025B350 0015: ??_C@_0BF@MOJDLNAG@NONE?5?$CB?$DN?5lookup_index?$AA@ (0000) 0025B368 001d: ??_C@_0BN@OIBHHACP@place?4place?5?$DM?5maximum_places?$AA@ (0000) 0025B388 002c: ??_C@_0CM@IEEKPKHB@?$CI?$CBall_tied?5?$HM?$HM?5?$CItied?$CJ?$CJ?5?$HM?$HM?5?$CI1?5?$DN?$DN?5g@ (0000) 0025B3B4 001e: ??_C@_0BO@HPNHAHOG@levels?2test?2carousel?2carousel?$AA@ (0000) 0025B3D4 000c: ??_C@_1M@CDCAHPKB@?$AA?$CF?$AAs?$AA?3?$AA?$CF?$AAs?$AA?$AA@ (0000) 0025B3E0 0008: ??_C@_17PMDFIHDK@?$AA0?$AA?$CF?$AAd?$AA?$AA@ (0000) 0025B3E8 0006: ??_C@_15KNBIKKIN@?$AA?$CF?$AAd?$AA?$AA@ (0000) 0025B3F0 0004: ??_C@_13HOIJIPNN@?$AA?5?$AA?$AA@ (0000) 0025B3F8 0046: ??_C@_0EG@GDPJDEHO@NETGAME?5CODE?5FAILURE?3?5game_engin@ (0000) 0025B440 001b: ??_C@_0BL@FBPHGMEB@player?9?$DOteam_index?5?$CB?$DN?5NONE?$AA@ (0000) 0025B45C 0013: ??_C@_0BD@JDPLED@player_index?$CB?$DNNONE?$AA@ (0000) 0025B470 002b: ??_C@_0CL@DLMKKJM@player_count?5?$DM?5MULTIPLAYER_MAXIM@ (0000) 0025B49C 0022: ??_C@_0CC@FPLNHLNP@place?$DMMULTIPLAYER_MAXIMUM_PLAYER@ (0000) 0025B4C0 0013: ??_C@_0BD@EGHPGJBP@found?5local?5player?$AA@ (0000) 0025B4D4 001d: ??_C@_0BN@LMCPNAFN@player_count?$DN?$CFd?0?5maxcount?$DN?$CFd?$AA@ (0000) 0025B4F8 0008: __real@3fc0bf25a0000000 (0000) 0025B500 0015: ??_C@_0BF@IHIEPAI@NONE?5?$CB?$DN?5player_index?$AA@ (0000) 0025B518 0016: ??_C@_0BG@OJNPNGFL@failed?5to?5teleport?5?$CFd?$AA@ (0000) 0025B530 0008: __real@3fe3333340000000 (0000) 0025B538 000a: ??_C@_09DOOCKEDN@team_king?$AA@ (0000) 0025B544 0005: ??_C@_04PJOEONHN@king?$AA@ (0000) 0025B54C 0008: ??_C@_07LIOCHKOH@ironctf?$AA@ (0000) 0025B554 0004: ??_C@_03JHHHHEKD@ctf?$AA@ (0000) 0025B558 0008: ??_C@_07JHGHBFJP@oddball?$AA@ (0000) 0025B560 000d: ??_C@_0N@JBNPINGA@accumulation?$AA@ (0000) 0025B570 000d: ??_C@_0N@CPCFMJOB@team_oddball?$AA@ (0000) 0025B580 0008: ??_C@_07IAHNGGND@stalker?$AA@ (0000) 0025B588 000c: ??_C@_0M@MLMHALFL@elimination?$AA@ (0000) 0025B594 000c: ??_C@_0M@NOCMPHHF@team_slayer?$AA@ (0000) 0025B5A0 0007: ??_C@_06CBFFIGEC@slayer?$AA@ (0000) 0025B5A8 0006: ??_C@_05GCEFBECL@rally?$AA@ (0000) 0025B5B0 000a: ??_C@_09KMEIIIPA@team_race?$AA@ (0000) 0025B5BC 0005: ??_C@_04GLEOMBLA@race?$AA@ (0000) 0025B5C4 0004: __real@3f0ccccd (0000) 0025B5C8 003b: ??_C@_0DL@MBPNEOKF@NETGAME?5MAP?5FAILURE?3?5failed?5to?5f@ (0000) 0025B604 003b: ??_C@_0DL@FDFHGCGI@NETGAME?5MAP?5FAILURE?3?5failed?5to?5f@ (0000) 0025B640 003e: ??_C@_0DO@GDPJBCBM@NETGAME?5MAP?5FAILURE?3?5failed?5to?5f@ (0000) 0025B680 003d: ??_C@_0DN@GLOHFEFE@NETGAME?5MAP?5FAILURE?3?5failed?5to?5f@ (0000) 0025B6C0 003a: ??_C@_0DK@COLEDOMN@NETGAME?5MAP?5FAILURE?3?5failed?5to?5f@ (0000) 0025B700 0047: ??_C@_0EH@GJGBEMDH@NETGAME?5MAP?5FAILURE?3?5failed?5to?5f@ (0000) 0025B748 0047: ??_C@_0EH@BMGHBGKH@NETGAME?5MAP?5FAILURE?3?5failed?5to?5f@ (0000) 0025B790 004a: ??_C@_0EK@DHDLIGMN@NETGAME?5MAP?5FAILURE?3?5failed?5to?5f@ (0000) 0025B7E0 0049: ??_C@_0EJ@FACIONE@NETGAME?5MAP?5FAILURE?3?5failed?5to?5f@ (0000) 0025B830 004f: ??_C@_0EP@CGHPHLAM@NETGAME?5MAP?5FAILURE?3?5failed?5to?5f@ (0000) 0025B880 004f: ??_C@_0EP@MJLNBADC@NETGAME?5MAP?5FAILURE?3?5failed?5to?5f@ (0000) 0025B8D0 0039: ??_C@_0DJ@MPJKFIIJ@NETGAME?5MAP?5FAILURE?3?5duplicate?5r@ (0000) 0025B90C 0031: ??_C@_0DB@KDFIELID@NETGAME?5MAP?5FAILURE?3?5missing?5rac@ (0000) 0025B940 0034: ??_C@_0DE@KIBANADL@NETGAME?5MAP?5FAILURE?3?5missing?5odd@ (0000) 0025B974 0031: ??_C@_0DB@IKODHNKI@NETGAME?5MAP?5FAILURE?3?5missing?5hil@ (0000) 0025B9A8 0035: ??_C@_0DF@EHBELDKM@NETGAME?5MAP?5FAILURE?3?5ctf?5flag?5ou@ (0000) 0025B9E0 0032: ??_C@_0DC@DCAAPLNK@NETGAME?5MAP?5FAILURE?3?5duplicate?5c@ (0000) 0025BA14 0030: ??_C@_0DA@LKKJMHEH@NETGAME?5MAP?5FAILURE?3?5missing?5ctf@ (0000) 0025BA48 0057: ??_C@_0FH@ODDMDCIB@failed?5to?5initialize?5custome?5gam@ (0000) 0025BAA0 000d: ??_C@_0N@MABFIMPF@title_string?$AA@ (0000) 0025BAB0 001e: ??_C@_1BO@NNCACCCJ@?$AA?5?$AA?7?$AA?5?$AA?7?$AA?5?$AA?7?$AA?5?$AA?7?$AA?5?$AA?7?$AA?5?$AA?7?$AA?$CF?$AAd?$AA?$AA@ (0000) 0025BAD0 001a: ??_C@_1BK@JPAKMPFP@?$AA?5?$AA?7?$AA?5?$AA?7?$AA?5?$AA?7?$AA?5?$AA?7?$AA?5?$AA?7?$AA?$CF?$AAd?$AA?$AA@ (0000) 0025BAEC 0016: ??_C@_1BG@EEDCCJKF@?$AA?5?$AA?7?$AA?5?$AA?7?$AA?5?$AA?7?$AA?5?$AA?7?$AA?$CF?$AAd?$AA?$AA@ (0000) 0025BB04 0012: ??_C@_1BC@DGONEHBJ@?$AA?5?$AA?7?$AA?5?$AA?7?$AA?5?$AA?7?$AA?$CF?$AAs?$AA?$AA@ (0000) 0025BB18 000e: ??_C@_1O@HAEHHN@?$AA?5?$AA?7?$AA?5?$AA?7?$AA?$CF?$AAs?$AA?$AA@ (0000) 0025BB28 000a: ??_C@_19GCIMBEIF@?$AA?5?$AA?7?$AA?$CF?$AAs?$AA?$AA@ (0000) 0025BB34 0026: ??_C@_1CG@MDCDIADO@?$AA?7?$AA?$CF?$AAs?$AA?7?$AA?$CF?$AAs?$AA?7?$AA?$CF?$AAs?$AA?7?$AA?$CF?$AAs?$AA?7?$AA?$CF?$AAs?$AA?7?$AA?$CF?$AAs?$AA?$AA@ (0000) 0025BB5C 0012: ??_C@_0BC@FNEEDLLE@ambient_computers?$AA@ (0000) 0025BB70 0012: ??_C@_0BC@GOFPLFEH@ambient_machinery?$AA@ (0000) 0025BB84 000f: ??_C@_0P@PLBDAHNN@ambient_nature?$AA@ (0000) 0025BB94 0014: ??_C@_1BE@EFJKLKFF@?$AA?7?$AA?$CF?$AAs?$AA?7?$AA?$CF?$AAs?$AA?7?$AA?$CF?$AAs?$AA?$AA@ (0000) 0025BBA8 001b: ??_C@_0BL@NPJONDMM@NONE?5?$CB?$DN?5local_player_index?$AA@ (0000) 002DE3E0 0020: _debug_player_color (0000) 0043E498 047c: _global_goal (0000) _global_variant (0400) _game_engine (0468) _global_autogenerate_list (0474) _global_autogenerate_count (0478) */ /* ---------- headers */ #include "cseries.h" #include "cseries_windows.h" #include "errors.h" #include "aim_assist.h" #include "game_engine.h" #include "game_engine_list.h" #include "game_engine_place.h" #include "bitmaps/bitmap_group.h" #include "bitmaps/bitmap_group_lookup.h" #include "effects/player_effects.h" #include "game_allegiance.h" #include "game_globals.h" #include "interface/interface.h" #include "interface/hud.h" #include "interface/hud_definitions.h" #include "interface/hud_messaging.h" #include "interface/player_ui.h" #include "interface/terminal.h" #include "interface/ui_widget.h" #include "input/input.h" #include "items/equipment_definitions.h" #include "items/item_definitions.h" #include "items/weapon_definitions.h" #include "items/weapons.h" #include "main/console.h" #include "main/main.h" #include "math/integer_math.h" #include "networking/network_game_globals.h" #include "networking/network_server_manager.h" #include "objects.h" #include "objects/damage_effect_definitions.h" #include "physics/collision_features.h" #include "player_rumble.h" #include "players.h" #include "players_runtime.h" #include "rasterizer/rasterizer.h" #include "rasterizer/rasterizer_console_vars.h" #include "render/render.h" #include "saved games/player_profile.h" #include "scenario/scenario.h" #include "scenario/scenario_definitions.h" #include "sound/sound_classes.h" #include "text/draw_string.h" #include "text/font_group.h" #include "text/text_group.h" #include "text/unicode.h" #include "units/bipeds.h" #include "units/units.h" /* network_game_globals.c's */ boolean network_game_distributed_client(void); /* port/linux/game/network_distributed.c's */ void network_distributed_player_killed(long *killing_player_index, long *killing_object_index, long dead_player_index, boolean *friendly_fire); /* port/linux/game/network_damage.c's */ boolean network_damage_killer_score(long player_index, long *score); /* ---------- constants */ enum { /* port: the native builds' session limit (halo_port_limits.h) */ MULTIPLAYER_MAXIMUM_PLAYERS = HALO_PORT_MAXIMUM_NETWORK_PLAYERS, /* ui\multiplayer_game_text only has strings for the first 16 places */ NUMBER_OF_PLACE_STRINGS = 16, }; /* scenario_starting_equipment.flags (HCEX names) */ enum { _netgame_starting_equipment_flag_no_grenades_bit = 0, _netgame_starting_equipment_flag_plasma_greandes_bit, /* (sic) HCEX spelling */ }; /* game_engine_globals.flags and universal_variant.flags bits used only by this file (the lower bits are declared with their siblings in game_engine.h) */ enum { _game_engine_9_or_more_players_bit = 3, _game_engine_game_over_sound_disabled_bit, }; enum { _game_variant_generic_starting_equipment_bit = 5, }; enum { _game_engine_all = 12, _game_engine_all_non_team, _game_engine_all_normal, }; enum { _multiplayer_sound_game_over = 0x1, _multiplayer_sound_double_kill = 0xE, _multiplayer_sound_triple_kill, _multiplayer_sound_killtacular_kill, _multiplayer_sound_running_riot, _multiplayer_sound_killing_spree, _multiplayer_sound_teleporter_activate = 0x1B, _multiplayer_sound_countdown_for_respawn = 0x1D, _multiplayer_sound_respawn = 0x1F, }; enum game_engine_mode { game_engine_mode_active = 0, game_engine_mode_postgame_delay, game_engine_mode_postgame_rasterize_delay, game_engine_mode_postgame_rasterize, }; enum postgame_statistic { _postgame_statistic_ranking = 0, _postgame_statistic_score, _postgame_statistic_kills, _postgame_statistic_assists, _postgame_statistic_deaths, NUMBER_OF_POSTGAME_STATISTICS, }; enum game_engine_weapons { _game_engine_weapons_normal = 0, _game_engine_weapons_pistols, _game_engine_weapons_assault_rifles, _game_engine_weapons_plasma_weapons, _game_engine_weapons_sniping, _game_engine_weapons_no_sniping, _game_engine_weapons_rocket_launchers, _game_engine_weapons_shotguns, _game_engine_weapons_short_range, _game_engine_weapons_human, _game_engine_weapons_no_grenades, NUMBER_OF_GAME_ENGINE_WEAPON_SETS, }; enum goal_radar { _radar_motion_tracker = 0, _radar_nav_point, _radar_none, }; enum game_engine_vehicles { _game_engine_vehicles_default = 0, _game_engine_vehicles_none, _game_engine_vehicles_warthog, _game_engine_vehicles_ghost, _game_engine_vehicles_tank, NUMBER_OF_GAME_ENGINE_VEHICLE_SETS, }; enum { _weapon_list_assault_rifle = 0, _weapon_list_flamethrower, _weapon_list_gravity_rifle, _weapon_list_needler, _weapon_list_pistol, _weapon_list_plasma_pistol, _weapon_list_plasma_rifle, _weapon_list_rocket_launcher, _weapon_list_shotgun, _weapon_list_sniper_rifle, _weapon_list_ball, _weapon_list_flag, _weapon_list_frag_grenade, _weapon_list_plasma_grenade, }; /* ---------- macros */ #define hud_globals_definition_get(index) \ ((struct hud_globals_definition *)tag_get('hudg', (index))) #define item_collection_definition_get(index) ((struct item_collection_definition *)tag_get(ITEM_COLLECTION_DEFINITION_TAG, index)) /* ---------- structures */ struct netgame_goal { real_point3d position; boolean in_use; long player_index; short team_index; long ignore_player_index; short nav_index; }; struct game_engine_globals { unsigned long flags; long next_team_index; real postgame_timer; real postgame_progress; long postgame_state; real hud_message_timers[MAXIMUM_LOCAL_PLAYERS]; }; struct game_engine_stage { char map_name[64]; struct game_variant variant; }; struct statistic_buffer { long player_index; long score; long custom; long kills; long deaths; long assists; long place; }; typedef char verify_statistic_buffer_size[ sizeof(struct statistic_buffer) == 0x1C ? 1 : -1]; typedef char verify_netgame_goal_size[sizeof(struct netgame_goal) == 0x20 ? 1 : -1]; typedef char verify_game_engine_globals_postgame_timer_offset[ offsetof(struct game_engine_globals, postgame_timer) == 0x8 ? 1 : -1]; typedef char verify_game_engine_globals_next_team_index_offset[ offsetof(struct game_engine_globals, next_team_index) == 0x4 ? 1 : -1]; typedef char verify_game_engine_globals_postgame_progress_offset[ offsetof(struct game_engine_globals, postgame_progress) == 0xC ? 1 : -1]; typedef char verify_game_engine_globals_postgame_state_offset[ offsetof(struct game_engine_globals, postgame_state) == 0x10 ? 1 : -1]; typedef char verify_game_engine_globals_hud_message_timers_offset[ offsetof(struct game_engine_globals, hud_message_timers) == 0x14 ? 1 : -1]; typedef char verify_game_engine_globals_size[ sizeof(struct game_engine_globals) == 0x24 ? 1 : -1]; typedef char verify_game_engine_stage_variant_offset[ offsetof(struct game_engine_stage, variant) == 0x40 ? 1 : -1]; typedef char verify_game_engine_stage_size[ sizeof(struct game_engine_stage) == 0xA8 ? 1 : -1]; /* ---------- prototypes */ void game_engine_playlist_next( long parameter0, long parameter1, long playlist_type); static void game_engine_build_lighting( void); static boolean is_place_tied( struct statistic_buffer const *entry); int __cdecl sort_statistic_buffer( void const *entry0_pointer, void const *entry1_pointer); int __cdecl sort_statistic_buffer_ranking( void const *entry0_pointer, void const *entry1_pointer); static void drawline( wchar_t const *string, long row_index, short justification); static boolean game_engine_infinite_grenades_internal( void); static long select_players_to_display( enum postgame_statistic statistic, long player_index, struct statistic_buffer *output, long maximum_count); static void netgame_flag_verify_no_team_duplicates( short flag_type, char const *error_message); static void netgame_flag_verify_team_range( short flag_type, short minimum_index, short maximum_index, char const *error_message); static void netgame_verify_spawn_points( short game_type, short unused_team_index, short minimum_count, char const *error_message); static void netgame_verify_equipment( short game_type, char const *error_message); boolean multiple_teams_alive( void); static void game_engine_post_rasterize_in_game( void); static void game_engine_rasterize_in_game_score( long player_index, real alpha); static void game_engine_predict_resources( void); static void game_engine_verify_current_map( void); void game_engine_post_rasterize_post_game( void); static boolean internal_rasterize_score( long player_index, long message, long message_data, wchar_t *buffer, long buffer_size); static void game_engine_update_player_no_shield( long player_index); static void game_engine_update_teleporter( long player_index); static void game_engine_update_weapons( void); static void game_engine_update_item_spawn( void); boolean team_has_players( long team_index); static struct statistic_buffer game_engine_get_player_place( long player_index); long postgame_statistic_get_rating( long player_index, enum postgame_statistic statistic, boolean inverse); long populate_statistic_buffer( struct statistic_buffer *statistic_buffer, enum postgame_statistic statistic, boolean inverse); static boolean find_closest_player_callback( long object_index, void *custom_data); static long find_closest_player_index( long player_index); static void internal_rasterize_target_name( long player_index); static long adjust_score_for_ranking( long score, long player_index); /* ---------- globals */ short debug_player_color = NONE; /* port: slayer's kill-in-order target uses the player's absolute index as its goal index, so every player needs a goal slot */ struct netgame_goal global_goal[MAX(32, MULTIPLAYER_MAXIMUM_PLAYERS)] = { 0 }; struct game_variant global_variant = { 0 }; struct game_engine *game_engine = NULL; extern struct game_engine_globals game_engine_globals; extern struct game_engine_stage global_stage; extern long timeout_for_endgame_sound; /* whether a client has had the host's game type state this game (what changes in the first, from this machine's own start, it only takes: the game types show what changes in the next) */ static boolean game_engine_network_state_read = FALSE; /* ---------- public code */ long game_globals_get_weapon( struct game_globals *game_globals, long weapon_list_index) { struct tag_reference *weapon = TAG_BLOCK_GET_ELEMENT( &game_globals->weapon_list, weapon_list_index, struct tag_reference); long weapon_definition_index = weapon->index; return weapon_definition_index; } long game_engine_get_team_score( long team_index) { struct data_iterator iterator; struct player_datum *player; data_iterator_new(&iterator, player_data); player = (struct player_datum *)data_iterator_next(&iterator); while (player) { if (player->team_index == team_index) return game_engine->get_player_score(iterator.datum_index, TRUE); player = (struct player_datum *)data_iterator_next(&iterator); } return 0; } real linear_to_non_linear_alpha( real linear_alpha) { real non_linear_alpha = (real)pow((double)linear_alpha, 1.9f); return non_linear_alpha; } static void initialize_player_multiplayer_data( long player_index) { struct player_datum *player = player_get(player_index); player->state_message = NONE; player->state_message_player_index = NONE; player->speed_multiplier = 1.0f; player->teleporter_index = NONE; player->player_display_index = NONE; csmemset( &player->statistics.multiplayer_statistics, 0, sizeof(long)); return; } /* port: English ordinal for a zero-based place past the string list's 16 ("17th", "22nd", "111th"; "tied for 17th" when tied), in a static buffer */ static wchar_t *place_ordinal_string( long place, boolean tied) { static wchar_t string[32]; long number = place + 1; wchar_t const *suffix = L"th"; if (number % 100 < 11 || number % 100 > 13) { switch (number % 10) { case 1: suffix = L"st"; break; case 2: suffix = L"nd"; break; case 3: suffix = L"rd"; break; } } usnprintf( string, NUMBEROF(string), tied ? L"tied for %d%s" : L"%d%s", number, suffix); string[NUMBEROF(string) - 1] = 0; return string; } static wchar_t *get_place_string( struct statistic_buffer *entry) { long string_index = PIN(entry->place & 0x7F, 0, 15); long string_list_index; /* port: places past the 16th are spelled out instead of clamped (the caller's format string marks a tie) */ if ((entry->place & 0x7F) >= NUMBER_OF_PLACE_STRINGS) return place_ordinal_string(entry->place & 0x7F, FALSE); string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) return unicode_string_list_get_string( string_list_index, string_index + 36); return L""; } static long game_engine_did_team_win( long team_index) { struct data_iterator iterator; struct player_datum *player; long result = FALSE; data_iterator_new(&iterator, player_data); player = (struct player_datum *)data_iterator_next(&iterator); while (player) { if (player->team_index == team_index) { result = FALSE; if (game_engine) { if (game_engine->did_player_win) result = game_engine->did_player_win(iterator.datum_index); else result = game_engine_did_player_win_default( iterator.datum_index); } break; } player = (struct player_datum *)data_iterator_next(&iterator); } return result; } static void game_engine_generate_title_string( wchar_t *title_string, long player_index) { wchar_t *secondary_string; wchar_t life_string[128]; wchar_t score_string[256]; long string_list_index; wchar_t *format_string; player_get(player_index); secondary_string = L""; match_assert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0x36E, title_string); if (global_variant.universal_variant.lives > 0) { struct player_datum *player = player_get(player_index); long remaining_lives = global_variant.universal_variant.lives - player->statistics.deaths; switch (remaining_lives) { case 0: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) { secondary_string = unicode_string_list_get_string( string_list_index, 0x34); } else secondary_string = L""; break; case 1: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) { secondary_string = unicode_string_list_get_string( string_list_index, 0x35); } else secondary_string = L""; break; default: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) { format_string = unicode_string_list_get_string( string_list_index, 0x36); } else format_string = L""; usnprintf( life_string, NUMBEROF(life_string), format_string, remaining_lives); life_string[NUMBEROF(life_string) - 1] = 0; secondary_string = life_string; break; } } if (game_engine_globals.postgame_state == game_engine_mode_postgame_delay) { long did_player_win = game_engine_did_player_win(player_index); boolean has_teams = game_engine_has_teams(); wchar_t *outcome_string; switch (did_player_win) { case NONE: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) { outcome_string = unicode_string_list_get_string( string_list_index, 0x37); } else outcome_string = L""; ustrncpy(title_string, outcome_string, 80); break; case FALSE: if (has_teams) { string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) { outcome_string = unicode_string_list_get_string( string_list_index, 0x38); } else outcome_string = L""; ustrncpy(title_string, outcome_string, 80); } else { string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) { outcome_string = unicode_string_list_get_string( string_list_index, 0x39); } else outcome_string = L""; ustrncpy(title_string, outcome_string, 80); } break; case TRUE: if (has_teams) { string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) { outcome_string = unicode_string_list_get_string( string_list_index, 0x3A); } else outcome_string = L""; ustrncpy(title_string, outcome_string, 80); } else { string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) { outcome_string = unicode_string_list_get_string( string_list_index, 0x3B); } else outcome_string = L""; ustrncpy(title_string, outcome_string, 80); } break; } } else if (game_engine_has_teams()) { wchar_t team0_name[8]; wchar_t team1_name[8]; long team0_score; long team1_score; game_engine->format_team_name( 0, team0_name); game_engine->format_team_name( 1, team1_name); team0_score = game_engine_get_team_score(0); team1_score = game_engine_get_team_score(1); if (team0_score > team1_score) { string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) { format_string = unicode_string_list_get_string(string_list_index, 0x3C); } else format_string = L""; usnprintf( title_string, 80, format_string, team0_name, team1_name, secondary_string); } else if (team0_score < team1_score) { string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) { format_string = unicode_string_list_get_string(string_list_index, 0x3D); } else format_string = L""; usnprintf( title_string, 80, format_string, team1_name, team0_name, secondary_string); } else { string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) { format_string = unicode_string_list_get_string(string_list_index, 0x3E); } else format_string = L""; usnprintf( title_string, 80, format_string, team1_name, secondary_string); } } else { struct statistic_buffer entry; entry = game_engine_get_player_place(player_index); game_engine->format_player_score(player_index, score_string); if (is_place_tied(&entry)) { string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) { format_string = unicode_string_list_get_string( string_list_index, 0x3F); } else format_string = L""; usnprintf( title_string, 80, format_string, get_place_string(&entry), score_string, secondary_string); } else { string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) { format_string = unicode_string_list_get_string( string_list_index, 0x40); } else format_string = L""; usnprintf( title_string, 80, format_string, get_place_string(&entry), score_string, secondary_string); } } title_string[79] = 0; return; } static void rasterize_in_game_score_draw_line( wchar_t const *string, boolean brighten, real_argb_color *color, long row_index) { rectangle2d bounds = render.camera.window_bounds; short narrow_tab_stops[3]; short wide_tab_stops[3]; short *tab_stops; boolean splitscreen; long font_index; splitscreen = local_player_count() > 1; font_index = hud_get_font_index(); narrow_tab_stops[0] = 80; narrow_tab_stops[1] = 125; narrow_tab_stops[2] = 200; wide_tab_stops[0] = 130; wide_tab_stops[1] = 195; wide_tab_stops[2] = 315; if (bounds.x1 - bounds.x0 > 320) tab_stops = wide_tab_stops; else tab_stops = narrow_tab_stops; if (row_index) draw_string_set_tab_stops(tab_stops, 3); else draw_string_set_tab_stops(NULL, 0); offset_rectangle2d( &bounds, -render.camera.viewport_bounds.x0, -render.camera.viewport_bounds.y0); if (font_index != NONE) { struct font_header *font = font_definition_get(font_index); long row_offset = 4 * (splitscreen == FALSE) + 4; long line_height = font->leading_height; if (!splitscreen) line_height += font->descending_height; line_height += font->ascending_height; if (brighten) { color->red += 0.4f; color->green += 0.4f; color->blue += 0.4f; if (color->red > 1.0f) color->red = 1.0f; if (color->green > 1.0f) color->green = 1.0f; if (color->blue > 1.0f) color->blue = 1.0f; } row_index += row_offset; bounds.y0 = (short)(row_index * line_height); bounds.y1 = (short)((row_index + 1) * line_height); draw_string_set_draw_mode(font_index, NONE, 0, 0, color); rasterizer_draw_unicode_string(&bounds, NULL, NULL, 0, string); } draw_string_set_tab_stops(NULL, 0); return; } long populate_statistic_buffer( struct statistic_buffer *statistic_buffer, enum postgame_statistic statistic, boolean inverse) { long player_count = 0; boolean invert = statistic == _postgame_statistic_deaths ? !inverse : inverse; struct data_iterator player_iterator; struct player_datum *player; long statistic_index; data_iterator_new(&player_iterator, player_data); while ((player = (struct player_datum *)data_iterator_next(&player_iterator)) != NULL) { match_assert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0x2C8, player_count < MULTIPLAYER_MAXIMUM_PLAYERS); if (player_count < MULTIPLAYER_MAXIMUM_PLAYERS) { statistic_buffer[player_count].player_index = player_iterator.datum_index; player_count++; } } for (statistic_index = 0; statistic_index < player_count; statistic_index++) { long player_index = statistic_buffer[statistic_index].player_index; player = player_get(player_index); switch (statistic) { case _postgame_statistic_ranking: statistic_buffer[statistic_index].custom = 0; if (game_engine->get_player_score) { statistic_buffer[statistic_index].custom = adjust_score_for_ranking( game_engine->get_player_score(player_index, FALSE), player_index); } statistic_buffer[statistic_index].kills = player->statistics.kills[0]; statistic_buffer[statistic_index].assists = player->statistics.assists[0]; statistic_buffer[statistic_index].deaths = player->statistics.deaths; break; case _postgame_statistic_score: statistic_buffer[statistic_index].score = 0; if (game_engine->get_player_score) { statistic_buffer[statistic_index].score = adjust_score_for_ranking( game_engine->get_player_score(player_index, FALSE), player_index); } break; case _postgame_statistic_kills: statistic_buffer[statistic_index].score = player->statistics.kills[0]; break; case _postgame_statistic_assists: statistic_buffer[statistic_index].score = player->statistics.assists[0]; break; case _postgame_statistic_deaths: statistic_buffer[statistic_index].score = player->statistics.deaths; break; default: match_assert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0x2FD, !"unreachable"); break; } if (invert) statistic_buffer[statistic_index].score = -statistic_buffer[statistic_index].score; } if (statistic == _postgame_statistic_ranking) { qsort( statistic_buffer, player_count, sizeof(*statistic_buffer), sort_statistic_buffer_ranking); } else { qsort( statistic_buffer, player_count, sizeof(*statistic_buffer), sort_statistic_buffer); } for (statistic_index = 0; statistic_index < player_count; statistic_index++) { if (statistic_index != 0 && sort_statistic_buffer_ranking( &statistic_buffer[statistic_index - 1], &statistic_buffer[statistic_index]) == 0) { statistic_buffer[statistic_index - 1].place |= 0x80000000; statistic_buffer[statistic_index].place = statistic_buffer[statistic_index - 1].place; } else { statistic_buffer[statistic_index].place = statistic_index; } } return player_count; } static long select_players_to_display( enum postgame_statistic statistic, long player_index, struct statistic_buffer *output, long maximum_count) { struct statistic_buffer statistic_buffer[MULTIPLAYER_MAXIMUM_PLAYERS]; long player_count = populate_statistic_buffer(statistic_buffer, statistic, FALSE); boolean debug = rasterizer_debug_options.pad3 == 'E'; long local_player_count = 0; long statistic_index; if (debug) { terminal_printf( global_real_argb_white, "player_count=%d, maxcount=%d", player_count, maximum_count); } for (statistic_index = 0; statistic_index < player_count; statistic_index++) { struct player_datum *player = player_get(statistic_buffer[statistic_index].player_index); if (player->local_player_index != NONE) local_player_count++; } if (player_count > maximum_count) { long outside_range_count = 0; struct statistic_buffer outside_range[MAXIMUM_LOCAL_PLAYERS]; long outside_range_index; for (statistic_index = maximum_count; statistic_index < player_count; statistic_index++) { struct player_datum *player = player_get(statistic_buffer[statistic_index].player_index); if (player && player->local_player_index != NONE) { if (debug) { terminal_printf( global_real_argb_white, "found local player"); } outside_range[outside_range_count] = statistic_buffer[statistic_index]; outside_range_count++; } } for (outside_range_index = 0; outside_range_index < outside_range_count; outside_range_index++) { long insertion_index; for (insertion_index = maximum_count - 1; insertion_index >= 0; insertion_index--) { struct player_datum *player = player_get(statistic_buffer[insertion_index].player_index); if (player->local_player_index == NONE) { csmemmove( &statistic_buffer[insertion_index], &statistic_buffer[insertion_index + 1], maximum_count * sizeof(struct statistic_buffer) - (insertion_index + 1) * sizeof(struct statistic_buffer)); statistic_buffer[maximum_count - 1] = outside_range[outside_range_index]; break; } } } } csmemcpy( output, statistic_buffer, MIN(maximum_count, player_count) * sizeof(struct statistic_buffer)); return MIN(maximum_count, player_count); } static void game_engine_rasterize_in_game_score( long player_index, real alpha) { wchar_t row_string[256]; wchar_t score_string[256]; struct statistic_buffer entries[6]; wchar_t title_string[80]; real_argb_color text_color; real_argb_color team_colors[2]; real_argb_color color; boolean has_teams = game_engine_has_teams(); boolean is_current_player; long entry_count; long entry_index; long string_list_index; wchar_t *column_name; wchar_t *score_name; game_engine_generate_title_string(title_string, player_index); entry_count = select_players_to_display( _postgame_statistic_ranking, player_index, entries, NUMBEROF(entries)); color.alpha = alpha; color.red = 0.7f; color.green = 0.7f; color.blue = 0.7f; rasterize_in_game_score_draw_line(title_string, FALSE, &color, 0); color.red = 0.5f; color.green = 0.5f; color.blue = 0.5f; color.alpha = alpha; string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) column_name = unicode_string_list_get_string(string_list_index, 0x43); else column_name = L""; string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) score_name = unicode_string_list_get_string(string_list_index, 0x44); else score_name = L""; game_engine->format_score_name(score_string); usprintf(row_string, L"\t%s\t%s\t%s", column_name, score_name, score_string); rasterize_in_game_score_draw_line(row_string, FALSE, &color, 1); for (entry_index = 0; entry_index < entry_count; entry_index++) { long entry_player_index = entries[entry_index].player_index; struct player_datum *player = player_try_and_get(entry_player_index); wchar_t *status_string; wchar_t *place_string; real_argb_color *row_color; is_current_player = player_index == entry_player_index; if (player) { color = *hud_get_text_color(&text_color); player = player_get(entry_player_index); team_colors[0].alpha = alpha; team_colors[0].red = 0.6f; team_colors[0].green = 0.3f; team_colors[0].blue = 0.3f; team_colors[1].alpha = alpha; team_colors[1].red = 0.3f; team_colors[1].green = 0.3f; team_colors[1].blue = 0.6f; color.alpha = alpha; game_engine->format_player_score( entry_player_index, score_string); if (game_engine_player_is_out_of_lives(entry_player_index)) { string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) status_string = unicode_string_list_get_string(string_list_index, 0x8A); else status_string = L""; } else if (player->quit_out_of_game) { string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) status_string = unicode_string_list_get_string(string_list_index, 0x8B); else status_string = L""; } else status_string = score_string; place_string = get_place_string(&entries[entry_index]); usprintf( row_string, L"\t%s\t%s\t%s", place_string, player->name, status_string); if (has_teams) row_color = &team_colors[PIN(player->team_index, 0, 1)]; else row_color = &color; rasterize_in_game_score_draw_line( row_string, is_current_player, row_color, entry_index + 2); } } return; } static void drawline( wchar_t const *string, long row_index, short justification) { rectangle2d bounds = render.camera.window_bounds; long line_height = 18; long line_spacing = 8; offset_rectangle2d( &bounds, -render.camera.viewport_bounds.x0, -render.camera.viewport_bounds.y0); bounds.y0 = (short)(row_index * line_height); bounds.y1 = (short)((row_index + 1) * line_height + line_spacing); draw_string_set_format(NONE, justification, 0); rasterizer_draw_unicode_string(&bounds, NULL, NULL, 0, string); return; } void game_engine_post_rasterize_post_game( void) { /* port: sized like every buffer handed to populate_statistic_buffer */ struct statistic_buffer entries[MULTIPLAYER_MAXIMUM_PLAYERS]; wchar_t score_string[256]; wchar_t row_string[256]; short tab_stops[6]; real_argb_color winner_color; real_argb_color normal_color; real_argb_color hilite_color; long font_index; long entry_count; long entry_index; if (!game_engine) return; tab_stops[0] = 50; tab_stops[1] = 125; tab_stops[2] = 250; tab_stops[3] = 350; tab_stops[4] = 410; tab_stops[5] = 500; font_index = hud_globals->messaging.single_player_font.index; get_postgame_hilite_colors( &winner_color, &normal_color, &hilite_color); draw_string_set_draw_mode( font_index, NONE, 2, 8, &winner_color); draw_string_set_color(&winner_color); draw_string_set_format(NONE, 0, 0); { struct hud_globals_definition *hud_definition = hud_globals_definition_get( interface_get_tag_index(_interface_hud_globals)); rectangle2d bounds; bounds.x0 = 0; bounds.y0 = 0; bounds.x1 = 640; bounds.y1 = 480; if (bitmap_group_try_and_get_bitmap(hud_definition->carnage_report_bitmap.index, 0)) { draw_bitmap_in_rect( bitmap_group_try_and_get_bitmap(hud_definition->carnage_report_bitmap.index, 0), &bounds, &bounds, NULL, NONE, NULL, TRUE); } } if (global_variant.universal_variant.teams) { short team_tab_stops[6] = { 50, 200, 300, 350, 410, 500 }; long team_order[2] = { 0, 1 }; wchar_t const *team_formats[2]; long string_list_index; long team_row; long red_team_won; red_team_won = game_engine_did_team_win(0); string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); team_formats[0] = string_list_index != NONE ? unicode_string_list_get_string(string_list_index, 0x41) : L""; string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); team_formats[1] = string_list_index != NONE ? unicode_string_list_get_string(string_list_index, 0x42) : L""; if (!red_team_won) { team_order[0] = 1; team_order[1] = 0; } draw_string_set_tab_stops(team_tab_stops, NUMBEROF(team_tab_stops)); for (team_row = 0; team_row < 2; team_row++) { long team_index = team_order[team_row]; game_engine->format_team_name( team_index, score_string); usnprintf( row_string, NUMBEROF(row_string), team_formats[team_index], score_string); row_string[NUMBEROF(row_string) - 1] = 0; drawline(row_string, team_row + 4, 0); } } { long string_list_index; wchar_t const *column_strings[5]; string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); column_strings[0] = string_list_index != NONE ? unicode_string_list_get_string(string_list_index, 0x43) : L""; string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); column_strings[1] = string_list_index != NONE ? unicode_string_list_get_string(string_list_index, 0x44) : L""; string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); column_strings[2] = string_list_index != NONE ? unicode_string_list_get_string(string_list_index, 0x45) : L""; string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); column_strings[3] = string_list_index != NONE ? unicode_string_list_get_string(string_list_index, 0x46) : L""; string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); column_strings[4] = string_list_index != NONE ? unicode_string_list_get_string(string_list_index, 0x47) : L""; game_engine->format_score_name(score_string); usnprintf( row_string, NUMBEROF(row_string), L"\t%s\t%s\t%s\t%s\t%s\t%s", column_strings[0], column_strings[1], score_string, column_strings[2], column_strings[3], column_strings[4]); row_string[NUMBEROF(row_string) - 1] = 0; draw_string_set_tab_stops(tab_stops, NUMBEROF(tab_stops)); drawline(row_string, 7, 0); } entry_count = select_players_to_display(_postgame_statistic_ranking, NONE, entries, 12); for (entry_index = 0; entry_index < entry_count; entry_index++) { long player_index = entries[entry_index].player_index; long draw_row = entry_index + 8; struct player_datum *player = player_get(player_index); real_argb_color team_colors[2]; wchar_t const *place_string; if (player->local_player_index != NONE) draw_string_set_color(&normal_color); else draw_string_set_color(&winner_color); draw_string_set_tab_stops(tab_stops, NUMBEROF(tab_stops)); place_string = get_place_string(&entries[entry_index]); usnprintf(row_string, NUMBEROF(row_string), L" \t%s", place_string); row_string[NUMBEROF(row_string) - 1] = 0; drawline(row_string, draw_row, 0); draw_string_set_color(&winner_color); if (global_variant.universal_variant.teams) { long team_index = player->team_index; team_colors[0].red = 0.8f; team_colors[0].green = 0.4f; team_colors[0].blue = 0.4f; team_colors[0].alpha = 1.0f; team_colors[1].red = 0.4f; team_colors[1].green = 0.4f; team_colors[1].blue = 0.8f; team_colors[1].alpha = 1.0f; draw_string_set_color( &team_colors[PIN(team_index, 0, 1)]); } usnprintf(row_string, NUMBEROF(row_string), L" \t \t%s", player->name); row_string[NUMBEROF(row_string) - 1] = 0; drawline(row_string, draw_row, 0); draw_string_set_color(&winner_color); if (!postgame_statistic_get_rating(player_index, _postgame_statistic_score, FALSE)) draw_string_set_color(&hilite_color); game_engine->format_player_score( player_index, score_string); usnprintf(row_string, NUMBEROF(row_string), L" \t \t \t%s", score_string); row_string[NUMBEROF(row_string) - 1] = 0; drawline(row_string, draw_row, 0); draw_string_set_color(&winner_color); if (!postgame_statistic_get_rating(player_index, _postgame_statistic_kills, FALSE)) draw_string_set_color(&hilite_color); usnprintf( row_string, NUMBEROF(row_string), L" \t \t \t \t%d", (long)player->statistics.kills[0]); row_string[NUMBEROF(row_string) - 1] = 0; drawline(row_string, draw_row, 0); draw_string_set_color(&winner_color); if (!postgame_statistic_get_rating(player_index, _postgame_statistic_assists, FALSE)) draw_string_set_color(&hilite_color); usnprintf( row_string, NUMBEROF(row_string), L" \t \t \t \t \t%d", (long)player->statistics.assists[0]); row_string[NUMBEROF(row_string) - 1] = 0; drawline(row_string, draw_row, 0); draw_string_set_color(&winner_color); if (!postgame_statistic_get_rating(player_index, _postgame_statistic_deaths, FALSE)) draw_string_set_color(&hilite_color); usnprintf( row_string, NUMBEROF(row_string), L" \t \t \t \t \t \t%d", (long)player->statistics.deaths); row_string[NUMBEROF(row_string) - 1] = 0; drawline(row_string, draw_row, 0); draw_string_set_tab_stops(tab_stops, NUMBEROF(tab_stops)); } { real_argb_color prompt_color; long string_list_index; wchar_t const *prompt; struct network_game_server *server; rectangle2d bounds; bounds = render.camera.window_bounds; prompt_color = winner_color; prompt_color.alpha = game_engine_globals.postgame_progress; bounds.y0 = 410; bounds.x0 = 70; offset_rectangle2d( &bounds, -render.camera.viewport_bounds.x0, -render.camera.viewport_bounds.y0); draw_string_set_tab_stops(NULL, 0); draw_string_set_color(&prompt_color); server = global_network_game_server_get(); if (server) { bounds.x0 = 380; string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) prompt = unicode_string_list_get_string(string_list_index, 0x48); else prompt = L""; draw_string_and_hack_in_icons( &bounds, NULL, NULL, 0, prompt, FALSE); return; } bounds.x0 = 520; string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) prompt = unicode_string_list_get_string(string_list_index, 0x49); else prompt = L""; draw_string_and_hack_in_icons( &bounds, NULL, NULL, 0, prompt, FALSE); } return; } static long find_closest_player_index( long player_index) { struct player_datum *player = player_get(player_index); long best_object_index = NONE; if (player->local_player_index != NONE && player_control_get_autoaim_level(player->local_player_index) > 0.0f) { best_object_index = player_control_get_target_object_index(player->local_player_index); } if (best_object_index == NONE) { long object_indices[32]; real_point3d camera_position; real_vector3d facing_direction; long object_count; long object_index; unit_get_camera_position(player->unit_index, &camera_position); player_control_get_facing_direction( player->local_player_index, &facing_direction); object_count = find_objects_from_point_vector( &camera_position, &facing_direction, find_closest_player_callback, &player_index, NUMBEROF(object_indices), object_indices); for (object_index = 0; object_index < object_count; object_index++) { long candidate_object_index = object_indices[object_index]; struct unit_datum *candidate = unit_get(candidate_object_index); real distance_squared = distance_squared3d( &camera_position, &candidate->object.position); real_point3d target_position; real_vector3d target_direction; real target_distance; real target_angle; if ((candidate->unit.active_camouflage < 1.0f || player->player_display_index == player_index_from_unit_index(candidate_object_index)) && autoaim_compute_target( object_indices[object_index], &camera_position, &facing_direction, player->unit_index, &target_position, &target_direction, &target_distance, &target_angle) && fabs(target_angle) < 0.13083334267139435 && distance_squared < 400.0f && distance_squared < 900.0f) { best_object_index = object_indices[object_index]; } } } if (best_object_index != NONE) best_object_index = player_index_from_unit_index(best_object_index); return best_object_index; } long game_engine_remap_equipment( long equipment_definition_index) { struct equipment_definition *equipment; long weapon_list_index; boolean remap_equipment; if (equipment_definition_index == NONE) equipment = NULL; else equipment = equipment_definition_get(equipment_definition_index); weapon_list_index = weapon_definition_index_to_list_index(equipment_definition_index); remap_equipment = weapon_list_index == _weapon_list_frag_grenade || weapon_list_index == _weapon_list_plasma_grenade; if (!remap_equipment) { if (equipment) { if (equipment->equipment.powerup_type == _equipment_powerup_overshield) { if (TEST_FLAG(global_variant.universal_variant.flags, _game_variant_no_shields_bit)) return NONE; } else if (equipment->equipment.powerup_type == _equipment_powerup_active_camouflage) { if (TEST_FLAG(global_variant.universal_variant.flags, _game_variant_always_invisible_bit)) return NONE; } } } else { switch (global_variant.universal_variant.weapon_set) { case _game_engine_weapons_plasma_weapons: weapon_list_index = _weapon_list_plasma_grenade; break; case _game_engine_weapons_human: weapon_list_index = _weapon_list_frag_grenade; break; case _game_engine_weapons_no_grenades: weapon_list_index = NONE; break; } if (!TEST_FLAG(game_engine_globals.flags, _game_engine_5_or_more_players_bit)) { if (game_engine_infinite_grenades_internal()) { weapon_list_index = NONE; } } if (TEST_FLAG(game_engine_globals.flags, _game_engine_9_or_more_players_bit)) { if (real_seed_random(get_global_random_seed_address()) > 0.3f) weapon_list_index = NONE; } else if (TEST_FLAG(game_engine_globals.flags, _game_engine_5_or_more_players_bit)) { if (real_seed_random(get_global_random_seed_address()) > 0.55f) weapon_list_index = NONE; } } if (remap_equipment) return list_index_to_weapon_definition_index(weapon_list_index); return equipment_definition_index; } int __cdecl sort_statistic_buffer( void const *entry0_pointer, void const *entry1_pointer) { struct statistic_buffer const *entry0 = entry0_pointer; struct statistic_buffer const *entry1 = entry1_pointer; long result = 0; if (entry0->score > entry1->score) result = -1; else if (entry1->score > entry0->score) result = 1; return result; } int __cdecl sort_statistic_buffer_ranking( void const *entry0_pointer, void const *entry1_pointer) { struct statistic_buffer const *entry0 = entry0_pointer; struct statistic_buffer const *entry1 = entry1_pointer; long result = 0; if (entry0->custom > entry1->custom) result = -1; else if (entry1->custom > entry0->custom) result = 1; else if (entry0->kills > entry1->kills) result = -1; else if (entry1->kills > entry0->kills) result = 1; else if (entry0->deaths > entry1->deaths) result = 1; else if (entry1->deaths > entry0->deaths) result = -1; else if (entry0->assists > entry1->assists) result = -1; else if (entry1->assists > entry0->assists) result = 1; return result; } static boolean is_place_tied( struct statistic_buffer const *entry) { boolean result = (entry->place & 0x80000000) > 0; return result; } static long place_get_position( struct statistic_buffer const *entry) { return entry->place & ~FLAG(31); } static void get_selected_color( real_argb_color *color) { color->red = 0.98f; color->green = 0.96f; color->blue = 0.96f; color->alpha = 1.0f; return; } static boolean can_delete_item( long weapon_index) { boolean result = TRUE; if (weapon_try_and_get(weapon_index) && weapon_is_flag(weapon_index)) { result = FALSE; } return result; } static boolean multiplayer_message_internal( long player_index, long message, long message_data, wchar_t *buffer, long buffer_size) { boolean result = FALSE; if (game_engine->format_message) { result = game_engine->format_message( player_index, message, message_data, buffer, buffer_size); } if (!result) { result = internal_rasterize_score( player_index, message, message_data, buffer, buffer_size); } return result; } static void multiplayer_message( long player_index, long message, long message_data) { struct player_datum *player = player_get(player_index); if (player->local_player_index != NONE) { wchar_t buffer[1024]; if (multiplayer_message_internal( player_index, message, message_data, buffer, NUMBEROF(buffer))) { buffer[NUMBEROF(buffer) - 1] = 0; hud_print_message(player->local_player_index, buffer); } } return; } static void game_show_score_one_player( long player_index, long message, long message_data) { if (message != NONE) multiplayer_message(player_index, message, message_data); return; } void game_show_score_team( long team_index, long score) { struct data_iterator iterator; struct player_datum *player; data_iterator_new(&iterator, player_data); player = (struct player_datum *)data_iterator_next(&iterator); while (player) { if (player->team_index == team_index) { game_show_score_one_player(iterator.datum_index, score, NONE); } player = (struct player_datum *)data_iterator_next(&iterator); } return; } void game_show_score_you_ally_enemy( long player_index, long you_score, long ally_score, long enemy_score, long other_player_index) { struct player_datum *local_player; struct data_iterator iterator; struct player_datum *player; long score; local_player = player_get(player_index); match_assert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0xB1A, NONE != player_index); data_iterator_new(&iterator, player_data); player = (struct player_datum *)data_iterator_next(&iterator); while (player) { if (iterator.datum_index == player_index) { if (you_score != NONE) { game_show_score_one_player( iterator.datum_index, you_score, other_player_index); } } else { if (game_team_is_enemy( (short)local_player->team_index, (short)player->team_index)) { score = enemy_score; } else score = ally_score; if (score != NONE) { game_show_score_one_player( iterator.datum_index, score, other_player_index); } } player = (struct player_datum *)data_iterator_next(&iterator); } return; } void game_show_score_extended( long player_index, long score, long team_index) { if (player_index != NONE) { game_show_score_one_player(player_index, score, team_index); } else { struct data_iterator iterator; data_iterator_new(&iterator, player_data); while (data_iterator_next(&iterator)) { game_show_score_one_player( iterator.datum_index, score, team_index); } } return; } long players_in_game( void) { struct data_iterator iterator; long player_count = 0; data_iterator_new(&iterator, player_data); while (data_iterator_next(&iterator)) player_count++; return player_count; } static void game_engine_press_start_to_begin( void) { return; } static boolean goal_matches_player( struct player_datum *player, long player_index, long goal_index) { boolean result = FALSE; if (game_engine) { struct netgame_goal *goal = &global_goal[goal_index]; if (game_engine->player_can_see_goal) { if (goal->in_use) result = game_engine->player_can_see_goal(player_index, goal_index); } else if ( goal->in_use && (goal->player_index == NONE || player_index == goal->player_index) && (goal->team_index == NONE || player->team_index == goal->team_index) && (goal->ignore_player_index == NONE || player_index != goal->ignore_player_index)) { result = TRUE; } } return result; } static boolean nearby_vehicle( long player_index, struct player_starting_location const *starting_location) { struct location location; long object_indices[16]; short object_count; short object_index; player_get(player_index); scenario_location_from_point(&location, &starting_location->position); object_count = objects_in_sphere( 0, 0x11F, &location, &starting_location->position, 0.1f, object_indices, NUMBEROF(object_indices)); for (object_index = 0; object_index < object_count; object_index++) { struct object_datum *object = object_get(object_indices[object_index]); match_assert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0xEE9, object); if (object->object.type == _object_type_vehicle) { struct vehicle_datum *vehicle = (struct vehicle_datum *)object_get_and_verify_type( object_indices[object_index], _object_mask_vehicle); match_assert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0xEEE, vehicle); if (vehicle) return TRUE; return FALSE; } } return FALSE; } static real game_engine_get_friendly_bonus( long player_index, real_point3d const *position) { struct player_datum *player = player_get(player_index); struct data_iterator iterator; struct player_datum *other_player; real rating = 0.0f; data_iterator_new(&iterator, player_data); other_player = (struct player_datum *)data_iterator_next(&iterator); while (other_player) { if (player->team_index == other_player->team_index && other_player->unit_index != NONE) { real_point3d origin; real distance; object_get_origin(other_player->unit_index, &origin); distance = distance3d(&origin, position); if (distance >= 1.0f && distance <= 6.0f) { rating += (real)pow( (double)(1.0f - (distance - 1.0f) * 0.2f), (double)0.6f); } } other_player = (struct player_datum *)data_iterator_next(&iterator); } if (rating > 3.0f) rating = 3.0f; return rating * 3.0f + 1.0f; } static real default_starting_location_rate_function( long player_index, struct player_starting_location const *starting_location) { struct player_datum *player = player_get(player_index); real rating = 1.0f; if (game_engine_running() && game_engine_test_flag(0) && player->team_index != starting_location->team_index) { rating = 0.0f; } if (rating > 0.0f) { rating *= game_engine_get_distance_rating_for_spawn( player_index, &starting_location->position); } if (game_engine_running() && rating > 0.0f && game_engine_has_teams()) { rating *= game_engine_get_friendly_bonus( player_index, &starting_location->position); } if (game_engine_running() && game_engine->starting_location_rating) { rating *= game_engine->starting_location_rating( player_index, starting_location); } return rating; } static boolean test_any_gamepad_button( long button_index) { boolean result = FALSE; long gamepad_index; for (gamepad_index = 0; gamepad_index < 4; gamepad_index++) { struct gamepad_state const *gamepad_state = input_get_gamepad_state((short)gamepad_index); if (gamepad_state && gamepad_state->buttons[button_index]) { result = TRUE; break; } } return result; } boolean match_game_type( long game_type, long count, short const *game_types) { boolean result; long index; if (game_engine) { result = FALSE; for (index = 0; index < count; index++) { short entry = game_types[index]; result = result | (entry == game_type); if (entry == _game_engine_all) { result = result | TRUE; } else if (entry == _game_engine_all_non_team) { result = result | (game_type != game_engine_ctf); } else if (entry == _game_engine_all_normal) { result = result | (game_type != game_engine_ctf && game_type != game_engine_race); } } } else { result = TRUE; for (index = 0; index < count; index++) result = result & (game_types[index] == game_engine_none); } return result; } static boolean game_engine_infinite_grenades_internal( void) { boolean infinite_grenades = FALSE; if (!TEST_FLAG(game_engine_globals.flags, _game_engine_5_or_more_players_bit)) infinite_grenades = TEST_FLAG(global_variant.universal_variant.flags, _game_variant_infinite_grenades_bit); return infinite_grenades; } static boolean find_closest_player_callback( long object_index, void *custom_data) { long excluded_player = *(long *)custom_data; boolean result = FALSE; struct object_datum *object = object_get(object_index); if (!TEST_FLAG(object->object.flags, _object_invisible_bit) && TEST_FLAG(_object_mask_biped, object->object.type) && !TEST_FLAG(object->object.damage_flags, _object_dead_bit) && player_index_from_unit_index(object_index) != excluded_player) { result = TRUE; } return result; } static void game_engine_update_purge( void) { long cutoff_time = game_time_get() - 900; /* (a client of the distributed netcode removes items when the host does, port/linux/game/network_distributed.c) */ if (!network_game_distributed_client()) { struct object_iterator item_iterator; object_iterator_new(&item_iterator, _object_mask_item, 0); while (object_iterator_next(&item_iterator)) { struct item_datum *item = item_get(item_iterator.index); if (item->item.last_owned_time < cutoff_time && !TEST_FLAG(item->item.flags, _item_attached_to_unit_bit)) { long item_index = item_iterator.index; if (can_delete_item(item_index)) object_delete(item_iterator.index); } } } { struct object_iterator biped_iterator; object_iterator_new(&biped_iterator, _object_mask_biped, 0); while (object_iterator_next(&biped_iterator)) { struct object_datum *object; cutoff_time = 900; object = object_get(biped_iterator.index); if (object->object.idle_ticks > (short)cutoff_time && TEST_FLAG(object->object.damage_flags, _object_dead_bit)) { object_delete(biped_iterator.index); } } } return; } void game_engine_flag_reset( long weapon_index, real_point3d const *position) { if (weapon_index != NONE) { struct weapon_datum *weapon = weapon_get(weapon_index); object_set_position(weapon_index, position, global_forward3d, global_up3d); object_reset(weapon_index); SET_FLAG(weapon->weapon.flags, _weapon_runtime_game_engine_active_bit, FALSE); weapon->item.last_owned_time = game_time_get(); weapon->item.ignore_object_index = NONE; } return; } void ticks_to_unicode_time_string( long ticks, unsigned long character_count, wchar_t *string) { long total_seconds; long minutes; long seconds; wchar_t minute_string[64]; wchar_t second_string[64]; total_seconds = ticks / 30; minutes = total_seconds / 60; seconds = total_seconds - minutes * 60; if (minutes == 0) usnprintf(minute_string, NUMBEROF(minute_string), L" "); else usnprintf(minute_string, NUMBEROF(minute_string), L"%d", minutes); if (seconds <= 9) usnprintf(second_string, NUMBEROF(second_string), L"0%d", seconds); else usnprintf(second_string, NUMBEROF(second_string), L"%d", seconds); usnprintf(string, character_count, L"%s:%s", minute_string, second_string); return; } void game_engine_playlist_initialize( void) { game_engine_playlist_next(0, 0, 2); return; } void game_engine_playlist_begin( void) { main_set_multiplayer_map_name(global_stage.map_name); game_set_game_variant(&global_stage.variant); if (!network_game_is_active()) main_reset_map(); return; } boolean game_engine_get_current_stage( struct game_variant *variant, char *map_name) { match_assert("c:\\halo\\SOURCE\\game\\game_engine.c", 0x918, variant && map_name); csmemcpy(variant, &global_stage.variant, sizeof(*variant)); csstrncpy(map_name, global_stage.map_name, sizeof(global_stage.map_name)-1); map_name[sizeof(global_stage.map_name)-1] = 0; return TRUE; } long list_index_to_weapon_definition_index( long weapon_list_index) { long weapon_definition_index = NONE; if (weapon_list_index!=NONE) { struct game_globals *game_globals = scenario_get_game_globals(); weapon_definition_index = game_globals_get_weapon(game_globals, weapon_list_index); } return weapon_definition_index; } long weapon_definition_index_to_list_index( long weapon_definition_index) { struct game_globals *game_globals = scenario_get_game_globals(); struct tag_reference *weapons = game_globals->weapon_list.count ? TAG_BLOCK_GET_ELEMENT(&game_globals->weapon_list, 0, struct tag_reference) : NULL; long result = NONE; long weapon_list_index; for (weapon_list_index = 0; weapon_list_index < game_globals->weapon_list.count; weapon_list_index++) { long definition_index = weapons[weapon_list_index].index; if (weapon_definition_index == definition_index) { result = weapon_list_index; break; } } return result; } void game_engine_state_message( long player_index, long state_message, long state_message_player_index) { struct player_datum *player = player_get(player_index); player->state_message = state_message; player->state_message_player_index = state_message_player_index; return; } void game_engine_player_depower_active_camo( long player_index) { if (player_index!=NONE) { struct player_datum *player = player_get(player_index); if (player->unit_index!=NONE) { struct unit_datum *unit = unit_get(player->unit_index); if (TEST_FLAG(unit->unit.flags, _unit_active_camouflaged_bit)) unit->unit.active_camouflage = 0.5f; } } return; } real get_blink_alpha( void) { long phase = system_milliseconds()%2700; return sin(phase * (3.14159265358979/2700.0)); } void game_engine_rasterize_message( wchar_t const *message, real alpha) { rectangle2d bounds; real_argb_color color; long font_index; long terminal_font_index; if (local_player_count()) font_index = hud_globals->messaging.multi_player_font.index; else font_index = hud_globals->messaging.single_player_font.index; bounds = render.camera.window_bounds; terminal_font_index = interface_get_tag_index(_interface_font_terminal); draw_string_set_draw_mode( terminal_font_index, NONE, 0, 0, global_real_argb_white); color.alpha = alpha; color.red = 0.45882353f; color.green = 0.7294118f; color.blue = 1.0f; offset_rectangle2d( &bounds, -render.camera.viewport_bounds.x0, -render.camera.viewport_bounds.y0); bounds.y1 = (short)((5 * bounds.y0 + bounds.y1) / 6 + 9); bounds.y0 = bounds.y1 - 15; draw_string_set_draw_mode(font_index, NONE, 2, 8, &color); draw_string_set_color(&color); rasterizer_draw_unicode_string(&bounds, NULL, NULL, 0, message); draw_string_set_format(NONE, 0, 0); draw_string_set_tab_stops(NULL, 0); return; } static void game_engine_post_rasterize_in_game( void) { long local_player_index; long player_index; struct player_datum *player; struct gamepad_state const *gamepad; real fade; local_player_index = render.local_player_index; player_index = local_player_get_player_index(local_player_index); player = player_get(player_index); match_assert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0x770, NONE != local_player_index); match_assert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0x771, NULL != game_engine); if (game_engine && player) internal_rasterize_target_name(player_index); gamepad = input_get_gamepad_state(local_player_index); fade = game_engine_globals.hud_message_timers[local_player_index]; if ((!gamepad || !gamepad->buttons[_gamepad_binary_button_back]) && game_engine_globals.postgame_state != game_engine_mode_postgame_delay) { /* a frame is no longer a tick (render_interpolation.c): fade in half a second, not in 15 frames */ fade -= 0.06666667f * main_get_seconds_elapsed() * TICKS_PER_SECOND; } else { fade += 0.06666667f * main_get_seconds_elapsed() * TICKS_PER_SECOND; } fade = PIN(fade, 0.0f, 1.0f); if (fade > 0.0f) { real alpha = linear_to_non_linear_alpha(fade); game_engine_rasterize_in_game_score(player_index, alpha); } game_engine_globals.hud_message_timers[local_player_index] = fade; return; } long game_engine_player_get_team_index( long player_index) { long team_index = 1; match_assert("c:\\halo\\SOURCE\\game\\game_engine.c", 0xC11, game_engine); if (!game_engine->team_index_override) /* port: 0 or 1 (another machine's player has no local player: -1) */ team_index = PIN(player_get(player_index)->local_player_index % 2, 0, 1); return team_index; } void game_engine_update_player_always_invis( long player_index) { if (game_engine) { if ((TEST_FLAG(global_variant.universal_variant.flags, _game_variant_always_invisible_bit) || game_engine_test_trait(player_index, 1)) && player_get(player_index)->unit_index!=NONE) { player_handle_powerup_minor(player_index, 0, 15); } } return; } boolean game_engine_player_has_flag( long player_index) { boolean has_flag = FALSE; if (player_index!=NONE) { struct player_datum *player = player_get(player_index); if (player->unit_index!=NONE) { struct unit_datum *unit = unit_get(player->unit_index); long weapon_index; for (weapon_index = 0; weapon_indexunit.weapon_object_indices[weapon_index]; if (weapon_object_index!=NONE && weapon_is_flag(weapon_object_index)) { has_flag = TRUE; break; } } } } return has_flag; } void game_show_score( long player_index, long score) { game_show_score_extended(player_index, score, NONE); return; } void get_postgame_hilite_colors( real_argb_color *winner_color, real_argb_color *normal_color, real_argb_color *hilite_color) { winner_color->red = 0.45882353f; winner_color->green = 0.7294118f; winner_color->blue = 1.0f; winner_color->alpha = 1.0f; normal_color->red = 1.0f; normal_color->green = 1.0f; normal_color->blue = 0.0f; normal_color->alpha = 1.0f; get_selected_color(hilite_color); return; } boolean game_engine_running( void) { boolean running = game_engine!=NULL; return running; } /* port: whether the game is over and its scores are shown (where the host's button starts the next, game_engine_update) */ boolean game_engine_showing_postgame( void) { return game_engine != NULL && game_engine_globals.postgame_state == game_engine_mode_postgame_rasterize; } boolean game_engine_force_single_screen( void) { boolean force_single_screen = FALSE; if (game_engine && game_engine_globals.postgame_state>=game_engine_mode_postgame_rasterize_delay && game_engine_globals.postgame_state<=game_engine_mode_postgame_rasterize) { force_single_screen = TRUE; } return force_single_screen; } void game_engine_dispose( void) { if (game_engine) { if (game_engine->dispose) game_engine->dispose(); game_engine = NULL; } return; } static void game_engine_update_player_no_shield( long player_index) { struct player_datum *player; if (game_engine_has_shield(player_index)) return; player = player_get(player_index); if (player->unit_index != NONE) { struct unit_datum *unit = unit_get(player->unit_index); unit->object.shield_vitality = 0.0f; unit->object.maximum_shield_vitality = 0.0f; } return; } static void game_engine_build_lighting( void) { long player_count = 0; long object_count = 0; { struct data_iterator iterator; data_iterator_new(&iterator, player_data); while (data_iterator_next(&iterator)) player_count++; } if (global_variant.universal_variant.vehicle_set != _game_engine_vehicles_none) { if (global_variant.game_engine_index == game_engine_race) { struct scenario *scenario = global_scenario_get(); short flag_index; for (flag_index = 0; flag_index < scenario->netgame_flags.count; flag_index++) { struct scenario_netgame_flag *flag = TAG_BLOCK_GET_ELEMENT( &scenario->netgame_flags, flag_index, struct scenario_netgame_flag); if (flag->type == _netgame_flag_race_vehicle) object_count++; } if (object_count > player_count) object_count = player_count; } else { struct object_iterator object_iterator; object_iterator_new( &object_iterator, _object_mask_vehicle, 0); while (object_iterator_next(&object_iterator)) object_count++; } } if ((player_count > 8 && object_count >= 2) || player_count >= 13) { SET_FLAG(game_engine_globals.flags, _game_engine_disable_dynamic_light_bit, TRUE); } if (player_count > 4 || object_count >= 4 || TEST_FLAG(game_engine_globals.flags, _game_engine_disable_dynamic_light_bit)) { SET_FLAG(game_engine_globals.flags, _game_engine_disable_integrated_lights_bit, TRUE); } if (player_count >= 5) SET_FLAG(game_engine_globals.flags, _game_engine_5_or_more_players_bit, TRUE); if (player_count >= 9) SET_FLAG(game_engine_globals.flags, _game_engine_9_or_more_players_bit, TRUE); return; } void game_engine_dispose_from_old_map( void) { if (game_engine && game_engine->dispose_from_old_map) game_engine->dispose_from_old_map(); return; } void game_engine_game_ending( void) { if (game_engine && game_engine->game_ending) game_engine->game_ending(); return; } void game_engine_game_starting( void) { if (game_engine) { if (game_engine->game_starting) game_engine->game_starting(); game_engine_build_lighting(); } return; } void game_engine_statistics_append( long statistic) { if (game_engine && game_engine->statistics_append) game_engine->statistics_append(statistic); return; } void game_engine_handle_client_message( void *message) { if (game_engine && game_engine->handle_client_message) game_engine->handle_client_message(message); return; } void game_engine_handle_server_message( void *message) { if (game_engine && game_engine->handle_server_message) game_engine->handle_server_message(message); return; } void game_engine_post_rasterize_objects( void) { if (game_engine && game_engine->post_rasterize_objects) game_engine->post_rasterize_objects(); return; } void game_engine_post_rasterize( void) { if (game_engine) { switch (game_engine_globals.postgame_state) { case game_engine_mode_active: case game_engine_mode_postgame_delay: game_engine_post_rasterize_in_game(); break; case game_engine_mode_postgame_rasterize_delay: case game_engine_mode_postgame_rasterize: game_engine_post_rasterize_post_game(); break; default: match_assert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0x7B7, !"unreachable"); break; } } return; } void game_engine_nonplayer_post_rasterize( void) { if (game_engine) { switch (game_engine_globals.postgame_state) { case game_engine_mode_active: case game_engine_mode_postgame_delay: game_engine_press_start_to_begin(); break; case game_engine_mode_postgame_rasterize_delay: case game_engine_mode_postgame_rasterize: { rectangle2d window_bounds; long local_player_index; game_engine_post_rasterize_post_game(); window_bounds.x0 = 0; window_bounds.x1 = 640; window_bounds.y0 = 0; window_bounds.y1 = 480; for (local_player_index = 0; local_player_index < MAXIMUM_LOCAL_PLAYERS; local_player_index++) { render_ui_widgets_postgame( (short)local_player_index, &window_bounds); rumble_player_clear((short)local_player_index); } } break; default: match_assert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0xE3B, !"unreachable"); break; } } return; } static void game_engine_update_teleporter( long player_index) { static int blocked_message_delay = 0; static long fade_function = 0; static long screen_flash_type = 6; static real max_intensity = 1.0f; static real alpha = 0.5f; static real red = 0.35f; static real green = 1.0f; static real blue = 0.35f; static real duration = 1.0f; struct scenario *scenario = global_scenario_get(); struct player_datum *player = player_get(player_index); struct unit_datum *unit; struct scenario_netgame_flag *source_flag; struct scenario_netgame_flag *destination_flag; real_vector3d forward; long source_flag_index; long destination_flag_index; if (player->unit_index == NONE) return; unit = unit_get(player->unit_index); if (player->teleporter_index != NONE) { struct scenario_netgame_flag *previous_flag = TAG_BLOCK_GET_ELEMENT( &scenario->netgame_flags, player->teleporter_index, struct scenario_netgame_flag); if (distance_squared3d(&previous_flag->position, &unit->object.position) > 1.0f) player->teleporter_index = NONE; } source_flag_index = NONE; find_netgame_flags( &unit->object.position, 0.5f, 0.0f, _netgame_flag_teleporter_source, NONE, 1, &source_flag_index); if (source_flag_index == NONE || source_flag_index == player->teleporter_index) { return; } source_flag = TAG_BLOCK_GET_ELEMENT( &scenario->netgame_flags, source_flag_index, struct scenario_netgame_flag); destination_flag_index = NONE; find_netgame_flags( NULL, 0.0f, 0.0f, _netgame_flag_teleporter_target, source_flag->team_index, 1, &destination_flag_index); if (destination_flag_index != NONE) { destination_flag = TAG_BLOCK_GET_ELEMENT( &scenario->netgame_flags, destination_flag_index, struct scenario_netgame_flag); { struct collision_feature_list features; struct player_datum *unit_player; real_point3d position; real height; real radius; unit = unit_get(player->unit_index); forward = unit->object.forward; unit_player = player_get(player_index); biped_get_physics_pill( unit_player->unit_index, &position, &height, &radius); position = destination_flag->position; { struct collision_plane point_test_result; if (collision_get_features_in_sphere( 0x200380, &position, height + radius * 2.0f, height, radius, NONE, &features) && collision_features_test_point( &features, &position, &point_test_result)) { if (point_test_result.object_index != NONE) { if (TEST_FLAG( _object_mask_unit, object_get( point_test_result.object_index) ->object.type)) { struct unit_datum *blocking_unit = unit_get( point_test_result.object_index); if (blocking_unit->unit.player_index != NONE) { struct player_datum *blocking_player = player_get(blocking_unit->unit.player_index); blocking_player->is_blocking_teleporter = TRUE; blocking_player->telefrag_timeout++; } } } if (blocked_message_delay > 0) { blocked_message_delay--; return; } blocked_message_delay = 120; { long string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); wchar_t const *message; if (string_list_index != NONE) message = unicode_string_list_get_string( string_list_index, 0x65); else message = L""; hud_print_message( unit_get_local_player_index(unit_player->unit_index), message); } return; } } } if (player->local_player_index != NONE) { game_engine_play_multiplayer_sound(_multiplayer_sound_teleporter_activate); if (player->local_player_index != NONE) { struct screen_flash_definition screen_flash = { 0 }; screen_flash.fade_function = fade_function; screen_flash.type = screen_flash_type; screen_flash.duration = duration; screen_flash.priority = 2; screen_flash.max_intensity = max_intensity; screen_flash.zero_scale_factor = 0.0f; screen_flash.screen_flash_color.alpha = alpha; screen_flash.screen_flash_color.red = red; screen_flash.screen_flash_color.green = green; screen_flash.screen_flash_color.blue = blue; player_effect_screen_flash( player_index, &screen_flash, 1.0f); } } { real angle = arctangent(forward.j, forward.i) + destination_flag->facing - source_flag->facing; forward.i = cosine(angle); forward.j = sine(angle); normalize3d(&forward); object_set_position( player->unit_index, &destination_flag->position, &forward, NULL); if (player->local_player_index != NONE) player_control_set_facing(player->local_player_index, &forward); player->teleporter_index = find_netgame_flag( &unit->object.position, 1.0f, 0.0f, _netgame_flag_teleporter_source, NONE); } return; } console_printf( FALSE, "failed to teleport %d", source_flag->team_index); return; } void game_engine_update( void) { if (game_engine) { game_engine_update_multiplayer_sound(); game_engine_update_purge(); game_engine_update_weapons(); game_engine_update_item_spawn(); { struct data_iterator player_iterator; data_iterator_new(&player_iterator, player_data); while (data_iterator_next(&player_iterator)) { game_engine_update_player_no_shield(player_iterator.datum_index); game_engine_update_player_always_invis(player_iterator.datum_index); game_engine_update_teleporter(player_iterator.datum_index); /* (on a client of the distributed netcode each game type skips what the host decides, whose scores, flags, balls and hills it has: game_engine_read_network_state) */ if (game_engine->player_update_each_tick) game_engine->player_update_each_tick(player_iterator.datum_index); } } if (game_engine->update) game_engine->update(); switch (game_engine_globals.postgame_state) { case game_engine_mode_active: /* (a client of the distributed netcode ends the game when the host has) */ if ( !network_game_distributed_client() && game_engine_should_end_game()) { game_engine_end_game(); } break; case game_engine_mode_postgame_delay: if (game_engine_globals.postgame_timer <= 2.0f && !TEST_FLAG(game_engine_globals.flags, _game_engine_game_over_sound_disabled_bit)) { sound_class_set_gain("", 0.0f, 30); sound_class_set_gain("ambient_nature", 0.2f, 30); sound_class_set_gain("ambient_machinery", 0.2f, 30); sound_class_set_gain("ambient_computers", 0.2f, 30); SET_FLAG(game_engine_globals.flags, _game_engine_game_over_sound_disabled_bit, TRUE); } game_engine_globals.postgame_timer -= 1.0f / TICKS_PER_SECOND; if (game_engine_globals.postgame_timer <= 0.0f) { struct network_game_server *server; game_engine_globals.postgame_progress = 0.0f; game_engine_globals.postgame_state = game_engine_mode_postgame_rasterize_delay; game_engine_globals.postgame_timer = 5.0f; { struct data_iterator player_iterator; struct player_datum *player; data_iterator_new(&player_iterator, player_data); player = (struct player_datum *)data_iterator_next(&player_iterator); while (player) { if (player->unit_index != NONE) unit_kill(player->unit_index); if (player->local_player_index != NONE) rumble_player_clear(player->local_player_index); player = (struct player_datum *)data_iterator_next(&player_iterator); } } { struct object_iterator object_iterator; object_iterator_new( &object_iterator, _object_mask_vehicle, 0); while (object_iterator_next(&object_iterator)) { object_delete(object_iterator.index); } } server = global_network_game_server_get(); if (server) network_game_server_switch_to_postgame( global_network_game_server_get()); } break; case game_engine_mode_postgame_rasterize_delay: case game_engine_mode_postgame_rasterize: break; default: match_assert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0xA03, !"unreachable"); break; } } return; } enum game_engine_message { _game_engine_message_welcome = 0, _game_engine_message_killed_by_unknown, _game_engine_message_killed_by_biped, _game_engine_message_killed_by_vehicle, _game_engine_message_killed_by_player, _game_engine_message_killed_by_friendly_fire, _game_engine_message_killed_by_self, _game_engine_message_double_kill, _game_engine_message_killed_enemy, _game_engine_message_triple_kill, _game_engine_message_multi_kill, _game_engine_message_five_kills_in_row, _game_engine_message_ten_kills_in_a_row, _game_engine_message_killed_friendly, _game_engine_message_multi_kill_with_score, _game_engine_message_triple_kill_with_score, _game_engine_message_double_kill_with_score, _game_engine_message_ten_kills_in_a_row_with_score, _game_engine_message_five_kills_in_row_with_score, _game_engine_message_killed_enemy_with_score, _game_engine_message_winner, _game_engine_message_team_winner, _game_engine_message_show_score, _game_engine_message_odd_man_out, _game_engine_message_out_of_lives, _game_engine_message_respawn_timer, _game_engine_message_waiting_for_space_to_clear, _game_engine_message_player_quit_self, _game_engine_message_quit, _game_engine_message_press_back_for_score, _game_engine_message_time_left, NUMBER_OF_GAME_ENGINE_MESSAGES, }; enum { _equipment_created_at_rest_bit = 0, }; void game_engine_player_killed( long killing_player_index, long killing_object_index, long dead_player_index, boolean friendly_fire) { struct player_datum *dead_player = player_get(dead_player_index); boolean player_kill; boolean same_player; boolean valid_players; long message = NONE; match_assert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0xA31, dead_player_index != NONE); if (!game_engine) return; /* the distributed netcode: a client's copy of a death has the host's killer (port/linux/game/network_distributed.c) */ network_distributed_player_killed(&killing_player_index, &killing_object_index, dead_player_index, &friendly_fire); /* the host's kill of a player who quit, ahead of this client's clock (game_update_quit_players has not come to its time yet) */ if (network_game_distributed_client() && dead_player->quit_out_of_game_time != NONE && killing_player_index == dead_player_index) { dead_player->quit_out_of_game = TRUE; } dead_player->death_time = game_time_get(); if (game_engine->player_killed_player) { game_engine->player_killed_player( killing_player_index, killing_object_index, dead_player_index, friendly_fire); } same_player = killing_player_index == dead_player_index; if (killing_player_index != NONE && dead_player_index != NONE) valid_players = TRUE; else valid_players = FALSE; player_kill = !friendly_fire && valid_players && !same_player; dead_player->respawn_timer = dead_player->respawn_penalty + global_variant.universal_variant.respawn_time; if (global_variant.universal_variant.respawn_time_growth > 0) { dead_player->respawn_penalty += global_variant.universal_variant.respawn_time_growth; dead_player->respawn_penalty = MIN( dead_player->respawn_penalty, global_variant.universal_variant.respawn_time_growth * 5); if (player_kill && killing_player_index != NONE) { struct player_datum *killing_player = player_get(killing_player_index); killing_player->respawn_penalty -= global_variant.universal_variant.respawn_time_growth; killing_player->respawn_penalty = MAX(killing_player->respawn_penalty, 0); } } if (!player_kill) dead_player->respawn_timer += global_variant.universal_variant.suicide_penalty; dead_player->respawn_timer = MAX(dead_player->respawn_timer, 90); dead_player = player_get(dead_player_index); if (!dead_player->quit_out_of_game) { if (killing_player_index == NONE) { if (killing_object_index == NONE) message = _game_engine_message_killed_by_unknown; else { struct object_datum *object = object_get(killing_object_index); unit_try_and_get(killing_object_index); switch (object->object.type) { case _object_type_biped: message = _game_engine_message_killed_by_biped; break; case _object_type_vehicle: message = _game_engine_message_killed_by_vehicle; break; default: message = _game_engine_message_killed_by_unknown; break; } } } else if (killing_player_index == dead_player_index) message = _game_engine_message_killed_by_self; else message = _game_engine_message_killed_by_player + (friendly_fire != FALSE); game_show_score_extended( dead_player_index, message, killing_player_index); } else { struct data_iterator iterator; data_iterator_new(&iterator, player_data); while (data_iterator_next(&iterator)) { multiplayer_message( iterator.datum_index, _game_engine_message_quit, dead_player_index); } } if (message == _game_engine_message_killed_by_friendly_fire) { if (killing_player_index != NONE) { multiplayer_message( killing_player_index, _game_engine_message_killed_friendly, dead_player_index); } else { struct data_iterator iterator; data_iterator_new(&iterator, player_data); while (data_iterator_next(&iterator)) { multiplayer_message( iterator.datum_index, _game_engine_message_killed_friendly, dead_player_index); } } } else if (message == _game_engine_message_killed_by_player) { struct player_datum *killing_player = player_get(killing_player_index); if (killing_player->statistics.multiple_kills >= 4) { message = _game_engine_message_multi_kill; } else if (killing_player->statistics.multiple_kills == 3) message = _game_engine_message_triple_kill; else if (killing_player->statistics.multiple_kills == 2) message = _game_engine_message_double_kill; else if (killing_player->statistics.kills_in_a_row == 5) message = _game_engine_message_five_kills_in_row; else { message = (killing_player->statistics.kills_in_a_row % 5) ? _game_engine_message_killed_enemy : _game_engine_message_ten_kills_in_a_row; } game_show_score_extended( killing_player_index, message, dead_player_index); } return; } void game_engine_update_non_deterministic( real delta_seconds) { if (game_engine) { switch (game_engine_globals.postgame_state) { case game_engine_mode_postgame_rasterize_delay: rumble_clear_all_now(); game_engine_globals.postgame_timer -= delta_seconds; if (game_engine_globals.postgame_timer <= 0.0f) game_engine_globals.postgame_state = game_engine_mode_postgame_rasterize; break; case game_engine_mode_postgame_rasterize: rumble_clear_all_now(); game_engine_globals.postgame_progress += delta_seconds; if (game_engine_globals.postgame_progress > 1.0f) game_engine_globals.postgame_progress = 1.0f; if (test_any_gamepad_button(0) || test_any_gamepad_button(12)) { if (global_network_game_server_get()) network_game_server_reset_to_pregame( global_network_game_server_get()); } else if (test_any_gamepad_button(1) || test_any_gamepad_button(13)) { network_game_abort(); } break; } } return; } boolean game_engine_display_team_indicators( void) { boolean display = FALSE; if (game_engine) display = TEST_FLAG(global_variant.universal_variant.flags, _game_variant_allow_friendly_navpoints_bit) && global_variant.universal_variant.teams; return display; } boolean game_engine_can_score( void) { boolean can_score = TRUE; if (game_engine) can_score = game_engine_globals.postgame_state==game_engine_mode_active; return can_score; } boolean game_engine_infinite_grenades( long player_index) { boolean infinite_grenades = FALSE; if (game_engine_running() && player_index!=NONE) infinite_grenades = game_engine_infinite_grenades_internal(); return infinite_grenades; } boolean game_engine_has_shield( long player_index) { boolean has_shield = TRUE; if (game_engine && player_index!=NONE) has_shield = !TEST_FLAG(global_variant.universal_variant.flags, _game_variant_no_shields_bit); return has_shield; } boolean game_engine_draw_object_in_motion_sensor( long unit_index) { boolean draw_object = TRUE; if (game_engine) draw_object = TEST_FLAG(global_variant.universal_variant.flags, _game_variant_draw_object_in_motion_sensor_bit); return draw_object; } boolean game_engine_hud_draw_motion_sensor( long player_index) { boolean draw_motion_sensor = TRUE; if (game_engine) { boolean default_draw_motion_sensor; draw_motion_sensor = TEST_FLAG(global_variant.universal_variant.flags, _game_variant_draw_object_in_motion_sensor_bit); default_draw_motion_sensor = global_variant.universal_variant.goal_radar == _radar_motion_tracker; if (global_variant.game_engine_index == game_engine_slayer && !global_variant.game_engine_variant.slayer.kill_in_order) default_draw_motion_sensor = FALSE; draw_motion_sensor |= default_draw_motion_sensor; } return draw_motion_sensor; } boolean game_engine_player_has_stealth_weapon( long player_index) { boolean has_stealth_weapon = FALSE; if (player_index != NONE) { struct player_datum *player = player_get(player_index); if (player->unit_index != NONE) { struct unit_datum *unit = unit_get(player->unit_index); long current_weapon_index = unit->unit.current_weapon_index; if (current_weapon_index != NONE) { long weapon_index = unit->unit.weapon_object_indices[current_weapon_index]; if (weapon_index != NONE) { struct weapon_datum *weapon = weapon_get(weapon_index); if (weapon->definition_index != NONE) { struct weapon_definition *weapon_definition = weapon_definition_get(weapon->definition_index); has_stealth_weapon = TEST_FLAG( weapon_definition->weapon.flags, _weapon_does_not_depower_active_camo_bit); } } } } } return has_stealth_weapon; } void game_engine_weapon_fired( long player_index) { if (game_engine && player_index != NONE) { struct player_datum *player = player_get(player_index); long unit_index = player->unit_index; if (unit_index != NONE) { struct unit_datum *unit = unit_get(unit_index); long weapon_index = unit_inventory_get_weapon( unit_index, unit_get(unit_index)->unit.current_weapon_index); real active_camouflage_decrease = 0.1f; if (game_engine_player_has_stealth_weapon(player_index)) { active_camouflage_decrease = 0.0f; } else if (weapon_index != NONE) { struct weapon_datum *weapon = weapon_get(weapon_index); struct weapon_definition *weapon_definition = weapon_definition_get(weapon->definition_index); if (0.0f != weapon_definition->weapon.active_camo_ding) active_camouflage_decrease = weapon_definition->weapon.active_camo_ding; } if (unit->unit.active_camouflage >= 0.05f) { unit->unit.active_camouflage -= active_camouflage_decrease; unit->unit.cause_for_camo_regrowth = 1; unit->unit.active_camouflage = MAX(0.05f, unit->unit.active_camouflage); } } } return; } wchar_t *get_place_name( struct game_engine_place place) { long lookup_index; long string_list_index; /* port: a session can have more places than the string list names */ match_vassert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0x1316, place.place < MULTIPLAYER_MAXIMUM_PLAYERS, "place.place < maximum_places"); if (TEST_FLAG(place.flags, _place_two_groups) && TEST_FLAG(place.flags, _place_tied)) lookup_index = 35; else if (TEST_FLAG(place.flags, _place_two_groups) && place.place == 0) lookup_index = 33; else if (TEST_FLAG(place.flags, _place_two_groups) && place.place == 1) lookup_index = 34; else if (TEST_FLAG(place.flags, _place_all_tied)) lookup_index = 32; /* port: places past the 16th are spelled out; a tie is marked like the string list's tied places */ else if (place.place >= NUMBER_OF_PLACE_STRINGS) return place_ordinal_string(place.place, TEST_FLAG(place.flags, _place_tied)); else { lookup_index = place.place; if (TEST_FLAG(place.flags, _place_tied)) lookup_index += 16; } match_vassert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0x1331, lookup_index != NONE, "NONE != lookup_index"); string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) return unicode_string_list_get_string( string_list_index, lookup_index + 0x66); return L""; } long find_netgame_flags( real_point3d const *position, real radius, real height, short type, short team_index, long maximum_count, long *flag_indices) { real radius_squared = radius * radius; long found_count = 0; short flag_index; struct scenario *scenario; scenario = global_scenario_get(); for (flag_index = 0; flag_index < scenario->netgame_flags.count; flag_index++) { struct scenario_netgame_flag *flag = TAG_BLOCK_GET_ELEMENT( &scenario->netgame_flags, flag_index, struct scenario_netgame_flag); if (type != NONE && type != flag->type) continue; if (team_index != NONE && team_index != flag->team_index) continue; if (position) { if (radius >= 0.0f && distance_squared3d(&flag->position, position) > radius_squared) { continue; } if (height > 0.0f && fabs(flag->position.z - position->z) > height) { continue; } } if (found_count < maximum_count) flag_indices[found_count++] = flag_index; } return found_count; } long find_netgame_flag( real_point3d const *position, real radius, real height, short type, short team_index) { long flag_index = NONE; find_netgame_flags( position, radius, height, type, team_index, 1, &flag_index); return flag_index; } /* playlist data: in the Aug-15-2001 build the playlist functions were the only users of these two globals; this build no longer reads them. their definition point here, with the playlist code, is inferred (the Aug-2001 users and the later builds' layout), not attested */ void *global_autogenerate_list = NULL; long global_autogenerate_count = 0; void game_engine_playlist_next( long parameter0, long parameter1, long playlist_type) { char *map_name; struct game_variant variant; csstrcpy(global_stage.map_name, "levels\\test\\carousel\\carousel"); map_name = main_get_multiplayer_map_name(); if (map_name && map_name[0]) { csstrncpy(global_stage.map_name, map_name, sizeof(global_stage.map_name) - 1); global_stage.map_name[sizeof(global_stage.map_name) - 1] = 0; } if (player_ui_game_variant_specified(&variant)) csmemcpy(&global_stage.variant, &variant, sizeof(global_stage.variant)); return; } boolean game_engine_should_end_game( void) { boolean should_end_game = FALSE; if (game_engine && !multiple_teams_alive()) should_end_game = TRUE; return should_end_game; } void game_engine_clear_goal_position( short goal_index) { /* port: a goal of the table only */ if (!VALID_INDEX(goal_index, NUMBEROF(global_goal))) return; csmemset(&global_goal[goal_index], 0, sizeof(struct netgame_goal)); return; } long get_flag_definition_index( void) { struct game_globals *game_globals; struct game_globals_multiplayer_information *multiplayer_information; global_scenario_get(); game_globals = scenario_get_game_globals(); multiplayer_information = TAG_BLOCK_GET_ELEMENT( &game_globals->multiplayer_information, 0, struct game_globals_multiplayer_information); return multiplayer_information->flag.index; } long get_ball_definition_index( void) { struct game_globals *game_globals; struct game_globals_multiplayer_information *multiplayer_information; global_scenario_get(); game_globals = scenario_get_game_globals(); multiplayer_information = TAG_BLOCK_GET_ELEMENT( &game_globals->multiplayer_information, 0, struct game_globals_multiplayer_information); return multiplayer_information->ball.index; } void game_engine_override_map_name( char const *map_name) { if (map_name && map_name[0]) csstrncpy(global_stage.map_name, map_name, sizeof(global_stage.map_name)-1); return; } void game_engine_override_game_variant( struct game_variant const *variant) { if (variant) csmemcpy(&global_stage.variant, variant, sizeof(global_stage.variant)); return; } void game_engine_switch_to_postgame( void) { if (game_engine_globals.postgame_state==game_engine_mode_active) { if (global_network_game_server_get()) { game_engine_globals.postgame_state = game_engine_mode_postgame_delay; game_engine_globals.postgame_timer = 7.0f; } else { game_engine_globals.postgame_state = game_engine_mode_postgame_rasterize; } } return; } void game_engine_load_stage( char const *map_name) { if (!map_name || csstrcmp(global_stage.map_name, map_name)) main_set_multiplayer_map_name(global_stage.map_name); game_set_game_variant(&global_stage.variant); if (!network_game_is_active()) main_reset_map(); return; } void game_engine_end_game( void) { if (game_engine_globals.postgame_state==game_engine_mode_active) { game_engine_globals.postgame_state = game_engine_mode_postgame_delay; game_engine_globals.postgame_timer = 7.0f; game_engine_play_multiplayer_sound(_multiplayer_sound_game_over); ui_widgets_close_all(); } return; } void game_engine_player_damaged_player( long damaging_player_index, long dead_player_index, boolean damage_type) { match_assert("c:\\halo\\SOURCE\\game\\game_engine.c", 0xA20, dead_player_index != NONE); /* (a client of the distributed netcode replaying the host's damage has the host's game type state, game_engine_read_network_state) */ if (network_game_distributed_client()) return; if (game_engine && game_engine->player_damaged_player) game_engine->player_damaged_player(damaging_player_index, dead_player_index, damage_type); return; } static boolean game_engine_player_is_odd_man_out( long player_index) { struct player_datum *player = player_get(player_index); boolean result = FALSE; if (global_variant.universal_variant.odd_man_out && player->unit_index==NONE) { struct data_iterator iterator; struct player_datum *other_player; result = TRUE; data_iterator_new(&iterator, player_data); other_player = (struct player_datum *)data_iterator_next(&iterator); while (other_player) { if (other_player->unit_index==NONE && other_player!=player && (other_player->death_time>player->death_time || (other_player->death_time==player->death_time && DATUM_INDEX_TO_ABSOLUTE_INDEX(player_index)< DATUM_INDEX_TO_ABSOLUTE_INDEX(iterator.datum_index)))) { result = FALSE; } other_player = (struct player_datum *)data_iterator_next(&iterator); } } return result; } boolean game_engine_player_is_out_of_lives( long player_index) { boolean out_of_lives = FALSE; if (global_variant.universal_variant.lives>0) { struct player_datum *player = player_get(player_index); if (player->unit_index==NONE && player->statistics.deaths>=global_variant.universal_variant.lives) out_of_lives = TRUE; } return out_of_lives; } short game_engine_player_get_custom_motion_sensor_positions( long player_index, real_point2d *positions, byte *goal_indices, short maximum_count) { short count = 0; if (game_engine && global_variant.universal_variant.goal_radar == _radar_motion_tracker && player_index != NONE) { struct player_datum *player = player_get(player_index); long goal_index; /* port: every goal, one per player in the native builds' sessions */ for (goal_index = 0; goal_index < (long)NUMBEROF(global_goal); goal_index++) { struct netgame_goal *goal = &global_goal[goal_index]; if (goal_matches_player(player, player_index, goal_index) && count < maximum_count) { goal_indices[count] = goal_index; positions[count].x = goal->position.x; positions[count].y = goal->position.y; count++; } } } return count; } void game_engine_render_nav_points( short local_player_index) { if (game_engine && global_variant.universal_variant.goal_radar == _radar_nav_point && local_player_index != NONE) { long player_index = local_player_get_player_index(local_player_index); if (player_index != NONE) { struct player_datum *player = player_get(player_index); long goal_index; if (player->unit_index != NONE) { real_point3d head_position; unit_get_head_position(player->unit_index, &head_position); /* port: every goal, one per player in the native builds' sessions */ for (goal_index = 0; goal_index < (long)NUMBEROF(global_goal); goal_index++) { if (goal_matches_player(player, player_index, goal_index)) { short render_type = hud_get_nav_point_render_type( local_player_index, &head_position, &global_goal[goal_index].position, NONE); custom_render_nav_point( local_player_index, &global_goal[goal_index].position, global_goal[goal_index].nav_index, render_type); } } } } } return; } boolean game_engine_hud_draw_messages( long player_index) { boolean draw_messages = TRUE; if (game_engine && player_index!=NONE) { struct player_datum *player = player_get(player_index); if (player->local_player_index!=NONE && game_engine_globals.hud_message_timers[player->local_player_index]>0.0f) { draw_messages = FALSE; } } return draw_messages; } boolean game_engine_force_autopickup( long unit_index, long weapon_index) { boolean force_autopickup = FALSE; if (game_engine && global_variant.game_engine_index==game_engine_ctf && weapon_is_flag(weapon_index)) force_autopickup = TRUE; return force_autopickup; } boolean game_engine_allow_pick_up( long unit_index, long weapon_index) { boolean allow_pick_up = TRUE; if (game_engine && game_engine->allow_pick_up) allow_pick_up = game_engine->allow_pick_up(unit_index, weapon_index); return allow_pick_up; } boolean game_engine_picking_up( long unit_index, long weapon_index) { boolean allow_pick_up = TRUE; if (game_engine) { struct weapon_datum *weapon = weapon_try_and_get(weapon_index); if (weapon && weapon_is_flag(weapon_index)) { if (TEST_FLAG(weapon->weapon.flags, _weapon_runtime_game_engine_active_bit)) { SET_FLAG(weapon->weapon.flags, _weapon_runtime_game_engine_active_bit, FALSE); if (game_engine->weapon_dropped) game_engine->weapon_dropped(weapon_index); } SET_FLAG(weapon->weapon.flags, _weapon_runtime_game_engine_active_bit, TRUE); if (game_engine->picking_up) { long player_index = player_index_from_unit_index(unit_index); allow_pick_up = game_engine->picking_up( weapon_index, player_index); } match_assert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0xF58, !allow_pick_up || !TEST_FLAG(weapon->weapon.flags, _weapon_must_be_readied_bit) || !unit_has_weapon_with_flag(unit_index, _weapon_must_be_readied_bit)); } } return allow_pick_up; } boolean game_engine_test_flag( long flag) { boolean result = FALSE; if (game_engine && game_engine->test_flag) result = game_engine->test_flag(flag); return result; } boolean game_engine_test_trait( long trait, long value) { boolean result = FALSE; if (game_engine && game_engine->test_trait) result = game_engine->test_trait(trait, value); return result; } void game_engine_prespawn_player_update( long player_index) { if (game_engine) { if (game_engine->prespawn_player_update) game_engine->prespawn_player_update(player_index); else { struct player_datum *player = player_get(player_index); /* port: 0 or 1 (another machine's player has no local player: -1) */ player->team_index = PIN(player->local_player_index % 2, 0, 1); } } return; } static struct statistic_buffer game_engine_get_player_place( long player_index) { struct statistic_buffer statistic_buffer[MULTIPLAYER_MAXIMUM_PLAYERS]; long place = 0; populate_statistic_buffer(statistic_buffer, _postgame_statistic_ranking, FALSE); while (TRUE) { if (statistic_buffer[place].player_index == player_index) break; place++; match_assert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0x362, placedid_player_win) result = game_engine->did_player_win(player_index); else result = game_engine_did_player_win_default(player_index); } return result; } boolean multiple_teams_alive( void) { boolean result = FALSE; if (players_in_game() <= 1) return TRUE; else { struct data_iterator iterator; struct player_datum *player; long team_index = NONE; data_iterator_new(&iterator, player_data); player = (struct player_datum *)data_iterator_next(&iterator); while (player) { if (!player->quit_out_of_game && (player->unit_index != NONE || (!game_engine_player_is_odd_man_out(iterator.datum_index) && !game_engine_player_is_out_of_lives( iterator.datum_index)))) { match_assert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0x1BE, player->team_index != NONE); if (player->team_index != team_index) { if (team_index != NONE) { result = TRUE; break; } team_index = player->team_index; } } player = (struct player_datum *)data_iterator_next(&iterator); } } return result; } boolean team_has_players( long team_index) { boolean result = FALSE; if (players_in_game() <= 1) { result = TRUE; goto done; } else { struct data_iterator iterator; struct player_datum *player; data_iterator_new(&iterator, player_data); player = (struct player_datum *)data_iterator_next(&iterator); while (player) { if (!player->quit_out_of_game && (player->unit_index != NONE || (!game_engine_player_is_odd_man_out(iterator.datum_index) && !game_engine_player_is_out_of_lives( iterator.datum_index)))) { match_assert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0x1F7, player->team_index != NONE); /* Preserve the retail first-eligible-player behavior. */ if (player->team_index == team_index) result = TRUE; break; } player = (struct player_datum *)data_iterator_next(&iterator); } } done: return result; } long game_engine_did_player_win_default( long player_index) { long result = 0; if (global_variant.universal_variant.teams) { long team0_score = game_engine_get_team_score(0); long team1_score = game_engine_get_team_score(1); long winning_team_index; struct player_datum *player = player_get(player_index); if (multiple_teams_alive()) winning_team_index = team0_score == team1_score ? NONE : (team0_score > team1_score ? 0 : 1); else winning_team_index = team_has_players(0) ? 0 : 1; if (winning_team_index == NONE) result = NONE; else if (player->team_index == winning_team_index) result = TRUE; else result = FALSE; } else { struct statistic_buffer entry; entry = game_engine_get_player_place(player_index); if (is_place_tied(&entry) && !place_get_position(&entry)) result = NONE; else if (!place_get_position(&entry)) result = TRUE; else result = FALSE; } return result; } static long game_engine_get_type( void) { long game_engine_type = NONE; if (game_engine) game_engine_type = game_engine->type; return game_engine_type; } struct game_variant *game_engine_get_variant( void) { return &global_variant; } struct game_variant *game_engine_get_variant_by_name( struct game_variant *variant, char const *name) { struct game_variant temporary; struct game_variant result; csmemset(&result, 0, sizeof(result)); if (csstrcmp(name, "race") == 0) result = *build_game_variant_race(&temporary); else if (csstrcmp(name, "team_race") == 0) result = *build_game_variant_team_race(&temporary); else if (csstrcmp(name, "rally") == 0) result = *build_game_variant_rally(&temporary); else if (csstrcmp(name, "slayer") == 0) result = *build_game_variant_slayer(&temporary); else if (csstrcmp(name, "team_slayer") == 0) result = *build_game_variant_team_slayer(&temporary); else if (csstrcmp(name, "elimination") == 0) result = *build_game_variant_elimination(&temporary); else if (csstrcmp(name, "stalker") == 0) result = *build_game_variant_stalker(&temporary); else if (csstrcmp(name, "team_oddball") == 0) result = *build_game_variant_team_oddball(&temporary); else if (csstrcmp(name, "accumulation") == 0) result = *build_game_variant_accumulation(&temporary); else if (csstrcmp(name, "oddball") == 0) result = *build_game_variant_oddball(&temporary); else if (csstrcmp(name, "ctf") == 0) result = *build_game_variant_ctf(&temporary); else if (csstrcmp(name, "ironctf") == 0) result = *build_game_variant_iron_ctf(&temporary); else if (csstrcmp(name, "king") == 0) result = *build_game_variant_king(&temporary); else if (csstrcmp(name, "team_king") == 0) result = *build_game_variant_team_king(&temporary); *variant = result; return variant; } boolean game_engine_get_goal_in_use( short goal_index) { return global_goal[goal_index].in_use; } real_point3d *game_engine_get_goal_position( real_point3d *position, short index) { match_assert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0xFE2, global_goal[index].in_use); *position = global_goal[index].position; return position; } void game_engine_set_goal_position( short goal_index, real_point3d const *position, real height, char const *name, long player_index, short team_index, long ignore_player_index) { /* port: a goal of the table only */ if (!VALID_INDEX(goal_index, NUMBEROF(global_goal))) return; global_goal[goal_index].ignore_player_index = ignore_player_index; global_goal[goal_index].nav_index = find_nav_point(name); global_goal[goal_index].in_use = TRUE; global_goal[goal_index].position = *position; global_goal[goal_index].team_index = team_index; global_goal[goal_index].position.z += height + 0.63f; global_goal[goal_index].player_index = player_index; return; } boolean game_engine_man_out( long player_index) { struct player_datum *player = player_get(player_index); boolean man_out = player->quit_out_of_game || game_engine_player_is_out_of_lives(player_index) || game_engine_player_is_odd_man_out(player_index); return man_out; } real_rgb_color *game_engine_player_get_change_color( real_rgb_color *change_color, long player_index) { struct player_datum *player = player_get(player_index); real_rgb_color result; if (global_variant.universal_variant.teams) { if (player->team_index == 0) result = *global_real_rgb_red; else result = *global_real_rgb_blue; } else { long color_index = player->network_player_data.primary_color_index; if (debug_player_color != NONE) color_index = debug_player_color; result = player_profile_get_rgb_color(color_index); } *change_color = result; return change_color; } boolean game_engine_has_teams( void) { boolean has_teams = FALSE; if (game_engine) has_teams = global_variant.universal_variant.teams; return has_teams; } boolean game_engine_allow_pause( void) { return game_engine_globals.postgame_state==game_engine_mode_active; } boolean game_engine_allow_dynamic_lighting( void) { boolean allow_dynamic_lighting = TRUE; if (game_engine) allow_dynamic_lighting = !TEST_FLAG(game_engine_globals.flags, _game_engine_disable_dynamic_light_bit); return allow_dynamic_lighting; } boolean game_engine_allow_integrated_lights( long object_index) { boolean allow_integrated_lights = TRUE; if (game_engine) allow_integrated_lights = !TEST_FLAG(game_engine_globals.flags, _game_engine_disable_integrated_lights_bit); return allow_integrated_lights; } void game_engine_variant_cleanup( struct game_variant *variant) { struct game_variant original = *variant; variant->human_readable_game_description[NUMBEROF(variant->human_readable_game_description) - 1] = 0; variant->game_engine_index = PIN(variant->game_engine_index, 1, 5); variant->universal_variant.teams = !!variant->universal_variant.teams; variant->universal_variant.odd_man_out = !!variant->universal_variant.odd_man_out; variant->universal_variant.respawn_time_growth = MAX(variant->universal_variant.respawn_time_growth, 0); variant->universal_variant.respawn_time = MAX(variant->universal_variant.respawn_time, 0); variant->universal_variant.suicide_penalty = MAX(variant->universal_variant.suicide_penalty, 0); variant->universal_variant.lives = MAX(variant->universal_variant.lives, 0); variant->universal_variant.health = PIN(variant->universal_variant.health, 0.25f, 4.0f); variant->universal_variant.weapon_set = PIN(variant->universal_variant.weapon_set, 0, NUMBER_OF_GAME_ENGINE_WEAPON_SETS - 1); variant->universal_variant.vehicle_set = PIN(variant->universal_variant.vehicle_set, 0, NUMBER_OF_GAME_ENGINE_VEHICLE_SETS - 1); switch (variant->game_engine_index) { case game_engine_ctf: variant->game_engine_variant.ctf.assault = !!variant->game_engine_variant.ctf.assault; variant->game_engine_variant.ctf.reset_on_capture = !!variant->game_engine_variant.ctf.reset_on_capture; variant->game_engine_variant.ctf.flag_must_reset = !!variant->game_engine_variant.ctf.flag_must_reset; variant->universal_variant.teams = TRUE; variant->game_engine_variant.ctf.flag_at_home_to_score = !!variant->game_engine_variant.ctf.flag_at_home_to_score; variant->game_engine_variant.ctf.single_flag_time = FLOOR(variant->game_engine_variant.ctf.single_flag_time, 0); break; case game_engine_slayer: variant->game_engine_variant.slayer.no_death_bonus = !!variant->game_engine_variant.slayer.no_death_bonus; variant->game_engine_variant.slayer.no_kill_penalty = !!variant->game_engine_variant.slayer.no_kill_penalty; variant->game_engine_variant.slayer.kill_in_order = !!variant->game_engine_variant.slayer.kill_in_order; break; case game_engine_oddball: /* port: the balls' arrays and goals hold no more */ variant->game_engine_variant.oddball.ball_spawn_count = PIN(variant->game_engine_variant.oddball.ball_spawn_count, 0, MAXIMUM_ODDBALLS); break; } if (csmemcmp(&original, variant, sizeof(original)) != 0) { struct game_variant cleaned; csmemcpy(&cleaned, &original, sizeof(cleaned)); csmemcpy(&cleaned, variant, sizeof(cleaned)); error( _error_silent, "NETGAME CODE FAILURE: game_engine_variant_cleanup changed the variant"); } return; } static void game_engine_predict_resources( void) { struct game_globals *game_globals; struct game_globals_multiplayer_information *multiplayer_information; struct game_globals_vehicle *vehicle; long weapon_indices[10]; long weapon_index; game_globals = scenario_get_game_globals(); multiplayer_information = TAG_BLOCK_GET_ELEMENT( &game_globals->multiplayer_information, 0, struct game_globals_multiplayer_information); switch (global_variant.universal_variant.vehicle_set) { case _game_engine_vehicles_warthog: vehicle = TAG_BLOCK_GET_ELEMENT( &multiplayer_information->vehicles, 0, struct game_globals_vehicle); object_definition_predict(vehicle->vehicle.index); break; case _game_engine_vehicles_ghost: vehicle = TAG_BLOCK_GET_ELEMENT( &multiplayer_information->vehicles, 1, struct game_globals_vehicle); object_definition_predict(vehicle->vehicle.index); break; case _game_engine_vehicles_tank: vehicle = TAG_BLOCK_GET_ELEMENT( &multiplayer_information->vehicles, 2, struct game_globals_vehicle); object_definition_predict(vehicle->vehicle.index); break; default: { struct tag_block *vehicles = &multiplayer_information->vehicles; vehicle = TAG_BLOCK_GET_ELEMENT( vehicles, 0, struct game_globals_vehicle); object_definition_predict(vehicle->vehicle.index); vehicle = TAG_BLOCK_GET_ELEMENT( vehicles, 1, struct game_globals_vehicle); object_definition_predict(vehicle->vehicle.index); vehicle = TAG_BLOCK_GET_ELEMENT( vehicles, 2, struct game_globals_vehicle); object_definition_predict(vehicle->vehicle.index); break; } } object_definition_predict(list_index_to_weapon_definition_index(_weapon_list_frag_grenade)); object_definition_predict(list_index_to_weapon_definition_index(_weapon_list_plasma_grenade)); if (global_variant.game_engine_index == game_engine_oddball) object_definition_predict(list_index_to_weapon_definition_index(_weapon_list_ball)); if (global_variant.game_engine_index == game_engine_ctf) object_definition_predict(list_index_to_weapon_definition_index(_weapon_list_flag)); weapon_indices[0] = list_index_to_weapon_definition_index(0); weapon_indices[1] = list_index_to_weapon_definition_index(1); weapon_indices[2] = list_index_to_weapon_definition_index(2); weapon_indices[3] = list_index_to_weapon_definition_index(3); weapon_indices[4] = list_index_to_weapon_definition_index(4); weapon_indices[5] = list_index_to_weapon_definition_index(5); weapon_indices[6] = list_index_to_weapon_definition_index(6); weapon_indices[7] = list_index_to_weapon_definition_index(7); weapon_indices[8] = list_index_to_weapon_definition_index(8); weapon_indices[9] = list_index_to_weapon_definition_index(9); for (weapon_index = 0; weapon_index < 10; weapon_index++) { object_definition_predict( game_engine_remap_weapon(weapon_indices[weapon_index])); } return; } void game_engine_initialize( struct game_variant *variant) { csmemset(&game_engine_globals, 0, sizeof(game_engine_globals)); game_engine_globals.postgame_state = game_engine_mode_active; if (variant && variant->game_engine_index) { global_variant = *variant; game_engine_variant_cleanup(&global_variant); /* port: the cleaned variant's (the one given may be any number) */ game_engine = game_engines[global_variant.game_engine_index]; } return; } void game_engine_initialize_for_new_map( void) { if (game_engine) { game_engine_verify_current_map(); game_engine_intialize_queued_sounds(); csmemset(global_goal, 0, sizeof(global_goal)); game_engine_globals.next_team_index = 0; timeout_for_endgame_sound = 0; game_engine_network_state_read = FALSE; if (game_engine->initialize_for_new_map && !game_engine->initialize_for_new_map()) { error( _error_silent, "failed to initialize custome game engine for new map, reverting to default game engine"); if (game_engine) { if (game_engine->dispose) game_engine->dispose(); game_engine = NULL; } } game_engine_predict_resources(); } return; } void game_engine_player_added( long player_index) { initialize_player_multiplayer_data(player_index); if (game_engine) { struct player_datum *player = player_get(player_index); long *next_team_index = &game_engine_globals.next_team_index; if (global_variant.universal_variant.teams) { if (global_network_game_client_get()) { player->team_index = PIN((signed char)player->network_player_data.team_index % 2, 0, 1); } else { player->network_player_data.team_index = (char)*next_team_index; player->team_index = (signed char)*next_team_index; *next_team_index = (*next_team_index + 1) % 2; } } else { /* port: a free-for-all team is the player's own slot, which stays below the player limit and is the same on every machine */ long team_index = DATUM_INDEX_TO_ABSOLUTE_INDEX(player_index); player->network_player_data.team_index = (char)team_index; player->team_index = (signed char)team_index; } if (player_index != NONE) { multiplayer_message(player_index, 0, NONE); } else { struct data_iterator iterator; data_iterator_new(&iterator, player_data); while (data_iterator_next(&iterator)) multiplayer_message(iterator.datum_index, 0, NONE); } if (game_engine->player_added) game_engine->player_added(player_index); } return; } real game_engine_get_distance_rating_for_spawn( long player_index, real_point3d const *position) { boolean has_teams = game_engine ? global_variant.universal_variant.teams : FALSE; struct player_datum *player; struct data_iterator iterator; struct player_datum *other_player; real rating; player = player_get(player_index); rating = 1.0f; data_iterator_new(&iterator, player_data); other_player = (struct player_datum *)data_iterator_next(&iterator); while (other_player) { if (other_player->unit_index!=NONE) { real_point3d origin; real distance; object_get_origin(other_player->unit_index, &origin); distance = distance3d(&origin, position); if (!has_teams || other_player->team_index!=player->team_index || !(distance>0.25f)) { if (distance<0.25f) rating = 0.0f; else if (distance<1.0f) rating *= 0.1f; if (other_player->team_index!=player->team_index) { if (distance<2.0f) rating = 0.0f; else if (!(distance>5.0f)) rating = (distance-2.0f)*rating*0.33333334f; } } } other_player = (struct player_datum *)data_iterator_next(&iterator); } return rating; } real game_engine_get_starting_location_rating( long player_index, struct player_starting_location const *starting_location) { if (!match_game_type(game_engine_get_type(), 4, starting_location->game_types)) return 0.0f; if (nearby_vehicle(player_index, starting_location)) return 0.0f; return default_starting_location_rate_function(player_index, starting_location); } real game_engine_get_damage_multiplier( long damaging_player_index, long damaged_player_index) { real result = 1.0f; if (game_engine) result /= PIN(global_variant.universal_variant.health, 0.25f, 4.0f); if (damaging_player_index != NONE && damaged_player_index != NONE) { if (game_engine_test_trait(damaging_player_index, 2)) { result *= 1.5f; } if (game_engine_test_trait(damaged_player_index, 3)) { result *= 0.5f; } } return result; } long game_engine_remap_vehicle( long vehicle_definition_index) { long result = vehicle_definition_index; if (game_engine) { struct game_globals *game_globals; struct game_globals_multiplayer_information *multiplayer_information; struct tag_block *vehicles; struct game_globals_vehicle *vehicle0; struct game_globals_vehicle *vehicle1; struct game_globals_vehicle *vehicle2; struct game_globals_vehicle *vehicle; game_globals = scenario_get_game_globals(); multiplayer_information = TAG_BLOCK_GET_ELEMENT( &game_globals->multiplayer_information, 0, struct game_globals_multiplayer_information); vehicle0 = TAG_BLOCK_GET_ELEMENT( &multiplayer_information->vehicles, 0, struct game_globals_vehicle); vehicles = &multiplayer_information->vehicles; vehicle1 = TAG_BLOCK_GET_ELEMENT(vehicles, 1, struct game_globals_vehicle); vehicle2 = TAG_BLOCK_GET_ELEMENT(vehicles, 2, struct game_globals_vehicle); if (result != vehicle0->vehicle.index && result != vehicle1->vehicle.index && result != vehicle2->vehicle.index) { result = NONE; } switch (global_variant.universal_variant.vehicle_set) { case _game_engine_vehicles_none: result = NONE; break; case _game_engine_vehicles_warthog: vehicle = TAG_BLOCK_GET_ELEMENT( vehicles, 0, struct game_globals_vehicle); if (vehicle->vehicle.index != result) result = NONE; break; case _game_engine_vehicles_ghost: vehicle = TAG_BLOCK_GET_ELEMENT( vehicles, 1, struct game_globals_vehicle); if (vehicle->vehicle.index != result) result = NONE; break; case _game_engine_vehicles_tank: vehicle = TAG_BLOCK_GET_ELEMENT( vehicles, 2, struct game_globals_vehicle); if (vehicle->vehicle.index != result) result = NONE; break; } } return result; } long game_engine_remap_weapon( long weapon_definition_index) { long weapon_list_index = weapon_definition_index_to_list_index(weapon_definition_index); if (weapon_list_index == _weapon_list_ball || weapon_list_index == _weapon_list_flag || weapon_list_index == NONE) { return weapon_definition_index; } if (weapon_list_index == _weapon_list_flamethrower) weapon_list_index = _weapon_list_rocket_launcher; switch (global_variant.universal_variant.weapon_set) { case _game_engine_weapons_pistols: switch (weapon_list_index) { case _weapon_list_needler: case _weapon_list_pistol: case _weapon_list_plasma_rifle: case _weapon_list_rocket_launcher: weapon_list_index = _weapon_list_plasma_pistol; break; case _weapon_list_plasma_pistol: default: weapon_list_index = _weapon_list_pistol; break; } break; case _game_engine_weapons_assault_rifles: switch (weapon_list_index) { case _weapon_list_needler: case _weapon_list_pistol: case _weapon_list_plasma_rifle: case _weapon_list_rocket_launcher: weapon_list_index = _weapon_list_plasma_rifle; break; case _weapon_list_plasma_pistol: default: weapon_list_index = _weapon_list_assault_rifle; break; } break; case _game_engine_weapons_plasma_weapons: if (weapon_list_index >= _weapon_list_needler && weapon_list_index <= _weapon_list_plasma_pistol) weapon_list_index = _weapon_list_plasma_pistol; else weapon_list_index = _weapon_list_plasma_rifle; break; case _game_engine_weapons_sniping: if (weapon_list_index != _weapon_list_pistol && weapon_list_index != _weapon_list_sniper_rifle) weapon_list_index = _weapon_list_sniper_rifle; break; case _game_engine_weapons_no_sniping: switch (weapon_list_index) { case _weapon_list_pistol: weapon_list_index = _weapon_list_assault_rifle; break; case _weapon_list_sniper_rifle: weapon_list_index = _weapon_list_shotgun; break; } break; case _game_engine_weapons_rocket_launchers: weapon_list_index = _weapon_list_rocket_launcher; break; case _game_engine_weapons_shotguns: weapon_list_index = _weapon_list_shotgun; break; case _game_engine_weapons_short_range: switch (weapon_list_index) { case _weapon_list_assault_rifle: case _weapon_list_needler: case _weapon_list_pistol: case _weapon_list_sniper_rifle: weapon_list_index = _weapon_list_shotgun; break; } break; case _game_engine_weapons_human: switch (weapon_list_index) { case _weapon_list_needler: case _weapon_list_plasma_rifle: weapon_list_index = _weapon_list_assault_rifle; break; case _weapon_list_plasma_pistol: weapon_list_index = _weapon_list_pistol; break; } break; } return list_index_to_weapon_definition_index(weapon_list_index); } long game_engine_remap_object_definition( long definition_index) { short object_type; if (!game_engine_running() || definition_index==NONE) { return definition_index; } object_type = object_definition_get(definition_index)->object.type; if (object_type==_object_type_vehicle) { return game_engine_remap_vehicle(definition_index); } if (object_type==_object_type_weapon) { return game_engine_remap_weapon(definition_index); } if (object_type==_object_type_equipment) { return game_engine_remap_equipment(definition_index); } return definition_index; } /* ---------- private code */ static void netgame_flag_verify_no_team_duplicates( short flag_type, char const *error_message) { struct scenario *scenario = global_scenario_get(); short flag_index; for (flag_index = 0; flag_index < scenario->netgame_flags.count; flag_index++) { struct scenario_netgame_flag *flag = TAG_BLOCK_GET_ELEMENT( &scenario->netgame_flags, flag_index, struct scenario_netgame_flag); short duplicate_index; if (flag_type != flag->type) continue; for (duplicate_index = flag_index + 1; duplicate_index < scenario->netgame_flags.count; duplicate_index++) { struct scenario_netgame_flag *duplicate = TAG_BLOCK_GET_ELEMENT( &scenario->netgame_flags, duplicate_index, struct scenario_netgame_flag); if (flag_type == duplicate->type && duplicate->team_index == flag->team_index) { error( _error_silent, error_message, duplicate->team_index); } } } return; } static void update_weapon_inventory( long weapon_index) { struct weapon_datum *weapon; match_assert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0x80A, weapon_index != NONE); match_assert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0x80B, weapon_is_flag(weapon_index)); weapon = weapon_get(weapon_index); if (weapon->object.parent_object_index == NONE && !TEST_FLAG(weapon->item.flags, _item_attached_to_unit_bit) && TEST_FLAG( weapon->weapon.flags, _weapon_runtime_game_engine_active_bit)) { weapon->weapon.flags &= ~FLAG(_weapon_runtime_game_engine_active_bit); if (game_engine->weapon_dropped) game_engine->weapon_dropped(weapon_index); } return; } static void game_engine_update_weapons( void) { struct object_iterator iterator; match_assert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0x828, NULL != game_engine); object_iterator_new( &iterator, _object_mask_item, 0); while (object_iterator_next(&iterator)) { struct item_datum *item = item_get(iterator.index); if (TEST_FLAG(item->item.flags, _item_attached_to_unit_bit)) { item->object.scale = 1.f; } else { struct item_definition *definition = item_definition_get(item->definition_index); item->object.scale = definition->item.scale != 0.f ? definition->item.scale : 1.f; match_assert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0x83E, (item->object.scale >= 0.5f) && (item->object.scale <= 3.f)); } /* * Slot 0x38 is populated by the CTF and oddball engines. It receives * each objective weapon during the per-tick item scan. */ if (game_engine->objective_weapon_update) { struct weapon_datum *weapon = weapon_try_and_get(iterator.index); if (weapon && weapon_is_flag(iterator.index)) { update_weapon_inventory(iterator.index); game_engine->objective_weapon_update( iterator.index, weapon); } } } return; } static void netgame_flag_verify_team_range( short flag_type, short minimum_index, short maximum_index, char const *error_message) { struct scenario *scenario = global_scenario_get(); short flag_index; for (flag_index = 0; flag_index < scenario->netgame_flags.count; flag_index++) { struct scenario_netgame_flag *flag = TAG_BLOCK_GET_ELEMENT( &scenario->netgame_flags, flag_index, struct scenario_netgame_flag); if (flag_type == flag->type && (flag->team_index < minimum_index || flag->team_index > maximum_index)) { error( _error_silent, error_message, flag->team_index); } } return; } static void netgame_verify_equipment( short game_type, char const *error_message) { long matching_count = 0; struct scenario *scenario = global_scenario_get(); short equipment_index; for (equipment_index = 0; equipment_index < scenario->netgame_equipment.count; equipment_index++) { struct scenario_netgame_equipment *equipment = TAG_BLOCK_GET_ELEMENT( &scenario->netgame_equipment, equipment_index, struct scenario_netgame_equipment); if (match_game_type( game_type, 4, equipment->game_type)) { matching_count++; } } if (matching_count == 0) error(_error_silent, error_message); return; } static void netgame_flag_verify_team_exists( short flag_type, short flag_index, char const *error_message) { if (find_netgame_flag( NULL, 0.f, 0.f, flag_type, flag_index) == NONE) { error( _error_silent, error_message, flag_index); } return; } static void game_engine_verify_current_map( void) { netgame_flag_verify_team_exists( _netgame_flag_ctf_flag, 0, "NETGAME MAP FAILURE: missing ctf flag [team %d]"); netgame_flag_verify_team_exists( _netgame_flag_ctf_flag, 1, "NETGAME MAP FAILURE: missing ctf flag [team %d]"); netgame_flag_verify_no_team_duplicates( _netgame_flag_ctf_flag, "NETGAME MAP FAILURE: duplicate ctf flag [team %d]"); netgame_flag_verify_team_range( _netgame_flag_ctf_flag, 0, 1, "NETGAME MAP FAILURE: ctf flag out of range [team %d]"); netgame_flag_verify_team_exists( _netgame_flag_hill, 0, "NETGAME MAP FAILURE: missing hill flag [team %d]"); netgame_flag_verify_team_exists( _netgame_flag_hill, 1, "NETGAME MAP FAILURE: missing hill flag [team %d]"); netgame_flag_verify_team_exists( _netgame_flag_oddball_ball_spawn, 0, "NETGAME MAP FAILURE: missing oddball flag [team %d]"); netgame_flag_verify_team_exists( _netgame_flag_oddball_ball_spawn, 1, "NETGAME MAP FAILURE: missing oddball flag [team %d]"); netgame_flag_verify_team_exists( _netgame_flag_race_track, 0, "NETGAME MAP FAILURE: missing race flag [team %d]"); netgame_flag_verify_team_exists( _netgame_flag_race_track, 1, "NETGAME MAP FAILURE: missing race flag [team %d]"); netgame_flag_verify_no_team_duplicates( _netgame_flag_race_track, "NETGAME MAP FAILURE: duplicate race track flag [team %d]"); /* BUG (preserved for exact matching): January passes team index zero for * both CTF checks, and netgame_verify_spawn_points never reads that * formal parameter. * A corrected build should filter starting locations by an authoritatively * recovered team-index field before reporting per-team counts. */ netgame_verify_spawn_points( game_engine_ctf, 0, 4, "NETGAME MAP FAILURE: failed to find enough spawn points for ctf team 0 (%d/%d)"); netgame_verify_spawn_points( game_engine_ctf, 0, 4, "NETGAME MAP FAILURE: failed to find enough spawn points for ctf team 1 (%d/%d)"); netgame_verify_spawn_points( game_engine_slayer, 0, 4, "NETGAME MAP FAILURE: failed to find enough spawn points for slayer %d/%d"); netgame_verify_spawn_points( game_engine_oddball, 0, 4, "NETGAME MAP FAILURE: failed to find enough spawn points for oddball %d/%d"); netgame_verify_spawn_points( game_engine_king, 0, 4, "NETGAME MAP FAILURE: failed to find enough spawn points for king %d/%d"); netgame_verify_spawn_points( game_engine_race, 0, 4, "NETGAME MAP FAILURE: failed to find enough spawn points for race %d/%d"); netgame_verify_equipment( game_engine_ctf, "NETGAME MAP FAILURE: failed to find any equipment for ctf"); netgame_verify_equipment( game_engine_slayer, "NETGAME MAP FAILURE: failed to find any equipment for slayer"); netgame_verify_equipment( game_engine_oddball, "NETGAME MAP FAILURE: failed to find any equipment for oddball"); netgame_verify_equipment( game_engine_king, "NETGAME MAP FAILURE: failed to find any equipment for king"); netgame_verify_equipment( game_engine_race, "NETGAME MAP FAILURE: failed to find any equipment for race"); return; } static void internal_rasterize_target_name( long player_index) { struct player_datum *player; long target_player_index; player = player_get(player_index); target_player_index = NONE; if (player->local_player_index != NONE && player->unit_index != NONE) { target_player_index = find_closest_player_index(player_index); if (target_player_index == player_index) target_player_index = NONE; } /* This is drawn once a frame, several frames per tick (render_interpolation.c): the hold time counts ticks, as it did on the Xbox. */ { static long last_game_times[MAXIMUM_NUMBER_OF_LOCAL_PLAYERS]; long *last_game_time = NULL; if (player->local_player_index >= 0 && player->local_player_index < MAXIMUM_NUMBER_OF_LOCAL_PLAYERS) { last_game_time = &last_game_times[player->local_player_index]; } if (!last_game_time || *last_game_time != game_time_get()) { if (last_game_time) *last_game_time = game_time_get(); if (player->player_display_index != target_player_index) { if (player->player_display_count > 0) player->player_display_count--; if (player->player_display_count == 0) player->player_display_index = target_player_index; } else if (player->player_display_count < 15) { player->player_display_count++; } } } if (player->player_display_index != NONE) { struct player_datum *target_player = player_get(player->player_display_index); wchar_t target_name[12] = { 0 }; long hold_time = player->player_display_count; real alpha; if (hold_time >= 10) hold_time = 10; ustrncpy(target_name, target_player->name, NUMBEROF(target_name) - 1); target_name[NUMBEROF(target_name) - 1] = 0; alpha = linear_to_non_linear_alpha(hold_time * 0.1f) * 0.5f; game_engine_rasterize_message(target_name, alpha); } return; } static boolean internal_rasterize_score( long player_index, long message, long message_data, wchar_t *buffer, long buffer_size) { struct player_datum *player; struct player_datum *other_player; long score; long string_list_index; wchar_t const *format; boolean result; result = TRUE; player = player_get(player_index); score = 0; if (game_engine_test_flag(1)) { switch (message) { case _game_engine_message_multi_kill: message = _game_engine_message_multi_kill_with_score; break; case _game_engine_message_triple_kill: message = _game_engine_message_triple_kill_with_score; break; case _game_engine_message_double_kill: message = _game_engine_message_double_kill_with_score; break; case _game_engine_message_ten_kills_in_a_row: message = _game_engine_message_ten_kills_in_a_row_with_score; break; case _game_engine_message_five_kills_in_row: message = _game_engine_message_five_kills_in_row_with_score; break; case _game_engine_message_killed_enemy: message = _game_engine_message_killed_enemy_with_score; break; default: break; } if (message >= _game_engine_message_multi_kill_with_score && message <= _game_engine_message_killed_enemy_with_score) { /* port: a client of the distributed netcode replaying the host's killing blow: the score the host's game type gave its killer (its own has it only once the game type's state comes) */ if (!network_damage_killer_score(player_index, &score)) score = game_engine->get_player_score(player_index, TRUE); } } switch (message) { case _game_engine_message_welcome: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x4A); else format = L""; usnprintf( buffer, buffer_size, format, player->name); break; case _game_engine_message_killed_by_unknown: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x4B); else format = L""; usnprintf( buffer, buffer_size, format, player->name); break; case _game_engine_message_killed_by_biped: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x4C); else format = L""; usnprintf( buffer, buffer_size, format, player->name); break; case _game_engine_message_killed_by_vehicle: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x4D); else format = L""; usnprintf( buffer, buffer_size, format, player->name); break; case _game_engine_message_killed_by_player: other_player = player_get(message_data); string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x4E); else format = L""; usnprintf( buffer, buffer_size, format, player->name, other_player->name); break; case _game_engine_message_killed_by_friendly_fire: other_player = player_get(message_data); string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x4F); else format = L""; usnprintf( buffer, buffer_size, format, player->name, other_player->name); break; case _game_engine_message_quit: other_player = player_get(message_data); string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x50); else format = L""; usnprintf( buffer, buffer_size, format, other_player->name); break; case _game_engine_message_killed_by_self: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x51); else format = L""; usnprintf( buffer, buffer_size, format, player->name); break; case _game_engine_message_killed_friendly: other_player = player_get(message_data); string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x52); else format = L""; usnprintf( buffer, buffer_size, format, other_player->name); break; case _game_engine_message_multi_kill: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x53); else format = L""; ustrncpy( buffer, format, buffer_size); game_engine_play_multiplayer_sound(_multiplayer_sound_killtacular_kill); break; case _game_engine_message_triple_kill: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x54); else format = L""; ustrncpy( buffer, format, buffer_size); game_engine_play_multiplayer_sound(_multiplayer_sound_triple_kill); break; case _game_engine_message_double_kill: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x55); else format = L""; ustrncpy( buffer, format, buffer_size); game_engine_play_multiplayer_sound(_multiplayer_sound_double_kill); break; case _game_engine_message_ten_kills_in_a_row: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x56); else format = L""; ustrncpy( buffer, format, buffer_size); game_engine_play_multiplayer_sound(_multiplayer_sound_running_riot); break; case _game_engine_message_five_kills_in_row: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x57); else format = L""; ustrncpy( buffer, format, buffer_size); game_engine_play_multiplayer_sound(_multiplayer_sound_killing_spree); break; case _game_engine_message_killed_enemy: other_player = player_get(message_data); string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x58); else format = L""; usnprintf( buffer, buffer_size, format, other_player->name); break; case _game_engine_message_multi_kill_with_score: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x59); else format = L""; usnprintf( buffer, buffer_size, format, score); game_engine_play_multiplayer_sound(_multiplayer_sound_killtacular_kill); break; case _game_engine_message_triple_kill_with_score: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x5A); else format = L""; usnprintf( buffer, buffer_size, format, score); game_engine_play_multiplayer_sound(_multiplayer_sound_triple_kill); break; case _game_engine_message_double_kill_with_score: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x5B); else format = L""; usnprintf( buffer, buffer_size, format, score); game_engine_play_multiplayer_sound(_multiplayer_sound_double_kill); break; case _game_engine_message_ten_kills_in_a_row_with_score: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x5C); else format = L""; usnprintf( buffer, buffer_size, format, score); game_engine_play_multiplayer_sound(_multiplayer_sound_running_riot); break; case _game_engine_message_five_kills_in_row_with_score: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x5D); else format = L""; usnprintf( buffer, buffer_size, format, score); game_engine_play_multiplayer_sound(_multiplayer_sound_killing_spree); break; case _game_engine_message_killed_enemy_with_score: other_player = player_get(message_data); string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x5E); else format = L""; usnprintf( buffer, buffer_size, format, other_player->name, score); break; case _game_engine_message_odd_man_out: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x5F); else format = L""; ustrncpy( buffer, format, buffer_size); break; case _game_engine_message_out_of_lives: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x60); else format = L""; ustrncpy( buffer, format, buffer_size); break; case _game_engine_message_respawn_timer: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x61); else format = L""; usnprintf( buffer, buffer_size, format, message_data); break; case _game_engine_message_waiting_for_space_to_clear: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x62); else format = L""; ustrncpy( buffer, format, buffer_size); break; case _game_engine_message_player_quit_self: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x63); else format = L""; ustrncpy( buffer, format, buffer_size); break; case _game_engine_message_press_back_for_score: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) format = unicode_string_list_get_string(string_list_index, 0x64); else format = L""; ustrncpy( buffer, format, buffer_size); break; default: result = FALSE; break; } buffer[buffer_size - 1] = 0; return result; } static long adjust_score_for_ranking( long score, long player_index) { struct player_datum *player; long result = 0; match_assert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0x29D, player_index!=NONE); player = player_get(player_index); if (score < -1000) score = -1000; score += 1000; if (player->statistics.deaths < global_variant.universal_variant.lives) { result = 0x40000000; } if (!player->quit_out_of_game) result |= 0x20000000; return result | score; } static long item_collection_get_total( struct tag_block const *permutations) { struct item_permutation_definition const *permutation = (struct item_permutation_definition const *)permutations->address; long permutation_count = permutations->count; long result = 0; long permutation_index; for (permutation_index = 0; permutation_index < permutation_count; permutation_index++) { result = (long)( (real)result + permutation[permutation_index].weight); } return result; } static long random_item( long item_collection_index) { struct item_collection_definition *definition = item_collection_definition_get(item_collection_index); struct tag_block *permutations = &definition->permutations; long permutation_count = permutations->count; long remaining_weight = seed_random_range( get_global_random_seed_address(), 0, (short)item_collection_get_total(permutations)); struct item_permutation_definition const *permutation = permutations->address; long permutation_index = 0; while (permutation_index < permutation_count) { remaining_weight = (long)((real)remaining_weight - permutation[permutation_index].weight); if (remaining_weight < 0) return permutation[permutation_index].item.index; permutation_index++; } return NONE; } static void game_engine_update_item_spawn( void) { struct scenario *scenario = global_scenario_get(); short equipment_index; /* a client of the distributed netcode has the host's items (port/linux/game/network_distributed.c) */ if (network_game_distributed_client()) return; for (equipment_index = 0; equipment_index < scenario->netgame_equipment.count; equipment_index++) { struct scenario_netgame_equipment *equipment = TAG_BLOCK_GET_ELEMENT( &scenario->netgame_equipment, equipment_index, struct scenario_netgame_equipment); if (match_game_type( game_engine_get_type(), NUMBEROF(equipment->game_type), equipment->game_type)) { long respawn_period = 30 * TICKS_PER_SECOND; short respawn_time = equipment->spawn_time; if (respawn_time == 0) { if (equipment->item_collection.index != NONE) { struct item_collection_definition *item_collection = item_collection_definition_get( equipment->item_collection.index); respawn_time = item_collection->spawn_time; if (respawn_time != 0) respawn_period = respawn_time * TICKS_PER_SECOND; } } else { respawn_period = respawn_time * TICKS_PER_SECOND; } if (game_time_get() % respawn_period == 0) { long definition_index = random_item(equipment->item_collection.index); struct object_placement_data placement_data; long object_index; object_placement_data_new( &placement_data, definition_index, NONE); placement_data.position = equipment->position; object_index = object_new(&placement_data); if (object_index != NONE) { struct item_datum *item = item_get(object_index); object_set_garbage(object_index, FALSE); if (TEST_FLAG( equipment->flags, _equipment_created_at_rest_bit)) { SET_FLAG(item->object.flags, _object_at_rest_bit, TRUE); } item->item.last_owned_time += respawn_period - 30 * TICKS_PER_SECOND; } } } } return; } static void handle_custom_starting_equipment( long unit_index, long *fragmentation_grenade_count, long *plasma_grenade_count) { struct scenario *scenario = global_scenario_get(); long starting_equipment_index; for (starting_equipment_index = 0; starting_equipment_index < scenario->scenario_starting_equipment.count; starting_equipment_index++) { struct scenario_starting_equipment *starting_equipment = TAG_BLOCK_GET_ELEMENT( &scenario->scenario_starting_equipment, starting_equipment_index, struct scenario_starting_equipment); if (match_game_type( game_engine_get_type(), 4, starting_equipment->game_type)) { boolean first_weapon = TRUE; long item_collection_index; /* January visits the first five of the six item collections */ for (item_collection_index = 0; item_collection_index < 5; item_collection_index++) { if (starting_equipment->item_collection[item_collection_index].index != NONE) { long definition_index = random_item( starting_equipment->item_collection[item_collection_index].index); long weapon_index; struct object_placement_data placement_data; object_placement_data_new( &placement_data, definition_index, NONE); weapon_index = object_new(&placement_data); if (weapon_index != NONE) { struct object_datum *weapon = object_get_and_verify_type( weapon_index, _object_mask_item); if (!first_weapon && unit_has_weapon_definition_index( unit_index, weapon->definition_index)) { object_delete(weapon_index); } else { unit_add_weapon_to_inventory( unit_index, weapon_index, first_weapon ? 2 : 0); first_weapon = FALSE; } } } } if (TEST_FLAG(starting_equipment->flags, _netgame_starting_equipment_flag_no_grenades_bit)) { *fragmentation_grenade_count = 0; *plasma_grenade_count = 0; } if (TEST_FLAG(starting_equipment->flags, _netgame_starting_equipment_flag_plasma_greandes_bit)) { *plasma_grenade_count += *fragmentation_grenade_count; *fragmentation_grenade_count = 0; } break; } } return; } void game_engine_postspawn_player_update( long player_index) { struct player_datum *player; long unit_index; struct game_globals_grenade *fragmentation_grenade; struct game_globals_grenade *plasma_grenade; long fragmentation_grenade_count; long plasma_grenade_count; if (!game_engine) return; if (game_engine->player_update) { game_engine->player_update(player_index); return; } player = player_get(player_index); unit_index = player->unit_index; fragmentation_grenade = TAG_BLOCK_GET_ELEMENT( &scenario_get_game_globals()->grenades, _unit_grenade_human_fragmentation, struct game_globals_grenade); plasma_grenade = TAG_BLOCK_GET_ELEMENT( &scenario_get_game_globals()->grenades, _unit_grenade_covenant_plasma, struct game_globals_grenade); plasma_grenade_count = plasma_grenade->maximum_count; fragmentation_grenade_count = fragmentation_grenade->maximum_count; if (TEST_FLAG(game_engine_globals.flags, _game_engine_9_or_more_players_bit)) { fragmentation_grenade_count = 1; plasma_grenade_count = 1; } else if (TEST_FLAG(game_engine_globals.flags, _game_engine_5_or_more_players_bit)) { fragmentation_grenade_count = 2; plasma_grenade_count = 2; } { long starting_fragmentation_grenade_count = fragmentation_grenade_count; long starting_plasma_grenade_count = 0; if (!TEST_FLAG(global_variant.universal_variant.flags, _game_variant_generic_starting_equipment_bit)) { handle_custom_starting_equipment( unit_index, &starting_fragmentation_grenade_count, &starting_plasma_grenade_count); } if (game_engine_infinite_grenades_internal()) { starting_plasma_grenade_count = plasma_grenade_count; starting_fragmentation_grenade_count = fragmentation_grenade_count; } if (unit_index != NONE) { struct unit_datum *unit = object_get_and_verify_type( unit_index, _object_mask_unit); switch (global_variant.universal_variant.weapon_set) { case _game_engine_weapons_plasma_weapons: starting_plasma_grenade_count += starting_fragmentation_grenade_count; starting_fragmentation_grenade_count = 0; break; case _game_engine_weapons_human: starting_fragmentation_grenade_count += starting_plasma_grenade_count; starting_plasma_grenade_count = 0; break; case _game_engine_weapons_no_grenades: if (!game_engine_infinite_grenades_internal()) { starting_fragmentation_grenade_count = 0; starting_plasma_grenade_count = 0; } break; } starting_fragmentation_grenade_count = MIN( starting_fragmentation_grenade_count, fragmentation_grenade_count); starting_plasma_grenade_count = MIN( starting_plasma_grenade_count, plasma_grenade_count); unit->unit.grenade_counts[ _unit_grenade_human_fragmentation] = (char)starting_fragmentation_grenade_count; unit->unit.grenade_counts[ _unit_grenade_covenant_plasma] = (char)starting_plasma_grenade_count; } } return; } boolean game_engine_get_state_message( long player_index, wchar_t *message, long message_character_count) { struct player_datum *player; long state_message_parameter; long state_message; long respawn_timer; boolean result = FALSE; if (game_engine) { player = player_get(player_index); if (player->state_message >= _game_engine_message_odd_man_out && player->state_message <= _game_engine_message_waiting_for_space_to_clear) { player->state_message = NONE; } if (player->unit_index == NONE) { state_message_parameter = 0; if (player->quit_out_of_game == TRUE) state_message = _game_engine_message_player_quit_self; else if (game_engine_player_is_out_of_lives(player_index)) state_message = _game_engine_message_out_of_lives; else if (game_engine_player_is_odd_man_out(player_index)) state_message = _game_engine_message_odd_man_out; else { respawn_timer = player->respawn_timer; if (respawn_timer > 0) { state_message = _game_engine_message_respawn_timer; state_message_parameter = respawn_timer / TICKS_PER_SECOND; } else state_message = _game_engine_message_waiting_for_space_to_clear; } result = multiplayer_message_internal( player_index, state_message, state_message_parameter, message, message_character_count); } else if (game_time_get() < 15 * TICKS_PER_SECOND) { result = multiplayer_message_internal( player_index, _game_engine_message_press_back_for_score, NONE, message, message_character_count); } else if (player->state_message != NONE) { result = multiplayer_message_internal( player_index, player->state_message, player->state_message_player_index, message, message_character_count); } } return result; } /* port: a distributed client's players spawn when the host spawns them (network_player_attach_unit), but their respawn countdown runs here as it does on the host (game_engine_should_spawn_player), for the hud's count and its sounds: it stops a tick short, where the host's unit ends it */ void game_engine_client_respawn_countdown( long player_index) { struct player_datum *player; if (!game_engine) return; player = player_get(player_index); if (player->quit_out_of_game == TRUE || player->statistics.deaths == 0 || player->respawn_timer <= 1 || game_engine_player_is_out_of_lives(player_index) || game_engine_player_is_odd_man_out(player_index) || game_engine_globals.postgame_state == game_engine_mode_postgame_rasterize || game_engine_globals.postgame_state == game_engine_mode_postgame_rasterize_delay) { return; } if (player->local_player_index != NONE && (player->respawn_timer == 90 || player->respawn_timer == 60 || player->respawn_timer == 30)) { game_engine_play_multiplayer_sound(_multiplayer_sound_countdown_for_respawn); } player->respawn_timer--; if (player->local_player_index != NONE && player->respawn_timer == 1) game_engine_play_multiplayer_sound(_multiplayer_sound_respawn); return; } boolean game_engine_should_spawn_player( long player_index) { boolean should_spawn = FALSE; if (game_engine) { struct player_datum *player = player_get(player_index); if (player->quit_out_of_game == TRUE) { should_spawn = FALSE; } else if (player->statistics.deaths == 0) { should_spawn = TRUE; } else if (game_engine_player_is_out_of_lives(player_index)) { should_spawn = FALSE; } else if (game_engine_player_is_odd_man_out(player_index)) { should_spawn = FALSE; } else if (game_engine_globals.postgame_state == game_engine_mode_postgame_rasterize || game_engine_globals.postgame_state == game_engine_mode_postgame_rasterize_delay) { should_spawn = FALSE; } else { long respawn_timer = player->respawn_timer; if (respawn_timer > 0) { if (player->local_player_index != NONE) { if (respawn_timer == 90) { game_engine_play_multiplayer_sound( _multiplayer_sound_countdown_for_respawn); } else if (respawn_timer == 60) { game_engine_play_multiplayer_sound( _multiplayer_sound_countdown_for_respawn); } else if (respawn_timer == 30) { game_engine_play_multiplayer_sound( _multiplayer_sound_countdown_for_respawn); } else if (respawn_timer == 1) { game_engine_play_multiplayer_sound( _multiplayer_sound_respawn); } } player->respawn_timer--; should_spawn = player->respawn_timer == 0; } else { should_spawn = TRUE; } } if (should_spawn && game_time_get() > 3 && game_time_get() % 32 != DATUM_INDEX_TO_ABSOLUTE_INDEX(player_index) % 32) { should_spawn = FALSE; } } return should_spawn; } struct game_engine_place game_engine_get_place( long player_index, enum get_score_type score_type) { struct player_datum *player; long group_count; boolean tied; boolean all_tied; struct game_engine_place result; player = player_get(player_index); all_tied = TRUE; tied = FALSE; group_count = 1; result.place = 0; if (game_engine->get_player_score) { struct data_iterator iterator; struct player_datum *other_player; /* port: free-for-all team indices run up to the player limit */ unsigned long team_mask[BIT_VECTOR_SIZE_IN_LONGS(MULTIPLAYER_MAXIMUM_PLAYERS)] = { 0 }; long score = game_engine->get_player_score(player_index, score_type); data_iterator_new(&iterator, player_data); other_player = (struct player_datum *)data_iterator_next(&iterator); while (other_player) { boolean different_player; if (score_type == _get_score_team) { different_player = other_player->team_index != player->team_index; } else different_player = iterator.datum_index != player_index; if (different_player && score_type == _get_score_team) { /* a team outside the mask (none yet) counts on its own */ if (VALID_INDEX(other_player->team_index, MULTIPLAYER_MAXIMUM_PLAYERS)) { if (BIT_VECTOR_TEST_FLAG(team_mask, other_player->team_index)) different_player = FALSE; else BIT_VECTOR_SET_FLAG(team_mask, other_player->team_index, TRUE); } } if (different_player) { long other_score = game_engine->get_player_score( iterator.datum_index, score_type); group_count++; if (other_score != score) all_tied = FALSE; if (other_score > score) result.place++; else if (other_score == score) tied = TRUE; } other_player = (struct player_datum *)data_iterator_next(&iterator); } match_vassert( "c:\\halo\\SOURCE\\game\\game_engine.c", 0x1377, !all_tied || tied || group_count == 1, "(!all_tied || (tied)) || (1 == group_count)"); } all_tied &= tied; result.flags = score_type != _get_score_team ? 0 : FLAG(_place_team); SET_FLAG(result.flags, _place_tied, tied); SET_FLAG( result.flags, _place_all_tied, all_tied); SET_FLAG( result.flags, _place_two_groups, group_count == 2); return result; } static void netgame_verify_spawn_points( short game_type, short unused_team_index, short minimum_count, char const *error_message) { short starting_location_count; long matching_count; short starting_location_index; starting_location_count = player_get_starting_location_count(); matching_count = 0; for (starting_location_index = 0; starting_location_index < starting_location_count; starting_location_index++) { struct player_starting_location *starting_location = player_get_starting_location(starting_location_index); if (match_game_type( game_type, 4, starting_location->game_types)) { matching_count++; } } if (matching_count < minimum_count) { error( _error_silent, error_message, matching_count, minimum_count); } return; } long game_engine_slayer_write_network_state(byte *buffer, long size); boolean game_engine_slayer_read_network_state(byte const *buffer, long size, boolean first); long game_engine_ctf_write_network_state(byte *buffer, long size); boolean game_engine_ctf_read_network_state(byte const *buffer, long size, boolean first); long game_engine_oddball_write_network_state(byte *buffer, long size); boolean game_engine_oddball_read_network_state(byte const *buffer, long size, boolean first); long game_engine_king_write_network_state(byte *buffer, long size); boolean game_engine_king_read_network_state(byte const *buffer, long size, boolean first); long game_engine_race_write_network_state(byte *buffer, long size); boolean game_engine_race_read_network_state(byte const *buffer, long size, boolean first); /* the distributed netcode (port/linux/game/network_damage.c): a player's score as the game type has it (with its killing blows' messages), 0 for none */ long game_engine_network_player_score( long player_index) { if (!game_engine || !game_engine->get_player_score) return 0; return game_engine->get_player_score(player_index, TRUE); } /* the distributed netcode (port/linux/game/network_distributed.c): the current game type's state (scores, and what else every machine must agree on), which the host sends its clients; the size written, 0 for none */ long game_engine_write_network_state( byte *buffer, long size) { long postgame_state; long written; if (!game_engine || size < (long)sizeof(postgame_state)) return 0; /* whether the game is over, then the game type's */ postgame_state = game_engine_globals.postgame_state; csmemcpy(buffer, &postgame_state, sizeof(postgame_state)); buffer += sizeof(postgame_state); size -= sizeof(postgame_state); switch (game_engine_get_type()) { case game_engine_ctf: written = game_engine_ctf_write_network_state(buffer, size); break; case game_engine_slayer: written = game_engine_slayer_write_network_state(buffer, size); break; case game_engine_oddball: written = game_engine_oddball_write_network_state(buffer, size); break; case game_engine_king: written = game_engine_king_write_network_state(buffer, size); break; case game_engine_race: written = game_engine_race_write_network_state(buffer, size); break; default: written = 0; break; } return sizeof(postgame_state) + written; } /* a client: the host's */ void game_engine_read_network_state( byte const *buffer, long size) { long postgame_state; boolean first; boolean read; if (!game_engine || size < (long)sizeof(postgame_state)) return; csmemcpy(&postgame_state, buffer, sizeof(postgame_state)); buffer += sizeof(postgame_state); size -= sizeof(postgame_state); first = !game_engine_network_state_read; switch (game_engine_get_type()) { case game_engine_ctf: read = game_engine_ctf_read_network_state(buffer, size, first); break; case game_engine_slayer: read = game_engine_slayer_read_network_state(buffer, size, first); break; case game_engine_oddball: read = game_engine_oddball_read_network_state(buffer, size, first); break; case game_engine_king: read = game_engine_king_read_network_state(buffer, size, first); break; case game_engine_race: read = game_engine_race_read_network_state(buffer, size, first); break; default: read = TRUE; break; } /* (a state the game type refused is not had: the first it takes is, whose events it only takes) */ if (!read) return; game_engine_network_state_read = TRUE; /* the game ended on the host (after what ended it is shown, as the host shows it) */ if (postgame_state != 0 && game_engine_globals.postgame_state == 0) game_engine_end_game(); }