1296 lines
27 KiB
C++
1296 lines
27 KiB
C++
// g_utils.c -- misc utility functions for game module
|
|
|
|
#include "g_local.h"
|
|
#include "matrix4.h"
|
|
#include "fields.h"
|
|
#include "..\qcommon\ef_flags.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.
|
|
|
|
chars to compare is the number of characters to compare for a match (like strncmp) -
|
|
set to 0 to mimic old behavior
|
|
|
|
=============
|
|
*/
|
|
edict_t *G_Find (edict_t *from, int fieldofs, char *match, int charsToCompare)
|
|
{
|
|
char *s;
|
|
|
|
if (!match)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
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(charsToCompare)
|
|
{
|
|
if (!strnicmp(s, match, charsToCompare))
|
|
return from;
|
|
}
|
|
else
|
|
{
|
|
if (!stricmp (s, match))
|
|
return from;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
findradius
|
|
|
|
Returns entities that have origins within a spherical area
|
|
|
|
findradius (origin, radius)
|
|
=================
|
|
*/
|
|
//#if 1
|
|
//fast version
|
|
|
|
CRadiusContent::CRadiusContent(vec3_t center, float radius, areatype_t desiredSolidTypes)
|
|
{
|
|
numFound = 0;
|
|
|
|
float max2;
|
|
vec3_t min;
|
|
vec3_t max;
|
|
vec3_t eorg;
|
|
int j;
|
|
edict_t *attachTouchList[MAX_RADIUS_FIND_SIZE];
|
|
int numTrigFound = 0;
|
|
|
|
max2=radius*radius;
|
|
VectorCopy(center,min);
|
|
VectorCopy(center,max);
|
|
for (j=0 ; j<3 ; j++)
|
|
{
|
|
min[j]-=radius;
|
|
max[j]+=radius;
|
|
}
|
|
|
|
switch (desiredSolidTypes)
|
|
{
|
|
case RADAREA_SOLIDS:
|
|
numFound = gi.BoxEdicts(min,max, touchlist, MAX_RADIUS_FIND_SIZE, AREA_SOLID);
|
|
break;
|
|
case RADAREA_TRIGGERS:
|
|
numFound = gi.BoxEdicts(min,max, touchlist, MAX_RADIUS_FIND_SIZE, AREA_TRIGGERS);
|
|
break;
|
|
case RADAREA_SOLIDSANDTRIGGERS:
|
|
numFound = gi.BoxEdicts(min,max, touchlist, MAX_RADIUS_FIND_SIZE, AREA_SOLID);
|
|
numTrigFound = gi.BoxEdicts(min,max, attachTouchList, MAX_RADIUS_FIND_SIZE, AREA_TRIGGERS);
|
|
break;
|
|
default:
|
|
case RADAREA_NONE:
|
|
return;
|
|
break;
|
|
}
|
|
|
|
for (int i = 0; i < numTrigFound; i++)
|
|
{
|
|
touchlist[numFound+i] = attachTouchList[i];
|
|
}
|
|
numFound += numTrigFound;
|
|
|
|
// if this returns MAX_RADIUS_FIND_SIZE, you've just searched an area with at least 128 ents around - wow! If so, we've got
|
|
// problems... That's just too much.
|
|
assert(numFound < MAX_RADIUS_FIND_SIZE);
|
|
|
|
// The following two steps eliminate invalid guys
|
|
|
|
int foundNum = numFound;
|
|
|
|
for( i = 0; i < foundNum; i++)
|
|
{
|
|
if(!touchlist[i]->inuse)
|
|
{
|
|
touchlist[i] = 0;
|
|
numFound--;
|
|
}
|
|
else
|
|
{
|
|
for (j = 0 ; j<3 ; j++)
|
|
{
|
|
eorg[j] = center[j] - (touchlist[i]->s.origin[j] + (touchlist[i]->mins[j] + touchlist[i]->maxs[j])*0.5);
|
|
}
|
|
if(VectorLengthSquared(eorg) > max2)
|
|
{
|
|
touchlist[i] = 0;
|
|
numFound--;
|
|
}
|
|
}
|
|
}
|
|
|
|
edict_t **start = &touchlist[0];
|
|
edict_t **end = &touchlist[foundNum-1];
|
|
|
|
do
|
|
{
|
|
while((*start)&&(start < end))
|
|
{
|
|
start++;
|
|
}
|
|
while((!(*end))&&(end > start))
|
|
{
|
|
end--;
|
|
}
|
|
|
|
// this should be the first set of empty / fulls we can find
|
|
*start = *end;
|
|
start++;
|
|
end--;
|
|
}while(start < end);
|
|
}
|
|
|
|
CRadiusContent::CRadiusContent(vec3_t center, float radius, int useAIPoints, int nodeID, int squashZ)
|
|
{
|
|
numFound = 0;
|
|
|
|
if(useAIPoints)
|
|
{
|
|
// this no longer uses the AIpoints but instead something a bit quicker, easier, and more consistent
|
|
gmonster.FindGuysInRadius(center, radius, this, squashZ);
|
|
return;
|
|
}
|
|
|
|
float max2;
|
|
vec3_t min;
|
|
vec3_t max;
|
|
vec3_t eorg;
|
|
int j;
|
|
|
|
max2=radius*radius;
|
|
VectorCopy(center,min);
|
|
VectorCopy(center,max);
|
|
for (j=0 ; j<3 ; j++)
|
|
{
|
|
min[j]-=radius;
|
|
max[j]+=radius;
|
|
}
|
|
|
|
numFound = gi.BoxEdicts(min,max, touchlist, MAX_RADIUS_FIND_SIZE, AREA_SOLID);
|
|
|
|
// if this returns MAX_RADIUS_FIND_SIZE, you've just searched an area with at least 128 ents around - wow! If so, we've got
|
|
// problems... That's just too much.
|
|
assert(numFound < MAX_RADIUS_FIND_SIZE);
|
|
|
|
// The following two steps eliminate invalid guys
|
|
|
|
int foundNum = numFound;
|
|
|
|
for(int i = 0; i < foundNum; i++)
|
|
{
|
|
if(!touchlist[i]->inuse)
|
|
{
|
|
touchlist[i] = 0;
|
|
numFound--;
|
|
}
|
|
else
|
|
{
|
|
for (j = 0 ; j<3 ; j++)
|
|
{
|
|
eorg[j] = center[j] - (touchlist[i]->s.origin[j] + (touchlist[i]->mins[j] + touchlist[i]->maxs[j])*0.5);
|
|
}
|
|
if(VectorLengthSquared(eorg) > max2)
|
|
{
|
|
touchlist[i] = 0;
|
|
numFound--;
|
|
}
|
|
}
|
|
}
|
|
|
|
edict_t **start = &touchlist[0];
|
|
edict_t **end = &touchlist[foundNum-1];
|
|
|
|
do
|
|
{
|
|
while((*start)&&(start < end))
|
|
{
|
|
start++;
|
|
}
|
|
while((!(*end))&&(end > start))
|
|
{
|
|
end--;
|
|
}
|
|
|
|
// this should be the first set of empty / fulls we can find
|
|
*start = *end;
|
|
start++;
|
|
end--;
|
|
}while(start < end);
|
|
}
|
|
|
|
/********************/
|
|
|
|
edict_t *performTriggerSearch(edict_t *ent, vec3_t source, float useRange)
|
|
{
|
|
edict_t *curSearch = NULL;
|
|
|
|
VectorCopy(ent->s.origin, source);
|
|
|
|
// if our bbox is intersecting with multiple triggers, that's a
|
|
//design error in the map. if we find a trigger, use it.
|
|
while( (curSearch = findradius(curSearch, source, useRange, AREA_TRIGGERS)) )
|
|
{
|
|
if (curSearch->plUse && strstr(curSearch->classname, "trigger"))
|
|
{
|
|
return curSearch;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
edict_t *performEntitySearch(edict_t *ent, vec3_t source, float useRange)
|
|
{
|
|
vec3_t toOther,looking;
|
|
float bestDp, curDp, dpMin;
|
|
edict_t *bestSearch = NULL, *curSearch = NULL;
|
|
vec3_t tempOrg;
|
|
trace_t tr;
|
|
|
|
dpMin = bestDp = 0.2; // this value affects how similar the players view vector and the vector
|
|
//from the player to the used entity have to be
|
|
AngleVectors(ent->client->ps.viewangles,looking,NULL,NULL);
|
|
|
|
VectorCopy(ent->s.origin, source);
|
|
source[2] += ent->client->ps.viewoffset[2];
|
|
|
|
while(curSearch = findradius(curSearch, source, useRange, AREA_SOLID))
|
|
{
|
|
if (!curSearch->plUse)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FindCorrectOrigin(curSearch, tempOrg);
|
|
|
|
VectorSubtract(tempOrg, ent->s.origin, toOther);
|
|
toOther[2] = 0; // we're gonna ignore the z component so there!
|
|
looking[2] = 0;
|
|
VectorNormalize(looking);
|
|
VectorNormalize(toOther);
|
|
curDp = DotProduct(toOther, looking);
|
|
|
|
if(curDp <= bestDp)continue;
|
|
|
|
gi.trace(source, NULL, NULL, tempOrg, ent, MASK_ALL, &tr);
|
|
if((tr.ent != curSearch) && (tr.fraction != 1.0))continue;
|
|
|
|
bestDp = curDp;
|
|
bestSearch = curSearch;
|
|
}
|
|
return bestSearch;
|
|
}
|
|
|
|
edict_t *findinbounds(edict_t *from, vec3_t min, vec3_t max, int nAreatype)
|
|
{
|
|
static edict_t *touchlist[MAX_EDICTS];
|
|
static int index=-1;
|
|
static int num;
|
|
|
|
if (!from)
|
|
{
|
|
num = gi.BoxEdicts(min,max, touchlist, MAX_EDICTS, nAreatype/*usually AREA_SOLID*/);
|
|
index=0;
|
|
}
|
|
else
|
|
{
|
|
assert(touchlist[index]==from);
|
|
// you cannot adjust the pointers yourself...
|
|
// this means you did not call it with the previous edict
|
|
index++;
|
|
}
|
|
for (;index<num;index++)
|
|
{
|
|
if (!touchlist[index]->inuse)
|
|
continue;
|
|
return touchlist[index];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
edict_t *findradius (edict_t *from, vec3_t org, float rad, int nAreatype /*AREA_SOLID*/)
|
|
{
|
|
static float max2;
|
|
static vec3_t min;
|
|
static vec3_t max;
|
|
vec3_t eorg;
|
|
int j;
|
|
float elen;
|
|
|
|
if (!from)
|
|
{
|
|
max2=rad*rad;
|
|
VectorCopy(org,min);
|
|
VectorCopy(org,max);
|
|
for (j=0 ; j<3 ; j++)
|
|
{
|
|
min[j]-=rad;
|
|
max[j]+=rad;
|
|
}
|
|
}
|
|
while (1)
|
|
{
|
|
from=findinbounds(from,min,max, nAreatype);
|
|
if (!from)
|
|
{
|
|
return 0;
|
|
}
|
|
if (!from->inuse)
|
|
continue;
|
|
// if we did an AREA_TRIGGERS search and came back with a trigger, there's no need
|
|
//to check anything else. just return the trigger.
|
|
if (AREA_TRIGGERS == nAreatype)
|
|
{
|
|
return from;
|
|
}
|
|
for (j=0 ; j<3 ; j++)
|
|
eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5);
|
|
elen = DotProduct(eorg,eorg);
|
|
if (elen > max2)
|
|
continue;
|
|
return from;
|
|
}
|
|
}
|
|
|
|
//#else
|
|
//slow version
|
|
edict_t *oldfindradius (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;
|
|
}
|
|
//#endif
|
|
|
|
/*
|
|
=============
|
|
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->killfacing = ent->killfacing;
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// print the message
|
|
//
|
|
if ((ent->message || ent->sp_message) && !(activator->svflags & SVF_MONSTER))
|
|
{
|
|
if (ent->sp_message)
|
|
{
|
|
gi.SP_Print(activator, ent->sp_message);
|
|
}
|
|
else
|
|
{
|
|
gi.centerprintf (activator, "%s", ent->message);
|
|
}
|
|
if (ent->noise_index)
|
|
{
|
|
gi.sound (activator, CHAN_AUTO, ent->noise_index, .6, ATTN_NORM, 0);
|
|
}
|
|
else
|
|
{
|
|
gi.sound (activator, CHAN_AUTO, gi.soundindex ("Misc/Talk.wav"), .6, 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 (!stricmp(t->classname, "func_areaportal") &&
|
|
(!stricmp(ent->classname, "func_door") || !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;
|
|
else
|
|
{
|
|
yaw = (int) (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI);
|
|
if (yaw < 0)
|
|
yaw += 360;
|
|
}
|
|
|
|
return yaw;
|
|
}
|
|
|
|
|
|
vec_t VectorNormalize2 (vec3_t v, vec3_t out)
|
|
{
|
|
float length;
|
|
float ilength = 1;
|
|
|
|
length = sqrt(DotProduct(v, v));
|
|
|
|
if (length)
|
|
{
|
|
ilength /= length;
|
|
out[0] = v[0] * ilength;
|
|
out[1] = v[1] * ilength;
|
|
out[2] = v[2] * ilength;
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
void LocalToWorldVect(const vec3_t angles,const vec3_t in, vec3_t out)
|
|
{
|
|
Matrix4 m;
|
|
m.Rotate(-angles[2],-angles[0],angles[1]);
|
|
m.XFormVect(*(Vect3 *)out,*(const Vect3 *)in);
|
|
}
|
|
|
|
void AnglesFromDir(vec3_t direction, vec3_t angles)
|
|
{
|
|
angles[YAW] = atan2(direction[1], direction[0]);
|
|
angles[PITCH] = -atan2(direction[2], sqrt(direction[0]*direction[0] + direction[1]*direction[1]));
|
|
angles[ROLL] = 0;
|
|
}
|
|
|
|
void vectoangles(vec3_t in, vec3_t out)
|
|
{
|
|
// vec3_t temp;
|
|
|
|
// VectorNormalize2(in, temp);
|
|
AnglesFromDir(in, out);
|
|
VectorRadiansToDegrees(out, out);
|
|
}
|
|
|
|
|
|
char *G_CopyString (char *in)
|
|
{
|
|
char *out;
|
|
|
|
out = (char*)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->s.number = e - g_edicts;
|
|
|
|
e->gravity = 1.0;
|
|
e->friction = 1.0;
|
|
e->airresistance = 1.0;
|
|
e->bouyancy = 0.0;
|
|
e->elasticity = 0.5;
|
|
|
|
// rjr e->rendermodel = NULL;
|
|
e->s.renderindex = -1;
|
|
|
|
e->ghoulInst= 0;
|
|
|
|
// reset some CTF variables
|
|
e->count = e->ctf_flags = e->ctf_hurt_carrier = 0;
|
|
|
|
// set these prev fields to any silly values that won't match. There are no sensible #defines to use, so...
|
|
//
|
|
VectorSet(e->prev_mins, 1000, 1000, 1000);
|
|
VectorSet(e->prev_maxs, -1000,-1000,-1000);
|
|
VectorSet(e->prev_origin,10000,10000,10000);
|
|
e->prev_solid = SOLID_NOMATCH;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
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 = (int)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;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
FreeEdictBoltData
|
|
|
|
deallocate any model-specific data we stored in the edict_t
|
|
=================
|
|
*/
|
|
void FreeEdictBoltData(edict_t *ed)
|
|
{
|
|
if (ed->objInfo)
|
|
{
|
|
delete ed->objInfo;
|
|
ed->objInfo = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
G_FreeEdict
|
|
|
|
Marks the edict as free
|
|
=================
|
|
*/
|
|
void G_FreeEdict (edict_t *ed)
|
|
{
|
|
// if this was a pickup of some sort, it might have been registered with thePickupList
|
|
if (ed->flags & FL_PICKUP)
|
|
{
|
|
thePickupList.Unregister(ed);
|
|
}
|
|
|
|
// if this edict is a complex model of some sort we need to get rid of its
|
|
//bolton-related info
|
|
FreeEdictBoltData(ed);
|
|
|
|
game_ghoul.RemoveObjectInstances(ed);
|
|
/*
|
|
if (ed->ghoulInst)
|
|
{
|
|
ed->ghoulInst->Destroy();
|
|
ed->ghoulInst=0;
|
|
}
|
|
*/
|
|
gi.unlinkentity (ed); // unlink from world
|
|
|
|
if (ed->ai)
|
|
{
|
|
ed->ai.Destroy();
|
|
}
|
|
|
|
PHYS_ClearAttachList(ed);
|
|
|
|
if ((ed - g_edicts) <= ((int)maxclients->value + BODY_QUEUE_SIZE))
|
|
{
|
|
// gi.dprintf("tried to free special edict\n");
|
|
ed->s.renderfx=0;
|
|
return;
|
|
}
|
|
|
|
memset (ed, 0, sizeof(*ed));
|
|
ed->classname = "freed";
|
|
ed->freetime = level.time;
|
|
ed->inuse = false;
|
|
}
|
|
|
|
void G_Explode(edict_t *ent, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
|
{
|
|
// Com_Printf("The explosion is being triggered now.\n");
|
|
ent->nextthink = level.time + .2;
|
|
ent->s.effects |= EF_EXPLODING;
|
|
ent->think = G_FreeEdict;
|
|
}
|
|
|
|
void G_FreeAllEdicts()
|
|
{
|
|
edict_t *ent;
|
|
int i;
|
|
|
|
ent = g_edicts;
|
|
for(i = 0; i < game.maxentities; i++, ent++)
|
|
{
|
|
if(ent->inuse)
|
|
{
|
|
G_FreeEdict(ent);
|
|
}
|
|
}
|
|
memset (g_edicts, 0, game.maxentities * sizeof (g_edicts[0]));
|
|
|
|
// This is just being ultra safe - the above should have cleared out everything
|
|
game_ghoul.LevelCleanUp();
|
|
}
|
|
|
|
/*
|
|
============
|
|
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)
|
|
{
|
|
gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, NULL, MASK_PLAYERSOLID, &tr);
|
|
if (!tr.ent)
|
|
break;
|
|
|
|
// nail it
|
|
T_Damage (tr.ent, ent, ent, vec3_origin, ent->s.origin, ent->s.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
|
|
}
|
|
|
|
int GetGhoulPosDir(vec3_t sourcePos, vec3_t sourceAng, IGhoulInst *inst,
|
|
GhoulID partID, char *name, vec3_t pos, vec3_t dir, vec3_t right, vec3_t up)
|
|
{
|
|
//what the hell? This doesn't seem to work correctly for bolt-ons ;(
|
|
//perhaps I'm doing something wrong? I'm quite good at that...
|
|
|
|
IGhoulObj *obj = inst->GetGhoulObject();
|
|
|
|
if(obj)
|
|
{
|
|
GhoulID part = (partID) ? partID:obj->FindPart(name);
|
|
|
|
if(part)
|
|
{
|
|
Matrix4 EntityToWorld;
|
|
EntToWorldMatrix(sourcePos,sourceAng,EntityToWorld);
|
|
|
|
Matrix4 BoltToWorld;
|
|
Matrix4 BoltToEntity;
|
|
|
|
inst->GetBoltMatrix(level.time, BoltToEntity, part, IGhoulInst::MatrixType::Entity);
|
|
BoltToWorld.Concat(BoltToEntity,EntityToWorld);
|
|
|
|
Vect3 ChunkLoc;
|
|
BoltToWorld.GetRow(3,ChunkLoc);
|
|
|
|
if (pos)
|
|
VectorCopy((float *)&ChunkLoc, pos);
|
|
|
|
if (dir)
|
|
BoltToWorld.GetRow(2,*(Vect3 *)dir);
|
|
|
|
if(right)
|
|
{
|
|
BoltToWorld.GetRow(1,*(Vect3 *)right);
|
|
}
|
|
if(up)
|
|
{
|
|
BoltToWorld.GetRow(0,*(Vect3 *)up);
|
|
}
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (pos)
|
|
VectorClear(pos);
|
|
if (dir)
|
|
VectorClear(dir);
|
|
return 0;
|
|
}
|
|
|
|
void EntToWorldMatrix(vec3_t org, vec3_t angles, Matrix4 &m)
|
|
{
|
|
vec3_t fwd, right, up;
|
|
|
|
AngleVectors(angles, fwd, right, up);
|
|
m.Identity();
|
|
(*(Vect3 *)right)*=-1.0f;
|
|
m.SetRow(0,*(Vect3 *)fwd);
|
|
m.SetRow(1,*(Vect3 *)right);
|
|
m.SetRow(2,*(Vect3 *)up);
|
|
m.SetRow(3,*(Vect3 *)org);
|
|
m.CalcFlags();
|
|
}
|
|
|
|
void G_UpdateFrameEffects(edict_t *self)
|
|
{
|
|
if (!(self->takedamage))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check for burning effects, but ONLY on dead people!
|
|
if (self->burntime > level.time && self->health <= 0)
|
|
{
|
|
// Only every .3 seconds.
|
|
if ((int)(level.time * 10.0)%3 == 0)
|
|
{
|
|
IGhoulInst *inst = self->ghoulInst;
|
|
float r, g, b, a;
|
|
|
|
if (self->burninflictor == NULL)
|
|
self->burninflictor = world;
|
|
T_RadiusDamage (self, self->burninflictor, 10, self, 80, MOD_FIRE, DAMAGE_NO_ARMOR);
|
|
|
|
if (inst)
|
|
{
|
|
inst->GetTint(&r, &g, &b, &a);
|
|
if (r>0.2 || g>0.2 || b>0.2)
|
|
{
|
|
r-=.05;
|
|
g-=.05;
|
|
b-=.05;
|
|
inst->SetTintOnAll(r, g, b, a);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for white phosphorous slow burn
|
|
if (self->phosburntime > level.time)
|
|
{
|
|
// Only every .3 seconds, and only if dead.
|
|
if (((int)(level.time * 10.0) % 3) == 0)
|
|
{
|
|
if (self->health <= 0)
|
|
{
|
|
IGhoulInst *inst = self->ghoulInst;
|
|
float r, g, b, a;
|
|
|
|
if (inst)
|
|
{
|
|
inst->GetTint(&r, &g, &b, &a);
|
|
if (r>0.2 || g>0.2 || b>0.2)
|
|
{
|
|
r-=.05;
|
|
g-=.05;
|
|
b-=.05;
|
|
inst->SetTintOnAll(r, g, b, a);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (self->burninflictor == NULL)
|
|
self->burninflictor = world;
|
|
// Also apply some nasty slow damage to the mix.
|
|
T_Damage(self, self, self->burninflictor, vec3_up, self->s.origin, self->s.origin,
|
|
(self->phosburntime - level.time)*5.0, 0, (int)DAMAGE_NO_ARMOR, (int)MOD_FIRE, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Ignite(edict_t *target, edict_t *damager, float dmgAmount)
|
|
{
|
|
assert(target); // we got a crash reported here, so I'll make it not crash in release...
|
|
assert(damager); // If anyone gets stuck here, please tell me(Nathan)
|
|
|
|
if (lock_deaths)
|
|
{ // Adult lockout
|
|
return;
|
|
}
|
|
|
|
// kef -- I'm sorry to have to do this, but Sabre needs to be invincible if his count isn't 100
|
|
// dpk -- woo hoo, Dekker too!
|
|
bool bSabre = target->classname && (0 == strcmp("m_x_mskinboss", target->classname));
|
|
bool bDekker = target->classname && ((0 == strcmp("m_x_mraiderboss1", target->classname)) ||
|
|
(0 == strcmp("m_x_mraiderboss2", target->classname)));
|
|
|
|
if ((bSabre || bDekker) && (target->count != 100))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ((target->takedamage == DAMAGE_NO) || (target->flags & FL_GODMODE))
|
|
{ //no burning of dead stuff? I dunno.
|
|
return;
|
|
}
|
|
|
|
if (target->burntime <= level.time + 2.0 && target->health <= 0) // Only if dead.
|
|
{
|
|
if(target->ghoulInst)
|
|
{
|
|
fxRunner.exec("environ/onfireburst", target, 0);
|
|
}
|
|
|
|
target->burntime = level.time + 4.0;
|
|
}
|
|
else
|
|
{
|
|
if(target->ghoulInst)
|
|
{
|
|
fxRunner.exec("environ/quickfireburst", target, 0);
|
|
}
|
|
}
|
|
target->burninflictor = damager;
|
|
}
|
|
|
|
|
|
void Electrocute(edict_t *target, edict_t *damager)
|
|
{
|
|
assert(target); // we got a crash reported here, so I'll make it not crash in release...
|
|
assert(damager); // If anyone gets stuck here, please tell me(Nathan)
|
|
|
|
|
|
// kef -- I'm sorry to have to do this, but Sabre needs to be invincible if his count isn't 100
|
|
// dpk -- woo hoo, Dekker too!
|
|
bool bSabre = target->classname && (0 == strcmp("m_x_mskinboss", target->classname));
|
|
bool bDekker = target->classname && ((0 == strcmp("m_x_mraiderboss1", target->classname)) ||
|
|
(0 == strcmp("m_x_mraiderboss2", target->classname)));
|
|
|
|
if ((bSabre || bDekker) && (target->count != 100))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ((target->takedamage == DAMAGE_NO) || (target->flags & FL_GODMODE))
|
|
{ //no burning of dead stuff? I dunno.
|
|
return;
|
|
}
|
|
|
|
if(target->health <= 0)
|
|
{ // char dead folks
|
|
if (!lock_deaths)
|
|
{
|
|
if (target->burntime <= level.time)
|
|
{
|
|
if(target->ghoulInst)
|
|
{
|
|
fxRunner.exec("environ/onfireburst", target, 0);
|
|
}
|
|
else
|
|
{
|
|
fxRunner.exec("environ/onfireburst", target);
|
|
}
|
|
target->burntime = level.time + 2.0;
|
|
}
|
|
}
|
|
}
|
|
else if (target->zapfxtime <= level.time)
|
|
{
|
|
if(target->ghoulInst)
|
|
{
|
|
fxRunner.exec("weapons/world/mpgzap", target, 0);
|
|
}
|
|
else
|
|
{
|
|
fxRunner.exec("weapons/world/mpgzap", target);
|
|
}
|
|
target->burntime = level.time + 1.0;
|
|
}
|
|
target->burninflictor = damager;
|
|
|
|
// Blind, shake target.
|
|
if (target->client)
|
|
{ // Was a player!
|
|
target->client->blinding_alpha += 0.3;
|
|
if (target->client->blinding_alpha > 0.9)
|
|
target->client->blinding_alpha = 0.9;
|
|
|
|
if (target->client->blinding_alpha_delta <= 0 || target->client->blinding_alpha_delta > 0.2)
|
|
{
|
|
target->client->blinding_alpha_delta = 0.2;
|
|
}
|
|
|
|
FX_SetEvent_Data(target, EV_CAMERA_SHAKE_VERYHEAVY, DEFAULT_JITTER_DELTA);
|
|
}
|
|
else if (target->ai)
|
|
{ // Tis a monster!
|
|
target->ai->MuteSenses(sight_mask, 25, smute_recov_linear, 25);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void RadiusBurn(edict_t *source, float radius)
|
|
{
|
|
edict_t *target = NULL;
|
|
trace_t los;
|
|
|
|
CRadiusContent rad(source->s.origin, radius);
|
|
|
|
for(int i = 0; i < rad.getNumFound(); i++)
|
|
{
|
|
target = rad.foundEdict(i);
|
|
|
|
gi.trace(source->s.origin, vec3_origin, vec3_origin, target->s.origin, source, MASK_SOLID, &los);
|
|
if (los.fraction > 0.99)
|
|
{
|
|
Ignite(target, source, 3);
|
|
}
|
|
}
|
|
}
|
|
|
|
// this requires 3 sqrts :(
|
|
int pointLineIntersect(vec3_t start, vec3_t end, vec3_t point, float rad)
|
|
{
|
|
vec3_t line1, line2, line3;
|
|
float len;
|
|
float cosVal;
|
|
float sinVal;
|
|
float minDistToLine;
|
|
|
|
VectorSubtract(end, start, line1);
|
|
VectorSubtract(point, start, line2);
|
|
VectorSubtract(point, end, line3);
|
|
|
|
// prelim quick tests to see if we're 'tween these two planes
|
|
if(DotProduct(line1, line2) < 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if(DotProduct(line3, line1) > 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
VectorNormalize(line1);
|
|
len = VectorNormalize(line2);
|
|
|
|
cosVal = DotProduct(line1, line2);
|
|
|
|
sinVal = sqrt(1 - (cosVal * cosVal));
|
|
|
|
minDistToLine = len * sinVal;
|
|
|
|
if(minDistToLine < rad)
|
|
{
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|