//===========================================================================//
// File:	JumpJet.cpp										
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
// 05/18/99  DPB  Created File
//---------------------------------------------------------------------------//
// Copyright (C) 1998, Fasa Interactive										 //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//

#include "MW4Headers.hpp"

#include "JumpJet.hpp"
#include "Mech.hpp"
#include "VehicleInterface.hpp"
#include "SubsystemClassData.hpp"

#include <Adept\Effect.hpp>
#include <Adept\Map.hpp>
#include <MLR\MLRTexturePool.hpp>
#include <Adept\Site.hpp>
#include "ai_userconstants.hpp"

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

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

const StateEngine::StateEntry
	JumpJet::ExecutionStateEngine::StateEntries[]=
{
	STATE_ENTRY(JumpJet__ExecutionStateEngine, Jumping),
	STATE_ENTRY(JumpJet__ExecutionStateEngine, Recharging)
};

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


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void MechWarrior4::JumpJetSecurityCheckStart()
{
	_asm
	{
		nop
	}	
}



//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	JumpJet::ExecutionStateEngine::RequestState(
		int new_state,
		void* data
	)
{
	Check_Object(this);
	Check_Object(owningEntity);

	//
	//-------------------------------------------
	// If there is no change of state just return
	//-------------------------------------------
	//
	if (new_state == currentState)
		return currentState;

	//
	//---------------------------------------------------
	// If we have less than one second charge, don't jump
	//---------------------------------------------------
	//
	JumpJet *jump_jet;
	jump_jet = Cast_Object(JumpJet*, owningEntity);
	if (new_state == JumpingState && DECRYPT(jump_jet->m_currentEncryptedCharge) < 0.25f)
		return currentState;

	if(new_state == DestroyedState)
		return currentState;

	//
	//--------------------------------------------------------------------
	// Look for the transition type.  Our base class should handle destroy
	// and never execute commands, but make sure that we only go into
	// zero-g state from jumping state
	//--------------------------------------------------------------------
	//
	switch (BaseClass::RequestState(new_state, data))
	{
	case JumpingState:
		{
			jump_jet->UsePostCollision();
			jump_jet->lastExecuted = gos_GetElapsedTime();
			jump_jet->m_needToCreateEffects = true;

			//
			//----------------------------------------------------------------
			// If the mech is walking right now, we launch the initial effects
			//----------------------------------------------------------------
			//
			Check_Object(jump_jet->m_parentMech);
			Mech::ExecutionStateEngine *engine =
				Cast_Object(Mech::ExecutionStateEngine *, jump_jet->m_parentMech->executionState);
			Check_Object(engine);
			if (engine->GetState() == engine->DrivingMotionState)
				jump_jet->m_needToCreateInitialEffects = true;
			jump_jet->lastParameterization = gos_GetElapsedTime();
		}
		break;

	case NeverExecuteState:
		if (oldState != RechargingState)
			jump_jet->CleanUpEffects();
		jump_jet->m_needToCreateEffects = false;
		jump_jet->m_needToCreateInitialEffects = false;
		jump_jet->lastParameterization = gos_GetElapsedTime();
		break;

	case RechargingState:
		if (oldState != NeverExecuteState)
			jump_jet->CleanUpEffects();
		jump_jet->m_needToCreateEffects = false;
		jump_jet->m_needToCreateInitialEffects = false;
		jump_jet->lastParameterization = gos_GetElapsedTime();
		break;

	//
	//------------------------------------------------------------------
	// Our base class has handled destruction, so don't do anything more
	//------------------------------------------------------------------
	//
	case DestroyedState:
		break;

	default:
		STOP(("Shouldn't be here"));
		break;
	};

	return currentState;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	JumpJet::ExecutionStateEngine::TestInstance() const
{
	Verify(IsDerivedFrom(DefaultData));
}

//#############################################################################
//###############################    JumpJet    ###############################
//#############################################################################

JumpJet::ClassData*
	JumpJet::DefaultData = NULL;

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



	DIRECT_GAME_MODEL_ATTRIBUTE(
		JumpJet__GameModel,
		RechargeTime,
		m_rechargeTime,
		Scalar
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		JumpJet__GameModel,
		BurnTime,
		m_burnTime,
		Scalar,
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		JumpJet__GameModel,
		FlightTime,
		m_flightTime,
		Scalar,
	);

	DIRECT_GAME_MODEL_ATTRIBUTE(
		JumpJet__GameModel,
		SlipDistance,
		m_slipDistance,
		Scalar,
	);
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

//moved it so it won't be in the same spot in the new executable


void JumpJet::PreCollisionExecute(Time till)
{
	Check_Object(this);
	Check_Object(m_parentMech);
	PRECOLLISION_LOGIC("Subsystem::JumpJet");


	Stuff::Scalar current_charge = DECRYPT(m_currentEncryptedCharge);
	//
	//---------------------
	// Figure out our state
	//---------------------
	//
	Scalar time_slice = GetTimeSlice(till);
	const GameModel* model = GetGameModel();
	Check_Object(model);
	Check_Object(executionState);
	int pre_state = executionState->GetState();
	Verify(pre_state != ExecutionStateEngine::NeverExecuteState);

	//
	//---------------------------------------------------------------------
	// If we are jumping, deal with applying the force if we are not out of
	// power
	//---------------------------------------------------------------------
	//
	switch (pre_state)
	{
	case ExecutionStateEngine::JumpingState:
		
		// only the master is allowed to do decrement the charge
//		if (!m_EffectOnly)
//		{

			// everyone is allowed to decrement the charge...
			current_charge -= time_slice;
			SetCurrentCharge(current_charge);
	
			if (current_charge <= 0.0f)
			{
				current_charge = 0.0f;
				SetCurrentCharge(current_charge);
				executionState->RequestState(ExecutionStateEngine::RechargingState);
			}
			else
			{
				Vector3D &accel = m_parentMech->localSpaceAcceleration.linearMotion;
				Vector3D &velocity = m_parentMech->localSpaceVelocity.linearMotion;
				if (velocity.z > 0.0f && m_parentMech->speedDemand <= 0.0f)
					accel.z += m_parentMech->speedDemand*m_slide;
				else
					accel.y += m_acceleration;
				if (MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::flying_turn_rate) == 0.0f)
					accel.x += m_parentMech->yawDemand * m_slide;
			}
//		}
		break;

	//
	//-----------------
	// Start recharging
	//-----------------
	//
	case ExecutionStateEngine::RechargingState:

		// but only the master/server is allowed to increment the jump charge
//		if (GetReplicatorMode() == ServerMasterMode || GetReplicatorMode() == MasterMode)
//		{
			Verify(!m_leftJumpJetEffect.GetCurrent());
			Verify(!m_rightJumpJetEffect.GetCurrent());
			current_charge += time_slice * m_rechargeRate;
			Max_Clamp(current_charge, model->m_burnTime);
			SetCurrentCharge(current_charge);
//		}

		if(current_charge == model->m_burnTime)
			executionState->RequestState(ExecutionStateEngine::NeverExecuteState);

		break;
	}



	SPEWALWAYS(("jerryeds", "JUMP FUEL: %f", current_charge));

	
	
	//
	//------------------------------------
	// Let our parent deal with things now
	//------------------------------------
	//
	BaseClass::PreCollisionExecute(till);
}


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

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
JumpJet::JumpJet(
	ClassData *class_data,
	CreateMessage *message,
	ReplicatorID *base_id,
	ElementRenderer::Element *element
):
	Subsystem(class_data, message, base_id, element),
	m_rightJumpJetEffect(NULL),
	m_leftJumpJetEffect(NULL),
	m_initialRightJumpJetEffect(NULL),
	m_initialLeftJumpJetEffect(NULL)
	
{
	Check_Pointer(this);
	Check_Object(message);

	CommonCreation(message);

	m_jumpJetEffectResource = ResourceID::Null;
	m_initialJumpJetEffectResource = ResourceID::Null;
	m_leftJumpJetSite = NULL;
	m_rightJumpJetSite = NULL;
	m_parentMech = NULL;
}

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

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	JumpJet::CommonCreation(CreateMessage *message)
{
	Check_Object(this);
	Check_Object(message);

	const GameModel *model = GetGameModel();
	Check_Object(model);
	
	m_needToCreateEffects = false;
	m_needToCreateInitialEffects = false;
	m_currentEncryptedCharge = ENCRYPT(model->m_burnTime);

	Verify(model->m_burnTime > 0);
	Verify(model->m_rechargeTime > 0);
	m_rechargeRate = model->m_burnTime / model->m_rechargeTime;

	//
	//------------------------------------------------------------
	// Now calculate the acceleration needed to give these numbers
	//------------------------------------------------------------
	//
	Scalar temp = model->m_flightTime - model->m_burnTime;
	Verify(temp > 0.0f);
	m_acceleration = g_Gravity*temp*temp / (2.0f*model->m_flightTime - model->m_burnTime);
	Verify(m_acceleration > g_Gravity);

	//
	//---------------------------------
	// Figure out our side acceleration
	//---------------------------------
	//
	Verify(model->m_flightTime >= 0.0f);
	m_slide = model->m_slipDistance / (model->m_burnTime*(model->m_flightTime - 0.5f*model->m_burnTime));
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
JumpJet::~JumpJet()
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Stuff::Scalar
	JumpJet::CalcBurnTime(
		Stuff::Scalar distance,
		Stuff::Scalar heightdelta,
		Stuff::Scalar forwardvel
	)
{
	Stuff::Scalar burn,upvel,downvel;
	Stuff::Scalar num,denom,ds;

	if (forwardvel == 0.0f)
		return FLT_MAX;
	ds = distance / forwardvel;
	upvel = m_acceleration;
	downvel = g_Gravity;
	num = ((upvel+downvel) * ((upvel*ds*ds) - (2.0f*heightdelta)));
	denom = upvel+downvel;
	if (num <= SMALL)
		return FLT_MAX;
	num = Sqrt(num);
	Verify (denom != 0.0f);
	burn = ds - (num/denom);
	return burn;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void MechWarrior4::JumpJetSecurityCheckStop()
{
	_asm
	{
		nop
	}	
}


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

	if(m_needToCreateInitialEffects)
	{
		CreateInitialJumpEffects();
		m_needToCreateInitialEffects = false;
	}

	if(m_needToCreateEffects)
	{
		CreateJumpJetEffects();
		m_needToCreateEffects = false;
	}

	IgnorePostCollision();
	BaseClass::PostCollisionExecute(till);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	JumpJet::CreateJumpJetEffects()
{
	Check_Object(this);
	Check_Object(m_parentMech);
	Check_Object(m_leftJumpJetSite);
	Check_Object(m_rightJumpJetSite);

	Verify(executionState->GetState() == ExecutionStateEngine::JumpingState);

	Effect *jump_jet_effect;
	if(m_leftJumpJetEffect.GetCurrent() == NULL)
	{
		jump_jet_effect = m_parentMech->CreateLoopingEffect(
			m_jumpJetEffectResource,
			m_leftJumpJetSite,
			true
		);
		Check_Object(jump_jet_effect);
		jump_jet_effect->BecomeInteresting (true);
		jump_jet_effect->executionState->RequestState(Effect::ExecutionStateEngine::RunningState);
		m_leftJumpJetEffect.Add(jump_jet_effect);
	}
	if(m_rightJumpJetEffect.GetCurrent() == NULL)
	{
		jump_jet_effect = m_parentMech->CreateLoopingEffect(
			m_jumpJetEffectResource,
			m_rightJumpJetSite,
			true
		);
		Check_Object(jump_jet_effect);
		jump_jet_effect->BecomeInteresting (true);
		jump_jet_effect->executionState->RequestState(Effect::ExecutionStateEngine::RunningState);
		m_rightJumpJetEffect.Add(jump_jet_effect);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	JumpJet::CreateInitialJumpEffects()
{
	Check_Object(this);
	Check_Object(m_parentMech);
	Check_Object(m_leftJumpJetSite);
	Check_Object(m_rightJumpJetSite);

	Verify(executionState->GetState() == ExecutionStateEngine::JumpingState);

	Effect *jump_jet_effect;
	if(m_initialLeftJumpJetEffect.GetCurrent() == NULL)
	{
		jump_jet_effect = m_parentMech->CreateEffect(
			m_initialJumpJetEffectResource,
			m_leftJumpJetSite,
			true
		);
		if(jump_jet_effect)
			m_initialLeftJumpJetEffect.Add(jump_jet_effect);
	}
	if(m_initialRightJumpJetEffect.GetCurrent() == NULL)
	{
		jump_jet_effect = m_parentMech->CreateEffect(
			m_initialJumpJetEffectResource,
			m_rightJumpJetSite,
			true
		);
		if(jump_jet_effect)
			m_initialRightJumpJetEffect.Add(jump_jet_effect);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
	JumpJet::ConnectSubsystem()
{
	Check_Object(this);
	Verify(GetParentVehicle()->IsDerivedFrom(Mech::DefaultData));

	m_parentMech = Cast_Pointer(Mech*, GetParentVehicle());
	const Mech::GameModel *mech_model = m_parentMech->GetGameModel();
	Check_Object(mech_model);

	bool return_value = false;
	if(m_parentMech->DoesHaveAvailableTonage(mech_model->m_jumpJetTonnage))
		return_value = BaseClass::ConnectSubsystem();

	if(return_value)
		m_parentMech->AddTonage(mech_model->m_jumpJetTonnage);

	return return_value;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//
bool
	JumpJet::DisconnectSubsystem()
{
	Check_Object(this);
	Verify(GetParentVehicle()->IsDerivedFrom(Mech::DefaultData));
	
	m_parentMech = Cast_Pointer(Mech*, GetParentVehicle());
	const Mech::GameModel *mech_model = m_parentMech->GetGameModel();
	Check_Object(mech_model);

	bool return_value = false;
	return_value = BaseClass::DisconnectSubsystem();

	if(return_value)
		m_parentMech->SubtractTonage(mech_model->m_jumpJetTonnage);

	return return_value;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
	JumpJet::TakeCriticalHit()
{
	Check_Object(this);

	//Don't do anything here
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	JumpJet::DestroySubsystem()
{
	Check_Object(this);

#if 0
	BaseClass::DestroySubsystem();
	const Mech::GameModel *mech_model = m_parentMech->GetGameModel();
	Check_Object(mech_model);
	m_parentMech->SubtractTonage(mech_model->m_jumpJetTonnage);
	EnterNeverExecuteState();
	IgnorePostCollision();
	CleanUpEffects();
#endif
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	JumpJet::CleanUpEffects()
{
	Check_Object(this);

	if(m_leftJumpJetEffect.GetCurrent())
	{
		m_leftJumpJetEffect.GetCurrent()->executionState->RequestState(
			Effect::ExecutionStateEngine::StoppingState);
		m_leftJumpJetEffect.Remove();
	}
	if(m_rightJumpJetEffect.GetCurrent())
	{
		m_rightJumpJetEffect.GetCurrent()->executionState->RequestState(
			Effect::ExecutionStateEngine::StoppingState);
		m_rightJumpJetEffect.Remove();
	}
	
}

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

