//===========================================================================// // File: emitter.cpp // // Project: BattleTech Brick: Mech weapons // // Contents: Emitter -- the energy-weapon (beam) base // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. // // All Rights reserved worldwide // // This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // //===========================================================================// #include #pragma hdrstop #if !defined(EMITTER_HPP) # include #endif #if !defined(MECH_HPP) # include #endif Derivation Emitter::ClassDerivations( MechWeapon::ClassDerivations, "Emitter" ); // //############################################################################# // Attribute Support. Emitter publishes the beam charge level (ChargeLevel, at // the authentic 0x1D past the MechWeapon table end): the HUD weapon-charge // gauge binds it, and -- load-bearing -- GaussRifle's AttributeIndex chains // THIS index (GAUSS.CPP), and PPC::DefaultData binds it via the inherited // PPC::AttributeIndex. It MUST be a real defined index (a declared-but- // undefined Emitter::AttributeIndex leaves activeAttributeIndex NULL and // faults the first GetAttributePointer on any PPC/Gauss). Chained to the // MechWeapon index so every energy weapon exposes the full weapon table // (TriggerState / PercentDone / ...). //############################################################################# // const Emitter::IndexEntry Emitter::AttributePointers[]= { ATTRIBUTE_ENTRY(Emitter, ChargeLevel, chargeLevel) }; Emitter::AttributeIndexSet Emitter::AttributeIndex( ELEMENTS(Emitter::AttributePointers), Emitter::AttributePointers, MechWeapon::AttributeIndex ); Emitter::SharedData Emitter::DefaultData( Emitter::ClassDerivations, MechWeapon::MessageHandlers, Emitter::AttributeIndex, Subsystem::StateCount ); Emitter::Emitter( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data ): MechWeapon(owner, subsystem_ID, subsystem_resource, shared_data) { Check(owner); Check_Pointer(subsystem_resource); chargeLevel = 0.0f; dischargeTime = subsystem_resource->dischargeTime; dischargeTimer = 0.0f; // // Install the beam-weapon fire state machine (a replicant copy is driven by // console updates / ServiceDischarge instead, still staged). // if (owner->GetInstance() != Entity::ReplicantInstance) { SetPerformance(&Emitter::EmitterSimulation); } Check_Fpu(); } Emitter::~Emitter() { } Logical Emitter::TestClass(Mech &) { return True; } Logical Emitter::TestInstance() const { return IsDerivedFrom(ClassDerivations); } // //############################################################################# // FireWeapon The energy-beam discharge (PARTIAL -- binary @004bace8). The // authentic body: re-arm the beam-on countdown, compute the per-shot damage / // heat from the charge energy (0.5*V^2*EC closed forms), dump the heat into // the inherited thermal accumulator, spend the charge, build the beam // (muzzle -> target) and submit the Damage record at the owner's target. // The energy/heat algebra needs the electrical charge model (TrackSeekVoltage // / seekVoltage / generator -- the powersub wave), and the beam/damage need // the targeting slot + renderer. This partial performs the DISCHARGE // bookkeeping -- countdown re-arm, charge spent, recoil loaded so the recharge // dial animates -- so the fire state machine runs end-to-end. //############################################################################# // void Emitter::FireWeapon() { Check(this); dischargeTimer = dischargeTime; chargeLevel = 0.0f; recoil = rechargeRate; // full recoil -> dial 0, decays in Loading ComputeOutputVoltage(); // // Dump the firing heat into our own thermal accumulator; the HeatSink step // absorbs it next frame and conducts it toward the linked Condenser bank. // The heat chain is 1e7-unit-native (the BT411 calibration audit): the // authored heatCostToFire is the closed form's full-charge value / 1e7 // (PPC: 11 -> 1.1e8 units -> +632 K on its own 174000-mass sink). // (PARTIAL: the (1-dF)*E*chargeRatio^2 charge-scaling joins with the // electrical-charge wave.) // AddPendingHeat(heatCostToFire * 10000000.0f); if (getenv("BT_MECH_LOG")) { DEBUG_STREAM << "[fire] '" << GetName() << "' FIRED (discharge=" << dischargeTime << "s recharge=" << rechargeRate << "s heat+=" << heatCostToFire << " T=" << CurrentTemperatureOf() << ")" << endl << flush; } } // //############################################################################# // ResetFiringState (binary @004ba9a8) -- the beam has finished: drop back to // Loading so the recharge cycle begins. //############################################################################# // void Emitter::ResetFiringState() { Check(this); weaponAlarm.SetLevel(LoadingState); } // //############################################################################# // EmitterSimulation The beam-weapon per-frame fire state machine (binary // @004baa88). The weapon state is carried in the weapon alarm level: // 0 = Firing, 2 = Loaded (ready), 3 = Loading, 4 = the trigger-during-load // blip. PARTIAL: the leading PoweredSubsystem electrical step and the // destroyed / heat-failure / dead-mech hard gates are deferred with the // power / heat / damage waves; the Loading charge uses the authored // RechargeRate seconds directly (recoil decay -> ComputeOutputVoltage dial) // instead of the TrackSeekVoltage generator integration; the Loaded->Firing // gate honors viewFireEnable (the look-view arm) -- the HasActiveTarget gate // joins it with the targeting wave. // // DEV hook BT_FORCE_FIRE=1: pulses the trigger whenever the weapon is Loaded // (press while Loaded, release otherwise), so every armed weapon auto-fires at // its authored recharge cadence -- the headless fire-cycle verification. //############################################################################# // void Emitter::EmitterSimulation(Scalar time_slice) { Check(this); // // The PoweredSubsystem step first (binary @004baa88 head): the HeatSink // thermal absorb/conduct + the electrical state machine. // PoweredSubsystem::PoweredSubsystemSimulation(time_slice); // // Hard failure (@4baab9): weapon DESTROYED or its own sink at FailureHeat // -> drop the beam state and the charge, and hold there. Unlike the // ballistic roach-motel this recovers by itself: once conduction cools the // sink below the failure threshold the gate stops firing and the weapon // resumes from Loading. (The owning-mech-disabled half joins with the // damage wave.) // if (GetSimulationState() == 1 || GetHeatState() == FailureHeat) { if (getenv("BT_MECH_LOG") && GetWeaponState() != LoadingState) { DEBUG_STREAM << "[fire] '" << GetName() << "' THERMAL SHUTDOWN (T=" << CurrentTemperatureOf() << ")" << endl << flush; } ResetFiringState(); chargeLevel = 0.0f; ComputeOutputVoltage(); Check_Fpu(); return; } { static int forceFire = -1; if (forceFire < 0) { forceFire = (getenv("BT_FORCE_FIRE") != NULL) ? 1 : 0; } if (forceFire) { fireImpulse = (GetWeaponState() == LoadedState) ? 1.0f : 0.0f; } } Logical fireEdge = CheckFireEdge(); switch (GetWeaponState()) { case FiringState: // // Count the beam-on timer down; when it expires, drop to Loading. // dischargeTimer -= time_slice; if (dischargeTimer <= 0.0f) { ResetFiringState(); } break; case LoadedState: if (fireEdge) { if (viewFireEnable) { weaponAlarm.SetLevel(FiringState); FireWeapon(); } else { weaponAlarm.SetLevel(TriggerDuringLoadState); weaponAlarm.SetLevel(LoadedState); } } break; case LoadingState: if (fireEdge) { weaponAlarm.SetLevel(TriggerDuringLoadState); weaponAlarm.SetLevel(LoadingState); } // // Recharge only while the electrical supply is Ready (the authentic // charge integration gates on the voltage state): decay the recoil // over the authored RechargeRate seconds; the dial rises 0 -> 1. // if (GetVoltageState() == Ready) { recoil -= time_slice; if (recoil <= 0.0f) { recoil = 0.0f; } } ComputeOutputVoltage(); if (recoil == 0.0f) { weaponAlarm.SetLevel(LoadedState); if (getenv("BT_MECH_LOG")) { DEBUG_STREAM << "[fire] '" << GetName() << "' LOADED (T=" << CurrentTemperatureOf() << ")" << endl << flush; } } break; default: break; } Check_Fpu(); } // //############################################################################# // CreateStreamedSubsystem Model-load-time construction. Not yet reconstructed. //############################################################################# // int Emitter::CreateStreamedSubsystem( ResourceFile *, NotationFile *, const char *, const char *, SubsystemResource *, NotationFile *, const ResourceDirectories *, int ) { Fail("Emitter::CreateStreamedSubsystem -- emitter.cpp not yet reconstructed"); return 0; }