//============================================================================//
// File:	missile.cpp                                                          //
// Project: BattleTech																			//
// Contents: Guided self-propelled projectile entity (Missile : Projectile)    //
//----------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved  //
//                   	PROPRIETARY AND CONFIDENTIAL                          //
//============================================================================//
//
// RECONSTRUCTED from the shipped binary.  Behaviour follows the Ghidra
// pseudo-C in part_013.c; there is NO surviving missile.cpp -- only MISSILE.TCP
// (Missile::TestClass) and PROJTILE.TCP guide the member/method naming.  Each
// non-trivial method cites the originating @ADDR.  Coverage is partial by
// design (Missile is the lowest-priority of the three classes; it has no HPP).
//
//   confident : ctor @004bf5b4 (subsystem roster build), MoveAndCollide skeleton
//               @004bef78, WriteUpdateRecord @004bef4c
//   best-effort: member names/offsets past the Projectile base, the collision
//               + detonation branch (FUN_0042291c / FUN_004be078)
//   excluded   : the Projectile base ctor @004be1bc and the 0x41xxxx/0x42xxxx
//               Entity engine vtable slots
//
// Decoded MoveAndCollide tuning constants (.rdata @004bf594..@004bf5b0):
//      0x40000000 = 2.0f    0x38d1b717 = 1.0e-4f   0x40800000 = 4.0f
//      0x3dcccccd = 0.1f    0x41200000 = 10.0f     0xbf800000 = -1.0f
//
// Helper-function name mapping:
//      FUN_004be1bc  Projectile base constructor (vtable @00512a5c)
//      FUN_004bec34  Seeker::Seeker            FUN_004be7c4  MissileThruster::MissileThruster
//      FUN_0042291c  world collision query     FUN_004be078  spawn explosion/Damage entity
//      FUN_00421b2c  Entity::IntegrateMotion   FUN_00421bac  Entity::AdvanceModel
//      FUN_0042061c  Entity::Retire            FUN_00402298  operator new
//      FUN_00408644  Vector3D = a - b          FUN_004086ac  scaled add
//      FUN_004dd138  sqrt()                    FUN_0041a1a4  IsDerivedFrom
//

#include <bt.hpp>
#pragma hdrstop

#if !defined(MISSILE_HPP)
#	include <missile.hpp>
#endif
#if !defined(SEEKER_HPP)
#	include <seeker.hpp>
#endif
#if !defined(MISTHRST_HPP)
#	include <misthrst.hpp>
#endif
#if !defined(TESTBT_HPP)
#	include <testbt.hpp>
#endif

//
// MoveAndCollide tuning constants (read-only globals in the decomp).
//
static const Scalar MissileLifeWarn   =  2.0f;	// _DAT_004bf594  age-ratio dead threshold
static const Scalar MissileSteerEps   = 1.0e-4f;// _DAT_004bf598/9c/a0 steering deadband
static const Scalar MissileTurnGain   =  4.0f;	// _DAT_004bf5a4
static const Scalar MissileDriftGain  =  0.1f;	// _DAT_004bf5a8
static const Scalar MissileMinSpeed2  = 10.0f;	// _DAT_004bf5ac/a8 min squared-speed before fizzle
static const Scalar MissileDead       = -1.0f;	// _DAT_004bf5b0

//###########################################################################
//###########################################################################
//					Missile
//###########################################################################
//###########################################################################

//#############################################################################
// Decompiler-artifact stand-ins.
//
static char	DAT_00512d48[0x368] = { 0 };	// default streamed-entity record

//#############################################################################
// Shared Data Support
//
Derivation
	Missile::ClassDerivations(
		&Projectile::ClassDerivations,
		"Missile"
	);

Receiver::MessageHandlerSet		Missile::MessageHandlers;
Simulation::AttributeIndexSet	Missile::AttributeIndex;

Missile::SharedData
	Missile::DefaultData(
		&Missile::ClassDerivations,
		Missile::MessageHandlers,
		Missile::AttributeIndex,
		Missile::StateCount,
		&Missile::Make
	);

Entity *Missile::Make(MakeMessage * /*creation_message*/)
{
	return 0;
}

//#############################################################################
// Allocation
//
// @004bf8bc  Missile::New(spawn_context) -- allocate 0x368 bytes and construct
// with the default streamed-entity record &DAT_00512d48.
//
Missile *Missile::New(int spawn_context)
{
	Missile *missile = (Missile *)::operator new(0x368);		// FUN_00402298
	if (missile != 0)
		missile = new(missile) Missile(spawn_context, DAT_00512d48);	// @004bf5b4
	return missile;
}

//#############################################################################
// Construction
//
// @004bf5b4  Missile::Missile(...)
//
//   Chains the Projectile base ctor (which copies the launch transform,
//   velocity and segment data out of the spawn descriptor), stamps the Missile
//   vtable, resolves the detonation model + lifetime out of the entity model
//   record, then constructs the two hosted subsystems into a freshly allocated
//   2-entry roster:
//        roster[SeekerSubsystem]          = new Seeker("Seeker", target, offset)
//        roster[MissileThrusterSubsystem] = new MissileThruster("MissleThruster")
//
Missile::Missile(int owner, const char *spawn_descriptor)
:
	// Projectile base ctor (Entity transform, velocity, model, segment).
	Projectile(owner, spawn_descriptor)						// @004be1bc
{
	// vtable installed by the compiler (PTR_FUN_00512f2c)

	mass			= 1.0f;									// model mass (avoid /0)
	ageFraction		= 0.0f;									// @0x344 this[0xd1]
	targetConnection	= ResolveTargetConnection(owner);	// @0x34c this[0xd3]
	targetOffset		= LaunchAimOffset(owner);			// @0x350 this[0xd4..0xd6]
	age				= 0.0f;									// @0x35c this[0xd7]
	simulationFlags |= 0x10;								// this[0x0a] |= 0x10 (guided)

	// pull lifetime / thrust / detonation-mode out of the entity model record
	const ModelRecord *model = ResolveModel();				// FUN_00407064 chain
	lifetime		= model->maxTimeOfFlight;				// @0x340 this[0xd0]  (model +0x44)
	thrustScale		= (Scalar)Sqrt(model->thrust / mass);	// @0x348 this[0xd2]  (model +0x48)
	detonationFlag	= model->detonationMode;				// @0x364 this[0xd9]  (model +0x50)

	// allocate the 2-entry subsystem roster
	subsystemCount	= SubsystemCount;						// @0x124 this[0x49] = 2
	subsystems		= (Subsystem **)::operator new(SubsystemCount * sizeof(Subsystem *));	// @0x128 this[0x4a]

	// slot 0: Seeker  (homing guidance) -- "Seeker" @00512e1e
	subsystems[SeekerSubsystem] =
		(Subsystem *)new Seeker((Missile *)this, 0, 0, (Entity *)targetConnection,
			Point3D(targetOffset.x, targetOffset.y, targetOffset.z));	// @004bec34

	// slot 1: MissileThruster  (propulsion) -- "MissleThruster" @00512e25
	subsystems[MissileThrusterSubsystem] =
		(Subsystem *)new MissileThruster((Missile *)this, 1, /*resource*/0);	// @004be7c4

	// Install the guided per-frame integrator as this entity's active Performance.
	// The Projectile base ctor installed the plain Projectile::MoveAndCollide;
	// the Missile overrides it so the flight entity homes + collides each frame.
	// (Without this re-install the missile would coast on the unguided base pass.)
	SetPerformance(&Missile::MoveAndCollide);				// @004bef78

	// hand ourselves to the spawning launcher's projectile slot
	// (FUN_0049fb54 chain: launcher->currentProjectile = this)  @004bf5b4 tail
}

//#############################################################################
// Destruction
//
// @004bf890  ~Missile() -- restores the Missile vtable, destroys the hosted
// subsystems via the Projectile base destructor (@004be358).
//
Missile::~Missile()
{
	// vtable reset + base ~Projectile chaining handled by the compiler.
	if (subsystems != 0)
		::operator delete(subsystems);
}

//#############################################################################
// Per-frame integration
//
// @004bef78  Missile::MoveAndCollide(time_slice)
//
//   1. Age the missile (age += time_slice) and update ageFraction.  Past the
//      lifetime / below MissileMinSpeed2 the missile flags itself dead
//      (flags |= 1) and is retired by the entity manager.
//   2. Sample the Seeker lead point and the target's relative geometry; apply
//      a per-axis steering impulse (turn toward the lead, gain MissileTurnGain,
//      deadband MissileSteerEps) into the velocity (this+0x1dc) plus the
//      MissileThruster acceleration (this+0x234 -> this+0x1e8).
//   3. Integrate the motion (FUN_00421b2c / FUN_00421bac) and run the world
//      collision query FUN_0042291c.
//   4. On a hit, spawn the explosion + Damage entity (FUN_004be078, streamed
//      ClassID 0x5C) at the impact point and retire; otherwise advance the
//      smoke-trail model and bounds-check.
//
void Missile::MoveAndCollide(Scalar time_slice)
{
	age += time_slice;									// @0x35c this[0xd7]

	// dead if it has out-lived its flight envelope
	ageFraction = (age <= lifetime) ? age / lifetime : 1.0f;	// @0x344
	if (age > lifetime)
		simulationFlags |= 1;

	// --- drive the hosted subsystems for this slice -----------------------
	// In the shipped binary the Seeker / MissileThruster Performance work is
	// folded into this integrator (it samples their state out of the roster),
	// so rather than ticking them through a separate subsystem loop we advance
	// them explicitly here: re-lead the (moving) target and bleed burn time.
	Seeker          *seeker  = (Seeker *)subsystems[SeekerSubsystem];
	MissileThruster *thruster = (MissileThruster *)subsystems[MissileThrusterSubsystem];
	if (seeker != 0)
		seeker->FindTarget(time_slice);					// @004beae4 LeadTarget
	if (thruster != 0)
		thruster->MissileThrusterSimulation(time_slice);	// bleed burnTimeRemaining

	// --- guidance: build steering toward the Seeker's lead point ----------
	Vector3D forward;
	GetForward(forward);								// FUN_0040d150(.., +0x100)
	const ThrusterState *thr =
		(const ThrusterState *)this->subsystems[MissileThrusterSubsystem];	// this+0x250

	// vector from the missile's facing to the Seeker's lead/aim point  (@0x1c4)
	Vector3D toLead;
	toLead.Subtract(seeker->targetPosition, forward);

	// per-axis: turn-rate vs drift gain depending on alignment, squared error
	Vector3D steer;
	for (int axis = 0; axis < 3; ++axis)
		steer[axis] = (fabs(toLead[axis]) <= MissileSteerEps)
						? toLead[axis] * MissileDriftGain
						: toLead[axis] * MissileTurnGain;
	Scalar thrustAccel = (thr != 0) ? thr->acceleration : 0.0f;	// FUN_004086ac
	velocity.x += steer.x * thrustAccel;					// this+0x1dc
	velocity.y += steer.y * thrustAccel;
	velocity.z += steer.z * thrustAccel;

	// thruster acceleration (this+0x234) folded into velocity (this+0x1e8)
	thrustVelocity.x += thrusterAxis.x * thrustImpulse;		// this+0x1e8 += this+0x234 * this+0x1d0
	thrustVelocity.y += thrusterAxis.y * thrustImpulse;
	thrustVelocity.z += thrusterAxis.z * thrustImpulse;

	// --- integrate + collide ---------------------------------------------
	IntegrateMotion();									// FUN_00421b2c
	AdvanceModel(time_slice);							// FUN_00421bac

	Entity *hit = CollisionQuery(time_slice);			// FUN_0042291c

	if (hit == 0)
	{
		// no impact this frame -- bounds check vs MissileMinSpeed2 elsewhere
	}
	else
	{
		Retire();										// FUN_0042061c
		// spawn explosion + Damage entity at impact  (FUN_004be078, ClassID 0x5C)
		SpawnDetonation(hit);
		simulationFlags |= 1;
	}
}

//#############################################################################
// WriteUpdateRecord
//
// @004bef4c  vtable slot 7 -- chains the Entity base writer (FUN_004225a4),
// stamps record tag 0x78 and appends the detonation/age word (this+0x35c).
//
void Missile::WriteUpdateRecord(UpdateRecord *message, int update_model)
{
	Entity::WriteUpdateRecord(message, update_model);	// @004225a4
	message->recordID = 0x78;							// record tag
	// the shipped record also appends the age word (this+0x35c) for replication.
}

//#############################################################################
// TestInstance
//
Logical Missile::TestInstance() const
{
	return Projectile::TestInstance();
}

//#############################################################################
// Recovered helpers (engine/model plumbing rendered as decomp artifacts).
//
void Missile::GetForward(Vector3D &out)
{
	out = Vector3D(0.0f, 0.0f, -1.0f);					// FUN_0040d150 (facing -Z)
}

void *Missile::ResolveTargetConnection(int /*owner*/)
{
	return 0;											// resolved target/launcher link
}

Vector3D Missile::LaunchAimOffset(int /*owner*/)
{
	return Vector3D(0.0f, 0.0f, 0.0f);
}

const Missile::ModelRecord *Missile::ResolveModel()
{
	// FUN_00407064 chain -- the entity model record (lifetime/thrust/detonation).
	static const ModelRecord defaultModel = { 10.0f, 1.0f, 0 };
	return &defaultModel;
}

void Missile::SpawnDetonation(Entity * /*hit*/)
{
	// FUN_004be078 -- spawn the explosion + Damage entity (streamed ClassID 0x5C)
	// at the impact point.  Cross-family (entity factory / damage) integration.
}
