/* Emacs style mode select   -*- C++ -*-
 *-----------------------------------------------------------------------------
 *
 *
 *  PrBoom a Doom port merged with LxDoom and LSDLDoom
 *  based on BOOM, a modified and improved DOOM engine
 *  Copyright (C) 1999 by
 *  id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman
 *  Copyright (C) 1999-2000 by
 *  Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 *  02111-1307, USA.
 *
 * DESCRIPTION:
 *      Enemy thinking, AI.
 *      Action Pointer Functions
 *      that are associated with states/frames.
 *
 *-----------------------------------------------------------------------------*/

#include "doomstat.h"
#include "m_random.h"
#include "r_main.h"
#include "p_maputl.h"
#include "p_map.h"
#include "p_setup.h"
#include "p_spec.h"
#include "s_sound.h"
#include "sounds.h"
#include "p_inter.h"
#include "g_game.h"
#include "p_enemy.h"
#include "p_tick.h"
#include "m_bbox.h"

#include "rockmacros.h"

static mobj_t *current_actor;

enum {
   DI_EAST,
   DI_NORTHEAST,
   DI_NORTH,
   DI_NORTHWEST,
   DI_WEST,
   DI_SOUTHWEST,
   DI_SOUTH,
   DI_SOUTHEAST,
   DI_NODIR,
   NUMDIRS
};
typedef unsigned dirtype_t;

void A_Fall(mobj_t *actor);
void A_FaceTarget(mobj_t *actor);
static void P_NewChaseDir(mobj_t *actor);
void P_ZBumpCheck(mobj_t *);                                        // phares

//
// ENEMY THINKING
// Enemies are allways spawned
// with targetplayer = -1, threshold = 0
// Most monsters are spawned unaware of all players,
// but some can be made preaware
//

//
// Called by P_NoiseAlert.
// Recursively traverse adjacent sectors,
// sound blocking lines cut off traversal.
//
// killough 5/5/98: reformatted, cleaned up

static void P_RecursiveSound(sector_t *sec, int soundblocks,
                             mobj_t *soundtarget)
{
   int i;

   // wake up all monsters in this sector
   if (sec->validcount == validcount && sec->soundtraversed <= soundblocks+1)
      return;             // already flooded

   sec->validcount = validcount;
   sec->soundtraversed = soundblocks+1;
   P_SetTarget(&sec->soundtarget, soundtarget);

   for (i=0; i<sec->linecount; i++)
   {
      sector_t *other;
      line_t *check = sec->lines[i];

      if (!(check->flags & ML_TWOSIDED))
         continue;

      P_LineOpening(check);

      if (openrange <= 0)
         continue;       // closed door

      other=sides[check->sidenum[sides[check->sidenum[0]].sector==sec]].sector;

      if (!(check->flags & ML_SOUNDBLOCK))
         P_RecursiveSound(other, soundblocks, soundtarget);
      else
         if (!soundblocks)
            P_RecursiveSound(other, 1, soundtarget);
   }
}

//
// P_NoiseAlert
// If a monster yells at a player,
// it will alert other monsters to the player.
//
void P_NoiseAlert(mobj_t *target, mobj_t *emitter)
{
   validcount++;
   P_RecursiveSound(emitter->subsector->sector, 0, target);
}

//
// P_CheckMeleeRange
//

static boolean P_CheckMeleeRange(mobj_t *actor)
{
   mobj_t *pl = actor->target;

   return  // killough 7/18/98: friendly monsters don't attack other friends
      pl && !(actor->flags & pl->flags & MF_FRIEND) &&
      (P_AproxDistance(pl->x-actor->x, pl->y-actor->y) <
       MELEERANGE - 20*FRACUNIT + pl->info->radius) &&
      P_CheckSight(actor, actor->target);
}

//
// P_HitFriend()
//
// killough 12/98
// This function tries to prevent shooting at friends

static boolean P_HitFriend(mobj_t *actor)
{
   return actor->flags & MF_FRIEND && actor->target &&
          (P_AimLineAttack(actor,
                           R_PointToAngle2(actor->x, actor->y,
                                           actor->target->x, actor->target->y),
                           P_AproxDistance(actor->x-actor->target->x,
                                           actor->y-actor->target->y), 0),
           linetarget) && linetarget != actor->target &&
          !((linetarget->flags ^ actor->flags) & MF_FRIEND);
}

//
// P_CheckMissileRange
//
boolean P_CheckMissileRange(mobj_t *actor)
{
   fixed_t dist;

   if (!P_CheckSight(actor, actor->target))
      return false;

   if (actor->flags & MF_JUSTHIT)
   {      // the target just hit the enemy, so fight back!
      actor->flags &= ~MF_JUSTHIT;

      /* killough 7/18/98: no friendly fire at corpses
       * killough 11/98: prevent too much infighting among friends
       * cph - yikes, talk about fitting everything on one line... */

      return
         !(actor->flags & MF_FRIEND) ||
         (actor->target->health > 0 &&
          (!(actor->target->flags & MF_FRIEND) ||
           (actor->target->player ?
            monster_infighting || P_Random(pr_defect) >128 :
            !(actor->target->flags & MF_JUSTHIT) && P_Random(pr_defect) >128)));
   }

   /* killough 7/18/98: friendly monsters don't attack other friendly
    * monsters or players (except when attacked, and then only once)
    */
   if (actor->flags & actor->target->flags & MF_FRIEND)
      return false;

   if (actor->reactiontime)
      return false;       // do not attack yet

   // OPTIMIZE: get this from a global checksight
   dist = P_AproxDistance ( actor->x-actor->target->x,
                            actor->y-actor->target->y) - 64*FRACUNIT;

   if (!actor->info->meleestate)
      dist -= 128*FRACUNIT;       // no melee attack, so fire more

   dist >>= FRACBITS;

   if (actor->type == MT_VILE)
      if (dist > 14*64)
         return false;     // too far away


   if (actor->type == MT_UNDEAD)
   {
      if (dist < 196)
         return false;   // close for fist attack
      dist >>= 1;
   }

   if (actor->type == MT_CYBORG ||
         actor->type == MT_SPIDER ||
         actor->type == MT_SKULL)
      dist >>= 1;

   if (dist > 200)
      dist = 200;

   if (actor->type == MT_CYBORG && dist > 160)
      dist = 160;

   if (P_Random(pr_missrange) < dist)
      return false;

   if (P_HitFriend(actor))
      return false;

   return true;
}

/*
 * P_IsOnLift
 *
 * killough 9/9/98:
 *
 * Returns true if the object is on a lift. Used for AI,
 * since it may indicate the need for crowded conditions,
 * or that a monster should stay on the lift for a while
 * while it goes up or down.
 */

static boolean P_IsOnLift(const mobj_t *actor)
{
   const sector_t *sec = actor->subsector->sector;
   line_t line;
   int l;

   // Short-circuit: it's on a lift which is active.
   if (sec->floordata && ((thinker_t *) sec->floordata)->function==T_PlatRaise)
      return true;

   // Check to see if it's in a sector which can be activated as a lift.
   if ((line.tag = sec->tag))
      for (l = -1; (l = P_FindLineFromLineTag(&line, l)) >= 0;)
         switch (lines[l].special)
         {
case  10: case  14: case  15: case  20: case  21: case  22:
case  47: case  53: case  62: case  66: case  67: case  68:
case  87: case  88: case  95: case 120: case 121: case 122:
case 123: case 143: case 162: case 163: case 181: case 182:
case 144: case 148: case 149: case 211: case 227: case 228:
case 231: case 232: case 235: case 236:
            return true;
         }

   return false;
}

/*
 * P_IsUnderDamage
 *
 * killough 9/9/98:
 *
 * Returns nonzero if the object is under damage based on
 * their current position. Returns 1 if the damage is moderate,
 * -1 if it is serious. Used for AI.
 */

static int P_IsUnderDamage(mobj_t *actor)
{
   const struct msecnode_s *seclist;
   const ceiling_t *cl;             // Crushing ceiling
   int dir = 0;
   for (seclist=actor->touching_sectorlist; seclist; seclist=seclist->m_tnext)
      if ((cl = seclist->m_sector->ceilingdata) &&
            cl->thinker.function == T_MoveCeiling)
         dir |= cl->direction;
   return dir;
}

//
// P_Move
// Move in the current direction,
// returns false if the move is blocked.
//

static fixed_t xspeed[8] = {FRACUNIT,47000,0,-47000,-FRACUNIT,-47000,0,47000};
static fixed_t yspeed[8] = {0,47000,FRACUNIT,47000,0,-47000,-FRACUNIT,-47000};

// 1/11/98 killough: Limit removed on special lines crossed
extern  line_t **spechit;          // New code -- killough
extern  int    numspechit;

static boolean P_Move(mobj_t *actor, boolean dropoff) /* killough 9/12/98 */
{
   fixed_t tryx, tryy, deltax, deltay, origx, origy;
   boolean try_ok;
   int movefactor = ORIG_FRICTION_FACTOR;    // killough 10/98
   int friction = ORIG_FRICTION;
   int speed;

   if (actor->movedir == DI_NODIR)
      return false;

#ifdef RANGECHECK
   if ((unsigned)actor->movedir >= 8)
      I_Error ("P_Move: Weird actor->movedir!");
#endif

   // killough 10/98: make monsters get affected by ice and sludge too:

   if (monster_friction)
      movefactor = P_GetMoveFactor(actor, &friction);

   speed = actor->info->speed;

   if (friction < ORIG_FRICTION &&     // sludge
         !(speed = ((ORIG_FRICTION_FACTOR - (ORIG_FRICTION_FACTOR-movefactor)/2)
                    * speed) / ORIG_FRICTION_FACTOR))
      speed = 1;      // always give the monster a little bit of speed

   tryx = (origx = actor->x) + (deltax = speed * xspeed[actor->movedir]);
   tryy = (origy = actor->y) + (deltay = speed * yspeed[actor->movedir]);

   try_ok = P_TryMove(actor, tryx, tryy, dropoff);

   // killough 10/98:
   // Let normal momentum carry them, instead of steptoeing them across ice.

   if (try_ok && friction > ORIG_FRICTION)
   {
      actor->x = origx;
      actor->y = origy;
      movefactor *= FRACUNIT / ORIG_FRICTION_FACTOR / 4;
      actor->momx += FixedMul(deltax, movefactor);
      actor->momy += FixedMul(deltay, movefactor);
   }

   if (!try_ok)
   {      // open any specials
      int good;

      if (actor->flags & MF_FLOAT && floatok)
      {
         if (actor->z < tmfloorz)          // must adjust height
            actor->z += FLOATSPEED;
         else
            actor->z -= FLOATSPEED;

         actor->flags |= MF_INFLOAT;

         return true;
      }

      if (!numspechit)
         return false;

      actor->movedir = DI_NODIR;

      /* if the special is not a door that can be opened, return false
       *
       * killough 8/9/98: this is what caused monsters to get stuck in
       * doortracks, because it thought that the monster freed itself
       * by opening a door, even if it was moving towards the doortrack,
       * and not the door itself.
       *
       * killough 9/9/98: If a line blocking the monster is activated,
       * return true 90% of the time. If a line blocking the monster is
       * not activated, but some other line is, return false 90% of the
       * time. A bit of randomness is needed to ensure it's free from
       * lockups, but for most cases, it returns the correct result.
       *
       * Do NOT simply return false 1/4th of the time (causes monsters to
       * back out when they shouldn't, and creates secondary stickiness).
       */

      for (good = false; numspechit--; )
         if (P_UseSpecialLine(actor, spechit[numspechit], 0))
            good |= spechit[numspechit] == blockline ? 1 : 2;

      /* cph - compatibility maze here
       * Boom v2.01 and orig. Doom return "good"
       * Boom v2.02 and LxDoom return good && (P_Random(pr_trywalk)&3)
       * MBF plays even more games
       */
      if (!good || comp[comp_doorstuck]) return good;
      if (!mbf_features)
         return (P_Random(pr_trywalk)&3); /* jff 8/13/98 */
      else /* finally, MBF code */
         return ((P_Random(pr_opendoor) >= 230) ^ (good & 1));
   }
   else
      actor->flags &= ~MF_INFLOAT;

   /* killough 11/98: fall more slowly, under gravity, if felldown==true */
   if (!(actor->flags & MF_FLOAT) &&
         (!felldown || !mbf_features))
      actor->z = actor->floorz;

   return true;
}

/*
 * P_SmartMove
 *
 * killough 9/12/98: Same as P_Move, except smarter
 */

static boolean P_SmartMove(mobj_t *actor)
{
   mobj_t *target = actor->target;
   int on_lift, dropoff = false, under_damage;

   /* killough 9/12/98: Stay on a lift if target is on one */
   on_lift = !comp[comp_staylift]
             && target && target->health > 0
             && target->subsector->sector->tag==actor->subsector->sector->tag &&
             P_IsOnLift(actor);

   under_damage = monster_avoid_hazards && P_IsUnderDamage(actor);

   // killough 10/98: allow dogs to drop off of taller ledges sometimes.
   // dropoff==1 means always allow it, dropoff==2 means only up to 128 high,
   // and only if the target is immediately on the other side of the line.

#ifdef DOGS
   if (actor->type == MT_DOGS && target && dog_jumping &&
         !((target->flags ^ actor->flags) & MF_FRIEND) &&
         P_AproxDistance(actor->x - target->x,
                         actor->y - target->y) < FRACUNIT*144 &&
         P_Random(pr_dropoff) < 235)
      dropoff = 2;
#endif

   if (!P_Move(actor, dropoff))
      return false;

   // killough 9/9/98: avoid crushing ceilings or other damaging areas
   if (
      (on_lift && P_Random(pr_stayonlift) < 230 &&      // Stay on lift
       !P_IsOnLift(actor))
      ||
      (monster_avoid_hazards && !under_damage &&  // Get away from damage
       (under_damage = P_IsUnderDamage(actor)) &&
       (under_damage < 0 || P_Random(pr_avoidcrush) < 200))
   )
      actor->movedir = DI_NODIR;    // avoid the area (most of the time anyway)

   return true;
}

//
// TryWalk
// Attempts to move actor on
// in its current (ob->moveangle) direction.
// If blocked by either a wall or an actor
// returns FALSE
// If move is either clear or blocked only by a door,
// returns TRUE and sets...
// If a door is in the way,
// an OpenDoor call is made to start it opening.
//

boolean P_TryWalk(mobj_t *actor)
{
   if (!P_SmartMove(actor))
      return false;
   actor->movecount = P_Random(pr_trywalk)&15;
   return true;
}

//
// P_DoNewChaseDir
//
// killough 9/8/98:
//
// Most of P_NewChaseDir(), except for what
// determines the new direction to take
//

static void P_DoNewChaseDir(mobj_t *actor, fixed_t deltax, fixed_t deltay)
{
   dirtype_t xdir, ydir, tdir;
   dirtype_t olddir = actor->movedir;
   dirtype_t turnaround = olddir;

   if (turnaround != DI_NODIR)         // find reverse direction
      turnaround ^= 4;

   xdir =
      deltax >  10*FRACUNIT ? DI_EAST :
      deltax < -10*FRACUNIT ? DI_WEST : DI_NODIR;

   ydir =
      deltay < -10*FRACUNIT ? DI_SOUTH :
      deltay >  10*FRACUNIT ? DI_NORTH : DI_NODIR;

   // try direct route
   if (xdir != DI_NODIR && ydir != DI_NODIR && turnaround !=
      (actor->movedir = deltay < 0 ? deltax > 0 ? DI_SOUTHEAST : DI_SOUTHWEST :
                           deltax > 0 ? DI_NORTHEAST : DI_NORTHWEST) && P_TryWalk(actor))
      return;

   // try other directions
   if (P_Random(pr_newchase) > 200 || abs(deltay)>abs(deltax))
      tdir = xdir, xdir = ydir, ydir = tdir;

   if ((xdir == turnaround ? xdir = DI_NODIR : xdir) != DI_NODIR &&
         (actor->movedir = xdir, P_TryWalk(actor)))
      return;         // either moved forward or attacked

   if ((ydir == turnaround ? ydir = DI_NODIR : ydir) != DI_NODIR &&
         (actor->movedir = ydir, P_TryWalk(actor)))
      return;

   // there is no direct path to the player, so pick another direction.
   if (olddir != DI_NODIR && (actor->movedir = olddir, P_TryWalk(actor)))
      return;

   // randomly determine direction of search
   if (P_Random(pr_newchasedir) & 1)
   {
      for (tdir = DI_EAST; tdir <= DI_SOUTHEAST; tdir++)
         if (tdir != turnaround && (actor->movedir = tdir, P_TryWalk(actor)))
            return;
   }
   else
      for (tdir = DI_SOUTHEAST; tdir != (dirtype_t)(DI_EAST-1); tdir--)
         if (tdir != turnaround && (actor->movedir = tdir, P_TryWalk(actor)))
            return;

   if ((actor->movedir = turnaround) != DI_NODIR && !P_TryWalk(actor))
      actor->movedir = DI_NODIR;
}

//
// killough 11/98:
//
// Monsters try to move away from tall dropoffs.
//
// In Doom, they were never allowed to hang over dropoffs,
// and would remain stuck if involuntarily forced over one.
// This logic, combined with p_map.c (P_TryMove), allows
// monsters to free themselves without making them tend to
// hang over dropoffs.

static fixed_t dropoff_deltax, dropoff_deltay, floorz;

static boolean PIT_AvoidDropoff(line_t *line)
{
   if (line->backsector                          && // Ignore one-sided linedefs
         tmbbox[BOXRIGHT]  > line->bbox[BOXLEFT]   &&
         tmbbox[BOXLEFT]   < line->bbox[BOXRIGHT]  &&
         tmbbox[BOXTOP]    > line->bbox[BOXBOTTOM] && // Linedef must be contacted
         tmbbox[BOXBOTTOM] < line->bbox[BOXTOP]    &&
         P_BoxOnLineSide(tmbbox, line) == -1)
   {
      fixed_t front = line->frontsector->floorheight;
      fixed_t back  = line->backsector->floorheight;
      angle_t angle;

      // The monster must contact one of the two floors,
      // and the other must be a tall dropoff (more than 24).

      if (back == floorz && front < floorz - FRACUNIT*24)
         angle = R_PointToAngle2(0,0,line->dx,line->dy);   // front side dropoff
      else
         if (front == floorz && back < floorz - FRACUNIT*24)
            angle = R_PointToAngle2(line->dx,line->dy,0,0); // back side dropoff
         else
            return true;

      // Move away from dropoff at a standard speed.
      // Multiple contacted linedefs are cumulative (e.g. hanging over corner)
      dropoff_deltax -= finesine[angle >> ANGLETOFINESHIFT]*32;
      dropoff_deltay += finecosine[angle >> ANGLETOFINESHIFT]*32;
   }
   return true;
}

//
// Driver for above
//

static fixed_t P_AvoidDropoff(mobj_t *actor)
{
   int yh=((tmbbox[BOXTOP]   = actor->y+actor->radius)-bmaporgy)>>MAPBLOCKSHIFT;
   int yl=((tmbbox[BOXBOTTOM]= actor->y-actor->radius)-bmaporgy)>>MAPBLOCKSHIFT;
   int xh=((tmbbox[BOXRIGHT] = actor->x+actor->radius)-bmaporgx)>>MAPBLOCKSHIFT;
   int xl=((tmbbox[BOXLEFT]  = actor->x-actor->radius)-bmaporgx)>>MAPBLOCKSHIFT;
   int bx, by;

   floorz = actor->z;            // remember floor height

   dropoff_deltax = dropoff_deltay = 0;

   // check lines

   validcount++;
   for (bx=xl ; bx<=xh ; bx++)
      for (by=yl ; by<=yh ; by++)
         P_BlockLinesIterator(bx, by, PIT_AvoidDropoff);  // all contacted lines

   return dropoff_deltax | dropoff_deltay;   // Non-zero if movement prescribed
}

//
// P_NewChaseDir
//
// killough 9/8/98: Split into two functions
//

static void P_NewChaseDir(mobj_t *actor)
{
   mobj_t *target = actor->target;
   fixed_t deltax = target->x - actor->x;
   fixed_t deltay = target->y - actor->y;

   // killough 8/8/98: sometimes move away from target, keeping distance
   //
   // 1) Stay a certain distance away from a friend, to avoid being in their way
   // 2) Take advantage over an enemy without missiles, by keeping distance

   actor->strafecount = 0;

   if (mbf_features) {
      if (actor->floorz - actor->dropoffz > FRACUNIT*24 &&
            actor->z <= actor->floorz &&
            !(actor->flags & (MF_DROPOFF|MF_FLOAT)) &&
            !comp[comp_dropoff] &&
            P_AvoidDropoff(actor)) /* Move away from dropoff */
      {
         P_DoNewChaseDir(actor, dropoff_deltax, dropoff_deltay);

         // If moving away from dropoff, set movecount to 1 so that
         // small steps are taken to get monster away from dropoff.

         actor->movecount = 1;
         return;
      }
      else
      {
         fixed_t dist = P_AproxDistance(deltax, deltay);

         // Move away from friends when too close, except
         // in certain situations (e.g. a crowded lift)

         if (actor->flags & target->flags & MF_FRIEND &&
               distfriend << FRACBITS > dist &&
               !P_IsOnLift(target) && !P_IsUnderDamage(actor))
         {
            deltax = -deltax, deltay = -deltay;
         } else
            if (target->health > 0 && (actor->flags ^ target->flags) & MF_FRIEND)
            {   // Live enemy target
               if (monster_backing &&
                     actor->info->missilestate && actor->type != MT_SKULL &&
                     ((!target->info->missilestate && dist < MELEERANGE*2) ||
                      (target->player && dist < MELEERANGE*3 &&
                       (target->player->readyweapon == wp_fist ||
                        target->player->readyweapon == wp_chainsaw))))
               {       // Back away from melee attacker
                  actor->strafecount = P_Random(pr_enemystrafe) & 15;
                  deltax = -deltax, deltay = -deltay;
               }
            }
      }
   }

   P_DoNewChaseDir(actor, deltax, deltay);

   // If strafing, set movecount to strafecount so that old Doom
   // logic still works the same, except in the strafing part

   if (actor->strafecount)
      actor->movecount = actor->strafecount;
}

//
// P_IsVisible
//
// killough 9/9/98: whether a target is visible to a monster
//

static boolean P_IsVisible(mobj_t *actor, mobj_t *mo, boolean allaround)
{
   if (!allaround)
   {
      angle_t an = R_PointToAngle2(actor->x, actor->y,
                                   mo->x, mo->y) - actor->angle;
      if (an > ANG90 && an < ANG270 &&
            P_AproxDistance(mo->x-actor->x, mo->y-actor->y) > MELEERANGE)
         return false;
   }
   return P_CheckSight(actor, mo);
}

//
// PIT_FindTarget
//
// killough 9/5/98
//
// Finds monster targets for other monsters
//

static int current_allaround;

static boolean PIT_FindTarget(mobj_t *mo)
{
   mobj_t *actor = current_actor;

   if (!((mo->flags ^ actor->flags) & MF_FRIEND &&        // Invalid target
         mo->health > 0 && (mo->flags & MF_COUNTKILL || mo->type == MT_SKULL)))
      return true;

   // If the monster is already engaged in a one-on-one attack
   // with a healthy friend, don't attack around 60% the time
   {
      const mobj_t *targ = mo->target;
      if (targ && targ->target == mo &&
            P_Random(pr_skiptarget) > 100 &&
            (targ->flags ^ mo->flags) & MF_FRIEND &&
            targ->health*2 >= targ->info->spawnhealth)
         return true;
   }

   if (!P_IsVisible(actor, mo, current_allaround))
      return true;

   P_SetTarget(&actor->lastenemy, actor->target);  // Remember previous target
   P_SetTarget(&actor->target, mo);                // Found target

   // Move the selected monster to the end of its associated
   // list, so that it gets searched last next time.

   {
      thinker_t *cap = &thinkerclasscap[mo->flags & MF_FRIEND ?
                                        th_friends : th_enemies];
      (mo->thinker.cprev->cnext = mo->thinker.cnext)->cprev = mo->thinker.cprev;
      (mo->thinker.cprev = cap->cprev)->cnext = &mo->thinker;
      (mo->thinker.cnext = cap)->cprev = &mo->thinker;
   }

   return false;
}

//
// P_LookForPlayers
// If allaround is false, only look 180 degrees in front.
// Returns true if a player is targeted.
//

static boolean P_LookForPlayers(mobj_t *actor, boolean allaround)
{
   player_t *player;
   int stop, stopc, c;

   if (actor->flags & MF_FRIEND)
   {  // killough 9/9/98: friendly monsters go about players differently
      int anyone;

#if 0
      if (!allaround) // If you want friendly monsters not to awaken unprovoked
         return false;
#endif

      // Go back to a player, no matter whether it's visible or not
      for (anyone=0; anyone<=1; anyone++)
         for (c=0; c<MAXPLAYERS; c++)
            if (playeringame[c] && players[c].playerstate==PST_LIVE &&
                  (anyone || P_IsVisible(actor, players[c].mo, allaround)))
            {
               P_SetTarget(&actor->target, players[c].mo);

               // killough 12/98:
               // get out of refiring loop, to avoid hitting player accidentally

               if (actor->info->missilestate)
               {
                  P_SetMobjState(actor, actor->info->seestate);
                  actor->flags &= ~MF_JUSTHIT;
               }

               return true;
            }

      return false;
   }

   // Change mask of 3 to (MAXPLAYERS-1) -- killough 2/15/98:
   stop = (actor->lastlook-1)&(MAXPLAYERS-1);

   c = 0;

   stopc = !mbf_features &&
           !demo_compatibility && monsters_remember ?
           MAXPLAYERS : 2;       // killough 9/9/98

   for (;; actor->lastlook = (actor->lastlook+1)&(MAXPLAYERS-1))
   {
      if (!playeringame[actor->lastlook])
         continue;

      // killough 2/15/98, 9/9/98:
      if (c++ == stopc || actor->lastlook == stop)  // done looking
         return false;

      player = &players[actor->lastlook];

      if (player->health <= 0)
         continue;               // dead

      if (!P_IsVisible(actor, player->mo, allaround))
         continue;

      P_SetTarget(&actor->target, player->mo);

      /* killough 9/9/98: give monsters a threshold towards getting players
       * (we don't want it to be too easy for a player with dogs :)
       */
      if (!comp[comp_pursuit])
         actor->threshold = 60;

      return true;
   }
}

//
// Friendly monsters, by Lee Killough 7/18/98
//
// Friendly monsters go after other monsters first, but
// also return to owner if they cannot find any targets.
// A marine's best friend :)  killough 7/18/98, 9/98
//

static boolean P_LookForMonsters(mobj_t *actor, boolean allaround)
{
   thinker_t *cap, *th;

   if (demo_compatibility)
      return false;

   if (actor->lastenemy && actor->lastenemy->health > 0 && monsters_remember &&
         !(actor->lastenemy->flags & actor->flags & MF_FRIEND)) // not friends
   {
      P_SetTarget(&actor->target, actor->lastenemy);
      P_SetTarget(&actor->lastenemy, NULL);
      return true;
   }

   /* Old demos do not support monster-seeking bots */
   if (!mbf_features)
      return false;

   // Search the threaded list corresponding to this object's potential targets
   cap = &thinkerclasscap[actor->flags & MF_FRIEND ? th_enemies : th_friends];

   // Search for new enemy

   if (cap->cnext != cap)        // Empty list? bail out early
   {
      int x = (actor->x - bmaporgx)>>MAPBLOCKSHIFT;
      int y = (actor->y - bmaporgy)>>MAPBLOCKSHIFT;
      int d;

      current_actor = actor;
      current_allaround = allaround;

      // Search first in the immediate vicinity.

      if (!P_BlockThingsIterator(x, y, PIT_FindTarget))
         return true;

      for (d=1; d<5; d++)
      {
         int i = 1 - d;
         do
            if (!P_BlockThingsIterator(x+i, y-d, PIT_FindTarget) ||
                  !P_BlockThingsIterator(x+i, y+d, PIT_FindTarget))
               return true;
         while (++i < d);
         do
            if (!P_BlockThingsIterator(x-d, y+i, PIT_FindTarget) ||
                  !P_BlockThingsIterator(x+d, y+i, PIT_FindTarget))
               return true;
         while (--i + d >= 0);
      }

      {   // Random number of monsters, to prevent patterns from forming
         int n = (P_Random(pr_friends) & 31) + 15;

         for (th = cap->cnext; th != cap; th = th->cnext)
            if (--n < 0)
            {
               // Only a subset of the monsters were searched. Move all of
               // the ones which were searched so far, to the end of the list.

               (cap->cnext->cprev = cap->cprev)->cnext = cap->cnext;
               (cap->cprev = th->cprev)->cnext = cap;
               (th->cprev = cap)->cnext = th;
               break;
            }
            else
               if (!PIT_FindTarget((mobj_t *) th))   // If target sighted
                  return true;
      }
   }

   return false;  // No monster found
}

//
// P_LookForTargets
//
// killough 9/5/98: look for targets to go after, depending on kind of monster
//

static boolean P_LookForTargets(mobj_t *actor, int allaround)
{
   return actor->flags & MF_FRIEND ?
          P_LookForMonsters(actor, allaround) || P_LookForPlayers (actor, allaround):
          P_LookForPlayers (actor, allaround) || P_LookForMonsters(actor, allaround);
}

//
// P_HelpFriend
//
// killough 9/8/98: Help friends in danger of dying
//

static boolean P_HelpFriend(mobj_t *actor)
{
   thinker_t *cap, *th;

   // If less than 33% health, self-preservation rules
   if (actor->health*3 < actor->info->spawnhealth)
      return false;

   current_actor = actor;
   current_allaround = true;

   // Possibly help a friend under 50% health
   cap = &thinkerclasscap[actor->flags & MF_FRIEND ? th_friends : th_enemies];

   for (th = cap->cnext; th != cap; th = th->cnext)
      if (((mobj_t *) th)->health*2 >= ((mobj_t *) th)->info->spawnhealth)
      {
         if (P_Random(pr_helpfriend) < 180)
            break;
      }
      else
         if (((mobj_t *) th)->flags & MF_JUSTHIT &&
               ((mobj_t *) th)->target &&
               ((mobj_t *) th)->target != actor->target &&
               !PIT_FindTarget(((mobj_t *) th)->target))
         {
            // Ignore any attacking monsters, while searching for friend
            actor->threshold = BASETHRESHOLD;
            return true;
         }

   return false;
}

//
// A_KeenDie
// DOOM II special, map 32.
// Uses special tag 666.
//
void A_KeenDie(mobj_t* mo)
{
   thinker_t *th;
   line_t   junk;

   A_Fall(mo);

   // scan the remaining thinkers to see if all Keens are dead

   for (th = thinkercap.next ; th != &thinkercap ; th=th->next)
      if (th->function == P_MobjThinker)
      {
         mobj_t *mo2 = (mobj_t *) th;
         if (mo2 != mo && mo2->type == mo->type && mo2->health > 0)
            return;                           // other Keen not dead
      }

   junk.tag = 666;
   EV_DoDoor(&junk,p_open);
}


//
// ACTION ROUTINES
//

//
// A_Look
// Stay in state until a player is sighted.
//

void A_Look(mobj_t *actor)
{
   mobj_t *targ = actor->subsector->sector->soundtarget;
   actor->threshold = 0; // any shot will wake up

   /* killough 7/18/98:
    * Friendly monsters go after other monsters first, but
    * also return to player, without attacking them, if they
    * cannot find any targets. A marine's best friend :)
    */
   actor->pursuecount = 0;

   if (!(actor->flags & MF_FRIEND && P_LookForTargets(actor, false)) &&
         !((targ = actor->subsector->sector->soundtarget) &&
           targ->flags & MF_SHOOTABLE &&
           (P_SetTarget(&actor->target, targ),
            !(actor->flags & MF_AMBUSH) || P_CheckSight(actor, targ))) &&
         (actor->flags & MF_FRIEND || !P_LookForTargets(actor, false)))
      return;

   // go into chase state

   if (actor->info->seesound)
   {
      int sound;
      switch (actor->info->seesound)
      {
      case sfx_posit1:
      case sfx_posit2:
      case sfx_posit3:
         sound = sfx_posit1+P_Random(pr_see)%3;
         break;

      case sfx_bgsit1:
      case sfx_bgsit2:
         sound = sfx_bgsit1+P_Random(pr_see)%2;
         break;

      default:
         sound = actor->info->seesound;
         break;
      }
      if (actor->type==MT_SPIDER || actor->type == MT_CYBORG)
         S_StartSound(NULL, sound);          // full volume
      else
         S_StartSound(actor, sound);
   }
   P_SetMobjState(actor, actor->info->seestate);
}

//
// A_KeepChasing
//
// killough 10/98:
// Allows monsters to continue movement while attacking
//

void A_KeepChasing(mobj_t *actor)
{
   if (actor->movecount)
   {
      actor->movecount--;
      if (actor->strafecount)
         actor->strafecount--;
      P_SmartMove(actor);
   }
}

//
// A_Chase
// Actor has a melee attack,
// so it tries to close as fast as possible
//

void A_Chase(mobj_t *actor)
{
   if (actor->reactiontime)
      actor->reactiontime--;

   if (actor->threshold) { /* modify target threshold */
      if (!actor->target || actor->target->health <= 0)
         actor->threshold = 0;
      else
         actor->threshold--;
   }

   /* turn towards movement direction if not there yet
    * killough 9/7/98: keep facing towards target if strafing or backing out
    */

   if (actor->strafecount)
      A_FaceTarget(actor);
   else if (actor->movedir < 8)
   {
      int delta = (actor->angle &= (7<<29)) - (actor->movedir << 29);
      if (delta > 0)
         actor->angle -= ANG90/2;
      else
         if (delta < 0)
            actor->angle += ANG90/2;
   }

   if (!actor->target || !(actor->target->flags&MF_SHOOTABLE))
   {
      if (!P_LookForTargets(actor,true)) // look for a new target
         P_SetMobjState(actor, actor->info->spawnstate); // no new target
      return;
   }

   // do not attack twice in a row
   if (actor->flags & MF_JUSTATTACKED)
   {
      actor->flags &= ~MF_JUSTATTACKED;
      if (gameskill != sk_nightmare && !fastparm)
         P_NewChaseDir(actor);
      return;
   }

   // check for melee attack
   if (actor->info->meleestate && P_CheckMeleeRange(actor))
   {
      if (actor->info->attacksound)
         S_StartSound(actor, actor->info->attacksound);
      P_SetMobjState(actor, actor->info->meleestate);
      /* killough 8/98: remember an attack
      * cph - DEMOSYNC? */
      if (!actor->info->missilestate)
         actor->flags |= MF_JUSTHIT;
      return;
   }

   // check for missile attack
   if (actor->info->missilestate)
      if (!(gameskill < sk_nightmare && !fastparm && actor->movecount))
         if (P_CheckMissileRange(actor))
         {
            P_SetMobjState(actor, actor->info->missilestate);
            actor->flags |= MF_JUSTATTACKED;
            return;
         }

   if (!actor->threshold) {
      if (!mbf_features)
      {   /* killough 9/9/98: for backward demo compatibility */
         if (netgame && !P_CheckSight(actor, actor->target) &&
               P_LookForPlayers(actor, true))
            return;
      }
      /* killough 7/18/98, 9/9/98: new monster AI */
      else if (help_friends && P_HelpFriend(actor))
         return;      /* killough 9/8/98: Help friends in need */
      /* Look for new targets if current one is bad or is out of view */
      else if (actor->pursuecount)
         actor->pursuecount--;
      else {
         /* Our pursuit time has expired. We're going to think about
          * changing targets */
         actor->pursuecount = BASETHRESHOLD;

         /* Unless (we have a live target
          *         and it's not friendly
          *         and we can see it)
          *  try to find a new one; return if sucessful */

         if (!(actor->target && actor->target->health > 0 &&
               ((comp[comp_pursuit] && !netgame) ||
                (((actor->target->flags ^ actor->flags) & MF_FRIEND ||
                  (!(actor->flags & MF_FRIEND) && monster_infighting)) &&
                 P_CheckSight(actor, actor->target))))
               && P_LookForTargets(actor, true))
            return;

         /* (Current target was good, or no new target was found.)
          *
          * If monster is a missile-less friend, give up pursuit and
          * return to player, if no attacks have occurred recently.
          */

         if (!actor->info->missilestate && actor->flags & MF_FRIEND) {
            if (actor->flags & MF_JUSTHIT)          /* if recent action, */
               actor->flags &= ~MF_JUSTHIT;          /* keep fighting */
            else if (P_LookForPlayers(actor, true)) /* else return to player */
               return;
         }
      }
   }

   if (actor->strafecount)
      actor->strafecount--;

   // chase towards player
   if (--actor->movecount<0 || !P_SmartMove(actor))
      P_NewChaseDir(actor);

   // make active sound
   if (actor->info->activesound && P_Random(pr_see)<3)
      S_StartSound(actor, actor->info->activesound);
}

//
// A_FaceTarget
//
void A_FaceTarget(mobj_t *actor)
{
   if (!actor->target)
      return;
   actor->flags &= ~MF_AMBUSH;
   actor->angle = R_PointToAngle2(actor->x, actor->y,
                                  actor->target->x, actor->target->y);
   if (actor->target->flags & MF_SHADOW)
   { // killough 5/5/98: remove dependence on order of evaluation:
      int t = P_Random(pr_facetarget);
      actor->angle += (t-P_Random(pr_facetarget))<<21;
   }
}

//
// A_PosAttack
//

void A_PosAttack(mobj_t *actor)
{
   int angle, damage, slope, t;

   if (!actor->target)
      return;
   A_FaceTarget(actor);
   angle = actor->angle;
   slope = P_AimLineAttack(actor, angle, MISSILERANGE, 0); /* killough 8/2/98 */
   S_StartSound(actor, sfx_pistol);

   // killough 5/5/98: remove dependence on order of evaluation:
   t = P_Random(pr_posattack);
   angle += (t - P_Random(pr_posattack))<<20;
   damage = (P_Random(pr_posattack)%5 + 1)*3;
   P_LineAttack(actor, angle, MISSILERANGE, slope, damage);
}

void A_SPosAttack(mobj_t* actor)
{
   int i, bangle, slope;

   if (!actor->target)
      return;
   S_StartSound(actor, sfx_shotgn);
   A_FaceTarget(actor);
   bangle = actor->angle;
   slope = P_AimLineAttack(actor, bangle, MISSILERANGE, 0); /* killough 8/2/98 */
   for (i=0; i<3; i++)
   {  // killough 5/5/98: remove dependence on order of evaluation:
      int t = P_Random(pr_sposattack);
      int angle = bangle + ((t - P_Random(pr_sposattack))<<20);
      int damage = ((P_Random(pr_sposattack)%5)+1)*3;
      P_LineAttack(actor, angle, MISSILERANGE, slope, damage);
   }
}

void A_CPosAttack(mobj_t *actor)
{
   int angle, bangle, damage, slope, t;

   if (!actor->target)
      return;
   S_StartSound(actor, sfx_shotgn);
   A_FaceTarget(actor);
   bangle = actor->angle;
   slope = P_AimLineAttack(actor, bangle, MISSILERANGE, 0); /* killough 8/2/98 */

   // killough 5/5/98: remove dependence on order of evaluation:
   t = P_Random(pr_cposattack);
   angle = bangle + ((t - P_Random(pr_cposattack))<<20);
   damage = ((P_Random(pr_cposattack)%5)+1)*3;
   P_LineAttack(actor, angle, MISSILERANGE, slope, damage);
}

void A_CPosRefire(mobj_t *actor)
{
   // keep firing unless target got out of sight
   A_FaceTarget(actor);

   /* killough 12/98: Stop firing if a friend has gotten in the way */
   if (P_HitFriend(actor))
      goto stop;

   /* killough 11/98: prevent refiring on friends continuously */
   if (P_Random(pr_cposrefire) < 40) {
      if (actor->target && actor->flags & actor->target->flags & MF_FRIEND)
         goto stop;
      else
         return;
   }

   if (!actor->target || actor->target->health <= 0
         || !P_CheckSight(actor, actor->target))
stop:  P_SetMobjState(actor, actor->info->seestate);
}

void A_SpidRefire(mobj_t* actor)
{
   // keep firing unless target got out of sight
   A_FaceTarget(actor);

   /* killough 12/98: Stop firing if a friend has gotten in the way */
   if (P_HitFriend(actor))
      goto stop;

   if (P_Random(pr_spidrefire) < 10)
      return;

   // killough 11/98: prevent refiring on friends continuously
   if (!actor->target || actor->target->health <= 0
         || actor->flags & actor->target->flags & MF_FRIEND
         || !P_CheckSight(actor, actor->target))
stop: P_SetMobjState(actor, actor->info->seestate);
}

void A_BspiAttack(mobj_t *actor)
{
   if (!actor->target)
      return;
   A_FaceTarget(actor);
   P_SpawnMissile(actor, actor->target, MT_ARACHPLAZ);  // launch a missile
}

//
// A_TroopAttack
//

void A_TroopAttack(mobj_t *actor)
{
   if (!actor->target)
      return;
   A_FaceTarget(actor);
   if (P_CheckMeleeRange(actor))
   {
      int damage;
      S_StartSound(actor, sfx_claw);
      damage = (P_Random(pr_troopattack)%8+1)*3;
      P_DamageMobj(actor->target, actor, actor, damage);
      return;
   }
   P_SpawnMissile(actor, actor->target, MT_TROOPSHOT);  // launch a missile
}

void A_SargAttack(mobj_t *actor)
{
   if (!actor->target)
      return;
   A_FaceTarget(actor);
   if (P_CheckMeleeRange(actor))
   {
      int damage = ((P_Random(pr_sargattack)%10)+1)*4;
      P_DamageMobj(actor->target, actor, actor, damage);
   }
}

void A_HeadAttack(mobj_t *actor)
{
   if (!actor->target)
      return;
   A_FaceTarget (actor);
   if (P_CheckMeleeRange(actor))
   {
      int damage = (P_Random(pr_headattack)%6+1)*10;
      P_DamageMobj(actor->target, actor, actor, damage);
      return;
   }
   P_SpawnMissile(actor, actor->target, MT_HEADSHOT);  // launch a missile
}

void A_CyberAttack(mobj_t *actor)
{
   if (!actor->target)
      return;
   A_FaceTarget(actor);
   P_SpawnMissile(actor, actor->target, MT_ROCKET);
}

void A_BruisAttack(mobj_t *actor)
{
   if (!actor->target)
      return;
   if (P_CheckMeleeRange(actor))
   {
      int damage;
      S_StartSound(actor, sfx_claw);
      damage = (P_Random(pr_bruisattack)%8+1)*10;
      P_DamageMobj(actor->target, actor, actor, damage);
      return;
   }
   P_SpawnMissile(actor, actor->target, MT_BRUISERSHOT);  // launch a missile
}

//
// A_SkelMissile
//

void A_SkelMissile(mobj_t *actor)
{
   mobj_t *mo;

   if (!actor->target)
      return;

   A_FaceTarget (actor);
   actor->z += 16*FRACUNIT;      // so missile spawns higher
   mo = P_SpawnMissile (actor, actor->target, MT_TRACER);
   actor->z -= 16*FRACUNIT;      // back to normal

   mo->x += mo->momx;
   mo->y += mo->momy;
   P_SetTarget(&mo->tracer, actor->target);
}

int     TRACEANGLE = 0xc000000;

void A_Tracer(mobj_t *actor)
{
   angle_t       exact;
   fixed_t       dist;
   fixed_t       slope;
   mobj_t        *dest;
   mobj_t        *th;

   /* killough 1/18/98: this is why some missiles do not have smoke
    * and some do. Also, internal demos start at random gametics, thus
    * the bug in which revenants cause internal demos to go out of sync.
    *
    * killough 3/6/98: fix revenant internal demo bug by subtracting
    * levelstarttic from gametic.
    *
    * killough 9/29/98: use new "basetic" so that demos stay in sync
    * during pauses and menu activations, while retaining old demo sync.
    *
    * leveltime would have been better to use to start with in Doom, but
    * since old demos were recorded using gametic, we must stick with it,
    * and improvise around it (using leveltime causes desync across levels).
    */

   if ((gametic-basetic) & 3)
      return;

   // spawn a puff of smoke behind the rocket
   P_SpawnPuff(actor->x, actor->y, actor->z);

   th = P_SpawnMobj (actor->x-actor->momx,
                     actor->y-actor->momy,
                     actor->z, MT_SMOKE);

   th->momz = FRACUNIT;
   th->tics -= P_Random(pr_tracer) & 3;
   if (th->tics < 1)
      th->tics = 1;

   // adjust direction
   dest = actor->tracer;

   if (!dest || dest->health <= 0)
      return;

   // change angle
   exact = R_PointToAngle2(actor->x, actor->y, dest->x, dest->y);

   if (exact != actor->angle) {
      if (exact - actor->angle > 0x80000000)
      {
         actor->angle -= TRACEANGLE;
         if (exact - actor->angle < 0x80000000)
            actor->angle = exact;
      }
      else
      {
         actor->angle += TRACEANGLE;
         if (exact - actor->angle > 0x80000000)
            actor->angle = exact;
      }
   }

   exact = actor->angle>>ANGLETOFINESHIFT;
   actor->momx = FixedMul(actor->info->speed, finecosine[exact]);
   actor->momy = FixedMul(actor->info->speed, finesine[exact]);

   // change slope
   dist = P_AproxDistance(dest->x - actor->x, dest->y - actor->y);

   dist = dist / actor->info->speed;

   if (dist < 1)
      dist = 1;

   slope = (dest->z+40*FRACUNIT - actor->z) / dist;

   if (slope < actor->momz)
      actor->momz -= FRACUNIT/8;
   else
      actor->momz += FRACUNIT/8;
}

void A_SkelWhoosh(mobj_t *actor)
{
   if (!actor->target)
      return;
   A_FaceTarget(actor);
   S_StartSound(actor,sfx_skeswg);
}

void A_SkelFist(mobj_t *actor)
{
   if (!actor->target)
      return;
   A_FaceTarget(actor);
   if (P_CheckMeleeRange(actor))
   {
      int damage = ((P_Random(pr_skelfist)%10)+1)*6;
      S_StartSound(actor, sfx_skepch);
      P_DamageMobj(actor->target, actor, actor, damage);
   }
}

//
// PIT_VileCheck
// Detect a corpse that could be raised.
//

mobj_t* corpsehit;
mobj_t* vileobj;
fixed_t viletryx;
fixed_t viletryy;

boolean PIT_VileCheck(mobj_t *thing)
{
   int     maxdist;
   boolean check;

   if (!(thing->flags & MF_CORPSE) )
      return true;        // not a monster

   if (thing->tics != -1)
      return true;        // not lying still yet

   if (thing->info->raisestate == S_NULL)
      return true;        // monster doesn't have a raise state

   maxdist = thing->info->radius + mobjinfo[MT_VILE].radius;

   if (D_abs(thing->x-viletryx) > maxdist || D_abs(thing->y-viletryy) > maxdist)
      return true;                // not actually touching

   // Check to see if the radius and height are zero. If they are      // phares
   // then this is a crushed monster that has been turned into a       //   |
   // gib. One of the options may be to ignore this guy.               //   V

   // Option 1: the original, buggy method, -> ghost (compatibility)
   // Option 2: ressurect the monster, but not as a ghost
   // Option 3: ignore the gib

   //    if (Option3)                                                  //   ^
   //        if ((thing->height == 0) && (thing->radius == 0))         //   |
   //            return true;                                          // phares

   corpsehit = thing;
   corpsehit->momx = corpsehit->momy = 0;
   if (comp[comp_vile])                                            // phares
   {                                                             //   |
      corpsehit->height <<= 2;                                    //   V
      check = P_CheckPosition(corpsehit,corpsehit->x,corpsehit->y);
      corpsehit->height >>= 2;
   }
   else
   {
      int height,radius;

      height = corpsehit->height; // save temporarily
      radius = corpsehit->radius; // save temporarily
      corpsehit->height = corpsehit->info->height;
      corpsehit->radius = corpsehit->info->radius;
      corpsehit->flags |= MF_SOLID;
      check = P_CheckPosition(corpsehit,corpsehit->x,corpsehit->y);
      corpsehit->height = height; // restore
      corpsehit->radius = radius; // restore                      //   ^
      corpsehit->flags &= ~MF_SOLID;
   }                                                             //   |
   // phares
   if (!check)
      return true;              // doesn't fit here
   return false;               // got one, so stop checking
}

//
// A_VileChase
// Check for ressurecting a body
//

void A_VileChase(mobj_t* actor)
{
   int xl, xh;
   int yl, yh;
   int bx, by;

   if (actor->movedir != DI_NODIR)
   {
      // check for corpses to raise
      viletryx =
         actor->x + actor->info->speed*xspeed[actor->movedir];
      viletryy =
         actor->y + actor->info->speed*yspeed[actor->movedir];

      xl = (viletryx - bmaporgx - MAXRADIUS*2)>>MAPBLOCKSHIFT;
      xh = (viletryx - bmaporgx + MAXRADIUS*2)>>MAPBLOCKSHIFT;
      yl = (viletryy - bmaporgy - MAXRADIUS*2)>>MAPBLOCKSHIFT;
      yh = (viletryy - bmaporgy + MAXRADIUS*2)>>MAPBLOCKSHIFT;

      vileobj = actor;
      for (bx=xl ; bx<=xh ; bx++)
      {
         for (by=yl ; by<=yh ; by++)
         {
            // Call PIT_VileCheck to check
            // whether object is a corpse
            // that canbe raised.
            if (!P_BlockThingsIterator(bx,by,PIT_VileCheck))
            {
               mobjinfo_t *info;

               // got one!
               mobj_t* temp = actor->target;
               actor->target = corpsehit;
               A_FaceTarget(actor);
               actor->target = temp;

               P_SetMobjState(actor, S_VILE_HEAL1);
               S_StartSound(corpsehit, sfx_slop);
               info = corpsehit->info;

               P_SetMobjState(corpsehit,info->raisestate);

               if (comp[comp_vile])                              // phares
                  corpsehit->height <<= 2;                        //   |
               else                                              //   V
               {
                  corpsehit->height = info->height; // fix Ghost bug
                  corpsehit->radius = info->radius; // fix Ghost bug
               }                                               // phares

               /* killough 7/18/98:
                * friendliness is transferred from AV to raised corpse
                */
               corpsehit->flags =
                  (info->flags & ~MF_FRIEND) | (actor->flags & MF_FRIEND);

               if (!((corpsehit->flags ^ MF_COUNTKILL) & (MF_FRIEND | MF_COUNTKILL)))
                  totallive++;

               corpsehit->health = info->spawnhealth;
               P_SetTarget(&corpsehit->target, NULL);  // killough 11/98

               if (mbf_features)
               {         /* kilough 9/9/98 */
                  P_SetTarget(&corpsehit->lastenemy, NULL);
                  corpsehit->flags &= ~MF_JUSTHIT;
               }

               /* killough 8/29/98: add to appropriate thread */
               P_UpdateThinker(&corpsehit->thinker);

               return;
            }
         }
      }
   }
   A_Chase(actor);  // Return to normal attack.
}

//
// A_VileStart
//

void A_VileStart(mobj_t *actor)
{
   S_StartSound(actor, sfx_vilatk);
}

//
// A_Fire
// Keep fire in front of player unless out of sight
//

void A_Fire(mobj_t *actor);

void A_StartFire(mobj_t *actor)
{
   S_StartSound(actor,sfx_flamst);
   A_Fire(actor);
}

void A_FireCrackle(mobj_t* actor)
{
   S_StartSound(actor,sfx_flame);
   A_Fire(actor);
}

void A_Fire(mobj_t *actor)
{
   unsigned an;
   mobj_t *dest = actor->tracer;

   if (!dest)
      return;

   // don't move it if the vile lost sight
   if (!P_CheckSight(actor->target, dest) )
      return;

   an = dest->angle >> ANGLETOFINESHIFT;

   P_UnsetThingPosition(actor);
   actor->x = dest->x + FixedMul(24*FRACUNIT, finecosine[an]);
   actor->y = dest->y + FixedMul(24*FRACUNIT, finesine[an]);
   actor->z = dest->z;
   P_SetThingPosition(actor);
}

//
// A_VileTarget
// Spawn the hellfire
//

void A_VileTarget(mobj_t *actor)
{
   mobj_t *fog;

   if (!actor->target)
      return;

   A_FaceTarget(actor);

   // killough 12/98: fix Vile fog coordinates // CPhipps - compatibility optioned
   fog = P_SpawnMobj(actor->target->x,
                     (compatibility_level < lxdoom_1_compatibility) ? actor->target->x : actor->target->y,
                     actor->target->z,MT_FIRE);

   P_SetTarget(&actor->tracer, fog);
   P_SetTarget(&fog->target, actor);
   P_SetTarget(&fog->tracer, actor->target);
   A_Fire(fog);
}

//
// A_VileAttack
//

void A_VileAttack(mobj_t *actor)
{
   mobj_t *fire;
   int    an;

   if (!actor->target)
      return;

   A_FaceTarget(actor);

   if (!P_CheckSight(actor, actor->target))
      return;

   S_StartSound(actor, sfx_barexp);
   P_DamageMobj(actor->target, actor, actor, 20);
   actor->target->momz = 1000*FRACUNIT/actor->target->info->mass;

   an = actor->angle >> ANGLETOFINESHIFT;

   fire = actor->tracer;

   if (!fire)
      return;

   // move the fire between the vile and the player
   fire->x = actor->target->x - FixedMul (24*FRACUNIT, finecosine[an]);
   fire->y = actor->target->y - FixedMul (24*FRACUNIT, finesine[an]);
   P_RadiusAttack(fire, actor, 70);
}

//
// Mancubus attack,
// firing three missiles (bruisers)
// in three different directions?
// Doesn't look like it.
//

#define FATSPREAD       (ANG90/8)

void A_FatRaise(mobj_t *actor)
{
   A_FaceTarget(actor);
   S_StartSound(actor, sfx_manatk);
}

void A_FatAttack1(mobj_t *actor)
{
   mobj_t *mo;
   int    an;

   A_FaceTarget(actor);

   // Change direction  to ...
   actor->angle += FATSPREAD;

   P_SpawnMissile(actor, actor->target, MT_FATSHOT);

   mo = P_SpawnMissile (actor, actor->target, MT_FATSHOT);
   mo->angle += FATSPREAD;
   an = mo->angle >> ANGLETOFINESHIFT;
   mo->momx = FixedMul(mo->info->speed, finecosine[an]);
   mo->momy = FixedMul(mo->info->speed, finesine[an]);
}

void A_FatAttack2(mobj_t *actor)
{
   mobj_t *mo;
   int    an;

   A_FaceTarget(actor);
   // Now here choose opposite deviation.
   actor->angle -= FATSPREAD;
   P_SpawnMissile(actor, actor->target, MT_FATSHOT);

   mo = P_SpawnMissile(actor, actor->target, MT_FATSHOT);
   mo->angle -= FATSPREAD*2;
   an = mo->angle >> ANGLETOFINESHIFT;
   mo->momx = FixedMul(mo->info->speed, finecosine[an]);
   mo->momy = FixedMul(mo->info->speed, finesine[an]);
}

void A_FatAttack3(mobj_t *actor)
{
   mobj_t *mo;
   int    an;

   A_FaceTarget(actor);

   mo = P_SpawnMissile(actor, actor->target, MT_FATSHOT);
   mo->angle -= FATSPREAD/2;
   an = mo->angle >> ANGLETOFINESHIFT;
   mo->momx = FixedMul(mo->info->speed, finecosine[an]);
   mo->momy = FixedMul(mo->info->speed, finesine[an]);

   mo = P_SpawnMissile(actor, actor->target, MT_FATSHOT);
   mo->angle += FATSPREAD/2;
   an = mo->angle >> ANGLETOFINESHIFT;
   mo->momx = FixedMul(mo->info->speed, finecosine[an]);
   mo->momy = FixedMul(mo->info->speed, finesine[an]);
}


//
// SkullAttack
// Fly at the player like a missile.
//
#define SKULLSPEED              (20*FRACUNIT)

void A_SkullAttack(mobj_t *actor)
{
   mobj_t  *dest;
   angle_t an;
   int     dist;

   if (!actor->target)
      return;

   dest = actor->target;
   actor->flags |= MF_SKULLFLY;

   S_StartSound(actor, actor->info->attacksound);
   A_FaceTarget(actor);
   an = actor->angle >> ANGLETOFINESHIFT;
   actor->momx = FixedMul(SKULLSPEED, finecosine[an]);
   actor->momy = FixedMul(SKULLSPEED, finesine[an]);
   dist = P_AproxDistance(dest->x - actor->x, dest->y - actor->y);
   dist = dist / SKULLSPEED;

   if (dist < 1)
      dist = 1;
   actor->momz = (dest->z+(dest->height>>1) - actor->z) / dist;
}

//
// A_PainShootSkull
// Spawn a lost soul and launch it at the target
//

void A_PainShootSkull(mobj_t *actor, angle_t angle)
{
   fixed_t       x,y,z;
   mobj_t        *newmobj;
   angle_t       an;
   int           prestep;

   // The original code checked for 20 skulls on the level,            // phares
   // and wouldn't spit another one if there were. If not in           // phares
   // compatibility mode, we remove the limit.                         // phares
   // phares
   if (comp[comp_pain]) /* killough 10/98: compatibility-optioned */
   {
      // count total number of skulls currently on the level
      int count = 0;
      thinker_t *currentthinker;
      for (currentthinker = thinkercap.next;
            currentthinker != &thinkercap;
            currentthinker = currentthinker->next)
         if ((currentthinker->function == P_MobjThinker)
               && ((mobj_t *)currentthinker)->type == MT_SKULL)
            count++;
      if (count > 20)                                               // phares
         return;                                                     // phares
   }

   // okay, there's room for another one

   an = angle >> ANGLETOFINESHIFT;

   prestep = 4*FRACUNIT + 3*(actor->info->radius + mobjinfo[MT_SKULL].radius)/2;

   x = actor->x + FixedMul(prestep, finecosine[an]);
   y = actor->y + FixedMul(prestep, finesine[an]);
   z = actor->z + 8*FRACUNIT;

   if (comp[comp_skull])   /* killough 10/98: compatibility-optioned */
      newmobj = P_SpawnMobj(x, y, z, MT_SKULL);                     // phares
   else                                                            //   V
   {
      // Check whether the Lost Soul is being fired through a 1-sided
      // wall or an impassible line, or a "monsters can't cross" line.
      // If it is, then we don't allow the spawn. This is a bug fix, but
      // it should be considered an enhancement, since it may disturb
      // existing demos, so don't do it in compatibility mode.

      if (Check_Sides(actor,x,y))
         return;

      newmobj = P_SpawnMobj(x, y, z, MT_SKULL);

      // Check to see if the new Lost Soul's z value is above the
      // ceiling of its new sector, or below the floor. If so, kill it.

      if ((newmobj->z >
            (newmobj->subsector->sector->ceilingheight - newmobj->height)) ||
            (newmobj->z < newmobj->subsector->sector->floorheight))
      {
         // kill it immediately
         P_DamageMobj(newmobj,actor,actor,10000);
         return;                                                 //   ^
      }                                                         //   |
   }                                                            // phares

   /* killough 7/20/98: PEs shoot lost souls with the same friendliness */
   newmobj->flags = (newmobj->flags & ~MF_FRIEND) | (actor->flags & MF_FRIEND);

   /* killough 8/29/98: add to appropriate thread */
   P_UpdateThinker(&newmobj->thinker);

   // Check for movements.
   // killough 3/15/98: don't jump over dropoffs:

   if (!P_TryMove(newmobj, newmobj->x, newmobj->y, false))
   {
      // kill it immediately
      P_DamageMobj(newmobj, actor, actor, 10000);
      return;
   }

   P_SetTarget(&newmobj->target, actor->target);
   A_SkullAttack(newmobj);
}

//
// A_PainAttack
// Spawn a lost soul and launch it at the target
//

void A_PainAttack(mobj_t *actor)
{
   if (!actor->target)
      return;
   A_FaceTarget(actor);
   A_PainShootSkull(actor, actor->angle);
}

void A_PainDie(mobj_t *actor)
{
   A_Fall(actor);
   A_PainShootSkull(actor, actor->angle+ANG90);
   A_PainShootSkull(actor, actor->angle+ANG180);
   A_PainShootSkull(actor, actor->angle+ANG270);
}

void A_Scream(mobj_t *actor)
{
   int sound;

   switch (actor->info->deathsound)
   {
   case 0:
      return;

   case sfx_podth1:
   case sfx_podth2:
   case sfx_podth3:
      sound = sfx_podth1 + P_Random(pr_scream)%3;
      break;

   case sfx_bgdth1:
   case sfx_bgdth2:
      sound = sfx_bgdth1 + P_Random(pr_scream)%2;
      break;

   default:
      sound = actor->info->deathsound;
      break;
   }

   // Check for bosses.
   if (actor->type==MT_SPIDER || actor->type == MT_CYBORG)
      S_StartSound(NULL, sound); // full volume
   else
      S_StartSound(actor, sound);
}

void A_XScream(mobj_t *actor)
{
   S_StartSound(actor, sfx_slop);
}

void A_Pain(mobj_t *actor)
{
   if (actor->info->painsound)
      S_StartSound(actor, actor->info->painsound);
}

void A_Fall(mobj_t *actor)
{
   // actor is on ground, it can be walked over
   actor->flags &= ~MF_SOLID;
}

//
// A_Explode
//
void A_Explode(mobj_t *thingy)
{
   P_RadiusAttack( thingy, thingy->target, 128 );
}

//
// A_BossDeath
// Possibly trigger special effects
// if on first boss level
//

void A_BossDeath(mobj_t *mo)
{
   thinker_t *th;
   line_t    junk;
   int       i;

   if (gamemode == commercial)
   {
      if (gamemap != 7)
         return;

      if ((mo->type != MT_FATSO)
            && (mo->type != MT_BABY))
         return;
   }
   else
   {
      switch(gameepisode)
      {
      case 1:
         if (gamemap != 8)
            return;

         if (mo->type != MT_BRUISER)
            return;
         break;

      case 2:
         if (gamemap != 8)
            return;

         if (mo->type != MT_CYBORG)
            return;
         break;

      case 3:
         if (gamemap != 8)
            return;

         if (mo->type != MT_SPIDER)
            return;

         break;

      case 4:
         switch(gamemap)
         {
         case 6:
            if (mo->type != MT_CYBORG)
               return;
            break;

         case 8:
            if (mo->type != MT_SPIDER)
               return;
            break;

         default:
            return;
            break;
         }
         break;

      default:
         if (gamemap != 8)
            return;
         break;
      }

   }

   // make sure there is a player alive for victory
   for (i=0; i<MAXPLAYERS; i++)
      if (playeringame[i] && players[i].health > 0)
         break;

   if (i==MAXPLAYERS)
      return;     // no one left alive, so do not end game

   // scan the remaining thinkers to see
   // if all bosses are dead
   for (th = thinkercap.next ; th != &thinkercap ; th=th->next)
      if (th->function == P_MobjThinker)
      {
         mobj_t *mo2 = (mobj_t *) th;
         if (mo2 != mo && mo2->type == mo->type && mo2->health > 0)
            return;         // other boss not dead
      }

   // victory!
   if ( gamemode == commercial)
   {
      if (gamemap == 7)
      {
         if (mo->type == MT_FATSO)
         {
            junk.tag = 666;
            EV_DoFloor(&junk,lowerFloorToLowest);
            return;
         }

         if (mo->type == MT_BABY)
         {
            junk.tag = 667;
            EV_DoFloor(&junk,raiseToTexture);
            return;
         }
      }
   }
   else
   {
      switch(gameepisode)
      {
      case 1:
         junk.tag = 666;
         EV_DoFloor(&junk, lowerFloorToLowest);
         return;
         break;

      case 4:
         switch(gamemap)
         {
         case 6:
            junk.tag = 666;
            EV_DoDoor(&junk, blazeOpen);
            return;
            break;

         case 8:
            junk.tag = 666;
            EV_DoFloor(&junk, lowerFloorToLowest);
            return;
            break;
         }
      }
   }
   G_ExitLevel();
}


void A_Hoof (mobj_t* mo)
{
   S_StartSound(mo, sfx_hoof);
   A_Chase(mo);
}

void A_Metal(mobj_t *mo)
{
   S_StartSound(mo, sfx_metal);
   A_Chase(mo);
}

void A_BabyMetal(mobj_t *mo)
{
   S_StartSound(mo, sfx_bspwlk);
   A_Chase(mo);
}

void A_OpenShotgun2(player_t *player, pspdef_t *psp)
{
   (void)psp;
   S_StartSound(player->mo, sfx_dbopn);
}

void A_LoadShotgun2(player_t *player, pspdef_t *psp)
{
   (void)psp;
   S_StartSound(player->mo, sfx_dbload);
}

void A_ReFire(player_t *player, pspdef_t *psp);

void A_CloseShotgun2(player_t *player, pspdef_t *psp)
{
   S_StartSound(player->mo, sfx_dbcls);
   A_ReFire(player,psp);
}

// killough 2/7/98: Remove limit on icon landings:
mobj_t **braintargets;
int    numbraintargets_alloc;
int    numbraintargets;

struct brain_s brain;   // killough 3/26/98: global state of boss brain

// killough 3/26/98: initialize icon landings at level startup,
// rather than at boss wakeup, to prevent savegame-related crashes

void P_SpawnBrainTargets(void)  // killough 3/26/98: renamed old function
{
   thinker_t *thinker;

   // find all the target spots
   numbraintargets = 0;
   brain.targeton = 0;
   brain.easy = 0;           // killough 3/26/98: always init easy to 0

   for (thinker = thinkercap.next ;
         thinker != &thinkercap ;
         thinker = thinker->next)
      if (thinker->function == P_MobjThinker)
      {
         mobj_t *m = (mobj_t *) thinker;

         if (m->type == MT_BOSSTARGET )
         {   // killough 2/7/98: remove limit on icon landings:
            if (numbraintargets >= numbraintargets_alloc)
               braintargets = realloc(braintargets,
                                      (numbraintargets_alloc = numbraintargets_alloc ?
                                                               numbraintargets_alloc*2 : 32) *sizeof *braintargets);
            braintargets[numbraintargets++] = m;
         }
      }
}

void A_BrainAwake(mobj_t *mo)
{
   (void)mo;
   S_StartSound(NULL,sfx_bossit); // killough 3/26/98: only generates sound now
}

void A_BrainPain(mobj_t *mo)
{
   (void)mo;
   S_StartSound(NULL,sfx_bospn);
}

void A_BrainScream(mobj_t *mo)
{
   int x;
   for (x=mo->x - 196*FRACUNIT ; x< mo->x + 320*FRACUNIT ; x+= FRACUNIT*8)
   {
      int y = mo->y - 320*FRACUNIT;
      int z = 128 + P_Random(pr_brainscream)*2*FRACUNIT;
      mobj_t *th = P_SpawnMobj (x,y,z, MT_ROCKET);
      th->momz = P_Random(pr_brainscream)*512;
      P_SetMobjState(th, S_BRAINEXPLODE1);
      th->tics -= P_Random(pr_brainscream)&7;
      if (th->tics < 1)
         th->tics = 1;
   }
   S_StartSound(NULL,sfx_bosdth);
}

void A_BrainExplode(mobj_t *mo)
{  // killough 5/5/98: remove dependence on order of evaluation:
   (void)mo;
   int t = P_Random(pr_brainexp);
   int x = mo->x + (t - P_Random(pr_brainexp))*2048;
   int y = mo->y;
   int z = 128 + P_Random(pr_brainexp)*2*FRACUNIT;
   mobj_t *th = P_SpawnMobj(x,y,z, MT_ROCKET);
   th->momz = P_Random(pr_brainexp)*512;
   P_SetMobjState(th, S_BRAINEXPLODE1);
   th->tics -= P_Random(pr_brainexp)&7;
   if (th->tics < 1)
      th->tics = 1;
}

void A_BrainDie(mobj_t *mo)
{
   (void)mo;
   G_ExitLevel();
}

void A_BrainSpit(mobj_t *mo)
{
   mobj_t *targ, *newmobj;

   if (!numbraintargets)     // killough 4/1/98: ignore if no targets
      return;

   brain.easy ^= 1;          // killough 3/26/98: use brain struct
   if (gameskill <= sk_easy && !brain.easy)
      return;

   // shoot a cube at current target
   targ = braintargets[brain.targeton++]; // killough 3/26/98:
   brain.targeton %= numbraintargets;     // Use brain struct for targets

   // spawn brain missile
   newmobj = P_SpawnMissile(mo, targ, MT_SPAWNSHOT);
   P_SetTarget(&newmobj->target, targ);
   newmobj->reactiontime = (short)(((targ->y-mo->y)/newmobj->momy)/newmobj->state->tics);

   // killough 7/18/98: brain friendliness is transferred
   newmobj->flags = (newmobj->flags & ~MF_FRIEND) | (mo->flags & MF_FRIEND);

   // killough 8/29/98: add to appropriate thread
   P_UpdateThinker(&newmobj->thinker);

   S_StartSound(NULL, sfx_bospit);
}

void A_SpawnFly(mobj_t *mo);

// travelling cube sound
void A_SpawnSound(mobj_t *mo)
{
   S_StartSound(mo,sfx_boscub);
   A_SpawnFly(mo);
}

void A_SpawnFly(mobj_t *mo)
{
   mobj_t *newmobj;
   mobj_t *fog;
   mobj_t *targ;
   int    r;
   mobjtype_t type;

   if (--mo->reactiontime)
      return;     // still flying

   targ = mo->target;

   // First spawn teleport fog.
   fog = P_SpawnMobj(targ->x, targ->y, targ->z, MT_SPAWNFIRE);
   S_StartSound(fog, sfx_telept);

   // Randomly select monster to spawn.
   r = P_Random(pr_spawnfly);

   // Probability distribution (kind of :), decreasing likelihood.
   if ( r<50 )
      type = MT_TROOP;
   else if (r<90)
      type = MT_SERGEANT;
   else if (r<120)
      type = MT_SHADOWS;
   else if (r<130)
      type = MT_PAIN;
   else if (r<160)
      type = MT_HEAD;
   else if (r<162)
      type = MT_VILE;
   else if (r<172)
      type = MT_UNDEAD;
   else if (r<192)
      type = MT_BABY;
   else if (r<222)
      type = MT_FATSO;
   else if (r<246)
      type = MT_KNIGHT;
   else
      type = MT_BRUISER;

   newmobj = P_SpawnMobj(targ->x, targ->y, targ->z, type);

   /* killough 7/18/98: brain friendliness is transferred */
   newmobj->flags = (newmobj->flags & ~MF_FRIEND) | (mo->flags & MF_FRIEND);

   /* killough 8/29/98: add to appropriate thread */
   P_UpdateThinker(&newmobj->thinker);

   if (P_LookForTargets(newmobj,true))      /* killough 9/4/98 */
      P_SetMobjState(newmobj, newmobj->info->seestate);

   // telefrag anything in this spot
   P_TeleportMove(newmobj, newmobj->x, newmobj->y, true); /* killough 8/9/98 */

   // remove self (i.e., cube).
   P_RemoveMobj(mo);
}

void A_PlayerScream(mobj_t *mo)
{
   int sound = sfx_pldeth;  // Default death sound.
   if (gamemode != shareware && mo->health < -50)
      sound = sfx_pdiehi;   // IF THE PLAYER DIES LESS THAN -50% WITHOUT GIBBING
   S_StartSound(mo, sound);
}

/* cph - MBF-added codepointer functions */

// killough 11/98: kill an object
void A_Die(mobj_t *actor)
{
   P_DamageMobj(actor, NULL, NULL, actor->health);
}

//
// A_Detonate
// killough 8/9/98: same as A_Explode, except that the damage is variable
//

void A_Detonate(mobj_t *mo)
{
   P_RadiusAttack(mo, mo->target, mo->info->damage);
}

//
// killough 9/98: a mushroom explosion effect, sorta :)
// Original idea: Linguica
//

void A_Mushroom(mobj_t *actor)
{
   int i, j, n = actor->info->damage;

   A_Explode(actor);  // First make normal explosion

   // Now launch mushroom cloud
   for (i = -n; i <= n; i += 8)
      for (j = -n; j <= n; j += 8)
      {
         mobj_t target = *actor, *mo;
         target.x += i << FRACBITS;    // Aim in many directions from source
         target.y += j << FRACBITS;
         target.z += P_AproxDistance(i,j) << (FRACBITS+2); // Aim up fairly high
         mo = P_SpawnMissile(actor, &target, MT_FATSHOT);  // Launch fireball
         mo->momx >>= 1;
         mo->momy >>= 1;                                   // Slow it down a bit
         mo->momz >>= 1;
         mo->flags &= ~MF_NOGRAVITY;   // Make debris fall under gravity
      }
}

//
// killough 11/98
//
// The following were inspired by Len Pitre
//
// A small set of highly-sought-after code pointers
//

void A_Spawn(mobj_t *mo)
{
   if (mo->state->misc1)
   {
      /* mobj_t *newmobj = */
      P_SpawnMobj(mo->x, mo->y, (mo->state->misc2 << FRACBITS) + mo->z,
                  mo->state->misc1 - 1);
      /* CPhipps - no friendlyness (yet)
      newmobj->flags = (newmobj->flags & ~MF_FRIEND) | (mo->flags & MF_FRIEND);
      */
   }
}

void A_Turn(mobj_t *mo)
{
   mo->angle += (unsigned int)(((uint_64_t) mo->state->misc1 << 32) / 360);
}

void A_Face(mobj_t *mo)
{
   mo->angle = (unsigned int)(((uint_64_t) mo->state->misc1 << 32) / 360);
}

void A_Scratch(mobj_t *mo)
{
   mo->target && (A_FaceTarget(mo), P_CheckMeleeRange(mo)) ?
   mo->state->misc2 ? S_StartSound(mo, mo->state->misc2) : (void) 0,
   P_DamageMobj(mo->target, mo, mo, mo->state->misc1) : (void) 0;
}

void A_PlaySound(mobj_t *mo)
{
   S_StartSound(mo->state->misc2 ? NULL : mo, mo->state->misc1);
}

void A_RandomJump(mobj_t *mo)
{
   if (P_Random(pr_randomjump) < mo->state->misc2)
      P_SetMobjState(mo, mo->state->misc1);
}

//
// This allows linedef effects to be activated inside deh frames.
//

void A_LineEffect(mobj_t *mo)
{
   static line_t junk;
   player_t player;
   player_t *oldplayer;
   junk = *lines;
   oldplayer = mo->player;
   mo->player = &player;
   player.health = 100;
   junk.special = (short)mo->state->misc1;
   if (!junk.special)
      return;
   junk.tag = (short)mo->state->misc2;
   if (!P_UseSpecialLine(mo, &junk, 0))
      P_CrossSpecialLine(&junk, 0, mo);
   mo->state->misc1 = junk.special;
   mo->player = oldplayer;
}

/***** Start of new functions for Andy Baker's stealth monsters ******/

void P_BecomeVisible(mobj_t* actor)
{
   actor->invisible = false;
   actor->flags &= ~MF_TRANSLUCBITS;
};

void P_IncreaseVisibility(mobj_t* actor)
{
   if (actor->invisible) {
      actor->invisible = false;
      actor->flags |= MF_TRANSLUC25;
   } else switch (actor->flags & MF_TRANSLUCBITS) {
      case MF_TRANSLUC25:
         actor->flags &= ~MF_TRANSLUCBITS;
         actor->flags |= MF_TRANSLUC50;
         break;
      case MF_TRANSLUC50:
         actor->flags &= ~MF_TRANSLUCBITS;
         actor->flags |= MF_TRANSLUC25;
         actor->flags |= MF_TRANSLUC50;
         break;
      case MF_TRANSLUC75:
         actor->flags &= ~MF_TRANSLUCBITS;
         break;
      }
}

void P_DecreaseVisibility(mobj_t* actor)
{
   if (actor->invisible)
      return;     // already invisible

   switch (actor->flags & MF_TRANSLUCBITS) {
   case 0:
      actor->flags &= ~MF_TRANSLUCBITS;
      actor->flags |= MF_TRANSLUC75;
      break;
   case MF_TRANSLUC75:
      actor->flags &= ~MF_TRANSLUCBITS;
      actor->flags |= MF_TRANSLUC50;
      break;
   case MF_TRANSLUC50:
      actor->flags &= ~MF_TRANSLUCBITS;
      actor->flags |= MF_TRANSLUC25;
      break;
   case MF_TRANSLUC25:
      actor->flags &= ~MF_TRANSLUCBITS;
      actor->invisible = true;
   }
}
/***** End of new functions for Andy Baker's stealth monsters ******/

