2001-12-31 16:16:59 +00:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
// $Id$
|
|
|
|
//
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//
|
2001-12-31 16:28:42 +00:00
|
|
|
// $Log$
|
2002-07-09 03:31:25 +00:00
|
|
|
// Revision 1.14 2002/07/09 03:31:25 niceass
|
|
|
|
// oops
|
|
|
|
//
|
2002-07-09 03:23:14 +00:00
|
|
|
// Revision 1.13 2002/07/09 03:23:14 niceass
|
|
|
|
// added a alive function
|
|
|
|
//
|
2002-06-16 20:06:15 +00:00
|
|
|
// Revision 1.12 2002/06/16 20:06:14 jbravo
|
|
|
|
// Reindented all the source files with "indent -kr -ut -i8 -l120 -lc120 -sob -bad -bap"
|
|
|
|
//
|
2002-06-06 18:08:01 +00:00
|
|
|
// Revision 1.11 2002/06/06 18:08:01 makro
|
|
|
|
// Removed pathtarget code for trigger_pushes for now
|
|
|
|
//
|
2002-05-23 15:55:25 +00:00
|
|
|
// Revision 1.10 2002/05/23 15:55:25 makro
|
|
|
|
// Elevators
|
|
|
|
//
|
2002-05-20 16:25:48 +00:00
|
|
|
// Revision 1.9 2002/05/20 16:25:48 makro
|
|
|
|
// Triggerable cameras
|
|
|
|
//
|
2002-01-11 20:20:58 +00:00
|
|
|
// Revision 1.8 2002/01/11 20:20:58 jbravo
|
|
|
|
// Adding TP to main branch
|
|
|
|
//
|
2002-01-11 19:48:33 +00:00
|
|
|
// Revision 1.7 2002/01/11 19:48:30 jbravo
|
|
|
|
// Formatted the source in non DOS format.
|
|
|
|
//
|
2001-12-31 16:28:42 +00:00
|
|
|
// Revision 1.6 2001/12/31 16:28:42 jbravo
|
|
|
|
// I made a Booboo with the Log tag.
|
|
|
|
//
|
2001-12-31 16:16:59 +00:00
|
|
|
//
|
|
|
|
//-----------------------------------------------------------------------------
|
2001-05-06 20:50:27 +00:00
|
|
|
// Copyright (C) 1999-2000 Id Software, Inc.
|
|
|
|
//
|
|
|
|
// g_utils.c -- misc utility functions for game module
|
|
|
|
|
|
|
|
#include "g_local.h"
|
|
|
|
|
|
|
|
typedef struct {
|
2002-06-16 20:06:15 +00:00
|
|
|
char oldShader[MAX_QPATH];
|
|
|
|
char newShader[MAX_QPATH];
|
|
|
|
float timeOffset;
|
2001-05-06 20:50:27 +00:00
|
|
|
} shaderRemap_t;
|
|
|
|
|
|
|
|
#define MAX_SHADER_REMAPS 128
|
|
|
|
|
|
|
|
int remapCount = 0;
|
|
|
|
shaderRemap_t remappedShaders[MAX_SHADER_REMAPS];
|
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
void AddRemap(const char *oldShader, const char *newShader, float timeOffset)
|
|
|
|
{
|
2001-05-06 20:50:27 +00:00
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < remapCount; i++) {
|
|
|
|
if (Q_stricmp(oldShader, remappedShaders[i].oldShader) == 0) {
|
|
|
|
// found it, just update this one
|
2002-06-16 20:06:15 +00:00
|
|
|
strcpy(remappedShaders[i].newShader, newShader);
|
2001-05-06 20:50:27 +00:00
|
|
|
remappedShaders[i].timeOffset = timeOffset;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (remapCount < MAX_SHADER_REMAPS) {
|
2002-06-16 20:06:15 +00:00
|
|
|
strcpy(remappedShaders[remapCount].newShader, newShader);
|
|
|
|
strcpy(remappedShaders[remapCount].oldShader, oldShader);
|
2001-05-06 20:50:27 +00:00
|
|
|
remappedShaders[remapCount].timeOffset = timeOffset;
|
|
|
|
remapCount++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
const char *BuildShaderStateConfig()
|
|
|
|
{
|
|
|
|
static char buff[MAX_STRING_CHARS * 4];
|
2001-05-06 20:50:27 +00:00
|
|
|
char out[(MAX_QPATH * 2) + 5];
|
|
|
|
int i;
|
2002-06-16 20:06:15 +00:00
|
|
|
|
2001-05-06 20:50:27 +00:00
|
|
|
memset(buff, 0, MAX_STRING_CHARS);
|
|
|
|
for (i = 0; i < remapCount; i++) {
|
2002-06-16 20:06:15 +00:00
|
|
|
Com_sprintf(out, (MAX_QPATH * 2) + 5, "%s=%s:%5.2f@", remappedShaders[i].oldShader,
|
|
|
|
remappedShaders[i].newShader, remappedShaders[i].timeOffset);
|
|
|
|
Q_strcat(buff, sizeof(buff), out);
|
2001-05-06 20:50:27 +00:00
|
|
|
}
|
|
|
|
return buff;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=========================================================================
|
|
|
|
|
|
|
|
model / sound configstring indexes
|
|
|
|
|
|
|
|
=========================================================================
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
G_FindConfigstringIndex
|
|
|
|
|
|
|
|
================
|
|
|
|
*/
|
2002-06-16 20:06:15 +00:00
|
|
|
int G_FindConfigstringIndex(char *name, int start, int max, qboolean create)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
char s[MAX_STRING_CHARS];
|
2001-05-06 20:50:27 +00:00
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
if (!name || !name[0]) {
|
2001-05-06 20:50:27 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
for (i = 1; i < max; i++) {
|
|
|
|
trap_GetConfigstring(start + i, s, sizeof(s));
|
|
|
|
if (!s[0]) {
|
2001-05-06 20:50:27 +00:00
|
|
|
break;
|
|
|
|
}
|
2002-06-16 20:06:15 +00:00
|
|
|
if (!strcmp(s, name)) {
|
2001-05-06 20:50:27 +00:00
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
if (!create) {
|
2001-05-06 20:50:27 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
if (i == max) {
|
|
|
|
G_Error("G_FindConfigstringIndex: overflow");
|
2001-05-06 20:50:27 +00:00
|
|
|
}
|
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
trap_SetConfigstring(start + i, name);
|
2001-05-06 20:50:27 +00:00
|
|
|
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
int G_ModelIndex(char *name)
|
|
|
|
{
|
|
|
|
return G_FindConfigstringIndex(name, CS_MODELS, MAX_MODELS, qtrue);
|
2001-05-06 20:50:27 +00:00
|
|
|
}
|
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
int G_SoundIndex(char *name)
|
|
|
|
{
|
|
|
|
return G_FindConfigstringIndex(name, CS_SOUNDS, MAX_SOUNDS, qtrue);
|
2001-05-06 20:50:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//=====================================================================
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
G_TeamCommand
|
|
|
|
|
|
|
|
Broadcasts a command to only a specific team
|
|
|
|
================
|
|
|
|
*/
|
2002-06-16 20:06:15 +00:00
|
|
|
void G_TeamCommand(team_t team, char *cmd)
|
|
|
|
{
|
|
|
|
int i;
|
2001-05-06 20:50:27 +00:00
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
for (i = 0; i < level.maxclients; i++) {
|
|
|
|
if (level.clients[i].pers.connected == CON_CONNECTED) {
|
|
|
|
if (level.clients[i].sess.sessionTeam == team) {
|
|
|
|
trap_SendServerCommand(i, va("%s", cmd));
|
2001-05-06 20:50:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=============
|
|
|
|
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 entity after from, or the beginning if NULL
|
|
|
|
NULL will be returned if the end of the list is reached.
|
|
|
|
|
|
|
|
=============
|
|
|
|
*/
|
2002-06-16 20:06:15 +00:00
|
|
|
gentity_t *G_Find(gentity_t * from, int fieldofs, const char *match)
|
2001-05-06 20:50:27 +00:00
|
|
|
{
|
2002-06-16 20:06:15 +00:00
|
|
|
char *s;
|
2001-05-06 20:50:27 +00:00
|
|
|
|
|
|
|
if (!from)
|
|
|
|
from = g_entities;
|
|
|
|
else
|
|
|
|
from++;
|
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
for (; from < &g_entities[level.num_entities]; from++) {
|
2001-05-06 20:50:27 +00:00
|
|
|
if (!from->inuse)
|
|
|
|
continue;
|
2002-06-16 20:06:15 +00:00
|
|
|
s = *(char **) ((byte *) from + fieldofs);
|
2001-05-06 20:50:27 +00:00
|
|
|
if (!s)
|
|
|
|
continue;
|
2002-06-16 20:06:15 +00:00
|
|
|
if (!Q_stricmp(s, match))
|
2001-05-06 20:50:27 +00:00
|
|
|
return from;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2002-05-23 15:55:25 +00:00
|
|
|
//Makro - returns the first entity that matches both fieldofs
|
2002-06-16 20:06:15 +00:00
|
|
|
gentity_t *G_Find2(gentity_t * from, int fieldofs, const char *match, int fieldofs2, const char *match2)
|
|
|
|
{
|
|
|
|
gentity_t *ent;
|
2002-05-23 15:55:25 +00:00
|
|
|
|
|
|
|
for (ent = G_Find(from, fieldofs, match); ent; ent = G_Find(ent, fieldofs, match)) {
|
2002-06-16 20:06:15 +00:00
|
|
|
char *s = *(char **) ((byte *) ent + fieldofs2);
|
|
|
|
|
2002-06-06 18:08:01 +00:00
|
|
|
if (!s)
|
|
|
|
continue;
|
2002-05-23 15:55:25 +00:00
|
|
|
if (!Q_stricmp(s, match2))
|
|
|
|
return ent;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
2001-05-06 20:50:27 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
=============
|
|
|
|
G_PickTarget
|
|
|
|
|
|
|
|
Selects a random entity from among the targets
|
|
|
|
=============
|
|
|
|
*/
|
|
|
|
#define MAXCHOICES 32
|
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
gentity_t *G_PickTarget(char *targetname)
|
2001-05-06 20:50:27 +00:00
|
|
|
{
|
2002-06-16 20:06:15 +00:00
|
|
|
gentity_t *ent = NULL;
|
|
|
|
int num_choices = 0;
|
|
|
|
gentity_t *choice[MAXCHOICES];
|
2001-05-06 20:50:27 +00:00
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
if (!targetname) {
|
2001-05-06 20:50:27 +00:00
|
|
|
G_Printf("G_PickTarget called with NULL targetname\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
while (1) {
|
|
|
|
ent = G_Find(ent, FOFS(targetname), targetname);
|
2001-05-06 20:50:27 +00:00
|
|
|
if (!ent)
|
|
|
|
break;
|
|
|
|
choice[num_choices++] = ent;
|
|
|
|
if (num_choices == MAXCHOICES)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
if (!num_choices) {
|
2001-05-06 20:50:27 +00:00
|
|
|
G_Printf("G_PickTarget: target %s not found\n", targetname);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return choice[rand() % num_choices];
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==============================
|
|
|
|
G_UseTargets
|
|
|
|
|
|
|
|
"activator" should be set to the entity that initiated the firing.
|
|
|
|
|
|
|
|
Search for (string)targetname in all entities that
|
|
|
|
match (string)self.target and call their .use function
|
|
|
|
|
|
|
|
==============================
|
|
|
|
*/
|
2002-06-16 20:06:15 +00:00
|
|
|
void G_UseTargets(gentity_t * ent, gentity_t * activator)
|
|
|
|
{
|
|
|
|
gentity_t *t;
|
|
|
|
|
|
|
|
if (!ent) {
|
2001-05-06 20:50:27 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ent->targetShaderName && ent->targetShaderNewName) {
|
|
|
|
float f = level.time * 0.001;
|
2002-06-16 20:06:15 +00:00
|
|
|
|
2001-05-06 20:50:27 +00:00
|
|
|
AddRemap(ent->targetShaderName, ent->targetShaderNewName, f);
|
|
|
|
trap_SetConfigstring(CS_SHADERSTATE, BuildShaderStateConfig());
|
|
|
|
}
|
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
if (!ent->target) {
|
2001-05-06 20:50:27 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
t = NULL;
|
2002-06-16 20:06:15 +00:00
|
|
|
while ((t = G_Find(t, FOFS(targetname), ent->target)) != NULL) {
|
|
|
|
if (t == ent) {
|
|
|
|
G_Printf("WARNING: Entity used itself.\n");
|
2001-05-06 20:50:27 +00:00
|
|
|
} else {
|
2002-06-16 20:06:15 +00:00
|
|
|
if (t->use) {
|
|
|
|
t->use(t, ent, activator);
|
2001-05-06 20:50:27 +00:00
|
|
|
}
|
|
|
|
}
|
2002-06-16 20:06:15 +00:00
|
|
|
if (!ent->inuse) {
|
2001-05-06 20:50:27 +00:00
|
|
|
G_Printf("entity was removed while using targets\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=============
|
|
|
|
TempVector
|
|
|
|
|
|
|
|
This is just a convenience function
|
|
|
|
for making temporary vectors for function calls
|
|
|
|
=============
|
|
|
|
*/
|
2002-06-16 20:06:15 +00:00
|
|
|
float *tv(float x, float y, float z)
|
|
|
|
{
|
|
|
|
static int index;
|
|
|
|
static vec3_t vecs[8];
|
|
|
|
float *v;
|
2001-05-06 20:50:27 +00:00
|
|
|
|
|
|
|
// use an array so that multiple tempvectors won't collide
|
|
|
|
// for a while
|
|
|
|
v = vecs[index];
|
2002-06-16 20:06:15 +00:00
|
|
|
index = (index + 1) & 7;
|
2001-05-06 20:50:27 +00:00
|
|
|
|
|
|
|
v[0] = x;
|
|
|
|
v[1] = y;
|
|
|
|
v[2] = z;
|
|
|
|
|
|
|
|
return v;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=============
|
|
|
|
VectorToString
|
|
|
|
|
|
|
|
This is just a convenience function
|
|
|
|
for printing vectors
|
|
|
|
=============
|
|
|
|
*/
|
2002-06-16 20:06:15 +00:00
|
|
|
char *vtos(const vec3_t v)
|
|
|
|
{
|
|
|
|
static int index;
|
|
|
|
static char str[8][32];
|
|
|
|
char *s;
|
2001-05-06 20:50:27 +00:00
|
|
|
|
|
|
|
// use an array so that multiple vtos won't collide
|
|
|
|
s = str[index];
|
2002-06-16 20:06:15 +00:00
|
|
|
index = (index + 1) & 7;
|
2001-05-06 20:50:27 +00:00
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
Com_sprintf(s, 32, "(%i %i %i)", (int) v[0], (int) v[1], (int) v[2]);
|
2001-05-06 20:50:27 +00:00
|
|
|
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
G_SetMovedir
|
|
|
|
|
|
|
|
The editor only specifies a single value for angles (yaw),
|
|
|
|
but we have special constants to generate an up or down direction.
|
|
|
|
Angles will be cleared, because it is being used to represent a direction
|
|
|
|
instead of an orientation.
|
|
|
|
===============
|
|
|
|
*/
|
2002-06-16 20:06:15 +00:00
|
|
|
void G_SetMovedir(vec3_t angles, vec3_t movedir)
|
|
|
|
{
|
|
|
|
static vec3_t VEC_UP = { 0, -1, 0 };
|
|
|
|
static vec3_t MOVEDIR_UP = { 0, 0, 1 };
|
|
|
|
static vec3_t VEC_DOWN = { 0, -2, 0 };
|
|
|
|
static vec3_t MOVEDIR_DOWN = { 0, 0, -1 };
|
|
|
|
|
|
|
|
if (VectorCompare(angles, VEC_UP)) {
|
|
|
|
VectorCopy(MOVEDIR_UP, movedir);
|
|
|
|
} else if (VectorCompare(angles, VEC_DOWN)) {
|
|
|
|
VectorCopy(MOVEDIR_DOWN, movedir);
|
2001-05-06 20:50:27 +00:00
|
|
|
} else {
|
2002-06-16 20:06:15 +00:00
|
|
|
AngleVectors(angles, movedir, NULL, NULL);
|
2001-05-06 20:50:27 +00:00
|
|
|
}
|
2002-06-16 20:06:15 +00:00
|
|
|
VectorClear(angles);
|
2001-05-06 20:50:27 +00:00
|
|
|
}
|
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
float vectoyaw(const vec3_t vec)
|
|
|
|
{
|
|
|
|
float yaw;
|
2001-05-06 20:50:27 +00:00
|
|
|
|
|
|
|
if (vec[YAW] == 0 && vec[PITCH] == 0) {
|
|
|
|
yaw = 0;
|
|
|
|
} else {
|
|
|
|
if (vec[PITCH]) {
|
2002-06-16 20:06:15 +00:00
|
|
|
yaw = (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI);
|
2001-05-06 20:50:27 +00:00
|
|
|
} else if (vec[YAW] > 0) {
|
|
|
|
yaw = 90;
|
|
|
|
} else {
|
|
|
|
yaw = 270;
|
|
|
|
}
|
|
|
|
if (yaw < 0) {
|
|
|
|
yaw += 360;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return yaw;
|
|
|
|
}
|
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
void G_InitGentity(gentity_t * e)
|
|
|
|
{
|
2001-05-06 20:50:27 +00:00
|
|
|
e->inuse = qtrue;
|
|
|
|
e->classname = "noclass";
|
|
|
|
e->s.number = e - g_entities;
|
|
|
|
e->r.ownerNum = ENTITYNUM_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
G_Spawn
|
|
|
|
|
|
|
|
Either finds a free entity, or allocates a new one.
|
|
|
|
|
|
|
|
The slots from 0 to MAX_CLIENTS-1 are always reserved for clients, and will
|
|
|
|
never be used by anything else.
|
|
|
|
|
|
|
|
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.
|
|
|
|
=================
|
|
|
|
*/
|
2002-06-16 20:06:15 +00:00
|
|
|
gentity_t *G_Spawn(void)
|
|
|
|
{
|
|
|
|
int i, force;
|
|
|
|
gentity_t *e;
|
2001-05-06 20:50:27 +00:00
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
e = NULL; // shut up warning
|
|
|
|
i = 0; // shut up warning
|
|
|
|
for (force = 0; force < 2; force++) {
|
2001-05-06 20:50:27 +00:00
|
|
|
// if we go through all entities and can't find one to free,
|
|
|
|
// override the normal minimum times before use
|
|
|
|
e = &g_entities[MAX_CLIENTS];
|
2002-06-16 20:06:15 +00:00
|
|
|
for (i = MAX_CLIENTS; i < level.num_entities; i++, e++) {
|
|
|
|
if (e->inuse) {
|
2001-05-06 20:50:27 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// the first couple seconds of server time can involve a lot of
|
|
|
|
// freeing and allocating, so relax the replacement policy
|
2002-06-16 20:06:15 +00:00
|
|
|
if (!force && e->freetime > level.startTime + 2000 && level.time - e->freetime < 1000) {
|
2001-05-06 20:50:27 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// reuse this slot
|
2002-06-16 20:06:15 +00:00
|
|
|
G_InitGentity(e);
|
2001-05-06 20:50:27 +00:00
|
|
|
return e;
|
|
|
|
}
|
2002-06-16 20:06:15 +00:00
|
|
|
if (i != MAX_GENTITIES) {
|
2001-05-06 20:50:27 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2002-06-16 20:06:15 +00:00
|
|
|
if (i == ENTITYNUM_MAX_NORMAL) {
|
2001-05-06 20:50:27 +00:00
|
|
|
for (i = 0; i < MAX_GENTITIES; i++) {
|
|
|
|
G_Printf("%4i: %s\n", i, g_entities[i].classname);
|
|
|
|
}
|
2002-06-16 20:06:15 +00:00
|
|
|
G_Error("G_Spawn: no free entities");
|
2001-05-06 20:50:27 +00:00
|
|
|
}
|
|
|
|
// open up a new slot
|
|
|
|
level.num_entities++;
|
|
|
|
|
|
|
|
// let the server system know that there are more entities
|
2002-06-16 20:06:15 +00:00
|
|
|
trap_LocateGameData(level.gentities, level.num_entities, sizeof(gentity_t),
|
|
|
|
&level.clients[0].ps, sizeof(level.clients[0]));
|
2001-05-06 20:50:27 +00:00
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
G_InitGentity(e);
|
2001-05-06 20:50:27 +00:00
|
|
|
return e;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
G_EntitiesFree
|
|
|
|
=================
|
|
|
|
*/
|
2002-06-16 20:06:15 +00:00
|
|
|
qboolean G_EntitiesFree(void)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
gentity_t *e;
|
2001-05-06 20:50:27 +00:00
|
|
|
|
|
|
|
e = &g_entities[MAX_CLIENTS];
|
2002-06-16 20:06:15 +00:00
|
|
|
for (i = MAX_CLIENTS; i < level.num_entities; i++, e++) {
|
|
|
|
if (e->inuse) {
|
2001-05-06 20:50:27 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// slot available
|
|
|
|
return qtrue;
|
|
|
|
}
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
G_FreeEntity
|
|
|
|
|
|
|
|
Marks the entity as free
|
|
|
|
=================
|
|
|
|
*/
|
2002-06-16 20:06:15 +00:00
|
|
|
void G_FreeEntity(gentity_t * ed)
|
|
|
|
{
|
2001-05-06 20:50:27 +00:00
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
trap_UnlinkEntity(ed); // unlink from world
|
2001-05-06 20:50:27 +00:00
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
if (ed->neverFree) {
|
2001-05-06 20:50:27 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
memset(ed, 0, sizeof(*ed));
|
2001-05-06 20:50:27 +00:00
|
|
|
ed->classname = "freed";
|
|
|
|
ed->freetime = level.time;
|
|
|
|
ed->inuse = qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
G_TempEntity
|
|
|
|
|
|
|
|
Spawns an event entity that will be auto-removed
|
|
|
|
The origin will be snapped to save net bandwidth, so care
|
|
|
|
must be taken if the origin is right on a surface (snap towards start vector first)
|
|
|
|
=================
|
|
|
|
*/
|
2002-06-16 20:06:15 +00:00
|
|
|
gentity_t *G_TempEntity(vec3_t origin, int event)
|
|
|
|
{
|
|
|
|
gentity_t *e;
|
|
|
|
vec3_t snapped;
|
2001-05-06 20:50:27 +00:00
|
|
|
|
|
|
|
e = G_Spawn();
|
|
|
|
e->s.eType = ET_EVENTS + event;
|
|
|
|
|
|
|
|
e->classname = "tempEntity";
|
|
|
|
e->eventTime = level.time;
|
|
|
|
e->freeAfterEvent = qtrue;
|
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
VectorCopy(origin, snapped);
|
|
|
|
SnapVector(snapped); // save network bandwidth
|
|
|
|
G_SetOrigin(e, snapped);
|
2001-05-06 20:50:27 +00:00
|
|
|
|
|
|
|
// find cluster for PVS
|
2002-06-16 20:06:15 +00:00
|
|
|
trap_LinkEntity(e);
|
2001-05-06 20:50:27 +00:00
|
|
|
|
|
|
|
return e;
|
|
|
|
}
|
|
|
|
|
2001-06-08 04:47:30 +00:00
|
|
|
/*
|
|
|
|
=================
|
|
|
|
G_TempEntity2
|
|
|
|
Elder: Like above, except can sneak in event parameters
|
|
|
|
=================
|
|
|
|
*/
|
2002-06-16 20:06:15 +00:00
|
|
|
gentity_t *G_TempEntity2(vec3_t origin, int event, int eParm)
|
|
|
|
{
|
|
|
|
gentity_t *e;
|
|
|
|
vec3_t snapped;
|
2001-06-08 04:47:30 +00:00
|
|
|
|
|
|
|
e = G_Spawn();
|
|
|
|
e->s.eType = ET_EVENTS + event;
|
|
|
|
e->s.eventParm = eParm;
|
|
|
|
|
|
|
|
e->classname = "tempEntity";
|
|
|
|
e->eventTime = level.time;
|
|
|
|
e->freeAfterEvent = qtrue;
|
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
VectorCopy(origin, snapped);
|
|
|
|
SnapVector(snapped); // save network bandwidth
|
|
|
|
G_SetOrigin(e, snapped);
|
2001-06-08 04:47:30 +00:00
|
|
|
|
|
|
|
// find cluster for PVS
|
2002-06-16 20:06:15 +00:00
|
|
|
trap_LinkEntity(e);
|
2001-06-08 04:47:30 +00:00
|
|
|
|
|
|
|
return e;
|
|
|
|
}
|
2001-05-06 20:50:27 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
==============================================================================
|
|
|
|
|
|
|
|
Kill box
|
|
|
|
|
|
|
|
==============================================================================
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
G_KillBox
|
|
|
|
|
|
|
|
Kills all entities that would touch the proposed new positioning
|
|
|
|
of ent. Ent should be unlinked before calling this!
|
|
|
|
=================
|
|
|
|
*/
|
2002-06-16 20:06:15 +00:00
|
|
|
void G_KillBox(gentity_t * ent)
|
|
|
|
{
|
|
|
|
int i, num;
|
|
|
|
int touch[MAX_GENTITIES];
|
|
|
|
gentity_t *hit;
|
|
|
|
vec3_t mins, maxs;
|
2001-05-06 20:50:27 +00:00
|
|
|
|
2002-01-11 20:20:58 +00:00
|
|
|
// JBravo attempting to stop telefrags
|
2002-06-16 20:06:15 +00:00
|
|
|
if (g_gametype.integer == GT_TEAMPLAY) { // && level.lights_camera_action) {
|
2002-01-11 20:20:58 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
VectorAdd(ent->client->ps.origin, ent->r.mins, mins);
|
|
|
|
VectorAdd(ent->client->ps.origin, ent->r.maxs, maxs);
|
|
|
|
num = trap_EntitiesInBox(mins, maxs, touch, MAX_GENTITIES);
|
2001-05-06 20:50:27 +00:00
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
for (i = 0; i < num; i++) {
|
2001-05-06 20:50:27 +00:00
|
|
|
hit = &g_entities[touch[i]];
|
2002-06-16 20:06:15 +00:00
|
|
|
if (!hit->client) {
|
2001-05-06 20:50:27 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// nail it
|
2002-06-16 20:06:15 +00:00
|
|
|
G_Damage(hit, ent, ent, NULL, NULL, 100000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
|
2001-05-06 20:50:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
G_AddPredictableEvent
|
|
|
|
|
|
|
|
Use for non-pmove events that would also be predicted on the
|
|
|
|
client side: jumppads and item pickups
|
|
|
|
Adds an event+parm and twiddles the event counter
|
|
|
|
===============
|
|
|
|
*/
|
2002-06-16 20:06:15 +00:00
|
|
|
void G_AddPredictableEvent(gentity_t * ent, int event, int eventParm)
|
|
|
|
{
|
|
|
|
if (!ent->client) {
|
2001-05-06 20:50:27 +00:00
|
|
|
return;
|
|
|
|
}
|
2002-06-16 20:06:15 +00:00
|
|
|
BG_AddPredictableEventToPlayerstate(event, eventParm, &ent->client->ps);
|
2001-05-06 20:50:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
G_AddEvent
|
|
|
|
|
|
|
|
Adds an event+parm and twiddles the event counter
|
|
|
|
===============
|
|
|
|
*/
|
2002-06-16 20:06:15 +00:00
|
|
|
void G_AddEvent(gentity_t * ent, int event, int eventParm)
|
|
|
|
{
|
|
|
|
int bits;
|
2001-05-06 20:50:27 +00:00
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
if (!event) {
|
|
|
|
G_Printf("G_AddEvent: zero event added for entity %i\n", ent->s.number);
|
2001-05-06 20:50:27 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
// clients need to add the event in playerState_t instead of entityState_t
|
2002-06-16 20:06:15 +00:00
|
|
|
if (ent->client) {
|
2001-05-06 20:50:27 +00:00
|
|
|
bits = ent->client->ps.externalEvent & EV_EVENT_BITS;
|
2002-06-16 20:06:15 +00:00
|
|
|
bits = (bits + EV_EVENT_BIT1) & EV_EVENT_BITS;
|
2001-05-06 20:50:27 +00:00
|
|
|
ent->client->ps.externalEvent = event | bits;
|
|
|
|
ent->client->ps.externalEventParm = eventParm;
|
|
|
|
ent->client->ps.externalEventTime = level.time;
|
|
|
|
} else {
|
|
|
|
bits = ent->s.event & EV_EVENT_BITS;
|
2002-06-16 20:06:15 +00:00
|
|
|
bits = (bits + EV_EVENT_BIT1) & EV_EVENT_BITS;
|
2001-05-06 20:50:27 +00:00
|
|
|
ent->s.event = event | bits;
|
|
|
|
ent->s.eventParm = eventParm;
|
|
|
|
}
|
|
|
|
ent->eventTime = level.time;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=============
|
|
|
|
G_Sound
|
|
|
|
=============
|
|
|
|
*/
|
2002-06-16 20:06:15 +00:00
|
|
|
void G_Sound(gentity_t * ent, int channel, int soundIndex)
|
|
|
|
{
|
|
|
|
gentity_t *te;
|
2001-05-06 20:50:27 +00:00
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
te = G_TempEntity(ent->r.currentOrigin, EV_GENERAL_SOUND);
|
2001-05-06 20:50:27 +00:00
|
|
|
te->s.eventParm = soundIndex;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
G_SetOrigin
|
|
|
|
|
|
|
|
Sets the pos trajectory for a fixed position
|
|
|
|
================
|
|
|
|
*/
|
2002-06-16 20:06:15 +00:00
|
|
|
void G_SetOrigin(gentity_t * ent, vec3_t origin)
|
|
|
|
{
|
|
|
|
VectorCopy(origin, ent->s.pos.trBase);
|
2001-05-06 20:50:27 +00:00
|
|
|
ent->s.pos.trType = TR_STATIONARY;
|
|
|
|
ent->s.pos.trTime = 0;
|
|
|
|
ent->s.pos.trDuration = 0;
|
2002-06-16 20:06:15 +00:00
|
|
|
VectorClear(ent->s.pos.trDelta);
|
2001-05-06 20:50:27 +00:00
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
VectorCopy(origin, ent->r.currentOrigin);
|
2001-05-06 20:50:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
DebugLine
|
|
|
|
|
|
|
|
debug polygons only work when running a local game
|
|
|
|
with r_debugSurface set to 2
|
|
|
|
================
|
|
|
|
*/
|
2002-06-16 20:06:15 +00:00
|
|
|
int DebugLine(vec3_t start, vec3_t end, int color)
|
|
|
|
{
|
|
|
|
vec3_t points[4], dir, cross, up = { 0, 0, 1 };
|
2001-05-06 20:50:27 +00:00
|
|
|
float dot;
|
|
|
|
|
|
|
|
VectorCopy(start, points[0]);
|
|
|
|
VectorCopy(start, points[1]);
|
|
|
|
//points[1][2] -= 2;
|
|
|
|
VectorCopy(end, points[2]);
|
|
|
|
//points[2][2] -= 2;
|
|
|
|
VectorCopy(end, points[3]);
|
|
|
|
|
|
|
|
VectorSubtract(end, start, dir);
|
|
|
|
VectorNormalize(dir);
|
|
|
|
dot = DotProduct(dir, up);
|
2002-06-16 20:06:15 +00:00
|
|
|
if (dot > 0.99 || dot < -0.99)
|
|
|
|
VectorSet(cross, 1, 0, 0);
|
|
|
|
else
|
|
|
|
CrossProduct(dir, up, cross);
|
2001-05-06 20:50:27 +00:00
|
|
|
|
|
|
|
VectorNormalize(cross);
|
|
|
|
|
|
|
|
VectorMA(points[0], 2, cross, points[0]);
|
|
|
|
VectorMA(points[1], -2, cross, points[1]);
|
|
|
|
VectorMA(points[2], -2, cross, points[2]);
|
|
|
|
VectorMA(points[3], 2, cross, points[3]);
|
|
|
|
|
|
|
|
return trap_DebugPolygonCreate(color, 4, points);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Blaze: Find radius
|
|
|
|
//
|
2002-06-16 20:06:15 +00:00
|
|
|
gentity_t *findradius(gentity_t * from, vec3_t org, float rad)
|
2001-05-06 20:50:27 +00:00
|
|
|
{
|
2002-06-16 20:06:15 +00:00
|
|
|
vec3_t eorg;
|
|
|
|
int j;
|
2001-05-06 20:50:27 +00:00
|
|
|
|
|
|
|
if (!from)
|
|
|
|
from = g_entities;
|
|
|
|
else
|
|
|
|
from++;
|
|
|
|
|
2002-06-16 20:06:15 +00:00
|
|
|
for (; from < &g_entities[level.num_entities]; from++) {
|
2001-05-06 20:50:27 +00:00
|
|
|
if (!from->inuse)
|
|
|
|
continue;
|
2002-06-16 20:06:15 +00:00
|
|
|
for (j = 0; j < 3; j++)
|
|
|
|
eorg[j] = org[j] - (from->r.currentOrigin[j] + (from->r.mins[j] + from->r.maxs[j]) * 0.5);
|
2001-05-06 20:50:27 +00:00
|
|
|
if (VectorLength(eorg) > rad)
|
|
|
|
continue;
|
|
|
|
return from;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2001-06-24 02:08:15 +00:00
|
|
|
/*
|
|
|
|
================
|
|
|
|
Added by Elder
|
|
|
|
|
|
|
|
RQ3_SaveZoomLevel
|
|
|
|
|
|
|
|
Quick function to save the last zoom setting
|
|
|
|
================
|
|
|
|
*/
|
2002-06-16 20:06:15 +00:00
|
|
|
void RQ3_SaveZoomLevel(gentity_t * ent)
|
|
|
|
{
|
2001-06-24 02:08:15 +00:00
|
|
|
if (ent->client) {
|
2002-06-16 20:06:15 +00:00
|
|
|
if ((ent->client->ps.stats[STAT_RQ3] & RQ3_ZOOM_LOW) == RQ3_ZOOM_LOW)
|
2001-06-24 02:08:15 +00:00
|
|
|
ent->client->lastzoom |= RQ3_ZOOM_LOW;
|
2002-06-16 20:06:15 +00:00
|
|
|
if ((ent->client->ps.stats[STAT_RQ3] & RQ3_ZOOM_MED) == RQ3_ZOOM_MED)
|
2001-06-24 02:08:15 +00:00
|
|
|
ent->client->lastzoom |= RQ3_ZOOM_MED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
Added by Elder
|
|
|
|
|
|
|
|
RQ3_isZoomed
|
|
|
|
|
|
|
|
Quick function to determine if player is zoomed in
|
|
|
|
================
|
|
|
|
*/
|
2002-06-16 20:06:15 +00:00
|
|
|
int RQ3_isZoomed(gentity_t * ent)
|
|
|
|
{
|
2001-06-24 02:08:15 +00:00
|
|
|
if (!ent->client) {
|
2002-06-16 20:06:15 +00:00
|
|
|
G_Error("RQ3_isZoomed: passed non-client entity");
|
2001-06-24 02:08:15 +00:00
|
|
|
return -1;
|
2002-06-16 20:06:15 +00:00
|
|
|
} else {
|
|
|
|
return ((ent->client->ps.stats[STAT_RQ3] & RQ3_ZOOM_LOW) == RQ3_ZOOM_LOW ||
|
|
|
|
(ent->client->ps.stats[STAT_RQ3] & RQ3_ZOOM_MED) == RQ3_ZOOM_MED);
|
2001-06-24 02:08:15 +00:00
|
|
|
}
|
|
|
|
}
|
2002-07-09 03:23:14 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
G_PlayerAlive by NiceAss
|
|
|
|
|
|
|
|
Created to fix complications when inbetween death and being a spectator in TP
|
|
|
|
*/
|
|
|
|
int G_PlayerAlive(gentity_t *ent)
|
|
|
|
{
|
|
|
|
if (g_gametype.integer < GT_TEAM)
|
|
|
|
{
|
|
|
|
if ( ent->health > 0 )
|
|
|
|
return qtrue;
|
|
|
|
else
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2002-07-09 03:31:25 +00:00
|
|
|
if ( ent->health > 0 && ( ent->client->sess.sessionTeam == TEAM_RED || ent->client->sess.sessionTeam == TEAM_BLUE ) )
|
2002-07-09 03:23:14 +00:00
|
|
|
return qtrue;
|
|
|
|
else
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
}
|