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