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 }