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 // ROGUE STUFF
16 #define SLIDING_TROOPS 1
17 #define MAX_SIDESTEP 8.0
18 //
19
20 //============================================================================
21
22
23 /*
24 =================
25 AI_SetSightClient
26
27 Called once each frame to set level.sight_client to the
28 player to be checked for in findtarget.
29
30 If all clients are either dead or in notarget, sight_client
31 will be null.
32
33 In coop games, sight_client will cycle between the clients.
34 =================
35 */
AI_SetSightClient(void)36 void AI_SetSightClient (void)
37 {
38 edict_t *ent;
39 int start, check;
40
41 if (level.sight_client == NULL)
42 start = 1;
43 else
44 start = level.sight_client - g_edicts;
45
46 check = start;
47 while (1)
48 {
49 check++;
50 if (check > game.maxclients)
51 check = 1;
52 ent = &g_edicts[check];
53 if (ent->inuse
54 && ent->health > 0
55 && !(ent->flags & (FL_NOTARGET|FL_DISGUISED)) )
56 {
57 level.sight_client = ent;
58 return; // got one
59 }
60 if (check == start)
61 {
62 level.sight_client = NULL;
63 return; // nobody to see
64 }
65 }
66 }
67
68 //============================================================================
69
70 /*
71 =============
72 ai_move
73
74 Move the specified distance at current facing.
75 This replaces the QC functions: ai_forward, ai_back, ai_pain, and ai_painforward
76 ==============
77 */
ai_move(edict_t * self,float dist)78 void ai_move (edict_t *self, float dist)
79 {
80 M_walkmove (self, self->s.angles[YAW], dist);
81 }
82
83
84 /*
85 =============
86 ai_stand
87
88 Used for standing around and looking for players
89 Distance is for slight position adjustments needed by the animations
90 ==============
91 */
ai_stand(edict_t * self,float dist)92 void ai_stand (edict_t *self, float dist)
93 {
94 vec3_t v;
95 // PMM
96 qboolean retval;
97
98 if (dist)
99 M_walkmove (self, self->s.angles[YAW], dist);
100
101 if (self->monsterinfo.aiflags & AI_STAND_GROUND)
102 {
103 if (self->enemy)
104 {
105 VectorSubtract (self->enemy->s.origin, self->s.origin, v);
106 self->ideal_yaw = vectoyaw(v);
107 if (self->s.angles[YAW] != self->ideal_yaw && self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
108 {
109 self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
110 self->monsterinfo.run (self);
111 }
112 if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
113 M_ChangeYaw (self);
114 // PMM
115 // find out if we're going to be shooting
116 retval = ai_checkattack (self, 0);
117 // record sightings of player
118 if ((self->enemy) && (self->enemy->inuse) && (visible(self, self->enemy)))
119 {
120 self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
121 VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
122 VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target);
123 self->monsterinfo.trail_time = level.time;
124 self->monsterinfo.blind_fire_delay = 0;
125 }
126 // check retval to make sure we're not blindfiring
127 else if (!retval)
128 {
129 FindTarget (self);
130 return;
131 }
132 // ai_checkattack (self, 0);
133 // pmm
134 }
135 else
136 FindTarget (self);
137 return;
138 }
139
140 if (FindTarget (self))
141 return;
142
143 if (level.time > self->monsterinfo.pausetime)
144 {
145 self->monsterinfo.walk (self);
146 return;
147 }
148
149 if (!(self->spawnflags & 1) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time))
150 {
151 if (self->monsterinfo.idle_time)
152 {
153 self->monsterinfo.idle (self);
154 self->monsterinfo.idle_time = level.time + 15 + random() * 15;
155 }
156 else
157 {
158 self->monsterinfo.idle_time = level.time + random() * 15;
159 }
160 }
161 }
162
163
164 /*
165 =============
166 ai_walk
167
168 The monster is walking it's beat
169 =============
170 */
ai_walk(edict_t * self,float dist)171 void ai_walk (edict_t *self, float dist)
172 {
173 M_MoveToGoal (self, dist);
174
175 // check for noticing a player
176 if (FindTarget (self))
177 return;
178
179 if ((self->monsterinfo.search) && (level.time > self->monsterinfo.idle_time))
180 {
181 if (self->monsterinfo.idle_time)
182 {
183 self->monsterinfo.search (self);
184 self->monsterinfo.idle_time = level.time + 15 + random() * 15;
185 }
186 else
187 {
188 self->monsterinfo.idle_time = level.time + random() * 15;
189 }
190 }
191 }
192
193
194 /*
195 =============
196 ai_charge
197
198 Turns towards target and advances
199 Use this call with a distance of 0 to replace ai_face
200 ==============
201 */
ai_charge(edict_t * self,float dist)202 void ai_charge (edict_t *self, float dist)
203 {
204 vec3_t v;
205 // PMM
206 float ofs;
207 // PMM
208
209 // PMM - made AI_MANUAL_STEERING affect things differently here .. they turn, but
210 // don't set the ideal_yaw
211
212 // This is put in there so monsters won't move towards the origin after killing
213 // a tesla. This could be problematic, so keep an eye on it.
214 if(!self->enemy || !self->enemy->inuse) //PGM
215 return; //PGM
216
217 // PMM - save blindfire target
218 if (visible(self, self->enemy))
219 VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target);
220 // pmm
221
222 if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
223 {
224 VectorSubtract (self->enemy->s.origin, self->s.origin, v);
225 self->ideal_yaw = vectoyaw(v);
226 // gi.dprintf ("enemy = %s\n", vtos (self->enemy->s.origin));
227 // gi.dprintf ("enemy: ideal yaw is %f\n", self->ideal_yaw);
228 }
229 // if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
230 M_ChangeYaw (self);
231 // PMM
232 // if (dist)
233 // M_walkmove (self, self->s.angles[YAW], dist);
234
235 if (dist)
236 {
237 if (self->monsterinfo.aiflags & AI_CHARGING)
238 {
239 M_MoveToGoal (self, dist);
240 return;
241 }
242 // circle strafe support
243 if (self->monsterinfo.attack_state == AS_SLIDING)
244 {
245 // if we're fighting a tesla, NEVER circle strafe
246 if ((self->enemy) && (self->enemy->classname) && (!strcmp(self->enemy->classname, "tesla")))
247 ofs = 0;
248 else if (self->monsterinfo.lefty)
249 ofs = 90;
250 else
251 ofs = -90;
252
253 if (M_walkmove (self, self->ideal_yaw + ofs, dist))
254 return;
255
256 self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
257 M_walkmove (self, self->ideal_yaw - ofs, dist);
258 }
259 else
260 M_walkmove (self, self->s.angles[YAW], dist);
261 }
262 // PMM
263 }
264
265
266 /*
267 =============
268 ai_turn
269
270 don't move, but turn towards ideal_yaw
271 Distance is for slight position adjustments needed by the animations
272 =============
273 */
ai_turn(edict_t * self,float dist)274 void ai_turn (edict_t *self, float dist)
275 {
276 if (dist)
277 M_walkmove (self, self->s.angles[YAW], dist);
278
279 if (FindTarget (self))
280 return;
281
282 if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
283 M_ChangeYaw (self);
284 }
285
286
287 /*
288
289 .enemy
290 Will be world if not currently angry at anyone.
291
292 .movetarget
293 The next path spot to walk toward. If .enemy, ignore .movetarget.
294 When an enemy is killed, the monster will try to return to it's path.
295
296 .hunt_time
297 Set to time + something when the player is in sight, but movement straight for
298 him is blocked. This causes the monster to use wall following code for
299 movement direction instead of sighting on the player.
300
301 .ideal_yaw
302 A yaw angle of the intended direction, which will be turned towards at up
303 to 45 deg / state. If the enemy is in view and hunt_time is not active,
304 this will be the exact line towards the enemy.
305
306 .pausetime
307 A monster will leave it's stand state and head towards it's .movetarget when
308 time > .pausetime.
309
310 walkmove(angle, speed) primitive is all or nothing
311 */
312
313 /*
314 =============
315 range
316
317 returns the range catagorization of an entity reletive to self
318 0 melee range, will become hostile even if back is turned
319 1 visibility and infront, or visibility and show hostile
320 2 infront and show hostile
321 3 only triggered by damage
322 =============
323 */
range(edict_t * self,edict_t * other)324 int range (edict_t *self, edict_t *other)
325 {
326 vec3_t v;
327 float len;
328
329 VectorSubtract (self->s.origin, other->s.origin, v);
330 len = VectorLength (v);
331 if (len < MELEE_DISTANCE)
332 return RANGE_MELEE;
333 if (len < 500)
334 return RANGE_NEAR;
335 if (len < 1000)
336 return RANGE_MID;
337 return RANGE_FAR;
338 }
339
340 /*
341 =============
342 visible
343
344 returns 1 if the entity is visible to self, even if not infront ()
345 =============
346 */
visible(edict_t * self,edict_t * other)347 qboolean visible (edict_t *self, edict_t *other)
348 {
349 vec3_t spot1;
350 vec3_t spot2;
351 trace_t trace;
352
353 VectorCopy (self->s.origin, spot1);
354 spot1[2] += self->viewheight;
355 VectorCopy (other->s.origin, spot2);
356 spot2[2] += other->viewheight;
357 trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE);
358
359 if (trace.fraction == 1.0 || trace.ent == other) // PGM
360 return true;
361 return false;
362 }
363
364
365 /*
366 =============
367 infront
368
369 returns 1 if the entity is in front (in sight) of self
370 =============
371 */
infront(edict_t * self,edict_t * other)372 qboolean infront (edict_t *self, edict_t *other)
373 {
374 vec3_t vec;
375 float dot;
376 vec3_t forward;
377
378 AngleVectors (self->s.angles, forward, NULL, NULL);
379 VectorSubtract (other->s.origin, self->s.origin, vec);
380 VectorNormalize (vec);
381 dot = DotProduct (vec, forward);
382
383 if (dot > 0.3)
384 return true;
385 return false;
386 }
387
388
389 //============================================================================
390
HuntTarget(edict_t * self)391 void HuntTarget (edict_t *self)
392 {
393 vec3_t vec;
394
395 self->goalentity = self->enemy;
396 if (self->monsterinfo.aiflags & AI_STAND_GROUND)
397 self->monsterinfo.stand (self);
398 else
399 self->monsterinfo.run (self);
400 VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
401 self->ideal_yaw = vectoyaw(vec);
402 // wait a while before first attack
403 if (!(self->monsterinfo.aiflags & AI_STAND_GROUND))
404 AttackFinished (self, 1);
405 }
406
FoundTarget(edict_t * self)407 void FoundTarget (edict_t *self)
408 {
409 // let other monsters see this monster for a while
410 if (self->enemy->client)
411 {
412 if(self->enemy->flags & FL_DISGUISED)
413 {
414 // level.disguise_violator = self->enemy;
415 // level.disguise_violation_framenum = level.framenum + 5;
416 self->enemy->flags &= ~FL_DISGUISED;
417 }
418
419 level.sight_entity = self;
420 level.sight_entity_framenum = level.framenum;
421 level.sight_entity->light_level = 128;
422 }
423
424 self->show_hostile = level.time + 1; // wake up other monsters
425
426 VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
427 self->monsterinfo.trail_time = level.time;
428 // PMM
429 VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target);
430 self->monsterinfo.blind_fire_delay = 0;
431 // PMM
432
433 if (!self->combattarget)
434 {
435 HuntTarget (self);
436 return;
437 }
438
439 self->goalentity = self->movetarget = G_PickTarget(self->combattarget);
440 if (!self->movetarget)
441 {
442 self->goalentity = self->movetarget = self->enemy;
443 HuntTarget (self);
444 gi.dprintf("%s at %s, combattarget %s not found\n", self->classname, vtos(self->s.origin), self->combattarget);
445 return;
446 }
447
448 // clear out our combattarget, these are a one shot deal
449 self->combattarget = NULL;
450 self->monsterinfo.aiflags |= AI_COMBAT_POINT;
451
452 // clear the targetname, that point is ours!
453 self->movetarget->targetname = NULL;
454 self->monsterinfo.pausetime = 0;
455
456 // run for it
457 self->monsterinfo.run (self);
458 }
459
460
461 /*
462 ===========
463 FindTarget
464
465 Self is currently not attacking anything, so try to find a target
466
467 Returns TRUE if an enemy was sighted
468
469 When a player fires a missile, the point of impact becomes a fakeplayer so
470 that monsters that see the impact will respond as if they had seen the
471 player.
472
473 To avoid spending too much time, only a single client (or fakeclient) is
474 checked each frame. This means multi player games will have slightly
475 slower noticing monsters.
476 ============
477 */
FindTarget(edict_t * self)478 qboolean FindTarget (edict_t *self)
479 {
480 edict_t *client;
481 qboolean heardit;
482 int r;
483
484 if (self->monsterinfo.aiflags & AI_GOOD_GUY)
485 {
486 if (self->goalentity && self->goalentity->inuse && self->goalentity->classname)
487 {
488 if (strcmp(self->goalentity->classname, "target_actor") == 0)
489 return false;
490 }
491
492 //FIXME look for monsters?
493 return false;
494 }
495
496 // if we're going to a combat point, just proceed
497 if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
498 return false;
499
500 // if the first spawnflag bit is set, the monster will only wake up on
501 // really seeing the player, not another monster getting angry or hearing
502 // something
503
504 // revised behavior so they will wake up if they "see" a player make a noise
505 // but not weapon impact/explosion noises
506
507 heardit = false;
508 if ((level.sight_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) )
509 {
510 client = level.sight_entity;
511 if (client->enemy == self->enemy)
512 {
513 return false;
514 }
515 }
516 else if (level.disguise_violation_framenum > level.framenum)
517 {
518 client = level.disguise_violator;
519 }
520 else if (level.sound_entity_framenum >= (level.framenum - 1))
521 {
522 client = level.sound_entity;
523 heardit = true;
524 }
525 else if (!(self->enemy) && (level.sound2_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) )
526 {
527 client = level.sound2_entity;
528 heardit = true;
529 }
530 else
531 {
532 client = level.sight_client;
533 if (!client)
534 return false; // no clients to get mad at
535 }
536
537 // if the entity went away, forget it
538 if (!client->inuse)
539 return false;
540
541 if (client == self->enemy)
542 return true; // JDC false;
543
544 //PMM - hintpath coop fix
545 if ((self->monsterinfo.aiflags & AI_HINT_PATH) && (coop) && (coop->value))
546 {
547 // if ((heardit) && (g_showlogic) && (g_showlogic->value))
548 // gi.dprintf ("ignoring coop sound target\n");
549 heardit = false;
550 }
551 // pmm
552
553 if (client->client)
554 {
555 if (client->flags & FL_NOTARGET)
556 return false;
557 }
558 else if (client->svflags & SVF_MONSTER)
559 {
560 if (!client->enemy)
561 return false;
562 if (client->enemy->flags & FL_NOTARGET)
563 return false;
564 }
565 else if (heardit)
566 {
567 // pgm - a little more paranoia won't hurt....
568 if ((client->owner) && (client->owner->flags & FL_NOTARGET))
569 return false;
570 }
571 else
572 return false;
573
574 if (!heardit)
575 {
576 r = range (self, client);
577
578 if (r == RANGE_FAR)
579 return false;
580
581 // this is where we would check invisibility
582
583 // is client in an spot too dark to be seen?
584 if (client->light_level <= 5)
585 return false;
586
587 if (!visible (self, client))
588 {
589 return false;
590 }
591
592 if (r == RANGE_NEAR)
593 {
594 if (client->show_hostile < level.time && !infront (self, client))
595 {
596 return false;
597 }
598 }
599 else if (r == RANGE_MID)
600 {
601 if (!infront (self, client))
602 {
603 return false;
604 }
605 }
606
607 self->enemy = client;
608
609 if (strcmp(self->enemy->classname, "player_noise") != 0)
610 {
611 self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
612
613 if (!self->enemy->client)
614 {
615 self->enemy = self->enemy->enemy;
616 if (!self->enemy->client)
617 {
618 self->enemy = NULL;
619 return false;
620 }
621 }
622 }
623 }
624 else // heardit
625 {
626 vec3_t temp;
627
628 if (self->spawnflags & 1)
629 {
630 if (!visible (self, client))
631 return false;
632 }
633 else
634 {
635 if (!gi.inPHS(self->s.origin, client->s.origin))
636 return false;
637 }
638
639 VectorSubtract (client->s.origin, self->s.origin, temp);
640
641 if (VectorLength(temp) > 1000) // too far to hear
642 {
643 return false;
644 }
645
646 // check area portals - if they are different and not connected then we can't hear it
647 if (client->areanum != self->areanum)
648 if (!gi.AreasConnected(self->areanum, client->areanum))
649 return false;
650
651 self->ideal_yaw = vectoyaw(temp);
652 if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
653 M_ChangeYaw (self);
654
655 // hunt the sound for a bit; hopefully find the real player
656 self->monsterinfo.aiflags |= AI_SOUND_TARGET;
657 self->enemy = client;
658 }
659
660 //
661 // got one
662 //
663 // PMM - if we got an enemy, we need to bail out of hint paths, so take over here
664 if (self->monsterinfo.aiflags & AI_HINT_PATH)
665 {
666 // if(g_showlogic && g_showlogic->value)
667 // gi.dprintf("stopped following hint paths in FindTarget\n");
668
669 // this calls foundtarget for us
670 hintpath_stop (self);
671 }
672 else
673 {
674 FoundTarget (self);
675 }
676 // pmm
677 if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) && (self->monsterinfo.sight))
678 self->monsterinfo.sight (self, self->enemy);
679
680 return true;
681 }
682
683
684 //=============================================================================
685
686 /*
687 ============
688 FacingIdeal
689
690 ============
691 */
FacingIdeal(edict_t * self)692 qboolean FacingIdeal(edict_t *self)
693 {
694 float delta;
695
696 delta = anglemod(self->s.angles[YAW] - self->ideal_yaw);
697 if (delta > 45 && delta < 315)
698 return false;
699 return true;
700 }
701
702
703 //=============================================================================
704
M_CheckAttack(edict_t * self)705 qboolean M_CheckAttack (edict_t *self)
706 {
707 vec3_t spot1, spot2;
708 float chance;
709 trace_t tr;
710
711 if (self->enemy->health > 0)
712 {
713 // see if any entities are in the way of the shot
714 VectorCopy (self->s.origin, spot1);
715 spot1[2] += self->viewheight;
716 VectorCopy (self->enemy->s.origin, spot2);
717 spot2[2] += self->enemy->viewheight;
718
719 tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW);
720
721 // do we have a clear shot?
722 if (tr.ent != self->enemy)
723 {
724 // PGM - we want them to go ahead and shoot at info_notnulls if they can.
725 if(self->enemy->solid != SOLID_NOT || tr.fraction < 1.0) //PGM
726 {
727 // PMM - if we can't see our target, and we're not blocked by a monster, go into blind fire if available
728 if ((!(tr.ent->svflags & SVF_MONSTER)) && (!visible(self, self->enemy)))
729 {
730 if ((self->monsterinfo.blindfire) && (self->monsterinfo.blind_fire_delay <= 20.0))
731 {
732 if (level.time < self->monsterinfo.attack_finished)
733 {
734 return false;
735 }
736 if (level.time < (self->monsterinfo.trail_time + self->monsterinfo.blind_fire_delay))
737 {
738 // wait for our time
739 return false;
740 }
741 else
742 {
743 // gi.WriteByte (svc_temp_entity);
744 // gi.WriteByte (TE_DEBUGTRAIL);
745 // gi.WritePosition (spot1);
746 // gi.WritePosition (self->monsterinfo.blind_fire_target);
747 // gi.multicast (self->s.origin, MULTICAST_ALL);
748 // make sure we're not going to shoot a monster
749 tr = gi.trace (spot1, NULL, NULL, self->monsterinfo.blind_fire_target, self, CONTENTS_MONSTER);
750 if (tr.allsolid || tr.startsolid || ((tr.fraction < 1.0) && (tr.ent != self->enemy)))
751 {
752 // if ((g_showlogic) && (g_showlogic->value))
753 // gi.dprintf ("blindfire blocked\n");
754 return false;
755 }
756
757 self->monsterinfo.attack_state = AS_BLIND;
758 return true;
759 }
760 }
761 }
762 // pmm
763 return false;
764 }
765 }
766 }
767
768 // melee attack
769 if (enemy_range == RANGE_MELEE)
770 {
771 // don't always melee in easy mode
772 if (skill->value == 0 && (rand()&3) )
773 {
774 // PMM - fix for melee only monsters & strafing
775 self->monsterinfo.attack_state = AS_STRAIGHT;
776 return false;
777 }
778 if (self->monsterinfo.melee)
779 self->monsterinfo.attack_state = AS_MELEE;
780 else
781 self->monsterinfo.attack_state = AS_MISSILE;
782 return true;
783 }
784
785 // missile attack
786 if (!self->monsterinfo.attack)
787 {
788 // PMM - fix for melee only monsters & strafing
789 self->monsterinfo.attack_state = AS_STRAIGHT;
790 return false;
791 }
792
793 if (level.time < self->monsterinfo.attack_finished)
794 return false;
795
796 if (enemy_range == RANGE_FAR)
797 return false;
798
799 if (self->monsterinfo.aiflags & AI_STAND_GROUND)
800 {
801 chance = 0.4;
802 }
803 else if (enemy_range == RANGE_MELEE)
804 {
805 chance = 0.2;
806 }
807 else if (enemy_range == RANGE_NEAR)
808 {
809 chance = 0.1;
810 }
811 else if (enemy_range == RANGE_MID)
812 {
813 chance = 0.02;
814 }
815 else
816 {
817 return false;
818 }
819
820 if (skill->value == 0)
821 chance *= 0.5;
822 else if (skill->value >= 2)
823 chance *= 2;
824
825 // PGM - go ahead and shoot every time if it's a info_notnull
826 if ((random () < chance) || (self->enemy->solid == SOLID_NOT))
827 {
828 self->monsterinfo.attack_state = AS_MISSILE;
829 self->monsterinfo.attack_finished = level.time + 2*random();
830 return true;
831 }
832
833 // PMM -daedalus should strafe more .. this can be done here or in a customized
834 // check_attack code for the hover.
835 if (self->flags & FL_FLY)
836 {
837 // originally, just 0.3
838 float strafe_chance;
839 if (!(strcmp(self->classname, "monster_daedalus")))
840 strafe_chance = 0.8;
841 else
842 strafe_chance = 0.6;
843
844 // if enemy is tesla, never strafe
845 if ((self->enemy) && (self->enemy->classname) && (!strcmp(self->enemy->classname, "tesla")))
846 strafe_chance = 0;
847
848 if (random() < strafe_chance)
849 self->monsterinfo.attack_state = AS_SLIDING;
850 else
851 self->monsterinfo.attack_state = AS_STRAIGHT;
852 }
853 // do we want the monsters strafing?
854 #ifdef SLIDING_TROOPS
855 else
856 {
857 if (random() < 0.4)
858 self->monsterinfo.attack_state = AS_SLIDING;
859 else
860 self->monsterinfo.attack_state = AS_STRAIGHT;
861 }
862 #endif
863 //-PMM
864
865 return false;
866 }
867
868
869 /*
870 =============
871 ai_run_melee
872
873 Turn and close until within an angle to launch a melee attack
874 =============
875 */
ai_run_melee(edict_t * self)876 void ai_run_melee(edict_t *self)
877 {
878 self->ideal_yaw = enemy_yaw;
879 if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
880 M_ChangeYaw (self);
881
882 if (FacingIdeal(self))
883 {
884 self->monsterinfo.melee (self);
885 self->monsterinfo.attack_state = AS_STRAIGHT;
886 }
887 }
888
889
890 /*
891 =============
892 ai_run_missile
893
894 Turn in place until within an angle to launch a missile attack
895 =============
896 */
ai_run_missile(edict_t * self)897 void ai_run_missile(edict_t *self)
898 {
899 self->ideal_yaw = enemy_yaw;
900 if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
901 M_ChangeYaw (self);
902
903 if (FacingIdeal(self))
904 {
905 self->monsterinfo.attack (self);
906 // if (self->monsterinfo.attack_state == AS_MISSILE)
907 if ((self->monsterinfo.attack_state == AS_MISSILE) || (self->monsterinfo.attack_state == AS_BLIND))
908 self->monsterinfo.attack_state = AS_STRAIGHT;
909 // else if (self->monsterinfo.attack_state != AS_SLIDING)
910 // gi.dprintf ("ai_run_missile: Unexpected attack state %d !\n", self->monsterinfo.attack_state);
911 }
912 };
913
914
915 /*
916 =============
917 ai_run_slide
918
919 Strafe sideways, but stay at aproximately the same range
920 =============
921 */
ai_run_slide(edict_t * self,float distance)922 void ai_run_slide(edict_t *self, float distance)
923 {
924 float ofs;
925 float angle;
926
927 self->ideal_yaw = enemy_yaw;
928
929 // if (self->flags & FL_FLY)
930 // angle = 90;
931 // else
932 // angle = 45;
933
934 angle = 90;
935
936 if (self->monsterinfo.lefty)
937 ofs = angle;
938 else
939 ofs = -angle;
940 //
941 // if (!(self->flags & FL_FLY))
942 // {
943 // // non fliers should actually turn towards the direction their trying to run
944 // self->ideal_yaw += ofs;
945 // }
946 //
947 if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
948 M_ChangeYaw (self);
949
950 /*
951 if (!(self->flags & FL_FLY))
952 {
953 if (M_walkmove (self, self->ideal_yaw + ofs, distance))
954 return;
955 }
956 else
957 {
958 if (M_walkmove (self, self->ideal_yaw, distance))
959 return;
960 }
961 */
962 // PMM - clamp maximum sideways move for non flyers to make them look less jerky
963 if (!self->flags & FL_FLY)
964 distance = min (distance, MAX_SIDESTEP);
965 if (M_walkmove (self, self->ideal_yaw + ofs, distance))
966 return;
967 // PMM - if we're dodging, give up on it and go straight
968 if (self->monsterinfo.aiflags & AI_DODGING)
969 {
970 monster_done_dodge (self);
971 // by setting as_straight, caller will know to try straight move
972 self->monsterinfo.attack_state = AS_STRAIGHT;
973 return;
974 }
975
976 self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
977 if (M_walkmove (self, self->ideal_yaw - ofs, distance))
978 return;
979 // PMM - if we're dodging, give up on it and go straight
980 if (self->monsterinfo.aiflags & AI_DODGING)
981 monster_done_dodge (self);
982
983 // PMM - the move failed, so signal the caller (ai_run) to try going straight
984 self->monsterinfo.attack_state = AS_STRAIGHT;
985 /*
986 if (!(self->flags & FL_FLY))
987 {
988 M_walkmove (self, self->ideal_yaw + ofs, distance);
989 }
990 else
991 {
992 M_walkmove (self, self->ideal_yaw, distance);
993 }*/
994 }
995
996
997 /*
998 =============
999 ai_checkattack
1000
1001 Decides if we're going to attack or do something else
1002 used by ai_run and ai_stand
1003 =============
1004 */
ai_checkattack(edict_t * self,float dist)1005 qboolean ai_checkattack (edict_t *self, float dist)
1006 {
1007 vec3_t temp;
1008 qboolean hesDeadJim;
1009 // PMM
1010 qboolean retval;
1011
1012 // this causes monsters to run blindly to the combat point w/o firing
1013 if (self->goalentity)
1014 {
1015 if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
1016 return false;
1017
1018 if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
1019 {
1020 if ((level.time - self->enemy->teleport_time) > 5.0)
1021 {
1022 if (self->goalentity == self->enemy)
1023 if (self->movetarget)
1024 self->goalentity = self->movetarget;
1025 else
1026 self->goalentity = NULL;
1027 self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
1028 if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
1029 self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
1030 }
1031 else
1032 {
1033 self->show_hostile = level.time + 1;
1034 return false;
1035 }
1036 }
1037 }
1038
1039 enemy_vis = false;
1040
1041 // see if the enemy is dead
1042 hesDeadJim = false;
1043 if ((!self->enemy) || (!self->enemy->inuse))
1044 {
1045 hesDeadJim = true;
1046 }
1047 else if (self->monsterinfo.aiflags & AI_MEDIC)
1048 {
1049 if (!(self->enemy->inuse) || (self->enemy->health > 0))
1050 {
1051 hesDeadJim = true;
1052 // self->monsterinfo.aiflags &= ~AI_MEDIC;
1053 }
1054 }
1055 else
1056 {
1057 if (self->monsterinfo.aiflags & AI_BRUTAL)
1058 {
1059 if (self->enemy->health <= -80)
1060 hesDeadJim = true;
1061 }
1062 else
1063 {
1064 if (self->enemy->health <= 0)
1065 hesDeadJim = true;
1066 }
1067 }
1068
1069 if (hesDeadJim)
1070 {
1071 self->monsterinfo.aiflags &= ~AI_MEDIC;
1072 self->enemy = NULL;
1073 // FIXME: look all around for other targets
1074 if (self->oldenemy && self->oldenemy->health > 0)
1075 {
1076 self->enemy = self->oldenemy;
1077 self->oldenemy = NULL;
1078 HuntTarget (self);
1079 }
1080 //ROGUE - multiple teslas make monsters lose track of the player.
1081 else if(self->monsterinfo.last_player_enemy && self->monsterinfo.last_player_enemy->health > 0)
1082 {
1083 // if ((g_showlogic) && (g_showlogic->value))
1084 // gi.dprintf("resorting to last_player_enemy...\n");
1085 self->enemy = self->monsterinfo.last_player_enemy;
1086 self->oldenemy = NULL;
1087 self->monsterinfo.last_player_enemy = NULL;
1088 HuntTarget (self);
1089 }
1090 //ROGUE
1091 else
1092 {
1093 if (self->movetarget)
1094 {
1095 self->goalentity = self->movetarget;
1096 self->monsterinfo.walk (self);
1097 }
1098 else
1099 {
1100 // we need the pausetime otherwise the stand code
1101 // will just revert to walking with no target and
1102 // the monsters will wonder around aimlessly trying
1103 // to hunt the world entity
1104 self->monsterinfo.pausetime = level.time + 100000000;
1105 self->monsterinfo.stand (self);
1106 }
1107 return true;
1108 }
1109 }
1110
1111 self->show_hostile = level.time + 1; // wake up other monsters
1112
1113 // check knowledge of enemy
1114 enemy_vis = visible(self, self->enemy);
1115 if (enemy_vis)
1116 {
1117 self->monsterinfo.search_time = level.time + 5;
1118 VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
1119 // PMM
1120 self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
1121 self->monsterinfo.trail_time = level.time;
1122 VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target);
1123 self->monsterinfo.blind_fire_delay = 0;
1124 // pmm
1125 }
1126
1127 // look for other coop players here
1128 // if (coop && self->monsterinfo.search_time < level.time)
1129 // {
1130 // if (FindTarget (self))
1131 // return true;
1132 // }
1133
1134 enemy_infront = infront(self, self->enemy);
1135 enemy_range = range(self, self->enemy);
1136 VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
1137 enemy_yaw = vectoyaw(temp);
1138
1139
1140 // JDC self->ideal_yaw = enemy_yaw;
1141
1142 // PMM -- reordered so the monster specific checkattack is called before the run_missle/melee/checkvis
1143 // stuff .. this allows for, among other things, circle strafing and attacking while in ai_run
1144 retval = self->monsterinfo.checkattack (self);
1145 if (retval)
1146 {
1147 // PMM
1148 if (self->monsterinfo.attack_state == AS_MISSILE)
1149 {
1150 ai_run_missile (self);
1151 return true;
1152 }
1153 if (self->monsterinfo.attack_state == AS_MELEE)
1154 {
1155 ai_run_melee (self);
1156 return true;
1157 }
1158 // PMM -- added so monsters can shoot blind
1159 if (self->monsterinfo.attack_state == AS_BLIND)
1160 {
1161 ai_run_missile (self);
1162 return true;
1163 }
1164 // pmm
1165
1166 // if enemy is not currently visible, we will never attack
1167 if (!enemy_vis)
1168 return false;
1169 // PMM
1170 }
1171 return retval;
1172 // PMM
1173 // return self->monsterinfo.checkattack (self);
1174 }
1175
1176
1177 /*
1178 =============
1179 ai_run
1180
1181 The monster has an enemy it is trying to kill
1182 =============
1183 */
ai_run(edict_t * self,float dist)1184 void ai_run (edict_t *self, float dist)
1185 {
1186 vec3_t v;
1187 edict_t *tempgoal;
1188 edict_t *save;
1189 qboolean new;
1190 edict_t *marker;
1191 float d1, d2;
1192 trace_t tr;
1193 vec3_t v_forward, v_right;
1194 float left, center, right;
1195 vec3_t left_target, right_target;
1196 //PMM
1197 qboolean retval;
1198 qboolean alreadyMoved = false;
1199 qboolean gotcha = false;
1200 edict_t *realEnemy;
1201
1202 // if we're going to a combat point, just proceed
1203 if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
1204 {
1205 M_MoveToGoal (self, dist);
1206 return;
1207 }
1208
1209 // PMM
1210 if (self->monsterinfo.aiflags & AI_DUCKED)
1211 {
1212 // if ((g_showlogic) && (g_showlogic->value))
1213 // gi.dprintf ("%s - duck flag cleaned up!\n", self->classname);
1214 self->monsterinfo.aiflags &= ~AI_DUCKED;
1215 }
1216 if (self->maxs[2] != self->monsterinfo.base_height)
1217 {
1218 // if ((g_showlogic) && (g_showlogic->value))
1219 // gi.dprintf ("%s - ducked height corrected!\n", self->classname);
1220 monster_duck_up (self);
1221 }
1222 // if ((self->monsterinfo.aiflags & AI_MANUAL_STEERING) && (strcmp(self->classname, "monster_turret")))
1223 // {
1224 // if ((g_showlogic) && (g_showlogic->value))
1225 // gi.dprintf ("%s - manual steering in ai_run!\n", self->classname);
1226 // }
1227 // pmm
1228
1229 //==========
1230 //PGM
1231 // if we're currently looking for a hint path
1232 if (self->monsterinfo.aiflags & AI_HINT_PATH)
1233 {
1234 // determine direction to our destination hintpath.
1235 // FIXME - is this needed EVERY time? I was having trouble with them
1236 // sometimes not going after it, and this fixed it.
1237 // VectorSubtract(self->movetarget->s.origin, self->s.origin, v);
1238 // vectoangles(v, v_forward);
1239 // self->ideal_yaw = v_forward[YAW];
1240 // gi.dprintf("seeking hintpath. origin: %s %0.1f\n", vtos(v), self->ideal_yaw);
1241 M_MoveToGoal (self, dist);
1242 if(!self->inuse)
1243 return; // PGM - g_touchtrigger free problem
1244 // return;
1245
1246 // if we've already seen the player, and can't see him now, return
1247 // if(self->enemy && !visible(self, self->enemy))
1248 // return;
1249
1250 // if not and can't find the player, return
1251 // if(!FindTarget(self))
1252 // return;
1253
1254 // first off, make sure we're looking for the player, not a noise he made
1255 if (self->enemy)
1256 {
1257 if (self->enemy->inuse)
1258 {
1259 if (strcmp(self->enemy->classname, "player_noise") != 0)
1260 realEnemy = self->enemy;
1261 else if (self->enemy->owner)
1262 realEnemy = self->enemy->owner;
1263 else // uh oh, can't figure out enemy, bail
1264 {
1265 self->enemy = NULL;
1266 hintpath_stop (self);
1267 return;
1268 }
1269 }
1270 else
1271 {
1272 self->enemy = NULL;
1273 hintpath_stop (self);
1274 return;
1275 }
1276 }
1277 else
1278 {
1279 hintpath_stop (self);
1280 return;
1281 }
1282
1283 if (coop && coop->value)
1284 {
1285 // if we're in coop, check my real enemy first .. if I SEE him, set gotcha to true
1286 if (self->enemy && visible(self, realEnemy))
1287 gotcha = true;
1288 else // otherwise, let FindTarget bump us out of hint paths, if appropriate
1289 FindTarget(self);
1290 }
1291 else
1292 {
1293 if(self->enemy && visible(self, realEnemy))
1294 gotcha = true;
1295 }
1296
1297 // if we see the player, stop following hintpaths.
1298 if (gotcha)
1299 {
1300 // if(g_showlogic && g_showlogic->value)
1301 // gi.dprintf("stopped following hint paths in ai_run\n");
1302
1303 // disconnect from hintpaths and start looking normally for players.
1304 hintpath_stop (self);
1305 // pmm - no longer needed, since hintpath_stop does it
1306 // HuntTarget(self);
1307 }
1308 return;
1309 }
1310 //PGM
1311 //==========
1312
1313 if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
1314 {
1315 // PMM - paranoia checking
1316 if (self->enemy)
1317 VectorSubtract (self->s.origin, self->enemy->s.origin, v);
1318
1319 if ((!self->enemy) || (VectorLength(v) < 64))
1320 // pmm
1321 {
1322 self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
1323 self->monsterinfo.stand (self);
1324 return;
1325 }
1326
1327 M_MoveToGoal (self, dist);
1328 // PMM - prevent double moves for sound_targets
1329 alreadyMoved = true;
1330 // pmm
1331 if(!self->inuse)
1332 return; // PGM - g_touchtrigger free problem
1333
1334 if (!FindTarget (self))
1335 return;
1336 }
1337
1338 // PMM -- moved ai_checkattack up here so the monsters can attack while strafing or charging
1339
1340 // PMM -- if we're dodging, make sure to keep the attack_state AS_SLIDING
1341
1342 retval = ai_checkattack (self, dist);
1343
1344 // PMM - don't strafe if we can't see our enemy
1345 if ((!enemy_vis) && (self->monsterinfo.attack_state == AS_SLIDING))
1346 self->monsterinfo.attack_state = AS_STRAIGHT;
1347 // unless we're dodging (dodging out of view looks smart)
1348 if (self->monsterinfo.aiflags & AI_DODGING)
1349 self->monsterinfo.attack_state = AS_SLIDING;
1350 // pmm
1351
1352 if (self->monsterinfo.attack_state == AS_SLIDING)
1353 {
1354 // PMM - protect against double moves
1355 if (!alreadyMoved)
1356 ai_run_slide (self, dist);
1357 // PMM
1358 // we're using attack_state as the return value out of ai_run_slide to indicate whether or not the
1359 // move succeeded. If the move succeeded, and we're still sliding, we're done in here (since we've
1360 // had our chance to shoot in ai_checkattack, and have moved).
1361 // if the move failed, our state is as_straight, and it will be taken care of below
1362 if ((!retval) && (self->monsterinfo.attack_state == AS_SLIDING))
1363 return;
1364 }
1365 else if (self->monsterinfo.aiflags & AI_CHARGING)
1366 {
1367 self->ideal_yaw = enemy_yaw;
1368 if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
1369 M_ChangeYaw (self);
1370 }
1371 if (retval)
1372 {
1373 // PMM - is this useful? Monsters attacking usually call the ai_charge routine..
1374 // the only monster this affects should be the soldier
1375 if ((dist != 0) && (!alreadyMoved) && (self->monsterinfo.attack_state == AS_STRAIGHT) && (!(self->monsterinfo.aiflags & AI_STAND_GROUND)))
1376 {
1377 M_MoveToGoal (self, dist);
1378 }
1379 if ((self->enemy) && (self->enemy->inuse) && (enemy_vis))
1380 {
1381 self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
1382 VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
1383 self->monsterinfo.trail_time = level.time;
1384 //PMM
1385 VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target);
1386 self->monsterinfo.blind_fire_delay = 0;
1387 //pmm
1388 }
1389 return;
1390 }
1391 //PMM
1392 // if (ai_checkattack (self, dist))
1393 // return;
1394
1395 // if (self->monsterinfo.attack_state == AS_SLIDING)
1396 // {
1397 // ai_run_slide (self, dist);
1398 // return;
1399 // }
1400
1401 // PGM - added a little paranoia checking here... 9/22/98
1402 if ((self->enemy) && (self->enemy->inuse) && (enemy_vis))
1403 {
1404 // if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
1405 // gi.dprintf("regained sight\n");
1406 // PMM - check for alreadyMoved
1407 if (!alreadyMoved)
1408 M_MoveToGoal (self, dist);
1409 if(!self->inuse)
1410 return; // PGM - g_touchtrigger free problem
1411
1412 self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
1413 VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
1414 self->monsterinfo.trail_time = level.time;
1415 // PMM
1416 VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target);
1417 self->monsterinfo.blind_fire_delay = 0;
1418 // pmm
1419 return;
1420 }
1421
1422 //=======
1423 //PGM
1424 // if we've been looking (unsuccessfully) for the player for 10 seconds
1425 // PMM - reduced to 5, makes them much nastier
1426 if((self->monsterinfo.trail_time + 5) <= level.time)
1427 {
1428 // and we haven't checked for valid hint paths in the last 10 seconds
1429 if((self->monsterinfo.last_hint_time + 10) <= level.time)
1430 {
1431 // check for hint_paths.
1432 self->monsterinfo.last_hint_time = level.time;
1433 if(monsterlost_checkhint(self))
1434 return;
1435 }
1436 }
1437 //PGM
1438 //=======
1439
1440 // PMM - moved down here to allow monsters to get on hint paths
1441 // coop will change to another enemy if visible
1442 if (coop->value)
1443 { // FIXME: insane guys get mad with this, which causes crashes!
1444 if (FindTarget (self))
1445 return;
1446 }
1447 // pmm
1448
1449 if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20)))
1450 {
1451 // PMM - double move protection
1452 if (!alreadyMoved)
1453 M_MoveToGoal (self, dist);
1454 self->monsterinfo.search_time = 0;
1455 // gi.dprintf("search timeout\n");
1456 return;
1457 }
1458
1459 save = self->goalentity;
1460 tempgoal = G_Spawn();
1461 self->goalentity = tempgoal;
1462
1463 new = false;
1464
1465 if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT))
1466 {
1467 // just lost sight of the player, decide where to go first
1468 // gi.dprintf("lost sight of player, last seen at %s\n", vtos(self->monsterinfo.last_sighting));
1469 self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN);
1470 self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP);
1471 new = true;
1472 }
1473
1474 if (self->monsterinfo.aiflags & AI_PURSUE_NEXT)
1475 {
1476 // vec3_t debug_vec;
1477
1478 self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT;
1479 // VectorSubtract(self->monsterinfo.last_sighting, self->s.origin, debug_vec);
1480 // gi.dprintf("reached current goal: %s %s %f", vtos(self->s.origin), vtos(self->monsterinfo.last_sighting), VectorLength(debug_vec));
1481
1482 // give ourself more time since we got this far
1483 self->monsterinfo.search_time = level.time + 5;
1484
1485 if (self->monsterinfo.aiflags & AI_PURSUE_TEMP)
1486 {
1487 // gi.dprintf("was temp goal; retrying original\n");
1488 self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP;
1489 marker = NULL;
1490 VectorCopy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting);
1491 new = true;
1492 }
1493 else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN)
1494 {
1495 self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN;
1496 marker = PlayerTrail_PickFirst (self);
1497 }
1498 else
1499 {
1500 marker = PlayerTrail_PickNext (self);
1501 }
1502
1503 if (marker)
1504 {
1505 VectorCopy (marker->s.origin, self->monsterinfo.last_sighting);
1506 self->monsterinfo.trail_time = marker->timestamp;
1507 self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW];
1508 // gi.dprintf("heading is %0.1f\n", self->ideal_yaw);
1509
1510 // debug_drawline(self.origin, self.last_sighting, 52);
1511 new = true;
1512 }
1513 }
1514
1515 VectorSubtract (self->s.origin, self->monsterinfo.last_sighting, v);
1516 d1 = VectorLength(v);
1517 if (d1 <= dist)
1518 {
1519 self->monsterinfo.aiflags |= AI_PURSUE_NEXT;
1520 dist = d1;
1521 }
1522
1523 VectorCopy (self->monsterinfo.last_sighting, self->goalentity->s.origin);
1524
1525 if (new)
1526 {
1527 // gi.dprintf("checking for course correction\n");
1528
1529 tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID);
1530 if (tr.fraction < 1)
1531 {
1532 VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1533 d1 = VectorLength(v);
1534 center = tr.fraction;
1535 d2 = d1 * ((center+1)/2);
1536 self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1537 AngleVectors(self->s.angles, v_forward, v_right, NULL);
1538
1539 VectorSet(v, d2, -16, 0);
1540 G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
1541 tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID);
1542 left = tr.fraction;
1543
1544 VectorSet(v, d2, 16, 0);
1545 G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
1546 tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID);
1547 right = tr.fraction;
1548
1549 center = (d1*center)/d2;
1550 if (left >= center && left > right)
1551 {
1552 if (left < 1)
1553 {
1554 VectorSet(v, d2 * left * 0.5, -16, 0);
1555 G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
1556 // gi.dprintf("incomplete path, go part way and adjust again\n");
1557 }
1558 VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
1559 self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
1560 VectorCopy (left_target, self->goalentity->s.origin);
1561 VectorCopy (left_target, self->monsterinfo.last_sighting);
1562 VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1563 self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1564 // gi.dprintf("adjusted left\n");
1565 // debug_drawline(self.origin, self.last_sighting, 152);
1566 }
1567 else if (right >= center && right > left)
1568 {
1569 if (right < 1)
1570 {
1571 VectorSet(v, d2 * right * 0.5, 16, 0);
1572 G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
1573 // gi.dprintf("incomplete path, go part way and adjust again\n");
1574 }
1575 VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
1576 self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
1577 VectorCopy (right_target, self->goalentity->s.origin);
1578 VectorCopy (right_target, self->monsterinfo.last_sighting);
1579 VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1580 self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1581 // gi.dprintf("adjusted right\n");
1582 // debug_drawline(self.origin, self.last_sighting, 152);
1583 }
1584 }
1585 // else gi.dprintf("course was fine\n");
1586 }
1587
1588 M_MoveToGoal (self, dist);
1589 if(!self->inuse)
1590 return; // PGM - g_touchtrigger free problem
1591
1592 G_FreeEdict(tempgoal);
1593
1594 if (self)
1595 self->goalentity = save;
1596 }
1597