kingpin-sdk/gamesrc/g_ai_fight.c
2000-03-27 00:00:00 +00:00

586 lines
14 KiB
C

//==============================================================
// g_ai_fight.c
//
// Combat oriented AI routines
//==============================================================
#include "g_local.h"
//============================================================================
void AI_CheckMakeEnemy( edict_t *self, edict_t *other )
{
if (other != self->enemy && other->cast_group != self->cast_group && other->enemy == self)
{ // make them our enemy now
if ( !self->enemy
|| !(self->cast_info.aiflags & AI_MELEE)
|| VectorDistance(self->s.origin, other->s.origin) < 128)
{
if (level.global_cast_memory[self->character_index][other->character_index])
self->enemy = other;
}
}
}
/*
==================
AI_BeginAttack
Returns true if OK to attack, false otherwise
May start a new movement for the character, to straighten up, or avoid something/someone
==================
*/
qboolean AI_BeginAttack( edict_t *self )
{
vec3_t vec;
float dist;
// Ridah, 7-5-99, If they've been ordered to attack us by a client, get mad at them also
if (self->enemy && self->enemy->leader)
AI_MakeEnemy( self, self->enemy->leader, 0 );
if (!self->enemy || (self->enemy->health <= 0))
{
self->cast_info.currentmove = self->cast_info.move_stand;
return false;
}
dist = VectorDistance( self->s.origin, self->enemy->s.origin );
// Ridah, 19-may-99, Grenade AI
if (self->cast_info.aiflags & AI_GRENADE_GUY)
{
if (( self->s.origin[2] - self->enemy->s.origin[2] ) < -256)
{
if (self->cast_info.currentmove != self->cast_info.move_avoid_run)
AI_EndAttack(self); // try find something else to do
return false;
}
}
if ( !(self->cast_info.aiflags & AI_SIDE_ATTACK))
// Ridah 7/5/99, fixes shooting with back to player (happens in SR1 courtyard sometimes)
// && ( (dist > FACE_ANIM_DIST)
// || (self->cast_info.aiflags & AI_FACE_ATTACK)))
{
if (!directly_infront(self, self->enemy))
{ // we need to straighten up
float dist;
VectorSubtract( self->enemy->s.origin, self->s.origin, vec );
dist = VectorNormalize( vec );
self->ideal_yaw = vectoyaw( vec );
if ((dist > 384) && (self->maxs[2] == self->cast_info.standing_max_z))
{
int side_result;
side_result = AI_SideTrace( self, 48, 90, SIDE_RANDOM );
if (side_result && self->cast_info.move_lside_step)
{
if (side_result < 0)
self->cast_info.currentmove = self->cast_info.move_lside_step;
else
self->cast_info.currentmove = self->cast_info.move_rside_step;
}
else
{
self->cast_info.currentmove = self->cast_info.move_avoid_walk;
}
return false;
}
else
{
M_ChangeYaw(self);
}
}
}
/*
if ( (self->cast_info.move_avoid_reverse_run)
&& (self->cast_info.last_reverse < (level.time - 5))
&& (dist < 64))
{
// trace a line backwards, make sure we can move there
if ( AI_YawTrace( self, 64, 180 ) )
{
self->cast_info.currentmove = self->cast_info.move_avoid_reverse_run;
self->cast_info.last_reverse = level.time;
return false;
}
}
*/
if (!AI_ClearSight(self, self->enemy, true))
{ // can't see them directly, will we hit another enemy? if so keep firing
static vec3_t mins = { -8, -8, -4 };
static vec3_t maxs = { 8, 8, 4 };
vec3_t start, end;
trace_t tr;
cast_memory_t *tr_memory;
VectorCopy( self->s.origin, start );
start[2] += self->viewheight - 8;
VectorCopy( self->enemy->s.origin, end );
end[2] += self->enemy->viewheight - 8;
tr = gi.trace( start, mins, maxs, end, self, MASK_PLAYERSOLID );
if (tr.fraction < 1)
{
if (tr.ent == world)
{
if (self->cast_info.currentmove != self->cast_info.move_avoid_run)
AI_EndAttack(self); // try find something else to do
return false;
}
else if (tr.ent->svflags & SVF_MONSTER || tr.ent->client)
{
tr_memory = level.global_cast_memory[self->character_index][tr.ent->character_index];
if (!tr_memory || !(tr_memory->flags & MEMORY_HOSTILE_ENEMY))
{
if (self->cast_info.currentmove != self->cast_info.move_avoid_run)
AI_EndAttack(self); // try find something else to do
return false;
}
}
}
}
if (self->dont_takecover_time == 99999)
{ // attack for at least 2 seconds
if (!(self->cast_info.aiflags & AI_MELEE))
self->dont_takecover_time = level.time + 2 + random()*4;
else
self->dont_takecover_time = 0;
}
return true;
}
/*
=============
AI_AvoidDangerousEntity
Alerts all AI in vaccinity of the given ent, so they can flee if possible
=============
*/
void AI_AvoidDangerousEntity( edict_t *ent )
{
int i;
float old_time;
if (deathmatch->value)
return;
ent->s.origin[2] += 4;
old_time = level.time;
for (i=1; i<level.num_characters; i++)
{
if (!level.characters[i] || level.characters[i]->health <= 0)
continue;
if (level.characters[i] == ent)
continue;
if (!ent->client && VectorDistance( level.characters[i]->s.origin, ent->s.origin ) > ent->dmg_radius*2)
continue;
// if they are already fleeing this ent, check our fleeing position
if (level.characters[i]->cast_info.aiflags & AI_TAKE_COVER)
{
if (level.characters[i]->combat_goalent && !CanDamage( level.characters[i]->combat_goalent, ent ))
continue;
}
if (!gi.inPVS( level.characters[i]->s.origin, ent->s.origin ))
continue;
if ((VectorDistance( level.characters[i]->s.origin, ent->s.origin ) > 64) && !CanDamage( level.characters[i], ent))
continue;
// gi.dprintf( "RUN FOR YOUR LIVES!!!!\n" );
level.characters[i]->last_gethidepos = 0;
level.time -= 0.1; // so we bypass speed optimization
AI_ForceTakeCover( level.characters[i], ent, (ent->client == NULL) );
ent->noise_time = level.time;
}
level.time = old_time;
ent->s.origin[2] -= 4;
}
/*
=============
AI_CheckAttack
Generic check for ability and willingness to attack our enemy
=============
*/
qboolean AI_CheckAttack(edict_t *self)
{
float dist;
// Ridah, 17-may-99, fixes health_threshold not working
if (self->goal_ent && (self->cast_info.aiflags & AI_GOAL_IGNOREENEMY) && (VectorDistance(self->s.origin, self->goal_ent->s.origin) > 256))
{
self->cast_info.currentmove = self->cast_info.move_run;
return false;
}
if ( !self->cast_info.attack || !self->cast_info.long_attack )
return false; // never attack if we are unable to
if (!self->cast_info.move_run)
return false;
if (self->cast_info.pausetime > (level.time - 1))
return false;
if (self->combat_goalent && (VectorDistance(self->s.origin, self->combat_goalent->s.origin) > 128))
return false;
if (!AI_HasLeaderButGoForEnemy(self, self->enemy))
{
return false;
}
if ( !self->enemy || (self->enemy->health <= 0) || !self->enemy->inuse )
{
self->enemy = NULL;
return false;
}
if ( !AI_ClearSight( self, self->enemy, true ) )
{
route_t route;
// we can't hit them from here, should we pursue them?
if ( NAV_Route_EntityToEntity( self, NULL, self->enemy, VIS_PARTIAL, false, &route ) )
{
AI_StartRun( self );
}
return false;
}
if ( self->cast_info.aiflags & AI_MELEE )
{
if ( (fabs( self->s.origin[2] - self->enemy->s.origin[2] ) > 32)
|| (VectorDistance( self->s.origin, self->enemy->s.origin ) > 96 ))
{
return false;
}
}
dist = VectorDistance(self->enemy->s.origin, self->s.origin);
/*
if ( ((dist < FACE_ANIM_DIST) && !infront(self, self->enemy))
|| ((dist >= FACE_ANIM_DIST) && !directly_infront(self, self->enemy)))
{
vec3_t vec;
float dist;
// just straighten up
VectorSubtract( self->enemy->s.origin, self->s.origin, vec );
dist = VectorNormalize( vec );
if ( self->maxs[2] == self->cast_info.standing_max_z )
{
self->ideal_yaw = vectoyaw( vec );
if (dist < AI_GUARDING_DIST)
{
if (self->cast_info.move_avoid_reverse_walk)
self->cast_info.currentmove = self->cast_info.move_avoid_reverse_walk;
}
else
self->cast_info.avoid(self, self->enemy, true);
return true;
}
else
{
if (self->cast_info.move_avoid_crwalk)
{
self->ideal_yaw = vectoyaw( vec );
self->cast_info.currentmove = self->cast_info.move_avoid_crwalk;
return true;
}
else // just keep using ai_run()
{
self->cast_info.currentmove = self->cast_info.move_crwalk;
return false;
}
}
}
*/
if (dist < self->cast_info.max_attack_distance)
{
// we can attack from here
return self->cast_info.attack(self);
}
else if (!(self->cast_info.aiflags & AI_MELEE))
{ // attack from far away
self->cast_info.long_attack(self);
return false;
}
return false;
}
/*
===========
AI_Goto_CombatTarget
===========
*/
qboolean AI_Goto_CombatTarget( edict_t *self )
{
if (self->combattarget)
{
edict_t *target, *oldtarget;
target = NULL;
while ((target = G_Find (target, FOFS(targetname), self->combattarget)) != NULL)
{
if (strcmp(target->classname, "path_corner") == 0)
{
if (self->health > 0)
{
self->combat_goalent = target;
target->cast_info.aiflags |= AI_GOAL_RUN;
self->cast_info.aiflags |= AI_RUN_LIKE_HELL;
self->cast_info.currentmove = self->cast_info.move_run;
self->combattarget = NULL;
return true;
}
else // we're dead, but check for pathtarget's
{
oldtarget = target;
// check all items in chain
while (target)
{
if (target->pathtarget)
{ // fire it in 2 seconds (otherwise we might cause a loop)
char *savetarget;
if (target->delay < 2)
target->delay = 2;
savetarget = target->target;
target->target = target->pathtarget;
G_UseTargets (target, self);
target->target = savetarget;
}
if (!target->target)
return false;
target = G_Find (NULL, FOFS(targetname), target->target);
if (target == oldtarget) // looped around
return false;
}
}
}
}
}
return false;
}
void GotoCombatTargetThink( edict_t *self )
{
if (self->owner->health > 0)
AI_Goto_CombatTarget( self->owner );
G_FreeEdict( self );
}
/*
===========
AI_StartAttack
Begins an attack on enemy
===========
*/
void AI_StartAttack(edict_t *self, edict_t *enemy)
{
mmove_t *oldmove;
cast_memory_t *mem;
if (!self->pain_debounce_time)
self->pain_debounce_time = 0.1; // so we attack them from now on
// FIXME: remember the current enemy, if it exists?
self->enemy = enemy;
if (mem = level.global_cast_memory[self->character_index][enemy->character_index])
{
mem->flags |= MEMORY_STARTED_ATTACK;
}
if (self->combattarget)
{
if (self->delay)
{ // set a thinker to get us moving
edict_t *thinker;
thinker = G_Spawn();
thinker->owner = self;
thinker->think = GotoCombatTargetThink;
thinker->nextthink = level.time + self->delay;
}
else // go now
{
if (AI_Goto_CombatTarget( self ))
return;
}
}
oldmove = self->cast_info.currentmove;
self->cast_info.checkattack(self);
if (oldmove == self->cast_info.currentmove)
{ // start running
self->cast_info.currentmove = self->cast_info.move_run;
}
}
//============================================================================
// General Combat AI routines
#define COMBAT_FORCED_HIDE 1
/*
=============
CombatHideThink
=============
*/
/*
void CombatHideThink ( edict_t *thinker )
{
edict_t *ent;
ent = thinker->owner;
if (!(ent->cast_info.aiflags & AI_TAKE_COVER))
{ // not hiding anymore so free ourself
G_FreeEdict( thinker );
}
}
*/
/*
=============
AI_ProcessCombat
For each AI character, take a look at what they're currently doing, and look
for something they could be doing that would be better for them.
=============
*/
void CheckStillHiding( edict_t *self );
void AI_ProcessCombat (void)
{
int i;
edict_t *ent;
for (i=0; i<level.num_characters; i++)
{
ent = level.characters[i];
if (!ent || ent->client)
continue;
if (!ent->enemy)
continue;
if (ent->health <= 0)
continue;
if (ent->enemy->health <= 0)
continue;
if (AI_ClearSight( ent, ent->enemy, false ))
{ // visible
ent->combat_last_visible = level.time;
VectorCopy( ent->enemy->s.origin, ent->combat_last_visible_pos );
}
else // not visible
{
if (!(ent->cast_info.aiflags & AI_TAKE_COVER))
{ // not currently hiding
// should we start hiding, rather than walk straight into their trap?
if ( (ent->combat_last_visible > (level.time - 0.5))
&& ( (!ent->enemy->client && !(ent->enemy->cast_info.aiflags & AI_MELEE))
|| (ent->enemy->client && ent->enemy->client->pers.weapon && ent->enemy->client->pers.weapon->ammo))) // they have a weapon capable of doing some damage
{
// ok so start hiding, but should we hide from the position they were last visible from? or just them?
if ((ent->moral < MORAL_PSYCOTIC) /*&& (VectorDistance( ent->enemy->s.origin, ent->combat_last_visible_pos ) < (256 * ent->moral))*/)
{ // hide from last visible position
{
vec3_t org;
VectorCopy( ent->enemy->s.origin, org );
VectorCopy( ent->combat_last_visible_pos, ent->enemy->s.origin );
AI_ForceTakeCover( ent, ent->enemy, true );
VectorCopy( org, ent->enemy->s.origin );
if (ent->moral > 2) // don't wait there forever
{
ent->cast_info.aiflags &= ~AI_TAKECOVER_IGNOREHEALTH;
ent->take_cover_time = level.time + (float)(ent->moral * 2);
}
}
}
}
}
else // we are currently hiding, yell something out perhaps?
{
}
}
}
}