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>
2998 lines
88 KiB
C++
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)
|
|
¤tColorIndex, 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)
|
|
¤tColorIndex, 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)
|
|
¤tColorIndex, 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)
|
|
¤tColorIndex, 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, ¤tValue, 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.
|