Six TUs born in one wave (4-agent re-hosting workflow from the BT411 donors + inline integration): BTL4GAUG (16 widget primitives + 5 connections, the TU anchor @004c3f6c), BTL4GAU2 (13 composite panel clusters), BTL4GAU3 (PilotList/PlayerStatus/SectorDisplay/MessageBoard/mapping group), BTL4RDR (the MapDisplay radar vs the authentic surviving header), BTL4GRND (the real renderer: L4LampManager + L4Warehouse + BuildConfigurationFile with the 19-entry BT registry chained onto the engine table). btl4.lib grows 7 -> 11 members; all 50 game TUs compile; link clean. VERIFIED LIVE: with L4GAUGE=640x480x16 the interpreter parses the full authentic L4GAUGE.CFG, Bhk1Init runs, ColorMapperArmor binds the live mech's damage zones by name (cross-chassis names take the authentic inert path), and the complete mutual missile fight runs with the cockpit stack updating from real combat -- 16/16 rounds, 138 zone hits, zero faults. Gauges-off regressions untouched. Engine bring-up deviation (documented): ParseAttribute normalizes unpublished attribute names to a shared writable zero cell -- the 1995 GaugeConnectionDirectOf ctor hard-derefs its source (first boot crashed there); widgets on unpublished names draw static zeros and light up automatically as the attribute waves publish the 1995 CFG spellings the bindings already carry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2977 lines
88 KiB
C++
2977 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();
|
|
}
|
|
|
|
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.
|