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