//===========================================================================// // File: powersub.cpp // // Project: BattleTech Brick: Mech subsystems // // Contents: PoweredSubsystem -- a HeatSink drawing electrical power // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. // // All Rights reserved worldwide // // This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // //===========================================================================// #include #pragma hdrstop #if !defined(POWERSUB_HPP) # include #endif #if !defined(MECH_HPP) # include #endif // //############################################################################# // Shared data support //############################################################################# // Derivation PoweredSubsystem::ClassDerivations( HeatSink::ClassDerivations, "PoweredSubsystem" ); PoweredSubsystem::SharedData PoweredSubsystem::DefaultData( PoweredSubsystem::ClassDerivations, Subsystem::MessageHandlers, Subsystem::AttributeIndex, Subsystem::StateCount ); // //############################################################################# // A HeatSink that draws electrical power from a generator (binary ctor // @004b0f74). Resolves the "VoltageSource" roster index to the powering // generator, attaches the tap, and primes the electrical state machine. The // voltageSourceIndex indexes the owner mech's SUBSYSTEM ROSTER (the same // index space the AmmoBin link uses) -- the roster slots ahead of this // subsystem are already constructed by the segment walk, and the shipped // stream orders the generators first. //############################################################################# // PoweredSubsystem::PoweredSubsystem( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data ): HeatSink(owner, subsystem_ID, subsystem_resource, shared_data), voltageSource(), electricalStateAlarm(5), modeAlarm(3) { Check(owner); Check_Pointer(subsystem_resource); inputVoltage = 0.0f; outputVoltage = 0.0f; ratedVoltage = 0.0f; thermalResistivityCoefficient = subsystem_resource->thermalResistivityCoefficient; startTime = subsystem_resource->startTime; startTimer = startTime; voltageScale = 1.0f; // // Resolve the voltage source from the roster and attach the tap. // Subsystem *source = NULL; if (subsystem_resource->voltageSourceIndex >= 0 && subsystem_resource->voltageSourceIndex < owner->GetSubsystemCount()) { source = owner->GetSubsystem(subsystem_resource->voltageSourceIndex); } if (source != NULL) { AttachToVoltageSource(source); } if (getenv("BT_POWER_LOG")) { DEBUG_STREAM << "[power] '" << GetName() << "' srcIdx=" << subsystem_resource->voltageSourceIndex << " -> "; if (source != NULL) { DEBUG_STREAM << source->GetName(); } else { DEBUG_STREAM << ""; } DEBUG_STREAM << " startTime=" << startTime << endl << flush; } electricalStateAlarm.SetLevel(Ready); modeAlarm.SetLevel(Connected); // // A master (non-replicant) instance runs the per-frame electrical // simulation. Derived subsystems (the weapons, Sensor, ...) override with // their own Performance in their ctors, each of which chains this step. // if (owner->GetInstance() != Entity::ReplicantInstance) { SetPerformance(&PoweredSubsystem::PoweredSubsystemSimulation); } Check_Fpu(); } // //############################################################################# //############################################################################# // PoweredSubsystem::~PoweredSubsystem() { } // //############################################################################# //############################################################################# // void PoweredSubsystem::ResetToInitialState(Logical powered) { Check(this); HeatSink::ResetToInitialState(powered); inputVoltage = 0.0f; outputVoltage = 0.0f; electricalStateAlarm.SetLevel(0); modeAlarm.SetLevel(0); } // //############################################################################# //############################################################################# // Logical PoweredSubsystem::TestClass(Mech &) { return True; } Logical PoweredSubsystem::TestInstance() const { return IsDerivedFrom(ClassDerivations); } // //############################################################################# // AttachToVoltageSource Link this subsystem to its powering generator // (binary @004b0dd8): take a tap on the generator (-1 when every tap is // taken) and hold the live connection. //############################################################################# // int PoweredSubsystem::AttachToVoltageSource(Subsystem *source) { Check(this); Check(source); Generator *generator = (Generator *)source; if (generator->TapVoltageSource() != 0) { return -1; } voltageSource.Add(source); inputVoltage = generator->MeasuredVoltage(); return 0; } // //############################################################################# // ChargeTimeScale -- the heat/firepower feedback (binary @004b0d50): // rise = max(0, sourceTemperature - sourceStartingTemperature) // scale = max(voltageScale, // (thermalResistivityCoefficient * rise + 1) * voltageScale) // A hot generator stretches the exponential charge constant, so recharging // slows exactly when the electrical plant is cooking. //############################################################################# // Scalar PoweredSubsystem::ChargeTimeScale() { Check(this); Generator *source = (Generator *)voltageSource.Resolve(); if (source == NULL) { return voltageScale; } Scalar rise = source->CurrentTemperatureOf() - source->StartingTemperatureOf(); if (rise < 0.0f) { rise = 0.0f; } Scalar stretched = (thermalResistivityCoefficient * rise + 1.0f) * voltageScale; return (stretched > voltageScale) ? stretched : voltageScale; } // //############################################################################# // ForceShortRecovery (binary @004b11bc): on a short event, drive the powering // generator to Shorted and clear its output; GeneratorSimulation then runs // the short-recovery timer back to Ready. Both @4ac9c8 calls in the binary // are the not-novice experience predicate (this part and its source share // the same mech, hence the same player) -- novice cockpits never see // electrical shorts. //############################################################################# // void PoweredSubsystem::ForceShortRecovery() { Check(this); if (NoviceLockout()) { return; } Generator *source = (Generator *)voltageSource.Resolve(); if (source != NULL) { source->ForceShort(); if (getenv("BT_POWER_LOG") || getenv("BT_MECH_LOG")) { DEBUG_STREAM << "[short] '" << source->GetName() << "' SHORTED by special damage" << endl << flush; } } } // //############################################################################# // PoweredSubsystemSimulation -- the per-frame electrical step (binary // @004b0bd0). Runs the HeatSink thermal step, then advances the electrical // state machine from the state of the powering generator. // // PARTIAL: the AutoConnect replacement-generator hunt (modeAlarm AutoConnect + // the status-flag gate) joins with the damage wave. //############################################################################# // void PoweredSubsystem::PoweredSubsystemSimulation(Scalar time_slice) { Check(this); HeatSink::HeatSinkSimulation(time_slice); Generator *source = (Generator *)voltageSource.Resolve(); if (source == NULL) { electricalStateAlarm.SetLevel(NoVoltage); } else { if (source->GeneratorStateOf() == Generator::GeneratorShorted) { electricalStateAlarm.SetLevel(Shorted); } if (source->GeneratorStateOf() == Generator::GeneratorStarting || source->GeneratorStateOf() == Generator::GeneratorFailed) { electricalStateAlarm.SetLevel(GeneratorOff); } } switch (electricalStateAlarm.GetLevel()) { case Starting: startTimer += time_slice; if (startTime <= startTimer) { electricalStateAlarm.SetLevel(Ready); } break; case NoVoltage: if (source != NULL) { electricalStateAlarm.SetLevel(Starting); startTimer = 0.0f; } break; case Shorted: case GeneratorOff: if (source != NULL && source->GeneratorStateOf() == Generator::GeneratorReady) { electricalStateAlarm.SetLevel(Starting); startTimer = 0.0f; } break; } if (source != NULL) { inputVoltage = source->MeasuredVoltage(); } Check_Fpu(); } //########################################################################### //############################## Generator ############################# //########################################################################### // //############################################################################# // Shared data support //############################################################################# // Derivation Generator::ClassDerivations( HeatSink::ClassDerivations, "Generator" ); Generator::SharedData Generator::DefaultData( Generator::ClassDerivations, Subsystem::MessageHandlers, Subsystem::AttributeIndex, Subsystem::StateCount ); // //############################################################################# // The generator -- the voltage source loads tap. //############################################################################# // Generator::Generator( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data ): HeatSink(owner, subsystem_ID, subsystem_resource, shared_data), stateAlarm(5) { Check(owner); Check_Pointer(subsystem_resource); ratedVoltage = subsystem_resource->ratedVoltage; outputVoltage = ratedVoltage; maxTapCount = subsystem_resource->maxTapCount; currentTapCount = 0; percentVoltageAvailable = 1.0f; startTime = subsystem_resource->startTime; startTimer = startTime; stateAlarm.SetLevel(GeneratorReady); generatorOn = 1; shortRecoveryTime = subsystem_resource->shortRecoveryTime; shortTimer = shortRecoveryTime; // // Generator number from the last character of the segment name // ('A' -> 1, 'B' -> 2, ...). // const char *name = GetName(); generatorNumber = name[strlen(name) - 1] - 0x40; // // Install the generator's per-frame electrical Performance. // if (owner->GetInstance() != Entity::ReplicantInstance) { SetPerformance(&Generator::GeneratorSimulation); } Check_Fpu(); } // //############################################################################# //############################################################################# // Generator::~Generator() { } // //############################################################################# //############################################################################# // Logical Generator::TestClass(Mech &) { return True; } Logical Generator::TestInstance() const { return IsDerivedFrom(ClassDerivations); } // //############################################################################# //############################################################################# // void Generator::ResetToInitialState() { Check(this); HeatSink::ResetToInitialState(True); outputVoltage = ratedVoltage; currentTapCount = 0; percentVoltageAvailable = 1.0f; startTimer = startTime; shortTimer = shortRecoveryTime; generatorOn = 1; stateAlarm.SetLevel(GeneratorReady); } // //############################################################################# // GeneratorSimulation -- the generator's per-frame step (PARTIAL). Runs the // HeatSink thermal step and the start/short-recovery timers. The authentic // load model (output voltage sag under tap load / I^2R self-heat feeding the // charge integration) joins with the electrical-charge wave // (TrackSeekVoltage). A healthy generator holds GeneratorReady at its rated // voltage. //############################################################################# // void Generator::GeneratorSimulation(Scalar time_slice) { Check(this); HeatSink::HeatSinkSimulation(time_slice); switch (stateAlarm.GetLevel()) { case GeneratorStarting: startTimer += time_slice; if (startTime <= startTimer) { stateAlarm.SetLevel(GeneratorReady); outputVoltage = ratedVoltage; } break; case GeneratorShorted: shortTimer -= time_slice; if (shortTimer <= 0.0f) { shortTimer = shortRecoveryTime; stateAlarm.SetLevel(GeneratorStarting); startTimer = 0.0f; } break; default: break; } Check_Fpu(); } //########################################################################### //############################ PowerWatcher ############################ //########################################################################### Derivation PowerWatcher::ClassDerivations( HeatWatcher::ClassDerivations, "PowerWatcher" ); PowerWatcher::SharedData PowerWatcher::DefaultData( PowerWatcher::ClassDerivations, Subsystem::MessageHandlers, Subsystem::AttributeIndex, Subsystem::StateCount ); PowerWatcher::PowerWatcher( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data ): HeatWatcher(owner, subsystem_ID, subsystem_resource, shared_data), watchdogAlarm(5) { Check(owner); Check_Pointer(subsystem_resource); // // minVoltage is a scaled fraction of the watched supply; the exact scale // constant is a tuning value (stored 1:1 here until located). // minVoltage = subsystem_resource->minVoltagePercent; Check_Fpu(); } PowerWatcher::~PowerWatcher() { } Logical PowerWatcher::TestClass(Mech &) { return True; } Logical PowerWatcher::TestInstance() const { return IsDerivedFrom(ClassDerivations); } void PowerWatcher::ResetToInitialState(Logical powered) { Check(this); HeatWatcher::ResetToInitialState(powered); watchdogAlarm.SetLevel(0); } // // Per-frame supply-voltage watchdog. Not yet reconstructed. // void PowerWatcher::Simulation(Scalar) { Fail("PowerWatcher::Simulation -- powersub.cpp not yet reconstructed"); }