mirror of
https://github.com/astromaddie/HaloCE-VR.git
synced 2026-10-23 12:00:16 +02:00
3983 lines
107 KiB
C
3983 lines
107 KiB
C
/*
|
|
PLAYERS.C
|
|
|
|
symbols in this file:
|
|
000A9470 0070:
|
|
_players_initialize (0000)
|
|
000A94E0 00b0:
|
|
_players_initialize_for_new_map (0000)
|
|
000A9590 0020:
|
|
_players_dispose_from_old_map (0000)
|
|
000A95B0 0030:
|
|
_players_dispose (0000)
|
|
000A95E0 0020:
|
|
_machine_get_player_list (0000)
|
|
000A9600 0050:
|
|
_machine_add_player (0000)
|
|
000A9650 0060:
|
|
_local_player_exists (0000)
|
|
000A96B0 0050:
|
|
_find_unused_local_player_index (0000)
|
|
000A9700 0020:
|
|
_player_delete (0000)
|
|
000A9720 0010:
|
|
_players_get_respawn_failure (0000)
|
|
000A9730 0050:
|
|
_local_player_get_player_index (0000)
|
|
000A9780 00a0:
|
|
_local_player_set_player_index (0000)
|
|
000A9820 0010:
|
|
_local_player_count (0000)
|
|
000A9830 0040:
|
|
_local_player_get_next (0000)
|
|
000A9870 0050:
|
|
_player_index_from_unit_index (0000)
|
|
000A98C0 0090:
|
|
_player_died (0000)
|
|
000A9950 0010:
|
|
_players_are_all_dead (0000)
|
|
000A9960 00c0:
|
|
_players_set_local_player_unit (0000)
|
|
000A9A20 0010:
|
|
_players_get_combined_pvs_local (0000)
|
|
000A9A30 0010:
|
|
_players_get_combined_pvs (0000)
|
|
000A9A40 0020:
|
|
_player_input_enable (0000)
|
|
000A9A60 0020:
|
|
_player_input_enabled (0000)
|
|
000A9A80 00f0:
|
|
_any_player_is_in_the_air (0000)
|
|
000A9B70 0050:
|
|
_any_player_is_dead (0000)
|
|
000A9BC0 0040:
|
|
_is_player_in_trigger (0000)
|
|
000A9C00 00e0:
|
|
_player_pseudo_kill (0000)
|
|
000A9CE0 0120:
|
|
_player_control_fix_for_loaded_game_state (0000)
|
|
000A9E00 0050:
|
|
_player_get_starting_location_count (0000)
|
|
000A9E50 00c0:
|
|
_player_get_starting_location (0000)
|
|
000A9F10 0070:
|
|
_placement_data_set_change_color (0000)
|
|
000A9F80 0070:
|
|
_create_weapon (0000)
|
|
000A9FF0 0170:
|
|
_players_compute_combined_pvs (0000)
|
|
000AA160 0020:
|
|
_players_compute_local_player_count (0000)
|
|
000AA180 0010:
|
|
_player_examine_nearby_unit (0000)
|
|
000AA190 0090:
|
|
_unit_should_autopick_weapon (0000)
|
|
000AA220 0020:
|
|
_player_reset_action_result (0000)
|
|
000AA240 00c0:
|
|
_player_handle_weapon_swap (0000)
|
|
000AA300 00b0:
|
|
_player_over_shield_screen_effect (0000)
|
|
000AA3B0 00b0:
|
|
_player_active_camo_screen_effect (0000)
|
|
000AA460 0090:
|
|
_player_health_pack_screen_effect (0000)
|
|
000AA4F0 0040:
|
|
_player_powerup_on (0000)
|
|
000AA530 0030:
|
|
_player_powerup_additional (0000)
|
|
000AA560 0030:
|
|
_player_powerup_off (0000)
|
|
000AA590 0070:
|
|
_players_handle_deleted_object (0000)
|
|
000AA600 0020:
|
|
_random_direction3d (0000)
|
|
000AA620 0040:
|
|
_valid_real_vector2d (0000)
|
|
000AA660 0120:
|
|
_player_new (0000)
|
|
000AA780 0150:
|
|
_player_add_equipment (0000)
|
|
000AA8D0 0110:
|
|
_player_aiming_vector_from_facing (0000)
|
|
000AA9E0 0510:
|
|
_player_teleport_internal (0000)
|
|
000AAEF0 0060:
|
|
_player_teleport (0000)
|
|
000AAF50 00d0:
|
|
_find_best_starting_location_index (0000)
|
|
000AB020 0330:
|
|
_player_spawn (0000)
|
|
000AB350 00f0:
|
|
_player_set_action_result (0000)
|
|
000AB440 0250:
|
|
_player_handle_action (0000)
|
|
000AB690 00f0:
|
|
_player_handle_powerup (0000)
|
|
000AB780 00a0:
|
|
_player_handle_powerup_minor (0000)
|
|
000AB820 0070:
|
|
_player_update_powerups (0000)
|
|
000AB890 01a0:
|
|
_players_debug_render (0000)
|
|
000ABA30 0090:
|
|
_debug_player_teleport (0000)
|
|
000ABAC0 01d0:
|
|
_players_respawn_coop (0000)
|
|
000ABC90 0140:
|
|
_player_teleport_on_bsp_switch (0000)
|
|
000ABDD0 02e0:
|
|
_players_reconnect_to_structure_bsp (0000)
|
|
000AC0B0 01c0:
|
|
_player_examine_nearby_vehicle (0000)
|
|
000AC270 00b0:
|
|
_player_examine_nearby_device (0000)
|
|
000AC320 0150:
|
|
_player_handle_powerup_equipment (0000)
|
|
000AC470 06e0:
|
|
_players_update_before_game (0000)
|
|
000ACB50 0320:
|
|
_player_examine_nearby_item (0000)
|
|
000ACE70 00f0:
|
|
_player_examine_nearby_objects (0000)
|
|
000ACF60 0330:
|
|
_players_update_after_game (0000)
|
|
0025CED8 006c:
|
|
_adjustment_weights (0000)
|
|
0025CF44 001a:
|
|
??_C@_0BK@OOAKICCI@players_update_after_game?$AA@ (0000)
|
|
0025CF60 001b:
|
|
??_C@_0BL@FKGHDGFM@players_update_before_game?$AA@ (0000)
|
|
0025CF7C 0010:
|
|
??_C@_0BA@CEEHCBBD@players?5globals?$AA@ (0000)
|
|
0025CF8C 0006:
|
|
??_C@_05EINLALJG@teams?$AA@ (0000)
|
|
0025CF94 0008:
|
|
??_C@_07BCGOFIOM@players?$AA@ (0000)
|
|
0025CF9C 001a:
|
|
??_C@_0BK@CGPNFENM@failed?5to?5create?5a?5player?$AA@ (0000)
|
|
0025CFB8 001e:
|
|
??_C@_0BO@IAEAGICD@c?3?2halo?2SOURCE?2game?2players?4c?$AA@ (0000)
|
|
0025CFD8 004f:
|
|
??_C@_0EP@PCIBAKOM@local_player_index?$DO?$DNNONE?5?$CG?$CG?5loca@ (0000)
|
|
0025D028 009b:
|
|
??_C@_0JL@EEPEKCFO@tried?5to?5fix?5broken?5player?5contr@ (0000)
|
|
0025D0C8 0069:
|
|
??_C@_0GJ@LOLKJFPO@failed?5to?5correct?5player?5control@ (0000)
|
|
0025D134 0031:
|
|
??_C@_0DB@FKMNJOMP@corrected?5player?5control?5for?5res@ (0000)
|
|
0025D168 0070:
|
|
??_C@_0HA@DONIDEOA@?$CI?$CIlocal_player_index?$DO?$DN0?$CJ?5?$CG?$CG?5?$CIloc@ (0000)
|
|
0025D1D8 002b:
|
|
??_C@_0CL@OKOJEGMM@Could?5not?5attach?5starting?5weapon@ (0000)
|
|
0025D204 0021:
|
|
??_C@_0CB@EBNMJKAL@player?9?$DOlocal_player_index?$CB?$DNNONE@ (0000)
|
|
0025D228 002f:
|
|
??_C@_0CP@BNOOKFKL@couldn?8t?5teleport?5player?5into?5a?5@ (0000)
|
|
0025D258 0019:
|
|
??_C@_0BJ@LHNHGGK@player?9?$DOunit_index?$CB?$DNNONE?$AA@ (0000)
|
|
0025D274 0006:
|
|
??_C@_05MHMEBBGO@scale?$AA@ (0000)
|
|
0025D27C 0025:
|
|
??_C@_0CF@PKKNMABJ@magnitude3d?$CI?$CGbest_adjustment_vec@ (0000)
|
|
0025D2A4 0032:
|
|
??_C@_0DC@CLGKJEDK@source_unit_index?$DN?$DNNONE?5?$HM?$HM?5local@ (0000)
|
|
0025D2D8 0008:
|
|
__real@3fe6a09e60000000 (0000)
|
|
0025D2E0 003a:
|
|
??_C@_0DK@GFFNPIMP@powerup_type?$DO?$DN0?5?$CG?$CG?5powerup_type?$DM@ (0000)
|
|
0025D31C 0016:
|
|
??_C@_0BG@HMDKOAOM@no?5players?5in?5the?5bsp?$AA@ (0000)
|
|
0025D334 0013:
|
|
??_C@_0BD@ELAHGGCE@seat_index?5?$CB?$DN?5NONE?$AA@ (0000)
|
|
0025D348 0046:
|
|
??_C@_0EG@POGHKPOA@?$CINONE?5?$DN?$DN?5control_data?4zoom_level@ (0000)
|
|
0025D390 008c:
|
|
??_C@_0IM@DKBPHNBM@?$CINONE?5?$DN?$DN?5control_data?4grenade_in@ (0000)
|
|
0025D420 0085:
|
|
??_C@_0IF@OCHKLACI@?$CINONE?5?$DN?$DN?5control_data?4weapon_ind@ (0000)
|
|
0025D4A8 004c:
|
|
??_C@_0EM@ECBIAFIL@?$CINONE?5?$DN?$DN?5action?9?$DOdesired_zoom_le@ (0000)
|
|
0025D4F8 0095:
|
|
??_C@_0JF@HCBNBPOG@?$CINONE?5?$DN?$DN?5action?9?$DOdesired_grenade@ (0000)
|
|
0025D590 008e:
|
|
??_C@_0IO@DMKDCDID@?$CINONE?5?$DN?$DN?5action?9?$DOdesired_weapon_@ (0000)
|
|
0025D620 0018:
|
|
??_C@_0BI@GKAKFFBF@action?9?$DOprimary_trigger?$AA@ (0000)
|
|
0025D638 0027:
|
|
??_C@_0CH@OILDLPCJ@?$CFs?3?5assert_valid_real_vector2d?$CI?$CF@ (0000)
|
|
0025D660 0012:
|
|
??_C@_0BC@MPDBBHIH@?$CGaction?9?$DOthrottle?$AA@ (0000)
|
|
0025D674 001b:
|
|
??_C@_0BL@PMBMFGJI@action?9?$DOdesired_facing?4yaw?$AA@ (0000)
|
|
0025D690 001d:
|
|
??_C@_0BN@NJOPCJPO@action?9?$DOdesired_facing?4pitch?$AA@ (0000)
|
|
0025D6B0 0042:
|
|
??_C@_0EC@JMPLFNIK@action_index?$DO?$DN0?5?$CG?$CG?5action_index?$DM@ (0000)
|
|
0025D6F4 003b:
|
|
??_C@_0DL@NOPAIALJ@?$CB?$CB?$CBWARNING?$CB?$CB?$CB?5teleported?5player?5@ (0000)
|
|
0025D730 0004:
|
|
__real@3c360b61 (0000)
|
|
002DEE08 0c18:
|
|
_players_static_data (0000)
|
|
00453408 005d:
|
|
_machine_to_player_table (0000)
|
|
_debug_render_player_teleport (005c)
|
|
*/
|
|
|
|
/* ---------- headers */
|
|
|
|
#include "cseries.h"
|
|
#include "cseries/errors.h"
|
|
#include "cseries/profile.h"
|
|
#include "ai/ai.h"
|
|
#include "ai/ai_debug.h"
|
|
#include "ai/ai_scenario_definitions.h"
|
|
#include "camera/observer.h"
|
|
#include "data.h"
|
|
#include "devices/devices.h"
|
|
#include "editor/editor_stubs.h"
|
|
#include "effects/effects.h"
|
|
#include "effects/player_effects.h"
|
|
#include "game.h"
|
|
#include "game/game_globals.h"
|
|
#include "game_engine.h"
|
|
#include "input.h"
|
|
#include "interface/hud.h"
|
|
#include "interface/hud_messaging.h"
|
|
#include "interface/hud_unit.h"
|
|
#include "interface/hud_weapon.h"
|
|
#include "interface/player_ui.h"
|
|
#include "interface/ui_widget.h"
|
|
#include "items/equipment.h"
|
|
#include "items/equipment_definitions.h"
|
|
#include "items/projectiles.h"
|
|
#include "items/weapon_definitions.h"
|
|
#include "items/weapons.h"
|
|
#include "main/main.h"
|
|
#include "objects/damage.h"
|
|
#include "objects/damage_effect_definitions.h"
|
|
#include "physics/collisions.h"
|
|
#include "real_math.h"
|
|
#include "render/render_debug.h"
|
|
#include "players.h"
|
|
#include "player_queues_new.h"
|
|
#include "objects/objects.h"
|
|
#include "saved games/game_state.h"
|
|
#include "scenario/scenario.h"
|
|
#include "scenario/scenario_definitions.h"
|
|
#include "structures/structure_bsp_definitions.h"
|
|
#include "tag_files/tag_groups.h"
|
|
#include "text/text_group.h"
|
|
#include "text/unicode.h"
|
|
#include "units/biped_definitions.h"
|
|
#include "units/bipeds.h"
|
|
#include "units/units.h"
|
|
#include "units/vehicle_definitions.h"
|
|
#include "units/vehicles.h"
|
|
|
|
/* network_game_globals.c's */
|
|
boolean network_game_distributed_client(void);
|
|
/* port/linux/game/network_distributed.c's */
|
|
void network_distributed_player_picked_up(long player_index, short kind, long definition_index, short count);
|
|
/* game_sound.c's */
|
|
long unspatialized_impulse_sound_new(long sound_definition_index, real scale);
|
|
|
|
/* what a player picked up, for the distributed netcode's client whose
|
|
player it is: the host decides the pickup, the client shows it
|
|
(network_player_show_pickup) */
|
|
enum
|
|
{
|
|
_network_pickup_weapon,
|
|
_network_pickup_ammunition,
|
|
_network_pickup_grenade,
|
|
_network_pickup_equipment,
|
|
_network_pickup_powerup,
|
|
};
|
|
|
|
#define player_network_picked_up(player, player_index, kind, definition_index, count) \
|
|
if ((player)->local_player_index == NONE) \
|
|
network_distributed_player_picked_up(player_index, kind, definition_index, count)
|
|
|
|
static void network_player_log_idle_action(long player_index, unsigned long control_flags);
|
|
|
|
/* whether this machine decides pickups: not a client of the distributed
|
|
netcode, whose players' weapons, grenades and power-ups are the host's
|
|
(port/linux/game/network_distributed.c) */
|
|
#define players_decide_pickups() (!network_game_distributed_client())
|
|
|
|
/* ---------- constants */
|
|
|
|
enum
|
|
{
|
|
/* the native builds' session limits (port/linux/include/halo_port_limits.h) */
|
|
NETWORK_GAME_MAXIMUM_PLAYER_COUNT = HALO_PORT_MAXIMUM_NETWORK_PLAYERS,
|
|
MAXIMUM_NETWORK_MACHINE_COUNT = HALO_PORT_MAXIMUM_NETWORK_MACHINES,
|
|
MULTIPLAYER_GAME_TEXT_YOU_WERE_TELEFRAGGED = 183,
|
|
_collision_test_for_player_teleport_flags =
|
|
FLAG(_collision_test_front_facing_surfaces_bit) |
|
|
FLAG(_collision_test_ignore_invisible_surfaces_bit) |
|
|
FLAG(_collision_test_structure_bit) |
|
|
FLAG(_collision_test_objects_scenery_bit),
|
|
_object_mask_player_interaction =
|
|
_object_mask_unit |
|
|
_object_mask_item |
|
|
_object_mask_control
|
|
};
|
|
|
|
/* ---------- macros */
|
|
|
|
/* ---------- structures */
|
|
|
|
/* These scenario records are consumed only by Players. The broader scenario
|
|
header intentionally cannot own them yet: older consumers still carry their
|
|
own incompatible partial definitions. */
|
|
struct scenario_bsp_switch_trigger_volume
|
|
{
|
|
short trigger_volume_index;
|
|
short source_structure_bsp_index;
|
|
short destination_structure_bsp_index;
|
|
short cutscene_flag_index;
|
|
};
|
|
|
|
struct unit_control_data
|
|
{
|
|
char animation_state;
|
|
char aiming_speed;
|
|
word control_flags;
|
|
short weapon_index;
|
|
short grenade_index;
|
|
short zoom_level;
|
|
short pad;
|
|
real_vector3d throttle;
|
|
real primary_trigger;
|
|
real_vector3d facing_vector;
|
|
real_vector3d aiming_vector;
|
|
real_vector3d looking_vector;
|
|
};
|
|
|
|
typedef char unit_control_data_size_assert[
|
|
sizeof(struct unit_control_data) == 0x40 ? 1 : -1];
|
|
|
|
/* Players needs the vehicle control prefix only. VEHICLES.C owns the full
|
|
runtime datum, which has not yet been made a shared definition. */
|
|
struct _players_vehicle_datum
|
|
{
|
|
word flags;
|
|
short unknown426;
|
|
byte unknown_state;
|
|
char approach_type;
|
|
byte unknown42A;
|
|
byte unused42B;
|
|
};
|
|
|
|
struct players_vehicle_datum
|
|
{
|
|
long definition_index;
|
|
struct _object_datum object;
|
|
struct _unit_datum unit;
|
|
struct _players_vehicle_datum vehicle;
|
|
};
|
|
|
|
struct player_screen_flash_parameters
|
|
{
|
|
short type;
|
|
word pad;
|
|
real maximum_intensity;
|
|
real red;
|
|
real green;
|
|
real duration;
|
|
};
|
|
|
|
struct players_static_data
|
|
{
|
|
struct profile_section update_before_game_profile;
|
|
struct profile_section update_after_game_profile;
|
|
struct player_screen_flash_parameters screen_flash_parameters[2];
|
|
};
|
|
|
|
typedef char players_static_data_size_assert[
|
|
sizeof(struct players_static_data) == 0xC18 ? 1 : -1];
|
|
typedef char players_static_data_screen_flash_offset_assert[
|
|
offsetof(struct players_static_data, screen_flash_parameters) == 0xBF0 ? 1 : -1];
|
|
|
|
/* ---------- prototypes */
|
|
|
|
static long is_player_in_trigger(
|
|
short bsp_switch_trigger_volume_index,
|
|
long object_index);
|
|
static void player_reset_action_result(
|
|
long player_index);
|
|
static boolean player_handle_weapon_swap(
|
|
long player_index);
|
|
static void player_over_shield_screen_effect(
|
|
long player_index);
|
|
static void player_active_camo_screen_effect(
|
|
long player_index);
|
|
static void player_health_pack_screen_effect(
|
|
long player_index);
|
|
static void player_powerup_on(
|
|
long player_index,
|
|
short powerup_type);
|
|
static void player_powerup_additional(
|
|
long player_index,
|
|
short powerup_type);
|
|
static void player_powerup_off(
|
|
long player_index,
|
|
short powerup_type);
|
|
static void player_set_action_result(
|
|
long player_index,
|
|
short action_result,
|
|
long object_index,
|
|
short seat_index);
|
|
static boolean player_handle_action(
|
|
long player_index);
|
|
static void player_update_powerups(
|
|
long player_index);
|
|
static void player_examine_nearby_vehicle(
|
|
long player_index,
|
|
long vehicle_index);
|
|
static void player_examine_nearby_item(
|
|
long player_index,
|
|
long item_index);
|
|
static void player_examine_nearby_objects(
|
|
long player_index);
|
|
static void player_teleport_on_bsp_switch(
|
|
long player_index,
|
|
long source_unit_index,
|
|
real_point3d const *position);
|
|
static void player_handle_powerup_equipment(
|
|
long player_index,
|
|
long equipment_index);
|
|
|
|
/* ---------- globals */
|
|
|
|
struct players_globals *players_globals;
|
|
struct data_array *team_data;
|
|
struct data_array *player_data;
|
|
static long machine_to_player_table[MAXIMUM_NETWORK_MACHINE_COUNT][MAXIMUM_LOCAL_PLAYERS] = { 0 };
|
|
static boolean players_lost_map_started = FALSE;
|
|
static short over_shield_screen_flash_fade_function = 0;
|
|
static real over_shield_screen_flash_alpha = 0.f;
|
|
static real over_shield_screen_flash_green = 0.f;
|
|
static short active_camo_screen_flash_fade_function = 0;
|
|
static real active_camo_screen_flash_alpha = 0.f;
|
|
static real active_camo_screen_flash_blue = 0.f;
|
|
boolean debug_render_player_teleport = FALSE;
|
|
static struct players_static_data players_static_data =
|
|
{
|
|
{ "players_update_before_game", NONE, TRUE },
|
|
{ "players_update_after_game", NONE, TRUE },
|
|
{
|
|
{ 5, 0, 1.0f, 0.8f, 0.8f, 2.0f },
|
|
{ 2, 0, 1.0f, 0.35f, 0.35f, 2.0f },
|
|
},
|
|
};
|
|
|
|
/* January owns this nine-point co-op teleport search table in Players.obj
|
|
.rdata (0x6C bytes, no relocations); HCEA independently authenticates the
|
|
name, element type, and values. */
|
|
static real_point3d const adjustment_weights[9] =
|
|
{
|
|
{ { 1.f, 0.f, 0.f } },
|
|
{ { 0.f, 1.f, 0.f } },
|
|
{ { 0.f, -1.f, 0.f } },
|
|
{ { 0.70710677f, -0.70710677f, 0.f } },
|
|
{ { 0.70710677f, 0.70710677f, 0.f } },
|
|
{ { 0.57735026f, 0.57735026f, 0.57735026f } },
|
|
{ { 0.57735026f, 0.57735026f, -0.57735026f } },
|
|
{ { 0.57735026f, -0.57735026f, 0.57735026f } },
|
|
{ { 0.57735026f, -0.57735026f, -0.57735026f } },
|
|
};
|
|
|
|
#define PLAYERS_UPDATE_BEFORE_GAME_PROFILE \
|
|
(players_static_data.update_before_game_profile)
|
|
#define PLAYERS_UPDATE_AFTER_GAME_PROFILE \
|
|
(players_static_data.update_after_game_profile)
|
|
|
|
/* ---------- public code */
|
|
|
|
void players_initialize(
|
|
void)
|
|
{
|
|
player_data = game_state_data_new(
|
|
"players",
|
|
/* a player's datum index is also its action slot in every update */
|
|
NETWORK_GAME_MAXIMUM_PLAYER_COUNT,
|
|
sizeof(struct player_datum));
|
|
team_data = game_state_data_new(
|
|
"teams",
|
|
16,
|
|
0x40);
|
|
players_globals = (struct players_globals *)game_state_malloc(
|
|
"players globals",
|
|
NULL,
|
|
sizeof(struct players_globals));
|
|
|
|
csmemset(
|
|
players_globals->local_players,
|
|
NONE,
|
|
sizeof(players_globals->local_players));
|
|
players_globals->unknown0 = NONE;
|
|
players_globals->local_player_count = 0;
|
|
|
|
player_control_initialize();
|
|
|
|
return;
|
|
}
|
|
|
|
void players_initialize_for_new_map(
|
|
void)
|
|
{
|
|
player_control_dispose();
|
|
csmemset(
|
|
players_globals,
|
|
0,
|
|
sizeof(struct players_globals));
|
|
csmemset(
|
|
players_globals->local_players,
|
|
NONE,
|
|
sizeof(players_globals->local_players));
|
|
csmemset(
|
|
players_globals->dead_units,
|
|
NONE,
|
|
sizeof(players_globals->dead_units));
|
|
players_globals->unknown0 = NONE;
|
|
players_globals->input_disabled = FALSE;
|
|
players_globals->double_speed_ticks = 0;
|
|
players_globals->all_dead = FALSE;
|
|
players_globals->pending_teleport_starting_location_index = NONE;
|
|
players_globals->respawn_failure = 0;
|
|
data_make_valid(player_data);
|
|
data_make_valid(team_data);
|
|
csmemset(
|
|
machine_to_player_table,
|
|
NONE,
|
|
sizeof(machine_to_player_table));
|
|
|
|
return;
|
|
}
|
|
|
|
void players_dispose_from_old_map(
|
|
void)
|
|
{
|
|
data_make_invalid(player_data);
|
|
data_make_invalid(team_data);
|
|
|
|
return;
|
|
}
|
|
|
|
void players_dispose(
|
|
void)
|
|
{
|
|
if (player_data)
|
|
player_data = NULL;
|
|
if (team_data)
|
|
team_data = NULL;
|
|
if (players_globals)
|
|
players_globals = NULL;
|
|
|
|
return;
|
|
}
|
|
|
|
long *machine_get_player_list(
|
|
long machine_index)
|
|
{
|
|
return machine_to_player_table[DATUM_INDEX_TO_ABSOLUTE_INDEX(machine_index)];
|
|
}
|
|
|
|
boolean local_player_exists(
|
|
long local_player_index)
|
|
{
|
|
boolean result;
|
|
struct player_datum *player;
|
|
struct data_iterator iterator;
|
|
|
|
result = FALSE;
|
|
data_iterator_new(
|
|
&iterator,
|
|
player_data);
|
|
while ((player = (struct player_datum *)data_iterator_next(&iterator)) != NULL)
|
|
{
|
|
if (player->local_player_index == local_player_index)
|
|
{
|
|
result = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
long find_unused_local_player_index(
|
|
void)
|
|
{
|
|
long index;
|
|
long result;
|
|
|
|
result = NONE;
|
|
for (index = 0; index < MAXIMUM_LOCAL_PLAYERS; index++)
|
|
{
|
|
if (input_has_gamepad((short)index) && !local_player_exists(index))
|
|
{
|
|
result = index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (result == NONE)
|
|
{
|
|
for (index = 0; index < MAXIMUM_LOCAL_PLAYERS; index++)
|
|
{
|
|
if (!local_player_exists(index))
|
|
{
|
|
result = index;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void player_delete(
|
|
long player_index)
|
|
{
|
|
datum_delete(
|
|
player_data,
|
|
player_index);
|
|
|
|
return;
|
|
}
|
|
|
|
short players_get_respawn_failure(
|
|
void)
|
|
{
|
|
return players_globals->respawn_failure;
|
|
}
|
|
|
|
long local_player_get_player_index(
|
|
short local_player_index)
|
|
{
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
943,
|
|
local_player_index>=NONE && local_player_index<MAXIMUM_NUMBER_OF_LOCAL_PLAYERS);
|
|
|
|
if (local_player_index == NONE)
|
|
return NONE;
|
|
|
|
return players_globals->local_players[local_player_index];
|
|
}
|
|
|
|
void local_player_set_player_index(
|
|
short local_player_index,
|
|
long player_index)
|
|
{
|
|
long previous_player_index;
|
|
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
952,
|
|
local_player_index>=0 && local_player_index<MAXIMUM_NUMBER_OF_LOCAL_PLAYERS);
|
|
|
|
previous_player_index = players_globals->local_players[local_player_index];
|
|
if (previous_player_index != NONE)
|
|
player_get(previous_player_index)->local_player_index = NONE;
|
|
|
|
players_globals->local_players[local_player_index] = player_index;
|
|
|
|
if (player_index != NONE)
|
|
{
|
|
player_get(players_globals->local_players[local_player_index])->local_player_index =
|
|
local_player_index;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
short local_player_count(
|
|
void)
|
|
{
|
|
return players_globals->local_player_count;
|
|
}
|
|
|
|
short local_player_get_next(
|
|
short local_player_index)
|
|
{
|
|
short result;
|
|
short index;
|
|
|
|
result = NONE;
|
|
for (index = 0; index < MAXIMUM_LOCAL_PLAYERS; index++)
|
|
{
|
|
if (players_globals->local_players[index] != NONE &&
|
|
index > local_player_index &&
|
|
(index < result || result == NONE))
|
|
{
|
|
result = index;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
long player_index_from_unit_index(
|
|
long unit_index)
|
|
{
|
|
long player_index;
|
|
struct player_datum *player;
|
|
struct data_iterator iterator;
|
|
|
|
player_index = NONE;
|
|
data_iterator_new(
|
|
&iterator,
|
|
player_data);
|
|
while ((player = (struct player_datum *)data_iterator_next(&iterator)) != NULL)
|
|
{
|
|
if (player->unit_index == unit_index)
|
|
player_index = iterator.datum_index;
|
|
}
|
|
|
|
return player_index;
|
|
}
|
|
|
|
void player_died(
|
|
long player_index)
|
|
{
|
|
struct player_datum *player;
|
|
struct player_datum *other_player;
|
|
struct data_iterator iterator;
|
|
|
|
player = player_get(player_index);
|
|
player->dead_unit_index = player->unit_index;
|
|
player->unit_index = NONE;
|
|
if (player->local_player_index != NONE)
|
|
{
|
|
player_control_new_unit(
|
|
player->local_player_index,
|
|
NONE);
|
|
}
|
|
|
|
players_globals->all_dead = TRUE;
|
|
data_iterator_new(
|
|
&iterator,
|
|
player_data);
|
|
while ((other_player = (struct player_datum *)data_iterator_next(&iterator)) != NULL)
|
|
{
|
|
if (other_player->unit_index != NONE)
|
|
players_globals->all_dead = FALSE;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void players_set_local_player_unit(
|
|
short local_player_index,
|
|
long unit_index)
|
|
{
|
|
long old_unit_index;
|
|
long player_index;
|
|
struct player_datum *player;
|
|
struct unit_datum *unit;
|
|
|
|
old_unit_index = player_control_get_unit_index(local_player_index);
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
1060,
|
|
game_connection()==_game_connection_local);
|
|
|
|
if (old_unit_index != NONE)
|
|
{
|
|
unit = unit_get(old_unit_index);
|
|
unit->unit.player_index = NONE;
|
|
unit_set_actively_controlled(old_unit_index, FALSE);
|
|
}
|
|
|
|
if (unit_index != NONE)
|
|
{
|
|
unit = unit_get(unit_index);
|
|
unit_set_actively_controlled(unit_index, TRUE);
|
|
unit->unit.player_index = local_player_get_player_index(local_player_index);
|
|
}
|
|
|
|
player_index = local_player_get_player_index(local_player_index);
|
|
player = player_get(player_index);
|
|
player->unit_index = unit_index;
|
|
player->dead_unit_index = NONE;
|
|
player_control_new_unit(local_player_index, unit_index);
|
|
|
|
return;
|
|
}
|
|
|
|
boolean players_are_all_dead(
|
|
void)
|
|
{
|
|
return players_globals->all_dead;
|
|
}
|
|
|
|
unsigned long const *players_get_combined_pvs_local(
|
|
void)
|
|
{
|
|
return players_globals->combined_pvs_local;
|
|
}
|
|
|
|
unsigned long const *players_get_combined_pvs(
|
|
void)
|
|
{
|
|
return players_globals->combined_pvs;
|
|
}
|
|
|
|
void player_input_enable(
|
|
boolean enable)
|
|
{
|
|
players_globals->input_disabled = !enable;
|
|
|
|
return;
|
|
}
|
|
|
|
boolean player_input_enabled(
|
|
void)
|
|
{
|
|
return !players_globals->input_disabled;
|
|
}
|
|
|
|
boolean any_player_is_in_the_air(
|
|
void)
|
|
{
|
|
struct data_iterator iterator;
|
|
struct player_datum *player;
|
|
struct unit_datum *unit;
|
|
struct object_datum *root_object;
|
|
struct players_vehicle_datum *vehicle;
|
|
struct unit_definition *vehicle_definition;
|
|
long root_object_index;
|
|
|
|
data_iterator_new(&iterator, player_data);
|
|
while (player = data_iterator_next(&iterator))
|
|
{
|
|
if (player->unit_index == NONE)
|
|
continue;
|
|
|
|
unit = unit_get(player->unit_index);
|
|
root_object_index = object_get_ultimate_parent(player->unit_index);
|
|
root_object = object_get(root_object_index);
|
|
if (TEST_FLAG(root_object->object.flags, _object_outside_of_map_bit))
|
|
return TRUE;
|
|
|
|
if (unit->object.parent_object_index == NONE)
|
|
{
|
|
if (unit->object.type == _object_type_biped)
|
|
{
|
|
if (biped_flying_through_air(player->unit_index))
|
|
return TRUE;
|
|
continue;
|
|
}
|
|
if (unit->object.type != _object_type_vehicle)
|
|
continue;
|
|
vehicle = (struct players_vehicle_datum *)unit;
|
|
}
|
|
else
|
|
{
|
|
vehicle = (struct players_vehicle_datum *)object_try_and_get_and_verify_type(
|
|
unit->object.parent_object_index,
|
|
_object_mask_vehicle);
|
|
if (!vehicle)
|
|
continue;
|
|
vehicle_definition = vehicle_definition_get(vehicle->definition_index);
|
|
if (!TEST_FLAG(
|
|
vehicle_definition->unit.flags,
|
|
_unit_causes_passenger_dialogue_bit))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (vehicle->vehicle.unknown_state > 2)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
boolean any_player_is_dead(
|
|
void)
|
|
{
|
|
struct player_datum *player;
|
|
struct data_iterator iterator;
|
|
|
|
data_iterator_new(
|
|
&iterator,
|
|
player_data);
|
|
while ((player = (struct player_datum *)data_iterator_next(&iterator)) != NULL)
|
|
{
|
|
if (player->unit_index == NONE)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void player_control_fix_for_loaded_game_state(
|
|
void)
|
|
{
|
|
struct player_datum *player;
|
|
long player_index;
|
|
short controller_local_player_index;
|
|
short local_player_index;
|
|
|
|
local_player_index = 0;
|
|
controller_local_player_index =
|
|
player_ui_get_single_player_local_player_controller(0);
|
|
if (controller_local_player_index == NONE)
|
|
controller_local_player_index = 0;
|
|
|
|
if (local_player_get_player_index(controller_local_player_index) == NONE)
|
|
{
|
|
if (player_spawn_count == 1)
|
|
{
|
|
for (local_player_index = 0;
|
|
local_player_index < MAXIMUM_LOCAL_PLAYERS;
|
|
local_player_index++)
|
|
{
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
943,
|
|
local_player_index>=NONE &&
|
|
local_player_index<MAXIMUM_NUMBER_OF_LOCAL_PLAYERS);
|
|
player_index = local_player_index == NONE
|
|
? NONE
|
|
: players_globals->local_players[local_player_index];
|
|
if (player_index != NONE)
|
|
{
|
|
player = player_get(player_index);
|
|
local_player_set_player_index(local_player_index, NONE);
|
|
player_control_new_unit(local_player_index, NONE);
|
|
local_player_set_player_index(
|
|
controller_local_player_index,
|
|
player_index);
|
|
player_control_new_unit(
|
|
controller_local_player_index,
|
|
player->unit_index);
|
|
hud_fix_weapon_data(
|
|
local_player_index,
|
|
controller_local_player_index);
|
|
hud_fix_unit_data(
|
|
local_player_index,
|
|
controller_local_player_index);
|
|
error(2, "corrected player control for restored saved game");
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (local_player_index == MAXIMUM_LOCAL_PLAYERS)
|
|
{
|
|
error(2,
|
|
"failed to correct player control for restored saved game... probably won't be able to control the player");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
error(2,
|
|
"tried to fix broken player control for a game w/ player_spawn_count= %d... but we don't allow restored games for anything other than player_spawn_count= 1",
|
|
player_spawn_count);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
short player_get_starting_location_count(
|
|
void)
|
|
{
|
|
struct scenario *scenario;
|
|
struct encounter_definition *encounter;
|
|
short starting_location_count;
|
|
|
|
scenario = global_scenario_get();
|
|
starting_location_count = scenario->players.count;
|
|
if (ai_debug.selected_squad_index != NONE)
|
|
{
|
|
encounter = TAG_BLOCK_GET_ELEMENT(
|
|
&scenario->ai_encounters,
|
|
DATUM_INDEX_TO_ABSOLUTE_INDEX(ai_debug.selected_squad_index),
|
|
struct encounter_definition);
|
|
if (encounter->player_starting_locations.count > 0)
|
|
starting_location_count = encounter->player_starting_locations.count;
|
|
}
|
|
|
|
return starting_location_count;
|
|
}
|
|
|
|
struct player_starting_location *player_get_starting_location(
|
|
short starting_location_index)
|
|
{
|
|
struct scenario *scenario;
|
|
struct encounter_definition *encounter;
|
|
struct player_starting_location *starting_location;
|
|
short structure_bsp_reference_index;
|
|
|
|
scenario = global_scenario_get();
|
|
starting_location = NULL;
|
|
if (starting_location_index >= 0 && starting_location_index < scenario->players.count)
|
|
{
|
|
starting_location = TAG_BLOCK_GET_ELEMENT(
|
|
&scenario->players,
|
|
starting_location_index,
|
|
struct player_starting_location);
|
|
}
|
|
|
|
if (ai_debug.selected_squad_index != NONE)
|
|
{
|
|
encounter = TAG_BLOCK_GET_ELEMENT(
|
|
&scenario->ai_encounters,
|
|
DATUM_INDEX_TO_ABSOLUTE_INDEX(ai_debug.selected_squad_index),
|
|
struct encounter_definition);
|
|
|
|
if (starting_location_index >= 0 &&
|
|
starting_location_index < encounter->player_starting_locations.count)
|
|
{
|
|
starting_location = TAG_BLOCK_GET_ELEMENT(
|
|
&encounter->player_starting_locations,
|
|
starting_location_index,
|
|
struct player_starting_location);
|
|
structure_bsp_reference_index = encounter->runtime_structure_bsp_reference_index;
|
|
if (structure_bsp_reference_index >= 0 &&
|
|
structure_bsp_reference_index < scenario->structure_bsp_references.count)
|
|
{
|
|
starting_location->structure_bsp_reference_index = structure_bsp_reference_index;
|
|
}
|
|
}
|
|
}
|
|
|
|
return starting_location;
|
|
}
|
|
|
|
static long is_player_in_trigger(
|
|
short bsp_switch_trigger_volume_index,
|
|
long object_index)
|
|
{
|
|
struct scenario_bsp_switch_trigger_volume *bsp_switch_trigger_volume;
|
|
|
|
if (bsp_switch_trigger_volume_index != NONE)
|
|
{
|
|
bsp_switch_trigger_volume = TAG_BLOCK_GET_ELEMENT(
|
|
&global_scenario_get()->bsp_switch_trigger_volumes,
|
|
bsp_switch_trigger_volume_index,
|
|
struct scenario_bsp_switch_trigger_volume);
|
|
if (scenario_trigger_volume_test_object(
|
|
bsp_switch_trigger_volume->trigger_volume_index,
|
|
object_index))
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void placement_data_set_change_color(
|
|
struct object_placement_data *placement_data,
|
|
real_rgb_color const *change_color)
|
|
{
|
|
placement_data->change_colors[_object_change_color_a] = *change_color;
|
|
placement_data->change_colors[_object_change_color_b] = *change_color;
|
|
placement_data->change_colors[_object_change_color_c] = *change_color;
|
|
placement_data->change_colors[_object_change_color_d] = *change_color;
|
|
|
|
return;
|
|
}
|
|
|
|
boolean valid_real_vector2d(
|
|
real_vector2d const *vector)
|
|
{
|
|
return valid_real(vector->i) && valid_real(vector->j);
|
|
}
|
|
|
|
static void machine_add_player(
|
|
long machine_index,
|
|
long player_index)
|
|
{
|
|
long *machine_player_list;
|
|
long machine_player_index;
|
|
|
|
machine_player_list =
|
|
machine_to_player_table[DATUM_INDEX_TO_ABSOLUTE_INDEX(machine_index)];
|
|
for (machine_player_index = 0;
|
|
machine_player_index < MAXIMUM_LOCAL_PLAYERS;
|
|
machine_player_index++)
|
|
{
|
|
if (machine_player_list[machine_player_index] == NONE)
|
|
{
|
|
machine_player_list[machine_player_index] = player_index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (machine_player_index == MAXIMUM_LOCAL_PLAYERS)
|
|
{
|
|
display_assert(
|
|
"failed to create a player",
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
240,
|
|
TRUE);
|
|
system_exit(-1);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* port: a player who left the game in progress is no longer its machine's
|
|
(its datum stays until the game ends): a machine that joins at the same index
|
|
fills the list from its first free entry, and the old players' entries left
|
|
it full, or its players taken for the old ones */
|
|
void machine_remove_player(
|
|
long player_index)
|
|
{
|
|
long machine_index;
|
|
long machine_player_index;
|
|
|
|
if (player_index == NONE)
|
|
return;
|
|
for (machine_index = 0; machine_index < MAXIMUM_NETWORK_MACHINE_COUNT; machine_index++)
|
|
{
|
|
for (machine_player_index = 0;
|
|
machine_player_index < MAXIMUM_LOCAL_PLAYERS;
|
|
machine_player_index++)
|
|
{
|
|
/* (by its absolute index: a datum's slot is one player's) */
|
|
if (machine_to_player_table[machine_index][machine_player_index] != NONE &&
|
|
DATUM_INDEX_TO_ABSOLUTE_INDEX(machine_to_player_table[machine_index][machine_player_index]) ==
|
|
DATUM_INDEX_TO_ABSOLUTE_INDEX(player_index))
|
|
{
|
|
machine_to_player_table[machine_index][machine_player_index] = NONE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
long player_new(
|
|
long machine_index,
|
|
long player_index,
|
|
short local_player_index,
|
|
struct network_player const *network_player)
|
|
{
|
|
struct player_datum *player;
|
|
struct player_datum *player2;
|
|
wchar_t const *player_name;
|
|
|
|
if (player_index == NONE)
|
|
player_index = datum_new(player_data);
|
|
else
|
|
player_index = datum_new_at_index(player_data, player_index);
|
|
|
|
match_assert("c:\\halo\\SOURCE\\game\\players.c", 309,
|
|
((local_player_index>=0) && (local_player_index<MAXIMUM_NUMBER_OF_LOCAL_PLAYERS)) || (local_player_index==NONE));
|
|
|
|
if (player_index != NONE)
|
|
{
|
|
player = player_get(player_index);
|
|
player_name = L"";
|
|
if (network_player)
|
|
player_name = network_player->name;
|
|
ustrncpy(player->name, player_name, 11);
|
|
player->name[11] = 0;
|
|
player->local_player_index = local_player_index;
|
|
player->unit_index = NONE;
|
|
player->dead_unit_index = NONE;
|
|
player->squad_index = NONE;
|
|
player->cluster_index = NONE;
|
|
player->aim_assist_unit_index = NONE;
|
|
player->speed_multiplier = 1.0f;
|
|
player->team_index = 1;
|
|
|
|
player2 = player_get(player_index);
|
|
player2->action_result = _player_action_result_reload;
|
|
player2->action_object_index = NONE;
|
|
player->quit_out_of_game_time = NONE;
|
|
player->quit_out_of_game = FALSE;
|
|
|
|
if (network_player)
|
|
csmemcpy(&player->network_player_data, network_player, sizeof(struct network_player));
|
|
}
|
|
|
|
machine_add_player(machine_index, player_index);
|
|
|
|
return player_index;
|
|
}
|
|
|
|
long find_best_starting_location_index(
|
|
long player_index)
|
|
{
|
|
struct scenario *scenario;
|
|
struct encounter_definition *encounter;
|
|
struct player_starting_location *starting_location;
|
|
short starting_location_count;
|
|
short starting_location_index;
|
|
short best_starting_location_index;
|
|
real starting_location_rating;
|
|
real best_starting_location_rating;
|
|
|
|
scenario = global_scenario_get();
|
|
starting_location_count = scenario->players.count;
|
|
if (ai_debug.selected_squad_index != NONE)
|
|
{
|
|
encounter = TAG_BLOCK_GET_ELEMENT(
|
|
&scenario->ai_encounters,
|
|
DATUM_INDEX_TO_ABSOLUTE_INDEX(ai_debug.selected_squad_index),
|
|
struct encounter_definition);
|
|
if (encounter->player_starting_locations.count > 0)
|
|
starting_location_count = encounter->player_starting_locations.count;
|
|
}
|
|
|
|
best_starting_location_index = NONE;
|
|
best_starting_location_rating = 0.0f;
|
|
for (starting_location_index = 0;
|
|
starting_location_index < starting_location_count;
|
|
starting_location_index++)
|
|
{
|
|
starting_location = player_get_starting_location(starting_location_index);
|
|
starting_location_rating = game_engine_get_starting_location_rating(
|
|
player_index,
|
|
starting_location);
|
|
starting_location_rating *= (real)pow(
|
|
(double)real_random_range(0.0f, 1.0f),
|
|
0.5);
|
|
if (starting_location_rating > best_starting_location_rating)
|
|
{
|
|
best_starting_location_rating = starting_location_rating;
|
|
best_starting_location_index = starting_location_index;
|
|
}
|
|
}
|
|
|
|
return best_starting_location_index;
|
|
}
|
|
|
|
static void player_spawn(
|
|
long player_index)
|
|
{
|
|
struct player_datum *player;
|
|
struct game_globals *game_globals;
|
|
struct player_datum *player2;
|
|
struct unit_datum *unit;
|
|
struct scenario *scenario;
|
|
struct game_globals_player_information *player_information;
|
|
struct game_globals_multiplayer_information *multiplayer_information;
|
|
struct player_starting_location *starting_location;
|
|
struct object_placement_data placement_data;
|
|
real_rgb_color change_color;
|
|
real_rgb_color change_color_storage;
|
|
long saved_unit_index;
|
|
long weapon_index;
|
|
long unit_definition_index;
|
|
long unit_index;
|
|
long starting_equipment_count;
|
|
short starting_location_index;
|
|
|
|
player = player_get(player_index);
|
|
saved_unit_index = NONE;
|
|
if (!game_engine_running() && player->local_player_index != NONE)
|
|
{
|
|
saved_unit_index =
|
|
players_globals->dead_units[player->local_player_index];
|
|
players_globals->dead_units[player->local_player_index] = NONE;
|
|
if (saved_unit_index != NONE)
|
|
{
|
|
unit = unit_get(saved_unit_index);
|
|
if (TEST_FLAG(unit->object.damage_flags, _object_dead_bit))
|
|
{
|
|
object_delete(saved_unit_index);
|
|
saved_unit_index = NONE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!game_engine_running() && saved_unit_index != NONE)
|
|
{
|
|
unit = unit_get(saved_unit_index);
|
|
weapon_index = unit_inventory_get_weapon(
|
|
saved_unit_index,
|
|
unit->unit.current_weapon_index);
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
0x73A,
|
|
player->local_player_index!=NONE);
|
|
object_activate(saved_unit_index);
|
|
object_set_visibility(saved_unit_index, TRUE);
|
|
players_set_local_player_unit(
|
|
player->local_player_index,
|
|
saved_unit_index);
|
|
if (weapon_index != NONE)
|
|
object_set_visibility(weapon_index, TRUE);
|
|
}
|
|
else
|
|
{
|
|
scenario = global_scenario_get();
|
|
if (ai_debug.selected_squad_index != NONE)
|
|
{
|
|
TAG_BLOCK_GET_ELEMENT(
|
|
&scenario->ai_encounters,
|
|
DATUM_INDEX_TO_ABSOLUTE_INDEX(ai_debug.selected_squad_index),
|
|
struct encounter_definition);
|
|
}
|
|
|
|
starting_location_index =
|
|
(short)find_best_starting_location_index(player_index);
|
|
if (starting_location_index != NONE)
|
|
{
|
|
game_globals = scenario_get_game_globals();
|
|
player_information = TAG_BLOCK_GET_ELEMENT(
|
|
&scenario_get_game_globals()->player_information,
|
|
0,
|
|
struct game_globals_player_information);
|
|
if (player_information->player_unit.index != NONE)
|
|
{
|
|
starting_location =
|
|
player_get_starting_location(starting_location_index);
|
|
if (game_engine_running())
|
|
{
|
|
multiplayer_information = TAG_BLOCK_GET_ELEMENT(
|
|
&game_globals->multiplayer_information,
|
|
0,
|
|
struct game_globals_multiplayer_information);
|
|
unit_definition_index = multiplayer_information->unit.index;
|
|
}
|
|
else
|
|
{
|
|
unit_definition_index = player_information->player_unit.index;
|
|
}
|
|
|
|
object_placement_data_new(
|
|
&placement_data,
|
|
unit_definition_index,
|
|
NONE);
|
|
placement_data.position = starting_location->position;
|
|
vector3d_from_angle(
|
|
&placement_data.forward,
|
|
starting_location->facing);
|
|
placement_data.up = *global_up3d;
|
|
change_color = *game_engine_player_get_change_color(
|
|
&change_color_storage,
|
|
player_index);
|
|
placement_data_set_change_color(
|
|
&placement_data,
|
|
&change_color);
|
|
unit_index = object_new(&placement_data);
|
|
if (unit_index != NONE)
|
|
{
|
|
unit = unit_try_and_get(unit_index);
|
|
if (unit != NULL)
|
|
{
|
|
player2 = player_get(player_index);
|
|
unit->object.owner_player_index = player_index;
|
|
unit->object.owner_team_index = (short)player2->team_index;
|
|
unit->unit.player_index = player_index;
|
|
player2->unit_index = unit_index;
|
|
unit_set_actively_controlled(unit_index, TRUE);
|
|
if (player2->local_player_index != NONE)
|
|
{
|
|
player_control_new_unit(
|
|
player2->local_player_index,
|
|
unit_index);
|
|
}
|
|
|
|
if (!game_engine_running())
|
|
{
|
|
scenario = global_scenario_get();
|
|
starting_equipment_count =
|
|
*(long *)((byte *)scenario + 0x348);
|
|
if (starting_equipment_count > 1 &&
|
|
*(short *)((byte *)player2 + 0xAA) > 0)
|
|
{
|
|
player_add_equipment(
|
|
player2->unit_index,
|
|
1,
|
|
TRUE);
|
|
}
|
|
else if (starting_equipment_count != 0)
|
|
{
|
|
player_add_equipment(
|
|
player2->unit_index,
|
|
0,
|
|
TRUE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
csmemset(
|
|
player->powerup_durations,
|
|
0,
|
|
sizeof(player->powerup_durations));
|
|
player2 = player_get(player_index);
|
|
player2->action_result = _player_action_result_reload;
|
|
player2->action_object_index = NONE;
|
|
if (player->local_player_index != NONE)
|
|
observer_obsolete_position(player->local_player_index);
|
|
|
|
return;
|
|
}
|
|
|
|
/* the distributed netcode (port/linux/game/network_distributed.c): a
|
|
client's player takes the unit the host spawned it with (the host's object,
|
|
at the host's index, with the host's weapons), as player_spawn gives a
|
|
player the unit it makes */
|
|
void network_player_attach_unit(
|
|
long player_index,
|
|
long unit_index)
|
|
{
|
|
struct player_datum *player = player_get(player_index);
|
|
struct unit_datum *unit = unit_get(unit_index);
|
|
|
|
unit->object.owner_player_index = player_index;
|
|
unit->object.owner_team_index = (short)player->team_index;
|
|
unit->unit.player_index = player_index;
|
|
player->unit_index = unit_index;
|
|
unit_set_actively_controlled(unit_index, TRUE);
|
|
if (player->local_player_index != NONE)
|
|
player_control_new_unit(player->local_player_index, unit_index);
|
|
csmemset(player->powerup_durations, 0, sizeof(player->powerup_durations));
|
|
player->action_result = _player_action_result_reload;
|
|
player->action_object_index = NONE;
|
|
if (player->local_player_index != NONE)
|
|
observer_obsolete_position(player->local_player_index);
|
|
}
|
|
|
|
/* ... and shows what the host says its player picked up (the host decides
|
|
pickups): the HUD's message, the pickup's sound, a powerup's screen flash */
|
|
void network_player_show_pickup(
|
|
long player_index,
|
|
short kind,
|
|
long definition_index,
|
|
short count)
|
|
{
|
|
struct player_datum *player = player_get(player_index);
|
|
boolean tag_index_is_group(long tag_index, long group_tag);
|
|
|
|
if (player->local_player_index == NONE)
|
|
return;
|
|
switch (kind)
|
|
{
|
|
case _network_pickup_weapon:
|
|
if (tag_index_is_group(definition_index, WEAPON_DEFINITION_TAG))
|
|
{
|
|
hud_picked_up_weapon(player->local_player_index, definition_index);
|
|
if (player->unit_index != NONE)
|
|
player_control_unzoom(player->unit_index);
|
|
}
|
|
break;
|
|
case _network_pickup_ammunition:
|
|
if (tag_index_is_group(definition_index, WEAPON_DEFINITION_TAG))
|
|
{
|
|
struct weapon_definition *weapon_definition = weapon_definition_get(definition_index);
|
|
|
|
hud_picked_up_ammunition(player->local_player_index, definition_index, count);
|
|
if (weapon_definition->weapon.pickup_sound.index != NONE)
|
|
unspatialized_impulse_sound_new(weapon_definition->weapon.pickup_sound.index, 1.0f);
|
|
}
|
|
break;
|
|
case _network_pickup_grenade:
|
|
if (tag_index_is_group(definition_index, EQUIPMENT_DEFINITION_TAG))
|
|
{
|
|
hud_picked_up_grenade(player->local_player_index, definition_index);
|
|
equipment_definition_handle_pickup(definition_index);
|
|
}
|
|
break;
|
|
case _network_pickup_equipment:
|
|
if (tag_index_is_group(definition_index, EQUIPMENT_DEFINITION_TAG))
|
|
hud_picked_up_powerup(player->local_player_index, definition_index);
|
|
break;
|
|
case _network_pickup_powerup:
|
|
if (tag_index_is_group(definition_index, EQUIPMENT_DEFINITION_TAG))
|
|
{
|
|
/* (as player_handle_powerup_equipment shows its player) */
|
|
switch (equipment_definition_get(definition_index)->equipment.powerup_type)
|
|
{
|
|
case _equipment_powerup_overshield: player_over_shield_screen_effect(player_index); break;
|
|
case _equipment_powerup_health: player_health_pack_screen_effect(player_index); break;
|
|
case _equipment_powerup_active_camouflage: player_active_camo_screen_effect(player_index); break;
|
|
}
|
|
hud_picked_up_powerup(player->local_player_index, definition_index);
|
|
equipment_definition_handle_pickup(definition_index);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* the host: a player on another machine presses the action button with
|
|
nothing here to do (to pick up, swap for, enter): what the host has around
|
|
them, to the log, at most every few seconds (a client sees what to pick up
|
|
where the host has nothing) */
|
|
static void network_player_log_idle_action(
|
|
long player_index,
|
|
unsigned long control_flags)
|
|
{
|
|
/* (a time past this game's is the last game's: game time restarts) */
|
|
static long logged_times[HALO_PORT_MAXIMUM_NETWORK_PLAYERS];
|
|
struct player_datum *player = player_get(player_index);
|
|
long absolute_index = DATUM_INDEX_TO_ABSOLUTE_INDEX(player_index);
|
|
struct object_iterator iterator;
|
|
struct unit_datum *unit;
|
|
long nearest_index = NONE;
|
|
real nearest_distance = 0.0f;
|
|
|
|
if (game_connection() != _game_connection_network_server ||
|
|
player->local_player_index != NONE || player->action_result != _player_action_result_reload ||
|
|
!(control_flags & (FLAG(_unit_control_action_bit) | FLAG(_unit_control_swap_weapons_bit))) ||
|
|
absolute_index >= HALO_PORT_MAXIMUM_NETWORK_PLAYERS ||
|
|
(logged_times[absolute_index] && logged_times[absolute_index] <= game_time_get() &&
|
|
game_time_get() - logged_times[absolute_index] < 3 * TICKS_PER_SECOND))
|
|
{
|
|
return;
|
|
}
|
|
logged_times[absolute_index] = game_time_get();
|
|
unit = unit_get(player->unit_index);
|
|
object_iterator_new(&iterator, _object_mask_weapon | _object_mask_equipment, 0);
|
|
while (object_iterator_next(&iterator))
|
|
{
|
|
struct object_datum *object = object_get(iterator.index);
|
|
real distance;
|
|
|
|
if (object->object.parent_object_index != NONE || !TEST_FLAG(object->object.flags, _object_connected_to_map_bit))
|
|
continue;
|
|
distance = distance3d(&unit->object.position, &object->object.position);
|
|
if (nearest_index == NONE || distance < nearest_distance)
|
|
{
|
|
nearest_index = iterator.index;
|
|
nearest_distance = distance;
|
|
}
|
|
}
|
|
error(2, "distributed: player %ld pressed action with nothing to pick up here: unit %lx at %.2f %.2f %.2f, "
|
|
"nearest item %lx %s at %.2f",
|
|
absolute_index, player->unit_index, unit->object.position.x, unit->object.position.y, unit->object.position.z,
|
|
nearest_index, nearest_index != NONE ? tag_get_name(object_get(nearest_index)->definition_index) : "",
|
|
nearest_distance);
|
|
/* ... and the nearest vehicle: how far its nearest seat's entrance is
|
|
(within 1.0 to get in), and whether the unit moves or the vehicle turns
|
|
too fast (player_examine_nearby_vehicle) */
|
|
{
|
|
struct object_iterator vehicles;
|
|
long vehicle_index = NONE;
|
|
real vehicle_distance = 0.0f;
|
|
|
|
object_iterator_new(&vehicles, _object_mask_vehicle, 0);
|
|
while (object_iterator_next(&vehicles))
|
|
{
|
|
real distance = distance3d(&unit->object.position, &object_get(vehicles.index)->object.position);
|
|
|
|
if (vehicle_index == NONE || distance < vehicle_distance)
|
|
{
|
|
vehicle_index = vehicles.index;
|
|
vehicle_distance = distance;
|
|
}
|
|
}
|
|
if (vehicle_index != NONE && vehicle_distance < 10.0f)
|
|
{
|
|
struct unit_datum *vehicle = unit_get(vehicle_index);
|
|
short seat_count = unit_definition_get(vehicle->definition_index)->unit.seats.count;
|
|
short seat_index;
|
|
real entrance_distance = REAL_MAX;
|
|
|
|
for (seat_index = 0; seat_index < seat_count; seat_index++)
|
|
{
|
|
real_point3d entrance;
|
|
real_point3d seat;
|
|
|
|
if (unit_get_seat_entrance_point(player->unit_index, vehicle_index, seat_index, &entrance, &seat, NULL))
|
|
{
|
|
entrance_distance = MIN(entrance_distance,
|
|
MIN(distance3d(&entrance, &unit->object.bounding_sphere_center),
|
|
distance3d(&seat, &unit->object.bounding_sphere_center)));
|
|
}
|
|
}
|
|
error(2, "distributed: ... nearest vehicle %lx %s at %.2f %.2f %.2f (%.2f away), seat entrance %.2f away, "
|
|
"unit speed %.3f, vehicle turning %.3f, up %.2f",
|
|
vehicle_index, tag_get_name(vehicle->definition_index), vehicle->object.position.x,
|
|
vehicle->object.position.y, vehicle->object.position.z, vehicle_distance, entrance_distance,
|
|
magnitude3d(&unit->object.translational_velocity), magnitude3d(&vehicle->object.angular_velocity),
|
|
vehicle->object.up.k);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ... and gives up the one it has (the host's unit for it is another) */
|
|
void network_player_detach_unit(
|
|
long player_index)
|
|
{
|
|
struct player_datum *player = player_get(player_index);
|
|
struct unit_datum *unit = player->unit_index != NONE ?
|
|
(struct unit_datum *)object_try_and_get_and_verify_type(player->unit_index, _object_mask_unit) : NULL;
|
|
|
|
if (unit)
|
|
{
|
|
unit->unit.player_index = NONE;
|
|
unit_set_actively_controlled(player->unit_index, FALSE);
|
|
}
|
|
player->unit_index = NONE;
|
|
if (player->local_player_index != NONE)
|
|
player_control_new_unit(player->local_player_index, NONE);
|
|
}
|
|
|
|
/* Exact: January emits this private dead-unit replacement helper from the
|
|
reconstructed player_teleport_internal caller below. */
|
|
static void player_pseudo_kill(
|
|
long player_index,
|
|
long replacement_dead_unit_index)
|
|
{
|
|
struct player_datum *player;
|
|
struct unit_datum *unit;
|
|
long dead_unit_index;
|
|
long weapon_index;
|
|
|
|
player = player_get(player_index);
|
|
if (player->unit_index != NONE)
|
|
{
|
|
if (game_engine_can_score())
|
|
{
|
|
game_statistics_record_kill(
|
|
player->unit_index,
|
|
NONE,
|
|
NONE,
|
|
NONE);
|
|
}
|
|
|
|
players_globals->dead_units[player->local_player_index] =
|
|
player->unit_index;
|
|
player_died(player_index);
|
|
|
|
dead_unit_index =
|
|
players_globals->dead_units[player->local_player_index];
|
|
unit = unit_get(dead_unit_index);
|
|
weapon_index = unit_inventory_get_weapon(
|
|
dead_unit_index,
|
|
unit_get(dead_unit_index)->unit.current_weapon_index);
|
|
unit->unit.player_index = NONE;
|
|
object_deactivate(dead_unit_index);
|
|
object_set_visibility(dead_unit_index, FALSE);
|
|
if (weapon_index != NONE)
|
|
object_set_visibility(weapon_index, FALSE);
|
|
|
|
if (replacement_dead_unit_index != NONE)
|
|
player->dead_unit_index = replacement_dead_unit_index;
|
|
players_globals->all_dead = FALSE;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* Matching status is tracked in the Players object log; this is the ordinary
|
|
combined-PVS calculation used by the game loop. */
|
|
/* Residual instruction scheduling is recorded in the matching log. */
|
|
static void players_compute_combined_pvs(
|
|
unsigned long *combined_pvs,
|
|
boolean local_player_only)
|
|
{
|
|
struct data_iterator iterator;
|
|
struct player_datum *player;
|
|
struct object_datum *root_object;
|
|
struct structure_bsp *structure_bsp;
|
|
struct structure_leaf *leaf;
|
|
long leaf_index;
|
|
short activating_cluster_index;
|
|
long root_object_index;
|
|
short cluster_index;
|
|
|
|
structure_bsp = global_structure_bsp_get();
|
|
csmemset(combined_pvs, 0, 0x40);
|
|
if (!game_in_editor())
|
|
{
|
|
activating_cluster_index = objects_get_activating_cluster_index();
|
|
data_iterator_new(&iterator, player_data);
|
|
while (player = data_iterator_next(&iterator))
|
|
{
|
|
if (local_player_only && player->local_player_index == NONE)
|
|
continue;
|
|
|
|
if (player->unit_index != NONE)
|
|
{
|
|
root_object_index =
|
|
object_get_ultimate_parent(player->unit_index);
|
|
root_object = object_get(root_object_index);
|
|
if (root_object->object.location.cluster_index != NONE)
|
|
{
|
|
player->cluster_index =
|
|
root_object->object.location.cluster_index;
|
|
}
|
|
}
|
|
|
|
cluster_index = player->cluster_index;
|
|
if (cluster_index != NONE)
|
|
{
|
|
bit_vector_or(
|
|
(short)structure_bsp->clusters.count,
|
|
combined_pvs,
|
|
structure_bsp_get_cluster_pvs(
|
|
structure_bsp,
|
|
cluster_index),
|
|
combined_pvs);
|
|
}
|
|
}
|
|
|
|
if (activating_cluster_index != NONE)
|
|
{
|
|
bit_vector_or(
|
|
(short)structure_bsp->clusters.count,
|
|
combined_pvs,
|
|
structure_bsp_get_cluster_pvs(
|
|
structure_bsp,
|
|
activating_cluster_index),
|
|
combined_pvs);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
leaf_index = scenario_leaf_index_from_point(
|
|
&observer_get_camera(0)->position);
|
|
if (leaf_index != NONE)
|
|
{
|
|
leaf = TAG_BLOCK_GET_ELEMENT(
|
|
&global_structure_bsp_get()->leaves,
|
|
scenario_leaf_index_from_point(
|
|
&observer_get_camera(0)->position) & LONG_MAX,
|
|
struct structure_leaf);
|
|
cluster_index = leaf->cluster_index;
|
|
if (cluster_index != NONE)
|
|
{
|
|
bit_vector_or(
|
|
(short)structure_bsp->clusters.count,
|
|
combined_pvs,
|
|
structure_bsp_get_cluster_pvs(
|
|
structure_bsp,
|
|
cluster_index),
|
|
combined_pvs);
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static boolean player_handle_action(
|
|
long player_index)
|
|
{
|
|
struct player_datum *player;
|
|
struct unit_datum *unit;
|
|
struct unit_datum *nearby_unit;
|
|
struct item_datum *equipment;
|
|
struct players_vehicle_datum *target_vehicle;
|
|
real_matrix4x3 player_matrix;
|
|
real_matrix4x3 target_matrix;
|
|
real_point3d *player_position;
|
|
real_point3d *target_position;
|
|
real_vector3d delta;
|
|
real dot;
|
|
char approach_type;
|
|
boolean result;
|
|
|
|
player = player_get(player_index);
|
|
unit_get(player->unit_index);
|
|
result = FALSE;
|
|
switch (player->action_result)
|
|
{
|
|
case _player_action_result_touch_device:
|
|
device_touched(player->action_object_index, player->unit_index);
|
|
result = TRUE;
|
|
break;
|
|
|
|
case _player_action_result_swap_for_powerup:
|
|
/* port: a distributed client's inventories are the host's (the
|
|
powerup is swapped where the host decides pickups, and the relayed
|
|
action of a remote player reaches here too): it swaps nothing */
|
|
if (!players_decide_pickups())
|
|
{
|
|
result = TRUE;
|
|
break;
|
|
}
|
|
unit_drop_current_equipment(player->unit_index);
|
|
if (unit_add_equipment_to_inventory(
|
|
player->unit_index,
|
|
player->action_object_index,
|
|
FALSE))
|
|
{
|
|
equipment = equipment_get(player->action_object_index);
|
|
hud_picked_up_powerup(
|
|
player->local_player_index,
|
|
equipment->definition_index);
|
|
player_network_picked_up(player, player_index, _network_pickup_equipment, equipment->definition_index, 0);
|
|
}
|
|
result = TRUE;
|
|
break;
|
|
|
|
case _player_action_result_enter_vehicle:
|
|
case _player_action_result_evict_from_vehicle:
|
|
{
|
|
long nearby_unit_index;
|
|
|
|
if (unit_can_enter_seat(
|
|
player->unit_index,
|
|
player->action_object_index,
|
|
player->action_seat_index,
|
|
(nearby_unit_index = NONE, &nearby_unit_index)))
|
|
{
|
|
unit_enter_seat(
|
|
player->unit_index,
|
|
player->action_object_index,
|
|
player->action_seat_index);
|
|
}
|
|
else if (nearby_unit_index != NONE)
|
|
{
|
|
nearby_unit = unit_get(nearby_unit_index);
|
|
if (nearby_unit->unit.actor_index != NONE)
|
|
{
|
|
ai_try_vehicle_eviction(
|
|
nearby_unit->unit.actor_index,
|
|
player->unit_index,
|
|
TRUE);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case _player_action_result_flip_vehicle:
|
|
unit = unit_get(player->unit_index);
|
|
target_vehicle = (struct players_vehicle_datum *)vehicle_get(
|
|
player->action_object_index);
|
|
unit->unit.last_vehicle_index = player->action_object_index;
|
|
unit->unit.game_time_at_last_vehicle_exit = game_time_get();
|
|
|
|
if (fabs(target_vehicle->object.forward.k) > 0.70710677f)
|
|
{
|
|
approach_type = (target_vehicle->object.forward.k < 0.f) + 3;
|
|
}
|
|
else
|
|
{
|
|
target_position = &object_get_world_matrix(
|
|
player->action_object_index,
|
|
&target_matrix)->position;
|
|
player_position = &object_get_world_matrix(
|
|
player->unit_index,
|
|
&player_matrix)->position;
|
|
delta.i = target_position->x - player_position->x;
|
|
delta.j = target_position->y - player_position->y;
|
|
delta.k = target_position->z - player_position->z;
|
|
cross_product3d(global_up3d, &delta, &delta);
|
|
dot = dot_product3d(&delta, &target_vehicle->object.forward);
|
|
approach_type = (dot > 0.f) + 1;
|
|
}
|
|
|
|
SET_FLAG(target_vehicle->vehicle.flags, _vehicle_upending_bit, TRUE);
|
|
target_vehicle->vehicle.approach_type = approach_type;
|
|
target_vehicle->vehicle.unknown42A = 0;
|
|
result = TRUE;
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Matching status is tracked in the Players object log. */
|
|
static boolean player_teleport_internal(
|
|
long player_index,
|
|
long source_unit_index,
|
|
real_point3d const *position)
|
|
{
|
|
struct player_datum *player = player_get(player_index);
|
|
long player_unit_index = player->unit_index;
|
|
struct biped_datum *biped = biped_get(player_unit_index);
|
|
boolean result = FALSE;
|
|
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
0x4FB,
|
|
source_unit_index==NONE || local_player_count()>1);
|
|
if (source_unit_index != NONE &&
|
|
object_get_ultimate_parent(source_unit_index) != source_unit_index)
|
|
{
|
|
long source_root_object_index = object_get_ultimate_parent(source_unit_index);
|
|
struct object_datum *source_root_object;
|
|
real_vector3d best_adjustment_vector;
|
|
real collision_radius;
|
|
real_matrix4x3 adjustment_matrix;
|
|
real scale;
|
|
short adjustment_index;
|
|
real_point3d anchor_point;
|
|
|
|
unit_get(source_unit_index);
|
|
source_root_object = object_get(source_root_object_index);
|
|
best_adjustment_vector = source_root_object->object.translational_velocity;
|
|
best_adjustment_vector.k = 0.f;
|
|
source_unit_index = source_root_object_index;
|
|
if (!(magnitude_squared3d(&best_adjustment_vector) > 0.f))
|
|
{
|
|
if (source_root_object->object.forward.k < 0.70710677f)
|
|
{
|
|
best_adjustment_vector = source_root_object->object.forward;
|
|
}
|
|
else
|
|
{
|
|
best_adjustment_vector = source_root_object->object.up;
|
|
}
|
|
}
|
|
best_adjustment_vector.k = 0.f;
|
|
|
|
collision_radius = biped_definition_get(biped->definition_index)->biped.collision_radius;
|
|
scale = collision_radius * 3.f + source_root_object->object.bounding_sphere_radius;
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
0x525,
|
|
magnitude3d(&best_adjustment_vector));
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
0x526,
|
|
scale);
|
|
|
|
negate_vector3d(
|
|
&best_adjustment_vector,
|
|
&best_adjustment_vector);
|
|
normalize3d(&best_adjustment_vector);
|
|
matrix4x3_from_point_and_vectors(
|
|
&adjustment_matrix,
|
|
&source_root_object->object.bounding_sphere_center,
|
|
&best_adjustment_vector,
|
|
global_up3d);
|
|
adjustment_matrix.scale = scale;
|
|
|
|
for (adjustment_index = 0;
|
|
adjustment_index < NUMBEROF(adjustment_weights) && !result;
|
|
adjustment_index++)
|
|
{
|
|
matrix4x3_transform_point(
|
|
&adjustment_matrix,
|
|
&adjustment_weights[adjustment_index],
|
|
&anchor_point);
|
|
result = biped_fix_position(
|
|
player_unit_index,
|
|
source_unit_index,
|
|
&anchor_point,
|
|
NULL,
|
|
2.f,
|
|
FALSE,
|
|
FALSE,
|
|
TRUE);
|
|
|
|
if (!result)
|
|
{
|
|
short random_adjustment_index;
|
|
real_point3d new_position;
|
|
real_vector3d random_offset;
|
|
|
|
for (random_adjustment_index = 0;
|
|
random_adjustment_index < 8 && !result;
|
|
random_adjustment_index++)
|
|
{
|
|
random_offset = *global_zero_vector3d;
|
|
random_direction3d(&random_offset);
|
|
point_from_line3d(
|
|
&anchor_point,
|
|
&random_offset,
|
|
collision_radius,
|
|
&new_position);
|
|
result = biped_fix_position(
|
|
player_unit_index,
|
|
source_unit_index,
|
|
&new_position,
|
|
NULL,
|
|
2.f,
|
|
FALSE,
|
|
FALSE,
|
|
TRUE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result = biped_fix_position(
|
|
player_unit_index,
|
|
source_unit_index,
|
|
(real_point3d *)position,
|
|
NULL,
|
|
2.f,
|
|
FALSE,
|
|
FALSE,
|
|
TRUE);
|
|
}
|
|
|
|
player->cluster_index = NONE;
|
|
if (result)
|
|
{
|
|
struct scenario *scenario = global_scenario_get();
|
|
short bsp_switch_trigger_volume_index;
|
|
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
0x56A,
|
|
player->unit_index!=NONE);
|
|
for (bsp_switch_trigger_volume_index = 0;
|
|
bsp_switch_trigger_volume_index < scenario->bsp_switch_trigger_volumes.count;
|
|
bsp_switch_trigger_volume_index++)
|
|
{
|
|
struct scenario_bsp_switch_trigger_volume *bsp_switch_trigger_volume = TAG_BLOCK_GET_ELEMENT(
|
|
&scenario->bsp_switch_trigger_volumes,
|
|
bsp_switch_trigger_volume_index,
|
|
struct scenario_bsp_switch_trigger_volume);
|
|
|
|
if (bsp_switch_trigger_volume->source_structure_bsp_index ==
|
|
global_structure_bsp_index &&
|
|
scenario_trigger_volume_test_object(
|
|
bsp_switch_trigger_volume->trigger_volume_index,
|
|
player->unit_index))
|
|
{
|
|
result = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (result)
|
|
{
|
|
biped->object.translational_velocity = *global_zero_vector3d;
|
|
if (source_unit_index != NONE)
|
|
{
|
|
real_vector3d forward = unit_get(source_unit_index)->object.forward;
|
|
struct biped_datum *source_biped = biped_try_and_get(source_unit_index);
|
|
|
|
if (source_biped && source_biped->biped.elevator_object_index != NONE)
|
|
{
|
|
biped->biped.elevator_object_index =
|
|
source_biped->biped.elevator_object_index;
|
|
biped->biped.elevator_ticks = source_biped->biped.elevator_ticks;
|
|
}
|
|
|
|
biped->unit.desired_facing_vector = forward;
|
|
biped->unit.desired_aiming_vector = forward;
|
|
biped->unit.desired_looking_vector = forward;
|
|
if (player->local_player_index != NONE)
|
|
{
|
|
player_control_set_facing(
|
|
player->local_player_index,
|
|
&forward);
|
|
}
|
|
}
|
|
|
|
if (source_unit_index != NONE)
|
|
{
|
|
long respawn_effect_index = TAG_BLOCK_GET_ELEMENT(
|
|
&scenario_get_game_globals()->player_information,
|
|
0,
|
|
struct game_globals_player_information)->coop_respawn_effect.index;
|
|
|
|
if (respawn_effect_index != NONE)
|
|
{
|
|
players_compute_combined_pvs(players_globals->combined_pvs, FALSE);
|
|
effect_new_from_object(
|
|
respawn_effect_index,
|
|
player_unit_index,
|
|
player_unit_index,
|
|
NONE,
|
|
0.f,
|
|
0.f,
|
|
NULL,
|
|
NULL);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
error(2, "couldn't teleport player into a valid location");
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
0x5AB,
|
|
player->local_player_index!=NONE);
|
|
player_pseudo_kill(player_index, source_unit_index);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
boolean player_teleport(
|
|
long player_index,
|
|
long source_unit_index,
|
|
real_point3d const *position)
|
|
{
|
|
struct player_datum *player;
|
|
struct biped_datum *biped;
|
|
long unit_index;
|
|
boolean result;
|
|
|
|
player = player_get(player_index);
|
|
unit_index = player->unit_index;
|
|
biped = biped_try_and_get(unit_index);
|
|
result = FALSE;
|
|
if (biped)
|
|
{
|
|
if (biped->object.parent_object_index != NONE)
|
|
unit_exit_seat_end(unit_index);
|
|
result = player_teleport_internal(player_index, source_unit_index, position);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void players_debug_render(
|
|
void)
|
|
{
|
|
struct collision_result collision;
|
|
real_point3d pill_position;
|
|
real_vector3d pill_vector;
|
|
real initial_radius;
|
|
real_point3d fixed_position;
|
|
short local_player_count;
|
|
real pill_height;
|
|
real pill_width;
|
|
short local_player_index;
|
|
long player_index;
|
|
long biped_index;
|
|
struct player_datum *player;
|
|
struct unit_datum *unit;
|
|
struct biped_definition *biped_definition;
|
|
|
|
if (debug_render_player_teleport)
|
|
{
|
|
for (local_player_count = 0, local_player_index = local_player_get_next(NONE);
|
|
local_player_count < 2 && local_player_index != NONE;
|
|
local_player_count++, local_player_index = local_player_get_next(local_player_index))
|
|
{
|
|
match_assert("c:\\halo\\SOURCE\\game\\players.c", 0x3AF,
|
|
local_player_index>=NONE && local_player_index<MAXIMUM_NUMBER_OF_LOCAL_PLAYERS);
|
|
|
|
if (players_globals->local_players[local_player_index] != NONE)
|
|
{
|
|
player_index = local_player_get_player_index(local_player_index);
|
|
player = player_get(player_index);
|
|
biped_index = player->unit_index;
|
|
if (biped_index != NONE)
|
|
{
|
|
unit = unit_get(biped_index);
|
|
biped_get_physics_pill(
|
|
biped_index,
|
|
&fixed_position,
|
|
&initial_radius,
|
|
&initial_radius);
|
|
if (biped_fix_position(
|
|
NONE,
|
|
biped_index,
|
|
&fixed_position,
|
|
&fixed_position,
|
|
2.0f,
|
|
FALSE,
|
|
TRUE,
|
|
TRUE))
|
|
{
|
|
biped_definition = biped_definition_get(unit->definition_index);
|
|
biped_get_physics_pill(
|
|
biped_index,
|
|
&pill_position,
|
|
&pill_height,
|
|
&pill_width);
|
|
fixed_position.z +=
|
|
biped_definition->biped.collision_radius;
|
|
scale_vector3d(global_up3d, pill_height, &pill_vector);
|
|
if (collision_test_pill(
|
|
_collision_test_for_player_teleport_flags,
|
|
&fixed_position,
|
|
&pill_vector,
|
|
pill_width,
|
|
biped_index,
|
|
&collision))
|
|
{
|
|
render_debug_pill(
|
|
FALSE,
|
|
&fixed_position,
|
|
&pill_vector,
|
|
pill_width,
|
|
global_real_argb_red);
|
|
}
|
|
else
|
|
{
|
|
render_debug_pill(
|
|
FALSE,
|
|
&fixed_position,
|
|
&pill_vector,
|
|
pill_width,
|
|
global_real_argb_green);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void player_update_powerups(
|
|
long player_index)
|
|
{
|
|
struct player_datum *player;
|
|
short powerup_index;
|
|
|
|
player = player_get(player_index);
|
|
for (powerup_index= 0; powerup_index<NUMBER_OF_PLAYER_POWERUPS; powerup_index++)
|
|
{
|
|
if (player->powerup_durations[powerup_index] > 0)
|
|
{
|
|
player->powerup_durations[powerup_index]--;
|
|
if (player->powerup_durations[powerup_index] == 0)
|
|
player_powerup_off(player_index, powerup_index);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void debug_player_teleport(
|
|
short local_player_index,
|
|
short target_local_player_index)
|
|
{
|
|
long player_index;
|
|
long unit_index;
|
|
long target_player_index;
|
|
long target_unit_index;
|
|
struct player_datum *player;
|
|
struct unit_datum *target_unit;
|
|
|
|
player_index = local_player_get_player_index(local_player_index);
|
|
if (player_index == NONE)
|
|
{
|
|
unit_index = NONE;
|
|
}
|
|
else
|
|
{
|
|
player = player_get(local_player_get_player_index(local_player_index));
|
|
unit_index = player->unit_index;
|
|
}
|
|
|
|
target_player_index = local_player_get_player_index(target_local_player_index);
|
|
if (target_player_index == NONE)
|
|
{
|
|
target_unit_index = NONE;
|
|
}
|
|
else
|
|
{
|
|
player = player_get(local_player_get_player_index(target_local_player_index));
|
|
target_unit_index = player->unit_index;
|
|
}
|
|
|
|
if (unit_index != NONE && target_unit_index != NONE)
|
|
{
|
|
target_unit = unit_get(target_unit_index);
|
|
player_teleport_internal(
|
|
player_index_from_unit_index(unit_index),
|
|
target_unit_index,
|
|
&target_unit->object.bounding_sphere_center);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
boolean players_respawn_coop(
|
|
void)
|
|
{
|
|
struct data_iterator iterator;
|
|
struct player_datum *player;
|
|
struct biped_datum *biped;
|
|
struct players_vehicle_datum *vehicle;
|
|
long respawn_unit_index;
|
|
long root_object_index;
|
|
boolean result;
|
|
boolean dangerous;
|
|
|
|
players_globals->respawn_failure = 0;
|
|
result = FALSE;
|
|
if (!players_globals->respawn_failed)
|
|
{
|
|
if (dangerous_projectiles_near_player() ||
|
|
any_unit_is_dangerous())
|
|
{
|
|
players_globals->respawn_failure = 1;
|
|
goto result_complete;
|
|
}
|
|
}
|
|
|
|
if (!players_globals->respawn_failed &&
|
|
ai_enemies_attacking_player())
|
|
{
|
|
players_globals->respawn_failure = 2;
|
|
goto result_complete;
|
|
}
|
|
|
|
respawn_unit_index = NONE;
|
|
data_iterator_new(&iterator, player_data);
|
|
while (player = data_iterator_next(&iterator))
|
|
{
|
|
if (player->unit_index != NONE)
|
|
{
|
|
root_object_index =
|
|
object_get_ultimate_parent(player->unit_index);
|
|
if (root_object_index == player->unit_index)
|
|
{
|
|
biped = biped_try_and_get(player->unit_index);
|
|
if (biped)
|
|
{
|
|
dangerous = TEST_FLAG(
|
|
biped->biped.flags,
|
|
_biped_airborne_bit);
|
|
}
|
|
else
|
|
{
|
|
dangerous = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vehicle =
|
|
(struct players_vehicle_datum *)
|
|
object_try_and_get_and_verify_type(
|
|
root_object_index,
|
|
_object_mask_vehicle);
|
|
if (vehicle)
|
|
dangerous = vehicle->vehicle.unknown_state > 0;
|
|
else
|
|
dangerous = FALSE;
|
|
}
|
|
|
|
if (!dangerous)
|
|
respawn_unit_index = player->unit_index;
|
|
else
|
|
players_globals->respawn_failure = 3;
|
|
}
|
|
}
|
|
|
|
if (respawn_unit_index != NONE)
|
|
{
|
|
result = TRUE;
|
|
data_iterator_new(&iterator, player_data);
|
|
while (player = data_iterator_next(&iterator))
|
|
{
|
|
if (player->unit_index == NONE)
|
|
{
|
|
player_spawn(iterator.datum_index);
|
|
if (player->unit_index != NONE)
|
|
{
|
|
result = player_teleport(
|
|
iterator.datum_index,
|
|
respawn_unit_index,
|
|
&object_get(respawn_unit_index)->
|
|
object.bounding_sphere_center);
|
|
}
|
|
else
|
|
{
|
|
result = FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
players_globals->respawn_failed =
|
|
players_globals->respawn_failed && !result;
|
|
if (result)
|
|
players_globals->respawn_failure = 0;
|
|
|
|
result_complete:
|
|
return result;
|
|
}
|
|
|
|
static void player_teleport_on_bsp_switch(
|
|
long player_index,
|
|
long source_unit_index,
|
|
real_point3d const *position)
|
|
{
|
|
struct player_datum *player;
|
|
struct biped_datum *biped;
|
|
struct biped_datum *source_biped;
|
|
long unit_index;
|
|
boolean outside_switch_trigger;
|
|
boolean teleport_succeeded;
|
|
|
|
player = player_get(player_index);
|
|
unit_index = player->unit_index;
|
|
biped = biped_try_and_get(unit_index);
|
|
match_assert("c:\\halo\\SOURCE\\game\\players.c", 0x4CB,
|
|
player_index!=NONE);
|
|
match_assert("c:\\halo\\SOURCE\\game\\players.c", 0x4CC,
|
|
position);
|
|
if (biped)
|
|
{
|
|
if (players_globals->pending_teleport_starting_location_index != NONE &&
|
|
!scenario_trigger_volume_test_object(
|
|
TAG_BLOCK_GET_ELEMENT(
|
|
&global_scenario_get()->bsp_switch_trigger_volumes,
|
|
players_globals->pending_teleport_starting_location_index,
|
|
struct scenario_bsp_switch_trigger_volume)->trigger_volume_index,
|
|
unit_index))
|
|
{
|
|
outside_switch_trigger = TRUE;
|
|
}
|
|
else
|
|
{
|
|
outside_switch_trigger = FALSE;
|
|
}
|
|
|
|
if (scenario_leaf_index_from_point(
|
|
&biped->object.bounding_sphere_center) == NONE ||
|
|
outside_switch_trigger)
|
|
{
|
|
if (biped->object.parent_object_index != NONE)
|
|
{
|
|
source_biped = biped_get(source_unit_index);
|
|
if (biped->object.parent_object_index !=
|
|
source_biped->object.parent_object_index)
|
|
{
|
|
unit_exit_seat_end(unit_index);
|
|
}
|
|
}
|
|
|
|
if (biped->object.parent_object_index == NONE)
|
|
{
|
|
teleport_succeeded = player_teleport_internal(
|
|
player_index,
|
|
source_unit_index,
|
|
position);
|
|
}
|
|
else
|
|
{
|
|
teleport_succeeded = TRUE;
|
|
}
|
|
players_globals->respawn_failed = !teleport_succeeded;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void players_reconnect_to_structure_bsp(
|
|
void)
|
|
{
|
|
struct data_iterator iterator;
|
|
struct player_datum *player;
|
|
struct scenario *scenario;
|
|
struct scenario_bsp_switch_trigger_volume *bsp_switch;
|
|
struct scenario_cutscene_flag *cutscene_flag;
|
|
real_point3d teleport_position;
|
|
long source_unit_index;
|
|
boolean teleport_position_valid;
|
|
boolean found_player;
|
|
short local_player_index;
|
|
short cutscene_flag_index;
|
|
long player_index;
|
|
long player_unit_index;
|
|
|
|
if (players_globals->pending_teleport_starting_location_index != NONE &&
|
|
players_globals->local_player_count > 1)
|
|
{
|
|
scenario = global_scenario_get();
|
|
bsp_switch = TAG_BLOCK_GET_ELEMENT(
|
|
&scenario->bsp_switch_trigger_volumes,
|
|
players_globals->pending_teleport_starting_location_index,
|
|
struct scenario_bsp_switch_trigger_volume);
|
|
source_unit_index = NONE;
|
|
found_player = FALSE;
|
|
teleport_position_valid = FALSE;
|
|
cutscene_flag_index = bsp_switch->cutscene_flag_index;
|
|
if (cutscene_flag_index != NONE)
|
|
{
|
|
real adjustment = 0.f;
|
|
|
|
cutscene_flag = TAG_BLOCK_GET_ELEMENT(
|
|
&scenario->cutscene_flags,
|
|
cutscene_flag_index,
|
|
struct scenario_cutscene_flag);
|
|
teleport_position = cutscene_flag->position;
|
|
while (adjustment < 0.3f &&
|
|
collision_test_point(
|
|
FLAG(_collision_test_front_facing_surfaces_bit) |
|
|
FLAG(_collision_test_ignore_invisible_surfaces_bit) |
|
|
FLAG(_collision_test_structure_bit) |
|
|
FLAG(_collision_test_objects_scenery_bit),
|
|
&teleport_position,
|
|
NONE))
|
|
{
|
|
teleport_position.z += 0.05f;
|
|
adjustment += 0.05f;
|
|
}
|
|
teleport_position_valid = adjustment < 0.3f;
|
|
}
|
|
|
|
data_iterator_new(&iterator, player_data);
|
|
while ((player = data_iterator_next(&iterator)) && !found_player)
|
|
{
|
|
player_unit_index = player->unit_index;
|
|
if (player_unit_index != NONE &&
|
|
players_globals->pending_teleport_starting_location_index != NONE)
|
|
{
|
|
if (is_player_in_trigger(
|
|
players_globals->pending_teleport_starting_location_index,
|
|
player_unit_index))
|
|
{
|
|
real_point3d biped_base;
|
|
real biped_height;
|
|
real biped_width;
|
|
long cluster_index;
|
|
|
|
biped_get_physics_pill(
|
|
player->unit_index,
|
|
&biped_base,
|
|
&biped_height,
|
|
&biped_width);
|
|
cluster_index = scenario_leaf_index_from_point(&biped_base) == NONE ?
|
|
NONE :
|
|
TAG_BLOCK_GET_ELEMENT(
|
|
&global_structure_bsp_get()->leaves,
|
|
scenario_leaf_index_from_point(&biped_base) & LONG_MAX,
|
|
struct structure_leaf)->cluster_index;
|
|
if (cluster_index != NONE)
|
|
{
|
|
if (!teleport_position_valid)
|
|
{
|
|
teleport_position = biped_base;
|
|
}
|
|
else
|
|
{
|
|
teleport_position.z += biped_width;
|
|
}
|
|
|
|
source_unit_index = player->unit_index;
|
|
found_player = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
match_vassert(
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
0x63E,
|
|
found_player,
|
|
"no players in the bsp");
|
|
if (found_player)
|
|
{
|
|
local_player_index = local_player_get_next(NONE);
|
|
while (local_player_index != NONE)
|
|
{
|
|
player_index = local_player_get_player_index(local_player_index);
|
|
player = player_get(player_index);
|
|
if (player->unit_index != NONE &&
|
|
player->unit_index != source_unit_index)
|
|
{
|
|
player_teleport_on_bsp_switch(
|
|
player_index,
|
|
source_unit_index,
|
|
&teleport_position);
|
|
player_get(player_index)->cluster_index = NONE;
|
|
}
|
|
local_player_index = local_player_get_next(local_player_index);
|
|
}
|
|
}
|
|
players_globals->pending_teleport_starting_location_index = NONE;
|
|
}
|
|
|
|
data_iterator_new(&iterator, player_data);
|
|
while (player = data_iterator_next(&iterator))
|
|
player->cluster_index = NONE;
|
|
|
|
return;
|
|
}
|
|
|
|
boolean unit_should_autopick_weapon(
|
|
long unit_index,
|
|
long weapon_index)
|
|
{
|
|
struct weapon_datum *weapon;
|
|
struct weapon_definition *weapon_definition;
|
|
long weapon_count;
|
|
boolean result;
|
|
|
|
weapon = weapon_try_and_get(weapon_index);
|
|
weapon_definition = weapon_definition_get(weapon->definition_index);
|
|
weapon_count = unit_get_weapon_count(unit_index);
|
|
|
|
if ((unit_approve_weapon_pickup(unit_index, weapon_index) &&
|
|
TEST_FLAG(weapon_definition->weapon.flags, _weapon_doesnt_count_toward_maximum_bit)) ||
|
|
weapon_count == 0 ||
|
|
(!game_engine_running() &&
|
|
unit_approve_weapon_pickup(unit_index, weapon_index) &&
|
|
weapon_count < 2) ||
|
|
game_engine_force_autopickup(unit_index, weapon_index))
|
|
{
|
|
result = TRUE;
|
|
}
|
|
else
|
|
{
|
|
result = FALSE;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static long players_compute_local_player_count(
|
|
void)
|
|
{
|
|
long local_player_count = 0;
|
|
short local_player_index;
|
|
|
|
for (local_player_index = 0;
|
|
local_player_index < MAXIMUM_LOCAL_PLAYERS;
|
|
local_player_index++)
|
|
{
|
|
if (players_globals->local_players[local_player_index] != NONE)
|
|
local_player_count++;
|
|
}
|
|
|
|
return local_player_count;
|
|
}
|
|
|
|
/* Reset the per-tick interaction proposal before scanning nearby objects. */
|
|
static void player_reset_action_result(
|
|
long player_index)
|
|
{
|
|
struct player_datum *player;
|
|
|
|
player = player_get(player_index);
|
|
player->action_result = _player_action_result_reload;
|
|
player->action_object_index = NONE;
|
|
|
|
return;
|
|
}
|
|
|
|
static boolean player_handle_weapon_swap(
|
|
long player_index)
|
|
{
|
|
struct player_datum *player;
|
|
struct unit_datum *unit;
|
|
struct weapon_datum *weapon;
|
|
boolean result;
|
|
|
|
player = player_get(player_index);
|
|
unit = unit_get(player->unit_index);
|
|
result = FALSE;
|
|
if (!players_decide_pickups())
|
|
return result;
|
|
switch (player->action_result)
|
|
{
|
|
case _player_action_result_swap_for_weapon:
|
|
if (unit_drop_current_weapon(player->unit_index, TRUE) &&
|
|
unit_add_weapon_to_inventory(
|
|
player->unit_index,
|
|
player->action_object_index,
|
|
TRUE))
|
|
{
|
|
weapon = weapon_get(player->action_object_index);
|
|
hud_picked_up_weapon(
|
|
player->local_player_index,
|
|
weapon->definition_index);
|
|
player_network_picked_up(player, player_index, _network_pickup_weapon, weapon->definition_index, 0);
|
|
player_control_unzoom(player->unit_index);
|
|
}
|
|
result = TRUE;
|
|
break;
|
|
|
|
case _player_action_result_add_weapon_to_inventory:
|
|
if (unit_add_weapon_to_inventory(
|
|
player->unit_index,
|
|
player->action_object_index,
|
|
TRUE))
|
|
{
|
|
weapon = weapon_get(player->action_object_index);
|
|
hud_picked_up_weapon(
|
|
player->local_player_index,
|
|
weapon->definition_index);
|
|
player_network_picked_up(player, player_index, _network_pickup_weapon, weapon->definition_index, 0);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
boolean player_handle_powerup(
|
|
long player_index,
|
|
short powerup_type,
|
|
short duration)
|
|
{
|
|
struct player_datum *player;
|
|
struct unit_datum *unit;
|
|
|
|
player = player_get(player_index);
|
|
match_assert("c:\\halo\\SOURCE\\game\\players.c", 0xAEE,
|
|
powerup_type>=0 && powerup_type<NUMBER_OF_PLAYER_POWERUPS);
|
|
|
|
if (powerup_type == _player_powerup_active_camouflage)
|
|
{
|
|
long unit_index = player->unit_index;
|
|
|
|
unit = unit_get(unit_index);
|
|
if (TEST_FLAG(unit->unit.flags, _unit_active_camouflaged_bit))
|
|
return FALSE;
|
|
}
|
|
|
|
if (player->powerup_durations[powerup_type] == 0)
|
|
player_powerup_on(player_index, powerup_type);
|
|
else if (!game_engine_running())
|
|
player_powerup_additional(player_index, powerup_type);
|
|
|
|
player->powerup_durations[powerup_type] += duration;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void player_handle_powerup_minor(
|
|
long player_index,
|
|
short powerup_type,
|
|
short duration)
|
|
{
|
|
struct player_datum *player;
|
|
|
|
player = player_get(player_index);
|
|
match_assert("c:\\halo\\SOURCE\\game\\players.c", 0xB19,
|
|
powerup_type>=0 && powerup_type<NUMBER_OF_PLAYER_POWERUPS);
|
|
|
|
if (player->powerup_durations[powerup_type] == 0)
|
|
player_powerup_on(player_index, powerup_type);
|
|
|
|
player->powerup_durations[powerup_type] =
|
|
MAX(player->powerup_durations[powerup_type], duration);
|
|
|
|
return;
|
|
}
|
|
|
|
static void player_over_shield_screen_effect(
|
|
long player_index)
|
|
{
|
|
struct player_datum *player;
|
|
|
|
if (player_index != NONE)
|
|
{
|
|
player = player_get(player_index);
|
|
if (player->local_player_index != NONE)
|
|
{
|
|
struct screen_flash_definition screen_flash = { 0 };
|
|
|
|
screen_flash.fade_function =
|
|
over_shield_screen_flash_fade_function;
|
|
screen_flash.type =
|
|
players_static_data.screen_flash_parameters[0].type;
|
|
screen_flash.priority = 2;
|
|
screen_flash.duration =
|
|
players_static_data.screen_flash_parameters[0].duration;
|
|
screen_flash.max_intensity =
|
|
players_static_data.screen_flash_parameters[0].maximum_intensity;
|
|
screen_flash.zero_scale_factor = 0.f;
|
|
screen_flash.screen_flash_color.alpha =
|
|
over_shield_screen_flash_alpha;
|
|
screen_flash.screen_flash_color.red =
|
|
players_static_data.screen_flash_parameters[0].red;
|
|
screen_flash.screen_flash_color.green =
|
|
over_shield_screen_flash_green;
|
|
screen_flash.screen_flash_color.blue =
|
|
players_static_data.screen_flash_parameters[0].green;
|
|
player_effect_screen_flash(player_index, &screen_flash, 1.f);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void player_active_camo_screen_effect(
|
|
long player_index)
|
|
{
|
|
struct player_datum *player;
|
|
|
|
if (player_index != NONE)
|
|
{
|
|
player = player_get(player_index);
|
|
if (player->local_player_index != NONE)
|
|
{
|
|
struct screen_flash_definition screen_flash = { 0 };
|
|
|
|
screen_flash.fade_function =
|
|
active_camo_screen_flash_fade_function;
|
|
screen_flash.type =
|
|
players_static_data.screen_flash_parameters[1].type;
|
|
screen_flash.priority = 2;
|
|
screen_flash.duration =
|
|
players_static_data.screen_flash_parameters[1].duration;
|
|
screen_flash.max_intensity =
|
|
players_static_data.screen_flash_parameters[1].maximum_intensity;
|
|
screen_flash.zero_scale_factor = 0.f;
|
|
screen_flash.screen_flash_color.alpha =
|
|
active_camo_screen_flash_alpha;
|
|
screen_flash.screen_flash_color.red =
|
|
players_static_data.screen_flash_parameters[1].red;
|
|
screen_flash.screen_flash_color.green =
|
|
players_static_data.screen_flash_parameters[1].green;
|
|
screen_flash.screen_flash_color.blue =
|
|
active_camo_screen_flash_blue;
|
|
player_effect_screen_flash(player_index, &screen_flash, 1.f);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void player_health_pack_screen_effect(
|
|
long player_index)
|
|
{
|
|
struct player_datum *player;
|
|
|
|
if (player_index != NONE)
|
|
{
|
|
player = player_get(player_index);
|
|
if (player->local_player_index != NONE)
|
|
{
|
|
struct screen_flash_definition screen_flash = { 0 };
|
|
|
|
screen_flash.fade_function = 1;
|
|
screen_flash.type = 6;
|
|
screen_flash.priority = 2;
|
|
screen_flash.duration = 2.f;
|
|
screen_flash.max_intensity = 0.5f;
|
|
screen_flash.zero_scale_factor = 0.f;
|
|
screen_flash.screen_flash_color.alpha = 1.f;
|
|
screen_flash.screen_flash_color.red = 0.917647f;
|
|
screen_flash.screen_flash_color.green = 0.917647f;
|
|
screen_flash.screen_flash_color.blue = 0.917647f;
|
|
player_effect_screen_flash(player_index, &screen_flash, 1.f);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* HCEA authenticates this helper and both callers. Keep the ordinary call
|
|
topology even when VC7 chooses to inline one call site. */
|
|
static void player_powerup_on(
|
|
long player_index,
|
|
short powerup_type)
|
|
{
|
|
struct player_datum *player;
|
|
struct unit_datum *unit;
|
|
|
|
player = player_get(player_index);
|
|
unit = unit_get(player->unit_index);
|
|
switch (powerup_type)
|
|
{
|
|
case _player_powerup_active_camouflage:
|
|
SET_FLAG(unit->unit.flags, _unit_active_camouflaged_bit, TRUE);
|
|
unit->unit.cause_for_camo_regrowth = cause_for_camo_regrowth_default;
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* HCEA authenticates the additional/off helpers and their call topology. */
|
|
static void player_powerup_additional(
|
|
long player_index,
|
|
short powerup_type)
|
|
{
|
|
struct player_datum *player;
|
|
struct unit_datum *unit;
|
|
|
|
player = player_get(player_index);
|
|
unit = unit_get(player->unit_index);
|
|
switch (powerup_type)
|
|
{
|
|
case _player_powerup_active_camouflage:
|
|
SET_FLAG(unit->unit.flags, _unit_super_camouflaged_bit, TRUE);
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void player_powerup_off(
|
|
long player_index,
|
|
short powerup_type)
|
|
{
|
|
struct player_datum *player;
|
|
struct unit_datum *unit;
|
|
|
|
player = player_get(player_index);
|
|
unit = unit_get(player->unit_index);
|
|
switch (powerup_type)
|
|
{
|
|
case _player_powerup_active_camouflage:
|
|
SET_FLAG(unit->unit.flags, _unit_active_camouflaged_bit, FALSE);
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void player_set_action_result(
|
|
long player_index,
|
|
short action_result,
|
|
long object_index,
|
|
short seat_index)
|
|
{
|
|
struct player_datum *player;
|
|
real_point3d const *unit_position;
|
|
real_point3d const *current_position;
|
|
real_point3d const *new_position;
|
|
boolean set_action = FALSE;
|
|
|
|
player = player_get(player_index);
|
|
if (action_result == _player_action_result_flip_vehicle)
|
|
{
|
|
set_action = TRUE;
|
|
}
|
|
else if (action_result == player->action_result)
|
|
{
|
|
unit_position = &object_get(player->unit_index)->object.position;
|
|
current_position =
|
|
&object_get(player->action_object_index)->object.position;
|
|
new_position = &object_get(object_index)->object.position;
|
|
set_action = distance3d(unit_position, current_position) >
|
|
distance3d(unit_position, new_position);
|
|
}
|
|
else if (action_result > player->action_result)
|
|
{
|
|
set_action = TRUE;
|
|
}
|
|
|
|
if (set_action)
|
|
{
|
|
player->action_result = action_result;
|
|
player->action_object_index = object_index;
|
|
player->action_seat_index = seat_index;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void player_examine_nearby_unit(
|
|
long player_index,
|
|
long unit_index)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void player_examine_nearby_vehicle(
|
|
long player_index,
|
|
long vehicle_index)
|
|
{
|
|
struct player_datum *player;
|
|
struct players_vehicle_datum *vehicle;
|
|
struct unit_datum *unit;
|
|
struct game_globals_player_control *player_control;
|
|
real flipping_angle;
|
|
short seat_state;
|
|
|
|
player = player_get(player_index);
|
|
vehicle = (struct players_vehicle_datum *)vehicle_get(vehicle_index);
|
|
if (!TEST_FLAG(vehicle->object.damage_flags, _object_dead_bit))
|
|
{
|
|
player_control = TAG_BLOCK_GET_ELEMENT(
|
|
&scenario_get_game_globals()->player_control,
|
|
0,
|
|
struct game_globals_player_control);
|
|
flipping_angle =
|
|
(_pi * 0.5f) - player_control->minimum_vehicle_flipping_angle;
|
|
if (vehicle_get(vehicle_index)->object.up.k > (real)cos(flipping_angle))
|
|
{
|
|
if (!unit_overcharged(player->unit_index))
|
|
{
|
|
unit = unit_get(player->unit_index);
|
|
if (magnitude_squared3d(&unit->object.translational_velocity) < 0.01f)
|
|
{
|
|
if (magnitude_squared3d(
|
|
&((struct players_vehicle_datum *)vehicle_get(
|
|
vehicle_index))->object.angular_velocity) <
|
|
0.01f)
|
|
{
|
|
short seat_index;
|
|
|
|
seat_index = NONE;
|
|
seat_state = unit_find_nearby_seat(
|
|
player->unit_index,
|
|
vehicle_index,
|
|
&seat_index);
|
|
switch (seat_state)
|
|
{
|
|
case _unit_nearby_seat_available:
|
|
if (seat_index == NONE)
|
|
{
|
|
display_assert(
|
|
"seat_index != NONE",
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
0x83C,
|
|
TRUE);
|
|
system_exit(-1);
|
|
}
|
|
player_set_action_result(
|
|
player_index,
|
|
_player_action_result_enter_vehicle,
|
|
vehicle_index,
|
|
seat_index);
|
|
break;
|
|
|
|
case _unit_nearby_seat_occupied:
|
|
if (seat_index == NONE)
|
|
{
|
|
display_assert(
|
|
"seat_index != NONE",
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
0x841,
|
|
TRUE);
|
|
system_exit(-1);
|
|
}
|
|
player_set_action_result(
|
|
player_index,
|
|
_player_action_result_evict_from_vehicle,
|
|
vehicle_index,
|
|
seat_index);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (!TEST_FLAG(vehicle->vehicle.flags, _vehicle_upending_bit) &&
|
|
vehicle->unit.driver_object_index == NONE)
|
|
{
|
|
player_set_action_result(
|
|
player_index,
|
|
_player_action_result_flip_vehicle,
|
|
vehicle_index,
|
|
NONE);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void player_examine_nearby_item(
|
|
long player_index,
|
|
long item_index)
|
|
{
|
|
struct player_datum *player;
|
|
struct unit_datum *unit;
|
|
struct item_datum *item;
|
|
struct item_datum *equipment;
|
|
struct equipment_definition *equipment_definition;
|
|
struct equipment_definition *current_equipment_definition;
|
|
long current_equipment_index;
|
|
|
|
player = player_get(player_index);
|
|
unit = unit_get(player->unit_index);
|
|
item = item_get(item_index);
|
|
if (item->object.parent_object_index != NONE ||
|
|
item->item.ignore_object_index == player->unit_index)
|
|
{
|
|
return;
|
|
}
|
|
|
|
{
|
|
short inventory_index;
|
|
short ammunition_count;
|
|
long inventory_item_index;
|
|
|
|
for (inventory_index = 0;
|
|
inventory_index < MAXIMUM_WEAPONS_PER_UNIT;
|
|
inventory_index++)
|
|
{
|
|
inventory_item_index =
|
|
unit->unit.weapon_object_indices[inventory_index];
|
|
if (inventory_item_index != NONE && players_decide_pickups() &&
|
|
weapon_handle_potential_inventory_item(
|
|
inventory_item_index,
|
|
item_index,
|
|
player->local_player_index,
|
|
&ammunition_count))
|
|
{
|
|
if (ammunition_count > 0)
|
|
{
|
|
hud_picked_up_ammunition(
|
|
player->local_player_index,
|
|
weapon_get(inventory_item_index)->definition_index,
|
|
ammunition_count);
|
|
player_network_picked_up(player, player_index, _network_pickup_ammunition,
|
|
weapon_get(inventory_item_index)->definition_index, ammunition_count);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
equipment = equipment_try_and_get(item_index);
|
|
if (equipment)
|
|
{
|
|
equipment_definition = equipment_definition_get(equipment->definition_index);
|
|
if (equipment_definition->equipment.powerup_type == _equipment_powerup_grenade)
|
|
{
|
|
if (players_decide_pickups() && unit_add_grenade_to_inventory(player->unit_index, item_index))
|
|
{
|
|
hud_picked_up_grenade(
|
|
player->local_player_index,
|
|
equipment->definition_index);
|
|
player_network_picked_up(player, player_index, _network_pickup_grenade, equipment->definition_index, 0);
|
|
}
|
|
}
|
|
else if (equipment_definition->equipment.powerup_type != _equipment_powerup_none)
|
|
{
|
|
current_equipment_index = unit_get_current_equipment(player->unit_index);
|
|
if (current_equipment_index == NONE)
|
|
{
|
|
if (players_decide_pickups())
|
|
player_handle_powerup_equipment(player_index, item_index);
|
|
}
|
|
else
|
|
{
|
|
/* BUG (original): January code and the HCEA disassembly both
|
|
* re-fetch the current equipment but then use the nearby
|
|
* equipment's definition again. A corrected build should pass
|
|
* equipment_get(current_equipment_index)->definition_index to
|
|
* equipment_definition_get below. */
|
|
equipment_get(current_equipment_index);
|
|
current_equipment_definition =
|
|
equipment_definition_get(equipment->definition_index);
|
|
if (equipment_definition->equipment.powerup_type !=
|
|
current_equipment_definition->equipment.powerup_type)
|
|
{
|
|
player_set_action_result(
|
|
player_index,
|
|
_player_action_result_swap_for_powerup,
|
|
item_index,
|
|
NONE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
struct weapon_datum *weapon;
|
|
struct weapon_datum *current_weapon;
|
|
struct weapon_definition *weapon_definition;
|
|
struct weapon_definition *current_weapon_definition;
|
|
long current_weapon_index;
|
|
long weapon_count;
|
|
long weapon_item_index;
|
|
boolean weapon_trigger_active;
|
|
boolean current_weapon_does_not_count;
|
|
|
|
weapon_item_index = item_index;
|
|
weapon = weapon_try_and_get(weapon_item_index);
|
|
if (!weapon || !unit_can_use_weapon(player->unit_index, weapon_item_index))
|
|
{
|
|
return;
|
|
}
|
|
|
|
weapon_definition = weapon_definition_get(weapon->definition_index);
|
|
weapon_trigger_active =
|
|
TEST_FLAG(unit->unit.control_flags, _unit_control_weapon_primary_trigger_bit) ||
|
|
TEST_FLAG(unit->unit.control_flags, _unit_control_weapon_secondary_trigger_bit);
|
|
current_weapon_index = unit_inventory_get_weapon(
|
|
player->unit_index,
|
|
unit_get(player->unit_index)->unit.current_weapon_index);
|
|
weapon_count = unit_get_weapon_count(player->unit_index);
|
|
current_weapon_does_not_count = FALSE;
|
|
if (weapon_count >= 2 &&
|
|
current_weapon_index != NONE &&
|
|
!TEST_FLAG(
|
|
weapon_definition->weapon.flags,
|
|
_weapon_doesnt_count_toward_maximum_bit))
|
|
{
|
|
current_weapon = weapon_get(current_weapon_index);
|
|
current_weapon_definition =
|
|
weapon_definition_get(current_weapon->definition_index);
|
|
if (TEST_FLAG(
|
|
current_weapon_definition->weapon.flags,
|
|
_weapon_doesnt_count_toward_maximum_bit))
|
|
{
|
|
current_weapon_does_not_count = TRUE;
|
|
}
|
|
}
|
|
|
|
if (weapon_trigger_active &&
|
|
TEST_FLAG(weapon_definition->weapon.flags, _weapon_must_be_readied_bit))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (unit_should_autopick_weapon(player->unit_index, weapon_item_index))
|
|
{
|
|
if (players_decide_pickups() &&
|
|
unit_add_weapon_to_inventory(
|
|
player->unit_index,
|
|
weapon_item_index,
|
|
TRUE))
|
|
{
|
|
hud_picked_up_weapon(
|
|
player->local_player_index,
|
|
weapon_get(weapon_item_index)->definition_index);
|
|
player_network_picked_up(player, player_index, _network_pickup_weapon,
|
|
weapon_get(weapon_item_index)->definition_index, 0);
|
|
player_control_unzoom(player->unit_index);
|
|
}
|
|
}
|
|
else if (!current_weapon_does_not_count &&
|
|
unit_approve_weapon_swap(player->unit_index, weapon_item_index))
|
|
{
|
|
current_weapon = weapon_try_and_get(current_weapon_index);
|
|
if (weapon_count == 1 &&
|
|
current_weapon &&
|
|
current_weapon->definition_index != weapon->definition_index)
|
|
{
|
|
player_set_action_result(
|
|
player_index,
|
|
_player_action_result_add_weapon_to_inventory,
|
|
weapon_item_index,
|
|
NONE);
|
|
}
|
|
else
|
|
{
|
|
player_set_action_result(
|
|
player_index,
|
|
_player_action_result_swap_for_weapon,
|
|
weapon_item_index,
|
|
NONE);
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void player_examine_nearby_device(
|
|
long player_index,
|
|
long device_index)
|
|
{
|
|
struct player_datum *player;
|
|
struct unit_datum *unit;
|
|
struct device_datum *device;
|
|
real_point3d camera;
|
|
|
|
player = player_get(player_index);
|
|
unit = unit_get(player->unit_index);
|
|
device = device_get(device_index);
|
|
unit_get_camera_position(player->unit_index, &camera);
|
|
if (fast_vector_intersects_sphere(
|
|
&camera,
|
|
&unit->unit.aiming_vector,
|
|
&device->object.bounding_sphere_center,
|
|
device->object.bounding_sphere_radius) &&
|
|
device_frontfacing(device_index, &camera, &unit->unit.aiming_vector) &&
|
|
device_can_change_position(device_index))
|
|
{
|
|
player_set_action_result(
|
|
player_index,
|
|
_player_action_result_touch_device,
|
|
device_index,
|
|
NONE);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void player_handle_powerup_equipment(
|
|
long player_index,
|
|
long equipment_index)
|
|
{
|
|
struct player_datum *player;
|
|
struct item_datum *equipment;
|
|
struct equipment_definition *equipment_definition;
|
|
short powerup_type;
|
|
short duration;
|
|
long powerup_index;
|
|
|
|
player = player_get(player_index);
|
|
equipment = equipment_get(equipment_index);
|
|
equipment_definition = equipment_definition_get(equipment->definition_index);
|
|
duration = (short)(equipment_definition->equipment.powerup_time * 30.f);
|
|
if (duration <= 0)
|
|
return;
|
|
|
|
powerup_type = equipment_definition->equipment.powerup_type;
|
|
if (powerup_type == _equipment_powerup_double_speed)
|
|
{
|
|
players_globals->double_speed_ticks += duration;
|
|
game_set_players_are_double_speed(TRUE);
|
|
}
|
|
else if (powerup_type == _equipment_powerup_overshield)
|
|
{
|
|
if (!object_double_charge_shield(player->unit_index))
|
|
return;
|
|
player_over_shield_screen_effect(player_index);
|
|
}
|
|
else if (powerup_type == _equipment_powerup_health)
|
|
{
|
|
if (!object_restore_body(player->unit_index))
|
|
return;
|
|
player_health_pack_screen_effect(player_index);
|
|
}
|
|
else
|
|
{
|
|
switch (powerup_type)
|
|
{
|
|
case _equipment_powerup_active_camouflage:
|
|
powerup_index = 0;
|
|
break;
|
|
|
|
case _equipment_powerup_full_spectrum_vision:
|
|
powerup_index = 1;
|
|
break;
|
|
|
|
/* powerup_index is left unassigned only by this default arm. Not reached unassigned: the
|
|
* arm's assertion failure calls system_exit, which does not return in January
|
|
* (0x47c960 jumps to halt_and_catch_fire 0x4f21c0, which loops or calls exit).
|
|
* Source-policy approval pending (2026-09-27 audit). */
|
|
default:
|
|
display_assert(
|
|
NULL,
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
0xACB,
|
|
TRUE);
|
|
system_exit(-1);
|
|
}
|
|
|
|
if (!player_handle_powerup(player_index, (short)powerup_index, duration))
|
|
return;
|
|
if ((short)powerup_index == 0)
|
|
player_active_camo_screen_effect(player_index);
|
|
}
|
|
|
|
equipment = equipment_get(equipment_index);
|
|
hud_picked_up_powerup(
|
|
(unsigned short)player->local_player_index,
|
|
equipment->definition_index);
|
|
player_network_picked_up(player, player_index, _network_pickup_powerup, equipment->definition_index, 0);
|
|
if (player->local_player_index != NONE)
|
|
equipment_handle_pickup(equipment_index);
|
|
object_delete(equipment_index);
|
|
|
|
return;
|
|
}
|
|
|
|
void players_handle_deleted_object(
|
|
long deleted_object_index)
|
|
{
|
|
struct data_iterator iterator;
|
|
struct player_datum *player;
|
|
struct object_datum *object;
|
|
|
|
object = object_get(deleted_object_index);
|
|
if (TEST_FLAG(_object_mask_unit, object->object.type))
|
|
{
|
|
data_iterator_new(&iterator, player_data);
|
|
while (player = data_iterator_next(&iterator))
|
|
{
|
|
if (player->unit_index == deleted_object_index)
|
|
player_died(iterator.datum_index);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void player_examine_nearby_objects(
|
|
long player_index)
|
|
{
|
|
struct player_datum *player;
|
|
struct unit_datum *unit;
|
|
struct object_datum *object;
|
|
long object_indices[16];
|
|
short object_count;
|
|
short object_number;
|
|
|
|
player = player_get(player_index);
|
|
if (player->unit_index != NONE)
|
|
{
|
|
unit = unit_get(player->unit_index);
|
|
if (unit->object.parent_object_index == NONE)
|
|
{
|
|
object_count = objects_in_sphere(
|
|
0,
|
|
_object_mask_player_interaction,
|
|
&unit->object.location,
|
|
&unit->object.bounding_sphere_center,
|
|
unit->object.bounding_sphere_radius,
|
|
object_indices,
|
|
NUMBEROF(object_indices));
|
|
for (object_number = 0; object_number < object_count; object_number++)
|
|
{
|
|
object = object_get(object_indices[object_number]);
|
|
switch (object->object.type)
|
|
{
|
|
case _object_type_biped:
|
|
player_examine_nearby_unit(player_index, object_indices[object_number]);
|
|
break;
|
|
|
|
case _object_type_vehicle:
|
|
player_examine_nearby_vehicle(player_index, object_indices[object_number]);
|
|
break;
|
|
|
|
case _object_type_weapon:
|
|
case _object_type_equipment:
|
|
player_examine_nearby_item(player_index, object_indices[object_number]);
|
|
break;
|
|
|
|
case _object_type_control:
|
|
player_examine_nearby_device(player_index, object_indices[object_number]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static long create_weapon(
|
|
struct scenario_starting_profile_weapon const *starting_weapon,
|
|
long unit_index)
|
|
{
|
|
struct object_placement_data placement_data;
|
|
struct weapon_datum *weapon;
|
|
long weapon_index;
|
|
|
|
weapon_index = NONE;
|
|
if (starting_weapon->weapon.index != NONE)
|
|
{
|
|
object_placement_data_new(
|
|
&placement_data,
|
|
starting_weapon->weapon.index,
|
|
unit_index);
|
|
weapon_index = object_new(&placement_data);
|
|
if (weapon_index != NONE)
|
|
{
|
|
weapon = weapon_get(weapon_index);
|
|
weapon->weapon.magazines[0].rounds_total =
|
|
starting_weapon->rounds_total;
|
|
weapon->weapon.magazines[0].rounds_loaded =
|
|
starting_weapon->rounds_loaded;
|
|
}
|
|
}
|
|
|
|
return weapon_index;
|
|
}
|
|
|
|
void player_add_equipment(
|
|
long unit_index,
|
|
short starting_profile_index,
|
|
boolean reset_equipment)
|
|
{
|
|
struct unit_datum *unit;
|
|
struct scenario_starting_profile *starting_profile;
|
|
struct scenario *scenario;
|
|
long weapon_index;
|
|
short grenade_index;
|
|
|
|
if (unit_index != NONE && starting_profile_index != NONE)
|
|
{
|
|
unit = unit_try_and_get(unit_index);
|
|
if (unit->unit.player_index != NONE)
|
|
{
|
|
scenario = global_scenario_get();
|
|
starting_profile = TAG_BLOCK_GET_ELEMENT(
|
|
&scenario->starting_profiles,
|
|
starting_profile_index,
|
|
struct scenario_starting_profile);
|
|
|
|
if (reset_equipment)
|
|
{
|
|
unit_delete_all_weapons(unit_index);
|
|
unit->object.shield_vitality = 0.f;
|
|
unit->object.body_vitality = 0.f;
|
|
*(short *)unit->unit.grenade_counts = 0;
|
|
}
|
|
|
|
if (starting_profile->primary_weapon.weapon.index != NONE)
|
|
{
|
|
weapon_index = create_weapon(
|
|
&starting_profile->primary_weapon,
|
|
unit_index);
|
|
if (weapon_index != NONE &&
|
|
!unit_add_weapon_to_inventory(
|
|
unit_index,
|
|
weapon_index,
|
|
reset_equipment != FALSE))
|
|
{
|
|
error(2, "Could not attach starting weapon to player");
|
|
object_delete(weapon_index);
|
|
}
|
|
}
|
|
|
|
if (starting_profile->secondary_weapon.weapon.index != NONE)
|
|
{
|
|
weapon_index = create_weapon(
|
|
&starting_profile->secondary_weapon,
|
|
unit_index);
|
|
if (weapon_index != NONE &&
|
|
!unit_add_weapon_to_inventory(
|
|
unit_index,
|
|
weapon_index,
|
|
FALSE))
|
|
{
|
|
error(2, "Could not attach starting weapon to player");
|
|
object_delete(weapon_index);
|
|
}
|
|
}
|
|
|
|
unit->object.shield_vitality +=
|
|
starting_profile->starting_shield_modifier;
|
|
unit->object.body_vitality +=
|
|
starting_profile->starting_health_modifier;
|
|
unit = (struct unit_datum *)unit->unit.grenade_counts;
|
|
starting_profile =
|
|
(struct scenario_starting_profile *)starting_profile->grenade_counts;
|
|
grenade_index = NUMBER_OF_UNIT_GRENADE_TYPES;
|
|
do
|
|
{
|
|
*(byte *)unit += *(byte *)starting_profile;
|
|
starting_profile = (struct scenario_starting_profile *)(
|
|
(byte *)starting_profile + 1);
|
|
unit = (struct unit_datum *)((byte *)unit + 1);
|
|
}
|
|
while (--grenade_index);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void player_aiming_vector_from_facing(
|
|
long player_index,
|
|
real_vector3d *aiming_vector,
|
|
real_euler_angles2d const *facing)
|
|
{
|
|
struct player_datum *player;
|
|
struct unit_datum *unit;
|
|
struct players_vehicle_datum *vehicle;
|
|
struct unit_definition *vehicle_definition;
|
|
struct unit_seat *seat;
|
|
real_matrix4x3 rotation;
|
|
|
|
player = player_get(player_index);
|
|
vector3d_from_euler_angles2d(aiming_vector, facing);
|
|
if (player->unit_index != NONE)
|
|
{
|
|
unit = unit_get(player->unit_index);
|
|
if (unit->object.parent_object_index != NONE)
|
|
{
|
|
vehicle = (struct players_vehicle_datum *)object_try_and_get_and_verify_type(
|
|
unit->object.parent_object_index,
|
|
_object_mask_vehicle);
|
|
if (vehicle)
|
|
{
|
|
vehicle_definition = vehicle_definition_get(
|
|
vehicle->definition_index);
|
|
seat = TAG_BLOCK_GET_ELEMENT(
|
|
&vehicle_definition->unit.seats,
|
|
unit->unit.parent_seat_index,
|
|
struct unit_seat);
|
|
if (!TEST_FLAG(seat->flags, _unit_seat_third_person_camera_bit))
|
|
{
|
|
cross_product3d(
|
|
&vehicle->object.up,
|
|
global_right3d,
|
|
&rotation.forward);
|
|
if (normalize3d(&rotation.forward) == 0.f)
|
|
{
|
|
cross_product3d(
|
|
&vehicle->object.up,
|
|
global_down3d,
|
|
&rotation.forward);
|
|
normalize3d(&rotation.forward);
|
|
}
|
|
matrix4x3_rotation_from_vectors(
|
|
&rotation,
|
|
&rotation.forward,
|
|
&vehicle->object.up);
|
|
matrix4x3_transform_normal(
|
|
&rotation,
|
|
aiming_vector,
|
|
aiming_vector);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void players_update_before_game(
|
|
void)
|
|
{
|
|
struct player_action actions[NETWORK_GAME_MAXIMUM_PLAYER_COUNT];
|
|
struct data_iterator iterator;
|
|
struct player_datum *player;
|
|
struct unit_datum *unit;
|
|
struct player_action *action;
|
|
struct unit_control_data control_data;
|
|
long weapon_index;
|
|
short action_index;
|
|
|
|
profile_enter(PLAYERS_UPDATE_BEFORE_GAME_PROFILE);
|
|
if (update_client_dequeue(actions))
|
|
{
|
|
data_iterator_new(&iterator, player_data);
|
|
while (player = data_iterator_next(&iterator))
|
|
{
|
|
action_index = (short)iterator.datum_index;
|
|
action = &actions[action_index];
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
0x256,
|
|
action_index>=0 && action_index<NETWORK_GAME_MAXIMUM_PLAYER_COUNT);
|
|
match_assert_valid_real(
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
0x25B,
|
|
action->desired_facing.pitch);
|
|
match_assert_valid_real(
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
0x25C,
|
|
action->desired_facing.yaw);
|
|
match_vassert(
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
0x25D,
|
|
valid_real_vector2d(&action->throttle),
|
|
csprintf(
|
|
temporary,
|
|
"%s: assert_valid_real_vector2d(%f, %f)",
|
|
"&action->throttle",
|
|
action->throttle.i,
|
|
action->throttle.j));
|
|
match_assert_valid_real(
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
0x25E,
|
|
action->primary_trigger);
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
0x260,
|
|
(NONE == action->desired_weapon_index) ||
|
|
((action->desired_weapon_index >= 0) &&
|
|
(action->desired_weapon_index <= MAXIMUM_WEAPONS_PER_UNIT)));
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
0x261,
|
|
(NONE == action->desired_grenade_index) ||
|
|
((action->desired_grenade_index >= 0) &&
|
|
(action->desired_grenade_index <= NUMBER_OF_UNIT_GRENADE_TYPES)));
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
0x262,
|
|
(NONE == action->desired_zoom_level) || ((action->desired_zoom_level >= 0)));
|
|
|
|
if (player->unit_index == NONE && !game_in_editor())
|
|
{
|
|
if (game_engine_running())
|
|
{
|
|
/* (a client of the distributed netcode's players take the units
|
|
the host spawns them with, network_player_attach_unit) */
|
|
if (
|
|
!network_game_distributed_client() &&
|
|
game_engine_should_spawn_player(iterator.datum_index))
|
|
{
|
|
game_engine_prespawn_player_update(iterator.datum_index);
|
|
player_spawn(iterator.datum_index);
|
|
if (player->unit_index != NONE)
|
|
game_engine_postspawn_player_update(iterator.datum_index);
|
|
else
|
|
player->respawn_timer = 1;
|
|
}
|
|
else if (network_game_distributed_client())
|
|
game_engine_client_respawn_countdown(iterator.datum_index);
|
|
}
|
|
else if (!main_menu_is_active())
|
|
{
|
|
if (player->statistics.deaths == 0)
|
|
player_spawn(iterator.datum_index);
|
|
else if (!players_globals->all_dead)
|
|
main_respawn(players_globals->respawn_failed);
|
|
}
|
|
}
|
|
|
|
if (player->unit_index != NONE && unit_controllable(player->unit_index))
|
|
{
|
|
unit = unit_get(player->unit_index);
|
|
if (!players_globals->input_disabled)
|
|
{
|
|
network_player_log_idle_action(iterator.datum_index, action->control_flags);
|
|
if (TEST_FLAG(action->control_flags, _unit_control_action_bit) &&
|
|
unit->object.parent_object_index == NONE &&
|
|
!player_handle_action(iterator.datum_index))
|
|
{
|
|
SET_FLAG(action->control_flags, _unit_control_weapon_reload_bit, TRUE);
|
|
}
|
|
|
|
if (TEST_FLAG(action->control_flags, _unit_control_swap_weapons_bit) &&
|
|
unit->object.parent_object_index == NONE)
|
|
{
|
|
if (!player->swapped_weapons)
|
|
player->swapped_weapons = player_handle_weapon_swap(iterator.datum_index);
|
|
}
|
|
else
|
|
{
|
|
player->swapped_weapons = FALSE;
|
|
}
|
|
|
|
if (TEST_FLAG(action->control_flags, _unit_control_use_equipment_bit) &&
|
|
unit->unit.equipment_object_index != NONE)
|
|
{
|
|
player_handle_powerup_equipment(
|
|
iterator.datum_index,
|
|
unit->unit.equipment_object_index);
|
|
unit_delete_current_equipment(player->unit_index);
|
|
}
|
|
|
|
weapon_index = unit_inventory_get_weapon(
|
|
player->unit_index,
|
|
unit_get(player->unit_index)->unit.current_weapon_index);
|
|
if (weapon_index != NONE && weapon_must_be_readied(weapon_index))
|
|
{
|
|
if (TEST_FLAG(action->control_flags, _unit_control_weapon_primary_trigger_bit) ||
|
|
TEST_FLAG(action->control_flags, _unit_control_weapon_secondary_trigger_bit))
|
|
{
|
|
unit_drop_current_weapon(player->unit_index, TRUE);
|
|
}
|
|
action->desired_weapon_index = unit->unit.current_weapon_index;
|
|
}
|
|
|
|
control_data.control_flags = (word)action->control_flags;
|
|
player_aiming_vector_from_facing(
|
|
iterator.datum_index,
|
|
&control_data.aiming_vector,
|
|
&action->desired_facing);
|
|
control_data.facing_vector =
|
|
control_data.looking_vector = control_data.aiming_vector;
|
|
control_data.throttle.i = action->throttle.i;
|
|
control_data.throttle.j = action->throttle.j;
|
|
control_data.throttle.k = 0.f;
|
|
control_data.primary_trigger = action->primary_trigger;
|
|
control_data.animation_state = _unit_animation_state_in_combat;
|
|
control_data.weapon_index = action->desired_weapon_index;
|
|
control_data.grenade_index = action->desired_grenade_index;
|
|
control_data.zoom_level = action->desired_zoom_level;
|
|
control_data.aiming_speed = 0;
|
|
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
0x2E3,
|
|
(NONE == control_data.weapon_index) ||
|
|
((control_data.weapon_index >= 0) &&
|
|
(control_data.weapon_index <= MAXIMUM_WEAPONS_PER_UNIT)));
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
0x2E4,
|
|
(NONE == control_data.grenade_index) ||
|
|
((control_data.grenade_index >= 0) &&
|
|
(control_data.grenade_index <= NUMBER_OF_UNIT_GRENADE_TYPES)));
|
|
match_assert(
|
|
"c:\\halo\\SOURCE\\game\\players.c",
|
|
0x2E5,
|
|
(NONE == control_data.zoom_level) || ((control_data.zoom_level >= 0)));
|
|
unit_control(player->unit_index, &control_data);
|
|
}
|
|
else if (unit->unit.swarm_actor_index == NONE &&
|
|
unit->unit.actor_index == NONE)
|
|
{
|
|
struct unit_control_data inhibited_control_data;
|
|
|
|
inhibited_control_data.animation_state = _unit_animation_state_in_combat;
|
|
inhibited_control_data.aiming_speed = 0;
|
|
inhibited_control_data.control_flags = 0;
|
|
inhibited_control_data.weapon_index = NONE;
|
|
inhibited_control_data.grenade_index = NONE;
|
|
inhibited_control_data.zoom_level = NONE;
|
|
inhibited_control_data.throttle = *global_zero_vector3d;
|
|
inhibited_control_data.facing_vector = unit->unit.desired_facing_vector;
|
|
inhibited_control_data.aiming_vector = unit->unit.desired_aiming_vector;
|
|
inhibited_control_data.primary_trigger = 0.f;
|
|
inhibited_control_data.looking_vector = unit->unit.desired_looking_vector;
|
|
unit_control(player->unit_index, &inhibited_control_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
players_compute_combined_pvs(players_globals->combined_pvs_local, TRUE);
|
|
players_compute_combined_pvs(players_globals->combined_pvs, FALSE);
|
|
players_globals->local_player_count = (short)players_compute_local_player_count();
|
|
}
|
|
else
|
|
{
|
|
display_assert(NULL, "c:\\halo\\SOURCE\\game\\players.c", 0x30B, TRUE);
|
|
system_exit(-1);
|
|
}
|
|
|
|
profile_exit(PLAYERS_UPDATE_BEFORE_GAME_PROFILE);
|
|
|
|
return;
|
|
}
|
|
|
|
/* the telefrag message to a local player (port: its own function, which a
|
|
client of the distributed netcode calls with the host's telefrag kill,
|
|
port/linux/game/network_damage.c) */
|
|
void players_show_telefragged(
|
|
long player_index)
|
|
{
|
|
struct player_datum *player = player_get(player_index);
|
|
long message_list_index;
|
|
|
|
if (player->local_player_index == NONE)
|
|
return;
|
|
message_list_index = tag_loaded(
|
|
UNICODE_STRING_LIST_TAG,
|
|
"ui\\multiplayer_game_text");
|
|
hud_print_message(
|
|
player->local_player_index,
|
|
message_list_index != NONE
|
|
? unicode_string_list_get_string(
|
|
message_list_index,
|
|
MULTIPLAYER_GAME_TEXT_YOU_WERE_TELEFRAGGED)
|
|
: L"");
|
|
|
|
return;
|
|
}
|
|
|
|
void players_update_after_game(
|
|
void)
|
|
{
|
|
struct data_iterator iterator;
|
|
struct player_datum *player;
|
|
struct unit_datum *unit;
|
|
struct object_datum *root_object;
|
|
struct scenario *scenario;
|
|
struct scenario_bsp_switch_trigger_volume *bsp_switch_trigger_volume;
|
|
long telefrag_ticks;
|
|
long root_object_index;
|
|
short bsp_switch_trigger_volume_index;
|
|
|
|
profile_enter(PLAYERS_UPDATE_AFTER_GAME_PROFILE);
|
|
|
|
if (players_globals->double_speed_ticks > 0)
|
|
{
|
|
players_globals->double_speed_ticks--;
|
|
if (players_globals->double_speed_ticks == 0)
|
|
game_set_players_are_double_speed(FALSE);
|
|
}
|
|
|
|
data_iterator_new(&iterator, player_data);
|
|
while (player = data_iterator_next(&iterator))
|
|
{
|
|
if (!player->is_blocking_teleporter)
|
|
{
|
|
if (player->telefrag_timeout > 0)
|
|
player->telefrag_timeout--;
|
|
}
|
|
else
|
|
{
|
|
telefrag_ticks = player->telefrag_timeout;
|
|
if (telefrag_ticks >= 90)
|
|
{
|
|
/* a client's view of who blocks is a latency late: the
|
|
host's kill arrives with its damage events, and its message
|
|
with it (players_show_telefragged) */
|
|
if (network_game_distributed_client())
|
|
player_telefrag_effect_stop(iterator.datum_index);
|
|
else if (player->unit_index != NONE)
|
|
{
|
|
unit = unit_get(player->unit_index);
|
|
if (!TEST_FLAG(unit->object.damage_flags, _object_die_act_of_god_bit))
|
|
{
|
|
players_show_telefragged(iterator.datum_index);
|
|
player_telefrag_effect_stop(iterator.datum_index);
|
|
unit_kill(player->unit_index);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
player_telefrag_effect_start(
|
|
iterator.datum_index,
|
|
(real)telefrag_ticks * (1.f / 90.f));
|
|
}
|
|
}
|
|
|
|
player->is_blocking_teleporter = FALSE;
|
|
if (player->unit_index != NONE)
|
|
player_update_powerups(iterator.datum_index);
|
|
|
|
if (player->unit_index != NONE)
|
|
{
|
|
root_object_index = object_get_ultimate_parent(player->unit_index);
|
|
root_object = object_get(root_object_index);
|
|
if (!TEST_FLAG(root_object->object.flags, _object_outside_of_map_bit))
|
|
{
|
|
scenario = global_scenario_get();
|
|
for (bsp_switch_trigger_volume_index = 0;
|
|
bsp_switch_trigger_volume_index < scenario->bsp_switch_trigger_volumes.count;
|
|
bsp_switch_trigger_volume_index++)
|
|
{
|
|
bsp_switch_trigger_volume = TAG_BLOCK_GET_ELEMENT(
|
|
&scenario->bsp_switch_trigger_volumes,
|
|
bsp_switch_trigger_volume_index,
|
|
struct scenario_bsp_switch_trigger_volume);
|
|
if (bsp_switch_trigger_volume->source_structure_bsp_index ==
|
|
global_structure_bsp_index &&
|
|
scenario_trigger_volume_test_object(
|
|
bsp_switch_trigger_volume->trigger_volume_index,
|
|
player->unit_index))
|
|
{
|
|
if (players_globals->local_player_triggered_switch !=
|
|
_local_player_triggered_switch_none &&
|
|
players_globals->local_player_triggered_switch !=
|
|
player->local_player_index)
|
|
{
|
|
error(
|
|
2,
|
|
"!!!WARNING!!! teleported player triggering a bsp switch!!!");
|
|
}
|
|
|
|
players_globals->bsp_check_recursive_switch_ticks = 0;
|
|
players_globals->local_player_triggered_switch =
|
|
player->local_player_index;
|
|
players_globals->pending_teleport_starting_location_index =
|
|
bsp_switch_trigger_volume_index;
|
|
main_switch_structure_bsp(
|
|
bsp_switch_trigger_volume->destination_structure_bsp_index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
player_reset_action_result(iterator.datum_index);
|
|
player_examine_nearby_objects(iterator.datum_index);
|
|
}
|
|
|
|
if (players_globals->local_player_triggered_switch !=
|
|
_local_player_triggered_switch_none)
|
|
{
|
|
players_globals->bsp_check_recursive_switch_ticks++;
|
|
if (players_globals->bsp_check_recursive_switch_ticks > 0xC)
|
|
{
|
|
players_globals->local_player_triggered_switch =
|
|
_local_player_triggered_switch_none;
|
|
players_globals->bsp_check_recursive_switch_ticks = 0;
|
|
}
|
|
}
|
|
|
|
if (players_globals->all_dead)
|
|
{
|
|
if (!game_engine_running() && !players_lost_map_started)
|
|
{
|
|
main_lost_map();
|
|
players_lost_map_started = TRUE;
|
|
}
|
|
}
|
|
else if (players_lost_map_started)
|
|
{
|
|
players_lost_map_started = FALSE;
|
|
}
|
|
|
|
profile_exit(PLAYERS_UPDATE_AFTER_GAME_PROFILE);
|
|
|
|
return;
|
|
}
|
|
|
|
/* ---------- private code */
|