From fdb6a8997989038017e09e4e827534e628ea2db5 Mon Sep 17 00:00:00 2001 From: Jonas Volman Date: Wed, 26 Aug 2026 12:27:59 -0700 Subject: [PATCH] Match Units shared-interface package Authorized three-correction interface package: private unit_ready_desired_weapon, long animation-impulse formals with the natural tester scratch, boolean game-engine damaged-player damage type. Six January owners strict exact on the sole 33-TU emission; Units 174/189 code and 128/129 data; zero inherited losses across the closure. Co-Authored-By: Claude Fable 5 --- ...jonas_shared_interface_package_20260826.md | 249 ++++ source/game/game_engine.c | 2 +- source/game/game_engine.h | 4 +- source/units/units.c | 1043 ++++++++++++++++- source/units/units.h | 10 +- 5 files changed, 1290 insertions(+), 18 deletions(-) create mode 100644 docs/object_matching_logs/units_obj_jonas_shared_interface_package_20260826.md diff --git a/docs/object_matching_logs/units_obj_jonas_shared_interface_package_20260826.md b/docs/object_matching_logs/units_obj_jonas_shared_interface_package_20260826.md new file mode 100644 index 00000000..446bf0aa --- /dev/null +++ b/docs/object_matching_logs/units_obj_jonas_shared_interface_package_20260826.md @@ -0,0 +1,249 @@ +# Units shared-interface package (Jonas, 2026-08-26) + +## Result and authorized boundary + +The operator authorized the pending Units shared-header package with the +required phrase, conditioned on byte-matching standards; the fail-closed +one-shot protocol below is that condition. This wave starts from clean +cumulative commit `278d55f9e57f8ccac6d5ced80c22fa29db688f79` on isolated +branch `jonas/units-shared-interface-20260826` and applies the three +interface corrections frozen in the read-only packet +(`i-w\outputs\UNITS_SHARED_INTERFACE_PACKAGE_FROZEN_20260826.md`): + +1. remove the stale public `unit_ready_desired_weapon` declaration and keep + the helper translation-unit-private; +2. change the animation-impulse formal from `short` to `long` consistently + and use the natural four-byte typed tester scratch; and +3. change the game-engine damaged-player damage-type formal from `long` to + `boolean` across the callback member, wrapper declaration, and wrapper + definition. + +Exactly one production emission of the 33-TU affected closure adjudicated the +package. **All six promised owners are strict exact on that sole shot:** + +| January owner | Padded | Relocs | Normalized SHA-256 | +| --- | ---: | ---: | --- | +| `_unit_ready_desired_weapon` | 432 | 28 | `ef9948642c40b1c7ed12419d3594176c8a95ce0a965641607b4656020d5d13c4` | +| `_code_001a2030` | 64 | 3 | `6090b208fa2eeac097ab40901051c49f89951bee54a622363d14c51bfd8765af` | +| `_unit_exit_seat_end` | 656 | 23 | `229c70eae582daa2590313251b16ed2e020a38c832643a153aee22dd5de25a24` | +| `_unit_start_animation_impulse` | 288 | 11 | `db9ae277a81fe8df84632bfeb74678459480eff07df3a181eb0c65a80b5f1416` | +| `_unit_test_animation_impulse` | 192 | 7 | `186cc732892efe8197f5539ad09d64ad7fdb3b264ab6ef8a40c41bcf4e41d1dd` | +| `_unit_damage_aftermath` | 1600 | 60 | `8bb721e70465d8c4c3d09069f879176ddc08a639d1e67c75fe525f9d367870dd` | + +The direct hardened Units census moves from 168/12/9 to **174 exact / 11 +emitted nonexact / 4 absent** code owners and from 126/0/3 to **128 exact / +0 emitted nonexact / 1 absent** runtime-data owners. The two data gains are +the aftermath console-format string +`??_C@_0CE@MIPDNKIJ@p?$CFd?3?5body?5?$CF?42f?5shld?5?$CF?42f?5from?5?$CFs@` +and `__real@4001475cd2000000`; the sole remaining absent data owner is +`__real@3d0f5c29`, owned by still-absent `_unit_cause_player_melee_damage`. +`units.obj` remains `NonMatching`; no completion label is claimed. + +`_code_001a0cf0` is emitted as real nonexact support required by +`_unit_damage_aftermath` and receives no credit: 1,568 padded bytes, 43 +relocations (all identities and addends equal to January, including +`_base_seat_labels+8` into the strict `.data` aggregate), candidate +normalized SHA-256 +`ad4d59f2ea92489e...` versus January +`365511af9b5f31d519ee339975e9f667004f04caa4d8c868266e41c17278fc63`. + +## Immutable authority, worktree, and compiler + +- January `cachebeta.exe`: 6,584,672 bytes, SHA-256 + `4cc87b45f721270392a96f1674ed2b5cd4a7bb4355faeab4531d1cf1884d9520`. +- January split `source/units/units.obj`: 138,090 bytes, SHA-256 + `e6a24ca597147dacff1f06806613d6bcfa4fa48f359dfbf542c34935f674b19f` + (regenerated in this worktree by csplit and re-hashed). +- Baseline commit `278d55f9`; baseline blobs + `units.c c2ef1a3b08bec91cc2c534443f09732b6f3003cd`, + `units.h 54492a4ee4cb8b37739f9ebbfa89c72fbd0e08fc`, + `game_engine.c 47515f3ecf28ab5a8aeabdd196b1846b75b66501`, + `game_engine.h e8d54eec1c666e866884542a07350b28b42b04b4`. +- Retained staged blobs: + `units.c a62d26fd2aba7e8018f5786c1f449bc4937b612e`, + `units.h 0184ad19e7f7b9966ecb1eb2756b889c4ad0b0bd`, + `game_engine.c f63f8a84182b64045fc3926afeed8f37d700b249`, + `game_engine.h 8d0526700a54f70e5f9331db86c6260651eb9da8`. +- Compiler: XDK 3911 VC7 `CL.Exe` 13.00.9254.1, 81,920 bytes, SHA-256 + `483e00c47bb08d699475a642bcff15b5b2036350b31c540e88a506baf101da11`; + flags `/nologo /c /O2 /Oy- /DDEBUG /Dxbox` and the unchanged include + graph. The generated `build.ninja` CL command line was respelled to this + worktree's absolute compiler path because the relative forward-slash form + fails CreateProcess in this invocation context; same binary, same flags, + no configuration change. +- Pre-edit worktree-local `units.obj` (full clean baseline build): + SHA-256 `6ded5e7da2c0cb07926340e4045dcea8af017805f33849e38736f44d38956a22`; + post-emission 150,373 bytes, raw SHA-256 + `65258dabe3b2043f66b7018aa24bb9f051ac08e7347a04cb8cf16d395380d432`. + Pre-edit `game_engine.obj` SHA-256 + `4d0b09446b43c182852b03837ccd3a520fb834e9aa62c8d271dcfb4b31b59004`; + post-emission 97,824 bytes, raw SHA-256 + `4806536057caa6bc959080bfd3735bbc6a799e98b7bb01c843d4512eb5909557`. + Raw whole-object hashes are timestamp/debug-phase specific; admission is + padded-section plus complete relocation identity, never a whole-object + hash. + +The full baseline was built natively in this worktree before editing (569 +base objects + 833 split objects) and reproduced the frozen packet baseline +exactly: Units 168/12/9 and 126/0/3, the 33-object closure table +identical row-for-row (867 inherited exact code owners), and the split hash +above. + +## Source provenance and applied form + +The readable carrier is Claude blob +`124f29e32497c091c4163b5faafc58dbf8890161` at `deb5554a`. The four new +bodies (`unit_test_animation_impulse`, `code_001a0cf0`, +`unit_exit_seat_end`, `unit_damage_aftermath`) were transplanted verbatim +from carrier lines 3872-3920, 1563-1998, 5469-5588, and 7902-8242, with +exactly two documented policy transforms: + +- the tester's four-byte codegen union (prohibited) became the natural + `short interpolation_frame_count;` under the coherent `long` formal — + January's own frame (one dword local at `[ebp-4]`, full-dword impulse + load `mov edi,[ebp+0xc]`) is the level-1 evidence; the union defended + against short-formal argument-padding recycling that cannot occur with the + long formal. This previously-unmeasured spelling produced the exact + January tester on the sole emission; and +- `code_001a0cf0`'s call through the carrier's raw `+0xb6` debug macro + became the accepted named-field spelling + `TEST_FLAG(unit->object.functions_active_flags, _unit_debug_function_active_bit)` + already emitted by the update-animation closure. + +`code_001a2030` was already present in the baseline source as the dead +invented-name static `unit_adjust_for_seat_change` (community commit +`bc6663de`), unemitted because its only January caller was absent. Per house +rules §4 the wave removed that name (prototype and definition) and applied +the carrier body under the split's placeholder name at the carrier-relative +position before `unit_enter_seat`. + +TU-local support added with the bodies, all carrier-authenticated and +storage-free: declarations for `ai_handle_death`, `unit_make_damage_sound` +(defined in January `unit_dialogue.obj`; kept TU-local to hold the public +interface delta at the three scoped corrections), and `biped_exit_seat_end`; +the damage-animation/-direction/-part enums; the aftermath-flag enum +(`lethal=0`, `player_damage_type=4`, `died_instantly=6`); the two +direction-cone `#define`s; the aftermath animation-flags mask; and TU-local +stand-ins for `_damage_data_suppress_unit_reaction_bit = 4` and +`_damage_definition_pings_resistant_units_bit = 2` / +`_does_not_ping_units_bit = 4`, whose owning shared headers +(`objects/damage.h`, `objects/damage_effect_definitions.h`) are outside this +authorization. `units.h` lost the stale ready declaration and its impulse +declarations became `long` in one-parameter-per-line form; `game_engine.h` +and `game_engine.c` changed only the three damage-type formals. + +The retained diff (4 files: +1,041/-18 lines) contains no assembly, +`volatile`, barrier, pragma, intrinsic, attribute, `register`, raw offset, +pointer or union pun, inactive-union access, undefined behavior, synthetic +anchor, byte patch, or compiler-option change. `git diff --check` passes. + +## One-shot discipline + +The complete candidate was applied by a count-asserted script (16 edits; any +count mismatch aborts). `/Zs` parse-only passed for both edited TUs on the +first attempt with no declaration residue. The affected-TU closure was +recomputed from the generated graph at the patched state: exactly the +frozen 33 TUs (28 reach `units/units.h`, 10 reach `game/game_engine.h`). +Because the Ninja graph tracks no header dependencies, the 33 affected +objects were deleted with per-path containment proof (each resolved absolute +path begins with this worktree root), absence was verified, and a dry run +then listed **exactly 33 CL edges and nothing else**. One ordinary `ninja` +invocation compiled all 33. There was no retry, tuning, or post-emission +source change. The only compile diagnostic was the known inherited +`ai_debug.c(293)` C4047 warning in an untouched protected file; `units.c` +and `game_engine.c` emitted no warning. + +## Whole-closure preservation + +- Direct hardened comparison of all 33 affected objects against their + January split targets reproduces the frozen baseline table exactly, with + `source/units/units` the only changed row (174/11/4 code, 128/0/1 data). + Total inherited exact code owners across the closure: 867, all preserved; + campaign-wide exact totals rose only by the six Units gains. +- Named sentinels: `_game_engine_player_damaged_player` remains strict exact + at 80 bytes / 5 relocations / `75c31dac76987b65...` after the boolean + change; `_unit_add_weapon_to_inventory` remains strict exact + (304/11/`de883e04...`); fully exact `objects.obj` remains 121/121 + + 99/99; protected `ai_debug.obj` remains 11/3/46 + 268/1/69; the strict + `.data` aggregate (1,564 B / 7 relocs / `6ed61c39...`) and `_unit_globals` + BSS (4 B / `df3f6198...`) are unchanged. +- The 33-unit pre-edit regression manifest + (`build/audit/units_shared_interface_pre_20260826.json`, 38,522,813 + bytes, SHA-256 + `cbfd5d0fd6845ca802e34b3bdb65362844a2a29d791b6a11b1979bfb74339e86`, + pinned to `278d55f9`) fails closed as designed on the intended package: + its six warnings are exactly the six `NEWLY_EXACT` owners, and its 238 + strict findings were adjudicated one class at a time at the runtime + evidence level (padded size, normalized hash, relocation + address/type/addend/destination): + - **every runtime-evidence delta in the entire closure lives in + `source/units/units`**, and the changed set is exactly the intended one: + five appeared owners (`_unit_test_animation_impulse`, + `_unit_exit_seat_end`, `_unit_damage_aftermath`, `_code_001a2030`, + `_code_001a0cf0`), two owners that became exact + (`_unit_ready_desired_weapon`, `_unit_start_animation_impulse`), and two + support owners whose call sites re-formed around the now-private helper + (`_unit_enter_seat`: 592/34, now `a0bfb14f...`; `_unit_update`: + 5,984/262, now `6117f737...`; both were and remain nonexact). + `_code_00198fd0` kept its previous bytes (`197a8fec...`) despite the + new tester caller; `_unit_died`, `_unit_update_animation`, + `_unit_animation_set_state`, `_unit_euler_aiming_update`, + `_unit_preprocess_node_orientations`, and + `_unit_throw_grenade_release` are byte-identical to the baseline; + - all remaining manifest findings are `$L` compiler-local label + renumbering (the header edits shift VC7's internal label counters in + every including TU; every added/removed symbol in every affected object + is a `$L` label, zero real symbols changed), defined-versus-undefined + callee metadata for the newly defined owners, and physical + section/symbol renumbering from natural source-position insertion in + `units.c` — the same fail-closed structural classes documented by every + prior wave. The manifest's four `objects.obj` "accepted" identities + that vanished are `$L` labels, not functions; `objects.obj`'s real + census is unchanged at 121/121; + - the candidate's new select-any literal COMDATs authenticate + byte-exact against their January natural owners + (`__real@3fe921fb60000000` → `ai_debug.obj`, 8 B; + `__real@40000000` → `action_vehicle.obj`, 4 B) and receive no data + credit. No writable `.data`, `.bss`, or COMMON owner changed anywhere. + +## Full gates + +- `halobetacache_build` and `libcmt_build`: no work remaining after the + 33-edge emission; report, semantic-report, and progress regeneration pass. +- Semantic audit: 470 units, 4,271 functions evaluated, 4,130 semantic + exact, 114 hidden exact / 64,705 hidden code bytes, 4,191 accepted exact, + zero unit errors. The authoritative baseline at the same commit measures + 4,125 semantic / 114 hidden / 4,186 accepted, so the delta is exactly the + five external gains; `missing_base_symbol` falls by exactly the four newly + emitted externals (3,462 → 3,458). Static `_code_001a2030` is strict + exact by direct comparison but, like prior private helpers, receives no + ordinary-report or semantic-ledger credit in this source-only wave. +- Campaign progress: 375/833 objects, 4,165/11,060 functions, + 505,876/2,198,102 code bytes, 1,835,212/4,176,062 data bytes; + halobetacache 273/468 objects, 3,998/7,574 functions, 492,962 code bytes; + libcmt unchanged at 102/212, 167/476, 12,914 bytes. +- Object admission audit: zero candidates, zero revocations; only the + inherited unrelated `source/shell/shell_xbox` completion-label + contradiction remains. +- Parked-function audit: three active, zero stale, zero invalid. +- Complete tooling suite: **179/179 tests pass** (`pytest tools/`). +- Source scope: exactly `source/units/units.c`, `source/units/units.h`, + `source/game/game_engine.c`, `source/game/game_engine.h`, plus this new + ledger. The other protected sentinels (`vehicles.c`, `ai_debug.c`, + `ai_script.c`, `actions.c`) are untouched. + +## Commit/replay boundary + +This initial ledger records the frozen one-shot evidence. After the +implementation commit is clean, a fresh 33-unit accepted-state snapshot will +pin that commit; the 33 generated objects will be containment-proven, +deleted, rebuilt by one ordinary Ninja invocation, and re-checked +immediately (expecting zero failures against the committed snapshot); the +six-owner comparison, complete 189/129 censuses, and closure table will be +repeated; and a final dry run must report no work. The same sequence then +runs at corrected HEAD after the ledger-only replay commit. No push, amend, +rebase, history rewrite, or worktree removal is performed. + +## Actual committed-state forced replay + +(appended after the implementation commit) diff --git a/source/game/game_engine.c b/source/game/game_engine.c index 47515f3e..f63f8a84 100644 --- a/source/game/game_engine.c +++ b/source/game/game_engine.c @@ -4403,7 +4403,7 @@ void game_engine_end_game( void game_engine_player_damaged_player( long damaging_player_index, long dead_player_index, - long damage_type) + boolean damage_type) { match_assert("c:\\halo\\SOURCE\\game\\game_engine.c", 0xA20, dead_player_index != NONE); diff --git a/source/game/game_engine.h b/source/game/game_engine.h index e8d54eec..8d052670 100644 --- a/source/game/game_engine.h +++ b/source/game/game_engine.h @@ -132,7 +132,7 @@ struct game_engine void (*player_damaged_player)( long damaging_player_index, long dead_player_index, - long damage_type); + boolean damage_type); void (*unknown60)(void); boolean (*format_message)( long player_index, @@ -427,7 +427,7 @@ void ticks_to_unicode_time_string( void game_engine_player_damaged_player( long damaging_player_index, long dead_player_index, - long damage_type); + boolean damage_type); boolean game_engine_player_is_out_of_lives( long player_index); diff --git a/source/units/units.c b/source/units/units.c index c2ef1a3b..a62d26fd 100644 --- a/source/units/units.c +++ b/source/units/units.c @@ -741,6 +741,55 @@ enum _unit_debug_function_active_bit = 2, }; +enum +{ + _unit_damage_animation_soft_ping = 0, + _unit_damage_animation_hard_ping, + _unit_damage_animation_soft_kill, + _unit_damage_animation_hard_kill, +}; + +enum +{ + _unit_damage_direction_front = 0, + _unit_damage_direction_left, + _unit_damage_direction_right, + _unit_damage_direction_back, +}; + +enum +{ + _unit_damage_part_head = 2, +}; + +/* January computes these direction-cone limits from single-precision pi, then + promotes the rounded results for the double-precision fabs comparisons. */ +#define UNIT_DAMAGE_REAR_CONE_ANGLE 0.7853981852531433 +#define UNIT_DAMAGE_FRONT_CONE_ANGLE 2.159845009446144 + +enum +{ + _unit_damage_aftermath_lethal_bit = 0, + _unit_damage_aftermath_player_damage_type_bit = 4, + _unit_damage_aftermath_died_instantly_bit = 6, +}; + +#define UNIT_DAMAGE_AFTERMATH_ANIMATION_FLAGS_MASK \ + (FLAG(1) | FLAG(3) | FLAG(7)) + +/* January-authenticated damage flag values; the owning shared headers are + outside this wave, so the used values stay translation-unit local. */ +enum +{ + _damage_data_suppress_unit_reaction_bit = 4, +}; + +enum +{ + _damage_definition_pings_resistant_units_bit = 2, + _damage_definition_does_not_ping_units_bit = 4, +}; + /* ---------- macros */ #define unit_get_current_weapon_index(unit_index) unit_inventory_get_weapon((unit_index), unit_get((unit_index))->unit.current_weapon_index) @@ -962,8 +1011,10 @@ void weapon_stop_reload( boolean weapon_prevents_grenade_throwing( long weapon_index); -static void unit_adjust_for_seat_change(long unit_index); static short unit_weapon_next_index(long unit_index, short current_index, short delta); +static void unit_ready_desired_weapon( + long unit_index, + boolean immediate); static void unit_throw_grenade_move_to_hand(long unit_index); @@ -1029,6 +1080,20 @@ void unit_detach_from_parent( long unit_index); void unit_exit_seat_end( long unit_index); +void ai_handle_death( + long unit_index, + long killer_object_index, + short damage_category); +boolean unit_make_damage_sound( + long unit_index, + struct damage_data *damage_data, + boolean died, + boolean died_instantly, + real body_damage, + real shield_damage); +void biped_exit_seat_end( + long biped_index, + long parent_unit_index); short actors_spawn_from_unit( long unit_index, long actor_variant_definition_index, @@ -2947,6 +3012,20 @@ boolean unit_can_enter_seat( return result; } +static void code_001a2030( + long unit_index) +{ + struct unit_datum *unit = unit_get(unit_index); + + unit->unit.desired_weapon_index = unit_weapon_next_index( + unit_index, + unit->unit.current_weapon_index, + 0); + unit_ready_desired_weapon(unit_index, TRUE); + + return; +} + boolean unit_enter_seat( long unit_index, long target_unit_index, @@ -3072,6 +3151,127 @@ boolean unit_enter_seat( return result; } +void unit_exit_seat_end( + long unit_index) +{ + struct unit_datum *unit = unit_get(unit_index); + long parent_unit_index = unit->object.parent_object_index; + + if (parent_unit_index!=NONE && unit->unit.parent_seat_index!=NONE) + { + struct unit_datum *parent_unit = unit_get(parent_unit_index); + struct unit_definition *parent_unit_definition = + unit_definition_get(parent_unit->definition_index); + struct unit_seat *seat = TAG_BLOCK_GET_ELEMENT( + &parent_unit_definition->unit.seats, + unit->unit.parent_seat_index, + struct unit_seat); + real_matrix4x3 *unit_node_matrix = object_get_node_matrix(unit_index, 0); + struct object_marker seat_marker; + real_vector3d relative_position; + real_vector3d exit_offset; + struct unit_definition *unit_definition; + struct model *model; + struct model_node *root_node; + real_point3d root_translation; + real_matrix4x3 const *root_inverse_matrix; + real_matrix4x3 const *updated_unit_node_matrix; + real_point3d position; + real_matrix4x3 matrix; + struct real_orientation *node_orientations; + + object_get_marker_by_name( + parent_unit_index, + seat->marker_name, + &seat_marker, + 1); + vector_from_points3d( + &seat_marker.matrix.position, + &unit_node_matrix->position, + &relative_position); + matrix4x3_inverse_transform_vector( + &seat_marker.matrix, + &relative_position, + &exit_offset); + + unit_definition = unit_definition_get(unit->definition_index); + model = model_definition_get(unit_definition->object.model.index); + root_node = TAG_BLOCK_GET_ELEMENT(&model->nodes, 0, struct model_node); + root_inverse_matrix = &root_node->runtime_default_inverse_matrix; + root_translation = root_node->default_translation; + + if (parent_unit->unit.driver_object_index==unit_index && + parent_unit->unit.animation.state!=_unit_state_opening && + unit->object.parent_object_index!=NONE) + { + unit_animation_set_state( + unit->object.parent_object_index, + _unit_state_opening); + } + + unit->unit.last_vehicle_index = parent_unit_index; + unit->unit.game_time_at_last_vehicle_exit = game_time_get(); + if (unit->unit.driver_object_index==unit_index) + { + unit->unit.driver_object_index = NONE; + } + if (unit->unit.gunner_object_index==unit_index) + { + unit->unit.gunner_object_index = NONE; + } + + object_detach(unit_index); + position.x = relative_position.i + unit->object.position.x; + position.y = relative_position.j + unit->object.position.y; + position.z = relative_position.k + unit->object.position.z - + root_translation.z; + object_set_position(unit_index, &position, NULL, NULL); + + updated_unit_node_matrix = object_get_node_matrix(unit_index, 0); + matrix4x3_multiply( + updated_unit_node_matrix, + root_inverse_matrix, + &matrix); + unit->object.forward = matrix.forward; + unit->object.up = matrix.up; + object_set_visibility(unit_index, TRUE); + unit->unit.parent_seat_index = NONE; + unit->unit.animation.base_seat_index = _unit_base_seat_stand; + + if (parent_unit->unit.driver_object_index==unit_index) + { + parent_unit->unit.driver_object_index = NONE; + } + if (parent_unit->unit.gunner_object_index==unit_index) + { + parent_unit->unit.gunner_object_index = NONE; + } + + code_0019a170(parent_unit_index); + code_001a2030(unit_index); + + { + struct unit_animation_update_data animation_update_data; + + animation_update_data.state_desired = _unit_state_airborne; + animation_update_data.crouching = FALSE; + unit_update_animation(unit_index, &animation_update_data); + } + node_orientations = object_header_block_get( + unit_index, + &unit->object.original_node_orientations); + node_orientations->translation = root_translation; + + if (unit->object.type==_object_type_biped) + { + biped_exit_seat_end(unit_index, parent_unit_index); + } + object_compute_node_matrices_recursive(unit_index); + } + + return; +} + void unit_impulse( long unit_index, long impulse_index, @@ -5846,6 +6046,787 @@ void unit_record_damage( return; } +void code_001a0cf0( + long unit_index, + boolean killed, + boolean feign_death, + boolean suppress_random_death_frame, + boolean suppress_hard_ping, + boolean force_hard_ping, + real damage_direction_angle, + short damage_part, + real_vector2d const *alignment_vector) +{ + struct unit_datum *unit; + struct unit_definition *unit_definition; + struct animation_graph *animation_graph; + boolean ping; + boolean hard_ping; + short damage_direction; + short damage_animation_index; + short animation_index; + short damage_animation_type; + short animation_state; + boolean apply_animation; + struct biped_datum *biped; + struct biped_definition *biped_definition; + struct animation *animation; + short quarter_frame_count; + short random_frame; + long front_animation_index; + word animation_flags; + + unit = unit_get(unit_index); + unit_definition = unit_definition_get(unit->definition_index); + + if (killed) + { + feign_death = FALSE; + ping = TRUE; + + if (unit_definition->unit.hard_death_threshold>0.f && + unit->object.current_body_damage> + unit_definition->unit.hard_death_threshold) + { + hard_ping = TRUE; + } + else + { + hard_ping = FALSE; + } + } + else if (feign_death) + { + killed = TRUE; + ping = TRUE; + hard_ping = FALSE; + } + else + { + ping = + unit->object.current_body_damage> + unit_definition->unit.soft_ping_threshold || + unit->object.current_shield_damage> + unit_definition->unit.soft_ping_threshold; + hard_ping = + unit->object.current_body_damage> + unit_definition->unit.hard_ping_threshold; + + if (suppress_hard_ping || + TEST_FLAG(unit->unit.flags, _unit_ignore_hard_pings_bit)) + { + hard_ping = FALSE; + } + } + + if (force_hard_ping) + { + hard_ping = TRUE; + ping = TRUE; + } + + if (damage_part==NONE) + { + damage_part = 0; + } + + if (fabs(damage_direction_angle)UNIT_DAMAGE_FRONT_CONE_ANGLE) + { + damage_direction = _unit_damage_direction_front; + } + else if (damage_direction_angle>0.f) + { + damage_direction = _unit_damage_direction_left; + } + else + { + damage_direction = _unit_damage_direction_right; + } + + if (game_engine_running() && + damage_part==_unit_damage_part_head && + hard_ping && + killed) + { + damage_direction = _unit_damage_direction_left; + } + + if (!ping && !killed) + { + return; + } + + { + unit_definition = unit_definition_get(unit->definition_index); + animation_graph = animation_graph_definition_get( + unit_definition->object.animation_graph.index); + + if (!hard_ping && !killed) + { + if (unit->unit.animation.soft_ping_animation.index!=NONE && + unit->unit.animation.soft_ping_animation.frame_index<= + unit_definition->unit.runtime_soft_ping_minimum_interrupt_ticks) + { + return; + } + + damage_animation_index = build_damage_animation_index( + _unit_damage_animation_soft_ping, + damage_direction, + damage_part); + + { + long animation_graph_index; + long selected_damage_animation_index; + + if (damage_animation_index>=0 && + damage_animation_index< + animation_graph->unit_damage_animations.count) + { + selected_damage_animation_index = + animation_graph_animation_index_get( + &animation_graph->unit_damage_animations) + [damage_animation_index].animation_index; + } + else + { + selected_damage_animation_index = NONE; + } + animation_graph_index = + unit_definition->object.animation_graph.index; + + animation_index = animation_choose_random_permutation_internal( + TRUE, + animation_graph_index, + selected_damage_animation_index); + } + + if (animation_index==NONE) + { + return; + } + + unit->unit.animation.soft_ping_animation.index = animation_index; + unit->unit.animation.soft_ping_animation.frame_index = 0; + + return; + } + else + { + animation_state = killed ? _unit_state_dying : _unit_state_hard_ping; + + if (killed) + { + damage_animation_type = + hard_ping ? + _unit_damage_animation_hard_kill : + _unit_damage_animation_soft_kill; + } + else + { + damage_animation_type = _unit_damage_animation_hard_ping; + } + + apply_animation = + killed || + code_00197f90( + &unit->unit.animation, + animation_state); + + if (unit->unit.animation.state==_unit_state_hard_ping && + unit->object.animation.state.frame_index> + unit_definition->unit.runtime_hard_ping_minimum_interrupt_ticks) + { + apply_animation = TRUE; + } + + if (!killed) + { + if (TEST_FLAG( + unit->object.functions_active_flags, + _unit_debug_function_active_bit)) + { + apply_animation = FALSE; + } + + if (unit->object.parent_object_index!=NONE) + { + return; + } + } + + if (!apply_animation) + { + return; + } + + if (killed) + { + char const *seat_label = + base_seat_labels[_unit_base_seat_stand]; + + unit_set_or_test_seat_and_weapon_label( + unit_index, + seat_label, + code_0019dff0(unit_index), + TRUE); + } + + if (animation_state==_unit_state_dying && + unit->object.type==_object_type_biped) + { + biped = biped_get(unit_index); + biped_definition = biped_definition_get(biped->definition_index); + + if (TEST_FLAG(biped->biped.flags, _biped_limping_bit) && + !TEST_FLAG( + biped_definition->biped.flags, + _biped_has_no_dying_airborne_bit)) + { + animation_state = _unit_state_dying_airborne; + + if (unit_animation_set_state( + unit_index, + _unit_state_dying_airborne)) + { + goto apply_alignment; + } + } + } + + damage_animation_index = build_damage_animation_index( + damage_animation_type, + damage_direction, + damage_part); + + { + long animation_graph_index; + long selected_damage_animation_index; + + if (damage_animation_index>=0 && + damage_animation_index< + animation_graph->unit_damage_animations.count) + { + selected_damage_animation_index = + animation_graph_animation_index_get( + &animation_graph->unit_damage_animations) + [damage_animation_index].animation_index; + } + else + { + selected_damage_animation_index = NONE; + } + animation_graph_index = + unit_definition->object.animation_graph.index; + + animation_index = animation_choose_random_permutation_internal( + TRUE, + animation_graph_index, + selected_damage_animation_index); + } + + if (animation_index!=NONE) + { + if (unit->unit.animation.state==_unit_state_throw_grenade) + { + unit_throw_grenade_release(unit_index, TRUE); + } + + object_start_interpolation(unit_index, 3); + unit->unit.animation.state = (char)animation_state; + code_0019b0b0( + unit_index, + unit_definition->object.animation_graph.index, + animation_index); + SET_FLAG( + unit->unit.animation.flags, + _unit_animation_postpone_weapon_ik_until_interpolation_ends_bit, + TRUE); + + if (killed) + { + if (!suppress_random_death_frame && !feign_death) + { + animation = TAG_BLOCK_GET_ELEMENT( + &animation_graph->animations, + animation_index, + struct animation); + quarter_frame_count = animation->frame_count>>2; + random_frame = random_range( + quarter_frame_count, + (animation->frame_count>>1) + quarter_frame_count); + unit->unit.weapon_drop_delay_ticks = (char)random_frame; + unit->unit.weapon_drop_delay_ticks = + MAX((char)random_frame, 1); + } + else + { + unit->unit.weapon_drop_delay_ticks = 0; + } + } + + if (damage_direction!=_unit_damage_direction_front) + { + animation = TAG_BLOCK_GET_ELEMENT( + &animation_graph->animations, + animation_index, + struct animation); + + if (build_damage_animation_index( + damage_animation_type, + _unit_damage_direction_front, + damage_part)>=0 && + build_damage_animation_index( + damage_animation_type, + _unit_damage_direction_front, + damage_part)< + animation_graph->unit_damage_animations.count) + { + front_animation_index = + animation_graph_animation_index_get( + &animation_graph->unit_damage_animations) + [build_damage_animation_index( + damage_animation_type, + _unit_damage_direction_front, + damage_part)].animation_index; + } + else + { + front_animation_index = NONE; + } + + if (animation->runtime_parent_animation_index== + front_animation_index) + { + damage_direction = _unit_damage_direction_front; + } + } + + if (killed) + { + SET_FLAG( + unit->unit.animation.flags, + _unit_animation_fallen_on_front_bit, + damage_direction==_unit_damage_direction_back); + } + } + else if (killed) + { + animation_flags = unit->unit.animation.flags; + + animation_flags &= + ~FLAG(_unit_animation_fallen_on_front_bit); + animation_flags |= + FLAG(_unit_animation_ignore_translation_bit); + unit->unit.animation.flags = animation_flags; + + if (TEST_FLAG( + unit_definition->unit.flags, + _unit_is_destroyed_after_dying_bit)) + { + unit_destroy(unit_index); + } + } + } + } + +apply_alignment: + if (alignment_vector!=NULL && + !TEST_FLAG( + unit_definition->unit.flags, + _unit_does_not_reorient_during_pings_bit) && + unit->object.type==_object_type_biped && + unit->object.parent_object_index==NONE && + (hard_ping || killed)) + { + real_vector2d transformed_alignment; + + switch (damage_direction) + { + case _unit_damage_direction_back: + transformed_alignment = *alignment_vector; + code_0019ea70(unit_index, &transformed_alignment); + return; + + case _unit_damage_direction_front: + transformed_alignment.i = -alignment_vector->i; + transformed_alignment.j = -alignment_vector->j; + code_0019ea70(unit_index, &transformed_alignment); + return; + + case _unit_damage_direction_left: + transformed_alignment.i = -alignment_vector->j; + transformed_alignment.j = alignment_vector->i; + code_0019ea70(unit_index, &transformed_alignment); + return; + + case _unit_damage_direction_right: + transformed_alignment.i = alignment_vector->j; + transformed_alignment.j = -alignment_vector->i; + code_0019ea70(unit_index, &transformed_alignment); + return; + + default: + display_assert( + NULL, + "c:\\halo\\SOURCE\\units\\units.c", + 4562, + TRUE); + system_exit(-1); + code_0019ea70(unit_index, &transformed_alignment); + return; + } + } + + return; +} + +void unit_damage_aftermath( + long unit_index, + struct damage_data *damage_data, + unsigned long damage_flags, + real shield_damage, + real body_damage, + long unused, + long animation_index) +{ + struct unit_datum *unit; + struct unit_definition *unit_definition; + struct damage_definition *damage_definition; + real total_damage; + boolean lethal; + boolean feigned; + boolean instantaneous; + + unit = unit_get(unit_index); + unit_definition = unit_definition_get(unit->definition_index); + damage_definition = &damage_effect_definition_get( + damage_data->definition_index)->damage; + total_damage = shield_damage + body_damage; + lethal = TEST_FLAG( + damage_flags, + _unit_damage_aftermath_lethal_bit); + feigned = FALSE; + + (void)unused; + + if (debug_damage_taken && unit->unit.player_index != NONE) + { + char const *owner_name = ""; + char const *damage_name = ""; + + if (damage_data->owner_object_index != NONE) + { + struct object_datum *owner_object = + object_get(damage_data->owner_object_index); + struct object_definition *owner_definition = + object_definition_get(owner_object->definition_index); + char const *separator = strrchr( + owner_definition->object.model.name, + '\\'); + + if (separator) + { + owner_name = separator + 1; + } + } + + if (damage_data->definition_index != NONE) + { + char const *tag_name = + tag_get_name(damage_data->definition_index); + char const *separator = strrchr( + tag_name, + '\\'); + + if (separator) + { + damage_name = separator + 1; + } + } + + console_printf( + FALSE, + "p%d: body %.2f shld %.2f from %s %s", + DATUM_INDEX_TO_ABSOLUTE_INDEX(unit->unit.player_index), + body_damage, + shield_damage, + owner_name, + damage_name); + } + + { + real vitality = + unit->object.recent_shield_damage + + unit->object.recent_body_damage; + + if (vitality > 0.f) + { + unit->unit.last_damage_category = damage_definition->category; + unit->unit.delayed_damage_timer = 45; + if (vitality < unit->unit.delayed_damage_peak) + { + vitality = unit->unit.delayed_damage_peak; + } + unit->unit.delayed_damage_peak = vitality; + + if (damage_data->owner_object_index != NONE) + { + unit->unit.delayed_damage_attacker_object_index = + damage_data->owner_object_index; + } + } + } + + if (TEST_FLAG(unit->unit.flags, _unit_active_camouflaged_bit)) + { + unit->unit.active_camouflage -= + damage_definition->active_camouflage_damage; + + if (unit->unit.active_camouflage < 0.f) + { + unit->unit.active_camouflage = 0.f; + } + } + + instantaneous = + lethal && + damage_definition->instantaneous_acceleration >= 2.f; + + if (!lethal && + TEST_FLAG(unit->unit.flags, _unit_feign_death_allowed_bit) && + unit_definition->unit.feign_death_threshold > 0.f && + unit_definition->unit.feign_death_time > 0.f && + unit->object.body_vitality > 0.f && + unit->object.recent_body_damage > + unit_definition->unit.feign_death_threshold) + { + real feign_death_ticks = + (real_random_range(0.f, 1.f) + + unit_definition->unit.feign_death_time) * TICKS_PER_SECOND; + + SET_FLAG(unit->object.damage_flags, _object_dead_bit, TRUE); + feigned = TRUE; + unit->unit.feign_death_timer = (short)MAX( + 1.f, + feign_death_ticks); + match_assert( + "c:\\halo\\SOURCE\\units\\units.c", + 0x1284, + unit->unit.feign_death_timer > 0); + } + + if (!TEST_FLAG( + damage_data->flags, + _damage_data_suppress_unit_reaction_bit) && + (lethal || + feigned || + !TEST_FLAG(unit->object.damage_flags, _object_dead_bit)) && + !TEST_FLAG(unit->unit.flags, _unit_impervious_bit) && + !TEST_FLAG( + damage_definition->flags, + _damage_definition_does_not_ping_units_bit)) + { + real_vector2d damage_direction; + real_vector2d unit_forward; + boolean resists_pings; + boolean animation_flag; + boolean direction_valid; + real angle; + + damage_direction.i = damage_data->direction.i; + damage_direction.j = damage_data->direction.j; + unit_forward.i = unit->object.forward.i; + unit_forward.j = unit->object.forward.j; + resists_pings = FALSE; + animation_flag = FALSE; + direction_valid = FALSE; + angle = 0.f; + + if (normalize2d(&damage_direction) > 0.f && + normalize2d(&unit_forward) > 0.f) + { + angle = signed_angle_between_vectors2d( + &damage_direction, + &unit_forward); + direction_valid = TRUE; + } + + if (TEST_FLAG( + unit_definition->unit.flags, + _unit_resists_pings_bit) && + !TEST_FLAG( + damage_definition->flags, + _damage_definition_pings_resistant_units_bit)) + { + resists_pings = TRUE; + } + + if (unit->unit.flaming_death_delay > 0) + { + resists_pings = TRUE; + } + + if (damage_flags & UNIT_DAMAGE_AFTERMATH_ANIMATION_FLAGS_MASK) + { + animation_flag = TRUE; + } + + code_001a0cf0( + unit_index, + lethal, + feigned, + instantaneous, + resists_pings, + animation_flag, + angle, + (short)animation_index, + direction_valid ? &damage_direction : NULL); + } + + { + long owner_player_index = damage_data->owner_player_index; + long player_index = unit->unit.player_index; + + if (owner_player_index != NONE && player_index != NONE) + { + game_engine_player_damaged_player( + owner_player_index, + player_index, + TEST_FLAG( + damage_flags, + _unit_damage_aftermath_player_damage_type_bit)); + } + } + + if (damage_data->owner_player_index != NONE || + damage_data->owner_object_index != NONE) + { + unit_record_damage( + unit_index, + total_damage, + damage_definition->category, + lethal, + damage_data->owner_player_index, + damage_data->owner_team_index, + damage_data->owner_object_index); + } + + if (!TEST_FLAG( + damage_data->flags, + _damage_data_suppress_unit_reaction_bit) && + (TEST_FLAG( + damage_flags, + _unit_damage_aftermath_lethal_bit) || + body_damage > 0.f || + shield_damage > 0.f)) + { + unit_make_damage_sound( + unit_index, + damage_data, + lethal | feigned, + TEST_FLAG( + damage_flags, + _unit_damage_aftermath_died_instantly_bit), + body_damage, + shield_damage); + } + + if (body_damage > 0.f || shield_damage > 0.f) + { + unit_unzoom(unit_index); + } + + if (unit->object.type == _object_type_biped) + { + if (lethal) + { + ai_handle_death( + unit_index, + damage_data->owner_object_index, + damage_definition->category); + } + else if (!TEST_FLAG(unit->object.damage_flags, _object_dead_bit)) + { + ai_handle_damage( + unit_index, + damage_data->owner_object_index, + damage_definition->category, + total_damage, + &damage_data->direction, + FALSE); + } + } + + if (unit->unit.player_index != NONE && + damage_definition->stun > 0.f && + (game_engine_running() || stun_enable)) + { + struct game_globals_player_information *player_information = + TAG_BLOCK_GET_ELEMENT( + &scenario_get_game_globals()->player_information, + 0, + struct game_globals_player_information); + real stun_increment = + damage_definition->stun * damage_data->scale; + real maximum_stun = + damage_definition->maximum_stun * damage_data->scale; + short stun_ticks; + short minimum_stun_ticks; + short maximum_stun_ticks; + + if (stun_increment < 0.f) + { + stun_increment = 0.f; + } + if (maximum_stun < 0.f) + { + maximum_stun = 0.f; + } + else if (maximum_stun >= 1.f) + { + maximum_stun = 1.f; + } + + if (unit->unit.body_stun < maximum_stun) + { + unit->unit.body_stun += stun_increment; + if (unit->unit.body_stun > maximum_stun) + { + unit->unit.body_stun = maximum_stun; + } + } + + stun_ticks = (short)( + damage_definition->stun_time * TICKS_PER_SECOND); + minimum_stun_ticks = (short)( + player_information->minimum_stun_time * TICKS_PER_SECOND); + maximum_stun_ticks = (short)( + player_information->maximum_stun_time * TICKS_PER_SECOND); + + if (unit->unit.body_stun_ticks < minimum_stun_ticks) + { + unit->unit.body_stun_ticks = minimum_stun_ticks; + } + unit->unit.body_stun_ticks += stun_ticks; + if (unit->unit.body_stun_ticks > maximum_stun_ticks) + { + unit->unit.body_stun_ticks = maximum_stun_ticks; + } + } + + if (lethal || feigned) + { + unit_died(unit_index, feigned); + } + + return; +} + void unit_flame_to_death( long unit_index) { @@ -6188,7 +7169,7 @@ long unit_inventory_get_weapon( return unit_get_weapon(unit, index); } -void unit_ready_desired_weapon( +static void unit_ready_desired_weapon( long unit_index, boolean immediate) { @@ -6350,15 +7331,6 @@ static void unit_refresh_illumination( return; } -static void unit_adjust_for_seat_change( - long unit_index) -{ - struct unit_datum *unit = unit_get(unit_index); - unit->unit.desired_weapon_index = unit_weapon_next_index(unit_index, unit->unit.current_weapon_index, 0); - unit_ready_desired_weapon(unit_index, TRUE); - return; -} - static short unit_weapon_next_index( long unit_index, short current_index, @@ -7145,9 +8117,56 @@ static boolean code_00198fd0( return result; } + +boolean unit_test_animation_impulse( + long unit_index, + long animation_impulse) +{ + struct unit_datum *unit = unit_get(unit_index); + struct unit_definition *unit_definition; + struct animation_graph *animation_graph; + struct animation_graph_unit_seat *unit_seat; + struct animation_graph_weapon_class *weapon_class; + short interpolation_frame_count; + short animation_type; + + if (code_00198fd0(unit_index, animation_impulse)) + { + unit_definition = unit_definition_get(unit->definition_index); + animation_graph = animation_graph_definition_get( + unit_definition->object.animation_graph.index); + unit_seat = TAG_BLOCK_GET_ELEMENT( + &animation_graph->unit_seats, + unit->unit.animation.seat_index, + struct animation_graph_unit_seat); + weapon_class = TAG_BLOCK_GET_ELEMENT( + &unit_seat->weapon_classes, + unit->unit.animation.weapon_index, + struct animation_graph_weapon_class); + animation_type = code_00198e40( + animation_impulse, + &interpolation_frame_count); + + if (animation_type>=0 && animation_typeanimations.count) + { + animation_type = + animation_graph_animation_index_get(&weapon_class->animations) + [animation_type].animation_index; + } + else + { + animation_type = NONE; + } + + return (boolean)(animation_type!=NONE); + } + + return FALSE; +} + boolean unit_start_animation_impulse( long unit_index, - short animation_impulse, + long animation_impulse, real_vector2d *alignment_vector) { struct unit_datum *unit = unit_get(unit_index); diff --git a/source/units/units.h b/source/units/units.h index 54492a4e..0184ad19 100644 --- a/source/units/units.h +++ b/source/units/units.h @@ -581,8 +581,13 @@ void unit_estimate_position( void unit_get_center_of_mass( long unit_index, real_point3d *center_of_mass); -boolean unit_test_animation_impulse(long unit_index, short animation_impulse); -boolean unit_start_animation_impulse(long unit_index, short animation_impulse, real_vector2d *alignment_vector); +boolean unit_test_animation_impulse( + long unit_index, + long animation_impulse); +boolean unit_start_animation_impulse( + long unit_index, + long animation_impulse, + real_vector2d *alignment_vector); long unit_get_aiming_unit_index(long unit_index); void unit_get_aiming_vector( long unit_index, @@ -680,7 +685,6 @@ boolean unit_set_seat( long unit_index, char const *seat_label); -void unit_ready_desired_weapon(long unit_index, boolean immediate); boolean unit_can_use_weapon( long unit_index,