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