y+0xb3 -> y+0xb4. Earlier this session I measured 0xb3 as the optimum and wrote that the banner was at most 1-2px off -- that reading was confounded: the strip-art misplacement (5.3.35) was ten times larger and overlapped the same bands, so it dominated the metric and hid the one-row error. With the strip art correct the title fringe isolated cleanly at 553/553 missing/extra per head, and +1 collapsed it. head missing extra Mfd1 1107 -> 140 1646 -> 679 Mfd2 1616 -> 97 1981 -> 462 Mfd3 584 -> 31 1014 -> 461 Cockpit: 97.9% identical / 97% coverage, from 89.7%/92% at the start of the session. Method note for the worksheet: fix the largest error on a head FIRST, then re-measure the small ones -- a big overlapping defect makes a small one measure as noise, or as already-optimal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3119 lines
105 KiB
C++
3119 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 != ' |