//===========================================================================// // File: mechtech.cpp // // Project: BattleTech Brick: Entity Manager // // Contents: MechTech -- the mech's subsystem-monitor / diagnostics subsystem // //---------------------------------------------------------------------------// // 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 @004ad124-@004ad6c9 cluster; the public interface and the // member / type names are GROUND TRUTH from the surviving MECHTECH.HPP. // Each non-trivial method cites its originating @ADDR. // // What MechTech does: // At construction it walks the owning Mech's segment roster and, for every // damageable MechSubsystem (those flagged with segment-bit 0x8), allocates a // SubsystemMonitor and threads it onto the `subsystemMonitors` chain. Each // frame the registered Performance (TechnicalAssistance @004ad33c) iterates // that chain, samples each watched subsystem's status flags, and -- whenever // a status bit changes -- posts an "alert" / "clear" message into the // cockpit status / MFD message sink. Set alerts carry the `alarmModel` // resource; the per-cell wooHoo timer latches an alert for a duration so the // readout doesn't flicker. // // Binary identity (mech.cpp's switch mislabelled these -- corrected here): // MechTech == ClassID 0xBDC, ctor @004ad228, size 0x104, // vtable @0050e3a0. // (mech.cpp's "MechTech" @004b7f94 / 0xBD6 is actually the HUD // torso-horizon gimbal display and is NOT this class.) // // Helper-function name mapping (engine internals referenced by the decomp): // FUN_0041c52c Subsystem base constructor // FUN_0041c684 Subsystem::CreateStreamedSubsystem // FUN_0041c648 Subsystem destructor // FUN_0041a1a4 IsDerivedFrom(classDerivations) // FUN_004022d0 operator delete (global heap) // FUN_00415e90 Plug constructor FUN_00415ed8 Plug destructor // FUN_004de998 array-construct N elements (count, stride, ..., elem-init) // FUN_004ad619 trivial element-init thunk (returns its arg) // FUN_00402f74 MemoryBlock::New(&block) FUN_00402f98 MemoryBlock::Delete(&block,p) // FUN_00417be0 / 00417c0c / 00417d00 / 00417d28 / 00417d54 // ChainOf<> / chain-iterator internals (ctor/dtor/iter) // FUN_004ad621 ChainOf ctor (vtable @0050e398) // FUN_004ad640 ChainOf<> dtor // FUN_004ad68b chain iterator ctor (vtable @0050e348) // FUN_004ad6c9 chain iterator dtor // FUN_00402f74(0x50e248) ... SubsystemMonitor::operator new // FUN_00404088 NotationFile::Read(section,key,&string) // FUN_00406ff8 ResourceFile::ResolveModel(name,...) -> ResourceID* // FUN_004dbb24 DebugStream << (error reporting) // FUN_004366b8 build "status cleared" cockpit message // FUN_00436688 build "status set" cockpit message (carries alarmModel) // FUN_004364e4 dispatch cockpit/MFD message to the status sink // DAT_004efc94 the running Application/World; (+0x38) == status sink // #include #pragma hdrstop #if !defined(MECHTECH_HPP) # include #endif #if !defined(APP_HPP) # include #endif #if !defined(TESTBT_HPP) # include #endif //########################################################################### //########################################################################### // MechTech::SubsystemMonitor //########################################################################### //########################################################################### //############################################################################# // Memory Allocation Support // // Static MemoryBlock @0050e248 (zero-filled in .data, primed at runtime). // MemoryBlock MechTech::SubsystemMonitor::AllocatedMemory( sizeof(MechTech__SubsystemMonitor), // record size (0x80) 8, // initial record count 8, // growth delta "MechTech::SubsystemMonitor" ); //############################################################################# // Construction / Destruction // // @004ad124 -- construct a monitor bound to one watched MechSubsystem. // // FUN_00415e90(this,1) -> Plug base ctor // *this = &PTR_FUN_0050e3d0 -> SubsystemMonitor vtable // FUN_004de998(this+0xC, 0x10, 7, 1, ...) -> placement-build statusArray[7] // this[0x7C] = subsystem -> monitoredSubsystem // loop x7: zero {currentStatus,wooHooed,startTime,wooHooEnd} // MechTech__SubsystemMonitor::MechTech__SubsystemMonitor(MechSubsystem *subsystem) : Plug() { monitoredSubsystem = subsystem; for (int i = 0; i < MechSubsystem::TechStatusTypeCount; ++i) // 7 { statusArray[i].currentStatus = 0; statusArray[i].wooHooed = 0; statusArray[i].startTime = 0L; // Time::operator=(long) statusArray[i].wooHooEnd = 0L; // Time::operator=(long) } } // // @004ad180 -- destructor + deleting-destructor thunk. Re-seats the vtable, // runs the Plug base teardown (FUN_00415ed8) and, when invoked as the // deleting destructor (flag bit 0), returns the storage to the MemoryBlock. // MechTech__SubsystemMonitor::~MechTech__SubsystemMonitor() { // Plug base + AllocatedMemory.Delete(this) handled by the generated // deleting-destructor thunk (FUN_00402f98(&AllocatedMemory, this)). } //########################################################################### //########################################################################### // MechTech //########################################################################### //########################################################################### //############################################################################# // Shared Data Support // // DefaultData @0050e270 ClassDerivations @0050e280 // Derivation MechTech::ClassDerivations( Subsystem::GetClassDerivations(), // MUNGA Subsystem exposes a Derivation* getter "MechTech" ); Receiver::MessageHandlerSet MechTech::MessageHandlers; MechTech::AttributeIndexSet MechTech::AttributeIndex; MechTech::SharedData MechTech::DefaultData( &MechTech::ClassDerivations, MechTech::MessageHandlers, MechTech::AttributeIndex, MechTech::StateCount ); //############################################################################# // Construction / Destruction // // @004ad228 (mech.cpp allocates 0x104 bytes and calls this for ClassID 0xBDC). // // FUN_0041c52c(...) -> Subsystem base ctor // *this = &PTR_FUN_0050e3a0 -> MechTech vtable // FUN_004ad621(this+0xE4, 0) -> subsystemMonitors chain ctor // // A live (non-copy / non-destroyed) instance -- (entity flags & 0xC) == 0 -- // installs the per-frame Performance and builds its monitor roster; a copy // just flags itself dormant (this[10] |= 2). // MechTech::MechTech( Mech *entity, int subsystem_id, SubsystemResource *model, SharedData &shared_data ): Subsystem(entity, subsystem_id, model, shared_data), subsystemMonitors(0) // FUN_004ad621(this+0xE4, 0) -- Chain(Node* == 0) { Check(entity); Check_Pointer(model); segmentFlags = 0; // (entity+0x28 & 0xC) == 0 -- owner is a live primary, not a copy/template. // CROSS-FAMILY: the original reads the owning Mech's per-segment flags // (Mech::GetSegmentFlags); mapped here to the entity's simulationFlags. if ((entity->simulationFlags & SegmentCopyMask) == 0) { SetPerformance(&MechTech::TechnicalAssistance); // this[7..9] = {&TechnicalAssistance,0,0} @0050e2b0 alarmModel = model->alarmModel; // res +0x30 -> this+0x100 wooHooMinimumDuration = model->wooHooMinimumDuration; // res +0x34 -> this+0xF4 wooHooDurationRange = model->wooHooDurationRange; // res +0x38 -> this+0xF8 wooHooChance = model->wooHooChance; // res +0x3C -> this+0xFC // // Walk the owner's subsystem roster and attach a monitor to every // damageable MechSubsystem. (Binary walked a Mech-specific segment // table at owner+0x124/+0x128 whose entries are these subsystem // objects; the engine exposes the same roster via Entity::GetSubsystem.) // for (int seg = entity->GetSubsystemCount() - 1; seg >= 0; --seg) { Subsystem *sub = entity->GetSubsystem(seg); if ( sub != 0 && sub->IsDerivedFrom(MechSubsystem::ClassDerivations) // FUN_0041a1a4(**sub[3], 0x50de2c) && sub->damageZone != 0 // damageable segment (segment bit 0x8) ) { SubsystemMonitor *monitor = new SubsystemMonitor((MechSubsystem *)sub); // MemoryBlock @0050e248 subsystemMonitors.Add(monitor); // (*this[0x39].vt[1])(...) } } } else { segmentFlags |= DormantInstanceFlag; // this[10] |= 2 -- dormant (copy / template instance) } Check_Fpu(); } // // @004ad1b8 -- destructor + deleting-destructor thunk. // // *this = &PTR_FUN_0050e3a0 // FUN_004ad68b/FUN_004ad6c9 ... walk + tear down the monitor chain, // FUN_004ad640(this+0xE4, 2) -> subsystemMonitors dtor, // FUN_0041c648(this, 0) -> ~Subsystem. // MechTech::~MechTech() { Check(this); // // Release every SubsystemMonitor still on the chain, then the chain // itself; ~Subsystem finishes the base teardown. // ChainIteratorOf iterator(&subsystemMonitors); for ( iterator.First(); SubsystemMonitor *monitor = iterator.GetCurrent(); /* Remove() re-seats currentLink */ ) { iterator.Remove(); delete monitor; } Check_Fpu(); } //########################################################################### // TestInstance -- MechTech // // @004ad470 -> FUN_0041a1a4(**this[3], 0x50e280) // Logical MechTech::TestInstance() const { return IsDerivedFrom(ClassDerivations); } //############################################################################# // Per-frame Performance // // // @004ad33c -- TechnicalAssistance: poll every monitored subsystem's status // flags and emit cockpit/MFD status messages on change. // // Decomp shape: // sink = *(DAT_004efc94 + 0x38); // the status / MFD message sink // for (monitor in subsystemMonitors) // iterator FUN_004ad68b // { // sub = monitor->monitoredSubsystem; // monitor[0x7C] // flags = sub->GetStatusFlags(); // vtable slot @+0x30 // for (type = 0; type < 7; ++type) // TechStatusTypeCount // { // bit = flags & 1; // cell = &monitor->statusArray[type]; // if (cell->wooHooed) // an alert is latched // { // if (cell->wooHooEnd < GetTime()) cell->wooHooed = 0; // expired // else bit = cell->currentStatus; // hold // } // if (bit != cell->currentStatus) // status changed -> report // { // Mech *owner = GetEntity(); // if (bit == 0) // cleared // FUN_004364e4(sink, owner, BuildClear(owner, sub, type)); // else // set (carries alarmModel) // FUN_004364e4(sink, owner, BuildSet(owner, sub, type, alarmModel)); // } // cell->currentStatus = bit; // flags >>= 1; // } // } // // NOTE (best-effort): the actual latch -- setting cell->wooHooed / // cell->wooHooEnd from wooHooChance / wooHooMinimumDuration / // wooHooDurationRange -- lives inside the message-builder helpers // (FUN_00436688 et al.), which are part of the HUD/message brick and were // not captured in this window. TechnicalAssistance only *clears* expired // latches and detects edges. // void MechTech::TechnicalAssistance(Scalar /*time_slice*/) { Check(this); Mech *owner = GetEntity(); void *sink = StatusMessageSink(); // *(DAT_004efc94 + 0x38) ChainIteratorOf it(&subsystemMonitors); // FUN_004ad68b SubsystemMonitor *monitor; for ( it.First(); (monitor = it.GetCurrent()) != 0; it.Next() ) { MechSubsystem *sub = monitor->monitoredSubsystem; LWord flags = sub->GetStatusFlags(); // vt[+0x30] for (int type = 0; type < MechSubsystem::TechStatusTypeCount; ++type) { int bit = (int)(flags & 1); StatusInfo &cell = monitor->statusArray[type]; if (cell.wooHooed != 0) { if (cell.wooHooEnd.ticks < GetTime().ticks) // this+0x10 == current time (integer tick compare) { cell.wooHooed = 0; // latch expired } else { bit = cell.currentStatus; // hold the latched state } } if (bit != cell.currentStatus) { if (bit == 0) { ReportStatusCleared(sink, owner, sub, type); // FUN_004366b8 + FUN_004364e4 } else { ReportStatusSet(sink, owner, sub, type, alarmModel);// FUN_00436688 + FUN_004364e4 } } cell.currentStatus = bit; flags >>= 1; } } Check_Fpu(); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // CreateStreamedSubsystem -- MechTech // // @004ad48c // // FUN_0041c684(...) -> Subsystem::CreateStreamedSubsystem // resource->subsystemModelSize = 0x40 (res[0x24] = 0x40) // resource->classID = 0xBDC (res[0x20] = 0x0BDC) // resource->alarmModel = -1 (res+0x30) // resource->wooHoo{Min,Range,Chance} = 0 (res+0x34,+0x38,+0x3C) // // "AlarmModel" is optional: if absent (and alarmModel still -1) the parse // succeeds with no alarm. If present, the named model is resolved against // the resource file; failure reports " couldn't locate .mod". // Logical MechTech::CreateStreamedSubsystem( NotationFile *model_file, const char *model_name, const char *subsystem_name, SubsystemResource *subsystem_resource, NotationFile *subsystem_file, const ResourceDirectories *directories, ResourceFile *resource_file ) { if ( !Subsystem::CreateStreamedSubsystem( // FUN_0041c684 model_file, model_name, subsystem_name, subsystem_resource, subsystem_file, directories ) ) { return False; } subsystem_resource->subsystemModelSize = sizeof(*subsystem_resource); // 0x40 subsystem_resource->classID = RegisteredClass::MechTechClassID; // 0xBDC subsystem_resource->alarmModel = -1; subsystem_resource->wooHooMinimumDuration = 0.0f; subsystem_resource->wooHooDurationRange = 0.0f; subsystem_resource->wooHooChance = 0.0f; // // "AlarmModel" -- optional model name resolved to a resource id. // const char *alarmModelName = 0; if ( subsystem_resource->alarmModel == -1 && !subsystem_file->GetEntry(subsystem_name, "AlarmModel", &alarmModelName) // FUN_00404088(param_5,param_3,"AlarmModel",&local_8) ) { return True; // no alarm model specified -- fine } // // Resolve the named model to a resource id. The binary calls // FUN_00406ff8(resource_file, name, 1, -1) ("ResolveModel"); the engine // ResourceFile exposes FindResourceDescription instead. // TODO(verify): confirm the resource type used for the alarm .mod. // ResourceDescription *description = resource_file->FindResourceDescription( alarmModelName, ResourceDescription::GameModelResourceType ); if (description == 0) { // @004ad48c: " couldn't locate .mod" // (original logged via DebugStream / FUN_004dbb24 -- HUD/diag brick). return False; } subsystem_resource->alarmModel = description->resourceID; Check_Fpu(); return True; } //############################################################################# // Status reporting support // // CROSS-FAMILY stubs. The shipped code dispatches cockpit/MFD status messages // through the running Application/World status sink (DAT_004efc94 + 0x38) using // the HUD message builders FUN_004366b8 ("status cleared") / FUN_00436688 // ("status set", carries alarmModel) and dispatcher FUN_004364e4. Those live // in the cockpit-message brick (btl4gaug / app), not reconstructed here, so the // bodies are stubs that satisfy the call sites in TechnicalAssistance. // // // @0050e248-era global: the status / MFD message sink == *(DAT_004efc94 + 0x38). // void* MechTech::StatusMessageSink() { return 0; // TODO(cross-family): *(theApplication + 0x38) -- HUD status sink } // // Engine current time (binary read this+0x10; the WinTesla clock is Now()). // Time MechTech::GetTime() const { return Now(); } // // FUN_004366b8 (build "status cleared" message) + FUN_004364e4 (dispatch). // void MechTech::ReportStatusCleared( void * /*sink*/, Mech * /*owner*/, MechSubsystem * /*sub*/, int /*type*/ ) { // TODO(cross-family): build + dispatch the cockpit "status cleared" message. } // // FUN_00436688 (build "status set" message, carries alarmModel) + FUN_004364e4. // void MechTech::ReportStatusSet( void * /*sink*/, Mech * /*owner*/, MechSubsystem * /*sub*/, int /*type*/, ResourceDescription::ResourceID /*alarm_model*/ ) { // TODO(cross-family): build + dispatch the cockpit "status set" message. } //===========================================================================// // WAVE 2 factory bridge -- MechTech (factory case 0xBDC, "HeatableSubsystem" // label; the case currently builds the heat base + discards the pointer). //===========================================================================// Subsystem *CreateMechTechSubsystem(Mech *owner, int id, void *seg) { Check(sizeof(MechTech) <= 0x140); return (Subsystem *) new (Memory::Allocate(0x140)) MechTech(owner, id, (MechTech::SubsystemResource *)seg, MechTech::DefaultData); }