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