1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2013 - 2015, OpenJK contributors
7
8 This file is part of the OpenJK source code.
9
10 OpenJK is free software; you can redistribute it and/or modify it
11 under the terms of the GNU General Public License version 2 as
12 published by the Free Software Foundation.
13
14 This program 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 this program; if not, see <http://www.gnu.org/licenses/>.
21 ===========================================================================
22 */
23
24 // g_utils.c -- misc utility functions for game module
25
26 #include "../cgame/cg_local.h"
27 #include "Q3_Interface.h"
28 #include "g_local.h"
29 #include "g_functions.h"
30 #include "g_navigator.h"
31 #include "b_local.h"
32 #include "g_nav.h"
33
34 #define ACT_ACTIVE qtrue
35 #define ACT_INACTIVE qfalse
36 extern void NPC_UseResponse ( gentity_t *self, gentity_t *user, qboolean useWhenDone );
37 extern qboolean PM_CrouchAnim( int anim );
38 /*
39 =========================================================================
40
41 model / sound configstring indexes
42
43 =========================================================================
44 */
45
46 /*
47 ================
48 G_FindConfigstringIndex
49
50 ================
51 */
52 extern void ForceTelepathy( gentity_t *self );
G_FindConfigstringIndex(const char * name,int start,int max,qboolean create)53 int G_FindConfigstringIndex( const char *name, int start, int max, qboolean create ) {
54 int i;
55 char s[MAX_STRING_CHARS];
56
57 if ( !name || !name[0] ) {
58 return 0;
59 }
60
61 for ( i=1 ; i<max ; i++ ) {
62 gi.GetConfigstring( start + i, s, sizeof( s ) );
63 if ( !s[0] ) {
64 break;
65 }
66 if ( !Q_stricmp( s, name ) ) {
67 return i;
68 }
69 }
70
71 if ( !create ) {
72 return 0;
73 }
74
75 if ( i == max ) {
76 G_Error( "G_FindConfigstringIndex: overflow adding %s to set %d-%d", name, start, max );
77 }
78
79 gi.SetConfigstring( start + i, name );
80
81 return i;
82 }
83 /*
84 Ghoul2 Insert Start
85 */
86
G_SkinIndex(const char * name)87 int G_SkinIndex( const char *name ) {
88 return G_FindConfigstringIndex (name, CS_CHARSKINS, MAX_CHARSKINS, qtrue);
89 }
90
91 /*
92 Ghoul2 Insert End
93 */
94
G_ModelIndex(const char * name)95 int G_ModelIndex( const char *name ) {
96 return G_FindConfigstringIndex (name, CS_MODELS, MAX_MODELS, qtrue);
97 }
98
G_SoundIndex(const char * name)99 int G_SoundIndex( const char *name ) {
100 assert( name && name[0] );
101 char stripped[MAX_QPATH];
102 COM_StripExtension(name, stripped, sizeof(stripped));
103
104 return G_FindConfigstringIndex (stripped, CS_SOUNDS, MAX_SOUNDS, qtrue);
105 }
106
G_EffectIndex(const char * name)107 int G_EffectIndex( const char *name )
108 {
109 char temp[MAX_QPATH];
110
111 // We just don't want extensions on the things we are registering
112 COM_StripExtension( name, temp, sizeof(temp) );
113
114 return G_FindConfigstringIndex( temp, CS_EFFECTS, MAX_FX, qtrue );
115 }
116
G_BSPIndex(char * name)117 int G_BSPIndex( char *name )
118 {
119 return G_FindConfigstringIndex (name, CS_BSP_MODELS, MAX_SUB_BSP, qtrue);
120 }
121
122 #define FX_ENT_RADIUS 32
123
124 //-----------------------------
125 // Effect playing utilities
126 //-----------------------------
127
128 //-----------------------------
G_PlayEffect(int fxID,const vec3_t origin,const vec3_t fwd)129 void G_PlayEffect( int fxID, const vec3_t origin, const vec3_t fwd )
130 {
131 gentity_t *tent;
132 vec3_t temp;
133
134 tent = G_TempEntity( origin, EV_PLAY_EFFECT );
135 tent->s.eventParm = fxID;
136
137 VectorSet( tent->maxs, FX_ENT_RADIUS, FX_ENT_RADIUS, FX_ENT_RADIUS );
138 VectorScale( tent->maxs, -1, tent->mins );
139
140 VectorCopy( fwd, tent->pos3 );
141
142 // Assume angles, we'll do a cross product on the other end to finish up
143 MakeNormalVectors( fwd, tent->pos4, temp );
144 gi.linkentity( tent );
145 }
146
147 // Play an effect at the origin of the specified entity
148 //----------------------------
G_PlayEffect(int fxID,int entNum,const vec3_t fwd)149 void G_PlayEffect( int fxID, int entNum, const vec3_t fwd )
150 {
151 gentity_t *tent;
152 vec3_t temp;
153
154 tent = G_TempEntity( g_entities[entNum].currentOrigin, EV_PLAY_EFFECT );
155 tent->s.eventParm = fxID;
156 tent->s.otherEntityNum = entNum;
157 VectorSet( tent->maxs, FX_ENT_RADIUS, FX_ENT_RADIUS, FX_ENT_RADIUS );
158 VectorScale( tent->maxs, -1, tent->mins );
159 VectorCopy( fwd, tent->pos3 );
160
161 // Assume angles, we'll do a cross product on the other end to finish up
162 MakeNormalVectors( fwd, tent->pos4, temp );
163 }
164
165 // Play an effect bolted onto the muzzle of the specified client
166 //----------------------------
G_PlayEffect(const char * name,int clientNum)167 void G_PlayEffect( const char *name, int clientNum )
168 {
169 gentity_t *tent;
170
171 tent = G_TempEntity( g_entities[clientNum].currentOrigin, EV_PLAY_MUZZLE_EFFECT );
172 tent->s.eventParm = G_EffectIndex( name );
173 tent->s.otherEntityNum = clientNum;
174 VectorSet( tent->maxs, FX_ENT_RADIUS, FX_ENT_RADIUS, FX_ENT_RADIUS );
175 VectorScale( tent->maxs, -1, tent->mins );
176 }
177
178 //-----------------------------
G_PlayEffect(int fxID,const vec3_t origin,const vec3_t axis[3])179 void G_PlayEffect( int fxID, const vec3_t origin, const vec3_t axis[3] )
180 {
181 gentity_t *tent;
182
183 tent = G_TempEntity( origin, EV_PLAY_EFFECT );
184 tent->s.eventParm = fxID;
185
186 VectorSet( tent->maxs, FX_ENT_RADIUS, FX_ENT_RADIUS, FX_ENT_RADIUS );
187 VectorScale( tent->maxs, -1, tent->mins );
188
189 // We can just assume axis[2] from doing a cross product on these.
190 VectorCopy( axis[0], tent->pos3 );
191 VectorCopy( axis[1], tent->pos4 );
192 }
193
194 // Effect playing utilities - bolt an effect to a ghoul2 models bolton point
195 //-----------------------------
G_PlayEffect(int fxID,const int modelIndex,const int boltIndex,const int entNum,const vec3_t origin,int iLoopTime,qboolean isRelative)196 void G_PlayEffect( int fxID, const int modelIndex, const int boltIndex, const int entNum, const vec3_t origin, int iLoopTime, qboolean isRelative )//iLoopTime 0 = not looping, 1 for infinite, else duration
197 {
198 gentity_t *tent;
199
200 tent = G_TempEntity( origin, EV_PLAY_EFFECT );
201 tent->s.eventParm = fxID;
202
203 tent->s.loopSound = iLoopTime;
204 tent->s.weapon = isRelative;
205
206 tent->svFlags |=SVF_BROADCAST;
207 gi.G2API_AttachEnt(&tent->s.boltInfo, &g_entities[entNum].ghoul2[modelIndex], boltIndex, entNum, modelIndex);
208 }
209
210 //-----------------------------
G_PlayEffect(const char * name,const vec3_t origin)211 void G_PlayEffect( const char *name, const vec3_t origin )
212 {
213 vec3_t up = {0, 0, 1};
214
215 G_PlayEffect( G_EffectIndex( name ), origin, up );
216 }
217
G_PlayEffect(int fxID,const vec3_t origin)218 void G_PlayEffect( int fxID, const vec3_t origin )
219 {
220 vec3_t up = {0, 0, 1};
221
222 G_PlayEffect( fxID, origin, up );
223 }
224
225 //-----------------------------
G_PlayEffect(const char * name,const vec3_t origin,const vec3_t fwd)226 void G_PlayEffect( const char *name, const vec3_t origin, const vec3_t fwd )
227 {
228 G_PlayEffect( G_EffectIndex( name ), origin, fwd );
229 }
230
231 //-----------------------------
G_PlayEffect(const char * name,const vec3_t origin,const vec3_t axis[3])232 void G_PlayEffect( const char *name, const vec3_t origin, const vec3_t axis[3] )
233 {
234 G_PlayEffect( G_EffectIndex( name ), origin, axis );
235 }
236
G_StopEffect(int fxID,const int modelIndex,const int boltIndex,const int entNum)237 void G_StopEffect( int fxID, const int modelIndex, const int boltIndex, const int entNum )
238 {
239 gentity_t *tent;
240
241 tent = G_TempEntity( g_entities[entNum].currentOrigin, EV_STOP_EFFECT );
242 tent->s.eventParm = fxID;
243 tent->svFlags |= SVF_BROADCAST;
244 gi.G2API_AttachEnt( &tent->s.boltInfo, &g_entities[entNum].ghoul2[modelIndex], boltIndex, entNum, modelIndex );
245 }
246
G_StopEffect(const char * name,const int modelIndex,const int boltIndex,const int entNum)247 void G_StopEffect(const char *name, const int modelIndex, const int boltIndex, const int entNum )
248 {
249 G_StopEffect( G_EffectIndex( name ), modelIndex, boltIndex, entNum );
250 }
251
252 //===Bypass network for sounds on specific channels====================
253
254 extern void cgi_S_StartSound( const vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx );
255 #include "../cgame/cg_media.h" //access to cgs
256 extern qboolean CG_TryPlayCustomSound( vec3_t origin, int entityNum, soundChannel_t channel, const char *soundName, int customSoundSet );
257 extern cvar_t *g_timescale;
258 //NOTE: Do NOT Try to use this before the cgame DLL is valid, it will NOT work!
G_SoundOnEnt(gentity_t * ent,soundChannel_t channel,const char * soundPath)259 void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath )
260 {
261 int index = G_SoundIndex( (char *)soundPath );
262
263 if ( !ent )
264 {
265 return;
266 }
267 if ( g_timescale->integer > 50 )
268 {//Skip the sound!
269 return;
270 }
271
272 cgi_S_UpdateEntityPosition( ent->s.number, ent->currentOrigin );
273 if ( cgs.sound_precache[ index ] )
274 {
275 cgi_S_StartSound( NULL, ent->s.number, channel, cgs.sound_precache[ index ] );
276 }
277 else
278 {
279 CG_TryPlayCustomSound( NULL, ent->s.number, channel, soundPath, -1 );
280 }
281 }
282
G_SoundIndexOnEnt(gentity_t * ent,soundChannel_t channel,int index)283 void G_SoundIndexOnEnt( gentity_t *ent, soundChannel_t channel, int index )
284 {
285 if ( !ent )
286 {
287 return;
288 }
289
290 cgi_S_UpdateEntityPosition( ent->s.number, ent->currentOrigin );
291 if ( cgs.sound_precache[ index ] )
292 {
293 cgi_S_StartSound( NULL, ent->s.number, channel, cgs.sound_precache[ index ] );
294 }
295 }
296
297 extern cvar_t *g_skippingcin;
G_SpeechEvent(gentity_t * self,int event)298 void G_SpeechEvent( gentity_t *self, int event )
299 {
300 if ( in_camera
301 && g_skippingcin
302 && g_skippingcin->integer )
303 {//Skipping a cinematic, so skip NPC voice sounds...
304 return;
305 }
306 //update entity pos, too
307 cgi_S_UpdateEntityPosition( self->s.number, self->currentOrigin );
308 switch ( event )
309 {
310 case EV_ANGER1: //Say when acquire an enemy when didn't have one before
311 case EV_ANGER2:
312 case EV_ANGER3:
313 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*anger%i.wav", event - EV_ANGER1 + 1), CS_COMBAT );
314 break;
315 case EV_VICTORY1: //Say when killed an enemy
316 case EV_VICTORY2:
317 case EV_VICTORY3:
318 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*victory%i.wav", event - EV_VICTORY1 + 1), CS_COMBAT );
319 break;
320 case EV_CONFUSE1: //Say when confused
321 case EV_CONFUSE2:
322 case EV_CONFUSE3:
323 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*confuse%i.wav", event - EV_CONFUSE1 + 1), CS_COMBAT );
324 break;
325 case EV_PUSHED1: //Say when pushed
326 case EV_PUSHED2:
327 case EV_PUSHED3:
328 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*pushed%i.wav", event - EV_PUSHED1 + 1), CS_COMBAT );
329 break;
330 case EV_CHOKE1: //Say when choking
331 case EV_CHOKE2:
332 case EV_CHOKE3:
333 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*choke%i.wav", event - EV_CHOKE1 + 1), CS_COMBAT );
334 break;
335 case EV_FFWARN: //Warn ally to stop shooting you
336 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, "*ffwarn.wav", CS_COMBAT );
337 break;
338 case EV_FFTURN: //Turn on ally after being shot by them
339 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, "*ffturn.wav", CS_COMBAT );
340 break;
341 //extra sounds for ST
342 case EV_CHASE1:
343 case EV_CHASE2:
344 case EV_CHASE3:
345 if ( !CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*chase%i.wav", event - EV_CHASE1 + 1), CS_EXTRA ) )
346 {
347 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*anger%i.wav", Q_irand(1,3)), CS_COMBAT );
348 }
349 break;
350 case EV_COVER1:
351 case EV_COVER2:
352 case EV_COVER3:
353 case EV_COVER4:
354 case EV_COVER5:
355 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*cover%i.wav", event - EV_COVER1 + 1), CS_EXTRA );
356 break;
357 case EV_DETECTED1:
358 case EV_DETECTED2:
359 case EV_DETECTED3:
360 case EV_DETECTED4:
361 case EV_DETECTED5:
362 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*detected%i.wav", event - EV_DETECTED1 + 1), CS_EXTRA );
363 break;
364 case EV_GIVEUP1:
365 case EV_GIVEUP2:
366 case EV_GIVEUP3:
367 case EV_GIVEUP4:
368 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*giveup%i.wav", event - EV_GIVEUP1 + 1), CS_EXTRA );
369 break;
370 case EV_LOOK1:
371 case EV_LOOK2:
372 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*look%i.wav", event - EV_LOOK1 + 1), CS_EXTRA );
373 break;
374 case EV_LOST1:
375 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, "*lost1.wav", CS_EXTRA );
376 break;
377 case EV_OUTFLANK1:
378 case EV_OUTFLANK2:
379 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*outflank%i.wav", event - EV_OUTFLANK1 + 1), CS_EXTRA );
380 break;
381 case EV_ESCAPING1:
382 case EV_ESCAPING2:
383 case EV_ESCAPING3:
384 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*escaping%i.wav", event - EV_ESCAPING1 + 1), CS_EXTRA );
385 break;
386 case EV_SIGHT1:
387 case EV_SIGHT2:
388 case EV_SIGHT3:
389 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*sight%i.wav", event - EV_SIGHT1 + 1), CS_EXTRA );
390 break;
391 case EV_SOUND1:
392 case EV_SOUND2:
393 case EV_SOUND3:
394 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*sound%i.wav", event - EV_SOUND1 + 1), CS_EXTRA );
395 break;
396 case EV_SUSPICIOUS1:
397 case EV_SUSPICIOUS2:
398 case EV_SUSPICIOUS3:
399 case EV_SUSPICIOUS4:
400 case EV_SUSPICIOUS5:
401 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*suspicious%i.wav", event - EV_SUSPICIOUS1 + 1), CS_EXTRA );
402 break;
403 //extra sounds for Jedi
404 case EV_COMBAT1:
405 case EV_COMBAT2:
406 case EV_COMBAT3:
407 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*combat%i.wav", event - EV_COMBAT1 + 1), CS_JEDI );
408 break;
409 case EV_JDETECTED1:
410 case EV_JDETECTED2:
411 case EV_JDETECTED3:
412 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*jdetected%i.wav", event - EV_JDETECTED1 + 1), CS_JEDI );
413 break;
414 case EV_TAUNT1:
415 case EV_TAUNT2:
416 case EV_TAUNT3:
417 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*taunt%i.wav", event - EV_TAUNT1 + 1), CS_JEDI );
418 break;
419 case EV_JCHASE1:
420 case EV_JCHASE2:
421 case EV_JCHASE3:
422 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*jchase%i.wav", event - EV_JCHASE1 + 1), CS_JEDI );
423 break;
424 case EV_JLOST1:
425 case EV_JLOST2:
426 case EV_JLOST3:
427 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*jlost%i.wav", event - EV_JLOST1 + 1), CS_JEDI );
428 break;
429 case EV_DEFLECT1:
430 case EV_DEFLECT2:
431 case EV_DEFLECT3:
432 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*deflect%i.wav", event - EV_DEFLECT1 + 1), CS_JEDI );
433 break;
434 case EV_GLOAT1:
435 case EV_GLOAT2:
436 case EV_GLOAT3:
437 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*gloat%i.wav", event - EV_GLOAT1 + 1), CS_JEDI );
438 break;
439 case EV_PUSHFAIL:
440 CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, "*pushfail.wav", CS_JEDI );
441 break;
442 }
443 }
444 //=====================================================================
445
446
447
448 /*
449 =============
450 G_Find
451
452 Searches all active entities for the next one that holds
453 the matching string at fieldofs (use the FOFS() macro) in the structure.
454
455 Searches beginning at the entity after from, or the beginning if NULL
456 NULL will be returned if the end of the list is reached.
457
458 =============
459 */
G_Find(gentity_t * from,int fieldofs,const char * match)460 gentity_t *G_Find (gentity_t *from, int fieldofs, const char *match)
461 {
462 char *s;
463
464 if(!match || !match[0])
465 {
466 return NULL;
467 }
468
469 if (!from)
470 from = g_entities;
471 else
472 from++;
473
474 // for ( ; from < &g_entities[globals.num_entities] ; from++)
475 int i=from-g_entities;
476 for ( ; i < globals.num_entities ; i++)
477 {
478 // if (!from->inuse)
479 if(!PInUse(i))
480 continue;
481
482 from=&g_entities[i];
483 s = *(char **) ((byte *)from + fieldofs);
484 if (!s)
485 continue;
486 if (!Q_stricmp (s, match))
487 return from;
488 }
489
490 return NULL;
491 }
492
493
494 /*
495 ============
496 G_RadiusList - given an origin and a radius, return all entities that are in use that are within the list
497 ============
498 */
G_RadiusList(vec3_t origin,float radius,gentity_t * ignore,qboolean takeDamage,gentity_t * ent_list[MAX_GENTITIES])499 int G_RadiusList ( vec3_t origin, float radius, gentity_t *ignore, qboolean takeDamage, gentity_t *ent_list[MAX_GENTITIES])
500 {
501 float dist;
502 gentity_t *ent;
503 gentity_t *entityList[MAX_GENTITIES];
504 int numListedEntities;
505 vec3_t mins, maxs;
506 vec3_t v;
507 int i, e;
508 int ent_count = 0;
509
510 if ( radius < 1 )
511 {
512 radius = 1;
513 }
514
515 for ( i = 0 ; i < 3 ; i++ )
516 {
517 mins[i] = origin[i] - radius;
518 maxs[i] = origin[i] + radius;
519 }
520
521 numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
522 radius *= radius; //square for the length squared below
523 for ( e = 0 ; e < numListedEntities ; e++ )
524 {
525 ent = entityList[ e ];
526
527 if ((ent == ignore) || !(ent->inuse) || ent->takedamage != takeDamage)
528 continue;
529
530 // find the distance from the edge of the bounding box
531 for ( i = 0 ; i < 3 ; i++ )
532 {
533 if ( origin[i] < ent->absmin[i] )
534 {
535 v[i] = ent->absmin[i] - origin[i];
536 } else if ( origin[i] > ent->absmax[i] )
537 {
538 v[i] = origin[i] - ent->absmax[i];
539 } else
540 {
541 v[i] = 0;
542 }
543 }
544
545 dist = VectorLengthSquared( v );
546 if ( dist >= radius )
547 {
548 continue;
549 }
550
551 // ok, we are within the radius, add us to the incoming list
552 ent_list[ent_count] = ent;
553 ent_count++;
554
555 }
556 // we are done, return how many we found
557 return(ent_count);
558 }
559
560
561 /*
562 =============
563 G_PickTarget
564
565 Selects a random entity from among the targets
566 =============
567 */
568 #define MAXCHOICES 32
569
G_PickTarget(char * targetname)570 gentity_t *G_PickTarget (char *targetname)
571 {
572 gentity_t *ent = NULL;
573 int num_choices = 0;
574 gentity_t *choice[MAXCHOICES];
575
576 if (!targetname)
577 {
578 gi.Printf("G_PickTarget called with NULL targetname\n");
579 return NULL;
580 }
581
582 while(1)
583 {
584 ent = G_Find (ent, FOFS(targetname), targetname);
585 if (!ent)
586 break;
587 choice[num_choices++] = ent;
588 if (num_choices == MAXCHOICES)
589 break;
590 }
591
592 if (!num_choices)
593 {
594 gi.Printf("G_PickTarget: target %s not found\n", targetname);
595 return NULL;
596 }
597
598 return choice[rand() % num_choices];
599 }
600
G_UseTargets2(gentity_t * ent,gentity_t * activator,const char * string)601 void G_UseTargets2 (gentity_t *ent, gentity_t *activator, const char *string)
602 {
603 gentity_t *t;
604
605 //
606 // fire targets
607 //
608 if (string)
609 {
610 if( !Q_stricmp( string, "self") )
611 {
612 t = ent;
613 if (t->e_UseFunc != useF_NULL) // check can be omitted
614 {
615 GEntity_UseFunc(t, ent, activator);
616 }
617
618 if (!ent->inuse)
619 {
620 gi.Printf("entity was removed while using targets\n");
621 return;
622 }
623 }
624 else
625 {
626 t = NULL;
627 while ( (t = G_Find (t, FOFS(targetname), (char *) string)) != NULL )
628 {
629 if (t == ent)
630 {
631 // gi.Printf ("WARNING: Entity used itself.\n");
632 }
633 if (t->e_UseFunc != useF_NULL) // check can be omitted
634 {
635 GEntity_UseFunc(t, ent, activator);
636 }
637
638 if (!ent->inuse)
639 {
640 gi.Printf("entity was removed while using targets\n");
641 return;
642 }
643 }
644 }
645 }
646 }
647
648 /*
649 ==============================
650 G_UseTargets
651
652 "activator" should be set to the entity that initiated the firing.
653
654 Search for (string)targetname in all entities that
655 match (string)self.target and call their .use function
656
657 ==============================
658 */
G_UseTargets(gentity_t * ent,gentity_t * activator)659 void G_UseTargets (gentity_t *ent, gentity_t *activator)
660 {
661 //
662 // fire targets
663 //
664 G_UseTargets2 (ent, activator, ent->target);
665 }
666
667 /*
668 =============
669 VectorToString
670
671 This is just a convenience function
672 for printing vectors
673 =============
674 */
vtos(const vec3_t v)675 char *vtos( const vec3_t v ) {
676 static int index;
677 static char str[8][32];
678 char *s;
679
680 // use an array so that multiple vtos won't collide
681 s = str[index];
682 index = (index + 1)&7;
683
684 Com_sprintf (s, 32, "(%4.2f %4.2f %4.2f)", v[0], v[1], v[2]);
685
686 return s;
687 }
688
689
690 /*
691 ===============
692 G_SetMovedir
693
694 The editor only specifies a single value for angles (yaw),
695 but we have special constants to generate an up or down direction.
696 Angles will be cleared, because it is being used to represent a direction
697 instead of an orientation.
698 ===============
699 */
G_SetMovedir(vec3_t angles,vec3_t movedir)700 void G_SetMovedir( vec3_t angles, vec3_t movedir ) {
701 static vec3_t VEC_UP = {0, -1, 0};
702 static vec3_t MOVEDIR_UP = {0, 0, 1};
703 static vec3_t VEC_DOWN = {0, -2, 0};
704 static vec3_t MOVEDIR_DOWN = {0, 0, -1};
705
706 if ( VectorCompare (angles, VEC_UP) ) {
707 VectorCopy (MOVEDIR_UP, movedir);
708 } else if ( VectorCompare (angles, VEC_DOWN) ) {
709 VectorCopy (MOVEDIR_DOWN, movedir);
710 } else {
711 AngleVectors (angles, movedir, NULL, NULL);
712 }
713 VectorClear( angles );
714 }
715
716
vectoyaw(const vec3_t vec)717 float vectoyaw( const vec3_t vec ) {
718 float yaw;
719
720 if (vec[YAW] == 0 && vec[PITCH] == 0) {
721 yaw = 0;
722 } else {
723 if (vec[PITCH]) {
724 yaw = ( atan2( vec[YAW], vec[PITCH]) * 180 / M_PI );
725 } else if (vec[YAW] > 0) {
726 yaw = 90;
727 } else {
728 yaw = 270;
729 }
730 if (yaw < 0) {
731 yaw += 360;
732 }
733 }
734
735 return yaw;
736 }
737
738
G_InitGentity(gentity_t * e,qboolean bFreeG2)739 void G_InitGentity( gentity_t *e, qboolean bFreeG2 )
740 {
741 e->inuse = qtrue;
742 SetInUse(e);
743 e->m_iIcarusID = IIcarusInterface::ICARUS_INVALID;
744 e->classname = "noclass";
745 e->s.number = e - g_entities;
746
747 // remove any ghoul2 models here in case we're reusing
748 if (bFreeG2 && e->ghoul2.IsValid())
749 {
750 gi.G2API_CleanGhoul2Models(e->ghoul2);
751 }
752 //Navigational setups
753 e->waypoint = WAYPOINT_NONE;
754 e->lastWaypoint = WAYPOINT_NONE;
755 }
756
757 /*
758 =================
759 G_Spawn
760
761 Either finds a free entity, or allocates a new one.
762
763 The slots from 0 to MAX_CLIENTS-1 are always reserved for clients, and will
764 never be used by anything else.
765
766 Try to avoid reusing an entity that was recently freed, because it
767 can cause the client to think the entity morphed into something else
768 instead of being removed and recreated, which can cause interpolated
769 angles and bad trails.
770 =================
771 */
G_Spawn(void)772 gentity_t *G_Spawn( void )
773 {
774 int i, force;
775 gentity_t *e;
776
777 e = NULL; // shut up warning
778 i = 0; // shut up warning
779 for ( force = 0 ; force < 2 ; force++ )
780 {
781 // if we go through all entities and can't find one to free,
782 // override the normal minimum times before use
783 e = &g_entities[MAX_CLIENTS];
784 // for ( i = MAX_CLIENTS ; i<globals.num_entities ; i++, e++)
785 // {
786 // if ( e->inuse )
787 // {
788 // continue;
789 // }
790 for ( i = MAX_CLIENTS ; i<globals.num_entities ; i++)
791 {
792 if(PInUse(i))
793 {
794 continue;
795 }
796 e=&g_entities[i];
797
798 // the first couple seconds of server time can involve a lot of
799 // freeing and allocating, so relax the replacement policy
800 if ( !force && e->freetime > 2000 && level.time - e->freetime < 1000 ) {
801 continue;
802 }
803
804 // reuse this slot
805 G_InitGentity( e, qtrue );
806 return e;
807 }
808 e=&g_entities[i];
809 if ( i != ENTITYNUM_MAX_NORMAL )
810 {
811 break;
812 }
813 }
814 if ( i == ENTITYNUM_MAX_NORMAL )
815 {
816
817 //#ifndef FINAL_BUILD
818 e = &g_entities[0];
819
820 //--------------Use this to dump directly to a file
821 char buff[256];
822 FILE *fp;
823
824 fp = fopen( "c:/nofreeentities.txt", "w" );
825 for ( i = 0 ; i<globals.num_entities ; i++, e++)
826 {
827 if ( e->classname )
828 {
829 sprintf( buff, "%d: %s\n", i, e->classname );
830 }
831
832 fputs( buff, fp );
833 }
834 fclose( fp );
835 /*
836 //---------------Or use this to dump to the console -- beware though, the console will fill quickly and you probably won't see the full list
837 for ( i = 0 ; i<globals.num_entities ; i++, e++)
838 {
839 if ( e->classname )
840 {
841 Com_Printf( "%d: %s\n", i, e->classname );
842
843 }
844 }
845 */
846 //FINAL_BUILD
847 G_Error( "G_Spawn: no free entities" );
848 }
849
850 // open up a new slot
851 globals.num_entities++;
852 G_InitGentity( e, qtrue );
853 return e;
854 }
855
856 extern void Vehicle_Remove(gentity_t *ent);
857
858 /*
859 =================
860 G_FreeEntity
861
862 Marks the entity as free
863 =================
864 */
G_FreeEntity(gentity_t * ed)865 void G_FreeEntity( gentity_t *ed ) {
866 gi.unlinkentity (ed); // unlink from world
867
868 // Free the Game Element (the entity) and delete the Icarus ID.
869 Quake3Game()->FreeEntity( ed );
870
871 /*if ( ed->neverFree ) {
872 return;
873 }*/
874
875 if (ed->wayedge!=0)
876 {
877 NAV::WayEdgesNowClear(ed);
878 }
879
880
881 // remove any ghoul2 models here
882 gi.G2API_CleanGhoul2Models(ed->ghoul2);
883
884 if (ed->client && ed->client->NPC_class == CLASS_VEHICLE)
885 {
886 Vehicle_Remove(ed);
887
888 if ( ed->m_pVehicle )
889 {
890 gi.Free( ed->m_pVehicle );
891 }
892 //CVehicleNPC *pVeh = static_cast< CVehicleNPC * >( ed->NPC );
893 //delete pVeh;
894 //gi.Free((char*)ed->NPC-4);//crazy hack for class vtables
895 }
896
897 //free this stuff now, rather than waiting until the level ends.
898 if (ed->NPC)
899 {
900 gi.Free(ed->NPC);
901
902 if(ed->client->clientInfo.customBasicSoundDir && gi.bIsFromZone(ed->client->clientInfo.customBasicSoundDir, TAG_G_ALLOC)) {
903 gi.Free(ed->client->clientInfo.customBasicSoundDir);
904 }
905 if(ed->client->clientInfo.customCombatSoundDir) {
906 gi.Free(ed->client->clientInfo.customCombatSoundDir);
907 }
908 if(ed->client->clientInfo.customExtraSoundDir) {
909 gi.Free(ed->client->clientInfo.customExtraSoundDir);
910 }
911 if(ed->client->clientInfo.customJediSoundDir) {
912 gi.Free(ed->client->clientInfo.customJediSoundDir);
913 }
914 if(ed->client->ps.saber[0].name && gi.bIsFromZone(ed->client->ps.saber[0].name, TAG_G_ALLOC) ) {
915 gi.Free(ed->client->ps.saber[0].name);
916 }
917 if(ed->client->ps.saber[0].model && gi.bIsFromZone(ed->client->ps.saber[0].model, TAG_G_ALLOC) ) {
918 gi.Free(ed->client->ps.saber[0].model);
919 }
920 if(ed->client->ps.saber[1].name && gi.bIsFromZone(ed->client->ps.saber[1].name, TAG_G_ALLOC) ) {
921 gi.Free(ed->client->ps.saber[1].name);
922 }
923 if(ed->client->ps.saber[1].model && gi.bIsFromZone(ed->client->ps.saber[1].model, TAG_G_ALLOC) ) {
924 gi.Free(ed->client->ps.saber[1].model);
925 }
926
927 gi.Free(ed->client);
928 }
929
930 if (ed->soundSet && gi.bIsFromZone(ed->soundSet, TAG_G_ALLOC)) {
931 gi.Free(ed->soundSet);
932 }
933 if (ed->targetname && gi.bIsFromZone(ed->targetname, TAG_G_ALLOC)) {
934 gi.Free(ed->targetname);
935 }
936 if (ed->NPC_targetname && gi.bIsFromZone(ed->NPC_targetname, TAG_G_ALLOC)) {
937 gi.Free(ed->NPC_targetname);
938 }
939 if (ed->NPC_type && gi.bIsFromZone(ed->NPC_type, TAG_G_ALLOC)) {
940 gi.Free(ed->NPC_type);
941 }
942 if (ed->classname && gi.bIsFromZone(ed->classname, TAG_G_ALLOC)) {
943 gi.Free(ed->classname );
944 }
945 if (ed->message && gi.bIsFromZone(ed->message, TAG_G_ALLOC)) {
946 gi.Free(ed->message);
947 }
948 if (ed->model && gi.bIsFromZone(ed->model, TAG_G_ALLOC)) {
949 gi.Free(ed->model);
950 }
951
952 //scripting
953 if (ed->script_targetname && gi.bIsFromZone(ed->script_targetname, TAG_G_ALLOC)) {
954 gi.Free(ed->script_targetname);
955 }
956 if (ed->cameraGroup && gi.bIsFromZone(ed->cameraGroup, TAG_G_ALLOC)) {
957 gi.Free(ed->cameraGroup);
958 }
959 if (ed->paintarget && gi.bIsFromZone(ed->paintarget, TAG_G_ALLOC)) {
960 gi.Free(ed->paintarget);
961 }
962 if(ed->parms) {
963 gi.Free(ed->parms);
964 }
965
966 //Limbs
967 if (ed->target && gi.bIsFromZone(ed->target , TAG_G_ALLOC)) {
968 gi.Free(ed->target);
969 }
970 if (ed->target2 && gi.bIsFromZone(ed->target2 , TAG_G_ALLOC)) {
971 gi.Free(ed->target2);
972 }
973 if (ed->target3 && gi.bIsFromZone(ed->target3 , TAG_G_ALLOC)) {
974 gi.Free(ed->target3);
975 }
976 if (ed->target4 && gi.bIsFromZone(ed->target4 , TAG_G_ALLOC)) {
977 gi.Free(ed->target4);
978 }
979 if (ed->opentarget) {
980 gi.Free(ed->opentarget);
981 }
982 if (ed->closetarget) {
983 gi.Free(ed->closetarget);
984 }
985 // Free any associated timers
986 TIMER_Clear(ed->s.number);
987
988 memset (ed, 0, sizeof(*ed));
989 ed->s.number = ENTITYNUM_NONE;
990 ed->classname = "freed";
991 ed->freetime = level.time;
992 ed->inuse = qfalse;
993 ClearInUse(ed);
994 }
995
996 /*
997 =================
998 G_TempEntity
999
1000 Spawns an event entity that will be auto-removed
1001 The origin will be snapped to save net bandwidth, so care
1002 must be taken if the origin is right on a surface (snap towards start vector first)
1003 =================
1004 */
G_TempEntity(const vec3_t origin,int event)1005 gentity_t *G_TempEntity( const vec3_t origin, int event ) {
1006 gentity_t *e;
1007 vec3_t snapped;
1008
1009 e = G_Spawn();
1010 e->s.eType = ET_EVENTS + event;
1011
1012 e->classname = "tempEntity";
1013 e->eventTime = level.time;
1014 e->freeAfterEvent = qtrue;
1015
1016 VectorCopy( origin, snapped );
1017 SnapVector( snapped ); // save network bandwidth
1018 G_SetOrigin( e, snapped );
1019
1020 // find cluster for PVS
1021 gi.linkentity( e );
1022
1023 return e;
1024 }
1025
1026
1027
1028 /*
1029 ==============================================================================
1030
1031 Kill box
1032
1033 ==============================================================================
1034 */
1035
1036 /*
1037 =================
1038 G_KillBox
1039
1040 Kills all entities that would touch the proposed new positioning
1041 of ent. Ent should be unlinked before calling this!
1042 =================
1043 */
G_KillBox(gentity_t * ent)1044 void G_KillBox (gentity_t *ent) {
1045 int i, num;
1046 gentity_t *touch[MAX_GENTITIES], *hit;
1047 vec3_t mins, maxs;
1048
1049 VectorAdd( ent->client->ps.origin, ent->mins, mins );
1050 VectorAdd( ent->client->ps.origin, ent->maxs, maxs );
1051 num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
1052
1053 for (i=0 ; i<num ; i++) {
1054 hit = touch[i];
1055 if ( !hit->client ) {
1056 continue;
1057 }
1058 if ( hit == ent ) {
1059 continue;
1060 }
1061 if ( ent->s.number && hit->client->ps.stats[STAT_HEALTH] <= 0 )
1062 {//NPC
1063 continue;
1064 }
1065 if ( ent->s.number )
1066 {//NPC
1067 if ( !(hit->contents&CONTENTS_BODY) )
1068 {
1069 continue;
1070 }
1071 }
1072 else
1073 {//player
1074 if ( !(hit->contents & ent->contents) )
1075 {
1076 continue;
1077 }
1078 }
1079
1080 // nail it
1081 G_Damage ( hit, ent, ent, NULL, NULL,
1082 100000, DAMAGE_NO_PROTECTION, MOD_UNKNOWN);
1083 }
1084
1085 }
1086
1087 //==============================================================================
1088
1089
1090 /*
1091 ===============
1092 G_AddEvent
1093
1094 Adds an event+parm and twiddles the event counter
1095 ===============
1096 */
G_AddEvent(gentity_t * ent,int event,int eventParm)1097 void G_AddEvent( gentity_t *ent, int event, int eventParm ) {
1098 int bits;
1099
1100 if ( !event ) {
1101 gi.Printf( "G_AddEvent: zero event added for entity %i\n", ent->s.number );
1102 return;
1103 }
1104
1105 #if 0 // FIXME: allow multiple events on an entity
1106 // if the entity has an event that hasn't expired yet, don't overwrite
1107 // it unless it is identical (repeated footsteps / muzzleflashes / etc )
1108 if ( ent->s.event && ent->s.event != event ) {
1109 gentity_t *temp;
1110
1111 // generate a temp entity that references the original entity
1112 gi.Printf( "eventPush\n" );
1113
1114 temp = G_Spawn();
1115 temp->s.eType = ET_EVENT_ONLY;
1116 temp->s.otherEntityNum = ent->s.number;
1117 G_SetOrigin( temp, ent->s.origin );
1118 G_AddEvent( temp, event, eventParm );
1119 temp->freeAfterEvent = qtrue;
1120 gi.linkentity( temp );
1121 return;
1122 }
1123 #endif
1124
1125 // clients need to add the event in playerState_t instead of entityState_t
1126 if ( !ent->s.number ) //only one client
1127 {
1128 #if 0
1129 bits = ent->client->ps.externalEvent & EV_EVENT_BITS;
1130 bits = ( bits + EV_EVENT_BIT1 ) & EV_EVENT_BITS;
1131 ent->client->ps.externalEvent = event | bits;
1132 ent->client->ps.externalEventParm = eventParm;
1133 ent->client->ps.externalEventTime = level.time;
1134 #endif
1135 if ( eventParm > 255 )
1136 {
1137 if ( event == EV_PAIN )
1138 {//must have cheated, in undying?
1139 eventParm = 255;
1140 }
1141 else
1142 {
1143 assert( eventParm < 256 );
1144 }
1145 }
1146 AddEventToPlayerstate( event, eventParm, &ent->client->ps );
1147 } else {
1148 bits = ent->s.event & EV_EVENT_BITS;
1149 bits = ( bits + EV_EVENT_BIT1 ) & EV_EVENT_BITS;
1150 ent->s.event = event | bits;
1151 ent->s.eventParm = eventParm;
1152 }
1153 ent->eventTime = level.time;
1154 }
1155
1156
1157 /*
1158 =============
1159 G_Sound
1160 =============
1161 */
G_Sound(gentity_t * ent,int soundIndex)1162 void G_Sound( gentity_t *ent, int soundIndex )
1163 {
1164 gentity_t *te;
1165
1166 te = G_TempEntity( ent->currentOrigin, EV_GENERAL_SOUND );
1167 te->s.eventParm = soundIndex;
1168 }
1169
1170 /*
1171 =============
1172 G_Sound
1173 =============
1174 */
G_SoundAtSpot(vec3_t org,int soundIndex,qboolean broadcast)1175 void G_SoundAtSpot( vec3_t org, int soundIndex, qboolean broadcast )
1176 {
1177 gentity_t *te;
1178
1179 te = G_TempEntity( org, EV_GENERAL_SOUND );
1180 te->s.eventParm = soundIndex;
1181 if ( broadcast )
1182 {
1183 te->svFlags |= SVF_BROADCAST;
1184 }
1185 }
1186
1187 /*
1188 =============
1189 G_SoundBroadcast
1190
1191 Plays sound that can permeate PVS blockage
1192 =============
1193 */
G_SoundBroadcast(gentity_t * ent,int soundIndex)1194 void G_SoundBroadcast( gentity_t *ent, int soundIndex )
1195 {
1196 gentity_t *te;
1197
1198 te = G_TempEntity( ent->currentOrigin, EV_GLOBAL_SOUND ); //full volume
1199 te->s.eventParm = soundIndex;
1200 te->svFlags |= SVF_BROADCAST;
1201 }
1202
1203 //==============================================================================
1204
1205 /*
1206 ================
1207 G_SetOrigin
1208
1209 Sets the pos trajectory for a fixed position
1210 ================
1211 */
G_SetOrigin(gentity_t * ent,const vec3_t origin)1212 void G_SetOrigin( gentity_t *ent, const vec3_t origin )
1213 {
1214 VectorCopy( origin, ent->s.pos.trBase );
1215 if(ent->client)
1216 {
1217 VectorCopy( origin, ent->client->ps.origin );
1218 VectorCopy( origin, ent->s.origin );
1219 }
1220 else
1221 {
1222 ent->s.pos.trType = TR_STATIONARY;
1223 }
1224 ent->s.pos.trTime = 0;
1225 ent->s.pos.trDuration = 0;
1226 VectorClear( ent->s.pos.trDelta );
1227
1228 VectorCopy( origin, ent->currentOrigin );
1229
1230 // clear waypoints
1231 if( ent->client && ent->NPC )
1232 {
1233 ent->waypoint = 0;
1234 ent->lastWaypoint = 0;
1235 if( NAV::HasPath( ent ) )
1236 {
1237 NAV::ClearPath( ent );
1238 }
1239 }
1240
1241 }
1242
G_CheckInSolidTeleport(const vec3_t & teleportPos,gentity_t * self)1243 qboolean G_CheckInSolidTeleport (const vec3_t& teleportPos, gentity_t *self)
1244 {
1245 trace_t trace;
1246 vec3_t end, mins;
1247
1248 VectorCopy(teleportPos, end);
1249 end[2] += self->mins[2];
1250 VectorCopy(self->mins, mins);
1251 mins[2] = 0;
1252
1253 gi.trace(&trace, teleportPos, mins, self->maxs, end, self->s.number, self->clipmask, (EG2_Collision)0, 0);
1254 if(trace.allsolid || trace.startsolid)
1255 {
1256 return qtrue;
1257 }
1258 return qfalse;
1259 }
1260
1261 //===============================================================================
G_CheckInSolid(gentity_t * self,qboolean fix)1262 qboolean G_CheckInSolid (gentity_t *self, qboolean fix)
1263 {
1264 trace_t trace;
1265 vec3_t end, mins;
1266
1267 VectorCopy(self->currentOrigin, end);
1268 end[2] += self->mins[2];
1269 VectorCopy(self->mins, mins);
1270 mins[2] = 0;
1271
1272 gi.trace(&trace, self->currentOrigin, mins, self->maxs, end, self->s.number, self->clipmask, (EG2_Collision)0, 0);
1273 if(trace.allsolid || trace.startsolid)
1274 {
1275 return qtrue;
1276 }
1277
1278 #ifdef _DEBUG
1279 if(trace.fraction < 0.99999713)
1280 #else
1281 if(trace.fraction < 1.0)
1282 #endif
1283 {
1284 if(fix)
1285 {//Put them at end of trace and check again
1286 vec3_t neworg;
1287
1288 VectorCopy(trace.endpos, neworg);
1289 neworg[2] -= self->mins[2];
1290 G_SetOrigin(self, neworg);
1291 gi.linkentity(self);
1292
1293 return G_CheckInSolid(self, qfalse);
1294 }
1295 else
1296 {
1297 return qtrue;
1298 }
1299 }
1300
1301 return qfalse;
1302 }
1303
infront(gentity_t * from,gentity_t * to)1304 qboolean infront(gentity_t *from, gentity_t *to)
1305 {
1306 vec3_t angles, dir, forward;
1307 float dot;
1308
1309 angles[PITCH] = angles[ROLL] = 0;
1310 angles[YAW] = from->s.angles[YAW];
1311 AngleVectors(angles, forward, NULL, NULL);
1312
1313 VectorSubtract(to->s.origin, from->s.origin, dir);
1314 VectorNormalize(dir);
1315
1316 dot = DotProduct(forward, dir);
1317 if(dot < 0.0f)
1318 {
1319 return qfalse;
1320 }
1321
1322 return qtrue;
1323 }
1324
Svcmd_Use_f(void)1325 void Svcmd_Use_f( void )
1326 {
1327 const char *cmd1 = gi.argv(1);
1328
1329 if ( !cmd1 || !cmd1[0] )
1330 {
1331 //FIXME: warning message
1332 gi.Printf( "'use' takes targetname of ent or 'list' (lists all usable ents)\n" );
1333 return;
1334 }
1335 else if ( !Q_stricmp("list", cmd1) )
1336 {
1337 gentity_t *ent;
1338
1339 gi.Printf("Listing all usable entities:\n");
1340
1341 for ( int i = 1; i < ENTITYNUM_WORLD; i++ )
1342 {
1343 ent = &g_entities[i];
1344 if ( ent )
1345 {
1346 if ( ent->targetname && ent->targetname[0] )
1347 {
1348 if ( ent->e_UseFunc != useF_NULL )
1349 {
1350 if ( ent->NPC )
1351 {
1352 gi.Printf( "%s (NPC)\n", ent->targetname );
1353 }
1354 else
1355 {
1356 gi.Printf( "%s\n", ent->targetname );
1357 }
1358 }
1359 }
1360 }
1361 }
1362
1363 gi.Printf("End of list.\n");
1364 }
1365 else
1366 {
1367 G_UseTargets2( &g_entities[0], &g_entities[0], cmd1 );
1368 }
1369 }
1370
1371 //======================================================
1372
G_SetActiveState(char * targetstring,qboolean actState)1373 void G_SetActiveState(char *targetstring, qboolean actState)
1374 {
1375 gentity_t *target = NULL;
1376 while( NULL != (target = G_Find(target, FOFS(targetname), targetstring)) )
1377 {
1378 target->svFlags = actState ? (target->svFlags&~SVF_INACTIVE) : (target->svFlags|SVF_INACTIVE);
1379 }
1380 }
1381
target_activate_use(gentity_t * self,gentity_t * other,gentity_t * activator)1382 void target_activate_use(gentity_t *self, gentity_t *other, gentity_t *activator)
1383 {
1384 G_ActivateBehavior(self,BSET_USE);
1385
1386 G_SetActiveState(self->target, ACT_ACTIVE);
1387 }
1388
target_deactivate_use(gentity_t * self,gentity_t * other,gentity_t * activator)1389 void target_deactivate_use(gentity_t *self, gentity_t *other, gentity_t *activator)
1390 {
1391 G_ActivateBehavior(self,BSET_USE);
1392
1393 G_SetActiveState(self->target, ACT_INACTIVE);
1394 }
1395
1396 //FIXME: make these apply to doors, etc too?
1397 /*QUAKED target_activate (1 0 0) (-4 -4 -4) (4 4 4)
1398 Will set the target(s) to be usable/triggerable
1399 */
SP_target_activate(gentity_t * self)1400 void SP_target_activate( gentity_t *self )
1401 {
1402 G_SetOrigin( self, self->s.origin );
1403 self->e_UseFunc = useF_target_activate_use;
1404 }
1405
1406 /*QUAKED target_deactivate (1 0 0) (-4 -4 -4) (4 4 4)
1407 Will set the target(s) to be non-usable/triggerable
1408 */
SP_target_deactivate(gentity_t * self)1409 void SP_target_deactivate( gentity_t *self )
1410 {
1411 G_SetOrigin( self, self->s.origin );
1412 self->e_UseFunc = useF_target_deactivate_use;
1413 }
1414
1415
1416 //======================================================
1417
1418 /*
1419 ==============
1420 ValidUseTarget
1421
1422 Returns whether or not the targeted entity is useable
1423 ==============
1424 */
ValidUseTarget(gentity_t * ent)1425 qboolean ValidUseTarget( gentity_t *ent )
1426 {
1427 if ( ent->e_UseFunc == useF_NULL )
1428 {
1429 return qfalse;
1430 }
1431
1432 if ( ent->svFlags & SVF_INACTIVE )
1433 {//set by target_deactivate
1434 return qfalse;
1435 }
1436
1437 if ( !(ent->svFlags & SVF_PLAYER_USABLE) )
1438 {//Check for flag that denotes BUTTON_USE useability
1439 return qfalse;
1440 }
1441
1442 //FIXME: This is only a temp fix..
1443 if ( !Q_strncmp( ent->classname, "trigger", 7) )
1444 {
1445 return qfalse;
1446 }
1447
1448 return qtrue;
1449 }
1450
DebugTraceForNPC(gentity_t * ent)1451 static void DebugTraceForNPC(gentity_t *ent)
1452 {
1453 trace_t trace;
1454 vec3_t src, dest, vf;
1455
1456 VectorCopy( ent->client->renderInfo.eyePoint, src );
1457
1458 AngleVectors( ent->client->ps.viewangles, vf, NULL, NULL );//ent->client->renderInfo.eyeAngles was cg.refdef.viewangles, basically
1459 //extend to find end of use trace
1460 VectorMA( src, 4096, vf, dest );
1461
1462 //Trace ahead to find a valid target
1463 gi.trace( &trace, src, vec3_origin, vec3_origin, dest, ent->s.number, MASK_OPAQUE|CONTENTS_SOLID|CONTENTS_TERRAIN|CONTENTS_BODY|CONTENTS_ITEM|CONTENTS_CORPSE, (EG2_Collision)0, 0 );
1464
1465 if (trace.fraction < 0.99f)
1466 {
1467 gentity_t *found = &g_entities[trace.entityNum];
1468
1469 if (found)
1470 {
1471 const char *targetName = found->targetname;
1472 const char *className = found->classname;
1473
1474 if (targetName == 0)
1475 {
1476 targetName = "<NULL>";
1477 }
1478 if (className == 0)
1479 {
1480 className = "<NULL>";
1481 }
1482 Com_Printf("found targetname '%s', classname '%s'\n", targetName, className);
1483 }
1484 }
1485 }
1486
G_ValidActivateBehavior(gentity_t * self,int bset)1487 static qboolean G_ValidActivateBehavior (gentity_t* self, int bset)
1488 {
1489 if ( !self )
1490 {
1491 return qfalse;
1492 }
1493
1494 const char *bs_name = self->behaviorSet[bset];
1495
1496 if( !(VALIDSTRING( bs_name )) )
1497 {
1498 return qfalse;
1499 }
1500
1501 return qtrue;
1502 }
1503
G_IsTriggerUsable(gentity_t * self,gentity_t * other)1504 static qboolean G_IsTriggerUsable(gentity_t* self, gentity_t* other)
1505 {
1506 if ( self->svFlags & SVF_INACTIVE )
1507 {//set by target_deactivate
1508 return qfalse;
1509 }
1510
1511 if( self->noDamageTeam )
1512 {
1513 if ( other->client->playerTeam != self->noDamageTeam )
1514 {
1515 return qfalse;
1516 }
1517 }
1518
1519
1520 if ( self->spawnflags & 4 )
1521 {//USE_BUTTON
1522 if ( !other->client )
1523 {
1524 return qfalse;
1525 }
1526 }
1527 else
1528 {
1529 return qfalse;
1530 }
1531
1532 if ( self->spawnflags & 2 )
1533 {//FACING
1534 vec3_t forward;
1535
1536 if ( other->client )
1537 {
1538 AngleVectors( other->client->ps.viewangles, forward, NULL, NULL );
1539 }
1540 else
1541 {
1542 AngleVectors( other->currentAngles, forward, NULL, NULL );
1543 }
1544
1545 if ( DotProduct( self->movedir, forward ) < 0.5 )
1546 {//Not Within 45 degrees
1547 return qfalse;
1548 }
1549 }
1550
1551 if ((!G_ValidActivateBehavior (self, BSET_USE) && !self->target) ||
1552 (self->target &&
1553 (Q_stricmp(self->target, "n") == 0 ||
1554 (Q_stricmp(self->target, "neveropen") == 0 ||
1555 (Q_stricmp(self->target, "run_gran_drop") == 0) ||
1556 (Q_stricmp(self->target, "speaker") == 0) ||
1557 (Q_stricmp(self->target, "locked") == 0)
1558 ))))
1559 {
1560 return qfalse;
1561 }
1562
1563
1564 /*
1565 //NOTE: This doesn't stop you from using it, just delays the use action!
1566 if(self->delay && self->painDebounceTime < (level.time + self->delay) )
1567 {
1568 return qfalse;
1569 }
1570 */
1571
1572 return qtrue;
1573 }
1574
CanUseInfrontOfPartOfLevel(gentity_t * ent)1575 static qboolean CanUseInfrontOfPartOfLevel(gentity_t* ent ) //originally from VV
1576 {
1577 int i, num;
1578 gentity_t *touch[MAX_GENTITIES], *hit;
1579 vec3_t mins, maxs;
1580 const vec3_t range = { 40, 40, 52 };
1581
1582 if ( !ent->client ) {
1583 return qfalse;
1584 }
1585
1586 VectorSubtract( ent->client->ps.origin, range, mins );
1587 VectorAdd( ent->client->ps.origin, range, maxs );
1588
1589 num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
1590
1591 // can't use ent->absmin, because that has a one unit pad
1592 VectorAdd( ent->client->ps.origin, ent->mins, mins );
1593 VectorAdd( ent->client->ps.origin, ent->maxs, maxs );
1594
1595 for ( i=0 ; i<num ; i++ ) {
1596 hit = touch[i];
1597
1598 if ( (hit->e_TouchFunc == touchF_NULL) && (ent->e_TouchFunc == touchF_NULL) ) {
1599 continue;
1600 }
1601 if ( !( hit->contents & CONTENTS_TRIGGER ) ) {
1602 continue;
1603 }
1604
1605 if ( !gi.EntityContact( mins, maxs, hit ) ) {
1606 continue;
1607 }
1608
1609 if ( hit->e_TouchFunc != touchF_NULL ) {
1610 switch (hit->e_TouchFunc )
1611 {
1612 case touchF_Touch_Multi:
1613 if (G_IsTriggerUsable(hit, ent))
1614 {
1615 return qtrue;
1616 }
1617 continue;
1618 break;
1619 default:
1620 continue;
1621 }
1622 }
1623 }
1624 return qfalse;
1625 }
1626
1627 #define USE_DISTANCE 64.0f
1628 extern qboolean eweb_can_be_used( gentity_t *self, gentity_t *other, gentity_t *activator );
CanUseInfrontOf(gentity_t * ent)1629 qboolean CanUseInfrontOf(gentity_t *ent)
1630 {
1631 gentity_t *target;
1632 trace_t trace;
1633 vec3_t src, dest, vf;
1634
1635 if ( ent->s.number && ent->client->NPC_class == CLASS_ATST )
1636 {//a player trying to get out of his ATST
1637 // GEntity_UseFunc( ent->activator, ent, ent );
1638 return qfalse;
1639 }
1640
1641 if (ent->client->ps.viewEntity != ent->s.number)
1642 {
1643 ent = &g_entities[ent->client->ps.viewEntity];
1644
1645 if ( !Q_stricmp( "misc_camera", ent->classname ) )
1646 { // we are in a camera
1647 gentity_t *next = 0;
1648 if ( ent->target2 != NULL )
1649 {
1650 next = G_Find( NULL, FOFS(targetname), ent->target2 );
1651 }
1652 if ( next )
1653 {//found another one
1654 if ( !Q_stricmp( "misc_camera", next->classname ) )
1655 {//make sure it's another camera
1656 return qtrue;
1657 }
1658 }
1659 else //if ( ent->health > 0 )
1660 {//I was the last (only?) one, clear out the viewentity
1661 return qfalse;
1662 }
1663 }
1664 }
1665
1666 if ( !ent->client ) {
1667 return qfalse;
1668 }
1669
1670
1671 //FIXME: this does not match where the new accurate crosshair aims...
1672 //cg.refdef.vieworg, basically
1673 VectorCopy( ent->client->renderInfo.eyePoint, src );
1674
1675 AngleVectors( ent->client->ps.viewangles, vf, NULL, NULL );
1676 //extend to find end of use trace
1677 VectorMA( src, USE_DISTANCE, vf, dest );
1678
1679 //Trace ahead to find a valid target
1680 gi.trace( &trace, src, vec3_origin, vec3_origin, dest, ent->s.number, MASK_OPAQUE|CONTENTS_SOLID|CONTENTS_TERRAIN|CONTENTS_BODY|CONTENTS_ITEM|CONTENTS_CORPSE , G2_NOCOLLIDE, 10);
1681
1682 if ( trace.fraction == 1.0f || trace.entityNum >= ENTITYNUM_WORLD )
1683 {
1684 return (CanUseInfrontOfPartOfLevel(ent));
1685 }
1686
1687 target = &g_entities[trace.entityNum];
1688
1689 if ( target && target->client && target->client->NPC_class == CLASS_VEHICLE )
1690 {
1691 // Attempt to board this vehicle.
1692 return qtrue;
1693 }
1694 //Check for a use command
1695 if (ValidUseTarget( target )) {
1696 if ( target->s.eType == ET_ITEM )
1697 {//item, see if we could actually pick it up
1698 if ( (target->spawnflags&128/*ITMSF_USEPICKUP*/) )
1699 {//player has to be touching me and hit use to pick it up, so don't allow this
1700 if ( !G_BoundsOverlap( target->absmin, target->absmax, ent->absmin, ent->absmax ) )
1701 {//not touching
1702 return qfalse;
1703 }
1704 }
1705 if ( !BG_CanItemBeGrabbed( &target->s, &ent->client->ps ) )
1706 {//nope, so don't indicate that we can use it
1707 return qfalse;
1708 }
1709 }
1710 else if ( target->e_UseFunc == useF_misc_atst_use )
1711 {//drivable AT-ST from JK2
1712 if ( ent->client->ps.groundEntityNum != target->s.number )
1713 {//must be standing on it to use it
1714 return qfalse;
1715 }
1716 }
1717 else if ( target->NPC!=NULL && target->health<=0 )
1718 {
1719 return qfalse;
1720 }
1721 else if ( target->e_UseFunc == useF_eweb_use )
1722 {
1723 if ( !eweb_can_be_used( target, ent, ent ) )
1724 {
1725 return qfalse;
1726 }
1727 }
1728 return qtrue;
1729 }
1730
1731 if ( target->client
1732 && target->client->ps.pm_type < PM_DEAD
1733 && target->NPC!=NULL
1734 && target->client->playerTeam
1735 && (target->client->playerTeam == ent->client->playerTeam || target->client->playerTeam == TEAM_NEUTRAL)
1736 && !(target->NPC->scriptFlags&SCF_NO_RESPONSE)
1737 && G_ValidActivateBehavior (target, BSET_USE))
1738 {
1739 return qtrue;
1740 }
1741
1742 if (CanUseInfrontOfPartOfLevel(ent)) {
1743 return qtrue;
1744 }
1745
1746 return qfalse;
1747 }
1748
1749 /*
1750 ==============
1751 TryUse
1752
1753 Try and use an entity in the world, directly ahead of us
1754 ==============
1755 */
1756
1757
TryUse(gentity_t * ent)1758 void TryUse( gentity_t *ent )
1759 {
1760 gentity_t *target;
1761 trace_t trace;
1762 vec3_t src, dest, vf;
1763
1764 if (ent->s.number == 0 && g_npcdebug->integer == 1)
1765 {
1766 DebugTraceForNPC(ent);
1767 }
1768
1769 if ( ent->s.number == 0 && ent->client->NPC_class == CLASS_ATST )
1770 {//a player trying to get out of his ATST
1771 GEntity_UseFunc( ent->activator, ent, ent );
1772 return;
1773 }
1774
1775 // TODO: turo-boost.
1776 /* if ( ent->client->ps.vehicleIndex != VEHICLE_NONE )
1777 {//in a vehicle, use key makes you turbo-boost
1778 return;
1779 }*/
1780
1781 //FIXME: this does not match where the new accurate crosshair aims...
1782 //cg.refdef.vieworg, basically
1783 VectorCopy( ent->client->renderInfo.eyePoint, src );
1784
1785 AngleVectors( ent->client->ps.viewangles, vf, NULL, NULL );//ent->client->renderInfo.eyeAngles was cg.refdef.viewangles, basically
1786 //extend to find end of use trace
1787 VectorMA( src, USE_DISTANCE, vf, dest );
1788
1789 //Trace ahead to find a valid target
1790 gi.trace( &trace, src, vec3_origin, vec3_origin, dest, ent->s.number, MASK_OPAQUE|CONTENTS_SOLID|CONTENTS_TERRAIN|CONTENTS_BODY|CONTENTS_ITEM|CONTENTS_CORPSE , G2_NOCOLLIDE, 10);
1791
1792 if ( trace.fraction == 1.0f || trace.entityNum >= ENTITYNUM_WORLD )
1793 {
1794 //TODO: Play a failure sound
1795 /*
1796 if ( ent->s.number == 0 )
1797 {//if nothing else, try the force telepathy power
1798 ForceTelepathy( ent );
1799 }
1800 */
1801 return;
1802 }
1803
1804 target = &g_entities[trace.entityNum];
1805
1806 if ( target && target->client && target->client->NPC_class == CLASS_VEHICLE )
1807 {
1808 // Attempt to board this vehicle.
1809 target->m_pVehicle->m_pVehicleInfo->Board( target->m_pVehicle, ent );
1810
1811 return;
1812 }
1813
1814 //Check for a use command
1815 if ( ValidUseTarget( target ) )
1816 {
1817 NPC_SetAnim( ent, SETANIM_TORSO, BOTH_BUTTON_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
1818 /*
1819 if ( !VectorLengthSquared( ent->client->ps.velocity ) && !PM_CrouchAnim( ent->client->ps.legsAnim ) )
1820 {
1821 NPC_SetAnim( ent, SETANIM_LEGS, BOTH_BUTTON_HOLD, SETANIM_FLAG_NORMAL|SETANIM_FLAG_HOLD );
1822 }
1823 */
1824 //ent->client->ps.weaponTime = ent->client->ps.torsoAnimTimer;
1825 GEntity_UseFunc( target, ent, ent );
1826 return;
1827 }
1828 else if ( target->client
1829 && target->client->ps.pm_type < PM_DEAD
1830 && target->NPC!=NULL
1831 && target->client->playerTeam
1832 && (target->client->playerTeam == ent->client->playerTeam || target->client->playerTeam == TEAM_NEUTRAL)
1833 && !(target->NPC->scriptFlags&SCF_NO_RESPONSE) )
1834 {
1835 NPC_UseResponse ( target, ent, qfalse );
1836 return;
1837 }
1838 /*
1839 if ( ent->s.number == 0 )
1840 {//if nothing else, try the force telepathy power
1841 ForceTelepathy( ent );
1842 }
1843 */
1844 }
1845
1846 extern int killPlayerTimer;
G_ChangeMap(const char * mapname,const char * spawntarget,qboolean hub)1847 void G_ChangeMap (const char *mapname, const char *spawntarget, qboolean hub)
1848 {
1849 // gi.Printf("Loading...");
1850 //ignore if player is dead
1851 if (g_entities[0].client->ps.pm_type == PM_DEAD)
1852 return;
1853 if ( killPlayerTimer )
1854 {//can't go to next map if your allies have turned on you
1855 return;
1856 }
1857
1858 if (mapname[0] == '+') //fire up the menu instead
1859 {
1860 gi.SendConsoleCommand( va("uimenu %s\n", mapname+1) );
1861 gi.cvar_set("skippingCinematic", "0");
1862 gi.cvar_set("timescale", "1");
1863 return;
1864 }
1865
1866 if ( spawntarget == NULL ) {
1867 spawntarget = ""; //prevent it from becoming "(null)"
1868 }
1869 if ( hub == qtrue )
1870 {
1871 gi.SendConsoleCommand( va("loadtransition %s %s\n", mapname, spawntarget) );
1872 }
1873 else
1874 {
1875 gi.SendConsoleCommand( va("maptransition %s %s\n", mapname, spawntarget) );
1876 }
1877 }
1878
G_PointInBounds(const vec3_t point,const vec3_t mins,const vec3_t maxs)1879 qboolean G_PointInBounds( const vec3_t point, const vec3_t mins, const vec3_t maxs )
1880 {
1881 for(int i = 0; i < 3; i++ )
1882 {
1883 if ( point[i] < mins[i] )
1884 {
1885 return qfalse;
1886 }
1887 if ( point[i] > maxs[i] )
1888 {
1889 return qfalse;
1890 }
1891 }
1892
1893 return qtrue;
1894 }
1895
G_BoxInBounds(const vec3_t point,const vec3_t mins,const vec3_t maxs,const vec3_t boundsMins,const vec3_t boundsMaxs)1896 qboolean G_BoxInBounds( const vec3_t point, const vec3_t mins, const vec3_t maxs, const vec3_t boundsMins, const vec3_t boundsMaxs )
1897 {
1898 vec3_t boxMins;
1899 vec3_t boxMaxs;
1900
1901 VectorAdd( point, mins, boxMins );
1902 VectorAdd( point, maxs, boxMaxs );
1903
1904 if(boxMaxs[0]>boundsMaxs[0])
1905 return qfalse;
1906
1907 if(boxMaxs[1]>boundsMaxs[1])
1908 return qfalse;
1909
1910 if(boxMaxs[2]>boundsMaxs[2])
1911 return qfalse;
1912
1913 if(boxMins[0]<boundsMins[0])
1914 return qfalse;
1915
1916 if(boxMins[1]<boundsMins[1])
1917 return qfalse;
1918
1919 if(boxMins[2]<boundsMins[2])
1920 return qfalse;
1921
1922 //box is completely contained within bounds
1923 return qtrue;
1924 }
1925
1926
G_SetAngles(gentity_t * ent,const vec3_t angles)1927 void G_SetAngles( gentity_t *ent, const vec3_t angles )
1928 {
1929 VectorCopy( angles, ent->currentAngles );
1930 VectorCopy( angles, ent->s.angles );
1931 VectorCopy( angles, ent->s.apos.trBase );
1932 }
1933
G_ClearTrace(const vec3_t start,const vec3_t mins,const vec3_t maxs,const vec3_t end,int ignore,int clipmask)1934 qboolean G_ClearTrace( const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int ignore, int clipmask )
1935 {
1936 static trace_t tr;
1937
1938 gi.trace( &tr, start, mins, maxs, end, ignore, clipmask, (EG2_Collision)0, 0 );
1939
1940 if ( tr.allsolid || tr.startsolid || tr.fraction < 1.0 )
1941 {
1942 return qfalse;
1943 }
1944
1945 return qtrue;
1946 }
1947
1948 extern void CG_TestLine( vec3_t start, vec3_t end, int time, unsigned int color, int radius);
G_DebugLine(vec3_t A,vec3_t B,int duration,int color,qboolean deleteornot)1949 void G_DebugLine(vec3_t A, vec3_t B, int duration, int color, qboolean deleteornot)
1950 {
1951 /*
1952 gentity_t *tent = G_TempEntity( A, EV_DEBUG_LINE );
1953 VectorCopy(B, tent->s.origin2 );
1954 tent->s.time = duration; // Pause
1955 tent->s.time2 = color; // Color
1956 tent->s.weapon = 1; // Dimater
1957 tent->freeAfterEvent = deleteornot;
1958 */
1959
1960 CG_TestLine( A, B, duration, color, 1 );
1961 }
1962
G_ExpandPointToBBox(vec3_t point,const vec3_t mins,const vec3_t maxs,int ignore,int clipmask)1963 qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask )
1964 {
1965 trace_t tr;
1966 vec3_t start, end;
1967
1968 VectorCopy( point, start );
1969
1970 for ( int i = 0; i < 3; i++ )
1971 {
1972 VectorCopy( start, end );
1973 end[i] += mins[i];
1974 gi.trace( &tr, start, vec3_origin, vec3_origin, end, ignore, clipmask, (EG2_Collision)0, 0 );
1975 if ( tr.allsolid || tr.startsolid )
1976 {
1977 return qfalse;
1978 }
1979 if ( tr.fraction < 1.0 )
1980 {
1981 VectorCopy( start, end );
1982 end[i] += maxs[i]-(mins[i]*tr.fraction);
1983 gi.trace( &tr, start, vec3_origin, vec3_origin, end, ignore, clipmask, (EG2_Collision)0, 0 );
1984 if ( tr.allsolid || tr.startsolid )
1985 {
1986 return qfalse;
1987 }
1988 if ( tr.fraction < 1.0 )
1989 {
1990 return qfalse;
1991 }
1992 VectorCopy( end, start );
1993 }
1994 }
1995 //expanded it, now see if it's all clear
1996 gi.trace( &tr, start, mins, maxs, start, ignore, clipmask, (EG2_Collision)0, 0 );
1997 if ( tr.allsolid || tr.startsolid )
1998 {
1999 return qfalse;
2000 }
2001 VectorCopy( start, point );
2002 return qtrue;
2003 }
2004 /*
2005 Ghoul2 Insert Start
2006 */
2007
removeBoltSurface(gentity_t * ent)2008 void removeBoltSurface( gentity_t *ent)
2009 {
2010 gentity_t *hitEnt = &g_entities[ent->cantHitEnemyCounter];
2011
2012 // check first to be sure the bolt is still there on the model
2013 if ((hitEnt->ghoul2.size() > ent->damage) &&
2014 (hitEnt->ghoul2[ent->damage].mModelindex != -1) &&
2015 (hitEnt->ghoul2[ent->damage].mSlist.size() > (unsigned int)ent->aimDebounceTime) &&
2016 (hitEnt->ghoul2[ent->damage].mSlist[ent->aimDebounceTime].surface != -1) &&
2017 (hitEnt->ghoul2[ent->damage].mSlist[ent->aimDebounceTime].offFlags == G2SURFACEFLAG_GENERATED))
2018 {
2019 // remove the bolt
2020 gi.G2API_RemoveBolt(&hitEnt->ghoul2[ent->damage], ent->attackDebounceTime);
2021 // now remove a surface if there is one
2022 if (ent->aimDebounceTime != -1)
2023 {
2024 gi.G2API_RemoveSurface(&hitEnt->ghoul2[ent->damage], ent->aimDebounceTime);
2025 }
2026 }
2027 // we are done with this entity.
2028 G_FreeEntity(ent);
2029 }
2030
G_SetBoltSurfaceRemoval(const int entNum,const int modelIndex,const int boltIndex,const int surfaceIndex,float duration)2031 void G_SetBoltSurfaceRemoval( const int entNum, const int modelIndex, const int boltIndex, const int surfaceIndex , float duration ) {
2032 gentity_t *e;
2033 vec3_t snapped = {0,0,0};
2034
2035 e = G_Spawn();
2036
2037 e->classname = "BoltRemoval";
2038 e->cantHitEnemyCounter = entNum;
2039 e->damage = modelIndex;
2040 e->attackDebounceTime = boltIndex;
2041 e->aimDebounceTime = surfaceIndex;
2042
2043 G_SetOrigin( e, snapped );
2044
2045 // find cluster for PVS
2046 gi.linkentity( e );
2047
2048 e->nextthink = level.time + duration;
2049 e->e_ThinkFunc = thinkF_removeBoltSurface;
2050
2051 }
2052
2053 /*
2054 Ghoul2 Insert End
2055 */
2056
2057