rogue/src/g_ai.c

1757 lines
34 KiB
C
Raw Normal View History

2013-05-01 08:25:53 +00:00
/* =======================================================================
*
* The basic AI functions like enemy detection, attacking and so on.
*
* =======================================================================
*/
2009-03-12 20:03:41 +00:00
2011-10-11 11:40:43 +00:00
#include "header/local.h"
2014-02-22 12:20:22 +00:00
2013-05-01 08:25:53 +00:00
extern cvar_t *maxclients;
int enemy_range;
float enemy_yaw;
qboolean ai_checkattack(edict_t *self, float dist);
qboolean enemy_infront;
qboolean enemy_vis;
qboolean FindTarget(edict_t *self);
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
/* ========================================================================== */
2009-03-12 20:03:41 +00:00
/*
2013-05-01 08:25:53 +00:00
* Called once each frame to set level.sight_client
* to the player to be checked for in findtarget.
* If all clients are either dead or in notarget,
* sight_client will be null.
* In coop games, sight_client will cycle
* between the clients.
*/
void
AI_SetSightClient(void)
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
edict_t *ent;
int start, check;
2009-03-12 20:03:41 +00:00
if (level.sight_client == NULL)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
start = 1;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
else
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
start = level.sight_client - g_edicts;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
check = start;
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
while (1)
{
check++;
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if (check > game.maxclients)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
check = 1;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
ent = &g_edicts[check];
2013-05-01 08:25:53 +00:00
if (ent->inuse && (ent->health > 0) &&
!(ent->flags & (FL_NOTARGET | FL_DISGUISED)))
2009-03-12 20:03:41 +00:00
{
level.sight_client = ent;
2013-05-01 08:25:53 +00:00
return; /* got one */
2009-03-12 20:03:41 +00:00
}
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if (check == start)
{
level.sight_client = NULL;
2013-05-01 08:25:53 +00:00
return; /* nobody to see */
2009-03-12 20:03:41 +00:00
}
}
}
/*
2013-05-01 08:25:53 +00:00
* Move the specified distance at current facing.
*/
void
ai_move(edict_t *self, float dist)
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
if (!self)
{
return;
}
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
M_walkmove(self, self->s.angles[YAW], dist);
}
2009-03-12 20:03:41 +00:00
/*
2013-05-01 08:25:53 +00:00
*
* Used for standing around and looking
* for players Distance is for slight
* position adjustments needed by the
* animations
*/
void
ai_stand(edict_t *self, float dist)
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
vec3_t v;
2009-03-12 20:03:41 +00:00
qboolean retval;
2014-02-22 12:20:22 +00:00
2013-05-01 08:25:53 +00:00
if (!self)
{
return;
}
2014-02-22 12:20:22 +00:00
2009-03-12 20:03:41 +00:00
if (dist)
2013-05-01 08:25:53 +00:00
{
M_walkmove(self, self->s.angles[YAW], dist);
}
2009-03-12 20:03:41 +00:00
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
{
if (self->enemy)
{
2013-05-01 08:25:53 +00:00
VectorSubtract(self->enemy->s.origin, self->s.origin, v);
2009-03-12 20:03:41 +00:00
self->ideal_yaw = vectoyaw(v);
2013-05-01 08:25:53 +00:00
if ((self->s.angles[YAW] != self->ideal_yaw) &&
self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
self->monsterinfo.aiflags &=
~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
self->monsterinfo.run(self);
2009-03-12 20:03:41 +00:00
}
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
2013-05-01 08:25:53 +00:00
{
M_ChangeYaw(self);
}
/* find out if we're going to be shooting */
retval = ai_checkattack(self, 0);
/* record sightings of player */
if ((self->enemy) && (self->enemy->inuse) &&
(visible(self, self->enemy)))
2009-03-12 20:03:41 +00:00
{
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
2013-05-01 08:25:53 +00:00
VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
VectorCopy(self->enemy->s.origin, self->monsterinfo.blind_fire_target);
2009-03-12 20:03:41 +00:00
self->monsterinfo.trail_time = level.time;
self->monsterinfo.blind_fire_delay = 0;
}
2013-05-01 08:25:53 +00:00
/* check retval to make sure we're not blindfiring */
2009-03-12 20:03:41 +00:00
else if (!retval)
{
2013-05-01 08:25:53 +00:00
FindTarget(self);
2009-03-12 20:03:41 +00:00
return;
}
}
else
2013-05-01 08:25:53 +00:00
{
FindTarget(self);
}
2009-03-12 20:03:41 +00:00
return;
}
2013-05-01 08:25:53 +00:00
if (FindTarget(self))
{
2009-03-12 20:03:41 +00:00
return;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
if (level.time > self->monsterinfo.pausetime)
{
2013-05-01 08:25:53 +00:00
self->monsterinfo.walk(self);
2009-03-12 20:03:41 +00:00
return;
}
2013-05-01 08:25:53 +00:00
if (!(self->spawnflags & 1) && (self->monsterinfo.idle) &&
(level.time > self->monsterinfo.idle_time))
2009-03-12 20:03:41 +00:00
{
if (self->monsterinfo.idle_time)
{
2013-05-01 08:25:53 +00:00
self->monsterinfo.idle(self);
2009-03-12 20:03:41 +00:00
self->monsterinfo.idle_time = level.time + 15 + random() * 15;
}
else
{
self->monsterinfo.idle_time = level.time + random() * 15;
}
}
}
/*
2013-05-01 08:25:53 +00:00
* The monster is walking it's beat
*/
void
ai_walk(edict_t *self, float dist)
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
M_MoveToGoal(self, dist);
2014-02-22 12:20:22 +00:00
2013-05-01 08:25:53 +00:00
if (!self)
{
return;
}
2014-02-22 12:20:22 +00:00
2013-05-01 08:25:53 +00:00
/* check for noticing a player */
if (FindTarget(self))
{
2009-03-12 20:03:41 +00:00
return;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
if ((self->monsterinfo.search) &&
(level.time > self->monsterinfo.idle_time))
2009-03-12 20:03:41 +00:00
{
if (self->monsterinfo.idle_time)
{
2013-05-01 08:25:53 +00:00
self->monsterinfo.search(self);
2009-03-12 20:03:41 +00:00
self->monsterinfo.idle_time = level.time + 15 + random() * 15;
}
else
{
self->monsterinfo.idle_time = level.time + random() * 15;
}
}
}
/*
2013-05-01 08:25:53 +00:00
* Turns towards target and advances
* Use this call with a distnace of 0
* to replace ai_face
2014-02-22 12:20:22 +00:00
*/
2013-05-01 08:25:53 +00:00
void
ai_charge(edict_t *self, float dist)
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
vec3_t v;
float ofs;
2014-02-22 12:20:22 +00:00
2013-05-01 08:25:53 +00:00
if (!self)
{
return;
}
2014-02-22 12:20:22 +00:00
2013-05-01 08:25:53 +00:00
if (!self->enemy || !self->enemy->inuse)
{
return;
}
2009-03-12 20:03:41 +00:00
if (visible(self, self->enemy))
2013-05-01 08:25:53 +00:00
{
VectorCopy(self->enemy->s.origin, self->monsterinfo.blind_fire_target);
}
2009-03-12 20:03:41 +00:00
if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
{
2013-05-01 08:25:53 +00:00
VectorSubtract(self->enemy->s.origin, self->s.origin, v);
2009-03-12 20:03:41 +00:00
self->ideal_yaw = vectoyaw(v);
}
2013-05-01 08:25:53 +00:00
M_ChangeYaw(self);
2009-03-12 20:03:41 +00:00
if (dist)
{
if (self->monsterinfo.aiflags & AI_CHARGING)
{
2013-05-01 08:25:53 +00:00
M_MoveToGoal(self, dist);
2009-03-12 20:03:41 +00:00
return;
}
2013-05-01 08:25:53 +00:00
/* circle strafe support */
2009-03-12 20:03:41 +00:00
if (self->monsterinfo.attack_state == AS_SLIDING)
{
2013-05-01 08:25:53 +00:00
/* if we're fighting a tesla, NEVER circle strafe */
if ((self->enemy) && (self->enemy->classname) &&
(!strcmp(self->enemy->classname, "tesla")))
{
2009-03-12 20:03:41 +00:00
ofs = 0;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
else if (self->monsterinfo.lefty)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
ofs = 90;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
else
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
ofs = -90;
2013-05-01 08:25:53 +00:00
}
if (M_walkmove(self, self->ideal_yaw + ofs, dist))
{
2009-03-12 20:03:41 +00:00
return;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
2013-05-01 08:25:53 +00:00
M_walkmove(self, self->ideal_yaw - ofs, dist);
2009-03-12 20:03:41 +00:00
}
else
2013-05-01 08:25:53 +00:00
{
M_walkmove(self, self->s.angles[YAW], dist);
}
2009-03-12 20:03:41 +00:00
}
}
/*
2013-05-01 08:25:53 +00:00
* 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)
2014-02-22 12:20:22 +00:00
{
2013-05-01 08:25:53 +00:00
if (!self)
{
return;
}
2014-02-22 12:20:22 +00:00
2009-03-12 20:03:41 +00:00
if (dist)
2013-05-01 08:25:53 +00:00
{
M_walkmove(self, self->s.angles[YAW], dist);
}
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
if (FindTarget(self))
{
2009-03-12 20:03:41 +00:00
return;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
2013-05-01 08:25:53 +00:00
{
M_ChangeYaw(self);
}
2009-03-12 20:03:41 +00:00
}
2013-05-01 08:25:53 +00:00
/* ========================================================================== */
2009-03-12 20:03:41 +00:00
/*
2013-05-01 08:25:53 +00:00
* .enemy
* Will be world if not currently angry at anyone.
*
* .movetarget
* The next path spot to walk toward. If .enemy, ignore .movetarget.
* When an enemy is killed, the monster will try to return to it's path.
*
* .hunt_time
* Set to time + something when the player is in sight, but movement straight for
* him is blocked. This causes the monster to use wall following code for
* movement direction instead of sighting on the player.
*
* .ideal_yaw
* A yaw angle of the intended direction, which will be turned towards at up
* to 45 deg / state. If the enemy is in view and hunt_time is not active,
* this will be the exact line towards the enemy.
*
* .pausetime
* A monster will leave it's stand state and head towards it's .movetarget when
* time > .pausetime.
*/
/* ========================================================================== */
2009-03-12 20:03:41 +00:00
/*
2013-05-01 08:25:53 +00:00
* 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)
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
vec3_t v;
float len;
if (!self || !other)
{
return 0;
}
VectorSubtract(self->s.origin, other->s.origin, v);
len = VectorLength(v);
2009-03-12 20:03:41 +00:00
if (len < MELEE_DISTANCE)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
return RANGE_MELEE;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
if (len < 500)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
return RANGE_NEAR;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
if (len < 1000)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
return RANGE_MID;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
return RANGE_FAR;
}
/*
2013-05-01 08:25:53 +00:00
* returns 1 if the entity is visible
* to self, even if not infront
2014-02-22 12:20:22 +00:00
*/
2013-05-01 08:25:53 +00:00
qboolean
visible(edict_t *self, edict_t *other)
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
vec3_t spot1;
vec3_t spot2;
trace_t trace;
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
if (!self || !other)
{
return false;
}
VectorCopy(self->s.origin, spot1);
2009-03-12 20:03:41 +00:00
spot1[2] += self->viewheight;
2013-05-01 08:25:53 +00:00
VectorCopy(other->s.origin, spot2);
2009-03-12 20:03:41 +00:00
spot2[2] += other->viewheight;
2013-05-01 08:25:53 +00:00
trace = gi.trace(spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE);
if ((trace.fraction == 1.0) || (trace.ent == other))
{
2009-03-12 20:03:41 +00:00
return true;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
return false;
}
/*
2013-05-01 08:25:53 +00:00
* returns 1 if the entity is in
* front (in sight) of self
*/
qboolean
infront(edict_t *self, edict_t *other)
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
vec3_t vec;
float dot;
vec3_t forward;
if ((self == NULL) || (other == NULL))
{
return false;
2013-05-01 08:25:53 +00:00
}
AngleVectors(self->s.angles, forward, NULL, NULL);
2013-05-01 08:25:53 +00:00
if ((self == NULL) || (other == NULL))
{
return false;
2013-05-01 08:25:53 +00:00
}
VectorSubtract(other->s.origin, self->s.origin, vec);
VectorNormalize(vec);
dot = DotProduct(vec, forward);
2009-03-12 20:03:41 +00:00
if (dot > 0.3)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
return true;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
return false;
}
2013-05-01 08:25:53 +00:00
/* ============================================================================ */
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
void
HuntTarget(edict_t *self)
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
vec3_t vec;
2014-02-22 12:20:22 +00:00
2013-05-01 08:25:53 +00:00
if (!self)
{
return;
}
2014-02-22 12:20:22 +00:00
2009-03-12 20:03:41 +00:00
self->goalentity = self->enemy;
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
2013-05-01 08:25:53 +00:00
{
self->monsterinfo.stand(self);
}
2009-03-12 20:03:41 +00:00
else
2013-05-01 08:25:53 +00:00
if (self->monsterinfo.run)
{
self->monsterinfo.run(self);
}
if(visible(self, self->enemy))
{
VectorSubtract(self->enemy->s.origin, self->s.origin, vec);
}
2009-03-12 20:03:41 +00:00
self->ideal_yaw = vectoyaw(vec);
2013-05-01 08:25:53 +00:00
/* wait a while before first attack */
2009-03-12 20:03:41 +00:00
if (!(self->monsterinfo.aiflags & AI_STAND_GROUND))
2013-05-01 08:25:53 +00:00
{
AttackFinished(self, 1);
}
2009-03-12 20:03:41 +00:00
}
2013-05-01 08:25:53 +00:00
void
FoundTarget(edict_t *self)
2014-02-22 12:20:22 +00:00
{
if (!self|| !self->enemy || !self->enemy->inuse)
2013-05-01 08:25:53 +00:00
{
return;
}
2014-02-22 12:20:22 +00:00
2013-05-01 08:25:53 +00:00
/* let other monsters see this monster for a while */
2009-03-12 20:03:41 +00:00
if (self->enemy->client)
{
2013-05-01 08:25:53 +00:00
if (self->enemy->flags & FL_DISGUISED)
2009-03-12 20:03:41 +00:00
{
self->enemy->flags &= ~FL_DISGUISED;
}
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
level.sight_entity = self;
level.sight_entity_framenum = level.framenum;
level.sight_entity->light_level = 128;
}
self->show_hostile = (int)level.time + 1; /* wake up other monsters */
2009-03-12 20:03:41 +00:00
VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
self->monsterinfo.trail_time = level.time;
2013-05-01 08:25:53 +00:00
VectorCopy(self->enemy->s.origin, self->monsterinfo.blind_fire_target);
2009-03-12 20:03:41 +00:00
self->monsterinfo.blind_fire_delay = 0;
if (!self->combattarget)
{
2013-05-01 08:25:53 +00:00
HuntTarget(self);
2009-03-12 20:03:41 +00:00
return;
}
self->goalentity = self->movetarget = G_PickTarget(self->combattarget);
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if (!self->movetarget)
{
self->goalentity = self->movetarget = self->enemy;
2013-05-01 08:25:53 +00:00
HuntTarget(self);
gi.dprintf("%s at %s, combattarget %s not found\n",
self->classname, vtos(self->s.origin),
self->combattarget);
2009-03-12 20:03:41 +00:00
return;
}
2013-05-01 08:25:53 +00:00
/* clear out our combattarget, these are a one shot deal */
2009-03-12 20:03:41 +00:00
self->combattarget = NULL;
self->monsterinfo.aiflags |= AI_COMBAT_POINT;
2013-05-01 08:25:53 +00:00
/* clear the targetname, that point is ours! */
2009-03-12 20:03:41 +00:00
self->movetarget->targetname = NULL;
self->monsterinfo.pausetime = 0;
2013-05-01 08:25:53 +00:00
/* run for it */
self->monsterinfo.run(self);
2009-03-12 20:03:41 +00:00
}
/*
2013-05-01 08:25:53 +00:00
* Self is currently not attacking anything,
* so try to find a target
*
* Returns TRUE if an enemy was sighted
*
* When a player fires a missile, the point
* of impact becomes a fakeplayer so that
* monsters that see the impact will respond
* as if they had seen the player.
*
* To avoid spending too much time, only
* a single client (or fakeclient) is
* checked each frame. This means multi
* player games will have slightly
* slower noticing monsters.
*/
qboolean
FindTarget(edict_t *self)
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
edict_t *client;
qboolean heardit;
int r;
2014-02-22 12:20:22 +00:00
2013-05-01 08:25:53 +00:00
if (!self)
{
return false;
}
2014-02-22 12:20:22 +00:00
2009-03-12 20:03:41 +00:00
if (self->monsterinfo.aiflags & AI_GOOD_GUY)
{
return false;
}
2013-05-01 08:25:53 +00:00
/* if we're going to a combat point, just proceed */
2009-03-12 20:03:41 +00:00
if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
return false;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
/* if the first spawnflag bit is set, the monster
will only wake up on really seeing the player,
not another monster getting angry or hearing
something */
2009-03-12 20:03:41 +00:00
heardit = false;
2013-05-01 08:25:53 +00:00
if ((level.sight_entity_framenum >= (level.framenum - 1)) &&
!(self->spawnflags & 1))
2009-03-12 20:03:41 +00:00
{
client = level.sight_entity;
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if (client->enemy == self->enemy)
{
return false;
}
}
else if (level.disguise_violation_framenum > level.framenum)
{
client = level.disguise_violator;
}
else if (level.sound_entity_framenum >= (level.framenum - 1))
{
client = level.sound_entity;
heardit = true;
}
2013-05-01 08:25:53 +00:00
else if (!(self->enemy) &&
(level.sound2_entity_framenum >= (level.framenum - 1)) &&
!(self->spawnflags & 1))
2009-03-12 20:03:41 +00:00
{
client = level.sound2_entity;
heardit = true;
}
else
{
client = level.sight_client;
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if (!client)
2013-05-01 08:25:53 +00:00
{
return false; /* no clients to get mad at */
}
2009-03-12 20:03:41 +00:00
}
2013-05-01 08:25:53 +00:00
/* if the entity went away, forget it */
2009-03-12 20:03:41 +00:00
if (!client->inuse)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
return false;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
if (client == self->enemy)
2013-05-01 08:25:53 +00:00
{
return true;
}
2009-03-12 20:03:41 +00:00
if ((self->monsterinfo.aiflags & AI_HINT_PATH) && (coop) && (coop->value))
{
heardit = false;
}
if (client->client)
{
if (client->flags & FL_NOTARGET)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
return false;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
}
else if (client->svflags & SVF_MONSTER)
{
if (!client->enemy)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
return false;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
if (client->enemy->flags & FL_NOTARGET)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
return false;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
}
else if (heardit)
{
if ((client->owner) && (client->owner->flags & FL_NOTARGET))
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
return false;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
}
else
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
return false;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
if (!heardit)
{
2013-05-01 08:25:53 +00:00
r = range(self, client);
2009-03-12 20:03:41 +00:00
if (r == RANGE_FAR)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
return false;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
/* is client in an spot too dark to be seen? */
2009-03-12 20:03:41 +00:00
if (client->light_level <= 5)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
return false;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
if (!visible(self, client))
2009-03-12 20:03:41 +00:00
{
return false;
}
if (r == RANGE_NEAR)
{
if ((client->show_hostile < (int)level.time) && !infront(self, client))
2009-03-12 20:03:41 +00:00
{
return false;
}
}
else if (r == RANGE_MID)
{
2013-05-01 08:25:53 +00:00
if (!infront(self, client))
2009-03-12 20:03:41 +00:00
{
return false;
}
}
self->enemy = client;
if (strcmp(self->enemy->classname, "player_noise") != 0)
{
self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
if (!self->enemy->client)
{
self->enemy = self->enemy->enemy;
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if (!self->enemy->client)
{
self->enemy = NULL;
return false;
}
}
}
}
2013-05-01 08:25:53 +00:00
else /* heardit */
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
vec3_t temp;
2009-03-12 20:03:41 +00:00
if (self->spawnflags & 1)
{
2013-05-01 08:25:53 +00:00
if (!visible(self, client))
{
2009-03-12 20:03:41 +00:00
return false;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
}
else
{
if (!gi.inPHS(self->s.origin, client->s.origin))
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
return false;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
}
2013-05-01 08:25:53 +00:00
VectorSubtract(client->s.origin, self->s.origin, temp);
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
if (VectorLength(temp) > 1000) /* too far to hear */
2009-03-12 20:03:41 +00:00
{
return false;
}
2014-02-22 12:20:22 +00:00
/* check area portals - if they are different
2013-05-01 08:25:53 +00:00
and not connected then we can't hear it */
2009-03-12 20:03:41 +00:00
if (client->areanum != self->areanum)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
if (!gi.AreasConnected(self->areanum, client->areanum))
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
return false;
2013-05-01 08:25:53 +00:00
}
}
2009-03-30 14:11:48 +00:00
2009-03-12 20:03:41 +00:00
self->ideal_yaw = vectoyaw(temp);
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
2013-05-01 08:25:53 +00:00
{
M_ChangeYaw(self);
}
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
/* hunt the sound for a bit; hopefully find the real player */
2009-03-12 20:03:41 +00:00
self->monsterinfo.aiflags |= AI_SOUND_TARGET;
self->enemy = client;
}
2013-05-01 08:25:53 +00:00
/* if we got an enemy, we need to bail out of
hint paths, so take over here */
2009-03-12 20:03:41 +00:00
if (self->monsterinfo.aiflags & AI_HINT_PATH)
{
2013-05-01 08:25:53 +00:00
/* this calls foundtarget for us */
hintpath_stop(self);
2009-03-12 20:03:41 +00:00
}
else
{
2013-05-01 08:25:53 +00:00
FoundTarget(self);
}
if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) &&
(self->monsterinfo.sight))
{
self->monsterinfo.sight(self, self->enemy);
2009-03-12 20:03:41 +00:00
}
return true;
}
2013-05-01 08:25:53 +00:00
/* ============================================================================= */
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
qboolean
FacingIdeal(edict_t *self)
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
float delta;
2014-02-22 12:20:22 +00:00
2013-05-01 08:25:53 +00:00
if (!self)
{
return false;
}
2014-02-22 12:20:22 +00:00
2009-03-12 20:03:41 +00:00
delta = anglemod(self->s.angles[YAW] - self->ideal_yaw);
2013-05-01 08:25:53 +00:00
if ((delta > 45) && (delta < 315))
{
2009-03-12 20:03:41 +00:00
return false;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
return true;
}
2013-05-01 08:25:53 +00:00
/* ============================================================================= */
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
qboolean
M_CheckAttack(edict_t *self)
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
vec3_t spot1, spot2;
float chance;
trace_t tr;
2014-02-22 12:20:22 +00:00
if (!self || !self->enemy || !self->enemy->inuse)
2013-05-01 08:25:53 +00:00
{
return false;
}
2014-02-22 12:20:22 +00:00
2009-03-12 20:03:41 +00:00
if (self->enemy->health > 0)
{
2013-05-01 08:25:53 +00:00
/* see if any entities are in the way of the shot */
VectorCopy(self->s.origin, spot1);
2009-03-12 20:03:41 +00:00
spot1[2] += self->viewheight;
2013-05-01 08:25:53 +00:00
VectorCopy(self->enemy->s.origin, spot2);
2009-03-12 20:03:41 +00:00
spot2[2] += self->enemy->viewheight;
2013-05-01 08:25:53 +00:00
tr = gi.trace(spot1, NULL, NULL, spot2, self,
CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME | CONTENTS_LAVA |
CONTENTS_WINDOW);
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
/* do we have a clear shot? */
2009-03-12 20:03:41 +00:00
if (tr.ent != self->enemy)
2013-05-01 08:25:53 +00:00
{
/* we want them to go ahead and shoot at info_notnulls if they can. */
if ((self->enemy->solid != SOLID_NOT) || (tr.fraction < 1.0))
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
/* if we can't see our target, and we're not
2014-02-22 12:20:22 +00:00
blocked by a monster, go into blind fire
2013-05-01 08:25:53 +00:00
if available */
if ((!(tr.ent->svflags & SVF_MONSTER)) &&
(!visible(self, self->enemy)))
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
if ((self->monsterinfo.blindfire) &&
(self->monsterinfo.blind_fire_delay <= 20.0))
2009-03-12 20:03:41 +00:00
{
if (level.time < self->monsterinfo.attack_finished)
{
return false;
}
2013-05-01 08:25:53 +00:00
if (level.time <
(self->monsterinfo.trail_time +
self->monsterinfo.blind_fire_delay))
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
/* wait for our time */
2009-03-12 20:03:41 +00:00
return false;
}
else
{
2013-05-01 08:25:53 +00:00
/* make sure we're not going to shoot a monster */
tr = gi.trace(spot1, NULL, NULL,
self->monsterinfo.blind_fire_target,
self, CONTENTS_MONSTER);
if (tr.allsolid || tr.startsolid ||
((tr.fraction < 1.0) &&
(tr.ent != self->enemy)))
2009-03-12 20:03:41 +00:00
{
return false;
}
self->monsterinfo.attack_state = AS_BLIND;
return true;
}
}
}
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
return false;
}
}
}
2013-05-01 08:25:53 +00:00
/* melee attack */
2009-03-12 20:03:41 +00:00
if (enemy_range == RANGE_MELEE)
{
2013-05-01 08:25:53 +00:00
/* don't always melee in easy mode */
if ((skill->value == 0) && (rand() & 3))
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
/* fix for melee only monsters & strafing */
2009-03-12 20:03:41 +00:00
self->monsterinfo.attack_state = AS_STRAIGHT;
return false;
}
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if (self->monsterinfo.melee)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
self->monsterinfo.attack_state = AS_MELEE;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
else
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
self->monsterinfo.attack_state = AS_MISSILE;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
return true;
}
2013-05-01 08:25:53 +00:00
/* missile attack */
2009-03-12 20:03:41 +00:00
if (!self->monsterinfo.attack)
{
2013-05-01 08:25:53 +00:00
/* fix for melee only monsters & strafing */
2009-03-12 20:03:41 +00:00
self->monsterinfo.attack_state = AS_STRAIGHT;
return false;
}
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if (level.time < self->monsterinfo.attack_finished)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
return false;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
if (enemy_range == RANGE_FAR)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
return false;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
{
chance = 0.4;
}
else if (enemy_range == RANGE_MELEE)
{
chance = 0.2;
}
else if (enemy_range == RANGE_NEAR)
{
chance = 0.1;
}
else if (enemy_range == RANGE_MID)
{
chance = 0.02;
}
else
{
return false;
}
if (skill->value == 0)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
chance *= 0.5;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
else if (skill->value >= 2)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
chance *= 2;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
/* go ahead and shoot every time if it's a info_notnull */
if ((random() < chance) || (self->enemy->solid == SOLID_NOT))
2009-03-12 20:03:41 +00:00
{
self->monsterinfo.attack_state = AS_MISSILE;
2013-05-01 08:25:53 +00:00
self->monsterinfo.attack_finished = level.time + 2 * random();
2009-03-12 20:03:41 +00:00
return true;
}
2013-05-01 08:25:53 +00:00
/* daedalus should strafe more.. this can be done
here or in a customized check_attack code for
the hover. */
2009-03-12 20:03:41 +00:00
if (self->flags & FL_FLY)
{
2013-05-01 08:25:53 +00:00
/* originally, just 0.3 */
2009-03-12 20:03:41 +00:00
float strafe_chance;
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if (!(strcmp(self->classname, "monster_daedalus")))
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
strafe_chance = 0.8;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
else
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
strafe_chance = 0.6;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
/* if enemy is tesla, never strafe */
if ((self->enemy) && (self->enemy->classname) &&
(!strcmp(self->enemy->classname, "tesla")))
{
2009-03-12 20:03:41 +00:00
strafe_chance = 0;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
if (random() < strafe_chance)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
self->monsterinfo.attack_state = AS_SLIDING;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
else
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
self->monsterinfo.attack_state = AS_STRAIGHT;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
}
else
{
2013-05-01 08:25:53 +00:00
/* do we want the monsters strafing? */
2009-03-12 20:03:41 +00:00
if (random() < 0.4)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
self->monsterinfo.attack_state = AS_SLIDING;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
else
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
self->monsterinfo.attack_state = AS_STRAIGHT;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
}
return false;
}
/*
2013-05-01 08:25:53 +00:00
* Turn and close until within an
* angle to launch a melee attack
*/
void
ai_run_melee(edict_t *self)
2014-02-22 12:20:22 +00:00
{
2013-05-01 08:25:53 +00:00
if (!self)
{
return;
}
2014-02-22 12:20:22 +00:00
2009-03-12 20:03:41 +00:00
self->ideal_yaw = enemy_yaw;
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
2013-05-01 08:25:53 +00:00
{
M_ChangeYaw(self);
}
2009-03-12 20:03:41 +00:00
if (FacingIdeal(self))
{
if (self->monsterinfo.melee)
{
self->monsterinfo.melee(self);
self->monsterinfo.attack_state = AS_STRAIGHT;
}
2009-03-12 20:03:41 +00:00
}
}
/*
2013-05-01 08:25:53 +00:00
* Turn in place until within an
* angle to launch a missile attack
2014-02-22 12:20:22 +00:00
*/
2013-05-01 08:25:53 +00:00
void
ai_run_missile(edict_t *self)
2014-02-22 12:20:22 +00:00
{
2013-05-01 08:25:53 +00:00
if (!self)
{
return;
}
2014-02-22 12:20:22 +00:00
2009-03-12 20:03:41 +00:00
self->ideal_yaw = enemy_yaw;
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
2013-05-01 08:25:53 +00:00
{
M_ChangeYaw(self);
}
2009-03-12 20:03:41 +00:00
if (FacingIdeal(self))
{
if (self->monsterinfo.attack)
2013-05-01 08:25:53 +00:00
{
self->monsterinfo.attack(self);
if ((self->monsterinfo.attack_state == AS_MISSILE) ||
(self->monsterinfo.attack_state == AS_BLIND)) {
self->monsterinfo.attack_state = AS_STRAIGHT;
}
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
}
2009-03-30 14:11:48 +00:00
}
2009-03-12 20:03:41 +00:00
/*
2013-05-01 08:25:53 +00:00
* Strafe sideways, but stay at
* aproximately the same range
*/
void
ai_run_slide(edict_t *self, float distance)
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
float ofs;
float angle;
2014-02-22 12:20:22 +00:00
2013-05-01 08:25:53 +00:00
if (!self)
{
return;
}
2014-02-22 12:20:22 +00:00
2009-03-12 20:03:41 +00:00
self->ideal_yaw = enemy_yaw;
angle = 90;
2009-03-30 14:11:48 +00:00
2009-03-12 20:03:41 +00:00
if (self->monsterinfo.lefty)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
ofs = angle;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
else
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
ofs = -angle;
2013-05-01 08:25:53 +00:00
}
2009-03-30 14:11:48 +00:00
2009-03-12 20:03:41 +00:00
if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
2013-05-01 08:25:53 +00:00
{
M_ChangeYaw(self);
}
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
/* clamp maximum sideways move for non flyers to make them look less jerky */
if (!(self->flags & FL_FLY))
2013-05-01 08:25:53 +00:00
{
distance = min(distance, 0.8);
}
if (M_walkmove(self, self->ideal_yaw + ofs, distance))
{
2009-03-12 20:03:41 +00:00
return;
2013-05-01 08:25:53 +00:00
}
/* if we're dodging, give up on it and go straight */
2009-03-12 20:03:41 +00:00
if (self->monsterinfo.aiflags & AI_DODGING)
{
2013-05-01 08:25:53 +00:00
monster_done_dodge(self);
2009-03-12 20:03:41 +00:00
self->monsterinfo.attack_state = AS_STRAIGHT;
return;
}
self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
2013-05-01 08:25:53 +00:00
if (M_walkmove(self, self->ideal_yaw - ofs, distance))
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
return;
2009-03-12 20:03:41 +00:00
}
2013-05-01 08:25:53 +00:00
/* if we're dodging, give up on it and go straight */
if (self->monsterinfo.aiflags & AI_DODGING)
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
monster_done_dodge(self);
}
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
/* the move failed, so signal the caller (ai_run) to try going straight */
self->monsterinfo.attack_state = AS_STRAIGHT;
}
2009-03-12 20:03:41 +00:00
/*
2013-05-01 08:25:53 +00:00
* Decides if we're going to attack
* or do something else used by
* ai_run and ai_stand
*/
qboolean
ai_checkattack(edict_t *self, float dist)
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
vec3_t temp;
qboolean hesDeadJim;
qboolean retval;
2014-02-22 12:20:22 +00:00
if (!self)
2013-05-01 08:25:53 +00:00
{
enemy_vis = false;
2013-05-01 08:25:53 +00:00
return false;
}
2014-02-22 12:20:22 +00:00
2013-05-01 08:25:53 +00:00
/* this causes monsters to run blindly
to the combat point w/o firing */
2009-03-12 20:03:41 +00:00
if (self->goalentity)
{
if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
return false;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
if ((self->monsterinfo.aiflags & AI_SOUND_TARGET) && !visible(self, self->goalentity))
2009-03-12 20:03:41 +00:00
{
if ((level.time - self->enemy->last_sound_time) > 5.0)
2009-03-12 20:03:41 +00:00
{
if (self->goalentity == self->enemy)
2009-03-30 14:11:48 +00:00
{
2009-03-12 20:03:41 +00:00
if (self->movetarget)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
self->goalentity = self->movetarget;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
else
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
self->goalentity = NULL;
2013-05-01 08:25:53 +00:00
}
2009-03-30 14:11:48 +00:00
}
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
}
else
{
self->show_hostile = (int)level.time + 1;
2009-03-12 20:03:41 +00:00
return false;
}
}
}
enemy_vis = false;
2013-05-01 08:25:53 +00:00
/* see if the enemy is dead */
2009-03-12 20:03:41 +00:00
hesDeadJim = false;
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if ((!self->enemy) || (!self->enemy->inuse))
{
hesDeadJim = true;
}
else if (self->monsterinfo.aiflags & AI_MEDIC)
{
if (!(self->enemy->inuse) || (self->enemy->health > 0))
{
hesDeadJim = true;
}
}
else
{
if (self->monsterinfo.aiflags & AI_BRUTAL)
{
if (self->enemy->health <= -80)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
hesDeadJim = true;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
}
else
{
if (self->enemy->health <= 0)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
hesDeadJim = true;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
}
}
if (hesDeadJim)
{
self->monsterinfo.aiflags &= ~AI_MEDIC;
self->enemy = NULL;
2013-05-01 08:25:53 +00:00
if (self->oldenemy && (self->oldenemy->health > 0))
2009-03-12 20:03:41 +00:00
{
self->enemy = self->oldenemy;
self->oldenemy = NULL;
2013-05-01 08:25:53 +00:00
HuntTarget(self);
2009-03-12 20:03:41 +00:00
}
2013-05-01 08:25:53 +00:00
else if (self->monsterinfo.last_player_enemy &&
(self->monsterinfo.last_player_enemy->health > 0))
2009-03-12 20:03:41 +00:00
{
self->enemy = self->monsterinfo.last_player_enemy;
self->oldenemy = NULL;
self->monsterinfo.last_player_enemy = NULL;
2013-05-01 08:25:53 +00:00
HuntTarget(self);
2009-03-12 20:03:41 +00:00
}
else
{
if (self->movetarget)
{
self->goalentity = self->movetarget;
2013-05-01 08:25:53 +00:00
self->monsterinfo.walk(self);
2009-03-12 20:03:41 +00:00
}
else
{
2014-02-22 12:20:22 +00:00
/* we need the pausetime otherwise the stand code
will just revert to walking with no target and
the monsters will wonder around aimlessly trying
2013-05-01 08:25:53 +00:00
to hunt the world entity */
2009-03-12 20:03:41 +00:00
self->monsterinfo.pausetime = level.time + 100000000;
2013-05-01 08:25:53 +00:00
self->monsterinfo.stand(self);
2009-03-12 20:03:41 +00:00
}
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
return true;
}
}
self->show_hostile = (int)level.time + 1; /* wake up other monsters */
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
/* check knowledge of enemy */
2009-03-12 20:03:41 +00:00
enemy_vis = visible(self, self->enemy);
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if (enemy_vis)
{
self->monsterinfo.search_time = level.time + 5;
2013-05-01 08:25:53 +00:00
VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
2009-03-12 20:03:41 +00:00
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
self->monsterinfo.trail_time = level.time;
2013-05-01 08:25:53 +00:00
VectorCopy(self->enemy->s.origin, self->monsterinfo.blind_fire_target);
2009-03-12 20:03:41 +00:00
self->monsterinfo.blind_fire_delay = 0;
}
if (coop && coop->value && (self->monsterinfo.search_time < level.time))
{
if (FindTarget(self))
{
return true;
}
}
2009-03-12 20:03:41 +00:00
if (self->enemy)
{
enemy_infront = infront(self, self->enemy);
enemy_range = range(self, self->enemy);
VectorSubtract(self->enemy->s.origin, self->s.origin, temp);
enemy_yaw = vectoyaw(temp);
}
2013-05-01 08:25:53 +00:00
retval = self->monsterinfo.checkattack(self);
2009-03-12 20:03:41 +00:00
if (retval)
{
if (self->monsterinfo.attack_state == AS_MISSILE)
{
2013-05-01 08:25:53 +00:00
ai_run_missile(self);
2009-03-12 20:03:41 +00:00
return true;
}
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if (self->monsterinfo.attack_state == AS_MELEE)
{
2013-05-01 08:25:53 +00:00
ai_run_melee(self);
2009-03-12 20:03:41 +00:00
return true;
}
2013-05-01 08:25:53 +00:00
/* added so monsters can shoot blind */
2009-03-12 20:03:41 +00:00
if (self->monsterinfo.attack_state == AS_BLIND)
{
2013-05-01 08:25:53 +00:00
ai_run_missile(self);
2009-03-12 20:03:41 +00:00
return true;
}
2013-05-01 08:25:53 +00:00
/* if enemy is not currently visible,
we will never attack */
2009-03-12 20:03:41 +00:00
if (!enemy_vis)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
return false;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
}
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
return retval;
}
/*
2014-02-22 12:20:22 +00:00
* The monster has an enemy
2013-05-01 08:25:53 +00:00
* it is trying to kill
*/
void
ai_run(edict_t *self, float dist)
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
vec3_t v;
edict_t *tempgoal;
edict_t *save;
qboolean new;
edict_t *marker;
float d1, d2;
trace_t tr;
vec3_t v_forward, v_right;
float left, center, right;
vec3_t left_target, right_target;
qboolean retval;
qboolean alreadyMoved = false;
qboolean gotcha = false;
edict_t *realEnemy;
2014-02-22 12:20:22 +00:00
if (!self)
2013-05-01 08:25:53 +00:00
{
return;
}
2014-02-22 12:20:22 +00:00
2013-05-01 08:25:53 +00:00
/* if we're going to a combat point, just proceed */
2009-03-12 20:03:41 +00:00
if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
{
2013-05-01 08:25:53 +00:00
M_MoveToGoal(self, dist);
2009-03-12 20:03:41 +00:00
return;
}
if (self->monsterinfo.aiflags & AI_DUCKED)
{
self->monsterinfo.aiflags &= ~AI_DUCKED;
}
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if (self->maxs[2] != self->monsterinfo.base_height)
{
2013-05-01 08:25:53 +00:00
monster_duck_up(self);
2009-03-12 20:03:41 +00:00
}
2013-05-01 08:25:53 +00:00
/* if we're currently looking for a hint path */
2009-03-12 20:03:41 +00:00
if (self->monsterinfo.aiflags & AI_HINT_PATH)
{
2013-05-01 08:25:53 +00:00
M_MoveToGoal(self, dist);
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
if (!self->inuse)
{
return;
}
/* first off, make sure we're looking for
the player, not a noise he made */
2009-03-12 20:03:41 +00:00
if (self->enemy)
{
if (self->enemy->inuse)
{
if (strcmp(self->enemy->classname, "player_noise") != 0)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
realEnemy = self->enemy;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
else if (self->enemy->owner)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
realEnemy = self->enemy->owner;
2013-05-01 08:25:53 +00:00
}
else /* uh oh, can't figure out enemy, bail */
2009-03-12 20:03:41 +00:00
{
self->enemy = NULL;
2013-05-01 08:25:53 +00:00
hintpath_stop(self);
2009-03-12 20:03:41 +00:00
return;
}
}
else
{
self->enemy = NULL;
2013-05-01 08:25:53 +00:00
hintpath_stop(self);
2009-03-12 20:03:41 +00:00
return;
}
}
else
{
2013-05-01 08:25:53 +00:00
hintpath_stop(self);
2009-03-12 20:03:41 +00:00
return;
}
if (coop && coop->value)
{
2013-05-01 08:25:53 +00:00
/* if we're in coop, check my real enemy first..
if I SEE him, set gotcha to true */
2009-03-12 20:03:41 +00:00
if (self->enemy && visible(self, realEnemy))
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
gotcha = true;
2013-05-01 08:25:53 +00:00
}
else /* otherwise, let FindTarget bump us out of hint paths, if appropriate */
{
2009-03-12 20:03:41 +00:00
FindTarget(self);
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
}
else
{
2013-05-01 08:25:53 +00:00
if (self->enemy && visible(self, realEnemy))
{
2009-03-12 20:03:41 +00:00
gotcha = true;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
}
2013-05-01 08:25:53 +00:00
/* if we see the player, stop following hintpaths. */
2009-03-12 20:03:41 +00:00
if (gotcha)
{
2013-05-01 08:25:53 +00:00
/* disconnect from hintpaths and start looking normally for players. */
hintpath_stop(self);
2009-03-12 20:03:41 +00:00
}
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
return;
}
if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
{
2013-05-01 08:25:53 +00:00
/* paranoia checking */
2009-03-12 20:03:41 +00:00
if (self->enemy)
2013-05-01 08:25:53 +00:00
{
VectorSubtract(self->s.origin, self->enemy->s.origin, v);
}
2009-03-12 20:03:41 +00:00
if ((!self->enemy) || (VectorLength(v) < 64))
{
self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
2013-05-01 08:25:53 +00:00
self->monsterinfo.stand(self);
2009-03-12 20:03:41 +00:00
return;
}
2013-05-01 08:25:53 +00:00
M_MoveToGoal(self, dist);
/* prevent double moves for sound_targets */
2009-03-12 20:03:41 +00:00
alreadyMoved = true;
2013-05-01 08:25:53 +00:00
if (!self->inuse)
{
2009-03-12 20:03:41 +00:00
return;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
if (!FindTarget(self))
{
return;
}
}
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
retval = ai_checkattack(self, dist);
2009-03-12 20:03:41 +00:00
2013-05-01 08:25:53 +00:00
/* don't strafe if we can't see our enemy */
2009-03-12 20:03:41 +00:00
if ((!enemy_vis) && (self->monsterinfo.attack_state == AS_SLIDING))
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
self->monsterinfo.attack_state = AS_STRAIGHT;
2013-05-01 08:25:53 +00:00
}
/* unless we're dodging (dodging out of view looks smart) */
2009-03-12 20:03:41 +00:00
if (self->monsterinfo.aiflags & AI_DODGING)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
self->monsterinfo.attack_state = AS_SLIDING;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
if (self->monsterinfo.attack_state == AS_SLIDING)
{
2013-05-01 08:25:53 +00:00
/* protect against double moves */
2009-03-12 20:03:41 +00:00
if (!alreadyMoved)
2013-05-01 08:25:53 +00:00
{
ai_run_slide(self, dist);
}
/* we're using attack_state as the return value out of
2014-02-22 12:20:22 +00:00
ai_run_slide to indicate whether or not the move
2013-05-01 08:25:53 +00:00
succeeded. If the move succeeded, and we're still
sliding, we're done in here (since we've had our
chance to shoot in ai_checkattack, and have moved).
if the move failed, our state is as_straight, and
it will be taken care of below */
2009-03-12 20:03:41 +00:00
if ((!retval) && (self->monsterinfo.attack_state == AS_SLIDING))
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
return;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
}
else if (self->monsterinfo.aiflags & AI_CHARGING)
{
self->ideal_yaw = enemy_yaw;
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
2013-05-01 08:25:53 +00:00
{
M_ChangeYaw(self);
}
2009-03-12 20:03:41 +00:00
}
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if (retval)
{
2013-05-01 08:25:53 +00:00
if ((dist != 0) && (!alreadyMoved) &&
(self->monsterinfo.attack_state == AS_STRAIGHT) &&
(!(self->monsterinfo.aiflags & AI_STAND_GROUND)))
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
M_MoveToGoal(self, dist);
2009-03-12 20:03:41 +00:00
}
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if ((self->enemy) && (self->enemy->inuse) && (enemy_vis))
{
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
2013-05-01 08:25:53 +00:00
VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
2009-03-12 20:03:41 +00:00
self->monsterinfo.trail_time = level.time;
2013-05-01 08:25:53 +00:00
VectorCopy(self->enemy->s.origin, self->monsterinfo.blind_fire_target);
2009-03-12 20:03:41 +00:00
self->monsterinfo.blind_fire_delay = 0;
}
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
return;
}
if ((self->enemy) && (self->enemy->inuse) && (enemy_vis))
{
2013-05-01 08:25:53 +00:00
/* check for alreadyMoved */
2009-03-12 20:03:41 +00:00
if (!alreadyMoved)
2013-05-01 08:25:53 +00:00
{
M_MoveToGoal(self, dist);
}
if (!self->inuse)
{
return;
}
2009-03-12 20:03:41 +00:00
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
2013-05-01 08:25:53 +00:00
VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
2009-03-12 20:03:41 +00:00
self->monsterinfo.trail_time = level.time;
2013-05-01 08:25:53 +00:00
VectorCopy(self->enemy->s.origin, self->monsterinfo.blind_fire_target);
2009-03-12 20:03:41 +00:00
self->monsterinfo.blind_fire_delay = 0;
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
return;
}
2013-05-01 08:25:53 +00:00
if ((self->monsterinfo.trail_time + 5) <= level.time)
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
/* and we haven't checked for valid hint paths in the last 10 seconds */
if ((self->monsterinfo.last_hint_time + 10) <= level.time)
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
/* check for hint_paths. */
2009-03-12 20:03:41 +00:00
self->monsterinfo.last_hint_time = level.time;
2013-05-01 08:25:53 +00:00
if (monsterlost_checkhint(self))
{
2009-03-12 20:03:41 +00:00
return;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
}
}
2013-05-01 08:25:53 +00:00
if ((self->monsterinfo.search_time) &&
(level.time > (self->monsterinfo.search_time + 20)))
2009-03-12 20:03:41 +00:00
{
2013-05-01 08:25:53 +00:00
/* double move protection */
2009-03-12 20:03:41 +00:00
if (!alreadyMoved)
2013-05-01 08:25:53 +00:00
{
M_MoveToGoal(self, dist);
}
2009-03-12 20:03:41 +00:00
self->monsterinfo.search_time = 0;
return;
}
save = self->goalentity;
tempgoal = G_Spawn();
self->goalentity = tempgoal;
new = false;
if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT))
{
2013-05-01 08:25:53 +00:00
/* just lost sight of the player, decide where to go first */
2009-03-12 20:03:41 +00:00
self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN);
self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP);
new = true;
}
if (self->monsterinfo.aiflags & AI_PURSUE_NEXT)
{
self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT;
2013-05-01 08:25:53 +00:00
/* give ourself more time since we got this far */
2009-03-12 20:03:41 +00:00
self->monsterinfo.search_time = level.time + 5;
if (self->monsterinfo.aiflags & AI_PURSUE_TEMP)
{
self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP;
marker = NULL;
2013-05-01 08:25:53 +00:00
VectorCopy(self->monsterinfo.saved_goal, self->monsterinfo.last_sighting);
2009-03-12 20:03:41 +00:00
new = true;
}
else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN)
{
self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN;
2013-05-01 08:25:53 +00:00
marker = PlayerTrail_PickFirst(self);
2009-03-12 20:03:41 +00:00
}
else
{
2013-05-01 08:25:53 +00:00
marker = PlayerTrail_PickNext(self);
2009-03-12 20:03:41 +00:00
}
if (marker)
{
2013-05-01 08:25:53 +00:00
VectorCopy(marker->s.origin, self->monsterinfo.last_sighting);
2009-03-12 20:03:41 +00:00
self->monsterinfo.trail_time = marker->timestamp;
self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW];
new = true;
}
}
2013-05-01 08:25:53 +00:00
VectorSubtract(self->s.origin, self->monsterinfo.last_sighting, v);
2009-03-12 20:03:41 +00:00
d1 = VectorLength(v);
2013-05-01 08:25:53 +00:00
2009-03-12 20:03:41 +00:00
if (d1 <= dist)
{
self->monsterinfo.aiflags |= AI_PURSUE_NEXT;
dist = d1;
}
2013-05-01 08:25:53 +00:00
VectorCopy(self->monsterinfo.last_sighting, self->goalentity->s.origin);
2009-03-12 20:03:41 +00:00
if (new)
{
2013-05-01 08:25:53 +00:00
tr = gi.trace(self->s.origin, self->mins, self->maxs,
self->monsterinfo.last_sighting, self,
MASK_PLAYERSOLID);
2009-03-12 20:03:41 +00:00
if (tr.fraction < 1)
{
2013-05-01 08:25:53 +00:00
VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
2009-03-12 20:03:41 +00:00
d1 = VectorLength(v);
center = tr.fraction;
2013-05-01 08:25:53 +00:00
d2 = d1 * ((center + 1) / 2);
2009-03-12 20:03:41 +00:00
self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
AngleVectors(self->s.angles, v_forward, v_right, NULL);
VectorSet(v, d2, -16, 0);
2013-05-01 08:25:53 +00:00
G_ProjectSource(self->s.origin, v, v_forward, v_right, left_target);
tr = gi.trace(self->s.origin, self->mins, self->maxs,
left_target, self, MASK_PLAYERSOLID);
2009-03-12 20:03:41 +00:00
left = tr.fraction;
VectorSet(v, d2, 16, 0);
2013-05-01 08:25:53 +00:00
G_ProjectSource(self->s.origin, v, v_forward, v_right, right_target);
tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target,
self, MASK_PLAYERSOLID);
2009-03-12 20:03:41 +00:00
right = tr.fraction;
2013-05-01 08:25:53 +00:00
center = (d1 * center) / d2;
if ((left >= center) && (left > right))
2009-03-12 20:03:41 +00:00
{
if (left < 1)
{
VectorSet(v, d2 * left * 0.5, -16, 0);
2013-05-01 08:25:53 +00:00
G_ProjectSource(self->s.origin, v, v_forward,
v_right, left_target);
2009-03-12 20:03:41 +00:00
}
2013-05-01 08:25:53 +00:00
VectorCopy(self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
2009-03-12 20:03:41 +00:00
self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
2013-05-01 08:25:53 +00:00
VectorCopy(left_target, self->goalentity->s.origin);
VectorCopy(left_target, self->monsterinfo.last_sighting);
VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
2009-03-12 20:03:41 +00:00
self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
}
2013-05-01 08:25:53 +00:00
else if ((right >= center) && (right > left))
2009-03-12 20:03:41 +00:00
{
if (right < 1)
{
VectorSet(v, d2 * right * 0.5, 16, 0);
2013-05-01 08:25:53 +00:00
G_ProjectSource(self->s.origin, v, v_forward, v_right,
right_target);
2009-03-12 20:03:41 +00:00
}
2013-05-01 08:25:53 +00:00
VectorCopy(self->monsterinfo.last_sighting,
self->monsterinfo.saved_goal);
2009-03-12 20:03:41 +00:00
self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
2013-05-01 08:25:53 +00:00
VectorCopy(right_target, self->goalentity->s.origin);
VectorCopy(right_target, self->monsterinfo.last_sighting);
VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
2009-03-12 20:03:41 +00:00
self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
}
}
}
2013-05-01 08:25:53 +00:00
M_MoveToGoal(self, dist);
if (!self->inuse)
{
return;
}
2009-03-12 20:03:41 +00:00
G_FreeEdict(tempgoal);
if (self)
2013-05-01 08:25:53 +00:00
{
2009-03-12 20:03:41 +00:00
self->goalentity = save;
2013-05-01 08:25:53 +00:00
}
2009-03-12 20:03:41 +00:00
}