1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4
5 This file is part of Quake III Arena source code.
6
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.
11
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.
16
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 ===========================================================================
21 */
22 //
23 // g_utils.c -- misc utility functions for game module
24
25 #include "g_local.h"
26
27 typedef struct {
28 char oldShader[MAX_QPATH];
29 char newShader[MAX_QPATH];
30 float timeOffset;
31 } shaderRemap_t;
32
33 #define MAX_SHADER_REMAPS 128
34
35 int remapCount = 0;
36 shaderRemap_t remappedShaders[MAX_SHADER_REMAPS];
37
AddRemap(const char * oldShader,const char * newShader,float timeOffset)38 void AddRemap(const char *oldShader, const char *newShader, float timeOffset) {
39 int i;
40
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;
46 return;
47 }
48 }
49 if (remapCount < MAX_SHADER_REMAPS) {
50 strcpy(remappedShaders[remapCount].newShader,newShader);
51 strcpy(remappedShaders[remapCount].oldShader,oldShader);
52 remappedShaders[remapCount].timeOffset = timeOffset;
53 remapCount++;
54 }
55 }
56
BuildShaderStateConfig(void)57 const char *BuildShaderStateConfig(void) {
58 static char buff[MAX_STRING_CHARS*4];
59 char out[(MAX_QPATH * 2) + 5];
60 int i;
61
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);
66 }
67 return buff;
68 }
69
70 /*
71 =========================================================================
72
73 model / sound configstring indexes
74
75 =========================================================================
76 */
77
78 /*
79 ================
80 G_FindConfigstringIndex
81
82 ================
83 */
G_FindConfigstringIndex(char * name,int start,int max,qboolean create)84 int G_FindConfigstringIndex( char *name, int start, int max, qboolean create ) {
85 int i;
86 char s[MAX_STRING_CHARS];
87
88 if ( !name || !name[0] ) {
89 return 0;
90 }
91
92 for ( i=1 ; i<max ; i++ ) {
93 trap_GetConfigstring( start + i, s, sizeof( s ) );
94 if ( !s[0] ) {
95 break;
96 }
97 if ( !strcmp( s, name ) ) {
98 return i;
99 }
100 }
101
102 if ( !create ) {
103 return 0;
104 }
105
106 if ( i == max ) {
107 G_Error( "G_FindConfigstringIndex: overflow" );
108 }
109
110 trap_SetConfigstring( start + i, name );
111
112 return i;
113 }
114
115
G_ModelIndex(char * name)116 int G_ModelIndex( char *name ) {
117 return G_FindConfigstringIndex (name, CS_MODELS, MAX_MODELS, qtrue);
118 }
119
G_SoundIndex(char * name)120 int G_SoundIndex( char *name ) {
121 return G_FindConfigstringIndex (name, CS_SOUNDS, MAX_SOUNDS, qtrue);
122 }
123
124 //=====================================================================
125
126
127 /*
128 ================
129 G_TeamCommand
130
131 Broadcasts a command to only a specific team
132 ================
133 */
G_TeamCommand(team_t team,char * cmd)134 void G_TeamCommand( team_t team, char *cmd ) {
135 int i;
136
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 ));
141 }
142 }
143 }
144 }
145
146
147 /*
148 =============
149 G_Find
150
151 Searches all active entities for the next one that holds
152 the matching string at fieldofs (use the FOFS() macro) in the structure.
153
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.
156
157 =============
158 */
G_Find(gentity_t * from,int fieldofs,const char * match)159 gentity_t *G_Find (gentity_t *from, int fieldofs, const char *match)
160 {
161 char *s;
162
163 if (!from)
164 from = g_entities;
165 else
166 from++;
167
168 for ( ; from < &g_entities[level.num_entities] ; from++)
169 {
170 if (!from->inuse)
171 continue;
172 s = *(char **) ((byte *)from + fieldofs);
173 if (!s)
174 continue;
175 if (!Q_stricmp (s, match))
176 return from;
177 }
178
179 return NULL;
180 }
181
182
183 /*
184 =============
185 G_PickTarget
186
187 Selects a random entity from among the targets
188 =============
189 */
190 #define MAXCHOICES 32
191
G_PickTarget(char * targetname)192 gentity_t *G_PickTarget (char *targetname)
193 {
194 gentity_t *ent = NULL;
195 int num_choices = 0;
196 gentity_t *choice[MAXCHOICES];
197
198 if (!targetname)
199 {
200 G_Printf("G_PickTarget called with NULL targetname\n");
201 return NULL;
202 }
203
204 while(1)
205 {
206 ent = G_Find (ent, FOFS(targetname), targetname);
207 if (!ent)
208 break;
209 choice[num_choices++] = ent;
210 if (num_choices == MAXCHOICES)
211 break;
212 }
213
214 if (!num_choices)
215 {
216 G_Printf("G_PickTarget: target %s not found\n", targetname);
217 return NULL;
218 }
219
220 return choice[rand() % num_choices];
221 }
222
223
224 /*
225 ==============================
226 G_UseTargets
227
228 "activator" should be set to the entity that initiated the firing.
229
230 Search for (string)targetname in all entities that
231 match (string)self.target and call their .use function
232
233 ==============================
234 */
G_UseTargets(gentity_t * ent,gentity_t * activator)235 void G_UseTargets( gentity_t *ent, gentity_t *activator ) {
236 gentity_t *t;
237
238 if ( !ent ) {
239 return;
240 }
241
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());
246 }
247
248 if ( !ent->target ) {
249 return;
250 }
251
252 t = NULL;
253 while ( (t = G_Find (t, FOFS(targetname), ent->target)) != NULL ) {
254 if ( t == ent ) {
255 G_Printf ("WARNING: Entity used itself.\n");
256 } else {
257 if ( t->use ) {
258 t->use (t, ent, activator);
259 }
260 }
261 if ( !ent->inuse ) {
262 G_Printf("entity was removed while using targets\n");
263 return;
264 }
265 }
266 }
267
268
269 /*
270 =============
271 TempVector
272
273 This is just a convenience function
274 for making temporary vectors for function calls
275 =============
276 */
tv(float x,float y,float z)277 float *tv( float x, float y, float z ) {
278 static int index;
279 static vec3_t vecs[8];
280 float *v;
281
282 // use an array so that multiple tempvectors won't collide
283 // for a while
284 v = vecs[index];
285 index = (index + 1)&7;
286
287 v[0] = x;
288 v[1] = y;
289 v[2] = z;
290
291 return v;
292 }
293
294
295 /*
296 =============
297 VectorToString
298
299 This is just a convenience function
300 for printing vectors
301 =============
302 */
vtos(const vec3_t v)303 char *vtos( const vec3_t v ) {
304 static int index;
305 static char str[8][32];
306 char *s;
307
308 // use an array so that multiple vtos won't collide
309 s = str[index];
310 index = (index + 1)&7;
311
312 Com_sprintf (s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]);
313
314 return s;
315 }
316
317
318 /*
319 ===============
320 G_SetMovedir
321
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.
326 ===============
327 */
G_SetMovedir(vec3_t angles,vec3_t movedir)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};
333
334 if ( VectorCompare (angles, VEC_UP) ) {
335 VectorCopy (MOVEDIR_UP, movedir);
336 } else if ( VectorCompare (angles, VEC_DOWN) ) {
337 VectorCopy (MOVEDIR_DOWN, movedir);
338 } else {
339 AngleVectors (angles, movedir, NULL, NULL);
340 }
341 VectorClear( angles );
342 }
343
344
vectoyaw(const vec3_t vec)345 float vectoyaw( const vec3_t vec ) {
346 float yaw;
347
348 if (vec[YAW] == 0 && vec[PITCH] == 0) {
349 yaw = 0;
350 } else {
351 if (vec[PITCH]) {
352 yaw = ( atan2( vec[YAW], vec[PITCH]) * 180 / M_PI );
353 } else if (vec[YAW] > 0) {
354 yaw = 90;
355 } else {
356 yaw = 270;
357 }
358 if (yaw < 0) {
359 yaw += 360;
360 }
361 }
362
363 return yaw;
364 }
365
366
G_InitGentity(gentity_t * e)367 void G_InitGentity( gentity_t *e ) {
368 e->inuse = qtrue;
369 e->classname = "noclass";
370 e->s.number = e - g_entities;
371 e->r.ownerNum = ENTITYNUM_NONE;
372 }
373
374 /*
375 =================
376 G_Spawn
377
378 Either finds a free entity, or allocates a new one.
379
380 The slots from 0 to MAX_CLIENTS-1 are always reserved for clients, and will
381 never be used by anything else.
382
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.
387 =================
388 */
G_Spawn(void)389 gentity_t *G_Spawn( void ) {
390 int i, force;
391 gentity_t *e;
392
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++) {
400 if ( e->inuse ) {
401 continue;
402 }
403
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 ) {
407 continue;
408 }
409
410 // reuse this slot
411 G_InitGentity( e );
412 return e;
413 }
414 if ( i != MAX_GENTITIES ) {
415 break;
416 }
417 }
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);
421 }
422 G_Error( "G_Spawn: no free entities" );
423 }
424
425 // open up a new slot
426 level.num_entities++;
427
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] ) );
431
432 G_InitGentity( e );
433 return e;
434 }
435
436 /*
437 =================
438 G_EntitiesFree
439 =================
440 */
G_EntitiesFree(void)441 qboolean G_EntitiesFree( void ) {
442 int i;
443 gentity_t *e;
444
445 e = &g_entities[MAX_CLIENTS];
446 for ( i = MAX_CLIENTS; i < level.num_entities; i++, e++) {
447 if ( e->inuse ) {
448 continue;
449 }
450 // slot available
451 return qtrue;
452 }
453 return qfalse;
454 }
455
456
457 /*
458 =================
459 G_FreeEntity
460
461 Marks the entity as free
462 =================
463 */
G_FreeEntity(gentity_t * ed)464 void G_FreeEntity( gentity_t *ed ) {
465 trap_UnlinkEntity (ed); // unlink from world
466
467 if ( ed->neverFree ) {
468 return;
469 }
470
471 memset (ed, 0, sizeof(*ed));
472 ed->classname = "freed";
473 ed->freetime = level.time;
474 ed->inuse = qfalse;
475 }
476
477 /*
478 =================
479 G_TempEntity
480
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)
484 =================
485 */
G_TempEntity(vec3_t origin,int event)486 gentity_t *G_TempEntity( vec3_t origin, int event ) {
487 gentity_t *e;
488 vec3_t snapped;
489
490 e = G_Spawn();
491 e->s.eType = ET_EVENTS + event;
492
493 e->classname = "tempEntity";
494 e->eventTime = level.time;
495 e->freeAfterEvent = qtrue;
496
497 VectorCopy( origin, snapped );
498 SnapVector( snapped ); // save network bandwidth
499 G_SetOrigin( e, snapped );
500
501 // find cluster for PVS
502 trap_LinkEntity( e );
503
504 return e;
505 }
506
507
508
509 /*
510 ==============================================================================
511
512 Kill box
513
514 ==============================================================================
515 */
516
517 /*
518 =================
519 G_KillBox
520
521 Kills all entities that would touch the proposed new positioning
522 of ent. Ent should be unlinked before calling this!
523 =================
524 */
G_KillBox(gentity_t * ent)525 void G_KillBox (gentity_t *ent) {
526 int i, num;
527 int touch[MAX_GENTITIES];
528 gentity_t *hit;
529 vec3_t mins, maxs;
530
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 );
534
535 for (i=0 ; i<num ; i++) {
536 hit = &g_entities[touch[i]];
537 if ( !hit->client ) {
538 continue;
539 }
540
541 // nail it
542 G_Damage ( hit, ent, ent, NULL, NULL,
543 100000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
544 }
545
546 }
547
548 //==============================================================================
549
550 /*
551 ===============
552 G_AddPredictableEvent
553
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
557 ===============
558 */
G_AddPredictableEvent(gentity_t * ent,int event,int eventParm)559 void G_AddPredictableEvent( gentity_t *ent, int event, int eventParm ) {
560 if ( !ent->client ) {
561 return;
562 }
563 BG_AddPredictableEventToPlayerstate( event, eventParm, &ent->client->ps );
564 }
565
566
567 /*
568 ===============
569 G_AddEvent
570
571 Adds an event+parm and twiddles the event counter
572 ===============
573 */
G_AddEvent(gentity_t * ent,int event,int eventParm)574 void G_AddEvent( gentity_t *ent, int event, int eventParm ) {
575 int bits;
576
577 if ( !event ) {
578 G_Printf( "G_AddEvent: zero event added for entity %i\n", ent->s.number );
579 return;
580 }
581
582 // clients need to add the event in playerState_t instead of entityState_t
583 if ( ent->client ) {
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;
589 } else {
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;
594 }
595 ent->eventTime = level.time;
596 }
597
598
599 /*
600 =============
601 G_Sound
602 =============
603 */
G_Sound(gentity_t * ent,int channel,int soundIndex)604 void G_Sound( gentity_t *ent, int channel, int soundIndex ) {
605 gentity_t *te;
606
607 te = G_TempEntity( ent->r.currentOrigin, EV_GENERAL_SOUND );
608 te->s.eventParm = soundIndex;
609 }
610
611
612 //==============================================================================
613
614
615 /*
616 ================
617 G_SetOrigin
618
619 Sets the pos trajectory for a fixed position
620 ================
621 */
G_SetOrigin(gentity_t * ent,vec3_t origin)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 );
628
629 VectorCopy( origin, ent->r.currentOrigin );
630 }
631
632 /*
633 ================
634 DebugLine
635
636 debug polygons only work when running a local game
637 with r_debugSurface set to 2
638 ================
639 */
DebugLine(vec3_t start,vec3_t end,int color)640 int DebugLine(vec3_t start, vec3_t end, int color) {
641 vec3_t points[4], dir, cross, up = {0, 0, 1};
642 float dot;
643
644 VectorCopy(start, points[0]);
645 VectorCopy(start, points[1]);
646 //points[1][2] -= 2;
647 VectorCopy(end, points[2]);
648 //points[2][2] -= 2;
649 VectorCopy(end, points[3]);
650
651
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);
657
658 VectorNormalize(cross);
659
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]);
664
665 return trap_DebugPolygonCreate(color, 4, points);
666 }
667