//===========================================================================//
// File:	Armor.cpp														 //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
// 12/19/01  MSL  Added StandardArmor                                        //
//---------------------------------------------------------------------------//
// Copyright (C) 1998, Fasa Interactive										 //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//

#include "MW4Headers.hpp"

#include "Armor.hpp"
#include "Mech.hpp"
#include "MWDamageObject.hpp"
#include "SubsystemClassData.hpp"

#include <Adept\DamageObject.hpp>

//#############################################################################
//###############################    Armor    #################################
//#############################################################################

Armor::ClassData*
	Armor::DefaultData = NULL;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Armor::InitializeClass()
{
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			ArmorClassID,
			"MechWarrior4::Armor",
			Subsystem::DefaultData,
			0, NULL,
			(Entity::Factory)Make,
			(Entity::CreateMessage::Factory)CreateMessage::ConstructCreateMessage,
			ExecutionStateEngine::DefaultData,
			(Entity::GameModel::Factory)GameModel::ConstructGameModel,
			NULL,
			(Entity::GameModel::ReadAndVerifier)GameModel::ReadAndVerify,
			(Entity::GameModel::ModelWrite)GameModel::WriteToText,
			(Entity::GameModel::ModelSave)GameModel::SaveGameModel,
			(Subsystem::StreamCreate) CreateStream
		);
	Register_Object(DefaultData);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		Armor__GameModel,
		ArmDistributive,
		m_armDistributive,
		Scalar
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		Armor__GameModel,
		LegDistributive,
		m_legDistributive,
		Scalar
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		Armor__GameModel,
		SideFrontDistributive,
		m_sideFrontDistributive,
		Scalar
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		Armor__GameModel,
		CenterFrontDistributive,
		m_centerFrontDistributive,
		Scalar
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		Armor__GameModel,
		CenterRearDistributive,
		m_centerRearDistributive,
		Scalar
	);
// MSL 5.00 Armor
	CUSTOM_DIRECT_GAME_MODEL_ATTRIBUTE(
		Armor__GameModel,
		PointsPerStandardTon,
		m_pointsPerStandardTon,
		int,
		IntClassID
	);


	CUSTOM_DIRECT_GAME_MODEL_ATTRIBUTE(
		Armor__GameModel,
		PointsPerFerroTon,
		m_pointsPerFerroTon,
		int,
		IntClassID
	);

	CUSTOM_DIRECT_GAME_MODEL_ATTRIBUTE(
		Armor__GameModel,
		PointsPerReflectiveTon,
		m_pointsPerReflectiveTon,
		int,
		IntClassID
	);

	CUSTOM_DIRECT_GAME_MODEL_ATTRIBUTE(
		Armor__GameModel,
		PointsPerReactiveTon,
		m_pointsPerReactiveTon,
		int,
		IntClassID
	);

	CUSTOM_DIRECT_GAME_MODEL_ATTRIBUTE(
		Armor__GameModel,
		PointsPerSolarianTon,
		m_pointsPerSolarianTon,
		int,
		IntClassID
	);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Armor::TerminateClass()
{
	Unregister_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Armor*
	Armor::Make(
		CreateMessage *message,
		ReplicatorID *base_id
	)
{
	Check_Object(message);
	gos_PushCurrentHeap(Heap);
	Armor *new_entity = 
		new Armor(DefaultData, message, base_id, NULL);
	gos_PopCurrentHeap();
	Check_Object(new_entity);
	return new_entity;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Replicator::CreateMessage*
	Armor::SaveMakeMessage(MemoryStream *stream, ResourceFile *res_file)
{
	Check_Object(this);
	Check_Object(stream);

	stream->AllocateBytes(sizeof(CreateMessage));
	Subsystem::SaveMakeMessage(stream, res_file);
	CreateMessage *message =
		Cast_Pointer(CreateMessage*, stream->GetPointer());
	message->messageLength = sizeof(*message);

	message->m_leftLegArmor = m_armorZoneValues[DamageObject::LeftLegArmorZone];
	message->m_rightLegArmor = m_armorZoneValues[DamageObject::RightLegArmorZone];
	message->m_leftArmArmor = m_armorZoneValues[DamageObject::LeftArmArmorZone];
	message->m_rightArmArmor = m_armorZoneValues[DamageObject::RightArmArmorZone];
	message->m_leftFrontTorsoArmor = m_armorZoneValues[DamageObject::LeftTorsoArmorZone];
	message->m_rightFrontTorsoArmor = m_armorZoneValues[DamageObject::RightTorsoArmorZone];
	message->m_centerFrontTorsoArmor = m_armorZoneValues[DamageObject::CenterTorsoArmorZone];
	message->m_centerRearTorsoArmor = m_armorZoneValues[DamageObject::CenterRearTorsoArmorZone];
	message->m_headArmor = m_armorZoneValues[DamageObject::HeadArmorZone];

	message->m_armorType = m_armorType;
	message ->m_internalType = m_internalType;

	return message;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Armor::Armor(
	ClassData *class_data,
	CreateMessage *message,
	ReplicatorID *base_id,
	ElementRenderer::Element *element
):
	Subsystem(class_data, message, base_id, element)
{
	Check_Pointer(this);
	Check_Object(message);

	//Set up the armor Zone array
	m_totalArmor = 0;
	m_armorZoneValues[DamageObject::LeftLegArmorZone] = message->m_leftLegArmor;
	m_totalArmor += message->m_leftLegArmor;
	m_armorZoneValues[DamageObject::RightLegArmorZone] = message->m_rightLegArmor;
	m_totalArmor += message->m_rightLegArmor;
	m_armorZoneValues[DamageObject::LeftArmArmorZone] = message->m_leftArmArmor;
	m_totalArmor += message->m_leftArmArmor;
	m_armorZoneValues[DamageObject::RightArmArmorZone] = message->m_rightArmArmor;
	m_totalArmor += message->m_rightArmArmor;
	m_armorZoneValues[DamageObject::RightTorsoArmorZone] = message->m_rightFrontTorsoArmor;
	m_totalArmor += message->m_rightFrontTorsoArmor;
	m_armorZoneValues[DamageObject::LeftTorsoArmorZone] = message->m_leftFrontTorsoArmor;
	m_totalArmor += message->m_leftFrontTorsoArmor;
	m_armorZoneValues[DamageObject::CenterTorsoArmorZone] = message->m_centerFrontTorsoArmor;
	m_totalArmor += message->m_centerFrontTorsoArmor;
	m_armorZoneValues[DamageObject::CenterRearTorsoArmorZone] = message->m_centerRearTorsoArmor;
	m_totalArmor += message->m_centerRearTorsoArmor;
	m_armorZoneValues[DamageObject::HeadArmorZone] = message->m_headArmor;
	m_totalArmor += message->m_headArmor;

	m_armorType = message->m_armorType,
	m_internalType = message->m_internalType;

	const GameModel *model = GetGameModel();
	Check_Object(model);
	m_armorMultiplier = 1;
	switch(m_armorType)
	{
// MSL 5.00 Armor
	case StandardArmor: 
		m_armorMultiplier = model->m_pointsPerStandardTon;
		break;
	case FerroFiberusArmor: 
		m_armorMultiplier = model->m_pointsPerFerroTon;
		break;
	case ReactiveArmor:
		m_armorMultiplier = model->m_pointsPerReactiveTon;
		break;
	case ReflectiveArmor:
		m_armorMultiplier = model->m_pointsPerReflectiveTon;
		break;
	case SolarianArmor:
		m_armorMultiplier = model->m_pointsPerSolarianTon;
		break;
	}

	m_totalTonage =(m_totalArmor / m_armorMultiplier);
		
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Armor::~Armor()
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool 
	Armor::ConnectSubsystem()
{
	InitializeDamageObjectsWithArmor();
	GetParentVehicle()->AddTonage(m_totalTonage);

	//Must call the inherited virtual function!!!!
	return Subsystem::ConnectSubsystem();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void 
	Armor::InitializeDamageObjectsWithArmor()
{
	Check_Object(this);

	MWObject *parent = GetParentVehicle();
	Check_Object(parent);

	DamageObject *damage_object;
	MWInternalDamageObject *internal_object;
	SortedChainIteratorOf<DamageObject *, int> damage_iterator(&parent->damageObjects);
	SortedChainIteratorOf<MWInternalDamageObject *, int> internal_iterator(&parent->internalDamageObjects);

	//
	//---------------------------------------------------------------
	//Setup the Damage Objects with Armor Type and their distribution
	//---------------------------------------------------------------
	//
	for(int i=DamageObject::LeftLegArmorZone; i<=DamageObject::HeadArmorZone; i++)
	{
		damage_object = parent->damageObjects.Find(i);
		if(damage_object)
		{
			damage_object->SetType(m_armorType);
			damage_object->AppendArmor(m_armorZoneValues[i]);
		}
#if defined(LAB_ONLY)
		else
		{
			PAUSE(("This mech is missing a %s zone!", DamageObject::ArmorZoneAsciiToText(i)));
		}
#endif
	}

	//
	//------------------------------------
	//Setup Internal Zones with their type
	//------------------------------------
	//
	while((internal_object = internal_iterator.ReadAndNext()) != NULL)
	{
		Check_Object(internal_object);
		internal_object->SetType(m_internalType);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	Armor::ArmorTypeTextToAscii(const char *armor_type_string)
{	
// MSL 5.00 Armor
	if(!_stricmp(armor_type_string, "Standard"))
		return StandardArmor;
	if(!_stricmp(armor_type_string, "FerroFiberus"))
		return FerroFiberusArmor;
	if(!_stricmp(armor_type_string, "Reactive"))
		return ReactiveArmor;
	if(!_stricmp(armor_type_string, "Reflective"))
		return ReflectiveArmor;
	if(!_stricmp(armor_type_string, "Solarian"))
		return SolarianArmor;

	PAUSE(("That is a bad Armor String: %s", armor_type_string));
	return -1;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
const char*
	Armor::ArmorTypeAsciiToText(int armor_type)
{	
	switch(armor_type)
	{
// MSL 5.00 Armor
	case StandardArmor:
		return "Standard";
	case FerroFiberusArmor:
		return "FerroFiberus";
	case ReactiveArmor:
		return "Reactive";
	case ReflectiveArmor:
		return "Reflective";
	case SolarianArmor:
		return "Solarian";
	}

	PAUSE(("That is a bad armor value"));
	return "Bad";
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	Armor::InternalTypeTextToAscii(const char *internal_type_string)
{	
	if(!_stricmp(internal_type_string, "Standard"))
		return StandardInternal;
	if(!_stricmp(internal_type_string, "EndoSteel"))
		return EndoSteelInternal;

	PAUSE(("That is a bad Internal String: %s", internal_type_string));
	return -1;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
const char*
	Armor::InternalTypeAsciiToText(int internal_type)
{	
	switch(internal_type)
	{
	case StandardInternal:
		return "Statndard";
	case EndoSteelInternal:
		return "EndoSteel";
	}

	PAUSE(("That is a bad armor value"));
	return "Bad";
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool 
	Armor::ApplyDistributiveArmor()
{
	Check_Object(this);

	//Ok so the easiest way to do this is for us...to just clear all the armor and then
	//run through them and distribute it all.
	Scalar total_armor = 0.0f;
	Scalar left_over_armor = 0.0f;
	Scalar armor_used = 0.0f;  //use for armoring purposes (code armor that is)

	for(int i=DamageObject::LeftLegArmorZone; i<=DamageObject::HeadArmorZone; i++)
	{
		total_armor += m_armorZoneValues[i];
		m_armorZoneValues[i] = 0.0f;
	}

	const GameModel *model = GetGameModel();
	Check_Object(model);

	//We need to make the distribution array so we can easily run everything else through a loop
	Scalar armor_dist[DamageObject::HeadArmorZone];
	armor_dist[DamageObject::LeftLegArmorZone] = model->m_legDistributive;
	armor_dist[DamageObject::RightLegArmorZone] = model->m_legDistributive;
	armor_dist[DamageObject::LeftArmArmorZone] = model->m_armDistributive;
	armor_dist[DamageObject::RightArmArmorZone] = model->m_armDistributive;
	armor_dist[DamageObject::RightTorsoArmorZone] = model->m_sideFrontDistributive;
	armor_dist[DamageObject::LeftTorsoArmorZone] = model->m_sideFrontDistributive;
	armor_dist[DamageObject::CenterTorsoArmorZone] = model->m_centerFrontDistributive;
	armor_dist[DamageObject::CenterRearTorsoArmorZone] = model->m_centerRearDistributive;

	//first grab the head
	//Ok now we run the loop and try to distribute the armor based upon our nice little array 
	//We should not have to worry at all about tonnage since the armor has already been added...we
	//just need to redistribute the numbers!
	MWObject *parent = GetParentVehicle();
	Check_Object(parent);

	//first grab the head and fill it
	DamageObject *damage_object = parent->damageObjects.Find(DamageObject::HeadArmorZone);
	Check_Object(damage_object);
	m_armorZoneValues[DamageObject::HeadArmorZone] = damage_object->maxArmorValue;
	total_armor -= damage_object->maxArmorValue;

	for(i = DamageObject::LeftLegArmorZone; i<DamageObject::HeadArmorZone; i++)
	{	
		Verify(armor_dist[i] >= 0.0f);
		Scalar armor_amount = total_armor * armor_dist[i];
		//Check the	max on this one first...see if we have left_over
		DamageObject *damage_object = parent->damageObjects.Find(i);
		Check_Object(damage_object);
		if(armor_amount > damage_object->maxArmorValue)
			left_over_armor += armor_amount - damage_object->maxArmorValue;
		Clamp(armor_amount, 0.0f, damage_object->maxArmorValue);
		m_armorZoneValues[i] = armor_amount;
		armor_used += armor_amount;
		Verify(armor_used <= total_armor);
	}

	//now run through the left over and just place the shit!
	for(i = DamageObject::HeadArmorZone - 1; i>=DamageObject::LeftLegArmorZone; i--)
	{	
		DamageObject *damage_object = parent->damageObjects.Find(i);
		Check_Object(damage_object);
		Scalar armor_amount = left_over_armor;

		//Try to add it all...just keep track of what we don't add.
		if(left_over_armor > damage_object->maxArmorValue)
			left_over_armor = left_over_armor - damage_object->maxArmorValue;
		else
			left_over_armor = 0.0f;
		Clamp(armor_amount, 0.0f, damage_object->maxArmorValue);
		m_armorZoneValues[i] += armor_amount;
		armor_used += armor_amount;
		Verify(armor_used <= total_armor);
	}
	
	Verify(armor_used = total_armor);
	return true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
	Armor::ApplyZoneBasedArmor(int armor_zone, Stuff::Scalar armor_amount)
{
	Check_Object(this);

	MWObject *parent = GetParentVehicle();
	Check_Object(parent);

	DamageObject *damage_object;
	damage_object = parent->damageObjects.Find(armor_zone);

	if(!damage_object)
		return false;

	//these checks are needed so we know when to add tonnage
	if(((m_armorZoneValues[armor_zone] + armor_amount * m_armorMultiplier) - damage_object->maxArmorValue) > SMALL)
	{
		Max_Clamp(m_armorZoneValues[armor_zone], damage_object->maxArmorValue);
		return false;
	}

	if((m_armorZoneValues[armor_zone] + (armor_amount * m_armorMultiplier)) < 0.0f)
	{
		Min_Clamp(m_armorZoneValues[armor_zone], 1.0f);
		return false;
	}
	
	if(GetParentVehicle()->DoesHaveAvailableTonage(armor_amount))
	{
		m_armorZoneValues[armor_zone] += (armor_amount * m_armorMultiplier);
		Clamp(m_armorZoneValues[armor_zone], 0.0f, damage_object->maxArmorValue);
		m_totalTonage += armor_amount;
		GetParentVehicle()->AddTonage(armor_amount);
		return true;
	}

	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
	Armor::SetZoneBasedArmor(int armor_zone, Stuff::Scalar armor_amount)
{
	Check_Object(this);

	Scalar amount_to_add = armor_amount - (m_armorZoneValues[armor_zone] / m_armorMultiplier);
	return ApplyZoneBasedArmor(armor_zone, amount_to_add);
}
	
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	Armor::SetArmorType(int new_type)
{
	Check_Object(this);
	const GameModel *model = GetGameModel();
	Check_Object(model);

	//The first thing that we need to do is to get all the values in tonnages so
	//we can manipulate all the values with the armor change.

	Scalar ratio = (Scalar)m_armorMultiplier;
	
	switch(new_type)
	{
// MSL 5.00 Armor
		case StandardArmor: 
			m_armorMultiplier = model->m_pointsPerStandardTon;
			break;
		case FerroFiberusArmor: 
			m_armorMultiplier = model->m_pointsPerFerroTon;
			break;
		case ReactiveArmor:
			m_armorMultiplier = model->m_pointsPerReactiveTon;
			break;
		case ReflectiveArmor:
			m_armorMultiplier = model->m_pointsPerReflectiveTon;
			break;
		case SolarianArmor:
			m_armorMultiplier = model->m_pointsPerSolarianTon;
			break;
		default:
			return -1;
	}

	//i.e. if old was 20 per ton and new is 16 per ton....we want 16/20
	ratio = m_armorMultiplier / ratio;

	for(int i=DamageObject::LeftLegArmorZone; i<=DamageObject::HeadArmorZone; i++)
	{
		m_armorZoneValues[i] *= ratio;
	}

	if(ratio > 1.0f)
	{
		//This means that we may have over stepped our points bounds!  We need to run through
		//And check to make sure that this is not true
		for(i=DamageObject::LeftLegArmorZone; i<=DamageObject::HeadArmorZone; i++)
		{
			DamageObject *damage_object;
			damage_object = GetParentVehicle()->damageObjects.Find(i);
			Check_Object(damage_object);

			if(m_armorZoneValues[i] > damage_object->maxArmorValue)
			{
				//We have surpased our max and must clamp and release some tonnage!
				Scalar overflow = m_armorZoneValues[i] - damage_object->maxArmorValue;
				Scalar tonnage_overflow = overflow / m_armorMultiplier;
				Max_Clamp(m_armorZoneValues[i], damage_object->maxArmorValue);
				GetParentVehicle()->AddTonage(-tonnage_overflow);
			}
		}
	}

	m_armorType = new_type;
	return 1;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Armor::CreateStream(ResourceID data_list, MemoryStream *stream)
{
	Check_Pointer(stream);

	stream->AllocateBytes(sizeof(Armor__CreateMessage));
	Subsystem::CreateStream(data_list, stream);

	Armor__CreateMessage *create_message = Cast_Pointer(
		Armor__CreateMessage *, stream->GetPointer());

	create_message->m_leftLegArmor = 0.0f;
	create_message->m_rightLegArmor = 0.0f;
	create_message->m_leftArmArmor = 0.0f;
	create_message->m_rightArmArmor = 0.0f;
	create_message->m_leftFrontTorsoArmor = 0.0f;
	create_message->m_rightFrontTorsoArmor = 0.0f;
	create_message->m_centerFrontTorsoArmor = 0.0f;
	create_message->m_centerRearTorsoArmor = 0.0f;
	create_message->m_headArmor = 0.0f;

// MSL 5.00 Armor
//	create_message->m_armorType = FerroFiberusArmor;
	create_message->m_armorType = StandardArmor;
	create_message->m_internalType = StandardInternal;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Armor::TestInstance() const
{
	Verify(IsDerivedFrom(DefaultData));
}

