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