//===========================================================================//
// 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 <bt.hpp>
#pragma hdrstop

#if !defined(BTDIRECT_HPP)
#	include <btdirect.hpp>
#endif
#if !defined(BTPLAYER_HPP)
#	include <btplayer.hpp>
#endif
#if !defined(DIRECTOR_HPP)
#	include <director.hpp>
#endif
#if !defined(APP_HPP)
#	include <app.hpp>
#endif
#if !defined(NTTMGR_HPP)
#	include <nttmgr.hpp>
#endif
#if !defined(CHAIN_HPP)
#	include <chain.hpp>
#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<Node*> 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();
}
