mirror of
https://github.com/blendogames/thirtyflightsofloving.git
synced 2024-11-15 00:41:21 +00:00
1e5348cbe0
Added new graphic for text box. Made edict_t pointer arrays static in 3ZB2, Awakening2, and Zaero DLLs due to stack size concerns.
1790 lines
36 KiB
C
1790 lines
36 KiB
C
// 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, size_t fieldofs, char *match) // Knightmare- changed fieldofs from int
|
|
{
|
|
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;
|
|
}
|
|
|
|
//CW++
|
|
/*
|
|
=================
|
|
FindClientRadius
|
|
|
|
Returns active client entities whose bounding boxes are within a
|
|
spherical volume centred around the specified point.
|
|
=================
|
|
*/
|
|
edict_t *FindClientRadius(edict_t *from, vec3_t origin, float radius)
|
|
{
|
|
vec3_t ent_vec;
|
|
int i;
|
|
|
|
if (!from)
|
|
from = g_edicts;
|
|
else
|
|
++from;
|
|
for ( ; from < &g_edicts[game.maxclients]; ++from)
|
|
{
|
|
if (!from->client)
|
|
continue;
|
|
if (!from->inuse)
|
|
continue;
|
|
if (from->solid == SOLID_NOT)
|
|
continue;
|
|
|
|
for (i = 0; i < 3; ++i)
|
|
ent_vec[i] = origin[i] - (from->s.origin[i] + (from->mins[i] + from->maxs[i])*0.5);
|
|
|
|
if (VectorLength(ent_vec) > radius)
|
|
continue;
|
|
|
|
return from;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
FindPointRadius
|
|
|
|
Returns solid and non-solid entities whose origins are within a
|
|
spherical volume centred around the specified point.
|
|
=================
|
|
*/
|
|
edict_t *FindPointRadius(edict_t *from, vec3_t org, float rad)
|
|
{
|
|
vec3_t eorg;
|
|
int i;
|
|
|
|
if (!from)
|
|
from = g_edicts;
|
|
else
|
|
from++;
|
|
for ( ; from < &g_edicts[globals.num_edicts]; ++from)
|
|
{
|
|
if (!from->inuse)
|
|
continue;
|
|
|
|
for (i = 0; i < 3; ++i)
|
|
eorg[i] = org[i] - from->s.origin[i];
|
|
if (VectorLength(eorg) > rad)
|
|
continue;
|
|
|
|
return from;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
//CW--
|
|
|
|
/*
|
|
=================
|
|
FindRadius
|
|
|
|
Returns solid entities whose bounding boxes are within a
|
|
spherical volume centred around the specified point.
|
|
=================
|
|
*/
|
|
edict_t *FindRadius(edict_t *from, vec3_t org, float rad)
|
|
{
|
|
vec3_t eorg;
|
|
int i;
|
|
|
|
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 (i = 0; i < 3; ++i)
|
|
eorg[i] = org[i] - (from->s.origin[i] + (from->mins[i] + from->maxs[i])*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;
|
|
edict_t *choice[MAXCHOICES];
|
|
int num_choices = 0;
|
|
|
|
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;
|
|
|
|
//CW++
|
|
edict_t *cl_ent;
|
|
int i;
|
|
//CW--
|
|
|
|
|
|
// 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;
|
|
//CW++
|
|
t->svflags |= SVF_NOCLIENT;
|
|
//CW--
|
|
return;
|
|
}
|
|
|
|
// Print the message.
|
|
|
|
if ((ent->message) && !(activator->svflags & SVF_MONSTER))
|
|
{
|
|
|
|
//CW++
|
|
// For a trigger_waypoint, the message is displayed to all players.
|
|
|
|
if (ent->classname && !Q_stricmp(ent->classname, "trigger_waypoint"))
|
|
{
|
|
for (i = 1; i <= game.maxclients; ++i)
|
|
{
|
|
cl_ent = &g_edicts[i];
|
|
|
|
if (!cl_ent->client)
|
|
continue;
|
|
if (!cl_ent->inuse)
|
|
continue;
|
|
|
|
gi_centerprintf(cl_ent, "%s", ent->message);
|
|
}
|
|
|
|
if (ent->noise_index)
|
|
gi.positioned_sound(world->s.origin, world, CHAN_AUTO, ent->noise_index, 1, ATTN_NONE, 0);
|
|
else
|
|
gi.positioned_sound(world->s.origin, world, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NONE, 0);
|
|
}
|
|
else
|
|
{
|
|
//CW--
|
|
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;
|
|
}
|
|
|
|
//CW++
|
|
/*
|
|
=============
|
|
VectorToString - floating point
|
|
|
|
Prints out vector components to 3dp.
|
|
=============
|
|
*/
|
|
char *vtosf(vec3_t v)
|
|
{
|
|
static int index;
|
|
static char str[8][64];
|
|
char *s;
|
|
|
|
// use an array so that multiple vtos won't collide
|
|
s = str[index];
|
|
index = (index + 1)&7;
|
|
|
|
Com_sprintf(s, 64, "(%.3f %.3f %.3f)", v[0], v[1], v[2]);
|
|
return s;
|
|
}
|
|
//CW--
|
|
|
|
|
|
vec3_t VEC_UP = {0.0F, -1.0F, 0.0F};
|
|
vec3_t MOVEDIR_UP = {0.0F, 0.0F, 1.0F};
|
|
vec3_t VEC_DOWN = {0.0F, -2.0F, 0.0F};
|
|
vec3_t MOVEDIR_DOWN = {0.0F, 0.0F, -1.0F};
|
|
|
|
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.0)
|
|
{
|
|
yaw = 0.0;
|
|
if (vec[YAW] > 0.0)
|
|
yaw = 90.0;
|
|
else if (vec[YAW] < 0.0)
|
|
yaw = -90.0;
|
|
}
|
|
else
|
|
{
|
|
yaw = (int)(RAD2DEG(atan2(vec[YAW], vec[PITCH]))); //CW
|
|
if (yaw < 0.0)
|
|
yaw += 360.0;
|
|
}
|
|
|
|
return yaw;
|
|
}
|
|
|
|
void vectoangles(vec3_t value1, vec3_t angles)
|
|
{
|
|
float forward;
|
|
float yaw;
|
|
float pitch;
|
|
|
|
if ((value1[1] == 0.0) && (value1[0] == 0.0))
|
|
{
|
|
yaw = 0.0F;
|
|
if (value1[2] > 0.0)
|
|
pitch = 90.0;
|
|
else
|
|
pitch = 270.0;
|
|
}
|
|
else
|
|
{
|
|
if (value1[0])
|
|
yaw = (int)(RAD2DEG(atan2(value1[1], value1[0]))); //CW
|
|
else if (value1[1] > 0.0)
|
|
yaw = 90.0;
|
|
else
|
|
yaw = -90.0;
|
|
if (yaw < 0)
|
|
yaw += 360.0;
|
|
|
|
forward = sqrt(value1[0]*value1[0] + value1[1]*value1[1]);
|
|
pitch = (int)(RAD2DEG(atan2(value1[2], forward))); //CW
|
|
if (pitch < 0.0)
|
|
pitch += 360.0;
|
|
}
|
|
|
|
angles[PITCH] = -pitch;
|
|
angles[YAW] = yaw;
|
|
angles[ROLL] = 0.0;
|
|
}
|
|
|
|
// Knightmare added
|
|
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
|
|
{
|
|
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;
|
|
}
|
|
// end Knightmare
|
|
|
|
char *G_CopyString(char *in)
|
|
{
|
|
size_t outSize;
|
|
char *out;
|
|
|
|
// out = gi.TagMalloc((int)strlen(in)+1, TAG_LEVEL);
|
|
outSize = strlen(in) + 1;
|
|
out = gi.TagMalloc(outSize, TAG_LEVEL);
|
|
// strcpy(out, in);
|
|
Com_strcpy(out, outSize, 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)
|
|
{
|
|
edict_t *e;
|
|
int i;
|
|
|
|
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 && ((level.time - e->freetime > 0.5) || (e->freetime < 2.0)))
|
|
{
|
|
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) <= ((int)maxclients->value + BODY_QUEUE_SIZE))
|
|
{
|
|
gi.dprintf("BUG: tried to free special edict (%d)\n\n", (ed - g_edicts));
|
|
//CW++
|
|
if (ed->classname)
|
|
gi.dprintf("ed->classname = %s\n", ed->classname);
|
|
gi.dprintf("ed->movetype = %d\n", ed->movetype);
|
|
gi.dprintf("ed->solid = %d\n", ed->solid);
|
|
if (ed->model)
|
|
gi.dprintf("ed->model = %s\n", ed->model);
|
|
if (ed->client)
|
|
gi.dprintf("ed->client->name = %s\n", ed->client->pers.netname);
|
|
gi.dprintf("ed->burning = %s\n", (ed->burning)?"TRUE":"FALSE");
|
|
gi.dprintf("ed->disintegrated = %s\n", (ed->disintegrated)?"TRUE":"FALSE");
|
|
|
|
if (ed->owner && ed->owner->client)
|
|
gi.dprintf("owner->name = %s\n", ed->owner->client->pers.netname);
|
|
if (ed->owner && ed->owner->classname)
|
|
gi.dprintf("owner->classname = %s\n", ed->owner->classname);
|
|
|
|
if (ed->enemy)
|
|
{
|
|
if (ed->enemy->client)
|
|
gi.dprintf("enemy->name = %s\n", ed->enemy->client->pers.netname);
|
|
if (ed->enemy->classname)
|
|
gi.dprintf("enemy->classname = %s\n", ed->enemy->classname);
|
|
gi.dprintf("enemy->burning = %s\n", (ed->enemy->burning)?"TRUE":"FALSE");
|
|
gi.dprintf("enemy->disintegrated = %s\n", (ed->enemy->disintegrated)?"TRUE":"FALSE");
|
|
}
|
|
|
|
if (ed->oldenemy)
|
|
{
|
|
if (ed->oldenemy->client)
|
|
gi.dprintf("oldenemy->name = %s\n", ed->oldenemy->client->pers.netname);
|
|
if (ed->oldenemy->classname)
|
|
gi.dprintf("oldenemy->classname = %s\n", ed->oldenemy->classname);
|
|
gi.dprintf("oldenemy->burning = %s\n", (ed->oldenemy->burning)?"TRUE":"FALSE");
|
|
gi.dprintf("oldenemy->disintegrated = %s\n", (ed->oldenemy->disintegrated)?"TRUE":"FALSE");
|
|
}
|
|
|
|
gi.dprintf("----------------------------\n\n");
|
|
//CW--
|
|
return;
|
|
}
|
|
|
|
memset(ed, 0, sizeof(*ed));
|
|
ed->classname = "freed";
|
|
ed->freetime = level.time;
|
|
ed->inuse = false;
|
|
}
|
|
|
|
//CW++
|
|
void trigger_push_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
|
|
//CW--
|
|
|
|
/*
|
|
============
|
|
G_TouchTriggers
|
|
|
|
============
|
|
*/
|
|
void G_TouchTriggers(edict_t *ent)
|
|
{
|
|
static edict_t *touch[MAX_EDICTS]; // Knightmare- made static due to stack size
|
|
edict_t *hit;
|
|
int num;
|
|
int i;
|
|
|
|
// 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;
|
|
//DH++
|
|
if (ent->client && ent->client->spycam)
|
|
continue;
|
|
//DH--
|
|
|
|
//CW++
|
|
if (ent->isabot && (hit->touch == trigger_push_touch))
|
|
ent->client->movestate |= STS_TRIGPUSH;
|
|
//CW--
|
|
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)
|
|
{
|
|
static edict_t *touch[MAX_EDICTS]; // Knightmare- made static due to stack size
|
|
edict_t *hit;
|
|
int num;
|
|
int i;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
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;
|
|
|
|
//CW++
|
|
trace_t tr_check;
|
|
edict_t *kill_ent;
|
|
//CW--
|
|
|
|
while (1)
|
|
{
|
|
tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, ent->s.origin, NULL, MASK_PLAYERSOLID);
|
|
|
|
//CW++
|
|
// If we're inside a solid, do a point check on our origin to see if
|
|
// we're also inside another player (this fixes the "buried teleporters" bug).
|
|
|
|
if (tr.startsolid)
|
|
{
|
|
tr_check = gi.trace(ent->s.origin, vec3_origin, vec3_origin, ent->s.origin, NULL, MASK_PLAYERSOLID);
|
|
if (!tr_check.ent)
|
|
break;
|
|
else
|
|
kill_ent = tr_check.ent;
|
|
}
|
|
else
|
|
{
|
|
if (!tr.ent)
|
|
break;
|
|
else
|
|
kill_ent = tr.ent;
|
|
}
|
|
//CW--
|
|
// nail it
|
|
T_Damage(kill_ent, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG); //CW
|
|
|
|
// if we didn't kill it, fail
|
|
if (kill_ent->solid) //CW
|
|
return false;
|
|
}
|
|
|
|
return true; // all clear
|
|
}
|
|
|
|
// Knightmare added
|
|
void GameDirRelativePath (const char *filename, char *output, size_t outputSize)
|
|
{
|
|
#ifdef KMQUAKE2_ENGINE_MOD
|
|
Com_sprintf(output, outputSize, "%s/%s", gi.GameDir(), filename);
|
|
#else // KMQUAKE2_ENGINE_MOD
|
|
cvar_t *basedir, *gamedir;
|
|
|
|
basedir = gi.cvar("basedir", "", 0);
|
|
gamedir = gi.cvar("gamedir", "", 0);
|
|
if (strlen(gamedir->string))
|
|
Com_sprintf(output, outputSize, "%s/%s/%s", basedir->string, gamedir->string, filename);
|
|
else
|
|
Com_sprintf(output, outputSize, "%s/baseq2/%s", basedir->string, filename);
|
|
#endif // KMQUAKE2_ENGINE_MOD
|
|
}
|
|
|
|
void SavegameDirRelativePath (const char *filename, char *output, size_t outputSize)
|
|
{
|
|
#ifdef KMQUAKE2_ENGINE_MOD
|
|
Com_sprintf(output, outputSize, "%s/%s", gi.SaveGameDir(), filename);
|
|
#else // KMQUAKE2_ENGINE_MOD
|
|
cvar_t *basedir, *gamedir;
|
|
|
|
basedir = gi.cvar("basedir", "", 0);
|
|
gamedir = gi.cvar("gamedir", "", 0);
|
|
if (strlen(gamedir->string))
|
|
Com_sprintf(output, outputSize, "%s/%s/%s", basedir->string, gamedir->string, filename);
|
|
else
|
|
Com_sprintf(output, outputSize, "%s/baseq2/%s", basedir->string, filename);
|
|
#endif // KMQUAKE2_ENGINE_MOD
|
|
}
|
|
|
|
void CreatePath (const char *path)
|
|
{
|
|
#ifdef KMQUAKE2_ENGINE_MOD
|
|
gi.CreatePath (path);
|
|
#else // KMQUAKE2_ENGINE_MOD
|
|
char tmpBuf[MAX_OSPATH];
|
|
char *ofs;
|
|
|
|
if (strstr(path, "..") || strstr(path, "::") || strstr(path, "\\\\") || strstr(path, "//"))
|
|
{
|
|
gi.dprintf("WARNING: refusing to create relative path '%s'\n", path);
|
|
return;
|
|
}
|
|
Com_strcpy (tmpBuf, sizeof(tmpBuf), path);
|
|
|
|
for (ofs = tmpBuf+1 ; *ofs ; ofs++)
|
|
{
|
|
if (*ofs == '/' || *ofs == '\\')
|
|
{ // create the directory
|
|
*ofs = 0;
|
|
_mkdir (tmpBuf);
|
|
*ofs = '/';
|
|
}
|
|
}
|
|
#endif // KMQUAKE2_ENGINE_MOD
|
|
}
|
|
// end Knightmare
|
|
|
|
/*
|
|
=============
|
|
visible
|
|
|
|
returns 1 if the entity is visible to self, even if not infront()
|
|
=============
|
|
*/
|
|
qboolean visible(edict_t *self, edict_t *other) //CW
|
|
{
|
|
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);
|
|
|
|
if (trace.fraction == 1.0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
=============
|
|
infront
|
|
|
|
returns 1 if the entity is in front (in sight) of self
|
|
=============
|
|
*/
|
|
qboolean infront(edict_t *self, edict_t *other) //CW
|
|
{
|
|
vec3_t vec;
|
|
vec3_t forward;
|
|
float dot;
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
//CW++
|
|
/*
|
|
============
|
|
VecRange
|
|
|
|
Returns the magnitude of the vector between two points.
|
|
============
|
|
*/
|
|
float VecRange(vec3_t start, vec3_t end)
|
|
{
|
|
vec3_t vec;
|
|
|
|
VectorSubtract(end, start, vec);
|
|
return VectorLength(vec);
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
TList_DelNode
|
|
|
|
Deletes a node from a player's linked list of C4/Trap entities
|
|
============
|
|
*/
|
|
void TList_DelNode(edict_t *ent)
|
|
{
|
|
edict_t *index;
|
|
edict_t *prev;
|
|
|
|
// Sanity checks.
|
|
|
|
if (!ent->owner)
|
|
{
|
|
gi.dprintf("BUG: Entity with no owner passed to TList_DelNode().\nPlease contact musashi@planetquake.com\n");
|
|
return;
|
|
}
|
|
|
|
if (!ent->owner->next_node)
|
|
{
|
|
gi.dprintf("BUG: TList_DelNode() called for an empty list.\nPlease contact musashi@planetquake.com\n");
|
|
return;
|
|
}
|
|
|
|
if ((ent->owner->next_node->die != C4_DieFromDamage) && (ent->owner->next_node->die != Trap_DieFromDamage))
|
|
{
|
|
gi.dprintf("BUG: Invalid pointer passed to TList_DelNode().\nPlease contact musashi@planetquake.com\n");
|
|
return;
|
|
}
|
|
|
|
// Find the node.
|
|
|
|
index = ent->owner->next_node;
|
|
prev = ent->owner->next_node;
|
|
while (ent != index)
|
|
{
|
|
if (!index)
|
|
return;
|
|
|
|
prev = index;
|
|
index = index->next_node;
|
|
}
|
|
|
|
// Remove the node from the list, and reroute the linking.
|
|
|
|
if (index == ent->owner->next_node) //first in chain
|
|
ent->owner->next_node = ent->owner->next_node->next_node;
|
|
else if (!index->next_node) //last in chain
|
|
prev->next_node = NULL;
|
|
else
|
|
{
|
|
prev->next_node = index->next_node;
|
|
index->next_node->prev_node = prev;
|
|
}
|
|
|
|
--ent->owner->client->resp.nodes_active;
|
|
}
|
|
|
|
/*
|
|
============
|
|
TList_AddNode
|
|
|
|
Adds a node to a player's linked list of C4/Trap entities
|
|
============
|
|
*/
|
|
void TList_AddNode(edict_t *ent)
|
|
{
|
|
edict_t *index;
|
|
|
|
// Sanity checks.
|
|
|
|
if (!ent->owner)
|
|
{
|
|
gi.dprintf("BUG: Entity with no owner passed to TList_AddNode().\nPlease contact musashi@planetquake.com\n");
|
|
return;
|
|
}
|
|
|
|
if (ent->owner->next_node && (ent->owner->next_node->die != C4_DieFromDamage) && (ent->owner->next_node->die != Trap_DieFromDamage))
|
|
{
|
|
gi.dprintf("BUG: Invalid pointer passed to TList_AddNode().\nPlease contact musashi@planetquake.com\n");
|
|
return;
|
|
}
|
|
|
|
// If the player currently has their maximum number of C4/Traps active, pop the oldest (first) one.
|
|
|
|
if (ent->owner->client->resp.nodes_active >= (int)sv_traps_max_active->value)
|
|
{
|
|
if (ent->owner->next_node->die == C4_DieFromDamage)
|
|
C4_Die(ent->owner->next_node);
|
|
else
|
|
Trap_Die(ent->owner->next_node);
|
|
}
|
|
|
|
// Add the new node to the end of the list.
|
|
|
|
if (ent->owner->next_node)
|
|
{
|
|
index = ent->owner->next_node;
|
|
while (index->next_node)
|
|
index = index->next_node;
|
|
|
|
index->next_node = ent;
|
|
}
|
|
else
|
|
ent->owner->next_node = ent;
|
|
|
|
++ent->owner->client->resp.nodes_active;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PrintFFAScores
|
|
|
|
Sort the player scores for DM/FFA games,
|
|
and print them to the server console.
|
|
=================
|
|
*/
|
|
void PrintFFAScores(void)
|
|
{
|
|
edict_t *cl_ent;
|
|
gclient_t *cl;
|
|
int sorted[MAX_CLIENTS];
|
|
int sortedscores[MAX_CLIENTS];
|
|
int score;
|
|
int total;
|
|
int i;
|
|
int j;
|
|
int k;
|
|
|
|
gi.dprintf("-------------------------\n");
|
|
gi.dprintf("Player Scores:\n\n");
|
|
|
|
// Sort the clients according to score.
|
|
|
|
total = 0;
|
|
for (i = 0; i < game.maxclients; i++)
|
|
{
|
|
cl_ent = g_edicts + 1 + i;
|
|
if (!cl_ent->inuse)
|
|
continue;
|
|
if (cl_ent->client->spectator)
|
|
continue;
|
|
|
|
score = game.clients[i].resp.score;
|
|
for (j = 0; j < total; j++)
|
|
{
|
|
if (score > sortedscores[j])
|
|
break;
|
|
}
|
|
|
|
for (k = total; k > j; k--)
|
|
{
|
|
sorted[k] = sorted[k-1];
|
|
sortedscores[k] = sortedscores[k-1];
|
|
}
|
|
|
|
sorted[j] = i;
|
|
sortedscores[j] = score;
|
|
total++;
|
|
}
|
|
|
|
// Write the sorted list to the server console.
|
|
|
|
for (i = 0; i < total; i++)
|
|
{
|
|
cl = &game.clients[sorted[i]];
|
|
gi.dprintf(" %-16.16s : %3d\n", cl->pers.netname, cl->resp.score);
|
|
}
|
|
|
|
// Write the spectator list to the server console.
|
|
|
|
gi.dprintf("-------------------------\n");
|
|
gi.dprintf("Spectators:\n\n");
|
|
for (i = 0; i < game.maxclients; i++)
|
|
{
|
|
cl_ent = g_edicts + 1 + i;
|
|
if (!cl_ent->inuse)
|
|
continue;
|
|
|
|
if (cl_ent->client->spectator)
|
|
gi.dprintf(" %-16.16s\n", cl_ent->client->pers.netname);
|
|
}
|
|
|
|
gi.dprintf("-------------------------\n");
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PrintTeamScores
|
|
|
|
Sort the player scores by team,
|
|
and print them to the server console.
|
|
=================
|
|
*/
|
|
void PrintTeamScores(void)
|
|
{
|
|
edict_t *cl_ent;
|
|
gclient_t *cl;
|
|
int sorted[2][MAX_CLIENTS];
|
|
int sortedscores[2][MAX_CLIENTS];
|
|
int score;
|
|
int total[2];
|
|
int totalscore[2];
|
|
int team;
|
|
int i;
|
|
int j;
|
|
int k;
|
|
|
|
gi.dprintf("-------------------------\n");
|
|
|
|
// Sort the clients according to team and score.
|
|
|
|
total[0] = total[1] = 0;
|
|
totalscore[0] = totalscore[1] = 0;
|
|
for (i = 0; i < game.maxclients; i++)
|
|
{
|
|
cl_ent = g_edicts + 1 + i;
|
|
if (!cl_ent->inuse)
|
|
continue;
|
|
if (cl_ent->client->spectator)
|
|
continue;
|
|
|
|
if (game.clients[i].resp.ctf_team == CTF_TEAM1)
|
|
team = 0;
|
|
else if (game.clients[i].resp.ctf_team == CTF_TEAM2)
|
|
team = 1;
|
|
else
|
|
continue;
|
|
|
|
score = game.clients[i].resp.score;
|
|
for (j = 0; j < total[team]; j++)
|
|
{
|
|
if (score > sortedscores[team][j])
|
|
break;
|
|
}
|
|
|
|
for (k = total[team]; k > j; k--)
|
|
{
|
|
sorted[team][k] = sorted[team][k-1];
|
|
sortedscores[team][k] = sortedscores[team][k-1];
|
|
}
|
|
|
|
sorted[team][j] = i;
|
|
sortedscores[team][j] = score;
|
|
totalscore[team] += score;
|
|
total[team]++;
|
|
}
|
|
|
|
// Write the sorted lists to the server console...
|
|
// Team 1's scores.
|
|
|
|
gi.dprintf("Red Team Scores : %4d\n\n", totalscore[0]);
|
|
for (i = 0; i < total[0]; i++)
|
|
{
|
|
cl = &game.clients[sorted[0][i]];
|
|
gi.dprintf(" %-16.16s : %4d\n", cl->pers.netname, cl->resp.score);
|
|
}
|
|
|
|
// Team 2's scores.
|
|
|
|
gi.dprintf("-------------------------\n");
|
|
gi.dprintf("Blue Team Scores : %4d\n\n", totalscore[1]);
|
|
for (i = 0; i < total[1]; i++)
|
|
{
|
|
cl = &game.clients[sorted[1][i]];
|
|
gi.dprintf(" %-16.16s : %4d\n", cl->pers.netname, cl->resp.score);
|
|
}
|
|
|
|
// Write the spectator list to the server console.
|
|
|
|
gi.dprintf("-------------------------\n");
|
|
gi.dprintf("Spectators:\n\n");
|
|
for (i = 0; i < game.maxclients; i++)
|
|
{
|
|
cl_ent = g_edicts + 1 + i;
|
|
if (!cl_ent->inuse)
|
|
continue;
|
|
|
|
if (cl_ent->client->spectator)
|
|
gi.dprintf(" %-16.16s\n", cl_ent->client->pers.netname);
|
|
}
|
|
|
|
gi.dprintf("-------------------------\n");
|
|
}
|
|
|
|
/*
|
|
============
|
|
dm
|
|
|
|
"Debug Message" - displays a single line of text in the server console
|
|
(used for development/debugging purposes).
|
|
============
|
|
*/
|
|
void dm(char *msg)
|
|
{
|
|
gi.dprintf("%s", msg);
|
|
}
|
|
|
|
/*
|
|
===============================
|
|
File opening functions
|
|
===============================
|
|
*/
|
|
FILE* OpenMaplistFile(qboolean report)
|
|
{
|
|
FILE *iostream;
|
|
cvar_t *game;
|
|
char filename[MAX_OSPATH];
|
|
|
|
if (strlen(sv_map_file->string) == 0)
|
|
return NULL;
|
|
|
|
game = gi.cvar("game", "", 0);
|
|
if (!*game->string)
|
|
Com_sprintf(filename, sizeof(filename), "%s/%s", GAMEVERSION, sv_map_file->string);
|
|
else
|
|
Com_sprintf(filename, sizeof(filename), "%s/%s", game->string, sv_map_file->string);
|
|
|
|
iostream = fopen(filename, "r");
|
|
if (report)
|
|
{
|
|
if (iostream != NULL)
|
|
gi.dprintf("\n>> Map list \"%s\" opened\n", filename);
|
|
else
|
|
gi.dprintf("\n** Failed to open \"%s\"\n", filename);
|
|
}
|
|
|
|
return iostream;
|
|
}
|
|
|
|
FILE* OpenConfiglistFile(qboolean report)
|
|
{
|
|
FILE *iostream;
|
|
cvar_t *game;
|
|
char filename[MAX_OSPATH];
|
|
|
|
if (strlen(sv_config_file->string) == 0)
|
|
return NULL;
|
|
|
|
game = gi.cvar("game", "", 0);
|
|
if (!*game->string)
|
|
Com_sprintf(filename, sizeof(filename), "%s/%s", GAMEVERSION, sv_config_file->string);
|
|
else
|
|
Com_sprintf(filename, sizeof(filename), "%s/%s", game->string, sv_config_file->string);
|
|
|
|
iostream = fopen(filename, "r");
|
|
if (report)
|
|
{
|
|
if (iostream != NULL)
|
|
gi.dprintf("\n>> Config list \"%s\" opened\n", filename);
|
|
else
|
|
gi.dprintf("\n** Failed to open \"%s\"\n", filename);
|
|
}
|
|
|
|
return iostream;
|
|
}
|
|
|
|
FILE* OpenAGMDropFile(qboolean report, qboolean readonly)
|
|
{
|
|
FILE *iostream;
|
|
cvar_t *game;
|
|
char filename[MAX_OSPATH];
|
|
|
|
if (strlen(sv_agm_drop_file->string) == 0)
|
|
return NULL;
|
|
|
|
game = gi.cvar("game", "", 0);
|
|
if (!*game->string)
|
|
Com_sprintf(filename, sizeof(filename), "%s/%s", GAMEVERSION, sv_agm_drop_file->string);
|
|
else
|
|
Com_sprintf(filename, sizeof(filename), "%s/%s", game->string, sv_agm_drop_file->string);
|
|
|
|
if (readonly)
|
|
iostream = fopen(filename, "r");
|
|
else
|
|
iostream = fopen(filename, "a+");
|
|
|
|
if (report)
|
|
{
|
|
if (iostream != NULL)
|
|
gi.dprintf("\n>> AGM drop file \"%s\" opened\n", filename);
|
|
else
|
|
gi.dprintf("\n** Failed to open \"%s\"\n", filename);
|
|
}
|
|
|
|
return iostream;
|
|
}
|
|
|
|
FILE* OpenDiscLauncherDropFile(qboolean report, qboolean readonly)
|
|
{
|
|
FILE *iostream;
|
|
cvar_t *game;
|
|
char filename[MAX_OSPATH];
|
|
|
|
if (strlen(sv_disc_drop_file->string) == 0)
|
|
return NULL;
|
|
|
|
game = gi.cvar("game", "", 0);
|
|
if (!*game->string)
|
|
Com_sprintf(filename, sizeof(filename), "%s/%s", GAMEVERSION, sv_disc_drop_file->string);
|
|
else
|
|
Com_sprintf(filename, sizeof(filename), "%s/%s", game->string, sv_disc_drop_file->string);
|
|
|
|
if (readonly)
|
|
iostream = fopen(filename, "r");
|
|
else
|
|
iostream = fopen(filename, "a+");
|
|
|
|
if (report)
|
|
{
|
|
if (iostream != NULL)
|
|
gi.dprintf("\n>> Disc Launcher drop file \"%s\" opened\n", filename);
|
|
else
|
|
gi.dprintf("\n** Failed to open \"%s\"\n", filename);
|
|
}
|
|
|
|
return iostream;
|
|
}
|
|
|
|
FILE* OpenBotConfigFile(qboolean report, qboolean readonly)
|
|
{
|
|
FILE *iostream;
|
|
cvar_t *game;
|
|
char filename[MAX_OSPATH];
|
|
|
|
if (strlen(sv_bots_config_file->string) == 0)
|
|
return NULL;
|
|
|
|
game = gi.cvar("game", "", 0);
|
|
if (!*game->string)
|
|
Com_sprintf(filename, sizeof(filename), "%s/%s", GAMEVERSION, sv_bots_config_file->string);
|
|
else
|
|
Com_sprintf(filename, sizeof(filename), "%s/%s", game->string, sv_bots_config_file->string);
|
|
|
|
if (readonly)
|
|
iostream = fopen(filename, "r");
|
|
else
|
|
iostream = fopen(filename, "a+");
|
|
|
|
if (report)
|
|
{
|
|
if (iostream != NULL)
|
|
gi.dprintf("\n>> Bot config file \"%s\" opened\n", filename);
|
|
else
|
|
gi.dprintf("\n** Failed to open \"%s\"\n", filename);
|
|
}
|
|
|
|
return iostream;
|
|
}
|
|
//CW--
|
|
|
|
//DH++
|
|
//CW: These functions were written by David Hyde for the Lazarus mod.
|
|
|
|
edict_t *LookingAt(edict_t *ent, int filter, vec3_t endpos, float *range)
|
|
{
|
|
edict_t *who;
|
|
static edict_t *trigger[MAX_EDICTS]; // Knightmare- made static due to stack size
|
|
edict_t *ignore;
|
|
trace_t tr;
|
|
vec_t r;
|
|
vec3_t end;
|
|
vec3_t forward;
|
|
vec3_t start;
|
|
vec3_t dir;
|
|
vec3_t entp;
|
|
vec3_t mins;
|
|
vec3_t maxs;
|
|
int i;
|
|
int 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, WORLD_SIZE, forward, end); // was 8192.0
|
|
|
|
// First check for looking directly at a pickup item
|
|
VectorSet(mins, -4096.0, -4096.0, -4096.0);
|
|
VectorSet(maxs, 4096.0, 4096.0, 4096.0);
|
|
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.0)
|
|
continue;
|
|
if (entp[1] < who->s.origin[1] - 17.0)
|
|
continue;
|
|
if (entp[2] < who->s.origin[2] - 17.0)
|
|
continue;
|
|
if (entp[0] > who->s.origin[0] + 17.0)
|
|
continue;
|
|
if (entp[1] > who->s.origin[1] + 17.0)
|
|
continue;
|
|
if (entp[2] > who->s.origin[2] + 17.0)
|
|
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 ((Q_stricmp(tr.ent->classname, "worldspawn") == 0) && (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 AnglesNormalize(vec3_t vec)
|
|
{
|
|
while(vec[0] > 180.0)
|
|
vec[0] -= 360.0;
|
|
|
|
while(vec[0] < -180.0)
|
|
vec[0] += 360.0;
|
|
|
|
while(vec[1] > 360.0)
|
|
vec[1] -= 360.0;
|
|
|
|
while(vec[1] < 0.0)
|
|
vec[1] += 360.0;
|
|
}
|
|
|
|
float SnapToEights(float x)
|
|
{
|
|
x *= 8.0;
|
|
if (x > 0.0)
|
|
x += 0.5;
|
|
else
|
|
x -= 0.5;
|
|
|
|
return 0.125 * (int)x;
|
|
}
|
|
|
|
/*
|
|
===============================
|
|
Checks that the specified file is inside a PAK.
|
|
===============================
|
|
*/
|
|
qboolean InPak(char *basedir, char *gamedir, char *filename)
|
|
{
|
|
char pakfile[MAX_OSPATH];
|
|
FILE *f;
|
|
pak_header_t pakheader;
|
|
pak_item_t pakitem;
|
|
qboolean found = false;
|
|
int k;
|
|
int kk;
|
|
int num;
|
|
int numitems;
|
|
|
|
// Search paks in the game folder.
|
|
|
|
for (k = 9; (k >= 0) && !found; k--)
|
|
{
|
|
// strcpy(pakfile, basedir);
|
|
Com_strcpy(pakfile, sizeof(pakfile), basedir);
|
|
if (strlen(gamedir))
|
|
{
|
|
Com_strcat(pakfile, sizeof(pakfile), "/");
|
|
Com_strcat(pakfile, sizeof(pakfile), gamedir);
|
|
}
|
|
Com_strcat(pakfile, sizeof(pakfile), va("/pak%d.pak", k));
|
|
|
|
if (NULL != (f = fopen(pakfile, "rb")))
|
|
{
|
|
num = (int)fread(&pakheader, 1, sizeof(pak_header_t), f);
|
|
if (num >= sizeof(pak_header_t))
|
|
{
|
|
if ((pakheader.id[0] == 'P') && (pakheader.id[1] == 'A') && (pakheader.id[2] == 'C') && (pakheader.id[3] == 'K'))
|
|
{
|
|
numitems = pakheader.dsize / sizeof(pak_item_t);
|
|
fseek(f, pakheader.dstart, SEEK_SET);
|
|
for (kk = 0; (kk < numitems) && !found; kk++)
|
|
{
|
|
fread(&pakitem, 1, sizeof(pak_item_t), f);
|
|
if (!Q_stricmp(pakitem.name, filename))
|
|
found = true;
|
|
}
|
|
}
|
|
}
|
|
fclose(f);
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
//DH--
|
|
|
|
//CW++
|
|
/*
|
|
===============================
|
|
Checks that the specified file exists.
|
|
NB: This function assumes that the file specified as 'checkname' has no extension.
|
|
|
|
This is based on code written by David Hyde for the Lazarus mod.
|
|
===============================
|
|
*/
|
|
qboolean FileExists(char *checkname, filetype_t ftype)
|
|
{
|
|
FILE *fstream;
|
|
cvar_t *basedir;
|
|
cvar_t *gamedir;
|
|
char filename[MAX_OSPATH];
|
|
char path[MAX_OSPATH];
|
|
|
|
basedir = gi.cvar("basedir", "", 0);
|
|
gamedir = gi.cvar("gamedir", "", 0);
|
|
|
|
switch (ftype)
|
|
{
|
|
case FILE_MAP:
|
|
Com_sprintf(path, sizeof(path), "maps/%s.bsp", checkname);
|
|
break;
|
|
|
|
case FILE_MODEL:
|
|
Com_sprintf(path, sizeof(path), "models/%s.md2", checkname);
|
|
break;
|
|
|
|
case FILE_SOUND:
|
|
Com_sprintf(path, sizeof(path), "sound/%s.wav", checkname);
|
|
break;
|
|
|
|
case FILE_TEXTURE:
|
|
Com_sprintf(path, sizeof(path), "textures/%s.wal", checkname);
|
|
break;
|
|
|
|
default:
|
|
Com_sprintf(path, sizeof(path), "%s", checkname);
|
|
break;
|
|
}
|
|
|
|
if (strlen(gamedir->string))
|
|
{
|
|
|
|
// Search in the game directory for external file.
|
|
|
|
Com_sprintf(filename, sizeof(filename), "%s/%s/%s", basedir->string, gamedir->string, path);
|
|
if ((fstream = fopen(filename, "r")) != NULL)
|
|
{
|
|
fclose(fstream);
|
|
return true;
|
|
}
|
|
|
|
// Search paks in the game directory.
|
|
|
|
if (InPak(basedir->string, gamedir->string, path))
|
|
return true;
|
|
}
|
|
|
|
// Search in the 'baseq2' directory for external file.
|
|
|
|
Com_sprintf(filename, sizeof(filename), "%s/baseq2/%s", basedir->string, path);
|
|
if ((fstream = fopen(filename, "r")) != NULL)
|
|
{
|
|
fclose(fstream);
|
|
return true;
|
|
}
|
|
|
|
// Search paks in the 'baseq2' directory.
|
|
|
|
if (InPak(basedir->string, "baseq2", path))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
//CW--
|
|
|
|
//Maj++
|
|
void gi_cprintf(edict_t *ent, int printlevel, char *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
char bigbuffer[0x10000];
|
|
int len;
|
|
|
|
if (!ent || !ent->inuse || !ent->client)
|
|
return;
|
|
|
|
// bots don't get messages
|
|
if (ent->isabot)
|
|
return;
|
|
|
|
va_start(argptr, fmt);
|
|
// len = vsprintf(bigbuffer, fmt, argptr);
|
|
len = Q_vsnprintf(bigbuffer, sizeof(bigbuffer), fmt, argptr);
|
|
|
|
//CW++ //r1
|
|
// Check for overflow.
|
|
|
|
if (len > sizeof(bigbuffer))
|
|
{
|
|
gi.dprintf("String too long for gi_bprintf().\n");
|
|
return;
|
|
}
|
|
//CW--
|
|
|
|
va_end(argptr);
|
|
|
|
gi.cprintf(ent, printlevel, "%s", bigbuffer); //r1: format string exploit fix
|
|
}
|
|
|
|
|
|
void gi_centerprintf(edict_t *ent, char *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
char bigbuffer[0x10000];
|
|
int len;
|
|
|
|
if (!ent || !ent->inuse || !ent->client)
|
|
return;
|
|
|
|
// bots don't get messages
|
|
if (ent->isabot)
|
|
return;
|
|
|
|
va_start(argptr, fmt);
|
|
// len = vsprintf(bigbuffer, fmt, argptr);
|
|
len = Q_vsnprintf(bigbuffer, sizeof(bigbuffer), fmt, argptr);
|
|
|
|
//CW++ //r1
|
|
// Check for overflow.
|
|
|
|
if (len > sizeof(bigbuffer))
|
|
{
|
|
gi.dprintf("String too long for gi_bprintf().\n");
|
|
return;
|
|
}
|
|
//CW--
|
|
|
|
va_end(argptr);
|
|
|
|
gi.centerprintf(ent, "%s", bigbuffer); //r1: format string exploit fix
|
|
}
|
|
|
|
|
|
void gi_bprintf(int printlevel, char *fmt, ...)
|
|
{
|
|
edict_t *ent;
|
|
va_list argptr;
|
|
char bigbuffer[0x10000];
|
|
int len;
|
|
int i;
|
|
|
|
va_start(argptr, fmt);
|
|
// len = vsprintf(bigbuffer, fmt, argptr);
|
|
len = Q_vsnprintf(bigbuffer, sizeof(bigbuffer), fmt, argptr);
|
|
|
|
//CW++ //r1
|
|
// Check for overflow.
|
|
|
|
if (len > sizeof(bigbuffer))
|
|
{
|
|
gi.dprintf("String too long for gi_bprintf().\n");
|
|
return;
|
|
}
|
|
//CW--
|
|
|
|
va_end(argptr);
|
|
|
|
if (dedicated->value)
|
|
gi.cprintf(NULL, printlevel, "%s", bigbuffer); //r1: format string exploit fix
|
|
|
|
for (i = 0; i < game.maxclients; i++)
|
|
{
|
|
ent = g_edicts + 1 + i;
|
|
gi_cprintf(ent, printlevel, "%s", bigbuffer); //r1: format string exploit fix
|
|
}
|
|
}
|
|
//Maj--
|
|
|
|
|
|
//r1++
|
|
#define MASK_VOLUME 1
|
|
#define MASK_ATTENUATION 2
|
|
#define MASK_POSITION 4
|
|
#define MASK_ENTITY_CHANNEL 8
|
|
#define MASK_TIMEOFS 16
|
|
|
|
void unicastSound (edict_t *player, int soundIndex, float volume)
|
|
{
|
|
int mask = MASK_ENTITY_CHANNEL;
|
|
|
|
// Knightmare- don't send this to bots- causes fatal server error
|
|
if (!player || player->isabot)
|
|
return;
|
|
|
|
if (volume != 1.0)
|
|
mask |= MASK_VOLUME;
|
|
|
|
gi.WriteByte(svc_sound);
|
|
gi.WriteByte((byte)mask);
|
|
gi.WriteByte((byte)soundIndex);
|
|
|
|
if (mask & MASK_VOLUME)
|
|
gi.WriteByte((byte)(volume * 255));
|
|
|
|
gi.WriteShort(((player - g_edicts - 1) << 3) + CHAN_NO_PHS_ADD);
|
|
gi.unicast(player, true);
|
|
}
|
|
//r1--
|