/* 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;
}

