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 #include "../cgame/cg_local.h"
24 #include "b_local.h"
25 #include "g_nav.h"
26 #include "Q3_Interface.h"
27
28 extern int g_crosshairEntNum;
29
30 /*
31 void NPC_LostEnemyDecideChase(void)
32
33 We lost our enemy and want to drop him but see if we should chase him if we are in the proper bState
34 */
35
NPC_LostEnemyDecideChase(void)36 void NPC_LostEnemyDecideChase(void)
37 {
38 switch( NPCInfo->behaviorState )
39 {
40 case BS_HUNT_AND_KILL:
41 //We were chasing him and lost him, so try to find him
42 if ( NPC->enemy == NPCInfo->goalEntity && NPC->enemy->lastWaypoint != WAYPOINT_NONE )
43 {//Remember his last valid Wp, then check it out
44 //FIXME: Should we only do this if there's no other enemies or we've got LOCKED_ENEMY on?
45 NPC_BSSearchStart( NPC->enemy->lastWaypoint, BS_SEARCH );
46 }
47 //If he's not our goalEntity, we're running somewhere else, so lose him
48 break;
49 default:
50 break;
51 }
52 G_ClearEnemy( NPC );
53 }
54 /*
55 -------------------------
56 NPC_StandIdle
57 -------------------------
58 */
59
NPC_StandIdle(void)60 void NPC_StandIdle( void )
61 {
62 /*
63 //Must be done with any other animations
64 if ( NPC->client->ps.legsAnimTimer != 0 )
65 return;
66
67 //Not ready to do another one
68 if ( TIMER_Done( NPC, "idleAnim" ) == false )
69 return;
70
71 int anim = NPC->client->ps.legsAnim;
72
73 if ( anim != BOTH_STAND1 && anim != BOTH_STAND2 )
74 return;
75
76 //FIXME: Account for STAND1 or STAND2 here and set the base anim accordingly
77 int baseSeq = ( anim == BOTH_STAND1 ) ? BOTH_STAND1_RANDOM1 : BOTH_STAND2_RANDOM1;
78
79 //Must have at least one random idle animation
80 //NOTENOTE: This relies on proper ordering of animations, which SHOULD be okay
81 if ( PM_HasAnimation( NPC, baseSeq ) == false )
82 return;
83
84 int newIdle = Q_irand( 0, MAX_IDLE_ANIMS-1 );
85
86 //FIXME: Technically this could never complete.. but that's not really too likely
87 while( 1 )
88 {
89 if ( PM_HasAnimation( NPC, baseSeq + newIdle ) )
90 break;
91
92 newIdle = Q_irand( 0, MAX_IDLE_ANIMS );
93 }
94
95 //Start that animation going
96 NPC_SetAnim( NPC, SETANIM_BOTH, baseSeq + newIdle, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
97
98 int newTime = PM_AnimLength( NPC->client->clientInfo.animFileIndex, (animNumber_t) (baseSeq + newIdle) );
99
100 //Don't do this again for a random amount of time
101 TIMER_Set( NPC, "idleAnim", newTime + Q_irand( 2000, 10000 ) );
102 */
103 }
104
NPC_StandTrackAndShoot(gentity_t * NPC,qboolean canDuck)105 qboolean NPC_StandTrackAndShoot (gentity_t *NPC, qboolean canDuck)
106 {
107 qboolean attack_ok = qfalse;
108 qboolean duck_ok = qfalse;
109 qboolean faced = qfalse;
110 float attack_scale = 1.0;
111
112 //First see if we're hurt bad- if so, duck
113 //FIXME: if even when ducked, we can shoot someone, we should.
114 //Maybe is can be shot even when ducked, we should run away to the nearest cover?
115 if ( canDuck )
116 {
117 if ( NPC->health < 20 )
118 {
119 // if( NPC->svFlags&SVF_HEALING || Q_flrand(0.0f, 1.0f) )
120 if( Q_flrand(0.0f, 1.0f) )
121 {
122 duck_ok = qtrue;
123 }
124 }
125 else if ( NPC->health < 40 )
126 {
127 // if ( NPC->svFlags&SVF_HEALING )
128 // {//Medic is on the way, get down!
129 // duck_ok = qtrue;
130 // }
131 }
132 }
133
134 //NPC_CheckEnemy( qtrue, qfalse );
135
136 if ( !duck_ok )
137 {//made this whole part a function call
138 attack_ok = NPC_CheckCanAttack( attack_scale, qtrue );
139 faced = qtrue;
140 }
141
142 if ( canDuck && (duck_ok || (!attack_ok && client->fireDelay == 0)) && ucmd.upmove != -127 )
143 {//if we didn't attack check to duck if we're not already
144 if( !duck_ok )
145 {
146 if ( NPC->enemy->client )
147 {
148 if ( NPC->enemy->enemy == NPC )
149 {
150 if ( NPC->enemy->client->buttons & BUTTON_ATTACK )
151 {//FIXME: determine if enemy fire angles would hit me or get close
152 if ( NPC_CheckDefend( 1.0 ) )//FIXME: Check self-preservation? Health?
153 {
154 duck_ok = qtrue;
155 }
156 }
157 }
158 }
159 }
160
161 if ( duck_ok )
162 {//duck and don't shoot
163 attack_ok = qfalse;
164 ucmd.upmove = -127;
165 NPCInfo->duckDebounceTime = level.time + 1000;//duck for a full second
166 }
167 }
168
169 return faced;
170 }
171
172
NPC_BSIdle(void)173 void NPC_BSIdle( void )
174 {
175 //FIXME if there is no nav data, we need to do something else
176 // if we're stuck, try to move around it
177 if ( UpdateGoal() )
178 {
179 NPC_MoveToGoal( qtrue );
180 }
181
182 if ( ( ucmd.forwardmove == 0 ) && ( ucmd.rightmove == 0 ) && ( ucmd.upmove == 0 ) )
183 {
184 // NPC_StandIdle();
185 }
186
187 NPC_UpdateAngles( qtrue, qtrue );
188 ucmd.buttons |= BUTTON_WALKING;
189 }
190
NPC_BSRun(void)191 void NPC_BSRun (void)
192 {
193 //FIXME if there is no nav data, we need to do something else
194 // if we're stuck, try to move around it
195 if ( UpdateGoal() )
196 {
197 NPC_MoveToGoal( qtrue );
198 }
199
200 NPC_UpdateAngles( qtrue, qtrue );
201 }
202
NPC_BSStandGuard(void)203 void NPC_BSStandGuard (void)
204 {
205 //FIXME: Use Snapshot info
206 if ( NPC->enemy == NULL )
207 {//Possible to pick one up by being shot
208 if( Q_flrand(0.0f, 1.0f) < 0.5 )
209 {
210 if(NPC->client->enemyTeam)
211 {
212 gentity_t *newenemy = NPC_PickEnemy(
213 NPC, NPC->client->enemyTeam,
214 (qboolean)(NPC->cantHitEnemyCounter < 10),
215 (qboolean)(NPC->client->enemyTeam == TEAM_PLAYER),
216 qtrue);
217
218 //only checks for vis if couldn't hit last enemy
219 if(newenemy)
220 {
221 G_SetEnemy( NPC, newenemy );
222 }
223 }
224 }
225 }
226
227 if ( NPC->enemy != NULL )
228 {
229 if( NPCInfo->tempBehavior == BS_STAND_GUARD )
230 {
231 NPCInfo->tempBehavior = BS_DEFAULT;
232 }
233
234 if( NPCInfo->behaviorState == BS_STAND_GUARD )
235 {
236 NPCInfo->behaviorState = BS_STAND_AND_SHOOT;
237 }
238 }
239
240 NPC_UpdateAngles( qtrue, qtrue );
241 }
242
243 /*
244 -------------------------
245 NPC_BSHuntAndKill
246 -------------------------
247 */
248
NPC_BSHuntAndKill(void)249 void NPC_BSHuntAndKill( void )
250 {
251 qboolean turned = qfalse;
252 vec3_t vec;
253 float enemyDist;
254 visibility_t oEVis;
255 int curAnim;
256
257 NPC_CheckEnemy( (qboolean)(NPCInfo->tempBehavior != BS_HUNT_AND_KILL), qfalse );//don't find new enemy if this is tempbehav
258
259 if ( NPC->enemy )
260 {
261 oEVis = enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_FOV|CHECK_SHOOT );//CHECK_360|//CHECK_PVS|
262 if(enemyVisibility > VIS_PVS)
263 {
264 if ( !NPC_EnemyTooFar( NPC->enemy, 0, qtrue ) )
265 {//Enemy is close enough to shoot - FIXME: this next func does this also, but need to know here for info on whether ot not to turn later
266 NPC_CheckCanAttack( 1.0, qfalse );
267 turned = qtrue;
268 }
269 }
270
271 curAnim = NPC->client->ps.legsAnim;
272 if(curAnim != BOTH_ATTACK1 && curAnim != BOTH_ATTACK2 && curAnim != BOTH_ATTACK3 && curAnim != BOTH_MELEE1 && curAnim != BOTH_MELEE2 )
273 {//Don't move toward enemy if we're in a full-body attack anim
274 //FIXME, use IdealDistance to determin if we need to close distance
275 VectorSubtract(NPC->enemy->currentOrigin, NPC->currentOrigin, vec);
276 enemyDist = VectorLength(vec);
277 if( enemyDist > 48 && ((enemyDist*1.5)*(enemyDist*1.5) >= NPC_MaxDistSquaredForWeapon() ||
278 oEVis != VIS_SHOOT ||
279 //!(ucmd.buttons & BUTTON_ATTACK) ||
280 enemyDist > IdealDistance(NPC)*3 ) )
281 {//We should close in?
282 NPCInfo->goalEntity = NPC->enemy;
283
284 NPC_MoveToGoal( qtrue );
285 }
286 else if(enemyDist < IdealDistance(NPC))
287 {//We should back off?
288 //if(ucmd.buttons & BUTTON_ATTACK)
289 {
290 NPCInfo->goalEntity = NPC->enemy;
291 NPCInfo->goalRadius = 12;
292 NPC_MoveToGoal( qtrue );
293
294 ucmd.forwardmove *= -1;
295 ucmd.rightmove *= -1;
296 VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir );
297
298 ucmd.buttons |= BUTTON_WALKING;
299 }
300 }//otherwise, stay where we are
301 }
302 }
303 else
304 {//ok, stand guard until we find an enemy
305 if( NPCInfo->tempBehavior == BS_HUNT_AND_KILL )
306 {
307 NPCInfo->tempBehavior = BS_DEFAULT;
308 }
309 else
310 {
311 NPCInfo->tempBehavior = BS_STAND_GUARD;
312 NPC_BSStandGuard();
313 }
314 return;
315 }
316
317 if(!turned)
318 {
319 NPC_UpdateAngles(qtrue, qtrue);
320 }
321 }
322
NPC_BSStandAndShoot(void)323 void NPC_BSStandAndShoot (void)
324 {
325 //FIXME:
326 //When our numbers outnumber enemies 3 to 1, or only one of them,
327 //go into hunt and kill mode
328
329 //FIXME:
330 //When they're all dead, go to some script or wander off to sickbay?
331
332 if(NPC->client->playerTeam && NPC->client->enemyTeam)
333 {
334 //FIXME: don't realize this right away- or else enemies show up and we're standing around
335 /*
336 if( teamNumbers[NPC->enemyTeam] == 0 )
337 {//ok, stand guard until we find another enemy
338 //reset our rush counter
339 teamCounter[NPC->playerTeam] = 0;
340 NPCInfo->tempBehavior = BS_STAND_GUARD;
341 NPC_BSStandGuard();
342 return;
343 }*/
344 /*
345 //FIXME: whether to do this or not should be settable
346 else if( NPC->playerTeam != TEAM_BORG )//Borg don't rush
347 {
348 //FIXME: In case reinforcements show up, we should wait a few seconds
349 //and keep checking before rushing!
350 //Also: what if not everyone on our team is going after playerTeam?
351 //Also: our team count includes medics!
352 if(NPC->health > 25)
353 {//Can we rush the enemy?
354 if(teamNumbers[NPC->enemyTeam] == 1 ||
355 teamNumbers[NPC->playerTeam] >= teamNumbers[NPC->enemyTeam]*3)
356 {//Only one of them or we outnumber 3 to 1
357 if(teamStrength[NPC->playerTeam] >= 75 ||
358 (teamStrength[NPC->playerTeam] >= 50 && teamStrength[NPC->playerTeam] > teamStrength[NPC->enemyTeam]))
359 {//Our team is strong enough to rush
360 teamCounter[NPC->playerTeam]++;
361 if(teamNumbers[NPC->playerTeam] * 17 <= teamCounter[NPC->playerTeam])
362 {//ok, we waited 1.7 think cycles on average and everyone is go, let's do it!
363 //FIXME: Should we do this to everyone on our team?
364 NPCInfo->behaviorState = BS_HUNT_AND_KILL;
365 //FIXME: if the tide changes, we should retreat!
366 //FIXME: when do we reset the counter?
367 NPC_BSHuntAndKill ();
368 return;
369 }
370 }
371 else//Oops! Something's wrong, reset the counter to rush
372 teamCounter[NPC->playerTeam] = 0;
373 }
374 else//Oops! Something's wrong, reset the counter to rush
375 teamCounter[NPC->playerTeam] = 0;
376 }
377 }
378 */
379 }
380
381 NPC_CheckEnemy(qtrue, qfalse);
382
383 if(NPCInfo->duckDebounceTime > level.time && NPC->client->ps.weapon != WP_SABER )
384 {
385 ucmd.upmove = -127;
386 if(NPC->enemy)
387 {
388 NPC_CheckCanAttack(1.0, qtrue);
389 }
390 return;
391 }
392
393 if(NPC->enemy)
394 {
395 if(!NPC_StandTrackAndShoot( NPC, qtrue ))
396 {//That func didn't update our angles
397 NPCInfo->desiredYaw = NPC->client->ps.viewangles[YAW];
398 NPCInfo->desiredPitch = NPC->client->ps.viewangles[PITCH];
399 NPC_UpdateAngles(qtrue, qtrue);
400 }
401 }
402 else
403 {
404 NPCInfo->desiredYaw = NPC->client->ps.viewangles[YAW];
405 NPCInfo->desiredPitch = NPC->client->ps.viewangles[PITCH];
406 NPC_UpdateAngles(qtrue, qtrue);
407 // NPC_BSIdle();//only moves if we have a goal
408 }
409 }
410
NPC_BSRunAndShoot(void)411 void NPC_BSRunAndShoot (void)
412 {
413 /*if(NPC->playerTeam && NPC->enemyTeam)
414 {
415 //FIXME: don't realize this right away- or else enemies show up and we're standing around
416 if( teamNumbers[NPC->enemyTeam] == 0 )
417 {//ok, stand guard until we find another enemy
418 //reset our rush counter
419 teamCounter[NPC->playerTeam] = 0;
420 NPCInfo->tempBehavior = BS_STAND_GUARD;
421 NPC_BSStandGuard();
422 return;
423 }
424 }*/
425
426 //NOTE: are we sure we want ALL run and shoot people to move this way?
427 //Shouldn't it check to see if we have an enemy and our enemy is our goal?!
428 //Moved that check into NPC_MoveToGoal
429 //NPCInfo->combatMove = qtrue;
430
431 NPC_CheckEnemy( qtrue, qfalse );
432
433 if ( NPCInfo->duckDebounceTime > level.time ) // && NPCInfo->hidingGoal )
434 {
435 ucmd.upmove = -127;
436 if ( NPC->enemy )
437 {
438 NPC_CheckCanAttack( 1.0, qfalse );
439 }
440 return;
441 }
442
443 if ( NPC->enemy )
444 {
445 int monitor = NPC->cantHitEnemyCounter;
446 NPC_StandTrackAndShoot( NPC, qfalse );//(NPCInfo->hidingGoal != NULL) );
447
448 if ( !(ucmd.buttons & BUTTON_ATTACK) && ucmd.upmove >= 0 && NPC->cantHitEnemyCounter > monitor )
449 {//not crouching and not firing
450 vec3_t vec;
451
452 VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, vec );
453 vec[2] = 0;
454 if ( VectorLength( vec ) > 128 || NPC->cantHitEnemyCounter >= 10 )
455 {//run at enemy if too far away
456 //The cantHitEnemyCounter getting high has other repercussions
457 //100 (10 seconds) will make you try to pick a new enemy...
458 //But we're chasing, so we clamp it at 50 here
459 if ( NPC->cantHitEnemyCounter > 60 )
460 {
461 NPC->cantHitEnemyCounter = 60;
462 }
463
464 if ( NPC->cantHitEnemyCounter >= (NPCInfo->stats.aggression+1) * 10 )
465 {
466 NPC_LostEnemyDecideChase();
467 }
468
469 //chase and face
470 ucmd.angles[YAW] = 0;
471 ucmd.angles[PITCH] = 0;
472 NPCInfo->goalEntity = NPC->enemy;
473 NPCInfo->goalRadius = 12;
474 NPC_MoveToGoal( qtrue );
475 NPC_UpdateAngles(qtrue, qtrue);
476 }
477 else
478 {
479 //FIXME: this could happen if they're just on the other side
480 //of a thin wall or something else blocking out shot. That
481 //would make us just stand there and not go around it...
482 //but maybe it's okay- might look like we're waiting for
483 //him to come out...?
484 //Current solution: runs around if cantHitEnemyCounter gets
485 //to 10 (1 second).
486 }
487 }
488 else
489 {//Clear the can't hit enemy counter here
490 NPC->cantHitEnemyCounter = 0;
491 }
492 }
493 else
494 {
495 if ( NPCInfo->tempBehavior == BS_HUNT_AND_KILL )
496 {//lost him, go back to what we were doing before
497 NPCInfo->tempBehavior = BS_DEFAULT;
498 return;
499 }
500
501 // NPC_BSRun();//only moves if we have a goal
502 }
503 }
504
505 //Simply turn until facing desired angles
NPC_BSFace(void)506 void NPC_BSFace (void)
507 {
508 //FIXME: once you stop sending turning info, they reset to whatever their delta_angles was last????
509 //Once this is over, it snaps back to what it was facing before- WHY???
510 if( NPC_UpdateAngles ( qtrue, qtrue ) )
511 {
512 Q3_TaskIDComplete( NPC, TID_BSTATE );
513
514 NPCInfo->desiredYaw = client->ps.viewangles[YAW];
515 NPCInfo->desiredPitch = client->ps.viewangles[PITCH];
516
517 NPCInfo->aimTime = 0;//ok to turn normally now
518 }
519 }
520
NPC_BSPointShoot(qboolean shoot)521 void NPC_BSPointShoot (qboolean shoot)
522 {//FIXME: doesn't check for clear shot...
523 vec3_t muzzle, dir, angles, org;
524
525 if ( !NPC->enemy || !NPC->enemy->inuse || (NPC->enemy->NPC && NPC->enemy->health <= 0) )
526 {//FIXME: should still keep shooting for a second or two after they actually die...
527 Q3_TaskIDComplete( NPC, TID_BSTATE );
528 goto finished;
529 return;
530 }
531
532 CalcEntitySpot(NPC, SPOT_WEAPON, muzzle);
533 CalcEntitySpot(NPC->enemy, SPOT_HEAD, org);//Was spot_org
534 //Head is a little high, so let's aim for the chest:
535 if ( NPC->enemy->client )
536 {
537 org[2] -= 12;//NOTE: is this enough?
538 }
539
540 VectorSubtract(org, muzzle, dir);
541 vectoangles(dir, angles);
542
543 switch( NPC->client->ps.weapon )
544 {
545 case WP_NONE:
546 case WP_MELEE:
547 case WP_TUSKEN_STAFF:
548 case WP_SABER:
549 //don't do any pitch change if not holding a firing weapon
550 break;
551 default:
552 NPCInfo->desiredPitch = NPCInfo->lockedDesiredPitch = AngleNormalize360(angles[PITCH]);
553 break;
554 }
555
556 NPCInfo->desiredYaw = NPCInfo->lockedDesiredYaw = AngleNormalize360(angles[YAW]);
557
558 if ( NPC_UpdateAngles ( qtrue, qtrue ) )
559 {//FIXME: if angles clamped, this may never work!
560 //NPCInfo->shotTime = NPC->attackDebounceTime = 0;
561
562 if ( shoot )
563 {//FIXME: needs to hold this down if using a weapon that requires it, like phaser...
564 ucmd.buttons |= BUTTON_ATTACK;
565 }
566
567 if ( !shoot || !(NPC->svFlags & SVF_LOCKEDENEMY) )
568 {//If locked_enemy is on, dont complete until it is destroyed...
569 Q3_TaskIDComplete( NPC, TID_BSTATE );
570 goto finished;
571 }
572 }
573 else if ( shoot && (NPC->svFlags & SVF_LOCKEDENEMY) )
574 {//shooting them till their dead, not aiming right at them yet...
575 /*
576 qboolean movingTarget = qfalse;
577
578 if ( NPC->enemy->client )
579 {
580 if ( VectorLengthSquared( NPC->enemy->client->ps.velocity ) )
581 {
582 movingTarget = qtrue;
583 }
584 }
585 else if ( VectorLengthSquared( NPC->enemy->s.pos.trDelta ) )
586 {
587 movingTarget = qtrue;
588 }
589
590 if (movingTarget )
591 */
592 {
593 float dist = VectorLength( dir );
594 float yawMiss, yawMissAllow = NPC->enemy->maxs[0];
595 float pitchMiss, pitchMissAllow = (NPC->enemy->maxs[2] - NPC->enemy->mins[2])/2;
596
597 if ( yawMissAllow < 8.0f )
598 {
599 yawMissAllow = 8.0f;
600 }
601
602 if ( pitchMissAllow < 8.0f )
603 {
604 pitchMissAllow = 8.0f;
605 }
606
607 yawMiss = tan(DEG2RAD(AngleDelta ( NPC->client->ps.viewangles[YAW], NPCInfo->desiredYaw ))) * dist;
608 pitchMiss = tan(DEG2RAD(AngleDelta ( NPC->client->ps.viewangles[PITCH], NPCInfo->desiredPitch))) * dist;
609
610 if ( yawMissAllow >= yawMiss && pitchMissAllow > pitchMiss )
611 {
612 ucmd.buttons |= BUTTON_ATTACK;
613 }
614 }
615 }
616
617 return;
618
619 finished:
620 NPCInfo->desiredYaw = client->ps.viewangles[YAW];
621 NPCInfo->desiredPitch = client->ps.viewangles[PITCH];
622
623 NPCInfo->aimTime = 0;//ok to turn normally now
624 }
625
626 /*
627 void NPC_BSMove(void)
628 Move in a direction, face another
629 */
NPC_BSMove(void)630 void NPC_BSMove(void)
631 {
632 gentity_t *goal = NULL;
633
634 NPC_CheckEnemy(qtrue, qfalse);
635 if(NPC->enemy)
636 {
637 NPC_CheckCanAttack(1.0, qfalse);
638 }
639 else
640 {
641 NPC_UpdateAngles(qtrue, qtrue);
642 }
643
644 goal = UpdateGoal();
645 if(goal)
646 {
647 // NPCInfo->moveToGoalMod = 1.0;
648
649 NPC_SlideMoveToGoal();
650 }
651 }
652
653 /*
654 void NPC_BSShoot(void)
655 Move in a direction, face another
656 */
657
NPC_BSShoot(void)658 void NPC_BSShoot(void)
659 {
660 // NPC_BSMove();
661
662 enemyVisibility = VIS_SHOOT;
663
664 if ( client->ps.weaponstate != WEAPON_READY && client->ps.weaponstate != WEAPON_FIRING )
665 {
666 client->ps.weaponstate = WEAPON_READY;
667 }
668
669 WeaponThink(qtrue);
670 }
671
672 /*
673 void NPC_BSPatrol( void )
674
675 Same as idle, but you look for enemies every "vigilance"
676 using your angles, HFOV, VFOV and visrange, and listen for sounds within earshot...
677 */
NPC_BSPatrol(void)678 void NPC_BSPatrol( void )
679 {
680 //int alertEventNum;
681
682 if(level.time > NPCInfo->enemyCheckDebounceTime)
683 {
684 NPCInfo->enemyCheckDebounceTime = level.time + (NPCInfo->stats.vigilance * 1000);
685 NPC_CheckEnemy(qtrue, qfalse);
686 if(NPC->enemy)
687 {//FIXME: do anger script
688 NPCInfo->behaviorState = BS_HUNT_AND_KILL;
689 //NPC_AngerSound();
690 return;
691 }
692 }
693
694 //FIXME: Implement generic sound alerts
695 /*
696 alertEventNum = NPC_CheckAlertEvents( qtrue, qtrue );
697 if( alertEventNum != -1 )
698 {//If we heard something, see if we should check it out
699 if ( NPC_CheckInvestigate( alertEventNum ) )
700 {
701 return;
702 }
703 }
704 */
705
706 NPCInfo->investigateSoundDebounceTime = 0;
707 //FIXME if there is no nav data, we need to do something else
708 // if we're stuck, try to move around it
709 if ( UpdateGoal() )
710 {
711 NPC_MoveToGoal( qtrue );
712 }
713
714 NPC_UpdateAngles( qtrue, qtrue );
715
716 ucmd.buttons |= BUTTON_WALKING;
717 }
718
719 /*
720 void NPC_BSDefault(void)
721 uses various scriptflags to determine how an npc should behave
722 */
723 extern void NPC_CheckGetNewWeapon( void );
724 extern void NPC_BSST_Attack( void );
725
NPC_BSDefault(void)726 void NPC_BSDefault( void )
727 {
728 // vec3_t enemyDir;
729 // float enemyDist;
730 // float shootDist;
731 // qboolean enemyFOV = qfalse;
732 // qboolean enemyShotFOV = qfalse;
733 // qboolean enemyPVS = qfalse;
734 // vec3_t enemyHead;
735 // vec3_t muzzle;
736 // qboolean enemyLOS = qfalse;
737 // qboolean enemyCS = qfalse;
738 qboolean move = qtrue;
739 // qboolean shoot = qfalse;
740
741
742 if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON )
743 {
744 WeaponThink( qtrue );
745 }
746
747 if ( NPCInfo->scriptFlags & SCF_FORCED_MARCH )
748 {//being forced to walk
749 if( NPC->client->ps.torsoAnim != TORSO_SURRENDER_START )
750 {
751 NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_SURRENDER_START, SETANIM_FLAG_HOLD );
752 }
753 }
754 //look for a new enemy if don't have one and are allowed to look, validate current enemy if have one
755 NPC_CheckEnemy( (qboolean)((NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) != 0), qfalse );
756 if ( !NPC->enemy )
757 {//still don't have an enemy
758 if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
759 {//check for alert events
760 //FIXME: Check Alert events, see if we should investigate or just look at it
761 int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qtrue, AEL_DISCOVERED );
762
763 //There is an event to look at
764 if ( alertEvent >= 0 )//&& level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID )
765 {//heard/saw something
766 if ( level.alertEvents[alertEvent].level >= AEL_DISCOVERED && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) )
767 {//was a big event
768 if ( level.alertEvents[alertEvent].owner
769 && level.alertEvents[alertEvent].owner != NPC
770 && level.alertEvents[alertEvent].owner->client
771 && level.alertEvents[alertEvent].owner->health >= 0
772 && level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam )
773 {//an enemy
774 G_SetEnemy( NPC, level.alertEvents[alertEvent].owner );
775 }
776 }
777 else
778 {//FIXME: investigate lesser events
779 }
780 }
781 //FIXME: also check our allies' condition?
782 }
783 }
784
785 if ( NPC->enemy && !(NPCInfo->scriptFlags&SCF_FORCED_MARCH) )
786 {
787 // just use the stormtrooper attack AI...
788 NPC_CheckGetNewWeapon();
789 if ( NPC->client->leader
790 && NPCInfo->goalEntity == NPC->client->leader
791 && !Q3_TaskIDPending( NPC, TID_MOVE_NAV ) )
792 {
793 NPC_ClearGoal();
794 }
795 NPC_BSST_Attack();
796 return;
797 /*
798 //have an enemy
799 //FIXME: if one of these fails, meaning we can't shoot, do we really need to do the rest?
800 VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, enemyDir );
801 enemyDist = VectorNormalize( enemyDir );
802 enemyDist *= enemyDist;
803 shootDist = NPC_MaxDistSquaredForWeapon();
804
805 enemyFOV = InFOV( NPC->enemy, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov );
806 enemyShotFOV = InFOV( NPC->enemy, NPC, 20, 20 );
807 enemyPVS = gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin );
808
809 if ( enemyPVS )
810 {//in the pvs
811 trace_t tr;
812
813 CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemyHead );
814 enemyHead[2] -= Q_flrand( 0.0f, NPC->enemy->maxs[2]*0.5f );
815 CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
816 enemyLOS = NPC_ClearLOS( muzzle, enemyHead );
817
818 gi.trace ( &tr, muzzle, vec3_origin, vec3_origin, enemyHead, NPC->s.number, MASK_SHOT );
819 enemyCS = NPC_EvaluateShot( tr.entityNum, qtrue );
820 }
821 else
822 {//skip thr 2 traces since they would have to fail
823 enemyLOS = qfalse;
824 enemyCS = qfalse;
825 }
826
827 if ( enemyCS && enemyShotFOV )
828 {//can hit enemy if we want
829 NPC->cantHitEnemyCounter = 0;
830 }
831 else
832 {//can't hit
833 NPC->cantHitEnemyCounter++;
834 }
835
836 if ( enemyCS && enemyShotFOV && enemyDist < shootDist )
837 {//can shoot
838 shoot = qtrue;
839 if ( NPCInfo->goalEntity == NPC->enemy )
840 {//my goal is my enemy and I have a clear shot, no need to chase right now
841 move = qfalse;
842 }
843 }
844 else
845 {//don't shoot yet, keep chasing
846 shoot = qfalse;
847 move = qtrue;
848 }
849
850 //shoot decision
851 if ( !(NPCInfo->scriptFlags&SCF_DONT_FIRE) )
852 {//try to shoot
853 if ( NPC->enemy )
854 {
855 if ( shoot )
856 {
857 if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here
858 {
859 WeaponThink( qtrue );
860 }
861 }
862 }
863 }
864
865 //chase decision
866 if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
867 {//go after him
868 NPCInfo->goalEntity = NPC->enemy;
869 //FIXME: don't need to chase when have a clear shot and in range?
870 if ( !enemyCS && NPC->cantHitEnemyCounter > 60 )
871 {//haven't been able to shoot enemy for about 6 seconds, need to do something
872 //FIXME: combat points? Just chase?
873 if ( enemyPVS )
874 {//in my PVS, just pick a combat point
875 //FIXME: implement
876 }
877 else
878 {//just chase him
879 }
880 }
881 //FIXME: in normal behavior, should we use combat Points? Do we care? Is anyone actually going to ever use this AI?
882 }
883 else if ( NPC->cantHitEnemyCounter > 60 )
884 {//pick a new one
885 NPC_CheckEnemy( qtrue, qfalse );
886 }
887
888 if ( enemyPVS && enemyLOS )//&& !enemyShotFOV )
889 {//have a clear LOS to him//, but not looking at him
890 //Find the desired angles
891 vec3_t angles;
892
893 GetAnglesForDirection( muzzle, enemyHead, angles );
894
895 NPCInfo->desiredYaw = AngleNormalize180( angles[YAW] );
896 NPCInfo->desiredPitch = AngleNormalize180( angles[PITCH] );
897 }
898 */
899 }
900
901 if ( UpdateGoal() )
902 {//have a goal
903 if ( !NPC->enemy
904 && NPC->client->leader
905 && NPCInfo->goalEntity == NPC->client->leader
906 && !Q3_TaskIDPending( NPC, TID_MOVE_NAV ) )
907 {
908 NPC_BSFollowLeader();
909 }
910 else
911 {
912 //set angles
913 if ( (NPCInfo->scriptFlags & SCF_FACE_MOVE_DIR) || NPCInfo->goalEntity != NPC->enemy )
914 {//face direction of movement, NOTE: default behavior when not chasing enemy
915 NPCInfo->combatMove = qfalse;
916 }
917 else
918 {//face goal.. FIXME: what if have a navgoal but want to face enemy while moving? Will this do that?
919 vec3_t dir, angles;
920
921 NPCInfo->combatMove = qfalse;
922
923 VectorSubtract( NPCInfo->goalEntity->currentOrigin, NPC->currentOrigin, dir );
924 vectoangles( dir, angles );
925 NPCInfo->desiredYaw = angles[YAW];
926 if ( NPCInfo->goalEntity == NPC->enemy )
927 {
928 NPCInfo->desiredPitch = angles[PITCH];
929 }
930 }
931
932 //set movement
933 //override default walk/run behavior
934 //NOTE: redundant, done in NPC_ApplyScriptFlags
935 if ( NPCInfo->scriptFlags & SCF_RUNNING )
936 {
937 ucmd.buttons &= ~BUTTON_WALKING;
938 }
939 else if ( NPCInfo->scriptFlags & SCF_WALKING )
940 {
941 ucmd.buttons |= BUTTON_WALKING;
942 }
943 else if ( NPCInfo->goalEntity == NPC->enemy )
944 {
945 ucmd.buttons &= ~BUTTON_WALKING;
946 }
947 else
948 {
949 ucmd.buttons |= BUTTON_WALKING;
950 }
951
952 if ( NPCInfo->scriptFlags & SCF_FORCED_MARCH )
953 {//being forced to walk
954 if ( g_crosshairEntNum != NPC->s.number )
955 {//don't walk if player isn't aiming at me
956 move = qfalse;
957 }
958 }
959
960 if ( move )
961 {
962 //move toward goal
963 NPC_MoveToGoal( qtrue );
964 }
965 }
966 }
967 else if ( !NPC->enemy && NPC->client->leader )
968 {
969 NPC_BSFollowLeader();
970 }
971
972 //update angles
973 NPC_UpdateAngles( qtrue, qtrue );
974 }