CoolingLoopConnection::Update wrote a constant 0 -- the documented inert stub -- because HeatSink::coolantAvailable, HeatSink::linkedSinks and Condenser::condenserNumber are protected with no accessor, so every MFD panel showed OFF where the shipped cockpit shows the loop number. heat.hpp now publishes GetCoolantAvailable(), ResolveCoolingMaster() and GetCondenserNumber(), and the connection follows the binary: a sink with coolant reports the loop number of the Condenser it is plumbed to, otherwise frame 0. Same shape as PowerSourceConnection beside it, which was already live and already matched. The box now reads 4 / A against the shipped binary's 4 / A, pixel for pixel. head missing extra Mfd1 1331 -> 1107 1776 -> 1646 Mfd2 1908 -> 1616 2196 -> 1981 Mfd3 730 -> 584 1119 -> 1014 Cockpit: 96.6% identical / 96% coverage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
577 lines
18 KiB
C++
577 lines
18 KiB
C++
//===========================================================================//
|
|
// File: heat.hpp //
|
|
// Project: BattleTech //
|
|
// Contents: Implementation details for heatable subsystems //
|
|
//---------------------------------------------------------------------------//
|
|
// Date Who Modification //
|
|
// -------- --- ---------------------------------------------------------- //
|
|
// //
|
|
//---------------------------------------------------------------------------//
|
|
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
|
// All Rights reserved worldwide //
|
|
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
|
//===========================================================================//
|
|
|
|
#if !defined(HEAT_HPP)
|
|
# define HEAT_HPP
|
|
|
|
# if !defined(MECHSUB_HPP)
|
|
# include <mechsub.hpp>
|
|
# endif
|
|
|
|
# if !defined(AVERAGE_HPP)
|
|
# include <average.hpp>
|
|
# endif
|
|
|
|
//##################### Forward Class Declarations #######################
|
|
class Mech;
|
|
|
|
//###########################################################################
|
|
//################## HeatableSubsystem Model Resource ###################
|
|
//###########################################################################
|
|
|
|
struct HeatableSubsystem__SubsystemResource:
|
|
public MechSubsystem::SubsystemResource
|
|
{
|
|
Scalar startingTemperature;
|
|
Scalar degradationTemperature;
|
|
Scalar failureTemperature;
|
|
Scalar thermalConductance;
|
|
Scalar thermalMass;
|
|
};
|
|
|
|
//###########################################################################
|
|
//######################## HeatableSubsystem ############################
|
|
//###########################################################################
|
|
|
|
class HeatableSubsystem:
|
|
public MechSubsystem
|
|
{
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Shared Data Support
|
|
//
|
|
public:
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Attribute Support. The cockpit gauges bind these by NAME through the
|
|
// interpreter (ParseAttribute -> GetAttributePointer): the 1995
|
|
// L4GAUGE.CFG spellings are the enum names. The whole heat/power chain
|
|
// publishes IDs 2..0x0E so PoweredSubsystem::NextAttributeID lands on the
|
|
// authentic 0x0F -- Sensor's table (surviving SENSOR.HPP) and
|
|
// MechWeapon's pinned 0x12 pads are both numbered off it. CONTIGUITY IS
|
|
// LOAD-BEARING: AttributeIndexSet::Build leaves uncovered gap slots
|
|
// uninitialized.
|
|
//
|
|
public:
|
|
enum {
|
|
CurrentTemperatureAttributeID = Subsystem::NextAttributeID, // 2
|
|
HeatLoadAttributeID, // 3
|
|
DegradationTemperatureAttributeID, // 4
|
|
FailureTemperatureAttributeID, // 5
|
|
NextAttributeID // 6
|
|
};
|
|
|
|
static const IndexEntry AttributePointers[];
|
|
static AttributeIndexSet AttributeIndex;
|
|
|
|
static Derivation ClassDerivations;
|
|
static SharedData DefaultData;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Test Class Support
|
|
//
|
|
public:
|
|
static Logical
|
|
TestClass(Mech &);
|
|
Logical
|
|
TestInstance() const;
|
|
void
|
|
ResetToInitialState();
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// 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
|
|
);
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Local Data
|
|
//
|
|
protected:
|
|
Scalar currentTemperature;
|
|
Scalar heatLoad;
|
|
Scalar degradationTemperature;
|
|
Scalar failureTemperature;
|
|
};
|
|
|
|
//###########################################################################
|
|
//########################## SubsystemConnection ########################
|
|
//###########################################################################
|
|
//
|
|
// A slot linking a heat sink to its master / linked heat sink.
|
|
//
|
|
class SubsystemConnection
|
|
{
|
|
public:
|
|
SubsystemConnection() { linked = NULL; }
|
|
void Add(Subsystem *s) { linked = s; }
|
|
Subsystem* Resolve() const { return linked; }
|
|
void Clear() { linked = NULL; }
|
|
protected:
|
|
Subsystem *linked;
|
|
int reserved[2];
|
|
};
|
|
|
|
//###########################################################################
|
|
//####################### HeatSink Model Resource #######################
|
|
//###########################################################################
|
|
|
|
struct HeatSink__SubsystemResource:
|
|
public HeatableSubsystem__SubsystemResource
|
|
{
|
|
//
|
|
// WIRE-VERIFIED (raw-stream dump): the roster index of the sink this
|
|
// one conducts its heat into -- the weapons/equipment link to the
|
|
// Condenser bank (slots 4-9), the Condensers to the central HeatSink.
|
|
// This was THE missing ancestry int that shifted every descendant
|
|
// resource block +1 (PoweredSubsystem voltageSourceIndex, the MechWeapon
|
|
// block, ...).
|
|
//
|
|
int linkedSinkIndex;
|
|
};
|
|
|
|
//###########################################################################
|
|
//############################## HeatSink ###############################
|
|
//###########################################################################
|
|
|
|
//###########################################################################
|
|
//################### HeatWatcher Model Resource #######################
|
|
//###########################################################################
|
|
|
|
struct HeatWatcher__SubsystemResource:
|
|
public MechSubsystem__SubsystemResource
|
|
{
|
|
int watchedSubsystem;
|
|
Scalar degradationTemperature;
|
|
Scalar failureTemperature;
|
|
};
|
|
|
|
//###########################################################################
|
|
//############################# HeatWatcher #############################
|
|
//###########################################################################
|
|
//
|
|
// Watches another subsystem's temperature and drives a 3-level alarm. A
|
|
// sibling branch to HeatableSubsystem (derives straight from MechSubsystem);
|
|
// PowerWatcher and the Torso/HUD/Gyroscope leaves descend from it.
|
|
//
|
|
class HeatWatcher:
|
|
public MechSubsystem
|
|
{
|
|
public:
|
|
static Derivation ClassDerivations;
|
|
static SharedData DefaultData;
|
|
|
|
static Logical
|
|
TestClass(Mech &);
|
|
Logical
|
|
TestInstance() const;
|
|
void
|
|
ResetToInitialState(Logical powered);
|
|
|
|
//
|
|
// Respawn restore: base heal + the thermal state back to spawn
|
|
// (temperature/coolant via ResetToInitialState).
|
|
//
|
|
virtual void
|
|
DeathReset(Logical full_reset);
|
|
void
|
|
WatchSimulation(Scalar time_slice);
|
|
|
|
public:
|
|
typedef HeatWatcher__SubsystemResource SubsystemResource;
|
|
|
|
HeatWatcher(
|
|
Mech *owner,
|
|
int subsystem_ID,
|
|
SubsystemResource *subsystem_resource,
|
|
SharedData &shared_data = DefaultData
|
|
);
|
|
~HeatWatcher();
|
|
|
|
protected:
|
|
SubsystemConnection watchedLink;
|
|
int watchedSubsystem;
|
|
Scalar degradationTemperature;
|
|
Scalar failureTemperature;
|
|
AlarmIndicator heatAlarm;
|
|
};
|
|
|
|
class HeatSink:
|
|
public HeatableSubsystem
|
|
{
|
|
friend class Condenser;
|
|
friend class Reservoir;
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Shared Data Support
|
|
//
|
|
public:
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Attribute Support (the coolant family the cockpit bars read).
|
|
//
|
|
public:
|
|
enum {
|
|
CoolantMassAttributeID = HeatableSubsystem::NextAttributeID, // 6
|
|
CoolantCapacityAttributeID, // 7
|
|
CoolantMassLeakRateAttributeID, // 8
|
|
CoolantAvailableAttributeID, // 9
|
|
NextAttributeID // 0x0A
|
|
};
|
|
|
|
static const IndexEntry AttributePointers[];
|
|
static AttributeIndexSet AttributeIndex;
|
|
|
|
static Derivation ClassDerivations;
|
|
static SharedData DefaultData;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Test Class Support
|
|
//
|
|
public:
|
|
static Logical
|
|
TestClass(Mech &);
|
|
Logical
|
|
TestInstance() const;
|
|
void
|
|
ResetToInitialState(Logical powered);
|
|
|
|
//
|
|
// Respawn restore: base heal + the thermal state back to spawn.
|
|
//
|
|
virtual void
|
|
DeathReset(Logical full_reset);
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Messaging Support. The cockpit cooling toggle rides the first
|
|
// subsystem-family message slot (id 3 = Receiver::NextMessageID); the
|
|
// per-class id-4 slot is claimed by each subclass (Condenser MoveValve,
|
|
// Reservoir InjectCoolant, ...).
|
|
//
|
|
public:
|
|
enum {
|
|
ToggleCoolingMessageID = Receiver::NextMessageID, // 3
|
|
NextMessageID // 4
|
|
};
|
|
|
|
static const HandlerEntry MessageHandlerEntries[];
|
|
static MessageHandlerSet MessageHandlers;
|
|
|
|
void
|
|
ToggleCoolingMessageHandler(ReceiverDataMessageOf<int> *message);
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Heat state (carried in heatAlarm, 3 levels). The thresholds are the
|
|
// authored degradation/failure temperatures.
|
|
//
|
|
public:
|
|
enum {
|
|
NormalHeat = 0,
|
|
DegradationHeat,
|
|
FailureHeat,
|
|
HeatStateCount
|
|
};
|
|
|
|
unsigned
|
|
GetHeatState() { Check(this); return heatAlarm.GetLevel(); }
|
|
Scalar
|
|
CurrentTemperatureOf() { Check(this); return currentTemperature; }
|
|
Scalar
|
|
StartingTemperatureOf() { Check(this); return startingTemperature; }
|
|
void
|
|
AddPendingHeat(Scalar heat)
|
|
{ Check(this); pendingHeat += heat; }
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Simulation Support
|
|
//
|
|
public:
|
|
typedef void
|
|
(HeatSink::*Performance)(Scalar time_slice);
|
|
void
|
|
SetPerformance(Performance performance)
|
|
{
|
|
Check(this);
|
|
activePerformance = (Simulation::Performance)performance;
|
|
}
|
|
|
|
//
|
|
// The heat-model master switch (binary FUN_004ad7d4): the owning
|
|
// BTPlayer's heatModelOn experience flag, reached through the mech's
|
|
// playerLink. ON only for veteran / expert; a NULL player reads ON.
|
|
//
|
|
Logical
|
|
HeatModelActive();
|
|
|
|
void
|
|
HeatSinkSimulation(Scalar time_slice);
|
|
void
|
|
UpdateHeatLoad();
|
|
void
|
|
ConductHeat(Scalar time_slice);
|
|
Scalar
|
|
ComputeHeatFlow(HeatSink *other, Scalar time_slice);
|
|
void
|
|
UpdateCoolant(Scalar time_slice);
|
|
void
|
|
BalanceCoolant(Scalar time_slice);
|
|
virtual Scalar
|
|
DrawCoolant(Scalar requested);
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Construction and Destruction
|
|
//
|
|
public:
|
|
typedef HeatSink__SubsystemResource SubsystemResource;
|
|
|
|
HeatSink(
|
|
Mech *owner,
|
|
int subsystem_ID,
|
|
SubsystemResource *subsystem_resource,
|
|
SharedData &shared_data = DefaultData
|
|
);
|
|
|
|
~HeatSink();
|
|
|
|
//
|
|
// The cockpit's cooling-loop lamp needs both of these from BT_L4:
|
|
// whether this sink has coolant, and which sink it is plumbed to
|
|
// (the loop master, whose Condenser number IS the lamp's frame).
|
|
//
|
|
int
|
|
GetCoolantAvailable() const { Check(this); return coolantAvailable; }
|
|
Subsystem *
|
|
ResolveCoolingMaster() { Check(this); return linkedSinks.Resolve(); }
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Local Data
|
|
//
|
|
protected:
|
|
Scalar coolantEfficiency;
|
|
Scalar thermalCapacity;
|
|
Scalar coolantLevel;
|
|
Scalar coolantDraw;
|
|
int coolantAvailable;
|
|
int coolantActive;
|
|
Scalar startingTemperature;
|
|
Scalar thermalConductance;
|
|
Scalar filterDecay;
|
|
Scalar thermalMass;
|
|
Scalar heatEnergy;
|
|
Scalar coolantFlowScale;
|
|
Scalar massScale;
|
|
Scalar pendingHeat;
|
|
Scalar radiatedHeat;
|
|
SubsystemConnection linkedSinks;
|
|
AlarmIndicator heatAlarm;
|
|
AverageOf<Scalar> heatFilter;
|
|
};
|
|
|
|
//###########################################################################
|
|
//#################### Condenser Model Resource ########################
|
|
//###########################################################################
|
|
|
|
struct Condenser__SubsystemResource:
|
|
public HeatSink__SubsystemResource
|
|
{
|
|
Scalar refrigerationFactor;
|
|
};
|
|
|
|
//###########################################################################
|
|
//############################## Condenser #############################
|
|
//###########################################################################
|
|
//
|
|
// A HeatSink with active refrigeration (a coolant loop's condenser).
|
|
//
|
|
class Condenser:
|
|
public HeatSink
|
|
{
|
|
public:
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Attribute Support (the valve slider on the Eng page).
|
|
//
|
|
public:
|
|
enum {
|
|
ValveSettingAttributeID = HeatSink::NextAttributeID, // 0x0A
|
|
NextAttributeID
|
|
};
|
|
|
|
static const IndexEntry AttributePointers[];
|
|
static AttributeIndexSet AttributeIndex;
|
|
|
|
static Derivation ClassDerivations;
|
|
static SharedData DefaultData;
|
|
|
|
static Logical
|
|
TestClass(Mech &);
|
|
Logical
|
|
TestInstance() const;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Messaging Support: the cockpit condenser-valve button (binary handler
|
|
// table @0x50E52C, exactly one entry -- id 4, "MoveValve"). Each press
|
|
// cycles this condenser's valve 1 -> 5 -> 50 -> 0 -> 1 and recomputes every
|
|
// condenser's coolant flow share (valve / sum-of-valves).
|
|
//
|
|
public:
|
|
enum {
|
|
MoveValveMessageID = HeatSink::NextMessageID, // 4
|
|
NextMessageID
|
|
};
|
|
|
|
static const HandlerEntry MessageHandlerEntries[];
|
|
static MessageHandlerSet MessageHandlers;
|
|
|
|
void
|
|
MoveValveMessageHandler(ReceiverDataMessageOf<int> *message);
|
|
|
|
//
|
|
// Recompute every condenser's coolantFlowScale as its share of the
|
|
// total valve opening (called by MoveValve, and once at mech spawn so
|
|
// the initial all-1 valves yield equal shares).
|
|
//
|
|
static void
|
|
RecomputeValves(Entity *owner_mech);
|
|
|
|
public:
|
|
typedef Condenser__SubsystemResource SubsystemResource;
|
|
|
|
Condenser(
|
|
Mech *owner,
|
|
int subsystem_ID,
|
|
SubsystemResource *subsystem_resource,
|
|
SharedData &shared_data = DefaultData
|
|
);
|
|
~Condenser();
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Per-frame refrigeration (binary @4ae4d8): the condenser actively pumps
|
|
// heat toward the central bank -- massScale is recomputed each frame as
|
|
// (1 - own damage) * refrigerationFactor, clamped >= 1, so an undamaged
|
|
// condenser behaves refrigerationFactor-times "hotter" in the conduction
|
|
// exchange and chills itself below ambient (which is what cools the
|
|
// weapons equalizing against it). Damage degrades it toward a plain sink.
|
|
//
|
|
public:
|
|
typedef void
|
|
(Condenser::*Performance)(Scalar time_slice);
|
|
void
|
|
SetPerformance(Performance performance)
|
|
{
|
|
Check(this);
|
|
activePerformance = (Simulation::Performance)performance;
|
|
}
|
|
void
|
|
RefrigerationSimulation(Scalar time_slice);
|
|
|
|
//
|
|
// The loop number (1..6) -- the cockpit lamp's image-strip frame.
|
|
//
|
|
int
|
|
GetCondenserNumber() const { Check(this); return condenserNumber; }
|
|
|
|
protected:
|
|
int valveState;
|
|
int condenserNumber;
|
|
Scalar refrigerationFactor;
|
|
};
|
|
|
|
//###########################################################################
|
|
//##################### AggregateHeatSink Resource ######################
|
|
//###########################################################################
|
|
|
|
struct AggregateHeatSink__SubsystemResource:
|
|
public HeatSink__SubsystemResource
|
|
{
|
|
int heatSinkCount;
|
|
};
|
|
|
|
//###########################################################################
|
|
//######################### AggregateHeatSink ###########################
|
|
//###########################################################################
|
|
//
|
|
// The mech's heat-sink BANK (binary classID 0x0BBE -- the value our VDATA
|
|
// enum names HeatSinkClassID; there is no streamed plain-HeatSink class).
|
|
// Holds the aggregate heat-sink count and the ambient-temperature setpoint,
|
|
// and runs the AMBIENT RADIATOR -- the only place heat ever LEAVES the mech.
|
|
//
|
|
class AggregateHeatSink:
|
|
public HeatSink
|
|
{
|
|
public:
|
|
static Derivation ClassDerivations;
|
|
static SharedData DefaultData;
|
|
|
|
static Logical
|
|
TestClass(Mech &);
|
|
Logical
|
|
TestInstance() const;
|
|
|
|
public:
|
|
typedef AggregateHeatSink__SubsystemResource SubsystemResource;
|
|
|
|
AggregateHeatSink(
|
|
Mech *owner,
|
|
int subsystem_ID,
|
|
SubsystemResource *subsystem_resource,
|
|
SharedData &shared_data = DefaultData
|
|
);
|
|
~AggregateHeatSink();
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// The radiator (binary @4ae73c) -- replaces the base HeatSinkSimulation on
|
|
// the bank: absorb, then relax toward the ambient setpoint (the heat EXIT),
|
|
// then top the bank's coolant up from the attached store.
|
|
//
|
|
public:
|
|
typedef void
|
|
(AggregateHeatSink::*Performance)(Scalar time_slice);
|
|
void
|
|
SetPerformance(Performance performance)
|
|
{
|
|
Check(this);
|
|
activePerformance = (Simulation::Performance)performance;
|
|
}
|
|
void
|
|
RadiatorSimulation(Scalar time_slice);
|
|
|
|
int
|
|
GetHeatSinkCount() const { Check(this); return heatSinkCount; }
|
|
|
|
protected:
|
|
int heatSinkCount;
|
|
Scalar ambientTemperature;
|
|
};
|
|
|
|
#endif
|