Files
BT411/game/reconstructed/mechweap.hpp
T
arcattackandClaude Opus 4.8 070af409f7 gauge wave P1a: publish heat/power/weapon attributes + radar zoom
The gauge-databinding-map workflow found most cockpit gauges resolve NULL
because the reconstructed subsystems publish only a fraction of the attributes
the config binds.  First publishing batch (attribute tables are read-only static
data; ids kept a dense prefix from each parent's NextAttributeID):

- HeatSink table dense-append: DegradationTemperature/FailureTemperature (the
  condenser temp-bar warn/max endpoints -- were NULL, so the two-part bars could
  not scale), NormalizedPressure/DegradationPressure/CoolantMassLeakRate, and the
  HeatSink link.  Condenser/Reservoir inherit this -> all 6 condenser temp bars
  now resolve current/warn/max (verified: BT_GAUGE_ATTR_LOG all OK).
- PoweredSubsystem::GetAttributeIndex() (new) publishes InputVoltage->voltageSource
  -- the cluster power-branch gate (the power-lamp/generator-voltage/state-lamp
  sub-branch is skipped when it resolves NULL).  Flows to Sensor/Myomers/weapons.
- MechWeapon::GetAttributeIndex() (new) publishes OutputVoltage/PercentDone->
  rechargeLevel; Emitter/PPC/ProjectileWeapon/MissileLauncher/GaussRifle DefaultData
  re-pointed at it (they carried an EMPTY default-constructed index -> resolved
  NOTHING).  Verified: the ER MED LASER / PPC / STREAK weapon clusters now render
  live recharge dials (were blank TEMP/STATUS).
- Mech::SetTargetRange un-stubbed (radarRange = range) -> the radar map scale +
  overlay range readout track the mapper's zoom (was frozen at 1000).
- GAUGREND ParseAttribute: env-gated per-binding resolution trace (BT_GAUGE_ATTR_LOG)
  -- durable diagnostic infra for the wave.

Verified DBASE+dev gauges: no startup/gauge-construction crash (dense chain intact),
combat un-regressed (TARGET DESTROYED), clusters build with InputVoltage resolving.
Remaining config-binding NULLs: HeatSink/AmbientTemperature (aggregate bank, P3) +
Searchlight/LightOn (P1b).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 16:22:45 -05:00

328 lines
14 KiB
C++

//===========================================================================//
// File: mechweap.hpp //
// Project: BattleTech Brick: Entity Manager //
// Contents: MechWeapon -- abstract base for every mounted Mech weapon //
//---------------------------------------------------------------------------//
// 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
// @004b95ec-@004b9d10, file=bt/mechweap.cpp; pure-virtual stub @004ba45c,
// file=bt/mechweap.hpp). Cross-referenced HEAVILY against the SURVIVING
// derived-class sources PPC.HPP/PPC.CPP and GAUSS.HPP/GAUSS.CPP (which derive
// from Emitter : MechWeapon) and MISLANCH.HPP (MissileLauncher : ProjectileWeapon
// : MechWeapon), plus the MUNGA SUBSYSTM.HPP base interface and the RP
// WEAPSYS.h analog. See mechweap.cpp for per-method @ADDR evidence.
//
// Inheritance chain established from the decomp:
// Simulation -> Subsystem -> PoweredSubsystem (powersub.cpp, ctor @004b0f74)
// -> MechWeapon (this file, vtable @00511d2c)
// -> Emitter / ProjectileWeapon -> PPC / GaussRifle / MissileLauncher
//
// The MechWeapon ctor (@004b99a8) installs vtable PTR_FUN_00511d2c and chains
// to the PoweredSubsystem ctor @004b0f74, confirming the base. PPC and Gauss
// FireWeapon() chain to Emitter::FireWeapon(); MechWeapon::FireWeapon() is the
// pure-virtual default that asserts "MechWeapon abstract class should..." (@004b95ec)
// -- the exact analog of RP GenericGun::Fire()'s "Should not be here!" stub.
//
// Field offsets in comments are byte offsets into the shipped object
// (e.g. "@0x34c" == this word index 0xd3). Names taken from PPC.CPP
// (targetWithinRange, damageData) and the resource-parser tag strings
// (WeaponRange, DamageAmount, HeatCostToFire, RechargeRate, DamageType,
// PipPosition/PipColor/PipExtendedRange, ExplosionModelFile); the remainder
// are inferred from usage and flagged best-effort where uncertain.
//
#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
#include <color.hpp> // RGBColor
#include <linmtrx.hpp> // LinearMatrix
#include <vector3d.hpp> // Vector3D
#include <point3d.hpp> // Point3D
//##################### Forward Class Declarations #######################
class Mech;
class Entity;
class ResourceFile;
class NotationFile;
class CockpitHud;
//###########################################################################
//#################### MechWeapon Model Resource ########################
//###########################################################################
//
// Extends the PoweredSubsystem resource record. The MechWeapon-specific
// fields begin at +0x190 in the parsed record (see CreateStreamedSubsystem
// @004b9d10). Total record size is 0x1BC (444 bytes) -- the parser stamps
// subsystemModelSize = 0x1BC and classID = MechWeaponClassID (0xBCD).
//
struct MechWeapon__SubsystemResource:
public PoweredSubsystem::SubsystemResource
{
Scalar rechargeRate; // +0x190 "RechargeRate"
Scalar weaponRange; // +0x194 "WeaponRange"
ResourceDescription::ResourceID
explosionResourceID; // +0x198 "ExplosionModelFile"
Scalar damageAmount; // +0x19C "DamageAmount" (-> damageData)
Enumeration damageType; // +0x1A0 "DamageType" (Collision..Energy)
Scalar heatCostToFire; // +0x1A4 "HeatCostToFire"
int pipPosition; // +0x1A8 "PipPosition"
RGBColor pipColor; // +0x1AC "PipColor" (3 floats, init -1)
int pipExtendedRange; // +0x1B8 "PipExtendedRange"
}; // sizeof == 0x1BC
//###########################################################################
//######################### MechWeapon ############################
//###########################################################################
//
// Abstract base for any subsystem that can be aimed at a target and fired,
// dealing damage and (optionally) spawning an explosion model and a HUD pip.
// (vtable @00511d2c, ctor @004b99a8, dtor @004b9b9c.)
//
class MechWeapon:
public PoweredSubsystem
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Shared Data Support
//
public:
static Derivation ClassDerivations;
static Receiver::MessageHandlerSet MessageHandlers;
static AttributeIndexSet AttributeIndex;
static SharedData DefaultData;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Attribute Support (gauge data-binding wave)
//
// The energy/ballistic weapon engineering clusters read OutputVoltage (the
// charge/ready level -- animates as the weapon recharges after firing) and
// PercentDone (the recharge dial), both onto rechargeLevel (1.0 == ready).
// GetAttributeIndex() chains to PoweredSubsystem's so InputVoltage + the
// HeatSink temps also resolve; Emitter/PPC/ProjectileWeapon/MissileLauncher
// point their DefaultData at MechWeapon::GetAttributeIndex() to inherit it
// (they carried an EMPTY default-constructed index -> resolved NOTHING).
//
public:
enum {
OutputVoltageAttributeID = PoweredSubsystem::NextAttributeID,
PercentDoneAttributeID,
NextAttributeID
};
private:
static const IndexEntry AttributePointers[];
public:
static AttributeIndexSet& GetAttributeIndex();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Update record (replication) -- extends the base Simulation record with
// the weapon-specific alarm/idle state appended by WriteUpdateRecord.
//
public:
struct MechWeapon__UpdateRecord:
public PoweredSubsystem::UpdateRecord
{
Enumeration alarmState; // weaponAlarm state
int weaponIdle; // 1 == idle (alarm level 0)
};
typedef MechWeapon__UpdateRecord UpdateRecord;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Firing -- the central abstract operation
//
// Pure-virtual in spirit: the base body @004b95ec just asserts
// "MechWeapon abstract class should [not be fired]". Emitter (PPC, Gauss)
// and ProjectileWeapon (MissileLauncher) provide the real implementations;
// PPC::FireWeapon / GaussRifle::FireWeapon chain up to Emitter::FireWeapon.
//
protected:
virtual void
FireWeapon(); // @004b95ec (abstract stub)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Subsystem virtual overrides (slots on vtable @00511d2c)
//
// Slot indices verified by diffing against the Subsystem vtable tail of
// @0050e170 and the PoweredSubsystem vtable @0050f9d8:
// slot 6 TakeDamage(Damage&) (Subsystem base @0041bd34)
// slot 7 WriteUpdateRecord(...) (Subsystem base @0041c500)
// slot 9 ReadUpdateRecord(...) (PoweredSubsystem base @004b0efc)
// slot 10 ResetToInitialState() (PoweredSubsystem base @004b0e6c)
// slot 13 PrintState() (PoweredSubsystem base @004b1224)
//
public:
void
TakeDamage(Damage &damage); // slot 6, @004b964c
void
ResetToInitialState(); // slot 10, @004b96ec
void
PrintState(); // slot 13, @004b9d00
protected:
void
WriteUpdateRecord( // slot 7, @004b9690
UpdateRecord *message,
int update_model
);
void
ReadUpdateRecord(UpdateRecord *message); // slot 9, @004b96d4
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Test Class Support
//
public:
static Logical
TestClass(Mech&);
Logical
TestInstance() const;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Targeting / aiming helpers (used by FireWeapon and the per-frame update)
//
protected:
// E3: read the owning Mech's CURRENT target slot (mech+0x388, the Entity* that
// mech4.cpp's targeting step writes) directly -- the old `hasTarget` member was
// never set (the Mech per-frame refresh isn't reconstructed) so the fire gate was
// permanently false. GetOwningSimulation is non-const, hence the cast.
Logical
HasActiveTarget() const
{
char *o = (char *)owner; // inherited MechSubsystem::owner (the Mech)
return (o != 0 && *(void **)(o + 0x388) != 0) ? True : False;
}
// @004b9bdc -- recompute rangeToTarget and the heat-degraded
// effectiveRange, then set targetWithinRange. No target => range -1.
// Returns the resulting targetWithinRange flag (consumed by Emitter /
// ProjectileWeapon simulation).
Logical
UpdateTargeting();
// @004b9608 -- rising-edge detector on fireImpulse (best-effort name);
// returns True the frame the weapon's discharge signal goes positive.
Logical
CheckFireEdge(); // TODO: confirm semantic (recharge/discharge edge)
// @004b9cbc -- copy the owning Mech's current target position.
void
GetTargetPosition(Point3D &position);
// @004b9cdc -- direction from a supplied point to the weapon mount.
void
GetVectorToWeapon(const Point3D &from, Vector3D &direction);
// @004b9948 -- resolve the weapon's muzzle/site world position from the
// owning Mech's segment table.
void
GetMuzzlePoint(Point3D &point);
// @004b9864 -- build an aim orientation (atan2 of a direction vector).
void
ComputeAimOrientation(LinearMatrix &orientation, const Vector3D &direction);
// @004b9728 -- register the targeting "pip" / fire marker with the
// owning Mech's cockpit HUD manager.
void
DrawWeaponPip(const LinearMatrix &transform);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 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( // @004b9d10
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 weapon base class.
// Offsets are byte offsets into the shipped object (PoweredSubsystem
// occupies everything below +0x31c).
//
protected:
// --- recharge / firing state ---
Scalar fireImpulse; // @0x31C reset 0 (edge-detected; best-effort name)
Scalar rechargeLevel; // @0x320 reset 1.0 (1.0 == fully charged/ready)
Scalar rangeToTarget; // @0x324 distance to current target (-1 if none)
Scalar effectiveRange; // @0x328 (1 - heatLoad) * weaponRange (heat-degraded)
Scalar weaponRange; // @0x32C resource WeaponRange (configured max)
int pipState; // @0x330 reset 0 (TODO: name -- pip/active state)
Logical usesExternalModel; // @0x334 resource model-name compare flag (best-effort)
int pipPosition; // @0x338 resource PipPosition
RGBColor pipColor; // @0x33C resource PipColor (3 floats, init -1,-1,-1)
int pipExtendedRange; // @0x348 resource PipExtendedRange
Logical targetWithinRange; // @0x34C PPC.CPP: "if (targetWithinRange)"
// --- damage / display alarm ---
GaugeAlarm54 weaponAlarm; // @0x350 0x54 AlarmIndicator (ctor FUN_0041b9ec); level @0x364 -> ends 0x3A4
Scalar previousFireImpulse; // @0x3A4 reset 0 (fire-edge history, see @004b9608)
Damage damageData; // @0x3A8 damageType @0x3A8, damageAmount @0x3AC
// PPC.CPP: Damage energy_damage(damageData)
Scalar heatCostToFire; // @0x3D8 resource HeatCostToFire
Scalar rechargeRate; // @0x3DC resource RechargeRate
Logical useConfiguredPip; // @0x3E0 = (usesExternalModel == 0) (best-effort)
ResourceDescription::ResourceID
explosionResourceID; // @0x3E4 resource ExplosionModelFile
Scalar recoil; // @0x3E8 reset 0 (Emitter::firingArmed aliases this slot)
int segmentPageIndex; // @0x3EC resource segmentIndex (+0x28) -- LAST field, binary ends 0x3F0
// The binary MechWeapon ends at 0x3F0 (segmentPageIndex is the last ctor write,
// @004b99a8 param_1[0xfb]). The former tail fields segmentReference/pipSegment/
// hasTarget/targetPoint/muzzlePoint were PHANTOM (no binary storage) and, worse,
// collided with the Emitter subclass's own fields at 0x3F0+; removed:
// * segmentReference -> the muzzle segment is the inherited base slot this+0xdc
// (GetMuzzlePoint resolves it live via the mech4 BTResolveWeaponMuzzle bridge)
// * pipSegment -> DrawWeaponPip gates on useConfiguredPip
// * hasTarget -> dead (HasActiveTarget reads owner+0x388)
// * targetPoint -> GetTargetPosition reads owner+0x37c
// * muzzlePoint -> resolved live by GetMuzzlePoint
friend struct MechWeaponLayoutCheck;
};
struct MechWeaponLayoutCheck {
static_assert(offsetof(MechWeapon, weaponAlarm) == 0x350, "MechWeapon::weaponAlarm @0x350 (0x54 alarm)");
static_assert(offsetof(MechWeapon, previousFireImpulse) == 0x3A4, "MechWeapon::previousFireImpulse @0x3A4 (alarm ends here)");
static_assert(offsetof(MechWeapon, recoil) == 0x3E8, "MechWeapon::recoil @0x3E8");
static_assert(offsetof(MechWeapon, segmentPageIndex) == 0x3EC, "MechWeapon::segmentPageIndex @0x3EC (last field)");
static_assert(sizeof(MechWeapon) == 0x3F0, "sizeof(MechWeapon) 0x3F0");
};
#endif