//===========================================================================// // File: searchlight.hpp // // Project: BattleTech // // Contents: Searchlight (illumination) subsystem // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved // // PROPRIETARY AND CONFIDENTIAL // //===========================================================================// // // RECONSTRUCTED from the shipped binary (Ghidra pseudo-C, recovered shard // part_013.c). NO SEARCHLIGHT.HPP survived; the class body is inferred from // usage and the embedded string pool. Each method cites its @ADDR. // // IDENTIFICATION: // * mech3.cpp factory map: Searchlight::CreateStreamedSubsystem @004b85a8, // DefaultData &DAT_00511198. // * vtable @005114d4 (slot0 dtor @004b8568), ctor @004b84dc. // * CreateStreamedSubsystem @004b85a8 stamps classID 0x0BD8 and streamed // model size 0xF4; it parses NO searchlight-specific model fields (empty // resource extension). // * String pool @00511440 ("ToggleLamp", "Searchlight", "LightOn", // "LightState"). ClassDerivations name "Searchlight" @005111a8. // * A message-handler record @00511210 binds "ToggleLamp" -> FUN_004b860c. // // BASE CLASS: // Searchlight IS-A PowerWatcher (powersub.hpp): ctor @004b84dc chains to the // PowerWatcher ctor FUN_004b18a4 with &DAT_00511198, and // CreateStreamedSubsystem chains to PowerWatcher::CreateStreamedSubsystem // FUN_004b198c. The shared base occupies bytes 0..0x1D8; Searchlight's own // members begin at 0x1D8. It exposes one queryable attribute "LightState". // #if !defined(SEARCHLIGHT_HPP) # define SEARCHLIGHT_HPP #if !defined(POWERSUB_HPP) #include "powersub.hpp" #endif //########################################################################## //##################### Local multi-level alarm shim ################# //########################################################################## // // CROSS-FAMILY: heat.hpp does `typedef GaugeAlarm AlarmIndicator;` but the // engine GaugeAlarm (GAUGALRM.h) has a PROTECTED ctor/dtor and no // Initialize/Finalize/SetLevel; the mech.hpp/mechrecon.hpp alias resolves // AlarmIndicator to ReconAlarm, which also lacks Initialize/Finalize. Until // the heat family provides a real multi-level AlarmIndicator, this module owns // a tiny faithful stand-in for ITS OWN alarm member (it does NOT redefine the // AlarmIndicator typedef). // #if !defined(BT_LOCAL_ALARM_SHIM) # define BT_LOCAL_ALARM_SHIM struct BtAlarm { int level; BtAlarm() : level(0) {} void Initialize(int /*levels*/) { level = 0; } // FUN_0041b9ec void Finalize() {} // FUN_0041baa4 void SetLevel(int n) { level = n; } // FUN_0041bbd8 int GetLevel() const { return level; } }; #endif //########################################################################## //##################### Searchlight::SubsystemResource ############### //########################################################################## // // Empty extension of the PowerWatcher resource (CreateStreamedSubsystem // @004b85a8 reads no light-specific fields; it only stamps classID/model // size). The ctor seeds commandedOn from the inherited segment field (+0x28). // struct Searchlight__SubsystemResource: public PowerWatcher::SubsystemResource { // RESOURCE_AUDIT.md: Searchlight adds NO resource field. The ctor seeds // commandedOn from the INHERITED segmentIndex (resource +0x28); the old // `int segmentDefault;` was appended after the base (compiled at ~0xF4, OOB) // and read garbage. Empty extension = correct 0xF4 model size. }; //########################################################################## //########################## Searchlight ############################# //########################################################################## class Searchlight: public PowerWatcher { friend struct SearchlightLayoutCheck; // compile-time offset locks (searchlight.cpp) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Shared Data Support // // DefaultData @00511198, ClassDerivations @005111a8 ("Searchlight"). // public: static Derivation ClassDerivations; // @005111a8 static SharedData DefaultData; // @00511198 static Receiver::MessageHandlerSet MessageHandlers; // @00511210 ("ToggleLamp") //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Attribute Support // // AttributePointers IndexEntry table @005111e0: // id 5 "LightState" -> @0x1D8 (offset encoded 0x1D9 == 0x1D8|1) // public: enum { // The cockpit button-5 lamp binds "Searchlight/LightOn" (L4GAUGE.CFG:5017); // the published NAME must be "LightOn" for Find() to hit, bound to the // reported on/off member lightState@0x1D8. LightOnAttributeID = PowerWatcher::NextAttributeID, // 5 NextAttributeID }; private: static const IndexEntry AttributePointers[]; // @005111e0 protected: static AttributeIndexSet AttributeIndex; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Local data. // protected: int lightState; // @0x1D8 reported on/off (gated by power + heat each frame); "LightState" int commandedOn; // @0x1DC resource +0x28; toggled by the "ToggleLamp" message (@004b860c) int requestedOn; // @0x1E0 init 0; on/off input consumed by SearchlightSimulation BtAlarm stateAlarm; // @0x1E4 2-level light indicator (size 0x54) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // BT segment / base-state compatibility shims (CROSS-FAMILY). // // These accessors / mutators logically belong on the shared bases // (Subsystem::GetSegmentFlags / SetInstanceFlag / SetGraphicsDirty, // PowerWatcher::HostShutDown / WatchedVoltageLevel, HeatSink::HeatStateLevel, // Mech controls-mapper lightsEnabled). Until those families expose them they // are backed by local members so this module compiles; see report. // public: // De-shimmed (WAVE 4 un-stub): the 7 CROSS-FAMILY backing fields // (segmentFlags/hostShutDown/watchedVoltageLevel/heatStateLevel/ // controlsAllowLights/graphicsDirty/instanceFlags) are DELETED. They // duplicated engine-base state AND -- via the old shadow `segmentFlags` // (seeded 0) -- made the ctor gate ((0&0xC)==0 && (0&0x100)!=0) FALSE, so // SearchlightSimulation was NEVER installed. Accessors now read the REAL // inherited base members (mirrors torso.hpp/torso.cpp): // HostShutDown <- MechSubsystem::simulationState == Destroyed (this+0x40) // WatchedVoltageLevel <- PowerWatcher::watchdogAlarm level (this+0x198) // HeatStateLevel <- HeatWatcher::heatAlarm level (this+0x140) // SetGraphicsDirty -> engine Simulation::ForceUpdate() (this+0x18) // SetInstanceFlag -> engine Simulation::simulationFlags |= bits (this+0x28) // GetSegmentFlags() removed (the ctor gate now reads owner->simulationFlags). // ControlsAllowLights(): no ported base member (the real read is the // owner controls-mapper lightsEnabled via an unsafe raw owner-offset // chain) -> return True for bring-up; the only consumer (ToggleLamp) is // dormant. Faithful follow-up = a real Mech::ControlsAllowLights(). Logical HostShutDown() const { return simulationState == 1; /* Destroyed */ } int WatchedVoltageLevel() const { return watchdogAlarm.GetLevel(); } // @0x198 int HeatStateLevel() const { return heatAlarm.GetLevel(); } // @0x140 Logical ControlsAllowLights() const { return True; } // bring-up (see above) void SetGraphicsDirty() { ForceUpdate(); } // this[0x18] |= 1 void SetInstanceFlag(int bits) { simulationFlags |= bits; } // this[10] |= bits // The "ToggleLamp" command arg is carried at message+0xC (decomp). static int MessageArg(Message &m) { return *(const int *)((const char *)&m + 0xC); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Simulation Support // public: typedef void (Searchlight::*Performance)(Scalar time_slice); void SetPerformance(Performance performance) { Check(this); activePerformance = (Simulation::Performance)performance; } void SearchlightSimulation(Scalar time_slice); // @004b841c (Performance, PTR @00511200) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Message handling // public: Logical ToggleLamp(Message &message); // @004b860c (bound to "ToggleLamp" @00511210) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Subsystem virtual overrides (vtable @005114d4) // // Only slots 6 and 7 (network state replication) are HUD-specific; slot 9 // (HandleMessage) and slot 10 (ResetToInitialState) inherit PowerWatcher's // @004b179c / @004b1804. // // The decomp named these ReadUpdate/WriteUpdate over a fictional // NetworkPacket.value / NetworkRecord{kind,value}. The real engine // network-state-replication virtuals (SIMULATE.h) are // ReadUpdateRecord(UpdateRecord*) / WriteUpdateRecord(UpdateRecord*,int); // the replicated on/off and the 0x14 record tag map to the real // UpdateRecord::simulationState / ::recordID fields. // public: void ReadUpdateRecord(UpdateRecord *message); // slot 6, @004b83b8 void WriteUpdateRecord(UpdateRecord *message, int update_model); // slot 7, @004b83f0 (record tag 0x14) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Test Class Support // public: static Logical TestClass(Mech&); Logical TestInstance() const; // @004b85f0 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Construction and Destruction // public: typedef Searchlight__SubsystemResource SubsystemResource; Searchlight( Mech *owner, int subsystem_ID, SubsystemResource *subsystem_resource, SharedData &shared_data = DefaultData ); // @004b84dc ~Searchlight(); // @004b8568 static int CreateStreamedSubsystem( // @004b85a8 NotationFile *model_file, const char *model_name, const char *subsystem_name, SubsystemResource *subsystem_resource, NotationFile *subsystem_file, const ResourceDirectories *directories, int passes = 1 ); }; #endif