//==========================================================================// // File: mechdmg.hpp // // Project: BattleTech // // Contents: Mech::DamageZone // //---------------------------------------------------------------------------// // Date Who Modification // // -------- --- ---------------------------------------------------------- // // 06/05/95 JM Initial coding. // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. // // All Rights reserved worldwide // // This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // //===========================================================================// // // RECONSTRUCTED from the shipped binary (Ghidra pseudo-C in part_012.c) // cross-referenced with the SURVIVING header MECHDMG.HPP (ground truth -- // declarations below are reproduced verbatim from it). The only additions // over the surviving header are the byte-offset annotations ("@0x..") and // the per-method @ADDR evidence, which live in comments. See mechdmg.cpp. // // Object layout (size 0x1b8, ctor @0049ce50): // [0x000] DamageZone base (vtable @0050bb90, base ctor @0041df5c): // owner Mech @0x00c, graphic alarms @0x010 / @0x064, flags @0x0b8, // 5 armor/structure scalars @0x144..0x154, structureLevel @0x158, // ref-counted name @0x15c. // [0x160] Mech::DamageZone members (see field comments below). // #if !defined(MECHDMG_HPP) # define MECHDMG_HPP #include # if !defined(DAMAGE_HPP) # include # endif # if !defined(SLOT_HPP) # include # endif # if !defined(TABLE_HPP) # include # endif # if !defined(ENTITYID_HPP) # include # endif # include "mechrecon.hpp" // reconstruction shim class Mech; class Subsystem; class DamageZone; //======================================================================// // Reconstruction proxies for the per-zone containers/slots. // The recovered code drives the redirect table, integer plugs and the // parent/subsystem slots through a verb set (Construct / Add / Resolve / // Next / Count / value ...) that the modern engine TableOf / PlugOf / // SlotOf spell differently; these behaviour-neutral stand-ins carry that // surface so the recovered bodies compile. (Reconstruction only.) //======================================================================// struct DZIntegerPlug // PlugOf stand-in { int value; DZIntegerPlug() : value(0) {} DZIntegerPlug(const int *p) : value(p ? *p : 0) {} }; struct DZIndexTable // TableOf stand-in { void Construct(int, int) {} void Add(DZIntegerPlug *) {} }; struct DZIndexTableIterator // TableIteratorOf<...> stand-in { DZIndexTableIterator(DZIndexTable *) {} int Count() { return 0; } DZIntegerPlug *Next() { return 0; } DZIntegerPlug *operator[](int) { return 0; } void DeleteContents() {} }; // (task #2: the DZSlot stand-in is RETIRED -- the engine SlotOf // (engine/MUNGA/SLOT.h) is the real type; the binary vtables 0050bb84 / // 0050bb7c ARE its / instantiations: // FUN_004179d4 = Slot::Slot(Node*), FUN_00417a80 = AddImplementation // (BIND), FUN_00417ab4 = GetCurrentPlug (READ). The stand-in's // always-null Resolve() was why every critical plug read as unbound.) //########################################################################## //###################### MechCriticalSubsystem ################# //########################################################################## // // One critical subsystem that a damage zone can destroy. Streamed from the // "CriticalSubsystem" tokens in the .dmg file (" "). // Object size 0x1c (28 bytes). ctor @0049dd18, dtor @0049dd44, // vtable @0050bb8c (single slot: the destructor). // class MechCriticalSubsystem SIGNATURED { public: MechCriticalSubsystem(DamageZone *owner); // @0049dd18 virtual ~MechCriticalSubsystem(); // @0049dd44 (vtable slot 0) SlotOf subsystemPlug; // @0x04 ctor @0049dd7e / dtor @0049dd9d (vtable @0050bb84) Scalar damagePercentage, // @0x10 streamed (1st "%f") damagePercentageUsed; // @0x14 streamed (2nd "%f"); init 0 Scalar criticalWeight; // @0x18 init 0; summed into owner.criticalWeightSum Logical TestInstance() const; // @0049dd74 (returns 1) }; //########################################################################## //#################### Mech::DamageZone ########################### //########################################################################## // // vtable @0050bb90 (7 slots). Overridden slots vs the DamageZone base: // slot 0 (dtor) -> ~Mech::DamageZone @0049d23c // slot 3 (SetGraphic) -> SetGraphicState @0049c66c // slot 6 (TakeDamage) -> TakeDamage @0049c690 // Slots 1,2,4,5 (@004173b8/@00417938/@0041ddd8/@0041dd98) inherit DamageZone. // class Mech__DamageZone : public DamageZone { // The owning Mech reads zone state (structureLevel, criticalSubsystemCount, // vital/destroyed flags) when routing damage + reporting -- the binary's Mech // damage code (FUN_004a0230 / FUN_0049fb54 etc.) is a Mech method touching // these zone fields directly. friend class Mech; friend class MechDeathHandler; // walks the descriptor table + applies the destroyed skin //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // LOD Support // protected: void LODDamageRouter(EntityID inflicting, Damage damage); // @0049c40c int RandomRedirect(); // @0049c600 typedef DZIntegerPlug IntegerPlug; // (was PlugOf) typedef DZIndexTable DamageZoneIndexTable; // (was TableOf) typedef DZIndexTableIterator DamageZoneIndexTableIterator; // (was TableIteratorOf<...>) // Zones which are artifacts have a table of their real children. DamageZoneIndexTable redirectTable; // @0x160 ctor @00424767(,0,1) // The result of the last random redirect int lastHitZone; // @0x178 init RandomRedirect() or 0 Scalar lastDamageTime; // @0x17c init now() (ticks/TicksPerSec) EntityID lastInflicting; // @0x180 copied from mech[+0x184] // // Zones which are real have a pointer (slot) back to their artifact parent. // With the current art (1/30/96) no zone has both a parent and children, // and many zones have neither. SlotOf parentArtifactZone; // @0x188 ctor @0049ddc9 (vtable @0050bb7c) // --- reconstruction-named state (decomp spellings; real DamageZone // base uses damageLevel/damageZoneState/etc.) ------------------ // NOTE: structureLevel was a RE-DECLARATION of the engine DamageZone base // field `damageLevel` (binary offset 0x158). Compiled as a subclass of the // engine DamageZone, the re-declared copy landed at a different offset and // stayed uninitialised (0xCDCDCDCD) while the engine ctor/TakeDamage wrote // the *base* damageLevel -- so the death/vital logic read garbage. Removed; // the reconstruction now uses the inherited `damageLevel` (the same field the // binary's 0x158 IS). Likewise damageScale[5]/defaultArmorPoints are the // inherited base members, not raw 0x144/0x140 offsets. // graphicState/graphicStateAlarm/flags/state were RE-DECLARATIONS of engine // DamageZone base fields (binary 0x010/0x064/0x0b8): the StateIndicators // damageZoneState (2 states, @0x010) and damageZoneGraphicState (3 states, // @0x064), and changedFlags. The engine ctor initialises them; the // re-declared copies stayed uninitialised -> SetLevel on garbage crashed when // a vital zone was destroyed. Removed; the reconstruction uses the inherited // accessors: SetDamageZoneState/GetDamageZoneState (was graphicState) and // SetGraphicState/GetGraphicState (was graphicStateAlarm/state). void UpdateLODDamageLevel(); // @0049c51c //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Descending Heirarchy when DamageZone Destroyed Support // void ShortAttachedGenerators(); // inlined into TakeDamage @0049c690 void SendSubsystemDamage(); // @0049c9a8 void RecurseSegmentTable(Mech *my_mech); // @0049cad4 int segmentIndex; // @0x194 GetSegmentIndex() result public: // The zone's segment slot -- consumed by the @004d097c per-zone effect // dispatcher (BTL4VideoRenderer::StartEntityEffectImplementation). int EffectSegmentIndex() const { return segmentIndex; } protected: //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Damage Support // public: void TakeDamage(Damage& damage); // @0049c690 (vtable slot 6) Subsystem* CriticalHit(Damage &damage_data); // @0049ccc4 protected: Logical vitalDamageZone; // @0x198 streamed "VitalDamageZone" Logical descendOnDestruction; // @0x19c streamed "DescendOnDestruction" Logical destroySiblingsOnDestruction; // @0x1a0 streamed "DestroySiblingsOnDestruction" Logical leftLeg; // @0x1a4 "LegDamageZone == LeftLeg" Logical rightLeg; // @0x1a8 "LegDamageZone == RightLeg" int criticalSubsystemCount; // @0x1ac streamed count Scalar criticalWeightSum; // @0x1b0 sum of criticalWeight MechCriticalSubsystem **criticalSubsystems; // @0x1b4 array[criticalSubsystemCount] //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Damage-state descriptor table (binary this+0xd4). Streamed from the // type-0x1e resource by LoadCriticalSubsystems (FUN_0041e4a8 -> FUN_0042a748); // as this zone's damageLevel crosses each entry's threshold, MechDeathHandler // fires the entry's explosion + applies its GraphicState (destroyed skin). // Entries ascend by damageLevel. (Was un-built -- the loader was a stub.) // public: struct DamageDescriptor // binary entry 0x1C (ctor FUN_0042a2c8) { Scalar damageLevel; // +0xc threshold [0,1] int graphicState; // +0x10 GraphicState enum (Destroyed=1) int effectResource; // +0x14 ExplosionModelFile resource id Scalar timeDelay; // +0x18 }; // @0042a664: first entry whose threshold > level (0 if none / fully destroyed). const DamageDescriptor *DescriptorForLevel(Scalar level) const; // @0042a5f4: true iff some entry's threshold lies in (oldLevel, newLevel]. Logical DescriptorCrossed(Scalar oldLevel, Scalar newLevel) const; // @0042a6c4: first entry whose GraphicState == gstate (0 if none). const DamageDescriptor *DescriptorForGraphicState(int gstate) const; Scalar GetStructureDamageLevel() const; // inherited damageLevel (binary +0x158) // Apply a descriptor's GraphicState (Destroyed=1) to this zone -- the // destroyed-skin swap the renderer reads (public wrapper so MechDeathHandler // can drive it; the base SetGraphicState is otherwise unreachable from it). void ApplyDamageGraphicState(int gstate); // Restore this zone to full structure + intact skin -- the respawn heal // (Mech::Reset, @0049fb74 resets every subsystem's structure). void Heal(); protected: std::vector damageDescriptors; // binary this+0xd4 collection //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Graphic State Support // public: // SetGraphicState (vtable slot 3) is NOT overridden -- the binary's override is // byte-identical to the engine base DamageZone::SetGraphicState (sets // damageZoneGraphicState + GraphicStateChangedFlag), so we use the base. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Construction / Destruction // public: static const int NullDamageZone; // = -1 Mech__DamageZone( // @0049ce50 Mech *mech, int damage_zone_index, MemoryStream *stream ); void SetLODParentPointers(); // @0049d1d0 void LoadCriticalSubsystems(MemoryStream *stream); // @0041e4a8 (crit table) ~Mech__DamageZone(); // @0049d23c static Logical CreateStreamedDamageZone( // @0049d304 (one zone) ResourceFile *resource_file, NotationFile *model_file, const char *model_name, NotationFile *skl_file, const char *damage_zone_name, NotationFile *dmg_file, const ResourceDirectories *directories, MemoryStream *damage_zone_stream ); static int GetSegmentIndex( // @0049db20 CString damage_zone_name, NotationFile *skl_file ); }; //########################################################################### //########################################################################### // MechDeathHandler (@0042a984 / @0042aa2c) // // A per-frame handler built by the Mech ctor (cached at mech[0x214]) that turns // subsystem/zone damage into VISIBLE destruction: each tick it walks the mech's // damage zones and, as a zone's damageLevel crosses a descriptor threshold, // spawns that entry's explosion effect at the zone + applies its GraphicState // (the destroyed skin). The binary registers it on the mech's Performance list // (mech+0xbc); the bring-up drive override does not tick that list, so // Mech::PerformAndWatch drives Tick() directly. Effect spawn uses the // established Explosion::Make port (the authentic effect-message manager, the // 0xBD3 SubsystemMessageManager, is unreconstructed) -- see BTSpawnDamageEffect. //########################################################################### class MechDeathHandler { public: MechDeathHandler(Mech *mech); // @0042a984 ~MechDeathHandler(); // @0042a9f4 void Tick(); // @0042aa2c (the Performance) private: Mech *owner; // @0x14 std::vector lastLevel; // @0x10 per-zone last damageLevel cache // PORT (respawn replication): latched when any zone is destroyed, so a // REPLICANT observer can reverse the one-way wreck swap when the master's // Mech::Reset heals + re-broadcasts the zones (falling damage edge). Not a // binary field -- the authentic un-wreck rides the type-0 graphic-state hook // (Mech::ReadUpdateRecord case 0), which our port drives via the zone levels. Logical wasWrecked; }; #endif