//===========================================================================// // 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(); } // // Execute -- scale the raw fraction by the 270-degree span, draw, then // put the raw value back. Restoring matters: the connection only // rewrites currentValue when its source changes, so scaling in place // would compound frame after frame and walk the dial to zero. // void SegmentArc270::Execute() { Check(this); Scalar raw = currentValue; currentValue = raw * segmentSpan; SegmentArc::Execute(); currentValue = raw; 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.