The cylinder table resource = type 29 DamageLookupTableStream, 18 in BTL4.RES (one per mech) -> STEP 6 is FEASIBLE, not content-blocked. Name copied from a sibling resource (local_130+0xc). RE phase complete; build (containers + handler + load + wire) is the next phase. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
99 lines
7.3 KiB
Markdown
99 lines
7.3 KiB
Markdown
---
|
||
id: combat-damage
|
||
title: "Combat & Damage — targeting, firing, damage zones, death"
|
||
status: established
|
||
source_sections: "PROGRESS_LOG.md §10c; docs/HARD_PROBLEMS.md (P5)"
|
||
related_topics: [subsystems, decomp-reference, reconstruction-gotchas, locomotion, rendering]
|
||
key_terms: [damage-zone, TakeDamage, viewpoint-entity, master, replicant]
|
||
open_questions:
|
||
- "Per-impact aim (cylinder lookup); collision damage application; DeathShutdown + collapse anim"
|
||
---
|
||
|
||
# Combat & Damage
|
||
|
||
The drive→target→fire→damage→destroy loop. Full detail: `docs/PROGRESS_LOG.md §10c`; the death/
|
||
teardown forensics: `docs/HARD_PROBLEMS.md` (P5).
|
||
|
||
## No AI — BT is PvP-only (verified, not a recovery gap)
|
||
The mission system is NETWORKED and BT never had NPCs/AI. Every mech is driven by a `BTPlayer` (or a
|
||
`BTCameraDirector` spectator). The BT registry knows only Mech/Projectile/Missile/BTPlayer/BTTeam; no
|
||
Bot/Brain/AI class exists; `BTL4OPT.EXE` has no AI strings. `Crusher/Blocker/Runner` are RED PLANET
|
||
classes, NOT BT. ⇒ a solo mission has no enemy by design; `BT_SPAWN_ENEMY` is a TEST DUMMY
|
||
(uncontrolled Mech). The authentic opponent path is [[multiplayer]]. [T1]
|
||
|
||
## Entity spawn + the per-mech gate
|
||
`Mech::Make(MakeMessage*)` (`FUN_004a2d48`) allocs `sizeof(Mech)` + runs the ctor; the base Entity
|
||
ctor self-registers it (renders + ticks). `Mech::PerformAndWatch` runs for EVERY mech → gate
|
||
player-only logic on `this == application->GetViewpointEntity()` (else a spawned enemy is driven by
|
||
player input). [T1]
|
||
|
||
## Targeting (mech offsets, [[decomp-reference]] §3)
|
||
`mech+0x37c` = target world Point3D; `mech+0x388` = target `Entity*` (the `HasActiveTarget()` gate);
|
||
`mech+0x38c` = targeted sub-zone (−1=whole). Weapons cache hasTarget/targetPoint/muzzlePoint,
|
||
refreshed each frame. [T1]
|
||
|
||
## Firing arc + muzzles
|
||
There is NO firing-arc field in the binary (`Emitter::FireWeapon` fires whenever HasActiveTarget) —
|
||
the 1995 game got away with it because it was **cockpit-only** (a beam behind you is off-camera). The
|
||
port's external camera exposes it, so the arc is gated on the **torso twist range**
|
||
(`Torso::GetHorizontalReach`, 0 for the twist-disabled Blackhawk) + a base aim tolerance
|
||
(`BT_FIRE_ARC`, a labeled PORT presentation param). Beams fire from the mech's real gun-port SITE
|
||
segments (siterugunport etc.) resolved via `GetSegment(name)→segmentToEntity×localToWorld`. [T2]
|
||
|
||
## Weapon fire is a PORT reconstruction (the dpl_* beam layer is unported)
|
||
`Emitter::FireWeapon` builds a muzzle→target beam but the `dpl_*` beam renderable was never ported →
|
||
the beam is drawn by `BTPushBeam`/`BTDrawBeams` (L4VIDEO — an additive quad / the real `ermlaser.bgf`
|
||
tube with the scrolling `bexp` grit). Fire rate is decoded-exact (DischargeTime 0.2 + RechargeRate 2.2
|
||
per ER-M laser; [[decomp-reference]] §5). Flying projectiles (LRM/autocannon) are also a PORT
|
||
reconstruction (`BTPushProjectile` — the 2007 Entity is too small for the binary's raw integrator
|
||
offsets). [T2]
|
||
|
||
## Damage delivery + the real damage model
|
||
`Entity::TakeDamageMessage(id, size, inflictingEntityID, zone, Damage&)` → `target->Dispatch`.
|
||
**Base handler IGNORES zone==−1** (`Entity::TakeDamageMessageHandler`, ENTITY.cpp:878 — returns on
|
||
`damageZone==−1`; the message carries `invalidDamageZone = damageZone<0` + `damageData.impactPoint`).
|
||
The Mech override that resolves an unaimed hit's zone from the impact point (the **cylinder hit-location
|
||
model**, the deferred **STEP 6**) is NOT reconstructed — meanwhile send a VALID zone (mech4 aims a vital
|
||
zone). **Investigated 2026-07 (durable):** the earlier-cited addresses `FUN_004a0230`/`FUN_0049ed0c` do
|
||
NOT exist — the real pieces are: the **CylinderDamageZoneTable** = a list of 0x30-byte cylinder entries
|
||
(entry ctor `FUN_0049e740`) loaded from resource type **0x1d** by the mech's cylinder-table NAME; the
|
||
table ctor is `FUN_0049ea48` (vtable 0x50bd84, alloc 0x2c, cached at `Mech[0x111]`=byte 0x444) — the recon
|
||
DOES build it (mislabeled `StandingAnimation`, mech.cpp:1221) but with an EMPTY name → 0 entries → the
|
||
lookup is a no-op. **Fully reversed (2026-07):** the table is a passive nested-list **height × angle grid** — 3 container
|
||
classes: TABLE (0x2c, ctor `FUN_0049ea48` / dtor `0x49eadc`, vtable 0x50bd84, `this[3]`=mech,
|
||
`this[4]`=row list, `this[10]`=row count) → ROWS by HEIGHT (0x30, ctor `FUN_0049e740` / dtor `0x49e814`,
|
||
vtable 0x50bd90, `this[0xb]`=cell count, cell key `i·(2π/count)` — `_DAT_0049e810`=**6.2831855=2π**;
|
||
`this[4]`=mech+0x438) → CELLS by ANGLE (0x2c, ctor `FUN_0049deb0` / dtor `0x49df80`, vtable 0x50bd9c) →
|
||
zone ref(s). All three vtables are MINIMAL (dtor + 2 Node slots, no method) → **the lookup is entirely in
|
||
the unexported `Mech::TakeDamageMessageHandler`** (a handler-SET entry, not a vtable slot — ENTITY.h has
|
||
no `virtual`; find it via the Mech message-handler registration, then disassemble). So STEP 6 remaining =
|
||
(1) fix the resource-name load (build the table WITH data, not the empty-name stub); (2) reconstruct the 3
|
||
container classes from the captured ctors; (3) disassemble + reconstruct `Mech::TakeDamageMessageHandler`
|
||
(compute the impactPoint's local height+angle → index the grid → dispatch to `damageZones[zone]->TakeDamage`);
|
||
(4) register the handler + verify unaimed hits distribute across zones. A genuine multi-pass reconstruction
|
||
(the container classes are tedious; the handler is the disassembly). **Content confirmed (2026-07): the
|
||
resource is type 29 = `DamageLookupTableStream`, 18 present in `BTL4.RES` (one per mech) — so STEP 6 is
|
||
FEASIBLE, not content-blocked.** The recon's empty-name stub (mech.cpp:1219) must be fixed to the real
|
||
name (the binary copies it from a sibling resource, `local_130+0xc` @part_012.c:10361 — reuse the mech's
|
||
DamageZoneStream/model name). RE phase COMPLETE; the build (containers + handler + load + wire) is next. `Mech__DamageZone::TakeDamage` → engine armor model (`damageLevel
|
||
+= damageAmount*damageScale[type]`) → 1.0 = zone destroyed → death. The Mech ctor populates the
|
||
inherited `damageZones[]` from the DamageZoneStream (type 0x14) resource. `class Damage{ damageType
|
||
(Collision/Ballistic/Explosive/Laser/Energy), damageAmount, damageForce, surfaceNormal, impactPoint,
|
||
burstCount }`. Verified: `structure` climbs, vital zone destroyed → `*** TARGET DESTROYED ***`. [T2]
|
||
Two non-field fixes were needed: **message-handler chaining** (an unchained set drops TakeDamage
|
||
silently) + **entity validity** (`SetValidFlag()` on a manually-spawned entity) — see
|
||
[[reconstruction-gotchas]] §9. [T2]
|
||
|
||
## Death — the wreck STAYS (P5 CLOSED)
|
||
A killed mech does NOT disappear — BT death is a STATE transition
|
||
(`SetGraphicState(DestroyedGraphicState)` + death anim + effect/splash), the mech becomes a persistent
|
||
WRECK (RP analog: `VTV::DeathShutdown`). **NEVER issue `DestroyEntityMessage` on death** — the
|
||
teardown crash was an artifact of forcing a removal the original never does. The actual bug was ONE
|
||
thing: an EXPLICIT `JointedMover::~JointedMover()` in the reconstructed `~Mech` ran the whole base-dtor
|
||
chain TWICE (the dtor-epilogue rule, [[reconstruction-gotchas]] §7) → a double-free of `collisionLists`
|
||
+ the segment table. Removing it fixed BOTH the death-row crash AND app-exit crashes. [T2]
|
||
|
||
## Key Relationships
|
||
- Weapons/roster: [[subsystems]]. Aim source: [[locomotion]] (drive/facing). Effects: [[rendering]].
|
||
- P5 forensics: `docs/HARD_PROBLEMS.md`. Data: [[decomp-reference]] §4-5.
|