//===========================================================================//
// File:	btplayer.hpp                                                        //
// Project: BattleTech           Brick: Player / Scoring                      //
// Contents: BattleTech player & scoring entity (BT analog of RPPLAYER)       //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
//  --/--/96  JM   Initial coding.                                           //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
//                      PROPRIETARY AND CONFIDENTIAL                         //
//===========================================================================//
//
// RECONSTRUCTED from the shipped binary (BTL4OPT.EXE) Ghidra pseudo-C
// (the bt/btplayer.cpp cluster @004c012c-@004c0f28) cross-referenced against
// the surviving Red Planet sibling RPPLAYER.cpp/.h (same engine, same Player
// base, same scoring/message-handler/registry idioms) and the surviving BT
// headers BTTEAM.HPP, BTREG.HPP and MUNGA PLAYER.HPP / SCNROLE.HPP.
//
// BTPlayer is the BattleTech player/scoring entity.  Where Red Planet's
// RPPlayer scored "score zones", speed bonuses and crusher/runner roles,
// BTPlayer scores mech-vs-mech combat: damage inflicted, damage received,
// kills and friendly-fire, all scaled by the players' relative tonnage and
// by a per-role ScenarioRole modifier set (see SCNROLE.HPP).
//
// Field offsets in comments are the byte offsets observed in the decompiled
// 0x294-byte object (e.g. "@0x278" == this[0x9e]).  Names are taken from
// PLAYER.HPP / SCNROLE.HPP where the field is inherited, and inferred from
// usage (and flagged) where BT-specific.  See btplayer.cpp for per-method
// @ADDR evidence.
//

#if !defined(BTPLAYER_HPP)
#	define BTPLAYER_HPP

#if !defined(PLAYER_HPP)
#	include <player.hpp>
#endif
#if !defined(SCNROLE_HPP)
#	include <scnrole.hpp>
#endif
#if !defined(CSTR_HPP)
#	include <cstr.hpp>
#endif

//#####################    Forward Class Declarations  #######################
class Mech;
class BTTeam;
class DropZone__ReplyMessage;

//###########################################################################
//#####################    BTPlayer::ScoreMessage    ########################
//###########################################################################
//
//	Extends the MUNGA Player::ScoreMessage (which carries only scoreAward
//	@0x1c) with a scoreType selector and the EntityID of the mech that
//	generated the points.  Parallels RPPlayer__ScoreMessage, but the BT
//	score types describe mech combat instead of score zones.
//
//	Observed message layout (param_2 in the handlers):
//		+0x1c	scoreAward		(Scalar, base Player::ScoreMessage)
//		+0x20	scoreType		(int)
//		+0x24	damageAmount	(Scalar)	raw damage / point quantity
//		+0x28	pointSenderHi	(EntityID word)
//		+0x2c	pointSenderLo	(EntityID word)
//		+0x30	auxID			(EntityID / host word)
//		+0x34	senderMechID	(EntityID -- resolved to the inflicting Mech)
//
	class BTPlayer__ScoreMessage:
		public Player::ScoreMessage
	{
	public:
		//
		// Kind of scoring event.  Recovered from the branch selector at
		// @004c02e4 (this->scoreType, message+0x20) and the dedicated
		// inflicted-damage handler at @004c0200.
		//
		enum ScoreType {
			DamageInflictedScore	= 0,	// to ScoreInflictedMessageHandler
			DamageReceivedScore		= 1,	// I took damage
			KillScore				= 2		// I destroyed / was destroyed
		};

		int
			scoreType;			// +0x20

		Scalar
			damageAmount;		// +0x24

		//
		// Point-source / auxiliary handles carried alongside the damage record
		// (read by ScoreMessageHandler when building the console feed message).
		//
		int
			pointSenderHi;		// +0x28
		int
			pointSenderLo;		// +0x2c
		int
			auxID;				// +0x30

		EntityID
			senderMechID;		// +0x34  inflicting mech (point sender)

		BTPlayer__ScoreMessage(
			Receiver::MessageID	message_ID,
			size_t				length,
			int					score_type,
			Scalar				score_award,
			Scalar				damage_amount,
			const EntityID		&sender_mech_ID
		):
			Player::ScoreMessage(message_ID, length, score_award),
			scoreType(score_type),
			damageAmount(damage_amount),
			senderMechID(sender_mech_ID)
				{}
	};

//###########################################################################
//#####################    BTPlayer::MakeMessage    #########################
//###########################################################################
//
//	Construction message.  BTRegistry::MakePlayer (BTREG.CPP) builds this
//	from the mission's role name and team name; the ctor (@004c0bc8) copies
//	the team name out of it (+0x50) and resolves the role resource (+0x90).
//
	class BTPlayer__MakeMessage:
		public Player::MakeMessage
	{
	public:
		//
		// ⚠ WIRE FORMAT: MakeMessages are sent RAW over the network when the
		// entity replicates (InterestManager -> remote Registry::MakeEntity), so
		// string payload must be INLINE at the binary's fixed offsets -- teamName
		// @ +0x50, roleName @ +0x90 (0x40 bytes each).  The earlier draft stored
		// const char* POINTERS: fine in one address space, garbage on the
		// receiving pod (the first cross-pod BTPlayer replication crashed in
		// CString(roleName)).
		//
		char
			teamName[0x40];		// +0x50  team this player belongs to (inline)
		char
			roleName[0x40];		// +0x90  scenario-role resource name (inline)

		BTPlayer__MakeMessage(
			Receiver::MessageID				message_ID,
			size_t							length,
			Entity::ClassID					class_ID,
			const EntityID					&owner_ID,
			ResourceDescription::ResourceID	resource_ID,
			LWord							instance_flags,
			const Origin					&origin,
			int								player_bitmap_index,
			const char						*role_name,
			const char						*team_name
		):
			Player::MakeMessage(
				message_ID, length, class_ID, owner_ID,
				resource_ID, instance_flags, origin, player_bitmap_index
			)
		{
			teamName[0] = 0;
			roleName[0] = 0;
			if (team_name)
			{
				strncpy(teamName, team_name, sizeof(teamName) - 1);
				teamName[sizeof(teamName) - 1] = 0;
			}
			if (role_name)
			{
				strncpy(roleName, role_name, sizeof(roleName) - 1);
				roleName[sizeof(roleName) - 1] = 0;
			}
		}
	};

//###########################################################################
//###########################    BTPlayer    ################################
//###########################################################################
//
//	(vtable @00513300, ctor @004c0bc8, Make @004c0ecc, scalar-deleting
//	 destructor @004c0efc, TestInstance @004c0f28.)
//
	class BTPlayer:
		public Player
	{
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Shared Data support
	//
	// WinTesla refactored the static derivation/handler/attribute objects
	// behind Get* accessors (avoids static-init ordering bugs).
	//
	public:
		static Derivation* GetClassDerivations();
		static SharedData  DefaultData;

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Scoring / construction message typedefs
	//
	// (Declared up here so the message-handler signatures below resolve
	//  ScoreMessage to BTPlayer__ScoreMessage rather than the inherited
	//  Player::ScoreMessage.)
	//
	public:
		typedef BTPlayer__ScoreMessage	ScoreMessage;
		typedef BTPlayer__MakeMessage	MakeMessage;

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Message Support
	//
	// Handler table (MESSAGE_ENTRY names recovered from the .data string
	// block @005130c0):  DropZoneReply, VehicleDead, Score, ScoreUpdate,
	// MissionStarting, MissionEnding -- plus the dedicated ScoreInflicted
	// handler (assert string @00513110).  ScoreInflicted is listed here as
	// best-effort; its MESSAGE_ENTRY name was not captured in the dump.
	//
	public:
		enum {
			ScoreInflictedMessageID = Player::NextMessageID,
			ScoreUpdateMessageID,
			NextMessageID
		};

	private:
		static const HandlerEntry MessageHandlerEntries[];

	protected:
		static MessageHandlerSet& GetMessageHandlers();

		void
			VehicleDeadMessageHandler(VehicleDeadMessage *message);	// @004c012c
		void
			ScoreInflictedMessageHandler(ScoreMessage *message);	// @004c0200
		void
			ScoreUpdateMessageHandler(ScoreMessage *message);		// @004c02a8
		void
			ScoreMessageHandler(ScoreMessage *message);				// @004c02e4

		//
		// @004bffd0 -- the spawn / respawn handshake.  When the drop zone
		// replies with our spawn location we create (or reset) the player's
		// mech there and bind it to us.  BT analog of Blocker/RPPlayer
		// DropZoneReplyMessageHandler; reconstructed from FUN_004bffd0 (the
		// MESSAGE_ENTRY(BTPlayer,DropZoneReply) handler at table @00512fd8).
		//
		void
			DropZoneReplyMessageHandler(DropZone__ReplyMessage *message);	// @004bffd0

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Vehicle creation
	//
	// CreatePlayerVehicle is the virtual the base Player calls to build the
	// player's vehicle; BT overrides it (vtable slot +0x40 -> FUN_004bfcac)
	// to instantiate a Mech from the mission's game-model resource.
	// InitializePlayerLink (FUN_004bfee0) binds the freshly-made mech back to
	// this player.  Structural model = RPPlayer::CreatePlayerVehicle /
	// ::InitializePlayerLink (same engine, same MakeAndLinkViewpointEntity).
	//
	protected:
		virtual void
			CreatePlayerVehicle(Origin mech_location);	// @004bfcac (vtable +0x40)
		void
			InitializePlayerLink();						// @004bfee0

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Scoring Support
	//
	protected:
		//
		// @004c052c -- the shared "how many points is this damage worth"
		// calculation used by both ScoreInflicted and the kill branch of
		// ScoreMessageHandler.  Applies the role's damageInflictedModifier,
		// damageBias and (for same-team hits) friendlyFirePenalty.
		//
		Scalar
			CalcInflictedScore(
				const Scalar	&damage_amount,
				Mech			*other_mech,
				Scalar			bias
			);

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Model Support
	//
	public:
		typedef void
			(BTPlayer::*Performance)(Scalar time_slice);

		void
			SetPerformance(Performance performance)
			{
				Check(this);
				activePerformance = (Simulation::Performance)performance;
			}

		void
			PlayerSimulation(Scalar time_slice);		// @004c083c (console update)

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Construction and Destruction
	//
	public:
		static BTPlayer*
			Make(MakeMessage *creation_message);		// @004c0ecc

		BTPlayer(
			MakeMessage	*creation_message,
			SharedData	&shared_data = DefaultData
		);												// @004c0bc8
		~BTPlayer();									// @004c0efc (deleting dtor)

		Logical
			TestInstance() const;						// @004c0f28

		//
		// gauge scoring wave: public scoreboard accessors so the cockpit gauges
		// (PilotList KILLS/DEATHS) read the SAME compiled members the scoring
		// handlers write -- the earlier raw-offset reads (pilot+0x27c / +0x200)
		// don't match our compiled layout (engine base != the 1995 binary) and
		// silently read 0.  currentScore/playerRanking are inherited Player fields.
		//
		int		GetKillCount() const	{ return killCount; }		// @0x27c
		int		GetDeaths() const		{ return deathCount; }		// @0x200 (Player)
		Scalar	GetScore() const		{ return currentScore; }	// @0x278 (Player)
		int		GetRanking() const		{ return playerRanking; }	// (Player)
		Mech	*GetObjectiveMech() const	{ return objectiveMech; }	// @0x284 (current target)

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Local data
	//
	// Offsets are byte offsets into the shipped 0x294-byte object.  The
	// base/derived boundary is approximate; fields known to be inherited
	// from Player/Entity (playerVehicle, deathCount, scenarioRole,
	// currentScore, ownerID) are noted as such.
	//
	protected:
		//	--- inherited from Player / Entity, referenced here ---
		//	EntityID		ownerID;			// @0x18c (Entity)
		//	Mech			*playerVehicle;		// @0x1fc (Player)
		//	int				deathCount;			// @0x200 (Player)
		//	ScenarioRole	*scenarioRole;		// @0x208 (Player) -- scoring role
		//	Scalar			currentScore;		// @0x278 (Player) -- running score

		//	--- BT-specific ---
		Mission
			*btMission;			// @0x1f8  cached mission / role registry source
		char
			teamName[64];		// @0x20c  copied from MakeMessage (+0x50)
		BTTeam
			*teamEntity;		// @0x24c  resolved from the "Teams" group
		Logical
			freeForAll;			// @0x250  game type == "freeforall" (no team scoring)
		Logical
			consoleAttached;	// @0x254  a console host exists for us
		Logical
			suppressConsole;	// @0x258  skip console notify for this score event

		//
		// Per-role display toggles, selected from the role's "returnFromDeath"
		// class index (scenarioRole resource +0xe4, values 0..3) at @004c0bc8.
		//
		Logical
			showDamageReceived;	// @0x25c
		Logical
			showKills;			// @0x260
		Logical
			showDamageInflicted;// @0x264
		Logical
			showScore;			// @0x270
		Scalar
			roleReturnDelay;	// @0x268  (role resource +0xf0)
		Scalar
			roleReturnDelay2;	// @0x26c
		int
			roleClassIndex;		// @0x274  (role resource +0xe4)

		int
			killCount;			// @0x27c  kills credited to this player
		int
			pad_0x280;			// @0x280
		Mech
			*objectiveMech;		// @0x284  designated target / objective mech
		Time
			lastPerformance;	// @0x288  cached sim clock
		Time
			lastConsoleUpdate;	// @0x28c  last time score was sent to console
		Logical
			deathPending;		// @0x290  death notice scheduled / in flight

		//
		// Name of whoever last killed us (shown on the pilot HUD).  Copied from
		// the VehicleDead message in the binary; @0x1d0 (CString).
		//
		CString
			killerName;			// @0x1d0
	};

#endif
