//===========================================================================//
// File:	BeamWeapon.cpp										
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
// 09/09/98  JSE  Inital base class based off of Shadowrun/Adept			 //
// 09/24/98  BDB  Inital Weapon subsytem class based off of Subsystem		 //
// 10/19/98  BDB  Created LauchedWeapon subsytem based off of Weapon		 //
//---------------------------------------------------------------------------//
// Copyright (C) 1998, Fasa Interactive										 //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//

#include "MW4Headers.hpp"

#include "BeamWeapon.hpp"
#include "MWMover.hpp"
#include "BeamEntity.hpp"
#include "GUIWeaponManager.hpp"
#include "SubsystemClassData.hpp"

#include <Adept\Map.hpp>
#include <Adept\Site.hpp>
#include <Adept\EntityManager.hpp>
#include <adept\damageobject.hpp>

#include <MLR\MLRTexturePool.hpp>


// ngLog addition
#include "mech.hpp"
#if !defined(NO_LOG)
#include "nglog_mw4.hpp"
#endif // !defined(NO_LOG)
#include "MWApplication.hpp"


//#############################################################################
//##########################    ExecutionStateEngine    #######################
//#############################################################################

BeamWeapon::ExecutionStateEngine::ClassData*
	BeamWeapon::ExecutionStateEngine::DefaultData = NULL;

const StateEngine::StateEntry
	BeamWeapon::ExecutionStateEngine::StateEntries[]=
{
	STATE_ENTRY(BeamWeapon__ExecutionStateEngine, Firing),
	STATE_ENTRY(BeamWeapon__ExecutionStateEngine, Recharging)
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BeamWeapon::ExecutionStateEngine::InitializeClass()
{
	Check_Object(StateEngine::DefaultData);
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			BeamWeapon__ExecutionStateEngineClassID,
			"BeamWeapon::ExecutionStateEngine",
			BaseClass::DefaultData,
			ELEMENTS(StateEntries), StateEntries,
			(Entity::ExecutionStateEngine::Factory)Make,
			(Entity::ExecutionStateEngine::FactoryRequest::Factory)
				&FactoryRequest::ConstructFactoryRequest
		);
	Register_Object(DefaultData);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BeamWeapon::ExecutionStateEngine::TerminateClass()
{
	Unregister_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BeamWeapon::ExecutionStateEngine*
	BeamWeapon::ExecutionStateEngine::Make(
		BeamWeapon *weapon,
		FactoryRequest *request
	)
{
	Check_Object(weapon);
	Check_Object(request);
	gos_PushCurrentHeap(Heap);
	BeamWeapon::ExecutionStateEngine *engine =
		new BeamWeapon::ExecutionStateEngine(DefaultData, weapon, request);
	gos_PopCurrentHeap();

	return engine;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	BeamWeapon::ExecutionStateEngine::RequestState(
		int new_state,
		void* data
	)
{
	Check_Object(this);
	Check_Object(owningEntity);

	//
	//------------------------------
	// Quick exit if no state change
	//------------------------------
	//
	if (new_state == currentState)
		return currentState;

	//
	//-------------------------------------------------------
	// If we are currently reloading, ignore a firing request
	//-------------------------------------------------------
	//
	if(new_state == FiringState && currentState == RechargingState)
		return currentState;

	//
	//---------------------
	// Deal with our states
	//---------------------
	//
	BeamWeapon *beam;
	beam = Cast_Object(BeamWeapon *, owningEntity);
	switch (BaseClass::RequestState(new_state, data))
	{
	case FiringState:
		{
//			SPEW(("jackyc", "FIRING WEAPON - Weapon %d", beam->weaponID));

			//We need to set up new target info here!
			
			beam->UsePostCollision();
			Check_Object(EntityManager::GetInstance());
			EntityManager::GetInstance()->RequestPostCollisionExecution(beam);

			// Start the sound
			beam->beamStartSFX = 1;
			beam->lastParameterization = gos_GetElapsedTime();
			if(beam->guiWeapon.GetCurrent())
				beam->guiWeapon.GetCurrent()->Reload();
			beam->ApplyHeat();
		}
		break;
	case NeverExecuteState:
	case DestroyedState:
		break;
	default:
		{
			owningEntity->IgnorePostCollision();
			break;
		}
	}
	return currentState;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BeamWeapon::ExecutionStateEngine::TestInstance() const
{
	Verify(IsDerivedFrom(DefaultData));
}

//#############################################################################
//###############################    Weapon    ################################
//#############################################################################

BeamWeapon::ClassData*
	BeamWeapon::DefaultData = NULL;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BeamWeapon::InitializeClass()
{
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			BeamWeaponClassID,
			"MechWarrior4::BeamWeapon",
			BaseClass::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);

	INDIRECT_STATE_ATTRIBUTE(
		BeamWeapon,
		ExecutionState,
		executionState,
		BeamWeapon__ExecutionStateEngine
	);

	CUSTOM_DIRECT_ATTRIBUTE(
		BeamWeapon,
		BeamDistance,
		beamDistance,
		Scalar,
		ScalarClassID
	);

	CUSTOM_DIRECT_ATTRIBUTE(
		BeamWeapon,
		BeamStartSFX,
		beamStartSFX,
		int,
		IntClassID
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		BeamWeapon__GameModel,
		FireDuration,
		fireDuration,
		Scalar
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		BeamWeapon__GameModel,
		BeamModelResource,
		beamModelResource,
		ResourceID
	);
}

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

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


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BeamWeapon::BeamWeapon(
	ClassData *class_data,
	CreateMessage *message,
	ReplicatorID *base_id,
	ElementRenderer::Element *element
):
	Weapon(class_data, message, base_id, element),
	targetQuery(NULL, NULL, CanBeShotFlag, NULL),
	beamEntity(NULL)
{
	Check_Pointer(this);
	Check_Object(message);

	targetQuery.m_line = &collisionLine;
	targetQuery.m_normal = &collisionNormal;

	CommonCreation(message);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BeamWeapon::CommonCreation(CreateMessage *message)
{
	Check_Object(this);
	Check_Object(message);
	
	fireRequest = false;
	accumlatedRechargeTime	= 0.0f;
	accumlatedDuration		= 0.0f;
	beamDistance = 0.01f;
	beamStartSFX = 0;

	beamEntity.Remove();

	const GameModel *model = GetGameModel();
	beamRechargeTime = model->reloadTime;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BeamWeapon::Respawn(Entity::CreateMessage *message)
{
	Check_Object(this);
	Check_Object(message);

	BaseClass::Respawn(message);
	CreateMessage *beam_message = Cast_Pointer(CreateMessage *, message);
	CommonCreation(beam_message);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BeamWeapon::~BeamWeapon()
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void BeamWeapon::PreCollisionExecute(Time till)
{
	const GameModel* model = GetGameModel();
	PRECOLLISION_LOGIC("Subsystem::Beam");

	Check_Object(executionState);
	int pre_state = executionState->GetState();

	switch (pre_state)
	{
		case ExecutionStateEngine::FiringState:
			{
				UsePostCollision();
				if ((GetTimeParameter(till) >= model->fireDuration) &&
					(beamEntity.GetCurrent()))
				{
			//	if(beamEntity.GetCurrent())
			//	{
					Check_Object(beamEntity.GetCurrent());
			//		if(!beamEntity.GetCurrent()->beamStatus)
			//		{
						executionState->RequestState(ExecutionStateEngine::RechargingState);
						m_PlayedRechargeSound = false;
						lastParameterization = gos_GetElapsedTime();
						Check_Object(beamEntity.GetCurrent());
						beamEntity.GetCurrent()->SentenceToDeathRow();
			//		}
				}
				break;
			}

		case ExecutionStateEngine::RechargingState:
			{
				IgnorePostCollision();
				beamEntity.Remove();

				Scalar count = beamRechargeTime - GetTimeParameter(till);
				Min_Clamp(count, 0.0f);
				SetCount(count);

				if (beamRechargeTime > 0.49f)
				{
					if ((!m_PlayedRechargeSound) && (GetTimeParameter(till) >= beamRechargeTime-0.5f))
					{
						if (GetParentVehicle ())
							if (GetParentVehicle()->vehicleInterface)
								GetParentVehicle ()->vehicleInterface->ReactToEvent (VehicleInterface::RECHARGE_BEAM);
						m_PlayedRechargeSound = true;
					}
				}
				if (GetTimeParameter(till) >= beamRechargeTime)
				{
					executionState->RequestState(ExecutionStateEngine::NeverExecuteState);
					if(guiWeapon.GetCurrent())
						guiWeapon.GetCurrent()->Ready();
				}
				break;
			}
	}
	BaseClass::PreCollisionExecute(till);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BeamWeapon::PostCollisionExecute(Time till)
{
	Check_Object(this);
	POSTCOLLISION_LOGIC("Subsystem::Beam");

	if(!beamEntity.GetCurrent())
	{
		CreateBeam();
		CreateLightAmpFlareOut();
		CreateMuzzleFlash();
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void BeamWeapon::CreateBeam()
{
	const GameModel *model = GetGameModel();
	Check_Object(model);


	ClassID class_ID;
	class_ID = GetClassIDFromDataListID(model->beamModelResource);
	Verify(class_ID != NullClassID);

	Point3D target_point = Point3D::Identity;
	ReplicatorID target_id = ReplicatorID::Null;
	Normal3D target_normal(1.0f, 0.0f, 0.0f);

	Check_Object(sitePointer);
	const char *site_name = sitePointer->GetName();
	ReplicatorID inflicting_id = ReplicatorID::Null;
	inflicting_id = GetParentVehicle()->GetReplicatorID();
	
	if(targetQuery.m_line->m_length > model->maxDistance)
	{
//		SPEWALWAYS(("jackyc", "Create Beam - Length too long.  NULL target"));

		targetQuery.m_line->m_length = model->maxDistance;
		targetQuery.m_line->FindEnd(&target_point);
		targetQuery.m_raySource = NULL;
	}
	else
	{
		if(targetQuery.m_raySource)
		{
//			SPEWALWAYS(("jackyc", "Create Beam - Using normal m_raySource target"));

			Check_Object(targetQuery.m_raySource);
			target_id = targetQuery.m_raySource->GetReplicatorID();
			targetQuery.m_line->FindEnd(&target_point);
			target_normal = *targetQuery.m_normal;
		}
		else
		{
//			SPEWALWAYS(("jackyc", "Create Beam - NULL m_raySource target"));

			targetQuery.m_line->FindEnd(&target_point);
		}
	}

	BeamEntity::CreateMessage beam_create_message(
		sizeof(BeamEntity::CreateMessage),
		DefaultEventPriority,
		CreateMessage::DefaultFlags,
		class_ID,
		BeamEntity::DefaultFlags,
		model->beamModelResource,
		sitePointer->GetLocalToWorld(),
		0.0f,
		ExecutionStateEngine::AlwaysExecuteState,
		NameTable::NullObjectID,
		Entity::DefaultAlignment,
		inflicting_id,
		site_name,
		target_point,
		target_id,
		target_normal,
		model->damageAmount,
		model->heatToDeal,
		targetQuery.m_material,
		model->maxDistance,
// MSL 5.03 Min Distance
//		model->minDistance,
		model->itemID
	);
	MemoryStream stream(&beam_create_message, beam_create_message.messageLength);

	Entity *entity;
	ReplicatorID beam_id = Connection::Hermit->GetNextReplicatorID();
	entity = CreateEntity(&stream, &beam_id, false);
	Check_Object(entity);
	Verify(entity->IsDerivedFrom(BeamEntity::DefaultData));
	beamEntity.Add(Cast_Object(BeamEntity *, entity));
	Check_Object(Map::GetInstance());
	Map::GetInstance()->AddChild(beamEntity.GetCurrent());
}
		
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void BeamWeapon::QuickFire (Adept::Entity *target)
{
	const GameModel *model = GetGameModel();
	Check_Object(model);
	Check_Object(this);

	executionState->RequestState(ExecutionStateEngine::RechargingState);
	lastParameterization = gos_GetElapsedTime();

	if (target == 0)
	{
		return;
	}

	Check_Object(target);
	ReplicatorID inflicting_id = ReplicatorID::Null;
	inflicting_id = GetParentVehicle()->GetReplicatorID();

// MSL 5.06 Armor Mode
	MWApplication *m_App;
	m_App = MWApplication::GetInstance ();
	m_ArmorMode = m_App->GetLocalNetParams()->m_armormodeOn;

	Entity::TakeDamageMessage damage_message(
		target->GetReplicatorID(),
		inflicting_id,
		model->damageAmount,
		m_ArmorMode,
		BeamDamageType,
		Stuff::UnitVector3D(1,0,0),
		Point3D (-1,-1,-1),
		model->heatToDeal,
		true,
		model->itemID
	);
	
	target->Receive(&damage_message);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void BeamWeapon::Fire(CollisionQuery *query,
					Time time_locked,
					const Stuff::Point3D& lockedTargetEntityPartOffset)
{
	//copy the query

	targetQuery.m_collisionMask = query->m_collisionMask;
	targetQuery.m_material = query->m_material;
	targetQuery.m_raySource = query->m_raySource;

	collisionLine = *query->m_line;
	collisionNormal = *query->m_normal;
	

	// ngLog addition
	if(ReadyToFire()) {
#if !defined(NO_LOG)
		MWObject *mobj = GetParentVehicle();
		if(mobj && mobj->IsDerivedFrom(Mech::DefaultData)) {
			Mech *mech = Cast_Object(Mech *, mobj);
		
			if(mech && mech->IsLanceMate()) {
				int p_id = mech->lancemateIndex;

				MWApplication::GetInstance()->lancemateConnectionData[p_id].wep_atts[GetGameModel()->itemID] += GetGameModel()->ammoPerShot;
			} else {
				int p_id = GetParentVehicle()->GetReplicatorID().connectionID;

				MWApplication::GetInstance()->servedConnectionData[p_id].wep_atts[GetGameModel()->itemID] += GetGameModel()->ammoPerShot;
			}
		}
#endif // !defined(NO_LOG)

#if !defined(NO_MR)
		if(GetParentVehicle()->IsPlayerVehicle()) {
			MWApplication::GetInstance()->SetMissionReviewWeaponFired(GetParentVehicle()->GetReplicatorID().connectionID);
		}
#endif // !defined(NO_MR)
	}


	executionState->RequestState(ExecutionStateEngine::FiringState, &targetQuery);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool BeamWeapon::ReadyToFire()
{

	// if this is the local mech, always true in single player.
	if (GetReplicatorID().connectionID == Connection::Local->GetID())
	{
		if (CanFireFromCurrentArm() == false)
		{
			return (false);
		}

		return (executionState->GetState()==ExecutionStateEngine::NeverExecuteState);
	}


//		SPEWALWAYS(("jackyc", "SERVER LAST RECHARGE START TIME - %f", lastParameterization));
//		SPEWALWAYS(("jackyc", "SERVER CURRENT TIME - %f", gos_GetElapsedTime()));	
//		SPEWALWAYS(("jackyc", "SERVER RECHARGE TIME - %f", (gos_GetElapsedTime() - lastParameterization)));
//		SPEWALWAYS(("jackyc", "FULL RECHARGE TIME - %f", beamRechargeTime));
//		SPEWALWAYS(("jackyc", "RECHARGE EPSILON - %f", ((0.1f * beamRechargeTime) + (packet_time_diff * 0.001f))));

	if (executionState->GetState()==ExecutionStateEngine::RechargingState)
	{
		if (gos_GetElapsedTime() - lastParameterization <= 0.1f)
		{
//			SPEWALWAYS(("jackyc", "RESETTING Recharge"));
			executionState->RequestState(ExecutionStateEngine::NeverExecuteState);
		}

		return true;
	}


	return (executionState->GetState()==ExecutionStateEngine::NeverExecuteState);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BeamWeapon::CleanUpCreatedBeam()
{
	Check_Object(this);
	if(beamEntity.GetCurrent())
		beamEntity.GetCurrent()->SentenceToDeathRow();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BeamWeapon::DestroySubsystem()
{
	Check_Object(this);
	
	EnterNeverExecuteState();
	IgnorePostCollision();
	CleanUpCreatedBeam();
	BaseClass::DestroySubsystem();
}

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

