//===========================================================================// // File: mechsub.cpp // // Project: BattleTech Brick: Entity Manager // // Contents: MechSubsystem -- BT damage-coupled base for all mech subsystems // //---------------------------------------------------------------------------// // Date Who Modification // // -------- --- ---------------------------------------------------------- // // --/--/95 ?? Initial coding. // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved // // PROPRIETARY AND CONFIDENTIAL // //===========================================================================// // // RECONSTRUCTED from the shipped binary (BEST-EFFORT). Behaviour follows the // Ghidra pseudo-C for the cluster @0x4ac07c..@0x4ac9ec. No function in the // cluster is linker-tagged (all file=?); the module name "mechsub.cpp" is taken // from the surviving MECHTECH.HPP `#include `. This cluster was // previously attributed to heat.cpp's "Subsystem/HeatableSubsystem base"; the // classID 0xBBB / model 0xE4 streamer @0x4ac9ec, the "MechSubsystem" // Derivation name (@0x50def5), the DamageZone coupling, and the absence of any // thermal field access show it is MechSubsystem. See mechsub.hpp for the full // reconciliation note and the CLASSMAP correction. Each method cites its @ADDR. // // Float constants converted to decimal: // _DAT_004ac140 = 0x3f800000 = 1.0f (status threshold) // _DAT_004ac18c = 0x3f800000 = 1.0f (GetStatusFlags upper threshold) // _DAT_004ac190 = 0.0f (GetStatusFlags lower threshold) // _DAT_004ac860 = 0x3f800000 = 1.0f (armour-inversion numerator) // _DAT_004ac864 = 0x38d1b717 ~= 1e-4f (armour-inversion epsilon) // DAT_004e0f80 = identity ResourceID // // Helper-function name mapping (engine internals referenced by the decomp): // FUN_0041c5c0 Subsystem ctor (name + classID overload) // FUN_0041c52c Subsystem ctor (resource overload) // FUN_0041c648 Subsystem dtor FUN_0041c684 Subsystem::CreateStreamed // FUN_0041bbd8 AlarmIndicator::SetLevel(n) // FUN_0041de1c DamageZone ctor (0x160) FUN_004023f4 RefCount ctor // FUN_00402298 operator new FUN_004022d0 operator delete // FUN_00402a98 TextString connection bind // FUN_00408440 ResourceID copy // FUN_0041c800 (assert / GenerateFault default) // FUN_004dcd00 fabsf() // FUN_004dbb24 DebugStream::operator<< FUN_004d9c38 endl // FUN_00404088/0118/0190 NotationFile::Read(char**/float*/int*) // FUN_004274f8 Get_Segment_Index FUN_004d4b58 Strcmp() // #include #pragma hdrstop #if !defined(MECHSUB_HPP) # include #endif // // Tuning constants observed as read-only globals in the decomp. // static const Scalar StatusThreshold = 1.0f; // _DAT_004ac140 / _DAT_004ac18c static const Scalar StatusFloor = 0.0f; // _DAT_004ac190 static const Scalar ArmourNumerator = 1.0f; // _DAT_004ac860 static const Scalar ArmourEpsilon = 1.0e-4f; // _DAT_004ac864 static const Scalar ResourceUnset = -1.0f; // 0xbf800000 // // Status-state name table @0x50de74 walked by LookupStatusType (@0x4ac194). // struct StatusNameEntry { const char *name; int value; }; static const StatusNameEntry StatusNameTable[] = { { "Destroyed", 0 }, { "Damaged", 1 }, { "CoolantLeaking", 2 }, { "Overheating", 3 }, { "AmmoBurning", 4 }, { "Jammed", 5 }, { "BadPower", 6 }, { 0, 0 } }; // // Status-state name table @0x50de74 -> {char* name, int value} pairs, walked by // LookupStatusType (@0x4ac194). Names @0x50df17. // // PTR_s_Destroyed_0050de74 = { // "Destroyed",0, "Damaged",1, "CoolantLeaking",2, "Overheating",3, // "AmmoBurning",4, "Jammed",5, "BadPower",6, 0 // }; //############################################################################# // Shared Data Support // MechSubsystem::SharedData MechSubsystem::DefaultData( &MechSubsystem::ClassDerivations, ReconMessageHandlers, ReconAttributeIndex, MechSubsystem::StateCount ); Derivation MechSubsystem::ClassDerivations( Subsystem::GetClassDerivations(), "MechSubsystem" // @0x50def5 ); //############################################################################# // Construction / Destruction // // // @0x4ac530 -- light constructor (name + classID). Chains the MUNGA Subsystem // name/classID ctor, lays the vtable @0x50e210, allocates the status memory // block and a DamageZone (0x160 bytes) named "None", and primes flags. // MechSubsystem::MechSubsystem( Mech *owner, int subsystem_ID, const char *subsystem_name, RegisteredClass::ClassID class_id, SharedData &shared_data ): Subsystem(owner, subsystem_ID, subsystem_name, class_id, shared_data) // FUN_0041c5c0 { refCount = NewRefCount(0x10); // this[0x3d] this->owner = owner; // this[0x34] -- the owning Mech (canonical) hostEntity = owner; // this[0x3a] = param_7 subsystemId2 = subsystem_ID; // this[0x3b] = param_8 videoObjectFlag = 0; // this[0x39] BindName(refCount, "None"); // FUN_00402a98(.,this+0x3d,"None") @0x50df7a alarmModel = 0; // identity ResourceID // FUN_00408440(this+0x3e, identity) criticalReference = 0; // this[0x42] collisionCriticalHitWeight = 0; // this[0x43] printSimulationState = 0; // this[0x41] vitalSubsystemIndex = -1; // this[0x44] damageZone = (ReconDamageZone *)new DamageZone(this, 0); // this[0x38] = FUN_0041de1c(new 0x160, this, 0) } // // @0x4ac644 -- resource constructor. Chains the MUNGA Subsystem resource ctor, // lays the same vtable @0x50e210, builds the DamageZone, and seeds it from the // resource: structureReference -> DamageZone+0x140, armorByFacing[5] -> // DamageZone+0x144..+0x154. Each armour scalar is then inverted to a // per-facing absorption coefficient (1.0 / (armour * reference)) where the // reciprocal exceeds the epsilon. // (heat.cpp mislabels this ctor as "HeatableSubsystem"; the thermal fields it // claims at this+0x114 are never written here -- they belong to HeatSink.) // MechSubsystem::MechSubsystem( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data ): Subsystem(owner, subsystem_ID, subsystem_resource, shared_data) // FUN_0041c52c { refCount = NewRefCount(0x10); // this[0x3d] this->owner = owner; // this[0x34] -- the owning Mech. (The recon // ctor only set hostEntity; subclasses read // `owner` widely -> must be set or it is garbage.) statusAlarm.SetLevel(0); // FUN_0041bbd8(this+0xb, 0) hostEntity = (Mech *)subsystem_resource->segmentIndex; // this[0x3a]=param_6 subsystemId2 = subsystem_ID; // this[0x3b]=param_7 resource = subsystem_resource; // this[0x3c] vitalSubsystemIndex = -1; // this[0x44] videoObjectFlag = (subsystem_resource->vitalSubsystemIndex == 1); // res+0x48==1 BindName(refCount, subsystem_resource->videoObjectName); // res+0x4c alarmModel = subsystem_resource->alarmModel; // res+0xcc -> this[0x3e] printSimulationState = subsystem_resource->printSimulationState; // res+0xd8 criticalReference = subsystem_resource->criticalReference; // res+0xe0 collisionCriticalHitWeight = subsystem_resource->collisionCriticalHitWeight; // res+0xdc damageZone = (ReconDamageZone *)new DamageZone(this, 0); // this[0x38] BindName((char *)damageZone + 0x15c, GetName()); // FUN_00402a98(., dz+0x15c, name) // structure reference and per-facing armour into the DamageZone damageZone->structureReference = subsystem_resource->structureReference; // dz+0x140 = res+0x44 for (int i = 0; i < 5; ++i) { damageZone->armour[i] = subsystem_resource->armorByFacing[i]; // dz+0x144+i = res+0x30+i } // invert each facing into an absorption coefficient for (int i = 0; i < 5; ++i) { Scalar a = damageZone->armour[i] - ArmourNumerator / damageZone->structureReference; if (ArmourEpsilon < fabsf(a)) // FUN_004dcd00 { damageZone->armour[i] = ArmourNumerator / (damageZone->armour[i] * damageZone->structureReference); } } } // // @0x4ac868 -- releases the DamageZone (virtual slot0, arg 3), drops the status // memory block, then chains the MUNGA Subsystem dtor. // MechSubsystem::~MechSubsystem() { // *this = &PTR_FUN_0050e210; damageZone->~DamageZone(); release(refCount); // FUN_0041c648(this, 0) (Subsystem dtor) } Logical MechSubsystem::TestClass(Mech &) { return True; } Logical MechSubsystem::TestInstance() const { return IsDerivedFrom(ClassDerivations); } // // gauge-complete wave: this subsystem's OWN structural damage level [0,1] (0=intact, // 1=destroyed). The zone at this+0xE0 is an ENGINE DamageZone (new DamageZone(this,0) // above); read/write its public NAMED damageLevel (DAMAGE.h) via the ENGINE type -- // NOT the ReconDamageZone proxy (whose offset-0 structureLevel aliases the vtable ptr // as a float = garbage). The binary reads/writes *(this[0x38]+0x158) for exactly this // (SensorSimulation @004b1c4c, UpdateCoolant @004adbf8, HeatSink::HandleMessage @004add6c). // Scalar MechSubsystem::GetSubsystemDamageLevel() const { return damageZone ? ((DamageZone *)damageZone)->damageLevel : 0.0f; } void MechSubsystem::SetSubsystemDamageLevel(Scalar level) { if (damageZone) { ((DamageZone *)damageZone)->damageLevel = level; } } //############################################################################# // Subsystem virtual overrides // // // @0x4ac144 -- damage tier from the DamageZone structure level (dz+0x158): // >= 1.0 -> 1 (intact / nominal), > 0.0 -> 2 (damaged), else 0 (dead). // LWord MechSubsystem::GetStatusFlags() { Scalar structure = damageZone->structureLevel; // *(this[0x38]+0x158) if (StatusThreshold <= structure) return 1; // _DAT_004ac18c if (StatusFloor < structure) return 2; // _DAT_004ac190 return 0; } // // @0x4ac0bc -- per-frame structure watchdog. Forwards the message to the base // (vtable+0x18), and when the DamageZone structure has recovered to >= 1.0, // raises the status alarm to level 1, fires the alarm-changed callback, pins // structure to exactly 1.0, and (if a model is attached) raises alarm 9 on the // owner's status block. // Logical MechSubsystem::HandleMessage(int message) { (*BaseSlot0x18())(this, message); // *(this[0x38].vtable+0x18) if (StatusThreshold <= damageZone->structureLevel) // _DAT_004ac140 { statusAlarm.SetLevel(1); // FUN_0041bbd8(this+0xb, 1) if (printSimulationState != 0) // this[0x41] { OnAlarmChanged(); // (*this.vtable+0x34)(this) } damageZone->structureLevel = 1.0f; // dz+0x158 = 1.0 if (videoObjectFlag != 0) // this[0x39] { ((Mech *)owner)->RaiseStatusAlarm(9); // FUN_0041bbd8(owner+0x2c, 9) } } return True; } // // @0x4ac1d4 -- reset. On powered==0: restore structure to 1.0, set status // alarm level 1 and fire the alarm-changed callback. Otherwise delegate to the // fault/assert path (FUN_0041c800). // void MechSubsystem::ResetToInitialState(Logical powered) { if (!powered) { if (damageZone != 0) { damageZone->structureLevel = 1.0f; // dz+0x158 = 1.0 } statusAlarm.SetLevel(1); // FUN_0041bbd8(this+0xb, 1) if (printSimulationState != 0) { OnAlarmChanged(); // (*this.vtable+0x34)(this) } } else { GenerateFaultDefault(); // FUN_0041c800 } } // // @0x4ac22c -- ClearStatus: zero the structure and both alarms (the status // alarm and the DamageZone's own alarm), then fire the alarm-changed callback. // void MechSubsystem::ClearStatus() { damageZone->structureLevel = 0.0f; // dz+0x158 = 0 damageZone->alarm.SetLevel(0); // FUN_0041bbd8(dz+0x10, 0) statusAlarm.SetLevel(0); // FUN_0041bbd8(this+0xb, 0) if (printSimulationState != 0) { OnAlarmChanged(); } } // // @0x4ac8c0 -- prints " = ". Status @this+0x40. // void MechSubsystem::PrintState() { switch (simulationState) // *(this+0x40) { case DefaultState: DebugStream << GetName() << " = DefaultState" << endl; break; // 0050df99 case DestroyedState: DebugStream << GetName() << " = Destroyed" << endl; break; // 0050df7f case ExplodingState: DebugStream << GetName() << " = Exploding" << endl; break; // 0050df8c default: DebugStream << GetName() << " = UnknownState!!! : " << simulationState << endl; break; // 0050dfa9 } } // // @0x4ac9c8 -- IsDamaged: true (dead) when the owning mech's electrical bus is // down -- *(*(*(owner+0xd0)+0x190)+0x274) == 0. // Logical MechSubsystem::IsDamaged() { return (*(int *)(*(int *)(*(int *)((char *)owner + 0xd0) + 0x190) + 0x274) == 0); } //############################################################################# // Damage / critical-hit helpers // // // @0x4ac07c -- run TakeDamage (virtual slot+0x24) and return the change in the // DamageZone structure level it produced. // Scalar MechSubsystem::ApplyDamageAndMeasure(Damage &damage) { Scalar before = damageZone->structureLevel; // dz+0x158 TakeDamage(damage); // (*this.vtable+0x24)(this, &damage) return damageZone->structureLevel - before; } // // @0x4ac274 -- distribute a critical hit across the contained critical // subsystems. Drives the status alarm through level 2 then 1, pins structure // to 1.0, then iterates the critical-subsystem chain (ClassID 0x4E entries), // divides the incoming damage amount by the critical count, and re-applies the // scaled damage to each, logging "ammo explosion damaging " per hit. // void MechSubsystem::DistributeCriticalHit(Damage &damage) { statusAlarm.SetLevel(2); if (printSimulationState) OnAlarmChanged(); statusAlarm.SetLevel(1); if (printSimulationState) OnAlarmChanged(); damageZone->structureLevel = 1.0f; CriticalChain criticals(this); // FUN_004acfa9 int count = criticals.CountOfType(0x4E); // entries whose +4 == 0x4E damage.damageAmount /= (Scalar)count; // per-critical share for (CriticalEntry *c = criticals.First(); c != 0; c = criticals.Next()) { DebugStream << "ammo explosion damaging " << c->Subsystem()->GetName(); // 0050df61 c->Subsystem()->TakeDamage(damage); // (*owner.vtable+0xc)(...) } } // // @0x4ac194 -- map a status-state name to its TechStatusType value via the // table @0x50de74. Returns False if not found. // Logical MechSubsystem::LookupStatusType(const char *name, int *out_value) { for (const StatusNameEntry *e = StatusNameTable; e->name != 0; ++e) // PTR @0x50de74 { if (Strcmp(name, e->name) == 0) // FUN_004d4b58 { *out_value = e->value; return True; } } return False; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // CreateStreamedSubsystem -- MechSubsystem (@0x4ac9ec) // // Stamps classID 0xBBB / model size 0xE4 and parses the damage/critical fields // on top of the base Subsystem record. // int MechSubsystem::CreateStreamedSubsystem( NotationFile *model_file, const char *model_name, const char *subsystem_name, SubsystemResource *subsystem_resource, NotationFile *subsystem_file, const ResourceDirectories *directories, int passes ) { if (!Subsystem::CreateStreamedSubsystem( // FUN_0041c684 model_file, model_name, subsystem_name, subsystem_resource, subsystem_file, directories)) { return False; } subsystem_resource->subsystemModelSize = 0xE4; // res+0x24 subsystem_resource->classID = (RegisteredClass::ClassID)0x0BBB; // MechSubsystemClassID, res+0x20 if (passes == 1) { // first pass: prime to "unset" subsystem_resource->criticalReference = ResourceUnset; // res+0xe0 = -1.0f for (int i = 0; i < 5; ++i) subsystem_resource->armorByFacing[i] = ResourceUnset; // res+0x30.. = -1.0f subsystem_resource->structureReference = ResourceUnset; // res+0x44 subsystem_resource->vitalSubsystemIndex = -1; // res+0x48 memset(subsystem_resource->videoObjectName, 0, 128); // res+0x4c strcpy(subsystem_resource->videoObjectName, "None"); // DAT_0050dfc0 subsystem_resource->alarmModel = 0; // identity ResourceID // res+0xcc subsystem_resource->printSimulationState = 0; // res+0xd8 subsystem_resource->collisionCriticalHitWeight = 0; // res+0xdc } model_file->GetEntry(subsystem_name, "PrintSimulationState", &subsystem_resource->printSimulationState); // 0050dfc5 model_file->GetEntry(subsystem_name, "CollisionCriticalHitWeight", &subsystem_resource->collisionCriticalHitWeight); // 0050dfda const char *videoName = "Unspecified"; // 0050dff5 model_file->GetEntry(subsystem_name, "VideoObjectName", &videoName); // 0050e001 if (strcmp(videoName, "Unspecified") != 0) // 0050e011 { strcpy(subsystem_resource->videoObjectName, videoName); // res+0x4c } const char *vitalName = "Unspecified"; // 0050e01d if (!model_file->GetEntry(subsystem_name, "VitalSubsystem", &vitalName) // 0050e029 && subsystem_resource->vitalSubsystemIndex == -1) { // VitalSubsystem optional; resolved to a segment index when present } if (strcmp(vitalName, "Unspecified") != 0) { subsystem_resource->vitalSubsystemIndex = Get_Segment_Index(model_file, model_name, directories, vitalName); // FUN_004274f8 } Check_Fpu(); return True; }