//===========================================================================// // File: btstubs.cpp // // Project: BattleTech port (WinTesla / btl4) // //---------------------------------------------------------------------------// // BRING-UP STUBS + engine data-global definitions needed to LINK btl4.exe. // // // // Every definition in this file is a flagged first-link placeholder. None // // of these carry the real shipped behaviour yet -- they exist only so the // // executable links and can be booted. Each must be replaced with the real // // body recovered from BTL4OPT.EXE (the binary oracle) / the RP analogue. // // // // == RUNTIME BRING-UP WORKLIST (replace these) == // // Mech::GetMissionReviewMode / IsAirborne / SetTargetRange / // // SetMappingSubsystem / RaiseStatusAlarm // // Mech__DamageZone::LoadCriticalSubsystems // // MechSubsystem::TakeDamage / OnAlarmChanged // // Generator::ForceShortRecovery // // Is_Destroyed / ToggleVoiceAssist / Set_Alarm_Level / // // Notify_Objective_Reached // // data: FrameTimeScale, StatusMessagePool, BTPlayerVTable, // // BTStatusMessageVTable, allPresets // //===========================================================================// #include // Mech, MechSubsystem, BTPlayer, Damage, Scalar, ... #pragma hdrstop #if !defined(POWERSUB_HPP) # include // Generator #endif #if !defined(MECHDMG_HPP) # include // Mech__DamageZone (full definition) #endif #include // PRESETINFO + extern allPresets[2][100] #include // MemoryBlock #if !defined(BTL4VID_HPP) # include // BT*Renderable hierarchy + dpl2d_* prototypes #endif //===========================================================================// // Engine data globals (declared extern by the engine/game but defined in a // .cpp that is not part of munga_engine.lib). //===========================================================================// // TODO(bring-up): per-frame time scale; real value is set by the sim clock. // Default 1.0 so the mech-age divide in mech.cpp does not divide by zero. Scalar FrameTimeScale = 1.0f; // TODO(bring-up): audio preset bank table (real data lives in WTPresets/L4AUDLVL). // Zero-initialised -> PRESET_isImplemented() returns false (no presets) at boot. PRESETINFO allPresets[2][100] = {}; //===========================================================================// // BattleTech globals (status-message pool + hand-rolled vtable pointers the // recovered ctors splice in). Real homes were btplayer.cpp .data. //===========================================================================// // TODO(bring-up): BT StatusMessage MemoryBlock pool (@00512f6c). Must be a real // MemoryBlock sized for StatusMessage before status messages are allocated. MemoryBlock *StatusMessagePool = 0; // TODO(bring-up): hand-built vtables the recovered BTPlayer/StatusMessage ctors // patch into the object's first word (PTR_FUN_00513300 / PTR_LAB_00513344). // Null for now -- any virtual call through these will fault until reconstructed. void *BTPlayerVTable = 0; void *BTStatusMessageVTable = 0; //===========================================================================// // Free-function stubs (declared extern at the call sites). //===========================================================================// // TODO(bring-up): true when the entity handle refers to a destroyed entity. Logical Is_Destroyed(int /*entity_handle*/) { return False; } // TODO(bring-up): toggle the pilot voice-assist subsystem. void ToggleVoiceAssist(int /*voice_assist_subsystem*/) { } // TODO(bring-up): raise/clear a cockpit alarm indicator to the given level. void Set_Alarm_Level(void * /*alarm_indicator*/, int /*level*/) { } // TODO(bring-up): notify mission scoring that an objective subsystem was reached. void Notify_Objective_Reached(int * /*objective_subsystem*/, Mech * /*mech*/) { } //===========================================================================// // Mech method stubs. //===========================================================================// // TODO(bring-up): reads mech+0x414 (mission-review playback flag). int Mech::GetMissionReviewMode() { return 0; } // TODO(bring-up): true while the mech is off the ground (jump-jet / fall state). int Mech::IsAirborne() { return 0; } // FUN_0049fb54 -- true when the mech is destroyed/shut down (masks control input // and freezes gait). For the drivable bring-up the mech is always live. // TODO(bring-up): derive from the status/master alarm once damage is wired. // Mech::IsDisabled -- @0049fb54. Reconstructed: the mech is "disabled" (its action // requests are masked, gait frozen) when its movementMode (mech+0x40, the gait/death // selector) is in a death state -- 2 or 9. movementMode is driven to those values by // the gait/death-transition code (mech2 SetLegAnimation/SetBodyAnimation family) when a // vital zone is destroyed; that transition path is part of the locomotion/Simulate // reconstruction (bypassed in the bring-up drive override), so this reads false until // that lands. Faithful to the binary regardless. Logical Mech::IsDisabled() { return (movementMode == 2 || movementMode == 9) ? True : False; } // mech+0x404 = the radar display scale. Both the radar map's currentScale and // the overlay range readout bind Mech/RadarRange -> radarRange. The controls // mapper slews targetRangeExponent and calls SetTargetRange(250*2^exp) every // frame (btl4mppr.cpp:407) off the zoom buttons, so writing the member makes the // radar map zoom + the range numeric track the zoom (default exp 2.0 -> 1000, // matching the historic frozen value). Faithful body (the binary writes [this+0x404]). void Mech::SetTargetRange(Scalar range) { radarRange = range; } // Install the cockpit-mapping subsystem into the Mech's reserved ControlsMapper // roster slot (index 0), mirroring VTV::SetMappingSubsystem (RP/VTV.cpp:394) and // VTV::ControlsMapperSubsystem==0. The streamed control-mapping resource binds // its DirectMappings to subsystemID 0, so the mapper MUST live there for // Entity::GetSimulation(0) to resolve it. (Previously a no-op -> slot 0 stayed // NULL -> LBE4ControlsManager::CreateStreamedMappings dereferenced a NULL // subsystem.) controlsMapper mirrors the same pointer for the gameplay path. void Mech::SetMappingSubsystem(Subsystem *subsystem) { Check(this); Check_Pointer(subsystemArray); if (subsystemArray[0] != 0) { // re-spawn: drop the previous mapper (NULL on first spawn). Unregister_Object(subsystemArray[0]); delete subsystemArray[0]; } subsystemArray[0] = subsystem; controlsMapper = (MechControlsMapper *)subsystem; } // TODO(bring-up): raise a mech-level status alarm of the given id. void Mech::RaiseStatusAlarm(int /*alarm_id*/) { } // Mech::Reset (@0049fb74) -- place the mech at a (re)spawn origin. // The shipped Reset additionally zeroes ~50 heat/damage/motion state fields and // resets every subsystem in the roster (slot vtable+0x28 with the reset mode); // that full sweep is deferred (TODO(bring-up)). For first spawn a freshly // constructed mech is already clean, so positioning it + refreshing the world // transform + forcing an update is enough to get it placed and rendering. void Mech::Reset(const Origin &origin, int /*mode*/) { Check(this); localOrigin = origin; // this+0x100 (FUN_0040a938 copy) localToWorld = localOrigin; // this+0xd0 (FUN_0040ab44 rebuild) ForceUpdate(); // FUN_004a4c54 family -- push to renderer/replicants // BRING-UP (Tier-2 locomotion): get the player mech onto the per-frame // simulation path so it can walk. Two engine gates block a freshly spawned // master entity from being executed: // (1) Entity::Execute only calls PerformAndWatch when the app is in // RunningMission/EndingMission OR the entity IsPreRunnable() // (ENTITY.cpp:~549) -> set the pre-run flag. // (2) UpdateManager::Execute skips a master unless IsInteresting() // (interestCount!=0) && IsNonReplicantExecutable() (UPDATE.cpp:148) // -> force it interesting so the update manager ticks it. // Without these the mech renders but is never simulated (static // localToWorld). See Mech::PerformAndWatch (mech4.cpp). SetPreRunFlag(); if (interestCount == 0) interestCount = 1; // Entity::interestCount (public) DEBUG_STREAM << "[drive] Mech::Reset spawn at (" << localOrigin.linearPosition.x << ", " << localOrigin.linearPosition.y << ", " << localOrigin.linearPosition.z << ") -- prerun+interest forced\n" << std::flush; Check_Fpu(); } // (Mech__DamageZone::LoadCriticalSubsystems was a no-op stub here; it is now the // real type-0x1e damage-state descriptor-table loader in mechdmg.cpp.) //===========================================================================// // MechSubsystem method stubs. //===========================================================================// // TODO(bring-up): apply damage to a generic mech subsystem (virtual override). void MechSubsystem::TakeDamage(Damage & /*damage*/) { } // TODO(bring-up): react to a change in this subsystem's alarm level. void MechSubsystem::OnAlarmChanged() { } //===========================================================================// // Generator method stub. //===========================================================================// // TODO(bring-up): force the generator into the short-event fast-recovery path. void Generator::ForceShortRecovery() { } //===========================================================================// // BT subsystem<->Mech bridge accessors (declared extern in powersub.hpp). //---------------------------------------------------------------------------// // INTEGRATION NOTE (Task 2): the shipped binary reads these off fixed Mech byte // offsets (msg-mgr @+0x190, bus-live @*(+0x190)+0x274, myomers @+0x374). The // reconstructed Mech is NOT byte-exact (named members + a Wword scratch bank), // so a raw `*(owner+0x190)` would read an unrelated member and, when chased one // level further for the bus flag, dereference garbage. These are therefore // implemented against intent rather than raw offsets, and kept deref-safe: // * BT_IsBusLive -> True: the running player mech is a powered master; this // un-gates the power-dependent subsystem paths (PoweredSubsystem/Generator) // so they compute when those real classes are wired (today their consumers // are RECON_SUBSYS stubs -- see RECONCILE.md -- so this is latent but safe). // * BT_GetMessageManager -> 0: the SubsystemMessageManager IS in the roster // (slot "MessageManager") but cannot be located here without the byte-exact // layout; returning a bogus non-null would be WORSE (callers deref it). Left // 0 (callers null-guard) until the roster lookup / real layout is restored. // * BT_ClearMyomers -> no-op: no myomer back-pointer member exists at a known // offset in the reconstructed Mech to clear. //===========================================================================// // Real impl reads *(owner+0x190). No byte-exact +0x190 in the reconstructed // Mech; callers null-guard, so 0 is the safe value (see note above). SubsystemMessageManager *BT_GetMessageManager(Mech * /*owner*/) { return 0; } // The running player mech is a live, powered master -> its electrical bus is up. // (Real impl read *(*(owner+0x190)+0x274); see layout note above.) Logical BT_IsBusLive(Mech * /*owner*/) { return True; } // No known-offset myomer back-pointer in the reconstructed Mech to clear. void BT_ClearMyomers(Mech * /*owner*/) { } // BT_GetSegmentFlags -- the per-subsystem segment-flag accessor the HUD (hud.cpp) // and the segment-flag-gated subsystem ctors call. It was DECLARED extern but // never DEFINED, so it only linked under /FORCE (an unresolved external whose // return was undefined if ever called). The oracle-verified authoritative source // for the master/copy + master-bit gate is the OWNER's simulationFlags // (owner+0x28): (flags & 0xC)==0 (master, not a replicant copy) && (flags & 0x100) // (master bit). Defining it here resolves the dangling symbol and feeds the gate // the same authoritative value the reconciled subsystem gates now read directly. LWord BT_GetSegmentFlags(Mech *owner) { return owner ? owner->simulationFlags : 0; } //===========================================================================// // Legacy libDPL material-name callback. //---------------------------------------------------------------------------// // The dpl2d_ 2D display-list API (NewDisplayList/Begin/SetColor/Circle/ // PushMatrix/PopMatrix/MoveTo/End/Compile) was MOVED OUT of this file and // reimplemented over Direct3D 9 in decomp/reconstructed/dpl2d.cpp (the HUD/ // reticle 2D layer). Only the 3D material-name-substitution hook -- which is // part of the world renderer, not the 2D HUD -- remains stubbed here. // TODO(bring-up): wire the material substitution table (FUN_00459eb8). //===========================================================================// void dpl_SetMaterialNameCallback(char *(*)(char *)) {} //===========================================================================// // BTL4VideoRenderer helper methods whose bodies were not reconstructed. //---------------------------------------------------------------------------// // TODO(bring-up): real bodies @FUN_0045a724 / 00498448 / 0045a994 / 00489cec // and LoadMissionImplementation drive the D3D scene/object pipeline. //===========================================================================// Scene *BTL4VideoRenderer::GetScene() { return 0; } d3d_OBJECT *BTL4VideoRenderer::LoadObject(const char * /*name*/) { return 0; } void BTL4VideoRenderer::AttachToEyeDCS(dpl_DCS *, LinearMatrix &) {} void BTL4VideoRenderer::AddDynamicRenderable(VideoComponent *, Entity *) {} void BTL4VideoRenderer::LoadMissionImplementation(Mission *mission) { // Load the real BattleTech renderer environment for this mission: paths, // clip range, projection, fog, ambient + directional lights, cached shapes, // effects -- driven by BTDPL.INI (L4DPLCFG, set in btl4main.cpp). This is // what RP does (RPL4VideoRenderer::LoadMissionImplementation -> base), and is // what gives the cavern world its environment. DPLReadINIPage builds a valid // RH projection from the INI viewangle/clip, so the mech view is preserved. DPLRenderer::LoadMissionImplementation(mission); // Safety net (mProjectionMatrix is private to DPLRenderer, so we cannot // inspect it here): unconditionally re-assert a known-good RH projection and // disable fog so the freshly loaded mech body + cavern geometry are not // clipped or fogged to the background. DPLReadINIPage already set the // background colour / paths / lights from BTDPL.INI; this only guarantees the // view stays valid. TODO(bring-up): once the env-driven projection/fog are // confirmed good, drop this and honour the INI fog/clip directly. EnsureValidProjection(); } //===========================================================================// // BattleTech renderable hierarchy (the unreconstructed "BT renderables" module). //---------------------------------------------------------------------------// // TODO(bring-up): these ctors (FUN_0045xxxx) built the D3D DCS/instance graph // for each mech part/effect. Stubbed to chain to BTRenderableBase so the object // is a valid (empty) VideoComponent; nothing is drawn until reconstructed. //===========================================================================// BTWorldContainerRenderable::BTWorldContainerRenderable( Entity *entity, int, int, Scene *) : BTRenderableBase(entity) {} BTRootRenderable::BTRootRenderable( Entity *entity, int, d3d_OBJECT *, Scene *, int, uint32) : BTRenderableBase(entity) {} BTDCSObjectRenderable::BTDCSObjectRenderable( Entity *entity, Scene *, d3d_OBJECT *, int, uint32, LinearMatrix *, dpl_DCS *) : BTRenderableBase(entity) {} BTHingeRenderable::BTHingeRenderable( Entity *entity, int, d3d_OBJECT *, Scene *, int, uint32, dpl_DCS *, LinearMatrix *, const Hinge *) : BTRenderableBase(entity) {} BTBallJointRenderable::BTBallJointRenderable( Entity *entity, int, d3d_OBJECT *, Scene *, int, uint32, dpl_DCS *, LinearMatrix *, const EulerAngles *) : BTRenderableBase(entity) {} BTBallTranslateJointRenderable::BTBallTranslateJointRenderable( Entity *entity, int, d3d_OBJECT *, Scene *, int, uint32, dpl_DCS *, LinearMatrix *, const EulerAngles *, const Point3D *) : BTRenderableBase(entity) {} BTEyeRenderable::BTEyeRenderable( Entity *entity, Scene *, LinearMatrix *, dpl_DCS *, dpl_VIEW *, EulerAngles *) : BTRenderableBase(entity) {} BTDeathEffectRenderable::BTDeathEffectRenderable( Entity *entity, int, dpl_VIEW *, Scene *, dpl_DCS *, StateIndicator *, int) : BTRenderableBase(entity) {} BTMarkerWatcherRenderable::BTMarkerWatcherRenderable( Entity *entity, int, dpl_VIEW *, dpl_DCS *) : BTRenderableBase(entity) {} // BTTranslocationRenderable (the "blue warp" translocation sphere) is now a real // reconstruction in btl4vid.cpp (task #52) -- no longer a stub here. BTPOVStartEndRenderable::BTPOVStartEndRenderable( Entity *entity, int, dpl_VIEW *, dpl_ZONE *, dpl_ZONE *, StateIndicator *, float, float, float, float, float, int, int) : BTRenderableBase(entity) {} BTTracerEffectRenderable::BTTracerEffectRenderable( Entity *entity, int, void *, int, dpl_ZONE *, dpl_DCS *, LinearMatrix *) : BTRenderableBase(entity) {} BTEmitterBeamRenderable::BTEmitterBeamRenderable( Entity *entity, int, d3d_OBJECT *, Scene *, int, uint32, dpl_DCS *, LinearMatrix *, void *, int, void *, void *) : BTRenderableBase(entity) {} BTPPCBeamRenderable::BTPPCBeamRenderable( Entity *entity, int, d3d_OBJECT *, Scene *, int, uint32, dpl_DCS *, LinearMatrix *, void *, int, void *, void *, float) : BTRenderableBase(entity) {} BTLightConnection::BTLightConnection( Entity *entity, int, dpl_INSTANCE *, int, int *) : BTRenderableBase(entity) {} // (BTReticleRenderable ctor/dtor/Draw now live in btl4vid.cpp -- the real // @004cc40c reconstruction replaced the old bring-up stubs here.) //===========================================================================// // VideoComponent (legacy renderable base) -- declared in L4VIDRND.h but NOT // implemented anywhere in the WinTesla engine (superseded by VideoRenderable). // BTRenderableBase still derives from it, so provide first-link stub bodies. //---------------------------------------------------------------------------// // TODO(bring-up): either reimplement VideoComponent over the new D3D renderable // hierarchy or reparent BTRenderableBase onto VideoRenderable. //===========================================================================// VideoComponent::VideoComponent(Entity * /*entity*/, VideoExecutionType /*type*/) : HierarchicalDrawComponent((RegisteredClass::ClassID)0) , videoComponentSocket((Node *)0) // TODO(bring-up): owner node when reimplemented { } VideoComponent::~VideoComponent() { } void VideoComponent::Add(VideoComponent * /*component_to_add*/) { } void VideoComponent::Connect(VideoComponent * /*component_to_connect*/) { } void VideoComponent::ReceiveControl(VideoControlID /*control_ID*/, Scalar /*control_value*/) { } void VideoComponent::Execute() { }