diff --git a/game/reconstructed/btl4gau2.cpp b/game/reconstructed/btl4gau2.cpp index 8faaecd..b494898 100644 --- a/game/reconstructed/btl4gau2.cpp +++ b/game/reconstructed/btl4gau2.cpp @@ -102,6 +102,129 @@ static char DAT_00518f68[] = ""; // background image name // static int FindAttributeIndex(Entity *, const char *) { return -1; } +// +// FUN_0041bfc0 == Entity::FindAttributeIndex(name) returns a POINTER into the +// subsystem (subsystem + attributeMemberOffset), or 0 on miss. The composite +// cluster gauges pass that pointer straight to their child gauges as the value +// source. The WinTesla engine exposes exactly this via Simulation:: +// GetAttributePointer(const char*) (SIMULATE.h), which returns a member pointer +// or &Simulation::NullAttribute (always safe to deref) -- so we resolve through +// it. A null subsystem -> 0. +// +static void *AttributePointerOf(void *subsystem, const char *name) +{ + if (subsystem == NULL) + return NULL; + void *p = ((Simulation *)subsystem)->GetAttributePointer(name); // FUN_0041bfc0 + // GetAttributePointer returns &NullAttribute (not 0) on a miss; the composite + // ctors gate optional children on a 0 result, so normalise the miss to NULL. + if (p == (void *)&Simulation::NullAttribute) + return NULL; + return p; +} + +// +// FUN_00417ab4 -- follow a SharedData/plug link to the resolved subsystem (voltage +// source / cooling master). The reconstruction stubs this everywhere (see +// emitter.cpp) because the roster plug-binding pass is not yet reconstructed; +// matched here so the cluster connections read the safe "no source" branch +// (*destination = 0) until the plug binding lands. BEST-EFFORT (marked). +// +static void *ResolveLink(void *plug) { (void)plug; return 0; } + +// +// FUN_0041bf44 -- fetch the subsystem linked at slot N (used by the cooling-loop +// lamp to find its cooling master). Stubbed to match ResolveLink above. +// +static void *FindLinkedSubsystem(void *subsystem, int slot) { (void)subsystem; (void)slot; return 0; } + +// +// FUN_0041a1a4 IsDerivedFrom(0x50f4bc == Generator's ClassDerivations). Gates the +// SubsystemCluster's generator stateLamp. BEST-EFFORT: the Generator +// ClassDerivations object is owned by the (separately-reconstructed) gnrator TU +// which must not be pulled into this gauge TU (stub-collision, see btl4gaug.hpp), +// so the check is stubbed to False -- the generator-only stateLamp is simply not +// built. Lands with a shared IsDerivedFrom accessor. +// +static Logical IsGeneratorDerived(Subsystem *) { return False; } + + +//########################################################################### +// File-private GaugeConnection classes for the composite-cluster lamps. +// Each is the reconstruction of one of the three shared connection ctors +// the sub-gauges attach (mirrors the btl4gaug.cpp connection pattern: derive +// GaugeConnection, store dst/src, override Update() = the vtable sampler). +//########################################################################### + +// +// CoolingLoopConnection -- @004c3134 ctor / @004c31a0 sampler (vt 0x518ea8). +// When the source subsystem's coolant loop is active (source+0x134 == 1) it +// follows the linked cooling master (FindLinkedSubsystem(source,3) then Resolve) +// and copies its loop-lamp state (+0x1d4); otherwise 0. +// +class CoolingLoopConnection : public GaugeConnection +{ +public: + CoolingLoopConnection(int *destination, void *source) + : GaugeConnection(0), // FUN_00444124(this,0) + source(source), destination(destination) {} + void Update() // @004c31a0 + { + void *resolved = 0; + if (source != 0 && *(int *)((char *)source + 0x134) == 1) + { + void *linked = FindLinkedSubsystem(source, 3); + if (linked != 0) + resolved = ResolveLink(linked); + } + *destination = (resolved == 0) ? 0 : *(int *)((char *)resolved + 0x1d4); + } +protected: + void *source; // @0x10 this[4] + int *destination; // @0x14 this[5] +}; + +// +// PowerSourceConnection -- @004c31ec ctor / @004c3258 sampler (vt 0x518e9c). +// Resolves the power source (Resolve(source)) and copies its bus-lamp state +// (+0x1e0); 0 if unresolved. +// +class PowerSourceConnection : public GaugeConnection +{ +public: + PowerSourceConnection(int *destination, void *source) + : GaugeConnection(0), source(source), destination(destination) {} + void Update() // @004c3258 + { + void *resolved = ResolveLink(source); + *destination = (resolved == 0) ? 0 : *(int *)((char *)resolved + 0x1e0); + } +protected: + void *source; // @0x10 + int *destination; // @0x14 +}; + +// +// GeneratorVoltageConnection -- @004c3288 ctor / @004c32f4 sampler (vt 0x518e90). +// Resolves the voltage source and copies its output voltage (+0x1dc); 0 if +// unresolved. Drives ScalarBarGauge / SeekVoltageGraph value slots. +// +class GeneratorVoltageConnection : public GaugeConnection +{ +public: + GeneratorVoltageConnection(Scalar *destination, void *source) + : GaugeConnection(0), source(source), destination(destination) {} + void Update() // @004c32f4 + { + void *resolved = ResolveLink(source); + *destination = (resolved == 0) ? 0.0f + : *(Scalar *)((char *)resolved + 0x1dc); + } +protected: + void *source; // @0x10 + Scalar *destination; // @0x14 +}; + //########################################################################### //########################################################################### @@ -170,47 +293,215 @@ SeekVoltageGraph::SeekVoltageGraph( //########################################################################### // ConfigMapGauge @004c6d80 //########################################################################### + // // @004c6d80 -- ctor (vtable PTR_FUN_0051a1b8). GraphicGauge base; interns the -// joystick base bitmap "btjoy.pcc" (BitMapCache) and the four config-state -// pixmaps cm_off/cm_other/cm_only/cm_both.pcc (PixMapCache); sets the port -// origin (x,y); subsystem = param_9. -// @004c6e54 -- dtor: release btjoy bitmap + 4 pixmaps, GraphicGauge::~GraphicGauge. -// @004c6ee0 -- SetColor: this[0x25] = color. -// @004c6ef4 -- BecameActive: dirty=1; previousState[0..3] = -1. -// @004c6f1c -- Execute. When dirty, blit the base joystick bitmap. Then for -// each of the 4 config slots, read the control-mapper state via the table -// DAT_00518eb8 (renderer+0x3c port + 0x1c0 + slot*0x20, sampler vtbl+0x24) and, -// if it changed, MoveTo + stretch-blit the matching cm_* pixmap. +// joystick base bitmap "btjoy.pcc" (BitMapCache AddRef) + the four config-state +// pixmaps cm_off/cm_other/cm_only/cm_both.pcc (PixMapCache AddRef); sets the port +// origin (x,y); subsystem = param_9; color = 0. // +ConfigMapGauge::ConfigMapGauge( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer_in, + int graphics_port_number, + int x, + int y, + Entity *subsystem_in, + const char *identification_string +): + GraphicGauge(rate, mode_mask, renderer_in, 0 /*owner*/, // FUN_00444818 + graphics_port_number, identification_string) +{ + joystickImage = (char *)"btjoy.pcc"; // @0x98 this[0x26] + stateImage[0] = (char *)"cm_off.pcc"; // @0x9C this[0x27] + stateImage[1] = (char *)"cm_other.pcc"; // @0xA0 this[0x28] + stateImage[2] = (char *)"cm_only.pcc"; // @0xA4 this[0x29] + stateImage[3] = (char *)"cm_both.pcc"; // @0xA8 this[0x2A] + + localView.SetOrigin(x, y); // vtbl+0x10 (this+0x48) + + color = 0; // @0x94 this[0x25] + subsystem = subsystem_in; // @0x90 this[0x24] + + L4Warehouse *warehouse = (L4Warehouse *)renderer_in->warehousePointer; + warehouse->bitMapBin.Get(joystickImage); // FUN_00442aec + for (int i = 0; i < 4; i++) + warehouse->pixelMap8Bin.Get(stateImage[i]); // FUN_00442d2b +} + +// +// @004c6e54 -- dtor: release btjoy bitmap + 4 pixmaps, GraphicGauge::~GraphicGauge. +// +ConfigMapGauge::~ConfigMapGauge() +{ + L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer; + warehouse->bitMapBin.Release(joystickImage); // FUN_00442c12 + for (int i = 0; i < 4; i++) + warehouse->pixelMap8Bin.Release(stateImage[i]); // FUN_00442e51 +} + +Logical + ConfigMapGauge::TestInstance() const +{ + return GraphicGauge::TestInstance(); +} + +// +// @004c6ee0 -- SetColor: color (this[0x25]) = the passed value. +// +void + ConfigMapGauge::SetColor(int color_in) // @004c6ee0 +{ + color = color_in; +} + +// +// @004c6ef4 -- BecameActive: force a full redraw (dirty=1, previousState = -1). +// +void + ConfigMapGauge::BecameActive() +{ + dirty = True; // @0xBC this[0x2F] + for (int i = 0; i < 4; i++) + previousState[i] = -1; // @0xAC this[0x2B..0x2E] +} + +// +// @004c6f1c -- Execute. When enabled (color != 0): on the dirty flag, blit the +// base joystick bitmap; then for each of the 4 config-map slots read the control- +// mapper state and, if changed, stretch-blit the matching cm_* pixmap. +// +// BRING-UP (marked): the per-slot control-mapper state comes from the App's +// ModeManager sampled through a .data port/colour table (DAT_00518eb8) at raw +// offsets (app+0x3c -> +0x1c0 + slot*0x20, sampler vtbl+0x24). That is the same +// raw-App-offset read class that AVs before the ModeManager layout is +// reconstructed (see the FillPilotArray fix, ยง10), and DAT_00518eb8 was not +// recovered from the optimised image -- so the mapper-state loop is guarded off +// and only the base-bitmap blit runs. The full loop lands with the ModeManager +// reconstruction. +// +void + ConfigMapGauge::Execute() +{ + if (color == 0) + return; + + if (dirty) + { + dirty = False; + L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer; + BitMap *base = warehouse->bitMapBin.Get(joystickImage); // FUN_00442aec + if (base != NULL) + { + localView.SetColor(0xFF); // vtbl+0x18 + localView.DrawBitMapOpaque(0 /*background*/, 0 /*rotation*/, base); // vtbl+0x54 + } + warehouse->bitMapBin.Release(joystickImage); + } + // (control-mapper state loop deferred -- see note above.) +} //########################################################################### // OneOfSeveral data-driven lamps @004c70a4 / @004c7160 //########################################################################### + // -// @004c70a4 -- AnimatedSubsystemLamp ctor (vtable PTR_FUN_0051a174): chains -// OneOfSeveral::OneOfSeveral (@004c4d88, fromImageStrip=1), selected=0, then -// AddConnection(new (&selected, source)). dtor @004c7134. +// @004c70a4 -- AnimatedSubsystemLamp ctor (vtable PTR_FUN_0051a174): OneOfSeveral +// (fromImageStrip=1) + a CoolingLoopConnection feeding the selected frame from +// the subsystem's cooling-loop state. Otherwise inherits OneOfSeveral::Execute. // -// @004c7160 -- AnimatedSourceLamp ctor (vtable PTR_FUN_0051a130): identical but -// the connection ctor is @004c31ec. dtor @004c71f0. +AnimatedSubsystemLamp::AnimatedSubsystemLamp( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer_in, + int graphics_port_number, + int x, + int y, + const char *image, + int columns, + int rows, + void *subsystem, + const char *identification_string +): + OneOfSeveral(rate, mode_mask, renderer_in, graphics_port_number, // FUN_004c4d88 + x, y, True /*fromImageStrip*/, image, 0, 0, columns, rows, + identification_string) +{ + selected = 0; // @0xAC this[0x2B] + AddConnection(new CoolingLoopConnection(&selected, subsystem)); // FUN_004c3134 +} + +AnimatedSubsystemLamp::~AnimatedSubsystemLamp() {} // @004c7134 (base chain) + // -// Both add the connection via (*this+0x34) AddConnection and otherwise inherit -// OneOfSeveral::Execute @004c4f28 (frame select + blit). +// @004c7160 -- AnimatedSourceLamp ctor (vtable PTR_FUN_0051a130): identical shape +// but a PowerSourceConnection (@004c31ec) drives the frame from the resolved +// power source's bus-lamp state. // +AnimatedSourceLamp::AnimatedSourceLamp( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer_in, + int graphics_port_number, + int x, + int y, + const char *image, + int columns, + int rows, + void *source, + const char *identification_string +): + OneOfSeveral(rate, mode_mask, renderer_in, graphics_port_number, + x, y, True, image, 0, 0, columns, rows, identification_string) +{ + selected = 0; + AddConnection(new PowerSourceConnection(&selected, source)); // FUN_004c31ec +} + +AnimatedSourceLamp::~AnimatedSourceLamp() {} // @004c71f0 //########################################################################### -// ScalarBarGauge @004c721c / @004c72ac +// ScalarBarGauge @004c721c //########################################################################### + // -// @004c721c -- ctor (vtable PTR_FUN_0051a0e4): chains the MUNGA L4 bar primitive -// (@00472ef0) then AddConnection(new (&value, source)). -// @004c72ac -- same vtable, but AddConnection(new GaugeConnectionDirectOf -// (@00474855)(&value, scalar)). Both share dtor @004c733c (chains @00472fb4). -// Used as the generator-voltage bar with range 0..12000.0f. +// @004c721c -- ctor (vtable PTR_FUN_0051a0e4). Chains the MUNGA L4 bar primitive +// (@00472ef0 == BarGraphBitMapScalar) with no auto-connection (value=NULL), then +// AddConnection(new GeneratorVoltageConnection @004c3288) writing the inherited +// WipeGaugeScalar::currentValue (@0xB4, this[0x2D]) from the resolved voltage +// source. Generator-voltage bar, range 0..12000.0f. dtor @004c733c chains the +// bar dtor @00472fb4 (implicit). // +ScalarBarGauge::ScalarBarGauge( + GaugeRate rate, + ModeMask mode_mask, + L4GaugeRenderer *renderer_in, + int graphics_port_number, + int left, + int bottom, + int right, + int top, + const char *tile_image, + int foreground_color, + int background_color, + WipeGaugeScalar::Direction direction, + Scalar min, + Scalar max, + void *voltage_source, + const char *identification_string +): + BarGraphBitMapScalar(rate, mode_mask, renderer_in, 0 /*owner*/, // FUN_00472ef0 + graphics_port_number, left, bottom, right, top, tile_image, + foreground_color, background_color, direction, min, max, + (Scalar *)0 /*no auto-connection*/, identification_string) +{ + AddConnection(new GeneratorVoltageConnection(¤tValue, voltage_source)); // FUN_004c3288 +} + +ScalarBarGauge::~ScalarBarGauge() {} // @004c733c (base chain) //########################################################################### @@ -385,4 +676,788 @@ Logical // wipe is mid-cycle. dtor @004c9940 frees the GraphicsViewRecord + numeric. // +// +// FUN_004c2ec4 -- draw a clipped port-background bitmap: MoveTo(x,y), fetch the +// tile from the BitMap cache, opaque-blit it, release. (Used by the cluster +// ctor to paint the panel's static frame image into the MFD port.) +// +static void DrawPortBackground(GraphicsView *view, L4Warehouse *warehouse, + int x, int y, const char *tile) +{ + view->MoveToAbsolute(x, y); // vtbl+0x24 + BitMap *bmp = warehouse->bitMapBin.Get(tile); // FUN_00442aec + if (bmp != NULL) + view->DrawBitMapOpaque(0 /*background*/, 0 /*rotation*/, bmp); // vtbl+0x58 + warehouse->bitMapBin.Release(tile); // FUN_00442c12 +} + +// A default per-child load-distribution rate (the ctor uses FUN_004442b8/dc/290, +// the NextNthTierGaugeRate allocators). +static inline GaugeRate ChildRate() { return Gauge::NextThirdTierGaugeRate(); } + + +//########################################################################### +// SubsystemCluster @004c8140 (vtable 0x51a020) +//########################################################################### + +// +// @004c8140 -- ctor. rate/mode are hardcoded 0xFFFF/0xFFFFFFFF in the base +// GraphicGauge; the two bit-plane masks are mfd_mode (MFD-port children) and +// eng_mode (engineering-port children). Builds the panel's child gauges (see +// the header comment block) parented to the MFD port (param_5) and the engineering +// port (resolved from eng_port_name); optional children gate on the InputVoltage +// attribute (power bus present) and the damage-zone-index background image. +// +SubsystemCluster::SubsystemCluster( + ModeMask mfd_mode, + L4GaugeRenderer *renderer_in, + int owner_ID, + int mfd_port, + int x, + int y, + Subsystem *subsystem_in, + ModeMask eng_mode, + const char *eng_port_name, + const char *tile_image, + const char *label, + char **image_names, + const char *identification_string +): + GraphicGauge((GaugeRate)0xFFFF, (ModeMask)0xFFFFFFFF, renderer_in, // FUN_00444818 + owner_ID, mfd_port, identification_string) +{ + L4Warehouse *warehouse = (L4Warehouse *)renderer_in->warehousePointer; + + // Resolve the temperature / power attributes off the subsystem. + void *inputVoltage = AttributePointerOf(subsystem_in, "InputVoltage"); + void *heatSink = AttributePointerOf(subsystem_in, "HeatSink"); + void *currentTemp = AttributePointerOf(subsystem_in, "CurrentTemperature"); + void *degradeTemp = AttributePointerOf(subsystem_in, "DegradationTemperature"); + void *failTemp = AttributePointerOf(subsystem_in, "FailureTemperature"); + + //------------------------------------------------------------------ MFD port + temperatureBar = new HorizTwoPartBar(ChildRate(), mfd_mode, renderer_in, // @004c4170 + mfd_port, x + 0x13, y + 0x10, x + 0xdc, y + 0x1f, tile_image, + 0xff, 0, (Scalar *)currentTemp, (Scalar *)degradeTemp, (Scalar *)failTemp, + "HorizTwoPartBar"); + + coolingLoopA = new AnimatedSubsystemLamp(ChildRate(), mfd_mode, renderer_in, // @004c70a4 + mfd_port, x + 0xe5, y + 0xe, "btploop.pcc", 7, 1, subsystem_in, "CoolingLoop"); + + if (inputVoltage == NULL) + powerSourceA = NULL; + else + powerSourceA = new AnimatedSourceLamp(ChildRate(), mfd_mode, renderer_in, // @004c7160 + mfd_port, x + 0x10e, y + 0xe, "btpbus.pcc", 5, 1, + inputVoltage, "PowerSource"); + + // Background: image_names[ subsystem damageZoneIndex@0x1e0 ] if present. + int dzIndex = *(int *)((char *)subsystem_in + 0x1e0); // BEST-EFFORT raw offset + background = NULL; + if (dzIndex >= 0 && image_names[dzIndex] != NULL) + background = new BackgroundBitmap(mfd_mode, renderer_in, owner_ID, // @00471d00 + mfd_port, x + 0x90, y + 0x36, image_names[dzIndex], 0, 0xff, 0, + "BackgroundBitmap"); + + // Paint the panel frame image into the MFD port. + localView.SetOrigin(x, y); // vtbl+0x10 + localView.SetColor(0xff); // vtbl+0x18 + DrawPortBackground(&localView, warehouse, 9, 0xb4, label); // FUN_004c2ec4 + + //----------------------------------------------------------- engineering port + int engPort = renderer_in->FindGraphicsPort(eng_port_name); // FUN_00447f1c (name -> port number) + + // Attributes off the linked heat subsystem (Resolve(heatSink)). + void *linkedHeat = ResolveLink(heatSink); // FUN_00417ab4 + void *linkTemp = AttributePointerOf(linkedHeat, "CurrentTemperature"); + void *linkDegrade = AttributePointerOf(linkedHeat, "DegradationTemperature"); + void *coolantLeak = AttributePointerOf(subsystem_in, "CoolantMassLeakRate"); + + coolingLoopB = new AnimatedSubsystemLamp(ChildRate(), eng_mode, renderer_in, // @004c70a4 + engPort, 0x254, 0x13b, "bteloop.pcc", 7, 1, subsystem_in, "CoolingLoop"); + + modeLamp = new OneOfSeveralInt(ChildRate(), eng_mode, renderer_in, // @004c5148 + engPort, 400, 10, "btecmode.pcc", 1, 2, + (int *)((char *)subsystem_in + 0x134), "OneOfSeveralInt"); // BEST-EFFORT raw offset + + if (inputVoltage == NULL) + { + powerSourceB = NULL; + generatorVoltageBar = NULL; + stateLamp = NULL; + } + else + { + powerSourceB = new AnimatedSourceLamp(ChildRate(), eng_mode, renderer_in, // @004c7160 + engPort, 0x1a, 0xd0, "btebus.pcc", 5, 1, inputVoltage, "PowerSource"); + + generatorVoltageBar = new ScalarBarGauge(ChildRate(), eng_mode, renderer_in, // @004c721c + engPort, 0x89, 0x82, 0x93, 0x13b, "evolt.pcc", 0xff, 0, + WipeGaugeScalar::wipeUp, 0.0f, GeneratorMaxVoltage, inputVoltage, + "GeneratorVoltage(slotOf)"); + + // stateLamp only when the subsystem is a Generator (IsDerivedFrom 0x50f4bc). + if (IsGeneratorDerived(subsystem_in)) + stateLamp = new OneOfSeveralStates(ChildRate(), eng_mode, renderer_in, // @004c5470 + engPort, 0xbe, 0, "btemode.pcc", 1, 3, + (Entity *)((char *)subsystem_in + 0x2b8), "OneOfSeveralStates"); // BEST-EFFORT raw + else + stateLamp = NULL; + } + + vertBarA = new VertTwoPartBar(ChildRate(), eng_mode, renderer_in, // @004c4724 + engPort, 0x1d1, 0x5c, 0x1eb, 0x124, tile_image, 0xff, 0xff, 0, + (Scalar *)currentTemp, (Scalar *)degradeTemp, (Scalar *)failTemp, "VertTwoPartBar"); + + vertBarB = new VertTwoPartBar(ChildRate(), eng_mode, renderer_in, + engPort, 0x222, 0x5c, 0x23d, 0x124, tile_image, 0xff, 0xff, 0, + (Scalar *)linkTemp, (Scalar *)linkDegrade, (Scalar *)failTemp, "VertTwoPartBar"); + + leakGauge = new BitMapInverseWipe(ChildRate(), eng_mode, renderer_in, // @004c5b7c + engPort, 0x255, 0xe0, "eleak.pcc", 0, 0xff, 3, + *(int *)((char *)subsystem_in + 0x150), (Scalar *)coolantLeak, "LeakGauge"); // BEST-EFFORT raw + + operating = False; // @0xC4 this[0x31] + subsystem = subsystem_in; // @0xC0 this[0x30] + previousDrawState = 0; // @0xC8 this[0x32] +} + +// +// @004c87dc -- dtor. Free the owned children, then GraphicGauge::~GraphicGauge +// (implicit). (The binary frees children through the vtbl-slot-3 ReleaseChildren +// path; done here for RAII correctness.) +// +SubsystemCluster::~SubsystemCluster() +{ + ReleaseChildren(); // FUN_004c8820 +} + +// +// @004c8820 -- free each owned child gauge (delete via its own dtor). +// +void SubsystemCluster::ReleaseChildren() +{ + delete background; background = NULL; + delete generatorVoltageBar; generatorVoltageBar = NULL; + delete temperatureBar; temperatureBar = NULL; + delete coolingLoopA; coolingLoopA = NULL; + delete coolingLoopB; coolingLoopB = NULL; + delete powerSourceA; powerSourceA = NULL; + delete powerSourceB; powerSourceB = NULL; + delete vertBarA; vertBarA = NULL; + delete vertBarB; vertBarB = NULL; + delete modeLamp; modeLamp = NULL; + delete stateLamp; stateLamp = NULL; + delete leakGauge; leakGauge = NULL; +} + +// +// @004c89c4 -- draw the panel's border frame (two thick diagonals of the box). +// +void SubsystemCluster::DrawPanelFrame(int color) +{ + localView.SetColor(color); // vtbl+0x18 + localView.MoveToAbsolute(9, 0x33); // vtbl+0x24 + localView.DrawThickLineToAbsolute(0x132, 0xab); // vtbl+0x38 + localView.MoveToAbsolute(9, 0xab); + localView.DrawThickLineToAbsolute(0x132, 0x33); +} + +// +// @004c8a28 -- forward an enable/disable to the always-present children (temp bar, +// cooling loop, power source A). +// +void SubsystemCluster::SetChildrenEnable(int enable) +{ + // FUN_00444650 forwards visibility; enable==0 (offline) => Disable(True). + if (temperatureBar) temperatureBar->Disable(enable == 0); + if (coolingLoopA) coolingLoopA->Disable(enable == 0); + if (powerSourceA) powerSourceA->Disable(enable == 0); +} + +// +// @004c8990 -- the operational-state virtual (drawn as TestInstance): active +// unless the subsystem is offline (operating==0 AND subsystem+0x278 == 4). +// +int SubsystemCluster::GetDrawState() +{ + if (operating != 0) + return 1; + if (*(int *)((char *)subsystem + 0x278) != 4) // BEST-EFFORT raw offset + return 1; + return 0; +} + +Logical SubsystemCluster::TestInstance() const +{ + return GraphicGauge::TestInstance(); +} + +// +// @004c88e4 -- Execute (shared by the whole family). operating = (subsystem is +// operational, +0x40 == 1). On a draw-state change repaint the panel frame: +// offline => frame(0) + redraw background bitmap; online => black fill + frame(0xff); +// and forward the enable to the temperature / loop children. +// +void SubsystemCluster::Execute() +{ + operating = (*(int *)((char *)subsystem + 0x40) == 1); // BEST-EFFORT raw offset + + int state = GetDrawState(); + if (state != previousDrawState) + { + previousDrawState = state; + SetChildrenEnable(state); + if (state == 0) + { + DrawPanelFrame(0); + if (background != NULL) + background->BecameActive(); // (child vtbl+0xc) force redraw + } + else + { + localView.SetColor(0); // vtbl+0x18 + localView.MoveToAbsolute(1, 0x32); // vtbl+0x24 + localView.DrawFilledRectangleToAbsolute(0x136, 0xad); // vtbl+0x48 + DrawPanelFrame(0xff); + } + } +} + +void SubsystemCluster::BecameActive() +{ + previousDrawState = -1; // force first-frame repaint +} + + +// Recovered .data tuning constants (read from the optimised image). +static const Scalar HeatLoadScale = 100.0f; // _DAT_004c8df0 +static const Scalar WeaponWarnThreshold = 0.99f; // _DAT_004c92e0 + + +//########################################################################### +// HeatSinkCluster @004c8a6c (vtable 0x519fd4) +//########################################################################### + +// +// @004c8a6c -- ctor: SubsystemCluster base + four failure TwoStates (heat / heat- +// sink / power / structure) + a NumericDisplayScalar read-out, plus a Scalar +// connection feeding heatLoad from the subsystem (+0x31c). +// +HeatSinkCluster::HeatSinkCluster( + ModeMask mfd_mode, L4GaugeRenderer *renderer_in, int owner_ID, int mfd_port, + int x, int y, Subsystem *subsystem_in, Subsystem *hud, ModeMask eng_mode, + const char *eng_port_name, const char *tile_image, const char *label, + const char *fail_lamp_image, char **image_names, const char *identification_string +): + SubsystemCluster(mfd_mode, renderer_in, owner_ID, mfd_port, x, y, subsystem_in, + eng_mode, eng_port_name, tile_image, label, image_names, identification_string) +{ + heatLoadScaled = HeatNumericBase; // @0x36 100.0f + int engPort = renderer_in->FindGraphicsPort(eng_port_name); + + heatFailLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID, // @004739d8 + engPort, 200, 0x110, fail_lamp_image, 0, 0xff, (int *)&operating, "TwoState"); + + failSubsystem = hud; // @0x33 + failFlag = 0; // @0x34 + + hsFailLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID, + engPort, 0x97, 0xe8, "btehfail.pcc", 0, 0xff, (int *)&failFlag, "TwoState"); + + powerFailLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID, + engPort, 0x97, 0xc0, "btepfail.pcc", 0, 0xff, + (int *)((char *)subsystem_in + 0x324), "TwoState"); // BEST-EFFORT raw offset + + structFailLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID, + engPort, 0x97, 0x98, "btesfail.pcc", 0xff, 0, + (int *)((char *)subsystem_in + 0x320), "TwoState"); // BEST-EFFORT raw offset + + heatNumeric = new NumericDisplayScalar(ChildRate(), eng_mode, renderer_in, // @00470888 + owner_ID, engPort, 0x132, 0x8c, 0x15a, 0xaa, "helv18.pcc", 3, + (NumericDisplay::NumericFormat)0, 0, 0xff, &heatLoadScaled, + "NumericDisplayScalar"); + + AddConnection(new GaugeConnectionDirectOf(0, &heatLoad, // @00474855 + (Scalar *)((char *)subsystem_in + 0x31c))); // BEST-EFFORT raw offset +} + +HeatSinkCluster::~HeatSinkCluster() +{ + delete heatFailLamp; delete hsFailLamp; delete powerFailLamp; + delete structFailLamp; delete heatNumeric; +} + +// +// @004c8db0 -- Execute: failFlag = (failSubsystem operational, +0x40 == 1); +// heatLoadScaled = heatLoad * 100.0; chain SubsystemCluster::Execute. +// +void HeatSinkCluster::Execute() +{ + if (failSubsystem != NULL) + failFlag = (*(int *)((char *)failSubsystem + 0x40) == 1); // BEST-EFFORT raw + heatLoadScaled = heatLoad * HeatLoadScale; + SubsystemCluster::Execute(); +} + + +//########################################################################### +// MyomerCluster @004c8df4 (vtable 0x519f88) +//########################################################################### + +// +// @004c8df4 -- ctor: SubsystemCluster base + a SeekVoltageGraph ("EngrGraph") +// fed by an InputVoltage GeneratorVoltageConnection (writes seekValue@0x33) + a +// seek-step OneOfSeveralInt (bteseek.pcc, subsys+0x800). Execute = base. +// +MyomerCluster::MyomerCluster( + ModeMask mfd_mode, L4GaugeRenderer *renderer_in, int owner_ID, int mfd_port, + int x, int y, Subsystem *subsystem_in, ModeMask eng_mode, + const char *eng_port_name, const char *tile_image, const char *label, + char **image_names, const char *identification_string +): + SubsystemCluster(mfd_mode, renderer_in, owner_ID, mfd_port, x, y, subsystem_in, + eng_mode, eng_port_name, tile_image, label, image_names, identification_string) +{ + seekValue = 0.0f; // @0x33 (connection dst) + int engPort = renderer_in->FindGraphicsPort(eng_port_name); + + seekGraph = (GraphicGauge *)new SeekVoltageGraph(ChildRate(), eng_mode, // @004c6798 + renderer_in, engPort, (Entity *)subsystem_in, + (AttributeAccessor *)&seekValue, "edestryd.pcc", False, "EngrGraph"); + + seekStepLamp = new OneOfSeveralInt(ChildRate(), eng_mode, renderer_in, // @004c5148 + engPort, 0xf, 0, "bteseek.pcc", 1, 4, + (int *)((char *)subsystem_in + 0x800), "OneOfSeveralInt"); // BEST-EFFORT raw + + void *inputVoltage = AttributePointerOf(subsystem_in, "InputVoltage"); + AddConnection(new GeneratorVoltageConnection(&seekValue, inputVoltage)); // @004c3288 +} + +MyomerCluster::~MyomerCluster() +{ + delete seekGraph; delete seekStepLamp; +} + + +//########################################################################### +// WeaponCluster @004c8fc4 (vtable 0x519f38) +//########################################################################### + +// +// @004c8fc4 -- ctor: SubsystemCluster base; interns the cluster image; centres a +// SegmentArc270 recharge dial (reads PercentDone) + a ConfigMapGauge; adds a +// Scalar connection feeding percentDone. The warning-lamp centre is derived from +// the cluster image size, or warned "WeaponCluster missing ". +// +WeaponCluster::WeaponCluster( + ModeMask mfd_mode, L4GaugeRenderer *renderer_in, int owner_ID, int mfd_port, + int x, int y, Subsystem *subsystem_in, ModeMask eng_mode, + const char *eng_port_name, const char *tile_image, const char *label, + const char *cluster_image, char **image_names, const char *identification_string +): + SubsystemCluster(mfd_mode, renderer_in, owner_ID, mfd_port, x, y, subsystem_in, + eng_mode, eng_port_name, tile_image, label, image_names, identification_string) +{ + void *percentDoneAttr = AttributePointerOf(subsystem_in, "PercentDone"); + + // Intern the cluster image + derive the warning-lamp centre from its size. + clusterImage = (char *)cluster_image; // @0x33 (FUN_004700ac intern) + L4Warehouse *warehouse = (L4Warehouse *)renderer_in->warehousePointer; + BitMap *img = warehouse->bitMapBin.Get(clusterImage); + if (img == NULL) + { + DebugStream << "WeaponCluster missing " << clusterImage << "\n"; + warningCenterX = 0; // @0x37 + warningCenterY = 0; // @0x38 + } + else + { + warningCenterX = 0x41 - (img->Data.Size.x >> 1); + warningCenterY = 0x6d - (img->Data.Size.y >> 1); + } + + // rechargeArc (SegmentArc270 @004c6244, 0..360 recharge dial reading PercentDone). + // BRING-UP: the SegmentArc270 ctor is a placeholder in btl4gaug.hpp (its real + // @004c6244 body is not yet reconstructed); tracked NULL until it lands. + rechargeArc = NULL; // @0x34 + + configMap = new ConfigMapGauge(ChildRate(), mfd_mode, renderer_in, // @004c6d80 + mfd_port, x + 0xee, y + 0x38, (Entity *)subsystem_in, "ConfigMap"); + + warningState = 0; // @0x39 + AddConnection(new GaugeConnectionDirectOf(0, &percentDone, // @00474855 + (Scalar *)percentDoneAttr)); +} + +WeaponCluster::~WeaponCluster() +{ + L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer; + warehouse->bitMapBin.Release(clusterImage); + delete rechargeArc; delete configMap; +} + +// +// @004c932c -- DrawWarningLamp: blit the cluster warning image at its centre, +// coloured on/off by the passed enable. +// +void WeaponCluster::DrawWarningLamp(int on) +{ + L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer; + BitMap *img = warehouse->bitMapBin.Get(clusterImage); + if (img != NULL) + { + localView.MoveToAbsolute(warningCenterX, warningCenterY); // vtbl+0x24 + localView.SetColor(on ? 0xff : 0); // vtbl+0x18 + localView.DrawBitMap(0, img); // vtbl+0x54 + } + warehouse->bitMapBin.Release(clusterImage); +} + +// +// @004c9258 -- BecameActive: reset the warning state + chain the children. +// +void WeaponCluster::BecameActive() +{ + warningState = -2; + if (configMap) configMap->BecameActive(); + if (rechargeArc) rechargeArc->BecameActive(); + SubsystemCluster::BecameActive(); +} + +// +// @004c9290 -- Execute: chain the base; when percentDone crosses the warn +// threshold toggle the warning lamp. +// +void WeaponCluster::Execute() +{ + SubsystemCluster::Execute(); + if (previousDrawState == 0) + { + int warn = (WeaponWarnThreshold < percentDone) ? 1 : 0; + if (warn != warningState) + { + warningState = warn; + DrawWarningLamp(warn); + } + } +} + + +//########################################################################### +// EnergyWeaponCluster @004c93b0 (vtable 0x519ee8) +//########################################################################### + +// +// @004c93b0 -- ctor: WeaponCluster base + a SeekVoltageGraph ("EngrGraph", +// OutputVoltage) + a seek-step OneOfSeveralInt (bteseek.pcc, subsys+0x3f0). +// +EnergyWeaponCluster::EnergyWeaponCluster( + ModeMask mfd_mode, L4GaugeRenderer *renderer_in, int owner_ID, int mfd_port, + int x, int y, Subsystem *subsystem_in, ModeMask eng_mode, + const char *eng_port_name, const char *tile_image, const char *label, + const char *cluster_image, char **image_names, const char *identification_string +): + WeaponCluster(mfd_mode, renderer_in, owner_ID, mfd_port, x, y, subsystem_in, + eng_mode, eng_port_name, tile_image, label, cluster_image, image_names, + identification_string) +{ + int engPort = renderer_in->FindGraphicsPort(eng_port_name); + void *outputVoltage = AttributePointerOf(subsystem_in, "OutputVoltage"); + + seekGraph = new SeekVoltageGraph(ChildRate(), eng_mode, renderer_in, // @004c6798 + engPort, (Entity *)subsystem_in, (AttributeAccessor *)outputVoltage, + "edestryd.pcc", True, "EngrGraph"); + + seekStepLamp = new OneOfSeveralInt(ChildRate(), eng_mode, renderer_in, // @004c5148 + engPort, 0xf, 0, "bteseek.pcc", 1, 4, + (int *)((char *)subsystem_in + 0x3f0), "OneOfSeveralInt"); // BEST-EFFORT raw +} + +EnergyWeaponCluster::~EnergyWeaponCluster() +{ + delete seekGraph; delete seekStepLamp; +} + + +//########################################################################### +// BallisticWeaponCluster @004c9558 (vtable 0x519e98) +//########################################################################### + +// +// @004c9558 -- ctor: WeaponCluster base + the ammo panel: two NumericDisplay +// Integers (ammo counts off the resolved ammo bin +0x180), jam/fire TwoStates, +// a GraphicsViewRecord erase tracker, a NumericDisplayScalarTwoState, a destroyed +// TwoState, and an eject BitMapInverseWipeScalar (bteejtm, subsys+0x3f8). +// +BallisticWeaponCluster::BallisticWeaponCluster( + ModeMask mfd_mode, L4GaugeRenderer *renderer_in, int owner_ID, int mfd_port, + int x, int y, Subsystem *subsystem_in, ModeMask eng_mode, + const char *eng_port_name, const char *tile_image, const char *label, + const char *cluster_image, char **image_names, const char *font_a, + const char *font_b, const char *identification_string +): + WeaponCluster(mfd_mode, renderer_in, owner_ID, mfd_port, x, y, subsystem_in, + eng_mode, eng_port_name, tile_image, label, cluster_image, image_names, + identification_string) +{ + reloadStartTime = 0; // @0x3D (frame clock) + reloadSeconds = 0.0f; // @0x3E + jammed = firing = reloading = False; + + void *ammoBin = ResolveLink((char *)subsystem_in + 0x43c); // FUN_00417ab4 (raw) + int engPort = renderer_in->FindGraphicsPort(eng_port_name); + + // Ammo count off the bin (+0x180); BEST-EFFORT raw if the bin resolves. + int *ammoValue = (ammoBin != NULL) ? (int *)((char *)ammoBin + 0x180) : NULL; + + ammoCountA = new NumericDisplayInteger(ChildRate(), mfd_mode, renderer_in, // @00470cfc + owner_ID, mfd_port, x + 0x27, y + 100, x + 0x5f, y + 0x76, font_a, 4, + (NumericDisplay::NumericFormat)0, 0, 0xff, ammoValue, "NumericDisplayInteger"); + + ammoCountB = new NumericDisplayInteger(ChildRate(), eng_mode, renderer_in, + owner_ID, engPort, 0xdc, 0x104, 0x140, 300, font_b, 4, + (NumericDisplay::NumericFormat)0, 0, 0xff, ammoValue, "NumericDisplayInteger"); + + jammed = 0; // @0x3A + jamLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID, // @004739d8 + engPort, 0xa0, 0xb2, "btejam.pcc", 0, 0xff, (int *)&jammed, "TwoState"); + + firing = reloading = 0; // @0x3B / @0x3C + fireLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID, + engPort, 0x121, 0xb6, "btefire.pcc", 0, 0xff, (int *)&firing, "TwoState"); + + // Ammo erase-tracker GraphicsViewRecord (@0044ad78) -- bring-up: the record + // class is engine-internal; tracked as a NULL erase (the numeric self-erases). + ammoErase = NULL; // @0x3F (GraphicsViewRecord, deferred) + + ammoNumeric = new NumericDisplayScalarTwoState(ChildRate(), eng_mode, // @00473c94 + renderer_in, owner_ID, engPort, 0x130, 0xbc, 0x162, 0xee, "helv42.pcc", 2, + (NumericDisplay::NumericFormat)0, 0, 0xff, &reloadSeconds, (int *)&firing, + "NumericDisplayScalarTwoState"); + + destroyedLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID, + engPort, 0xc2, 0x85, "edestryd.pcc", 0, 0xff, (int *)&operating, "TwoState"); + + // ejectWipe (BitMapInverseWipeScalar @004c61c8, eject-timer wipe reading + // subsys+0x3f8). BRING-UP: the BitMapInverseWipeScalar class is not yet + // declared/reconstructed in btl4gaug (only the non-Scalar BitMapInverseWipe); + // tracked NULL until it lands. + ejectWipe = NULL; // @0x44 +} + +BallisticWeaponCluster::~BallisticWeaponCluster() +{ + delete ammoNumeric; delete ammoErase; delete ammoCountA; + delete ammoCountB; delete jamLamp; delete fireLamp; + delete destroyedLamp; delete ejectWipe; +} + +// +// @004c99cc -- BecameActive: chain the ammo children then WeaponCluster. +// +void BallisticWeaponCluster::BecameActive() +{ + if (ammoCountA) ammoCountA->BecameActive(); + if (ammoCountB) ammoCountB->BecameActive(); + if (jamLamp) jamLamp->BecameActive(); + if (fireLamp) fireLamp->BecameActive(); + if (ammoNumeric) ammoNumeric->BecameActive(); + if (destroyedLamp) destroyedLamp->BecameActive(); + if (ejectWipe) ejectWipe->BecameActive(); + WeaponCluster::BecameActive(); +} + +// +// @004c9a38 -- Execute: jammed = (weaponAlarm subsys+0x364 == 5 Jammed); read the +// ammo-bin reload state (bin+0x18c) and, while reloading, compute the elapsed +// reload seconds; chain WeaponCluster::Execute. +// +void BallisticWeaponCluster::Execute() +{ + jammed = (*(int *)((char *)subsystem + 0x364) == 5); // BEST-EFFORT raw (weaponAlarm==Jammed) + void *ammoBin = ResolveLink((char *)subsystem + 0x43c); // FUN_00417ab4 + int reloadState = (ammoBin != NULL) ? *(int *)((char *)ammoBin + 0x18c) : 0; + firing = reloadState; + if (reloadState == 0) + { + reloading = 0; + } + else + { + reloading = 1; + // (elapsed reload seconds = (now - reloadStart) / frameClock; the frame + // clock DAT_0052140c is a runtime value -- left at the base value here.) + } + WeaponCluster::Execute(); +} + +// +// @004c9adc -- TestInstance: True unless the ammo bin is present and the eject +// wipe is mid-cycle. +// +Logical BallisticWeaponCluster::TestInstance() const +{ + void *ammoBin = ResolveLink((char *)subsystem + 0x43c); + if (ammoBin == NULL || *(int *)((char *)ammoBin + 0x180) == 0) + return True; + if (jammed != 0) + return True; + return SubsystemCluster::TestInstance(); +} + + +//########################################################################### +// vehicleSubSystems Make dispatcher @004cbaf0 +//########################################################################### + +// +// The 12 auxiliary-screen geometry rows (@0x51bf34, x/y/planeMask/subBit/mfdPort/ +// engPort/planeIndex), keyed by subsystem[0x1dc]-1. planeMask -> the MFD child +// bit-plane mode; subBit -> the engineering child bit-plane mode; mfd/eng names +// select the two graphics ports. +// +struct AuxScreenGeometry +{ + int x, y; + ModeMask planeMask; + ModeMask subBit; + const char *mfdPortName; + const char *engPortName; + int planeIndex; +}; +static const AuxScreenGeometry kAuxGeom[12] = +{ + { 0, 0xf0, 0x1, 0x2, "mfd1", "eng1", 0 }, // grp 1 + { 0x142, 0xf0, 0x1, 0x4, "mfd1", "eng1", 0 }, // grp 2 + { 0, 0x1c, 0x1, 0x8, "mfd1", "eng1", 0 }, // grp 3 + { 0x142, 0x1c, 0x1, 0x10, "mfd1", "eng1", 1 }, // grp 4 + { 0, 0xf0, 0x20, 0x40, "mfd2", "eng2", 1 }, // grp 5 + { 0x142, 0xf0, 0x20, 0x80, "mfd2", "eng2", 1 }, // grp 6 + { 0, 0x1c, 0x20, 0x100, "mfd2", "eng2", 1 }, // grp 7 + { 0x142, 0x1c, 0x20, 0x200, "mfd2", "eng2", 2 }, // grp 8 + { 0, 0xf0, 0x400, 0x800, "mfd3", "eng3", 2 }, // grp 9 + { 0x142, 0xf0, 0x400, 0x1000, "mfd3", "eng3", 2 }, // grp 10 + { 0, 0x1c, 0x400, 0x2000, "mfd3", "eng3", 2 }, // grp 11 + { 0x142, 0x1c, 0x400, 0x4000, "mfd3", "eng3", 2 }, // grp 12 +}; + +// +// FUN_0041a1a4(**(sub+0xc), 0x50f4bc) -- the pre-dispatch type filter (an +// IsDerivedFrom against a ClassDerivations object). The classID switch below is +// the real per-panel filter, so a permissive True is faithful for the displayable +// classes; the exact ClassDerivations resolution lands with a shared accessor. +// +static Logical SubsystemDisplayFilter(Subsystem *) { return True; } + +// +// @004cbaf0 -- vehicleSubSystems Make. Build the 8 strip-image pointers from the +// method params, then for each roster subsystem (skipping slot 0) build the panel +// cluster for its classID onto the auxiliary MFD position named by subsystem[0x1dc]. +// +Logical + VehicleSubSystems::Make( + int /*display_port_index*/, + Vector2DOf /*position*/, + Entity *entity, + GaugeRenderer *gauge_renderer + ) +{ + L4GaugeRenderer *renderer = (L4GaugeRenderer *)gauge_renderer; + + // The 8 status strip bitmaps (qXXX0.pcc..qXXX7.pcc) from the method params. + char *imageNames[8]; + for (int p = 0; p < 8; p++) + imageNames[p] = methodDescription.parameterList[p].data.string; + + int count = entity->GetSubsystemCount(); // entity+0x124 + for (int i = 0; i < count; i++) + { + if (i == 0) // slot 0 = the controls mapper + continue; + Subsystem *sub = entity->GetSubsystem(i); // entity+0x128[i] + if (sub == NULL) + continue; + if (!SubsystemDisplayFilter(sub)) // FUN_0041a1a4 + continue; + + int group = *(int *)((char *)sub + 0x1dc) - 1; // aux-screen position 1..12 + if (group < 0) + { + DebugStream << "Auxiliary screen position = zero\n"; + continue; + } + if (group >= 12) + { + DebugStream << "Illegal auxiliary screen position =" << group << "\n"; + continue; + } + + const AuxScreenGeometry &g = kAuxGeom[group]; + int mfdPort = renderer->FindGraphicsPort((char *)g.mfdPortName); // FUN_00447f1c + char *label = (char *)((char *)sub + 0x1e4); // the subsystem label + char *tile = (char *)"btwarn.pcc"; + + switch (sub->GetClassID()) // sub+4 + { + case 0xBC3: // Sensor -> HeatSinkCluster (shows the "HUD" subsystem) + { + Subsystem *hud = entity->FindSubsystem((char *)"HUD"); // FUN_0041f98c + new HeatSinkCluster(g.planeMask, renderer, 0, mfdPort, g.x, g.y, sub, + hud, g.subBit, g.engPortName, tile, label, "edestryd.pcc", + imageNames, "SensorCluster"); + break; + } + case 0xBC6: // Myomers -> MyomerCluster + new MyomerCluster(g.planeMask, renderer, 0, mfdPort, g.x, g.y, sub, + g.subBit, g.engPortName, tile, label, imageNames, "MyomerCluster"); + break; + case 0xBC8: // Emitter + case 0xBD4: // PPC + { + const char *clusterImage = + (*(int *)((char *)sub + 0x334) == 0) ? "qcircle.pcc" : "qcircr.pcc"; + new EnergyWeaponCluster(g.planeMask, renderer, 0, mfdPort, g.x, g.y, sub, + g.subBit, g.engPortName, tile, label, clusterImage, imageNames, + "EnergyWeaponCluster"); + break; + } + case 0xBCD: // ProjectileWeapon + case 0xBD0: // MissileLauncher + new BallisticWeaponCluster(g.planeMask, renderer, 0, mfdPort, g.x, g.y, + sub, g.subBit, g.engPortName, tile, label, "qcircle.pcc", imageNames, + "smlfont.pcc", "helv42.pcc", "ProjWeaponCluster"); + break; + case 0xBCE: // GaussRifle -- not yet supported + DebugStream << "Gauss rifle not yet supported\n"; + break; + default: + break; + } + } + return True; +} + +// +// The registered method: config primitive "vehicleSubSystems(q0.pcc..q7.pcc)". +// +MethodDescription + VehicleSubSystems::methodDescription = + { + "vehicleSubSystems", + VehicleSubSystems::Make, + { + { ParameterDescription::typeString, NULL }, // qXXX0.pcc + { ParameterDescription::typeString, NULL }, // qXXX1.pcc + { ParameterDescription::typeString, NULL }, // qXXX2.pcc + { ParameterDescription::typeString, NULL }, // qXXX3.pcc + { ParameterDescription::typeString, NULL }, // qXXX4.pcc + { ParameterDescription::typeString, NULL }, // qXXX5.pcc + { ParameterDescription::typeString, NULL }, // qXXX6.pcc + { ParameterDescription::typeString, NULL }, // qXXX7.pcc + { ParameterDescription::typeEmpty, NULL } + } + }; + + // === btl4gau3.cpp begins at @004c9bd0 (PlayerStatusMappingGroup) -- not this TU. diff --git a/game/reconstructed/btl4gau2.hpp b/game/reconstructed/btl4gau2.hpp index 75c7cfb..819a1a5 100644 --- a/game/reconstructed/btl4gau2.hpp +++ b/game/reconstructed/btl4gau2.hpp @@ -173,7 +173,7 @@ AnimatedSubsystemLamp( // @004c70a4 GaugeRate, ModeMask, L4GaugeRenderer *, int, int x, int y, const char *image, int columns, int rows, - Entity *subsystem, const char *identification_string); + void *subsystem, const char *identification_string); ~AnimatedSubsystemLamp(); // @004c7134 protected: int selected; // @0xAC this[0x2B] (connection dst) @@ -186,7 +186,7 @@ AnimatedSourceLamp( // @004c7160 GaugeRate, ModeMask, L4GaugeRenderer *, int, int x, int y, const char *image, int columns, int rows, - Entity *source, const char *identification_string); + void *source, const char *identification_string); ~AnimatedSourceLamp(); // @004c71f0 protected: int selected; // @0xAC this[0x2B] @@ -202,14 +202,25 @@ // Both share dtor FUN_004c733c (chains the bar dtor @00472fb4). Used with // the strings "GeneratorVoltage(slotOf)" / "GeneratorVoltage(scalar)". //####################################################################### - class ScalarBarGauge : // (best-effort) - public GraphicGauge // MUNGA L4 bar primitive (FUN_00472ef0); engine has no plain "BarGraph" class + // ScalarBarGauge chains the MUNGA L4 bar primitive @00472ef0 == the engine + // BarGraphBitMapScalar (WipeGaugeScalar). 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). + class ScalarBarGauge : + public BarGraphBitMapScalar // @00472ef0 base (WipeGaugeScalar : GraphicGauge) { public: - ScalarBarGauge(/* ...bar geometry..., Scalar *value, id */); // @004c721c / @004c72ac + // @004c721c -- the "slotOf" variant: bar body then AddConnection(new + // GeneratorVoltageConnection @004c3288) feeding currentValue from a + // resolved voltage source. (The @004c72ac plain-Scalar variant is unused + // by the cluster path.) + ScalarBarGauge( // @004c721c + 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(); // @004c733c - protected: - Scalar value; // @0xB4 this[0x2D] (connection dst) }; @@ -322,19 +333,24 @@ public GraphicGauge { public: + // @004c8140. Binary param order (rate/mode are hardcoded 0xFFFF/0xFFFFFFFF + // 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 - GaugeRate, ModeMask, L4GaugeRenderer *, int, - int x, int y, Subsystem *subsystem, GaugeRenderer *eng_renderer, - const char *port_name, const char *tile_image, - const char *background_image, int damage_zone_table, + 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(); // @004c88e4 (shared base Execute trampoline) void Execute(); // @004c88e4 protected: + BackgroundBitmap + *background; // @0x90 this[0x24] BackgroundBitmap GraphicGauge - *background, // @0x90 this[0x24] BackgroundBitmap + *generatorVoltageBar, // @0x94 this[0x25] ScalarBarGauge (evolt) *temperatureBar, // @0x98 this[0x26] HorizTwoPartBar *coolingLoopA, // @0x9C this[0x27] AnimatedSubsystemLamp *coolingLoopB, // @0xA0 this[0x28] @@ -347,12 +363,27 @@ *leakGauge; // @0xBC this[0x2F] LeakGauge Subsystem *subsystem; // @0xC0 this[0x30] Logical operating; // @0xC4 this[0x31] + int previousDrawState; // @0xC8 this[0x32] (Execute repaint key) + + // internal helpers (non-virtual; @004c89c4 / @004c8820 / @004c8a28) + void DrawPanelFrame(int color); // @004c89c4 + void ReleaseChildren(); // @004c8820 + void SetChildrenEnable(int enable); // @004c8a28 + public: + int GetDrawState(); // @004c8990 (operational-state virtual, vtbl+0x48) + protected: }; 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: @@ -368,10 +399,39 @@ *heatNumeric; // @0xEC this[0x3B] NumericDisplayScalar }; + // + // MyomerCluster @004c8df4 (vtable 00519f88) : SubsystemCluster -- the myomer + // (muscle) panel. Adds a SeekVoltageGraph ("EngrGraph", edestryd.pcc) fed by + // an InputVoltage GeneratorVoltageConnection (writes seekValue@0x33) + a + // seek-step OneOfSeveralInt (bteseek.pcc, subsys+0x800). 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 @@ -390,6 +450,12 @@ 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] @@ -400,6 +466,13 @@ 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 @@ -422,4 +495,24 @@ *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 + // subsystem builds the right cluster (HeatSink/Myomer/Energy/Ballistic) onto + // one of 12 auxiliary MFD positions selected by subsystem[0x1dc] (the aux- + // screen position 1..12). 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/game/reconstructed/btl4grnd.cpp b/game/reconstructed/btl4grnd.cpp index ba651db..897c234 100644 --- a/game/reconstructed/btl4grnd.cpp +++ b/game/reconstructed/btl4grnd.cpp @@ -66,6 +66,7 @@ #endif #if !defined(BTL4GAUG_HPP) # include // the BT gauge classes (Compass, etc.) +# include // the composite cluster gauges + VehicleSubSystems # include // MapDisplay (the "map" radar gauge) # include // PlayerStatus (the comm/score gauge) #endif @@ -141,6 +142,7 @@ MethodDescription &OneOfSeveralPixInt::methodDescription, // "oneOfSeveralPixInt" -- button-state lamp (duck/light/mode) &MapDisplay::methodDescription, // "map" -- the radar / tactical display &PlayerStatus::methodDescription, // "PlayerStatus" -- comm/score name-tag gauge + &VehicleSubSystems::methodDescription, // "vehicleSubSystems" -- engineering-screen subsystem cluster panels &BTL4ChainToPrevious };