//============================================================================// // File: ammobin.hpp // // Project: BattleTech // // Contents: Ammunition bin -- holds rounds, feeds a ProjectileWeapon, and // // cooks off when overheated. // //----------------------------------------------------------------------------// // 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 // @004bd2c0-@004bdb94 + CSS @004bd6f0 in part_013.c). Only the test fragment // AMMOBIN.TCP survives (AmmoBin::TestClass -> True; AmmoBin::ResetToInitialState // -> empty). Member/method names are inferred from the streamed resource keys // ("AmmoCount"/"FeedRate"/"AmmoClassID"/"ProjectileClassID"/"MissileClassID"/ // "AmmoModelFile"/"ExplosionModelFile") and from usage; see ammobin.cpp for the // per-method @ADDR evidence. Names past the HeatWatcher base are best-effort. // // Inheritance chain established from the decomp: // Subsystem -> HeatableSubsystem -> HeatWatcher (ctor @4aeb40, vtable // @0050ed10) -> AmmoBin (ctor @4bd5c4, vtable @0051286c, classID 0x0BCB). // AmmoBin IS a HeatWatcher: it watches a temperature against the resource's // degradation/failure thresholds, and when the watch alarm (this+0x140) reaches // FAILURE it arms a randomised cook-off timer that dumps all stored rounds' // heat into the mech and empties the bin. A ProjectileWeapon links to it as a // SharedData connection (launcher this[0x10F]@0x43C, connection ctor @4bcbb0, // vtable @00512424) and pulls a round via FeedAmmo() each time it fires. // // Object byte offsets (this is an int* in the decomp, word index in parens). // Everything below +0x180 is the HeatWatcher / HeatableSubsystem / Subsystem // base (watch heatAlarm level @0x140 = word 0x50; base status alarm @0x2C). // #if !defined (AMMOBIN_HPP) # define AMMOBIN_HPP # if !defined(HEATFAMILY_RESLICE_HPP) # include // HeatWatcher base (heat family) # endif //######################### Forward Class Declarations ######################## class Mech; //########################################################################### //################# AmmoBin Model Resource ############################## //########################################################################### // // Streamed by AmmoBin::CreateStreamedSubsystem @004bd6f0 (record size 0x104, // classID 0x0BCB). The ammo-specific block sits at +0xF0 past the heat-watcher // resource header. "AmmoClassID" accepts the symbolic forms "ProjectileClassID" // (stamps 0x0BD1) and "MissileClassID" (stamps 0x0BBA), naming the entity class // the bin spawns when a round is fired. // struct AmmoBin__SubsystemResource: public HeatWatcher::SubsystemResource { int ammoClassID; // +0xF0 "AmmoClassID"/"ProjectileClassID"/"MissileClassID" int ammoModelFile; // +0xF4 "AmmoModelFile" (resolved model index) int explosionModelFile; // +0xF8 "ExplosionModelFile" (resolved model index) int ammoCount; // +0xFC "AmmoCount" Scalar feedRate; // +0x100 "FeedRate" (seconds per round feed) }; // ends 0x104 static_assert(offsetof(AmmoBin__SubsystemResource, ammoClassID) == 0xF0, "AmmoBin ammoClassID must be at 0xF0"); static_assert(offsetof(AmmoBin__SubsystemResource, feedRate) == 0x100, "AmmoBin feedRate must be at 0x100"); static_assert(sizeof(AmmoBin__SubsystemResource) == 0x104, "AmmoBin record must be 0x104"); //########################################################################### //######################### CLASS -- AmmoBin ######################### //########################################################################### // // (vtable @0051286c, ctor @004bd5c4, dtor @004bd6b0, CSS @004bd6f0.) // class AmmoBin: public HeatWatcher { friend struct AmmoBinLayoutCheck; // compile-time offset locks (ammobin.cpp) friend int *BTAmmoBinCountPtr(void *bin); // panel ammo counter bridge (Streak fix) friend int BTAmmoBinFeeding(void *bin); // panel reload-state bridge //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Shared Data Support // public: static Derivation ClassDerivations; // IsDerivedFrom tag 0x512488 static Receiver::MessageHandlerSet MessageHandlers; static AttributeIndexSet AttributeIndex; static SharedData DefaultData; // resolved as &DAT_005125bc //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Ammo feed state machine (this->ammoAlarm @0x194, level @0x1A8 = word 0x6A) // // The 6-level AlarmIndicator carries the feed cycle. Levels confirmed from // usage; 3 and 4 are unobserved. // public: enum AmmoState { Feeding = 0, // a round is in transit (feedTimer counting down) Loaded = 1, // a round is chambered and ready to fire Empty = 2, // no rounds remain Dumped = 5 // transient pulse raised by DumpAmmo() before Empty }; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Model Support // public: enum { CookingOff = HeatWatcher::StateCount, StateCount }; typedef void (AmmoBin::*Performance)(Scalar time_slice); // @004bd394 -- the registered Performance. Drives the watch alarm, ticks // the feed timer (Feeding -> Loaded), arms/fires the cook-off, and trips // the Empty alarm when exhausted. void AmmoBinSimulation(Scalar time_slice); void SetPerformance(Performance performance) { Check(this); activePerformance = (Simulation::Performance)performance; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Ammo feed interface (called by the linked ProjectileWeapon) // public: // @004bd4f4 -- request one round. When Loaded: chamber the next round, // reset the feed timer to feedRate, decrement ammoCount, and return the // remaining count (Empty alarm raised at 0). Returns 0 if not ready / // already feeding / empty / destroyed. int FeedAmmo(); // @004bd588 -- forcibly empty the bin (eject / strip): zero ammoCount and // pulse the alarm Dumped(5) -> Empty(2). void DumpAmmo(); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Subsystem virtual overrides (vtable @0051286c) // public: Logical HandleMessage(int message); // slot 8, @004bdb94 void ReadUpdateRecord(UpdateRecord *update); // slot 9, @004bd2c0 void ResetToInitialState(); // slot 10 (AMMOBIN.TCP: empty) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Local helpers // protected: // @004bd300 -- cook-off: inject (ammoCount * heatPerRound) heat into the // mech, then zero ammoCount and raise the Empty alarm. void CookOff(); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // TestClass Support (AMMOBIN.TCP) // public: static Logical TestClass(Mech &); // AMMOBIN.TCP: returns True Logical TestInstance() const; // @004bd1e0 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Construction and Destruction // public: typedef AmmoBin__SubsystemResource SubsystemResource; AmmoBin( // @004bd5c4 Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource ); ~AmmoBin(); // @004bd6b0 static int CreateStreamedSubsystem( // @004bd6f0 (record size 0x104) 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 appended past the HeatWatcher base (offsets best-effort). // protected: int ammoCount; // @0x180 (word 0x60) rounds remaining Scalar feedTimer; // @0x184 (word 0x61) seconds left in current feed int ammoClassID; // @0x188 (word 0x62) entity class spawned per round int cookOffArmed; // @0x18C (word 0x63) cook-off timer running int cookOffTime; // @0x190 (word 0x64) game clock at which it detonates WatcherGaugeAlarm ammoAlarm; // @0x194 (0x54) 6-level feed alarm (level @0x1A8) // (WAVE 4) `int statusState` DELETED -- it was a this+0x40 SHADOW of the // inherited MechSubsystem::simulationState; the reads now use that base // member. The 0x54 ammoAlarm (was an 8-byte HeatAlarm) lands ammoModelFile // at 0x1E8 -- exact binary layout, locked by AmmoBinLayoutCheck. int ammoModelFile; // @0x1E8 (word 0x7A) round model index int explosionModelFile; // @0x1EC (word 0x7B) cook-off explosion model index // @0x1F0 (word 0x7C) cook-off heat-injection descriptor, inited by // FUN_0041db7c; CookOff() fills word 0x7D (@0x1F4) below with the total // heat (ammoCount * heatPerRound) before injecting via HeatableSubsystem. int cookOffHeatSource[1]; // @0x1F0 (word 0x7C) Scalar heatPerRound; // @0x1F4 (word 0x7D) int cookOffHeatRest[10]; // @0x1F8..0x21C (words 0x7E..0x87) int initialAmmoCount; // @0x220 (word 0x88) ammoCount captured at ctor Scalar feedRate; // @0x224 (word 0x89) resource FeedRate int reserved; // @0x228 (word 0x8A) init 0 (unused) }; #endif