//===========================================================================// // File: dmgtable.hpp // // Project: BattleTech Brick: Entity Manager // // Contents: Damage lookup table -- layered / pie-slice hit-location selector // //---------------------------------------------------------------------------// // Date Who Modification // // -------- --- ---------------------------------------------------------- // // --/--/95 ?? Initial coding. // // 07/08/26 RE Completed for RUNTIME: real vector storage (was no-op // // ReconTable shims), stream name-string read, PieSlice flag // // fix, ResolveHit->zone chain, named accessors. Byte-format // // verified against BTL4.RES type-29 (see combat-damage.md). // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved // // PROPRIETARY AND CONFIDENTIAL // //===========================================================================// // // RECONSTRUCTED from the shipped binary. Behaviour follows the Ghidra // pseudo-C for the cluster @0x49de14..@0x49f5fb. The three-level resolver is: // // DamageLookupTable -- stack of armour LAYERS by penetration depth (height) // -> PieSlice -- an angular sector wheel (optionally torso-relative) // -> DamageZonePercentTable // -- a weighted list of {cumulative %, zone segment index} // whose percentages rise to 1.0; a uniform random roll // then selects the final damage-zone index. // // Stream format (type-29 DamageLookupTableStream), byte-verified against the // shipped BTL4.RES (18 tables, EXACT consumption): // Table { i32 layerCount; Layer[layerCount] } // Layer { i32 rotateWithTorso; i32 sliceCount; Slice[sliceCount] } // Slice { i32 nameLen; char name[nameLen]; u8 0x00; i32 count; Entry[count] } // Entry { f32 cumulativeThreshold; i32 zoneIndex } // // Runtime lookup (FUN_0049eb54 + FUN_0049e678 + FUN_0049de14 + glue FUN_0049ed0c): // local = owner->WorldToLocal(impact) (owner+0xd0 xform) // layer = floor(layerCount * local.y / heightRef) (heightRef=owner+0x2ec[+0xc]) // theta = atan2(local.z, local.x) [+ torso twist] (degenerate -> random*2PI) // slice = floor(sliceCount * theta / 2PI) // zone = first entry whose cumulative threshold > RandomUnit() // // vtables: 0x50bd84 (DamageLookupTable), 0x50bd90 (PieSlice), // 0x50bd9c (DamageZonePercentTable / leaf). // #if !defined(DMGTABLE_HPP) # define DMGTABLE_HPP #include #if !defined(PLUG_HPP) # include #endif #include "mechrecon.hpp" // reconstruction shim (Scalar, DebugStream, ...) //##################### Forward Class Declarations ####################### class Mech; class NotationFile; class MemoryStream; class Point3D; //########################################################################### //##################### DamageZonePercentTable ########################## //########################################################################### // // Leaf node. Holds a sorted, cumulative-weighted list of damage-zone segment // indices; a uniform random roll turns a [0,1) draw into one of those zones. // (vtable @0x50bd9c, ctor @0x49deb0, dtor @0x49df80.) // class DamageZonePercentTable: public Plug { public: struct Entry // binary: zoneIndex@+0xc, cumulative@+0x14 { Scalar cumulative; // ascending sort key (rises to 1.0) int zoneIndex; // damageZones[] / skeleton segment index }; DamageZonePercentTable() {} // stack-temp / offline builder DamageZonePercentTable( // @0x49deb0 (ReadEntries @0x49e5e4) Mech *owner, MemoryStream *stream ); ~DamageZonePercentTable(); // @0x49df80 Logical TestInstance() const { return True; } // @0x49e000 // // @0x49de14 -- uniform weighted roll: draw a [0,1) sample (RandomUnit, // FUN_00408050) and return the zone index of the first entry whose // cumulative threshold exceeds the draw; -1 if the list is empty. // int SelectZone() const; // // @0x49e5e4 -- read the leading cell NAME string (i32 len + len+1 bytes // incl. NUL, per FUN_00402948), then the entry count and that many // {cumulative, zoneIndex} pairs. // void ReadEntries(MemoryStream *stream); // (offline @0x49e00c BuildFromNotation / @0x49e524 WriteEntries -- the // content-build authoring path -- is not part of the runtime port; the // shipped .RES is pre-built. Omitted here.) protected: Mech *owner; // @0x0c std::vector entries; // @0x14 (was ReconTable) -- sorted ascending }; //########################################################################### //############################# PieSlice ################################ //########################################################################### // // An angular sector "wheel". N equal-angle slices (each 2*PI/N rad); the slice // containing the incoming hit direction is chosen and its leaf table rolled. // When rotateWithTorso is set the incoming angle is measured relative to the // live torso twist (owner->TorsoHeading, == torso+0x1d8). // (vtable @0x50bd90, ctor @0x49e740.) // class PieSlice: public Plug { public: PieSlice() : owner(0), rotateWithTorso(0), sliceCount(0) {} PieSlice( // @0x49e740 Mech *owner, MemoryStream *stream ); ~PieSlice(); // // @0x49e678 -- pick the slice for incoming angle 'theta' (radians). When // rotateWithTorso, theta is shifted by the live torso twist and wrapped // into [0, 2*PI); slice index = floor(theta * sliceCount / 2*PI). // DamageZonePercentTable *SelectSlice(Scalar theta) const; protected: Mech *owner; // @0x0c std::vector slices; // @0x14 (was ReconTable) int rotateWithTorso; // @0x0c-flag in binary (this[3]) int sliceCount; // @0x2c (this[0xb]) }; //########################################################################### //######################### DamageLookupTable ########################### //########################################################################### // // Top-level table streamed from the mech's ".tbl". A stack of armour LAYERS: // the higher the local-frame impact, the higher the layer index. Each layer // is a PieSlice wheel. (vtable @0x50bd84, ctor @0x49ea48, dtor @0x49eadc.) // class DamageLookupTable: public Plug { public: DamageLookupTable( // @0x49ea48 Mech *owner, MemoryStream *stream ); ~DamageLookupTable(); // @0x49eadc Logical TestInstance() const { return True; } // @0x49eb48 // // @0x49eb54 (+ @0x49e678 slice + @0x49de14 roll, chained by glue @0x49ed0c) // -- resolve a world impact point to a damage-zone index. Returns -1 when // the table is empty or the roll falls through (treated as a miss). // int ResolveHit(const Point3D &impact) const; int LayerCount() const { return layerCount; } // bring-up verify protected: Mech *owner; // @0x0c std::vector layers; // @0x10 (was ReconTable) int layerCount; // @0x28 (this[10]) }; #endif