Same binary (md5 identical), different run: Eng1/Eng2/Comm read 17981/17981/15656 on some runs and 19011/19011/16909 on others. The shipped binary is STABLE across three runs, so this is ours. Eleven consecutive clean runs is what let it hide, and the dirty numbers are exactly the pre-gate numbers from 5.3.32 -- the same artifact recurring. Read off the raw words rather than inferred: in the artifact rect the shipped binary writes ONE masked plane bit per pixel (0x4000 = Heat, 0x002D = sec), while we write arbitrary 16-bit words (0x8BD6 / 0x8AD6 / 0x8AC0) that light sec+overlay+Mfd1+Eng1+Eng2+Comm at once. Raw values going into a plane-packed buffer -- which is why one bad draw appears as extra pixels on six heads and as 483 MISSING on Heat at the same time. Intermittent because these widgets are change-driven: on runs where the coolant value never moves after the first paint, the bad draw never happens. The six fixes of 5.3.33-39 stand (each moved specific attributable pixels). What must be restated is the claim that four heads are exact -- true only when this defect does not fire. The rig now needs a run-variance check before any per-head number is believed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3121 lines
105 KiB
C++
3121 lines
105 KiB
C++
//===========================================================================//
|
|
// File: btl4gau2.cpp //
|
|
// Project: BattleTech Brick: Gauge Renderer Manager //
|
|
// Contents: Cockpit instrument library, part 2 -- composite panel gauges //
|
|
// (see btl4gau2.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 build. Behaviour follows the Ghidra pseudo-C @004c6798..@004c9b50
|
|
// (+ VehicleSubSystems::Make @004cbaf0); per-method @ADDR evidence is cited
|
|
// inline. This TU links between btl4gaug.obj and btl4gau3.obj (BTL4.MAK).
|
|
//
|
|
// DATA BINDINGS (the re-host rule: bind by NAME through the engine's
|
|
// Simulation::GetAttributePointer, or through a documented accessor on the
|
|
// reconstructed subsystems -- NEVER a raw 1995 byte offset):
|
|
// LIVE PercentDone / TriggerState / WeaponState / RearFiring / ChargeLevel
|
|
// -- published by name (mechweap.cpp / emitter.cpp attribute tables).
|
|
// LIVE generator voltage / number / on-lamp -- PoweredSubsystem::
|
|
// ResolveVoltageSource() -> Generator::MeasuredVoltage() /
|
|
// GetGeneratorNumber(), and the PUBLIC Generator::outputVoltage /
|
|
// generatorOn members (powersub.hpp).
|
|
// LIVE destroyed / failed -- Simulation::GetSimulationState() ==
|
|
// MechSubsystem::DestroyedState, MechSubsystem::
|
|
// GetSubsystemDamageLevel() >= 1.0 (the PPC-dial polarity truth
|
|
// source, mechdmg.cpp DistributeCriticalHit).
|
|
// LIVE jam -- MechWeapon::GetWeaponState() == MechWeapon::JammedState
|
|
// (retires the binary's raw *(subsys+0x364) read).
|
|
// NAME-DEFERRED CurrentTemperature / DegradationTemperature /
|
|
// FailureTemperature / CoolantMassLeakRate / HeatLoad /
|
|
// CoolantAvailable / CurrentSeekVoltageIndex / Min / Max /
|
|
// SeekVoltage -- bound by name; the source410 heat / powersub /
|
|
// seek attribute waves do not publish them yet, so the resolve
|
|
// misses and the widget reads the unbound zero cell. Each binding
|
|
// goes LIVE automatically the day its table row lands.
|
|
// INERT (see the stub ledger at each site) -- seek-voltage response
|
|
// sampler, cooling-loop lamp frame, linked-sink temperatures,
|
|
// ammo count / feed state, aux-screen number / placement / label.
|
|
//
|
|
// Recovered constant-pool values:
|
|
// 0x43660000=230.0f / 0x433b0000=187.0f SeekVoltageGraph x/y scale
|
|
// 0x461c3c00=9999.0f BecameActive "force redraw" sentinel
|
|
// 0x463b8000=12000.0f generator max voltage
|
|
// 0x42c80000=100.0f HeatSinkCluster numeric scale (_DAT_004c8df0)
|
|
// _DAT_004c92e0=0.99f warning threshold
|
|
// _DAT_004c6bdc=1200.0f / _DAT_004c6be0=12000.0f seek-curve step/limit
|
|
// ld80 @004c6bd0 / @004c6d74 = 8.3333333333333e-05 (exactly 1/12000) --
|
|
// the voltage->y normaliser (y = v * (1/12000) * yScale)
|
|
//
|
|
|
|
#include <btl4.hpp>
|
|
#pragma hdrstop
|
|
|
|
#if !defined(BTL4GAU2_HPP)
|
|
# include <btl4gau2.hpp>
|
|
#endif
|
|
|
|
#if !defined(BTL4MODE_HPP)
|
|
# include <btl4mode.hpp>
|
|
#endif
|
|
|
|
#if !defined(L4CTRL_HPP)
|
|
# include <l4ctrl.hpp>
|
|
#endif
|
|
|
|
#if !defined(L4WRHOUS_HPP)
|
|
# include <l4wrhous.hpp>
|
|
#endif
|
|
|
|
#if !defined(APP_HPP)
|
|
# include <app.hpp>
|
|
#endif
|
|
|
|
#if !defined(MECHSUB_HPP)
|
|
# include <mechsub.hpp>
|
|
#endif
|
|
|
|
#if !defined(POWERSUB_HPP)
|
|
# include <powersub.hpp>
|
|
#endif
|
|
|
|
#if !defined(MECHWEAP_HPP)
|
|
# include <mechweap.hpp>
|
|
#endif
|
|
|
|
#if !defined(PROJWEAP_HPP)
|
|
# include <projweap.hpp>
|
|
#endif
|
|
|
|
#if !defined(HEAT_HPP)
|
|
# include <heat.hpp>
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
|
|
static const Scalar SeekVoltageXScale = 230.0f; // 0x43660000
|
|
static const Scalar SeekVoltageYScale = 187.0f; // 0x433b0000
|
|
static const Scalar RedrawSentinel = 9999.0f; // 0x461c3c00
|
|
static const Scalar GeneratorMaxVoltage = 12000.0f; // 0x463b8000
|
|
static const Scalar HeatNumericBase = 100.0f; // 0x42c80000
|
|
// SeekVoltageGraph plot constants (capstone disasm of @004c6934):
|
|
static const Scalar SeekCurveStep = 1200.0f; // _DAT_004c6bdc (10 segments)
|
|
static const Scalar SeekCurveLimit = 12000.0f; // _DAT_004c6be0 (y-axis full scale)
|
|
static const Scalar SeekCurveYNorm = (Scalar)(1.0 / 12000.0); // ld80 @004c6bd0/@004c6d74
|
|
|
|
// Recovered .data tuning constants (read from the optimised image).
|
|
static const Scalar HeatLoadScale = 100.0f; // _DAT_004c8df0
|
|
static const Scalar WeaponWarnThreshold = 0.99f; // _DAT_004c92e0
|
|
|
|
//
|
|
//#############################################################################
|
|
// UNBOUND-FEED CELLS. Every engine widget copies through its value pointer
|
|
// each frame (GaugeConnectionDirectOf<T> hard-verifies and derefs the source
|
|
// at construction -- a NULL feed is a crash, not a blank). Any binding whose
|
|
// attribute row / accessor is not reconstructed yet is pointed at one of
|
|
// these zero cells instead: the widget draws, the value is static, and the
|
|
// binding site documents what will light it up. The cells are read-only to
|
|
// every consumer.
|
|
//#############################################################################
|
|
//
|
|
static Scalar UnboundScalarSource = 0.0f;
|
|
static int UnboundIntegerSource = 0;
|
|
|
|
//
|
|
// FUN_0041bfc0 == the engine's name-keyed attribute-pointer resolve. The
|
|
// authentic Simulation::GetAttributePointer(const char*) (SIMULATE.CPP:444)
|
|
// already returns NULL on a miss, so no NullAttribute normalisation is
|
|
// needed here -- only the NULL-subsystem guard.
|
|
//
|
|
static void *
|
|
AttributePointerOf(
|
|
void *subsystem,
|
|
const char *name
|
|
)
|
|
{
|
|
if (subsystem == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
return ((Subsystem *)subsystem)->GetAttributePointer(name);
|
|
}
|
|
|
|
//
|
|
// Feed resolvers: attribute by name, or the unbound zero cell on a miss.
|
|
// (The composite ctors use the RAW AttributePointerOf only where the binary
|
|
// itself gated behaviour on a missing attribute.)
|
|
//
|
|
static Scalar *
|
|
ScalarAttributeOf(
|
|
void *subsystem,
|
|
const char *name
|
|
)
|
|
{
|
|
Scalar
|
|
*pointer = (Scalar *)AttributePointerOf(subsystem, name);
|
|
|
|
return (pointer != NULL) ? pointer : &UnboundScalarSource;
|
|
}
|
|
|
|
static int *
|
|
IntegerAttributeOf(
|
|
void *subsystem,
|
|
const char *name
|
|
)
|
|
{
|
|
int
|
|
*pointer = (int *)AttributePointerOf(subsystem, name);
|
|
|
|
return (pointer != NULL) ? pointer : &UnboundIntegerSource;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// Accessor bridges onto the reconstructed subsystems (replace the WinTesla
|
|
// port's extern "BT*" bridge functions with direct reads through the
|
|
// documented public interfaces).
|
|
//#############################################################################
|
|
//
|
|
|
|
//
|
|
// The binary's IsDerivedFrom(0x50f4bc) gate (FUN_0041a1a4): PoweredSubsystem's
|
|
// ClassDerivations. Gates the power-bus children (the 1995 ctor gated them on
|
|
// the "InputVoltage" attribute resolving, which is the same population).
|
|
//
|
|
static Logical
|
|
IsPoweredSubsystemDerived(Subsystem *subsystem)
|
|
{
|
|
if (subsystem == NULL)
|
|
{
|
|
return False;
|
|
}
|
|
return subsystem->IsDerivedFrom(PoweredSubsystem::ClassDerivations);
|
|
}
|
|
|
|
//
|
|
// The powering generator, resolved through the NAMED connection (powersub.hpp
|
|
// ResolveVoltageSource) -- the databinding-rule replacement for the binary's
|
|
// ResolveLink(AttributePointerOf("InputVoltage")) walk. NULL when the
|
|
// subsystem is unpowered, the tap is unresolved, or the source is not a
|
|
// Generator.
|
|
//
|
|
static Generator *
|
|
ResolvedGeneratorOf(void *subsystem)
|
|
{
|
|
Subsystem
|
|
*source;
|
|
|
|
if (!IsPoweredSubsystemDerived((Subsystem *)subsystem))
|
|
{
|
|
return NULL;
|
|
}
|
|
source = ((PoweredSubsystem *)subsystem)->ResolveVoltageSource();
|
|
if (source == NULL || !source->IsDerivedFrom(Generator::ClassDerivations))
|
|
{
|
|
return NULL;
|
|
}
|
|
return (Generator *)source;
|
|
}
|
|
|
|
//
|
|
// The destroyed test == binary *(subsystem+0x40) == 1: the simulation state
|
|
// word, 1 == MechSubsystem::DestroyedState (the same hard-failure gate the
|
|
// weapon simulations check).
|
|
//
|
|
static Logical
|
|
SubsystemDestroyed(Subsystem *subsystem)
|
|
{
|
|
if (subsystem == NULL)
|
|
{
|
|
return False;
|
|
}
|
|
return subsystem->GetSimulationState() == MechSubsystem::DestroyedState;
|
|
}
|
|
|
|
//
|
|
// PPC-dial POLARITY truth source (kept on re-host): a subsystem is FAILED
|
|
// when its own damage zone saturates (DistributeCriticalHit destroys at
|
|
// >= 1.0, mechdmg.cpp) -- read through the public accessor.
|
|
//
|
|
static Scalar
|
|
SubsystemDamageLevelOf(Subsystem *subsystem)
|
|
{
|
|
if (subsystem == NULL ||
|
|
!subsystem->IsDerivedFrom(MechSubsystem::ClassDerivations))
|
|
{
|
|
return 0.0f;
|
|
}
|
|
return ((MechSubsystem *)subsystem)->GetSubsystemDamageLevel();
|
|
}
|
|
|
|
//
|
|
// The jam test (retires the binary's raw *(subsys+0x364)==5 -- our recon
|
|
// publishes the weapon-state alarm through GetWeaponState, mechweap.hpp).
|
|
//
|
|
static Logical
|
|
WeaponJammedOf(Subsystem *subsystem)
|
|
{
|
|
if (subsystem == NULL ||
|
|
!subsystem->IsDerivedFrom(MechWeapon::ClassDerivations))
|
|
{
|
|
return False;
|
|
}
|
|
return ((MechWeapon *)subsystem)->GetWeaponState() == MechWeapon::JammedState
|
|
? True : False;
|
|
}
|
|
|
|
//
|
|
// INERT STUB -- the seek-voltage RESPONSE sampler. The 1995 subsystem
|
|
// published a voltage-response virtual (binary subsystem vtbl slot 15:
|
|
// Emitter @004bb42c = sqrt(seekPower(v)/2.0e8), Myomers @004b8f94 =
|
|
// sqrt(speed(v)*3.6/350)). Neither response model is reconstructed on the
|
|
// source410 subsystems, so the plot samples a flat response: the graph still
|
|
// performs its box-clear / erase duties (the shared Eng bit-plane's top-box
|
|
// eraser, Gitea #10 finding A) and the destroyed bitmap still shows, but the
|
|
// curve is a stand-in until the emitter/myomer response wave lands.
|
|
//
|
|
static Scalar
|
|
SeekResponseOf(
|
|
Subsystem *,
|
|
Scalar
|
|
)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
// File-private GaugeConnection classes for the composite-cluster lamps.
|
|
// Each derives GaugeConnection, stores dst/src, and overrides Update()
|
|
// (the per-frame sampler Gauge::Update drives -- GAUGE.CPP:569).
|
|
//###########################################################################
|
|
|
|
//
|
|
// The cooling-loop lamp's frame: the loop number of the Condenser this
|
|
// subsystem's sink is plumbed to, or 0 ("OFF") when it has no coolant,
|
|
// no loop master, or a master that is not a Condenser.
|
|
//
|
|
static int
|
|
CoolingLoopFrameOf(void *source)
|
|
{
|
|
Subsystem
|
|
*subsystem = (Subsystem *)source;
|
|
if (subsystem == NULL
|
|
|| !subsystem->IsDerivedFrom(HeatSink::ClassDerivations))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
HeatSink
|
|
*sink = (HeatSink *)subsystem;
|
|
if (sink->GetCoolantAvailable() != 1)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
Subsystem
|
|
*master = sink->ResolveCoolingMaster();
|
|
if (master == NULL
|
|
|| !master->IsDerivedFrom(Condenser::ClassDerivations))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return ((Condenser *)master)->GetCondenserNumber();
|
|
}
|
|
|
|
//
|
|
// CoolingLoopConnection -- @004c3134 ctor / @004c31a0 sampler (vt 0x518ea8).
|
|
// The binary: when the source subsystem's coolant loop is available
|
|
// (HeatSink coolantAvailable == 1) it follows the linked cooling master (the
|
|
// "HeatSink" link == linkedSinks) and shows its Condenser loop number
|
|
// (condenserNumber == the image-strip frame); otherwise 0.
|
|
//
|
|
// LIVE (5.3.36): heat.hpp now publishes GetCoolantAvailable() /
|
|
// ResolveCoolingMaster() / GetCondenserNumber(), so the lamp follows the
|
|
// binary exactly -- the same shape as PowerSourceConnection below. A sink
|
|
// with no coolant, no loop master, or a master that is not a Condenser
|
|
// reads frame 0 ("OFF"), which is what the box showed for every panel
|
|
// while this was stubbed.
|
|
//
|
|
class CoolingLoopConnection : public GaugeConnection
|
|
{
|
|
public:
|
|
CoolingLoopConnection(int *destination, void *source):
|
|
GaugeConnection(0),
|
|
source(source),
|
|
destination(destination)
|
|
{}
|
|
void Update() // @004c31a0
|
|
{
|
|
*destination = CoolingLoopFrameOf(source);
|
|
}
|
|
protected:
|
|
void *source; // @0x10 this[4]
|
|
int *destination; // @0x14 this[5]
|
|
};
|
|
|
|
//
|
|
// PowerSourceConnection -- @004c31ec ctor / @004c3258 sampler (vt 0x518e9c).
|
|
// The binary resolved the subsystem's InputVoltage link and read the
|
|
// generator's number (1..4 -> the btpbus generator-letter frame A..D;
|
|
// 0 == OFF). LIVE: resolved through the named voltageSource connection
|
|
// (ResolveVoltageSource / GetGeneratorNumber, powersub.hpp) -- the
|
|
// databinding-rule rewrite that fixed the frozen bus lamps.
|
|
//
|
|
class PowerSourceConnection : public GaugeConnection
|
|
{
|
|
public:
|
|
PowerSourceConnection(int *destination, void *source):
|
|
GaugeConnection(0),
|
|
source(source),
|
|
destination(destination)
|
|
{}
|
|
void Update() // @004c3258
|
|
{
|
|
Generator
|
|
*generator = ResolvedGeneratorOf(source);
|
|
|
|
*destination = (generator != NULL) ? generator->GetGeneratorNumber() : 0;
|
|
}
|
|
protected:
|
|
void *source; // @0x10
|
|
int *destination; // @0x14
|
|
};
|
|
|
|
//
|
|
// GeneratorVoltageConnection -- @004c3288 ctor / @004c32f4 sampler (vt
|
|
// 0x518e90). The binary resolved the subsystem's InputVoltage link and
|
|
// copied its output voltage; 0 if unresolved. Drives the eng-page
|
|
// generator-voltage bar (ScalarBarGauge) + the MyomerCluster seek graph.
|
|
// LIVE: ResolveVoltageSource()->MeasuredVoltage() (powersub.hpp).
|
|
//
|
|
class GeneratorVoltageConnection : public GaugeConnection
|
|
{
|
|
public:
|
|
GeneratorVoltageConnection(Scalar *destination, void *source):
|
|
GaugeConnection(0),
|
|
source(source),
|
|
destination(destination)
|
|
{}
|
|
void Update() // @004c32f4
|
|
{
|
|
Generator
|
|
*generator = ResolvedGeneratorOf(source);
|
|
|
|
*destination = (generator != NULL) ? generator->MeasuredVoltage() : 0.0f;
|
|
}
|
|
protected:
|
|
void *source; // @0x10
|
|
Scalar *destination; // @0x14
|
|
};
|
|
|
|
|
|
//###########################################################################
|
|
//###########################################################################
|
|
// SeekVoltageGraph @004c6798
|
|
// (fully reconstructed from the capstone disasm of @004c6798/@004c6920/
|
|
// @004c6934/@004c6be4/@004c6c30/@004c6c6c -- Ghidra had dropped every
|
|
// x87 argument.)
|
|
//###########################################################################
|
|
//###########################################################################
|
|
|
|
//
|
|
// @004c6798 -- ctor (vtable PTR_FUN_0051a1fc). GraphicGauge base, AddRefs
|
|
// the "destroyed" bitmap, resolves the four SeekVoltage attribute POINTERS
|
|
// off the weapon subsystem BY NAME (a miss reads NULL and gates the tick /
|
|
// cursor draws -- the emitter/myomer seek rows are not published by the
|
|
// source410 tables yet, so today they gate CLOSED), sets the view XOR draw
|
|
// op + the port extent (0x97,0x80,0x17d,0x13b) and stashes the 230x187 plot
|
|
// scale.
|
|
//
|
|
SeekVoltageGraph::SeekVoltageGraph(
|
|
GaugeRate rate,
|
|
ModeMask mode_mask,
|
|
L4GaugeRenderer *renderer_in,
|
|
int graphics_port_number,
|
|
Entity *subsystem_in,
|
|
AttributeAccessor *seek_voltage_pointer,
|
|
const char *destroyed_image,
|
|
Logical draw_cursor,
|
|
const char *identification_string
|
|
):
|
|
GraphicGauge(rate, mode_mask, renderer_in, 0xFFFF /*owner*/,
|
|
graphics_port_number, identification_string)
|
|
{
|
|
Check_Pointer(this);
|
|
|
|
//------------------------------------------------------------
|
|
// Retain the persistent resource string and AddRef the bitmap
|
|
// (the callers pass string literals; released in the dtor).
|
|
//------------------------------------------------------------
|
|
destroyedImage = (char *)destroyed_image;
|
|
((L4Warehouse *)renderer_in->warehousePointer)->
|
|
bitMapBin.Get(destroyedImage); // AddRef
|
|
|
|
//------------------------------------------------------------
|
|
// The four seek attributes, by NAME (NULL on a miss -- gated).
|
|
//------------------------------------------------------------
|
|
currentSeekIndexPtr = (int *)AttributePointerOf(subsystem_in, "CurrentSeekVoltageIndex"); // this[0x25]
|
|
minSeekIndexPtr = (int *)AttributePointerOf(subsystem_in, "MinSeekVoltageIndex"); // this[0x26]
|
|
maxSeekIndexPtr = (int *)AttributePointerOf(subsystem_in, "MaxSeekVoltageIndex"); // this[0x27]
|
|
seekVoltageTablePtr = (Scalar *)AttributePointerOf(subsystem_in, "SeekVoltage"); // this[0x28]
|
|
|
|
//------------------------------------------------------------
|
|
// The live-cursor voltage source; Execute derefs it every
|
|
// frame, so a missed binding is normalised to the zero cell.
|
|
//------------------------------------------------------------
|
|
liveVoltage = (Scalar *)seek_voltage_pointer; // this[0x29]
|
|
if (liveVoltage == NULL)
|
|
{
|
|
liveVoltage = &UnboundScalarSource;
|
|
}
|
|
subsystem = (Subsystem *)subsystem_in; // this[0x2A]
|
|
|
|
//------------------------------------------------------------
|
|
// @004c6798: SetOperation(Xor) + SetPositionWithinPort -- the
|
|
// XOR op is what lets DrawTicks/DrawCursorBox erase by redraw.
|
|
//------------------------------------------------------------
|
|
localView.SetOperation(GraphicsDisplay::Xor); // vtbl+0x0C
|
|
localView.SetPositionWithinPort(0x97, 0x80, 0x17d, 0x13b); // vtbl+0x08
|
|
|
|
xScale = SeekVoltageXScale; // this[0x2C]
|
|
yScale = SeekVoltageYScale; // this[0x2D]
|
|
drawCursor = draw_cursor; // this[0x2F]
|
|
destroyedShown = 0; // this[0x2E]
|
|
//
|
|
// (The binary leaves this[0x2B]/[0x30..0x32] uninitialised -- the
|
|
// activation sentinel forces the first replot, which seeds them before
|
|
// any read. Seeded here for determinism.)
|
|
//
|
|
previousVoltage = RedrawSentinel; // this[0x2B]
|
|
cursorX = -1; // this[0x30]
|
|
cursorY = -1; // this[0x31]
|
|
tickIndexShown = -1; // this[0x32]
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
// @004c68ac -- dtor. Release the destroyed bitmap; base chain implicit.
|
|
//
|
|
SeekVoltageGraph::~SeekVoltageGraph()
|
|
{
|
|
Check(this);
|
|
|
|
L4Warehouse
|
|
*warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
|
|
warehouse->bitMapBin.Release(destroyedImage);
|
|
}
|
|
|
|
Logical
|
|
SeekVoltageGraph::TestInstance() const
|
|
{
|
|
return GraphicGauge::TestInstance();
|
|
}
|
|
|
|
//
|
|
// @004c6920 -- BecameActive: previousVoltage = 9999.0f (poison the cached
|
|
// response so the next Execute clears + replots the whole box -- THE top-box
|
|
// eraser on a page switch; the binary body writes ONLY this[0x2B]).
|
|
//
|
|
void
|
|
SeekVoltageGraph::BecameActive()
|
|
{
|
|
previousVoltage = RedrawSentinel; // @0xAC force full redraw
|
|
}
|
|
|
|
//
|
|
// @004c6be4 -- the full-box erase: SetOperation(Replace), SetColor(0),
|
|
// MoveToAbsolute(0,0), DrawFilledRectangleToAbsolute(1000,1000) -- clipped to
|
|
// the view = the whole (0x97,0x80)-(0x17d,0x13b) top data box painted black.
|
|
// NOTE: leaves the view in Replace mode (the polyline plot relies on that;
|
|
// Execute restores Xor).
|
|
//
|
|
void
|
|
SeekVoltageGraph::ClearGraph()
|
|
{
|
|
localView.SetOperation(GraphicsDisplay::Replace); // vtbl+0x0C
|
|
localView.SetColor(0); // vtbl+0x18
|
|
localView.MoveToAbsolute(0, 0); // vtbl+0x24
|
|
localView.DrawFilledRectangleToAbsolute(1000, 1000); // vtbl+0x48
|
|
}
|
|
|
|
//
|
|
// @004c6c30 -- the live cursor: an 8x8 filled square centred on
|
|
// (cursorX,cursorY), drawn in the view's current (Xor) op so a second call
|
|
// erases it.
|
|
//
|
|
void
|
|
SeekVoltageGraph::DrawCursorBox()
|
|
{
|
|
localView.MoveToAbsolute(cursorX - 4, cursorY - 4); // vtbl+0x24
|
|
localView.DrawFilledRectangleToRelative(8, 8); // vtbl+0x4C
|
|
}
|
|
|
|
//
|
|
// @004c6c6c -- the per-gear tick marks (XOR). For each seek index i in
|
|
// [*min..*max]: V = seekVoltage[i]; x = Round(response(V) * xScale);
|
|
// y = Round(V * (1/12000) * yScale) [ld80 @004c6d74]. The gear the
|
|
// highlight was last drawn at (this[0x32]) gets the full L (axis->point->
|
|
// axis); the rest get 10-pixel axis stubs. Called in pairs to move the
|
|
// highlight (XOR erase + redraw).
|
|
//
|
|
void
|
|
SeekVoltageGraph::DrawTicks()
|
|
{
|
|
int
|
|
i,
|
|
last;
|
|
|
|
//
|
|
// (Guard kept from the decode: the binary derefs min/max unguarded --
|
|
// its call sites only guarantee current+table non-null. On the re-host
|
|
// the seek attributes may legitimately be unpublished, so the guard is
|
|
// load-bearing.)
|
|
//
|
|
if (minSeekIndexPtr == NULL || maxSeekIndexPtr == NULL ||
|
|
seekVoltageTablePtr == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
last = *maxSeekIndexPtr; // this[0x27]
|
|
for (i = *minSeekIndexPtr; i <= last; i++) // this[0x26]
|
|
{
|
|
Scalar
|
|
gearVoltage = seekVoltageTablePtr[i]; // this[0x28][i]
|
|
int
|
|
x = Round(SeekResponseOf(subsystem, gearVoltage) * xScale),
|
|
y = Round(gearVoltage * SeekCurveYNorm * yScale);
|
|
|
|
if (i == tickIndexShown) // this+0xC8
|
|
{
|
|
localView.MoveToAbsolute(0, y); // vtbl+0x24
|
|
localView.DrawLineToAbsolute(x, y); // vtbl+0x30
|
|
localView.DrawLineToAbsolute(x, 0);
|
|
}
|
|
else
|
|
{
|
|
localView.MoveToAbsolute(0, y);
|
|
localView.DrawLineToAbsolute(10, y);
|
|
localView.MoveToAbsolute(x, 0);
|
|
localView.DrawLineToAbsolute(x, 10);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// @004c6934 -- Execute (the seek-voltage response plot; full x87 recovery).
|
|
//
|
|
// DESTROYED branch (simulationState == DestroyedState): once, clear the box
|
|
// and draw the centred "destroyed" bitmap (edestryd.pcc) at
|
|
// ((Round(xScale)-w)/2, (Round(yScale)-h)/2), color 0xFF.
|
|
//
|
|
// LIVE branch:
|
|
// * on the destroyed->alive edge, call BecameActive (own vtbl slot) to
|
|
// poison the cache;
|
|
// * change-test: sample the response at 12000 V and compare with the cached
|
|
// this[0x2B]; on change (or the 9999 activation sentinel): CLEAR
|
|
// (@004c6be4 -- the ghost eraser), SetColor(0xFF), MoveTo(0,0), then the
|
|
// polyline v = 0,1200,...,10800; reset the cursor slots to -1, restore
|
|
// SetOperation(Xor), and draw the gear ticks at the current index;
|
|
// * tick maintenance: when the current seek index moved, XOR-erase the old
|
|
// highlight + redraw at the new (two DrawTicks calls);
|
|
// * cursor (draw_cursor pages only): XOR-erase the old box (if any), sample
|
|
// at the LIVE voltage (*this[0x29]) and draw the new 8x8 box.
|
|
//
|
|
void
|
|
SeekVoltageGraph::Execute()
|
|
{
|
|
Scalar
|
|
yStep,
|
|
response,
|
|
v;
|
|
|
|
if (SubsystemDestroyed(subsystem))
|
|
{
|
|
if (destroyedShown == 0)
|
|
{
|
|
destroyedShown = 1; // this[0x2E]
|
|
ClearGraph(); // @004c6be4
|
|
|
|
L4Warehouse
|
|
*warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
BitMap
|
|
*image = warehouse->bitMapBin.Get(destroyedImage);
|
|
|
|
if (image != NULL) // (guard; binary derefs unchecked)
|
|
{
|
|
localView.MoveToAbsolute( // vtbl+0x24 centre the bitmap
|
|
(Round(xScale) - image->Data.Size.x) >> 1,
|
|
(Round(yScale) - image->Data.Size.y) >> 1);
|
|
localView.SetColor(0xff); // vtbl+0x18
|
|
localView.DrawBitMap(0, image, // vtbl+0x54
|
|
0, 0, image->Data.Size.x - 1, image->Data.Size.y - 1);
|
|
}
|
|
warehouse->bitMapBin.Release(destroyedImage);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (destroyedShown != 0)
|
|
{
|
|
destroyedShown = 0;
|
|
BecameActive(); // own vtbl slot (@004c6920)
|
|
}
|
|
|
|
yStep = SeekCurveYNorm * yScale; // fld tbyte @004c6bd0 * this[0x2D]
|
|
|
|
//------------------------------------------------------------
|
|
// change-test: the response curve's value at max voltage.
|
|
//------------------------------------------------------------
|
|
response = SeekResponseOf(subsystem, GeneratorMaxVoltage);
|
|
if (previousVoltage != response) // this[0x2B]
|
|
{
|
|
previousVoltage = response;
|
|
ClearGraph(); // @004c6be4 -- erases the WHOLE box
|
|
localView.SetColor(0xff); // vtbl+0x18 (Replace mode from the clear)
|
|
localView.MoveToAbsolute(0, 0); // vtbl+0x24
|
|
for (v = 0.0f; v < SeekCurveLimit; v += SeekCurveStep)
|
|
{
|
|
localView.DrawLineToAbsolute( // vtbl+0x30
|
|
Round(SeekResponseOf(subsystem, v) * xScale),
|
|
Round(v * yStep));
|
|
}
|
|
cursorX = -1; // this[0x30]
|
|
cursorY = -1; // this[0x31]
|
|
localView.SetOperation(GraphicsDisplay::Xor); // vtbl+0x0C back to Xor
|
|
if (currentSeekIndexPtr != NULL && seekVoltageTablePtr != NULL)
|
|
{
|
|
tickIndexShown = *currentSeekIndexPtr; // this[0x32]
|
|
DrawTicks(); // @004c6c6c
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
// gear-change tick maintenance (XOR pair: erase old, draw new).
|
|
//------------------------------------------------------------
|
|
if (currentSeekIndexPtr != NULL && seekVoltageTablePtr != NULL)
|
|
{
|
|
int
|
|
currentIndex = *currentSeekIndexPtr;
|
|
|
|
if (currentIndex != tickIndexShown)
|
|
{
|
|
DrawTicks(); // erase at the old index
|
|
tickIndexShown = currentIndex;
|
|
DrawTicks(); // draw at the new
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
// live-voltage cursor (drawCursor pages: the emitter/PPC graphs).
|
|
//------------------------------------------------------------
|
|
if (drawCursor != 0) // this[0x2F]
|
|
{
|
|
Scalar
|
|
live;
|
|
|
|
if (cursorX >= 0)
|
|
{
|
|
DrawCursorBox(); // XOR-erase the old box
|
|
}
|
|
live = *liveVoltage; // *this[0x29]
|
|
cursorX = Round(SeekResponseOf(subsystem, live) * xScale);
|
|
cursorY = Round(live * yStep);
|
|
DrawCursorBox(); // @004c6c30
|
|
}
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
// ConfigMapGauge @004c6d80
|
|
//###########################################################################
|
|
|
|
//
|
|
// The 4-slot state table (DAT_00518eb4, PE-recovered {y, button} pairs; the
|
|
// button numbers resolve to the authentic L4CTRL.HPP names). Blit x = 0xc.
|
|
//
|
|
struct ConfigMapSlot
|
|
{
|
|
int
|
|
y;
|
|
int
|
|
button;
|
|
};
|
|
|
|
static const ConfigMapSlot
|
|
configMapSlot[4] =
|
|
{
|
|
{ 0x0d, LBE4ControlsManager::ButtonJoystickPinky }, // 0x45
|
|
{ 0x25, LBE4ControlsManager::ButtonJoystickThumbLow }, // 0x46
|
|
{ 0x3d, LBE4ControlsManager::ButtonJoystickTrigger }, // 0x40
|
|
{ 0x55, LBE4ControlsManager::ButtonJoystickThumbHigh }, // 0x47
|
|
};
|
|
|
|
//
|
|
// @004c6d80 -- ctor (vtable PTR_FUN_0051a1b8). GraphicGauge base; AddRefs the
|
|
// joystick base bitmap "btjoy.pcc" + the four config-state pixmaps
|
|
// cm_off/cm_other/cm_only/cm_both.pcc; sets the port origin (x,y);
|
|
// linkedEntity = 0 (armed later by the LinkToEntity broadcast).
|
|
//
|
|
ConfigMapGauge::ConfigMapGauge(
|
|
GaugeRate rate,
|
|
ModeMask mode_mask,
|
|
L4GaugeRenderer *renderer_in,
|
|
int graphics_port_number,
|
|
int x,
|
|
int y,
|
|
Entity *subsystem_in,
|
|
const char *identification_string
|
|
):
|
|
GraphicGauge(rate, mode_mask, renderer_in, 0 /*owner*/,
|
|
graphics_port_number, identification_string)
|
|
{
|
|
int
|
|
i;
|
|
|
|
Check_Pointer(this);
|
|
|
|
joystickImage = (char *)"btjoy.pcc"; // @0x98 this[0x26]
|
|
stateImage[0] = (char *)"cm_off.pcc"; // @0x9C this[0x27]
|
|
stateImage[1] = (char *)"cm_other.pcc"; // @0xA0 this[0x28]
|
|
stateImage[2] = (char *)"cm_only.pcc"; // @0xA4 this[0x29]
|
|
stateImage[3] = (char *)"cm_both.pcc"; // @0xA8 this[0x2A]
|
|
|
|
localView.SetOrigin(x, y); // vtbl+0x10 (this+0x48)
|
|
|
|
//
|
|
// @0x94 is NOT a colour and @004c6ee0 is NOT an uncalled SetColor -- it
|
|
// is the virtual GaugeBase::LinkToEntity override (vtbl slot 9, verified
|
|
// against the binary vtable @0051a1b8). The engine broadcasts
|
|
// LinkToEntity(viewpointEntity) to every gauge when the viewpoint binds,
|
|
// which arms this gate -- the trigger-config joystick DOES render in the
|
|
// shipped game.
|
|
//
|
|
linkedEntity = NULL; // @0x94 this[0x25]
|
|
subsystem = subsystem_in; // @0x90 this[0x24]
|
|
|
|
L4Warehouse
|
|
*warehouse = (L4Warehouse *)renderer_in->warehousePointer;
|
|
|
|
warehouse->bitMapBin.Get(joystickImage); // AddRef
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
warehouse->pixelMap8Bin.Get(stateImage[i]); // AddRef
|
|
}
|
|
}
|
|
|
|
//
|
|
// @004c6e54 -- dtor: release btjoy bitmap + 4 pixmaps; base chain implicit.
|
|
//
|
|
ConfigMapGauge::~ConfigMapGauge()
|
|
{
|
|
int
|
|
i;
|
|
|
|
Check(this);
|
|
|
|
L4Warehouse
|
|
*warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
|
|
warehouse->bitMapBin.Release(joystickImage);
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
warehouse->pixelMap8Bin.Release(stateImage[i]);
|
|
}
|
|
}
|
|
|
|
Logical
|
|
ConfigMapGauge::TestInstance() const
|
|
{
|
|
return GraphicGauge::TestInstance();
|
|
}
|
|
|
|
//
|
|
// @004c6ee0 -- LinkToEntity (GaugeBase vtbl slot 9): linkedEntity (this[0x25])
|
|
// = the viewpoint entity. Broadcast by GaugeRenderer::LinkToEntity when the
|
|
// viewpoint binds; arms the Execute gate.
|
|
//
|
|
void
|
|
ConfigMapGauge::LinkToEntity(Entity *entity) // @004c6ee0
|
|
{
|
|
linkedEntity = entity;
|
|
}
|
|
|
|
//
|
|
// @004c6ef4 -- BecameActive: force a full redraw (dirty=1, previousState=-1).
|
|
//
|
|
void
|
|
ConfigMapGauge::BecameActive()
|
|
{
|
|
int
|
|
i;
|
|
|
|
dirty = True; // @0xBC this[0x2F]
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
previousState[i] = -1; // @0xAC this[0x2B..0x2E]
|
|
}
|
|
}
|
|
|
|
//
|
|
// @004c6f1c -- Execute. Once linked (linkedEntity != 0): on the dirty flag,
|
|
// blit the base joystick bitmap; then for each of the 4 config-map slots read
|
|
// the fire button's map state and, if changed, blit the matching cm_* pixmap.
|
|
//
|
|
// The sampler is LBE4ControlsManager::buttonGroup[btn].GetMapState (the
|
|
// engine ControlsUpdateManager, CONTROLS.HPP): unmapped=0 / mappedByOthers=1
|
|
// / mappedByMe=2 / both=3 == the cm_off/cm_other/cm_only/cm_both frame index
|
|
// -- the per-weapon lamp column over the btjoy joystick image. The feed the
|
|
// streamed direct mapping registered is the weapon's TriggerState attribute
|
|
// (== &fireImpulse, published by name in mechweap.cpp) with a 0 message id;
|
|
// the mode is the authentic BTL4ModeManager::ModeNonMapping (== the binary's
|
|
// 0x10000 literal -- the persistent groups).
|
|
//
|
|
void
|
|
ConfigMapGauge::Execute()
|
|
{
|
|
int
|
|
slot;
|
|
|
|
if (linkedEntity == NULL) // @004c6f28: not yet linked to the viewpoint
|
|
{
|
|
return;
|
|
}
|
|
|
|
L4Warehouse
|
|
*warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
|
|
if (dirty)
|
|
{
|
|
BitMap
|
|
*base = warehouse->bitMapBin.Get(joystickImage);
|
|
|
|
dirty = False;
|
|
if (base != NULL)
|
|
{
|
|
localView.SetColor(0xFF); // vtbl+0x18
|
|
//
|
|
// issue #42 (doubled joystick), kept on re-host: the binary
|
|
// repositions to the view ORIGIN before the blit. Without it the
|
|
// FIRST draw worked (fresh cursor) and every forced redraw (the
|
|
// DISPLAY page round-trip's BecameActive) blitted at the state
|
|
// loop's last cursor (0xc, 0x55) -- the offset ghost joystick.
|
|
//
|
|
localView.MoveToAbsolute(0, 0); // vtbl+0x24
|
|
localView.DrawBitMapOpaque(0 /*background*/, 0 /*rotation*/, base);
|
|
}
|
|
warehouse->bitMapBin.Release(joystickImage);
|
|
}
|
|
|
|
LBE4ControlsManager
|
|
*controls = (LBE4ControlsManager *)application->GetControlsManager();
|
|
|
|
if (controls == NULL || subsystem == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// The mapping feed: the weapon's fire-button destination. (The stored
|
|
// pointer's dynamic type is Subsystem -- cast back before use.)
|
|
//
|
|
void
|
|
*destination = AttributePointerOf((Subsystem *)subsystem, "TriggerState");
|
|
|
|
for (slot = 0; slot < 4; slot++)
|
|
{
|
|
int
|
|
state = controls->buttonGroup[configMapSlot[slot].button].GetMapState(
|
|
destination, // &fireImpulse
|
|
(Receiver *)(Subsystem *)subsystem, // the subsystem itself
|
|
0, // message id (weapons use 0)
|
|
(ModeMask)BTL4ModeManager::ModeNonMapping); // the persistent groups
|
|
|
|
if (state != previousState[slot])
|
|
{
|
|
PixelMap8
|
|
*pix;
|
|
|
|
previousState[slot] = state;
|
|
pix = warehouse->pixelMap8Bin.Get(stateImage[state]);
|
|
if (pix != NULL)
|
|
{
|
|
localView.MoveToAbsolute(0xc, configMapSlot[slot].y); // vtbl+0x24
|
|
localView.DrawPixelMap8(0 /*opaque*/, 0 /*rotation*/, pix);
|
|
}
|
|
warehouse->pixelMap8Bin.Release(stateImage[state]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
// OneOfSeveral data-driven lamps @004c70a4 / @004c7160
|
|
//###########################################################################
|
|
|
|
//
|
|
// @004c70a4 -- AnimatedSubsystemLamp ctor (vtable PTR_FUN_0051a174):
|
|
// OneOfSeveral (fromImageStrip=1) + a CoolingLoopConnection feeding the
|
|
// INHERITED `selected` frame from the subsystem's cooling-loop state.
|
|
// Otherwise inherits OneOfSeveral::Execute.
|
|
//
|
|
// COLOR FIX kept (verified vs binary @004c70a4 / caller @004c8140): the
|
|
// binary passes bg=0xff, fg=0 (the MFD on-colour) -- an earlier transcript
|
|
// dropped them to 0,0 and the loop/gen boxes drew black-on-black.
|
|
//
|
|
AnimatedSubsystemLamp::AnimatedSubsystemLamp(
|
|
GaugeRate rate,
|
|
ModeMask mode_mask,
|
|
L4GaugeRenderer *renderer_in,
|
|
int graphics_port_number,
|
|
int x,
|
|
int y,
|
|
const char *image,
|
|
int columns,
|
|
int rows,
|
|
void *subsystem,
|
|
const char *identification_string
|
|
):
|
|
OneOfSeveral(rate, mode_mask, renderer_in, graphics_port_number,
|
|
x, y, True /*fromImageStrip*/, image, 0xff, 0, columns, rows,
|
|
identification_string)
|
|
{
|
|
GaugeConnection
|
|
*connection;
|
|
|
|
//
|
|
// SHADOW-FIELD FIX kept: `selected` is the INHERITED OneOfSeveral member
|
|
// (@0xAC) -- a local redeclaration froze the lamp at frame 0.
|
|
//
|
|
selected = 0;
|
|
|
|
connection = new CoolingLoopConnection(&selected, subsystem); // @004c3134
|
|
Register_Object(connection);
|
|
AddConnection(connection);
|
|
}
|
|
|
|
AnimatedSubsystemLamp::~AnimatedSubsystemLamp() // @004c7134 (base chain)
|
|
{
|
|
}
|
|
|
|
//
|
|
// @004c7160 -- AnimatedSourceLamp ctor (vtable PTR_FUN_0051a130): identical
|
|
// shape but a PowerSourceConnection (@004c31ec) drives the frame from the
|
|
// resolved power source's generator number (the btpbus A..D letter lamp).
|
|
//
|
|
AnimatedSourceLamp::AnimatedSourceLamp(
|
|
GaugeRate rate,
|
|
ModeMask mode_mask,
|
|
L4GaugeRenderer *renderer_in,
|
|
int graphics_port_number,
|
|
int x,
|
|
int y,
|
|
const char *image,
|
|
int columns,
|
|
int rows,
|
|
void *source,
|
|
const char *identification_string
|
|
):
|
|
OneOfSeveral(rate, mode_mask, renderer_in, graphics_port_number,
|
|
x, y, True, image, 0xff, 0, columns, rows, identification_string)
|
|
{
|
|
GaugeConnection
|
|
*connection;
|
|
|
|
selected = 0;
|
|
|
|
connection = new PowerSourceConnection(&selected, source); // @004c31ec
|
|
Register_Object(connection);
|
|
AddConnection(connection);
|
|
}
|
|
|
|
AnimatedSourceLamp::~AnimatedSourceLamp() // @004c71f0
|
|
{
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
// ScalarBarGauge @004c721c / @004c72ac
|
|
//###########################################################################
|
|
|
|
//
|
|
// @004c721c -- the "slotOf" variant (vtable PTR_FUN_0051a0e4). Chains the
|
|
// engine BarGraphBitMapScalar with no auto-connection (value=NULL -- the
|
|
// engine ctor documents that a derived object then builds the connection),
|
|
// then adds a GeneratorVoltageConnection writing the inherited
|
|
// WipeGaugeScalar::currentValue (@0xB4) from the resolved voltage source.
|
|
// Generator-voltage bar, range 0..12000. dtor @004c733c = base chain.
|
|
//
|
|
ScalarBarGauge::ScalarBarGauge(
|
|
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 foreground_color,
|
|
int background_color,
|
|
WipeGaugeScalar::Direction direction,
|
|
Scalar min,
|
|
Scalar max,
|
|
void *voltage_source,
|
|
const char *identification_string
|
|
):
|
|
BarGraphBitMapScalar(rate, mode_mask, renderer_in, 0 /*owner*/,
|
|
graphics_port_number, left, bottom, right, top, tile_image,
|
|
foreground_color, background_color, direction, min, max,
|
|
(Scalar *)NULL /*no auto-connection*/, identification_string)
|
|
{
|
|
GaugeConnection
|
|
*connection;
|
|
|
|
connection = new GeneratorVoltageConnection(¤tValue, voltage_source); // @004c3288
|
|
Register_Object(connection);
|
|
AddConnection(connection);
|
|
}
|
|
|
|
//
|
|
// @004c72ac -- the plain-Scalar variant. Same bar body but a
|
|
// GaugeConnectionDirectOf<Scalar> from a DIRECT Scalar* (the generator's own
|
|
// output-voltage member) instead of a GeneratorVoltageConnection.
|
|
// GeneratorCluster is its caller. Disambiguated from the void* overload by
|
|
// the Scalar* argument (exact match here, std-conversion to void*).
|
|
//
|
|
ScalarBarGauge::ScalarBarGauge(
|
|
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 foreground_color,
|
|
int background_color,
|
|
WipeGaugeScalar::Direction direction,
|
|
Scalar min,
|
|
Scalar max,
|
|
Scalar *value_source,
|
|
const char *identification_string
|
|
):
|
|
BarGraphBitMapScalar(rate, mode_mask, renderer_in, 0 /*owner*/,
|
|
graphics_port_number, left, bottom, right, top, tile_image,
|
|
foreground_color, background_color, direction, min, max,
|
|
(Scalar *)NULL /*no auto-connection*/, identification_string)
|
|
{
|
|
GaugeConnection
|
|
*connection;
|
|
|
|
connection = new GaugeConnectionDirectOf<Scalar>(0, ¤tValue, value_source);
|
|
Register_Object(connection);
|
|
AddConnection(connection);
|
|
}
|
|
|
|
ScalarBarGauge::~ScalarBarGauge() // @004c733c (base chain)
|
|
{
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
// shared cluster helpers
|
|
//###########################################################################
|
|
|
|
//
|
|
// FUN_004c2ec4 -- draw a clipped port-background bitmap: MoveTo(x,y), fetch
|
|
// the tile from the BitMap cache, opaque-blit it, release. (Used by the
|
|
// cluster ctors to paint the panel's static frame image into the MFD port.)
|
|
//
|
|
static void
|
|
DrawPortBackground(
|
|
GraphicsView *view,
|
|
L4Warehouse *warehouse,
|
|
int x,
|
|
int y,
|
|
const char *tile
|
|
)
|
|
{
|
|
BitMap
|
|
*bmp;
|
|
|
|
view->MoveToAbsolute(x, y); // vtbl+0x24
|
|
bmp = warehouse->bitMapBin.Get(tile);
|
|
if (bmp != NULL)
|
|
{
|
|
view->DrawBitMapOpaque(0 /*background*/, 0 /*rotation*/, bmp);
|
|
}
|
|
warehouse->bitMapBin.Release(tile);
|
|
}
|
|
|
|
//
|
|
// A default per-child load-distribution rate (the binary ctors call the
|
|
// NextNthTierGaugeRate allocators FUN_004442b8/dc/290).
|
|
//
|
|
static inline GaugeRate
|
|
ChildRate()
|
|
{
|
|
return Gauge::NextThirdTierGaugeRate();
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// AUX-SCREEN ASSIGNMENT (RECONSTRUCTION STAND-IN). The 1995 build read the
|
|
// authored auxScreenNumber / auxScreenPlacement / auxScreenLabel off the
|
|
// powered subsystem (binary sub+0x1dc/0x1e0/0x1e4, streamed from
|
|
// PoweredSubsystem__SubsystemResource). Our reconstructed subsystems free
|
|
// the resource block after construction (MECHSUB.HPP `resource` -- NEVER
|
|
// deref) and keep NO live copy of the aux fields, so:
|
|
// * the screen NUMBER is assigned SEQUENTIALLY in roster order over the
|
|
// displayable subsystem classes (deterministic, and identical between
|
|
// VehicleSubSystems::Make and PrepEngrScreen::BecameActive);
|
|
// * the strip-image PLACEMENT has no source -> the panel backdrop child is
|
|
// not built;
|
|
// * the label .pcc has no source -> the label blits are skipped.
|
|
// The ClassID dispatch itself is authentic. All three feeds go live when an
|
|
// aux-screen wave lands live members on PoweredSubsystem.
|
|
//#############################################################################
|
|
//
|
|
static Logical
|
|
IsPanelSubsystem(Subsystem *subsystem)
|
|
{
|
|
switch (subsystem->GetClassID())
|
|
{
|
|
case RegisteredClass::SensorClassID: // 0xBC3
|
|
case RegisteredClass::MyomersClassID: // 0xBC6
|
|
case RegisteredClass::EmitterClassID: // 0xBC8
|
|
case RegisteredClass::PPCClassID: // 0xBD4
|
|
case RegisteredClass::ProjectileWeaponClassID: // 0xBCD
|
|
case RegisteredClass::MissileLauncherClassID: // 0xBD0
|
|
case RegisteredClass::GaussRifleClassID: // 0xBCE (warned-unsupported)
|
|
return True;
|
|
default:
|
|
return False;
|
|
}
|
|
}
|
|
|
|
//
|
|
// The AUTHORED aux screen (binary sub+0x1dc). PoweredSubsystem caches the
|
|
// streamed block at ctor time (its resource memory dies with the Mech
|
|
// ctor's stream buffer), so the panels land on the screens the artists
|
|
// assigned -- not in roster order. A non-powered panel subsystem (or a
|
|
// model that authored nothing) falls back to the sequential walk.
|
|
//
|
|
static int
|
|
AuxScreenAssignmentOf(
|
|
Entity *mech,
|
|
Subsystem *subsystem
|
|
)
|
|
{
|
|
int
|
|
i,
|
|
position = 0,
|
|
count = mech->GetSubsystemCount();
|
|
|
|
if (subsystem->IsDerivedFrom(PoweredSubsystem::ClassDerivations))
|
|
{
|
|
int authored =
|
|
((PoweredSubsystem *)subsystem)->GetAuxScreenNumber();
|
|
if (authored > 0)
|
|
{
|
|
return authored;
|
|
}
|
|
}
|
|
|
|
for (i = 1; i < count; i++) // slot 0 = the controls mapper
|
|
{
|
|
Subsystem
|
|
*candidate = mech->GetSubsystem(i);
|
|
|
|
if (candidate == NULL || !IsPanelSubsystem(candidate))
|
|
{
|
|
continue;
|
|
}
|
|
++position;
|
|
if (candidate == subsystem)
|
|
{
|
|
return position; // 1-based aux screen
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
//###########################################################################
|
|
// GeneratorCluster (config keyword "GeneratorCluster" -- the 4 generator
|
|
// engineering panels, buttons 9-12 / GeneratorA..D; vtable PTR_FUN_0051a0a0)
|
|
// Make @004c7368 / ctor @004c746c / dtor @004c7778.
|
|
//###########################################################################
|
|
//###########################################################################
|
|
|
|
//
|
|
// CFG shape (L4GAUGE.CFG:5046): NO leading rate token; p[0] = the mode mask
|
|
// (the panel's own rate is hard-wired 0xFFFF). KEYWORD + PARAMETER LIST ARE
|
|
// PARSE-CRITICAL -- the interpreter assigns opcodes by table position and
|
|
// Save()s each parameter after the opcode byte.
|
|
//
|
|
MethodDescription
|
|
GeneratorCluster::methodDescription =
|
|
{
|
|
"GeneratorCluster",
|
|
GeneratorCluster::Make,
|
|
{
|
|
{ ParameterDescription::typeModeMask, NULL }, // p[0] mode mask (ModeAlwaysActive)
|
|
{ ParameterDescription::typeString, NULL }, // p[1] generator name (GeneratorA..D)
|
|
{ ParameterDescription::typeString, NULL }, // p[2] background pcc
|
|
{ ParameterDescription::typeColor, NULL }, // p[3] extra/leak colorA
|
|
{ ParameterDescription::typeString, NULL }, // p[4] temperature tile
|
|
{ ParameterDescription::typeColor, NULL }, // p[5] temp bg color
|
|
{ ParameterDescription::typeColor, NULL }, // p[6] temp fill color
|
|
{ ParameterDescription::typeString, NULL }, // p[7] voltage tile
|
|
{ ParameterDescription::typeColor, NULL }, // p[8] voltage fg color
|
|
{ ParameterDescription::typeString, NULL }, // p[9] leak pcc
|
|
{ ParameterDescription::typeColor, NULL }, // p[10] leak colorB
|
|
{ ParameterDescription::typeString, NULL }, // p[11] lamp A pcc
|
|
{ ParameterDescription::typeString, NULL }, // p[12] lamp B pcc
|
|
{ ParameterDescription::typeColor, NULL }, // p[13] lamp on color
|
|
{ ParameterDescription::typeColor, NULL }, // p[14] lamp off color
|
|
PARAMETER_DESCRIPTION_END
|
|
}
|
|
};
|
|
|
|
//
|
|
// @004c7368 -- Make. Resolve the named generator (config p[1], e.g.
|
|
// "GeneratorA"); warn + bail if absent. Native Make pattern: plain new +
|
|
// Register_Object (the binary's operator new(0xb4) + in-place construct).
|
|
//
|
|
Logical
|
|
GeneratorCluster::Make(
|
|
int display_port_index,
|
|
Vector2DOf<int> position,
|
|
Entity *entity,
|
|
GaugeRenderer *gauge_renderer
|
|
)
|
|
{
|
|
ParameterDescription
|
|
*p = methodDescription.parameterList;
|
|
|
|
Check(gauge_renderer);
|
|
|
|
Subsystem
|
|
*subsystem = entity->FindSubsystem(p[1].data.string);
|
|
|
|
if (subsystem == NULL)
|
|
{
|
|
DEBUG_STREAM << "Subsystem " << p[1].data.string << " not found\n";
|
|
return False;
|
|
}
|
|
|
|
GeneratorCluster
|
|
*gauge = new GeneratorCluster(
|
|
p[0].data.modeMask, // mode (ModeAlwaysActive)
|
|
(L4GaugeRenderer *)gauge_renderer,
|
|
0, // owner_ID (binary hard-wires 0)
|
|
display_port_index, // graphics_port_number
|
|
position.x, position.y, // panel origin
|
|
subsystem, // resolved generator
|
|
p[2].data.string, // background image
|
|
p[3].data.color, // extra/leak colorA
|
|
p[4].data.string, // temperature tile
|
|
p[5].data.color, // temp bg color
|
|
p[6].data.color, // temp fill color
|
|
p[7].data.string, // voltage tile
|
|
p[8].data.color, // voltage fg color
|
|
p[9].data.string, // leak image
|
|
p[10].data.color, // leak colorB
|
|
p[11].data.string, // lampA image
|
|
p[12].data.string, // lampB image
|
|
p[13].data.color, // lamp on color
|
|
p[14].data.color, // lamp off color
|
|
"GeneratorCluster");
|
|
Register_Object(gauge);
|
|
|
|
return True;
|
|
}
|
|
|
|
//
|
|
// @004c746c -- ctor (vtable PTR_FUN_0051a0a0). GraphicGauge base (own rate
|
|
// hard-wired 0xFFFF; mode from config). Resolves the generator's feeds,
|
|
// interns + AddRefs the panel background pixmap, builds the 5 child gauges
|
|
// into this panel's port at offsets relative to the panel origin (x,y).
|
|
//
|
|
// FEEDS: outputVoltage / generatorOn are PUBLIC Generator members
|
|
// (powersub.hpp:413/411) -- LIVE. The temperatures and the coolant leak are
|
|
// bound by NAME (unpublished by the source410 heat wave -> unbound cells
|
|
// until it lands). The leak gauge's `third` (binary *(subsys+0x150), a raw
|
|
// 1995-layout read) is INERT 0 -- the field is stored-but-unused by the base
|
|
// BitMapInverseWipe::Execute, so the constant is behaviour-neutral.
|
|
//
|
|
GeneratorCluster::GeneratorCluster(
|
|
ModeMask mode_mask,
|
|
L4GaugeRenderer *renderer_in,
|
|
int owner_ID,
|
|
int graphics_port_number,
|
|
int x,
|
|
int y,
|
|
Subsystem *subsystem_in,
|
|
const char *background_image,
|
|
int extra_color,
|
|
const char *temp_tile,
|
|
int temp_bg_color,
|
|
int temp_fill_color,
|
|
const char *voltage_tile,
|
|
int voltage_fg_color,
|
|
const char *leak_image,
|
|
int leak_color_b,
|
|
const char *lampA_image,
|
|
const char *lampB_image,
|
|
int lamp_on_color,
|
|
int lamp_off_color,
|
|
const char *identification_string
|
|
):
|
|
GraphicGauge((GaugeRate)Gauge::gaugeRate_A, mode_mask, renderer_in,
|
|
owner_ID, graphics_port_number, identification_string)
|
|
{
|
|
Check_Pointer(this);
|
|
|
|
L4Warehouse
|
|
*warehouse = (L4Warehouse *)renderer_in->warehousePointer;
|
|
|
|
GaugeRate
|
|
rate1 = ChildRate(), // VertTwoPartBar
|
|
rate2 = ChildRate(), // ScalarBarGauge
|
|
rate3 = ChildRate(), // LeakGauge
|
|
rate4 = ChildRate(); // both lamps
|
|
|
|
Scalar
|
|
*currentTemp = ScalarAttributeOf(subsystem_in, "CurrentTemperature"),
|
|
*degradeTemp = ScalarAttributeOf(subsystem_in, "DegradationTemperature"),
|
|
*failTemp = ScalarAttributeOf(subsystem_in, "FailureTemperature"),
|
|
*coolantLeak = ScalarAttributeOf(subsystem_in, "CoolantMassLeakRate");
|
|
|
|
//
|
|
// LIVE generator feeds through the public members (a config typo could
|
|
// name a non-generator, so the derivation is checked).
|
|
//
|
|
Scalar
|
|
*outputVolt = &UnboundScalarSource;
|
|
int
|
|
*generatorOn = &UnboundIntegerSource;
|
|
|
|
if (subsystem_in != NULL &&
|
|
subsystem_in->IsDerivedFrom(Generator::ClassDerivations))
|
|
{
|
|
outputVolt = &((Generator *)subsystem_in)->outputVoltage;
|
|
generatorOn = &((Generator *)subsystem_in)->generatorOn; // binary subsys+0x1D4
|
|
}
|
|
|
|
localView.SetOrigin(x, y); // vtbl+0x10
|
|
secondaryColor = leak_color_b; // @0x94 (stored; unread here)
|
|
|
|
backgroundImage = nameCopy(background_image); // @0x90 (config string is transient)
|
|
warehouse->pixelMap8Bin.Get(backgroundImage, False); // AddRef
|
|
|
|
//------------------------------------------------------------
|
|
// child 1: vertical two-part temperature bar
|
|
//------------------------------------------------------------
|
|
temperatureBar = new VertTwoPartBar(rate1, mode_mask, renderer_in,
|
|
graphics_port_number,
|
|
x + 5, y + 0x29, x + 0xC, y + 0x5C,
|
|
temp_tile, temp_bg_color, temp_fill_color, extra_color,
|
|
currentTemp, degradeTemp, failTemp, "VertTwoPartBar");
|
|
Register_Object(temperatureBar);
|
|
|
|
//------------------------------------------------------------
|
|
// child 2: generator-voltage bar 0..12000 (@004c72ac variant)
|
|
//------------------------------------------------------------
|
|
voltageBar = new ScalarBarGauge(rate2, mode_mask, renderer_in,
|
|
graphics_port_number,
|
|
x + 0x18, y + 0x29, x + 0x1C, y + 0x5C,
|
|
voltage_tile, voltage_fg_color, extra_color,
|
|
WipeGaugeScalar::wipeUp, 0.0f, GeneratorMaxVoltage, outputVolt,
|
|
"GeneratorVoltage(scalar)");
|
|
Register_Object(voltageBar);
|
|
|
|
//------------------------------------------------------------
|
|
// child 3: coolant-leak inverse-wipe (third INERT, frames=3)
|
|
//------------------------------------------------------------
|
|
leakGauge = new BitMapInverseWipe(rate3, mode_mask, renderer_in,
|
|
graphics_port_number,
|
|
x, y + 0x27, leak_image, extra_color, leak_color_b,
|
|
0 /*third: binary *(subsys+0x150); stored-unused*/, 3 /*frames*/,
|
|
coolantLeak, "LeakGauge");
|
|
Register_Object(leakGauge);
|
|
|
|
//------------------------------------------------------------
|
|
// child 4/5: two status lamps (generatorOn -- binary subsys+0x1D4)
|
|
//------------------------------------------------------------
|
|
lampA = new TwoState(rate4, mode_mask, renderer_in, (unsigned)owner_ID,
|
|
graphics_port_number,
|
|
x + 0xC, y + 2, lampA_image, lamp_on_color, lamp_off_color,
|
|
generatorOn, "TwoState");
|
|
Register_Object(lampA);
|
|
|
|
lampB = new TwoState(rate4, mode_mask, renderer_in, (unsigned)owner_ID,
|
|
graphics_port_number,
|
|
x + 2, y + 2, lampB_image, lamp_on_color, lamp_off_color,
|
|
generatorOn, "TwoState");
|
|
Register_Object(lampB);
|
|
|
|
if (getenv("BT_VIS_LOG"))
|
|
DEBUG_STREAM << "[gen] port=" << graphics_port_number
|
|
<< " x=" << x << " y=" << y
|
|
<< " lampA='" << lampA_image << "' at " << (x + 0xC) << "," << (y + 2)
|
|
<< " lampB='" << lampB_image << "' at " << (x + 2) << "," << (y + 2)
|
|
<< " on=" << *generatorOn
|
|
<< " col0=" << lamp_on_color << " col1=" << lamp_off_color
|
|
<< "\n" << flush;
|
|
|
|
subsystem = subsystem_in; // @0xAC
|
|
_reserved0xB0 = 0; // @0xB0 (binary leaves uninit; zeroed)
|
|
|
|
Check_Fpu();
|
|
}
|
|
|
|
//
|
|
// @004c7778 -- dtor. Release the background pixmap + free the interned name.
|
|
// The 5 children are NOT deleted here (faithful to the binary: each
|
|
// self-registered with the renderer via the base Gauge ctor and is owned
|
|
// there -- deleting would double-free with the renderer chain teardown).
|
|
//
|
|
GeneratorCluster::~GeneratorCluster()
|
|
{
|
|
Check(this);
|
|
|
|
L4Warehouse
|
|
*warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
|
|
warehouse->pixelMap8Bin.Release(backgroundImage);
|
|
Unregister_Pointer(backgroundImage);
|
|
delete[] backgroundImage;
|
|
backgroundImage = NULL;
|
|
}
|
|
|
|
//
|
|
// BecameActive -- paint the panel's static background pixmap. MUST override
|
|
// the default (which self-inactivates the panel); a real paint keeps the
|
|
// panel active so its 5 self-registered child gauges keep drawing.
|
|
//
|
|
void
|
|
GeneratorCluster::BecameActive()
|
|
{
|
|
L4Warehouse
|
|
*warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
PixelMap8
|
|
*bg = warehouse->pixelMap8Bin.Get(backgroundImage); // already AddRef'd by the ctor
|
|
|
|
if (bg != NULL)
|
|
{
|
|
localView.MoveToAbsolute(0, 0); // vtbl+0x24
|
|
localView.DrawPixelMap8(True, 0, bg, 0, 0, // vtbl+0x5C (opaque, full image)
|
|
bg->Data.Size.x - 1, bg->Data.Size.y - 1);
|
|
}
|
|
warehouse->pixelMap8Bin.Release(backgroundImage); // balance this Get
|
|
}
|
|
|
|
//
|
|
// Execute -- the panel itself has no per-frame content (the temp / voltage /
|
|
// leak / lamp children each Execute independently), but Execute MUST be
|
|
// overridden: the engine base Gauge::Execute is Fail("not overridden")
|
|
// (GAUGE.CPP:598; the 1995 base was a no-op).
|
|
//
|
|
void
|
|
GeneratorCluster::Execute()
|
|
{
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
//###########################################################################
|
|
// PrepEngrScreen @004c7b30 Make / @004c7bf0 ctor
|
|
//###########################################################################
|
|
//###########################################################################
|
|
|
|
//
|
|
// FUN_004c79c8 -- draw the three status-label cells (+ optional top erase
|
|
// box). Each cell blits its label .pcc, or blanks the cell. The absolute
|
|
// cell rectangles are the binary's exact coordinates. (The C-cell compound
|
|
// blank: the binary uses an extra GraphicsView relative-fill (99,10); a
|
|
// plain fill at the same cell is the marked faithful-enough substitute.)
|
|
//
|
|
static void
|
|
DrawStatusCells(
|
|
GraphicsView *view,
|
|
L4Warehouse *warehouse,
|
|
int topBox,
|
|
const char *A,
|
|
const char *B,
|
|
const char *C
|
|
)
|
|
{
|
|
if (topBox)
|
|
{
|
|
view->SetColor(0);
|
|
view->MoveToAbsolute(0x97, 0x80);
|
|
view->DrawFilledRectangleToAbsolute(0x17d, 0x13b);
|
|
}
|
|
if (A == NULL)
|
|
{
|
|
view->SetColor(0);
|
|
view->MoveToAbsolute(0x15a, 0x145);
|
|
view->DrawFilledRectangleToAbsolute(0x17f, 0x159);
|
|
}
|
|
else
|
|
{
|
|
view->SetColor(0xff);
|
|
DrawPortBackground(view, warehouse, 0x15a, 0x145, A);
|
|
}
|
|
if (B == NULL)
|
|
{
|
|
view->SetColor(0);
|
|
view->MoveToAbsolute(0x85, 0x58);
|
|
view->DrawFilledRectangleToAbsolute(0x17f, 0x7c);
|
|
}
|
|
else
|
|
{
|
|
view->SetColor(0xff);
|
|
DrawPortBackground(view, warehouse, 0x85, 0x58, B);
|
|
}
|
|
if (C == NULL)
|
|
{
|
|
view->SetColor(0);
|
|
view->MoveToAbsolute(0x2a, 0x34);
|
|
view->DrawFilledRectangleToAbsolute(0x9f, 0x2d); // (compound-blank substitute)
|
|
}
|
|
else
|
|
{
|
|
view->SetColor(0xff);
|
|
DrawPortBackground(view, warehouse, 0x2a, 0x34, C);
|
|
}
|
|
}
|
|
|
|
//
|
|
// CFG shape (L4GAUGE.CFG:4595): mode, int screen(1..12), then 7 .pcc names
|
|
// (font + 6 labels). 9 params. PARSE-CRITICAL.
|
|
//
|
|
MethodDescription
|
|
PrepEngrScreen::methodDescription =
|
|
{
|
|
"prepEngr",
|
|
PrepEngrScreen::Make,
|
|
{
|
|
{ ParameterDescription::typeModeMask, NULL }, // p[0] mode (ModeMFD*Eng*)
|
|
{ ParameterDescription::typeInteger, NULL }, // p[1] screen number 1..12
|
|
{ ParameterDescription::typeString, NULL }, // p[2] font helv42.pcc -> statusImage[0]
|
|
{ ParameterDescription::typeString, NULL }, // p[3] escnd.pcc -> statusImage[1]
|
|
{ ParameterDescription::typeString, NULL }, // p[4] edmg.pcc -> statusImage[2]
|
|
{ ParameterDescription::typeString, NULL }, // p[5] bteslev.pcc -> statusImage[3]
|
|
{ ParameterDescription::typeString, NULL }, // p[6] bteunjm.pcc -> statusImage[4]
|
|
{ ParameterDescription::typeString, NULL }, // p[7] espeed.pcc -> statusImage[5]
|
|
{ ParameterDescription::typeString, NULL }, // p[8] btesrnge.pcc -> statusImage[6]
|
|
PARAMETER_DESCRIPTION_END
|
|
}
|
|
};
|
|
|
|
//
|
|
// @004c7b30 -- Make. Validate the screen number (p[1], 1..12) then
|
|
// construct. The GraphicGaugeBackground base self-registers with the
|
|
// renderer, so BecameActive fires whenever its Eng screen is selected.
|
|
//
|
|
Logical
|
|
PrepEngrScreen::Make(
|
|
int display_port_index,
|
|
Vector2DOf<int> /*position -- PrepEngr draws at absolute coords*/,
|
|
Entity *entity,
|
|
GaugeRenderer *gauge_renderer
|
|
)
|
|
{
|
|
ParameterDescription
|
|
*p = methodDescription.parameterList;
|
|
|
|
int
|
|
screen = p[1].data.integer;
|
|
|
|
if (screen < 1 || screen > 12)
|
|
{
|
|
DEBUG_STREAM << "PrepEngr: screen number out of range " << screen << "\n";
|
|
return False;
|
|
}
|
|
|
|
PrepEngrScreen
|
|
*gauge = new PrepEngrScreen(
|
|
p[0].data.modeMask, // mode (ModeMFD*Eng*)
|
|
(L4GaugeRenderer *)gauge_renderer,
|
|
0, // owner_ID (binary hard-wires 0)
|
|
display_port_index, // graphics_port_number
|
|
entity, // mech
|
|
screen, // screen number
|
|
p[2].data.string, p[3].data.string, p[4].data.string,
|
|
p[5].data.string, p[6].data.string, p[7].data.string,
|
|
p[8].data.string,
|
|
"PrepEngr");
|
|
Register_Object(gauge);
|
|
|
|
return True;
|
|
}
|
|
|
|
//
|
|
// @004c7bf0 -- ctor. GraphicGaugeBackground base; store screen + mech (FIX
|
|
// kept: an early transcript SWAPPED them); intern the 7 image names (the
|
|
// config strings are transient) and pre-load (AddRef) each from the cache.
|
|
//
|
|
PrepEngrScreen::PrepEngrScreen(
|
|
ModeMask mode_mask,
|
|
L4GaugeRenderer *renderer_in,
|
|
unsigned int owner_ID,
|
|
int graphics_port_number,
|
|
Entity *mech_in,
|
|
int screen_number,
|
|
const char *img0,
|
|
const char *img1,
|
|
const char *img2,
|
|
const char *img3,
|
|
const char *img4,
|
|
const char *img5,
|
|
const char *img6,
|
|
const char *identification_string
|
|
):
|
|
GraphicGaugeBackground(mode_mask, renderer_in, owner_ID,
|
|
graphics_port_number, identification_string)
|
|
{
|
|
int
|
|
i;
|
|
const char
|
|
*source[7];
|
|
|
|
Check_Pointer(this);
|
|
|
|
screenNumber = screen_number; // @0x6C
|
|
mech = mech_in; // @0x70
|
|
|
|
source[0] = img0; source[1] = img1; source[2] = img2;
|
|
source[3] = img3; source[4] = img4; source[5] = img5;
|
|
source[6] = img6;
|
|
|
|
L4Warehouse
|
|
*warehouse = (L4Warehouse *)renderer_in->warehousePointer;
|
|
|
|
for (i = 0; i < 7; i++)
|
|
{
|
|
if (source[i] != NULL)
|
|
{
|
|
statusImage[i] = nameCopy(source[i]);
|
|
warehouse->bitMapBin.Get(statusImage[i]); // pre-load/AddRef
|
|
}
|
|
else
|
|
{
|
|
statusImage[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// @004c7d14 -- dtor. Release the 7 pre-loaded images + free their name
|
|
// copies; the localView + base dtor run implicitly.
|
|
//
|
|
PrepEngrScreen::~PrepEngrScreen()
|
|
{
|
|
int
|
|
i;
|
|
|
|
Check(this);
|
|
|
|
L4Warehouse
|
|
*warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
|
|
for (i = 0; i < 7; i++)
|
|
{
|
|
if (statusImage[i] != NULL)
|
|
{
|
|
warehouse->bitMapBin.Release(statusImage[i]);
|
|
Unregister_Pointer(statusImage[i]);
|
|
delete[] statusImage[i];
|
|
statusImage[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// @004c7e48 -- BecameActive. Find the subsystem assigned to this screen and
|
|
// paint its labels, dispatched on ClassID. NON-inactivating (a real paint;
|
|
// does not chain the base) -> the paint-on-activation contract.
|
|
//
|
|
// STAND-INS at this site (see the aux-screen ledger above):
|
|
// * screen match = the sequential roster assignment;
|
|
// * numeric #2 (the linked heat-sink number, binary sink+0x1d4 ==
|
|
// Condenser::condenserNumber -- protected, no accessor) draws a static 0;
|
|
// * the subsystem label bitmap (binary sub+0x224 name -> @(0x125,0x172))
|
|
// is skipped -- no live auxScreenLabel.
|
|
//
|
|
void
|
|
PrepEngrScreen::BecameActive()
|
|
{
|
|
int
|
|
i,
|
|
count;
|
|
const char
|
|
*font;
|
|
|
|
if (mech == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
L4Warehouse
|
|
*warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
|
|
font = statusImage[0]; // helv42.pcc
|
|
if (font == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
count = mech->GetSubsystemCount();
|
|
for (i = 1; i < count; i++) // skip slot 0 (controls mapper)
|
|
{
|
|
Subsystem
|
|
*sub = mech->GetSubsystem(i);
|
|
|
|
if (sub == NULL || !IsPanelSubsystem(sub))
|
|
{
|
|
continue;
|
|
}
|
|
if (AuxScreenAssignmentOf(mech, sub) != screenNumber)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//---- numeric #1 : the screen number, 2 digits @(0xE9,0x176)
|
|
{
|
|
NumericDisplay
|
|
num(warehouse, 0xe9, 0x176, font, 2,
|
|
NumericDisplay::unsignedFormat, 0, 0xff);
|
|
|
|
num.Draw(&localView, (Scalar)screenNumber);
|
|
}
|
|
|
|
//---- numeric #2 : linked heat-sink number, 1 digit @(0x15E,5)
|
|
// INERT 0 (Condenser::condenserNumber unpublished -- see ledger).
|
|
{
|
|
NumericDisplay
|
|
num(warehouse, 0x15e, 5, font, 1,
|
|
NumericDisplay::unsignedFormat, 0, 0xff);
|
|
|
|
num.Draw(&localView, 0.0f);
|
|
}
|
|
|
|
//---- subsystem label bitmap @(0x125,0x172): the AUTHORED aux-screen
|
|
// label, cached on PoweredSubsystem at ctor time (the streamed
|
|
// resource dies with the Mech ctor's buffer). This is the panel
|
|
// title art -- "SENSOR CLUSTER", "MYOMERS", the weapon name and
|
|
// range strip.
|
|
//
|
|
if (sub->IsDerivedFrom(PoweredSubsystem::ClassDerivations))
|
|
{
|
|
const char *label =
|
|
((PoweredSubsystem *)sub)->GetAuxScreenLabel();
|
|
if (label != NULL && *label != ' |