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