//===========================================================================// // File: projweap.hpp // // Project: BattleTech Brick: Entity Manager // // Contents: ProjectileWeapon -- MechWeapon that spawns physical projectiles // // (tracers / missiles) and consumes ammunition from an AmmoBin. // //---------------------------------------------------------------------------// // Date Who Modification // // -------- --- ---------------------------------------------------------- // // 04/13/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 // @004bbae0-@004bcff0, file=bt/projweap.cpp). Cross-referenced against the // SURVIVING sources PROJWEAP.TCP (TestClass/ResetToInitialState stubs), // MISLANCH.HPP (MissileLauncher : ProjectileWeapon -- pins this interface and // the resource layout), and the reconstructed base mechweap.hpp/.cpp. // // Inheritance chain established from the decomp: // ... -> PoweredSubsystem -> MechWeapon (mechweap.cpp, ctor @004b99a8) // -> ProjectileWeapon (this file, vtable @0051242c, ctor @004bc3fc) // -> MissileLauncher (mislanch.cpp, vtable @00512570, ctor @004bcff0) // // EVIDENCE that this class is ProjectileWeapon (not Emitter): // * The class-name string "ProjectileWeapon" lives at DAT_00512280, immediately // adjacent to this class's vtable group (@00512424/@0051242c). // * MechWeapon has exactly TWO direct children whose ctors chain to the // MechWeapon ctor @004b99a8: the energy weapon Emitter (ctor @004bb120, // vtable @00512078, parses SeekVoltage/DischargeTime) and THIS class // (ctor @004bc3fc, vtable @0051242c, parses AmmoBin/TracerInterval). // * MissileLauncher's ctor (@004bcff0) chains to THIS ctor (@004bc3fc) and adds // exactly { int missileCount; Vector3D muzzleVelocity; } at resource +0x1D0 / // +0x1D4 -- precisely the MissileLauncher__SubsystemResource of MISLANCH.HPP. // By definition MissileLauncher's direct base IS ProjectileWeapon, so the // class at @004bc3fc is ProjectileWeapon. // // NOTE on PROJWEAP.TCP: the surviving .TCP is an early test-scaffold (no copyright // banner) in which TestClass() just returns True and ResetToInitialState() is // empty. The shipped binary implements a real ResetToInitialState (@004bbaf8); // the empty body was filled in after the .TCP snapshot. TestClass()==True still // holds. // // Field offsets in comments are byte offsets into the shipped object; the // MechWeapon base occupies everything below +0x3F0. ProjectileWeapon members // span +0x3F0..+0x447 (object size 0x448). Several member NAMES are taken from // the binary's own attribute strings ("TotalTimeToEject"/"TimeToEject"/ // "PercentOfEject"/"EjectAmmo" @DAT_00512290) and resource tag strings // ("TracerInterval","AmmoBin","MinTimeOfFlight","MinVoltagePercentToFire", // "MinJamChance"); the remainder are inferred from usage and flagged best-effort. // #if !defined(PROJWEAP_HPP) # define PROJWEAP_HPP #if !defined(MECHWEAP_HPP) # include #endif //##################### Forward Class Declarations ####################### class Mech; class Entity; class AmmoBin; class ResourceFile; class NotationFile; //########################################################################### //################## ProjectileWeapon Model Resource ################### //########################################################################### // // Extends the MechWeapon resource record. The ProjectileWeapon-specific fields // begin at +0x1BC in the parsed record (see CreateStreamedSubsystem @004bc7cc). // Total record size is 0x1D0 -- the parser stamps subsystemModelSize = 0x1D0. // struct ProjectileWeapon__SubsystemResource: public MechWeapon::SubsystemResource { int tracerInterval; // +0x1BC "TracerInterval" (read as int/bool) int ammoBinIndex; // +0x1C0 "AmmoBin" (subsystem-name -> index, +2 bias) Scalar minTimeOfFlight; // +0x1C4 "MinTimeOfFlight" Scalar minVoltagePercentToFire; // +0x1C8 "MinVoltagePercentToFire" Scalar minJamChance; // +0x1CC "MinJamChance" }; // sizeof == 0x1D0 //########################################################################### //####################### ProjectileWeapon ######################### //########################################################################### // // Abstract MechWeapon specialisation for weapons that launch a physical // projectile (a tracer round or a Missile) and draw rounds from an AmmoBin. // Adds an eject/reload cycle, a heat-driven jam roll, a minimum time-of-flight // lead solution, and per-shot projectile spawning. // (vtable @0051242c, ctor @004bc3fc, dtor @004bc688.) // class ProjectileWeapon: public MechWeapon { //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Shared Data Support // public: static Derivation ClassDerivations; // parent = MechWeapon::ClassDerivations, // name = "ProjectileWeapon" (DAT_00512280) static Receiver::MessageHandlerSet& GetMessageHandlers(); // inherits MechWeapon's (task #6) static AttributeIndexSet AttributeIndex; static SharedData DefaultData; // Embedded ref-counted connection to the AmmoBin subsystem (resource // AmmoBin index). The shipped layout is a 0xC-byte SharedData::Connection // (ctor FUN_004bcbb0->FUN_004179d4; resolver FUN_00417ab4 two-level-derefs +8), // NOT an empty proxy -- modeling it as 1 byte made ProjectileWeapon 8 bytes // short (0x440 vs the binary 0x448) and slid MissileLauncher's missileCount off // 0x448. Named access (Construct/Connect/Resolve/Destruct) is unchanged + inert // in bring-up (no plug resolves). Same 0xC pattern as SubsystemConnection. struct AmmoBinConnection { void *linked; // +0x00 (SharedData::Connection; inert here) int _reserved[2]; // pad to the binary 0xC connection void Construct() { linked = 0; _reserved[0] = _reserved[1] = 0; } template void Connect(T s) { linked = (void *)s; } void Destruct() { linked = 0; } void *Resolve() { return linked; } }; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Weapon firing-state alarm levels (this+0x350 weaponAlarm, reported by // PrintState @004bc6c8). Levels 0..4 are inherited from the MechWeapon / // Emitter scheme (Firing/Loaded/Loading/TriggerDuringLoad); ProjectileWeapon // adds the ammo-specific states: // public: enum WeaponState { JammedState = 5, // HandleMessage(2) -> SetLevel(5) TriggerDuringJamState = 6, NoAmmoState = 7 }; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Simulation Support (Performance member-function pointer; the per-frame // driver lives at @004bbd04 and is installed by the ctor unless the model // is flagged "no simulation"). // public: typedef void (ProjectileWeapon::*Performance)(Scalar time_slice); void SetPerformance(Performance performance) { Check(this); activePerformance = (Simulation::Performance)performance; } // @004bbd04 -- ProjectileWeapon per-frame update (Performance). Body not // recovered by the decompiler (function prologue confirmed at 0x4bbd04); // drives UpdateEject / DrawFiringCharge / CheckForJam / ComputeTimeOfFlight // and triggers FireWeapon via the vtable. void ProjectileWeaponSimulation(Scalar time_slice); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Firing -- overrides MechWeapon's pure-virtual FireWeapon (vtable slot 18). // // MechWeapon's slot-18 body is the pure-virtual trap @004ba45c // ("ERROR: Accessing Pure Virtual Method"). ProjectileWeapon provides the // real spawn at @004bc104; MissileLauncher overrides again at @004bcc60. // protected: void FireWeapon(); // slot 18, @004bc104 (body not recovered) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Subsystem virtual overrides (slots on vtable @0051242c) // // Slot indices verified by decoding the raw vtable bytes (section_dump). // SLOT MAP CORRECTED (task #51): 6=ReadUpdateRecord, 9=TakeDamage (the // old attributions were swapped -- see mechweap.hpp for the evidence): // slot 6 ReadUpdateRecord INHERITED MechWeapon @004b964c // slot 7 WriteUpdateRecord INHERITED MechWeapon @004b9690 // slot 8 HandleMessage OVERRIDE @004bcabc // slot 9 TakeDamage OVERRIDE @004bbae0 (thin -> MechWeapon) // slot 10 ResetToInitialState OVERRIDE @004bbaf8 // slot 12 GetStatusFlags OVERRIDE @004bbf88 (body not recovered) // slot 13 PrintState OVERRIDE @004bc6c8 // slot 16 (per-frame hook) OVERRIDE @004bbc20 (body not recovered) // slot 18 FireWeapon OVERRIDE @004bc104 (body not recovered) // public: LWord GetStatusFlags(); // slot 12, @004bbf88 Logical HandleMessage(int message); // slot 8, @004bcabc void ResetToInitialState(); // slot 10, @004bbaf8 void PrintState(); // slot 13, @004bc6c8 // FIRE-VISUAL REPLICATION (task #61 -- the AC twin of MissileLauncher's // salvo mirror, mislanch.hpp). The port's AC is a LOCAL flying round, // so the enemy's cannon fire never reached the peer. Same proven path // the missile + emitter use: FireWeapon calls ForceUpdate() (sets the // updateModel bit), so the mech's per-frame subsystem serialize // (mech4.cpp:3893 subsystem->PerformAndWatch(update_stream)) writes this // record; Entity::UpdateMessageHandler routes it to the peer's replicant // by subsystemID. The master serializes a monotonic fire counter + aim; // the replicant edge-detects it and mirrors ONE visual round + DAFC // muzzle flash. Fire STATE lives in a port-side table (projweap.cpp); // the 0x1D0 object layout is byte-locked and must not grow. struct ProjectileWeapon__UpdateRecord: public MechWeapon::UpdateRecord { int fireCounter; // AC shots since spawn (edge-detected by peers) Point3D fireTarget; // the aim point the round flew at }; typedef ProjectileWeapon__UpdateRecord UpdateRecord; void WriteUpdateRecord(Simulation__UpdateRecord *message, int update_model); // slot 7 void ReadUpdateRecord(Simulation__UpdateRecord *message); // slot 6 protected: // TASK #51 RENAME: slot 9 is TakeDamage (the old ReadUpdateRecord // attribution was swapped; the record read is INHERITED from // MechWeapon slot 6 @004b964c -- see mechweap.hpp for the evidence). void TakeDamage(Damage &damage); // slot 9, @004bbae0 // slot 16, @004bbc20 -- MechWeapon-family per-frame hook (overrides // MechWeapon @004b0b5c). Body not recovered. TODO: confirm role. virtual void UpdateWeaponState(); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Test Class Support // public: static Logical TestClass(Mech&); // @ PROJWEAP.TCP -> True Logical TestInstance() const; // @004bd1e0 family (IsDerivedFrom) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Internal model helpers (recovered) // protected: // @004bbb50 -- advance the eject/reload countdown (TimeToEject), update // PercentOfEject, and when complete request a fresh round from the AmmoBin // (NoAmmo alarm if the bin is dry). void UpdateEject(Scalar time_slice); // @004bbc78 -- bleed the firing charge (MechWeapon recoil @0x3E8) according // to the available voltage / ammo-bin supply. void DrawFiringCharge(Scalar time_slice); // @004bbfcc -- roll a heat-scaled jam chance: returns True if the weapon // jams this shot. Jam probability = clamp((0.41*currentTemperature / // failureTemperature), minJamChance, 1.0) compared against a uniform random. Logical CheckForJam(); // @004bc06c -- lead solution: project the target position by launchVelocity // and return the time-of-flight (or minTimeOfFlight + bias when no target // is in range). Scalar ComputeTimeOfFlight(); // --- cross-family isolation helpers (owning Mech subsystem roster + // cockpit controls live in the mech/controls families). Declared // here so the recovered bodies compile; the mech family wires the // real roster/controls query. --- int OwnerSubsystemCount(Mech *owner) const; // Mech roster size Subsystem * OwnerSubsystem(Mech *owner, int index) const; // Mech roster lookup Logical LiveFireEnabled() const; // controls "live fire" flag // Resolve the linked AmmoBin and pull one round (AmmoBin::FeedAmmo). // Returns True when a round was dispensed; on a dry / not-ready / missing // bin it raises the NoAmmo alarm and returns False. Shared by the // ProjectileWeapon and MissileLauncher FireWeapon spawn paths. Logical ConsumeRound(); // True when the weapon has recovered its firing charge (recoil counts // down toward 0 in DrawFiringCharge; 0 == ready) and is not mid-eject. Logical ReadyToFire() const { return (recoil <= 0.0f) && (ejectState != 0); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Construction and Destruction // public: typedef ProjectileWeapon__SubsystemResource SubsystemResource; ProjectileWeapon( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data = DefaultData ); ~ProjectileWeapon(); static int CreateStreamedSubsystem( // @004bc7cc 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 projectile-weapon class. // Offsets are byte offsets into the shipped object (MechWeapon occupies // everything below +0x3F0). Object size is 0x448. // protected: // --- eject / reload cycle (attribute names from DAT_00512290) --- Scalar totalTimeToEject; // @0x3F0 "TotalTimeToEject" init 3.0 Scalar timeToEject; // @0x3F4 "TimeToEject" countdown Scalar percentOfEject; // @0x3F8 "PercentOfEject" 0..1 progress (HUD) Scalar minJamChance; // @0x3FC resource MinJamChance (+0x1CC) int ejectState; // @0x400 -1 = idle, 0 = ejecting (init -1) Scalar minVoltagePercentToFire;// @0x404 resource MinVoltagePercentToFire (+0x1C8) int tracerCounter; // @0x408 init 0 (best-effort: tracer cadence) // --- launch / lead solution --- Scalar minTimeOfFlight; // @0x40C resource MinTimeOfFlight (+0x1C4) Vector3D launchVelocity; // @0x410 init {0,0,-effectiveRange/speed} // (MissileLauncher overwrites w/ MuzzleVelocity) int tracerModelHandle; // @0x41C spawned tracer/explosion model handle Point3D leadPosition; // @0x420 computed target lead point (init origin) Vector3D tracerOrigin; // @0x42C best-effort: runtime muzzle origin (sim scratch) // --- ammunition --- int tracerInterval; // @0x438 resource TracerInterval (+0x1BC) AmmoBinConnection ammoBinLink; // @0x43C embedded 0xC connection to the AmmoBin subsystem friend void *BTWeaponAmmoBin(void *weapon); // panel ammo-bin bridge (AFC100-counter fix) // (ctor @004bcbb0, dtor @004bcbcf, link vtable // @00512424; resolved via the resource AmmoBin // index at +0x1C0) -- LAST field, object ends 0x448 friend struct ProjectileWeaponLayoutCheck; }; // sizeof == 0x448 struct ProjectileWeaponLayoutCheck { static_assert(offsetof(ProjectileWeapon, totalTimeToEject) == 0x3F0, "ProjectileWeapon::totalTimeToEject @0x3F0 (MechWeapon must end 0x3F0)"); static_assert(offsetof(ProjectileWeapon, launchVelocity) == 0x410, "ProjectileWeapon::launchVelocity @0x410"); static_assert(offsetof(ProjectileWeapon, leadPosition) == 0x420, "ProjectileWeapon::leadPosition @0x420"); static_assert(offsetof(ProjectileWeapon, tracerInterval) == 0x438, "ProjectileWeapon::tracerInterval @0x438"); static_assert(offsetof(ProjectileWeapon, ammoBinLink) == 0x43C, "ProjectileWeapon::ammoBinLink @0x43C"); static_assert(sizeof(ProjectileWeapon) == 0x448, "sizeof(ProjectileWeapon) 0x448 (== factory alloc)"); }; #endif