//===========================================================================// // File: emitter.hpp // // Project: BattleTech Brick: Entity Manager // // Contents: Emitter -- energy/beam weapon base (PPC, GaussRifle derive) // //---------------------------------------------------------------------------// // Date Who Modification // // -------- --- ---------------------------------------------------------- // // --/--/95 JM Initial coding. // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved // // PROPRIETARY AND CONFIDENTIAL // //===========================================================================// // // RECONSTRUCTED from the shipped binary (Ghidra pseudo-C, module cluster // @004ba478-@004bb478, file=bt/emitter.cpp; the abstract-stub @004ba45c is // emitted at the head of this translation unit but belongs to mechweap.hpp). // Cross-referenced HEAVILY against: // * the SURVIVING derived sources PPC.HPP/PPC.CPP and GAUSS.HPP/GAUSS.CPP // (both `: public Emitter`; both chain ctor + FireWeapon to Emitter), and // * the SURVIVING EMITTER.TCP test (names: dischargeVoltage, outputVoltage, // currentLevel, EmitterSimulation, ResetToInitialState), and // * the reconstructed base mechweap.hpp/mechweap.cpp (MechWeapon layout). // See emitter.cpp for per-method @ADDR evidence and the resolved constants. // // Inheritance chain (from CLASSMAP.md + the ctor chain @004bb120 -> @004b99a8): // ... -> PoweredSubsystem -> MechWeapon (vtable @00511d2c, ctor @004b99a8) // -> Emitter (vtable @00512078, ctor @004bb120) // ├─ PPC (vtable @0051214c, ctor @004bb888) // └─ GaussRifle (vtable @00512934) // // The Emitter ctor @004bb120 installs PTR_FUN_00512078 and chains to the // MechWeapon ctor @004b99a8. PPC::FireWeapon (@004bb878) is a one-liner that // tail-calls Emitter::FireWeapon (@004bace8) -- exactly as PPC.CPP shows. // GaussRifle::FireWeapon overrides instead (sets outputVoltage = 0). // // Field offsets in comments are byte offsets into the shipped object. Member // names come from EMITTER.TCP / GAUSS.HPP where available, otherwise inferred // from usage and flagged best-effort. PoweredSubsystem occupies everything // below +0x31c; MechWeapon occupies +0x31c..+0x3ef; Emitter starts at +0x3f0. // #if !defined(EMITTER_HPP) # define EMITTER_HPP #if !defined(MECHWEAP_HPP) # include #endif //##################### Forward Class Declarations ####################### class Mech; class ResourceFile; class NotationFile; //########################################################################### //#################### Emitter Model Resource ####################### //########################################################################### // // Extends the MechWeapon resource record. The Emitter-specific fields begin // at +0x1BC in the parsed record (see CreateStreamedSubsystem @004bb478). // The parser stamps subsystemModelSize = 0x1DC and classID = EmitterClassID // (0xBC8). // struct Emitter__SubsystemResource: public MechWeapon::SubsystemResource { Scalar graphicLength; // +0x1BC "GraphicLength" (beam visual length) Scalar dischargeTime; // +0x1C0 "DischargeTime" (beam-on duration) Scalar seekVoltage[5]; // +0x1C4 "SeekVoltage" (per-index charge curve) int seekVoltageRecommendedIndex;// +0x1D8 "SeekVoltageRecommendedIndex" }; // sizeof == 0x1DC //########################################################################### //######################### Emitter ########################## //########################################################################### // // Base class for charge-and-discharge beam weapons. It accumulates charge // (currentLevel) toward a per-index target (seekVoltage), exposing a // normalised outputVoltage; when fully charged and triggered it discharges, // converting the stored energy into damage + self-heat and projecting a beam // at the owning Mech's current target. (vtable @00512078, ctor @004bb120, // dtor @004bb3c8.) // class Emitter: public MechWeapon { //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Shared Data Support // public: static Derivation ClassDerivations; static Receiver::MessageHandlerSet& GetMessageHandlers(); // inherits MechWeapon's (task #6) static AttributeIndexSet AttributeIndex; static SharedData DefaultData; // Update record (replication) -- extends the MechWeapon record with the // Emitter beam-replication fields appended by WriteUpdateRecord (@004ba65c). struct Emitter__UpdateRecord: public MechWeapon::UpdateRecord { int firingActive; // rec+0x18 <- 0x418 Point3D beamEndpoint; // rec+0x1c <- 0x460 int beamFlag; // rec+0x28 <- 0x46c int targetLocalFlag; // rec+0x2c <- 0x470 // DISASM CORRECTION (task #51, @004ba65c/@004ba568): rec+0x30 is the // TARGET's EntityID -- FUN_00420ef4 (an EntityID copy) from // targetEntity+0x184 (the 1995 Entity::entityID) or DAT_00522524 // (EntityID::Null) -- NOT an RGB colour. The reader resolves it // through the entity index and DROPS the record when a non-null id // is unknown on this host. EntityID targetID; // rec+0x30 int payloadCount; // (AdvanceSeekVoltage) }; typedef Emitter__UpdateRecord UpdateRecord; // (task #10: the old compact "VoltageSource" proxy struct is RETIRED -- // the +0x1d0 plug resolves to the real Generator, whose named members // carry the decomp offsets: outputVoltage@0x1dc, ratedVoltage@0x1d8, // state@0x210, pendingHeat@0x1c8.) protected: // State / flag accessors the recovered bodies read (mapped onto the // MechWeapon weaponAlarm + Simulation flags). int GetWeaponState(); int GetVoltageState(); LWord GetFlags(); int GetFaultState(); void SetDirty(); void ClearDirty(); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Simulation Support // // The per-frame step runs through the Simulation "activePerformance" // member-function pointer (see GAUSS.HPP SetPerformance). EmitterSimulation // is the installed performance for this class. // public: typedef void (Emitter::*Performance)(Scalar time_slice); void SetPerformance(Performance performance) { Check(this); activePerformance = (Simulation::Performance)performance; } // @004baa88 -- the installed per-frame performance: drive the // Loading->Loaded->Firing state machine, recharge, and fire on edge. void EmitterSimulation(Scalar time_slice); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Firing // // The real discharge implementation. PPC::FireWeapon chains straight here // (@004bb878); GaussRifle::FireWeapon overrides it. // protected: virtual void FireWeapon(); // vtable slot 18, @004bace8 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Subsystem / Simulation virtual overrides (vtable @00512078) // // SLOT MAP CORRECTED (task #51): slot 6 is ReadUpdateRecord, not TakeDamage // (@004ba568 resolves an EntityID at rec+0x30 through the entity index and // applies alarm/beam fields -- record semantics; it chains @004b964c, the // MechWeapon slot-6 read). The real Emitter::TakeDamage is slot 9 // @004bafc8 (undecoded -- inherit MechWeapon::TakeDamage until recovered). // slot 6 ReadUpdateRecord @004ba568 (MechWeapon base @004b964c) // slot 7 WriteUpdateRecord @004ba65c (MechWeapon base @004b9690) // slot 9 TakeDamage @004bafc8 (NOT yet reconstructed) // slot 10 ResetToInitialState (MechWeapon base @004b96ec) // slot 13 PrintState (MechWeapon base @004b9d00) // slot 16 ReadyToDischarge (Simulation interface) // slot 17 ComputeOutputVoltage (Simulation interface) // slot 18 FireWeapon // public: void ReadUpdateRecord(Simulation__UpdateRecord *message); // slot 6, @004ba568 void ResetToInitialState(); // slot 10, @004ba4d0 void PrintState(); // slot 13, @004bb014 // // Beam-render accessors (the visible-beam layer draws each weapon's own // beam from this live sim state -- the authentic per-weapon fire look). // int BeamOn() const { return beamFlag; } // 0x46c Scalar DischargeTimer() const { return dischargeTimer; } // forensics // REPLICANT-side beam aging (port, 2026-07-13): replicated discharge // state arrives via update records, but replicant subsystems never // Execute -- a lost/undelivered beam-END record left the peer's beam // ON forever, drawn at its stale fire-time endpoint (the phantom // "4th gold beam", user-reported). Run the master's deterministic // countdown locally: no alarms, no upstream records; a live master // refreshes the state with every real record anyway. void ReplicantServiceBeam(Scalar dt) { if (beamFlag == 0) return; dischargeTimer -= dt; if (dischargeTimer <= 0.0f) { beamFlag = 0; firingActive = 0; dischargeTimer = dischargeTime; } } const Point3D &BeamEndpoint() const { return beamEndpoint; } // 0x460 public: void WriteUpdateRecord( // slot 7, @004ba65c Simulation__UpdateRecord *message, int update_model ); protected: // @004ba6e0 -- True when the supplied (or linked) voltage source can // satisfy the current seekVoltage index and is in the "ready" state (2). Logical ReadyToDischarge(Generator *source); // slot 16 // @004ba738 -- recompute outputVoltage = currentLevel / seekVoltage[idx], // snapped to 1.0 near full and clamped to [0,1]. void ComputeOutputVoltage(); // slot 17 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Internal charge / discharge helpers (non-virtual; called by the sim) // protected: // @004ba838 -- track the linked voltage source: derive a seek rate and // integrate currentLevel; dump the I^2R heat back into the source. void TrackSeekVoltage(Scalar time_slice); // @004ba8d0 -- count the beam-on dischargeTimer down; when it expires // (or the firing message is refused) drop back to the Loaded state. void ServiceDischarge(Scalar time_slice); // @004ba9a8 -- reset the transient firing/beam fields and re-arm to the // Loading state (alarm level 3). void ResetFiringState(); // @004baa20 -- keep an active beam alive for another frame (re-send the // beam message, or tear it down if the target subsystem is gone). void ContinueDischarge(); // @004ba478 -- advance the seekVoltage index, wrapping modulo the count; // re-arm if it wrapped. (best-effort name) int AdvanceSeekVoltage(UpdateRecord *message); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Test Class Support (EMITTER.TCP) // public: static Logical TestClass(Mech&); Logical TestInstance() const; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Construction and Destruction // public: typedef Emitter__SubsystemResource SubsystemResource; Emitter( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data = DefaultData ); ~Emitter(); static int CreateStreamedSubsystem( // @004bb478 ResourceFile *resource_file, 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 for the energy-weapon base class. // Byte offsets into the shipped object; MechWeapon ends at +0x3EF. // protected: // Byte-exact own fields, ctor @004bb120 (param_1[N] == byte N*4), 0x3F0..0x478. // The former "additional firing/beam scratch" block (beamHitPoint/beamImpact/ // beamImpactScalar/beamColor/beamHitData/firingArmed/energyRampTime) + outputVoltage // + beamLengthRatio were phantom/duplicate members that over-sized Emitter to 0x4B0; // removed (see the .cpp for where each really lives / became a local): // * outputVoltage -> inherited MechWeapon::rechargeLevel@0x320 (ComputeOutputVoltage writes it) // * beamHitData -> a LinearMatrix local in FireWeapon (aim transform) // * beamHitPoint/beamImpact/beamImpactScalar -> binary writes into the inherited // Damage damageData (0x3C8/0x3B0/0x3AC); the recon assignments were dead -> deleted // * beamColor -> never read (WriteUpdateRecord builds colour from targetEntity) // * firingArmed -> the Loaded->Firing gate reads inherited useConfiguredPip@0x3E0 // * energyRampTime -> binary writes the base slot voltageScale@0x310; recon computes a local // * beamLengthRatio -> is beamScale.z@0x434 int seekVoltageIndex; // @0x3F0 (param_1[0xfc]) current charge-curve index int seekVoltageRecommendedIndex;// @0x3F4 (param_1[0xfd]) recommended index int seekStepCounter; // @0x3F8 (param_1[0xfe]) reset 0 int seekVoltageCount; // @0x3FC (param_1[0xff]) index modulus / count-1 Scalar seekVoltage[5]; // @0x400 (param_1[0x100..0x104]) per-index target voltages Scalar currentLevel; // @0x414 (param_1[0x105]) accumulated charge int firingActive; // @0x418 (param_1[0x106]) reset 0; FireWeapon sets 1 Quaternion beamOrientation; // @0x41C (param_1[0x107..0x10a], 16B) aim orientation Vector3D beamScale; // @0x42C (param_1[0x10b..0x10d]) beam scale; .z = length ratio Scalar graphicLength; // @0x438 (param_1[0x10e]) resource GraphicLength Scalar dischargeTime; // @0x43C (param_1[0x10f]) resource DischargeTime Scalar dischargeTimer; // @0x440 (param_1[0x110]) beam-on countdown Scalar damageFraction; // @0x444 (param_1[0x111]) damageAmount / (damageAmount + heatCostToFire) Scalar energyTotal; // @0x448 (param_1[0x112]) (damageAmount + heatCostToFire) * 1e7 Scalar damagePortion; // @0x44C (param_1[0x113]) per-shot damage energy Scalar heatPortion; // @0x450 (param_1[0x114]) per-shot self-heat energy Scalar energyCoefficient; // @0x454 (param_1[0x115]) energyTotal / (seekVoltage^2 * k) Scalar seekReserved; // @0x458 (param_1[0x116]) reset 0 Scalar seekRate; // @0x45C (param_1[0x117]) d(level)/dt working value Point3D beamEndpoint; // @0x460 (param_1[0x118..0x11a]) last hit / beam-end point int beamFlag; // @0x46C (param_1[0x11b]) beam-active flag int targetLocalFlag; // @0x470 (param_1[0x11c]) target-relative vs world Entity *targetEntity; // @0x474 (param_1[0x11d]) current beam target -- LAST, ends 0x478 friend struct EmitterLayoutCheck; }; struct EmitterLayoutCheck { static_assert(offsetof(Emitter, seekVoltageIndex) == 0x3F0, "Emitter::seekVoltageIndex @0x3F0 (MechWeapon must end 0x3F0)"); static_assert(offsetof(Emitter, currentLevel) == 0x414, "Emitter::currentLevel @0x414"); static_assert(offsetof(Emitter, beamOrientation) == 0x41C, "Emitter::beamOrientation @0x41C (Quaternion 16B)"); static_assert(offsetof(Emitter, beamScale) == 0x42C, "Emitter::beamScale @0x42C"); static_assert(offsetof(Emitter, targetEntity) == 0x474, "Emitter::targetEntity @0x474 (last)"); static_assert(sizeof(Emitter) == 0x478, "sizeof(Emitter) 0x478 (== factory alloc)"); }; #endif