//===========================================================================// // Project: BattleTech // //---------------------------------------------------------------------------// // Copyright (C) 1995, Virtual World Entertainment, Inc. // // All Rights reserved worldwide // // This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // //===========================================================================// // // BTCameraDirector -- the spectator/live-cam director (camera-role eggs). // The surviving BTDIRECT.HPP is ground truth for the interface. The two // Performance bodies fell in a Ghidra gap (@004c11b3..@004c1408) and were // recovered by direct disassembly of the shipped exe (ndisasm of the CODE // section); the construction family comes from the decomp (part_013.c). // // ctor @004c1408 (chains CameraDirector @0042eec8) // Make @004c1454 (new 0x238 + ctor) // ~BTCameraDirector @004c1484 // TestInstance @004c14b0 // CreateBTCameraShip @004c13d4 initial Performance // BeABTDirector @004c1230 steady Performance // // The engine base (RP DIRECTOR.CPP survives) is the literal template: // CameraDirector::BeADirector = PlayerSimulation + UpdateHUD + a 10s // rotation onto FindPlayerByRank(0)'s vehicle. The BT override widens the // scan to the first four ranks, stretches the rotation to 30s, offsets the // camera eye (0, 5, 0), refreshes the lock while the mission ends -- and // CUTS AWAY from the followed mech the moment it enters the death // transition (Mech::InDeathTransition, binary @0049fb54). NOTE: the BT411 // donor's pick loop is inverted here (it breaks on a DYING vehicle); the // disassembly's pick breaks on jz -- the first LIVE vehicle wins. // // The "BT-specific" track message turns out to be the engine's own // CameraShip::DirectionMessage (ID 0x15 == Entity::NextMessageID, 0x34 // bytes) -- no BT message class exists. // #include #pragma hdrstop #if !defined(BTDIRECT_HPP) # include #endif #if !defined(CAMSHIP_HPP) # include // CameraShip::DirectionMessage (the track) #endif #if !defined(APP_HPP) # include // application state gates #endif #define CAMERA_SWITCH_INTERVAL 30.0f // 0x41f00000 -- BT stretches the base 10s #define CAMERA_EYE_HEIGHT 5.0f // 0x40a00000 -- fixed eye offset Derivation BTCameraDirector::ClassDerivations( CameraDirector::ClassDerivations, "BTCameraDirector" ); BTCameraDirector::SharedData BTCameraDirector::DefaultData( BTCameraDirector::ClassDerivations, CameraDirector::MessageHandlers, CameraDirector::AttributeIndex, 1, (Entity::MakeHandler)BTCameraDirector::Make ); // //############################################################################# // Construction / Destruction // // @004c1408 -- chains the engine CameraDirector ctor; a non-replicant copy // gets CreateBTCameraShip as its initial Performance. The replicant test is // the entity flag pair (simulationFlags & 0xC) == 4 -- these flags arrive // via the MakeMessage's instance flags, so the binary's own gate works here // (unlike the subsystem masters, whose 0xC/0x100 seeding still waits on the // mech stream builders). //############################################################################# // BTCameraDirector::BTCameraDirector( MakeMessage *creation_message, SharedData &shared_data ): CameraDirector(creation_message, shared_data) { Check(this); if ((simulationFlags & 0xC) != 4) { SetPerformance(&BTCameraDirector::CreateBTCameraShip); } Check_Fpu(); } BTCameraDirector::~BTCameraDirector() { } // // @004c1454 -- the registered MakeHandler: allocate (0x238) and construct. // BTCameraDirector* BTCameraDirector::Make(CameraDirector::MakeMessage *creation_message) { BTCameraDirector *director = new BTCameraDirector(creation_message); Register_Object(director); return director; } // // @004c14b0 // Logical BTCameraDirector::TestInstance() const { return IsDerivedFrom(ClassDerivations); } // //############################################################################# // CreateBTCameraShip -- @004c13d4, the initial Performance. One-shot: let // the engine base spawn the spectator camera ship and wire its viewport, // then hand the per-frame work to the steady director. //############################################################################# // void BTCameraDirector::CreateBTCameraShip(Scalar time_slice) { Check(this); CameraDirector::CreateCameraShip(time_slice); SetPerformance(&BTCameraDirector::BeABTDirector); } // //############################################################################# // BeABTDirector -- @004c1230, the steady Performance (disassembly-verified). // // Per frame: the inherited player-side update and viewpoint/HUD work, then // the pick. While the rotation timer runs during a live mission the camera // stays put -- UNLESS the followed mech has entered its death transition, // in which case the re-pick happens immediately (the cut away from the // kill). On a re-pick the first of the four ranked players whose vehicle // is alive wins; a switch restarts the 30s timer and sends the camera ship // a DirectionMessage with the (0, 5, 0) eye offset. While the mission is // ending the lock refreshes even if the target is unchanged. //############################################################################# // void BTCameraDirector::BeABTDirector(Scalar time_slice) { Check(this); Player::PlayerSimulation(time_slice); UpdateHUD(time_slice); Logical mission_ending = False; if ( timeLeftOnPlayer > 0.0f && application->GetApplicationState() == Application::RunningMission ) { if (!Mech::InDeathTransition(GetGoalEntity())) { timeLeftOnPlayer -= time_slice; return; } // the followed mech is dying -- fall through and cut now } else if (application->GetApplicationState() == Application::EndingMission) { mission_ending = True; } // // The first ranked player (0..3) with a live vehicle wins the camera. // Player *player = NULL; Entity *vehicle = NULL; int rank; for (rank = 0; rank < 4; rank++) { player = FindPlayerByRank(rank); if (player != NULL) { vehicle = player->GetPlayerVehicle(); if (vehicle != NULL && !Mech::InDeathTransition(vehicle)) { break; } } } if (player == NULL || vehicle == NULL) { return; } if (vehicle == GetGoalEntity() && !mission_ending) { return; } SetGoalEntity(vehicle); timeLeftOnPlayer = CAMERA_SWITCH_INTERVAL; CameraShip::DirectionMessage track( CameraShip::DirectionMessageID, sizeof(CameraShip::DirectionMessage), GetGoalEntity()->GetEntityID(), CAMERA_SWITCH_INTERVAL, Point3D(0.0f, CAMERA_EYE_HEIGHT, 0.0f) ); Check(cameraShip); cameraShip->Dispatch(&track); Check_Fpu(); }