2020-01-05 02:25:00 +00:00
|
|
|
|
|
|
|
#include "g_local.h"
|
|
|
|
|
|
|
|
//===============================
|
|
|
|
// BLOCKED Logic
|
|
|
|
//===============================
|
|
|
|
|
|
|
|
/*
|
2021-07-26 06:55:41 +00:00
|
|
|
gi.WriteByte (svc_temp_entity);
|
|
|
|
gi.WriteByte (TE_DEBUGTRAIL);
|
|
|
|
gi.WritePosition (pt1);
|
|
|
|
gi.WritePosition (pt2);
|
|
|
|
gi.multicast (pt1, MULTICAST_PVS);
|
2020-01-05 02:25:00 +00:00
|
|
|
|
2021-07-26 06:55:41 +00:00
|
|
|
self->nextthink = level.time + 10;
|
2020-01-05 02:25:00 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!self->enemy)
|
2020-01-05 02:25:00 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
// blocked checkshot is only against players. this will
|
|
|
|
// filter out player sounds and other shit they should
|
|
|
|
// not be firing at.
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!(self->enemy->client))
|
2020-01-05 02:25:00 +00:00
|
|
|
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;
|
|
|
|
|
2020-10-29 17:03:20 +00:00
|
|
|
if (self->monsterinfo.attack)
|
2020-01-05 02:25:00 +00:00
|
|
|
self->monsterinfo.attack(self);
|
|
|
|
|
|
|
|
self->monsterinfo.aiflags &= ~AI_BLOCKED;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
playerVisible = visible (self, self->enemy);
|
2021-07-26 06:55:41 +00:00
|
|
|
// always shoot at teslas and prox
|
2020-10-29 17:03:20 +00:00
|
|
|
if (playerVisible)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
2021-07-26 06:55:41 +00:00
|
|
|
// if (!strcmp(self->enemy->classname, "tesla"))
|
|
|
|
if ( !strcmp(self->enemy->classname, "tesla") || !strcmp(self->enemy->classname, "prox") )
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
2021-07-26 06:55:41 +00:00
|
|
|
// if (g_showlogic && g_showlogic->value)
|
|
|
|
// gi.dprintf("blocked: taking a shot\n");
|
2020-01-05 02:25:00 +00:00
|
|
|
|
|
|
|
// turn on AI_BLOCKED to let the monster know the attack is being called
|
|
|
|
// by the blocked functions...
|
|
|
|
self->monsterinfo.aiflags |= AI_BLOCKED;
|
|
|
|
|
2020-10-29 17:03:20 +00:00
|
|
|
if (self->monsterinfo.attack)
|
2021-07-26 06:55:41 +00:00
|
|
|
self->monsterinfo.attack (self);
|
2020-01-05 02:25:00 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!self->enemy)
|
2020-01-05 02:25:00 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
// check player's relative altitude
|
2020-10-29 17:03:20 +00:00
|
|
|
if (self->enemy->absmin[2] >= self->absmax[2])
|
2020-01-05 02:25:00 +00:00
|
|
|
playerPosition = 1;
|
2020-10-29 17:03:20 +00:00
|
|
|
else if (self->enemy->absmax[2] <= self->absmin[2])
|
2020-01-05 02:25:00 +00:00
|
|
|
playerPosition = -1;
|
|
|
|
else
|
|
|
|
playerPosition = 0;
|
|
|
|
|
|
|
|
// if we're close to the same position, don't bother trying plats.
|
2020-10-29 17:03:20 +00:00
|
|
|
if (playerPosition == 0)
|
2020-01-05 02:25:00 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
plat = NULL;
|
|
|
|
|
|
|
|
// see if we're already standing on a plat.
|
2020-10-29 17:03:20 +00:00
|
|
|
if (self->groundentity && self->groundentity != world)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!strncmp(self->groundentity->classname, "func_plat", 8))
|
2020-01-05 02:25:00 +00:00
|
|
|
plat = self->groundentity;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if we're not, check to see if we'll step onto one with this move
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!plat)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
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);
|
2020-10-29 17:03:20 +00:00
|
|
|
if (trace.fraction < 1 && !trace.allsolid && !trace.startsolid)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!strncmp(trace.ent->classname, "func_plat", 8))
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
plat = trace.ent;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if we've found a plat, trigger it.
|
2020-10-29 17:03:20 +00:00
|
|
|
if (plat && plat->use)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
if (playerPosition == 1)
|
|
|
|
{
|
2020-10-29 17:03:20 +00:00
|
|
|
if ((self->groundentity == plat && plat->moveinfo.state == STATE_BOTTOM) ||
|
2020-01-05 02:25:00 +00:00
|
|
|
(self->groundentity != plat && plat->moveinfo.state == STATE_TOP))
|
|
|
|
{
|
2020-10-29 17:03:20 +00:00
|
|
|
// if (g_showlogic && g_showlogic->value)
|
2020-01-05 02:25:00 +00:00
|
|
|
// gi.dprintf("player above, and plat will raise. using!\n");
|
|
|
|
plat->use (plat, self, self);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2020-10-29 17:03:20 +00:00
|
|
|
else if (playerPosition == -1)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
2020-10-29 17:03:20 +00:00
|
|
|
if ((self->groundentity == plat && plat->moveinfo.state == STATE_TOP) ||
|
2020-01-05 02:25:00 +00:00
|
|
|
(self->groundentity != plat && plat->moveinfo.state == STATE_BOTTOM))
|
|
|
|
{
|
2020-10-29 17:03:20 +00:00
|
|
|
// if (g_showlogic && g_showlogic->value)
|
2020-01-05 02:25:00 +00:00
|
|
|
// gi.dprintf("player below, and plat will lower. using!\n");
|
|
|
|
plat->use (plat, self, self);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2020-10-29 17:03:20 +00:00
|
|
|
// if (g_showlogic && g_showlogic->value)
|
2020-01-05 02:25:00 +00:00
|
|
|
// 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
|
|
|
|
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!self->monsterinfo.jump)
|
2020-01-05 02:25:00 +00:00
|
|
|
return false;
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!self->enemy)
|
2020-01-05 02:25:00 +00:00
|
|
|
return false;
|
2020-10-29 17:03:20 +00:00
|
|
|
if (self->enemy)
|
2020-01-05 02:25:00 +00:00
|
|
|
target = self->enemy;
|
2020-10-29 17:03:20 +00:00
|
|
|
else if (self->movetarget)
|
2020-01-05 02:25:00 +00:00
|
|
|
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);
|
|
|
|
|
2020-10-29 17:03:20 +00:00
|
|
|
if (target->absmin[2] > (self->absmin[2] + 16))
|
2020-01-05 02:25:00 +00:00
|
|
|
playerPosition = 1;
|
2020-10-29 17:03:20 +00:00
|
|
|
else if (target->absmin[2] < (self->absmin[2] - 16))
|
2020-01-05 02:25:00 +00:00
|
|
|
playerPosition = -1;
|
|
|
|
else
|
|
|
|
playerPosition = 0;
|
|
|
|
|
2020-10-29 17:03:20 +00:00
|
|
|
if (playerPosition == -1 && maxDown)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
// 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);
|
2020-10-29 17:03:20 +00:00
|
|
|
if (trace.fraction < 1)
|
2020-01-05 02:25:00 +00:00
|
|
|
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);
|
2020-10-29 17:03:20 +00:00
|
|
|
if (trace.fraction < 1 && !trace.allsolid && !trace.startsolid)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
2020-10-29 17:03:20 +00:00
|
|
|
if ((self->absmin[2] - trace.endpos[2]) >= 24 && trace.contents & MASK_SOLID)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
2020-10-29 17:03:20 +00:00
|
|
|
if ( (target->absmin[2] - trace.endpos[2]) > 32)
|
2020-01-05 02:25:00 +00:00
|
|
|
return false;
|
2020-10-29 17:03:20 +00:00
|
|
|
if (trace.plane.normal[2] < 0.9)
|
2020-01-05 02:25:00 +00:00
|
|
|
return false;
|
|
|
|
VectorSubtract(target->s.origin,trace.endpos,pt1);
|
|
|
|
d1 = VectorLength(pt1);
|
2020-10-29 17:03:20 +00:00
|
|
|
if (d0 < d1)
|
2020-01-05 02:25:00 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-10-29 17:03:20 +00:00
|
|
|
else if (playerPosition == 1 && maxUp)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
VectorCopy(pt1, pt2);
|
|
|
|
pt1[2] = self->absmax[2] + maxUp;
|
|
|
|
|
|
|
|
trace = gi.trace(pt1, vec3_origin, vec3_origin, pt2, self, MASK_MONSTERSOLID | MASK_WATER);
|
2020-10-29 17:03:20 +00:00
|
|
|
if (trace.fraction < 1 && !trace.allsolid && !trace.startsolid)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
2020-10-29 17:03:20 +00:00
|
|
|
if ((trace.endpos[2] - self->absmin[2]) <= maxUp && trace.contents & MASK_SOLID)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
VectorSubtract(target->s.origin,trace.endpos,pt1);
|
|
|
|
d1 = VectorLength(pt1);
|
2020-10-29 17:03:20 +00:00
|
|
|
if (d0 < d1)
|
2020-01-05 02:25:00 +00:00
|
|
|
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;
|
|
|
|
}
|
2020-10-29 17:03:20 +00:00
|
|
|
// else if (g_showlogic && g_showlogic->value)
|
2020-01-05 02:25:00 +00:00
|
|
|
// gi.dprintf("Too high to jump %0.1f\n", (trace.endpos[2] - self->absmin[2]));
|
|
|
|
}
|
2020-10-29 17:03:20 +00:00
|
|
|
// else if (g_showlogic && g_showlogic->value)
|
2020-01-05 02:25:00 +00:00
|
|
|
// gi.dprintf("Not something I could jump onto\n");
|
|
|
|
}
|
2020-10-29 17:03:20 +00:00
|
|
|
// else if (g_showlogic && g_showlogic->value)
|
2020-01-05 02:25:00 +00:00
|
|
|
// 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
|
|
|
|
// =============
|
2020-10-29 17:03:20 +00:00
|
|
|
edict_t *hintpath_findstart (edict_t *ent)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
edict_t *e;
|
|
|
|
edict_t *last;
|
|
|
|
int field;
|
|
|
|
|
2020-10-29 17:03:20 +00:00
|
|
|
if (ent->target) // starting point
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
last = world;
|
|
|
|
field = FOFS(targetname);
|
|
|
|
e = G_Find(NULL, field, ent->target);
|
2020-10-29 17:03:20 +00:00
|
|
|
while (e)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
last = e;
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!e->target)
|
2020-01-05 02:25:00 +00:00
|
|
|
break;
|
|
|
|
e = G_Find(NULL, field, e->target);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else // end point
|
|
|
|
{
|
|
|
|
last = world;
|
|
|
|
field = FOFS(target);
|
|
|
|
e = G_Find(NULL, field, ent->targetname);
|
2020-10-29 17:03:20 +00:00
|
|
|
while (e)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
last = e;
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!e->targetname)
|
2020-01-05 02:25:00 +00:00
|
|
|
break;
|
|
|
|
e = G_Find(NULL, field, e->targetname);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!(last->spawnflags & HINT_ENDPOINT))
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
// gi.dprintf ("end of chain is not HINT_ENDPOINT\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2020-10-29 17:03:20 +00:00
|
|
|
if (last == world)
|
2020-01-05 02:25:00 +00:00
|
|
|
last = NULL;
|
|
|
|
return last;
|
|
|
|
}
|
|
|
|
|
|
|
|
// =============
|
|
|
|
// hintpath_other_end - given one endpoint of a hintpath, returns the other end.
|
|
|
|
// =============
|
2020-10-29 17:03:20 +00:00
|
|
|
edict_t *hintpath_other_end (edict_t *ent)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
edict_t *e;
|
|
|
|
edict_t *last;
|
|
|
|
int field;
|
|
|
|
|
2020-10-29 17:03:20 +00:00
|
|
|
if (ent->target) // starting point
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
last = world;
|
|
|
|
field = FOFS(targetname);
|
|
|
|
e = G_Find(NULL, field, ent->target);
|
2020-10-29 17:03:20 +00:00
|
|
|
while (e)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
last = e;
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!e->target)
|
2020-01-05 02:25:00 +00:00
|
|
|
break;
|
|
|
|
e = G_Find(NULL, field, e->target);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else // end point
|
|
|
|
{
|
|
|
|
last = world;
|
|
|
|
field = FOFS(target);
|
|
|
|
e = G_Find(NULL, field, ent->targetname);
|
2020-10-29 17:03:20 +00:00
|
|
|
while (e)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
last = e;
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!e->targetname)
|
2020-01-05 02:25:00 +00:00
|
|
|
break;
|
|
|
|
e = G_Find(NULL, field, e->targetname);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!(last->spawnflags & HINT_ENDPOINT))
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
// gi.dprintf ("end of chain is not HINT_ENDPOINT\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2020-10-29 17:03:20 +00:00
|
|
|
if (last == world)
|
2020-01-05 02:25:00 +00:00
|
|
|
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, *monster_pathchain, *target_pathchain, *checkpoint;
|
|
|
|
edict_t *closest;
|
|
|
|
float closest_range = 1000000;
|
|
|
|
edict_t *start, *destination;
|
|
|
|
int field;
|
|
|
|
int count1=0, count2=0, count3=0, count4=0, count5=0;
|
|
|
|
float r;
|
|
|
|
int i;
|
|
|
|
qboolean hint_path_represented[MAX_HINT_CHAINS];
|
|
|
|
|
|
|
|
// if there are no hint paths on this map, exit immediately.
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!hint_paths_present)
|
2020-01-05 02:25:00 +00:00
|
|
|
return false;
|
|
|
|
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!self->enemy)
|
2020-01-05 02:25:00 +00:00
|
|
|
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];
|
2020-10-29 17:03:20 +00:00
|
|
|
while (e)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
count1++;
|
|
|
|
if (e->monster_hint_chain)
|
|
|
|
e->monster_hint_chain = NULL;
|
|
|
|
if (monster_pathchain)
|
|
|
|
{
|
|
|
|
checkpoint->monster_hint_chain = e;
|
|
|
|
checkpoint = e;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
int field;
|
|
|
|
int playerVisible, selfVisible;
|
|
|
|
|
|
|
|
// if there are no hint paths on this map, exit immediately.
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!hint_paths_present)
|
2020-01-05 02:25:00 +00:00
|
|
|
return false;
|
|
|
|
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!self->enemy)
|
2020-01-05 02:25:00 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
goPoint = NULL;
|
|
|
|
field = FOFS(classname);
|
|
|
|
|
|
|
|
// check all the hint_paths.
|
|
|
|
e = G_Find(NULL, field, "hint_path");
|
2020-10-29 17:03:20 +00:00
|
|
|
while (e)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
// if it's an endpoint, check for "validity"
|
2020-10-29 17:03:20 +00:00
|
|
|
if (e->spawnflags & HINT_ENDPOINT)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
// 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.
|
2020-10-29 17:03:20 +00:00
|
|
|
if (selfVisible || playerVisible)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
// if endpoint 1 saw me, set my destination to it.
|
2020-10-29 17:03:20 +00:00
|
|
|
if (selfVisible)
|
2020-01-05 02:25:00 +00:00
|
|
|
goPoint = e;
|
|
|
|
|
|
|
|
// if both aren't visible, try the other endpoint
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!selfVisible || !playerVisible)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
e2 = hintpath_other_end(e);
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!e2) // could not connect to the other endpoint
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
gi.dprintf("Unlinked hint paths!\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if endpoint 1 saw the enemy, see if endpoint 2 sees me
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!selfVisible)
|
2020-01-05 02:25:00 +00:00
|
|
|
selfVisible = visible(e2, self);
|
|
|
|
// if endpoint 1 saw me, see if endpoint 2 sees the enemy
|
2020-10-29 17:03:20 +00:00
|
|
|
else if (!playerVisible)
|
2020-01-05 02:25:00 +00:00
|
|
|
playerVisible = visible(e2, self->enemy);
|
|
|
|
|
|
|
|
// if endpoint 2 saw me, set my destination to it.
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!goPoint && selfVisible)
|
2020-01-05 02:25:00 +00:00
|
|
|
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.
|
2020-10-29 17:03:20 +00:00
|
|
|
if (selfVisible && playerVisible)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
// set me to go to goPoint
|
2020-10-29 17:03:20 +00:00
|
|
|
if (g_showlogic && g_showlogic->value)
|
2020-01-05 02:25:00 +00:00
|
|
|
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
|
2020-10-29 17:03:20 +00:00
|
|
|
if (g_showlogic && g_showlogic->value)
|
2020-01-05 02:25:00 +00:00
|
|
|
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, *goal, *next;
|
|
|
|
// int chain; // direction - (-1) = upstream, (1) = downstream, (0) = done
|
|
|
|
qboolean goalFound = false;
|
|
|
|
|
2020-10-29 17:03:20 +00:00
|
|
|
if (other->monsterinfo.aiflags & AI_MEDIC_PATROL)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
2020-10-29 17:03:20 +00:00
|
|
|
if (other->movetarget == self)
|
2020-01-05 02:25:00 +00:00
|
|
|
medic_NextPatrolPoint(other,self);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// make sure we're the target of it's obsession
|
2020-10-29 17:03:20 +00:00
|
|
|
if (other->movetarget == self)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
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.
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!next)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
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.
|
2020-10-29 17:03:20 +00:00
|
|
|
if (self->wait)
|
2020-01-05 02:25:00 +00:00
|
|
|
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
|
2020-10-29 17:03:20 +00:00
|
|
|
if (other->movetarget == self)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
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.
|
2020-10-29 17:03:20 +00:00
|
|
|
if (other->monsterinfo.last_hint == NULL)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
2020-10-29 17:03:20 +00:00
|
|
|
if (self->target) // forward chaining
|
2020-01-05 02:25:00 +00:00
|
|
|
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]))
|
|
|
|
{
|
2020-10-29 17:03:20 +00:00
|
|
|
if (g_showlogic && g_showlogic->value)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
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.
|
2020-10-29 17:03:20 +00:00
|
|
|
if (self->spawnflags & HINT_ENDPOINT)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
chain = 0;
|
|
|
|
}
|
|
|
|
// if last hint's target is our targetname, it's forward chaining.
|
2020-10-29 17:03:20 +00:00
|
|
|
else if (last->target && self->targetname && !strcmp(last->target, self->targetname))
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
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
|
2020-10-29 17:03:20 +00:00
|
|
|
else if (self->target && last->targetname && !strcmp(last->targetname, self->target))
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
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
|
2020-10-29 17:03:20 +00:00
|
|
|
if (chain == 1 && self->target) // forward chaining
|
2020-01-05 02:25:00 +00:00
|
|
|
next = G_Find(NULL, FOFS(targetname), self->target);
|
2020-10-29 17:03:20 +00:00
|
|
|
else if (chain == -1 && self->targetname) // backward chaining
|
2020-01-05 02:25:00 +00:00
|
|
|
next = G_Find(NULL, FOFS(target), self->targetname);
|
|
|
|
|
|
|
|
// if we couldn't find it, have the monster go back to normal hunting.
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!next)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
2020-10-29 17:03:20 +00:00
|
|
|
if (g_showlogic && g_showlogic->value)
|
2020-01-05 02:25:00 +00:00
|
|
|
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;
|
2020-10-29 17:03:20 +00:00
|
|
|
if (g_showlogic && g_showlogic->value)
|
2020-01-05 02:25:00 +00:00
|
|
|
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.
|
2020-10-29 17:03:20 +00:00
|
|
|
if (self->wait)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
2020-10-29 17:03:20 +00:00
|
|
|
if (g_showlogic && g_showlogic->value)
|
2020-01-05 02:25:00 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-09-04 07:39:59 +00:00
|
|
|
self->class_id = ENTITY_HINT_PATH;
|
|
|
|
|
2020-01-05 02:25:00 +00:00
|
|
|
// 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 field, i, count2;
|
|
|
|
qboolean errors = false;
|
|
|
|
|
|
|
|
hint_paths_present = 0;
|
|
|
|
|
|
|
|
// check all the hint_paths.
|
|
|
|
field = FOFS(classname);
|
|
|
|
e = G_Find(NULL, field, "hint_path");
|
2020-10-29 17:03:20 +00:00
|
|
|
if (e)
|
2020-01-05 02:25:00 +00:00
|
|
|
hint_paths_present = 1;
|
|
|
|
else
|
|
|
|
return;
|
|
|
|
|
|
|
|
memset (hint_path_start, 0, MAX_HINT_CHAINS*sizeof (edict_t *));
|
|
|
|
num_hint_paths = 0;
|
2020-10-29 17:03:20 +00:00
|
|
|
while (e)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
2020-10-29 17:03:20 +00:00
|
|
|
if (e->spawnflags & HINT_ENDPOINT)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
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);
|
2020-10-29 17:03:20 +00:00
|
|
|
if (tr.fraction < 1 && !tr.allsolid && !tr.startsolid)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
vectoangles2(tr.plane.normal, ang);
|
|
|
|
self->ideal_yaw = ang[YAW] + 180;
|
2020-10-29 17:03:20 +00:00
|
|
|
if (self->ideal_yaw > 360)
|
2020-01-05 02:25:00 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2021-07-26 06:55:41 +00:00
|
|
|
edict_t *SpawnBadArea (vec3_t mins, vec3_t maxs, float lifespan, edict_t *owner)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
edict_t *badarea;
|
|
|
|
vec3_t origin;
|
|
|
|
|
2021-07-26 06:55:41 +00:00
|
|
|
VectorAdd (mins, maxs, origin);
|
|
|
|
VectorScale (origin, 0.5, origin);
|
2020-01-05 02:25:00 +00:00
|
|
|
|
2021-07-26 06:55:41 +00:00
|
|
|
VectorSubtract (maxs, origin, maxs);
|
|
|
|
VectorSubtract (mins, origin, mins);
|
2020-01-05 02:25:00 +00:00
|
|
|
|
|
|
|
badarea = G_Spawn();
|
2021-07-26 06:55:41 +00:00
|
|
|
VectorCopy (origin, badarea->s.origin);
|
|
|
|
VectorCopy (maxs, badarea->maxs);
|
|
|
|
VectorCopy (mins, badarea->mins);
|
2020-01-05 02:25:00 +00:00
|
|
|
badarea->touch = badarea_touch;
|
|
|
|
badarea->movetype = MOVETYPE_NONE;
|
|
|
|
badarea->solid = SOLID_TRIGGER;
|
|
|
|
badarea->classname = "bad_area";
|
|
|
|
gi.linkentity (badarea);
|
|
|
|
|
2020-10-29 17:03:20 +00:00
|
|
|
if (lifespan)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
badarea->think = G_FreeEdict;
|
|
|
|
badarea->nextthink = level.time + lifespan;
|
|
|
|
}
|
2020-10-29 17:03:20 +00:00
|
|
|
if (owner)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
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.
|
2021-07-26 06:55:41 +00:00
|
|
|
edict_t *CheckForBadArea (edict_t *ent)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
int i, num;
|
|
|
|
edict_t *touch[MAX_EDICTS], *hit;
|
|
|
|
vec3_t mins, maxs;
|
|
|
|
|
2021-07-31 06:07:06 +00:00
|
|
|
VectorAdd (ent->s.origin, ent->mins, mins);
|
|
|
|
VectorAdd (ent->s.origin, ent->maxs, maxs);
|
2020-01-05 02:25:00 +00:00
|
|
|
|
|
|
|
num = gi.BoxEdicts (mins, maxs, touch, MAX_EDICTS, AREA_TRIGGERS);
|
|
|
|
|
2021-07-31 06:07:06 +00:00
|
|
|
// drawbbox (ent);
|
2020-01-05 02:25:00 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
|
2021-04-23 09:08:26 +00:00
|
|
|
qboolean MarkTeslaArea (edict_t *self, edict_t *tesla)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
vec3_t mins, maxs;
|
2021-07-26 06:55:41 +00:00
|
|
|
edict_t *e, *tail, *area;
|
2020-01-05 02:25:00 +00:00
|
|
|
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!tesla || !self)
|
2020-01-05 02:25:00 +00:00
|
|
|
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;
|
2020-10-29 17:03:20 +00:00
|
|
|
if (!strcmp(e->classname, "bad_area"))
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
2021-04-23 09:08:26 +00:00
|
|
|
// gi.dprintf("tesla already has a bad area marked\n");
|
2020-01-05 02:25:00 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
e = e->teamchain;
|
|
|
|
}
|
|
|
|
|
|
|
|
// see if we can grab the trigger directly
|
2020-10-29 17:03:20 +00:00
|
|
|
if (tesla->teamchain && tesla->teamchain->inuse)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
edict_t *trigger;
|
|
|
|
|
|
|
|
trigger = tesla->teamchain;
|
|
|
|
|
2021-04-23 09:08:26 +00:00
|
|
|
// VectorAdd (trigger->s.origin, trigger->mins, mins);
|
|
|
|
// VectorAdd (trigger->s.origin, trigger->maxs, maxs);
|
2020-01-05 02:25:00 +00:00
|
|
|
VectorCopy(trigger->absmin, mins);
|
|
|
|
VectorCopy(trigger->absmax, maxs);
|
|
|
|
|
2020-10-29 17:03:20 +00:00
|
|
|
if (tesla->air_finished)
|
2020-01-05 02:25:00 +00:00
|
|
|
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);
|
2021-07-31 06:07:06 +00:00
|
|
|
// Knightmare- these mins/maxs are absolute, NOT bbox coords
|
|
|
|
VectorAdd (tesla->s.origin, mins, mins);
|
|
|
|
VectorAdd (tesla->s.origin, maxs, maxs);
|
2020-01-05 02:25:00 +00:00
|
|
|
|
2021-07-26 06:55:41 +00:00
|
|
|
// 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);
|
2020-01-05 02:25:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// if we spawned a bad area, then link it to the tesla
|
2020-10-29 17:03:20 +00:00
|
|
|
if (area)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
2021-07-26 06:55:41 +00:00
|
|
|
// if ((g_showlogic) && (g_showlogic->value))
|
|
|
|
// gi.dprintf("Bad area marker spawned and linked to tesla\n");
|
2020-01-05 02:25:00 +00:00
|
|
|
tail->teamchain = area;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-07-26 06:55:41 +00:00
|
|
|
// 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);
|
2021-07-31 06:07:06 +00:00
|
|
|
// These mins/maxs are absolute, NOT bbox coords
|
|
|
|
VectorAdd (prox->s.origin, mins, mins);
|
|
|
|
VectorAdd (prox->s.origin, maxs, maxs);
|
2021-07-26 06:55:41 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
VectorSet (mins, -PROX_DAMAGE_RADIUS, -PROX_DAMAGE_RADIUS, -PROX_DAMAGE_RADIUS);
|
|
|
|
VectorSet (maxs, PROX_DAMAGE_RADIUS, PROX_DAMAGE_RADIUS, PROX_DAMAGE_RADIUS);
|
2021-07-31 06:07:06 +00:00
|
|
|
// These mins/maxs are absolute, NOT bbox coords
|
|
|
|
VectorAdd (prox->s.origin, mins, mins);
|
|
|
|
VectorAdd (prox->s.origin, maxs, maxs);
|
2021-07-26 06:55:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2021-04-23 09:08:26 +00:00
|
|
|
|
2020-01-05 02:25:00 +00:00
|
|
|
// 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)
|
|
|
|
{
|
2020-10-29 17:03:20 +00:00
|
|
|
if (self->enemy->health > 0)
|
2020-01-05 02:25:00 +00:00
|
|
|
return false;
|
|
|
|
}
|
2020-10-29 17:03:20 +00:00
|
|
|
else if (self->enemy->health < 1)
|
2020-01-05 02:25:00 +00:00
|
|
|
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.
|
2020-10-29 17:03:20 +00:00
|
|
|
if (self->enemy && self->enemy->client)
|
2020-01-05 02:25:00 +00:00
|
|
|
self->monsterinfo.last_player_enemy = self->enemy;
|
|
|
|
|
2020-10-29 17:03:20 +00:00
|
|
|
if (self->enemy != tesla)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
self->oldenemy = self->enemy;
|
|
|
|
self->enemy = tesla;
|
2020-10-29 17:03:20 +00:00
|
|
|
if (self->monsterinfo.attack)
|
2020-01-05 02:25:00 +00:00
|
|
|
{
|
|
|
|
if (self->health <= 0)
|
|
|
|
{
|
2021-07-26 06:55:41 +00:00
|
|
|
// if ((g_showlogic) && (g_showlogic->value))
|
|
|
|
// gi.dprintf ("bad tesla attack avoided!\n");
|
2020-01-05 02:25:00 +00:00
|
|
|
return;
|
|
|
|
}
|
2021-07-26 06:55:41 +00:00
|
|
|
self->monsterinfo.attack (self);
|
2020-01-05 02:25:00 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-07-26 06:55:41 +00:00
|
|
|
FoundTarget (self);
|
2020-01-05 02:25:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|