File: game\g_ai.c

    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 
   24 qboolean FindTarget (edict_t *self);
   25 extern cvar_t   *maxclients;
   26 
   27 qboolean ai_checkattack (edict_t *self, float dist);
   28 
   29 qboolean        enemy_vis;
   30 qboolean        enemy_infront;
   31 int                     enemy_range;
   32 float           enemy_yaw;
   33 
   34 //============================================================================
   35 
   36 
   37 /*
   38 =================
   39 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 */
  287 qboolean visible (edict_t *self, edict_t *other)
  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 */
  312 qboolean infront (edict_t *self, edict_t *other)
  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 */
  407 qboolean FindTarget (edict_t *self)
  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                 {
  526                         self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
  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 */
  594 qboolean FacingIdeal(edict_t *self)
  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 
  607 qboolean M_CheckAttack (edict_t *self)
  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 
  621                 tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW);
  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 */
  703 void ai_run_melee(edict_t *self)
  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 */
  723 void ai_run_missile(edict_t *self)
  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");
  995                 self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN);
  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 }
 1118