The per-weapon TEMP/STATUS bar (HorizTwoPartBar) rendered as a solid block starting at the warn-threshold pixel -- it "started in the middle" and was never striped. The port's Execute had been rewritten with DrawFilledRectangle and never used the interned tile image, unlike the (correct) VertTwoPartBar which tiles via DrawTiledBitmap. Restored the binary's three-zone render (disasm @004c4340), along X: [0, warnPix) DrawTiledBitmap(tileImage) -- the striped/dotted pattern [warnPix, valPix) backgroundColor solid (only when value > low) [valPix, width) fillColor solid So the bar now fills the striped tile from the beginning and matches the reference (hatched fill block on the left + dotted tick scale). Render- verified vs the DOSBox reference capture. Both builds clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2460 lines
90 KiB
C++
2460 lines
90 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 class-derivation tags (HeatableSubsystem /
|
|
// HEATWATCHER -- task #57 relabel; was "HeatSink")
|
|
//
|
|
// 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
|
|
}
|
|
|
|
|
|
//
|
|
// ArmorZoneConnection (@004c33a4 ctor / @004c3430 Transfer) -- drives a
|
|
// ColorMapper's colour index from ONE damage zone's live damage ratio. The ctor
|
|
// resolves the zone from the owner's inherited Entity::damageZones[zone_index]
|
|
// (raw *(entity+0x120)[idx]); Transfer feeds it each frame. Used by ColorMapperArmor.
|
|
//
|
|
class ArmorZoneConnection : public GaugeConnection
|
|
{
|
|
public:
|
|
ArmorZoneConnection(int *destination, Entity *entity, int zone_index)
|
|
: GaugeConnection(0), // FUN_00444124(this,0)
|
|
zone((zone_index < 0) ? NULL : entity->damageZones[zone_index]),
|
|
currentColorIndex(destination)
|
|
{}
|
|
|
|
protected:
|
|
//
|
|
// Per-frame feed (@004c3430): no zone -> 100 (fail-safe full tint); else the
|
|
// zone's damage RATIO (damageLevel @0x158, 0..1) scaled to a 0..100 percentage
|
|
// and rounded -- ColorMapper::Execute clamps it + pushes the palette slot,
|
|
// recolouring that zone on the cockpit ARMOR DAMAGE schematic.
|
|
//
|
|
void Update(); // override
|
|
|
|
DamageZone *zone; // source@0x10
|
|
int *currentColorIndex; // destination@0x14
|
|
};
|
|
|
|
void
|
|
ArmorZoneConnection::Update()
|
|
{
|
|
if (zone == NULL)
|
|
{
|
|
*currentColorIndex = 100;
|
|
return;
|
|
}
|
|
*currentColorIndex = HeatRound(zone->damageLevel * 100.0f); // zone+0x158 * 100
|
|
}
|
|
|
|
|
|
//
|
|
// MultiArmorConnection (@004c346c ctor / @004c34f4 Transfer) -- drives a
|
|
// ColorMapper's colour index from the WORST of up to 8 damage zones. The ctor
|
|
// copies the 8 zone indices + the owner; Transfer scans them each frame and
|
|
// feeds the maximum damage ratio. Used by ColorMapperMultiArmor.
|
|
//
|
|
class MultiArmorConnection : public GaugeConnection
|
|
{
|
|
public:
|
|
MultiArmorConnection(int *destination, Entity *entity, const int *zone_indices)
|
|
: GaugeConnection(0), // FUN_00444124(this,0)
|
|
owner(entity),
|
|
currentColorIndex(destination)
|
|
{
|
|
for (int i = 0; i < 8; i++)
|
|
zoneIndex[i] = zone_indices[i];
|
|
}
|
|
|
|
protected:
|
|
//
|
|
// Per-frame feed (@004c34f4): scan the (up to 8) zones -- skipping index < 0
|
|
// ("unused") and absent zones -- keep the WORST (max) damageLevel (@0x158),
|
|
// scale to 0..100 and round; no zone present -> 100.
|
|
//
|
|
void Update(); // override
|
|
|
|
Entity *owner; // @0x10
|
|
int zoneIndex[8]; // @0x18..0x34
|
|
int *currentColorIndex; // @0x38
|
|
};
|
|
|
|
void
|
|
MultiArmorConnection::Update()
|
|
{
|
|
Scalar worst = 0.0f;
|
|
int count = 0;
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
if (zoneIndex[i] >= 0)
|
|
{
|
|
DamageZone *zone = owner->damageZones[zoneIndex[i]];
|
|
if (zone != NULL)
|
|
{
|
|
++count;
|
|
if (worst < zone->damageLevel)
|
|
worst = zone->damageLevel;
|
|
}
|
|
}
|
|
}
|
|
*currentColorIndex = (count == 0) ? 100 : HeatRound(worst * 100.0f);
|
|
}
|
|
|
|
|
|
//
|
|
// CriticalConnection (@004c3598 ctor / @004c3610 Transfer) -- drives a
|
|
// ColorMapper's colour index from ONE subsystem's operational state. Used by
|
|
// ColorMapperCritical. Transfer: no subsystem -> 0 (blank); subsystem DESTROYED
|
|
// (simulationState == 1) -> 100 (full critical tint); else the subsystem's own
|
|
// damage-zone damageLevel (0..1) as a 0..100 percentage (@0xE0 -> @0x158 * 100).
|
|
//
|
|
class CriticalConnection : public GaugeConnection
|
|
{
|
|
public:
|
|
CriticalConnection(int *destination, Subsystem *source)
|
|
: GaugeConnection(0), // FUN_00444124(this,0)
|
|
subsystem(source),
|
|
currentColorIndex(destination)
|
|
{}
|
|
|
|
protected:
|
|
void Update(); // override
|
|
|
|
Subsystem *subsystem; // @0x10 source
|
|
int *currentColorIndex; // @0x14 destination
|
|
};
|
|
|
|
void
|
|
CriticalConnection::Update()
|
|
{
|
|
if (subsystem == NULL)
|
|
{
|
|
*currentColorIndex = 0;
|
|
return;
|
|
}
|
|
MechSubsystem *sub = (MechSubsystem *)subsystem;
|
|
if (sub->GetSimulationState() == 1) // DestroyedState -> full critical tint
|
|
{
|
|
*currentColorIndex = 100;
|
|
return;
|
|
}
|
|
// else: the subsystem's own damage zone (the proxy IS a real DamageZone). The
|
|
// binary reads @0xe0 unconditionally; guard NULL (not every recon subsystem has
|
|
// its zone wired) -> 0, matching an intact/undamaged reading.
|
|
DamageZone *dz = (DamageZone *)sub->GetDamageZoneProxy();
|
|
*currentColorIndex = (dz != NULL) ? HeatRound(dz->damageLevel * 100.0f) : 0;
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
//###########################################################################
|
|
// 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 HeatableSubsystem) vs. the
|
|
// alternate (0x50e604 = HEATWATCHER, task #57 relabel); destination@0x18.
|
|
// Transfer: no subsystem -> 100 ; subsystem operational (+0x40 == 1) -> 100 ;
|
|
// else Round( Resolve(subsys+0x114) ) -- NB for a Heatable that offset is
|
|
// currentTemperature, for a WATCHER it is watchedLink (the flag@0x14 likely
|
|
// picks the read; the watcher path is unverified [T4]).
|
|
// 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
|
|
//###########################################################################
|
|
//
|
|
// Tints ONE damage zone's colour on the cockpit ARMOR DAMAGE schematic by that
|
|
// zone's live damage. CFG shape (L4GAUGE.CFG:4789, inside the colorMapArmor
|
|
// macro): cmArmor( rate, mode, colourSlot, paletteA, paletteB, zoneName )
|
|
// e.g. cmArmor(H, ModeSecondaryDamage, 32, adpal.pcc, adpal2.pcc, dz_ltorso);
|
|
//
|
|
|
|
MethodDescription
|
|
ColorMapperArmor::methodDescription =
|
|
{
|
|
"cmArmor",
|
|
ColorMapperArmor::Make,
|
|
{
|
|
{ ParameterDescription::typeRate, NULL }, // rate ID
|
|
{ ParameterDescription::typeModeMask, NULL }, // mode mask
|
|
{ ParameterDescription::typeColor, NULL }, // hardware colour slot
|
|
{ ParameterDescription::typeString, NULL }, // palette name A
|
|
{ ParameterDescription::typeString, NULL }, // palette name B
|
|
{ ParameterDescription::typeString, NULL }, // damage-zone name (e.g. "dz_ltorso")
|
|
PARAMETER_DESCRIPTION_END
|
|
}
|
|
};
|
|
|
|
//
|
|
// @004c3aa4 -- Make. Resolve the named damage zone to an index on the entity,
|
|
// then construct. (The binary built a transient zone-name descriptor and called
|
|
// FUN_0042076c; Entity::GetDamageZoneIndex IS that lookup -- it scans
|
|
// damageZones[] matching each zone's name.)
|
|
//
|
|
Logical
|
|
ColorMapperArmor::Make(
|
|
int display_port_index,
|
|
Vector2DOf<int> /*position*/,
|
|
Entity *entity,
|
|
GaugeRenderer *gauge_renderer
|
|
)
|
|
{
|
|
ParameterDescription *p = methodDescription.parameterList;
|
|
|
|
int zone_index = entity->GetDamageZoneIndex(CString(p[5].data.string)); // FUN_0042076c
|
|
if (zone_index < 0)
|
|
{
|
|
DebugStream << "ColorMapperArmor warning: damage zone "
|
|
<< p[5].data.string << " not found\n";
|
|
}
|
|
|
|
ColorMapperArmor *gauge = (ColorMapperArmor *)operator new(0x70); // FUN_00402298(0x70)
|
|
if (gauge != NULL)
|
|
{
|
|
new (gauge) ColorMapperArmor( // FUN_004c3b98
|
|
p[0].data.rate, p[1].data.modeMask,
|
|
(L4GaugeRenderer *)gauge_renderer,
|
|
display_port_index, // graphics port number (the runtime port)
|
|
p[2].data.color, // colour slot
|
|
entity,
|
|
p[3].data.string, // palette A
|
|
p[4].data.string, // palette B
|
|
zone_index,
|
|
"ColorMapperArmor");
|
|
}
|
|
return True;
|
|
}
|
|
|
|
//
|
|
// @004c3b98 -- ctor: ColorMapper base + wire an ArmorZoneConnection feeding the
|
|
// resolved damage zone's live damage to the colour index.
|
|
//
|
|
ColorMapperArmor::ColorMapperArmor(
|
|
GaugeRate rate,
|
|
ModeMask mode_mask,
|
|
L4GaugeRenderer *renderer,
|
|
int graphics_port_number,
|
|
int color_index,
|
|
Entity *entity,
|
|
const char *palette_a,
|
|
const char *palette_b,
|
|
int damage_zone_index,
|
|
const char *identification_string
|
|
):
|
|
ColorMapper( // FUN_004c37dc (owner_ID 0 folded in, as for cmHeat)
|
|
rate, mode_mask, renderer, graphics_port_number,
|
|
color_index, palette_a, palette_b, identification_string)
|
|
{
|
|
unused = 0; // this[0x1B] @0x6C
|
|
|
|
ArmorZoneConnection *connection =
|
|
(ArmorZoneConnection *)operator new(0x18); // FUN_004c33a4 (0x18 bytes)
|
|
if (connection != NULL)
|
|
new (connection) ArmorZoneConnection(¤tColorIndex, entity, damage_zone_index);
|
|
AddConnection(connection); // (*this+0x34)
|
|
}
|
|
|
|
//
|
|
// ColorMapperArmor destructor. No extra work: the ArmorZoneConnection is
|
|
// released by the base Gauge teardown, the palettes by ~ColorMapper -- the
|
|
// base-dtor chain runs implicitly at the closing brace.
|
|
//
|
|
ColorMapperArmor::~ColorMapperArmor()
|
|
{
|
|
}
|
|
|
|
//###########################################################################
|
|
// ColorMapperMultiArmor @004c3c48 Make / @004c3d60 ctor
|
|
//###########################################################################
|
|
//
|
|
// Tints one schematic colour by the WORST of up to 8 damage zones (e.g. a whole
|
|
// torso section). CFG shape (L4GAUGE.CFG:96):
|
|
// colorMapperMultiArmor( rate, mode, colourSlot, paletteA, paletteB,
|
|
// zone1, zone2, ... zone8 ) ("unused" for empty slots)
|
|
//
|
|
|
|
MethodDescription
|
|
ColorMapperMultiArmor::methodDescription =
|
|
{
|
|
"colorMapperMultiArmor",
|
|
ColorMapperMultiArmor::Make,
|
|
{
|
|
{ ParameterDescription::typeRate, NULL }, // rate ID
|
|
{ ParameterDescription::typeModeMask, NULL }, // mode mask
|
|
{ ParameterDescription::typeColor, NULL }, // hardware colour slot
|
|
{ ParameterDescription::typeString, NULL }, // palette name A
|
|
{ ParameterDescription::typeString, NULL }, // palette name B
|
|
{ ParameterDescription::typeString, NULL }, // zone 1
|
|
{ ParameterDescription::typeString, NULL }, // zone 2
|
|
{ ParameterDescription::typeString, NULL }, // zone 3
|
|
{ ParameterDescription::typeString, NULL }, // zone 4
|
|
{ ParameterDescription::typeString, NULL }, // zone 5
|
|
{ ParameterDescription::typeString, NULL }, // zone 6
|
|
{ ParameterDescription::typeString, NULL }, // zone 7
|
|
{ ParameterDescription::typeString, NULL }, // zone 8
|
|
PARAMETER_DESCRIPTION_END
|
|
}
|
|
};
|
|
|
|
//
|
|
// @004c3c48 -- Make. Resolve up to 8 named zones to indices; build if any
|
|
// resolves. (The binary flag test is `index != 0`, so a valid zone OR an
|
|
// "unused"/-1 slot both count as "present" -- i.e. it always builds; the
|
|
// Transfer simply skips index < 0.)
|
|
//
|
|
Logical
|
|
ColorMapperMultiArmor::Make(
|
|
int display_port_index,
|
|
Vector2DOf<int> /*position*/,
|
|
Entity *entity,
|
|
GaugeRenderer *gauge_renderer
|
|
)
|
|
{
|
|
ParameterDescription *p = methodDescription.parameterList;
|
|
|
|
int zone_indices[8];
|
|
Logical any = False;
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
zone_indices[i] = entity->GetDamageZoneIndex(CString(p[5 + i].data.string)); // FUN_0042076c
|
|
if (zone_indices[i] != 0) // binary: iVar2 != 0 (quirk -- see note above)
|
|
any = True;
|
|
}
|
|
if (!any)
|
|
{
|
|
DebugStream << "colorMapperMultiArmor: No Damage zones found\n";
|
|
return False;
|
|
}
|
|
|
|
ColorMapperMultiArmor *gauge = (ColorMapperMultiArmor *)operator new(0x6c); // FUN_00402298(0x6c)
|
|
if (gauge != NULL)
|
|
{
|
|
new (gauge) ColorMapperMultiArmor( // FUN_004c3d60
|
|
p[0].data.rate, p[1].data.modeMask,
|
|
(L4GaugeRenderer *)gauge_renderer,
|
|
display_port_index, // graphics port number
|
|
p[2].data.color, // colour slot
|
|
entity,
|
|
p[3].data.string, // palette A
|
|
p[4].data.string, // palette B
|
|
zone_indices,
|
|
"ColorMapperMultiArmor");
|
|
}
|
|
return True;
|
|
}
|
|
|
|
//
|
|
// @004c3d60 -- ctor: ColorMapper base + a MultiArmorConnection over the 8 zones.
|
|
//
|
|
ColorMapperMultiArmor::ColorMapperMultiArmor(
|
|
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 int *zone_indices,
|
|
const char *identification_string
|
|
):
|
|
ColorMapper( // FUN_004c37dc
|
|
rate, mode_mask, renderer, graphics_port_number,
|
|
color_index, palette_a, palette_b, identification_string)
|
|
{
|
|
MultiArmorConnection *connection =
|
|
(MultiArmorConnection *)operator new(0x3c); // FUN_004c346c (0x3c bytes)
|
|
if (connection != NULL)
|
|
new (connection) MultiArmorConnection(¤tColorIndex, entity, zone_indices);
|
|
AddConnection(connection);
|
|
}
|
|
|
|
ColorMapperMultiArmor::~ColorMapperMultiArmor()
|
|
{
|
|
// base-dtor chain (~ColorMapper -> ~Gauge) releases the connection + palettes.
|
|
}
|
|
|
|
//###########################################################################
|
|
// ColorMapperCritical @004c3ddc Make / @004c3e40 ctor
|
|
//###########################################################################
|
|
//
|
|
// Tints one schematic colour by a subsystem's operational state (the "critical"
|
|
// secondary display mode). CFG shape (L4GAUGE.CFG:143):
|
|
// cmCrit( rate, mode, colourSlot, paletteA, paletteB, subsystemName )
|
|
// e.g. cmCrit(H, ModeSecondaryCritical, 32, adpal.pcc, adpal2.pcc, GeneratorA);
|
|
//
|
|
|
|
MethodDescription
|
|
ColorMapperCritical::methodDescription =
|
|
{
|
|
"cmCrit",
|
|
ColorMapperCritical::Make,
|
|
{
|
|
{ ParameterDescription::typeRate, NULL }, // rate ID
|
|
{ ParameterDescription::typeModeMask, NULL }, // mode mask
|
|
{ ParameterDescription::typeColor, NULL }, // hardware colour slot
|
|
{ ParameterDescription::typeString, NULL }, // palette name A
|
|
{ ParameterDescription::typeString, NULL }, // palette name B
|
|
{ ParameterDescription::typeString, NULL }, // subsystem name
|
|
PARAMETER_DESCRIPTION_END
|
|
}
|
|
};
|
|
|
|
//
|
|
// @004c3ddc -- Make.
|
|
//
|
|
Logical
|
|
ColorMapperCritical::Make(
|
|
int display_port_index,
|
|
Vector2DOf<int> /*position*/,
|
|
Entity *entity,
|
|
GaugeRenderer *gauge_renderer
|
|
)
|
|
{
|
|
ParameterDescription *p = methodDescription.parameterList;
|
|
|
|
ColorMapperCritical *gauge = (ColorMapperCritical *)operator new(0x6c); // FUN_00402298(0x6c)
|
|
if (gauge != NULL)
|
|
{
|
|
new (gauge) ColorMapperCritical( // FUN_004c3e40
|
|
p[0].data.rate, p[1].data.modeMask,
|
|
(L4GaugeRenderer *)gauge_renderer,
|
|
display_port_index, // graphics port number
|
|
p[2].data.color, // colour slot
|
|
entity,
|
|
p[3].data.string, // palette A
|
|
p[4].data.string, // palette B
|
|
p[5].data.string, // subsystem name
|
|
"ColorMapperCritical");
|
|
}
|
|
return True;
|
|
}
|
|
|
|
//
|
|
// @004c3e40 -- ctor: ColorMapper base + a CriticalConnection on the named subsystem.
|
|
//
|
|
ColorMapperCritical::ColorMapperCritical(
|
|
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 *subsystem_name,
|
|
const char *identification_string
|
|
):
|
|
ColorMapper( // FUN_004c37dc
|
|
rate, mode_mask, renderer, graphics_port_number,
|
|
color_index, palette_a, palette_b, identification_string)
|
|
{
|
|
Subsystem *subsystem = entity->FindSubsystem(subsystem_name); // FUN_0041f98c
|
|
if (subsystem == NULL)
|
|
{
|
|
DebugStream << "ColorMapperCritical warning: subsystem "
|
|
<< subsystem_name << " does not exist\n";
|
|
}
|
|
|
|
CriticalConnection *connection =
|
|
(CriticalConnection *)operator new(0x18); // FUN_004c3598 (0x18 bytes)
|
|
if (connection != NULL)
|
|
new (connection) CriticalConnection(¤tColorIndex, subsystem);
|
|
AddConnection(connection);
|
|
}
|
|
|
|
ColorMapperCritical::~ColorMapperCritical()
|
|
{
|
|
// base-dtor chain (~ColorMapper -> ~Gauge) releases the connection + palettes.
|
|
}
|
|
|
|
//###########################################################################
|
|
// 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.
|
|
//
|
|
extern int BTIsHeatWatcher(Subsystem *sub); // heatfamily_reslice.cpp bridge
|
|
if (!subsystem->IsDerivedFrom(*HeatableSubsystem::GetClassDerivations()) && // FUN_0041a1a4 (0x50e3ec)
|
|
!BTIsHeatWatcher(subsystem)) // (0x50e604 = HEATWATCHER -- task #57 relabel:
|
|
// the old "HeatSink" reading was redundant after
|
|
// the HeatableSubsystem test; the binary's
|
|
// alternate branch is the disjoint WATCHER family
|
|
// (ammo bins / torso carry a mirrored heatAlarm))
|
|
{
|
|
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.
|
|
//
|
|
|
|
//###########################################################################
|
|
// VertTwoPartBar @004c462c Make / @004c4724 ctor / @004c48e0 BecameActive
|
|
// @004c48fc Execute (vtable PTR_FUN_00518c78)
|
|
//
|
|
// A vertical two-part fill bar (config keyword "vertBar"). Three Scalar
|
|
// connections drive it: value (current), low (warn threshold), high (max). The
|
|
// bar grows bottom->top; pixels are value/high and low/high fractions of the bar
|
|
// height. Used for the cockpit COOLANT bars (config binds current=CoolantMass,
|
|
// warn=CoolantCapacity, max=CoolantCapacity -- so the warn line sits at full and
|
|
// the bar simply empties as CoolantMass drops during firing).
|
|
//###########################################################################
|
|
|
|
MethodDescription
|
|
VertTwoPartBar::methodDescription =
|
|
{
|
|
"vertBar",
|
|
VertTwoPartBar::Make,
|
|
{
|
|
//
|
|
// CFG shape (L4GAUGE.CFG:4521):
|
|
// vertBar( rate, mode, (w,h), tile.pcc, bg,fill,extra,
|
|
// currentAttr, warnAttr, maxAttr )
|
|
// e.g. vertBar(C,ModeAlwaysActive,(31,160),btwarn.pcc,255,255,0,
|
|
// HeatSink/CoolantMass, HeatSink/CoolantCapacity,
|
|
// HeatSink/CoolantCapacity);
|
|
//
|
|
{ ParameterDescription::typeRate, NULL }, // rate ID
|
|
{ ParameterDescription::typeModeMask, NULL }, // mode mask
|
|
{ ParameterDescription::typeVector, NULL }, // (width,height)
|
|
{ ParameterDescription::typeString, NULL }, // tile bitmap name
|
|
{ ParameterDescription::typeColor, NULL }, // background colour -> @0x94
|
|
{ ParameterDescription::typeColor, NULL }, // fill colour -> @0x98
|
|
{ ParameterDescription::typeColor, NULL }, // extra/empty colour-> @0x9C
|
|
{ ParameterDescription::typeAttribute, NULL }, // current value -> @0xB0
|
|
{ ParameterDescription::typeAttribute, NULL }, // warn threshold -> @0xB4
|
|
{ ParameterDescription::typeAttribute, NULL }, // max -> @0xB8
|
|
PARAMETER_DESCRIPTION_END
|
|
}
|
|
};
|
|
|
|
//
|
|
// @004c462c -- Make. Allocate + construct, then verify the tile bitmap exists
|
|
// (== the binary's "VertTwoPartBarNormalized: Missing image" warning path).
|
|
//
|
|
Logical
|
|
VertTwoPartBar::Make(
|
|
int display_port_index,
|
|
Vector2DOf<int> position,
|
|
Entity * /*entity -- unused*/,
|
|
GaugeRenderer *gauge_renderer
|
|
)
|
|
{
|
|
ParameterDescription *p = methodDescription.parameterList;
|
|
|
|
VertTwoPartBar *gauge = (VertTwoPartBar *)operator new(0xbc); // FUN_00402298(0xbc)
|
|
if (gauge != NULL)
|
|
{
|
|
new (gauge) VertTwoPartBar( // FUN_004c4724
|
|
p[0].data.rate, p[1].data.modeMask,
|
|
(L4GaugeRenderer *)gauge_renderer,
|
|
display_port_index, // graphics_port_number
|
|
position.x, position.y, // left, bottom
|
|
position.x + p[2].data.vector.x, // right = x + width
|
|
position.y + p[2].data.vector.y, // top = y + height
|
|
p[3].data.string, // tile bitmap
|
|
p[4].data.color, // backgroundColor
|
|
p[5].data.color, // fillColor
|
|
p[6].data.color, // extraColor
|
|
(Scalar *)p[7].data.attributePointer, // current value source
|
|
(Scalar *)p[8].data.attributePointer, // warn threshold source
|
|
(Scalar *)p[9].data.attributePointer, // max source
|
|
"VertTwoPartBar");
|
|
}
|
|
|
|
L4Warehouse *warehouse = (L4Warehouse *)gauge_renderer->warehousePointer;
|
|
if (warehouse->bitMapBin.Get(p[3].data.string) == NULL) // FUN_00442aec
|
|
{
|
|
DebugStream << "VertTwoPartBarNormalized: Missing image '"
|
|
<< p[3].data.string << "'\n";
|
|
return False;
|
|
}
|
|
warehouse->bitMapBin.Release(p[3].data.string); // FUN_00442c12
|
|
return True;
|
|
}
|
|
|
|
//
|
|
// @004c4724 -- ctor. GraphicGauge base; intern + ref-count the tile bitmap; set
|
|
// the graphics-port extent (bottom->top); store the three colours and the bar
|
|
// size; wire three GaugeConnectionDirectOf<Scalar> feeds (value/low/high).
|
|
//
|
|
VertTwoPartBar::VertTwoPartBar(
|
|
GaugeRate rate,
|
|
ModeMask mode_mask,
|
|
L4GaugeRenderer *renderer_in,
|
|
int graphics_port_number,
|
|
int left,
|
|
int bottom,
|
|
int right,
|
|
int top,
|
|
const char *tile_image,
|
|
int background_color,
|
|
int fill_color,
|
|
int extra_color,
|
|
Scalar *value_pointer,
|
|
Scalar *low_pointer,
|
|
Scalar *high_pointer,
|
|
const char *identification_string
|
|
):
|
|
GraphicGauge(rate, mode_mask, renderer_in, 0, // FUN_00444818 (owner_ID 0)
|
|
graphics_port_number, identification_string)
|
|
{
|
|
// Own a copy of the tile-bitmap name (Make passes the transient interpreter
|
|
// scratch buffer) and hold a ref for the gauge's life.
|
|
tileImage = new char[strlen(tile_image) + 1]; // @0x90 FUN_004700ac
|
|
strcpy(tileImage, tile_image);
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer_in->warehousePointer;
|
|
warehouse->bitMapBin.Get(tileImage); // FUN_00442aec (held)
|
|
|
|
localView.SetPositionWithinPort(left, bottom, right, top); // this+0x48 vtbl+0x08
|
|
|
|
backgroundColor = background_color; // @0x94 this[0x25]
|
|
fillColor = fill_color; // @0x98 this[0x26]
|
|
extraColor = extra_color; // @0x9C this[0x27]
|
|
width = (right - left) - 1; // @0xA0 this[0x28]
|
|
height = top - bottom; // @0xA4 this[0x29]
|
|
|
|
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, &value, value_pointer)); // @0xB0
|
|
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, &low, low_pointer)); // @0xB4
|
|
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, &high, high_pointer)); // @0xB8
|
|
}
|
|
|
|
//
|
|
// @004c486c -- dtor. Release the tile ref (keyed on tileImage, before the free),
|
|
// free the interned name; the base chain runs implicitly.
|
|
//
|
|
VertTwoPartBar::~VertTwoPartBar()
|
|
{
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
warehouse->bitMapBin.Release(tileImage); // FUN_00442c12
|
|
delete[] tileImage; // FUN_004022e8
|
|
tileImage = NULL;
|
|
}
|
|
|
|
Logical
|
|
VertTwoPartBar::TestInstance() const
|
|
{
|
|
return GraphicGauge::TestInstance();
|
|
}
|
|
|
|
//
|
|
// @004c48e0 -- BecameActive: force a full redraw on the first Execute
|
|
// (previousFull=0, previousFill=width -- neither can match a real pixel pair).
|
|
//
|
|
void
|
|
VertTwoPartBar::BecameActive()
|
|
{
|
|
previousFull = 0; // @0xAC this[0x2B]
|
|
previousFill = width; // @0xA8 this[0x2A]
|
|
}
|
|
|
|
//
|
|
// @004c48fc -- Execute. Clamp value to [0,high]; map value and warn to bar
|
|
// pixels (half-up round of height*x/high, clamped to [0,height]); if either
|
|
// changed, repaint: tile the bitmap over [0,warnPix), fill [warnPix,valPix) with
|
|
// fillColor, and clear [valPix,height) with extraColor. (x87 pixel math recovered
|
|
// by disassembly @004c4940/@004c4960 -- Ghidra dropped the FPU args.)
|
|
//
|
|
void
|
|
VertTwoPartBar::Execute()
|
|
{
|
|
// clamp value to [0, high] (@004c4908 fcomp 0.0 ; @004c4929 fcomp high)
|
|
if (value < 0.0f)
|
|
value = 0.0f;
|
|
else if (value > high)
|
|
value = high;
|
|
|
|
int warnPix = (int)((Scalar)height * low / high + 0.5f); // warn threshold pixel
|
|
int valPix = (int)((Scalar)height * value / high + 0.5f); // current value pixel
|
|
if (warnPix < 0) warnPix = 0; else if (warnPix > height) warnPix = height;
|
|
if (valPix < 0) valPix = 0; else if (valPix > height) valPix = height;
|
|
|
|
if (warnPix != previousFull || valPix != previousFill)
|
|
{
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
BitMap *tile = warehouse->bitMapBin.Get(tileImage); // FUN_00442aec
|
|
|
|
localView.SetColor(backgroundColor); // vtbl+0x18
|
|
DrawTiledBitmap(&localView, 0, 0, width, warnPix - 1, extraColor, tile);
|
|
warehouse->bitMapBin.Release(tileImage); // FUN_00442c12
|
|
|
|
if (warnPix < valPix) // normal fill above the warn line
|
|
{
|
|
localView.SetColor(fillColor);
|
|
localView.MoveToAbsolute(0, warnPix); // vtbl+0x24
|
|
localView.DrawFilledRectangleToAbsolute(width, valPix - 1); // vtbl+0x48
|
|
}
|
|
if (valPix < height) // empty region above the value
|
|
{
|
|
localView.SetColor(extraColor);
|
|
localView.MoveToAbsolute(0, valPix);
|
|
localView.DrawFilledRectangleToAbsolute(width, height);
|
|
}
|
|
|
|
previousFull = warnPix;
|
|
previousFill = valPix;
|
|
}
|
|
}
|
|
|
|
//###########################################################################
|
|
// VertNormalSlider (config keyword "vertNormalSlider" -- the condenser VALVE
|
|
// slider @2 in GenericHeatGauges1/2; vtable PTR_FUN_00518c34). Reconstructed
|
|
// from part_013.c:14051-14175 (Make @004c4b08 by disassembly). One
|
|
// GaugeConnectionDirectOf<Scalar> drives a normalised [0,1] value; Execute maps
|
|
// it to row = Round(span*value) and XOR-toggles a width x baseline indicator.
|
|
//###########################################################################
|
|
|
|
// CFG shape (L4GAUGE.CFG:4839): vertNormalSlider(rate,mode,(w,h),fgColor,bgColor,thickness,valveAttr)
|
|
MethodDescription
|
|
VertNormalSlider::methodDescription =
|
|
{
|
|
"vertNormalSlider",
|
|
VertNormalSlider::Make,
|
|
{
|
|
{ ParameterDescription::typeRate, NULL }, // rate ID
|
|
{ ParameterDescription::typeModeMask, NULL }, // mode mask
|
|
{ ParameterDescription::typeVector, NULL }, // (width,height)
|
|
{ ParameterDescription::typeColor, NULL }, // fill / foreground colour
|
|
{ ParameterDescription::typeColor, NULL }, // background colour
|
|
{ ParameterDescription::typeInteger, NULL }, // baseline (indicator thickness)
|
|
{ ParameterDescription::typeAttribute, NULL }, // value source (Condenser/ValveSetting, Scalar [0,1])
|
|
PARAMETER_DESCRIPTION_END
|
|
}
|
|
};
|
|
|
|
//
|
|
// @004c4b08 -- Make. Allocate 0xb0 + placement-construct. No owned bitmap, so no
|
|
// image-exists check; always returns True (the binary returns eax=1 even on alloc
|
|
// failure). `entity` unused (binds by attribute pointer already resolved into
|
|
// parameterList[6]).
|
|
//
|
|
Logical
|
|
VertNormalSlider::Make(
|
|
int display_port_index,
|
|
Vector2DOf<int> position,
|
|
Entity * /*entity -- unused*/,
|
|
GaugeRenderer *gauge_renderer
|
|
)
|
|
{
|
|
ParameterDescription *p = methodDescription.parameterList;
|
|
|
|
VertNormalSlider *gauge = (VertNormalSlider *)operator new(0xb0); // FUN_00402298(0xb0)
|
|
if (gauge != NULL)
|
|
{
|
|
new (gauge) VertNormalSlider( // FUN_004c4b84
|
|
p[0].data.rate, p[1].data.modeMask,
|
|
(L4GaugeRenderer *)gauge_renderer,
|
|
display_port_index, // graphics_port_number
|
|
position.x, position.y, // left, bottom
|
|
position.x + p[2].data.vector.x, // right = x + width
|
|
position.y + p[2].data.vector.y, // top = y + height
|
|
p[3].data.color, // fill / foreground colour
|
|
p[4].data.color, // background colour
|
|
p[5].data.integer, // baseline (indicator thickness)
|
|
(Scalar *)p[6].data.attributePointer, // value source (Condenser/ValveSetting)
|
|
"VertNormalSlider");
|
|
}
|
|
return True;
|
|
}
|
|
|
|
//
|
|
// @004c4b84 -- ctor (vtable PTR_FUN_00518c34). GraphicGauge base; set the port
|
|
// extent (bottom->top), latch the raster op to XOR + the pen to the fill colour
|
|
// ONCE (so every Draw toggles the indicator in place), cache geometry, wire one
|
|
// Scalar connection. previousFill is NOT init here -- BecameActive sets it -1.
|
|
//
|
|
VertNormalSlider::VertNormalSlider(
|
|
GaugeRate rate,
|
|
ModeMask mode_mask,
|
|
L4GaugeRenderer *renderer_in,
|
|
int graphics_port_number,
|
|
int left,
|
|
int bottom,
|
|
int right,
|
|
int top,
|
|
int fill_color,
|
|
int background_color,
|
|
int baseline_in,
|
|
Scalar *value_pointer,
|
|
const char *identification_string
|
|
):
|
|
GraphicGauge(rate, mode_mask, renderer_in, 0, // FUN_00444818 (owner_ID 0)
|
|
graphics_port_number, identification_string)
|
|
{
|
|
localView.SetPositionWithinPort(left, bottom, right, top); // vtbl+0x08
|
|
localView.SetOperation(GraphicsDisplay::Xor); // vtbl+0x0c (op == 3)
|
|
localView.SetColor(fill_color); // vtbl+0x18
|
|
|
|
fillColor = fill_color; // @0xA4
|
|
backgroundColor = background_color; // @0xA8
|
|
baseline = baseline_in; // @0x9C
|
|
width = (right - left) - 1; // @0x94
|
|
height = top - bottom; // @0x98
|
|
span = (top - bottom) - baseline_in; // @0xA0
|
|
|
|
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, &value, value_pointer)); // -> @0xAC
|
|
}
|
|
|
|
//
|
|
// @004c4c68 -- dtor. Owns no resource; the connection + localView are released by
|
|
// the base teardown, so the body is empty (the base-dtor chain runs implicitly).
|
|
//
|
|
VertNormalSlider::~VertNormalSlider()
|
|
{
|
|
}
|
|
|
|
//
|
|
// @004c4c94 -- TestInstance. Non-virtual out-of-line forward to the base (its body
|
|
// MUST exist because the header declares it -- else /FORCE stubs it to an AV).
|
|
//
|
|
Logical
|
|
VertNormalSlider::TestInstance() const
|
|
{
|
|
return GraphicGauge::TestInstance();
|
|
}
|
|
|
|
//
|
|
// @004c4cac -- BecameActive. Invalidate the cached row so the first Execute repaints.
|
|
//
|
|
void
|
|
VertNormalSlider::BecameActive()
|
|
{
|
|
previousFill = -1; // @0x90
|
|
}
|
|
|
|
//
|
|
// @004c4cc0 -- Execute. Clamp value to [0,1], map to row = Round(span*value), and
|
|
// on a change XOR-erase the old indicator + XOR-draw the new one.
|
|
//
|
|
void
|
|
VertNormalSlider::Execute()
|
|
{
|
|
if (value < 0.0f)
|
|
value = 0.0f;
|
|
else if (value > 1.0f)
|
|
value = 1.0f;
|
|
|
|
int pixel = HeatRound((Scalar)span * value); // row
|
|
|
|
if (pixel != previousFill)
|
|
{
|
|
Draw(); // erase old (XOR at old previousFill)
|
|
previousFill = pixel;
|
|
Draw(); // draw new (XOR at new previousFill)
|
|
}
|
|
}
|
|
|
|
//
|
|
// @004c4d48 -- Draw (file-private, non-virtual). Move the pen to the track's left
|
|
// edge at the current row, draw a width x baseline filled rectangle (XOR op + fill
|
|
// colour latched in the ctor, so a re-Draw at the same row erases).
|
|
//
|
|
void
|
|
VertNormalSlider::Draw()
|
|
{
|
|
if (previousFill >= 0)
|
|
{
|
|
localView.MoveToAbsolute(0, previousFill); // vtbl+0x24
|
|
localView.DrawFilledRectangleToRelative(width, baseline); // vtbl+0x4c
|
|
}
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
//###########################################################################
|
|
// 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.
|
|
//
|
|
|
|
//
|
|
// @004c4d88 -- OneOfSeveral base ctor (vtable PTR_FUN_00518bf0). Intern the
|
|
// image name, hold a ref, read the source strip size from the appropriate cache
|
|
// (PixMap8Cache if !fromImageStrip, else BitMapCache), divide by columns/rows to
|
|
// get the per-frame size, and set the graphics-port origin.
|
|
//
|
|
OneOfSeveral::OneOfSeveral(
|
|
GaugeRate rate,
|
|
ModeMask mode_mask,
|
|
L4GaugeRenderer *renderer_in,
|
|
int graphics_port_number,
|
|
int x,
|
|
int y,
|
|
Logical from_image_strip,
|
|
const char *image_name,
|
|
int background_color,
|
|
int foreground_color,
|
|
int columns_in,
|
|
int rows_in,
|
|
const char *identification_string
|
|
):
|
|
GraphicGauge(rate, mode_mask, renderer_in, 0, // FUN_00444818 (owner_ID 0)
|
|
graphics_port_number, identification_string)
|
|
{
|
|
backgroundColor = background_color; // @0x98
|
|
foregroundColor = foreground_color; // @0x9C
|
|
columns = columns_in; // @0xA0
|
|
fromImageStrip = from_image_strip; // @0x90
|
|
|
|
imageName = new char[strlen(image_name) + 1]; // @0x94 FUN_004700ac
|
|
strcpy(imageName, image_name);
|
|
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer_in->warehousePointer;
|
|
if (fromImageStrip == 0) // PixMap8 strip
|
|
{
|
|
PixelMap8 *pm = warehouse->pixelMap8Bin.Get(imageName); // FUN_00442d2b (held)
|
|
frameWidth = (pm != NULL ? pm->Data.Size.x : 0) / columns_in; // @0xA4
|
|
frameHeight = (pm != NULL ? pm->Data.Size.y : 0) / rows_in; // @0xA8
|
|
}
|
|
else // BitMap strip
|
|
{
|
|
BitMap *bm = warehouse->bitMapBin.Get(imageName); // FUN_00442aec (held)
|
|
frameWidth = (bm != NULL ? bm->Data.Size.x : 0) / columns_in;
|
|
frameHeight = (bm != NULL ? bm->Data.Size.y : 0) / rows_in;
|
|
}
|
|
|
|
localView.SetOrigin(x, y); // this+0x48 vtbl+0x10
|
|
}
|
|
|
|
//
|
|
// @004c4e7c -- dtor. Release the image from the matching cache (keyed on
|
|
// imageName, before the free); base chain implicit.
|
|
//
|
|
OneOfSeveral::~OneOfSeveral()
|
|
{
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
if (fromImageStrip == 0)
|
|
warehouse->pixelMap8Bin.Release(imageName); // FUN_00442e51
|
|
else
|
|
warehouse->bitMapBin.Release(imageName); // FUN_00442c12
|
|
delete[] imageName; // FUN_004022e8
|
|
imageName = NULL;
|
|
}
|
|
|
|
Logical
|
|
OneOfSeveral::TestInstance() const
|
|
{
|
|
return GraphicGauge::TestInstance();
|
|
}
|
|
|
|
//
|
|
// @004c4f14 -- BecameActive: previousSelected = -1 (force the first redraw).
|
|
//
|
|
void
|
|
OneOfSeveral::BecameActive()
|
|
{
|
|
previousSelected = -1; // @0xB0
|
|
}
|
|
|
|
//
|
|
// @004c4f28 -- Execute. On a change of `selected`, compute the frame's (col,row)
|
|
// within the strip and blit that sub-rectangle: DrawPixelMap8 (opaque) for a
|
|
// pixmap strip, or SetColor(bg)+DrawBitMapOpaque(fg,..) for a bitmap strip.
|
|
//
|
|
void
|
|
OneOfSeveral::Execute()
|
|
{
|
|
if (selected == previousSelected)
|
|
return;
|
|
previousSelected = selected;
|
|
|
|
int col, row;
|
|
if (columns == 1)
|
|
{
|
|
col = 0;
|
|
row = selected;
|
|
}
|
|
else
|
|
{
|
|
col = selected % columns;
|
|
row = selected / columns;
|
|
}
|
|
|
|
int sx0 = col * frameWidth;
|
|
int sx1 = frameWidth + sx0 - 1;
|
|
int sy0 = row * frameHeight;
|
|
int sy1 = frameHeight + sy0 - 1;
|
|
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
if (fromImageStrip == 0) // PixMap8: own palette, no SetColor
|
|
{
|
|
PixelMap8 *pm = warehouse->pixelMap8Bin.Get(imageName); // FUN_00442d2b
|
|
localView.DrawPixelMap8(True, 0, pm, sx0, sy0, sx1, sy1); // vtbl+0x5C (opaque)
|
|
warehouse->pixelMap8Bin.Release(imageName); // FUN_00442e51
|
|
}
|
|
else // BitMap strip
|
|
{
|
|
localView.SetColor(backgroundColor); // vtbl+0x18
|
|
BitMap *bm = warehouse->bitMapBin.Get(imageName); // FUN_00442aec
|
|
localView.DrawBitMapOpaque(foregroundColor, 0, bm, sx0, sy0, sx1, sy1); // vtbl+0x58
|
|
warehouse->bitMapBin.Release(imageName); // FUN_00442c12
|
|
}
|
|
}
|
|
|
|
//###########################################################################
|
|
// OneOfSeveralPixInt @004c5204 Make / @004c52d8 ctor (vtable PTR_FUN_00518b68)
|
|
//
|
|
// The cockpit button-state lamps (config "oneOfSeveralPixInt"): a PixMap8 strip
|
|
// of N frames selected by an integer game attribute (DuckState / Searchlight/
|
|
// LightOn / ControlsMapper/DisplayMode). Drawing is inherited from OneOfSeveral;
|
|
// this subclass just forces the PixMap path and wires the int connection.
|
|
//###########################################################################
|
|
|
|
MethodDescription
|
|
OneOfSeveralPixInt::methodDescription =
|
|
{
|
|
"oneOfSeveralPixInt",
|
|
OneOfSeveralPixInt::Make,
|
|
{
|
|
//
|
|
// CFG shape (L4GAUGE.CFG:5001):
|
|
// oneOfSeveralPixInt( rate, mode, strip.pcc, columns, rows, stateAttr )
|
|
// e.g. oneOfSeveralPixInt(E,ModeAlwaysActive,bduck.pcc,3,1,DuckState);
|
|
//
|
|
{ ParameterDescription::typeRate, NULL }, // rate ID
|
|
{ ParameterDescription::typeModeMask, NULL }, // mode mask
|
|
{ ParameterDescription::typeString, NULL }, // pixmap strip name
|
|
{ ParameterDescription::typeInteger, NULL }, // columns (frame count)
|
|
{ ParameterDescription::typeInteger, NULL }, // rows
|
|
{ ParameterDescription::typeAttribute, NULL }, // integer state selector
|
|
PARAMETER_DESCRIPTION_END
|
|
}
|
|
};
|
|
|
|
//
|
|
// @004c5204 -- Make. Allocate + construct, then verify the pixmap strip exists.
|
|
//
|
|
Logical
|
|
OneOfSeveralPixInt::Make(
|
|
int display_port_index,
|
|
Vector2DOf<int> position,
|
|
Entity * /*entity -- unused*/,
|
|
GaugeRenderer *gauge_renderer
|
|
)
|
|
{
|
|
ParameterDescription *p = methodDescription.parameterList;
|
|
|
|
OneOfSeveralPixInt *gauge = (OneOfSeveralPixInt *)operator new(0xb4); // FUN_00402298(0xb4)
|
|
if (gauge != NULL)
|
|
{
|
|
new (gauge) OneOfSeveralPixInt( // FUN_004c52d8
|
|
p[0].data.rate, p[1].data.modeMask,
|
|
(L4GaugeRenderer *)gauge_renderer,
|
|
display_port_index, // graphics_port_number
|
|
position.x, position.y,
|
|
p[2].data.string, // pixmap strip
|
|
p[3].data.integer, p[4].data.integer, // columns, rows
|
|
(int *)p[5].data.attributePointer, // integer state source
|
|
"OneOfSeveralPixInt");
|
|
}
|
|
|
|
L4Warehouse *warehouse = (L4Warehouse *)gauge_renderer->warehousePointer;
|
|
if (warehouse->pixelMap8Bin.Get(p[2].data.string) == NULL) // FUN_00442d2b
|
|
{
|
|
DebugStream << "OneOfSeveralPixInt: Missing image '" << p[2].data.string << "'\n";
|
|
return False;
|
|
}
|
|
warehouse->pixelMap8Bin.Release(p[2].data.string); // FUN_00442e51
|
|
return True;
|
|
}
|
|
|
|
//
|
|
// @004c52d8 -- ctor. OneOfSeveral base with fromImageStrip=0 (PixMap8) and
|
|
// bg/fg=0 (the pixmap carries its own palette); one int connection -> selected.
|
|
//
|
|
OneOfSeveralPixInt::OneOfSeveralPixInt(
|
|
GaugeRate rate,
|
|
ModeMask mode_mask,
|
|
L4GaugeRenderer *renderer_in,
|
|
int graphics_port_number,
|
|
int x,
|
|
int y,
|
|
const char *image,
|
|
int columns_in,
|
|
int rows_in,
|
|
int *value_pointer,
|
|
const char *identification_string
|
|
):
|
|
OneOfSeveral(rate, mode_mask, renderer_in, graphics_port_number, x, y,
|
|
False, // fromImageStrip = 0 (PixMap)
|
|
image, 0, 0, // bg, fg = 0
|
|
columns_in, rows_in, identification_string)
|
|
{
|
|
selected = 0; // @0xAC this[0x2B]
|
|
AddConnection(new GaugeConnectionDirectOf<int>(0, &selected, value_pointer)); // FUN_004749de
|
|
}
|
|
|
|
//
|
|
// @004c5364 -- deleting-dtor thunk. All teardown is ~OneOfSeveral; implicit.
|
|
//
|
|
OneOfSeveralPixInt::~OneOfSeveralPixInt()
|
|
{
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
// Additional composite-cluster child gauges (used by btl4gau2's
|
|
// SubsystemCluster family). HorizTwoPartBar mirrors VertTwoPartBar
|
|
// (horizontal); OneOfSeveralInt/States are OneOfSeveral subclasses; the
|
|
// LeakGauge base BitMapInverseWipe reveals a bitmap up to a level.
|
|
//###########################################################################
|
|
|
|
//
|
|
// StateConnection -- @004c3324 ctor / @004c3390 Transfer. Copies a Subsystem's
|
|
// state word (@0x14) into the destination each frame (drives OneOfSeveralStates).
|
|
//
|
|
class StateConnection : public GaugeConnection
|
|
{
|
|
public:
|
|
StateConnection(int *destination, Entity *source)
|
|
: GaugeConnection(0), source(source), destination(destination) {} // FUN_004c3324
|
|
void Update() // @004c3390
|
|
{
|
|
*destination = *(int *)((char *)source + 0x14); // Subsystem state @0x14
|
|
}
|
|
protected:
|
|
Entity *source; // @0x10
|
|
int *destination; // @0x14
|
|
};
|
|
|
|
//
|
|
// @004c4170 -- HorizTwoPartBar ctor (vtable 0x518cbc). GraphicGauge base, intern
|
|
// the tile bitmap, SetExtent, store fill/bg + width/height, wire the value/low/
|
|
// high Scalar connections. Grows left->right (mirror of VertTwoPartBar).
|
|
//
|
|
HorizTwoPartBar::HorizTwoPartBar(
|
|
GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer_in,
|
|
int graphics_port_number, int left, int bottom, int right, int top,
|
|
const char *tile_image, int fill_color, int background_color,
|
|
Scalar *value_pointer, Scalar *low_pointer, Scalar *high_pointer,
|
|
const char *identification_string
|
|
):
|
|
GraphicGauge(rate, mode_mask, renderer_in, 0, graphics_port_number, // FUN_00444818
|
|
identification_string)
|
|
{
|
|
tileImage = new char[strlen(tile_image) + 1];
|
|
strcpy(tileImage, tile_image);
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer_in->warehousePointer;
|
|
warehouse->bitMapBin.Get(tileImage);
|
|
localView.SetPositionWithinPort(left, bottom, right, top); // vtbl+0x08
|
|
fillColor = fill_color;
|
|
backgroundColor = background_color;
|
|
width = right - left;
|
|
height = (top - bottom) - 1;
|
|
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, &value, value_pointer));
|
|
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, &low, low_pointer));
|
|
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, &high, high_pointer));
|
|
}
|
|
|
|
HorizTwoPartBar::~HorizTwoPartBar()
|
|
{
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
warehouse->bitMapBin.Release(tileImage);
|
|
delete[] tileImage;
|
|
tileImage = NULL;
|
|
}
|
|
|
|
Logical HorizTwoPartBar::TestInstance() const { return GraphicGauge::TestInstance(); }
|
|
|
|
void HorizTwoPartBar::BecameActive() // @004c4324
|
|
{
|
|
previousFull = 0;
|
|
previousFill = width;
|
|
}
|
|
|
|
//
|
|
// @004c4340 -- Execute: clamp value to [0,high], map value/warn to bar pixels
|
|
// (half-up round of width*x/high), repaint on change. THREE zones along X
|
|
// (mirror of VertTwoPartBar along Y):
|
|
// [0, warnPix) striped TILE pattern (DrawTiledBitmap @004c4482)
|
|
// [warnPix, valPix) backgroundColor solid (only when value > low) @004c449d
|
|
// [valPix, width) fillColor solid @004c44da
|
|
// FIX 2026-07-21: the port had solid-filled from warnPix (no tile, no [0,warnPix]
|
|
// zone) -- so the bar read as "starts in the middle" + "not striped". Restored
|
|
// the tiled first zone + the correct zone colours from the binary disasm.
|
|
//
|
|
void HorizTwoPartBar::Execute()
|
|
{
|
|
if (value < 0.0f) value = 0.0f; // @004c434c fcomp 0.0
|
|
else if (value > high) value = high; // @004c4367 fcomp high
|
|
|
|
int warnPix = (int)((Scalar)width * low / high + 0.5f); // low/warn threshold pixel
|
|
int valPix = (int)((Scalar)width * value / high + 0.5f); // current value pixel
|
|
if (warnPix < 0) warnPix = 0; else if (warnPix > width) warnPix = width;
|
|
if (valPix < 0) valPix = 0; else if (valPix > width) valPix = width;
|
|
|
|
if (warnPix != previousFull || valPix != previousFill)
|
|
{
|
|
// Zone 1 [0, warnPix): the striped tile (the binary's tiled blit, NOT a
|
|
// solid fill). SetColor(backgroundColor) is the tile's opaque backdrop.
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
BitMap *tile = warehouse->bitMapBin.Get(tileImage); // FUN_00442aec
|
|
localView.SetColor(backgroundColor); // vtbl+0x18 @004c443a
|
|
DrawTiledBitmap(&localView, 0, 0, warnPix - 1, height, fillColor, tile);
|
|
warehouse->bitMapBin.Release(tileImage); // FUN_00442c12
|
|
|
|
// Zone 2 [warnPix, valPix): backgroundColor (binary keeps the current
|
|
// colour, still backgroundColor from zone 1) -- only when value > low.
|
|
if (valPix > warnPix)
|
|
{
|
|
localView.SetColor(backgroundColor);
|
|
localView.MoveToAbsolute(warnPix, 0); // vtbl+0x24
|
|
localView.DrawFilledRectangleToAbsolute(valPix - 1, height); // vtbl+0x48
|
|
}
|
|
|
|
// Zone 3 [valPix, width): the solid fill colour.
|
|
if (valPix < width)
|
|
{
|
|
localView.SetColor(fillColor); // @004c44da
|
|
localView.MoveToAbsolute(valPix, 0);
|
|
localView.DrawFilledRectangleToAbsolute(width, height);
|
|
}
|
|
|
|
previousFull = warnPix;
|
|
previousFill = valPix;
|
|
}
|
|
}
|
|
|
|
//
|
|
// @004c5148 -- OneOfSeveralInt ctor (vtable 0x518bac): OneOfSeveral(fromStrip=1) +
|
|
// a GaugeConnectionDirectOf<int> feeding the selected frame. dtor @004c51d8.
|
|
//
|
|
OneOfSeveralInt::OneOfSeveralInt(
|
|
GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer_in,
|
|
int graphics_port_number, int x, int y, const char *image,
|
|
int columns, int rows, int *value_pointer, const char *identification_string
|
|
):
|
|
OneOfSeveral(rate, mode_mask, renderer_in, graphics_port_number, x, y,
|
|
True, image, 0, 0, columns, rows, identification_string)
|
|
{
|
|
selected = 0;
|
|
AddConnection(new GaugeConnectionDirectOf<int>(0, &selected, value_pointer)); // FUN_004749de
|
|
}
|
|
|
|
OneOfSeveralInt::~OneOfSeveralInt() {} // @004c51d8
|
|
|
|
//
|
|
// @004c5470 -- OneOfSeveralStates ctor (vtable 0x518b24): OneOfSeveral + a
|
|
// StateConnection reading the subsystem state word @0x14. BecameActive @004c552c
|
|
// clamps the state >= 0 then chains OneOfSeveral::Execute. dtor @004c5500.
|
|
//
|
|
OneOfSeveralStates::OneOfSeveralStates(
|
|
GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer_in,
|
|
int graphics_port_number, int x, int y, const char *image,
|
|
int columns, int rows, Entity *subsystem_source, const char *identification_string
|
|
):
|
|
OneOfSeveral(rate, mode_mask, renderer_in, graphics_port_number, x, y,
|
|
True, image, 0, 0, columns, rows, identification_string)
|
|
{
|
|
selected = 0;
|
|
AddConnection(new StateConnection(&selected, subsystem_source)); // FUN_004c3324
|
|
}
|
|
|
|
OneOfSeveralStates::~OneOfSeveralStates() {} // @004c5500
|
|
|
|
void OneOfSeveralStates::BecameActive() // @004c552c
|
|
{
|
|
if (selected < 0)
|
|
selected = 0;
|
|
OneOfSeveral::BecameActive();
|
|
}
|
|
|
|
//
|
|
// gauge wave P2 -- the "LeakGauge" config factory + methodDescription. The class
|
|
// body (ctor/dtor/BecameActive/Execute) already existed + is byte-faithful; it was
|
|
// just UNREGISTERED, so every `LeakGauge(...)` config line (GenericHeatGauges1/2)
|
|
// was parse-skipped and the coolant-leak inverse-wipe never built. CFG shape
|
|
// (L4GAUGE.CFG:4858): LeakGauge(rate,mode,image.pcc,colorA,colorB,frames,third,levelAttr).
|
|
// value source @6 = Condenser/CoolantMassLeakRate -- already published (heat.cpp:345).
|
|
//
|
|
MethodDescription
|
|
BitMapInverseWipe::methodDescription =
|
|
{
|
|
"LeakGauge",
|
|
BitMapInverseWipe::Make,
|
|
{
|
|
{ ParameterDescription::typeRate, NULL }, // rate ID
|
|
{ ParameterDescription::typeModeMask, NULL }, // mode mask
|
|
{ ParameterDescription::typeString, NULL }, // leak strip bitmap -> @0x90
|
|
{ ParameterDescription::typeColor, NULL }, // colorA empty-cell -> @0x94
|
|
{ ParameterDescription::typeColor, NULL }, // colorB leak-cell -> @0x98
|
|
{ ParameterDescription::typeInteger, NULL }, // frames (=3) -> frames/fullWidth
|
|
{ ParameterDescription::typeScalar, NULL }, // third (.15; raw @0xB0, inert in base Execute)
|
|
{ ParameterDescription::typeAttribute, NULL }, // level source (Scalar CoolantMassLeakRate)
|
|
PARAMETER_DESCRIPTION_END
|
|
}
|
|
};
|
|
|
|
Logical
|
|
BitMapInverseWipe::Make(
|
|
int display_port_index,
|
|
Vector2DOf<int> position,
|
|
Entity * /*entity -- unused*/,
|
|
GaugeRenderer *gauge_renderer
|
|
)
|
|
{
|
|
ParameterDescription *p = methodDescription.parameterList;
|
|
|
|
BitMapInverseWipe *gauge = (BitMapInverseWipe *)operator new(0xb8); // FUN_00402298(0xb8)
|
|
if (gauge != NULL)
|
|
{
|
|
// NOTE the ctor lists `third` BEFORE `frames`, so map p[6]->third, p[5]->frames BY NAME.
|
|
new (gauge) BitMapInverseWipe( // FUN_004c5b7c
|
|
p[0].data.rate, p[1].data.modeMask,
|
|
(L4GaugeRenderer *)gauge_renderer,
|
|
display_port_index, // graphics_port_number
|
|
position.x, position.y, // -> localView.SetOrigin(x,y)
|
|
p[2].data.string, // image (eleak.pcc)
|
|
p[3].data.color, // color_a
|
|
p[4].data.color, // color_b
|
|
p[6].data.integer, // third (.15 raw bits; inert) -> @0xB0
|
|
p[5].data.integer, // frames (3)
|
|
(Scalar *)p[7].data.attributePointer, // level source (Condenser/CoolantMassLeakRate)
|
|
"LeakGauge");
|
|
}
|
|
|
|
L4Warehouse *warehouse = (L4Warehouse *)gauge_renderer->warehousePointer;
|
|
if (warehouse->bitMapBin.Get(p[2].data.string) == NULL) // FUN_00442aec
|
|
{
|
|
DebugStream << "LeakGauge: Missing image '" << p[2].data.string << "'\n";
|
|
return False;
|
|
}
|
|
warehouse->bitMapBin.Release(p[2].data.string); // FUN_00442c12
|
|
return True;
|
|
}
|
|
|
|
//
|
|
// @004c5b7c -- BitMapInverseWipe ctor (vtable 0x518a9c): GraphicGauge base;
|
|
// SetOrigin(x,y), intern the image, store colours + frame geometry (frameWidth =
|
|
// imageWidth/3, fullWidth = frames*2), wire a Scalar level connection. Used as
|
|
// the coolant LeakGauge.
|
|
//
|
|
BitMapInverseWipe::BitMapInverseWipe(
|
|
GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer_in,
|
|
int graphics_port_number, int x, int y, const char *image,
|
|
int color_a, int color_b, int third, int frames,
|
|
Scalar *value_pointer, const char *identification_string
|
|
):
|
|
GraphicGauge(rate, mode_mask, renderer_in, 0, graphics_port_number,
|
|
identification_string)
|
|
{
|
|
localView.SetOrigin(x, y); // vtbl+0x10
|
|
imageName = new char[strlen(image) + 1];
|
|
strcpy(imageName, image);
|
|
colorA = color_a;
|
|
colorB = color_b;
|
|
this->frames = frames;
|
|
fullWidth = frames * 2;
|
|
this->third = third;
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer_in->warehousePointer;
|
|
BitMap *bmp = warehouse->bitMapBin.Get(imageName);
|
|
if (bmp == NULL)
|
|
{
|
|
frameWidth = 0;
|
|
frameHeight = 0;
|
|
}
|
|
else
|
|
{
|
|
frameWidth = bmp->Data.Size.x / 3;
|
|
frameHeight = bmp->Data.Size.y;
|
|
}
|
|
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, &value, value_pointer)); // FUN_00474855
|
|
}
|
|
|
|
BitMapInverseWipe::~BitMapInverseWipe() // @004c5c80
|
|
{
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
warehouse->bitMapBin.Release(imageName);
|
|
delete[] imageName;
|
|
imageName = NULL;
|
|
}
|
|
|
|
Logical BitMapInverseWipe::TestInstance() const { return GraphicGauge::TestInstance(); }
|
|
|
|
void BitMapInverseWipe::BecameActive() // @004c5cf4
|
|
{
|
|
previousLevel = -1;
|
|
}
|
|
|
|
//
|
|
// @004c5d08 -- Execute. Round the level to [0, fullWidth] (a tiny non-zero value
|
|
// forces at least 1); on a change repaint `frames` segments stacked vertically,
|
|
// each picking its strip frame from the level: <1 = empty (colorA, frame 0), ==1 =
|
|
// half (colorB, middle frame), >1 = full (colorB, last frame), decrementing the
|
|
// level by 2 per segment. (The coolant LeakGauge; value = CoolantMassLeakRate.)
|
|
//
|
|
void BitMapInverseWipe::Execute()
|
|
{
|
|
int level = (int)(value + (value < 0.0f ? -0.5f : 0.5f)); // FUN_004dcd94 round
|
|
if (level < 0) level = 0;
|
|
if (level > fullWidth) level = fullWidth;
|
|
if (value > 0.0025f && level < 1) level = 1; // _DAT_0050e3d8
|
|
|
|
if (level != previousLevel)
|
|
{
|
|
previousLevel = level;
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
BitMap *bmp = warehouse->bitMapBin.Get(imageName);
|
|
if (bmp != NULL)
|
|
{
|
|
int y = -frameHeight;
|
|
for (int seg = frames; seg > 0; seg--)
|
|
{
|
|
localView.MoveToAbsolute(0, y); // vtbl+0x24
|
|
int frameIndex;
|
|
if (level < 1) { localView.SetColor(colorA); frameIndex = 0; }
|
|
else if (level == 1) { localView.SetColor(colorB); frameIndex = frameWidth; }
|
|
else { localView.SetColor(colorB); frameIndex = frameWidth * 2; }
|
|
localView.DrawBitMap(0, bmp, frameIndex, 0, // vtbl+0x54
|
|
frameWidth + frameIndex, frameHeight);
|
|
level -= 2;
|
|
y -= frameHeight;
|
|
}
|
|
}
|
|
warehouse->bitMapBin.Release(imageName);
|
|
}
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
//###########################################################################
|
|
// 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).
|
|
//
|
|
//###########################################################################
|
|
// SegmentArcRatio @004c62fc Make / @004c6394 ctor / @004c6488 Execute
|
|
// (vtable PTR_FUN_0051898c; base = engine SegmentArc)
|
|
//
|
|
// A segmented arc dial (config keyword "segmentArcRatio"). Two Scalar
|
|
// connections drive it -- numerator (value) and denominator (max) -- and Execute
|
|
// lights numerator/denominator of the arc. Used for the cockpit SPEED arc
|
|
// (config binds numerator=LinearSpeed, denominator=MaxRunSpeed), so the arc
|
|
// sweeps as the mech accelerates. Unlike VertTwoPartBar, the drawing is
|
|
// inherited from the engine SegmentArc base -- this class only computes the
|
|
// [0,1] fill fraction into the base's currentValue, then delegates the render.
|
|
//###########################################################################
|
|
|
|
MethodDescription
|
|
SegmentArcRatio::methodDescription =
|
|
{
|
|
"segmentArcRatio",
|
|
SegmentArcRatio::Make,
|
|
{
|
|
//
|
|
// CFG shape (L4GAUGE.CFG:4964):
|
|
// segmentArcRatio( rate, mode, inner,outer, deg0,deg1, segs,
|
|
// bg,fg, valueAttr, maxAttr )
|
|
// e.g. segmentArcRatio(C,ModeAlwaysActive,32,39,0,360,36,0,2,
|
|
// LinearSpeed, MaxRunSpeed);
|
|
//
|
|
{ ParameterDescription::typeRate, NULL }, // rate ID
|
|
{ ParameterDescription::typeModeMask, NULL }, // mode mask
|
|
{ ParameterDescription::typeScalar, NULL }, // inner radius
|
|
{ ParameterDescription::typeScalar, NULL }, // outer radius
|
|
{ ParameterDescription::typeScalar, NULL }, // start angle (deg)
|
|
{ ParameterDescription::typeScalar, NULL }, // end angle (deg)
|
|
{ ParameterDescription::typeInteger, NULL }, // segment count (+dir)
|
|
{ ParameterDescription::typeColor, NULL }, // background colour
|
|
{ ParameterDescription::typeColor, NULL }, // foreground colour
|
|
{ ParameterDescription::typeAttribute, NULL }, // numerator (value)
|
|
{ ParameterDescription::typeAttribute, NULL }, // denominator (max)
|
|
PARAMETER_DESCRIPTION_END
|
|
}
|
|
};
|
|
|
|
//
|
|
// @004c62fc -- Make. Allocate + construct; no resource to verify (returns True).
|
|
//
|
|
Logical
|
|
SegmentArcRatio::Make(
|
|
int display_port_index,
|
|
Vector2DOf<int> position,
|
|
Entity * /*entity -- unused*/,
|
|
GaugeRenderer *gauge_renderer
|
|
)
|
|
{
|
|
ParameterDescription *p = methodDescription.parameterList;
|
|
|
|
SegmentArcRatio *gauge = (SegmentArcRatio *)operator new(0xcc); // FUN_00402298(0xcc)
|
|
if (gauge != NULL)
|
|
{
|
|
new (gauge) SegmentArcRatio( // FUN_004c6394
|
|
p[0].data.rate, p[1].data.modeMask,
|
|
(L4GaugeRenderer *)gauge_renderer,
|
|
display_port_index, // graphics_port_number
|
|
position.x, position.y, // centre
|
|
p[2].data.scalar, p[3].data.scalar, // inner, outer radius
|
|
p[4].data.scalar, p[5].data.scalar, // start, end angle
|
|
p[6].data.integer, // segment count (+dir)
|
|
p[7].data.color, p[8].data.color, // bg, fg
|
|
(Scalar *)p[9].data.attributePointer, // numerator source
|
|
(Scalar *)p[10].data.attributePointer, // denominator source
|
|
"SegmentArcRatio");
|
|
}
|
|
return True; // binary returns 1 unconditionally
|
|
}
|
|
|
|
//
|
|
// @004c6394 -- ctor. Engine SegmentArc base (inner0==inner1, outer0==outer1 --
|
|
// the Make duplicates them, so the arc has a constant radius); precompute the
|
|
// span factor and wire the two Scalar connections.
|
|
//
|
|
SegmentArcRatio::SegmentArcRatio(
|
|
GaugeRate rate,
|
|
ModeMask mode_mask,
|
|
L4GaugeRenderer *renderer_in,
|
|
int graphics_port_number,
|
|
int center_x,
|
|
int center_y,
|
|
Scalar inner,
|
|
Scalar outer,
|
|
Scalar deg0,
|
|
Scalar deg1,
|
|
int number_of_segs,
|
|
int background_color,
|
|
int foreground_color,
|
|
Scalar *numerator_pointer,
|
|
Scalar *denominator_pointer,
|
|
const char *identification_string
|
|
):
|
|
SegmentArc(rate, mode_mask, renderer_in, 0, // FUN_00473f44 (owner_ID 0)
|
|
graphics_port_number, center_x, center_y,
|
|
inner, outer, inner, outer, // inner0/outer0 == inner1/outer1
|
|
deg0, deg1, number_of_segs,
|
|
background_color, foreground_color,
|
|
True, // use_thick_lines (binary param_20 = 1)
|
|
identification_string)
|
|
{
|
|
// segmentSpan = (Scalar)(|n| / (|n|-1)) * 0.75f -- |n|/(|n|-1) is INTEGER
|
|
// division (== 1 for n>=2), so this is 0.75 for the 36-segment speed arc.
|
|
int n = (number_of_segs < 0) ? -number_of_segs : number_of_segs; // @0xC8
|
|
segmentSpan = (Scalar)(n / (n - 1)) * 0.75f; // _DAT_004c6484 = 0.75f
|
|
|
|
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, &numerator, numerator_pointer)); // @0xC0
|
|
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, &denominator, denominator_pointer)); // @0xC4
|
|
}
|
|
|
|
//
|
|
// @004c668d -- deleting-dtor thunk. Connections + arc base are released by the
|
|
// implicit base-dtor chain (FUN_00474094); no own teardown.
|
|
//
|
|
SegmentArcRatio::~SegmentArcRatio()
|
|
{
|
|
}
|
|
|
|
//
|
|
// @004c6488 -- Execute. currentValue = clamp(|numerator/denominator * span|,0,1);
|
|
// then delegate to the engine SegmentArc::Execute (0x474300) which lights that
|
|
// fraction of the segments. (x87 recovered by disassembly: FUN_004dcd00 = fabs.)
|
|
//
|
|
void
|
|
SegmentArcRatio::Execute()
|
|
{
|
|
if (denominator < 1.0f) // guard div-by-tiny / unpowered
|
|
{
|
|
currentValue = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
currentValue = numerator / denominator * segmentSpan;
|
|
if (currentValue < 0.0f) currentValue = -currentValue; // FUN_004dcd00 = fabs
|
|
if (currentValue < 0.0f)
|
|
currentValue = 0.0f;
|
|
else if (currentValue > 1.0f)
|
|
currentValue = 1.0f;
|
|
}
|
|
SegmentArc::Execute(); // 0x474300 -- the base draw
|
|
}
|
|
|
|
//
|
|
// @004c6244 -- SegmentArc270 ctor (vtable 0x5189d0). Engine SegmentArc base + one
|
|
// Scalar connection driving currentValue (a 0..1 fraction, e.g. a weapon's
|
|
// PercentDone recharge) + the segment-span factor (|n|/(|n|-1) * 0.75, integer
|
|
// division == 0.75 for n>=2). Inherits SegmentArc::Execute (draws the lit segments
|
|
// up to currentValue). Used as the WeaponCluster recharge dial.
|
|
//
|
|
SegmentArc270::SegmentArc270(
|
|
GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer_in, int owner_ID,
|
|
int graphics_port_number, int center_x, int center_y,
|
|
Scalar inner0, Scalar outer0, Scalar inner1, Scalar outer1,
|
|
Scalar deg0, Scalar deg1, int number_of_segs,
|
|
int background_color, int foreground_color,
|
|
Scalar *value_pointer, Logical use_thick_lines, const char *identification_string
|
|
):
|
|
SegmentArc(rate, mode_mask, renderer_in, owner_ID, graphics_port_number,
|
|
center_x, center_y, inner0, outer0, inner1, outer1, deg0, deg1,
|
|
number_of_segs, background_color, foreground_color, use_thick_lines,
|
|
identification_string)
|
|
{
|
|
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, ¤tValue, value_pointer));
|
|
int n = (number_of_segs < 0) ? -number_of_segs : number_of_segs;
|
|
segmentSpan = (Scalar)(n / (n - 1)) * 0.75f; // _DAT_004c62d4 = 0.75
|
|
diagName = 0;
|
|
}
|
|
|
|
SegmentArc270::~SegmentArc270() {} // @004c66b3 (base chain)
|
|
|
|
//
|
|
// PORT diagnostic wrapper (frozen-dial hunt, 2026-07-12): log the exact value
|
|
// and segment memory the arc runs with, then chain the engine draw untouched.
|
|
//
|
|
void
|
|
SegmentArc270::Execute()
|
|
{
|
|
if (getenv("BT_PANEL_LOG") && diagName != 0)
|
|
{
|
|
static int s_al = 0;
|
|
if ((s_al++ % 60) == 0)
|
|
DEBUG_STREAM << "[arc] " << diagName
|
|
<< " val=" << (float)currentValue
|
|
<< " prevSeg=" << previousSegment
|
|
<< " segs=" << numberOfSegments << "\n" << std::flush;
|
|
}
|
|
SegmentArc::Execute();
|
|
}
|
|
|
|
// === btl4gau2.cpp begins at @004c6798 (SeekVoltage gauge) -- not part of this TU.
|