1400 lines
32 KiB
C
1400 lines
32 KiB
C
// g_utils.c -- misc utility functions for game module
|
|
|
|
#include "g_local.h"
|
|
|
|
|
|
void *TagMalloc (int size, int tag)
|
|
{
|
|
if(tag == TAG_LEVEL) return Mem_Alloc(zone_level, size);
|
|
else if(tag == TAG_GAME) return Mem_Alloc(zone_game, size);
|
|
|
|
gi.dprintf("Warning: try to alloc unknown tag\n");
|
|
return NULL;
|
|
}
|
|
|
|
void FreeTags (int tag)
|
|
{
|
|
if(tag == TAG_LEVEL) Mem_EmptyPool(zone_level);
|
|
else if(tag == TAG_GAME) Mem_EmptyPool(zone_game);
|
|
else gi.dprintf("Warning: try to free unknown tag\n");
|
|
}
|
|
|
|
void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result)
|
|
{
|
|
result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1];
|
|
result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1];
|
|
result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + distance[2];
|
|
}
|
|
|
|
void G_ProjectSource2 (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t up, vec3_t result)
|
|
{
|
|
result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1] + up[0] * distance[2];
|
|
result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1] + up[1] * distance[2];
|
|
result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + up[2] * distance[2];
|
|
}
|
|
|
|
/*
|
|
=============
|
|
G_Find
|
|
|
|
Searches all active entities for the next one that holds
|
|
the matching string at fieldofs (use the FOFS() macro) in the structure.
|
|
|
|
Searches beginning at the edict after from, or the beginning if NULL
|
|
NULL will be returned if the end of the list is reached.
|
|
|
|
=============
|
|
*/
|
|
edict_t *G_Find (edict_t *from, int fieldofs, char *match)
|
|
{
|
|
char *s;
|
|
|
|
if (!from)
|
|
from = g_edicts;
|
|
else
|
|
from++;
|
|
|
|
for ( ; from < &g_edicts[globals.num_edicts] ; from++)
|
|
{
|
|
if (!from->inuse)
|
|
continue;
|
|
s = *(char **) ((byte *)from + fieldofs);
|
|
if (!s)
|
|
continue;
|
|
if (!strcasecmp (s, match))
|
|
return from;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
findradius
|
|
|
|
Returns entities that have origins within a spherical area
|
|
|
|
findradius (origin, radius)
|
|
=================
|
|
*/
|
|
edict_t *findradius (edict_t *from, vec3_t org, float rad)
|
|
{
|
|
vec3_t eorg;
|
|
int j;
|
|
|
|
if (!from)
|
|
from = g_edicts;
|
|
else
|
|
from++;
|
|
for ( ; from < &g_edicts[globals.num_edicts]; from++)
|
|
{
|
|
if (!from->inuse)
|
|
continue;
|
|
if (from->solid == SOLID_NOT)
|
|
continue;
|
|
for (j=0 ; j<3 ; j++)
|
|
eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5);
|
|
if (VectorLength(eorg) > rad)
|
|
continue;
|
|
return from;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
findradius2
|
|
|
|
Returns entities that have origins within a spherical area
|
|
|
|
only returns entities that can be damaged
|
|
only returns entities that are SVF_DAMAGEABLE
|
|
|
|
findradius2 (origin, radius)
|
|
=================
|
|
*/
|
|
edict_t *findradius2 (edict_t *from, vec3_t org, float rad)
|
|
{
|
|
// rad must be positive
|
|
vec3_t eorg;
|
|
int j;
|
|
|
|
if (!from)
|
|
from = g_edicts;
|
|
else
|
|
from++;
|
|
for ( ; from < &g_edicts[globals.num_edicts]; from++)
|
|
{
|
|
if (!from->inuse)
|
|
continue;
|
|
if (from->solid == SOLID_NOT)
|
|
continue;
|
|
if (!from->takedamage)
|
|
continue;
|
|
for (j=0 ; j<3 ; j++)
|
|
eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5);
|
|
if (VectorLength(eorg) > rad)
|
|
continue;
|
|
return from;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
G_PickTarget
|
|
|
|
Searches all active entities for the next one that holds
|
|
the matching string at fieldofs (use the FOFS() macro) in the structure.
|
|
|
|
Searches beginning at the edict after from, or the beginning if NULL
|
|
NULL will be returned if the end of the list is reached.
|
|
|
|
=============
|
|
*/
|
|
#define MAXCHOICES 8
|
|
|
|
edict_t *G_PickTarget (char *targetname)
|
|
{
|
|
edict_t *ent = NULL;
|
|
int num_choices = 0;
|
|
edict_t *choice[MAXCHOICES];
|
|
|
|
if (!targetname)
|
|
{
|
|
gi.dprintf("G_PickTarget called with NULL targetname\n");
|
|
return NULL;
|
|
}
|
|
|
|
while(1)
|
|
{
|
|
ent = G_Find (ent, FOFS(targetname), targetname);
|
|
if (!ent)
|
|
break;
|
|
choice[num_choices++] = ent;
|
|
if (num_choices == MAXCHOICES)
|
|
break;
|
|
}
|
|
|
|
if (!num_choices)
|
|
{
|
|
gi.dprintf("G_PickTarget: target %s not found\n", targetname);
|
|
return NULL;
|
|
}
|
|
|
|
return choice[rand() % num_choices];
|
|
}
|
|
|
|
|
|
|
|
void Think_Delay (edict_t *ent)
|
|
{
|
|
G_UseTargets (ent, ent->activator);
|
|
G_FreeEdict (ent);
|
|
}
|
|
|
|
/*
|
|
==============================
|
|
G_UseTargets
|
|
|
|
the global "activator" should be set to the entity that initiated the firing.
|
|
|
|
If self.delay is set, a DelayedUse entity will be created that will actually
|
|
do the SUB_UseTargets after that many seconds have passed.
|
|
|
|
Centerprints any self.message to the activator.
|
|
|
|
Search for (string)targetname in all entities that
|
|
match (string)self.target and call their .use function
|
|
|
|
==============================
|
|
*/
|
|
void G_UseTargets (edict_t *ent, edict_t *activator)
|
|
{
|
|
edict_t *t;
|
|
|
|
//
|
|
// check for a delay
|
|
//
|
|
if (ent->delay)
|
|
{
|
|
// create a temp object to fire at a later time
|
|
t = G_Spawn();
|
|
t->classname = "DelayedUse";
|
|
t->nextthink = level.time + ent->delay;
|
|
t->think = Think_Delay;
|
|
t->activator = activator;
|
|
if (!activator)
|
|
gi.dprintf ("Think_Delay with no activator\n");
|
|
t->message = ent->message;
|
|
t->target = ent->target;
|
|
t->killtarget = ent->killtarget;
|
|
t->noise_index = ent->noise_index;
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// print the message
|
|
//
|
|
if ((ent->message) && !(activator->svflags & SVF_MONSTER))
|
|
{
|
|
// Lazarus - change so that noise_index < 0 means no sound
|
|
gi.centerprintf (activator, "%s", ent->message);
|
|
if (ent->noise_index > 0)
|
|
gi.sound (activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0);
|
|
else if (ent->noise_index == 0)
|
|
gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
//
|
|
// kill killtargets
|
|
//
|
|
if (ent->killtarget)
|
|
{
|
|
t = NULL;
|
|
while ((t = G_Find (t, FOFS(targetname), ent->killtarget)))
|
|
{
|
|
// Lazarus: remove LIVE killtargeted monsters from total_monsters
|
|
if((t->svflags & SVF_MONSTER) && (t->deadflag == DEAD_NO))
|
|
{
|
|
if(!t->dmgteam || strcmp(t->dmgteam,"player"))
|
|
if(!(t->monsterinfo.aiflags & AI_GOOD_GUY))
|
|
level.total_monsters--;
|
|
}
|
|
// and decrement secret count if target_secret is removed
|
|
else if(t->class_id == ENTITY_TARGET_SECRET)
|
|
level.total_secrets--;
|
|
// same deal with target_goal, but also turn off CD music if applicable
|
|
else if(t->class_id == ENTITY_TARGET_GOAL)
|
|
{
|
|
level.total_goals--;
|
|
if (level.found_goals >= level.total_goals)
|
|
gi.configstring (CS_CDTRACK, "0");
|
|
}
|
|
G_FreeEdict (t);
|
|
if (!ent->inuse)
|
|
{
|
|
gi.dprintf("entity was removed while using killtargets\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// fire targets
|
|
//
|
|
if (ent->target)
|
|
{
|
|
t = NULL;
|
|
while ((t = G_Find (t, FOFS(targetname), ent->target)))
|
|
{
|
|
// doors fire area portals in a specific way
|
|
if ( (t->class_id == ENTITY_FUNC_AREAPORTAL) &&
|
|
( (ent->class_id == ENTITY_FUNC_DOOR) ||
|
|
(ent->class_id == ENTITY_FUNC_DOOR_ROTATING)) )
|
|
continue;
|
|
|
|
if (t == ent)
|
|
{
|
|
gi.dprintf ("WARNING: Entity used itself.\n");
|
|
}
|
|
else
|
|
{
|
|
if (t->use)
|
|
t->use (t, ent, activator);
|
|
}
|
|
if (!ent->inuse)
|
|
{
|
|
gi.dprintf("entity was removed while using targets\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=============
|
|
TempVector
|
|
|
|
This is just a convenience function
|
|
for making temporary vectors for function calls
|
|
=============
|
|
*/
|
|
float *tv (float x, float y, float z)
|
|
{
|
|
static int index;
|
|
static vec3_t vecs[8];
|
|
float *v;
|
|
|
|
// use an array so that multiple tempvectors won't collide
|
|
// for a while
|
|
v = vecs[index];
|
|
index = (index + 1)&7;
|
|
|
|
v[0] = x;
|
|
v[1] = y;
|
|
v[2] = z;
|
|
|
|
return v;
|
|
}
|
|
|
|
|
|
/*
|
|
=============
|
|
VectorToString
|
|
|
|
This is just a convenience function
|
|
for printing vectors
|
|
=============
|
|
*/
|
|
char *vtos (vec3_t v)
|
|
{
|
|
static int index;
|
|
static char str[8][32];
|
|
char *s;
|
|
|
|
// use an array so that multiple vtos won't collide
|
|
s = str[index];
|
|
index = (index + 1)&7;
|
|
|
|
sprintf (s, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]);
|
|
|
|
return s;
|
|
}
|
|
|
|
|
|
vec3_t VEC_UP = {0, -1, 0};
|
|
vec3_t MOVEDIR_UP = {0, 0, 1};
|
|
vec3_t VEC_DOWN = {0, -2, 0};
|
|
vec3_t MOVEDIR_DOWN = {0, 0, -1};
|
|
|
|
void G_SetMovedir (vec3_t angles, vec3_t movedir)
|
|
{
|
|
if (VectorCompare (angles, VEC_UP))
|
|
{
|
|
VectorCopy (MOVEDIR_UP, movedir);
|
|
}
|
|
else if (VectorCompare (angles, VEC_DOWN))
|
|
{
|
|
VectorCopy (MOVEDIR_DOWN, movedir);
|
|
}
|
|
else
|
|
{
|
|
AngleVectors (angles, movedir, NULL, NULL);
|
|
}
|
|
|
|
VectorClear (angles);
|
|
}
|
|
|
|
|
|
float vectoyaw (vec3_t vec)
|
|
{
|
|
float yaw;
|
|
|
|
if (vec[PITCH] == 0) {
|
|
if (vec[YAW] == 0)
|
|
yaw = 0;
|
|
else if (vec[YAW] > 0)
|
|
yaw = 90;
|
|
else
|
|
yaw = 270;
|
|
} else {
|
|
yaw = (int) (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI);
|
|
if (yaw < 0)
|
|
yaw += 360;
|
|
}
|
|
|
|
return yaw;
|
|
}
|
|
|
|
float vectoyaw2 (vec3_t vec)
|
|
{
|
|
float yaw;
|
|
|
|
if (vec[PITCH] == 0) {
|
|
if (vec[YAW] == 0)
|
|
yaw = 0;
|
|
else if (vec[YAW] > 0)
|
|
yaw = 90;
|
|
else
|
|
yaw = 270;
|
|
} else {
|
|
yaw = (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI);
|
|
if (yaw < 0)
|
|
yaw += 360;
|
|
}
|
|
|
|
return yaw;
|
|
}
|
|
|
|
|
|
void vectoangles (vec3_t value1, vec3_t angles)
|
|
{
|
|
float forward;
|
|
float yaw, pitch;
|
|
|
|
if (value1[1] == 0 && value1[0] == 0)
|
|
{
|
|
yaw = 0;
|
|
if (value1[2] > 0)
|
|
pitch = 90;
|
|
else
|
|
pitch = 270;
|
|
}
|
|
else
|
|
{
|
|
// PMM - fixed to correct for pitch of 0
|
|
if (value1[0])
|
|
yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI);
|
|
else if (value1[1] > 0)
|
|
yaw = 90;
|
|
else
|
|
yaw = 270;
|
|
if (yaw < 0)
|
|
yaw += 360;
|
|
|
|
forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]);
|
|
pitch = (int) (atan2(value1[2], forward) * 180 / M_PI);
|
|
if (pitch < 0)
|
|
pitch += 360;
|
|
}
|
|
|
|
angles[PITCH] = -pitch;
|
|
angles[YAW] = yaw;
|
|
angles[ROLL] = 0;
|
|
}
|
|
|
|
void vectoangles2 (vec3_t value1, vec3_t angles)
|
|
{
|
|
float forward;
|
|
float yaw, pitch;
|
|
|
|
if (value1[1] == 0 && value1[0] == 0)
|
|
{
|
|
yaw = 0;
|
|
if (value1[2] > 0)
|
|
pitch = 90;
|
|
else
|
|
pitch = 270;
|
|
}
|
|
else
|
|
{
|
|
// PMM - fixed to correct for pitch of 0
|
|
if (value1[0])
|
|
yaw = (atan2(value1[1], value1[0]) * 180 / M_PI);
|
|
else if (value1[1] > 0)
|
|
yaw = 90;
|
|
else
|
|
yaw = 270;
|
|
|
|
if (yaw < 0)
|
|
yaw += 360;
|
|
|
|
forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]);
|
|
pitch = (atan2(value1[2], forward) * 180 / M_PI);
|
|
if (pitch < 0)
|
|
pitch += 360;
|
|
}
|
|
|
|
angles[PITCH] = -pitch;
|
|
angles[YAW] = yaw;
|
|
angles[ROLL] = 0;
|
|
}
|
|
|
|
char *G_CopyString (char *in)
|
|
{
|
|
char *out;
|
|
|
|
out = TagMalloc (strlen(in)+1, TAG_LEVEL);
|
|
strcpy (out, in);
|
|
return out;
|
|
}
|
|
|
|
|
|
void G_InitEdict (edict_t *e)
|
|
{
|
|
e->inuse = true;
|
|
e->classname = "noclass";
|
|
e->gravity = 1.0;
|
|
e->s.number = e - g_edicts;
|
|
e->org_movetype = -1;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
G_Spawn
|
|
|
|
Either finds a free edict, or allocates a new one.
|
|
Try to avoid reusing an entity that was recently freed, because it
|
|
can cause the client to think the entity morphed into something else
|
|
instead of being removed and recreated, which can cause interpolated
|
|
angles and bad trails.
|
|
=================
|
|
*/
|
|
edict_t *G_Spawn (void)
|
|
{
|
|
int i;
|
|
edict_t *e;
|
|
|
|
e = &g_edicts[(int)maxclients->value+1];
|
|
for ( i=maxclients->value+1 ; i<globals.num_edicts ; i++, e++)
|
|
{
|
|
// the first couple seconds of server time can involve a lot of
|
|
// freeing and allocating, so relax the replacement policy
|
|
if (!e->inuse && ( e->freetime < 2 || level.time - e->freetime > 0.5 ) )
|
|
{
|
|
G_InitEdict (e);
|
|
return e;
|
|
}
|
|
}
|
|
|
|
if (i == game.maxentities)
|
|
gi.error ("ED_Alloc: no free edicts");
|
|
|
|
globals.num_edicts++;
|
|
|
|
if(developer->value && readout->value)
|
|
gi.dprintf("num_edicts = %d\n",globals.num_edicts);
|
|
|
|
G_InitEdict (e);
|
|
return e;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
G_FreeEdict
|
|
|
|
Marks the edict as free
|
|
=================
|
|
*/
|
|
void G_FreeEdict (edict_t *ed)
|
|
{
|
|
// Lazarus - if part of a movewith chain, remove from
|
|
// the chain and repair broken links
|
|
gi.unlinkentity (ed); // unlink from world
|
|
|
|
// Lazarus: In SP we no longer reserve slots for bodyque's
|
|
if (deathmatch->value || coop->value) {
|
|
if ((ed - g_edicts) <= (maxclients->value + BODY_QUEUE_SIZE))
|
|
{
|
|
// gi.dprintf("tried to free special edict\n");
|
|
return;
|
|
}
|
|
} else {
|
|
if ((ed - g_edicts) <= maxclients->value)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Lazarus: actor muzzle flash
|
|
if (ed->flash)
|
|
{
|
|
memset (ed->flash, 0, sizeof(*ed));
|
|
ed->flash->classname = "freed";
|
|
ed->flash->freetime = level.time;
|
|
ed->flash->inuse = false;
|
|
}
|
|
|
|
memset (ed, 0, sizeof(*ed));
|
|
ed->classname = "freed";
|
|
ed->freetime = level.time;
|
|
ed->inuse = false;
|
|
}
|
|
|
|
/*
|
|
============
|
|
G_TouchTriggers
|
|
|
|
============
|
|
*/
|
|
void G_TouchTriggers (edict_t *ent)
|
|
{
|
|
int i, num;
|
|
edict_t *touch[MAX_EDICTS], *hit;
|
|
|
|
// Lazarus: nothing touches anything if game is frozen
|
|
if (level.freeze)
|
|
return;
|
|
|
|
// dead things don't activate triggers!
|
|
if ((ent->client || (ent->svflags & SVF_MONSTER)) && (ent->health <= 0))
|
|
return;
|
|
|
|
num = gi.BoxEdicts (ent->absmin, ent->absmax, touch
|
|
, MAX_EDICTS, AREA_TRIGGERS);
|
|
|
|
// 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)
|
|
continue;
|
|
hit->touch (hit, ent, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
G_TouchSolids
|
|
|
|
Call after linking a new trigger in during gameplay
|
|
to force all entities it covers to immediately touch it
|
|
============
|
|
*/
|
|
void G_TouchSolids (edict_t *ent)
|
|
{
|
|
int i, num;
|
|
edict_t *touch[MAX_EDICTS], *hit;
|
|
|
|
num = gi.BoxEdicts (ent->absmin, ent->absmax, touch
|
|
, MAX_EDICTS, AREA_SOLID);
|
|
|
|
// 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 (ent->touch)
|
|
ent->touch (hit, ent, NULL, NULL);
|
|
if (!ent->inuse)
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
Kill box
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
/*
|
|
=================
|
|
KillBox
|
|
|
|
Kills all entities that would touch the proposed new positioning
|
|
of ent. Ent should be unlinked before calling this!
|
|
=================
|
|
*/
|
|
bool KillBox (edict_t *ent)
|
|
{
|
|
trace_t tr;
|
|
|
|
while (1)
|
|
{
|
|
tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, NULL, MASK_PLAYERSOLID);
|
|
if (!tr.ent)
|
|
break;
|
|
// nail it
|
|
T_Damage (tr.ent, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
|
|
|
|
// if we didn't kill it, fail
|
|
if (tr.ent->solid)
|
|
return false;
|
|
}
|
|
|
|
return true; // all clear
|
|
}
|
|
|
|
void AnglesNormalize(vec3_t vec)
|
|
{
|
|
while(vec[0] > 180) vec[0] -= 360;
|
|
while(vec[0] < -180) vec[0] += 360;
|
|
while(vec[1] > 360) vec[1] -= 360;
|
|
while(vec[1] < 0) vec[1] += 360;
|
|
}
|
|
|
|
float SnapToEights(float x)
|
|
{
|
|
x *= 8.0;
|
|
if (x > 0.0) x += 0.5;
|
|
else x -= 0.5;
|
|
return 0.125 * (int)x;
|
|
}
|
|
|
|
|
|
/* Lazarus - added functions */
|
|
|
|
void stuffcmd(edict_t *pent, char *pszCommand)
|
|
{
|
|
MESSAGE_BEGIN(svc_stufftext);
|
|
WRITE_STRING(pszCommand);
|
|
MESSAGE_SEND(MSG_ONE_R, NULL, pent);
|
|
}
|
|
|
|
bool point_infront (edict_t *self, vec3_t point)
|
|
{
|
|
vec3_t vec;
|
|
float dot;
|
|
vec3_t forward;
|
|
|
|
AngleVectors (self->s.angles, forward, NULL, NULL);
|
|
VectorSubtract (point, self->s.origin, vec);
|
|
VectorNormalize (vec);
|
|
dot = DotProduct (vec, forward);
|
|
|
|
if (dot > 0.3) return true;
|
|
return false;
|
|
}
|
|
|
|
float AtLeast(float x, float dx)
|
|
{
|
|
float xx;
|
|
|
|
xx = (float)(floor(x/dx - 0.5)+1.)*dx;
|
|
if(xx < x) xx += dx;
|
|
return xx;
|
|
}
|
|
|
|
edict_t *LookingAt(edict_t *ent, int filter, vec3_t endpos, float *range)
|
|
{
|
|
edict_t *who;
|
|
edict_t *trigger[MAX_EDICTS];
|
|
edict_t *ignore;
|
|
trace_t tr;
|
|
vec_t r;
|
|
vec3_t end, forward, start;
|
|
vec3_t dir, entp, mins, maxs;
|
|
int i, num;
|
|
|
|
if(!ent->client) {
|
|
if(endpos) VectorClear(endpos);
|
|
if(range) *range = 0;
|
|
return NULL;
|
|
}
|
|
VectorClear(end);
|
|
if(ent->client->spycam)
|
|
{
|
|
AngleVectors(ent->client->ps.viewangles, forward, NULL, NULL);
|
|
VectorCopy(ent->s.origin,start);
|
|
ignore = ent->client->spycam;
|
|
}
|
|
else
|
|
{
|
|
AngleVectors(ent->client->v_angle, forward, NULL, NULL);
|
|
VectorCopy(ent->s.origin, start);
|
|
start[2] += ent->viewheight;
|
|
ignore = ent;
|
|
}
|
|
|
|
VectorMA(start, 8192, forward, end);
|
|
|
|
/* First check for looking directly at a pickup item */
|
|
VectorSet(mins,-4096,-4096,-4096);
|
|
VectorSet(maxs, 4096, 4096, 4096);
|
|
num = gi.BoxEdicts (mins, maxs, trigger, MAX_EDICTS, AREA_TRIGGERS);
|
|
for (i=0 ; i<num ; i++)
|
|
{
|
|
who = trigger[i];
|
|
if (!who->inuse)
|
|
continue;
|
|
if (!who->item)
|
|
continue;
|
|
if (!visible(ent,who))
|
|
continue;
|
|
if (!infront(ent,who))
|
|
continue;
|
|
VectorSubtract(who->s.origin,start,dir);
|
|
r = VectorLength(dir);
|
|
VectorMA(start, r, forward, entp);
|
|
if(entp[0] < who->s.origin[0] - 17) continue;
|
|
if(entp[1] < who->s.origin[1] - 17) continue;
|
|
if(entp[2] < who->s.origin[2] - 17) continue;
|
|
if(entp[0] > who->s.origin[0] + 17) continue;
|
|
if(entp[1] > who->s.origin[1] + 17) continue;
|
|
if(entp[2] > who->s.origin[2] + 17) continue;
|
|
if(endpos)
|
|
VectorCopy(who->s.origin,endpos);
|
|
if(range)
|
|
*range = r;
|
|
return who;
|
|
}
|
|
|
|
tr = gi.trace (start, NULL, NULL, end, ignore, MASK_SHOT);
|
|
if (tr.fraction == 1.0)
|
|
{
|
|
// too far away
|
|
gi.sound (ent, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
|
|
return NULL;
|
|
}
|
|
if(!tr.ent)
|
|
{
|
|
// no hit
|
|
gi.sound (ent, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
|
|
return NULL;
|
|
}
|
|
if(!tr.ent->classname)
|
|
{
|
|
// should never happen
|
|
gi.sound (ent, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
|
|
return NULL;
|
|
}
|
|
|
|
if((strstr(tr.ent->classname,"func_") != NULL) && (filter & LOOKAT_NOBRUSHMODELS))
|
|
{
|
|
// don't hit on brush models
|
|
gi.sound (ent, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
|
|
return NULL;
|
|
}
|
|
if( (tr.ent == world) && (filter & LOOKAT_NOWORLD))
|
|
{
|
|
// world brush
|
|
gi.sound (ent, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
|
|
return NULL;
|
|
}
|
|
if(endpos) {
|
|
endpos[0] = tr.endpos[0];
|
|
endpos[1] = tr.endpos[1];
|
|
endpos[2] = tr.endpos[2];
|
|
}
|
|
if(range) {
|
|
VectorSubtract(tr.endpos,start,start);
|
|
*range = VectorLength(start);
|
|
}
|
|
return tr.ent;
|
|
}
|
|
|
|
void GameDirRelativePath(char *filename, char *output)
|
|
{
|
|
strcpy(output, filename );
|
|
}
|
|
|
|
/*
|
|
Lazarus: G_UseTarget is similar to G_UseTargets, but only triggers
|
|
a single target rather than all entities matching target
|
|
criteria. It *does*, however, kill all killtargets
|
|
*/
|
|
|
|
void Think_Delay_Single (edict_t *ent)
|
|
{
|
|
G_UseTarget (ent, ent->activator, ent->target_ent);
|
|
G_FreeEdict (ent);
|
|
}
|
|
|
|
void G_UseTarget (edict_t *ent, edict_t *activator, edict_t *target)
|
|
{
|
|
edict_t *t;
|
|
|
|
//
|
|
// check for a delay
|
|
//
|
|
if (ent->delay)
|
|
{
|
|
// create a temp object to fire at a later time
|
|
t = G_Spawn();
|
|
t->classname = "DelayedUse";
|
|
t->nextthink = level.time + ent->delay;
|
|
t->think = Think_Delay_Single;
|
|
t->activator = activator;
|
|
t->target_ent = target;
|
|
if (!activator)
|
|
gi.dprintf ("Think_Delay_Single with no activator\n");
|
|
t->message = ent->message;
|
|
t->target = ent->target;
|
|
t->killtarget = ent->killtarget;
|
|
t->noise_index = ent->noise_index;
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// print the message
|
|
//
|
|
if ((ent->message) && !(activator->svflags & SVF_MONSTER))
|
|
{
|
|
gi.centerprintf (activator, "%s", ent->message);
|
|
if (ent->noise_index > 0)
|
|
gi.sound (activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0);
|
|
else if (ent->noise_index == 0)
|
|
gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
//
|
|
// kill killtargets
|
|
//
|
|
if (ent->killtarget)
|
|
{
|
|
t = NULL;
|
|
while ((t = G_Find (t, FOFS(targetname), ent->killtarget)))
|
|
{
|
|
// Lazarus: remove killtargeted monsters from total_monsters
|
|
if(t->svflags & SVF_MONSTER) {
|
|
if(!t->dmgteam || strcmp(t->dmgteam,"player"))
|
|
if(!(t->monsterinfo.aiflags & AI_GOOD_GUY))
|
|
level.total_monsters--;
|
|
}
|
|
G_FreeEdict (t);
|
|
if (!ent->inuse)
|
|
{
|
|
gi.dprintf("entity was removed while using killtargets\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// fire target
|
|
//
|
|
if (target)
|
|
{
|
|
// doors fire area portals in a specific way
|
|
if ( (target->class_id == ENTITY_FUNC_AREAPORTAL) &&
|
|
((ent->class_id == ENTITY_FUNC_DOOR) ||
|
|
(ent->class_id == ENTITY_FUNC_DOOR_ROTATING)))
|
|
return;
|
|
|
|
if (target == ent)
|
|
{
|
|
gi.dprintf ("WARNING: Entity used itself.\n");
|
|
}
|
|
else
|
|
{
|
|
if (target->use)
|
|
target->use (target, ent, activator);
|
|
}
|
|
if (!ent->inuse)
|
|
{
|
|
gi.dprintf("entity was removed while using target\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
visible
|
|
|
|
returns 1 if the entity is visible to self, even if not infront ()
|
|
=============
|
|
*/
|
|
bool visible (edict_t *self, edict_t *other)
|
|
{
|
|
vec3_t spot1;
|
|
vec3_t spot2;
|
|
trace_t trace;
|
|
|
|
VectorCopy (self->s.origin, spot1);
|
|
spot1[2] += self->viewheight;
|
|
VectorCopy (other->s.origin, spot2);
|
|
spot2[2] += other->viewheight;
|
|
trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE);
|
|
|
|
// Lazarus: Take fog into account for monsters
|
|
|
|
if ( (trace.fraction == 1.0) || (trace.ent == other))
|
|
{
|
|
self->monsterinfo.visibility = 1.0;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
=============
|
|
infront
|
|
|
|
returns 1 if the entity is in front (in sight) of self
|
|
=============
|
|
*/
|
|
bool infront (edict_t *self, edict_t *other)
|
|
{
|
|
vec3_t vec;
|
|
float dot;
|
|
vec3_t forward;
|
|
|
|
AngleVectors (self->s.angles, forward, NULL, NULL);
|
|
VectorSubtract (other->s.origin, self->s.origin, vec);
|
|
VectorNormalize (vec);
|
|
dot = DotProduct (vec, forward);
|
|
|
|
if (dot > 0.3)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
void T_Damage (edict_t *in_targ, edict_t *inflictor, edict_t *in_attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod)
|
|
{
|
|
gclient_t *client;
|
|
int take;
|
|
int save;
|
|
int te_sparks;
|
|
edict_t *attacker;
|
|
edict_t *targ;
|
|
|
|
targ = in_targ;
|
|
|
|
if (!targ || !targ->inuse)
|
|
return;
|
|
|
|
if (!targ->takedamage)
|
|
return;
|
|
|
|
if (!in_attacker)
|
|
attacker = world;
|
|
else
|
|
attacker = in_attacker;
|
|
|
|
|
|
// If targ is a fake player for the real player viewing camera, get that player
|
|
// out of the camera and do the damage to him
|
|
if (targ->class_id == ENTITY_CAMPLAYER) {
|
|
if(targ->target_ent && targ->target_ent->client && targ->target_ent->client->spycam)
|
|
{
|
|
if(attacker->enemy == targ)
|
|
{
|
|
attacker->enemy = targ->target_ent;
|
|
attacker->goalentity = NULL;
|
|
attacker->movetarget = NULL;
|
|
}
|
|
targ = targ->target_ent;
|
|
if(attacker->svflags & SVF_MONSTER)
|
|
{
|
|
if(attacker->spawnflags & SF_MONSTER_GOODGUY)
|
|
{
|
|
if(attacker->enemy == targ)
|
|
{
|
|
attacker->enemy = NULL;
|
|
attacker->monsterinfo.aiflags &= ~AI_FOLLOW_LEADER;
|
|
attacker->monsterinfo.attack_finished = 0;
|
|
attacker->monsterinfo.pausetime = level.time + 100000000;
|
|
if(attacker->monsterinfo.stand)
|
|
attacker->monsterinfo.stand(attacker);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
return; // don't damage target_monitor camplayer
|
|
}
|
|
// friendly fire avoidance
|
|
// if enabled you can't hurt teammates (but you can hurt yourself)
|
|
// knockback still occurs
|
|
if ((targ != attacker) && ((deathmatch->value && ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->value))
|
|
{
|
|
if (OnSameTeam (targ, attacker))
|
|
{
|
|
if ((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE)
|
|
damage = 0;
|
|
else
|
|
mod |= MOD_FRIENDLY_FIRE;
|
|
}
|
|
}
|
|
meansOfDeath = mod;
|
|
|
|
// easy mode takes half damage
|
|
if (skill->value == 0 && deathmatch->value == 0 && targ->client)
|
|
{
|
|
damage *= 0.5;
|
|
if (!damage)
|
|
damage = 1;
|
|
}
|
|
|
|
client = targ->client;
|
|
|
|
if (dflags & DAMAGE_BULLET)
|
|
te_sparks = TE_BULLET_SPARKS;
|
|
else
|
|
te_sparks = TE_SPARKS;
|
|
|
|
VectorNormalize(dir);
|
|
|
|
// bonus damage for suprising a monster
|
|
if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0))
|
|
damage *= 2;
|
|
|
|
if (targ->flags & FL_NO_KNOCKBACK)
|
|
knockback = 0;
|
|
|
|
// Lazarus: If monster is currently chasing a "thing" and mod is a laser,
|
|
// ignore knockback to give poor guy a chance to vamoose.
|
|
if (targ->movetarget && (targ->movetarget->class_id == ENTITY_THING) && (mod == MOD_TARGET_LASER))
|
|
knockback = 0;
|
|
|
|
// figure momentum add
|
|
if (!(dflags & DAMAGE_NO_KNOCKBACK))
|
|
{
|
|
// Lazarus: Added MOVETYPE_TOSS to no knockback (pickup items and dead bodies)
|
|
if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP) && (targ->movetype != MOVETYPE_TOSS))
|
|
{
|
|
vec3_t kvel;
|
|
float mass;
|
|
|
|
if (targ->mass < 50)
|
|
mass = 50;
|
|
else
|
|
mass = targ->mass;
|
|
|
|
if (targ->client && attacker == targ)
|
|
{
|
|
VectorScale (dir, 1600.0 * (float)knockback / mass, kvel); // the rocket jump hack...
|
|
}
|
|
else
|
|
{
|
|
VectorScale (dir, 500.0 * (float)knockback / mass, kvel);
|
|
}
|
|
VectorAdd (targ->velocity, kvel, targ->velocity);
|
|
}
|
|
}
|
|
|
|
take = damage;
|
|
save = 0;
|
|
|
|
// check for invincibility
|
|
if ((client && client->invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION))
|
|
{
|
|
if (targ->pain_debounce_time < level.time)
|
|
{
|
|
gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0);
|
|
targ->pain_debounce_time = level.time + 2;
|
|
}
|
|
take = 0;
|
|
save = damage;
|
|
}
|
|
|
|
// do the damage
|
|
if (take)
|
|
{
|
|
if(targ->client)
|
|
{
|
|
if(in_targ != targ)
|
|
{
|
|
// Then player has taken the place of whatever was originally
|
|
// damaged, as in switching from func_monitor usage. Limit
|
|
// damage so that player isn't killed, and make him temporarily
|
|
// invincible
|
|
targ->health = max(2,targ->health - take);
|
|
targ->client->invincible_framenum = level.framenum+2;
|
|
targ->pain_debounce_time = max(targ->pain_debounce_time,level.time+0.3);
|
|
}
|
|
else if(level.framenum - targ->client->startframe > 30)
|
|
targ->health = targ->health - take;
|
|
else if(targ->health > 10)
|
|
targ->health = max(10,targ->health - take);
|
|
}
|
|
else
|
|
{
|
|
// Lazarus: For func_explosive target, check spawnflags and, if needed,
|
|
// damage type
|
|
if(targ->class_id == ENTITY_FUNC_EXPLOSIVE)
|
|
{
|
|
bool good_damage = true;
|
|
|
|
if(targ->spawnflags & 8) // explosion only
|
|
{
|
|
good_damage = false;
|
|
if(mod == MOD_GRENADE) good_damage = true;
|
|
if(mod == MOD_G_SPLASH) good_damage = true;
|
|
if(mod == MOD_ROCKET) good_damage = true;
|
|
if(mod == MOD_R_SPLASH) good_damage = true;
|
|
if(mod == MOD_BFG_BLAST) good_damage = true;
|
|
if(mod == MOD_HANDGRENADE) good_damage = true;
|
|
if(mod == MOD_HG_SPLASH) good_damage = true;
|
|
if(mod == MOD_EXPLOSIVE) good_damage = true;
|
|
if(mod == MOD_BARREL) good_damage = true;
|
|
if(mod == MOD_BOMB) good_damage = true;
|
|
}
|
|
if(!good_damage) return;
|
|
}
|
|
|
|
targ->health = targ->health - take;
|
|
}
|
|
|
|
if (targ->health <= 0)
|
|
{
|
|
if ((targ->svflags & SVF_MONSTER) || (client))
|
|
targ->flags |= FL_NO_KNOCKBACK;
|
|
Killed (targ, inflictor, attacker, take, point);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (client)
|
|
{
|
|
if (!(targ->flags & FL_GODMODE) && (take))
|
|
targ->pain (targ, attacker, knockback, take);
|
|
}
|
|
else if (take)
|
|
{
|
|
if (targ->pain)
|
|
targ->pain (targ, attacker, knockback, take);
|
|
}
|
|
|
|
// add to the damage inflicted on a player this frame
|
|
// the total will be turned into screen blends and view angle kicks
|
|
// at the end of the frame
|
|
if (client)
|
|
{
|
|
client->damage_parmor += 10;
|
|
client->damage_armor += 10;
|
|
client->damage_blood += take;
|
|
client->damage_knockback += knockback;
|
|
VectorCopy (point, client->damage_from);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
T_RadiusDamage
|
|
|
|
Lazarus adds dmg_slope to alter the radius damage equation. (Standard Q2 = -0.5)
|
|
============
|
|
*/
|
|
void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod, double dmg_slope)
|
|
{
|
|
float points;
|
|
edict_t *ent = NULL;
|
|
vec3_t v;
|
|
vec3_t dir;
|
|
|
|
while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL)
|
|
{
|
|
if (ent == ignore)
|
|
continue;
|
|
if (!ent->takedamage)
|
|
continue;
|
|
|
|
VectorAdd (ent->mins, ent->maxs, v);
|
|
VectorMA (ent->s.origin, 0.5, v, v);
|
|
VectorSubtract (inflictor->s.origin, v, v);
|
|
points = damage + dmg_slope * VectorLength (v);
|
|
if (ent == attacker)
|
|
points = points * 0.5;
|
|
if (points > 0)
|
|
{
|
|
if (CanDamage (ent, inflictor))
|
|
{
|
|
VectorSubtract (ent->s.origin, inflictor->s.origin, dir);
|
|
T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
Killed
|
|
============
|
|
*/
|
|
void misc_deadsoldier_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
|
|
void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
|
{
|
|
if (targ->health < -999) targ->health = -999;
|
|
|
|
targ->enemy = attacker;
|
|
|
|
if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
|
|
{
|
|
if (!(targ->monsterinfo.aiflags & AI_GOOD_GUY) )
|
|
{
|
|
level.killed_monsters++;
|
|
if (coop->value && attacker->client)
|
|
attacker->client->resp.score++;
|
|
// medics won't heal monsters that they kill themselves
|
|
if (attacker->class_id == ENTITY_MONSTER_MEDIC)
|
|
targ->owner = attacker;
|
|
}
|
|
}
|
|
|
|
if (targ->movetype == MOVETYPE_PUSH || targ->movetype == MOVETYPE_STOP || targ->movetype == MOVETYPE_NONE)
|
|
{ // doors, triggers, etc
|
|
targ->die (targ, inflictor, attacker, damage, point);
|
|
return;
|
|
}
|
|
|
|
if (inflictor->movetype == MOVETYPE_PUSH)
|
|
{
|
|
// Lazarus - Die function won't gib NO_GIB monsters... blow 'em up
|
|
if((targ->die != misc_deadsoldier_die) && (targ->spawnflags & SF_MONSTER_NOGIB))
|
|
{
|
|
BecomeExplosion1(targ);
|
|
return;
|
|
}
|
|
}
|
|
targ->die (targ, inflictor, attacker, damage, point);
|
|
}
|
|
|
|
/*
|
|
============
|
|
CanDamage
|
|
|
|
Returns true if the inflictor can directly damage the target. Used for
|
|
explosions and melee attacks.
|
|
============
|
|
*/
|
|
bool CanDamage (edict_t *targ, edict_t *inflictor)
|
|
{
|
|
vec3_t dest;
|
|
trace_t trace;
|
|
|
|
// bmodels need special checking because their origin is 0,0,0
|
|
if (targ->movetype == MOVETYPE_PUSH)
|
|
{
|
|
VectorAdd (targ->absmin, targ->absmax, dest);
|
|
VectorScale (dest, 0.5, dest);
|
|
trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
|
|
if (trace.fraction == 1.0)
|
|
return true;
|
|
if (trace.ent == targ)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, targ->s.origin, inflictor, MASK_SOLID);
|
|
if (trace.fraction == 1.0 || trace.ent == targ)
|
|
return true;
|
|
|
|
// Lazarus: This is kinda cheesy, but avoids doing goofy things in a map to make this work. If a LOS
|
|
// from inflictor to targ is blocked by a func_tracktrain, AND the targ is riding/driving
|
|
// the tracktrain, go ahead and hurt him.
|
|
|
|
if(trace.ent && (trace.ent->flags & FL_TRACKTRAIN) && ((trace.ent->owner == targ) || (targ->groundentity == trace.ent)) )
|
|
return true;
|
|
|
|
VectorCopy (targ->s.origin, dest);
|
|
dest[0] += 15.0;
|
|
dest[1] += 15.0;
|
|
trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
|
|
if (trace.fraction == 1.0 || trace.ent == targ)
|
|
return true;
|
|
|
|
VectorCopy (targ->s.origin, dest);
|
|
dest[0] += 15.0;
|
|
dest[1] -= 15.0;
|
|
trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
|
|
if (trace.fraction == 1.0 || trace.ent == targ)
|
|
return true;
|
|
|
|
VectorCopy (targ->s.origin, dest);
|
|
dest[0] -= 15.0;
|
|
dest[1] += 15.0;
|
|
trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
|
|
if (trace.fraction == 1.0 || trace.ent == targ)
|
|
return true;
|
|
|
|
VectorCopy (targ->s.origin, dest);
|
|
dest[0] -= 15.0;
|
|
dest[1] -= 15.0;
|
|
trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
|
|
if (trace.fraction == 1.0 || trace.ent == targ)
|
|
return true;
|
|
|
|
|
|
return false;
|
|
} |