- hud.hpp/hud.cpp/CLASSMAP.md: the binary HUD AttributePointers table starts at @5110b8 with a FULL id-3 FlickerRate record (the old transcription read it as a label and shifted every offset one slot). Re-based member names/offsets: rotationOfTorsoHorizontal@0x1DC (<- Torso+0x1D8 twist), limits@0x1E0/0x1E4, speed@0x1E8, rangeToTarget@0x1EC, @0x1F0 unbound, Visible@0x1F4 (init 1), Lock@0x1F8 (init 0), HotBoxVector P3D@0x1FC, ThreatVector P3D@0x208, CompassHeading Scalar@0x214 (= yaw euler[0] + twist, HudSimulation :5676). Cross-checked vs ctor @004b7f94 + HudSimulation @004b7830 (Lock rule writes @0x1F8 :5622/:5633; range default 1200 + 500 m/s slide @0x1EC). SetCompassHeading renamed SetThreatVector (@004b7810 writes @0x208 = the threat vector; no port caller). No behavioral change: the reticle feeds via port globals and the CFG never binds HUD attrs. Resolves the gauges-hud open question (Gitea #10 entry b). - L4VIDEO.cpp: BT_SHOT capture moved AFTER BTDrawGaugeInset -- the 2026-07-18 reorder had left it before the dock blit, so BT_DEV_GAUGES_DOCK screenshots silently omitted the gauge panel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
281 lines
13 KiB
C++
281 lines
13 KiB
C++
//===========================================================================//
|
|
// File: hud.hpp //
|
|
// Project: BattleTech //
|
|
// Contents: Cockpit Heads-Up Display subsystem //
|
|
//---------------------------------------------------------------------------//
|
|
// Date Who Modification //
|
|
// -------- --- ---------------------------------------------------------- //
|
|
// --/--/95 ?? Initial coding. //
|
|
//---------------------------------------------------------------------------//
|
|
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
|
|
// PROPRIETARY AND CONFIDENTIAL //
|
|
//===========================================================================//
|
|
//
|
|
// RECONSTRUCTED from the shipped binary (Ghidra pseudo-C, recovered shard
|
|
// part_013.c) cross-referenced with the sibling subsystem sources
|
|
// (powersub.hpp/.cpp, heat.hpp/.cpp, sensor.hpp/.cpp). NO HUD.HPP survived;
|
|
// the class body below is inferred from usage, the embedded string pool and
|
|
// the streamed-resource / attribute tables. Each method cites its @ADDR.
|
|
//
|
|
// ---------------------------------------------------------------------------
|
|
// IDENTIFICATION (resolves a CLASSMAP "gap")
|
|
// ---------------------------------------------------------------------------
|
|
// * mech3.cpp factory map: HUD::CreateStreamedSubsystem @004b81d4,
|
|
// DefaultData &DAT_00511078.
|
|
// * vtable @0050511510 (slot0 dtor @004b816c), ctor @004b7f94.
|
|
// * CreateStreamedSubsystem @004b81d4 stamps classID 0x0BD6 and streamed
|
|
// model size 0x104 (resource +0x20 / +0x24), and parses the HUD-specific
|
|
// model fields "FlickerRate" / "HorizontalMovementPerSecond" /
|
|
// "HorizontalLimit" plus a "SegmentPageName" index.
|
|
// * String pool @005112d0..@00511390 ("HUD", "FlickerRate",
|
|
// "RotationOfTorsoHorizontal", "HorizontalTorsoLimitRight/Left",
|
|
// "SpeedOfTorsoHorizontal", "RangeToTarget", "Visible", "Lock",
|
|
// "HotBoxVector", "ThreatVector", "CompassHeading") names the gauge
|
|
// read-out attributes.
|
|
//
|
|
// CLASSMAP.md "Resolved conflict" already flagged @004b7f94 / vtable
|
|
// @00511510 as "the HUD torso-horizon gimbal display" mis-labelled
|
|
// "MechTech" by mech.cpp's factory switch (case 0xBD6). This file confirms
|
|
// that: classID 0xBD6, base PowerWatcher.
|
|
//
|
|
// ---------------------------------------------------------------------------
|
|
// BASE CLASS
|
|
// ---------------------------------------------------------------------------
|
|
// HUD IS-A PowerWatcher (powersub.hpp): the ctor @004b7f94 chains to the
|
|
// PowerWatcher ctor FUN_004b18a4 and CreateStreamedSubsystem chains to
|
|
// PowerWatcher::CreateStreamedSubsystem FUN_004b198c. PowerWatcher is a
|
|
// HeatWatcher/HeatableSubsystem-derived *monitor* subsystem (NOT a
|
|
// PoweredSubsystem); the HUD therefore inherits the heat/voltage watchdog
|
|
// state (heat-state level @0x140, watched-voltage alarm level @0x198) used
|
|
// by the per-frame update. The shared base object occupies bytes 0..0x1D8;
|
|
// HUD's own members begin at 0x1D8. Total object size 0x2A4 (matches
|
|
// mech.cpp factory allocation for the 0xBD6 case).
|
|
//
|
|
#if !defined(HUD_HPP)
|
|
# define HUD_HPP
|
|
|
|
#if !defined(POWERSUB_HPP)
|
|
#include "powersub.hpp"
|
|
#endif
|
|
|
|
//##########################################################################
|
|
//####################### HUD::SubsystemResource #####################
|
|
//##########################################################################
|
|
//
|
|
// Extends the PowerWatcher/HeatableSubsystem resource record. The HUD fields
|
|
// begin at +0xF4 (see CreateStreamedSubsystem @004b81d4).
|
|
//
|
|
struct HUD__SubsystemResource:
|
|
public PowerWatcher::SubsystemResource
|
|
{
|
|
Scalar flickerRate; // +0xF4 "FlickerRate" (defaults to -1.0 in the SegmentPage form)
|
|
int segmentPageIndex; // +0xF8 resolved "SegmentPageName" (copied from +0x28)
|
|
Scalar horizontalMovementPerSecond; // +0xFC "HorizontalMovementPerSecond"
|
|
Scalar horizontalLimit; // +0x100 "HorizontalLimit"
|
|
};
|
|
|
|
//##########################################################################
|
|
//############################## HUD #################################
|
|
//##########################################################################
|
|
|
|
class HUD:
|
|
public PowerWatcher
|
|
{
|
|
friend struct HUDLayoutCheck; // live Watcher-branch re-base witness (hud.cpp)
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Shared Data Support
|
|
//
|
|
// DefaultData @00511078, ClassDerivations @00511088 ("HUD").
|
|
//
|
|
public:
|
|
static Derivation *GetClassDerivations(); // @00511088 (engine moved to accessor form)
|
|
static SharedData DefaultData; // @00511078
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Attribute Support
|
|
//
|
|
// AttributePointers IndexEntry table @005110c0 (each record binds a name
|
|
// to an attribute id and an object offset, the offset encoded as
|
|
// byte_offset|1 -- the same scheme used by sensor.hpp).
|
|
//
|
|
// RE-DUMPED 2026-07-19 (Gitea #10 audit; section_dump.txt @5110b8, 16-byte
|
|
// records {id, namePtr, offset|1, 0}, terminator @511160). The table
|
|
// actually STARTS at @005110b8 with a FULL id-3 FlickerRate record (the old
|
|
// transcription had misread it as a "category label" and shifted every
|
|
// subsequent offset down one slot):
|
|
// id 3 "FlickerRate" -> @0x1D8
|
|
// id 4 "RotationOfTorsoHorizontal" -> @0x1DC
|
|
// id 5 "HorizontalTorsoLimitRight" -> @0x1E0
|
|
// id 6 "HorizontalTorsoLimitLeft" -> @0x1E4
|
|
// id 7 "SpeedOfTorsoHorizontal" -> @0x1E8
|
|
// id 8 "RangeToTarget" -> @0x1EC
|
|
// id 9 "Visible" -> @0x1F4 (init 1; @0x1F0 is unbound)
|
|
// id 10 "Lock" -> @0x1F8 (init 0)
|
|
// id 11 "HotBoxVector" -> @0x1FC (Point3D)
|
|
// id 12 "ThreatVector" -> @0x208 (Point3D)
|
|
// id 13 "CompassHeading" -> @0x214 (Scalar: yaw + torso twist)
|
|
// CROSS-CHECKED against the ctor @004b7f94 (inits @0x1F4=1 Visible-on /
|
|
// @0x1F8=0 Lock-off; @0x1F0 uninitialized) and HudSimulation @004b7830
|
|
// (per-frame @0x1DC <- linkedTorso+0x1D8 = Torso::currentTwist; @0x1EC
|
|
// range default 1200.0 + the 500 m/s display slide; the Lock rule writes
|
|
// @0x1F8 at :5622/:5633; @0x214 = EulerAngles(mech)[0] + @0x1DC at :5676;
|
|
// the hotbox Point3D copy @0x1FC at :5680). All consistent.
|
|
//
|
|
// CONFIRMED (task #37): the recovered reticle Execute @004cdcf0 binds and
|
|
// USES ids 4/5/6/8/0xA/0xB/0xC/0xD exactly per these names --
|
|
// RotationOfTorsoHorizontal drives the twist tape over the
|
|
// HorizontalTorsoLimit full-scales, RangeToTarget the range caret, Lock
|
|
// the spinning lock ring, HotBoxVector the target box (eye-space vec3),
|
|
// ThreatVector the compass threat trail, CompassHeading the rose
|
|
// rotation.
|
|
//
|
|
public:
|
|
enum {
|
|
RotationOfTorsoHorizontalAttributeID = PowerWatcher::NextAttributeID, // 4
|
|
HorizontalTorsoLimitRightAttributeID, // 5
|
|
HorizontalTorsoLimitLeftAttributeID, // 6
|
|
SpeedOfTorsoHorizontalAttributeID, // 7
|
|
RangeToTargetAttributeID, // 8
|
|
VisibleAttributeID, // 9
|
|
LockAttributeID, // 10
|
|
HotBoxVectorAttributeID, // 11
|
|
ThreatVectorAttributeID, // 12
|
|
CompassHeadingAttributeID, // 13
|
|
NextAttributeID
|
|
};
|
|
|
|
private:
|
|
static const IndexEntry AttributePointers[]; // @005110c0
|
|
protected:
|
|
static AttributeIndexSet AttributeIndex;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Local data. Offsets are byte offsets into the shipped object; names are
|
|
// best-effort from the attribute pool + ctor/HudSimulation usage.
|
|
//
|
|
// Member names re-based 2026-07-19 (Gitea #10) against the RE-DUMPED attr
|
|
// table @005110b8 -- the old names were shifted one slot (see the table
|
|
// comment above).
|
|
protected:
|
|
Scalar flickerRate; // @0x1D8 resource +0xF4 ("FlickerRate", attr id 3)
|
|
// Torso / target read-outs sampled at construction AND per-frame in
|
|
// HudSimulation from the linked Torso subsystem (owner Mech +0x438 ->
|
|
// this->linkedEntity @0x234):
|
|
Scalar rotationOfTorsoHorizontal; // @0x1DC <- linkedEntity +0x1D8 (Torso::currentTwist; attr 4)
|
|
Scalar torsoLimitRight; // @0x1E0 <- linkedEntity +0x1DC (attr 5)
|
|
Scalar torsoLimitLeft; // @0x1E4 <- linkedEntity +0x1E0 (attr 6)
|
|
Scalar torsoSpeed; // @0x1E8 <- linkedEntity +0x1E8 (attr 7 "SpeedOfTorsoHorizontal")
|
|
Scalar rangeToTarget; // @0x1EC computed target distance; 1200.0 default; 500 m/s display slide (attr 8)
|
|
int scratch1F0; // @0x1F0 unbound / not ctor-initialized in the binary
|
|
int visible; // @0x1F4 init 1 ("Visible", attr 9; heat/page gating)
|
|
int lockFlag; // @0x1F8 init 0 ("Lock", attr 10; the :5619-5634 lock rule)
|
|
Point3D hotBoxVector; // @0x1FC init (0,0,0) ("HotBoxVector", attr 11)
|
|
Point3D threatVector; // @0x208 init (0,0,0) ("ThreatVector", attr 12); see SetThreatVector
|
|
Scalar compassHeading; // @0x214 init 0 ("CompassHeading", attr 13; yaw euler[0] + torso twist @:5676)
|
|
Scalar blinkTimer; // @0x218 accumulates time, reset against flickerRate
|
|
Scalar flickerTimer; // @0x21C
|
|
int blinkState; // @0x224 init 1 (visible phase of the blink)
|
|
int transitionFlag; // @0x22C init 0
|
|
Scalar previousHorizontal; // @0x230
|
|
int linkedEntity; // @0x234 owner Mech +0x438 (active weapon/reticle source)
|
|
HeatAlarm statusAlarm; // @0x238 2-level page/status indicator (Initialize/SetLevel surface)
|
|
// Free-aim / reticle-slew demand: WRITTEN by MechControlsMapper::InterpretControls
|
|
// (@004afd10 cockpit+0x28c <- stick yaw when the torso's horizontal twist is
|
|
// disabled; zeroed in Basic mode). Was placeholder-named flickerPhase (init-only).
|
|
Scalar freeAimSlew; // @0x28C
|
|
public:
|
|
void SetFreeAimSlew(Scalar v) { freeAimSlew = v; } // mapper write (@004afd10 cockpit+0x28c)
|
|
protected:
|
|
Scalar scratch290; // @0x290 init 0
|
|
Scalar horizontalTorsoOffset; // @0x294 init 0; clamped to +/- horizontalLimit, written to owner +0x36C
|
|
Scalar horizontalMovementPerSecond; // @0x298 resource +0xFC
|
|
Scalar horizontalLimit; // @0x29C resource +0x100
|
|
int flickerActive; // @0x2A0 current flicker on/off (UpdateFlicker @004b7ed4)
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Simulation Support
|
|
//
|
|
public:
|
|
typedef void
|
|
(HUD::*Performance)(Scalar time_slice);
|
|
|
|
void
|
|
SetPerformance(Performance performance)
|
|
{
|
|
Check(this);
|
|
activePerformance = (Simulation::Performance)performance;
|
|
}
|
|
|
|
void
|
|
HudSimulation(Scalar time_slice); // @004b7830 (Performance, PTR @00511170)
|
|
|
|
// @004b7810 writes the Point3D @0x208 -- which the RE-DUMPED attr table
|
|
// (2026-07-19) proves is the THREAT vector, not the compass (the compass
|
|
// is the Scalar @0x214). Renamed from the old mislabel SetCompassHeading;
|
|
// no port caller exists (the port threat-trail feed goes through the
|
|
// reticle globals, btl4vid.cpp).
|
|
void
|
|
SetThreatVector(const Point3D &threat); // @004b7810 (writes threatVector @0x208)
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Subsystem virtual overrides (vtable @00511510)
|
|
//
|
|
public:
|
|
Logical
|
|
HandleMessage(int message); // slot 9, @004b7780 (body not captured)
|
|
void
|
|
ResetToInitialState(Logical powered = True); // slot 10, @004b77bc
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Test Class Support
|
|
//
|
|
public:
|
|
static Logical TestClass(Mech&);
|
|
Logical TestInstance() const; // @004b81b8
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Construction and Destruction
|
|
//
|
|
public:
|
|
typedef HUD__SubsystemResource SubsystemResource;
|
|
|
|
HUD(
|
|
Mech *owner,
|
|
int subsystem_ID,
|
|
SubsystemResource *subsystem_resource,
|
|
SharedData &shared_data = DefaultData
|
|
); // @004b7f94
|
|
~HUD(); // @004b816c
|
|
|
|
static int
|
|
CreateStreamedSubsystem( // @004b81d4
|
|
NotationFile *model_file,
|
|
const char *model_name,
|
|
const char *subsystem_name,
|
|
SubsystemResource *subsystem_resource,
|
|
NotationFile *subsystem_file,
|
|
const ResourceDirectories *directories,
|
|
int passes = 1
|
|
);
|
|
|
|
protected:
|
|
// Flicker helper @004b7ed4: drives flickerActive (@0x2A0) toward 0,
|
|
// returns non-zero while still flickering.
|
|
Logical UpdateFlicker(Scalar time_slice); // @004b7ed4
|
|
|
|
//
|
|
// Reconstruction accessors for the inherited watchdog state read by
|
|
// HudSimulation: heat-watcher flag (this+0x40), watched-voltage alarm
|
|
// level (@0x198) and heat-state level (@0x140). The PowerWatcher /
|
|
// HeatWatcher base in this header set does not expose these, so they are
|
|
// best-effort placeholders that let the per-frame page-gating logic
|
|
// compile; a human should bind them to the real inherited members.
|
|
// (See CROSS-FAMILY NEEDS.)
|
|
//
|
|
int HeatWatcherFlag() const { return 0; }
|
|
int WatchedVoltageLevel() const { return 0; }
|
|
int HeatStateLevel() const { return 0; }
|
|
};
|
|
|
|
#endif
|