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 //NPC_utils.cpp
24 
25 #include "b_local.h"
26 #include "icarus/Q3_Interface.h"
27 #include "ghoul2/G2.h"
28 
29 int	teamNumbers[TEAM_NUM_TEAMS];
30 int	teamStrength[TEAM_NUM_TEAMS];
31 int	teamCounter[TEAM_NUM_TEAMS];
32 
33 #define	VALID_ATTACK_CONE	2.0f	//Degrees
34 extern void G_DebugPrint( int level, const char *format, ... );
35 
36 /*
37 void CalcEntitySpot ( gentity_t *ent, spot_t spot, vec3_t point )
38 
39 Added: Uses shootAngles if a NPC has them
40 
41 */
CalcEntitySpot(const gentity_t * ent,const spot_t spot,vec3_t point)42 void CalcEntitySpot ( const gentity_t *ent, const spot_t spot, vec3_t point )
43 {
44 	vec3_t	forward, up, right;
45 	vec3_t	start, end;
46 	trace_t	tr;
47 
48 	if ( !ent )
49 	{
50 		return;
51 	}
52 	switch ( spot )
53 	{
54 	case SPOT_ORIGIN:
55 		if(VectorCompare(ent->r.currentOrigin, vec3_origin))
56 		{//brush
57 			VectorSubtract(ent->r.absmax, ent->r.absmin, point);//size
58 			VectorMA(ent->r.absmin, 0.5, point, point);
59 		}
60 		else
61 		{
62 			VectorCopy ( ent->r.currentOrigin, point );
63 		}
64 		break;
65 
66 	case SPOT_CHEST:
67 	case SPOT_HEAD:
68 		if ( ent->client && VectorLengthSquared( ent->client->renderInfo.eyePoint ) /*&& (ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD)*/ )
69 		{//Actual tag_head eyespot!
70 			//FIXME: Stasis aliens may have a problem here...
71 			VectorCopy( ent->client->renderInfo.eyePoint, point );
72 			if ( ent->client->NPC_class == CLASS_ATST )
73 			{//adjust up some
74 				point[2] += 28;//magic number :)
75 			}
76 			if ( ent->NPC )
77 			{//always aim from the center of my bbox, so we don't wiggle when we lean forward or backwards
78 				point[0] = ent->r.currentOrigin[0];
79 				point[1] = ent->r.currentOrigin[1];
80 			}
81 			/*
82 			else if (ent->s.eType == ET_PLAYER )
83 			{
84 				SubtractLeanOfs( ent, point );
85 			}
86 			*/
87 		}
88 		else
89 		{
90 			VectorCopy ( ent->r.currentOrigin, point );
91 			if ( ent->client )
92 			{
93 				point[2] += ent->client->ps.viewheight;
94 			}
95 		}
96 		if ( spot == SPOT_CHEST && ent->client )
97 		{
98 			if ( ent->client->NPC_class != CLASS_ATST )
99 			{//adjust up some
100 				point[2] -= ent->r.maxs[2]*0.2f;
101 			}
102 		}
103 		break;
104 
105 	case SPOT_HEAD_LEAN:
106 		if ( ent->client && VectorLengthSquared( ent->client->renderInfo.eyePoint ) /*&& (ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD*/ )
107 		{//Actual tag_head eyespot!
108 			//FIXME: Stasis aliens may have a problem here...
109 			VectorCopy( ent->client->renderInfo.eyePoint, point );
110 			if ( ent->client->NPC_class == CLASS_ATST )
111 			{//adjust up some
112 				point[2] += 28;//magic number :)
113 			}
114 			if ( ent->NPC )
115 			{//always aim from the center of my bbox, so we don't wiggle when we lean forward or backwards
116 				point[0] = ent->r.currentOrigin[0];
117 				point[1] = ent->r.currentOrigin[1];
118 			}
119 			/*
120 			else if ( ent->s.eType == ET_PLAYER )
121 			{
122 				SubtractLeanOfs( ent, point );
123 			}
124 			*/
125 			//NOTE: automatically takes leaning into account!
126 		}
127 		else
128 		{
129 			VectorCopy ( ent->r.currentOrigin, point );
130 			if ( ent->client )
131 			{
132 				point[2] += ent->client->ps.viewheight;
133 			}
134 			//AddLeanOfs ( ent, point );
135 		}
136 		break;
137 
138 	//FIXME: implement...
139 	//case SPOT_CHEST:
140 		//Returns point 3/4 from tag_torso to tag_head?
141 		//break;
142 
143 	case SPOT_LEGS:
144 		VectorCopy ( ent->r.currentOrigin, point );
145 		point[2] += (ent->r.mins[2] * 0.5);
146 		break;
147 
148 	case SPOT_WEAPON:
149 		if( ent->NPC && !VectorCompare( ent->NPC->shootAngles, vec3_origin ) && !VectorCompare( ent->NPC->shootAngles, ent->client->ps.viewangles ))
150 		{
151 			AngleVectors( ent->NPC->shootAngles, forward, right, up );
152 		}
153 		else
154 		{
155 			AngleVectors( ent->client->ps.viewangles, forward, right, up );
156 		}
157 		CalcMuzzlePoint( (gentity_t*)ent, forward, right, up, point );
158 		//NOTE: automatically takes leaning into account!
159 		break;
160 
161 	case SPOT_GROUND:
162 		// if entity is on the ground, just use it's absmin
163 		if ( ent->s.groundEntityNum != ENTITYNUM_NONE )
164 		{
165 			VectorCopy( ent->r.currentOrigin, point );
166 			point[2] = ent->r.absmin[2];
167 			break;
168 		}
169 
170 		// if it is reasonably close to the ground, give the point underneath of it
171 		VectorCopy( ent->r.currentOrigin, start );
172 		start[2] = ent->r.absmin[2];
173 		VectorCopy( start, end );
174 		end[2] -= 64;
175 		trap->Trace( &tr, start, ent->r.mins, ent->r.maxs, end, ent->s.number, MASK_PLAYERSOLID, qfalse, 0, 0 );
176 		if ( tr.fraction < 1.0 )
177 		{
178 			VectorCopy( tr.endpos, point);
179 			break;
180 		}
181 
182 		// otherwise just use the origin
183 		VectorCopy( ent->r.currentOrigin, point );
184 		break;
185 
186 	default:
187 		VectorCopy ( ent->r.currentOrigin, point );
188 		break;
189 	}
190 }
191 
192 
193 //===================================================================================
194 
195 /*
196 qboolean NPC_UpdateAngles ( qboolean doPitch, qboolean doYaw )
197 
198 Added: option to do just pitch or just yaw
199 
200 Does not include "aim" in it's calculations
201 
202 FIXME: stop compressing angles into shorts!!!!
203 */
NPC_UpdateAngles(qboolean doPitch,qboolean doYaw)204 qboolean NPC_UpdateAngles ( qboolean doPitch, qboolean doYaw )
205 {
206 #if 1
207 
208 	float		error;
209 	float		decay;
210 	float		targetPitch = 0;
211 	float		targetYaw = 0;
212 	float		yawSpeed;
213 	qboolean	exact = qtrue;
214 
215 	// if angle changes are locked; just keep the current angles
216 	// aimTime isn't even set anymore... so this code was never reached, but I need a way to lock NPC's yaw, so instead of making a new SCF_ flag, just use the existing render flag... - dmv
217 	if ( !NPCS.NPC->enemy && ( (level.time < NPCS.NPCInfo->aimTime) /*|| NPC->client->renderInfo.renderFlags & RF_LOCKEDANGLE*/) )
218 	{
219 		if(doPitch)
220 			targetPitch = NPCS.NPCInfo->lockedDesiredPitch;
221 
222 		if(doYaw)
223 			targetYaw = NPCS.NPCInfo->lockedDesiredYaw;
224 	}
225 	else
226 	{
227 		// we're changing the lockedDesired Pitch/Yaw below so it's lost it's original meaning, get rid of the lock flag
228 	//	NPC->client->renderInfo.renderFlags &= ~RF_LOCKEDANGLE;
229 
230 		if(doPitch)
231 		{
232 			targetPitch = NPCS.NPCInfo->desiredPitch;
233 			NPCS.NPCInfo->lockedDesiredPitch = NPCS.NPCInfo->desiredPitch;
234 		}
235 
236 		if(doYaw)
237 		{
238 			targetYaw = NPCS.NPCInfo->desiredYaw;
239 			NPCS.NPCInfo->lockedDesiredYaw = NPCS.NPCInfo->desiredYaw;
240 		}
241 	}
242 
243 	if ( NPCS.NPC->s.weapon == WP_EMPLACED_GUN )
244 	{
245 		// FIXME: this seems to do nothing, actually...
246 		yawSpeed = 20;
247 	}
248 	else
249 	{
250 		yawSpeed = NPCS.NPCInfo->stats.yawSpeed;
251 	}
252 
253 	if ( NPCS.NPC->s.weapon == WP_SABER && NPCS.NPC->client->ps.fd.forcePowersActive&(1<<FP_SPEED) )
254 	{
255 		char buf[128];
256 		float tFVal = 0;
257 
258 		trap->Cvar_VariableStringBuffer("timescale", buf, sizeof(buf));
259 
260 		tFVal = atof(buf);
261 
262 		yawSpeed *= 1.0f/tFVal;
263 	}
264 
265 	if( doYaw )
266 	{
267 		// decay yaw error
268 		error = AngleDelta ( NPCS.NPC->client->ps.viewangles[YAW], targetYaw );
269 		if( fabs(error) > MIN_ANGLE_ERROR )
270 		{
271 			if ( error )
272 			{
273 				exact = qfalse;
274 
275 				decay = 60.0 + yawSpeed * 3;
276 				decay *= 50.0f / 1000.0f;//msec
277 
278 				if ( error < 0.0 )
279 				{
280 					error += decay;
281 					if ( error > 0.0 )
282 					{
283 						error = 0.0;
284 					}
285 				}
286 				else
287 				{
288 					error -= decay;
289 					if ( error < 0.0 )
290 					{
291 						error = 0.0;
292 					}
293 				}
294 			}
295 		}
296 
297 		NPCS.ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + error ) - NPCS.client->ps.delta_angles[YAW];
298 	}
299 
300 	//FIXME: have a pitchSpeed?
301 	if( doPitch )
302 	{
303 		// decay pitch error
304 		error = AngleDelta ( NPCS.NPC->client->ps.viewangles[PITCH], targetPitch );
305 		if ( fabs(error) > MIN_ANGLE_ERROR )
306 		{
307 			if ( error )
308 			{
309 				exact = qfalse;
310 
311 				decay = 60.0 + yawSpeed * 3;
312 				decay *= 50.0f / 1000.0f;//msec
313 
314 				if ( error < 0.0 )
315 				{
316 					error += decay;
317 					if ( error > 0.0 )
318 					{
319 						error = 0.0;
320 					}
321 				}
322 				else
323 				{
324 					error -= decay;
325 					if ( error < 0.0 )
326 					{
327 						error = 0.0;
328 					}
329 				}
330 			}
331 		}
332 
333 		NPCS.ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + error ) - NPCS.client->ps.delta_angles[PITCH];
334 	}
335 
336 	NPCS.ucmd.angles[ROLL] = ANGLE2SHORT ( NPCS.NPC->client->ps.viewangles[ROLL] ) - NPCS.client->ps.delta_angles[ROLL];
337 
338 	if ( exact && trap->ICARUS_TaskIDPending( (sharedEntity_t *)NPCS.NPC, TID_ANGLE_FACE ) )
339 	{
340 		trap->ICARUS_TaskIDComplete( (sharedEntity_t *)NPCS.NPC, TID_ANGLE_FACE );
341 	}
342 	return exact;
343 
344 #else
345 
346 	float		error;
347 	float		decay;
348 	float		targetPitch = 0;
349 	float		targetYaw = 0;
350 	float		yawSpeed;
351 	//float		runningMod = NPCInfo->currentSpeed/100.0f;
352 	qboolean	exact = qtrue;
353 	qboolean	doSound = qfalse;
354 
355 	// if angle changes are locked; just keep the current angles
356 	if ( level.time < NPCInfo->aimTime )
357 	{
358 		if(doPitch)
359 			targetPitch = NPCInfo->lockedDesiredPitch;
360 		if(doYaw)
361 			targetYaw = NPCInfo->lockedDesiredYaw;
362 	}
363 	else
364 	{
365 		if(doPitch)
366 			targetPitch = NPCInfo->desiredPitch;
367 		if(doYaw)
368 			targetYaw = NPCInfo->desiredYaw;
369 
370 //		NPCInfo->aimTime = level.time + 250;
371 		if(doPitch)
372 			NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch;
373 		if(doYaw)
374 			NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw;
375 	}
376 
377 	yawSpeed = NPCInfo->stats.yawSpeed;
378 
379 	if(doYaw)
380 	{
381 		// decay yaw error
382 		error = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw );
383 		if( fabs(error) > MIN_ANGLE_ERROR )
384 		{
385 			/*
386 			if(NPC->client->playerTeam == TEAM_BORG&&
387 				NPCInfo->behaviorState != BS_FACE&&NPCInfo->tempBehavior!= BS_FACE)
388 			{//HACK - borg turn more jittery
389 				if ( error )
390 				{
391 					exact = qfalse;
392 
393 					decay = 60.0 + yawSpeed * 3;
394 					decay *= 50.0 / 1000.0;//msec
395 					//Snap to
396 					if(fabs(error) > 10)
397 					{
398 						if(Q_flrand(0.0f, 1.0f) > 0.6)
399 						{
400 							doSound = qtrue;
401 						}
402 					}
403 
404 					if ( error < 0.0)//-10.0 )
405 					{
406 						error += decay;
407 						if ( error > 0.0 )
408 						{
409 							error = 0.0;
410 						}
411 					}
412 					else if ( error > 0.0)//10.0 )
413 					{
414 						error -= decay;
415 						if ( error < 0.0 )
416 						{
417 							error = 0.0;
418 						}
419 					}
420 				}
421 			}
422 			else*/
423 
424 			if ( error )
425 			{
426 				exact = qfalse;
427 
428 				decay = 60.0 + yawSpeed * 3;
429 				decay *= 50.0 / 1000.0;//msec
430 
431 				if ( error < 0.0 )
432 				{
433 					error += decay;
434 					if ( error > 0.0 )
435 					{
436 						error = 0.0;
437 					}
438 				}
439 				else
440 				{
441 					error -= decay;
442 					if ( error < 0.0 )
443 					{
444 						error = 0.0;
445 					}
446 				}
447 			}
448 		}
449 		ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + error ) - client->ps.delta_angles[YAW];
450 	}
451 
452 	//FIXME: have a pitchSpeed?
453 	if(doPitch)
454 	{
455 		// decay pitch error
456 		error = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch );
457 		if ( fabs(error) > MIN_ANGLE_ERROR )
458 		{
459 			/*
460 			if(NPC->client->playerTeam == TEAM_BORG&&
461 				NPCInfo->behaviorState != BS_FACE&&NPCInfo->tempBehavior!= BS_FACE)
462 			{//HACK - borg turn more jittery
463 				if ( error )
464 				{
465 					exact = qfalse;
466 
467 					decay = 60.0 + yawSpeed * 3;
468 					decay *= 50.0 / 1000.0;//msec
469 					//Snap to
470 					if(fabs(error) > 10)
471 					{
472 						if(Q_flrand(0.0f, 1.0f) > 0.6)
473 						{
474 							doSound = qtrue;
475 						}
476 					}
477 
478 					if ( error < 0.0)//-10.0 )
479 					{
480 						error += decay;
481 						if ( error > 0.0 )
482 						{
483 							error = 0.0;
484 						}
485 					}
486 					else if ( error > 0.0)//10.0 )
487 					{
488 						error -= decay;
489 						if ( error < 0.0 )
490 						{
491 							error = 0.0;
492 						}
493 					}
494 				}
495 			}
496 			else*/
497 
498 			if ( error )
499 			{
500 				exact = qfalse;
501 
502 				decay = 60.0 + yawSpeed * 3;
503 				decay *= 50.0 / 1000.0;//msec
504 
505 				if ( error < 0.0 )
506 				{
507 					error += decay;
508 					if ( error > 0.0 )
509 					{
510 						error = 0.0;
511 					}
512 				}
513 				else
514 				{
515 					error -= decay;
516 					if ( error < 0.0 )
517 					{
518 						error = 0.0;
519 					}
520 				}
521 			}
522 		}
523 		ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + error ) - client->ps.delta_angles[PITCH];
524 	}
525 
526 	ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL];
527 
528 	/*
529 	if(doSound)
530 	{
531 		G_Sound(NPC, G_SoundIndex(va("sound/enemies/borg/borgservo%d.wav", Q_irand(1, 8))));
532 	}
533 	*/
534 
535 	return exact;
536 
537 #endif
538 
539 }
540 
NPC_AimWiggle(vec3_t enemy_org)541 void NPC_AimWiggle( vec3_t enemy_org )
542 {
543 	//shoot for somewhere between the head and torso
544 	//NOTE: yes, I know this looks weird, but it works
545 	if ( NPCS.NPCInfo->aimErrorDebounceTime < level.time )
546 	{
547 		NPCS.NPCInfo->aimOfs[0] = 0.3*flrand(NPCS.NPC->enemy->r.mins[0], NPCS.NPC->enemy->r.maxs[0]);
548 		NPCS.NPCInfo->aimOfs[1] = 0.3*flrand(NPCS.NPC->enemy->r.mins[1], NPCS.NPC->enemy->r.maxs[1]);
549 		if ( NPCS.NPC->enemy->r.maxs[2] > 0 )
550 		{
551 			NPCS.NPCInfo->aimOfs[2] = NPCS.NPC->enemy->r.maxs[2]*flrand(0.0f, -1.0f);
552 		}
553 	}
554 	VectorAdd( enemy_org, NPCS.NPCInfo->aimOfs, enemy_org );
555 }
556 
557 /*
558 qboolean NPC_UpdateFiringAngles ( qboolean doPitch, qboolean doYaw )
559 
560   Includes aim when determining angles - so they don't always hit...
561   */
NPC_UpdateFiringAngles(qboolean doPitch,qboolean doYaw)562 qboolean NPC_UpdateFiringAngles ( qboolean doPitch, qboolean doYaw )
563 {
564 #if 0
565 
566 	float		diff;
567 	float		error;
568 	float		targetPitch = 0;
569 	float		targetYaw = 0;
570 	qboolean	exact = qtrue;
571 
572 	if ( level.time < NPCInfo->aimTime )
573 	{
574 		if( doPitch )
575 			targetPitch = NPCInfo->lockedDesiredPitch;
576 
577 		if( doYaw )
578 			targetYaw = NPCInfo->lockedDesiredYaw;
579 	}
580 	else
581 	{
582 		if( doPitch )
583 		{
584 			targetPitch = NPCInfo->desiredPitch;
585 			NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch;
586 		}
587 
588 		if( doYaw )
589 		{
590 			targetYaw = NPCInfo->desiredYaw;
591 			NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw;
592 		}
593 	}
594 
595 	if( doYaw )
596 	{
597 		// add yaw error based on NPCInfo->aim value
598 		error = ((float)(6 - NPCInfo->stats.aim)) * flrand(-1, 1);
599 
600 		if(Q_irand(0, 1))
601 			error *= -1;
602 
603 		diff = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw );
604 
605 		if ( diff )
606 			exact = qfalse;
607 
608 		ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + diff + error ) - client->ps.delta_angles[YAW];
609 	}
610 
611 	if( doPitch )
612 	{
613 		// add pitch error based on NPCInfo->aim value
614 		error = ((float)(6 - NPCInfo->stats.aim)) * flrand(-1, 1);
615 
616 		diff = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch );
617 
618 		if ( diff )
619 			exact = qfalse;
620 
621 		ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + diff + error ) - client->ps.delta_angles[PITCH];
622 	}
623 
624 	ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL];
625 
626 	return exact;
627 
628 #else
629 
630 	float		error, diff;
631 	float		decay;
632 	float		targetPitch = 0;
633 	float		targetYaw = 0;
634 	qboolean	exact = qtrue;
635 
636 	// if angle changes are locked; just keep the current angles
637 	if ( level.time < NPCS.NPCInfo->aimTime )
638 	{
639 		if(doPitch)
640 			targetPitch = NPCS.NPCInfo->lockedDesiredPitch;
641 		if(doYaw)
642 			targetYaw = NPCS.NPCInfo->lockedDesiredYaw;
643 	}
644 	else
645 	{
646 		if(doPitch)
647 			targetPitch = NPCS.NPCInfo->desiredPitch;
648 		if(doYaw)
649 			targetYaw = NPCS.NPCInfo->desiredYaw;
650 
651 //		NPCInfo->aimTime = level.time + 250;
652 		if(doPitch)
653 			NPCS.NPCInfo->lockedDesiredPitch = NPCS.NPCInfo->desiredPitch;
654 		if(doYaw)
655 			NPCS.NPCInfo->lockedDesiredYaw = NPCS.NPCInfo->desiredYaw;
656 	}
657 
658 	if ( NPCS.NPCInfo->aimErrorDebounceTime < level.time )
659 	{
660 		if ( Q_irand(0, 1 ) )
661 		{
662 			NPCS.NPCInfo->lastAimErrorYaw = ((float)(6 - NPCS.NPCInfo->stats.aim)) * flrand(-1, 1);
663 		}
664 		if ( Q_irand(0, 1 ) )
665 		{
666 			NPCS.NPCInfo->lastAimErrorPitch = ((float)(6 - NPCS.NPCInfo->stats.aim)) * flrand(-1, 1);
667 		}
668 		NPCS.NPCInfo->aimErrorDebounceTime = level.time + Q_irand(250, 2000);
669 	}
670 
671 	if(doYaw)
672 	{
673 		// decay yaw diff
674 		diff = AngleDelta ( NPCS.NPC->client->ps.viewangles[YAW], targetYaw );
675 
676 		if ( diff)
677 		{
678 			exact = qfalse;
679 
680 			decay = 60.0 + 80.0;
681 			decay *= 50.0f / 1000.0f;//msec
682 			if ( diff < 0.0 )
683 			{
684 				diff += decay;
685 				if ( diff > 0.0 )
686 				{
687 					diff = 0.0;
688 				}
689 			}
690 			else
691 			{
692 				diff -= decay;
693 				if ( diff < 0.0 )
694 				{
695 					diff = 0.0;
696 				}
697 			}
698 		}
699 
700 		// add yaw error based on NPCInfo->aim value
701 		error = NPCS.NPCInfo->lastAimErrorYaw;
702 
703 		/*
704 		if(Q_irand(0, 1))
705 		{
706 			error *= -1;
707 		}
708 		*/
709 
710 		NPCS.ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + diff + error ) - NPCS.client->ps.delta_angles[YAW];
711 	}
712 
713 	if(doPitch)
714 	{
715 		// decay pitch diff
716 		diff = AngleDelta ( NPCS.NPC->client->ps.viewangles[PITCH], targetPitch );
717 		if ( diff)
718 		{
719 			exact = qfalse;
720 
721 			decay = 60.0 + 80.0;
722 			decay *= 50.0f / 1000.0f;//msec
723 			if ( diff < 0.0 )
724 			{
725 				diff += decay;
726 				if ( diff > 0.0 )
727 				{
728 					diff = 0.0;
729 				}
730 			}
731 			else
732 			{
733 				diff -= decay;
734 				if ( diff < 0.0 )
735 				{
736 					diff = 0.0;
737 				}
738 			}
739 		}
740 
741 		error = NPCS.NPCInfo->lastAimErrorPitch;
742 
743 		NPCS.ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + diff + error ) - NPCS.client->ps.delta_angles[PITCH];
744 	}
745 
746 	NPCS.ucmd.angles[ROLL] = ANGLE2SHORT ( NPCS.NPC->client->ps.viewangles[ROLL] ) - NPCS.client->ps.delta_angles[ROLL];
747 
748 	return exact;
749 
750 #endif
751 
752 }
753 //===================================================================================
754 
755 /*
756 static void NPC_UpdateShootAngles (vec3_t angles, qboolean doPitch, qboolean doYaw )
757 
758 Does update angles on shootAngles
759 */
760 
NPC_UpdateShootAngles(vec3_t angles,qboolean doPitch,qboolean doYaw)761 void NPC_UpdateShootAngles (vec3_t angles, qboolean doPitch, qboolean doYaw )
762 {//FIXME: shoot angles either not set right or not used!
763 	float		error;
764 	float		decay;
765 	float		targetPitch = 0;
766 	float		targetYaw = 0;
767 
768 	if(doPitch)
769 		targetPitch = angles[PITCH];
770 	if(doYaw)
771 		targetYaw = angles[YAW];
772 
773 
774 	if(doYaw)
775 	{
776 		// decay yaw error
777 		error = AngleDelta ( NPCS.NPCInfo->shootAngles[YAW], targetYaw );
778 		if ( error )
779 		{
780 			decay = 60.0 + 80.0 * NPCS.NPCInfo->stats.aim;
781 			decay *= 100.0f / 1000.0f;//msec
782 			if ( error < 0.0 )
783 			{
784 				error += decay;
785 				if ( error > 0.0 )
786 				{
787 					error = 0.0;
788 				}
789 			}
790 			else
791 			{
792 				error -= decay;
793 				if ( error < 0.0 )
794 				{
795 					error = 0.0;
796 				}
797 			}
798 		}
799 		NPCS.NPCInfo->shootAngles[YAW] = targetYaw + error;
800 	}
801 
802 	if(doPitch)
803 	{
804 		// decay pitch error
805 		error = AngleDelta ( NPCS.NPCInfo->shootAngles[PITCH], targetPitch );
806 		if ( error )
807 		{
808 			decay = 60.0 + 80.0 * NPCS.NPCInfo->stats.aim;
809 			decay *= 100.0f / 1000.0f;//msec
810 			if ( error < 0.0 )
811 			{
812 				error += decay;
813 				if ( error > 0.0 )
814 				{
815 					error = 0.0;
816 				}
817 			}
818 			else
819 			{
820 				error -= decay;
821 				if ( error < 0.0 )
822 				{
823 					error = 0.0;
824 				}
825 			}
826 		}
827 		NPCS.NPCInfo->shootAngles[PITCH] = targetPitch + error;
828 	}
829 }
830 
831 /*
832 void SetTeamNumbers (void)
833 
834 Sets the number of living clients on each team
835 
836 FIXME: Does not account for non-respawned players!
837 FIXME: Don't include medics?
838 */
SetTeamNumbers(void)839 void SetTeamNumbers (void)
840 {
841 	gentity_t	*found;
842 	int			i;
843 
844 	for( i = 0; i < TEAM_NUM_TEAMS; i++ )
845 	{
846 		teamNumbers[i] = 0;
847 		teamStrength[i] = 0;
848 	}
849 
850 	//OJKFIXME: clientnum 0
851 	for( i = 0; i < 1 ; i++ )
852 	{
853 		found = &g_entities[i];
854 
855 		if( found->client )
856 		{
857 			if( found->health > 0 )//FIXME: or if a player!
858 			{
859 				teamNumbers[found->client->playerTeam]++;
860 				teamStrength[found->client->playerTeam] += found->health;
861 			}
862 		}
863 	}
864 
865 	for( i = 0; i < TEAM_NUM_TEAMS; i++ )
866 	{//Get the average health
867 		teamStrength[i] = floor( ((float)(teamStrength[i])) / ((float)(teamNumbers[i])) );
868 	}
869 }
870 
871 extern stringID_table_t BSTable[];
872 extern stringID_table_t BSETTable[];
G_ActivateBehavior(gentity_t * self,int bset)873 qboolean G_ActivateBehavior (gentity_t *self, int bset )
874 {
875 	bState_t	bSID = (bState_t)-1;
876 	char *bs_name = NULL;
877 
878 	if ( !self )
879 	{
880 		return qfalse;
881 	}
882 
883 	bs_name = self->behaviorSet[bset];
884 
885 	if( !(VALIDSTRING( bs_name )) )
886 	{
887 		return qfalse;
888 	}
889 
890 	if ( self->NPC )
891 	{
892 		bSID = (bState_t)(GetIDForString( BSTable, bs_name ));
893 	}
894 
895 	if(bSID != (bState_t)-1)
896 	{
897 		self->NPC->tempBehavior = BS_DEFAULT;
898 		self->NPC->behaviorState = bSID;
899 	}
900 	else
901 	{
902 		/*
903 		char			newname[MAX_FILENAME_LENGTH];
904 		sprintf((char *) &newname, "%s/%s", Q3_SCRIPT_DIR, bs_name );
905 		*/
906 
907 		//FIXME: between here and actually getting into the ICARUS_RunScript function, the stack gets blown!
908 		//if ( ( ICARUS_entFilter == -1 ) || ( ICARUS_entFilter == self->s.number ) )
909 		if (0)
910 		{
911 			G_DebugPrint( WL_VERBOSE, "%s attempting to run bSet %s (%s)\n", self->targetname, GetStringForID( BSETTable, bset ), bs_name );
912 		}
913 		trap->ICARUS_RunScript( (sharedEntity_t *)self, va( "%s/%s", Q3_SCRIPT_DIR, bs_name ) );
914 	}
915 	return qtrue;
916 }
917 
918 
919 /*
920 =============================================================================
921 
922 	Extended Functions
923 
924 =============================================================================
925 */
926 
927 //rww - special system for sync'ing bone angles between client and server.
NPC_SetBoneAngles(gentity_t * ent,char * bone,vec3_t angles)928 void NPC_SetBoneAngles(gentity_t *ent, char *bone, vec3_t angles)
929 {
930 	int *thebone = &ent->s.boneIndex1;
931 	int *firstFree = NULL;
932 	int i = 0;
933 	int boneIndex = G_BoneIndex(bone);
934 	int flags, up, right, forward;
935 	vec3_t *boneVector = &ent->s.boneAngles1;
936 	vec3_t *freeBoneVec = NULL;
937 
938 	while (thebone)
939 	{
940 		if (!*thebone && !firstFree)
941 		{ //if the value is 0 then this index is clear, we can use it if we don't find the bone we want already existing.
942 			firstFree = thebone;
943 			freeBoneVec = boneVector;
944 		}
945 		else if (*thebone)
946 		{
947 			if (*thebone == boneIndex)
948 			{ //this is it
949 				break;
950 			}
951 		}
952 
953 		switch (i)
954 		{
955 		case 0:
956 			thebone = &ent->s.boneIndex2;
957 			boneVector = &ent->s.boneAngles2;
958 			break;
959 		case 1:
960 			thebone = &ent->s.boneIndex3;
961 			boneVector = &ent->s.boneAngles3;
962 			break;
963 		case 2:
964 			thebone = &ent->s.boneIndex4;
965 			boneVector = &ent->s.boneAngles4;
966 			break;
967 		default:
968 			thebone = NULL;
969 			boneVector = NULL;
970 			break;
971 		}
972 
973 		i++;
974 	}
975 
976 	if (!thebone)
977 	{ //didn't find it, create it
978 		if (!firstFree)
979 		{ //no free bones.. can't do a thing then.
980 			Com_Printf("WARNING: NPC has no free bone indexes\n");
981 			return;
982 		}
983 
984 		thebone = firstFree;
985 
986 		*thebone = boneIndex;
987 		boneVector = freeBoneVec;
988 	}
989 
990 	//If we got here then we have a vector and an index.
991 
992 	//Copy the angles over the vector in the entitystate, so we can use the corresponding index
993 	//to set the bone angles on the client.
994 	VectorCopy(angles, *boneVector);
995 
996 	//Now set the angles on our server instance if we have one.
997 
998 	if (!ent->ghoul2)
999 	{
1000 		return;
1001 	}
1002 
1003 	flags = BONE_ANGLES_POSTMULT;
1004 	up = POSITIVE_X;
1005 	right = NEGATIVE_Y;
1006 	forward = NEGATIVE_Z;
1007 
1008 	//first 3 bits is forward, second 3 bits is right, third 3 bits is up
1009 	ent->s.boneOrient = ((forward)|(right<<3)|(up<<6));
1010 
1011 	trap->G2API_SetBoneAngles(ent->ghoul2, 0, bone, angles, flags, up, right, forward, NULL, 100, level.time);
1012 }
1013 
1014 //rww - and another method of automatically managing surface status for the client and server at once
1015 #define TURN_ON				0x00000000
1016 #define TURN_OFF			0x00000100
1017 
NPC_SetSurfaceOnOff(gentity_t * ent,const char * surfaceName,int surfaceFlags)1018 void NPC_SetSurfaceOnOff(gentity_t *ent, const char *surfaceName, int surfaceFlags)
1019 {
1020 	int i = 0;
1021 	qboolean foundIt = qfalse;
1022 
1023 	while (i < BG_NUM_TOGGLEABLE_SURFACES && bgToggleableSurfaces[i])
1024 	{
1025 		if (!Q_stricmp(surfaceName, bgToggleableSurfaces[i]))
1026 		{ //got it
1027 			foundIt = qtrue;
1028 			break;
1029 		}
1030 		i++;
1031 	}
1032 
1033 	if (!foundIt)
1034 	{
1035 		Com_Printf("WARNING: Tried to toggle NPC surface that isn't in toggleable surface list (%s)\n", surfaceName);
1036 		return;
1037 	}
1038 
1039 	if (surfaceFlags == TURN_ON)
1040 	{ //Make sure the entitystate values reflect this surface as on now.
1041 		ent->s.surfacesOn |= (1 << i);
1042 		ent->s.surfacesOff &= ~(1 << i);
1043 	}
1044 	else
1045 	{ //Otherwise make sure they're off.
1046 		ent->s.surfacesOn &= ~(1 << i);
1047 		ent->s.surfacesOff |= (1 << i);
1048 	}
1049 
1050 	if (!ent->ghoul2)
1051 	{
1052 		return;
1053 	}
1054 
1055 	trap->G2API_SetSurfaceOnOff(ent->ghoul2, surfaceName, surfaceFlags);
1056 }
1057 
1058 //rww - cheap check to see if an armed client is looking in our general direction
NPC_SomeoneLookingAtMe(gentity_t * ent)1059 qboolean NPC_SomeoneLookingAtMe(gentity_t *ent)
1060 {
1061 	int i = 0;
1062 	gentity_t *pEnt;
1063 
1064 	while (i < MAX_CLIENTS)
1065 	{
1066 		pEnt = &g_entities[i];
1067 
1068 		if (pEnt && pEnt->inuse && pEnt->client && pEnt->client->sess.sessionTeam != TEAM_SPECTATOR &&
1069 			pEnt->client->tempSpectate < level.time && !(pEnt->client->ps.pm_flags & PMF_FOLLOW) && pEnt->s.weapon != WP_NONE)
1070 		{
1071 			if (trap->InPVS(ent->r.currentOrigin, pEnt->r.currentOrigin))
1072 			{
1073 				if (InFOV( ent, pEnt, 30, 30 ))
1074 				{ //I'm in a 30 fov or so cone from this player.. that's enough I guess.
1075 					return qtrue;
1076 				}
1077 			}
1078 		}
1079 
1080 		i++;
1081 	}
1082 
1083 	return qfalse;
1084 }
1085 
NPC_ClearLOS(const vec3_t start,const vec3_t end)1086 qboolean NPC_ClearLOS( const vec3_t start, const vec3_t end )
1087 {
1088 	return G_ClearLOS( NPCS.NPC, start, end );
1089 }
NPC_ClearLOS5(const vec3_t end)1090 qboolean NPC_ClearLOS5( const vec3_t end )
1091 {
1092 	return G_ClearLOS5( NPCS.NPC, end );
1093 }
NPC_ClearLOS4(gentity_t * ent)1094 qboolean NPC_ClearLOS4( gentity_t *ent )
1095 {
1096 	return G_ClearLOS4( NPCS.NPC, ent );
1097 }
NPC_ClearLOS3(const vec3_t start,gentity_t * ent)1098 qboolean NPC_ClearLOS3( const vec3_t start, gentity_t *ent )
1099 {
1100 	return G_ClearLOS3( NPCS.NPC, start, ent );
1101 }
NPC_ClearLOS2(gentity_t * ent,const vec3_t end)1102 qboolean NPC_ClearLOS2( gentity_t *ent, const vec3_t end )
1103 {
1104 	return G_ClearLOS2( NPCS.NPC, ent, end );
1105 }
1106 
1107 /*
1108 -------------------------
1109 NPC_ValidEnemy
1110 -------------------------
1111 */
1112 
NPC_ValidEnemy(gentity_t * ent)1113 qboolean NPC_ValidEnemy( gentity_t *ent )
1114 {
1115 	int entTeam = TEAM_FREE;
1116 
1117 	//Must be a valid pointer
1118 	if ( ent == NULL )
1119 		return qfalse;
1120 
1121 	//Must not be me
1122 	if ( ent == NPCS.NPC )
1123 		return qfalse;
1124 
1125 	//Must not be deleted
1126 	if ( ent->inuse == qfalse )
1127 		return qfalse;
1128 
1129 	//Must be alive
1130 	if ( ent->health <= 0 )
1131 		return qfalse;
1132 
1133 	//In case they're in notarget mode
1134 	if ( ent->flags & FL_NOTARGET )
1135 		return qfalse;
1136 
1137 	//Must be an NPC
1138 	if ( ent->client == NULL )
1139 	{
1140 	//	if ( ent->svFlags&SVF_NONNPC_ENEMY )
1141 		if (ent->s.eType != ET_NPC)
1142 		{//still potentially valid
1143 			if ( ent->alliedTeam == NPCS.NPC->client->playerTeam )
1144 			{
1145 				return qfalse;
1146 			}
1147 			else
1148 			{
1149 				return qtrue;
1150 			}
1151 		}
1152 		else
1153 		{
1154 			return qfalse;
1155 		}
1156 	}
1157 	else if ( ent->client && ent->client->sess.sessionTeam == TEAM_SPECTATOR )
1158 	{//don't go after spectators
1159 		return qfalse;
1160 	}
1161 	else if ( ent->client && ent->client->tempSpectate >= level.time )
1162 	{//don't go after spectators
1163 		return qfalse;
1164 	}
1165 	if ( ent->NPC && ent->client )
1166 	{
1167 		entTeam = ent->client->playerTeam;
1168 	}
1169 	else if ( ent->client )
1170 	{
1171 		if (level.gametype < GT_TEAM)
1172 		{
1173 			entTeam = NPCTEAM_PLAYER;
1174 		}
1175 		else
1176 		{
1177 			if ( ent->client->sess.sessionTeam == TEAM_BLUE )
1178 			{
1179 				entTeam = NPCTEAM_PLAYER;
1180 			}
1181 			else if ( ent->client->sess.sessionTeam == TEAM_RED )
1182 			{
1183 				entTeam = NPCTEAM_ENEMY;
1184 			}
1185 			else
1186 			{
1187 				entTeam = NPCTEAM_NEUTRAL;
1188 			}
1189 		}
1190 	}
1191 	//Can't be on the same team
1192 	if ( ent->client->playerTeam == NPCS.NPC->client->playerTeam )
1193 		return qfalse;
1194 
1195 	//if haven't seen him in a while, give up
1196 	//if ( NPCInfo->enemyLastSeenTime != 0 && level.time - NPCInfo->enemyLastSeenTime > 7000 )//FIXME: make a stat?
1197 	//	return qfalse;
1198 	if ( entTeam == NPCS.NPC->client->enemyTeam //simplest case: they're on my enemy team
1199 		|| (NPCS.NPC->client->enemyTeam == NPCTEAM_FREE && ent->client->NPC_class != NPCS.NPC->client->NPC_class )//I get mad at anyone and this guy isn't the same class as me
1200 		|| (ent->client->NPC_class == CLASS_WAMPA && ent->enemy )//a rampaging wampa
1201 		|| (ent->client->NPC_class == CLASS_RANCOR && ent->enemy )//a rampaging rancor
1202 		|| (entTeam == NPCTEAM_FREE && ent->client->enemyTeam == NPCTEAM_FREE && ent->enemy && ent->enemy->client && (ent->enemy->client->playerTeam == NPCS.NPC->client->playerTeam||(ent->enemy->client->playerTeam != NPCTEAM_ENEMY&&NPCS.NPC->client->playerTeam==NPCTEAM_PLAYER))) //enemy is a rampaging non-aligned creature who is attacking someone on our team or a non-enemy (this last condition is used only if we're a good guy - in effect, we protect the innocent)
1203 		)
1204 	{
1205 		return qtrue;
1206 	}
1207 
1208 	return qfalse;
1209 }
1210 
1211 /*
1212 -------------------------
1213 NPC_TargetVisible
1214 -------------------------
1215 */
1216 
NPC_TargetVisible(gentity_t * ent)1217 qboolean NPC_TargetVisible( gentity_t *ent )
1218 {
1219 
1220 	//Make sure we're in a valid range
1221 	if ( DistanceSquared( ent->r.currentOrigin, NPCS.NPC->r.currentOrigin ) > ( NPCS.NPCInfo->stats.visrange * NPCS.NPCInfo->stats.visrange ) )
1222 		return qfalse;
1223 
1224 	//Check our FOV
1225 	if ( InFOV( ent, NPCS.NPC, NPCS.NPCInfo->stats.hfov, NPCS.NPCInfo->stats.vfov ) == qfalse )
1226 		return qfalse;
1227 
1228 	//Check for sight
1229 	if ( NPC_ClearLOS4( ent ) == qfalse )
1230 		return qfalse;
1231 
1232 	return qtrue;
1233 }
1234 
1235 /*
1236 -------------------------
1237 NPC_GetCheckDelta
1238 -------------------------
1239 */
1240 
1241 /*
1242 #define	CHECK_TIME_BASE				250
1243 #define CHECK_TIME_BASE_SQUARED		( CHECK_TIME_BASE * CHECK_TIME_BASE )
1244 
1245 static int NPC_GetCheckDelta( void )
1246 {
1247 	if ( NPC_ValidEnemy( NPC->enemy ) == qfalse )
1248 	{
1249 		int distance = DistanceSquared( NPC->r.currentOrigin, g_entities[0].currentOrigin );
1250 
1251 		distance /= CHECK_TIME_BASE_SQUARED;
1252 
1253 		return ( CHECK_TIME_BASE * distance );
1254 	}
1255 
1256 	return 0;
1257 }
1258 */
1259 
1260 /*
1261 -------------------------
1262 NPC_FindNearestEnemy
1263 -------------------------
1264 */
1265 
1266 #define	MAX_RADIUS_ENTS			256	//NOTE: This can cause entities to be lost
1267 #define NEAR_DEFAULT_RADIUS		256
1268 
NPC_FindNearestEnemy(gentity_t * ent)1269 int NPC_FindNearestEnemy( gentity_t *ent )
1270 {
1271 	int			iradiusEnts[ MAX_RADIUS_ENTS ];
1272 	gentity_t	*radEnt;
1273 	vec3_t		mins, maxs;
1274 	int			nearestEntID = -1;
1275 	float		nearestDist = (float)WORLD_SIZE*(float)WORLD_SIZE;
1276 	float		distance;
1277 	int			numEnts, numChecks = 0;
1278 	int			i;
1279 
1280 	//Setup the bbox to search in
1281 	for ( i = 0; i < 3; i++ )
1282 	{
1283 		mins[i] = ent->r.currentOrigin[i] - NPCS.NPCInfo->stats.visrange;
1284 		maxs[i] = ent->r.currentOrigin[i] + NPCS.NPCInfo->stats.visrange;
1285 	}
1286 
1287 	//Get a number of entities in a given space
1288 	numEnts = trap->EntitiesInBox( mins, maxs, iradiusEnts, MAX_RADIUS_ENTS );
1289 
1290 	for ( i = 0; i < numEnts; i++ )
1291 	{
1292 		radEnt = &g_entities[iradiusEnts[i]];
1293 		//Don't consider self
1294 		if ( radEnt == ent )
1295 			continue;
1296 
1297 		//Must be valid
1298 		if ( NPC_ValidEnemy( radEnt ) == qfalse )
1299 			continue;
1300 
1301 		numChecks++;
1302 		//Must be visible
1303 		if ( NPC_TargetVisible( radEnt ) == qfalse )
1304 			continue;
1305 
1306 		distance = DistanceSquared( ent->r.currentOrigin, radEnt->r.currentOrigin );
1307 
1308 		//Found one closer to us
1309 		if ( distance < nearestDist )
1310 		{
1311 			nearestEntID = radEnt->s.number;
1312 			nearestDist = distance;
1313 		}
1314 	}
1315 
1316 	return nearestEntID;
1317 }
1318 
1319 /*
1320 -------------------------
1321 NPC_PickEnemyExt
1322 -------------------------
1323 */
1324 
NPC_PickEnemyExt(qboolean checkAlerts)1325 gentity_t *NPC_PickEnemyExt( qboolean checkAlerts )
1326 {
1327 	//If we've asked for the closest enemy
1328 	int entID = NPC_FindNearestEnemy( NPCS.NPC );
1329 
1330 	//If we have a valid enemy, use it
1331 	if ( entID >= 0 )
1332 		return &g_entities[entID];
1333 
1334 	if ( checkAlerts )
1335 	{
1336 		int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qtrue, AEL_DISCOVERED );
1337 
1338 		//There is an event to look at
1339 		if ( alertEvent >= 0 )
1340 		{
1341 			alertEvent_t *event = &level.alertEvents[alertEvent];
1342 
1343 			//Don't pay attention to our own alerts
1344 			if ( event->owner == NPCS.NPC )
1345 				return NULL;
1346 
1347 			if ( event->level >= AEL_DISCOVERED )
1348 			{
1349 				//If it's the player, attack him
1350 				//OJKFIXME: clientnum 0
1351 				if ( event->owner == &g_entities[0] )
1352 					return event->owner;
1353 
1354 				//If it's on our team, then take its enemy as well
1355 				if ( ( event->owner->client ) && ( event->owner->client->playerTeam == NPCS.NPC->client->playerTeam ) )
1356 					return event->owner->enemy;
1357 			}
1358 		}
1359 	}
1360 
1361 	return NULL;
1362 }
1363 
1364 /*
1365 -------------------------
1366 NPC_FindPlayer
1367 -------------------------
1368 */
1369 
NPC_FindPlayer(void)1370 qboolean NPC_FindPlayer( void )
1371 {
1372 	//OJKFIXME: clientnum 0
1373 	return NPC_TargetVisible( &g_entities[0] );
1374 }
1375 
1376 /*
1377 -------------------------
1378 NPC_CheckPlayerDistance
1379 -------------------------
1380 */
1381 
NPC_CheckPlayerDistance(void)1382 static qboolean NPC_CheckPlayerDistance( void )
1383 {
1384 	return qfalse;//MOOT in MP
1385 	/*
1386 	float distance;
1387 
1388 	//Make sure we have an enemy
1389 	if ( NPC->enemy == NULL )
1390 		return qfalse;
1391 
1392 	//Only do this for non-players
1393 	if ( NPC->enemy->s.number == 0 )
1394 		return qfalse;
1395 
1396 	//must be set up to get mad at player
1397 	if ( !NPC->client || NPC->client->enemyTeam != NPCTEAM_PLAYER )
1398 		return qfalse;
1399 
1400 	//Must be within our FOV
1401 	if ( InFOV( &g_entities[0], NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ) == qfalse )
1402 		return qfalse;
1403 
1404 	distance = DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin );
1405 
1406 	if ( distance > DistanceSquared( NPC->r.currentOrigin, g_entities[0].r.currentOrigin ) )
1407 	{ //rwwFIXMEFIXME: care about all clients not just client 0
1408 		G_SetEnemy( NPC, &g_entities[0] );
1409 		return qtrue;
1410 	}
1411 
1412 	return qfalse;
1413 	*/
1414 }
1415 
1416 /*
1417 -------------------------
1418 NPC_FindEnemy
1419 -------------------------
1420 */
1421 
NPC_FindEnemy(qboolean checkAlerts)1422 qboolean NPC_FindEnemy( qboolean checkAlerts )
1423 {
1424 	gentity_t *newenemy;
1425 
1426 	//We're ignoring all enemies for now
1427 	//if( NPC->svFlags & SVF_IGNORE_ENEMIES )
1428 	if (0) //rwwFIXMEFIXME: support for flag
1429 	{
1430 		G_ClearEnemy( NPCS.NPC );
1431 		return qfalse;
1432 	}
1433 
1434 	//we can't pick up any enemies for now
1435 	if( NPCS.NPCInfo->confusionTime > level.time )
1436 	{
1437 		return qfalse;
1438 	}
1439 
1440 	//Don't want a new enemy
1441 	//rwwFIXMEFIXME: support for locked enemy
1442 	//if ( ( ValidEnemy( NPC->enemy ) ) && ( NPC->svFlags & SVF_LOCKEDENEMY ) )
1443 	//	return qtrue;
1444 
1445 	//See if the player is closer than our current enemy
1446 	if ( NPC_CheckPlayerDistance() )
1447 	{
1448 		return qtrue;
1449 	}
1450 
1451 	//Otherwise, turn off the flag
1452 //	NPC->svFlags &= ~SVF_LOCKEDENEMY;
1453 	//See if the player is closer than our current enemy
1454 	if ( NPCS.NPC->client->NPC_class != CLASS_RANCOR
1455 		&& NPCS.NPC->client->NPC_class != CLASS_WAMPA
1456 		//&& NPC->client->NPC_class != CLASS_SAND_CREATURE
1457 		&& NPC_CheckPlayerDistance() )
1458 	{//rancors, wampas & sand creatures don't care if player is closer, they always go with closest
1459 		return qtrue;
1460 	}
1461 
1462 	//If we've gotten here alright, then our target it still valid
1463 	if ( NPC_ValidEnemy( NPCS.NPC->enemy ) )
1464 		return qtrue;
1465 
1466 	newenemy = NPC_PickEnemyExt( checkAlerts );
1467 
1468 	//if we found one, take it as the enemy
1469 	if( NPC_ValidEnemy( newenemy ) )
1470 	{
1471 		G_SetEnemy( NPCS.NPC, newenemy );
1472 		return qtrue;
1473 	}
1474 
1475 	return qfalse;
1476 }
1477 
1478 /*
1479 -------------------------
1480 NPC_CheckEnemyExt
1481 -------------------------
1482 */
1483 
NPC_CheckEnemyExt(qboolean checkAlerts)1484 qboolean NPC_CheckEnemyExt( qboolean checkAlerts )
1485 {
1486 	//Make sure we're ready to think again
1487 /*
1488 	if ( NPCInfo->enemyCheckDebounceTime > level.time )
1489 		return qfalse;
1490 
1491 	//Get our next think time
1492 	NPCInfo->enemyCheckDebounceTime = level.time + NPC_GetCheckDelta();
1493 
1494 	//Attempt to find an enemy
1495 	return NPC_FindEnemy();
1496 */
1497 	return NPC_FindEnemy( checkAlerts );
1498 }
1499 
1500 /*
1501 -------------------------
1502 NPC_FacePosition
1503 -------------------------
1504 */
1505 
NPC_FacePosition(vec3_t position,qboolean doPitch)1506 qboolean NPC_FacePosition( vec3_t position, qboolean doPitch )
1507 {
1508 	vec3_t		muzzle;
1509 	vec3_t		angles;
1510 	float		yawDelta;
1511 	qboolean	facing = qtrue;
1512 
1513 	//Get the positions
1514 	if ( NPCS.NPC->client && (NPCS.NPC->client->NPC_class == CLASS_RANCOR || NPCS.NPC->client->NPC_class == CLASS_WAMPA) )// || NPC->client->NPC_class == CLASS_SAND_CREATURE) )
1515 	{
1516 		CalcEntitySpot( NPCS.NPC, SPOT_ORIGIN, muzzle );
1517 		muzzle[2] += NPCS.NPC->r.maxs[2] * 0.75f;
1518 	}
1519 	else if ( NPCS.NPC->client && NPCS.NPC->client->NPC_class == CLASS_GALAKMECH )
1520 	{
1521 		CalcEntitySpot( NPCS.NPC, SPOT_WEAPON, muzzle );
1522 	}
1523 	else
1524 	{
1525 		CalcEntitySpot( NPCS.NPC, SPOT_HEAD_LEAN, muzzle );//SPOT_HEAD
1526 	}
1527 
1528 	//Find the desired angles
1529 	GetAnglesForDirection( muzzle, position, angles );
1530 
1531 	NPCS.NPCInfo->desiredYaw		= AngleNormalize360( angles[YAW] );
1532 	NPCS.NPCInfo->desiredPitch	= AngleNormalize360( angles[PITCH] );
1533 
1534 	if ( NPCS.NPC->enemy && NPCS.NPC->enemy->client && NPCS.NPC->enemy->client->NPC_class == CLASS_ATST )
1535 	{
1536 		// FIXME: this is kind of dumb, but it was the easiest way to get it to look sort of ok
1537 		NPCS.NPCInfo->desiredYaw	+= flrand( -5, 5 ) + sin( level.time * 0.004f ) * 7;
1538 		NPCS.NPCInfo->desiredPitch += flrand( -2, 2 );
1539 	}
1540 	//Face that yaw
1541 	NPC_UpdateAngles( qtrue, qtrue );
1542 
1543 	//Find the delta between our goal and our current facing
1544 	yawDelta = AngleNormalize360( NPCS.NPCInfo->desiredYaw - ( SHORT2ANGLE( NPCS.ucmd.angles[YAW] + NPCS.client->ps.delta_angles[YAW] ) ) );
1545 
1546 	//See if we are facing properly
1547 	if ( fabs( yawDelta ) > VALID_ATTACK_CONE )
1548 		facing = qfalse;
1549 
1550 	if ( doPitch )
1551 	{
1552 		//Find the delta between our goal and our current facing
1553 		float currentAngles = ( SHORT2ANGLE( NPCS.ucmd.angles[PITCH] + NPCS.client->ps.delta_angles[PITCH] ) );
1554 		float pitchDelta = NPCS.NPCInfo->desiredPitch - currentAngles;
1555 
1556 		//See if we are facing properly
1557 		if ( fabs( pitchDelta ) > VALID_ATTACK_CONE )
1558 			facing = qfalse;
1559 	}
1560 
1561 	return facing;
1562 }
1563 
1564 /*
1565 -------------------------
1566 NPC_FaceEntity
1567 -------------------------
1568 */
1569 
NPC_FaceEntity(gentity_t * ent,qboolean doPitch)1570 qboolean NPC_FaceEntity( gentity_t *ent, qboolean doPitch )
1571 {
1572 	vec3_t		entPos;
1573 
1574 	//Get the positions
1575 	CalcEntitySpot( ent, SPOT_HEAD_LEAN, entPos );
1576 
1577 	return NPC_FacePosition( entPos, doPitch );
1578 }
1579 
1580 /*
1581 -------------------------
1582 NPC_FaceEnemy
1583 -------------------------
1584 */
1585 
NPC_FaceEnemy(qboolean doPitch)1586 qboolean NPC_FaceEnemy( qboolean doPitch )
1587 {
1588 	if ( NPCS.NPC == NULL )
1589 		return qfalse;
1590 
1591 	if ( NPCS.NPC->enemy == NULL )
1592 		return qfalse;
1593 
1594 	return NPC_FaceEntity( NPCS.NPC->enemy, doPitch );
1595 }
1596 
1597 /*
1598 -------------------------
1599 NPC_CheckCanAttackExt
1600 -------------------------
1601 */
1602 
NPC_CheckCanAttackExt(void)1603 qboolean NPC_CheckCanAttackExt( void )
1604 {
1605 	//We don't want them to shoot
1606 	if( NPCS.NPCInfo->scriptFlags & SCF_DONT_FIRE )
1607 		return qfalse;
1608 
1609 	//Turn to face
1610 	if ( NPC_FaceEnemy( qtrue ) == qfalse )
1611 		return qfalse;
1612 
1613 	//Must have a clear line of sight to the target
1614 	if ( NPC_ClearShot( NPCS.NPC->enemy ) == qfalse )
1615 		return qfalse;
1616 
1617 	return qtrue;
1618 }
1619 
1620 /*
1621 -------------------------
1622 NPC_ClearLookTarget
1623 -------------------------
1624 */
1625 
NPC_ClearLookTarget(gentity_t * self)1626 void NPC_ClearLookTarget( gentity_t *self )
1627 {
1628 	if ( !self->client )
1629 	{
1630 		return;
1631 	}
1632 
1633 	if ( (self->client->ps.eFlags2&EF2_HELD_BY_MONSTER) )
1634 	{//lookTarget is set by and to the monster that's holding you, no other operations can change that
1635 		return;
1636 	}
1637 
1638 	self->client->renderInfo.lookTarget = ENTITYNUM_NONE;//ENTITYNUM_WORLD;
1639 	self->client->renderInfo.lookTargetClearTime = 0;
1640 }
1641 
1642 /*
1643 -------------------------
1644 NPC_SetLookTarget
1645 -------------------------
1646 */
NPC_SetLookTarget(gentity_t * self,int entNum,int clearTime)1647 void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime )
1648 {
1649 	if ( !self->client )
1650 	{
1651 		return;
1652 	}
1653 
1654 	if ( (self->client->ps.eFlags2&EF2_HELD_BY_MONSTER) )
1655 	{//lookTarget is set by and to the monster that's holding you, no other operations can change that
1656 		return;
1657 	}
1658 
1659 	self->client->renderInfo.lookTarget = entNum;
1660 	self->client->renderInfo.lookTargetClearTime = clearTime;
1661 }
1662 
1663 /*
1664 -------------------------
1665 NPC_CheckLookTarget
1666 -------------------------
1667 */
NPC_CheckLookTarget(gentity_t * self)1668 qboolean NPC_CheckLookTarget( gentity_t *self )
1669 {
1670 	if ( self->client )
1671 	{
1672 		if ( self->client->renderInfo.lookTarget >= 0 && self->client->renderInfo.lookTarget < ENTITYNUM_WORLD )
1673 		{//within valid range
1674 			if ( (&g_entities[self->client->renderInfo.lookTarget] == NULL) || !g_entities[self->client->renderInfo.lookTarget].inuse )
1675 			{//lookTarget not inuse or not valid anymore
1676 				NPC_ClearLookTarget( self );
1677 			}
1678 			else if ( self->client->renderInfo.lookTargetClearTime && self->client->renderInfo.lookTargetClearTime < level.time )
1679 			{//Time to clear lookTarget
1680 				NPC_ClearLookTarget( self );
1681 			}
1682 			else if ( g_entities[self->client->renderInfo.lookTarget].client && self->enemy && (&g_entities[self->client->renderInfo.lookTarget] != self->enemy) )
1683 			{//should always look at current enemy if engaged in battle... FIXME: this could override certain scripted lookTargets...???
1684 				NPC_ClearLookTarget( self );
1685 			}
1686 			else
1687 			{
1688 				return qtrue;
1689 			}
1690 		}
1691 	}
1692 
1693 	return qfalse;
1694 }
1695 
1696 /*
1697 -------------------------
1698 NPC_CheckCharmed
1699 -------------------------
1700 */
1701 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
NPC_CheckCharmed(void)1702 void NPC_CheckCharmed( void )
1703 {
1704 
1705 	if ( NPCS.NPCInfo->charmedTime && NPCS.NPCInfo->charmedTime < level.time && NPCS.NPC->client )
1706 	{//we were charmed, set us back!
1707 		NPCS.NPC->client->playerTeam	= NPCS.NPC->genericValue1;
1708 		NPCS.NPC->client->enemyTeam		= NPCS.NPC->genericValue2;
1709 		NPCS.NPC->s.teamowner			= NPCS.NPC->genericValue3;
1710 
1711 		NPCS.NPC->client->leader = NULL;
1712 		if ( NPCS.NPCInfo->tempBehavior == BS_FOLLOW_LEADER )
1713 		{
1714 			NPCS.NPCInfo->tempBehavior = BS_DEFAULT;
1715 		}
1716 		G_ClearEnemy( NPCS.NPC );
1717 		NPCS.NPCInfo->charmedTime = 0;
1718 		//say something to let player know you've snapped out of it
1719 		G_AddVoiceEvent( NPCS.NPC, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 );
1720 	}
1721 }
1722 
G_GetBoltPosition(gentity_t * self,int boltIndex,vec3_t pos,int modelIndex)1723 void G_GetBoltPosition( gentity_t *self, int boltIndex, vec3_t pos, int modelIndex )
1724 {
1725 	mdxaBone_t	boltMatrix;
1726 	vec3_t		result, angles;
1727 
1728 	if (!self || !self->inuse)
1729 	{
1730 		return;
1731 	}
1732 
1733 	if (self->client)
1734 	{ //clients don't actually even keep r.currentAngles maintained
1735 		VectorSet(angles, 0, self->client->ps.viewangles[YAW], 0);
1736 	}
1737 	else
1738 	{
1739 		VectorSet(angles, 0, self->r.currentAngles[YAW], 0);
1740 	}
1741 
1742 	if ( /*!self || ...haha (sorry, i'm tired)*/ !self->ghoul2 )
1743 	{
1744 		return;
1745 	}
1746 
1747 	trap->G2API_GetBoltMatrix( self->ghoul2, modelIndex,
1748 				boltIndex,
1749 				&boltMatrix, angles, self->r.currentOrigin, level.time,
1750 				NULL, self->modelScale );
1751 	if ( pos )
1752 	{
1753 		BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, result );
1754 		VectorCopy( result, pos );
1755 	}
1756 }
1757 
NPC_EntRangeFromBolt(gentity_t * targEnt,int boltIndex)1758 float NPC_EntRangeFromBolt( gentity_t *targEnt, int boltIndex )
1759 {
1760 	vec3_t	org;
1761 
1762 	if ( !targEnt )
1763 	{
1764 		return Q3_INFINITE;
1765 	}
1766 
1767 	G_GetBoltPosition( NPCS.NPC, boltIndex, org, 0 );
1768 
1769 	return (Distance( targEnt->r.currentOrigin, org ));
1770 }
1771 
NPC_EnemyRangeFromBolt(int boltIndex)1772 float NPC_EnemyRangeFromBolt( int boltIndex )
1773 {
1774 	return (NPC_EntRangeFromBolt( NPCS.NPC->enemy, boltIndex ));
1775 }
1776 
NPC_GetEntsNearBolt(int * radiusEnts,float radius,int boltIndex,vec3_t boltOrg)1777 int NPC_GetEntsNearBolt( int *radiusEnts, float radius, int boltIndex, vec3_t boltOrg )
1778 {
1779 	vec3_t		mins, maxs;
1780 	int			i;
1781 
1782 	//get my handRBolt's position
1783 	vec3_t	org;
1784 
1785 	G_GetBoltPosition( NPCS.NPC, boltIndex, org, 0 );
1786 
1787 	VectorCopy( org, boltOrg );
1788 
1789 	//Setup the bbox to search in
1790 	for ( i = 0; i < 3; i++ )
1791 	{
1792 		mins[i] = boltOrg[i] - radius;
1793 		maxs[i] = boltOrg[i] + radius;
1794 	}
1795 
1796 	//Get the number of entities in a given space
1797 	return (trap->EntitiesInBox( mins, maxs, radiusEnts, 128 ));
1798 }
1799