//===========================================================================// // File: btdirect.cpp // // Project: BattleTech Brick: Camera / Spectator // // Contents: BattleTech camera director -- decides which player the spectator // // camera ships follow (BT analog of RP's RPCameraDirector). // //---------------------------------------------------------------------------// // Date Who Modification // // -------- --- ---------------------------------------------------------- // // 01/15/95 JM Initial coding. (date from the surviving BTDIRECT.HPP) // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved // // PROPRIETARY AND CONFIDENTIAL // //===========================================================================// // // RECONSTRUCTED from the shipped binary (BTL4OPT.EXE). The class interface // is ground truth (surviving BTDIRECT.HPP). Bodies follow the Ghidra // pseudo-C for the bt camera cluster plus a direct objdump disassembly of the // two performance methods (they fell in a gap Ghidra never decompiled, // @004c11b3..@004c1408). Each method cites its originating @ADDR. // // CLASS MAP // BTCameraDirector : public CameraDirector vtable @005134c4 // ctor @004c1408 (chains CameraDirector @0042eec8) // Make @004c1454 (operator new 0x238 + ctor) // ~BTCameraDirector @004c1484 (slot0; chains base dtor @0042efa4) // TestInstance @004c14b0 (IsDerivedFrom ClassDerivations @00513414) // CreateBTCameraShip @004c13d4 (initial Performance; chains base // CameraDirector::CreateCameraShip @0042ed68) // BeABTDirector @004c1230 (steady Performance; BT override of the // base CameraDirector::BeADirector @0042ec3a) // // Only slot0 of the vtable is a BT override; slots 1..16 are inherited // CameraDirector/Director/Simulation/Receiver implementations (41xxxx/42xxxx). // // HEX FLOAT / STATE CONSTANTS // 0xbf800000 = -1.0f (base ctor seed for switchTimer @0x218) // 0x41200000 = 10.0f (base BeADirector rotation interval / base member 0x230) // 0x41700000 = 15.0f (base member 0x234) // 0x40a00000 = 5.0f (BT eye offset / view height in the track message) // 0x41f00000 = 30.0f (BT rotation interval -- BTCameraDirector overrides 10->30) // _DAT_004c13d0 = 0.0f (switchTimer comparison) // application state (app+0x88): 5 = MissionRunning, 6 = EndingMission, // 7 = (mission over / scoreboard) // // INHERITED CameraDirector BASE LAYOUT (DIRECTOR.HPP; base vtable @004e6b4c): // this+0x1c activePerformance (Simulation::Performance ptr-to-member, 12B) // this+0x28 simulationFlags ((flags & 0xc)==4 => replicant) // this+0x1fc cameraShip receiver used during ship creation (base idx 0x7f) // this+0x20c followedVehicle (embedded target Connection, base idx 0x83) // this+0x218 switchTimer (Scalar, base idx 0x86) // this+0x21c cameraShip* (Receiver*, base idx 0x87; Dispatch target) // // ENGINE HELPERS referenced (names inferred; same pool as btplayer.cpp): // FUN_0042eec8 CameraDirector::CameraDirector(make, shared) [base ctor] // FUN_0042efa4 CameraDirector::~CameraDirector [base dtor] // FUN_0042e328 Player/Entity base ctor (chained by 0042eec8) // FUN_0042e100 CameraDirector::DirectorSimulation(ts) [base, vtable slot14] // FUN_0042eb38 CameraDirector::UpdateViewpoint(ts) [base viewpoint cut FSM] // FUN_0042ed68 CameraDirector::CreateCameraShip(ts) [base ship spawn] // FUN_0042ec3a CameraDirector::BeADirector(ts) [base steady perf] // FUN_0042efe4 CameraDirector::GetPlayerByIndex(i) -- walks the "Players" // EntityGroup (@004e6b3c) returning the player whose // displayIndex(+0x1cc)==i. // FUN_0049fb54 Mech-validity test on a candidate vehicle (alive / right class) // FUN_00417ab4 Connection::GetTarget(&conn) -- resolve followedVehicle // FUN_00417a5c Connection::Reset(&conn) // FUN_00420ea4 EntityID::EntityID(const char*) (empty string @00522524) // FUN_0041a1a4 IsDerivedFrom(classDerivations) // FUN_00402298 operator new (raw alloc) // DAT_004efc94 the application singleton (app+0x88 state, app+0x24 EntityMgr) // // d:\tesla_bt\bt\btdirect.cpp is the presumed original path (not asserted by // any string in this TU -- BTCameraDirector raises no Verify()s). // #include #pragma hdrstop #if !defined(BTDIRECT_HPP) # include #endif #if !defined(BTPLAYER_HPP) # include #endif #if !defined(DIRECTOR_HPP) # include #endif #if !defined(APP_HPP) # include #endif #if !defined(NTTMGR_HPP) # include #endif #if !defined(CHAIN_HPP) # include #endif #define CAMERA_SWITCH_INTERVAL 30.0f // 0x41f00000 this+0x218 reset value #define CAMERA_EYE_HEIGHT 5.0f // 0x40a00000 fixed eye offset in track msg // // FUN_0049fb54 -- BT mech-validity test on a candidate vehicle (alive / right // class). This is a BT-side helper (lives outside this TU in the shipped // binary); declared here so BeABTDirector can call it. Typed against the // engine's Entity* (goalEntity / GetPlayerVehicle both deal in Entity*), // which is what the followed "mech" is stored as. // static Logical IsLiveMech(Entity *vehicle); //############################################################################# //########################### BTCameraDirector ############################ //############################################################################# //############################################################################# // Shared Data Support // // ClassDerivations @00513414, name string "BTCameraDirector" @005134b1. // DefaultData @00513444. BTCameraDirector registers no message handlers of // its own (the vtable's HandleMessage slot is inherited), so the shared-data // record reuses the base CameraDirector handler set. (Best-effort: the static // initializer lives in the CRT global-ctor array and was not decompiled; the // argument list is reconstructed by analogy to BTPlayer::DefaultData.) // // (Engine modernization: the shipped code referenced the static member // CameraDirector::ClassDerivations / ::AttributeIndex directly; the WinTesla // engine replaced those with GetClassDerivations()/GetAttributeIndex() // accessors -- mapped through here.) Derivation BTCameraDirector::ClassDerivations( CameraDirector::GetClassDerivations(), "BTCameraDirector" // @005134b1 ); BTCameraDirector::SharedData BTCameraDirector::DefaultData( &BTCameraDirector::ClassDerivations, CameraDirector::MessageHandlers, // no BT-specific handlers CameraDirector::GetAttributeIndex(), CameraDirector::StateCount, (Entity::MakeHandler)BTCameraDirector::Make ); //############################################################################# // Simulation Support // //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // CreateBTCameraShip // // @004c13d4 The director's *initial* Performance (installed by the ctor on a // master/standalone copy). It defers to the engine CameraDirector to spawn // the spectator camera ship and post its setup messages, then hands the // per-frame work off to the steady-state BeABTDirector performance. // // Recovered by direct disassembly (Ghidra left @004c11b3..@004c1408 raw): // call 0042ed68(this,ts) ; CameraDirector::CreateCameraShip // this[0x1c..0x24] = {004c1230,0,0} ; activePerformance = &BeABTDirector // void BTCameraDirector::CreateBTCameraShip(Scalar time_slice) { // // Let the base director create the camera ship and wire up its viewport. // CameraDirector::CreateCameraShip(time_slice); // FUN_0042ed68 // // One-shot: from now on run the steady director loop. // SetPerformance(&BTCameraDirector::BeABTDirector); // this+0x1c = {004c1230,0,0} } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // BeABTDirector // // @004c1230 The steady-state Performance. Once per frame it runs the base // director simulation, then decides which player's vehicle the camera ships // should track. This is the BT override of CameraDirector::BeADirector // (@0042ec3a): the base watches a single player (GetPlayerByIndex(0)) and // rotates on a 10s timer; BattleTech scans the first four player slots and // rotates on a 30s timer. When it changes the followed vehicle it restarts // the timer and sends a "track this transform" message (type 0x15, 0x34 bytes) // to the camera ship. // // Recovered by direct disassembly (gap, see above). // void BTCameraDirector::BeABTDirector(Scalar time_slice) { // // Inherited per-frame director update + the base viewpoint blend/cut state // machine (which advances this+0x224/0x228/0x22c against the 10/15s base // members and may drop the cached owner-id at this+0x220). // // (Engine modernization: the shipped binary called base helpers // DirectorSimulation(@0042e100)+UpdateViewpoint(@0042eb38); the WinTesla // base BeADirector implements the same per-frame work as PlayerSimulation // + UpdateHUD -- mapped through here.) Player::PlayerSimulation(time_slice); // FUN_0042e100 (sim update) UpdateHUD(time_slice); // FUN_0042eb38 (viewpoint/HUD) Logical missionEnding = False; // ebp-0x4 // // While the rotation timer is still running and the mission is live, keep // the cameras on the current vehicle -- unless it has been destroyed, in // which case linger on the wreck while the timer bleeds down to zero (the // top-of-frame test then forces a re-pick on a later frame). // // (switchTimer @0x218 == the engine base member timeLeftOnPlayer; // app state 5 "MissionRunning" == Application::RunningMission.) // if (timeLeftOnPlayer > 0.0f // this+0x218 && application->GetApplicationState() == Application::RunningMission) // app+0x88 == 5 { Entity *current = GetGoalEntity(); // FUN_00417ab4(this+0x20c) if (!IsLiveMech(current)) // FUN_0049fb54 == 0 { timeLeftOnPlayer -= time_slice; // this+0x218 -= ts return; } // current vehicle still alive: fall through and re-evaluate the pick. } else if (application->GetApplicationState() == Application::EndingMission) // app+0x88 == 6 { missionEnding = True; } // // Pick the lowest-indexed player slot (0..3) that currently owns a live // vehicle. // BTPlayer *player = 0; // esi Entity *vehicle = 0; // edi (the picked mech, stored as Entity*) for (int i = 0; i < 4; ++i) // ebp-0x8 { player = (BTPlayer *)GetPlayerByIndex(i); // FUN_0042efe4(i) if (player) { vehicle = player->GetPlayerVehicle(); // player+0x1fc if (IsLiveMech(vehicle)) // FUN_0049fb54 != 0 { break; } } } if (player == 0 || vehicle == 0) { return; } // // If the cameras are already locked on this vehicle, do nothing -- unless // the mission is ending, in which case refresh the lock. // Entity *current = GetGoalEntity(); // FUN_00417ab4(this+0x20c) if (vehicle == current && !missionEnding) { return; } // // Switch the followed vehicle and restart the rotation timer. // (followedVehicle.Reset()+Connect() == the engine SetGoalEntity().) // SetGoalEntity(vehicle); // FUN_00417a5c + Connect(this+0x20c) timeLeftOnPlayer = CAMERA_SWITCH_INTERVAL; // this+0x218 = 30.0f // // Tell the camera ship which target to follow. The shipped binary used a // BT-specific transform-carrying message (type 0x15, 0x34 bytes) with a // fixed eye offset (0, 5.0, 0); the WinTesla engine resolves the followed // entity's transform inside the camera ship, so we send the engine's // CameraShip::DirectionMessage with the followed entity's ID and the BT // eye offset preserved as the focus offset. // Entity *target = GetGoalEntity(); // FUN_00417ab4(this+0x20c) CameraShip::DirectionMessage track( CameraShip::DirectionMessageID, sizeof(CameraShip::DirectionMessage), target->GetEntityID(), // target+0x184 transform owner CAMERA_SWITCH_INTERVAL, // time on camera Point3D(0.0f, CAMERA_EYE_HEIGHT, 0.0f) // (0, 5.0, 0) ); cameraShip->Dispatch(&track); // (*this[0x21c]->vtbl[3])(this[0x21c], &track) } //############################################################################# // Construction and Destruction // //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // BTCameraDirector (constructor) // // @004c1408 Chains the engine CameraDirector ctor, stamps our vtable, and -- // on a master/standalone copy (not a network replicant) -- installs // CreateBTCameraShip as the initial Performance. Replicant copies leave the // base performance in place (they only mirror the chosen viewpoint). // BTCameraDirector::BTCameraDirector( MakeMessage *creation_message, SharedData &shared_data ): CameraDirector(creation_message, shared_data) // FUN_0042eec8 { // (decompiler artifact removed: "*(void**)this = &BTCameraDirectorVTable" // -- the vtable pointer is stamped by the C++ compiler automatically.) if ((simulationFlags & 0xc) != 4) // this+0x28 -- not a replicant { SetPerformance(&BTCameraDirector::CreateBTCameraShip); // this+0x1c = {004c13d4,0,0} } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Make // // @004c1454 Allocate a 0x238-byte BTCameraDirector and construct it with the // default shared data (@00513444). // // (The bt entity factory @004c15f0 also builds one of these directly, without // going through Make: when an incoming creation request names the entity // "camera" (@00513549) it news a 0x238 BTCameraDirector via the ctor @004c1408; // otherwise it news a 0x294 BTPlayer. See btplayer.cpp.) // BTCameraDirector* BTCameraDirector::Make(CameraDirector::MakeMessage *creation_message) { return new BTCameraDirector(creation_message); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~BTCameraDirector // // @004c1484 (scalar-deleting destructor thunk, vtable slot0). Restores our // vtable and chains ~CameraDirector (which tears down the followedVehicle // connection at this+0x20c). BTCameraDirector owns no extra resources. // BTCameraDirector::~BTCameraDirector() { // (decompiler artifact removed: "*(void**)this = &BTCameraDirectorVTable" // -- the compiler restores the vtable pointer during destruction.) // base chain: CameraDirector::~CameraDirector (FUN_0042efa4) } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // TestInstance // // @004c14b0 // Logical BTCameraDirector::TestInstance() const { return IsDerivedFrom(ClassDerivations); // FUN_0041a1a4(**this[3], 0x513414) } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // GetPlayerByIndex // // @0042efe4 Walk the "Players" EntityGroup and return the player occupying // slot `index` (the shipped binary matched displayIndex @0x1cc; the WinTesla // engine exposes the slot as Player::playerBitmapIndex). Mirrors the engine // CameraDirector::FindPlayerByRank iteration idiom. // Player* BTCameraDirector::GetPlayerByIndex(int index) { Check(application); EntityGroup *player_group = application->GetEntityManager()->FindGroup("Players"); if (player_group) { Player *candidate; ChainIteratorOf iterator(player_group->groupMembers); while ((candidate = (Player *)iterator.ReadAndNext()) != NULL) { Check(candidate); if (candidate->playerBitmapIndex == index) { return candidate; } } } return NULL; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // IsLiveMech (FUN_0049fb54) // // Best-effort reconstruction: the candidate is a "live mech" if it exists and // still carries the engine ValidFlag (i.e. has not been torn down / destroyed). // The shipped helper additionally screened on the mech class; that screen is // elided here because the BT Mech class is reconstructed in a separate TU. // static Logical IsLiveMech(Entity *vehicle) { return (vehicle != NULL) && vehicle->IsValid(); }