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