2
===========================================================================
3
Copyright (C) 1999-2005 Id Software, Inc.
5
This file is part of Quake III Arena source code.
7
Quake III Arena source code is free software; you can redistribute it
8
and/or modify it under the terms of the GNU General Public License as
9
published by the Free Software Foundation; either version 2 of the License,
10
or (at your option) any later version.
12
Quake III Arena source code is distributed in the hope that it will be
13
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
GNU General Public License for more details.
17
You should have received a copy of the GNU General Public License
18
along with Quake III Arena source code; if not, write to the Free Software
19
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20
===========================================================================
23
// g_utils.c -- misc utility functions for game module
28
char oldShader[MAX_QPATH];
29
char newShader[MAX_QPATH];
33
#define MAX_SHADER_REMAPS 128
36
shaderRemap_t remappedShaders[MAX_SHADER_REMAPS];
38
void AddRemap(const char *oldShader, const char *newShader, float timeOffset) {
41
for (i = 0; i < remapCount; i++) {
42
if (Q_stricmp(oldShader, remappedShaders[i].oldShader) == 0) {
43
// found it, just update this one
44
strcpy(remappedShaders[i].newShader,newShader);
45
remappedShaders[i].timeOffset = timeOffset;
49
if (remapCount < MAX_SHADER_REMAPS) {
50
strcpy(remappedShaders[remapCount].newShader,newShader);
51
strcpy(remappedShaders[remapCount].oldShader,oldShader);
52
remappedShaders[remapCount].timeOffset = timeOffset;
57
const char *BuildShaderStateConfig(void) {
58
static char buff[MAX_STRING_CHARS*4];
59
char out[(MAX_QPATH * 2) + 5];
62
memset(buff, 0, MAX_STRING_CHARS);
63
for (i = 0; i < remapCount; i++) {
64
Com_sprintf(out, (MAX_QPATH * 2) + 5, "%s=%s:%5.2f@", remappedShaders[i].oldShader, remappedShaders[i].newShader, remappedShaders[i].timeOffset);
65
Q_strcat( buff, sizeof( buff ), out);
71
=========================================================================
73
model / sound configstring indexes
75
=========================================================================
80
G_FindConfigstringIndex
84
int G_FindConfigstringIndex( char *name, int start, int max, qboolean create ) {
86
char s[MAX_STRING_CHARS];
88
if ( !name || !name[0] ) {
92
for ( i=1 ; i<max ; i++ ) {
93
trap_GetConfigstring( start + i, s, sizeof( s ) );
97
if ( !strcmp( s, name ) ) {
107
G_Error( "G_FindConfigstringIndex: overflow" );
110
trap_SetConfigstring( start + i, name );
116
int G_ModelIndex( char *name ) {
117
return G_FindConfigstringIndex (name, CS_MODELS, MAX_MODELS, qtrue);
120
int G_SoundIndex( char *name ) {
121
return G_FindConfigstringIndex (name, CS_SOUNDS, MAX_SOUNDS, qtrue);
124
//=====================================================================
131
Broadcasts a command to only a specific team
134
void G_TeamCommand( team_t team, char *cmd ) {
137
for ( i = 0 ; i < level.maxclients ; i++ ) {
138
if ( level.clients[i].pers.connected == CON_CONNECTED ) {
139
if ( level.clients[i].sess.sessionTeam == team ) {
140
trap_SendServerCommand( i, va("%s", cmd ));
151
Searches all active entities for the next one that holds
152
the matching string at fieldofs (use the FOFS() macro) in the structure.
154
Searches beginning at the entity after from, or the beginning if NULL
155
NULL will be returned if the end of the list is reached.
159
gentity_t *G_Find (gentity_t *from, int fieldofs, const char *match)
168
for ( ; from < &g_entities[level.num_entities] ; from++)
172
s = *(char **) ((byte *)from + fieldofs);
175
if (!Q_stricmp (s, match))
187
Selects a random entity from among the targets
190
#define MAXCHOICES 32
192
gentity_t *G_PickTarget (char *targetname)
194
gentity_t *ent = NULL;
196
gentity_t *choice[MAXCHOICES];
200
G_Printf("G_PickTarget called with NULL targetname\n");
206
ent = G_Find (ent, FOFS(targetname), targetname);
209
choice[num_choices++] = ent;
210
if (num_choices == MAXCHOICES)
216
G_Printf("G_PickTarget: target %s not found\n", targetname);
220
return choice[rand() % num_choices];
225
==============================
228
"activator" should be set to the entity that initiated the firing.
230
Search for (string)targetname in all entities that
231
match (string)self.target and call their .use function
233
==============================
235
void G_UseTargets( gentity_t *ent, gentity_t *activator ) {
242
if (ent->targetShaderName && ent->targetShaderNewName) {
243
float f = level.time * 0.001;
244
AddRemap(ent->targetShaderName, ent->targetShaderNewName, f);
245
trap_SetConfigstring(CS_SHADERSTATE, BuildShaderStateConfig());
248
if ( !ent->target ) {
253
while ( (t = G_Find (t, FOFS(targetname), ent->target)) != NULL ) {
255
G_Printf ("WARNING: Entity used itself.\n");
258
t->use (t, ent, activator);
262
G_Printf("entity was removed while using targets\n");
273
This is just a convenience function
274
for making temporary vectors for function calls
277
float *tv( float x, float y, float z ) {
279
static vec3_t vecs[8];
282
// use an array so that multiple tempvectors won't collide
285
index = (index + 1)&7;
299
This is just a convenience function
303
char *vtos( const vec3_t v ) {
305
static char str[8][32];
308
// use an array so that multiple vtos won't collide
310
index = (index + 1)&7;
312
Com_sprintf (s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]);
322
The editor only specifies a single value for angles (yaw),
323
but we have special constants to generate an up or down direction.
324
Angles will be cleared, because it is being used to represent a direction
325
instead of an orientation.
328
void G_SetMovedir( vec3_t angles, vec3_t movedir ) {
329
static vec3_t VEC_UP = {0, -1, 0};
330
static vec3_t MOVEDIR_UP = {0, 0, 1};
331
static vec3_t VEC_DOWN = {0, -2, 0};
332
static vec3_t MOVEDIR_DOWN = {0, 0, -1};
334
if ( VectorCompare (angles, VEC_UP) ) {
335
VectorCopy (MOVEDIR_UP, movedir);
336
} else if ( VectorCompare (angles, VEC_DOWN) ) {
337
VectorCopy (MOVEDIR_DOWN, movedir);
339
AngleVectors (angles, movedir, NULL, NULL);
341
VectorClear( angles );
345
float vectoyaw( const vec3_t vec ) {
348
if (vec[YAW] == 0 && vec[PITCH] == 0) {
352
yaw = ( atan2( vec[YAW], vec[PITCH]) * 180 / M_PI );
353
} else if (vec[YAW] > 0) {
367
void G_InitGentity( gentity_t *e ) {
369
e->classname = "noclass";
370
e->s.number = e - g_entities;
371
e->r.ownerNum = ENTITYNUM_NONE;
378
Either finds a free entity, or allocates a new one.
380
The slots from 0 to MAX_CLIENTS-1 are always reserved for clients, and will
381
never be used by anything else.
383
Try to avoid reusing an entity that was recently freed, because it
384
can cause the client to think the entity morphed into something else
385
instead of being removed and recreated, which can cause interpolated
386
angles and bad trails.
389
gentity_t *G_Spawn( void ) {
393
e = NULL; // shut up warning
394
i = 0; // shut up warning
395
for ( force = 0 ; force < 2 ; force++ ) {
396
// if we go through all entities and can't find one to free,
397
// override the normal minimum times before use
398
e = &g_entities[MAX_CLIENTS];
399
for ( i = MAX_CLIENTS ; i<level.num_entities ; i++, e++) {
404
// the first couple seconds of server time can involve a lot of
405
// freeing and allocating, so relax the replacement policy
406
if ( !force && e->freetime > level.startTime + 2000 && level.time - e->freetime < 1000 ) {
414
if ( i != MAX_GENTITIES ) {
418
if ( i == ENTITYNUM_MAX_NORMAL ) {
419
for (i = 0; i < MAX_GENTITIES; i++) {
420
G_Printf("%4i: %s\n", i, g_entities[i].classname);
422
G_Error( "G_Spawn: no free entities" );
425
// open up a new slot
426
level.num_entities++;
428
// let the server system know that there are more entities
429
trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ),
430
&level.clients[0].ps, sizeof( level.clients[0] ) );
441
qboolean G_EntitiesFree( void ) {
445
e = &g_entities[MAX_CLIENTS];
446
for ( i = MAX_CLIENTS; i < level.num_entities; i++, e++) {
461
Marks the entity as free
464
void G_FreeEntity( gentity_t *ed ) {
465
trap_UnlinkEntity (ed); // unlink from world
467
if ( ed->neverFree ) {
471
memset (ed, 0, sizeof(*ed));
472
ed->classname = "freed";
473
ed->freetime = level.time;
481
Spawns an event entity that will be auto-removed
482
The origin will be snapped to save net bandwidth, so care
483
must be taken if the origin is right on a surface (snap towards start vector first)
486
gentity_t *G_TempEntity( vec3_t origin, int event ) {
491
e->s.eType = ET_EVENTS + event;
493
e->classname = "tempEntity";
494
e->eventTime = level.time;
495
e->freeAfterEvent = qtrue;
497
VectorCopy( origin, snapped );
498
SnapVector( snapped ); // save network bandwidth
499
G_SetOrigin( e, snapped );
501
// find cluster for PVS
502
trap_LinkEntity( e );
510
==============================================================================
514
==============================================================================
521
Kills all entities that would touch the proposed new positioning
522
of ent. Ent should be unlinked before calling this!
525
void G_KillBox (gentity_t *ent) {
527
int touch[MAX_GENTITIES];
531
VectorAdd( ent->client->ps.origin, ent->r.mins, mins );
532
VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs );
533
num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
535
for (i=0 ; i<num ; i++) {
536
hit = &g_entities[touch[i]];
537
if ( !hit->client ) {
542
G_Damage ( hit, ent, ent, NULL, NULL,
543
100000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
548
//==============================================================================
552
G_AddPredictableEvent
554
Use for non-pmove events that would also be predicted on the
555
client side: jumppads and item pickups
556
Adds an event+parm and twiddles the event counter
559
void G_AddPredictableEvent( gentity_t *ent, int event, int eventParm ) {
560
if ( !ent->client ) {
563
BG_AddPredictableEventToPlayerstate( event, eventParm, &ent->client->ps );
571
Adds an event+parm and twiddles the event counter
574
void G_AddEvent( gentity_t *ent, int event, int eventParm ) {
578
G_Printf( "G_AddEvent: zero event added for entity %i\n", ent->s.number );
582
// clients need to add the event in playerState_t instead of entityState_t
584
bits = ent->client->ps.externalEvent & EV_EVENT_BITS;
585
bits = ( bits + EV_EVENT_BIT1 ) & EV_EVENT_BITS;
586
ent->client->ps.externalEvent = event | bits;
587
ent->client->ps.externalEventParm = eventParm;
588
ent->client->ps.externalEventTime = level.time;
590
bits = ent->s.event & EV_EVENT_BITS;
591
bits = ( bits + EV_EVENT_BIT1 ) & EV_EVENT_BITS;
592
ent->s.event = event | bits;
593
ent->s.eventParm = eventParm;
595
ent->eventTime = level.time;
604
void G_Sound( gentity_t *ent, int channel, int soundIndex ) {
607
te = G_TempEntity( ent->r.currentOrigin, EV_GENERAL_SOUND );
608
te->s.eventParm = soundIndex;
612
//==============================================================================
619
Sets the pos trajectory for a fixed position
622
void G_SetOrigin( gentity_t *ent, vec3_t origin ) {
623
VectorCopy( origin, ent->s.pos.trBase );
624
ent->s.pos.trType = TR_STATIONARY;
625
ent->s.pos.trTime = 0;
626
ent->s.pos.trDuration = 0;
627
VectorClear( ent->s.pos.trDelta );
629
VectorCopy( origin, ent->r.currentOrigin );
636
debug polygons only work when running a local game
637
with r_debugSurface set to 2
640
int DebugLine(vec3_t start, vec3_t end, int color) {
641
vec3_t points[4], dir, cross, up = {0, 0, 1};
644
VectorCopy(start, points[0]);
645
VectorCopy(start, points[1]);
647
VectorCopy(end, points[2]);
649
VectorCopy(end, points[3]);
652
VectorSubtract(end, start, dir);
653
VectorNormalize(dir);
654
dot = DotProduct(dir, up);
655
if (dot > 0.99 || dot < -0.99) VectorSet(cross, 1, 0, 0);
656
else CrossProduct(dir, up, cross);
658
VectorNormalize(cross);
660
VectorMA(points[0], 2, cross, points[0]);
661
VectorMA(points[1], -2, cross, points[1]);
662
VectorMA(points[2], -2, cross, points[2]);
663
VectorMA(points[3], 2, cross, points[3]);
665
return trap_DebugPolygonCreate(color, 4, points);