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