Reconstructed the 4th unbuilt widget (the 4 generator engineering panels, buttons 9-12) + its companions, but left it UNREGISTERED pending a runtime-abort fix: - Generator OutputVoltage attribute table (powersub) -> outputVoltage@0x1DC, chained to HeatSink's dense index (temps already reach it). Correct + reusable. - ScalarBarGauge @004c72ac plain-Scalar variant (btl4gau2) -- the DIRECT-Scalar* bar the GeneratorCluster voltage bar needs (the existing @004c721c takes a resolved source object). Correct + harmless. - GeneratorCluster Make/ctor/dtor + 15-param methodDescription reconstructed from part_014.c:891-1013 (replacing the prose + placeholder Make + the bogus DAT pool); builds the 5 children (temp bar / voltage bar / leak wipe / 2 lamps). Header fixed to the binary ctor param order (secondaryColor@0x94, +_reserved0xB0 -> sizeof 0xB4). ChildRate forward-declared. ⚠ REGISTRATION GATED (commented out in BTL4MethodDescription[]): registering it aborts at runtime -- the 4 panels build (the 4 "BecameActive not defined" warnings fire) then abort(). ISOLATED to the PARENT lifecycle: the abort PERSISTS with all 5 children nulled; there are no pure virtuals; Execute is SEH-guarded under BT_DEV_GAUGES so the fault is in the unguarded ctor/lifecycle path. The sibling SubsystemCluster overrides BecameActive/Execute/TestInstance whereas the decode has GeneratorCluster overriding ONLY the dtor -- that "inherits only the dtor" claim is the prime suspect. Deferred; the 3 other P2 widgets (LeakGauge/VertNormalSlider/PilotList) ship clean. Verified: unregistered -> parse-skips -> no abort; combat un-regressed (DESTROYED), 0 crashes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1689 lines
68 KiB
C++
1689 lines
68 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). Behaviour follows the
|
|
// Ghidra pseudo-C in recovered/all/part_014.c (@004c6798..@004c9b50). Class /
|
|
// member names taken from the CODE name-string pool; per-method @ADDR evidence
|
|
// is cited inline. This TU links between btl4gaug.obj and btl4gau3.obj.
|
|
//
|
|
// Engine-internal helper map (consistent with btl4gaug.cpp / btl4rdr.cpp):
|
|
// FUN_00444818 GraphicGauge ctor FUN_00444870 GraphicGauge dtor
|
|
// FUN_004448ac GraphicGauge::TestInstance
|
|
// FUN_004440d4 Gauge-group ctor FUN_00443f7c Gauge-group dtor
|
|
// FUN_0044ae0c ChainOf<Gauge*> dtor (PrepEngrScreen child list)
|
|
// FUN_004442b8 default GaugeRate FUN_004442dc default ModeMask
|
|
// FUN_00444290 alt default rate/mask
|
|
// FUN_004700ac GaugeRenderer::InternName FUN_004022e8 FreeName
|
|
// FUN_00442aec/00442c12 BitMapCache AddRef/Release (renderer+0x4C, +4)
|
|
// FUN_00442d2b/00442e51 PixMapCache AddRef/Release (renderer+0x4C, +0x20)
|
|
// FUN_0041bfc0 Entity::FindAttributeIndex(name)
|
|
// FUN_0041f98c Entity::FindSubObject(name)
|
|
// FUN_00447f1c GaugeRenderer::InstallPort / FUN_00447f84 GetGraphicsPort
|
|
// FUN_00417ab4 SharedData::Resolve / operator Entity*
|
|
// FUN_0041a1a4 IsDerivedFrom(classDerivations) (0x50f4bc = Generator)
|
|
// FUN_0042076c Subsystem::FindDamageZone(record) -> index
|
|
// FUN_004dcd94 Round(Scalar) -> int
|
|
// FUN_004700bc NumericDisplay ctor FUN_0047018c ::dtor
|
|
// FUN_004703f4 NumericDisplay::Reset FUN_00470430 ::DrawAt FUN_00470758 ::Erase
|
|
// FUN_00470ec8 NumericDisplay::SetColors FUN_00470cfc NumericDisplayInteger ctor
|
|
// FUN_00470888 NumericDisplayScalar ctor FUN_00473c94 NumericDisplayScalarTwoState
|
|
// FUN_004739d8 TwoState ctor FUN_00471d00 BackgroundBitmap ctor
|
|
// FUN_00472ef0 BarGraph ctor FUN_00472fb4 BarGraph dtor
|
|
// FUN_00474855 GaugeConnectionDirectOf<Scalar>::ctor FUN_004749de <int>
|
|
// FUN_004c2ec4 DrawClippedBitmap (file-private, btl4gaug) FUN_004c2f88 -> 7
|
|
// FUN_004c3134/004c31ec/004c3288 shared GaugeConnection ctors (btl4gaug TU)
|
|
// FUN_004c4170 HorizTwoPartBar FUN_004c4724 VertTwoPartBar
|
|
// FUN_004c4d88 OneOfSeveral FUN_004c5148 OneOfSeveralInt
|
|
// FUN_004c5470 OneOfSeveralStates FUN_004c5b7c LeakGauge(BitMapInverseWipe)
|
|
// FUN_004c61c8 BitMapInverseWipeScalar FUN_004c6244 SegmentArc270
|
|
// FUN_004c3b98 ColorMapperArmor ctor (btl4gaug)
|
|
// FUN_0040385c Verify FUN_004dbb24 DebugStream<<char* FUN_004db78c <<int
|
|
// DAT_00524e20 DebugStream (warning channel) DAT_004efc94 the Application
|
|
//
|
|
// 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 base
|
|
// _DAT_004c8df0 heat numeric scale _DAT_004c92e0 warning threshold
|
|
// _DAT_004c6bdc/_DAT_004c6be0 seek-curve plot step/limit
|
|
// DAT_0051936c PrepEngr engineer-screen number (1..12)
|
|
//
|
|
|
|
// See btl4gaug.cpp: suppress heat.hpp's optional DebugStream so the shared
|
|
// mechrecon ReconStream definition is the only one active in this TU.
|
|
#define BT_DEBUGSTREAM_DEFINED
|
|
|
|
#include <bt.hpp>
|
|
#pragma hdrstop
|
|
|
|
#if !defined(BTL4GAU2_HPP)
|
|
# include <btl4gau2.hpp>
|
|
#endif
|
|
#if !defined(APP_HPP)
|
|
# include <app.hpp>
|
|
#endif
|
|
|
|
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
|
|
|
|
// (DebugStream warning channel, DAT_00524e20, is provided by the shared
|
|
// reconstruction headers -- heat.hpp / mechrecon.hpp.)
|
|
|
|
//
|
|
// Unrecovered .data resource pool used by GeneratorCluster::Make. Concrete
|
|
// bytes were not recovered from the optimised image; typed placeholders so the
|
|
// resolve-and-construct path compiles. (BEST-EFFORT.)
|
|
//
|
|
static char DAT_00518f24[] = "GeneratorCluster"; // subsystem name to resolve
|
|
static int DAT_00518ee0 = 0; // GaugeRate
|
|
static char DAT_00518f68[] = ""; // background image name
|
|
|
|
//
|
|
// Original FUN_0041bfc0 = Entity::FindAttributeIndex(name) -> ordinal. The
|
|
// WinTesla engine exposes attribute lookup via Simulation::AttributeIndexSet
|
|
// (Find/FindEntry by name) rather than a name->ordinal call, so this best-effort
|
|
// shim stands in for it; the stored indices are consumed by the (summarised)
|
|
// SeekVoltageGraph::Execute plot.
|
|
//
|
|
static int FindAttributeIndex(Entity *, const char *) { return -1; }
|
|
|
|
//
|
|
// FUN_0041bfc0 == Entity::FindAttributeIndex(name) returns a POINTER into the
|
|
// subsystem (subsystem + attributeMemberOffset), or 0 on miss. The composite
|
|
// cluster gauges pass that pointer straight to their child gauges as the value
|
|
// source. The WinTesla engine exposes exactly this via Simulation::
|
|
// GetAttributePointer(const char*) (SIMULATE.h), which returns a member pointer
|
|
// or &Simulation::NullAttribute (always safe to deref) -- so we resolve through
|
|
// it. A null subsystem -> 0.
|
|
//
|
|
static void *AttributePointerOf(void *subsystem, const char *name)
|
|
{
|
|
if (subsystem == NULL)
|
|
return NULL;
|
|
void *p = ((Simulation *)subsystem)->GetAttributePointer(name); // FUN_0041bfc0
|
|
// GetAttributePointer returns &NullAttribute (not 0) on a miss; the composite
|
|
// ctors gate optional children on a 0 result, so normalise the miss to NULL.
|
|
if (p == (void *)&Simulation::NullAttribute)
|
|
return NULL;
|
|
return p;
|
|
}
|
|
|
|
//
|
|
// FUN_00417ab4 -- SubsystemConnection::Resolve: follow the plug's bound link to the
|
|
// resolved subsystem. The link is `*(plug+8)`; the resolved subsystem is
|
|
// `*(link+8)`. An unbound plug (link == 0) resolves to 0 -- the authentic
|
|
// behaviour when a subsystem has no attached voltage source / cooling master (the
|
|
// connection then reads its "no source" branch, *destination = 0).
|
|
//
|
|
static void *ResolveLink(void *plug)
|
|
{
|
|
if (plug == 0)
|
|
return 0;
|
|
void *link = *(void **)((char *)plug + 8);
|
|
if (link == 0)
|
|
return 0;
|
|
return *(void **)((char *)link + 8);
|
|
}
|
|
|
|
//
|
|
// Bridge (defined in powersub.cpp) that reads the engineering-screen assignment
|
|
// off the real PoweredSubsystem type (auxScreenNumber/Placement/Label) -- the
|
|
// reconstructed heat-leaf branch is not byte-exact, so the raw sub+0x1dc/0x1e0/
|
|
// 0x1e4 offsets read garbage; the named fields (populated by the PoweredSubsystem
|
|
// ctor from the resource) are correct. Returns False for non-PoweredSubsystem-
|
|
// derived subsystems (== the vehicleSubSystems type filter).
|
|
//
|
|
extern bool BTGetSubsystemAuxScreen(Subsystem *sub, int *screen, int *placement, char *label64);
|
|
|
|
//
|
|
// FUN_0041bf44 -- GetAttributePointer(index): a pointer to the subsystem's Nth
|
|
// attribute member (the cooling-loop lamp reaches its cooling-master link at
|
|
// attribute slot 3), or 0 if the index is out of range. The engine exposes this
|
|
// via Simulation::GetAttributePointer(AttributeID); a miss returns &NullAttribute,
|
|
// normalised to 0.
|
|
//
|
|
static void *FindLinkedSubsystem(void *subsystem, int slot)
|
|
{
|
|
if (subsystem == 0)
|
|
return 0;
|
|
void *p = ((Simulation *)subsystem)->GetAttributePointer((Simulation::AttributeID)slot);
|
|
if (p == (void *)&Simulation::NullAttribute)
|
|
return 0;
|
|
return p;
|
|
}
|
|
|
|
//
|
|
// FUN_0041a1a4 IsDerivedFrom(0x50f4bc == PoweredSubsystem's ClassDerivations).
|
|
// Gates the SubsystemCluster's generator stateLamp. Every subsystem that reaches
|
|
// a SubsystemCluster passed the vehicleSubSystems type filter (BTGetSubsystemAuxScreen
|
|
// -> the SAME IsDerivedFrom(PoweredSubsystem) check), so this is always True here;
|
|
// the real check is done in powersub.cpp via BTIsPoweredSubsystem for robustness.
|
|
//
|
|
extern bool BTIsPoweredSubsystem(Subsystem *sub);
|
|
static Logical IsGeneratorDerived(Subsystem *sub) { return BTIsPoweredSubsystem(sub) ? True : False; }
|
|
|
|
|
|
//###########################################################################
|
|
// File-private GaugeConnection classes for the composite-cluster lamps.
|
|
// Each is the reconstruction of one of the three shared connection ctors
|
|
// the sub-gauges attach (mirrors the btl4gaug.cpp connection pattern: derive
|
|
// GaugeConnection, store dst/src, override Update() = the vtable sampler).
|
|
//###########################################################################
|
|
|
|
//
|
|
// CoolingLoopConnection -- @004c3134 ctor / @004c31a0 sampler (vt 0x518ea8).
|
|
// When the source subsystem's coolant loop is active (source+0x134 == 1) it
|
|
// follows the linked cooling master (FindLinkedSubsystem(source,3) then Resolve)
|
|
// and copies its loop-lamp state (+0x1d4); otherwise 0.
|
|
//
|
|
class CoolingLoopConnection : public GaugeConnection
|
|
{
|
|
public:
|
|
CoolingLoopConnection(int *destination, void *source)
|
|
: GaugeConnection(0), // FUN_00444124(this,0)
|
|
source(source), destination(destination) {}
|
|
void Update() // @004c31a0
|
|
{
|
|
void *resolved = 0;
|
|
if (source != 0 && *(int *)((char *)source + 0x134) == 1)
|
|
{
|
|
void *linked = FindLinkedSubsystem(source, 3);
|
|
if (linked != 0)
|
|
resolved = ResolveLink(linked);
|
|
}
|
|
*destination = (resolved == 0) ? 0 : *(int *)((char *)resolved + 0x1d4);
|
|
}
|
|
protected:
|
|
void *source; // @0x10 this[4]
|
|
int *destination; // @0x14 this[5]
|
|
};
|
|
|
|
//
|
|
// PowerSourceConnection -- @004c31ec ctor / @004c3258 sampler (vt 0x518e9c).
|
|
// Resolves the power source (Resolve(source)) and copies its bus-lamp state
|
|
// (+0x1e0); 0 if unresolved.
|
|
//
|
|
class PowerSourceConnection : public GaugeConnection
|
|
{
|
|
public:
|
|
PowerSourceConnection(int *destination, void *source)
|
|
: GaugeConnection(0), source(source), destination(destination) {}
|
|
void Update() // @004c3258
|
|
{
|
|
void *resolved = ResolveLink(source);
|
|
*destination = (resolved == 0) ? 0 : *(int *)((char *)resolved + 0x1e0);
|
|
}
|
|
protected:
|
|
void *source; // @0x10
|
|
int *destination; // @0x14
|
|
};
|
|
|
|
//
|
|
// GeneratorVoltageConnection -- @004c3288 ctor / @004c32f4 sampler (vt 0x518e90).
|
|
// Resolves the voltage source and copies its output voltage (+0x1dc); 0 if
|
|
// unresolved. Drives ScalarBarGauge / SeekVoltageGraph value slots.
|
|
//
|
|
class GeneratorVoltageConnection : public GaugeConnection
|
|
{
|
|
public:
|
|
GeneratorVoltageConnection(Scalar *destination, void *source)
|
|
: GaugeConnection(0), source(source), destination(destination) {}
|
|
void Update() // @004c32f4
|
|
{
|
|
void *resolved = ResolveLink(source);
|
|
*destination = (resolved == 0) ? 0.0f
|
|
: *(Scalar *)((char *)resolved + 0x1dc);
|
|
}
|
|
protected:
|
|
void *source; // @0x10
|
|
Scalar *destination; // @0x14
|
|
};
|
|
|
|
|
|
//###########################################################################
|
|
//###########################################################################
|
|
// SeekVoltageGraph @004c6798
|
|
//###########################################################################
|
|
//###########################################################################
|
|
|
|
//
|
|
// @004c6798 -- ctor (vtable PTR_FUN_0051a1fc). GraphicGauge base, interns and
|
|
// ref-counts the "destroyed" bitmap, resolves the four SeekVoltage attribute
|
|
// indices off the weapon subsystem, sets the port extent (0x97,0x80,0x17d,0x13b)
|
|
// and stashes the 230x187 plot scale.
|
|
//
|
|
SeekVoltageGraph::SeekVoltageGraph(
|
|
GaugeRate rate,
|
|
ModeMask mode_mask,
|
|
L4GaugeRenderer *renderer,
|
|
int graphics_port_number,
|
|
Entity *subsystem,
|
|
AttributeAccessor *seek_voltage_pointer,
|
|
const char *destroyed_image,
|
|
Logical draw_cursor,
|
|
const char *identification_string
|
|
):
|
|
GraphicGauge(rate, mode_mask, renderer, /*owner*/0xFFFF,
|
|
graphics_port_number, identification_string) // FUN_00444818
|
|
{
|
|
// Original interned a copy of the name (FUN_004700ac) + AddRef'd it in the
|
|
// bitmap cache; retain the persistent resource string and AddRef the bitmap.
|
|
destroyedImage = (char *)destroyed_image;
|
|
renderer->warehousePointer->bitMapBin.Get(destroyedImage); // FUN_00442aec (AddRef)
|
|
|
|
currentSeekIndex = FindAttributeIndex(subsystem, "CurrentSeekVoltageIndex"); // FUN_0041bfc0
|
|
minSeekIndex = FindAttributeIndex(subsystem, "MinSeekVoltageIndex");
|
|
maxSeekIndex = FindAttributeIndex(subsystem, "MaxSeekVoltageIndex");
|
|
seekVoltageTable = FindAttributeIndex(subsystem, "SeekVoltage");
|
|
|
|
voltageSubsystem = (Entity *)seek_voltage_pointer; // this[0x29]
|
|
this->subsystem = (Subsystem *)subsystem; // this[0x2A]
|
|
|
|
// graphicsView -> GraphicGauge::localView. SetExtent == SetPositionWithinPort;
|
|
// the original SetClip() (vtbl+0x0C) constrained drawing to that extent.
|
|
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]
|
|
}
|
|
|
|
//
|
|
// @004c68ac -- dtor. Release the destroyed bitmap, GraphicGauge::~GraphicGauge.
|
|
//
|
|
SeekVoltageGraph::~SeekVoltageGraph()
|
|
{
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
warehouse->bitMapBin.Release(destroyedImage);
|
|
}
|
|
|
|
Logical SeekVoltageGraph::TestInstance() const
|
|
{
|
|
return GraphicGauge::TestInstance();
|
|
}
|
|
|
|
//
|
|
// @004c6920 -- BecameActive: previousVoltage = 9999.0f (force first redraw).
|
|
//
|
|
void SeekVoltageGraph::BecameActive()
|
|
{
|
|
previousVoltage = RedrawSentinel; // @0xAC force first redraw
|
|
destroyedShown = 0;
|
|
}
|
|
|
|
//
|
|
// @004c6934 -- Execute (the seek-voltage curve plot). BRING-UP: the full plot
|
|
// (SetupClip/DrawTicks/DrawCursor over a 0..1 voltage polyline) needs the live
|
|
// seek-voltage sampler (subsys vtbl+0x3c) + the four attribute indices; the
|
|
// attribute resolve is summarised (FindAttributeIndex returns -1), so the plot is
|
|
// deferred and Execute is inert until the sampler + attribute reads land. Kept as
|
|
// a defined no-op so the gauge's vtable is complete (no /FORCE crash when the
|
|
// Myomer/Energy cluster ticks its SeekVoltageGraph child).
|
|
//
|
|
void SeekVoltageGraph::Execute()
|
|
{
|
|
// (seek-voltage plot deferred -- see note above.)
|
|
}
|
|
|
|
//
|
|
// @004c6934 (original notes preserved) -- Execute. If the subsystem is destroyed
|
|
// (subsys+0x40 == 1) draw
|
|
// the centred "destroyed" bitmap once. Otherwise, when the live seek voltage
|
|
// (subsys vtbl+0x3c sampler) changes: clear (SetupClip @004c6be4), re-plot the
|
|
// 0..1 voltage curve as a polyline (step _DAT_004c6bdc up to _DAT_004c6be0,
|
|
// Round each point), then draw the min/max tick marks (DrawTicks @004c6c6c)
|
|
// and, if drawCursor, the live cursor at the current seek index
|
|
// (DrawCursor @004c6c30).
|
|
//
|
|
|
|
|
|
//###########################################################################
|
|
// ConfigMapGauge @004c6d80
|
|
//###########################################################################
|
|
|
|
//
|
|
// @004c6d80 -- ctor (vtable PTR_FUN_0051a1b8). GraphicGauge base; interns the
|
|
// joystick base bitmap "btjoy.pcc" (BitMapCache AddRef) + the four config-state
|
|
// pixmaps cm_off/cm_other/cm_only/cm_both.pcc (PixMapCache AddRef); sets the port
|
|
// origin (x,y); subsystem = param_9; color = 0.
|
|
//
|
|
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*/, // FUN_00444818
|
|
graphics_port_number, identification_string)
|
|
{
|
|
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)
|
|
|
|
color = 0; // @0x94 this[0x25]
|
|
subsystem = subsystem_in; // @0x90 this[0x24]
|
|
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer_in->warehousePointer;
|
|
warehouse->bitMapBin.Get(joystickImage); // FUN_00442aec
|
|
for (int i = 0; i < 4; i++)
|
|
warehouse->pixelMap8Bin.Get(stateImage[i]); // FUN_00442d2b
|
|
}
|
|
|
|
//
|
|
// @004c6e54 -- dtor: release btjoy bitmap + 4 pixmaps, GraphicGauge::~GraphicGauge.
|
|
//
|
|
ConfigMapGauge::~ConfigMapGauge()
|
|
{
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
warehouse->bitMapBin.Release(joystickImage); // FUN_00442c12
|
|
for (int i = 0; i < 4; i++)
|
|
warehouse->pixelMap8Bin.Release(stateImage[i]); // FUN_00442e51
|
|
}
|
|
|
|
Logical
|
|
ConfigMapGauge::TestInstance() const
|
|
{
|
|
return GraphicGauge::TestInstance();
|
|
}
|
|
|
|
//
|
|
// @004c6ee0 -- SetColor: color (this[0x25]) = the passed value.
|
|
//
|
|
void
|
|
ConfigMapGauge::SetColor(int color_in) // @004c6ee0
|
|
{
|
|
color = color_in;
|
|
}
|
|
|
|
//
|
|
// @004c6ef4 -- BecameActive: force a full redraw (dirty=1, previousState = -1).
|
|
//
|
|
void
|
|
ConfigMapGauge::BecameActive()
|
|
{
|
|
dirty = True; // @0xBC this[0x2F]
|
|
for (int i = 0; i < 4; i++)
|
|
previousState[i] = -1; // @0xAC this[0x2B..0x2E]
|
|
}
|
|
|
|
//
|
|
// @004c6f1c -- Execute. When enabled (color != 0): on the dirty flag, blit the
|
|
// base joystick bitmap; then for each of the 4 config-map slots read the control-
|
|
// mapper state and, if changed, stretch-blit the matching cm_* pixmap.
|
|
//
|
|
// BRING-UP (marked): the per-slot control-mapper state comes from the App's
|
|
// ModeManager sampled through a .data port/colour table (DAT_00518eb8) at raw
|
|
// offsets (app+0x3c -> +0x1c0 + slot*0x20, sampler vtbl+0x24). That is the same
|
|
// raw-App-offset read class that AVs before the ModeManager layout is
|
|
// reconstructed (see the FillPilotArray fix, §10), and DAT_00518eb8 was not
|
|
// recovered from the optimised image -- so the mapper-state loop is guarded off
|
|
// and only the base-bitmap blit runs. The full loop lands with the ModeManager
|
|
// reconstruction.
|
|
//
|
|
void
|
|
ConfigMapGauge::Execute()
|
|
{
|
|
if (color == 0)
|
|
return;
|
|
|
|
if (dirty)
|
|
{
|
|
dirty = False;
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
BitMap *base = warehouse->bitMapBin.Get(joystickImage); // FUN_00442aec
|
|
if (base != NULL)
|
|
{
|
|
localView.SetColor(0xFF); // vtbl+0x18
|
|
localView.DrawBitMapOpaque(0 /*background*/, 0 /*rotation*/, base); // vtbl+0x54
|
|
}
|
|
warehouse->bitMapBin.Release(joystickImage);
|
|
}
|
|
// (control-mapper state loop deferred -- see note above.)
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
// OneOfSeveral data-driven lamps @004c70a4 / @004c7160
|
|
//###########################################################################
|
|
|
|
//
|
|
// @004c70a4 -- AnimatedSubsystemLamp ctor (vtable PTR_FUN_0051a174): OneOfSeveral
|
|
// (fromImageStrip=1) + a CoolingLoopConnection feeding the selected frame from
|
|
// the subsystem's cooling-loop state. Otherwise inherits OneOfSeveral::Execute.
|
|
//
|
|
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, // FUN_004c4d88
|
|
x, y, True /*fromImageStrip*/, image, 0, 0, columns, rows,
|
|
identification_string)
|
|
{
|
|
selected = 0; // @0xAC this[0x2B]
|
|
AddConnection(new CoolingLoopConnection(&selected, subsystem)); // FUN_004c3134
|
|
}
|
|
|
|
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 bus-lamp state.
|
|
//
|
|
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, 0, 0, columns, rows, identification_string)
|
|
{
|
|
selected = 0;
|
|
AddConnection(new PowerSourceConnection(&selected, source)); // FUN_004c31ec
|
|
}
|
|
|
|
AnimatedSourceLamp::~AnimatedSourceLamp() {} // @004c71f0
|
|
|
|
|
|
//###########################################################################
|
|
// ScalarBarGauge @004c721c
|
|
//###########################################################################
|
|
|
|
//
|
|
// @004c721c -- ctor (vtable PTR_FUN_0051a0e4). Chains the MUNGA L4 bar primitive
|
|
// (@00472ef0 == BarGraphBitMapScalar) with no auto-connection (value=NULL), then
|
|
// AddConnection(new GeneratorVoltageConnection @004c3288) writing the inherited
|
|
// WipeGaugeScalar::currentValue (@0xB4, this[0x2D]) from the resolved voltage
|
|
// source. Generator-voltage bar, range 0..12000.0f. dtor @004c733c chains the
|
|
// bar dtor @00472fb4 (implicit).
|
|
//
|
|
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*/, // FUN_00472ef0
|
|
graphics_port_number, left, bottom, right, top, tile_image,
|
|
foreground_color, background_color, direction, min, max,
|
|
(Scalar *)0 /*no auto-connection*/, identification_string)
|
|
{
|
|
AddConnection(new GeneratorVoltageConnection(¤tValue, voltage_source)); // FUN_004c3288
|
|
}
|
|
|
|
ScalarBarGauge::~ScalarBarGauge() {} // @004c733c (base chain)
|
|
|
|
|
|
//###########################################################################
|
|
//###########################################################################
|
|
// GeneratorCluster (config keyword "GeneratorCluster" -- the 4 generator
|
|
// engineering panels, buttons 9-12 / GeneratorA..D; vtable PTR_FUN_0051a0a0).
|
|
// Reconstructed from part_014.c:891-1013 (Make @004c7368 / ctor @004c746c /
|
|
// dtor @004c7778). Was PROSE + a placeholder Make -> the "GeneratorCluster"
|
|
// config lines parse-skipped and the 4 panels never built. Only the dtor is
|
|
// overridden; Execute/BecameActive/TestInstance are inherited from GraphicGauge.
|
|
//###########################################################################
|
|
//###########################################################################
|
|
|
|
static inline GaugeRate ChildRate(); // fwd-decl (defined later, after SubsystemCluster)
|
|
|
|
//
|
|
// @004c72ac -- ScalarBarGauge plain-Scalar variant. Same bar body as @004c721c but
|
|
// a GaugeConnectionDirectOf<Scalar> from a DIRECT Scalar* (the generator's
|
|
// OutputVoltage attribute pointer) instead of a GeneratorVoltageConnection.
|
|
//
|
|
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, // DIRECT scalar
|
|
const char *identification_string
|
|
):
|
|
BarGraphBitMapScalar(rate, mode_mask, renderer_in, 0 /*owner*/, // FUN_00472ef0
|
|
graphics_port_number, left, bottom, right, top, tile_image,
|
|
foreground_color, background_color, direction, min, max,
|
|
(Scalar *)0 /*no auto-connection*/, identification_string)
|
|
{
|
|
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, ¤tValue, value_source)); // FUN_00474855
|
|
}
|
|
|
|
// CFG shape (L4GAUGE.CFG:5046): NO leading rate token; p[0] = the mode mask (the
|
|
// panel's own rate is hard-wired 0xFFFF).
|
|
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. Then alloc 0xB4 + construct.
|
|
//
|
|
Logical
|
|
GeneratorCluster::Make(
|
|
int display_port_index,
|
|
Vector2DOf<int> position,
|
|
Entity *entity,
|
|
GaugeRenderer *gauge_renderer
|
|
)
|
|
{
|
|
ParameterDescription *p = methodDescription.parameterList;
|
|
|
|
Subsystem *subsystem = entity->FindSubsystem(p[1].data.string); // FUN_0041f98c (GeneratorA..D)
|
|
if (subsystem == NULL)
|
|
{
|
|
DebugStream << "Subsystem " << p[1].data.string << " not found\n";
|
|
return False;
|
|
}
|
|
|
|
GeneratorCluster *gauge = (GeneratorCluster *)operator new(0xb4); // FUN_00402298(0xb4)
|
|
if (gauge != NULL)
|
|
{
|
|
new (gauge) GeneratorCluster(
|
|
p[0].data.modeMask, // mode (ModeAlwaysActive)
|
|
(L4GaugeRenderer *)gauge_renderer,
|
|
0, // owner_ID (raw 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");
|
|
}
|
|
return True;
|
|
}
|
|
|
|
//
|
|
// @004c746c -- ctor (vtable PTR_FUN_0051a0a0). GraphicGauge base (own rate hard-
|
|
// wired 0xFFFF; mode from config). Resolve the generator's temp/voltage attributes,
|
|
// intern + AddRef the panel background pixmap, build the 5 child gauges into this
|
|
// panel's port at offsets relative to the panel origin (x,y).
|
|
//
|
|
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)0xFFFF, mode_mask, renderer_in, owner_ID, // FUN_00444818
|
|
graphics_port_number, identification_string)
|
|
{
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer_in->warehousePointer;
|
|
|
|
GaugeRate rate1 = ChildRate(); // VertTwoPartBar
|
|
GaugeRate rate2 = ChildRate(); // ScalarBarGauge
|
|
GaugeRate rate3 = ChildRate(); // LeakGauge
|
|
GaugeRate rate4 = ChildRate(); // both lamps
|
|
|
|
Scalar *currentTemp = (Scalar *)AttributePointerOf(subsystem_in, "CurrentTemperature");
|
|
Scalar *degradeTemp = (Scalar *)AttributePointerOf(subsystem_in, "DegradationTemperature");
|
|
Scalar *failTemp = (Scalar *)AttributePointerOf(subsystem_in, "FailureTemperature");
|
|
Scalar *coolantLeak = (Scalar *)AttributePointerOf(subsystem_in, "CoolantMassLeakRate");
|
|
Scalar *outputVolt = (Scalar *)AttributePointerOf(subsystem_in, "OutputVoltage");
|
|
|
|
localView.SetOrigin(x, y); // vtbl+0x10
|
|
secondaryColor = leak_color_b; // @0x94 (stored; unread here)
|
|
|
|
backgroundImage = new char[strlen(background_image) + 1]; // @0x90
|
|
strcpy(backgroundImage, background_image);
|
|
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");
|
|
|
|
// child 2: generator-voltage bar 0..12000 (OutputVoltage); @004c72ac Scalar* 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, 12000.0f, outputVolt,
|
|
"GeneratorVoltage(scalar)");
|
|
|
|
// child 3: coolant-leak inverse-wipe (CoolantMassLeakRate); third=*(subsys+0x150), frames=3
|
|
leakGauge = new BitMapInverseWipe(rate3, mode_mask, renderer_in, graphics_port_number,
|
|
x, y + 0x27, leak_image, extra_color, leak_color_b,
|
|
*(int *)((char *)subsystem_in + 0x150), // third
|
|
3, // frames
|
|
coolantLeak, "LeakGauge");
|
|
|
|
// child 4/5: two status lamps (subsys+0x1D4 == generatorOn)
|
|
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,
|
|
(int *)((char *)subsystem_in + 0x1D4), "TwoState");
|
|
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,
|
|
(int *)((char *)subsystem_in + 0x1D4), "TwoState");
|
|
|
|
subsystem = subsystem_in; // @0xAC
|
|
_reserved0xB0 = 0; // @0xB0 (raw leaves uninit; zero for determinism)
|
|
}
|
|
|
|
//
|
|
// @004c7778 -- dtor. Release the background pixmap + free the interned name; the
|
|
// base chain runs implicitly. 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()
|
|
{
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
warehouse->pixelMap8Bin.Release(backgroundImage);
|
|
delete[] backgroundImage;
|
|
backgroundImage = NULL;
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
//###########################################################################
|
|
// PrepEngrScreen @004c7b30 Make / @004c7bf0 ctor
|
|
//###########################################################################
|
|
//###########################################################################
|
|
|
|
//
|
|
// @004c7b30 -- Make. DAT_0051936c is the engineer-screen number; must be 1..12
|
|
// or "PrepEngr: screen number out of range <n>" is warned. Allocates (0x90)
|
|
// and constructs with seven status-image names (DAT_005193b0..00519548).
|
|
//
|
|
// @004c7bf0 -- ctor (vtable PTR_FUN_0051a06c). Derives from the non-graphic
|
|
// Gauge group (FUN_004440d4) -- it owns no GraphicsView, only a child-gauge
|
|
// chain at +0x24. Interns and BitMapCache-AddRefs the seven status pixmaps
|
|
// (this[0x1D..0x23]); stores mech (this[0x1B]) and screen number (this[0x1C]).
|
|
//
|
|
// @004c7d14 -- dtor: release + free the seven images, tear down the child chain
|
|
// (FUN_0044ae0c), Gauge-group dtor (FUN_00443f7c).
|
|
//
|
|
// @004c7e48 -- Execute. Walk the mech's critical-subsystem roster
|
|
// (mech+0x124 count, mech+0x128 array). For each subsystem that (a) is a
|
|
// Generator-derived weapon (IsDerivedFrom 0x50f4bc) and (b) belongs to the
|
|
// selected screen (subsys+0x1dc == screenNumber), draw its label and -- via
|
|
// NumericDisplay (FUN_004700bc/0047043 0) -- its temperature and a HeatSink
|
|
// read-out, then dispatch on ClassID to lay out the weapon's status bitmaps
|
|
// (FUN_004c79c8 = draw three labelled status cells):
|
|
// 0xBC3 Sensor -> one cell + ammo bitmap (this[0x23])
|
|
// 0xBC6 -> two cells (this[0x22],this[0x20])
|
|
// 0xBC8/0xBD4 Emitter/PPC -> three cells (this[0x1E..0x20])
|
|
// 0xBCD/0xBD0 Proj/Missile -> one cell (this[0x21])
|
|
// 0xBCE GaussRifle -> warn "Gauss rifle not yet supported", one cell
|
|
//
|
|
|
|
|
|
//###########################################################################
|
|
//###########################################################################
|
|
// SubsystemCluster family @004c8140 base
|
|
//###########################################################################
|
|
//###########################################################################
|
|
|
|
//
|
|
// @004c8140 -- SubsystemCluster ctor (vtable PTR_FUN_0051a020). GraphicGauge
|
|
// base. Resolves InputVoltage/HeatSink/CurrentTemperature/Degradation/Failure
|
|
// attribute indices, then builds the panel's child gauges into its port:
|
|
// this[0x26] HorizTwoPartBar (temperature) @004c4170
|
|
// this[0x27] AnimatedSubsystemLamp ("CoolingLoop",btploop) @004c70a4
|
|
// this[0x29] AnimatedSourceLamp ("PowerSource",btpbus) @004c7160 (if InputVoltage)
|
|
// this[0x24] BackgroundBitmap (if background present) @00471d00
|
|
// this[0x28] AnimatedSubsystemLamp (bteloop) @004c70a4
|
|
// this[0x2D] OneOfSeveralInt (btecmode, subsys+0x134) @004c5148
|
|
// this[0x2A] AnimatedSourceLamp (btebus) @004c7160 (if InputVoltage)
|
|
// this[0x25] ScalarBarGauge (evolt, GeneratorVoltage) @004c721c (if InputVoltage)
|
|
// this[0x2E] OneOfSeveralStates (btemode, subsys+0x2b8) @004c5470
|
|
// (only when subsystem IsDerivedFrom Generator 0x50f4bc)
|
|
// this[0x2C] VertTwoPartBar x2 (temperatures) @004c4724
|
|
// this[0x2F] LeakGauge (eleak, CoolantMassLeakRate) @004c5b7c
|
|
// this[0x30] subsystem ; this[0x31] operating
|
|
// @004c87dc -- dtor: GraphicGauge::~GraphicGauge (children freed by subclasses).
|
|
// @004c88e4 -- Execute: operating = (subsys+0x40==1); when it changes, repaint
|
|
// the panel frame (operational => coloured rectangle; offline => background
|
|
// bitmap blit) and forward enable to the temperature/loop children
|
|
// (SetEnable @004c8a28 / FUN_00444650).
|
|
//
|
|
// @004c8a6c -- HeatSinkCluster ctor (vtable PTR_FUN_00519fd4): chains
|
|
// SubsystemCluster, this[0x36]=100.0f, then builds four failure TwoStates
|
|
// (this[0x37] heat, this[0x38] btehfail, this[0x39] btepfail subsys+0x324,
|
|
// this[0x3A] btesfail subsys+0x320) and a NumericDisplayScalar (this[0x3B],
|
|
// helv18, this[0x36]) plus a Scalar connection (&this[0x35], subsys+0x31c).
|
|
// Execute @004c8db0: failFlag = (failSubsystem+0x40==1); heatLoadScaled =
|
|
// heatLoad * _DAT_004c8df0; chain SubsystemCluster::Execute.
|
|
//
|
|
// @004c8fc4 -- WeaponCluster ctor (vtable PTR_FUN_00519f38): chains
|
|
// SubsystemCluster; resolves "PercentDone"; interns the cluster image
|
|
// (this[0x33]); warns "WeaponCluster missing <name>" if absent; centres a
|
|
// SegmentArc270 recharge dial (this[0x34], 0..360, @004c6244) and a
|
|
// ConfigMapGauge (this[0x35], "ConfigMap", @004c6d80); adds a Scalar connection
|
|
// (&percentDone, PercentDone). Execute @004c9290: chain base Execute; when
|
|
// percentDone > _DAT_004c92e0 toggle the warning lamp (DrawWarningLamp @004c932c).
|
|
//
|
|
// @004c93b0 -- EnergyWeaponCluster ctor (vtable PTR_FUN_00519ee8): chains
|
|
// WeaponCluster; adds a SeekVoltageGraph (this[0x3A], "EngrGraph", edestryd.pcc,
|
|
// @004c6798) reading OutputVoltage, and a seek-step OneOfSeveralInt (this[0x3B],
|
|
// bteseek.pcc, subsys+0x3f0, @004c5148). dtor @004c94dc.
|
|
//
|
|
// @004c9558 -- BallisticWeaponCluster ctor (vtable PTR_FUN_00519e98): chains
|
|
// WeaponCluster; resolves the ammo bin (Resolve subsys+0x43c) and builds:
|
|
// this[0x45]/[0x46] NumericDisplayInteger (ammo counts) @00470cfc
|
|
// this[0x42] TwoState (btejam) this[0x43] TwoState (btefire) @004739d8
|
|
// this[0x3F] GraphicsViewRecord erase tracker @0044ad78
|
|
// this[0x40] NumericDisplayScalarTwoState (helv42) @00473c94
|
|
// this[0x41] TwoState (edestryd) @004739d8
|
|
// this[0x44] BitMapInverseWipeScalar (bteejtm, subsys+0x3f8) @004c61c8
|
|
// Execute @004c9a38: jammed = (subsys+0x364==5 weaponAlarm Jammed); read the
|
|
// ammo-bin reload state (bin+0x18c) and, while reloading, compute elapsed
|
|
// seconds (frame clock @00414b60 / DAT_0052140c); chain WeaponCluster::Execute.
|
|
// TestInstance @004c9adc: True unless the ammo bin is present and the eject
|
|
// wipe is mid-cycle. dtor @004c9940 frees the GraphicsViewRecord + numeric.
|
|
//
|
|
|
|
//
|
|
// 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
|
|
// ctor 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)
|
|
{
|
|
view->MoveToAbsolute(x, y); // vtbl+0x24
|
|
BitMap *bmp = warehouse->bitMapBin.Get(tile); // FUN_00442aec
|
|
if (bmp != NULL)
|
|
view->DrawBitMapOpaque(0 /*background*/, 0 /*rotation*/, bmp); // vtbl+0x58
|
|
warehouse->bitMapBin.Release(tile); // FUN_00442c12
|
|
}
|
|
|
|
// A default per-child load-distribution rate (the ctor uses FUN_004442b8/dc/290,
|
|
// the NextNthTierGaugeRate allocators).
|
|
static inline GaugeRate ChildRate() { return Gauge::NextThirdTierGaugeRate(); }
|
|
|
|
|
|
//###########################################################################
|
|
// SubsystemCluster @004c8140 (vtable 0x51a020)
|
|
//###########################################################################
|
|
|
|
//
|
|
// @004c8140 -- ctor. rate/mode are hardcoded 0xFFFF/0xFFFFFFFF in the base
|
|
// GraphicGauge; the two bit-plane masks are mfd_mode (MFD-port children) and
|
|
// eng_mode (engineering-port children). Builds the panel's child gauges (see
|
|
// the header comment block) parented to the MFD port (param_5) and the engineering
|
|
// port (resolved from eng_port_name); optional children gate on the InputVoltage
|
|
// attribute (power bus present) and the damage-zone-index background image.
|
|
//
|
|
SubsystemCluster::SubsystemCluster(
|
|
ModeMask mfd_mode,
|
|
L4GaugeRenderer *renderer_in,
|
|
int owner_ID,
|
|
int mfd_port,
|
|
int x,
|
|
int y,
|
|
Subsystem *subsystem_in,
|
|
ModeMask eng_mode,
|
|
const char *eng_port_name,
|
|
const char *tile_image,
|
|
const char *label,
|
|
char **image_names,
|
|
const char *identification_string
|
|
):
|
|
GraphicGauge((GaugeRate)0xFFFF, (ModeMask)0xFFFFFFFF, renderer_in, // FUN_00444818
|
|
owner_ID, mfd_port, identification_string)
|
|
{
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer_in->warehousePointer;
|
|
|
|
// Resolve the temperature / power attributes off the subsystem.
|
|
void *inputVoltage = AttributePointerOf(subsystem_in, "InputVoltage");
|
|
void *heatSink = AttributePointerOf(subsystem_in, "HeatSink");
|
|
void *currentTemp = AttributePointerOf(subsystem_in, "CurrentTemperature");
|
|
void *degradeTemp = AttributePointerOf(subsystem_in, "DegradationTemperature");
|
|
void *failTemp = AttributePointerOf(subsystem_in, "FailureTemperature");
|
|
|
|
//------------------------------------------------------------------ MFD port
|
|
temperatureBar = new HorizTwoPartBar(ChildRate(), mfd_mode, renderer_in, // @004c4170
|
|
mfd_port, x + 0x13, y + 0x10, x + 0xdc, y + 0x1f, tile_image,
|
|
0xff, 0, (Scalar *)currentTemp, (Scalar *)degradeTemp, (Scalar *)failTemp,
|
|
"HorizTwoPartBar");
|
|
|
|
coolingLoopA = new AnimatedSubsystemLamp(ChildRate(), mfd_mode, renderer_in, // @004c70a4
|
|
mfd_port, x + 0xe5, y + 0xe, "btploop.pcc", 7, 1, subsystem_in, "CoolingLoop");
|
|
|
|
if (inputVoltage == NULL)
|
|
powerSourceA = NULL;
|
|
else
|
|
powerSourceA = new AnimatedSourceLamp(ChildRate(), mfd_mode, renderer_in, // @004c7160
|
|
mfd_port, x + 0x10e, y + 0xe, "btpbus.pcc", 5, 1,
|
|
inputVoltage, "PowerSource");
|
|
|
|
// Background: image_names[ auxScreenPlacement ] if present. (Placement@0x1e0 is
|
|
// read via the PoweredSubsystem bridge, not the non-byte-exact raw offset.)
|
|
int dzIndex = -1;
|
|
BTGetSubsystemAuxScreen(subsystem_in, NULL, &dzIndex, NULL); // auxScreenPlacement
|
|
background = NULL;
|
|
if (dzIndex >= 0 && image_names[dzIndex] != NULL)
|
|
background = new BackgroundBitmap(mfd_mode, renderer_in, owner_ID, // @00471d00
|
|
mfd_port, x + 0x90, y + 0x36, image_names[dzIndex], 0, 0xff, 0,
|
|
"BackgroundBitmap");
|
|
|
|
// Paint the panel frame image into the MFD port.
|
|
localView.SetOrigin(x, y); // vtbl+0x10
|
|
localView.SetColor(0xff); // vtbl+0x18
|
|
DrawPortBackground(&localView, warehouse, 9, 0xb4, label); // FUN_004c2ec4
|
|
|
|
//----------------------------------------------------------- engineering port
|
|
int engPort = renderer_in->FindGraphicsPort(eng_port_name); // FUN_00447f1c (name -> port number)
|
|
|
|
// Attributes off the linked heat subsystem (Resolve(heatSink)).
|
|
void *linkedHeat = ResolveLink(heatSink); // FUN_00417ab4
|
|
void *linkTemp = AttributePointerOf(linkedHeat, "CurrentTemperature");
|
|
void *linkDegrade = AttributePointerOf(linkedHeat, "DegradationTemperature");
|
|
void *coolantLeak = AttributePointerOf(subsystem_in, "CoolantMassLeakRate");
|
|
|
|
coolingLoopB = new AnimatedSubsystemLamp(ChildRate(), eng_mode, renderer_in, // @004c70a4
|
|
engPort, 0x254, 0x13b, "bteloop.pcc", 7, 1, subsystem_in, "CoolingLoop");
|
|
|
|
modeLamp = new OneOfSeveralInt(ChildRate(), eng_mode, renderer_in, // @004c5148
|
|
engPort, 400, 10, "btecmode.pcc", 1, 2,
|
|
(int *)((char *)subsystem_in + 0x134), "OneOfSeveralInt"); // BEST-EFFORT raw offset
|
|
|
|
if (inputVoltage == NULL)
|
|
{
|
|
powerSourceB = NULL;
|
|
generatorVoltageBar = NULL;
|
|
stateLamp = NULL;
|
|
}
|
|
else
|
|
{
|
|
powerSourceB = new AnimatedSourceLamp(ChildRate(), eng_mode, renderer_in, // @004c7160
|
|
engPort, 0x1a, 0xd0, "btebus.pcc", 5, 1, inputVoltage, "PowerSource");
|
|
|
|
generatorVoltageBar = new ScalarBarGauge(ChildRate(), eng_mode, renderer_in, // @004c721c
|
|
engPort, 0x89, 0x82, 0x93, 0x13b, "evolt.pcc", 0xff, 0,
|
|
WipeGaugeScalar::wipeUp, 0.0f, GeneratorMaxVoltage, inputVoltage,
|
|
"GeneratorVoltage(slotOf)");
|
|
|
|
// stateLamp only when the subsystem is a Generator (IsDerivedFrom 0x50f4bc).
|
|
if (IsGeneratorDerived(subsystem_in))
|
|
stateLamp = new OneOfSeveralStates(ChildRate(), eng_mode, renderer_in, // @004c5470
|
|
engPort, 0xbe, 0, "btemode.pcc", 1, 3,
|
|
(Entity *)((char *)subsystem_in + 0x2b8), "OneOfSeveralStates"); // BEST-EFFORT raw
|
|
else
|
|
stateLamp = NULL;
|
|
}
|
|
|
|
vertBarA = new VertTwoPartBar(ChildRate(), eng_mode, renderer_in, // @004c4724
|
|
engPort, 0x1d1, 0x5c, 0x1eb, 0x124, tile_image, 0xff, 0xff, 0,
|
|
(Scalar *)currentTemp, (Scalar *)degradeTemp, (Scalar *)failTemp, "VertTwoPartBar");
|
|
|
|
vertBarB = new VertTwoPartBar(ChildRate(), eng_mode, renderer_in,
|
|
engPort, 0x222, 0x5c, 0x23d, 0x124, tile_image, 0xff, 0xff, 0,
|
|
(Scalar *)linkTemp, (Scalar *)linkDegrade, (Scalar *)failTemp, "VertTwoPartBar");
|
|
|
|
leakGauge = new BitMapInverseWipe(ChildRate(), eng_mode, renderer_in, // @004c5b7c
|
|
engPort, 0x255, 0xe0, "eleak.pcc", 0, 0xff,
|
|
*(int *)((char *)subsystem_in + 0x150) /*third*/, 3 /*frames*/,
|
|
(Scalar *)coolantLeak, "LeakGauge"); // BEST-EFFORT raw (subsys+0x150)
|
|
|
|
operating = False; // @0xC4 this[0x31]
|
|
subsystem = subsystem_in; // @0xC0 this[0x30]
|
|
previousDrawState = 0; // @0xC8 this[0x32]
|
|
}
|
|
|
|
//
|
|
// @004c87dc -- dtor. Free the owned children, then GraphicGauge::~GraphicGauge
|
|
// (implicit). (The binary frees children through the vtbl-slot-3 ReleaseChildren
|
|
// path; done here for RAII correctness.)
|
|
//
|
|
SubsystemCluster::~SubsystemCluster()
|
|
{
|
|
ReleaseChildren(); // FUN_004c8820
|
|
}
|
|
|
|
//
|
|
// @004c8820 -- free each owned child gauge (delete via its own dtor).
|
|
//
|
|
void SubsystemCluster::ReleaseChildren()
|
|
{
|
|
delete background; background = NULL;
|
|
delete generatorVoltageBar; generatorVoltageBar = NULL;
|
|
delete temperatureBar; temperatureBar = NULL;
|
|
delete coolingLoopA; coolingLoopA = NULL;
|
|
delete coolingLoopB; coolingLoopB = NULL;
|
|
delete powerSourceA; powerSourceA = NULL;
|
|
delete powerSourceB; powerSourceB = NULL;
|
|
delete vertBarA; vertBarA = NULL;
|
|
delete vertBarB; vertBarB = NULL;
|
|
delete modeLamp; modeLamp = NULL;
|
|
delete stateLamp; stateLamp = NULL;
|
|
delete leakGauge; leakGauge = NULL;
|
|
}
|
|
|
|
//
|
|
// @004c89c4 -- draw the panel's border frame (two thick diagonals of the box).
|
|
//
|
|
void SubsystemCluster::DrawPanelFrame(int color)
|
|
{
|
|
localView.SetColor(color); // vtbl+0x18
|
|
localView.MoveToAbsolute(9, 0x33); // vtbl+0x24
|
|
localView.DrawThickLineToAbsolute(0x132, 0xab); // vtbl+0x38
|
|
localView.MoveToAbsolute(9, 0xab);
|
|
localView.DrawThickLineToAbsolute(0x132, 0x33);
|
|
}
|
|
|
|
//
|
|
// @004c8a28 -- forward an enable/disable to the always-present children (temp bar,
|
|
// cooling loop, power source A).
|
|
//
|
|
void SubsystemCluster::SetChildrenEnable(int enable)
|
|
{
|
|
// FUN_00444650 forwards visibility; enable==0 (offline) => Disable(True).
|
|
if (temperatureBar) temperatureBar->Disable(enable == 0);
|
|
if (coolingLoopA) coolingLoopA->Disable(enable == 0);
|
|
if (powerSourceA) powerSourceA->Disable(enable == 0);
|
|
}
|
|
|
|
//
|
|
// @004c8990 -- the operational-state virtual (drawn as TestInstance): active
|
|
// unless the subsystem is offline (operating==0 AND subsystem+0x278 == 4).
|
|
//
|
|
int SubsystemCluster::GetDrawState()
|
|
{
|
|
if (operating != 0)
|
|
return 1;
|
|
if (*(int *)((char *)subsystem + 0x278) != 4) // BEST-EFFORT raw offset
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
Logical SubsystemCluster::TestInstance() const
|
|
{
|
|
return GraphicGauge::TestInstance();
|
|
}
|
|
|
|
//
|
|
// @004c88e4 -- Execute (shared by the whole family). operating = (subsystem is
|
|
// operational, +0x40 == 1). On a draw-state change repaint the panel frame:
|
|
// offline => frame(0) + redraw background bitmap; online => black fill + frame(0xff);
|
|
// and forward the enable to the temperature / loop children.
|
|
//
|
|
void SubsystemCluster::Execute()
|
|
{
|
|
operating = (*(int *)((char *)subsystem + 0x40) == 1); // BEST-EFFORT raw offset
|
|
|
|
int state = GetDrawState();
|
|
if (state != previousDrawState)
|
|
{
|
|
previousDrawState = state;
|
|
SetChildrenEnable(state);
|
|
if (state == 0)
|
|
{
|
|
DrawPanelFrame(0);
|
|
if (background != NULL)
|
|
background->BecameActive(); // (child vtbl+0xc) force redraw
|
|
}
|
|
else
|
|
{
|
|
localView.SetColor(0); // vtbl+0x18
|
|
localView.MoveToAbsolute(1, 0x32); // vtbl+0x24
|
|
localView.DrawFilledRectangleToAbsolute(0x136, 0xad); // vtbl+0x48
|
|
DrawPanelFrame(0xff);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SubsystemCluster::BecameActive()
|
|
{
|
|
previousDrawState = -1; // force first-frame repaint
|
|
}
|
|
|
|
|
|
// 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
|
|
|
|
|
|
//###########################################################################
|
|
// HeatSinkCluster @004c8a6c (vtable 0x519fd4)
|
|
//###########################################################################
|
|
|
|
//
|
|
// @004c8a6c -- ctor: SubsystemCluster base + four failure TwoStates (heat / heat-
|
|
// sink / power / structure) + a NumericDisplayScalar read-out, plus a Scalar
|
|
// connection feeding heatLoad from the subsystem (+0x31c).
|
|
//
|
|
HeatSinkCluster::HeatSinkCluster(
|
|
ModeMask mfd_mode, L4GaugeRenderer *renderer_in, int owner_ID, int mfd_port,
|
|
int x, int y, Subsystem *subsystem_in, Subsystem *hud, ModeMask eng_mode,
|
|
const char *eng_port_name, const char *tile_image, const char *label,
|
|
const char *fail_lamp_image, char **image_names, const char *identification_string
|
|
):
|
|
SubsystemCluster(mfd_mode, renderer_in, owner_ID, mfd_port, x, y, subsystem_in,
|
|
eng_mode, eng_port_name, tile_image, label, image_names, identification_string)
|
|
{
|
|
heatLoadScaled = HeatNumericBase; // @0x36 100.0f
|
|
int engPort = renderer_in->FindGraphicsPort(eng_port_name);
|
|
|
|
heatFailLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID, // @004739d8
|
|
engPort, 200, 0x110, fail_lamp_image, 0, 0xff, (int *)&operating, "TwoState");
|
|
|
|
failSubsystem = hud; // @0x33
|
|
failFlag = 0; // @0x34
|
|
|
|
hsFailLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID,
|
|
engPort, 0x97, 0xe8, "btehfail.pcc", 0, 0xff, (int *)&failFlag, "TwoState");
|
|
|
|
powerFailLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID,
|
|
engPort, 0x97, 0xc0, "btepfail.pcc", 0, 0xff,
|
|
(int *)((char *)subsystem_in + 0x324), "TwoState"); // BEST-EFFORT raw offset
|
|
|
|
structFailLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID,
|
|
engPort, 0x97, 0x98, "btesfail.pcc", 0xff, 0,
|
|
(int *)((char *)subsystem_in + 0x320), "TwoState"); // BEST-EFFORT raw offset
|
|
|
|
heatNumeric = new NumericDisplayScalar(ChildRate(), eng_mode, renderer_in, // @00470888
|
|
owner_ID, engPort, 0x132, 0x8c, 0x15a, 0xaa, "helv18.pcc", 3,
|
|
(NumericDisplay::NumericFormat)0, 0, 0xff, &heatLoadScaled,
|
|
"NumericDisplayScalar");
|
|
|
|
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, &heatLoad, // @00474855
|
|
(Scalar *)((char *)subsystem_in + 0x31c))); // BEST-EFFORT raw offset
|
|
}
|
|
|
|
HeatSinkCluster::~HeatSinkCluster()
|
|
{
|
|
delete heatFailLamp; delete hsFailLamp; delete powerFailLamp;
|
|
delete structFailLamp; delete heatNumeric;
|
|
}
|
|
|
|
//
|
|
// @004c8db0 -- Execute: failFlag = (failSubsystem operational, +0x40 == 1);
|
|
// heatLoadScaled = heatLoad * 100.0; chain SubsystemCluster::Execute.
|
|
//
|
|
void HeatSinkCluster::Execute()
|
|
{
|
|
if (failSubsystem != NULL)
|
|
failFlag = (*(int *)((char *)failSubsystem + 0x40) == 1); // BEST-EFFORT raw
|
|
heatLoadScaled = heatLoad * HeatLoadScale;
|
|
SubsystemCluster::Execute();
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
// MyomerCluster @004c8df4 (vtable 0x519f88)
|
|
//###########################################################################
|
|
|
|
//
|
|
// @004c8df4 -- ctor: SubsystemCluster base + a SeekVoltageGraph ("EngrGraph")
|
|
// fed by an InputVoltage GeneratorVoltageConnection (writes seekValue@0x33) + a
|
|
// seek-step OneOfSeveralInt (bteseek.pcc, subsys+0x800). Execute = base.
|
|
//
|
|
MyomerCluster::MyomerCluster(
|
|
ModeMask mfd_mode, L4GaugeRenderer *renderer_in, int owner_ID, int mfd_port,
|
|
int x, int y, Subsystem *subsystem_in, ModeMask eng_mode,
|
|
const char *eng_port_name, const char *tile_image, const char *label,
|
|
char **image_names, const char *identification_string
|
|
):
|
|
SubsystemCluster(mfd_mode, renderer_in, owner_ID, mfd_port, x, y, subsystem_in,
|
|
eng_mode, eng_port_name, tile_image, label, image_names, identification_string)
|
|
{
|
|
seekValue = 0.0f; // @0x33 (connection dst)
|
|
int engPort = renderer_in->FindGraphicsPort(eng_port_name);
|
|
|
|
seekGraph = (GraphicGauge *)new SeekVoltageGraph(ChildRate(), eng_mode, // @004c6798
|
|
renderer_in, engPort, (Entity *)subsystem_in,
|
|
(AttributeAccessor *)&seekValue, "edestryd.pcc", False, "EngrGraph");
|
|
|
|
seekStepLamp = new OneOfSeveralInt(ChildRate(), eng_mode, renderer_in, // @004c5148
|
|
engPort, 0xf, 0, "bteseek.pcc", 1, 4,
|
|
(int *)((char *)subsystem_in + 0x800), "OneOfSeveralInt"); // BEST-EFFORT raw
|
|
|
|
void *inputVoltage = AttributePointerOf(subsystem_in, "InputVoltage");
|
|
AddConnection(new GeneratorVoltageConnection(&seekValue, inputVoltage)); // @004c3288
|
|
}
|
|
|
|
MyomerCluster::~MyomerCluster()
|
|
{
|
|
delete seekGraph; delete seekStepLamp;
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
// WeaponCluster @004c8fc4 (vtable 0x519f38)
|
|
//###########################################################################
|
|
|
|
//
|
|
// @004c8fc4 -- ctor: SubsystemCluster base; interns the cluster image; centres a
|
|
// SegmentArc270 recharge dial (reads PercentDone) + a ConfigMapGauge; adds a
|
|
// Scalar connection feeding percentDone. The warning-lamp centre is derived from
|
|
// the cluster image size, or warned "WeaponCluster missing <name>".
|
|
//
|
|
WeaponCluster::WeaponCluster(
|
|
ModeMask mfd_mode, L4GaugeRenderer *renderer_in, int owner_ID, int mfd_port,
|
|
int x, int y, Subsystem *subsystem_in, ModeMask eng_mode,
|
|
const char *eng_port_name, const char *tile_image, const char *label,
|
|
const char *cluster_image, char **image_names, const char *identification_string
|
|
):
|
|
SubsystemCluster(mfd_mode, renderer_in, owner_ID, mfd_port, x, y, subsystem_in,
|
|
eng_mode, eng_port_name, tile_image, label, image_names, identification_string)
|
|
{
|
|
void *percentDoneAttr = AttributePointerOf(subsystem_in, "PercentDone");
|
|
|
|
// Intern the cluster image + derive the warning-lamp centre from its size.
|
|
clusterImage = (char *)cluster_image; // @0x33 (FUN_004700ac intern)
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer_in->warehousePointer;
|
|
BitMap *img = warehouse->bitMapBin.Get(clusterImage);
|
|
if (img == NULL)
|
|
{
|
|
DebugStream << "WeaponCluster missing " << clusterImage << "\n";
|
|
warningCenterX = 0; // @0x37
|
|
warningCenterY = 0; // @0x38
|
|
}
|
|
else
|
|
{
|
|
warningCenterX = 0x41 - (img->Data.Size.x >> 1);
|
|
warningCenterY = 0x6d - (img->Data.Size.y >> 1);
|
|
}
|
|
|
|
// rechargeArc (SegmentArc270 @004c6244): the 0..360 recharge dial reading the
|
|
// weapon's PercentDone attribute (a real 0..1 fraction).
|
|
rechargeArc = (GraphicGauge *)new SegmentArc270((GaugeRate)0xffff, mfd_mode, // @004c6244
|
|
renderer_in, owner_ID, mfd_port, x + 0x41, y + 0x6d, 40.0f, 60.0f, 40.0f,
|
|
60.0f, 0.0f, 360.0f, 0x14, 0, 0xff, (Scalar *)percentDoneAttr, True,
|
|
"SegmentArc270");
|
|
|
|
configMap = new ConfigMapGauge(ChildRate(), mfd_mode, renderer_in, // @004c6d80
|
|
mfd_port, x + 0xee, y + 0x38, (Entity *)subsystem_in, "ConfigMap");
|
|
|
|
warningState = 0; // @0x39
|
|
AddConnection(new GaugeConnectionDirectOf<Scalar>(0, &percentDone, // @00474855
|
|
(Scalar *)percentDoneAttr));
|
|
}
|
|
|
|
WeaponCluster::~WeaponCluster()
|
|
{
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
warehouse->bitMapBin.Release(clusterImage);
|
|
delete rechargeArc; delete configMap;
|
|
}
|
|
|
|
//
|
|
// @004c932c -- DrawWarningLamp: blit the cluster warning image at its centre,
|
|
// coloured on/off by the passed enable.
|
|
//
|
|
void WeaponCluster::DrawWarningLamp(int on)
|
|
{
|
|
L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer;
|
|
BitMap *img = warehouse->bitMapBin.Get(clusterImage);
|
|
if (img != NULL)
|
|
{
|
|
localView.MoveToAbsolute(warningCenterX, warningCenterY); // vtbl+0x24
|
|
localView.SetColor(on ? 0xff : 0); // vtbl+0x18
|
|
localView.DrawBitMap(0, img); // vtbl+0x54
|
|
}
|
|
warehouse->bitMapBin.Release(clusterImage);
|
|
}
|
|
|
|
//
|
|
// @004c9258 -- BecameActive: reset the warning state + chain the children.
|
|
//
|
|
void WeaponCluster::BecameActive()
|
|
{
|
|
warningState = -2;
|
|
if (configMap) configMap->BecameActive();
|
|
if (rechargeArc) rechargeArc->BecameActive();
|
|
SubsystemCluster::BecameActive();
|
|
}
|
|
|
|
//
|
|
// @004c9290 -- Execute: chain the base; when percentDone crosses the warn
|
|
// threshold toggle the warning lamp.
|
|
//
|
|
void WeaponCluster::Execute()
|
|
{
|
|
SubsystemCluster::Execute();
|
|
if (previousDrawState == 0)
|
|
{
|
|
int warn = (WeaponWarnThreshold < percentDone) ? 1 : 0;
|
|
if (warn != warningState)
|
|
{
|
|
warningState = warn;
|
|
DrawWarningLamp(warn);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
// EnergyWeaponCluster @004c93b0 (vtable 0x519ee8)
|
|
//###########################################################################
|
|
|
|
//
|
|
// @004c93b0 -- ctor: WeaponCluster base + a SeekVoltageGraph ("EngrGraph",
|
|
// OutputVoltage) + a seek-step OneOfSeveralInt (bteseek.pcc, subsys+0x3f0).
|
|
//
|
|
EnergyWeaponCluster::EnergyWeaponCluster(
|
|
ModeMask mfd_mode, L4GaugeRenderer *renderer_in, int owner_ID, int mfd_port,
|
|
int x, int y, Subsystem *subsystem_in, ModeMask eng_mode,
|
|
const char *eng_port_name, const char *tile_image, const char *label,
|
|
const char *cluster_image, char **image_names, const char *identification_string
|
|
):
|
|
WeaponCluster(mfd_mode, renderer_in, owner_ID, mfd_port, x, y, subsystem_in,
|
|
eng_mode, eng_port_name, tile_image, label, cluster_image, image_names,
|
|
identification_string)
|
|
{
|
|
int engPort = renderer_in->FindGraphicsPort(eng_port_name);
|
|
void *outputVoltage = AttributePointerOf(subsystem_in, "OutputVoltage");
|
|
|
|
seekGraph = new SeekVoltageGraph(ChildRate(), eng_mode, renderer_in, // @004c6798
|
|
engPort, (Entity *)subsystem_in, (AttributeAccessor *)outputVoltage,
|
|
"edestryd.pcc", True, "EngrGraph");
|
|
|
|
seekStepLamp = new OneOfSeveralInt(ChildRate(), eng_mode, renderer_in, // @004c5148
|
|
engPort, 0xf, 0, "bteseek.pcc", 1, 4,
|
|
(int *)((char *)subsystem_in + 0x3f0), "OneOfSeveralInt"); // BEST-EFFORT raw
|
|
}
|
|
|
|
EnergyWeaponCluster::~EnergyWeaponCluster()
|
|
{
|
|
delete seekGraph; delete seekStepLamp;
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
// BallisticWeaponCluster @004c9558 (vtable 0x519e98)
|
|
//###########################################################################
|
|
|
|
//
|
|
// @004c9558 -- ctor: WeaponCluster base + the ammo panel: two NumericDisplay
|
|
// Integers (ammo counts off the resolved ammo bin +0x180), jam/fire TwoStates,
|
|
// a GraphicsViewRecord erase tracker, a NumericDisplayScalarTwoState, a destroyed
|
|
// TwoState, and an eject BitMapInverseWipeScalar (bteejtm, subsys+0x3f8).
|
|
//
|
|
BallisticWeaponCluster::BallisticWeaponCluster(
|
|
ModeMask mfd_mode, L4GaugeRenderer *renderer_in, int owner_ID, int mfd_port,
|
|
int x, int y, Subsystem *subsystem_in, ModeMask eng_mode,
|
|
const char *eng_port_name, const char *tile_image, const char *label,
|
|
const char *cluster_image, char **image_names, const char *font_a,
|
|
const char *font_b, const char *identification_string
|
|
):
|
|
WeaponCluster(mfd_mode, renderer_in, owner_ID, mfd_port, x, y, subsystem_in,
|
|
eng_mode, eng_port_name, tile_image, label, cluster_image, image_names,
|
|
identification_string)
|
|
{
|
|
reloadStartTime = 0; // @0x3D (frame clock)
|
|
reloadSeconds = 0.0f; // @0x3E
|
|
jammed = firing = reloading = False;
|
|
|
|
void *ammoBin = ResolveLink((char *)subsystem_in + 0x43c); // FUN_00417ab4 (raw)
|
|
int engPort = renderer_in->FindGraphicsPort(eng_port_name);
|
|
|
|
// Ammo count off the bin (+0x180); BEST-EFFORT raw if the bin resolves.
|
|
int *ammoValue = (ammoBin != NULL) ? (int *)((char *)ammoBin + 0x180) : NULL;
|
|
|
|
ammoCountA = new NumericDisplayInteger(ChildRate(), mfd_mode, renderer_in, // @00470cfc
|
|
owner_ID, mfd_port, x + 0x27, y + 100, x + 0x5f, y + 0x76, font_a, 4,
|
|
(NumericDisplay::NumericFormat)0, 0, 0xff, ammoValue, "NumericDisplayInteger");
|
|
|
|
ammoCountB = new NumericDisplayInteger(ChildRate(), eng_mode, renderer_in,
|
|
owner_ID, engPort, 0xdc, 0x104, 0x140, 300, font_b, 4,
|
|
(NumericDisplay::NumericFormat)0, 0, 0xff, ammoValue, "NumericDisplayInteger");
|
|
|
|
jammed = 0; // @0x3A
|
|
jamLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID, // @004739d8
|
|
engPort, 0xa0, 0xb2, "btejam.pcc", 0, 0xff, (int *)&jammed, "TwoState");
|
|
|
|
firing = reloading = 0; // @0x3B / @0x3C
|
|
fireLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID,
|
|
engPort, 0x121, 0xb6, "btefire.pcc", 0, 0xff, (int *)&firing, "TwoState");
|
|
|
|
// Ammo erase-tracker GraphicsViewRecord (@0044ad78) -- bring-up: the record
|
|
// class is engine-internal; tracked as a NULL erase (the numeric self-erases).
|
|
ammoErase = NULL; // @0x3F (GraphicsViewRecord, deferred)
|
|
|
|
ammoNumeric = new NumericDisplayScalarTwoState(ChildRate(), eng_mode, // @00473c94
|
|
renderer_in, owner_ID, engPort, 0x130, 0xbc, 0x162, 0xee, "helv42.pcc", 2,
|
|
(NumericDisplay::NumericFormat)0, 0, 0xff, &reloadSeconds, (int *)&firing,
|
|
"NumericDisplayScalarTwoState");
|
|
|
|
destroyedLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID,
|
|
engPort, 0xc2, 0x85, "edestryd.pcc", 0, 0xff, (int *)&operating, "TwoState");
|
|
|
|
// ejectWipe (BitMapInverseWipeScalar @004c61c8, eject-timer wipe reading
|
|
// subsys+0x3f8). BRING-UP: the BitMapInverseWipeScalar class is not yet
|
|
// declared/reconstructed in btl4gaug (only the non-Scalar BitMapInverseWipe);
|
|
// tracked NULL until it lands.
|
|
ejectWipe = NULL; // @0x44
|
|
}
|
|
|
|
BallisticWeaponCluster::~BallisticWeaponCluster()
|
|
{
|
|
delete ammoNumeric; delete ammoErase; delete ammoCountA;
|
|
delete ammoCountB; delete jamLamp; delete fireLamp;
|
|
delete destroyedLamp; delete ejectWipe;
|
|
}
|
|
|
|
//
|
|
// @004c99cc -- BecameActive: chain the ammo children then WeaponCluster.
|
|
//
|
|
void BallisticWeaponCluster::BecameActive()
|
|
{
|
|
if (ammoCountA) ammoCountA->BecameActive();
|
|
if (ammoCountB) ammoCountB->BecameActive();
|
|
if (jamLamp) jamLamp->BecameActive();
|
|
if (fireLamp) fireLamp->BecameActive();
|
|
if (ammoNumeric) ammoNumeric->BecameActive();
|
|
if (destroyedLamp) destroyedLamp->BecameActive();
|
|
if (ejectWipe) ejectWipe->BecameActive();
|
|
WeaponCluster::BecameActive();
|
|
}
|
|
|
|
//
|
|
// @004c9a38 -- Execute: jammed = (weaponAlarm subsys+0x364 == 5 Jammed); read the
|
|
// ammo-bin reload state (bin+0x18c) and, while reloading, compute the elapsed
|
|
// reload seconds; chain WeaponCluster::Execute.
|
|
//
|
|
void BallisticWeaponCluster::Execute()
|
|
{
|
|
jammed = (*(int *)((char *)subsystem + 0x364) == 5); // BEST-EFFORT raw (weaponAlarm==Jammed)
|
|
void *ammoBin = ResolveLink((char *)subsystem + 0x43c); // FUN_00417ab4
|
|
int reloadState = (ammoBin != NULL) ? *(int *)((char *)ammoBin + 0x18c) : 0;
|
|
firing = reloadState;
|
|
if (reloadState == 0)
|
|
{
|
|
reloading = 0;
|
|
}
|
|
else
|
|
{
|
|
reloading = 1;
|
|
// (elapsed reload seconds = (now - reloadStart) / frameClock; the frame
|
|
// clock DAT_0052140c is a runtime value -- left at the base value here.)
|
|
}
|
|
WeaponCluster::Execute();
|
|
}
|
|
|
|
//
|
|
// @004c9adc -- TestInstance: True unless the ammo bin is present and the eject
|
|
// wipe is mid-cycle.
|
|
//
|
|
Logical BallisticWeaponCluster::TestInstance() const
|
|
{
|
|
void *ammoBin = ResolveLink((char *)subsystem + 0x43c);
|
|
if (ammoBin == NULL || *(int *)((char *)ammoBin + 0x180) == 0)
|
|
return True;
|
|
if (jammed != 0)
|
|
return True;
|
|
return SubsystemCluster::TestInstance();
|
|
}
|
|
|
|
|
|
//###########################################################################
|
|
// vehicleSubSystems Make dispatcher @004cbaf0
|
|
//###########################################################################
|
|
|
|
//
|
|
// The 12 auxiliary-screen geometry rows (@0x51bf34, x/y/planeMask/subBit/mfdPort/
|
|
// engPort/planeIndex), keyed by subsystem[0x1dc]-1. planeMask -> the MFD child
|
|
// bit-plane mode; subBit -> the engineering child bit-plane mode; mfd/eng names
|
|
// select the two graphics ports.
|
|
//
|
|
struct AuxScreenGeometry
|
|
{
|
|
int x, y;
|
|
ModeMask planeMask;
|
|
ModeMask subBit;
|
|
const char *mfdPortName;
|
|
const char *engPortName;
|
|
int planeIndex;
|
|
};
|
|
static const AuxScreenGeometry kAuxGeom[12] =
|
|
{
|
|
{ 0, 0xf0, 0x1, 0x2, "mfd1", "eng1", 0 }, // grp 1
|
|
{ 0x142, 0xf0, 0x1, 0x4, "mfd1", "eng1", 0 }, // grp 2
|
|
{ 0, 0x1c, 0x1, 0x8, "mfd1", "eng1", 0 }, // grp 3
|
|
{ 0x142, 0x1c, 0x1, 0x10, "mfd1", "eng1", 1 }, // grp 4
|
|
{ 0, 0xf0, 0x20, 0x40, "mfd2", "eng2", 1 }, // grp 5
|
|
{ 0x142, 0xf0, 0x20, 0x80, "mfd2", "eng2", 1 }, // grp 6
|
|
{ 0, 0x1c, 0x20, 0x100, "mfd2", "eng2", 1 }, // grp 7
|
|
{ 0x142, 0x1c, 0x20, 0x200, "mfd2", "eng2", 2 }, // grp 8
|
|
{ 0, 0xf0, 0x400, 0x800, "mfd3", "eng3", 2 }, // grp 9
|
|
{ 0x142, 0xf0, 0x400, 0x1000, "mfd3", "eng3", 2 }, // grp 10
|
|
{ 0, 0x1c, 0x400, 0x2000, "mfd3", "eng3", 2 }, // grp 11
|
|
{ 0x142, 0x1c, 0x400, 0x4000, "mfd3", "eng3", 2 }, // grp 12
|
|
};
|
|
|
|
//
|
|
// FUN_0041a1a4(**(sub+0xc), 0x50f4bc) -- the pre-dispatch type filter (an
|
|
// IsDerivedFrom against a ClassDerivations object). The classID switch below is
|
|
// the real per-panel filter, so a permissive True is faithful for the displayable
|
|
// classes; the exact ClassDerivations resolution lands with a shared accessor.
|
|
//
|
|
static Logical SubsystemDisplayFilter(Subsystem *) { return True; }
|
|
|
|
//
|
|
// @004cbaf0 -- vehicleSubSystems Make. Build the 8 strip-image pointers from the
|
|
// method params, then for each roster subsystem (skipping slot 0) build the panel
|
|
// cluster for its classID onto the auxiliary MFD position named by subsystem[0x1dc].
|
|
//
|
|
Logical
|
|
VehicleSubSystems::Make(
|
|
int /*display_port_index*/,
|
|
Vector2DOf<int> /*position*/,
|
|
Entity *entity,
|
|
GaugeRenderer *gauge_renderer
|
|
)
|
|
{
|
|
L4GaugeRenderer *renderer = (L4GaugeRenderer *)gauge_renderer;
|
|
|
|
// The 8 status strip bitmaps (qXXX0.pcc..qXXX7.pcc) from the method params.
|
|
char *imageNames[8];
|
|
for (int p = 0; p < 8; p++)
|
|
imageNames[p] = methodDescription.parameterList[p].data.string;
|
|
|
|
int count = entity->GetSubsystemCount(); // entity+0x124
|
|
if (getenv("BT_VSS_LOG"))
|
|
DEBUG_STREAM << "[vss] Make called: subsystemCount=" << count << "\n" << std::flush;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
if (i == 0) // slot 0 = the controls mapper
|
|
continue;
|
|
Subsystem *sub = entity->GetSubsystem(i); // entity+0x128[i]
|
|
if (sub == NULL)
|
|
continue;
|
|
|
|
// Read the engineering-screen assignment via the PoweredSubsystem bridge
|
|
// (also the type filter -- False == not PoweredSubsystem-derived).
|
|
int auxScreen = 0, placement = -1;
|
|
char labelBuf[64];
|
|
if (!BTGetSubsystemAuxScreen(sub, &auxScreen, &placement, labelBuf)) // FUN_0041a1a4 + sub[0x1dc]/[0x1e4]
|
|
continue;
|
|
|
|
int group = auxScreen - 1; // aux-screen position 1..12
|
|
if (getenv("BT_VSS_LOG"))
|
|
DEBUG_STREAM << "[vss] i=" << i << " classID=" << (int)sub->GetClassID()
|
|
<< " auxScreen=" << auxScreen << " placement=" << placement
|
|
<< " label='" << labelBuf << "'\n" << std::flush;
|
|
if (group < 0)
|
|
{
|
|
DebugStream << "Auxiliary screen position = zero\n";
|
|
continue;
|
|
}
|
|
if (group >= 12)
|
|
{
|
|
DebugStream << "Illegal auxiliary screen position =" << group << "\n";
|
|
continue;
|
|
}
|
|
|
|
const AuxScreenGeometry &g = kAuxGeom[group];
|
|
int mfdPort = renderer->FindGraphicsPort((char *)g.mfdPortName); // FUN_00447f1c
|
|
char *label = labelBuf; // the subsystem label (bridge)
|
|
char *tile = (char *)"btwarn.pcc";
|
|
|
|
switch (sub->GetClassID()) // sub+4
|
|
{
|
|
case 0xBC3: // Sensor -> HeatSinkCluster (shows the "HUD" subsystem)
|
|
{
|
|
Subsystem *hud = entity->FindSubsystem((char *)"HUD"); // FUN_0041f98c
|
|
new HeatSinkCluster(g.planeMask, renderer, 0, mfdPort, g.x, g.y, sub,
|
|
hud, g.subBit, g.engPortName, tile, label, "edestryd.pcc",
|
|
imageNames, "SensorCluster");
|
|
break;
|
|
}
|
|
case 0xBC6: // Myomers -> MyomerCluster
|
|
new MyomerCluster(g.planeMask, renderer, 0, mfdPort, g.x, g.y, sub,
|
|
g.subBit, g.engPortName, tile, label, imageNames, "MyomerCluster");
|
|
break;
|
|
case 0xBC8: // Emitter
|
|
case 0xBD4: // PPC
|
|
{
|
|
const char *clusterImage =
|
|
(*(int *)((char *)sub + 0x334) == 0) ? "qcircle.pcc" : "qcircr.pcc";
|
|
new EnergyWeaponCluster(g.planeMask, renderer, 0, mfdPort, g.x, g.y, sub,
|
|
g.subBit, g.engPortName, tile, label, clusterImage, imageNames,
|
|
"EnergyWeaponCluster");
|
|
break;
|
|
}
|
|
case 0xBCD: // ProjectileWeapon
|
|
case 0xBD0: // MissileLauncher
|
|
new BallisticWeaponCluster(g.planeMask, renderer, 0, mfdPort, g.x, g.y,
|
|
sub, g.subBit, g.engPortName, tile, label, "qcircle.pcc", imageNames,
|
|
"smlfont.pcc", "helv42.pcc", "ProjWeaponCluster");
|
|
break;
|
|
case 0xBCE: // GaussRifle -- not yet supported
|
|
DebugStream << "Gauss rifle not yet supported\n";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return True;
|
|
}
|
|
|
|
//
|
|
// The registered method: config primitive "vehicleSubSystems(q0.pcc..q7.pcc)".
|
|
//
|
|
MethodDescription
|
|
VehicleSubSystems::methodDescription =
|
|
{
|
|
"vehicleSubSystems",
|
|
VehicleSubSystems::Make,
|
|
{
|
|
{ ParameterDescription::typeString, NULL }, // qXXX0.pcc
|
|
{ ParameterDescription::typeString, NULL }, // qXXX1.pcc
|
|
{ ParameterDescription::typeString, NULL }, // qXXX2.pcc
|
|
{ ParameterDescription::typeString, NULL }, // qXXX3.pcc
|
|
{ ParameterDescription::typeString, NULL }, // qXXX4.pcc
|
|
{ ParameterDescription::typeString, NULL }, // qXXX5.pcc
|
|
{ ParameterDescription::typeString, NULL }, // qXXX6.pcc
|
|
{ ParameterDescription::typeString, NULL }, // qXXX7.pcc
|
|
{ ParameterDescription::typeEmpty, NULL }
|
|
}
|
|
};
|
|
|
|
|
|
// === btl4gau3.cpp begins at @004c9bd0 (PlayerStatusMappingGroup) -- not this TU.
|