Files
Stefan MaierandClaude Opus 5.5 8097ac23b0 Merge OpenCE upstream: network version 32, HDR, model detail, texture filtering, co-op options, aim assist policy, its local bots, 5.1 sound
Ringfall's side of it:
- The bots: OpenCE's own (bots.c, a local split screen game's) are not
  used in VR builds (their lobby hook would take Ringfall's host bots
  off, and their bots.count and bots.skill differ); Ringfall's are now
  host_bots.c, so that OpenCE's bots.c merges as it changes. Each
  platform's bots.count and bots.skill are its own (config_setting_index
  takes this platform's of two of a name).
- HDR is never on in VR (d3d8_gl.c); the flat window's present is
  OpenCE's, inside the VR port's branch for it.
- The VR frame begins where the events are pumped (sdl_platform.c), as
  before, now that exit_after_check is apart.
- The message numbers: OpenCE's aim assist policy (90) before the VR
  port's poses (240), which stay last.
- The visibility counters keep the VR port's snapshot ring (both eyes'
  counts halved); OpenCE's tone mapping goes before the antialiasing, off
  the VR eyes' pass.
- Join codes, the online list and OpenCE's probes all in the signalling
  topics; the HUD's PNG helpers both ways; the disc import keeps the
  language file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 19:10:30 +02:00

3460 lines
115 KiB
C

/*
FIRST_PERSON_WEAPONS.C
symbols in this file:
000CBC60 0020:
_animation_update_render_only (0000)
000CBC80 0040:
_first_person_weapons_initialize (0000)
000CBCC0 0010:
_first_person_weapons_dispose (0000)
000CBCD0 0050:
_first_person_weapons_initialize_for_new_map (0000)
000CBD20 0010:
_first_person_weapons_dispose_from_old_map (0000)
000CBD30 00c0:
_code_000cbd30 (0000)
000CBDF0 0110:
_code_000cbdf0 (0000)
000CBF00 0120:
_code_000cbf00 (0000)
000CC020 0040:
_code_000cc020 (0000)
000CC060 00a0:
_code_000cc060 (0000)
000CC100 00b0:
_code_000cc100 (0000)
000CC1B0 00e0:
_code_000cc1b0 (0000)
000CC290 0060:
_code_000cc290 (0000)
000CC2F0 0040:
_code_000cc2f0 (0000)
000CC330 0080:
_code_000cc330 (0000)
000CC3B0 0290:
_first_person_weapon_draw (0000)
000CC640 0080:
_first_person_weapon_get_local_index (0000)
000CC6C0 00d0:
_first_person_weapon_get_marker_by_name (0000)
000CC790 00e0:
_first_person_weapon_center_flashlight (0000)
000CC870 00d0:
_first_person_weapon_adjust_light (0000)
000CC940 00c0:
_first_person_weapon_get_node_matrix (0000)
000CCA00 00b0:
_code_000cca00 (0000)
000CCAB0 0560:
_code_000ccab0 (0000)
000CD010 00b0:
_first_person_weapon_render_update (0000)
000CD0C0 0040:
_first_person_weapon_get_marker_by_name_render (0000)
000CD100 02b0:
_code_000cd100 (0000)
000CD3B0 0260:
_code_000cd3b0 (0000)
000CD610 0060:
_code_000cd610 (0000)
000CD670 0220:
_code_000cd670 (0000)
000CD890 0050:
_first_person_weapon_message_from_unit (0000)
000CD8E0 0040:
_first_person_weapon_message_from_weapon (0000)
000CD920 0170:
_code_000cd920 (0000)
000CDA90 0600:
_code_000cda90 (0000)
000CE090 00f0:
_first_person_weapons_update (0000)
0027076C 0015:
??_C@_0BF@MEAEHHHF@first_person_weapons?$AA@ (0000)
00270784 0030:
??_C@_0DA@LGOECHKB@c?3?2halo?2SOURCE?2interface?2first_p@ (0000)
002707B4 0015:
??_C@_0BF@LKJCKPFC@first?5person?5weapons?$AA@ (0000)
002707CC 0028:
??_C@_0CI@FFNKKBPO@first_person_weapon?9?$DOweapon_inde@ (0000)
002707F8 0059:
??_C@_0FJ@DFINBDBK@animation_graph_node_index?$DO?$DN0?5?$CG?$CG@ (0000)
00270854 000b:
??_C@_0L@HGCOMEEN@flashlight?$AA@ (0000)
00270860 0039:
??_C@_0DJ@KDPHKICA@node_index?$DO?$DN0?5?$CG?$CG?5node_index?$DManim@ (0000)
0027089C 0035:
??_C@_0DF@COECBIGL@local?5player?5?$CFd?0?5weapon?5?$CI0x?$CFx?$CJ?0?5@ (0000)
002708D8 00a5:
??_C@_0KF@OCJNMLNK@first_person_weapon?9?$DOshotgun_rel@ (0000)
00270980 0004:
__real@c1f00000 (0000)
00453CC8 0004:
_bss_00453cc8 (0000)
*/
/* ---------- headers */
#define REAL_MATH_EXTERNAL_SIGNED_ANGULAR_DIFFERENCE
#define REAL_MATH_EXTERNAL_REAL_LOCAL_RANDOM
#define REAL_MATH_EXTERNAL_REAL_LOCAL_RANDOM_RANGE
#include "interface/first_person_weapons.h"
#include "cache/predicted_resources.h"
#include "camera/director.h"
#include "cseries/cseries.h"
#include "cseries/errors.h"
#include "effects/effects.h"
#include "effects/particles.h"
#include "game/game_globals.h"
#include "game/players.h"
#include "items/weapon_datum_flags.h"
#include "items/weapon_definitions.h"
#include "items/weapons.h"
#include "models/model_animation_definitions.h"
#include "models/model_definitions.h"
#include "models/models.h"
#include "networking/network_connection.h"
#include "objects/objects.h"
#include "rasterizer/rasterizer_model_types.h"
#include "render/render.h"
#include "saved games/game_state.h"
#include "scenario/scenario.h"
#include "sound/game_sound.h"
#include "sound/sound_manager.h"
#include "units/units.h"
#ifdef HALO_ANDROID
#include "halo_vr.h"
#include "vr_arms.h"
#include "vr_body.h"
#endif
#include "view_fov.h" /* port: port/linux/game/view_fov.c */
#include "shaders/shader_definitions.h" /* port: (the shield's flare on the arms) */
#include "shaders/shaders.h"
/* port: port/linux/game/shield_color.c */
real_rgb_color const *shield_color_colors(long unit_index, real_rgb_color const *colors);
/* port: port/linux/game/overshield_effect.c */
real const *overshield_effect_values(long unit_index, real const *values, boolean arms);
/* ---------- constants */
enum
{
_render_model_first_person_bit = 3,
};
enum
{
NUMBER_OF_FIRST_PERSON_WEAPON_OVERLAY_FRAMES = 9,
FIRST_PERSON_WEAPON_RELOAD_SETTLE_TICKS = 44,
};
#define FIRST_PERSON_WEAPON_FIRING_PUSH_BACK_VELOCITY 0.05f
enum first_person_weapon_state
{
_first_person_weapon_state_idle = 0,
_first_person_weapon_state_overheating,
_first_person_weapon_state_overheating_again,
_first_person_weapon_state_overheated,
_first_person_weapon_state_charged,
_first_person_weapon_state_posing,
_first_person_weapon_state_primary_fire,
_first_person_weapon_state_secondary_fire,
_first_person_weapon_state_primary_misfire,
_first_person_weapon_state_secondary_misfire,
_first_person_weapon_state_melee,
_first_person_weapon_state_light_on,
_first_person_weapon_state_light_off,
_first_person_weapon_state_reload_while_empty,
_first_person_weapon_state_reload_while_full,
_first_person_weapon_state_shotgun_enter_reload,
_first_person_weapon_state_shotgun_exit_reload_empty,
_first_person_weapon_state_shotgun_exit_reload_full,
_first_person_weapon_state_put_away,
_first_person_weapon_state_ready,
_first_person_weapon_state_throw_grenade,
_first_person_weapon_state_throw_grenade_overheated,
_first_person_weapon_state_overheated_exit,
_first_person_weapon_state_overheating_super_recoil,
NUMBER_OF_FIRST_PERSON_WEAPON_STATES,
};
enum
{
_render_model_effect_type_none = 0,
_render_model_effect_type_active_camouflage,
};
enum
{
animation_update_kind_render_only = 0,
};
enum animation_update_result
{
_animation_no_key_frame = 0,
_animation_key_frame,
_animation_will_restart_on_next_frame,
};
/* ---------- macros */
/* ---------- structures */
struct shader;
/* Kept TU-private because moving this recovered layout into a shared header perturbs VC7 output. */
struct animation_state_continuous
{
short index;
word pad;
real frame_index;
};
/* Recovered animation-graph block layouts used only by this translation unit. */
struct animation_graph_sound_reference
{
struct tag_reference sound;
long unused;
};
typedef char verify_animation_graph_sound_reference_size[
sizeof(struct animation_graph_sound_reference) == 0x14 ? 1 : -1];
struct animation_graph_first_person_weapon_animations
{
long unused[4];
struct tag_block animations; // animation_graph_animation_index
};
typedef char verify_animation_graph_first_person_weapon_animations_size[
sizeof(struct animation_graph_first_person_weapon_animations) == 0x1C ? 1 : -1];
/* Only the leading name is consumed here; the remaining bytes preserve the January layout. */
struct animation_graph_node
{
char name[TAG_STRING_LENGTH+1];
byte reserved0020[0x20];
};
typedef char verify_animation_graph_node_size[
sizeof(struct animation_graph_node) == 0x40 ? 1 : -1];
typedef char verify_render_model_effect_size[
sizeof(struct render_model_effect) == 0x28 ? 1 : -1];
struct first_person_weapon
{
boolean visible;
byte reserved0001[3];
long unit_index;
long weapon_index;
short state;
short ticks_until_pose;
short ticks_idle;
short ticks_until_predict;
short overlay_animation_index;
struct animation_state state_animation;
struct animation_state moving_animation;
short reserved001e;
struct animation_state_continuous overcharged_jitter_animation;
real firing_push_back;
real firing_push_back_velocity;
real_vector2d position;
real_vector2d position_velocity;
real_vector2d turning;
real_vector2d turning_velocity;
boolean rendered;
byte reserved0051[3];
real_vector3d render_forward;
real_euler_angles2d render_facing;
real_euler_angles2d last_render_facing;
real_point3d render_position;
real_point3d last_render_position;
short interpolation_frame_index;
short interpolation_frame_count;
real_orientation node_orientations[MAXIMUM_NODES_PER_ANIMATION];
real_orientation original_node_orientations[MAXIMUM_NODES_PER_ANIMATION];
struct real_matrix4x3 node_matrices[MAXIMUM_NODES_PER_ANIMATION];
boolean weapon_node_remapping_table_valid;
byte reserved1d8d;
short weapon_node_remapping_table[MAXIMUM_NODES_PER_ANIMATION];
boolean hands_node_remapping_table_valid;
byte reserved1e0f;
short hands_node_remapping_table[MAXIMUM_NODES_PER_ANIMATION];
boolean shotgun_empty;
byte reserved1e91;
short shotgun_shells_to_reload;
short shotgun_reload_type;
short reserved1e96;
long current_sound_index;
short current_sound_state;
byte reserved1e9e[2];
};
typedef char verify_first_person_weapon_size[
sizeof(struct first_person_weapon) == 0x1EA0 ? 1 : -1];
typedef char verify_first_person_weapon_node_matrices_offset[
offsetof(struct first_person_weapon, node_matrices) == 0x108C ? 1 : -1];
typedef char verify_first_person_weapon_rendered_offset[
offsetof(struct first_person_weapon, rendered) == 0x50 ? 1 : -1];
typedef char verify_first_person_weapon_node_orientations_offset[
offsetof(struct first_person_weapon, node_orientations) == 0x8C ? 1 : -1];
typedef char verify_first_person_weapon_node_remapping_table_offset[
offsetof(struct first_person_weapon, weapon_node_remapping_table) == 0x1D8E ? 1 : -1];
typedef char verify_first_person_weapon_shotgun_empty_offset[
offsetof(struct first_person_weapon, shotgun_empty) == 0x1E90 ? 1 : -1];
typedef char verify_first_person_weapon_current_sound_index_offset[
offsetof(struct first_person_weapon, current_sound_index) == 0x1E98 ? 1 : -1];
typedef char verify_first_person_weapon_current_sound_state_offset[
offsetof(struct first_person_weapon, current_sound_state) == 0x1E9C ? 1 : -1];
typedef char verify_weapon_first_person_animations_index_offset[
offsetof(
struct weapon_definition,
weapon.interface_definition.first_person_animations.index) == 0x478 ? 1 : -1];
typedef char verify_weapon_flags_offset[
offsetof(struct weapon_datum, weapon.flags) == 0x1DC ? 1 : -1];
typedef char verify_weapon_type_offset[
offsetof(struct weapon_definition, weapon.weapon_type) == 0x4E2 ? 1 : -1];
typedef char verify_animation_graph_first_person_weapon_animations_offset[
offsetof(struct animation_graph, first_person_weapon_animations) == 0x48 ? 1 : -1];
typedef char verify_unit_flags_offset[
offsetof(struct unit_datum, unit.flags) == 0x1B4 ? 1 : -1];
typedef char verify_unit_active_camouflage_offset[
offsetof(struct unit_datum, unit.active_camouflage) == 0x32C ? 1 : -1];
typedef char verify_object_outgoing_function_values_offset[
offsetof(struct object_datum, object.outgoing_function_values) == 0xE4 ? 1 : -1];
typedef char verify_object_outgoing_change_colors_offset[
offsetof(struct object_datum, object.outgoing_change_colors) == 0x168 ? 1 : -1];
typedef char verify_game_globals_first_person_interface_offset[
offsetof(struct game_globals, first_person_interface) == 0x17C ? 1 : -1];
typedef char verify_animation_graph_nodes_offset[
offsetof(struct animation_graph, nodes) == 0x68 ? 1 : -1];
/* ---------- prototypes */
static void first_person_weapon_set_visibility(
short local_player_index,
boolean visible);
static void first_person_weapon_build_node_matrices(
short local_player_index);
static boolean first_person_weapon_animation_fits(
struct animation_graph const *animation_graph,
struct animation const *animation);
static void first_person_weapon_predict(
short local_player_index);
static void first_person_weapon_start_interpolation(
short local_player_index,
short frame_count);
static void first_person_weapon_set_state(
short local_player_index,
short new_state,
boolean reset_sounds);
static void first_person_weapon_switch_weapons(
short local_player_index);
static void first_person_weapon_forget_weapon(
short local_player_index);
static void first_person_weapon_new_unit(
short local_player_index,
long unit_index);
static void first_person_weapon_message(
short local_player_index,
short message_type);
static void first_person_weapon_next_state(
short local_player_index);
static void first_person_weapon_update(
short local_player_index);
static boolean model_build_remapping_table_for_animation_graph(
long model_index,
long animation_graph_index,
short *node_remapping_table);
static void weapon_play_first_person_weapon_sound(
long weapon_index,
short message_type);
static long first_person_weapon_state_from_weapon_message(
short message_type);
static long first_person_animation_type_from_weapon_state(
short state);
static void model_remap_node_matrices_to_match_animation_graph(
long model_index,
struct real_matrix4x3 *model_node_matrices,
long animation_graph_index,
struct real_matrix4x3 const *node_matrices,
short const *node_remapping_table);
static struct first_person_weapon *first_person_weapon_get(
short local_player_index);
static short first_person_weapon_index_from_weapon_index(
long weapon_index);
static short first_person_weapon_index_from_unit_index(
long unit_index);
/* port/linux/game/pal_tags.c's */
boolean pal_tags_first_person_advance(short local_player_index, long graph_index, short animation_index,
short frame_index);
real pal_tags_first_person_fraction(short local_player_index, long graph_index, short animation_index,
short frame_index);
/* ---------- globals */
static struct first_person_weapon *first_person_weapons;
/* ---------- public code */
short animation_update_render_only(
long animation_graph_index,
struct animation_state *state,
long *sound_index)
{
return animation_update_internal(
0,
animation_graph_index,
state,
sound_index);
}
void first_person_weapons_initialize(
void)
{
first_person_weapons = (struct first_person_weapon *)game_state_malloc(
"first person weapons",
NULL,
sizeof(*first_person_weapons) * MAXIMUM_NUMBER_OF_LOCAL_PLAYERS);
match_assert(
"c:\\halo\\SOURCE\\interface\\first_person_weapons.c",
240,
first_person_weapons);
return;
}
void first_person_weapons_dispose(
void)
{
return;
}
void first_person_weapons_initialize_for_new_map(
void)
{
short local_player_index;
for (local_player_index=0; local_player_index<MAXIMUM_NUMBER_OF_LOCAL_PLAYERS; local_player_index++)
{
csmemset(
&first_person_weapons[local_player_index],
0,
sizeof(first_person_weapons[local_player_index]));
first_person_weapons[local_player_index].unit_index= NONE;
first_person_weapons[local_player_index].current_sound_index= NONE;
first_person_weapons[local_player_index].current_sound_state= NONE;
}
return;
}
void first_person_weapons_dispose_from_old_map(
void)
{
return;
}
long first_person_weapon_get_local_index(
long weapon_index)
{
short local_player_index;
for (local_player_index=0; local_player_index<MAXIMUM_NUMBER_OF_LOCAL_PLAYERS; local_player_index++)
{
struct first_person_weapon *first_person_weapon= first_person_weapon_get(local_player_index);
if (first_person_weapon->weapon_index==weapon_index && first_person_weapon->visible)
{
break;
}
}
return (local_player_index==MAXIMUM_NUMBER_OF_LOCAL_PLAYERS) ? NONE : local_player_index;
}
#ifdef HALO_ANDROID
int config_boolean(char const *name);
void model_vr_log_skin(long model_index);
/* VR: the headset's frame each local player's pose was last built for
from the controller (halo_vr_frame_serial; 0 when not), and its weapon */
static unsigned long first_person_weapon_vr_pose_serial[MAXIMUM_NUMBER_OF_LOCAL_PLAYERS];
static long first_person_weapon_vr_pose_weapon[MAXIMUM_NUMBER_OF_LOCAL_PLAYERS];
/* the pose was built for this frame already, from the controller, which
the eyes do not move (from the eye's own camera, each eye builds its own) */
static boolean first_person_weapon_vr_pose_kept(
short local_player_index)
{
unsigned long serial= halo_vr_frame_serial();
return serial!=0 &&
first_person_weapon_vr_pose_serial[local_player_index]==serial &&
first_person_weapon_vr_pose_weapon[local_player_index]==first_person_weapon_get(local_player_index)->weapon_index;
}
/* VR: the grenade the player holds (the free hand's trigger, units.c)
hangs from the body, which the first-person view leaves out, and with it
the grenade: drawn instead in the free hand, as it is this frame, with the
hands (which are drawn over the world, and would hide it wherever they
overlap; in the room, not mirrored) */
static void first_person_weapon_draw_held_grenade(
long unit_index,
struct render_lighting *lighting,
struct render_model_effect const *hands_effect)
{
struct unit_datum *unit= unit_try_and_get(unit_index);
struct object_datum *grenade;
struct object_definition *definition;
struct model *model;
struct render_model_effect model_effect;
real_matrix4x3 *node_matrices;
real_matrix4x3 hand, inverse, delta;
real_point3d position;
real_vector3d forward, up;
float hand_position[3], hand_forward[3], hand_up[3], scale;
short node_index;
if (!unit ||
unit->unit.grenade_object_index==NONE ||
unit->unit.grenade_throw_state!=_unit_grenade_throw_in_hand ||
!halo_vr_grenade_in_hand(render.local_player_index, hand_position, hand_forward, hand_up, &scale))
{
return;
}
/* (deleted since the tick: none to draw) */
grenade= object_try_and_get(unit->unit.grenade_object_index);
if (!grenade)
return;
definition= object_definition_get(grenade->definition_index);
if (definition->object.model.index==NONE || TEST_FLAG(grenade->object.flags, _object_invisible_bit))
return;
model= model_definition_get(definition->object.model.index);
if (model->nodes.count<=0)
return;
node_matrices= object_get_node_matrices(unit->unit.grenade_object_index);
/* the whole grenade moved so its root lies in the hand, as the hand
turns (drawn only: the next tick puts it back on the body) */
position.x= hand_position[0];
position.y= hand_position[1];
position.z= hand_position[2];
forward.i= hand_forward[0];
forward.j= hand_forward[1];
forward.k= hand_forward[2];
up.i= hand_up[0];
up.j= hand_up[1];
up.k= hand_up[2];
matrix4x3_from_point_and_vectors(&hand, &position, &forward, &up);
hand.scale= scale;
matrix4x3_inverse(&node_matrices[0], &inverse);
matrix4x3_multiply(&hand, &inverse, &delta);
for (node_index= 0; node_index<model->nodes.count; node_index++)
{
real_matrix4x3 node= node_matrices[node_index];
matrix4x3_multiply(&delta, &node, &node_matrices[node_index]);
}
model_effect= *hands_effect;
render_model(
definition->object.model.index,
0.0f,
node_matrices,
grenade->object.region_permutations,
grenade->object.outgoing_change_colors,
grenade->object.outgoing_function_values,
lighting,
&render.camera.position,
0.0f,
&model_effect,
unit->unit.grenade_object_index,
grenade->object.forced_shader_permutation_index,
FLAG(_render_model_first_person_bit));
}
#endif
void first_person_weapon_draw(
void)
{
#ifdef HALO_ANDROID
/* VR: not in the scope's view, taken from the weapon itself */
if (halo_vr_scope_pass())
return;
#endif
/* port: not drawn with display.viewmodel_visible off (view_fov.c) */
if (!viewmodel_is_visible())
return;
#ifdef HALO_ANDROID
/* (debug.vr_body: the body seen from aside, flat, vr_body.c) */
if (vr_body_debug_hides_first_person())
return;
#endif
if (render.local_player_index!=NONE)
{
struct first_person_weapon *first_person_weapon= first_person_weapon_get(render.local_player_index);
if (local_player_get_player_index(render.local_player_index)!=NONE)
{
long unit_index= player_get(
local_player_get_player_index(render.local_player_index))->unit_index;
if (unit_index!=NONE &&
first_person_weapon->visible &&
first_person_weapon->unit_index!=NONE &&
first_person_weapon->weapon_index!=NONE
#ifdef HALO_ANDROID
/* VR: at a wheel or a gun the body's own hands hold them */
&& !(halo_vr_active() && render_object_vr_at_controls(unit_index))
#endif
)
{
struct unit_datum *unit= unit_get(unit_index);
struct weapon_datum *weapon= weapon_get(first_person_weapon->weapon_index);
struct weapon_definition *weapon_definition= weapon_definition_get(weapon->definition_index);
if (weapon_definition->weapon.interface_definition.first_person_animations.index!=NONE)
{
struct real_matrix4x3 model_node_matrices[MAXIMUM_NODES_PER_ANIMATION];
struct render_model_effect model_effect;
struct game_globals_first_person_interface *first_person_interface=
TAG_BLOCK_GET_ELEMENT(
&scenario_get_game_globals()->first_person_interface,
0,
struct game_globals_first_person_interface);
struct animation_graph *animation_graph= animation_graph_definition_get(
weapon_definition->weapon.interface_definition.first_person_animations.index);
struct render_lighting *lighting= object_get_cached_render_lighting(unit_index, FLT_MAX);
model_effect.modifier_shader= NULL;
if (TEST_FLAG(unit->unit.flags, _unit_active_camouflaged_bit) ||
unit->unit.active_camouflage>0.0f)
{
model_effect.type= _render_model_effect_type_active_camouflage;
model_effect.intensity= unit->unit.active_camouflage;
model_effect.parameter= unit->unit.active_camouflage_super_amount;
model_effect.source_object_index= unit_index;
model_effect.source_object_centroid= render.camera.position;
}
else
{
model_effect.type= _render_model_effect_type_none;
}
if (first_person_weapon->weapon_node_remapping_table_valid &&
weapon_definition->weapon.interface_definition.first_person_model.index!=NONE)
{
model_remap_node_matrices_to_match_animation_graph(
weapon_definition->weapon.interface_definition.first_person_model.index,
model_node_matrices,
weapon_definition->weapon.interface_definition.first_person_animations.index,
first_person_weapon->node_matrices,
first_person_weapon->weapon_node_remapping_table);
render_model(
weapon_definition->weapon.interface_definition.first_person_model.index,
0.0f,
model_node_matrices,
NULL,
weapon->object.outgoing_change_colors,
weapon->object.outgoing_function_values,
lighting,
&render.camera.position,
0.0f,
&model_effect,
first_person_weapon->weapon_index,
0,
FLAG(_render_model_first_person_bit));
}
/* port: the shield's flare on the arms as on the body, the
unit's own modifier shader (display.viewmodel_shield,
view_fov.c), in the color chosen for it as on the body
(display.shield_color, shield_color.c); the weapon above is
drawn without it */
if (viewmodel_shield_is_visible())
{
struct object_definition *unit_definition= object_definition_get(object_get(unit_index)->definition_index);
if (unit_definition->object.modifier_shader.index!=NONE)
{
struct shader *modifier_shader= shader_definition_get(unit_definition->object.modifier_shader.index);
if (shader_type_is_valid_for_modifier(modifier_shader->base.type))
{
model_effect.modifier_shader= modifier_shader;
model_effect.modifier_animation.colors= shield_color_colors(unit_index,
unit->object.outgoing_change_colors);
model_effect.modifier_animation.values= overshield_effect_values(unit_index,
unit->object.outgoing_function_values, TRUE);
}
}
}
if (first_person_weapon->hands_node_remapping_table_valid &&
first_person_interface->hands.index!=NONE)
{
model_remap_node_matrices_to_match_animation_graph(
first_person_interface->hands.index,
model_node_matrices,
weapon_definition->weapon.interface_definition.first_person_animations.index,
first_person_weapon->node_matrices,
first_person_weapon->hands_node_remapping_table);
#ifdef HALO_ANDROID
/* (vr.log_performance: once, how its skin hangs from its
bones, for the VR arms) */
{
static int log_skin= -1;
if (log_skin<0)
log_skin= config_boolean("vr.log_performance")!=0;
if (log_skin)
model_vr_log_skin(first_person_interface->hands.index);
}
#endif
#ifdef HALO_ANDROID
/* VR: the weapon alone (vr.body "off") */
if (halo_vr_body_mode()!=HALO_VR_BODY_OFF)
#endif
render_model(
first_person_interface->hands.index,
0.0f,
model_node_matrices,
NULL,
unit->object.outgoing_change_colors,
unit->object.outgoing_function_values,
lighting,
&render.camera.position,
0.0f,
&model_effect,
first_person_weapon->weapon_index,
0,
FLAG(_render_model_first_person_bit));
}
#ifdef HALO_ANDROID
if (halo_vr_active())
first_person_weapon_draw_held_grenade(unit_index, lighting, &model_effect);
#endif
}
}
}
}
return;
}
short first_person_weapon_get_marker_by_name(
long weapon_index,
char const *name,
struct object_marker *markers,
short maximum_marker_count)
{
short marker_count= 0;
struct weapon_datum *weapon= weapon_try_and_get(weapon_index);
if (weapon)
{
short local_player_index= first_person_weapon_index_from_weapon_index(weapon_index);
if (local_player_index!=NONE &&
director_get_perspective(local_player_index)==_director_perspective_first_person)
{
struct first_person_weapon *first_person_weapon= first_person_weapon_get(local_player_index);
struct weapon_definition *weapon_definition= weapon_definition_get(weapon->definition_index);
if (first_person_weapon->weapon_node_remapping_table_valid &&
weapon_definition->weapon.interface_definition.first_person_model.index!=NONE &&
weapon_definition->weapon.interface_definition.first_person_animations.index!=NONE)
{
marker_count= model_get_marker_by_name(
weapon_definition->weapon.interface_definition.first_person_model.index,
name,
NULL,
first_person_weapon->weapon_node_remapping_table,
(short)animation_graph_definition_get(
weapon_definition->weapon.interface_definition.first_person_animations.index)->nodes.count,
first_person_weapon->node_matrices,
FALSE,
markers,
maximum_marker_count);
}
}
}
return marker_count;
}
void first_person_weapon_render_update(
void)
{
if (render.local_player_index!=NONE)
{
struct first_person_weapon *first_person_weapon= first_person_weapon_get(render.local_player_index);
if (first_person_weapon->unit_index!=NONE &&
first_person_weapon->weapon_index!=NONE)
{
boolean visible= director_get_perspective(render.local_player_index)==_director_perspective_first_person &&
player_control_get_zoom_level(render.local_player_index)==NONE;
#ifdef HALO_ANDROID
/* VR: the weapon stays in the hand zoomed in (the scope is on it) */
if (halo_vr_active())
visible= director_get_perspective(render.local_player_index)==_director_perspective_first_person;
#endif
first_person_weapon_set_visibility(render.local_player_index, visible);
if (first_person_weapon->visible)
{
#ifdef HALO_ANDROID
unsigned long long timing= halo_vr_timing_start();
/* VR: posed from the controller, the pose is the same for
every eye (and the scope's view): built once a frame */
if (first_person_weapon_vr_pose_kept(render.local_player_index))
{
halo_vr_timing_end(HALO_VR_TIMING_FIRST_PERSON_KEPT, timing);
}
else
{
first_person_weapon_build_node_matrices(render.local_player_index);
halo_vr_timing_end(HALO_VR_TIMING_FIRST_PERSON, timing);
}
#else
first_person_weapon_build_node_matrices(render.local_player_index);
#endif
}
}
}
return;
}
short first_person_weapon_get_marker_by_name_render(
long weapon_index,
char const *name,
struct object_marker *markers,
short maximum_marker_count)
{
if (render.local_player_index==first_person_weapon_index_from_weapon_index(weapon_index))
{
return first_person_weapon_get_marker_by_name(
weapon_index,
name,
markers,
maximum_marker_count);
}
return 0;
}
void first_person_weapon_center_flashlight(
long unit_index,
real_point3d *position,
real_vector3d *forward,
real_vector3d *up)
{
short local_player_index= first_person_weapon_index_from_unit_index(unit_index);
if (local_player_index!=NONE)
{
struct first_person_weapon *first_person_weapon= first_person_weapon_get(local_player_index);
if (first_person_weapon->visible)
{
struct object_marker marker;
if (first_person_weapon_get_marker_by_name(
first_person_weapon->weapon_index,
"flashlight",
&marker,
1)>0)
{
position->x= marker.matrix.position.x - marker.matrix.forward.i*0.5f;
position->y= marker.matrix.position.y - marker.matrix.forward.j*0.5f;
position->z= marker.matrix.position.z - marker.matrix.forward.k*0.5f;
*forward= marker.matrix.forward;
*up= marker.matrix.up;
}
}
}
return;
}
boolean first_person_weapon_adjust_light(
long weapon_index,
char const *marker_name,
real_point3d *position,
real_vector3d *forward,
real_vector3d *up)
{
struct weapon_datum *weapon= weapon_get(weapon_index);
struct unit_datum *unit= unit_get(weapon->object.parent_object_index);
long player_index= unit->unit.player_index;
boolean adjusted= FALSE;
if (player_index!=NONE)
{
struct player_datum *player= player_get(player_index);
short local_player_index= player->local_player_index;
struct object_marker marker;
if (local_player_index!=NONE &&
local_player_index==render.local_player_index &&
first_person_weapon_get(local_player_index)->visible &&
first_person_weapon_get_marker_by_name(weapon_index, marker_name, &marker, 1)>0)
{
*position= marker.matrix.position;
*forward= marker.matrix.forward;
*up= marker.matrix.up;
adjusted= TRUE;
}
}
return adjusted;
}
void first_person_weapon_message_from_unit(
long unit_index,
short message_type)
{
short local_player_index= first_person_weapon_index_from_unit_index(unit_index);
first_person_weapon_message(local_player_index, message_type);
if (local_player_index==NONE)
{
struct unit_datum *unit= unit_get(unit_index);
/* port: the weapon in hand's object (not its slot's number, which
played some other object's sound) */
if (unit->unit.current_weapon_index>=0 &&
unit->unit.current_weapon_index<MAXIMUM_WEAPONS_PER_UNIT)
{
weapon_play_first_person_weapon_sound(
unit->unit.weapon_object_indices[unit->unit.current_weapon_index],
message_type);
}
}
return;
}
void first_person_weapon_message_from_weapon(
long weapon_index,
short message_type)
{
short local_player_index= first_person_weapon_index_from_weapon_index(weapon_index);
first_person_weapon_message(local_player_index, message_type);
if (local_player_index==NONE)
{
weapon_play_first_person_weapon_sound(weapon_index, message_type);
}
return;
}
struct real_matrix4x3 *first_person_weapon_get_node_matrix(
short local_player_index,
short node_index)
{
struct first_person_weapon *first_person_weapon;
struct weapon_datum *weapon;
struct weapon_definition *weapon_definition;
struct animation_graph *animation_graph;
match_assert(
"c:\\halo\\SOURCE\\interface\\first_person_weapons.c",
1433,
local_player_index>=0 && local_player_index<MAXIMUM_NUMBER_OF_LOCAL_PLAYERS);
first_person_weapon = &first_person_weapons[local_player_index];
/* port: the last matrices built when the weapon is gone (an effect or
particle on it asking before first_person_weapon_forget_weapon stopped
them was a NULL weapon, and a crash) */
weapon = first_person_weapon->weapon_index!=NONE ? weapon_try_and_get(first_person_weapon->weapon_index) : NULL;
if (!weapon)
{
return &first_person_weapon->node_matrices[
node_index>=0 && node_index<MAXIMUM_NODES_PER_ANIMATION ? node_index : 0];
}
weapon_definition = weapon_definition_get(weapon->definition_index);
animation_graph = animation_graph_definition_get(
weapon_definition->weapon.interface_definition.first_person_animations.index);
match_assert(
"c:\\halo\\SOURCE\\interface\\first_person_weapons.c",
718,
node_index>=0 && node_index<animation_graph->nodes.count);
return &first_person_weapon->node_matrices[node_index];
}
void first_person_weapons_update(
void)
{
short local_player_index;
for (local_player_index=0; local_player_index<MAXIMUM_NUMBER_OF_LOCAL_PLAYERS; local_player_index++)
{
long player_index= local_player_get_player_index(local_player_index);
if (player_index!=NONE)
{
struct first_person_weapon *first_person_weapon= first_person_weapon_get(local_player_index);
long unit_index= player_get(player_index)->unit_index;
if (first_person_weapon->unit_index!=unit_index)
{
first_person_weapon_new_unit(local_player_index, unit_index);
}
if (first_person_weapon->weapon_index==NONE)
{
first_person_weapon_switch_weapons(local_player_index);
}
first_person_weapon_update(local_player_index);
}
}
return;
}
/* ---------- private code */
static void first_person_weapon_set_visibility(
short local_player_index,
boolean visible)
{
struct first_person_weapon *first_person_weapon= first_person_weapon_get(local_player_index);
if (visible!=first_person_weapon->visible)
{
if (visible)
{
match_assert(
"c:\\halo\\SOURCE\\interface\\first_person_weapons.c",
1555,
first_person_weapon->weapon_index!=NONE);
effects_start_on_first_person_weapon(local_player_index, first_person_weapon->weapon_index);
}
else
{
effects_stop_on_first_person_weapon(local_player_index);
particles_stop_on_first_person_weapon(local_player_index);
}
first_person_weapon->visible= visible;
}
return;
}
/* port: the weapon dropped or deleted: hidden first, which stops the effects
and particles that follow it in first person (they asked for the weapon's
nodes, and found none, until the next update switched weapons; the
renderer shows the next weapon again) */
static void first_person_weapon_forget_weapon(
short local_player_index)
{
first_person_weapon_set_visibility(local_player_index, FALSE);
first_person_weapon_get(local_player_index)->weapon_index= NONE;
return;
}
static void first_person_weapon_predict(
short local_player_index)
{
struct first_person_weapon *first_person_weapon= first_person_weapon_get(local_player_index);
if (first_person_weapon->weapon_index!=NONE)
{
struct weapon_datum *weapon= weapon_get(first_person_weapon->weapon_index);
struct weapon_definition *weapon_definition= weapon_definition_get(weapon->definition_index);
predicted_resources_precache(&weapon_definition->weapon.predicted_resources);
}
first_person_weapon->ticks_until_predict= TICKS_PER_SECOND;
return;
}
static void first_person_weapon_start_interpolation(
short local_player_index,
short frame_count)
{
struct first_person_weapon *first_person_weapon= first_person_weapon_get(local_player_index);
struct weapon_datum *weapon= weapon_get(first_person_weapon->weapon_index);
struct weapon_definition *weapon_definition= weapon_definition_get(weapon->definition_index);
struct animation_graph *animation_graph= animation_graph_definition_get(
weapon_definition->weapon.interface_definition.first_person_animations.index);
/* port: no more nodes than the weapon's orientations hold (a map's count) */
csmemcpy(
first_person_weapon->original_node_orientations,
first_person_weapon->node_orientations,
PIN(animation_graph->nodes.count, 0, MAXIMUM_NODES_PER_ANIMATION)*sizeof(real_orientation));
if (frame_count>=first_person_weapon->interpolation_frame_count-
first_person_weapon->interpolation_frame_index)
{
first_person_weapon->interpolation_frame_index= 0;
first_person_weapon->interpolation_frame_count= frame_count;
}
return;
}
static void first_person_weapon_set_state(
short local_player_index,
short new_state,
boolean reset_sounds)
{
struct first_person_weapon *first_person_weapon= first_person_weapon_get(local_player_index);
if (first_person_weapon->weapon_index!=NONE)
{
struct weapon_datum *weapon= weapon_get(first_person_weapon->weapon_index);
if (TEST_FLAG(weapon->weapon.flags, _weapon_overheated_bit))
{
switch (new_state)
{
case _first_person_weapon_state_ready:
new_state= _first_person_weapon_state_overheating_again;
break;
case _first_person_weapon_state_throw_grenade:
new_state= _first_person_weapon_state_throw_grenade_overheated;
break;
}
}
}
switch (new_state)
{
case _first_person_weapon_state_ready:
if (first_person_weapon->state==_first_person_weapon_state_ready)
{
new_state= NONE;
}
break;
case _first_person_weapon_state_light_off:
case _first_person_weapon_state_light_on:
if (first_person_weapon->state!=_first_person_weapon_state_idle &&
first_person_weapon->state!=_first_person_weapon_state_posing)
{
new_state= NONE;
}
break;
case _first_person_weapon_state_primary_fire:
case _first_person_weapon_state_secondary_fire:
case _first_person_weapon_state_primary_misfire:
case _first_person_weapon_state_secondary_misfire:
if (first_person_weapon->state!=_first_person_weapon_state_idle &&
first_person_weapon->state!=_first_person_weapon_state_posing &&
first_person_weapon->state!=_first_person_weapon_state_primary_fire &&
first_person_weapon->state!=_first_person_weapon_state_charged &&
first_person_weapon->state!=_first_person_weapon_state_shotgun_enter_reload &&
first_person_weapon->state!=_first_person_weapon_state_overheated_exit &&
first_person_weapon->state!=_first_person_weapon_state_shotgun_exit_reload_empty &&
first_person_weapon->state!=_first_person_weapon_state_shotgun_exit_reload_full &&
first_person_weapon->state!=_first_person_weapon_state_reload_while_empty &&
first_person_weapon->state!=_first_person_weapon_state_reload_while_full)
{
new_state= NONE;
}
break;
}
if (new_state!=NONE && first_person_weapon->weapon_index!=NONE)
{
struct weapon_datum *weapon= weapon_get(first_person_weapon->weapon_index);
struct weapon_definition *weapon_definition= weapon_definition_get(weapon->definition_index);
short animation_type;
short interpolation_frame_count;
if (weapon_definition->weapon.weapon_type==_weapon_type_plasma_pistol &&
new_state==_first_person_weapon_state_overheated &&
!TEST_FLAG(weapon->weapon.flags, _weapon_overheated_bit))
{
new_state= _first_person_weapon_state_idle;
}
animation_type= (short)first_person_animation_type_from_weapon_state(new_state);
if (weapon_definition->weapon.weapon_type==_weapon_type_shotgun &&
first_person_weapon->state==_first_person_weapon_state_shotgun_exit_reload_empty)
{
interpolation_frame_count= 0;
}
else
{
switch (new_state)
{
case _first_person_weapon_state_overheated:
case _first_person_weapon_state_melee:
case _first_person_weapon_state_ready:
interpolation_frame_count= 0;
break;
case _first_person_weapon_state_primary_fire:
case _first_person_weapon_state_secondary_fire:
case _first_person_weapon_state_primary_misfire:
case _first_person_weapon_state_secondary_misfire:
interpolation_frame_count= 3;
break;
default:
interpolation_frame_count= 6;
break;
}
}
if (first_person_weapon->unit_index!=NONE &&
first_person_weapon->weapon_index!=NONE)
{
struct weapon_datum *weapon= weapon_get(first_person_weapon->weapon_index);
struct weapon_definition *weapon_definition= weapon_definition_get(weapon->definition_index);
struct animation_graph *animation_graph= animation_graph_definition_get(
weapon_definition->weapon.interface_definition.first_person_animations.index);
if (animation_graph->first_person_weapon_animations.count)
{
struct animation_graph_first_person_weapon_animations *first_person_weapon_animations=
TAG_BLOCK_GET_ELEMENT(
&animation_graph->first_person_weapon_animations,
0,
struct animation_graph_first_person_weapon_animations);
if (first_person_weapon_animations &&
animation_type>=0 &&
animation_type<first_person_weapon_animations->animations.count)
{
short animation_index= animation_graph_animation_index_get(
&first_person_weapon_animations->animations)[animation_type].animation_index;
if (animation_index!=NONE)
{
if (reset_sounds &&
first_person_weapon->current_sound_index!=NONE &&
first_person_weapon->current_sound_state!=_first_person_weapon_state_overheating)
{
sound_stop_impulse(first_person_weapon->current_sound_index);
first_person_weapon->current_sound_index= NONE;
first_person_weapon->current_sound_state= NONE;
}
if (interpolation_frame_count>0)
{
first_person_weapon_start_interpolation(
local_player_index,
interpolation_frame_count);
}
first_person_weapon->state= new_state;
first_person_weapon->state_animation.index= animation_index;
first_person_weapon->state_animation.frame_index= 0;
}
}
}
}
}
return;
}
#ifdef HALO_ANDROID
/* ---------- VR arms
The first-person skeleton hangs from the camera at the weapon hand's
controller (halo_vr_weapon_camera), so the weapon and the right hand lie
where they should; its arms, though, reach up to shoulders that float with
the weapon. Here each arm's upper arm and forearm are turned (two-bone
inverse kinematics, port/linux/game/vr_arms.c) to run from the player's
shoulders, below the neck and square to the torso, to its wrist, the
controller's roll shared along the forearm. The left wrist, unless the hand
holds the weapon's foregrip, goes to the free controller, its fingers with
it.
Left-handed (vr.handedness) all this is done in model space (halo_vr.h): a
mirror image of the room, where the left controller holds the weapon as a
right hand would; the body is mirrored into it, and the posed skeleton
mirrored back (vr_mirror_pose), so that the weapon lies in the left hand,
the model's left arm reaches to the right controller. */
#define VR_METRES_PER_WORLD_UNIT 3.048f
/* a point or a vector across the mirror (a point on it, its normal) */
static void vr_mirror_point(
float const on[3],
float const normal[3],
float point[3])
{
float along = (point[0] - on[0]) * normal[0] + (point[1] - on[1]) * normal[1] + (point[2] - on[2]) * normal[2];
point[0] -= 2.f * along * normal[0];
point[1] -= 2.f * along * normal[1];
point[2] -= 2.f * along * normal[2];
}
static void vr_mirror_vector(
float const normal[3],
float vector[3])
{
float along = vector[0] * normal[0] + vector[1] * normal[1] + vector[2] * normal[2];
vector[0] -= 2.f * along * normal[0];
vector[1] -= 2.f * along * normal[1];
vector[2] -= 2.f * along * normal[2];
}
/* the posed skeleton across a mirror: every node mirrored, its axes too
(a left-handed frame: rasterizer_xbox.c turns its triangles' winding over).
Numeric displays drawn so, the assault rifle's ammo counter and compass,
are read the right way round by rasterizer_xbox_transparent_geometry.c. */
static void vr_mirror_nodes(
float const on[3],
float const normal[3],
real_matrix4x3 *node_matrices,
short node_count)
{
short node_index;
for (node_index = 0; node_index < node_count; node_index++)
{
real_matrix4x3 *node = &node_matrices[node_index];
real_vector3d *axes[3];
float vector[3];
short axis;
vector[0] = node->position.x;
vector[1] = node->position.y;
vector[2] = node->position.z;
vr_mirror_point(on, normal, vector);
node->position.x = vector[0];
node->position.y = vector[1];
node->position.z = vector[2];
axes[0] = &node->forward;
axes[1] = &node->left;
axes[2] = &node->up;
for (axis = 0; axis < 3; axis++)
{
vector[0] = axes[axis]->i;
vector[1] = axes[axis]->j;
vector[2] = axes[axis]->k;
vr_mirror_vector(normal, vector);
axes[axis]->i = vector[0];
axes[axis]->j = vector[1];
axes[axis]->k = vector[2];
}
}
}
/* the posed skeleton out of model space (left-handed) */
static void vr_mirror_pose(
short local_player_index,
real_matrix4x3 *node_matrices,
short node_count)
{
float on[3], normal[3];
if (halo_vr_first_person_mirror(local_player_index, on, normal))
vr_mirror_nodes(on, normal, node_matrices, node_count);
}
/* debug.mirror_first_person (a test aid, flat): the first-person weapon
mirrored across the camera's own forward and up, as the left hand's is in
VR; its numeric displays can so be checked off the headset */
int config_boolean(char const *name);
static void debug_mirror_first_person(
struct render_camera const *camera,
real_matrix4x3 *node_matrices,
short node_count)
{
static int wanted = -1;
real_vector3d left;
float on[3], normal[3];
if (wanted < 0)
wanted = config_boolean("debug.mirror_first_person") ? 1 : 0;
if (!wanted)
return;
cross_product3d(&camera->up, &camera->forward, &left);
if (normalize3d(&left) == 0.0f)
return;
on[0] = camera->position.x;
on[1] = camera->position.y;
on[2] = camera->position.z;
normal[0] = left.i;
normal[1] = left.j;
normal[2] = left.k;
vr_mirror_nodes(on, normal, node_matrices, node_count);
}
struct vr_arm_nodes
{
long graph_index;
short upper[2];
short fore[2];
short wrist[2];
short gun;
/* the left hand's knuckles: index, middle and little finger */
short left_index;
short left_middle;
short left_pinky;
long barrel_logged_graph_index;
/* the fingers' joints (vr_finger_nodes) of the skeleton they were
found in: each hand's thumb, index, middle, ring, little finger */
long fingers_graph_index;
short fingers[2][5][3];
/* the whole pose in the weapon camera's frame, outside a melee attack */
boolean idle_pose_valid;
short idle_pose_count;
real_matrix4x3 idle_pose[MAXIMUM_NODES_PER_ANIMATION];
};
static struct vr_arm_nodes vr_arm_nodes = { NONE, { NONE, NONE }, { NONE, NONE }, { NONE, NONE }, NONE, NONE, NONE, NONE,
NONE, NONE };
/* the skeleton this frame, and each arm's last (its elbow's swing and its
forearm's twist run on from them) */
static struct vr_skeleton vr_arm_skeleton;
static struct vr_limb_history vr_arm_history[2];
static void vr_vector_scale_add(
real_vector3d *out,
real_vector3d const *vector,
real scale)
{
out->i += vector->i * scale;
out->j += vector->j * scale;
out->k += vector->k * scale;
}
/* turns the vector by the least rotation that takes from to to (unit) */
static void vr_rotate_between(
real_vector3d const *from,
real_vector3d const *to,
real_vector3d *v)
{
real_vector3d axis;
real_vector3d turned;
real sine, cosine, along;
cross_product3d(from, to, &axis);
sine = normalize3d(&axis);
if (sine < 1e-6f)
return;
cosine = dot_product3d(from, to);
cross_product3d(&axis, v, &turned);
along = dot_product3d(&axis, v);
v->i = v->i * cosine + turned.i * sine + axis.i * along * (1.f - cosine);
v->j = v->j * cosine + turned.j * sine + axis.j * along * (1.f - cosine);
v->k = v->k * cosine + turned.k * sine + axis.k * along * (1.f - cosine);
}
/* a node turned so that the bone to its child, from old to new direction */
static void vr_turn_bone(
real_matrix4x3 *node,
real_vector3d const *old_direction,
real_vector3d const *new_direction,
real_point3d const *position)
{
real_vector3d from = *old_direction;
real_vector3d to = *new_direction;
normalize3d(&from);
normalize3d(&to);
vr_rotate_between(&from, &to, &node->forward);
vr_rotate_between(&from, &to, &node->left);
vr_rotate_between(&from, &to, &node->up);
node->position = *position;
}
/* the upper arm turned about the elbow to point at the shoulder, keeping
its length (the free left arm: vr_left_hand_free) */
static void vr_point_upper_arm(
real_matrix4x3 *node_matrices,
short upper,
short fore,
real_point3d const *shoulder)
{
real_vector3d old_direction, new_direction;
real_point3d position;
real length;
vector_from_points3d(&node_matrices[upper].position, &node_matrices[fore].position, &old_direction);
vector_from_points3d(shoulder, &node_matrices[fore].position, &new_direction);
length = magnitude3d(&old_direction);
if (length < 1e-4f || normalize3d(&new_direction) < 1e-4f)
return;
point_from_line3d(&node_matrices[fore].position, &new_direction, -length, &position);
vr_turn_bone(&node_matrices[upper], &old_direction, &new_direction, &position);
}
/* an orthonormal frame from a direction and a second one across it */
static void vr_frame_from(
real_vector3d const *direction,
real_vector3d const *across,
real_vector3d frame[3])
{
frame[0] = *direction;
normalize3d(&frame[0]);
frame[1] = *across;
vr_vector_scale_add(&frame[1], &frame[0], -dot_product3d(across, &frame[0]));
normalize3d(&frame[1]);
cross_product3d(&frame[0], &frame[1], &frame[2]);
}
/* the free left hand: the wrist behind the controller's grip, the fingers
along the controller and the knuckles, little finger to index, up its
handle, as a hand holds it; its fingers with it. The arm is bent to it
from the shoulder afterwards (vr_solve_arm), or, rigid (vr.arm_ik off),
follows the hand as it is shaped on the foregrip. (The arm's skin hangs
from the arm's bones alone, none from the skeleton's root: 3a.0's log.) */
static void vr_left_hand_free(
struct vr_skeleton const *skeleton,
real_matrix4x3 *node_matrices,
boolean rigid,
float const hand_position[3],
float const hand_forward[3],
float const hand_up[3])
{
short wrist_index = vr_arm_nodes.wrist[0];
real_matrix4x3 old_wrist = node_matrices[wrist_index];
real_matrix4x3 *wrist = &node_matrices[wrist_index];
real_vector3d fingers, knuckles, controller_forward, controller_up;
real_vector3d from[3], to[3];
real_vector3d *axes[3];
short axis;
if (vr_arm_nodes.left_index == NONE || vr_arm_nodes.left_middle == NONE || vr_arm_nodes.left_pinky == NONE)
return;
vector_from_points3d(&old_wrist.position, &node_matrices[vr_arm_nodes.left_middle].position, &fingers);
vector_from_points3d(&node_matrices[vr_arm_nodes.left_pinky].position,
&node_matrices[vr_arm_nodes.left_index].position, &knuckles);
controller_forward.i = hand_forward[0];
controller_forward.j = hand_forward[1];
controller_forward.k = hand_forward[2];
controller_up.i = hand_up[0];
controller_up.j = hand_up[1];
controller_up.k = hand_up[2];
vr_frame_from(&fingers, &knuckles, from);
vr_frame_from(&controller_forward, &controller_up, to);
axes[0] = &wrist->forward;
axes[1] = &wrist->left;
axes[2] = &wrist->up;
for (axis = 0; axis < 3; axis++)
{
real_vector3d old_axis = *axes[axis];
real x = dot_product3d(&old_axis, &from[0]);
real y = dot_product3d(&old_axis, &from[1]);
real z = dot_product3d(&old_axis, &from[2]);
axes[axis]->i = to[0].i * x + to[1].i * y + to[2].i * z;
axes[axis]->j = to[0].j * x + to[1].j * y + to[2].j * z;
axes[axis]->k = to[0].k * x + to[1].k * y + to[2].k * z;
}
wrist->position.x = hand_position[0];
wrist->position.y = hand_position[1];
wrist->position.z = hand_position[2];
point_from_line3d(&wrist->position, &to[0], -0.07f / VR_METRES_PER_WORLD_UNIT, &wrist->position);
/* what the wrist carries (its fingers) with it, and rigid, the forearm
and the upper arm too, as they are on the foregrip */
{
real_matrix4x3 inverse, delta, moved;
short node_index;
matrix4x3_inverse(&old_wrist, &inverse);
matrix4x3_multiply(wrist, &inverse, &delta);
for (node_index = 0; node_index < skeleton->node_count; node_index++)
{
if (node_index == wrist_index ||
!(vr_skeleton_under(skeleton, node_index, wrist_index) ||
(rigid && (node_index == vr_arm_nodes.fore[0] || node_index == vr_arm_nodes.upper[0]))))
{
continue;
}
matrix4x3_multiply(&delta, &node_matrices[node_index], &moved);
node_matrices[node_index] = moved;
}
}
}
/* Through a melee attack the skeleton keeps the pose it had before, in the
hand: the attack's animation swings the gun before the camera and moves
both hands about it (the right one to the stock, the left one onto the
barrel), and the swing is the player's own. The pose is kept while the
weapon is held ready (store), and held through the attack (melee). */
static void vr_hold_pose(
real_matrix4x3 *node_matrices,
short node_count,
struct render_camera const *weapon_camera,
boolean store,
boolean melee)
{
real_matrix4x3 camera_matrix;
real_matrix4x3 inverse;
short node_index;
if (node_count > MAXIMUM_NODES_PER_ANIMATION)
return;
matrix4x3_from_point_and_vectors(&camera_matrix, &weapon_camera->position, &weapon_camera->forward,
&weapon_camera->up);
if (store)
{
matrix4x3_inverse(&camera_matrix, &inverse);
for (node_index = 0; node_index < node_count; node_index++)
matrix4x3_multiply(&inverse, &node_matrices[node_index], &vr_arm_nodes.idle_pose[node_index]);
vr_arm_nodes.idle_pose_count = node_count;
vr_arm_nodes.idle_pose_valid = TRUE;
}
else if (melee && vr_arm_nodes.idle_pose_valid && vr_arm_nodes.idle_pose_count == node_count)
{
for (node_index = 0; node_index < node_count; node_index++)
matrix4x3_multiply(&camera_matrix, &vr_arm_nodes.idle_pose[node_index], &node_matrices[node_index]);
}
}
/* While the animation turns the weapon away from where it aims (a reload,
drawing or putting it away), the laser follows the barrel: the gun node's
turn from its ready pose, applied to the laser at the ready. */
static void vr_laser_follows_barrel(
short local_player_index,
real_matrix4x3 const *node_matrices,
struct render_camera const *weapon_camera)
{
real_matrix4x3 camera_matrix, ready_gun, inverse, delta;
real_point3d start, moved_start;
real_vector3d direction, moved_direction;
float laser_start[3], laser_direction[3];
if (vr_arm_nodes.gun == NONE || !vr_arm_nodes.idle_pose_valid ||
!halo_vr_ready_laser(local_player_index, laser_start, laser_direction))
{
return;
}
{
/* (the laser at the ready is the room's: into model space) */
float on[3], normal[3];
if (halo_vr_first_person_mirror(local_player_index, on, normal))
{
vr_mirror_point(on, normal, laser_start);
vr_mirror_vector(normal, laser_direction);
}
}
matrix4x3_from_point_and_vectors(&camera_matrix, &weapon_camera->position, &weapon_camera->forward,
&weapon_camera->up);
matrix4x3_multiply(&camera_matrix, &vr_arm_nodes.idle_pose[vr_arm_nodes.gun], &ready_gun);
matrix4x3_inverse(&ready_gun, &inverse);
matrix4x3_multiply(&node_matrices[vr_arm_nodes.gun], &inverse, &delta);
start.x = laser_start[0];
start.y = laser_start[1];
start.z = laser_start[2];
direction.i = laser_direction[0];
direction.j = laser_direction[1];
direction.k = laser_direction[2];
matrix4x3_transform_point(&delta, &start, &moved_start);
matrix4x3_transform_vector(&delta, &direction, &moved_direction);
normalize3d(&moved_direction);
laser_start[0] = moved_start.x;
laser_start[1] = moved_start.y;
laser_start[2] = moved_start.z;
laser_direction[0] = moved_direction.i;
laser_direction[1] = moved_direction.j;
laser_direction[2] = moved_direction.k;
halo_vr_set_laser(local_player_index, laser_start, laser_direction);
}
/* The fingers (vr.fingers): the free left hand's from its controller's
trigger, grip and touch (vr_pose_fingers); of the hand on the weapon, only
the index finger, lifted a little off the trigger when the controller
says it is (the rest keeps the weapon's grip). */
static void first_person_weapon_vr_fingers(
short local_player_index,
real_matrix4x3 *node_matrices,
boolean left_free,
float const hand_forward[3],
float const hand_up[3])
{
float curls[4];
int fingers;
if (vr_arm_nodes.fingers_graph_index != vr_arm_skeleton.graph_index)
{
vr_arm_nodes.fingers_graph_index = vr_arm_skeleton.graph_index;
vr_finger_nodes(&vr_arm_skeleton, vr_arm_nodes.wrist[0], "l ", vr_arm_nodes.fingers[0]);
vr_finger_nodes(&vr_arm_skeleton, vr_arm_nodes.wrist[1], "r ", vr_arm_nodes.fingers[1]);
}
if (left_free && vr_arm_nodes.wrist[0] != NONE &&
halo_vr_finger_curls(local_player_index, 0, curls))
{
/* the palm's frame: along the fingers, up the knuckles, and out of
the left palm (inwards, to the right) */
real_vector3d forward, up, palm;
real along;
forward.i = hand_forward[0];
forward.j = hand_forward[1];
forward.k = hand_forward[2];
up.i = hand_up[0];
up.j = hand_up[1];
up.k = hand_up[2];
along = dot_product3d(&forward, &up);
vr_vector_scale_add(&up, &forward, -along);
if (normalize3d(&forward) > 1e-4f && normalize3d(&up) > 1e-4f)
{
cross_product3d(&forward, &up, &palm);
vr_pose_fingers(&vr_arm_skeleton, node_matrices, vr_arm_nodes.wrist[0], vr_arm_nodes.fingers[0], curls,
&palm, &forward, &up);
}
}
fingers = halo_vr_finger_curls(local_player_index, 1, curls);
if (vr_arm_nodes.wrist[1] != NONE && (fingers & HALO_VR_FINGERS_TOUCH))
{
/* (resting on the trigger the index curls 0.22 or more) */
vr_straighten_finger(&vr_arm_skeleton, node_matrices, vr_arm_nodes.fingers[1][1],
0.6f * PIN(1.f - curls[1] / 0.22f, 0.f, 1.f));
}
}
static void first_person_weapon_vr_arms(
short local_player_index,
long graph_index,
real_matrix4x3 *node_matrices,
short node_count,
struct render_camera const *weapon_camera,
short state)
{
/* a grenade's throw is the player's own too */
boolean melee = state==_first_person_weapon_state_melee ||
state==_first_person_weapon_state_throw_grenade ||
state==_first_person_weapon_state_throw_grenade_overheated;
/* held ready: firing or not, the gun where it aims */
boolean ready = state==_first_person_weapon_state_idle ||
state==_first_person_weapon_state_primary_fire ||
state==_first_person_weapon_state_secondary_fire ||
state==_first_person_weapon_state_primary_misfire ||
state==_first_person_weapon_state_secondary_misfire;
static char const *const upper_names[2] = { "l upperarm", "r upperarm" };
static char const *const fore_names[2] = { "l forearm", "r forearm" };
static char const *const wrist_names[2] = { "l wriste", "r wriste" };
float head[3], neck[3], body_forward[3];
/* the whole body's shoulders (vr.body "full", vr_body.c) */
real_point3d body_shoulders[2];
boolean body_drawn = FALSE;
real_vector3d forward, left, weapon_left, weapon_right;
real_point3d eye;
real_matrix4x3 animated_upper[2];
real_matrix4x3 animated_forearm[2];
real_matrix4x3 animated_wrist[2];
short side;
boolean gripping;
boolean left_free;
boolean rigid = !halo_vr_free_arm_bends();
int body = halo_vr_body_mode();
float hand_position[3], hand_forward[3], hand_up[3];
if (vr_arm_nodes.graph_index != graph_index)
{
vr_arm_nodes.graph_index = graph_index;
for (side = 0; side < 2; side++)
{
vr_arm_nodes.upper[side] = animation_graph_find_node(graph_index, upper_names[side]);
vr_arm_nodes.fore[side] = animation_graph_find_node(graph_index, fore_names[side]);
vr_arm_nodes.wrist[side] = animation_graph_find_node(graph_index, wrist_names[side]);
}
vr_arm_nodes.gun = animation_graph_find_node(graph_index, "gun");
vr_arm_nodes.left_index = animation_graph_find_node(graph_index, "l index low");
vr_arm_nodes.left_middle = animation_graph_find_node(graph_index, "l middlelow");
vr_arm_nodes.left_pinky = animation_graph_find_node(graph_index, "l pinky low");
vr_arm_nodes.idle_pose_valid = FALSE;
}
vr_hold_pose(node_matrices, node_count, weapon_camera, ready, melee);
if (vr_arm_nodes.gun != NONE && vr_arm_nodes.idle_pose_valid)
{
/* the gun at the ready, as drawn: where the weapon's shots and its
crosshair start (in a reload, where it was at the ready) */
real_matrix4x3 camera_matrix;
real_matrix4x3 const *ready_gun = &vr_arm_nodes.idle_pose[vr_arm_nodes.gun];
real_vector3d const *axes[3];
real_point3d gun;
float barrel[3], direction[3];
real best = 0.f;
short axis;
matrix4x3_from_point_and_vectors(&camera_matrix, &weapon_camera->position, &weapon_camera->forward,
&weapon_camera->up);
matrix4x3_transform_point(&camera_matrix, &ready_gun->position, &gun);
barrel[0] = gun.x;
barrel[1] = gun.y;
barrel[2] = gun.z;
/* the barrel: the gun node's axis nearest the camera's forward, in
the camera's frame (forward, left, up) */
axes[0] = &ready_gun->forward;
axes[1] = &ready_gun->left;
axes[2] = &ready_gun->up;
direction[0] = 1.f;
direction[1] = direction[2] = 0.f;
for (axis = 0; axis < 3; axis++)
{
real along = axes[axis]->i;
if (along * along > best * best)
{
real length = magnitude3d(axes[axis]);
real sign = along < 0.f ? -1.f : 1.f;
best = along;
if (length > 0.f)
{
direction[0] = sign * axes[axis]->i / length;
direction[1] = sign * axes[axis]->j / length;
direction[2] = sign * axes[axis]->k / length;
}
}
}
halo_vr_set_barrel(local_player_index, barrel, direction);
if (vr_arm_nodes.barrel_logged_graph_index != graph_index)
{
/* once per weapon (debug.txt) */
error(2, "VR barrel: %.1f degrees off the weapon camera; gun node %.2f %.2f %.2f m from it (forward, left, up)",
arccosine(PIN(direction[0], -1.f, 1.f)) * 180.f / _pi,
ready_gun->position.x * VR_METRES_PER_WORLD_UNIT, ready_gun->position.y * VR_METRES_PER_WORLD_UNIT,
ready_gun->position.z * VR_METRES_PER_WORLD_UNIT);
vr_arm_nodes.barrel_logged_graph_index = graph_index;
}
}
if (ready && vr_arm_nodes.wrist[1] != NONE)
{
/* where this weapon's animations hold its hand, from the camera
(forward, right, up): the camera is put by it, so that the hand
lands on the real one (vr.c, halo_vr_weapon_camera) */
real_point3d const *wrist = &node_matrices[vr_arm_nodes.wrist[1]].position;
real_vector3d from_camera, camera_right;
float held[3];
vector_from_points3d(&weapon_camera->position, wrist, &from_camera);
cross_product3d(&weapon_camera->forward, &weapon_camera->up, &camera_right);
held[0] = dot_product3d(&from_camera, &weapon_camera->forward);
held[1] = dot_product3d(&from_camera, &camera_right);
held[2] = dot_product3d(&from_camera, &weapon_camera->up);
halo_vr_weapon_wrist(graph_index, held);
}
if (!ready && !melee)
vr_laser_follows_barrel(local_player_index, node_matrices, weapon_camera);
if (!halo_vr_body_frame(local_player_index, head, neck, body_forward) ||
!vr_skeleton_build(&vr_arm_skeleton, graph_index, node_count))
{
return;
}
{
/* (the body is the room's: into model space, where the shoulders
change sides) */
float on[3], normal[3];
if (halo_vr_first_person_mirror(local_player_index, on, normal))
{
vr_mirror_point(on, normal, head);
vr_mirror_point(on, normal, neck);
vr_mirror_vector(normal, body_forward);
}
/* (the drawn body's shoulders: the room's left one is the model's
right in its mirror image) */
if (body == HALO_VR_BODY_FULL && vr_body_shoulders(local_player_index, body_shoulders))
{
body_drawn = TRUE;
if (halo_vr_first_person_mirror(local_player_index, on, normal))
{
real_point3d room_left = body_shoulders[0];
body_shoulders[0] = body_shoulders[1];
body_shoulders[1] = room_left;
for (side = 0; side < 2; side++)
{
float point[3];
point[0] = body_shoulders[side].x;
point[1] = body_shoulders[side].y;
point[2] = body_shoulders[side].z;
vr_mirror_point(on, normal, point);
body_shoulders[side].x = point[0];
body_shoulders[side].y = point[1];
body_shoulders[side].z = point[2];
}
}
}
}
/* (the shoulders hang from the neck, which the head turns about) */
eye.x = neck[0];
eye.y = neck[1];
eye.z = neck[2];
forward.i = body_forward[0];
forward.j = body_forward[1];
forward.k = 0.f;
cross_product3d(global_up3d, &forward, &left);
cross_product3d(&weapon_camera->up, &weapon_camera->forward, &weapon_left);
weapon_right.i = -weapon_left.i;
weapon_right.j = -weapon_left.j;
weapon_right.k = -weapon_left.k;
/* the animated arms, for the forearms' twist */
for (side = 0; side < 2; side++)
{
if (vr_arm_nodes.fore[side] != NONE && vr_arm_nodes.wrist[side] != NONE)
{
animated_forearm[side] = node_matrices[vr_arm_nodes.fore[side]];
animated_wrist[side] = node_matrices[vr_arm_nodes.wrist[side]];
if (vr_arm_nodes.upper[side] != NONE)
animated_upper[side] = node_matrices[vr_arm_nodes.upper[side]];
}
}
/* the left hand on the foregrip, or at the controller */
gripping = FALSE;
left_free = FALSE;
if (vr_arm_nodes.wrist[0] != NONE && vr_arm_nodes.wrist[1] != NONE)
{
real_matrix4x3 const *left_wrist = &node_matrices[vr_arm_nodes.wrist[0]];
real_matrix4x3 const *right_wrist = &node_matrices[vr_arm_nodes.wrist[1]];
real_vector3d between;
float foregrip[3], reference[3];
vector_from_points3d(&right_wrist->position, &left_wrist->position, &between);
foregrip[0] = left_wrist->position.x;
foregrip[1] = left_wrist->position.y;
foregrip[2] = left_wrist->position.z;
reference[0] = dot_product3d(&between, &weapon_camera->forward);
reference[1] = dot_product3d(&between, &weapon_right);
reference[2] = dot_product3d(&between, &weapon_camera->up);
gripping = halo_vr_two_hand(local_player_index, foregrip, reference);
}
/* (the weapon alone: no hands to pose) */
if (body == HALO_VR_BODY_OFF)
return;
if (!gripping && vr_arm_nodes.wrist[0] != NONE)
{
if (halo_vr_free_hand(local_player_index, hand_position, hand_forward, hand_up))
{
/* (rigid: the hand and its arm carried to the controller now;
bent, the arm is solved to it first, below, and the hand turned
after, as HaloCE-Quest-VR does: turned first, the solve's swing
turned it on and the arm's skin stretched) */
if (rigid)
vr_left_hand_free(&vr_arm_skeleton, node_matrices, rigid, hand_position, hand_forward, hand_up);
left_free = TRUE;
}
}
for (side = 0; side < 2; side++)
{
real_point3d shoulder;
real_vector3d pole;
short bones[NUMBER_OF_VR_ARM_BONES];
real outward = side ? -1.f : 1.f; /* left is +left */
if (vr_arm_nodes.upper[side] == NONE || vr_arm_nodes.fore[side] == NONE || vr_arm_nodes.wrist[side] == NONE)
continue;
/* the shoulder: below the eyes, behind them (a little behind the
neck: in front of it, the open end of the sleeve came into view
looking down to the side), to the side, square to the torso */
shoulder = eye;
point_from_line3d(&shoulder, global_up3d, -0.20f / VR_METRES_PER_WORLD_UNIT, &shoulder);
point_from_line3d(&shoulder, &forward, -0.04f / VR_METRES_PER_WORLD_UNIT, &shoulder);
point_from_line3d(&shoulder, &left, outward * 0.19f / VR_METRES_PER_WORLD_UNIT, &shoulder);
/* (the whole body drawn: from its shoulders) */
if (body_drawn)
shoulder = body_shoulders[side];
/* the elbow bends down, out and back */
pole.i = -global_up3d->i;
pole.j = -global_up3d->j;
pole.k = -global_up3d->k;
vr_vector_scale_add(&pole, &left, outward * 0.6f);
vr_vector_scale_add(&pole, &forward, -0.3f);
/* and as the hand rolls: an elbow lifted out to the side rolls the
controller's top in, an elbow tucked in rolls it out. Its underside
(away from the thumb) leads the elbow: the right hand's the weapon's,
the free left hand's its controller's (the hand on the foregrip
keeps the default) */
{
real_vector3d below;
boolean rolls = FALSE;
if (side)
{
below.i = -weapon_camera->up.i;
below.j = -weapon_camera->up.j;
below.k = -weapon_camera->up.k;
rolls = TRUE;
}
else if (left_free)
{
below.i = -hand_up[0];
below.j = -hand_up[1];
below.k = -hand_up[2];
rolls = TRUE;
}
if (rolls && normalize3d(&pole) > 0.f)
{
vr_vector_scale_add(&pole, &below, 1.5f);
if (normalize3d(&pole) <= 0.f)
{
pole.i = -global_up3d->i;
pole.j = -global_up3d->j;
pole.k = -global_up3d->k;
}
}
}
bones[_vr_arm_upper] = vr_arm_nodes.upper[side];
bones[_vr_arm_fore] = vr_arm_nodes.fore[side];
bones[_vr_arm_hand] = vr_arm_nodes.wrist[side];
/* (the hands alone: the arms are shrunk away below) */
if (body == HALO_VR_BODY_HANDS)
continue;
if (!side && left_free && rigid)
{
/* the free left arm keeps its shape (vr_left_hand_free), but
for the upper arm, which points back at the shoulder */
vr_point_upper_arm(node_matrices, vr_arm_nodes.upper[side], vr_arm_nodes.fore[side], &shoulder);
continue;
}
if (!side && left_free)
{
/* the free left arm bent to the controller's grip (the wrist
7 cm behind it), the hand turned to the controller after */
real_point3d target;
target.x = hand_position[0] - hand_forward[0] * 0.07f / VR_METRES_PER_WORLD_UNIT;
target.y = hand_position[1] - hand_forward[1] * 0.07f / VR_METRES_PER_WORLD_UNIT;
target.z = hand_position[2] - hand_forward[2] * 0.07f / VR_METRES_PER_WORLD_UNIT;
vr_solve_arm(&vr_arm_skeleton, node_matrices, bones, shoulder, &target, &pole, FALSE,
&vr_arm_history[side]);
vr_left_hand_free(&vr_arm_skeleton, node_matrices, FALSE, hand_position, hand_forward, hand_up);
}
else
{
/* the hand stays where it is (on the weapon): the arm bends to
it */
vr_solve_arm(&vr_arm_skeleton, node_matrices, bones, shoulder,
&node_matrices[bones[_vr_arm_hand]].position, &pole, TRUE, &vr_arm_history[side]);
}
vr_align_elbow(node_matrices, bones, &animated_upper[side], &animated_forearm[side], &animated_wrist[side]);
vr_distribute_arm_twist(node_matrices, bones, &animated_forearm[side], &animated_wrist[side],
&vr_arm_history[side]);
}
first_person_weapon_vr_fingers(local_player_index, node_matrices, left_free, hand_forward, hand_up);
if (body == HALO_VR_BODY_HANDS)
{
for (side = 0; side < 2; side++)
{
short bones[NUMBER_OF_VR_ARM_BONES];
bones[_vr_arm_upper] = vr_arm_nodes.upper[side];
bones[_vr_arm_fore] = vr_arm_nodes.fore[side];
bones[_vr_arm_hand] = vr_arm_nodes.wrist[side];
if (bones[_vr_arm_upper] != NONE && bones[_vr_arm_fore] != NONE && bones[_vr_arm_hand] != NONE)
vr_hide_arm(&vr_arm_skeleton, node_matrices, bones, vr_arm_nodes.fingers[side], vr_arm_nodes.gun);
}
}
}
#endif
/* port: the weapon's orientations are the animation graph's nodes (64 at
most, the weapon's arrays): an animation of more nodes than the graph has,
or than the arrays hold (the map's counts), is not applied, said once. Every
retail first-person animation has the graph's count (up to 54). That is not
the first-person model's count (up to 17, the weapon without the hands),
which every retail animation is above. */
static boolean first_person_weapon_animation_fits(
struct animation_graph const *animation_graph,
struct animation const *animation)
{
static boolean too_many_nodes_reported = FALSE;
if (animation->node_count>=0 &&
animation->node_count<=animation_graph->nodes.count &&
animation->node_count<=MAXIMUM_NODES_PER_ANIMATION)
{
return TRUE;
}
if (!too_many_nodes_reported)
{
too_many_nodes_reported= TRUE;
error(
_error_silent,
"first person animation %.32s of %d nodes (the graph has %d) not applied",
animation->name,
animation->node_count,
animation_graph->nodes.count);
}
return FALSE;
}
static void first_person_weapon_build_node_matrices(
short local_player_index)
{
struct first_person_weapon *first_person_weapon= first_person_weapon_get(local_player_index);
struct render_camera const *camera= &render.camera;
#ifdef HALO_ANDROID
/* VR: posed from the weapon hand's controller, so the weapon lies in the
hand (port/linux/src/vr.c) */
struct render_camera weapon_camera;
float weapon_position[3], weapon_forward[3], weapon_up[3];
/* (the weapon may have been deleted since the tick: below, the game
forgets it, "deleted unexpectedly") */
if (first_person_weapon->weapon_index!=NONE &&
weapon_try_and_get(first_person_weapon->weapon_index))
{
/* once per skeleton: its nodes, for the arms (debug.txt) */
static long logged_graph_index= NONE;
struct weapon_datum *weapon= weapon_get(first_person_weapon->weapon_index);
long graph_index= weapon_definition_get(weapon->definition_index)->weapon.interface_definition.first_person_animations.index;
if (graph_index!=NONE && graph_index!=logged_graph_index)
{
short node_index;
logged_graph_index= graph_index;
for (node_index= 0; node_index<animation_graph_node_count(graph_index); node_index++)
{
error(2, "first-person node %d '%s' parent %d", node_index,
animation_graph_node_name(graph_index, node_index),
animation_graph_node_parent(graph_index, node_index));
}
}
}
if (halo_vr_weapon_camera(local_player_index,
first_person_weapon->weapon_index!=NONE ?
weapon_definition_get(weapon_get(first_person_weapon->weapon_index)->definition_index)->
weapon.interface_definition.first_person_animations.index :
NONE,
weapon_position, weapon_forward, weapon_up))
{
weapon_camera= render.camera;
weapon_camera.position.x= weapon_position[0];
weapon_camera.position.y= weapon_position[1];
weapon_camera.position.z= weapon_position[2];
weapon_camera.forward.i= weapon_forward[0];
weapon_camera.forward.j= weapon_forward[1];
weapon_camera.forward.k= weapon_forward[2];
weapon_camera.up.i= weapon_up[0];
weapon_camera.up.j= weapon_up[1];
weapon_camera.up.k= weapon_up[2];
camera= &weapon_camera;
}
#endif
if (!first_person_weapon->rendered)
{
euler_angles2d_from_vector3d(&first_person_weapon->render_facing, &camera->forward);
first_person_weapon->render_position= camera->position;
}
/* The native ports draw several frames per tick. The turning sway
(first_person_weapon_update) takes the facing change since the last
frame as a tick's worth: move the last facing on once a tick. */
{
static long last_render_ticks[MAXIMUM_LOCAL_PLAYERS] = { NONE, NONE, NONE, NONE };
if (!halo_interpolation_enabled() || last_render_ticks[local_player_index] != game_time_get())
{
first_person_weapon->last_render_facing= first_person_weapon->render_facing;
first_person_weapon->last_render_position= first_person_weapon->render_position;
last_render_ticks[local_player_index]= game_time_get();
}
}
euler_angles2d_from_vector3d(&first_person_weapon->render_facing, &camera->forward);
first_person_weapon->render_position= camera->position;
first_person_weapon->render_forward= camera->forward;
first_person_weapon->rendered= TRUE;
if (first_person_weapon->weapon_index!=NONE &&
!weapon_try_and_get(first_person_weapon->weapon_index))
{
error(
_error_log,
"local player %d, weapon (0x%x), deleted unexpectedly",
local_player_index,
first_person_weapon->weapon_index);
first_person_weapon_forget_weapon(local_player_index);
}
if (first_person_weapon->weapon_index!=NONE)
{
struct weapon_datum *weapon= weapon_get(first_person_weapon->weapon_index);
struct weapon_definition *weapon_definition= weapon_definition_get(weapon->definition_index);
struct model *model= model_definition_get(
weapon_definition->weapon.interface_definition.first_person_model.index);
struct animation_graph *animation_graph= animation_graph_definition_get(
weapon_definition->weapon.interface_definition.first_person_animations.index);
if (animation_graph->first_person_weapon_animations.count)
{
struct animation_graph_first_person_weapon_animations *first_person_weapon_animations=
TAG_BLOCK_GET_ELEMENT(
&animation_graph->first_person_weapon_animations,
0,
struct animation_graph_first_person_weapon_animations);
if (first_person_weapon_animations)
{
short firing_animation_index;
short overlay_animation_index;
if (first_person_weapon->state_animation.index!=NONE)
{
struct animation *state_animation= TAG_BLOCK_GET_ELEMENT(
&animation_graph->animations,
first_person_weapon->state_animation.index,
struct animation);
animation_get_node_orientations(
NULL,
state_animation,
first_person_weapon->state_animation.frame_index,
first_person_weapon->node_orientations);
/* port: a PAL map's animation, slowed to the NTSC maps' pace,
between the frame it is on and the next: held on the frame, it
stood still for that tick, and a reload moved in fits and starts
(port/linux/game/pal_tags.c) */
{
real fraction= pal_tags_first_person_fraction(
local_player_index,
weapon_definition->weapon.interface_definition.first_person_animations.index,
first_person_weapon->state_animation.index,
first_person_weapon->state_animation.frame_index);
if (fraction>0.0f &&
first_person_weapon->state_animation.frame_index+1<state_animation->frame_count)
{
real_orientation next_node_orientations[MAXIMUM_NODES_PER_ANIMATION];
short node_index;
animation_get_node_orientations(
NULL,
state_animation,
(short)(first_person_weapon->state_animation.frame_index+1),
next_node_orientations);
/* port: no more nodes than the orientations hold (a
map's count) */
for (node_index= 0; node_index<MIN(state_animation->node_count, MAXIMUM_NODES_PER_ANIMATION); node_index++)
{
orientations_interpolate(
&first_person_weapon->node_orientations[node_index],
&next_node_orientations[node_index],
fraction,
&first_person_weapon->node_orientations[node_index]);
}
}
}
}
else
{
match_vassert(
"c:\\halo\\SOURCE\\interface\\first_person_weapons.c",
1294,
FALSE,
NULL);
model_get_node_orientations(model, first_person_weapon->node_orientations);
}
firing_animation_index= VALID_INDEX(
_first_person_weapon_animation_ammunition,
first_person_weapon_animations->animations.count) ?
animation_graph_animation_index_get(
&first_person_weapon_animations->animations)
[_first_person_weapon_animation_ammunition].animation_index :
(short)NONE;
if (firing_animation_index!=NONE)
{
struct animation *firing_animation= TAG_BLOCK_GET_ELEMENT(
&animation_graph->animations,
firing_animation_index,
struct animation);
/* port: and one of the graph's nodes (see first_person_weapon_animation_fits) */
if (!first_person_weapon_animation_fits(animation_graph, firing_animation))
{
/* (not applied) */
}
else if (weapon_definition->weapon.weapon_type==_weapon_type_needler &&
(first_person_weapons->state==_first_person_weapon_state_reload_while_empty ||
first_person_weapons->state==_first_person_weapon_state_reload_while_full))
{
short frame_index= weapon->weapon.magazines[0].rounds_loaded;
short settle_ticks= weapon->weapon.magazines[0].original_time-
weapon->weapon.magazines[0].state_timer;
if (settle_ticks>=FIRST_PERSON_WEAPON_RELOAD_SETTLE_TICKS)
{
struct weapon_magazine_definition *magazine_definition= TAG_BLOCK_GET_ELEMENT(
&weapon_definition->weapon.magazines,
0,
struct weapon_magazine_definition);
real settle_fraction= (settle_ticks-FIRST_PERSON_WEAPON_RELOAD_SETTLE_TICKS)*0.2f;
short predicted_frame_index;
if (settle_fraction>1.0f)
{
settle_fraction= 1.0f;
}
predicted_frame_index= (short)MIN(
weapon->weapon.magazines[0].rounds_total,
magazine_definition->rounds_loaded_maximum);
frame_index= (short)((predicted_frame_index-frame_index)*settle_fraction)+
frame_index;
}
overlay_animation_apply(
firing_animation,
frame_index,
first_person_weapon->node_orientations);
}
else if (weapon->weapon.magazines[0].rounds_loaded<firing_animation->frame_count)
{
overlay_animation_apply(
firing_animation,
weapon->weapon.magazines[0].rounds_loaded,
first_person_weapon->node_orientations);
}
}
if (first_person_weapon->moving_animation.index!=NONE)
{
struct animation *moving_animation= TAG_BLOCK_GET_ELEMENT(
&animation_graph->animations,
first_person_weapon->moving_animation.index,
struct animation);
/* port: and one of the graph's nodes */
if (first_person_weapon_animation_fits(animation_graph, moving_animation))
{
overlay_animation_apply(
moving_animation,
first_person_weapon->moving_animation.frame_index,
first_person_weapon->node_orientations);
}
}
if (first_person_weapon->overcharged_jitter_animation.index!=NONE)
{
struct animation *overcharged_jitter_animation= TAG_BLOCK_GET_ELEMENT(
&animation_graph->animations,
first_person_weapon->overcharged_jitter_animation.index,
struct animation);
/* port: and one of the graph's nodes */
if (first_person_weapon_animation_fits(animation_graph, overcharged_jitter_animation))
{
overlay_animation_apply_continuous_scaled(
overcharged_jitter_animation,
first_person_weapon->overcharged_jitter_animation.frame_index,
weapon->weapon.overcharged+0.5f,
first_person_weapon->node_orientations);
}
}
overlay_animation_index= VALID_INDEX(
_first_person_weapon_animation_overlays,
first_person_weapon_animations->animations.count) ?
animation_graph_animation_index_get(
&first_person_weapon_animations->animations)
[_first_person_weapon_animation_overlays].animation_index :
(short)NONE;
if (overlay_animation_index!=NONE)
{
struct animation *overlay_animation= TAG_BLOCK_GET_ELEMENT(
&animation_graph->animations,
overlay_animation_index,
struct animation);
/* port: and one of the graph's nodes */
if (overlay_animation->frame_count>=NUMBER_OF_FIRST_PERSON_WEAPON_OVERLAY_FRAMES &&
first_person_weapon_animation_fits(animation_graph, overlay_animation))
{
if (first_person_weapon->position.i>0.0f)
{
overlay_animation_apply_scaled(
overlay_animation,
0,
first_person_weapon->position.i,
first_person_weapon->node_orientations);
}
else if (first_person_weapon->position.i<0.0f)
{
overlay_animation_apply_scaled(
overlay_animation,
1,
-first_person_weapon->position.i,
first_person_weapon->node_orientations);
}
if (first_person_weapon->position.j>0.0f)
{
overlay_animation_apply_scaled(
overlay_animation,
3,
first_person_weapon->position.j,
first_person_weapon->node_orientations);
}
else if (first_person_weapon->position.j<0.0f)
{
overlay_animation_apply_scaled(
overlay_animation,
2,
-first_person_weapon->position.j,
first_person_weapon->node_orientations);
}
if (first_person_weapon->turning.i>0.0f)
{
overlay_animation_apply_scaled(
overlay_animation,
4,
first_person_weapon->turning.i,
first_person_weapon->node_orientations);
}
else if (first_person_weapon->turning.i<0.0f)
{
overlay_animation_apply_scaled(
overlay_animation,
5,
-first_person_weapon->turning.i,
first_person_weapon->node_orientations);
}
if (first_person_weapon->turning.j>0.0f)
{
overlay_animation_apply_scaled(
overlay_animation,
7,
first_person_weapon->turning.j,
first_person_weapon->node_orientations);
}
else if (first_person_weapon->turning.j<0.0f)
{
overlay_animation_apply_scaled(
overlay_animation,
6,
-first_person_weapon->turning.j,
first_person_weapon->node_orientations);
}
if (first_person_weapon->firing_push_back>0.0f)
{
overlay_animation_apply_scaled(
overlay_animation,
8,
first_person_weapon->firing_push_back,
first_person_weapon->node_orientations);
}
}
}
if (first_person_weapon->interpolation_frame_count>0)
{
interpolate_node_orientations(
(short)animation_graph->nodes.count,
first_person_weapon->original_node_orientations,
first_person_weapon->node_orientations,
first_person_weapon->interpolation_frame_index,
first_person_weapon->interpolation_frame_count);
}
}
}
animation_graph_node_matrices_from_orientations(
weapon_definition->weapon.interface_definition.first_person_animations.index,
first_person_weapon->node_matrices,
first_person_weapon->node_orientations,
&camera->position,
&camera->forward,
&camera->up);
/* the pose between the last two ticks (render_interpolation.c) */
/* port: no more nodes than the weapon's matrices hold (a map's count) */
render_interpolation_first_person(
local_player_index,
first_person_weapon->node_matrices,
(short)MIN(animation_graph->nodes.count, MAXIMUM_NODES_PER_ANIMATION),
camera);
#ifdef HALO_ANDROID
first_person_weapon_vr_pose_serial[local_player_index]= camera!=&render.camera ? halo_vr_frame_serial() : 0;
first_person_weapon_vr_pose_weapon[local_player_index]= first_person_weapon->weapon_index;
if (camera!=&render.camera)
{
unsigned long long timing= halo_vr_timing_start();
first_person_weapon_vr_arms(
local_player_index,
weapon_definition->weapon.interface_definition.first_person_animations.index,
first_person_weapon->node_matrices,
(short)MIN(animation_graph->nodes.count, MAXIMUM_NODES_PER_ANIMATION),
camera,
first_person_weapon->state);
/* (left-handed: out of model space; what the frame takes from
the pose after, markers, effects, is then the room's) */
vr_mirror_pose(local_player_index, first_person_weapon->node_matrices,
(short)MIN(animation_graph->nodes.count, MAXIMUM_NODES_PER_ANIMATION));
halo_vr_timing_end(HALO_VR_TIMING_ARMS, timing);
}
else
{
debug_mirror_first_person(camera, first_person_weapon->node_matrices,
(short)MIN(animation_graph->nodes.count, MAXIMUM_NODES_PER_ANIMATION));
}
#endif
}
return;
}
static void first_person_weapon_message(
short local_player_index,
short message_type)
{
if (local_player_index!=NONE)
{
struct first_person_weapon *first_person_weapon= first_person_weapon_get(local_player_index);
short state= NONE;
switch (message_type)
{
case _first_person_weapon_message_drop:
first_person_weapon_forget_weapon(local_player_index);
break;
case _first_person_weapon_message_ready:
first_person_weapon_switch_weapons(local_player_index);
break;
case _first_person_weapon_message_primary_fire:
first_person_weapon->firing_push_back_velocity+=
FIRST_PERSON_WEAPON_FIRING_PUSH_BACK_VELOCITY;
break;
case _first_person_weapon_message_reload_while_empty:
case _first_person_weapon_message_reload_while_full:
player_control_unzoom(first_person_weapon->unit_index);
break;
}
if (first_person_weapon->weapon_index!=NONE)
{
struct weapon_datum *weapon= weapon_get(first_person_weapon->weapon_index);
if (weapon->definition_index!=NONE)
{
struct weapon_definition *weapon_definition= weapon_definition_get(weapon->definition_index);
switch (weapon_definition->weapon.weapon_type)
{
case _weapon_type_shotgun:
if (message_type==_first_person_weapon_message_reload_while_empty ||
message_type==_first_person_weapon_message_reload_while_full)
{
struct weapon_magazine_definition *magazine_definition= TAG_BLOCK_GET_ELEMENT(
&weapon_definition->weapon.magazines,
0,
struct weapon_magazine_definition);
short rounds_loaded= weapon->weapon.magazines[0].rounds_loaded;
short rounds_total= weapon->weapon.magazines[0].rounds_total;
if (first_person_weapon->state==_first_person_weapon_state_shotgun_enter_reload ||
first_person_weapon->state==_first_person_weapon_state_overheated_exit ||
first_person_weapon->state==_first_person_weapon_state_shotgun_exit_reload_empty ||
first_person_weapon->state==_first_person_weapon_state_shotgun_exit_reload_full ||
first_person_weapon->state==_first_person_weapon_state_reload_while_empty ||
first_person_weapon->state==_first_person_weapon_state_reload_while_full ||
weapon->weapon.magazines[0].state)
{
if (MIN(magazine_definition->rounds_loaded_maximum-rounds_loaded, rounds_total)==1)
{
first_person_weapon->shotgun_reload_type= _shotgun_reload_type_last_round;
}
else
{
first_person_weapon->shotgun_reload_type= NONE;
}
}
else
{
short shells_to_reload= (short)MIN(
magazine_definition->rounds_loaded_maximum-rounds_loaded,
rounds_total);
first_person_weapon->shotgun_shells_to_reload= shells_to_reload;
first_person_weapon->shotgun_empty= rounds_loaded==0;
first_person_weapon->shotgun_reload_type= (shells_to_reload!=1) ?
_shotgun_reload_type_first_round :
_shotgun_reload_type_first_and_last_round;
}
switch (first_person_weapon->shotgun_reload_type)
{
case _shotgun_reload_type_first_round:
case _shotgun_reload_type_first_and_last_round:
state= _first_person_weapon_state_shotgun_enter_reload;
break;
case NONE:
state= _first_person_weapon_state_reload_while_empty;
break;
}
}
break;
}
}
}
if (state==NONE)
{
state= (short)first_person_weapon_state_from_weapon_message(message_type);
}
if (state!=NONE)
{
first_person_weapon_set_state(local_player_index, state, TRUE);
}
switch (message_type)
{
case _first_person_weapon_message_ready:
first_person_weapon->interpolation_frame_count= 0;
break;
}
}
return;
}
static void first_person_weapon_switch_weapons(
short local_player_index)
{
struct first_person_weapon *first_person_weapon= first_person_weapon_get(local_player_index);
boolean visible= first_person_weapon->visible;
first_person_weapon->weapon_index= NONE;
if (visible)
{
first_person_weapon_set_visibility(local_player_index, FALSE);
}
if (first_person_weapon->unit_index!=NONE)
{
struct unit_datum *unit= unit_get(first_person_weapon->unit_index);
long weapon_index= unit_inventory_get_weapon(
first_person_weapon->unit_index,
unit->unit.current_weapon_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->weapon.interface_definition.first_person_model.index!=NONE &&
weapon_definition->weapon.interface_definition.first_person_animations.index!=NONE)
{
struct animation_graph *animation_graph= animation_graph_definition_get(
weapon_definition->weapon.interface_definition.first_person_animations.index);
if (animation_graph->first_person_weapon_animations.count)
{
struct animation_graph_first_person_weapon_animations *first_person_weapon_animations=
TAG_BLOCK_GET_ELEMENT(
&animation_graph->first_person_weapon_animations,
0,
struct animation_graph_first_person_weapon_animations);
if (first_person_weapon_animations)
{
struct game_globals_first_person_interface *first_person_interface;
first_person_weapon->overlay_animation_index= NONE;
if (VALID_INDEX(
_first_person_weapon_animation_overlays,
first_person_weapon_animations->animations.count))
{
short overlay_animation_index= animation_graph_animation_index_get(
&first_person_weapon_animations->animations)
[_first_person_weapon_animation_overlays].animation_index;
if (overlay_animation_index!=NONE &&
TAG_BLOCK_GET_ELEMENT(
&animation_graph->animations,
overlay_animation_index,
struct animation)->frame_count>=
NUMBER_OF_FIRST_PERSON_WEAPON_OVERLAY_FRAMES)
{
first_person_weapon->overlay_animation_index= overlay_animation_index;
}
}
first_person_interface= TAG_BLOCK_GET_ELEMENT(
&scenario_get_game_globals()->first_person_interface,
0,
struct game_globals_first_person_interface);
if (first_person_interface->hands.index!=NONE)
{
first_person_weapon->hands_node_remapping_table_valid=
model_build_remapping_table_for_animation_graph(
first_person_interface->hands.index,
weapon_definition->weapon.interface_definition.first_person_animations.index,
first_person_weapon->hands_node_remapping_table);
}
first_person_weapon->weapon_node_remapping_table_valid=
model_build_remapping_table_for_animation_graph(
weapon_definition->weapon.interface_definition.first_person_model.index,
weapon_definition->weapon.interface_definition.first_person_animations.index,
first_person_weapon->weapon_node_remapping_table);
if (first_person_weapon->weapon_node_remapping_table_valid &&
first_person_weapon->hands_node_remapping_table_valid)
{
first_person_weapon->weapon_index= weapon_index;
first_person_weapon->state= NONE;
first_person_weapon->state_animation.index= NONE;
first_person_weapon->moving_animation.index= NONE;
first_person_weapon->overcharged_jitter_animation.index= NONE;
first_person_weapon->firing_push_back= 0.0f;
first_person_weapon->firing_push_back_velocity= 0.0f;
first_person_weapon->ticks_idle= 0;
first_person_weapon->current_sound_index= NONE;
first_person_weapon->current_sound_state= NONE;
first_person_weapon_set_state(
local_player_index,
_first_person_weapon_state_idle,
TRUE);
first_person_weapon->interpolation_frame_count= 0;
if (visible)
{
first_person_weapon_set_visibility(local_player_index, TRUE);
}
}
}
}
}
}
}
first_person_weapon_predict(local_player_index);
return;
}
static void first_person_weapon_new_unit(
short local_player_index,
long unit_index)
{
struct first_person_weapon *first_person_weapon= first_person_weapon_get(local_player_index);
first_person_weapon->rendered= FALSE;
first_person_weapon->unit_index= unit_index;
first_person_weapon_switch_weapons(local_player_index);
return;
}
static void first_person_weapon_next_state(
short local_player_index)
{
struct first_person_weapon *first_person_weapon= first_person_weapon_get(local_player_index);
short new_state= NONE;
switch (first_person_weapon->state)
{
case _first_person_weapon_state_idle:
case _first_person_weapon_state_posing:
case _first_person_weapon_state_primary_fire:
case _first_person_weapon_state_secondary_fire:
case _first_person_weapon_state_primary_misfire:
case _first_person_weapon_state_secondary_misfire:
case _first_person_weapon_state_melee:
case _first_person_weapon_state_light_off:
case _first_person_weapon_state_light_on:
case _first_person_weapon_state_shotgun_exit_reload_empty:
case _first_person_weapon_state_shotgun_exit_reload_full:
case _first_person_weapon_state_ready:
case _first_person_weapon_state_throw_grenade:
case _first_person_weapon_state_overheated_exit:
new_state= _first_person_weapon_state_idle;
break;
case _first_person_weapon_state_overheating:
case _first_person_weapon_state_overheating_again:
case _first_person_weapon_state_throw_grenade_overheated:
case _first_person_weapon_state_overheating_super_recoil:
new_state= _first_person_weapon_state_overheated;
break;
case _first_person_weapon_state_put_away:
first_person_weapon->state_animation.frame_index--;
break;
case _first_person_weapon_state_shotgun_enter_reload:
{
struct weapon_datum *weapon= weapon_get(first_person_weapon->weapon_index);
struct weapon_definition *weapon_definition= weapon_definition_get(weapon->definition_index);
if (weapon_definition->weapon.weapon_type==_weapon_type_shotgun &&
first_person_weapon->shotgun_reload_type==_shotgun_reload_type_first_and_last_round)
{
new_state= first_person_weapon->shotgun_empty ?
_first_person_weapon_state_shotgun_exit_reload_empty :
_first_person_weapon_state_shotgun_exit_reload_full;
}
else
{
new_state= _first_person_weapon_state_idle;
}
break;
}
case _first_person_weapon_state_reload_while_empty:
case _first_person_weapon_state_reload_while_full:
{
struct weapon_datum *weapon= weapon_get(first_person_weapon->weapon_index);
struct weapon_definition *weapon_definition= weapon_definition_get(weapon->definition_index);
if (weapon_definition->weapon.weapon_type!=_weapon_type_shotgun ||
!first_person_weapon->shotgun_reload_type ||
first_person_weapon->shotgun_reload_type==NONE)
{
new_state= _first_person_weapon_state_idle;
}
else
{
match_assert(
"c:\\halo\\SOURCE\\interface\\first_person_weapons.c",
799,
first_person_weapon->shotgun_reload_type == _shotgun_reload_type_first_and_last_round ||
first_person_weapon->shotgun_reload_type == _shotgun_reload_type_last_round);
new_state= first_person_weapon->shotgun_empty ?
_first_person_weapon_state_shotgun_exit_reload_empty :
_first_person_weapon_state_shotgun_exit_reload_full;
}
break;
}
}
if (new_state!=NONE)
{
first_person_weapon_set_state(local_player_index, new_state, FALSE);
}
return;
}
static void first_person_weapon_update(
short local_player_index)
{
struct first_person_weapon *first_person_weapon= first_person_weapon_get(local_player_index);
if (first_person_weapon->weapon_index!=NONE &&
!weapon_try_and_get(first_person_weapon->weapon_index))
{
error(
_error_log,
"local player %d, weapon (0x%x), deleted unexpectedly",
local_player_index,
first_person_weapon->weapon_index);
first_person_weapon_forget_weapon(local_player_index);
}
if (first_person_weapon->unit_index!=NONE && first_person_weapon->weapon_index!=NONE)
{
struct unit_datum *unit= unit_get(first_person_weapon->unit_index);
struct weapon_datum *weapon= weapon_get(first_person_weapon->weapon_index);
struct weapon_definition *weapon_definition= weapon_definition_get(weapon->definition_index);
struct animation_graph *animation_graph;
long sound_definition_index;
short animation_update_result;
boolean moving;
model_definition_get(weapon_definition->weapon.interface_definition.first_person_model.index);
animation_graph= animation_graph_definition_get(
weapon_definition->weapon.interface_definition.first_person_animations.index);
if (first_person_weapon->state==_first_person_weapon_state_overheated ||
first_person_weapon->state==_first_person_weapon_state_overheating)
{
if (TEST_FLAG(weapon->weapon.flags, _weapon_overheated_exit_bit))
{
first_person_weapon_set_state(
local_player_index,
_first_person_weapon_state_overheated_exit,
TRUE);
}
if (!TEST_FLAG(weapon->weapon.flags, _weapon_overheated_bit))
{
first_person_weapon_set_state(
local_player_index,
_first_person_weapon_state_idle,
TRUE);
}
}
/* port: a PAL map's first-person animation at the NTSC maps' pace,
which the weapon's timing keeps (port/linux/game/pal_tags.c) */
if (!pal_tags_first_person_advance(local_player_index,
weapon_definition->weapon.interface_definition.first_person_animations.index,
first_person_weapon->state_animation.index, first_person_weapon->state_animation.frame_index))
{
animation_update_result= _animation_no_key_frame;
sound_definition_index= NONE;
}
else
animation_update_result= animation_update_render_only(
weapon_definition->weapon.interface_definition.first_person_animations.index,
&first_person_weapon->state_animation,
&sound_definition_index);
if (animation_update_result==_animation_key_frame)
{
/* a key frame does not advance the first-person weapon state */
}
else if (animation_update_result==_animation_will_restart_on_next_frame)
{
first_person_weapon_next_state(local_player_index);
}
if (sound_definition_index!=NONE &&
director_get_perspective(local_player_index)==_director_perspective_first_person)
{
first_person_weapon->current_sound_index= object_impulse_sound_new(
first_person_weapon->weapon_index,
sound_definition_index,
NONE,
global_origin3d,
global_forward3d,
1.0f);
first_person_weapon->current_sound_state= first_person_weapon->state;
}
moving= magnitude3d(&unit->unit.throttle)>0.1f;
if (unit_flying_through_air(first_person_weapon->unit_index))
{
moving= FALSE;
}
if (first_person_weapon->moving_animation.index!=NONE)
{
animation_update_render_only(
weapon_definition->weapon.interface_definition.first_person_animations.index,
&first_person_weapon->moving_animation,
NULL);
if (!moving)
{
if (first_person_weapon->state==_first_person_weapon_state_idle)
{
first_person_weapon_start_interpolation(local_player_index, 6);
}
first_person_weapon->moving_animation.index= NONE;
}
}
else if (moving)
{
struct animation_graph_first_person_weapon_animations *first_person_weapon_animations=
!animation_graph->first_person_weapon_animations.count ?
NULL :
TAG_BLOCK_GET_ELEMENT(
&animation_graph->first_person_weapon_animations,
0,
struct animation_graph_first_person_weapon_animations);
first_person_weapon->moving_animation.frame_index= 0;
/* no NULL test (January, and the later first-party build at /Od 0x61fcea): a non-NONE
* weapon_index is only committed by first_person_weapon_switch_weapons inside
* `if (animation_graph->first_person_weapon_animations.count)` for this same graph. The
* new-map reset leaves weapon_index 0 but unit_index NONE, and first_person_weapon_new_unit
* runs switch_weapons (which stores NONE first) as soon as it sets unit_index, so the NULL
* arm above cannot reach this read. */
first_person_weapon->moving_animation.index=
VALID_INDEX(
_first_person_weapon_animation_moving,
first_person_weapon_animations->animations.count) ?
animation_graph_animation_index_get(
&first_person_weapon_animations->animations)
[_first_person_weapon_animation_moving].animation_index :
(short)NONE;
}
if (first_person_weapon->overcharged_jitter_animation.index==NONE)
{
if (first_person_weapon->state==_first_person_weapon_state_charged)
{
struct animation_graph_first_person_weapon_animations *first_person_weapon_animations=
!animation_graph->first_person_weapon_animations.count ?
NULL :
TAG_BLOCK_GET_ELEMENT(
&animation_graph->first_person_weapon_animations,
0,
struct animation_graph_first_person_weapon_animations);
first_person_weapon->overcharged_jitter_animation.frame_index= 0.0f;
/* no NULL test: the same switch_weapons invariant as the moving animation above
* (January and /Od 0x61fd96). */
first_person_weapon->overcharged_jitter_animation.index=
VALID_INDEX(
_first_person_weapon_animation_overcharged_jitter,
first_person_weapon_animations->animations.count) ?
animation_graph_animation_index_get(
&first_person_weapon_animations->animations)
[_first_person_weapon_animation_overcharged_jitter].animation_index :
(short)NONE;
}
}
else if (first_person_weapon->state==_first_person_weapon_state_charged)
{
struct animation *animation= TAG_BLOCK_GET_ELEMENT(
&animation_graph->animations,
first_person_weapon->overcharged_jitter_animation.index,
struct animation);
first_person_weapon->overcharged_jitter_animation.frame_index= (real)fmod(
(weapon->weapon.overcharged+1.0f)*2.0f+
first_person_weapon->overcharged_jitter_animation.frame_index,
(real)animation->frame_count);
}
else
{
first_person_weapon->overcharged_jitter_animation.index= NONE;
}
if (first_person_weapon->rendered)
{
real_vector2d turning;
accelerate_to_position(
&first_person_weapon->position.i,
&first_person_weapon->position_velocity.i,
unit->unit.throttle.i,
0.08f,
0.5f,
-1.0f,
1.0f,
FALSE);
accelerate_to_position(
&first_person_weapon->position.j,
&first_person_weapon->position_velocity.j,
unit->unit.throttle.j,
0.08f,
0.5f,
-1.0f,
1.0f,
FALSE);
turning.i= signed_angular_difference(
first_person_weapon->last_render_facing.yaw,
first_person_weapon->render_facing.yaw)*30.0f;
turning.j= signed_angular_difference(
first_person_weapon->last_render_facing.pitch,
first_person_weapon->render_facing.pitch)*-30.0f;
turning.i= PIN(turning.i, -1.0f, 1.0f);
turning.j= PIN(turning.j, -1.0f, 1.0f);
accelerate_to_position(
&first_person_weapon->turning.i,
&first_person_weapon->turning_velocity.i,
turning.i,
0.03f,
0.2f,
-1.0f,
1.0f,
FALSE);
accelerate_to_position(
&first_person_weapon->turning.j,
&first_person_weapon->turning_velocity.j,
turning.j,
0.03f,
0.2f,
-1.0f,
1.0f,
FALSE);
}
accelerate_to_position(
&first_person_weapon->firing_push_back,
&first_person_weapon->firing_push_back_velocity,
0.0f,
0.01f,
0.2f,
0.0f,
1.0f,
FALSE);
if (first_person_weapon->firing_push_back==1.0f)
{
first_person_weapon->firing_push_back_velocity= 0.0f;
}
if (first_person_weapon->interpolation_frame_count>0)
{
first_person_weapon->interpolation_frame_index++;
if (first_person_weapon->interpolation_frame_index>=
first_person_weapon->interpolation_frame_count)
{
first_person_weapon->interpolation_frame_count= 0;
}
}
if (player_control_get_autoaim_level(local_player_index)!=0.0f ||
player_control_get_zoom_level(local_player_index)!=NONE ||
first_person_weapon->firing_push_back!=0.0f ||
first_person_weapon->position.i!=0.0f ||
first_person_weapon->position.j!=0.0f ||
first_person_weapon->turning.i!=0.0f ||
first_person_weapon->turning.j!=0.0f)
{
first_person_weapon->ticks_idle= 0;
if (first_person_weapon->state==_first_person_weapon_state_posing)
{
first_person_weapon_set_state(
local_player_index,
_first_person_weapon_state_idle,
TRUE);
}
}
else if (first_person_weapon->state==_first_person_weapon_state_idle)
{
struct game_globals_player_information *player_information= TAG_BLOCK_GET_ELEMENT(
&scenario_get_game_globals()->player_information,
0,
struct game_globals_player_information);
if (!first_person_weapon->ticks_until_pose)
{
first_person_weapon->ticks_until_pose= (short)(real_local_random_range(
player_information->first_person_idle_time_lower_bound,
player_information->first_person_idle_time_upper_bound)*TICKS_PER_SECOND);
}
first_person_weapon->ticks_idle++;
if (first_person_weapon->ticks_idle>first_person_weapon->ticks_until_pose)
{
first_person_weapon->ticks_until_pose= 0;
if (real_local_random()>=player_information->first_person_idle_skip_fraction)
{
first_person_weapon_set_state(
local_player_index,
_first_person_weapon_state_posing,
TRUE);
}
}
}
else
{
first_person_weapon->ticks_idle= 0;
}
}
first_person_weapon->ticks_until_predict--;
if (first_person_weapon->ticks_until_predict<=0)
{
first_person_weapon_predict(local_player_index);
}
return;
}
static boolean model_build_remapping_table_for_animation_graph(
long model_index,
long animation_graph_index,
short *node_remapping_table)
{
struct model *model= model_definition_get(model_index);
struct animation_graph *animation_graph= animation_graph_definition_get(animation_graph_index);
boolean valid= TRUE;
short model_node_index;
/* port: no more model nodes than the table holds, and only graph nodes
the weapon's matrices hold (a map's counts) */
for (model_node_index=0; model_node_index<MIN(model->nodes.count, MAXIMUM_NODES_PER_ANIMATION); model_node_index++)
{
struct model_node *model_node= TAG_BLOCK_GET_ELEMENT(
&model->nodes,
model_node_index,
struct model_node);
short animation_graph_node_index= NONE;
short node_index;
for (node_index=0; node_index<MIN(animation_graph->nodes.count, MAXIMUM_NODES_PER_ANIMATION); node_index++)
{
struct animation_graph_node *animation_graph_node= TAG_BLOCK_GET_ELEMENT(
&animation_graph->nodes,
node_index,
struct animation_graph_node);
if (!csstrcmp(model_node->name, animation_graph_node->name))
{
animation_graph_node_index= node_index;
break;
}
}
if (animation_graph_node_index!=NONE)
{
node_remapping_table[model_node_index]= animation_graph_node_index;
}
else
{
valid= FALSE;
}
}
return valid;
}
static void weapon_play_first_person_weapon_sound(
long weapon_index,
short message_type)
{
if (weapon_index!=NONE && message_type!=NONE && weapon_try_and_get(weapon_index))
{
struct weapon_datum *weapon= weapon_get(weapon_index);
struct weapon_definition *weapon_definition= weapon_definition_get(weapon->definition_index);
if (weapon_definition->weapon.interface_definition.first_person_animations.index!=NONE)
{
short state= (short)first_person_weapon_state_from_weapon_message(message_type);
if (state!=NONE)
{
short animation_type= (short)first_person_animation_type_from_weapon_state(state);
if (animation_type!=NONE)
{
struct animation_graph *animation_graph= animation_graph_definition_get(
weapon_definition->weapon.interface_definition.first_person_animations.index);
struct animation_graph_first_person_weapon_animations *first_person_weapon_animations=
!animation_graph->first_person_weapon_animations.count ?
NULL :
TAG_BLOCK_GET_ELEMENT(
&animation_graph->first_person_weapon_animations,
0,
struct animation_graph_first_person_weapon_animations);
/* port: and the graph has first-person animations (a map's
graph without them was a NULL read) */
short animation_index= (first_person_weapon_animations &&
animation_type>=0 &&
animation_type<first_person_weapon_animations->animations.count) ?
animation_graph_animation_index_get(
&first_person_weapon_animations->animations)
[animation_type].animation_index :
(short)NONE;
if (animation_index!=NONE)
{
short sound_index= TAG_BLOCK_GET_ELEMENT(
&animation_graph->animations,
animation_index,
struct animation)->sound_index;
/* port: a sound the graph has (a map's index) */
if (VALID_INDEX(sound_index, animation_graph->sound_references.count))
{
long definition_index= TAG_BLOCK_GET_ELEMENT(
&animation_graph->sound_references,
sound_index,
struct animation_graph_sound_reference)->sound.index;
if (definition_index!=NONE)
{
object_impulse_sound_new(
weapon_index,
definition_index,
NONE,
global_origin3d,
global_forward3d,
1.0f);
}
}
}
}
}
}
}
return;
}
static long first_person_weapon_state_from_weapon_message(
short message_type)
{
switch (message_type)
{
case _first_person_weapon_message_primary_fire:
return _first_person_weapon_state_primary_fire;
case _first_person_weapon_message_secondary_fire:
return _first_person_weapon_state_secondary_fire;
case _first_person_weapon_message_primary_misfire:
return _first_person_weapon_state_primary_misfire;
case _first_person_weapon_message_secondary_misfire:
return _first_person_weapon_state_secondary_misfire;
case _first_person_weapon_message_melee:
return _first_person_weapon_state_melee;
case _first_person_weapon_message_light_on:
return _first_person_weapon_state_light_on;
case _first_person_weapon_message_light_off:
return _first_person_weapon_state_light_off;
case _first_person_weapon_message_reload_while_empty:
return _first_person_weapon_state_reload_while_empty;
case _first_person_weapon_message_reload_while_full:
return _first_person_weapon_state_reload_while_full;
case _first_person_weapon_message_put_away:
return _first_person_weapon_state_put_away;
case _first_person_weapon_message_ready:
return _first_person_weapon_state_ready;
case _first_person_weapon_message_charged:
return _first_person_weapon_state_charged;
case _first_person_weapon_message_overheating:
return _first_person_weapon_state_overheating;
case _first_person_weapon_message_throw_grenade:
return _first_person_weapon_state_throw_grenade;
case _first_person_weapon_message_overheating_super_recoil:
return _first_person_weapon_state_overheating_super_recoil;
}
return NONE;
}
static long first_person_animation_type_from_weapon_state(
short state)
{
switch (state)
{
case _first_person_weapon_state_idle:
return _first_person_weapon_animation_idle;
case _first_person_weapon_state_overheated:
return _first_person_weapon_animation_overheated;
case _first_person_weapon_state_charged:
return _first_person_weapon_animation_overcharged;
case _first_person_weapon_state_posing:
return _first_person_weapon_animation_posing;
case _first_person_weapon_state_primary_fire:
return _first_person_weapon_animation_primary_fire;
case _first_person_weapon_state_secondary_fire:
return _first_person_weapon_animation_secondary_fire;
case _first_person_weapon_state_primary_misfire:
return _first_person_weapon_animation_primary_misfire;
case _first_person_weapon_state_secondary_misfire:
return _first_person_weapon_animation_secondary_misfire;
case _first_person_weapon_state_melee:
return _first_person_weapon_animation_melee;
case _first_person_weapon_state_light_on:
return _first_person_weapon_animation_light_on;
case _first_person_weapon_state_light_off:
return _first_person_weapon_animation_light_off;
case _first_person_weapon_state_reload_while_empty:
return _first_person_weapon_animation_reload_while_empty;
case _first_person_weapon_state_reload_while_full:
return _first_person_weapon_animation_reload_while_full;
case _first_person_weapon_state_put_away:
return _first_person_weapon_animation_put_away;
case _first_person_weapon_state_ready:
return _first_person_weapon_animation_ready;
case _first_person_weapon_state_throw_grenade:
return _first_person_weapon_animation_throw_grenade;
case _first_person_weapon_state_throw_grenade_overheated:
return _first_person_weapon_animation_throw_grenade_overheated;
case _first_person_weapon_state_overheating:
return _first_person_weapon_animation_overheating;
case _first_person_weapon_state_overheating_again:
return _first_person_weapon_animation_overheating_again;
case _first_person_weapon_state_shotgun_enter_reload:
return _first_person_weapon_animation_shotgun_enter;
case _first_person_weapon_state_shotgun_exit_reload_empty:
return _first_person_weapon_animation_shotgun_exit_empty;
case _first_person_weapon_state_shotgun_exit_reload_full:
return _first_person_weapon_animation_shotgun_exit_full;
case _first_person_weapon_state_overheated_exit:
return _first_person_weapon_animation_overheated_exit;
case _first_person_weapon_state_overheating_super_recoil:
return _first_person_weapon_animation_overheated_supercharge_enter;
}
return NONE;
}
static void model_remap_node_matrices_to_match_animation_graph(
long model_index,
struct real_matrix4x3 *model_node_matrices,
long animation_graph_index,
struct real_matrix4x3 const *node_matrices,
short const *node_remapping_table)
{
struct model *model= model_definition_get(model_index);
struct animation_graph *animation_graph= animation_graph_definition_get(animation_graph_index);
short node_index;
/* port: no more nodes than the table and model_node_matrices hold (a
map's count) */
for (node_index=0; node_index<MIN(model->nodes.count, MAXIMUM_NODES_PER_ANIMATION); node_index++)
{
short animation_graph_node_index= node_remapping_table[node_index];
match_assert(
"c:\\halo\\SOURCE\\interface\\first_person_weapons.c",
1587,
animation_graph_node_index>=0 && animation_graph_node_index<animation_graph->nodes.count);
model_node_matrices[node_index]= node_matrices[animation_graph_node_index];
}
return;
}
static struct first_person_weapon *first_person_weapon_get(
short local_player_index)
{
match_assert(
"c:\\halo\\SOURCE\\interface\\first_person_weapons.c",
1433,
local_player_index>=0 && local_player_index<MAXIMUM_NUMBER_OF_LOCAL_PLAYERS);
return &first_person_weapons[local_player_index];
}
static short first_person_weapon_index_from_weapon_index(
long weapon_index)
{
short local_player_index;
for (local_player_index=0; local_player_index<MAXIMUM_NUMBER_OF_LOCAL_PLAYERS; local_player_index++)
{
long player_index= local_player_get_player_index(local_player_index);
if (player_index!=NONE)
{
long unit_index= player_get(player_index)->unit_index;
if (unit_index!=NONE)
{
struct unit_datum *unit= unit_get(unit_index);
if (unit->unit.current_weapon_index!=NONE &&
weapon_index==unit->unit.weapon_object_indices[unit->unit.current_weapon_index])
{
return local_player_index;
}
}
}
}
return NONE;
}
static short first_person_weapon_index_from_unit_index(
long unit_index)
{
short local_player_index;
for (local_player_index=0; local_player_index<MAXIMUM_NUMBER_OF_LOCAL_PLAYERS; local_player_index++)
{
long player_index= local_player_get_player_index(local_player_index);
if (player_index!=NONE && player_get(player_index)->unit_index==unit_index)
{
return local_player_index;
}
}
return NONE;
}