//===========================================================================// // File: powersub.cpp // // Project: BattleTech Brick: Entity Manager // // Contents: Powered (electrically driven) subsystems -- voltage routing, // // generators, myomer drive and the low-voltage watchdog // //---------------------------------------------------------------------------// // Date Who Modification // // -------- --- ---------------------------------------------------------- // // --/--/95 ?? Initial coding. // //---------------------------------------------------------------------------// // 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 module cluster @0x4b02f0..@0x4b2678. The only function the // decomp tagged is the PoweredSubsystem ctor @004b0f74 (file=bt/powersub.cpp); // the cluster was recovered via the call graph, the class vtables and the // "d:\tesla_bt\bt\POWERSUB.CPP" assertion strings. Each non-trivial method // cites the originating @ADDR. // // Hex/byte float constants converted to decimal: // 0x3f800000 = 1.0f 0x3f000000 = 0.5f 0xbf800000 = -1.0f // _DAT_004b0bcc = 1.0e-4f _DAT_004b297c = 0.0f // // Helper-function name mapping (engine internals referenced by the decomp; // the heat.* names are carried over from the heat-subsystem reconstruction): // FUN_004adda0 HeatSink::HeatSink (PoweredSubsystem / Generator base ctor) // FUN_004adfd4 HeatSink::~HeatSink // FUN_004ad924 HeatSink::HeatSinkSimulation (base Performance) // FUN_004ad760 HeatSink::ResetToInitialState // FUN_004ad748 HeatSink::HandleMessage (base message dispatch) // FUN_004add30 HeatSink::GetStatusFlags // FUN_004ac868 HeatableSubsystem::~HeatableSubsystem (chained from PowerController dtor) // FUN_004ac530 Subsystem ctor (PowerController base) // FUN_004ac9c8 Subsystem::IsDamaged() (returns True when the part is dead) // FUN_0049fb54 Subsystem::IsDamaged()/IsDead variant used by PowerController // FUN_00417ab4 SharedData::Resolve() (connection -> Subsystem*) // FUN_004179d4 SharedData ctor FUN_004179f8 SharedData dtor FUN_00417a5c SharedData::Clear // FUN_0041b9ec AlarmIndicator(levels) FUN_0041baa4 ~AlarmIndicator // FUN_0041bbd8 AlarmIndicator::SetLevel(n) // FUN_0041a1a4 IsDerivedFrom(classDerivations) // FUN_0040385c Verify()/assert(msg,file,line) // FUN_004dcd00 fabsf() // FUN_004dbb24 DebugStream::operator<<(char*) FUN_004d9c38 DebugStream::endl // FUN_004d4a78 strlen() // FUN_00404088 NotationFile::Read(name,key,char**) // FUN_00404118 NotationFile::Read(name,key,float*) // FUN_004040d8 NotationFile::Read(name,key,int*) // FUN_004215b0 Get_Segment_Index(file,name) // FUN_004ae150 HeatSink::CreateStreamedSubsystem // #include #pragma hdrstop #if !defined(POWERSUB_HPP) # include #endif #if !defined(APP_HPP) # include #endif #if !defined(TESTBT_HPP) # include #endif // // Tuning constants observed as read-only globals in the decomp. // static const Scalar VoltageEpsilon = 1.0e-4f; // _DAT_004b0bcc (short-detect threshold) static const Scalar MinOutputVoltage = 0.0f; // _DAT_004b2158 / _DAT_004b297c static const Scalar MaxOutputVoltage = 1.0f; // _DAT_004b2154 / _DAT_004b2258 static const Scalar RatedVoltageRef = 1.0f; // _DAT_004b1d10 (myomer rated reference) static const Scalar DegradedDriveScale = 0.5f; // _DAT_004b1d14 (heat-degradation throttle) static const Scalar ResourceUnset = -1.0f; // _DAT_004b177c / _DAT_004b1a3c / _DAT_004b2580 static const Scalar MinVoltageScale = 1.0f; // _DAT_004b1924 (PowerWatcher threshold scale) //########################################################################### //########################################################################### // PoweredSubsystem //########################################################################### //########################################################################### //############################################################################# // Attribute Support (gauge data-binding wave) // // InputVoltage -> voltageSource (the 0xC connection to the powering Generator; // the cluster's power-branch gate + the source of GeneratorVoltageConnection). // Chained to HeatSink's dense index; a function-local static so the parent // index is safely constructed on first use regardless of static-init order. // const PoweredSubsystem::IndexEntry PoweredSubsystem::AttributePointers[]= { ATTRIBUTE_ENTRY(PoweredSubsystem, InputVoltage, voltageSource) // @0x1D0 }; PoweredSubsystem::AttributeIndexSet& PoweredSubsystem::GetAttributeIndex() { static PoweredSubsystem::AttributeIndexSet attributeIndex( ELEMENTS(PoweredSubsystem::AttributePointers), PoweredSubsystem::AttributePointers, HeatSink::GetAttributeIndex() ); return attributeIndex; } //############################################################################# // Shared Data Support // PoweredSubsystem::SharedData PoweredSubsystem::DefaultData( PoweredSubsystem::GetClassDerivations(), PoweredSubsystem::GetMessageHandlers(), PoweredSubsystem::GetAttributeIndex(), PoweredSubsystem::StateCount ); Derivation* PoweredSubsystem::GetClassDerivations() { static Derivation classDerivations(HeatSink::GetClassDerivations(), "PoweredSubsystem"); return &classDerivations; } //############################################################################# // Construction / Destruction // // @004b0f74 -- THE tagged powersub.cpp function (size 486). Builds on the // HeatSink base, then resolves and attaches the voltage source named in the // resource and primes the electrical state machine. // PoweredSubsystem::PoweredSubsystem( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data ): HeatSink(owner, subsystem_ID, subsystem_resource, shared_data), // FUN_004adda0 voltageSource(0), // FUN_004b1bb1(this+0x74, 0) electricalStateAlarm(5), // FUN_0041b9ec(this+0x99, 5) modeAlarm(3) // FUN_0041b9ec(this+0xae, 3) { Check(owner); Check_Pointer(subsystem_resource); // // Resolve the "VoltageSource" index to the generator subsystem. // // ⚠ ROOT-CAUSE FIX (sibling of the heat.cpp heap corruption): voltageSourceIndex // indexes the owner's SUBSYSTEM ROSTER, not the skeleton segment table. Raw // PoweredSubsystem ctor (part_013.c:1198): // if (res->voltageSourceIndex /*+0xFC*/ < owner->subsystemCount /*+0x124*/) // source = owner->subsystemArray[voltageSourceIndex] /*+0x128*/; // The earlier GetSegment draft cast an EntitySegment (288B) to Generator*, so // AttachToVoltageSource's `currentTapCount += 1` wrote 136 bytes past that heap // block at every mech spawn. Roster slots are pre-zeroed -> NULL = warn path. Subsystem *source = 0; if (subsystem_resource->voltageSourceIndex >= 0 && subsystem_resource->voltageSourceIndex < owner->GetSubsystemCount()) { source = owner->GetSubsystem(subsystem_resource->voltageSourceIndex); } else { // @004b0f74: "Bad voltageSourceIndex to connect power to!" POWERSUB.CPP:0x264 Verify(False, "Bad voltageSourceIndex to connect power to!", __FILE__, 0x264); } // // A "master" instance (segment flagged 0x100, not a sub-/damaged copy) // runs the per-frame electrical simulation and owns the live link. // // INTEGRATION (gate reconcile): master-gate reads OWNER simulationFlags // (param_2+0x28), the oracle-verified authoritative source — not the per- // segment resource flags (which stream 0 here). See heat.cpp note. if ( (owner->simulationFlags & SegmentCopyMask) == 0 // (owner flags & 0xC) == 0 && (owner->simulationFlags & MasterHeatSinkFlag) != 0 // owner flags & 0x100 ) { SetPerformance(&PoweredSubsystem::PoweredSubsystemSimulation); // this[7..9] = PTR @0050f5b0 if (source == 0) { // @004b0f74: POWERSUB.CPP:0x272 Verify(False, "Master PoweredSubsystem is missing required generator", __FILE__, 0x272); } else { AttachToVoltageSource(source); // FUN_004b0dd8 } } else if ( // gate on OWNER flags, matching raw part_013.c:1216 (param_2+0x28 & 0xc)==4 // (the earlier draft read the resource flags here -- always 0 -> dead branch) (owner->simulationFlags & SegmentCopyMask) == 4 && source != 0 ) { AttachToVoltageSource(source); } thermalResistivityCoefficient = subsystem_resource->thermalResistivityCoefficient; // +0x100 startTime = subsystem_resource->startTime; // +0x18C startTimer = startTime; electricalStateAlarm.SetLevel(Ready); // FUN_0041bbd8(this+0x99, 4) modeAlarm.SetLevel(Connected); // FUN_0041bbd8(this+0xae, 1) voltageScale = 1.0f; auxScreenNumber = subsystem_resource->auxScreenNumber; // +0x104 auxScreenPlacement = subsystem_resource->auxScreenPlacement; // +0x108 strcpy(auxScreenLabel, subsystem_resource->auxScreenLabel); // inline copy from +0x10C strcpy(engScreenLabel, subsystem_resource->engScreenLabel); // inline copy from +0x14C Check_Fpu(); } // // @004b115c -- tears down the alarms and the voltage-source connection, then // chains to ~HeatSink. // PoweredSubsystem::~PoweredSubsystem() { Check(this); // modeAlarm (@0xae) / electricalStateAlarm (@0x99) / voltageSource (@0x74) // are released here; FUN_004adfd4 finishes the HeatSink chain. Check_Fpu(); } //########################################################################### // TestClass / TestInstance -- PoweredSubsystem // Logical PoweredSubsystem::TestClass(Mech &) { return True; } Logical PoweredSubsystem::TestInstance() const // @004b1208 { return IsDerivedFrom(*GetClassDerivations()); // FUN_0041a1a4(**this[3], 0x50f4bc) } //############################################################################# // Per-frame simulation // // // @004b0bd0 -- registered Performance for a master PoweredSubsystem. Runs the // HeatSink simulation, then advances the electrical state machine from the // state of the powering generator and (in AutoConnect mode) hunts for a // replacement generator when the link is lost. // void PoweredSubsystem::PoweredSubsystemSimulation(Scalar time_slice) { HeatSink::HeatSinkSimulation(time_slice); // FUN_004ad924 Generator *source = (Generator *)voltageSource.Resolve(); // FUN_00417ab4(this+0x74) if (source == 0) { electricalStateAlarm.SetLevel(NoVoltage); } else { if (source->GeneratorStateOf() == 3) // source+0x210 == 3 (shorted) { electricalStateAlarm.SetLevel(Shorted); } if (source->GeneratorStateOf() == 1 || source->GeneratorStateOf() == 4) { electricalStateAlarm.SetLevel(GeneratorOff); } } switch (electricalStateAlarm.GetLevel()) // this[0x9e] @0x278 { case Starting: startTimer += time_slice; if (startTime <= startTimer) { electricalStateAlarm.SetLevel(Ready); } break; case NoVoltage: if (source != 0) { electricalStateAlarm.SetLevel(Starting); startTimer = 0.0f; } break; case Shorted: case GeneratorOff: if (source != 0 && source->GeneratorStateOf() == 2) // generator back to Ready { electricalStateAlarm.SetLevel(Starting); startTimer = 0.0f; } break; } // // AutoConnect: if the mode alarm is in the auto state and we are not // damaged and currently unpowered, scan every GeneratorClassID segment in // the mech and attach to the first one that will accept a tap. // if ( modeAlarm.GetLevel() == AutoConnect // this[0xb3] @0x2cc == 2 && GetStatusFlags() == 0 // (*this[0x40])(this,0) == 0 ) { Mech *mech = (Mech *)owner; for (int i = 0; ; ++i) // owner+0x124 segment scan { EntitySegment *segment_node = mech->GetSegment(i); // owner+0x128[i] if (segment_node == 0) { break; } Subsystem *segment = (Subsystem *)segment_node; if ( segment->GetClassID() == RegisteredClass::GeneratorClassID // segment+4 == 0xbc1 && GetStatusFlags() != 0 && AttachToVoltageSource(segment) != -1 // FUN_004b0dd8 ) { break; } } } Check_Fpu(); } //############################################################################# // Subsystem virtual overrides // // // @004b0e6c -- powered/unpowered transition. When power is cut the electrical // alarm goes idle; when restored it returns to "GeneratorOff" with the start // timer reset, but only while the source is actually Ready (state 2). // void PoweredSubsystem::ResetToInitialState(Logical powered) { HeatSink::ResetToInitialState(True); // FUN_004ad760 Generator *source = (Generator *)voltageSource.Resolve(); // FUN_00417ab4(this+0x74) if (!powered) { if (source != 0) { electricalStateAlarm.SetLevel(NoVoltage); // SetLevel 0 in decomp -> Starting/idle startTimer = 0.0f; // this[0xc6] = 0 } } else { if (source != 0 && source->GeneratorStateOf() == 2) { electricalStateAlarm.SetLevel(Ready); // SetLevel 4 startTimer = startTime; // this[0xc6] = this[0xc5] } } } // // @004b0efc -- pass the message to the HeatSink base; if the part is alive, // the source short flag is set and message 4 arrives, force a short-recovery. // Logical PoweredSubsystem::HandleMessage(int message) { HeatSink::HandleMessage(message); // FUN_004ad748 // Raw @004b0efc reads *(int*)(*(owner+0x190) + 0x25c) -- the message-manager's // short-event flag (NOT a Generator field). The manager is null in bring-up // (BT_GetMessageManager stub), so guard the deref; the real read resumes once // the SubsystemMessageManager (0xBD3) roster lookup is wired. SubsystemMessageManager *msgMgr = BT_GetMessageManager((Mech *)owner); // *(this[0x34]+0x190) if (!IsDamaged() // FUN_004ac9c8 == 0 && msgMgr != 0 && *(LWord *)((char *)msgMgr + 0x25c) != 0 // message-manager short-event flag && message == 4) { ForceShortRecovery(); // FUN_004b11bc(this) } return True; } // // @004b11bc -- on a short event, if neither this part nor its live source is // damaged, drive the source's alarm to "shorted" (3) and clear its output. // void PoweredSubsystem::ForceShortRecovery() // internal helper for @004b0efc { if (IsDamaged()) // FUN_004ac9c8 return; Generator *source = (Generator *)voltageSource.Resolve(); // FUN_00417ab4(this+0x1d0) if (source != 0 && !source->IsDamaged()) { source->stateAlarm.SetLevel(Generator::GeneratorShorted); // source+0x1fc -> 3 source->outputVoltage = 0.0f; // source+0x1f8 = 0 } } // // @004b0f48 -- base HeatSink flags plus 0x40 when this part has no usable // voltage (the slot-0x40 virtual returns 0). // LWord PoweredSubsystem::GetStatusFlags() { LWord flags = HeatSink::GetStatusFlags(); // FUN_004add30 if (!HasVoltage()) // virtual "has voltage" query, slot @vtable+0x40 { flags |= 0x40; } return flags; } // // Virtual "has usable voltage" query (vtable+0x40). Base powered subsystem // reports voltage present whenever the electrical state machine is Ready. // Logical PoweredSubsystem::HasVoltage() { return (electricalStateAlarm.GetLevel() == Ready) ? True : False; } // // @004b1224 -- prints " = ". // void PoweredSubsystem::PrintState() { HeatSink::PrintState(); // FUN_004ae050 switch (electricalStateAlarm.GetLevel()) // this+0x278 { case Starting: DebugStream << GetName() << " = Starting" << endl; break; case NoVoltage: DebugStream << GetName() << " = NoVoltage" << endl; break; case Shorted: DebugStream << GetName() << " = Shorted" << endl; break; case GeneratorOff: DebugStream << GetName() << " = GeneratorOff" << endl; break; case Ready: DebugStream << GetName() << " = Ready" << endl; break; default: DebugStream << GetName() << " Unknown Electrical State!" << endl; break; } } //############################################################################# // Voltage-source linkage helpers // // // @004b0dd8 -- claim a tap on 'source' if it has a spare one, drop any current // link, then bind the connection object. Returns the new tap index, -1 on // failure (source already fully loaded). // int PoweredSubsystem::AttachToVoltageSource(Subsystem *source) { int tap; Generator *gen = (Generator *)source; // the voltage source is a Generator if (gen->currentTapCount < gen->maxTapCount) // source+0x1e8 < source+0x1e4 { gen->currentTapCount += 1; tap = gen->currentTapCount; } else { tap = -1; } if (tap >= 0) { DetachFromVoltageSource(); // FUN_004b0e30 voltageSource.Add(source); // (**(this[0x74]+4))(this+0x74, source) } return tap; } // // @004b0e30 -- release our tap on the current source and raise the // "disconnected" mode alarm. // void PoweredSubsystem::DetachFromVoltageSource() { Generator *source = (Generator *)voltageSource.Resolve(); // FUN_00417ab4(this+0x1d0) if (source != 0) { source->currentTapCount -= 1; // source+0x1e8 -= 1 voltageSource.Clear(); // FUN_00417a5c(this+0x1d0) } electricalStateAlarm.SetLevel(NoVoltage); // FUN_0041bbd8(this+0x264, 1) -- 0x264 IS electricalStateAlarm (was wrongly modeAlarm@0x2B8) } // // @004b0b5c -- True when the (resolved or supplied) source is in the shorted // state (state 2 in this query) and its measured voltage exceeds the short // threshold. // Logical PoweredSubsystem::IsSourceShorted(Subsystem *source) { if (source == 0) { source = voltageSource.Resolve(); // FUN_00417ab4(this+0x1d0) } Generator *gen = (Generator *)source; if ( gen != 0 && gen->GeneratorStateOf() == 2 // source+0x210 == 2 && fabsf(gen->MeasuredVoltage() - VoltageEpsilon) > VoltageEpsilon // source+0x1dc ) { return True; } return False; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // CreateStreamedSubsystem -- PoweredSubsystem (@004b13ac) // // Parses the power-specific resource fields on top of the HeatSink record. // int PoweredSubsystem::CreateStreamedSubsystem( NotationFile *model_file, const char *model_name, const char *subsystem_name, SubsystemResource *subsystem_resource, NotationFile *subsystem_file, const ResourceDirectories *directories, int passes ) { if ( !HeatSink::CreateStreamedSubsystem( // FUN_004ae150 model_file, model_name, subsystem_name, subsystem_resource, subsystem_file, directories, passes ) ) { return False; } subsystem_resource->subsystemModelSize = 0x190; // resource+0x24 subsystem_resource->classID = RegisteredClass::PoweredSubsystemClassID; // 0x0bc2, resource+0x20 if (passes == 1) { // first pass: prime fields to "unset" subsystem_resource->voltageSourceIndex = -1; subsystem_resource->thermalResistivityCoefficient = ResourceUnset; // -1.0f subsystem_resource->auxScreenPlacement = -1; memset(subsystem_resource->auxScreenLabel, 0, 64); // resource+0x10C memset(subsystem_resource->engScreenLabel, 0, 64); // resource+0x14C subsystem_resource->startTime = ResourceUnset; } // // "VoltageSource" -- segment name; resolve to a segment index (biased +2). // const char *voltageSourceName = "Unspecified"; int found = model_file->GetEntry(subsystem_name, "VoltageSource", &voltageSourceName); if (!found && subsystem_resource->voltageSourceIndex == -1) { DebugStream << subsystem_name << " missing VoltageSource!"; return False; } if (strcmp(voltageSourceName, "Unspecified") != 0) { subsystem_resource->voltageSourceIndex = Get_Segment_Index(model_file, model_name, directories, voltageSourceName); // FUN_004215b0 } if (subsystem_resource->voltageSourceIndex < 0) { DebugStream << subsystem_name << " has an invalid voltage source!"; return False; } if (strcmp(voltageSourceName, "Unspecified") != 0) { subsystem_resource->voltageSourceIndex += 2; } if ( !model_file->GetEntry(subsystem_name, "ThermalResistivityCoefficient", &subsystem_resource->thermalResistivityCoefficient) && subsystem_resource->thermalResistivityCoefficient == ResourceUnset ) { DebugStream << subsystem_name << " missing ThermalResistivityCoefficient!"; return False; } if ( !model_file->GetEntry(subsystem_name, "AuxScreenPlacement", &subsystem_resource->auxScreenPlacement) && subsystem_resource->auxScreenPlacement == -1 ) { subsystem_resource->auxScreenPlacement = -1; // left unset (optional) } if ( !model_file->GetEntry(subsystem_name, "AuxScreenNumber", &subsystem_resource->auxScreenNumber) && subsystem_resource->auxScreenNumber == -1 ) { DebugStream << subsystem_name << " missing AuxScreenNumber!"; return False; } const char *label; if (model_file->GetEntry(subsystem_name, "AuxScreenLabel", &label)) { strcpy(subsystem_resource->auxScreenLabel, label); } else if (subsystem_resource->auxScreenLabel[0] == '\0') { DebugStream << subsystem_name << " missing AuxScreenLabel!"; return False; } if (model_file->GetEntry(subsystem_name, "EngScreenLabel", &label)) { strcpy(subsystem_resource->engScreenLabel, label); } else if (subsystem_resource->engScreenLabel[0] == '\0') { DebugStream << subsystem_name << " missing EngScreenLabel!"; return False; } if ( !model_file->GetEntry(subsystem_name, "StartTime", &subsystem_resource->startTime) && subsystem_resource->startTime == ResourceUnset ) { DebugStream << subsystem_name << " missing StartTime!"; return False; } Check_Fpu(); return True; } //########################################################################### //########################################################################### // Myomers //########################################################################### //########################################################################### //############################################################################# // Shared Data Support // Myomers::SharedData Myomers::DefaultData( Myomers::GetClassDerivations(), Myomers::GetMessageHandlers(), Myomers::GetAttributeIndex(), Myomers::StateCount ); Derivation* Myomers::GetClassDerivations() { static Derivation classDerivations(PoweredSubsystem::GetClassDerivations(), "Myomers"); return &classDerivations; } // // @004b1d18 -- master myomer bundle registers a separate Performance and a // status-flag bit (0x8); copies skip the live simulation. // Myomers::Myomers( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data ): PoweredSubsystem(owner, subsystem_ID, subsystem_resource, /*shared_data=*/DefaultData) // FUN_004b0f74(..,&DAT_0050fa1c,0,0) { outputVoltage = 1.0f; // this[0xc7] powered = 0; // this[0xc8] voltageAvailable = 0; // this[0xc9] simulationFlags |= 0x8; // this[0xa] |= 8 if ((subsystem_resource->subsystemFlags & SegmentCopyMask) != 4) // (flags & 0xC) != 4 { SetPerformance((Performance)&Myomers::MyomersSimulation); // PTR @0050fa94 } } // // @004b1d90 // Myomers::~Myomers() { Check(this); BT_ClearMyomers((Mech *)owner); // *(this[0x34]+0x374) = 0 Check_Fpu(); } Logical Myomers::TestClass(Mech &) { return True; } Logical Myomers::TestInstance() const // @004b1e18 { return IsDerivedFrom(*GetClassDerivations()); // FUN_0041a1a4(..,0x50fa2c) } // // @004b1c4c -- compute the actuator drive voltage available to the locomotion // model from the powering generator, throttled by heat damage and gated by // the electrical state. // void Myomers::MyomersSimulation(Scalar time_slice) { PoweredSubsystem::PoweredSubsystemSimulation(time_slice); // FUN_004b0bd0 Generator *source = (Generator *)voltageSource.Resolve(); // this[0x38] linked source outputVoltage = RatedVoltageRef - source->MeasuredVoltage(); // 1.0 - *(source+0x158) if (simulationState == 1) // this[0x10] @0x40 { outputVoltage = 0.0f; powered = 0; } else { powered = 1; } if (electricalStateAlarm.GetLevel() == Ready) // this[0x9e] == 4 { voltageAvailable = 0; } else { voltageAvailable = 1; outputVoltage = 0.0f; } // // Heat damage (HeatSink heat-state @0x184) throttles the drive. // switch (heatAlarm.GetLevel()) // this+0x184 (heat-state inside heatAlarm@0x170+0x14) { case HeatSink::NormalHeat: // 0 powered = 1; break; case HeatSink::DegradationHeat: // 1 outputVoltage *= DegradedDriveScale; // *= 0.5 powered = 1; break; case HeatSink::FailureHeat: // 2 outputVoltage = 0.0f; powered = 0; break; } } // // @004b1c18 -- reset clears the myomer output state, then chains to the base. // void Myomers::ResetToInitialState(Logical powered_flag) { if (powered_flag) { outputVoltage = 1.0f; // this[0xc7] powered = 0; // this[0xc8] voltageAvailable = 0; // this[0xc9] } PoweredSubsystem::ResetToInitialState(powered_flag); // FUN_004b0e6c } // // @004b1bfc -- straight delegation to the base. // Logical Myomers::HandleMessage(int message) { return PoweredSubsystem::HandleMessage(message); // FUN_004b0efc } // // @004b1dcc -- no extra resource fields; just tags the record as Myomers. // int Myomers::CreateStreamedSubsystem( NotationFile *model_file, const char *model_name, const char *subsystem_name, SubsystemResource *subsystem_resource, NotationFile *subsystem_file, const ResourceDirectories *directories, int passes ) { if ( !PoweredSubsystem::CreateStreamedSubsystem( // FUN_004b13ac model_file, model_name, subsystem_name, subsystem_resource, subsystem_file, directories, passes ) ) { return False; } subsystem_resource->subsystemModelSize = 0x190; subsystem_resource->classID = RegisteredClass::MyomersClassID; // 0x0bc3, resource+0x20 return True; } //########################################################################### //########################################################################### // Generator //########################################################################### //########################################################################### //############################################################################# // Shared Data Support // Generator::SharedData Generator::DefaultData( Generator::GetClassDerivations(), Generator::GetMessageHandlers(), Generator::GetAttributeIndex(), Generator::StateCount ); Derivation* Generator::GetClassDerivations() { static Derivation classDerivations(HeatSink::GetClassDerivations(), "Generator"); return &classDerivations; } // // @004b225c -- a generator is itself a HeatSink (it gets hot producing power). // It starts on-line at rated voltage; the trailing letter of the segment name // ("GeneratorA".."GeneratorD") becomes the generator number. // Generator::Generator( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data ): HeatSink(owner, subsystem_ID, subsystem_resource, /*shared_data=*/DefaultData), // FUN_004adda0(..,&DAT_0050fb50,0,0) stateAlarm(5) // FUN_0041b9ec(this+0x7f, 5) { if ( (owner->simulationFlags & SegmentCopyMask) == 0 // owner+0x28 (gate reconcile) && (owner->simulationFlags & MasterHeatSinkFlag) != 0 ) { SetPerformance(&Generator::GeneratorSimulation); // PTR @0050fc14 } ratedVoltage = subsystem_resource->ratedVoltage; // resource+0xFC outputVoltage = ratedVoltage; maxTapCount = subsystem_resource->maxTapCount; // resource+0x100 currentTapCount = 0; percentVoltageAvailable = 1.0f; startTime = subsystem_resource->startTime; // resource+0x104 startTimer = startTime; stateAlarm.SetLevel(GeneratorReady); // SetLevel 2 generatorOn = 1; shortRecoveryTime = subsystem_resource->shortRecoveryTime; // resource+0x108 shortTimer = shortRecoveryTime; simulationFlags |= 0x8; // this[0xa] |= 8 // last character of the segment name -> generator number ('A'==0x41 -> 1) const char *name = GetName(); // this[0x35] generatorNumber = name[strlen(name) - 1] - 0x40; // FUN_004d4a78 Check_Fpu(); } // // @004b237c // Generator::~Generator() { Check(this); // stateAlarm (@0x7f) released here; FUN_004adfd4 finishes the HeatSink chain. Check_Fpu(); } Logical Generator::TestClass(Mech &) { return True; } Logical Generator::TestInstance() const { return IsDerivedFrom(*GetClassDerivations()); } // // Generator vtable slot 10 -- recovered VERBATIM from the surviving GNRATOR.TCP // fragment (the one Generator method the @0x4b02f0.. cluster decomp omitted; see // gnrator.cpp). Chains the HeatableSubsystem reset (NOT the HeatSink one -- // matching the inlined base chain seen across the HeatSink family) and forces // the generator output cold. // void Generator::ResetToInitialState(Logical /*powered*/) { HeatableSubsystem::ResetToInitialState(True); outputVoltage = 0.0f; } // // @004b1f7c -- generator state machine: spin-up, short recovery, and the // resulting percent-of-rated voltage made available to attached loads. // void Generator::GeneratorSimulation(Scalar time_slice) { HeatSink::HeatSinkSimulation(time_slice); // FUN_004ad924 if (simulationState == 1) // this[0x10] @0x40 { stateAlarm.SetLevel(GeneratorIdle); // SetLevel 1 } if (heatAlarm.GetLevel() == HeatSink::FailureHeat) // this+0x184 == 2 { stateAlarm.SetLevel(GeneratorRecovered); // SetLevel 4 } switch (stateAlarm.GetLevel()) // this[0x84] @0x210 { case GeneratorStarting: // 0 outputVoltage = 0.0f; startTimer += time_slice; if (startTime <= startTimer) { stateAlarm.SetLevel(GeneratorReady); // 2 outputVoltage = (MaxOutputVoltage - SourceLevel()) * ratedVoltage; // (1.0 - *(this[0x38]+0x158)) * this[0x76] } break; case GeneratorIdle: // 1 outputVoltage = 0.0f; break; case GeneratorShorted: // 3 outputVoltage = 0.0f; shortTimer += time_slice; if (shortRecoveryTime <= shortTimer) { stateAlarm.SetLevel(GeneratorReady); // 2 outputVoltage = (MaxOutputVoltage - SourceLevel()) * ratedVoltage; } break; case GeneratorRecovered: // 4 outputVoltage = 0.0f; if (heatAlarm.GetLevel() == HeatSink::NormalHeat) // this+0x184 == 0 { if (generatorOn == 0) // this[0x75] { stateAlarm.SetLevel(GeneratorIdle); // 1 } else { startTimer = 0.0f; stateAlarm.SetLevel(GeneratorStarting); // 0 } } break; } // // percent of rated voltage actually available, clamped to [0, 1]. // percentVoltageAvailable = outputVoltage / ratedVoltage; // this[0x74] if (percentVoltageAvailable < MinOutputVoltage) { percentVoltageAvailable = MinOutputVoltage; // 0.0 } else if (percentVoltageAvailable > MaxOutputVoltage) { percentVoltageAvailable = MaxOutputVoltage; // 1.0 } } // // @004b21d0 -- when the mech's electrical bus is live: message 4 with the // generator damaged/off drives it into the shorted-recovery state; any other // message recomputes the output from the source level. // Logical Generator::HandleMessage(int message) { HeatSink::HandleMessage(message); // FUN_004ad748 Mech *mech = (Mech *)owner; if (BT_IsBusLive(mech)) // *(*(this[0x34]+0x190)+0x274) != 0 { if (message == 4) { if ((simulationState == 2 || simulationState == 0) && !IsDamaged()) // this[0x10]; FUN_004ac9c8 { stateAlarm.SetLevel(GeneratorShorted); // 3 shortTimer = 0.0f; // this[0x7e] } } else { outputVoltage = (MaxOutputVoltage - SourceLevel()) * ratedVoltage; } } return True; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // CreateStreamedSubsystem -- Generator (@004b23bc) // int Generator::CreateStreamedSubsystem( NotationFile *model_file, const char *model_name, const char *subsystem_name, SubsystemResource *subsystem_resource, NotationFile *subsystem_file, const ResourceDirectories *directories, int passes ) { if ( !HeatSink::CreateStreamedSubsystem( // FUN_004ae150 model_file, model_name, subsystem_name, subsystem_resource, subsystem_file, directories, passes ) ) { return False; } subsystem_resource->subsystemModelSize = 0x10c; subsystem_resource->classID = RegisteredClass::GeneratorClassID; // 0x0bc1, resource+0x20 if (passes == 1) { subsystem_resource->ratedVoltage = ResourceUnset; // -1.0f subsystem_resource->startTime = ResourceUnset; subsystem_resource->shortRecoveryTime = ResourceUnset; } if ( !model_file->GetEntry(subsystem_name, "RatedVoltage", &subsystem_resource->ratedVoltage) && subsystem_resource->ratedVoltage == ResourceUnset ) { DebugStream << subsystem_name << " missing RatedVoltage!"; return False; } if ( !model_file->GetEntry(subsystem_name, "MaxTapCount", &subsystem_resource->maxTapCount) && subsystem_resource->maxTapCount == -1 ) { DebugStream << subsystem_name << " missing MaxTapCount!"; return False; } if ( !model_file->GetEntry(subsystem_name, "StartTime", &subsystem_resource->startTime) && subsystem_resource->startTime == ResourceUnset ) { DebugStream << subsystem_name << " missing StartTime!"; return False; } if ( !model_file->GetEntry(subsystem_name, "ShortRecoveryTime", &subsystem_resource->shortRecoveryTime) && subsystem_resource->shortRecoveryTime == ResourceUnset ) { DebugStream << subsystem_name << " missing ShortRecoveryTime!"; return False; } Check_Fpu(); return True; } //########################################################################### //########################################################################### // PowerWatcher (BEST-EFFORT) //########################################################################### //########################################################################### // // NOTE: PowerWatcher's base class lies just below the captured decomp window // (base ctor @004aeb40, base streamer @004aec54, base message/sim @004aea84 / // @004aea9c). Only the power-specific override bodies were recovered; the // base relationship is reconstructed and flagged best-effort. // PowerWatcher::SharedData PowerWatcher::DefaultData( PowerWatcher::GetClassDerivations(), PowerWatcher::GetMessageHandlers(), PowerWatcher::GetAttributeIndex(), PowerWatcher::StateCount ); Derivation* PowerWatcher::GetClassDerivations() { // Real base ctor is @004aeb40 (HeatWatcher); HeatableSubsystem stands in. static Derivation classDerivations(HeatableSubsystem::GetClassDerivations(), "PowerWatcher"); return &classDerivations; } // // @004b18a4 -- threshold = scale * resource->minVoltagePercent. // PowerWatcher::PowerWatcher( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data ): HeatWatcher(owner, subsystem_ID, subsystem_resource, shared_data), // FUN_004aeb40 -- HeatWatcher base watchdogAlarm(5) // FUN_0041b9ec(this+0x61, 5) { minVoltage = MinVoltageScale * subsystem_resource->minVoltagePercent; // _DAT_004b1924 * (resource+0xf0) if ( (owner->simulationFlags & SegmentCopyMask) == 0 // owner+0x28 (gate reconcile) && (owner->simulationFlags & MasterHeatSinkFlag) != 0 ) { SetPerformance(&PowerWatcher::Simulation); // PTR @0050f5fc } } // // @004b1930 // PowerWatcher::~PowerWatcher() { Check(this); Check_Fpu(); } Logical PowerWatcher::TestClass(Mech &) { return True; } Logical PowerWatcher::TestInstance() const { return IsDerivedFrom(*GetClassDerivations()); } // @004b1970 // // @004b179c -- on message 4, if the watched source's source is shorted (and // alive), force the short-recovery alarm; then chain to the base handler. // Logical PowerWatcher::HandleMessage(int message) { if (message == 4) { Subsystem *watched = watchedLink.Resolve(); // FUN_00417ab4(this+0x45) Generator *source = (watched != 0) // FUN_00417ab4(watched+0x1d0) ? (Generator *)((PoweredSubsystem *)watched)->ResolveVoltageSource() : 0; if (source != 0 && !source->IsDamaged()) { source->stateAlarm.SetLevel(Generator::GeneratorShorted); // source+0x1fc -> 3 source->outputVoltage = 0.0f; // source+0x1f8 = 0 } } return MechSubsystem::HandleMessage(message); // FUN_004aea84 -> FUN_004ac0bc = MechSubsystem::HandleMessage } // // @004b1804 -- delegate to the base simulation. // void PowerWatcher::Simulation(Scalar time_slice) { HeatWatcher::ResetToInitialState(True); // FUN_004aea9c = HeatWatcher::ResetToInitialState (void)time_slice; } // // @004b198c -- reads "MinVoltagePercent". // int PowerWatcher::CreateStreamedSubsystem( NotationFile *model_file, const char *model_name, const char *subsystem_name, SubsystemResource *subsystem_resource, NotationFile *subsystem_file, const ResourceDirectories *directories, int passes ) { if ( !HeatWatcher::CreateStreamedSubsystem( // FUN_004aec54 -- HeatWatcher parse model_file, model_name, subsystem_name, subsystem_resource, subsystem_file, directories, passes ) ) { return False; } subsystem_resource->subsystemModelSize = 0xf4; subsystem_resource->classID = RegisteredClass::PowerWatcherClassID; // resource+0x20 if (passes == 1) { subsystem_resource->minVoltagePercent = ResourceUnset; // -1.0f } if ( !model_file->GetEntry(subsystem_name, "MinVoltagePercent", &subsystem_resource->minVoltagePercent) && subsystem_resource->minVoltagePercent == ResourceUnset ) { DebugStream << subsystem_name << " missing MinVoltagePercent!"; return False; } return True; } //########################################################################### //########################################################################### // VoltageSourceConnection //########################################################################### //########################################################################### // // Small SharedData specialisation that links a PoweredSubsystem to its // Generator segment (vtable @0050f994). // // // @004b1bb1 // VoltageSourceConnection::VoltageSourceConnection(int initial): SubsystemConnection(initial) // FUN_004179d4 { // *this = &PTR_FUN_0050f994 } // // @004b1bd0 // VoltageSourceConnection::~VoltageSourceConnection() { // *this = &PTR_FUN_0050f994; FUN_004179f8(this, 0) } //===========================================================================// // WAVE 3a factory bridges -- the power bus (Generator 0xBC1, PoweredSubsystem // 0xBC2). Energy weapons resolve a real VoltageSource off these to charge. //===========================================================================// Subsystem *CreateGeneratorSubsystem(Mech *owner, int id, void *seg) { Check(sizeof(Generator) <= 0x250); return (Subsystem *) new (Memory::Allocate(0x250)) Generator(owner, id, (Generator::SubsystemResource *)seg, Generator::DefaultData); } Subsystem *CreatePoweredSubsystem(Mech *owner, int id, void *seg) { Check(sizeof(PoweredSubsystem) <= 0x31c); return (Subsystem *) new (Memory::Allocate(0x31c)) PoweredSubsystem(owner, id, (PoweredSubsystem::SubsystemResource *)seg, PoweredSubsystem::DefaultData); } // // Bridge for the vehicleSubSystems gauge factory (btl4gau2.cpp). The engineering- // screen assignment lives on PoweredSubsystem (auxScreenNumber / auxScreenPlacement // / auxScreenLabel, resource +0x104/+0x108/+0x10C). Reading it from btl4gau2.cpp // via a raw offset (sub+0x1dc) is wrong because the reconstructed heat-leaf branch // is not byte-exact -- so the gauge factory calls this bridge, which casts through // the real PoweredSubsystem type and returns the NAMED fields. Returns False for // non-PoweredSubsystem-derived subsystems (== the FUN_0041a1a4 / 0x50f4bc type // filter the vehicleSubSystems Make applies before dispatch). // // FUN_0041a1a4(sub->classDerivations, 0x50f4bc) -- is the subsystem PoweredSubsystem- // derived? (The vehicleSubSystems type filter + the generator stateLamp gate.) bool BTIsPoweredSubsystem(Subsystem *sub) { return sub != NULL && sub->IsDerivedFrom(*PoweredSubsystem::GetClassDerivations()); } bool BTGetSubsystemAuxScreen(Subsystem *sub, int *screen, int *placement, char *label64) { if (!BTIsPoweredSubsystem(sub)) // FUN_0041a1a4(...,0x50f4bc) return false; PoweredSubsystem *ps = (PoweredSubsystem *)sub; if (screen != NULL) *screen = ps->auxScreenNumber; // +0x104 if (placement != NULL) *placement = ps->auxScreenPlacement; // +0x108 if (label64 != NULL) strcpy(label64, ps->auxScreenLabel); // +0x10C return true; }