1 /*
2 ===========================================================================
3 
4 Return to Castle Wolfenstein multiplayer GPL Source Code
5 Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
6 
7 This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”).
8 
9 RTCW MP Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13 
14 RTCW MP Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with RTCW MP Source Code.  If not, see <http://www.gnu.org/licenses/>.
21 
22 In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code.  If not, please request a copy in writing from id Software at the address below.
23 
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25 
26 ===========================================================================
27 */
28 
29 /*
30  * name:	g_utils.c
31  *
32  * desc:	misc utility functions for game module
33  *
34 */
35 
36 #include "g_local.h"
37 
38 typedef struct {
39 	char oldShader[MAX_QPATH];
40 	char newShader[MAX_QPATH];
41 	float timeOffset;
42 } shaderRemap_t;
43 
44 #define MAX_SHADER_REMAPS 128
45 
46 int remapCount = 0;
47 shaderRemap_t remappedShaders[MAX_SHADER_REMAPS];
48 
AddRemap(const char * oldShader,const char * newShader,float timeOffset)49 void AddRemap( const char *oldShader, const char *newShader, float timeOffset ) {
50 	int i;
51 
52 	for ( i = 0; i < remapCount; i++ ) {
53 		if ( Q_stricmp( oldShader, remappedShaders[i].oldShader ) == 0 ) {
54 			// found it, just update this one
55 			strcpy( remappedShaders[i].newShader,newShader );
56 			remappedShaders[i].timeOffset = timeOffset;
57 			return;
58 		}
59 	}
60 	if ( remapCount < MAX_SHADER_REMAPS ) {
61 		strcpy( remappedShaders[remapCount].newShader,newShader );
62 		strcpy( remappedShaders[remapCount].oldShader,oldShader );
63 		remappedShaders[remapCount].timeOffset = timeOffset;
64 		remapCount++;
65 	}
66 }
67 
BuildShaderStateConfig(void)68 const char *BuildShaderStateConfig(void) {
69 	static char buff[MAX_STRING_CHARS * 4];
70 	char out[( MAX_QPATH * 2 ) + 5];
71 	int i;
72 
73 	memset( buff, 0, MAX_STRING_CHARS );
74 	for ( i = 0; i < remapCount; i++ ) {
75 		Com_sprintf( out, ( MAX_QPATH * 2 ) + 5, "%s=%s:%5.2f@", remappedShaders[i].oldShader, remappedShaders[i].newShader, remappedShaders[i].timeOffset );
76 		Q_strcat( buff, sizeof( buff ), out );
77 	}
78 	return buff;
79 }
80 
81 /*
82 =========================================================================
83 
84 model / sound configstring indexes
85 
86 =========================================================================
87 */
88 
89 /*
90 ================
91 G_FindConfigstringIndex
92 
93 ================
94 */
G_FindConfigstringIndex(const char * name,int start,int max,qboolean create)95 int G_FindConfigstringIndex( const char *name, int start, int max, qboolean create ) {
96 	int i;
97 	char s[MAX_STRING_CHARS];
98 
99 	if ( !name || !name[0] ) {
100 		return 0;
101 	}
102 
103 	for ( i = 1 ; i < max ; i++ ) {
104 		trap_GetConfigstring( start + i, s, sizeof( s ) );
105 		if ( !s[0] ) {
106 			break;
107 		}
108 		if ( !strcmp( s, name ) ) {
109 			return i;
110 		}
111 	}
112 
113 	if ( !create ) {
114 		return 0;
115 	}
116 
117 	if ( i == max ) {
118 		G_Error( "G_FindConfigstringIndex: overflow" );
119 	}
120 
121 	trap_SetConfigstring( start + i, name );
122 
123 	return i;
124 }
125 
126 
G_ModelIndex(char * name)127 int G_ModelIndex( char *name ) {
128 	return G_FindConfigstringIndex( name, CS_MODELS, MAX_MODELS, qtrue );
129 }
130 
G_SoundIndex(const char * name)131 int G_SoundIndex( const char *name ) {
132 	return G_FindConfigstringIndex( name, CS_SOUNDS, MAX_SOUNDS, qtrue );
133 }
134 
135 //=====================================================================
136 
137 
138 /*
139 ================
140 G_TeamCommand
141 
142 Broadcasts a command to only a specific team
143 ================
144 */
G_TeamCommand(team_t team,char * cmd)145 void G_TeamCommand( team_t team, char *cmd ) {
146 	int i;
147 
148 	for ( i = 0 ; i < level.maxclients ; i++ ) {
149 		if ( level.clients[i].pers.connected == CON_CONNECTED ) {
150 			if ( level.clients[i].sess.sessionTeam == team ) {
151 				trap_SendServerCommand( i, va( "%s", cmd ) );
152 			}
153 		}
154 	}
155 }
156 
157 
158 /*
159 =============
160 G_Find
161 
162 Searches all active entities for the next one that holds
163 the matching string at fieldofs (use the FOFS() macro) in the structure.
164 
165 Searches beginning at the entity after from, or the beginning if NULL
166 NULL will be returned if the end of the list is reached.
167 
168 =============
169 */
G_Find(gentity_t * from,int fieldofs,const char * match)170 gentity_t *G_Find( gentity_t *from, int fieldofs, const char *match ) {
171 	char    *s;
172 
173 	if ( !from ) {
174 		from = g_entities;
175 	} else {
176 		from++;
177 	}
178 
179 	for ( ; from < &g_entities[level.num_entities] ; from++ )
180 	{
181 		if ( !from->inuse ) {
182 			continue;
183 		}
184 		s = *( char ** )( (byte *)from + fieldofs );
185 		if ( !s ) {
186 			continue;
187 		}
188 		if ( !Q_stricmp( s, match ) ) {
189 			return from;
190 		}
191 	}
192 
193 	return NULL;
194 }
195 
196 
197 /*
198 =============
199 G_PickTarget
200 
201 Selects a random entity from among the targets
202 =============
203 */
204 #define MAXCHOICES  32
205 
G_PickTarget(char * targetname)206 gentity_t *G_PickTarget( char *targetname ) {
207 	gentity_t   *ent = NULL;
208 	int num_choices = 0;
209 	gentity_t   *choice[MAXCHOICES];
210 
211 	if ( !targetname ) {
212 		//G_Printf("G_PickTarget called with NULL targetname\n");
213 		return NULL;
214 	}
215 
216 	while ( 1 )
217 	{
218 		ent = G_Find( ent, FOFS( targetname ), targetname );
219 		if ( !ent ) {
220 			break;
221 		}
222 		choice[num_choices++] = ent;
223 		if ( num_choices == MAXCHOICES ) {
224 			break;
225 		}
226 	}
227 
228 	if ( !num_choices ) {
229 		G_Printf( "G_PickTarget: target %s not found\n", targetname );
230 		return NULL;
231 	}
232 
233 	return choice[rand() % num_choices];
234 }
235 
236 
237 /*
238 ==============================
239 G_UseTargets
240 
241 "activator" should be set to the entity that initiated the firing.
242 
243 Search for (string)targetname in all entities that
244 match (string)self.target and call their .use function
245 
246 ==============================
247 */
G_UseTargets(gentity_t * ent,gentity_t * activator)248 void G_UseTargets( gentity_t *ent, gentity_t *activator ) {
249 	gentity_t       *t;
250 
251 	if ( !ent ) {
252 		return;
253 	}
254 
255 	if ( ent->targetShaderName && ent->targetShaderNewName ) {
256 		float f = level.time * 0.001;
257 		AddRemap( ent->targetShaderName, ent->targetShaderNewName, f );
258 		trap_SetConfigstring( CS_SHADERSTATE, BuildShaderStateConfig() );
259 	}
260 
261 	if ( !ent->target ) {
262 		return;
263 	}
264 
265 	t = NULL;
266 	while ( ( t = G_Find( t, FOFS( targetname ), ent->target ) ) != NULL ) {
267 		if ( t == ent ) {
268 			G_Printf( "WARNING: Entity used itself.\n" );
269 		} else {
270 			if ( t->use ) {
271 				//G_Printf ("ent->classname %s ent->targetname %s t->targetname %s t->s.number %d\n", ent->classname, ent->targetname, t->targetname, t->s.number);
272 
273 				t->flags |= ( ent->flags & FL_KICKACTIVATE ); // (SA) If 'ent' was kicked to activate, pass this along to it's targets.
274 															  //		It may become handy to put a "KICKABLE" flag in ents so that it knows whether to pass this along or not
275 															  //		Right now, the only situation where it would be weird would be an invisible_user that is a 'button' near
276 															  //		a rotating door that it triggers.  Kick the switch and the door next to it flies open.
277 
278 				t->flags |= ( ent->flags & FL_SOFTACTIVATE ); // (SA) likewise for soft activation
279 
280 				if (    activator &&
281 						(   ( Q_stricmp( t->classname, "func_door" ) == 0 ) ||
282 							( Q_stricmp( t->classname, "func_door_rotating" ) == 0 )
283 						)
284 						) {
285 					// check door usage rules before allowing any entity to trigger a door open
286 					G_TryDoor( t, ent, activator );       // (door,other,activator)
287 				} else {
288 					t->use( t, ent, activator );
289 				}
290 			}
291 		}
292 		if ( !ent->inuse ) {
293 			G_Printf( "entity was removed while using targets\n" );
294 			return;
295 		}
296 	}
297 }
298 
299 
300 /*
301 =============
302 TempVector
303 
304 This is just a convenience function
305 for making temporary vectors for function calls
306 =============
307 */
308 /*
309 float	*tv( float x, float y, float z ) {
310 	static	int		index;
311 	static	vec3_t	vecs[8];
312 	float	*v;
313 
314 	// use an array so that multiple tempvectors won't collide
315 	// for a while
316 	v = vecs[index];
317 	index = (index + 1)&7;
318 
319 	v[0] = x;
320 	v[1] = y;
321 	v[2] = z;
322 
323 	return v;
324 }
325 */
326 
327 /*
328 =============
329 VectorToString
330 
331 This is just a convenience function
332 for printing vectors
333 =============
334 */
vtos(const vec3_t v)335 char    *vtos( const vec3_t v ) {
336 	static int index;
337 	static char str[8][32];
338 	char    *s;
339 
340 	// use an array so that multiple vtos won't collide
341 	s = str[index];
342 	index = ( index + 1 ) & 7;
343 
344 	Com_sprintf( s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2] );
345 
346 	return s;
347 }
vtosf(const vec3_t v)348 char    *vtosf( const vec3_t v ) {
349 	static int index;
350 	static char str[8][64];
351 	char    *s;
352 
353 	// use an array so that multiple vtos won't collide
354 	s = str[index];
355 	index = ( index + 1 ) & 7;
356 
357 	Com_sprintf( s, 64, "(%f %f %f)", v[0], v[1], v[2] );
358 
359 	return s;
360 }
361 
362 
363 /*
364 ===============
365 G_SetMovedir
366 
367 The editor only specifies a single value for angles (yaw),
368 but we have special constants to generate an up or down direction.
369 Angles will be cleared, because it is being used to represent a direction
370 instead of an orientation.
371 ===============
372 */
G_SetMovedir(vec3_t angles,vec3_t movedir)373 void G_SetMovedir( vec3_t angles, vec3_t movedir ) {
374 	static vec3_t VEC_UP        = {0, -1, 0};
375 	static vec3_t MOVEDIR_UP    = {0, 0, 1};
376 	static vec3_t VEC_DOWN      = {0, -2, 0};
377 	static vec3_t MOVEDIR_DOWN  = {0, 0, -1};
378 
379 	if ( VectorCompare( angles, VEC_UP ) ) {
380 		VectorCopy( MOVEDIR_UP, movedir );
381 	} else if ( VectorCompare( angles, VEC_DOWN ) ) {
382 		VectorCopy( MOVEDIR_DOWN, movedir );
383 	} else {
384 		AngleVectors( angles, movedir, NULL, NULL );
385 	}
386 	VectorClear( angles );
387 }
388 
389 
390 
G_InitGentity(gentity_t * e)391 void G_InitGentity( gentity_t *e ) {
392 	e->inuse = qtrue;
393 	e->classname = "noclass";
394 	e->s.number = e - g_entities;
395 	e->r.ownerNum = ENTITYNUM_NONE;
396 	e->headshotDamageScale = 1.0;   // RF, default value
397 
398 	// RF, init scripting
399 	e->scriptStatus.scriptEventIndex = -1;
400 }
401 
402 /*
403 =================
404 G_Spawn
405 
406 Either finds a free entity, or allocates a new one.
407 
408   The slots from 0 to MAX_CLIENTS-1 are always reserved for clients, and will
409 never be used by anything else.
410 
411 Try to avoid reusing an entity that was recently freed, because it
412 can cause the client to think the entity morphed into something else
413 instead of being removed and recreated, which can cause interpolated
414 angles and bad trails.
415 =================
416 */
G_Spawn(void)417 gentity_t *G_Spawn( void ) {
418 	int i, force;
419 	gentity_t   *e;
420 
421 	e = NULL;   // shut up warning
422 	for ( force = 0 ; force < 2 ; force++ ) {
423 		// if we go through all entities and can't find one to free,
424 		// override the normal minimum times before use
425 		e = &g_entities[MAX_CLIENTS];
426 		for ( i = MAX_CLIENTS ; i < level.num_entities ; i++, e++ ) {
427 			if ( e->inuse ) {
428 				continue;
429 			}
430 
431 			// the first couple seconds of server time can involve a lot of
432 			// freeing and allocating, so relax the replacement policy
433 			if ( !force && e->freetime > level.startTime + 2000 && level.time - e->freetime < 1000 ) {
434 				continue;
435 			}
436 
437 			// reuse this slot
438 			G_InitGentity( e );
439 			return e;
440 		}
441 		if ( level.num_entities < ENTITYNUM_MAX_NORMAL ) {
442 			break;
443 		}
444 	}
445 	if ( level.num_entities == ENTITYNUM_MAX_NORMAL ) {
446 		for ( i = 0; i < MAX_GENTITIES; i++ ) {
447 			G_Printf( "%4i: %s\n", i, g_entities[i].classname );
448 		}
449 		G_Error( "G_Spawn: no free entities" );
450 	}
451 
452 	// open up a new slot
453 	level.num_entities++;
454 
455 	// let the server system know that there are more entities
456 	trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ),
457 						 &level.clients[0].ps, sizeof( level.clients[0] ) );
458 
459 	G_InitGentity( e );
460 	return e;
461 }
462 
463 /*
464 =================
465 G_EntitiesFree
466 =================
467 */
G_EntitiesFree(void)468 qboolean G_EntitiesFree( void ) {
469 	int i;
470 	gentity_t   *e;
471 
472 	if ( level.num_entities < ENTITYNUM_MAX_NORMAL ) {
473 		// can open a new slot if needed
474 		return qtrue;
475 	}
476 
477 	e = &g_entities[MAX_CLIENTS];
478 	for ( i = MAX_CLIENTS; i < level.num_entities; i++, e++ ) {
479 		if ( e->inuse ) {
480 			continue;
481 		}
482 		// slot available
483 		return qtrue;
484 	}
485 	return qfalse;
486 }
487 
488 
489 /*
490 =================
491 G_FreeEntity
492 
493 Marks the entity as free
494 =================
495 */
G_FreeEntity(gentity_t * ed)496 void G_FreeEntity( gentity_t *ed ) {
497 	trap_UnlinkEntity( ed );     // unlink from world
498 
499 	if ( ed->neverFree ) {
500 		return;
501 	}
502 
503 	memset( ed, 0, sizeof( *ed ) );
504 	ed->classname = "freed";
505 	ed->freetime = level.time;
506 	ed->inuse = qfalse;
507 }
508 
509 /*
510 =================
511 G_TempEntity
512 
513 Spawns an event entity that will be auto-removed
514 The origin will be snapped to save net bandwidth, so care
515 must be taken if the origin is right on a surface (snap towards start vector first)
516 =================
517 */
G_TempEntity(vec3_t origin,int event)518 gentity_t *G_TempEntity( vec3_t origin, int event ) {
519 	gentity_t       *e;
520 	vec3_t snapped;
521 
522 	e = G_Spawn();
523 	e->s.eType = ET_EVENTS + event;
524 
525 	e->classname = "tempEntity";
526 	e->eventTime = level.time;
527 	e->r.eventTime = level.time;
528 	e->freeAfterEvent = qtrue;
529 
530 	VectorCopy( origin, snapped );
531 	SnapVector( snapped );      // save network bandwidth
532 	G_SetOrigin( e, snapped );
533 
534 	// find cluster for PVS
535 	trap_LinkEntity( e );
536 
537 	return e;
538 }
539 
540 
541 
542 /*
543 ==============================================================================
544 
545 Kill box
546 
547 ==============================================================================
548 */
549 
550 /*
551 =================
552 G_KillBox
553 
554 Kills all entities that would touch the proposed new positioning
555 of ent.  Ent should be unlinked before calling this!
556 =================
557 */
G_KillBox(gentity_t * ent)558 void G_KillBox( gentity_t *ent ) {
559 	int i, num;
560 	int touch[MAX_GENTITIES];
561 	gentity_t   *hit;
562 	vec3_t mins, maxs;
563 
564 	VectorAdd( ent->client->ps.origin, ent->r.mins, mins );
565 	VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs );
566 	num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
567 
568 	for ( i = 0 ; i < num ; i++ ) {
569 		hit = &g_entities[touch[i]];
570 		if ( !hit->client ) {
571 			continue;
572 		}
573 		if ( !hit->r.linked ) { // RF, inactive AI shouldn't be gibbed
574 			continue;
575 		}
576 
577 		// nail it
578 		G_Damage( hit, ent, ent, NULL, NULL,
579 				  100000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG );
580 	}
581 
582 }
583 
584 //==============================================================================
585 
586 /*
587 ===============
588 G_AddPredictableEvent
589 
590 Use for non-pmove events that would also be predicted on the
591 client side: jumppads and item pickups
592 Adds an event+parm and twiddles the event counter
593 ===============
594 */
G_AddPredictableEvent(gentity_t * ent,int event,int eventParm)595 void G_AddPredictableEvent( gentity_t *ent, int event, int eventParm ) {
596 	if ( !ent->client ) {
597 		return;
598 	}
599 	BG_AddPredictableEventToPlayerstate( event, eventParm, &ent->client->ps );
600 }
601 
602 
603 /*
604 ===============
605 G_AddEvent
606 
607 Adds an event+parm and twiddles the event counter
608 ===============
609 */
G_AddEvent(gentity_t * ent,int event,int eventParm)610 void G_AddEvent( gentity_t *ent, int event, int eventParm ) {
611 //	int		bits;
612 
613 	if ( !event ) {
614 		G_Printf( "G_AddEvent: zero event added for entity %i\n", ent->s.number );
615 		return;
616 	}
617 
618 	// Ridah, use the sequential event list
619 	if ( ent->client ) {
620 		// NERVE - SMF - commented in - externalEvents not being handled properly in Wolf right now
621 		ent->client->ps.events[ent->client->ps.eventSequence & ( MAX_EVENTS - 1 )] = event;
622 		ent->client->ps.eventParms[ent->client->ps.eventSequence & ( MAX_EVENTS - 1 )] = eventParm;
623 		ent->client->ps.eventSequence++;
624 		// -NERVE - SMF
625 
626 		// NERVE - SMF - commented out
627 //		bits = ent->client->ps.externalEvent & EV_EVENT_BITS;
628 //		bits = ( bits + EV_EVENT_BIT1 ) & EV_EVENT_BITS;
629 //		ent->client->ps.externalEvent = event | bits;
630 //		ent->client->ps.externalEventParm = eventParm;
631 //		ent->client->ps.externalEventTime = level.time;
632 		// -NERVE - SMF
633 	} else {
634 		// NERVE - SMF - commented in - externalEvents not being handled properly in Wolf right now
635 		ent->s.events[ent->s.eventSequence & ( MAX_EVENTS - 1 )] = event;
636 		ent->s.eventParms[ent->s.eventSequence & ( MAX_EVENTS - 1 )] = eventParm;
637 		ent->s.eventSequence++;
638 		// -NERVE - SMF
639 
640 		// NERVE - SMF - commented out
641 //		bits = ent->s.event & EV_EVENT_BITS;
642 //		bits = ( bits + EV_EVENT_BIT1 ) & EV_EVENT_BITS;
643 //		ent->s.event = event | bits;
644 //		ent->s.eventParm = eventParm;
645 		// -NERVE - SMF
646 	}
647 	ent->eventTime = level.time;
648 	ent->r.eventTime = level.time;
649 }
650 
651 
652 /*
653 =============
654 G_Sound
655 
656   Ridah, removed channel parm, since it wasn't used, and could cause confusion
657 =============
658 */
G_Sound(gentity_t * ent,int soundIndex)659 void G_Sound( gentity_t *ent, int soundIndex ) {
660 	gentity_t   *te;
661 
662 	te = G_TempEntity( ent->r.currentOrigin, EV_GENERAL_SOUND );
663 	te->s.eventParm = soundIndex;
664 }
665 
666 /*
667 =============
668 G_AnimScriptSound
669 =============
670 */
G_AnimScriptSound(int soundIndex,vec3_t org,int client)671 void G_AnimScriptSound( int soundIndex, vec3_t org, int client ) {
672 	gentity_t *e;
673 	e = &g_entities[client];
674 	G_AddEvent( e, EV_GENERAL_SOUND, soundIndex );
675 	AICast_RecordScriptSound( client );
676 }
677 
678 //==============================================================================
679 
680 
681 /*
682 ================
683 G_SetOrigin
684 
685 Sets the pos trajectory for a fixed position
686 ================
687 */
G_SetOrigin(gentity_t * ent,vec3_t origin)688 void G_SetOrigin( gentity_t *ent, vec3_t origin ) {
689 	VectorCopy( origin, ent->s.pos.trBase );
690 	ent->s.pos.trType = TR_STATIONARY;
691 	ent->s.pos.trTime = 0;
692 	ent->s.pos.trDuration = 0;
693 	VectorClear( ent->s.pos.trDelta );
694 
695 	VectorCopy( origin, ent->r.currentOrigin );
696 }
697 
698 
699 /*
700 ==============
701 G_SetOrigin
702 ==============
703 */
G_SetAngle(gentity_t * ent,vec3_t angle)704 void G_SetAngle( gentity_t *ent, vec3_t angle ) {
705 
706 	VectorCopy( angle, ent->s.apos.trBase );
707 	ent->s.apos.trType = TR_STATIONARY;
708 	ent->s.apos.trTime = 0;
709 	ent->s.apos.trDuration = 0;
710 	VectorClear( ent->s.apos.trDelta );
711 
712 	VectorCopy( angle, ent->r.currentAngles );
713 
714 //	VectorCopy (ent->s.angles, ent->s.apos.trDelta );
715 
716 }
717 
718 /*
719 ====================
720 infront
721 ====================
722 */
723 
infront(gentity_t * self,gentity_t * other)724 qboolean infront( gentity_t *self, gentity_t *other ) {
725 	vec3_t vec;
726 	float dot;
727 	vec3_t forward;
728 
729 	AngleVectors( self->s.angles, forward, NULL, NULL );
730 	VectorSubtract( other->r.currentOrigin, self->r.currentOrigin, vec );
731 	VectorNormalize( vec );
732 	dot = DotProduct( vec, forward );
733 	// G_Printf( "other %5.2f\n",	dot);
734 	if ( dot > 0.0 ) {
735 		return qtrue;
736 	}
737 	return qfalse;
738 }
739 
740 //RF, tag connections
741 /*
742 ==================
743 G_ProcessTagConnect
744 ==================
745 */
G_ProcessTagConnect(gentity_t * ent)746 void G_ProcessTagConnect( gentity_t *ent ) {
747 	if ( !ent->tagName ) {
748 		G_Error( "G_ProcessTagConnect: NULL ent->tagName\n" );
749 	}
750 	if ( !ent->tagParent ) {
751 		G_Error( "G_ProcessTagConnect: NULL ent->tagParent\n" );
752 	}
753 	G_FindConfigstringIndex( va( "%i %i %s", ent->s.number, ent->tagParent->s.number, ent->tagName ), CS_TAGCONNECTS, MAX_TAGCONNECTS, qtrue );
754 	ent->s.eFlags |= EF_TAGCONNECT;
755 
756 	// clear out the angles so it always starts out facing the tag direction
757 	VectorClear( ent->s.angles );
758 	VectorCopy( ent->s.angles, ent->s.apos.trBase );
759 	ent->s.apos.trTime = level.time;
760 	ent->s.apos.trDuration = 0;
761 	ent->s.apos.trType = TR_STATIONARY;
762 	VectorClear( ent->s.apos.trDelta );
763 	VectorClear( ent->r.currentAngles );
764 }
765 
766 /*
767 ================
768 DebugLine
769 
770   debug polygons only work when running a local game
771   with r_debugSurface set to 2
772 ================
773 */
DebugLine(vec3_t start,vec3_t end,int color)774 int DebugLine( vec3_t start, vec3_t end, int color ) {
775 	vec3_t points[4], dir, cross, up = {0, 0, 1};
776 	float dot;
777 
778 	VectorCopy( start, points[0] );
779 	VectorCopy( start, points[1] );
780 	//points[1][2] -= 2;
781 	VectorCopy( end, points[2] );
782 	//points[2][2] -= 2;
783 	VectorCopy( end, points[3] );
784 
785 
786 	VectorSubtract( end, start, dir );
787 	VectorNormalize( dir );
788 	dot = DotProduct( dir, up );
789 	if ( dot > 0.99 || dot < -0.99 ) {
790 		VectorSet( cross, 1, 0, 0 );
791 	} else { CrossProduct( dir, up, cross );}
792 
793 	VectorNormalize( cross );
794 
795 	VectorMA( points[0], 2, cross, points[0] );
796 	VectorMA( points[1], -2, cross, points[1] );
797 	VectorMA( points[2], -2, cross, points[2] );
798 	VectorMA( points[3], 2, cross, points[3] );
799 
800 	return trap_DebugPolygonCreate( color, 4, points );
801 }
802