thirtyflightsofloving/missionpack/g_newai.c
Knightmare66 608814c830 Fixed firing player weapon while using spycam/remote turret in default Lazarus and missionpack DLLs.
Fixed Tactician Gunner's flechettes going thru player bbox at point blank.
Made edict_t pointer arrays static in server, default Lazarus, and missionpack DLLs due to stack size concerns.
Added contact grenade mode for special monster flag for gunners in default Lazarus and missionpack DLLs.
2021-08-10 16:37:04 -04:00

1949 lines
46 KiB
C

#include "g_local.h"
//===============================
// BLOCKED Logic
//===============================
/*
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_DEBUGTRAIL);
gi.WritePosition (pt1);
gi.WritePosition (pt2);
gi.multicast (pt1, MULTICAST_PVS);
self->nextthink = level.time + 10;
*/
// plat states, copied from g_func.c
#define STATE_TOP 0
#define STATE_BOTTOM 1
#define STATE_UP 2
#define STATE_DOWN 3
qboolean face_wall (edict_t *self);
void HuntTarget (edict_t *self);
// PMM
qboolean parasite_drain_attack_ok (vec3_t start, vec3_t end);
// blocked_checkshot
// shotchance: 0-1, chance they'll take the shot if it's clear.
qboolean blocked_checkshot (edict_t *self, float shotChance)
{
qboolean playerVisible;
if (!self->enemy)
return false;
// blocked checkshot is only against players. this will
// filter out player sounds and other shit they should
// not be firing at.
if (!(self->enemy->client))
return false;
if (random() < shotChance)
return false;
// PMM - special handling for the parasite
if (!strcmp(self->classname, "monster_parasite"))
{
vec3_t f, r, offset, start, end;
trace_t tr;
AngleVectors (self->s.angles, f, r, NULL);
VectorSet (offset, 24, 0, 6);
G_ProjectSource (self->s.origin, offset, f, r, start);
VectorCopy (self->enemy->s.origin, end);
if (!parasite_drain_attack_ok(start, end))
{
end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8;
if (!parasite_drain_attack_ok(start, end))
{
end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8;
if (!parasite_drain_attack_ok(start, end))
return false;
}
}
VectorCopy (self->enemy->s.origin, end);
tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT);
if (tr.ent != self->enemy)
{
self->monsterinfo.aiflags |= AI_BLOCKED;
if (self->monsterinfo.attack)
self->monsterinfo.attack(self);
self->monsterinfo.aiflags &= ~AI_BLOCKED;
return true;
}
}
playerVisible = visible (self, self->enemy);
// always shoot at teslas and prox
if (playerVisible)
{
// if (!strcmp(self->enemy->classname, "tesla"))
if ( !strcmp(self->enemy->classname, "tesla") || !strcmp(self->enemy->classname, "prox") )
{
// if (g_showlogic && g_showlogic->value)
// gi.dprintf("blocked: taking a shot\n");
// turn on AI_BLOCKED to let the monster know the attack is being called
// by the blocked functions...
self->monsterinfo.aiflags |= AI_BLOCKED;
if (self->monsterinfo.attack)
self->monsterinfo.attack (self);
self->monsterinfo.aiflags &= ~AI_BLOCKED;
return true;
}
}
return false;
}
// blocked_checkplat
// dist: how far they are trying to walk.
qboolean blocked_checkplat (edict_t *self, float dist)
{
int playerPosition;
trace_t trace;
vec3_t pt1, pt2;
vec3_t forward;
edict_t *plat;
if (!self->enemy)
return false;
// check player's relative altitude
if (self->enemy->absmin[2] >= self->absmax[2])
playerPosition = 1;
else if (self->enemy->absmax[2] <= self->absmin[2])
playerPosition = -1;
else
playerPosition = 0;
// if we're close to the same position, don't bother trying plats.
if (playerPosition == 0)
return false;
plat = NULL;
// see if we're already standing on a plat.
if (self->groundentity && self->groundentity != world)
{
if (!strncmp(self->groundentity->classname, "func_plat", 8))
plat = self->groundentity;
}
// if we're not, check to see if we'll step onto one with this move
if (!plat)
{
AngleVectors (self->s.angles, forward, NULL, NULL);
VectorMA(self->s.origin, dist, forward, pt1);
VectorCopy (pt1, pt2);
pt2[2] -= 384;
trace = gi.trace(pt1, vec3_origin, vec3_origin, pt2, self, MASK_MONSTERSOLID);
if (trace.fraction < 1 && !trace.allsolid && !trace.startsolid)
{
if (!strncmp(trace.ent->classname, "func_plat", 8))
{
plat = trace.ent;
}
}
}
// if we've found a plat, trigger it.
if (plat && plat->use)
{
if (playerPosition == 1)
{
if ((self->groundentity == plat && plat->moveinfo.state == STATE_BOTTOM) ||
(self->groundentity != plat && plat->moveinfo.state == STATE_TOP))
{
// if (g_showlogic && g_showlogic->value)
// gi.dprintf("player above, and plat will raise. using!\n");
plat->use (plat, self, self);
return true;
}
}
else if (playerPosition == -1)
{
if ((self->groundentity == plat && plat->moveinfo.state == STATE_TOP) ||
(self->groundentity != plat && plat->moveinfo.state == STATE_BOTTOM))
{
// if (g_showlogic && g_showlogic->value)
// gi.dprintf("player below, and plat will lower. using!\n");
plat->use (plat, self, self);
return true;
}
}
// if (g_showlogic && g_showlogic->value)
// gi.dprintf("hit a plat, not using. ppos: %d plat: %d\n", playerPosition, plat->moveinfo.state);
}
return false;
}
// blocked_checkjump
// dist: how far they are trying to walk.
// maxDown/maxUp: how far they'll ok a jump for. set to 0 to disable that direction.
qboolean blocked_checkjump (edict_t *self, float dist, float maxDown, float maxUp)
{
int playerPosition;
trace_t trace;
vec3_t pt1, pt2;
vec3_t forward, up;
vec_t d0, d1;
edict_t *target;
// Lazarus: Rogue only did this for enemies. We do it for enemies or
// movetargets
if (!self->monsterinfo.jump)
return false;
if (!self->enemy)
return false;
if (self->enemy)
target = self->enemy;
else if (self->movetarget)
target = self->movetarget;
else
return false;
VectorSubtract(target->s.origin,self->s.origin,pt1);
d0 = VectorLength(pt1);
AngleVectors (self->s.angles, forward, NULL, up);
VectorMA(self->s.origin, 48, forward, pt1);
if (target->absmin[2] > (self->absmin[2] + 16))
playerPosition = 1;
else if (target->absmin[2] < (self->absmin[2] - 16))
playerPosition = -1;
else
playerPosition = 0;
if (playerPosition == -1 && maxDown)
{
// check to make sure we can even get to the spot we're going to "fall" from
trace = gi.trace(self->s.origin, self->mins, self->maxs, pt1, self, MASK_MONSTERSOLID);
if (trace.fraction < 1)
return false;
VectorCopy (pt1, pt2);
pt2[2] = self->mins[2] - maxDown - 1;
trace = gi.trace(pt1, vec3_origin, vec3_origin, pt2, self, MASK_MONSTERSOLID | MASK_WATER);
if (trace.fraction < 1 && !trace.allsolid && !trace.startsolid)
{
if ((self->absmin[2] - trace.endpos[2]) >= 24 && trace.contents & MASK_SOLID)
{
if ( (target->absmin[2] - trace.endpos[2]) > 32)
return false;
if (trace.plane.normal[2] < 0.9)
return false;
VectorSubtract(target->s.origin,trace.endpos,pt1);
d1 = VectorLength(pt1);
if (d0 < d1)
return false;
self->velocity[0] = forward[0]*dist*10;
self->velocity[1] = forward[1]*dist*10;
self->velocity[2] = max(self->velocity[2],100);
gi.linkentity(self);
return true;
}
}
}
else if (playerPosition == 1 && maxUp)
{
VectorCopy(pt1, pt2);
pt1[2] = self->absmax[2] + maxUp;
trace = gi.trace(pt1, vec3_origin, vec3_origin, pt2, self, MASK_MONSTERSOLID | MASK_WATER);
if (trace.fraction < 1 && !trace.allsolid && !trace.startsolid)
{
if ((trace.endpos[2] - self->absmin[2]) <= maxUp && trace.contents & MASK_SOLID)
{
VectorSubtract(target->s.origin,trace.endpos,pt1);
d1 = VectorLength(pt1);
if (d0 < d1)
return false;
face_wall(self);
self->monsterinfo.jump(self);
self->velocity[0] = forward[0]*dist*10;
self->velocity[1] = forward[1]*dist*10;
self->velocity[2] = max(self->velocity[2],200);
gi.linkentity(self);
return true;
}
// else if (g_showlogic && g_showlogic->value)
// gi.dprintf("Too high to jump %0.1f\n", (trace.endpos[2] - self->absmin[2]));
}
// else if (g_showlogic && g_showlogic->value)
// gi.dprintf("Not something I could jump onto\n");
}
// else if (g_showlogic && g_showlogic->value)
// gi.dprintf("Player at similar level. No need to jump up?\n");
return false;
}
// checks to see if another coop player is nearby, and will switch.
qboolean blocked_checknewenemy (edict_t *self)
{
/*
int player;
edict_t *ent;
if (!(coop->value))
return false;
for (player = 1; player <= game.maxclients; player++)
{
ent = &g_edicts[player];
if (!ent->inuse)
continue;
if (!ent->client)
continue;
if (ent == self->enemy)
continue;
if (visible (self, ent))
{
if (g_showlogic && g_showlogic->value)
gi.dprintf ("B_CNE: %s acquired new enemy %s\n", self->classname, ent->client->pers.netname);
self->enemy = ent;
FoundTarget (self);
return true;
}
}
return false;
*/
return false;
}
// *************************
// HINT PATHS
// *************************
#define HINT_ENDPOINT 0x0001
#define MAX_HINT_CHAINS 100
int hint_paths_present;
edict_t *hint_path_start[MAX_HINT_CHAINS];
int num_hint_paths;
//
// AI code
//
// =============
// hintpath_findstart - given any hintpath node, finds the start node
// =============
edict_t *hintpath_findstart (edict_t *ent)
{
edict_t *e;
edict_t *last;
size_t field;
if (ent->target) // starting point
{
last = world;
field = FOFS(targetname);
e = G_Find(NULL, field, ent->target);
while (e)
{
last = e;
if (!e->target)
break;
e = G_Find(NULL, field, e->target);
}
}
else // end point
{
last = world;
field = FOFS(target);
e = G_Find(NULL, field, ent->targetname);
while (e)
{
last = e;
if (!e->targetname)
break;
e = G_Find(NULL, field, e->targetname);
}
}
if (!(last->spawnflags & HINT_ENDPOINT))
{
// gi.dprintf ("end of chain is not HINT_ENDPOINT\n");
return NULL;
}
if (last == world)
last = NULL;
return last;
}
// =============
// hintpath_other_end - given one endpoint of a hintpath, returns the other end.
// =============
edict_t *hintpath_other_end (edict_t *ent)
{
edict_t *e;
edict_t *last;
size_t field;
if (ent->target) // starting point
{
last = world;
field = FOFS(targetname);
e = G_Find(NULL, field, ent->target);
while (e)
{
last = e;
if (!e->target)
break;
e = G_Find(NULL, field, e->target);
}
}
else // end point
{
last = world;
field = FOFS(target);
e = G_Find(NULL, field, ent->targetname);
while (e)
{
last = e;
if (!e->targetname)
break;
e = G_Find(NULL, field, e->targetname);
}
}
if (!(last->spawnflags & HINT_ENDPOINT))
{
// gi.dprintf ("end of chain is not HINT_ENDPOINT\n");
return NULL;
}
if (last == world)
last = NULL;
return last;
}
// =============
// hintpath_go - starts a monster (self) moving towards the hintpath (point)
// disables all contrary AI flags.
// =============
void hintpath_go (edict_t *self, edict_t *point)
{
vec3_t dir;
vec3_t angles;
VectorSubtract (point->s.origin, self->s.origin, dir);
vectoangles2 (dir, angles);
self->ideal_yaw = angles[YAW];
self->goalentity = self->movetarget = point;
self->monsterinfo.pausetime = 0;
self->monsterinfo.aiflags |= AI_HINT_PATH;
self->monsterinfo.aiflags &= ~(AI_SOUND_TARGET | AI_PURSUIT_LAST_SEEN | AI_PURSUE_NEXT | AI_PURSUE_TEMP);
// run for it
self->monsterinfo.search_time = level.time;
self->monsterinfo.run (self);
}
// =============
// hintpath_stop - bails a monster out of following hint paths
// =============
void hintpath_stop (edict_t *self)
{
self->goalentity = NULL;
self->movetarget = NULL;
// self->monsterinfo.last_hint = NULL;
self->monsterinfo.last_hint_time = level.time;
self->monsterinfo.goal_hint = NULL;
self->monsterinfo.aiflags &= ~AI_HINT_PATH;
if (has_valid_enemy(self))
{
// if we can see our target, go nuts
if (visible(self, self->enemy))
{
FoundTarget (self);
return;
}
// otherwise, keep chasing
HuntTarget (self);
return;
}
// if our enemy is no longer valid, forget about our enemy and go into stand
self->enemy = NULL;
// we need the pausetime otherwise the stand code
// will just revert to walking with no target and
// the monsters will wonder around aimlessly trying
// to hunt the world entity
self->monsterinfo.pausetime = level.time + 100000000;
self->monsterinfo.stand (self);
}
// =============
// monsterlost_checkhint - the monster (self) will check around for valid hintpaths.
// a valid hintpath is one where the two endpoints can see both the monster
// and the monster's enemy. if only one person is visible from the endpoints,
// it will not go for it.
// =============
//qboolean monsterlost_checkhint2 (edict_t *self);
qboolean monsterlost_checkhint (edict_t *self)
{
edict_t *e=NULL;
edict_t *monster_pathchain=NULL, *target_pathchain=NULL, *checkpoint=NULL;
edict_t *closest=NULL, *start=NULL, *destination=NULL;
float closest_range = 1000000;
float r;
// size_t field;
int count1=0, count2=0, count3=0, count4=0, count5=0;
int i;
qboolean hint_path_represented[MAX_HINT_CHAINS];
// if there are no hint paths on this map, exit immediately.
if (!hint_paths_present)
return false;
if (!self->enemy)
return false;
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
return false;
if (!strcmp(self->classname, "monster_turret"))
return false;
monster_pathchain = NULL;
// field = FOFS(classname);
// find all the hint_paths.
// FIXME - can we not do this every time?
for (i=0; i < num_hint_paths; i++)
{
e = hint_path_start[i];
while (e)
{
count1++;
if (e->monster_hint_chain)
e->monster_hint_chain = NULL;
if (monster_pathchain)
{
checkpoint->monster_hint_chain = e;
checkpoint = e;
}
else // add first node
{
monster_pathchain = e;
checkpoint = e;
}
e = e->hint_chain;
}
}
// filter them by distance and visibility to the monster
e = monster_pathchain;
checkpoint = NULL;
while (e)
{
r = realrange (self, e);
// if (r > 512)
// count3++;
if (r > 512)
{
count2++;
if (checkpoint)
{
checkpoint->monster_hint_chain = e->monster_hint_chain;
e->monster_hint_chain = NULL;
e = checkpoint->monster_hint_chain;
continue;
}
else
{
// use checkpoint as temp pointer
checkpoint = e;
e = e->monster_hint_chain;
checkpoint->monster_hint_chain = NULL;
// and clear it again
checkpoint = NULL;
// since we have yet to find a valid one (or else checkpoint would be set) move the
// start of monster_pathchain
monster_pathchain = e;
continue;
}
}
if (!visible(self, e))
{
count4++;
if (checkpoint)
{
checkpoint->monster_hint_chain = e->monster_hint_chain;
e->monster_hint_chain = NULL;
e = checkpoint->monster_hint_chain;
continue;
}
else
{
// use checkpoint as temp pointer
checkpoint = e;
e = e->monster_hint_chain;
checkpoint->monster_hint_chain = NULL;
// and clear it again
checkpoint = NULL;
// since we have yet to find a valid one (or else checkpoint would be set) move the
// start of monster_pathchain
monster_pathchain = e;
continue;
}
}
count5++;
checkpoint = e;
e = e->monster_hint_chain;
}
// at this point, we have a list of all of the eligible hint nodes for the monster
// we now take them, figure out what hint chains they're on, and traverse down those chains,
// seeing whether any can see the player
//
// first, we figure out which hint chains we have represented in monster_pathchain
if (count5 == 0)
return false;
for (i=0; i < num_hint_paths; i++)
{
hint_path_represented[i] = false;
}
e = monster_pathchain;
checkpoint = NULL;
while (e)
{
if ((e->hint_chain_id < 0) || (e->hint_chain_id > num_hint_paths))
return false;
hint_path_represented[e->hint_chain_id] = true;
e = e->monster_hint_chain;
}
count1 = 0;
count2 = 0;
count3 = 0;
count4 = 0;
count5 = 0;
// now, build the target_pathchain which contains all of the hint_path nodes we need to check for
// validity (within range, visibility)
target_pathchain = NULL;
checkpoint = NULL;
for (i=0; i < num_hint_paths; i++)
{
// if this hint chain is represented in the monster_hint_chain, add all of it's nodes to the target_pathchain
// for validity checking
if (hint_path_represented[i])
{
e = hint_path_start[i];
while (e)
{
if (target_pathchain)
{
checkpoint->target_hint_chain = e;
checkpoint = e;
}
else
{
target_pathchain = e;
checkpoint = e;
}
e = e->hint_chain;
}
}
}
// target_pathchain is a list of all of the hint_path nodes we need to check for validity relative to the target
e = target_pathchain;
checkpoint = NULL;
while (e)
{
r = realrange (self->enemy, e);
if (r > 512)
{
count2++;
if (checkpoint)
{
checkpoint->target_hint_chain = e->target_hint_chain;
e->target_hint_chain = NULL;
e = checkpoint->target_hint_chain;
continue;
}
else
{
// use checkpoint as temp pointer
checkpoint = e;
e = e->target_hint_chain;
checkpoint->target_hint_chain = NULL;
// and clear it again
checkpoint = NULL;
target_pathchain = e;
continue;
}
}
if (!visible(self->enemy, e))
{
count4++;
if (checkpoint)
{
checkpoint->target_hint_chain = e->target_hint_chain;
e->target_hint_chain = NULL;
e = checkpoint->target_hint_chain;
continue;
}
else
{
// use checkpoint as temp pointer
checkpoint = e;
e = e->target_hint_chain;
checkpoint->target_hint_chain = NULL;
// and clear it again
checkpoint = NULL;
target_pathchain = e;
continue;
}
}
// if it passes all the tests, it's a keeper
count5++;
checkpoint = e;
e = e->target_hint_chain;
}
// at this point we should have:
// monster_pathchain - a list of "monster valid" hint_path nodes linked together by monster_hint_chain
// target_pathcain - a list of "target valid" hint_path nodes linked together by target_hint_chain. these
// are filtered such that only nodes which are on the same chain as "monster valid" nodes
//
// Now, we figure out which "monster valid" node we want to use
//
// To do this, we first off make sure we have some target nodes. If we don't, there are no valid hint_path nodes
// for us to take
//
// If we have some, we filter all of our "monster valid" nodes by which ones have "target valid" nodes on them
//
// Once this filter is finished, we select the closest "monster valid" node, and go to it.
if (count5 == 0)
return false;
// reuse the hint_chain_represented array, this time to see which chains are represented by the target
for (i=0; i < num_hint_paths; i++)
{
hint_path_represented[i] = false;
}
e = target_pathchain;
checkpoint = NULL;
while (e)
{
if ((e->hint_chain_id < 0) || (e->hint_chain_id > num_hint_paths))
return false;
hint_path_represented[e->hint_chain_id] = true;
e = e->target_hint_chain;
}
// traverse the monster_pathchain - if the hint_node isn't represented in the "target valid" chain list,
// remove it
// if it is on the list, check it for range from the monster. If the range is the closest, keep it
//
closest = NULL;
e = monster_pathchain;
while (e)
{
if (!(hint_path_represented[e->hint_chain_id]))
{
checkpoint = e->monster_hint_chain;
e->monster_hint_chain = NULL;
e = checkpoint;
continue;
}
r = realrange(self, e);
if (r < closest_range)
{
closest = e;
closest_range = r; // Lazarus: This was left out, ya doofuses
}
e = e->monster_hint_chain;
}
if (!closest)
return false;
start = closest;
// now we know which one is the closest to the monster .. this is the one the monster will go to
// we need to finally determine what the DESTINATION node is for the monster .. walk down the hint_chain,
// and find the closest one to the player
closest = NULL;
closest_range = 10000000;
e = target_pathchain;
while (e)
{
if (start->hint_chain_id == e->hint_chain_id)
{
r = realrange(self->enemy, e); // Lazarus: This WAS realrange(self, e)... that can't be right
if (r < closest_range)
{
closest = e;
closest_range = r; // Lazarus: again, you idjits left this out
}
}
e = e->target_hint_chain;
}
if (!closest)
return false;
destination = closest;
self->monsterinfo.goal_hint = destination;
// self->monsterinfo.last_hint = NULL;
hintpath_go (self, start);
return true;
}
/*
qboolean monsterlost_checkhint2 (edict_t *self)
{
edict_t *e, *e2, *goPoint;
size_t field;
int playerVisible, selfVisible;
// if there are no hint paths on this map, exit immediately.
if (!hint_paths_present)
return false;
if (!self->enemy)
return false;
goPoint = NULL;
field = FOFS(classname);
// check all the hint_paths.
e = G_Find(NULL, field, "hint_path");
while (e)
{
// if it's an endpoint, check for "validity"
if (e->spawnflags & HINT_ENDPOINT)
{
// check visibility from this spot
selfVisible = visible(e, self);
playerVisible = visible(e, self->enemy);
// gi.dprintf("checking endpoint at %s %d %d\n", vtos(e->s.origin),selfVisible,playerVisible);
// at least one of us is visible from this endpoint.
// now check the other one if needed.
if (selfVisible || playerVisible)
{
// if endpoint 1 saw me, set my destination to it.
if (selfVisible)
goPoint = e;
// if both aren't visible, try the other endpoint
if (!selfVisible || !playerVisible)
{
e2 = hintpath_other_end(e);
if (!e2) // could not connect to the other endpoint
{
gi.dprintf("Unlinked hint paths!\n");
return false;
}
// if endpoint 1 saw the enemy, see if endpoint 2 sees me
if (!selfVisible)
selfVisible = visible(e2, self);
// if endpoint 1 saw me, see if endpoint 2 sees the enemy
else if (!playerVisible)
playerVisible = visible(e2, self->enemy);
// if endpoint 2 saw me, set my destination to it.
if (!goPoint && selfVisible)
goPoint = e2;
// gi.dprintf("checking other endpoint at %s %d %d\n", vtos(e2->s.origin),selfVisible,playerVisible);
}
// if both are visible from at least one endpoint,
// go for it.
if (selfVisible && playerVisible)
{
// set me to go to goPoint
if (g_showlogic && g_showlogic->value)
gi.dprintf("found path. proceed to %s\n", vtos(goPoint->s.origin));
// since this is a new hint path trip, set last_hint to NULL
self->monsterinfo.last_hint = NULL;
hintpath_go(self, goPoint);
return true;
}
}
}
e = G_Find(e, field, "hint_path");
}
// if we got here, we didn't find a valid path
if (g_showlogic && g_showlogic->value)
gi.dprintf("blocked_checkhint: found no paths\n");
return false;
}
*/
//
// Path code
//
// =============
// hint_path_touch - someone's touched the hint_path
// =============
void hint_path_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
edict_t *e=NULL, *goal=NULL, *next=NULL;
// int chain; // direction - (-1) = upstream, (1) = downstream, (0) = done
qboolean goalFound = false;
if (other->monsterinfo.aiflags & AI_MEDIC_PATROL)
{
if (other->movetarget == self)
medic_NextPatrolPoint(other, self);
return;
}
if (other->monsterinfo.aiflags2 & AI2_HINT_TEST)
{
if (other->movetarget == self)
HintTestNext (other, self);
return;
}
// make sure we're the target of it's obsession
if (other->movetarget == self)
{
goal = other->monsterinfo.goal_hint;
// if the monster is where he wants to be
if (goal == self)
{
hintpath_stop (other);
return;
}
else
{
// if we aren't, figure out which way we want to go
e = hint_path_start[self->hint_chain_id];
while (e)
{
// if we get up to ourselves on the hint chain, we're going down it
if (e == self)
{
next = e->hint_chain;
break;
}
if (e == goal)
goalFound = true;
// if we get to where the next link on the chain is this hint_path and have found the goal on the way
// we're going upstream, so remember who the previous link is
if ((e->hint_chain == self) && goalFound)
{
next = e;
break;
}
e = e->hint_chain;
}
}
// if we couldn't find it, have the monster go back to normal hunting.
if (!next)
{
hintpath_stop(other);
return;
}
// set the last_hint entry to this hint_path, and
// send him on his way
hintpath_go (other, next);
// have the monster freeze if the hint path we just touched has a wait time
// on it, for example, when riding a plat.
if (self->wait)
other->nextthink = level.time + self->wait;
}
}
/*
void hint_path_touch2 (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
edict_t *next, *last;
int chain;
// make sure we're the target of it's obsession
if (other->movetarget == self)
{
chain = 0; // direction the monster is going in the chain
next = NULL; // next hint_path
// gi.dprintf("hint_path %s\n", vtos(self->s.origin));
// is this the first hintpath targeted? if so, we can do this easily.
if (other->monsterinfo.last_hint == NULL)
{
if (self->target) // forward chaining
chain = 1;
else // backward chaining
chain = -1;
}
else
{
// shortcut to last_hint
last = other->monsterinfo.last_hint;
// make sure it's valid...
if ( (last < g_edicts) || (last >= &g_edicts[globals.num_edicts]))
{
if (g_showlogic && g_showlogic->value)
{
gi.dprintf("bogus last_hint encountered.\n");
gi.dprintf("detaching from hint path %d\n", chain);
}
hintpath_stop (other);
return;
}
// if we're an endpoint, then the monster is done moving.
if (self->spawnflags & HINT_ENDPOINT)
{
chain = 0;
}
// if last hint's target is our targetname, it's forward chaining.
else if (last->target && self->targetname && !strcmp(last->target, self->targetname))
{
chain = 1;
}
// if last hint's targetname is our target, it's backward chaining.
// FIXME - last->targetname was 1, not NULL ???? was a screwed up hintpath
else if (self->target && last->targetname && !strcmp(last->targetname, self->target))
{
chain = -1;
}
else // if it gets here, i'm not sure how
{
gi.dprintf("hit an uncovered possibility in hint_path_touch\n");
chain = 0;
}
}
// find the "next" hint_path
if (chain == 1 && self->target) // forward chaining
next = G_Find(NULL, FOFS(targetname), self->target);
else if (chain == -1 && self->targetname) // backward chaining
next = G_Find(NULL, FOFS(target), self->targetname);
// if we couldn't find it, have the monster go back to normal hunting.
if (!next)
{
if (g_showlogic && g_showlogic->value)
gi.dprintf("detaching from hint path %d\n", chain);
hintpath_stop(other);
return;
}
// set the last_hint entry to this hint_path, and
// send him on his way
other->monsterinfo.last_hint = self;
if (g_showlogic && g_showlogic->value)
gi.dprintf("moving to next point, %s\n", vtos(next->s.origin));
hintpath_go(other, next);
// have the monster freeze if the hint path we just touched has a wait time
// on it, for example, when riding a plat.
if (self->wait)
{
if (g_showlogic && g_showlogic->value)
gi.dprintf("monster waiting %0.1f\n", self->wait);
other->nextthink = level.time + self->wait;
}
}
}
*/
/*QUAKED hint_path (.5 .3 0) (-8 -8 -8) (8 8 8) END
Target: next hint path
END - set this flag on the endpoints of each hintpath.
"wait" - set this if you want the monster to freeze when they touch this hintpath
*/
void SP_hint_path (edict_t *self)
{
if (deathmatch->value)
{
G_FreeEdict(self);
return;
}
if (!self->targetname && !self->target)
{
gi.dprintf ("unlinked hint_path at %s\n", vtos(self->s.origin));
G_FreeEdict (self);
return;
}
self->class_id = ENTITY_HINT_PATH;
// Lazarus: Corrections for mappers that can't follow instructions :-)
if (!self->targetname)
self->spawnflags |= HINT_ENDPOINT;
if (!self->target)
self->spawnflags |= HINT_ENDPOINT;
self->solid = SOLID_TRIGGER;
self->touch = hint_path_touch;
VectorSet (self->mins, -8, -8, -8);
VectorSet (self->maxs, 8, 8, 8);
self->svflags |= SVF_NOCLIENT;
gi.linkentity (self);
}
//int hint_paths_present;
//edict_t *hint_path_start[100];
//int num_hint_paths;
// ============
// InitHintPaths - Called by InitGame (g_save) to enable quick exits if valid
// ============
void InitHintPaths (void)
{
edict_t *e, *current;
int i, count2;
size_t field;
qboolean errors = false;
hint_paths_present = 0;
// check all the hint_paths.
field = FOFS(classname);
e = G_Find(NULL, field, "hint_path");
if (e)
hint_paths_present = 1;
else
return;
memset (hint_path_start, 0, MAX_HINT_CHAINS*sizeof (edict_t *));
num_hint_paths = 0;
while (e)
{
if (e->spawnflags & HINT_ENDPOINT)
{
if (e->target) // start point
{
if (e->targetname) // this is a bad end, ignore it
{
gi.dprintf ("Hint path at %s marked as endpoint with both target (%s) and targetname (%s)\n",
vtos (e->s.origin), e->target, e->targetname);
errors = true;
}
else
{
if (num_hint_paths >= MAX_HINT_CHAINS)
{
// gi.dprintf ("Only %d hint chains allowed. Connect some together!\n", MAX_HINT_CHAINS);
break;
}
hint_path_start[num_hint_paths++] = e;
}
}
}
e = G_Find(e, field, "hint_path");
}
field = FOFS(targetname);
for (i=0; i< num_hint_paths; i++)
{
count2 = 1;
current = hint_path_start[i];
current->hint_chain_id = i;
e = G_Find(NULL, field, current->target);
if (G_Find(e, field, current->target))
{
gi.dprintf ("\nForked hint path at %s detected for chain %d, target %s\n",
vtos (current->s.origin), num_hint_paths, current->target);
hint_path_start[i]->hint_chain = NULL;
count2 = 0;
errors = true;
continue;
}
while (e)
{
if (e->hint_chain)
{
gi.dprintf ("\nCircular hint path at %s detected for chain %d, targetname %s\n",
vtos (e->s.origin), num_hint_paths, e->targetname);
hint_path_start[i]->hint_chain = NULL;
count2 = 0;
errors = true;
break;
}
count2++;
current->hint_chain = e;
current = e;
current->hint_chain_id = i;
if (!current->target)
break;
e = G_Find(NULL, field, current->target);
if (G_Find(e, field, current->target))
{
gi.dprintf ("\nForked hint path at %s detected for chain %d, target %s\n",
vtos (current->s.origin), num_hint_paths, current->target);
hint_path_start[i]->hint_chain = NULL;
count2 = 0;
break;
}
}
}
}
// *****************************
// MISCELLANEOUS STUFF
// *****************************
// PMM - inback
// use to see if opponent is behind you (not to side)
// if it looks a lot like infront, well, there's a reason
qboolean inback (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.3)
return true;
return false;
}
float realrange (edict_t *self, edict_t *other)
{
vec3_t dir;
VectorSubtract (self->s.origin, other->s.origin, dir);
return VectorLength(dir);
}
qboolean face_wall (edict_t *self)
{
vec3_t pt;
vec3_t forward;
vec3_t ang;
trace_t tr;
AngleVectors (self->s.angles, forward, NULL, NULL);
VectorMA(self->s.origin, 64, forward, pt);
tr = gi.trace(self->s.origin, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID);
if (tr.fraction < 1 && !tr.allsolid && !tr.startsolid)
{
vectoangles2(tr.plane.normal, ang);
self->ideal_yaw = ang[YAW] + 180;
if (self->ideal_yaw > 360)
self->ideal_yaw -= 360;
M_ChangeYaw(self);
return true;
}
return false;
}
//
// Monster "Bad" Areas
//
void badarea_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
{
// drawbbox(ent);
}
edict_t *SpawnBadArea (vec3_t mins, vec3_t maxs, float lifespan, edict_t *owner)
{
edict_t *badarea;
vec3_t origin;
VectorAdd (mins, maxs, origin);
VectorScale (origin, 0.5, origin);
VectorSubtract (maxs, origin, maxs);
VectorSubtract (mins, origin, mins);
badarea = G_Spawn();
VectorCopy (origin, badarea->s.origin);
VectorCopy (maxs, badarea->maxs);
VectorCopy (mins, badarea->mins);
badarea->touch = badarea_touch;
badarea->movetype = MOVETYPE_NONE;
badarea->solid = SOLID_TRIGGER;
badarea->classname = "bad_area";
gi.linkentity (badarea);
if (lifespan)
{
badarea->think = G_FreeEdict;
badarea->nextthink = level.time + lifespan;
}
if (owner)
{
badarea->owner = owner;
}
// drawbbox(badarea);
return badarea;
}
// CheckForBadArea
// This is a customized version of G_TouchTriggers that will check
// for bad area triggers and return them if they're touched.
edict_t *CheckForBadArea (edict_t *ent)
{
int i, num;
static edict_t *touch[MAX_EDICTS]; // Knightmare- made static due to stack size
edict_t *hit;
vec3_t mins, maxs;
VectorAdd (ent->s.origin, ent->mins, mins);
VectorAdd (ent->s.origin, ent->maxs, maxs);
num = gi.BoxEdicts (mins, maxs, touch, MAX_EDICTS, AREA_TRIGGERS);
// drawbbox (ent);
// be careful, it is possible to have an entity in this
// list removed before we get to it (killtriggered)
for (i=0 ; i<num ; i++)
{
hit = touch[i];
if (!hit->inuse)
continue;
if (hit->touch == badarea_touch)
{
return hit;
}
}
return NULL;
}
#define TESLA_DAMAGE_RADIUS 128
qboolean MarkTeslaArea (edict_t *self, edict_t *tesla)
{
vec3_t mins, maxs;
edict_t *e, *tail, *area;
if (!tesla || !self)
return false;
area = NULL;
// make sure this tesla doesn't have a bad area around it already...
e = tesla->teamchain;
tail = tesla;
while (e)
{
tail = tail->teamchain;
if (!strcmp(e->classname, "bad_area"))
{
// gi.dprintf("tesla already has a bad area marked\n");
return false;
}
e = e->teamchain;
}
// see if we can grab the trigger directly
if (tesla->teamchain && tesla->teamchain->inuse)
{
edict_t *trigger;
trigger = tesla->teamchain;
// VectorAdd (trigger->s.origin, trigger->mins, mins);
// VectorAdd (trigger->s.origin, trigger->maxs, maxs);
VectorCopy(trigger->absmin, mins);
VectorCopy(trigger->absmax, maxs);
if (tesla->air_finished)
area = SpawnBadArea (mins, maxs, tesla->air_finished, tesla);
else
area = SpawnBadArea (mins, maxs, tesla->nextthink, tesla);
}
// otherwise we just guess at how long it'll last.
else
{
VectorSet (mins, -TESLA_DAMAGE_RADIUS, -TESLA_DAMAGE_RADIUS, tesla->mins[2]);
VectorSet (maxs, TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS);
// Knightmare- these mins/maxs are absolute, NOT bbox coords
VectorAdd (tesla->s.origin, mins, mins);
VectorAdd (tesla->s.origin, maxs, maxs);
// area = SpawnBadArea (mins, maxs, 30.0f, tesla);
if (tesla->air_finished)
area = SpawnBadArea (mins, maxs, tesla->air_finished, tesla);
else
area = SpawnBadArea (mins, maxs, tesla->nextthink, tesla);
}
// if we spawned a bad area, then link it to the tesla
if (area)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf("Bad area marker spawned and linked to tesla\n");
tail->teamchain = area;
}
return true;
}
// Knightmare added
#define PROX_DAMAGE_RADIUS 192
qboolean MarkProxArea (edict_t *prox)
{
vec3_t mins, maxs;
edict_t *e, *tail, *area;
if (!prox)
return false;
area = NULL;
// make sure this prox doesn't have a bad area around it already...
e = prox->teamchain;
tail = prox;
while (e)
{
tail = tail->teamchain;
if (!strcmp(e->classname, "bad_area"))
{
// gi.dprintf("prox already has a bad area marked\n");
return false;
}
e = e->teamchain;
}
// see if we can grab the dmg_radius, else use macro'd default
if (prox->dmg_radius > 0) {
VectorSet (mins, -prox->dmg_radius, -prox->dmg_radius, -prox->dmg_radius);
VectorSet (maxs, prox->dmg_radius, prox->dmg_radius, prox->dmg_radius);
// These mins/maxs are absolute, NOT bbox coords
VectorAdd (prox->s.origin, mins, mins);
VectorAdd (prox->s.origin, maxs, maxs);
}
else {
VectorSet (mins, -PROX_DAMAGE_RADIUS, -PROX_DAMAGE_RADIUS, -PROX_DAMAGE_RADIUS);
VectorSet (maxs, PROX_DAMAGE_RADIUS, PROX_DAMAGE_RADIUS, PROX_DAMAGE_RADIUS);
// These mins/maxs are absolute, NOT bbox coords
VectorAdd (prox->s.origin, mins, mins);
VectorAdd (prox->s.origin, maxs, maxs);
}
// use the prox timer if available
if (prox->delay > 0)
area = SpawnBadArea (mins, maxs, prox->delay, prox);
else if (prox->nextthink > 0)
area = SpawnBadArea (mins, maxs, prox->nextthink, prox);
else
area = SpawnBadArea (mins, maxs, 30, prox);
// if we spawned a bad area, then link it to the prox
if (area)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf("Bad area marker spawned and linked to prox\n");
tail->teamchain = area;
}
return true;
}
// end Knightmare
// predictive calculator
// target is who you want to shoot
// start is where the shot comes from
// bolt_speed is how fast the shot is
// eye_height is a boolean to say whether or not to adjust to targets eye_height
// offset is how much time to miss by
// aimdir is the resulting aim direction (pass in NULL if you don't want it)
// aimpoint is the resulting aimpoint (pass in NULL if don't want it)
void PredictAim (edict_t *target, vec3_t start, float bolt_speed, qboolean eye_height, float offset, vec3_t aimdir, vec3_t aimpoint)
{
vec3_t dir, vec;
float dist, time;
if (!target || !target->inuse)
{
VectorCopy (vec3_origin, aimdir);
return;
}
VectorSubtract(target->s.origin, start, dir);
if (eye_height)
dir[2] += target->viewheight;
dist = VectorLength(dir);
time = dist / bolt_speed;
VectorMA(target->s.origin, time - offset, target->velocity, vec);
if (eye_height)
vec[2] += target->viewheight;
if (aimdir)
{
VectorSubtract (vec, start, aimdir);
VectorNormalize (aimdir);
}
if (aimpoint)
{
VectorCopy (vec, aimpoint);
}
}
qboolean below (edict_t *self, edict_t *other)
{
vec3_t vec;
float dot;
vec3_t down;
VectorSubtract (other->s.origin, self->s.origin, vec);
VectorNormalize (vec);
VectorSet (down, 0, 0, -1);
dot = DotProduct (vec, down);
if (dot > 0.95) // 18 degree arc below
return true;
return false;
}
void drawbbox (edict_t *self)
{
int lines[4][3] = {
{1, 2, 4},
{1, 2, 7},
{1, 4, 5},
{2, 4, 7}
};
int starts[4] = {0, 3, 5, 6};
vec3_t pt[8];
int i, j, k;
vec3_t coords[2];
vec3_t newbox;
vec3_t f,r,u, dir;
VectorCopy (self->absmin, coords[0]);
VectorCopy (self->absmax, coords[1]);
for (i=0; i<=1; i++)
{
for (j=0; j<=1; j++)
{
for (k=0; k<=1; k++)
{
pt[4*i+2*j+k][0] = coords[i][0];
pt[4*i+2*j+k][1] = coords[j][1];
pt[4*i+2*j+k][2] = coords[k][2];
}
}
}
for (i=0; i<= 3; i++)
{
for (j=0; j<= 2; j++)
{
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_DEBUGTRAIL);
gi.WritePosition (pt[starts[i]]);
gi.WritePosition (pt[lines[i][j]]);
gi.multicast (pt[starts[i]], MULTICAST_ALL);
}
}
vectoangles2 (self->s.angles, dir);
AngleVectors (dir, f, r, u);
VectorMA (self->s.origin, 50, f, newbox);
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_DEBUGTRAIL);
gi.WritePosition (self->s.origin);
gi.WritePosition (newbox);
gi.multicast (self->s.origin, MULTICAST_PVS);
VectorClear (newbox);
VectorMA (self->s.origin, 50, r, newbox);
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_DEBUGTRAIL);
gi.WritePosition (self->s.origin);
gi.WritePosition (newbox);
gi.multicast (self->s.origin, MULTICAST_PVS);
VectorClear (newbox);
VectorMA (self->s.origin, 50, u, newbox);
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_DEBUGTRAIL);
gi.WritePosition (self->s.origin);
gi.WritePosition (newbox);
gi.multicast (self->s.origin, MULTICAST_PVS);
VectorClear (newbox);
}
//
// New dodge code
//
void M_MonsterDodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr)
{
float r = random();
float height;
qboolean ducker = false, dodger = false;
// this needs to be here since this can be called after the monster has "died"
if (self->health < 1)
return;
if ((self->monsterinfo.duck) && (self->monsterinfo.unduck))
ducker = true;
if ((self->monsterinfo.sidestep) && !(self->monsterinfo.aiflags & AI_STAND_GROUND))
dodger = true;
if ((!ducker) && (!dodger))
return;
// if ((g_showlogic) && (g_showlogic->value))
// {
// if (self->monsterinfo.aiflags & AI_DODGING)
// gi.dprintf ("dodging - ");
// if (self->monsterinfo.aiflags & AI_DUCKED)
// gi.dprintf ("ducked - ");
// }
if (!self->enemy)
{
self->enemy = attacker;
FoundTarget (self);
}
// PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was
// seeing numbers like 13 and 14)
if ((eta < 0.1) || (eta > 5))
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("timeout\n");
return;
}
// skill level determination..
if (r > (0.25*((skill->value)+1)))
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("skillout\n");
return;
}
// stop charging, since we're going to dodge (somehow) instead
// soldier_stop_charge (self);
if (ducker)
{
height = self->absmax[2]-32-1; // the -1 is because the absmax is s.origin + maxs + 1
// FIXME, make smarter
// if we only duck, and ducking won't help or we're already ducking, do nothing
//
// need to add monsterinfo.abort_duck() and monsterinfo.next_duck_time
if ((!dodger) && ((tr->endpos[2] <= height) || (self->monsterinfo.aiflags & AI_DUCKED)))
return;
}
else
height = self->absmax[2];
if (dodger)
{
// if we're already dodging, just finish the sequence, i.e. don't do anything else
if (self->monsterinfo.aiflags & AI_DODGING)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("already dodging\n");
return;
}
// if we're ducking already, or the shot is at our knees
if ((tr->endpos[2] <= height) || (self->monsterinfo.aiflags & AI_DUCKED))
{
vec3_t right, diff;
AngleVectors (self->s.angles, NULL, right, NULL);
VectorSubtract (tr->endpos, self->s.origin, diff);
if (DotProduct (right, diff) < 0)
{
self->monsterinfo.lefty = 0;
// gi.dprintf ("left\n");
} else {
self->monsterinfo.lefty = 1;
// gi.dprintf ("right\n");
}
// if we are currently ducked, unduck
if ((ducker) && (self->monsterinfo.aiflags & AI_DUCKED))
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("unducking - ");
self->monsterinfo.unduck(self);
}
self->monsterinfo.aiflags |= AI_DODGING;
self->monsterinfo.attack_state = AS_SLIDING;
// call the monster specific code here
self->monsterinfo.sidestep (self);
return;
}
}
if (ducker)
{
if (self->monsterinfo.next_duck_time > level.time)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("ducked too often, not ducking\n");
return;
}
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("ducking!\n");
monster_done_dodge (self);
// set this prematurely; it doesn't hurt, and prevents extra iterations
self->monsterinfo.aiflags |= AI_DUCKED;
self->monsterinfo.duck (self, eta);
}
}
void monster_duck_down (edict_t *self)
{
// if (self->monsterinfo.aiflags & AI_DUCKED)
// return;
self->monsterinfo.aiflags |= AI_DUCKED;
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("duck down!\n");
// self->maxs[2] -= 32;
self->maxs[2] = self->monsterinfo.base_height - 32;
self->takedamage = DAMAGE_YES;
if (self->monsterinfo.duck_wait_time < level.time)
self->monsterinfo.duck_wait_time = level.time + 1;
gi.linkentity (self);
}
void monster_duck_hold (edict_t *self)
{
if (level.time >= self->monsterinfo.duck_wait_time)
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
else
self->monsterinfo.aiflags |= AI_HOLD_FRAME;
}
void monster_duck_up (edict_t *self)
{
self->monsterinfo.aiflags &= ~AI_DUCKED;
// self->maxs[2] += 32;
self->maxs[2] = self->monsterinfo.base_height;
self->takedamage = DAMAGE_AIM;
self->monsterinfo.next_duck_time = level.time + DUCK_INTERVAL;
gi.linkentity (self);
}
//=========================
//=========================
qboolean has_valid_enemy (edict_t *self)
{
if (!self->enemy)
return false;
if (!self->enemy->inuse)
return false;
// Lazarus: this doesn't take into account medics pursuing dead monsters
// if (self->enemy->health < 1)
// return false;
if (self->monsterinfo.aiflags & AI_MEDIC)
{
if (self->enemy->health > 0)
return false;
}
else if (self->enemy->health < 1)
return false;
return true;
}
void TargetTesla (edict_t *self, edict_t *tesla)
{
if ((!self) || (!tesla))
return;
// PMM - medic bails on healing things
if (self->monsterinfo.aiflags & AI_MEDIC)
{
if (self->enemy)
cleanupHealTarget(self->enemy);
self->monsterinfo.aiflags &= ~AI_MEDIC;
}
// store the player enemy in case we lose track of him.
if (self->enemy && self->enemy->client)
self->monsterinfo.last_player_enemy = self->enemy;
if (self->enemy != tesla)
{
self->oldenemy = self->enemy;
self->enemy = tesla;
if (self->monsterinfo.attack)
{
if (self->health <= 0)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("bad tesla attack avoided!\n");
return;
}
self->monsterinfo.attack (self);
}
else
{
FoundTarget (self);
}
}
}
// this returns a randomly selected coop player who is visible to self
// returns NULL if bad
edict_t * PickCoopTarget (edict_t *self)
{
// no more than 4 players in coop, so..
edict_t *targets[4];
int num_targets = 0, targetID;
edict_t *ent;
int player;
// if we're not in coop, this is a noop
if (!coop || !coop->value)
return NULL;
memset (targets, 0, 4*sizeof(edict_t *));
for (player = 1; player <= game.maxclients; player++)
{
ent = &g_edicts[player];
if (!ent->inuse)
continue;
if (!ent->client)
continue;
if (visible(self, ent))
targets[num_targets++] = ent;
}
if (!num_targets)
return NULL;
// get a number from 0 to (num_targets-1)
targetID = (random() * (float)num_targets);
// just in case we got a 1.0 from random
if (targetID == num_targets)
targetID--;
return targets[targetID];
}
// only meant to be used in coop
int CountPlayers (void)
{
edict_t *ent;
int count = 0;
int player;
// if we're not in coop, this is a noop
if (!coop || !coop->value)
return 1;
for (player = 1; player <= game.maxclients; player++)
{
ent = &g_edicts[player];
if (!ent->inuse)
continue;
if (!ent->client)
continue;
count++;
}
/*
ent = g_edicts+1; // skip the worldspawn
while (ent)
{
if ((ent->client) && (ent->inuse))
{
ent++;
count++;
}
else
ent = NULL;
}
*/
return count;
}
//*******************
// JUMPING AIDS
//*******************
void monster_jump_start (edict_t *self)
{
self->timestamp = level.time;
}
qboolean monster_jump_finished (edict_t *self)
{
if ((level.time - self->timestamp) > 3)
return true;
// Lazarus: Ummm? I suppose this should return false here, though this
// line was not included in Rogue's code
return false;
}