1 /*
2 ===========================================================================
3 
4 Return to Castle Wolfenstein single player 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 single player GPL Source Code (“RTCW SP Source Code”).
8 
9 RTCW SP 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 SP 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 SP Source Code.  If not, see <http://www.gnu.org/licenses/>.
21 
22 In addition, the RTCW SP 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 SP 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 	e->eventTime = 0;
398 	e->freeAfterEvent = qfalse;
399 	e->neverFree = qfalse;
400 
401 	// RF, init scripting
402 	e->scriptStatus.scriptEventIndex = -1;
403 }
404 
405 /*
406 =================
407 G_Spawn
408 
409 Either finds a free entity, or allocates a new one.
410 
411   The slots from 0 to MAX_CLIENTS-1 are always reserved for clients, and will
412 never be used by anything else.
413 
414 Try to avoid reusing an entity that was recently freed, because it
415 can cause the client to think the entity morphed into something else
416 instead of being removed and recreated, which can cause interpolated
417 angles and bad trails.
418 =================
419 */
G_Spawn(void)420 gentity_t *G_Spawn( void ) {
421 	int i, force;
422 	gentity_t   *e;
423 
424 	e = NULL;   // shut up warning
425 	for ( force = 0 ; force < 2 ; force++ ) {
426 		// if we go through all entities and can't find one to free,
427 		// override the normal minimum times before use
428 		e = &g_entities[MAX_CLIENTS];
429 		for ( i = MAX_CLIENTS ; i < level.num_entities ; i++, e++ ) {
430 			if ( e->inuse ) {
431 				continue;
432 			}
433 
434 			// the first couple seconds of server time can involve a lot of
435 			// freeing and allocating, so relax the replacement policy
436 			if ( !force && e->freetime > level.startTime + 2000 && level.time - e->freetime < 1000 ) {
437 				continue;
438 			}
439 
440 			// reuse this slot
441 			G_InitGentity( e );
442 			return e;
443 		}
444 		if ( level.num_entities < ENTITYNUM_MAX_NORMAL ) {
445 			break;
446 		}
447 	}
448 	if ( level.num_entities == ENTITYNUM_MAX_NORMAL ) {
449 		for ( i = 0; i < MAX_GENTITIES; i++ ) {
450 			G_Printf( "%4i: %s\n", i, g_entities[i].classname );
451 		}
452 		G_Error( "G_Spawn: no free entities" );
453 	}
454 
455 	// open up a new slot
456 	level.num_entities++;
457 
458 	// let the server system know that there are more entities
459 	trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ),
460 						 &level.clients[0].ps, sizeof( level.clients[0] ) );
461 
462 	G_InitGentity( e );
463 	return e;
464 }
465 
466 /*
467 =================
468 G_EntitiesFree
469 =================
470 */
G_EntitiesFree(void)471 qboolean G_EntitiesFree( void ) {
472 	int i;
473 	gentity_t   *e;
474 
475 	if ( level.num_entities < ENTITYNUM_MAX_NORMAL ) {
476 		// can open a new slot if needed
477 		return qtrue;
478 	}
479 
480 	e = &g_entities[MAX_CLIENTS];
481 	for ( i = MAX_CLIENTS; i < level.num_entities; i++, e++ ) {
482 		if ( e->inuse ) {
483 			continue;
484 		}
485 		// slot available
486 		return qtrue;
487 	}
488 	return qfalse;
489 }
490 
491 
492 /*
493 =================
494 G_FreeEntity
495 
496 Marks the entity as free
497 =================
498 */
G_FreeEntity(gentity_t * ed)499 void G_FreeEntity( gentity_t *ed ) {
500 	trap_UnlinkEntity( ed );     // unlink from world
501 
502 	if ( ed->neverFree ) {
503 		return;
504 	}
505 
506 	memset( ed, 0, sizeof( *ed ) );
507 	ed->classname = "freed";
508 	ed->freetime = level.time;
509 	ed->inuse = qfalse;
510 }
511 
512 /*
513 =================
514 G_TempEntity
515 
516 Spawns an event entity that will be auto-removed
517 The origin will be snapped to save net bandwidth, so care
518 must be taken if the origin is right on a surface (snap towards start vector first)
519 =================
520 */
G_TempEntity(vec3_t origin,int event)521 gentity_t *G_TempEntity( vec3_t origin, int event ) {
522 	gentity_t       *e;
523 	vec3_t snapped;
524 
525 	e = G_Spawn();
526 	e->s.eType = ET_EVENTS + event;
527 
528 	e->classname = "tempEntity";
529 	e->eventTime = level.time;
530 	e->r.eventTime = level.time;
531 	e->freeAfterEvent = qtrue;
532 
533 	VectorCopy( origin, snapped );
534 	SnapVector( snapped );      // save network bandwidth
535 	G_SetOrigin( e, snapped );
536 
537 	// find cluster for PVS
538 	trap_LinkEntity( e );
539 
540 	return e;
541 }
542 
543 
544 
545 /*
546 ==============================================================================
547 
548 Kill box
549 
550 ==============================================================================
551 */
552 
553 /*
554 =================
555 G_KillBox
556 
557 Kills all entities that would touch the proposed new positioning
558 of ent.  Ent should be unlinked before calling this!
559 =================
560 */
G_KillBox(gentity_t * ent)561 void G_KillBox( gentity_t *ent ) {
562 	int i, num;
563 	int touch[MAX_GENTITIES];
564 	gentity_t   *hit;
565 	vec3_t mins, maxs;
566 
567 	VectorAdd( ent->client->ps.origin, ent->r.mins, mins );
568 	VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs );
569 	num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
570 
571 	for ( i = 0 ; i < num ; i++ ) {
572 		hit = &g_entities[touch[i]];
573 		if ( !hit->client ) {
574 			continue;
575 		}
576 		if ( !hit->r.linked ) { // RF, inactive AI shouldn't be gibbed
577 			continue;
578 		}
579 
580 		// nail it
581 		G_Damage( hit, ent, ent, NULL, NULL,
582 				  100000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG );
583 	}
584 
585 }
586 
587 //==============================================================================
588 
589 /*
590 ===============
591 G_AddPredictableEvent
592 
593 Use for non-pmove events that would also be predicted on the
594 client side: jumppads and item pickups
595 Adds an event+parm and twiddles the event counter
596 ===============
597 */
G_AddPredictableEvent(gentity_t * ent,int event,int eventParm)598 void G_AddPredictableEvent( gentity_t *ent, int event, int eventParm ) {
599 	if ( !ent->client ) {
600 		return;
601 	}
602 	BG_AddPredictableEventToPlayerstate( event, eventParm, &ent->client->ps );
603 }
604 
605 
606 /*
607 ===============
608 G_AddEvent
609 
610 Adds an event+parm and twiddles the event counter
611 ===============
612 */
G_AddEvent(gentity_t * ent,int event,int eventParm)613 void G_AddEvent( gentity_t *ent, int event, int eventParm ) {
614 //	int		bits;
615 
616 	if ( !event ) {
617 		G_Printf( "G_AddEvent: zero event added for entity %i\n", ent->s.number );
618 		return;
619 	}
620 
621 	// Ridah, use the sequential event list
622 	if ( ent->client ) {
623 		// NERVE - SMF - commented in - externalEvents not being handled properly in Wolf right now
624 		ent->client->ps.events[ent->client->ps.eventSequence & ( MAX_EVENTS - 1 )] = event;
625 		ent->client->ps.eventParms[ent->client->ps.eventSequence & ( MAX_EVENTS - 1 )] = eventParm;
626 		ent->client->ps.eventSequence++;
627 		// -NERVE - SMF
628 
629 		// NERVE - SMF - commented out
630 //		bits = ent->client->ps.externalEvent & EV_EVENT_BITS;
631 //		bits = ( bits + EV_EVENT_BIT1 ) & EV_EVENT_BITS;
632 //		ent->client->ps.externalEvent = event | bits;
633 //		ent->client->ps.externalEventParm = eventParm;
634 //		ent->client->ps.externalEventTime = level.time;
635 		// -NERVE - SMF
636 	} else {
637 		// NERVE - SMF - commented in - externalEvents not being handled properly in Wolf right now
638 		ent->s.events[ent->s.eventSequence & ( MAX_EVENTS - 1 )] = event;
639 		ent->s.eventParms[ent->s.eventSequence & ( MAX_EVENTS - 1 )] = eventParm;
640 		ent->s.eventSequence++;
641 		// -NERVE - SMF
642 
643 		// NERVE - SMF - commented out
644 //		bits = ent->s.event & EV_EVENT_BITS;
645 //		bits = ( bits + EV_EVENT_BIT1 ) & EV_EVENT_BITS;
646 //		ent->s.event = event | bits;
647 //		ent->s.eventParm = eventParm;
648 		// -NERVE - SMF
649 	}
650 	ent->eventTime = level.time;
651 	ent->r.eventTime = level.time;
652 }
653 
654 
655 /*
656 =============
657 G_Sound
658 
659   Ridah, removed channel parm, since it wasn't used, and could cause confusion
660 =============
661 */
G_Sound(gentity_t * ent,int soundIndex)662 void G_Sound( gentity_t *ent, int soundIndex ) {
663 	gentity_t   *te;
664 
665 	te = G_TempEntity( ent->r.currentOrigin, EV_GENERAL_SOUND );
666 	te->s.eventParm = soundIndex;
667 }
668 
669 /*
670 =============
671 G_AnimScriptSound
672 =============
673 */
G_AnimScriptSound(int soundIndex,vec3_t org,int client)674 void G_AnimScriptSound( int soundIndex, vec3_t org, int client ) {
675 	gentity_t *e;
676 	e = &g_entities[client];
677 	G_AddEvent( e, EV_GENERAL_SOUND, soundIndex );
678 	AICast_RecordScriptSound( client );
679 }
680 
681 //==============================================================================
682 
683 
684 /*
685 ================
686 G_SetOrigin
687 
688 Sets the pos trajectory for a fixed position
689 ================
690 */
G_SetOrigin(gentity_t * ent,vec3_t origin)691 void G_SetOrigin( gentity_t *ent, vec3_t origin ) {
692 	VectorCopy( origin, ent->s.pos.trBase );
693 	ent->s.pos.trType = TR_STATIONARY;
694 	ent->s.pos.trTime = 0;
695 	ent->s.pos.trDuration = 0;
696 	VectorClear( ent->s.pos.trDelta );
697 
698 	VectorCopy( origin, ent->r.currentOrigin );
699 }
700 
701 
702 /*
703 ==============
704 G_SetOrigin
705 ==============
706 */
G_SetAngle(gentity_t * ent,vec3_t angle)707 void G_SetAngle( gentity_t *ent, vec3_t angle ) {
708 
709 	VectorCopy( angle, ent->s.apos.trBase );
710 	ent->s.apos.trType = TR_STATIONARY;
711 	ent->s.apos.trTime = 0;
712 	ent->s.apos.trDuration = 0;
713 	VectorClear( ent->s.apos.trDelta );
714 
715 	VectorCopy( angle, ent->r.currentAngles );
716 
717 //	VectorCopy (ent->s.angles, ent->s.apos.trDelta );
718 
719 }
720 
721 /*
722 ====================
723 infront
724 ====================
725 */
726 
infront(gentity_t * self,gentity_t * other)727 qboolean infront( gentity_t *self, gentity_t *other ) {
728 	vec3_t vec;
729 	float dot;
730 	vec3_t forward, otherOrigin;
731 
732 	if ( self->client ) {
733 		AngleVectors( self->client->ps.viewangles, forward, NULL, NULL );
734 	} else {
735 		AngleVectors( self->s.angles, forward, NULL, NULL );
736 	}
737 
738 
739 	if ( self->activateArc ) {
740 		// move the origin of the 'other' up/down so that it matches the 'self' so the check is along a horizontal plane
741 		VectorCopy( other->r.currentOrigin, otherOrigin );
742 		otherOrigin[2] = self->r.currentOrigin[2];
743 		VectorSubtract( otherOrigin, self->r.currentOrigin, vec );
744 	} else {
745 		VectorSubtract( other->r.currentOrigin, self->r.currentOrigin, vec );
746 	}
747 
748 	VectorNormalize( vec );
749 	dot = DotProduct( vec, forward );
750 	// G_Printf( "other %5.2f\n",	dot);
751 
752 	if ( !other->aiCharacter && self->activateArc ) {  //----(SA)	make sure ai's aren't constrained to the grabarc of an mg42
753 		float angle;
754 		angle = RAD2DEG( M_PI - acos( dot ) );
755 		if ( angle < ( self->activateArc * 2.0 ) ) { // arc is 'half arc' since that's the way the other angles in the mg42 were done
756 			return qfalse;
757 		} else {
758 			return qtrue;
759 		}
760 	}
761 
762 	if ( dot > 0.0 ) {
763 		return qtrue;
764 	}
765 	return qfalse;
766 }
767 
768 //RF, tag connections
769 /*
770 ==================
771 G_ProcessTagConnect
772 ==================
773 */
G_ProcessTagConnect(gentity_t * ent,qboolean clearAngles)774 void G_ProcessTagConnect( gentity_t *ent, qboolean clearAngles ) {
775 	if ( !ent->tagName ) {
776 		G_Error( "G_ProcessTagConnect: NULL ent->tagName\n" );
777 	}
778 	if ( !ent->tagParent ) {
779 		G_Error( "G_ProcessTagConnect: NULL ent->tagParent\n" );
780 	}
781 	G_FindConfigstringIndex( va( "%i %i %s", ent->s.number, ent->tagParent->s.number, ent->tagName ), CS_TAGCONNECTS, MAX_TAGCONNECTS, qtrue );
782 	ent->s.eFlags |= EF_TAGCONNECT;
783 
784 	if ( clearAngles ) {
785 		// clear out the angles so it always starts out facing the tag direction
786 		VectorClear( ent->s.angles );
787 		VectorCopy( ent->s.angles, ent->s.apos.trBase );
788 		ent->s.apos.trTime = level.time;
789 		ent->s.apos.trDuration = 0;
790 		ent->s.apos.trType = TR_STATIONARY;
791 		VectorClear( ent->s.apos.trDelta );
792 		VectorClear( ent->r.currentAngles );
793 	}
794 }
795 
796 /*
797 ================
798 DebugLine
799 
800   debug polygons only work when running a local game
801   with r_debugSurface set to 2
802 ================
803 */
DebugLine(vec3_t start,vec3_t end,int color)804 int DebugLine( vec3_t start, vec3_t end, int color ) {
805 	vec3_t points[4], dir, cross, up = {0, 0, 1};
806 	float dot;
807 
808 	VectorCopy( start, points[0] );
809 	VectorCopy( start, points[1] );
810 	//points[1][2] -= 2;
811 	VectorCopy( end, points[2] );
812 	//points[2][2] -= 2;
813 	VectorCopy( end, points[3] );
814 
815 
816 	VectorSubtract( end, start, dir );
817 	VectorNormalize( dir );
818 	dot = DotProduct( dir, up );
819 	if ( dot > 0.99 || dot < -0.99 ) {
820 		VectorSet( cross, 1, 0, 0 );
821 	} else { CrossProduct( dir, up, cross );}
822 
823 	VectorNormalize( cross );
824 
825 	VectorMA( points[0], 2, cross, points[0] );
826 	VectorMA( points[1], -2, cross, points[1] );
827 	VectorMA( points[2], -2, cross, points[2] );
828 	VectorMA( points[3], 2, cross, points[3] );
829 
830 	return trap_DebugPolygonCreate( color, 4, points );
831 }
832