You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
568 lines
11 KiB
568 lines
11 KiB
/* |
|
Copyright (C) 1997-2001 Id Software, Inc. |
|
|
|
This program is free software; you can redistribute it and/or |
|
modify it under the terms of the GNU General Public License |
|
as published by the Free Software Foundation; either version 2 |
|
of the License, or (at your option) any later version. |
|
|
|
This program is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
|
|
|
See the GNU General Public License for more details. |
|
|
|
You should have received a copy of the GNU General Public License |
|
along with this program; if not, write to the Free Software |
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|
|
|
*/ |
|
// g_utils.c -- misc utility functions for game module |
|
|
|
#include "g_local.h" |
|
|
|
|
|
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]; |
|
} |
|
|
|
|
|
/* |
|
============= |
|
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 (!Q_stricmp (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; |
|
} |
|
|
|
|
|
/* |
|
============= |
|
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; |
|
return; |
|
} |
|
|
|
|
|
// |
|
// print the message |
|
// |
|
if ((ent->message) && !(activator->svflags & SVF_MONSTER)) |
|
{ |
|
gi.centerprintf (activator, "%s", ent->message); |
|
if (ent->noise_index) |
|
gi.sound (activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0); |
|
else |
|
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))) |
|
{ |
|
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 (!Q_stricmp(t->classname, "func_areaportal") && |
|
(!Q_stricmp(ent->classname, "func_door") || !Q_stricmp(ent->classname, "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; |
|
|
|
Com_sprintf (s, 32, "(%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[YAW] == 0 &&*/ vec[PITCH] == 0) |
|
{ |
|
yaw = 0; |
|
if (vec[YAW] > 0) |
|
yaw = 90; |
|
else if (vec[YAW] < 0) |
|
yaw = -90; |
|
} |
|
else |
|
{ |
|
yaw = (int) (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 |
|
{ |
|
if (value1[0]) |
|
yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI); |
|
else if (value1[1] > 0) |
|
yaw = 90; |
|
else |
|
yaw = -90; |
|
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; |
|
} |
|
|
|
char *G_CopyString (char *in) |
|
{ |
|
char *out; |
|
|
|
out = gi.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; |
|
} |
|
|
|
/* |
|
================= |
|
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++; |
|
G_InitEdict (e); |
|
return e; |
|
} |
|
|
|
/* |
|
================= |
|
G_FreeEdict |
|
|
|
Marks the edict as free |
|
================= |
|
*/ |
|
void G_FreeEdict (edict_t *ed) |
|
{ |
|
gi.unlinkentity (ed); // unlink from world |
|
|
|
if ((ed - g_edicts) <= (maxclients->value + BODY_QUEUE_SIZE)) |
|
{ |
|
// gi.dprintf("tried to free special edict\n"); |
|
return; |
|
} |
|
|
|
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; |
|
|
|
// 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! |
|
================= |
|
*/ |
|
qboolean 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 |
|
}
|
|
|