1 // g_ai.c
2
3 #include "g_local.h"
4
5 qboolean FindTarget (edict_t *self);
6 extern cvar_t *maxclients;
7
8 qboolean ai_checkattack (edict_t *self, float dist);
9
10 qboolean enemy_vis;
11 qboolean enemy_infront;
12 int enemy_range;
13 float enemy_yaw;
14
15 //============================================================================
16
17
18 /*
19 =================
20 AI_SetSightClient
21
22 Called once each frame to set level.sight_client to the
23 player to be checked for in findtarget.
24
25 If all clients are either dead or in notarget, sight_client
26 will be null.
27
28 In coop games, sight_client will cycle between the clients.
29 =================
30 */
AI_SetSightClient(void)31 void AI_SetSightClient (void)
32 {
33 edict_t *ent;
34 int start, check;
35 qboolean stealth = false;
36
37 if (level.sight_client == NULL)
38 start = 1;
39 else
40 start = level.sight_client - g_edicts;
41
42 check = start;
43 while (1)
44 {
45 check++;
46 if (check > game.maxclients)
47 check = 1;
48 ent = &g_edicts[check];
49 if (ent->inuse
50 && ent->health > 0
51 && !(ent->flags & FL_NOTARGET))
52 {
53 if (ent->client)
54 if (ent->client->aquasuit)
55 if (VectorLength(ent->velocity)<250)
56 stealth=true;
57 if (!stealth)
58 {
59 level.sight_client = ent;
60 return; // got one
61 }
62 }
63 if (check == start)
64 {
65 level.sight_client = NULL;
66 return; // nobody to see
67 }
68 }
69 }
70
71 //============================================================================
72
73 /*
74 =============
75 ai_move
76
77 Move the specified distance at current facing.
78 This replaces the QC functions: ai_forward, ai_back, ai_pain, and ai_painforward
79 ==============
80 */
ai_move(edict_t * self,float dist)81 void ai_move (edict_t *self, float dist)
82 {
83 M_walkmove (self, self->s.angles[YAW], dist);
84 }
85
86
87 /*
88 =============
89 ai_stand
90
91 Used for standing around and looking for players
92 Distance is for slight position adjustments needed by the animations
93 ==============
94 */
ai_stand(edict_t * self,float dist)95 void ai_stand (edict_t *self, float dist)
96 {
97 vec3_t v;
98
99 if (dist)
100 M_walkmove (self, self->s.angles[YAW], dist);
101
102 if (self->monsterinfo.aiflags & AI_STAND_GROUND)
103 {
104 if (self->enemy)
105 {
106 VectorSubtract (self->enemy->s.origin, self->s.origin, v);
107 self->ideal_yaw = vectoyaw(v);
108 if (self->s.angles[YAW] != self->ideal_yaw && self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
109 {
110 self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
111 self->monsterinfo.run (self);
112 }
113 M_ChangeYaw (self);
114 ai_checkattack (self, 0);
115 }
116 else
117 FindTarget (self);
118 return;
119 }
120
121 if (FindTarget (self))
122 return;
123
124 if (level.time > self->monsterinfo.pausetime)
125 {
126 self->monsterinfo.walk (self);
127 return;
128 }
129
130 if (!(self->spawnflags & 1) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time))
131 {
132 if (self->monsterinfo.idle_time)
133 {
134 self->monsterinfo.idle (self);
135 self->monsterinfo.idle_time = level.time + 15 + random() * 15;
136 }
137 else
138 {
139 self->monsterinfo.idle_time = level.time + random() * 15;
140 }
141 }
142 }
143
144
145 /*
146 =============
147 ai_walk
148
149 The monster is walking it's beat
150 =============
151 */
ai_walk(edict_t * self,float dist)152 void ai_walk (edict_t *self, float dist)
153 {
154 M_MoveToGoal (self, dist);
155
156 // check for noticing a player
157 if (FindTarget (self))
158 return;
159
160 if ((self->monsterinfo.search) && (level.time > self->monsterinfo.idle_time))
161 {
162 if (self->monsterinfo.idle_time)
163 {
164 self->monsterinfo.search (self);
165 self->monsterinfo.idle_time = level.time + 15 + random() * 15;
166 }
167 else
168 {
169 self->monsterinfo.idle_time = level.time + random() * 15;
170 }
171 }
172 }
173
174
175 /*
176 =============
177 ai_charge
178
179 Turns towards target and advances
180 Use this call with a distnace of 0 to replace ai_face
181 ==============
182 */
ai_charge(edict_t * self,float dist)183 void ai_charge (edict_t *self, float dist)
184 {
185 vec3_t v;
186
187 VectorSubtract (self->enemy->s.origin, self->s.origin, v);
188 self->ideal_yaw = vectoyaw(v);
189 M_ChangeYaw (self);
190
191 if (dist)
192 M_walkmove (self, self->s.angles[YAW], dist);
193 }
194
195
196 /*
197 =============
198 ai_turn
199
200 don't move, but turn towards ideal_yaw
201 Distance is for slight position adjustments needed by the animations
202 =============
203 */
ai_turn(edict_t * self,float dist)204 void ai_turn (edict_t *self, float dist)
205 {
206 if (dist)
207 M_walkmove (self, self->s.angles[YAW], dist);
208
209 if (FindTarget (self))
210 return;
211
212 M_ChangeYaw (self);
213 }
214
215
216 /*
217
218 .enemy
219 Will be world if not currently angry at anyone.
220
221 .movetarget
222 The next path spot to walk toward. If .enemy, ignore .movetarget.
223 When an enemy is killed, the monster will try to return to it's path.
224
225 .hunt_time
226 Set to time + something when the player is in sight, but movement straight for
227 him is blocked. This causes the monster to use wall following code for
228 movement direction instead of sighting on the player.
229
230 .ideal_yaw
231 A yaw angle of the intended direction, which will be turned towards at up
232 to 45 deg / state. If the enemy is in view and hunt_time is not active,
233 this will be the exact line towards the enemy.
234
235 .pausetime
236 A monster will leave it's stand state and head towards it's .movetarget when
237 time > .pausetime.
238
239 walkmove(angle, speed) primitive is all or nothing
240 */
241
242 /*
243 =============
244 range
245
246 returns the range catagorization of an entity reletive to self
247 0 melee range, will become hostile even if back is turned
248 1 visibility and infront, or visibility and show hostile
249 2 infront and show hostile
250 3 only triggered by damage
251 =============
252 */
range(edict_t * self,edict_t * other)253 int range (edict_t *self, edict_t *other)
254 {
255 vec3_t v;
256 float len;
257
258 VectorSubtract (self->s.origin, other->s.origin, v);
259 len = VectorLength (v);
260 if (len < MELEE_DISTANCE)
261 return RANGE_MELEE;
262 if (len < 500)
263 return RANGE_NEAR;
264 if (len < 1000)
265 return RANGE_MID;
266 return RANGE_FAR;
267 }
268
269 /*
270 =============
271 visible
272
273 returns 1 if the entity is visible to self, even if not infront ()
274 =============
275 */
visible(edict_t * self,edict_t * other)276 qboolean visible (edict_t *self, edict_t *other)
277 {
278 vec3_t spot1;
279 vec3_t spot2;
280 trace_t trace;
281
282 VectorCopy (self->s.origin, spot1);
283 spot1[2] += self->viewheight;
284 VectorCopy (other->s.origin, spot2);
285 spot2[2] += other->viewheight;
286 trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE);
287
288 if (trace.fraction == 1.0)
289 return true;
290 return false;
291 }
292
293
294 /*
295 =============
296 infront
297
298 returns 1 if the entity is in front (in sight) of self
299 =============
300 */
infront(edict_t * self,edict_t * other)301 qboolean infront (edict_t *self, edict_t *other)
302 {
303 vec3_t vec;
304 float dot;
305 vec3_t forward;
306
307 AngleVectors (self->s.angles, forward, NULL, NULL);
308 VectorSubtract (other->s.origin, self->s.origin, vec);
309 VectorNormalize (vec);
310 dot = DotProduct (vec, forward);
311
312 if (dot > 0.3)
313 return true;
314 return false;
315 }
316
317
318 //============================================================================
319
HuntTarget(edict_t * self)320 void HuntTarget (edict_t *self)
321 {
322 vec3_t vec;
323
324 self->goalentity = self->enemy;
325 if (self->monsterinfo.aiflags & AI_STAND_GROUND)
326 self->monsterinfo.stand (self);
327 else
328 self->monsterinfo.run (self);
329 VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
330 self->ideal_yaw = vectoyaw(vec);
331 // wait a while before first attack
332 if (!(self->monsterinfo.aiflags & AI_STAND_GROUND))
333 AttackFinished (self, 1);
334 }
335
FoundTarget(edict_t * self)336 void FoundTarget (edict_t *self)
337 {
338 // let other monsters see this monster for a while
339 if (self->enemy->client)
340 {
341 level.sight_entity = self;
342 level.sight_entity_framenum = level.framenum;
343 level.sight_entity->light_level = 128;
344 }
345
346 self->show_hostile = level.time + 1; // wake up other monsters
347
348 VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
349 self->monsterinfo.trail_time = level.time;
350
351 if (!self->combattarget)
352 {
353 HuntTarget (self);
354 return;
355 }
356
357 self->goalentity = self->movetarget = G_PickTarget(self->combattarget);
358 if (!self->movetarget)
359 {
360 self->goalentity = self->movetarget = self->enemy;
361 HuntTarget (self);
362 gi.dprintf("%s at %s, combattarget %s not found\n", self->classname, vtos(self->s.origin), self->combattarget);
363 return;
364 }
365
366 // clear out our combattarget, these are a one shot deal
367 self->combattarget = NULL;
368 self->monsterinfo.aiflags |= AI_COMBAT_POINT;
369
370 // clear the targetname, that point is ours!
371 self->movetarget->targetname = NULL;
372 self->monsterinfo.pausetime = 0;
373
374 // run for it
375 self->monsterinfo.run (self);
376 }
377
378
379 /*
380 ===========
381 FindTarget
382
383 Self is currently not attacking anything, so try to find a target
384
385 Returns TRUE if an enemy was sighted
386
387 When a player fires a missile, the point of impact becomes a fakeplayer so
388 that monsters that see the impact will respond as if they had seen the
389 player.
390
391 To avoid spending too much time, only a single client (or fakeclient) is
392 checked each frame. This means multi player games will have slightly
393 slower noticing monsters.
394 ============
395 */
FindTarget(edict_t * self)396 qboolean FindTarget (edict_t *self)
397 {
398 edict_t *target;
399 qboolean heardit;
400 int r;
401
402 if (self->flashbanged)
403 return false;
404
405 if (self->monsterinfo.aiflags & AI_GOOD_GUY)
406 {
407 if (self->goalentity && self->goalentity->inuse && self->goalentity->classname)
408 {
409 if (strcmp(self->goalentity->classname, "target_actor") == 0)
410 return false;
411 }
412
413 //FIXME look for monsters?
414 return false;
415 }
416
417 // if we're going to a combat point, just proceed
418 if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
419 return false;
420
421 // if the first spawnflag bit is set, the monster will only wake up on
422 // really seeing the player, not another monster getting angry or hearing
423 // something
424
425 // revised behavior so they will wake up if they "see" a player make a noise
426 // but not weapon impact/explosion noises
427
428 heardit = false;
429 if ((level.sight_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) )
430 {
431 target = level.sight_entity;
432 if (target->enemy == self->enemy)
433 {
434 return false;
435 }
436 }
437 else if (level.sound_entity_framenum >= (level.framenum - 1))
438 {
439 target = level.sound_entity;
440 heardit = true;
441 }
442 else if (!(self->enemy) && (level.sound2_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) )
443 {
444 target = level.sound2_entity;
445 heardit = true;
446 }
447 else
448 {
449 target = level.sight_client;
450 if (!target)
451 return false; // no clients to get mad at
452 }
453
454 // if the entity went away, forget it
455 if (!target->inuse)
456 return false;
457
458 if (target == self->enemy)
459 return true; // JDC false;
460
461 if (target->client)
462 {
463 if (target->flags & FL_NOTARGET)
464 return false;
465 }
466 else if (target->svflags & SVF_MONSTER)
467 {
468 if (!self->monsterinfo.enemycheck || !self->monsterinfo.enemycheck(self, target))
469 {
470 if (!target->enemy)
471 return false;
472 if (target->enemy->flags & FL_NOTARGET)
473 return false;
474 }
475 }
476 else if (target)
477 {
478 if (target->owner->flags & FL_NOTARGET)
479 return false;
480 }
481 else
482 return false;
483
484 if (!heardit)
485 {
486 r = range (self, target);
487
488 if (r == RANGE_FAR)
489 return false;
490
491 // this is where we would check invisibility
492
493 // is client in an spot too dark to be seen?
494 if (target->client && target->light_level <= 5)
495 return false;
496
497 if (!visible (self, target))
498 {
499 return false;
500 }
501
502 if (r == RANGE_NEAR)
503 {
504 if (target->show_hostile < level.time && !infront (self, target))
505 {
506 return false;
507 }
508 }
509 else if (r == RANGE_MID)
510 {
511 if (!infront (self, target))
512 {
513 return false;
514 }
515 }
516
517 self->enemy = target;
518
519 if (strcmp(self->enemy->classname, "player_noise") != 0)
520 {
521 self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
522
523 if (!self->enemy->client)
524 {
525 self->enemy = self->enemy->enemy;
526 if (!self->enemy->client)
527 {
528 self->enemy = NULL;
529 return false;
530 }
531 }
532 }
533 }
534 else // heardit
535 {
536 vec3_t temp;
537
538 if (self->spawnflags & 1)
539 {
540 if (!visible (self, target))
541 return false;
542 }
543 else
544 {
545 if (!gi.inPHS(self->s.origin, target->s.origin))
546 return false;
547 }
548
549 VectorSubtract (target->s.origin, self->s.origin, temp);
550
551 if (VectorLength(temp) > 1000) // too far to hear
552 {
553 return false;
554 }
555
556 // check area portals - if they are different and not connected then we can't hear it
557 if (target->areanum != self->areanum)
558 if (!gi.AreasConnected(self->areanum, target->areanum))
559 return false;
560
561 self->ideal_yaw = vectoyaw(temp);
562 M_ChangeYaw (self);
563
564 // hunt the sound for a bit; hopefully find the real player
565 self->monsterinfo.aiflags |= AI_SOUND_TARGET;
566 self->enemy = target;
567 }
568
569 //
570 // got one
571 //
572 FoundTarget (self);
573
574 if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) && (self->monsterinfo.sight))
575 self->monsterinfo.sight (self, self->enemy);
576
577 return true;
578 }
579
580
581 //=============================================================================
582
583 /*
584 ============
585 FacingIdeal
586
587 ============
588 */
FacingIdeal(edict_t * self)589 qboolean FacingIdeal(edict_t *self)
590 {
591 float delta;
592
593 delta = anglemod(self->s.angles[YAW] - self->ideal_yaw);
594 if (delta > 45 && delta < 315)
595 return false;
596 return true;
597 }
598
599
600 //=============================================================================
601
M_CheckAttack(edict_t * self)602 qboolean M_CheckAttack (edict_t *self)
603 {
604 vec3_t spot1, spot2;
605 float chance;
606 trace_t tr;
607
608 if (self->flashbanged)
609 return false;
610
611 if (self->enemy->health > 0)
612 {
613 // see if any entities are in the way of the shot
614 VectorCopy (self->s.origin, spot1);
615 spot1[2] += self->viewheight;
616 VectorCopy (self->enemy->s.origin, spot2);
617 spot2[2] += self->enemy->viewheight;
618
619 tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW);
620
621 // do we have a clear shot?
622 if (tr.ent != self->enemy)
623 return false;
624 }
625
626 // melee attack
627 if (enemy_range == RANGE_MELEE)
628 {
629 // don't always melee in easy mode
630 if (skill->value == 0 && (rand()&3) )
631 return false;
632 if (self->monsterinfo.melee)
633 self->monsterinfo.attack_state = AS_MELEE;
634 else
635 self->monsterinfo.attack_state = AS_MISSILE;
636 return true;
637 }
638
639 // missile attack
640 if (!self->monsterinfo.attack)
641 return false;
642
643 if (level.time < self->monsterinfo.attack_finished)
644 return false;
645
646 if (enemy_range == RANGE_FAR)
647 return false;
648
649 if (self->monsterinfo.aiflags & AI_STAND_GROUND)
650 {
651 chance = 0.4;
652 }
653 else if (enemy_range == RANGE_MELEE)
654 {
655 chance = 0.2;
656 }
657 else if (enemy_range == RANGE_NEAR)
658 {
659 chance = 0.1;
660 }
661 else if (enemy_range == RANGE_MID)
662 {
663 chance = 0.02;
664 }
665 else
666 {
667 return false;
668 }
669
670 if (skill->value == 0)
671 chance *= 0.5;
672 else if (skill->value >= 2)
673 chance *= 2;
674
675 if (random () < chance)
676 {
677 self->monsterinfo.attack_state = AS_MISSILE;
678 self->monsterinfo.attack_finished = level.time + 2*random();
679 return true;
680 }
681
682 if (self->flags & FL_FLY)
683 {
684 if (random() < 0.3)
685 self->monsterinfo.attack_state = AS_SLIDING;
686 else
687 self->monsterinfo.attack_state = AS_STRAIGHT;
688 }
689
690 return false;
691 }
692
693
694 /*
695 =============
696 ai_run_melee
697
698 Turn and close until within an angle to launch a melee attack
699 =============
700 */
ai_run_melee(edict_t * self)701 void ai_run_melee(edict_t *self)
702 {
703 self->ideal_yaw = enemy_yaw;
704 M_ChangeYaw (self);
705
706 if (FacingIdeal(self))
707 {
708 self->monsterinfo.melee (self);
709 self->monsterinfo.attack_state = AS_STRAIGHT;
710 }
711 }
712
713
714 /*
715 =============
716 ai_run_missile
717
718 Turn in place until within an angle to launch a missile attack
719 =============
720 */
ai_run_missile(edict_t * self)721 void ai_run_missile(edict_t *self)
722 {
723 self->ideal_yaw = enemy_yaw;
724 M_ChangeYaw (self);
725
726 if (FacingIdeal(self))
727 {
728 self->monsterinfo.attack (self);
729 self->monsterinfo.attack_state = AS_STRAIGHT;
730 }
731 };
732
733
734 /*
735 =============
736 ai_run_slide
737
738 Strafe sideways, but stay at aproximately the same range
739 =============
740 */
ai_run_slide(edict_t * self,float distance)741 void ai_run_slide(edict_t *self, float distance)
742 {
743 float ofs;
744
745 self->ideal_yaw = enemy_yaw;
746 M_ChangeYaw (self);
747
748 if (self->monsterinfo.lefty)
749 ofs = 90;
750 else
751 ofs = -90;
752
753 if (M_walkmove (self, self->ideal_yaw + ofs, distance))
754 return;
755
756 self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
757 M_walkmove (self, self->ideal_yaw - ofs, distance);
758 }
759
760
761 /*
762 =============
763 ai_checkattack
764
765 Decides if we're going to attack or do something else
766 used by ai_run and ai_stand
767 =============
768 */
ai_checkattack(edict_t * self,float dist)769 qboolean ai_checkattack (edict_t *self, float dist)
770 {
771 vec3_t temp;
772 qboolean hesDeadJim;
773
774 // this causes monsters to run blindly to the combat point w/o firing
775 if (self->goalentity)
776 {
777 if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
778 return false;
779
780 if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
781 {
782 if ((level.time - self->enemy->teleport_time) > 5.0)
783 {
784 if (self->goalentity == self->enemy)
785 if (self->movetarget)
786 self->goalentity = self->movetarget;
787 else
788 self->goalentity = NULL;
789 self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
790 if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
791 self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
792 }
793 else
794 {
795 self->show_hostile = level.time + 1;
796 return false;
797 }
798 }
799 }
800
801 enemy_vis = false;
802
803 // see if the enemy is dead
804 hesDeadJim = false;
805 if ((!self->enemy) || (!self->enemy->inuse))
806 {
807 hesDeadJim = true;
808 }
809 else if (self->monsterinfo.aiflags & AI_MEDIC)
810 {
811 if (self->enemy->health > 0)
812 {
813 hesDeadJim = true;
814 self->monsterinfo.aiflags &= ~AI_MEDIC;
815 }
816 }
817 else
818 {
819 if (self->monsterinfo.aiflags & AI_BRUTAL)
820 {
821 if (self->enemy->health <= -80)
822 hesDeadJim = true;
823 }
824 else
825 {
826 if (self->enemy->health <= 0)
827 hesDeadJim = true;
828 }
829 }
830
831 if (hesDeadJim)
832 {
833 self->enemy = NULL;
834 // FIXME: look all around for other targets
835 if (self->oldenemy && self->oldenemy->health > 0)
836 {
837 self->enemy = self->oldenemy;
838 self->oldenemy = NULL;
839 HuntTarget (self);
840 }
841 else
842 {
843 if (self->movetarget)
844 {
845 self->goalentity = self->movetarget;
846 self->monsterinfo.walk (self);
847 }
848 else
849 {
850 // we need the pausetime otherwise the stand code
851 // will just revert to walking with no target and
852 // the monsters will wonder around aimlessly trying
853 // to hunt the world entity
854 self->monsterinfo.pausetime = level.time + 100000000;
855 self->monsterinfo.stand (self);
856 }
857 return true;
858 }
859 }
860
861 self->show_hostile = level.time + 1; // wake up other monsters
862
863 // check knowledge of enemy
864 enemy_vis = visible(self, self->enemy);
865 if (enemy_vis)
866 {
867 self->monsterinfo.search_time = level.time + 5;
868 VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
869 }
870
871 // look for other coop players here
872 // if (coop && self->monsterinfo.search_time < level.time)
873 // {
874 // if (FindTarget (self))
875 // return true;
876 // }
877
878 enemy_infront = infront(self, self->enemy);
879 enemy_range = range(self, self->enemy);
880 VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
881 enemy_yaw = vectoyaw(temp);
882
883
884 // JDC self->ideal_yaw = enemy_yaw;
885
886 if (self->monsterinfo.attack_state == AS_MISSILE)
887 {
888 ai_run_missile (self);
889 return true;
890 }
891 if (self->monsterinfo.attack_state == AS_MELEE)
892 {
893 ai_run_melee (self);
894 return true;
895 }
896
897 // if enemy is not currently visible, we will never attack
898 if (!enemy_vis)
899 return false;
900
901 return self->monsterinfo.checkattack (self);
902 }
903
904
905 /*
906 =============
907 ai_run
908
909 The monster has an enemy it is trying to kill
910 =============
911 */
ai_run(edict_t * self,float dist)912 void ai_run (edict_t *self, float dist)
913 {
914 vec3_t v;
915 edict_t *tempgoal;
916 edict_t *save;
917 qboolean new;
918 edict_t *marker;
919 float d1, d2;
920 trace_t tr;
921 vec3_t v_forward, v_right;
922 float left, center, right;
923 vec3_t left_target, right_target;
924
925 // if we're going to a combat point, just proceed
926 if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
927 {
928 M_MoveToGoal (self, dist);
929 return;
930 }
931
932 if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
933 {
934 VectorSubtract (self->s.origin, self->enemy->s.origin, v);
935 if (VectorLength(v) < 64)
936 {
937 self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
938 self->monsterinfo.stand (self);
939 return;
940 }
941
942 M_MoveToGoal (self, dist);
943
944 if (!FindTarget (self))
945 return;
946 }
947
948 if (ai_checkattack (self, dist))
949 return;
950
951 if (self->monsterinfo.attack_state == AS_SLIDING)
952 {
953 ai_run_slide (self, dist);
954 return;
955 }
956
957 if (enemy_vis)
958 {
959 // if (self.aiflags & AI_LOST_SIGHT)
960 // dprint("regained sight\n");
961 M_MoveToGoal (self, dist);
962 self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
963 VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
964 self->monsterinfo.trail_time = level.time;
965 return;
966 }
967
968 // coop will change to another enemy if visible
969 if (coop->value)
970 { // FIXME: insane guys get mad with this, which causes crashes!
971 if (FindTarget (self))
972 return;
973 }
974
975 if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20)))
976 {
977 M_MoveToGoal (self, dist);
978 self->monsterinfo.search_time = 0;
979 // dprint("search timeout\n");
980 return;
981 }
982
983 save = self->goalentity;
984 tempgoal = G_Spawn();
985 self->goalentity = tempgoal;
986
987 new = false;
988
989 if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT))
990 {
991 // just lost sight of the player, decide where to go first
992 // dprint("lost sight of player, last seen at "); dprint(vtos(self.last_sighting)); dprint("\n");
993 self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN);
994 self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP);
995 new = true;
996 }
997
998 if (self->monsterinfo.aiflags & AI_PURSUE_NEXT)
999 {
1000 self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT;
1001 // dprint("reached current goal: "); dprint(vtos(self.origin)); dprint(" "); dprint(vtos(self.last_sighting)); dprint(" "); dprint(ftos(vlen(self.origin - self.last_sighting))); dprint("\n");
1002
1003 // give ourself more time since we got this far
1004 self->monsterinfo.search_time = level.time + 5;
1005
1006 if (self->monsterinfo.aiflags & AI_PURSUE_TEMP)
1007 {
1008 // dprint("was temp goal; retrying original\n");
1009 self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP;
1010 marker = NULL;
1011 VectorCopy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting);
1012 new = true;
1013 }
1014 else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN)
1015 {
1016 self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN;
1017 marker = PlayerTrail_PickFirst (self);
1018 }
1019 else
1020 {
1021 marker = PlayerTrail_PickNext (self);
1022 }
1023
1024 if (marker)
1025 {
1026 VectorCopy (marker->s.origin, self->monsterinfo.last_sighting);
1027 self->monsterinfo.trail_time = marker->timestamp;
1028 self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW];
1029 // dprint("heading is "); dprint(ftos(self.ideal_yaw)); dprint("\n");
1030
1031 // debug_drawline(self.origin, self.last_sighting, 52);
1032 new = true;
1033 }
1034 }
1035
1036 VectorSubtract (self->s.origin, self->monsterinfo.last_sighting, v);
1037 d1 = VectorLength(v);
1038 if (d1 <= dist)
1039 {
1040 self->monsterinfo.aiflags |= AI_PURSUE_NEXT;
1041 dist = d1;
1042 }
1043
1044 VectorCopy (self->monsterinfo.last_sighting, self->goalentity->s.origin);
1045
1046 if (new)
1047 {
1048 // gi.dprintf("checking for course correction\n");
1049
1050 tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID);
1051 if (tr.fraction < 1)
1052 {
1053 VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1054 d1 = VectorLength(v);
1055 center = tr.fraction;
1056 d2 = d1 * ((center+1)/2);
1057 self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1058 AngleVectors(self->s.angles, v_forward, v_right, NULL);
1059
1060 VectorSet(v, d2, -16, 0);
1061 G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
1062 tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID);
1063 left = tr.fraction;
1064
1065 VectorSet(v, d2, 16, 0);
1066 G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
1067 tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID);
1068 right = tr.fraction;
1069
1070 center = (d1*center)/d2;
1071 if (left >= center && left > right)
1072 {
1073 if (left < 1)
1074 {
1075 VectorSet(v, d2 * left * 0.5, -16, 0);
1076 G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
1077 // gi.dprintf("incomplete path, go part way and adjust again\n");
1078 }
1079 VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
1080 self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
1081 VectorCopy (left_target, self->goalentity->s.origin);
1082 VectorCopy (left_target, self->monsterinfo.last_sighting);
1083 VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1084 self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1085 // gi.dprintf("adjusted left\n");
1086 // debug_drawline(self.origin, self.last_sighting, 152);
1087 }
1088 else if (right >= center && right > left)
1089 {
1090 if (right < 1)
1091 {
1092 VectorSet(v, d2 * right * 0.5, 16, 0);
1093 G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
1094 // gi.dprintf("incomplete path, go part way and adjust again\n");
1095 }
1096 VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
1097 self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
1098 VectorCopy (right_target, self->goalentity->s.origin);
1099 VectorCopy (right_target, self->monsterinfo.last_sighting);
1100 VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1101 self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1102 // gi.dprintf("adjusted right\n");
1103 // debug_drawline(self.origin, self.last_sighting, 152);
1104 }
1105 }
1106 // else gi.dprintf("course was fine\n");
1107 }
1108
1109 M_MoveToGoal (self, dist);
1110
1111 G_FreeEdict(tempgoal);
1112
1113 if (self)
1114 self->goalentity = save;
1115 }
1116