// Emacs style mode select   -*- C++ -*-
//-----------------------------------------------------------------------------
//
// $Id: f61275cf5a9b350a1100d7cf2b730890b9415a92 $
//
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 2006-2026 by The Odamex Team.
//
// 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.
//
// DESCRIPTION:
//		Movement, collision handling.
//		Shooting and aiming.
//
//-----------------------------------------------------------------------------


#include "odamex.h"

#include "m_alloc.h"
#include "m_bbox.h"
#include "m_random.h"
#include "i_system.h"

#include "p_local.h"
#include "p_lnspec.h"
#include "p_boomfspec.h"
#include "c_effect.h"
#include "p_mobj.h"
#include "svc_message.h"

#include "s_sound.h"

#include "m_wdlstats.h"
#include "g_gametype.h"
#include "g_skill.h"
#include "p_mapformat.h"
// State.
#include "r_state.h"
#include "r_sky.h"

#include "z_zone.h"
#include "p_unlag.h"
#include "m_vectors.h"
#include "p_mapformat.h"
#include <math.h>
#include <set>

bool P_ShouldClipPlayer(AActor* projectile, AActor* player);

fixed_t 		tmbbox[4];
static AActor  *tmthing;
static int 		tmflags;
static fixed_t	tmx;
static fixed_t	tmy;
static fixed_t	tmz;	// [RH] Needed for third dimension of teleporters
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".
int				floatok;
int				felldown;

fixed_t 		tmfloorz;
fixed_t 		tmceilingz;
fixed_t 		tmdropoffz;
sector_t*		tmfloorsector;

extern sector_t *openbottomsec;

// keep track of the line that lowers the ceiling,
// so missiles don't explode against sky hack walls
line_t* 		ceilingline;
line_t			*BlockingLine;

// keep track of special lines as they are hit,
// but don't process them until the move is proven valid
std::vector<line_t*> spechit;

AActor *onmobj; // generic global onmobj...used for landing on pods/players
AActor *BlockingMobj;

// Temporary holder for thing_sectorlist threads
msecnode_t* sector_list = NULL;		// phares 3/16/98

// [SL] 2012-03-07 - Sectors that can change floor/ceiling height
std::set<short>	movable_sectors;

EXTERN_CVAR (co_fixweaponimpacts)
EXTERN_CVAR (co_boomphys)				// [ML] Roll-up of various compat options
EXTERN_CVAR (co_zdoomphys)
EXTERN_CVAR (co_blockmapfix)
EXTERN_CVAR (co_boomsectortouch)
EXTERN_CVAR (sv_friendlyfire)
EXTERN_CVAR (sv_friendlymonsterfire)
EXTERN_CVAR (sv_unblockplayers)
EXTERN_CVAR (sv_unblockfriendly)
EXTERN_CVAR (co_monstersclimbsteep)

CVAR_FUNC_IMPL (sv_gravity)
{
	level.gravity = var;
}

//
// TELEPORT MOVE
//

//
// PIT_StompThing
//
bool PIT_StompThing (AActor& thing, const bool StompAlwaysFrags)
{
	fixed_t blockdist;

	if (!(thing.flags & MF_SHOOTABLE))
		return true;

	// Spectators shouldn't stomp anybody, and cannot be stomped.
	if (thing.player && thing.player->spectator)
		return true;

	if (tmthing->player && tmthing->player->spectator)
		return true;

	// Unblocked players shouldn't telefrag other players.  Thanks Amateur Spammer!
	if (tmthing->player && thing.player && sv_unblockplayers)
		return true;

	// Unblocked friendlies shouldn't telefrag anyone.
	if (tmthing && thing.flags & MF_FRIEND && P_IsFriendlyThing(&thing, tmthing) &&
	    sv_unblockfriendly)
		return true;

	// don't clip against self
	if (&thing == tmthing)
		return true;

	blockdist = thing.radius + tmthing->radius;

	if (abs(thing.x - tmx) >= blockdist || abs(thing.y - tmy) >= blockdist)
	{
		// didn't hit it
		return true;
	}

	if (P_AllowPassover())
	{
		// [RH] Z-Check
		if (tmz > thing.z + thing.height)
			return true;        // overhead
		if (tmz + tmthing->height < thing.z)
			return true;        // underneath
	}

	// monsters don't stomp things except on boss level
	if (StompAlwaysFrags)
	{
		static constexpr int telefrag_damage = 10000;
		// [AM] Surprise, avatars telefrag players who try to telefrag it!
		//      Not your lucky day, I suppose.
		if (thing.type == MT_AVATAR && tmthing->player)
			P_DamageMobj(tmthing, &thing, &thing, telefrag_damage, MOD_TELEFRAG);
		else
			P_DamageMobj(&thing, tmthing, tmthing, telefrag_damage, MOD_TELEFRAG);
		return true;
	}
	return false;
}


//
// P_TeleportMove
//
// [RH] Added telefrag parameter: When true, anything in the spawn spot
//		will always be telefragged, and the move will be successful.
//		Added z parameter. Originally, the thing's z was set *after* the
//		move was made, so the height checking I added for 1.13 could
//		potentially erroneously indicate the move was okay if the thing
//		was being teleported between two non-overlapping height ranges.
bool P_TeleportMove (AActor *thing, fixed_t x, fixed_t y, fixed_t z, bool telefrag)
{
	int 				xl;
	int 				xh;
	int 				yl;
	int 				yh;
	int 				bx;
	int 				by;

	subsector_t*		newsubsec;

	// kill anything occupying the position
	tmthing = thing;

	tmx = x;
	tmy = y;
	tmz = z;

	if (!P_IsVoodooDoll(thing))
	{
		player_t* player = thing->player;

		if (player)
		{
			AActor* camera = player->camera;
			if (camera)
			{
				camera->prevx = tmx;
				camera->prevy = tmy;
				player->prevviewz = tmz + player->viewheight;
			}
		}
	}

	tmbbox[BOXTOP] = y + tmthing->radius;
	tmbbox[BOXBOTTOM] = y - tmthing->radius;
	tmbbox[BOXRIGHT] = x + tmthing->radius;
	tmbbox[BOXLEFT] = x - tmthing->radius;

	newsubsec = P_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 = P_FloorHeight(x, y, newsubsec->sector);
	tmceilingz = P_CeilingHeight(x, y, newsubsec->sector);
	tmfloorsector = newsubsec->sector;

	validcount++;
	spechit.clear();

	const bool StompAlwaysFrags = P_IsPlayerOrAvatar(*tmthing) ||
	                              (level.flags & LEVEL_MONSTERSTELEFRAG) || telefrag;

	// 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, nullptr, StompAlwaysFrags))
				return false;

	// the move is ok,
	// so link the thing into its new position
	thing->SetOrigin (x, y, z);
	thing->floorz = tmfloorz;
	thing->ceilingz = tmceilingz;
	thing->dropoffz = tmfloorz;
	thing->floorsector = tmfloorsector;

	return true;
}

//
// killough 8/28/98:
//
// P_GetFriction()
//
// Returns the friction associated with a particular mobj.

int P_GetFriction (const AActor *mo, int *frictionfactor)
{
	int friction = ORIG_FRICTION;
	int movefactor = ORIG_FRICTION_FACTOR;
	const msecnode_t *m;
	const sector_t *sec;

	if (mo->flags2 & MF2_FLY)
	{
		friction = FRICTION_FLY;
	}
	else if (mo->subsector
			&& ((!(mo->flags & MF_NOGRAVITY) && mo->waterlevel > 1)
				|| (mo->waterlevel == 1 && (mo->z > mo->floorz + 6*FRACUNIT)))
			)
	{
		friction = mo->subsector->sector->friction;
		movefactor = mo->subsector->sector->movefactor >> 1;
	}
	else if (!(mo->flags & (MF_NOCLIP|MF_NOGRAVITY)))
	{	// When the object is straddling sectors with the same
		// floorheight that have different frictions, use the lowest
		// friction value (muddy has precedence over icy).

		for (m = mo->touching_sectorlist; m; m = m->m_tnext)
			if ((sec = m->m_sector)->flags & SECF_FRICTION &&
				(sec->friction < friction || friction == ORIG_FRICTION) &&
				(mo->z <= P_FloorHeight(sec) ||
				(sec->heightsec && !(sec->heightsec->MoreFlags & SECF_IGNOREHEIGHTSEC) &&
				mo->z <= P_FloorHeight(sec))))
			  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 AActor *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;
}

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

namespace
{

//
// CheckForPushSpecial
//
void CheckForPushSpecial (line_t *line, int side, AActor *mobj)
{
	if (line->special)
	{
		if (mobj->flags2 & MF2_PUSHWALL)
		{
			P_PushSpecialLine(mobj, line, side);
		}
	}
}


// killough 3/26/98: make static
// now in anonymous namespace
bool PIT_CrossLine (const line_t& ld)
{
	if (!(ld.flags & ML_TWOSIDED) ||
		(ld.flags & (ML_BLOCKING|ML_BLOCKMONSTERS|ML_BLOCKEVERYTHING)))
		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

//
// PIT_CheckLine
// Adjusts tmfloorz and tmceilingz as lines are contacted
//

// killough 3/26/98: make static
// now in anonymous namespace
bool PIT_CheckLine(line_t& ld, bool tmunstuck)
{
	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;


	const auto untouched = [](line_t& ld)
	{
		const fixed_t x = tmthing->x;
		const fixed_t y = tmthing->y;
		std::array<fixed_t, 4> tmbbox{};
		tmbbox[BOXRIGHT] = x+tmthing->radius;
    	tmbbox[BOXLEFT] = x-tmthing->radius;
    	tmbbox[BOXTOP] = y+tmthing->radius;
    	tmbbox[BOXBOTTOM] = y-tmthing->radius;
  		return
    		tmbbox[BOXRIGHT] <= ld.bbox[BOXLEFT] ||
    		tmbbox[BOXLEFT]>= ld.bbox[BOXRIGHT] ||
    		tmbbox[BOXTOP] <= ld.bbox[BOXBOTTOM] ||
    		tmbbox[BOXBOTTOM] >= ld.bbox[BOXTOP] ||
    		P_BoxOnLineSide(tmbbox.data(), &ld) != -1;
	};

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

	if (!ld.backsector)
	{ // One sided line
		BlockingLine = &ld;
		CheckForPushSpecial(&ld, 0, tmthing);
		return tmunstuck && !untouched(ld) && FixedMul(tmx-tmthing->x,ld.dy) > FixedMul(tmy-tmthing->y,ld.dx);
	}

    if (!(tmthing->flags & (MF_MISSILE | MF_BOUNCES)) || (ld.flags & ML_BLOCKEVERYTHING))
    {
		if ((ld.flags &
		     (ML_BLOCKING | ML_BLOCKEVERYTHING)) || // explicitly blocking everything
		    (P_IsPlayerOrAvatar(*tmthing) &&
		     (ld.flags & ML_BLOCKPLAYERS))) // [Blair] Block players only
		{
			CheckForPushSpecial(&ld, 0, tmthing);
			return tmunstuck && !untouched(ld);
		}

		if ((!P_IsPlayerOrAvatar(*tmthing) && !(tmthing->flags & MF_FRIEND) && (ld.flags & ML_BLOCKMONSTERS)) || // block monsters only
		    (!P_IsPlayerOrAvatar(*tmthing) && !(tmthing->flags & MF_FRIEND) && (ld.flags & ML_BLOCKLANDMONSTERS) &&
		     !(tmthing->flags & MF_FLOAT))) // [Blair] Block land monsters.
		{
			CheckForPushSpecial(&ld, 0, tmthing);
			return false;
		}
    }

	// [RH] Steep sectors count as dropoffs (unless already in one)
	if (!(tmthing->flags & MF_DROPOFF) &&
		!(tmthing->flags & (MF_NOGRAVITY|MF_NOCLIP)))
	{
		if (ld.frontsector->floorplane.c < STEEPSLOPE ||
			ld.backsector->floorplane.c < STEEPSLOPE)
		{
			const msecnode_t *node = tmthing->touching_sectorlist;
			bool allow = false;
			while (node != NULL)
			{
				if (node->m_sector->floorplane.c < STEEPSLOPE)
				{
					allow = true;
					break;
				}
				node = node->m_tnext;
			}

			if (!allow)
				return false;
		}
	}

	// set openrange, opentop, openbottom

	// Are the sectors on both sides of the line non-sloped?
	if (P_IsPlaneLevel(&ld.frontsector->floorplane) &&
		P_IsPlaneLevel(&ld.backsector->floorplane) &&
		P_IsPlaneLevel(&ld.frontsector->ceilingplane) &&
		P_IsPlaneLevel(&ld.backsector->ceilingplane))
	{
		P_LineOpening(&ld, tmx, tmy, tmx, tmy);
	}
	else
	{
		// Find the point on the line closest to the actor's center, and use
		// that to calculate openings
		const double dx = FIXED2DOUBLE(ld.dx);
		const double dy = FIXED2DOUBLE(ld.dy);
		const double r  = ((FIXED2DOUBLE(tmx - ld.v1->x) * dx) +
		                   (FIXED2DOUBLE(tmy - ld.v1->y) * dy)) /
		                   ((dx * dx) + (dy * dy));

		if (r <= 0.0)
		{
			P_LineOpening (&ld, ld.v1->x, ld.v1->y, tmx, tmy);
		}
		else if (r >= 1.0)
		{
			P_LineOpening (&ld, ld.v2->x, ld.v2->y, tmthing->x, tmthing->y);
		}
		else
		{
			const fixed_t sx = ld.v1->x + (r * ld.dx);
			const fixed_t sy = ld.v1->y + (r * ld.dy);
			P_LineOpening (&ld, sx, sy, tmx, tmy);
		}
	}

	// adjust floor / ceiling heights
	if (opentop < tmceilingz)
	{
		tmceilingz = opentop;
		ceilingline = &ld;
		BlockingLine = &ld;
	}

	if (openbottom > tmfloorz)
	{
		tmfloorz = openbottom;
		tmfloorsector = openbottomsec;
		BlockingLine = &ld;
	}

	if (lowfloor < tmdropoffz)
		tmdropoffz = lowfloor;

	// if contacted a special line, add it to the list
	if (ld.special)
		spechit.push_back(&ld);

	return true;
}

} // namespace

/*
 * @brief Determines if a projectile should clip a friendly monster.
 *
 * @param projectile (suspected) projectile actor
 * @param player (suspected) player actor
 * @return true if the player should be clipped.
 */
bool P_ShouldClipFriendly(AActor* projectile, AActor* monster)
{
	if (!sv_unblockfriendly)
	{
		return true; // Clip all friendlies all the time.
	}
	else if (projectile->target && projectile->target->flags & MF_FRIEND && P_IsFriendlyThing(projectile->target, monster))
	{
		if (sv_friendlymonsterfire && !P_ProjectileImmune(monster, projectile->target))
		{
			return true; // Always clip if friendly monster fire is on.
		}
		else
		{
			return false; // Friendly monster
		}
	}
	else
	{
		return true; // Not a friendly.
	}
}

/*
 * @brief Determines if a projectile should clip a player.
 *
 * @param projectile (suspected) projectile actor
 * @param player (suspected) player actor
 * @return true if the player should be clipped.
 */
bool P_ShouldClipPlayer(AActor* projectile, AActor* player)
{
	if (!sv_unblockplayers)
	{
		return true; // Clip all players all the time.
	}
	else if (projectile->target && projectile->target->player && player->player)
	{
		if (sv_friendlyfire)
		{
			return true; // Always clip if friendly fire is on.
		}
		else if (G_IsCoopGame() ||
		    (projectile->target->player->userinfo.team == player->player->userinfo.team &&
		     G_IsTeamGame()))
		{
			return false; // Friendly player
		}
		else
		{
			return true; // Enemy player
		}
	}
	else
	{
		return true; // Not a player.
	}
}

//
// PIT_CheckThing
//

bool P_ProjectileImmune(AActor* target, AActor* source)
{
	return ( // PG_GROUPLESS means no immunity, even to own species
	           mobjinfo[target->type].projectile_group != PG_GROUPLESS ||
	           target == source) &&
	       (( // target type has default behaviour, and things are the same type
	            mobjinfo[target->type].projectile_group == PG_DEFAULT &&
	            source->type == target->type) ||
	        ( // target type has special behaviour, and things have the same group
	            mobjinfo[target->type].projectile_group != PG_DEFAULT &&
	            mobjinfo[target->type].projectile_group ==
	                mobjinfo[source->type].projectile_group));
}

namespace
{

bool PIT_CheckThing (AActor& thing)
{
	const bool solid = thing.flags & MF_SOLID;

	// don't clip against self
	if (&thing == tmthing)
		return true;

	if (!(thing.flags & (MF_SOLID|MF_SPECIAL|MF_SHOOTABLE|MF_TOUCHY)) )
		return true;	// can't hit thing

	// GhostlyDeath -- Spectators go through everything!
	if ((thing.player && thing.player->spectator) ||
		(tmthing->player && tmthing->player->spectator))
		return true;

	if (tmthing->player && thing.player && sv_unblockplayers)
		return true;

	if (tmthing && thing.flags & MF_FRIEND &&
	    P_IsFriendlyThing(&thing, tmthing) && sv_unblockfriendly)
		return true;

	const fixed_t blockdist = thing.radius + tmthing->radius;
	if (abs(thing.x - tmx) >= blockdist || abs(thing.y - tmy) >= blockdist)
	{
		// didn't hit thing
		return true;
	}

	if (P_AllowPassover())
		BlockingMobj = &thing;

	if (P_AllowPassover() && (tmthing->flags2 & MF2_PASSMOBJ))
	{
		// check if a mobj passed over/under another object
		if (tmthing->z >= thing.z + thing.height || tmthing->z + tmthing->height <= thing.z)
			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.oflags & MFO_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, nullptr, nullptr, thing.health); // kill object
		return true;
	}

	// check for skulls slamming into things
	if (tmthing->flags & MF_SKULLFLY)
	{
		int damage = ((P_Random(tmthing)%8)+1) * tmthing->info->damage;
		P_DamageMobj (&thing, tmthing, tmthing, damage, MOD_HIT);
		tmthing->flags &= ~MF_SKULLFLY;
		tmthing->momx = tmthing->momy = tmthing->momz = 0;
		P_SetMobjState (tmthing, tmthing->info->spawnstate);
		if (P_AllowPassover())
			BlockingMobj = NULL;
		return false;			// stop moving
	}

	// missiles can hit other things
	// [Blair] This emulates hexen behavior, where rockets can push
	// dead/stationary things marked bouncy.
	// Out of place in Doom, should fix.
	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

    // Check with projectiles owner if we can explode
		if (tmthing->target &&
			(P_ProjectileImmune(&thing, tmthing->target) &&
		    !((level.flags2 & LEVEL2_INFIGHTINGMASK) ?
			    level.flags2 & LEVEL2_TOTALINFIGHTING :
			    G_GetCurrentSkill().flags & SKILL_TOTALINFIGHTING)))
		{
			// Don't hit same species as originator
			if (&thing == tmthing->target)
				return true;

			if (!thing.player)
			{
				// Run friendly clip check early if same species
				if ((thing.flags & tmthing->target->flags & MF_FRIEND) &&
				    !P_ShouldClipFriendly(tmthing, &thing))
					return true;

				// [RH] DeHackEd infighting is here.
				if (!deh.Infight &&
						(!((thing.flags ^ tmthing->target->flags) & MF_FRIEND) ||
						(thing.flags & tmthing->target->flags & MF_FRIEND && P_IsFriendlyThing(&thing, tmthing->target))))
					return false; // Hit same species as originator, explode, no damage
			}
		}

		if (!(thing.flags & MF_SHOOTABLE))
			return !solid;		// didn't do any damage

		// Don't clip the projectile unless it's not a teammate.
		if (!P_ShouldClipPlayer(tmthing, &thing))
			return true;

		// Don't clip the projectile unless it's not a friendly.
		if (!P_ShouldClipFriendly(tmthing, &thing))
			return true;

		if (tmthing->flags2 & MF2_RIP)
		{
			int damage = ((P_Random(tmthing) & 3) + 2) * tmthing->info->damage;
			if (!(thing.flags & MF_NOBLOOD))
				P_SpawnBlood(tmthing->x, tmthing->y, tmthing->z, damage);
			if (tmthing->info->ripsound)
				S_Sound(tmthing, CHAN_VOICE, tmthing->info->ripsound, 1, ATTN_NORM);

			P_DamageMobj(&thing, tmthing, tmthing->target, damage, MOD_UNKNOWN);
			if (thing.flags2 & MF2_PUSHABLE && !(tmthing->flags2 & MF2_CANNOTPUSH))
			{ // Push thing
				thing.momx += tmthing->momx >> 2;
				thing.momy += tmthing->momy >> 2;
			}

			return true;
		}

		// damage / explode
		if (tmthing->info->damage)
		{
			int damage = ((P_Random(tmthing)%8)+1) * tmthing->info->damage;
			{
				// [RH] figure out the means of death
				int mod;

				switch (tmthing->type) {
					case MT_ROCKET:
						mod = MOD_ROCKET;
						break;
					case MT_PLASMA:
						mod = MOD_PLASMARIFLE;
						break;
					case MT_BFG:
						mod = MOD_BFG_BOOM;
						break;
					// [AM] Monster fireballs get a special MOD.
				  // Unless they're from players
					default:
							if ((tmthing->target && tmthing->target->player) || !tmthing->target)
							{
						        mod = MOD_UNKNOWN;
							}
							else
							{
						        mod = MOD_FIREBALL;
							}
						break;
				}
				P_DamageMobj (&thing, tmthing, tmthing->target, damage, mod);
			}
		}

		return false;		// don't traverse any more
	}

	// check for special pickup
	if (thing.flags & MF_SPECIAL && tmthing->flags & MF_PICKUP)
	{
		// [SL] Work-around the additional height added to players
		// in P_CheckPosition. Don't let players grab items above
		// their real height!

		fixed_t max_z = tmthing->z + tmthing->height;

		if (tmthing->player)
			max_z -= 24 * FRACUNIT;

		if (!P_AllowPassover() || thing.z < max_z)
			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
	// ...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 (demoplayback || !co_boomphys
	    //&& !prboom_comp[PC_TREAT_NO_CLIPPING_THINGS_AS_NOT_BLOCKING].state
			)
		return !(thing.flags & MF_SOLID);
	else
		return !((thing.flags & MF_SOLID && !(thing.flags & MF_NOCLIP)) &&
		         (tmthing->flags & MF_SOLID || (demoplayback || !co_boomphys)));
}

} // namespace


// 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(const AActor* actor, int x, int y)
{
	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

	const int xl = (tmbbox[BOXLEFT]   - bmaporgx)>>MAPBLOCKSHIFT;
	const int xh = (tmbbox[BOXRIGHT]  - bmaporgx)>>MAPBLOCKSHIFT;
	const int yl = (tmbbox[BOXBOTTOM] - bmaporgy)>>MAPBLOCKSHIFT;
	const int yh = (tmbbox[BOXTOP]    - bmaporgy)>>MAPBLOCKSHIFT;

	// xl->xh, yl->yh determine the mapblock set to search

	validcount++; // prevents checking same line twice
	for (int bx = xl ; bx <= xh ; bx++)
		for (int by = yl ; by <= yh ; by++)
			if (!P_BlockLinesIterator(bx,by,PIT_CrossLine))
				return true;										//   ^
	return false;												//   |
}																// phares


//---------------------------------------------------------------------------
//
// PIT_CheckOnmobjZ
//
//---------------------------------------------------------------------------

bool PIT_CheckOnmobjZ (AActor& thing)
{
	if (!(thing.flags & MF_SOLID))
		return true;

	// [RH] Corpses and specials don't block moves
	if (thing.flags & (MF_CORPSE|MF_SPECIAL))
		return true;

	// Don't clip against self
	if (&thing == tmthing)
		return true;

	// Don't clip against a player
	if (tmthing->player && thing.player && sv_unblockplayers)
		return true;

	// Don't clip against friendlies
	if (tmthing && thing.flags & MF_FRIEND &&
	    P_IsFriendlyThing(&thing, tmthing) && sv_unblockfriendly)
		return true;

	// over / under thing
	if (tmthing->z > thing.z + thing.height)
		return true;
	if (tmthing->z + tmthing->height <= thing.z)
		return true;

	// Don't clip the projectile unless it's not a teammate.
	if (tmthing->flags & MF_MISSILE && !P_ShouldClipPlayer(tmthing, &thing))
		return true;

	const fixed_t blockdist = thing.radius+tmthing->radius;
	if (abs(thing.x - tmx) >= blockdist || abs(thing.y - tmy) >= blockdist)
		return true;		// Didn't hit thing

	onmobj = &thing;
	return false;
}

//
// MOVEMENT CLIPPING
//

//
// P_TestMobjLocation
//
// Returns true if the mobj is not blocked by anything at its current
// location, otherwise returns false.
//
bool P_TestMobjLocation (AActor *mobj)
{
	int flags = mobj->flags;
	mobj->flags &= ~MF_PICKUP;

	if (P_CheckPosition(mobj, mobj->x, mobj->y))
	{
		// XY is ok, now check Z
		mobj->flags = flags;
		if ((mobj->z < mobj->floorz) || (mobj->z + mobj->height > mobj->ceilingz))
			return false;		// Bad Z

		return true;
	}

	mobj->flags = flags;
	return false;
}

//
// P_CheckPosition
// This is purely informative, nothing is modified
// (except things picked up).
//
// in:
//	a AActor (can be valid or invalid)
//	a position to be checked
//	 (doesn't need to be related to the AActor->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
//  AActor *BlockingMobj = pointer to thing that blocked position (NULL if not
//   blocked, or blocked by a line).
bool P_CheckPosition (AActor *thing, fixed_t x, fixed_t y)
{
	AActor *thingblocker = NULL;
	fixed_t realheight = thing->height;
	bool spectator = thing->player && thing->player->spectator;
	subsector_t *subsec = P_PointInSubsector(x,y);
	// NOTE(jsd): NULL check here fixes crash while awaiting download in an active game.
	if (subsec == NULL)
		return false;
	sector_t *newsec = subsec->sector;

	tmbbox[BOXTOP] = y + thing->radius;
	tmbbox[BOXBOTTOM] = y - thing->radius;
	tmbbox[BOXRIGHT] = x + thing->radius;
	tmbbox[BOXLEFT] = x - thing->radius;

	// The base floor / ceiling is from the subsector that contains the point.
	// Any contacted lines the step closer together will adjust them.
	tmfloorz = tmdropoffz = P_FloorHeight(x, y, newsec);
	tmceilingz = P_CeilingHeight(x, y, newsec);
	tmfloorsector = newsec;
	tmthing = thing;
	tmflags = thing->flags;
	tmx = x;
	tmy = y;
	ceilingline = BlockingLine = NULL;

	validcount++;
	spechit.clear();

	if (tmflags & MF_NOCLIP && !(tmflags & MF_SKULLFLY))
		return true;

	// Check things first, possibly picking things up.
	// The bounding box is extended by MAXRADIUS
	// because DActors are grouped into mapblocks
	// based on their origin point, and can overlap
	// into adjacent blocks by up to MAXRADIUS units.
	int xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT;
	int xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT;
	int yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT;
	int yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT;

	BlockingMobj = NULL;

	if (P_AllowPassover() && !spectator)
	{
		if (thing->player)	// [RH] Fake taller height to catch stepping up into things.
			thing->height += 24*FRACUNIT;

		for (int bx = xl; bx <= xh; bx++)
		{
			for (int by = yl; by <= yh; by++)
			{
				AActor *robin = NULL;
				do
				{
					if (!P_BlockThingsIterator (bx, by, PIT_CheckThing, robin))
					{ // [RH] If a thing can be stepped up on, we need to continue checking
					  // other things in the blocks and see if we hit something that is
					  // definitely blocking. Otherwise, we need to check the lines, or we
					  // could end up stuck inside a wall.
						if (BlockingMobj == NULL)
						{ // Thing slammed into something; don't let it move now.
							thing->height = realheight;
							return false;
						}
						else if (!BlockingMobj->player && thing->player &&
							BlockingMobj->z + BlockingMobj->height - thing->z <= 24*FRACUNIT)
						{
							if (thingblocker == NULL ||	BlockingMobj->z > thingblocker->z)
								thingblocker = BlockingMobj;

							robin = BlockingMobj->bmapnode.Next(bx, by);
							BlockingMobj = NULL;
						}
						else if (thing->player &&
							thing->z + thing->height - BlockingMobj->z <= 24*FRACUNIT)
						{
							if (thingblocker)
							{ // There is something to step up on. Return this thing as
							  // the blocker so that we don't step up.
								thing->height = realheight;
								return false;
							}
							// Nothing is blocking us, but this actor potentially could
							// if there is something else to step on.
							robin = BlockingMobj->bmapnode.Next(bx, by);
							BlockingMobj = NULL;
						}
						else
						{ // Definitely blocking
							thing->height = realheight;
							return false;
						}
					}
					else
					{
						robin = NULL;
					}
				} while (robin);
			}
		}

		BlockingMobj = NULL;
		thing->height = realheight;
		if (tmflags & MF_NOCLIP)
			return (BlockingMobj = thingblocker) == NULL;
		if (tmceilingz - tmfloorz < thing->height)
			return false;
	}
	else
	{
		// vanilla Doom's check for blocking things
		for (int bx=xl ; bx<=xh ; bx++)
			for (int by=yl ; by<=yh ; by++)
				if (!P_BlockThingsIterator(bx,by,PIT_CheckThing, nullptr))
					return false;

		if (tmflags & MF_NOCLIP)
			return true;
	}

	// check lines
	xl = (tmbbox[BOXLEFT] - bmaporgx)>>MAPBLOCKSHIFT;
	xh = (tmbbox[BOXRIGHT] - bmaporgx)>>MAPBLOCKSHIFT;
	yl = (tmbbox[BOXBOTTOM] - bmaporgy)>>MAPBLOCKSHIFT;
	yh = (tmbbox[BOXTOP] - bmaporgy)>>MAPBLOCKSHIFT;

	// from mbf, allows players to get unstuck if they end up spawned
	// partially inside a wall or blocking line
	// i think this is safe to enable always (except for demo playback of course)
	// but if it turns out to cause issues with vanilla or boom maps,
	// we should change it to rely on co_mbfphys
	const bool tmunstuck = (thing->player != nullptr) && !P_IsVoodooDoll(thing) && !demoplayback;

	for (int bx=xl ; bx<=xh ; bx++)
		for (int by=yl ; by<=yh ; by++)
			if (!P_BlockLinesIterator (bx,by,PIT_CheckLine, tmunstuck))
				return false;

	if (P_AllowPassover())
		return (BlockingMobj = thingblocker) == NULL;

	return true;
}


//
// P_CheckOnmobj(AActor *thing)
// Checks if the new Z position is legal
//

AActor *P_CheckOnmobj (AActor *thing)
{
	const fixed_t oldz = thing->z;
	P_FakeZMovement (thing);
	const bool good = P_TestMobjZ (thing);
	thing->z = oldz;

	return good ? nullptr : onmobj;
}

bool P_TestMobjZ (AActor *actor)
{
	int	xl,xh,yl,yh,bx,by;
	fixed_t x, y;

	if (actor->flags & MF_NOCLIP)
		return true;

	if (!(actor->flags & MF_SOLID))
		return true;

	tmx = x = actor->x;
	tmy = y = actor->y;
	tmthing = actor;

	tmbbox[BOXTOP] = y + actor->radius;
	tmbbox[BOXBOTTOM] = y - actor->radius;
	tmbbox[BOXRIGHT] = x + actor->radius;
	tmbbox[BOXLEFT] = x - actor->radius;
//
// the bounding box is extended by MAXRADIUS because actors 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_CheckOnmobjZ, nullptr))
				return false;

	return true;
}


//
// P_FakeZMovement
// Fake the zmovement so that we can check if a move is legal
//
void P_FakeZMovement(AActor *mo)
{
//
// adjust height
//
	mo->z += mo->momz;
	if ((mo->flags&MF_FLOAT) && mo->target)
	{ // float down towards target if too close
		if (!(mo->flags & MF_SKULLFLY) && !(mo->flags & MF_INFLOAT))
		{
			fixed_t dist = P_AproxDistance (mo->x - mo->target->x, mo->y - mo->target->y);
			fixed_t delta = (mo->target->z + (mo->height>>1)) - mo->z;
			if (delta < 0 && dist < -(delta*3))
				mo->z -= FLOATSPEED;
			else if (delta > 0 && dist < (delta*3))
				mo->z += FLOATSPEED;
		}
	}
	if (mo->player && mo->flags2&MF2_FLY && (mo->z > mo->floorz))
	{
		mo->z += finesine[(FINEANGLES/80*level.time)&FINEMASK]/8;
	}

//
// clip movement
//
	if (mo->z <= mo->floorz)
	{ // hit the floor
		mo->z = mo->floorz;
	}

	if (mo->z + mo->height > mo->ceilingz)
	{ // hit the ceiling
		mo->z = mo->ceilingz - mo->height;
	}
}


void P_CheckPushLines(AActor *thing)
{
	if (!thing || !map_format.getZDoom())
		return;

	if (!(thing->flags&(MF_TELEPORT|MF_NOCLIP)))
	{
		for (size_t i = 0; i < spechit.size(); i++)
		{
			// see which lines were pushed
			line_t *ld = spechit[i];
			int side = P_PointOnLineSide(thing->x, thing->y, ld);
			CheckForPushSpecial(ld, side, thing);
		}
	}
}

//
// P_TryMove
// Attempt to move to a new position,
// crossing special lines unless MF_TELEPORT is set.
//
bool P_TryMove (AActor *thing, fixed_t x, fixed_t y,
				int dropoff, // killough 3/15/98: allow dropoff as option
				bool onfloor) // [RH] Let P_TryMove keep the thing on the floor
{
	fixed_t		testz = thing->z;

	if(not thing->subsector)
	{
		I_Error("P_TryMove: Thing {{type: {}, info->type: {}}} subsector was null", thing->type, thing->info->type);
	}

	sector_t*	oldsec = thing->subsector->sector;	// [RH] for sector actions

	felldown = floatok = false;

	if (onfloor)
		testz = P_FloorHeight(x, y, thing->floorsector);

	if (!P_CheckPosition(thing, x, y))
	{
		// Solid wall or thing
		if ((!BlockingMobj || BlockingMobj->player || !thing->player) ||
			(BlockingMobj->z + BlockingMobj->height - testz > (24 * FRACUNIT) ||
			(P_CeilingHeight(x, y, BlockingMobj->subsector->sector) -
			(BlockingMobj->z + BlockingMobj->height) < thing->height) ||
			(tmceilingz - (BlockingMobj->z + BlockingMobj->height) < thing->height)))
		{
			P_CheckPushLines(thing);
			return false;
		}

		if (!P_AllowPassover() || !(tmthing->flags2 & MF2_PASSMOBJ))
			return false;
	}

	if (onfloor && tmfloorsector == thing->floorsector)
		testz = tmfloorz;

	if (!(thing->flags & MF_NOCLIP) && !(thing->player && thing->player->spectator))
	{
		if (tmceilingz - tmfloorz < thing->height)
		{
			// doesn't fit
			P_CheckPushLines(thing);
			return false;
		}

		floatok = true;

		if (!(thing->flags & MF_TELEPORT)
			&& tmceilingz - testz < thing->height
			&& !(thing->flags2 & MF2_FLY))
		{
			// mobj must lower itself to fit
			P_CheckPushLines(thing);
			return false;
		}

		if (thing->flags2 & MF2_FLY && testz + thing->height > tmceilingz)
		{
			P_CheckPushLines(thing);
			return false;
		}

		if (!(thing->flags & MF_TELEPORT))
		{
			if (tmfloorz - testz > 24*FRACUNIT)
			{
				// too big a step up
				P_CheckPushLines(thing);
				return false;
			}
			else if (co_fixweaponimpacts && thing->flags & MF_MISSILE &&
					tmfloorz > testz)
			{
				// [SL] 2011-09-16 - Fix the vanilla Doom bug that allows
				// missiles to climb stairs.
				P_CheckPushLines(thing);
				return false;
			}
			else if (P_AllowPassover() && testz < tmfloorz)
			{
				// [RH] Check to make sure there's nothing in the way for the step up
				fixed_t savedz = thing->z;
				thing->z = tmfloorz;
				bool good = P_TestMobjZ(thing);
				thing->z = savedz;
				if (!good)
				{
					P_CheckPushLines(thing);
					return false;
				}
			}
		}

		/* 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
		*/
		// killough 3/15/98: Allow certain objects to drop off
		if (!(thing->flags & (MF_DROPOFF | MF_FLOAT | MF_MISSILE)))
		{
			if (P_AllowDropOff())
			{
				if (!dropoff || (dropoff == 2 && // large jump down (e.g. dogs)
				                 (tmfloorz - tmdropoffz > 128 * FRACUNIT ||
				                  !thing->target || thing->target->z > tmdropoffz)))
				{
					if (!co_monstersclimbsteep || !P_IsMBFCompatMode()
					        ? 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;
				}
			}
			else if (!(P_AllowDropOff() && dropoff))
			{
				// Allowing blasting off ledges without allowing dropoff is a Hexen thing
				// But this doesn't seem to break any demos
				if (tmfloorz - tmdropoffz > 24 * FRACUNIT &&
				    !(thing->flags2 & MF2_BLASTED))
				{
					return false;
				}
			}
		}

		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 (co_zdoomphys && thing->oflags & MFO_FALLING && tmfloorz - testz >
			FixedMul(thing->momx, thing->momx) + FixedMul(thing->momy, thing->momy))
		{
			return false;
		}
	}

	// the move is ok, so link the thing into its new position
	thing->UnlinkFromWorld ();

	fixed_t oldx = thing->x;
	fixed_t oldy = thing->y;
	thing->floorz = tmfloorz;
	thing->ceilingz = tmceilingz;
	thing->dropoffz = tmdropoffz;		// killough 11/98: keep track of dropoffs
	thing->floorsector = tmfloorsector;
	thing->x = x;
	thing->y = y;
	thing->z = testz;

	thing->LinkToWorld ();

	// if any special lines were hit, do the effect
	if (! (thing->flags&(MF_TELEPORT|MF_NOCLIP)) )
	{
		while (!spechit.empty())
		{
			// see if the line was crossed
			line_t *ld = spechit.back();
			spechit.pop_back();

			int side = P_PointOnLineSide (thing->x, thing->y, ld);
			int oldside = P_PointOnLineSide (oldx, oldy, ld);
			if (side != oldside && ld->special)
				P_CrossSpecialLine (ld, oldside, thing, false);
		}
	}

	// [RH] If changing sectors, trigger transitions
	sector_t *newsec = thing->subsector->sector;
	if (oldsec != newsec)
	{
		if (oldsec->SecActTarget)
		{
			A_TriggerAction(oldsec->SecActTarget, thing, SECSPAC_Exit);
		}
		if (newsec->SecActTarget)
		{
			int act = SECSPAC_Enter;
			if (testz <= P_FloorHeight(thing->x, thing->y, newsec))
			{
				act |= SECSPAC_HitFloor;
			}
			if (testz + thing->height >= P_CeilingHeight(thing->x, thing->y, newsec))
			{
				act |= SECSPAC_HitCeiling;
			}
			A_TriggerAction(newsec->SecActTarget, thing, act);
		}
	}

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

namespace
{

bool PIT_ApplyTorque (const 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)
	{
		AActor *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
			P_FloorHeight(mo->x, mo->y, ld.frontsector) < mo->z &&
			P_FloorHeight(mo->x, mo->y, ld.backsector) >= mo->z :
				P_FloorHeight(mo->x, mo->y, ld.backsector) < mo->z &&
				P_FloorHeight(mo->x, mo->y, ld.frontsector) >= 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 = abs(ld.dx);
			fixed_t y = 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;
}

} // namespace

//
// killough 9/12/98
//
// Applies "torque" to objects, based on all contacted linedefs
//

void P_ApplyTorque (AActor *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;
	int flags = mo->oflags;	//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->oflags |= MFO_FALLING;
	else  // Clear the engine-internal flag indicating falling object.
		mo->oflags &= ~MFO_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->oflags | flags) & MFO_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 (AActor* thing)
{
	if (!thing)
		return true;

	bool onfloor = (thing->z <= thing->floorz);

	AActor *underthing = P_CheckOnmobj(thing);
	bool onthing = P_AllowPassover() && underthing && underthing->z < thing->z;

	// calculate new floorz/ceilingz, etc
	P_CheckPosition (thing, thing->x, thing->y);

	thing->floorz = tmfloorz;
	thing->ceilingz = tmceilingz;
	thing->dropoffz = tmdropoffz;
	thing->floorsector = tmfloorsector;

	// standing on another actor - adjust the actor underneath first
	if (onthing && !P_ThingHeightClip(underthing))
		return false;

	fixed_t newz = (onthing) ?
					underthing->z + underthing->height :
					thing->floorz;

	if (onfloor || onthing)
	{
		if (!serverside && thing->player && thing->z != newz)
		{
			// [AM] The player's Z-axis was changed, do not try
			//      and further correct it with snapshots.  Also,
			//      do not remove the player check - we currently
			//      only unset this flag for players.
			thing->oflags |= MFO_NOSNAPZ;
		}
		thing->z = newz;

		/* killough 11/98: Possibly upset balance of objects hanging off ledges */
		if (thing->oflags & MFO_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;
	}

	// thing won't fit
	if (thing->ceilingz - newz < thing->height)
		return false;

	return true;
}


//============================================================================
//
// P_CheckSlopeWalk
//
//============================================================================

bool P_CheckSlopeWalk (AActor *actor, fixed_t &xmove, fixed_t &ymove)
{
	if (!actor
	 ||  actor->flags & MF_NOGRAVITY
	 || !actor->floorsector
	 || !actor->subsector)
		return false;

	const plane_t *plane = &actor->floorsector->floorplane;

	if (actor->floorsector != actor->subsector->sector)
		return false;

	// Don't bother with non-sloping floors
	if (P_IsPlaneLevel(plane))
		return false;

	fixed_t floorheight = P_FloorHeight(actor->x, actor->y, actor->floorsector);

	// not on floor ?
	if (actor->z - floorheight > FRACUNIT)
		return false;

	fixed_t destx = actor->x + xmove;
	fixed_t desty = actor->y + ymove;
	fixed_t t = FixedMul(plane->a, destx) + FixedMul(plane->b, desty) +
				FixedMul(plane->c, actor->z) + plane->d;

	if (t < 0)
	{ // Desired location is behind (below) the plane
	  // (i.e. Walking up the plane)
		if (plane->c < STEEPSLOPE)
		{
			// Can't climb up slopes of ~45 degrees or more
			if (actor->flags & MF_NOCLIP || (actor->player && actor->player->spectator))
				return true;
			else
			{
				const msecnode_t *node;
				bool dopush = true;

				if (plane->c > STEEPSLOPE*2/3)
				{
					for (node = actor->touching_sectorlist; node; node = node->m_tnext)
					{
						const sector_t *sec = node->m_sector;
						if (sec->floorplane.c >= STEEPSLOPE)
						{
							if (P_FloorHeight(destx, desty, sec) >= actor->z - 24*FRACUNIT)
							{
								dopush = false;
								break;
							}
						}
					}
				}
				if (dopush)
				{
					xmove = actor->momx = plane->a * 2;
					ymove = actor->momy = plane->b * 2;
				}
				return false;
			}
		}

		// Slide the desired location along the plane's normal
		// so that it lies on the plane's surface
		xmove -= FixedMul(plane->a, t);
		ymove -= FixedMul(plane->b, t);
		return true;
	}
	else if (t > 0)
	{ // Desired location is in front of (above) the plane
		if (floorheight == actor->z)
		{
			// Actor's current spot is on/in the plane, so walk down it
			// Same principle as walking up, except reversed
			xmove += FixedMul(plane->a, t);
			ymove += FixedMul(plane->b, t);
			return true;
		}
	}

	return false;
}

//
// SLIDE MOVE
// Allows the player to slide along any angled walls.
//
fixed_t 		bestslidefrac;
fixed_t 		secondslidefrac;

line_t* 		bestslideline;
line_t* 		secondslideline;

AActor* 		slidemo;

fixed_t 		tmxmove;
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?							// 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)
	icyfloor =
		slidemo->player && !slidemo->player->spectator &&	// Ch0wW: disables bouncing as a flying spectator. (prevents grunt1 sound from playing)
		(P_AproxDistance(tmxmove, tmymove) > 4*FRACUNIT) &&
		slidemo->z <= slidemo->floorz &&		  // killough 8/28/98: calc friction on demand
		P_GetFriction (slidemo, NULL) > ORIG_FRICTION;

	if (ld->slopetype == ST_HORIZONTAL)
	{
		if (icyfloor && (abs(tmymove) > abs(tmxmove)))
		{
			tmxmove /= 2; // absorb half the momentum
			tmymove = -tmymove/2;
			UV_SoundAvoidPlayer(slidemo, CHAN_VOICE, "player/male/grunt1", ATTN_IDLE);
		}
		else
			tmymove = 0; // no more movement in the Y direction
		return;
	}

	if (ld->slopetype == ST_VERTICAL)
	{
		if (icyfloor && (abs(tmxmove) > abs(tmymove)))
		{
			tmxmove = -tmxmove/2; // absorb half the momentum
			tmymove /= 2;
			UV_SoundAvoidPlayer(slidemo, CHAN_VOICE, "player/male/grunt1", ATTN_IDLE);
		}																		//   |
		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 = P_PointToAngle (0,0, ld->dx, ld->dy);

	if (side == 1)
		lineangle += ANG180;

	moveangle = P_PointToAngle (0,0, tmxmove, tmymove);
	deltaangle = moveangle-lineangle;

	if (icyfloor && (deltaangle > ANG45) && (deltaangle < ANG90+ANG45))
	{
		moveangle += 10;	// prevents sudden path reversal due to		// phares
							// rounding error							//   |
		deltaangle = moveangle-lineangle;								//   V
		movelen = P_AproxDistance (tmxmove, tmymove);

		moveangle = lineangle - deltaangle;
		movelen /= 2; // absorb
		UV_SoundAvoidPlayer(slidemo, CHAN_VOICE, "player/male/grunt1", ATTN_IDLE);

		moveangle >>= ANGLETOFINESHIFT;
		tmxmove = FixedMul (movelen, finecosine[moveangle]);
		tmymove = FixedMul (movelen, finesine[moveangle]);
	}																//   ^
	else															//   |
	{																// phares
		if (deltaangle > ANG180)
			deltaangle += ANG180;

		lineangle >>= ANGLETOFINESHIFT;
		deltaangle >>= ANGLETOFINESHIFT;

		movelen = P_AproxDistance (tmxmove, tmymove);
		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: non-line intercept\n");

	li = in->d.line;

	if ( ! (li->flags & ML_TWOSIDED) )
	{
		if (P_PointOnLineSide (slidemo->x, slidemo->y, li))
		{
			// don't hit the back side
			return true;
		}
		goto isblocking;
	}

	// set openrange, opentop, openbottom
	P_LineOpening(li, trace.x + FixedMul(trace.dx, in->frac),
				trace.y + FixedMul(trace.dy, in->frac));

	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
	}
	else if (co_zdoomphys && slidemo->z < openbottom)
	{
		// [RH] Check to make sure there's nothing in the way for the step up
		fixed_t savedz = slidemo->z;
		slidemo->z = openbottom;
		bool good = P_TestMobjZ (slidemo);
		slidemo->z = savedz;
		if (!good)
			goto isblocking;
	}

	// 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.
//
void P_SlideMove (AActor *mo)
{
    fixed_t		leadx;
    fixed_t		leady;
    fixed_t		trailx;
    fixed_t		traily;
    fixed_t		newx;
    fixed_t		newy;
    int			hitcount;

    bool		walkplane;

	slidemo = mo;
	hitcount = 3;

  retry:
	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)
	{
		fixed_t xmove, ymove;
		// the move must have hit the middle, so stairstep
	  stairstep:
		xmove = 0, ymove = mo->momy;
		walkplane = P_CheckSlopeWalk (mo, xmove, ymove);
		if (!P_TryMove (mo, mo->x + xmove, mo->y + ymove, true, walkplane))
		{
			xmove = mo->momx, ymove = 0;
			walkplane = P_CheckSlopeWalk (mo, xmove, ymove);
			P_TryMove (mo, mo->x + xmove, mo->y + ymove, true, walkplane);
		}
		return;
	}

	// fudge a bit to make sure it doesn't hit
	bestslidefrac -= 0x800;
	if (bestslidefrac > 0)
	{
		newx = FixedMul (mo->momx, bestslidefrac);
		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.
	bestslidefrac = FRACUNIT-(bestslidefrac+0x800);	// remanider
	if (bestslidefrac > FRACUNIT)
		bestslidefrac = FRACUNIT;
	else if (bestslidefrac <= 0)
		return;

	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)
	/*if (mo->player && mo->player->mo == mo)
	{
		if (abs(mo->player->momx) > abs(tmxmove))
			mo->player->momx = tmxmove;
		if (abs(mo->player->momy) > abs(tmymove))
			mo->player->momy = tmymove;
	}*/

	walkplane = P_CheckSlopeWalk (mo, tmxmove, tmymove);

	if (!P_TryMove (mo, mo->x+tmxmove, mo->y+tmymove, true, walkplane))
	{
		goto retry;
	}
}

//
// P_LineAttack
//
AActor* 		linetarget; 	// who got hit (or NULL)
AActor* 		shootthing;

// Height if not aiming up or down
// ???: use slope for monsters?
fixed_t 		shootz;

int 			la_damage;
fixed_t 		attackrange;
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.
//
bool PTR_AimTraverse (intercept_t* in)
{
	line_t* 			li;
	AActor* 			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, trace.x + FixedMul(trace.dx, in->frac),
				trace.y + FixedMul(trace.dy, in->frac));

		if (openbottom >= opentop)
			return false;				// stop

		dist = FixedMul (attackrange, in->frac);

		// [SL] 2012-02-08 - Calculate the point where the intercept crosses
		// the line
		fixed_t crossx = trace.x + FixedMul(trace.dx, in->frac);
		fixed_t crossy = trace.y + FixedMul(trace.dy, in->frac);

		if (P_FloorHeight(crossx, crossy, li->frontsector) !=
			P_FloorHeight(crossx, crossy, li->backsector))
		{
			slope = FixedDiv (openbottom - shootz , dist);
			if (slope > bottomslope)
				bottomslope = slope;
		}

		if (P_CeilingHeight(crossx, crossy, li->frontsector) !=
			P_CeilingHeight(crossx, crossy, li->backsector))
		{
			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

	// GhostlyDeath -- dont autoaim on spectators
	if ((th->player && th->player->spectator))
		return true;

	// [SL] 2011-10-31 - Don't aim at teammates
	if ((sv_gametype == GM_CTF || sv_gametype == GM_TEAMDM) &&
		shootthing->player && th->player &&
		shootthing->player->userinfo.team == th->player->userinfo.team &&
		!sv_friendlyfire)
		return true;

	// Don't aim at friendlies if you're a player
	if (shootthing->player && th->flags & MF_FRIEND && P_IsFriendlyThing(shootthing, th) &&
	    !sv_friendlymonsterfire)
		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
}

//
// P_ShootLine
//
// Helper function for PTR_ShootTraverse. Spawns a bullet puff if the intercept
// hits a line or the floor/ceiling. Returns true if the intercept should continue
// because it did not hit a solid line.
//
bool P_ShootLine(intercept_t* in)
{
	bool precise = (co_fixweaponimpacts != 0);
	line_t* li = in->d.line;

	if (!in->isaline)
		return true;

	if (li->special)
		P_ShootSpecialLine(shootthing, li);

	short spe;
	if (map_format.getZDoom())
		spe = Line_Horizon;
	else
		spe = 337;

	// don't shoot horizon lines
	if (li->special == spe)
		return false;

	// [SL] 2012-02-08 - Calculates where the intercept crosses the line
	fixed_t crossx = trace.x + FixedMul(trace.dx, in->frac);
	fixed_t crossy = trace.y + FixedMul(trace.dy, in->frac);

	// [SL] determine which sector is on the side of the line that faces the shooter
	sector_t *sec1, *sec2;
	if (!precise || !li->backsector	|| !P_PointOnLineSide(trace.x, trace.y, li))
	{
		sec1 = li->frontsector;
		sec2 = li->backsector;
	}
	else
	{
		sec1 = li->backsector;
		sec2 = li->frontsector;
	}

	fixed_t ceilingheight1 = P_CeilingHeight(crossx, crossy, sec1);
	fixed_t ceilingheight2 = sec2 ? P_CeilingHeight(crossx, crossy, sec2) : limits::MAXINT;
	fixed_t floorheight1 = P_FloorHeight(crossx, crossy, sec1);
	fixed_t floorheight2 = sec2 ? P_FloorHeight(crossx, crossy, sec2) : limits::MAXINT;

	// position the destination for the bullet puff a bit closer
	fixed_t frac = in->frac - FixedDiv(4 * FRACUNIT, attackrange);
	fixed_t z = shootz + FixedMul(aimslope, FixedMul(frac, attackrange));

	if (li->flags & ML_TWOSIDED && !(li->flags & ML_BLOCKEVERYTHING))
	{
		// crosses a two sided line
		P_LineOpening(li, trace.x + FixedMul(trace.dx, in->frac), trace.y + FixedMul(trace.dy, in->frac));

		if (precise)
		{
			if (z < opentop && z > openbottom)
			{
				// shot didn't hit the solid part of this line
				opentop = ceilingheight2;
				openbottom = floorheight2;
				return true;
			}
		}
		else
		{
			// e6y: emulation of missed back side on two-sided lines.
			// backsector can be NULL when emulating missing back side.

			fixed_t dist = FixedMul(attackrange, in->frac);
			bool hittop = (li->backsector == NULL || ceilingheight1 != ceilingheight2) &&
						FixedDiv(opentop - shootz, dist) < aimslope;
			bool hitbottom = (li->backsector == NULL || floorheight1 != floorheight2) &&
						FixedDiv(openbottom - shootz, dist) > aimslope;

			if (!hittop && !hitbottom)
				return true;	// shot didn't hit the solid part of this line
		}
	}

	// definitely hit the solid part of the line

	bool skyceiling1 = R_IsSkyFlat(sec1->ceilingpic);
	bool skyceiling2 = sec2 && R_IsSkyFlat(sec2->ceilingpic);

	// sky wall hack
	if (skyceiling1 && skyceiling2)
	{
		if (!precise || z > ceilingheight2)
			return false;
	}

	// check for shooting skies
	if (skyceiling1 && z > ceilingheight1)
		return false;

	// check for shooting sky floors
	if (precise && R_IsSkyFlat(sec1->floorpic) && z < floorheight1)
		return false;

	v3fixed_t lineorg, linedir, puffpos;

	// set origin vector for the bullet
	M_SetVec3Fixed(&lineorg, trace.x, trace.y, shootz);
	// set direction vector for the bullet
	M_SetVec3Fixed(&linedir, trace.dx, trace.dy, FixedMul(aimslope, attackrange));

	if (precise && z > ceilingheight1)
	{
		puffpos = P_LinePlaneIntersection(&sec1->ceilingplane, lineorg, linedir);
	}
	else if (precise && z < floorheight1)
	{
		puffpos =  P_LinePlaneIntersection(&sec1->floorplane, lineorg, linedir);
	}
	else
	{
		puffpos.x = trace.x + FixedMul(trace.dx, frac);
		puffpos.y = trace.y + FixedMul(trace.dy, frac);
		puffpos.z = z;
	}

	// Spawn bullet puffs.
	P_SpawnPuff(puffpos.x, puffpos.y, puffpos.z);
	return false;
}


//
// PTR_ShootTraverse
//
bool PTR_ShootTraverse (intercept_t* in)
{
	fixed_t x, y, z;
	fixed_t frac;
	AActor *th;
	fixed_t thingtopslope, thingbottomslope;

	if (in->isaline)
		return P_ShootLine(in);

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

	// GhostlyDeath -- Don't shoot spectators!
	if ((th->player && th->player->spectator))
		return true;

	// check angles to see if the thing can be aimed at
	fixed_t 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.
	bool spawnblood = !(in->d.thing->flags & MF_NOBLOOD);

	// [SL] 2011-05-11 - In unlagged games, spawn blood at the target's current
	// position, not at their reconciled position
	if (shootthing->player && th->player)
	{
		fixed_t xoffs = 0, yoffs = 0, zoffs = 0;
		Unlag::getInstance().getReconciliationOffset(th->player->id, xoffs, yoffs, zoffs);
		x += xoffs; y += yoffs; z += zoffs;

		if (P_AreTeammates(*shootthing->player, *th->player) && !sv_friendlyfire)
			spawnblood = false;
	}

	if (th->flags & MF_FRIEND && P_IsFriendlyThing(th, shootthing) && !sv_friendlymonsterfire)
		spawnblood = false;

	if (spawnblood)
		P_SpawnBlood(x, y, z, la_damage);
	else
		P_SpawnPuff(x, y, z);

	if (la_damage) {
		// [RH] try and figure out means of death;
		int mod = MOD_HITSCAN;

		if (shootthing->player) {
			switch (shootthing->player->readyweapon) {
				case wp_fist:
					mod = MOD_FIST;
					break;
				case wp_pistol:
					mod = MOD_PISTOL;
					break;
				case wp_shotgun:
					mod = MOD_SHOTGUN;
					break;
				case wp_chaingun:
					mod = MOD_CHAINGUN;
					break;
				case wp_supershotgun:
					mod = MOD_SSHOTGUN;
					break;
				case wp_chainsaw:
					mod = MOD_CHAINSAW;
					break;
				default:
					break;
			}
		}

		P_DamageMobj (th, shootthing, shootthing, la_damage, mod);
	}

	// don't go any farther
	return false;
}

EXTERN_CVAR(sv_freelook)

//
// P_AimLineAttack
//
fixed_t P_AimLineAttack (AActor *t1, angle_t angle, fixed_t distance)
{
	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

	// [RH] Technically, this is now correct for an engine with true 6 DOF
	// instead of one which implements y-shearing, like we currently do.
	angle_t topangle = t1->pitch - ANG(32);
	angle_t bottomangle = t1->pitch + ANG(32);

	if (topangle <= ANG360 - ANG180)
		topslope = finetangent[FINEANGLES/2-1];
	else
		topslope = finetangent[FINEANGLES/4-((signed)topangle>>ANGLETOFINESHIFT)];

	if (bottomangle >= ANG180)
		bottomslope = finetangent[0];
	else
		bottomslope = finetangent[FINEANGLES/4-((signed)bottomangle>>ANGLETOFINESHIFT)];

	attackrange = distance;
	linetarget = NULL;

	P_PathTraverse (t1->x, t1->y, x2, y2, PT_ADDLINES|PT_ADDTHINGS, PTR_AimTraverse);

	if (linetarget)
		return aimslope;

	return 0;
}

/**
 * A function created especially for implementing player autoaim.  First
 * attempts to shoot a tracer straight ahead, then tries progressively wider
 * and wider tracers in a short cone.
 *
 * @param  actor    Source actor.
 * @param  angle    Angle of shot.  Set to the 'correct' angle on return.
 * @param  spread   Maximum spread angle of shot, plus or minus 0 degrees.
 *                  Vanilla is 1 << 26.
 * @param  tracers  Number of tracers to shoot per side.  Fewer tracers means
 *                  you're less likely to hit a distant object that is between
 *                  straigt ahead and the maximum spread.  Vanilla is 1.
 * @param  distance Distance to shoot the tracer.  Vanilla is 16 * 64 * FRACUNIT.
 * @return          Slope of the resulting shot.
 */
fixed_t P_AutoAimLineAttack(AActor* actor, angle_t& angle, const angle_t spread, const int tracers, fixed_t distance)
{
	fixed_t slope = P_AimLineAttack(actor, angle, distance);

	if (linetarget)
		return slope;

	angle_t originangle = angle;
	angle_t testspread = spread / tracers;

	for (int i = 1;i <= tracers;i++)
	{
		angle = originangle;
		if (i == tracers)
			angle += spread;
		else
			angle += testspread * i;
		slope = P_AimLineAttack(actor, angle, distance);
		if (linetarget)
			return slope;

		if (i == tracers)
			angle -= spread * 2;
		else
			angle -= testspread * i * 2;
		slope = P_AimLineAttack(actor, angle, distance);
		if (linetarget)
			return slope;
	}

	return 0;
}


//
// P_LineAttack
// If damage == 0, it is just a test trace
// that will leave linetarget set.
//
void P_LineAttack (AActor *t1, angle_t angle, fixed_t distance,
				   fixed_t slope, int damage)
{
	fixed_t x2, 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;

	// [RH] If any lines are crossed in the shot's path, these will be changed
	opentop = P_CeilingHeight(t1);
	openbottom = P_FloorHeight(t1);

	if (P_PathTraverse (t1->x, t1->y, x2, y2, PT_ADDLINES|PT_ADDTHINGS, PTR_ShootTraverse)) {
		// [RH] No lines or things were hit in the shot's path, but	it
		//		might still hit the ceiling or floor before it gets out
		//		of range, so we might still need to show a bullet puff.
		// denis - no, this desyncs demos
		/*fixed_t frac;
		fixed_t z = shootz + FixedMul (distance, slope);
		int updown;

		opentop -= mobjinfo[MT_PUFF]->height;
		if (z < openbottom) {
			// hit floor
			frac = FixedDiv (openbottom - shootz, z - shootz);
			z = openbottom;
			x2 = t1->x + FixedMul (x2 - t1->x, frac);
			y2 = t1->y + FixedMul (y2 - t1->y, frac);
			updown = 0;
		} else if (z > opentop) {
			// hit ceiling
			subsector_t *subsector;

			frac = FixedDiv (opentop - shootz, z - shootz);
			z = opentop;
			x2 = t1->x + FixedMul (x2 - t1->x, frac);
			y2 = t1->y + FixedMul (y2 - t1->y, frac);
			updown = 1;
			if ( (subsector = P_PointInSubsector (x2, y2)) ) {
				if (subsector->sector->ceilingpic == skyflatnum)
					return;	// disappeared in the clouds
			} else
				return;	// outside map
		} else {
			// hit nothing
			return;
		}
		P_SpawnPuff (x2, y2, z, P_PointToAngle (0, 0, trace.dx, trace.dy) - ANG180, updown);*/
	}
}

//
// [RH] PTR_RailTraverse
//
static int MaxRailHits, NumRailHits;
static struct SRailHit {
	AActor *hitthing;
	fixed_t x,y,z;
} *RailHits;
static v3double_t RailEnd;

bool PTR_RailTraverse (intercept_t *in)
{
	fixed_t 			x;
	fixed_t 			y;
	fixed_t 			z;
	fixed_t 			frac;

	line_t* 			li;

	AActor* 			th;

	fixed_t 			dist;
	fixed_t 			thingtopslope;
	fixed_t 			thingbottomslope;
	fixed_t				floorheight;
	fixed_t				ceilingheight;

	if (in->isaline)
	{
		li = in->d.line;

		fixed_t crossx = trace.x + FixedMul (trace.dx, in->frac);
		fixed_t crossy = trace.y + FixedMul (trace.dy, in->frac);

		// [SL] 2012-04-18 - origin and direction vectors for the shot
		v3fixed_t lineorg, linedir;
		M_SetVec3Fixed(&lineorg, trace.x, trace.y, shootz);
		M_SetVec3Fixed(&linedir, trace.dx, trace.dy, FixedMul(aimslope, attackrange));

		frac = in->frac;
		z = shootz + FixedMul (aimslope, FixedMul (frac, attackrange));

		if (!(li->flags & ML_TWOSIDED) || (li->flags & ML_BLOCKEVERYTHING))
			goto hitline;

		// crosses a two sided line
		P_LineOpening(li, trace.x + FixedMul(trace.dx, in->frac),
				trace.y + FixedMul(trace.dy, in->frac));

		if (z >= opentop || z <= openbottom)
			goto hitline;

		// shot continues

		// TODO: handle SPAC_PCROSS specials

		return true;


		// hit line
	  hitline:
		plane_t *floorplane, *ceilingplane;

		if (!li->backsector || !P_PointOnLineSide (trace.x, trace.y, li))
		{
			ceilingplane = &li->frontsector->ceilingplane;
			floorplane = &li->frontsector->floorplane;

			ceilingheight = P_CeilingHeight(crossx, crossy, li->frontsector);
			floorheight = P_FloorHeight(crossx, crossy, li->frontsector);
		}
		else
		{
			ceilingplane = &li->backsector->ceilingplane;
			floorplane = &li->backsector->floorplane;

			ceilingheight = P_CeilingHeight(crossx, crossy, li->backsector);
			floorheight = P_FloorHeight(crossx, crossy, li->backsector);
		}

		if (z < floorheight) {
			// [SL] 2012-04-18 - Calculate where the the tracer intersects
			// with the floor plane
			v3fixed_t pt = P_LinePlaneIntersection(floorplane, lineorg, linedir);
			x = pt.x;  y = pt.y;  z = pt.z;
		} else if (z > ceilingheight) {
			// [SL] 2012-04-18 - Calculate where the the tracer intersects
			// with the ceiling plane
			v3fixed_t pt = P_LinePlaneIntersection(ceilingplane, lineorg, linedir);
			x = pt.x;  y = pt.y;  z = pt.z;
		} else {
			x = trace.x + FixedMul(trace.dx, frac);
			y = trace.y + FixedMul(trace.dy, frac);

			// Shot actually hit a wall. It might be set up for shoot activation
			if (li->special)
				P_ShootSpecialLine(shootthing, li);
		}

		// Save final position of rail shot.
		M_SetVec3(&RailEnd, 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
	// if it's invulnerable, it completely blocks the shot
	if (th->flags2 & MF2_INVULNERABLE)
		return false;

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

	// Save this thing for damaging later
	if (NumRailHits >= MaxRailHits)
	{
		MaxRailHits = MaxRailHits ? MaxRailHits * 2 : 16;
		RailHits = (SRailHit *) M_Realloc(RailHits, sizeof(*RailHits) * MaxRailHits);
	}
	RailHits[NumRailHits].hitthing = th;
	RailHits[NumRailHits].x = x;
	RailHits[NumRailHits].y = y;
	RailHits[NumRailHits].z = z;
	NumRailHits++;

	// continue the trace
	return true;
}

void P_RailAttack (AActor *source, int damage, int offset)
{
	v3double_t start, end;

	int angleidx = (source->angle - ANG90) >> ANGLETOFINESHIFT;
	fixed_t x1 = source->x + offset*finecosine[angleidx];
	fixed_t y1 = source->y + offset*finesine[angleidx];

	angleidx = (source->angle) >> ANGLETOFINESHIFT;
	fixed_t x2 = source->x + 8192*finecosine[angleidx];
	fixed_t y2 = source->y + 8192*finesine[angleidx];

	shootz = source->z + (source->height >> 1) + 8*FRACUNIT;
	attackrange = 8192*FRACUNIT;
	aimslope = finetangent[FINEANGLES/4-(source->pitch>>ANGLETOFINESHIFT)];
	shootthing = source;
	NumRailHits = 0;

	M_SetVec3(&start, x1, y1, shootz);

	if (P_PathTraverse (x1, y1, x2, y2, PT_ADDLINES|PT_ADDTHINGS, PTR_RailTraverse))
	{
		// Nothing hit, so just shoot the air
		M_AngleToVec3(&end, source->angle, source->pitch);

		M_ScaleVec3(&end, &end, 8192.0);
		M_AddVec3(&end, &start, &end);
	}
	else
	{
		// Hit a wall, maybe some things as well
		end = RailEnd;

		for (int i = 0; i < NumRailHits; i++)
		{
			if (RailHits[i].hitthing->flags & MF_NOBLOOD)
				P_SpawnPuff(RailHits[i].x, RailHits[i].y, RailHits[i].z);
			else
				P_SpawnBlood(RailHits[i].x, RailHits[i].y, RailHits[i].z, damage);
			P_DamageMobj (RailHits[i].hitthing, source, source, damage, MOD_RAILGUN);
		}
	}

	if (clientside)
		P_DrawRailTrail (start, end);
	else
	{
		for (auto& player : players)
		{
			AActor *mo = player.mo;
			if (!mo || mo == source)
				continue;

			buf_t* buf = &(player.client.netbuf);

			MSG_WriteSVC(buf, SVC_RailTrail(start, end));
		}
	}
}

//
// [RH] PTR_CameraTraverse
//
fixed_t CameraX, CameraY, CameraZ;
sector_t* CameraSector;
#define CAMERA_DIST	0x1000	// Minimum distance between camera and walls

bool PTR_CameraTraverse (intercept_t* in)
{
	fixed_t z;
	fixed_t frac;
	line_t *li;

	// ignore mobjs
	if (!in->isaline)
		return true;

	fixed_t crossx = trace.x + FixedMul(trace.dx, in->frac);
	fixed_t crossy = trace.y + FixedMul(trace.dy, in->frac);

	frac = in->frac - CAMERA_DIST;
	z = shootz + FixedMul (aimslope, FixedMul(frac, attackrange));

	li = in->d.line;

	if (!(li->flags & ML_TWOSIDED))
		goto hitline;

	// crosses a two sided line
	P_LineOpening(li, crossx, crossy);

	if (z >= opentop || z <= openbottom)
		goto hitline;

	return true;

	// hit line
  hitline:
	// If the trace went below/above the floor/ceiling, stop
	// in the right place and not on a wall.
	{
		fixed_t ceilingheight, floorheight;

		if (!li->backsector || !P_PointOnLineSide (trace.x, trace.y, li)) {
			ceilingheight = P_CeilingHeight(crossx, crossy, li->frontsector);
			floorheight = P_FloorHeight(crossx, crossy, li->frontsector);
		} else {
			ceilingheight = P_CeilingHeight(crossx, crossy, li->backsector);
			floorheight = P_FloorHeight(crossx, crossy, li->backsector);
		}
		ceilingheight -= CAMERA_DIST;
		floorheight += CAMERA_DIST;

		if (z < floorheight) {
			frac = FixedDiv (FixedMul (floorheight - shootz, frac), z - shootz);
			z = floorheight;
		} else if (z > ceilingheight) {
			frac = FixedDiv (FixedMul (ceilingheight - shootz, frac), z - shootz);
			z = ceilingheight;
		}
	}

	CameraX = trace.x + FixedMul (trace.dx, frac);
	CameraY = trace.y + FixedMul (trace.dy, frac);
	CameraZ = z;

	// don't go any farther
	return false;
}

//
// [RH] P_AimCamera
//
EXTERN_CVAR (chase_height)
EXTERN_CVAR (chase_dist)

void P_AimCamera (AActor *t1)
{
	fixed_t distance = (fixed_t)(chase_dist * FRACUNIT);
	angle_t angle = (t1->angle - ANG180) >> ANGLETOFINESHIFT;
	fixed_t x2 = t1->x + (distance>>FRACBITS)*finecosine[angle];
	fixed_t y2 = t1->y + (distance>>FRACBITS)*finesine[angle];
	subsector_t *subsector;

	shootthing = t1;
	shootz = t1->z + t1->height + (fixed_t)(chase_height * FRACUNIT);
	attackrange = distance;
	aimslope = finetangent[FINEANGLES/4+(t1->pitch>>ANGLETOFINESHIFT)];

	CameraZ = shootz + (fixed_t)(chase_dist * aimslope);
	subsector = P_PointInSubsector (x2, y2);
	if (subsector) {
		fixed_t ceilingheight = P_CeilingHeight(x2, y2, subsector->sector) - CAMERA_DIST;
		fixed_t floorheight = P_FloorHeight(x2, y2, subsector->sector) + CAMERA_DIST;
		fixed_t frac = FRACUNIT;
		CameraSector = subsector->sector;

		if (CameraZ < floorheight) {
			frac = FixedDiv (floorheight - shootz, CameraZ - shootz);
			CameraZ = floorheight;
		} else if (CameraZ > ceilingheight) {
			frac = FixedDiv (ceilingheight - shootz, CameraZ - shootz);
			CameraZ = ceilingheight;
		}

		CameraX = t1->x + FixedMul (x2 - t1->x, frac);
		CameraY = t1->y + FixedMul (y2 - t1->y, frac);
	} else {
		CameraX = x2;
		CameraY = y2;
	}

	P_PathTraverse (t1->x, t1->y, x2, y2, PT_ADDLINES, PTR_CameraTraverse);
}


//
// USE LINES
//
AActor *usething;
bool foundline;

bool PTR_UseTraverse (intercept_t *in)
{
	if (!in->isaline)
		I_Error ("PTR_UseTraverse: non-line intercept\n");

	if (!in->d.line->special)
	{
		P_LineOpening(in->d.line, trace.x + FixedMul(trace.dx, in->frac),
				trace.y + FixedMul(trace.dy, in->frac));

		if (openrange <= 0)
		{
			// [RH] Give sector a chance to intercept the use
			sector_t *sec = in->d.line->frontsector;
			if ((!sec->SecActTarget ||
			    !A_TriggerAction(sec->SecActTarget, usething, SECSPAC_Use|SECSPAC_UseWall)) &&
			    usething->player)
			{
				UV_SoundAvoidPlayer(usething, CHAN_VOICE, "player/male/grunt1", ATTN_NORM);
			}
			// can't use through a wall
			return false;
		}

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

	int side = (P_PointOnLineSide (usething->x, usething->y, in->d.line) == 1);

    P_UseSpecialLine (usething, in->d.line, side, false);

	//WAS can't use more than one special line in a row
	//jff 3/21/98 NOW multiple use allowed with enabling line flag
	//[RH] And now I've changed it again. If the line is of type
	//	   SPAC_USE, then it eats the use. Everything else passes
	//	   it through, including SPAC_USETHROUGH.
	//[ML] And NOW (8/16/10) it checks whether it's use or NOT the passthrough flags
	// (passthru on a cross or use line).  This may get augmented/changed even more in the future.

	bool donteatuse;
	if (map_format.getZDoom())
	{
		donteatuse = ((in->d.line->flags & ML_SPAC_USE) ||
		          (!(in->d.line->flags & ML_SPAC_CROSSTHROUGH) &&
		           (!(in->d.line->flags & ML_SPAC_USETHROUGH))))
		             ? false
		             : true;
	}
	else
	{
		donteatuse = (in->d.line->flags & ML_PASSUSE);
	}

	return donteatuse;
}

// 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)
{
	if (!in->isaline)
		I_Error ("PTR_NoWayTraverse: non-line intercept\n");

	line_t *ld = in->d.line;					// This linedef

	return ld->special || !(					// Ignore specials
		ld->flags & (ML_BLOCKING|ML_BLOCKEVERYTHING) || (		// Always blocking
		P_LineOpening(ld, trace.x + FixedMul(trace.dx, in->frac),
				trace.y + FixedMul(trace.dy, in->frac)),		// 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)
{
	// GhostlyDeath -- Spectators can't use special lines
	if (player.spectator)
		return;

	usething = player.mo;
	foundline = false;

	//Added by MC: Check if bot and use special activating (spin round) if it is.
	const int angle = player.mo->angle >> ANGLETOFINESHIFT;

	const fixed_t x1 = player.mo->x;
	const fixed_t y1 = player.mo->y;
	const fixed_t x2 = x1 + (USERANGE >> FRACBITS) * finecosine[angle];
	const fixed_t y2 = y1 + (USERANGE >> FRACBITS) * finesine[angle];

	if (P_PathTraverse (x1, y1, x2, y2, PT_ADDLINES, PTR_UseTraverse)) {
		// [RH] Give sector a chance to eat the use
		if (usething->subsector)
		{
			sector_t *sec = usething->subsector->sector;
			int spac = SECSPAC_Use;
			if (foundline)
				spac |= SECSPAC_UseWall;
			if ((!sec->SecActTarget || !A_TriggerAction(sec->SecActTarget, usething, spac)) &&
			    (co_boomphys && !P_PathTraverse(x1, y1, x2, y2, PT_ADDLINES, PTR_NoWayTraverse)))
			{
				// This added test makes the "oof" sound work on 2s lines -- killough:
				// [ML] It also apparently allows additional silent bfg tricks not present in vanilla...
				// [ML] co_boomlinecheck now part of co_boomphys
				UV_SoundAvoidPlayer(usething, CHAN_VOICE, "player/male/grunt1", ATTN_NORM);
			}
		}
	}
}

//
// RADIUS ATTACK
//

// [RH] Damage scale to apply to thing that shot the missile. (co_zdoomphys)
static float selfthrustscale;

CVAR_FUNC_IMPL(sv_splashfactor)
{
	selfthrustscale = 1.0f / var;
}

namespace
{

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

bool P_SplashImmune(const AActor& target, const AActor& spot)
{
	return // not default behaviour and same group
	    mobjinfo[target.type].splash_group != SG_DEFAULT &&
	    mobjinfo[target.type].splash_group == mobjinfo[spot.type].splash_group;
}

bool PIT_DoomRadiusAttack(AActor& thing,
                          AActor* bombsource,
                          const AActor* bombspot,
                          const int bombdamage,
                          const float /*bombdamagefloat*/,
                          const int bombdistance,
                          const float /*bombdistancefloat*/,
                          const bool /*DamageSource*/,
                          const int bombmod)
{
	if (!serverside || !(thing.flags & (MF_SHOOTABLE | MF_BOUNCES)))
		return true;

	// MBF21
	if (P_SplashImmune(thing, *bombspot))
		return true;

	// Boss spider and cyborg
	// take no damage from concussion.
	if (((thing.type == MT_CYBORG && bombsource->type == MT_CYBORG) ||
		(thing.flags3 & MF3_NORADIUSDMG || thing.flags2 & MF2_BOSS)) &&
		!(bombspot->flags3 & MF3_FORCERADIUSDMG))
		return true;

	const fixed_t dx = abs(thing.x - bombspot->x);
	const fixed_t dy = abs(thing.y - bombspot->y);
	const fixed_t dist = std::max((std::max(dx, dy) - thing.radius) >> FRACBITS, 0);

	if (dist >= bombdistance)
	{
		if (bombsource && bombsource->player)
		{
			M_LogWDLEvent(WDL_EVENT_PROJACCURACY, bombsource->player, NULL,
			              bombsource->player->mo->angle / 4, bombmod, 0,
			              GetMaxShotsForMod(bombmod));
		}
		return true; // out of range
	}

	if (P_CheckSight(&thing, bombspot))
	{
		int damage;

		if (bombdamage == bombdistance)
			damage = bombdamage - dist;
		else
			damage = (bombdamage * (bombdistance - dist) / bombdistance) + 1;

		// must be in direct path
		P_DamageMobj(&thing, bombspot, bombsource, damage * sv_splashfactor, bombmod);
	}

	return true;
}


//
// PIT_ZDoomRadiusAttack
//
// "bombsource" is the creature that caused the explosion at "bombspot".
// [RH] Now it knows about vertical distances and can thrust things vertically, too.
//
bool PIT_ZDoomRadiusAttack(AActor& thing,
                           AActor* bombsource,
                           const AActor* bombspot,
                           const int bombdamage,
                           const float bombdamagefloat,
                           const int bombdistance,
                           const float bombdistancefloat,
                           const bool DamageSource,
                           const int bombmod)
{
	if (!serverside || !(thing.flags & (MF_SHOOTABLE | MF_BOUNCES)))
		return true;

	// MBF21
	if (P_SplashImmune(thing, *bombspot))
		return true;

	// Boss spider and cyborg
	// take no damage from concussion.
	if (((thing.type == MT_CYBORG && bombsource->type == MT_CYBORG) ||
	   (thing.flags3 & MF3_NORADIUSDMG || thing.flags2 & MF2_BOSS)) &&
	   !(bombspot->flags3 & MF3_FORCERADIUSDMG))
		return true;

	// Barrels always use the original code, since this makes
	// them far too "active." BossBrains also use the old code
	// because some user levels require they have a height of 16,
	// which can make them near impossible to hit with the new code.
	if (bombspot->type == MT_BARREL || thing.type == MT_BARREL ||
		thing.type == MT_BOSSBRAIN)
	{
		return PIT_DoomRadiusAttack(thing, bombsource, bombspot, bombdamage, bombdamagefloat, bombdistance, bombdistancefloat, DamageSource, bombmod);
	}

	// [RH] New code. The bounding box only covers the
	// height of the thing and not the height of the map.
	const fixed_t dx = abs(thing.x - bombspot->x);
	const fixed_t dy = abs(thing.y - bombspot->y);
	auto len = float(std::max(dx, dy));
	const auto boxradius = float(thing.radius);

	if (bombspot->z < thing.z || bombspot->z >= thing.z + thing.height)
	{
		float dz;

		if (bombspot->z > thing.z)
			dz = float(thing.z + thing.height - bombspot->z);
		else
			dz = float(thing.z - bombspot->z);

		if (len <= boxradius)
			len = dz;
		else
		{
			len -= boxradius;
			len = sqrtf(len*len + dz*dz);
		}
	}
	else
	{
		len -= std::max(boxradius, 0.0f);
	}

	float points;
	if (bombdamage == bombdistance)
		points = bombdamage - (len / FRACUNIT) + 1.0f;
	else
		points = (bombdamage * (bombdistance - (len / FRACUNIT)) / bombdistance) + 1.0f;

	if (&thing == bombsource)
		points *= sv_splashfactor;

	if (points > 0.0f && P_CheckSight(&thing, bombspot))
	{
		// OK to damage; target is in direct path

		const fixed_t momx = thing.momx;
		const fixed_t momy = thing.momy;
		const int damage = (int)points;

		P_DamageMobj(&thing, bombspot, bombsource, damage, bombmod);

		float thrust = points * 0.5f / thing.info->mass;
		if (bombsource == &thing)
			thrust *= selfthrustscale;

		float momz = (float)(thing.z + (thing.height>>1) - bombspot->z) * thrust;
		if (bombsource != &thing)
			momz *= 0.5f;
		else
			momz *= 0.8f;

		thing.momx = momx + (fixed_t)((thing.x - bombspot->x) * thrust);
		thing.momy = momy + (fixed_t)((thing.y - bombspot->y) * thrust);
		thing.momz += (fixed_t)momz;
	}
	else
	{
		if (bombsource && bombsource->player)
		{
			M_LogWDLEvent(WDL_EVENT_PROJACCURACY, bombsource->player, NULL,
			              bombsource->player->mo->angle / 4, bombmod, 0,
			              GetMaxShotsForMod(bombmod));
		}
	}

	return true;
}

} // namespace

//
// P_RadiusAttack
// Source is the creature that caused the explosion at spot.
//
void P_RadiusAttack(AActor *spot, AActor *source, int damage, int distance,
	bool hurtSource, int mod)
{
	const fixed_t dist = (distance+MAXRADIUS)<<FRACBITS;
	const int yh = std::min<int>((spot->y + dist - bmaporgy)>>MAPBLOCKSHIFT, bmapheight - 1);
	const int yl = std::max<int>((spot->y - dist - bmaporgy)>>MAPBLOCKSHIFT, 0);
	const int xh = std::min<int>((spot->x + dist - bmaporgx)>>MAPBLOCKSHIFT, bmapwidth - 1);
	const int xl = std::max<int>((spot->x - dist - bmaporgx)>>MAPBLOCKSHIFT, 0);
	AActor* bombsource = source;
	const auto bombdamagefloat = (float)damage;
	const float bombdistancefloat = 1.f / (float)distance;

	// [Blair] Prevent crash from barrels hit by crushers
	if (!demoplayback && bombsource == nullptr && spot != nullptr)
	{
		bombsource = spot;
	}

	// decide which radius attack function to use
	const auto pAttackFunc = co_zdoomphys ?
		PIT_ZDoomRadiusAttack : PIT_DoomRadiusAttack;

	if (co_blockmapfix)
	{
		// [SL] 2012-12-03 - With co_blockmapfix, an actor can get radius
		// damage more than once since an actor can be in more than one block.
		// So we make a list of unique actors in the surrounding blocks and
		// then call the radius attack function once for each actor.

		std::set<AActor*> actorset;
		for (int y=yl ; y<=yh ; y++)
		{
			for (int x=xl ; x<=xh ; x++)
			{
				AActor *mobj = blocklinks[y*bmapwidth+x];
				while (mobj)
				{
					actorset.insert(mobj);
					mobj = mobj->bmapnode.Next(x, y);
				}
			}
		}

		for (const auto& actor : actorset)
		{
			pAttackFunc(*actor, bombsource, spot, damage, bombdamagefloat, distance, bombdistancefloat, hurtSource, mod);
		}
	}
	else
	{
		for (int y=yl ; y<=yh ; y++)
			for (int x=xl ; x<=xh ; x++)
				P_BlockThingsIterator (x, y, pAttackFunc, nullptr, bombsource, spot, damage, bombdamagefloat, distance, bombdistancefloat, hurtSource, mod);
	}
}



//
// 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 greater than zero, it will crush based on its value
// (BOOM's default crush is 10 dmg per crush)
// If Crunch is 0 or less, you should set the sector height back
//  the way it was and call P_ChangeSector again
//  to undo the changes.
//

//
// PIT_ChangeSector
//
bool PIT_ChangeSector (AActor& thing, const int crushchange, bool& nofit)
{
	if (P_ThingHeightClip (&thing))
	{
		// keep checking
		return true;
	}

	// GhostlyDeath -- if it's a spectator, keep checking
	if (thing.player && thing.player->spectator)
		return true;

	// crunch bodies to giblets
	if (thing.health <= 0)
	{
		P_SetMobjState (&thing, S_GIBS);
		thing.effects = 0;

		// [Nes] - Classic demo compatability: Ghost monster bug.
		if ((demoplayback)) {
			thing.height = 0;
			thing.radius = 0;
		}

		// keep checking
		return true;
	}

	// crunch dropped items
	if (thing.flags & MF_DROPPED)
	{
		thing.Destroy ();

		// keep checking
		return true;
	}

	/* killough 11/98: kill touchy things immediately */
	if (thing.flags & MF_TOUCHY && (thing.oflags & MFO_ARMED || sentient(&thing)))
	{
		P_DamageMobj(&thing, nullptr, nullptr, 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 > 0 && !(level.time&3) )
	{
		P_DamageMobj(&thing, nullptr, nullptr, crushchange, MOD_CRUSH);

		// spray blood in a random direction
		if (!(thing.flags&MF_NOBLOOD))
		{
			auto *mo = new AActor (thing.x,
									 thing.y,
									 thing.z + (thing.height/2), MT_BLOOD);

			mo->momx = P_RandomDiff (mo) << 12;
			mo->momy = P_RandomDiff (mo) << 12;
		}
	}

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

//
// P_ChangeSector	[RH] Was P_CheckSector in BOOM
// 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_ChangeSector (sector_t *sector, int crunch)
{
	if (!sector)
		return true;

	bool nofit = false;

	// [ML] co_boomsectortouch now part of co_boomphys
	if (co_boomphys)
	{
		msecnode_t *n;

		// 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 && !(n->m_thing->flags & MF_NOBLOCKMAP))	// [Blair] Add nullcheck here
						PIT_ChangeSector(*n->m_thing, crunch, nofit); 						// for clients that aren't updated yet.
					break;										// exit and start over
				}
		while (n);	// repeat from scratch until all things left are marked valid
	}
	else
	{
		// re-check heights for all things near the moving sector
		for (int x=sector->blockbox[BOXLEFT] ; x<= sector->blockbox[BOXRIGHT] ; x++)
			for (int y=sector->blockbox[BOXBOTTOM];y<= sector->blockbox[BOXTOP] ; y++)
				P_BlockThingsIterator (x, y, PIT_ChangeSector, nullptr, crunch, nofit);

	}

	return nofit;
}


// phares 3/21/98
//
// Maintain a freelist of msecnode_t's to reduce memory allocs and frees.

msecnode_t *headsecnode = NULL;

// P_GetSecnode() retrieves a node from the freelist. The calling routine
// should make sure it sets all fields properly.

msecnode_t *P_GetSecnode()
{
	msecnode_t *node;

	if (headsecnode)
	{
		node = headsecnode;
		headsecnode = headsecnode->m_snext;
	}
	else
		node = (msecnode_t *)Z_Malloc (sizeof(*node), PU_LEVEL, NULL);
	return node;
}

// P_PutSecnode() returns a node to the freelist.

void P_PutSecnode (msecnode_t *node)
{
	if (!node)
		return;

	node->m_snext = headsecnode;
	headsecnode = 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, AActor *thing, msecnode_t *nextnode)
{
	if (s == nullptr)
		return nextnode;

	msecnode_t *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 AActor->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.

bool PIT_GetSectors (const 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 AActor 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.

	if (ld.backsector)
		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 (AActor *thing, fixed_t x, fixed_t y)
{
	int xl;
	int xh;
	int yl;
	int yh;
	int bx;
	int by;
	msecnode_t *node;

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

	// denis - we may have been called from another P_BlockLinesIterator
	// e.g. when a telefrag results in an item drop.
	// so we need to back up tmthing and then restore it
	AActor *last_tmthing = tmthing;
	int last_tmx = tmx, last_tmy = tmy;
	int last_tmbbox[4] = {tmbbox[0], tmbbox[1], tmbbox[2], tmbbox[3]};

	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.

	if (thing->subsector)
	{
		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;
	}

	// denis - restore tmthing
	tmthing = last_tmthing;
	tmx = last_tmx; tmy = last_tmy;
	tmbbox[0] = last_tmbbox[0];
	tmbbox[1] = last_tmbbox[1];
	tmbbox[2] = last_tmbbox[2];
	tmbbox[3] = last_tmbbox[3];
}

//
// P_InvertPlane
//
// Changes the direction of a plane's normal vector to the opposite direction
//
void P_InvertPlane(plane_t *plane)
{
	if (plane)
	{
		plane->a = -plane->a;
		plane->b = -plane->b;
		plane->c = -plane->c;
		plane->d = -plane->d;
		plane->invc = -plane->invc;
	}
}

//
// P_IsPlaneLevel
//
// Returns true if the z-value is the same for all points on the plane.
// This occurs when the a and b coefficents of the planar equation are 0
// since 0*x + 0*y + c*z + d = 0
//
// In other words, is this a plain vanilla Doom floor/ceiling or a sloping one?
//
bool P_IsPlaneLevel(const plane_t *plane)
{
	if (!plane)
		return false;

	return (plane->a | plane->b) == 0;
}

fixed_t P_PlaneZ(fixed_t x, fixed_t y, const plane_t *plane)
{
	if (!plane)
		return limits::MAXFIXED;

	// Is the plane level?  (Z value is constant for entire plane)
	if (P_IsPlaneLevel(plane))
		return -FixedMul(plane->c, plane->d);

	return -FixedMul(plane->invc, FixedMul(plane->a, x) + FixedMul(plane->b, y) + plane->d);
}

double P_PlaneZ(double x, double y, const plane_t *plane)
{
	if (!plane)
		return limits::MAXINT / 65536.0;

	static constexpr double m = 1.0 / (65536.0 * 65536.0);

	// Is the plane level?  (Z value is constant for entire plane)
	if (P_IsPlaneLevel(plane))
		return -double(plane->c) * plane->d * m;

	return -(plane->a * x + plane->b * y + plane->d) / plane->c;
}


//
// P_FloorHeight()
//
// Returns the height of a floor plane at the point (x, y).  The subsector
// parameter is optional but provides a speedup if used because P_PlaneZ
// can avoid a call to P_PointInSubsector().
//
// Note that there is no check made to ensure the point (x, y) is actually
// within the subsector.
//
// MAXINT is returned if the point is not within any sector.
//
fixed_t P_FloorHeight(fixed_t x, fixed_t y, const sector_t *sector)
{
	if (!sector && !(sector = P_PointInSubsector(x, y)->sector))
		return limits::MAXFIXED;

	return P_PlaneZ(x, y, &sector->floorplane);
}

fixed_t P_FloorHeight(const AActor *mo)
{
	if (mo && mo->subsector && mo->subsector->sector)
		return P_PlaneZ(mo->x, mo->y, &mo->subsector->sector->floorplane);
	else
		return limits::MAXFIXED;
}

fixed_t P_FloorHeight(const sector_t *sector)
{
	if (!sector)
		return limits::MAXFIXED;

	const plane_t *plane = &sector->floorplane;
	return P_PlaneZ(plane->texx, plane->texy, plane);
}

//
// P_CeilingHeight()
//
// Returns the height of a ceiling plane at the point (x, y).  The subsector
// parameter is optional but provides a speedup if used because P_PlaneZ
// can avoid a call to P_PointInSubsector().
//
// Note that there is no check made to ensure the point (x, y) is actually
// within the subsector.
//
// MAXINT is returned if the point is not within any sector.
//
fixed_t P_CeilingHeight(fixed_t x, fixed_t y, const sector_t *sector)
{
	if (!sector && !(sector = P_PointInSubsector(x, y)->sector))
		return limits::MAXFIXED;

	return P_PlaneZ(x, y, &sector->ceilingplane);
}

fixed_t P_CeilingHeight(const AActor *mo)
{
	if (mo && mo->subsector && mo->subsector->sector)
		return P_PlaneZ(mo->x, mo->y, &mo->subsector->sector->ceilingplane);
	else
		return limits::MAXFIXED;
}

fixed_t P_CeilingHeight(const sector_t *sector)
{
	if (!sector)
		return limits::MAXFIXED;

	const plane_t *plane = &sector->ceilingplane;
	return P_PlaneZ(plane->texx, plane->texy, plane);
}

//
// P_LinePlaneIntersection
//
// Calculates the point of intersection of a plane and a line that begins
// at lineorg and is along the direction of linedir.
//
// If the line does not intersect the plane, it must be parallel to the surface
// of the plane.  In that case, a vector with the value MAXINT for all
// components is returned.
//
v3fixed_t P_LinePlaneIntersection(const plane_t *plane,
									const v3fixed_t &lineorg,
									const v3fixed_t &linedir)
{
	v3fixed_t pt;
	M_SetVec3Fixed(&pt, limits::MAXFIXED, limits::MAXFIXED, limits::MAXFIXED);	// marks as invalid

	if (!plane)		// sanity check
		return pt;

	fixed_t numer = -plane->d -
					FixedMul(plane->a, lineorg.x) -
					FixedMul(plane->b, lineorg.y) -
					FixedMul(plane->c, lineorg.z);

	fixed_t denom = FixedMul(plane->a, linedir.x) +
					FixedMul(plane->b, linedir.y) +
					FixedMul(plane->c, linedir.z);

	if (!denom)	// line is parallel to the surface of plane
		return pt;

	fixed_t t = FixedDiv(numer, denom);
	M_SetVec3Fixed(&pt, lineorg.x + FixedMul(t, linedir.x),
						lineorg.y + FixedMul(t, linedir.y),
						lineorg.z + FixedMul(t, linedir.z));

	return pt;
}

bool P_PointOnPlane(const plane_t *plane, fixed_t x, fixed_t y, fixed_t z)
{
	static constexpr fixed_t threshold = FRACUNIT >> 6;

	if (!plane)
		return false;

	fixed_t result =
		(FixedMul(plane->a, x) + FixedMul(plane->b, y) + FixedMul(plane->c, z) + plane->d);

	return abs(result) < threshold;
}

bool P_PointAbovePlane(const plane_t *plane, fixed_t x, fixed_t y, fixed_t z)
{
	if (!plane)
		return false;

	return (FixedMul(plane->a, x) + FixedMul(plane->b, y) + FixedMul(plane->c, z)) > 0;
}

bool P_PointBelowPlane(const plane_t *plane, fixed_t x, fixed_t y, fixed_t z)
{
	if (!plane)
		return false;

	return (FixedMul(plane->a, x) + FixedMul(plane->b, y) + FixedMul(plane->c, z)) < 0;
}

bool P_IdenticalPlanes(const plane_t *pl1, const plane_t *pl2)
{
	if (!pl1 || !pl2)
		return false;

	return (pl1 == pl2) ||
		(pl1->a == pl2->a && pl1->b == pl2->b && pl1->c == pl2->c && pl1->d == pl2->d);
}

void P_ChangeCeilingHeight(sector_t *sector, fixed_t amount)
{
	if (!sector)
		return;

	plane_t *plane = &sector->ceilingplane;
	plane->d -= FixedMul(amount, plane->c);

	// The sector's ceilingheight variable is still used for (among other things)
	// calculating wall texture offsets
	sector->ceilingheight += amount;
}

void P_ChangeFloorHeight(sector_t *sector, fixed_t amount)
{
	if (!sector)
		return;

	plane_t *plane = &sector->floorplane;
	plane->d -= FixedMul(amount, plane->c);

	// The sector's floorheight variable is still used for (among other things)
	// calculating wall texture offsets
	sector->floorheight += amount;
}

void P_SetCeilingHeight(sector_t *sector, fixed_t value)
{
	if (!sector)
		return;

	plane_t *plane = &sector->ceilingplane;
	fixed_t oldvalue = P_PlaneZ(plane->texx, plane->texy, plane);

	P_ChangeCeilingHeight(sector, value - oldvalue);
}

void P_SetFloorHeight(sector_t *sector, fixed_t value)
{
	if (!sector)
		return;

	plane_t *plane = &sector->floorplane;
	fixed_t oldvalue = P_PlaneZ(plane->texx, plane->texy, plane);

	P_ChangeFloorHeight(sector, value - oldvalue);
}

//
// P_LowestHeightOfCeiling
//
// Returns the lowest height of any point of the ceiling
//
fixed_t P_LowestHeightOfCeiling(sector_t *sector)
{
	fixed_t height = limits::MAXFIXED;
	if (!sector)
		return height;

	for (int i = 0; i < sector->linecount; i++)
	{
		vertex_t *v1 = sector->lines[i]->v1;
		vertex_t *v2 = sector->lines[i]->v2;

		fixed_t thisheight;
		if ((thisheight = P_CeilingHeight(v1->x, v1->y, sector)) < height)
			height = thisheight;
		if ((thisheight = P_CeilingHeight(v2->x, v2->y, sector)) < height)
			height = thisheight;
	}

	return height;
}

//
// P_LowestHeightOfFloor
//
// Returns the lowest height of any point of the floor
//
fixed_t P_LowestHeightOfFloor(sector_t *sector)
{
	fixed_t height = limits::MAXFIXED;
	if (!sector)
		return height;

	for (int i = 0; i < sector->linecount; i++)
	{
		vertex_t *v1 = sector->lines[i]->v1;
		vertex_t *v2 = sector->lines[i]->v2;

		fixed_t thisheight;
		if ((thisheight = P_FloorHeight(v1->x, v1->y, sector)) < height)
			height = thisheight;
		if ((thisheight = P_FloorHeight(v2->x, v2->y, sector)) < height)
			height = thisheight;
	}

	return height;
}

//
// P_HighestHeightOfCeiling
//
// Returns the highest height of any point of the ceiling
//
fixed_t P_HighestHeightOfCeiling(sector_t *sector)
{
	fixed_t height = limits::MINFIXED;
	if (!sector)
		return height;

	for (int i = 0; i < sector->linecount; i++)
	{
		vertex_t *v1 = sector->lines[i]->v1;
		vertex_t *v2 = sector->lines[i]->v2;

		fixed_t thisheight;
		if ((thisheight = P_CeilingHeight(v1->x, v1->y, sector)) > height)
			height = thisheight;
		if ((thisheight = P_CeilingHeight(v2->x, v2->y, sector)) > height)
			height = thisheight;
	}

	return height;
}

//
// P_HighestHeightOfFloor
//
// Returns the highest height of any point of the floor
//
fixed_t P_HighestHeightOfFloor(sector_t *sector)
{
	fixed_t height = limits::MINFIXED;
	if (!sector)
		return height;

	for (int i = 0; i < sector->linecount; i++)
	{
		vertex_t *v1 = sector->lines[i]->v1;
		vertex_t *v2 = sector->lines[i]->v2;

		fixed_t thisheight;
		if ((thisheight = P_FloorHeight(v1->x, v1->y, sector)) > height)
			height = thisheight;
		if ((thisheight = P_FloorHeight(v2->x, v2->y, sector)) > height)
			height = thisheight;
	}

	return height;
}

//
// P_CopySector
//
// This function allocates ceilingdata, floordata and lightingdata.
// Be sure to delete them once you're done with the copy.
//
void P_CopySector(sector_t *dest, sector_t *src)
{
	if (!dest || !src)
		return;

	dest->floorheight			= src->floorheight;
	dest->ceilingheight			= src->ceilingheight;
	dest->floorpic				= src->floorpic;
	dest->ceilingpic			= src->ceilingpic;
	dest->lightlevel			= src->lightlevel;
	dest->special				= src->special;
	dest->tag					= src->tag;
	dest->nexttag				= src->nexttag;
	dest->firsttag				= src->firsttag;
	dest->secretsector			= src->secretsector;
	dest->soundtraversed		= src->soundtraversed;
	dest->validcount			= src->validcount;
	dest->seqType				= src->seqType;
	dest->sky					= src->sky;
	dest->friction				= src->friction;
	dest->movefactor			= src->movefactor;
	dest->moveable				= src->moveable;
	dest->stairlock				= src->stairlock;
	dest->prevsec				= src->prevsec;
	dest->floor_xoffs			= src->floor_xoffs;
	dest->floor_yoffs			= src->floor_yoffs;
	dest->ceiling_xoffs			= src->ceiling_xoffs;
	dest->ceiling_yoffs			= src->ceiling_yoffs;
	dest->floor_xscale			= src->floor_xscale;
	dest->floor_yscale			= src->floor_yscale;
	dest->ceiling_xscale		= src->ceiling_xscale;
	dest->ceiling_yscale		= src->ceiling_yscale;
	dest->floor_angle			= src->floor_angle;
	dest->ceiling_angle			= src->ceiling_angle;
	dest->base_floor_angle		= src->base_floor_angle;
	dest->base_ceiling_angle	= src->base_ceiling_angle;
	dest->base_floor_yoffs		= src->base_floor_yoffs;
	dest->base_ceiling_yoffs	= src->base_ceiling_yoffs;
	dest->bottommap				= src->bottommap;
	dest->midmap				= src->midmap;
	dest->topmap				= src->topmap;
	dest->gravity				= src->gravity;
	dest->damageamount			= src->damageamount;
	dest->damageinterval		= src->damageinterval;
	dest->leakrate				= src->leakrate;
	dest->mod					= src->mod;
	dest->colormap				= src->colormap;
	dest->alwaysfake			= src->alwaysfake;
	dest->waterzone				= src->waterzone;
	dest->MoreFlags				= src->MoreFlags;

	dest->heightsec				= src->heightsec;
	dest->floorlightsec			= src->floorlightsec;
	dest->ceilinglightsec		= src->ceilinglightsec;

	dest->linecount				= src->linecount;
	dest->lines					= src->lines;

	memcpy(dest->blockbox, src->blockbox, sizeof(src->blockbox));
	memcpy(dest->soundorg, src->soundorg, sizeof(src->soundorg));

	dest->floorplane			= src->floorplane;
	dest->ceilingplane			= src->ceilingplane;

	dest->touching_thinglist	= src->touching_thinglist;
	dest->thinglist				= src->thinglist;
	dest->soundtarget			= src->soundtarget;

	if (src->ceilingdata != NULL)
		dest->ceilingdata = src->ceilingdata->Clone(dest);
	else
		dest->ceilingdata = NULL;

	if (src->floordata != NULL)
		dest->floordata = src->floordata->Clone(dest);
	else
		dest->floordata = NULL;

	if (src->lightingdata != NULL)
		dest->lightingdata = src->lightingdata->Clone(dest);
	else
		dest->lightingdata = NULL;
}


VERSION_CONTROL (p_map_cpp, "$Id: f61275cf5a9b350a1100d7cf2b730890b9415a92 $")
