diff --git a/restoration/source410/BT_L4/BTL4GAU2.CPP b/restoration/source410/BT_L4/BTL4GAU2.CPP new file mode 100644 index 00000000..b71d1bbc --- /dev/null +++ b/restoration/source410/BT_L4/BTL4GAU2.CPP @@ -0,0 +1,2925 @@ +//===========================================================================// +// 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 +#pragma hdrstop + +#if !defined(BTL4GAU2_HPP) +# include +#endif + +#if !defined(BTL4MODE_HPP) +# include +#endif + +#if !defined(L4CTRL_HPP) +# include +#endif + +#if !defined(L4WRHOUS_HPP) +# include +#endif + +#if !defined(APP_HPP) +# include +#endif + +#if !defined(MECHSUB_HPP) +# include +#endif + +#if !defined(POWERSUB_HPP) +# include +#endif + +#if !defined(MECHWEAP_HPP) +# include +#endif + +#include + +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 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). +//########################################################################### + +// +// 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. +// +// INERT STUB: HeatSink::coolantAvailable, HeatSink::linkedSinks and +// Condenser::condenserNumber are protected with no published accessor or +// attribute row in source410 (heat.hpp) -- the loop lamp draws frame 0 +// ("OFF") until the heat accessor wave lands. +// +class CoolingLoopConnection : public GaugeConnection +{ +public: + CoolingLoopConnection(int *destination, void *source): + GaugeConnection(0), + source(source), + destination(destination) + {} + void Update() // @004c31a0 + { + *destination = 0; + } +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 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(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; + } +} + +static int + AuxScreenAssignmentOf( + Entity *mech, + Subsystem *subsystem + ) +{ + int + i, + position = 0, + count = mech->GetSubsystemCount(); + + 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 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); + + 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 /*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): SKIPPED (no live label). + + //---- type-specific label cells (the authentic ClassID dispatch) + switch (sub->GetClassID()) + { + case RegisteredClass::SensorClassID: // 0xBC3 + DrawStatusCells(&localView, warehouse, 1, NULL, NULL, NULL); + localView.SetColor(0xff); + DrawPortBackground(&localView, warehouse, 0xb8, 0x8c, statusImage[6]); // btesrnge + return; + case RegisteredClass::MyomersClassID: // 0xBC6 + DrawStatusCells(&localView, warehouse, 0, NULL, statusImage[5], statusImage[3]); + return; + case RegisteredClass::EmitterClassID: // 0xBC8 + case RegisteredClass::PPCClassID: // 0xBD4 + DrawStatusCells(&localView, warehouse, 0, statusImage[1], statusImage[2], statusImage[3]); + return; + case RegisteredClass::ProjectileWeaponClassID: // 0xBCD + case RegisteredClass::MissileLauncherClassID: // 0xBD0 + DrawStatusCells(&localView, warehouse, 1, NULL, NULL, statusImage[4]); + return; + case RegisteredClass::GaussRifleClassID: // 0xBCE -- not yet supported + DEBUG_STREAM << "PrepEngr: Gauss rifle not yet supported\n"; + DrawStatusCells(&localView, warehouse, 1, NULL, NULL, statusImage[3]); + return; + default: + return; + } + } +} + + +//########################################################################### +//########################################################################### +// SubsystemCluster family @004c8140 base +//########################################################################### +//########################################################################### + +// +// @004c8140 -- SubsystemCluster ctor (vtable PTR_FUN_0051a020). GraphicGauge +// base (rate/mode hard-wired every-frame / always-active; the two bit-plane +// masks arrive as mfd_mode and eng_mode). Child roster (binary build order): +// this[0x26] HorizTwoPartBar (temperature, MFD) @004c4170 +// this[0x27] AnimatedSubsystemLamp ("CoolingLoop", btploop) @004c70a4 +// this[0x29] AnimatedSourceLamp ("PowerSource", btpbus) @004c7160 (powered only) +// this[0x24] BackgroundBitmap (strip art -- STAND-IN: not built) +// this[0x28] AnimatedSubsystemLamp (bteloop, Eng) @004c70a4 +// this[0x2D] OneOfSeveralInt (btecmode) @004c5148 +// this[0x2A] AnimatedSourceLamp (btebus) @004c7160 (powered only) +// this[0x25] ScalarBarGauge (evolt, slotOf) @004c721c (powered only) +// this[0x2E] OneOfSeveralStates (btemode) @004c5470 (powered only) +// this[0x2B]/[0x2C] VertTwoPartBar x2 (temperatures) @004c4724 +// this[0x2F] LeakGauge (eleak) @004c5b7c +// +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)Gauge::gaugeRate_A, + (ModeMask)ModeManager::ModeAlwaysActive, + renderer_in, owner_ID, mfd_port, identification_string) +{ + Check_Pointer(this); + + L4Warehouse + *warehouse = (L4Warehouse *)renderer_in->warehousePointer; + + // + // The power-bus gate: the binary gated the power children on the + // "InputVoltage" attribute resolving -- the same population as the + // PoweredSubsystem derivation (every subsystem with a power bus is one). + // + Logical + powered = IsPoweredSubsystemDerived(subsystem_in); + + // + // Temperature feeds by NAME (heat attribute wave pending -> unbound + // cells; the bars draw at minimum until it lands). + // + Scalar + *currentTemp = ScalarAttributeOf(subsystem_in, "CurrentTemperature"), + *degradeTemp = ScalarAttributeOf(subsystem_in, "DegradationTemperature"), + *failTemp = ScalarAttributeOf(subsystem_in, "FailureTemperature"), + *coolantLeak = ScalarAttributeOf(subsystem_in, "CoolantMassLeakRate"); + + //------------------------------------------------------------------ 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, currentTemp, degradeTemp, failTemp, "HorizTwoPartBar"); + Register_Object(temperatureBar); + + coolingLoopA = new AnimatedSubsystemLamp(ChildRate(), mfd_mode, renderer_in, // @004c70a4 + mfd_port, x + 0xe5, y + 0xe, "btploop.pcc", 7, 1, subsystem_in, "CoolingLoop"); + Register_Object(coolingLoopA); + + if (!powered) + { + powerSourceA = NULL; + } + else + { + powerSourceA = new AnimatedSourceLamp(ChildRate(), mfd_mode, renderer_in, // @004c7160 + mfd_port, x + 0x10e, y + 0xe, "btpbus.pcc", 5, 1, + subsystem_in, "PowerSource"); // the SUBSYSTEM -- the connection + // resolves voltageSource by name + Register_Object(powerSourceA); + } + + // + // Background strip art: the binary picked image_names[auxScreenPlacement] + // (sub+0x1e0). STAND-IN: no live placement source (see the aux-screen + // ledger) -> the backdrop child is not built. image_names stays in the + // signature for the day the placement feed lands. + // + (void)image_names; + background = NULL; + + //------------------------------------------------------------ + // Paint the panel frame image into the MFD port. + //------------------------------------------------------------ + localView.SetOrigin(x, y); // vtbl+0x10 + localView.SetColor(0xff); // vtbl+0x18 + if (label != NULL) // (STAND-IN: label may be absent) + { + DrawPortBackground(&localView, warehouse, 9, 0xb4, label); // FUN_004c2ec4 + } + + //----------------------------------------------------------- engineering port + int + engPort = renderer_in->FindGraphicsPort(eng_port_name); + + // + // The second vertical bar tracked the LINKED heat sink's temperatures + // (the binary resolved the "HeatSink" link attribute and read the linked + // sink). Neither the link row nor a linked-sink accessor is published + // yet -- INERT FEEDS (the bar draws, stays at minimum). + // + Scalar + *linkTemp = &UnboundScalarSource, + *linkDegrade = &UnboundScalarSource; + + coolingLoopB = new AnimatedSubsystemLamp(ChildRate(), eng_mode, renderer_in, // @004c70a4 + engPort, 0x254, 0x13b, "bteloop.pcc", 7, 1, subsystem_in, "CoolingLoop"); + Register_Object(coolingLoopB); + + // + // The cooling-mode lamp: binary *(subsys+0x134) == the 1995 HeatSink + // coolantAvailable word. Bound by NAME ("CoolantAvailable") -- unbound + // until the heat attribute wave publishes it (frame 0 = OFF meanwhile). + // + modeLamp = new OneOfSeveralInt(ChildRate(), eng_mode, renderer_in, // @004c5148 + engPort, 400, 10, "btecmode.pcc", 1, 2, + IntegerAttributeOf(subsystem_in, "CoolantAvailable"), "OneOfSeveralInt"); + Register_Object(modeLamp); + + if (!powered) + { + powerSourceB = NULL; + generatorVoltageBar = NULL; + stateLamp = NULL; + } + else + { + powerSourceB = new AnimatedSourceLamp(ChildRate(), eng_mode, renderer_in, // @004c7160 + engPort, 0x1a, 0xd0, "btebus.pcc", 5, 1, subsystem_in, "PowerSource"); + Register_Object(powerSourceB); + + generatorVoltageBar = new ScalarBarGauge(ChildRate(), eng_mode, renderer_in, // @004c721c + engPort, 0x89, 0x82, 0x93, 0x13b, "evolt.pcc", 0xff, 0, + WipeGaugeScalar::wipeUp, 0.0f, GeneratorMaxVoltage, + (void *)subsystem_in, // the SUBSYSTEM -- BTGeneratorVoltage- + "GeneratorVoltage(slotOf)"); // style resolve through voltageSource + + Register_Object(generatorVoltageBar); + + // + // The connect-mode lamp (btemode, 1x3). Binary source = subsys+0x2b8 + // (the PoweredSubsystem modeAlarm region: Manual/Connected/Auto); + // gated IsDerivedFrom(0x50f4bc). STAND-IN: no mode accessor is + // published -- the subsystem itself is passed, so the lamp tracks the + // SIMULATION state word instead (frame 0 in normal play). + // + stateLamp = new OneOfSeveralStates(ChildRate(), eng_mode, renderer_in, // @004c5470 + engPort, 0xbe, 0, "btemode.pcc", 1, 3, + subsystem_in, "OneOfSeveralStates"); + Register_Object(stateLamp); + } + + vertBarA = new VertTwoPartBar(ChildRate(), eng_mode, renderer_in, // @004c4724 + engPort, 0x1d1, 0x5c, 0x1eb, 0x124, tile_image, 0xff, 0xff, 0, + currentTemp, degradeTemp, failTemp, "VertTwoPartBar"); + Register_Object(vertBarA); + + vertBarB = new VertTwoPartBar(ChildRate(), eng_mode, renderer_in, + engPort, 0x222, 0x5c, 0x23d, 0x124, tile_image, 0xff, 0xff, 0, + linkTemp, linkDegrade, failTemp, "VertTwoPartBar"); + Register_Object(vertBarB); + + leakGauge = new BitMapInverseWipe(ChildRate(), eng_mode, renderer_in, // @004c5b7c + engPort, 0x255, 0xe0, "eleak.pcc", 0, 0xff, + 0 /*third: binary *(subsys+0x150); stored-unused*/, 3 /*frames*/, + coolantLeak, "LeakGauge"); + Register_Object(leakGauge); + + failedState = False; // @0xC4 this[0x31] (1 = destroyed) + subsystem = subsystem_in; // @0xC0 this[0x30] + previousDrawState = 0; // @0xC8 this[0x32] + + Check_Fpu(); +} + +// +// @004c87dc -- dtor. Free the owned children (the binary frees them through +// the ReleaseChildren path @004c8820). +// +SubsystemCluster::~SubsystemCluster() +{ + Check(this); + ReleaseChildren(); // @004c8820 +} + +// +// @004c8820 -- free each owned child gauge. +// +void + SubsystemCluster::ReleaseChildren() +{ + if (background) { Unregister_Object(background); delete background; background = NULL; } + if (generatorVoltageBar){ Unregister_Object(generatorVoltageBar); delete generatorVoltageBar; generatorVoltageBar = NULL; } + if (temperatureBar) { Unregister_Object(temperatureBar); delete temperatureBar; temperatureBar = NULL; } + if (coolingLoopA) { Unregister_Object(coolingLoopA); delete coolingLoopA; coolingLoopA = NULL; } + if (coolingLoopB) { Unregister_Object(coolingLoopB); delete coolingLoopB; coolingLoopB = NULL; } + if (powerSourceA) { Unregister_Object(powerSourceA); delete powerSourceA; powerSourceA = NULL; } + if (powerSourceB) { Unregister_Object(powerSourceB); delete powerSourceB; powerSourceB = NULL; } + if (vertBarA) { Unregister_Object(vertBarA); delete vertBarA; vertBarA = NULL; } + if (vertBarB) { Unregister_Object(vertBarB); delete vertBarB; vertBarB = NULL; } + if (modeLamp) { Unregister_Object(modeLamp); delete modeLamp; modeLamp = NULL; } + if (stateLamp) { Unregister_Object(stateLamp); delete stateLamp; stateLamp = NULL; } + if (leakGauge) { Unregister_Object(leakGauge); 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 MFD children. +// POLARITY (PPC-dial fix, kept): the passed value is the DRAW STATE -- +// 0 = alive (children run), nonzero = dead panel (children off). +// +void + SubsystemCluster::SetChildrenEnable(int enable) +{ + if (temperatureBar) temperatureBar->Disable(enable != 0); + if (coolingLoopA) coolingLoopA->Disable(enable != 0); + if (powerSourceA) powerSourceA->Disable(enable != 0); +} + +// +// @004c8990 -- the operational-state read. POLARITY (kept): state 1 = the +// DEAD-panel presentation (black fill + bright frame + X lamps lit); state 0 +// = ALIVE (frame 0 + background art; the WeaponCluster lamp gate runs ONLY +// in state 0). The binary's secondary condition (*(subsystem+0x278) != 4 -> +// 1) remains unidentified [T4] and is dropped -- a healthy subsystem reads +// ALIVE here until that field is decoded. +// +int + SubsystemCluster::GetDrawState() +{ + return failedState ? 1 : 0; +} + +Logical + SubsystemCluster::TestInstance() const +{ + return GraphicGauge::TestInstance(); +} + +// +// @004c88e4 -- Execute (shared by the whole family). failedState = the +// subsystem's own damage saturation (>= 1.0 destroys, mechdmg.cpp -- the +// PPC-dial polarity truth source; the binary read *(subsystem+0x40)==1). +// On a draw-state change repaint the panel frame: alive => frame(0) + redraw +// background bitmap; dead => black fill + frame(0xff); and forward the +// enable to the temperature / loop children. +// +void + SubsystemCluster::Execute() +{ + int + state; + + failedState = (SubsystemDamageLevelOf(subsystem) >= 1.0f) ? True : False; + + state = GetDrawState(); + if (state != previousDrawState) + { + previousDrawState = state; + SetChildrenEnable(state); + if (state == 0) + { + DrawPanelFrame(0); + if (background != NULL) + { + background->BecameActive(); // force the strip-art redraw + } + } + else + { + localView.SetColor(0); // vtbl+0x18 + localView.MoveToAbsolute(1, 0x32); // vtbl+0x24 + localView.DrawFilledRectangleToAbsolute(0x136, 0xad); // vtbl+0x48 + DrawPanelFrame(0xff); + } + } +} + +// +// (Re-host addition, kept from the decode: force the first-frame repaint on +// every activation -- the engine-base default would self-inactivate.) +// +void + SubsystemCluster::BecameActive() +{ + previousDrawState = -1; +} + + +//########################################################################### +// 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. +// +// FEED LEDGER at this site: +// heatFailLamp <- own failedState LIVE +// hsFailLamp <- failFlag (the HUD subsystem's failure) LIVE (Execute) +// powerFailLamp <- binary subsys+0x324 INERT (unidentified +// structFailLamp <- binary subsys+0x320 1995-layout words; +// no recon member) +// heatLoad <- "HeatLoad" by name NAME-DEFERRED +// (binary subsys+0x31c == HeatableSubsystem::heatLoad; +// unpublished -> numeric reads 0 until the heat wave) +// +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) +{ + GaugeConnection + *connection; + int + engPort = renderer_in->FindGraphicsPort(eng_port_name); + + heatLoadScaled = HeatNumericBase; // @0xD8 (100.0f seed) + heatLoad = 0.0f; // @0xD4 + + heatFailLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID, // @004739d8 + engPort, 200, 0x110, fail_lamp_image, 0, 0xff, + (int *)&failedState, "TwoState"); // bright when FAILED + Register_Object(heatFailLamp); + + failSubsystem = hud; // @0xCC + failFlag = False; // @0xD0 + + hsFailLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID, + engPort, 0x97, 0xe8, "btehfail.pcc", 0, 0xff, + (int *)&failFlag, "TwoState"); + Register_Object(hsFailLamp); + + powerFailLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID, + engPort, 0x97, 0xc0, "btepfail.pcc", 0, 0xff, + &UnboundIntegerSource, "TwoState"); // INERT (binary subsys+0x324) + Register_Object(powerFailLamp); + + structFailLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID, + engPort, 0x97, 0x98, "btesfail.pcc", 0xff, 0, // (colour order = binary quirk) + &UnboundIntegerSource, "TwoState"); // INERT (binary subsys+0x320) + Register_Object(structFailLamp); + + 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"); + Register_Object(heatNumeric); + + connection = new GaugeConnectionDirectOf(0, &heatLoad, + ScalarAttributeOf(subsystem_in, "HeatLoad")); // NAME-DEFERRED + Register_Object(connection); + AddConnection(connection); +} + +HeatSinkCluster::~HeatSinkCluster() +{ + Check(this); + + if (heatFailLamp) { Unregister_Object(heatFailLamp); delete heatFailLamp; heatFailLamp = NULL; } + if (hsFailLamp) { Unregister_Object(hsFailLamp); delete hsFailLamp; hsFailLamp = NULL; } + if (powerFailLamp) { Unregister_Object(powerFailLamp); delete powerFailLamp; powerFailLamp = NULL; } + if (structFailLamp) { Unregister_Object(structFailLamp); delete structFailLamp; structFailLamp = NULL; } + if (heatNumeric) { Unregister_Object(heatNumeric); delete heatNumeric; heatNumeric = NULL; } +} + +// +// @004c8db0 -- Execute: failFlag = the HUD subsystem failed (the binary read +// *(failSubsystem+0x40)==1; re-hosted onto the same damage-saturation truth +// source the panel itself uses); heatLoadScaled = heatLoad * 100.0; chain +// SubsystemCluster::Execute. +// +void + HeatSinkCluster::Execute() +{ + if (failSubsystem != NULL) + { + failFlag = (SubsystemDamageLevelOf(failSubsystem) >= 1.0f || + SubsystemDestroyed(failSubsystem)) ? True : False; + } + heatLoadScaled = heatLoad * HeatLoadScale; + SubsystemCluster::Execute(); +} + + +//########################################################################### +// MyomerCluster @004c8df4 (vtable 0x519f88) +//########################################################################### + +// +// @004c8df4 -- ctor: SubsystemCluster base + a SeekVoltageGraph ("EngrGraph") +// fed by a GeneratorVoltageConnection (writes seekValue) + a seek-step +// OneOfSeveralInt (bteseek.pcc). Execute = base. +// +// The seek-step feed: the binary's +0x800 was OOB for a 0x358 Myomers +// (garbage); the decode re-pointed it at Myomers::currentSeekVoltageIndex +// (@0x320, INFERRED -- @004c8df4 isn't in the assert-anchored export). The +// re-host binds the same member BY NAME ("CurrentSeekVoltageIndex" -- +// unpublished by the source410 Myomers table yet -> frame 0 until it lands). +// +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) +{ + GaugeConnection + *connection; + int + engPort = renderer_in->FindGraphicsPort(eng_port_name); + + seekValue = 0.0f; // @0xCC (connection dst) + + seekGraph = (GraphicGauge *)new SeekVoltageGraph(ChildRate(), eng_mode, // @004c6798 + renderer_in, engPort, (Entity *)subsystem_in, + (AttributeAccessor *)&seekValue, "edestryd.pcc", False, "EngrGraph"); + Register_Object(seekGraph); + + seekStepLamp = new OneOfSeveralInt(ChildRate(), eng_mode, renderer_in, // @004c5148 + engPort, 0xf, 0, "bteseek.pcc", 1, 4, + IntegerAttributeOf(subsystem_in, "CurrentSeekVoltageIndex"), + "OneOfSeveralInt"); + Register_Object(seekStepLamp); + + // + // The graph's live input: the powering generator's voltage, resolved + // through the named voltageSource connection (LIVE). + // + connection = new GeneratorVoltageConnection(&seekValue, subsystem_in); // @004c3288 + Register_Object(connection); + AddConnection(connection); +} + +MyomerCluster::~MyomerCluster() +{ + Check(this); + + if (seekGraph) { Unregister_Object(seekGraph); delete seekGraph; seekGraph = NULL; } + if (seekStepLamp) { Unregister_Object(seekStepLamp); delete seekStepLamp; seekStepLamp = NULL; } +} + + +//########################################################################### +// WeaponCluster @004c8fc4 (vtable 0x519f38) +//########################################################################### + +// +// @004c8fc4 -- ctor: SubsystemCluster base; interns the cluster image; +// centres a SegmentArc270 recharge dial (reads PercentDone -- PUBLISHED, +// LIVE) + a ConfigMapGauge; adds a Scalar connection feeding percentDone. +// The warning-lamp centre is derived from the cluster image size, or warned +// "WeaponCluster missing ". +// +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) +{ + GaugeConnection + *connection; + Scalar + *percentDoneAttr = ScalarAttributeOf(subsystem_in, "PercentDone"); // LIVE (mechweap.cpp 0x12) + + //------------------------------------------------------------ + // Intern the cluster image + derive the warning-lamp centre. + //------------------------------------------------------------ + clusterImage = nameCopy(cluster_image); // @0xCC + + L4Warehouse + *warehouse = (L4Warehouse *)renderer_in->warehousePointer; + BitMap + *img = warehouse->bitMapBin.Get(clusterImage); // ctor AddRef (dtor releases) + + if (img == NULL) + { + DEBUG_STREAM << "WeaponCluster missing " << clusterImage << "\n"; + warningCenterX = 0; // @0xDC + warningCenterY = 0; // @0xE0 + } + 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 (a real 0..1 fraction). + //------------------------------------------------------------ + rechargeArc = (GraphicGauge *)new SegmentArc270( + (GaugeRate)Gauge::gaugeRate_A, mfd_mode, + 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, percentDoneAttr, True, + "SegmentArc270"); + Register_Object(rechargeArc); + + configMap = new ConfigMapGauge(ChildRate(), mfd_mode, renderer_in, // @004c6d80 + mfd_port, x + 0xee, y + 0x38, (Entity *)subsystem_in, "ConfigMap"); + Register_Object(configMap); + + warningState = 0; // @0xE4 + + connection = new GaugeConnectionDirectOf(0, &percentDone, percentDoneAttr); + Register_Object(connection); + AddConnection(connection); +} + +WeaponCluster::~WeaponCluster() +{ + Check(this); + + L4Warehouse + *warehouse = (L4Warehouse *)renderer->warehousePointer; + + warehouse->bitMapBin.Release(clusterImage); + Unregister_Pointer(clusterImage); + delete[] clusterImage; + clusterImage = NULL; + + if (rechargeArc) { Unregister_Object(rechargeArc); delete rechargeArc; rechargeArc = NULL; } + if (configMap) { Unregister_Object(configMap); delete configMap; configMap = NULL; } +} + +// +// @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 (draw-state 0 = alive panels only). +// +// PPC-dial REPAINT FIX (kept on re-host): the base Execute BLACK-FILLS + +// repaints the panel on every draw-state flip -- wiping the recharge arc's +// pixels while the arc's INCREMENTAL draw memory (previousSegment) still +// says "lit". The stale arc then draws nothing until the value crosses new +// segments ("tick marks frozen, dot still toggles"). Detect the repaint and +// reset the arc / configMap / lamp draw memory so they fully redraw. +// +void + WeaponCluster::Execute() +{ + int + preDrawState = previousDrawState; + + SubsystemCluster::Execute(); + if (previousDrawState != preDrawState) + { + if (rechargeArc) rechargeArc->BecameActive(); + if (configMap) configMap->BecameActive(); + warningState = -2; // lamp: force redraw too + } + + 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", +// live cursor) + a seek-step OneOfSeveralInt (bteseek.pcc). +// +// The live-cursor voltage: the binary bound the emitter's "OutputVoltage" +// attribute (the raw charge voltage, binary currentLevel @0x414). Our +// recon publishes that SAME member as "ChargeLevel" (emitter.hpp:159, the +// authentic 0x1D row) -- so the authentic name is tried first and the +// source410 alias second: the cursor is LIVE on every emitter/PPC today. +// +// The seek-step: binary subsys+0x3f0 == Emitter::seekVoltageIndex, bound BY +// NAME ("CurrentSeekVoltageIndex" -- unpublished by the source410 Emitter +// table yet -> frame 0 until that row lands). +// +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"); + + if (outputVoltage == NULL) + { + outputVoltage = AttributePointerOf(subsystem_in, "ChargeLevel"); // source410 alias + } + + seekGraph = new SeekVoltageGraph(ChildRate(), eng_mode, renderer_in, // @004c6798 + engPort, (Entity *)subsystem_in, (AttributeAccessor *)outputVoltage, + "edestryd.pcc", True, "EngrGraph"); + Register_Object(seekGraph); + + seekStepLamp = new OneOfSeveralInt(ChildRate(), eng_mode, renderer_in, // @004c5148 + engPort, 0xf, 0, "bteseek.pcc", 1, 4, + IntegerAttributeOf(subsystem_in, "CurrentSeekVoltageIndex"), + "OneOfSeveralInt"); + Register_Object(seekStepLamp); +} + +EnergyWeaponCluster::~EnergyWeaponCluster() +{ + Check(this); + + if (seekGraph) { Unregister_Object(seekGraph); delete seekGraph; seekGraph = NULL; } + if (seekStepLamp) { Unregister_Object(seekStepLamp); delete seekStepLamp; seekStepLamp = NULL; } +} + + +//########################################################################### +// BallisticWeaponCluster @004c9558 (vtable 0x519e98) +//########################################################################### + +// +// @004c9558 -- ctor: WeaponCluster base + the ammo panel: two +// NumericDisplayIntegers (ammo counts), jam/fire TwoStates, a +// NumericDisplayScalarTwoState (reload seconds), a destroyed TwoState, plus +// the erase tracker and eject wipe. +// +// FEED LEDGER at this site: +// jam lamp <- jammed = GetWeaponState()==JammedState LIVE (Execute) +// destroyed X <- own failedState LIVE +// ammo counters <- INERT 0: the weapon's ammo store is +// ProjectileWeapon::ammoBinLink (PROJWEAP.HPP:102) -- +// protected, no accessor, no published attribute; the +// AmmoBin accessors (GetAmmoCount/GetAmmoState) are +// unreachable without the link. (The binary read the +// resolved bin's count.) +// fire lamp / <- INERT 0: the feed/reload state (binary bin+0x18c) has +// reload numeric no recon member either. +// ammoErase <- NULL: GraphicsViewRecord tracker (binary @0044ad78) -- +// bring-up NULL kept; the numeric self-erases. +// ejectWipe <- NULL: BitMapInverseWipeScalar (@004c61c8) is not +// reconstructed in btl4gaug (notes-only); bring-up NULL. +// reload timer <- left 0 (the binary's frame clock DAT_0052140c +// equivalent is not wired). +// +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) +{ + int + engPort = renderer_in->FindGraphicsPort(eng_port_name); + int + *ammoValue = &UnboundIntegerSource; // INERT (see ledger) + + reloadStartTime = 0; // @0xF4 + reloadSeconds = 0.0f; // @0xF8 + jammed = False; // @0xE8 + firing = False; // @0xEC + reloading = False; // @0xF0 + + 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"); + Register_Object(ammoCountA); + + 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"); + Register_Object(ammoCountB); + + jamLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID, // @004739d8 + engPort, 0xa0, 0xb2, "btejam.pcc", 0, 0xff, (int *)&jammed, "TwoState"); + Register_Object(jamLamp); + + fireLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID, + engPort, 0x121, 0xb6, "btefire.pcc", 0, 0xff, (int *)&firing, "TwoState"); + Register_Object(fireLamp); + + ammoErase = NULL; // @0xFC (bring-up NULL, see ledger) + + 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"); + Register_Object(ammoNumeric); + + destroyedLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID, + engPort, 0xc2, 0x85, "edestryd.pcc", 0, 0xff, + (int *)&failedState, "TwoState"); // the destroyed X: bright when FAILED + Register_Object(destroyedLamp); + + ejectWipe = NULL; // @0x110 (bring-up NULL, see ledger) +} + +BallisticWeaponCluster::~BallisticWeaponCluster() +{ + Check(this); + + if (ammoNumeric) { Unregister_Object(ammoNumeric); delete ammoNumeric; ammoNumeric = NULL; } + if (ammoCountA) { Unregister_Object(ammoCountA); delete ammoCountA; ammoCountA = NULL; } + if (ammoCountB) { Unregister_Object(ammoCountB); delete ammoCountB; ammoCountB = NULL; } + if (jamLamp) { Unregister_Object(jamLamp); delete jamLamp; jamLamp = NULL; } + if (fireLamp) { Unregister_Object(fireLamp); delete fireLamp; fireLamp = NULL; } + if (destroyedLamp) { Unregister_Object(destroyedLamp); delete destroyedLamp; destroyedLamp = NULL; } + delete ammoErase; ammoErase = NULL; // (never allocated today) + delete ejectWipe; ejectWipe = NULL; +} + +// +// @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 = the weapon-state alarm reads Jammed (LIVE +// through GetWeaponState -- the binary's raw *(subsys+0x364)==5 retired); +// the ammo-bin feed/reload state is INERT (see the ctor ledger); chain +// WeaponCluster::Execute. +// +void + BallisticWeaponCluster::Execute() +{ + jammed = WeaponJammedOf(subsystem); + + // + // INERT: reload/feed state (binary bin+0x18c) -- no recon member; the + // fire lamp stays dark and the reload numeric stays gated off. + // + firing = False; + reloading = False; + + WeaponCluster::Execute(); +} + +// +// @004c9b50 -- DrawWarningLamp override: draw the fire-ready "dot" (the base +// blits the cluster image in on-colour 0xff / off-colour 0), THEN swap the +// base-page ammo count's colours so the digits stay legible against it: +// dot ABSENT (on==0) -> SetColors(bg=0, fg=0xff) == lit digits on black +// dot PRESENT (on!=0) -> SetColors(bg=0xff, fg=0) == dark digits cut out +// of the solid dot (the stencil look) +// The SetColors call ForceUpdate()s the numeric so it repaints over the +// freshly drawn dot on the next child-execute pass. (Verified vs binary +// FUN_004c9b50 + the base FUN_004c932c.) +// +void + BallisticWeaponCluster::DrawWarningLamp(int on) +{ + WeaponCluster::DrawWarningLamp(on); // @004c932c: draw the dot + if (ammoCountA) + { + ((NumericDisplayInteger *)ammoCountA)->SetColors( + on ? 0xff : 0, + on ? 0 : 0xff); + } +} + +// +// @004c9adc -- TestInstance. The binary: True unless the ammo bin is +// present with rounds and the weapon isn't jammed (then the base test runs). +// The bin is unreachable on the re-host (see the ctor ledger), so the +// jam-side of the predicate is kept and the ammo-empty gate is deferred +// with the ammo feed. +// +Logical + BallisticWeaponCluster::TestInstance() const +{ + 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 aux screen - 1. The +// recovered mask constants resolve exactly onto the authentic BTL4ModeManager +// names (BTL4MODE.HPP): planeMask = the MFD quad plane, subBit = the +// engineering sub-screen plane. +// +struct AuxScreenGeometry +{ + int + x, y; + ModeMask + planeMask; + ModeMask + subBit; + const char + *mfdPortName; + const char + *engPortName; + int + planeIndex; +}; + +static const AuxScreenGeometry + auxScreenGeometry[12] = + { + { 0, 0xf0, BTL4ModeManager::ModeMFD1Quad, BTL4ModeManager::ModeMFD1Eng1, "mfd1", "eng1", 0 }, // grp 1 + { 0x142, 0xf0, BTL4ModeManager::ModeMFD1Quad, BTL4ModeManager::ModeMFD1Eng2, "mfd1", "eng1", 0 }, // grp 2 + { 0, 0x1c, BTL4ModeManager::ModeMFD1Quad, BTL4ModeManager::ModeMFD1Eng3, "mfd1", "eng1", 0 }, // grp 3 + { 0x142, 0x1c, BTL4ModeManager::ModeMFD1Quad, BTL4ModeManager::ModeMFD1Eng4, "mfd1", "eng1", 1 }, // grp 4 + { 0, 0xf0, BTL4ModeManager::ModeMFD2Quad, BTL4ModeManager::ModeMFD2Eng1, "mfd2", "eng2", 1 }, // grp 5 + { 0x142, 0xf0, BTL4ModeManager::ModeMFD2Quad, BTL4ModeManager::ModeMFD2Eng2, "mfd2", "eng2", 1 }, // grp 6 + { 0, 0x1c, BTL4ModeManager::ModeMFD2Quad, BTL4ModeManager::ModeMFD2Eng3, "mfd2", "eng2", 1 }, // grp 7 + { 0x142, 0x1c, BTL4ModeManager::ModeMFD2Quad, BTL4ModeManager::ModeMFD2Eng4, "mfd2", "eng2", 2 }, // grp 8 + { 0, 0xf0, BTL4ModeManager::ModeMFD3Quad, BTL4ModeManager::ModeMFD3Eng1, "mfd3", "eng3", 2 }, // grp 9 + { 0x142, 0xf0, BTL4ModeManager::ModeMFD3Quad, BTL4ModeManager::ModeMFD3Eng2, "mfd3", "eng3", 2 }, // grp 10 + { 0, 0x1c, BTL4ModeManager::ModeMFD3Quad, BTL4ModeManager::ModeMFD3Eng3, "mfd3", "eng3", 2 }, // grp 11 + { 0x142, 0x1c, BTL4ModeManager::ModeMFD3Quad, BTL4ModeManager::ModeMFD3Eng4, "mfd3", "eng3", 2 }, // grp 12 + }; + +// +// @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. +// +// STAND-INS (see the aux-screen ledger): the screen number is the sequential +// roster assignment; the label .pcc is absent (NULL -> the panel-frame label +// blit is skipped). The energy branch's cluster-image pick keyed +// *(sub+0x334)==0 -> qcircle / else qcircr in the binary; that word is +// BEST-EFFORT identified as the rear-firing flag and read through the +// PUBLISHED "RearFiring" attribute here. +// +Logical + VehicleSubSystems::Make( + int /*display_port_index*/, + Vector2DOf /*position*/, + Entity *entity, + GaugeRenderer *gauge_renderer + ) +{ + L4GaugeRenderer + *renderer = (L4GaugeRenderer *)gauge_renderer; + char + *imageNames[8]; + int + p, + i, + count; + + Check(gauge_renderer); + Check(entity); + + //------------------------------------------------------------ + // The 8 status strip bitmaps (qXXX0.pcc..qXXX7.pcc). + //------------------------------------------------------------ + for (p = 0; p < 8; p++) + { + imageNames[p] = methodDescription.parameterList[p].data.string; + } + + count = entity->GetSubsystemCount(); + for (i = 1; i < count; i++) // slot 0 = the controls mapper + { + Subsystem + *sub = entity->GetSubsystem(i); + + if (sub == NULL || !IsPanelSubsystem(sub)) + { + continue; + } + + int + auxScreen = AuxScreenAssignmentOf(entity, sub), + group = auxScreen - 1; + + if (group < 0) + { + DEBUG_STREAM << "Auxiliary screen position = zero\n"; + continue; + } + if (group >= 12) + { + DEBUG_STREAM << "Illegal auxiliary screen position =" << group << "\n"; + continue; + } + + if (getenv("BT_MECH_LOG")) + { + DEBUG_STREAM << "[gau2] cluster slot=" << i + << " classID=" << (int)sub->GetClassID() + << " auxScreen=" << auxScreen + << " name=" << sub->GetName() << endl << flush; + } + + const AuxScreenGeometry + &g = auxScreenGeometry[group]; + int + mfdPort = renderer->FindGraphicsPort(g.mfdPortName); + char + *label = NULL, // STAND-IN: no live auxScreenLabel + *tile = (char *)"btwarn.pcc"; + + switch (sub->GetClassID()) + { + case RegisteredClass::SensorClassID: // 0xBC3 -> HeatSinkCluster (+ "HUD") + { + Subsystem + *hud = entity->FindSubsystem("HUD"); + HeatSinkCluster + *cluster = new HeatSinkCluster(g.planeMask, renderer, 0, mfdPort, + g.x, g.y, sub, hud, g.subBit, g.engPortName, tile, label, + "edestryd.pcc", imageNames, "SensorCluster"); + + Register_Object(cluster); + break; + } + case RegisteredClass::MyomersClassID: // 0xBC6 -> MyomerCluster + { + MyomerCluster + *cluster = new MyomerCluster(g.planeMask, renderer, 0, mfdPort, + g.x, g.y, sub, g.subBit, g.engPortName, tile, label, + imageNames, "MyomerCluster"); + + Register_Object(cluster); + break; + } + case RegisteredClass::EmitterClassID: // 0xBC8 + case RegisteredClass::PPCClassID: // 0xBD4 + { + // + // BEST-EFFORT (binary *(sub+0x334)==0): the ring-art pick, read + // through the published RearFiring attribute. + // + const char + *clusterImage = "qcircle.pcc"; + Logical + *rearFiring = (Logical *)AttributePointerOf(sub, "RearFiring"); + + if (rearFiring != NULL && *rearFiring != 0) + { + clusterImage = "qcircr.pcc"; + } + + EnergyWeaponCluster + *cluster = new EnergyWeaponCluster(g.planeMask, renderer, 0, mfdPort, + g.x, g.y, sub, g.subBit, g.engPortName, tile, label, + clusterImage, imageNames, "EnergyWeaponCluster"); + + Register_Object(cluster); + break; + } + case RegisteredClass::ProjectileWeaponClassID: // 0xBCD + case RegisteredClass::MissileLauncherClassID: // 0xBD0 + { + BallisticWeaponCluster + *cluster = 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"); + + Register_Object(cluster); + break; + } + case RegisteredClass::GaussRifleClassID: // 0xBCE -- not yet supported + DEBUG_STREAM << "Gauss rifle not yet supported\n"; + break; + default: + break; + } + } + return True; +} + +// +// The registered method: config primitive "vehicleSubSystems(q0..q7.pcc)". +// KEYWORD + PARAMETER LIST ARE PARSE-CRITICAL. +// +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 + PARAMETER_DESCRIPTION_END + } + }; + + +// === btl4gau3.cpp begins at @004c9bd0 (PlayerStatusMappingGroup) -- not this TU. diff --git a/restoration/source410/BT_L4/BTL4GAU2.HPP b/restoration/source410/BT_L4/BTL4GAU2.HPP new file mode 100644 index 00000000..8bb1ab3e --- /dev/null +++ b/restoration/source410/BT_L4/BTL4GAU2.HPP @@ -0,0 +1,572 @@ +//===========================================================================// +// File: btl4gau2.hpp // +// Project: BattleTech Brick: Gauge Renderer Manager // +// Contents: Cockpit instrument library, part 2 -- the COMPOSITE "panel" // +// gauges that the BattleTech engineering / weapon-status HUD is // +// assembled from. Where btl4gaug.cpp holds the primitive widgets // +// (bars, dials, lamps, colour-mappers), btl4gau2.cpp holds the // +// higher-level gauges that AGGREGATE those primitives into a single // +// subsystem read-out: // +// * SeekVoltageGraph -- energy-weapon seek-voltage curve // +// * ConfigMapGauge -- joystick / config-manager state lamp // +// * GeneratorCluster -- generator voltage + coolant + leak panel // +// * PrepEngrScreen -- per-weapon engineering screen builder // +// * SubsystemCluster +-> HeatSinkCluster // +// +-> MyomerCluster // +// +-> WeaponCluster +-> EnergyWeaponCluster // +// +-> BallisticWeaponClu.// +//---------------------------------------------------------------------------// +// Date Who Modification // +// -------- --- ---------------------------------------------------------- // +// 02/22/96 CPB Initial coding. // +//---------------------------------------------------------------------------// +// Copyright (C) 1996, Virtual World Entertainment, Inc. All rights reserved // +// PROPRIETARY AND CONFIDENTIAL // +//===========================================================================// +// +// STAGED RECONSTRUCTION. No BTL4GAU2.HPP survived; recovered from the shipped +// binary (BTL4OPT.EXE) and re-hosted onto the authentic 1995 engine headers +// (MUNGA GAUGE/GAUGREND + MUNGA_L4 L4GAUGE) for the BC++4.52 build. +// +// Translation-unit extent (BTL4.MAK link order: +// btl4mode -> btl4rdr -> btl4gaug -> btl4gau2 -> btl4gau3 -> btl4grnd -> ...): +// first gau2 code @004c6798 (SeekVoltageGraph::SeekVoltageGraph) +// last gau2 code @004c9b50 (BallisticWeaponCluster helpers) +// plus VehicleSubSystems::Make @004cbaf0 +// +// Class names recovered from the CODE name-string pool around 0x519760 +// (the gauge name list) and the per-instance identification strings: +// "GeneratorCluster" 0x519a8c "PrepEngr" 0x519a9d +// "ConfigMap" 0x519a38 "CoolingLoop" 0x519a42 "PowerSource" 0x519a4e +// "GeneratorVoltage(scalar)" 0x519a73 "GeneratorVoltage(slotOf)" 0x519a5a +// "EngrGraph" 0x519a2e "WeaponCluster ... " 0x519dcb +// Field offsets in comments are the BINARY's byte offsets (e.g. "@0x90" == +// this[0x24]); the recompiled objects are NOT byte-locked to them. +// +// Names flagged "(best-effort)" exist in the binary but their identification +// string could not be tied to them unambiguously. +// + +#if !defined(BTL4GAU2_HPP) +# define BTL4GAU2_HPP + +# if !defined(SLOT_HPP) +# include +# endif +# if !defined(L4GREND_HPP) +# include +# endif +# if !defined(L4GAUGE_HPP) +# include +# endif +# if !defined(BTL4GAUG_HPP) +# include +# endif +# if !defined(HEAT_HPP) +# include +# endif + // (see btl4gaug.hpp -- intentionally not pulled in; the + // composite gauges reference subsystems by pointer/attribute, not by type.) + + // + // Reconstruction type: the original referenced an "AttributeAccessor" handle + // (a small object that samples one named subsystem attribute). Only its + // address is used here (it is immediately reinterpreted as a Scalar*), so an + // opaque forward declaration is sufficient for this translation unit. + // + class AttributeAccessor; + + + //####################################################################### + // SeekVoltageGraph -- the energy-weapon / myomer "seek voltage" response + // graph on the engineering detail pages (the "POWER" box). + // @004c6798 ctor / @004c68ac dtor / @004c6920 BecameActive / @004c6934 Execute. + // vtable PTR_FUN_0051a1fc. Resolves the four SeekVoltage attribute + // POINTERS off the weapon subsystem by NAME ("CurrentSeekVoltageIndex" / + // "MinSeekVoltageIndex" / "MaxSeekVoltageIndex" / "SeekVoltage") and plots + // the subsystem's voltage-response curve (binary vtbl slot 15 sampler: + // Emitter @004bb42c / Myomers @004b8f94) over the top data box + // (0x97,0x80)-(0x17d,0x13b): x = response(v)*230 (0..1 normalised sqrt + // response), y = v/12000*187 (voltage axis), with per-gear tick marks (the + // current gear drawn as a full L) and, when draw_cursor, a live 8x8 XOR + // cursor at the subsystem's live voltage. When the subsystem is destroyed + // (simulationState == 1) it shows a centred "destroyed" bitmap instead. + // Its Execute's full-box CLEAR (@004c6be4) is what erases the sibling eng + // pages' stale pixels on the shared Eng bit-plane -- the widget IS the + // top-box eraser (Gitea #10 finding A). + //####################################################################### + class SeekVoltageGraph : + public GraphicGauge + { + public: + SeekVoltageGraph( // @004c6798 + 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); + ~SeekVoltageGraph(); // @004c68ac + Logical TestInstance() const; + void BecameActive(); // @004c6920 + void Execute(); // @004c6934 + protected: + void ClearGraph(); // @004c6be4 (full-box erase) + void DrawTicks(); // @004c6c6c (per-gear ticks, XOR) + void DrawCursorBox(); // @004c6c30 (8x8 cursor, XOR) + + char *destroyedImage; // @0x90 this[0x24] + int *currentSeekIndexPtr; // @0x94 this[0x25] attr ptr (or NULL) + int *minSeekIndexPtr; // @0x98 this[0x26] attr ptr (or NULL) + int *maxSeekIndexPtr; // @0x9C this[0x27] attr ptr (or NULL) + Scalar *seekVoltageTablePtr; // @0xA0 this[0x28] attr ptr (array base) + Scalar *liveVoltage; // @0xA4 this[0x29] the ctor's + // seek_voltage_pointer ("OutputVoltage" + // attr ptr / cluster seekValue cache) -- + // Execute derefs it for the live cursor + // (never NULL: ctor normalises a miss to + // the file-scope unbound cell) + Subsystem *subsystem; // @0xA8 this[0x2A] + Scalar previousVoltage; // @0xAC this[0x2B] cached response at + // 12000 V (9999 sentinel = force replot) + Scalar xScale; // @0xB0 this[0x2C] =230.0f + Scalar yScale; // @0xB4 this[0x2D] =187.0f + int destroyedShown; // @0xB8 this[0x2E] + Logical drawCursor; // @0xBC this[0x2F] + int cursorX; // @0xC0 this[0x30] (-1 = no cursor drawn) + int cursorY; // @0xC4 this[0x31] + int tickIndexShown; // @0xC8 this[0x32] gear index the tick + // highlight was last drawn at + }; + + + //####################################################################### + // ConfigMapGauge -- the joystick / config-manager (control-mapper) state + // lamp. @004c6d80 ctor / @004c6e54 dtor / @004c6ee0 LinkToEntity / + // @004c6ef4 BecameActive / @004c6f1c Execute. vtable PTR_FUN_0051a1b8. + // Owns a base joystick bitmap ("btjoy.pcc") plus four config-state pixmaps + // (cm_off/cm_other/cm_only/cm_both .pcc) selected from a 4-entry table + // (DAT_00518eb8) driven by the control-mapper state. Named "ConfigMap". + //####################################################################### + class ConfigMapGauge : + public GraphicGauge + { + public: + ConfigMapGauge( // @004c6d80 + GaugeRate, ModeMask, L4GaugeRenderer *, int graphics_port_number, + int x, int y, Entity *subsystem, const char *identification_string); + ~ConfigMapGauge(); // @004c6e54 + Logical TestInstance() const; + // @004c6ee0 -- the GaugeBase::LinkToEntity OVERRIDE (vtbl slot 9, +0x24), + // NOT a "SetColor": it stores the viewpoint entity into @0x94 and Execute + // gates on it. GaugeRenderer::LinkToEntity (GAUGREND.CPP:3011) broadcasts + // it to every gauge when the viewpoint binds -- THAT is what lights the + // trigger-config joystick in the shipped game. (The old "SetColor, no + // callers -> authentically dormant" claim was a mislabel.) + void LinkToEntity(Entity *entity); // @004c6ee0 writes this[0x25] + void BecameActive(); // @004c6ef4 + void Execute(); // @004c6f1c + protected: + Entity *subsystem; // @0x90 this[0x24] (a Subsystem, stored + // as the ctor's Entity* -- cast back + // through Subsystem* before use) + Entity *linkedEntity; // @0x94 this[0x25] (Execute gate) + char *joystickImage; // @0x98 this[0x26] "btjoy.pcc" + char *stateImage[4]; // @0x9C this[0x27..0x2A] + int previousState[4]; // @0xAC this[0x2B..0x2E] + Logical dirty; // @0xBC this[0x2F] + }; + + + //####################################################################### + // Two small OneOfSeveral subclasses that drive the selected frame from a + // subsystem value through a file-private GaugeConnection. + // AnimatedSubsystemLamp @004c70a4 (vtable 0051a174) -- connection @004c3134 + // AnimatedSourceLamp @004c7160 (vtable 0051a130) -- connection @004c31ec + // Both chain OneOfSeveral::OneOfSeveral (@004c4d88, fromImageStrip=1) and + // share dtor FUN_004c4e7c. Used for the cooling-loop / power-source lamps. + // (Class names best-effort; the gauge is generic and reused with the + // instance strings "CoolingLoop" and "PowerSource".) + // + // SHADOW-FIELD FIX (kept on re-host): 'selected' is INHERITED from + // OneOfSeveral (@0xAC). A redeclared 'int selected;' here shadowed the + // base at a higher offset -- the connection wrote the shadow while + // OneOfSeveral::Execute read the base (always 0), freezing the lamp at + // frame 0. Neither class declares its own copy. + //####################################################################### + class AnimatedSubsystemLamp : // (best-effort) + public OneOfSeveral + { + public: + AnimatedSubsystemLamp( // @004c70a4 + GaugeRate, ModeMask, L4GaugeRenderer *, int, + int x, int y, const char *image, int columns, int rows, + void *subsystem, const char *identification_string); + ~AnimatedSubsystemLamp(); // @004c7134 + }; + + class AnimatedSourceLamp : // (best-effort) + public OneOfSeveral + { + public: + AnimatedSourceLamp( // @004c7160 + GaugeRate, ModeMask, L4GaugeRenderer *, int, + int x, int y, const char *image, int columns, int rows, + void *source, const char *identification_string); + ~AnimatedSourceLamp(); // @004c71f0 + }; + + + //####################################################################### + // ScalarBarGauge -- a generator-voltage bar (derives from the engine + // BarGraphBitMapScalar, L4GAUGE.HPP -- WipeGaugeScalar : GraphicGauge). + // Two ctors, one vtable (0051a0e4), shared dtor @004c733c: + // @004c721c "slotOf" variant -- AddConnection(GeneratorVoltageConnection + // @004c3288) feeding the inherited currentValue from the + // subsystem's resolved voltage source. + // @004c72ac plain-Scalar variant -- a GaugeConnectionDirectOf + // (engine template) from a DIRECT Scalar*. + // Used with "GeneratorVoltage(slotOf)" / "GeneratorVoltage(scalar)". + // + // Its value slot @0xB4 (this[0x2D]) IS the inherited + // WipeGaugeScalar::currentValue -- the connection writes it, so NO own + // field is declared (declaring one would shadow / mis-align). + // + // BC++4.52 NOTE: the void* / Scalar* 15th-argument overload pair is + // ambiguous for a literal 0/NULL argument -- every call site must pass a + // TYPED pointer. + //####################################################################### + class ScalarBarGauge : + public BarGraphBitMapScalar + { + public: + ScalarBarGauge( // @004c721c (slotOf variant) + GaugeRate, ModeMask, L4GaugeRenderer *, 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); + ScalarBarGauge( // @004c72ac (plain-Scalar variant) + GaugeRate, ModeMask, L4GaugeRenderer *, 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); + ~ScalarBarGauge(); // @004c733c + }; + + + //####################################################################### + // GeneratorCluster -- the generator read-out panel (config keyword + // "GeneratorCluster": engineering buttons 9-12, GeneratorA..D). + // @004c7368 Make / @004c746c ctor / @004c7778 dtor. vtable PTR_FUN_0051a0a0. + // A GraphicGauge that owns five child gauges, parented to its own port: + // VertTwoPartBar (temperature) this[0x26] + // ScalarBarGauge (voltage 0..12kV) this[0x27] + // BitMapInverseWipe/Leak (coolant leak) this[0x28] + // TwoState x2 (status lamps) this[0x29],this[0x2A] + // Make resolves the named generator via FindSubsystem; warns + // "Subsystem not found". + //####################################################################### + class GeneratorCluster : + public GraphicGauge + { + public: + // Registered config primitive "GeneratorCluster" (15 params; NO leading + // rate token -- the panel rate is hard-wired 0xFFFF). + static MethodDescription methodDescription; + static Logical Make(int, Vector2DOf, Entity *, GaugeRenderer *); // @004c7368 + GeneratorCluster( // @004c746c (BINARY param order) + ModeMask, L4GaugeRenderer *, int owner_ID, int graphics_port_number, + int x, int y, Subsystem *subsystem, + 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); + ~GeneratorCluster(); // @004c7778 + // A container panel MUST override BecameActive + Execute: the engine + // base Gauge::Execute is Fail("not overridden") (GAUGE.CPP:598), and + // the default BecameActive self-inactivates. The panel draws its + // static background pixmap; the 5 self-registered child gauges draw + // the dynamic content. + void BecameActive(); + void Execute(); + protected: + char *backgroundImage; // @0x90 this[0x24] interned .pcc (AddRef'd) + int secondaryColor; // @0x94 this[0x25] = leak colorB (stored, unread) + GraphicGauge + *temperatureBar, // @0x98 this[0x26] VertTwoPartBar + *voltageBar, // @0x9C this[0x27] ScalarBarGauge (@004c72ac variant) + *leakGauge, // @0xA0 this[0x28] BitMapInverseWipe + *lampA, // @0xA4 this[0x29] TwoState + *lampB; // @0xA8 this[0x2A] TwoState + Subsystem *subsystem; // @0xAC this[0x2B] + int _reserved0xB0; // @0xB0 this[0x2C] (trailing pad -> binary sizeof 0xB4) + }; + + + //####################################################################### + // PrepEngrScreen (config keyword "prepEngr") -- the per-engineering-screen + // STATIC LABEL overlay. @004c7b30 Make / @004c7bf0 ctor / @004c7d14 dtor / + // @004c7e48 BecameActive. vtable PTR_FUN_0051a06c. + // + // A GraphicGaugeBackground (paint-on-activation, NOT an Execute gauge -- + // the same base/vtable shape as the engine's BackgroundBitmap: only the + // dtor + BecameActive slots are overridden). Interns 7 status .pcc's + // (font + 6 labels) and, when its MFD-Eng screen (1..12) activates, paints + // the labels for the ONE subsystem whose aux screen == this screen, + // dispatched by the subsystem's ClassID. + // (FIX kept from the decode: mech/screenNumber were SWAPPED in the first + // reconstruction, and the missing methodDescription parse-skipped all 12 + // CFG lines.) + //####################################################################### + class PrepEngrScreen : + public GraphicGaugeBackground + { + public: + static MethodDescription methodDescription; // "prepEngr" + static Logical Make(int, Vector2DOf, Entity *, GaugeRenderer *); // @004c7b30 + PrepEngrScreen( // @004c7bf0 + ModeMask, L4GaugeRenderer *, unsigned int owner_ID, + int graphics_port_number, Entity *mech, int screen_number, + const char *img0/*font*/, const char *img1, const char *img2, + const char *img3, const char *img4, const char *img5, + const char *img6, const char *identification_string = "PrepEngr"); + ~PrepEngrScreen(); // @004c7d14 + void BecameActive(); // @004c7e48 (the paint slot -- NOT Execute) + protected: + int screenNumber; // @0x6C this[0x1B] + Entity *mech; // @0x70 this[0x1C] + char *statusImage[7]; // @0x74 this[0x1D..0x23] (interned .pcc names) + // binary sizeof == 0x90 (Make alloc); no offset locks (every read is + // via named members). + }; + + + //####################################################################### + // SubsystemCluster family -- the big composite panels. Each is a + // GraphicGauge that builds a dozen child gauges into one cockpit instrument. + // + // SubsystemCluster @004c8140 (vtable 0051a020) -- base "engineering" + // panel: HorizTwoPartBar + cooling/power loops + generator voltage + + // OneOfSeveralInt + OneOfSeveralStates + 2x VertTwoPartBar + LeakGauge + // + BackgroundBitmap. Uses the btXXloop / btXbus / evolt / btecmode / + // eleak art. + // + // HeatSinkCluster @004c8a6c (vtable 00519fd4) : SubsystemCluster + // adds 4 failure-annunciator TwoStates (btehfail/btepfail/btesfail) + // and a NumericDisplayScalar. Execute @004c8db0 scales heatLoad by + // 100 (_DAT_004c8df0) into the numeric read-out. + // + // MyomerCluster @004c8df4 (vtable 00519f88) : SubsystemCluster + // adds a SeekVoltageGraph ("EngrGraph") + seek-step OneOfSeveralInt. + // + // WeaponCluster @004c8fc4 (vtable 00519f38) : SubsystemCluster + // adds a SegmentArc270 (recharge dial, 0..360) + a ConfigMapGauge. + // Reads "PercentDone". Execute @004c9290 toggles the warning lamp + // past _DAT_004c92e0 (0.99). + // + // EnergyWeaponCluster @004c93b0 (vtable 00519ee8) : WeaponCluster + // adds a SeekVoltageGraph ("EngrGraph", edestryd.pcc) and a + // seek-step OneOfSeveralInt (bteseek.pcc). + // BallisticWeaponCluster @004c9558 (vtable 00519e98) : WeaponCluster + // adds 2x NumericDisplayInteger (ammo), jam/fire TwoStates + // (btejam/btefire), NumericDisplayScalarTwoState, an eject + // BitMapInverseWipeScalar (bteejtm), plus a GraphicsViewRecord + // erase tracker (this[0x3F]). + //####################################################################### + class SubsystemCluster : + public GraphicGauge + { + public: + // @004c8140. Binary param order (rate/mode are hardcoded 0xFFFF / + // ModeAlwaysActive in the base GraphicGauge ctor; the panel's two + // bit-plane MODE masks are mfd_mode (MFD-port children) and eng_mode + // (engineering-port children)). + SubsystemCluster( // @004c8140 + ModeMask mfd_mode, L4GaugeRenderer *renderer, int owner_ID, + int mfd_port, int x, int y, Subsystem *subsystem, ModeMask eng_mode, + const char *eng_port_name, const char *tile_image, + const char *label, char **image_names, + const char *identification_string); + ~SubsystemCluster(); // @004c87dc + Logical TestInstance() const; + void BecameActive(); // (re-host addition: forces the first repaint) + void Execute(); // @004c88e4 + protected: + BackgroundBitmap + *background; // @0x90 this[0x24] BackgroundBitmap + GraphicGauge + *generatorVoltageBar, // @0x94 this[0x25] ScalarBarGauge (evolt) + *temperatureBar, // @0x98 this[0x26] HorizTwoPartBar + *coolingLoopA, // @0x9C this[0x27] AnimatedSubsystemLamp + *coolingLoopB, // @0xA0 this[0x28] + *powerSourceA, // @0xA4 this[0x29] AnimatedSourceLamp + *powerSourceB, // @0xA8 this[0x2A] + *vertBarA, // @0xAC this[0x2B] VertTwoPartBar + *vertBarB, // @0xB0 this[0x2C] + *modeLamp, // @0xB4 this[0x2D] OneOfSeveralInt + *stateLamp, // @0xB8 this[0x2E] OneOfSeveralStates + *leakGauge; // @0xBC this[0x2F] LeakGauge + Subsystem *subsystem; // @0xC0 this[0x30] + // POLARITY CORRECTED (PPC-dial fix, kept on re-host): the binary's + // *(subsystem+0x40)==1 means the subsystem FAILED -- it lights the + // destroyed-X TwoStates (the engine TwoState draws bright on NONZERO) + // and selects the dead-panel look (GetDrawState 1 = black fill). The + // old name `operating` had it backwards, inverting every consumer. + Logical failedState; // @0xC4 this[0x31] 1 = subsystem destroyed + int previousDrawState; // @0xC8 this[0x32] (Execute repaint key) + + // internal helpers (non-virtual) + void DrawPanelFrame(int color); // @004c89c4 + void ReleaseChildren(); // @004c8820 + void SetChildrenEnable(int enable); // @004c8a28 + public: + int GetDrawState(); // @004c8990 (operational-state virtual, vtbl+0x48) + }; + + class HeatSinkCluster : // @004c8a6c + public SubsystemCluster + { + public: + HeatSinkCluster( // @004c8a6c + ModeMask mfd_mode, L4GaugeRenderer *, int owner_ID, int mfd_port, + int x, int y, Subsystem *subsystem, 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); + ~HeatSinkCluster(); // @004c8d18 + void Execute(); // @004c8db0 + protected: + Subsystem *failSubsystem; // @0xCC this[0x33] + Logical failFlag; // @0xD0 this[0x34] + Scalar heatLoad; // @0xD4 this[0x35] (connection dst) + Scalar heatLoadScaled; // @0xD8 this[0x36] + GraphicGauge + *heatFailLamp, // @0xDC this[0x37] TwoState + *hsFailLamp, // @0xE0 this[0x38] TwoState (btehfail) + *powerFailLamp, // @0xE4 this[0x39] TwoState (btepfail) + *structFailLamp, // @0xE8 this[0x3A] TwoState (btesfail) + *heatNumeric; // @0xEC this[0x3B] NumericDisplayScalar + }; + + // + // MyomerCluster @004c8df4 (vtable 00519f88) : SubsystemCluster -- the myomer + // (muscle) panel. Adds a SeekVoltageGraph ("EngrGraph", edestryd.pcc) fed + // by a GeneratorVoltageConnection (writes seekValue) + a seek-step + // OneOfSeveralInt (bteseek.pcc). Execute = base. + // + class MyomerCluster : // @004c8df4 + public SubsystemCluster + { + public: + MyomerCluster( // @004c8df4 + ModeMask mfd_mode, L4GaugeRenderer *, int owner_ID, int mfd_port, + int x, int y, Subsystem *subsystem, ModeMask eng_mode, + const char *eng_port_name, const char *tile_image, const char *label, + char **image_names, const char *identification_string); + ~MyomerCluster(); // @004c8f54 + protected: + Scalar seekValue; // @0xCC this[0x33] (connection dst) + GraphicGauge + *seekGraph, // @0xD0 this[0x34] SeekVoltageGraph + *seekStepLamp; // @0xD4 this[0x35] OneOfSeveralInt + }; + + class WeaponCluster : // @004c8fc4 + public SubsystemCluster + { + public: + WeaponCluster( // @004c8fc4 + ModeMask mfd_mode, L4GaugeRenderer *, int owner_ID, int mfd_port, + int x, int y, Subsystem *subsystem, 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(); // @004c91d4 + void BecameActive(); // @004c9258 + void Execute(); // @004c9290 + virtual void DrawWarningLamp(int on); // @004c932c (virtual: BallisticWeaponCluster overrides) + protected: + char *clusterImage; // @0xCC this[0x33] (interned) + GraphicGauge *rechargeArc; // @0xD0 this[0x34] SegmentArc270 + ConfigMapGauge *configMap; // @0xD4 this[0x35] + Scalar percentDone; // @0xD8 this[0x36] (connection dst) + int warningCenterX; // @0xDC this[0x37] + int warningCenterY; // @0xE0 this[0x38] + int warningState; // @0xE4 this[0x39] + }; + + class EnergyWeaponCluster : // @004c93b0 + public WeaponCluster + { + public: + EnergyWeaponCluster( // @004c93b0 + ModeMask mfd_mode, L4GaugeRenderer *, int owner_ID, int mfd_port, + int x, int y, Subsystem *subsystem, 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); + ~EnergyWeaponCluster(); // @004c94dc + protected: + SeekVoltageGraph *seekGraph; // @0xE8 this[0x3A] + GraphicGauge *seekStepLamp; // @0xEC this[0x3B] OneOfSeveralInt + }; + + class BallisticWeaponCluster : // @004c9558 + public WeaponCluster + { + public: + BallisticWeaponCluster( // @004c9558 + ModeMask mfd_mode, L4GaugeRenderer *, int owner_ID, int mfd_port, + int x, int y, Subsystem *subsystem, 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); + ~BallisticWeaponCluster(); // @004c9940 + void BecameActive(); // @004c99cc + void Execute(); // @004c9a38 + void DrawWarningLamp(int on); // @004c9b50 (override: dot + ammo colour swap) + Logical TestInstance() const; // @004c9adc + protected: + Logical jammed; // @0xE8 this[0x3A] + Logical firing; // @0xEC this[0x3B] + Logical reloading; // @0xF0 this[0x3C] + int reloadStartTime; // @0xF4 this[0x3D] + Scalar reloadSeconds; // @0xF8 this[0x3E] + GraphicsViewRecord + *ammoErase; // @0xFC this[0x3F] (bring-up NULL) + GraphicGauge + *ammoNumeric, // @0x100 this[0x40] NumericDisplayScalarTwoState + *destroyedLamp, // @0x104 this[0x41] TwoState + *jamLamp, // @0x108 this[0x42] TwoState (btejam) + *fireLamp, // @0x10C this[0x43] TwoState (btefire) + *ejectWipe, // @0x110 this[0x44] BitMapInverseWipeScalar (NULL) + *ammoCountA, // @0x114 this[0x45] NumericDisplayInteger + *ammoCountB; // @0x118 this[0x46] NumericDisplayInteger + }; + + + //####################################################################### + // vehicleSubSystems -- the config primitive that builds the engineering- + // screen (MFD) subsystem cluster panels. Make @004cbaf0 (methodDescription + // @0x51c080, 8 typeString params = the 8 status strip bitmaps) is a per- + // subsystem factory: it walks the mech roster (skipping slot 0), and for + // each displayable subsystem builds the right cluster (HeatSink/Myomer/ + // Energy/Ballistic) onto one of 12 auxiliary MFD positions. This holder + // just carries the registered MethodDescription + the static Make + // dispatcher (there is no instance). + //####################################################################### + class VehicleSubSystems + { + public: + static MethodDescription methodDescription; + static Logical Make( // @004cbaf0 + int display_port_index, Vector2DOf position, + Entity *entity, GaugeRenderer *gauge_renderer); + }; + +#endif diff --git a/restoration/source410/BT_L4/BTL4GAU3.CPP b/restoration/source410/BT_L4/BTL4GAU3.CPP new file mode 100644 index 00000000..67e8946f --- /dev/null +++ b/restoration/source410/BT_L4/BTL4GAU3.CPP @@ -0,0 +1,1559 @@ +//===========================================================================// +// File: btl4gau3.cpp // +// Project: BattleTech Brick: Gauge Renderer Manager // +// Contents: Cockpit instrument library, part 3 -- multiplayer / tactical HUD // +// displays (see btl4gau3.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, @004c9bd0..@004cbea0) +// and re-hosted onto the authentic 1995 engine (MUNGA GAUGE/GAUGREND + +// MUNGA_L4 L4GAUGE). This TU links between btl4gau2.obj and btl4grnd.obj +// (BTL4.MAK order). Per-method binary addresses cited at each definition. +// +// Recovered string evidence: +// "SectorDisplay: Missing image " 0x51bc90 "Players" 0x51bcba/0x51bd97 +// "PlayerStatus: Make player number " 0x51bcd9 +// "CreateMutantPixelmap8: couldn't ..." 0x51bd0d/0x51bd40/0x51bd6d +// "PilotList" 0x51bb4a "PlayerStatus" 0x51bb54 "MessageBoard" 0x51bb61 +// "adpal.pcc" 0x51bd9f "adpal2.pcc" 0x51bda9 +// "PlayerStatusMappingGroup" 0x51bdb4 +// +// DATABINDING RULE (the gauge scoring wave): every player read below goes +// through the reconstructed Player/BTPlayer NAMED members (PLAYER.HPP / +// BTPLAYER.HPP) -- currentScore, GetDeathCount(), GetPlayerVehicle(), +// playerBitmapIndex, killCount -- never the 1995 binary byte offsets +// (+0x1C4/+0x1C8/+0x1CC/+0x1E0/+0x1FC), which do not match our compiled +// object layout. Feeds with no reconstructed source stay as clearly-marked +// INERT DEFAULTS (the widget draws, the value is static). +// + +#include +#pragma hdrstop + +#if !defined(BTL4GAU3_HPP) +# include +#endif + +#if !defined(APP_HPP) +# include +#endif + +#if !defined(BTPLAYER_HPP) +# include +#endif + +#if !defined(NTTMGR_HPP) +# include +#endif + +// +//############################################################################# +// File-private player-roster resolution +//############################################################################# +// +// The binary resolved pilots through the application's "Players" dictionary +// node (FindByName @00403ad0) and, for the Comm roster, through the viewpoint +// mech's ControlsMapper pilot array. Our reconstructed MechControlsMapper +// (MECHMPPR.HPP) does not carry the pilot roster, so the 1995-native +// equivalent is the EntityManager "Players" entity group: every non-camera +// player joins it in Player::Player (MUNGA PLAYER.CPP), and the engine's own +// Player::CalcRanking walks it with exactly this iterator idiom. Group order +// is creation order == the mission's player join order. +// +static Player * + ResolveRosterPlayer(int slot) +{ + if (application == NULL) + { + return NULL; + } + + EntityGroup + *players = application->GetEntityManager()->FindGroup("Players"); + if (players == NULL) + { + return NULL; + } + + ChainIteratorOf + iterator(players->groupMembers); + Player + *roster_player; + int + index = 0; + + while ((roster_player = (Player *) iterator.ReadAndNext()) != NULL) + { + if (index == slot) + { + return roster_player; + } + ++index; + } + return NULL; +} + +// +//############################################################################# +// KILLS access (the gauge scoring wave): the KILLS column must read the SAME +// compiled member the score handlers increment -- BTPlayer::killCount +// (BTPLAYER.CPP ScoreMessageHandler / VehicleDead path). The reconstructed +// BTPLAYER.HPP keeps killCount protected with no accessor yet, so this thin +// derived reader stands in for a GetKillCount() accessor: NAMED-member access +// only, no offsets, no data members, never constructed. Retire it the day +// BTPLAYER.HPP grows the accessor. +//############################################################################# +// +class BTPlayerKillReader: + public BTPlayer +{ +public: + static int + KillsOf(Player *pilot) + { + return ((BTPlayerKillReader *) pilot)->killCount; + } +private: + BTPlayerKillReader(); // reader cast only -- never constructed +}; + +// +// SELECT-TARGET highlight -- INERT DEFAULT. The binary tinted the roster row +// of the local pilot's current target (local player -> objectiveMech -> its +// player). BTPlayer::objectiveMech is protected with no accessor in the +// reconstruction, so the row tint stays off (rows draw untinted; the KILLS / +// DEATHS numerics are unaffected). +// +static Logical + RosterPlayerIsSelected(Player * /*pilot*/, Player * /*local_player*/) +{ + return False; +} + +// +// Player-name-bitmap cache -- INERT DEFAULT (the donor's own bring-up stub, +// kept per the worksheet). The binary looked prebuilt name rasters up in the +// application's name-bitmap cache by the player's name id (@0x1E0 == +// Player::playerBitmapIndex). Returning NULL routes every caller to the +// binary's own cache-miss branch (the tinted name box), never an AV. +// +static BitMap * + LookupPlayerNameBitmap(int /*player_bitmap_index*/) +{ + return NULL; +} + + +//############################################################################# +//############################################################################# +// PlayerStatusMappingGroup @004c9bd0 +//############################################################################# +//############################################################################# + +// +// The 28 named damage zones (PTR_s_dz_door_0051a240); each gets one +// ColorMapperArmor tinting that zone of the mech-outline schematic. +// +static const char *const + kPlayerStatusZone[playerStatusZoneCount] = + { + "dz_door","dz_dtorso","dz_hip","dz_larm","dz_ldleg","dz_lfoot","dz_lgun", + "dz_ltorso","dz_luleg","dz_missle","dz_rarm","dz_rdleg","dz_rfoot","dz_rgun", + "dz_rtorso","dz_ruleg","dz_searchlight","dz_utorso","dz_reardtorso", + "dz_rearltorso","dz_rearrtorso","dz_rearutorso","dz_lmissle","dz_rmissle", + "unused1","unused2","unused3","unused4" + }; + +// +//############################################################################# +// @004c9bd0 -- build the 28 ColorMapperArmor zone gauges. For each name in +// the damage-zone table resolve its index on the mech (GetDamageZoneIndex, +// binary FUN_0042076c; -1 => inert) and construct a ColorMapperArmor wired to +// that zone, incrementing the base colour index each iteration. +//############################################################################# +// +PlayerStatusMappingGroup::PlayerStatusMappingGroup( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer, + int graphics_port_number, + Entity *mech, + int base_color_index, + const char *palette_a, + const char *palette_b, + const char * /*identification_string*/ +) +{ + Check_Pointer(this); + + int + i; + + for (i = 0; i < playerStatusZoneCount; ++i) + { + // + // (BC4.52: CString temporaries are illegal in a ternary -- hoist.) + // + int + zone_index = -1; + if (mech != NULL) + { + zone_index = mech->GetDamageZoneIndex(kPlayerStatusZone[i]); + } + + zone[i] = new ColorMapperArmor( + rate, + mode_mask, + renderer, + graphics_port_number, + base_color_index + i, + mech, + palette_a, + palette_b, + zone_index, + "ColorMapperArmor" + ); + Register_Object(zone[i]); + } + Check_Fpu(); +} + +// +//############################################################################# +// @004c9ca8 -- destroy: delete all 28. +//############################################################################# +// +PlayerStatusMappingGroup::~PlayerStatusMappingGroup() +{ + Check(this); + + int + i; + + for (i = 0; i < playerStatusZoneCount; ++i) + { + if (zone[i] != NULL) + { + Unregister_Object(zone[i]); + delete zone[i]; + zone[i] = NULL; + } + } + Check_Fpu(); +} + +Logical + PlayerStatusMappingGroup::TestInstance() const +{ + return True; +} + +// +//############################################################################# +// @004c9cf4 -- Execute: run each element's Execute. +//############################################################################# +// +void + PlayerStatusMappingGroup::Execute() +{ + Check(this); + + int + i; + + for (i = 0; i < playerStatusZoneCount; ++i) + { + if (zone[i] != NULL) + { + zone[i]->Execute(); + } + } + Check_Fpu(); +} + +// +//############################################################################# +// @004c9d18 -- SetColor: push the state onto all 28. +//############################################################################# +// +void + PlayerStatusMappingGroup::SetColor(int state) +{ + Check(this); + + int + i; + + for (i = 0; i < playerStatusZoneCount; ++i) + { + if (zone[i] != NULL) + { + zone[i]->SetColor(state); + } + } + Check_Fpu(); +} + + +//############################################################################# +//############################################################################# +// SectorDisplay @004c9d44 Make / @004c9e10 ctor +//############################################################################# +//############################################################################# + +// +// @0x51a2b0 -- registered so the interpreter builds the CFG line +// sectorDisplay( K, ModeAlwaysActive, helv15.pcc, 0, 3 ) (L4GAUGE.CFG:5146; +// the port index + offset=(125,579) arrive via Make's args, not config +// tokens). Keyword + parameter list VERBATIM (parse-critical). +// +MethodDescription + SectorDisplay::methodDescription = + { + "sectorDisplay", + SectorDisplay::Make, + { + { ParameterDescription::typeRate, NULL }, // p[0] rate [row0 type=1] + { ParameterDescription::typeModeMask, NULL }, // p[1] modeMask [row1 type=2] + { ParameterDescription::typeString, NULL }, // p[2] image [row2 type=9] + { ParameterDescription::typeColor, NULL }, // p[3] color [row3 type=4] + { ParameterDescription::typeColor, NULL }, // p[4] okColor [row4 type=4] + PARAMETER_DESCRIPTION_END + } + }; + +// +//############################################################################# +// @004c9d44 -- Make. Construct, then check the grid image and warn +// "SectorDisplay: Missing image " if absent (binary-faithful: the base +// ctor self-registers the gauge regardless of the return value). +//############################################################################# +// +Logical + SectorDisplay::Make( + int display_port_index, + Vector2DOf position, + Entity * /*entity -- unused*/, + GaugeRenderer *gauge_renderer + ) +{ + ParameterDescription + *parameterList = methodDescription.parameterList; + + Check(gauge_renderer); + + SectorDisplay + *gauge = new SectorDisplay( + parameterList[0].data.rate, + parameterList[1].data.modeMask, + (L4GaugeRenderer *) gauge_renderer, + display_port_index, // graphics_port_number (FIX: was 0) + position.x, position.y, // SetOrigin (FIX: was 0,0) + parameterList[2].data.string, // grid/font image (helv15.pcc) + parameterList[3].data.color, // numericColor + parameterList[4].data.color, // gridColor / numeric okColor + "SectorDisplay" + ); + Register_Object(gauge); + + //-------------------------------------------------------- + // Make sure the grid image made it into the warehouse + //-------------------------------------------------------- + L4Warehouse + *warehouse = (L4Warehouse *) gauge_renderer->warehousePointer; + Check(warehouse); + + if (warehouse->bitMapBin.Get(parameterList[2].data.string) == NULL) + { + DEBUG_STREAM << "SectorDisplay: Missing image " + << parameterList[2].data.string << "\n" << flush; + Check_Fpu(); + return False; + } + warehouse->bitMapBin.Release(parameterList[2].data.string); + Check_Fpu(); + return True; +} + +// +//############################################################################# +// @004c9e10 -- ctor (vtable 0051beec). GraphicGauge base (owner 0); copy the +// grid-image name; from the bitmap compute cellWidth=imageW/14, +// gridLeft=cellWidth*12, gridRight=cellWidth*13-1, gridHeight=imageH; +// SetOrigin; build two 3-digit NumericDisplays (@0 and @cellWidth*4). +//############################################################################# +// +SectorDisplay::SectorDisplay( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer, + int graphics_port_number, + int x, int y, + const char *image, + int color, + int ok_color, + const char *identification_string +): + GraphicGauge( + rate, + mode_mask, + renderer, + 0, // owner folded to 0 (binary base call) + graphics_port_number, + identification_string + ) +{ + Check_Pointer(this); + + //------------------------------------------------------------ + // Execute reads gridImage per frame, so it must outlive the + // (transient) config parameterList string -- keep a durable + // copy (native nameCopy, cf. NumericDisplay ctor). + //------------------------------------------------------------ + gridImage = nameCopy(image); + + numericColor = color; // @0xA8 + gridColor = ok_color; // @0xAC + subject = NULL; // @0x90 + sectorBaseA = 500; // @0xB0 (0x1f4) + sectorBaseB = 500; // @0xB4 + dirty = 1; // @0xB8 (binary leaves uninit; BecameActive sets it) + + //------------------------------------------------------------ + // Size the grid cells from the image (Get / measure / Release, + // the NumericDisplay ctor idiom -- the art stays warehoused). + //------------------------------------------------------------ + L4Warehouse + *warehouse = (L4Warehouse *) renderer->warehousePointer; + Check(warehouse); + + BitMap + *grid = warehouse->bitMapBin.Get(gridImage); + if (grid == NULL) + { + cellWidth = gridLeft = gridRight = gridHeight = 0; + } + else + { + cellWidth = grid->Data.Size.x / 14; + gridLeft = cellWidth * 12; + gridRight = cellWidth * 12 + cellWidth - 1; + gridHeight = grid->Data.Size.y; + warehouse->bitMapBin.Release(gridImage); + } + + localView.SetOrigin(x, y); + + //------------------------------------------------------------ + // NumericDisplay(warehouse, x, y, image, digits=3, + // unsignedFormat, color, okColor) + //------------------------------------------------------------ + numericA = new NumericDisplay( + warehouse, 0, 0, image, 3, + NumericDisplay::unsignedFormat, color, ok_color); + Register_Object(numericA); + + numericB = new NumericDisplay( + warehouse, cellWidth * 4, 0, image, 3, + NumericDisplay::unsignedFormat, color, ok_color); + Register_Object(numericB); + + Check_Fpu(); +} + +// +//############################################################################# +// @004c9f94 -- dtor. Free the copied name + both numerics; the base +// GraphicGauge chain runs implicitly. +//############################################################################# +// +SectorDisplay::~SectorDisplay() +{ + Check(this); + + Unregister_Pointer(gridImage); + delete[] gridImage; + gridImage = NULL; + + Unregister_Object(numericA); + delete numericA; + numericA = NULL; + + Unregister_Object(numericB); + delete numericB; + numericB = NULL; + + Check_Fpu(); +} + +// @004ca020 -- TestInstance (out-of-line forward to the base). +Logical + SectorDisplay::TestInstance() const +{ + return GraphicGauge::TestInstance(); +} + +// +//############################################################################# +// @004ca068 -- LinkToEntity (slot 9): cache the viewpoint-bind broadcast's +// entity as the subject (APP.CPP MakeViewpointEntity -> GAUGREND.CPP slot-9 +// forward arms this on the authentic engine). +//############################################################################# +// +void + SectorDisplay::LinkToEntity(Entity *entity) +{ + Check(this); + subject = entity; +} + +// +//############################################################################# +// @004ca038 -- BecameActive (slot 3): mark dirty + reset both numerics. +// NON-inactivating (does NOT chain the base). +//############################################################################# +// +void + SectorDisplay::BecameActive() +{ + Check(this); + dirty = 1; + numericB->ForceUpdate(); // B then A -- binary order + numericA->ForceUpdate(); +} + +// +//############################################################################# +// @004ca07c -- Execute. Draw the two sector numerics from the linked mech's +// world X/Z (Round @FUN_004dcd94 -- the native SCALAR.HPP Round), then on the +// first active frame blit the grid-cell background. The binary gates purely +// on subject != 0 (@0x4ca08e). +//############################################################################# +// +void + SectorDisplay::Execute() +{ + Check(this); + + if (subject == NULL) + { + return; + } + + const Point3D + &mech_position = subject->localOrigin.linearPosition; + + int + sector_a = Round(-mech_position.z * 0.01f) + sectorBaseA, // 100 m sectors + sector_b = Round( mech_position.x * 0.01f) + sectorBaseB; + + numericA->Draw(&localView, (Scalar) sector_a); + numericB->Draw(&localView, (Scalar) sector_b); + + if (dirty) + { + dirty = 0; + + L4Warehouse + *warehouse = (L4Warehouse *) renderer->warehousePointer; + BitMap + *grid = warehouse->bitMapBin.Get(gridImage); + if (grid != NULL) + { + localView.MoveToAbsolute(cellWidth * 3, 0); + localView.SetColor(gridColor); + localView.DrawBitMap(0, grid, gridLeft, 0, gridRight, gridHeight); + warehouse->bitMapBin.Release(gridImage); + } + } + Check_Fpu(); +} + + +//############################################################################# +//############################################################################# +// TeamStatusDisplay @004ca208 +//############################################################################# +//############################################################################# +// +// DEFERRED -- prose-only reconstruction (no recovered bodies, no +// methodDescription, no config keyword registered; nothing constructs it). +// Documented shape, banked for the implementing pass: +// +// @004ca208 -- ctor (vtable 0051bea8). GraphicGauge base. rowSpacing = +// param_10; originY = param y - 0x20; flags = param_12. Reads the font +// bitmap width to size the highlight (cellW = w/14; highlightWidth = +// cellW*8 + 0x86, plus an extra column when flags&4). Builds 8 rows, each +// with a player-number NumericDisplay and -- when flags&4 -- a kill +// NumericDisplay; rows step down by rowSpacing. +// @004ca39c -- dtor: delete the 8 numeric pairs, base dtor. +// @004ca424 -- SetEnable: this[0x24] = enable. +// @004ca438 -- BecameActive: selectedRow = 999; per-row prev = -1, +// prevScore = -0.001f (0xba83126f); per-row prevRank = -1. +// @004ca478 -- Execute. Resolve the local player's index/team; read the +// "Players" roster, slot each player into [0..7] by player index (skipping +// spectators); on an occupant change blit the player's name bitmap (or +// clear), tinting friendly/self; update each kill numeric (binary +0x1c8 == +// the reconstruction's BTPlayer::killCount); when flags&4, also track each +// player's rank; finally redraw the selection underline on the local +// player's row. +// +// (When built, the kill / rank / spectator feeds bind through the same named +// Player/BTPlayer members this TU already uses -- see the DATABINDING RULE +// note at the top of the file.) +// + + +//############################################################################# +//############################################################################# +// PilotList @004ca90c Make / @004ca958 ctor +//############################################################################# +//############################################################################# + +// +// The Comm KILLS/DEATHS roster. Keyword + parameter list VERBATIM +// (parse-critical): pilotList( C, ModeAlwaysActive, bigfont.pcc ). +// +MethodDescription + PilotList::methodDescription = + { + "pilotList", + PilotList::Make, + { + { ParameterDescription::typeRate, NULL }, // rate (C) + { ParameterDescription::typeModeMask, NULL }, // mode (ModeAlwaysActive) + { ParameterDescription::typeString, NULL }, // font (bigfont.pcc) + PARAMETER_DESCRIPTION_END + } + }; + +// +//############################################################################# +// @004ca90c -- Make. position UNUSED (each entry carries absolute coords +// from the DAT_0051af88 layout table). Always returns True (binary). +//############################################################################# +// +Logical + PilotList::Make( + int display_port_index, + Vector2DOf /*position -- unused*/, + Entity * /*entity -- unused*/, + GaugeRenderer *gauge_renderer + ) +{ + ParameterDescription + *parameterList = methodDescription.parameterList; + + Check(gauge_renderer); + + PilotList + *gauge = new PilotList( + parameterList[0].data.rate, + parameterList[1].data.modeMask, + (L4GaugeRenderer *) gauge_renderer, + display_port_index, // graphics_port_number + parameterList[2].data.string, // font (bigfont.pcc) + "PilotList" + ); + Register_Object(gauge); + + Check_Fpu(); + return True; +} + +// +//############################################################################# +// @004ca958 -- ctor (vtable 0051be64). GraphicGauge base; build 3 +// NumericDisplays per entry from the PE-parsed 8x(x, y, layoutMode) table +// (DAT_0051af88 -- exact bytes from BTL4OPT.EXE). +//############################################################################# +// +PilotList::PilotList( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer, + int graphics_port_number, + const char *font_image, + const char *identification_string +): + GraphicGauge( + rate, + mode_mask, + renderer, + 0, // owner folded to 0 (binary base call) + graphics_port_number, + identification_string + ) +{ + Check_Pointer(this); + + currentSlot = 0; + built = False; + + L4Warehouse + *warehouse = (L4Warehouse *) renderer->warehousePointer; + Check(warehouse); + + //------------------------------------------------------------ + // DAT_0051af88 -- 8 x (x, y, layoutMode) + //------------------------------------------------------------ + static const int + layout[8][3] = + { + {180,225,0},{17,443,1},{177,443,1},{337,443,1}, + {497,443,1},{17,7,2},{177,7,2},{337,7,2} + }; + + int + i; + + for (i = 0; i < 8; ++i) + { + int + x = layout[i][0], + y = layout[i][1], + layout_mode = layout[i][2]; + int + name_x, name_y, mech_x, mech_y; + + if (layout_mode == 1) + { + name_x = x - 8; name_y = y - 0x50; + mech_x = x + 0x48; mech_y = y - 0x50; + } + else if (layout_mode == 2) + { + name_x = x - 8; name_y = y + 0x44; + mech_x = x + 0x48; mech_y = y + 0x44; + } + else // layout_mode == 0 + { + name_x = x + 0x95; name_y = y - 0x0d; + mech_x = x + 0xe8; mech_y = y - 0x0d; + } + + entry[i].x = x; + entry[i].y = y; + entry[i].resolvedMech = 0; + entry[i].selected = 0; + + entry[i].nameDisplay = new NumericDisplay( // KILLS + warehouse, name_x, name_y, font_image, 2, + NumericDisplay::signedBlankedZerosFormat, 0, 0xff); + Register_Object(entry[i].nameDisplay); + + entry[i].mechDisplay = new NumericDisplay( // DEATHS + warehouse, mech_x, mech_y, font_image, 2, + NumericDisplay::signedBlankedZerosFormat, 0, 0xff); + Register_Object(entry[i].mechDisplay); + + entry[i].scoreDisplay = new NumericDisplay( // erase-only + warehouse, x, y, font_image, 1, + NumericDisplay::signedBlankedZerosFormat, 0, 0xff); + Register_Object(entry[i].scoreDisplay); + } + Check_Fpu(); +} + +// +//############################################################################# +// @004cab10 -- dtor. Free the 3 numerics per entry; base chain implicit. +//############################################################################# +// +PilotList::~PilotList() +{ + Check(this); + + int + i; + + for (i = 0; i < 8; ++i) + { + Unregister_Object(entry[i].nameDisplay); + delete entry[i].nameDisplay; + entry[i].nameDisplay = NULL; + + Unregister_Object(entry[i].mechDisplay); + delete entry[i].mechDisplay; + entry[i].mechDisplay = NULL; + + Unregister_Object(entry[i].scoreDisplay); + delete entry[i].scoreDisplay; + entry[i].scoreDisplay = NULL; + } + Check_Fpu(); +} + +// @004cab8c -- TestInstance (out-of-line forward to the base). +Logical + PilotList::TestInstance() const +{ + return GraphicGauge::TestInstance(); +} + +// +//############################################################################# +// @004caba4 -- BecameActive. Invalidate every entry so the first cycle +// repaints; currentSlot = 9 forces the wrap-reset on the first Execute. +//############################################################################# +// +void + PilotList::BecameActive() +{ + Check(this); + + int + i; + + for (i = 0; i < 8; ++i) + { + entry[i].resolvedMech = -1; + } + currentSlot = 9; +} + +// +//############################################################################# +// @004cad70 -- DrawMechIcon. Blit the pilot's name bitmap (or, on a cache +// miss -- always, while the name-bitmap cache is the inert default -- a +// tinted 0x80 x 0x20 box), tinted by the selected flag. The binary's name +// id read (@0x1E0) is the reconstruction's Player::playerBitmapIndex. +//############################################################################# +// +void + PilotList::DrawMechIcon(Player *pilot, int selected) +{ + Check(this); + Check(pilot); + + BitMap + *name_bitmap = LookupPlayerNameBitmap(pilot->playerBitmapIndex); + int + box_color = selected ? 0xff : 0, + blit_color = selected ? 0 : 0xff; + + if (name_bitmap == NULL) + { + localView.SetColor(box_color); + localView.DrawFilledRectangleToRelative(0x80, 0x20); + } + else + { + localView.SetColor(blit_color); + localView.DrawBitMapOpaque(box_color, 0, name_bitmap, 0, 0, + name_bitmap->Data.Size.x - 1, name_bitmap->Data.Size.y - 1); + } +} + +// +//############################################################################# +// @004cabd0 -- Execute (one roster slot per frame). Look up the current slot +// in the shared player roster; erase an empty slot, or draw the pilot's name +// icon + KILLS + DEATHS for an occupied one. +// +// The KILLS/DEATHS numerics read the COMPILED named members the score +// handlers write (BTPlayer::killCount / Player::deathCount) -- the binary's +// raw pilot+0x27c / +0x200 offsets read a stale/garbage slot on our compiled +// layout (the silent-zero scoreboard bug on record). +//############################################################################# +// +void + PilotList::Execute() +{ + Check(this); + + // + // The local pilot gates the whole body (binary: roster slot 0 of the + // viewpoint mech's mapper roster; native = the mission player). + // + Player + *local_player = application->GetMissionPlayer(); + if (local_player == NULL) + { + return; + } + + if (currentSlot > 7) // wrap the round-robin + { + currentSlot = 0; + built = False; + } + + Entry + &e = entry[currentSlot]; + + Player + *pilot; + if (built == False) + { + pilot = ResolveRosterPlayer(currentSlot); + if (pilot == NULL) + { + built = True; // past the first empty slot -> rest empty this cycle + } + } + else + { + pilot = NULL; + } + + if (pilot == NULL) + { + if (e.resolvedMech != 0) // (-1 after BecameActive) -> erase + { + localView.MoveToAbsolute(e.x, e.y); + localView.SetColor(0); + localView.DrawFilledRectangleToRelative(0x80, 0x20); + e.nameDisplay->Erase(&localView); + e.mechDisplay->Erase(&localView); + e.scoreDisplay->Erase(&localView); + } + } + else + { + int + selected = RosterPlayerIsSelected(pilot, local_player); + + if ((int) pilot != e.resolvedMech || e.selected != selected) + { + localView.MoveToAbsolute(e.x, e.y); + DrawMechIcon(pilot, selected); + e.selected = selected; + // (byte-faithful: the binary does NOT latch e.resolvedMech here, + // so the icon redraws each occupied frame.) + } + + e.nameDisplay->Draw(&localView, // KILLS (killCount) + (Scalar) BTPlayerKillReader::KillsOf(pilot)); + e.mechDisplay->Draw(&localView, // DEATHS (deathCount) + (Scalar) pilot->GetDeathCount()); + } + + ++currentSlot; + Check_Fpu(); +} + + +//############################################################################# +//############################################################################# +// PlayerStatus @004cae90 Make / @004cb1a8 ctor +//############################################################################# +//############################################################################# + +// +// @004cafac -- per-player pixmap recolour. INERT DEFAULT (the donor's own +// bring-up stub, kept per the worksheet). The faithful body resolves the +// mech's type-0x20 pixmap sub-resource from the application resource file +// (warns "CreateMutantPixelmap8: couldn't find ..." on a miss), opens it as a +// memory stream, reads the embedded image name (ReadStreamString @004caf50, +// 0x50 chars -- deferred with this stub), looks the source PixMap8 up in the +// renderer warehouse, and copies it biasing every palette index >= 0x20 by +// (palette_base - 0x20) so each player's mech outline draws in its own colour +// ramp. Deferred: the memory-stream read API + pixel copy are unmapped. +// With this stub recoloredMech stays NULL -> Execute skips the outline blit; +// the score, name box and alive/dead status box still render. +// +static Pixmap * + CreateMutantPixelmap8( + Entity * /*mech*/, + L4Warehouse * /*warehouse*/, + int /*palette_base*/, + Pixmap *destination + ) +{ + return destination; +} + +// +// Keyword + parameter list VERBATIM (parse-critical). CFG (L4GAUGE.CFG:29): +// PlayerStatus( D, ModeAlwaysActive, 1, helv15.pcc, 0, 11, 3, 1 ) +// +MethodDescription + PlayerStatus::methodDescription = + { + "PlayerStatus", + PlayerStatus::Make, + { + { ParameterDescription::typeRate, NULL }, // p[0] rate + { ParameterDescription::typeModeMask, NULL }, // p[1] mode mask + { ParameterDescription::typeInteger, NULL }, // p[2] player number 1..8 + { ParameterDescription::typeString, NULL }, // p[3] font image + { ParameterDescription::typeColor, NULL }, // p[4] color + { ParameterDescription::typeColor, NULL }, // p[5] okColor + { ParameterDescription::typeColor, NULL }, // p[6] aliveColor + { ParameterDescription::typeColor, NULL }, // p[7] deadColor + PARAMETER_DESCRIPTION_END + } + }; + +// +//############################################################################# +// @004cae90 -- Make. The player number must be 1..8 or "PlayerStatus: Make +// player number " is warned (string 0x51bcd9). Reads the parameterList +// like every registered widget (the binary read raw DAT_ pools) + the +// port/x/y bug fixed on record (the binary-side Make passed entity/renderer +// as x/y and dropped the port). +//############################################################################# +// +Logical + PlayerStatus::Make( + int display_port_index, + Vector2DOf position, + Entity * /*entity -- unused by PlayerStatus*/, + GaugeRenderer *gauge_renderer + ) +{ + ParameterDescription + *parameterList = methodDescription.parameterList; + + Check(gauge_renderer); + + int + player_number = parameterList[2].data.integer; + if (player_number < 1 || player_number > 8) + { + DEBUG_STREAM << "PlayerStatus: Make player number " + << player_number << " out of range\n" << flush; + return False; + } + + PlayerStatus + *gauge = new PlayerStatus( + parameterList[0].data.rate, + parameterList[1].data.modeMask, + (L4GaugeRenderer *) gauge_renderer, + display_port_index, // graphics_port_number <-- FIX (was dropped) + position.x, position.y, // x, y <-- FIX (was entity/renderer) + player_number, + parameterList[3].data.string, + parameterList[4].data.color, + parameterList[5].data.color, + parameterList[6].data.color, + parameterList[7].data.color, + "PlayerStatus" + ); + Register_Object(gauge); + + Check_Fpu(); + return True; +} + +// +//############################################################################# +// @004cb1a8 -- ctor (vtable 0051be20). GraphicGauge base (owner folded to +// 0); stash the four state colours + port; set the port origin; build the +// score NumericDisplay (5 digits @ (0x72, 0xD8)). +//############################################################################# +// +PlayerStatus::PlayerStatus( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer, + int graphics_port_number, + int x, int y, + int player_number, + const char *font_image, + int color_in, + int ok_color, + int alive_color, + int dead_color, + const char *identification_string +): + GraphicGauge( + rate, + mode_mask, + renderer, + 0, // owner folded to 0 (binary base call) + graphics_port_number, + identification_string + ) +{ + Check_Pointer(this); + + color = color_in; // @0xA4 + okColor = ok_color; // @0xA8 + aliveColor = alive_color; // @0xAC + deadColor = dead_color; // @0xB0 + port = graphics_port_number; // @0x90 + + localView.SetOrigin(x, y); + + playerIndex = player_number - 1; // @0x94 + player = NULL; // @0x98 + nameImage = NULL; // @0x9C + recoloredMech = NULL; // @0xA0 + mappingGroup = NULL; // @0xB4 + + L4Warehouse + *warehouse = (L4Warehouse *) renderer->warehousePointer; + Check(warehouse); + + scoreDisplay = new NumericDisplay( // @0xB8 + warehouse, 0x72, 0xD8, font_image, 5, + NumericDisplay::signedBlankedZerosFormat, color, okColor); + Register_Object(scoreDisplay); + + // dirty/previousStatus/previousMappingState set by BecameActive + // (byte-faithful). + Check_Fpu(); +} + +// +//############################################################################# +// @004cb28c -- dtor. Free the score numeric + the recoloured mech pixmap; +// base chain implicit. (nameImage is cache-owned; mappingGroup deleted here +// as a correctness fix on record -- the binary leaks it.) +//############################################################################# +// +PlayerStatus::~PlayerStatus() +{ + Check(this); + + player = NULL; + + Unregister_Object(scoreDisplay); + delete scoreDisplay; + scoreDisplay = NULL; + + if (recoloredMech != NULL) + { + delete recoloredMech; + recoloredMech = NULL; + } + if (mappingGroup != NULL) + { + delete mappingGroup; // (the binary leaks this) + mappingGroup = NULL; + } + Check_Fpu(); +} + +// @004cb2f8 -- TestInstance (out-of-line forward to the base). +Logical + PlayerStatus::TestInstance() const +{ + return GraphicGauge::TestInstance(); +} + +// +//############################################################################# +// @004cb310 -- BecameActive: reset the score readout, run the mapping group +// once, and force a full redraw next Execute. +//############################################################################# +// +void + PlayerStatus::BecameActive() +{ + Check(this); + + scoreDisplay->ForceUpdate(); + if (mappingGroup != NULL) + { + mappingGroup->Execute(); // @004c9cf4 + } + dirty = 1; + previousStatus = -1; + previousMappingState = -1; +} + +// +//############################################################################# +// @004cb358 -- Execute. Resolve player N from the shared roster (the binary +// indexed the application's "Players" node; native = the "Players" entity +// group, creation order); on a change rebuild the recoloured mech pixmap + +// the 28-zone mapping group; each frame draw the score + mech outline + name +// box + alive/dead status box. +//############################################################################# +// +void + PlayerStatus::Execute() +{ + Check(this); + + //--- PART 1: resolve / change-detect the player ------------------------ + Player + *resolved = ResolveRosterPlayer(playerIndex); + + if (resolved != player) + { + player = resolved; + if (player != NULL) + { + Entity + *mech = player->GetPlayerVehicle(); // named accessor (was raw +0x1FC) + if (mech == NULL) + { + player = NULL; // no vehicle yet -> retry next frame + } + else + { + L4Warehouse + *warehouse = (L4Warehouse *) renderer->warehousePointer; + int + palette_base = playerIndex * 0x1C + 0x10; + + recoloredMech = CreateMutantPixelmap8( + mech, warehouse, palette_base, recoloredMech); + + if (mappingGroup != NULL) + { + delete mappingGroup; + } + mappingGroup = new PlayerStatusMappingGroup( + rate, modeMask, (L4GaugeRenderer *) renderer, port, + mech, palette_base, + "adpal.pcc", "adpal2.pcc", + "PlayerStatusMappingGroup"); + + // INERT DEFAULT: the application name-bitmap cache is not + // reconstructed -> the filled name-box branch below draws. + nameImage = NULL; + + BecameActive(); // force a full redraw + } + } + } + + //--- PART 2: per-frame draw (only with a bound player) ----------------- + if (player == NULL) + { + return; + } + Entity + *mech = player->GetPlayerVehicle(); + if (mech == NULL) + { + return; + } + + // + // (a) score readout -- the published Player::currentScore (the same + // member the score handlers write; was raw +0x1C8-as-Scalar). + // + scoreDisplay->Draw(&localView, player->currentScore); + + // + // (b) mapping-group disabled-state refresh -- INERT DEFAULT: the binary + // keyed this off the mech's damage-mapping derivation; the recon has + // no bound source in this TU (Mech::IsDisabled needs mech.hpp, which + // collides here), so the group holds the enabled tint. + // + int + disabled = 0; + if (disabled != previousMappingState) + { + previousMappingState = disabled; + if (mappingGroup != NULL) + { + mappingGroup->SetColor(disabled); // @004c9d18 + } + } + + // + // (c) mech outline + name box, dirty-gated + // + if (dirty) + { + dirty = 0; + if (recoloredMech != NULL) + { + localView.MoveToAbsolute(1, 1); + localView.DrawPixelMap8(True, 0, recoloredMech, 0, 0, + recoloredMech->Data.Size.x - 1, + recoloredMech->Data.Size.y - 1); + } + localView.MoveToAbsolute(1, 0xCF); + if (nameImage == NULL) // no name bitmap -> filled box in `color` + { + localView.SetColor(color); + localView.DrawFilledRectangleToRelative(0x80, 0x20); + } + else + { + localView.SetColor(0xFF); + localView.DrawBitMapOpaque(color, 0, nameImage, 0, 0, + nameImage->Data.Size.x - 1, nameImage->Data.Size.y - 1); + } + } + + // + // (d) alive/dead status box, state-change gated. INERT DEFAULT: the + // binary keyed this off the player record's status word (@0x1C4); no + // alive/dead member is published on the reconstructed Player/BTPlayer + // yet, so the box paints once in aliveColor and holds. + // + int + state = 0; + if (state != previousStatus) + { + previousStatus = state; + localView.SetColor(state == 0 ? aliveColor : deadColor); + localView.MoveToAbsolute(0, 0); + localView.DrawFilledRectangleToAbsolute(0x9F, 0xEF); + } + Check_Fpu(); +} + + +//############################################################################# +//############################################################################# +// MessageBoard @004cb678 Make / @004cb704 ctor +//############################################################################# +//############################################################################# + +// +// Keyword + parameter list VERBATIM (parse-critical). CFG shape +// (L4GAUGE.CFG:4913, port=sec, offset=(113,607)): +// messageBoard( E, ModeAlwaysActive, btsmsgs.pcx, 0 ) +// +MethodDescription + MessageBoard::methodDescription = + { + "messageBoard", + MessageBoard::Make, + { + { ParameterDescription::typeRate, NULL }, // p[0] rate (E) + { ParameterDescription::typeModeMask, NULL }, // p[1] mode (ModeAlwaysActive) + { ParameterDescription::typeString, NULL }, // p[2] strip bitmap (btsmsgs.pcx) + { ParameterDescription::typeColor, NULL }, // p[3] draw/erase color (0) + PARAMETER_DESCRIPTION_END + } + }; + +// +//############################################################################# +// @004cb678 -- Make. Construct + presence-check the strip bitmap (the binary +// returns whether it exists). The `entity` param is ignored by the binary. +//############################################################################# +// +Logical + MessageBoard::Make( + int display_port_index, + Vector2DOf position, + Entity * /*entity -- ignored by the binary*/, + GaugeRenderer *gauge_renderer + ) +{ + ParameterDescription + *parameterList = methodDescription.parameterList; + + Check(gauge_renderer); + + MessageBoard + *gauge = new MessageBoard( + parameterList[0].data.rate, + parameterList[1].data.modeMask, + (L4GaugeRenderer *) gauge_renderer, + display_port_index, // graphics_port_number + position.x, position.y, // offset=(113,607) -> SetOrigin + parameterList[2].data.string, // strip image (btsmsgs.pcx) + parameterList[3].data.color, // color (0) + "MessageBoard" + ); + Register_Object(gauge); + + L4Warehouse + *warehouse = (L4Warehouse *) gauge_renderer->warehousePointer; + Check(warehouse); + + if (warehouse->bitMapBin.Get(parameterList[2].data.string) == NULL) + { + Check_Fpu(); + return False; + } + warehouse->bitMapBin.Release(parameterList[2].data.string); + Check_Fpu(); + return True; +} + +// +//############################################################################# +// @004cb704 -- ctor (vtable 0051bddc). GraphicGauge base (owner 0); copy + +// hold the strip bitmap; SetOrigin; color = param. trackedMech starts NULL. +//############################################################################# +// +MessageBoard::MessageBoard( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer, + int graphics_port_number, + int x, int y, + const char *strip_image, + int color_in, + const char *identification_string +): + GraphicGauge( + rate, + mode_mask, + renderer, + 0, // owner folded to 0 (binary base call) + graphics_port_number, + identification_string + ) +{ + Check_Pointer(this); + + stripImage = nameCopy(strip_image); + + L4Warehouse + *warehouse = (L4Warehouse *) renderer->warehousePointer; + Check(warehouse); + warehouse->bitMapBin.Get(stripImage); // hold a reference for life + + localView.SetOrigin(x, y); + color = color_in; // @0x98 + trackedMech = NULL; // @0x90 + // previousNameId / previousMessageId set by BecameActive (byte-faithful). + Check_Fpu(); +} + +// +//############################################################################# +// @004cb788 -- dtor. Release + free the strip; base chain implicit. +//############################################################################# +// +MessageBoard::~MessageBoard() +{ + Check(this); + + L4Warehouse + *warehouse = (L4Warehouse *) renderer->warehousePointer; + warehouse->bitMapBin.Release(stripImage); + + Unregister_Pointer(stripImage); + delete[] stripImage; + stripImage = NULL; + Check_Fpu(); +} + +// @004cb7e4 -- TestInstance (out-of-line forward to the base). +Logical + MessageBoard::TestInstance() const +{ + return GraphicGauge::TestInstance(); +} + +// +//############################################################################# +// @004cb818 -- SetSource: bind the source mech. (No recovered caller in the +// shipped binary -> the board is authentically dormant; see Execute.) +//############################################################################# +// +void + MessageBoard::SetSource(Entity *mech) +{ + Check(this); + trackedMech = mech; +} + +// +//############################################################################# +// @004cb7fc -- BecameActive: reset the change-sentinels. NON-inactivating. +//############################################################################# +// +void + MessageBoard::BecameActive() +{ + Check(this); + previousNameId = -1; // @0x9C + previousMessageId = -2; // @0xA0 +} + +// +//############################################################################# +// @004cb82c -- Execute. Draw the message strip cell + sender name when they +// change. +// +// FAITHFUL / DEFERRED: the board only draws while a source mech is bound; the +// binder (SetSource) has no recovered caller AND the per-player status feed +// (the pool that fills Player::statusMessagePointer, PLAYER.HPP) is not +// reconstructed, so trackedMech stays NULL and Execute is a safe no-op == the +// authentically empty board. The resolve below is an INERT DEFAULT ("no +// message"); the cell math is byte-verified and goes live the day the feed +// and the SetSource caller are built. +//############################################################################# +// +void + MessageBoard::Execute() +{ + Check(this); + + if (trackedMech == NULL) // binary gate + { + return; + } + + // + // INERT DEFAULT message resolve: the native record is the tracked mech's + // player's statusMessagePointer (messageType -> strip cell id, + // playerInvolved -> sender name bitmap), but its producer pool is + // unbuilt -- report "no message" until it lands. + // + int + messageId = -1; + BitMap + *nameBitmap = NULL; + + //--- message strip cell ------------------------------------------------- + if (messageId != previousMessageId) + { + previousMessageId = messageId; + localView.MoveToAbsolute(0, 0); + if (messageId == -1) + { + localView.SetColor(color); + localView.DrawFilledRectangleToRelative(0x80, 0x20); + } + else + { + int + cellX = (messageId & 3) << 7, // 0x80-wide cells, 4 across + cellY = (messageId >> 2) << 5; // 0x20-tall rows + + localView.SetColor(7); // FUN_004c2f88() == 7 + L4Warehouse + *warehouse = (L4Warehouse *) renderer->warehousePointer; + BitMap + *strip = warehouse->bitMapBin.Get(stripImage); + if (strip != NULL) + { + localView.DrawBitMapOpaque(color, 0, strip, + cellX, cellY, cellX + 0x7f, cellY + 0x1f); + warehouse->bitMapBin.Release(stripImage); + } + } + } + + //--- sender name cell (x=128). The binary tracks the previous name + // ENTITY pointer @0x9C; the reconstruction tracks the resolved BITMAP + // pointer -- the same change-detection granularity (a new sender = a + // different prebuilt raster), no entity re-deref. + int + nameState = (int) nameBitmap; + if (nameState != previousNameId) + { + localView.MoveToAbsolute(0x80, 0); + if (nameBitmap == NULL) + { + localView.SetColor(color); + localView.DrawFilledRectangleToRelative(0x80, 0x20); + } + else + { + localView.SetColor(7); + localView.DrawBitMapOpaque(color, 0, nameBitmap, 0, 0, + nameBitmap->Data.Size.x - 1, nameBitmap->Data.Size.y - 1); + } + previousNameId = nameState; + } + Check_Fpu(); +} + +// === btl4grnd.cpp begins at @004cbea0 (BTL4GaugeRenderer ctor) -- not this TU. diff --git a/restoration/source410/BT_L4/BTL4GAU3.HPP b/restoration/source410/BT_L4/BTL4GAU3.HPP new file mode 100644 index 00000000..3f5e3b95 --- /dev/null +++ b/restoration/source410/BT_L4/BTL4GAU3.HPP @@ -0,0 +1,439 @@ +//===========================================================================// +// File: btl4gau3.hpp // +// Project: BattleTech Brick: Gauge Renderer Manager // +// Contents: Cockpit instrument library, part 3 -- the MULTIPLAYER / TACTICAL // +// HUD displays: the scoreboard, pilot roster, per-player status // +// name-tags, the sector display and the message board. These read // +// the shared "Players" roster and the live mech roster rather than // +// a single subsystem. // +// * PlayerStatusMappingGroup -- 28-zone armour colour-tint group // +// * SectorDisplay -- grid-cell blit + dual numeric // +// * TeamStatusDisplay -- 8-row scoreboard (DEFERRED) // +// * PilotList -- 8-pilot KILLS/DEATHS roster // +// * PlayerStatus -- single-player status name-tag // +// * MessageBoard -- message / sender bitmap board // +//---------------------------------------------------------------------------// +// Date Who Modification // +// -------- --- ---------------------------------------------------------- // +// 02/22/96 CPB Initial coding. // +//---------------------------------------------------------------------------// +// Copyright (C) 1996, Virtual World Entertainment, Inc. All rights reserved // +// PROPRIETARY AND CONFIDENTIAL // +//===========================================================================// +// +// STAGED RECONSTRUCTION. No BTL4GAU3.HPP survived; recovered from +// BTL4OPT.EXE and re-hosted onto the authentic 1995 engine headers +// (MUNGA/GAUGE.HPP + GAUGREND.HPP, MUNGA_L4/L4GAUGE.HPP). Class names from +// the per-instance identification strings near 0x51bb34: +// "SectorDisplay" 0x51bb34 "PilotList" 0x51bb4a "PlayerStatus" 0x51bb54 +// "MessageBoard" 0x51bb61 "PlayerStatusMappingGroup" 0x51bdb4 +// "Players" 0x51bcba/0x51bd97 (shared roster node) +// +// Translation-unit extent (BTL4.MAK: btl4gau2 -> btl4gau3 -> btl4grnd): +// first gau3 code @004c9bd0 (PlayerStatusMappingGroup builder) +// last gau3 code @004cbea0-1 (MessageBoard::Execute @004cb82c) +// btl4grnd begins @004cbea0 (BTL4GaugeRenderer ctor -- not this TU) +// +// Vtables (recovered from the ctors): SectorDisplay 0051beec, +// TeamStatusDisplay 0051bea8, PilotList 0051be64, PlayerStatus 0051be20, +// MessageBoard 0051bddc. All derive from GraphicGauge. +// +// The @0xNN member annotations are the BINARY object offsets (provenance +// only): the reconstructed engine bases are not byte-identical to the +// shipped objects, and every read in this TU is through named members. +// + +#if !defined(BTL4GAU3_HPP) +# define BTL4GAU3_HPP + +# if !defined(SLOT_HPP) +# include +# endif +# if !defined(L4GREND_HPP) +# include +# endif +# if !defined(L4GAUGE_HPP) +# include +# endif +# if !defined(BTL4GAUG_HPP) +# include +# endif + +//##################### Forward Class Declarations ####################### + class Player; + + // + // Reconstruction alias: the binary's per-player recoloured "Pixmap" is the + // engine's 8-bit pixel map. (Only Pixmap* appears in this header / TU.) + // + typedef PixelMap8 Pixmap; + + + //####################################################################### + // PlayerStatusMappingGroup -- a colour-tint group of 28 ColorMapperArmor + // gauges, one per named damage zone ("dz_door"... PTR_s_dz_door_0051a240, + // 0x1C=28 entries). Built by PlayerStatus to tint a player's mech outline + // bitmap by per-zone armour damage. @004c9bd0 build / @004c9ca8 destroy / + // @004c9cf4 Execute-all / @004c9d18 SetColor-all. Each element is a + // ColorMapperArmor (btl4gaug) wired to a resolved damage-zone index. + // (palette files adpal.pcc / adpal2.pcc; name "PlayerStatusMappingGroup".) + //####################################################################### + enum { playerStatusZoneCount = 28 }; + + class PlayerStatusMappingGroup + { + public: + PlayerStatusMappingGroup( // @004c9bd0 + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer, + int graphics_port_number, + Entity *mech, + int base_color_index, + const char *palette_a, + const char *palette_b, + const char *identification_string + ); + ~PlayerStatusMappingGroup(); // @004c9ca8 + + Logical + TestInstance() const; + + void + Execute(); // @004c9cf4 (run all 28) + void + SetColor(int color); // @004c9d18 (recolour all 28) + + protected: + ColorMapperArmor + *zone[playerStatusZoneCount]; // this[0..0x1B] + }; + + + //####################################################################### + // SectorDisplay (config keyword "sectorDisplay") -- the radar SECTOR X/Z + // read-out on the Secondary overlay: a one-shot grid-cell background blit + + // two 3-digit NumericDisplays showing the linked mech's world sector coords + // (Round(-Z*0.01)+500, Round(X*0.01)+500 -> 100-unit sectors). + // @004c9d44 Make / @004c9e10 ctor / @004c9f94 dtor / @004ca038 BecameActive / + // @004ca068 LinkToEntity (slot 9) / @004ca07c Execute. vtable 0051beec. + // Was PROSE-ONLY (parse-skipped "sectorDisplay" line); reconstructed + // byte-verified from the disassembly + methodDescription PE-parse. + //####################################################################### + class SectorDisplay : + public GraphicGauge + { + public: + static MethodDescription + methodDescription; // "sectorDisplay" + + static Logical + Make( // @004c9d44 + int display_port_index, + Vector2DOf position, + Entity *entity, + GaugeRenderer *gauge_renderer + ); + + SectorDisplay( // @004c9e10 (owner folded to 0, like the siblings) + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer, + int graphics_port_number, + int x, int y, + const char *image, + int color, + int ok_color, + const char *identification_string + ); + ~SectorDisplay(); // @004c9f94 + + Logical + TestInstance() const; + + void + LinkToEntity(Entity *entity); // @004ca068 slot 9 -- caches the subject mech + void + BecameActive(); // @004ca038 slot 3 (non-inactivating) + void + Execute(); // @004ca07c + + protected: + Entity *subject; // @0x90 LinkToEntity target (ctor=0) + char *gridImage; // @0x94 interned image name (nameCopy; blit source) + int cellWidth; // @0x98 imageWidth / 14 + int gridLeft; // @0x9C cellWidth*12 + int gridRight; // @0xA0 cellWidth*13 - 1 + int gridHeight; // @0xA4 imageHeight + int numericColor; // @0xA8 param color (both numerics' color) + int gridColor; // @0xAC param okColor (blit color + numeric okColor) + int sectorBaseA; // @0xB0 = 500 (added to the -Z numeric) + int sectorBaseB; // @0xB4 = 500 (added to the +X numeric) + int dirty; // @0xB8 redraw flag (BecameActive=1, Execute clears) + NumericDisplay + *numericA, // @0xBC -Z sector readout + *numericB; // @0xC0 X sector readout + // Binary sizeof == 0xC4 (the Make alloc). Provenance only: the + // reconstructed GraphicGauge base is not byte-identical to 1995 and + // every read here is via named members. + }; + + + //####################################################################### + // TeamStatusDisplay -- the 8-slot multiplayer scoreboard. @004ca208 ctor / + // @004ca39c dtor / @004ca424 SetEnable / @004ca438 BecameActive / + // @004ca478 Execute. vtable 0051bea8. Each of the 8 rows owns a + // NumericDisplay pair (player number + kill count). Execute reads the + // shared "Players" node (FindByName @00403ad0, "Players" 0x51bcba), slots + // players by their player index (entity+0x1cc), draws each player's name + // bitmap / kill numeric and a selection highlight on the local player. + // + // DEFERRED: the donor reconstruction is PROSE-ONLY (no recovered bodies, + // no methodDescription, no config keyword registered) -- declaration kept + // so the binary class inventory stays visible; NO bodies exist in + // BTL4GAU3.CPP and nothing constructs it. See the deferred note there. + //####################################################################### + class TeamStatusDisplay : + public GraphicGauge + { + public: + TeamStatusDisplay( // @004ca208 + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer, + int graphics_port_number, + int x, int y, + const char *font_image, + int row_spacing, + int flags, + const char *identification_string + ); + ~TeamStatusDisplay(); // @004ca39c + + Logical + TestInstance() const; + + void + SetEnable(Logical enable); // @004ca424 this[0x24] + void + BecameActive(); // @004ca438 + void + Execute(); // @004ca478 + + protected: + Logical enabled; // @0x90 this[0x24] + int selectedRow; // @0x94 this[0x25] (=999) + int originX; // @0x98 this[0x26] + int originY; // @0x9C this[0x27] (param y - 0x20) + int flags; // @0xA0 this[0x28] + int rowSpacing; // @0xA4 this[0x29] + int highlightWidth; // @0xA8 this[0x2A] + struct Row { // 8 rows starting at this[0x3B] + NumericDisplay *numberDisplay; // player-number numeric + NumericDisplay *killDisplay; // kill-count numeric (flags&4) + } row[8]; + }; + + + //####################################################################### + // PilotList (config keyword "pilotList") -- the Comm KILLS/DEATHS roster. + // @004ca90c Make / @004ca958 ctor / @004cab10 dtor / @004caba4 BecameActive / + // @004cabd0 Execute / @004cad70 DrawMechIcon. vtable 0051be64, binary + // sizeof 0x178. Each entry has three NumericDisplays laid out by a + // per-entry layout mode (0/1/2 -> the PE-parsed DAT_0051af88 table). + // Execute walks the pilot roster ONE slot per frame (round-robin over 8). + // Was PROSE-ONLY -> the "pilotList" line was parse-skipped and the Comm + // surface showed only the baked btcomm.pcx labels. + //####################################################################### + class PilotList : + public GraphicGauge + { + public: + static MethodDescription + methodDescription; // "pilotList" + + static Logical + Make( // @004ca90c + int display_port_index, + Vector2DOf position, + Entity *entity, + GaugeRenderer *gauge_renderer + ); + + PilotList( // @004ca958 (NO x,y: positions come from DAT_0051af88) + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer, + int graphics_port_number, + const char *font_image, + const char *identification_string + ); + ~PilotList(); // @004cab10 + + Logical + TestInstance() const; + + void + BecameActive(); // @004caba4 + void + Execute(); // @004cabd0 + + protected: + void + DrawMechIcon(Player *pilot, int selected); // @004cad70 + // (DrawAmmoCount @004cae20 -- documented, no recovered caller.) + + int currentSlot; // @0x90 this[0x24] (round-robin, =9 init) + Logical built; // @0x94 this[0x25] + struct Entry { // 8 entries (stride 0x1C) from this[0x26] + int x; // +0x00 + int y; // +0x04 + int resolvedMech; // +0x08 (=-1 after BecameActive) + int selected; // +0x0C + NumericDisplay *nameDisplay; // +0x10 KILLS numeric + NumericDisplay *mechDisplay; // +0x14 DEATHS numeric + NumericDisplay *scoreDisplay; // +0x18 erase-only + } entry[8]; + }; + + + //####################################################################### + // PlayerStatus (config keyword "PlayerStatus") -- a single-player status + // name-tag. @004cae90 Make / @004cb1a8 ctor / @004cb28c dtor / @004cb310 + // BecameActive / @004cb358 Execute. vtable 0051be20, 200-byte alloc. + // Make validates the player number (must be 1..8; warns "PlayerStatus: + // Make player number "). Execute resolves player N from the shared + // "Players" roster ("Players" 0x51bd97), recolours its mech-outline bitmap + // per player palette via CreateMutantPixelmap8 (@004cafac, file-private) + + // PlayerStatusMappingGroup (adpal.pcc/adpal2.pcc), and shows the player + // score + an alive/dead colour state. + //####################################################################### + class PlayerStatus : + public GraphicGauge + { + public: + static MethodDescription + methodDescription; // "PlayerStatus" + + static Logical + Make( // @004cae90 + int display_port_index, + Vector2DOf position, + Entity *entity, + GaugeRenderer *gauge_renderer + ); + + PlayerStatus( // @004cb1a8 (13 args; owner folded to 0 in base call) + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer, + int graphics_port_number, + int x, int y, + int player_number, + const char *font_image, + int color, + int okColor, + int aliveColor, + int deadColor, + const char *identification_string + ); + ~PlayerStatus(); // @004cb28c + + Logical + TestInstance() const; + + void + BecameActive(); // @004cb310 + void + Execute(); // @004cb358 + + protected: + int port; // @0x90 this[0x24] graphics_port_number + int playerIndex; // @0x94 this[0x25] (player_number-1) + Player *player; // @0x98 this[0x26] cached roster player (non-owned) + BitMap *nameImage; // @0x9C this[0x27] name-cache BitMap (cache-owned) + Pixmap *recoloredMech; // @0xA0 this[0x28] CreateMutantPixelmap8 result (owned) + int color; // @0xA4 this[0x29] + int okColor; // @0xA8 this[0x2A] + int aliveColor; // @0xAC this[0x2B] + int deadColor; // @0xB0 this[0x2C] + PlayerStatusMappingGroup + *mappingGroup; // @0xB4 this[0x2D] (owned; the binary LEAKED it) + NumericDisplay + *scoreDisplay; // @0xB8 this[0x2E] + int dirty; // @0xBC this[0x2F] redraw flag + int previousStatus; // @0xC0 this[0x30] last alive/dead state + int previousMappingState; // @0xC4 this[0x31] + }; + + + //####################################################################### + // MessageBoard (config keyword "messageBoard") -- the secondary-MFD comm/ + // status message ticker. @004cb678 Make / @004cb704 ctor / @004cb788 dtor / + // @004cb7fc BecameActive / @004cb818 SetSource / @004cb82c Execute. + // vtable 0051bddc, binary sizeof 0xA4 (the Make alloc). Blits one cell of + // the strip bitmap btsmsgs.pcx (message id -> cell (id&3)<<7, (id>>2)<<5) + // plus the sender's name bitmap. Was PROSE-ONLY -> "messageBoard" was + // parse-skipped. + // + // DEFERRED / EMPTY (authentic for bring-up): the source mech is never + // bound (SetSource @004cb818 has no recovered caller) AND the per-player + // status-message feed (Player::statusMessagePointer's producer pool) is + // not reconstructed, so there are no messages -> Execute early-returns on + // the NULL source (a safe no-op == the empty board). + // + // Field-order FIXes on record: trackedMech@0x90 (was decoded "int + // enabled"); previousNameId@0x9C=-1 / previousMessageId@0xA0=-2 (were + // swapped in the first decode). + //####################################################################### + class MessageBoard : + public GraphicGauge + { + public: + static MethodDescription + methodDescription; // "messageBoard" + + static Logical + Make( // @004cb678 + int display_port_index, + Vector2DOf position, + Entity *entity, + GaugeRenderer *gauge_renderer + ); + + MessageBoard( // @004cb704 + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer, + int graphics_port_number, + int x, int y, + const char *strip_image, + int color, + const char *identification_string + ); + ~MessageBoard(); // @004cb788 + + Logical + TestInstance() const; + + void + BecameActive(); // @004cb7fc + void + SetSource(Entity *mech); // @004cb818 (binds trackedMech; no caller -> deferred) + void + Execute(); // @004cb82c + + protected: + Entity *trackedMech; // @0x90 this[0x24] source mech (SetSource; ctor=0) + char *stripImage; // @0x94 this[0x25] interned btsmsgs.pcx (held ref) + int color; // @0x98 this[0x26] param (CFG 4th arg = 0) + int previousNameId; // @0x9C this[0x27] BecameActive=-1 + int previousMessageId; // @0xA0 this[0x28] BecameActive=-2 + }; + + + // NOTE: CreateMutantPixelmap8 (@004cafac) + ReadStreamString (@004caf50) + // are file-PRIVATE in the binary -- declared static at the top of + // BTL4GAU3.CPP, not here. + +#endif diff --git a/restoration/source410/BT_L4/BTL4GAUG.CPP b/restoration/source410/BT_L4/BTL4GAUG.CPP new file mode 100644 index 00000000..4ea43350 --- /dev/null +++ b/restoration/source410/BT_L4/BTL4GAUG.CPP @@ -0,0 +1,2976 @@ +//===========================================================================// +// File: btl4gaug.cpp // +// Project: BattleTech Brick: Gauge Renderer Manager // +// Contents: The cockpit instrument gauge library (see btl4gaug.hpp). // +//---------------------------------------------------------------------------// +// Date Who Modification // +// -------- --- ---------------------------------------------------------- // +// 02/22/96 CPB Initial coding. // +//---------------------------------------------------------------------------// +// Copyright (C) 1996, Virtual World Entertainment, Inc. All rights reserved // +// PROPRIETARY AND CONFIDENTIAL // +//===========================================================================// +// +// RECONSTRUCTED from the shipped binary (BTL4OPT.EXE) and re-hosted onto the +// authentic 1995 engine (MUNGA GAUGE/GAUGREND, MUNGA_L4 L4GAUGE) for the +// BC++4.52 DOS build. Behaviour follows the Ghidra pseudo-C in +// recovered/all/part_013.c (@004c2f94..@004c5e84) and part_014.c +// (@004c5e84..@004c6394); per-method @ADDR evidence is cited inline. +// +// Translation-unit extent (linked between btl4rdr.obj and btl4gau2.obj): +// first gaug code @004c2f94 (DrawTiledBitmap helper @004c2ff8) +// anchor @004c3f6c ColorMapperHeat::ColorMapperHeat +// assert "...\BTL4GAUG.CPP" line 0x68a +// last gaug code @004c6394 SegmentArcRatio (+ dtor thunks @004c66xx) +// btl4gau2 begins @004c6798 (SeekVoltage gauge, string pool 0x5199xx) +// +// Engine-internal helper map (consistent with btl4rdr.cpp's table): +// FUN_00444308 Gauge ctor FUN_00444360 Gauge dtor +// FUN_00444124 GaugeConnection ctor FUN_00444148 GaugeConnection dtor +// FUN_00444818 GraphicGauge ctor FUN_00444870 GraphicGauge dtor +// FUN_004700ac name intern -> nameCopy (L4GAUGE.CPP:80) +// FUN_00447f84 GaugeRenderer::GetGraphicsPort(port_number) +// FUN_00442f6a / 00443090 palette8Bin Get(peek) / Release +// FUN_00442aec / 00442c12 bitMapBin Get / Release +// FUN_00442d2b / 00442e51 pixelMap8Bin Get / Release +// FUN_0041f98c Entity::FindSubsystem(name) +// FUN_0041a1a4 IsDerivedFrom(class_derivations) +// FUN_0042076c Entity::GetDamageZoneIndex(name) +// FUN_004dcd94 Round(Scalar) -> int (declared in scalar.hpp) +// FUN_00408328 SinCosPair::operator=(const Radian&) +// FUN_004700bc NumericDisplay ctor FUN_0047018c ::dtor +// FUN_004703f4 NumericDisplay::ForceUpdate FUN_00470430 ::Draw +// FUN_0044a5b4/5dc GraphicsViewRecord ctor/dtor +// FUN_0044a650/630 GraphicsViewRecord Draw / Clear +// FUN_00474855 GaugeConnectionDirectOf ctor +// FUN_004749de GaugeConnectionDirectOf ctor +// FUN_00473f44 SegmentArc ctor @00474300 SegmentArc::Execute +// DAT_00524e20 warning channel -> DEBUG_STREAM +// 0x50e3ec / 0x50e604 heat class-derivation tags (HeatableSubsystem / +// HeatWatcher) +// +// Recovered constant-pool floats (section_dump.txt): +// _DAT_004c452c = 0.0f (HorizTwoPartBar low clamp) +// _DAT_004c4b00 = 0.0f (VertTwoPartBar low clamp) +// _DAT_004c4d3c = 0.0f , _DAT_004c4d40 = 1.0f (VertNormalSlider clamp) +// _DAT_004c5acc = 0.0f , 0x42652ee1 = 57.29578f (HeadingPointer rad->deg) +// _DAT_004c62d4 = 0.75f, _DAT_004c6484 = 0.75f (arc span fraction) +// _DAT_0050e3d8 = 0.0025f (leak-wipe 'trace leak' threshold) +// + +#include +#pragma hdrstop + +#if !defined(BTL4GAUG_HPP) +# include +#endif + +#if !defined(HEAT_HPP) +# include +#endif + +#if !defined(ROTATION_HPP) +# include +#endif + +static const Scalar ZeroClamp = 0.0f; // _DAT_004c452c / _DAT_004c4b00 +static const Scalar OneClamp = 1.0f; // _DAT_004c4d40 +static const Scalar RadiansToDegrees = 57.29578f; // 0x42652ee1 +static const Scalar TraceLeakLevel = 0.0025f; // _DAT_0050e3d8 + + +//########################################################################### +//########################################################################### +// File-private GaugeConnection subclasses +// +// Every gauge in this module that is "driven by mech subsystem state" is +// fed through one of these small GaugeConnection objects. Gauge::Update +// (GAUGE.CPP:549) walks the gauge's instanceList calling each connection's +// virtual Update() once per frame, then calls the gauge's Execute(). +// +// Data bindings are through the reconstructed subsystems' public members +// and accessors (heat.hpp / mechsub.hpp / the engine damage model), never +// raw 1995 byte offsets: +// HeatConnection @004c3664 ctor / @004c3720 Update +// ArmorZoneConnection @004c33a4 ctor / @004c3430 Update +// MultiArmorConnection@004c346c ctor / @004c34f4 Update +// CriticalConnection @004c3598 ctor / @004c3610 Update +// StateConnection @004c3324 ctor / @004c3390 Update +// (@004c3134/@004c31ec/@004c3288 in this address range belong to the +// btl4gau2 cluster connections and are reconstructed there.) +//########################################################################### +//########################################################################### + +// +// StateConnection -- @004c3324 ctor / @004c3390 Update. Copies a Subsystem's +// simulation state into the destination each frame (drives the frame index +// of OneOfSeveralStates). The binary's raw read of the "state word @0x14" +// is Simulation::GetSimulationState(). +// +class StateConnection : public GaugeConnection +{ +public: + StateConnection(int *destination, Subsystem *source): + GaugeConnection(0), // FUN_00444124(this,0) + source(source), + destination(destination) + {} + +protected: + void Update(); // @004c3390 + + Subsystem *source; // @0x10 + int *destination; // @0x14 +}; + +void + StateConnection::Update() +{ + Check(this); + *destination = (source != NULL) ? (int)source->GetSimulationState() : 0; +} + +// +// ArmorZoneConnection -- @004c33a4 ctor / @004c3430 Update. Drives a +// ColorMapper's colour index from ONE damage zone's live damage ratio. The +// ctor resolves the zone from the entity's damageZones[zone_index]; Update +// feeds it each frame. Used by ColorMapperArmor. +// +class ArmorZoneConnection : public GaugeConnection +{ +public: + ArmorZoneConnection(int *destination, Entity *entity, int zone_index): + GaugeConnection(0), // FUN_00444124(this,0) + zone((zone_index < 0) ? NULL : entity->damageZones[zone_index]), + currentColorIndex(destination) + {} + +protected: + // + // Per-frame feed (@004c3430): no zone -> 100 (fail-safe full tint); else + // the zone's damage RATIO (DamageZone::damageLevel, 0..1) scaled to a + // 0..100 percentage and rounded -- ColorMapper::Execute clamps it and + // pushes the palette slot, recolouring that zone on the cockpit ARMOR + // DAMAGE schematic. + // + void Update(); + + DamageZone *zone; // @0x10 source + int *currentColorIndex; // @0x14 destination +}; + +void + ArmorZoneConnection::Update() +{ + Check(this); + if (zone == NULL) + { + *currentColorIndex = 100; + return; + } + *currentColorIndex = Round(zone->damageLevel * 100.0f); // FUN_004dcd94 +} + +// +// MultiArmorConnection -- @004c346c ctor / @004c34f4 Update. Drives a +// ColorMapper's colour index from the WORST of up to 8 damage zones. The +// ctor copies the 8 zone indices + the owner; Update scans them each frame +// and feeds the maximum damage ratio. Used by ColorMapperMultiArmor. +// +class MultiArmorConnection : public GaugeConnection +{ +public: + MultiArmorConnection(int *destination, Entity *entity, const int *zone_indices): + GaugeConnection(0), // FUN_00444124(this,0) + owner(entity), + currentColorIndex(destination) + { + int i; + for (i = 0; i < 8; i++) + { + zoneIndex[i] = zone_indices[i]; + } + } + +protected: + // + // Per-frame feed (@004c34f4): scan the (up to 8) zones -- skipping + // index < 0 ("unused") and absent zones -- keep the WORST (max) + // damageLevel, scale to 0..100 and round; no zone present -> 100. + // + void Update(); + + Entity *owner; // @0x10 + int zoneIndex[8]; // @0x18..0x34 + int *currentColorIndex; // @0x38 +}; + +void + MultiArmorConnection::Update() +{ + Check(this); + + Scalar + worst = 0.0f; + int + count = 0, + i; + + for (i = 0; i < 8; i++) + { + if (zoneIndex[i] >= 0) + { + DamageZone + *zone = owner->damageZones[zoneIndex[i]]; + if (zone != NULL) + { + ++count; + if (worst < zone->damageLevel) + { + worst = zone->damageLevel; + } + } + } + } + *currentColorIndex = (count == 0) ? 100 : Round(worst * 100.0f); +} + +// +// CriticalConnection -- @004c3598 ctor / @004c3610 Update. Drives a +// ColorMapper's colour index from ONE subsystem's operational state. Used +// by ColorMapperCritical. +// +class CriticalConnection : public GaugeConnection +{ +public: + CriticalConnection(int *destination, Subsystem *source): + GaugeConnection(0), // FUN_00444124(this,0) + subsystem(source), + currentColorIndex(destination) + {} + +protected: + // + // Per-frame feed (@004c3610): no subsystem -> 0 (blank); subsystem + // DESTROYED -> 100 (full critical tint); else the subsystem's own + // damage-zone damageLevel (0..1) as a 0..100 percentage. + // + // POLARITY (on record): the binary's "+0x40 == 1" is the simulation + // state's DESTROYED reading -- GetSimulationState() == + // MechSubsystem::DestroyedState -- NOT an "operational" flag. + // + void Update(); + + Subsystem *subsystem; // @0x10 source + int *currentColorIndex; // @0x14 destination +}; + +void + CriticalConnection::Update() +{ + Check(this); + if (subsystem == NULL) + { + *currentColorIndex = 0; + return; + } + if (subsystem->GetSimulationState() == MechSubsystem::DestroyedState) + { + *currentColorIndex = 100; + return; + } + // + // The subsystem's own damage zone (the proxy IS a real DamageZone -- + // Subsystem::damageZone). The binary reads it unconditionally; guard + // NULL (not every subsystem has its zone wired) -> 0, matching an + // intact/undamaged reading. + // + DamageZone + *zone = subsystem->damageZone; + *currentColorIndex = (zone != NULL) ? Round(zone->damageLevel * 100.0f) : 0; +} + +// +// HeatConnection -- @004c3664 ctor / @004c3720 Update. Drives a +// ColorMapper's colour index from a heat-bearing subsystem's live +// temperature. Used by ColorMapperHeat -- THIS is the heat tint's data +// feed. The binary's flag@0x14 recorded "primary heat class vs. watcher"; +// here the resolved HeatSink view plays that role. +// +class HeatConnection : public GaugeConnection +{ +public: + // + // The owning ctor (ColorMapperHeat, @004c3f6c) verified the subsystem + // derives from HeatableSubsystem or HeatWatcher; resolve which branch + // once, here. Every concrete heat-bearing subsystem in the recon + // (HeatSink, Condenser, Reservoir, Generator, the weapons, Myomers, ...) + // descends from HeatSink, whose public CurrentTemperatureOf() is the + // live temperature accessor. + // + HeatConnection(int *destination, Subsystem *source): + GaugeConnection(0), // FUN_00444124(this,0) + heatSubsystem(source), + currentColorIndex(destination) + { + heatSink = NULL; + if (source != NULL && source->IsDerivedFrom(HeatSink::ClassDerivations)) + { + heatSink = (HeatSink *)source; + } + } + +protected: + // + // The per-frame data feed (@004c3720): + // no subsystem -> 100 (fail safe: full tint) + // subsystem destroyed -> 100 (the recovered "+0x40 == 1" path) + // HeatSink family -> Round(current temperature) + // HeatWatcher family -> 0 (INERT -- see note below) + // + // OPEN QUESTION (kept from the decode worksheet): the recovered code + // rounds the raw temperature straight into the 0..255 colour index that + // ColorMapper::Execute clamps to [0,255]. The exact temperature-> + // percentage scaling (HEAT.TCP seeds currentTemperature at 300.0) is a + // tuning detail to reconcile against the original l4gauge.cfg palette + // ramp; the live data path itself is correct. + // + // HEATWATCHER BRANCH [T4, INERT]: for a watcher the binary resolved a + // link at the same offset the Heatable keeps its temperature at + // (unverified read path), and our reconstruction exposes no public + // accessor for the watched temperature (HeatWatcher::watchedLink is + // protected). The feed is left at 0 -- the tint draws, stays cool. + // + void Update(); + + Subsystem *heatSubsystem; // @0x10 source + HeatSink *heatSink; // (the binary's flag@0x14 role) + int *currentColorIndex; // @0x18 destination +}; + +void + HeatConnection::Update() +{ + Check(this); + if (heatSubsystem == NULL) + { + *currentColorIndex = 100; + return; + } + if (heatSubsystem->GetSimulationState() == MechSubsystem::DestroyedState) + { + *currentColorIndex = 100; + return; + } + if (heatSink != NULL) + { + *currentColorIndex = Round(heatSink->CurrentTemperatureOf()); // the live feed + } + else + { + *currentColorIndex = 0; // HeatWatcher branch -- INERT (see above) + } +} + + +//########################################################################### +//########################################################################### +// ColorMapper (base : Gauge) +//########################################################################### +//########################################################################### + +// +// @004c37dc -- ctor (vtable PTR_FUN_00518e10), base name passed through to +// Gauge. Interns both palette names, flags twoColorMode when they differ, +// references each palette in the warehouse palette bin (a missing palette is +// a content warning), stashes the target hardware palette slot and resolves +// the renderer graphics port that owns the live palette. +// +ColorMapper::ColorMapper( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer, + int graphics_port_number, + int color_index, + const char *palette_name_a, + const char *palette_name_b, + const char *identification_string +): + Gauge(rate, mode_mask, renderer, 0, identification_string) // FUN_00444308 (owner_ID 0) +{ + Check_Pointer(this); + + //------------------------------------------------------------ + // Intern a private copy of each palette name (the strings the + // interpreter hands in are transient parse scratch) + //------------------------------------------------------------ + paletteName[0] = nameCopy(palette_name_a); // FUN_004700ac + paletteName[1] = nameCopy(palette_name_b); + twoColorMode = (Logical)(stricmp(paletteName[0], paletteName[1]) != 0); + + //------------------------------------------------------------ + // Reference each palette in the warehouse palette bin (held + // for the gauge's life); warn if the content is missing + //------------------------------------------------------------ + L4Warehouse + *warehouse = (L4Warehouse *)renderer->warehousePointer; + Check(warehouse); + + int + i; + for (i = 0; i < 2; ++i) + { + if (warehouse->palette8Bin.Get(paletteName[i]) == NULL) // FUN_00442f6a + { + DEBUG_STREAM << "ColorMapper cannot find palette " + << paletteName[i] << "\n"; + } + } + + previousColorIndex = -1; // this[0x16] (force first push) + previousRed = previousGreen = previousBlue = 0; + colorSlot = color_index; // this[0x17] + paletteToggle = 0; // this[0x18] + graphicsPort = renderer->GetGraphicsPort(graphics_port_number); // FUN_00447f84 -> this[0x1A] + + Check_Fpu(); +} + +// +// @004c38dc -- dtor. Release both palette refs (keyed on the names, so +// before the frees), free the interned names, then the base ~Gauge chain. +// (The deleting-destructor thunks @004c66ff / @004c6725 / @004c674b / +// @004c6771 forward here for the four derived ColorMappers.) +// +ColorMapper::~ColorMapper() +{ + Check(this); + + L4Warehouse + *warehouse = (L4Warehouse *)renderer->warehousePointer; + + int + i; + for (i = 0; i < 2; ++i) + { + warehouse->palette8Bin.Release(paletteName[i]); // FUN_00443090 + Unregister_Pointer(paletteName[i]); + delete[] paletteName[i]; + paletteName[i] = NULL; + } + // base ~Gauge -- FUN_00444360 (releases the connections) + Check_Fpu(); +} + +// +// @004c395c -- BecameActive. On (re)activation, force the next Execute +// (@004c3980) to re-push the hardware palette: invalidate the cached colour +// index + the last-written RGB so the "unchanged" fast-out cannot skip the +// write (the surface may have been cleared/redrawn while inactive). +// +void + ColorMapper::BecameActive() +{ + Check(this); + currentColorIndex = -1; // this[0x15] @0x54 + previousColorIndex = -1; // this[0x16] @0x58 + previousRed = previousGreen = previousBlue = 0xFF; // @0x64-0x66 +} + +// +// @004c3980 -- the palette push. Clamp the driving value to a [0,255] +// colour index, choose the active palette (flashing alternates the two when +// twoColorMode), read the index's R/G/B triple and, if it differs from last +// frame, write it straight into the hardware palette slot. +// +// The palette lookup is a PEEK of the already-loaded resource: under the +// 1995 warehouse API GetIfAlreadyExists() takes a reference, so it is paired +// with a Release() (net zero; the ctor's Get() keeps the palette resident). +// +void + ColorMapper::Execute() +{ + Check(this); + + if (currentColorIndex > 254) + { + currentColorIndex = 255; + } + if (currentColorIndex < 1) + { + currentColorIndex = 0; + } + + if (!twoColorMode) + { + if (currentColorIndex == previousColorIndex) + { + return; // nothing changed + } + previousColorIndex = currentColorIndex; + } + else + { + // + // Flash: alternate palette 0 / palette 1 every frame. + // + if (++paletteToggle > 1) + { + paletteToggle = 0; + } + } + + L4Warehouse + *warehouse = (L4Warehouse *)renderer->warehousePointer; + Palette8 + *palette = warehouse->palette8Bin.GetIfAlreadyExists(paletteName[paletteToggle]); + if (palette != NULL) + { + // + // Read the index's R/G/B triple and, if it differs from last frame, + // push it straight into the hardware palette slot. + // + PaletteTriplet + &entry = palette->Color[currentColorIndex]; + if (previousRed != entry.Red || + previousGreen != entry.Green || + previousBlue != entry.Blue) + { + previousRed = entry.Red; + previousGreen = entry.Green; + previousBlue = entry.Blue; + + graphicsPort->SetColor(&entry, colorSlot); // GraphicsPort::SetColor(PaletteTriplet*,int) + } + warehouse->palette8Bin.Release(paletteName[paletteToggle]); + } + Check_Fpu(); +} + + +//########################################################################### +// ColorMapperArmor @004c3aa4 Make / @004c3b98 ctor +//########################################################################### +// +// Tints ONE damage zone's colour on the cockpit ARMOR DAMAGE schematic by +// that zone's live damage. CFG shape (L4GAUGE.CFG:4789, inside the +// colorMapArmor macro): +// cmArmor( rate, mode, colourSlot, paletteA, paletteB, zoneName ) +// e.g. cmArmor(H, ModeSecondaryDamage, 32, adpal.pcc, adpal2.pcc, dz_ltorso); +// + +MethodDescription + ColorMapperArmor::methodDescription = + { + "cmArmor", + ColorMapperArmor::Make, + { + { ParameterDescription::typeRate, NULL }, // rate ID + { ParameterDescription::typeModeMask, NULL }, // mode mask + { ParameterDescription::typeColor, NULL }, // hardware colour slot + { ParameterDescription::typeString, NULL }, // palette name A + { ParameterDescription::typeString, NULL }, // palette name B + { ParameterDescription::typeString, NULL }, // damage-zone name (e.g. "dz_ltorso") + PARAMETER_DESCRIPTION_END + } + }; + +// +// @004c3aa4 -- Make. Resolve the named damage zone to an index on the +// entity, then construct. (The binary built a transient zone-name +// descriptor and called FUN_0042076c; Entity::GetDamageZoneIndex IS that +// lookup -- it scans damageZones[] matching each zone's name; -1 = miss.) +// +Logical + ColorMapperArmor::Make( + int display_port_index, + Vector2DOf /*position*/, + Entity *entity, + GaugeRenderer *gauge_renderer + ) +{ + ParameterDescription + *parameterList = methodDescription.parameterList; + + Check(gauge_renderer); + Check_Pointer(entity); + + int + zone_index = entity->GetDamageZoneIndex(CString(parameterList[5].data.string)); + if (zone_index < 0) + { + DEBUG_STREAM << "ColorMapperArmor warning: damage zone " + << parameterList[5].data.string << " not found\n"; + } + +# if DEBUG_LEVEL > 0 + ColorMapperArmor + *gauge = +# endif + new ColorMapperArmor( // FUN_004c3b98 (0x70 bytes) + parameterList[0].data.rate, + parameterList[1].data.modeMask, + (L4GaugeRenderer *)gauge_renderer, + display_port_index, // graphics port number (the runtime port) + parameterList[2].data.color, // colour slot + entity, + parameterList[3].data.string, // palette A + parameterList[4].data.string, // palette B + zone_index, + "ColorMapperArmor"); + Register_Object(gauge); + + Check_Fpu(); + return True; +} + +// +// @004c3b98 -- ctor: ColorMapper base + wire an ArmorZoneConnection feeding +// the resolved damage zone's live damage to the colour index. +// +ColorMapperArmor::ColorMapperArmor( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer, + int graphics_port_number, + int color_index, + Entity *entity, + const char *palette_a, + const char *palette_b, + int damage_zone_index, + const char *identification_string +): + ColorMapper( // FUN_004c37dc (owner_ID 0 folded in, as for cmHeat) + rate, mode_mask, renderer, graphics_port_number, + color_index, palette_a, palette_b, identification_string) +{ + Check_Pointer(this); + + unused = 0; // this[0x1B] @0x6C + + GaugeConnection + *connection; + + connection = new ArmorZoneConnection( // @004c33a4 (0x18 bytes) + ¤tColorIndex, entity, damage_zone_index); + Check(connection); + Register_Object(connection); + AddConnection(connection); + + Check_Fpu(); +} + +// +// ColorMapperArmor destructor. No extra work: the ArmorZoneConnection is +// released by the base Gauge teardown, the palettes by ~ColorMapper -- the +// base-dtor chain runs implicitly at the closing brace. +// +ColorMapperArmor::~ColorMapperArmor() +{ +} + +//########################################################################### +// ColorMapperMultiArmor @004c3c48 Make / @004c3d60 ctor +//########################################################################### +// +// Tints one schematic colour by the WORST of up to 8 damage zones (e.g. a +// whole torso section). CFG shape (L4GAUGE.CFG:96): +// colorMapperMultiArmor( rate, mode, colourSlot, paletteA, paletteB, +// zone1, zone2, ... zone8 ) ("unused" = empty slot) +// + +MethodDescription + ColorMapperMultiArmor::methodDescription = + { + "colorMapperMultiArmor", + ColorMapperMultiArmor::Make, + { + { ParameterDescription::typeRate, NULL }, // rate ID + { ParameterDescription::typeModeMask, NULL }, // mode mask + { ParameterDescription::typeColor, NULL }, // hardware colour slot + { ParameterDescription::typeString, NULL }, // palette name A + { ParameterDescription::typeString, NULL }, // palette name B + { ParameterDescription::typeString, NULL }, // zone 1 + { ParameterDescription::typeString, NULL }, // zone 2 + { ParameterDescription::typeString, NULL }, // zone 3 + { ParameterDescription::typeString, NULL }, // zone 4 + { ParameterDescription::typeString, NULL }, // zone 5 + { ParameterDescription::typeString, NULL }, // zone 6 + { ParameterDescription::typeString, NULL }, // zone 7 + { ParameterDescription::typeString, NULL }, // zone 8 + PARAMETER_DESCRIPTION_END + } + }; + +// +// @004c3c48 -- Make. Resolve up to 8 named zones to indices; build if any +// resolves. BINARY QUIRK (kept, byte-faithful): the presence test is +// `index != 0`, so a valid zone 0 AND a -1 "unused" slot both count as +// "present" -- i.e. it always builds; the connection simply skips index < 0. +// +Logical + ColorMapperMultiArmor::Make( + int display_port_index, + Vector2DOf /*position*/, + Entity *entity, + GaugeRenderer *gauge_renderer + ) +{ + ParameterDescription + *parameterList = methodDescription.parameterList; + + Check(gauge_renderer); + Check_Pointer(entity); + + int + zone_indices[8]; + Logical + any = False; + int + i; + for (i = 0; i < 8; i++) + { + zone_indices[i] = + entity->GetDamageZoneIndex(CString(parameterList[5 + i].data.string)); + if (zone_indices[i] != 0) // binary: iVar2 != 0 (quirk -- see note above) + { + any = True; + } + } + if (!any) + { + DEBUG_STREAM << "colorMapperMultiArmor: No Damage zones found\n"; + return False; + } + +# if DEBUG_LEVEL > 0 + ColorMapperMultiArmor + *gauge = +# endif + new ColorMapperMultiArmor( // FUN_004c3d60 (0x6c bytes) + parameterList[0].data.rate, + parameterList[1].data.modeMask, + (L4GaugeRenderer *)gauge_renderer, + display_port_index, // graphics port number + parameterList[2].data.color, // colour slot + entity, + parameterList[3].data.string, // palette A + parameterList[4].data.string, // palette B + zone_indices, + "ColorMapperMultiArmor"); + Register_Object(gauge); + + Check_Fpu(); + return True; +} + +// +// @004c3d60 -- ctor: ColorMapper base + a MultiArmorConnection over the 8 +// zones. +// +ColorMapperMultiArmor::ColorMapperMultiArmor( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer, + int graphics_port_number, + int color_index, + Entity *entity, + const char *palette_a, + const char *palette_b, + const int *zone_indices, + const char *identification_string +): + ColorMapper( // FUN_004c37dc + rate, mode_mask, renderer, graphics_port_number, + color_index, palette_a, palette_b, identification_string) +{ + Check_Pointer(this); + + GaugeConnection + *connection; + + connection = new MultiArmorConnection( // @004c346c (0x3c bytes) + ¤tColorIndex, entity, zone_indices); + Check(connection); + Register_Object(connection); + AddConnection(connection); + + Check_Fpu(); +} + +ColorMapperMultiArmor::~ColorMapperMultiArmor() +{ + // base-dtor chain (~ColorMapper -> ~Gauge) releases the connection + palettes. +} + +//########################################################################### +// ColorMapperCritical @004c3ddc Make / @004c3e40 ctor +//########################################################################### +// +// Tints one schematic colour by a subsystem's operational state (the +// "critical" secondary display mode). CFG shape (L4GAUGE.CFG:143): +// cmCrit( rate, mode, colourSlot, paletteA, paletteB, subsystemName ) +// e.g. cmCrit(H, ModeSecondaryCritical, 32, adpal.pcc, adpal2.pcc, GeneratorA); +// + +MethodDescription + ColorMapperCritical::methodDescription = + { + "cmCrit", + ColorMapperCritical::Make, + { + { ParameterDescription::typeRate, NULL }, // rate ID + { ParameterDescription::typeModeMask, NULL }, // mode mask + { ParameterDescription::typeColor, NULL }, // hardware colour slot + { ParameterDescription::typeString, NULL }, // palette name A + { ParameterDescription::typeString, NULL }, // palette name B + { ParameterDescription::typeString, NULL }, // subsystem name + PARAMETER_DESCRIPTION_END + } + }; + +// +// @004c3ddc -- Make. +// +Logical + ColorMapperCritical::Make( + int display_port_index, + Vector2DOf /*position*/, + Entity *entity, + GaugeRenderer *gauge_renderer + ) +{ + ParameterDescription + *parameterList = methodDescription.parameterList; + + Check(gauge_renderer); + +# if DEBUG_LEVEL > 0 + ColorMapperCritical + *gauge = +# endif + new ColorMapperCritical( // FUN_004c3e40 (0x6c bytes) + parameterList[0].data.rate, + parameterList[1].data.modeMask, + (L4GaugeRenderer *)gauge_renderer, + display_port_index, // graphics port number + parameterList[2].data.color, // colour slot + entity, + parameterList[3].data.string, // palette A + parameterList[4].data.string, // palette B + parameterList[5].data.string, // subsystem name + "ColorMapperCritical"); + Register_Object(gauge); + + Check_Fpu(); + return True; +} + +// +// @004c3e40 -- ctor: ColorMapper base + a CriticalConnection on the named +// subsystem. +// +ColorMapperCritical::ColorMapperCritical( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer, + int graphics_port_number, + int color_index, + Entity *entity, + const char *palette_a, + const char *palette_b, + const char *subsystem_name, + const char *identification_string +): + ColorMapper( // FUN_004c37dc + rate, mode_mask, renderer, graphics_port_number, + color_index, palette_a, palette_b, identification_string) +{ + Check_Pointer(this); + + Subsystem + *subsystem = entity->FindSubsystem(subsystem_name); // FUN_0041f98c + if (subsystem == NULL) + { + DEBUG_STREAM << "ColorMapperCritical warning: subsystem " + << subsystem_name << " does not exist\n"; + } + + GaugeConnection + *connection; + + connection = new CriticalConnection( // @004c3598 (0x18 bytes) + ¤tColorIndex, subsystem); + Check(connection); + Register_Object(connection); + AddConnection(connection); + + Check_Fpu(); +} + +ColorMapperCritical::~ColorMapperCritical() +{ + // base-dtor chain (~ColorMapper -> ~Gauge) releases the connection + palettes. +} + +//########################################################################### +// ColorMapperHeat @004c3f08 Make / @004c3f6c ctor *** ANCHOR *** +//########################################################################### +// +// The gauge's configured parameters come LIVE from the config interpreter +// (the CFG "cmHeat" primitive) via methodDescription.parameterList -- the +// interpreter Restore()s each instance's parsed params into this scratch +// list before calling Make (the engine pattern, cf. NumericDisplayScalar:: +// Make, L4GAUGE.CPP:571). CFG shape (verified L4GAUGE.CFG:182): +// cmHeat( rate, modeMask, colourSlot, paletteA, paletteB, subsystemName ) +// e.g. cmHeat(H, ModeSecondaryHeat, 32, heatpal.pcc, heatpal2.pcc, GeneratorA); +// + +MethodDescription + ColorMapperHeat::methodDescription = + { + "cmHeat", + ColorMapperHeat::Make, + { + { ParameterDescription::typeRate, NULL }, // refresh-rate ID + { ParameterDescription::typeModeMask, NULL }, // display mode mask + { ParameterDescription::typeColor, NULL }, // hardware colour slot + { ParameterDescription::typeString, NULL }, // palette name A + { ParameterDescription::typeString, NULL }, // palette name B + { ParameterDescription::typeString, NULL }, // heat subsystem name + PARAMETER_DESCRIPTION_END + } + }; + +// +// @004c3f08 -- Make. Allocate a ColorMapperHeat (0x6c bytes in the shipped +// object) and construct it from the parsed parameters and the class name +// "ColorMapperHeat" (string pool 0x5184c4). +// +Logical + ColorMapperHeat::Make( + int display_port_index, + Vector2DOf /*position*/, + Entity *entity, + GaugeRenderer *gauge_renderer + ) +{ + ParameterDescription + *parameterList = methodDescription.parameterList; + + Check(gauge_renderer); + +# if DEBUG_LEVEL > 0 + ColorMapperHeat + *gauge = +# endif + new ColorMapperHeat( // FUN_004c3f6c + parameterList[0].data.rate, // rate ID + parameterList[1].data.modeMask, // mode mask + (L4GaugeRenderer *)gauge_renderer, + display_port_index, // graphics port number (the runtime port) + parameterList[2].data.color, // colour slot + entity, + parameterList[3].data.string, // palette name A + parameterList[4].data.string, // palette name B + parameterList[5].data.string, // heat subsystem name (e.g. "GeneratorA") + "ColorMapperHeat"); + Register_Object(gauge); + + Check_Fpu(); + return True; +} + +// +// @004c3f6c -- ColorMapperHeat constructor. THE ANCHOR (assert path +// "d:\tesla\bt\bt_l4\BTL4GAUG.CPP", line 0x68a). +// +// Builds a ColorMapper (vtable PTR_FUN_00518d00), locates the named heat +// subsystem on the host entity, *verifies it is a heat-bearing subsystem*, +// and wires a HeatConnection so that the subsystem's live temperature +// drives the palette tint. +// +ColorMapperHeat::ColorMapperHeat( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer, + int graphics_port_number, + int color_index, + Entity *entity, + const char *palette_a, + const char *palette_b, + const char *heat_subsystem_name, + const char *identification_string +): + ColorMapper( // FUN_004c37dc + rate, mode_mask, renderer, graphics_port_number, + color_index, palette_a, palette_b, identification_string) +{ + Check_Pointer(this); + + Subsystem + *subsystem = entity->FindSubsystem(heat_subsystem_name); // FUN_0041f98c + if (subsystem == NULL) + { + DEBUG_STREAM << "ColorMapperHeat warning: subsystem " + << heat_subsystem_name << " does not exist\n"; + } + else + { + // + // The subsystem MUST be heat-bearing (one of the two heat class + // derivation tables: 0x50e3ec HeatableSubsystem, or the disjoint + // 0x50e604 HeatWatcher family -- ammo bins / torso carry a mirrored + // heat alarm) or the data feed is meaningless. + // + if (!subsystem->IsDerivedFrom(HeatableSubsystem::ClassDerivations) && // FUN_0041a1a4 + !subsystem->IsDerivedFrom(HeatWatcher::ClassDerivations)) + { + Verify(False); // "Bad subsystem type" -- BTL4GAUG.CPP line 0x68a + } + } + + // + // HeatConnection: copies the subsystem's live temperature into + // ColorMapper::currentColorIndex every frame (100 when the subsystem is + // absent or destroyed; Execute clamps to [0,255]). + // + GaugeConnection + *connection; + + connection = new HeatConnection( // FUN_004c3664 (0x1c bytes) + ¤tColorIndex, subsystem); + Check(connection); + Register_Object(connection); + AddConnection(connection); + + Check_Fpu(); +} + +// +// ColorMapperHeat destructor. No extra work: the HeatConnection added above +// is released by the base Gauge teardown (RemoveAllConnections), and the two +// palettes by ~ColorMapper -- so the base-dtor chain (~ColorMapper -> ~Gauge) +// runs implicitly at the closing brace. (The binary's deleting-dtor thunks +// @004c66ff/6725/674b/6771 forward to that same base chain.) +// +ColorMapperHeat::~ColorMapperHeat() +{ +} + + +//########################################################################### +//########################################################################### +// Two-part fill bars +//########################################################################### +//########################################################################### + +// +// @004c2ff8 -- DrawTiledBitmap helper (file-private, shared by both bars). +// Tiles a source bitmap across a destination rectangle, clipping the last +// partial row/column. For each tile it MoveTo()s and issues a clipped blit. +// The tile size is read from the bitmap (Data.Size). +// +static void + DrawTiledBitmap( + GraphicsView *view, + int originX, + int originY, + int width, + int height, + int /*color -- unused (no colour arg on the blit)*/, + BitMap *tile + ) +{ + Check_Pointer(view); + Check_Pointer(tile); + + int + tileW = tile->Data.Size.x, + tileH = tile->Data.Size.y, + x, + y, + rowH, + colW; + + for (y = 0; y < height; y += tileH) + { + rowH = height - y; + if (rowH > tileH) + { + rowH = tileH; + } + if (rowH <= 0) + { + continue; + } + + for (x = 0; x < width; x += tileW) + { + colW = width - x; + if (colW > tileW) + { + colW = tileW; + } + if (colW <= 0) + { + continue; + } + + view->MoveToAbsolute(x, y); + view->DrawBitMap(0, tile, + originX, originY, + originX + colW - 1, originY + rowH - 1); + } + } + Check_Fpu(); +} + +//########################################################################### +// HorizTwoPartBar @004c4170 ctor / @004c4324 BecameActive +// @004c4340 Execute (vtable PTR_FUN_00518cbc) +// +// A horizontal two-part fill bar, built directly by the btl4gau2 cluster +// panels (no config keyword). Three Scalar connections drive it (value, +// low/warn, high/max); the bar grows left->right. +//########################################################################### + +// +// @004c4170 -- ctor. GraphicGauge base, intern the tile bitmap + hold a +// ref, set the view extent, store fill/bg + width/height, wire the value/ +// low/high Scalar connections. +// +HorizTwoPartBar::HorizTwoPartBar( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer_in, + int graphics_port_number, + int left, + int bottom, + int right, + int top, + const char *tile_image, + int fill_color, + int background_color, + Scalar *value_pointer, + Scalar *low_pointer, + Scalar *high_pointer, + const char *identification_string +): + GraphicGauge(rate, mode_mask, renderer_in, 0, // FUN_00444818 (owner_ID 0) + graphics_port_number, identification_string) +{ + Check_Pointer(this); + + tileImage = nameCopy(tile_image); // @0x90 + ((L4Warehouse *)renderer_in->warehousePointer)-> + bitMapBin.Get(tileImage); // held for the gauge's life + + localView.SetPositionWithinPort(left, bottom, right, top); // this+0x48 + + fillColor = fill_color; // @0xA0 + backgroundColor = background_color; // @0xA4 + width = right - left; // @0xA8 + height = (top - bottom) - 1; // @0xAC + + GaugeConnection + *connection; + + connection = new GaugeConnectionDirectOf(0, &value, value_pointer); // @0x90 value + Check(connection); + Register_Object(connection); + AddConnection(connection); + + connection = new GaugeConnectionDirectOf(0, &low, low_pointer); // @0x94 + Check(connection); + Register_Object(connection); + AddConnection(connection); + + connection = new GaugeConnectionDirectOf(0, &high, high_pointer); // @0x98 + Check(connection); + Register_Object(connection); + AddConnection(connection); + + Check_Fpu(); +} + +// +// dtor. Release the tile ref (keyed on the name, before the free); the +// base chain releases the connections. +// +HorizTwoPartBar::~HorizTwoPartBar() +{ + Check(this); + ((L4Warehouse *)renderer->warehousePointer)-> + bitMapBin.Release(tileImage); + Unregister_Pointer(tileImage); + delete[] tileImage; + tileImage = NULL; + Check_Fpu(); +} + +Logical + HorizTwoPartBar::TestInstance() const +{ + return GraphicGauge::TestInstance(); +} + +// +// @004c4324 -- BecameActive: force a full repaint on the first Execute +// (previousFull=0, previousFill=width -- neither can match a real pair). +// +void + HorizTwoPartBar::BecameActive() +{ + Check(this); + previousFull = 0; + previousFill = width; +} + +// +// @004c4340 -- Execute: clamp value to [0,high], map value/warn to bar +// pixels (half-up round of width*x/high), repaint on change. THREE zones +// along X: +// [0, warnPix) the striped TILE pattern @004c4482 +// [warnPix, valPix) fillColor solid (when value > low) @004c449d +// [valPix, width) backgroundColor solid @004c44da +// +// ZONE-COLOUR MAP (verified against the binary disasm; a transcription with +// the two colours swapped rendered the whole remainder in the fill colour): +// the SetColor before the tile AND the zone-2 fill use fillColor ([0xA0]); +// only zone 3 (the unfilled remainder) uses backgroundColor ([0xA4]). +// +void + HorizTwoPartBar::Execute() +{ + Check(this); + + if (value < ZeroClamp) // @004c434c fcomp 0.0 + { + value = 0.0f; + } + else if (value > high) // @004c4367 fcomp high + { + value = high; + } + + int + warnPix = (int)((Scalar)width * low / high + 0.5f), // low/warn threshold pixel + valPix = (int)((Scalar)width * value / high + 0.5f); // current value pixel + if (warnPix < 0) + { + warnPix = 0; + } + else if (warnPix > width) + { + warnPix = width; + } + if (valPix < 0) + { + valPix = 0; + } + else if (valPix > width) + { + valPix = width; + } + + if (warnPix != previousFull || valPix != previousFill) + { + // + // Zone 1 [0, warnPix): the striped tile. + // + L4Warehouse + *warehouse = (L4Warehouse *)renderer->warehousePointer; + BitMap + *tile = warehouse->bitMapBin.Get(tileImage); + localView.SetColor(fillColor); // @004c443a ([0xA0]) + DrawTiledBitmap(&localView, 0, 0, warnPix - 1, height, backgroundColor, tile); + warehouse->bitMapBin.Release(tileImage); + + // + // Zone 2 [warnPix, valPix): the over-threshold fill (the binary + // keeps the current colour, still fillColor from zone 1) -- only + // when value > low. + // + if (valPix > warnPix) + { + localView.SetColor(fillColor); + localView.MoveToAbsolute(warnPix, 0); + localView.DrawFilledRectangleToAbsolute(valPix - 1, height); + } + + // + // Zone 3 [valPix, width): the unfilled remainder -- backgroundColor. + // + if (valPix < width) + { + localView.SetColor(backgroundColor); // @004c44da ([0xA4]) + localView.MoveToAbsolute(valPix, 0); + localView.DrawFilledRectangleToAbsolute(width, height); + } + + previousFull = warnPix; + previousFill = valPix; + } + Check_Fpu(); +} + +//########################################################################### +// VertTwoPartBar @004c462c Make / @004c4724 ctor / @004c48e0 BecameActive +// @004c48fc Execute (vtable PTR_FUN_00518c78) +// +// A vertical two-part fill bar (config keyword "vertBar"). Three Scalar +// connections drive it: value (current), low (warn threshold), high (max). +// The bar grows bottom->top; pixels are value/high and low/high fractions +// of the bar height. Used for the cockpit COOLANT bars (config binds +// current=CoolantMass, warn=CoolantCapacity, max=CoolantCapacity -- so the +// warn line sits at full and the bar simply empties as CoolantMass drops). +//########################################################################### + +MethodDescription + VertTwoPartBar::methodDescription = + { + "vertBar", + VertTwoPartBar::Make, + { + // + // CFG shape (L4GAUGE.CFG:4521): + // vertBar( rate, mode, (w,h), tile.pcc, bg,fill,extra, + // currentAttr, warnAttr, maxAttr ) + // e.g. vertBar(C,ModeAlwaysActive,(31,160),btwarn.pcc,255,255,0, + // HeatSink/CoolantMass, HeatSink/CoolantCapacity, + // HeatSink/CoolantCapacity); + // + { ParameterDescription::typeRate, NULL }, // rate ID + { ParameterDescription::typeModeMask, NULL }, // mode mask + { ParameterDescription::typeVector, NULL }, // (width,height) + { ParameterDescription::typeString, NULL }, // tile bitmap name + { ParameterDescription::typeColor, NULL }, // background colour -> @0x94 + { ParameterDescription::typeColor, NULL }, // fill colour -> @0x98 + { ParameterDescription::typeColor, NULL }, // extra/empty colour-> @0x9C + { ParameterDescription::typeAttribute, NULL }, // current value -> @0xB0 + { ParameterDescription::typeAttribute, NULL }, // warn threshold -> @0xB4 + { ParameterDescription::typeAttribute, NULL }, // max -> @0xB8 + PARAMETER_DESCRIPTION_END + } + }; + +// +// @004c462c -- Make. Construct, then verify the tile bitmap exists (== the +// binary's "VertTwoPartBarNormalized: Missing image" warning path). +// +Logical + VertTwoPartBar::Make( + int display_port_index, + Vector2DOf position, + Entity * /*entity -- unused*/, + GaugeRenderer *gauge_renderer + ) +{ + ParameterDescription + *parameterList = methodDescription.parameterList; + + Check(gauge_renderer); + +# if DEBUG_LEVEL > 0 + VertTwoPartBar + *gauge = +# endif + new VertTwoPartBar( // FUN_004c4724 + parameterList[0].data.rate, + parameterList[1].data.modeMask, + (L4GaugeRenderer *)gauge_renderer, + display_port_index, // graphics_port_number + position.x, position.y, // left, bottom + position.x + parameterList[2].data.vector.x, // right = x + width + position.y + parameterList[2].data.vector.y, // top = y + height + parameterList[3].data.string, // tile bitmap + parameterList[4].data.color, // backgroundColor + parameterList[5].data.color, // fillColor + parameterList[6].data.color, // extraColor + (Scalar *)parameterList[7].data.attributePointer, // current value + (Scalar *)parameterList[8].data.attributePointer, // warn threshold + (Scalar *)parameterList[9].data.attributePointer, // max + + // HACK!!!! The (Scalar *) is VERY dangerous! + "VertTwoPartBar"); + Register_Object(gauge); + + //-------------------------------------------------------- + // Make sure the tile was properly placed in the warehouse + //-------------------------------------------------------- + L4Warehouse + *warehouse = (L4Warehouse *)gauge_renderer->warehousePointer; + Check(warehouse); + + if (warehouse->bitMapBin.Get(parameterList[3].data.string) == NULL) + { + DEBUG_STREAM << "VertTwoPartBarNormalized: Missing image '" + << parameterList[3].data.string << "'\n"; + return False; + } + warehouse->bitMapBin.Release(parameterList[3].data.string); + + Check_Fpu(); + return True; +} + +// +// @004c4724 -- ctor. GraphicGauge base; intern + ref-count the tile bitmap; +// set the view extent (bottom->top); store the three colours and the bar +// size; wire three GaugeConnectionDirectOf feeds (value/low/high). +// +VertTwoPartBar::VertTwoPartBar( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer_in, + int graphics_port_number, + int left, + int bottom, + int right, + int top, + const char *tile_image, + int background_color, + int fill_color, + int extra_color, + Scalar *value_pointer, + Scalar *low_pointer, + Scalar *high_pointer, + const char *identification_string +): + GraphicGauge(rate, mode_mask, renderer_in, 0, // FUN_00444818 (owner_ID 0) + graphics_port_number, identification_string) +{ + Check_Pointer(this); + + // + // Own a copy of the tile-bitmap name (Make passes the transient + // interpreter scratch buffer) and hold a ref for the gauge's life. + // + tileImage = nameCopy(tile_image); // @0x90 + ((L4Warehouse *)renderer_in->warehousePointer)-> + bitMapBin.Get(tileImage); + + localView.SetPositionWithinPort(left, bottom, right, top); // this+0x48 + + backgroundColor = background_color; // @0x94 this[0x25] + fillColor = fill_color; // @0x98 this[0x26] + extraColor = extra_color; // @0x9C this[0x27] + width = (right - left) - 1; // @0xA0 this[0x28] + height = top - bottom; // @0xA4 this[0x29] + + GaugeConnection + *connection; + + connection = new GaugeConnectionDirectOf(0, &value, value_pointer); // @0xB0 + Check(connection); + Register_Object(connection); + AddConnection(connection); + + connection = new GaugeConnectionDirectOf(0, &low, low_pointer); // @0xB4 + Check(connection); + Register_Object(connection); + AddConnection(connection); + + connection = new GaugeConnectionDirectOf(0, &high, high_pointer); // @0xB8 + Check(connection); + Register_Object(connection); + AddConnection(connection); + + Check_Fpu(); +} + +// +// @004c486c -- dtor. Release the tile ref (keyed on tileImage, before the +// free), free the interned name; the base chain runs implicitly. +// +VertTwoPartBar::~VertTwoPartBar() +{ + Check(this); + ((L4Warehouse *)renderer->warehousePointer)-> + bitMapBin.Release(tileImage); + Unregister_Pointer(tileImage); + delete[] tileImage; + tileImage = NULL; + Check_Fpu(); +} + +Logical + VertTwoPartBar::TestInstance() const +{ + return GraphicGauge::TestInstance(); +} + +// +// @004c48e0 -- BecameActive: force a full redraw on the first Execute +// (previousFull=0, previousFill=width -- neither can match a real pair). +// +void + VertTwoPartBar::BecameActive() +{ + Check(this); + previousFull = 0; // @0xAC this[0x2B] + previousFill = width; // @0xA8 this[0x2A] +} + +// +// @004c48fc -- Execute. Clamp value to [0,high]; map value and warn to bar +// pixels (half-up round of height*x/high, clamped to [0,height]); if either +// changed, repaint: tile the bitmap over [0,warnPix), fill [warnPix,valPix) +// with fillColor, and clear [valPix,height) with extraColor. (x87 pixel +// math recovered by disassembly @004c4940/@004c4960.) +// +void + VertTwoPartBar::Execute() +{ + Check(this); + + // clamp value to [0, high] (@004c4908 fcomp 0.0 ; @004c4929 fcomp high) + if (value < ZeroClamp) + { + value = 0.0f; + } + else if (value > high) + { + value = high; + } + + int + warnPix = (int)((Scalar)height * low / high + 0.5f), // warn threshold pixel + valPix = (int)((Scalar)height * value / high + 0.5f); // current value pixel + if (warnPix < 0) + { + warnPix = 0; + } + else if (warnPix > height) + { + warnPix = height; + } + if (valPix < 0) + { + valPix = 0; + } + else if (valPix > height) + { + valPix = height; + } + + if (warnPix != previousFull || valPix != previousFill) + { + L4Warehouse + *warehouse = (L4Warehouse *)renderer->warehousePointer; + BitMap + *tile = warehouse->bitMapBin.Get(tileImage); + + localView.SetColor(backgroundColor); + DrawTiledBitmap(&localView, 0, 0, width, warnPix - 1, extraColor, tile); + warehouse->bitMapBin.Release(tileImage); + + if (warnPix < valPix) // normal fill above the warn line + { + localView.SetColor(fillColor); + localView.MoveToAbsolute(0, warnPix); + localView.DrawFilledRectangleToAbsolute(width, valPix - 1); + } + if (valPix < height) // empty region above the value + { + localView.SetColor(extraColor); + localView.MoveToAbsolute(0, valPix); + localView.DrawFilledRectangleToAbsolute(width, height); + } + + previousFull = warnPix; + previousFill = valPix; + } + Check_Fpu(); +} + +//########################################################################### +// VertNormalSlider @004c4b08 Make / @004c4b84 ctor / @004c4cc0 Execute +// (vtable PTR_FUN_00518c34) +// +// The condenser VALVE slider (config keyword "vertNormalSlider", the @2 +// widget in GenericHeatGauges1/2). One GaugeConnectionDirectOf +// drives a normalised [0,1] value; Execute maps it to row = +// Round(span*value) and XOR-toggles a width x baseline indicator. +//########################################################################### + +// CFG shape (L4GAUGE.CFG:4839): +// vertNormalSlider(rate,mode,(w,h),fgColor,bgColor,thickness,valveAttr) +MethodDescription + VertNormalSlider::methodDescription = + { + "vertNormalSlider", + VertNormalSlider::Make, + { + { ParameterDescription::typeRate, NULL }, // rate ID + { ParameterDescription::typeModeMask, NULL }, // mode mask + { ParameterDescription::typeVector, NULL }, // (width,height) + { ParameterDescription::typeColor, NULL }, // fill / foreground colour + { ParameterDescription::typeColor, NULL }, // background colour + { ParameterDescription::typeInteger, NULL }, // baseline (indicator thickness) + { ParameterDescription::typeAttribute, NULL }, // value source (Condenser/ValveSetting, [0,1]) + PARAMETER_DESCRIPTION_END + } + }; + +// +// @004c4b08 -- Make. No owned bitmap, so no image-exists check; always +// returns True (the binary returns 1 even on allocation failure). `entity` +// unused (binds by the attribute pointer already resolved into +// parameterList[6]). +// +Logical + VertNormalSlider::Make( + int display_port_index, + Vector2DOf position, + Entity * /*entity -- unused*/, + GaugeRenderer *gauge_renderer + ) +{ + ParameterDescription + *parameterList = methodDescription.parameterList; + + Check(gauge_renderer); + +# if DEBUG_LEVEL > 0 + VertNormalSlider + *gauge = +# endif + new VertNormalSlider( // FUN_004c4b84 + parameterList[0].data.rate, + parameterList[1].data.modeMask, + (L4GaugeRenderer *)gauge_renderer, + display_port_index, // graphics_port_number + position.x, position.y, // left, bottom + position.x + parameterList[2].data.vector.x, // right + position.y + parameterList[2].data.vector.y, // top + parameterList[3].data.color, // fill / foreground colour + parameterList[4].data.color, // background colour + parameterList[5].data.integer, // baseline (indicator thickness) + (Scalar *)parameterList[6].data.attributePointer, // Condenser/ValveSetting + "VertNormalSlider"); + Register_Object(gauge); + + Check_Fpu(); + return True; +} + +// +// @004c4b84 -- ctor (vtable PTR_FUN_00518c34). GraphicGauge base; set the +// view extent (bottom->top), latch the raster op to XOR + the pen to the +// fill colour ONCE (so every Draw toggles the indicator in place), cache +// geometry, wire one Scalar connection. previousFill is NOT set here -- +// BecameActive sets it to -1. +// +VertNormalSlider::VertNormalSlider( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer_in, + int graphics_port_number, + int left, + int bottom, + int right, + int top, + int fill_color, + int background_color, + int baseline_in, + Scalar *value_pointer, + const char *identification_string +): + GraphicGauge(rate, mode_mask, renderer_in, 0, // FUN_00444818 (owner_ID 0) + graphics_port_number, identification_string) +{ + Check_Pointer(this); + + localView.SetPositionWithinPort(left, bottom, right, top); + localView.SetOperation(GraphicsDisplay::Xor); // op == 3 + localView.SetColor(fill_color); + + fillColor = fill_color; // @0xA4 + backgroundColor = background_color; // @0xA8 + baseline = baseline_in; // @0x9C + width = (right - left) - 1; // @0x94 + height = top - bottom; // @0x98 + span = (top - bottom) - baseline_in; // @0xA0 + + GaugeConnection + *connection; + + connection = new GaugeConnectionDirectOf(0, &value, value_pointer); // -> @0xAC + Check(connection); + Register_Object(connection); + AddConnection(connection); + + Check_Fpu(); +} + +// +// @004c4c68 -- dtor. Owns no resource; the connection + localView are +// released by the base teardown, so the body is empty. +// +VertNormalSlider::~VertNormalSlider() +{ +} + +// +// @004c4c94 -- TestInstance. Out-of-line forward to the base. +// +Logical + VertNormalSlider::TestInstance() const +{ + return GraphicGauge::TestInstance(); +} + +// +// @004c4cac -- BecameActive. Invalidate the cached row so the first +// Execute repaints. +// +void + VertNormalSlider::BecameActive() +{ + Check(this); + previousFill = -1; // @0x90 +} + +// +// @004c4cc0 -- Execute. Clamp value to [0,1], map to row = +// Round(span*value), and on a change XOR-erase the old indicator + +// XOR-draw the new one. +// +void + VertNormalSlider::Execute() +{ + Check(this); + + if (value < ZeroClamp) // _DAT_004c4d3c + { + value = 0.0f; + } + else if (value > OneClamp) // _DAT_004c4d40 + { + value = 1.0f; + } + + int + pixel = Round((Scalar)span * value); // FUN_004dcd94 + + if (pixel != previousFill) + { + Draw(); // erase old (XOR at old previousFill) + previousFill = pixel; + Draw(); // draw new (XOR at new previousFill) + } + Check_Fpu(); +} + +// +// @004c4d48 -- Draw (file-private, non-virtual). Move the pen to the +// track's left edge at the current row, draw a width x baseline filled +// rectangle (XOR op + fill colour latched in the ctor, so a re-Draw at the +// same row erases). +// +void + VertNormalSlider::Draw() +{ + if (previousFill >= 0) + { + localView.MoveToAbsolute(0, previousFill); + localView.DrawFilledRectangleToRelative(width, baseline); + } +} + + +//########################################################################### +//########################################################################### +// OneOfSeveral family -- multi-frame bitmap selectors +//########################################################################### +//########################################################################### + +// +// @004c4d88 -- OneOfSeveral base ctor (vtable PTR_FUN_00518bf0). Stores the +// strip-vs-pixmap flag, interns the image name + holds a ref in the matching +// warehouse bin (pixelMap8Bin when !fromImageStrip, else bitMapBin), +// computes the per-frame width/height by dividing the source size by the +// column/row counts, and sets the view origin. +// +OneOfSeveral::OneOfSeveral( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer_in, + int graphics_port_number, + int x, + int y, + Logical from_image_strip, + const char *image_name, + int background_color, + int foreground_color, + int columns_in, + int rows_in, + const char *identification_string +): + GraphicGauge(rate, mode_mask, renderer_in, 0, // FUN_00444818 (owner_ID 0) + graphics_port_number, identification_string) +{ + Check_Pointer(this); + + backgroundColor = background_color; // @0x98 + foregroundColor = foreground_color; // @0x9C + columns = columns_in; // @0xA0 + fromImageStrip = from_image_strip; // @0x90 + + imageName = nameCopy(image_name); // @0x94 + + L4Warehouse + *warehouse = (L4Warehouse *)renderer_in->warehousePointer; + if (fromImageStrip == 0) // PixelMap8 strip + { + PixelMap8 + *pixel_map = warehouse->pixelMap8Bin.Get(imageName); // held + if (pixel_map == NULL) + { + frameWidth = 0; + frameHeight = 0; + } + else + { + frameWidth = pixel_map->Data.Size.x / columns_in; // @0xA4 + frameHeight = pixel_map->Data.Size.y / rows_in; // @0xA8 + } + } + else // BitMap strip + { + BitMap + *bit_map = warehouse->bitMapBin.Get(imageName); // held + if (bit_map == NULL) + { + frameWidth = 0; + frameHeight = 0; + } + else + { + frameWidth = bit_map->Data.Size.x / columns_in; + frameHeight = bit_map->Data.Size.y / rows_in; + } + } + + localView.SetOrigin(x, y); // this+0x48 + + Check_Fpu(); +} + +// +// @004c4e7c -- dtor. Release the image from the matching bin (keyed on +// imageName, before the free); base chain implicit. +// +OneOfSeveral::~OneOfSeveral() +{ + Check(this); + + L4Warehouse + *warehouse = (L4Warehouse *)renderer->warehousePointer; + if (fromImageStrip == 0) + { + warehouse->pixelMap8Bin.Release(imageName); + } + else + { + warehouse->bitMapBin.Release(imageName); + } + Unregister_Pointer(imageName); + delete[] imageName; + imageName = NULL; + Check_Fpu(); +} + +Logical + OneOfSeveral::TestInstance() const +{ + return GraphicGauge::TestInstance(); +} + +// +// @004c4f14 -- BecameActive: previousSelected = -1 (force the first redraw). +// +void + OneOfSeveral::BecameActive() +{ + Check(this); + previousSelected = -1; // @0xB0 +} + +// +// @004c4f28 -- Execute. On a change of `selected`, compute the frame's +// (col,row) within the strip and blit that sub-rectangle: DrawPixelMap8 +// (opaque) for a pixmap strip, or SetColor(bg) + DrawBitMapOpaque(fg,...) +// for a bitmap strip. +// +void + OneOfSeveral::Execute() +{ + Check(this); + + if (selected == previousSelected) + { + return; + } + previousSelected = selected; + + int + col, + row; + if (columns == 1) + { + col = 0; + row = selected; + } + else + { + col = selected % columns; + row = selected / columns; + } + + int + sx0 = col * frameWidth, + sx1 = frameWidth + sx0 - 1, + sy0 = row * frameHeight, + sy1 = frameHeight + sy0 - 1; + + L4Warehouse + *warehouse = (L4Warehouse *)renderer->warehousePointer; + if (fromImageStrip == 0) // PixelMap8: own palette, no SetColor + { + PixelMap8 + *pixel_map = warehouse->pixelMap8Bin.Get(imageName); + localView.DrawPixelMap8(True, 0, pixel_map, sx0, sy0, sx1, sy1); + warehouse->pixelMap8Bin.Release(imageName); + } + else // BitMap strip + { + localView.SetColor(backgroundColor); + BitMap + *bit_map = warehouse->bitMapBin.Get(imageName); + localView.DrawBitMapOpaque(foregroundColor, 0, bit_map, sx0, sy0, sx1, sy1); + warehouse->bitMapBin.Release(imageName); + } + Check_Fpu(); +} + +//########################################################################### +// OneOfSeveralInt @004c5148 ctor / @004c51d8 dtor +// (vtable PTR_FUN_00518bac; cluster-built) +// +// OneOfSeveral(fromImageStrip=True) + one GaugeConnectionDirectOf +// feeding the selected frame. +//########################################################################### + +OneOfSeveralInt::OneOfSeveralInt( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer_in, + int graphics_port_number, + int x, + int y, + const char *image, + int columns, + int rows, + int *value_pointer, + const char *identification_string +): + OneOfSeveral(rate, mode_mask, renderer_in, graphics_port_number, x, y, + True, image, 0, 0, columns, rows, identification_string) +{ + Check_Pointer(this); + + selected = 0; + + GaugeConnection + *connection; + + connection = new GaugeConnectionDirectOf(0, &selected, value_pointer); // FUN_004749de + Check(connection); + Register_Object(connection); + AddConnection(connection); + + Check_Fpu(); +} + +OneOfSeveralInt::~OneOfSeveralInt() // @004c51d8 +{ +} + +//########################################################################### +// OneOfSeveralPixInt @004c5204 Make / @004c52d8 ctor +// (vtable PTR_FUN_00518b68) +// +// The cockpit button-state lamps (config "oneOfSeveralPixInt"): a PixelMap8 +// strip of N frames selected by an integer game attribute (DuckState / +// Searchlight/LightOn / ControlsMapper/DisplayMode). Drawing is inherited +// from OneOfSeveral; this subclass just forces the pixmap path and wires +// the int connection. +//########################################################################### + +MethodDescription + OneOfSeveralPixInt::methodDescription = + { + "oneOfSeveralPixInt", + OneOfSeveralPixInt::Make, + { + // + // CFG shape (L4GAUGE.CFG:5001): + // oneOfSeveralPixInt( rate, mode, strip.pcc, columns, rows, stateAttr ) + // e.g. oneOfSeveralPixInt(E,ModeAlwaysActive,bduck.pcc,3,1,DuckState); + // + { ParameterDescription::typeRate, NULL }, // rate ID + { ParameterDescription::typeModeMask, NULL }, // mode mask + { ParameterDescription::typeString, NULL }, // pixmap strip name + { ParameterDescription::typeInteger, NULL }, // columns (frame count) + { ParameterDescription::typeInteger, NULL }, // rows + { ParameterDescription::typeAttribute, NULL }, // integer state selector + PARAMETER_DESCRIPTION_END + } + }; + +// +// @004c5204 -- Make. Construct, then verify the pixmap strip exists. +// +Logical + OneOfSeveralPixInt::Make( + int display_port_index, + Vector2DOf position, + Entity * /*entity -- unused*/, + GaugeRenderer *gauge_renderer + ) +{ + ParameterDescription + *parameterList = methodDescription.parameterList; + + Check(gauge_renderer); + +# if DEBUG_LEVEL > 0 + OneOfSeveralPixInt + *gauge = +# endif + new OneOfSeveralPixInt( // FUN_004c52d8 + parameterList[0].data.rate, + parameterList[1].data.modeMask, + (L4GaugeRenderer *)gauge_renderer, + display_port_index, // graphics_port_number + position.x, position.y, + parameterList[2].data.string, // pixmap strip + parameterList[3].data.integer, // columns + parameterList[4].data.integer, // rows + (int *)parameterList[5].data.attributePointer, // integer state source + "OneOfSeveralPixInt"); + Register_Object(gauge); + + L4Warehouse + *warehouse = (L4Warehouse *)gauge_renderer->warehousePointer; + if (warehouse->pixelMap8Bin.Get(parameterList[2].data.string) == NULL) + { + DEBUG_STREAM << "OneOfSeveralPixInt: Missing image '" + << parameterList[2].data.string << "'\n"; + return False; + } + warehouse->pixelMap8Bin.Release(parameterList[2].data.string); + + Check_Fpu(); + return True; +} + +// +// @004c52d8 -- ctor. OneOfSeveral base with fromImageStrip=False (PixelMap8) +// and bg/fg=0 (the pixmap carries its own palette); one int connection -> +// selected. +// +OneOfSeveralPixInt::OneOfSeveralPixInt( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer_in, + int graphics_port_number, + int x, + int y, + const char *image, + int columns_in, + int rows_in, + int *value_pointer, + const char *identification_string +): + OneOfSeveral(rate, mode_mask, renderer_in, graphics_port_number, x, y, + False, // fromImageStrip = 0 (pixmap) + image, 0, 0, // bg, fg = 0 + columns_in, rows_in, identification_string) +{ + Check_Pointer(this); + + selected = 0; // @0xAC this[0x2B] + + GaugeConnection + *connection; + + connection = new GaugeConnectionDirectOf(0, &selected, value_pointer); // FUN_004749de + Check(connection); + Register_Object(connection); + AddConnection(connection); + + Check_Fpu(); +} + +// +// @004c5364 -- deleting-dtor thunk. All teardown is ~OneOfSeveral; implicit. +// +OneOfSeveralPixInt::~OneOfSeveralPixInt() +{ +} + +//########################################################################### +// OneOfSeveralStates @004c5470 ctor / @004c5500 dtor / @004c552c +// BecameActive (vtable PTR_FUN_00518b24; cluster-built) +// +// OneOfSeveral + a StateConnection reading the subsystem's simulation state. +//########################################################################### + +OneOfSeveralStates::OneOfSeveralStates( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer_in, + int graphics_port_number, + int x, + int y, + const char *image, + int columns, + int rows, + Subsystem *subsystem_source, + const char *identification_string +): + OneOfSeveral(rate, mode_mask, renderer_in, graphics_port_number, x, y, + True, image, 0, 0, columns, rows, identification_string) +{ + Check_Pointer(this); + + selected = 0; + + GaugeConnection + *connection; + + connection = new StateConnection(&selected, subsystem_source); // @004c3324 + Check(connection); + Register_Object(connection); + AddConnection(connection); + + Check_Fpu(); +} + +OneOfSeveralStates::~OneOfSeveralStates() // @004c5500 +{ +} + +// +// @004c552c -- BecameActive: clamp the state >= 0, then the base +// invalidation (force the first redraw). +// +void + OneOfSeveralStates::BecameActive() +{ + Check(this); + if (selected < 0) + { + selected = 0; + } + OneOfSeveral::BecameActive(); +} + + +//########################################################################### +//########################################################################### +// HeadingPointer @004c554c Make / @004c562c ctor +//########################################################################### +//########################################################################### +// +// Reconstructed from the binary (raw pseudo-C part_013.c:14560-14791 + a +// disassembly of Execute @004c5914 and ctor @004c562c to recover the x87 +// endpoint arithmetic Ghidra dropped). The compass is a radial NEEDLE (a +// thick line from innerRadius to outerRadius at the heading angle) plus a +// NumericDisplay showing the heading in whole degrees. +// + +MethodDescription + HeadingPointer::methodDescription = + { + "headingPointer", + HeadingPointer::Make, + { + // + // CFG shape (L4GAUGE.CFG:4941): + // headingPointer( rate, mode, needleColor, numericFg, eraseColor, + // innerRadius, outerRadius, font ) + // e.g. headingPointer(E, ModeAlwaysActive, 2,1,0, 20,39, helv15.pcc); + // + { ParameterDescription::typeRate, NULL }, // rate ID + { ParameterDescription::typeModeMask, NULL }, // mode mask + { ParameterDescription::typeColor, NULL }, // needle colour -> @0x90 + { ParameterDescription::typeColor, NULL }, // numeric fg -> NumericDisplay + { ParameterDescription::typeColor, NULL }, // erase/bg -> @0x94 + ND bg + { ParameterDescription::typeInteger, NULL }, // inner radius -> @0x98 + { ParameterDescription::typeInteger, NULL }, // outer radius -> @0x9C + { ParameterDescription::typeString, NULL }, // font name + PARAMETER_DESCRIPTION_END + } + }; + +// +// @004c554c -- Make. Construct the gauge, then probe that the glyph font +// exists (== NumericDisplayScalar::Make, L4GAUGE.CPP:626-650); the readout +// cannot draw without it. +// +Logical + HeadingPointer::Make( + int display_port_index, + Vector2DOf position, + Entity * /*entity -- unused*/, + GaugeRenderer *gauge_renderer + ) +{ + ParameterDescription + *parameterList = methodDescription.parameterList; + + Check(gauge_renderer); + +# if DEBUG_LEVEL > 0 + HeadingPointer + *gauge = +# endif + new HeadingPointer( // FUN_004c562c + parameterList[0].data.rate, + parameterList[1].data.modeMask, + (L4GaugeRenderer *)gauge_renderer, + 0, // owner_ID + display_port_index, // graphics port number + position.x, position.y, + parameterList[2].data.color, // needle colour + parameterList[3].data.color, // numeric fg colour + parameterList[4].data.color, // erase/bg colour + parameterList[5].data.integer, // inner radius + parameterList[6].data.integer, // outer radius (tip) + parameterList[7].data.string, // font name + "HeadingPointer"); + Register_Object(gauge); + + L4Warehouse + *warehouse = (L4Warehouse *)gauge_renderer->warehousePointer; + if (warehouse->bitMapBin.Get(parameterList[7].data.string) == NULL) + { + DEBUG_STREAM << "HeadingPointer: missing font '" + << parameterList[7].data.string << "'\n"; + return False; + } + warehouse->bitMapBin.Release(parameterList[7].data.string); + + Check_Fpu(); + return True; +} + +// +// @004c562c -- ctor (vtable PTR_FUN_00518ae0). GraphicGauge base; the +// embedded GraphicsViewRecord (previousDrawing) default-constructs as a +// member (== the binary's FUN_0044a5b4). Set the view origin, store the +// colours/radii, hold a ref to the glyph font, and build an owned +// NumericDisplay centred on the compass mid-point (half the 3-digit glyph +// extent). +// +HeadingPointer::HeadingPointer( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer_in, + unsigned int owner_ID, + int graphics_port_number, + int x, + int y, + int needle_color, + int numeric_color, + int erase_color, + int inner_radius, + int outer_radius, + const char *font_name, + const char *identification_string +): + GraphicGauge(rate, mode_mask, renderer_in, owner_ID, // FUN_00444818 + graphics_port_number, identification_string) +{ + Check_Pointer(this); + + localView.SetOrigin(x, y); // this+0x48 + + needleColor = needle_color; // @0x90 + eraseColor = erase_color; // @0x94 + innerRadius = inner_radius; // @0x98 + outerRadius = outer_radius; // @0x9C + + // + // Own a copy of the font name (in Make it is the transient interpreter + // scratch buffer, so it MUST be copied). + // + imageName = nameCopy(font_name); // @0xA0 + + L4Warehouse + *warehouse = (L4Warehouse *)renderer_in->warehousePointer; + BitMap + *font = warehouse->bitMapBin.Get(imageName); // held for the gauge's life + int + fontW = (font != NULL) ? font->Data.Size.x : 0, + fontH = (font != NULL) ? font->Data.Size.y : 0; + + // 14 == NumericDisplay::totalDigitsPerFont; centre the 3-digit readout + int + halfW = ((fontW / NumericDisplay::totalDigitsPerFont) * 3) / 2, + halfH = fontH / 2; + + numericDisplay = new NumericDisplay( // @0xBC FUN_004700bc + warehouse, + -halfW, -halfH, // centre on the compass mid-point + imageName, + 3, // number_of_digits + NumericDisplay::unsignedFormat, // format 0 + erase_color, // background_color + numeric_color); // foreground_color + Register_Object(numericDisplay); + + Check_Fpu(); +} + +// +// @004c573c -- dtor. Release the font ref (keyed on imageName, so before +// the free), free imageName, delete the NumericDisplay. The base-dtor +// chain (~GraphicsViewRecord on previousDrawing, then ~GraphicGauge) runs +// implicitly. +// +HeadingPointer::~HeadingPointer() +{ + Check(this); + + ((L4Warehouse *)renderer->warehousePointer)-> + bitMapBin.Release(imageName); // drop the ctor's Get ref + Unregister_Pointer(imageName); + delete[] imageName; + imageName = NULL; + + Unregister_Object(numericDisplay); + delete numericDisplay; // FUN_0047018c + numericDisplay = NULL; + + Check_Fpu(); +} + +Logical + HeadingPointer::TestInstance() const +{ + return GraphicGauge::TestInstance(); +} + +// +// @004c57d0 -- ShowInstance (debug dump; bgColor printed first, per the +// recovered order). +// +void + HeadingPointer::ShowInstance(char *indent) +{ + Check(this); + + cout << indent << "HeadingPointer:\n"; + + char + temp[80]; + + Str_Copy(temp, indent, 80); + Str_Cat(temp, "...", 80); + + cout << temp << "bgColor=" << eraseColor << "\n"; // @0x94 + cout << temp << "fgColor=" << needleColor << "\n"; // @0x90 + GraphicGauge::ShowInstance(temp); + Check_Fpu(); +} + +// +// @004c58e8 -- BecameActive: force a full redraw + reset the readout. +// +void + HeadingPointer::BecameActive() +{ + Check(this); + previousX = -999; // 0xfffffc19 -- guaranteed != any real endpoint + previousY = -999; + numericDisplay->ForceUpdate(); // FUN_004703f4 +} + +// +// @004c5914 -- Execute. Read the linked mech's heading from its orientation +// quaternion, draw the needle as a radial line innerRadius..outerRadius at +// that angle (recording the strokes so the next frame can erase them), and +// update the numeric readout. Endpoint rounding is half-up: (int)(v+0.5f), +// matching the binary's `fadd 0.5 ; _ftol`. +// +// HEADING DECOMPOSITION (deviation on record): the binary read an +// EulerAngles component (FUN_0040954c), but the EulerAngles(Quaternion) +// decomposition is ambiguous for a yawing mech -- as the yaw sweeps past +// +/-pi the quaternion double-cover flips it to a pitch=roll=pi branch and +// euler.yaw jumps discontinuously (the needle spins erratically). +// YawPitchRoll (ROTATION.HPP:162) applies yaw FIRST, so its .yaw is the +// clean, continuous heading with pitch=roll~0. Kept per the decode +// worksheet (correctness-load-bearing). +// +void + HeadingPointer::Execute() +{ + Check(this); + + Entity + *owner = renderer->GetLinkedEntity(); // the renderer's entity socket + if (owner == NULL) + { + return; + } + + YawPitchRoll + ypr; + ypr = owner->localOrigin.angularPosition; // the orientation quaternion + Scalar + heading = -(Scalar)ypr.yaw; // negate for the needle (as the binary did) + + SinCosPair + sc; + sc = Radian(heading); // FUN_00408328 -- sin/cos of the heading + + // Needle endpoints: a radial line innerRadius..outerRadius along the heading. + int + ax = (int)((Scalar)innerRadius * sc.sine + 0.5f), + ay = (int)((Scalar)innerRadius * sc.cosine + 0.5f), + bx = (int)((Scalar)outerRadius * sc.sine + 0.5f), + by = (int)((Scalar)outerRadius * sc.cosine + 0.5f); + + if (bx != previousX || by != previousY) // redraw only when the tip moves + { + previousX = bx; + previousY = by; + previousDrawing.Draw(&localView, eraseColor); // erase the last needle + previousDrawing.Clear(); + localView.AttachRecorder(&previousDrawing); // record the new strokes + localView.SetColor(needleColor); + localView.MoveToAbsolute(ax, ay); + localView.DrawThickLineToAbsolute(bx, by); + localView.DetachRecorder(); + } + + // Numeric heading readout: whole degrees, normalized, shown as (360-deg). + int + deg = (int)((Scalar)ypr.yaw * RadiansToDegrees + 0.5f); // 0x42652ee1 + if ((Scalar)deg < ZeroClamp) // _DAT_004c5acc + { + deg = (int)((Scalar)deg + 360.0f); + } + int + shown = (int)(360.0f - (Scalar)deg); + numericDisplay->Draw(&localView, (Scalar)shown); // FUN_00470430 + + Check_Fpu(); +} + + +//########################################################################### +//########################################################################### +// BitMap wipe gauges +//########################################################################### +//########################################################################### +// +// NOT RECONSTRUCTED (notes only, from the same address range): +// @004c5e84 ctor / @004c5f30 dtor / @004c5fa4 BecameActive / @004c5fb8 +// Execute -- a second wipe base (vtable PTR_FUN_00518a58): a two-segment +// reveal of one strip image; frame size read from the bitmap. +// @004c61c8 ctor (vtable PTR_FUN_00518a14) -- its Scalar-driven variant +// ("BitMapInverseWipeScalar"): chains @004c5e84 then AddConnection(new +// GaugeConnectionDirectOf). Deleting-dtor thunk @004c66d9. +// btl4gau2's BallisticWeaponCluster ejectWipe needs the Scalar variant -- +// still an open bring-up NULL there. +// + +//########################################################################### +// BitMapInverseWipe @004c5b7c ctor / @004c5c80 dtor / @004c5cf4 +// BecameActive / @004c5d08 Execute +// (vtable PTR_FUN_00518a9c; name unconfirmed) +// +// The coolant LeakGauge (config keyword "LeakGauge"): a 3-frame strip +// (frameWidth = imageWidth/3) revealed in steps. CFG shape +// (L4GAUGE.CFG:4858): +// LeakGauge(rate,mode,image.pcc,colorA,colorB,frames,third,levelAttr) +// value source @6 = Condenser/CoolantMassLeakRate. +//########################################################################### + +MethodDescription + BitMapInverseWipe::methodDescription = + { + "LeakGauge", + BitMapInverseWipe::Make, + { + { ParameterDescription::typeRate, NULL }, // rate ID + { ParameterDescription::typeModeMask, NULL }, // mode mask + { ParameterDescription::typeString, NULL }, // leak strip bitmap -> @0x90 + { ParameterDescription::typeColor, NULL }, // colorA empty-cell -> @0x94 + { ParameterDescription::typeColor, NULL }, // colorB leak-cell -> @0x98 + { ParameterDescription::typeInteger, NULL }, // frames (=3) -> frames/fullWidth + { ParameterDescription::typeScalar, NULL }, // third (.15; raw @0xB0, inert in Execute) + { ParameterDescription::typeAttribute, NULL }, // level source (Scalar CoolantMassLeakRate) + PARAMETER_DESCRIPTION_END + } + }; + +Logical + BitMapInverseWipe::Make( + int display_port_index, + Vector2DOf position, + Entity * /*entity -- unused*/, + GaugeRenderer *gauge_renderer + ) +{ + ParameterDescription + *parameterList = methodDescription.parameterList; + + Check(gauge_renderer); + + // + // NOTE the ctor lists `third` BEFORE `frames`, so map parameter 6 -> + // third and parameter 5 -> frames BY NAME (parse-order != arg-order). + // +# if DEBUG_LEVEL > 0 + BitMapInverseWipe + *gauge = +# endif + new BitMapInverseWipe( // FUN_004c5b7c + parameterList[0].data.rate, + parameterList[1].data.modeMask, + (L4GaugeRenderer *)gauge_renderer, + display_port_index, // graphics_port_number + position.x, position.y, // -> localView.SetOrigin(x,y) + parameterList[2].data.string, // image (eleak.pcc) + parameterList[3].data.color, // color_a + parameterList[4].data.color, // color_b + parameterList[6].data.integer, // third (.15 raw bits; inert) -> @0xB0 + parameterList[5].data.integer, // frames (3) + (Scalar *)parameterList[7].data.attributePointer, // level source + "LeakGauge"); + Register_Object(gauge); + + L4Warehouse + *warehouse = (L4Warehouse *)gauge_renderer->warehousePointer; + if (warehouse->bitMapBin.Get(parameterList[2].data.string) == NULL) + { + DEBUG_STREAM << "LeakGauge: Missing image '" + << parameterList[2].data.string << "'\n"; + return False; + } + warehouse->bitMapBin.Release(parameterList[2].data.string); + + Check_Fpu(); + return True; +} + +// +// @004c5b7c -- ctor (vtable PTR_FUN_00518a9c): GraphicGauge base; +// SetOrigin(x,y), intern the image + hold a ref, store colours + frame +// geometry (frameWidth = imageWidth/3, fullWidth = frames*2), wire a Scalar +// level connection. +// +BitMapInverseWipe::BitMapInverseWipe( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer_in, + int graphics_port_number, + int x, + int y, + const char *image, + int color_a, + int color_b, + int third_in, + int frames_in, + Scalar *value_pointer, + const char *identification_string +): + GraphicGauge(rate, mode_mask, renderer_in, 0, + graphics_port_number, identification_string) +{ + Check_Pointer(this); + + localView.SetOrigin(x, y); + + imageName = nameCopy(image); // @0x90 + colorA = color_a; // @0x94 + colorB = color_b; // @0x98 + frames = frames_in; // @0xA0 + fullWidth = frames_in * 2; // @0x9C + third = third_in; // @0xB0 + + BitMap + *bit_map = ((L4Warehouse *)renderer_in->warehousePointer)-> + bitMapBin.Get(imageName); // held + if (bit_map == NULL) + { + frameWidth = 0; + frameHeight = 0; + } + else + { + frameWidth = bit_map->Data.Size.x / 3; // @0xA8 + frameHeight = bit_map->Data.Size.y; // @0xA4 + } + + GaugeConnection + *connection; + + connection = new GaugeConnectionDirectOf(0, &value, value_pointer); // FUN_00474855 + Check(connection); + Register_Object(connection); + AddConnection(connection); + + Check_Fpu(); +} + +BitMapInverseWipe::~BitMapInverseWipe() // @004c5c80 +{ + Check(this); + ((L4Warehouse *)renderer->warehousePointer)-> + bitMapBin.Release(imageName); + Unregister_Pointer(imageName); + delete[] imageName; + imageName = NULL; + Check_Fpu(); +} + +Logical + BitMapInverseWipe::TestInstance() const +{ + return GraphicGauge::TestInstance(); +} + +void + BitMapInverseWipe::BecameActive() // @004c5cf4 +{ + Check(this); + previousLevel = -1; +} + +// +// @004c5d08 -- Execute. Round the level to [0, fullWidth] (a tiny non-zero +// value forces at least 1); on a change repaint `frames` segments stacked +// vertically, each picking its strip frame from the level: <1 = empty +// (colorA, frame 0), ==1 = half (colorB, middle frame), >1 = full (colorB, +// last frame), decrementing the level by 2 per segment. (The coolant +// LeakGauge; value = CoolantMassLeakRate.) +// +void + BitMapInverseWipe::Execute() +{ + Check(this); + + int + level = Round(value); // FUN_004dcd94 + if (level < 0) + { + level = 0; + } + if (level > fullWidth) + { + level = fullWidth; + } + if (value > TraceLeakLevel && level < 1) // _DAT_0050e3d8 (0.0025) + { + level = 1; + } + + if (level != previousLevel) + { + previousLevel = level; + + L4Warehouse + *warehouse = (L4Warehouse *)renderer->warehousePointer; + BitMap + *bit_map = warehouse->bitMapBin.Get(imageName); + if (bit_map != NULL) + { + int + y = -frameHeight, + frameIndex, + seg; + for (seg = frames; seg > 0; seg--) + { + localView.MoveToAbsolute(0, y); + if (level < 1) + { + localView.SetColor(colorA); + frameIndex = 0; + } + else if (level == 1) + { + localView.SetColor(colorB); + frameIndex = frameWidth; + } + else + { + localView.SetColor(colorB); + frameIndex = frameWidth * 2; + } + localView.DrawBitMap(0, bit_map, frameIndex, 0, + frameWidth + frameIndex, frameHeight); + level -= 2; + y -= frameHeight; + } + } + warehouse->bitMapBin.Release(imageName); + } + Check_Fpu(); +} + + +//########################################################################### +//########################################################################### +// SegmentArc gauges +//########################################################################### +//########################################################################### + +//########################################################################### +// SegmentArc270 @004c6244 ctor (vtable PTR_FUN_005189d0; cluster-built) +// +// Engine SegmentArc base + one Scalar connection driving currentValue (a +// 0..1 fraction, e.g. a weapon's PercentDone recharge) + the segment-span +// factor (|n|/(|n|-1) * 0.75, integer division == 0.75 for n>=2). Inherits +// SegmentArc::Execute (@00474300 -- draws the lit segments up to +// currentValue). Used as the WeaponCluster recharge dial. Deleting-dtor +// thunk @004c66b3 -> the arc base dtor (FUN_00474094). +//########################################################################### + +SegmentArc270::SegmentArc270( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer_in, + int owner_ID, + int graphics_port_number, + int center_x, + int center_y, + Scalar inner0, + Scalar outer0, + Scalar inner1, + Scalar outer1, + Scalar deg0, + Scalar deg1, + int number_of_segs, + int background_color, + int foreground_color, + Scalar *value_pointer, + Logical use_thick_lines, + const char *identification_string +): + SegmentArc(rate, mode_mask, renderer_in, owner_ID, graphics_port_number, // FUN_00473f44 + center_x, center_y, inner0, outer0, inner1, outer1, deg0, deg1, + number_of_segs, background_color, foreground_color, use_thick_lines, + identification_string) +{ + Check_Pointer(this); + + GaugeConnection + *connection; + + connection = new GaugeConnectionDirectOf(0, ¤tValue, value_pointer); + Check(connection); + Register_Object(connection); + AddConnection(connection); + + // |n|/(|n|-1) is INTEGER division (== 1 for n>=2), so this is 0.75. + int + n = (number_of_segs < 0) ? -number_of_segs : number_of_segs; + segmentSpan = (Scalar)(n / (n - 1)) * 0.75f; // _DAT_004c62d4 + + Check_Fpu(); +} + +SegmentArc270::~SegmentArc270() // @004c66b3 (base chain) +{ +} + +//########################################################################### +// SegmentArcRatio @004c62fc Make / @004c6394 ctor / @004c6488 Execute +// (vtable PTR_FUN_0051898c; base = engine SegmentArc) +// +// A segmented arc dial (config keyword "segmentArcRatio"). Two Scalar +// connections drive it -- numerator (value) and denominator (max) -- and +// Execute lights numerator/denominator of the arc. Used for the cockpit +// SPEED arc (config binds numerator=LinearSpeed, denominator=MaxRunSpeed), +// so the arc sweeps as the mech accelerates. The drawing is inherited from +// the engine SegmentArc base -- this class only computes the [0,1] fill +// fraction into the base's currentValue, then delegates the render. +//########################################################################### + +MethodDescription + SegmentArcRatio::methodDescription = + { + "segmentArcRatio", + SegmentArcRatio::Make, + { + // + // CFG shape (L4GAUGE.CFG:4964): + // segmentArcRatio( rate, mode, inner,outer, deg0,deg1, segs, + // bg,fg, valueAttr, maxAttr ) + // e.g. segmentArcRatio(C,ModeAlwaysActive,32,39,0,360,36,0,2, + // LinearSpeed, MaxRunSpeed); + // + { ParameterDescription::typeRate, NULL }, // rate ID + { ParameterDescription::typeModeMask, NULL }, // mode mask + { ParameterDescription::typeScalar, NULL }, // inner radius + { ParameterDescription::typeScalar, NULL }, // outer radius + { ParameterDescription::typeScalar, NULL }, // start angle (deg) + { ParameterDescription::typeScalar, NULL }, // end angle (deg) + { ParameterDescription::typeInteger, NULL }, // segment count (+dir) + { ParameterDescription::typeColor, NULL }, // background colour + { ParameterDescription::typeColor, NULL }, // foreground colour + { ParameterDescription::typeAttribute, NULL }, // numerator (value) + { ParameterDescription::typeAttribute, NULL }, // denominator (max) + PARAMETER_DESCRIPTION_END + } + }; + +// +// @004c62fc -- Make. No resource to verify (returns True unconditionally, +// as the binary does). +// +Logical + SegmentArcRatio::Make( + int display_port_index, + Vector2DOf position, + Entity * /*entity -- unused*/, + GaugeRenderer *gauge_renderer + ) +{ + ParameterDescription + *parameterList = methodDescription.parameterList; + + Check(gauge_renderer); + +# if DEBUG_LEVEL > 0 + SegmentArcRatio + *gauge = +# endif + new SegmentArcRatio( // FUN_004c6394 + parameterList[0].data.rate, + parameterList[1].data.modeMask, + (L4GaugeRenderer *)gauge_renderer, + display_port_index, // graphics_port_number + position.x, position.y, // centre + parameterList[2].data.scalar, // inner radius + parameterList[3].data.scalar, // outer radius + parameterList[4].data.scalar, // start angle + parameterList[5].data.scalar, // end angle + parameterList[6].data.integer, // segment count (+dir) + parameterList[7].data.color, // bg + parameterList[8].data.color, // fg + (Scalar *)parameterList[9].data.attributePointer, // numerator + (Scalar *)parameterList[10].data.attributePointer, // denominator + + // HACK!!!! The (Scalar *) is VERY dangerous! + "SegmentArcRatio"); + Register_Object(gauge); + + Check_Fpu(); + return True; +} + +// +// @004c6394 -- ctor. Engine SegmentArc base (inner0==inner1, +// outer0==outer1 -- the Make duplicates them, so the arc has a constant +// radius); precompute the span factor and wire the two Scalar connections. +// +SegmentArcRatio::SegmentArcRatio( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer_in, + int graphics_port_number, + int center_x, + int center_y, + Scalar inner, + Scalar outer, + Scalar deg0, + Scalar deg1, + int number_of_segs, + int background_color, + int foreground_color, + Scalar *numerator_pointer, + Scalar *denominator_pointer, + const char *identification_string +): + SegmentArc(rate, mode_mask, renderer_in, 0, // FUN_00473f44 (owner_ID 0) + graphics_port_number, center_x, center_y, + inner, outer, inner, outer, // inner0/outer0 == inner1/outer1 + deg0, deg1, number_of_segs, + background_color, foreground_color, + True, // use_thick_lines (binary param_20 = 1) + identification_string) +{ + Check_Pointer(this); + + // segmentSpan = (Scalar)(|n| / (|n|-1)) * 0.75f -- |n|/(|n|-1) is + // INTEGER division (== 1 for n>=2), so this is 0.75 for the 36-segment + // speed arc. + int + n = (number_of_segs < 0) ? -number_of_segs : number_of_segs; // @0xC8 + segmentSpan = (Scalar)(n / (n - 1)) * 0.75f; // _DAT_004c6484 + + GaugeConnection + *connection; + + connection = new GaugeConnectionDirectOf(0, &numerator, numerator_pointer); // @0xC0 + Check(connection); + Register_Object(connection); + AddConnection(connection); + + connection = new GaugeConnectionDirectOf(0, &denominator, denominator_pointer); // @0xC4 + Check(connection); + Register_Object(connection); + AddConnection(connection); + + Check_Fpu(); +} + +// +// @004c668d -- deleting-dtor thunk. Connections + arc base are released by +// the implicit base-dtor chain (FUN_00474094); no own teardown. +// +SegmentArcRatio::~SegmentArcRatio() +{ +} + +// +// @004c6488 -- Execute. currentValue = clamp(|numerator/denominator * +// span|, 0, 1); then delegate to the engine SegmentArc::Execute (@00474300) +// which lights that fraction of the segments. (x87 recovered by +// disassembly: FUN_004dcd00 = fabs.) +// +void + SegmentArcRatio::Execute() +{ + Check(this); + + if (denominator < 1.0f) // guard div-by-tiny / unpowered + { + currentValue = 0.0f; + } + else + { + currentValue = numerator / denominator * segmentSpan; + if (currentValue < 0.0f) // FUN_004dcd00 = fabs + { + currentValue = -currentValue; + } + if (currentValue > 1.0f) + { + currentValue = 1.0f; + } + } + SegmentArc::Execute(); // @00474300 -- the base draw +} + +// === btl4gau2.cpp begins at @004c6798 (SeekVoltage gauge) -- not part of this TU. diff --git a/restoration/source410/BT_L4/BTL4GAUG.HPP b/restoration/source410/BT_L4/BTL4GAUG.HPP new file mode 100644 index 00000000..90b4c7b4 --- /dev/null +++ b/restoration/source410/BT_L4/BTL4GAUG.HPP @@ -0,0 +1,601 @@ +//===========================================================================// +// File: btl4gaug.hpp // +// Project: BattleTech Brick: Gauge Renderer Manager // +// Contents: The cockpit instrument gauge library -- the family of small // +// "widget" gauges that the BattleTech HUD is assembled from: // +// * ColorMapper* -- palette-animating tint gauges driven by // +// subsystem state (heat / armour / crit) // +// * Horiz/VertTwoPartBar, VertNormalSlider // +// -- two-colour fill bars (throttle / damage) // +// * OneOfSeveral* -- multi-frame bitmap selectors (state lamp) // +// * HeadingPointer -- rotating numeric heading readout // +// * SegmentArc270/Ratio-- arc / dial style gauges // +// * BitMap wipe gauges -- bitmap "fuel / leak" wipe indicators // +//---------------------------------------------------------------------------// +// Date Who Modification // +// -------- --- ---------------------------------------------------------- // +// 02/22/96 CPB Initial coding. // +//---------------------------------------------------------------------------// +// Copyright (C) 1996, Virtual World Entertainment, Inc. All rights reserved // +// PROPRIETARY AND CONFIDENTIAL // +//===========================================================================// +// +// STAGED RECONSTRUCTION. No BTL4GAUG.HPP survived (the BT_L4 source tree +// retains only BTL4RDR.HPP and BTL4GALM.HPP). This declaration is recovered +// from the shipped binary (BTL4OPT.EXE) and re-hosted onto the authentic 1995 +// engine headers (MUNGA GAUGE/GAUGREND + MUNGA_L4 L4GAUGE) for the BC++4.52 +// DOS build. The module is the .obj that links immediately after btl4rdr.obj +// and before btl4gau2.obj (BTL4.MAK), so it owns the contiguous code run +// [ @004c2f94 .. @004c6771 ] (btl4gau2.cpp begins at @004c6798). +// +// The sole address with a surviving embedded assert path -- the anchor -- +// is ColorMapperHeat's constructor: +// @004c3f6c "d:\tesla\bt\bt_l4\BTL4GAUG.CPP", line 0x68a +// +// Class names are recovered verbatim from the CODE name-string pool +// (section_dump.txt @0x518480..0x51859e): +// ColorMapper ColorMapperArmor ColorMapperMultiArmor ColorMapperCritical +// ColorMapperHeat HorizTwoPartBar VertTwoPartBar VertNormalSlider +// OneOfSeveral OneOfSeveralInt OneOfSeveralPixInt OneOfSeveralStates +// HeadingPointer BitMapInverseWipe BitMapInverseWipeScalar +// SegmentArc270 SegmentArcRatio +// +// Field offsets in comments are the byte offsets observed in the decompiled +// object (e.g. "@0x90" == this[0x24]). The two base gauges are the MUNGA +// gauge primitives (GAUGE.HPP): +// Gauge ctor FUN_00444308 dtor FUN_00444360 -- non-graphic; +// used by the ColorMapper family (they tint a palette, they +// do not own a GraphicsView). +// GraphicGauge ctor FUN_00444818 dtor FUN_00444870 -- owns the embedded +// GraphicsView localView at +0x48 (this[0x12]); used by every +// drawing gauge. (Same base as MapDisplay in btl4rdr.cpp.) +// +// The file-private GaugeConnection feeds (HeatConnection, ArmorZoneConnection, +// MultiArmorConnection, CriticalConnection, StateConnection) live in +// BTL4GAUG.CPP only. +// +// Names flagged "(name unconfirmed)" are best-effort: the class exists in the +// binary but its identification string could not be tied to it unambiguously. +// + +#if !defined(BTL4GAUG_HPP) +# define BTL4GAUG_HPP + +# if !defined(SLOT_HPP) +# include +# endif + +# if !defined(L4GREND_HPP) +# include +# endif + +# if !defined(L4GAUGE_HPP) +# include +# endif + +//##################### Forward Class Declarations ####################### + class Entity; + class Subsystem; + + //####################################################################### + //####################################################################### + // ColorMapper family + // + // A ColorMapper is NOT a drawing gauge: every frame it reads a single + // subsystem-derived value (0..255), looks the corresponding R/G/B + // triple out of a named palette resource, and -- if it has changed -- + // writes it straight into one hardware palette slot (colorIndex). The + // cockpit art that uses that slot therefore changes colour live. With + // two palette names it alternates between them every frame to flash. + // + // The driving value arrives through a small file-private GaugeConnection + // (see btl4gaug.cpp) wired to currentColorIndex. ColorMapperHeat's + // connection reads the heat subsystem's live temperature. + //####################################################################### + //####################################################################### + + class ColorMapper : + public Gauge + { + public: + ColorMapper( // @004c37dc vtable PTR_FUN_00518e10 + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer, + int graphics_port_number, + int color_index, + const char *palette_name_a, + const char *palette_name_b, + const char *identification_string + ); + ~ColorMapper(); // @004c38dc + + void BecameActive(); // @004c395c + void Execute(); // @004c3980 -- palette push + + protected: + char *paletteName[2]; // @0x48,@0x4C this[0x12],[0x13] + Logical twoColorMode; // @0x50 this[0x14] (names differ) + int currentColorIndex; // @0x54 this[0x15] (connection dst) + int previousColorIndex; // @0x58 this[0x16] + int colorSlot; // @0x5C this[0x17] hw palette slot + int paletteToggle; // @0x60 this[0x18] 0/1 flash phase + unsigned char + previousRed, // @0x64 this[0x19].b0 + previousGreen, // @0x65 + previousBlue; // @0x66 + GraphicsPort + *graphicsPort; // @0x68 this[0x1A] (renderer port) + }; + + // + // ColorMapperArmor -- tints by the damage ratio of one named damage zone. + // Connection: @004c33a4 (reads the entity's damageZones[index]). + // + class ColorMapperArmor : + public ColorMapper + { + public: + // Registered in BTL4MethodDescription[] (btl4grnd.cpp) as "cmArmor". + static MethodDescription methodDescription; + + static Logical Make( // @004c3aa4 + int display_port_index, Vector2DOf position, + Entity *entity, GaugeRenderer *gauge_renderer); + + ColorMapperArmor( // @004c3b98 + GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer, + int graphics_port_number, int color_index, + Entity *entity, const char *palette_a, const char *palette_b, + int damage_zone_index, const char *identification_string); + ~ColorMapperArmor(); + + // @004c3c38 -- store the group state into @0x6C (used by btl4gau3's + // PlayerStatusMappingGroup to push the mech's disabled/enabled state + // onto all 28 zone tints). + void SetColor(int state) { unused = state; } + protected: + int unused; // @0x6C this[0x1B] (state slot; SetColor writes it) + }; + + // + // ColorMapperMultiArmor -- worst-of-N damage zones. + // Connection: @004c346c (copies 8 zone indices, takes the max ratio). + // + class ColorMapperMultiArmor : + public ColorMapper + { + public: + // Registered in BTL4MethodDescription[] (btl4grnd.cpp) as + // "colorMapperMultiArmor". + static MethodDescription methodDescription; + + static Logical Make(int, Vector2DOf, Entity *, GaugeRenderer *); // @004c3c48 + ColorMapperMultiArmor( // @004c3d60 + GaugeRate, ModeMask, L4GaugeRenderer *, int, int, + Entity *, const char *, const char *, + const int *zone_indices /*[8]*/, const char *); + ~ColorMapperMultiArmor(); + }; + + // + // ColorMapperCritical -- tints by a subsystem's operational state. + // Connection: @004c3598 (subsystem destroyed -> 100, else zone damage). + // + class ColorMapperCritical : + public ColorMapper + { + public: + // Registered in BTL4MethodDescription[] (btl4grnd.cpp) as "cmCrit". + static MethodDescription methodDescription; + + static Logical Make(int, Vector2DOf, Entity *, GaugeRenderer *); // @004c3ddc + ColorMapperCritical( // @004c3e40 + GaugeRate, ModeMask, L4GaugeRenderer *, int, int, + Entity *, const char *, const char *, + const char *subsystem_name, const char *); + ~ColorMapperCritical(); + }; + + // + // ColorMapperHeat -- the cockpit heat tint. ANCHOR @004c3f6c. + // Connection: @004c3664 (reads the heat subsystem's currentTemperature + // as a 0..100 percentage). The named subsystem must derive from a heat + // class (IsDerivedFrom HeatableSubsystem 0x50e3ec or HeatWatcher 0x50e604) + // or the ctor asserts (line 0x68a). + // + class ColorMapperHeat : + public ColorMapper + { + public: + static Logical Make(int, Vector2DOf, Entity *, GaugeRenderer *); // @004c3f08 + ColorMapperHeat( // @004c3f6c + GaugeRate, ModeMask, L4GaugeRenderer *, int, int, + Entity *, const char *, const char *, + const char *heat_subsystem_name, const char *); + ~ColorMapperHeat(); + + // Registered in BTL4MethodDescription[] (btl4grnd.cpp) so the config + // interpreter builds this gauge from the CFG "cmHeat" primitive. + static MethodDescription methodDescription; + }; + + + //####################################################################### + //####################################################################### + // Two-part fill bars (: GraphicGauge) + // + // A bar is split into a "filled" colour and a "background" colour at a + // pixel computed from a Scalar input. HorizTwoPartBar grows + // left->right, VertTwoPartBar grows bottom->top. Both blit a tile + // bitmap behind the fill (DrawTiledBitmap @004c2ff8, file-private). + //####################################################################### + //####################################################################### + + // + // HorizTwoPartBar has NO config keyword: it is built directly by the + // btl4gau2 SubsystemCluster family. (A Make body exists in the binary at + // @004c407c but no methodDescription for it appears in the registered + // BTL4MethodDescription table; its parameter list is unrecovered, so the + // factory is intentionally NOT declared here -- clusters call the ctor.) + // + class HorizTwoPartBar : + public GraphicGauge + { + public: + HorizTwoPartBar( // @004c4170 vtable PTR_FUN_00518cbc + GaugeRate, ModeMask, L4GaugeRenderer *, int graphics_port_number, + int left, int bottom, int right, int top, + const char *tile_image, + int fill_color, int background_color, + Scalar *value_pointer, Scalar *low_pointer, Scalar *high_pointer, + const char *identification_string); + ~HorizTwoPartBar(); + + Logical TestInstance() const; + void BecameActive(); // @004c4324 + void Execute(); // @004c4340 + + protected: + Scalar value; // @0x90 this[0x24] (connection) + Scalar low; // @0x94 this[0x25] + Scalar high; // @0x98 this[0x26] + char *tileImage; // @0x9C this[0x27] + int fillColor; // @0xA0 this[0x28] + int backgroundColor; // @0xA4 this[0x29] + int width; // @0xA8 this[0x2A] right-left + int height; // @0xAC this[0x2B] (top-bottom)-1 + int previousFill; // @0xB0 this[0x2C] + int previousFull; // @0xB4 this[0x2D] + }; + + class VertTwoPartBar : + public GraphicGauge + { + public: + // Registered in BTL4MethodDescription[] (btl4grnd.cpp) as "vertBar". + static MethodDescription methodDescription; + + static Logical Make(int, Vector2DOf, Entity *, GaugeRenderer *); // @004c462c + + VertTwoPartBar( // @004c4724 (ctor arg order = binary param_12..17) + GaugeRate, ModeMask, L4GaugeRenderer *, int graphics_port_number, + int left, int bottom, int right, int top, + const char *tile_image, + int background_color, int fill_color, int extra_color, + Scalar *value_pointer, Scalar *low_pointer, Scalar *high_pointer, + const char *identification_string); + ~VertTwoPartBar(); // @004c486c + + Logical TestInstance() const; + void BecameActive(); // @004c48e0 + void Execute(); // @004c48fc + + protected: + char *tileImage; // @0x90 this[0x24] + int backgroundColor; // @0x94 this[0x25] + int fillColor; // @0x98 this[0x26] + int extraColor; // @0x9C this[0x27] + int width; // @0xA0 this[0x28] (right-left)-1 + int height; // @0xA4 this[0x29] top-bottom + int previousFill; // @0xA8 this[0x2A] + int previousFull; // @0xAC this[0x2B] + Scalar value; // @0xB0 this[0x2C] (connection) + Scalar low; // @0xB4 this[0x2D] + Scalar high; // @0xB8 this[0x2E] + }; + + // + // VertNormalSlider -- a single-colour rectangle whose height tracks a + // normalised [0,1] value (clamped at @004c4cc0). (name unconfirmed but + // matches the "VertNormalSlider" pool entry and the vtable ordering.) + // + class VertNormalSlider : + public GraphicGauge + { + public: + // The "vertNormalSlider" config factory (the condenser VALVE slider + // @2 in GenericHeatGauges1/2). Registered in BTL4MethodDescription[] + // (btl4grnd.cpp). + static MethodDescription methodDescription; + static Logical Make(int, Vector2DOf, Entity *, GaugeRenderer *); // @004c4b08 + + VertNormalSlider( // @004c4b84 vtable PTR_FUN_00518c34 + GaugeRate, ModeMask, L4GaugeRenderer *, int, + int left, int bottom, int right, int top, + int fill_color, int background_color, int baseline, + Scalar *value_pointer, const char *identification_string); + ~VertNormalSlider(); // @004c4c68 + + Logical TestInstance() const; // @004c4c94 -> GraphicGauge::TestInstance + void BecameActive(); // @004c4cac + void Execute(); // @004c4cc0 -> Draw @004c4d48 + private: + void Draw(); // @004c4d48 (XOR indicator blit; non-virtual) + + protected: + // Byte-exact layout (from the ctor's this[N] stores @004c4b84). + // 8 words, 0x90..0xAF, sizeof == 0xB0. + int previousFill; // @0x90 this[0x24] XOR indicator row; -1 = none + int width; // @0x94 this[0x25] (right-left)-1 + int height; // @0x98 this[0x26] top-bottom (track height) + int baseline; // @0x9C this[0x27] indicator thickness + int span; // @0xA0 this[0x28] height-baseline (travel) + int fillColor; // @0xA4 this[0x29] indicator colour + int backgroundColor; // @0xA8 this[0x2A] stored, unused by Draw + Scalar value; // @0xAC this[0x2B] connection dst [0,1] + }; + + + //####################################################################### + //####################################################################### + // OneOfSeveral family -- multi-frame bitmap selectors + // + // A strip bitmap holds N frames; the gauge blits whichever frame the + // driving value selects. The base (OneOfSeveral) stores the strip and + // the per-frame size; subclasses add the value source. + //####################################################################### + //####################################################################### + + class OneOfSeveral : + public GraphicGauge + { + public: + OneOfSeveral( // @004c4d88 vtable PTR_FUN_00518bf0 + GaugeRate, ModeMask, L4GaugeRenderer *, int, + int x, int y, Logical from_image_strip, + const char *image_name, + int background_color, int foreground_color, + int columns, int rows, + const char *identification_string); + ~OneOfSeveral(); // @004c4e7c + + Logical TestInstance() const; + void BecameActive(); // @004c4f14 + void Execute(); // @004c4f28 + + protected: + Logical fromImageStrip; // @0x90 this[0x24] + char *imageName; // @0x94 this[0x25] + int backgroundColor; // @0x98 this[0x26] + int foregroundColor; // @0x9C this[0x27] (DrawBitMapOpaque bg arg) + int columns; // @0xA0 this[0x28] + int frameWidth; // @0xA4 this[0x29] + int frameHeight; // @0xA8 this[0x2A] + int selected; // @0xAC this[0x2B] (connection) + int previousSelected; // @0xB0 this[0x2C] + }; + + // + // OneOfSeveralInt -- frame = a direct int input. ctor @004c5148, dtor + // @004c51d8, vtable PTR_FUN_00518bac. No config keyword: built directly + // by the btl4gau2 clusters (a binary Make exists @004c5068 but no + // registered methodDescription; its parameter list is unrecovered, so no + // factory is declared -- clusters call the ctor). + // + class OneOfSeveralInt : + public OneOfSeveral + { + public: + OneOfSeveralInt(GaugeRate, ModeMask, L4GaugeRenderer *, int, + int x, int y, const char *image, int columns, int rows, + int *value_pointer, const char *); + ~OneOfSeveralInt(); // @004c51d8 + }; + + // + // OneOfSeveralPixInt -- frame from a packed-pixel int strip. + // @004c52d8 Make @004c5204 vtable PTR_FUN_00518b68 + // + class OneOfSeveralPixInt : + public OneOfSeveral + { + public: + // Registered in BTL4MethodDescription[] (btl4grnd.cpp) as + // "oneOfSeveralPixInt". + static MethodDescription methodDescription; + + static Logical Make(int, Vector2DOf, Entity *, GaugeRenderer *); // @004c5204 + OneOfSeveralPixInt( // @004c52d8 + GaugeRate, ModeMask, L4GaugeRenderer *, int graphics_port_number, + int x, int y, const char *image, int columns, int rows, + int *value_pointer, const char *identification_string); + ~OneOfSeveralPixInt(); // @004c5364 + }; + + // + // OneOfSeveralStates -- frame from a subsystem state value (clamped >=0). + // ctor @004c5470, dtor @004c5500, vtable PTR_FUN_00518b24. + // Connection: @004c3324 (reads the Subsystem simulation state -- the + // binary's raw "state word @0x14" is Simulation::GetSimulationState()). + // No config keyword (cluster-built; binary Make @004c5390 unregistered, + // not declared -- see OneOfSeveralInt). + // + // RE-HOST NOTE: the source argument is typed Subsystem* here (the donor + // reconstruction typed it Entity* and read a raw +0x14); btl4gau2 call + // sites pass the subsystem. + // + class OneOfSeveralStates : + public OneOfSeveral + { + public: + OneOfSeveralStates(GaugeRate, ModeMask, L4GaugeRenderer *, int, + int x, int y, const char *image, int columns, int rows, + Subsystem *subsystem_source, const char *); + ~OneOfSeveralStates(); // @004c5500 + void BecameActive(); // @004c552c + }; + + + //####################################################################### + // HeadingPointer -- a rotating numeric compass-heading readout. + // @004c562c Make @004c554c Execute @004c5914 (rotate by mech yaw, + // degrees = radian * 57.29578). Owns a NumericDisplay (@0xBC, + // FUN_004700bc) and a GraphicsViewRecord (@0xA4) for erase tracking. + //####################################################################### + class HeadingPointer : + public GraphicGauge + { + public: + // Registered in BTL4MethodDescription[] (btl4grnd.cpp) as + // "headingPointer". + static MethodDescription methodDescription; + + static Logical Make(int, Vector2DOf, Entity *, GaugeRenderer *); // @004c554c + + // + // The binary ctor (@004c562c) takes 14 args. Fields @0x98/@0x9C are + // the needle's inner/outer RADIUS (Execute multiplies them by sin/cos + // of the heading). + // + HeadingPointer( // @004c562c vtable PTR_FUN_00518ae0 + GaugeRate, ModeMask, L4GaugeRenderer *, + unsigned int owner_ID, + int graphics_port_number, + int x, int y, + int needle_color, // -> @0x90 the drawn needle colour + int numeric_color, // -> NumericDisplay foreground (readout) + int erase_color, // -> @0x94 erase + NumericDisplay background + int inner_radius, // -> @0x98 needle near-end radius + int outer_radius, // -> @0x9C needle tip radius (the change key) + const char *font_name, + const char *identification_string = "HeadingPointer"); + ~HeadingPointer(); // @004c573c + + Logical TestInstance() const; + void ShowInstance(char *indent); // @004c57d0 + void BecameActive(); // @004c58e8 + void Execute(); // @004c5914 + + protected: + int needleColor; // @0x90 needle (fg) colour + int eraseColor; // @0x94 erase (bg) colour + int innerRadius; // @0x98 needle near-end radius + int outerRadius; // @0x9C needle tip radius (change key) + char *imageName; // @0xA0 interned font name + GraphicsViewRecord + previousDrawing; // @0xA4 erase-tracking record + int previousX; // @0xB4 + int previousY; // @0xB8 + NumericDisplay + *numericDisplay; // @0xBC + }; + + + //####################################################################### + // BitMap "wipe" gauges -- a fuel/leak style bar that reveals a bitmap up + // to a level. (names "BitMapInverseWipe" / "BitMapInverseWipeScalar" are + // best-effort from the pool; the Scalar variant adds a GaugeConnection.) + // BitMapInverseWipe @004c5b7c (the coolant LeakGauge; also used + // as a base by btl4gau2) + // @004c5e84 NOT RECONSTRUCTED (notes only) + // Scalar @004c61c8 NOT RECONSTRUCTED (notes only; + // needed by btl4gau2 BallisticWeaponCluster's + // ejectWipe -- still an open bring-up NULL) + //####################################################################### + class BitMapInverseWipe : // (name unconfirmed) + public GraphicGauge + { + public: + // Registered in BTL4MethodDescription[] (btl4grnd.cpp) as + // "LeakGauge" -- the coolant-leak inverse-wipe factory. + static MethodDescription methodDescription; + static Logical Make(int, Vector2DOf, Entity *, GaugeRenderer *); + + BitMapInverseWipe( // @004c5b7c vtable PTR_FUN_00518a9c + GaugeRate, ModeMask, L4GaugeRenderer *, int, + int x, int y, const char *image, + int color_a, int color_b, int third, int frames, + Scalar *value_pointer, const char *); + ~BitMapInverseWipe(); // @004c5c80 + Logical TestInstance() const; + void BecameActive(); // @004c5cf4 + void Execute(); // @004c5d08 + protected: + char *imageName; // @0x90 this[0x24] + int colorA; // @0x94 this[0x25] + int colorB; // @0x98 this[0x26] + int fullWidth; // @0x9C this[0x27] frames*2 + int frames; // @0xA0 this[0x28] + int frameHeight; // @0xA4 this[0x29] + int frameWidth; // @0xA8 this[0x2A] width/3 + int previousLevel; // @0xAC this[0x2B] + int third; // @0xB0 this[0x2C] + Scalar value; // @0xB4 this[0x2D] (connection) + }; + + //####################################################################### + // SegmentArc gauges -- needle / dial over a 270-degree arc. Derived + // from the MUNGA L4 arc primitive (engine SegmentArc, L4GAUGE.HPP:1086; + // ctor FUN_00473f44, Execute @00474300). + // SegmentArc270 @004c6244 (the WeaponCluster recharge dial; + // cluster-built, no config keyword) + // SegmentArcRatio @004c62fc Make / @004c6394 ctor (the speed arc) + //####################################################################### + class SegmentArc270 : + public SegmentArc // MUNGA L4 base (engine class SegmentArc) + { + public: + SegmentArc270( // @004c6244 vtable PTR_FUN_005189d0 + GaugeRate, ModeMask, L4GaugeRenderer *, int owner_ID, + int graphics_port_number, int center_x, int center_y, + Scalar inner0, Scalar outer0, Scalar inner1, Scalar outer1, + Scalar deg0, Scalar deg1, int number_of_segs, + int background_color, int foreground_color, + Scalar *value_pointer, Logical use_thick_lines, + const char *identification_string); + ~SegmentArc270(); // dtor thunk @004c66b3 + + // No Execute override: inherits the engine SegmentArc::Execute + // (@00474300), which draws the lit segments up to currentValue. + protected: + Scalar segmentSpan; // @0xC0 this[0x30] + }; + + class SegmentArcRatio : + public SegmentArc // MUNGA L4 base (engine SegmentArc) + { + public: + // Registered in BTL4MethodDescription[] (btl4grnd.cpp) as + // "segmentArcRatio". + static MethodDescription methodDescription; + + static Logical Make(int, Vector2DOf, Entity *, GaugeRenderer *); // @004c62fc + SegmentArcRatio( // @004c6394 vtable PTR_FUN_0051898c + GaugeRate, ModeMask, L4GaugeRenderer *, int graphics_port_number, + int center_x, int center_y, + Scalar inner, Scalar outer, Scalar deg0, Scalar deg1, + int number_of_segs, int background_color, int foreground_color, + Scalar *numerator_pointer, Scalar *denominator_pointer, + const char *identification_string); + ~SegmentArcRatio(); // dtor thunk @004c668d + + void Execute(); // @004c6488 (ratio -> currentValue -> SegmentArc::Execute) + + protected: + Scalar numerator; // @0xC0 this[0x30] (connection) + Scalar denominator; // @0xC4 this[0x31] (connection) + Scalar segmentSpan; // @0xC8 this[0x32] (read by Execute) + }; + +#endif diff --git a/restoration/source410/BT_L4/BTL4GRND.CPP b/restoration/source410/BT_L4/BTL4GRND.CPP index 12f392e1..eaed5c33 100644 --- a/restoration/source410/BT_L4/BTL4GRND.CPP +++ b/restoration/source410/BT_L4/BTL4GRND.CPP @@ -1,12 +1,18 @@ //===========================================================================// // File: btl4grnd.cpp // -// Project: BattleTech // -// Contents: Implementation details for the BT gauge renderer // +// Project: BattleTech Brick: Gauge Renderer Manager // +// Contents: BTL4GaugeRenderer -- the BT cockpit gauge renderer // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. // // All Rights reserved worldwide // // This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // //===========================================================================// +// +// RECONSTRUCTED against the authentic surviving BTL4GRND.HPP (5 members, no +// data -- all state inherited). Binary anchors: ctor @004cbea0 (vtable +// 0051cebc), dtor @004cbf40, TestInstance @004cbf90, NotifyOfNew @004cbfa0, +// NotifyOfBecoming @004cc028; the BT registration table @0051c910. +// #include #pragma hdrstop @@ -15,20 +21,195 @@ # include #endif -BTL4GaugeRenderer::BTL4GaugeRenderer() +#if !defined(BTL4GAUG_HPP) +# include +#endif + +#if !defined(BTL4GAU2_HPP) +# include +#endif + +#if !defined(BTL4GAU3_HPP) +# include +#endif + +#if !defined(BTL4RDR_HPP) +# include +#endif + +#if !defined(L4WRHOUS_HPP) +# include +#endif + +#if !defined(L4LAMP_HPP) +# include +#endif + +#if !defined(APP_HPP) +# include +#endif + +#if !defined(MOVER_HPP) +# include +#endif + +#if !defined(TERRAIN_HPP) +# include +#endif + +#if !defined(RESOURCE_HPP) +# include +#endif + +// +//############################################################################# +// The BT gauge primitive registry (.data @0051c910): every L4GAUGE.CFG +// keyword the BT cockpit adds, chained onto the engine L4MethodDescription +// table so unmatched keywords (bgPixelMap / numeric / rankAndScore / ...) +// fall through to the base widgets. The CHAIN entry MUST stay LAST, and +// every listed methodDescription's Make must be real code -- the +// interpreter assigns opcodes by flattened table index and calls Make at +// Interpret time. +//############################################################################# +// +extern MethodDescription *L4MethodDescription[]; + +static MethodDescription + BTL4ChainToPrevious = METHOD_DESCRIPTION_CHAIN(L4MethodDescription); + +MethodDescription + *BTL4MethodDescription[] = + { + &ColorMapperHeat::methodDescription, // "cmHeat" + &HeadingPointer::methodDescription, // "headingPointer" + &ColorMapperArmor::methodDescription, // "cmArmor" + &ColorMapperMultiArmor::methodDescription, // "colorMapperMultiArmor" + &ColorMapperCritical::methodDescription, // "cmCrit" + &VertTwoPartBar::methodDescription, // "vertBar" + &SegmentArcRatio::methodDescription, // "segmentArcRatio" + &OneOfSeveralPixInt::methodDescription, // "oneOfSeveralPixInt" + &MapDisplay::methodDescription, // "map" + &PlayerStatus::methodDescription, // "PlayerStatus" + &VehicleSubSystems::methodDescription, // "vehicleSubSystems" + &BitMapInverseWipe::methodDescription, // "LeakGauge" + &VertNormalSlider::methodDescription, // "vertNormalSlider" + &PilotList::methodDescription, // "pilotList" + &GeneratorCluster::methodDescription, // "GeneratorCluster" + &SectorDisplay::methodDescription, // "sectorDisplay" + &PrepEngrScreen::methodDescription, // "prepEngr" + &MessageBoard::methodDescription, // "messageBoard" + &BTL4ChainToPrevious + }; + +// +//############################################################################# +// Construction (binary @004cbea0): the lamp manager, the warehouse, then +// the configuration build -- BuildConfigurationFile parses the ENTIRE +// gauge\l4gauge.cfg to bytecode against the registry and immediately runs +// the cfg's `Initialization` procedure (ports / global gauges, entity == +// NULL). The per-mech gauges instantiate later via ConfigureForModel +// ("Init") from MakeViewpointEntity. +//############################################################################# +// +BTL4GaugeRenderer::BTL4GaugeRenderer(): + L4GaugeRenderer() { + Check(this); + + lampManager = new L4LampManager( + (LBE4ControlsManager *)application->GetControlsManager()); + Register_Object(lampManager); + + warehousePointer = new L4Warehouse(); + Register_Object(warehousePointer); + + BuildConfigurationFile("gauge\\l4gauge.cfg", BTL4MethodDescription); + + Check_Fpu(); } +// +//############################################################################# +// Destruction (binary @004cbf40): remove every gauge BEFORE the warehouse +// dies (gauge dtors release warehouse resources). +//############################################################################# +// BTL4GaugeRenderer::~BTL4GaugeRenderer() { + Check(this); + + Remove(0); + + if (warehousePointer != NULL) + { + Unregister_Object(warehousePointer); + delete warehousePointer; + warehousePointer = NULL; + } +} + +Logical + BTL4GaugeRenderer::TestInstance() const +{ + return True; +} + +// +//############################################################################# +// The radar feed (binary @004cbfa0 / @004cc028): an interesting entity with +// a gauge-image (pip) resource joins the moving (Mover) or static (Terrain) +// list; everything ALWAYS chains to the base (the engine forwards the +// notification to every live gauge -- "all descendents MUST chain back"). +//############################################################################# +// +void + BTL4GaugeRenderer::NotifyOfNewInterestingEntity(Entity *entity) +{ + Check(this); + Check(entity); + + ResourceDescription *pip = + application->GetResourceFile()->SearchList( + entity->GetResourceID(), + ResourceDescription::GaugeImageStreamResourceType + ); + if (pip != NULL) + { + if (entity->IsDerivedFrom(Mover::ClassDerivations)) + { + movingEntities.Add(entity); + } + else if (entity->IsDerivedFrom(Terrain::ClassDerivations)) + { + staticEntities.Add(entity); + } + } + + L4GaugeRenderer::NotifyOfNewInterestingEntity(entity); } void - BTL4GaugeRenderer::NotifyOfNewInterestingEntity(Entity *) + BTL4GaugeRenderer::NotifyOfBecomingUninterestingEntity(Entity *entity) { -} + Check(this); + Check(entity); -void - BTL4GaugeRenderer::NotifyOfBecomingUninterestingEntity(Entity *) -{ + ResourceDescription *pip = + application->GetResourceFile()->SearchList( + entity->GetResourceID(), + ResourceDescription::GaugeImageStreamResourceType + ); + if (pip != NULL) + { + if (entity->IsDerivedFrom(Mover::ClassDerivations)) + { + movingEntities.Remove(entity); + } + else if (entity->IsDerivedFrom(Terrain::ClassDerivations)) + { + staticEntities.Remove(entity); + } + } + + L4GaugeRenderer::NotifyOfBecomingUninterestingEntity(entity); } diff --git a/restoration/source410/BT_L4/BTL4RDR.CPP b/restoration/source410/BT_L4/BTL4RDR.CPP new file mode 100644 index 00000000..90ad9ee7 --- /dev/null +++ b/restoration/source410/BT_L4/BTL4RDR.CPP @@ -0,0 +1,1161 @@ +//===========================================================================// +// File: btl4rdr.cpp // +// Project: BattleTech Brick: Gauge Renderer Manager // +// Contents: MapDisplay -- the cockpit radar / threat-map gauge. Draws a // +// top-down (X/Z) map centred on the operator mech, plots blips for // +// nearby static and moving entities, the player's view wedge, and // +// floating name labels for tracked mechs. // +//---------------------------------------------------------------------------// +// Date Who Modification // +// -------- --- ---------------------------------------------------------- // +// 09/13/95 CPB Initial coding. // +//---------------------------------------------------------------------------// +// Copyright (C) 1994, Virtual World Entertainment, Inc. All rights reserved // +// PROPRIETARY and CONFIDENTIAL // +//===========================================================================// +// +// RECONSTRUCTED (source410 re-host) from the shipped binary (BTL4OPT.EXE) +// against the SURVIVING header CODE\BT\BT_L4\BTL4RDR.HPP, whose member byte- +// offsets match the decompiled object exactly: +// +// nameArray[16] @0x090 backgroundColor @0x358 +// offsetPosition @0x2D0 staticColor @0x35C +// currentScale @0x2D4 boxColor @0x360 +// currentPositionPointer @0x2D8 halfWidth @0x364 +// currentAngularPointer @0x2DC halfHeight @0x368 +// currentCapabilitiesRatio@0x2E0 operating @0x36C +// LODIndex @0x2E4 rockAndRoll @0x370 +// pixelsPerMeter @0x2E8 flashState @0x374 +// metersPerPixel @0x2EC staticEntityList @0x378 +// maximumRange @0x2F0 movingEntityList @0x394 +// maximumDistanceSquared @0x2F4 previousDrawing @0x3B0 +// xMin..zMax @0x2F8.. halfViewWidthInUnits@0x3C0 +// viewingPosition @0x310 shadowTable @0x3C4 +// viewHorizontalRotation @0x31C worldToView @0x324 +// viewHalfHorizontalWidth @0x320 +// +// The TU is CONFIRMED by the embedded assert path "d:\tesla\bt\bt_l4\ +// BTL4RDR.CPP" (CalculateBounds line 0x224, DrawNames line 0x408). +// +// RE-HOST DELTAS vs the WinTesla-port donor (see GAUGE-BLOCK.NOTES.md, +// btl4rdr section): +// * the radar entity lists are fed by the AUTHENTIC interesting-entity +// notifications (BTL4GaugeRenderer::NotifyOfNewInterestingEntity -> +// GaugeRenderer movingEntities/staticEntities) -- the port-only +// RebuildEntityGrid call and the three visible-radius cull blocks are +// retired, as is the port's fallback mech cross-blip branch; +// * KEPT (correctness-load-bearing): the rotation-only-assignment-then- +// translation-row-LAST compose, the true affine Invert, and the +// YawPitchRoll (yaw-first) heading decode in DrawDisplay; +// * the torso-twist view-wedge feed reads Mech::CurrentTorsoTwist() through +// a file-private connection (the reconstructed Torso::currentTwist is +// protected -- no raw 1995 byte offsets); +// * the "pip" symbol system is the engine's L4GaugeImage, cached in +// L4Warehouse::gaugeImageBin (resource type 0x12), used directly. +// + +#include +#pragma hdrstop + +#if !defined(BTL4RDR_HPP) +# include +#endif + +#if !defined(APP_HPP) +# include +#endif + +#if !defined(MISSION_HPP) +# include +#endif + +#if !defined(PLAYER_HPP) +# include +#endif + +#if !defined(MECH_HPP) +# include +#endif + +#if !defined(L4WRHOUS_HPP) +# include +#endif + +// +// Tuning constants recovered from the CODE constant pool. +// +static const Scalar CalcBoundsEpsilon = 0.0f; // _DAT_004c2284 +static const Scalar MetersPerPixelNum = 1.0f; // _DAT_004c2288 +static const Scalar OperatingEpsilon = 0.0f; // _DAT_004c2174 +static const Scalar HeadingHalf = 0.5f; // _DAT_004c2480 (half-angle) +static const Scalar ViewWedgeRadius = 10000.0f; // _DAT_004c25ac +static const Scalar MapVerticalSpan = 32768.0f; // 0x47000000 / 0xc7000000 + +// +// FUN_004c2f88 -- the fixed blip / name colour index used throughout the map. +// (Returns the literal 7 in the shipped binary.) +// +static int + MapBlipColor() +{ + return 7; +} + + +//########################################################################### +//########################################################################### +// File-local helpers +//########################################################################### +//########################################################################### + +// +// Entity placement helpers -- the entity keeps its world placement in +// localOrigin (linearPosition @+0x100 + angularPosition). These give the +// radar the Point3D position and AffineMatrix transform the draw loops use. +// +static inline Point3D & + EntityPosition(Entity *entity) +{ + return entity->localOrigin.linearPosition; +} + +static inline void + EntityTransform(AffineMatrix *out, Entity *entity) +{ + *out = entity->localOrigin; // entity+0xD0 +} + +// +// FUN_00417ab4 -- the gauge renderer's "operator entity" (the mech whose +// cockpit this gauge belongs to): the renderer's linked-entity socket +// (renderer+0x40), armed by the viewpoint-bind broadcast before the per-model +// gauges are configured (BTL4APP.CPP:451 ConfigureForModel). Renderer:: +// GetLinkedEntity has been public "for gauges" since 05/25/95. +// +static Entity * + ResolveOperatorEntity(GaugeRenderer *renderer) +{ + return (renderer != NULL) ? renderer->GetLinkedEntity() : NULL; +} + +// +// renderer+0x4C -- GaugeRenderer::warehousePointer: the L4Warehouse whose +// gaugeImageBin (+0x58) holds the radar "pip" vector shapes (L4GaugeImage, +// resource type 0x12; BTL4.RES ships 110 of them -- every mech / vehicle / +// building / tree). +// +static L4Warehouse * + GetGaugeWarehouse(GaugeRenderer *renderer) +{ + return (renderer != NULL) ? (L4Warehouse *)renderer->warehousePointer : NULL; +} + +// +// FUN_004688ce(warehouse+0x58, entity+0x1BC, 0) -- the refcounting bin Get, +// exactly as the binary's per-frame draw loops call it (one vector shape per +// model; the mission-end warehouse Purge reclaims the references). +// +static L4GaugeImage * + LookUpGaugeImage( + L4Warehouse *warehouse, + ResourceDescription::ResourceID resource_ID + ) +{ + return warehouse->gaugeImageBin.Get(resource_ID, False); +} + +// +// The "global name-bitmap cache": the current Mission's SMALL name-bitmap +// chain (prebuilt 64x16 egg rasters keyed 1-based by the egg 'bitmapindex'). +// +static BitMap * + LookUpNameBitmap(int name_ID) +{ + Mission + *mission = (application != NULL) + ? application->GetCurrentMission() + : NULL; + + if (mission == NULL || name_ID <= 0) + { + return NULL; + } + return mission->GetSmallNameBitmap(name_ID); +} + +// +// entity+0x21C -- Landmark::landmarkID (the static loop's label key into the +// LARGE name chain). DORMANT in shipped content: no runtime writer exists +// (the id was baked by the 1995 map tool) and no [landmarks] section exists +// in any egg -- kept faithful-but-INERT (no landmark ever labels). +// +static int + GetNameID(Entity * /*entity*/) +{ + return -1; +} + +// +// FUN_0041a1a4 vs 0x4e70c4 -- IsDerivedFrom(Landmark). Dormant with +// GetNameID above (no landmark content ships) -- INERT. +// +static Logical + IsLabelledEntity(Entity * /*entity*/) +{ + return False; +} + +// +// The operator's current target -- the owning BTPlayer's objectiveMech +// (binary: *(*(owner+0x190)+0x284)). INERT FEED: BTPlayer::objectiveMech +// exists in the reconstruction (btplayer.hpp) but is protected with no +// accessor reconstructed AND no writer yet (btplayer.cpp only NULLs it in the +// constructor), so the target always resolves NULL here -- blips and labels +// draw normally, the hot-box / flash highlight simply never engages. Wire +// through a BTPlayer accessor once the targeting wave reconstructs the +// objectiveMech writer. +// +static Entity * + GetTarget(Entity * /*owner*/) +{ + return NULL; +} + + +//########################################################################### +//########################################################################### +// MapTwistConnection (file-private) +// +// The binary wired a direct-of-Radian gauge connection onto the mech's +// "Torso" sub-object horizontal rotation (Torso::currentTwist, torso+0x1D8; +// roster walk FUN_0041f98c) so the view wedge follows the torso twist. +// RE-HOST (databinding rule): currentTwist is protected on the reconstructed +// Torso, so this connection reads the documented accessor +// Mech::CurrentTorsoTwist() (mech.hpp -- NULL-safe: 0 with no torso +// subsystem, matching the binary's no-torso -> heading-0 behaviour) and +// copies it into MapDisplay::viewHorizontalRotation every frame, per the +// engine GaugeConnection contract (GAUGE.HPP:151). +//########################################################################### +//########################################################################### + +class MapTwistConnection: + public GaugeConnection +{ +public: + MapTwistConnection( + int parameter_ID, + Radian *data_destination, + Mech *twist_source + ); + +protected: + void + Update(); + + Mech + *twistSource; + Radian + *dataDestination; +}; + +MapTwistConnection::MapTwistConnection( + int parameter_ID, + Radian *data_destination, + Mech *twist_source +): + GaugeConnection(parameter_ID) +{ + Check_Pointer(this); + Check_Pointer(data_destination); + Check_Pointer(twist_source); + + dataDestination = data_destination; + twistSource = twist_source; + + *dataDestination = twistSource->CurrentTorsoTwist(); + Check(this); +} + +void + MapTwistConnection::Update() +{ + Check(this); + Check_Pointer(dataDestination); + Check_Pointer(twistSource); + + *dataDestination = twistSource->CurrentTorsoTwist(); +} + + +//########################################################################### +//########################################################################### +// MapName +//########################################################################### +//########################################################################### + +// +// @004c19fc -- snapshot a tracked entity into one name-array slot: project +// its world position into the (already scaled) view space, look up its name +// bitmap from the mission's name cache, and flag whether it is the hot-boxed +// target. +// +void + MapName::ExtractFromEntity( + Entity *entity, + AffineMatrix &worldToView, + Entity *exclude_entity, + Entity *hotbox_entity + ) +{ + // + // The operator's own mech (exclude_entity) draws no name. A NULL entity + // (the binary's Verify(False) count/iterator-mismatch path, which + // compiles out at DEBUG_LEVEL 0) would crash EntityPosition(NULL) below + // -- guard it. + // + if (entity == NULL || entity == exclude_entity) + { + nameBitmap = NULL; // param_1[6] = 0 + return; + } + + Player + *owning_player = entity->GetOwningPlayer(); // *(entity+0x190) + + if (owning_player == NULL) + { + nameBitmap = NULL; // unowned (dummy/effect): no label + } + else + { + // + // player+0x1E0 playerBitmapIndex -> the Mission's SMALL name-raster + // chain (prebuilt 64x16 egg bitmaps, loaded 1-based by the mission's + // name-bitmap loader). + // + nameBitmap = LookUpNameBitmap(owning_player->playerBitmapIndex); + } + + // + // Project the entity origin into screen space and stamp the label. + // + center.Multiply(EntityPosition(entity), worldToView); // FUN_00408b98 + color = MapBlipColor(); // 7 + hotBoxed = (Logical)(entity == hotbox_entity); + + // + // The decompiled object always parks the name's pointer direction at + // (-1, 0, 0) -- labels are screen-aligned, not entity-aligned. + // + direction.x = -1.0f; + direction.y = 0.0f; + direction.z = 0.0f; +} + +// +// @004c1ab0 -- blit one extracted name bitmap at its projected screen point, +// in either its own colour or (when hot-boxed) the supplied highlight colour. +// +void + MapName::Draw( + GraphicsView *graphics_view, + int hotbox_color + ) +{ + if (nameBitmap == NULL) + { + return; + } + + graphics_view->MoveToAbsolute(Round(center.x), Round(center.z)); // vtbl+0x24 + + if (!hotBoxed) + { + graphics_view->SetColor(color); // vtbl+0x18 + } + else + { + graphics_view->SetColor(hotbox_color); + } + + graphics_view->DrawBitMap( // vtbl+0x54 + 0, nameBitmap, 0, 0, + nameBitmap->Data.Size.x - 1, + nameBitmap->Data.Size.y - 1 + ); +} + + +//########################################################################### +//########################################################################### +// MapDisplay +//########################################################################### +//########################################################################### + +//############################################################################# +// Method Description + Make -- the config "map" primitive registration glue. +// +// NOT in the assert-anchored decomp (the ctor @004c1c24 has no exported +// caller and the "map" string was not captured), so this is reconstructed +// from the config invocation (L4GAUGE.CFG:4923) + the ctor signature. The +// offset_position keyword "center"/"bottom" is typed as a STRING and +// converted in Make. +// +MethodDescription + MapDisplay::methodDescription = + { + "map", + MapDisplay::Make, + { + // map( rate, mode, (w,h), offsetPos, viewDeg, bg,static,hotbox, + // maxRange, scaleAttr, positionAttr, angleAttr, capabilitiesAttr ) + { ParameterDescription::typeRate, NULL }, // rate ID + { ParameterDescription::typeModeMask, NULL }, // mode mask + { ParameterDescription::typeVector, NULL }, // (width,height) + { ParameterDescription::typeString, NULL }, // offset position ("center"/"bottom") + { ParameterDescription::typeInteger, NULL }, // view width (degrees) + { ParameterDescription::typeColor, NULL }, // background colour + { ParameterDescription::typeColor, NULL }, // static/default colour + { ParameterDescription::typeColor, NULL }, // hotbox colour + { ParameterDescription::typeScalar, NULL }, // maximum range (metres) + { ParameterDescription::typeAttribute, NULL }, // scale (RadarRange) + { ParameterDescription::typeAttribute, NULL }, // position (RadarLinearPosition, Point3D*) + { ParameterDescription::typeAttribute, NULL }, // angle (RadarAngularPosition, Quaternion*) + { ParameterDescription::typeAttribute, NULL }, // capabilities (Avionics/RadarPercent) + PARAMETER_DESCRIPTION_END + } + }; + +Logical + MapDisplay::Make( + int display_port_index, + Vector2DOf position, + Entity *entity, + GaugeRenderer *gauge_renderer + ) +{ + ParameterDescription + *parameterList = methodDescription.parameterList; + +# if DEBUG_LEVEL > 0 + parameterList[0].CheckIt(ParameterDescription::typeRate); // rate ID + parameterList[1].CheckIt(ParameterDescription::typeModeMask); // mode mask + parameterList[2].CheckIt(ParameterDescription::typeVector); // size + parameterList[3].CheckIt(ParameterDescription::typeString); // offset pos + parameterList[4].CheckIt(ParameterDescription::typeInteger); // view width + parameterList[5].CheckIt(ParameterDescription::typeColor); // background + parameterList[6].CheckIt(ParameterDescription::typeColor); // static + parameterList[7].CheckIt(ParameterDescription::typeColor); // hotbox + parameterList[8].CheckIt(ParameterDescription::typeScalar); // max range + parameterList[9].CheckIt(ParameterDescription::typeAttribute); // scale + parameterList[10].CheckIt(ParameterDescription::typeAttribute); // position + parameterList[11].CheckIt(ParameterDescription::typeAttribute); // angle + parameterList[12].CheckIt(ParameterDescription::typeAttribute); // capabilities +# endif + + Check(gauge_renderer); + + OffsetPosition + offset = + (strcmp(parameterList[3].data.string, "bottom") == 0) + ? bottom + : center; + +# if DEBUG_LEVEL > 0 + MapDisplay + *map_display = +# endif + new MapDisplay( + parameterList[0].data.rate, + parameterList[1].data.modeMask, + (L4GaugeRenderer *) gauge_renderer, + 0, // owner_ID + display_port_index, + position.x, position.y, // left, bottom + position.x + parameterList[2].data.vector.x, // right + position.y + parameterList[2].data.vector.y, // top + offset, + parameterList[4].data.integer, // view width (degrees) + parameterList[5].data.color, // background colour + parameterList[6].data.color, // static colour + parameterList[7].data.color, // hotbox colour + parameterList[8].data.scalar, // maximum range + entity, + (Scalar *) parameterList[9].data.attributePointer, // scale (RadarRange) + (Point3D **) parameterList[10].data.attributePointer, // position (RadarLinearPosition) + (Quaternion **) parameterList[11].data.attributePointer, // angle (RadarAngularPosition) + (Scalar *) parameterList[12].data.attributePointer // capabilities (Avionics/RadarPercent) + + // HACK!!!! The attribute casts are VERY dangerous! (The native + // idiom -- see NumericDisplayScalar::Make, L4GAUGE.CPP.) + ); + Register_Object(map_display); + + return True; +} + +//############################################################################# +// Construction / Destruction +// +// @004c1c24 -- vtable 0051422c, base name "MapDisplay". nameArray, the two +// entity lists and previousDrawing are member objects (the decompiler's +// explicit Construct_Array / ctor thunks are the compiler-generated member +// construction). Sizes the graphics port from (left,bottom,right,top), +// allocates the radar shadow table, and wires the four data-source gauge +// connections (scale, position, angle, capabilities) plus an optional +// torso-twist feed. +// +MapDisplay::MapDisplay( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer, + unsigned int owner_ID, + int graphics_port_number, + int left, + int bottom, + int right, + int top, + OffsetPosition offset_position, + int view_width_in_degrees, + int bg_color, + int static_color, + int hotbox_color, + Scalar maximum_range, + Entity *entity, + Scalar *scale_value_pointer, + Point3D **position_pointer, + Quaternion **angle_pointer, + Scalar *capabilities_ratio, + Logical allow_rock_and_roll +): + GraphicGauge( // FUN_00444818 + rate, mode_mask, renderer, owner_ID, graphics_port_number, "MapDisplay" + ) +{ + // + // Size the graphics port and centre the origin. + // + localView.SetPositionWithinPort(left, bottom, right, top); // vtbl+0x08 + if (offset_position == MapDisplay::bottom) + { + localView.SetOrigin((right - left) >> 1, 0); // vtbl+0x10 + } + else + { + localView.SetOrigin((right - left) >> 1, (top - bottom) >> 1); + } + + backgroundColor = bg_color; // @0x358 + staticColor = static_color; // @0x35C + boxColor = hotbox_color; // @0x360 + maximumRange = maximum_range; // @0x2F0 + rockAndRoll = allow_rock_and_roll; // @0x370 + + halfWidth = (right - left) >> 1; // @0x364 + halfHeight = (top - bottom) >> 1; // @0x368 + offsetPosition = offset_position; // @0x2D0 + + // + // The radar shadow table: the binary allocates halfViewWidthInUnits*8 + // bytes = one ShadowRecord per half-degree column across the full view + // width. Never populated in the shipped build (BuildRadarShadow below + // compiled empty) -- allocated for fidelity. + // + halfViewWidthInUnits = view_width_in_degrees >> 1; // @0x3C0 + shadowTable = new ShadowRecord[halfViewWidthInUnits * 2]; // @0x3C4 + + viewHorizontalRotation = 0.0f; // @0x31C + viewHalfHorizontalWidth = 0.3926991f; // @0x320 = PI/8 + flashState = False; // @0x374 + + // + // Data-source connections: each is a small heap GaugeConnection wired so + // that the gauge framework copies the external value into a member field + // every frame. scale -> currentScale, position -> currentPositionPointer, + // angle -> currentAngularPointer, capabilities -> currentCapabilitiesRatio. + // + AddConnection( // (*this+0x34) + new GaugeConnectionDirectOf( + 0, ¤tScale, scale_value_pointer)); // FUN_00474855 + AddConnection( + new GaugeConnectionDirectOf( + 0, ¤tPositionPointer, position_pointer)); // FUN_004c2a3e + AddConnection( + new GaugeConnectionDirectOf( + 0, ¤tAngularPointer, angle_pointer)); // FUN_004c2bc0 + AddConnection( + new GaugeConnectionDirectOf( + 0, ¤tCapabilitiesRatio, capabilities_ratio)); // FUN_00474855 + + // + // If the host entity is a mech (MechClassID 0xBB9), feed its live torso + // twist into the map's view heading so the view wedge follows the torso. + // Binary: "Torso" roster walk (FUN_0041f98c) + direct-of-Radian + // connection onto torso+0x1D8 (FUN_004c2d42); re-host: the accessor-based + // MapTwistConnection above (see its comment banner). + // + if (entity->GetClassID() == (Entity::ClassID)MechClassID) + { + AddConnection( + new MapTwistConnection( + 0, &viewHorizontalRotation, Cast_Object(Mech *, entity))); + } +} + +// +// @004c1ea8 -- free the shadow table. previousDrawing, the two entity lists +// and the name array are member objects and are destroyed automatically +// after this body runs. +// +MapDisplay::~MapDisplay() +{ + delete[] shadowTable; // FUN_004022E8(this[0xF1]) + // base ~GraphicGauge -- FUN_00444870 +} + +// +// @004c1f30 +// +Logical + MapDisplay::TestInstance() const +{ + return GraphicGauge::TestInstance(); // FUN_004448ac +} + +// +// @004c1f40 -- emit "ThreatIndicator:" then chain to the base ShowInstance +// with a deeper (tab) indent. +// +void + MapDisplay::ShowInstance(char *indent) +{ + cout << indent << "ThreatIndicator:"; // FUN_004dbb24 x2 + + char + deeper[80]; + strcpy(deeper, indent); + strcat(deeper, "\t"); // FUN_004d49b8 + + GraphicGauge::ShowInstance(deeper); // FUN_004448bc +} + +// +// @004c1fc0 -- reset the per-frame phase counter when the gauge is shown. +// +void + MapDisplay::BecameActive() +{ + operatingPhase = 0; // @0x354 +} + +// +// @004c1fd0 -- the per-frame work, spread across four ticks (phases 0..3) so +// the cost is amortised: +// 0 decide whether the radar is powered, compute range, set up bounds +// 1 spatial-query static (terrain/structure) entities into the list +// 2 spatial-query moving (mech/vehicle) entities into the list +// 3 erase the previous frame and redraw the map +// +// The renderer's movingEntities / staticEntities records queried in phases +// 1/2 are populated by the AUTHENTIC interesting-entity notifications +// (BTL4GaugeRenderer::NotifyOfNewInterestingEntity, btl4grnd.cpp -- Mover -> +// movingEntities, Terrain -> staticEntities, gated on the entity owning a +// type-0x12 gauge-image resource). Note the authentic grid quirks the range +// cull below compensates for: GaugeEntityList::CopyEntitiesWithinBounds's +// per-entity box test is commented out in GAUGMAP.CPP (grid-cell granular +// only), so this gauge's maximumDistanceSquared test in DrawStatic/DrawMoving +// is the REAL range filter. +// +void + MapDisplay::Execute() +{ + int + phase = operatingPhase++; // @0x354 + + if (phase == 0) + { + operating = (Logical)(OperatingEpsilon < currentCapabilitiesRatio); + if (operating) + { + maximumDistanceSquared = currentCapabilitiesRatio * maximumRange; + maximumDistanceSquared = maximumDistanceSquared * maximumDistanceSquared; + CalculateBounds(); // FUN_004c2178 + } + } + else if (phase == 1) + { + staticEntityList.Clear(); // FUN_004435ac(this+0x378) + if (operating) + { + renderer->GetStaticEntitiesWithinBounds( // staticEntities @+0xB0 + &staticEntityList, + xMin, yMin, zMin, xMax, yMax, zMax); + } + } + else if (phase == 2) + { + movingEntityList.Clear(); // FUN_004435ac(this+0x394) + if (operating) + { + renderer->GetMovingEntitiesWithinBounds( // movingEntities @+0x94 + &movingEntityList, + xMin, yMin, zMin, xMax, yMax, zMax); + } + } + else + { + if (phase == 3) + { + EraseDisplay(); // FUN_004c2294 + if (operating) + { + DrawDisplay(); // FUN_004c22c4 + } + + // + // Re-host verification trace (worksheet open question: does the + // authentic NotifyOf*InterestingEntity feed populate the radar + // lists once RebuildEntityGrid is retired?). Logs only when the + // gathered counts change. + // + if (getenv("BT_MECH_LOG") != NULL) + { + static int previousStaticCount = -1; + static int previousMovingCount = -1; + + int static_count = staticEntityList.NumberOfItems(); + int moving_count = movingEntityList.NumberOfItems(); + if (static_count != previousStaticCount + || moving_count != previousMovingCount) + { + previousStaticCount = static_count; + previousMovingCount = moving_count; + DEBUG_STREAM << "MapDisplay: operating=" << (int)operating + << " static=" << static_count + << " moving=" << moving_count << endl << flush; + } + } + } + operatingPhase = 0; + } +} + +// +// @004c2178 -- CONFIRMED by the embedded assert path +// "d:\tesla\bt\bt_l4\BTL4RDR.CPP", line 0x224. +// +// Derive the metres<->pixels scale from the current zoom (currentScale) and +// build the world-space axis-aligned box that the spatial queries will use. +// The map is top-down, so the X and Z bounds hug the viewing position while +// the Y (vertical) bounds are opened wide (+/-32768). +// +void + MapDisplay::CalculateBounds() +{ + if (currentScale <= CalcBoundsEpsilon) + { + // + // "MapDisplay::CalculateBounds detected a bad scale" + // ("d:\tesla\bt\bt_l4\BTL4RDR.CPP", line 0x224) + // + Verify(False); // FUN_0040385c + currentScale = 1.0f; // 0x3f800000 + } + + pixelsPerMeter = (Scalar)(halfWidth * 2) / currentScale; + metersPerPixel = MetersPerPixelNum / pixelsPerMeter; // 1.0 / ppm + LODIndex = metersPerPixel; + + Scalar + halfWorldWidth = (Scalar)halfWidth / pixelsPerMeter; + Scalar + halfWorldHeight = (Scalar)halfHeight / pixelsPerMeter; + + Entity + *owner = ResolveOperatorEntity(renderer); // FUN_00417ab4(renderer+0x40) + Check_Pointer(owner); + viewingPosition = EntityPosition(owner); // FUN_00408440(this+0x310) + + xMin = viewingPosition.x - halfWorldWidth; + xMax = viewingPosition.x + halfWorldWidth; + yMin = -MapVerticalSpan; // 0xc7000000 + yMax = MapVerticalSpan; // 0x47000000 + zMin = viewingPosition.z - halfWorldHeight; + zMax = viewingPosition.z + halfWorldHeight; +} + +// +// @004c2294 -- erase last frame's footprint and forget it. +// +void + MapDisplay::EraseDisplay() +{ + previousDrawing.Draw(&localView, backgroundColor); // FUN_0044a650 + previousDrawing.Clear(); // FUN_0044a630 +} + +// +// @004c22c4 -- compose the world->view matrix (translate to the viewing +// position, optionally rotate by heading, then scale to pixels), then paint +// the wedge, the static and moving blips and the names, recording the +// touched region for next frame's erase. +// +void + MapDisplay::DrawDisplay() +{ + AffineMatrix + view; + view.BuildIdentity(); // FUN_0040aadc (the decomp shows the + view.BuildIdentity(); // identity built twice -- faithful) + + // + // Default the centre to the operator mech if no explicit feed is wired. + // + Entity + *owner = ResolveOperatorEntity(renderer); + if (currentPositionPointer == NULL && owner != NULL) + { + currentPositionPointer = &EntityPosition(owner); // owner+0x100 + } + + if (currentPositionPointer != NULL) + { + Point3D + center = *currentPositionPointer; // FUN_00408440 + + // + // TRANSCRIPTION FIX (the invisible-pip bug): FUN_0040adec writes ONLY + // the 3x3 rotation elements ([0..2],[4..6],[8..10]) -- it never + // touches the translation row. The binary therefore ends with + // view = [R(q) | center]: rotation SET, centre PRESERVED. A full + // compose (view *= yaw) would also rotate the translation + // (view = [R | center x R]) -- the subsequent Invert then maps + // center x R to the origin instead of the viewer, so every blip lands + // hundreds of pixels off-scope. Rotation first (rotation-only + // assignment), translation row LAST. + // + if (currentAngularPointer != NULL) + { + if (!rockAndRoll) + { + // + // Top-down: keep heading only. Extract the yaw, build a + // yaw-only quaternion, SET the rotation block from it. + // + // FIX (kept on re-host): YawPitchRoll (yaw-first), not + // EulerAngles -- the EulerAngles(Quaternion) decomposition is + // ambiguous for a yawing mech: as yaw sweeps past +/-pi the + // quaternion double-cover flips it onto a pitch=roll=pi + // branch, so euler.yaw REVERSES (the radar spins back). + // YawPitchRoll applies yaw FIRST -> a clean continuous 360 + // degree heading (ROTATION.HPP:162, operator= at :190). + // + YawPitchRoll + euler; + euler = *currentAngularPointer; + Scalar + heading = (Scalar)euler.yaw * HeadingHalf; // * 0.5f half-angle + + SinCosPair + sc; + sc = Radian(heading); // FUN_00408328 + Quaternion + yaw(0.0f, sc.sine, 0.0f, sc.cosine); // FUN_00409948 + view = yaw; // rotation-only (FUN_0040adec) + } + else + { + // + // "Rock and roll": use the full orientation. + // + view = *currentAngularPointer; // rotation-only build + } + } + view.SetFromAxis(W_Axis, center); // FUN_0040b0ac -- translation LAST + } + + // + // TRANSCRIPTION FIX (the invisible-pip bug): FUN_0040b244 is the full + // affine INVERSE (cofactor expansion + determinant divide), not a copy. + // `view` above is the OPERATOR's local->world pose (translate to the mech + // + yaw); the radar needs its inverse -- world->scope, centre subtracted, + // heading unrotated. Read as a copy, every pip/name transforms to + // (world + centre) x ppm = thousands of pixels off-scope: the scope draws + // empty at all times. + // + worldToView.Invert(view); // FUN_0040b244(this+0x324) + + // + // Bake the metres->pixels scale into the matrix. + // + Vector3D + scale; + scale.x = scale.y = scale.z = pixelsPerMeter; + worldToView.Multiply(worldToView, scale); // FUN_0040b374 + + BuildRadarShadow(worldToView); // FUN_004c228c + + flashState = (Logical)!flashState; // @0x374 ^= 1 + + localView.AttachRecorder(&previousDrawing); // vtbl+0x64 + DrawViewWedge(); // FUN_004c2484 + DrawStatic(worldToView); // FUN_004c25b4 + DrawMoving(worldToView); // FUN_004c2788 + DrawNames(worldToView); // FUN_004c28c4 + localView.DetachRecorder(); // vtbl+0x68 +} + +// +// @004c228c -- BuildRadarShadow. In the shipped build this compiled to an +// empty body (the shadow-cast pass is disabled), so the allocated shadowTable +// is never populated. Reconstructed as a no-op to match the binary. +// (BEST-EFFORT: the intended algorithm -- ray-marching the shadowTable per +// half-degree column -- is not present in the captured object.) +// +void + MapDisplay::BuildRadarShadow(AffineMatrix & /*worldToView*/) +{ +} + +// +// @004c2484 -- draw the two edges of the player's field-of-view wedge as +// lines from the map centre out to a far (clipped) radius. +// +void + MapDisplay::DrawViewWedge() +{ + int + i; + + Radian + leftEdge = viewHorizontalRotation - viewHalfHorizontalWidth; + Radian + rightEdge = viewHorizontalRotation + viewHalfHorizontalWidth; + + localView.SetColor(staticColor); // vtbl+0x18 + + Radian + edges[2]; + edges[0] = leftEdge; + edges[1] = rightEdge; + + for (i = 0; i < 2; ++i) + { + Scalar + angle = -edges[i]; + SinCosPair + sc; + sc = Radian(angle); // FUN_00408328 + + Scalar + x = sc.sine * ViewWedgeRadius; // * 10000.0f + Scalar + z = sc.cosine * ViewWedgeRadius; + + localView.MoveToAbsolute(0, 0); // vtbl+0x24 (centre) + localView.DrawLineToAbsolute(Round(x), Round(z)); // vtbl+0x30 + } +} + +// +// @004c25b4 -- plot every in-range static entity as a blip, plus a name +// bitmap for the special (labelled) ones. +// +void + MapDisplay::DrawStatic(AffineMatrix &worldToView) +{ + ChainIteratorOf + iter(staticEntityList.GetInstanceList()); // FUN_0042ac1c(this+0x384) + iter.First(); + + L4Warehouse + *pips = GetGaugeWarehouse(renderer); // *(renderer+0x4C) + + for (;;) + { + Entity + *entity = iter.ReadAndNext(); // vtbl+0x28 + if (entity == NULL) + { + break; + } + + // + // The REAL range filter (the grid query is cell-granular only -- + // see the Execute banner). + // + Vector3D + delta; + delta.Subtract(EntityPosition(entity), viewingPosition); // FUN_00408644 + if (delta.x*delta.x + delta.y*delta.y + delta.z*delta.z + > maximumDistanceSquared) + { + continue; + } + + L4GaugeImage + *pip = (pips != NULL) + ? LookUpGaugeImage(pips, entity->GetResourceID()) // FUN_004688ce + : NULL; + if (pip != NULL) + { + AffineMatrix + blipXform; + AffineMatrix + entityXform; + EntityTransform(&entityXform, entity); + blipXform.Multiply(entityXform, worldToView); // FUN_0040b104 + pip->Draw( // FUN_0046f0c0 + LODIndex, metersPerPixel, &localView, + staticColor, blipXform, 0); + } + + // + // Labelled static entities (e.g. nav beacons) also blit a name + // bitmap. (Dormant: no landmark content ships -- see the inert + // helpers above.) + // + if (IsLabelledEntity(entity)) // FUN_0041a1a4 (Landmark 0x4e70c4) + { + int + nameID = GetNameID(entity); // entity+0x21C + BitMap + *bitmap = LookUpNameBitmap(nameID); + if (bitmap != NULL) + { + Point3D + screen; + screen.Multiply(EntityPosition(entity), worldToView); // FUN_00408b98 + localView.MoveToAbsolute(Round(screen.x), Round(screen.z)); + localView.SetColor(MapBlipColor()); // 7 + localView.DrawBitMap(0, bitmap, 0, 0, + bitmap->Data.Size.x - 1, bitmap->Data.Size.y - 1); + } + } + } +} + +// +// @004c2788 -- plot every in-range moving entity (mechs, vehicles) as a +// blip, skipping the operator's own mech and boxing the current target. +// +void + MapDisplay::DrawMoving(AffineMatrix &worldToView) +{ + ChainIteratorOf + iter(movingEntityList.GetInstanceList()); // FUN_0042ac1c(this+0x3A0) + iter.First(); + + L4Warehouse + *pips = GetGaugeWarehouse(renderer); // *(renderer+0x4C) + Entity + *owner = ResolveOperatorEntity(renderer); + Entity + *target = GetTarget(owner); // objectiveMech (inert feed) + + for (;;) + { + Entity + *entity = iter.ReadAndNext(); + if (entity == NULL) + { + break; + } + + if (entity == owner) + { + continue; + } + + Vector3D + delta; + delta.Subtract(EntityPosition(entity), viewingPosition); // FUN_00408644 + if (delta.x*delta.x + delta.y*delta.y + delta.z*delta.z + > maximumDistanceSquared) + { + continue; + } + + L4GaugeImage + *pip = (pips != NULL) + ? LookUpGaugeImage(pips, entity->GetResourceID()) + : NULL; + if (pip != NULL) + { + AffineMatrix + blipXform; + AffineMatrix + entityXform; + EntityTransform(&entityXform, entity); + blipXform.Multiply(entityXform, worldToView); // FUN_0040b104 + + int + boxColour = (entity == target) ? boxColor : 0; // highlight target + pip->Draw( // FUN_0046f0c0 + LODIndex, metersPerPixel, &localView, + MapBlipColor() /*7*/, blipXform, boxColour); + } + } +} + +// +// @004c28c4 -- CONFIRMED by the embedded assert path +// "d:\tesla\bt\bt_l4\BTL4RDR.CPP", line 0x408. +// +// Snapshot up to maximumNames moving entities into the name array, then draw +// their labels. The hot-box colour flashes (flashState) on the target. +// +void + MapDisplay::DrawNames(AffineMatrix &worldToView) +{ + int + i; + + Entity + *owner = ResolveOperatorEntity(renderer); + Entity + *target = GetTarget(owner); // objectiveMech (inert feed) + + ChainIteratorOf + iter(movingEntityList.GetInstanceList()); // FUN_0042ac1c(this+0x3A0) + iter.First(); + + int + count = iter.GetSize(); // vtbl+0x14 + if (count < 1) + { + return; + } + if (count > maximumNames) + { + count = maximumNames; + } + + // + // First pass: extract. + // + for (i = 0; i < count; ++i) + { + Entity + *entity = iter.ReadAndNext(); // vtbl+0x28 + if (entity == NULL) + { + // + // "MapDisplay::DrawNames -- GetSize() / iterator mismatch" + // ("d:\tesla\bt\bt_l4\BTL4RDR.CPP", line 0x408) + // + Verify(False); // FUN_0040385c + } + nameArray[i].ExtractFromEntity(entity, worldToView, owner, target); // FUN_004c19fc + } + + // + // Second pass: draw. The target's label flashes in boxColor. + // + int + hotboxColor = flashState ? boxColor : 0; + for (i = 0; i < count; ++i) + { + nameArray[i].Draw(&localView, hotboxColor); // FUN_004c1ab0 + } +} diff --git a/restoration/source410/BT_L4/GAUGE-BLOCK.NOTES.md b/restoration/source410/BT_L4/GAUGE-BLOCK.NOTES.md index d71a619c..d76a7bef 100644 --- a/restoration/source410/BT_L4/GAUGE-BLOCK.NOTES.md +++ b/restoration/source410/BT_L4/GAUGE-BLOCK.NOTES.md @@ -401,3 +401,41 @@ btl4grnd.cpp:136-154: '&ColorMapperHeat::methodDescription, // "cmHeat" ... &BTL - The second BitMapInverseWipe base @004c5e84 and its Scalar variant @004c61c8 are notes-only (not reconstructed); if btl4gau2's LeakGauge subclassing needs them they are missing code. - HeadingPointer uses YawPitchRoll where the binary used EulerAngles (FUN_0040954c) — a deliberate anti-glitch deviation; decide whether byte-fidelity or the fix wins for the 4.10 literal reconstruction. - ColorMapperMultiArmor::Make keeps the binary's `index != 0` presence-test quirk (a valid zone 0 and a -1 'unused' both count as present) — faithful, but flag if zone 0 semantics ever matter. + + +============================================================================= +# 5.3.26 LANDING REPORT (the first rendered milestone -- gauges LIVE) + +All six TUs re-hosted (4-agent workflow + integration) and the block BOOTS: +L4GAUGE=640x480x16 + HEAPSIZE=15000000 (gauge.conf / gaugefight.conf). +Verified: the interpreter parses the FULL cfg through the 19-entry BT +registry (only warn = the absent 'Initialization' label -- the BT cfg has +none, graceful skip); Bhk1Init RUNS (case-insensitive label match on +"bhk1"+"Init"); ColorMapperArmor Makes resolve zone NAMES against the live +mech (8 cross-chassis warnings = the authentic -1 inert path); the full +mutual missile fight runs 110s with the cockpit stack live (16/16 rounds, +138 zone hits, zero faults); gauges-off regressions untouched. + +ENGINE DEVIATION (bring-up, load-bearing): ParameterDescription:: +ParseAttribute normalizes unpublished attributes to a shared 64-byte zero +cell (gaugeUnboundCell) instead of NULL -- GaugeConnectionDirectOf's 1995 +ctor hard-derefs its source (the first boot crashed exactly there, +@GaugeConnectionDirectOf::ctor). Widgets on unpublished names draw +static zeros and LIGHT UP automatically when the attribute waves publish +(the agents baked the 1995 CFG spellings into every binding: HeatLoad / +CoolantAvailable / CurrentTemperature / Degradation~ / Failure~ / +CoolantMassLeakRate / CurrentSeekVoltageIndex / Min~ / Max~ / SeekVoltage / +RadarRange / RadarLinearPosition / RadarAngularPosition / RadarPercent). + +WHAT'S INERT UNTIL ITS FEED WAVE (full lists in the agent reports above): +seek-response curves (vtbl-slot-15 sampler), cooling-loop lamp frames, +aux-screen numbers/labels (resource freed post-ctor -- roster-order +stand-in), ballistic ammo numerics (ammoBinLink protected), heat-family +named attribute rows, radar family, MessageBoard feed (StatusMessagePool), +name bitmaps, TeamStatusDisplay (prose-only donor, deferred). + +NEXT gauge steps: publish the heat/seek/radar attribute rows (small HPP +tables -- lights half the panels), add BTPlayer::GetKillCount() and the +ammoBinLink accessor, then the VISUAL pass on the rig (emulator VPXLOG=1 + +VDB head windows / pod-launch --layout explode) diffing against the shipped +exe on the same GAUGE content. diff --git a/restoration/source410/MUNGA/GAUGREND.CPP b/restoration/source410/MUNGA/GAUGREND.CPP index b17805cf..93fd499d 100644 --- a/restoration/source410/MUNGA/GAUGREND.CPP +++ b/restoration/source410/MUNGA/GAUGREND.CPP @@ -2137,6 +2137,15 @@ void Check_Fpu(); } +// +// BRING-UP zero cell: an unpublished attribute binds HERE instead of NULL +// (GaugeConnectionDirectOf's 1995 ctor hard-derefs its source). 64 zeroed, +// writable bytes cover any consumer shape (Scalar/int/Point3D*/Quaternion*) +// -- the widget draws a static zero until the attribute wave publishes the +// real member, then lights up with no gauge-code change. +// +static char gaugeUnboundCell[64]; + void * ParameterDescription::ParseAttribute( const char *string_pointer, @@ -2178,7 +2187,7 @@ void * { Tell("parseAttribute - subsystem '" << source << "' not found\n"); Check_Fpu(); - return NULL; + return (void *)gaugeUnboundCell; } else { @@ -2202,7 +2211,8 @@ void * DEBUG_STREAM << "[attr] " << string_pointer << (attribute_pointer ? " OK\n" : " NULL\n") << flush; Check_Fpu(); - return attribute_pointer; + return (attribute_pointer != NULL) + ? attribute_pointer : (void *)gaugeUnboundCell; } } } @@ -2223,7 +2233,8 @@ void * DEBUG_STREAM << "[attr] " << source << (attribute_pointer ? " OK (entity)\n" : " NULL (entity)\n") << flush; Check_Fpu(); - return attribute_pointer; + return (attribute_pointer != NULL) + ? attribute_pointer : (void *)gaugeUnboundCell; } Logical