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