text: /* 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:
   *  Movement, collision handling.
   *  Shooting and aiming.
   *
   *-----------------------------------------------------------------------------*/
  
  #include "doomstat.h"
  #include "r_main.h"
  #include "p_mobj.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 "m_random.h"
  #include "m_bbox.h"
  #include "i_system.h"
  #include "rockmacros.h"
  
  static mobj_t    *tmthing;
  static fixed_t   tmx;
  static fixed_t   tmy;
  static int pe_x; // Pain Elemental position for Lost Soul checks // phares
  static int pe_y; // Pain Elemental position for Lost Soul checks // phares
  static int ls_x; // Lost Soul position for Lost Soul checks      // phares
  static int ls_y; // Lost Soul position for Lost Soul checks      // phares
  
  // If "floatok" true, move would be ok
  // if within "tmfloorz - tmceilingz".
  
  boolean   floatok;
  
  /* killough 11/98: if "felldown" true, object was pushed down ledge */
  boolean   felldown;
  
  // The tm* items are used to hold information globally, usually for
  // line or object intersection checking
  
  fixed_t   tmbbox[4];  // bounding box for line intersection checks
  fixed_t   tmfloorz;   // floor you'd hit if free to fall
  fixed_t   tmceilingz; // ceiling of sector you're in
  fixed_t   tmdropoffz; // dropoff on other side of line you're crossing
  
  // keep track of the line that lowers the ceiling,
  // so missiles don't explode against sky hack walls
  
  line_t    *ceilingline;
  line_t        *blockline;    /* killough 8/11/98: blocking linedef */
  line_t        *floorline;    /* killough 8/1/98: Highest touched floor */
  static int    tmunstuck;     /* killough 8/1/98: whether to allow unsticking */
  
  // keep track of special lines as they are hit,
  // but don't process them until the move is proven valid
  
  // 1/11/98 killough: removed limit on special lines crossed
  line_t **spechit;                // new code -- killough
  static int spechit_max;          // killough
  
  int numspechit;
  
  // Temporary holder for thing_sectorlist threads
  msecnode_t* sector_list = NULL;                             // phares 3/16/98
  
  //
  // TELEPORT MOVE
  //
  
  //
  // PIT_StompThing
  //
  
  static boolean telefrag;   /* killough 8/9/98: whether to telefrag at exit */
  
  boolean PIT_StompThing (mobj_t* thing)
  {
     fixed_t blockdist;
  
     // phares 9/10/98: moved this self-check to start of routine
  
     // don't clip against self
  
     if (thing == tmthing)
        return true;
  
     if (!(thing->flags & MF_SHOOTABLE)) // Can't shoot it? Can't stomp it!
        return true;
  
     blockdist = thing->radius + tmthing->radius;
  
     if (D_abs(thing->x - tmx) >= blockdist || D_abs(thing->y - tmy) >= blockdist)
        return true; // didn't hit it
  
     // monsters don't stomp things except on boss level
     if (!telefrag)  // killough 8/9/98: make consistent across all levels
        return false;
  
     P_DamageMobj (thing, tmthing, tmthing, 10000); // Stomp!
  
     return true;
  }
  
  
  /*
   * killough 8/28/98:
   *
   * P_GetFriction()
   *
   * Returns the friction associated with a particular mobj.
   */
  
  int P_GetFriction(const mobj_t *mo, int *frictionfactor)
  {
     int friction = ORIG_FRICTION;
     int movefactor = ORIG_FRICTION_FACTOR;
     const msecnode_t *m;
     const sector_t *sec;
  
     /* Assign the friction value to objects on the floor, non-floating,
      * and clipped. Normally the object's friction value is kept at
      * ORIG_FRICTION and this thinker changes it for icy or muddy floors.
      *
      * When the object is straddling sectors with the same
      * floorheight that have different frictions, use the lowest
      * friction value (muddy has precedence over icy).
      */
  
     if (!(mo->flags & (MF_NOCLIP|MF_NOGRAVITY))
           && (mbf_features || (mo->player && !compatibility)) &&
           variable_friction)
        for (m = mo->touching_sectorlist; m; m = m->m_tnext)
           if ((sec = m->m_sector)->special & FRICTION_MASK &&
                 (sec->friction < friction || friction == ORIG_FRICTION) &&
                 (mo->z <= sec->floorheight ||
                  (sec->heightsec != -1 &&
                   mo->z <= sectors[sec->heightsec].floorheight &&
                   mbf_features)))
              friction = sec->friction, movefactor = sec->movefactor;
  
     if (frictionfactor)
        *frictionfactor = movefactor;
  
     return friction;
  }
  
  /* phares 3/19/98
   * P_GetMoveFactor() returns the value by which the x,y
   * movements are multiplied to add to player movement.
   *
   * killough 8/28/98: rewritten
   */
  
  int P_GetMoveFactor(const mobj_t *mo, int *frictionp)
  {
     int movefactor, friction;
  
     // If the floor is icy or muddy, it's harder to get moving. This is where
     // the different friction factors are applied to 'trying to move'. In
     // p_mobj.c, the friction factors are applied as you coast and slow down.
  
     if ((friction = P_GetFriction(mo, &movefactor)) < ORIG_FRICTION)
     {
        // phares 3/11/98: you start off slowly, then increase as
        // you get better footing
  
        int momentum = P_AproxDistance(mo->momx,mo->momy);
  
        if (momentum > MORE_FRICTION_MOMENTUM<<2)
           movefactor <<= 3;
        else if (momentum > MORE_FRICTION_MOMENTUM<<1)
           movefactor <<= 2;
        else if (momentum > MORE_FRICTION_MOMENTUM)
           movefactor <<= 1;
     }
  
     if (frictionp)
        *frictionp = friction;
  
     return movefactor;
  }
  
  //
  // P_TeleportMove
  //
  
  boolean P_TeleportMove (mobj_t* thing,fixed_t x,fixed_t y, boolean boss)
  {
     int     xl;
     int     xh;
     int     yl;
     int     yh;
     int     bx;
     int     by;
  
     subsector_t*  newsubsec;
  
     /* killough 8/9/98: make telefragging more consistent, preserve compatibility */
     telefrag = thing->player ||
                (!comp[comp_telefrag] ? boss : (gamemap==30));
  
     // kill anything occupying the position
  
     tmthing = thing;
  
     tmx = x;
     tmy = y;
  
     tmbbox[BOXTOP] = y + tmthing->radius;
     tmbbox[BOXBOTTOM] = y - tmthing->radius;
     tmbbox[BOXRIGHT] = x + tmthing->radius;
     tmbbox[BOXLEFT] = x - tmthing->radius;
  
     newsubsec = R_PointInSubsector (x,y);
     ceilingline = NULL;
  
     // The base floor/ceiling is from the subsector
     // that contains the point.
     // Any contacted lines the step closer together
     // will adjust them.
  
     tmfloorz = tmdropoffz = newsubsec->sector->floorheight;
     tmceilingz = newsubsec->sector->ceilingheight;
  
     validcount++;
     numspechit = 0;
  
     // stomp on any things contacted
  
     xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT;
     xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT;
     yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT;
     yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT;
  
     for (bx=xl ; bx<=xh ; bx++)
        for (by=yl ; by<=yh ; by++)
           if (!P_BlockThingsIterator(bx,by,PIT_StompThing))
              return false;
  
     // the move is ok,
     // so unlink from the old position & link into the new position
  
     P_UnsetThingPosition (thing);
  
     thing->floorz = tmfloorz;
     thing->ceilingz = tmceilingz;
     thing->dropoffz = tmdropoffz;        // killough 11/98
  
     thing->x = x;
     thing->y = y;
  
     P_SetThingPosition (thing);
  
     return true;
  }
  
  
  //
  // MOVEMENT ITERATOR FUNCTIONS
  //
  
  //                                                                  // phares
  // PIT_CrossLine                                                    //   |
  // Checks to see if a PE->LS trajectory line crosses a blocking     //   V
  // line. Returns false if it does.
  //
  // tmbbox holds the bounding box of the trajectory. If that box
  // does not touch the bounding box of the line in question,
  // then the trajectory is not blocked. If the PE is on one side
  // of the line and the LS is on the other side, then the
  // trajectory is blocked.
  //
  // Currently this assumes an infinite line, which is not quite
  // correct. A more correct solution would be to check for an
  // intersection of the trajectory and the line, but that takes
  // longer and probably really isn't worth the effort.
  //
  
  static // killough 3/26/98: make static
  boolean PIT_CrossLine (line_t* ld)
  {
     if (!(ld->flags & ML_TWOSIDED) ||
           (ld->flags & (ML_BLOCKING|ML_BLOCKMONSTERS)))
        if (!(tmbbox[BOXLEFT]   > ld->bbox[BOXRIGHT]  ||
              tmbbox[BOXRIGHT]  < ld->bbox[BOXLEFT]   ||
              tmbbox[BOXTOP]    < ld->bbox[BOXBOTTOM] ||
              tmbbox[BOXBOTTOM] > ld->bbox[BOXTOP]))
           if (P_PointOnLineSide(pe_x,pe_y,ld) != P_PointOnLineSide(ls_x,ls_y,ld))
              return(false);  // line blocks trajectory                   //   ^
     return(true); // line doesn't block trajectory                    //   |
  }                                                                 // phares
  
  
  /* killough 8/1/98: used to test intersection between thing and line
   * assuming NO movement occurs -- used to avoid sticky situations.
   */
  
  static int untouched(line_t *ld)
  {
     fixed_t x, y, tmbbox[4];
     return
        (tmbbox[BOXRIGHT] = (x=tmthing->x)+tmthing->radius) <= ld->bbox[BOXLEFT] ||
        (tmbbox[BOXLEFT] = x-tmthing->radius) >= ld->bbox[BOXRIGHT] ||
        (tmbbox[BOXTOP] = (y=tmthing->y)+tmthing->radius) <= ld->bbox[BOXBOTTOM] ||
        (tmbbox[BOXBOTTOM] = y-tmthing->radius) >= ld->bbox[BOXTOP] ||
        P_BoxOnLineSide(tmbbox, ld) != -1;
  }
  
  //
  // PIT_CheckLine
  // Adjusts tmfloorz and tmceilingz as lines are contacted
  //
  
  static // killough 3/26/98: make static
  boolean PIT_CheckLine (line_t* ld)
  {
     if (tmbbox[BOXRIGHT] <= ld->bbox[BOXLEFT]
           || tmbbox[BOXLEFT] >= ld->bbox[BOXRIGHT]
           || tmbbox[BOXTOP] <= ld->bbox[BOXBOTTOM]
           || tmbbox[BOXBOTTOM] >= ld->bbox[BOXTOP] )
        return true; // didn't hit it
  
     if (P_BoxOnLineSide(tmbbox, ld) != -1)
        return true; // didn't hit it
  
     // A line has been hit
  
     // The moving thing's destination position will cross the given line.
     // If this should not be allowed, return false.
     // If the line is special, keep track of it
     // to process later if the move is proven ok.
     // NOTE: specials are NOT sorted by order,
     // so two special lines that are only 8 pixels apart
     // could be crossed in either order.
  
     // killough 7/24/98: allow player to move out of 1s wall, to prevent sticking
     if (!ld->backsector) // one sided line
     {
        blockline = ld;
        return tmunstuck && !untouched(ld) &&
               FixedMul(tmx-tmthing->x,ld->dy) > FixedMul(tmy-tmthing->y,ld->dx);
     }
  
     // killough 8/10/98: allow bouncing objects to pass through as missiles
     if (!(tmthing->flags & (MF_MISSILE | MF_BOUNCES)))
     {
        if (ld->flags & ML_BLOCKING)           // explicitly blocking everything
           return tmunstuck && !untouched(ld);  // killough 8/1/98: allow escape
  
        // killough 8/9/98: monster-blockers don't affect friends
        if (!(tmthing->flags & MF_FRIEND || tmthing->player)
              && ld->flags & ML_BLOCKMONSTERS)
           return false; // block monsters only
     }
  
     // set openrange, opentop, openbottom
     // these define a 'window' from one sector to another across this line
  
     P_LineOpening (ld);
  
     // adjust floor & ceiling heights
  
     if (opentop < tmceilingz)
     {
        tmceilingz = opentop;
        ceilingline = ld;
        blockline = ld;
     }
  
     if (openbottom > tmfloorz)
     {
        tmfloorz = openbottom;
        floorline = ld;          // killough 8/1/98: remember floor linedef
        blockline = ld;
     }
  
     if (lowfloor < tmdropoffz)
        tmdropoffz = lowfloor;
  
     // if contacted a special line, add it to the list
  
     if (ld->special)
     {
        // 1/11/98 killough: remove limit on lines hit, by array doubling
        if (numspechit >= spechit_max)
        {
           spechit_max = spechit_max ? spechit_max*2 : 8;
           spechit = realloc(spechit,sizeof *spechit*spechit_max); // killough
        }
        spechit[numspechit++] = ld;
     }
  
     return true;
  }
  
  //
  // PIT_CheckThing
  //
  
  static boolean PIT_CheckThing(mobj_t *thing) // killough 3/26/98: make static
  {
     fixed_t blockdist;
     int damage;
  
     // killough 11/98: add touchy things
     if (!(thing->flags & (MF_SOLID|MF_SPECIAL|MF_SHOOTABLE|MF_TOUCHY)))
        return true;
  
     blockdist = thing->radius + tmthing->radius;
  
     if (D_abs(thing->x - tmx) >= blockdist || D_abs(thing->y - tmy) >= blockdist)
        return true; // didn't hit it
  
     // killough 11/98:
     //
     // This test has less information content (it's almost always false), so it
     // should not be moved up to first, as it adds more overhead than it removes.
  
     // don't clip against self
  
     if (thing == tmthing)
        return true;
  
     /* killough 11/98:
      *
      * TOUCHY flag, for mines or other objects which die on contact with solids.
      * If a solid object of a different type comes in contact with a touchy
      * thing, and the touchy thing is not the sole one moving relative to fixed
      * surroundings such as walls, then the touchy thing dies immediately.
      */
  
     if (thing->flags & MF_TOUCHY &&                  // touchy object
           tmthing->flags & MF_SOLID &&                 // solid object touches it
           thing->health > 0 &&                         // touchy object is alive
           (thing->intflags & MIF_ARMED ||              // Thing is an armed mine
            sentient(thing)) &&                         // ... or a sentient thing
           (thing->type != tmthing->type ||             // only different species
            thing->type == MT_PLAYER) &&                // ... or different players
           thing->z + thing->height >= tmthing->z &&    // touches vertically
           tmthing->z + tmthing->height >= thing->z &&
           (thing->type ^ MT_PAIN) |                    // PEs and lost souls
           (tmthing->type ^ MT_SKULL) &&                // are considered same
           (thing->type ^ MT_SKULL) |                   // (but Barons & Knights
           (tmthing->type ^ MT_PAIN))                   // are intentionally not)
     {
        P_DamageMobj(thing, NULL, NULL, thing->health);  // kill object
        return true;
     }
  
     // check for skulls slamming into things
  
     if (tmthing->flags & MF_SKULLFLY)
     {
        // A flying skull is smacking something.
        // Determine damage amount, and the skull comes to a dead stop.
  
        int damage = ((P_Random(pr_skullfly)%8)+1)*tmthing->info->damage;
  
        P_DamageMobj (thing, tmthing, tmthing, damage);
  
        tmthing->flags &= ~MF_SKULLFLY;
        tmthing->momx = tmthing->momy = tmthing->momz = 0;
  
        P_SetMobjState (tmthing, tmthing->info->spawnstate);
  
        return false;   // stop moving
     }
  
     // missiles can hit other things
     // killough 8/10/98: bouncing non-solid things can hit other things too
  
     if (tmthing->flags & MF_MISSILE || (tmthing->flags & MF_BOUNCES &&
                                         !(tmthing->flags & MF_SOLID)))
     {
        // see if it went over / under
  
        if (tmthing->z > thing->z + thing->height)
           return true;    // overhead
  
        if (tmthing->z+tmthing->height < thing->z)
           return true;    // underneath
  
        if (tmthing->target && (tmthing->target->type == thing->type ||
                                (tmthing->target->type == MT_KNIGHT && thing->type == MT_BRUISER)||
                                (tmthing->target->type == MT_BRUISER && thing->type == MT_KNIGHT)))
        {
           if (thing == tmthing->target)
              return true;                // Don't hit same species as originator.
           else
              if (thing->type != MT_PLAYER) // Explode, but do no damage.
                 return false;         // Let players missile other players.
        }
  
        // killough 8/10/98: if moving thing is not a missile, no damage
        // is inflicted, and momentum is reduced if object hit is solid.
  
        if (!(tmthing->flags & MF_MISSILE)) {
           if (!(thing->flags & MF_SOLID)) {
              return true;
           } else {
              tmthing->momx = -tmthing->momx;
              tmthing->momy = -tmthing->momy;
              if (!(tmthing->flags & MF_NOGRAVITY))
              {
                 tmthing->momx >>= 2;
                 tmthing->momy >>= 2;
              }
              return false;
           }
        }
  
        if (!(thing->flags & MF_SHOOTABLE))
           return !(thing->flags & MF_SOLID); // didn't do any damage
  
        // damage / explode
  
        damage = ((P_Random(pr_damage)%8)+1)*tmthing->info->damage;
        P_DamageMobj (thing, tmthing, tmthing->target, damage);
  
        // don't traverse any more
        return false;
     }
  
     // check for special pickup
  
     if (thing->flags & MF_SPECIAL)
     {
        uint_64_t solid = thing->flags & MF_SOLID;
        if (tmthing->flags & MF_PICKUP)
           P_TouchSpecialThing(thing, tmthing); // can remove thing
        return !solid;
     }
  
     // killough 3/16/98: Allow non-solid moving objects to move through solid
     // ones, by allowing the moving thing (tmthing) to move if it's non-solid,
     // despite another solid thing being in the way.
     // killough 4/11/98: Treat no-clipping things as not blocking
  
     return !((thing->flags & MF_SOLID && !(thing->flags & MF_NOCLIP))
              && (tmthing->flags & MF_SOLID || demo_compatibility));
  
     // return !(thing->flags & MF_SOLID);   // old code -- killough
  }
  
  // This routine checks for Lost Souls trying to be spawned      // phares
  // across 1-sided lines, impassible lines, or "monsters can't   //   |
  // cross" lines. Draw an imaginary line between the PE          //   V
  // and the new Lost Soul spawn spot. If that line crosses
  // a 'blocking' line, then disallow the spawn. Only search
  // lines in the blocks of the blockmap where the bounding box
  // of the trajectory line resides. Then check bounding box
  // of the trajectory vs. the bounding box of each blocking
  // line to see if the trajectory and the blocking line cross.
  // Then check the PE and LS to see if they're on different
  // sides of the blocking line. If so, return true, otherwise
  // false.
  
  boolean Check_Sides(mobj_t* actor, int x, int y)
  {
     int bx,by,xl,xh,yl,yh;
  
     pe_x = actor->x;
     pe_y = actor->y;
     ls_x = x;
     ls_y = y;
  
     // Here is the bounding box of the trajectory
  
     tmbbox[BOXLEFT]   = pe_x < x ? pe_x : x;
     tmbbox[BOXRIGHT]  = pe_x > x ? pe_x : x;
     tmbbox[BOXTOP]    = pe_y > y ? pe_y : y;
     tmbbox[BOXBOTTOM] = pe_y < y ? pe_y : y;
  
     // Determine which blocks to look in for blocking lines
  
     xl = (tmbbox[BOXLEFT]   - bmaporgx)>>MAPBLOCKSHIFT;
     xh = (tmbbox[BOXRIGHT]  - bmaporgx)>>MAPBLOCKSHIFT;
     yl = (tmbbox[BOXBOTTOM] - bmaporgy)>>MAPBLOCKSHIFT;
     yh = (tmbbox[BOXTOP]    - bmaporgy)>>MAPBLOCKSHIFT;
  
     // xl->xh, yl->yh determine the mapblock set to search
  
     validcount++; // prevents checking same line twice
     for (bx = xl ; bx <= xh ; bx++)
        for (by = yl ; by <= yh ; by++)
           if (!P_BlockLinesIterator(bx,by,PIT_CrossLine))
              return true;                                                //   ^
     return(false);                                                    //   |
  }                                                                 // phares
  
  //
  // MOVEMENT CLIPPING
  //
  
  //
  // P_CheckPosition
  // This is purely informative, nothing is modified
  // (except things picked up).
  //
  // in:
  //  a mobj_t (can be valid or invalid)
  //  a position to be checked
  //   (doesn't need to be related to the mobj_t->x,y)
  //
  // during:
  //  special things are touched if MF_PICKUP
  //  early out on solid lines?
  //
  // out:
  //  newsubsec
  //  floorz
  //  ceilingz
  //  tmdropoffz
  //   the lowest point contacted
  //   (monsters won't move to a dropoff)
  //  speciallines[]
  //  numspeciallines
  //
  
  boolean P_CheckPosition (mobj_t* thing,fixed_t x,fixed_t y)
  {
     int     xl;
     int     xh;
     int     yl;
     int     yh;
     int     bx;
     int     by;
     subsector_t*  newsubsec;
  
     tmthing = thing;
  
     tmx = x;
     tmy = y;
  
     tmbbox[BOXTOP] = y + tmthing->radius;
     tmbbox[BOXBOTTOM] = y - tmthing->radius;
     tmbbox[BOXRIGHT] = x + tmthing->radius;
     tmbbox[BOXLEFT] = x - tmthing->radius;
  
     newsubsec = R_PointInSubsector (x,y);
     floorline = blockline = ceilingline = NULL; // killough 8/1/98
  
     // Whether object can get out of a sticky situation:
     tmunstuck = thing->player &&          /* only players */
                 thing->player->mo == thing &&       /* not voodoo dolls */
                 mbf_features; /* not under old demos */
  
     // The base floor / ceiling is from the subsector
     // that contains the point.
     // Any contacted lines the step closer together
     // will adjust them.
  
     tmfloorz = tmdropoffz = newsubsec->sector->floorheight;
     tmceilingz = newsubsec->sector->ceilingheight;
     validcount++;
     numspechit = 0;
  
     if ( tmthing->flags & MF_NOCLIP )
        return true;
  
     // Check things first, possibly picking things up.
     // The bounding box is extended by MAXRADIUS
     // because mobj_ts are grouped into mapblocks
     // based on their origin point, and can overlap
     // into adjacent blocks by up to MAXRADIUS units.
  
     xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT;
     xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT;
     yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT;
     yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT;
  
  
     for (bx=xl ; bx<=xh ; bx++)
        for (by=yl ; by<=yh ; by++)
           if (!P_BlockThingsIterator(bx,by,PIT_CheckThing))
              return false;
  
     // check lines
  
     xl = (tmbbox[BOXLEFT] - bmaporgx)>>MAPBLOCKSHIFT;
     xh = (tmbbox[BOXRIGHT] - bmaporgx)>>MAPBLOCKSHIFT;
     yl = (tmbbox[BOXBOTTOM] - bmaporgy)>>MAPBLOCKSHIFT;
     yh = (tmbbox[BOXTOP] - bmaporgy)>>MAPBLOCKSHIFT;
  
     for (bx=xl ; bx<=xh ; bx++)
        for (by=yl ; by<=yh ; by++)
           if (!P_BlockLinesIterator (bx,by,PIT_CheckLine))
              return false; // doesn't fit
  
     return true;
  }
  
  
  //
  // P_TryMove
  // Attempt to move to a new position,
  // crossing special lines unless MF_TELEPORT is set.
  //
  boolean P_TryMove(mobj_t* thing,fixed_t x,fixed_t y,
                    boolean dropoff) // killough 3/15/98: allow dropoff as option
  {
     fixed_t oldx;
     fixed_t oldy;
  
     felldown = floatok = false;               // killough 11/98
  
     if (!P_CheckPosition (thing, x, y))
        return false;   // solid wall or thing
  
     if ( !(thing->flags & MF_NOCLIP) )
     {
        // killough 7/26/98: reformatted slightly
        // killough 8/1/98: Possibly allow escape if otherwise stuck
  
        if (tmceilingz - tmfloorz < thing->height ||     // doesn't fit
              // mobj must lower to fit
              (floatok = true, !(thing->flags & MF_TELEPORT) &&
               tmceilingz - thing->z < thing->height) ||
              // too big a step up
              (!(thing->flags & MF_TELEPORT) &&
               tmfloorz - thing->z > 24*FRACUNIT))
           return tmunstuck
                  && !(ceilingline && untouched(ceilingline))
                  && !(  floorline && untouched(  floorline));
  
        /* killough 3/15/98: Allow certain objects to drop off
         * killough 7/24/98, 8/1/98:
         * Prevent monsters from getting stuck hanging off ledges
         * killough 10/98: Allow dropoffs in controlled circumstances
         * killough 11/98: Improve symmetry of clipping on stairs
         */
  
        if (!(thing->flags & (MF_DROPOFF|MF_FLOAT))) {
           if (comp[comp_dropoff])
           {
              if ((compatibility || !dropoff) && (tmfloorz - tmdropoffz > 24*FRACUNIT))
                 return false;                      // don't stand over a dropoff
           }
           else
              if (!dropoff || (dropoff==2 &&  // large jump down (e.g. dogs)
                               (tmfloorz-tmdropoffz > 128*FRACUNIT ||
                                !thing->target || thing->target->z >tmdropoffz)))
              {
                 if (!monkeys || !mbf_features ?
                       tmfloorz - tmdropoffz > 24*FRACUNIT :
                       thing->floorz  - tmfloorz > 24*FRACUNIT ||
                       thing->dropoffz - tmdropoffz > 24*FRACUNIT)
                    return false;
              }
              else { /* dropoff allowed -- check for whether it fell more than 24 */
                 felldown = !(thing->flags & MF_NOGRAVITY) &&
                            thing->z - tmfloorz > 24*FRACUNIT;
              }
        }
  
        if (thing->flags & MF_BOUNCES &&    // killough 8/13/98
              !(thing->flags & (MF_MISSILE|MF_NOGRAVITY)) &&
              !sentient(thing) && tmfloorz - thing->z > 16*FRACUNIT)
           return false; // too big a step up for bouncers under gravity
  
        // killough 11/98: prevent falling objects from going up too many steps
        if (thing->intflags & MIF_FALLING && tmfloorz - thing->z >
              FixedMul(thing->momx,thing->momx)+FixedMul(thing->momy,thing->momy))
           return false;
     }
  
     // the move is ok,
     // so unlink from the old position and link into the new position
  
     P_UnsetThingPosition (thing);
  
     oldx = thing->x;
     oldy = thing->y;
     thing->floorz = tmfloorz;
     thing->ceilingz = tmceilingz;
     thing->dropoffz = tmdropoffz;      // killough 11/98: keep track of dropoffs
     thing->x = x;
     thing->y = y;
  
     P_SetThingPosition (thing);
  
     // if any special lines were hit, do the effect
  
     if (! (thing->flags&(MF_TELEPORT|MF_NOCLIP)) )
        while (numspechit--)
           if (spechit[numspechit]->special)  // see if the line was crossed
           {
              int oldside;
              if ((oldside = P_PointOnLineSide(oldx, oldy, spechit[numspechit])) !=
                    P_PointOnLineSide(thing->x, thing->y, spechit[numspechit]))
                 P_CrossSpecialLine(spechit[numspechit], oldside, thing);
           }
  
     return true;
  }
  
  /*
   * killough 9/12/98:
   *
   * Apply "torque" to objects hanging off of ledges, so that they
   * fall off. It's not really torque, since Doom has no concept of
   * rotation, but it's a convincing effect which avoids anomalies
   * such as lifeless objects hanging more than halfway off of ledges,
   * and allows objects to roll off of the edges of moving lifts, or
   * to slide up and then back down stairs, or to fall into a ditch.
   * If more than one linedef is contacted, the effects are cumulative,
   * so balancing is possible.
   */
  
  static boolean PIT_ApplyTorque(line_t *ld)
  {
     if (ld->backsector &&       // If thing touches two-sided pivot linedef
           tmbbox[BOXRIGHT]  > ld->bbox[BOXLEFT]  &&
           tmbbox[BOXLEFT]   < ld->bbox[BOXRIGHT] &&
           tmbbox[BOXTOP]    > ld->bbox[BOXBOTTOM] &&
           tmbbox[BOXBOTTOM] < ld->bbox[BOXTOP] &&
           P_BoxOnLineSide(tmbbox, ld) == -1)
     {
        mobj_t *mo = tmthing;
  
        fixed_t dist =                               // lever arm
           + (ld->dx >> FRACBITS) * (mo->y >> FRACBITS)
           - (ld->dy >> FRACBITS) * (mo->x >> FRACBITS)
           - (ld->dx >> FRACBITS) * (ld->v1->y >> FRACBITS)
           + (ld->dy >> FRACBITS) * (ld->v1->x >> FRACBITS);
  
        if (dist < 0 ?                               // dropoff direction
              ld->frontsector->floorheight < mo->z &&
              ld->backsector->floorheight >= mo->z :
              ld->backsector->floorheight < mo->z &&
              ld->frontsector->floorheight >= mo->z)
        {
           /* At this point, we know that the object straddles a two-sided
            * linedef, and that the object's center of mass is above-ground.
            */
  
           fixed_t x = D_abs(ld->dx), y = D_abs(ld->dy);
  
           if (y > x)
           {
              fixed_t t = x;
              x = y;
              y = t;
           }
  
           y = finesine[(tantoangle[FixedDiv(y,x)>>DBITS] +
                         ANG90) >> ANGLETOFINESHIFT];
  
           /* Momentum is proportional to distance between the
            * object's center of mass and the pivot linedef.
            *
            * It is scaled by 2^(OVERDRIVE - gear). When gear is
            * increased, the momentum gradually decreases to 0 for
            * the same amount of pseudotorque, so that oscillations
            * are prevented, yet it has a chance to reach equilibrium.
            */
           dist = FixedDiv(FixedMul(dist, (mo->gear < OVERDRIVE) ?
                                    y << -(mo->gear - OVERDRIVE) :
                                    y >> +(mo->gear - OVERDRIVE)), x);
  
           /* Apply momentum away from the pivot linedef. */
  
           x = FixedMul(ld->dy, dist);
           y = FixedMul(ld->dx, dist);
  
           /* Avoid moving too fast all of a sudden (step into "overdrive") */
  
           dist = FixedMul(x,x) + FixedMul(y,y);
  
           while (dist > FRACUNIT*4 && mo->gear < MAXGEAR)
              ++mo->gear, x >>= 1, y >>= 1, dist >>= 1;
  
           mo->momx -= x;
           mo->momy += y;
        }
     }
     return true;
  }
  
  /*
   * killough 9/12/98
   *
   * Applies "torque" to objects, based on all contacted linedefs
   */
  
  void P_ApplyTorque(mobj_t *mo)
  {
     int xl = ((tmbbox[BOXLEFT] =
                   mo->x - mo->radius) - bmaporgx) >> MAPBLOCKSHIFT;
     int xh = ((tmbbox[BOXRIGHT] =
                   mo->x + mo->radius) - bmaporgx) >> MAPBLOCKSHIFT;
     int yl = ((tmbbox[BOXBOTTOM] =
                   mo->y - mo->radius) - bmaporgy) >> MAPBLOCKSHIFT;
     int yh = ((tmbbox[BOXTOP] =
                   mo->y + mo->radius) - bmaporgy) >> MAPBLOCKSHIFT;
     int bx,by,flags = mo->intflags; //Remember the current state, for gear-change
  
     tmthing = mo;
     validcount++; /* prevents checking same line twice */
  
     for (bx = xl ; bx <= xh ; bx++)
        for (by = yl ; by <= yh ; by++)
           P_BlockLinesIterator(bx, by, PIT_ApplyTorque);
  
     /* If any momentum, mark object as 'falling' using engine-internal flags */
     if (mo->momx | mo->momy)
        mo->intflags |= MIF_FALLING;
     else  // Clear the engine-internal flag indicating falling object.
        mo->intflags &= ~MIF_FALLING;
  
     /* If the object has been moving, step up the gear.
      * This helps reach equilibrium and avoid oscillations.
      *
      * Doom has no concept of potential energy, much less
      * of rotation, so we have to creatively simulate these
      * systems somehow :)
      */
  
     if (!((mo->intflags | flags) & MIF_FALLING))   // If not falling for a while,
        mo->gear = 0;                                // Reset it to full strength
     else
        if (mo->gear < MAXGEAR)                      // Else if not at max gear,
           mo->gear++;                                // move up a gear
  }
  
  //
  // P_ThingHeightClip
  // Takes a valid thing and adjusts the thing->floorz,
  // thing->ceilingz, and possibly thing->z.
  // This is called for all nearby monsters
  // whenever a sector changes height.
  // If the thing doesn't fit,
  // the z will be set to the lowest value
  // and false will be returned.
  //
  
  boolean P_ThingHeightClip (mobj_t* thing)
  {
     boolean   onfloor;
  
     onfloor = (thing->z == thing->floorz);
  
     P_CheckPosition (thing, thing->x, thing->y);
  
     /* what about stranding a monster partially off an edge?
      * killough 11/98: Answer: see below (upset balance if hanging off ledge)
      */
  
     thing->floorz = tmfloorz;
     thing->ceilingz = tmceilingz;
     thing->dropoffz = tmdropoffz;    /* killough 11/98: remember dropoffs */
  
     if (onfloor)
     {
  
        // walking monsters rise and fall with the floor
  
        thing->z = thing->floorz;
  
        /* killough 11/98: Possibly upset balance of objects hanging off ledges */
        if (thing->intflags & MIF_FALLING && thing->gear >= MAXGEAR)
           thing->gear = 0;
     }
     else
     {
  
        // don't adjust a floating monster unless forced to
  
        if (thing->z+thing->height > thing->ceilingz)
           thing->z = thing->ceilingz - thing->height;
     }
  
     return thing->ceilingz - thing->floorz >= thing->height;
  }
  
  
  //
  // SLIDE MOVE
  // Allows the player to slide along any angled walls.
  //
  
  /* killough 8/2/98: make variables static */
  static fixed_t   bestslidefrac;
  static fixed_t   secondslidefrac;
  static line_t*   bestslideline;
  static line_t*   secondslideline;
  static mobj_t*   slidemo;
  static fixed_t   tmxmove;
  static fixed_t   tmymove;
  
  
  //
  // P_HitSlideLine
  // Adjusts the xmove / ymove
  // so that the next move will slide along the wall.
  // If the floor is icy, then you can bounce off a wall.             // phares
  //
  
  void P_HitSlideLine (line_t* ld)
  {
     int     side;
     angle_t lineangle;
     angle_t moveangle;
     angle_t deltaangle;
     fixed_t movelen;
     fixed_t newlen;
     boolean icyfloor;  // is floor icy?                               // phares
     //   |
     // Under icy conditions, if the angle of approach to the wall     //   V
     // is more than 45 degrees, then you'll bounce and lose half
     // your momentum. If less than 45 degrees, you'll slide along
     // the wall. 45 is arbitrary and is believable.
  
     // Check for the special cases of horz or vert walls.
  
     /* killough 10/98: only bounce if hit hard (prevents wobbling)
      * cph - DEMOSYNC - should only affect players in Boom demos? */
     icyfloor =
        (mbf_features ?
         P_AproxDistance(tmxmove, tmymove) > 4*FRACUNIT : !compatibility) &&
        variable_friction &&  // killough 8/28/98: calc friction on demand
        slidemo->z <= slidemo->floorz &&
        P_GetFriction(slidemo, NULL) > ORIG_FRICTION;
  
     if (ld->slopetype == ST_HORIZONTAL)
     {
        if (icyfloor && (D_abs(tmymove) > D_abs(tmxmove)))
        {
           tmxmove /= 2; // absorb half the momentum
           tmymove = -tmymove/2;
           S_StartSound(slidemo,sfx_oof); // oooff!
        }
        else
           tmymove = 0; // no more movement in the Y direction
        return;
     }
  
     if (ld->slopetype == ST_VERTICAL)
     {
        if (icyfloor && (D_abs(tmxmove) > D_abs(tmymove)))
        {
           tmxmove = -tmxmove/2; // absorb half the momentum
           tmymove /= 2;
           S_StartSound(slidemo,sfx_oof); // oooff!                      //   ^
        }                                                             //   |
        else                                                            // phares
           tmxmove = 0; // no more movement in the X direction
        return;
     }
  
     // The wall is angled. Bounce if the angle of approach is         // phares
     // less than 45 degrees.                                          // phares
  
     side = P_PointOnLineSide (slidemo->x, slidemo->y, ld);
  
     lineangle = R_PointToAngle2 (0,0, ld->dx, ld->dy);
     if (side == 1)
        lineangle += ANG180;
     moveangle = R_PointToAngle2 (0,0, tmxmove, tmymove);
  
     // killough 3/2/98:
     // The moveangle+=10 breaks v1.9 demo compatibility in
     // some demos, so it needs demo_compatibility switch.
  
     if (!demo_compatibility)
        moveangle += 10; // prevents sudden path reversal due to        // phares
     // rounding error                              //   |
     deltaangle = moveangle-lineangle;                                 //   V
     movelen = P_AproxDistance (tmxmove, tmymove);
     if (icyfloor && (deltaangle > ANG45) && (deltaangle < ANG90+ANG45))
     {
        moveangle = lineangle - deltaangle;
        movelen /= 2; // absorb
        S_StartSound(slidemo,sfx_oof); // oooff!
        moveangle >>= ANGLETOFINESHIFT;
        tmxmove = FixedMul (movelen, finecosine[moveangle]);
        tmymove = FixedMul (movelen, finesine[moveangle]);
     }                                                               //   ^
     else                                                              //   |
     {                                                               // phares
        if (deltaangle > ANG180)
           deltaangle += ANG180;
  
        //  I_Error ("SlideLine: ang>ANG180");
  
        lineangle >>= ANGLETOFINESHIFT;
        deltaangle >>= ANGLETOFINESHIFT;
        newlen = FixedMul (movelen, finecosine[deltaangle]);
        tmxmove = FixedMul (newlen, finecosine[lineangle]);
        tmymove = FixedMul (newlen, finesine[lineangle]);
     }                                                               // phares
  }
  
  
  //
  // PTR_SlideTraverse
  //
  
  boolean PTR_SlideTraverse (intercept_t* in)
  {
     line_t* li;
  
     if (!in->isaline)
        I_Error ("PTR_SlideTraverse: not a line?");
  
     li = in->d.line;
  
     if ( ! (li->flags & ML_TWOSIDED) )
     {
        if (P_PointOnLineSide (slidemo->x, slidemo->y, li))
           return true; // don't hit the back side
        goto isblocking;
     }
  
     // set openrange, opentop, openbottom.
     // These define a 'window' from one sector to another across a line
  
     P_LineOpening (li);
  
     if (openrange < slidemo->height)
        goto isblocking;  // doesn't fit
  
     if (opentop - slidemo->z < slidemo->height)
        goto isblocking;  // mobj is too high
  
     if (openbottom - slidemo->z > 24*FRACUNIT )
        goto isblocking;  // too big a step up
  
     // this line doesn't block movement
  
     return true;
  
     // the line does block movement,
     // see if it is closer than best so far
  
  isblocking:
  
     if (in->frac < bestslidefrac)
     {
        secondslidefrac = bestslidefrac;
        secondslideline = bestslideline;
        bestslidefrac = in->frac;
        bestslideline = li;
     }
  
     return false; // stop
  }
  
  
  //
  // P_SlideMove
  // The momx / momy move is bad, so try to slide
  // along a wall.
  // Find the first line hit, move flush to it,
  // and slide along it
  //
  // This is a kludgy mess.
  //
  // killough 11/98: reformatted
  
  void P_SlideMove(mobj_t *mo)
  {
     int hitcount = 3;
  
     slidemo = mo; // the object that's sliding
  
     do
     {
        fixed_t leadx, leady, trailx, traily;
  
        if (!--hitcount)
           goto stairstep;   // don't loop forever
  
        // trace along the three leading corners
  
        if (mo->momx > 0)
           leadx = mo->x + mo->radius, trailx = mo->x - mo->radius;
        else
           leadx = mo->x - mo->radius, trailx = mo->x + mo->radius;
  
        if (mo->momy > 0)
           leady = mo->y + mo->radius, traily = mo->y - mo->radius;
        else
           leady = mo->y - mo->radius, traily = mo->y + mo->radius;
  
        bestslidefrac = FRACUNIT+1;
  
        P_PathTraverse(leadx, leady, leadx+mo->momx, leady+mo->momy,
                       PT_ADDLINES, PTR_SlideTraverse);
        P_PathTraverse(trailx, leady, trailx+mo->momx, leady+mo->momy,
                       PT_ADDLINES, PTR_SlideTraverse);
        P_PathTraverse(leadx, traily, leadx+mo->momx, traily+mo->momy,
                       PT_ADDLINES, PTR_SlideTraverse);
  
        // move up to the wall
  
        if (bestslidefrac == FRACUNIT+1)
        {
           // the move must have hit the middle, so stairstep
  
  stairstep:
  
           /* killough 3/15/98: Allow objects to drop off ledges
            *
            * phares 5/4/98: kill momentum if you can't move at all
            * This eliminates player bobbing if pressed against a wall
            * while on ice.
            *
            * killough 10/98: keep buggy code around for old Boom demos
            *
            * cph 2000/09//23: buggy code was only in Boom v2.01
            */
  
           if (!P_TryMove(mo, mo->x, mo->y + mo->momy, true))
              if (!P_TryMove(mo, mo->x + mo->momx, mo->y, true))
                 if (compatibility_level == boom_201_compatibility)
                    mo->momx = mo->momy = 0;
  
           break;
        }
  
        // fudge a bit to make sure it doesn't hit
  
        if ((bestslidefrac -= 0x800) > 0)
        {
           fixed_t newx = FixedMul(mo->momx, bestslidefrac);
           fixed_t newy = FixedMul(mo->momy, bestslidefrac);
  
           // killough 3/15/98: Allow objects to drop off ledges
  
           if (!P_TryMove(mo, mo->x+newx, mo->y+newy, true))
              goto stairstep;
        }
  
        // Now continue along the wall.
        // First calculate remainder.
  
        bestslidefrac = FRACUNIT-(bestslidefrac+0x800);
  
        if (bestslidefrac > FRACUNIT)
           bestslidefrac = FRACUNIT;
  
        if (bestslidefrac <= 0)
           break;
  
        tmxmove = FixedMul(mo->momx, bestslidefrac);
        tmymove = FixedMul(mo->momy, bestslidefrac);
  
        P_HitSlideLine(bestslideline); // clip the moves
  
        mo->momx = tmxmove;
        mo->momy = tmymove;
  
        /* killough 10/98: affect the bobbing the same way (but not voodoo dolls)
         * cph - DEMOSYNC? */
        if (mo->player && mo->player->mo == mo)
        {
           if (D_abs(mo->player->momx) > D_abs(tmxmove))
              mo->player->momx = tmxmove;
           if (D_abs(mo->player->momy) > D_abs(tmymove))
              mo->player->momy = tmymove;
        }
     }  // killough 3/15/98: Allow objects to drop off ledges:
     while (!P_TryMove(mo, mo->x+tmxmove, mo->y+tmymove, true));
  }
  
  //
  // P_LineAttack
  //
  mobj_t*   linetarget; // who got hit (or NULL)
  static mobj_t*   shootthing;
  
  /* killough 8/2/98: for more intelligent autoaiming */
  static uint_64_t aim_flags_mask;
  
  // Height if not aiming up or down
  fixed_t   shootz;
  
  int       la_damage;
  fixed_t   attackrange;
  
  static fixed_t   aimslope;
  
  // slopes to top and bottom of target
  // killough 4/20/98: make static instead of using ones in p_sight.c
  
  static fixed_t  topslope;
  static fixed_t  bottomslope;
  
  
  //
  // PTR_AimTraverse
  // Sets linetaget and aimslope when a target is aimed at.
  //
  boolean PTR_AimTraverse (intercept_t* in)
  {
     line_t* li;
     mobj_t* th;
     fixed_t slope;
     fixed_t thingtopslope;
     fixed_t thingbottomslope;
     fixed_t dist;
  
     if (in->isaline)
     {
        li = in->d.line;
  
        if ( !(li->flags & ML_TWOSIDED) )
           return false;   // stop
  
        // Crosses a two sided line.
        // A two sided line will restrict
        // the possible target ranges.
  
        P_LineOpening (li);
  
        if (openbottom >= opentop)
           return false;   // stop
  
        dist = FixedMul (attackrange, in->frac);
  
        if (li->frontsector->floorheight != li->backsector->floorheight)
        {
           slope = FixedDiv (openbottom - shootz , dist);
           if (slope > bottomslope)
              bottomslope = slope;
        }
  
        if (li->frontsector->ceilingheight != li->backsector->ceilingheight)
        {
           slope = FixedDiv (opentop - shootz , dist);
           if (slope < topslope)
              topslope = slope;
        }
  
        if (topslope <= bottomslope)
           return false;   // stop
  
        return true;    // shot continues
     }
  
     // shoot a thing
  
     th = in->d.thing;
     if (th == shootthing)
        return true;    // can't shoot self
  
     if (!(th->flags&MF_SHOOTABLE))
        return true;    // corpse or something
  
     /* killough 7/19/98, 8/2/98:
      * friends don't aim at friends (except players), at least not first
      */
     if (th->flags & shootthing->flags & aim_flags_mask && !th->player)
        return true;
  
     // check angles to see if the thing can be aimed at
  
     dist = FixedMul (attackrange, in->frac);
     thingtopslope = FixedDiv (th->z+th->height - shootz , dist);
  
     if (thingtopslope < bottomslope)
        return true;    // shot over the thing
  
     thingbottomslope = FixedDiv (th->z - shootz, dist);
  
     if (thingbottomslope > topslope)
        return true;    // shot under the thing
  
     // this thing can be hit!
  
     if (thingtopslope > topslope)
        thingtopslope = topslope;
  
     if (thingbottomslope < bottomslope)
        thingbottomslope = bottomslope;
  
     aimslope = (thingtopslope+thingbottomslope)/2;
     linetarget = th;
  
     return false;   // don't go any farther
  }
  
  
  //
  // PTR_ShootTraverse
  //
  boolean PTR_ShootTraverse (intercept_t* in)
  {
     fixed_t x;
     fixed_t y;
     fixed_t z;
     fixed_t frac;
  
     mobj_t* th;
  
     fixed_t slope;
     fixed_t dist;
     fixed_t thingtopslope;
     fixed_t thingbottomslope;
  
     if (in->isaline)
     {
        line_t *li = in->d.line;
  
        if (li->special)
           P_ShootSpecialLine (shootthing, li);
  
        if (li->flags & ML_TWOSIDED)
        {  // crosses a two sided (really 2s) line
           P_LineOpening (li);
           dist = FixedMul(attackrange, in->frac);
  
           // killough 11/98: simplify
  
           if ((li->frontsector->floorheight==li->backsector->floorheight ||
                 (slope = FixedDiv(openbottom - shootz , dist)) <= aimslope) &&
                 (li->frontsector->ceilingheight==li->backsector->ceilingheight ||
                  (slope = FixedDiv (opentop - shootz , dist)) >= aimslope))
              return true;      // shot continues
        }
  
        // hit line
        // position a bit closer
  
        frac = in->frac - FixedDiv (4*FRACUNIT,attackrange);
        x = trace.x + FixedMul (trace.dx, frac);
        y = trace.y + FixedMul (trace.dy, frac);
        z = shootz + FixedMul (aimslope, FixedMul(frac, attackrange));
  
        if (li->frontsector->ceilingpic == skyflatnum)
        {
           // don't shoot the sky!
  
           if (z > li->frontsector->ceilingheight)
              return false;
  
           // it's a sky hack wall
  
           if  (li->backsector && li->backsector->ceilingpic == skyflatnum)
  
              // fix bullet-eaters -- killough:
              // WARNING: Almost all demos will lose sync without this
              // demo_compatibility flag check!!! killough 1/18/98
              if (demo_compatibility || li->backsector->ceilingheight < z)
                 return false;
        }
  
        // Spawn bullet puffs.
  
        P_SpawnPuff (x,y,z);
  
        // don't go any farther
  
        return false;
     }
  
     // shoot a thing
  
     th = in->d.thing;
     if (th == shootthing)
        return true;  // can't shoot self
  
     if (!(th->flags&MF_SHOOTABLE))
        return true;  // corpse or something
  
     // check angles to see if the thing can be aimed at
  
     dist = FixedMul (attackrange, in->frac);
     thingtopslope = FixedDiv (th->z+th->height - shootz , dist);
  
     if (thingtopslope < aimslope)
        return true;  // shot over the thing
  
     thingbottomslope = FixedDiv (th->z - shootz, dist);
  
     if (thingbottomslope > aimslope)
        return true;  // shot under the thing
  
     // hit thing
     // position a bit closer
  
     frac = in->frac - FixedDiv (10*FRACUNIT,attackrange);
  
     x = trace.x + FixedMul (trace.dx, frac);
     y = trace.y + FixedMul (trace.dy, frac);
     z = shootz + FixedMul (aimslope, FixedMul(frac, attackrange));
  
     // Spawn bullet puffs or blod spots,
     // depending on target type.
     if (in->d.thing->flags & MF_NOBLOOD)
        P_SpawnPuff (x,y,z);
     else
        P_SpawnBlood (x,y,z, la_damage);
  
     if (la_damage)
        P_DamageMobj (th, shootthing, shootthing, la_damage);
  
     // don't go any farther
     return false;
  }
  
  
  //
  // P_AimLineAttack
  //
  fixed_t P_AimLineAttack(mobj_t* t1,angle_t angle,fixed_t distance, uint_64_t mask)
  {
     fixed_t x2;
     fixed_t y2;
  
     angle >>= ANGLETOFINESHIFT;
     shootthing = t1;
  
     x2 = t1->x + (distance>>FRACBITS)*finecosine[angle];
     y2 = t1->y + (distance>>FRACBITS)*finesine[angle];
     shootz = t1->z + (t1->height>>1) + 8*FRACUNIT;
  
     // can't shoot outside view angles
  
     topslope = 100*FRACUNIT/160;
     bottomslope = -100*FRACUNIT/160;
  
     attackrange = distance;
     linetarget = NULL;
  
     /* killough 8/2/98: prevent friends from aiming at friends */
     aim_flags_mask = mask;
  
     P_PathTraverse(t1->x,t1->y,x2,y2,PT_ADDLINES|PT_ADDTHINGS,PTR_AimTraverse);
  
     if (linetarget)
        return aimslope;
  
     return 0;
  }
  
  
  //
  // P_LineAttack
  // If damage == 0, it is just a test trace
  // that will leave linetarget set.
  //
  
  void P_LineAttack
  (mobj_t* t1,
   angle_t angle,
   fixed_t distance,
   fixed_t slope,
   int     damage)
  {
     fixed_t x2;
     fixed_t y2;
  
     angle >>= ANGLETOFINESHIFT;
     shootthing = t1;
     la_damage = damage;
     x2 = t1->x + (distance>>FRACBITS)*finecosine[angle];
     y2 = t1->y + (distance>>FRACBITS)*finesine[angle];
     shootz = t1->z + (t1->height>>1) + 8*FRACUNIT;
     attackrange = distance;
     aimslope = slope;
  
     P_PathTraverse(t1->x,t1->y,x2,y2,PT_ADDLINES|PT_ADDTHINGS,PTR_ShootTraverse);
  }
  
  
  //
  // USE LINES
  //
  
  mobj_t*   usething;
  
  boolean PTR_UseTraverse (intercept_t* in)
  {
     int side;
  
     if (!in->d.line->special)
     {
        P_LineOpening (in->d.line);
        if (openrange <= 0)
        {
           S_StartSound (usething, sfx_noway);
  
           // can't use through a wall
           return false;
        }
  
        // not a special line, but keep checking
  
        return true;
     }
  
     side = 0;
     if (P_PointOnLineSide (usething->x, usething->y, in->d.line) == 1)
        side = 1;
  
     //  return false;   // don't use back side
  
     P_UseSpecialLine (usething, in->d.line, side);
  
     //WAS can't use for than one special line in a row
     //jff 3/21/98 NOW multiple use allowed with enabling line flag
  
     return (!demo_compatibility && (in->d.line->flags&ML_PASSUSE))?
            true : false;
  }
  
  // Returns false if a "oof" sound should be made because of a blocking
  // linedef. Makes 2s middles which are impassable, as well as 2s uppers
  // and lowers which block the player, cause the sound effect when the
  // player tries to activate them. Specials are excluded, although it is
  // assumed that all special linedefs within reach have been considered
  // and rejected already (see P_UseLines).
  //
  // by Lee Killough
  //
  
  boolean PTR_NoWayTraverse(intercept_t* in)
  {
     line_t *ld = in->d.line;
     // This linedef
     return ld->special || !(                 // Ignore specials
               ld->flags & ML_BLOCKING || (            // Always blocking
                  P_LineOpening(ld),                      // Find openings
                  openrange <= 0 ||                       // No opening
                  openbottom > usething->z+24*FRACUNIT || // Too high it blocks
                  opentop < usething->z+usething->height  // Too low it blocks
               )
            );
  }
  
  //
  // P_UseLines
  // Looks for special lines in front of the player to activate.
  //
  void P_UseLines (player_t*  player)
  {
     int     angle;
     fixed_t x1;
     fixed_t y1;
     fixed_t x2;
     fixed_t y2;
  
     usething = player->mo;
  
     angle = player->mo->angle >> ANGLETOFINESHIFT;
  
     x1 = player->mo->x;
     y1 = player->mo->y;
     x2 = x1 + (USERANGE>>FRACBITS)*finecosine[angle];
     y2 = y1 + (USERANGE>>FRACBITS)*finesine[angle];
  
     // old code:
     //
     // P_PathTraverse ( x1, y1, x2, y2, PT_ADDLINES, PTR_UseTraverse );
     //
     // This added test makes the "oof" sound work on 2s lines -- killough:
  
     if (P_PathTraverse ( x1, y1, x2, y2, PT_ADDLINES, PTR_UseTraverse ))
        if (!comp[comp_sound] && !P_PathTraverse ( x1, y1, x2, y2, PT_ADDLINES, PTR_NoWayTraverse ))
           S_StartSound (usething, sfx_noway);
  }
  
  
  //
  // RADIUS ATTACK
  //
  
  static mobj_t *bombsource, *bombspot;
  static int bombdamage;
  
  
  //
  // PIT_RadiusAttack
  // "bombsource" is the creature
  // that caused the explosion at "bombspot".
  //
  
  boolean PIT_RadiusAttack (mobj_t* thing)
  {
     fixed_t dx;
     fixed_t dy;
     fixed_t dist;
  
     /* killough 8/20/98: allow bouncers to take damage
      * (missile bouncers are already excluded with MF_NOBLOCKMAP)
      */
  
     if (!(thing->flags & (MF_SHOOTABLE | MF_BOUNCES)))
        return true;
  
     // Boss spider and cyborg
     // take no damage from concussion.
  
     // killough 8/10/98: allow grenades to hurt anyone, unless
     // fired by Cyberdemons, in which case it won't hurt Cybers.
  
     if (bombspot->flags & MF_BOUNCES ?
           thing->type == MT_CYBORG && bombsource->type == MT_CYBORG :
           thing->type == MT_CYBORG || thing->type == MT_SPIDER)
        return true;
  
     dx = D_abs(thing->x - bombspot->x);
     dy = D_abs(thing->y - bombspot->y);
  
     dist = dx>dy ? dx : dy;
     dist = (dist - thing->radius) >> FRACBITS;
  
     if (dist < 0)
        dist = 0;
  
     if (dist >= bombdamage)
        return true;  // out of range
  
     if ( P_CheckSight (thing, bombspot) )
     {
        // must be in direct path
        P_DamageMobj (thing, bombspot, bombsource, bombdamage - dist);
     }
  
     return true;
  }
  
  
  //
  // P_RadiusAttack
  // Source is the creature that caused the explosion at spot.
  //
  void P_RadiusAttack(mobj_t* spot,mobj_t* source,int damage)
  {
     int x;
     int y;
  
     int xl;
     int xh;
     int yl;
     int yh;
  
     fixed_t dist;
  
     dist = (damage+MAXRADIUS)<<FRACBITS;
     yh = (spot->y + dist - bmaporgy)>>MAPBLOCKSHIFT;
     yl = (spot->y - dist - bmaporgy)>>MAPBLOCKSHIFT;
     xh = (spot->x + dist - bmaporgx)>>MAPBLOCKSHIFT;
     xl = (spot->x - dist - bmaporgx)>>MAPBLOCKSHIFT;
     bombspot = spot;
     bombsource = source;
     bombdamage = damage;
  
     for (y=yl ; y<=yh ; y++)
        for (x=xl ; x<=xh ; x++)
           P_BlockThingsIterator (x, y, PIT_RadiusAttack );
  }
  
  
  
  //
  // SECTOR HEIGHT CHANGING
  // After modifying a sectors floor or ceiling height,
  // call this routine to adjust the positions
  // of all things that touch the sector.
  //
  // If anything doesn't fit anymore, true will be returned.
  // If crunch is true, they will take damage
  //  as they are being crushed.
  // If Crunch is false, you should set the sector height back
  //  the way it was and call P_ChangeSector again
  //  to undo the changes.
  //
  
  static boolean crushchange, nofit;
  
  //
  // PIT_ChangeSector
  //
  
  boolean PIT_ChangeSector (mobj_t* thing)
  {
     mobj_t* mo;
  
     if (P_ThingHeightClip (thing))
        return true; // keep checking
  
     // crunch bodies to giblets
  
     if (thing->health <= 0)
     {
        P_SetMobjState (thing, S_GIBS);
  
        thing->flags &= ~MF_SOLID;
        thing->height = 0;
        thing->radius = 0;
        return true; // keep checking
     }
  
     // crunch dropped items
  
     if (thing->flags & MF_DROPPED)
     {
        P_RemoveMobj (thing);
  
        // keep checking
        return true;
     }
  
     /* killough 11/98: kill touchy things immediately */
     if (thing->flags & MF_TOUCHY &&
           (thing->intflags & MIF_ARMED || sentient(thing)))
     {
        P_DamageMobj(thing, NULL, NULL, thing->health);  // kill object
        return true;   // keep checking
     }
  
     if (! (thing->flags & MF_SHOOTABLE) )
     {
        // assume it is bloody gibs or something
        return true;
     }
  
     nofit = true;
  
     if (crushchange && !(leveltime&3)) {
        int t;
        P_DamageMobj(thing,NULL,NULL,10);
  
        // spray blood in a random direction
        mo = P_SpawnMobj (thing->x,
                          thing->y,
                          thing->z + thing->height/2, MT_BLOOD);
  
        /* killough 8/10/98: remove dependence on order of evaluation */
        t = P_Random(pr_crush);
        mo->momx = (t - P_Random (pr_crush))<<12;
        t = P_Random(pr_crush);
        mo->momy = (t - P_Random (pr_crush))<<12;
     }
  
     // keep checking (crush other things)
     return true;
  }
  
  
  //
  // P_ChangeSector
  //
  boolean P_ChangeSector(sector_t* sector,boolean crunch)
  {
     int   x;
     int   y;
  
     nofit = false;
     crushchange = crunch;
  
     // ARRGGHHH!!!!
     // This is horrendously slow!!!
     // killough 3/14/98
  
     // re-check heights for all things near the moving sector
  
     for (x=sector->blockbox[BOXLEFT] ; x<= sector->blockbox[BOXRIGHT] ; x++)
        for (y=sector->blockbox[BOXBOTTOM];y<= sector->blockbox[BOXTOP] ; y++)
           P_BlockThingsIterator (x, y, PIT_ChangeSector);
  
     return nofit;
  }
  
  //
  // P_CheckSector
  // jff 3/19/98 added to just check monsters on the periphery
  // of a moving sector instead of all in bounding box of the
  // sector. Both more accurate and faster.
  //
  
  boolean P_CheckSector(sector_t* sector,boolean crunch)
  {
     msecnode_t *n;
  
     if (comp[comp_floors]) /* use the old routine for old demos though */
        return P_ChangeSector(sector,crunch);
  
     nofit = false;
     crushchange = crunch;
  
     // killough 4/4/98: scan list front-to-back until empty or exhausted,
     // restarting from beginning after each thing is processed. Avoids
     // crashes, and is sure to examine all things in the sector, and only
     // the things which are in the sector, until a steady-state is reached.
     // Things can arbitrarily be inserted and removed and it won't mess up.
     //
     // killough 4/7/98: simplified to avoid using complicated counter
  
     // Mark all things invalid
  
     for (n=sector->touching_thinglist; n; n=n->m_snext)
        n->visited = false;
  
     do
        for (n=sector->touching_thinglist; n; n=n->m_snext)  // go through list
           if (!n->visited)               // unprocessed thing found
           {
              n->visited  = true;          // mark thing as processed
              if (!(n->m_thing->flags & MF_NOBLOCKMAP)) //jff 4/7/98 don't do these
                 PIT_ChangeSector(n->m_thing);    // process it
              break;                 // exit and start over
           }
     while (n);  // repeat from scratch until all things left are marked valid
  
     return nofit;
  }
  
  
  // CPhipps -
  // Use block memory allocator here
  
  #include "z_bmalloc.h"
  
  IMPLEMENT_BLOCK_MEMORY_ALLOC_ZONE(secnodezone, sizeof(msecnode_t), PU_LEVEL, 32, "SecNodes");
  
  inline static msecnode_t* P_GetSecnode(void)
  {
     return (msecnode_t*)Z_BMalloc(&secnodezone);
  }
  
  // P_PutSecnode() returns a node to the freelist.
  
  inline static void P_PutSecnode(msecnode_t* node)
  {
     Z_BFree(&secnodezone, node);
  }
  
  // phares 3/16/98
  //
  // P_AddSecnode() searches the current list to see if this sector is
  // already there. If not, it adds a sector node at the head of the list of
  // sectors this object appears in. This is called when creating a list of
  // nodes that will get linked in later. Returns a pointer to the new node.
  
  msecnode_t* P_AddSecnode(sector_t* s, mobj_t* thing, msecnode_t* nextnode)
  {
     msecnode_t* node;
  
     node = nextnode;
     while (node)
     {
        if (node->m_sector == s)   // Already have a node for this sector?
        {
           node->m_thing = thing; // Yes. Setting m_thing says 'keep it'.
           return(nextnode);
        }
        node = node->m_tnext;
     }
  
     // Couldn't find an existing node for this sector. Add one at the head
     // of the list.
  
     node = P_GetSecnode();
  
     // killough 4/4/98, 4/7/98: mark new nodes unvisited.
     node->visited = 0;
  
     node->m_sector = s;       // sector
     node->m_thing  = thing;     // mobj
     node->m_tprev  = NULL;    // prev node on Thing thread
     node->m_tnext  = nextnode;  // next node on Thing thread
     if (nextnode)
        nextnode->m_tprev = node; // set back link on Thing
  
     // Add new node at head of sector thread starting at s->touching_thinglist
  
     node->m_sprev  = NULL;    // prev node on sector thread
     node->m_snext  = s->touching_thinglist; // next node on sector thread
     if (s->touching_thinglist)
        node->m_snext->m_sprev = node;
     s->touching_thinglist = node;
     return(node);
  }
  
  
  // P_DelSecnode() deletes a sector node from the list of
  // sectors this object appears in. Returns a pointer to the next node
  // on the linked list, or NULL.
  
  msecnode_t* P_DelSecnode(msecnode_t* node)
  {
     msecnode_t* tp;  // prev node on thing thread
     msecnode_t* tn;  // next node on thing thread
     msecnode_t* sp;  // prev node on sector thread
     msecnode_t* sn;  // next node on sector thread
  
     if (node)
     {
  
        // Unlink from the Thing thread. The Thing thread begins at
        // sector_list and not from mobj_t->touching_sectorlist.
  
        tp = node->m_tprev;
        tn = node->m_tnext;
        if (tp)
           tp->m_tnext = tn;
        if (tn)
           tn->m_tprev = tp;
  
        // Unlink from the sector thread. This thread begins at
        // sector_t->touching_thinglist.
  
        sp = node->m_sprev;
        sn = node->m_snext;
        if (sp)
           sp->m_snext = sn;
        else
           node->m_sector->touching_thinglist = sn;
        if (sn)
           sn->m_sprev = sp;
  
        // Return this node to the freelist
  
        P_PutSecnode(node);
        return(tn);
     }
     return(NULL);
  }                             // phares 3/13/98
  
  // Delete an entire sector list
  
  void P_DelSeclist(msecnode_t* node)
  
  {
     while (node)
        node = P_DelSecnode(node);
  }
  
  
  // phares 3/14/98
  //
  // PIT_GetSectors
  // Locates all the sectors the object is in by looking at the lines that
  // cross through it. You have already decided that the object is allowed
  // at this location, so don't bother with checking impassable or
  // blocking lines.
  
  boolean PIT_GetSectors(line_t* ld)
  {
     if (tmbbox[BOXRIGHT]  <= ld->bbox[BOXLEFT]   ||
           tmbbox[BOXLEFT]   >= ld->bbox[BOXRIGHT]  ||
           tmbbox[BOXTOP]    <= ld->bbox[BOXBOTTOM] ||
           tmbbox[BOXBOTTOM] >= ld->bbox[BOXTOP])
        return true;
  
     if (P_BoxOnLineSide(tmbbox, ld) != -1)
        return true;
  
     // This line crosses through the object.
  
     // Collect the sector(s) from the line and add to the
     // sector_list you're examining. If the Thing ends up being
     // allowed to move to this position, then the sector_list
     // will be attached to the Thing's mobj_t at touching_sectorlist.
  
     sector_list = P_AddSecnode(ld->frontsector,tmthing,sector_list);
  
     /* Don't assume all lines are 2-sided, since some Things
      * like MT_TFOG are allowed regardless of whether their radius takes
      * them beyond an impassable linedef.
      *
      * killough 3/27/98, 4/4/98:
      * Use sidedefs instead of 2s flag to determine two-sidedness.
      * killough 8/1/98: avoid duplicate if same sector on both sides
      * cph - DEMOSYNC? */
  
     if (ld->backsector && ld->backsector != ld->frontsector)
        sector_list = P_AddSecnode(ld->backsector, tmthing, sector_list);
  
     return true;
  }
  
  
  // phares 3/14/98
  //
  // P_CreateSecNodeList alters/creates the sector_list that shows what sectors
  // the object resides in.
  
  void P_CreateSecNodeList(mobj_t* thing,fixed_t x,fixed_t y)
  {
     int xl;
     int xh;
     int yl;
     int yh;
     int bx;
     int by;
     msecnode_t* node;
     mobj_t* saved_tmthing = tmthing; /* cph - see comment at func end */
     fixed_t saved_tmx = tmx, saved_tmy = tmy; /* ditto */
  
     // First, clear out the existing m_thing fields. As each node is
     // added or verified as needed, m_thing will be set properly. When
     // finished, delete all nodes where m_thing is still NULL. These
     // represent the sectors the Thing has vacated.
  
     node = sector_list;
     while (node)
     {
        node->m_thing = NULL;
        node = node->m_tnext;
     }
  
     tmthing = thing;
  
     tmx = x;
     tmy = y;
  
     tmbbox[BOXTOP]  = y + tmthing->radius;
     tmbbox[BOXBOTTOM] = y - tmthing->radius;
     tmbbox[BOXRIGHT]  = x + tmthing->radius;
     tmbbox[BOXLEFT]   = x - tmthing->radius;
  
     validcount++; // used to make sure we only process a line once
  
     xl = (tmbbox[BOXLEFT] - bmaporgx)>>MAPBLOCKSHIFT;
     xh = (tmbbox[BOXRIGHT] - bmaporgx)>>MAPBLOCKSHIFT;
     yl = (tmbbox[BOXBOTTOM] - bmaporgy)>>MAPBLOCKSHIFT;
     yh = (tmbbox[BOXTOP] - bmaporgy)>>MAPBLOCKSHIFT;
  
     for (bx=xl ; bx<=xh ; bx++)
        for (by=yl ; by<=yh ; by++)
           P_BlockLinesIterator(bx,by,PIT_GetSectors);
  
     // Add the sector of the (x,y) point to sector_list.
  
     sector_list = P_AddSecnode(thing->subsector->sector,thing,sector_list);
  
     // Now delete any nodes that won't be used. These are the ones where
     // m_thing is still NULL.
  
     node = sector_list;
     while (node)
     {
        if (node->m_thing == NULL)
        {
           if (node == sector_list)
              sector_list = node->m_tnext;
           node = P_DelSecnode(node);
        }
        else
           node = node->m_tnext;
     }
  
     /* cph -
      * This is the strife we get into for using global variables. tmthing
      *  is being used by several different functions calling
      *  P_BlockThingIterator, including functions that can be called *from*
      *  P_BlockThingIterator. Using a global tmthing is not reentrant.
      * OTOH for Boom/MBF demos we have to preserve the buggy behavior.
      *  Fun. We restore its previous value unless we're in a Boom/MBF demo.
      */
     if ((compatibility_level < boom_compatibility_compatibility) ||
           (compatibility_level >= prboom_3_compatibility))
        tmthing = saved_tmthing;
     /* And, duh, the same for tmx/y - cph 2002/09/22
      * And for tmbbox - cph 2003/08/10 */
     if ((compatibility_level < boom_compatibility_compatibility) /* ||
                       (compatibility_level >= prboom_4_compatibility) */) {
        tmx = saved_tmx, tmy = saved_tmy;
        if (tmthing) {
           tmbbox[BOXTOP]  = tmy + tmthing->radius;
           tmbbox[BOXBOTTOM] = tmy - tmthing->radius;
           tmbbox[BOXRIGHT]  = tmx + tmthing->radius;
           tmbbox[BOXLEFT]   = tmx - tmthing->radius;
        }
     }
  }
  
  /* cphipps 2004/08/30 -
   * Must clear tmthing at tic end, as it might contain a pointer to a removed thinker, or the level might have ended/been ended and we clear the objects it was pointing too. Hopefully we don't need to carry this between tics for sync. */
  void P_MapStart(void) {
     if (tmthing) I_Error("P_MapStart: tmthing set!");
  }
  void P_MapEnd(void) {
     tmthing = NULL;
  }
  
  