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>
This commit is contained in:
@@ -1,49 +1,228 @@
|
||||
//===========================================================================//
|
||||
// Project: BattleTech //
|
||||
//---------------------------------------------------------------------------//
|
||||
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
||||
// All Rights reserved worldwide //
|
||||
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
||||
//===========================================================================//
|
||||
|
||||
#include <bt.hpp>
|
||||
#pragma hdrstop
|
||||
|
||||
#if !defined(BTDIRECT_HPP)
|
||||
# include <btdirect.hpp>
|
||||
#endif
|
||||
|
||||
Derivation
|
||||
BTCameraDirector::ClassDerivations(
|
||||
CameraDirector::ClassDerivations,
|
||||
"BTCameraDirector"
|
||||
);
|
||||
|
||||
BTCameraDirector::SharedData
|
||||
BTCameraDirector::DefaultData(
|
||||
BTCameraDirector::ClassDerivations,
|
||||
CameraDirector::MessageHandlers,
|
||||
CameraDirector::AttributeIndex,
|
||||
1,
|
||||
(Entity::MakeHandler)BTCameraDirector::Make
|
||||
);
|
||||
|
||||
BTCameraDirector::BTCameraDirector(
|
||||
MakeMessage *creation_message,
|
||||
SharedData &shared_data
|
||||
):
|
||||
CameraDirector(creation_message, shared_data)
|
||||
{
|
||||
Fail("BTCameraDirector -- btdirect.cpp not yet reconstructed");
|
||||
}
|
||||
|
||||
BTCameraDirector::~BTCameraDirector()
|
||||
{
|
||||
}
|
||||
|
||||
BTCameraDirector*
|
||||
BTCameraDirector::Make(CameraDirector::MakeMessage *)
|
||||
{
|
||||
Fail("BTCameraDirector::Make -- btdirect.cpp not yet reconstructed");
|
||||
return NULL;
|
||||
}
|
||||
//===========================================================================//
|
||||
// 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();
|
||||
}
|
||||
|
||||
@@ -796,6 +796,20 @@ Mech::~Mech()
|
||||
{
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// InDeathTransition -- binary @0049fb54 (32 bytes): simulation state 2 or 9.
|
||||
//#############################################################################
|
||||
//
|
||||
Logical
|
||||
Mech::InDeathTransition(Entity *vehicle)
|
||||
{
|
||||
Check(vehicle);
|
||||
return
|
||||
vehicle->GetSimulationState() == 2 ||
|
||||
vehicle->GetSimulationState() == 9;
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
// TakeDamageMessageHandler -- the Mech override of the Entity damage entry
|
||||
|
||||
@@ -597,6 +597,19 @@
|
||||
Subsystem*
|
||||
GetSensorSubsystem() { Check(this); return sensorSubsystem; }
|
||||
|
||||
//
|
||||
// The death-transition test (binary @0049fb54, mech.cpp): True while
|
||||
// the simulation state is 2 or 9 -- the two death-transition states
|
||||
// in the MovementMode ledger. Multiple families key off it: update
|
||||
// records strip their motion bits while it holds (@004a4c54), motion
|
||||
// stops integrating (mech4 dead-reckon head), and the spectator
|
||||
// camera director cuts AWAY from a mech the moment it starts dying
|
||||
// (BeABTDirector @004c1230). Takes the ENTITY view because callers
|
||||
// hold vehicles as Entity* (the state field is the Simulation base's).
|
||||
//
|
||||
static Logical
|
||||
InDeathTransition(Entity *vehicle);
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Locomotion parameters (read by MechControlsMapper::InterpretControls to
|
||||
// shape the demands, and by the drive in Simulate). reverseStrideLength is
|
||||
|
||||
Reference in New Issue
Block a user