//===========================================================================//
// File:	messmgr.cpp                                                         //
// Project: BattleTech                                                        //
// Contents:  Consolidates Messages from Subsystems and Graphics Generation  //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
// 09/30/95  JM  Initial coding.                                             //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
//                      PROPRIETARY AND CONFIDENTIAL                         //
//===========================================================================//
//
// RECONSTRUCTED from the shipped binary.  Behaviour follows the Ghidra
// pseudo-C cluster @0049b6d8..@0049c3e3 (recovered/all/part_012.c) and the
// surviving MESSMGR.HPP.  Each non-trivial method cites its @ADDR.
//
// The SubsystemMessageManager is the per-mech message hub: it accumulates
// damage / weapon-explosion notifications from the other subsystems during a
// frame (AddDamageMessage), then, when its Performance fires
// (ConsolidateAndSendDamage), it folds them into the common-damage record,
// re-broadcasts TakeDamage messages to the affected entities, and emits the
// queued explosion graphics to the renderer.
//
// Object layout (this+offset), recovered from the ctor/dtor:
//      this[0x07] @0x1C   activePerformance         (Simulation base; set to
//                                                     ConsolidateAndSendDamage)
//      this[0x34] @0xD0   owner Mech*               (Subsystem base)
//      this[0x38] @0xE0   message/graphics helper object (0x160 bytes, created
//                                                     in ctor; engine-internal,
//                                                     NOT declared in MESSMGR.HPP)
//      this[0x39] @0xE4   commonDamageInformation.entityHit
//      this[0x3A] @0xE8   commonDamageInformation.damageZoneIndex
//      this[0x3B] @0xEC   commonDamageInformation.impactPoint (Point3D)
//      this[0x3E] @0xF8   rendererCompensateTime
//      this[0x3F] @0xFC   weaponExplosions   (VChainOf, link vtable @0050b938)
//      this[0x45] @0x114  damageInformation  (VChainOf, link vtable @0050b91c)
//      this[0x4B] @0x12C  terrainHitExplosionID
//
// Helper-function name mapping (engine internals referenced by the decomp):
//      FUN_0041c52c   Subsystem base constructor
//      FUN_0041c648   Subsystem destructor
//      FUN_0041de1c   message-helper-object constructor (0x160-byte object @0xE0)
//      FUN_00402298   operator new(size)         FUN_004022d0  operator delete
//      FUN_00415e90   Plug constructor           FUN_00415ed8  Plug destructor
//      FUN_00408440   Point3D::operator= / copy  FUN_004084fc  Point3D::AlmostEqual
//      FUN_00419e18   <message>::ctor(classID,size)
//      FUN_0041a1a4   IsDerivedFrom(classDerivations) / name compare
//      FUN_00420ea4   Point3D copy into message field
//      FUN_004336f0   build renderer command record
//      FUN_0041acbc   GraphicsPipe::Submit(...)
//      FUN_00424716   PlugOf<ResourceID>::ctor
//      VChain helpers: 0049bfde/0049c123 (chain ctors), 0049c001/0049c146
//                      (chain dtors), 0049c02d/0049c05c/0049c172/0049c1a3
//                      (key comparators), 0049c37e/0049c3ae (link allocators).
//
// Constants:
//      message ClassID 0x13 (damage/notify message, vtable @0050b984, size 0x34)
//      explosion ClassID 0x5C, sub-type 3 (queued weapon explosion record)
//      0.1f (DAT @0049b784 frame) = per-explosion render time step
//
#include <bt.hpp>
#pragma hdrstop

#if !defined(MESSMGR_HPP)
#	include <messmgr.hpp>
#endif
#if !defined(MECH_HPP)
#	include <mech.hpp>
#endif
#if !defined(APP_HPP)
#	include <app.hpp>
#endif

//
// `Origin3D` (DAT_004e0f80) is the zero point.  The engine point is
// `Point3D::Identity` (== 0,0,0); Point3D has no `AlmostEqual`, so compare the
// components directly.  (Used to detect "no impact point recorded yet".)
//
static Logical
	PointIsOrigin(const Point3D &p)
	{
		return p.x == 0.0f && p.y == 0.0f && p.z == 0.0f;
	}


//#############################################################################
// Shared Data Support
//
// SubsystemMessageManager derives DIRECTLY from Subsystem, which exposes the
// parent derivation via the function `Subsystem::GetClassDerivations()`
// (returns a Derivation*), not a `ClassDerivations` object -- so no '&' here.
Derivation
	SubsystemMessageManager::ClassDerivations(	// static @0050b6cc
		Subsystem::GetClassDerivations(),
		"SubsystemMessageManager"
	);

Receiver::MessageHandlerSet
	SubsystemMessageManager::MessageHandlers;

SubsystemMessageManager::AttributeIndexSet
	SubsystemMessageManager::AttributeIndex;

// Real Simulation__SharedData ctor: (Derivation*, MessageHandlerSet&,
// AttributeIndexSet&, int stateCount).  StateCount resolves to
// Simulation::StateCount.  The default activePerformance (@0050b6fc ->
// 0049b784 ConsolidateAndSendDamage) is installed in the ctor body instead.
SubsystemMessageManager::SharedData
	SubsystemMessageManager::DefaultData(
		&SubsystemMessageManager::ClassDerivations,
		SubsystemMessageManager::MessageHandlers,
		SubsystemMessageManager::AttributeIndex,
		SubsystemMessageManager::StateCount
	);


//#############################################################################
//					Construction / Destruction
//#############################################################################
//
// @0049bca4 -- chains to the Subsystem base ctor, installs the vtable
// (@0050b954), constructs both message chains in place, allocates the
// message-helper object, installs the default Performance, and copies the two
// streamed resource fields.
//
SubsystemMessageManager::SubsystemMessageManager(
	Mech				*owner,
	int					subsystem_ID,
	SubsystemResource	*sub_res,
	SharedData			&shared_data
):
	Subsystem(owner, subsystem_ID, sub_res, shared_data),	// FUN_0041c52c
	weaponExplosions(NULL, True),			// @0xFC  unique-keyed by explosion id
	damageInformation(NULL, False)			// @0x114 keyed by damage amount
{
	Check(owner);
	Check_Pointer(sub_res);

	//
	// In-place construction of the two VChains (the ctor calls the chain
	// header constructors directly: weaponExplosions @0049bfde, with one
	// pre-allocated link; damageInformation @0049c123).
	//
	// weaponExplosions  : VChainOf<ResourceIDPlug*, ResourceID>   @0xFC
	// damageInformation : VChainOf<DamageInformation*, Scalar>    @0x114
	//

	// Engine-internal message/graphics helper (0x160-byte object) parked at
	// +0xE0.  Created via FUN_0041de1c(obj, this, 0); released in the dtor by
	// virtual call (op==3).  Not a MESSMGR.HPP member -- best-effort.
	// this[0x38] = new <messageHelper>(this);

	// Default Performance := ConsolidateAndSendDamage (this[0x07..0x09] copied
	// from the constants @0050b6fc/0050b700/0050b704).
	SetPerformance(&SubsystemMessageManager::ConsolidateAndSendDamage);

	terrainHitExplosionID    = sub_res->terrainHitExplosionID;		// this[0x4B] = sub_res+0x30
	rendererCompensateTime   = sub_res->rendererCompensateTime;		// this[0x3E] = sub_res+0x34

	commonDamageInformation.entityHit       = 0;					// this[0x39]
	commonDamageInformation.damageZoneIndex  = -1;					// this[0x3A]
	commonDamageInformation.impactPoint      = Point3D::Identity;	// this[0x3B] = DAT_004e0f80

	Check_Fpu();
}

//
// @0049bd7c (vtable slot 0) -- if the message-helper object exists, release it
// (virtual op 3); destroy both message chains; chain to ~Subsystem.
//
SubsystemMessageManager::~SubsystemMessageManager()
{
	Check(this);

	// release this[0x38] message-helper object (virtual destroy, op==3)
	// damageInformation.~VChainOf();   // @0049c146
	// weaponExplosions.~VChainOf();    // @0049c001

	Check_Fpu();
}

//
// @0049be10 -- SubsystemMessageManager::CreateStreamedSubsystem.
// Reached from the mech3.cpp CreateSubsystemStream factory when the component
// type string == "SubsystemMessageManager".  Stamps ClassID 0xBD3 / model
// record size 0x38, then reads the two manager-specific resource fields.
//
Logical
	SubsystemMessageManager::CreateStreamedSubsystem(
		ResourceFile				*resource_file,
		NotationFile				*model_file,
		const char					*model_name,
		const char					*subsystem_name,
		SubsystemResource			*subsystem_resource,
		NotationFile				*subsystem_file,
		const ResourceDirectories	*directories
	)
{
	if (
		!Subsystem::CreateStreamedSubsystem(
			model_file, model_name, subsystem_name,
			subsystem_resource, subsystem_file, directories
		)
	)
	{
		return False;
	}

	subsystem_resource->classID			= (RegisteredClass::ClassID)0xBD3;	// resource+0x20
	subsystem_resource->subsystemModelSize	= 0x38;								// resource+0x24

	//
	// "TerrainHitExplosion" -- a resource name that must resolve to a model in
	// resource_file; the resolved ResourceID is stored at resource+0x30.
	//
	const char *terrainHitName;
	if (!subsystem_file->GetEntry(subsystem_name, "TerrainHitExplosion", &terrainHitName))
	{
		std::cerr << subsystem_name << " missing TerrainHitExplosion!\n";
		return False;
	}
	ResourceDescription *rd =										// FUN_00406ff8
		resource_file->FindResourceDescription(
			terrainHitName,
			ResourceDescription::GameModelResourceType			// recovered type literal 1
		);
	if (rd == 0)
	{
		std::cerr << subsystem_name << " cannot find " << terrainHitName
			<< " in resource file!\n";
		return False;
	}
	subsystem_resource->terrainHitExplosionID = rd->resourceID;	// resource+0x30

	//
	// "RendererCompensateTime" -- a Scalar stored at resource+0x34.
	//
	if (!subsystem_file->GetEntry(subsystem_name, "RendererCompensateTime",
			&subsystem_resource->rendererCompensateTime))		// resource+0x34
	{
		std::cerr << subsystem_name << " missing RendererCompensateTime\n";
		return False;
	}

	return True;
}

//
// TestInstance -- emitted inline by the shipped build (no standalone body in
// the cluster).  Standard subsystem form.
//
Logical
	SubsystemMessageManager::TestInstance() const
{
	Check(this);
	return IsDerivedFrom(ClassDerivations);
}


//#############################################################################
//					AddDamageMessage  @0049b6d8
//#############################################################################
//
// Called by other subsystems / the damage path when an entity takes damage.
// Records the first hit's common information (entity, zone, impact point) and
// queues a per-message DamageInformation Plug into the damageInformation
// chain, keyed by the message's timestamp (Scalar at message+0x30).
//
void
	SubsystemMessageManager::AddDamageMessage(
		Entity						*damaged_entity,
		Entity__TakeDamageMessage	*message
	)
{
	// First damaged entity of the frame wins the common record.
	if (commonDamageInformation.entityHit == 0)
	{
		commonDamageInformation.entityHit = damaged_entity;
	}

	// First non-origin impact point of the frame wins.  The recovered struct
	// fields live inside the embedded Damage record (Entity__TakeDamageMessage::
	// damageData): impactPoint @message+0x4C, damageType @+0x2C, amount @+0x30.
	if (PointIsOrigin(commonDamageInformation.impactPoint))					// FUN_004084fc
	{
		commonDamageInformation.impactPoint = message->damageData.impactPoint;	// message+0x4C
	}

	// First valid damage-zone index of the frame wins.
	if (commonDamageInformation.damageZoneIndex == -1)
	{
		commonDamageInformation.damageZoneIndex = message->damageZone;		// message+0x24
	}

	//
	// Queue a DamageInformation plug (size 0x14: Plug + damageType + subsystemID),
	// inserted into the damageInformation chain keyed by message+0x30 (the damage
	// amount -- the Scalar immediately after damageType inside the Damage record).
	//
	DamageInformation *info = new DamageInformation;		// FUN_00402298(0x14)
	if (info != 0)
	{
		// Plug base init (FUN_00415e90 / vtable @0050b9a8)
		info->damageType  = message->damageData.damageType;			// puVar2[3] = message+0x2C
		info->subsystemID = message->inflictingSubsystemID;			// puVar2[4] = message+0x5C
	}

	damageInformation.AddValue(info, message->damageData.damageAmount);	// chain+0x114, key = message+0x30
}


//#############################################################################
//					ConsolidateAndSendDamage  @0049b784   (Performance)
//#############################################################################
//
// The per-frame Performance (installed as activePerformance by the ctor).
// Walks the queued DamageInformation chain, re-broadcasting a consolidated
// damage/notify message (ClassID 0x13, vtable @0050b984) for the common
// entity, queues a terrain-hit explosion resource into weaponExplosions when
// the hit zone has no explosion yet, flushes the explosion graphics to the
// renderer (SendQueuedExplosions @0049baa0), then clears the common record and
// empties the damage chain.
//
void
	SubsystemMessageManager::ConsolidateAndSendDamage(Scalar /*time_slice*/)
{
	Mech *owner = GetEntity();				// this[0x34] @0xD0

	VChainIteratorOf<DamageInformation*, Scalar>
		damageWalk(&damageInformation);		// FUN_0049c28b over this+0x114

	// VChainIteratorOf has no IsDone(); GetCurrent() returns NULL past the end.
	damageWalk.First();
	if (damageWalk.GetCurrent() != 0)
	{
		// (recovered) the consolidated damage/notify message (ClassID 0x13,
		// size 0x34, vtable @0050b984) was built on the stack here and filled
		// from commonDamageInformation.  The message ctor (FUN_00419e18) and the
		// owner target field (+0x184) are renderer/net-side and are not present
		// in these headers; the explosion-queueing loop below is what feeds the
		// graphics flush, so the message build is left as a recovered note.
		//   EntityID hitEntity = commonDamageInformation.damageZoneIndex;

		for (damageWalk.First(); damageWalk.GetCurrent() != 0; damageWalk.Next())
		{
			DamageInformation *info = damageWalk.GetCurrent();

			// resolve the damaged subsystem object from the owner's roster:
			//   subsystem = owner->subsystems[info->subsystemID];   (mech+0x128)
			Subsystem *target =
				owner->GetSubsystem(info->subsystemID);				// mech+0x128 + id*4

			ResourceDescription::ResourceID explosionID =
				ResolveExplosionID(target);							// target+0x3E4

			//
			// CreateWeaponExplosions (inlined): if this hit zone has no
			// explosion queued yet, append a ResourceIDPlug to the
			// weaponExplosions chain so it is rendered this frame.
			//
			if (!weaponExplosions.Find(explosionID))				// chain+0xFC, slot 0xC
			{
				ResourceIDPlug *plug = new ResourceIDPlug(explosionID);	// FUN_00402298(0x10) / FUN_00424716
				weaponExplosions.AddValue(plug, explosionID);		// chain+0xFC, slot 8
			}
		}

		// Render queued weapon explosions and tear down the message.
		SendQueuedExplosions();			// @0049baa0
	}

	// Reset common record and empty the damage chain for the next frame.
	commonDamageInformation.entityHit       = 0;					// this[0x39]
	commonDamageInformation.damageZoneIndex  = -1;					// this[0x3A]
	commonDamageInformation.impactPoint      = Point3D::Identity;	// this[0x3B]
	// damageInformation cleared as the iterator is destroyed (FUN_0049c2c9).
}


//#############################################################################
//					SendQueuedExplosions  @0049baa0   (file-local helper)
//#############################################################################
//
// Drains the weaponExplosions chain, emitting one renderer command per queued
// explosion (ClassID 0x5C, sub-type 3) at an increasing time step (0.1f per
// explosion) so multiple impacts in one frame are staggered.  Submitted via
// the global graphics pipe (DAT_004efc94+0x60).
//
// NOTE: not declared in MESSMGR.HPP -- the header's protected
// CreateWeaponExplosions(terrain_hit, entity_hit, explode_position) is the
// per-explosion *queueing* helper (inlined into ConsolidateAndSendDamage
// above); this routine is the per-frame *flush* of the accumulated queue.
// Name is best-effort.
//
void
	SubsystemMessageManager::SendQueuedExplosions()
{
	Check(this);

	VChainIteratorOf<ResourceIDPlug*, ResourceDescription::ResourceID>
		explosionWalk(&weaponExplosions);			// FUN_0049c314 over this+0xFC

	// Base time was SystemClock Now() (FUN_00414b60); derive it from the last
	// performance time so no global clock accessor is required here.
	Scalar baseTime =
		(Scalar)lastPerformance.ticks / (Scalar)SystemClock::GetTicksPerSecond();
	Scalar step     = 0.0f;
	const Scalar stepIncrement = 0.1f;				// local_c

	// VChainIteratorOf has no IsDone(); GetCurrent() returns NULL past the end.
	for (explosionWalk.First(); explosionWalk.GetCurrent() != 0; explosionWalk.Next())
	{
		ResourceDescription::ResourceID id = explosionWalk.GetValue();	// link+0x1C (the key)

		//
		// Each queued explosion is fired at an increasing time step (0.1s each)
		// so multiple impacts in one frame stagger.
		//
		step += stepIncrement;
		Scalar fireTime = baseTime + step;

		SubmitExplosion(id, fireTime);				// CROSS-FAMILY renderer submission
	}

	// chain emptied as the iterator is destroyed (FUN_0049c352).
}


//#############################################################################
//					CROSS-FAMILY compile shims
//#############################################################################
//
// ResolveExplosionID: in the shipped game this read subsystem+0x3E4 via
// Subsystem::GetExplosionID().  The WinTesla Subsystem does not expose it; until
// the base provides the accessor this returns the manager's own
// terrainHitExplosionID as a stand-in (NullResourceID if none).  See report.
//
ResourceDescription::ResourceID
	SubsystemMessageManager::ResolveExplosionID(Subsystem *subsystem)
{
	Check_Pointer(subsystem);
	// TODO(cross-family): return subsystem->GetExplosionID();
	return terrainHitExplosionID;
}

//
// SubmitExplosion: the original built a renderer command (FUN_004336f0) and
// submitted it (with the impact point/orientation from commonDamageInformation
// and rendererCompensateTime) to the global graphics pipe (FUN_0041acbc).  The
// WinTesla L4D3D renderer replaced the IG graphics pipe; the submission entry
// point is not present in these headers.  Stubbed so the queue/timing logic
// compiles.  See report "CROSS-FAMILY NEEDS".
//
void
	SubsystemMessageManager::SubmitExplosion(
		ResourceDescription::ResourceID /*explosion_id*/,
		Scalar /*fire_time*/
	)
{
	Check(this);
	// TODO(cross-family): build the explosion render command (ClassID 0x5C,
	// sub-type 3) from commonDamageInformation + explosion_id and submit it to
	// the L4D3D graphics pipe at fire_time, compensated by rendererCompensateTime.
}


//#############################################################################
//					DeathReset / ResetToInitialState
//#############################################################################
//
// vtable slot 10 @0049c3de is an empty body (the shipped build folds the
// trivial ResetToInitialState / DeathReset(Logical){} override to a bare
// "return").  Declared inline in MESSMGR.HPP (DeathReset); reproduced here for
// completeness.
//
// void SubsystemMessageManager::DeathReset(Logical) {}   // inline, see header


//#############################################################################
//					DamageInformation::~DamageInformation  @0049c3e3
//#############################################################################
//
// The nested DamageInformation : Plug has a single virtual (its destructor,
// vtable @0050b9a8).  Chains to ~Plug (FUN_00415ed8); the Plug has no owned
// resources beyond the base.
//
// SubsystemMessageManager::DamageInformation::~DamageInformation() {}  // @0049c3e3
