kingpin-sdk/gamesrc/G_AI.C
2000-03-27 00:00:00 +00:00

4788 lines
118 KiB
C

// g_ai.c
#include "g_local.h"
#include "g_func.h"
qboolean AI_FindTarget (edict_t *self);
// RAFAEL 28-dec-98
qboolean AI_HearPlayer (edict_t *self);
extern cvar_t *maxclients;
qboolean enemy_vis;
qboolean enemy_infront;
int enemy_range;
float enemy_yaw;
void AI_GetAvoidDirection( edict_t *self, edict_t *other );
//============================================================================
//============================================================================
/*
=============
ai_onfire_run
Called while a character is running around on fire
==============
*/
void ai_onfire_run( edict_t *self, float dist )
{
int side_result;
if (self->onfiretime <= 0)
{ // stopping running around
self->cast_info.currentmove = self->cast_info.move_stand;
return;
}
if (!self->groundentity)
return;
// M_ChangeYaw( self );
self->s.angles[YAW] = self->ideal_yaw;
if (M_walkmove (self, self->s.angles[YAW], dist*0.5) && AI_SideTrace( self, 64, 0, 1 ))
return;
// look for a new direction
side_result = AI_SideTrace( self, 128, 30, SIDE_RANDOM );
if (side_result)
{
self->ideal_yaw = anglemod(self->s.angles[YAW] + (30.0 * side_result));
return;
}
side_result = AI_SideTrace( self, 32, 45, SIDE_RANDOM );
if (side_result)
{
self->ideal_yaw = anglemod(self->s.angles[YAW] + (30.0 * side_result));
return;
}
// try a sharper angle
side_result = AI_SideTrace( self, 64, 100, SIDE_RANDOM );
if (side_result)
{
self->ideal_yaw = anglemod(self->s.angles[YAW] + (100.0 * side_result));
return;
}
side_result = AI_SideTrace( self, 8, 150, SIDE_RANDOM );
if (side_result)
{
self->ideal_yaw = anglemod(self->s.angles[YAW] + (150.0 * side_result));
return;
}
// last ditch, try running to our attacker
// ai_run( self, dist );
// self->cast_info.currentmove = self->cast_info.move_stand;
};
/*
=============
ai_move
Move the specified distance at current facing.
This replaces the QC functions: ai_forward, ai_back, ai_pain, and ai_painforward
==============
*/
void ai_move (edict_t *self, float dist)
{
M_walkmove (self, self->s.angles[YAW], dist);
// Ridah, added this so we turn to face our attacker after killed
if (self->health <= 0)
{
M_ChangeYaw( self );
if (self->velocity[2] < -50 && fabs(self->velocity[0]) < 50 && fabs(self->velocity[1]) < 50 && !self->groundentity && self->gravity < 1.0)
{
self->gravity = 0.1; // sliding down wall?
}
}
}
/*
============
AI_CheckEvade
============
*/
void AI_CheckEvade( edict_t *self )
{
vec3_t vec;
cast_memory_t *mem=NULL;
edict_t **enemy;
if (self->enemy)
enemy = &self->enemy;
else
enemy = &self->cast_info.avoid_ent;
if (!(*enemy))
{
self->cast_info.currentmove = self->cast_info.move_stand;
return;
}
if (self->combat_goalent)
{
if ( (*enemy)->client
&& (!(*enemy)->client->pers.holsteredweapon)
&& ((*enemy)->client->pers.weapon))
{ // get the fuck outta here
self->cast_info.currentmove = self->cast_info.move_run;
return;
}
else if ( !(*enemy)->client
&& ((*enemy)->noise_time > (level.time - 2) && (*enemy)->noise_type == PNOISE_WEAPON))
{ // get the fuck outta here
self->cast_info.currentmove = self->cast_info.move_run;
return;
}
}
else if ((enemy == &self->cast_info.avoid_ent) || self->pain_debounce_time < (level.time - 3))
{
if ((*enemy) && (*enemy)->health > 0)
{
mem = level.global_cast_memory[self->character_index][(*enemy)->character_index];
if (mem && (mem->timestamp > (level.time - 4)))
{
if ((*enemy)->client)
{
if ( (!(*enemy)->client->pers.holsteredweapon)
&& ((*enemy)->client->pers.weapon))
{
float dist;
VectorSubtract( (*enemy)->s.origin, self->s.origin, vec );
dist = VectorNormalize( vec );
self->ideal_yaw = vectoyaw( vec );
if (dist < AI_NOT_HOLSTERED_RANGE_1)
{
if (self->moral > MORAL_HAPPY || directly_infront((*enemy), self))
{
AI_StartAttack( self, (*enemy) );
if (self->cast_info.sight)
self->cast_info.sight ( self, (*enemy) );
}
else if (AI_SideTrace( self, -64, 0, 1) && dist < 64)
{ // go back again
self->cast_info.currentmove = self->cast_info.move_evade;
}
}
else if (directly_infront((*enemy), self)
&& (*enemy)->client
&& (*enemy)->client->pers.weapon
&& (*enemy)->client->pers.weapon->ammo)
{
if (AI_CheckTakeCover( self ))
{ // get outta here
self->cast_info.currentmove = self->cast_info.move_run;
}
else if (AI_SideTrace( self, -64, 0, 1))
{ // go back again
self->cast_info.currentmove = self->cast_info.move_evade;
}
else if (dist < 400)
{
AI_StartAttack( self, (*enemy) );
}
}
return;
}
}
else // AI character
{
// FIXME: do more thorough AI here?
if (enemy == &self->enemy)
{
self->cast_info.currentmove = self->cast_info.move_run;
}
return;
}
}
if (mem)
{ // they're not hostile anymore
mem->flags &= ~MEMORY_HOSTILE_ENEMY;
}
}
}
// noone to evade, resume standing
(*enemy) = NULL;
self->cast_info.currentmove = self->cast_info.move_stand;
}
/*
=============
AI_ClearSight
Checks that we can draw a line to other, without hitting something else in the process
=============
*/
qboolean AI_ClearSight ( edict_t *self, edict_t *other, qboolean boxtrace )
{
static vec3_t mins = { -8, -8, -4 };
static vec3_t maxs = { 8, 8, 4 };
// Ridah, used for Flamethrower AI
static vec3_t mins2 = { -12, -12, -12 };
static vec3_t maxs2 = { 12, 12, 12 };
vec3_t start, end, vec;
float *tmins, *tmaxs;
int mask;
trace_t tr;
qboolean rval;
float dist, progdist, distleft;
if (!self || !other)
return false;
if (boxtrace)
{
if (self->cast_info.max_attack_distance == 384)
{
tmins = mins2;
tmaxs = maxs2;
}
else
{
tmins = mins;
tmaxs = maxs;
}
mask = MASK_PLAYERSOLID;
}
else
{
tmins = NULL;
tmaxs = NULL;
mask = MASK_SOLID;
}
again:
VectorCopy( self->s.origin, start );
start[2] += self->viewheight-8*boxtrace; // adjust for gun position
VectorCopy( other->s.origin, end );
end[2] += other->viewheight-8*boxtrace;
VectorSubtract( end, start, vec );
dist = VectorNormalize( vec );
progdist = 0;
distleft = dist;
if (fabs(vec[2]) > 0.8) // too steep
return false;
tr = gi.trace( start, tmins, tmaxs, end, self, mask );
while ( (tr.fraction < 1)
&& ( ((tr.ent) && (tr.ent->s.renderfx2 & RF2_SURF_ALPHA))
|| (tr.contents & MASK_ALPHA)
|| (tr.surface->flags & SURF_ALPHA)
// Rafael : this will allow immortals to see through non breaking glass
|| (tr.contents & CONTENTS_TRANSLUCENT && tr.contents & CONTENTS_WINDOW && self->cast_info.aiflags & AI_IMMORTAL)
))
{
progdist += tr.fraction * dist;
distleft -= tr.fraction * dist;
if (distleft < 14)
{ // gone passed the point, return true
return true;
}
VectorMA( tr.endpos, 12, vec, start );
tr = gi.trace( start, tmins, tmaxs, end, self, mask );
}
rval = ((tr.ent == other) || (tr.fraction == 1));
if (!rval && self->deadflag && self->viewheight < 32)
{
self->viewheight = 32;
goto again;
}
return rval;
}
/*
=============
AI_MoveToPlatCenter
moves the character towards the center position of the platform
=============
*/
void AI_MoveToPlatCenter( edict_t *self, edict_t *plat)
{
vec3_t center, vec;
node_t *plat_node;
if (plat->deadflag)
{
plat_node = level.node_data->nodes[plat->nav_data.cache_node];
VectorCopy(plat_node->origin, center);
center[2] = self->s.origin[2];
VectorSubtract(center, self->s.origin, vec);
VectorNormalize2(vec, vec);
self->ideal_yaw = vectoyaw(vec);
M_ChangeYaw(self);
M_walkmove(self, self->ideal_yaw, 15);
}
}
/*
=============
AI_CheckTalk
look for a friend in close vicinity, to make a talking jesture at
=============
*/
qboolean AI_CheckTalk( edict_t *self )
{
cast_memory_t *cast=NULL;
float best_dist=500, this_dist;
edict_t *best_cast=NULL;
int count=1, total;
int i;
if (!self->cast_info.talk) // can't speak
return false;
if (self->goal_ent || self->enemy)
return false;
// check our current talk_ent
if (self->cast_info.talk_ent)
{
if (!(cast = level.global_cast_memory[self->character_index][self->cast_info.talk_ent->character_index]))
{
self->cast_info.talk_ent = NULL;
return false;
}
if ( ((cast->timestamp > (level.time - 1)) && (++count))
&& (/*!best_cast ||*/ infront(&g_edicts[cast->cast_ent], self)) // ignore them if they aren't facing us
&& ((this_dist = VectorDistance(g_edicts[cast->cast_ent].s.origin, self->s.origin)) < best_dist))
{
best_cast = &g_edicts[cast->cast_ent];
best_dist = this_dist;
}
}
// check clients
for (i=1; i<=maxclients->value; i++)
{
if (g_edicts[i].client && g_edicts[i].health > 0)
{
cast = level.global_cast_memory[self->character_index][g_edicts[i].character_index];
if (cast)
{
if ((cast->timestamp_dist < 300) && (cast->timestamp > (level.time - 3))
&& (((&g_edicts[i] == self->leader) && (g_edicts[i].last_talk_time < (level.time - 4)))
|| (directly_infront(&g_edicts[i], self))))
{
// pick the client that spoke most recently
if (!best_cast || !best_cast->client || (best_cast->last_talk_time < g_edicts[i].last_talk_time))
{
best_cast = &g_edicts[i];
}
}
}
}
}
if (best_cast)
goto done;
if (self->cast_info.last_talk_turn > (level.time - 2))
{ // ignore other AI characters for this time
goto done;
}
if (!best_cast) // only check for other ents if our current talk_ent isn't valid anymore
for (i=0; i<3; i++)
{
switch (i)
{
case 0:
cast = self->cast_info.friend_memory;
break;
case 1:
cast = self->cast_info.neutral_memory;
break;
case 2:
cast = self->cast_info.enemy_memory;
break;
}
total = (int) (random() * 10);
while (cast)
{
if ( ((cast->timestamp > (level.time - 1)) && (++count))
&& (/*!best_cast ||*/ infront(&g_edicts[cast->cast_ent], self)) // ignore them if they aren't facing us
&& (g_edicts[cast->cast_ent].cast_info.aiflags & AI_TALK)
&& !(g_edicts[cast->cast_ent].cast_info.aiflags & AI_NO_TALK)
&& ((this_dist = VectorDistance(g_edicts[cast->cast_ent].s.origin, self->s.origin)) < best_dist))
{
best_cast = &g_edicts[cast->cast_ent];
best_dist = this_dist;
}
// if (count > 6) // only check 5 visible friends at once
// break;
cast = cast->next;
}
if (i==0 && best_cast)
break;
}
self->cast_info.last_talk_turn = level.time;
done:
if (best_cast)
{
if (directly_infront(self, best_cast) || !self->cast_info.move_avoid_walk)
{
self->cast_info.talk(self);
}
// else // turn to face them
else
{
vec3_t vec;
if (self->cast_info.aiflags & AI_SIT_TALK)
{
self->cast_info.aiflags &= ~AI_SIT_TALK;
}
else
{
VectorSubtract(best_cast->s.origin, self->s.origin, vec);
VectorNormalize(vec);
self->ideal_yaw = vectoyaw(vec);
self->cast_info.currentmove = self->cast_info.move_avoid_walk;
}
self->cast_info.aiflags |= AI_TALK;
// talk straight away
//self->last_talk_time = 0;
}
self->cast_info.talk_ent = best_cast;
return true;
}
return false;
}
/*
=============
AI_TalkThink
Generic talking for all characters (currently only supports males)
=============
*/
void AI_TalkThink( edict_t *self, qboolean ismale )
{
#include "voice_punk.h"
#include "voice_bitch.h"
edict_t *talk_ent;
cast_memory_t *mem;
if (level.cut_scene_time)
return;
if (!(self->cast_info.aiflags & AI_TALK))
return;
if (self->enemy) // don't talk normally if we're mad
return;
if (!(talk_ent = self->cast_info.talk_ent))
return;
if (VectorDistance( talk_ent->s.origin, self->s.origin ) > 600 || !directly_infront(talk_ent, self))
{
self->cast_info.talk_ent = NULL;
return;
}
if ( (self->cast_info.talk_ent == &g_edicts[1])
&& (self->cast_info.talk_ent->last_talk_time < (level.time - TALK_OTHER_DELAY*2))
&& (self->last_talk_time > (level.time - TALK_SELF_DELAY)))
{
return;
}
// if (last_client_talk && last_client_talk > (level.time - TALK_OTHER_DELAY))
// return; // don't talk too much around the client
if (self->leader && VectorDistance( self->s.origin, self->leader->s.origin ) < 384 )
{
if ((self->last_talk_time < (level.time - TALK_SELF_DELAY)) && (self->health < self->max_health/2))
// if (self->last_talk_time < level.time - (TALK_SELF_DELAY * 4) && (self->health < 50))
{ // we've been hired, only speak when we're hurt
if (self->gender == GENDER_MALE)
{
Voice_Random( self, self->leader, friendlyhurt, NUM_FRIENDLYHURT );
return;
}
}
return;
}
if ((talk_ent->health <= 0) || !visible(self, talk_ent) || !infront(talk_ent, self))
{
self->cast_info.talk_ent = NULL;
return;
}
mem = level.global_cast_memory[self->character_index][talk_ent->character_index];
if (!mem || (mem->flags & MEMORY_NO_TALK))
return;
// say something!
if ( ( (self->last_talk_time < (level.time - TALK_SELF_DELAY)) // we haven't spoken for a while
|| ( (talk_ent->client || talk_ent->last_talk_time) // if they haven't spoken yet, don't bother
&& (talk_ent->last_talk_time > (level.time - TALK_OTHER_DELAY*1.5)) // or they've just said something, and we've allowed some time for them to finish saying it
&& (talk_ent->cast_info.talk_ent == self)
&& (self->last_talk_time < talk_ent->last_talk_time)
&& (self->last_talk_time < (level.time - TALK_OTHER_DELAY))))
&& (talk_ent->last_talk_time < (level.time - TALK_OTHER_DELAY)))
{
if (talk_ent->client)
{
if (!self->cast_group)
{ // we're neutral
cast_memory_t *mem;
mem = level.global_cast_memory[self->character_index][talk_ent->character_index];
if (!mem || !(mem->flags & MEMORY_ASSHOLE))
{
if (!EP_EventSpeech( self, talk_ent, say_neutral ))
{
if (ismale)
Voice_Random(self, self->cast_info.talk_ent, neutral_talk, NUM_NEUTRAL_TALK);
else
Voice_Random(self, self->cast_info.talk_ent, f_neutral_talk, F_NUM_NEUTRAL_TALK);
}
}
else // he's a jerk
{
if (!EP_EventSpeech( self, talk_ent, say_hostile ))
{
if (ismale)
Voice_Random(self, self->cast_info.talk_ent, neutral_asshole_talk, NUM_NEUTRAL_ASSHOLE_TALK);
else
Voice_Random(self, self->cast_info.talk_ent, f_profanity_level1, F_NUM_PROFANITY_LEVEL1);
}
}
}
else if (self->cast_group != talk_ent->cast_group)
{ // profanity time
if (!EP_EventSpeech( self, talk_ent, say_hostile ))
{
// JOSEPH 26-MAY-99
// if first sighting, play specific sound
if (self->name_index == NAME_NICKIBLANCO)
{
Voice_Specific(self, self->cast_info.talk_ent, nickiblanco, 10);
}
else if (self->name_index == NAME_TYRONE)
{
Voice_Specific(self, self->cast_info.talk_ent, ty_tyrone, 10);
}
else if (self->name_index == NAME_MOKER)
{
Voice_Specific(self, self->cast_info.talk_ent, steeltown_moker, 10);
}
else if (self->name_index == NAME_JESUS)
{
Voice_Specific(self, self->cast_info.talk_ent, sr_jesus, 10);
}
else if (self->name_index == NAME_HEILMAN)
{
Voice_Specific(self, self->cast_info.talk_ent, heilman, 10);
}
else if (self->name_index == NAME_BLUNT)
{
Voice_Specific(self, self->cast_info.talk_ent, blunt, 10);
}
else if (self->name_index == NAME_KINGPIN)
{
Voice_Specific(self, self->cast_info.talk_ent, kingpin, 10);
}
else if (ismale && specific[0].last_played < 0.1)
{
Voice_Specific(self, self->cast_info.talk_ent, specific, 0); // What are you doing on my street
}
// END JOSEPH
else if (!ismale && f_specific[0].last_played < 0.1)
{
Voice_Random (self, self->cast_info.talk_ent, f_specific, 4); // What are you doing on my street
}
// check profanity level
else if (talk_ent->profanity_level <= 1)
{
if (ismale)
Voice_Random(self, self->cast_info.talk_ent, profanity_level1, NUM_PROFANITY_LEVEL1);
else
Voice_Random(self, self->cast_info.talk_ent, f_profanity_level1, F_NUM_PROFANITY_LEVEL1);
}
else if (talk_ent->profanity_level <= 2)
{
if (ismale)
Voice_Random(self, self->cast_info.talk_ent, profanity_level2, NUM_PROFANITY_LEVEL2);
else
Voice_Random(self, self->cast_info.talk_ent, f_profanity_level2, F_NUM_PROFANITY_LEVEL2);
}
else if (talk_ent->profanity_level == 3)
{
// Ridah, 31-may-99, fixed the game crashing everytime you cussed a character 3 times
if (ismale)
Voice_Random(self, self->cast_info.talk_ent, profanity_level3, NUM_PROFANITY_LEVEL3); // kill that mofo
else
Voice_Random(self, self->cast_info.talk_ent, f_profanity_level3, F_NUM_PROFANITY_LEVEL3); // kill that mofo
// go for them!
AI_MakeEnemy(self, self->cast_info.talk_ent, 0);
}
// allow more of a delay than usual
last_client_talk = level.time + 3;
}
}
}
else // AI -> AI chat
{
if (!EP_EventSpeech( self, talk_ent, say_neutral))
{
if (ismale)
Voice_Random(self, self->cast_info.talk_ent, neutral_converse, NUM_NEUTRAL_CONVERSE);
else
Voice_Random(self, self->cast_info.talk_ent, f_neutral_converse, F_NUM_NEUTRAL_CONVERSE);
}
}
if (self->last_talk_time == level.time)
{
if (!directly_infront( self, self->cast_info.talk_ent ))
{
self->cast_info.avoid( self, self->cast_info.talk_ent, true );
}
else if (self->cast_info.currentmove == self->cast_info.move_stand)
{ // make talking gesture if we're just standing around
self->cast_info.talk(self);
}
}
}
}
#define TAKECOVER_BEHIND 8
#define TAKECOVER_INFRONT 13
#define TAKECOVER_DIRECTLY_INFRONT 18
#define SIGHT_ENEMY_ATTACKTIME 2.0 // attack for at least this duration before fleeing again
void CheckStillHiding( edict_t *self )
{
if (self->owner->health <= 0)
{
G_FreeEdict( self );
return;
}
if (!(self->owner->cast_info.aiflags & AI_TAKE_COVER) || (self->owner->cover_ent && !self->owner->cover_ent->inuse))
{ // they're not hiding anymore, stop checking
self->owner->combat_goalent = NULL;
self->owner->cast_info.aiflags &= ~AI_TAKE_COVER;
G_FreeEdict( self );
return;
}
if ((self->owner->cover_ent != self->owner->enemy) && (self->owner->cover_ent->noise_time < (level.time - 5)))
{ // stop hiding
self->owner->combat_goalent = NULL;
self->owner->cast_info.aiflags &= ~AI_TAKE_COVER;
G_FreeEdict( self );
return;
}
if (self->owner->cast_info.currentmove == self->owner->cast_info.move_evade)
{
// start running
self->owner->cast_info.currentmove = self->owner->cast_info.move_run;
self->nextthink = level.time + 1.0;
return;
}
if (self->owner->combat_goalent == self && self->owner->cover_ent && ((VectorDistance(self->owner->s.origin, self->s.origin) > 256) || ValidBoxAtLoc( self->s.origin, self->owner->mins, tv(16, 16, 4), self->owner, MASK_PLAYERSOLID )))
{
float dist;
vec3_t vec;
// turn to face them
VectorSubtract( self->owner->cover_ent->s.origin, self->owner->s.origin, vec );
vec[2] = 0;
dist = VectorNormalize(vec);
// should they keep hiding?
if ((self->owner->cast_info.currentmove->frame->aifunc == ai_stand) && (VectorDistance( self->s.origin, self->owner->s.origin ) < 128))
{
self->owner->ideal_yaw = vectoyaw(vec);
M_ChangeYaw( self->owner );
// make sure we're still aware of them
AI_RecordSighting(self->owner, self->owner->cover_ent, dist);
if (AI_ClearSight( self->owner->cover_ent, self->owner, false ))
{ // they can see us, we gotta do something or we're dead meat
self->owner->combat_goalent = NULL;
self->owner->cast_info.aiflags &= ~AI_TAKE_COVER;
if (!(self->owner->cast_info.aiflags & AI_MELEE))
self->owner->dont_takecover_time = 99999;//level.time + SIGHT_ENEMY_ATTACKTIME; // allow some time to get some shots off
self->owner->cast_info.aiflags &= ~AI_TAKECOVER_IGNOREHEALTH;
G_FreeEdict( self );
return;
}
if (self->owner->cast_info.aiflags & AI_TAKECOVER_IGNOREHEALTH)
{ // stay here until they see us
goto skiptest;
}
if (self->owner->cast_info.aiflags & AI_MELEE)
{
route_t route;
// Ridah, modified this so hiding Melee guys don't foolishly keep running out into the line of fire for no reason
if ( infront(self->owner->cover_ent, self->owner) // we're infront of them
&& self->owner->cover_ent->client && self->owner->cover_ent->client->pers.weapon && self->owner->cover_ent->client->pers.weapon->ammo)
{ // they have a weapon, keep hiding
goto skiptest;
}
else if (VectorDistance( self->owner->cover_ent->s.origin, self->owner->s.origin ) > 256)
{ // if too far away, keep hiding
if (NAV_Route_EntityToEntity( self->owner, NULL, self->owner->cover_ent, VIS_PARTIAL, false, &route ))
{
if (route.dist > 256)
goto skiptest;
}
else // no route, keep hiding
{
goto skiptest;
}
}
}
if (!infront( self->owner->cover_ent, self->owner ))
{ // they're not looking at us
if (self->owner->health > ((MORAL_MAX - self->owner->moral) * TAKECOVER_BEHIND))
{
self->owner->combat_goalent = NULL;
self->owner->cast_info.aiflags &= ~AI_TAKE_COVER;
self->owner->dont_takecover_time = 99999;//level.time + SIGHT_ENEMY_ATTACKTIME * (random()+1);
G_FreeEdict( self );
return;
}
}
else if (self->owner->cast_group && !directly_infront( self->owner->cover_ent, self->owner ))
{ // not looking directly at us, but in our general direction
if (self->owner->health > ((MORAL_MAX - self->owner->moral) * TAKECOVER_INFRONT))
{
self->owner->combat_goalent = NULL;
self->owner->cast_info.aiflags &= ~AI_TAKE_COVER;
self->owner->dont_takecover_time = 99999;//level.time + SIGHT_ENEMY_ATTACKTIME;
G_FreeEdict( self );
return;
}
}
else if (self->owner->cast_group)
{ // looking directly at us
if (self->owner->health > ((MORAL_MAX - self->owner->moral) * TAKECOVER_DIRECTLY_INFRONT))
{
self->owner->combat_goalent = NULL;
self->owner->cast_info.aiflags &= ~AI_TAKE_COVER;
self->owner->dont_takecover_time = 99999;//level.time + SIGHT_ENEMY_ATTACKTIME;
G_FreeEdict( self );
return;
}
}
}
skiptest:
if ((dist < 2000) && (VectorDistance( self->s.origin, self->owner->s.origin ) < 384) && (self->owner->last_getcombatpos < (level.time - 2.0)))
{ // look for somewhere else to go to
float *pos;
if ( (self->owner->cast_info.aiflags & AI_MELEE)
|| (self->owner->health < ((MORAL_MAX - self->owner->moral) * TAKECOVER_INFRONT)))
{ // look for somewhere further away
if (pos = NAV_GetHidePos( self->owner, self->owner->cover_ent, HIDEPOS_FURTHER ))
{
edict_t *combatent;
if (VectorDistance( pos, self->s.origin ) > 96)
{
combatent = self;
VectorCopy( pos, combatent->s.origin );
self->owner->combat_goalent = combatent;
combatent->cast_info.aiflags |= AI_GOAL_RUN;
self->owner->cast_info.aiflags |= AI_TAKE_COVER;
self->owner->cast_info.aiflags |= AI_RUN_LIKE_HELL;
self->owner->cast_info.currentmove = self->owner->cast_info.move_run;
self->owner->wait = -1;
combatent->owner = self->owner;
combatent->think = CheckStillHiding;
combatent->nextthink = level.time + 1; // give us some time to get there
}
return;
}
}
else if ( (self->owner->moral > MORAL_HAPPY)
&& (self->owner->health > ((MORAL_MAX - self->owner->moral) * TAKECOVER_INFRONT)))
{ // sneak up on them
if (pos = NAV_GetHidePos( self->owner, self->owner->cover_ent, HIDEPOS_CLOSER ))
{
edict_t *combatent;
if (VectorDistance( pos, self->s.origin ) > 96)
{
combatent = self;
VectorCopy( pos, combatent->s.origin );
self->owner->combat_goalent = combatent;
combatent->cast_info.aiflags |= AI_GOAL_RUN;
self->owner->cast_info.aiflags |= AI_TAKE_COVER;
self->owner->cast_info.aiflags |= AI_RUN_LIKE_HELL;
self->owner->cast_info.currentmove = self->owner->cast_info.move_run;
self->owner->wait = -1;
combatent->owner = self->owner;
combatent->think = CheckStillHiding;
combatent->nextthink = level.time + 1; // give us some time to get there
}
return;
}
}
}
self->nextthink = level.time + 0.2;
return;
}
if (self->owner->combat_goalent == self)
{
self->owner->combat_goalent = NULL;
self->owner->cast_info.aiflags &= ~AI_TAKE_COVER;
}
// not hiding anymore
G_FreeEdict( self );
}
/*
=============
AI_CheckTakeCover
Returns true if we should go hide, and we've found a good hiding position
=============
*/
void Weapon_Blackjack (edict_t *ent);
qboolean AI_CheckTakeCover( edict_t *self )
{
float *pos=NULL;
qboolean evade=true; // make evade action before fleeing
if (self->cast_info.aiflags & AI_NO_TAKE_COVER)
return false;
if (self->dont_takecover_time > level.time)
return false;
if (self->leader && !(self->cast_info.aiflags & AI_HOLD_POSITION))
return false; // no hiding when we're following
if (self->combat_goalent) // probably heading for a combat pos, let them continue
return false;
if (self->last_getcombatpos > (level.time - 1))
return false;
if (!self->enemy)
return false;
// Ridah 16-may-99, hired guys don't hide
if (self->cast_group == 1 && self->leader)
return false;
// done.
// should we hide?
// Ridah, always hide every now and then if we're a firing enemy, since that's what any sane human
// being would do
if ( !(self->cast_info.aiflags & AI_MELEE)
// && ((rand()%MORAL_MAX) < (MORAL_MAX+2 - self->moral))
&& !(self->enemy->cast_info.aiflags & AI_MELEE)
&& (!self->enemy->client || (self->enemy->client->pers.weapon && self->enemy->client->pers.weapon->ammo))
&& /*directly_*/infront(self->enemy, self)
&& AI_ClearSight( self->enemy, self, false ))
// && (self->last_gethidepos < (level.time - (1.0 + self->moral/MORAL_MAX))))
{
// cast_memory_t *mem;
// edict_t *trav;
qboolean nohide=false;
/*
// TEAM AI: if we have a friend around, only take cover if they're happily firing
mem = self->cast_info.friend_memory;
while (mem)
{
trav = &g_edicts[mem->cast_ent];
if ( (mem->timestamp > (level.time - 10))
&& (trav->health > 0)
&& (trav->enemy)
&& (!(trav->cast_info.aiflags & AI_MELEE))
&& ( (trav->noise_time < (level.time - 2))
|| (trav->cast_info.aiflags & AI_TAKE_COVER)))
{
nohide = true;
break;
}
mem = mem->next;
}
*/
if (!nohide && (pos = NAV_GetHidePos( self, self->enemy, HIDEPOS_ANY )))
{
evade = false;
goto done;
}
}
if ( (self->cast_info.aiflags & AI_MELEE)
&& ( (self->enemy->client)
&& (self->enemy->client->pers.weapon)
&& (self->enemy->client->pers.weapon->ammo))
&& (random() < 0.3 || VectorDistance( self->enemy->s.origin, self->s.origin ) > 128))
{ // don't do any other tests, we REALLY want to hide from this enemy!
if (pos = NAV_GetHidePos( self, self->enemy, HIDEPOS_FURTHER ))
{
goto done;
}
}
else if (infront( self->enemy, self ))
{
if (directly_infront( self->enemy, self ))
{
if (self->health > ((MORAL_MAX - self->moral) * TAKECOVER_DIRECTLY_INFRONT))
return false;
}
else
{
if (self->health > ((MORAL_MAX - self->moral) * TAKECOVER_INFRONT))
return false;
}
}
else if (self->health > ((MORAL_MAX - self->moral) * TAKECOVER_BEHIND))
return false;
// find somewhere to go
if (!pos)
{
if (self->cast_group)
{ // look for a friend with a gun (assume we know where all our friends are)
int i;
edict_t *other, *best=NULL;
float best_dist=2048, this_dist;
route_t route;
for (i=0; i<level.num_characters; i++)
{
other = level.characters[i];
if (!other)
continue;
if (other->health <= 0 || !other->inuse)
continue;
if (self->cast_group != other->cast_group)
continue;
if (self == other)
continue;
this_dist = VectorDistance( self->s.origin, other->s.origin );
if ((this_dist < 384) && other->enemy)
continue;
if (other->client)
goto accepted;
if (best)
{
if (best->cast_info.aiflags & AI_MELEE)
{
if (!(other->cast_info.aiflags & AI_MELEE))
{
goto accepted;
}
}
else
{
if (other->cast_info.aiflags & AI_MELEE)
{ // no good
continue;
}
}
}
else if (!(self->cast_info.aiflags & AI_MELEE))
{
if (other->cast_info.aiflags & AI_MELEE)
{ // no good, we have weapon and this friend doesn't
continue;
}
else // is it worth going to get them?
{
if (!NAV_Route_EntityToEntity( self, NULL, other, VIS_PARTIAL, false, &route ))
continue;
if (route.dist > 1000) // too far away
continue;
}
}
if (this_dist > best_dist)
continue;
accepted:
best = other;
best_dist = this_dist;
if (other->client)
break;
}
if (best)
{
//gi.dprintf( "%s fleeing to %s\n", self->name, best->name );
pos = best->s.origin;
self->cover_ent = self->enemy;
self->last_getcombatpos = level.time;
self->last_gethidepos = level.time;
goto done;
}
}
pos = NAV_GetHidePos( self, self->enemy, HIDEPOS_FURTHER );
}
done:
if (pos)
{
edict_t *combatent;
combatent = G_Spawn();
VectorCopy( pos, combatent->s.origin );
self->combat_goalent = combatent;
combatent->cast_info.aiflags |= AI_GOAL_RUN;
self->cast_info.aiflags |= AI_TAKE_COVER;
self->cast_info.aiflags |= AI_RUN_LIKE_HELL;
self->wait = -1;
combatent->owner = self;
combatent->think = CheckStillHiding;
combatent->nextthink = level.time + 1; // give us some time to get there
/*
// make sure we run AWAY from our enemy
{
node_t *node;
if (node = NAV_GetClosestNode( self, VIS_PARTIAL, true, true ))
self->nav_data.goal_index = node->index + 1;
}
*/
}
if ( evade
&& (self->last_stand_evade < (level.time - 4))
&& (pos || ((rand()%10) < 3))
&& (self->moral < 3)
&& self->cast_info.move_evade
&& self->maxs[2] == self->cast_info.standing_max_z
&& directly_infront( self->enemy, self )
&& AI_ClearSight(self, self->enemy, false)
&& self->health > self->max_health/2)
{
self->last_stand_evade = level.time;
self->cast_info.currentmove = self->cast_info.move_evade;
if (self->cast_info.backoff)
self->cast_info.backoff( self, self->enemy );
}
return (pos != NULL);
}
/*
=============
AI_ForceTakeCover
=============
*/
qboolean AI_ForceTakeCover( edict_t *self, edict_t *enemy, qboolean ignorehealth )
{
float *pos;
int hidepos_type;
if (self->cast_info.aiflags & AI_NO_TAKE_COVER)
return false;
// Note to Ryan: take a look at this. nasty hack because of crash bug
if (!(enemy))
return false;
if (enemy->svflags & SVF_MONSTER)
hidepos_type = HIDEPOS_FURTHER;
else
hidepos_type = HIDEPOS_ANY;
if (pos = NAV_GetHidePos( self, enemy, hidepos_type ))
{
edict_t *combatent;
combatent = G_Spawn();
VectorCopy( pos, combatent->s.origin );
self->combat_goalent = combatent;
combatent->cast_info.aiflags |= AI_GOAL_RUN;
combatent->cast_info.aiflags |= AI_RUN_LIKE_HELL;
self->cast_info.aiflags |= AI_TAKE_COVER;
self->cast_info.aiflags |= AI_RUN_LIKE_HELL;
combatent->owner = self;
combatent->think = CheckStillHiding;
combatent->nextthink = level.time + 1; // give us some time to get there
if (ignorehealth)
self->cast_info.aiflags |= AI_TAKECOVER_IGNOREHEALTH;
if (self->maxs[2] == self->cast_info.standing_max_z)
self->cast_info.currentmove = self->cast_info.move_run;
/*
// make sure we run AWAY from our enemy
{
node_t *node;
if (node = NAV_GetClosestNode( self, VIS_PARTIAL, true, true ))
self->nav_data.goal_index = node->index + 1;
}
*/
return true;
}
return false;
}
/*
=============
AI_TooClose
returns true if "goal" is within the AI_TOO_CLOSE_DIST range from "self"
mostly used to make sure followers don't obstruct their leader
=============
*/
qboolean AI_TooClose(edict_t *self, edict_t *goal)
{
float scale;
if (VectorCompare(goal->velocity, vec3_origin))
return false;
// allow to walk real close if they're standing on a plat and not moving
if (goal->groundentity && goal->groundentity->use && VectorCompare(goal->groundentity->velocity, vec3_origin))
return false;
scale = 1.0;
if (VectorLength(goal->velocity))
scale = 1.5;
return (VectorDistance(self->s.origin, goal->s.origin) < AI_TOO_CLOSE_DIST*scale);
}
/*
=============
AI_FollowLeader
modification of AI_TooClose(), returns true if we need to move away from, or towards the goal
=============
*/
qboolean AI_FollowLeader(edict_t *self, edict_t *goal)
{
float dist;
float scale;
dist = VectorDistance(self->s.origin, goal->s.origin);
// allow to walk real close if they're standing on a plat and not moving
if ( goal->groundentity
&& goal->groundentity->use)
{
if (goal->groundentity != self->groundentity)
return true;
else if (dist < 64 && !VectorCompare(goal->velocity, vec3_origin))
return true;
else
return false;
}
scale = 1.0;
if (VectorLength(goal->velocity))
scale = 1.5;
return ((dist < AI_TOO_CLOSE_DIST*scale) || (dist > scale*(AI_GUARDING_DIST * (infront(goal, self) ? 1 : 0.5))));
}
/*
==============
AI_End_CrouchStand_Down
Goes to the standing animation according to what we're currently doing
==============
*/
void AI_End_CrouchStand_Down(edict_t *self)
{
self->cast_info.currentmove = self->cast_info.move_crwalk;
}
/*
==============
AI_End_CrouchStand_Up
Goes to the standing animation according to what we're currently doing
==============
*/
void AI_End_CrouchStand_Up(edict_t *self)
{
self->cast_info.currentmove = self->cast_info.move_run;
}
/*
==============
AI_EndAttack
resume attacking if they so desire, otherwise "stand"
usually called at the end of pain, or attacking animations
==============
*/
void AI_EndAttack(edict_t *self)
{
mmove_t *oldmove;
// hack to turn off the flamethrow effect
if (self->s.renderfx2 & RF2_FLAMETHROWER)
self->s.renderfx2 &= ~RF2_FLAMETHROWER;
if (self->enemy && AI_CheckTakeCover(self))
{
if (self->cast_info.currentmove != self->cast_info.move_evade)
self->cast_info.currentmove = self->cast_info.move_run;
return;
}
oldmove = self->cast_info.currentmove;
if (!self->enemy || !self->cast_info.checkattack(self))
{
self->cast_info.currentmove = self->cast_info.move_stand;
self->cast_info.talk(self);
if (self->enemy)
self->cast_info.currentmove = self->cast_info.move_run;
}
}
/*
==============
AI_GetOrientation
returns one of ORIENTATION_* depending on which side of "self", "other" is on
==============
*/
int AI_GetOrientation( edict_t *self, edict_t *other )
{
vec3_t right, rorg, lorg, rvec, lvec;
float diff;
// determine right or left side impact
AngleVectors(self->s.angles, NULL, right, NULL);
VectorMA(self->s.origin, 16, right, rorg);
VectorMA(self->s.origin, -16, right, lorg);
VectorSubtract(other->s.origin, rorg, rvec);
VectorSubtract(other->s.origin, lorg, lvec);
diff = VectorLength(rvec) - VectorLength(lvec);
if (diff < -8) // right
{
return ORIENTATION_RIGHT;
}
else if (diff > 8) // left
{
return ORIENTATION_LEFT;
}
// center
return ORIENTATION_CENTER;
}
/*
==============
AI_CheckStillInair
called while jumping, so we stay on a single jumping frame while in the air
==============
*/
void AI_CheckStillInair(edict_t *self)
{
if (!self->groundentity)
self->s.frame--; // stay on the current frame
else
{
float dist;
dist = self->fall_height - self->s.origin[2];
if ( dist < 48)
{ // get out of this anim
self->cast_info.currentmove->endfunc(self);
}
else if ( dist > 256 )
{
T_Damage( self, world, world, vec3_origin, self->s.origin, vec3_origin, 2 + (int)((0.01*dist)*(0.01*dist)), 0, 0, MOD_FALLING );
}
}
}
/*
==============
AI_EndJump
called at the end of a jump, so we return to running, or crouch walking frames
==============
*/
void AI_EndJump(edict_t *self)
{
if (!self->cast_info.move_run)
return;
// clear cached nodes
// self->nav_data.cache_node = -1;
// self->nav_data.goal_index = 0;
// resume walking
if (self->maxs[2] < self->cast_info.standing_max_z)
self->cast_info.currentmove = self->cast_info.move_crwalk;
else
self->cast_info.currentmove = self->cast_info.move_run;
}
// JOSEPH 20-NOV-98
/*
==============
AI_AfterLife
Generic character elimination routine.
==============
*/
void AI_AfterLife(edict_t *self)
{
// Seconds dead
self->deadticks++;
// Set body on floor angle
if (self->deadticks <= 1)
{
/*vec3_t avec, stop;
trace_t trace;
VectorCopy(self->s.origin, stop);
stop[2] -= 16*2;
trace = gi.trace (self->s.origin, self->mins, self->maxs, stop, self, MASK_DEADSOLID);
if (trace.fraction < 1)
{
VectorCopy (trace.plane.normal, avec);
avec[0] = acos(trace.plane.normal[2])/M_PI*180;
avec[2] = atan2(trace.plane.normal[1], trace.plane.normal[0])/M_PI*180;
avec[1] = self->s.angles[1];
if (avec[2] == 180)
{
avec[0] = -avec[0];
avec[2] = 0;
}
else if (avec[2] == 90)
{
avec[2] = avec[0];
avec[0] = 0;
}
else if (avec[2] == -90)
{
avec[2] = -avec[0];
avec[0] = 0;
}
VectorCopy (avec, self->s.angles);
// Ridah, the angles seem to be opposite?
VectorScale( self->s.angles, -1, self->s.angles );
self->s.angles[YAW] = -self->s.angles[YAW]; // return YAW to normal
}*/
/*vec3_t avec1, avec2, avect, angles, start, stop, headorg, legsorg;
trace_t trace;
// Use object_bounds to get head position
{
vec3_t org, ang, mins, maxs;
vec3_t forward, right, up;
vec3_t rmins, rmaxs, pmins, pmaxs;
VectorCopy (self->s.origin, org);
VectorCopy (self->s.angles, ang);
VectorCopy (self->s.model_parts[PART_HEAD].object_bounds[0][self->s.frame].mins, mins);
VectorCopy (self->s.model_parts[PART_HEAD].object_bounds[0][self->s.frame].maxs, maxs);
AngleVectors (ang, forward, right, up);
VectorMA (org, ((mins[0] + maxs[0]) * 0.5), forward, org);
VectorMA (org, -((mins[1] + maxs[1]) * 0.5), right, org);
VectorMA (org, (mins[2] + maxs[2]) * 0.5, up, org);
// find rotated positions of mins/maxs, and then build the new min/max
VectorScale ( forward, mins[0], rmins);
VectorMA (rmins, -mins[1], right, rmins);
VectorMA (rmins, mins[2], up, rmins);
VectorScale ( forward, maxs[0], rmaxs);
VectorMA (rmaxs, -maxs[1], right, rmaxs);
VectorMA (rmaxs, maxs[2], up, rmaxs);
pmins[0] = (rmins[0] < rmaxs[0] ? rmins[0] : rmaxs[0]);
pmins[1] = (rmins[1] < rmaxs[1] ? rmins[1] : rmaxs[1]);
pmins[2] = (rmins[2] < rmaxs[2] ? rmins[2] : rmaxs[2]);
pmaxs[0] = (rmins[0] > rmaxs[0] ? rmins[0] : rmaxs[0]);
pmaxs[1] = (rmins[1] > rmaxs[1] ? rmins[1] : rmaxs[1]);
pmaxs[2] = (rmins[2] > rmaxs[2] ? rmins[2] : rmaxs[2]);
// now align the mins/maxs with the origin
mins[0] = pmins[0] - (0.5*(pmaxs[0] + pmins[0]));
mins[1] = pmins[1] - (0.5*(pmaxs[1] + pmins[1]));
mins[2] = pmins[2] - (0.5*(pmaxs[2] + pmins[2]));
maxs[0] = pmaxs[0] - (0.5*(pmaxs[0] + pmins[0]));
maxs[1] = pmaxs[1] - (0.5*(pmaxs[1] + pmins[1]));
maxs[2] = pmaxs[2] - (0.5*(pmaxs[2] + pmins[2]));
headorg[0] = org[0] + (mins[0] + maxs[0]) / 2;
headorg[1] = org[1] + (mins[1] + maxs[1]) / 2;
headorg[2] = org[2] + (mins[2] + maxs[2]) / 2;
}
// Use object_bounds to get legs position
{
vec3_t org, ang, mins, maxs;
vec3_t forward, right, up;
vec3_t rmins, rmaxs, pmins, pmaxs;
VectorCopy (self->s.origin, org);
VectorCopy (self->s.angles, ang);
VectorCopy (self->s.model_parts[PART_LEGS].object_bounds[0][self->s.frame].mins, mins);
VectorCopy (self->s.model_parts[PART_LEGS].object_bounds[0][self->s.frame].maxs, maxs);
AngleVectors (ang, forward, right, up);
VectorMA (org, ((mins[0] + maxs[0]) * 0.5), forward, org);
VectorMA (org, -((mins[1] + maxs[1]) * 0.5), right, org);
VectorMA (org, (mins[2] + maxs[2]) * 0.5, up, org);
// find rotated positions of mins/maxs, and then build the new min/max
VectorScale ( forward, mins[0], rmins);
VectorMA (rmins, -mins[1], right, rmins);
VectorMA (rmins, mins[2], up, rmins);
VectorScale ( forward, maxs[0], rmaxs);
VectorMA (rmaxs, -maxs[1], right, rmaxs);
VectorMA (rmaxs, maxs[2], up, rmaxs);
pmins[0] = (rmins[0] < rmaxs[0] ? rmins[0] : rmaxs[0]);
pmins[1] = (rmins[1] < rmaxs[1] ? rmins[1] : rmaxs[1]);
pmins[2] = (rmins[2] < rmaxs[2] ? rmins[2] : rmaxs[2]);
pmaxs[0] = (rmins[0] > rmaxs[0] ? rmins[0] : rmaxs[0]);
pmaxs[1] = (rmins[1] > rmaxs[1] ? rmins[1] : rmaxs[1]);
pmaxs[2] = (rmins[2] > rmaxs[2] ? rmins[2] : rmaxs[2]);
// now align the mins/maxs with the origin
mins[0] = pmins[0] - (0.5*(pmaxs[0] + pmins[0]));
mins[1] = pmins[1] - (0.5*(pmaxs[1] + pmins[1]));
mins[2] = pmins[2] - (0.5*(pmaxs[2] + pmins[2]));
maxs[0] = pmaxs[0] - (0.5*(pmaxs[0] + pmins[0]));
maxs[1] = pmaxs[1] - (0.5*(pmaxs[1] + pmins[1]));
maxs[2] = pmaxs[2] - (0.5*(pmaxs[2] + pmins[2]));
legsorg[0] = org[0] + (mins[0] + maxs[0]) / 2;
legsorg[1] = org[1] + (mins[1] + maxs[1]) / 2;
legsorg[2] = org[2] + (mins[2] + maxs[2]) / 2;
}
VectorCopy(headorg, start);
VectorCopy(headorg, stop);
stop[2] = start[2] - 16*4;
trace = gi.trace (start, NULL, NULL, stop, self, MASK_DEADSOLID);
if (trace.fraction < 1)
{
VectorCopy(trace.endpos, avec1);
}
else
{
VectorCopy(start, avec1);
}
VectorCopy(legsorg, start);
VectorCopy(legsorg, stop);
stop[2] = start[2] - 16*4;
trace = gi.trace (start, NULL, NULL, stop, self, MASK_DEADSOLID);
if (trace.fraction < 1)
{
VectorCopy(trace.endpos, avec2);
}
else
{
VectorCopy(start, avec2);
}
if ((avec1[2] != avec2[2]))
{
if ((headorg[0] > self->s.origin[0]) || (headorg[1] > self->s.origin[1]))
{
VectorSubtract(avec2, avec1, avect);
}
else
{
VectorSubtract(avec1, avec2, avect);
}
VectorNormalize(avect);
vectoangles(avect, angles);
angles[1] = self->s.angles[1];
VectorCopy (angles, self->s.angles);
}*/
}
// If the body is not to be moved
if (!self->deadticks)
{
self->think = NULL;
self->nextthink = -1;
self->solid = 0;
return;
}
if (self->deadticks > (60*1))
{
edict_t *other=NULL;
trace_t tr;
vec3_t end, travelvec;
byte i;
long len;
float dir;
int dir2;
byte BW = SFX_BLOOD_WIDTH;
byte BH = SFX_BLOOD_HEIGHT;
long rnd1, rnd2;
// Ridah, 11/22/98 added distance check and optimization
// If no player can see the body
// while (other = G_Find(other, FOFS(classname), "player"))
for (i=0; i<(int)maxclients->value; i++)
{
other = &g_edicts[1 + i]; // clients start at g_edicts[1]
if (!other->inuse)
continue;
if (infront(other, self) || (VectorDistance(other->s.origin, self->s.origin) < 1024))
{
self->nextthink = level.time + FRAMETIME * 20;
return;
}
}
// Determine drag direction
dir = -10;
dir2 = 0;
VectorCopy (self->s.origin, end);
end[dir2] += dir * 20;
tr = gi.trace (self->s.origin, NULL, NULL, end, self, MASK_SHOT);
VectorSubtract(self->s.origin, tr.endpos, travelvec);
len = VectorNormalize(travelvec);
if (len > 100) goto dragit;
dir = 10;
dir2 = 0;
VectorCopy (self->s.origin, end);
end[dir2] += dir * 20;
tr = gi.trace (self->s.origin, NULL, NULL, end, self, MASK_SHOT);
VectorSubtract(self->s.origin, tr.endpos, travelvec);
len = VectorNormalize(travelvec);
if (len > 100) goto dragit;
dir = -10;
dir2 = 1;
VectorCopy (self->s.origin, end);
end[dir2] += dir * 20;
tr = gi.trace (self->s.origin, NULL, NULL, end, self, MASK_SHOT);
VectorSubtract(self->s.origin, tr.endpos, travelvec);
len = VectorNormalize(travelvec);
if (len > 100) goto dragit;
dir = 10;
dir2 = 1;
VectorCopy (self->s.origin, end);
end[dir2] += dir * 20;
tr = gi.trace (self->s.origin, NULL, NULL, end, self, MASK_SHOT);
VectorSubtract(self->s.origin, tr.endpos, travelvec);
len = VectorNormalize(travelvec);
if (len > 100) goto dragit; else goto forgetit;
dragit:
rnd1 = ((rand()&7)-4);
// Drag the body
for (i=0; i <= 10; i++)
{
VectorCopy (self->s.origin, end);
end[2] -= 32+8;
tr = gi.trace (self->s.origin, NULL, NULL, end, self, MASK_SHOT);
SurfaceSpriteEffect(SFX_SPRITE_SURF_BLOOD1, BW--, BH--,
tr.ent, tr.endpos, tr.plane.normal);
self->s.origin[dir2] += dir;
rnd2 = ((rand()&3)-2);
if (dir == 1)
self->s.origin[2] += (rnd1 + rnd2);
else
self->s.origin[1] += (rnd2 + rnd2);
}
forgetit:
// Unload this cast's memory, since we won't be needing it anymore
//AI_UnloadCastMemory( self );
// Remove the body
self->think = NULL;
self->nextthink = -1;
self->solid = 0;
self->svflags |= SVF_NOCLIENT;
return;
}
self->nextthink = level.time + FRAMETIME * 10;
}
// END JOSEPH
/* trace_t tr;
vec3_t end;
VectorCopy (self->s.origin, end);
end[2] -= 32+8;
tr = gi.trace (self->s.origin, NULL, NULL, end, self, MASK_SHOT);
SurfaceSpriteEffect(SFX_SPRITE_SURF_BLOOD1, SFX_BLOOD_WIDTH, SFX_BLOOD_HEIGHT,
tr.ent, tr.endpos, tr.plane.normal);
*/
/*
==============
AI_EndDeath
Generic end of death routine. Should call this for all characters that die, then do any
special stuff for that character.
==============
*/
void AI_EndDeath(edict_t *self)
{
// Ridah 16-may-99, stop dead bodies from sinking through floor
if (!self->groundentity)
{
return;
}
// done.
// Ridah, 7-5-99, fixes shooting dead bodies
// if (!(self->svflags & SVF_DEADMONSTER))
if (self->mins[0] != -64)
{
// Ridah, had to expand this even further to accomodate the punk death frames
VectorSet (self->mins, -64, -64, -24);
VectorSet (self->maxs, 64, 64, -4);
self->movetype = MOVETYPE_NONE;
self->svflags |= SVF_DEADMONSTER;
gi.linkentity (self);
/*
// if this position isn't valid, use the old, smaller box
{
trace_t tr;
tr = gi.trace( self->s.origin, self->mins, self->maxs, self->s.origin, self, MASK_SHOT );
if (tr.startsolid)
{
VectorSet (self->mins, -16, -16, -24);
VectorSet (self->maxs, 16, 16, 0);
gi.linkentity (self);
}
}
*/
}
// Ridah, modified this so we only spawn blood once
if (!(self->flags & FL_SPAWNED_BLOODPOOL) && VectorCompare( self->velocity, vec3_origin ))
{
SpawnBloodPool (self);
self->flags |= FL_SPAWNED_BLOODPOOL;
}
AI_AfterLife(self);
// END JOSEPH
}
/*
===============
AI_SideTrace
Returns a 1 if a RIGHT rotation will point in a direction that is free of obstacles
Returns -1 if LEFT, 0 otherwise
===============
*/
int AI_SideTrace( edict_t *self, float dist, float inyaw, int side )
{
trace_t tr;
vec3_t angle, vec, end, end2, mins;
float yaw;
int count=0;
VectorCopy( self->mins, mins );
mins[2] += 16;
// pick a random direction
if ((side == 1) || (!side && (random() <= 0.5)))
yaw = inyaw;
else
yaw = -inyaw;
while (1)
{
VectorCopy(self->s.angles, angle);
angle[YAW] += yaw;
AngleVectors( angle, vec, NULL, NULL );
// trace in this direction
VectorMA( self->s.origin, dist, vec, end );
tr = gi.trace( self->s.origin, mins, self->maxs, end, self, MASK_PLAYERSOLID );
if (tr.fraction == 1)
{
// make sure we can see our enemy from here
if (self->enemy)
{
vec3_t oldorg;
int result;
VectorCopy( self->s.origin, oldorg );
VectorCopy( tr.endpos, self->s.origin );
result = AI_ClearSight(self, self->enemy, true);
VectorCopy( oldorg, self->s.origin );
if (!result)
{
goto blocked;
}
}
// check this is on ground
VectorCopy( end, end2 );
end2[2] -= 64;
tr = gi.trace( end, mins, self->maxs, end2, self, MASK_PLAYERSOLID );
if (tr.fraction < 1)
{
// go ahead!
if (yaw < 0)
return -1; // left side
else
return 1; // right side
}
}
blocked:
if (count++ || !inyaw || side)
break;
yaw *= -1;
}
return 0;
}
void AI_CheckStillClimbingLadder( edict_t *self )
{
if (self->groundentity)
{ // we're on ground, so resume moving
self->cast_info.currentmove = self->cast_info.move_stand;
}
}
/*
===============
AI_EndRun
checks to see if we should walk or run
===============
*/
void AI_EndRun( edict_t *self )
{
if (!self->last_goal)
return;
if (!self->cast_info.move_run)
return;
if ( (self->enemy != self->last_goal)
&& (VectorDistance( self->s.origin, self->last_goal->s.origin ) < AI_GUARDING_DIST * 2 )
&& (VectorLength( self->last_goal->velocity) < 200))
{ // close enough to walk
self->cast_info.currentmove = self->cast_info.move_runwalk;
}
else if ((self->last_goal != self->goal_ent) && (!self->last_goal || self->last_goal != self->guard_ent))
{
self->cast_info.currentmove = self->cast_info.move_run;
}
}
/*
===============
AI_YawTrace
Returns a 1 if a YAW rotation from self's angles will point in a direction that is free of obstacles
Returns 0 otherwise
===============
*/
int AI_YawTrace( edict_t *self, float dist, float inyaw )
{
trace_t tr;
vec3_t angle, vec, end, end2, mins;
int count=0;
VectorCopy( self->mins, mins );
mins[2] += 16;
VectorCopy(self->s.angles, angle);
angle[YAW] = anglemod(angle[YAW] + inyaw);
AngleVectors( angle, vec, NULL, NULL );
// trace in this direction
VectorMA( self->s.origin, dist, vec, end );
tr = gi.trace( self->s.origin, mins, self->maxs, end, self, MASK_PLAYERSOLID );
if (tr.fraction == 1)
{
// check this is on ground
VectorCopy( end, end2 );
end2[2] -= 64;
tr = gi.trace( end, mins, self->maxs, end2, self, MASK_PLAYERSOLID );
if (tr.fraction < 1)
{
// go ahead!
return true;
}
}
return false;
}
/*
=============
AI_StartRun
Start us moving
=============
*/
void AI_StartRun( edict_t *self )
{
// start running to them
if ( self->maxs[2] > DUCKING_MAX_Z )
self->cast_info.currentmove = self->cast_info.move_run;
else
self->cast_info.currentmove = self->cast_info.move_crwalk;
}
void AI_FreeAndClearGoalEnt( edict_t *self )
{
if (self->owner->goal_ent == self)
self->owner->goal_ent = NULL;
G_FreeEdict( self );
}
/*
=============
ai_stand
Used for standing around and looking for TARGETS
Distance is for slight position adjustments needed by the animations
==============
*/
void ai_stand (edict_t *self, float dist)
{
if (dist)
M_walkmove (self, self->s.angles[YAW], dist);
if (self->leader && !self->leader->active_node_data) // we're loading up a new map
{
return;
}
if (self->cast_info.aiflags & AI_DUCKATTACK)
{ // return to standing
self->cast_info.aiflags &= ~AI_DUCKATTACK;
if (self->maxs[2] < self->cast_info.standing_max_z)
{
self->maxs[2] = self->cast_info.standing_max_z;
self->cast_info.currentmove = self->cast_info.move_stand_up;
self->s.frame = self->cast_info.currentmove->firstframe;
return;
}
}
// Ridah, Check DUCKING/STANDING status
if (self->maxs[2] < self->cast_info.standing_max_z)
{
if (self->cast_info.move_crstand && self->cast_info.currentmove != self->cast_info.move_crstand)
{ // we should be ducking
self->cast_info.currentmove = self->cast_info.move_crstand;
self->s.frame = self->cast_info.currentmove->firstframe;
}
else if (self->leader && !(self->cast_info.aiflags & AI_TAKE_COVER) && (self->leader->maxs[2] > DUCKING_MAX_Z)) // can we stand now?
{
trace_t tr;
self->maxs[2] = self->cast_info.standing_max_z;
tr = gi.trace(self->s.origin, self->mins, self->maxs, self->s.origin, self, MASK_PLAYERSOLID);
if (tr.startsolid) // can't safely stand
{
self->maxs[2] = DUCKING_MAX_Z;
}
else
{
self->cast_info.currentmove = self->cast_info.move_stand;
self->s.frame = self->cast_info.currentmove->firstframe;
}
}
}
else // not physically ducking, so make sure our animation frame reflects this
{
if ( (self->cast_info.currentmove == self->cast_info.move_crstand)
&& (self->cast_info.move_crstand != self->cast_info.move_stand))
{ // we should be standing
self->cast_info.currentmove = self->cast_info.move_stand;
self->s.frame = self->cast_info.currentmove->firstframe;
}
}
// Ridah, done.
// Ridah, Do pausetime stuff, making sure we stay paused if we have to, or resume moving otherwise
if (!self->groundentity)
{
if (self->cast_info.move_jump && self->cast_info.currentmove != self->cast_info.move_jump)
self->cast_info.currentmove = self->cast_info.move_jump;
return;
}
else if (self->groundentity->use && !VectorCompare(self->groundentity->velocity, vec3_origin))
{
self->cast_info.pausetime = level.time + 1;
}
// make sure we don't abort our goal waypoint next time we try to move
self->moveinfo.wait = level.time + 3;
self->wait = level.time + 3;
if (self->cast_info.pausetime > level.time)
{
// see if we should wait for a lift or door
if (self->target_ent)
{
if (VectorCompare(self->target_ent->velocity, vec3_origin) && VectorCompare(self->target_ent->avelocity, vec3_origin))
{ // it's not moving, are we at our destination?
if (self->target_ent->use != Use_Plat)
{
if (self->groundentity != self->target_ent)
{ // stopped, and we're not standing on it, so proceed
self->target_ent = NULL;
self->cast_info.pausetime = 0;
return;
}
else if (self->target_ent->nextthink > (level.time + 0.1))
{
if (!self->nav_data.goal_index || (VectorDistance(level.node_data->nodes[self->nav_data.goal_index-1]->origin, self->s.origin) < 128))
{
self->target_ent = NULL;
self->cast_info.pausetime = 0;
return;
}
}
else // it's stopped, and not about to go anywhere
{
self->target_ent = NULL;
self->cast_info.pausetime = 0;
return;
}
}
else if (self->groundentity == self->target_ent)
{
if (self->target_ent->moveinfo.state == STATE_TOP)
{
self->target_ent = NULL;
self->cast_info.pausetime = 0;
return;
}
}
else if (self->target_ent->moveinfo.state == STATE_BOTTOM)
{
self->target_ent = NULL;
self->cast_info.pausetime = 0;
return;
}
}
// keep waiting
self->cast_info.pausetime = level.time + 0.2;
}
if (self->groundentity && self->groundentity->use)
{ // make sure all corners are on the platform
trace_t tr;
vec3_t org, orgdest;
float x, y;
for (x=-16; x<24; x+=32)
{
for (y=-16; y<24; y+=32)
{
VectorSet(org, self->s.origin[0]+x, self->s.origin[1]+y, self->s.origin[2]-23);
VectorSet(orgdest, self->s.origin[0]+x, self->s.origin[1]+y, self->s.origin[2]-30);
tr = gi.trace(org, NULL, NULL, orgdest, self, MASK_SOLID);
if ((tr.fraction == 1) || (tr.ent != self->groundentity))
{
// set a running frame
if (self->cast_info.move_run)
self->s.frame = self->cast_info.move_run->firstframe
+ ( ((int)(level.time * 10))
% (self->cast_info.move_run->lastframe - self->cast_info.move_run->firstframe));
AI_MoveToPlatCenter(self, self->groundentity);
return;
}
}
}
}
// should we attack someonw while waiting?
else if (self->enemy || AI_FindTarget(self))
{
if (AI_BeginAttack(self))
{
self->cast_info.attack( self );
}
}
// should we get out of the way?
else if (self->target_ent && self->target_ent->velocity[2])
{
// are we within the XY axis bounds?
if ( ( (self->s.origin[0] > (self->target_ent->absmin[0] - 20))
&& (self->s.origin[0] < (self->target_ent->absmax[0] + 20)))
&& ( (self->s.origin[1] > (self->target_ent->absmin[1] - 20))
&& (self->s.origin[1] < (self->target_ent->absmax[1] + 20))))
{
float *pos;
vec3_t mins, maxs;
// move out of it's way
// set a running frame
if (self->cast_info.move_run)
self->s.frame = self->cast_info.move_run->firstframe
+ ( ((int)(level.time * 10))
% (self->cast_info.move_run->lastframe - self->cast_info.move_run->firstframe));
VectorSet( mins,
self->target_ent->absmin[0] - 20,
self->target_ent->absmin[1] - 20,
-9999 );
VectorSet( maxs,
self->target_ent->absmax[0] + 20,
self->target_ent->absmax[1] + 20,
9999 );
// look for a safe position
if (pos = NAV_GetReachableNodeOutsideBounds ( self, mins, maxs ))
{
vec3_t vec;
// face this direction
VectorSubtract( pos, self->s.origin, vec );
VectorNormalize( vec );
self->ideal_yaw = vectoyaw( vec );
M_ChangeYaw( self );
// move towards it
M_walkmove( self, self->ideal_yaw, 10 );
}
}
}
return;
}
// Ridah, done.
// Ridah, check for a reason to do something (if we're too close to our leader, or there is an enemy we should seek)
if ((!(self->cast_info.aiflags & AI_HOLD_POSITION) || self->enemy) && self->leader && !(self->cast_info.aiflags & AI_TAKE_COVER))
{
if ( !(self->leader->waterlevel) // don't follow a swimming leader
&& AI_FollowLeader(self, self->leader))
{
self->last_goal = self->leader;
AI_EndRun(self); // does distance checking, will start us moving
if (self->cast_info.currentmove->frame->aifunc != ai_stand && VectorDistance(self->s.origin, self->leader->s.origin) <= AI_TOO_CLOSE_DIST)
{ // look for a node we can reach
AI_GetAvoidDirection( self, self->leader );
self->cast_info.currentmove = self->cast_info.move_avoid_walk;
}
}
}
if (self->cover_ent && (self->cast_info.aiflags & AI_TAKE_COVER))
{
float goaldist;
if ( self->combat_goalent
&& ( ((goaldist = VectorDistance( self->s.origin, self->combat_goalent->s.origin )) > 64)
|| ( !ValidBoxAtLoc( self->combat_goalent->s.origin, self->mins, self->maxs, self, MASK_PLAYERSOLID )
&& goaldist < 128 )))
{ // we should be running to it
self->cast_info.currentmove = self->cast_info.move_run;
return;
}
// pretend we're ducking for vis test
self->viewheight = DUCKING_MAX_Z - 4;
// if they can see us, and we're standing around, abort taking cover
if (AI_ClearSight( self->cover_ent, self, false))
{
self->cast_info.aiflags &= ~AI_TAKE_COVER;
self->combat_goalent = NULL;
self->cast_info.currentmove = self->cast_info.move_run;
return;
}
// Ridah, 5-7-99, had to modify this to prevent Betty crouching bug
self->viewheight = self->maxs[2] - 4;
if (self->maxs[2] > DUCKING_MAX_Z && AI_ClearSight( self->cover_ent, self, false))
{
self->maxs[2] = DUCKING_MAX_Z;
if (self->cast_info.move_crouch_down)
self->cast_info.currentmove = self->cast_info.move_crouch_down;
}
return;
}
/*
if ( self->goal_ent
&& !self->leader
&& !(self->cast_info.aiflags & AI_TAKE_COVER)
&& ( !self->enemy
|| (self->cast_info.aiflags & AI_GOAL_IGNOREENEMY)))
*/
if (self->goal_ent && !self->leader && !(self->cast_info.aiflags & AI_TAKE_COVER))
{
float goaldist;
if ( (self->cast_info.move_run)
&& ( (self->cast_info.goal_ent_pausetime < level.time)))
// || (self->goal_ent->solid == SOLID_TRIGGER)))
{
goaldist = VectorDistance(self->s.origin, self->goal_ent->s.origin);
if ( (self->goal_ent->touch == path_corner_cast_touch)
|| (goaldist > 256 ))
{
// Ridah, fixes jerky movement in SR1 intro
if (level.cut_scene_time)
{
if ((self->goal_ent->touch == path_corner_cast_touch) && (self->name_index == NAME_INTROGUY1) && (goaldist < 32))
{ // simulate touching it
self->goal_ent->touch( self->goal_ent, self, NULL, NULL );
return;
}
}
self->nav_data.cache_node = -1;
self->nav_data.goal_index = 0;
self->cast_info.currentmove = self->cast_info.move_runwalk;
return;
}
if (self->goal_ent->target)
{ // face the target (eg. the Radio in Skidrow points into the room)
edict_t *targ;
if (targ = G_Find( NULL, FOFS( targetname ), self->goal_ent->target ))
{
if (!directly_infront( self, targ )) // straighten up
{
if (self->cast_info.avoid)
{
self->cast_info.avoid( self, targ, true );
return;
}
}
}
else
{
// gi.dprintf( "ai_stand: Unable to locate 'target' to face\n" );
}
}
else if (!directly_infront( self, self->goal_ent )) // straighten up
{
if (self->cast_info.avoid)
{
self->cast_info.avoid( self, self->goal_ent, true );
return;
}
}
if (!(self->goal_ent->cast_info.aiflags & AI_GOALENT_MANUAL_CLEAR))
{
self->goal_ent = NULL; // we made it there
}
}
}
// If guarding something, and we've moved out of range, abort the pursuit
if (self->guard_ent && !(self->cast_info.aiflags & AI_TAKE_COVER))
{
if ( (VectorDistance( self->s.origin, self->guard_ent->s.origin ) > self->guard_ent->guard_radius)
|| (!AI_ClearSight(self, self->guard_ent, false)))
{
AI_StartRun ( self );
return;
}
}
// RAFAEL 28-dec-98
if (AI_HearPlayer (self))
{
return;
}
// look for things to be hostile at
if (self->enemy)
{
AI_StartAttack( self, self->enemy );
return;
}
else if (AI_FindTarget (self))
{
return;
}
if (!(self->cast_info.aiflags & AI_TAKE_COVER) && !(self->spawnflags & 1) && (self->cast_info.idle) && (level.time > self->cast_info.idle_time))
{
if (self->cast_info.idle_time)
{
self->cast_info.idle (self);
self->cast_info.idle_time = level.time + 15 + random() * 15;
}
else
{
self->cast_info.idle_time = level.time + random() * 15;
}
}
if ( !(self->cast_info.aiflags & AI_TAKE_COVER)
&& !(self->goal_ent)
&& (self->target))
// Ridah, removed this optimization, since we are always using trigger spawned characters anyway
// && ( (self->spawnflags & SPAWNFLAG_IMMEDIATE_FOLLOW_PATH)
// || ( (VectorDistance(self->s.origin, g_edicts[1].s.origin) < 800)
// && (gi.inPVS(g_edicts[1].s.origin, self->s.origin))))) // Optimization, so we only walk if the player is close
{ // resume following our path_corner
self->goal_ent = G_PickTarget(self->target);
if (!self->goal_ent)
{
gi.dprintf ("%s can't find target %s at %s\n", self->classname, self->target, vtos(self->s.origin));
self->target = NULL;
}
}
// if we're crouched, and we can stand, then go for it
if (self->maxs[2] < self->cast_info.standing_max_z)
{
if (self->stand_if_idle_time < level.time)
{
if (self->stand_if_idle_time > (level.time - 0.5))
{
self->maxs[2] = self->cast_info.standing_max_z;
if (!ValidBoxAtLoc( self->s.origin, self->mins, self->maxs, self, MASK_PLAYERSOLID ))
self->maxs[2] = DUCKING_MAX_Z;
else if (self->cast_info.move_stand_up)
self->cast_info.currentmove = self->cast_info.move_stand_up;
}
else
{
self->stand_if_idle_time = level.time + 3;
}
}
}
if ((self->cast_info.aiflags & AI_HOLD_POSITION) && !self->enemy)
{
// Ridah, 6-jun-99, Friendly's should avoid grenades when staying put, then come back
if (!(self->cast_info.aiflags & AI_TAKE_COVER) || !(self->cover_ent) || (self->cover_ent->svflags & SVF_MONSTER))
{ // we aren't avoiding anything
if (VectorDistance( self->s.origin, self->holdpos_ent->s.origin ) >= AI_GUARDING_DIST)
{
self->cast_info.currentmove = self->cast_info.move_run;
}
}
}
}
/*
=============
ai_walk
The monster is walking it's beat
=============
*/
void ai_walk (edict_t *self, float dist)
{
M_MoveToGoal (self, dist);
// check for noticing an enemy
if (AI_FindTarget (self))
return;
if ((self->cast_info.search) && (level.time > self->cast_info.idle_time))
{
if (self->cast_info.idle_time)
{
self->cast_info.search (self);
self->cast_info.idle_time = level.time + 15 + random() * 15;
}
else
{
self->cast_info.idle_time = level.time + random() * 15;
}
}
}
/*
=============
ai_charge
Turns towards target and advances
Use this call with a distnace of 0 to replace ai_face
==============
*/
void ai_charge (edict_t *self, float dist)
{
vec3_t v;
if (self->enemy)
{
VectorSubtract (self->enemy->s.origin, self->s.origin, v);
self->ideal_yaw = vectoyaw(v);
M_ChangeYaw (self);
}
if (dist)
if (!M_walkmove (self, self->s.angles[YAW], dist ))
M_walkmove (self, self->s.angles[YAW] + 60*(rand()%3 - 1), 0.5 * dist);
}
qboolean ai_checksafeground( edict_t *self, vec3_t oldpos )
{
vec3_t end, start;
trace_t tr;
float x, y;
for (x=-14; x<=14; x+=28)
{
for (y=-14; y<=14; y+=28)
{
VectorCopy( self->s.origin, start );
start[0] += x;
start[1] += y;
VectorCopy( start, end );
end[2] += self->mins[2] - 16;
tr = gi.trace( start, vec3_origin, vec3_origin, end, self, MASK_PLAYERSOLID );
if (tr.fraction == 1)
{
VectorCopy( oldpos, self->s.origin );
gi.linkentity( self );
return false;
}
}
}
return true;
}
/*
=============
ai_turn
don't move, but turn towards ideal_yaw
Distance is for slight position adjustments needed by the animations
=============
*/
void ai_turn (edict_t *self, float dist)
{
vec3_t oldpos;
// qboolean wassafeground;
// wassafeground = ai_checksafeground( self, oldpos );
VectorCopy( self->s.origin, oldpos );
self->cast_info.aiflags &= ~AI_TURN_BLOCKED;
if (dist > 32)
dist = 32;
if (self->groundentity && dist
&& (!(self->cast_info.aiflags & AI_NOWALK_FACE))) // Ridah 5-8-99, fixes "aiflags 1" not working
{
if (!M_walkmove (self, self->s.angles[YAW], dist) && !self->leader)
{
self->cast_info.aiflags |= AI_TURN_BLOCKED;
if (self->cast_info.currentmove->endfunc == AI_EndAttack)
{ // call it now, since we can't move
AI_EndAttack( self );
}
}
}
if (self->cast_info.avoid_ent)
{
// if not above safe ground, go back
// if (wassafeground && dist && !ai_checksafeground( self, oldpos ))
if (dist && !ai_checksafeground( self, oldpos ))
{
mmove_t *oldmove;
if (self->cast_info.currentmove->endfunc == AI_EndAttack)
{ // call it now, since we can't move
oldmove = self->cast_info.currentmove;
AI_EndAttack( self );
if (self->cast_info.currentmove == oldmove)
{
self->cast_info.currentmove = self->cast_info.move_stand;
}
goto done;
}
}
if (self->cast_info.last_avoid > (level.time - 1.5))
{
vec3_t vec;
VectorSubtract( self->cast_info.avoid_ent->s.origin, self->s.origin, vec );
VectorNormalize( vec );
self->ideal_yaw = vectoyaw( vec );
if ( (self->cast_info.currentmove->endfunc == AI_EndAttack)
&& (directly_infront( self, self->cast_info.avoid_ent )))
{ // call it now, since we're facing them
AI_EndAttack( self );
}
}
else if (dist) // been too long
{
self->cast_info.avoid_ent = NULL;
}
else // just straighten up
{
vec3_t vec;
VectorSubtract( self->cast_info.avoid_ent->s.origin, self->s.origin, vec );
VectorNormalize( vec );
self->ideal_yaw = vectoyaw(vec);
}
}
else if (self->enemy)
{
vec3_t vec;
VectorSubtract( self->enemy->s.origin, self->s.origin, vec );
VectorNormalize( vec );
self->ideal_yaw = vectoyaw(vec);
}
done:
M_ChangeYaw (self);
}
void ai_turn2 (edict_t *self, float dist)
{
/* Ridah, ai_turn does this now
// quick fix to face the player
if (self->enemy)
{
vec3_t vec;
VectorSubtract( self->enemy->s.origin, self->s.origin, vec );
VectorNormalize( vec );
self->ideal_yaw = vectoyaw( vec );
M_ChangeYaw( self );
}
*/
self->cast_info.avoid_ent = self->enemy;
ai_turn (self, dist);
}
/*
==============
ai_sidestep
move "dist" to the right
==============
*/
void ai_sidestep( edict_t *self, float dist)
{
vec3_t oldpos;
VectorCopy( self->s.origin, oldpos );
self->cast_info.aiflags &= ~AI_TURN_BLOCKED;
if (dist > 32)
dist = 32;
if (dist)
{
if (!M_walkmove (self, self->s.angles[YAW] + 90, dist))
{
self->cast_info.aiflags |= AI_TURN_BLOCKED;
if (self->cast_info.currentmove->endfunc == AI_EndAttack)
{ // call it now, since we can't move
mmove_t *om;
om = self->cast_info.currentmove;
AI_EndAttack( self );
if (self->cast_info.currentmove == om)
{
self->cast_info.currentmove = self->cast_info.move_run;
}
}
}
}
if (self->cast_info.avoid_ent)
{
// if not above safe ground, go back
if (dist && !ai_checksafeground( self, oldpos ))
{
if (self->cast_info.currentmove->endfunc == AI_EndAttack)
{ // call it now, since we can't move
AI_EndAttack( self );
}
}
if (self->cast_info.last_avoid > (level.time - 1.5))
{
vec3_t vec;
VectorSubtract( self->cast_info.avoid_ent->s.origin, self->s.origin, vec );
VectorNormalize( vec );
self->ideal_yaw = vectoyaw( vec );
}
else // been too long
{
self->cast_info.avoid_ent = NULL;
}
}
else if (self->enemy)
{
vec3_t vec;
VectorSubtract( self->enemy->s.origin, self->s.origin, vec );
VectorNormalize( vec );
self->ideal_yaw = vectoyaw(vec);
}
M_ChangeYaw (self);
}
/*
=============
range
returns the range catagorization of an entity reletive to self
0 melee range, will become hostile even if back is turned
1 visibility and infront, or visibility and show hostile
2 infront and show hostile
3 only triggered by damage
=============
*/
int range (edict_t *self, edict_t *other)
{
vec3_t v;
float len;
VectorSubtract (self->s.origin, other->s.origin, v);
len = VectorLength (v);
if (len < MELEE_DISTANCE)
return RANGE_MELEE;
if (len < 500)
return RANGE_NEAR;
if (len < 1000)
return RANGE_MID;
return RANGE_FAR;
}
/*
=============
visible
returns 1 if the entity is visible to self, even if not infront ()
=============
*/
qboolean visible (edict_t *self, edict_t *other)
{
vec3_t spot1;
vec3_t spot2;
trace_t trace;
VectorCopy (self->s.origin, spot1);
spot1[2] += self->viewheight;
VectorCopy (other->s.origin, spot2);
spot2[2] += other->viewheight;
trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE);
if (trace.fraction == 1.0)
return true;
return false;
}
/*
=============
infront
returns 1 if other is in front (in sight) of self
=============
*/
qboolean infront (edict_t *self, edict_t *other)
{
vec3_t vec;
float dot;
vec3_t forward;
AngleVectors (self->s.angles, forward, NULL, NULL);
VectorSubtract (other->s.origin, self->s.origin, vec);
VectorNormalize (vec);
dot = DotProduct (vec, forward);
if (dot > 0.2)
return true;
return false;
}
/*
=============
directly_infront
returns 1 if other is directly in front of self (pointing at)
=============
*/
qboolean directly_infront (edict_t *self, edict_t *other)
{
vec3_t vec;
float dot;
vec3_t forward, ang;
float len;
VectorCopy( self->s.angles, ang );
ang[PITCH] = 0;
AngleVectors (ang, forward, NULL, NULL);
VectorSubtract (other->s.origin, self->s.origin, vec);
vec[2] = 0;
len = VectorNormalize (vec);
dot = DotProduct (vec, forward);
if (dot > (0.98 - (len < 1024 ? 0.1 * (1.0 - (len/1024.0)) : 0)))
return true;
return false;
}
/*
=============
directly_infront_angles
returns 1 if other is directly in front of self (pointing at)
=============
*/
qboolean directly_infront_angle (vec3_t ang1, edict_t *self, edict_t *other)
{
vec3_t vec;
float dot;
vec3_t forward;//, ang;
// VectorCopy( self->s.angles, ang );
// ang[PITCH] = 0;
AngleVectors (ang1, forward, NULL, NULL);
VectorSubtract (other->s.origin, self->s.origin, vec);
// vec[2] = 0;
VectorNormalize (vec);
dot = DotProduct (vec, forward);
if (dot > 0.98)
return true;
return false;
}
//=============================================================================
/*
============
FacingIdeal
============
*/
qboolean FacingIdeal(edict_t *self)
{
float delta;
delta = anglemod(self->s.angles[YAW] - self->ideal_yaw);
if (delta > 45 && delta < 315)
return false;
return true;
}
//=============================================================================
//=============================================================================
extern void button_use (edict_t *self, edict_t *other, edict_t *activator);
extern void func_explosive_use(edict_t *self, edict_t *other, edict_t *activator);
extern void func_explosive_explode (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point, int mdx_part, int mdx_subobject);
// Ridah, new AI system movements functions
/*
================
AI_canmove
tries a (CPU friendly) move to the destination point, making sure we don't move inside
another solid, into water, or fall off an edge
================
*/
qboolean AI_canmove( edict_t *self, vec3_t dest )
{ // returns true if the dest box is valid
trace_t tr;
vec3_t dropdest;
float lift = 0;
vec3_t dir;
VectorSubtract( dest, self->s.origin, dir );
VectorNormalize( dir );
tr = gi.trace(dest, self->mins, self->maxs, dest, self, MASK_PLAYERSOLID|CONTENTS_MONSTERCLIP);
if (tr.startsolid || tr.allsolid)
{ // try lifting up slightly
dest[2] += (lift = 24);
tr = gi.trace(dest, self->mins, self->maxs, dest, self, MASK_PLAYERSOLID | CONTENTS_MONSTERCLIP);
if (tr.startsolid || tr.allsolid)
dest[2] -= lift;
}
if (tr.startsolid || tr.allsolid)
{
edict_t *activator;
if (tr.ent == world || !(tr.ent->svflags & SVF_MONSTER))
{ // try a line trace to find a blocking character
tr = gi.trace(self->s.origin, NULL, NULL, dest, self, MASK_PLAYERSOLID|CONTENTS_MONSTERCLIP);
}
// get this character to get out of our way
if ( (tr.fraction < 1)
&& (tr.ent != world) /* && [tr.ent is not an enemy (neutral or friend)]*/
&& (tr.ent->cast_info.avoid))
{
if (tr.ent->cast_info.last_avoid < (level.time - 2))
if ( (tr.ent->cast_info.currentmove->frame[0].aifunc == ai_stand)
|| (tr.ent->enemy && (VectorDistance(tr.ent->enemy->s.origin, tr.ent->s.origin) > 64)))
{
// tell them to move out the way, since they're not doing much
tr.ent->cast_info.avoid(tr.ent, self, false);
}
// stop going for our current node
self->nav_data.cache_node = -1;
self->nav_data.goal_index = 0;
// pause for a bit?
if (!self->enemy)
{
self->cast_info.currentmove = self->cast_info.move_stand;
self->cast_info.pausetime = level.time + 1;
}
return false;
}
else if ( (tr.ent->use)
&& (tr.ent->use == button_use)
&& (tr.ent->targetname)
&& (!self->activator)
&& (activator = G_Find( NULL, FOFS(target), tr.ent->targetname )))
{ // go for this button
self->activator = activator;
// stop going for our current node
self->nav_data.cache_node = -1;
self->nav_data.goal_index = 0;
}
else if ((tr.ent->die == func_explosive_explode) && (tr.ent->takedamage == DAMAGE_YES))
{ // blow it up
T_Damage( tr.ent, self, self, dir, tr.endpos, vec3_origin, 50, 0, 0, MOD_UNKNOWN );
}
else //if (tr.ent->client || tr.ent->svflags & SVF_MONSTER)
{ // go around it
int res;
res = AI_SideTrace( self, 32, 90, 0 );
if (res < 0)
self->ideal_yaw = self->s.angles[YAW] - 90;
else if (res > 0)
self->ideal_yaw = self->s.angles[YAW] - 90;
M_ChangeYaw( self );
}
return false;
}
// if (self->waterlevel) // swimming grunt doesn't need to check for onground
// return true;
VectorCopy(dest, dropdest);
dropdest[2] -= (16 + lift);
dest[2] += 0.1;
tr = gi.trace(dest, self->mins, self->maxs, dropdest, self, MASK_PLAYERSOLID|CONTENTS_MONSTERCLIP);
dest[2] -= 0.1;
if (!tr.startsolid && !tr.allsolid && (tr.fraction < 1))
{
dest[2] = tr.endpos[2];
return true;
}
else
{
dest[2] -= lift;
return false;
}
}
/*
=================
AI_movetogoal
More robust movement code, which tries a CPU friendly move first, and then
several walkmove()'s allowing for obstructions
=================
*/
qboolean AI_movetogoal (edict_t *self, edict_t *goal, float dist)
{
vec3_t dir, oldorg, dest;
int yaw, rnd;
int aborted=false;
float diff, goal_dist;
float slide_scale = 1.0;
qboolean moved=false;
qboolean changezval = false;
/*
if ( (self->cast_info.currentmove == self->cast_info.move_run)
&& (self->last_goal && self->last_goal->client)
&& ( (VectorLength( self->last_goal->velocity ) > dist*10)
|| (VectorDistance(self->s.origin, self->last_goal->s.origin) > 256)))
{
dist *= 2; // keep up with the player
if (dist > 32)
dist = 32; // Ridah, they can run through walls if more than this
}
*/
self->flags &= ~FL_FLY;
VectorSubtract(goal->s.origin, self->s.origin, dir);
goal_dist = VectorNormalize(dir);
self->ideal_yaw = vectoyaw(dir);
// if we're walking up a steep slope, make sure we don't try and move too far
if ((fabs(dir[2]) > 0.4) && (dist*fabs(dir[2]) > 20.0))
{
dist = 20.0 / fabs(dir[2]);
}
M_ChangeYaw(self);
yaw = self->s.angles[YAW];
if (dist > 4 && (diff = fabs(AngleDiff(self->s.angles[YAW], self->ideal_yaw))) > 30)
{
// dist *= 0.5; // turning, don't go so far
// if (dist < 4)
// dist = 4;
if (diff < 90)
dist *= (1 - 0.8*(diff / 90));
else
dist *= (1 - 0.8*(90 / 90));
// walk forwards
AngleVectors( self->s.angles, dir, NULL, NULL );
VectorMA( self->s.origin, dist, dir, dest );
}
// can we simply move towards this position?
else if ((dist > 4) || (goal_dist < 32))
{
VectorMA(self->s.origin, dist, dir, dest);
}
else // go forward if walking
{
vec3_t fwd;
AngleVectors( self->s.angles, fwd, NULL, NULL );
VectorMA(self->s.origin, dist, fwd, dest);
}
yaw = self->s.angles[YAW];
if (self->maxs[2] == DUCKING_MAX_Z)
{
self->maxs[2] = 8; //4; // Ridah, modified as per requested
changezval = true;
}
moved = AI_canmove(self, dest);
if (changezval)
{
self->maxs[2] = DUCKING_MAX_Z;
}
if (moved)
{
VectorCopy(dest, self->s.origin);
gi.linkentity(self);
G_TouchTriggers (self);
return true;
}
// nope, try walkmove()
if ((((int) level.time) % 6) < 3)
rnd = -1;
else
rnd = 1;
VectorCopy(self->s.origin, oldorg);
self->goalentity = goal; // used by SV_movestep() for falling from air
while (dist > 0)
{
if (M_walkmove(self, yaw, dist))
{
moved=true;
break;
}
if (dist > 16)
{
dist -= 8;
continue;
}
if (!M_walkmove(self, yaw+(60*rnd), dist*0.7))
if (!M_walkmove(self, yaw+(100*rnd), dist*0.5))
if (!M_walkmove(self, yaw+(140*rnd), dist*0.3))
/*
if (!M_walkmove(self, yaw-(60*rnd), dist*0.7))
if (!M_walkmove(self, yaw-(100*rnd), dist*0.5))
if (!M_walkmove(self, yaw-(140*rnd), dist*0.3))
*/
{
dist -= 3;
continue;
}
break;
}
/*
if ((dist <= 0) && (self->maxs[2] > DUCKING_MAX_Z))
{ // try ducking
self->maxs[2] = DUCKING_MAX_Z;
if (!M_walkmove(self, yaw, dist*0.5))
{
self->maxs[2] = self->cast_info.standing_max_z;
}
else
{
if (self->cast_info.move_crouch_down)
self->cast_info.currentmove = self->cast_info.move_crouch_down;
}
}
*/
if (aborted)
{
self->goalentity = NULL;
return false;
}
if (dist <= 0)
{
return false;
}
// always face the direction we just went
VectorSubtract(self->s.origin, oldorg, dir);
VectorNormalize2(dir, dir);
self->ideal_yaw = vectoyaw(dir);
self->yaw_speed *= 0.25;
M_ChangeYaw(self);
self->yaw_speed *= 4;
return moved;
}
/*
=================
AI_jump
Called whenever a character needs to jump somewhere
=================
*/
void AI_jump (edict_t *self, float dist)
{
vec3_t forward;
if (dist < 0)
dist = -dist;
// VectorCopy (self->goalentity->s.angles, self->s.angles);
AngleVectors (self->s.angles, forward, NULL, NULL);
VectorScale (forward, dist, self->velocity);
self->velocity[2] = dist;
if (self->groundentity)
self->groundentity = NULL;
}
/*
=================
AI_climb
Called whenever a character is climbing a ladder
=================
*/
void AI_climb (edict_t *self)
{
static edict_t *goal = NULL;
edict_t *save;
if (!goal)
goal = G_Spawn();
save = self->goalentity;
self->goalentity = goal;
VectorCopy (self->s.origin, goal->s.origin);
VectorCopy (self->s.angles, goal->s.angles);
goal->s.origin[2] += 20;
// self->enemy = goal;
self->velocity[2] = 200;
// ai_move (self, 0);
self->wait = level.time + 1;
self->moveinfo.wait = level.time + 1;
if (self->nav_data.goal_index && (level.node_data->nodes[self->nav_data.goal_index-1]->origin[2] > self->s.origin[2]))
{
self->flags |= FL_FLY;
}
else // done climbing
{
self->flags &= ~FL_FLY;
AI_jump (self, 150);
self->velocity[2] += 100;
self->groundentity = NULL;
self->cast_info.aiflags &= ~AI_SKILL_LADDER;
self->cast_info.currentmove = self->cast_info.move_stand;
self->wait = level.time + 4;
self->moveinfo.wait = level.time + 1;
level.node_data->nodes[self->nav_data.goal_index-1]->ignore_time = level.time + 3; // don't go back to the landing node, or we may fall down
self->nav_data.goal_index = 0; // re-scan for a new path when we land
self->nav_data.cache_node = -1;
}
// self->enemy = NULL;
self->goalentity = save;
self->s.frame = self->cast_info.move_stand->firstframe;
}
qboolean ValidBoxAtLoc(vec3_t org, vec3_t mins, vec3_t maxs, edict_t *ignore, int mask)
{
trace_t tr;
tr = gi.trace(org, mins, maxs, org, ignore, mask);
return (!tr.allsolid);
}
void AI_GetAvoidDirection( edict_t *self, edict_t *other )
{
vec3_t dir;
/*
if (AI_SideTrace( self, 64, 0, 1 ))
{
self->ideal_yaw = entyaw( other, self );
}
else
*/
if (NAV_GetAvoidDirection( self, other, dir ))
{
self->ideal_yaw = vectoyaw( dir );
}
else
{
self->ideal_yaw = anglemod( entyaw( other, self ) + 1.0*(rand()%90 - 45) );
}
}
// Ridah, done.
//------------------------------------------------------------------------------
// New AI system
//
// This is used whenever the cast member is doing something that involves moving
// - will weigh up all current objectives, and carry out the desired action
extern void path_corner_cast_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
extern void path_corner_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
void ai_runFLASHLIGHT ( edict_t *self, float dist )
{
trace_t tr;
vec3_t forward;
vec3_t end;
vec3_t mins, maxs;
edict_t *light;
vec3_t angles, origin;
ai_run (self, dist);
VectorCopy (self->s.angles, angles);
angles[0] += crandom() * 1.5;
angles[1] += crandom() * 1.5;
angles[2] += crandom() * 1.5;
VectorCopy (self->s.origin, origin);
AngleVectors (angles, forward, NULL, NULL);
VectorCopy (origin, end);
VectorMA (end, 8194, forward, end);
VectorSet (mins, -8, -8, -8 );
VectorSet (maxs, 8, 8, 8);
tr = gi.trace (self->s.origin, mins, maxs, end, self, MASK_SHOT);
if (tr.ent->client || tr.ent->svflags & SVF_MONSTER)
if (!(tr.ent->flags & FL_NOTARGET))
EP_EventSpeech (self, tr.ent, say_flashlight);
/*
if (tr.ent->client || tr.ent->svflags & SVF_MONSTER)
{
// If this is a friend, go easy on them
if (!(tr.ent->cast_group) && (tr.ent->cast_group != self->cast_group))
// they aren't on our side, are they a friend?
if (!(cast_memory = level.global_cast_memory[self->character_index][tr.ent->character_index]))
{ // record this sighting, so we can start to attack them
AI_RecordSighting(self, tr.ent, VectorDistance(self->s.origin, tr.ent->s.origin) );
cast_memory = level.global_cast_memory[self->character_index][tr.ent->character_index];
}
}
*/
light = G_Spawn();
VectorCopy (tr.endpos, light->s.origin);
light->s.renderfx |= RF_BEAM;
light->s.effects |= EF_FLASHLIGHT;
light->nextthink = level.time + 0.1;
light->think = G_FreeEdict;
gi.linkentity (light);
}
void ai_run ( edict_t *self, float dist )
{
static edict_t tempgoal;
edict_t **goal=NULL;
int len;
edict_t tempent, *ptempent;
route_t route;
node_t *goal_node;
vec3_t vec, oldorg;
int rval, reached_goal=false;
float goaldist, ideal_dist;
qboolean moved, avoiding=false;
if (self->cast_info.aiflags & AI_RUN_LIKE_HELL)
{
dist *= 1.5;
if (dist > 32)
dist = 32;
}
if (self->cast_info.move_crwalk && (self->maxs[2] < self->cast_info.standing_max_z) && (self->cast_info.currentmove != self->cast_info.move_crwalk))
{ // we should be ducking
self->cast_info.currentmove = self->cast_info.move_crwalk;
self->s.frame = self->cast_info.currentmove->firstframe;
}
// if ducking, don't stand for at least 5 seconds after we stop
if (self->maxs[2] < self->cast_info.standing_max_z)
{
self->stand_if_idle_time = level.time + 5;
}
tempgoal.active_node_data = level.node_data;
if (fabs(self->s.angles[2]) > 1)
self->s.angles[2] *= 0.8;
else
self->s.angles[2] = 0;
if (self->enemy && (self->enemy->health <= 0))
{
self->enemy = NULL;
if ( !(self->cast_info.aiflags & AI_TAKE_COVER)
&& !(self->goal_ent)
&& (self->cast_info.currentmove->frame->aifunc == ai_stand)
&& (self->start_ent)
&& (VectorDistance( self->s.origin, self->start_ent->s.origin) > 256))
{ // go back to the start pos if we're not doing anything
self->goal_ent = self->start_ent;
}
}
if (self->leader && (self->leader->health <= 0))
self->leader = NULL;
if (!self->groundentity)
{
if (self->cast_info.move_jump && self->cast_info.currentmove != self->cast_info.move_jump)
self->cast_info.currentmove = self->cast_info.move_jump;
return;
}
if (self->leader && (!self->leader->active_node_data || (self->leader->waterlevel > 1)))
{ // don't move
self->cast_info.currentmove = self->cast_info.move_stand;
return;
}
// if we've been told to stand put, only attack under special conditions
if ( (self->leader)
&& (self->cast_info.aiflags & AI_HOLD_POSITION)
&& (!(self->cast_info.aiflags & AI_MELEE) || !self->enemy || (VectorDistance(self->s.origin, self->enemy->s.origin) > 96)))
{
// if running away, let us go
if ( !(self->cast_info.aiflags & AI_TAKE_COVER)
&& (VectorDistance( self->s.origin, self->holdpos_ent->s.origin ) < AI_GUARDING_DIST)
&& (self->pain_debounce_time < (level.time - 2))) // Ridah, 20-may-99, added this so hired guy's don't stand around while being shot at
{
self->cast_info.currentmove = self->cast_info.move_stand;
self->s.frame = self->cast_info.currentmove->firstframe;
self->cast_info.pausetime = level.time + 1.0;
return;
}
}
if (self->cast_info.aiflags & AI_SKILL_LADDER)
{
if (self->groundentity)
self->cast_info.aiflags &= ~AI_SKILL_LADDER;
else
return;
}
else
{
self->flags &= ~FL_FLY;
}
if ((self->cast_info.aiflags & AI_HOLD_POSITION) && !self->enemy)
{
// Ridah, 6-jun-99, Friendly's should avoid grenades when staying put, then come back
if (!(self->cast_info.aiflags & AI_TAKE_COVER) || !(self->cover_ent) || (self->cover_ent->svflags & SVF_MONSTER))
{ // we aren't avoiding anything
if (VectorDistance( self->s.origin, self->holdpos_ent->s.origin ) < AI_GUARDING_DIST)
{
// make sure they stop taking cover
self->cast_info.aiflags &= ~AI_TAKE_COVER;
self->cover_ent = NULL;
self->cast_info.currentmove = self->cast_info.move_stand;
self->s.frame = self->cast_info.currentmove->firstframe;
return;
}
else // run to our hold position
{
goal = &self->holdpos_ent;
ideal_dist = AI_GUARDING_DIST;
goto got_goal;
}
}
else // we are running away from something dangerous, we should return afterwards
{
}
}
if (self->cast_info.pausetime > level.time)
{
self->cast_info.currentmove = self->cast_info.move_stand;
return;
}
if (self->groundentity && self->groundentity->use && !VectorCompare(self->groundentity->velocity, vec3_origin))
{ // standing on a plat that's moving
self->cast_info.pausetime = level.time + 0.5;
self->cast_info.currentmove = self->cast_info.move_stand;
}
// see if we should wait for a lift or door
if (self->target_ent)
{
if ( !VectorCompare(self->target_ent->velocity, vec3_origin) )
{ // it's moving, hang around
self->cast_info.currentmove = self->cast_info.move_stand;
self->cast_info.pausetime = level.time + 0.2;
return;
}
else if (self->target_ent->nextthink > level.time + 0.1)
{ // it's not moving, and isn't about to start moving, so stop checking it
self->target_ent = NULL;
}
}
//===================================================================================
//
// Select a "goal"
goal = &self->leader;
ideal_dist = AI_GUARDING_DIST;
if ( (*goal)
&& (AI_TooClose(self, self->leader)))
{ // always get out of leader's way
avoiding = true;
goto got_goal;
}
if (*goal && VectorDistance( self->s.origin, (*goal)->s.origin ) > 384)
{
self->cast_info.aiflags |= AI_RUN_LIKE_HELL;
}
if (*goal && (*goal)->groundentity && (*goal)->groundentity->use)
{
if (!(*goal)->velocity[0] && !(*goal)->velocity[1])
{ // standing still
ideal_dist = AI_TOO_CLOSE_DIST;
goto got_goal;
}
else // it's moving, so we should wait
{
self->cast_info.pausetime = level.time + 0.2;
self->target_ent = (*goal)->groundentity;
self->cast_info.currentmove = self->cast_info.move_stand;
return;
}
}
// if our leader has recently issued a followme, and is far away, go to them
else if (*goal && ((*goal)->order == ORDER_FOLLOWME) && ((*goal)->order_timestamp > (level.time - 20))
&& (((*goal)->order_timestamp > (level.time - 5)) || (VectorDistance((*goal)->s.origin, self->s.origin) > 256)))
{ // abort attack
goto got_goal;
}
enemy_again:
if (self->enemy || (self->combat_goalent && self->cover_ent))
{
cast_memory_t *mem;
edict_t **enemy;
// find out what we're going for
if (self->enemy)
{
enemy = &self->enemy;
if (!AI_HasLeaderButGoForEnemy(self, self->enemy))
{
self->enemy = NULL;
goto enemy_again;
}
}
else
{
enemy = &self->cover_ent;
}
// make sure we run
if (self->maxs[2] == self->cast_info.standing_max_z)
self->cast_info.currentmove = self->cast_info.move_run;
// make sure we can see them, or have recently seen them
if ((*enemy)->svflags & SVF_MONSTER || (*enemy)->client)
{
// if no memory, stop them being our enemy
if (!(mem = level.global_cast_memory[self->character_index][(*enemy)->character_index]))
{
(*enemy) = NULL;
goto enemy_again;
}
if ( ((!self->combat_goalent) || (self->combat_goalent->think != CheckStillHiding))
&& ((*enemy)->client && ((*enemy)->light_level <= 30))
&& (mem->timestamp < (level.time - 5)))
{ // go for the last sighted position
// DEMO HACK: send Bernie to Louie if he's alive (drastic times call for drastic measures)
if ( (level.episode == EP_SKIDROW)
&& (self->name_index == NAME_ARNOLD))
{
edict_t *louie;
route_t route;
louie = EP_GetCharacter( NAME_LOUIE );
// if they're near Louie, better go protect him
if ( (louie)
&& (louie->health > 0)
&& ( !NAV_Route_EntityToEntity((*enemy), NULL, louie, VIS_PARTIAL, false, &route)
|| (route.dist < 3000)))
{
self->goal_ent = louie;
(*enemy) = NULL;
louie->cast_info.aiflags |= AI_GOALENT_MANUAL_CLEAR;
goto gonetolouie;
}
}
// done DEMO HACK
memset( &tempent, 0, sizeof(edict_t) );
tempent.active_node_data = level.node_data;
VectorCopy( mem->last_known_origin, tempent.s.origin );
ptempent = &tempent;
goal = &ptempent;
VectorCopy( (*enemy)->maxs, tempent.maxs );
VectorCopy( (*enemy)->mins, tempent.mins );
if ((VectorDistance(self->s.origin, (*goal)->s.origin) < 128) && AI_ClearSight( self, (*goal), false ))
{
(*enemy) = NULL;
goto enemy_again;
}
gonetolouie:
goto got_goal;
}
}
goal = &(*enemy);
ideal_dist = AI_GUARDING_DIST;
if ( self->combat_goalent
&& (self->combat_goalent->inuse || (self->combat_goalent = NULL))) // clear it if it's been freed
{
goal = &self->combat_goalent;
ideal_dist = 16; // get really close
// if we're right infront of our enemy, abort
if ( (self->enemy)
&& !(self->cast_info.aiflags & AI_GOAL_IGNOREENEMY) // Ridah, 17-mar-99, fixes Jesus not going to threshold_target
&& (VectorDistance( self->s.origin, self->enemy->s.origin ) < 128)
&& (infront( self, self->enemy ))
&& (self->last_getcombatpos < (level.time - 3))
&& (AI_ClearSight( self->enemy, self, false )))
{ // abort
self->combat_goalent = NULL;
self->cast_info.aiflags &= ~AI_TAKE_COVER;
goto enemy_again;
}
if ( !(self->cast_info.aiflags & AI_MELEE)
&& !(self->cast_info.aiflags & AI_GOAL_IGNOREENEMY) // Ridah, 17-mar-99, fixes Jesus not going to threshold_target
&& !(self->cast_info.aiflags & AI_TAKE_COVER)
&& ((*goal)->think == G_FreeEdict)
&& AI_ClearSight(self, (*enemy), true))
{
if (self->cast_info.checkattack(self))
{
self->combat_goalent = NULL;
return; // this character has started an attack, and doesn't want to move any further
}
}
if ( (self->cast_info.aiflags & AI_TAKE_COVER)
&& (AI_ClearSight((*enemy), *goal, false)))
{ // abort it
self->cast_info.aiflags &= ~AI_TAKE_COVER;
self->combat_goalent = NULL;
self->dont_takecover_time = level.time + 10;
goto enemy_again;
}
}
else if (self->cast_info.checkattack)
{ // do a character specific check for beginning an attack
if (AI_CheckTakeCover(self))
{
// taking cover
// goto (*enemy)_again;
self->goalentity = NULL;
self->wait = -1;
return;
}
else if (AI_ClearSight(self, (*goal), true))
{
if (self->cast_info.checkattack(self))
{
self->dont_takecover_time = level.time + 5; // attack for at least a few seconds
return; // this character has started an attack, and doesn't want to move any further
}
}
else if (self->last_getcombatpos < (level.time - 2))
{ // look for a good combat position
float *pos;
if (pos = NAV_GetCombatPos( self, (*goal), self->cast_info.aiflags & AI_MELEE ))
{
edict_t *combatent;
combatent = G_Spawn();
VectorCopy( pos, combatent->s.origin );
self->cast_info.aiflags &= ~AI_GOAL_IGNOREENEMY;
self->cast_info.aiflags &= ~AI_TAKE_COVER;
self->combat_goalent = combatent;
combatent->cast_info.aiflags |= AI_GOAL_RUN;
combatent->think = G_FreeEdict;
combatent->nextthink = level.time + 5; // give us some time to get there
// go for it this frame
goto enemy_again;
}
}
}
}
else
{ // look for things to be hostile at
if ((self->s.frame == self->cast_info.currentmove->firstframe) && AI_FindTarget (self))
goto enemy_again;
}
// If guarding something, and we've moved out of range, abort the pursuit
if (self->guard_ent)
{
if (!(*goal))
{ // if not doing anything else, return to the guard_ent
if ( (VectorDistance( self->s.origin, self->guard_ent->s.origin ) > self->guard_ent->guard_radius)
|| (!AI_ClearSight(self, self->guard_ent, false)))
// if (VectorDistance( self->s.origin, self->guard_ent->s.origin ) > AI_GUARDING_DIST)
{
goal = &self->guard_ent;
ideal_dist = AI_GUARDING_DIST;
}
}
else if ( (VectorDistance( self->s.origin, self->guard_ent->s.origin ) > self->guard_ent->guard_radius)
&& (!gi.inPVS(self->s.origin, self->guard_ent->s.origin))
&& (!self->enemy || !AI_ClearSight(self, self->enemy, false))) // don't go back if we can see our enemy
{
cast_memory_t *mem;
// always abort an enemy if it exists
if (self->enemy)
{
mem = level.global_cast_memory[self->character_index][self->enemy->character_index];
if (mem)
{
mem->ignore_time = level.time + ENEMY_SIGHT_DURATION + 0.2; // ignore them for a bit
}
self->enemy = NULL;
}
goal = &self->guard_ent;
ideal_dist = AI_GUARDING_DIST;
}
}
got_goal:
// Ridah, 17-may-99, fixes Jesus not going to threshold_target
if ((!(*goal) || (self->cast_info.aiflags & AI_GOAL_IGNOREENEMY)) && self->goal_ent)
{
goal = &self->goal_ent;
if ((*goal)->solid == SOLID_TRIGGER)
ideal_dist = 0;
else if ((*goal)->dmg_radius)
ideal_dist = (*goal)->dmg_radius;
else
ideal_dist = 128;
}
// should we press a button?
if (self->activator)
{
if (self->activator->moveinfo.state != STATE_BOTTOM)
{ // button has been pressed (probably by someone else)
self->activator = NULL;
}
else // go for the button
{
goal = &self->activator;
ideal_dist = 0;
}
}
//===================================================================================
//
// move towards this "goal"
// always run to our attacker if on fire
if ((self->onfiretime > 0) && (self->onfireent))
{
goal = &self->onfireent;
ideal_dist = 64;
}
self->last_goal = *goal;
if (!(*goal))
{ // we don't have a goal, time for a beer
self->cast_info.currentmove = self->cast_info.move_stand;
return;
}
else // we have a "goal"
{
// if it's our leader, make sure they are still reachable (not on a platform)
if ( ((*goal) == self->leader)
&& ((*goal)->groundentity)
&& ((*goal)->groundentity->use)
&& (!VectorCompare((*goal)->groundentity->velocity, vec3_origin)))
{
// They're on a lift, so hang around for a bit
self->cast_info.pausetime = level.time + 2;
self->cast_info.currentmove = self->cast_info.move_stand;
self->s.frame = self->cast_info.currentmove->firstframe;
return;
}
// handle ->goal_ent specially
else if ((goal == &self->goal_ent) || (goal == &self->combat_goalent) || ((goal == &self->leader) && !avoiding))
{
float goal_dist;
goal_dist = VectorDistance( self->s.origin, (*goal)->s.origin );
// Make sure we are running if we should be
if (self->maxs[2] == self->cast_info.standing_max_z)
{
if (self->cast_info.move_runwalk && (goal == &self->leader) && (goal_dist < 256))
{
if (self->cast_info.currentmove != self->cast_info.move_runwalk)
{
self->cast_info.currentmove = self->cast_info.move_runwalk;
self->s.frame = self->cast_info.currentmove->firstframe;
self->cast_info.aiflags &= ~AI_RUN_LIKE_HELL;
}
}
else if (((*goal) == self->enemy) || ((*goal)->cast_info.aiflags & AI_GOAL_RUN) /*&& (goal_dist > ideal_dist*2)*/)
{
self->cast_info.currentmove = self->cast_info.move_run;
}
else if (self->cast_info.move_runwalk)
{
self->cast_info.currentmove = self->cast_info.move_runwalk;
self->cast_info.aiflags &= ~AI_RUN_LIKE_HELL;
}
}
// should we stop moving?
if ((*goal)->solid != SOLID_TRIGGER)
{ // we can't touch it, so just get close to it
if (goal_dist < ideal_dist) // FIXME: make this configurable?
{
if ( (goal != &self->leader)
|| ( ((*goal)->client == NULL && (*goal)->cast_info.currentmove->frame->aifunc == ai_stand)
|| ((*goal)->client != NULL && VectorLength((*goal)->velocity) < 50)))
{
self->cast_info.currentmove = self->cast_info.move_stand;
if ((goal == &self->goal_ent) && !(self->goal_ent->cast_info.aiflags & AI_GOALENT_MANUAL_CLEAR))
{
self->goal_ent = NULL; // we made it there
}
if (goal == &self->combat_goalent) // we've reached it, so clear it out
{
if (!(self->cast_info.aiflags & AI_TAKE_COVER))
{
self->combat_goalent = NULL;
}
}
else if ((*goal) == self->start_ent && (*goal) == self->goal_ent)
{
self->goal_ent->cast_info.aiflags &= ~AI_GOAL_RUN;
self->goal_ent = NULL;
}
return;
}
}
else
{
if ( ((*goal)->solid != SOLID_BBOX)
&& (VectorDistance( self->s.origin, (*goal)->s.origin ) < 64)
&& (!ValidBoxAtLoc( (*goal)->s.origin, self->mins, self->maxs, self, MASK_PLAYERSOLID )))
{ // we can't get there
self->cast_info.aiflags &= ~AI_TAKE_COVER;
*goal = NULL;
return;
}
}
}
// is it dangerous to go for it?
else if ( (self->enemy)
&& !(self->cast_info.aiflags & AI_GOAL_IGNOREENEMY) // Ridah, 17-mar-99, fixes Jesus not going to threshold_target
&& ((*goal)->touch == path_corner_cast_touch)
&& ( (VectorDistance( self->enemy->s.origin, (*goal)->s.origin ) < 256)
|| ( (VectorDistance( self->enemy->s.origin, self->s.origin ) < 512)
&& (infront(self->enemy, self)))))
{ // simulate touching the path_corner
(*goal)->touch( (*goal), self, NULL, NULL );
return;
}
// can we walk straight towards it?
else if ( (goal_dist < ideal_dist*2)
&& (NAV_Visible( self->s.origin, (*goal)->s.origin, VIS_PARTIAL, self->maxs[2] < self->cast_info.standing_max_z ))
&& (NAV_Reachable( self->s.origin, (*goal)->s.origin, (byte)self->waterlevel, (byte)(*goal)->waterlevel, (self->maxs[2] < self->cast_info.standing_max_z), REACHABLE_POOR )))
{
self->nav_data.cache_node = -1;
self->nav_data.goal_index = 0;
if (goal_dist < ideal_dist/2)
{
if ((*goal)->target && ((*goal)->touch == path_corner_cast_touch))
{ // simulate touching the path_corner
(*goal)->touch( (*goal), self, NULL, NULL );
}
else
{
if (!strcmp( (*goal)->classname, "cast_origin" ))
{
goal = NULL;
}
self->cast_info.currentmove = self->cast_info.move_stand;
return;
}
}
}
// NAV_DrawLine( self->s.origin, (*goal)->s.origin );
}
else if (goal == &self->guard_ent)
{
// just walk back there
if ( (self->maxs[2] == self->cast_info.standing_max_z)
&& (self->cast_info.currentmove == self->cast_info.move_run))
{
self->cast_info.currentmove = self->cast_info.move_runwalk;
}
}
}
// are we within the ideal range of our goal?
if ( ((*goal) != self->enemy)
&& !((*goal)->touch)
&& (ideal_dist > 0.1)
&& ((goaldist = VectorDistance (self->s.origin, (*goal)->s.origin)) < ideal_dist)
&& NAV_Visible( self->s.origin, (*goal)->s.origin, VIS_LINE, self->maxs[2] < self->cast_info.standing_max_z ))
// && AI_ClearSight(self, (*goal)))
{
self->cast_info.aiflags &= ~AI_IGNORE_ENEMY;
if ( (!self->leader || ((*goal) != self->leader->moveout_ent))
&& ( ((*goal) != self->leader)
|| (!(*goal)->groundentity)
|| (!(*goal)->groundentity->use)
|| (!VectorCompare((*goal)->velocity, vec3_origin)))
&& ( (AI_FollowLeader(self, (*goal)) // if they are on a lift, and trying to move, we should avoid them, otherwise stay real close
&& ( !((*goal)->groundentity)
|| !((*goal)->groundentity->use)
|| !VectorCompare((*goal)->velocity, vec3_origin)))))
{ // move back, away from goal
VectorSubtract(self->s.origin, (*goal)->s.origin, vec);
VectorNormalize2(vec, vec);
VectorScale(vec, 64, vec);
VectorAdd(self->s.origin, vec, tempgoal.s.origin);
AI_movetogoal(self, &tempgoal, dist);
return;
}
self->cast_info.currentmove = self->cast_info.move_stand;
return;
}
// Check for fast running (same as player)
// Ridah, if the guy is a friendly, and he's hurt, don't run as fast
if (self->cast_group != 1 || self->health > self->max_health/2)
{
if (((*goal)->client || ((*goal)->svflags & SVF_MONSTER)) || ((*goal)->cast_info.aiflags & AI_RUN_LIKE_HELL))
{
if (self->cast_info.aiflags & AI_RUN_LIKE_HELL)
{
dist*=2.5;
if (dist > 32)
dist = 32; // Ridah, they can run through walls if more than this
}
// RAFAEL 30-DEC-98
if (self->cast_info.aiflags & AI_RUSH_THE_PLAYER)
{
dist*=2.5;
if (dist > 32)
dist = 32;
}
}
}
// need to put this here because the dist for new models are > 32
if (dist > 32)
dist = 32;
//===================================================================================
//
// We need to move somewhere
tempgoal.nav_data.goal_index = self->nav_data.goal_index;
goal_node = NULL;
// find a path to the player
if (self->nav_data.goal_index > 0)
{
goal_node = level.node_data->nodes[self->nav_data.goal_index-1];
rval = ROUTE_INDIRECT;
}
else
{
rval = NAV_Route_EntityToEntity(self, goal_node, (*goal), VIS_PARTIAL, false, &route);
// update current node
self->nav_data.goal_index = tempgoal.nav_data.goal_index = route.path+1;
if (rval == ROUTE_INDIRECT)
{
goal_node = level.node_data->nodes[self->nav_data.goal_index-1];
// is it a crouching node?
if (goal_node->node_type & NODE_DUCKING)
{
if (self->maxs[2] > DUCKING_MAX_Z)
{
if (self->cast_info.move_crouch_down)
self->cast_info.currentmove = self->cast_info.move_crouch_down;
self->maxs[2] = DUCKING_MAX_Z;
return;
}
}
// allow 5 seconds to reach the new target
self->wait = level.time + 5;
}
else if (rval == ROUTE_DIRECT)
{
// Disabled this, if we have a direct route, then we don't need to change this
/*
if (((*goal)->solid == SOLID_BBOX) && ((*goal)->maxs[2] < (*goal)->cast_info.standing_max_z) && (self->maxs[2] > DUCKING_MAX_Z))
{
if (self->cast_info.move_crouch_down)
self->cast_info.currentmove = self->cast_info.move_crouch_down;
self->maxs[2] = DUCKING_MAX_Z;
return;
}
*/
self->wait = level.time + 3.5;
}
}
self->last_rval = rval;
if (!rval)
{ // no route
int max_cnt=50;
if ((goal != &self->enemy) || !(self->cast_info.aiflags & AI_MELEE) || (self->cast_info.aiflags & AI_NO_TAKE_COVER))
{ // resort to id's movement code (YUCK!)
self->goalentity = (*goal);
M_MoveToGoal (self, dist);
}
else // hide until we can reach them again
{
AI_ForceTakeCover( self, self->enemy, true );
self->cast_info.aiflags &= ~AI_TAKECOVER_IGNOREHEALTH;
}
if (nav_aipath->value && (*goal))
{
gi.dprintf( "%s", self->classname );
if (self->name)
gi.dprintf( " (%s)", self->name );
gi.dprintf( " has no route to %s\n", self->classname, (*goal)->classname );
}
if (gi.inPVS( g_edicts[1].s.origin, self->s.origin ))
{
max_cnt = 5;
}
if (self->noroute_count++ > max_cnt) // keep trying for some time
{
if (/*(*goal) ==*/ self->enemy) // don't bother going for them if there isn't a route
{
self->combat_goalent = NULL;
AI_ForceTakeCover( self, self->enemy, false );
// if (self->cast_info.aiflags & AI_TAKE_COVER)
return;
}
else if (self->combat_goalent && (*goal) == self->combat_goalent)
{
self->combat_goalent = NULL;
return;
}
// stop going for whatever we were going for
self->cast_info.pausetime = level.time + 2;
self->cast_info.currentmove = self->cast_info.move_stand;
self->s.frame = self->cast_info.currentmove->firstframe;
return;
}
goto done;
}
else
{
self->noroute_count = 0;
}
// successfully found a route
if (rval == ROUTE_DIRECT)
{ // we can reach the "goal" directly
float goal_dist;
goal_dist = VectorDistance( self->s.origin, (*goal)->s.origin );
if (goal_dist < dist)
dist = goal_dist;
self->goalentity = (*goal);
AI_movetogoal(self, (*goal), dist);
if (!(*goal)) // it's been cleared somewhere
return;
if (nav_aipath->value)
{
NAV_DrawLine( self->s.origin, (*goal)->s.origin );
}
goto done;
}
// indirect route
// =============================================================================================
// FIXME: if goal is a character, and we are not within gi.PHS() or gi.PVS(), then we can't
// follow them, since we can't see or hear them. Therefore, we should either go to their last
// sighted position, or give up on them.
// =============================================================================================
VectorCopy(goal_node->origin, tempgoal.s.origin);
tempgoal.s.origin[2] = self->s.origin[2];
len = VectorDistance(self->s.origin, tempgoal.s.origin);
tempgoal.s.origin[2] = goal_node->origin[2];
if (len <= dist*1.2)
{ // reached the waypoint
reached_goal = true;
dist = len;
}
if (nav_aipath->value)
{
NAV_DrawLine( self->s.origin, tempgoal.s.origin );
NAV_Debug_DrawPath( &tempgoal, (*goal) );
}
//===================================================================================
//
// are we about to reach the "goal"?
if (reached_goal)
{
// should we JUMP?
if (goal_node->node_type & NODE_JUMP)
{
// do we really want to jump here?
if ( (NAV_Route_NodeToEntity(goal_node, (*goal), VIS_PARTIAL, &route) == ROUTE_INDIRECT)
&& (route.path == goal_node->goal_index))
{
if ((level.node_data->nodes[goal_node->goal_index]->origin[2] - 54) > goal_node->origin[2])
{ // must be a ladder
// note to Ryan jun-07-99
// The dogs can go up and down ladders. Can this be stopped?
// move us to the ladder if possible, if not abort climbing
/*
if (ValidBoxAtLoc( tempgoal.s.origin, self->mins, self->maxs, self, MASK_PLAYERSOLID ))
{
VectorCopy( tempgoal.s.origin, self->s.origin );
self->s.angles[YAW] = self->ideal_yaw = (float) goal_node->yaw;
self->cast_info.aiflags |= AI_SKILL_LADDER;
self->nav_data.goal_index = route.path+1;
AI_climb (self);
if (self->cast_info.move_start_climb)
self->cast_info.currentmove = self->cast_info.move_start_climb;
}
else // abort running for a bit
{
self->cast_info.currentmove = self->cast_info.move_stand;
self->cast_info.pausetime = level.time + 1.0;
self->nav_data.goal_index = 0;
return;
}
*/
if ((self->gender) && ValidBoxAtLoc( tempgoal.s.origin, self->mins, self->maxs, self, MASK_PLAYERSOLID ))
{
VectorCopy( tempgoal.s.origin, self->s.origin );
self->s.angles[YAW] = self->ideal_yaw = (float) goal_node->yaw;
self->cast_info.aiflags |= AI_SKILL_LADDER;
self->nav_data.goal_index = route.path+1;
AI_climb (self);
if (self->cast_info.move_start_climb)
self->cast_info.currentmove = self->cast_info.move_start_climb;
}
else // abort running for a bit
{
self->cast_info.currentmove = self->cast_info.move_stand;
self->cast_info.pausetime = level.time + 1.0;
self->nav_data.goal_index = 0;
return;
}
}
else // normal jump
{
vec3_t neworg;
float speed;
self->flags &= ~FL_FLY;
VectorSubtract(level.node_data->nodes[goal_node->goal_index]->origin, goal_node->origin, vec);
VectorNormalize2(vec, vec);
self->s.angles[YAW] = vectoyaw(vec);
if ((speed = VectorLength(goal_node->jump_vel)) > 250)
{
vec3_t vec;
float length;
AI_jump (self, 200);
VectorCopy(goal_node->jump_vel, vec);
self->velocity[2] = 0;
vec[2] = 0;
length = VectorLength( vec );
if (length < 20)
{
VectorNormalize( vec );
VectorScale( vec, 20, vec );
goal_node->jump_vel[0] = vec[0];
goal_node->jump_vel[1] = vec[1];
level.node_data->modified = true;
}
else
{
VectorNormalize( self->velocity );
VectorScale( self->velocity, length, self->velocity );
}
self->velocity[2] = goal_node->jump_vel[2];
if (goal_node->jump_vel[2] > 200)
self->velocity[2] = 300;
else if (goal_node->jump_vel[2] < 180)
self->velocity[2] = 180;
}
else
{
AI_jump (self, 200);
self->velocity[2] = 200;
}
VectorCopy(self->s.origin, neworg);
neworg[2] += 1;
if (ValidBoxAtLoc(neworg, self->mins, self->maxs, self, MASK_PLAYERSOLID))
self->s.origin[2] += 1;
self->groundentity = NULL;
if (self->cast_info.move_jump)
self->cast_info.currentmove = self->cast_info.move_jump;
// we need a way of playing character specific jump sounds
//gi.sound(self, CHAN_VOICE, gi.soundindex("*jump1.wav"), 0.8, ATTN_NORM, 0);
gi.linkentity(self);
}
self->nav_data.goal_index = goal_node->goal_index + 1;
reached_goal = false;
goto done;
}
}
}
//===================================================================================
//
// Perform the move towards the goal node
VectorCopy(self->s.origin, oldorg);
// move towards the node
moved = AI_movetogoal(self, &tempgoal, dist);
//...................................................................................
// Evaluate the move
// did we get closer?
if (moved)
{
self->moveinfo.wait = level.time + 0.4;
}
else if ( (self->moveinfo.wait < level.time)
|| (self->wait < level.time)
|| (!ValidBoxAtLoc( tempgoal.s.origin, self->mins, self->maxs, self, MASK_PLAYERSOLID)))
{ // abort this node, we've been trying to get to it for long enough
goal_node->ignore_time = level.time + 3;
self->nav_data.cache_node = -1;
self->moveinfo.wait = level.time + 0.7; // give some time on the next node
self->wait = level.time + 3;
self->nav_data.goal_index = 0;
reached_goal = false;
}
else // will a jump be useful here?
{
if ((self->s.origin[2] + 16 < (&tempgoal)->s.origin[2]))
{
vec3_t start, mins, dir, org;
trace_t trace;
VectorCopy(self->s.origin, start);
AngleVectors(self->s.angles, dir, NULL, NULL);
VectorScale(dir, 32, dir);
VectorAdd(start, dir, org);
VectorCopy(self->mins, mins);
mins[2] += 8;
trace = gi.trace(start, mins, self->maxs, org, self, MASK_PLAYERSOLID);
if ((trace.fraction < 1) && !(trace.ent->svflags & SVF_MONSTER) && (fabs(trace.plane.normal[2]) < 0.1))
{
// set ideal velocity
dir[2] = 310;
// see if clear at head
start[2] += 32;
org[2] += 32;
trace = gi.trace(start, NULL, NULL, org, self, MASK_SOLID);
if (trace.fraction == 1)
{ // safe to jump
AI_jump (self, 150);
self->velocity[2] += 100;
if (self->cast_info.move_jump)
self->cast_info.currentmove = self->cast_info.move_jump;
}
}
}
}
done:
if ( (self->maxs[2] < self->cast_info.standing_max_z)
&& ( (goal_node && !(goal_node->node_type & NODE_DUCKING))
|| (rval == ROUTE_DIRECT)))
{ // we should return to standing, if possible
trace_t tr;
self->maxs[2] = self->cast_info.standing_max_z;
tr = gi.trace(self->s.origin, self->mins, self->maxs, self->s.origin, self, MASK_PLAYERSOLID);
if ( tr.startsolid
|| (goal_node && !NAV_Visible( self->s.origin, goal_node->origin, VIS_PARTIAL, false ))
|| ((rval == ROUTE_DIRECT) && (*goal) && !NAV_Visible( self->s.origin, (*goal)->s.origin, VIS_PARTIAL, false )))
{ // can't safely stand
self->maxs[2] = DUCKING_MAX_Z;
}
else if (self->cast_info.move_stand_up) // ok to stand
{
//gi.dprintf("2\n");
self->cast_info.currentmove = self->cast_info.move_stand_up;
self->s.frame = self->cast_info.currentmove->firstframe;
return;
}
else
{
self->cast_info.currentmove = self->cast_info.move_run;
}
}
if (reached_goal)
{
// press a button?
if ( (goal_node->node_type & NODE_BUTTON)
&& (goal_node->goal_ent)
&& ((goal_node->goal_ent->timestamp < (level.time - 4)) || (goal_node->goal_ent->activator != self))
&& (goal_node->goal_ent->use))
{ // simulate touching it
goal_node->goal_ent->use( goal_node->goal_ent, self, self );
self->cast_info.pausetime = level.time + 2; // wait for it to do something
// walk backwards
self->cast_info.currentmove = self->cast_info.move_avoid_reverse_walk;
self->ideal_yaw = (float)goal_node->yaw; // don't turn
self->activator = NULL; // stop going for a button
return;
}
if ( (goal_node->node_type & NODE_PLAT)
&& (self->groundentity == goal_node->goal_ent)
&& (VectorCompare( self->groundentity->velocity, vec3_origin )))
{
// we're standing on it, so trigger it
if (self->groundentity->targetname)
{
edict_t *btn=NULL, *btn2=NULL;
char *targetname;
targetname = self->groundentity->targetname;
while (btn = G_Find( btn, FOFS( target ), targetname ))
{
if (!strcmp(btn->classname, "trigger_relay") && btn->targetname)
{ // check this target
btn2 = NULL;
while (btn2 = G_Find( btn2, FOFS( target ), btn->targetname ))
{
if (fabs( btn2->absmin[2] - self->s.origin[2] ) < 64)
{
btn2->use( btn2, self, self );
break;
}
}
}
else if (btn->solid == SOLID_BSP)
{
if (fabs( btn->absmin[2] - self->s.origin[2] ) < 64)
{
btn->use( btn, self, self );
break;
}
}
}
}
else
{
self->groundentity->use( self->groundentity, self, self );
}
return;
}
if (!(*goal))
{ // it's been cleared
return;
}
// find the next waypoint
rval = NAV_Route_EntityToEntity( self, goal_node, (*goal), VIS_PARTIAL, false, &route);
self->nav_data.goal_index = tempgoal.nav_data.goal_index = route.path+1;
if (rval == ROUTE_INDIRECT)
{
int i, temp_rval, rval2;
node_t *temp_node;
// check for a plat along the route that's not ready
temp_node = goal_node;
i = 0;
while ((temp_rval = NAV_Route_NodeToEntity( temp_node, (*goal), VIS_PARTIAL, &route)) && (i++ < 3))
{
if (temp_rval == ROUTE_DIRECT)
break;
temp_node = level.node_data->nodes[route.path];
if (temp_node->node_type & NODE_PLAT)
{
if ( temp_node->goal_ent
&& VectorCompare(temp_node->goal_ent->velocity, vec3_origin))
{ // platform is stationary
trace_t tr;
vec3_t src, dest;
// make sure this lift will take us to the goal
if ( !(rval2 = NAV_Route_NodeToEntity( temp_node, (*goal), VIS_PARTIAL, &route))
|| (rval2 == ROUTE_DIRECT)
|| (route.path != temp_node->goal_index))
{
break; // we don't want to go on the lift
}
// make sure it is waiting for us
VectorCopy(temp_node->origin, dest);
dest[2] -= 1;
VectorCopy(temp_node->origin, src);
src[2] += 1;
tr = gi.trace(src, self->mins, self->maxs, dest, self, MASK_SOLID);
if (!tr.startsolid && (tr.ent == temp_node->goal_ent) && (tr.fraction < 1))
break; // cool, it's there waiting for us
// hmm, it's not waiting, should we press a button for it to come back down?
if (temp_node->goal_ent->targetname)
{
edict_t *btn=NULL, *btn2=NULL;
char *targetname;
targetname = temp_node->goal_ent->targetname;
while (btn = G_Find( btn, FOFS( target ), targetname ))
{
if (!strcmp(btn->classname, "trigger_relay") && btn->targetname)
{ // check this target
btn2 = NULL;
while (btn2 = G_Find( btn2, FOFS( target ), btn->targetname ))
{
if (fabs( btn2->absmin[2] - self->s.origin[2] ) < 64)
{
btn2->use( btn2, self, self );
break;
}
}
}
else if (btn->solid == SOLID_BSP)
{
if (fabs( btn->absmin[2] - self->s.origin[2] ) < 64)
{
btn->use( btn, self, self );
break;
}
}
}
}
}
// damn, route is broken, so hang around for a bit
self->cast_info.pausetime = level.time + 2;
self->target_ent = temp_node->goal_ent;
self->cast_info.currentmove = self->cast_info.move_stand;
self->nav_data.goal_index = tempgoal.nav_data.goal_index = goal_node->index+1; // check again when done waiting
return;
}
}
self->wait = level.time + 3;
goal_node = level.node_data->nodes[self->nav_data.goal_index-1];
// is it a crouching node?
if (goal_node->node_type & NODE_DUCKING)
{
if (self->maxs[2] == self->cast_info.standing_max_z)
{
if (self->cast_info.move_crouch_down)
self->cast_info.currentmove = self->cast_info.move_crouch_down;
self->maxs[2] = DUCKING_MAX_Z;
}
}
}
}
else if (goal_node && !(goal_node->node_type & NODE_DUCKING) && (self->maxs[2] < self->cast_info.standing_max_z))
{ // can we stand?
if (NAV_Visible( self->s.origin, goal_node->origin, VIS_PARTIAL, false ))
{
self->maxs[2] = self->cast_info.standing_max_z;
if (self->cast_info.move_stand_up)
{
self->cast_info.currentmove = self->cast_info.move_stand_up;
self->s.frame = self->cast_info.currentmove->firstframe;
return;
}
}
}
self->goalentity = (*goal);
}
void ai_runDOKEY (edict_t *self, float dist)
{
AI_movetogoal(self, self->goal_ent, dist);
}