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