Files
astromaddie--HaloCE-VR/source/ai/actor_combat.c
T
Jonas Volman 6bfd956737 Reconcile approved Halo fidelity packets and bounded data credit
Recover transparent geometry submit (+1,285 meaningful code, zero exact losses); reconcile COMMON ownership and Q10 85+EXT1 credit, actions/main data, and separately disclosed denominator corrections. Preserve canonical-only repairs and all held research. C09 is ordinary disclosed cleanup, not a claim of proven scaffolding intent. Clean rebuild, 1,663 tests, parks, audits, provider links and independent EXT1 check pass. Local reconciliation only.
2026-09-29 13:52:37 -07:00

2439 lines
67 KiB
C

/*
ACTOR_COMBAT.C
symbols in this file:
0000F590 0090:
_code_0000f590 (0000)
0000F620 0030:
_actor_combat_fire_wildly (0000)
0000F650 0040:
_actor_combat_disable_bursts (0000)
0000F690 0030:
_actor_firing_blindly (0000)
0000F6C0 0040:
_actor_combat_currently_firing_burst (0000)
0000F700 0040:
_actor_get_weapon_definition (0000)
0000F740 00c0:
_actor_get_weapon_vector (0000)
0000F800 0080:
_actor_combat_get_firing_variant_definition (0000)
0000F880 00a0:
_code_0000f880 (0000)
0000F920 0040:
_code_0000f920 (0000)
0000F960 0020:
_fast_ftol (0000)
0000F980 0010:
_sine (0000)
0000F990 0010:
_cosine (0000)
0000F9A0 0010:
_tangent (0000)
0000F9B0 0020:
_cross_product2d (0000)
0000F9D0 0030:
_add_vectors3d (0000)
0000FA00 0020:
_random_boolean (0000)
0000FA20 0020:
_valid_real (0000)
0000FA40 0160:
_actor_combat_find_nearby_target (0000)
0000FBA0 00b0:
_code_0000fba0 (0000)
0000FC50 00d0:
_code_0000fc50 (0000)
0000FD20 01c0:
_actor_combat_reaim_grenade (0000)
0000FEE0 0110:
_actor_combat_build_grenade_trajectory (0000)
0000FFF0 0100:
_actor_combat_find_grenade_target (0000)
000100F0 0370:
_actor_combat_check_collateral_damage (0000)
00010460 0120:
_actor_combat_plan_grenade_trajectory (0000)
00010580 0040:
_valid_realcmp (0000)
000105C0 0060:
_valid_real_normal3d (0000)
00010620 00b0:
_code_00010620 (0000)
000106D0 02d0:
_actor_aim_projectile (0000)
000109A0 07b0:
_code_000109a0 (0000)
00011150 0060:
_actor_combat_retarget_grenade (0000)
000111B0 0220:
_actor_aim_grenade (0000)
000113D0 1240:
_actor_combat_update (0000)
002444A0 000e:
??_C@_0O@HEGILGAC@weapon_vector?$AA@ (0000)
002444B0 0021:
??_C@_0CB@NKOAMEKE@c?3?2halo?2SOURCE?2ai?2actor_combat?4c@ (0000)
002444D4 0035:
??_C@_0DF@COLBENAC@burst_geometry_reference?5?$CG?$CG?5firi@ (0000)
0024450C 0004:
__real@3dcccccd (0000)
00244510 0004:
__real@3fd9999a (0000)
00244514 0016:
??_C@_0BG@ODFAEBCF@projectile_definition?$AA@ (0000)
00244530 0042:
??_C@_0EC@CDIDOCJH@grenade_origin?5?$CG?$CG?5desired_impact@ (0000)
00244574 0004:
__real@3e4ccccd (0000)
00244578 0008:
__real@3f50624de0000000 (0000)
00244580 0043:
??_C@_0ED@MKICAFGB@actor?9?$DOcontrol?4current_fire_targ@ (0000)
002445C4 002b:
??_C@_0CL@GJIEKICC@?$CFs?3?5assert_valid_real_normal3d?$CI?$CF@ (0000)
002445F0 0007:
??_C@_06NBFPPGEG@vector?$AA@ (0000)
002445F8 0004:
__real@3f490fdb (0000)
002445FC 003b:
??_C@_0DL@NNPMKEAI@?$CFs?3?5proj?5?$CF?41f?5rof?5?$CF?41f?5dmg?1s?5?$CF?41@ (0000)
00244638 0020:
??_C@_0CA@FHOKFHD@?$CFs?3?5manual?5damage?5modifier?5?$CF?42f?$AA@ (0000)
00244658 0010:
??_C@_0BA@FHPPJCFM@?$CGnew_aim_vector?$AA@ (0000)
00244668 0021:
??_C@_0CB@CPANGHMG@?$CGactor?9?$DOcontrol?4burst_aim_vector@ (0000)
0024468C 000f:
??_C@_0P@DBEDAMI@aiming_success?$AA@ (0000)
0024469C 0012:
??_C@_0BC@CHLPMLFC@?$CBhold_burst_start?$AA@ (0000)
002446B0 0025:
??_C@_0CF@EHKIDLPJ@new_state?$CB?$DNactor?9?$DOcontrol?4fire_s@ (0000)
002446D8 0018:
??_C@_0BI@MMOBPCCM@firing_decision?5?$DN?$DN?5NONE?$AA@ (0000)
002446F0 0004:
__real@3f3504f3 (0000)
002446F4 0004:
__real@3e3377ed (0000)
002446F8 004b:
??_C@_0EL@FHBLJBEG@actor?9?$DOcontrol?4current_fire_targ@ (0000)
00244744 0015:
??_C@_0BF@GLLAMKHP@weapon_index?5?$CB?$DN?5NONE?$AA@ (0000)
0024475C 0036:
??_C@_0DG@EHGBJHNC@actor?9?$DOcontrol?4current_fire_targ@ (0000)
*/
/* ---------- headers */
#define normalize3d normalize3d_inline
#define point_from_line3d actor_combat_point_from_line3d_inline
#define vector_from_points3d actor_combat_vector_from_points3d_inline
#define magnitude_squared3d actor_combat_magnitude_squared3d_inline
#define distance_squared3d actor_combat_distance_squared3d_inline
#define distance3d actor_combat_distance3d_inline
#define real_random_range actor_combat_real_random_range_inline
#define collision_test_line actor_combat_collision_test_line_inline
#include "cseries.h"
#include "ai.h"
#include "ai_communication.h"
#include "ai_debug.h"
#include "actor_definitions.h"
#include "actors.h"
#include "encounters.h"
#include "props.h"
#include "items/projectile_definitions.h"
#include "items/projectiles.h"
#include "items/weapon_definitions.h"
#include "items/weapons.h"
#include "game/game_globals.h"
#include "game/game.h"
#include "game/players.h"
#include "main/console.h"
#include "physics/collisions.h"
#include "scenario/scenario.h"
#include "tag_files/tag_files.h"
#include "units/vehicle_definitions.h"
#include "units/vehicles.h"
#undef collision_test_line
#undef real_random_range
#undef distance_squared3d
#undef distance3d
#undef magnitude_squared3d
#undef vector_from_points3d
#undef point_from_line3d
#undef normalize3d
#include "math/real_math_declarations.h"
/* ---------- constants */
enum
{
_actor_fire_target_none = 0,
_actor_fire_target_prop,
_actor_fire_target_manual_point,
_vehicle_ai_weapon_cannot_rotate_bit = 8,
MAXIMUM_COLLATERAL_DAMAGE_ACTORS = 32,
};
/* actor_definition.flags2 has no owner enumeration in actor_definitions.h yet;
* actor_firing_position.c, actor_moving.c, actor_stimulus.c and encounters.c
* carry partial TU-local copies of the same bit names. */
enum
{
_actor_definition_flags2_avoid_all_enemy_attack_vectors_bit = 0,
_actor_definition_flags2_must_stand_to_fire_bit,
_actor_definition_flags2_must_stop_to_fire_bit,
_actor_definition_flags2_disallow_vehicle_combat_bit,
_actor_definition_flags2_pathfinding_ignores_danger_bit,
_actor_definition_flags2_panic_in_groups_bit,
_actor_definition_flags2_no_corpse_shooting_bit,
NUMBER_OF_ACTOR_DEFINITION_FLAGS2
};
/* No shared header owns these tag/actor domains yet; action_charge.c carries a
* strafing-only copy, and actors.c, action_obey.c and actor_perception.c carry
* combat-status copies. */
enum
{
_actor_special_fire_mode_none = 0,
_actor_special_fire_mode_overcharge,
_actor_special_fire_mode_secondary,
NUMBER_OF_ACTOR_SPECIAL_FIRE_MODES,
};
enum
{
_actor_special_fire_situation_never = 0,
_actor_special_fire_situation_enemy_visible,
_actor_special_fire_situation_enemy_out_of_sight,
_actor_special_fire_situation_strafing,
NUMBER_OF_ACTOR_SPECIAL_FIRE_SITUATIONS,
};
enum
{
_actor_combat_status_none = 0,
_actor_combat_status_wary,
_actor_combat_status_investigate,
_actor_combat_status_definite,
_actor_combat_status_certain,
_actor_combat_status_clear_los,
_actor_combat_status_dangerous,
_actor_combat_status_visible,
NUMBER_OF_ACTOR_COMBAT_STATUS_LEVELS,
};
/* ---------- macros */
#define GRENADE_AIMING_ANGLE_COSINE 0.8660254f
#define GRENADE_AIMING_ANGLE_SINE 0.5f
#define actor_combat_vehicle_definition_get(index) \
((struct actor_combat_vehicle_definition_view *)vehicle_specific_definition_get(index))
/* ---------- structures */
struct encounter_actor_iterator
{
long encounter_index;
long index;
long next_index;
};
struct actor_combat_vehicle_definition_view
{
byte __unknown0[0x2F0];
unsigned long flags;
};
typedef char actor_combat_vehicle_definition_flags_offset_assert[
offsetof(struct actor_combat_vehicle_definition_view, flags) == 0x2F0 ? 1 : -1];
/* ---------- prototypes */
static boolean actor_combat_enable_special_fire_situation(
long actor_index,
short special_fire_situation);
static void actor_combat_get_firing_parameters(
long actor_index,
struct actor_variant_definition *firing_variant_definition,
struct actor_burst_geometry **burst_geometry_reference,
struct actor_firing_pattern **firing_pattern_reference);
static void actor_start_pause(
long actor_index);
static boolean actor_start_first_burst_delay(
long actor_index,
struct actor_variant_definition *firing_variant_definition);
static boolean actor_combat_allow_special_fire_situation(
long actor_index,
short special_fire_situation);
static void actor_start_burst(
long actor_index);
static void actor_combat_find_nearby_target(
real_point3d *target_point,
real miss_distance);
static void actor_get_weapon_vector(
long actor_index,
real_vector3d *weapon_vector);
static struct projectile_definition *actor_get_grenade_definition(
short grenade_type_index);
static boolean actor_combat_build_grenade_trajectory(
short grenade_type_index,
real_point3d const *grenade_origin,
real velocity_max,
real_point3d const *desired_impact_point,
real *target_ballistic_fraction_min,
boolean lob,
real_vector3d *aim_vector,
real *aim_speed,
real *aim_ticks,
real_vector3d *aim_velocity,
real *aim_gravity);
static boolean actor_combat_reaim_grenade(
long actor_index,
real_point3d const *grenade_origin);
static boolean actor_combat_retarget_grenade(
long actor_index,
real_point3d const *desired_grenade_target);
/* ---------- globals */
/* ---------- public code */
static boolean actor_combat_enable_special_fire_situation(
long actor_index,
short special_fire_situation)
{
struct actor_datum *actor = actor_get(actor_index);
boolean result = FALSE;
switch (special_fire_situation)
{
case _actor_special_fire_situation_enemy_visible:
result =
actor->control.current_fire_target_type == _actor_fire_target_prop &&
actor->target.target_type >= _actor_target_clear_line_of_sight_enemy;
break;
case _actor_special_fire_situation_enemy_out_of_sight:
result =
actor->control.current_fire_target_type == _actor_fire_target_none &&
actor->target.target_type >= _actor_target_uninspected_orphan &&
actor->target.target_really_alive &&
actor->target.since_any_target_visible_timer >= 75;
break;
case _actor_special_fire_situation_strafing:
result =
actor->control.current_fire_target_type == _actor_fire_target_prop &&
actor->target.target_type >= _actor_target_clear_line_of_sight_enemy &&
actor->input.vehicle_gunner;
break;
}
return result;
}
void actor_combat_fire_wildly(
long actor_index,
short fire_ticks)
{
struct actor_datum *actor = actor_get(actor_index);
actor->control.fire_state = _actor_fire_state_wild;
actor->control.fire_state_timer = fire_ticks;
return;
}
void actor_combat_disable_bursts(
long actor_index,
long disable_timer)
{
struct actor_datum *actor = actor_get(actor_index);
actor->control.burst_disable_timer =
MAX(actor->control.burst_disable_timer, disable_timer);
return;
}
boolean actor_firing_blindly(
long actor_index)
{
struct actor_datum *actor = actor_get(actor_index);
return actor->control.fire_state == _actor_fire_state_wild;
}
boolean actor_combat_currently_firing_burst(
long actor_index)
{
struct actor_datum *actor = actor_get(actor_index);
boolean result = FALSE;
if (actor->control.current_fire_target_type > _actor_fire_target_none)
{
result = actor->control.fire_state == _actor_fire_state_bursting;
}
return result;
}
struct weapon_definition *actor_get_weapon_definition(
long actor_index)
{
struct weapon_definition *result = NULL;
long weapon_index = actor_get_weapon(actor_index);
if (weapon_index != NONE)
{
struct weapon_datum *weapon = weapon_get(weapon_index);
result = weapon_definition_get(weapon->definition_index);
}
return result;
}
static void actor_get_weapon_vector(
long actor_index,
real_vector3d *weapon_vector)
{
struct actor_datum *actor = actor_get(actor_index);
long unit_index = actor->meta.unit_index;
boolean have_vector = FALSE;
match_assert(
"c:\\halo\\SOURCE\\ai\\actor_combat.c",
1166,
weapon_vector);
if (actor->input.vehicle_gunner)
{
struct unit_datum *vehicle = vehicle_get(actor->input.vehicle_index);
struct actor_combat_vehicle_definition_view *vehicle_definition =
actor_combat_vehicle_definition_get(vehicle->definition_index);
unit_index = actor->input.vehicle_index;
if (TEST_FLAG(
vehicle_definition->flags,
_vehicle_ai_weapon_cannot_rotate_bit))
{
*weapon_vector = vehicle->object.forward;
have_vector = TRUE;
}
}
if (!have_vector)
{
unit_get(unit_index);
unit_get_aiming_vector(unit_index, weapon_vector);
unit_clip_to_aiming_bounds(unit_index, weapon_vector, TRUE);
}
return;
}
struct actor_variant_definition *actor_combat_get_firing_variant_definition(
long actor_index)
{
struct actor_datum *actor = actor_get(actor_index);
struct actor_variant_definition *firing_variant_definition =
actor_variant_definition_get(actor->meta.variant_definition_index);
long weapon_index = actor_get_weapon(actor_index);
if (weapon_index != NONE)
{
struct weapon_datum *weapon = weapon_get(weapon_index);
struct weapon_definition *weapon_definition =
weapon_definition_get(weapon->definition_index);
if (weapon_definition != NULL &&
weapon_definition->weapon.ai_firing_parameters.index != NONE)
{
firing_variant_definition = actor_variant_definition_get(
weapon_definition->weapon.ai_firing_parameters.index);
}
}
return firing_variant_definition;
}
static void actor_combat_get_firing_parameters(
long actor_index,
struct actor_variant_definition *firing_variant_definition,
struct actor_burst_geometry **burst_geometry_reference,
struct actor_firing_pattern **firing_pattern_reference)
{
struct actor_datum *actor = actor_get(actor_index);
struct actor_burst_geometry *burst_geometry =
&firing_variant_definition->ranged_combat.burst_geometry;
struct actor_firing_pattern *firing_pattern = NULL;
if (actor->emotions.berserk)
{
firing_pattern = &firing_variant_definition->ranged_combat.berserk_pattern;
}
else if (actor->control.firing_at_new_target)
{
firing_pattern = &firing_variant_definition->ranged_combat.new_target_pattern;
}
else if (actor->control.firing_while_moving)
{
firing_pattern = &firing_variant_definition->ranged_combat.moving_pattern;
}
match_assert(
"c:\\halo\\SOURCE\\ai\\actor_combat.c",
1677,
burst_geometry_reference && firing_pattern_reference);
*burst_geometry_reference = burst_geometry;
*firing_pattern_reference = firing_pattern;
return;
}
static struct projectile_definition *actor_get_grenade_definition(
short grenade_type_index)
{
struct game_globals_grenade *grenade = TAG_BLOCK_GET_ELEMENT(
&scenario_get_game_globals()->grenades,
grenade_type_index,
struct game_globals_grenade);
struct projectile_definition *projectile_definition = NULL;
if (grenade && grenade->projectile.index != NONE)
{
projectile_definition =
projectile_definition_get(grenade->projectile.index);
}
return projectile_definition;
}
static void actor_combat_find_nearby_target(
real_point3d *target_point,
real miss_distance)
{
real_point3d above;
real_point3d target;
real_vector3d direction;
struct collision_result collision;
/* Keep January's inlined real-math schedule without materializing the
* header helpers as actor_combat-owned COMDATs. */
above.x = global_up3d->i*1.5f + target_point->x;
above.y = global_up3d->j*1.5f + target_point->y;
above.z = global_up3d->k*1.5f + target_point->z;
vector3d_from_angle(
&direction,
real_seed_random_range(get_global_random_seed_address(), -_pi, _pi));
target.x = direction.i*miss_distance + above.x;
target.y = direction.j*miss_distance + above.y;
target.z = direction.k*miss_distance + above.z;
{
real_vector3d collision_vector;
collision_vector.i = above.x - target_point->x;
collision_vector.j = above.y - target_point->y;
collision_vector.k = above.z - target_point->z;
if (collision_test_vector(
FLAG(_collision_test_front_facing_surfaces_bit) |
FLAG(_collision_test_back_facing_surfaces_bit) |
FLAG(_collision_test_structure_bit),
target_point,
&collision_vector,
NONE,
&collision))
{
above = *target_point;
}
}
{
real_vector3d collision_vector;
collision_vector.i = target.x - above.x;
collision_vector.j = target.y - above.y;
collision_vector.k = target.z - above.z;
if (collision_test_vector(
FLAG(_collision_test_front_facing_surfaces_bit) |
FLAG(_collision_test_back_facing_surfaces_bit) |
FLAG(_collision_test_structure_bit),
&above,
&collision_vector,
NONE,
&collision))
{
miss_distance = MAX(0.0f, collision.t*miss_distance - 0.1f);
target.x = direction.i*miss_distance + above.x;
target.y = direction.j*miss_distance + above.y;
target.z = direction.k*miss_distance + above.z;
}
}
*target_point = target;
return;
}
static void actor_start_pause(
long actor_index)
{
struct actor_datum *actor = actor_get(actor_index);
struct actor_burst_geometry *burst_geometry;
struct actor_firing_pattern *firing_pattern;
real burst_separation;
actor_combat_get_firing_parameters(
actor_index,
actor_combat_get_firing_variant_definition(actor_index),
&burst_geometry,
&firing_pattern);
burst_separation = actor_combat_real_random_range_inline(
burst_geometry->burst_separation_lower_bound,
burst_geometry->burst_separation_upper_bound)*
game_difficulty_get_team_value(
_game_difficulty_value_burst_separation,
actor->meta.team_index);
if (firing_pattern && firing_pattern->burst_separation_modifier != 0.0f)
{
burst_separation *= firing_pattern->burst_separation_modifier;
}
if (actor->external_orders.playfighting)
{
burst_separation *= 1.7f;
}
actor->control.fire_state_timer = (short)(burst_separation*TICKS_PER_SECOND);
return;
}
static boolean actor_start_first_burst_delay(
long actor_index,
struct actor_variant_definition *firing_variant_definition)
{
struct actor_datum *actor = actor_get(actor_index);
boolean fire_immediately = actor->orders.combat.override_firing_restrictions;
if (actor->control.current_fire_target_type == _actor_fire_target_prop)
{
struct prop_datum *prop =
prop_get(actor->control.current_fire_target_prop_index);
if (prop->state >= _prop_state_uninspected_orphan &&
prop->state <= _prop_state_inspected_orphan)
{
actor->firing_positions.pursuit_fired_at_orphan = TRUE;
fire_immediately = TRUE;
}
}
if (fire_immediately)
{
actor->control.fire_state_timer = 0;
}
else
{
actor->control.fire_state_timer = (short)(actor_combat_real_random_range_inline(
firing_variant_definition->ranged_combat.first_burst_delay_lower_bound,
firing_variant_definition->ranged_combat.first_burst_delay_upper_bound)*
TICKS_PER_SECOND);
}
return !fire_immediately;
}
static boolean actor_combat_reaim_grenade(
long actor_index,
real_point3d const *grenade_origin)
{
struct actor_datum *actor = actor_get(actor_index);
struct actor_variant_definition *variant_definition =
actor_variant_definition_get(actor->meta.variant_definition_index);
struct projectile_definition *projectile_definition =
actor_get_grenade_definition(
variant_definition->grenade_combat.grenade_type);
real_vector3d aim_vector;
real aim_speed;
real aim_ticks;
boolean linear;
boolean result = FALSE;
match_assert(
"c:\\halo\\SOURCE\\ai\\actor_combat.c",
1731,
projectile_definition);
if (projectile_aim(
projectile_definition,
grenade_origin,
&actor->control.grenade_current_target,
NULL,
NULL,
NULL,
&actor->control.grenade_current_aim_speed,
actor->control.grenade_current_lob,
&aim_vector,
&aim_speed,
&aim_ticks,
NULL,
&linear))
{
boolean within_aiming_angle;
{
real_vector2d aim_vector2d;
aim_vector2d.i = aim_vector.i;
aim_vector2d.j = aim_vector.j;
within_aiming_angle = normalize2d(&aim_vector2d) > 0.0f &&
aim_vector2d.i*actor->input.facing_vector.i +
aim_vector2d.j*actor->input.facing_vector.j >
GRENADE_AIMING_ANGLE_COSINE;
}
if (within_aiming_angle)
{
real_vector3d aim_velocity;
real aim_gravity;
aim_velocity.i = aim_vector.i*aim_speed;
aim_velocity.j = aim_vector.j*aim_speed;
aim_velocity.k = aim_vector.k*aim_speed;
aim_gravity = linear ?
0.0f :
projectile_get_ballistic_acceleration(projectile_definition);
if (ai_test_ballistic_line_of_fire(
actor_index,
grenade_origin,
aim_ticks,
&aim_velocity,
aim_gravity,
actor->control.grenade_current_ignore_object_index,
actor->input.vehicle_index != NONE))
{
actor->control.grenade_current_aim_vector = aim_vector;
actor->control.grenade_current_aim_speed = aim_speed;
result = TRUE;
}
}
}
return result;
}
static boolean actor_combat_build_grenade_trajectory(
short grenade_type_index,
real_point3d const *grenade_origin,
real velocity_max,
real_point3d const *desired_impact_point,
real *target_ballistic_fraction_min,
boolean lob,
real_vector3d *aim_vector,
real *aim_speed,
real *aim_ticks,
real_vector3d *aim_velocity,
real *aim_gravity)
{
boolean result = FALSE;
match_assert(
"c:\\halo\\SOURCE\\ai\\actor_combat.c",
1780,
grenade_origin && desired_impact_point && aim_vector && aim_speed);
{
struct projectile_definition *projectile_definition =
actor_get_grenade_definition(grenade_type_index);
if (projectile_definition)
{
boolean linear = FALSE;
if (projectile_aim(
projectile_definition,
grenade_origin,
desired_impact_point,
&velocity_max,
NULL,
target_ballistic_fraction_min,
NULL,
FALSE,
aim_vector,
aim_speed,
aim_ticks,
NULL,
&linear))
{
result = TRUE;
if (aim_velocity)
{
real speed = *aim_speed;
aim_velocity->i = speed*aim_vector->i;
aim_velocity->j = speed*aim_vector->j;
aim_velocity->k = speed*aim_vector->k;
}
if (aim_gravity)
{
*aim_gravity = linear ?
0.0f :
projectile_get_ballistic_acceleration(projectile_definition);
}
}
}
}
return result;
}
boolean actor_combat_find_grenade_target(
long actor_index,
real_point3d *grenade_target,
long *grenade_target_prop_index,
long *grenade_ignore_object_index)
{
struct actor_datum *actor = actor_get(actor_index);
struct actor_variant_definition *variant_definition =
actor_variant_definition_get(actor->meta.variant_definition_index);
boolean result = FALSE;
if (actor->target.target_prop_index != NONE)
{
struct prop_datum *prop = prop_get(actor->target.target_prop_index);
if (prop->enemy &&
!prop->dead &&
((prop->state >= _prop_state_becoming_unacknowledged &&
prop->state <= _prop_state_acknowledged) ||
prop->state == _prop_state_uninspected_orphan) &&
prop->distance > variant_definition->grenade_combat.grenade_range_lower_bound &&
prop->distance < variant_definition->grenade_combat.grenade_range_upper_bound)
{
*grenade_target = prop->body_position;
grenade_target->z += 0.2f;
*grenade_target_prop_index = actor->target.target_prop_index;
*grenade_ignore_object_index = prop->vehicle_index;
result = TRUE;
if (actor->external_orders.playfighting)
{
actor_combat_find_nearby_target(grenade_target, 1.5f);
}
}
}
return result;
}
boolean actor_combat_check_collateral_damage(
long actor_index,
real enemy_radius,
real collateral_damage_radius,
real_point3d const *test_point,
short *threat_count)
{
struct actor_datum *actor = actor_get(actor_index);
boolean result;
short threats;
short counted_actor_count;
long counted_actor_indices[MAXIMUM_COLLATERAL_DAMAGE_ACTORS];
/* Preserve the original tag validation before iterating combat state. */
actor_variant_definition_get(actor->meta.variant_definition_index);
result = TRUE;
threats = 0;
counted_actor_count = 0;
{
struct prop_iterator prop_iterator;
struct prop_datum *prop;
prop_iterator_new(&prop_iterator, actor_index);
for (prop = prop_iterator_next(&prop_iterator);
prop;
prop = prop_iterator_next(&prop_iterator))
{
if (prop->state >= _prop_state_becoming_unacknowledged &&
prop->state <= _prop_state_acknowledged &&
!prop->dead)
{
if (prop->enemy)
{
if (actor_combat_distance_squared3d_inline(&prop->body_position, test_point) <
enemy_radius*enemy_radius)
{
if (prop->player)
{
threats += 10;
}
else if (prop->vehicle_index != NONE)
{
threats += 5;
}
else if (prop->actor_index != NONE)
{
if (counted_actor_count < NUMBEROF(counted_actor_indices))
{
counted_actor_indices[counted_actor_count++] = prop->actor_index;
}
if (prop->swarm)
{
threats += actor_get(prop->actor_index)->meta.swarm_unit_count;
}
else
{
threats++;
}
}
}
}
else if (collateral_damage_radius > 0.0f &&
actor_combat_distance_squared3d_inline(&prop->body_position, test_point) <
collateral_damage_radius*collateral_damage_radius)
{
result = FALSE;
break;
}
}
}
}
if (enemy_radius > 0.0f && actor->target.target_prop_index != NONE)
{
struct prop_datum *target_prop = prop_get(actor->target.target_prop_index);
if (target_prop->actor_index != NONE)
{
struct actor_datum *target_actor = actor_get(target_prop->actor_index);
if (target_actor->meta.encounter_index != NONE)
{
struct encounter_actor_iterator actor_iterator;
struct actor_datum *enemy_actor;
encounter_actor_iterator_new(
&actor_iterator,
target_actor->meta.encounter_index);
for (enemy_actor = encounter_actor_iterator_next(&actor_iterator);
enemy_actor;
enemy_actor = encounter_actor_iterator_next(&actor_iterator))
{
short index;
boolean already_counted = FALSE;
for (index = 0; index < counted_actor_count; index++)
{
if (counted_actor_indices[index] == actor_iterator.index)
{
already_counted = TRUE;
break;
}
}
if (!already_counted &&
actor_combat_distance_squared3d_inline(
&enemy_actor->input.position.body_position,
test_point) < enemy_radius*enemy_radius)
{
if (enemy_actor->meta.swarm)
{
threats += enemy_actor->meta.swarm_unit_count;
}
else
{
threats++;
}
}
}
}
}
}
if (result && actor->meta.encounter_index != NONE && collateral_damage_radius > 0.0f)
{
struct encounter_actor_iterator actor_iterator;
struct actor_datum *friend_actor;
encounter_actor_iterator_new(
&actor_iterator,
actor->meta.encounter_index);
for (friend_actor = encounter_actor_iterator_next(&actor_iterator);
friend_actor;
friend_actor = encounter_actor_iterator_next(&actor_iterator))
{
if (actor_combat_distance_squared3d_inline(
&friend_actor->input.position.body_position,
test_point) < collateral_damage_radius*collateral_damage_radius)
{
result = FALSE;
break;
}
}
}
if (threat_count)
{
*threat_count = threats;
}
return result;
}
boolean actor_combat_plan_grenade_trajectory(
long actor_index,
short trajectory_type,
real_point3d const *grenade_target,
long grenade_target_prop_index,
long grenade_ignore_object_index)
{
struct actor_datum *actor = actor_get(actor_index);
struct actor_variant_definition *variant_definition =
actor_variant_definition_get(actor->meta.variant_definition_index);
real_point3d origin = actor->input.position.head_position;
real_vector3d aim_vector;
real_vector3d arc_initial_velocity;
real aim_speed;
real aim_ticks;
real aim_gravity;
boolean result = FALSE;
if (actor_combat_build_grenade_trajectory(
variant_definition->grenade_combat.grenade_type,
&origin,
variant_definition->grenade_combat.grenade_maximum_velocity,
grenade_target,
NULL,
FALSE,
&aim_vector,
&aim_speed,
&aim_ticks,
&arc_initial_velocity,
&aim_gravity) &&
ai_test_ballistic_line_of_fire(
actor_index,
&origin,
aim_ticks,
&arc_initial_velocity,
aim_gravity,
grenade_ignore_object_index,
actor->input.vehicle_index != NONE))
{
actor->control.grenade_current_target = *grenade_target;
actor->control.grenade_current_prop_index = grenade_target_prop_index;
actor->control.grenade_current_ignore_object_index = grenade_ignore_object_index;
actor->control.grenade_current_aim_vector = aim_vector;
actor->control.grenade_current_aim_speed = aim_speed;
actor->control.grenade_current_lob = FALSE;
result = TRUE;
}
return result;
}
static boolean actor_combat_allow_special_fire_situation(
long actor_index,
short special_fire_situation)
{
struct actor_datum *actor = actor_get(actor_index);
boolean result = TRUE;
if (special_fire_situation == _actor_special_fire_situation_strafing)
{
struct prop_datum *prop;
match_assert(
"c:\\halo\\SOURCE\\ai\\actor_combat.c",
970,
actor->control.current_fire_target_type == _actor_fire_target_prop);
prop = prop_get(actor->control.current_fire_target_prop_index);
if (prop->vehicle_index != NONE)
{
result = TRUE;
}
else if (prop->player)
{
result = FALSE;
}
else
{
short enemy_count = 0;
actor_combat_check_collateral_damage(
actor_index,
6.0f,
0.0f,
&prop->body_position,
&enemy_count);
result = enemy_count >= 3;
}
}
return result;
}
long actor_aim_projectile(
long actor_index,
real_point3d const *origin,
real_vector3d *vector,
real *error_reference)
{
struct actor_datum *actor = actor_get(actor_index);
long target_unit_index = NONE;
if (actor->control.fire_state == _actor_fire_state_bursting)
{
struct actor_debug_info *actor_debug_info =
&actor_debug_array[DATUM_INDEX_TO_ABSOLUTE_INDEX(actor_index)];
actor_debug_info->last_projectile_aiming_time = game_time_get();
if (actor->control.current_fire_target_type == _actor_fire_target_prop &&
actor->control.current_fire_target_prop_index != NONE)
{
struct prop_datum *prop =
prop_get(actor->control.current_fire_target_prop_index);
if (prop->state >= _prop_state_becoming_unacknowledged &&
prop->state <= _prop_state_acknowledged)
{
target_unit_index = prop->unit_index;
}
}
match_assert(
"c:\\halo\\SOURCE\\ai\\actor_combat.c",
1071,
vector);
actor_debug_info->field_60 = actor->control.burst_aim_by_vector;
actor_debug_info->field_64 = *origin;
if (actor->control.burst_aim_by_vector)
{
*vector = actor->control.burst_aim_vector;
}
else
{
real_point3d const *burst_target = &actor->control.burst_target;
actor_debug_info->field_7C = *burst_target;
vector->i = burst_target->x - origin->x;
vector->j = burst_target->y - origin->y;
vector->k = burst_target->z - origin->z;
normalize3d(vector);
}
match_assert_valid_real_normal3d(
"c:\\halo\\SOURCE\\ai\\actor_combat.c",
1090,
vector);
actor_debug_info->field_70 = *vector;
{
real_vector3d weapon_vector;
actor_get_weapon_vector(actor_index, &weapon_vector);
if (dot_product3d(&weapon_vector, vector) < GRENADE_AIMING_ANGLE_COSINE)
{
real_vector3d rotation_axis;
boolean aiming_success = TRUE;
cross_product3d(&weapon_vector, vector, &rotation_axis);
if (normalize3d(&rotation_axis) == 0.0f)
{
perpendicular3d(&weapon_vector, &rotation_axis);
if (normalize3d(&rotation_axis) == 0.0f)
{
aiming_success = FALSE;
}
}
*vector = weapon_vector;
if (aiming_success)
{
rotate_vector_about_axis(
vector,
&rotation_axis,
GRENADE_AIMING_ANGLE_SINE,
GRENADE_AIMING_ANGLE_COSINE);
}
actor_debug_info->field_88 = TRUE;
actor_debug_info->field_98 = weapon_vector;
actor_debug_info->field_8C = *vector;
}
else
{
actor_debug_info->field_88 = FALSE;
}
}
*error_reference = actor->control.burst_error;
}
return target_unit_index;
}
static void actor_start_burst(
long actor_index)
{
struct actor_datum *actor = actor_get(actor_index);
struct actor_variant_definition *firing_variant_definition =
actor_combat_get_firing_variant_definition(actor_index);
boolean use_nearby_target = FALSE;
struct actor_burst_geometry *burst_geometry;
struct actor_firing_pattern *firing_pattern;
real new_target_time;
if (actor->control.next_burst_secondary &&
!actor_combat_allow_special_fire_situation(
actor_index,
firing_variant_definition->ranged_combat.special_fire_situation))
{
actor->control.next_burst_secondary = FALSE;
}
actor->control.fire_burst_secondary = actor->control.next_burst_secondary;
actor->control.next_burst_secondary = FALSE;
if (actor->input.vehicle_index == NONE)
{
actor->control.firing_while_moving =
actor->input.in_midair || actor->control.moving;
}
else
{
struct unit_datum *vehicle = vehicle_get(actor->input.vehicle_index);
actor->control.firing_while_moving =
actor_combat_magnitude_squared3d_inline(&vehicle->object.translational_velocity) > 1.0f;
}
new_target_time = firing_variant_definition->ranged_combat.new_target_pattern_time;
new_target_time *= game_difficulty_get_team_value(
_game_difficulty_value_new_target_delay,
actor->meta.team_index);
actor->control.firing_at_new_target =
actor->control.current_fire_target_timer < new_target_time*TICKS_PER_SECOND;
actor_combat_get_firing_parameters(
actor_index,
firing_variant_definition,
&burst_geometry,
&firing_pattern);
{
real burst_duration;
if (actor->orders.combat.override_burst_duration > 0.0f)
{
burst_duration = actor->orders.combat.override_burst_duration;
}
else
{
burst_duration = actor_combat_real_random_range_inline(
burst_geometry->burst_duration_lower_bound,
burst_geometry->burst_duration_upper_bound);
if (firing_pattern && firing_pattern->burst_duration_modifier != 0.0f)
{
burst_duration *= firing_pattern->burst_duration_modifier;
}
if (actor->external_orders.playfighting)
{
burst_duration *= 0.6f;
}
}
actor->control.fire_state_timer = (short)(burst_duration*TICKS_PER_SECOND);
}
{
real burst_error = firing_variant_definition->ranged_combat.projectile_error_angle;
burst_error *= game_difficulty_get_team_value(
_game_difficulty_value_projectile_error,
actor->meta.team_index);
if (firing_pattern && firing_pattern->error_angle_modifier != 0.0f)
{
burst_error *= firing_pattern->error_angle_modifier;
}
if (actor->external_orders.playfighting)
{
burst_error = 2.0f*burst_error + DEGREES_TO_RADIANS(1.0f);
}
actor->control.burst_error = burst_error;
}
actor->control.burst_damage_modifier = 0.0f;
if (firing_variant_definition->ranged_combat.weapon_damage_modifier > 0.0f)
{
actor->control.burst_damage_modifier =
firing_variant_definition->ranged_combat.weapon_damage_modifier;
if (ai_debug.print_damage_modifiers)
{
console_printf(
FALSE,
"%s: manual damage modifier %.2f",
tag_name_strip_path(tag_get_name(actor->meta.variant_definition_index)),
actor->control.burst_damage_modifier);
}
}
else if (firing_variant_definition->ranged_combat.damage_per_second > 0.0f)
{
long weapon_index = actor_get_weapon(actor_index);
if (weapon_index != NONE)
{
struct weapon_datum *weapon = weapon_get(weapon_index);
real rate_of_fire;
real projectile_damage = weapon_definition_get_damage_potential(
weapon->definition_index,
&rate_of_fire);
real damage_per_second;
if (firing_variant_definition->ranged_combat.rate_of_fire > 0.0f &&
rate_of_fire > firing_variant_definition->ranged_combat.rate_of_fire)
{
rate_of_fire = firing_variant_definition->ranged_combat.rate_of_fire;
}
damage_per_second = rate_of_fire*projectile_damage;
if (damage_per_second > 0.0f)
{
actor->control.burst_damage_modifier =
firing_variant_definition->ranged_combat.damage_per_second/damage_per_second;
if (ai_debug.print_damage_modifiers)
{
console_printf(
FALSE,
"%s: proj %.1f rof %.1f dmg/s %.1f -> to get %.1f mod= %.2f",
tag_name_strip_path(tag_get_name(actor->meta.variant_definition_index)),
projectile_damage,
rate_of_fire,
damage_per_second,
firing_variant_definition->ranged_combat.damage_per_second,
actor->control.burst_damage_modifier);
}
}
}
}
if (actor->control.fire_burst_secondary || actor->control.overcharging_weapon)
{
if (firing_variant_definition->ranged_combat.special_damage_modifier > 0.0f)
{
actor->control.burst_damage_modifier *=
firing_variant_definition->ranged_combat.special_damage_modifier;
}
actor->control.burst_error +=
firing_variant_definition->ranged_combat.special_projectile_error;
}
if (firing_variant_definition->ranged_combat.weapon_bombardment_range > 0.0f &&
actor->control.current_fire_target_type == _actor_fire_target_prop)
{
struct prop_datum *prop = prop_get(actor->control.current_fire_target_prop_index);
use_nearby_target =
prop->state < _prop_state_becoming_unacknowledged ||
prop->state > _prop_state_acknowledged ||
prop->visibility == _actor_perception_none;
}
{
real_point3d target = actor->control.current_fire_target_position;
real_vector3d aim_vector;
real_vector3d burst_relative_position;
real_vector3d burst_adjustment;
real origin_angle;
real return_angle;
real origin_radius;
real return_radius;
if (use_nearby_target)
{
actor_combat_find_nearby_target(
&target,
firing_variant_definition->ranged_combat.weapon_bombardment_range);
}
{
real_vector3d target_direction;
real_vector3d const up = {0.0f, 0.0f, 1.0f};
actor_combat_vector_from_points3d_inline(
&actor->input.position.head_position,
&target,
&target_direction);
cross_product3d(&target_direction, &up, &aim_vector);
normalize3d(&aim_vector);
if (random_boolean())
{
negate_vector3d(&aim_vector, &aim_vector);
}
}
origin_angle = actor_combat_real_random_range_inline(
-burst_geometry->burst_origin_angle,
burst_geometry->burst_origin_angle);
return_angle = actor_combat_real_random_range_inline(
-burst_geometry->burst_return_angle,
burst_geometry->burst_return_angle) + origin_angle;
origin_radius = game_difficulty_get_team_value(
_game_difficulty_value_burst_error,
actor->meta.team_index)*burst_geometry->burst_origin_radius;
return_radius = actor_combat_real_random_range_inline(
burst_geometry->burst_return_length_lower_bound,
burst_geometry->burst_return_length_upper_bound);
return_radius *= game_difficulty_get_team_value(
_game_difficulty_value_burst_error,
actor->meta.team_index);
if (actor->external_orders.playfighting)
{
origin_radius *= 2.0f;
return_radius *= 2.0f;
}
if (actor->control.fire_state_timer > 0 &&
burst_geometry->burst_maximum_angular_velocity > 0.0f)
{
real burst_ticks = actor->control.fire_state_timer;
real sweep_angle = MIN(
burst_ticks*burst_geometry->burst_maximum_angular_velocity*(1.0f/TICKS_PER_SECOND),
_pi/4.0f);
real maximum_origin_radius = tangent(sweep_angle)*actor->control.current_fire_target_range;
if (origin_radius > maximum_origin_radius)
{
if (origin_radius < maximum_origin_radius*1.5f)
{
actor->control.fire_state_timer =
(short)fast_ftol(burst_ticks*(origin_radius/maximum_origin_radius));
}
else
{
actor->control.fire_state_timer = (short)fast_ftol(burst_ticks*1.5f);
maximum_origin_radius *= 1.5f;
return_radius = maximum_origin_radius/origin_radius*return_radius;
origin_radius = maximum_origin_radius;
}
}
}
{
real origin_cosine = cosine(origin_angle);
real origin_sine = sine(origin_angle);
real return_cosine = cosine(return_angle);
real return_sine = sine(return_angle);
burst_relative_position.i = (origin_cosine*aim_vector.i + 0.0f*origin_sine)*origin_radius;
burst_relative_position.j = (origin_cosine*aim_vector.j + 0.0f*origin_sine)*origin_radius;
burst_relative_position.k = (origin_cosine*aim_vector.k + origin_sine)*origin_radius;
burst_adjustment.i = -((return_cosine*aim_vector.i + return_sine*0.0f)*return_radius);
burst_adjustment.j = -((return_cosine*aim_vector.j + return_sine*0.0f)*return_radius);
burst_adjustment.k = -((return_cosine*aim_vector.k + return_sine)*return_radius);
}
if (actor->control.fire_state_timer > 0)
{
real inverse_ticks = 1.0f/actor->control.fire_state_timer;
burst_adjustment.i *= inverse_ticks;
burst_adjustment.j *= inverse_ticks;
burst_adjustment.k *= inverse_ticks;
}
actor->control.burst_initial_position = target;
actor->control.burst_relative_position = burst_relative_position;
actor->control.burst_adjustment = burst_adjustment;
add_vectors3d(
&actor->control.burst_initial_position,
&actor->control.burst_relative_position,
&actor->control.burst_target);
}
if (actor->state.combat_status >= _actor_combat_status_visible)
{
boolean traitor = FALSE;
long cause_unit_index = NONE;
short communication_type;
if (actor->control.current_fire_target_type == _actor_fire_target_prop)
{
struct prop_datum *prop = prop_get(actor->control.current_fire_target_prop_index);
traitor = prop->ally;
cause_unit_index = prop->unit_index;
}
if (actor->emotions.berserk)
{
communication_type = _ai_communication_shooting_berserk;
}
else if (traitor)
{
communication_type = _ai_communication_shooting_traitor;
}
else if (actor->situation.cumulative_threats[_actor_threat_visible] >= 5)
{
communication_type = _ai_communication_shooting_group;
}
else
{
communication_type = actor->input.vehicle_gunner ?
_ai_communication_shooting_vehicle :
_ai_communication_shooting;
}
ai_communication_event(
communication_type,
actor->meta.unit_index,
cause_unit_index,
_comm_hostility_enemy,
NONE,
NONE,
NULL);
}
return;
}
static boolean actor_combat_retarget_grenade(
long actor_index,
real_point3d const *desired_grenade_target)
{
struct actor_datum *actor = actor_get(actor_index);
struct actor_variant_definition *variant_definition =
actor_variant_definition_get(actor->meta.variant_definition_index);
boolean result = FALSE;
if (actor_combat_check_collateral_damage(
actor_index,
variant_definition->grenade_combat.enemy_radius,
variant_definition->grenade_combat.collateral_damage_radius,
desired_grenade_target,
NULL))
{
actor->control.grenade_current_target = *desired_grenade_target;
result = TRUE;
}
return result;
}
long actor_aim_grenade(
long actor_index,
real_point3d const *origin,
real_vector3d *vector)
{
struct actor_datum *actor = actor_get(actor_index);
long target_unit_index = NONE;
real_vector3d aim_vector;
if (actor->control.grenade_current_prop_index != NONE)
{
struct prop_datum *prop = prop_get(actor->control.grenade_current_prop_index);
if (prop->state >= _prop_state_becoming_unacknowledged &&
prop->state <= _prop_state_acknowledged)
{
target_unit_index = prop->unit_index;
}
if (prop->state < _prop_state_unacknowledged ||
prop->state > _prop_state_becoming_acknowledged)
{
real_point3d new_desired_target = prop->body_position;
new_desired_target.z += 0.2f;
actor_combat_retarget_grenade(
actor_index,
&new_desired_target);
}
}
actor_combat_reaim_grenade(actor_index, origin);
if (actor->input.vehicle_index == NONE)
{
real_vector2d horizontal_aim_vector;
aim_vector = actor->control.grenade_current_aim_vector;
horizontal_aim_vector = *(real_vector2d const *)&aim_vector;
if (normalize2d(&horizontal_aim_vector) > 0.0f &&
dot_product2d(
(real_vector2d const *)&actor->input.facing_vector,
&horizontal_aim_vector) < GRENADE_AIMING_ANGLE_COSINE)
{
real_vector3d new_aim_vector = actor->input.facing_vector;
boolean counterclockwise = cross_product2d(
(real_vector2d const *)&actor->input.facing_vector,
&horizontal_aim_vector) > 0.0f;
rotate_vector_about_axis(
&new_aim_vector,
global_up3d,
(counterclockwise ? 1 : -1)*GRENADE_AIMING_ANGLE_SINE,
GRENADE_AIMING_ANGLE_COSINE);
scale_vector2d(
(real_vector2d const *)&new_aim_vector,
magnitude2d((real_vector2d const *)&aim_vector),
(real_vector2d *)&new_aim_vector);
new_aim_vector.k = aim_vector.k;
match_assert_valid_real_normal3d(
"c:\\halo\\SOURCE\\ai\\actor_combat.c",
1865,
&new_aim_vector);
aim_vector = new_aim_vector;
}
}
/* BUG (preserved for exact matching): aim_vector is assigned only on foot.
* With actor->input.vehicle_index != NONE, January branches from its vehicle
* test straight to this scale and reads the unassigned local; the 2011 HCEX
* and later /Od builds keep the same path. A corrected build should start
* from actor->control.grenade_current_aim_vector.
*/
scale_vector3d(
&aim_vector,
actor->control.grenade_current_aim_speed,
vector);
return target_unit_index;
}
/* ---------- private code */
void actor_combat_update(
long actor_index)
{
struct actor_datum *actor = actor_get(actor_index);
struct actor_definition *actor_definition =
actor_definition_get(actor->meta.definition_index);
struct actor_variant_definition *actor_variant_definition =
actor_variant_definition_get(actor->meta.variant_definition_index);
struct actor_variant_definition *firing_variant_definition =
actor_combat_get_firing_variant_definition(actor_index);
struct weapon_definition *weapon_definition = actor_get_weapon_definition(actor_index);
struct unit_datum *unit = unit_get(actor->meta.unit_index);
boolean hold_burst_start = FALSE;
long weapon_index = actor_get_weapon(actor_index);
struct actor_debug_info *actor_debug_info =
&actor_debug_array[DATUM_INDEX_TO_ABSOLUTE_INDEX(actor_index)];
short firing_decision = NONE;
boolean fire;
if (actor->control.fire_state_timer > 0)
{
actor->control.fire_state_timer--;
}
if (actor->control.burst_disable_timer > 0)
{
actor->control.burst_disable_timer--;
}
if (actor->control.trigger_delay_timer > 0)
{
actor->control.trigger_delay_timer--;
}
if (actor->control.special_fire_delay > 0)
{
actor->control.special_fire_delay--;
}
if (actor->control.current_fire_target_type > _actor_fire_target_none)
{
actor->control.current_fire_target_timer++;
}
if (actor->control.fire_state != _actor_fire_state_bursting)
{
short fire_target_type = _actor_fire_target_none;
boolean target_changed;
if (actor->orders.combat.shoot_at_target)
{
if (actor->orders.combat.use_manual_target_point)
{
fire_target_type = _actor_fire_target_manual_point;
}
else if (actor->target.target_prop_index != NONE)
{
fire_target_type = _actor_fire_target_prop;
}
}
if (fire_target_type != actor->control.current_fire_target_type)
{
target_changed = TRUE;
}
else if (fire_target_type == _actor_fire_target_prop)
{
target_changed = actor->target.target_prop_index != actor->control.current_fire_target_prop_index;
}
else if (fire_target_type == _actor_fire_target_manual_point)
{
target_changed = distance_squared3d(
&actor->orders.combat.target_point,
&actor->control.current_fire_target_manual_point) > 0.25f;
}
else
{
target_changed = FALSE;
}
if (target_changed)
{
actor->control.current_fire_target_timer = 0;
}
actor->control.current_fire_target_type = fire_target_type;
if (fire_target_type == _actor_fire_target_prop)
{
actor->control.current_fire_target_prop_index = actor->target.target_prop_index;
match_assert(
"c:\\halo\\SOURCE\\ai\\actor_combat.c",
133,
actor->control.current_fire_target_prop_index != NONE);
}
else if (fire_target_type == _actor_fire_target_manual_point)
{
actor->control.current_fire_target_manual_point = actor->orders.combat.target_point;
}
}
actor->control.aiming_at_fire_target = FALSE;
actor->control.weapon_maximum_range = actor_has_ranged_weapon(actor_index) ?
firing_variant_definition->ranged_combat.maximum_firing_range :
0.0f;
if (actor->orders.combat.throw_grenade)
{
if (actor_variant_definition->grenade_combat.grenade_type != NONE &&
!unit_get_grenade_count(
actor->meta.unit_index,
actor_variant_definition->grenade_combat.grenade_type))
{
unit_add_grenade_type_to_inventory(
actor->meta.unit_index,
actor_variant_definition->grenade_combat.grenade_type,
1);
}
actor_unit_control_throw_grenade(actor_index);
ai_communication_event(
_ai_communication_grenade_throwing,
actor->meta.unit_index,
NONE,
NONE,
NONE,
NONE,
NULL);
fire = FALSE;
}
else if (!actor_has_ranged_weapon(actor_index))
{
fire = FALSE;
}
else if (actor->control.fire_state == _actor_fire_state_wild)
{
fire = TRUE;
}
else
{
boolean linear = TRUE;
match_assert(
"c:\\halo\\SOURCE\\ai\\actor_combat.c",
173,
weapon_index != NONE);
if (firing_variant_definition->ranged_combat.special_fire_mode > _actor_special_fire_mode_none &&
actor->control.fire_state != _actor_fire_state_bursting &&
actor->control.fire_state != _actor_fire_state_wild &&
actor->control.special_fire_delay <= 0 &&
actor->control.special_fire_deny_attempts <= 0)
{
struct weapon_definition *special_weapon_definition =
weapon_definition_get(weapon_get(weapon_index)->definition_index);
real special_fire_delay_scale = game_difficulty_get_team_value(
_game_difficulty_value_special_fire_delay,
actor->meta.team_index);
boolean special_fire = TRUE;
switch (firing_variant_definition->ranged_combat.special_fire_mode)
{
case _actor_special_fire_mode_overcharge:
{
real overcharge_chance = game_difficulty_get_team_value(
_game_difficulty_value_overcharge_chance,
actor->meta.team_index);
if (special_weapon_definition->weapon.triggers.count > 0)
{
struct weapon_trigger_definition *trigger = TAG_BLOCK_GET_ELEMENT(
&special_weapon_definition->weapon.triggers,
0,
struct weapon_trigger_definition);
}
else
{
special_fire = FALSE;
}
break;
}
case _actor_special_fire_mode_secondary:
special_fire = special_weapon_definition->weapon.triggers.count > 1;
break;
}
if (special_fire &&
actor_combat_enable_special_fire_situation(
actor_index,
firing_variant_definition->ranged_combat.special_fire_situation))
{
real special_fire_delay =
firing_variant_definition->ranged_combat.special_fire_delay + real_random_range(0.0f, 1.5f);
real special_fire_random = real_random();
actor->control.special_fire_delay = (short)(special_fire_delay*TICKS_PER_SECOND);
if (special_fire_random < firing_variant_definition->ranged_combat.special_fire_chance &&
actor_combat_allow_special_fire_situation(
actor_index,
firing_variant_definition->ranged_combat.special_fire_situation))
{
if (firing_variant_definition->ranged_combat.special_fire_situation ==
_actor_special_fire_situation_strafing)
{
actor->control.special_fire_deny_attempts = 3;
}
switch (firing_variant_definition->ranged_combat.special_fire_mode)
{
case _actor_special_fire_mode_overcharge:
actor->control.overcharging_weapon = TRUE;
break;
case _actor_special_fire_mode_secondary:
actor->control.next_burst_secondary = TRUE;
break;
}
}
}
}
if (actor->control.current_fire_target_type > _actor_fire_target_none)
{
if (actor->control.current_fire_target_type == _actor_fire_target_prop)
{
struct prop_datum *prop = prop_get(actor->control.current_fire_target_prop_index);
unsigned long const *combined_pvs;
actor->control.current_fire_target_range = prop->distance;
actor->control.current_fire_target_position = prop->center_of_mass;
actor->control.current_fire_target_line_of_sight = prop->line_of_sight;
actor->control.current_fire_target_underwater = prop->underwater;
combined_pvs = players_get_combined_pvs();
actor->control.current_fire_target_outside_active_area = TRUE;
if (prop->body_location.cluster_index != NONE)
{
actor->control.current_fire_target_outside_active_area =
!BIT_VECTOR_TEST_FLAG(combined_pvs, prop->body_location.cluster_index);
}
}
else
{
match_assert(
"c:\\halo\\SOURCE\\ai\\actor_combat.c",
296,
actor->control.current_fire_target_type == _actor_fire_target_manual_point);
actor->control.current_fire_target_position = actor->control.current_fire_target_manual_point;
actor->control.current_fire_target_range = distance3d(
&actor->input.position.head_position,
&actor->control.current_fire_target_manual_point);
actor->control.current_fire_target_underwater = FALSE;
actor->control.current_fire_target_outside_active_area = FALSE;
if ((actor->control.current_fire_target_timer % 10) == 0)
{
actor->control.current_fire_target_line_of_sight = ai_test_line_of_sight(
&actor->input.position.head_position,
actor->input.position.body_location.cluster_index,
&actor->control.current_fire_target_position,
NONE,
_ai_line_of_sight_normal,
FALSE,
NONE,
actor->input.vehicle_index != NONE);
}
}
actor->control.current_fire_target_superballistic = FALSE;
if (firing_variant_definition->ranged_combat.weapon_super_ballistic_range > 0.0f &&
actor->control.current_fire_target_range >
firing_variant_definition->ranged_combat.weapon_super_ballistic_range)
{
actor->control.current_fire_target_superballistic = TRUE;
}
actor->control.current_fire_target_bombardment =
actor->orders.combat.bombard_target &&
firing_variant_definition->ranged_combat.weapon_bombardment_range > 0.0f;
if (!weapon_aim(
weapon_index,
0,
&actor->input.position.head_position,
&actor->control.current_fire_target_position,
actor->control.current_fire_target_superballistic,
&actor->control.current_fire_target_aim_vector,
NULL,
&actor->control.current_fire_target_distance,
&linear))
{
actor->control.current_fire_target_type = _actor_fire_target_none;
}
}
if (actor->control.current_fire_target_type == _actor_fire_target_none)
{
firing_decision = _firing_no_target;
fire = FALSE;
}
else if (actor->control.current_fire_target_outside_active_area)
{
firing_decision = _firing_outside_active_area;
fire = FALSE;
}
else if (!actor->orders.combat.override_firing_restrictions &&
actor->control.burst_disable_timer > 0)
{
firing_decision = _firing_disabled;
fire = FALSE;
}
else if (actor_move_animation_busy(actor_index))
{
firing_decision = _firing_busy;
fire = FALSE;
}
else if (!actor->orders.combat.override_firing_restrictions &&
actor->input.in_midair &&
!actor->state.flying &&
!TEST_FLAG(actor_variant_definition->flags, _actor_variant_definition_can_shoot_while_flying_bit))
{
firing_decision = _firing_not_in_midair;
fire = FALSE;
}
else if (!actor->orders.combat.override_firing_restrictions &&
TEST_FLAG(actor_definition->flags, _actor_definition_must_crouch_to_fire_bit) &&
!actor->control.crouching)
{
firing_decision = _firing_not_crouching;
fire = FALSE;
}
else if (!actor->orders.combat.override_firing_restrictions &&
TEST_FLAG(actor_definition->flags2, _actor_definition_flags2_must_stand_to_fire_bit) &&
actor->control.crouching)
{
firing_decision = _firing_not_standing;
fire = FALSE;
}
else if (!actor->orders.combat.override_firing_restrictions &&
TEST_FLAG(actor_definition->flags2, _actor_definition_flags2_must_stop_to_fire_bit) &&
actor->control.moving)
{
firing_decision = _firing_not_stationary;
fire = FALSE;
}
else if (actor->control.current_fire_target_underwater || actor->input.underwater)
{
firing_decision = _firing_underwater;
fire = FALSE;
}
else if (weapon_definition &&
weapon_definition->weapon.ai_minimum_target_range > 0.0f &&
actor->control.current_fire_target_range < weapon_definition->weapon.ai_minimum_target_range)
{
firing_decision = _firing_min_range;
fire = FALSE;
}
else if (actor->orders.look.primary_priority == _primary_priority_none ||
actor->orders.look.primary_direction.type != _direction_specification_target ||
actor->control.aiming_away_from_primary)
{
firing_decision = _firing_wrong_target;
fire = FALSE;
}
else if (actor->control.fire_state == _actor_fire_state_bursting)
{
fire = TRUE;
}
else
{
actor->control.current_fire_target_visible =
actor->control.current_fire_target_line_of_sight == _ai_line_of_sight_clear ||
actor->control.current_fire_target_line_of_sight == _ai_line_of_sight_occluded;
if (!actor->control.current_fire_target_visible &&
!actor->control.current_fire_target_bombardment)
{
firing_decision = _firing_not_visible;
fire = FALSE;
}
else if (!actor->orders.combat.override_firing_restrictions &&
actor->control.current_fire_target_range >= actor->control.weapon_maximum_range)
{
firing_decision = _firing_outside_range;
fire = FALSE;
}
else
{
fire = TRUE;
actor->control.aiming_at_fire_target = TRUE;
actor_debug_info->burst_alignment.time = game_time_get();
if (TEST_FLAG(actor_definition->flags, _actor_definition_start_firing_before_aligned_bit))
{
actor_debug_info->burst_alignment.aligned = TRUE;
actor_debug_info->burst_alignment.aligned_immediately = TRUE;
}
else
{
real alignment_threshold = actor->control.current_fire_target_range < 1.5f ?
actor->control.current_fire_target_range*((0.97f - 0.70710677f)/1.5f) + 0.70710677f :
0.97f;
real_vector3d weapon_vector;
real alignment;
actor_get_weapon_vector(actor_index, &weapon_vector);
alignment = dot_product3d(&weapon_vector, &actor->control.current_fire_target_aim_vector);
if (alignment < alignment_threshold)
{
hold_burst_start = TRUE;
}
actor_debug_info->burst_alignment.weapon_vector = weapon_vector;
actor_debug_info->burst_alignment.aim_vector = actor->control.current_fire_target_aim_vector;
actor_debug_info->burst_alignment.alignment = alignment;
actor_debug_info->burst_alignment.threshold = alignment_threshold;
actor_debug_info->burst_alignment.aligned = alignment >= alignment_threshold;
actor_debug_info->burst_alignment.aligned_immediately = FALSE;
}
}
}
}
if (!fire)
{
actor->control.fire_state = _actor_fire_state_none;
}
if (actor->control.fire_state != _actor_fire_state_none)
{
match_assert(
"c:\\halo\\SOURCE\\ai\\actor_combat.c",
517,
firing_decision == NONE);
switch (actor->control.fire_state)
{
case _actor_fire_state_wild:
firing_decision = _firing_wild;
break;
case _actor_fire_state_holding:
firing_decision = actor->control.fire_state_timer ? _firing_holding : _firing_holding_for_line;
break;
case _actor_fire_state_bursting:
firing_decision = _firing_burst;
break;
case _actor_fire_state_pausing:
firing_decision = actor->control.fire_state_timer ? _firing_pausing : _firing_pausing_for_line;
break;
default:
match_vassert("c:\\halo\\SOURCE\\ai\\actor_combat.c", 538, FALSE, NULL);
break;
}
}
{
short new_state = NONE;
switch (actor->control.fire_state)
{
case _actor_fire_state_none:
if (fire)
{
new_state = _actor_fire_state_holding;
}
break;
case _actor_fire_state_bursting:
if (actor->control.fire_state_timer == 0)
{
new_state = _actor_fire_state_pausing;
}
break;
case _actor_fire_state_holding:
case _actor_fire_state_pausing:
if (!hold_burst_start && actor->control.fire_state_timer == 0)
{
new_state = _actor_fire_state_bursting;
}
break;
case _actor_fire_state_wild:
if (actor->control.fire_state_timer == 0)
{
new_state = _actor_fire_state_none;
}
break;
default:
match_vassert("c:\\halo\\SOURCE\\ai\\actor_combat.c", 582, FALSE, NULL);
break;
}
if (new_state != NONE)
{
match_assert(
"c:\\halo\\SOURCE\\ai\\actor_combat.c",
588,
new_state!=actor->control.fire_state);
switch (new_state)
{
case _actor_fire_state_holding:
if (!actor_start_first_burst_delay(actor_index, firing_variant_definition) &&
!hold_burst_start)
{
new_state = _actor_fire_state_bursting;
actor_start_burst(actor_index);
}
break;
case _actor_fire_state_bursting:
match_assert(
"c:\\halo\\SOURCE\\ai\\actor_combat.c",
611,
!hold_burst_start);
actor_start_burst(actor_index);
break;
case _actor_fire_state_pausing:
actor_start_pause(actor_index);
break;
}
actor->control.fire_state = new_state;
}
}
{
boolean fire_primary = FALSE;
boolean fire_secondary = FALSE;
actor->control.burst_aim_by_vector = FALSE;
if (actor->control.fire_state == _actor_fire_state_wild)
{
fire_primary = TRUE;
}
else if (actor->control.fire_state == _actor_fire_state_bursting)
{
long ignore_unit_index = NONE;
real_point3d origin;
real_vector3d fire_vector;
long blocking_prop_index;
boolean linear;
boolean aiming_success;
match_assert(
"c:\\halo\\SOURCE\\ai\\actor_combat.c",
640,
weapon_index != NONE);
actor->control.burst_origin = actor->control.burst_initial_position;
actor_debug_info->burst_tracked_position = actor->control.burst_initial_position;
actor_debug_info->burst_lead_vector = *global_zero_vector3d;
if (actor->control.current_fire_target_type == _actor_fire_target_prop)
{
struct prop_datum *prop = prop_get(actor->control.current_fire_target_prop_index);
real target_tracking;
real target_leading;
actor_debug_info->burst_last_known_position = prop->center_of_mass;
ignore_unit_index = prop->attached_to_unit_index;
target_tracking = firing_variant_definition->ranged_combat.target_track_fraction;
target_tracking += game_difficulty_get_team_value(
_game_difficulty_value_target_tracking,
actor->meta.team_index);
if (MIN(1.0f, target_tracking) > 0.0f &&
!actor->control.current_fire_target_bombardment)
{
real_vector3d tracking_vector;
vector_from_points3d(
&actor->control.burst_initial_position,
&prop->center_of_mass,
&tracking_vector);
point_from_line3d(
&actor->control.burst_origin,
&tracking_vector,
firing_variant_definition->ranged_combat.target_track_fraction,
&actor->control.burst_origin);
}
actor_debug_info->burst_tracked_position = actor->control.burst_origin;
target_leading = firing_variant_definition->ranged_combat.target_lead_fraction;
target_leading += game_difficulty_get_team_value(
_game_difficulty_value_target_leading,
actor->meta.team_index);
if (MIN(1.0f, target_leading) > 0.0f)
{
real time_to_target = weapon_estimate_time_to_target(
weapon_index,
actor->control.fire_burst_secondary != FALSE,
actor->control.current_fire_target_distance);
real_vector3d lead_vector;
lead_vector.i = time_to_target*prop->velocity.i;
lead_vector.j = time_to_target*prop->velocity.j;
lead_vector.k = time_to_target*prop->velocity.k;
actor_debug_info->burst_lead_vector = lead_vector;
point_from_line3d(
&actor->control.burst_origin,
&lead_vector,
firing_variant_definition->ranged_combat.target_lead_fraction,
&actor->control.burst_origin);
}
}
else
{
match_assert(
"c:\\halo\\SOURCE\\ai\\actor_combat.c",
707,
actor->control.current_fire_target_type == _actor_fire_target_manual_point);
actor_debug_info->burst_last_known_position = actor->control.burst_origin;
}
{
real_vector3d *burst_relative_position = &actor->control.burst_relative_position;
add_vectors3d(
&actor->control.burst_adjustment,
burst_relative_position,
burst_relative_position);
add_vectors3d(
burst_relative_position,
&actor->control.burst_origin,
&actor->control.burst_target);
}
if (actor->input.vehicle_index != NONE)
{
unit_get_camera_position(actor->meta.unit_index, &origin);
}
else
{
real_vector3d *gun_offset = NULL;
if (actor->control.crouching)
{
if (magnitude_squared3d(&firing_variant_definition->ranged_combat.gun_offset_crouch) > _real_epsilon)
{
gun_offset = &firing_variant_definition->ranged_combat.gun_offset_crouch;
}
else if (magnitude_squared3d(&actor_definition->perception.gun_offset_crouch) > _real_epsilon)
{
gun_offset = &actor_definition->perception.gun_offset_crouch;
}
}
else
{
if (magnitude_squared3d(&firing_variant_definition->ranged_combat.gun_offset_stand) > _real_epsilon)
{
gun_offset = &firing_variant_definition->ranged_combat.gun_offset_stand;
}
else if (magnitude_squared3d(&actor_definition->perception.gun_offset_stand) > _real_epsilon)
{
gun_offset = &actor_definition->perception.gun_offset_stand;
}
}
if (!gun_offset)
{
origin = actor->input.position.head_position;
}
else
{
real_vector3d facing;
actor_combat_vector_from_points3d_inline(
&actor->control.burst_target,
&actor->input.position.body_position,
&facing);
if (normalize2d((real_vector2d *)&facing) > 0.0f)
{
facing.k = 0.0f;
}
else
{
facing = actor->input.facing_vector;
}
unit_estimate_position(
actor->meta.unit_index,
_unit_estimate_gun_position,
&actor->input.position.body_position,
&facing,
gun_offset,
&origin);
}
}
aiming_success = weapon_aim(
weapon_index,
actor->control.fire_burst_secondary != FALSE,
&origin,
&actor->control.burst_target,
actor->control.current_fire_target_superballistic,
&actor->control.burst_aim_vector,
NULL,
NULL,
&linear);
match_assert(
"c:\\halo\\SOURCE\\ai\\actor_combat.c",
784,
aiming_success);
match_assert_valid_real_normal3d(
"c:\\halo\\SOURCE\\ai\\actor_combat.c",
785,
&actor->control.burst_aim_vector);
actor->control.burst_aim_by_vector = !linear;
actor_combat_vector_from_points3d_inline(
&origin,
&actor->control.burst_target,
&fire_vector);
if (ai_test_line_of_fire(
actor_index,
ignore_unit_index,
&origin,
&fire_vector,
&blocking_prop_index))
{
actor->control.blocked_communication_timer = 0;
if (actor->control.fire_burst_secondary)
{
fire_secondary = TRUE;
}
else
{
fire_primary = TRUE;
}
}
else
{
actor->control.blocked_communication_timer++;
actor->control.fire_state_timer++;
firing_decision = _firing_blocked;
if (actor->control.blocked_communication_timer >= 45 &&
actor->target.target_type >= _actor_target_acknowledged_enemy)
{
long cause_unit_index = NONE;
if (blocking_prop_index != NONE)
{
cause_unit_index = prop_get(blocking_prop_index)->unit_index;
}
ai_communication_event(
_ai_communication_blocked,
actor->meta.unit_index,
cause_unit_index,
_comm_hostility_friend,
NONE,
NONE,
NULL);
actor->control.blocked_communication_timer = 0;
}
}
}
{
boolean primary_trigger = FALSE;
boolean secondary_trigger = FALSE;
real primary_trigger_value = 0.0f;
if (fire_primary)
{
real rate_of_fire = firing_variant_definition->ranged_combat.rate_of_fire;
if (actor->control.overcharging_weapon)
{
actor->control.overcharging_weapon = FALSE;
}
else if (rate_of_fire == 0.0f)
{
primary_trigger = TRUE;
primary_trigger_value = 1.0f;
}
else if (actor->control.trigger_delay_timer == 0)
{
struct actor_burst_geometry *burst_geometry;
struct actor_firing_pattern *firing_pattern;
short trigger_delay;
primary_trigger = TRUE;
primary_trigger_value = 1.0f;
rate_of_fire *= game_difficulty_get_team_value(
_game_difficulty_value_rate_of_fire,
actor->meta.team_index);
actor_combat_get_firing_parameters(
actor_index,
firing_variant_definition,
&burst_geometry,
&firing_pattern);
if (firing_pattern && firing_pattern->rate_of_fire_modifier > 0.0f)
{
rate_of_fire *= firing_pattern->rate_of_fire_modifier;
}
actor_debug_info->shooting_rof = rate_of_fire;
trigger_delay = (short)fast_ftol(TICKS_PER_SECOND/rate_of_fire);
actor->control.trigger_delay_timer = MAX(2, trigger_delay);
}
}
else if (fire_secondary)
{
if (actor->control.overcharging_weapon)
{
actor->control.overcharging_weapon = FALSE;
}
else
{
secondary_trigger = TRUE;
}
}
else if (actor->control.overcharging_weapon)
{
primary_trigger = TRUE;
primary_trigger_value = 1.0f;
}
actor_unit_control_primary_trigger(actor_index, primary_trigger, primary_trigger_value);
actor_unit_control_secondary_trigger(actor_index, secondary_trigger);
}
}
actor_debug_info->firing_decision = firing_decision;
return;
}