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