//===========================================================================// // File: heat.hpp // // Project: BattleTech Brick: Entity Manager // // Contents: Heatable subsystems -- temperature model, heat sinks, condenser // //---------------------------------------------------------------------------// // Date Who Modification // // -------- --- ---------------------------------------------------------- // // --/--/95 ?? Initial coding. // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved // // PROPRIETARY AND CONFIDENTIAL // //===========================================================================// // // RECONSTRUCTED from the shipped binary (Ghidra pseudo-C in heat_cluster.c) // cross-referenced with the surviving HEAT.TCP fragment and the MUNGA // SUBSYSTM.HPP base interface. See heat.cpp for per-method @ADDR evidence. // // NOTE on the engine base: the WinTesla/MUNGA `Subsystem` (subsystm.h) is a // thin damageable-simulation base -- it does NOT carry the heat/power virtual // surface (ResetToInitialState / GetStatusFlags / HandleMessage / PrintState / // Simulation) that the BT game layer assumed lived on "Subsystem". Because // this family OWNS the subsystem base classes that the other BT families // derive from, that virtual surface is (re)introduced on HeatableSubsystem // here, and the per-class shared-data boilerplate follows the real engine // GetClassDerivations()/GetMessageHandlers()/GetAttributeIndex()/StateCount // idiom (cf. RP/VTVSUB.cpp), not the 3-arg form the raw decomp guessed. // #if !defined(HEAT_HPP) # define HEAT_HPP #if !defined(SUBSYSTM_HPP) # include #endif #if !defined(MECHSUB_HPP) # include // MechSubsystem -- HeatableSubsystem's base (re-base) #endif #include // AffineMatrix #include // AverageOf #include // Scalar #include // GaugeAlarm #include // SChainOf (GaugeAlarm54 watcher sockets) class Component; // fwd -- AddAudioWatcher(Component*) defined in heat.cpp #include #include //##################### Reconstruction type aliases ####################### #if !defined(BT_RECON_TYPE_ALIASES) # define BT_RECON_TYPE_ALIASES typedef AffineMatrix Matrix34; // 3x4 affine (AFFNMTRX.h) typedef GaugeAlarm AlarmIndicator; // GAUGALRM.h typedef AverageOf FilteredScalar; // 15-sample running average (AVERAGE.h) #endif // NOTE: `DebugStream` and `endl` (the ReconStream trace artifact) come from the // shared foundation header (mechrecon.hpp, pulled in via bt.hpp/mech.hpp). //##################### Segment flag bits (model resource) ############### // The "is this a master / damaged-copy segment" test. The decomp rendered // these as owner->GetSegmentFlags() reads; the flags actually live in the // streamed subsystem resource (subsystemFlags). #if !defined(BT_SEGMENT_FLAG_BITS) # define BT_SEGMENT_FLAG_BITS enum { SegmentCopyMask = 0x0C, // (flags & 0xC): 0 == master, 4 == copy MasterHeatSinkFlag = 0x100 // flags & 0x100 == participates in sim }; #endif //##################### Forward Class Declarations ####################### class Mech; //########################################################################### //################# Reconstruction helper value types ################### //########################################################################### // // The raw decomp leaned on three engine helpers whose real APIs differ from // what the pseudo-C assumed. Rather than bend the engine types, the family // owns small faithful wrappers that expose exactly the surface the recovered // bodies call. // // The binary AlarmIndicator (ctor FUN_0041b9ec) is a 0x54-byte object -- base // GaugeAlarm + three sub-indicators, level at +0x04. Every subsystem alarm the // binary builds via FUN_0041b9ec (heatAlarm, electricalState/mode/state/weapon/ // condenser/reservoir alarms) is this size; modeling it as 8 bytes is what slid the // whole heat-leaf branch 0x4C short. Access is ALWAYS through the named API // (SetLevel/GetLevel), never a raw internal offset, so the interior padding is // immaterial -- only sizeof must be 0x54. This is the SAME layout as the Watcher // branch's WatcherGaugeAlarm (heatfamily_reslice.hpp now typedefs to it), so the // already-locked Watcher LayoutChecks stay valid. class GaugeAlarm54 { public: GaugeAlarm54(int levels = 0) : audioWatcherSocket(0), videoWatcherSocket(0), gaugeWatcherSocket(0) { levelA = levelB = levels; level = 0; } void Initialize(int levels) { levelA = levelB = levels; level = 0; } // StateIndicator::SetState semantics: oldState (levelB@0x10) := currentState // BEFORE the change, so the audio watcher's StateChanged(oldState,newState) is // correct -- this is what the inverse/STOP AudioStateTriggers key on (old_state == // firing/charging state) to StopNote a looping charge/sustain/loading sample when // the weapon leaves that state. (In the binary levelB@0x10 IS oldState; the // weapon's LevelCountB() reading @0x10 is that same authentic oldState.) void SetLevel(int n) { levelB = level; if (n != level) { level = n; NotifyWatchers(); } } int GetLevel() const { return level; } int Level() const { return level; } int LevelCountB() const { return levelB; } // +0x10 (the weapon update-record "reset" source) // ReconAlarm/AlarmIndicator API aliases (callers that predate the retype use these): void SetState(unsigned n){ SetLevel((int)n); } unsigned GetState() const { return (unsigned)level; } // The binary's 0x54 alarm (FUN_0041b9ec) IS a StateIndicator: its "three // sub-indicators" @0x18/0x2c/0x40 are the audio/video/gauge watcher sockets, // and level@0x14 is currentState. So an AudioStateWatcher can bind to any // subsystem alarm BY NAME (GeneratorState/CondenserState/ReservoirState/...) and // AddAudioWatcher registers on the +0x18 socket; SetLevel fires it on change. // (Defined out-of-line in heat.cpp so this header stays free of Component/iterator.) void AddAudioWatcher(Component *watcher); void AddVideoWatcher(Component *watcher); void AddGaugeWatcher(Component *watcher); private: void NotifyWatchers(); // fire audio/video/gauge watchers (empty sockets == no-op) protected: // Interior mirrors FUN_0041b9ec / StateIndicator: base header (+0x00, a Node in // the binary), three state words @+0x0c/+0x10/+0x14 (== stateCount/oldState/ // currentState), three watcher sockets @+0x18/+0x2c/+0x40. level@+0x14 is the // STATUS level the binary reads (HeatSink+0x184 == alarm+0x14). levelB@+0x10 is // left as the weapon "reset source" (LevelCountB) -- SetLevel does NOT touch it, // so weapon alarms are unchanged; only the watcher sockets are now real. char _hdr[0x0c]; // +0x00 base header (vtable + FUN_004178cc header) int levelA; // +0x0c stateCount int levelB; // +0x10 oldState slot / weapon LevelCountB int level; // +0x14 currentState (status level) SChainOf audioWatcherSocket; // +0x18 SChainOf videoWatcherSocket; // +0x2c SChainOf gaugeWatcherSocket; // +0x40 }; // The 8-byte modeling type kept ONLY for HUD::statusAlarm (still on the old // HUDLayoutCheck-locked layout; retyping HUD to 0x54 is a separate follow-up F1). // All heat/power/weapon alarms now use GaugeAlarm54 above. class HeatAlarm { public: HeatAlarm(int levels = 0) { levelCount = levels; level = 0; } void Initialize(int levels) { levelCount = levels; level = 0; } void SetLevel(int n) { level = n; } int GetLevel() const { return level; } int Level() const { return level; } protected: int levelCount; int level; }; // The 15-sample running average behind heatLoad (Initialize/AddSample/Average). class HeatFilter { public: void Initialize(int count, Scalar value) { average.SetSize((size_t)count, value); } void AddSample(Scalar v) { average.Add(v); } Scalar Average() { return average.CalculateAverage(); } private: AverageOf average; }; // A ref to a linked subsystem (heat-sink linkage / voltage source / watched // subsystem). The original modelled this with a small SharedData-derived // connection object exposing Add/Resolve/Clear. // The binary connection is a 0xC-byte SharedData-derived link node (the raw // resolver FUN_00417ab4 two-level-derefs it: `if(*(p+8)) return *(*(p+8)+8)`), // NOT a bare 4-byte pointer. Modeling it as 4 bytes slid every field after an // embedded connection low -- the direct cause of the heat-leaf 0x4C deficit // (HeatSink::linkedSinks, PoweredSubsystem::voltageSource). Named access // (Add/Resolve/Clear on `linked`) is unchanged and inert in bring-up (no plug // resolves); the interior _reserved slots reconcile with FUN_00417ab4 when link // resolution is wired. Same 0xC layout as heatfamily_reslice.hpp WatchedConnection. class SubsystemConnection { public: SubsystemConnection(int = 0) { linked = 0; _reserved[0] = _reserved[1] = 0; } void Add(Subsystem *s) { linked = s; } Subsystem* Resolve() const { return linked; } void Clear() { linked = 0; } protected: Subsystem *linked; // +0x00 int _reserved[2]; // pad to the binary 0xC connection size }; static_assert(sizeof(SubsystemConnection) == 0x0C, "subsystem connection must be 0xC (SharedData link node)"); // condenserNumber = atoi(lastChar(name)). inline int NameTrailingNumber(const char *name) { if (name == 0 || *name == '\0') return 0; return atoi(name + (strlen(name) - 1)); } //########################################################################### //################# HeatableSubsystem Model Resource #################### //########################################################################### // // Extends the base damageable Subsystem resource. The heat-specific fields // begin at +0xE4 -- i.e. immediately after MechSubsystem__SubsystemResource // (model size 0xE4), NOT after Subsystem::SubsystemResource (0x30). The class // hierarchy is HeatableSubsystem : MechSubsystem : Subsystem, so the resource // MUST inherit MechSubsystem__SubsystemResource; inheriting Subsystem:: // SubsystemResource directly dropped the 0x30..0xE4 chunk and slid every heat // field 0xB4 bytes low -- so thermalMass/heatSinkIndex read neighbouring floats // (heatSinkIndex came back as 10.0f = 1/thermalMass, GetSegment -> OOB -> no link). // struct HeatableSubsystem__SubsystemResource: public MechSubsystem__SubsystemResource { Scalar startingTemperature; // +0xE4 "StartingTemperature" Scalar degradationTemperature; // +0xE8 "DegradationTemperature" Scalar failureTemperature; // +0xEC "FailureTemperature" Scalar thermalConductance; // +0xF0 "ThermalConductance" Scalar thermalMass; // +0xF4 "ThermalMass" int heatSinkIndex; // +0xF8 "HeatSink" (segment index, +2 bias) }; //########################################################################### //################# Condenser Model Resource ############################ //########################################################################### // // Condenser adds the refrigeration factor (+0xFC). Declared here because the // Condenser class itself is declared in this header (its bodies are split // between heat.cpp [best-effort] and heatfamily_reslice.cpp [completion]). // struct Condenser__SubsystemResource: public HeatableSubsystem__SubsystemResource { Scalar refrigerationFactor; // +0xFC "RefrigerationFactor" (sentinel -1.0f) }; //########################################################################### //######################### HeatableSubsystem ########################### //########################################################################### // // Abstract base for any subsystem that participates in the thermal model and // the shared "damageable subsystem" virtual surface the BT families expect. // (vtable @0050e210, destructor @004ac868.) // class HeatableSubsystem: public MechSubsystem // was: public Subsystem. mechsub.hpp's MechSubsystem // and this are overlapping reconstructions of the SAME // binary cluster (vtable 0050e210); re-base + de-shadow so // owner/simulationState/damageZone/the virtual surface come // from the one real base, not uninitialised duplicates. { //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Shared Data Support // public: static Derivation *GetClassDerivations(); static SharedData DefaultData; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Test Class Support // public: static Logical TestClass(Mech&); Logical TestInstance() const; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Damageable-subsystem virtual surface (engine Subsystem lacks these; the // heat/power families override them). // public: // These OVERRIDE the MechSubsystem base slots (same vtable, matching sigs) -- // not new/parallel slots. ResetToInitialState takes (Logical powered) to match. virtual void ResetToInitialState(Logical powered); virtual LWord GetStatusFlags(); virtual Logical HandleMessage(int message); virtual void PrintState(); virtual void Simulation(Scalar time_slice); // heat per-frame (not a MechSubsystem slot) Logical IsDamaged() { return (simulationState != 0) ? True : False; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Construction and Destruction // public: typedef HeatableSubsystem__SubsystemResource SubsystemResource; HeatableSubsystem( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data = DefaultData ); ~HeatableSubsystem(); static int CreateStreamedSubsystem( NotationFile *model_file, const char *model_name, const char *subsystem_name, SubsystemResource *subsystem_resource, NotationFile *subsystem_file, const ResourceDirectories *directories, int passes = 1 ); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Common subsystem state // public: // owner / simulationState(was "destroyed") / damageZone / hostEntity etc. are // INHERITED from MechSubsystem -- re-declaring them shadowed the real base // (engine ctor writes the base member; the duplicate is uninitialised + at a // wrong compiled offset). flags/statusFlags -> engine Simulation::simulationFlags; // statusBits -> ForceUpdate()/dirty word. Only the thermal fields are own. Scalar currentTemperature; // @0x114 HEAT.TCP: init 300.0f Scalar degradationTemperature; // @0x118 Scalar failureTemperature; // @0x11C Scalar heatLoad; // @0x120 HEAT.TCP: init 0.0f (filtered load) }; //########################################################################### //############################# HeatSink ################################ //########################################################################### // // Active heat sink. Accumulates heat energy, conducts heat to a linked // sink, draws coolant, and raises a degradation/failure alarm. // (vtable @0050edc4, ctor @004adda0, dtor @004adfd4.) // class HeatSink: public HeatableSubsystem { //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Shared Data Support // public: static Derivation *GetClassDerivations(); static SharedData DefaultData; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Attribute Support -- the cockpit gauge bindings resolved by name through // Simulation::GetAttributePointer (see engine GAUGREND.cpp ParseAttribute): // HeatSink/CoolantMass -> coolantLevel (@0x12C, live) // HeatSink/CoolantCapacity -> thermalCapacity (@0x128) // HeatSink/CurrentTemperature -> currentTemperature (@0x114, inherited) // Condenser and Reservoir derive from HeatSink and define no override, so // their DefaultData resolves Condenser6/CoolantMass/... to this same table. // (SimulationState id 1 is preserved by chaining to the parent index.) // public: enum { CoolantMassAttributeID = HeatableSubsystem::NextAttributeID, CoolantCapacityAttributeID, CurrentTemperatureAttributeID, // --- dense-append (gauge data-binding wave): the config binds these // per condenser/heat-sink (L4GAUGE.CFG GenericHeatGauges1/2 + the Eng // clusters). Ids stay contiguous so AttributeIndexSet::Build has no gap. DegradationTemperatureAttributeID, // @0x118 constant amber warn line FailureTemperatureAttributeID, // @0x11C constant max reference NormalizedPressureAttributeID, // @0x120 heatLoad (smoothed radiated heat) DegradationPressureAttributeID, // @0x124 coolantEfficiency CoolantMassLeakRateAttributeID, // @0x130 coolantDraw (damage-driven leak) HeatSinkAttributeID, // @0x164 linkedSinks (link to master sink) ValveSettingAttributeID, // @0x15C coolantFlowScale (condenser valve slider @2) NextAttributeID }; private: static const IndexEntry AttributePointers[]; public: static AttributeIndexSet& GetAttributeIndex(); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Heat alarm state (this+0x138, reported by PrintState @004ae050) // public: enum HeatState { NormalHeat = 0, DegradationHeat = 1, FailureHeat = 2 }; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Simulation Support // public: typedef void (HeatSink::*Performance)(Scalar time_slice); void SetPerformance(Performance performance) { Check(this); activePerformance = (Simulation::Performance)performance; } void HeatSinkSimulation(Scalar time_slice); // @004ad924 (Performance) // FUN_004ad748 -- the base per-frame step (vtable slot 9), reused by the // Condenser / aggregate sink in heatfamily_reslice.cpp. void HeatSink_Step(Scalar time_slice) { HeatSinkSimulation(time_slice); } // FUN_004ad7d4 -- entity heat-model active flag. Logical HeatModelActive() { return True; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Subsystem virtual overrides (slots on vtable @0050edc4) // public: LWord GetStatusFlags(); // slot 12, @004add30 Logical HandleMessage(int message); // slot 8, @004add6c void ResetToInitialState(Logical powered); // slot 10, @004ad760 void PrintState(); // slot 13, @004ae050 // slot 14 (vtable+0x38): asks the central cooling system for coolant // and returns how much was actually supplied (overridden by Reservoir). virtual Scalar DrawCoolant(Scalar requested); // FUN: link another sink/reservoir into this one (Reservoir ctor). void Attach(HeatSink *other) { linkedSinks.Add(other); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Test Class Support // public: static Logical TestClass(Mech&); Logical TestInstance() const; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Construction and Destruction // public: typedef HeatableSubsystem__SubsystemResource SubsystemResource; HeatSink( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data = DefaultData ); ~HeatSink(); static int CreateStreamedSubsystem( NotationFile *model_file, const char *model_name, const char *subsystem_name, SubsystemResource *subsystem_resource, NotationFile *subsystem_file, const ResourceDirectories *directories, int passes = 1 ); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Internal model helpers // public: void UpdateHeatLoad(); // @004ad7f0 void ClearHeatFilter(); // @004ad884 void ConductHeat(Scalar time_slice); // @004ad8ac Scalar ComputeHeatFlow( // @004ad9ec HeatSink *other, Scalar time_slice ); void BalanceCoolant( // @004ada94 Scalar time_slice ); void UpdateCoolant(Scalar time_slice); // @004adbf8 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Local data for the heat sink class. // Offsets are byte offsets into the shipped object. // public: // degradationTemperature/failureTemperature are BASE fields (HeatableSubsystem // @0x118/0x11C, written here by the HeatSink ctor from resource +0xE8/+0xEC) -- // re-declaring them shadowed the base + slid every own field 8 bytes low. // --- coolant model --- (ctor @004adda0 param_1[0x49..0x4e]) Scalar coolantEfficiency; // @0x124 init 0.5f Scalar thermalCapacity; // @0x128 init 1.0f (coolant capacity / divisor) Scalar coolantLevel; // @0x12C init = thermalCapacity (1.0f) Scalar coolantDraw; // @0x130 init 0 (per-frame coolant demand) int coolantAvailable; // @0x134 init 1 (coolant supply present) int coolantActive; // @0x138 init 0 (heat-model running flag; GetStatusFlags bit 0x4) // --- thermal parameters --- (ctor param_1[0x4f..0x58]) Scalar startingTemperature; // @0x13C resource +0xE4 (saved initial temp) Scalar thermalConductance; // @0x140 resource +0xF0 HeatFilter heatFilter; // @0x144 15-sample running average (12 bytes -> 0x150) Scalar filterDecay; // @0x150 init 0.4f Scalar thermalMass; // @0x154 resource +0xF4 Scalar heatEnergy; // @0x158 init = thermalMass * startingTemperature Scalar coolantFlowScale; // @0x15C init 1.0f (== "word57") Scalar massScale; // @0x160 init 1.0f // --- linkage / display --- (ctor param_1[0x59]=linkedSinks, [0x5c]=heatAlarm) SubsystemConnection linkedSinks; // @0x164 0xC connection to master/linked heat sink GaugeAlarm54 heatAlarm; // @0x170 0x54 alarm; status level (Normal/Deg/Fail) at +0x14 == subsystem+0x184 SubsystemResource *resource; // @0x1C4 saved resource pointer (ctor param_1[0x71]) Scalar pendingHeat; // @0x1C8 init 0 (heat delta queued for next frame; ctor param_1[0x72]) Scalar radiatedHeat; // @0x1CC currentTemperature * coolantLevel // object ends @0x1D0 (Reservoir/Condenser own fields begin here) friend struct HeatSinkLayoutCheck; }; // Byte-exact layout locks against the ctor @004adda0 (int* param_1[N] == byte N*4). // Compile-time proof the heat leaf now matches the shipped object; never silently regresses. struct HeatSinkLayoutCheck { static_assert(offsetof(HeatSink, coolantActive) == 0x138, "HeatSink::coolantActive @0x138 (param_1[0x4e])"); static_assert(offsetof(HeatSink, heatEnergy) == 0x158, "HeatSink::heatEnergy @0x158 (param_1[0x56])"); static_assert(offsetof(HeatSink, linkedSinks) == 0x164, "HeatSink::linkedSinks @0x164 (param_1[0x59])"); static_assert(offsetof(HeatSink, heatAlarm) == 0x170, "HeatSink::heatAlarm @0x170 (param_1[0x5c])"); static_assert(offsetof(HeatSink, resource) == 0x1C4, "HeatSink::resource @0x1C4 (param_1[0x71])"); static_assert(offsetof(HeatSink, pendingHeat) == 0x1C8, "HeatSink::pendingHeat @0x1C8 (param_1[0x72])"); static_assert(offsetof(HeatSink, radiatedHeat) == 0x1CC, "HeatSink::radiatedHeat @0x1CC"); static_assert(sizeof(HeatSink) == 0x1D0, "sizeof(HeatSink) 0x1D0"); }; //########################################################################### //############################# Condenser ############################### //########################################################################### // // A HeatSink subclass that models a refrigeration output fighting the master // heat sink's stored heat, carries a 3-position valve and a condenser number. // (vtable @0050ed88, ctor @4ae568, classID CondenserClassID.) The full body // set lives in heatfamily_reslice.cpp; heat.cpp carries a best-effort stub. // class Condenser: public HeatSink { //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Shared Data Support // public: static Derivation *GetClassDerivations(); // task #13: the Condenser handler table @0x50E52C -- exactly ONE entry, // {4, "MoveValve", @4ae464} (PE-verified). Per-receiver-class id space: // id 4 to a condenser = MoveValve; to a weapon = SelectGeneratorA. // Table + set are function-local statics in the accessor (the task #12 // static-init-order trap). static Receiver::MessageHandlerSet& GetMessageHandlers(); static SharedData DefaultData; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Attribute Support -- audio binds an AudioStateWatcher to CondenserState; // it resolves to condenserAlarm (@0x1DC, a 0x54 StateIndicator-compatible // GaugeAlarm54). RefrigerationSimulation SetLevel's it (2/1/0), so the // condenser cycling audio fires on the flow-change pulse. Chained to HeatSink. // public: enum { CondenserStateAttributeID = HeatSink::NextAttributeID, NextAttributeID }; private: static const IndexEntry AttributePointers[]; public: static AttributeIndexSet& GetAttributeIndex(); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Test Class Support // public: static Logical TestClass(Mech&); Logical TestInstance() const; void ResetToInitialState(Logical powered); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Simulation / overrides // public: void RefrigerationSimulation(Scalar time_slice); // @4ae4d8 (vtable slot 9) enum { MoveValveMessageID = 4 }; // table @0x50E52C [T1] void MoveValveMessageHandler( // @4ae464 (valve cycle 1->5->50->0) ReceiverDataMessageOf *message ); virtual void SetValveSetting(int setting) { valveState = setting; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Construction and Destruction // public: typedef Condenser__SubsystemResource SubsystemResource; Condenser( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data = DefaultData ); ~Condenser(); static int CreateStreamedSubsystem( NotationFile *model_file, const char *model_name, const char *subsystem_name, SubsystemResource *subsystem_resource, NotationFile *subsystem_file, const ResourceDirectories *directories, int passes = 1 ); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Local data. // public: // The per-frame refrigeration output reuses the inherited HeatSink massScale // slot (word 0x58 @0x160) -- ctor @004ae568 writes param_1[0x58]=res+0xFC. It // is NOT an own Condenser field (declaring it as one slid valveState off 0x1D0). int valveState; // @0x1D0 (word 0x74) init 1 (MoveValve 0..2) int condenserNumber; // @0x1D4 (word 0x75) last digit of name Scalar refrigerationFactor; // @0x1D8 (word 0x76) from resource +0xFC GaugeAlarm54 condenserAlarm; // @0x1DC (word 0x77) 0x54 alarm, 3 levels -> ends 0x230 friend struct CondenserLayoutCheck; }; struct CondenserLayoutCheck { static_assert(offsetof(Condenser, valveState) == 0x1D0, "Condenser::valveState @0x1D0 (word 0x74)"); static_assert(offsetof(Condenser, condenserAlarm)== 0x1DC, "Condenser::condenserAlarm @0x1DC (word 0x77)"); }; #endif