vkQuake2 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  self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
792  if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
793  self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
794  }
795  else
796  {
797  self->show_hostile = level.time + 1;
798  return false;
799  }
800  }
801  }
802 
803  enemy_vis = false;
804 
805 // see if the enemy is dead
806  hesDeadJim = false;
807  if ((!self->enemy) || (!self->enemy->inuse))
808  {
809  hesDeadJim = true;
810  }
811  else if (self->monsterinfo.aiflags & AI_MEDIC)
812  {
813  if (self->enemy->health > 0)
814  {
815  hesDeadJim = true;
816  self->monsterinfo.aiflags &= ~AI_MEDIC;
817  }
818  }
819  else
820  {
821  if (self->monsterinfo.aiflags & AI_BRUTAL)
822  {
823  if (self->enemy->health <= -80)
824  hesDeadJim = true;
825  }
826  else
827  {
828  if (self->enemy->health <= 0)
829  hesDeadJim = true;
830  }
831  }
832 
833  if (hesDeadJim)
834  {
835  self->enemy = NULL;
836  // FIXME: look all around for other targets
837  if (self->oldenemy && self->oldenemy->health > 0)
838  {
839  self->enemy = self->oldenemy;
840  self->oldenemy = NULL;
841  HuntTarget (self);
842  }
843  else
844  {
845  if (self->movetarget)
846  {
847  self->goalentity = self->movetarget;
848  self->monsterinfo.walk (self);
849  }
850  else
851  {
852  // we need the pausetime otherwise the stand code
853  // will just revert to walking with no target and
854  // the monsters will wonder around aimlessly trying
855  // to hunt the world entity
856  self->monsterinfo.pausetime = level.time + 100000000;
857  self->monsterinfo.stand (self);
858  }
859  return true;
860  }
861  }
862 
863  self->show_hostile = level.time + 1; // wake up other monsters
864 
865 // check knowledge of enemy
866  enemy_vis = visible(self, self->enemy);
867  if (enemy_vis)
868  {
869  self->monsterinfo.search_time = level.time + 5;
870  VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
871  }
872 
873 // look for other coop players here
874 // if (coop && self->monsterinfo.search_time < level.time)
875 // {
876 // if (FindTarget (self))
877 // return true;
878 // }
879 
880  enemy_infront = infront(self, self->enemy);
881  enemy_range = range(self, self->enemy);
882  VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
883  enemy_yaw = vectoyaw(temp);
884 
885 
886  // JDC self->ideal_yaw = enemy_yaw;
887 
888  if (self->monsterinfo.attack_state == AS_MISSILE)
889  {
890  ai_run_missile (self);
891  return true;
892  }
893  if (self->monsterinfo.attack_state == AS_MELEE)
894  {
895  ai_run_melee (self);
896  return true;
897  }
898 
899  // if enemy is not currently visible, we will never attack
900  if (!enemy_vis)
901  return false;
902 
903  return self->monsterinfo.checkattack (self);
904 }
905 
906 
907 /*
908 =============
909 ai_run
910 
911 The monster has an enemy it is trying to kill
912 =============
913 */
914 void ai_run (edict_t *self, float dist)
915 {
916  vec3_t v;
917  edict_t *tempgoal;
918  edict_t *save;
919  qboolean new;
920  edict_t *marker;
921  float d1, d2;
922  trace_t tr;
923  vec3_t v_forward, v_right;
924  float left, center, right;
925  vec3_t left_target, right_target;
926 
927  // if we're going to a combat point, just proceed
928  if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
929  {
930  M_MoveToGoal (self, dist);
931  return;
932  }
933 
934  if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
935  {
936  VectorSubtract (self->s.origin, self->enemy->s.origin, v);
937  if (VectorLength(v) < 64)
938  {
939  self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
940  self->monsterinfo.stand (self);
941  return;
942  }
943 
944  M_MoveToGoal (self, dist);
945 
946  if (!FindTarget (self))
947  return;
948  }
949 
950  if (ai_checkattack (self, dist))
951  return;
952 
953  if (self->monsterinfo.attack_state == AS_SLIDING)
954  {
955  ai_run_slide (self, dist);
956  return;
957  }
958 
959  if (enemy_vis)
960  {
961 // if (self.aiflags & AI_LOST_SIGHT)
962 // dprint("regained sight\n");
963  M_MoveToGoal (self, dist);
964  self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
965  VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
966  self->monsterinfo.trail_time = level.time;
967  return;
968  }
969 
970  // coop will change to another enemy if visible
971  if (coop->value)
972  { // FIXME: insane guys get mad with this, which causes crashes!
973  if (FindTarget (self))
974  return;
975  }
976 
977  if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20)))
978  {
979  M_MoveToGoal (self, dist);
980  self->monsterinfo.search_time = 0;
981 // dprint("search timeout\n");
982  return;
983  }
984 
985  save = self->goalentity;
986  tempgoal = G_Spawn();
987  self->goalentity = tempgoal;
988 
989  new = false;
990 
991  if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT))
992  {
993  // just lost sight of the player, decide where to go first
994 // dprint("lost sight of player, last seen at "); dprint(vtos(self.last_sighting)); dprint("\n");
996  self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP);
997  new = true;
998  }
999 
1000  if (self->monsterinfo.aiflags & AI_PURSUE_NEXT)
1001  {
1002  self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT;
1003 // 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");
1004 
1005  // give ourself more time since we got this far
1006  self->monsterinfo.search_time = level.time + 5;
1007 
1008  if (self->monsterinfo.aiflags & AI_PURSUE_TEMP)
1009  {
1010 // dprint("was temp goal; retrying original\n");
1011  self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP;
1012  marker = NULL;
1013  VectorCopy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting);
1014  new = true;
1015  }
1016  else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN)
1017  {
1018  self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN;
1019  marker = PlayerTrail_PickFirst (self);
1020  }
1021  else
1022  {
1023  marker = PlayerTrail_PickNext (self);
1024  }
1025 
1026  if (marker)
1027  {
1028  VectorCopy (marker->s.origin, self->monsterinfo.last_sighting);
1029  self->monsterinfo.trail_time = marker->timestamp;
1030  self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW];
1031 // dprint("heading is "); dprint(ftos(self.ideal_yaw)); dprint("\n");
1032 
1033 // debug_drawline(self.origin, self.last_sighting, 52);
1034  new = true;
1035  }
1036  }
1037 
1038  VectorSubtract (self->s.origin, self->monsterinfo.last_sighting, v);
1039  d1 = VectorLength(v);
1040  if (d1 <= dist)
1041  {
1042  self->monsterinfo.aiflags |= AI_PURSUE_NEXT;
1043  dist = d1;
1044  }
1045 
1046  VectorCopy (self->monsterinfo.last_sighting, self->goalentity->s.origin);
1047 
1048  if (new)
1049  {
1050 // gi.dprintf("checking for course correction\n");
1051 
1052  tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID);
1053  if (tr.fraction < 1)
1054  {
1055  VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1056  d1 = VectorLength(v);
1057  center = tr.fraction;
1058  d2 = d1 * ((center+1)/2);
1059  self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1060  AngleVectors(self->s.angles, v_forward, v_right, NULL);
1061 
1062  VectorSet(v, d2, -16, 0);
1063  G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
1064  tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID);
1065  left = tr.fraction;
1066 
1067  VectorSet(v, d2, 16, 0);
1068  G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
1069  tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID);
1070  right = tr.fraction;
1071 
1072  center = (d1*center)/d2;
1073  if (left >= center && left > right)
1074  {
1075  if (left < 1)
1076  {
1077  VectorSet(v, d2 * left * 0.5, -16, 0);
1078  G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
1079 // gi.dprintf("incomplete path, go part way and adjust again\n");
1080  }
1081  VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
1082  self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
1083  VectorCopy (left_target, self->goalentity->s.origin);
1084  VectorCopy (left_target, self->monsterinfo.last_sighting);
1085  VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1086  self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1087 // gi.dprintf("adjusted left\n");
1088 // debug_drawline(self.origin, self.last_sighting, 152);
1089  }
1090  else if (right >= center && right > left)
1091  {
1092  if (right < 1)
1093  {
1094  VectorSet(v, d2 * right * 0.5, 16, 0);
1095  G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
1096 // gi.dprintf("incomplete path, go part way and adjust again\n");
1097  }
1098  VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
1099  self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
1100  VectorCopy (right_target, self->goalentity->s.origin);
1101  VectorCopy (right_target, self->monsterinfo.last_sighting);
1102  VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1103  self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1104 // gi.dprintf("adjusted right\n");
1105 // debug_drawline(self.origin, self.last_sighting, 152);
1106  }
1107  }
1108 // else gi.dprintf("course was fine\n");
1109  }
1110 
1111  M_MoveToGoal (self, dist);
1112 
1113  G_FreeEdict(tempgoal);
1114 
1115  if (self)
1116  self->goalentity = save;
1117 }
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:970
RANGE_NEAR
#define RANGE_NEAR
Definition: g_local.h:120
edict_s::timestamp
float timestamp
Definition: g_local.h:1014
CONTENTS_MONSTER
#define CONTENTS_MONSTER
Definition: qfiles.h:359
AI_MEDIC
#define AI_MEDIC
Definition: g_local.h:142
YAW
#define YAW
Definition: q_shared.h:73
AI_COMBAT_POINT
#define AI_COMBAT_POINT
Definition: g_local.h:141
AI_BRUTAL
#define AI_BRUTAL
Definition: g_local.h:138
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
TRUE
#define TRUE
Definition: stb_vorbis.c:617
set
set(ROOT_NAMESPACE game) set(Header_Files "g_local.h" "game.h" "m_actor.h" "m_berserk.h" "m_boss2.h" "m_boss31.h" "m_boss32.h" "m_brain.h" "m_chick.h" "m_flipper.h" "m_float.h" "m_flyer.h" "m_gladiator.h" "m_gunner.h" "m_hover.h" "m_infantry.h" "m_insane.h" "m_medic.h" "m_mutant.h" "m_parasite.h" "m_player.h" "m_rider.h" "m_soldier.h" "m_supertank.h" "m_tank.h" "q_shared.h") source_group("Header Files" FILES $
Definition: CMakeLists.txt:1
G_Spawn
edict_t * G_Spawn(void)
Definition: g_utils.c:420
trace_t::fraction
float fraction
Definition: q_shared.h:457
MELEE_DISTANCE
#define MELEE_DISTANCE
Definition: g_local.h:82
VectorSubtract
#define VectorSubtract(a, b, c)
Definition: q_shared.h:163
AI_TEMP_STAND_GROUND
#define AI_TEMP_STAND_GROUND
Definition: g_local.h:130
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:137
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:1149
AI_LOST_SIGHT
#define AI_LOST_SIGHT
Definition: g_local.h:132
AI_STAND_GROUND
#define AI_STAND_GROUND
Definition: g_local.h:129
qboolean
qboolean
Definition: q_shared.h:63
edict_s::inuse
qboolean inuse
Definition: g_local.h:976
trace_t
Definition: q_shared.h:453
edict_s::client
struct gclient_s * client
Definition: g_local.h:971
C
#define C(label)
Definition: asm_i386.h:8
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:135
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:324
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:985
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:1061
edict_s::svflags
int svflags
Definition: g_local.h:989
edict_s
Definition: g_local.h:968
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:147
edict_s::viewheight
int viewheight
Definition: g_local.h:1067
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:994
AS_MISSILE
#define AS_MISSILE
Definition: g_local.h:149
project
project(ref_soft C) set(Header_Files "../client/ref.h" "../game/q_shared.h" "../qcommon/qcommon.h" "../qcommon/qfiles.h" "../win32/rw_win.h" "../win32/winquake.h" "adivtab.h" "anorms.h" "r_dither.h" "r_local.h" "r_model.h" "rand1k.h") source_group("Header Files" FILES $
Definition: CMakeLists.txt:1
DotProduct
#define DotProduct(x, y)
Definition: q_shared.h:162
cvar_s::value
float value
Definition: q_shared.h:331
vtos
char * vtos(vec3_t v)
Definition: g_utils.c:293
AI_PURSUIT_LAST_SEEN
#define AI_PURSUIT_LAST_SEEN
Definition: g_local.h:133
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:515
FL_NOTARGET
#define FL_NOTARGET
Definition: g_local.h:64
VectorNormalize
vec_t VectorNormalize(vec3_t v)
Definition: q_shared.c:681
NULL
#define NULL
Definition: q_shared.h:67
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:421
edict_s::goalentity
edict_t * goalentity
Definition: g_local.h:1037
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:400
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:1106
PlayerTrail_PickFirst
edict_t * PlayerTrail_PickFirst(edict_t *self)
Definition: p_trail.c:95
VectorCopy
#define VectorCopy(a, b)
Definition: q_shared.h:165
coop
cvar_t * coop
Definition: g_main.c:36
AI_SOUND_TARGET
#define AI_SOUND_TARGET
Definition: g_local.h:131
edict_s::monsterinfo
monsterinfo_t monsterinfo
Definition: g_local.h:1114
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:119
level
GLint level
Definition: qgl_win.c:116
ai_run
void ai_run(edict_t *self, float dist)
Definition: g_ai.c:914
edict_s::flags
int flags
Definition: g_local.h:1002
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:121
trace_t::ent
struct edict_s * ent
Definition: q_shared.h:462
FL_FLY
#define FL_FLY
Definition: g_local.h:59
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:134
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:168
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:1076
MASK_PLAYERSOLID
#define MASK_PLAYERSOLID
Definition: q_shared.h:396
entity_state_s::angles
vec3_t angles
Definition: q_shared.h:1150
AS_MELEE
#define AS_MELEE
Definition: g_local.h:148
RANGE_FAR
#define RANGE_FAR
Definition: g_local.h:122
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:134
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:146
edict_s::health
int health
Definition: g_local.h:1057
game_locals_t::maxclients
int maxclients
Definition: g_local.h:286