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