Files
TeslaRel410/restoration/source410/BT/BTDIRECT.CPP
T
CydandClaude Fable 5 454102ef35 BT410 5.3.116: the spectator camera direats -- BTCameraDirector reconstructed from a disassembly gap, and the donor's inverted pick caught
The two Performance bodies fell in a Ghidra gap (@004c11b3..@004c1408), so
BeABTDirector came straight off ndisasm of the shipped CODE section. The
verdict corrects the BT411 donor twice more: FUN_0049fb54 is not
'IsLiveMech' -- it is the mech DEATH-TRANSITION test (simulation state 2 or
9; its other callers strip update-record motion bits and halt motion
integration while it holds, and our own MovementMode ledger already said
so) -- and the donor's pick loop breaks on a DYING vehicle where the
binary's jz breaks on a LIVE one. The true 1995 behavior: the camera
lingers on its mech while the 30s timer runs, cuts away THE MOMENT the
followed mech starts dying, picks the first of four ranked players with a
live vehicle, and refreshes the lock during mission end. The 'BT-specific
0x15/0x34 track message' the port invented an engine substitute for IS the
engine's CameraShip::DirectionMessage -- ID 0x15 == Entity::NextMessageID,
0x34 bytes, byte-for-byte; the RP engine's own BeADirector source
(DIRECTOR.CPP survives) is the literal structural template BT overrode
(rank-0-only, 10s, no offset -> four ranks, 30s, (0,5,0) eye).

Mech::InDeathTransition lands in mech.cpp per the census (@0049fb54, 32
bytes). The ctor's replicant gate uses the binary's own flag test --
entity-side simulationFlags arrive live via the MakeMessage, unlike the
subsystem masters still waiting on the stream builders.

Stubs: 11 across 9 files (the 8 authoring CSS fns + the L4NET trio).
Pod-role soak clean; the director's live test rides the operator's
camera-egg / mission-review session.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 08:49:13 -05:00

229 lines
7.0 KiB
C++

//===========================================================================//
// 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 <bt.hpp>
#pragma hdrstop
#if !defined(BTDIRECT_HPP)
# include <btdirect.hpp>
#endif
#if !defined(CAMSHIP_HPP)
# include <camship.hpp> // CameraShip::DirectionMessage (the track)
#endif
#if !defined(APP_HPP)
# include <app.hpp> // 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();
}