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