/*****************************************************************************/

/* D2K: A Doom Source Port for the 21st Century                              */
/*                                                                           */
/* Copyright (C) 2014: See COPYRIGHT file                                    */
/*                                                                           */
/* This file is part of D2K.                                                 */
/*                                                                           */
/* D2K 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.                                                            */
/*                                                                           */
/* D2K 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 D2K.  If not, see <http://www.gnu.org/licenses/>.                    */
/*                                                                           */
/*****************************************************************************/

#include "z_zone.h"

#include "d_event.h"
#include "e6y.h"// e6y
#include "g_game.h"
#include "g_overflow.h"
#include "m_argv.h"
#include "m_bbox.h"
#include "m_random.h"
#include "p_setup.h"
#include "p_mobj.h"
#include "p_inter.h"
#include "p_map.h"
#include "p_maputl.h"
#include "p_spec.h"
#include "p_user.h"
#include "r_defs.h"
#include "r_main.h"
#include "r_state.h"
#include "s_sound.h"
#include "sounds.h"
#include "v_video.h"

#include "hu_lib.h"
#include "hu_tracers.h"

#include "z_bmalloc.h"

IMPLEMENT_BLOCK_MEMORY_ALLOC_ZONE(
  secnodezone,
  sizeof(msecnode_t),
  PU_LEVEL,
  256,
  "SecNodes"
);

extern bool onground;

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

static mobj_t *shootthing;

/* killough 8/2/98: for more intelligent autoaiming */
static uint64_t aim_flags_mask;

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;
//
// 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 bool crushchange;
static bool nofit;

// If "floatok" true, move would be ok
// if within "tmfloorz - tmceilingz".

// e6y static
mobj_t *bombsource
mogj_t *bombspot;

// e6y static
int bombdamage;

bool floatok;

/* killough 11/98: if "felldown" true, object was pushed down ledge */
bool felldown;

mobj_t *linetarget;               // who got hit (or NULL)
mobj_t *crosshair_target;
fixed_t shootz; // Height if not aiming up or down

int la_damage;
fixed_t attackrange;

// 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 bool telefrag; /* killough 8/9/98: whether to telefrag at exit */

bool 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;
}

//
// PIT_StompSpawnPointBlockers
//

bool PIT_StompSpawnPointBlockers(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;
  }

  if (thing->player) {
    thing->player->telefragged_by_spawn = true;
  }

  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_FLY) {
    friction = FRICTION_FLY;
  }
  else 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(mobj_t *mo, int *frictionp) {
  int movefactor;
  int friction;

  // e6y
  if (!mbf_features && !prboom_comp[PC_PRBOOM_FRICTION].state) {
    int momentum;

    movefactor = ORIG_FRICTION_FACTOR;

    if (!compatibility && variable_friction &&
        !(mo->flags & (MF_NOGRAVITY | MF_NOCLIP))) {
      friction = mo->friction;
      if (friction == ORIG_FRICTION) {                    // normal floor
      }
      else if (friction > ORIG_FRICTION) {                // ice
        movefactor = mo->movefactor;
        ((mobj_t *)mo)->movefactor = ORIG_FRICTION_FACTOR;// reset
      }
      else {                                              // sludge
        // phares 3/11/98: you start off slowly, then increase as
        // you get better footing
        momentum = (P_AproxDistance(mo->momx, mo->momy));
        movefactor = mo->movefactor;
        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;
        }

        ((mobj_t *)mo)->movefactor = ORIG_FRICTION_FACTOR; // reset
      }                                                    //   ^
    }                                                      //   |

    return movefactor;
  }

  // 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
//

static bool P_TeleportIterator(mobj_t *mo, fixed_t x, fixed_t y,
                                                      bool func(mobj_t *)) {
  int xl;
  int xh;
  int yl;
  int yh;
  int bx;
  int by;
  subsector_t *newsubsec;

  /* kill anything occupying the position */

  tmthing = mo;

  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   = newsubsec->sector->floorheight;
  tmdropoffz = newsubsec->sector->floorheight;
  tmceilingz = newsubsec->sector->ceilingheight;

  validcount++;
  numspechit = 0;

  /* stomp on any things contacted */

  xl = P_GetSafeBlockX(tmbbox[BOXLEFT]   - bmaporgx - MAXRADIUS);
  xh = P_GetSafeBlockX(tmbbox[BOXRIGHT]  - bmaporgx + MAXRADIUS);
  yl = P_GetSafeBlockY(tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS);
  yh = P_GetSafeBlockY(tmbbox[BOXTOP]    - bmaporgy + MAXRADIUS);

  for (bx = xl; bx <= xh; bx++) {
    for (by = yl; by <= yh; by++) {
      if (!P_BlockThingsIterator(bx, by, func)) {
        return false;
      }
    }
  }

  /*
   * the move is ok,
   * so unlink from the old position & link into the new position
   */

  P_UnsetThingPosition(mo);

  mo->floorz   = tmfloorz;
  mo->ceilingz = tmceilingz;
  mo->dropoffz = tmdropoffz;        // killough 11/98

  mo->x = x;
  mo->y = y;

  P_SetThingPosition(mo);

  mo->PrevX = x;
  mo->PrevY = y;
  mo->PrevZ = mo->floorz;

  return true;
}

bool P_TeleportMove(mobj_t *thing, fixed_t x, fixed_t y, bool boss) {
  /*
   * killough 8/9/98: make telefragging more consistent, preserve compatibility
   */
  telefrag = (
    thing->player ||
    (!comp[comp_telefrag] ? boss : (gamemap == 30))
  );

  return P_TeleportIterator(thing, x, y, PIT_StompThing);
}

bool P_StompSpawnPointBlockers(mobj_t *thing) {
  if (thing->player) {
    telefrag = true;
  }
  else {
    telefrag = false;
  }

  return P_TeleportIterator(
    thing, thing->x, thing->y, PIT_StompSpawnPointBlockers
  );
}

//
// 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.
//

// killough 3/26/98: make static
static bool 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                   //   ^
      }                                                           //   |
    }                                                             // phares
  }
  return true;       // line doesn't block trajectory
}

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

// killough 3/26/98: make static
static bool 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) { // block monsters only
      return false;
    }
  }

  // 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

  CheckLinesCrossTracer(ld);                                    // e6y
  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;

    // e6y: Spechits overrun emulation code
    if (numspechit > 8 && demo_compatibility) {
      static spechit_overrun_param_t spechit_overrun_param = {
        NULL,         // line_t *line; 
        &spechit,     // line_t **spechit;
        &numspechit,  // int *numspechit;
        tmbbox,       // fixed_t *tmbbox[4];
        &tmfloorz,    // fixed_t *tmfloorz;
        &tmceilingz,  // fixed_t *tmceilingz;
        &crushchange, // bool *crushchange;
        &nofit,       // bool *nofit;
      };
      spechit_overrun_param.line = ld;
      SpechitOverrun(&spechit_overrun_param);
    }
  }

  return true;
}

//
// PIT_CheckThing
//

static bool 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 are
      (tmthing->type ^ MT_SKULL) &&               // considered same (but
      (thing->type ^ MT_SKULL) |                  // (but Barons & Knights are
      (tmthing->type ^ MT_PAIN)) {                // intentionally not)
    P_DamageMobj(thing, NULL, NULL, thing->health); // kill object
    return true;
  }

  // check for skulls slamming into things

  if (tmthing->flags & MF_SKULLFLY) {
    int damage = ((P_Random(pr_skullfly) % 8) + 1) * tmthing->info->damage;

    // A flying skull is smacking something.
    // Determine damage amount, and the skull comes to a dead stop.

    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

  // see if it went over / under
  if (tmthing->flags & MF_MISSILE || (tmthing->flags & MF_BOUNCES &&
      !(tmthing->flags & MF_SOLID))) {
    if (tmthing->z > thing->z + thing->height) { // overhead
      return true;
    }

    if (tmthing->z + tmthing->height < thing->z) { // underneath
      return true;
    }

    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.
      }
      // e6y: Dehacked support - monsters infight
      else if (thing->type != MT_PLAYER && !monsters_infight) {
        // 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) {
    uint64_t solid = thing->flags & MF_SOLID;

    if (tmthing->flags & MF_PICKUP) {
      P_TouchSpecialThing(thing, tmthing); // can remove thing
    }

    return !solid;
  }

  // RjY
  // comperr_hangsolid, an attempt to handle blocking hanging bodies
  // A solid hanging body will allow sufficiently small things underneath it.
  if (comperr(comperr_hangsolid) &&
      !((~thing->flags) & (MF_SOLID | MF_SPAWNCEILING)) && // solid and hanging
      // invert everything, then both bits should be clear
      tmthing->z + tmthing->height <= thing->z) { // head height <= base
      // top of thing trying to move under the body <= bottom of body
    tmceilingz = thing->z; // pretend ceiling height is at body's base
    return true;
  }

  // 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
  // ...but not in demo_compatibility mode

  // e6y
  // Correction of wrong return value with demo_compatibility.
  // There is no more synch on http://www.doomworld.com/sda/dwdemo/w303-115.zip
  // (with correction in setMobjInfoValue)
  if (demo_compatibility &&
    !prboom_comp[PC_TREAT_NO_CLIPPING_THINGS_AS_NOT_BLOCKING].state) {
    return !(thing->flags & MF_SOLID);
  }

  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.

bool 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 = P_GetSafeBlockX(tmbbox[BOXLEFT]   - bmaporgx);
  xh = P_GetSafeBlockX(tmbbox[BOXRIGHT]  - bmaporgx);
  yl = P_GetSafeBlockY(tmbbox[BOXBOTTOM] - bmaporgy);
  yh = P_GetSafeBlockY(tmbbox[BOXTOP]    - bmaporgy);

  // 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
//

bool 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 = P_GetSafeBlockX(tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS);
  xh = P_GetSafeBlockX(tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS);
  yl = P_GetSafeBlockY(tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS);
  yh = P_GetSafeBlockY(tmbbox[BOXTOP] - bmaporgy + MAXRADIUS);

  for (bx = xl; bx <= xh; bx++) {
    for (by = yl; by <= yh; by++) {
      if (!P_BlockThingsIterator(bx, by, PIT_CheckThing)) {
        return false;
      }
    }
  }

  // check lines

  xl = P_GetSafeBlockX(tmbbox[BOXLEFT] - bmaporgx);
  xh = P_GetSafeBlockX(tmbbox[BOXRIGHT] - bmaporgx);
  yl = P_GetSafeBlockY(tmbbox[BOXBOTTOM] - bmaporgy);
  yh = P_GetSafeBlockY(tmbbox[BOXTOP] - bmaporgy);

  for (bx = xl; bx <= xh; bx++) {
    for (by = yl; by <= yh; by++) {
      if (!P_BlockLinesIterator(bx, by, PIT_CheckLine)) {
        return false;                           // doesn't fit
      }
    }
  }
  ClearLinesCrossTracer();                      // e6y
  return true;
}

//
// P_TryMove
// Attempt to move to a new position,
// crossing special lines unless MF_TELEPORT is set.
//
// killough 3/15/98: allow dropoff as option
bool P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, int dropoff) {
  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)) {
    if (thing->flags & MF_FLY) {
      // When flying, slide up or down blocking lines until the actor
      // is not blocked.
      if (thing->z + thing->height > tmceilingz) {
        thing->momz = -8 * FRACUNIT;
        return false;
      }
      else if (thing->z < tmfloorz && tmfloorz - tmdropoffz > 24 * FRACUNIT) {
        thing->momz = 8 * FRACUNIT;
        return false;
      }
    }

    // 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 && !(thing->flags & MF_FLY)) ||
        // 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]) {
        // e6y
        // Fix demosync bug in mbf compatibility mode
        // There is no more desync on v2-2822.lmp/vrack2.wad
        // -force_no_dropoff command-line switch is for mbf_compatibility demos
        // recorded with prboom 2.2.2 - 2.4.7
        // Links:
        // http://competn.doom2.net/pub/sda/t-z/v2-2822.zip
        // http://www.doomworld.com/idgames/index.php?id=11138
        if ((compatibility || !dropoff ||
            (!prboom_comp[PC_NO_DROPOFF].state && mbf_features &&
            compatibility_level <= prboom_2_compatibility)) &&
            (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 (!monsters_climb ||
            !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 bool 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 ? ld->frontsector->floorheight < mo->z &&
                   ld->backsector->floorheight >= mo->z : // dropoff direction
                   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 = P_GetSafeBlockX((tmbbox[BOXLEFT] =
    mo->x - mo->radius) - bmaporgx);
  int xh = P_GetSafeBlockX((tmbbox[BOXRIGHT] =
    mo->x + mo->radius) - bmaporgx);
  int yl = P_GetSafeBlockY((tmbbox[BOXBOTTOM] =
    mo->y - mo->radius) - bmaporgy);
  int yh = P_GetSafeBlockY((tmbbox[BOXTOP] =
    mo->y + mo->radius) - bmaporgy);
  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.
//

bool P_ThingHeightClip(mobj_t *thing) {
  bool 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 line_t *bestslideline;
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;
  bool icyfloor;                     // is floor icy?

  // Under icy conditions, if the angle of approach to the wall     // phares
  // is more than 45 degrees, then you'll bounce and lose half      //   |
  // your momentum. If less than 45 degrees, you'll slide along     //   V
  // 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? */

  // e6y
  if (mbf_features || prboom_comp[PC_PRBOOM_FRICTION].state) {
    icyfloor =
      P_AproxDistance(tmxmove, tmymove) > 4 * FRACUNIT &&
      variable_friction && // killough 8/28/98: calc friction on demand
      slidemo->z <= slidemo->floorz &&
      P_GetFriction(slidemo, NULL) > ORIG_FRICTION;
  }
  else {
    icyfloor = !compatibility &&
      variable_friction &&
      slidemo->player &&
      onground &&
      slidemo->friction > 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
  }                                  // rounding error              // phares
  deltaangle = moveangle - lineangle;                               //   |
  movelen = P_AproxDistance(tmxmove, tmymove);                      //   V
  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 {                                                            //   |
    if (deltaangle > ANG180) {                                      // phares
      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
//

bool 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) {
    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
//

//
// PTR_AimTraverse
// Sets linetaget and aimslope when a target is aimed at.
//
bool 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);

    // e6y: emulation of missed back side on two-sided lines.
    // backsector can be NULL if overrun_missedbackside_emulate is 1
    if (!li->backsector ||
      li->frontsector->floorheight != li->backsector->floorheight) {
      slope = FixedDiv(openbottom - shootz, dist);
      if (slope > bottomslope) {
        bottomslope = slope;
      }
    }

    // e6y: emulation of missed back side on two-sided lines.
    if (!li->backsector ||
      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
//
bool 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

      // e6y: emulation of missed back side on two-sided lines.
      // backsector can be NULL if overrun_missedbackside_emulate is 1
      if (!li->backsector) {
        if ((slope = FixedDiv(openbottom - shootz, dist)) <= aimslope &&
          (slope = FixedDiv(opentop - shootz, dist)) >= aimslope) {
          return true; // shot continues
        }
      }
      else 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,
                                                   uint64_t mask) {
  fixed_t x2;
  fixed_t y2;

  t1 = P_SubstNullMobj(t1);

  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;

bool 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);
      return false; // can't use through a wall
    }

    return true; // not a special line, but keep checking
  }

  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) || comperr(comperr_passuse))
    ) ? true : false; // e6y
  );
}

// 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
//

bool 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]) {
      return;
    }

    if (!P_PathTraverse(x1, y1, x2, y2, PT_ADDLINES, PTR_NoWayTraverse)) {
      S_StartSound(usething, sfx_noway);
    }
  }
}

//
// RADIUS ATTACK
//

//
// PIT_RadiusAttack
// "bombsource" is the creature
// that caused the explosion at "bombspot".
//

bool 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 = P_GetSafeBlockY(spot->y + dist - bmaporgy);
  yl = P_GetSafeBlockY(spot->y - dist - bmaporgy);
  xh = P_GetSafeBlockX(spot->x + dist - bmaporgx);
  xl = P_GetSafeBlockX(spot->x - dist - bmaporgx);
  bombspot = spot;
  bombsource = source;
  bombdamage = damage;

  for (y = yl; y <= yh; y++) {
    for (x = xl; x <= xh; x++) {
      P_BlockThingsIterator(x, y, PIT_RadiusAttack);
    }
  }
}

//
// PIT_ChangeSector
//

bool 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);

    if (compatibility_level != doom_12_compatibility) {
      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);

    return true; // keep checking
  }

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

  return true; // keep checking (crush other things)
}

//
// P_ChangeSector
//
bool P_ChangeSector(sector_t *sector, bool 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.
//

bool P_CheckSector(sector_t *sector, bool 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 {
    // go through list
    for (n = sector->touching_thinglist; n; n = n->m_snext) {
      if (!n->visited) {   // unprocessed thing found
        n->visited = true; // mark thing as processed
        // jff 4/7/98 don't do these
        if (!(n->m_thing->flags & MF_NOBLOCKMAP)) {
          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;
}

//
// P_FreeSecNodeList
//
void P_FreeSecNodeList(void) {
  DECLARE_BLOCK_MEMORY_ALLOC_ZONE(secnodezone);
  NULL_BLOCK_MEMORY_ALLOC_ZONE(secnodezone);
}

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) {
    return NULL;
  }

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

bool 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.

  for (node = sector_list; node != NULL; node = node->m_tnext) {
    node->m_thing = NULL;
  }

  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 = P_GetSafeBlockX(tmbbox[BOXLEFT] - bmaporgx);
  xh = P_GetSafeBlockX(tmbbox[BOXRIGHT] - bmaporgx);
  yl = P_GetSafeBlockY(tmbbox[BOXBOTTOM] - bmaporgy);
  yh = P_GetSafeBlockY(tmbbox[BOXTOP] - bmaporgy);

  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.

  for (node = sector_list; node != NULL; ) {
    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 (!prboom_comp[PC_FORCE_LXDOOM_DEMO_COMPATIBILITY].state) {
    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(const char *caller) {
  if (tmthing) {
    I_Error("P_MapStart (%s): tmthing set!", caller);
  }
}

void P_MapEnd(void) {
  tmthing = NULL;
}

/* vi: set et ts=2 sw=2: */
