Files
TeslaRel410/restoration/source410/BT_L4/BTL4GAUG.CPP
T
CydandClaude Fable 5 44aa7a349c BT410 5.3.38: SegmentArc270 never applied its 270 degrees
The class computed segmentSpan = 0.75 in its ctor and nothing ever read it:
the engine SegmentArc base has no span member, and SegmentArc270 declared no
Execute, so the recharge dial lit all 20 segments at PercentDone 1.0 where
the shipped cockpit lights 15 and leaves the quarter gap.  That was the last
MFD delta -- four extra spokes at the upper-left of every weapon disc.

A derived Execute is the only place the 0.75 can reach the draw, which is the
same role SegmentArcRatio's Execute plays with its own copy.  It scales the
raw fraction, draws, then restores: the connection only rewrites currentValue
when its source changes, so scaling in place would compound frame after frame
and walk the dial to zero.

  head   missing        extra
  Mfd1    140 (=)       679 -> 7
  Mfd2     97 (=)       462 -> 14
  Mfd3     31 (=)       461 -> 13

Cockpit: 98.2% identical / 97% coverage.  The three MFD heads and the three
engineering heads are now within a few dozen pixels of the shipped binary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 12:47:48 -05:00

2998 lines
88 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) and re-hosted onto the
// authentic 1995 engine (MUNGA GAUGE/GAUGREND, MUNGA_L4 L4GAUGE) for the
// BC++4.52 DOS build. Behaviour follows the Ghidra pseudo-C in
// recovered/all/part_013.c (@004c2f94..@004c5e84) and part_014.c
// (@004c5e84..@004c6394); 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_00444124 GaugeConnection ctor FUN_00444148 GaugeConnection dtor
// FUN_00444818 GraphicGauge ctor FUN_00444870 GraphicGauge dtor
// FUN_004700ac name intern -> nameCopy (L4GAUGE.CPP:80)
// FUN_00447f84 GaugeRenderer::GetGraphicsPort(port_number)
// FUN_00442f6a / 00443090 palette8Bin Get(peek) / Release
// FUN_00442aec / 00442c12 bitMapBin Get / Release
// FUN_00442d2b / 00442e51 pixelMap8Bin Get / Release
// FUN_0041f98c Entity::FindSubsystem(name)
// FUN_0041a1a4 IsDerivedFrom(class_derivations)
// FUN_0042076c Entity::GetDamageZoneIndex(name)
// FUN_004dcd94 Round(Scalar) -> int (declared in scalar.hpp)
// FUN_00408328 SinCosPair::operator=(const Radian&)
// FUN_004700bc NumericDisplay ctor FUN_0047018c ::dtor
// FUN_004703f4 NumericDisplay::ForceUpdate FUN_00470430 ::Draw
// FUN_0044a5b4/5dc GraphicsViewRecord ctor/dtor
// FUN_0044a650/630 GraphicsViewRecord Draw / Clear
// FUN_00474855 GaugeConnectionDirectOf<Scalar> ctor
// FUN_004749de GaugeConnectionDirectOf<int> ctor
// FUN_00473f44 SegmentArc ctor @00474300 SegmentArc::Execute
// DAT_00524e20 warning channel -> DEBUG_STREAM
// 0x50e3ec / 0x50e604 heat class-derivation tags (HeatableSubsystem /
// HeatWatcher)
//
// 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 clamp)
// _DAT_004c5acc = 0.0f , 0x42652ee1 = 57.29578f (HeadingPointer rad->deg)
// _DAT_004c62d4 = 0.75f, _DAT_004c6484 = 0.75f (arc span fraction)
// _DAT_0050e3d8 = 0.0025f (leak-wipe 'trace leak' threshold)
//
#include <btl4.hpp>
#pragma hdrstop
#if !defined(BTL4GAUG_HPP)
# include <btl4gaug.hpp>
#endif
#if !defined(HEAT_HPP)
# include <heat.hpp>
#endif
#if !defined(ROTATION_HPP)
# include <rotation.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.29578f; // 0x42652ee1
static const Scalar TraceLeakLevel = 0.0025f; // _DAT_0050e3d8
//###########################################################################
//###########################################################################
// File-private GaugeConnection subclasses
//
// Every gauge in this module that is "driven by mech subsystem state" is
// fed through one of these small GaugeConnection objects. Gauge::Update
// (GAUGE.CPP:549) walks the gauge's instanceList calling each connection's
// virtual Update() once per frame, then calls the gauge's Execute().
//
// Data bindings are through the reconstructed subsystems' public members
// and accessors (heat.hpp / mechsub.hpp / the engine damage model), never
// raw 1995 byte offsets:
// HeatConnection @004c3664 ctor / @004c3720 Update
// ArmorZoneConnection @004c33a4 ctor / @004c3430 Update
// MultiArmorConnection@004c346c ctor / @004c34f4 Update
// CriticalConnection @004c3598 ctor / @004c3610 Update
// StateConnection @004c3324 ctor / @004c3390 Update
// (@004c3134/@004c31ec/@004c3288 in this address range belong to the
// btl4gau2 cluster connections and are reconstructed there.)
//###########################################################################
//###########################################################################
//
// StateConnection -- @004c3324 ctor / @004c3390 Update. Copies a Subsystem's
// simulation state into the destination each frame (drives the frame index
// of OneOfSeveralStates). The binary's raw read of the "state word @0x14"
// is Simulation::GetSimulationState().
//
class StateConnection : public GaugeConnection
{
public:
StateConnection(int *destination, Subsystem *source):
GaugeConnection(0), // FUN_00444124(this,0)
source(source),
destination(destination)
{}
protected:
void Update(); // @004c3390
Subsystem *source; // @0x10
int *destination; // @0x14
};
void
StateConnection::Update()
{
Check(this);
*destination = (source != NULL) ? (int)source->GetSimulationState() : 0;
}
//
// ArmorZoneConnection -- @004c33a4 ctor / @004c3430 Update. Drives a
// ColorMapper's colour index from ONE damage zone's live damage ratio. The
// ctor resolves the zone from the entity's damageZones[zone_index]; Update
// 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 (DamageZone::damageLevel, 0..1) scaled to a
// 0..100 percentage and rounded -- ColorMapper::Execute clamps it and
// pushes the palette slot, recolouring that zone on the cockpit ARMOR
// DAMAGE schematic.
//
void Update();
DamageZone *zone; // @0x10 source
int *currentColorIndex; // @0x14 destination
};
void
ArmorZoneConnection::Update()
{
Check(this);
if (zone == NULL)
{
*currentColorIndex = 100;
return;
}
*currentColorIndex = Round(zone->damageLevel * 100.0f); // FUN_004dcd94
}
//
// MultiArmorConnection -- @004c346c ctor / @004c34f4 Update. Drives a
// ColorMapper's colour index from the WORST of up to 8 damage zones. The
// ctor copies the 8 zone indices + the owner; Update 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)
{
int i;
for (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, scale to 0..100 and round; no zone present -> 100.
//
void Update();
Entity *owner; // @0x10
int zoneIndex[8]; // @0x18..0x34
int *currentColorIndex; // @0x38
};
void
MultiArmorConnection::Update()
{
Check(this);
Scalar
worst = 0.0f;
int
count = 0,
i;
for (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 : Round(worst * 100.0f);
}
//
// CriticalConnection -- @004c3598 ctor / @004c3610 Update. Drives a
// ColorMapper's colour index from ONE subsystem's operational state. Used
// by ColorMapperCritical.
//
class CriticalConnection : public GaugeConnection
{
public:
CriticalConnection(int *destination, Subsystem *source):
GaugeConnection(0), // FUN_00444124(this,0)
subsystem(source),
currentColorIndex(destination)
{}
protected:
//
// Per-frame feed (@004c3610): no subsystem -> 0 (blank); subsystem
// DESTROYED -> 100 (full critical tint); else the subsystem's own
// damage-zone damageLevel (0..1) as a 0..100 percentage.
//
// POLARITY (on record): the binary's "+0x40 == 1" is the simulation
// state's DESTROYED reading -- GetSimulationState() ==
// MechSubsystem::DestroyedState -- NOT an "operational" flag.
//
void Update();
Subsystem *subsystem; // @0x10 source
int *currentColorIndex; // @0x14 destination
};
void
CriticalConnection::Update()
{
Check(this);
if (subsystem == NULL)
{
*currentColorIndex = 0;
return;
}
if (subsystem->GetSimulationState() == MechSubsystem::DestroyedState)
{
*currentColorIndex = 100;
return;
}
//
// The subsystem's own damage zone (the proxy IS a real DamageZone --
// Subsystem::damageZone). The binary reads it unconditionally; guard
// NULL (not every subsystem has its zone wired) -> 0, matching an
// intact/undamaged reading.
//
DamageZone
*zone = subsystem->damageZone;
*currentColorIndex = (zone != NULL) ? Round(zone->damageLevel * 100.0f) : 0;
}
//
// HeatConnection -- @004c3664 ctor / @004c3720 Update. Drives a
// ColorMapper's colour index from a heat-bearing subsystem's live
// temperature. Used by ColorMapperHeat -- THIS is the heat tint's data
// feed. The binary's flag@0x14 recorded "primary heat class vs. watcher";
// here the resolved HeatSink view plays that role.
//
class HeatConnection : public GaugeConnection
{
public:
//
// The owning ctor (ColorMapperHeat, @004c3f6c) verified the subsystem
// derives from HeatableSubsystem or HeatWatcher; resolve which branch
// once, here. Every concrete heat-bearing subsystem in the recon
// (HeatSink, Condenser, Reservoir, Generator, the weapons, Myomers, ...)
// descends from HeatSink, whose public CurrentTemperatureOf() is the
// live temperature accessor.
//
HeatConnection(int *destination, Subsystem *source):
GaugeConnection(0), // FUN_00444124(this,0)
heatSubsystem(source),
currentColorIndex(destination)
{
heatSink = NULL;
if (source != NULL && source->IsDerivedFrom(HeatSink::ClassDerivations))
{
heatSink = (HeatSink *)source;
}
}
protected:
//
// The per-frame data feed (@004c3720):
// no subsystem -> 100 (fail safe: full tint)
// subsystem destroyed -> 100 (the recovered "+0x40 == 1" path)
// HeatSink family -> Round(current temperature)
// HeatWatcher family -> 0 (INERT -- see note below)
//
// OPEN QUESTION (kept from the decode worksheet): the recovered code
// rounds the raw temperature straight into the 0..255 colour index that
// ColorMapper::Execute clamps to [0,255]. The exact temperature->
// percentage scaling (HEAT.TCP seeds currentTemperature at 300.0) is a
// tuning detail to reconcile against the original l4gauge.cfg palette
// ramp; the live data path itself is correct.
//
// HEATWATCHER BRANCH [T4, INERT]: for a watcher the binary resolved a
// link at the same offset the Heatable keeps its temperature at
// (unverified read path), and our reconstruction exposes no public
// accessor for the watched temperature (HeatWatcher::watchedLink is
// protected). The feed is left at 0 -- the tint draws, stays cool.
//
void Update();
Subsystem *heatSubsystem; // @0x10 source
HeatSink *heatSink; // (the binary's flag@0x14 role)
int *currentColorIndex; // @0x18 destination
};
void
HeatConnection::Update()
{
Check(this);
if (heatSubsystem == NULL)
{
*currentColorIndex = 100;
return;
}
if (heatSubsystem->GetSimulationState() == MechSubsystem::DestroyedState)
{
*currentColorIndex = 100;
return;
}
if (heatSink != NULL)
{
*currentColorIndex = Round(heatSink->CurrentTemperatureOf()); // the live feed
}
else
{
*currentColorIndex = 0; // HeatWatcher branch -- INERT (see above)
}
}
//###########################################################################
//###########################################################################
// ColorMapper (base : Gauge)
//###########################################################################
//###########################################################################
//
// @004c37dc -- ctor (vtable PTR_FUN_00518e10), base name passed through to
// Gauge. Interns both palette names, flags twoColorMode when they differ,
// references each palette in the warehouse palette bin (a missing palette is
// a content warning), 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, 0, identification_string) // FUN_00444308 (owner_ID 0)
{
Check_Pointer(this);
//------------------------------------------------------------
// Intern a private copy of each palette name (the strings the
// interpreter hands in are transient parse scratch)
//------------------------------------------------------------
paletteName[0] = nameCopy(palette_name_a); // FUN_004700ac
paletteName[1] = nameCopy(palette_name_b);
twoColorMode = (Logical)(stricmp(paletteName[0], paletteName[1]) != 0);
//------------------------------------------------------------
// Reference each palette in the warehouse palette bin (held
// for the gauge's life); warn if the content is missing
//------------------------------------------------------------
L4Warehouse
*warehouse = (L4Warehouse *)renderer->warehousePointer;
Check(warehouse);
int
i;
for (i = 0; i < 2; ++i)
{
if (warehouse->palette8Bin.Get(paletteName[i]) == NULL) // FUN_00442f6a
{
DEBUG_STREAM << "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]
Check_Fpu();
}
//
// @004c38dc -- dtor. Release both palette refs (keyed on the names, so
// before the frees), free the interned names, then the base ~Gauge chain.
// (The deleting-destructor thunks @004c66ff / @004c6725 / @004c674b /
// @004c6771 forward here for the four derived ColorMappers.)
//
ColorMapper::~ColorMapper()
{
Check(this);
L4Warehouse
*warehouse = (L4Warehouse *)renderer->warehousePointer;
int
i;
for (i = 0; i < 2; ++i)
{
warehouse->palette8Bin.Release(paletteName[i]); // FUN_00443090
Unregister_Pointer(paletteName[i]);
delete[] paletteName[i];
paletteName[i] = NULL;
}
// base ~Gauge -- FUN_00444360 (releases the connections)
Check_Fpu();
}
//
// @004c395c -- 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 cannot skip the
// write (the surface may have been cleared/redrawn while inactive).
//
void
ColorMapper::BecameActive()
{
Check(this);
currentColorIndex = -1; // this[0x15] @0x54
previousColorIndex = -1; // this[0x16] @0x58
previousRed = previousGreen = previousBlue = 0xFF; // @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.
//
// The palette lookup is a PEEK of the already-loaded resource: under the
// 1995 warehouse API GetIfAlreadyExists() takes a reference, so it is paired
// with a Release() (net zero; the ctor's Get() keeps the palette resident).
//
void
ColorMapper::Execute()
{
Check(this);
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;
}
}
L4Warehouse
*warehouse = (L4Warehouse *)renderer->warehousePointer;
Palette8
*palette = warehouse->palette8Bin.GetIfAlreadyExists(paletteName[paletteToggle]);
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)
}
warehouse->palette8Bin.Release(paletteName[paletteToggle]);
}
Check_Fpu();
}
//###########################################################################
// 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; -1 = miss.)
//
Logical
ColorMapperArmor::Make(
int display_port_index,
Vector2DOf<int> /*position*/,
Entity *entity,
GaugeRenderer *gauge_renderer
)
{
ParameterDescription
*parameterList = methodDescription.parameterList;
Check(gauge_renderer);
Check_Pointer(entity);
int
zone_index = entity->GetDamageZoneIndex(CString(parameterList[5].data.string));
if (zone_index < 0)
{
DEBUG_STREAM << "ColorMapperArmor warning: damage zone "
<< parameterList[5].data.string << " not found\n";
}
# if DEBUG_LEVEL > 0
ColorMapperArmor
*gauge =
# endif
new ColorMapperArmor( // FUN_004c3b98 (0x70 bytes)
parameterList[0].data.rate,
parameterList[1].data.modeMask,
(L4GaugeRenderer *)gauge_renderer,
display_port_index, // graphics port number (the runtime port)
parameterList[2].data.color, // colour slot
entity,
parameterList[3].data.string, // palette A
parameterList[4].data.string, // palette B
zone_index,
"ColorMapperArmor");
Register_Object(gauge);
Check_Fpu();
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)
{
Check_Pointer(this);
unused = 0; // this[0x1B] @0x6C
GaugeConnection
*connection;
connection = new ArmorZoneConnection( // @004c33a4 (0x18 bytes)
&currentColorIndex, entity, damage_zone_index);
Check(connection);
Register_Object(connection);
AddConnection(connection);
Check_Fpu();
}
//
// 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" = empty slot)
//
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. BINARY QUIRK (kept, byte-faithful): the presence test is
// `index != 0`, so a valid zone 0 AND a -1 "unused" slot both count as
// "present" -- i.e. it always builds; the connection simply skips index < 0.
//
Logical
ColorMapperMultiArmor::Make(
int display_port_index,
Vector2DOf<int> /*position*/,
Entity *entity,
GaugeRenderer *gauge_renderer
)
{
ParameterDescription
*parameterList = methodDescription.parameterList;
Check(gauge_renderer);
Check_Pointer(entity);
int
zone_indices[8];
Logical
any = False;
int
i;
for (i = 0; i < 8; i++)
{
zone_indices[i] =
entity->GetDamageZoneIndex(CString(parameterList[5 + i].data.string));
if (zone_indices[i] != 0) // binary: iVar2 != 0 (quirk -- see note above)
{
any = True;
}
}
if (!any)
{
DEBUG_STREAM << "colorMapperMultiArmor: No Damage zones found\n";
return False;
}
# if DEBUG_LEVEL > 0
ColorMapperMultiArmor
*gauge =
# endif
new ColorMapperMultiArmor( // FUN_004c3d60 (0x6c bytes)
parameterList[0].data.rate,
parameterList[1].data.modeMask,
(L4GaugeRenderer *)gauge_renderer,
display_port_index, // graphics port number
parameterList[2].data.color, // colour slot
entity,
parameterList[3].data.string, // palette A
parameterList[4].data.string, // palette B
zone_indices,
"ColorMapperMultiArmor");
Register_Object(gauge);
Check_Fpu();
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)
{
Check_Pointer(this);
GaugeConnection
*connection;
connection = new MultiArmorConnection( // @004c346c (0x3c bytes)
&currentColorIndex, entity, zone_indices);
Check(connection);
Register_Object(connection);
AddConnection(connection);
Check_Fpu();
}
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
*parameterList = methodDescription.parameterList;
Check(gauge_renderer);
# if DEBUG_LEVEL > 0
ColorMapperCritical
*gauge =
# endif
new ColorMapperCritical( // FUN_004c3e40 (0x6c bytes)
parameterList[0].data.rate,
parameterList[1].data.modeMask,
(L4GaugeRenderer *)gauge_renderer,
display_port_index, // graphics port number
parameterList[2].data.color, // colour slot
entity,
parameterList[3].data.string, // palette A
parameterList[4].data.string, // palette B
parameterList[5].data.string, // subsystem name
"ColorMapperCritical");
Register_Object(gauge);
Check_Fpu();
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)
{
Check_Pointer(this);
Subsystem
*subsystem = entity->FindSubsystem(subsystem_name); // FUN_0041f98c
if (subsystem == NULL)
{
DEBUG_STREAM << "ColorMapperCritical warning: subsystem "
<< subsystem_name << " does not exist\n";
}
GaugeConnection
*connection;
connection = new CriticalConnection( // @004c3598 (0x18 bytes)
&currentColorIndex, subsystem);
Check(connection);
Register_Object(connection);
AddConnection(connection);
Check_Fpu();
}
ColorMapperCritical::~ColorMapperCritical()
{
// base-dtor chain (~ColorMapper -> ~Gauge) releases the connection + palettes.
}
//###########################################################################
// ColorMapperHeat @004c3f08 Make / @004c3f6c ctor *** ANCHOR ***
//###########################################################################
//
// The gauge's configured parameters come LIVE from the config interpreter
// (the CFG "cmHeat" primitive) via methodDescription.parameterList -- the
// interpreter Restore()s each instance's parsed params into this scratch
// list before calling Make (the engine pattern, cf. NumericDisplayScalar::
// Make, L4GAUGE.CPP:571). 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
}
};
//
// @004c3f08 -- Make. Allocate a ColorMapperHeat (0x6c bytes in the shipped
// object) and construct it from the parsed parameters and the class name
// "ColorMapperHeat" (string pool 0x5184c4).
//
Logical
ColorMapperHeat::Make(
int display_port_index,
Vector2DOf<int> /*position*/,
Entity *entity,
GaugeRenderer *gauge_renderer
)
{
ParameterDescription
*parameterList = methodDescription.parameterList;
Check(gauge_renderer);
# if DEBUG_LEVEL > 0
ColorMapperHeat
*gauge =
# endif
new ColorMapperHeat( // FUN_004c3f6c
parameterList[0].data.rate, // rate ID
parameterList[1].data.modeMask, // mode mask
(L4GaugeRenderer *)gauge_renderer,
display_port_index, // graphics port number (the runtime port)
parameterList[2].data.color, // colour slot
entity,
parameterList[3].data.string, // palette name A
parameterList[4].data.string, // palette name B
parameterList[5].data.string, // heat subsystem name (e.g. "GeneratorA")
"ColorMapperHeat");
Register_Object(gauge);
Check_Fpu();
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)
{
Check_Pointer(this);
Subsystem
*subsystem = entity->FindSubsystem(heat_subsystem_name); // FUN_0041f98c
if (subsystem == NULL)
{
DEBUG_STREAM << "ColorMapperHeat warning: subsystem "
<< heat_subsystem_name << " does not exist\n";
}
else
{
//
// The subsystem MUST be heat-bearing (one of the two heat class
// derivation tables: 0x50e3ec HeatableSubsystem, or the disjoint
// 0x50e604 HeatWatcher family -- ammo bins / torso carry a mirrored
// heat alarm) or the data feed is meaningless.
//
if (!subsystem->IsDerivedFrom(HeatableSubsystem::ClassDerivations) && // FUN_0041a1a4
!subsystem->IsDerivedFrom(HeatWatcher::ClassDerivations))
{
Verify(False); // "Bad subsystem type" -- BTL4GAUG.CPP line 0x68a
}
}
//
// HeatConnection: copies the subsystem's live temperature into
// ColorMapper::currentColorIndex every frame (100 when the subsystem is
// absent or destroyed; Execute clamps to [0,255]).
//
GaugeConnection
*connection;
connection = new HeatConnection( // FUN_004c3664 (0x1c bytes)
&currentColorIndex, subsystem);
Check(connection);
Register_Object(connection);
AddConnection(connection);
Check_Fpu();
}
//
// 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 across a destination rectangle, clipping the last
// partial row/column. For each tile it MoveTo()s and issues a clipped blit.
// The tile size is read from the bitmap (Data.Size).
//
static void
DrawTiledBitmap(
GraphicsView *view,
int originX,
int originY,
int width,
int height,
int /*color -- unused (no colour arg on the blit)*/,
BitMap *tile
)
{
Check_Pointer(view);
Check_Pointer(tile);
int
tileW = tile->Data.Size.x,
tileH = tile->Data.Size.y,
x,
y,
rowH,
colW;
for (y = 0; y < height; y += tileH)
{
rowH = height - y;
if (rowH > tileH)
{
rowH = tileH;
}
if (rowH <= 0)
{
continue;
}
for (x = 0; x < width; x += tileW)
{
colW = width - x;
if (colW > tileW)
{
colW = tileW;
}
if (colW <= 0)
{
continue;
}
view->MoveToAbsolute(x, y);
view->DrawBitMap(0, tile,
originX, originY,
originX + colW - 1, originY + rowH - 1);
}
}
Check_Fpu();
}
//###########################################################################
// HorizTwoPartBar @004c4170 ctor / @004c4324 BecameActive
// @004c4340 Execute (vtable PTR_FUN_00518cbc)
//
// A horizontal two-part fill bar, built directly by the btl4gau2 cluster
// panels (no config keyword). Three Scalar connections drive it (value,
// low/warn, high/max); the bar grows left->right.
//###########################################################################
//
// @004c4170 -- ctor. GraphicGauge base, intern the tile bitmap + hold a
// ref, set the view extent, store fill/bg + width/height, wire the value/
// low/high Scalar connections.
//
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, // FUN_00444818 (owner_ID 0)
graphics_port_number, identification_string)
{
Check_Pointer(this);
tileImage = nameCopy(tile_image); // @0x90
((L4Warehouse *)renderer_in->warehousePointer)->
bitMapBin.Get(tileImage); // held for the gauge's life
localView.SetPositionWithinPort(left, bottom, right, top); // this+0x48
fillColor = fill_color; // @0xA0
backgroundColor = background_color; // @0xA4
width = right - left; // @0xA8
height = (top - bottom) - 1; // @0xAC
GaugeConnection
*connection;
connection = new GaugeConnectionDirectOf<Scalar>(0, &value, value_pointer); // @0x90 value
Check(connection);
Register_Object(connection);
AddConnection(connection);
connection = new GaugeConnectionDirectOf<Scalar>(0, &low, low_pointer); // @0x94
Check(connection);
Register_Object(connection);
AddConnection(connection);
connection = new GaugeConnectionDirectOf<Scalar>(0, &high, high_pointer); // @0x98
Check(connection);
Register_Object(connection);
AddConnection(connection);
Check_Fpu();
}
//
// dtor. Release the tile ref (keyed on the name, before the free); the
// base chain releases the connections.
//
HorizTwoPartBar::~HorizTwoPartBar()
{
Check(this);
((L4Warehouse *)renderer->warehousePointer)->
bitMapBin.Release(tileImage);
Unregister_Pointer(tileImage);
delete[] tileImage;
tileImage = NULL;
Check_Fpu();
}
Logical
HorizTwoPartBar::TestInstance() const
{
return GraphicGauge::TestInstance();
}
//
// @004c4324 -- BecameActive: force a full repaint on the first Execute
// (previousFull=0, previousFill=width -- neither can match a real pair).
//
void
HorizTwoPartBar::BecameActive()
{
Check(this);
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:
// [0, warnPix) the striped TILE pattern @004c4482
// [warnPix, valPix) fillColor solid (when value > low) @004c449d
// [valPix, width) backgroundColor solid @004c44da
//
// ZONE-COLOUR MAP (verified against the binary disasm; a transcription with
// the two colours swapped rendered the whole remainder in the fill colour):
// the SetColor before the tile AND the zone-2 fill use fillColor ([0xA0]);
// only zone 3 (the unfilled remainder) uses backgroundColor ([0xA4]).
//
void
HorizTwoPartBar::Execute()
{
Check(this);
if (value < ZeroClamp) // @004c434c fcomp 0.0
{
value = 0.0f;
}
else if (value > high) // @004c4367 fcomp high
{
value = high;
}
int
warnPix = (int)((Scalar)width * low / high + 0.5f), // low/warn threshold pixel
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.
//
L4Warehouse
*warehouse = (L4Warehouse *)renderer->warehousePointer;
BitMap
*tile = warehouse->bitMapBin.Get(tileImage);
localView.SetColor(fillColor); // @004c443a ([0xA0])
DrawTiledBitmap(&localView, 0, 0, warnPix - 1, height, backgroundColor, tile);
warehouse->bitMapBin.Release(tileImage);
//
// Zone 2 [warnPix, valPix): the over-threshold fill (the binary
// keeps the current colour, still fillColor from zone 1) -- only
// when value > low.
//
if (valPix > warnPix)
{
localView.SetColor(fillColor);
localView.MoveToAbsolute(warnPix, 0);
localView.DrawFilledRectangleToAbsolute(valPix - 1, height);
}
//
// Zone 3 [valPix, width): the unfilled remainder -- backgroundColor.
//
if (valPix < width)
{
localView.SetColor(backgroundColor); // @004c44da ([0xA4])
localView.MoveToAbsolute(valPix, 0);
localView.DrawFilledRectangleToAbsolute(width, height);
}
previousFull = warnPix;
previousFill = valPix;
}
Check_Fpu();
}
//###########################################################################
// 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).
//###########################################################################
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. 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
*parameterList = methodDescription.parameterList;
Check(gauge_renderer);
# if DEBUG_LEVEL > 0
VertTwoPartBar
*gauge =
# endif
new VertTwoPartBar( // FUN_004c4724
parameterList[0].data.rate,
parameterList[1].data.modeMask,
(L4GaugeRenderer *)gauge_renderer,
display_port_index, // graphics_port_number
position.x, position.y, // left, bottom
position.x + parameterList[2].data.vector.x, // right = x + width
position.y + parameterList[2].data.vector.y, // top = y + height
parameterList[3].data.string, // tile bitmap
parameterList[4].data.color, // backgroundColor
parameterList[5].data.color, // fillColor
parameterList[6].data.color, // extraColor
(Scalar *)parameterList[7].data.attributePointer, // current value
(Scalar *)parameterList[8].data.attributePointer, // warn threshold
(Scalar *)parameterList[9].data.attributePointer, // max
// HACK!!!! The (Scalar *) is VERY dangerous!
"VertTwoPartBar");
Register_Object(gauge);
//--------------------------------------------------------
// Make sure the tile was properly placed in the warehouse
//--------------------------------------------------------
L4Warehouse
*warehouse = (L4Warehouse *)gauge_renderer->warehousePointer;
Check(warehouse);
if (warehouse->bitMapBin.Get(parameterList[3].data.string) == NULL)
{
DEBUG_STREAM << "VertTwoPartBarNormalized: Missing image '"
<< parameterList[3].data.string << "'\n";
return False;
}
warehouse->bitMapBin.Release(parameterList[3].data.string);
Check_Fpu();
return True;
}
//
// @004c4724 -- ctor. GraphicGauge base; intern + ref-count the tile bitmap;
// set the view 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)
{
Check_Pointer(this);
//
// 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 = nameCopy(tile_image); // @0x90
((L4Warehouse *)renderer_in->warehousePointer)->
bitMapBin.Get(tileImage);
localView.SetPositionWithinPort(left, bottom, right, top); // this+0x48
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]
GaugeConnection
*connection;
connection = new GaugeConnectionDirectOf<Scalar>(0, &value, value_pointer); // @0xB0
Check(connection);
Register_Object(connection);
AddConnection(connection);
connection = new GaugeConnectionDirectOf<Scalar>(0, &low, low_pointer); // @0xB4
Check(connection);
Register_Object(connection);
AddConnection(connection);
connection = new GaugeConnectionDirectOf<Scalar>(0, &high, high_pointer); // @0xB8
Check(connection);
Register_Object(connection);
AddConnection(connection);
Check_Fpu();
}
//
// @004c486c -- dtor. Release the tile ref (keyed on tileImage, before the
// free), free the interned name; the base chain runs implicitly.
//
VertTwoPartBar::~VertTwoPartBar()
{
Check(this);
((L4Warehouse *)renderer->warehousePointer)->
bitMapBin.Release(tileImage);
Unregister_Pointer(tileImage);
delete[] tileImage;
tileImage = NULL;
Check_Fpu();
}
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 pair).
//
void
VertTwoPartBar::BecameActive()
{
Check(this);
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.)
//
void
VertTwoPartBar::Execute()
{
Check(this);
// clamp value to [0, high] (@004c4908 fcomp 0.0 ; @004c4929 fcomp high)
if (value < ZeroClamp)
{
value = 0.0f;
}
else if (value > high)
{
value = high;
}
int
warnPix = (int)((Scalar)height * low / high + 0.5f), // warn threshold pixel
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);
localView.SetColor(backgroundColor);
DrawTiledBitmap(&localView, 0, 0, width, warnPix - 1, extraColor, tile);
warehouse->bitMapBin.Release(tileImage);
if (warnPix < valPix) // normal fill above the warn line
{
localView.SetColor(fillColor);
localView.MoveToAbsolute(0, warnPix);
localView.DrawFilledRectangleToAbsolute(width, valPix - 1);
}
if (valPix < height) // empty region above the value
{
localView.SetColor(extraColor);
localView.MoveToAbsolute(0, valPix);
localView.DrawFilledRectangleToAbsolute(width, height);
}
previousFull = warnPix;
previousFill = valPix;
}
Check_Fpu();
}
//###########################################################################
// VertNormalSlider @004c4b08 Make / @004c4b84 ctor / @004c4cc0 Execute
// (vtable PTR_FUN_00518c34)
//
// The condenser VALVE slider (config keyword "vertNormalSlider", the @2
// widget in GenericHeatGauges1/2). 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, [0,1])
PARAMETER_DESCRIPTION_END
}
};
//
// @004c4b08 -- Make. No owned bitmap, so no image-exists check; always
// returns True (the binary returns 1 even on allocation failure). `entity`
// unused (binds by the attribute pointer already resolved into
// parameterList[6]).
//
Logical
VertNormalSlider::Make(
int display_port_index,
Vector2DOf<int> position,
Entity * /*entity -- unused*/,
GaugeRenderer *gauge_renderer
)
{
ParameterDescription
*parameterList = methodDescription.parameterList;
Check(gauge_renderer);
# if DEBUG_LEVEL > 0
VertNormalSlider
*gauge =
# endif
new VertNormalSlider( // FUN_004c4b84
parameterList[0].data.rate,
parameterList[1].data.modeMask,
(L4GaugeRenderer *)gauge_renderer,
display_port_index, // graphics_port_number
position.x, position.y, // left, bottom
position.x + parameterList[2].data.vector.x, // right
position.y + parameterList[2].data.vector.y, // top
parameterList[3].data.color, // fill / foreground colour
parameterList[4].data.color, // background colour
parameterList[5].data.integer, // baseline (indicator thickness)
(Scalar *)parameterList[6].data.attributePointer, // Condenser/ValveSetting
"VertNormalSlider");
Register_Object(gauge);
Check_Fpu();
return True;
}
//
// @004c4b84 -- ctor (vtable PTR_FUN_00518c34). GraphicGauge base; set the
// view 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 set here --
// BecameActive sets it to -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)
{
Check_Pointer(this);
localView.SetPositionWithinPort(left, bottom, right, top);
localView.SetOperation(GraphicsDisplay::Xor); // op == 3
localView.SetColor(fill_color);
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
GaugeConnection
*connection;
connection = new GaugeConnectionDirectOf<Scalar>(0, &value, value_pointer); // -> @0xAC
Check(connection);
Register_Object(connection);
AddConnection(connection);
Check_Fpu();
}
//
// @004c4c68 -- dtor. Owns no resource; the connection + localView are
// released by the base teardown, so the body is empty.
//
VertNormalSlider::~VertNormalSlider()
{
}
//
// @004c4c94 -- TestInstance. Out-of-line forward to the base.
//
Logical
VertNormalSlider::TestInstance() const
{
return GraphicGauge::TestInstance();
}
//
// @004c4cac -- BecameActive. Invalidate the cached row so the first
// Execute repaints.
//
void
VertNormalSlider::BecameActive()
{
Check(this);
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()
{
Check(this);
if (value < ZeroClamp) // _DAT_004c4d3c
{
value = 0.0f;
}
else if (value > OneClamp) // _DAT_004c4d40
{
value = 1.0f;
}
int
pixel = Round((Scalar)span * value); // FUN_004dcd94
if (pixel != previousFill)
{
Draw(); // erase old (XOR at old previousFill)
previousFill = pixel;
Draw(); // draw new (XOR at new previousFill)
}
Check_Fpu();
}
//
// @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);
localView.DrawFilledRectangleToRelative(width, baseline);
}
}
//###########################################################################
//###########################################################################
// OneOfSeveral family -- multi-frame bitmap selectors
//###########################################################################
//###########################################################################
//
// @004c4d88 -- OneOfSeveral base ctor (vtable PTR_FUN_00518bf0). Stores the
// strip-vs-pixmap flag, interns the image name + holds a ref in the matching
// warehouse bin (pixelMap8Bin when !fromImageStrip, else bitMapBin),
// computes the per-frame width/height by dividing the source size by the
// column/row counts, and sets the view 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)
{
Check_Pointer(this);
backgroundColor = background_color; // @0x98
foregroundColor = foreground_color; // @0x9C
columns = columns_in; // @0xA0
fromImageStrip = from_image_strip; // @0x90
imageName = nameCopy(image_name); // @0x94
L4Warehouse
*warehouse = (L4Warehouse *)renderer_in->warehousePointer;
if (fromImageStrip == 0) // PixelMap8 strip
{
PixelMap8
*pixel_map = warehouse->pixelMap8Bin.Get(imageName); // held
if (pixel_map == NULL)
{
frameWidth = 0;
frameHeight = 0;
}
else
{
frameWidth = pixel_map->Data.Size.x / columns_in; // @0xA4
frameHeight = pixel_map->Data.Size.y / rows_in; // @0xA8
}
}
else // BitMap strip
{
BitMap
*bit_map = warehouse->bitMapBin.Get(imageName); // held
if (bit_map == NULL)
{
frameWidth = 0;
frameHeight = 0;
}
else
{
frameWidth = bit_map->Data.Size.x / columns_in;
frameHeight = bit_map->Data.Size.y / rows_in;
}
}
localView.SetOrigin(x, y); // this+0x48
Check_Fpu();
}
//
// @004c4e7c -- dtor. Release the image from the matching bin (keyed on
// imageName, before the free); base chain implicit.
//
OneOfSeveral::~OneOfSeveral()
{
Check(this);
L4Warehouse
*warehouse = (L4Warehouse *)renderer->warehousePointer;
if (fromImageStrip == 0)
{
warehouse->pixelMap8Bin.Release(imageName);
}
else
{
warehouse->bitMapBin.Release(imageName);
}
Unregister_Pointer(imageName);
delete[] imageName;
imageName = NULL;
Check_Fpu();
}
Logical
OneOfSeveral::TestInstance() const
{
return GraphicGauge::TestInstance();
}
//
// @004c4f14 -- BecameActive: previousSelected = -1 (force the first redraw).
//
void
OneOfSeveral::BecameActive()
{
Check(this);
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()
{
Check(this);
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,
sx1 = frameWidth + sx0 - 1,
sy0 = row * frameHeight,
sy1 = frameHeight + sy0 - 1;
L4Warehouse
*warehouse = (L4Warehouse *)renderer->warehousePointer;
if (fromImageStrip == 0) // PixelMap8: own palette, no SetColor
{
PixelMap8
*pixel_map = warehouse->pixelMap8Bin.Get(imageName);
localView.DrawPixelMap8(True, 0, pixel_map, sx0, sy0, sx1, sy1);
warehouse->pixelMap8Bin.Release(imageName);
}
else // BitMap strip
{
localView.SetColor(backgroundColor);
BitMap
*bit_map = warehouse->bitMapBin.Get(imageName);
localView.DrawBitMapOpaque(foregroundColor, 0, bit_map, sx0, sy0, sx1, sy1);
warehouse->bitMapBin.Release(imageName);
}
Check_Fpu();
}
//###########################################################################
// OneOfSeveralInt @004c5148 ctor / @004c51d8 dtor
// (vtable PTR_FUN_00518bac; cluster-built)
//
// OneOfSeveral(fromImageStrip=True) + one GaugeConnectionDirectOf<int>
// feeding the selected frame.
//###########################################################################
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)
{
Check_Pointer(this);
selected = 0;
GaugeConnection
*connection;
connection = new GaugeConnectionDirectOf<int>(0, &selected, value_pointer); // FUN_004749de
Check(connection);
Register_Object(connection);
AddConnection(connection);
Check_Fpu();
}
OneOfSeveralInt::~OneOfSeveralInt() // @004c51d8
{
}
//###########################################################################
// OneOfSeveralPixInt @004c5204 Make / @004c52d8 ctor
// (vtable PTR_FUN_00518b68)
//
// The cockpit button-state lamps (config "oneOfSeveralPixInt"): a PixelMap8
// 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. Construct, then verify the pixmap strip exists.
//
Logical
OneOfSeveralPixInt::Make(
int display_port_index,
Vector2DOf<int> position,
Entity * /*entity -- unused*/,
GaugeRenderer *gauge_renderer
)
{
ParameterDescription
*parameterList = methodDescription.parameterList;
Check(gauge_renderer);
# if DEBUG_LEVEL > 0
OneOfSeveralPixInt
*gauge =
# endif
new OneOfSeveralPixInt( // FUN_004c52d8
parameterList[0].data.rate,
parameterList[1].data.modeMask,
(L4GaugeRenderer *)gauge_renderer,
display_port_index, // graphics_port_number
position.x, position.y,
parameterList[2].data.string, // pixmap strip
parameterList[3].data.integer, // columns
parameterList[4].data.integer, // rows
(int *)parameterList[5].data.attributePointer, // integer state source
"OneOfSeveralPixInt");
Register_Object(gauge);
L4Warehouse
*warehouse = (L4Warehouse *)gauge_renderer->warehousePointer;
if (warehouse->pixelMap8Bin.Get(parameterList[2].data.string) == NULL)
{
DEBUG_STREAM << "OneOfSeveralPixInt: Missing image '"
<< parameterList[2].data.string << "'\n";
return False;
}
warehouse->pixelMap8Bin.Release(parameterList[2].data.string);
Check_Fpu();
return True;
}
//
// @004c52d8 -- ctor. OneOfSeveral base with fromImageStrip=False (PixelMap8)
// 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)
{
Check_Pointer(this);
selected = 0; // @0xAC this[0x2B]
GaugeConnection
*connection;
connection = new GaugeConnectionDirectOf<int>(0, &selected, value_pointer); // FUN_004749de
Check(connection);
Register_Object(connection);
AddConnection(connection);
Check_Fpu();
}
//
// @004c5364 -- deleting-dtor thunk. All teardown is ~OneOfSeveral; implicit.
//
OneOfSeveralPixInt::~OneOfSeveralPixInt()
{
}
//###########################################################################
// OneOfSeveralStates @004c5470 ctor / @004c5500 dtor / @004c552c
// BecameActive (vtable PTR_FUN_00518b24; cluster-built)
//
// OneOfSeveral + a StateConnection reading the subsystem's simulation state.
//###########################################################################
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,
Subsystem *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)
{
Check_Pointer(this);
selected = 0;
GaugeConnection
*connection;
connection = new StateConnection(&selected, subsystem_source); // @004c3324
Check(connection);
Register_Object(connection);
AddConnection(connection);
Check_Fpu();
}
OneOfSeveralStates::~OneOfSeveralStates() // @004c5500
{
}
//
// @004c552c -- BecameActive: clamp the state >= 0, then the base
// invalidation (force the first redraw).
//
void
OneOfSeveralStates::BecameActive()
{
Check(this);
if (selected < 0)
{
selected = 0;
}
OneOfSeveral::BecameActive();
}
//###########################################################################
//###########################################################################
// 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. Construct the gauge, then probe that the glyph font
// exists (== NumericDisplayScalar::Make, L4GAUGE.CPP:626-650); the readout
// cannot draw without it.
//
Logical
HeadingPointer::Make(
int display_port_index,
Vector2DOf<int> position,
Entity * /*entity -- unused*/,
GaugeRenderer *gauge_renderer
)
{
ParameterDescription
*parameterList = methodDescription.parameterList;
Check(gauge_renderer);
# if DEBUG_LEVEL > 0
HeadingPointer
*gauge =
# endif
new HeadingPointer( // FUN_004c562c
parameterList[0].data.rate,
parameterList[1].data.modeMask,
(L4GaugeRenderer *)gauge_renderer,
0, // owner_ID
display_port_index, // graphics port number
position.x, position.y,
parameterList[2].data.color, // needle colour
parameterList[3].data.color, // numeric fg colour
parameterList[4].data.color, // erase/bg colour
parameterList[5].data.integer, // inner radius
parameterList[6].data.integer, // outer radius (tip)
parameterList[7].data.string, // font name
"HeadingPointer");
Register_Object(gauge);
L4Warehouse
*warehouse = (L4Warehouse *)gauge_renderer->warehousePointer;
if (warehouse->bitMapBin.Get(parameterList[7].data.string) == NULL)
{
DEBUG_STREAM << "HeadingPointer: missing font '"
<< parameterList[7].data.string << "'\n";
return False;
}
warehouse->bitMapBin.Release(parameterList[7].data.string);
Check_Fpu();
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 view 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)
{
Check_Pointer(this);
localView.SetOrigin(x, y); // this+0x48
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).
//
imageName = nameCopy(font_name); // @0xA0
L4Warehouse
*warehouse = (L4Warehouse *)renderer_in->warehousePointer;
BitMap
*font = warehouse->bitMapBin.Get(imageName); // held for the gauge's life
int
fontW = (font != NULL) ? font->Data.Size.x : 0,
fontH = (font != NULL) ? font->Data.Size.y : 0;
// 14 == NumericDisplay::totalDigitsPerFont; centre the 3-digit readout
int
halfW = ((fontW / NumericDisplay::totalDigitsPerFont) * 3) / 2,
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
Register_Object(numericDisplay);
Check_Fpu();
}
//
// @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()
{
Check(this);
((L4Warehouse *)renderer->warehousePointer)->
bitMapBin.Release(imageName); // drop the ctor's Get ref
Unregister_Pointer(imageName);
delete[] imageName;
imageName = NULL;
Unregister_Object(numericDisplay);
delete numericDisplay; // FUN_0047018c
numericDisplay = NULL;
Check_Fpu();
}
Logical
HeadingPointer::TestInstance() const
{
return GraphicGauge::TestInstance();
}
//
// @004c57d0 -- ShowInstance (debug dump; bgColor printed first, per the
// recovered order).
//
void
HeadingPointer::ShowInstance(char *indent)
{
Check(this);
cout << indent << "HeadingPointer:\n";
char
temp[80];
Str_Copy(temp, indent, 80);
Str_Cat(temp, "...", 80);
cout << temp << "bgColor=" << eraseColor << "\n"; // @0x94
cout << temp << "fgColor=" << needleColor << "\n"; // @0x90
GraphicGauge::ShowInstance(temp);
Check_Fpu();
}
//
// @004c58e8 -- BecameActive: force a full redraw + reset the readout.
//
void
HeadingPointer::BecameActive()
{
Check(this);
previousX = -999; // 0xfffffc19 -- guaranteed != any real endpoint
previousY = -999;
numericDisplay->ForceUpdate(); // FUN_004703f4
}
//
// @004c5914 -- Execute. Read the linked 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`.
//
// HEADING DECOMPOSITION (deviation on record): the binary read an
// EulerAngles component (FUN_0040954c), but the 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 and
// euler.yaw jumps discontinuously (the needle spins erratically).
// YawPitchRoll (ROTATION.HPP:162) applies yaw FIRST, so its .yaw is the
// clean, continuous heading with pitch=roll~0. Kept per the decode
// worksheet (correctness-load-bearing).
//
void
HeadingPointer::Execute()
{
Check(this);
Entity
*owner = renderer->GetLinkedEntity(); // the renderer's entity socket
if (owner == NULL)
{
return;
}
YawPitchRoll
ypr;
ypr = owner->localOrigin.angularPosition; // 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),
ay = (int)((Scalar)innerRadius * sc.cosine + 0.5f),
bx = (int)((Scalar)outerRadius * sc.sine + 0.5f),
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); // erase the last needle
previousDrawing.Clear();
localView.AttachRecorder(&previousDrawing); // record the new strokes
localView.SetColor(needleColor);
localView.MoveToAbsolute(ax, ay);
localView.DrawThickLineToAbsolute(bx, by);
localView.DetachRecorder();
}
// Numeric heading readout: whole degrees, normalized, shown as (360-deg).
int
deg = (int)((Scalar)ypr.yaw * RadiansToDegrees + 0.5f); // 0x42652ee1
if ((Scalar)deg < ZeroClamp) // _DAT_004c5acc
{
deg = (int)((Scalar)deg + 360.0f);
}
int
shown = (int)(360.0f - (Scalar)deg);
numericDisplay->Draw(&localView, (Scalar)shown); // FUN_00470430
Check_Fpu();
}
//###########################################################################
//###########################################################################
// BitMap wipe gauges
//###########################################################################
//###########################################################################
//
// NOT RECONSTRUCTED (notes only, from the same address range):
// @004c5e84 ctor / @004c5f30 dtor / @004c5fa4 BecameActive / @004c5fb8
// Execute -- a second wipe base (vtable PTR_FUN_00518a58): a two-segment
// reveal of one strip image; frame size read from the bitmap.
// @004c61c8 ctor (vtable PTR_FUN_00518a14) -- its Scalar-driven variant
// ("BitMapInverseWipeScalar"): chains @004c5e84 then AddConnection(new
// GaugeConnectionDirectOf<Scalar>). Deleting-dtor thunk @004c66d9.
// btl4gau2's BallisticWeaponCluster ejectWipe needs the Scalar variant --
// still an open bring-up NULL there.
//
//###########################################################################
// BitMapInverseWipe @004c5b7c ctor / @004c5c80 dtor / @004c5cf4
// BecameActive / @004c5d08 Execute
// (vtable PTR_FUN_00518a9c; name unconfirmed)
//
// The coolant LeakGauge (config keyword "LeakGauge"): a 3-frame strip
// (frameWidth = imageWidth/3) revealed in steps. CFG shape
// (L4GAUGE.CFG:4858):
// LeakGauge(rate,mode,image.pcc,colorA,colorB,frames,third,levelAttr)
// value source @6 = Condenser/CoolantMassLeakRate.
//###########################################################################
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 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
*parameterList = methodDescription.parameterList;
Check(gauge_renderer);
//
// NOTE the ctor lists `third` BEFORE `frames`, so map parameter 6 ->
// third and parameter 5 -> frames BY NAME (parse-order != arg-order).
//
# if DEBUG_LEVEL > 0
BitMapInverseWipe
*gauge =
# endif
new BitMapInverseWipe( // FUN_004c5b7c
parameterList[0].data.rate,
parameterList[1].data.modeMask,
(L4GaugeRenderer *)gauge_renderer,
display_port_index, // graphics_port_number
position.x, position.y, // -> localView.SetOrigin(x,y)
parameterList[2].data.string, // image (eleak.pcc)
parameterList[3].data.color, // color_a
parameterList[4].data.color, // color_b
parameterList[6].data.integer, // third (.15 raw bits; inert) -> @0xB0
parameterList[5].data.integer, // frames (3)
(Scalar *)parameterList[7].data.attributePointer, // level source
"LeakGauge");
Register_Object(gauge);
L4Warehouse
*warehouse = (L4Warehouse *)gauge_renderer->warehousePointer;
if (warehouse->bitMapBin.Get(parameterList[2].data.string) == NULL)
{
DEBUG_STREAM << "LeakGauge: Missing image '"
<< parameterList[2].data.string << "'\n";
return False;
}
warehouse->bitMapBin.Release(parameterList[2].data.string);
Check_Fpu();
return True;
}
//
// @004c5b7c -- ctor (vtable PTR_FUN_00518a9c): GraphicGauge base;
// SetOrigin(x,y), intern the image + hold a ref, store colours + frame
// geometry (frameWidth = imageWidth/3, fullWidth = frames*2), wire a Scalar
// level connection.
//
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_in,
int frames_in,
Scalar *value_pointer,
const char *identification_string
):
GraphicGauge(rate, mode_mask, renderer_in, 0,
graphics_port_number, identification_string)
{
Check_Pointer(this);
localView.SetOrigin(x, y);
imageName = nameCopy(image); // @0x90
colorA = color_a; // @0x94
colorB = color_b; // @0x98
frames = frames_in; // @0xA0
fullWidth = frames_in * 2; // @0x9C
third = third_in; // @0xB0
BitMap
*bit_map = ((L4Warehouse *)renderer_in->warehousePointer)->
bitMapBin.Get(imageName); // held
if (bit_map == NULL)
{
frameWidth = 0;
frameHeight = 0;
}
else
{
frameWidth = bit_map->Data.Size.x / 3; // @0xA8
frameHeight = bit_map->Data.Size.y; // @0xA4
}
GaugeConnection
*connection;
connection = new GaugeConnectionDirectOf<Scalar>(0, &value, value_pointer); // FUN_00474855
Check(connection);
Register_Object(connection);
AddConnection(connection);
Check_Fpu();
}
BitMapInverseWipe::~BitMapInverseWipe() // @004c5c80
{
Check(this);
((L4Warehouse *)renderer->warehousePointer)->
bitMapBin.Release(imageName);
Unregister_Pointer(imageName);
delete[] imageName;
imageName = NULL;
Check_Fpu();
}
Logical
BitMapInverseWipe::TestInstance() const
{
return GraphicGauge::TestInstance();
}
void
BitMapInverseWipe::BecameActive() // @004c5cf4
{
Check(this);
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()
{
Check(this);
int
level = Round(value); // FUN_004dcd94
if (level < 0)
{
level = 0;
}
if (level > fullWidth)
{
level = fullWidth;
}
if (value > TraceLeakLevel && level < 1) // _DAT_0050e3d8 (0.0025)
{
level = 1;
}
if (level != previousLevel)
{
previousLevel = level;
L4Warehouse
*warehouse = (L4Warehouse *)renderer->warehousePointer;
BitMap
*bit_map = warehouse->bitMapBin.Get(imageName);
if (bit_map != NULL)
{
int
y = -frameHeight,
frameIndex,
seg;
for (seg = frames; seg > 0; seg--)
{
localView.MoveToAbsolute(0, y);
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, bit_map, frameIndex, 0,
frameWidth + frameIndex, frameHeight);
level -= 2;
y -= frameHeight;
}
}
warehouse->bitMapBin.Release(imageName);
}
Check_Fpu();
}
//###########################################################################
//###########################################################################
// SegmentArc gauges
//###########################################################################
//###########################################################################
//###########################################################################
// SegmentArc270 @004c6244 ctor (vtable PTR_FUN_005189d0; cluster-built)
//
// 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 (@00474300 -- draws the lit segments up to
// currentValue). Used as the WeaponCluster recharge dial. Deleting-dtor
// thunk @004c66b3 -> the arc base dtor (FUN_00474094).
//###########################################################################
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, // FUN_00473f44
center_x, center_y, inner0, outer0, inner1, outer1, deg0, deg1,
number_of_segs, background_color, foreground_color, use_thick_lines,
identification_string)
{
Check_Pointer(this);
GaugeConnection
*connection;
connection = new GaugeConnectionDirectOf<Scalar>(0, &currentValue, value_pointer);
Check(connection);
Register_Object(connection);
AddConnection(connection);
// |n|/(|n|-1) is INTEGER division (== 1 for n>=2), so this is 0.75.
int
n = (number_of_segs < 0) ? -number_of_segs : number_of_segs;
segmentSpan = (Scalar)(n / (n - 1)) * 0.75f; // _DAT_004c62d4
Check_Fpu();
}
//
// Execute -- scale the raw fraction by the 270-degree span, draw, then
// put the raw value back. Restoring matters: the connection only
// rewrites currentValue when its source changes, so scaling in place
// would compound frame after frame and walk the dial to zero.
//
void
SegmentArc270::Execute()
{
Check(this);
Scalar
raw = currentValue;
currentValue = raw * segmentSpan;
SegmentArc::Execute();
currentValue = raw;
Check_Fpu();
}
SegmentArc270::~SegmentArc270() // @004c66b3 (base chain)
{
}
//###########################################################################
// 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. 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. No resource to verify (returns True unconditionally,
// as the binary does).
//
Logical
SegmentArcRatio::Make(
int display_port_index,
Vector2DOf<int> position,
Entity * /*entity -- unused*/,
GaugeRenderer *gauge_renderer
)
{
ParameterDescription
*parameterList = methodDescription.parameterList;
Check(gauge_renderer);
# if DEBUG_LEVEL > 0
SegmentArcRatio
*gauge =
# endif
new SegmentArcRatio( // FUN_004c6394
parameterList[0].data.rate,
parameterList[1].data.modeMask,
(L4GaugeRenderer *)gauge_renderer,
display_port_index, // graphics_port_number
position.x, position.y, // centre
parameterList[2].data.scalar, // inner radius
parameterList[3].data.scalar, // outer radius
parameterList[4].data.scalar, // start angle
parameterList[5].data.scalar, // end angle
parameterList[6].data.integer, // segment count (+dir)
parameterList[7].data.color, // bg
parameterList[8].data.color, // fg
(Scalar *)parameterList[9].data.attributePointer, // numerator
(Scalar *)parameterList[10].data.attributePointer, // denominator
// HACK!!!! The (Scalar *) is VERY dangerous!
"SegmentArcRatio");
Register_Object(gauge);
Check_Fpu();
return True;
}
//
// @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)
{
Check_Pointer(this);
// 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
GaugeConnection
*connection;
connection = new GaugeConnectionDirectOf<Scalar>(0, &numerator, numerator_pointer); // @0xC0
Check(connection);
Register_Object(connection);
AddConnection(connection);
connection = new GaugeConnectionDirectOf<Scalar>(0, &denominator, denominator_pointer); // @0xC4
Check(connection);
Register_Object(connection);
AddConnection(connection);
Check_Fpu();
}
//
// @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 (@00474300)
// which lights that fraction of the segments. (x87 recovered by
// disassembly: FUN_004dcd00 = fabs.)
//
void
SegmentArcRatio::Execute()
{
Check(this);
if (denominator < 1.0f) // guard div-by-tiny / unpowered
{
currentValue = 0.0f;
}
else
{
currentValue = numerator / denominator * segmentSpan;
if (currentValue < 0.0f) // FUN_004dcd00 = fabs
{
currentValue = -currentValue;
}
if (currentValue > 1.0f)
{
currentValue = 1.0f;
}
}
SegmentArc::Execute(); // @00474300 -- the base draw
}
// === btl4gau2.cpp begins at @004c6798 (SeekVoltage gauge) -- not part of this TU.