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 #include "g_headers.h"
23 
24 #include "b_local.h"
25 #include "g_nav.h"
26 #include "anims.h"
27 #include "g_navigator.h"
28 
29 extern void CG_DrawAlert( vec3_t origin, float rating );
30 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
31 extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
32 extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime );
33 extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask );
34 extern void NPC_AimAdjust( int change );
35 extern qboolean FlyingCreature( gentity_t *ent );
36 
37 extern	CNavigator	navigator;
38 
39 #define	MAX_VIEW_DIST		1024
40 #define MAX_VIEW_SPEED		250
41 #define	MAX_LIGHT_INTENSITY 255
42 #define	MIN_LIGHT_THRESHOLD	0.1
43 
44 #define	DISTANCE_SCALE		0.25f
45 #define	DISTANCE_THRESHOLD	0.075f
46 #define	SPEED_SCALE			0.25f
47 #define	FOV_SCALE			0.5f
48 #define	LIGHT_SCALE			0.25f
49 
50 #define	REALIZE_THRESHOLD	0.6f
51 #define CAUTIOUS_THRESHOLD	( REALIZE_THRESHOLD * 0.75 )
52 
53 qboolean NPC_CheckPlayerTeamStealth( void );
54 
55 static qboolean enemyLOS;
56 static qboolean enemyCS;
57 static qboolean faceEnemy;
58 static qboolean AImove;
59 static qboolean shoot;
60 static float	enemyDist;
61 
62 //Local state enums
63 enum
64 {
65 	LSTATE_NONE = 0,
66 	LSTATE_UNDERFIRE,
67 	LSTATE_INVESTIGATE,
68 };
69 
Grenadier_ClearTimers(gentity_t * ent)70 void Grenadier_ClearTimers( gentity_t *ent )
71 {
72 	TIMER_Set( ent, "chatter", 0 );
73 	TIMER_Set( ent, "duck", 0 );
74 	TIMER_Set( ent, "stand", 0 );
75 	TIMER_Set( ent, "shuffleTime", 0 );
76 	TIMER_Set( ent, "sleepTime", 0 );
77 	TIMER_Set( ent, "enemyLastVisible", 0 );
78 	TIMER_Set( ent, "roamTime", 0 );
79 	TIMER_Set( ent, "hideTime", 0 );
80 	TIMER_Set( ent, "attackDelay", 0 );	//FIXME: Slant for difficulty levels
81 	TIMER_Set( ent, "stick", 0 );
82 	TIMER_Set( ent, "scoutTime", 0 );
83 	TIMER_Set( ent, "flee", 0 );
84 }
85 
NPC_Grenadier_PlayConfusionSound(gentity_t * self)86 void NPC_Grenadier_PlayConfusionSound( gentity_t *self )
87 {//FIXME: make this a custom sound in sound set
88 	if ( self->health > 0 )
89 	{
90 		G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 );
91 	}
92 	//reset him to be totally unaware again
93 	TIMER_Set( self, "enemyLastVisible", 0 );
94 	TIMER_Set( self, "flee", 0 );
95 	self->NPC->squadState = SQUAD_IDLE;
96 	self->NPC->tempBehavior = BS_DEFAULT;
97 
98 	//self->NPC->behaviorState = BS_PATROL;
99 	G_ClearEnemy( self );//FIXME: or just self->enemy = NULL;?
100 
101 	self->NPC->investigateCount = 0;
102 }
103 
104 
105 /*
106 -------------------------
107 NPC_ST_Pain
108 -------------------------
109 */
110 
NPC_Grenadier_Pain(gentity_t * self,gentity_t * inflictor,gentity_t * other,vec3_t point,int damage,int mod)111 void NPC_Grenadier_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, vec3_t point, int damage, int mod )
112 {
113 	self->NPC->localState = LSTATE_UNDERFIRE;
114 
115 	TIMER_Set( self, "duck", -1 );
116 	TIMER_Set( self, "stand", 2000 );
117 
118 	NPC_Pain( self, inflictor, other, point, damage, mod );
119 
120 	if ( !damage && self->health > 0 )
121 	{//FIXME: better way to know I was pushed
122 		G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
123 	}
124 }
125 
126 /*
127 -------------------------
128 ST_HoldPosition
129 -------------------------
130 */
131 
Grenadier_HoldPosition(void)132 static void Grenadier_HoldPosition( void )
133 {
134 	NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue );
135 	NPCInfo->goalEntity = NULL;
136 
137 	/*if ( TIMER_Done( NPC, "stand" ) )
138 	{//FIXME: what if can't shoot from this pos?
139 		TIMER_Set( NPC, "duck", Q_irand( 2000, 4000 ) );
140 	}
141 	*/
142 }
143 
144 /*
145 -------------------------
146 ST_Move
147 -------------------------
148 */
149 
Grenadier_Move(void)150 static qboolean Grenadier_Move( void )
151 {
152 	NPCInfo->combatMove = qtrue;//always move straight toward our goal
153 
154 	qboolean	moved = NPC_MoveToGoal( qtrue );
155 	navInfo_t	info;
156 
157 	//Get the move info
158 	NAV_GetLastMove( info );
159 
160 	//FIXME: if we bump into another one of our guys and can't get around him, just stop!
161 	//If we hit our target, then stop and fire!
162 	if ( info.flags & NIF_COLLISION )
163 	{
164 		if ( info.blocker == NPC->enemy )
165 		{
166 			Grenadier_HoldPosition();
167 		}
168 	}
169 
170 	//If our move failed, then reset
171 	if ( moved == qfalse )
172 	{//couldn't get to enemy
173 		if ( (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) && NPC->client->ps.weapon == WP_THERMAL && NPCInfo->goalEntity && NPCInfo->goalEntity == NPC->enemy )
174 		{//we were running after enemy
175 			//Try to find a combat point that can hit the enemy
176 			int cpFlags = (CP_CLEAR|CP_HAS_ROUTE);
177 			if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST )
178 			{
179 				cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST);
180 				cpFlags |= CP_NEAREST;
181 			}
182 			int cp = NPC_FindCombatPoint( NPC->currentOrigin, NPC->currentOrigin, NPC->currentOrigin, cpFlags, 32 );
183 			if ( cp == -1 && !(NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) )
184 			{//okay, try one by the enemy
185 				cp = NPC_FindCombatPoint( NPC->currentOrigin, NPC->currentOrigin, NPC->enemy->currentOrigin, CP_CLEAR|CP_HAS_ROUTE|CP_HORZ_DIST_COLL, 32 );
186 			}
187 			//NOTE: there may be a perfectly valid one, just not one within CP_COLLECT_RADIUS of either me or him...
188 			if ( cp != -1 )
189 			{//found a combat point that has a clear shot to enemy
190 				NPC_SetCombatPoint( cp );
191 				NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp );
192 				return moved;
193 			}
194 		}
195 		//just hang here
196 		Grenadier_HoldPosition();
197 	}
198 
199 	return moved;
200 }
201 
202 /*
203 -------------------------
204 NPC_BSGrenadier_Patrol
205 -------------------------
206 */
207 
NPC_BSGrenadier_Patrol(void)208 void NPC_BSGrenadier_Patrol( void )
209 {//FIXME: pick up on bodies of dead buddies?
210 	if ( NPCInfo->confusionTime < level.time )
211 	{
212 		//Look for any enemies
213 		if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )
214 		{
215 			if ( NPC_CheckPlayerTeamStealth() )
216 			{
217 				//NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be automatic now
218 				//NPC_AngerSound();
219 				NPC_UpdateAngles( qtrue, qtrue );
220 				return;
221 			}
222 		}
223 
224 		if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
225 		{
226 			//Is there danger nearby
227 			int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_SUSPICIOUS );
228 			if ( NPC_CheckForDanger( alertEvent ) )
229 			{
230 				NPC_UpdateAngles( qtrue, qtrue );
231 				return;
232 			}
233 			else
234 			{//check for other alert events
235 				//There is an event to look at
236 				if ( alertEvent >= 0 && level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID )
237 				{
238 					NPCInfo->lastAlertID = level.alertEvents[alertEvent].ID;
239 					if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED )
240 					{
241 						if ( level.alertEvents[alertEvent].owner &&
242 							level.alertEvents[alertEvent].owner->client &&
243 							level.alertEvents[alertEvent].owner->health >= 0 &&
244 							level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam )
245 						{//an enemy
246 							G_SetEnemy( NPC, level.alertEvents[alertEvent].owner );
247 							//NPCInfo->enemyLastSeenTime = level.time;
248 							TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
249 						}
250 					}
251 					else
252 					{//FIXME: get more suspicious over time?
253 						//Save the position for movement (if necessary)
254 						VectorCopy( level.alertEvents[alertEvent].position, NPCInfo->investigateGoal );
255 						NPCInfo->investigateDebounceTime = level.time + Q_irand( 500, 1000 );
256 						if ( level.alertEvents[alertEvent].level == AEL_SUSPICIOUS )
257 						{//suspicious looks longer
258 							NPCInfo->investigateDebounceTime += Q_irand( 500, 2500 );
259 						}
260 					}
261 				}
262 			}
263 
264 			if ( NPCInfo->investigateDebounceTime > level.time )
265 			{//FIXME: walk over to it, maybe?  Not if not chase enemies
266 				//NOTE: stops walking or doing anything else below
267 				vec3_t	dir, angles;
268 				float	o_yaw, o_pitch;
269 
270 				VectorSubtract( NPCInfo->investigateGoal, NPC->client->renderInfo.eyePoint, dir );
271 				vectoangles( dir, angles );
272 
273 				o_yaw = NPCInfo->desiredYaw;
274 				o_pitch = NPCInfo->desiredPitch;
275 				NPCInfo->desiredYaw = angles[YAW];
276 				NPCInfo->desiredPitch = angles[PITCH];
277 
278 				NPC_UpdateAngles( qtrue, qtrue );
279 
280 				NPCInfo->desiredYaw = o_yaw;
281 				NPCInfo->desiredPitch = o_pitch;
282 				return;
283 			}
284 		}
285 	}
286 
287 	//If we have somewhere to go, then do that
288 	if ( UpdateGoal() )
289 	{
290 		ucmd.buttons |= BUTTON_WALKING;
291 		NPC_MoveToGoal( qtrue );
292 	}
293 
294 	NPC_UpdateAngles( qtrue, qtrue );
295 }
296 
297 /*
298 -------------------------
299 NPC_BSGrenadier_Idle
300 -------------------------
301 */
302 /*
303 void NPC_BSGrenadier_Idle( void )
304 {
305 	//FIXME: check for other alert events?
306 
307 	//Is there danger nearby?
308 	if ( NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) )
309 	{
310 		NPC_UpdateAngles( qtrue, qtrue );
311 		return;
312 	}
313 
314 	TIMER_Set( NPC, "roamTime", 2000 + Q_irand( 1000, 2000 ) );
315 
316 	NPC_UpdateAngles( qtrue, qtrue );
317 }
318 */
319 /*
320 -------------------------
321 ST_CheckMoveState
322 -------------------------
323 */
324 
Grenadier_CheckMoveState(void)325 static void Grenadier_CheckMoveState( void )
326 {
327 	//See if we're a scout
328 	if ( !(NPCInfo->scriptFlags & SCF_CHASE_ENEMIES) )//behaviorState == BS_STAND_AND_SHOOT )
329 	{
330 		if ( NPCInfo->goalEntity == NPC->enemy )
331 		{
332 			AImove = qfalse;
333 			return;
334 		}
335 	}
336 	//See if we're running away
337 	else if ( NPCInfo->squadState == SQUAD_RETREAT )
338 	{
339 		if ( TIMER_Done( NPC, "flee" ) )
340 		{
341 			NPCInfo->squadState = SQUAD_IDLE;
342 		}
343 		else
344 		{
345 			faceEnemy = qfalse;
346 		}
347 	}
348 	/*
349 	else if ( NPCInfo->squadState == SQUAD_IDLE )
350 	{
351 		if ( !NPCInfo->goalEntity )
352 		{
353 			AImove = qfalse;
354 			return;
355 		}
356 		//Should keep moving toward player when we're out of range... right?
357 	}
358 	*/
359 
360 	//See if we're moving towards a goal, not the enemy
361 	if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) )
362 	{
363 		//Did we make it?
364 		if ( NAV_HitNavGoal( NPC->currentOrigin, NPC->mins, NPC->maxs, NPCInfo->goalEntity->currentOrigin, 16, FlyingCreature( NPC ) ) ||
365 			( NPCInfo->squadState == SQUAD_SCOUT && enemyLOS && enemyDist <= 10000 ) )
366 		{
367 			//int	newSquadState = SQUAD_STAND_AND_SHOOT;
368 			//we got where we wanted to go, set timers based on why we were running
369 			switch ( NPCInfo->squadState )
370 			{
371 			case SQUAD_RETREAT://was running away
372 				TIMER_Set( NPC, "duck", (NPC->max_health - NPC->health) * 100 );
373 				TIMER_Set( NPC, "hideTime", Q_irand( 3000, 7000 ) );
374 				//newSquadState = SQUAD_COVER;
375 				break;
376 			case SQUAD_TRANSITION://was heading for a combat point
377 				TIMER_Set( NPC, "hideTime", Q_irand( 2000, 4000 ) );
378 				break;
379 			case SQUAD_SCOUT://was running after player
380 				break;
381 			default:
382 				break;
383 			}
384 			NPC_ReachedGoal();
385 			//don't attack right away
386 			TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) );	//FIXME: Slant for difficulty levels
387 			//don't do something else just yet
388 			TIMER_Set( NPC, "roamTime", Q_irand( 1000, 4000 ) );
389 			//stop fleeing
390 			if ( NPCInfo->squadState == SQUAD_RETREAT )
391 			{
392 				TIMER_Set( NPC, "flee", -level.time );
393 				NPCInfo->squadState = SQUAD_IDLE;
394 			}
395 			return;
396 		}
397 
398 		//keep going, hold of roamTimer until we get there
399 		TIMER_Set( NPC, "roamTime", Q_irand( 4000, 8000 ) );
400 	}
401 
402 	if ( !NPCInfo->goalEntity )
403 	{
404 		if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
405 		{
406 			NPCInfo->goalEntity = NPC->enemy;
407 		}
408 	}
409 }
410 
411 /*
412 -------------------------
413 ST_CheckFireState
414 -------------------------
415 */
416 
Grenadier_CheckFireState(void)417 static void Grenadier_CheckFireState( void )
418 {
419 	if ( enemyCS )
420 	{//if have a clear shot, always try
421 		return;
422 	}
423 
424 	if ( NPCInfo->squadState == SQUAD_RETREAT || NPCInfo->squadState == SQUAD_TRANSITION || NPCInfo->squadState == SQUAD_SCOUT )
425 	{//runners never try to fire at the last pos
426 		return;
427 	}
428 
429 	if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) )
430 	{//if moving at all, don't do this
431 		return;
432 	}
433 
434 	//continue to fire on their last position
435 	/*
436 	if ( !Q_irand( 0, 1 ) && NPCInfo->enemyLastSeenTime && level.time - NPCInfo->enemyLastSeenTime < 4000 )
437 	{
438 		//Fire on the last known position
439 		vec3_t	muzzle, dir, angles;
440 
441 		CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
442 		VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir );
443 
444 		VectorNormalize( dir );
445 
446 		vectoangles( dir, angles );
447 
448 		NPCInfo->desiredYaw		= angles[YAW];
449 		NPCInfo->desiredPitch	= angles[PITCH];
450 		//FIXME: they always throw toward enemy, so this will be very odd...
451 		shoot = qtrue;
452 		faceEnemy = qfalse;
453 
454 		return;
455 	}
456 	*/
457 }
458 
Grenadier_EvaluateShot(int hit)459 qboolean Grenadier_EvaluateShot( int hit )
460 {
461 	if ( !NPC->enemy )
462 	{
463 		return qfalse;
464 	}
465 
466 	if ( hit == NPC->enemy->s.number || (&g_entities[hit] != NULL && (g_entities[hit].svFlags&SVF_GLASS_BRUSH)) )
467 	{//can hit enemy or will hit glass, so shoot anyway
468 		return qtrue;
469 	}
470 	return qfalse;
471 }
472 
473 /*
474 -------------------------
475 NPC_BSGrenadier_Attack
476 -------------------------
477 */
478 
NPC_BSGrenadier_Attack(void)479 void NPC_BSGrenadier_Attack( void )
480 {
481 	//Don't do anything if we're hurt
482 	if ( NPC->painDebounceTime > level.time )
483 	{
484 		NPC_UpdateAngles( qtrue, qtrue );
485 		return;
486 	}
487 
488 	//NPC_CheckEnemy( qtrue, qfalse );
489 	//If we don't have an enemy, just idle
490 	if ( NPC_CheckEnemyExt() == qfalse )//!NPC->enemy )//
491 	{
492 		NPC->enemy = NULL;
493 		NPC_BSGrenadier_Patrol();//FIXME: or patrol?
494 		return;
495 	}
496 
497 	if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) )
498 	{//going to run
499 		NPC_UpdateAngles( qtrue, qtrue );
500 		return;
501 	}
502 
503 	if ( !NPC->enemy )
504 	{//WTF?  somehow we lost our enemy?
505 		NPC_BSGrenadier_Patrol();//FIXME: or patrol?
506 		return;
507 	}
508 
509 	enemyLOS = enemyCS = qfalse;
510 	AImove = qtrue;
511 	faceEnemy = qfalse;
512 	shoot = qfalse;
513 	enemyDist = DistanceSquared( NPC->enemy->currentOrigin, NPC->currentOrigin );
514 
515 	//See if we should switch to melee attack
516 	if ( enemyDist < 16384 && (!NPC->enemy->client||NPC->enemy->client->ps.weapon != WP_SABER||!NPC->enemy->client->ps.saberActive) )//128
517 	{//enemy is close and not using saber
518 		if ( NPC->client->ps.weapon == WP_THERMAL )
519 		{//grenadier
520 			trace_t	trace;
521 			gi.trace ( &trace, NPC->currentOrigin, NPC->enemy->mins, NPC->enemy->maxs, NPC->enemy->currentOrigin, NPC->s.number, NPC->enemy->clipmask, G2_NOCOLLIDE, 0 );
522 			if ( !trace.allsolid && !trace.startsolid && (trace.fraction == 1.0 || trace.entityNum == NPC->enemy->s.number ) )
523 			{//I can get right to him
524 				//reset fire-timing variables
525 				NPC_ChangeWeapon( WP_MELEE );
526 				if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//NPCInfo->behaviorState == BS_STAND_AND_SHOOT )
527 				{//FIXME: should we be overriding scriptFlags?
528 					NPCInfo->scriptFlags |= SCF_CHASE_ENEMIES;//NPCInfo->behaviorState = BS_HUNT_AND_KILL;
529 				}
530 			}
531 		}
532 	}
533 	else if ( enemyDist > 65536 || (NPC->enemy->client && NPC->enemy->client->ps.weapon == WP_SABER && NPC->enemy->client->ps.saberActive) )//256
534 	{//enemy is far or using saber
535 		if ( NPC->client->ps.weapon == WP_MELEE && (NPC->client->ps.stats[STAT_WEAPONS]&(1<<WP_THERMAL)) )
536 		{//fisticuffs, make switch to thermal if have it
537 			//reset fire-timing variables
538 			NPC_ChangeWeapon( WP_THERMAL );
539 		}
540 	}
541 
542 	//can we see our target?
543 	if ( NPC_ClearLOS( NPC->enemy ) )
544 	{
545 		NPCInfo->enemyLastSeenTime = level.time;
546 		enemyLOS = qtrue;
547 
548 		if ( NPC->client->ps.weapon == WP_MELEE )
549 		{
550 			if ( enemyDist <= 4096 && InFOV( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 90, 45 ) )//within 64 & infront
551 			{
552 				VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation );
553 				enemyCS = qtrue;
554 			}
555 		}
556 		else if ( InFOV( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 45, 90 ) )
557 		{//in front of me
558 			//can we shoot our target?
559 			//FIXME: how accurate/necessary is this check?
560 			int hit = NPC_ShotEntity( NPC->enemy );
561 			gentity_t *hitEnt = &g_entities[hit];
562 			if ( hit == NPC->enemy->s.number
563 				|| ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) )
564 			{
565 				VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation );
566 				float enemyHorzDist = DistanceHorizontalSquared( NPC->enemy->currentOrigin, NPC->currentOrigin );
567 				if ( enemyHorzDist < 1048576 )
568 				{//within 1024
569 					enemyCS = qtrue;
570 					NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy
571 				}
572 				else
573 				{
574 					NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy
575 				}
576 			}
577 		}
578 	}
579 	else
580 	{
581 		NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy
582 	}
583 	/*
584 	else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) )
585 	{
586 		NPCInfo->enemyLastSeenTime = level.time;
587 		faceEnemy = qtrue;
588 	}
589 	*/
590 
591 	if ( enemyLOS )
592 	{//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot?
593 		faceEnemy = qtrue;
594 	}
595 
596 	if ( enemyCS )
597 	{
598 		shoot = qtrue;
599 		if ( NPC->client->ps.weapon == WP_THERMAL )
600 		{//don't chase and throw
601 			AImove = qfalse;
602 		}
603 		else if ( NPC->client->ps.weapon == WP_MELEE && enemyDist < (NPC->maxs[0]+NPC->enemy->maxs[0]+16)*(NPC->maxs[0]+NPC->enemy->maxs[0]+16) )
604 		{//close enough
605 			AImove = qfalse;
606 		}
607 	}//this should make him chase enemy when out of range...?
608 
609 	//Check for movement to take care of
610 	Grenadier_CheckMoveState();
611 
612 	//See if we should override shooting decision with any special considerations
613 	Grenadier_CheckFireState();
614 
615 	if ( AImove )
616 	{//move toward goal
617 		if ( NPCInfo->goalEntity )//&& ( NPCInfo->goalEntity != NPC->enemy || enemyDist > 10000 ) )//100 squared
618 		{
619 			AImove = Grenadier_Move();
620 		}
621 		else
622 		{
623 			AImove = qfalse;
624 		}
625 	}
626 
627 	if ( !AImove )
628 	{
629 		if ( !TIMER_Done( NPC, "duck" ) )
630 		{
631 			ucmd.upmove = -127;
632 		}
633 		//FIXME: what about leaning?
634 	}
635 	else
636 	{//stop ducking!
637 		TIMER_Set( NPC, "duck", -1 );
638 	}
639 
640 	if ( !faceEnemy )
641 	{//we want to face in the dir we're running
642 		if ( AImove )
643 		{//don't run away and shoot
644 			NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW];
645 			NPCInfo->desiredPitch = 0;
646 			shoot = qfalse;
647 		}
648 		NPC_UpdateAngles( qtrue, qtrue );
649 	}
650 	else// if ( faceEnemy )
651 	{//face the enemy
652 		NPC_FaceEnemy();
653 	}
654 
655 	if ( NPCInfo->scriptFlags&SCF_DONT_FIRE )
656 	{
657 		shoot = qfalse;
658 	}
659 
660 	//FIXME: don't shoot right away!
661 	if ( shoot )
662 	{//try to shoot if it's time
663 		if ( TIMER_Done( NPC, "attackDelay" ) )
664 		{
665 			if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here
666 			{
667 				WeaponThink( qtrue );
668 				TIMER_Set( NPC, "attackDelay", NPCInfo->shotTime-level.time );
669 			}
670 
671 		}
672 	}
673 }
674 
NPC_BSGrenadier_Default(void)675 void NPC_BSGrenadier_Default( void )
676 {
677 	if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON )
678 	{
679 		WeaponThink( qtrue );
680 	}
681 
682 	if( !NPC->enemy )
683 	{//don't have an enemy, look for one
684 		NPC_BSGrenadier_Patrol();
685 	}
686 	else//if ( NPC->enemy )
687 	{//have an enemy
688 		NPC_BSGrenadier_Attack();
689 	}
690 }
691