//===========================================================================// // File: reservr.cpp // // Project: BattleTech Brick: Mech subsystems // // Contents: Reservoir -- the coolant store // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. // // All Rights reserved worldwide // // This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // //===========================================================================// #include #pragma hdrstop #if !defined(RESERVR_HPP) # include #endif #if !defined(MECH_HPP) # include #endif #if !defined(MECHWEAP_HPP) # include #endif #include Derivation Reservoir::ClassDerivations( HeatSink::ClassDerivations, "Reservoir" ); // // The cockpit coolant-flush button (id 4, "InjectCoolant"); ToggleCooling // (id 3) is inherited from the HeatSink chain. // const Reservoir::HandlerEntry Reservoir::MessageHandlerEntries[]= { MESSAGE_ENTRY(Reservoir, InjectCoolant) }; Reservoir::MessageHandlerSet Reservoir::MessageHandlers( ELEMENTS(Reservoir::MessageHandlerEntries), Reservoir::MessageHandlerEntries, HeatSink::MessageHandlers ); Reservoir::SharedData Reservoir::DefaultData( Reservoir::ClassDerivations, Reservoir::MessageHandlers, Subsystem::AttributeIndex, Subsystem::StateCount ); // //############################################################################# // The coolant store (binary ctor @4af408): CoolantCapacity overlays the // inherited HeatSink thermalCapacity slot; the charge starts full; the flush // flow scale is zero (a reservoir never conducts like an ordinary sink). A // master reservoir registers the CoolantSimulation performance. // // Deferred with the AggregateHeatSink wave: the authentic master path also // attaches the reservoir into the central bank's radiator loop and rescales // the capacity by 0.05 x the bank's heat-sink count (the central sink here is // still a plain HeatSink, which has no count) -- until then the streamed // capacity is used as-is (a larger tank than authentic; noted). //############################################################################# // Reservoir::Reservoir( Mech *owner, int subsystem_ID, SubsystemResource *r, SharedData &shared_data ): HeatSink(owner, subsystem_ID, r, shared_data) { Check(owner); Check_Pointer(r); reservoirAlarm.Initialize(2); squirtEfficiency = 0.5f; thermalCapacity = r->coolantCapacity; coolantSquirtMass = r->coolantSquirtMass; coolantLevel = thermalCapacity; coolantFlowScale = 0.0f; injectAccumulator = 0.0f; reservoirAlarm.SetLevel(0); if (getenv("BT_POWER_LOG")) { DEBUG_STREAM << "[resv] '" << GetName() << "' capacity=" << thermalCapacity << " squirtMass=" << coolantSquirtMass << endl << flush; } if (owner->GetInstance() != Entity::ReplicantInstance) { SetPerformance(&Reservoir::CoolantSimulation); // // The authentic master path scales the tank by the bank's aggregate // heat-sink count: capacity = 0.05 x count x streamed (binary // @4af408, CoolantCapacityScale byte-verified 0.05). The linked sink // (from the resource) IS the bank. (The bank-side Attach that routes // its DrawCoolant top-ups here joins the attach wave.) // HeatSink *link = (HeatSink *)linkedSinks.Resolve(); if (link != NULL && link->IsDerivedFrom(AggregateHeatSink::ClassDerivations)) { Scalar masterScale = (Scalar)((AggregateHeatSink *)link)->GetHeatSinkCount(); thermalCapacity = 0.05f * masterScale * thermalCapacity; coolantLevel = thermalCapacity; if (getenv("BT_POWER_LOG")) { DEBUG_STREAM << "[resv] capacity rescaled by bank count -> " << thermalCapacity << endl << flush; } } } Check_Fpu(); } Reservoir::~Reservoir() { } Logical Reservoir::TestClass(Mech &) { return True; } Logical Reservoir::TestInstance() const { return IsDerivedFrom(ClassDerivations); } // //############################################################################# // DrawCoolant -- the SOURCE (binary @4af3b0): hand out up to coolantLevel of // the requested amount and deduct it from the charge. //############################################################################# // Scalar Reservoir::DrawCoolant(Scalar requested) { Check(this); Scalar supplied = 0.0f; if (requested >= 0.0f) { supplied = requested; if (coolantLevel < requested) { supplied = coolantLevel; } } coolantLevel -= supplied; return supplied; } // //############################################################################# // InjectCoolantMessageHandler -- the cockpit coolant-flush button (binary // @4aee70, id 4): novice-locked. Release drops the inject alarm (flush OFF); // press raises it when the tank holds charge (the flush-cloud effect joins // the psfx wave) and zeroes the elapsed accumulator. //############################################################################# // void Reservoir::InjectCoolantMessageHandler(ReceiverDataMessageOf *message) { Check(this); Check(message); if (NoviceLockout()) { return; } if (message->dataContents < 1) { reservoirAlarm.SetLevel(0); // release -> flush OFF } else { if (reservoirAlarm.GetLevel() != 1 && coolantLevel > 0.0f) { reservoirAlarm.SetLevel(1); // flush ON if (getenv("BT_MECH_LOG")) { DEBUG_STREAM << "[resv] FLUSH ON via button (charge=" << coolantLevel << ")" << endl << flush; } } injectAccumulator = 0.0f; } ForceUpdate(); } // //############################################################################# // CoolantSimulation -- the registered Performance (binary @4aef78): while // injection is active, accumulate elapsed time and run the coolant // distribution. The InjectCoolant COCKPIT BUTTON (the id-4 message handler // that raises the alarm) joins with the cockpit-button message wave; the DEV // hook BT_FORCE_FLUSH=1 raises it here so the flush machinery is exercisable // headlessly (one press ~4 s after the sim starts ticking). //############################################################################# // void Reservoir::CoolantSimulation(Scalar time_slice) { Check(this); { static int forceFlush = -1; if (forceFlush < 0) { forceFlush = (getenv("BT_FORCE_FLUSH") != NULL) ? 1 : 0; } if (forceFlush == 1) { static Scalar armAccum = 0.0f; armAccum += time_slice; if (armAccum >= 4.0f && reservoirAlarm.GetLevel() != 1 && coolantLevel > 0.0f) { forceFlush = 2; reservoirAlarm.SetLevel(1); injectAccumulator = 0.0f; if (getenv("BT_MECH_LOG")) { DEBUG_STREAM << "[resv] FLUSH ON (charge=" << coolantLevel << ")" << endl << flush; } } } } if (reservoirAlarm.GetLevel() == 1) { injectAccumulator += time_slice; InjectCoolant(time_slice); } } // //############################################################################# // InjectCoolant -- the coolant-flush distribution (binary @4aefa4). Walk the // roster gathering the flush targets in the authentic pass order -- the // condensers, then the weapons, then every heat sink, then the linked master // (the duplicate visits are intentional weighting) -- and squirt // (coolantSquirtMass x the target's coolantFlowScale x dt) into each, // crediting a negative pending-heat chill for the moved mass. Squirts only // ever leave the tank; the chill only ever cools. //############################################################################# // void Reservoir::InjectCoolant(Scalar time_slice) { Check(this); if (fabs(coolantLevel) <= 1.0e-4f) // the tank is empty { return; } enum { kMaxWork = 96 }; HeatSink *work[kMaxWork]; int workCount = 0; Entity *own = (Entity *)owner; int count = own->GetSubsystemCount(); int i; for (i = 2; i < count && workCount < kMaxWork; ++i) { Subsystem *s = own->GetSubsystem(i); if (s != NULL && s->IsDerivedFrom(Condenser::ClassDerivations)) { work[workCount++] = (HeatSink *)s; } } for (i = 2; i < count && workCount < kMaxWork; ++i) { Subsystem *s = own->GetSubsystem(i); if (s != NULL && s->IsDerivedFrom(MechWeapon::ClassDerivations)) { work[workCount++] = (HeatSink *)s; } } for (i = 2; i < count && workCount < kMaxWork; ++i) { Subsystem *s = own->GetSubsystem(i); if (s != NULL && s->IsDerivedFrom(HeatSink::ClassDerivations)) { work[workCount++] = (HeatSink *)s; } } { HeatSink *link = (HeatSink *)linkedSinks.Resolve(); if (link != NULL && workCount < kMaxWork) { work[workCount++] = link; } } for (int w = 0; w < workCount; ++w) { if (fabs(coolantLevel) <= 1.0e-4f) // ran dry mid-pass { return; } HeatSink *sink = work[w]; if (sink->coolantFlowScale == 0.0f) { continue; } // // squirt = -(squirtMass x flowScale x dt), clamped to [-coolantLevel, 0]. // Scalar move = -coolantSquirtMass * sink->coolantFlowScale * time_slice; Scalar lo = -coolantLevel; if (move < lo) move = lo; if (move > 0.0f) move = 0.0f; // // The heat delta riding the moved mass (computed BEFORE the level // updates, as in the binary). // Scalar den = (fabs(sink->coolantLevel) > 1.0e-4f) ? sink->coolantLevel : sink->thermalCapacity; Scalar moved = (move < 0.0f) ? -move : move; Scalar fracSink = moved / den; Scalar fracRes = moved / coolantLevel; Scalar heatDelta = sink->heatEnergy * fracSink - heatEnergy * fracRes; coolantLevel += move; // the reservoir drains (move <= 0) sink->coolantLevel -= move; // the sink gains if (sink->coolantLevel >= 0.0f) { if (sink->coolantLevel > sink->thermalCapacity) { sink->coolantLevel = sink->thermalCapacity; } } else { sink->coolantLevel = 0.0f; } Scalar cap = sink->thermalMass * startingTemperature; if (heatDelta > cap) { heatDelta = cap; } Scalar chill = -heatDelta; if (chill > 0.0f) { chill = 0.0f; // the flush only ever COOLS } sink->pendingHeat += chill; } }