1 /*
2 ===========================================================================
3 Copyright (C) 2000 - 2013, Raven Software, Inc.
4 Copyright (C) 2001 - 2013, Activision, Inc.
5 Copyright (C) 2013 - 2015, OpenJK contributors
6
7 This file is part of the OpenJK source code.
8
9 OpenJK is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License version 2 as
11 published by the Free Software Foundation.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, see <http://www.gnu.org/licenses/>.
20 ===========================================================================
21 */
22
23 //NPC_combat.cpp
24
25 #include "b_local.h"
26 #include "g_nav.h"
27 #include "g_navigator.h"
28 #include "wp_saber.h"
29 #include "g_functions.h"
30
31 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
32 extern void G_SetEnemy( gentity_t *self, gentity_t *enemy );
33 extern qboolean NPC_CheckLookTarget( gentity_t *self );
34 extern void NPC_ClearLookTarget( gentity_t *self );
35 extern void NPC_Jedi_RateNewEnemy( gentity_t *self, gentity_t *enemy );
36 extern qboolean PM_DroidMelee( int npc_class );
37 extern int delayedShutDown;
38 extern qboolean G_ValidEnemy( gentity_t *self, gentity_t *enemy );
39
40 void ChangeWeapon( gentity_t *ent, int newWeapon );
41
G_ClearEnemy(gentity_t * self)42 void G_ClearEnemy (gentity_t *self)
43 {
44 NPC_CheckLookTarget( self );
45
46 if ( self->enemy )
47 {
48 if ( ( G_ValidEnemy(self, self->enemy ) ) && ( self->svFlags & SVF_LOCKEDENEMY ) )
49 {
50 return;
51 }
52
53
54 if( self->client && self->client->renderInfo.lookTarget == self->enemy->s.number )
55 {
56 NPC_ClearLookTarget( self );
57 }
58
59 if ( self->NPC && self->enemy == self->NPC->goalEntity )
60 {
61 self->NPC->goalEntity = NULL;
62 }
63 //FIXME: set last enemy?
64 }
65
66 self->enemy = NULL;
67 }
68
69 /*
70 -------------------------
71 NPC_AngerAlert
72 -------------------------
73 */
74
75 #define ANGER_ALERT_RADIUS 512
76 #define ANGER_ALERT_SOUND_RADIUS 256
77
G_AngerAlert(gentity_t * self)78 void G_AngerAlert( gentity_t *self )
79 {
80 if ( self && self->NPC && (self->NPC->scriptFlags&SCF_NO_GROUPS) )
81 {//I'm not a team playa...
82 return;
83 }
84 if ( !TIMER_Done( self, "interrogating" ) )
85 {//I'm interrogating, don't wake everyone else up yet... FIXME: this may never wake everyone else up, though!
86 return;
87 }
88 //FIXME: hmm.... with all the other new alerts now, is this still neccesary or even a good idea...?
89 G_AlertTeam( self, self->enemy, ANGER_ALERT_RADIUS, ANGER_ALERT_SOUND_RADIUS );
90 }
91
92 /*
93 -------------------------
94 G_TeamEnemy
95 -------------------------
96 */
97
G_TeamEnemy(gentity_t * self)98 qboolean G_TeamEnemy( gentity_t *self )
99 {//FIXME: Probably a better way to do this, is a linked list of your teammates already available?
100 int i;
101 gentity_t *ent;
102
103 if ( !self->client || self->client->playerTeam == TEAM_FREE )
104 {
105 return qfalse;
106 }
107 if ( self && self->NPC && (self->NPC->scriptFlags&SCF_NO_GROUPS) )
108 {//I'm not a team playa...
109 return qfalse;
110 }
111
112 for( i = 1; i < MAX_GENTITIES; i++ )
113 {
114 ent = &g_entities[i];
115
116 if ( ent == self )
117 {
118 continue;
119 }
120
121 if ( ent->health <= 0 )
122 {
123 continue;
124 }
125
126 if ( !ent->client )
127 {
128 continue;
129 }
130
131 if ( ent->client->playerTeam != self->client->playerTeam )
132 {//ent is not on my team
133 continue;
134 }
135
136 if ( ent->enemy )
137 {//they have an enemy
138 if ( !ent->enemy->client || ent->enemy->client->playerTeam != self->client->playerTeam )
139 {//the ent's enemy is either a normal ent or is a player/NPC that is not on my team
140 return qtrue;
141 }
142 }
143 }
144
145 return qfalse;
146 }
147
G_CheckSaberAllyAttackDelay(gentity_t * self,gentity_t * enemy)148 qboolean G_CheckSaberAllyAttackDelay( gentity_t *self, gentity_t *enemy )
149 {
150 if ( !self || !self->enemy )
151 {
152 return qfalse;
153 }
154 if ( self->NPC
155 && self->client->leader == player
156 && self->enemy
157 && self->enemy->s.weapon != WP_SABER
158 && self->s.weapon == WP_SABER )
159 {//assisting the player and I'm using a saber and my enemy is not
160 TIMER_Set( self, "allyJediDelay", -level.time );
161 //use the distance to the enemy to determine how long to delay
162 float distance = Distance( enemy->currentOrigin, self->currentOrigin );
163 if ( distance < 256 )
164 {
165 return qtrue;
166 }
167 int delay = 500;
168 if ( distance > 2048 )
169 {//the farther they are, the shorter the delay
170 delay = 5000-floor(distance);//(6-g_spskill->integer));
171 if ( delay < 500 )
172 {
173 delay = 500;
174 }
175 }
176 else
177 {//the close they are, the shorter the delay
178 delay = floor(distance*4);//(6-g_spskill->integer));
179 if ( delay > 5000 )
180 {
181 delay = 5000;
182 }
183 }
184 TIMER_Set( self, "allyJediDelay", delay );
185
186 return qtrue;
187 }
188 return qfalse;
189 }
190
G_AttackDelay(gentity_t * self,gentity_t * enemy)191 void G_AttackDelay( gentity_t *self, gentity_t *enemy )
192 {
193 if ( enemy && self->client && self->NPC )
194 {//delay their attack based on how far away they're facing from enemy
195 vec3_t fwd, dir;
196 int attDelay;
197
198 VectorSubtract( self->client->renderInfo.eyePoint, enemy->currentOrigin, dir );//purposely backwards
199 VectorNormalize( dir );
200 AngleVectors( self->client->renderInfo.eyeAngles, fwd, NULL, NULL );
201 //dir[2] = fwd[2] = 0;//ignore z diff?
202
203 attDelay = (4-g_spskill->integer)*500;//initial: from 1000ms delay on hard to 2000ms delay on easy
204 if ( self->client->playerTeam == TEAM_PLAYER )
205 {//invert
206 attDelay = 2000-attDelay;
207 }
208 attDelay += floor( (DotProduct( fwd, dir )+1.0f) * 2000.0f );//add up to 4000ms delay if they're facing away
209
210 //FIXME: should distance matter, too?
211
212 //Now modify the delay based on NPC_class, weapon, and team
213 //NOTE: attDelay should be somewhere between 1000 to 6000 milliseconds
214 switch ( self->client->NPC_class )
215 {
216 case CLASS_IMPERIAL://they give orders and hang back
217 attDelay += Q_irand( 500, 1500 );
218 break;
219 case CLASS_STORMTROOPER://stormtroopers shoot sooner
220 if ( self->NPC->rank >= RANK_LT )
221 {//officers shoot even sooner
222 attDelay -= Q_irand( 500, 1500 );
223 }
224 else
225 {//normal stormtroopers don't have as fast reflexes as officers
226 attDelay -= Q_irand( 0, 1000 );
227 }
228 break;
229 case CLASS_SWAMPTROOPER://shoot very quickly? What about guys in water?
230 attDelay -= Q_irand( 1000, 2000 );
231 break;
232 case CLASS_IMPWORKER://they panic, don't fire right away
233 attDelay += Q_irand( 1000, 2500 );
234 break;
235 case CLASS_TRANDOSHAN:
236 attDelay -= Q_irand( 500, 1500 );
237 break;
238 case CLASS_JAN:
239 case CLASS_LANDO:
240 case CLASS_PRISONER:
241 case CLASS_REBEL:
242 attDelay -= Q_irand( 500, 1500 );
243 break;
244 case CLASS_GALAKMECH:
245 case CLASS_ATST:
246 attDelay -= Q_irand( 1000, 2000 );
247 break;
248 case CLASS_REELO:
249 case CLASS_UGNAUGHT:
250 case CLASS_JAWA:
251 return;
252 break;
253 case CLASS_MINEMONSTER:
254 case CLASS_MURJJ:
255 return;
256 break;
257 case CLASS_INTERROGATOR:
258 case CLASS_PROBE:
259 case CLASS_MARK1:
260 case CLASS_MARK2:
261 case CLASS_SENTRY:
262 return;
263 break;
264 case CLASS_REMOTE:
265 case CLASS_SEEKER:
266 return;
267 break;
268 /*
269 case CLASS_GRAN:
270 case CLASS_RODIAN:
271 case CLASS_WEEQUAY:
272 case CLASS_TUSKEN:
273 break;
274 case CLASS_JEDI:
275 case CLASS_SHADOWTROOPER:
276 case CLASS_TAVION:
277 case CLASS_REBORN:
278 case CLASS_LUKE:
279 case CLASS_KYLE:
280 case CLASS_DESANN:
281 break;
282 */
283 default:
284 break;
285 }
286
287 switch ( self->s.weapon )
288 {
289 case WP_NONE:
290 case WP_SABER:
291 return;
292 break;
293 case WP_BRYAR_PISTOL:
294 break;
295 case WP_BLASTER:
296 if ( self->NPC->scriptFlags & SCF_ALT_FIRE )
297 {//rapid-fire blasters
298 attDelay += Q_irand( 0, 500 );
299 }
300 else
301 {//regular blaster
302 attDelay -= Q_irand( 0, 500 );
303 }
304 break;
305 case WP_BOWCASTER:
306 attDelay += Q_irand( 0, 500 );
307 break;
308 case WP_REPEATER:
309 if ( !(self->NPC->scriptFlags&SCF_ALT_FIRE) )
310 {//rapid-fire blasters
311 attDelay += Q_irand( 0, 500 );
312 }
313 break;
314 case WP_FLECHETTE:
315 attDelay += Q_irand( 500, 1500 );
316 break;
317 case WP_ROCKET_LAUNCHER:
318 attDelay += Q_irand( 500, 1500 );
319 break;
320 case WP_CONCUSSION:
321 attDelay += Q_irand( 500, 1500 );
322 break;
323 case WP_BLASTER_PISTOL: // apparently some enemy only version of the blaster
324 attDelay -= Q_irand( 500, 1500 );
325 break;
326 case WP_DISRUPTOR://sniper's don't delay?
327 return;
328 break;
329 case WP_THERMAL://grenade-throwing has a built-in delay
330 return;
331 break;
332 case WP_MELEE: // Any ol' melee attack
333 return;
334 break;
335 case WP_EMPLACED_GUN:
336 return;
337 break;
338 case WP_TURRET: // turret guns
339 return;
340 break;
341 case WP_BOT_LASER: // Probe droid - Laser blast
342 return;
343 break;
344 case WP_NOGHRI_STICK:
345 attDelay += Q_irand( 0, 500 );
346 break;
347 /*
348 case WP_DEMP2:
349 break;
350 case WP_TRIP_MINE:
351 break;
352 case WP_DET_PACK:
353 break;
354 case WP_STUN_BATON:
355 break;
356 case WP_ATST_MAIN:
357 break;
358 case WP_ATST_SIDE:
359 break;
360 case WP_TIE_FIGHTER:
361 break;
362 case WP_RAPID_FIRE_CONC:
363 break;
364 */
365 }
366
367 if ( self->client->playerTeam == TEAM_PLAYER )
368 {//clamp it
369 if ( attDelay > 2000 )
370 {
371 attDelay = 2000;
372 }
373 }
374
375 //don't shoot right away
376 if ( attDelay > 4000+((2-g_spskill->integer)*3000) )
377 {
378 attDelay = 4000+((2-g_spskill->integer)*3000);
379 }
380 TIMER_Set( self, "attackDelay", attDelay );//Q_irand( 1500, 4500 ) );
381 //don't move right away either
382 if ( attDelay > 4000 )
383 {
384 attDelay = 4000 - Q_irand(500, 1500);
385 }
386 else
387 {
388 attDelay -= Q_irand(500, 1500);
389 }
390
391 TIMER_Set( self, "roamTime", attDelay );//was Q_irand( 1000, 3500 );
392 }
393 }
394 /*
395 -------------------------
396 G_SetEnemy
397 -------------------------
398 */
399 extern gentity_t *G_CheckControlledTurretEnemy(gentity_t *self, gentity_t *enemy, qboolean validate );
400
401 void Saboteur_Cloak( gentity_t *self );
402 void G_AimSet( gentity_t *self, int aim );
G_SetEnemy(gentity_t * self,gentity_t * enemy)403 void G_SetEnemy( gentity_t *self, gentity_t *enemy )
404 {
405 int event = 0;
406
407 //Must be valid
408 if ( enemy == NULL )
409 return;
410
411 //Must be valid
412 if ( enemy->inuse == 0 )
413 {
414 return;
415 }
416
417 enemy = G_CheckControlledTurretEnemy(self, enemy, qtrue);
418 if (!enemy)
419 {
420 return;
421 }
422
423 //Don't take the enemy if in notarget
424 if ( enemy->flags & FL_NOTARGET )
425 return;
426
427 if ( !self->NPC )
428 {
429 self->enemy = enemy;
430 return;
431 }
432
433 if ( self->NPC->confusionTime > level.time )
434 {//can't pick up enemies if confused
435 return;
436 }
437
438 #ifdef _DEBUG
439 if ( self->s.number )
440 {
441 assert( enemy != self );
442 }
443 #endif// _DEBUG
444
445 // if ( enemy->client && enemy->client->playerTeam == TEAM_DISGUISE )
446 // {//unmask the player
447 // enemy->client->playerTeam = TEAM_PLAYER;
448 // }
449
450 if ( self->client && self->NPC && enemy->client && enemy->client->playerTeam == self->client->playerTeam )
451 {//Probably a damn script!
452 if ( self->NPC->charmedTime > level.time )
453 {//Probably a damn script!
454 return;
455 }
456 }
457
458 if ( self->NPC && self->client && self->client->ps.weapon == WP_SABER )
459 {
460 //when get new enemy, set a base aggression based on what that enemy is using, how far they are, etc.
461 NPC_Jedi_RateNewEnemy( self, enemy );
462 }
463
464 //NOTE: this is not necessarily true!
465 //self->NPC->enemyLastSeenTime = level.time;
466
467 if ( self->enemy == NULL )
468 {
469 //TEMP HACK: turn on our saber
470 if ( self->health > 0 )
471 {
472 self->client->ps.SaberActivate();
473 }
474
475 //FIXME: Have to do this to prevent alert cascading
476 G_ClearEnemy( self );
477 self->enemy = enemy;
478 if (self->client && self->client->NPC_class == CLASS_SABOTEUR)
479 {
480 Saboteur_Cloak(NPC); // Cloak
481 TIMER_Set(self, "decloakwait", 3000); // Wait 3 sec before decloak and attack
482 }
483
484
485 //Special case- if player is being hunted by his own people, set the player's team to team_free
486 if ( self->client->playerTeam == TEAM_PLAYER
487 && enemy->s.number == 0
488 && enemy->client
489 && enemy->client->playerTeam == TEAM_PLAYER )
490 {//make the player "evil" so that everyone goes after him
491 enemy->client->enemyTeam = TEAM_FREE;
492 enemy->client->playerTeam = TEAM_FREE;
493 }
494
495 //If have an anger script, run that instead of yelling
496 if( G_ActivateBehavior( self, BSET_ANGER ) )
497 {
498 }
499 else if ( self->client
500 && self->client->NPC_class == CLASS_KYLE
501 && self->client->leader == player
502 && !TIMER_Done( self, "kyleAngerSoundDebounce" ) )
503 {//don't yell that you have an enemy more than once every five seconds
504 }
505 else if ( self->client && enemy->client && self->client->playerTeam != enemy->client->playerTeam )
506 {
507 //FIXME: Use anger when entire team has no enemy.
508 // Basically, you're first one to notice enemies
509 if ( self->forcePushTime < level.time ) // not currently being pushed
510 {
511 if ( !G_TeamEnemy( self ) && self->client->NPC_class != CLASS_BOBAFETT)
512 {//team did not have an enemy previously
513 if ( self->NPC
514 && self->client->playerTeam == TEAM_PLAYER
515 && enemy->s.number < MAX_CLIENTS
516 && self->client->clientInfo.customBasicSoundDir
517 && self->client->clientInfo.customBasicSoundDir[0]
518 && Q_stricmp( "jedi2", self->client->clientInfo.customBasicSoundDir ) == 0 )
519 {
520 switch ( Q_irand( 0, 2 ) )
521 {
522 case 0:
523 G_SoundOnEnt( self, CHAN_VOICE, "sound/chars/jedi2/28je2008.wav" );
524 break;
525 case 1:
526 G_SoundOnEnt( self, CHAN_VOICE, "sound/chars/jedi2/28je2009.wav" );
527 break;
528 case 2:
529 G_SoundOnEnt( self, CHAN_VOICE, "sound/chars/jedi2/28je2012.wav" );
530 break;
531 }
532 self->NPC->blockedSpeechDebounceTime = level.time + 2000;
533 }
534 else
535 {
536 if ( Q_irand( 0, 1 ))
537 {//hell, we're loading them, might as well use them!
538 event = Q_irand(EV_CHASE1, EV_CHASE3);
539 }
540 else
541 {
542 event = Q_irand(EV_ANGER1, EV_ANGER3);
543 }
544 }
545 }
546 }
547
548 if ( event )
549 {//yell
550 if ( self->client
551 && self->client->NPC_class == CLASS_KYLE
552 && self->client->leader == player )
553 {//don't yell that you have an enemy more than once every 4-8 seconds
554 TIMER_Set( self, "kyleAngerSoundDebounce", Q_irand( 4000, 8000 ) );
555 }
556 G_AddVoiceEvent( self, event, 2000 );
557 }
558 }
559
560 if ( self->s.weapon == WP_BLASTER || self->s.weapon == WP_REPEATER ||
561 self->s.weapon == WP_THERMAL || self->s.weapon == WP_BLASTER_PISTOL
562 || self->s.weapon == WP_BOWCASTER )
563 {//Hmm, how about sniper and bowcaster?
564 //When first get mad, aim is bad
565 //Hmm, base on game difficulty, too? Rank?
566 if ( self->client->playerTeam == TEAM_PLAYER )
567 {
568 G_AimSet( self, Q_irand( self->NPC->stats.aim - (5*(g_spskill->integer)), self->NPC->stats.aim - g_spskill->integer ) );
569 }
570 else
571 {
572 int minErr = 3;
573 int maxErr = 12;
574 if ( self->client->NPC_class == CLASS_IMPWORKER )
575 {
576 minErr = 15;
577 maxErr = 30;
578 }
579 else if ( self->client->NPC_class == CLASS_STORMTROOPER && self->NPC && self->NPC->rank <= RANK_CREWMAN )
580 {
581 minErr = 5;
582 maxErr = 15;
583 }
584
585 G_AimSet( self, Q_irand( self->NPC->stats.aim - (maxErr*(3-g_spskill->integer)), self->NPC->stats.aim - (minErr*(3-g_spskill->integer)) ) );
586 }
587 }
588
589 //Alert anyone else in the area
590 if ( Q_stricmp( "desperado", self->NPC_type ) != 0 && Q_stricmp( "paladin", self->NPC_type ) != 0 )
591 {//special holodeck enemies exception
592 if ( !(self->client->ps.eFlags&EF_FORCE_GRIPPED) )
593 {//gripped people can't call for help
594 G_AngerAlert( self );
595 }
596 }
597
598 if ( !G_CheckSaberAllyAttackDelay( self, enemy ) )
599 {//not a saber ally holding back
600 //Stormtroopers don't fire right away!
601 G_AttackDelay( self, enemy );
602 }
603
604 //FIXME: this is a disgusting hack that is supposed to make the Imperials start with their weapon holstered- need a better way
605 if ( self->client->ps.weapon == WP_NONE && !Q_stricmpn( self->NPC_type, "imp", 3 ) && !(self->NPC->scriptFlags & SCF_FORCED_MARCH) )
606 {
607 if ( self->client->ps.stats[STAT_WEAPONS] & ( 1 << WP_BLASTER ) )
608 {
609 ChangeWeapon( self, WP_BLASTER );
610 self->client->ps.weapon = WP_BLASTER;
611 self->client->ps.weaponstate = WEAPON_READY;
612 G_CreateG2AttachedWeaponModel( self, weaponData[WP_BLASTER].weaponMdl, self->handRBolt, 0 );
613 }
614 else if ( self->client->ps.stats[STAT_WEAPONS] & ( 1 << WP_BLASTER_PISTOL ) )
615 {
616 ChangeWeapon( self, WP_BLASTER_PISTOL );
617 self->client->ps.weapon = WP_BLASTER_PISTOL;
618 self->client->ps.weaponstate = WEAPON_READY;
619 G_CreateG2AttachedWeaponModel( self, weaponData[WP_BLASTER_PISTOL].weaponMdl, self->handRBolt, 0 );
620 }
621 }
622 return;
623 }
624
625 //Otherwise, just picking up another enemy
626
627 if ( event )
628 {
629 G_AddVoiceEvent( self, event, 2000 );
630 }
631
632 //Take the enemy
633 G_ClearEnemy(self);
634 self->enemy = enemy;
635 }
636
637
638 /*
639 int ChooseBestWeapon( void )
640 {
641 int n;
642 int weapon;
643
644 // check weapons in the NPC's weapon preference order
645 for ( n = 0; n < MAX_WEAPONS; n++ )
646 {
647 weapon = NPCInfo->weaponOrder[n];
648
649 if ( weapon == WP_NONE )
650 {
651 break;
652 }
653
654 if ( !HaveWeapon( weapon ) )
655 {
656 continue;
657 }
658
659 if ( client->ps.ammo[weaponData[weapon].ammoIndex] )
660 {
661 return weapon;
662 }
663 }
664
665 // check weapons serially (mainly in case a weapon is not on the NPC's list)
666 for ( weapon = 1; weapon < WP_NUM_WEAPONS; weapon++ )
667 {
668 if ( !HaveWeapon( weapon ) )
669 {
670 continue;
671 }
672
673 if ( client->ps.ammo[weaponData[weapon].ammoIndex] )
674 {
675 return weapon;
676 }
677 }
678
679 return client->ps.weapon;
680 }
681 */
682
ChangeWeapon(gentity_t * ent,int newWeapon)683 void ChangeWeapon( gentity_t *ent, int newWeapon )
684 {
685 if ( !ent || !ent->client || !ent->NPC )
686 {
687 return;
688 }
689
690 ent->client->ps.weapon = newWeapon;
691 ent->NPC->shotTime = 0;
692 ent->NPC->burstCount = 0;
693 ent->NPC->attackHold = 0;
694 ent->NPC->currentAmmo = ent->client->ps.ammo[weaponData[newWeapon].ammoIndex];
695
696 switch ( newWeapon )
697 {
698 case WP_BRYAR_PISTOL://prifle
699 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
700 ent->NPC->burstSpacing = 1000;//attackdebounce
701 break;
702
703 case WP_BLASTER_PISTOL:
704 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
705 if ( ent->weaponModel[1] > 0 )
706 {//commando
707 ent->NPC->aiFlags |= NPCAI_BURST_WEAPON;
708 ent->NPC->burstMin = 4;
709 #ifdef BASE_SAVE_COMPAT
710 ent->NPC->burstMean = 8;
711 #endif
712 ent->NPC->burstMax = 12;
713 if ( g_spskill->integer == 0 )
714 ent->NPC->burstSpacing = 600;//attack debounce
715 else if ( g_spskill->integer == 1 )
716 ent->NPC->burstSpacing = 400;//attack debounce
717 else
718 ent->NPC->burstSpacing = 250;//attack debounce
719 }
720 else if ( ent->client->NPC_class == CLASS_SABOTEUR )
721 {
722 if ( g_spskill->integer == 0 )
723 ent->NPC->burstSpacing = 900;//attack debounce
724 else if ( g_spskill->integer == 1 )
725 ent->NPC->burstSpacing = 600;//attack debounce
726 else
727 ent->NPC->burstSpacing = 400;//attack debounce
728 }
729 else
730 {
731 // ent->NPC->burstSpacing = 1000;//attackdebounce
732 if ( g_spskill->integer == 0 )
733 ent->NPC->burstSpacing = 1000;//attack debounce
734 else if ( g_spskill->integer == 1 )
735 ent->NPC->burstSpacing = 750;//attack debounce
736 else
737 ent->NPC->burstSpacing = 500;//attack debounce
738 }
739 break;
740
741 case WP_BOT_LASER://probe attack
742 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
743 // ent->NPC->burstSpacing = 600;//attackdebounce
744 if ( g_spskill->integer == 0 )
745 ent->NPC->burstSpacing = 600;//attack debounce
746 else if ( g_spskill->integer == 1 )
747 ent->NPC->burstSpacing = 400;//attack debounce
748 else
749 ent->NPC->burstSpacing = 200;//attack debounce
750 break;
751
752 case WP_SABER:
753 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
754 ent->NPC->burstSpacing = 0;//attackdebounce
755 break;
756
757 case WP_DISRUPTOR:
758 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
759 if ( ent->NPC->scriptFlags & SCF_ALT_FIRE )
760 {
761 switch( g_spskill->integer )
762 {
763 case 0:
764 ent->NPC->burstSpacing = 2500;//attackdebounce
765 break;
766 case 1:
767 ent->NPC->burstSpacing = 2000;//attackdebounce
768 break;
769 case 2:
770 ent->NPC->burstSpacing = 1500;//attackdebounce
771 break;
772 }
773 }
774 else
775 {
776 ent->NPC->burstSpacing = 1000;//attackdebounce
777 }
778 break;
779
780 case WP_TUSKEN_RIFLE:
781 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
782 if ( ent->NPC->scriptFlags & SCF_ALT_FIRE )
783 {
784 switch( g_spskill->integer )
785 {
786 case 0:
787 ent->NPC->burstSpacing = 2500;//attackdebounce
788 break;
789 case 1:
790 ent->NPC->burstSpacing = 2000;//attackdebounce
791 break;
792 case 2:
793 ent->NPC->burstSpacing = 1500;//attackdebounce
794 break;
795 }
796 }
797 else
798 {
799 ent->NPC->burstSpacing = 1000;//attackdebounce
800 }
801 break;
802
803 case WP_BOWCASTER:
804 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
805 // ent->NPC->burstSpacing = 1000;//attackdebounce
806 if ( g_spskill->integer == 0 )
807 ent->NPC->burstSpacing = 1000;//attack debounce
808 else if ( g_spskill->integer == 1 )
809 ent->NPC->burstSpacing = 750;//attack debounce
810 else
811 ent->NPC->burstSpacing = 500;//attack debounce
812 break;
813
814 case WP_REPEATER:
815 if ( ent->NPC->scriptFlags & SCF_ALT_FIRE )
816 {
817 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
818 ent->NPC->burstSpacing = 2000;//attackdebounce
819 }
820 else
821 {
822 ent->NPC->aiFlags |= NPCAI_BURST_WEAPON;
823 ent->NPC->burstMin = 3;
824 #ifdef BASE_SAVE_COMPAT
825 ent->NPC->burstMean = 6;
826 #endif
827 ent->NPC->burstMax = 10;
828 if ( g_spskill->integer == 0 )
829 ent->NPC->burstSpacing = 1500;//attack debounce
830 else if ( g_spskill->integer == 1 )
831 ent->NPC->burstSpacing = 1000;//attack debounce
832 else
833 ent->NPC->burstSpacing = 500;//attack debounce
834 }
835 break;
836
837 case WP_DEMP2:
838 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
839 ent->NPC->burstSpacing = 1000;//attackdebounce
840 break;
841
842 case WP_FLECHETTE:
843 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
844 if ( ent->NPC->scriptFlags & SCF_ALT_FIRE )
845 {
846 ent->NPC->burstSpacing = 2000;//attackdebounce
847 }
848 else
849 {
850 ent->NPC->burstSpacing = 1000;//attackdebounce
851 }
852 break;
853
854 case WP_ROCKET_LAUNCHER:
855 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
856 // ent->NPC->burstSpacing = 2500;//attackdebounce
857 if ( g_spskill->integer == 0 )
858 ent->NPC->burstSpacing = 2500;//attack debounce
859 else if ( g_spskill->integer == 1 )
860 ent->NPC->burstSpacing = 2000;//attack debounce
861 else
862 ent->NPC->burstSpacing = 1500;//attack debounce
863 break;
864
865 case WP_CONCUSSION:
866 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
867 if ( ent->NPC->scriptFlags & SCF_ALT_FIRE )
868 {//beam
869 ent->NPC->burstSpacing = 1200;//attackdebounce
870 }
871 else
872 {//rocket
873 if ( g_spskill->integer == 0 )
874 ent->NPC->burstSpacing = 2300;//attack debounce
875 else if ( g_spskill->integer == 1 )
876 ent->NPC->burstSpacing = 1800;//attack debounce
877 else
878 ent->NPC->burstSpacing = 1200;//attack debounce
879 }
880 break;
881
882 case WP_THERMAL:
883 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
884 // ent->NPC->burstSpacing = 3000;//attackdebounce
885 if ( g_spskill->integer == 0 )
886 // ent->NPC->burstSpacing = 3000;//attack debounce
887 ent->NPC->burstSpacing = 4500;//attack debounce
888 else if ( g_spskill->integer == 1 )
889 // ent->NPC->burstSpacing = 2500;//attack debounce
890 ent->NPC->burstSpacing = 3000;//attack debounce
891 else
892 ent->NPC->burstSpacing = 2000;//attack debounce
893 break;
894
895 /*
896 case WP_SABER:
897 ent->NPC->aiFlags |= NPCAI_BURST_WEAPON;
898 ent->NPC->burstMin = 5;//0.5 sec
899 ent->NPC->burstMax = 20;//3 seconds
900 ent->NPC->burstSpacing = 2000;//2 seconds
901 ent->NPC->attackHold = 1000;//Hold attack button for a 1-second burst
902 break;
903 */
904
905 case WP_BLASTER:
906 if ( ent->NPC->scriptFlags & SCF_ALT_FIRE )
907 {
908 ent->NPC->aiFlags |= NPCAI_BURST_WEAPON;
909 ent->NPC->burstMin = 3;
910 #ifdef BASE_SAVE_COMPAT
911 ent->NPC->burstMean = 3;
912 #endif
913 ent->NPC->burstMax = 3;
914 if ( g_spskill->integer == 0 )
915 ent->NPC->burstSpacing = 1500;//attack debounce
916 else if ( g_spskill->integer == 1 )
917 ent->NPC->burstSpacing = 1000;//attack debounce
918 else
919 ent->NPC->burstSpacing = 500;//attack debounce
920 }
921 else
922 {
923 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
924 if ( g_spskill->integer == 0 )
925 ent->NPC->burstSpacing = 1000;//attack debounce
926 else if ( g_spskill->integer == 1 )
927 ent->NPC->burstSpacing = 750;//attack debounce
928 else
929 ent->NPC->burstSpacing = 500;//attack debounce
930 // ent->NPC->burstSpacing = 1000;//attackdebounce
931 }
932 break;
933
934 case WP_MELEE:
935 case WP_TUSKEN_STAFF:
936 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
937 ent->NPC->burstSpacing = 1000;//attackdebounce
938 break;
939
940 case WP_ATST_MAIN:
941 case WP_ATST_SIDE:
942 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
943 // ent->NPC->burstSpacing = 1000;//attackdebounce
944 if ( g_spskill->integer == 0 )
945 ent->NPC->burstSpacing = 1000;//attack debounce
946 else if ( g_spskill->integer == 1 )
947 ent->NPC->burstSpacing = 750;//attack debounce
948 else
949 ent->NPC->burstSpacing = 500;//attack debounce
950 break;
951
952 case WP_EMPLACED_GUN:
953 //FIXME: give some designer-control over this?
954 if ( ent->client && ent->client->NPC_class == CLASS_REELO )
955 {
956 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
957 ent->NPC->burstSpacing = 1000;//attack debounce
958 // if ( g_spskill->integer == 0 )
959 // ent->NPC->burstSpacing = 300;//attack debounce
960 // else if ( g_spskill->integer == 1 )
961 // ent->NPC->burstSpacing = 200;//attack debounce
962 // else
963 // ent->NPC->burstSpacing = 100;//attack debounce
964 }
965 else
966 {
967 ent->NPC->aiFlags |= NPCAI_BURST_WEAPON;
968 ent->NPC->burstMin = 2; // 3 shots, really
969 #ifdef BASE_SAVE_COMPAT
970 ent->NPC->burstMean = 2;
971 #endif
972 ent->NPC->burstMax = 2;
973
974 if ( ent->owner ) // if we have an owner, it should be the chair at this point...so query the chair for its shot debounce times, etc.
975 {
976 if ( g_spskill->integer == 0 )
977 {
978 ent->NPC->burstSpacing = ent->owner->wait + 400;//attack debounce
979 ent->NPC->burstMin = ent->NPC->burstMax = 1; // two shots
980 }
981 else if ( g_spskill->integer == 1 )
982 {
983 ent->NPC->burstSpacing = ent->owner->wait + 200;//attack debounce
984 }
985 else
986 {
987 ent->NPC->burstSpacing = ent->owner->wait;//attack debounce
988 }
989 }
990 else
991 {
992 if ( g_spskill->integer == 0 )
993 {
994 ent->NPC->burstSpacing = 1200;//attack debounce
995 ent->NPC->burstMin = ent->NPC->burstMax = 1; // two shots
996 }
997 else if ( g_spskill->integer == 1 )
998 {
999 ent->NPC->burstSpacing = 1000;//attack debounce
1000 }
1001 else
1002 {
1003 ent->NPC->burstSpacing = 800;//attack debounce
1004 }
1005 }
1006 }
1007 break;
1008
1009 case WP_NOGHRI_STICK:
1010 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
1011 if ( g_spskill->integer == 0 )
1012 ent->NPC->burstSpacing = 2250;//attack debounce
1013 else if ( g_spskill->integer == 1 )
1014 ent->NPC->burstSpacing = 1500;//attack debounce
1015 else
1016 ent->NPC->burstSpacing = 750;//attack debounce
1017 break;
1018
1019 default:
1020 ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON;
1021 break;
1022 }
1023 }
1024
NPC_ChangeWeapon(int newWeapon)1025 void NPC_ChangeWeapon( int newWeapon )
1026 {
1027 qboolean changing = qfalse;
1028 if ( newWeapon != NPC->client->ps.weapon )
1029 {
1030 changing = qtrue;
1031 }
1032 if ( changing )
1033 {
1034 G_RemoveWeaponModels( NPC );
1035 }
1036 ChangeWeapon( NPC, newWeapon );
1037 if ( changing && NPC->client->ps.weapon != WP_NONE )
1038 {
1039 if ( NPC->client->ps.weapon == WP_SABER )
1040 {
1041 WP_SaberAddG2SaberModels( NPC );
1042 }
1043 else
1044 {
1045 G_CreateG2AttachedWeaponModel( NPC, weaponData[NPC->client->ps.weapon].weaponMdl, NPC->handRBolt, 0 );
1046 }
1047 }
1048 }
1049 /*
1050 void NPC_ApplyWeaponFireDelay(void)
1051 How long, if at all, in msec the actual fire should delay from the time the attack was started
1052 */
NPC_ApplyWeaponFireDelay(void)1053 void NPC_ApplyWeaponFireDelay(void)
1054 {
1055 if ( NPC->attackDebounceTime > level.time )
1056 {//Just fired, if attacking again, must be a burst fire, so don't add delay
1057 //NOTE: Borg AI uses attackDebounceTime "incorrectly", so this will always return for them!
1058 return;
1059 }
1060
1061 switch(client->ps.weapon)
1062 {
1063 case WP_BOT_LASER:
1064 NPCInfo->burstCount = 0;
1065 client->fireDelay = 500;
1066 break;
1067
1068 case WP_THERMAL:
1069 if ( client->ps.clientNum )
1070 {//NPCs delay...
1071 //FIXME: player should, too, but would feel weird in 1st person, even though it
1072 // would look right in 3rd person. Really should have a wind-up anim
1073 // for player as he holds down the fire button to throw, then play
1074 // the actual throw when he lets go...
1075 client->fireDelay = 700;
1076 }
1077 break;
1078
1079 case WP_MELEE:
1080 case WP_TUSKEN_STAFF:
1081 if ( !PM_DroidMelee( client->NPC_class ) )
1082 {//FIXME: should be unique per melee anim
1083 client->fireDelay = 300;
1084 }
1085 break;
1086
1087 case WP_TUSKEN_RIFLE:
1088 if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) )
1089 {//FIXME: should be unique per melee anim
1090 client->fireDelay = 300;
1091 }
1092 break;
1093
1094 default:
1095 client->fireDelay = 0;
1096 break;
1097 }
1098 };
1099
1100 /*
1101 -------------------------
1102 ShootThink
1103 -------------------------
1104 */
ShootThink(void)1105 void ShootThink( void )
1106 {
1107 int delay;
1108
1109 ucmd.buttons |= BUTTON_ATTACK;
1110
1111 NPCInfo->currentAmmo = client->ps.ammo[weaponData[client->ps.weapon].ammoIndex]; // checkme
1112
1113 NPC_ApplyWeaponFireDelay();
1114
1115 if ( NPCInfo->aiFlags & NPCAI_BURST_WEAPON )
1116 {
1117 if ( !NPCInfo->burstCount )
1118 {
1119 NPCInfo->burstCount = Q_irand( NPCInfo->burstMin, NPCInfo->burstMax );
1120 delay = 0;
1121 }
1122 else
1123 {
1124 NPCInfo->burstCount--;
1125 if ( NPCInfo->burstCount == 0 )
1126 {
1127 delay = NPCInfo->burstSpacing + Q_irand(-150, 150);
1128 }
1129 else
1130 {
1131 delay = 0;
1132 }
1133 }
1134
1135 if ( !delay )
1136 {
1137 // HACK: dirty little emplaced bits, but is done because it would otherwise require some sort of new variable...
1138 if ( client->ps.weapon == WP_EMPLACED_GUN )
1139 {
1140 if ( NPC->owner ) // try and get the debounce values from the chair if we can
1141 {
1142 if ( g_spskill->integer == 0 )
1143 {
1144 delay = NPC->owner->random + 150;
1145 }
1146 else if ( g_spskill->integer == 1 )
1147 {
1148 delay = NPC->owner->random + 100;
1149 }
1150 else
1151 {
1152 delay = NPC->owner->random;
1153 }
1154 }
1155 else
1156 {
1157 if ( g_spskill->integer == 0 )
1158 {
1159 delay = 350;
1160 }
1161 else if ( g_spskill->integer == 1 )
1162 {
1163 delay = 300;
1164 }
1165 else
1166 {
1167 delay = 200;
1168 }
1169 }
1170 }
1171 }
1172 }
1173 else
1174 {
1175 delay = NPCInfo->burstSpacing + Q_irand(-150, 150);
1176 }
1177
1178 NPCInfo->shotTime = level.time + delay;
1179 NPC->attackDebounceTime = level.time + NPC_AttackDebounceForWeapon();
1180 }
1181
1182 /*
1183 static void WeaponThink( qboolean inCombat )
1184 FIXME makes this so there's a delay from event that caused us to check to actually doing it
1185
1186 Added: hacks for Borg
1187 */
WeaponThink(qboolean inCombat)1188 void WeaponThink( qboolean inCombat )
1189 {
1190 ucmd.buttons &= ~BUTTON_ATTACK;
1191 if ( client->ps.weaponstate == WEAPON_RAISING || client->ps.weaponstate == WEAPON_DROPPING )
1192 {
1193 ucmd.weapon = client->ps.weapon;
1194 return;
1195 }
1196
1197 // can't shoot while shield is up
1198 if (NPC->flags&FL_SHIELDED && NPC->client->NPC_class==CLASS_ASSASSIN_DROID)
1199 {
1200 return;
1201 }
1202
1203 // Can't Fire While Cloaked
1204 if (NPC->client &&
1205 (NPC->client->ps.powerups[PW_CLOAKED] || (level.time<NPC->client->ps.powerups[PW_UNCLOAKING])))
1206 {
1207 return;
1208 }
1209
1210 if ( client->ps.weapon == WP_NONE )
1211 {
1212 return;
1213 }
1214
1215 if ( client->ps.weaponstate != WEAPON_READY && client->ps.weaponstate != WEAPON_FIRING && client->ps.weaponstate != WEAPON_IDLE)
1216 {
1217 return;
1218 }
1219
1220 if ( level.time < NPCInfo->shotTime )
1221 {
1222 return;
1223 }
1224
1225
1226 //MCG - Begin
1227 //For now, no-one runs out of ammo
1228 if ( NPC->client->ps.ammo[ weaponData[client->ps.weapon].ammoIndex ] < weaponData[client->ps.weapon].energyPerShot )
1229 {
1230 Add_Ammo( NPC, client->ps.weapon, weaponData[client->ps.weapon].energyPerShot*10 );
1231 }
1232 else if ( NPC->client->ps.ammo[ weaponData[client->ps.weapon].ammoIndex ] < weaponData[client->ps.weapon].altEnergyPerShot )
1233 {
1234 Add_Ammo( NPC, client->ps.weapon, weaponData[client->ps.weapon].altEnergyPerShot*5 );
1235 }
1236
1237 ucmd.weapon = client->ps.weapon;
1238 ShootThink();
1239 }
1240
1241 /*
1242 HaveWeapon
1243 */
1244
HaveWeapon(int weapon)1245 qboolean HaveWeapon( int weapon )
1246 {
1247 return (qboolean)( client->ps.stats[STAT_WEAPONS] & ( 1 << weapon ) );
1248 }
1249
EntIsGlass(gentity_t * check)1250 qboolean EntIsGlass (gentity_t *check)
1251 {
1252 if(check->classname &&
1253 !Q_stricmp("func_breakable", check->classname) &&
1254 check->count == 1 && check->health <= 100)
1255 {
1256 return qtrue;
1257 }
1258
1259 return qfalse;
1260 }
1261
ShotThroughGlass(trace_t * tr,gentity_t * target,vec3_t spot,int mask)1262 qboolean ShotThroughGlass (trace_t *tr, gentity_t *target, vec3_t spot, int mask)
1263 {
1264 gentity_t *hit = &g_entities[ tr->entityNum ];
1265 if(hit != target && EntIsGlass(hit))
1266 {//ok to shoot through breakable glass
1267 int skip = hit->s.number;
1268 vec3_t muzzle;
1269
1270 VectorCopy(tr->endpos, muzzle);
1271 gi.trace (tr, muzzle, NULL, NULL, spot, skip, mask, (EG2_Collision)0, 0 );
1272 return qtrue;
1273 }
1274
1275 return qfalse;
1276 }
1277
1278 /*
1279 CanShoot
1280 determine if NPC can directly target enemy
1281
1282 this function does not check teams, invulnerability, notarget, etc....
1283
1284 Added: If can't shoot center, try head, if not, see if it's close enough to try anyway.
1285 */
CanShoot(gentity_t * ent,gentity_t * shooter)1286 qboolean CanShoot ( gentity_t *ent, gentity_t *shooter )
1287 {
1288 trace_t tr;
1289 vec3_t muzzle;
1290 vec3_t spot, diff;
1291 gentity_t *traceEnt;
1292
1293 CalcEntitySpot( shooter, SPOT_WEAPON, muzzle );
1294 CalcEntitySpot( ent, SPOT_ORIGIN, spot ); //FIXME preferred target locations for some weapons (feet for R/L)
1295
1296 gi.trace ( &tr, muzzle, NULL, NULL, spot, shooter->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
1297 traceEnt = &g_entities[ tr.entityNum ];
1298
1299 // point blank, baby!
1300 if (tr.startsolid && (shooter->NPC) && (shooter->NPC->touchedByPlayer) )
1301 {
1302 traceEnt = shooter->NPC->touchedByPlayer;
1303 }
1304
1305 if ( ShotThroughGlass( &tr, ent, spot, MASK_SHOT ) )
1306 {
1307 traceEnt = &g_entities[ tr.entityNum ];
1308 }
1309
1310 // shot is dead on
1311 if ( traceEnt == ent )
1312 {
1313 return qtrue;
1314 }
1315 //MCG - Begin
1316 else
1317 {//ok, can't hit them in center, try their head
1318 CalcEntitySpot( ent, SPOT_HEAD, spot );
1319 gi.trace ( &tr, muzzle, NULL, NULL, spot, shooter->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
1320 traceEnt = &g_entities[ tr.entityNum ];
1321 if ( traceEnt == ent)
1322 {
1323 return qtrue;
1324 }
1325 }
1326
1327 //Actually, we should just check to fire in dir we're facing and if it's close enough,
1328 //and we didn't hit someone on our own team, shoot
1329 VectorSubtract(spot, tr.endpos, diff);
1330 if(VectorLength(diff) < Q_flrand(0.0f, 1.0f) * 32)
1331 {
1332 return qtrue;
1333 }
1334 //MCG - End
1335 // shot would hit a non-client
1336 if ( !traceEnt->client )
1337 {
1338 return qfalse;
1339 }
1340
1341 // shot is blocked by another player
1342
1343 // he's already dead, so go ahead
1344 if ( traceEnt->health <= 0 )
1345 {
1346 return qtrue;
1347 }
1348
1349 // don't deliberately shoot a teammate
1350 if ( traceEnt->client && ( traceEnt->client->playerTeam == shooter->client->playerTeam ) )
1351 {
1352 return qfalse;
1353 }
1354
1355 // he's just in the wrong place, go ahead
1356 return qtrue;
1357 }
1358
1359
1360 /*
1361 void NPC_CheckPossibleEnemy( gentity_t *other, visibility_t vis )
1362
1363 Added: hacks for scripted NPCs
1364 */
NPC_CheckPossibleEnemy(gentity_t * other,visibility_t vis)1365 void NPC_CheckPossibleEnemy( gentity_t *other, visibility_t vis )
1366 {
1367 // is he is already our enemy?
1368 if ( other == NPC->enemy )
1369 return;
1370
1371 if ( other->flags & FL_NOTARGET )
1372 return;
1373
1374 // we already have an enemy and this guy is in our FOV, see if this guy would be better
1375 if ( NPC->enemy && vis == VIS_FOV )
1376 {
1377 if ( NPCInfo->enemyLastSeenTime - level.time < 2000 )
1378 {
1379 return;
1380 }
1381 if ( enemyVisibility == VIS_UNKNOWN )
1382 {
1383 enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_360|CHECK_FOV );
1384 }
1385 if ( enemyVisibility == VIS_FOV )
1386 {
1387 return;
1388 }
1389 }
1390
1391 if ( !NPC->enemy )
1392 {//only take an enemy if you don't have one yet
1393 G_SetEnemy( NPC, other );
1394 }
1395
1396 if ( vis == VIS_FOV )
1397 {
1398 NPCInfo->enemyLastSeenTime = level.time;
1399 VectorCopy( other->currentOrigin, NPCInfo->enemyLastSeenLocation );
1400 NPCInfo->enemyLastHeardTime = 0;
1401 VectorClear( NPCInfo->enemyLastHeardLocation );
1402 }
1403 else
1404 {
1405 NPCInfo->enemyLastSeenTime = 0;
1406 VectorClear( NPCInfo->enemyLastSeenLocation );
1407 NPCInfo->enemyLastHeardTime = level.time;
1408 VectorCopy( other->currentOrigin, NPCInfo->enemyLastHeardLocation );
1409 }
1410 }
1411
1412
1413 //==========================================
1414 //MCG Added functions:
1415 //==========================================
1416
1417 /*
1418 int NPC_AttackDebounceForWeapon (void)
1419
1420 DOES NOT control how fast you can fire
1421 Only makes you keep your weapon up after you fire
1422
1423 */
NPC_AttackDebounceForWeapon(void)1424 int NPC_AttackDebounceForWeapon (void)
1425 {
1426 switch ( NPC->client->ps.weapon )
1427 {
1428 /*
1429 case WP_BLASTER://scav rifle
1430 return 1000;
1431 break;
1432
1433 case WP_BRYAR_PISTOL://prifle
1434 return 3000;
1435 break;
1436
1437 case WP_SABER:
1438 return 100;
1439 break;
1440 */
1441 case WP_SABER:
1442 if ( NPC->client->NPC_class == CLASS_KYLE
1443 && (NPC->spawnflags&1) )
1444 {
1445 return Q_irand( 1500, 5000 );
1446 }
1447 else
1448 {
1449 return 0;
1450 }
1451 break;
1452
1453 case WP_BOT_LASER:
1454
1455 if ( g_spskill->integer == 0 )
1456 return 2000;
1457
1458 if ( g_spskill->integer == 1 )
1459 return 1500;
1460
1461 return 1000;
1462 break;
1463
1464 default:
1465 return NPCInfo->burstSpacing + Q_irand(-100, 100);//was 100 by default
1466 break;
1467 }
1468 }
1469
1470 //FIXME: need a mindist for explosive weapons
NPC_MaxDistSquaredForWeapon(void)1471 float NPC_MaxDistSquaredForWeapon (void)
1472 {
1473 if(NPCInfo->stats.shootDistance > 0)
1474 {//overrides default weapon dist
1475 return NPCInfo->stats.shootDistance * NPCInfo->stats.shootDistance;
1476 }
1477
1478 switch ( NPC->s.weapon )
1479 {
1480 case WP_BLASTER://scav rifle
1481 return 1024 * 1024;//should be shorter?
1482 break;
1483
1484 case WP_BRYAR_PISTOL://prifle
1485 return 1024 * 1024;
1486 break;
1487
1488 case WP_BLASTER_PISTOL://prifle
1489 return 1024 * 1024;
1490 break;
1491
1492 case WP_DISRUPTOR://disruptor
1493 case WP_TUSKEN_RIFLE:
1494 if ( NPCInfo->scriptFlags & SCF_ALT_FIRE )
1495 {
1496 return ( 4096 * 4096 );
1497 }
1498 else
1499 {
1500 return 1024 * 1024;
1501 }
1502 break;
1503 /*
1504 case WP_SABER:
1505 return 1024 * 1024;
1506 break;
1507 */
1508 case WP_SABER:
1509 if ( NPC->client && NPC->client->ps.SaberLength() )
1510 {//FIXME: account for whether enemy and I are heading towards each other!
1511 return (NPC->client->ps.SaberLength() + NPC->maxs[0]*1.5)*(NPC->client->ps.SaberLength() + NPC->maxs[0]*1.5);
1512 }
1513 else
1514 {
1515 return 48*48;
1516 }
1517 break;
1518
1519 default:
1520 return 1024 * 1024;//was 0
1521 break;
1522 }
1523 }
1524
1525
1526
NPC_EnemyTooFar(gentity_t * enemy,float dist,qboolean toShoot)1527 qboolean NPC_EnemyTooFar(gentity_t *enemy, float dist, qboolean toShoot)
1528 {
1529 vec3_t vec;
1530
1531
1532 if ( !toShoot )
1533 {//Not trying to actually press fire button with this check
1534 if ( NPC->client->ps.weapon == WP_SABER )
1535 {//Just have to get to him
1536 return qfalse;
1537 }
1538 }
1539
1540
1541 if(!dist)
1542 {
1543 VectorSubtract(NPC->currentOrigin, enemy->currentOrigin, vec);
1544 dist = VectorLengthSquared(vec);
1545 }
1546
1547 if(dist > NPC_MaxDistSquaredForWeapon())
1548 return qtrue;
1549
1550 return qfalse;
1551 }
1552
1553 /*
1554 NPC_PickEnemy
1555
1556 Randomly picks a living enemy from the specified team and returns it
1557
1558 FIXME: For now, you MUST specify an enemy team
1559
1560 If you specify choose closest, it will find only the closest enemy
1561
1562 If you specify checkVis, it will return and enemy that is visible
1563
1564 If you specify findPlayersFirst, it will try to find players first
1565
1566 You can mix and match any of those options (example: find closest visible players first)
1567
1568 FIXME: this should go through the snapshot and find the closest enemy
1569 */
NPC_PickEnemy(gentity_t * closestTo,int enemyTeam,qboolean checkVis,qboolean findPlayersFirst,qboolean findClosest)1570 gentity_t *NPC_PickEnemy( gentity_t *closestTo, int enemyTeam, qboolean checkVis, qboolean findPlayersFirst, qboolean findClosest )
1571 {
1572 int num_choices = 0;
1573 int choice[128];//FIXME: need a different way to determine how many choices?
1574 gentity_t *newenemy = NULL;
1575 gentity_t *closestEnemy = NULL;
1576 int entNum;
1577 vec3_t diff;
1578 float relDist;
1579 float bestDist = Q3_INFINITE;
1580 qboolean failed = qfalse;
1581 int visChecks = (CHECK_360|CHECK_FOV|CHECK_VISRANGE);
1582 int minVis = VIS_FOV;
1583
1584 if ( enemyTeam == TEAM_NEUTRAL )
1585 {
1586 return NULL;
1587 }
1588
1589 if ( NPCInfo->behaviorState == BS_STAND_AND_SHOOT ||
1590 NPCInfo->behaviorState == BS_HUNT_AND_KILL )
1591 {//Formations guys don't require inFov to pick up a target
1592 //These other behavior states are active battle states and should not
1593 //use FOV. FOV checks are for enemies who are patrolling, guarding, etc.
1594 visChecks &= ~CHECK_FOV;
1595 minVis = VIS_360;
1596 }
1597
1598 if( findPlayersFirst )
1599 {//try to find a player first
1600 newenemy = &g_entities[0];
1601 if( newenemy->client && !(newenemy->flags & FL_NOTARGET) && !(newenemy->s.eFlags & EF_NODRAW))
1602 {
1603 if( newenemy->health > 0 )
1604 {
1605 if( NPC_ValidEnemy( newenemy) )//enemyTeam == TEAM_PLAYER || newenemy->client->playerTeam == enemyTeam || ( enemyTeam == TEAM_PLAYER ) )
1606 {//FIXME: check for range and FOV or vis?
1607 if( newenemy != NPC->lastEnemy )
1608 {//Make sure we're not just going back and forth here
1609 if ( gi.inPVS(newenemy->currentOrigin, NPC->currentOrigin) )
1610 {
1611 if(NPCInfo->behaviorState == BS_INVESTIGATE || NPCInfo->behaviorState == BS_PATROL)
1612 {
1613 if(!NPC->enemy)
1614 {
1615 if(!InVisrange(newenemy))
1616 {
1617 failed = qtrue;
1618 }
1619 else if(NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) != VIS_FOV)
1620 {
1621 failed = qtrue;
1622 }
1623 }
1624 }
1625
1626 if ( !failed )
1627 {
1628 VectorSubtract( closestTo->currentOrigin, newenemy->currentOrigin, diff );
1629 relDist = VectorLengthSquared(diff);
1630 if ( newenemy->client->hiddenDist > 0 )
1631 {
1632 if( relDist > newenemy->client->hiddenDist*newenemy->client->hiddenDist )
1633 {
1634 //out of hidden range
1635 if ( VectorLengthSquared( newenemy->client->hiddenDir ) )
1636 {//They're only hidden from a certain direction, check
1637 float dot;
1638 VectorNormalize( diff );
1639 dot = DotProduct( newenemy->client->hiddenDir, diff );
1640 if ( dot > 0.5 )
1641 {//I'm not looking in the right dir toward them to see them
1642 failed = qtrue;
1643 }
1644 else
1645 {
1646 Debug_Printf(debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDir %s targetDir %s dot %f\n", NPC->targetname, newenemy->targetname, vtos(newenemy->client->hiddenDir), vtos(diff), dot );
1647 }
1648 }
1649 else
1650 {
1651 failed = qtrue;
1652 }
1653 }
1654 else
1655 {
1656 Debug_Printf(debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDist %f\n", NPC->targetname, newenemy->targetname, newenemy->client->hiddenDist );
1657 }
1658 }
1659
1660 if(!failed)
1661 {
1662 if(findClosest)
1663 {
1664 if(relDist < bestDist)
1665 {
1666 if(!NPC_EnemyTooFar(newenemy, relDist, qfalse))
1667 {
1668 if(checkVis)
1669 {
1670 if( NPC_CheckVisibility ( newenemy, visChecks ) == minVis )
1671 {
1672 bestDist = relDist;
1673 closestEnemy = newenemy;
1674 }
1675 }
1676 else
1677 {
1678 bestDist = relDist;
1679 closestEnemy = newenemy;
1680 }
1681 }
1682 }
1683 }
1684 else if(!NPC_EnemyTooFar(newenemy, 0, qfalse))
1685 {
1686 if(checkVis)
1687 {
1688 if( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) == VIS_FOV )
1689 {
1690 choice[num_choices++] = newenemy->s.number;
1691 }
1692 }
1693 else
1694 {
1695 choice[num_choices++] = newenemy->s.number;
1696 }
1697 }
1698 }
1699 }
1700 }
1701 }
1702 }
1703 }
1704 }
1705 }
1706
1707 if (findClosest && closestEnemy)
1708 {
1709 return closestEnemy;
1710 }
1711
1712 if (num_choices)
1713 {
1714 return &g_entities[ choice[rand() % num_choices] ];
1715 }
1716
1717 /*
1718 //FIXME: used to have an option to look *only* for the player... now...? Still need it?
1719 if ( enemyTeam == TEAM_PLAYER )
1720 {//couldn't find the player
1721 return NULL;
1722 }
1723 */
1724
1725 num_choices = 0;
1726 bestDist = Q3_INFINITE;
1727 closestEnemy = NULL;
1728
1729 for ( entNum = 0; entNum < globals.num_entities; entNum++ )
1730 {
1731 newenemy = &g_entities[entNum];
1732
1733 if ( newenemy != NPC && (newenemy->client || newenemy->svFlags & SVF_NONNPC_ENEMY) && !(newenemy->flags & FL_NOTARGET) && !(newenemy->s.eFlags & EF_NODRAW))
1734 {
1735 if ( newenemy->health > 0 )
1736 {
1737 if ( (newenemy->client && NPC_ValidEnemy( newenemy))
1738 || (!newenemy->client && newenemy->noDamageTeam == enemyTeam) )
1739 {//FIXME: check for range and FOV or vis?
1740 if ( NPC->client->playerTeam == TEAM_PLAYER && enemyTeam == TEAM_PLAYER )
1741 {//player allies turning on ourselves? How?
1742 if ( newenemy->s.number )
1743 {//only turn on the player, not other player allies
1744 continue;
1745 }
1746 }
1747
1748 if ( newenemy != NPC->lastEnemy )
1749 {//Make sure we're not just going back and forth here
1750 if(!gi.inPVS(newenemy->currentOrigin, NPC->currentOrigin))
1751 {
1752 continue;
1753 }
1754
1755 if ( NPCInfo->behaviorState == BS_INVESTIGATE || NPCInfo->behaviorState == BS_PATROL )
1756 {
1757 if ( !NPC->enemy )
1758 {
1759 if ( !InVisrange( newenemy ) )
1760 {
1761 continue;
1762 }
1763 else if ( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) != VIS_FOV )
1764 {
1765 continue;
1766 }
1767 }
1768 }
1769
1770 VectorSubtract( closestTo->currentOrigin, newenemy->currentOrigin, diff );
1771 relDist = VectorLengthSquared(diff);
1772 if ( newenemy->client && newenemy->client->hiddenDist > 0 )
1773 {
1774 if( relDist > newenemy->client->hiddenDist*newenemy->client->hiddenDist )
1775 {
1776 //out of hidden range
1777 if ( VectorLengthSquared( newenemy->client->hiddenDir ) )
1778 {//They're only hidden from a certain direction, check
1779 float dot;
1780
1781 VectorNormalize( diff );
1782 dot = DotProduct( newenemy->client->hiddenDir, diff );
1783 if ( dot > 0.5 )
1784 {//I'm not looking in the right dir toward them to see them
1785 continue;
1786 }
1787 else
1788 {
1789 Debug_Printf(debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDir %s targetDir %s dot %f\n", NPC->targetname, newenemy->targetname, vtos(newenemy->client->hiddenDir), vtos(diff), dot );
1790 }
1791 }
1792 else
1793 {
1794 continue;
1795 }
1796 }
1797 else
1798 {
1799 Debug_Printf(debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDist %f\n", NPC->targetname, newenemy->targetname, newenemy->client->hiddenDist );
1800 }
1801 }
1802
1803 if ( findClosest )
1804 {
1805 if ( relDist < bestDist )
1806 {
1807 if ( !NPC_EnemyTooFar( newenemy, relDist, qfalse ) )
1808 {
1809 if ( checkVis )
1810 {
1811 //FIXME: NPCs need to be able to pick up other NPCs behind them,
1812 //but for now, commented out because it was picking up enemies it shouldn't
1813 //if ( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_VISRANGE ) >= VIS_360 )
1814 if ( NPC_CheckVisibility ( newenemy, visChecks ) == minVis )
1815 {
1816 bestDist = relDist;
1817 closestEnemy = newenemy;
1818 }
1819 }
1820 else
1821 {
1822 bestDist = relDist;
1823 closestEnemy = newenemy;
1824 }
1825 }
1826 }
1827 }
1828 else if ( !NPC_EnemyTooFar( newenemy, 0, qfalse ) )
1829 {
1830 if ( checkVis )
1831 {
1832 //if( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) == VIS_FOV )
1833 if ( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_VISRANGE ) >= VIS_360 )
1834 {
1835 choice[num_choices++] = newenemy->s.number;
1836 }
1837 }
1838 else
1839 {
1840 choice[num_choices++] = newenemy->s.number;
1841 }
1842 }
1843 }
1844 }
1845 }
1846 }
1847 }
1848
1849
1850 if (findClosest)
1851 {//FIXME: you can pick up an enemy around a corner this way.
1852 return closestEnemy;
1853 }
1854
1855 if (!num_choices)
1856 {
1857 return NULL;
1858 }
1859
1860 return &g_entities[ choice[rand() % num_choices] ];
1861 }
1862
1863 /*
1864 gentity_t *NPC_PickAlly ( void )
1865
1866 Simply returns closest visible ally
1867 */
1868
NPC_PickAlly(qboolean facingEachOther,float range,qboolean ignoreGroup,qboolean movingOnly)1869 gentity_t *NPC_PickAlly ( qboolean facingEachOther, float range, qboolean ignoreGroup, qboolean movingOnly )
1870 {
1871 gentity_t *ally = NULL;
1872 gentity_t *closestAlly = NULL;
1873 int entNum;
1874 vec3_t diff;
1875 float relDist;
1876 float bestDist = range;
1877
1878 for ( entNum = 0; entNum < globals.num_entities; entNum++ )
1879 {
1880 ally = &g_entities[entNum];
1881
1882 if ( ally->client )
1883 {
1884 if ( ally->health > 0 )
1885 {
1886 if ( ally->client && ( ally->client->playerTeam == NPC->client->playerTeam ||
1887 NPC->client->playerTeam == TEAM_ENEMY ) )// && ally->client->playerTeam == TEAM_DISGUISE ) ) )
1888 {//if on same team or if player is disguised as your team
1889 if ( ignoreGroup )
1890 {
1891 if ( ally == NPC->client->leader )
1892 {
1893 //reject
1894 continue;
1895 }
1896 if ( ally->client && ally->client->leader && ally->client->leader == NPC )
1897 {
1898 //reject
1899 continue;
1900 }
1901 }
1902
1903 if(!gi.inPVS(ally->currentOrigin, NPC->currentOrigin))
1904 {
1905 continue;
1906 }
1907
1908 if ( movingOnly && ally->client && NPC->client )
1909 {//They have to be moving relative to each other
1910 if ( !DistanceSquared( ally->client->ps.velocity, NPC->client->ps.velocity ) )
1911 {
1912 continue;
1913 }
1914 }
1915
1916 VectorSubtract( NPC->currentOrigin, ally->currentOrigin, diff );
1917 relDist = VectorNormalize( diff );
1918 if ( relDist < bestDist )
1919 {
1920 if ( facingEachOther )
1921 {
1922 vec3_t vf;
1923 float dot;
1924
1925 AngleVectors( ally->client->ps.viewangles, vf, NULL, NULL );
1926 VectorNormalize(vf);
1927 dot = DotProduct(diff, vf);
1928
1929 if ( dot < 0.5 )
1930 {//Not facing in dir to me
1931 continue;
1932 }
1933 //He's facing me, am I facing him?
1934 AngleVectors( NPC->client->ps.viewangles, vf, NULL, NULL );
1935 VectorNormalize(vf);
1936 dot = DotProduct(diff, vf);
1937
1938 if ( dot > -0.5 )
1939 {//I'm not facing opposite of dir to me
1940 continue;
1941 }
1942 //I am facing him
1943 }
1944
1945 if ( NPC_CheckVisibility ( ally, CHECK_360|CHECK_VISRANGE ) >= VIS_360 )
1946 {
1947 bestDist = relDist;
1948 closestAlly = ally;
1949 }
1950 }
1951 }
1952 }
1953 }
1954 }
1955
1956
1957 return closestAlly;
1958 }
1959
NPC_CheckEnemy(qboolean findNew,qboolean tooFarOk,qboolean setEnemy)1960 gentity_t *NPC_CheckEnemy( qboolean findNew, qboolean tooFarOk, qboolean setEnemy )
1961 {
1962 qboolean forcefindNew = qfalse;
1963 gentity_t *closestTo;
1964 gentity_t *newEnemy = NULL;
1965 //FIXME: have a "NPCInfo->persistance" we can set to determine how long to try to shoot
1966 //someone we can't hit? Rather than hard-coded 10?
1967
1968 //FIXME they shouldn't recognize enemy's death instantly
1969
1970 //TEMP FIX:
1971 //if(NPC->enemy->client)
1972 //{
1973 // NPC->enemy->health = NPC->enemy->client->ps.stats[STAT_HEALTH];
1974 //}
1975
1976 if ( NPC->enemy )
1977 {
1978 if ( !NPC->enemy->inuse )//|| NPC->enemy == NPC )//wtf? NPCs should never get mad at themselves!
1979 {
1980 if ( setEnemy )
1981 {
1982 G_ClearEnemy( NPC );
1983 }
1984 }
1985 }
1986
1987 if ( NPC->svFlags & SVF_IGNORE_ENEMIES )
1988 {//We're ignoring all enemies for now
1989 if ( setEnemy )
1990 {
1991 G_ClearEnemy( NPC );
1992 }
1993 return NULL;
1994 }
1995
1996 // Kyle does not get new enemies if not close to his leader
1997 if (NPC->client->NPC_class==CLASS_KYLE &&
1998 NPC->client->leader &&
1999 Distance(NPC->client->leader->currentOrigin, NPC->currentOrigin)>3000
2000 )
2001 {
2002 if (NPC->enemy)
2003 {
2004 G_ClearEnemy( NPC );
2005 }
2006 return NULL;
2007 }
2008
2009
2010 if ( NPC->svFlags & SVF_LOCKEDENEMY )
2011 {//keep this enemy until dead
2012 if ( NPC->enemy )
2013 {
2014 if ( (!NPC->NPC && !(NPC->svFlags & SVF_NONNPC_ENEMY) ) || NPC->enemy->health > 0 )
2015 {//Enemy never had health (a train or info_not_null, etc) or did and is now dead (NPCs, turrets, etc)
2016 return NULL;
2017 }
2018 }
2019 NPC->svFlags &= ~SVF_LOCKEDENEMY;
2020 }
2021
2022 if ( NPC->enemy )
2023 {
2024 if ( NPC_EnemyTooFar(NPC->enemy, 0, qfalse) )
2025 {
2026 if(findNew)
2027 {//See if there is a close one and take it if so, else keep this one
2028 forcefindNew = qtrue;
2029 }
2030 else if(!tooFarOk)//FIXME: don't need this extra bool any more
2031 {
2032 if ( setEnemy )
2033 {
2034 G_ClearEnemy( NPC );
2035 }
2036 }
2037 }
2038 else if ( !gi.inPVS(NPC->currentOrigin, NPC->enemy->currentOrigin ) )
2039 {//FIXME: should this be a line-of site check?
2040 //FIXME: a lot of things check PVS AGAIN when deciding whether
2041 //or not to shoot, redundant!
2042 //Should we lose the enemy?
2043 //FIXME: if lose enemy, run lostenemyscript
2044 if ( NPC->enemy->client && NPC->enemy->client->hiddenDist )
2045 {//He ducked into shadow while we weren't looking
2046 //Drop enemy and see if we should search for him
2047 NPC_LostEnemyDecideChase();
2048 }
2049 else
2050 {//If we're not chasing him, we need to lose him
2051 //NOTE: since we no longer have bStates, really, this logic doesn't work, so never give him up
2052
2053 /*
2054 switch( NPCInfo->behaviorState )
2055 {
2056 case BS_HUNT_AND_KILL:
2057 //Okay to lose PVS, we're chasing them
2058 break;
2059 case BS_RUN_AND_SHOOT:
2060 //FIXME: only do this if !(NPCInfo->scriptFlags&SCF_CHASE_ENEMY)
2061 //If he's not our goalEntity, we're running somewhere else, so lose him
2062 if ( NPC->enemy != NPCInfo->goalEntity )
2063 {
2064 G_ClearEnemy( NPC );
2065 }
2066 break;
2067 default:
2068 //We're not chasing him, so lose him as an enemy
2069 G_ClearEnemy( NPC );
2070 break;
2071 }
2072 */
2073 }
2074 }
2075 }
2076
2077 if ( NPC->enemy )
2078 {
2079 if ( NPC->enemy->health <= 0 || NPC->enemy->flags & FL_NOTARGET )
2080 {
2081 if ( setEnemy )
2082 {
2083 G_ClearEnemy( NPC );
2084 }
2085 }
2086 }
2087
2088 closestTo = NPC;
2089 //FIXME: check your defendEnt, if you have one, see if their enemy is different
2090 //than yours, or, if they don't have one, pick the closest enemy to THEM?
2091 if ( NPCInfo->defendEnt )
2092 {//Trying to protect someone
2093 if ( NPCInfo->defendEnt->health > 0 )
2094 {//Still alive, We presume we're close to them, navigation should handle this?
2095 if ( NPCInfo->defendEnt->enemy )
2096 {//They were shot or acquired an enemy
2097 if ( NPC->enemy != NPCInfo->defendEnt->enemy )
2098 {//They have a different enemy, take it!
2099 newEnemy = NPCInfo->defendEnt->enemy;
2100 if ( setEnemy )
2101 {
2102 G_SetEnemy( NPC, NPCInfo->defendEnt->enemy );
2103 }
2104 }
2105 }
2106 else if ( NPC->enemy == NULL )
2107 {//We don't have an enemy, so find closest to defendEnt
2108 closestTo = NPCInfo->defendEnt;
2109 }
2110 }
2111 }
2112
2113 if (!NPC->enemy || ( NPC->enemy && NPC->enemy->health <= 0 ) || forcefindNew )
2114 {//FIXME: NPCs that are moving after an enemy should ignore the can't hit enemy counter- that should only be for NPCs that are standing still
2115 //NOTE: cantHitEnemyCounter >= 100 means we couldn't hit enemy for a full
2116 // 10 seconds, so give up. This means even if we're chasing him, we would
2117 // try to find another enemy after 10 seconds (assuming the cantHitEnemyCounter
2118 // is allowed to increment in a chasing bState)
2119 qboolean foundenemy = qfalse;
2120
2121 if(!findNew)
2122 {
2123 if ( setEnemy )
2124 {
2125 NPC->lastEnemy = NPC->enemy;
2126 G_ClearEnemy(NPC);
2127 }
2128 return NULL;
2129 }
2130
2131 //If enemy dead or unshootable, look for others on out enemy's team
2132 if ( NPC->client->enemyTeam != TEAM_NEUTRAL)
2133 {
2134 //NOTE: this only checks vis if can't hit enemy for 10 tries, which I suppose
2135 // means they need to find one that in more than just PVS
2136 //newenemy = NPC_PickEnemy( closestTo, NPC->client->enemyTeam, (NPC->cantHitEnemyCounter > 10), qfalse, qtrue );//3rd parm was (NPC->enemyTeam == TEAM_STARFLEET)
2137 //For now, made it so you ALWAYS have to check VIS
2138 newEnemy = NPC_PickEnemy( closestTo, NPC->client->enemyTeam, qtrue, qfalse, qtrue );//3rd parm was (NPC->enemyTeam == TEAM_STARFLEET)
2139 if ( newEnemy )
2140 {
2141 foundenemy = qtrue;
2142 if ( setEnemy )
2143 {
2144 G_SetEnemy( NPC, newEnemy );
2145 }
2146 }
2147 }
2148
2149 //if ( !forcefindNew )
2150 {
2151 if ( !foundenemy )
2152 {
2153 if ( setEnemy )
2154 {
2155 NPC->lastEnemy = NPC->enemy;
2156 G_ClearEnemy(NPC);
2157 }
2158 }
2159
2160 NPC->cantHitEnemyCounter = 0;
2161 }
2162 //FIXME: if we can't find any at all, go into INdependant NPC AI, pursue and kill
2163 }
2164
2165 if ( NPC->enemy && NPC->enemy->client )
2166 {
2167 if(NPC->enemy->client->playerTeam
2168 && NPC->enemy->client->playerTeam != TEAM_FREE)
2169 {
2170 // assert( NPC->client->playerTeam != NPC->enemy->client->playerTeam);
2171 if( NPC->client->playerTeam != NPC->enemy->client->playerTeam
2172 && NPC->client->enemyTeam != TEAM_FREE
2173 && NPC->client->enemyTeam != NPC->enemy->client->playerTeam )
2174 {
2175 NPC->client->enemyTeam = NPC->enemy->client->playerTeam;
2176 }
2177 }
2178 }
2179 return newEnemy;
2180 }
2181
2182 /*
2183 -------------------------
2184 NPC_ClearShot
2185 -------------------------
2186 */
2187
NPC_ClearShot(gentity_t * ent)2188 qboolean NPC_ClearShot( gentity_t *ent )
2189 {
2190 if ( ( NPC == NULL ) || ( ent == NULL ) )
2191 return qfalse;
2192
2193 vec3_t muzzle;
2194 trace_t tr;
2195
2196 CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
2197
2198 // add aim error
2199 // use weapon instead of specific npc types, although you could add certain npc classes if you wanted
2200 // if ( NPC->client->playerTeam == TEAM_SCAVENGERS )
2201 if( NPC->s.weapon == WP_BLASTER || NPC->s.weapon == WP_BLASTER_PISTOL ) // any other guns to check for?
2202 {
2203 vec3_t mins = { -2, -2, -2 };
2204 vec3_t maxs = { 2, 2, 2 };
2205
2206 gi.trace ( &tr, muzzle, mins, maxs, ent->currentOrigin, NPC->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
2207 }
2208 else
2209 {
2210 gi.trace ( &tr, muzzle, NULL, NULL, ent->currentOrigin, NPC->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
2211 }
2212
2213 if ( tr.startsolid || tr.allsolid )
2214 {
2215 return qfalse;
2216 }
2217
2218 if ( tr.entityNum == ent->s.number )
2219 return qtrue;
2220
2221 return qfalse;
2222 }
2223
2224 /*
2225 -------------------------
2226 NPC_ShotEntity
2227 -------------------------
2228 */
2229
NPC_ShotEntity(gentity_t * ent,vec3_t impactPos)2230 int NPC_ShotEntity( gentity_t *ent, vec3_t impactPos )
2231 {
2232 if ( ( NPC == NULL ) || ( ent == NULL ) )
2233 return qfalse;
2234
2235 vec3_t muzzle;
2236 vec3_t targ;
2237 trace_t tr;
2238
2239 if ( NPC->s.weapon == WP_THERMAL )
2240 {//thermal aims from slightly above head
2241 //FIXME: what about low-angle shots, rolling the thermal under something?
2242 vec3_t angles, forward, end;
2243
2244 CalcEntitySpot( NPC, SPOT_HEAD, muzzle );
2245 VectorSet( angles, 0, NPC->client->ps.viewangles[1], 0 );
2246 AngleVectors( angles, forward, NULL, NULL );
2247 VectorMA( muzzle, 8, forward, end );
2248 end[2] += 24;
2249 gi.trace ( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
2250 VectorCopy( tr.endpos, muzzle );
2251 }
2252 else
2253 {
2254 CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
2255 }
2256 CalcEntitySpot( ent, SPOT_CHEST, targ );
2257
2258 // add aim error
2259 // use weapon instead of specific npc types, although you could add certain npc classes if you wanted
2260 // if ( NPC->client->playerTeam == TEAM_SCAVENGERS )
2261 if( NPC->s.weapon == WP_BLASTER || NPC->s.weapon == WP_BLASTER_PISTOL ) // any other guns to check for?
2262 {
2263 vec3_t mins = { -2, -2, -2 };
2264 vec3_t maxs = { 2, 2, 2 };
2265
2266 gi.trace ( &tr, muzzle, mins, maxs, targ, NPC->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
2267 }
2268 else
2269 {
2270 gi.trace ( &tr, muzzle, NULL, NULL, targ, NPC->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
2271 }
2272 //FIXME: if using a bouncing weapon like the bowcaster, should we check the reflection of the wall, too?
2273 if ( impactPos )
2274 {//they want to know *where* the hit would be, too
2275 VectorCopy( tr.endpos, impactPos );
2276 }
2277 /* // NPCs should be able to shoot even if the muzzle would be inside their target
2278 if ( tr.startsolid || tr.allsolid )
2279 {
2280 return ENTITYNUM_NONE;
2281 }
2282 */
2283 return tr.entityNum;
2284 }
2285
NPC_EvaluateShot(int hit,qboolean glassOK)2286 qboolean NPC_EvaluateShot( int hit, qboolean glassOK )
2287 {
2288 if ( !NPC->enemy )
2289 {
2290 return qfalse;
2291 }
2292
2293 if ( hit == NPC->enemy->s.number || (&g_entities[hit] != NULL && (g_entities[hit].svFlags&SVF_GLASS_BRUSH)) )
2294 {//can hit enemy or will hit glass, so shoot anyway
2295 return qtrue;
2296 }
2297 return qfalse;
2298 }
2299
2300 /*
2301 NPC_CheckAttack
2302
2303 Simply checks aggression and returns true or false
2304 */
2305
NPC_CheckAttack(float scale)2306 qboolean NPC_CheckAttack (float scale)
2307 {
2308 if(!scale)
2309 scale = 1.0;
2310
2311 if(((float)NPCInfo->stats.aggression) * scale < Q_flrand(0, 4))
2312 {
2313 return qfalse;
2314 }
2315
2316 if(NPCInfo->shotTime > level.time)
2317 return qfalse;
2318
2319 return qtrue;
2320 }
2321
2322 /*
2323 NPC_CheckDefend
2324
2325 Simply checks evasion and returns true or false
2326 */
2327
NPC_CheckDefend(float scale)2328 qboolean NPC_CheckDefend (float scale)
2329 {
2330 if(!scale)
2331 scale = 1.0;
2332
2333 if((float)(NPCInfo->stats.evasion) > Q_flrand(0.0f, 1.0f) * 4 * scale)
2334 return qtrue;
2335
2336 return qfalse;
2337 }
2338
2339
2340 //NOTE: BE SURE TO CHECK PVS BEFORE THIS!
NPC_CheckCanAttack(float attack_scale,qboolean stationary)2341 qboolean NPC_CheckCanAttack (float attack_scale, qboolean stationary)
2342 {
2343 vec3_t delta, forward;
2344 vec3_t angleToEnemy;
2345 vec3_t hitspot, muzzle, diff, enemy_org;//, enemy_head;
2346 float distanceToEnemy;
2347 qboolean attack_ok = qfalse;
2348 // qboolean duck_ok = qfalse;
2349 qboolean dead_on = qfalse;
2350 float aim_off;
2351 float max_aim_off = 128 - (16 * (float)NPCInfo->stats.aim);
2352 trace_t tr;
2353 gentity_t *traceEnt = NULL;
2354
2355 if(NPC->enemy->flags & FL_NOTARGET)
2356 {
2357 return qfalse;
2358 }
2359
2360 //FIXME: only check to see if should duck if that provides cover from the
2361 //enemy!!!
2362 if(!attack_scale)
2363 {
2364 attack_scale = 1.0;
2365 }
2366 //Yaw to enemy
2367 CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org );
2368 NPC_AimWiggle( enemy_org );
2369
2370 CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
2371
2372 VectorSubtract (enemy_org, muzzle, delta);
2373 vectoangles ( delta, angleToEnemy );
2374 distanceToEnemy = VectorNormalize(delta);
2375
2376 NPC->NPC->desiredYaw = angleToEnemy[YAW];
2377 NPC_UpdateFiringAngles(qfalse, qtrue);
2378
2379 if( NPC_EnemyTooFar(NPC->enemy, distanceToEnemy*distanceToEnemy, qtrue) )
2380 {//Too far away? Do not attack
2381 return qfalse;
2382 }
2383
2384 if(client->fireDelay > 0)
2385 {//already waiting for a shot to fire
2386 NPC->NPC->desiredPitch = angleToEnemy[PITCH];
2387 NPC_UpdateFiringAngles(qtrue, qfalse);
2388 return qfalse;
2389 }
2390
2391 if(NPCInfo->scriptFlags & SCF_DONT_FIRE)
2392 {
2393 return qfalse;
2394 }
2395
2396 NPCInfo->enemyLastVisibility = enemyVisibility;
2397 //See if they're in our FOV and we have a clear shot to them
2398 enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_360|CHECK_FOV);////CHECK_PVS|
2399
2400 if(enemyVisibility >= VIS_FOV)
2401 {//He's in our FOV
2402
2403 attack_ok = qtrue;
2404 //CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_head);
2405
2406 //Check to duck
2407 if ( NPC->enemy->client )
2408 {
2409 if ( NPC->enemy->enemy == NPC )
2410 {
2411 if ( NPC->enemy->client->buttons & BUTTON_ATTACK )
2412 {//FIXME: determine if enemy fire angles would hit me or get close
2413 if ( NPC_CheckDefend( 1.0 ) )//FIXME: Check self-preservation? Health?
2414 {//duck and don't shoot
2415 attack_ok = qfalse;
2416 ucmd.upmove = -127;
2417 }
2418 }
2419 }
2420 }
2421
2422 if(attack_ok)
2423 {
2424 //are we gonna hit him
2425 //NEW: use actual forward facing
2426 AngleVectors( client->ps.viewangles, forward, NULL, NULL );
2427 VectorMA( muzzle, distanceToEnemy, forward, hitspot );
2428 gi.trace( &tr, muzzle, NULL, NULL, hitspot, NPC->s.number, MASK_SHOT, (EG2_Collision)0, 0 );
2429 ShotThroughGlass( &tr, NPC->enemy, hitspot, MASK_SHOT );
2430 /*
2431 //OLD: trace regardless of facing
2432 gi.trace ( &tr, muzzle, NULL, NULL, enemy_org, NPC->s.number, MASK_SHOT );
2433 ShotThroughGlass(&tr, NPC->enemy, enemy_org, MASK_SHOT);
2434 */
2435
2436 traceEnt = &g_entities[tr.entityNum];
2437
2438 /*
2439 if( traceEnt != NPC->enemy &&//FIXME: if someone on our team is in the way, suggest that they duck if possible
2440 (!traceEnt || !traceEnt->client || !NPC->client->enemyTeam || NPC->client->enemyTeam != traceEnt->client->playerTeam) )
2441 {//no, so shoot for somewhere between the head and torso
2442 //NOTE: yes, I know this looks weird, but it works
2443 enemy_org[0] += 0.3*Q_flrand(NPC->enemy->mins[0], NPC->enemy->maxs[0]);
2444 enemy_org[1] += 0.3*Q_flrand(NPC->enemy->mins[1], NPC->enemy->maxs[1]);
2445 enemy_org[2] -= NPC->enemy->maxs[2]*Q_flrand(0.0f, 1.0f);
2446
2447 attack_scale *= 0.75;
2448 gi.trace ( &tr, muzzle, NULL, NULL, enemy_org, NPC->s.number, MASK_SHOT );
2449 ShotThroughGlass(&tr, NPC->enemy, enemy_org, MASK_SHOT);
2450 traceEnt = &g_entities[tr.entityNum];
2451 }
2452 */
2453
2454 VectorCopy( tr.endpos, hitspot );
2455
2456 if( traceEnt == NPC->enemy || (traceEnt->client && NPC->client->enemyTeam && NPC->client->enemyTeam == traceEnt->client->playerTeam) )
2457 {
2458 dead_on = qtrue;
2459 }
2460 else
2461 {
2462 attack_scale *= 0.5;
2463 if(NPC->client->playerTeam)
2464 {
2465 if(traceEnt && traceEnt->client && traceEnt->client->playerTeam)
2466 {
2467 if(NPC->client->playerTeam == traceEnt->client->playerTeam)
2468 {//Don't shoot our own team
2469 attack_ok = qfalse;
2470 }
2471 }
2472 }
2473 }
2474 }
2475
2476 if( attack_ok )
2477 {
2478 //ok, now adjust pitch aim
2479 VectorSubtract (hitspot, muzzle, delta);
2480 vectoangles ( delta, angleToEnemy );
2481 NPC->NPC->desiredPitch = angleToEnemy[PITCH];
2482 NPC_UpdateFiringAngles(qtrue, qfalse);
2483
2484 if( !dead_on )
2485 {//We're not going to hit him directly, try a suppressing fire
2486 //see if where we're going to shoot is too far from his origin
2487 if(traceEnt && (traceEnt->health <= 30 || EntIsGlass(traceEnt)))
2488 {//easy to kill - go for it
2489 if(traceEnt->e_DieFunc == dieF_ExplodeDeath_Wait && traceEnt->splashDamage)
2490 {//going to explode, don't shoot if close to self
2491 VectorSubtract(NPC->currentOrigin, traceEnt->currentOrigin, diff);
2492 if(VectorLengthSquared(diff) < traceEnt->splashRadius*traceEnt->splashRadius)
2493 {//Too close to shoot!
2494 attack_ok = qfalse;
2495 }
2496 else
2497 {//Hey, it might kill him, do it!
2498 attack_scale *= 2;//
2499 }
2500 }
2501 }
2502 else
2503 {
2504 AngleVectors (client->ps.viewangles, forward, NULL, NULL);
2505 VectorMA ( muzzle, distanceToEnemy, forward, hitspot);
2506 VectorSubtract(hitspot, enemy_org, diff);
2507 aim_off = VectorLength(diff);
2508 if(aim_off > Q_flrand(0.0f, 1.0f) * max_aim_off)//FIXME: use aim value to allow poor aim?
2509 {
2510 attack_scale *= 0.75;
2511 //see if where we're going to shoot is too far from his head
2512 VectorSubtract(hitspot, enemy_org, diff);
2513 aim_off = VectorLength(diff);
2514 if(aim_off > Q_flrand(0.0f, 1.0f) * max_aim_off)
2515 {
2516 attack_ok = qfalse;
2517 }
2518 }
2519 attack_scale *= (max_aim_off - aim_off + 1)/max_aim_off;
2520 }
2521 }
2522 }
2523 }
2524 else
2525 {//Update pitch anyway
2526 NPC->NPC->desiredPitch = angleToEnemy[PITCH];
2527 NPC_UpdateFiringAngles(qtrue, qfalse);
2528 }
2529
2530 if( attack_ok )
2531 {
2532 if( NPC_CheckAttack( attack_scale ))
2533 {//check aggression to decide if we should shoot
2534 enemyVisibility = VIS_SHOOT;
2535 WeaponThink(qtrue);
2536 }
2537 else
2538 attack_ok = qfalse;
2539 }
2540
2541 return attack_ok;
2542 }
2543 //========================================================================================
2544 //OLD id-style hunt and kill
2545 //========================================================================================
2546 /*
2547 IdealDistance
2548
2549 determines what the NPC's ideal distance from it's enemy should
2550 be in the current situation
2551 */
IdealDistance(gentity_t * self)2552 float IdealDistance ( gentity_t *self )
2553 {
2554 float ideal;
2555
2556 ideal = 225 - 20 * NPCInfo->stats.aggression;
2557 switch ( NPC->s.weapon )
2558 {
2559 case WP_ROCKET_LAUNCHER:
2560 ideal += 200;
2561 break;
2562
2563 case WP_CONCUSSION:
2564 ideal += 200;
2565 break;
2566
2567 case WP_THERMAL:
2568 ideal += 50;
2569 break;
2570
2571 case WP_SABER:
2572 case WP_BRYAR_PISTOL:
2573 case WP_BLASTER_PISTOL:
2574 case WP_BLASTER:
2575 default:
2576 break;
2577 }
2578
2579 return ideal;
2580 }
2581
2582 /*QUAKED point_combat (0.7 0 0.7) (-20 -20 -24) (20 20 45) DUCK FLEE INVESTIGATE SQUAD LEAN SNIPE
2583 NPCs in bState BS_COMBAT_POINT will find their closest empty combat_point
2584
2585 DUCK - NPC will duck and fire from this point, NOT IMPLEMENTED?
2586 FLEE - Will choose this point when running
2587 INVESTIGATE - Will look here if a sound is heard near it
2588 SQUAD - NOT IMPLEMENTED
2589 LEAN - Lean-type cover, NOT IMPLEMENTED
2590 SNIPE - Snipers look for these first, NOT IMPLEMENTED
2591 */
2592
SP_point_combat(gentity_t * self)2593 void SP_point_combat( gentity_t *self )
2594 {
2595 if(level.numCombatPoints >= MAX_COMBAT_POINTS)
2596 {
2597 #ifndef FINAL_BUILD
2598 gi.Printf(S_COLOR_RED"ERROR: Too many combat points, limit is %d\n", MAX_COMBAT_POINTS);
2599 #endif
2600 G_FreeEntity(self);
2601 return;
2602 }
2603
2604 self->s.origin[2] += 0.125;
2605 G_SetOrigin(self, self->s.origin);
2606 gi.linkentity(self);
2607
2608 if ( G_CheckInSolid( self, qtrue ) )
2609 {
2610 #ifndef FINAL_BUILD
2611 gi.Printf( S_COLOR_RED"ERROR: combat point at %s in solid!\n", vtos(self->currentOrigin) );
2612 #endif
2613 }
2614
2615 VectorCopy( self->currentOrigin, level.combatPoints[level.numCombatPoints].origin );
2616
2617 level.combatPoints[level.numCombatPoints].flags = self->spawnflags;
2618 level.combatPoints[level.numCombatPoints].occupied = qfalse;
2619
2620 level.numCombatPoints++;
2621
2622 NAV::SpawnedPoint(self, NAV::PT_COMBATNODE);
2623
2624 G_FreeEntity(self);
2625 };
2626
CP_FindCombatPointWaypoints(void)2627 void CP_FindCombatPointWaypoints( void )
2628 {
2629 for ( int i = 0; i < level.numCombatPoints; i++ )
2630 {
2631 level.combatPoints[i].waypoint = NAV::GetNearestNode(level.combatPoints[i].origin);
2632 if ( level.combatPoints[i].waypoint == WAYPOINT_NONE )
2633 {
2634 assert(0);
2635 level.combatPoints[i].waypoint = NAV::GetNearestNode(level.combatPoints[i].origin);
2636 gi.Printf( S_COLOR_RED"ERROR: Combat Point at %s has no waypoint!\n", vtos(level.combatPoints[i].origin) );
2637 delayedShutDown = level.time + 100;
2638 }
2639 }
2640 }
2641
2642
2643 /*
2644 -------------------------
2645 NPC_CollectCombatPoints
2646 -------------------------
2647 */
2648
2649 typedef std::map< float, int > combatPoint_m;
2650
NPC_CollectCombatPoints(const vec3_t origin,const float radius,combatPoint_m & points,const int flags)2651 static int NPC_CollectCombatPoints( const vec3_t origin, const float radius, combatPoint_m &points, const int flags )
2652 {
2653 float radiusSqr = (radius*radius);
2654 float distance;
2655
2656 //Collect all nearest
2657 for ( int i = 0; i < level.numCombatPoints; i++ )
2658 {
2659 //Must be vacant
2660 if ( level.combatPoints[i].occupied == (int) qtrue )
2661 continue;
2662
2663 //If we want a duck space, make sure this is one
2664 if ( ( flags & CP_DUCK ) && !( level.combatPoints[i].flags & CPF_DUCK ) )
2665 continue;
2666
2667 //If we want a flee point, make sure this is one
2668 if ( ( flags & CP_FLEE ) && !( level.combatPoints[i].flags & CPF_FLEE ) )
2669 continue;
2670
2671 //If we want a snipe point, make sure this is one
2672 if ( ( flags & CP_SNIPE ) && !( level.combatPoints[i].flags & CPF_SNIPE ) )
2673 continue;
2674
2675 ///Make sure this is an investigate combat point
2676 if ( ( flags & CP_INVESTIGATE ) && ( level.combatPoints[i].flags & CPF_INVESTIGATE ) )
2677 continue;
2678
2679 //Squad points are only valid if we're looking for them
2680 if ( ( level.combatPoints[i].flags & CPF_SQUAD ) && ( ( flags & CP_SQUAD ) == qfalse ) )
2681 continue;
2682
2683 if ( flags&CP_NO_PVS )
2684 {//must not be within PVS of mu current origin
2685 if ( gi.inPVS( origin, level.combatPoints[i].origin ) )
2686 {
2687 continue;
2688 }
2689 }
2690
2691 if ( flags&CP_HORZ_DIST_COLL )
2692 {
2693 distance = DistanceHorizontalSquared( origin, level.combatPoints[i].origin );
2694 }
2695 else
2696 {
2697 distance = DistanceSquared( origin, level.combatPoints[i].origin );
2698 }
2699
2700 if ( distance < radiusSqr )
2701 {
2702 //Using a map will sort nearest automatically
2703 points[ distance ] = i;
2704 }
2705 }
2706
2707 return points.size();
2708 }
2709
2710 /*
2711 -------------------------
2712 NPC_FindCombatPoint
2713 -------------------------
2714 */
2715
2716 #define MIN_AVOID_DOT 0.7f
2717 #define MIN_AVOID_DISTANCE 128
2718 #define MIN_AVOID_DISTANCE_SQUARED ( MIN_AVOID_DISTANCE * MIN_AVOID_DISTANCE )
2719 #define CP_COLLECT_RADIUS 512.0f
2720
NPC_FindCombatPoint(const vec3_t position,const vec3_t avoidPosition,vec3_t destPosition,const int flags,float avoidDist,const int ignorePoint)2721 int NPC_FindCombatPoint( const vec3_t position, const vec3_t avoidPosition, vec3_t destPosition, const int flags, float avoidDist, const int ignorePoint )
2722 {
2723 combatPoint_m points;
2724 combatPoint_m::iterator cpi;
2725
2726 int best = -1;//, cost, bestCost = Q3_INFINITE, waypoint = WAYPOINT_NONE, destWaypoint = WAYPOINT_NONE;
2727 trace_t tr;
2728 float collRad = CP_COLLECT_RADIUS;
2729 vec3_t eDir2Me, eDir2CP, weaponOffset;
2730 vec3_t enemyPosition;
2731 float dotToCp;
2732 //float distSqPointToNPC;
2733 float distSqPointToEnemy;
2734 float distSqPointToEnemyHoriz;
2735 float distSqPointToEnemyCheck;
2736 float distSqNPCToEnemy;
2737 float distSqNPCToEnemyHoriz;
2738 float distSqNPCToEnemyCheck;
2739 float visRangeSq = (NPCInfo->stats.visrange*NPCInfo->stats.visrange);
2740 bool useHorizDist = (NPC->s.weapon==WP_THERMAL) || (flags & CP_HORZ_DIST_COLL);
2741
2742 if (NPC->enemy)
2743 {
2744 VectorCopy(NPC->enemy->currentOrigin, enemyPosition);
2745 }
2746 else if (avoidPosition)
2747 {
2748 VectorCopy(avoidPosition, enemyPosition);
2749 }
2750 else if (destPosition)
2751 {
2752 VectorCopy(destPosition, enemyPosition);
2753 }
2754 else
2755 {
2756 VectorCopy(NPC->currentOrigin, enemyPosition);
2757 }
2758
2759 if ( avoidDist <= 0 )
2760 {
2761 avoidDist = MIN_AVOID_DISTANCE_SQUARED;
2762 }
2763 else
2764 {
2765 avoidDist *= avoidDist;
2766 }
2767
2768
2769 //Collect our nearest points
2770 if ( (flags & CP_NO_PVS) || (flags & CP_TRYFAR))
2771 {//much larger radius since most will be dropped?
2772 collRad = CP_COLLECT_RADIUS*4;
2773 }
2774 NPC_CollectCombatPoints( destPosition, collRad, points, flags );//position
2775
2776 for ( cpi = points.begin(); cpi != points.end(); ++cpi )
2777 {
2778 const int i = (*cpi).second;
2779
2780 //Must not be one we want to ignore
2781 if ( i == ignorePoint )
2782 {
2783 continue;
2784 }
2785
2786 //Get some distances for reasoning
2787 //distSqPointToNPC = (*cpi).first;
2788
2789 distSqPointToEnemy = DistanceSquared (level.combatPoints[i].origin, enemyPosition);
2790 distSqPointToEnemyHoriz = DistanceHorizontalSquared(level.combatPoints[i].origin, enemyPosition);
2791 distSqPointToEnemyCheck = (useHorizDist)?(distSqPointToEnemyHoriz):(distSqPointToEnemy);
2792
2793 distSqNPCToEnemy = DistanceSquared (NPC->currentOrigin, enemyPosition);
2794 distSqNPCToEnemyHoriz = DistanceHorizontalSquared(NPC->currentOrigin, enemyPosition);
2795 distSqNPCToEnemyCheck = (useHorizDist)?(distSqNPCToEnemyHoriz ):(distSqNPCToEnemy);
2796
2797
2798
2799 //Ignore points that are farther than currently located
2800 if ( (flags & CP_APPROACH_ENEMY) && (distSqPointToEnemyCheck > distSqNPCToEnemyCheck))
2801 {
2802 continue;
2803 }
2804
2805 //Ignore points that are closer than currently located
2806 if ( (flags & CP_RETREAT) && (distSqPointToEnemyCheck < distSqNPCToEnemyCheck))
2807 {
2808 continue;
2809 }
2810
2811 //Ignore points that are out of vis range
2812 if ( (flags & CP_CLEAR) && (distSqPointToEnemyCheck > visRangeSq))
2813 {
2814 continue;
2815 }
2816
2817 //Avoid this position?
2818 if ( avoidPosition && !(flags & CP_AVOID_ENEMY) && (flags & CP_AVOID) && (DistanceSquared(level.combatPoints[i].origin, avoidPosition)<avoidDist) )
2819 {
2820 continue;
2821 }
2822
2823 //We want a point on other side of the enemy from current pos
2824 if (flags & CP_FLANK )
2825 {
2826 VectorSubtract( position, enemyPosition, eDir2Me );
2827 VectorNormalize( eDir2Me );
2828
2829 VectorSubtract( level.combatPoints[i].origin, enemyPosition, eDir2CP );
2830 VectorNormalize( eDir2CP );
2831
2832 dotToCp = DotProduct( eDir2Me, eDir2CP );
2833
2834 //Not far enough behind enemy from current pos
2835 if ( dotToCp >= 0.4 )
2836 {
2837 continue;
2838 }
2839 }
2840
2841 //we must have a route to the combat point
2842 if ( (flags & CP_HAS_ROUTE) && !NAV::InSameRegion(NPC, level.combatPoints[i].origin))
2843 {
2844 continue;
2845 }
2846
2847
2848 //See if we're trying to avoid our enemy
2849 if (flags & CP_AVOID_ENEMY)
2850 {
2851 //Can't be too close to the enemy
2852 if (distSqPointToEnemy<avoidDist)
2853 {
2854 continue;
2855 }
2856
2857 // otherwise, if currently safe and the path is not safe, ignore this point
2858 if (distSqNPCToEnemy>(avoidDist) &&
2859 !NAV::SafePathExists(position, level.combatPoints[i].origin, enemyPosition, avoidDist))
2860 {
2861 continue;
2862 }
2863 }
2864
2865 //Okay, now make sure it's not blocked
2866 gi.trace( &tr, level.combatPoints[i].origin, NPC->mins, NPC->maxs, level.combatPoints[i].origin, NPC->s.number, NPC->clipmask, (EG2_Collision)0, 0 );
2867 if ( tr.allsolid || tr.startsolid )
2868 {
2869 continue;
2870 }
2871
2872 if (NPC->enemy)
2873 {
2874 // Ignore Points That Do Not Have A Clear LOS To The Player
2875 if ( (flags & CP_CLEAR) )
2876 {
2877 CalcEntitySpot(NPC, SPOT_WEAPON, weaponOffset);
2878 VectorSubtract(weaponOffset, NPC->currentOrigin, weaponOffset);
2879 VectorAdd(weaponOffset, level.combatPoints[i].origin, weaponOffset);
2880
2881 if (NPC_ClearLOS(weaponOffset, NPC->enemy)==qfalse)
2882 {
2883 continue;
2884 }
2885 }
2886
2887 // Ignore points that are not behind cover
2888 if ( (flags & CP_COVER) && NPC_ClearLOS(level.combatPoints[i].origin, NPC->enemy)==qtrue)
2889 {
2890 continue;
2891 }
2892 }
2893
2894 //they are sorted by this distance, so the first one to get this far is the closest
2895 return i;
2896 }
2897
2898 return best;
2899 }
2900
NPC_FindCombatPointRetry(const vec3_t position,const vec3_t avoidPosition,vec3_t destPosition,int * cpFlags,float avoidDist,const int ignorePoint)2901 int NPC_FindCombatPointRetry( const vec3_t position,
2902 const vec3_t avoidPosition,
2903 vec3_t destPosition,
2904 int *cpFlags,
2905 float avoidDist,
2906 const int ignorePoint )
2907 {
2908 int cp = -1;
2909 cp = NPC_FindCombatPoint( position,
2910 avoidPosition,
2911 destPosition,
2912 *cpFlags,
2913 avoidDist,
2914 ignorePoint );
2915 while ( cp == -1 && (*cpFlags&~CP_HAS_ROUTE) != CP_ANY )
2916 {//start "OR"ing out certain flags to see if we can find *any* point
2917 if ( *cpFlags & CP_INVESTIGATE )
2918 {//don't need to investigate
2919 *cpFlags &= ~CP_INVESTIGATE;
2920 }
2921 else if ( *cpFlags & CP_SQUAD )
2922 {//don't need to stick to squads
2923 *cpFlags &= ~CP_SQUAD;
2924 }
2925 else if ( *cpFlags & CP_DUCK )
2926 {//don't need to duck
2927 *cpFlags &= ~CP_DUCK;
2928 }
2929 else if ( *cpFlags & CP_NEAREST )
2930 {//don't need closest one to me
2931 *cpFlags &= ~CP_NEAREST;
2932 }
2933 else if ( *cpFlags & CP_FLANK )
2934 {//don't need to flank enemy
2935 *cpFlags &= ~CP_FLANK;
2936 }
2937 else if ( *cpFlags & CP_SAFE )
2938 {//don't need one that hasn't been shot at recently
2939 *cpFlags &= ~CP_SAFE;
2940 }
2941 else if ( *cpFlags & CP_CLOSEST )
2942 {//don't need to get closest to enemy
2943 *cpFlags &= ~CP_CLOSEST;
2944 //but let's try to approach at least
2945 *cpFlags |= CP_APPROACH_ENEMY;
2946 }
2947 else if ( *cpFlags & CP_APPROACH_ENEMY )
2948 {//don't need to approach enemy
2949 *cpFlags &= ~CP_APPROACH_ENEMY;
2950 }
2951 else if ( *cpFlags & CP_COVER )
2952 {//don't need cover
2953 *cpFlags &= ~CP_COVER;
2954 //but let's pick one that makes us duck
2955 //*cpFlags |= CP_DUCK;
2956 }
2957 // else if ( *cpFlags & CP_CLEAR )
2958 // {//don't need a clear shot to enemy
2959 // *cpFlags &= ~CP_CLEAR;
2960 // }
2961 // Never Give Up On Avoiding The Enemy
2962 // else if ( *cpFlags & CP_AVOID_ENEMY )
2963 // {//don't need to avoid enemy
2964 // *cpFlags &= ~CP_AVOID_ENEMY;
2965 // }
2966 else if ( *cpFlags & CP_RETREAT )
2967 {//don't need to retreat
2968 *cpFlags &= ~CP_RETREAT;
2969 }
2970 else if ( *cpFlags &CP_FLEE )
2971 {//don't need to flee
2972 *cpFlags &= ~CP_FLEE;
2973 //but at least avoid enemy and pick one that gives cover
2974 *cpFlags |= (CP_COVER|CP_AVOID_ENEMY);
2975 }
2976 else if ( *cpFlags & CP_AVOID )
2977 {//okay, even pick one right by me
2978 *cpFlags &= ~CP_AVOID;
2979 }
2980 else if ( *cpFlags & CP_SHORTEST_PATH )
2981 {//okay, don't need the one with the shortest path
2982 *cpFlags &= ~CP_SHORTEST_PATH;
2983 }
2984 else
2985 {//screw it, we give up!
2986 return -1;
2987 /*
2988 if ( *cpFlags & CP_HAS_ROUTE )
2989 {//NOTE: this is really an absolute worst case scenario - will go to the first cp on the map!
2990 *cpFlags &= ~CP_HAS_ROUTE;
2991 }
2992 else
2993 {//NOTE: this is really an absolute worst case scenario - will go to the first cp on the map!
2994 *cpFlags = CP_ANY;
2995 }
2996 */
2997 }
2998 //now try again
2999 cp = NPC_FindCombatPoint( position,
3000 avoidPosition,
3001 destPosition,
3002 *cpFlags,
3003 avoidDist,
3004 ignorePoint );
3005 }
3006 return cp;
3007 }
3008 /*
3009 -------------------------
3010 NPC_FindSquadPoint
3011 -------------------------
3012 */
3013
NPC_FindSquadPoint(vec3_t position)3014 int NPC_FindSquadPoint( vec3_t position )
3015 {
3016 float dist, nearestDist = (float)WORLD_SIZE*(float)WORLD_SIZE;
3017 int nearestPoint = -1;
3018
3019 //float playerDist = DistanceSquared( g_entities[0].currentOrigin, NPC->currentOrigin );
3020
3021 for ( int i = 0; i < level.numCombatPoints; i++ )
3022 {
3023 //Squad points are only valid if we're looking for them
3024 if ( ( level.combatPoints[i].flags & CPF_SQUAD ) == qfalse )
3025 continue;
3026
3027 //Must be vacant
3028 if ( level.combatPoints[i].occupied == qtrue )
3029 continue;
3030
3031 dist = DistanceSquared( position, level.combatPoints[i].origin );
3032
3033 //The point cannot take us past the player
3034 //if ( dist > ( playerDist * DotProduct( dirToPlayer, playerDir ) ) ) //FIXME: Retain this
3035 // continue;
3036
3037 //See if this is closer than the others
3038 if ( dist < nearestDist )
3039 {
3040 nearestPoint = i;
3041 nearestDist = dist;
3042 }
3043 }
3044
3045 return nearestPoint;
3046 }
3047
3048 /*
3049 -------------------------
3050 NPC_ReserveCombatPoint
3051 -------------------------
3052 */
3053
NPC_ReserveCombatPoint(int combatPointID)3054 qboolean NPC_ReserveCombatPoint( int combatPointID )
3055 {
3056 //Make sure it's valid
3057 if ( combatPointID > level.numCombatPoints )
3058 return qfalse;
3059
3060 //Make sure it's not already occupied
3061 if ( level.combatPoints[combatPointID].occupied )
3062 return qfalse;
3063
3064 //Reserve it
3065 level.combatPoints[combatPointID].occupied = qtrue;
3066
3067 return qtrue;
3068 }
3069
3070 /*
3071 -------------------------
3072 NPC_FreeCombatPoint
3073 -------------------------
3074 */
3075
NPC_FreeCombatPoint(int combatPointID,qboolean failed)3076 qboolean NPC_FreeCombatPoint( int combatPointID, qboolean failed )
3077 {
3078 if ( failed )
3079 {//remember that this one failed for us
3080 NPCInfo->lastFailedCombatPoint = combatPointID;
3081 }
3082 //Make sure it's valid
3083 if ( combatPointID > level.numCombatPoints )
3084 return qfalse;
3085
3086 //Make sure it's currently occupied
3087 if ( level.combatPoints[combatPointID].occupied == qfalse )
3088 return qfalse;
3089
3090 //Free it
3091 level.combatPoints[combatPointID].occupied = qfalse;
3092
3093 return qtrue;
3094 }
3095
3096 /*
3097 -------------------------
3098 NPC_SetCombatPoint
3099 -------------------------
3100 */
3101
NPC_SetCombatPoint(int combatPointID)3102 qboolean NPC_SetCombatPoint( int combatPointID )
3103 {
3104 if (combatPointID==NPCInfo->combatPoint)
3105 {
3106 return qtrue;
3107 }
3108
3109 //Free a combat point if we already have one
3110 if ( NPCInfo->combatPoint != -1 )
3111 {
3112 NPC_FreeCombatPoint( NPCInfo->combatPoint );
3113 }
3114
3115 if ( NPC_ReserveCombatPoint( combatPointID ) == qfalse )
3116 return qfalse;
3117
3118 NPCInfo->combatPoint = combatPointID;
3119
3120 return qtrue;
3121 }
3122
3123 extern qboolean CheckItemCanBePickedUpByNPC( gentity_t *item, gentity_t *pickerupper );
NPC_SearchForWeapons(void)3124 gentity_t *NPC_SearchForWeapons( void )
3125 {
3126 gentity_t *found = g_entities, *bestFound = NULL;
3127 float dist, bestDist = Q3_INFINITE;
3128 int i;
3129 // for ( found = g_entities; found < &g_entities[globals.num_entities] ; found++)
3130 for ( i = 0; i<globals.num_entities; i++)
3131 {
3132 // if ( !found->inuse )
3133 // {
3134 // continue;
3135 // }
3136 if(!PInUse(i))
3137 continue;
3138
3139 found=&g_entities[i];
3140
3141 //FIXME: Also look for ammo_racks that have weapons on them?
3142 if ( found->s.eType != ET_ITEM )
3143 {
3144 continue;
3145 }
3146 if ( found->item->giType != IT_WEAPON )
3147 {
3148 continue;
3149 }
3150 if ( found->s.eFlags & EF_NODRAW )
3151 {
3152 continue;
3153 }
3154 if ( CheckItemCanBePickedUpByNPC( found, NPC ) )
3155 {
3156 if ( gi.inPVS( found->currentOrigin, NPC->currentOrigin ) )
3157 {
3158 dist = DistanceSquared( found->currentOrigin, NPC->currentOrigin );
3159 if ( dist < bestDist )
3160 {
3161 if (NAV::InSameRegion(NPC, found))
3162 {//can nav to it
3163 bestDist = dist;
3164 bestFound = found;
3165 }
3166 }
3167 }
3168 }
3169 }
3170
3171 return bestFound;
3172 }
3173
NPC_SetPickUpGoal(gentity_t * foundWeap)3174 void NPC_SetPickUpGoal( gentity_t *foundWeap )
3175 {
3176 vec3_t org;
3177
3178 //NPCInfo->goalEntity = foundWeap;
3179 VectorCopy( foundWeap->currentOrigin, org );
3180 org[2] += 24 - (foundWeap->mins[2]*-1);//adjust the origin so that I am on the ground
3181 NPC_SetMoveGoal( NPC, org, foundWeap->maxs[0]*0.75, qfalse, -1, foundWeap );
3182 NPCInfo->tempGoal->waypoint = foundWeap->waypoint;
3183 NPCInfo->tempBehavior = BS_DEFAULT;
3184 NPCInfo->squadState = SQUAD_TRANSITION;
3185 }
3186
3187 extern void Q3_TaskIDComplete( gentity_t *ent, taskID_t taskType );
3188 extern qboolean G_CanPickUpWeapons( gentity_t *other );
NPC_CheckGetNewWeapon(void)3189 void NPC_CheckGetNewWeapon( void )
3190 {
3191 if ( NPC->client
3192 && !G_CanPickUpWeapons( NPC ) )
3193 {//this NPC can't pick up weapons...
3194 return;
3195 }
3196 if ( NPC->s.weapon == WP_NONE && NPC->enemy )
3197 {//if running away because dropped weapon...
3198 if ( NPCInfo->goalEntity
3199 && NPCInfo->goalEntity == NPCInfo->tempGoal
3200 && NPCInfo->goalEntity->enemy
3201 && !NPCInfo->goalEntity->enemy->inuse )
3202 {//maybe was running at a weapon that was picked up
3203 NPC_ClearGoal();
3204 Q3_TaskIDComplete( NPC, TID_MOVE_NAV );
3205 //NPCInfo->goalEntity = NULL;
3206 }
3207 if ( TIMER_Done( NPC, "panic" ) && NPCInfo->goalEntity == NULL )
3208 {//need a weapon, any lying around?
3209 gentity_t *foundWeap = NPC_SearchForWeapons();
3210 if ( foundWeap )
3211 {
3212 NPC_SetPickUpGoal( foundWeap );
3213 }
3214 }
3215 }
3216 }
3217
NPC_AimAdjust(int change)3218 void NPC_AimAdjust( int change )
3219 {
3220 if ( !TIMER_Exists( NPC, "aimDebounce" ) )
3221 {
3222 int debounce = 500+(3-g_spskill->integer)*100;
3223 TIMER_Set( NPC, "aimDebounce", Q_irand( debounce,debounce+1000 ) );
3224 //int debounce = 1000+(3-g_spskill->integer)*500;
3225 //TIMER_Set( NPC, "aimDebounce", Q_irand( debounce, debounce+2000 ) );
3226 return;
3227 }
3228 if ( TIMER_Done( NPC, "aimDebounce" ) )
3229 {
3230 NPCInfo->currentAim += change;
3231 if ( NPCInfo->currentAim > NPCInfo->stats.aim )
3232 {//can never be better than max aim
3233 NPCInfo->currentAim = NPCInfo->stats.aim;
3234 }
3235 else if ( NPCInfo->currentAim < -30 )
3236 {//can never be worse than this
3237 NPCInfo->currentAim = -30;
3238 }
3239
3240 //Com_Printf( "%s new aim = %d\n", NPC->NPC_type, NPCInfo->currentAim );
3241
3242 int debounce = 500+(3-g_spskill->integer)*100;
3243 TIMER_Set( NPC, "aimDebounce", Q_irand( debounce,debounce+1000 ) );
3244 //int debounce = 1000+(3-g_spskill->integer)*500;
3245 //TIMER_Set( NPC, "aimDebounce", Q_irand( debounce, debounce+2000 ) );
3246 }
3247 }
3248
G_AimSet(gentity_t * self,int aim)3249 void G_AimSet( gentity_t *self, int aim )
3250 {
3251 if ( self->NPC )
3252 {
3253 self->NPC->currentAim = aim;
3254 //Com_Printf( "%s new aim = %d\n", self->NPC_type, self->NPC->currentAim );
3255
3256 int debounce = 500+(3-g_spskill->integer)*100;
3257 TIMER_Set( self, "aimDebounce", Q_irand( debounce,debounce+1000 ) );
3258 // int debounce = 1000+(3-g_spskill->integer)*500;
3259 // TIMER_Set( self, "aimDebounce", Q_irand( debounce,debounce+2000 ) );
3260 }
3261 }
3262