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 {
788 if (self->movetarget)
789 self->goalentity = self->movetarget;
790 else
791 self->goalentity = NULL;
792 }
793 self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
794 if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
795 self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
796 }
797 else
798 {
799 self->show_hostile = level.time + 1;
800 return false;
801 }
802 }
803 }
804
805 enemy_vis = false;
806
807 // see if the enemy is dead
808 hesDeadJim = false;
809 if ((!self->enemy) || (!self->enemy->inuse))
810 {
811 hesDeadJim = true;
812 }
813 else if (self->monsterinfo.aiflags & AI_MEDIC)
814 {
815 if (self->enemy->health > 0)
816 {
817 hesDeadJim = true;
818 self->monsterinfo.aiflags &= ~AI_MEDIC;
819 }
820 }
821 else
822 {
823 if (self->monsterinfo.aiflags & AI_BRUTAL)
824 {
825 if (self->enemy->health <= -80)
826 hesDeadJim = true;
827 }
828 else
829 {
830 if (self->enemy->health <= 0)
831 hesDeadJim = true;
832 }
833 }
834
835 if (hesDeadJim)
836 {
837 self->enemy = NULL;
838 // FIXME: look all around for other targets
839 if (self->oldenemy && self->oldenemy->health > 0)
840 {
841 self->enemy = self->oldenemy;
842 self->oldenemy = NULL;
843 HuntTarget (self);
844 }
845 else
846 {
847 if (self->movetarget)
848 {
849 self->goalentity = self->movetarget;
850 self->monsterinfo.walk (self);
851 }
852 else
853 {
854 // we need the pausetime otherwise the stand code
855 // will just revert to walking with no target and
856 // the monsters will wonder around aimlessly trying
857 // to hunt the world entity
858 self->monsterinfo.pausetime = level.time + 100000000;
859 self->monsterinfo.stand (self);
860 }
861 return true;
862 }
863 }
864
865 self->show_hostile = level.time + 1; // wake up other monsters
866
867 // check knowledge of enemy
868 enemy_vis = visible(self, self->enemy);
869 if (enemy_vis)
870 {
871 self->monsterinfo.search_time = level.time + 5;
872 VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
873 }
874
875 // look for other coop players here
876 // if (coop && self->monsterinfo.search_time < level.time)
877 // {
878 // if (FindTarget (self))
879 // return true;
880 // }
881
882 enemy_infront = infront(self, self->enemy);
883 enemy_range = range(self, self->enemy);
884 VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
885 enemy_yaw = vectoyaw(temp);
886
887
888 // JDC self->ideal_yaw = enemy_yaw;
889
890 if (self->monsterinfo.attack_state == AS_MISSILE)
891 {
892 ai_run_missile (self);
893 return true;
894 }
895 if (self->monsterinfo.attack_state == AS_MELEE)
896 {
897 ai_run_melee (self);
898 return true;
899 }
900
901 // if enemy is not currently visible, we will never attack
902 if (!enemy_vis)
903 return false;
904
905 return self->monsterinfo.checkattack (self);
906 }
907
908
909 /*
910 =============
911 ai_run
912
913 The monster has an enemy it is trying to kill
914 =============
915 */
ai_run(edict_t * self,float dist)916 void ai_run (edict_t *self, float dist)
917 {
918 vec3_t v;
919 edict_t *tempgoal;
920 edict_t *save;
921 qboolean new;
922 edict_t *marker;
923 float d1, d2;
924 trace_t tr;
925 vec3_t v_forward, v_right;
926 float left, center, right;
927 vec3_t left_target, right_target;
928
929 // if we're going to a combat point, just proceed
930 if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
931 {
932 M_MoveToGoal (self, dist);
933 return;
934 }
935
936 if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
937 {
938 VectorSubtract (self->s.origin, self->enemy->s.origin, v);
939 if (VectorLength(v) < 64)
940 {
941 self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
942 self->monsterinfo.stand (self);
943 return;
944 }
945
946 M_MoveToGoal (self, dist);
947
948 if (!FindTarget (self))
949 return;
950 }
951
952 if (ai_checkattack (self, dist))
953 return;
954
955 if (self->monsterinfo.attack_state == AS_SLIDING)
956 {
957 ai_run_slide (self, dist);
958 return;
959 }
960
961 if (enemy_vis)
962 {
963 // if (self.aiflags & AI_LOST_SIGHT)
964 // dprint("regained sight\n");
965 M_MoveToGoal (self, dist);
966 self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
967 VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
968 self->monsterinfo.trail_time = level.time;
969 return;
970 }
971
972 // coop will change to another enemy if visible
973 if (coop->value)
974 { // FIXME: insane guys get mad with this, which causes crashes!
975 if (FindTarget (self))
976 return;
977 }
978
979 if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20)))
980 {
981 M_MoveToGoal (self, dist);
982 self->monsterinfo.search_time = 0;
983 // dprint("search timeout\n");
984 return;
985 }
986
987 save = self->goalentity;
988 tempgoal = G_Spawn();
989 self->goalentity = tempgoal;
990
991 new = false;
992
993 if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT))
994 {
995 // just lost sight of the player, decide where to go first
996 // dprint("lost sight of player, last seen at "); dprint(vtos(self.last_sighting)); dprint("\n");
997 self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN);
998 self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP);
999 new = true;
1000 }
1001
1002 if (self->monsterinfo.aiflags & AI_PURSUE_NEXT)
1003 {
1004 self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT;
1005 // 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");
1006
1007 // give ourself more time since we got this far
1008 self->monsterinfo.search_time = level.time + 5;
1009
1010 if (self->monsterinfo.aiflags & AI_PURSUE_TEMP)
1011 {
1012 // dprint("was temp goal; retrying original\n");
1013 self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP;
1014 marker = NULL;
1015 VectorCopy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting);
1016 new = true;
1017 }
1018 else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN)
1019 {
1020 self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN;
1021 marker = PlayerTrail_PickFirst (self);
1022 }
1023 else
1024 {
1025 marker = PlayerTrail_PickNext (self);
1026 }
1027
1028 if (marker)
1029 {
1030 VectorCopy (marker->s.origin, self->monsterinfo.last_sighting);
1031 self->monsterinfo.trail_time = marker->timestamp;
1032 self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW];
1033 // dprint("heading is "); dprint(ftos(self.ideal_yaw)); dprint("\n");
1034
1035 // debug_drawline(self.origin, self.last_sighting, 52);
1036 new = true;
1037 }
1038 }
1039
1040 VectorSubtract (self->s.origin, self->monsterinfo.last_sighting, v);
1041 d1 = VectorLength(v);
1042 if (d1 <= dist)
1043 {
1044 self->monsterinfo.aiflags |= AI_PURSUE_NEXT;
1045 dist = d1;
1046 }
1047
1048 VectorCopy (self->monsterinfo.last_sighting, self->goalentity->s.origin);
1049
1050 if (new)
1051 {
1052 // gi.dprintf("checking for course correction\n");
1053
1054 tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID);
1055 if (tr.fraction < 1)
1056 {
1057 VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1058 d1 = VectorLength(v);
1059 center = tr.fraction;
1060 d2 = d1 * ((center+1)/2);
1061 self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1062 AngleVectors(self->s.angles, v_forward, v_right, NULL);
1063
1064 VectorSet(v, d2, -16, 0);
1065 G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
1066 tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID);
1067 left = tr.fraction;
1068
1069 VectorSet(v, d2, 16, 0);
1070 G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
1071 tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID);
1072 right = tr.fraction;
1073
1074 center = (d1*center)/d2;
1075 if (left >= center && left > right)
1076 {
1077 if (left < 1)
1078 {
1079 VectorSet(v, d2 * left * 0.5, -16, 0);
1080 G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
1081 // gi.dprintf("incomplete path, go part way and adjust again\n");
1082 }
1083 VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
1084 self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
1085 VectorCopy (left_target, self->goalentity->s.origin);
1086 VectorCopy (left_target, self->monsterinfo.last_sighting);
1087 VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1088 self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1089 // gi.dprintf("adjusted left\n");
1090 // debug_drawline(self.origin, self.last_sighting, 152);
1091 }
1092 else if (right >= center && right > left)
1093 {
1094 if (right < 1)
1095 {
1096 VectorSet(v, d2 * right * 0.5, 16, 0);
1097 G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
1098 // gi.dprintf("incomplete path, go part way and adjust again\n");
1099 }
1100 VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
1101 self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
1102 VectorCopy (right_target, self->goalentity->s.origin);
1103 VectorCopy (right_target, self->monsterinfo.last_sighting);
1104 VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1105 self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1106 // gi.dprintf("adjusted right\n");
1107 // debug_drawline(self.origin, self.last_sighting, 152);
1108 }
1109 }
1110 // else gi.dprintf("course was fine\n");
1111 }
1112
1113 M_MoveToGoal (self, dist);
1114
1115 G_FreeEdict(tempgoal);
1116
1117 if (self)
1118 self->goalentity = save;
1119 }
1120