Files
BT412/game/reconstructed/heat.cpp
T
arcattackandClaude Opus 4.8 7b7d465e5e Initial commit: bt411 -- standalone Windows BattleTech (Tesla 4.10 port)
Clean, self-contained extraction of the BattleTech-specific work from the
reverse-engineering workspace -- engine + game + content + build, with nothing
from Red Planet or the raw archive dumps. Builds green (Win32) and runs the
single-player drive->animate->target->fire->damage->destroy loop out of the box.

Layout:
  engine/   MUNGA + MUNGA_L4 shared 2007 engine, carrying our BT render/loader
            work (bgfload/L4D3D/L4VIDEO: BSL bit-slice decode, LOD/ground/shadow
            models) + image codec; the minimal rp/ headers the audio HAL needs
  game/     reconstructed BT logic + surviving-original BT source + fwd shims
            + WinMain launcher
  content/  full runtime tree (BTL4.RES, VIDEO/, GAUGE/, AUDIO/, eggs, BTDPL.INI)
  docs/     format specs + reconstruction ledgers
  reference/ raw Ghidra pseudocode (recon source-of-truth) + decomp exporter
  tools/    MP console emulator + map/resource scanners

One top-level CMake builds munga_engine lib + bt410_l4 game lib + btl4.exe.
All paths relativized (186 fwd shims + ~437 CMake abs paths -> repo-relative);
DXSDK is the one external, overridable via -DDXSDK. Verified: builds to a
byte-identical 2.27MB exe and runs combat (TARGET DESTROYED, 0 crashes) against
the bundled content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 21:03:40 -05:00

968 lines
29 KiB
C++

//===========================================================================//
// File: heat.cpp //
// 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. Behaviour follows the Ghidra
// pseudo-C in heat_cluster.c; method names / member names follow HEAT.TCP
// and the sibling subsystem sources (gauss.cpp, sensor.hpp, ppc.hpp).
// Each non-trivial method cites the originating @ADDR. Hex float constants
// have been converted to decimal:
// 0x3f800000 = 1.0f 0x3f000000 = 0.5f 0x3ecccccd = 0.4f
//
// Helper-function name mapping (engine internals referenced by the decomp):
// FUN_004ac644 HeatableSubsystem base constructor
// FUN_0041c648 Subsystem destructor
// FUN_004ac22c Subsystem::ResetToInitialState
// FUN_004ac144 Subsystem::GetStatusFlags
// FUN_004ac1d4 Subsystem::HandleMessage
// FUN_004ac0bc Subsystem (slot 9 base impl)
// FUN_004ac8c0 Subsystem::PrintState
// FUN_0043ad4f FilteredScalar::Initialize(count,value)
// FUN_0043ade4 FilteredScalar::AddSample(value)
// FUN_0043ae0b FilteredScalar::Average()
// FUN_00417ab4 SharedData::Resolve() -> linked HeatSink*
// FUN_0041b9ec AlarmIndicator(levels)
// FUN_0041bbd8 AlarmIndicator::SetLevel(n)
// FUN_004dca38 expf() FUN_004dcd00 fabsf()
// FUN_0040385c Verify()/assert(msg,file,line)
// FUN_0041a1a4 IsDerivedFrom(classDerivations)
//
#include <bt.hpp>
#pragma hdrstop
#if !defined(HEAT_HPP)
# include <heat.hpp>
#endif
#if !defined(APP_HPP)
# include <app.hpp>
#endif
#if !defined(TESTBT_HPP)
# include <testbt.hpp>
#endif
#define JM_CLOSE_ENOUGH 0.0005f
//
// Tuning constants RECOVERED from the shipped image (.data / inline literal
// pool addresses below; raw bytes read from decomp/recovered/section_dump.txt).
// _DAT_004ad870 : 80-bit extended `3b df 4f 8d 97 6e 12 83 f6 3f`
// = 1.023992 x 2^-9 == 0.002f (heat-load normalising scale:
// radiatedHeat ~= currentTemp*coolant ~= 300 -> band [0,1]).
// _DAT_004ad87c : 00 00 00 00 == 0.0f
// _DAT_004ad880 : 00 00 80 3f == 1.0f
// _DAT_004ada90 : 00 00 80 3f == 1.0f (the "1 - exp(...)" unit term)
// _DAT_004adbf4 : 17 b7 d1 38 == 1.0e-4f (heat-equalise threshold)
// _DAT_004adcfc : 17 b7 d1 38 == 1.0e-4f (coolant-draw threshold)
//
static const Scalar HeatLoadScale = 0.002f; // _DAT_004ad870 (80-bit extended)
static const Scalar HeatLoadMinimum = 0.0f; // _DAT_004ad87c
static const Scalar HeatLoadMaximum = 1.0f; // _DAT_004ad880
static const Scalar HeatEqualizeEpsilon = 1.0e-4f; // _DAT_004adbf4
static const Scalar CoolantDrawEpsilon = 1.0e-4f; // _DAT_004adcfc / 0050e3d8 / 0050e3d4
//###########################################################################
//###########################################################################
// HeatableSubsystem
//###########################################################################
//###########################################################################
//#############################################################################
// Shared Data Support
//
HeatableSubsystem::SharedData
HeatableSubsystem::DefaultData(
HeatableSubsystem::GetClassDerivations(),
HeatableSubsystem::GetMessageHandlers(),
HeatableSubsystem::GetAttributeIndex(),
HeatableSubsystem::StateCount
);
Derivation*
HeatableSubsystem::GetClassDerivations()
{
static Derivation classDerivations(Subsystem::GetClassDerivations(), "HeatableSubsystem");
return &classDerivations;
}
//#############################################################################
// Construction / Destruction
//
// @004ac644 (base ctor body lies below the captured decomp window).
//
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);
// owner + simulationState are set by the MechSubsystem base ctor; the old
// flags/statusFlags/statusBits/destroyed were shadows of base state (removed).
ResetToInitialState(True);
Check_Fpu();
}
//
// @004ac868 -- releases the shared model object and the ref-counted resource
// then chains to ~Subsystem.
//
HeatableSubsystem::~HeatableSubsystem()
{
Check(this);
Check_Fpu();
}
//###########################################################################
// ResetToInitialState -- HeatableSubsystem
//
// HEAT.TCP ground truth.
//
void
HeatableSubsystem::ResetToInitialState(Logical /*powered*/)
{
currentTemperature = 300.0f;
heatLoad = 0.0f;
}
//###########################################################################
// TestClass -- HeatableSubsystem
//
// HEAT.TCP ground truth.
//
Logical
HeatableSubsystem::TestClass(Mech &)
{
return True;
}
Logical
HeatableSubsystem::TestInstance() const
{
return IsDerivedFrom(*GetClassDerivations());
}
//
// Base damageable-subsystem virtual surface. The engine `Subsystem` does not
// carry these; the heat/power families override them. Base implementations
// are the neutral defaults the recovered overrides chain into.
//
LWord
HeatableSubsystem::GetStatusFlags() { return 0; }
Logical
HeatableSubsystem::HandleMessage(int) { return False; }
void
HeatableSubsystem::PrintState() {}
void
HeatableSubsystem::Simulation(Scalar) {}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CreateStreamedSubsystem -- HeatableSubsystem
//
// @004ac9ec parses the base "damageable subsystem" record (WeaponDamagePoints,
// Collision/Ballistic/Explosive/Laser/EnergyDamagePoints, VitalSubsystem,
// SiteOffset, VideoObjectName, PrintSimulationState, CriticalHitScoreBonus).
// That logic belongs to the shared damageable base; reproduced here in
// summary form -- a human should fold it back into the proper base class.
//
int
HeatableSubsystem::CreateStreamedSubsystem(
NotationFile *model_file,
const char *model_name,
const char *subsystem_name,
SubsystemResource *subsystem_resource,
NotationFile *subsystem_file,
const ResourceDirectories *directories,
int passes
)
{
if (
!Subsystem::CreateStreamedSubsystem(
model_file, model_name, subsystem_name,
subsystem_resource, subsystem_file, directories
)
)
{
return False;
}
subsystem_resource->subsystemModelSize = sizeof(*subsystem_resource);
// TODO: verify against @004ac9ec -- parses damage-point fields, the
// "VitalSubsystem" True/False flag, "SiteOffset", "VideoObjectName",
// "PrintSimulationState" and "CriticalHitScoreBonus" and validates that
// WeaponDamagePoints / CriticalHitScoreBonus are present.
Check_Fpu();
return True;
}
//###########################################################################
//###########################################################################
// Condenser
//###########################################################################
//###########################################################################
//
// NOTE: the shipped Condenser implementation lies past the captured decomp
// window (@0x4ae1xx onward). Bodies below are reconstructed from HEAT.TCP
// plus the standard subsystem pattern and are BEST-EFFORT.
//
//#############################################################################
// Shared Data Support
//
Condenser::SharedData
Condenser::DefaultData(
Condenser::GetClassDerivations(),
Condenser::GetMessageHandlers(),
Condenser::GetAttributeIndex(),
Condenser::StateCount
);
Derivation*
Condenser::GetClassDerivations()
{
static Derivation classDerivations(HeatableSubsystem::GetClassDerivations(), "Condenser");
return &classDerivations;
}
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);
Check_Fpu();
}
Condenser::~Condenser()
{
Check(this);
Check_Fpu();
}
//###########################################################################
// ResetToInitialState -- Condenser (HEAT.TCP)
//
void
Condenser::ResetToInitialState(Logical /*powered*/)
{
HeatableSubsystem::ResetToInitialState(True);
}
//###########################################################################
// TestClass -- Condenser (HEAT.TCP)
//
Logical
Condenser::TestClass(Mech &)
{
return True;
}
Logical
Condenser::TestInstance() const
{
return IsDerivedFrom(*GetClassDerivations());
}
int
Condenser::CreateStreamedSubsystem(
NotationFile *model_file,
const char *model_name,
const char *subsystem_name,
SubsystemResource *subsystem_resource,
NotationFile *subsystem_file,
const ResourceDirectories *directories,
int passes
)
{
// TODO: verify -- decomp for the Condenser parser was not captured.
if (
!HeatableSubsystem::CreateStreamedSubsystem(
model_file, model_name, subsystem_name,
subsystem_resource, subsystem_file, directories, passes
)
)
{
return False;
}
subsystem_resource->subsystemModelSize = sizeof(*subsystem_resource);
Check_Fpu();
return True;
}
//###########################################################################
//###########################################################################
// HeatSink
//###########################################################################
//###########################################################################
//#############################################################################
// Shared Data Support
//
HeatSink::SharedData
HeatSink::DefaultData(
HeatSink::GetClassDerivations(),
HeatSink::GetMessageHandlers(),
HeatSink::GetAttributeIndex(),
HeatSink::StateCount
);
Derivation*
HeatSink::GetClassDerivations()
{
static Derivation classDerivations(HeatableSubsystem::GetClassDerivations(), "HeatSink");
return &classDerivations;
}
//#############################################################################
// Construction / Destruction
//
// @004adda0 (the one function tagged file=bt/heat.cpp in the decomp).
//
HeatSink::HeatSink(
Mech *owner,
int subsystem_ID,
SubsystemResource *subsystem_resource,
SharedData &shared_data
):
HeatableSubsystem(owner, subsystem_ID, subsystem_resource, shared_data),
linkedSinks(),
heatAlarm(3) // FUN_0041b9ec(...,3) -- 3 alarm levels
{
heatState = NormalHeat;
heatModelFlag = 1;
field_1d0 = 0.0f;
Check(owner);
Check_Pointer(subsystem_resource);
currentTemperature = subsystem_resource->startingTemperature; // +0xE4
degradationTemperature = subsystem_resource->degradationTemperature; // +0xE8
failureTemperature = subsystem_resource->failureTemperature; // +0xEC
heatLoad = 0.0f;
coolantEfficiency = 0.5f;
thermalCapacity = 1.0f;
coolantLevel = thermalCapacity;
coolantDraw = 0.0f;
coolantAvailable = 1;
coolantActive = 0;
startingTemperature = currentTemperature;
thermalConductance = subsystem_resource->thermalConductance; // +0xF0
heatFilter.Initialize(15, 0.0f); // FUN_0043ad4f(this+0x144, 0xF, 0)
filterDecay = 0.4f;
thermalMass = subsystem_resource->thermalMass; // +0xF4
heatEnergy = thermalMass * startingTemperature;
coolantFlowScale = 1.0f;
massScale = 1.0f;
pendingHeat = 0.0f;
//
// A "master" heat sink (segment flagged 0x100, not a sub-/damaged copy)
// drives the per-frame thermal simulation.
//
// INTEGRATION (gate reconcile): the binary master-gate reads the OWNER's
// simulationFlags (param_2+0x28), NOT the per-segment resource flags. The
// recovered C across all subsystem ctors is uniformly
// (*(uint*)(param_2+0x28) & 0xc)==0 && (*(uint*)(param_2+0x28) & 0x100)!=0
// (owner+0x28), which is the authoritative path the working AmmoBin/projweap
// gate already used. Reading subsystem_resource->subsystemFlags here was a
// reconstruction mis-attribution (that field streams 0 in our data → the gate
// never armed → HeatSinkSimulation was never installed → the sink decayed to
// DoNothingOnce after frame 1). The SAME correction applies to the linked-sink
// block below: the raw decomp of @004adda0 reads the OWNER flags (param_2+0x28)
// there too (lines 47/56), NOT the per-segment resource flags. With subsystemFlags
// streaming 0 the Add-gate was dead → linkedSinks empty → weapon/subsystem heat
// never conducted to its designated sink → the mech never heated. Fixed to owner
// flags to match the binary; the per-subsystem TARGET is still heatSinkIndex.
if (
(owner->simulationFlags & SegmentCopyMask) == 0 // (owner flags & 0xC) == 0
&& (owner->simulationFlags & MasterHeatSinkFlag) != 0 // owner flags & 0x100
)
{
SetPerformance(&HeatSink::HeatSinkSimulation); // this[7..9] = &HeatSinkSimulation
}
resource = subsystem_resource;
//
// Resolve and attach the linked heat sink referenced by heatSinkIndex.
// (Skipped for "Condenser"-class segments -- see @0041a1a4 guard.)
//
if (!IsDerivedFrom(*Condenser::GetClassDerivations())) // FUN_0041a1a4(*this[3], 0x50e590)
{
// ⚠ ROOT-CAUSE FIX (the BGF-load heap corruption): heatSinkIndex indexes the
// owner's SUBSYSTEM ROSTER, NOT the skeleton segment table. Raw @004adda0
// (part_012.c:16999): if (res->heatSinkIndex < owner->subsystemCount /*+0x124*/)
// linked = owner->subsystemArray[heatSinkIndex] /*+0x128*/;
// The earlier draft resolved it via owner->GetSegment(index) — an EntitySegment
// (288 bytes) cast to HeatSink* — so every per-frame ConductHeat/BalanceCoolant
// wrote pendingHeat/coolantLevel 100/20 bytes PAST that block: thousands of OOB
// heap writes during fire, detected later at an unrelated free (bld08.bgf load).
// subsystemArray is zeroed up front (mech.cpp), so a not-yet-built roster slot
// reads NULL -> the binary's "missing" warn path, exactly as the oracle.
Subsystem *linked = 0;
if (subsystem_resource->heatSinkIndex >= 0
&& subsystem_resource->heatSinkIndex < owner->GetSubsystemCount())
{
linked = owner->GetSubsystem(subsystem_resource->heatSinkIndex);
}
else
{
// @004adda0: "Bad subsystem resource ->heatSink" HEAT.CPP:0x25F
Verify(False, "Bad subsystem resource ->heatSink", __FILE__, 0x25F);
}
if (
(owner->simulationFlags & SegmentCopyMask) == 0 // param_2+0x28 & 0xc == 0
&& (owner->simulationFlags & MasterHeatSinkFlag) != 0 // param_2+0x28 & 0x100
)
{
if (linked == 0)
{
// HEAT.CPP:0x26B
Verify(False, "Master heatable subsystem is missing", __FILE__, 0x26B);
}
else
{
linkedSinks.Add(linked); // (**(this[0x59]+4))(this+0x59, linked)
}
}
else if (
(owner->simulationFlags & SegmentCopyMask) == 4 // param_2+0x28 & 0xc == 4
&& linked != 0
)
{
linkedSinks.Add(linked);
}
}
UpdateHeatLoad(); // FUN_004ad7f0
Check_Fpu();
}
//
// @004adfd4
//
HeatSink::~HeatSink()
{
Check(this);
// members (heatAlarm @0x5C, linkedSinks @0x59, heatFilter @0x51)
// are torn down by their own destructors; base chain handles the rest.
Check_Fpu();
}
Logical
HeatSink::TestInstance() const
{
// @004ae034 -> FUN_0041a1a4(**this[3], 0x50e3ec)
return IsDerivedFrom(*GetClassDerivations());
}
//###########################################################################
// TestClass -- HeatSink (HEAT.TCP)
//
Logical
HeatSink::TestClass(Mech &)
{
return True;
}
//###########################################################################
// ResetToInitialState -- HeatSink
//
// @004ad760
//
void
HeatSink::ResetToInitialState(Logical /*powered*/)
{
currentTemperature = startingTemperature;
heatEnergy = startingTemperature * thermalMass;
coolantLevel = thermalCapacity;
coolantDraw = 0.0f;
coolantAvailable = 1;
coolantFlowScale = 1.0f;
ClearHeatFilter(); // FUN_004ad884
UpdateHeatLoad(); // FUN_004ad7f0
coolantActive = 0;
HeatableSubsystem::ResetToInitialState(True); // FUN_004ac22c
}
//#############################################################################
// Per-frame simulation
//
//
// @004ad924 -- the registered Performance for a master heat sink.
//
void
HeatSink::HeatSinkSimulation(Scalar time_slice)
{
Check(this);
if (HeatModelActive()) // FUN_004ad7d4 (entity heat-model flag)
{
heatEnergy += pendingHeat;
currentTemperature = heatEnergy / thermalMass;
UpdateHeatLoad();
// BRING-UP verify (1 Hz, viewpoint mech only): show the mech heat climbing as
// weapons fire and relaxing when they stop. pendingHeat just absorbed above.
static Scalar s_heatLog = 0.0f;
s_heatLog += time_slice;
if (s_heatLog >= 1.0f && application != 0
&& (Entity *)owner == application->GetViewpointEntity() && pendingHeat > 0.0f)
{
s_heatLog = 0.0f;
DEBUG_STREAM << "[heat] sink temp=" << currentTemperature
<< " heatEnergy=" << heatEnergy << " absorbed=" << pendingHeat
<< " heatLoad=" << heatLoad << "\n" << std::flush;
}
pendingHeat = 0.0f;
ConductHeat(time_slice); // FUN_004ad8ac
}
if (HeatModelActive())
{
UpdateCoolant(time_slice); // FUN_004adbf8
}
//
// Drive the degradation / failure alarm.
//
if (currentTemperature > failureTemperature)
{
heatAlarm.SetLevel(FailureHeat); // FUN_0041bbd8(this+0x5C, 2)
}
else if (currentTemperature > degradationTemperature)
{
heatAlarm.SetLevel(DegradationHeat); // 1
}
else
{
heatAlarm.SetLevel(NormalHeat); // 0
}
Check_Fpu();
}
//
// @004ad7f0 -- recompute the radiated/instantaneous heat and feed it through
// the running-average filter to produce the smoothed heatLoad reading.
//
void
HeatSink::UpdateHeatLoad()
{
radiatedHeat = currentTemperature * coolantLevel;
Scalar sample = HeatLoadScale * radiatedHeat;
if (sample < HeatLoadMinimum)
{
sample = HeatLoadMinimum;
}
else if (sample > HeatLoadMaximum)
{
sample = HeatLoadMaximum;
}
heatFilter.AddSample(sample); // FUN_0043ade4
heatLoad = heatFilter.Average(); // FUN_0043ae0b
}
//
// @004ad884 -- flush the 15-sample filter back to zero.
//
void
HeatSink::ClearHeatFilter()
{
for (int i = 0; i < 15; ++i)
{
heatFilter.AddSample(0.0f);
}
}
//
// @004ad8ac -- conduct heat into the linked sink and rebalance coolant.
//
void
HeatSink::ConductHeat(Scalar time_slice)
{
HeatSink *other = (HeatSink *)linkedSinks.Resolve(); // FUN_00417ab4(this+0x164)
if (other != 0 && coolantAvailable != 0)
{
Scalar flow = ComputeHeatFlow(other, time_slice); // FUN_004ad9ec
other->pendingHeat += flow;
pendingHeat -= flow;
BalanceCoolant(time_slice); // FUN_004ada94
}
}
//
// @004ad9ec -- conductive heat-exchange between this sink and 'other'.
//
// 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)
{
Scalar tau = thermalMass / massScale; // this+0x154 / this+0x160
Scalar denom = tau + other->thermalMass; // + other+0x154
Scalar exponent =
(-time_slice
* thermalConductance // this+0x140
* (coolantLevel / thermalCapacity) // this+0x12C / this+0x128
* coolantFlowScale) // this+0x15C
/ denom;
Scalar decay = expf(exponent); // FUN_004dca38
return (currentTemperature * massScale
- (other->heatEnergy + other->pendingHeat + heatEnergy) / denom)
* tau
* (1.0f - decay); // _DAT_004ada90 == 1.0f
}
//
// @004ada94 -- move coolant between this sink and its linked sink so that the
// hotter side sheds load. Clamped on both ends so neither sink goes below 0
// or above its capacity.
//
void
HeatSink::BalanceCoolant(Scalar time_slice)
{
HeatSink *other = (HeatSink *)linkedSinks.Resolve(); // FUN_00417ab4(this+0x164)
if (other == 0)
{
return;
}
if (fabsf(radiatedHeat - other->radiatedHeat) <= HeatEqualizeEpsilon) // FUN_004dcd00
{
return;
}
Scalar delta = other->radiatedHeat / currentTemperature - coolantLevel;
// clamp delta to +/- (thermalCapacity * dt)
Scalar limit = thermalCapacity * time_slice;
if (delta < -limit)
{
delta = -limit;
}
else if (delta > limit)
{
delta = limit;
}
// clamp so this sink stays within [0, thermalCapacity]
Scalar hi = thermalCapacity - coolantLevel;
Scalar lo = -coolantLevel;
if (delta < lo) delta = lo;
else if (delta > hi) delta = hi;
// clamp so the other sink stays within its own [0, thermalCapacity]
Scalar otherLo = -(other->thermalCapacity - other->coolantLevel);
if (delta < otherLo) delta = otherLo;
else if (delta > other->coolantLevel) delta = other->coolantLevel;
delta = coolantFlowScale * delta;
coolantLevel += delta;
other->coolantLevel -= delta;
}
//
// @004adbf8 -- consume coolant proportional to current load, request a
// top-up from the central cooling system, and update the draw state machine.
//
void
HeatSink::UpdateCoolant(Scalar time_slice)
{
HeatSink *master = (HeatSink *)linkedSinks.Resolve(); // this[0x38] linked master
// NULL-master guard (reconstruction omission -- the sibling ConductHeat/BalanceCoolant
// guard this same Resolve(); UpdateCoolant didn't, so a Condenser/Reservoir with no
// linked master null-deref'd here on its first tick). No master -> no coolant draw.
coolantDraw = master ? master->heatEnergy * heatLoad : 0.0f; // *(this[0x38]+0x158) * this[0x48]
if (coolantDraw < CoolantDrawEpsilon)
{
coolantDraw = 0.0f;
}
Scalar amount = coolantDraw * time_slice;
if (coolantLevel < amount)
{
amount = coolantLevel;
}
coolantLevel -= amount;
if (fabsf(amount) > CoolantDrawEpsilon)
{
coolantLevel += DrawCoolant(amount); // virtual: (**(*this+0x38))(this, amount)
}
//
// Draw state machine (this+0x138).
//
if (coolantActive == 0 && coolantDraw > CoolantDrawEpsilon)
{
coolantActive = 1;
}
else if (coolantActive == 1 && coolantDraw < CoolantDrawEpsilon)
{
coolantActive = 0;
}
}
//
// vtable slot 14 (@vtable+0x38). Base sinks supply nothing on their own;
// a central-cooling-system subclass overrides this.
// TODO: verify against the real slot-14 target (not captured in decomp).
//
Scalar
HeatSink::DrawCoolant(Scalar /*requested*/)
{
return 0.0f;
}
//#############################################################################
// Subsystem virtual overrides
//
//
// @004add30 -- base flags, plus heat-active / coolant bits.
//
LWord
HeatSink::GetStatusFlags()
{
LWord flags = HeatableSubsystem::GetStatusFlags(); // FUN_004ac144
if (coolantActive != 0) // this+0x184 (TODO: confirm offset)
{
flags |= 0x8;
}
if (/* heat model running */ this->heatModelFlag != 0 // this+0x138
&& HeatModelActive()) // FUN_004ad7d4
{
flags |= 0x4;
}
return flags;
}
//
// @004add6c -- message 1 forces the linked master's heatEnergy to a known
// value; everything else falls through to the base handler.
//
Logical
HeatSink::HandleMessage(int message)
{
if (message == 1)
{
HeatSink *master = (HeatSink *)linkedSinks.Resolve();
master->heatEnergy = 0.5f; // *(this[0x38]+0x158) = 0.5f
return True;
}
return HeatableSubsystem::HandleMessage(message); // FUN_004ac1d4
}
//
// @004ae050 -- prints "<name> NormalHeat | DegradationHeat | FailureHeat".
//
void
HeatSink::PrintState()
{
HeatableSubsystem::PrintState(); // FUN_004ac8c0
switch (coolantActive) // this+0x184 (heat-state field)
{
case NormalHeat:
DebugStream << GetName() << " NormalHeat" << endl;
break;
case DegradationHeat:
DebugStream << GetName() << " DegradationHeat" << endl;
break;
case FailureHeat:
DebugStream << GetName() << " FailureHeat" << endl;
break;
default:
DebugStream << GetName() << " Unknown Heat State!" << endl;
break;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CreateStreamedSubsystem -- HeatSink
//
// @004ae150
//
int
HeatSink::CreateStreamedSubsystem(
NotationFile *model_file,
const char *model_name,
const char *subsystem_name,
SubsystemResource *subsystem_resource,
NotationFile *subsystem_file,
const ResourceDirectories *directories,
int passes
)
{
if (
!HeatableSubsystem::CreateStreamedSubsystem( // FUN_004ac9ec
model_file, model_name, subsystem_name,
subsystem_resource, subsystem_file, directories, passes
)
)
{
return False;
}
subsystem_resource->subsystemModelSize = sizeof(*subsystem_resource);
subsystem_resource->classID = RegisteredClass::HeatSinkClassID;
if (passes == 1)
{
// first pass: prime all heat fields to "unset" (-1.0f / -1)
subsystem_resource->startingTemperature = -1.0f;
subsystem_resource->degradationTemperature = -1.0f;
subsystem_resource->failureTemperature = -1.0f;
subsystem_resource->thermalConductance = -1.0f;
subsystem_resource->thermalMass = -1.0f;
subsystem_resource->heatSinkIndex = -1;
}
if (
!model_file->GetEntry(subsystem_name, "StartingTemperature",
&subsystem_resource->startingTemperature)
&& subsystem_resource->startingTemperature == -1.0f
)
{
DebugStream << subsystem_name << " missing StartingTemperature!";
return False;
}
if (
!model_file->GetEntry(subsystem_name, "DegradationTemperature",
&subsystem_resource->degradationTemperature)
&& subsystem_resource->degradationTemperature == -1.0f
)
{
DebugStream << subsystem_name << " missing DegradationTemperature!";
return False;
}
if (
!model_file->GetEntry(subsystem_name, "FailureTemperature",
&subsystem_resource->failureTemperature)
&& subsystem_resource->failureTemperature == -1.0f
)
{
DebugStream << subsystem_name << " missing FailureTemperature!";
return False;
}
if (
!model_file->GetEntry(subsystem_name, "ThermalConductance",
&subsystem_resource->thermalConductance)
&& subsystem_resource->thermalConductance == -1.0f
)
{
DebugStream << subsystem_name << " missing ThermalConductance!";
return False;
}
if (
!model_file->GetEntry(subsystem_name, "ThermalMass",
&subsystem_resource->thermalMass)
&& subsystem_resource->thermalMass == -1.0f
)
{
DebugStream << subsystem_name << " missing ThermalMass!";
return False;
}
//
// "HeatSink" names the segment that this sink links to. Resolve the
// name to a segment index (biased by +2 to leave room for sentinels).
//
const char *heatSinkName = "Unspecified";
int found = model_file->GetEntry(subsystem_name, "HeatSink", &heatSinkName);
if (!found && subsystem_resource->heatSinkIndex == -1)
{
DebugStream << subsystem_name << " missing HeatSink!";
return False;
}
if (strcmp(heatSinkName, "Unspecified") != 0)
{
subsystem_resource->heatSinkIndex =
Get_Segment_Index(model_file, model_name, directories, heatSinkName); // FUN_004215b0
}
if (subsystem_resource->heatSinkIndex < 0)
{
DebugStream << subsystem_name << " has an invalid heat sink!";
return False;
}
subsystem_resource->heatSinkIndex += 2;
Check_Fpu();
return True;
}
//===========================================================================//
// WAVE 2 factory bridges -- construct a real heat subsystem for mech.cpp's
// roster factory (which can't #include heat.hpp: its local RECON_SUBSYS stubs
// collide). Keep the binary's alloc SIZE; the Check guard turns a sizeof
// overrun (placement-new heap corruption) into an immediate assert.
//===========================================================================//
Subsystem *CreateCondenserSubsystem(Mech *owner, int id, void *seg)
{
Check(sizeof(Condenser) <= 0x230);
return (Subsystem *) new (Memory::Allocate(0x230))
Condenser(owner, id, (Condenser::SubsystemResource *)seg, Condenser::DefaultData);
}
Subsystem *CreateHeatSinkBankSubsystem(Mech *owner, int id, void *seg)
{
Check(sizeof(HeatSink) <= 0x1e4);
return (Subsystem *) new (Memory::Allocate(0x1e4))
HeatSink(owner, id, (HeatSink::SubsystemResource *)seg, HeatSink::DefaultData);
}