// m_heliactions.cpp

#include "g_local.h"
#include "ai_private.h"
#include "m_heliai.h"
//#include "m_heli.h"
 
//#include <windows.h>	// for OutputDebugString() only, do not leave in!


/**********************************************************************************
 **********************************************************************************/
heli_action::heli_action(decision_c *od, action_c *oa, mmove_t *newanim,
						 int nCommand, vec3_t vPos, edict_t* target, float fArg)
:action_c(od, oa, newanim, vPos, vPos, NULL, 999999, true)
{
	m_nCommand = nCommand;
	action_target = target;
	m_fArg = fArg;
	m_think = NULL;
	m_nextthink = NULL;
	m_nID = -1;
}

qboolean heli_action::Think(ai_c &which_ai, edict_t &monster)
{
	float fHealthPercentage = 1.0f;
	generic_ghoul_heli_ai* ai = (generic_ghoul_heli_ai*)(ai_public_c*)&which_ai;
	body_heli* body = NULL;

	if (ai)
	{
		body = ai->GetHeliBody();
	}
	if (!body)
	{
		return false;
	}
	qboolean bRet = true;
	if (m_think)
	{
		// set our script params in the body's member variables
		body->m_fArg = m_fArg;
		VectorCopy(dest, body->m_vPos);
		body->m_target = action_target;

		// this past think may have updated our next think
		if (m_nextthink && m_nextthink != m_think)
		{
			m_think = m_nextthink;
			m_nextthink = NULL;
		}

		m_think(&monster);

		fHealthPercentage = (float)monster.health/(float)monster.max_health;
		body->UpdateSmoke(&monster, fHealthPercentage);

		// kef -- dunno if I like this. probly a cleaner way...
		bRet = ((generic_ghoul_heli_ai*)&which_ai)->IsActionDone();
	}

	// check our need for creating a pseudo-action if our current action 
	//completed or if we just reached zer0 health
	if (bRet ||
		((body->m_flags & HELIFLAG_ISDEAD) && !(body->m_flags & HELIFLAG_ISDEADACKNOWLEDGED)))
	{
		// going to emulate Ste's CobraThink_Think here so we can "insert"
		//pseudo-actions into the action queue

		// if it's on the pad, explode
		if (((body->m_flags & HELIFLAG_ISDEAD) && !(body->m_flags & HELIFLAG_ISDEADACKNOWLEDGED)) &&
			fabs(monster.s.origin[0]) - fabs(body->m_v3HeliPadRestPos[0]) < 0.1f &&
			fabs(monster.s.origin[1]) - fabs(body->m_v3HeliPadRestPos[1]) < 0.1f &&
			fabs(monster.s.origin[2]) - fabs(body->m_v3HeliPadRestPos[2]) < 0.1f )
		{
			ai->TimeToDie(true);
			ResolveAction(which_ai, monster, 1.0, 1.0);
		}
		else if(InsertPseudoAction(body, &monster))
		{
			// inserted a pseudo-action, so we want the heli ai to believe
			//we're still executing the same action
			bRet = false;
		}
		else
		{
			ResolveAction(which_ai, monster, 1.0, 1.0);
		}

		// just finished an action so reset our variable
		((generic_ghoul_heli_ai*)&which_ai)->ActionIsDone(false);
	}
	return bRet;
}


// overriding this fn is a quick way of keeping action_c from screwing with
//our heli's sequence
qboolean heli_action::SetAnimation(ai_c &which_ai, edict_t &monster)
{
	//just bein safe
	if (!which_ai.GetBody())
	{
		gi.dprintf("heli_action::SetAnimation--ai has no body!\n");
		return false;
	}
	return true;
}

// overriding this fn is a quick way of keeping action_c from screwing with
//our heli's sequence
qboolean heli_action::ForceAnimation(ai_c &which_ai, edict_t &monster)
{
	//just bein safe
	if (!which_ai.GetBody())
	{
		gi.dprintf("heli_action::ForceAnimation--ai has no body!\n");
		return false;
	}
	return true;
}

qboolean heli_action::InsertPseudoAction(body_heli* body, edict_t* self)
{
	generic_ghoul_heli_ai* ai = (generic_ghoul_heli_ai*)(ai_public_c*)self->ai;

	// if we've been given the script command to turn ai on, check here
	//to see if we should turn it off and start executing more script cmds

	body->m_flags &= ~HELIFLAG_SCRIPTEDAIJUSTFINISHED;	// regardless

	if (body->m_flags & HELIFLAG_DOINGSCRIPTEDAI &&
		((ai && level.time > body->m_fHeliAITimer && ai->GetActionSize()) ||
		 (body->m_flags & HELIFLAG_ISDEAD))  )
	{
		body->m_flags &= ~(	HELIFLAG_DOINGSCRIPTEDAI |
							HELIFLAG_DOINGSMARTGOTO |
							HELIFLAG_DOINGSCRIPTEDFIREAT);
		body->m_flags |= HELIFLAG_SCRIPTEDAIJUSTFINISHED;	// so hover code can pick it up
	}

	if ((body->m_flags & HELIFLAG_ISDEAD) && !(body->m_flags & HELIFLAG_ISDEADACKNOWLEDGED))
	{
		body->m_flags |= HELIFLAG_ISDEADACKNOWLEDGED;
		if (self->spawnflags & ONDIE_HEAD2SPAWN)
		{
			Cobra2_LegaliseVector(self, body->m_v3HeliDeathDest, body->m_v3HeliDeathDest, true);	// qboolean qbUseWorldClip
			VectorCopy(body->m_v3HeliDeathDest, dest);
			m_think = Cobra2GotoCoordsHelper;
		}
		else
		{
			// store the current forward vector in body->m_v3HeliDeathDest
			vec3_t	vF, vR, vU;

			AngleVectors(self->s.angles, vF, vR, vU);
			VectorNegate(vR, body->m_v3HeliDeathDest);
			body->m_v3HeliDeathDest[2] = 0;
			body->m_fHeliHoverTimer = level.time + 2.0f;
			m_think = m_nextthink = Cobra2_OutOfControl;
		}
		return true;
	}

	if (body->m_flags & HELIFLAG_DOINGSMARTGOTO)	// this one MUST be before the 'HELIFLAG_DOINGSCRIPTEDLANDING' check
	{
		m_think = Cobra2Think_SmartHeadToDest;
		return true;
	}

	if (body->m_flags & HELIFLAG_DOINGSCRIPTEDLANDING)
	{
		m_nextthink = m_think = Cobra2Think_Land;
		body->m_flags &= ~HELIFLAG_DOINGSCRIPTEDLANDING;
		return true;
	}

	if (body->m_flags & HELIFLAG_DOINGSCRIPTEDFIREAT)
	{
		m_think = Cobra2Think_GotoFireAt;
		return true;
	}

	if (body->m_flags & HELIFLAG_DOINGSCRIPTEDHOVER)
	{
		m_think = Cobra2Think_Hover;
		return true;
	}

	if (body->m_flags & HELIFLAG_DOINGSCRIPTEDAI)
	{
		Cobra2Think_AI(self);
		return true;
	}

	return false;
}

heli_action::heli_action(heli_action *orig)
: action_c(orig)
{
	m_nID = orig->m_nID;    
	m_nCommand = orig->m_nCommand;
	m_fArg = orig->m_fArg;   

	*(int *)&m_think = GetThinkNum(orig->m_think);
	*(int *)&m_nextthink = GetThinkNum(orig->m_nextthink);
}

void heli_action::Evaluate(heli_action *orig)
{
	m_nID = orig->m_nID;    
	m_nCommand = orig->m_nCommand;
	m_fArg = orig->m_fArg;   

	m_think = (void (*)(edict_t *self))GetThinkPtr((int)orig->m_think);
	m_nextthink = (void (*)(edict_t *self))GetThinkPtr((int)orig->m_nextthink);

	action_c::Evaluate(orig);
}

void heli_action::Read()
{
	char	loaded[sizeof(heli_action)];;

	gi.ReadFromSavegame('AIHE', loaded, sizeof(heli_action));
	Evaluate((heli_action *)loaded);
}

void heli_action::Write()
{
	heli_action	*savable;

	savable = new heli_action(this);
	gi.AppendToSavegame('AIHE', savable, sizeof(*this));
	delete savable;
}

/**********************************************************************************
 **********************************************************************************/
helimove_action::helimove_action(decision_c *od, action_c *oa, ai_c* which_ai, 
								 mmove_t *newanim, int nCommand, vec3_t vPos, 
								 edict_t* target, float fArg, int nID)
:heli_action(od, oa, newanim, nCommand, vPos, target, fArg)
{
	generic_ghoul_heli_ai* ai = (generic_ghoul_heli_ai*)(ai_public_c*)which_ai;
	body_heli* body = NULL;

	if (ai)
	{
		body = ai->GetHeliBody();
	}
	if (!body)
	{
		return;
	}
	// save our id (uniquely determined by the ai)
	m_nID = nID;
	// save our script params
	m_fArg = fArg;
	VectorCopy(vPos, dest);
	action_target = target;

	switch(m_nCommand)
	{
	case generic_ghoul_heli_ai::hse_TAKEOFF:
		{
			m_think = Cobra2Think_TakeOff;
			break;
		}
	case generic_ghoul_heli_ai::hse_LAND:
		{
			VectorCopy(body->m_v3HeliPadRestPos,dest);
			m_think = Cobra2LandHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_GOTO_COORDS:
		{
			m_think = Cobra2GotoCoordsHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_GOTOREL_ENTITY:
		{
			if (target)
			{
				body->m_target = target;
				VectorCopy(vPos, body->m_vPos);
				m_think = Cobra2GotoRelEntHelper;
			}
			break;
		}
	case generic_ghoul_heli_ai::hse_GOTOREL_ENT_X:
		{
			if (target)
			{
				body->m_target = target;
				VectorSet(dest, fArg, 0, 0);
				m_think = Cobra2GotoRelEntHelper;
			}
			break;
		}
	case generic_ghoul_heli_ai::hse_GOTOREL_ENT_Y:
		{
			if (target)
			{
				body->m_target = target;
				VectorSet(dest, 0, fArg, 0);
				m_think = Cobra2GotoRelEntHelper;
			}
			break;
		}
	case generic_ghoul_heli_ai::hse_GOTOREL_ENT_Z:
		{
			if (target)
			{
				body->m_target = target;
				VectorSet(dest, 0, 0, fArg);
				m_think = Cobra2GotoRelEntHelper;
			}
			break;
		}
	case generic_ghoul_heli_ai::hse_MOVEREL:
		{
			VectorCopy(vPos, body->m_vPos);
			m_think = Cobra2GotoRelHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_STRAFE_RT:			
		{
			m_fArg = fabs(fArg);
			m_think = Cobra2StrafeHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_STRAFE_LT:
		{
			m_fArg = -fabs(fArg);
			m_think = Cobra2StrafeHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_HOVER_PASSIVE:
		{
			m_fArg = fArg;
			dest[0] = 0;
			m_think = Cobra2HoverHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_HOVER_AGGRESSIVE:
		{
			body->m_fArg = fArg;
			dest[0] = 1;
			m_think = Cobra2HoverHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_REPAIR:
		{
			m_think = Cobra2Think_Repair;
			break;
		}
	case generic_ghoul_heli_ai::hse_REARM:
		{
			m_think = Cobra2RearmHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_SET_WORLDMINS:
		{
			m_fArg = body->m_fArg = 0; // 0 == mins
			VectorCopy(vPos, body->m_vPos);
			m_think = Cobra2WorldHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_SET_WORLDMAXS:
		{
			m_fArg = body->m_fArg = 1; // 1 == maxs
			VectorCopy(vPos, body->m_vPos);
			m_think = Cobra2WorldHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_SET_DEATHDEST:
		{
			m_think = Cobra2DeathDestHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_SET_TRACEDIMS:
		{
			m_think = Cobra2TraceDimsHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_SET_HEALTH:
		{
			dest[0] = 0; // 0 == health
			m_think = Cobra2HealthHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_SET_MAXHEALTH:
		{
			dest[0] = 1;	// 1 == maxhealth
			m_think = Cobra2HealthHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_CHANGE_SKIN:
		{
			// set the skin
			if (1 == m_fArg)
			{
				SetSkin(target, "enemy/chopper", "cobrass", "cobra","m_x_chopper_black", true);
			}
			else if (2 == m_fArg)
			{
				SetSkin(target, "enemy/chopper", "cobrass", "cobra","m_x_chopper_green", true);
			}
			else
			{
				SetSkin(target, "enemy/chopper", "cobrass", "cobra","m_x_chopper_white", true);
			}
			break;
		}
	case generic_ghoul_heli_ai::hse_CHANGE_BODY:
		{
			// set the body
			if (1 == m_fArg)
			{
				ai->ChangeBody(target, "enemy/hind", "hind");
			}
			break;
		}
	case generic_ghoul_heli_ai::hse_OUT_OF_CONTROL:
		{
			// body->m_v3HeliDeathDest contains a base velocity for crashing
			VectorClear(body->m_v3HeliDeathDest);
			body->m_fHeliHoverTimer = level.time + 2.0f;
			m_think = Cobra2_OutOfControl;
			break;
		}
	case generic_ghoul_heli_ai::hse_AI:
		{
			m_think = Cobra2AIHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_WAYPOINT:
		{
			ai->AddWaypoint(vPos);
			break;
		}
	case generic_ghoul_heli_ai::hse_VOLUME:
		{
			ai->SetWaypointPtVolume(ai->GetNumWaypointPts()-1,vPos);
			break;
		}
	case generic_ghoul_heli_ai::hse_VOLUMEMINS:
		{
			// vPos is in world coords, change to rel-to-waypt-origin
			heliWaypoint* pt = ai->GetWaypointPt(ai->GetNumWaypointPts()-1);
			if (pt)
			{
				VectorCopy(vPos, pt->m_volMins);
			}
			break;
		}
	case generic_ghoul_heli_ai::hse_VOLUMEMAXS:
		{
			// vPos is in world coords, change to rel-to-waypt-origin
			vec3_t vTemp;
			heliWaypoint* pt = ai->GetWaypointPt(ai->GetNumWaypointPts()-1);
			if (pt)
			{
				VectorCopy(vPos, pt->m_volMaxs);
				VectorAdd(pt->m_volMins, pt->m_volMins, vTemp);
				VectorScale(vTemp, 0.5f, pt->m_v);
				VectorSubtract(pt->m_volMins, pt->m_v, pt->m_volMins);
				VectorSubtract(pt->m_volMaxs, pt->m_v, pt->m_volMaxs);
				ai->SetWaypointPtVolume(ai->GetNumWaypointPts()-1,pt->m_volMaxs);
			}
			break;
		}
	case generic_ghoul_heli_ai::hse_DEBUG:
		{
			// sets the proper spawnflag in ds.cpp, nothing to do here
			break;
		}
	default:
		break;
	}
}

/**********************************************************************************
 **********************************************************************************/
heliface_action::heliface_action(decision_c *od, action_c *oa, ai_c* which_ai, 
								 mmove_t *newanim, int nCommand, vec3_t vPos, 
								 edict_t* target, float fArg, int nID)
:heli_action(od, oa, newanim, nCommand, vPos, target, fArg)
{
	generic_ghoul_heli_ai* ai = (generic_ghoul_heli_ai*)(ai_public_c*)which_ai;
	body_heli* body = NULL;

	if (ai)
	{
		body = ai->GetHeliBody();
	}
	if (!body)
	{
		return;
	}
	// save our id (uniquely determined by the ai)
	m_nID = nID;
	// save our script params
	m_fArg = fArg;
	VectorCopy(vPos, dest);
	action_target = target;

	switch(m_nCommand)
	{
	case generic_ghoul_heli_ai::hse_FACE_RELENT:
		{
			if (target)
			{
				body->m_target = target;
				VectorCopy(vPos,  body->m_vPos);
				m_think = Cobra2FaceRelEntHelper;
			}
			break;
		}
	case generic_ghoul_heli_ai::hse_FACE_ABSCOORDS:
		{
			VectorCopy(vPos,  body->m_vPos);
			m_think = Cobra2FaceAbsHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_FACE_ABSDIR:
		{
			VectorCopy(vPos,  body->m_vPos);
			m_think = Cobra2FaceAbsDirHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_FACE_RELCOORDS:
		{
			VectorCopy(vPos, body->m_vPos);
			m_think = Cobra2FaceRelCoordsHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_PILOT_FACERELENT:
	case generic_ghoul_heli_ai::hse_GUNNER_FACERELENT:
		{
			if (target)
			{
				body->m_target = target;
				VectorCopy(vPos, body->m_vPos);
				if (m_nCommand == generic_ghoul_heli_ai::hse_GUNNER_FACERELENT)
				{
					m_fArg = 0; // 0 == gunner
				}
				else
				{
					m_fArg = 1; // 1 == pilot
				}
				m_think = Cobra2HeadFaceRelEntHelper;
			}
			break;
		}
	case generic_ghoul_heli_ai::hse_PILOT_FACERELCOORDS:
	case generic_ghoul_heli_ai::hse_GUNNER_FACERELCOORDS:
		{
			VectorCopy(vPos, body->m_vPos);
			if (m_nCommand == generic_ghoul_heli_ai::hse_GUNNER_FACERELCOORDS)
			{
				m_fArg = 0; // 0 == gunner
			}
			else
			{
				m_fArg = 1; // 1 == pilot
			}
			m_think = Cobra2HeadFaceRelCoordsHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_PILOT_FACEABSCOORDS:
	case generic_ghoul_heli_ai::hse_GUNNER_FACEABSCOORDS:
		{
			VectorCopy(vPos, body->m_vPos);
			if (m_nCommand == generic_ghoul_heli_ai::hse_GUNNER_FACEABSCOORDS)
			{
				m_fArg = 0; // 0 == gunner
			}
			else
			{
				m_fArg = 1; // 1 == pilot
			}
			m_think = Cobra2HeadFaceAbsCoordsHelper;
			break;
		}
	default:
		break;
	}
}

/**********************************************************************************
 **********************************************************************************/
heliattack_action::heliattack_action(decision_c *od, action_c *oa, ai_c* which_ai, 
								 mmove_t *newanim, int nCommand, vec3_t vPos, 
								 edict_t* target, float fArg, int nID)
:heli_action(od, oa, newanim, nCommand, vPos, target, fArg)
{
	generic_ghoul_heli_ai* ai = (generic_ghoul_heli_ai*)(ai_public_c*)which_ai;
	body_heli* body = NULL;

	if (ai)
	{
		body = ai->GetHeliBody();
	}
	if (!body)
	{
		return;
	}
	// save our id (uniquely determined by the ai)
	m_nID = nID;
	// save our script params
	m_fArg = fArg;
	VectorCopy(vPos, dest);
	action_target = target;

	switch(m_nCommand)
	{
	case generic_ghoul_heli_ai::hse_ROCKETS_ENABLE:
		{
			m_fArg = 1;
			m_think = Cobra2RocketsEnableHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_ROCKETS_DISABLE:
		{
			m_fArg = 0;
			m_think = Cobra2RocketsEnableHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_CHAINGUN_ENABLE:
		{
			m_fArg = 1;
			m_think = Cobra2ChainGunEnableHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_CHAINGUN_DISABLE:
		{
			m_fArg = 0;
			m_think = Cobra2ChainGunEnableHelper;
			Cobra2_Fire_OFF(body->GetHeliEdict());
			body->m_fArg = 0;
			ai->ActionIsDone(true);
			break;
		}
	case generic_ghoul_heli_ai::hse_AUTOFIRE_ON:
		{
			m_fArg = 1;
			m_think = Cobra2AutoFireHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_AUTOFIRE_OFF:
		{
			m_fArg = 0;
			m_think = Cobra2AutoFireHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_FIREAT_RELENT:
		{
			if (target)
			{
				body->m_target = target;
				VectorCopy(vPos,  body->m_vPos);
				body->m_fArg = fArg;
				m_think = Cobra2FireAtRelEntHelper;
			}
			break;
		}
	case generic_ghoul_heli_ai::hse_FIREAT_ABSCOORDS:
		{
			VectorCopy(vPos,  body->m_vPos);
			body->m_fArg = fArg;
			m_think = Cobra2FireAtAbsHelper;
			break;
		}
	case generic_ghoul_heli_ai::hse_FIREAT_RELCOORDS:
		{
			VectorCopy(vPos,  body->m_vPos);
			body->m_fArg = fArg;
			m_think = Cobra2FireAtRelHelper;
			break;
		}
	default:
		break;
	}
}



/**********************************************************************************
 **********************************************************************************/
helideath_action::helideath_action(decision_c *od, action_c *oa, ai_c* which_ai, 
								 mmove_t *newanim, int nCommand, vec3_t vPos, 
								 edict_t* target, float fArg, int nID)
:heli_action(od, oa, newanim, nCommand, vPos, target, fArg)
{
	generic_ghoul_heli_ai* ai = (generic_ghoul_heli_ai*)(ai_public_c*)which_ai;
	body_heli* body = NULL;

	if (ai)
	{
		body = ai->GetHeliBody();
	}
	if (!body)
	{
		return;
	}

	m_think = Cobra2GotoCoordsHelper;

	// save our id (uniquely determined by the ai)
	m_nID = nID;
	// save our script params
	m_fArg = fArg;
	VectorCopy(vPos, dest);
	action_target = target;

}

