//===========================================================================// // File: heat.cpp // // Project: BattleTech Brick: Mech subsystems // // Contents: HeatableSubsystem -- a MechSubsystem with a thermal state // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. // // All Rights reserved worldwide // // This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // //===========================================================================// #include #pragma hdrstop #if !defined(HEAT_HPP) # include #endif #if !defined(MECH_HPP) # include #endif #include // //############################################################################# // Shared data support -- reuses the base Subsystem sets (no boot-critical // handlers / attributes of its own). //############################################################################# // Derivation HeatableSubsystem::ClassDerivations( MechSubsystem::ClassDerivations, "HeatableSubsystem" ); HeatableSubsystem::SharedData HeatableSubsystem::DefaultData( HeatableSubsystem::ClassDerivations, Subsystem::MessageHandlers, Subsystem::AttributeIndex, Subsystem::StateCount ); // //############################################################################# //############################################################################# // HeatableSubsystem::HeatableSubsystem( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data ): MechSubsystem( owner, subsystem_ID, (MechSubsystem::SubsystemResource *)subsystem_resource, shared_data ) { Check(owner); Check_Pointer(subsystem_resource); ResetToInitialState(); Check_Fpu(); } // //############################################################################# //############################################################################# // HeatableSubsystem::~HeatableSubsystem() { } // //############################################################################# //############################################################################# // void HeatableSubsystem::ResetToInitialState() { Check(this); currentTemperature = 300.0f; heatLoad = 0.0f; } // //############################################################################# //############################################################################# // Logical HeatableSubsystem::TestClass(Mech &) { return True; } Logical HeatableSubsystem::TestInstance() const { return IsDerivedFrom(ClassDerivations); } // //############################################################################# // CreateStreamedSubsystem Model-load-time construction (thermal resource + // damage-zone stream). Not yet reconstructed (see MECHSUB.NOTES.md). //############################################################################# // int HeatableSubsystem::CreateStreamedSubsystem( NotationFile *, const char *, const char *, SubsystemResource *, NotationFile *, const ResourceDirectories *, int ) { Fail("HeatableSubsystem::CreateStreamedSubsystem -- heat.cpp not yet reconstructed"); return 0; } //########################################################################### //############################## HeatSink ############################### //########################################################################### // //############################################################################# // Shared data support //############################################################################# // Derivation HeatSink::ClassDerivations( HeatableSubsystem::ClassDerivations, "HeatSink" ); HeatSink::SharedData HeatSink::DefaultData( HeatSink::ClassDerivations, Subsystem::MessageHandlers, Subsystem::AttributeIndex, Subsystem::StateCount ); // //############################################################################# // The heat sink -- a thermal mass with a coolant loop. A master sink drives // the per-frame thermal simulation. //############################################################################# // HeatSink::HeatSink( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data ): HeatableSubsystem(owner, subsystem_ID, subsystem_resource, shared_data), linkedSinks(), heatAlarm(3) { Check(owner); Check_Pointer(subsystem_resource); currentTemperature = subsystem_resource->startingTemperature; degradationTemperature = subsystem_resource->degradationTemperature; failureTemperature = subsystem_resource->failureTemperature; heatLoad = 0.0f; coolantEfficiency = 0.5f; thermalCapacity = 1.0f; coolantLevel = thermalCapacity; coolantDraw = 0.0f; coolantAvailable = 1; coolantActive = 0; startingTemperature = currentTemperature; thermalConductance = subsystem_resource->thermalConductance; heatFilter.SetSize(15, 0.0f); filterDecay = 0.4f; thermalMass = subsystem_resource->thermalMass; heatEnergy = thermalMass * startingTemperature; coolantFlowScale = 1.0f; massScale = 1.0f; pendingHeat = 0.0f; radiatedHeat = 0.0f; // // Wire the heat-conduction link: the resource names the roster slot of the // sink this one drains into (weapons/equipment -> the Condenser bank, the // Condensers -> the central HeatSink). The shipped stream orders sinks so // the target is already constructed; an unresolvable index leaves the sink // standalone (its own thermal mass only). // { Subsystem *linked = NULL; if (subsystem_resource->linkedSinkIndex >= 0 && subsystem_resource->linkedSinkIndex < owner->GetSubsystemCount()) { linked = owner->GetSubsystem(subsystem_resource->linkedSinkIndex); } if (linked != NULL) { linkedSinks.Add(linked); } if (getenv("BT_POWER_LOG")) { DEBUG_STREAM << "[heat] '" << GetName() << "' linkedSinkIdx=" << subsystem_resource->linkedSinkIndex << " -> "; if (linked != NULL) { DEBUG_STREAM << linked->GetName(); } else { DEBUG_STREAM << ""; } DEBUG_STREAM << " thermalMass=" << thermalMass << " T0=" << currentTemperature << " degrade=" << degradationTemperature << " fail=" << failureTemperature << " conduct=" << thermalConductance << endl << flush; } } // // Install the per-frame thermal Performance (replicant copies are driven by // console updates instead). Derived classes (PoweredSubsystem, Generator, // the weapons) override with their own Performance in their ctors, each of // which chains this step. // if (owner->GetInstance() != Entity::ReplicantInstance) { SetPerformance(&HeatSink::HeatSinkSimulation); } Check_Fpu(); } // //############################################################################# //############################################################################# // HeatSink::~HeatSink() { } // //############################################################################# //############################################################################# // void HeatSink::ResetToInitialState(Logical /*powered*/) { Check(this); currentTemperature = startingTemperature; heatLoad = 0.0f; coolantLevel = thermalCapacity; coolantDraw = 0.0f; coolantActive = 0; heatEnergy = thermalMass * startingTemperature; pendingHeat = 0.0f; radiatedHeat = 0.0f; heatAlarm.SetLevel(0); } // //############################################################################# //############################################################################# // Logical HeatSink::TestClass(Mech &) { return True; } Logical HeatSink::TestInstance() const { return IsDerivedFrom(ClassDerivations); } // //############################################################################# // HeatSinkSimulation -- the per-frame thermal step (binary @004ad924). // Absorb the pending heat into the thermal mass, recompute the temperature and // the smoothed heat-load reading, conduct into the linked sink, then drive the // degradation / failure alarm from the authored thresholds. // // PARTIAL: the heat model runs unconditionally -- the authentic gate is the // player experience level (HeatModelActive: novice mode disables the heat // model / jams; joins with the player-link accessor wave). UpdateCoolant // (coolant depletion / venting) is deferred with the coolant wave; the // coolant level stays at capacity, which holds the conduction term at its // full-coolant value. //############################################################################# // void HeatSink::HeatSinkSimulation(Scalar time_slice) { Check(this); heatEnergy += pendingHeat; currentTemperature = heatEnergy / thermalMass; UpdateHeatLoad(); pendingHeat = 0.0f; ConductHeat(time_slice); // // Drive the degradation / failure alarm. // if (currentTemperature > failureTemperature) { heatAlarm.SetLevel(FailureHeat); } else if (currentTemperature > degradationTemperature) { heatAlarm.SetLevel(DegradationHeat); } else { heatAlarm.SetLevel(NormalHeat); } Check_Fpu(); } // //############################################################################# // UpdateHeatLoad -- recompute the radiated heat and feed it through the // 15-sample running-average filter to produce the smoothed heatLoad reading // (binary @004ad7f0; the HeatLoadScale / min / max shaping constants join with // the gauge-calibration wave). //############################################################################# // void HeatSink::UpdateHeatLoad() { Check(this); radiatedHeat = currentTemperature * coolantLevel; heatFilter.Add(radiatedHeat); heatLoad = heatFilter.CalculateAverage(); } // //############################################################################# // ConductHeat -- conduct heat into the linked sink (binary @004ad8ac). The // coolant rebalance (BalanceCoolant) is deferred with the coolant wave. //############################################################################# // void HeatSink::ConductHeat(Scalar time_slice) { Check(this); HeatSink *other = (HeatSink *)linkedSinks.Resolve(); if (other != NULL && coolantAvailable != 0) { Scalar flow = ComputeHeatFlow(other, time_slice); other->pendingHeat += flow; pendingHeat -= flow; } } // //############################################################################# // ComputeHeatFlow -- conductive heat exchange between this sink and 'other' // (binary @004ad9ec): // tau = thermalMass / massScale // denom = tau + other->thermalMass // q = (currentTemperature*massScale // - (other->heatEnergy + other->pendingHeat + heatEnergy) / denom) // * tau // * (1 - exp( -dt * thermalConductance // * (coolantLevel / thermalCapacity) // * coolantFlowScale / denom )) //############################################################################# // Scalar HeatSink::ComputeHeatFlow(HeatSink *other, Scalar time_slice) { Check(this); Check(other); Scalar tau = thermalMass / massScale; Scalar denom = tau + other->thermalMass; Scalar equilibrium = currentTemperature * massScale - (other->heatEnergy + other->pendingHeat + heatEnergy) / denom; Scalar response = 1.0f - (Scalar)exp( -time_slice * thermalConductance * (coolantLevel / thermalCapacity) * coolantFlowScale / denom ); return equilibrium * tau * response; } Scalar HeatSink::DrawCoolant(Scalar) { Fail("HeatSink::DrawCoolant -- heat.cpp not yet reconstructed"); return 0.0f; } //########################################################################### //############################# HeatWatcher ############################# //########################################################################### Derivation HeatWatcher::ClassDerivations( MechSubsystem::ClassDerivations, "HeatWatcher" ); HeatWatcher::SharedData HeatWatcher::DefaultData( HeatWatcher::ClassDerivations, Subsystem::MessageHandlers, Subsystem::AttributeIndex, Subsystem::StateCount ); HeatWatcher::HeatWatcher( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data ): MechSubsystem( owner, subsystem_ID, (MechSubsystem::SubsystemResource *)subsystem_resource, shared_data ), watchedLink(), heatAlarm(3) { Check(owner); Check_Pointer(subsystem_resource); degradationTemperature = subsystem_resource->degradationTemperature; failureTemperature = subsystem_resource->failureTemperature; watchedSubsystem = subsystem_resource->watchedSubsystem; // // The master instance runs WatchSimulation per-frame; the install is // deferred with that (staged) method. // Check_Fpu(); } HeatWatcher::~HeatWatcher() { } Logical HeatWatcher::TestClass(Mech &) { return True; } Logical HeatWatcher::TestInstance() const { return IsDerivedFrom(ClassDerivations); } void HeatWatcher::ResetToInitialState(Logical /*powered*/) { Check(this); heatAlarm.SetLevel(0); } // // Per-frame: resolve the watched subsystem, read its temperature, drive the // 3-level alarm. Not yet reconstructed. // void HeatWatcher::WatchSimulation(Scalar) { Fail("HeatWatcher::WatchSimulation -- heat.cpp not yet reconstructed"); } //########################################################################### //############################## Condenser ############################# //########################################################################### Derivation Condenser::ClassDerivations( HeatSink::ClassDerivations, "Condenser" ); Condenser::SharedData Condenser::DefaultData( Condenser::ClassDerivations, Subsystem::MessageHandlers, Subsystem::AttributeIndex, Subsystem::StateCount ); Condenser::Condenser( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data ): HeatSink(owner, subsystem_ID, subsystem_resource, shared_data) { Check(owner); Check_Pointer(subsystem_resource); valveState = 0; refrigerationFactor = subsystem_resource->refrigerationFactor; // // Condenser number from the segment-name suffix ('A' -> 1 ...). // const char *name = GetName(); condenserNumber = name[strlen(name) - 1] - 0x40; Check_Fpu(); } Condenser::~Condenser() { } Logical Condenser::TestClass(Mech &) { return True; } Logical Condenser::TestInstance() const { return IsDerivedFrom(ClassDerivations); }