//===========================================================================// // File: btl4gau2.cpp // // Project: BattleTech Brick: Gauge Renderer Manager // // Contents: Cockpit instrument library, part 2 -- composite panel gauges // // (see btl4gau2.hpp). // //---------------------------------------------------------------------------// // Date Who Modification // // -------- --- ---------------------------------------------------------- // // 02/22/96 CPB Initial coding. // //---------------------------------------------------------------------------// // Copyright (C) 1996, Virtual World Entertainment, Inc. All rights reserved // // PROPRIETARY AND CONFIDENTIAL // //===========================================================================// // // RECONSTRUCTED from the shipped binary (BTL4OPT.EXE). Behaviour follows the // Ghidra pseudo-C in recovered/all/part_014.c (@004c6798..@004c9b50). Class / // member names taken from the CODE name-string pool; per-method @ADDR evidence // is cited inline. This TU links between btl4gaug.obj and btl4gau3.obj. // // Engine-internal helper map (consistent with btl4gaug.cpp / btl4rdr.cpp): // FUN_00444818 GraphicGauge ctor FUN_00444870 GraphicGauge dtor // FUN_004448ac GraphicGauge::TestInstance // FUN_004440d4 Gauge-group ctor FUN_00443f7c Gauge-group dtor // FUN_0044ae0c ChainOf dtor (PrepEngrScreen child list) // FUN_004442b8 default GaugeRate FUN_004442dc default ModeMask // FUN_00444290 alt default rate/mask // FUN_004700ac GaugeRenderer::InternName FUN_004022e8 FreeName // FUN_00442aec/00442c12 BitMapCache AddRef/Release (renderer+0x4C, +4) // FUN_00442d2b/00442e51 PixMapCache AddRef/Release (renderer+0x4C, +0x20) // FUN_0041bfc0 Entity::FindAttributeIndex(name) // FUN_0041f98c Entity::FindSubObject(name) // FUN_00447f1c GaugeRenderer::InstallPort / FUN_00447f84 GetGraphicsPort // FUN_00417ab4 SharedData::Resolve / operator Entity* // FUN_0041a1a4 IsDerivedFrom(classDerivations) (0x50f4bc = Generator) // FUN_0042076c Subsystem::FindDamageZone(record) -> index // FUN_004dcd94 Round(Scalar) -> int // FUN_004700bc NumericDisplay ctor FUN_0047018c ::dtor // FUN_004703f4 NumericDisplay::Reset FUN_00470430 ::DrawAt FUN_00470758 ::Erase // FUN_00470ec8 NumericDisplay::SetColors FUN_00470cfc NumericDisplayInteger ctor // FUN_00470888 NumericDisplayScalar ctor FUN_00473c94 NumericDisplayScalarTwoState // FUN_004739d8 TwoState ctor FUN_00471d00 BackgroundBitmap ctor // FUN_00472ef0 BarGraph ctor FUN_00472fb4 BarGraph dtor // FUN_00474855 GaugeConnectionDirectOf::ctor FUN_004749de // FUN_004c2ec4 DrawClippedBitmap (file-private, btl4gaug) FUN_004c2f88 -> 7 // FUN_004c3134/004c31ec/004c3288 shared GaugeConnection ctors (btl4gaug TU) // FUN_004c4170 HorizTwoPartBar FUN_004c4724 VertTwoPartBar // FUN_004c4d88 OneOfSeveral FUN_004c5148 OneOfSeveralInt // FUN_004c5470 OneOfSeveralStates FUN_004c5b7c LeakGauge(BitMapInverseWipe) // FUN_004c61c8 BitMapInverseWipeScalar FUN_004c6244 SegmentArc270 // FUN_004c3b98 ColorMapperArmor ctor (btl4gaug) // FUN_0040385c Verify FUN_004dbb24 DebugStream< #pragma hdrstop #if !defined(BTL4GAU2_HPP) # include #endif #if !defined(APP_HPP) # include #endif static const Scalar SeekVoltageXScale = 230.0f; // 0x43660000 static const Scalar SeekVoltageYScale = 187.0f; // 0x433b0000 static const Scalar RedrawSentinel = 9999.0f; // 0x461c3c00 static const Scalar GeneratorMaxVoltage = 12000.0f; // 0x463b8000 static const Scalar HeatNumericBase = 100.0f; // 0x42c80000 // (DebugStream warning channel, DAT_00524e20, is provided by the shared // reconstruction headers -- heat.hpp / mechrecon.hpp.) // // Unrecovered .data resource pool used by GeneratorCluster::Make. Concrete // bytes were not recovered from the optimised image; typed placeholders so the // resolve-and-construct path compiles. (BEST-EFFORT.) // static char DAT_00518f24[] = "GeneratorCluster"; // subsystem name to resolve static int DAT_00518ee0 = 0; // GaugeRate static char DAT_00518f68[] = ""; // background image name // // Original FUN_0041bfc0 = Entity::FindAttributeIndex(name) -> ordinal. The // WinTesla engine exposes attribute lookup via Simulation::AttributeIndexSet // (Find/FindEntry by name) rather than a name->ordinal call, so this best-effort // shim stands in for it; the stored indices are consumed by the (summarised) // SeekVoltageGraph::Execute plot. // static int FindAttributeIndex(Entity *, const char *) { return -1; } // // FUN_0041bfc0 == Entity::FindAttributeIndex(name) returns a POINTER into the // subsystem (subsystem + attributeMemberOffset), or 0 on miss. The composite // cluster gauges pass that pointer straight to their child gauges as the value // source. The WinTesla engine exposes exactly this via Simulation:: // GetAttributePointer(const char*) (SIMULATE.h), which returns a member pointer // or &Simulation::NullAttribute (always safe to deref) -- so we resolve through // it. A null subsystem -> 0. // static void *AttributePointerOf(void *subsystem, const char *name) { if (subsystem == NULL) return NULL; void *p = ((Simulation *)subsystem)->GetAttributePointer(name); // FUN_0041bfc0 // GetAttributePointer returns &NullAttribute (not 0) on a miss; the composite // ctors gate optional children on a 0 result, so normalise the miss to NULL. if (p == (void *)&Simulation::NullAttribute) return NULL; return p; } // // FUN_00417ab4 -- SubsystemConnection::Resolve: follow the plug's bound link to the // resolved subsystem. The link is `*(plug+8)`; the resolved subsystem is // `*(link+8)`. An unbound plug (link == 0) resolves to 0 -- the authentic // behaviour when a subsystem has no attached voltage source / cooling master (the // connection then reads its "no source" branch, *destination = 0). // static void *ResolveLink(void *plug) { if (plug == 0) return 0; void *link = *(void **)((char *)plug + 8); if (link == 0) return 0; return *(void **)((char *)link + 8); } // // Bridge (defined in powersub.cpp) that reads the engineering-screen assignment // off the real PoweredSubsystem type (auxScreenNumber/Placement/Label) -- the // reconstructed heat-leaf branch is not byte-exact, so the raw sub+0x1dc/0x1e0/ // 0x1e4 offsets read garbage; the named fields (populated by the PoweredSubsystem // ctor from the resource) are correct. Returns False for non-PoweredSubsystem- // derived subsystems (== the vehicleSubSystems type filter). // extern bool BTGetSubsystemAuxScreen(Subsystem *sub, int *screen, int *placement, char *label64); // // FUN_0041bf44 -- GetAttributePointer(index): a pointer to the subsystem's Nth // attribute member (the cooling-loop lamp reaches its cooling-master link at // attribute slot 3), or 0 if the index is out of range. The engine exposes this // via Simulation::GetAttributePointer(AttributeID); a miss returns &NullAttribute, // normalised to 0. // static void *FindLinkedSubsystem(void *subsystem, int slot) { if (subsystem == 0) return 0; void *p = ((Simulation *)subsystem)->GetAttributePointer((Simulation::AttributeID)slot); if (p == (void *)&Simulation::NullAttribute) return 0; return p; } // // FUN_0041a1a4 IsDerivedFrom(0x50f4bc == PoweredSubsystem's ClassDerivations). // Gates the SubsystemCluster's generator stateLamp. Every subsystem that reaches // a SubsystemCluster passed the vehicleSubSystems type filter (BTGetSubsystemAuxScreen // -> the SAME IsDerivedFrom(PoweredSubsystem) check), so this is always True here; // the real check is done in powersub.cpp via BTIsPoweredSubsystem for robustness. // extern bool BTIsPoweredSubsystem(Subsystem *sub); static Logical IsGeneratorDerived(Subsystem *sub) { return BTIsPoweredSubsystem(sub) ? True : False; } //########################################################################### // File-private GaugeConnection classes for the composite-cluster lamps. // Each is the reconstruction of one of the three shared connection ctors // the sub-gauges attach (mirrors the btl4gaug.cpp connection pattern: derive // GaugeConnection, store dst/src, override Update() = the vtable sampler). //########################################################################### // // CoolingLoopConnection -- @004c3134 ctor / @004c31a0 sampler (vt 0x518ea8). // When the source subsystem's coolant loop is active (source+0x134 == 1) it // follows the linked cooling master (FindLinkedSubsystem(source,3) then Resolve) // and copies its loop-lamp state (+0x1d4); otherwise 0. // class CoolingLoopConnection : public GaugeConnection { public: CoolingLoopConnection(int *destination, void *source) : GaugeConnection(0), // FUN_00444124(this,0) source(source), destination(destination) {} void Update() // @004c31a0 { void *resolved = 0; if (source != 0 && *(int *)((char *)source + 0x134) == 1) { void *linked = FindLinkedSubsystem(source, 3); if (linked != 0) resolved = ResolveLink(linked); } *destination = (resolved == 0) ? 0 : *(int *)((char *)resolved + 0x1d4); } protected: void *source; // @0x10 this[4] int *destination; // @0x14 this[5] }; // // PowerSourceConnection -- @004c31ec ctor / @004c3258 sampler (vt 0x518e9c). // Resolves the power source (Resolve(source)) and copies its bus-lamp state // (+0x1e0); 0 if unresolved. // class PowerSourceConnection : public GaugeConnection { public: PowerSourceConnection(int *destination, void *source) : GaugeConnection(0), source(source), destination(destination) {} void Update() // @004c3258 { void *resolved = ResolveLink(source); *destination = (resolved == 0) ? 0 : *(int *)((char *)resolved + 0x1e0); } protected: void *source; // @0x10 int *destination; // @0x14 }; // // GeneratorVoltageConnection -- @004c3288 ctor / @004c32f4 sampler (vt 0x518e90). // Resolves the voltage source and copies its output voltage (+0x1dc); 0 if // unresolved. Drives ScalarBarGauge / SeekVoltageGraph value slots. // class GeneratorVoltageConnection : public GaugeConnection { public: GeneratorVoltageConnection(Scalar *destination, void *source) : GaugeConnection(0), source(source), destination(destination) {} void Update() // @004c32f4 { void *resolved = ResolveLink(source); *destination = (resolved == 0) ? 0.0f : *(Scalar *)((char *)resolved + 0x1dc); } protected: void *source; // @0x10 Scalar *destination; // @0x14 }; //########################################################################### //########################################################################### // SeekVoltageGraph @004c6798 //########################################################################### //########################################################################### // // @004c6798 -- ctor (vtable PTR_FUN_0051a1fc). GraphicGauge base, interns and // ref-counts the "destroyed" bitmap, resolves the four SeekVoltage attribute // indices off the weapon subsystem, sets the port extent (0x97,0x80,0x17d,0x13b) // and stashes the 230x187 plot scale. // SeekVoltageGraph::SeekVoltageGraph( GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer, int graphics_port_number, Entity *subsystem, AttributeAccessor *seek_voltage_pointer, const char *destroyed_image, Logical draw_cursor, const char *identification_string ): GraphicGauge(rate, mode_mask, renderer, /*owner*/0xFFFF, graphics_port_number, identification_string) // FUN_00444818 { // Original interned a copy of the name (FUN_004700ac) + AddRef'd it in the // bitmap cache; retain the persistent resource string and AddRef the bitmap. destroyedImage = (char *)destroyed_image; renderer->warehousePointer->bitMapBin.Get(destroyedImage); // FUN_00442aec (AddRef) currentSeekIndex = FindAttributeIndex(subsystem, "CurrentSeekVoltageIndex"); // FUN_0041bfc0 minSeekIndex = FindAttributeIndex(subsystem, "MinSeekVoltageIndex"); maxSeekIndex = FindAttributeIndex(subsystem, "MaxSeekVoltageIndex"); seekVoltageTable = FindAttributeIndex(subsystem, "SeekVoltage"); voltageSubsystem = (Entity *)seek_voltage_pointer; // this[0x29] this->subsystem = (Subsystem *)subsystem; // this[0x2A] // graphicsView -> GraphicGauge::localView. SetExtent == SetPositionWithinPort; // the original SetClip() (vtbl+0x0C) constrained drawing to that extent. localView.SetPositionWithinPort(0x97, 0x80, 0x17d, 0x13b); // vtbl+0x08 xScale = SeekVoltageXScale; // this[0x2C] yScale = SeekVoltageYScale; // this[0x2D] drawCursor = draw_cursor; // this[0x2F] destroyedShown = 0; // this[0x2E] } // // @004c68ac -- dtor. Release the destroyed bitmap, GraphicGauge::~GraphicGauge. // SeekVoltageGraph::~SeekVoltageGraph() { L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer; warehouse->bitMapBin.Release(destroyedImage); } Logical SeekVoltageGraph::TestInstance() const { return GraphicGauge::TestInstance(); } // // @004c6920 -- BecameActive: previousVoltage = 9999.0f (force first redraw). // void SeekVoltageGraph::BecameActive() { previousVoltage = RedrawSentinel; // @0xAC force first redraw destroyedShown = 0; } // // @004c6934 -- Execute (the seek-voltage curve plot). BRING-UP: the full plot // (SetupClip/DrawTicks/DrawCursor over a 0..1 voltage polyline) needs the live // seek-voltage sampler (subsys vtbl+0x3c) + the four attribute indices; the // attribute resolve is summarised (FindAttributeIndex returns -1), so the plot is // deferred and Execute is inert until the sampler + attribute reads land. Kept as // a defined no-op so the gauge's vtable is complete (no /FORCE crash when the // Myomer/Energy cluster ticks its SeekVoltageGraph child). // void SeekVoltageGraph::Execute() { // (seek-voltage plot deferred -- see note above.) } // // @004c6934 (original notes preserved) -- Execute. If the subsystem is destroyed // (subsys+0x40 == 1) draw // the centred "destroyed" bitmap once. Otherwise, when the live seek voltage // (subsys vtbl+0x3c sampler) changes: clear (SetupClip @004c6be4), re-plot the // 0..1 voltage curve as a polyline (step _DAT_004c6bdc up to _DAT_004c6be0, // Round each point), then draw the min/max tick marks (DrawTicks @004c6c6c) // and, if drawCursor, the live cursor at the current seek index // (DrawCursor @004c6c30). // //########################################################################### // ConfigMapGauge @004c6d80 //########################################################################### // // @004c6d80 -- ctor (vtable PTR_FUN_0051a1b8). GraphicGauge base; interns the // joystick base bitmap "btjoy.pcc" (BitMapCache AddRef) + the four config-state // pixmaps cm_off/cm_other/cm_only/cm_both.pcc (PixMapCache AddRef); sets the port // origin (x,y); subsystem = param_9; color = 0. // ConfigMapGauge::ConfigMapGauge( GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer_in, int graphics_port_number, int x, int y, Entity *subsystem_in, const char *identification_string ): GraphicGauge(rate, mode_mask, renderer_in, 0 /*owner*/, // FUN_00444818 graphics_port_number, identification_string) { joystickImage = (char *)"btjoy.pcc"; // @0x98 this[0x26] stateImage[0] = (char *)"cm_off.pcc"; // @0x9C this[0x27] stateImage[1] = (char *)"cm_other.pcc"; // @0xA0 this[0x28] stateImage[2] = (char *)"cm_only.pcc"; // @0xA4 this[0x29] stateImage[3] = (char *)"cm_both.pcc"; // @0xA8 this[0x2A] localView.SetOrigin(x, y); // vtbl+0x10 (this+0x48) color = 0; // @0x94 this[0x25] subsystem = subsystem_in; // @0x90 this[0x24] L4Warehouse *warehouse = (L4Warehouse *)renderer_in->warehousePointer; warehouse->bitMapBin.Get(joystickImage); // FUN_00442aec for (int i = 0; i < 4; i++) warehouse->pixelMap8Bin.Get(stateImage[i]); // FUN_00442d2b } // // @004c6e54 -- dtor: release btjoy bitmap + 4 pixmaps, GraphicGauge::~GraphicGauge. // ConfigMapGauge::~ConfigMapGauge() { L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer; warehouse->bitMapBin.Release(joystickImage); // FUN_00442c12 for (int i = 0; i < 4; i++) warehouse->pixelMap8Bin.Release(stateImage[i]); // FUN_00442e51 } Logical ConfigMapGauge::TestInstance() const { return GraphicGauge::TestInstance(); } // // @004c6ee0 -- SetColor: color (this[0x25]) = the passed value. // void ConfigMapGauge::SetColor(int color_in) // @004c6ee0 { color = color_in; } // // @004c6ef4 -- BecameActive: force a full redraw (dirty=1, previousState = -1). // void ConfigMapGauge::BecameActive() { dirty = True; // @0xBC this[0x2F] for (int i = 0; i < 4; i++) previousState[i] = -1; // @0xAC this[0x2B..0x2E] } // // @004c6f1c -- Execute. When enabled (color != 0): on the dirty flag, blit the // base joystick bitmap; then for each of the 4 config-map slots read the control- // mapper state and, if changed, stretch-blit the matching cm_* pixmap. // // BRING-UP (marked): the per-slot control-mapper state comes from the App's // ModeManager sampled through a .data port/colour table (DAT_00518eb8) at raw // offsets (app+0x3c -> +0x1c0 + slot*0x20, sampler vtbl+0x24). That is the same // raw-App-offset read class that AVs before the ModeManager layout is // reconstructed (see the FillPilotArray fix, ยง10), and DAT_00518eb8 was not // recovered from the optimised image -- so the mapper-state loop is guarded off // and only the base-bitmap blit runs. The full loop lands with the ModeManager // reconstruction. // void ConfigMapGauge::Execute() { if (color == 0) return; if (dirty) { dirty = False; L4Warehouse *warehouse = (L4Warehouse *)renderer->warehousePointer; BitMap *base = warehouse->bitMapBin.Get(joystickImage); // FUN_00442aec if (base != NULL) { localView.SetColor(0xFF); // vtbl+0x18 localView.DrawBitMapOpaque(0 /*background*/, 0 /*rotation*/, base); // vtbl+0x54 } warehouse->bitMapBin.Release(joystickImage); } // (control-mapper state loop deferred -- see note above.) } //########################################################################### // OneOfSeveral data-driven lamps @004c70a4 / @004c7160 //########################################################################### // // @004c70a4 -- AnimatedSubsystemLamp ctor (vtable PTR_FUN_0051a174): OneOfSeveral // (fromImageStrip=1) + a CoolingLoopConnection feeding the selected frame from // the subsystem's cooling-loop state. Otherwise inherits OneOfSeveral::Execute. // AnimatedSubsystemLamp::AnimatedSubsystemLamp( GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer_in, int graphics_port_number, int x, int y, const char *image, int columns, int rows, void *subsystem, const char *identification_string ): OneOfSeveral(rate, mode_mask, renderer_in, graphics_port_number, // FUN_004c4d88 x, y, True /*fromImageStrip*/, image, 0, 0, columns, rows, identification_string) { selected = 0; // @0xAC this[0x2B] AddConnection(new CoolingLoopConnection(&selected, subsystem)); // FUN_004c3134 } AnimatedSubsystemLamp::~AnimatedSubsystemLamp() {} // @004c7134 (base chain) // // @004c7160 -- AnimatedSourceLamp ctor (vtable PTR_FUN_0051a130): identical shape // but a PowerSourceConnection (@004c31ec) drives the frame from the resolved // power source's bus-lamp state. // AnimatedSourceLamp::AnimatedSourceLamp( GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer_in, int graphics_port_number, int x, int y, const char *image, int columns, int rows, void *source, const char *identification_string ): OneOfSeveral(rate, mode_mask, renderer_in, graphics_port_number, x, y, True, image, 0, 0, columns, rows, identification_string) { selected = 0; AddConnection(new PowerSourceConnection(&selected, source)); // FUN_004c31ec } AnimatedSourceLamp::~AnimatedSourceLamp() {} // @004c71f0 //########################################################################### // ScalarBarGauge @004c721c //########################################################################### // // @004c721c -- ctor (vtable PTR_FUN_0051a0e4). Chains the MUNGA L4 bar primitive // (@00472ef0 == BarGraphBitMapScalar) with no auto-connection (value=NULL), then // AddConnection(new GeneratorVoltageConnection @004c3288) writing the inherited // WipeGaugeScalar::currentValue (@0xB4, this[0x2D]) from the resolved voltage // source. Generator-voltage bar, range 0..12000.0f. dtor @004c733c chains the // bar dtor @00472fb4 (implicit). // ScalarBarGauge::ScalarBarGauge( GaugeRate rate, ModeMask mode_mask, L4GaugeRenderer *renderer_in, int graphics_port_number, int left, int bottom, int right, int top, const char *tile_image, int foreground_color, int background_color, WipeGaugeScalar::Direction direction, Scalar min, Scalar max, void *voltage_source, const char *identification_string ): BarGraphBitMapScalar(rate, mode_mask, renderer_in, 0 /*owner*/, // FUN_00472ef0 graphics_port_number, left, bottom, right, top, tile_image, foreground_color, background_color, direction, min, max, (Scalar *)0 /*no auto-connection*/, identification_string) { AddConnection(new GeneratorVoltageConnection(¤tValue, voltage_source)); // FUN_004c3288 } ScalarBarGauge::~ScalarBarGauge() {} // @004c733c (base chain) //########################################################################### //########################################################################### // GeneratorCluster @004c7368 Make / @004c746c ctor //########################################################################### //########################################################################### // // @004c7368 -- Make. Resolve the named subsystem ("GeneratorCluster") via // Entity::FindSubObject (DAT_00518f24); on failure warn "Subsystem not // found" and return 0. Else allocate (0xb4) and construct, passing the four // resource strings / colours from DAT_00518e..00519298. // Logical GeneratorCluster::Make( int /*display_port_index*/, Vector2DOf /*position*/, Entity *entity, GaugeRenderer *gauge_renderer ) { Subsystem *subsystem = entity->FindSubsystem((char *)&DAT_00518f24); // FUN_0041f98c if (subsystem == NULL) { DebugStream << "Subsystem " << (char *)&DAT_00518f24 << " not found\n"; // FUN_004dbb24 x3 return False; } GeneratorCluster *gauge = (GeneratorCluster *)operator new(0xb4); if (gauge != NULL) { // // Construct with the recovered subsystem + name; the geometry / colour / // image-name resource arguments (DAT_00518f68..00519298) were not // recovered from the optimised image, so are passed as typed placeholders. // new (gauge) GeneratorCluster( (GaugeRate)DAT_00518ee0, // rate (ModeMask)0, // mode mask (L4GaugeRenderer *)gauge_renderer, 0, // graphics port number 0, 0, // x, y (char *)&DAT_00518f68, // background image 0, // color (char *)0, // temperature tile (Scalar *)0, (Scalar *)0, // temp low / high (char *)0, // voltage tile (Scalar *)0, // voltage low (char *)0, // leak image (char *)0, (char *)0, // lampA / lampB images 0, 0, // on / off colours subsystem, "GeneratorCluster"); } return True; } // // @004c746c -- ctor (vtable PTR_FUN_0051a0a0). GraphicGauge base; resolves // CurrentTemperature/DegradationTemperature/FailureTemperature/ // CoolantMassLeakRate/OutputVoltage attribute indices; interns the background // image; then builds the five child gauges (each parented at this graphics // port, offset from x,y): // this[0x26] = new VertTwoPartBar(...,CurrentTemp,Degrade,Fail) @004c4724 // this[0x27] = new ScalarBarGauge(0..12000, OutputVoltage) @004c72ac // this[0x28] = new LeakGauge(CoolantMassLeakRate) @004c5b7c // this[0x29] = new TwoState(lampA, subsys+0x1d4) @004739d8 // this[0x2A] = new TwoState(lampB, subsys+0x1d4) @004739d8 // this[0x2B] = subsystem // @004c7778 -- dtor: release the background, delete the five children, base dtor. // //########################################################################### //########################################################################### // PrepEngrScreen @004c7b30 Make / @004c7bf0 ctor //########################################################################### //########################################################################### // // @004c7b30 -- Make. DAT_0051936c is the engineer-screen number; must be 1..12 // or "PrepEngr: screen number out of range " is warned. Allocates (0x90) // and constructs with seven status-image names (DAT_005193b0..00519548). // // @004c7bf0 -- ctor (vtable PTR_FUN_0051a06c). Derives from the non-graphic // Gauge group (FUN_004440d4) -- it owns no GraphicsView, only a child-gauge // chain at +0x24. Interns and BitMapCache-AddRefs the seven status pixmaps // (this[0x1D..0x23]); stores mech (this[0x1B]) and screen number (this[0x1C]). // // @004c7d14 -- dtor: release + free the seven images, tear down the child chain // (FUN_0044ae0c), Gauge-group dtor (FUN_00443f7c). // // @004c7e48 -- Execute. Walk the mech's critical-subsystem roster // (mech+0x124 count, mech+0x128 array). For each subsystem that (a) is a // Generator-derived weapon (IsDerivedFrom 0x50f4bc) and (b) belongs to the // selected screen (subsys+0x1dc == screenNumber), draw its label and -- via // NumericDisplay (FUN_004700bc/0047043 0) -- its temperature and a HeatSink // read-out, then dispatch on ClassID to lay out the weapon's status bitmaps // (FUN_004c79c8 = draw three labelled status cells): // 0xBC3 Sensor -> one cell + ammo bitmap (this[0x23]) // 0xBC6 -> two cells (this[0x22],this[0x20]) // 0xBC8/0xBD4 Emitter/PPC -> three cells (this[0x1E..0x20]) // 0xBCD/0xBD0 Proj/Missile -> one cell (this[0x21]) // 0xBCE GaussRifle -> warn "Gauss rifle not yet supported", one cell // //########################################################################### //########################################################################### // SubsystemCluster family @004c8140 base //########################################################################### //########################################################################### // // @004c8140 -- SubsystemCluster ctor (vtable PTR_FUN_0051a020). GraphicGauge // base. Resolves InputVoltage/HeatSink/CurrentTemperature/Degradation/Failure // attribute indices, then builds the panel's child gauges into its port: // this[0x26] HorizTwoPartBar (temperature) @004c4170 // this[0x27] AnimatedSubsystemLamp ("CoolingLoop",btploop) @004c70a4 // this[0x29] AnimatedSourceLamp ("PowerSource",btpbus) @004c7160 (if InputVoltage) // this[0x24] BackgroundBitmap (if background present) @00471d00 // this[0x28] AnimatedSubsystemLamp (bteloop) @004c70a4 // this[0x2D] OneOfSeveralInt (btecmode, subsys+0x134) @004c5148 // this[0x2A] AnimatedSourceLamp (btebus) @004c7160 (if InputVoltage) // this[0x25] ScalarBarGauge (evolt, GeneratorVoltage) @004c721c (if InputVoltage) // this[0x2E] OneOfSeveralStates (btemode, subsys+0x2b8) @004c5470 // (only when subsystem IsDerivedFrom Generator 0x50f4bc) // this[0x2C] VertTwoPartBar x2 (temperatures) @004c4724 // this[0x2F] LeakGauge (eleak, CoolantMassLeakRate) @004c5b7c // this[0x30] subsystem ; this[0x31] operating // @004c87dc -- dtor: GraphicGauge::~GraphicGauge (children freed by subclasses). // @004c88e4 -- Execute: operating = (subsys+0x40==1); when it changes, repaint // the panel frame (operational => coloured rectangle; offline => background // bitmap blit) and forward enable to the temperature/loop children // (SetEnable @004c8a28 / FUN_00444650). // // @004c8a6c -- HeatSinkCluster ctor (vtable PTR_FUN_00519fd4): chains // SubsystemCluster, this[0x36]=100.0f, then builds four failure TwoStates // (this[0x37] heat, this[0x38] btehfail, this[0x39] btepfail subsys+0x324, // this[0x3A] btesfail subsys+0x320) and a NumericDisplayScalar (this[0x3B], // helv18, this[0x36]) plus a Scalar connection (&this[0x35], subsys+0x31c). // Execute @004c8db0: failFlag = (failSubsystem+0x40==1); heatLoadScaled = // heatLoad * _DAT_004c8df0; chain SubsystemCluster::Execute. // // @004c8fc4 -- WeaponCluster ctor (vtable PTR_FUN_00519f38): chains // SubsystemCluster; resolves "PercentDone"; interns the cluster image // (this[0x33]); warns "WeaponCluster missing " if absent; centres a // SegmentArc270 recharge dial (this[0x34], 0..360, @004c6244) and a // ConfigMapGauge (this[0x35], "ConfigMap", @004c6d80); adds a Scalar connection // (&percentDone, PercentDone). Execute @004c9290: chain base Execute; when // percentDone > _DAT_004c92e0 toggle the warning lamp (DrawWarningLamp @004c932c). // // @004c93b0 -- EnergyWeaponCluster ctor (vtable PTR_FUN_00519ee8): chains // WeaponCluster; adds a SeekVoltageGraph (this[0x3A], "EngrGraph", edestryd.pcc, // @004c6798) reading OutputVoltage, and a seek-step OneOfSeveralInt (this[0x3B], // bteseek.pcc, subsys+0x3f0, @004c5148). dtor @004c94dc. // // @004c9558 -- BallisticWeaponCluster ctor (vtable PTR_FUN_00519e98): chains // WeaponCluster; resolves the ammo bin (Resolve subsys+0x43c) and builds: // this[0x45]/[0x46] NumericDisplayInteger (ammo counts) @00470cfc // this[0x42] TwoState (btejam) this[0x43] TwoState (btefire) @004739d8 // this[0x3F] GraphicsViewRecord erase tracker @0044ad78 // this[0x40] NumericDisplayScalarTwoState (helv42) @00473c94 // this[0x41] TwoState (edestryd) @004739d8 // this[0x44] BitMapInverseWipeScalar (bteejtm, subsys+0x3f8) @004c61c8 // Execute @004c9a38: jammed = (subsys+0x364==5 weaponAlarm Jammed); read the // ammo-bin reload state (bin+0x18c) and, while reloading, compute elapsed // seconds (frame clock @00414b60 / DAT_0052140c); chain WeaponCluster::Execute. // TestInstance @004c9adc: True unless the ammo bin is present and the eject // wipe is mid-cycle. dtor @004c9940 frees the GraphicsViewRecord + numeric. // // // FUN_004c2ec4 -- draw a clipped port-background bitmap: MoveTo(x,y), fetch the // tile from the BitMap cache, opaque-blit it, release. (Used by the cluster // ctor to paint the panel's static frame image into the MFD port.) // static void DrawPortBackground(GraphicsView *view, L4Warehouse *warehouse, int x, int y, const char *tile) { view->MoveToAbsolute(x, y); // vtbl+0x24 BitMap *bmp = warehouse->bitMapBin.Get(tile); // FUN_00442aec if (bmp != NULL) view->DrawBitMapOpaque(0 /*background*/, 0 /*rotation*/, bmp); // vtbl+0x58 warehouse->bitMapBin.Release(tile); // FUN_00442c12 } // A default per-child load-distribution rate (the ctor uses FUN_004442b8/dc/290, // the NextNthTierGaugeRate allocators). static inline GaugeRate ChildRate() { return Gauge::NextThirdTierGaugeRate(); } //########################################################################### // SubsystemCluster @004c8140 (vtable 0x51a020) //########################################################################### // // @004c8140 -- ctor. rate/mode are hardcoded 0xFFFF/0xFFFFFFFF in the base // GraphicGauge; the two bit-plane masks are mfd_mode (MFD-port children) and // eng_mode (engineering-port children). Builds the panel's child gauges (see // the header comment block) parented to the MFD port (param_5) and the engineering // port (resolved from eng_port_name); optional children gate on the InputVoltage // attribute (power bus present) and the damage-zone-index background image. // SubsystemCluster::SubsystemCluster( ModeMask mfd_mode, L4GaugeRenderer *renderer_in, int owner_ID, int mfd_port, int x, int y, Subsystem *subsystem_in, ModeMask eng_mode, const char *eng_port_name, const char *tile_image, const char *label, char **image_names, const char *identification_string ): GraphicGauge((GaugeRate)0xFFFF, (ModeMask)0xFFFFFFFF, renderer_in, // FUN_00444818 owner_ID, mfd_port, identification_string) { L4Warehouse *warehouse = (L4Warehouse *)renderer_in->warehousePointer; // Resolve the temperature / power attributes off the subsystem. void *inputVoltage = AttributePointerOf(subsystem_in, "InputVoltage"); void *heatSink = AttributePointerOf(subsystem_in, "HeatSink"); void *currentTemp = AttributePointerOf(subsystem_in, "CurrentTemperature"); void *degradeTemp = AttributePointerOf(subsystem_in, "DegradationTemperature"); void *failTemp = AttributePointerOf(subsystem_in, "FailureTemperature"); //------------------------------------------------------------------ MFD port temperatureBar = new HorizTwoPartBar(ChildRate(), mfd_mode, renderer_in, // @004c4170 mfd_port, x + 0x13, y + 0x10, x + 0xdc, y + 0x1f, tile_image, 0xff, 0, (Scalar *)currentTemp, (Scalar *)degradeTemp, (Scalar *)failTemp, "HorizTwoPartBar"); coolingLoopA = new AnimatedSubsystemLamp(ChildRate(), mfd_mode, renderer_in, // @004c70a4 mfd_port, x + 0xe5, y + 0xe, "btploop.pcc", 7, 1, subsystem_in, "CoolingLoop"); if (inputVoltage == NULL) powerSourceA = NULL; else powerSourceA = new AnimatedSourceLamp(ChildRate(), mfd_mode, renderer_in, // @004c7160 mfd_port, x + 0x10e, y + 0xe, "btpbus.pcc", 5, 1, inputVoltage, "PowerSource"); // Background: image_names[ auxScreenPlacement ] if present. (Placement@0x1e0 is // read via the PoweredSubsystem bridge, not the non-byte-exact raw offset.) int dzIndex = -1; BTGetSubsystemAuxScreen(subsystem_in, NULL, &dzIndex, NULL); // auxScreenPlacement background = NULL; if (dzIndex >= 0 && image_names[dzIndex] != NULL) background = new BackgroundBitmap(mfd_mode, renderer_in, owner_ID, // @00471d00 mfd_port, x + 0x90, y + 0x36, image_names[dzIndex], 0, 0xff, 0, "BackgroundBitmap"); // Paint the panel frame image into the MFD port. localView.SetOrigin(x, y); // vtbl+0x10 localView.SetColor(0xff); // vtbl+0x18 DrawPortBackground(&localView, warehouse, 9, 0xb4, label); // FUN_004c2ec4 //----------------------------------------------------------- engineering port int engPort = renderer_in->FindGraphicsPort(eng_port_name); // FUN_00447f1c (name -> port number) // Attributes off the linked heat subsystem (Resolve(heatSink)). void *linkedHeat = ResolveLink(heatSink); // FUN_00417ab4 void *linkTemp = AttributePointerOf(linkedHeat, "CurrentTemperature"); void *linkDegrade = AttributePointerOf(linkedHeat, "DegradationTemperature"); void *coolantLeak = AttributePointerOf(subsystem_in, "CoolantMassLeakRate"); coolingLoopB = new AnimatedSubsystemLamp(ChildRate(), eng_mode, renderer_in, // @004c70a4 engPort, 0x254, 0x13b, "bteloop.pcc", 7, 1, subsystem_in, "CoolingLoop"); modeLamp = new OneOfSeveralInt(ChildRate(), eng_mode, renderer_in, // @004c5148 engPort, 400, 10, "btecmode.pcc", 1, 2, (int *)((char *)subsystem_in + 0x134), "OneOfSeveralInt"); // BEST-EFFORT raw offset if (inputVoltage == NULL) { powerSourceB = NULL; generatorVoltageBar = NULL; stateLamp = NULL; } else { powerSourceB = new AnimatedSourceLamp(ChildRate(), eng_mode, renderer_in, // @004c7160 engPort, 0x1a, 0xd0, "btebus.pcc", 5, 1, inputVoltage, "PowerSource"); generatorVoltageBar = new ScalarBarGauge(ChildRate(), eng_mode, renderer_in, // @004c721c engPort, 0x89, 0x82, 0x93, 0x13b, "evolt.pcc", 0xff, 0, WipeGaugeScalar::wipeUp, 0.0f, GeneratorMaxVoltage, inputVoltage, "GeneratorVoltage(slotOf)"); // stateLamp only when the subsystem is a Generator (IsDerivedFrom 0x50f4bc). if (IsGeneratorDerived(subsystem_in)) stateLamp = new OneOfSeveralStates(ChildRate(), eng_mode, renderer_in, // @004c5470 engPort, 0xbe, 0, "btemode.pcc", 1, 3, (Entity *)((char *)subsystem_in + 0x2b8), "OneOfSeveralStates"); // BEST-EFFORT raw else stateLamp = NULL; } vertBarA = new VertTwoPartBar(ChildRate(), eng_mode, renderer_in, // @004c4724 engPort, 0x1d1, 0x5c, 0x1eb, 0x124, tile_image, 0xff, 0xff, 0, (Scalar *)currentTemp, (Scalar *)degradeTemp, (Scalar *)failTemp, "VertTwoPartBar"); vertBarB = new VertTwoPartBar(ChildRate(), eng_mode, renderer_in, engPort, 0x222, 0x5c, 0x23d, 0x124, tile_image, 0xff, 0xff, 0, (Scalar *)linkTemp, (Scalar *)linkDegrade, (Scalar *)failTemp, "VertTwoPartBar"); leakGauge = new BitMapInverseWipe(ChildRate(), eng_mode, renderer_in, // @004c5b7c engPort, 0x255, 0xe0, "eleak.pcc", 0, 0xff, *(int *)((char *)subsystem_in + 0x150) /*third*/, 3 /*frames*/, (Scalar *)coolantLeak, "LeakGauge"); // BEST-EFFORT raw (subsys+0x150) operating = False; // @0xC4 this[0x31] subsystem = subsystem_in; // @0xC0 this[0x30] previousDrawState = 0; // @0xC8 this[0x32] } // // @004c87dc -- dtor. Free the owned children, then GraphicGauge::~GraphicGauge // (implicit). (The binary frees children through the vtbl-slot-3 ReleaseChildren // path; done here for RAII correctness.) // SubsystemCluster::~SubsystemCluster() { ReleaseChildren(); // FUN_004c8820 } // // @004c8820 -- free each owned child gauge (delete via its own dtor). // void SubsystemCluster::ReleaseChildren() { delete background; background = NULL; delete generatorVoltageBar; generatorVoltageBar = NULL; delete temperatureBar; temperatureBar = NULL; delete coolingLoopA; coolingLoopA = NULL; delete coolingLoopB; coolingLoopB = NULL; delete powerSourceA; powerSourceA = NULL; delete powerSourceB; powerSourceB = NULL; delete vertBarA; vertBarA = NULL; delete vertBarB; vertBarB = NULL; delete modeLamp; modeLamp = NULL; delete stateLamp; stateLamp = NULL; delete leakGauge; leakGauge = NULL; } // // @004c89c4 -- draw the panel's border frame (two thick diagonals of the box). // void SubsystemCluster::DrawPanelFrame(int color) { localView.SetColor(color); // vtbl+0x18 localView.MoveToAbsolute(9, 0x33); // vtbl+0x24 localView.DrawThickLineToAbsolute(0x132, 0xab); // vtbl+0x38 localView.MoveToAbsolute(9, 0xab); localView.DrawThickLineToAbsolute(0x132, 0x33); } // // @004c8a28 -- forward an enable/disable to the always-present children (temp bar, // cooling loop, power source A). // void SubsystemCluster::SetChildrenEnable(int enable) { // FUN_00444650 forwards visibility; enable==0 (offline) => Disable(True). if (temperatureBar) temperatureBar->Disable(enable == 0); if (coolingLoopA) coolingLoopA->Disable(enable == 0); if (powerSourceA) powerSourceA->Disable(enable == 0); } // // @004c8990 -- the operational-state virtual (drawn as TestInstance): active // unless the subsystem is offline (operating==0 AND subsystem+0x278 == 4). // int SubsystemCluster::GetDrawState() { if (operating != 0) return 1; if (*(int *)((char *)subsystem + 0x278) != 4) // BEST-EFFORT raw offset return 1; return 0; } Logical SubsystemCluster::TestInstance() const { return GraphicGauge::TestInstance(); } // // @004c88e4 -- Execute (shared by the whole family). operating = (subsystem is // operational, +0x40 == 1). On a draw-state change repaint the panel frame: // offline => frame(0) + redraw background bitmap; online => black fill + frame(0xff); // and forward the enable to the temperature / loop children. // void SubsystemCluster::Execute() { operating = (*(int *)((char *)subsystem + 0x40) == 1); // BEST-EFFORT raw offset int state = GetDrawState(); if (state != previousDrawState) { previousDrawState = state; SetChildrenEnable(state); if (state == 0) { DrawPanelFrame(0); if (background != NULL) background->BecameActive(); // (child vtbl+0xc) force redraw } else { localView.SetColor(0); // vtbl+0x18 localView.MoveToAbsolute(1, 0x32); // vtbl+0x24 localView.DrawFilledRectangleToAbsolute(0x136, 0xad); // vtbl+0x48 DrawPanelFrame(0xff); } } } void SubsystemCluster::BecameActive() { previousDrawState = -1; // force first-frame repaint } // Recovered .data tuning constants (read from the optimised image). static const Scalar HeatLoadScale = 100.0f; // _DAT_004c8df0 static const Scalar WeaponWarnThreshold = 0.99f; // _DAT_004c92e0 //########################################################################### // HeatSinkCluster @004c8a6c (vtable 0x519fd4) //########################################################################### // // @004c8a6c -- ctor: SubsystemCluster base + four failure TwoStates (heat / heat- // sink / power / structure) + a NumericDisplayScalar read-out, plus a Scalar // connection feeding heatLoad from the subsystem (+0x31c). // HeatSinkCluster::HeatSinkCluster( ModeMask mfd_mode, L4GaugeRenderer *renderer_in, int owner_ID, int mfd_port, int x, int y, Subsystem *subsystem_in, Subsystem *hud, ModeMask eng_mode, const char *eng_port_name, const char *tile_image, const char *label, const char *fail_lamp_image, char **image_names, const char *identification_string ): SubsystemCluster(mfd_mode, renderer_in, owner_ID, mfd_port, x, y, subsystem_in, eng_mode, eng_port_name, tile_image, label, image_names, identification_string) { heatLoadScaled = HeatNumericBase; // @0x36 100.0f int engPort = renderer_in->FindGraphicsPort(eng_port_name); heatFailLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID, // @004739d8 engPort, 200, 0x110, fail_lamp_image, 0, 0xff, (int *)&operating, "TwoState"); failSubsystem = hud; // @0x33 failFlag = 0; // @0x34 hsFailLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID, engPort, 0x97, 0xe8, "btehfail.pcc", 0, 0xff, (int *)&failFlag, "TwoState"); powerFailLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID, engPort, 0x97, 0xc0, "btepfail.pcc", 0, 0xff, (int *)((char *)subsystem_in + 0x324), "TwoState"); // BEST-EFFORT raw offset structFailLamp = new TwoState(ChildRate(), eng_mode, renderer_in, owner_ID, engPort, 0x97, 0x98, "btesfail.pcc", 0xff, 0, (int *)((char *)subsystem_in + 0x320), "TwoState"); // BEST-EFFORT raw offset heatNumeric = new NumericDisplayScalar(ChildRate(), eng_mode, renderer_in, // @00470888 owner_ID, engPort, 0x132, 0x8c, 0x15a, 0xaa, "helv18.pcc", 3, (NumericDisplay::NumericFormat)0, 0, 0xff, &heatLoadScaled, "NumericDisplayScalar"); AddConnection(new GaugeConnectionDirectOf(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): the 0..360 recharge dial reading the // weapon's PercentDone attribute (a real 0..1 fraction). rechargeArc = (GraphicGauge *)new SegmentArc270((GaugeRate)0xffff, mfd_mode, // @004c6244 renderer_in, owner_ID, mfd_port, x + 0x41, y + 0x6d, 40.0f, 60.0f, 40.0f, 60.0f, 0.0f, 360.0f, 0x14, 0, 0xff, (Scalar *)percentDoneAttr, True, "SegmentArc270"); configMap = new ConfigMapGauge(ChildRate(), mfd_mode, renderer_in, // @004c6d80 mfd_port, x + 0xee, y + 0x38, (Entity *)subsystem_in, "ConfigMap"); warningState = 0; // @0x39 AddConnection(new GaugeConnectionDirectOf(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 if (getenv("BT_VSS_LOG")) DEBUG_STREAM << "[vss] Make called: subsystemCount=" << count << "\n" << std::flush; for (int i = 0; i < count; i++) { if (i == 0) // slot 0 = the controls mapper continue; Subsystem *sub = entity->GetSubsystem(i); // entity+0x128[i] if (sub == NULL) continue; // Read the engineering-screen assignment via the PoweredSubsystem bridge // (also the type filter -- False == not PoweredSubsystem-derived). int auxScreen = 0, placement = -1; char labelBuf[64]; if (!BTGetSubsystemAuxScreen(sub, &auxScreen, &placement, labelBuf)) // FUN_0041a1a4 + sub[0x1dc]/[0x1e4] continue; int group = auxScreen - 1; // aux-screen position 1..12 if (getenv("BT_VSS_LOG")) DEBUG_STREAM << "[vss] i=" << i << " classID=" << (int)sub->GetClassID() << " auxScreen=" << auxScreen << " placement=" << placement << " label='" << labelBuf << "'\n" << std::flush; if (group < 0) { DebugStream << "Auxiliary screen position = zero\n"; continue; } if (group >= 12) { DebugStream << "Illegal auxiliary screen position =" << group << "\n"; continue; } const AuxScreenGeometry &g = kAuxGeom[group]; int mfdPort = renderer->FindGraphicsPort((char *)g.mfdPortName); // FUN_00447f1c char *label = labelBuf; // the subsystem label (bridge) char *tile = (char *)"btwarn.pcc"; switch (sub->GetClassID()) // sub+4 { case 0xBC3: // Sensor -> HeatSinkCluster (shows the "HUD" subsystem) { Subsystem *hud = entity->FindSubsystem((char *)"HUD"); // FUN_0041f98c new HeatSinkCluster(g.planeMask, renderer, 0, mfdPort, g.x, g.y, sub, hud, g.subBit, g.engPortName, tile, label, "edestryd.pcc", imageNames, "SensorCluster"); break; } case 0xBC6: // Myomers -> MyomerCluster new MyomerCluster(g.planeMask, renderer, 0, mfdPort, g.x, g.y, sub, g.subBit, g.engPortName, tile, label, imageNames, "MyomerCluster"); break; case 0xBC8: // Emitter case 0xBD4: // PPC { const char *clusterImage = (*(int *)((char *)sub + 0x334) == 0) ? "qcircle.pcc" : "qcircr.pcc"; new EnergyWeaponCluster(g.planeMask, renderer, 0, mfdPort, g.x, g.y, sub, g.subBit, g.engPortName, tile, label, clusterImage, imageNames, "EnergyWeaponCluster"); break; } case 0xBCD: // ProjectileWeapon case 0xBD0: // MissileLauncher new BallisticWeaponCluster(g.planeMask, renderer, 0, mfdPort, g.x, g.y, sub, g.subBit, g.engPortName, tile, label, "qcircle.pcc", imageNames, "smlfont.pcc", "helv42.pcc", "ProjWeaponCluster"); break; case 0xBCE: // GaussRifle -- not yet supported DebugStream << "Gauss rifle not yet supported\n"; break; default: break; } } return True; } // // The registered method: config primitive "vehicleSubSystems(q0.pcc..q7.pcc)". // MethodDescription VehicleSubSystems::methodDescription = { "vehicleSubSystems", VehicleSubSystems::Make, { { ParameterDescription::typeString, NULL }, // qXXX0.pcc { ParameterDescription::typeString, NULL }, // qXXX1.pcc { ParameterDescription::typeString, NULL }, // qXXX2.pcc { ParameterDescription::typeString, NULL }, // qXXX3.pcc { ParameterDescription::typeString, NULL }, // qXXX4.pcc { ParameterDescription::typeString, NULL }, // qXXX5.pcc { ParameterDescription::typeString, NULL }, // qXXX6.pcc { ParameterDescription::typeString, NULL }, // qXXX7.pcc { ParameterDescription::typeEmpty, NULL } } }; // === btl4gau3.cpp begins at @004c9bd0 (PlayerStatusMappingGroup) -- not this TU.