The visible money-shot: a green compass needle that rotates with the mech's facing plus a numeric heading in whole degrees (render-verified: the needle swung and the number went 247 -> 182 as the mech turned). Full class reconstruction from the binary (Make/ctor/dtor/TestInstance/ ShowInstance/BecameActive/Execute). Ghidra dropped the x87 float math feeding Execute's needle endpoints and the ctor's NumericDisplay centering, so it was recovered by disassembling BTL4OPT.EXE with capstone (scratchpad/disas_hp.py): the needle is a radial line from innerRadius(20) to outerRadius(39) at the heading angle, endpoints rounded half-up; the readout is round(360 - deg). Two corrections vs the map: - The header ctor was 12 args; the binary's is 14 -- widened it. Fields @0x98/ @0x9C are the needle's inner/outer RADIUS (Execute multiplies them by sin/cos), not "color/spacing" as the old field names implied; renamed accordingly. - ENGINE-CONVENTION FIX: the binary read the heading from EulerAngles index [0], but the WinTesla EulerAngles(Quaternion) decomposition is ambiguous for a yawing mech (the quaternion double-cover flips it to a pitch=roll=pi branch as yaw sweeps past +/-pi -> the needle spins erratically). Switched to YawPitchRoll, whose yaw-first .yaw is the clean continuous heading (pitch=roll~0). Faithful to the binary's intent (heading), correct for this engine's decomposition. renderer->GetLinkedEntity() resolves the viewpoint mech (a GetViewpointEntity fallback is kept belt-and-braces). Deps (NumericDisplay/GraphicsViewRecord/ GraphicGauge) are real engine classes -> no /FORCE risk; verified no unresolved HeadingPointer externals, no parse desync, combat un-regressed (TARGET DESTROYED, 0 crashes). Details + the disasm technique: docs/GAUGE_COMPOSITE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
970 lines
39 KiB
C++
970 lines
39 KiB
C++
//===========================================================================//
|
|
// File: btl4gaug.cpp //
|
|
// Project: BattleTech Brick: Gauge Renderer Manager //
|
|
// Contents: The cockpit instrument gauge library (see btl4gaug.hpp). //
|
|
//---------------------------------------------------------------------------//
|
|
// Date Who Modification //
|
|
// -------- --- ---------------------------------------------------------- //
|
|
// 02/22/96 CPB Initial coding. //
|
|
//---------------------------------------------------------------------------//
|
|
// Copyright (C) 1996, Virtual World Entertainment, Inc. All rights reserved //
|
|
// PROPRIETARY AND CONFIDENTIAL //
|
|
//===========================================================================//
|
|
//
|
|
// RECONSTRUCTED from the shipped binary (BTL4OPT.EXE). Behaviour follows the
|
|
// Ghidra pseudo-C in recovered/all/part_013.c (@004c2f94..@004c5e84) and
|
|
// part_014.c (@004c5e84..@004c6394). Class/member names are taken from the
|
|
// CODE name-string pool; per-method @ADDR evidence is cited inline.
|
|
//
|
|
// Translation-unit extent (linked between btl4rdr.obj and btl4gau2.obj):
|
|
// first gaug code @004c2f94 (DrawTiledBitmap helper @004c2ff8)
|
|
// anchor @004c3f6c ColorMapperHeat::ColorMapperHeat
|
|
// assert "...\BTL4GAUG.CPP" line 0x68a
|
|
// last gaug code @004c6394 SegmentArcRatio (+ dtor thunks @004c66xx)
|
|
// btl4gau2 begins @004c6798 (SeekVoltage gauge, string pool 0x5199xx)
|
|
//
|
|
// Engine-internal helper map (consistent with btl4rdr.cpp's table):
|
|
// FUN_00444308 Gauge ctor FUN_00444360 Gauge dtor
|
|
// FUN_004443a4 Gauge::TestInstance
|
|
// FUN_00444124 GaugeConnection ctor FUN_00444148 GaugeConnection dtor
|
|
// FUN_00444818 GraphicGauge ctor FUN_00444870 GraphicGauge dtor
|
|
// FUN_004448ac GraphicGauge::TestInstance FUN_004448bc ::ShowInstance
|
|
// FUN_004700ac GaugeRenderer::InternName(name) -> char* (interned)
|
|
// FUN_00447f84 GaugeRenderer::GetGraphicsPort(renderer, portNumber)
|
|
// FUN_00442f6a / 00443090 PaletteCache AddRef / Release (renderer+0x4C, +0x3c)
|
|
// FUN_00442aec / 00442c12 BitMapCache AddRef / Release (renderer+0x4C, +4)
|
|
// FUN_00442d2b / 00442e51 PixMapCache AddRef / Release (renderer+0x4C, +0x20)
|
|
// FUN_0041f98c Entity::FindSubObject(name)
|
|
// FUN_0041bfc0 Entity::FindAttributeIndex(name) (btl4gau2)
|
|
// FUN_0041a1a4 IsDerivedFrom(classDerivations)
|
|
// FUN_00417ab4 SharedData::Resolve() / operator Entity*
|
|
// FUN_0042076c Subsystem::FindDamageZone(record) -> index
|
|
// FUN_004dcd94 Round(Scalar) -> int
|
|
// FUN_0040385c Verify(msg,file,line)
|
|
// FUN_004dbb24 DebugStream << (char*) FUN_004db78c DebugStream << (int)
|
|
// FUN_004d4b58 stricmp FUN_004d49b8 strcat
|
|
// FUN_00402298 operator new FUN_004022e8/004022d0 operator delete
|
|
// FUN_00474855 GaugeConnectionDirectOf<Scalar>::ctor
|
|
// FUN_004749de GaugeConnectionDirectOf<int>::ctor
|
|
// FUN_0040954c Quaternion->Euler FUN_00408328 SinCos
|
|
// FUN_004700bc NumericDisplay ctor FUN_0047018c ::dtor
|
|
// FUN_004703f4 NumericDisplay::Reset FUN_00470430 ::DrawAt
|
|
// FUN_0044a5b4/5dc GraphicsViewRecord ctor/dtor
|
|
// FUN_0044a650/630 GraphicsViewRecord Erase / Reset
|
|
// DAT_00524e20 DebugStream (warning channel)
|
|
// 0x50e3ec / 0x50e604 heat-subsystem class-derivation tables
|
|
//
|
|
// GraphicsView vtable slots used below (this+0x48):
|
|
// +0x08 SetExtent +0x10 SetOrigin +0x18 SetColor +0x24 MoveTo
|
|
// +0x38 LineTo/FillTo +0x48 FillTo +0x4C Fill +0x54 Blit
|
|
// +0x58 BlitClipped +0x5C BlitStretched +0x64 BeginClip +0x68 EndClip
|
|
//
|
|
// Recovered constant-pool floats (section_dump.txt):
|
|
// _DAT_004c452c = 0.0f (HorizTwoPartBar low clamp)
|
|
// _DAT_004c4b00 = 0.0f (VertTwoPartBar low clamp)
|
|
// _DAT_004c4d3c = 0.0f , _DAT_004c4d40 = 1.0f (VertNormalSlider [0,1] clamp)
|
|
// _DAT_004c5acc = 0.0f , 0x42652ee1 = 57.2958f (HeadingPointer rad->deg)
|
|
// _DAT_004c62d4 = 0.75f, _DAT_004c6484 = 0.75f (arc span fraction)
|
|
// _DAT_0050e3d8 = 0.0025f (OneOfSeveralStates 'critical' threshold)
|
|
// FUN_004c2f88() returns 7 (shared blip colour, defined in btl4rdr.cpp)
|
|
//
|
|
|
|
// The shared reconstruction headers heat.hpp and mechrecon.hpp each define a
|
|
// `DebugStream` diagnostic sink, and they do so incompatibly (std::ostream&
|
|
// vs the ReconStream artifact struct). heat.hpp's variant is the optional one
|
|
// (gated on this guard); pre-defining it keeps the single mechrecon ReconStream
|
|
// definition active throughout this translation unit and avoids the clash.
|
|
#define BT_DEBUGSTREAM_DEFINED
|
|
|
|
#include <bt.hpp>
|
|
#pragma hdrstop
|
|
|
|
#if !defined(BTL4GAUG_HPP)
|
|
# include <btl4gaug.hpp>
|
|
#endif
|
|
#if !defined(APP_HPP)
|
|
# include <app.hpp>
|
|
#endif
|
|
|
|
static const Scalar ZeroClamp = 0.0f; // _DAT_004c452c / _DAT_004c4b00
|
|
static const Scalar OneClamp = 1.0f; // _DAT_004c4d40
|
|
static const Scalar RadiansToDegrees = 57.2958f; // 0x42652ee1
|
|
static const Scalar StatesCriticalLevel = 0.0025f; // _DAT_0050e3d8
|
|
|
|
|
|
//###########################################################################
|
|
// Reconstruction shims (file-local)
|
|
//
|
|
// The shipped BT engine exposed a warning DebugStream (DAT_00524e20) and a
|
|
// GaugeRenderer name-intern + palette/bitmap cache. In the WinTesla engine
|
|
// these headers describe, the same concepts are reached through std::cerr and
|
|
// the Warehouse resource bins (Warehouse::palette8Bin), so the bodies below
|
|
// bind to those. Behaviour is preserved; only the access path differs.
|
|
// (The DebugStream warning channel, DAT_00524e20, is provided by the shared
|
|
// reconstruction headers -- heat.hpp / mechrecon.hpp.)
|
|
//###########################################################################
|
|
|
|
//
|
|
// HeatConnection (file-private) -- FUN_004c3664. Drives a ColorMapper's colour
|
|
// index from a heat-bearing subsystem's live temperature. Only its
|
|
// construction is emitted in this translation unit (ColorMapperHeat's ctor);
|
|
// the per-frame Transfer() is summarised in the notes above.
|
|
//
|
|
class HeatConnection : public GaugeConnection
|
|
{
|
|
public:
|
|
HeatConnection(int *destination, Subsystem *source)
|
|
: GaugeConnection(0),
|
|
currentColorIndex(destination),
|
|
heatSubsystem(source)
|
|
{}
|
|
|
|
protected:
|
|
//
|
|
// The per-frame data feed -- @004c3720 (the recovered "Transfer"). The
|
|
// gauge framework calls this once per frame (Plug/GaugeConnection vtbl
|
|
// virtual Update()). It samples the live heat-subsystem temperature and
|
|
// writes the colour index that ColorMapper::Execute then pushes into the
|
|
// hardware palette slot, tinting the cockpit heat art.
|
|
//
|
|
// no subsystem -> 100 (fail safe: full tint)
|
|
// subsystem destroyed -> 100 (recovered "+0x40 == 1" path)
|
|
// else -> Round(currentTemperature @0x114)
|
|
//
|
|
// NOTE (flagged): the recovered code rounds the value resolved at +0x114
|
|
// (currentTemperature) directly into the 0..255 colour index that
|
|
// ColorMapper::Execute then clamps to [0,255]. The exact temperature->
|
|
// percentage scaling (HEAT.TCP seeds currentTemperature at 300.0) is a
|
|
// tuning detail that a human should reconcile against the original
|
|
// l4gauge.cfg palette ramp; the live data path itself is correct.
|
|
//
|
|
void Update(); // override
|
|
|
|
int *currentColorIndex; // destination@0x18
|
|
Subsystem *heatSubsystem; // source@0x10
|
|
};
|
|
|
|
//
|
|
// Round-to-nearest (engine Round @004dcd94).
|
|
//
|
|
static inline int HeatRound(Scalar value)
|
|
{
|
|
return (int)((value >= 0.0f) ? (value + 0.5f) : (value - 0.5f));
|
|
}
|
|
|
|
void
|
|
HeatConnection::Update()
|
|
{
|
|
if (heatSubsystem == NULL)
|
|
{
|
|
*currentColorIndex = 100;
|
|
return;
|
|
}
|
|
|
|
//
|
|
// The ctor verified heatSubsystem derives from HeatableSubsystem / HeatSink
|
|
// (BTL4GAUG.CPP:0x68a), so the live temperature and damage state are reached
|
|
// through the HeatableSubsystem layout.
|
|
//
|
|
HeatableSubsystem *heat = (HeatableSubsystem *)heatSubsystem;
|
|
|
|
if (heat->IsDamaged()) // inherited MechSubsystem simulationState (+0x40) -> max tint
|
|
{
|
|
*currentColorIndex = 100;
|
|
return;
|
|
}
|
|
|
|
*currentColorIndex = HeatRound(heat->currentTemperature); // +0x114 live feed
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
//###########################################################################
|
|
// File-private GaugeConnection subclasses
|
|
//
|
|
// Every gauge in this module that is "driven by mech subsystem state" is fed
|
|
// through one of these tiny GaugeConnection objects. The base GaugeConnection
|
|
// stores source@0x10 and destination@0x14 ; Transfer() (a virtual) computes
|
|
// a value from the source and writes it to *destination. The gauge framework
|
|
// calls Transfer() once per frame (after AddConnection, vtbl+0x34).
|
|
//
|
|
// Layouts below use the ctor argument order:
|
|
// FUN_00444124(this, 0) base ctor
|
|
// this[4] (@0x10) = source this[5] (@0x14) = destination
|
|
// BEST-EFFORT: these classes were emitted alongside the gauges that own them
|
|
// and share this translation unit; their template spelling is inferred.
|
|
//###########################################################################
|
|
//###########################################################################
|
|
|
|
//
|
|
// SubsystemTorsoHeading connection -- @004c3134 ctor / @004c31a0 Transfer.
|
|
// If the owner mech's torso (sub-object index 3) is present, copies its
|
|
// heading (entity+0x1d4) to the destination, else 0.
|
|
//
|
|
// SubsystemNameId connection -- @004c31ec / @004c3258 (video-object+0x1e0).
|
|
// SubsystemAltId connection -- @004c3288 / @004c32f4 (video-object+0x1dc).
|
|
// (These three feed numeric / heading readouts.)
|
|
//
|
|
// StateConnection -- @004c3324 / @004c3390 -- copies Subsystem state@0x14.
|
|
// Used by OneOfSeveralStates.
|
|
//
|
|
// ArmorZoneConnection -- @004c33a4 ctor / @004c3430 Transfer.
|
|
// ctor resolves a single damage-zone slot: source = Subsystem.zones@0x120
|
|
// [zone_index] (or 0 if zone_index < 0). Transfer: no zone -> 100, else the
|
|
// zone's damage percentage (Round). Used by ColorMapperArmor.
|
|
//
|
|
// MultiArmorConnection -- @004c346c ctor / @004c34f4 Transfer.
|
|
// ctor copies 8 zone indices (this[6..13]). Transfer scans the 8 zones,
|
|
// keeps the maximum damage ratio (zone+0x158), Round -> destination (100 if
|
|
// none present). Used by ColorMapperMultiArmor.
|
|
//
|
|
// CriticalConnection -- @004c3598 ctor / @004c3610 Transfer.
|
|
// source = a Subsystem. Transfer: no subsystem -> 0 ; subsystem operational
|
|
// (subsys+0x40 == 1) -> 100 ; else Round of the live value.
|
|
// Used by ColorMapperCritical.
|
|
//
|
|
// HeatConnection -- @004c3664 ctor / @004c3720 Transfer.
|
|
// ctor: source = the heat subsystem; flag@0x14 records whether it is the
|
|
// "primary" heat class (IsDerivedFrom 0x50e3ec) vs. the alternate (0x50e604);
|
|
// destination@0x18. Transfer: no subsystem -> 100 ; subsystem operational
|
|
// (+0x40 == 1) -> 100 ; else Round( Resolve(subsys+0x114 currentTemperature) ).
|
|
// Used by ColorMapperHeat -- THIS is the heat gauge's data feed.
|
|
//
|
|
// All Transfer bodies are reproduced inside the owning gauge's notes; the
|
|
// connection classes themselves are otherwise trivial (TestInstance returns
|
|
// True; ShowInstance is the inherited no-op).
|
|
//
|
|
|
|
|
|
//###########################################################################
|
|
//###########################################################################
|
|
// ColorMapper (base : Gauge)
|
|
//###########################################################################
|
|
//###########################################################################
|
|
|
|
//
|
|
// @004c37dc -- vtable PTR_FUN_00518e10, base name passed through to Gauge.
|
|
// Interns both palette names, flags twoColorMode when they differ, validates
|
|
// that both palettes exist in the renderer's palette cache (warns otherwise),
|
|
// stashes the target hardware palette slot and resolves the renderer graphics
|
|
// port that owns the live palette.
|
|
//
|
|
ColorMapper::ColorMapper(
|
|
GaugeRate rate,
|
|
ModeMask mode_mask,
|
|
L4GaugeRenderer *renderer,
|
|
int graphics_port_number,
|
|
int color_index,
|
|
const char *palette_name_a,
|
|
const char *palette_name_b,
|
|
const char *identification_string
|
|
):
|
|
Gauge(rate, mode_mask, renderer, /*owner*/0, identification_string) // FUN_00444308
|
|
{
|
|
// Original interned a private copy of each name (FUN_004700ac); the resource
|
|
// strings handed in here persist for the gauge's lifetime, so retain them.
|
|
paletteName[0] = (char *)palette_name_a;
|
|
paletteName[1] = (char *)palette_name_b;
|
|
twoColorMode = (stricmp(paletteName[0], paletteName[1]) != 0); // FUN_004d4b58
|
|
|
|
//
|
|
// Reference each palette in the warehouse palette bin (AddRef + load); a
|
|
// missing palette is a content warning. (PaletteCache AddRef = FUN_00442f6a.)
|
|
//
|
|
WarehouseBinOf<Palette8> &palettes = renderer->warehousePointer->palette8Bin;
|
|
for (int i = 0; i < 2; ++i)
|
|
{
|
|
if (palettes.Get(paletteName[i]) == NULL)
|
|
{
|
|
DebugStream << "ColorMapper cannot find palette "
|
|
<< paletteName[i] << "\n";
|
|
}
|
|
}
|
|
|
|
previousColorIndex = -1; // this[0x16] (force first push)
|
|
previousRed = previousGreen = previousBlue = 0;
|
|
colorSlot = color_index; // this[0x17]
|
|
paletteToggle = 0; // this[0x18]
|
|
graphicsPort = renderer->GetGraphicsPort(graphics_port_number); // FUN_00447f84 -> this[0x1A]
|
|
}
|
|
|
|
//
|
|
// @004c38dc -- release both palette refs, then Gauge::~Gauge. (The deleting
|
|
// destructor thunks @004c66ff / @004c6725 / @004c674b / @004c6771 forward here
|
|
// for the four derived ColorMappers.)
|
|
//
|
|
ColorMapper::~ColorMapper()
|
|
{
|
|
WarehouseBinOf<Palette8> &palettes = renderer->warehousePointer->palette8Bin;
|
|
for (int i = 0; i < 2; ++i)
|
|
{
|
|
palettes.Release(paletteName[i]); // FUN_00443090 (release the AddRef'd palette)
|
|
paletteName[i] = NULL; // names not separately owned (no intern copy)
|
|
}
|
|
// base ~Gauge -- FUN_00444360
|
|
}
|
|
|
|
//
|
|
// @004c395c -- ColorMapper::BecameActive. On (re)activation, force the next
|
|
// Execute (@004c3980) to re-push the hardware palette: invalidate the cached
|
|
// colour index + the last-written RGB so the "unchanged" fast-out can't skip the
|
|
// write (the surface may have been cleared/redrawn while the gauge was inactive).
|
|
//
|
|
void
|
|
ColorMapper::BecameActive()
|
|
{
|
|
currentColorIndex = -1; // this[0x15] @0x54
|
|
previousColorIndex = -1; // this[0x16] @0x58
|
|
previousRed = previousGreen = previousBlue = 0xFF; // this[0x19] @0x64-0x66
|
|
}
|
|
|
|
//
|
|
// @004c3980 -- the palette push. Clamp the driving value to a [0,255] colour
|
|
// index, choose the active palette (flashing alternates the two when
|
|
// twoColorMode), read the index's R/G/B triple and, if it differs from last
|
|
// frame, write it straight into the hardware palette slot. (Called by every
|
|
// derived ColorMapper's Execute -- e.g. ColorMapperCritical::Execute @004c3c1c.)
|
|
//
|
|
void
|
|
ColorMapper::Execute()
|
|
{
|
|
if (currentColorIndex > 254) currentColorIndex = 255;
|
|
if (currentColorIndex < 1) currentColorIndex = 0;
|
|
|
|
if (!twoColorMode)
|
|
{
|
|
if (currentColorIndex == previousColorIndex)
|
|
{
|
|
return; // nothing changed
|
|
}
|
|
previousColorIndex = currentColorIndex;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Flash: alternate palette 0 / palette 1 every frame.
|
|
//
|
|
if (++paletteToggle > 1)
|
|
{
|
|
paletteToggle = 0;
|
|
}
|
|
}
|
|
|
|
WarehouseBinOf<Palette8> &palettes = renderer->warehousePointer->palette8Bin;
|
|
Palette8 *palette = palettes.GetIfAlreadyExists(paletteName[paletteToggle]); // FUN_00442f6a (peek, no AddRef)
|
|
if (palette != NULL)
|
|
{
|
|
//
|
|
// Read the index's R/G/B triple and, if it differs from last frame,
|
|
// push it straight into the hardware palette slot.
|
|
//
|
|
PaletteTriplet &entry = palette->Color[currentColorIndex];
|
|
if (previousRed != entry.Red ||
|
|
previousGreen != entry.Green ||
|
|
previousBlue != entry.Blue)
|
|
{
|
|
previousRed = entry.Red;
|
|
previousGreen = entry.Green;
|
|
previousBlue = entry.Blue;
|
|
|
|
graphicsPort->SetColor(&entry, colorSlot); // GraphicsPort::SetColor(PaletteTriplet*,int)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
// ColorMapperArmor @004c3aa4 Make / @004c3b98 ctor
|
|
//###########################################################################
|
|
//
|
|
// @004c3aa4 -- Make: build the damage-zone descriptor (DAT_00514414), look up
|
|
// the zone index on the host subsystem (FUN_0042076c; warns "damage zone ...
|
|
// not found" on failure), allocate (0x70) and construct a ColorMapperArmor
|
|
// wired with an ArmorZoneConnection.
|
|
//
|
|
// @004c3b98 -- ctor: ColorMapper base (vtable PTR_FUN_00518dcc), this[0x1B]=0,
|
|
// then AddConnection(new ArmorZoneConnection(¤tColorIndex, subsystem,
|
|
// zone_index)) (connection ctor FUN_004c33a4, 0x18 bytes).
|
|
//
|
|
// ArmorZoneConnection::Transfer @004c3430:
|
|
// if (source == 0) *destination = 100;
|
|
// else *destination = Round(<zone damage %>);
|
|
//
|
|
|
|
//###########################################################################
|
|
// ColorMapperMultiArmor @004c3c48 Make / @004c3d60 ctor
|
|
//###########################################################################
|
|
//
|
|
// @004c3c48 -- Make: probe an 8-entry table of damage-zone descriptors
|
|
// (DAT_00514704 stride 0x44); requires at least one to resolve (else warns
|
|
// "No Damage zones found"). Allocates (0x6c) and constructs.
|
|
//
|
|
// @004c3d60 -- ctor: ColorMapper base (vtable PTR_FUN_00518d88) +
|
|
// AddConnection(new MultiArmorConnection(¤tColorIndex, subsystem,
|
|
// zoneIndices[8])) (connection ctor FUN_004c346c, 0x3c bytes).
|
|
//
|
|
// MultiArmorConnection::Transfer @004c34f4:
|
|
// scan the 8 zones; keep the worst (max) damage ratio (zone+0x158);
|
|
// no zone present -> *destination = 100, else Round(worst).
|
|
//
|
|
|
|
//###########################################################################
|
|
// ColorMapperCritical @004c3ddc Make / @004c3e40 ctor
|
|
//###########################################################################
|
|
//
|
|
// @004c3ddc -- Make: allocate (0x6c) and construct with the four resource
|
|
// strings (DAT_00514b50..) and name "ColorMapperCritical".
|
|
//
|
|
// @004c3e40 -- ctor: ColorMapper base (vtable PTR_FUN_00518d44). Resolves the
|
|
// named subsystem via Entity::FindSubObject (FUN_0041f98c); warns
|
|
// "ColorMapperCritical warning: subsystem ... does not exist" if absent.
|
|
// AddConnection(new CriticalConnection(¤tColorIndex, subsystem))
|
|
// (connection ctor FUN_004c3598, 0x18 bytes).
|
|
//
|
|
// CriticalConnection::Transfer @004c3610:
|
|
// no subsystem -> *destination = 0;
|
|
// subsystem +0x40 == 1 -> *destination = 100; (fully operational)
|
|
// else -> *destination = Round(<value>);
|
|
//
|
|
|
|
//###########################################################################
|
|
// ColorMapperHeat @004c3f08 Make / @004c3f6c ctor *** ANCHOR ***
|
|
//###########################################################################
|
|
|
|
//
|
|
// @004c3f08 -- Make. Allocate a ColorMapperHeat (0x6c bytes) and construct it
|
|
// from the four resource strings (DAT_00514f98 rate, DAT_00514fdc mode-mask,
|
|
// DAT_00515020 port, DAT_00515064/0a8/0ec palette/slot/subsystem-name) and the
|
|
// class name "ColorMapperHeat" (0x5184c4).
|
|
//
|
|
|
|
//
|
|
// The gauge's configured parameters come LIVE from the config interpreter (the
|
|
// CFG "cmHeat" primitive) via methodDescription.parameterList -- the interpreter
|
|
// restores each instance's parsed params into this scratch list before calling
|
|
// Make (the engine pattern, cf. NumericDisplayScalar::Make L4GAUGE.cpp:569).
|
|
// The earlier DAT_* placeholders (unrecovered .data) are gone.
|
|
// CFG shape (verified L4GAUGE.CFG:182):
|
|
// cmHeat( rate, modeMask, colourSlot, paletteA, paletteB, subsystemName )
|
|
// e.g. cmHeat(H, ModeSecondaryHeat, 32, heatpal.pcc, heatpal2.pcc, GeneratorA);
|
|
//
|
|
MethodDescription
|
|
ColorMapperHeat::methodDescription =
|
|
{
|
|
"cmHeat",
|
|
ColorMapperHeat::Make,
|
|
{
|
|
{ ParameterDescription::typeRate, NULL }, // refresh-rate ID
|
|
{ ParameterDescription::typeModeMask, NULL }, // display mode mask
|
|
{ ParameterDescription::typeColor, NULL }, // hardware colour slot
|
|
{ ParameterDescription::typeString, NULL }, // palette name A
|
|
{ ParameterDescription::typeString, NULL }, // palette name B
|
|
{ ParameterDescription::typeString, NULL }, // heat subsystem name
|
|
PARAMETER_DESCRIPTION_END
|
|
}
|
|
};
|
|
|
|
Logical
|
|
ColorMapperHeat::Make(
|
|
int display_port_index,
|
|
Vector2DOf<int> /*position*/,
|
|
Entity *entity,
|
|
GaugeRenderer *gauge_renderer
|
|
)
|
|
{
|
|
ParameterDescription *p = methodDescription.parameterList;
|
|
|
|
ColorMapperHeat *gauge =
|
|
(ColorMapperHeat *)operator new(0x6c); // FUN_00402298(0x6c)
|
|
if (gauge != NULL)
|
|
{
|
|
new (gauge) ColorMapperHeat( // FUN_004c3f6c
|
|
p[0].data.rate, // rate ID
|
|
p[1].data.modeMask, // mode mask
|
|
(L4GaugeRenderer *)gauge_renderer,
|
|
display_port_index, // graphics port number (the runtime port)
|
|
p[2].data.color, // colour slot
|
|
entity,
|
|
p[3].data.string, // palette name A
|
|
p[4].data.string, // palette name B
|
|
p[5].data.string, // heat subsystem name (e.g. "GeneratorA")
|
|
"ColorMapperHeat");
|
|
}
|
|
return True;
|
|
}
|
|
|
|
//
|
|
// @004c3f6c -- ColorMapperHeat constructor. THE ANCHOR (assert path
|
|
// "d:\tesla\bt\bt_l4\BTL4GAUG.CPP", line 0x68a).
|
|
//
|
|
// Builds a ColorMapper (vtable PTR_FUN_00518d00), locates the named heat
|
|
// subsystem on the host entity, *verifies it is a heat-bearing subsystem*,
|
|
// and wires a HeatConnection so that the subsystem's live temperature drives
|
|
// the palette tint.
|
|
//
|
|
ColorMapperHeat::ColorMapperHeat(
|
|
GaugeRate rate,
|
|
ModeMask mode_mask,
|
|
L4GaugeRenderer *renderer,
|
|
int graphics_port_number,
|
|
int color_index,
|
|
Entity *entity,
|
|
const char *palette_a,
|
|
const char *palette_b,
|
|
const char *heat_subsystem_name,
|
|
const char *identification_string
|
|
):
|
|
ColorMapper( // FUN_004c37dc
|
|
rate, mode_mask, renderer, graphics_port_number,
|
|
color_index, palette_a, palette_b, identification_string)
|
|
{
|
|
// vtable already set to PTR_FUN_00518d00 by this point.
|
|
|
|
Subsystem *subsystem = entity->FindSubsystem(heat_subsystem_name); // FUN_0041f98c
|
|
if (subsystem == NULL)
|
|
{
|
|
DebugStream << "ColorMapperHeat warning: subsystem "
|
|
<< heat_subsystem_name << " does not exist\n"; // FUN_004dbb24 x3
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// The subsystem MUST be heat-bearing (one of the two heat class
|
|
// derivation tables) or the data feed is meaningless.
|
|
//
|
|
if (!subsystem->IsDerivedFrom(*HeatableSubsystem::GetClassDerivations()) && // FUN_0041a1a4 (0x50e3ec)
|
|
!subsystem->IsDerivedFrom(*HeatSink::GetClassDerivations())) // (0x50e604)
|
|
{
|
|
Verify(False); // FUN_0040385c -- "Bad subsystem type" BTL4GAUG.CPP:0x68a
|
|
}
|
|
}
|
|
|
|
//
|
|
// HeatConnection: copies the subsystem's currentTemperature (@0x114) into
|
|
// ColorMapper::currentColorIndex every frame (clamped to 0..100; 100 when
|
|
// the subsystem is absent or fully operational).
|
|
//
|
|
HeatConnection *connection =
|
|
(HeatConnection *)operator new(0x1c); // FUN_00402298(0x1c)
|
|
if (connection != NULL)
|
|
{
|
|
new (connection) HeatConnection( // FUN_004c3664
|
|
¤tColorIndex, subsystem);
|
|
}
|
|
AddConnection(connection); // (*this+0x34)
|
|
}
|
|
|
|
//
|
|
// ColorMapperHeat destructor. No extra work: the HeatConnection added above is
|
|
// released by the base Gauge teardown (RemoveAllConnections), and the two
|
|
// palettes by ~ColorMapper -- so the base-dtor chain (~ColorMapper -> ~Gauge)
|
|
// runs implicitly at the closing brace. (The binary's deleting-dtor thunks
|
|
// @004c66ff/6725/674b/6771 forward to that same base chain.)
|
|
//
|
|
ColorMapperHeat::~ColorMapperHeat()
|
|
{
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
//###########################################################################
|
|
// Two-part fill bars
|
|
//###########################################################################
|
|
//###########################################################################
|
|
|
|
//
|
|
// @004c2ff8 -- DrawTiledBitmap helper (file-private, shared by both bars).
|
|
// Tiles a source bitmap (param_7) across a destination rectangle, clipping the
|
|
// last partial row/column. For each tile it MoveTo()s (vtbl+0x24) and issues
|
|
// a clipped blit (vtbl+0x58). bitmap tile size is read from src+0x0C (width)
|
|
// and src+0x10 (height).
|
|
//
|
|
static void
|
|
DrawTiledBitmap(
|
|
GraphicsView *view,
|
|
int originX,
|
|
int originY,
|
|
int width,
|
|
int height,
|
|
int color,
|
|
BitMap *tile
|
|
)
|
|
{
|
|
int tileW = tile->Data.Size.x; // +0x0C
|
|
int tileH = tile->Data.Size.y; // +0x10
|
|
(void)color; // (BlitClipped colour arg has no GraphicsView::DrawBitMap analog)
|
|
|
|
for (int y = 0; y < height; y += tileH)
|
|
{
|
|
int rowH = height - y; if (rowH > tileH) rowH = tileH;
|
|
if (rowH <= 0) continue;
|
|
|
|
for (int x = 0; x < width; x += tileW)
|
|
{
|
|
int colW = width - x; if (colW > tileW) colW = tileW;
|
|
if (colW <= 0) continue;
|
|
|
|
view->MoveToAbsolute(x, y); // vtbl+0x24
|
|
view->DrawBitMap(0, tile, // vtbl+0x58 (clipped blit)
|
|
originX, originY,
|
|
originX + colW - 1, originY + rowH - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// @004c407c -- HorizTwoPartBar::Make. Allocates (0xb8), constructs, and
|
|
// verifies the tile image exists (warns "HorizTwoPartBar: Missing image ...").
|
|
//
|
|
// @004c4170 -- ctor: GraphicGauge base (vtable PTR_FUN_00518cbc), interns +
|
|
// ref-counts the tile image, sets the graphics-port extent
|
|
// (graphicsView.SetExtent(left,bottom,right,top)), stores fill/background
|
|
// colours and the bar size, then wires three GaugeConnectionDirectOf<Scalar>
|
|
// feeds (value, low, high -> this[0x24..0x26]). FUN_00474855 x3.
|
|
//
|
|
// @004c4324 -- BecameActive: previousFill=0; previousFull=width.
|
|
//
|
|
// @004c4340 -- Execute: clamp value to [low,high], map to a pixel column,
|
|
// repaint only the changed sub-rectangles (tiled fill for the "filled" part,
|
|
// background fill for the remainder). Uses _DAT_004c452c (0.0) as the floor.
|
|
//
|
|
|
|
//
|
|
// @004c462c Make / @004c4724 ctor / @004c48e0 BecameActive / @004c48fc Execute
|
|
// -- VertTwoPartBar: identical structure to HorizTwoPartBar but grows
|
|
// bottom->top (vtable PTR_FUN_00518c78). Floor constant _DAT_004c4b00 (0.0).
|
|
//
|
|
|
|
//
|
|
// @004c4b84 ctor / @004c4cac BecameActive / @004c4cc0 Execute
|
|
// -- VertNormalSlider (vtable PTR_FUN_00518c34): one GaugeConnectionDirectOf
|
|
// <Scalar> drives a normalised [0,1] value (clamped via _DAT_004c4d3c=0.0 /
|
|
// _DAT_004c4d40=1.0, saturating to 0x3f800000); Execute @004c4cc0 maps it to a
|
|
// row and calls Draw @004c4d48 which MoveTo()s and FillTo()s the lit rectangle.
|
|
//
|
|
|
|
|
|
//###########################################################################
|
|
//###########################################################################
|
|
// OneOfSeveral family -- multi-frame bitmap selectors
|
|
//###########################################################################
|
|
//###########################################################################
|
|
|
|
//
|
|
// @004c4d88 -- OneOfSeveral base ctor (vtable PTR_FUN_00518bf0). Stores the
|
|
// strip-vs-image flag (this[0x24]), interns the image name, computes the
|
|
// per-frame width/height by dividing the source bitmap (or pixmap) size by the
|
|
// column/row counts, and sets the graphics-port origin. Two resource caches
|
|
// are used depending on fromImageStrip: BitMapCache (+4) vs PixMapCache (+0x20).
|
|
//
|
|
// @004c4e7c -- dtor: release the image from the appropriate cache, free the
|
|
// interned name, GraphicGauge::~GraphicGauge.
|
|
//
|
|
// @004c4f14 -- BecameActive: previousSelected = -1 (force first redraw).
|
|
//
|
|
// @004c4f28 -- Execute: if selected != previousSelected, compute the frame's
|
|
// (row,col) sub-rectangle within the strip and blit it (clipped, vtbl+0x5C/0x58)
|
|
// or stretch-blit the pixmap path.
|
|
//
|
|
|
|
//
|
|
// @004c5068 Make / @004c5148 ctor -- OneOfSeveralInt (vtable PTR_FUN_00518bac):
|
|
// OneOfSeveral(fromImageStrip=1) + one GaugeConnectionDirectOf<int>
|
|
// (FUN_004749de) feeding the frame index. dtor @004c51d8.
|
|
//
|
|
// @004c5204 Make / @004c52d8 ctor -- OneOfSeveralPixInt (vtable PTR_FUN_00518b68):
|
|
// OneOfSeveral(fromImageStrip=0) + GaugeConnectionDirectOf<int>. dtor @004c5364.
|
|
//
|
|
// @004c5390 Make / @004c5470 ctor -- OneOfSeveralStates (vtable PTR_FUN_00518b24):
|
|
// OneOfSeveral(fromImageStrip=1) + a StateConnection (FUN_004c3324) reading the
|
|
// subsystem state word @0x14. BecameActive @004c552c clamps the state >= 0
|
|
// before chaining to OneOfSeveral::Execute @004c4f28. dtor @004c5500.
|
|
//
|
|
|
|
|
|
//###########################################################################
|
|
//###########################################################################
|
|
// HeadingPointer @004c554c Make / @004c562c ctor
|
|
//###########################################################################
|
|
//###########################################################################
|
|
|
|
//
|
|
// Reconstructed from the binary (raw pseudo-C part_013.c:14560-14791 + a
|
|
// disassembly of Execute@004c5914 and ctor@004c562c to recover the x87 endpoint
|
|
// arithmetic Ghidra dropped). The compass is a radial NEEDLE (a thick line from
|
|
// innerRadius to outerRadius at the heading angle) plus a NumericDisplay showing
|
|
// the heading in whole degrees.
|
|
//
|
|
|
|
MethodDescription
|
|
HeadingPointer::methodDescription =
|
|
{
|
|
"headingPointer",
|
|
HeadingPointer::Make,
|
|
{
|
|
//
|
|
// CFG shape (L4GAUGE.CFG:4941):
|
|
// headingPointer( rate, mode, needleColor, numericFg, eraseColor,
|
|
// innerRadius, outerRadius, font )
|
|
// e.g. headingPointer(E, ModeAlwaysActive, 2,1,0, 20,39, helv15.pcc);
|
|
//
|
|
{ ParameterDescription::typeRate, NULL }, // rate ID
|
|
{ ParameterDescription::typeModeMask, NULL }, // mode mask
|
|
{ ParameterDescription::typeColor, NULL }, // needle colour -> @0x90
|
|
{ ParameterDescription::typeColor, NULL }, // numeric fg -> NumericDisplay
|
|
{ ParameterDescription::typeColor, NULL }, // erase/bg -> @0x94 + ND bg
|
|
{ ParameterDescription::typeInteger, NULL }, // inner radius -> @0x98
|
|
{ ParameterDescription::typeInteger, NULL }, // outer radius -> @0x9C
|
|
{ ParameterDescription::typeString, NULL }, // font name
|
|
PARAMETER_DESCRIPTION_END
|
|
}
|
|
};
|
|
|
|
//
|
|
// @004c554c -- Make. Allocate + construct the gauge, then probe that the glyph
|
|
// font exists (== NumericDisplayScalar::Make, L4GAUGE.cpp:626-640); the readout
|
|
// cannot draw without it.
|
|
//
|
|
Logical
|
|
HeadingPointer::Make(
|
|
int display_port_index,
|
|
Vector2DOf<int> position,
|
|
Entity * /*entity -- unused*/,
|
|
GaugeRenderer *gauge_renderer
|
|
)
|
|
{
|
|
ParameterDescription *p = methodDescription.parameterList;
|
|
|
|
HeadingPointer *gauge = (HeadingPointer *)operator new(0xc0); // FUN_00402298(0xc0)
|
|
if (gauge != NULL)
|
|
{
|
|
new (gauge) HeadingPointer( // FUN_004c562c
|
|
p[0].data.rate, p[1].data.modeMask,
|
|
(L4GaugeRenderer *)gauge_renderer,
|
|
0, // owner_ID
|
|
display_port_index, // graphics port number (the runtime port)
|
|
position.x, position.y,
|
|
p[2].data.color, // needle colour
|
|
p[3].data.color, // numeric fg colour
|
|
p[4].data.color, // erase/bg colour
|
|
p[5].data.integer, // inner radius
|
|
p[6].data.integer, // outer radius (tip)
|
|
p[7].data.string, // font name
|
|
"HeadingPointer");
|
|
}
|
|
|
|
L4Warehouse *warehouse = (L4Warehouse *)gauge_renderer->warehousePointer;
|
|
if (warehouse->bitMapBin.Get(p[7].data.string) == NULL) // FUN_00442aec
|
|
{
|
|
DebugStream << "HeadingPointer: missing font '" << p[7].data.string << "'\n";
|
|
return False;
|
|
}
|
|
warehouse->bitMapBin.Release(p[7].data.string); // FUN_00442c12
|
|
return True;
|
|
}
|
|
|
|
//
|
|
// @004c562c -- ctor (vtable PTR_FUN_00518ae0). GraphicGauge base; the embedded
|
|
// GraphicsViewRecord (previousDrawing) default-constructs as a member (== the
|
|
// binary's FUN_0044a5b4). Set the port origin, store the colours/radii, hold a
|
|
// ref to the glyph font, and build an owned NumericDisplay centred on the compass
|
|
// mid-point (half the 3-digit glyph extent).
|
|
//
|
|
HeadingPointer::HeadingPointer(
|
|
GaugeRate rate,
|
|
ModeMask mode_mask,
|
|
L4GaugeRenderer *renderer_in,
|
|
unsigned int owner_ID,
|
|
int graphics_port_number,
|
|
int x,
|
|
int y,
|
|
int needle_color,
|
|
int numeric_color,
|
|
int erase_color,
|
|
int inner_radius,
|
|
int outer_radius,
|
|
const char *font_name,
|
|
const char *identification_string
|
|
):
|
|
GraphicGauge(rate, mode_mask, renderer_in, owner_ID, // FUN_00444818
|
|
graphics_port_number, identification_string)
|
|
{
|
|
localView.SetOrigin(x, y); // this+0x48 vtbl+0x10
|
|
|
|
needleColor = needle_color; // @0x90
|
|
eraseColor = erase_color; // @0x94
|
|
innerRadius = inner_radius; // @0x98
|
|
outerRadius = outer_radius; // @0x9C
|
|
|
|
// Own a copy of the font name (in Make it is the transient interpreter scratch
|
|
// buffer, so it MUST be copied -- the binary uses nameCopy/FUN_004700ac).
|
|
imageName = new char[strlen(font_name) + 1]; // @0xA0
|
|
strcpy(imageName, font_name);
|
|
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer_in->warehousePointer;
|
|
BitMap *font = warehouse->bitMapBin.Get(imageName); // FUN_00442aec -- held for the gauge's life
|
|
int fontW = (font != NULL) ? font->Data.Size.x : 0; // BitMap+0x0C
|
|
int fontH = (font != NULL) ? font->Data.Size.y : 0; // BitMap+0x10
|
|
|
|
int halfW = ((fontW / 14) * 3) / 2; // 14 = totalDigitsPerFont; centre the 3-digit readout
|
|
int halfH = fontH / 2;
|
|
|
|
numericDisplay = new NumericDisplay( // @0xBC FUN_004700bc
|
|
warehouse,
|
|
-halfW, -halfH, // centre on the compass mid-point
|
|
imageName,
|
|
3, // number_of_digits
|
|
NumericDisplay::unsignedFormat, // format 0
|
|
erase_color, // background_color
|
|
numeric_color); // foreground_color
|
|
}
|
|
|
|
//
|
|
// @004c573c -- dtor. Release the font ref (keyed on imageName, so before the
|
|
// free), free imageName, delete the NumericDisplay. The base-dtor chain
|
|
// (~GraphicsViewRecord on previousDrawing, then ~GraphicGauge) runs implicitly.
|
|
//
|
|
HeadingPointer::~HeadingPointer()
|
|
{
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
warehouse->bitMapBin.Release(imageName); // FUN_00442c12 -- drop the ctor's Get ref
|
|
delete[] imageName; // FUN_004022e8
|
|
imageName = NULL;
|
|
|
|
delete numericDisplay; // FUN_0047018c
|
|
numericDisplay = NULL;
|
|
}
|
|
|
|
Logical
|
|
HeadingPointer::TestInstance() const
|
|
{
|
|
return GraphicGauge::TestInstance();
|
|
}
|
|
|
|
//
|
|
// @004c57d0 -- ShowInstance (debug dump).
|
|
//
|
|
void
|
|
HeadingPointer::ShowInstance(char *indent)
|
|
{
|
|
DebugStream << indent << "HeadingPointer:\n";
|
|
char deeper[80];
|
|
strcpy(deeper, indent);
|
|
strcat(deeper, "...");
|
|
DebugStream << deeper << "bgColor=" << eraseColor << "\n"; // @0x94 (printed first, per decomp)
|
|
DebugStream << deeper << "fgColor=" << needleColor << "\n"; // @0x90
|
|
GraphicGauge::ShowInstance(deeper);
|
|
}
|
|
|
|
//
|
|
// @004c58e8 -- BecameActive: force a full redraw + reset the readout.
|
|
//
|
|
void
|
|
HeadingPointer::BecameActive()
|
|
{
|
|
previousX = -999; // 0xfffffc19 -- guaranteed != any real endpoint
|
|
previousY = -999;
|
|
numericDisplay->ForceUpdate(); // FUN_004703f4
|
|
}
|
|
|
|
//
|
|
// @004c5914 -- Execute. Read the owner mech's heading from its orientation
|
|
// quaternion, draw the needle as a radial line innerRadius..outerRadius at that
|
|
// angle (recording the strokes so the next frame can erase them), and update the
|
|
// numeric readout. Endpoint rounding is half-up: (int)(v + 0.5f), matching the
|
|
// binary's `fadd 0.5 ; _ftol`.
|
|
//
|
|
void
|
|
HeadingPointer::Execute()
|
|
{
|
|
Entity *owner = renderer->GetLinkedEntity(); // FUN_00417ab4(renderer+0x40)
|
|
if (owner == NULL)
|
|
{
|
|
// BRING-UP: if the gauge renderer's entitySocket isn't wired to the viewpoint
|
|
// mech, fall back to the player so the compass tracks the mech we're driving.
|
|
// (In practice GetLinkedEntity resolves; this is belt-and-braces.)
|
|
if (application != NULL)
|
|
owner = (Entity *)application->GetViewpointEntity();
|
|
if (owner == NULL)
|
|
return;
|
|
}
|
|
|
|
// The binary read an EulerAngles component (FUN_0040954c) -- but the WinTesla MUNGA
|
|
// EulerAngles(Quaternion) decomposition is AMBIGUOUS for a yawing mech: as the yaw
|
|
// sweeps past +/-pi the quaternion double-cover flips it to a pitch=roll=pi branch,
|
|
// so euler.yaw jumps discontinuously (the needle spins erratically). YawPitchRoll
|
|
// applies yaw FIRST, so its .yaw is the clean, continuous heading with pitch=roll~0.
|
|
YawPitchRoll ypr;
|
|
ypr = owner->localOrigin.angularPosition; // entity+0x10c (the orientation quaternion)
|
|
Scalar heading = -(Scalar)ypr.yaw; // negate for the needle (as the binary did)
|
|
|
|
SinCosPair sc;
|
|
sc = Radian(heading); // FUN_00408328 -- sin/cos of the heading
|
|
|
|
// Needle endpoints: a radial line innerRadius..outerRadius along the heading.
|
|
int ax = (int)((Scalar)innerRadius * sc.sine + 0.5f);
|
|
int ay = (int)((Scalar)innerRadius * sc.cosine + 0.5f);
|
|
int bx = (int)((Scalar)outerRadius * sc.sine + 0.5f);
|
|
int by = (int)((Scalar)outerRadius * sc.cosine + 0.5f);
|
|
|
|
if (bx != previousX || by != previousY) // redraw only when the tip moves
|
|
{
|
|
previousX = bx;
|
|
previousY = by;
|
|
previousDrawing.Draw(&localView, eraseColor); // FUN_0044a650 -- erase the last needle
|
|
previousDrawing.Clear(); // FUN_0044a630
|
|
localView.AttachRecorder(&previousDrawing); // vtbl+0x64 -- record the new strokes
|
|
localView.SetColor(needleColor); // vtbl+0x18
|
|
localView.MoveToAbsolute(ax, ay); // vtbl+0x24
|
|
localView.DrawThickLineToAbsolute(bx, by); // vtbl+0x38
|
|
localView.DetachRecorder(); // vtbl+0x68
|
|
}
|
|
|
|
// Numeric heading readout: whole degrees, normalized, displayed as (360 - deg).
|
|
int deg = (int)((Scalar)ypr.yaw * 57.29578f + 0.5f); // heading -> degrees (0x4c5ac8)
|
|
if ((Scalar)deg < 0.0f) // _DAT_004c5acc == 0.0f
|
|
deg = (int)((Scalar)deg + 360.0f);
|
|
int shown = (int)(360.0f - (Scalar)deg);
|
|
numericDisplay->Draw(&localView, (Scalar)shown); // FUN_00470430
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
//###########################################################################
|
|
// BitMap wipe gauges and arc gauges
|
|
//###########################################################################
|
|
//###########################################################################
|
|
|
|
//
|
|
// @004c5b7c ctor / @004c5c80 dtor / @004c5cf4 BecameActive / @004c5d08 Execute
|
|
// -- BitMapInverseWipe (vtable PTR_FUN_00518a9c; name unconfirmed).
|
|
// A 3-frame strip (frameWidth = imageWidth/3) revealed in steps. Execute
|
|
// @004c5d08 clamps the level to [0, frames], promotes it to 1 when the value
|
|
// is above _DAT_0050e3d8 (0.0025) but still rounds to 0, then blits each lit
|
|
// segment, stepping the colour through colorA/colorB by 2s. Used as a base by
|
|
// btl4gau2's LeakGauge.
|
|
//
|
|
// @004c5e84 ctor / @004c5f30 dtor / @004c5fa4 BecameActive / @004c5fb8 Execute
|
|
// -- BitMapInverseWipe base (vtable PTR_FUN_00518a58): a two-segment reveal
|
|
// of one strip image; frame size read from the bitmap (src+0x0C/0x10).
|
|
//
|
|
// @004c61c8 ctor (vtable PTR_FUN_00518a14) -- the Scalar-driven variant:
|
|
// chains @004c5e84 then AddConnection(new GaugeConnectionDirectOf<Scalar>
|
|
// (&level)) (FUN_00474855). Deleting-dtor thunk @004c66d9 -> @004c5f30.
|
|
//
|
|
// @004c6244 ctor (vtable PTR_FUN_005189d0) -- SegmentArc270: derives from the
|
|
// MUNGA L4 arc primitive (FUN_004745e0). Precomputes the per-segment span:
|
|
// segmentSpan = (int)( (float)(|n|/(|n|-1)) * 0.75f ) // _DAT_004c62d4
|
|
// Deleting-dtor thunk @004c66b3 -> FUN_00474094 (arc base dtor).
|
|
//
|
|
// @004c62fc Make / @004c6394 ctor (vtable PTR_FUN_0051898c) -- SegmentArcRatio:
|
|
// derives from the second MUNGA L4 arc primitive (FUN_00473f44) and adds TWO
|
|
// GaugeConnectionDirectOf<Scalar> feeds (numerator @0x30, denominator @0x31,
|
|
// FUN_00474855 x2); precomputes segmentSpan with _DAT_004c6484 (0.75f).
|
|
// Make @004c62fc allocates 0xcc and supplies the arc geometry resources
|
|
// (DAT_00517eb0..00518158) and the name "SegmentArcRatio".
|
|
// Deleting-dtor thunk @004c668d -> FUN_00474094.
|
|
//
|
|
|
|
// === btl4gau2.cpp begins at @004c6798 (SeekVoltage gauge) -- not part of this TU.
|