The blocking identity is settled: DamageZoneClassID == 78 == 0x4e, computed from our own VDATA enum, so the binary's collector filter selects DAMAGE ZONES and the blast is shared across the mech's zones. Landed: AmmoBin::AmmoBinSimulation -- a Performance the class never had, installed under the same master+dynamic gate the binary's ctor uses -- and AmmoBin::CookOff. A bin whose fed weapon reaches heat failure latches a randomised fuse; when it burns down the bin is destroyed and its remaining rounds go off inside the mech, damage scaled by rounds remaining and SHARED across the zones, every victim announced. Correcting my own 5.3.136 write-up: heat clearing does NOT cancel the countdown. Both the arm gate and the cancel read the same cell -- the ammo alarm settled on spent -- so once armed, only spending or destroying the bin calls it off. Letting the gun cool does not save you. Live on the rig: three bins arm, the fuse spread sends them off on different frames rather than together, 20 rounds x 25 = 500 damage over 22 zones from one bin and 16 x 50 = 800 from another, feeding straight into the existing destruction cascade -- Searchlight and ThermalSight destroyed as a consequence. [T1] recorded: the per-round charge is a stand-in. The binary reads it from the bin's streamed explosion record, in the block our cookOffState reserves and nothing parses yet; we use the fed weapon's authored per-shot damage until that record is read. And a mistake worth keeping in the notes: the first attempt sourced the charge from heatPerRound, a heat figure of about 3e-9, so the detonation computed 4.8e-08 of damage and did precisely nothing -- a mechanic that fires, announces itself, and is silently inert. Only printing the arithmetic caught it. Log the numbers, not just the event. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
287 lines
9.7 KiB
C++
287 lines
9.7 KiB
C++
//===========================================================================//
|
|
// File: mechweap.hpp //
|
|
// Project: BattleTech //
|
|
// Contents: Implementation details for mech weapons //
|
|
//---------------------------------------------------------------------------//
|
|
// Date Who Modification //
|
|
// -------- --- ---------------------------------------------------------- //
|
|
// //
|
|
//---------------------------------------------------------------------------//
|
|
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
|
// All Rights reserved worldwide //
|
|
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
|
//===========================================================================//
|
|
|
|
#if !defined(MECHWEAP_HPP)
|
|
# define MECHWEAP_HPP
|
|
|
|
# if !defined(POWERSUB_HPP)
|
|
# include <powersub.hpp>
|
|
# endif
|
|
|
|
# if !defined(DAMAGE_HPP)
|
|
# include <damage.hpp>
|
|
# endif
|
|
|
|
# if !defined(ALARM_HPP)
|
|
# include <alarm.hpp>
|
|
# endif
|
|
|
|
//##################### Forward Class Declarations #######################
|
|
class Mech;
|
|
|
|
//###########################################################################
|
|
//###################### MechWeapon Model Resource ######################
|
|
//###########################################################################
|
|
|
|
//
|
|
// WIRE-VERIFIED layout (raw-stream dump vs the BT411 verified overlay):
|
|
// eleven fields; the pip tail is pipPosition(int) + pipColor(3 floats) +
|
|
// pipExtendedRange(int). bhk1 PPC reads: recharge 5.0s, range 900,
|
|
// damage 12, type 4, heatCost 11, pipColor (0,0,1).
|
|
//
|
|
struct MechWeapon__SubsystemResource:
|
|
public PoweredSubsystem::SubsystemResource
|
|
{
|
|
Scalar rechargeRate;
|
|
Scalar weaponRange;
|
|
ResourceDescription::ResourceID
|
|
explosionModelFile;
|
|
Scalar damageAmount;
|
|
int damageType;
|
|
Scalar heatCostToFire;
|
|
int pipPosition;
|
|
Scalar pipColor[3];
|
|
int pipExtendedRange;
|
|
};
|
|
|
|
//###########################################################################
|
|
//############################ MechWeapon ###############################
|
|
//###########################################################################
|
|
|
|
class MechWeapon:
|
|
public PoweredSubsystem
|
|
{
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Shared Data Support
|
|
//
|
|
public:
|
|
static Derivation ClassDerivations;
|
|
static SharedData DefaultData;
|
|
static const HandlerEntry MessageHandlerEntries[];
|
|
static MessageHandlerSet MessageHandlers;
|
|
|
|
//
|
|
// The weapon-group config buttons (binary ids 9/10, chained after
|
|
// the PoweredSubsystem generator panel 4-8).
|
|
//
|
|
enum {
|
|
ConfigureMappablesMessageID = PoweredSubsystem::NextMessageID, // 9
|
|
ChooseButtonMessageID, // 10
|
|
NextMessageID // 11
|
|
};
|
|
|
|
void
|
|
ConfigureMappablesMessageHandler(ReceiverDataMessageOf<int> *message);
|
|
void
|
|
ChooseButtonMessageHandler(ReceiverDataMessageOf<int> *message);
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Attribute Support. The real IDs are PINNED to the 1995 binary numbering
|
|
// (table @0x511890, ids 0x12..0x1C): the streamed per-mech control mappings
|
|
// reference them by NUMBER -- TriggerState (0x13) is the fire-button binding.
|
|
// Our parent chain publishes nothing past Subsystem::NextAttributeID (2)
|
|
// yet, so pads bridge the gap 2..0x11 (they shrink to the authentic
|
|
// 0x0F..0x11 once the mechsub/heat/powersub attribute waves publish --
|
|
// SENSOR.HPP pins PoweredSubsystem::NextAttributeID at 0x0F). The pads are
|
|
// LOAD-BEARING: AttributeIndexSet::Build leaves unlisted gap slots as
|
|
// uninitialized garbage, so every ID up to the max must be covered.
|
|
//
|
|
public:
|
|
enum {
|
|
MechWeaponPadFirstAttributeID = PoweredSubsystem::NextAttributeID, // 0x0F
|
|
PercentDoneAttributeID = 0x12,
|
|
TriggerStateAttributeID, // 0x13 -- the streamed fire-button binding
|
|
DistanceToTargetAttributeID, // 0x14
|
|
TargetWithinRangeAttributeID, // 0x15
|
|
WeaponRangeAttributeID, // 0x16
|
|
PipPositionAttributeID, // 0x17
|
|
PipColorAttributeID, // 0x18
|
|
PipExtendedRangeAttributeID, // 0x19
|
|
EstimatedReadyTimeAttributeID, // 0x1A
|
|
RearFiringAttributeID, // 0x1B
|
|
WeaponStateAttributeID, // 0x1C (binary table end)
|
|
NextAttributeID // 0x1D (the Emitter family starts here)
|
|
};
|
|
|
|
static const IndexEntry AttributePointers[];
|
|
static AttributeIndexSet AttributeIndex;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Test Class Support
|
|
//
|
|
public:
|
|
static Logical
|
|
TestClass(Mech &);
|
|
Logical
|
|
TestInstance() const;
|
|
|
|
//
|
|
// The projectile/explosion GameModel this weapon spawns (binary
|
|
// @0x3e4) -- read by SubsystemMessageManager's consolidator to
|
|
// bundle the firing weapon's explosion with the damage it caused.
|
|
//
|
|
ResourceDescription::ResourceID
|
|
ExplosionResourceIDOf()
|
|
{ Check(this); return explosionResourceID; }
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Construction and Destruction
|
|
//
|
|
public:
|
|
typedef MechWeapon__SubsystemResource SubsystemResource;
|
|
|
|
MechWeapon(
|
|
Mech *owner,
|
|
int subsystem_ID,
|
|
SubsystemResource *subsystem_resource,
|
|
SharedData &shared_data = DefaultData
|
|
);
|
|
|
|
~MechWeapon();
|
|
|
|
static int
|
|
CreateStreamedSubsystem(
|
|
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
|
|
);
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Weapon interface
|
|
//
|
|
public:
|
|
virtual void
|
|
FireWeapon();
|
|
|
|
void
|
|
SendDamage(
|
|
Entity *target,
|
|
Damage &damage
|
|
);
|
|
|
|
//
|
|
// View-fire gating (the look-state commit re-arms these): a weapon
|
|
// mounted on a back gun port (rearFiring) fires only into the LOOK-BACK
|
|
// view; the rest fire only in the forward view.
|
|
//
|
|
Logical
|
|
IsRearFiring() const { Check(this); return rearFiring; }
|
|
Logical
|
|
GetViewFireEnable() const { Check(this); return viewFireEnable; }
|
|
void
|
|
SetViewFireEnable(Logical enable){ Check(this); viewFireEnable = enable; }
|
|
|
|
//
|
|
// Fire state machine. The weapon state is carried in the weapon alarm
|
|
// level: 0 = Firing, 2 = Loaded (ready), 3 = Loading, 4 = the
|
|
// trigger-during-load blip. CheckFireEdge is the rising-edge detector
|
|
// on fireImpulse (binary @004b9608): TriggerState carries the raw
|
|
// ControlsButton INT bit-copied by the streamed direct mapping, so the
|
|
// compare works on the BIT PATTERNS as signed ints (sign-correct for
|
|
// the button ints AND genuine floats), reproducing the binary's x87
|
|
// semantics. ComputeOutputVoltage (@004b9c9c) is the recharge-dial
|
|
// writer.
|
|
//
|
|
enum {
|
|
FiringState = 0,
|
|
DryTriggerState = 1,
|
|
LoadedState = 2,
|
|
LoadingState = 3,
|
|
TriggerDuringLoadState = 4,
|
|
JammedState = 5,
|
|
TriggerDuringJamState = 6,
|
|
NoAmmoState = 7,
|
|
WeaponStateCount = 8
|
|
};
|
|
|
|
unsigned
|
|
GetWeaponState() { Check(this); return weaponAlarm.GetLevel(); }
|
|
//
|
|
// The authored per-shot damage. Read by the ammo cook-off as its
|
|
// [T1] stand-in for the per-round charge (see AMMOBIN.CPP).
|
|
//
|
|
Scalar
|
|
DamageAmountOf() const { Check(this); return damageAmount; }
|
|
Logical
|
|
CheckFireEdge();
|
|
virtual void
|
|
ComputeOutputVoltage(); // vtable slot 17; the Emitter overrides
|
|
// with its charge-voltage form
|
|
|
|
//
|
|
// Targeting: the owner mech's target slot gates the Loaded->Firing
|
|
// transition (no target = the denial blip, no shot); UpdateTargeting
|
|
// (binary @004b9bdc) refreshes rangeToTarget / targetWithinRange from
|
|
// the target's position each frame.
|
|
//
|
|
Logical
|
|
HasActiveTarget();
|
|
Logical
|
|
UpdateTargeting();
|
|
|
|
//
|
|
// Respawn restore: base heal + the fire machine back to a fresh
|
|
// Loading cycle (charge/recoil/trigger cleared; jams and the NoAmmo
|
|
// roach-motel release -- the bins restock in their own DeathReset).
|
|
//
|
|
virtual void
|
|
DeathReset(Logical full_reset);
|
|
|
|
//
|
|
// The muzzle world position (binary @004b9948): the MOUNT segment's
|
|
// world frame -- rounds and beams leave FROM THE GUN, following the
|
|
// torso twist. Falls back to the mech origin when the mount
|
|
// segment doesn't resolve.
|
|
//
|
|
void
|
|
GetMuzzlePoint(Point3D &point);
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Local Data
|
|
//
|
|
protected:
|
|
Scalar rechargeRate;
|
|
Scalar weaponRange;
|
|
Scalar damageAmount;
|
|
int damageType;
|
|
Scalar heatCostToFire;
|
|
ResourceDescription::ResourceID
|
|
explosionResourceID; // binary @0x3e4 -- the projectile /
|
|
// explosion GameModel this weapon spawns
|
|
int targetWithinRange;
|
|
Damage damageData;
|
|
Logical rearFiring;
|
|
Logical viewFireEnable;
|
|
//
|
|
// Fire-machine state (binary @0x31C/0x320/0x324/0x328/0x330/0x350/
|
|
// 0x3A4/0x3E8): the trigger sample pair, the recharge dial, targeting
|
|
// ranges, and the weapon-state alarm.
|
|
//
|
|
Scalar fireImpulse;
|
|
Scalar previousFireImpulse;
|
|
Scalar rechargeLevel;
|
|
Scalar rangeToTarget;
|
|
Scalar effectiveRange;
|
|
int estimatedReadyTime;
|
|
Scalar recoil;
|
|
AlarmIndicator weaponAlarm;
|
|
};
|
|
|
|
#endif
|