//===========================================================================
//
// Name:			ai_cast_events.c
// Function:		Wolfenstein AI Character Events
// Programmer:		Ridah
// Tab Size:		4 (real tabs)
//===========================================================================

#include "../game/g_local.h"
#include "../game/q_shared.h"
#include "../game/botlib.h"		//bot lib interface
#include "../game/be_aas.h"
#include "../game/be_ea.h"
#include "../game/be_ai_gen.h"
#include "../game/be_ai_goal.h"
#include "../game/be_ai_move.h"
#include "../botai/botai.h"			//bot ai interface

#include "ai_cast.h"

/*
Contains response functions for various events that require specific handling
for Cast AI's.
*/

/*
============
AICast_Sight
============
*/
void AICast_Sight( gentity_t *ent, gentity_t *other, int lastSight )
{
	cast_state_t	*cs, *ocs;

	cs = AICast_GetCastState( ent->s.number );
	ocs = AICast_GetCastState( other->s.number );

	//
	// call the sightfunc for this cast, so we can play associated sounds, or do any character-specific things
	//
	if (cs->sightfunc) {
		// factor in the reaction time
		if (AICast_EntityVisible( cs, other->s.number, qfalse )) {
			cs->sightfunc( ent, other, lastSight );
		}
	}

	if (other->aiName && other->health <= 0) {

		// they died since we last saw them
		if (ocs->deathTime > lastSight) {
			if (!AICast_SameTeam(cs, other->s.number)) {
				AICast_ScriptEvent( cs, "enemysightcorpse", other->aiName );
			} else if (!(cs->castScriptStatus.scriptFlags & SFL_FRIENDLYSIGHTCORPSE_TRIGGERED)) {
				cs->castScriptStatus.scriptFlags |= SFL_FRIENDLYSIGHTCORPSE_TRIGGERED;
				AICast_ScriptEvent( cs, "friendlysightcorpse", "" );
			}
		}

	// if this is the first time, call the sight script event
	} else if (!lastSight && other->aiName) {
		if (!AICast_SameTeam(cs, other->s.number)) {
			// disabled.. triggered when entering combat mode
			//AICast_ScriptEvent( cs, "enemysight", other->aiName );
		} else {
			AICast_ScriptEvent( cs, "sight", other->aiName );
		}
	}
}

/*
============
AICast_Pain
============
*/
void AICast_Pain( gentity_t *targ, gentity_t *attacker, int damage, vec3_t point )
{
	cast_state_t	*cs;

	cs = AICast_GetCastState( targ->s.number );

	// print debugging message
	if (aicast_debug.integer == 2 && attacker->s.number == 0) {
		G_Printf( "hit %s %i\n", targ->aiName, targ->health );
	}

	// if we are below alert mode, then go there immediately
	if (cs->aiState < AISTATE_ALERT) {
		AICast_StateChange( cs, AISTATE_ALERT );
	}

	if (cs->aiFlags & AIFL_NOPAIN) {
		return;
	}

	// process the event (turn to face the attacking direction? go into hide/retreat state?)
	// need to weigh up the situation, but foremost, an inactive AI cast should always react in some way to being hurt
	cs->lastPain = level.time;

	// record the sighting (FIXME: silent weapons shouldn't do this, but the AI should react in some way)
	if (attacker->client) {
		AICast_UpdateVisibility( targ, attacker, qtrue, qtrue );
	}

	// if either of us are neutral, then we are now enemies
	if (targ->aiTeam == AITEAM_NEUTRAL || attacker->aiTeam == AITEAM_NEUTRAL) {
		cs->vislist[attacker->s.number].flags |= AIVIS_ENEMY;
	}

	AICast_ScriptEvent( cs, "pain", va("%d %d", targ->health, targ->health+damage) );

	if (cs->aiFlags & AIFL_DENYACTION) {
		// dont play any sounds
		return;
	}

	//
	// call the painfunc for this cast, so we can play associated sounds, or do any character-specific things
	//
	if (cs->painfunc)
	{
		cs->painfunc( targ, attacker, damage, point );
	}
}

/*
============
AICast_Die
============
*/
void AICast_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
{
	int				contents;
	int				killer;
	cast_state_t	*cs;
	qboolean		nogib = qtrue;

	// print debugging message
	if (aicast_debug.integer == 2 && attacker->s.number == 0) {
		G_Printf( "killed %s\n", self->aiName );
	}

	cs = AICast_GetCastState( self->s.number );

	if ( attacker )
		killer = attacker->s.number;
	else
		killer = ENTITYNUM_WORLD;

	// record the sighting (FIXME: silent weapons shouldn't do this, but the AI should react in some way)
	if (attacker->client) {
		AICast_UpdateVisibility( self, attacker, qtrue, qtrue );
	}

	// the zombie should show special effect instead of gibbing
	if (self->aiCharacter == AICHAR_ZOMBIE && cs->secondDeadTime)
	{
		if (cs->secondDeadTime > 1) {
			// we are already totally dead
			self->health += damage;	// don't drop below gib_health if we weren't already below it
			return;
		}
/*
		if (!cs->rebirthTime)
		{
			self->health = -999;
			damage = 999;
		} else if ( self->health >= GIB_HEALTH ) {
			// while waiting for rebirth, we only "die" if we drop below gib health
			return;
		}
*/
		// always gib
		self->health = -999;
		damage = 999;
	}

	// Zombies are very fragile against highly explosives
	if (self->aiCharacter == AICHAR_ZOMBIE && damage > 20 && inflictor != attacker) {
		self->health = -999;
		damage = 999;
	}

	// process the event
	if ( self->client->ps.pm_type == PM_DEAD ) {
		// already dead
		if ( self->health < GIB_HEALTH ) {
			if (self->aiCharacter == AICHAR_ZOMBIE) {
				// RF, changed this so Zombies always gib now
				GibEntity( self, killer );
				nogib = qfalse;
/*
				// Zombie has special exploding cloud effect
				if (attacker != inflictor || attacker->s.weapon == WP_VENOM)
				{
					GibEntity( self, killer );
					nogib = qfalse;
				} else {
					// Zombie will decompose upon dying
					self->client->ps.eFlags |= EF_MONSTER_EFFECT2;
					self->s.effect2Time = level.time+200;
					self->health = -1;
				}
*/
				self->takedamage = qfalse;
				self->r.contents = 0;
				cs->secondDeadTime = 2;
				cs->rebirthTime = 0;
				cs->revivingTime = 0;
			} else {
				body_die( self, inflictor, attacker, damage, meansOfDeath );
				return;
			}
		}

	} else {	// this is our first death, so set everything up

		if ( level.intermissiontime ) {
			return;
		}

		self->client->ps.pm_type = PM_DEAD;

		self->enemy = attacker;

		// drop a weapon?
		// if client is in a nodrop area, don't drop anything
		contents = trap_PointContents( self->r.currentOrigin, -1 );
		if ( !( contents & CONTENTS_NODROP ) ) {
			TossClientItems( self );
		}

		// make sure the client doesn't forget about this entity until it's set to "dead" frame
		// otherwise it might replay it's death animation if it goes out and into client view
		self->r.svFlags |= SVF_BROADCAST;

		self->takedamage = qtrue;	// can still be gibbed

		self->s.weapon = WP_NONE;
		self->s.powerups = 0;
		self->r.contents = CONTENTS_CORPSE;

		self->s.angles[0] = 0;
		self->s.angles[1] = self->client->ps.viewangles[1];
		self->s.angles[2] = 0;

		VectorCopy( self->s.angles, self->client->ps.viewangles );

		self->s.loopSound = 0;

		self->r.maxs[2] = -8;
		self->client->ps.maxs[2] = self->r.maxs[2];

		// remove powerups
		memset( self->client->ps.powerups, 0, sizeof(self->client->ps.powerups) );

		//cs->rebirthTime = 0;

		// never gib in a nodrop
		if ( self->health <= GIB_HEALTH ) {
			if (self->aiCharacter == AICHAR_ZOMBIE) {
				// RF, changed this so Zombies always gib now
				GibEntity( self, killer );
				nogib = qfalse;
/*
				// Zombie has special exploding cloud effect
				if (attacker != inflictor || attacker->s.weapon == WP_VENOM)
				{
					GibEntity( self, killer );
					nogib = qfalse;
					self->takedamage = qfalse;
					self->r.contents = 0;
					cs->secondDeadTime = 2;
				} else {
					self->client->ps.eFlags |= EF_MONSTER_EFFECT2;
					self->s.effect2Time = level.time+200;
					self->takedamage = qfalse;
					self->r.contents = 0;
					self->health = -1;
					cs->secondDeadTime = 2;
				}
*/
			} else if (!(contents & CONTENTS_NODROP)) {
				body_die( self, inflictor, attacker, damage, meansOfDeath );
				//GibEntity( self, killer );
				nogib = qfalse;
			}
		}

		// if we are a zombie, and lying down during our first death, then we should just die
		if (!(self->aiCharacter == AICHAR_ZOMBIE && cs->secondDeadTime && cs->rebirthTime)) {

			// set enemy weapon
			BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_WEAPON, 0, qfalse );
			if (attacker->client) {
				BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_WEAPON, inflictor->s.weapon, qtrue );
			} else {
				BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_WEAPON, 0, qfalse );
			}

			// set enemy location
			BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_POSITION, 0, qfalse );
			if (infront( self, inflictor )) {
				BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_POSITION, POSITION_INFRONT, qtrue );
			} else {
				BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_POSITION, POSITION_BEHIND, qtrue );
			}

			// play the animation
			BG_AnimScriptEvent( &self->client->ps, ANIM_ET_DEATH, qfalse, qtrue );

			// set this flag so no other anims override us
			self->client->ps.eFlags |= EF_DEAD;
			self->s.eFlags |= EF_DEAD;

		}
	}

	if(nogib)
	{
		// set for rebirth
		if (self->aiCharacter == AICHAR_ZOMBIE) {
			if (!cs->secondDeadTime) {
				cs->rebirthTime = level.time + 5000 + rand()%2000;
				cs->secondDeadTime = qtrue;
				cs->revivingTime = 0;
			} else if (cs->secondDeadTime > 1) {
				cs->rebirthTime = 0;
				cs->revivingTime = 0;
				cs->deathTime = level.time;
			}
		} else {
			// the body can still be gibbed
			self->die = body_die;
		}
	}

	trap_LinkEntity (self);

	// mark the time of death
	cs->deathTime = level.time;

	// dying ai's can trigger a target
	if (!cs->rebirthTime) {
		G_UseTargets(self, self);
		// really dead now, so call the script
		AICast_ScriptEvent( cs, "death", "" );
		// call the deathfunc for this cast, so we can play associated sounds, or do any character-specific things
		if (!(cs->aiFlags & AIFL_DENYACTION) && cs->deathfunc)
		{
			cs->deathfunc( self, attacker, damage, meansOfDeath);	//----(SA)	added mod
		}
	} else {
		// really dead now, so call the script
		AICast_ScriptEvent( cs, "fakedeath", "" );
		// call the deathfunc for this cast, so we can play associated sounds, or do any character-specific things
		if (!(cs->aiFlags & AIFL_DENYACTION) && cs->deathfunc)
		{
			cs->deathfunc( self, attacker, damage, meansOfDeath);	//----(SA)	added mod
		}
	}
}

/*
===============
AICast_EndChase
===============
*/
void AICast_EndChase( cast_state_t *cs )
{
	// anything?
}

/*
===============
AICast_AIDoor_Touch
===============
*/
void AICast_AIDoor_Touch( gentity_t *ent, gentity_t *aidoor_trigger, gentity_t *door )
{
	cast_state_t *cs, *ocs;
	gentity_t *trav;
	int i;
	trace_t tr;
	vec3_t mins, pos, dir;

	cs = AICast_GetCastState( ent->s.number );

	if (!cs->bs)
		return;

	// does the aidoor have ai_marker's?
	if (!aidoor_trigger->targetname) {
		G_Printf( "trigger_aidoor has no ai_marker's at %s\n", vtos(ent->r.currentOrigin) );
		return;
	}

	// are we heading for an ai_marker?
	if (cs->aifunc == AIFunc_DoorMarker)
		return;

	// if they are moving away from the door, ignore them
	if (VectorLength(cs->bs->velocity) > 1) {
		VectorAdd (door->r.absmin, door->r.absmax, pos);
		VectorScale (pos, 0.5, pos);
		VectorSubtract( pos, cs->bs->origin, dir );
		if (DotProduct( cs->bs->velocity, dir ) < 0) {
			return;
		}
	}

  // TTimo: assignment used as truth value
	for (trav = NULL; ( trav = G_Find( trav, FOFS(target), aidoor_trigger->targetname ) ); ) {
		// make sure the marker is vacant
		trap_Trace( &tr, trav->r.currentOrigin, ent->r.mins, ent->r.maxs, trav->r.currentOrigin, ent->s.number, ent->clipmask );
		if (tr.startsolid)
			continue;
		// search all other AI's, to see if they are heading for this marker
		for (i=0, ocs=AICast_GetCastState(0); i<aicast_maxclients; i++, ocs++) {
			if (!ocs->bs)
				continue;
			if (ocs->aifunc != AIFunc_DoorMarker)
				continue;
			if (ocs->doorMarker != trav->s.number)
				continue;
			// found a match
			break;
		}
		if (i<aicast_maxclients)
			continue;
		// make sure there is a clear path
		VectorCopy( ent->r.mins, mins );
		mins[2] += 16;	// step height
		trap_Trace( &tr, ent->r.currentOrigin, mins, ent->r.maxs, trav->r.currentOrigin, ent->s.number, ent->clipmask );
		if (tr.fraction < 1.0)
			continue;
		// the marker is vacant and available
		cs->doorMarkerTime = level.time;
		cs->doorMarkerNum = trav->s.number;
		cs->doorMarkerDoor = door->s.number;
		break;
	}
}

/*
============
AICast_ProcessActivate
============
*/
void AICast_ProcessActivate( int entNum, int activatorNum )
{
	cast_state_t *cs;
	gentity_t *newent, *ent, *activator;
	gclient_t *client;

	cs = AICast_GetCastState( entNum );
	client = &level.clients[entNum];
	ent = &g_entities[entNum];
	activator = &g_entities[activatorNum];

	if (!AICast_SameTeam(cs, activatorNum)) {

		if (ent->aiTeam == AITEAM_NEUTRAL) {
			AICast_ScriptEvent( cs, "activate", g_entities[activatorNum].aiName );
		}

		return;
	}

	// try running the activate event, if it denies us the request, then abort
	cs->aiFlags &= ~AIFL_DENYACTION;
	AICast_ScriptEvent( cs, "activate", g_entities[activatorNum].aiName );
	if (cs->aiFlags & AIFL_DENYACTION) {
		return;
	}

	// if we are doing something else
	if (cs->castScriptStatus.castScriptEventIndex >= 0) {
		if (ent->eventTime != level.time)
			G_AddEvent( &g_entities[entNum], EV_GENERAL_SOUND, G_SoundIndex(aiDefaults[cs->aiCharacter].ordersDenySoundScript) );
		return;
	}

	// if we are already following them, stop following
	if (cs->leaderNum == activatorNum) {
		if (ent->eventTime != level.time)
			G_AddEvent( &g_entities[entNum], EV_GENERAL_SOUND, G_SoundIndex(aiDefaults[cs->aiCharacter].staySoundScript) );

		cs->leaderNum = -1;

		// create a goal at this position
		newent = G_Spawn();
		newent->classname = "AI_wait_goal";
		newent->r.ownerNum = entNum;
		G_SetOrigin( newent, cs->bs->origin );
		AIFunc_ChaseGoalStart(cs, newent->s.number, 128, qtrue);

		//AIFunc_IdleStart( cs );
	} else {	// start following
		int count, i;
		cast_state_t *tcs;

		// if they already have enough followers, deny
		for (count=0, i=0, tcs=caststates; i<level.maxclients; i++, tcs++) {
			if (tcs->bs && tcs!=cs && tcs->entityNum != activatorNum && g_entities[tcs->entityNum].health > 0 && tcs->leaderNum == activatorNum) {
				count++;
			}
		}
		if (count >= 3) {
			if (ent->eventTime != level.time)
				G_AddEvent( &g_entities[entNum], EV_GENERAL_SOUND, G_SoundIndex(aiDefaults[cs->aiCharacter].ordersDenySoundScript) );
			return;
		}

		if (ent->eventTime != level.time)
			G_AddEvent( &g_entities[entNum], EV_GENERAL_SOUND, G_SoundIndex(aiDefaults[cs->aiCharacter].followSoundScript) );

		// if they have a wait goal, free it
		if (cs->followEntity >= MAX_CLIENTS && g_entities[cs->followEntity].classname && !strcmp(g_entities[cs->followEntity].classname, "AI_wait_goal")) {
			G_FreeEntity( &g_entities[cs->followEntity] );
		}

		cs->followEntity = -1;
		cs->leaderNum = activatorNum;
	}
}

/*
================
AICast_RecordScriptSound
================
*/
void AICast_RecordScriptSound( int client ) {
	cast_state_t *cs;

	cs = AICast_GetCastState( client );
	cs->lastScriptSound = level.time;
}
