// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2023 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file  p_map.c
/// \brief Movement, collision handling
///
///	Shooting and aiming

#include "doomdef.h"
#include "g_game.h"
#include "m_bbox.h"
#include "m_random.h"
#include "p_local.h"
#include "p_setup.h" // NiGHTS stuff
#include "r_fps.h"
#include "r_state.h"
#include "r_main.h"
#include "r_sky.h"
#include "s_sound.h"
#include "w_wad.h"

#include "r_splats.h"

#include "p_slopes.h"

#include "z_zone.h"

#include "lua_hook.h"

#include "m_perfstats.h" // ps_checkposition_calls

fixed_t tmbbox[4];
mobj_t *tmthing;
static INT32 tmflags;
fixed_t tmx;
fixed_t tmy;

static precipmobj_t *tmprecipthing;
static fixed_t preciptmbbox[4];

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

fixed_t tmfloorz, tmceilingz;
static fixed_t tmdropoffz, tmdrpoffceilz; // drop-off floor/ceiling heights
mobj_t *tmfloorthing; // the thing corresponding to tmfloorz or NULL if tmfloorz is from a sector
mobj_t *tmhitthing; // the solid thing you bumped into (for collisions)
ffloor_t *tmfloorrover, *tmceilingrover;
pslope_t *tmfloorslope, *tmceilingslope;

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

// set by PIT_CheckLine() for any line that stopped the PIT_CheckLine()
// that is, for any line which is 'solid'
line_t *blockingline;

msecnode_t *sector_list = NULL;
mprecipsecnode_t *precipsector_list = NULL;
camera_t *mapcampointer;

//
// TELEPORT MOVE
//

//
// P_TeleportMove
//
static boolean P_TeleportMove(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z)
{
	// the move is ok,
	// so link the thing into its new position
	P_UnsetThingPosition(thing);

	// Remove touching_sectorlist from mobj.
	if (sector_list)
	{
		P_DelSeclist(sector_list);
		sector_list = NULL;
	}

	thing->x = x;
	thing->y = y;
	thing->z = z;

	P_SetThingPosition(thing);

	P_CheckPosition(thing, thing->x, thing->y);

	if (P_MobjWasRemoved(thing))
		return true;

	thing->floorz = tmfloorz;
	thing->ceilingz = tmceilingz;
	thing->floorrover = tmfloorrover;
	thing->ceilingrover = tmceilingrover;

	return true;
}

// P_SetOrigin - P_TeleportMove which RESETS interpolation values.
//
boolean P_SetOrigin(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z)
{
	boolean result = P_TeleportMove(thing, x, y, z);

	if (result == true)
	{
		thing->old_x = thing->x;
		thing->old_y = thing->y;
		thing->old_z = thing->z;
	}

	return result;
}

//
// P_MoveOrigin - P_TeleportMove which KEEPS interpolation values.
//
boolean P_MoveOrigin(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z)
{
	return P_TeleportMove(thing, x, y, z);
}

// =========================================================================
//                       MOVEMENT ITERATOR FUNCTIONS
// =========================================================================

// P_DoSpring
//
// MF_SPRING does some weird, mildly hacky stuff sometimes.
//   mass = vertical speed
//   damage = horizontal speed
//   raisestate = state to change spring to on collision
//   reactiontime = number of times it can give 10 points (0 is standard)
//   painchance = spring mode:
//       0 = standard vanilla spring behaviour
//     Positive spring modes are minor variants of vanilla spring behaviour.
//       1 = launch players in jump
//       2 = don't modify player at all, just add momentum
//       3 = speed-booster mode (force onto ground, MF_AMBUSH causes auto-spin)
//     Negative spring modes are mildly-related gimmicks with customisation.
//      -1 = pinball bumper
//     Any other spring mode defaults to standard vanilla spring behaviour,
//     ****** but forward compatibility is not guaranteed for these. ******
//

boolean P_DoSpring(mobj_t *spring, mobj_t *object)
{
	fixed_t vertispeed = spring->info->mass;
	fixed_t horizspeed = spring->info->damage;
	boolean final = false;
	UINT8 strong = 0;

	// Object was already sprung this tic
	if (object->eflags & MFE_SPRUNG)
		return false;

	// Spectators don't trigger springs.
	if (object->player && object->player->spectator)
		return false;

	// "Even in Death" is a song from Volume 8, not a command.
	if (!spring->health || !object->health)
		return false;

	if (object->player)
	{
		if (spring->info->painchance == 3)
			;
		else if (object->player->powers[pw_strong] & STR_SPRING)
			strong = 1;
	}

	if (spring->info->painchance == -1) // Pinball bumper mode.
	{
		// The first of the entirely different spring modes!
		// Some of the attributes mean different things here.
		//   mass = default strength (can be controlled by mapthing's spawnangle)
		//   damage = unused
		angle_t horizangle, vertiangle;

		if (!vertispeed)
			return false;

		if (object->player && object->player->homing) // Sonic Heroes and Shadow the Hedgehog are the only games to contain homing-attackable bumpers!
		{
			horizangle = 0;
			vertiangle = ((object->eflags & MFE_VERTICALFLIP) ? ANGLE_270 : ANGLE_90) >> ANGLETOFINESHIFT;
			object->player->pflags &= ~PF_THOKKED;
			if (spring->eflags & MFE_VERTICALFLIP)
				object->z = spring->z - object->height - 1;
			else
				object->z = spring->z + spring->height + 1;
		}
		else
		{
			horizangle = R_PointToAngle2(spring->x, spring->y, object->x, object->y);
			vertiangle = (R_PointToAngle2(
							0,
							spring->z + spring->height/2,
							FixedHypot(object->x - spring->x, object->y - spring->y),
							object->z + object->height/2)
								>> ANGLETOFINESHIFT) & FINEMASK;
		}

		if (spring->spawnpoint && spring->spawnpoint->angle > 0)
			vertispeed = (spring->spawnpoint->angle<<(FRACBITS-1))/5;
		vertispeed = FixedMul(vertispeed, FixedMul(object->scale, spring->scale));

		if (object->player)
		{
			fixed_t playervelocity;

			if (strong)
				vertispeed <<= 1;

			if (!(object->player->pflags & PF_THOKKED) && !(object->player->homing)
			&& ((playervelocity = FixedDiv(9*FixedHypot(object->player->speed, object->momz), 10<<FRACBITS)) > vertispeed))
				vertispeed = playervelocity;

			if (object->player->powers[pw_carry] == CR_NIGHTSMODE) // THIS has NiGHTS support, at least...
			{
				angle_t nightsangle = 0;

				if (object->player->bumpertime > (TICRATE/2)-5)
					return false;

				if ((object->player->pflags & PF_TRANSFERTOCLOSEST) && object->player->axis1 && object->player->axis2)
				{
					nightsangle = R_PointToAngle2(object->player->axis1->x, object->player->axis1->y, object->player->axis2->x, object->player->axis2->y);
					nightsangle += ANGLE_90;
				}
				else if (object->target)
				{
					if (object->target->flags2 & MF2_AMBUSH)
						nightsangle = R_PointToAngle2(object->target->x, object->target->y, object->x, object->y);
					else
						nightsangle = R_PointToAngle2(object->x, object->y, object->target->x, object->target->y);
				}

				object->player->flyangle = AngleFixed(R_PointToAngle2(
					0,
					spring->z + spring->height/2,
					FixedMul(
						FINESINE(((nightsangle - horizangle) >> ANGLETOFINESHIFT) & FINEMASK),
						FixedHypot(object->x - spring->x, object->y - spring->y)),
					object->z + object->height/2))>>FRACBITS;

				object->player->bumpertime = TICRATE/2;
			}
			else
			{
				INT32 pflags = object->player->pflags & (PF_JUMPED|PF_NOJUMPDAMAGE|PF_SPINNING|PF_THOKKED|PF_BOUNCING|PF_CANCARRY); // Not identical to below...
				UINT8 secondjump = object->player->secondjump;
				UINT16 tailsfly = object->player->powers[pw_tailsfly];
				if (object->player->pflags & PF_GLIDING)
					P_SetMobjState(object, S_PLAY_FALL);
				P_ResetPlayer(object->player);
				object->player->pflags |= pflags;
				object->player->secondjump = secondjump;
				object->player->powers[pw_tailsfly] = tailsfly;
			}
		}

		if (!P_IsObjectOnGround(object)) // prevents uncurling when spinning due to "landing"
			object->momz = FixedMul(vertispeed, FINESINE(vertiangle));
		P_InstaThrust(object, horizangle, FixedMul(vertispeed, FINECOSINE(vertiangle)));

		object->eflags |= MFE_SPRUNG; // apply this flag asap!

		goto springstate;
	}

	// Does nothing?
	if (!vertispeed && !horizspeed)
		return false;

	object->standingslope = NULL; // Okay, now we know it's not going to be relevant - no launching off at silly angles for you.

	if (spring->eflags & MFE_VERTICALFLIP)
		vertispeed *= -1;

	if (object->player && (object->player->powers[pw_carry] == CR_NIGHTSMODE))
	{
		/*Someone want to make these work like bumpers?*/
		return false;
	}

	if (strong)
	{
		if (horizspeed)
			horizspeed = FixedMul(horizspeed, (4*FRACUNIT)/3);
		if (vertispeed)
			vertispeed = FixedMul(vertispeed, (6*FRACUNIT)/5); // aprox square root of above
	}

	object->eflags |= MFE_SPRUNG; // apply this flag asap!
	spring->flags &= ~(MF_SPRING|MF_SPECIAL); // De-solidify

	if (spring->info->painchance != 2)
	{
		if (object->player)
		{
			object->player->pflags &= ~PF_APPLYAUTOBRAKE;
#ifndef SPRINGSPIN
			object->player->powers[pw_justsprung] = 5;
			if (horizspeed)
				object->player->powers[pw_noautobrake] = ((horizspeed*TICRATE)>>(FRACBITS+3))/9; // TICRATE at 72*FRACUNIT
			else if (P_MobjFlip(object) == P_MobjFlip(spring))
				object->player->powers[pw_justsprung] |= (1<<15);
#else
			object->player->powers[pw_justsprung] = 15;
			if (horizspeed)
				object->player->powers[pw_noautobrake] = ((horizspeed*TICRATE)>>(FRACBITS+3))/9; // TICRATE at 72*FRACUNIT
			else
			{
				if (abs(object->player->rmomx) > object->scale || abs(object->player->rmomy) > object->scale)
					object->player->drawangle = R_PointToAngle2(0, 0, object->player->rmomx, object->player->rmomy);
				if (P_MobjFlip(object) == P_MobjFlip(spring))
					object->player->powers[pw_justsprung] |= (1<<15);
			}
#endif
		}

		if ((horizspeed && vertispeed) || (object->player && object->player->homing)) // Mimic SA
		{
			object->momx = object->momy = 0;
			P_TryMove(object, spring->x, spring->y, true);
		}

		if (vertispeed > 0)
			object->z = spring->z + spring->height + 1;
		else if (vertispeed < 0)
			object->z = spring->z - object->height - 1;
		else
		{
			fixed_t offx, offy;
			// Horizontal springs teleport you in FRONT of them.
			object->momx = object->momy = 0;

			// Overestimate the distance to position you at
			offx = P_ReturnThrustX(spring, spring->angle, (spring->radius + object->radius + 1) * 2);
			offy = P_ReturnThrustY(spring, spring->angle, (spring->radius + object->radius + 1) * 2);

			// Make it square by clipping
			if (offx > (spring->radius + object->radius + 1))
				offx = spring->radius + object->radius + 1;
			else if (offx < -(spring->radius + object->radius + 1))
				offx = -(spring->radius + object->radius + 1);

			if (offy > (spring->radius + object->radius + 1))
				offy = spring->radius + object->radius + 1;
			else if (offy < -(spring->radius + object->radius + 1))
				offy = -(spring->radius + object->radius + 1);

			// Set position!
			P_TryMove(object, spring->x + offx, spring->y + offy, true);

			if ((spring->info->painchance == 3))
			{
				object->z = spring->z;
				if (spring->eflags & MFE_VERTICALFLIP)
					object->z -= object->height;
				object->momz = 0;
			}
		}
	}

	if (vertispeed)
		object->momz = FixedMul(vertispeed,FixedSqrt(FixedMul(object->scale, spring->scale)));

	if (horizspeed)
		P_InstaThrustEvenIn2D(object, spring->angle, FixedMul(horizspeed,FixedSqrt(FixedMul(object->scale, spring->scale))));

	// Re-solidify
	spring->flags |= (spring->info->flags & (MF_SPRING|MF_SPECIAL));

	if (object->player)
	{
		INT32 pflags;
		UINT8 secondjump;

		if (spring->flags & MF_ENEMY) // Spring shells
			P_SetTarget(&spring->target, object);

		if (horizspeed)
		{
			object->angle = object->player->drawangle = spring->angle;

			if (!demoplayback || P_ControlStyle(object->player) == CS_LMAOGALOG)
				P_SetPlayerAngle(object->player, spring->angle);
		}

		if (object->player->pflags & PF_GLIDING)
			P_SetMobjState(object, S_PLAY_FALL);
		if ((spring->info->painchance == 3))
		{
			if (!(pflags = (object->player->pflags & PF_SPINNING)) &&
				(((object->player->charability2 == CA2_SPINDASH) && (object->player->cmd.buttons & BT_SPIN))
				|| (spring->flags2 & MF2_AMBUSH)))
			{
				pflags = PF_SPINNING;
				P_SetMobjState(object, S_PLAY_ROLL);
				S_StartSound(object, sfx_spin);
			}
			else
				P_SetMobjState(object, S_PLAY_ROLL);
		}
		else
		{
			boolean wasSpindashing = object->player->dashspeed > 0 && (object->player->charability2 == CA2_SPINDASH);

			pflags = object->player->pflags & (PF_STARTJUMP | PF_JUMPED | PF_NOJUMPDAMAGE | PF_SPINNING | PF_BOUNCING); // I still need these.

			if (wasSpindashing) // Ensure we're in the rolling state, and not spindash.
				P_SetMobjState(object, S_PLAY_ROLL);

			if (object->player->charability == CA_GLIDEANDCLIMB && object->player->skidtime && (pflags & PF_JUMPED))
			{
				object->player->skidtime = 0; // No skidding should be happening, either.
				pflags &= ~PF_JUMPED;
			}
		}
		secondjump = object->player->secondjump;
		P_ResetPlayer(object->player);

		if (spring->info->painchance == 1) // For all those ancient, SOC'd abilities.
		{
			object->player->pflags |= P_GetJumpFlags(object->player);
			P_SetMobjState(object, S_PLAY_JUMP);
		}
		else if ((spring->info->painchance == 2) || ((spring->info->painchance != 3) && (pflags & PF_BOUNCING))) // Adding momentum only.
		{
			object->player->pflags |= (pflags &~ PF_STARTJUMP);
			object->player->secondjump = secondjump;
		}
		else if (!vertispeed)
		{
			if (pflags & (PF_JUMPED|PF_SPINNING))
			{
				object->player->pflags |= pflags;
				object->player->secondjump = secondjump;
			}
			else if (object->player->dashmode >= DASHMODE_THRESHOLD)
				P_SetMobjState(object, S_PLAY_DASH);
			else if (P_IsObjectOnGround(object))
				P_SetMobjState(object, (horizspeed >= FixedMul(object->player->runspeed, object->scale)) ? S_PLAY_RUN : S_PLAY_WALK);
			else
				P_SetMobjState(object, (object->momz > 0) ? S_PLAY_SPRING : S_PLAY_FALL);
		}
		else if (P_MobjFlip(object)*vertispeed > 0)
			P_SetMobjState(object, S_PLAY_SPRING);
		else
			P_SetMobjState(object, S_PLAY_FALL);
	}
	else if (horizspeed
		&& object->tracer
		&& object->tracer->player
		&& object->tracer->player->powers[pw_carry] != CR_NONE
		&& object->tracer->tracer == object
		&& (!demoplayback || P_ControlStyle(object->tracer->player) == CS_LMAOGALOG))
			P_SetPlayerAngle(object->tracer->player, spring->angle);

	object->standingslope = NULL; // And again.

	final = true;

springstate:
	if ((statenum_t)(spring->state-states) < spring->info->raisestate)
	{
		P_SetMobjState(spring, spring->info->raisestate);
		if (object->player && spring->reactiontime && !(spring->info->flags & MF_ENEMY))
		{
			if (object->player->powers[pw_carry] != CR_NIGHTSMODE) // don't make graphic in NiGHTS
				P_SetMobjState(P_SpawnMobj(spring->x, spring->y, spring->z + (spring->height/2), MT_SCORE), mobjinfo[MT_SCORE].spawnstate+11);
			P_AddPlayerScore(object->player, 10);
			spring->reactiontime--;
		}

		if (strong)
		{
			if (object->player->charability == CA_TWINSPIN || object->player->charability2 == CA2_MELEE)
				P_TwinSpinRejuvenate(object->player, (object->player->charability == CA_TWINSPIN ? object->player->thokitem : object->player->revitem));
			S_StartSound(object, sfx_sprong); // strong spring. sprong.
		}
	}

	return final;
}

static void P_DoFan(mobj_t *fan, mobj_t *object)
{
	player_t *p = object->player; // will be NULL if not a player
	fixed_t zdist; // distance between bottoms
	fixed_t speed = fan->info->mass; // fans use this for the vertical thrust
	SINT8 flipval = P_MobjFlip(fan); // virtually everything here centers around the thruster's gravity, not the object's!

	if (p && object->state == &states[object->info->painstate]) // can't use fans when player is in pain!
		return;

	// is object's top below thruster's position? if not, calculate distance between their bottoms
	if (fan->eflags & MFE_VERTICALFLIP)
	{
		if (object->z > fan->z + fan->height)
			return;
		zdist = (fan->z + fan->height) - (object->z + object->height);
	}
	else
	{
		if (object->z + object->height < fan->z)
			return;
		zdist = object->z - fan->z;
	}

	object->standingslope = NULL; // No launching off at silly angles for you.

	switch (fan->type)
	{
		case MT_FAN: // fan
			if (zdist > (fan->health << FRACBITS)) // max z distance determined by health (set by map thing args[0])
				break;
			if (flipval*object->momz >= FixedMul(speed, fan->scale)) // if object's already moving faster than your best, don't bother
				break;
			if (p && (p->climbing || p->pflags & PF_GLIDING)) // doesn't affect Knux when he's using his abilities!
				break;

			object->momz += flipval*FixedMul(speed/4, fan->scale);

			// limit the speed if too high
			if (flipval*object->momz > FixedMul(speed, fan->scale))
				object->momz = flipval*FixedMul(speed, fan->scale);

			if (p && !p->powers[pw_tailsfly] && !p->powers[pw_carry]) // doesn't reset anim for Tails' flight
			{
				P_ResetPlayer(p);
				P_SetMobjState(object, S_PLAY_FALL);
				P_SetTarget(&object->tracer, fan);
				p->powers[pw_carry] = CR_FAN;
			}
			break;
		default:
			break;
	}
}

static void P_DoPterabyteCarry(player_t *player, mobj_t *ptera)
{
	if (player->powers[pw_carry] && player->powers[pw_carry] != CR_ROLLOUT)
		return;
	if (player->powers[pw_ignorelatch] & (1<<15))
		return;
	if (ptera->extravalue1 != 1)
		return; // Not swooping
	if (ptera->target != player->mo)
		return; // Not swooping for you!

	if (player->spectator)
		return;

	if ((player->mo->eflags & MFE_VERTICALFLIP) != (ptera->eflags & MFE_VERTICALFLIP))
		return; // Both should be in same gravity

	if (ptera->eflags & MFE_VERTICALFLIP)
	{
		if (ptera->ceilingz - (ptera->z + ptera->height) < player->mo->height - FixedMul(2*FRACUNIT, player->mo->scale))
			return;
	}
	else if (ptera->z - ptera->floorz < player->mo->height - FixedMul(2*FRACUNIT, player->mo->scale))
		return; // No room to pick up this guy!

	P_ResetPlayer(player);
	P_SetTarget(&player->mo->tracer, ptera);
	player->pflags &= ~PF_APPLYAUTOBRAKE;
	player->powers[pw_carry] = CR_PTERABYTE;
	S_StartSound(player->mo, sfx_s3k4a);
	P_UnsetThingPosition(player->mo);
	player->mo->x = ptera->x;
	player->mo->y = ptera->y;
	P_SetThingPosition(player->mo);
	ptera->movefactor = 3*TICRATE; // timer before dropping
	ptera->watertop = ptera->waterbottom = ptera->cusval = 0;
}

static void P_DoTailsCarry(player_t *sonic, player_t *tails)
{
	INT32 p;
	fixed_t zdist; // z distance between the two players' bottoms

	if (tails->powers[pw_carry])
		return;
	if (sonic->powers[pw_carry])
		return;

	if (tails->spectator)
		return;
	if (sonic->spectator)
		return;

	if (!(tails->pflags & PF_CANCARRY))
		return;

	if ((sonic->mo->eflags & MFE_VERTICALFLIP) != (tails->mo->eflags & MFE_VERTICALFLIP))
		return; // Both should be in same gravity

	if (tails->mo->eflags & MFE_VERTICALFLIP)
	{
		if (tails->mo->ceilingz - (tails->mo->z + tails->mo->height) < sonic->mo->height-FixedMul(2*FRACUNIT, sonic->mo->scale))
			return;
	}
	else if (tails->mo->z - tails->mo->floorz < sonic->mo->height-FixedMul(2*FRACUNIT, sonic->mo->scale))
		return; // No room to pick up this guy!

	// Search in case another player is already being carried by this fox.
	for (p = 0; p < MAXPLAYERS; p++)
		if (playeringame[p] && players[p].mo
		&& players[p].powers[pw_carry] == CR_PLAYER && players[p].mo->tracer == tails->mo)
			return;

	// Why block opposing teams from tailsflying each other?
	// Sneaking into the hands of a flying tails player in Race might be a viable strategy, who knows.
	/*
	if ((gametyperules & GTR_RACE)
		|| (netgame && (tails->spectator || sonic->spectator))
		|| (G_TagGametype() && (!(tails->pflags & PF_TAGIT) != !(sonic->pflags & PF_TAGIT)))
		|| (gametype == GT_MATCH)
		|| (G_GametypeHasTeams() && tails->ctfteam != sonic->ctfteam))
		return; */

	if (tails->mo->eflags & MFE_VERTICALFLIP)
		zdist = (sonic->mo->z + sonic->mo->height) - (tails->mo->z + tails->mo->height);
	else
		zdist = tails->mo->z - sonic->mo->z;

	if (zdist <= sonic->mo->height + sonic->mo->scale // FixedMul(FRACUNIT, sonic->mo->scale), but scale == FRACUNIT by default
		&& zdist > sonic->mo->height*2/3
		&& P_MobjFlip(tails->mo)*sonic->mo->momz <= 0
		&& !(sonic->powers[pw_ignorelatch] & (1<<15)))
	{
		if (sonic-players == consoleplayer && botingame)
			CV_SetValue(&cv_analog[1], false);
		P_ResetPlayer(sonic);
		P_SetTarget(&sonic->mo->tracer, tails->mo);
		sonic->powers[pw_carry] = CR_PLAYER;
		S_StartSound(sonic->mo, sfx_s3k4a);
		P_UnsetThingPosition(sonic->mo);
		sonic->mo->x = tails->mo->x;
		sonic->mo->y = tails->mo->y;
		P_SetThingPosition(sonic->mo);
	}
	else {
		if (sonic-players == consoleplayer && botingame)
			CV_SetValue(&cv_analog[1], true);
		P_SetTarget(&sonic->mo->tracer, NULL);
		sonic->powers[pw_carry] = CR_NONE;
	}
}

// Boss 5 post-defeat comedy
static void P_SlapStick(mobj_t *fang, mobj_t *pole)
{
	fixed_t momx1, momx2, momy1, momy2;

#define dist 3
	momx1 = pole->momx/dist;
	momy1 = pole->momy/dist;
	momx2 = fang->momx/dist;
	momy2 = fang->momy/dist;

	pole->tracer->tracer->momx = momx1 + (dist-1)*momx2;
	pole->tracer->tracer->momy = momy1 + (dist-1)*momy2;
	fang->momx = (dist-1)*momx1 + momx2;
	fang->momy = (dist-1)*momy1 + momy2;
#undef dist

	P_SetObjectMomZ(pole->tracer->tracer, 6*FRACUNIT, false);
	pole->tracer->tracer->flags &= ~(MF_NOGRAVITY|MF_NOCLIP);
	pole->tracer->tracer->movedir = ANGLE_67h;
	if ((R_PointToAngle(fang->x - pole->tracer->tracer->x, fang->y - pole->tracer->tracer->y) - pole->angle) > ANGLE_180)
		pole->tracer->tracer->movedir = InvAngle(pole->tracer->movedir);

	P_SetObjectMomZ(fang, 14*FRACUNIT, false);
	fang->flags |= MF_NOGRAVITY|MF_NOCLIP;
	P_SetMobjState(fang, fang->info->xdeathstate);

	pole->tracer->tracer->tics = pole->tracer->tics = pole->tics = fang->tics;

	var1 = var2 = 0;
	A_Scream(pole->tracer->tracer);
	S_StartSound(fang, sfx_altdi1);

	P_SetTarget(&pole->tracer->tracer, NULL);
	P_SetMobjState(pole->tracer, pole->info->xdeathstate);
	P_SetTarget(&pole->tracer, NULL);
	P_SetMobjState(pole, pole->info->deathstate);
}

static void P_PlayerBarrelCollide(mobj_t *toucher, mobj_t *barrel)
{
	if (toucher->momz < 0)
	{
		if (toucher->z + toucher->momz > barrel->z + barrel->height)
			return;
	}
	else
	{
		if (toucher->z > barrel->z + barrel->height)
			return;
	}

	if (toucher->momz > 0)
	{
		if (toucher->z + toucher->height + toucher->momz < barrel->z)
			return;
	}
	else
	{
		if (toucher->z + toucher->height < barrel->z)
			return;
	}

	if (P_PlayerCanDamage(toucher->player, barrel))
		P_DamageMobj(barrel, toucher, toucher, 1, 0);
}

enum
{
	CHECKTHING_NOCOLLIDE,
	CHECKTHING_COLLIDE,
	CHECKTHING_DONE,
	CHECKTHING_IGNORE
};

static unsigned PIT_DoCheckThing(mobj_t *thing)
{
	fixed_t blockdist;

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

	// Ignore... things.
	if (!tmthing || !thing || P_MobjWasRemoved(thing))
		return CHECKTHING_IGNORE;

	I_Assert(!P_MobjWasRemoved(tmthing));
	I_Assert(!P_MobjWasRemoved(thing));

	// Ignore spectators
	if ((tmthing->player && tmthing->player->spectator)
	|| (thing->player && thing->player->spectator))
		return CHECKTHING_IGNORE;

	// Do name checks all the way up here
	// So that NOTHING ELSE can see MT_NAMECHECK because it is client-side.
	if (tmthing->type == MT_NAMECHECK)
	{
		// Ignore things that aren't players, ignore spectators, ignore yourself.
		if (!thing->player || !(tmthing->target && tmthing->target->player) || thing->player->spectator || (tmthing->target && thing->player == tmthing->target->player))
			return CHECKTHING_IGNORE;

		// Now check that you actually hit them.
		blockdist = thing->radius + tmthing->radius;
		if (abs(thing->x - tmx) >= blockdist || abs(thing->y - tmy) >= blockdist)
			return CHECKTHING_NOCOLLIDE; // didn't hit it
		// see if it went over / under
		if (tmthing->z > thing->z + thing->height)
			return CHECKTHING_NOCOLLIDE; // overhead
		if (tmthing->z + tmthing->height < thing->z)
			return CHECKTHING_NOCOLLIDE; // underneath

		// Call any SeenPlayer Lua hooks
		if (!LUA_HookSeenPlayer(tmthing->target->player, thing->player))
			return CHECKTHING_DONE;

		seenplayer = thing->player;
		return CHECKTHING_DONE;
	}

	// Metal Sonic destroys tiny baby objects.
	if (tmthing->type == MT_METALSONIC_RACE
	&& (thing->flags & (MF_MISSILE|MF_ENEMY|MF_BOSS)
	|| (thing->type == MT_SPIKE
	|| thing->type == MT_WALLSPIKE)))
	{
		if ((thing->flags & (MF_ENEMY|MF_BOSS)) && (thing->health <= 0 || !(thing->flags & MF_SHOOTABLE)))
			return CHECKTHING_IGNORE;
		blockdist = thing->radius + tmthing->radius;
		if (abs(thing->x - tmx) >= blockdist || abs(thing->y - tmy) >= blockdist)
			return CHECKTHING_NOCOLLIDE; // didn't hit it
		// see if it went over / under
		if (tmthing->z > thing->z + thing->height)
			return CHECKTHING_NOCOLLIDE; // overhead
		if (tmthing->z + tmthing->height < thing->z)
			return CHECKTHING_NOCOLLIDE; // underneath
		if (thing->type == MT_SPIKE
		|| thing->type == MT_WALLSPIKE)
		{
			mobj_t *iter;
			if (thing->flags & MF_SOLID)
				S_StartSound(tmthing, thing->info->deathsound);
			for (iter = thing->subsector->sector->thinglist; iter; iter = iter->snext)
				if (iter->type == thing->type && iter->health > 0 && iter->flags & MF_SOLID && (iter == thing || P_AproxDistance(P_AproxDistance(thing->x - iter->x, thing->y - iter->y), thing->z - iter->z) < 56*thing->scale))//FixedMul(56*FRACUNIT, thing->scale))
					P_KillMobj(iter, tmthing, tmthing, 0);
		}
		else
		{
			thing->health = 0;
			P_KillMobj(thing, tmthing, tmthing, 0);
		}
		return CHECKTHING_COLLIDE;
	}

	// STR_SPIKE users destroy spikes
	if ((tmthing->player) && ((tmthing->player->powers[pw_strong] & STR_SPIKE) && (thing->type == MT_SPIKE || thing->type == MT_WALLSPIKE)))
	{
		mobj_t *iter;
		blockdist = thing->radius + tmthing->radius;
		if (abs(thing->x - tmx) >= blockdist || abs(thing->y - tmy) >= blockdist)
			return CHECKTHING_NOCOLLIDE; // didn't hit it
		// see if it went over / under
		if (tmthing->z > thing->z + thing->height)
			return CHECKTHING_NOCOLLIDE; // overhead
		if (tmthing->z + tmthing->height < thing->z)
			return CHECKTHING_NOCOLLIDE; // underneath

		if (thing->flags & MF_SOLID)
			S_StartSound(tmthing, thing->info->deathsound);
		for (iter = thing->subsector->sector->thinglist; iter; iter = iter->snext)
			if (iter->type == thing->type && iter->health > 0 && iter->flags & MF_SOLID && (iter == thing || P_AproxDistance(P_AproxDistance(thing->x - iter->x, thing->y - iter->y), thing->z - iter->z) < 56*thing->scale))//FixedMul(56*FRACUNIT, thing->scale))
				P_KillMobj(iter, tmthing, tmthing, 0);
		return CHECKTHING_COLLIDE;
	}

	// vectorise metal - done in a special case as at this point neither has the right flags for touching
	if (thing->type == MT_METALSONIC_BATTLE
	&& (tmthing->flags & MF_MISSILE)
	&& tmthing->target != thing
	&& thing->state == &states[thing->info->spawnstate])
	{
		blockdist = thing->radius + tmthing->radius;

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

		if (tmthing->z > thing->z + thing->height)
			return CHECKTHING_NOCOLLIDE; // overhead
		if (tmthing->z + tmthing->height < thing->z)
			return CHECKTHING_NOCOLLIDE; // underneath

		thing->flags2 |= MF2_CLASSICPUSH;

		return CHECKTHING_COLLIDE;
	}

	if ((thing->flags & MF_NOCLIPTHING) || !(thing->flags & (MF_SOLID|MF_SPECIAL|MF_PAIN|MF_SHOOTABLE|MF_SPRING)))
		return CHECKTHING_IGNORE;

	// Don't collide with your buddies while NiGHTS-flying.
	if (tmthing->player && thing->player && (maptol & TOL_NIGHTS)
		&& ((tmthing->player->powers[pw_carry] == CR_NIGHTSMODE) || (thing->player->powers[pw_carry] == CR_NIGHTSMODE)))
		return CHECKTHING_IGNORE;

	blockdist = thing->radius + tmthing->radius;

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

	if (thing->flags & MF_PAPERCOLLISION) // CAUTION! Very easy to get stuck inside MF_SOLID objects. Giving the player MF_PAPERCOLLISION is a bad idea unless you know what you're doing.
	{
		fixed_t cosradius, sinradius;
		vertex_t v1, v2; // fake vertexes
		line_t junk; // fake linedef

		cosradius = FixedMul(thing->radius, FINECOSINE(thing->angle>>ANGLETOFINESHIFT));
		sinradius = FixedMul(thing->radius, FINESINE(thing->angle>>ANGLETOFINESHIFT));

		v1.x = thing->x - cosradius;
		v1.y = thing->y - sinradius;
		v2.x = thing->x + cosradius;
		v2.y = thing->y + sinradius;

		junk.v1 = &v1;
		junk.v2 = &v2;
		junk.dx = 2*cosradius; // v2.x - v1.x;
		junk.dy = 2*sinradius; // v2.y - v1.y;

		if (tmthing->flags & MF_PAPERCOLLISION) // more strenuous checking to prevent clipping issues
		{
			INT32 check1, check2, check3, check4;
			fixed_t tmcosradius = FixedMul(tmthing->radius, FINECOSINE(tmthing->angle>>ANGLETOFINESHIFT));
			fixed_t tmsinradius = FixedMul(tmthing->radius, FINESINE(tmthing->angle>>ANGLETOFINESHIFT));
			if (abs(thing->x - tmx) >= (abs(tmcosradius) + abs(cosradius)) || abs(thing->y - tmy) >= (abs(tmsinradius) + abs(sinradius)))
				return CHECKTHING_NOCOLLIDE; // didn't hit it
			check1 = P_PointOnLineSide(tmx - tmcosradius, tmy - tmsinradius, &junk);
			check2 = P_PointOnLineSide(tmx + tmcosradius, tmy + tmsinradius, &junk);
			check3 = P_PointOnLineSide(tmx + tmthing->momx - tmcosradius, tmy + tmthing->momy - tmsinradius, &junk);
			check4 = P_PointOnLineSide(tmx + tmthing->momx + tmcosradius, tmy + tmthing->momy + tmsinradius, &junk);
			if ((check1 == check2) && (check2 == check3) && (check3 == check4))
				return CHECKTHING_NOCOLLIDE; // the line doesn't cross between collider's start or end
		}
		else
		{
			if (abs(thing->x - tmx) >= (tmthing->radius + abs(cosradius)) || abs(thing->y - tmy) >= (tmthing->radius + abs(sinradius)))
				return CHECKTHING_NOCOLLIDE; // didn't hit it
			if ((P_PointOnLineSide(tmx - tmthing->radius, tmy - tmthing->radius, &junk)
			== P_PointOnLineSide(tmx + tmthing->radius, tmy + tmthing->radius, &junk))
			&& (P_PointOnLineSide(tmx + tmthing->radius, tmy - tmthing->radius, &junk)
			== P_PointOnLineSide(tmx - tmthing->radius, tmy + tmthing->radius, &junk)))
				return CHECKTHING_NOCOLLIDE; // the line doesn't cross between either pair of opposite corners
		}
	}
	else if (tmthing->flags & MF_PAPERCOLLISION)
	{
		fixed_t tmcosradius, tmsinradius;
		vertex_t v1, v2; // fake vertexes
		line_t junk; // fake linedef

		tmcosradius = FixedMul(tmthing->radius, FINECOSINE(tmthing->angle>>ANGLETOFINESHIFT));
		tmsinradius = FixedMul(tmthing->radius, FINESINE(tmthing->angle>>ANGLETOFINESHIFT));

		if (abs(thing->x - tmx) >= (thing->radius + abs(tmcosradius)) || abs(thing->y - tmy) >= (thing->radius + abs(tmsinradius)))
			return CHECKTHING_NOCOLLIDE; // didn't hit it

		v1.x = tmx - tmcosradius;
		v1.y = tmy - tmsinradius;
		v2.x = tmx + tmcosradius;
		v2.y = tmy + tmsinradius;

		junk.v1 = &v1;
		junk.v2 = &v2;
		junk.dx = 2*tmcosradius; // v2.x - v1.x;
		junk.dy = 2*tmsinradius; // v2.y - v1.y;

		// no need to check whether other thing has MF_PAPERCOLLISION, since would fall under other condition
		if ((P_PointOnLineSide(thing->x - thing->radius, thing->y - thing->radius, &junk)
		== P_PointOnLineSide(thing->x + thing->radius, thing->y + thing->radius, &junk))
		&& (P_PointOnLineSide(thing->x + thing->radius, thing->y - thing->radius, &junk)
		== P_PointOnLineSide(thing->x - thing->radius, thing->y + thing->radius, &junk)))
			return CHECKTHING_NOCOLLIDE; // the line doesn't cross between either pair of opposite corners
	}

	{
		UINT8 shouldCollide = LUA_Hook2Mobj(thing, tmthing, MOBJ_HOOK(MobjCollide)); // checks hook for thing's type
		if (P_MobjWasRemoved(tmthing) || P_MobjWasRemoved(thing))
			return CHECKTHING_NOCOLLIDE; // one of them was removed???
		if (shouldCollide == 1)
			return CHECKTHING_DONE; // force collide
		else if (shouldCollide == 2)
			return CHECKTHING_NOCOLLIDE; // force no collide

		shouldCollide = LUA_Hook2Mobj(tmthing, thing, MOBJ_HOOK(MobjMoveCollide)); // checks hook for tmthing's type
		if (P_MobjWasRemoved(tmthing) || P_MobjWasRemoved(thing))
			return CHECKTHING_NOCOLLIDE; // one of them was removed???
		if (shouldCollide == 1)
			return CHECKTHING_DONE; // force collide
		else if (shouldCollide == 2)
			return CHECKTHING_NOCOLLIDE; // force no collide
	}

	if (tmthing->type == MT_LAVAFALL_LAVA && (thing->type == MT_RING || thing->type == MT_REDTEAMRING || thing->type == MT_BLUETEAMRING || thing->type == MT_FLINGRING))
	{
		//height check
		if (tmthing->z > thing->z + thing->height || thing->z > tmthing->z + tmthing->height || !(thing->health))
			return CHECKTHING_NOCOLLIDE;

		P_KillMobj(thing, tmthing, tmthing, DMG_FIRE);
	}

	if (tmthing->type == MT_MINECART)
	{
		//height check
		if (tmthing->z > thing->z + thing->height || thing->z > tmthing->z + tmthing->height || !(thing->health))
			return CHECKTHING_NOCOLLIDE;

		if (thing->type == MT_TNTBARREL)
			P_KillMobj(thing, tmthing, tmthing->target, 0);
		else if ((thing->flags & MF_MONITOR) || (thing->flags & MF_ENEMY))
		{
			P_KillMobj(thing, tmthing, tmthing->target, 0);
			if (tmthing->momz*P_MobjFlip(tmthing) < 0)
				tmthing->momz = abs(tmthing->momz)*P_MobjFlip(tmthing);
		}
	}

	if (thing->type == MT_SALOONDOOR && tmthing->player)
	{
		mobj_t *ref = (tmthing->player->powers[pw_carry] == CR_MINECART && tmthing->tracer && !P_MobjWasRemoved(tmthing->tracer)) ? tmthing->tracer : tmthing;
		if (((thing->flags2 & MF2_AMBUSH) && (tmthing->z <= thing->z + thing->height) && (tmthing->z + tmthing->height >= thing->z))
			|| ref != tmthing)
		{
			fixed_t dm = min(FixedHypot(ref->momx, ref->momy), 16*FRACUNIT);
			angle_t ang = R_PointToAngle2(0, 0, ref->momx, ref->momy) - thing->angle;
			fixed_t s = FINESINE((ang >> ANGLETOFINESHIFT) & FINEMASK);
			S_StartSound(tmthing, thing->info->activesound);
			thing->extravalue2 += 2*FixedMul(s, dm)/3;
			return CHECKTHING_COLLIDE;
		}
	}

	if (thing->type == MT_SALOONDOORCENTER && tmthing->player)
	{
		if (((thing->flags2 & MF2_AMBUSH) && (tmthing->z <= thing->z + thing->height) && (tmthing->z + tmthing->height >= thing->z))
			|| (tmthing->player->powers[pw_carry] == CR_MINECART && tmthing->tracer && !P_MobjWasRemoved(tmthing->tracer)))
			return CHECKTHING_COLLIDE;
	}

	if (thing->type == MT_ROLLOUTROCK && tmthing->player && tmthing->health)
	{
		if (tmthing->player->powers[pw_carry] == CR_ROLLOUT)
		{
			return CHECKTHING_NOCOLLIDE;
		}
		if ((thing->flags & MF_PUSHABLE) // not carrying a player
			&& (tmthing->player->powers[pw_carry] == CR_NONE) // player is not already riding something
			&& !(tmthing->player->powers[pw_ignorelatch] & (1<<15))
			&& (P_MobjFlip(tmthing)*tmthing->momz <= 0)
			&& ((!(tmthing->eflags & MFE_VERTICALFLIP) && abs(thing->z + thing->height - tmthing->z) < (thing->height>>2))
				|| (tmthing->eflags & MFE_VERTICALFLIP && abs(tmthing->z + tmthing->height - thing->z) < (thing->height>>2))))
		{
			thing->flags &= ~MF_PUSHABLE; // prevent riding player from applying pushable movement logic
			thing->flags2 &= ~MF2_DONTDRAW; // don't leave the rock invisible if it was flashing prior to boarding
			P_SetTarget(&thing->tracer, tmthing);
			P_ResetPlayer(tmthing->player);
			P_SetMobjState(tmthing, S_PLAY_WALK);
			tmthing->player->powers[pw_carry] = CR_ROLLOUT;
			P_SetTarget(&tmthing->tracer, thing);
			if (!P_IsObjectOnGround(thing))
				thing->momz += tmthing->momz;

			return CHECKTHING_COLLIDE;
		}
	}
	else if (tmthing->type == MT_ROLLOUTROCK)
	{
		if (tmthing->z > thing->z + thing->height || thing->z > tmthing->z + tmthing->height || !thing->health)
			return CHECKTHING_NOCOLLIDE;

		if (thing == tmthing->tracer) // don't collide with rider
			return CHECKTHING_IGNORE;

		if (thing->flags & MF_SPRING) // bounce on springs
		{
			P_DoSpring(thing, tmthing);
			return CHECKTHING_COLLIDE;
		}
		else if ((thing->flags & (MF_MONITOR|MF_SHOOTABLE)) == (MF_MONITOR|MF_SHOOTABLE) && !(tmthing->flags & MF_PUSHABLE)) // pop monitors while carrying a player
		{
			P_KillMobj(thing, tmthing, tmthing->tracer, 0);
			return CHECKTHING_COLLIDE;
		}

		if (thing->type == tmthing->type // bounce against other rollout rocks
			&& (tmthing->momx || tmthing->momy || thing->momx || thing->momy))
		{
			fixed_t tempmomx = thing->momx, tempmomy = thing->momy;
			thing->momx = tmthing->momx;
			thing->momy = tmthing->momy;
			tmthing->momx = tempmomx;
			tmthing->momy = tempmomy;
			S_StartSound(thing, thing->info->painsound);
		}
	}

	if (thing->type == MT_PTERABYTE && tmthing->player)
		P_DoPterabyteCarry(tmthing->player, thing);

	if (thing->type == MT_TNTBARREL && tmthing->player)
		P_PlayerBarrelCollide(tmthing, thing);

	if (thing->type == MT_VULTURE && tmthing->type == MT_VULTURE)
	{
		fixed_t dx = thing->x - tmthing->x;
		fixed_t dy = thing->y - tmthing->y;
		fixed_t dz = thing->z - tmthing->z;
		fixed_t dm = FixedHypot(dz, FixedHypot(dx, dy));
		thing->momx += FixedDiv(dx, dm);
		thing->momy += FixedDiv(dy, dm);
		thing->momz += FixedDiv(dz, dm);
	}

	if (tmthing->type == MT_FANG && thing->type == MT_FSGNB)
	{
		if (thing->z > tmthing->z + tmthing->height)
			return CHECKTHING_NOCOLLIDE; // overhead
		if (thing->z + thing->height < tmthing->z)
			return CHECKTHING_NOCOLLIDE; // underneath
		if (!thing->tracer || !thing->tracer->tracer)
			return CHECKTHING_IGNORE;
		P_SlapStick(tmthing, thing);
		// no return value was used in the original prototype script at this point,
		// so I'm assuming we fall back on the solid code to determine how it all ends?
		// -- Monster Iestyn
	}

	// Billiards mines!
	if (thing->type == MT_BIGMINE)
	{
		if (tmthing->type == MT_BIGMINE)
		{
			if (!tmthing->momx && !tmthing->momy)
				return CHECKTHING_IGNORE;
			if ((statenum_t)(thing->state-states) >= thing->info->meleestate)
				return CHECKTHING_IGNORE;
			if (thing->z > tmthing->z + tmthing->height)
				return CHECKTHING_NOCOLLIDE; // overhead
			if (thing->z + thing->height < tmthing->z)
				return CHECKTHING_NOCOLLIDE; // underneath

			thing->momx = tmthing->momx/3;
			thing->momy = tmthing->momy/3;
			thing->momz = tmthing->momz/3;
			tmthing->momx /= -8;
			tmthing->momy /= -8;
			tmthing->momz /= -8;
			if (thing->info->activesound)
				S_StartSound(thing, thing->info->activesound);
			P_SetMobjState(thing, thing->info->meleestate);
			P_SetTarget(&thing->tracer, tmthing->tracer);
			return CHECKTHING_COLLIDE;
		}
		else if (tmthing->type == MT_CRUSHCLAW)
		{
			if (tmthing->extravalue1 <= 0)
				return CHECKTHING_IGNORE;
			if ((statenum_t)(thing->state-states) >= thing->info->meleestate)
				return CHECKTHING_IGNORE;
			if (thing->z > tmthing->z + tmthing->height)
				return CHECKTHING_NOCOLLIDE; // overhead
			if (thing->z + thing->height < tmthing->z)
				return CHECKTHING_NOCOLLIDE; // underneath

			thing->momx = P_ReturnThrustX(tmthing, tmthing->angle, 2*tmthing->extravalue1*tmthing->scale/3);
			thing->momy = P_ReturnThrustY(tmthing, tmthing->angle, 2*tmthing->extravalue1*tmthing->scale/3);
			if (thing->info->activesound)
				S_StartSound(thing, thing->info->activesound);
			P_SetMobjState(thing, thing->info->meleestate);
			if (tmthing->tracer)
				P_SetTarget(&thing->tracer, tmthing->tracer->target);
			return CHECKTHING_DONE;
		}
	}

	// When solid spikes move, assume they just popped up and teleport things on top of them to hurt.
	if (tmthing->type == MT_SPIKE && (thing->flags & MF_SOLID) && (tmthing->flags & MF_SOLID))
	{
		if (thing->z > tmthing->z + tmthing->height)
			return CHECKTHING_NOCOLLIDE; // overhead
		if (thing->z + thing->height < tmthing->z)
			return CHECKTHING_NOCOLLIDE; // underneath

		if (tmthing->eflags & MFE_VERTICALFLIP)
			P_SetOrigin(thing, thing->x, thing->y, tmthing->z - thing->height - FixedMul(FRACUNIT, tmthing->scale));
		else
			P_SetOrigin(thing, thing->x, thing->y, tmthing->z + tmthing->height + FixedMul(FRACUNIT, tmthing->scale));
		if (thing->flags & MF_SHOOTABLE)
			P_DamageMobj(thing, tmthing, tmthing, 1, DMG_SPIKE);
		return CHECKTHING_COLLIDE;
	}

	if (thing->flags & MF_PAIN && tmthing->player)
	{ // Player touches painful thing sitting on the floor
		// see if it went over / under
		if (thing->z > tmthing->z + tmthing->height)
			return CHECKTHING_NOCOLLIDE; // overhead
		if (thing->z + thing->height < tmthing->z)
			return CHECKTHING_NOCOLLIDE; // underneath
		if (tmthing->flags & MF_SHOOTABLE && thing->health > 0)
		{
			UINT32 damagetype = (thing->info->mass & 0xFF);
			if (!damagetype && thing->flags & MF_FIRE) // BURN!
				damagetype = DMG_FIRE;
			if (P_DamageMobj(tmthing, thing, thing, 1, damagetype) && (damagetype = (thing->info->mass>>8)))
				S_StartSound(thing, damagetype);
			return CHECKTHING_COLLIDE;
		}
		return CHECKTHING_NOCOLLIDE;
	}
	else if (tmthing->flags & MF_PAIN && thing->player)
	{ // Painful thing splats player in the face
		// see if it went over / under
		if (tmthing->z > thing->z + thing->height)
			return CHECKTHING_NOCOLLIDE; // overhead
		if (tmthing->z + tmthing->height < thing->z)
			return CHECKTHING_NOCOLLIDE; // underneath
		if (thing->flags & MF_SHOOTABLE && tmthing->health > 0)
		{
			UINT32 damagetype = (tmthing->info->mass & 0xFF);
			if (!damagetype && tmthing->flags & MF_FIRE) // BURN!
				damagetype = DMG_FIRE;
			if (P_DamageMobj(thing, tmthing, tmthing, 1, damagetype) && (damagetype = (tmthing->info->mass>>8)))
				S_StartSound(tmthing, damagetype);
			return CHECKTHING_COLLIDE;
		}
		return CHECKTHING_NOCOLLIDE;
	}

	if (thing->type == MT_HOOPCOLLIDE && thing->flags & MF_SPECIAL && tmthing->player)
	{
		P_TouchSpecialThing(thing, tmthing, true);
		return CHECKTHING_COLLIDE;
	}

	// check for skulls slamming into things
	if (tmthing->flags2 & MF2_SKULLFLY)
	{
		if (tmthing->type == MT_EGGMOBILE) // Don't make Eggman stop!
			return CHECKTHING_COLLIDE; // Let him RUN YOU RIGHT OVER. >:3
		else
		{
			// see if it went over / under
			if (tmthing->z > thing->z + thing->height)
				return CHECKTHING_NOCOLLIDE; // overhead
			if (tmthing->z + tmthing->height < thing->z)
				return CHECKTHING_NOCOLLIDE; // underneath

			tmthing->flags2 &= ~MF2_SKULLFLY;
			tmthing->momx = tmthing->momy = tmthing->momz = 0;
			return CHECKTHING_DONE; // stop moving
		}
	}

	if ((thing->type == MT_SPRINGSHELL || thing->type == MT_YELLOWSHELL) && thing->health > 0
	 && (tmthing->player || (tmthing->flags & MF_PUSHABLE)) && tmthing->health > 0)
	{
		// Multiplying by -1 inherently flips "less than" and "greater than"
		fixed_t tmz     = ((thing->eflags & MFE_VERTICALFLIP) ? -(tmthing->z + tmthing->height) : tmthing->z);
		fixed_t tmznext = ((thing->eflags & MFE_VERTICALFLIP) ? -tmthing->momz : tmthing->momz) + tmz;
		fixed_t thzh    = ((thing->eflags & MFE_VERTICALFLIP) ? -thing->z : thing->z + thing->height);
		fixed_t sprarea = FixedMul(8*FRACUNIT, thing->scale) * P_MobjFlip(thing);

		if ((tmznext <= thzh && tmz > thzh) || (tmznext > thzh - sprarea && tmznext < thzh))
		{
			P_DoSpring(thing, tmthing);
			return CHECKTHING_COLLIDE;
		}
		else if (tmz > thzh - sprarea && tmz < thzh) // Don't damage people springing up / down
			return CHECKTHING_NOCOLLIDE;
	}

	// missiles can hit other things
	if (tmthing->flags & MF_MISSILE || tmthing->type == MT_SHELL)
	{
		// see if it went over / under
		if (tmthing->z > thing->z + thing->height)
			return CHECKTHING_NOCOLLIDE; // overhead
		if (tmthing->z + tmthing->height < thing->z)
			return CHECKTHING_NOCOLLIDE; // underneath

		if (tmthing->type != MT_SHELL && tmthing->target && tmthing->target->type == thing->type)
		{
			// Don't hit same species as originator.
			if (thing == tmthing->target)
				return CHECKTHING_IGNORE;

			if (thing->type != MT_PLAYER)
			{
				// Explode, but do no damage.
				// Let players missile other players.
				return CHECKTHING_DONE;
			}
		}

		// Special case for bounce rings so they don't get caught behind solid objects.
		if ((tmthing->type == MT_THROWNBOUNCE && tmthing->fuse > 8*TICRATE) && thing->flags & MF_SOLID)
			return CHECKTHING_IGNORE;

		// Missiles ignore Brak's helper.
		if (thing->type == MT_BLACKEGGMAN_HELPER)
			return CHECKTHING_IGNORE;

		// Hurting Brak
		if (tmthing->type == MT_BLACKEGGMAN_MISSILE
		    && thing->type == MT_BLACKEGGMAN && tmthing->target != thing)
		{
			// Not if Brak's already in pain
			if (!(thing->state >= &states[S_BLACKEGG_PAIN1] && thing->state <= &states[S_BLACKEGG_PAIN35]))
				P_SetMobjState(thing, thing->info->painstate);
			return CHECKTHING_DONE;
		}

		if (!(thing->flags & MF_SHOOTABLE) && !(thing->type == MT_EGGSHIELD))
		{
			// didn't do any damage
			if (thing->flags & MF_SOLID)
				return CHECKTHING_COLLIDE;
			else
				return CHECKTHING_NOCOLLIDE;
		}

		if (tmthing->flags & MF_MISSILE && thing->player && tmthing->target && tmthing->target->player
		&& thing->player->ctfteam == tmthing->target->player->ctfteam
		&& thing->player->powers[pw_carry] == CR_PLAYER && thing->tracer == tmthing->target)
			return CHECKTHING_IGNORE; // Don't give rings to your carry player by accident.

		if (thing->type == MT_EGGSHIELD)
		{
			angle_t angle = (R_PointToAngle2(thing->x, thing->y, tmthing->x - tmthing->momx, tmthing->y - tmthing->momy) - thing->angle) - ANGLE_90;

			if (angle < ANGLE_180) // hit shield from behind, shield is destroyed!
				P_KillMobj(thing, tmthing, tmthing, 0);
			return CHECKTHING_DONE;
		}

		// damage / explode
		if (tmthing->flags & MF_ENEMY) // An actual ENEMY! (Like the deton, for example)
			P_DamageMobj(thing, tmthing, tmthing, 1, 0);
		else if (tmthing->type == MT_BLACKEGGMAN_MISSILE && thing->player
			&& (thing->player->pflags & PF_JUMPED)
			&& !thing->player->powers[pw_flashing]
			&& !thing->player->powers[pw_ignorelatch]
			&& thing->tracer != tmthing
			&& tmthing->target != thing)
		{
			// Hop on the missile for a ride!
			thing->player->powers[pw_carry] = CR_GENERIC;
			thing->player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE);
			P_SetTarget(&thing->tracer, tmthing);
			P_SetTarget(&tmthing->target, thing); // Set owner to the player
			P_SetTarget(&tmthing->tracer, NULL); // Disable homing-ness
			tmthing->momz = 0;

			thing->angle = tmthing->angle;

			if (!demoplayback || P_ControlStyle(thing->player) == CS_LMAOGALOG)
				P_SetPlayerAngle(thing->player, thing->angle);

			return CHECKTHING_COLLIDE;
		}
		else if (tmthing->type == MT_BLACKEGGMAN_MISSILE && thing->player && ((thing->player->powers[pw_carry] == CR_GENERIC) || (thing->player->pflags & PF_JUMPED)))
		{
			// Ignore
		}
		else if (tmthing->type == MT_BLACKEGGMAN_GOOPFIRE)
		{
			P_UnsetThingPosition(tmthing);
			tmthing->x = thing->x;
			tmthing->y = thing->y;
			P_SetThingPosition(tmthing);
		}
		else if (!(tmthing->type == MT_SHELL && thing->player)) // player collision handled in touchspecial for shell
		{
			UINT8 damagetype = tmthing->info->mass;
			if (!damagetype && tmthing->flags & MF_FIRE) // BURN!
				damagetype = DMG_FIRE;
			P_DamageMobj(thing, tmthing, tmthing->target, 1, damagetype);
		}

		// Fireball touched an enemy
		// Don't bounce though, just despawn right there
		if ((tmthing->type == MT_FIREBALL) && (thing->flags & MF_ENEMY))
			P_KillMobj(tmthing, NULL, NULL, 0);

		// don't traverse any more
		if (tmthing->type == MT_SHELL)
			return CHECKTHING_COLLIDE;
		else
			return CHECKTHING_DONE;
	}

	if (thing->flags & MF_PUSHABLE && (tmthing->player || tmthing->flags & MF_PUSHABLE)
		&& tmthing->z + tmthing->height > thing->z && tmthing->z < thing->z + thing->height
		&& !(netgame && tmthing->player && tmthing->player->spectator)) // Push thing!
	{
		if (thing->flags2 & MF2_SLIDEPUSH) // Make it slide
		{
			if (tmthing->momy > 0 && tmthing->momy > FixedMul(4*FRACUNIT, thing->scale) && tmthing->momy > thing->momy)
			{
				thing->momy += FixedMul(PUSHACCEL, thing->scale);
				tmthing->momy -= FixedMul(PUSHACCEL, thing->scale);
			}
			else if (tmthing->momy < 0 && tmthing->momy < FixedMul(-4*FRACUNIT, thing->scale)
				&& tmthing->momy < thing->momy)
			{
				thing->momy -= FixedMul(PUSHACCEL, thing->scale);
				tmthing->momy += FixedMul(PUSHACCEL, thing->scale);
			}
			if (tmthing->momx > 0 && tmthing->momx > FixedMul(4*FRACUNIT, thing->scale)
				&& tmthing->momx > thing->momx)
			{
				thing->momx += FixedMul(PUSHACCEL, thing->scale);
				tmthing->momx -= FixedMul(PUSHACCEL, thing->scale);
			}
			else if (tmthing->momx < 0 && tmthing->momx < FixedMul(-4*FRACUNIT, thing->scale)
				&& tmthing->momx < thing->momx)
			{
				thing->momx -= FixedMul(PUSHACCEL, thing->scale);
				tmthing->momx += FixedMul(PUSHACCEL, thing->scale);
			}

			if (thing->momx > FixedMul(thing->info->speed, thing->scale))
				thing->momx = FixedMul(thing->info->speed, thing->scale);
			else if (thing->momx < -FixedMul(thing->info->speed, thing->scale))
				thing->momx = -FixedMul(thing->info->speed, thing->scale);
			if (thing->momy > FixedMul(thing->info->speed, thing->scale))
				thing->momy = FixedMul(thing->info->speed, thing->scale);
			else if (thing->momy < -FixedMul(thing->info->speed, thing->scale))
				thing->momy = -FixedMul(thing->info->speed, thing->scale);
		}
		else
		{
			if (tmthing->momx > FixedMul(4*FRACUNIT, thing->scale))
				tmthing->momx = FixedMul(4*FRACUNIT, thing->scale);
			else if (tmthing->momx < FixedMul(-4*FRACUNIT, thing->scale))
				tmthing->momx = FixedMul(-4*FRACUNIT, thing->scale);
			if (tmthing->momy > FixedMul(4*FRACUNIT, thing->scale))
				tmthing->momy = FixedMul(4*FRACUNIT, thing->scale);
			else if (tmthing->momy < FixedMul(-4*FRACUNIT, thing->scale))
				tmthing->momy = FixedMul(-4*FRACUNIT, thing->scale);

			thing->momx = tmthing->momx;
			thing->momy = tmthing->momy;
		}

		if (thing->type != MT_GARGOYLE || P_IsObjectOnGround(thing))
			S_StartSound(thing, thing->info->activesound);

		P_SetTarget(&thing->target, tmthing);
	}

	// NiGHTS lap logic
	if ((tmthing->type == MT_NIGHTSDRONE || thing->type == MT_NIGHTSDRONE)
	 && (tmthing->player || thing->player))
	{
		mobj_t *droneobj = (tmthing->type == MT_NIGHTSDRONE) ? tmthing : thing;
		player_t *pl = (droneobj == thing) ? tmthing->player : thing->player;

		// Must be NiGHTS, must wait about a second
		// must be flying in the SAME DIRECTION as the last time you came through.
		// not (your direction) xor (stored direction)
		// In other words, you can't u-turn and respawn rings near the drone.
		if ((pl->powers[pw_carry] == CR_NIGHTSMODE) && (INT32)leveltime > droneobj->extravalue2 && (
		   !(pl->flyangle > 90 &&   pl->flyangle < 270)
		^ (droneobj->extravalue1 > 90 && droneobj->extravalue1 < 270)
		))
		{
			pl->marelap++;
			pl->totalmarelap++;
			pl->lapbegunat = leveltime;
			pl->lapstartedtime = pl->nightstime;

			if (pl->bonustime)
			{
				pl->marebonuslap++;
				pl->totalmarebonuslap++;

				// Respawn rings and items
				P_ReloadRings();
			}

			P_RunNightsLapExecutors(pl->mo);
		}
		droneobj->extravalue1 = pl->flyangle;
		droneobj->extravalue2 = (INT32)leveltime + TICRATE;
	}

	// check for special pickup
	if (thing->flags & MF_SPECIAL)
	{
		P_TouchSpecialThing(thing, tmthing, true); // can remove thing
		return CHECKTHING_COLLIDE;
	}
	// check again for special pickup
	if (tmthing->flags & MF_SPECIAL)
	{
		P_TouchSpecialThing(tmthing, thing, true); // can remove thing
		return CHECKTHING_COLLIDE;
	}

	// Sprite Spikes!
	// Do not return because solidity code comes below.
	if (tmthing->type == MT_SPIKE && tmthing->flags & MF_SOLID && thing->player) // moving spike rams into player?!
	{
		if (tmthing->eflags & MFE_VERTICALFLIP)
		{
			if (thing->z + thing->height <= tmthing->z + FixedMul(FRACUNIT, tmthing->scale)
			&& thing->z + thing->height + thing->momz  >= tmthing->z + FixedMul(FRACUNIT, tmthing->scale) + tmthing->momz
			&& !(thing->player->charability == CA_BOUNCE && thing->player->panim == PA_ABILITY && thing->eflags & MFE_VERTICALFLIP))
				P_DamageMobj(thing, tmthing, tmthing, 1, DMG_SPIKE);
		}
		else if (thing->z >= tmthing->z + tmthing->height - FixedMul(FRACUNIT, tmthing->scale)
		&& thing->z + thing->momz <= tmthing->z + tmthing->height - FixedMul(FRACUNIT, tmthing->scale) + tmthing->momz
		&& !(thing->player->charability == CA_BOUNCE && thing->player->panim == PA_ABILITY && !(thing->eflags & MFE_VERTICALFLIP)))
			P_DamageMobj(thing, tmthing, tmthing, 1, DMG_SPIKE);
	}
	else if (thing->type == MT_SPIKE && thing->flags & MF_SOLID && tmthing->player) // unfortunate player falls into spike?!
	{
		if (thing->eflags & MFE_VERTICALFLIP)
		{
			if (tmthing->z + tmthing->height <= thing->z - FixedMul(FRACUNIT, thing->scale)
			&& tmthing->z + tmthing->height + tmthing->momz >= thing->z - FixedMul(FRACUNIT, thing->scale)
			&& !(tmthing->player->charability == CA_BOUNCE && tmthing->player->panim == PA_ABILITY && tmthing->eflags & MFE_VERTICALFLIP))
				P_DamageMobj(tmthing, thing, thing, 1, DMG_SPIKE);
		}
		else if (tmthing->z >= thing->z + thing->height + FixedMul(FRACUNIT, thing->scale)
		&& tmthing->z + tmthing->momz <= thing->z + thing->height + FixedMul(FRACUNIT, thing->scale)
		&& !(tmthing->player->charability == CA_BOUNCE && tmthing->player->panim == PA_ABILITY && !(tmthing->eflags & MFE_VERTICALFLIP)))
			P_DamageMobj(tmthing, thing, thing, 1, DMG_SPIKE);
	}

	if (tmthing->type == MT_WALLSPIKE && tmthing->flags & MF_SOLID && thing->player) // wall spike impales player
	{
		fixed_t bottomz, topz;
		bottomz = tmthing->z;
		topz = tmthing->z + tmthing->height;
		if (tmthing->eflags & MFE_VERTICALFLIP)
			bottomz -= FixedMul(FRACUNIT, tmthing->scale);
		else
			topz += FixedMul(FRACUNIT, tmthing->scale);

		if (thing->z + thing->height > bottomz // above bottom
		&&  thing->z < topz) // below top
		// don't check angle, the player was clearly in the way in this case
			P_DamageMobj(thing, tmthing, tmthing, 1, DMG_SPIKE);
	}
	else if (thing->type == MT_WALLSPIKE && thing->flags & MF_SOLID && tmthing->player)
	{
		fixed_t bottomz, topz;
		angle_t touchangle = R_PointToAngle2(thing->tracer->x, thing->tracer->y, tmthing->x, tmthing->y);

		if (P_PlayerInPain(tmthing->player) && (tmthing->momx || tmthing->momy))
		{
			angle_t playerangle = R_PointToAngle2(0, 0, tmthing->momx, tmthing->momy) - touchangle;
			if (playerangle > ANGLE_180)
				playerangle = InvAngle(playerangle);
			if (playerangle < ANGLE_90)
				return CHECKTHING_IGNORE; // Yes, this is intentionally outside the z-height check. No standing on spikes whilst moving away from them.
		}

		bottomz = thing->z;
		topz = thing->z + thing->height;

		if (thing->eflags & MFE_VERTICALFLIP)
			bottomz -= FixedMul(FRACUNIT, thing->scale);
		else
			topz += FixedMul(FRACUNIT, thing->scale);

		if (tmthing->z + tmthing->height > bottomz // above bottom
		&&  tmthing->z < topz // below top
		&& !P_MobjWasRemoved(thing->tracer)) // this probably wouldn't work if we didn't have a tracer
		{ // use base as a reference point to determine what angle you touched the spike at
			touchangle = thing->angle - touchangle;
			if (touchangle > ANGLE_180)
				touchangle = InvAngle(touchangle);
			if (touchangle <= ANGLE_22h) // if you touched it at this close an angle, you get poked!
				P_DamageMobj(tmthing, thing, thing, 1, DMG_SPIKE);
		}
	}

	if (thing->flags & MF_PUSHABLE)
	{
		if (tmthing->type == MT_FAN)
			P_DoFan(tmthing, thing);
	}

	if (tmthing->flags & MF_PUSHABLE)
	{
		if (thing->type == MT_FAN)
		{
			P_DoFan(thing, tmthing);
			return CHECKTHING_COLLIDE;
		}
		else if (thing->flags & MF_SPRING)
		{
			if ( thing->z <= tmthing->z + tmthing->height
			&& tmthing->z <= thing->z + thing->height)
				if (P_DoSpring(thing, tmthing))
					return CHECKTHING_DONE;
			return CHECKTHING_COLLIDE;
		}
	}

	// thanks to sal for solidenemies dot lua
	if (thing->flags & (MF_ENEMY|MF_BOSS) && tmthing->flags & (MF_ENEMY|MF_BOSS))
	{
		if ((thing->z + thing->height >= tmthing->z)
		&& (tmthing->z + tmthing->height >= thing->z))
			return CHECKTHING_DONE;
	}

	// Damage other players when invincible
	if (tmthing->player && thing->player
	// Make sure they aren't able to damage you ANYWHERE along the Z axis, you have to be TOUCHING the person.
		&& !(thing->z + thing->height < tmthing->z || thing->z > tmthing->z + tmthing->height))
	{
		if (G_RingSlingerGametype() && (!G_GametypeHasTeams() || tmthing->player->ctfteam != thing->player->ctfteam))
		{
			if ((tmthing->player->powers[pw_invulnerability] || tmthing->player->powers[pw_super] || (((tmthing->player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL) && (tmthing->player->pflags & PF_SHIELDABILITY)))
				&& !thing->player->powers[pw_super])
				P_DamageMobj(thing, tmthing, tmthing, 1, 0);
			else if ((thing->player->powers[pw_invulnerability] || thing->player->powers[pw_super] || (((thing->player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL) && (thing->player->pflags & PF_SHIELDABILITY)))
				&& !tmthing->player->powers[pw_super])
				P_DamageMobj(tmthing, thing, thing, 1, 0);
		}

		// If players are using touch tag, seekers damage hiders.
		if (G_TagGametype() && cv_touchtag.value &&
			((thing->player->pflags & PF_TAGIT) != (tmthing->player->pflags & PF_TAGIT)))
		{
			if ((tmthing->player->pflags & PF_TAGIT) && !(thing->player->pflags & PF_TAGIT))
				P_DamageMobj(thing, tmthing, tmthing, 1, 0);
			else if ((thing->player->pflags & PF_TAGIT) && !(tmthing->player->pflags & PF_TAGIT))
				P_DamageMobj(tmthing, thing, tmthing, 1, 0);
		}
	}

	// Force solid players in hide and seek to avoid corner stacking.
	if (cv_tailspickup.value && !(gametyperules & GTR_HIDEFROZEN))
	{
		if (tmthing->player && thing->player)
		{
			P_DoTailsCarry(thing->player, tmthing->player);
			return CHECKTHING_COLLIDE;
		}
	}
	else if (thing->player) {
		if (thing->player-players == consoleplayer && botingame)
			CV_SetValue(&cv_analog[1], true);
		if (thing->player->powers[pw_carry] == CR_PLAYER)
		{
			P_SetTarget(&thing->tracer, NULL);
			thing->player->powers[pw_carry] = CR_NONE;
		}
	}

	if (thing->player)
	{
		// Doesn't matter what gravity player's following! Just do your stuff in YOUR direction only
		if (tmthing->eflags & MFE_VERTICALFLIP
		&& (tmthing->z + tmthing->height + tmthing->momz < thing->z
		 || tmthing->z + tmthing->height + tmthing->momz >= thing->z + thing->height))
			;
		else if (!(tmthing->eflags & MFE_VERTICALFLIP)
		&& (tmthing->z + tmthing->momz > thing->z + thing->height
		 || tmthing->z + tmthing->momz <= thing->z))
			;
		else if  (P_IsObjectOnGround(thing)
			&& !P_IsObjectOnGround(tmthing) // Don't crush if the monitor is on the ground...
			&& (tmthing->flags & MF_SOLID))
		{
			if (tmthing->flags & (MF_MONITOR|MF_PUSHABLE))
			{
				// Objects kill you if it falls from above.
				if (thing != tmthing->target)
					P_DamageMobj(thing, tmthing, tmthing->target, 1, DMG_CRUSHED);

				tmthing->momz = -tmthing->momz/2; // Bounce, just for fun!
				// The tmthing->target allows the pusher of the object
				// to get the point if he topples it on an opponent.
			}
		}

		if (tmthing->type == MT_FAN)
			P_DoFan(tmthing, thing);
	}

	if (tmthing->player) // Is the moving/interacting object the player?
	{
		if (!tmthing->health)
			return CHECKTHING_IGNORE;

		if (thing->type == MT_FAN)
			P_DoFan(thing, tmthing);
		else if (thing->flags & MF_SPRING && tmthing->player->powers[pw_carry] != CR_MINECART)
		{
			if ( thing->z <= tmthing->z + tmthing->height
			&& tmthing->z <= thing->z + thing->height)
				if (P_DoSpring(thing, tmthing))
					return CHECKTHING_DONE;
			return CHECKTHING_COLLIDE;
		}
		// Monitor?
		else if (thing->flags & MF_MONITOR
		&& !((thing->type == MT_RING_REDBOX && tmthing->player->ctfteam != 1) || (thing->type == MT_RING_BLUEBOX && tmthing->player->ctfteam != 2))
		&& (!(thing->flags & MF_SOLID) || P_PlayerCanDamage(tmthing->player, thing)))
		{
			unsigned collide = CHECKTHING_NOCOLLIDE;

			if (thing->z - thing->scale <= tmthing->z + tmthing->height
			&& thing->z + thing->height + thing->scale >= tmthing->z)
			{
				player_t *player = tmthing->player;
				// 0 = none, 1 = elemental pierce, 2 = bubble bounce
				UINT8 elementalpierce = (((player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL || (player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP) && (player->pflags & PF_SHIELDABILITY)
				? (((player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL) ? 1 : 2)
				: 0);
				SINT8 flipval = P_MobjFlip(thing); // Save this value in case monitor gets removed.
				fixed_t *momz = &tmthing->momz; // tmthing gets changed by P_DamageMobj, so we need a new pointer?! X_x;;
				fixed_t *z = &tmthing->z; // aau.
				// Going down? Then bounce back up.
				if (P_DamageMobj(thing, tmthing, tmthing, 1, 0) // break the monitor
				&& (flipval*(*momz) < 0) // monitor is on the floor and you're going down, or on the ceiling and you're going up
				&& (elementalpierce != 1) && (!(player->powers[pw_strong] & STR_HEAVY))) // you're not piercing through the monitor...
				{
					fixed_t setmomz = -*momz; // Store this, momz get changed by P_DoJump within P_DoBubbleBounce

					if (elementalpierce == 2) // Reset bubblewrap, part 1
						P_DoBubbleBounce(player);
					*momz = setmomz; // Therefore, you should be thrust in the opposite direction, vertically.
					if (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)
						P_TwinSpinRejuvenate(player, player->thokitem);
					if (elementalpierce == 2) // Reset bubblewrap, part 2
					{
						boolean underwater = tmthing->eflags & MFE_UNDERWATER;

						if (underwater)
							*momz /= 2;
						*momz -= (*momz/(underwater ? 8 : 4)); // Cap the height!
					}
					collide = CHECKTHING_COLLIDE;
				}
				if (!(elementalpierce == 1 && thing->flags & MF_GRENADEBOUNCE)) // prevent gold monitor clipthrough.
				{
					if (player->pflags & PF_BOUNCING)
						P_DoAbilityBounce(player, false);
					collide = CHECKTHING_DONE;
				}
				else
					*z -= *momz; // to ensure proper collision.
			}

			return collide;
		}
	}

	// not solid not blocked
	unsigned collide = CHECKTHING_NOCOLLIDE;

	if ((tmthing->flags & MF_SPRING || tmthing->type == MT_SPIKE || tmthing->type == MT_WALLSPIKE) && (thing->player))
		; // springs and spikes should never be able to step up onto a player
	// z checking at last
	// Treat noclip things as non-solid!
	else if ((thing->flags & (MF_SOLID|MF_NOCLIP)) == MF_SOLID
		&& (tmthing->flags & (MF_SOLID|MF_NOCLIP)) == MF_SOLID)
	{
		fixed_t topz, tmtopz;

		if (tmthing->eflags & MFE_VERTICALFLIP)
		{
			// pass under
			tmtopz = tmthing->z;

			if (tmtopz > thing->z + thing->height)
			{
				if (thing->z + thing->height > tmfloorz)
				{
					tmfloorz = thing->z + thing->height;
					tmfloorrover = NULL;
					tmfloorslope = NULL;
				}
				return CHECKTHING_COLLIDE;
			}

			topz = thing->z - thing->scale; // FixedMul(FRACUNIT, thing->scale), but thing->scale == FRACUNIT in base scale anyways

			// block only when jumping not high enough,
			// (dont climb max. 24units while already in air)
			// since return CHECKTHING_DONE doesn't handle momentum properly,
			// we lie to P_TryMove() so it's always too high
			if (tmthing->player && tmthing->z + tmthing->height > topz
				&& tmthing->z + tmthing->height < tmthing->ceilingz)
			{
				if (thing->flags & MF_GRENADEBOUNCE && (thing->flags & MF_MONITOR || thing->info->flags & MF_MONITOR)) // Gold monitor hack...
					return CHECKTHING_DONE;

				tmfloorz = tmceilingz = topz; // block while in air
				tmceilingrover = NULL;
				tmceilingslope = NULL;
				tmfloorthing = thing; // needed for side collision

				collide = CHECKTHING_COLLIDE;
			}
			else if (topz < tmceilingz && tmthing->z <= thing->z+thing->height)
			{
				tmceilingz = topz;
				tmceilingrover = NULL;
				tmceilingslope = NULL;
				tmfloorthing = thing; // thing we may stand on

				collide = CHECKTHING_COLLIDE;
			}
		}
		else
		{
			// pass under
			tmtopz = tmthing->z + tmthing->height;

			if (tmtopz < thing->z)
			{
				if (thing->z < tmceilingz)
				{
					tmceilingz = thing->z;
					tmceilingrover = NULL;
					tmceilingslope = NULL;
				}
				return CHECKTHING_COLLIDE;
			}

			topz = thing->z + thing->height + thing->scale; // FixedMul(FRACUNIT, thing->scale), but thing->scale == FRACUNIT in base scale anyways

			// block only when jumping not high enough,
			// (dont climb max. 24units while already in air)
			// since return CHECKTHING_DONE doesn't handle momentum properly,
			// we lie to P_TryMove() so it's always too high
			if (tmthing->player && tmthing->z < topz
				&& tmthing->z > tmthing->floorz)
			{
				if (thing->flags & MF_GRENADEBOUNCE && (thing->flags & MF_MONITOR || thing->info->flags & MF_MONITOR)) // Gold monitor hack...
					return CHECKTHING_DONE;

				tmfloorz = tmceilingz = topz; // block while in air
				tmfloorrover = NULL;
				tmfloorslope = NULL;
				tmfloorthing = thing; // needed for side collision

				collide = CHECKTHING_COLLIDE;
			}
			else if (topz > tmfloorz && tmthing->z+tmthing->height >= thing->z)
			{
				tmfloorz = topz;
				tmfloorrover = NULL;
				tmfloorslope = NULL;
				tmfloorthing = thing; // thing we may stand on

				collide = CHECKTHING_COLLIDE;
			}
		}
	}

	return collide;
}

static boolean PIT_CheckThing(mobj_t *thing)
{
	return PIT_DoCheckThing(thing) != CHECKTHING_DONE;
}

// PIT_CheckCameraLine
// Adjusts tmfloorz and tmceilingz as lines are contacted - FOR CAMERA ONLY
static boolean PIT_CheckCameraLine(line_t *ld)
{
	if (ld->polyobj && !(ld->polyobj->flags & POF_SOLID))
		return true;

	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;

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

	// this line is out of the if so upper and lower textures can be hit by a splat
	blockingline = ld;
	if (!ld->backsector) // one sided line
	{
		if (P_PointOnLineSide(mapcampointer->x, mapcampointer->y, ld))
			return true; // don't hit the back side
		return false;
	}

	// set openrange, opentop, openbottom
	P_CameraLineOpening(ld);

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

	if (openbottom > tmfloorz)
	{
		tmfloorz = openbottom;
	}

	if (highceiling > tmdrpoffceilz)
		tmdrpoffceilz = highceiling;

	if (lowfloor < tmdropoffz)
		tmdropoffz = lowfloor;

	return true;
}

//
// PIT_CheckLine
// Adjusts tmfloorz and tmceilingz as lines are contacted
//
static boolean PIT_CheckLine(line_t *ld)
{
	if (ld->polyobj && !(ld->polyobj->flags & POF_SOLID))
		return true;

	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;

	if (tmthing->flags & MF_PAPERCOLLISION) // Caution! Turning whilst up against a wall will get you stuck. You probably shouldn't give the player this flag.
	{
		fixed_t cosradius, sinradius;
		cosradius = FixedMul(tmthing->radius, FINECOSINE(tmthing->angle>>ANGLETOFINESHIFT));
		sinradius = FixedMul(tmthing->radius, FINESINE(tmthing->angle>>ANGLETOFINESHIFT));
		if (P_PointOnLineSide(tmx - cosradius, tmy - sinradius, ld)
		== P_PointOnLineSide(tmx + cosradius, tmy + sinradius, ld))
			return true; // the line doesn't cross between collider's start or end
#ifdef PAPER_COLLISIONCORRECTION
		{
			fixed_t dist;
			vertex_t result;
			angle_t langle;
			P_ClosestPointOnLine(tmx, tmy, ld, &result);
			langle = R_PointToAngle2(ld->v1->x, ld->v1->y, ld->v2->x, ld->v2->y);
			langle += ANGLE_90*(P_PointOnLineSide(tmx, tmy, ld) ? -1 : 1);
			dist = abs(FixedMul(tmthing->radius, FINECOSINE((tmthing->angle - langle)>>ANGLETOFINESHIFT)));
			cosradius = FixedMul(dist, FINECOSINE(langle>>ANGLETOFINESHIFT));
			sinradius = FixedMul(dist, FINESINE(langle>>ANGLETOFINESHIFT));
			tmthing->flags |= MF_NOCLIP;
			P_MoveOrigin(tmthing, result.x + cosradius - tmthing->momx, result.y + sinradius - tmthing->momy, tmthing->z);
			tmthing->flags &= ~MF_NOCLIP;
		}
#endif
	}

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

	// this line is out of the if so upper and lower textures can be hit by a splat
	blockingline = ld;

	{
		UINT8 shouldCollide = LUA_HookMobjLineCollide(tmthing, blockingline); // checks hook for thing's type
		if (P_MobjWasRemoved(tmthing))
			return true; // one of them was removed???
		if (shouldCollide == 1)
			return false; // force collide
		else if (shouldCollide == 2)
			return true; // force no collide
	}

	if (!ld->backsector) // one sided line
	{
		if (P_PointOnLineSide(tmthing->x, tmthing->y, ld))
			return true; // don't hit the back side
		return false;
	}

	// missiles can cross uncrossable lines
	if (!(tmthing->flags & MF_MISSILE))
	{
		if (ld->flags & ML_IMPASSIBLE) // block objects from moving through this linedef.
			return false;
		if ((tmthing->flags & (MF_ENEMY|MF_BOSS)) && ld->flags & ML_BLOCKMONSTERS)
			return false; // block monsters only
	}

	// set openrange, opentop, openbottom
	P_LineOpening(ld, tmthing);

	// adjust floor / ceiling heights
	if (opentop < tmceilingz)
	{
		tmceilingz = opentop;
		ceilingline = ld;
		tmceilingrover = openceilingrover;
		tmceilingslope = opentopslope;
	}

	if (openbottom > tmfloorz)
	{
		tmfloorz = openbottom;
		tmfloorrover = openfloorrover;
		tmfloorslope = openbottomslope;
	}

	if (highceiling > tmdrpoffceilz)
		tmdrpoffceilz = highceiling;

	if (lowfloor < tmdropoffz)
		tmdropoffz = lowfloor;

	return true;
}

// =========================================================================
//                         MOVEMENT CLIPPING
// =========================================================================
boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
{
	INT32 xl, xh, yl, yh, bx, by;
	subsector_t *newsubsec;
	boolean blockval = true;

	ps_checkposition_calls.value.i++;

	I_Assert(thing != NULL);
#ifdef PARANOIA
	if (P_MobjWasRemoved(thing))
		I_Error("Previously-removed Thing of type %u crashes P_CheckPosition!", thing->type);
#endif

	P_SetTarget(&tmthing, thing);
	tmflags = thing->flags;

	tmx = x;
	tmy = y;

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

	newsubsec = R_PointInSubsector(x, y);
	ceilingline = blockingline = 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_GetFloorZ(thing, newsubsec->sector, x, y, NULL); //newsubsec->sector->floorheight;
	tmceilingz = P_GetCeilingZ(thing, newsubsec->sector, x, y, NULL); //newsubsec->sector->ceilingheight;
	tmfloorrover = NULL;
	tmceilingrover = NULL;
	tmfloorslope = newsubsec->sector->f_slope;
	tmceilingslope = newsubsec->sector->c_slope;

	// Check list of fake floors and see if tmfloorz/tmceilingz need to be altered.
	if (newsubsec->sector->ffloors)
	{
		ffloor_t *rover;
		fixed_t delta1, delta2;
		INT32 thingtop = thing->z + thing->height;

		for (rover = newsubsec->sector->ffloors; rover; rover = rover->next)
		{
			fixed_t topheight, bottomheight;

			if (!(rover->fofflags & FOF_EXISTS))
				continue;

			topheight = P_GetFOFTopZ(thing, newsubsec->sector, rover, x, y, NULL);
			bottomheight = P_GetFOFBottomZ(thing, newsubsec->sector, rover, x, y, NULL);

			if ((rover->fofflags & (FOF_SWIMMABLE|FOF_GOOWATER)) == (FOF_SWIMMABLE|FOF_GOOWATER) && !(thing->flags & MF_NOGRAVITY))
			{
				// If you're inside goowater and slowing down
				fixed_t sinklevel = FixedMul(thing->info->height/6, thing->scale);
				fixed_t minspeed = FixedMul(thing->info->height/9, thing->scale);
				if (thing->z < topheight && bottomheight < thingtop
				&& abs(thing->momz) < minspeed)
				{
					// Oh no! The object is stick in between the surface of the goo and sinklevel! help them out!
					if (!(thing->eflags & MFE_VERTICALFLIP) && thing->z > topheight - sinklevel
					&& thing->momz >= 0 && thing->momz < (minspeed>>2))
						thing->momz += minspeed>>2;
					else if (thing->eflags & MFE_VERTICALFLIP && thingtop < bottomheight + sinklevel
					&& thing->momz <= 0 && thing->momz > -(minspeed>>2))
						thing->momz -= minspeed>>2;

					// Land on the top or the bottom, depending on gravity flip.
					if (!(thing->eflags & MFE_VERTICALFLIP) && thing->z >= topheight - sinklevel && thing->momz <= 0)
					{
						if (tmfloorz < topheight - sinklevel) {
							tmfloorz = topheight - sinklevel;
							tmfloorrover = rover;
							tmfloorslope = *rover->t_slope;
						}
					}
					else if (thing->eflags & MFE_VERTICALFLIP && thingtop <= bottomheight + sinklevel && thing->momz >= 0)
					{
						if (tmceilingz > bottomheight + sinklevel) {
							tmceilingz = bottomheight + sinklevel;
							tmceilingrover = rover;
							tmceilingslope = *rover->b_slope;
						}
					}
				}
				continue;
			}

			if (thing->player && (P_CheckSolidLava(rover) || P_CanRunOnWater(thing->player, rover)))
				;
			else if (thing->type == MT_SKIM && (rover->fofflags & FOF_SWIMMABLE))
				;
			else if (!((rover->fofflags & FOF_BLOCKPLAYER && thing->player)
			    || (rover->fofflags & FOF_BLOCKOTHERS && !thing->player)
				|| rover->fofflags & FOF_QUICKSAND))
				continue;

			if (rover->fofflags & FOF_QUICKSAND)
			{
				if (!(thing->eflags & MFE_VERTICALFLIP) && thing->z < topheight && bottomheight < thingtop)
				{
					if (tmfloorz < thing->z) {
						tmfloorz = thing->z;
						tmfloorrover = rover;
						tmfloorslope = NULL;
					}
				}
				else if (thing->eflags & MFE_VERTICALFLIP && thing->z < topheight && bottomheight < thingtop)
				{
					if (tmceilingz > thingtop) {
						tmceilingz = thingtop;
						tmceilingrover = rover;
						tmceilingslope = NULL;
					}
				}
				// Quicksand blocks never change heights otherwise.
				continue;
			}

			delta1 = thing->z - (bottomheight
				+ ((topheight - bottomheight)/2));
			delta2 = thingtop - (bottomheight
				+ ((topheight - bottomheight)/2));

			if (topheight > tmfloorz && abs(delta1) < abs(delta2)
				&& !(rover->fofflags & FOF_REVERSEPLATFORM))
			{
				tmfloorz = tmdropoffz = topheight;
				tmfloorrover = rover;
				tmfloorslope = *rover->t_slope;
			}
			if (bottomheight < tmceilingz && abs(delta1) >= abs(delta2)
				&& !(rover->fofflags & FOF_PLATFORM)
				&& !(thing->type == MT_SKIM && (rover->fofflags & FOF_SWIMMABLE)))
			{
				tmceilingz = tmdrpoffceilz = bottomheight;
				tmceilingrover = rover;
				tmceilingslope = *rover->b_slope;
			}
		}
	}

	// The bounding box is extended by MAXRADIUS
	// because mobj_ts are grouped into mapblocks
	// based on their origin point, and can overlap
	// into adjacent blocks by up to MAXRADIUS units.

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

	BMBOUNDFIX(xl, xh, yl, yh);

	// Check polyobjects and see if tmfloorz/tmceilingz need to be altered
	{
		validcount++;

		for (by = yl; by <= yh; by++)
			for (bx = xl; bx <= xh; bx++)
			{
				INT32 offset;
				polymaplink_t *plink; // haleyjd 02/22/06

				if (bx < 0 || by < 0 || bx >= bmapwidth || by >= bmapheight)
					continue;

				offset = by*bmapwidth + bx;

				// haleyjd 02/22/06: consider polyobject lines
				plink = polyblocklinks[offset];

				while (plink)
				{
					polyobj_t *po = plink->po;

					if (po->validcount != validcount) // if polyobj hasn't been checked
					{
						sector_t *polysec;
						fixed_t delta1, delta2, thingtop;
						fixed_t polytop, polybottom;

						po->validcount = validcount;

						if (!P_BBoxInsidePolyobj(po, tmbbox)
							|| !(po->flags & POF_SOLID))
						{
							plink = (polymaplink_t *)(plink->link.next);
							continue;
						}

						// We're inside it! Yess...
						polysec = po->lines[0]->backsector;

						if (po->flags & POF_CLIPPLANES)
						{
							polytop = polysec->ceilingheight;
							polybottom = polysec->floorheight;
						}
						else
						{
							polytop = INT32_MAX;
							polybottom = INT32_MIN;
						}

						thingtop = thing->z + thing->height;
						delta1 = thing->z - (polybottom + ((polytop - polybottom)/2));
						delta2 = thingtop - (polybottom + ((polytop - polybottom)/2));

						if (polytop > tmfloorz && abs(delta1) < abs(delta2)) {
							tmfloorz = tmdropoffz = polytop;
							tmfloorslope = NULL;
							tmfloorrover = NULL;
						}

						if (polybottom < tmceilingz && abs(delta1) >= abs(delta2)) {
							tmceilingz = tmdrpoffceilz = polybottom;
							tmceilingslope = NULL;
							tmceilingrover = NULL;
						}
					}
					plink = (polymaplink_t *)(plink->link.next);
				}
			}
	}

	// tmfloorthing is set when tmfloorz comes from a thing's top
	tmfloorthing = NULL;
	tmhitthing = NULL;

	validcount++;

	if (tmflags & MF_NOCLIP)
		return true;

	// Check things first.
	if (!(thing->flags & MF_NOCLIPTHING))
	{
		for (bx = xl; bx <= xh; bx++)
			for (by = yl; by <= yh; by++)
			{
				if (!P_BlockThingsIterator(bx, by, PIT_CheckThing))
					blockval = false;
				else
					tmhitthing = tmfloorthing;
				if (P_MobjWasRemoved(tmthing))
					return false;
			}
	}

	validcount++;

	// check lines
	for (bx = xl; bx <= xh; bx++)
		for (by = yl; by <= yh; by++)
			if (!P_BlockLinesIterator(bx, by, PIT_CheckLine))
				blockval = false;

	return blockval;
}

static const fixed_t hoopblockdist = 16*FRACUNIT + 8*FRACUNIT;
static const fixed_t hoophalfheight = (56*FRACUNIT)/2;

// P_CheckPosition optimized for the MT_HOOPCOLLIDE object. This needs to be as fast as possible!
void P_CheckHoopPosition(mobj_t *hoopthing, fixed_t x, fixed_t y, fixed_t z, fixed_t radius)
{
	INT32 i;

	(void)radius; //unused
	for (i = 0; i < MAXPLAYERS; i++)
	{
		if (!playeringame[i] || !players[i].mo || players[i].spectator)
			continue;

		if (abs(players[i].mo->x - x) >= hoopblockdist ||
			abs(players[i].mo->y - y) >= hoopblockdist ||
			abs((players[i].mo->z+hoophalfheight) - z) >= hoopblockdist)
			continue; // didn't hit it

		// can remove thing
		P_TouchSpecialThing(hoopthing, players[i].mo, false);
		break;
	}

	return;
}

//
// P_CheckCameraPosition
//
boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam)
{
	INT32 xl, xh, yl, yh, bx, by;
	subsector_t *newsubsec;

	tmx = x;
	tmy = y;

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

	newsubsec = R_PointInSubsector(x, y);
	ceilingline = blockingline = NULL;

	mapcampointer = thiscam;

	if (newsubsec->sector->flags & MSF_NOCLIPCAMERA)
	{ // Camera noclip on entire sector.
		tmfloorz = tmdropoffz = thiscam->z;
		tmceilingz = tmdrpoffceilz = thiscam->z + thiscam->height;
		return true;
	}

	// 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_CameraGetFloorZ(thiscam, newsubsec->sector, x, y, NULL);

	tmceilingz = P_CameraGetCeilingZ(thiscam, newsubsec->sector, x, y, NULL);

	// Cameras use the heightsec's heights rather then the actual sector heights.
	// If you can see through it, why not move the camera through it too?
	if (newsubsec->sector->heightsec >= 0)
	{
		tmfloorz = tmdropoffz = sectors[newsubsec->sector->heightsec].floorheight;
		tmceilingz = tmdrpoffceilz = sectors[newsubsec->sector->heightsec].ceilingheight;
	}

	// Use preset camera clipping heights if set with Sector Special Parameters whose control sector has Camera Intangible special -Red
	if (newsubsec->sector->camsec >= 0)
	{
		tmfloorz = tmdropoffz = sectors[newsubsec->sector->camsec].floorheight;
		tmceilingz = tmdrpoffceilz = sectors[newsubsec->sector->camsec].ceilingheight;
	}

	// Check list of fake floors and see if tmfloorz/tmceilingz need to be altered.
	if (newsubsec->sector->ffloors)
	{
		ffloor_t *rover;
		fixed_t delta1, delta2;
		INT32 thingtop = thiscam->z + thiscam->height;

		for (rover = newsubsec->sector->ffloors; rover; rover = rover->next)
		{
			fixed_t topheight, bottomheight;
			if (!(rover->fofflags & FOF_BLOCKOTHERS) || !(rover->fofflags & FOF_EXISTS) || !(rover->fofflags & FOF_RENDERALL) || (rover->master->frontsector->flags & MSF_NOCLIPCAMERA))
				continue;

			topheight = P_CameraGetFOFTopZ(thiscam, newsubsec->sector, rover, x, y, NULL);
			bottomheight = P_CameraGetFOFBottomZ(thiscam, newsubsec->sector, rover, x, y, NULL);

			delta1 = thiscam->z - (bottomheight
				+ ((topheight - bottomheight)/2));
			delta2 = thingtop - (bottomheight
				+ ((topheight - bottomheight)/2));
			if (topheight > tmfloorz && abs(delta1) < abs(delta2))
			{
				tmfloorz = tmdropoffz = topheight;
			}
			if (bottomheight < tmceilingz && abs(delta1) >= abs(delta2))
			{
				tmceilingz = tmdrpoffceilz = bottomheight;
			}
		}
	}

	// The bounding box is extended by MAXRADIUS
	// because mobj_ts are grouped into mapblocks
	// based on their origin point, and can overlap
	// into adjacent blocks by up to MAXRADIUS units.

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

	BMBOUNDFIX(xl, xh, yl, yh);

	// Check polyobjects and see if tmfloorz/tmceilingz need to be altered
	{
		validcount++;

		for (by = yl; by <= yh; by++)
			for (bx = xl; bx <= xh; bx++)
			{
				INT32 offset;
				polymaplink_t *plink; // haleyjd 02/22/06

				if (bx < 0 || by < 0 || bx >= bmapwidth || by >= bmapheight)
					continue;

				offset = by*bmapwidth + bx;

				// haleyjd 02/22/06: consider polyobject lines
				plink = polyblocklinks[offset];

				while (plink)
				{
					polyobj_t *po = plink->po;

					if (po->validcount != validcount) // if polyobj hasn't been checked
					{
						sector_t *polysec;
						fixed_t delta1, delta2, thingtop;
						fixed_t polytop, polybottom;

						po->validcount = validcount;

						if (!P_PointInsidePolyobj(po, x, y) || !(po->flags & POF_SOLID))
						{
							plink = (polymaplink_t *)(plink->link.next);
							continue;
						}

						// We're inside it! Yess...
						polysec = po->lines[0]->backsector;

						if (polysec->flags & MSF_NOCLIPCAMERA)
						{ // Camera noclip polyobj.
							plink = (polymaplink_t *)(plink->link.next);
							continue;
						}

						if (po->flags & POF_CLIPPLANES)
						{
							polytop = polysec->ceilingheight;
							polybottom = polysec->floorheight;
						}
						else
						{
							polytop = INT32_MAX;
							polybottom = INT32_MIN;
						}

						thingtop = thiscam->z + thiscam->height;
						delta1 = thiscam->z - (polybottom + ((polytop - polybottom)/2));
						delta2 = thingtop - (polybottom + ((polytop - polybottom)/2));

						if (polytop > tmfloorz && abs(delta1) < abs(delta2))
							tmfloorz = tmdropoffz = polytop;

						if (polybottom < tmceilingz && abs(delta1) >= abs(delta2))
							tmceilingz = tmdrpoffceilz = polybottom;
					}
					plink = (polymaplink_t *)(plink->link.next);
				}
			}
	}

	// check lines
	for (bx = xl; bx <= xh; bx++)
		for (by = yl; by <= yh; by++)
			if (!P_BlockLinesIterator(bx, by, PIT_CheckCameraLine))
				return false;

	return true;
}

// The highest the camera will "step up" onto another floor.
#define MAXCAMERASTEPMOVE MAXSTEPMOVE

//
// P_TryCameraMove
//
// Attempt to move the camera to a new position
//
// Return true if the move succeeded and no sliding should be done.
//
boolean P_TryCameraMove(fixed_t x, fixed_t y, camera_t *thiscam)
{
	subsector_t *s = R_PointInSubsector(x, y);
	boolean itsatwodlevel = false;

	floatok = false;

	if (twodlevel
		|| (thiscam == &camera && players[displayplayer].mo && (players[displayplayer].mo->flags2 & MF2_TWOD))
		|| (thiscam == &camera2 && players[secondarydisplayplayer].mo && (players[secondarydisplayplayer].mo->flags2 & MF2_TWOD)))
		itsatwodlevel = true;

	if (!itsatwodlevel && players[displayplayer].mo)
	{
		fixed_t tryx = thiscam->x;
		fixed_t tryy = thiscam->y;

		if ((thiscam == &camera && (players[displayplayer].pflags & PF_NOCLIP || players[displayplayer].powers[pw_carry] == CR_NIGHTSMODE))
		|| (thiscam == &camera2 && (players[secondarydisplayplayer].pflags & PF_NOCLIP || players[secondarydisplayplayer].powers[pw_carry] == CR_NIGHTSMODE)))
		{ // Noclipping player camera noclips too!!
			floatok = true;
			thiscam->floorz = thiscam->z;
			thiscam->ceilingz = thiscam->z + thiscam->height;
			thiscam->x = x;
			thiscam->y = y;
			thiscam->subsector = s;
			return true;
		}

		do {
			if (x-tryx > MAXRADIUS)
				tryx += MAXRADIUS;
			else if (x-tryx < -MAXRADIUS)
				tryx -= MAXRADIUS;
			else
				tryx = x;
			if (y-tryy > MAXRADIUS)
				tryy += MAXRADIUS;
			else if (y-tryy < -MAXRADIUS)
				tryy -= MAXRADIUS;
			else
				tryy = y;

			if (!P_CheckCameraPosition(tryx, tryy, thiscam))
				return false; // solid wall or thing

			if (tmceilingz - tmfloorz < thiscam->height)
				return false; // doesn't fit

			floatok = true;

			if (tmceilingz - thiscam->z < thiscam->height)
			{
				if (s == thiscam->subsector && tmceilingz >= thiscam->z)
				{
					floatok = true;
					thiscam->floorz = tmfloorz;
					thiscam->ceilingz = tmfloorz + thiscam->height;
					thiscam->x = x;
					thiscam->y = y;
					thiscam->subsector = s;
					return true;
				}
				else
					return false; // mobj must lower itself to fit
			}

			if ((tmfloorz - thiscam->z > MAXCAMERASTEPMOVE))
				return false; // too big a step up
		} while(tryx != x || tryy != y);
	}
	else
	{
		tmfloorz = P_CameraGetFloorZ(thiscam, thiscam->subsector->sector, x, y, NULL);
		tmceilingz = P_CameraGetCeilingZ(thiscam, thiscam->subsector->sector, x, y, NULL);
	}

	// the move is ok,
	// so link the thing into its new position

	thiscam->floorz = tmfloorz;
	thiscam->ceilingz = tmceilingz;
	thiscam->x = x;
	thiscam->y = y;
	thiscam->subsector = s;

	return true;
}

//
// PIT_PushableMoved
//
// Move things standing on top
// of pushable things being pushed.
//
static mobj_t *stand;
static fixed_t standx, standy;

boolean PIT_PushableMoved(mobj_t *thing)
{
	fixed_t blockdist;

	if (!(thing->flags & MF_SOLID)
		|| (thing->flags & MF_NOGRAVITY))
		return true; // Don't move something non-solid!

	// Only pushables are supported... in 2.0. Now players can be moved too!
	if (!(thing->flags & MF_PUSHABLE || thing->player))
		return true;

	if (thing == stand)
		return true;

	blockdist = stand->radius + thing->radius;

	if (abs(thing->x - stand->x) >= blockdist || abs(thing->y - stand->y) >= blockdist)
		return true; // didn't hit it

	if ((!(stand->eflags & MFE_VERTICALFLIP) && thing->z != stand->z + stand->height + FixedMul(FRACUNIT, stand->scale))
	|| ((stand->eflags & MFE_VERTICALFLIP) && thing->z + thing->height != stand->z - FixedMul(FRACUNIT, stand->scale)))
		return true; // Not standing on top

	if (!stand->momx && !stand->momy)
		return true;

	// Move this guy!
	if (thing->player)
	{
		// Monster Iestyn - 29/11/13
		// Ridiculous amount of newly declared stuff so players can't get stuck in walls AND so gargoyles don't break themselves at the same time either
		// These are all non-static map variables that are changed for each and every single mobj
		// See, changing player's momx/y would possibly trigger stuff as if the player were running somehow, so this must be done to keep the player standing
		// All this so players can ride gargoyles!
		boolean oldfltok = floatok;
		fixed_t oldflrz = tmfloorz;
		fixed_t oldceilz = tmceilingz;
		mobj_t *oldflrthing = tmfloorthing;
		mobj_t *oldthing = tmthing;
		line_t *oldceilline = ceilingline;
		line_t *oldblockline = blockingline;
		ffloor_t *oldflrrover = tmfloorrover;
		ffloor_t *oldceilrover = tmceilingrover;
		pslope_t *oldfslope = tmfloorslope;
		pslope_t *oldcslope = tmceilingslope;

		// Move the player
		P_TryMove(thing, thing->x+stand->momx, thing->y+stand->momy, true);

		// Now restore EVERYTHING so the gargoyle doesn't keep the player's tmstuff and break
		floatok = oldfltok;
		tmfloorz = oldflrz;
		tmceilingz = oldceilz;
		tmfloorthing = oldflrthing;
		P_SetTarget(&tmthing, oldthing);
		ceilingline = oldceilline;
		blockingline = oldblockline;
		tmfloorrover = oldflrrover;
		tmceilingrover = oldceilrover;
		tmfloorslope = oldfslope;
		tmceilingslope = oldcslope;
		thing->momz = stand->momz;
	}
	else
	{
		thing->momx = stand->momx;
		thing->momy = stand->momy;
		thing->momz = stand->momz;
	}
	return true;
}

static boolean
increment_move
(		mobj_t * thing,
		fixed_t x,
		fixed_t y,
		boolean allowdropoff)
{
	fixed_t tryx = thing->x;
	fixed_t tryy = thing->y;
	fixed_t radius = thing->radius;
	fixed_t thingtop;
	floatok = false;

	// This makes sure that there are no freezes from computing extremely small movements.
	// Originally was MAXRADIUS/2, but that can cause some bad inconsistencies for small players.
	radius = max(radius, thing->scale);

	// And we also have to prevent Big Large (tm) movements, as those can skip too far
	// across slopes and cause us to fail step up checks on them when we otherwise shouldn't.
	radius = min(radius, 16 * thing->scale);

	// (This whole "step" system is flawed; it was OK before, but the addition of slopes has
	// exposed the problems with doing it like this. The right thing to do would be to use
	// raycasting for physics to fix colliding in weird order, double-checking collisions,
	// randomly colliding with slopes instead of going up them, etc. I don't feel like porting
	// that from RR, as its both a huge sweeping change and still incomplete at the time of
	// writing. Clamping radius to make our steps more precise will work just fine as long
	// as you keep all of your crazy intentions to poke any of the other deep-rooted movement
	// code to yourself. -- Sal 6/5/2023)

	do {
		if (thing->flags & MF_NOCLIP) {
			tryx = x;
			tryy = y;
		} else {
			if (x-tryx > radius)
				tryx += radius;
			else if (x-tryx < -radius)
				tryx -= radius;
			else
				tryx = x;
			if (y-tryy > radius)
				tryy += radius;
			else if (y-tryy < -radius)
				tryy -= radius;
			else
				tryy = y;
		}

		if (!P_CheckPosition(thing, tryx, tryy) || P_MobjWasRemoved(thing))
			return false; // solid wall or thing

		if (!(thing->flags & MF_NOCLIP))
		{
			//All things are affected by their scale.
			fixed_t maxstep = FixedMul(MAXSTEPMOVE, thing->scale);

			if (thing->player)
			{
				// If using SSF_DOUBLESTEPUP, double the maxstep.
				if (P_PlayerTouchingSectorSpecialFlag(thing->player, SSF_DOUBLESTEPUP)
				|| (R_PointInSubsector(x, y)->sector->specialflags & SSF_DOUBLESTEPUP))
					maxstep <<= 1;

				// If using SSF_NOSTEPDOWN, no maxstep.
				if (P_PlayerTouchingSectorSpecialFlag(thing->player, SSF_NOSTEPDOWN)
				|| (R_PointInSubsector(x, y)->sector->specialflags & SSF_NOSTEPDOWN))
					maxstep = 0;

				// Don't 'step up' while springing,
				// Only step up "if needed".
				if (thing->player->panim == PA_SPRING
				&& P_MobjFlip(thing)*thing->momz > FixedMul(FRACUNIT, thing->scale))
					maxstep = 0;
			}
			else if (thing->flags & MF_PUSHABLE)
			{
				// If using SSF_DOUBLESTEPUP, double the maxstep.
				if (R_PointInSubsector(x, y)->sector->specialflags & SSF_DOUBLESTEPUP)
					maxstep <<= 1;

				// If using SSF_NOSTEPDOWN, no maxstep.
				if (R_PointInSubsector(x, y)->sector->specialflags & SSF_NOSTEPDOWN)
					maxstep = 0;
			}

			if (thing->type == MT_SKIM)
				maxstep = 0;

			if (tmceilingz - tmfloorz < thing->height
				|| (thing->player
					&& tmceilingz - tmfloorz < P_GetPlayerHeight(thing->player)
					&& !P_PlayerCanEnterSpinGaps(thing->player)))
			{
				if (tmfloorthing)
					tmhitthing = tmfloorthing;
				return false; // doesn't fit
			}

			floatok = true;

			thingtop = thing->z + thing->height;

			// Step up
			if (thing->z < tmfloorz)
			{
				if (maxstep > 0 && tmfloorz - thing->z <= maxstep)
				{
					thing->z = thing->floorz = tmfloorz;
					thing->floorrover = tmfloorrover;
					thing->eflags |= MFE_JUSTSTEPPEDDOWN;
				}
				else
				{
					return false; // mobj must raise itself to fit
				}
			}
			else if (tmceilingz < thingtop)
			{
				if (maxstep > 0 && thingtop - tmceilingz <= maxstep)
				{
					thing->z = ( thing->ceilingz = tmceilingz ) - thing->height;
					thing->ceilingrover = tmceilingrover;
					thing->eflags |= MFE_JUSTSTEPPEDDOWN;
				}
				else
				{
					return false; // mobj must lower itself to fit
				}
			}
			else if (maxstep > 0) // Step down
			{
				// If the floor difference is MAXSTEPMOVE or less, and the sector isn't Section1:14, ALWAYS
				// step down! Formerly required a Section1:13 sector for the full MAXSTEPMOVE, but no more.

				if (thingtop == thing->ceilingz && tmceilingz > thingtop && tmceilingz - thingtop <= maxstep)
				{
					thing->z = (thing->ceilingz = tmceilingz) - thing->height;
					thing->ceilingrover = tmceilingrover;
					thing->eflags |= MFE_JUSTSTEPPEDDOWN;
				}
				else if (thing->z == thing->floorz && tmfloorz < thing->z && thing->z - tmfloorz <= maxstep)
				{
					thing->z = thing->floorz = tmfloorz;
					thing->floorrover = tmfloorrover;
					thing->eflags |= MFE_JUSTSTEPPEDDOWN;
				}
			}

			if (!allowdropoff && !(thing->flags & MF_FLOAT) && thing->type != MT_SKIM && !tmfloorthing)
			{
				if (thing->eflags & MFE_VERTICALFLIP)
				{
					if (tmdrpoffceilz - tmceilingz > maxstep)
						return false;
				}
				else if (tmfloorz - tmdropoffz > maxstep)
					return false; // don't stand over a dropoff
			}
		}
	} while (tryx != x || tryy != y);

	return true;
}

//
// P_CheckMove
// Check if a P_TryMove would be successful.
//
boolean P_CheckMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
{
	boolean moveok;
	mobj_t *hack = P_SpawnMobjFromMobj(thing, 0, 0, 0, MT_RAY);
	if (P_MobjWasRemoved(hack))
		return false;

	hack->radius = thing->radius;
	hack->height = thing->height;

	moveok = increment_move(hack, x, y, allowdropoff);
	P_RemoveMobj(hack);

	return moveok;
}

//
// P_TryMove
// Attempt to move to a new position.
//
boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
{
	fixed_t startingonground = P_IsObjectOnGround(thing);

	// The move is ok!
	if (!increment_move(thing, x, y, allowdropoff))
		return false;

	// If it's a pushable object, check if anything is
	// standing on top and move it, too.
	if (thing->flags & MF_PUSHABLE)
	{
		INT32 xl, xh, yl, yh;

		yh = (unsigned)(thing->y + MAXRADIUS - bmaporgy)>>MAPBLOCKSHIFT;
		yl = (unsigned)(thing->y - MAXRADIUS - bmaporgy)>>MAPBLOCKSHIFT;
		xh = (unsigned)(thing->x + MAXRADIUS - bmaporgx)>>MAPBLOCKSHIFT;
		xl = (unsigned)(thing->x - MAXRADIUS - bmaporgx)>>MAPBLOCKSHIFT;

		BMBOUNDFIX(xl, xh, yl, yh);

		stand = thing;
		standx = x;
		standy = y;

		P_DoBlockThingsIterate(xl, yl, xh, yh, PIT_PushableMoved);
	}

	// Link the thing into its new position
	P_UnsetThingPosition(thing);

	thing->floorz = tmfloorz;
	thing->ceilingz = tmceilingz;
	thing->floorrover = tmfloorrover;
	thing->ceilingrover = tmceilingrover;

	if (!(thing->flags & MF_NOCLIPHEIGHT))
	{
		// Assign thing's standingslope if needed
		if (thing->z <= tmfloorz && !(thing->eflags & MFE_VERTICALFLIP)) {
			if (!startingonground && tmfloorslope)
				P_HandleSlopeLanding(thing, tmfloorslope);

			if (thing->momz <= 0)
			{
				thing->standingslope = tmfloorslope;
				P_SetPitchRollFromSlope(thing, thing->standingslope);

				if (thing->momz == 0 && thing->player && !startingonground)
					P_PlayerHitFloor(thing->player, true);
			}
		}
		else if (thing->z+thing->height >= tmceilingz && (thing->eflags & MFE_VERTICALFLIP)) {
			if (!startingonground && tmceilingslope)
				P_HandleSlopeLanding(thing, tmceilingslope);

			if (thing->momz >= 0)
			{
				thing->standingslope = tmceilingslope;
				P_SetPitchRollFromSlope(thing, thing->standingslope);

				if (thing->momz == 0 && thing->player && !startingonground)
					P_PlayerHitFloor(thing->player, true);
			}
		}
	}
	else // don't set standingslope if you're not going to clip against it
		thing->standingslope = NULL;

	thing->x = x;
	thing->y = y;

	if (tmfloorthing)
		thing->eflags &= ~MFE_ONGROUND; // not on real floor
	else
		thing->eflags |= MFE_ONGROUND;

	P_SetThingPosition(thing);
	return true;
}

boolean P_SceneryTryMove(mobj_t *thing, fixed_t x, fixed_t y)
{
	fixed_t tryx, tryy;
	I_Assert(!P_MobjWasRemoved(thing));

	tryx = thing->x;
	tryy = thing->y;
	do {
		if (x-tryx > MAXRADIUS)
			tryx += MAXRADIUS;
		else if (x-tryx < -MAXRADIUS)
			tryx -= MAXRADIUS;
		else
			tryx = x;
		if (y-tryy > MAXRADIUS)
			tryy += MAXRADIUS;
		else if (y-tryy < -MAXRADIUS)
			tryy -= MAXRADIUS;
		else
			tryy = y;

		if (!P_CheckPosition(thing, tryx, tryy) || P_MobjWasRemoved(thing))
			return false; // solid wall or thing

		if (!(thing->flags & MF_NOCLIP))
		{
			const fixed_t maxstep = MAXSTEPMOVE;

			if (tmceilingz - tmfloorz < thing->height)
				return false; // doesn't fit

			if (tmceilingz - thing->z < thing->height)
				return false; // mobj must lower itself to fit

			if (tmfloorz - thing->z > maxstep)
				return false; // too big a step up
		}
	} while(tryx != x || tryy != y);

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

	thing->floorz = tmfloorz;
	thing->ceilingz = tmceilingz;
	thing->floorrover = tmfloorrover;
	thing->ceilingrover = tmceilingrover;
	thing->x = x;
	thing->y = y;

	if (tmfloorthing)
		thing->eflags &= ~MFE_ONGROUND; // not on real floor
	else
		thing->eflags |= MFE_ONGROUND;

	P_SetThingPosition(thing);
	return true;
}

//
// 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.
//
static boolean P_ThingHeightClip(mobj_t *thing)
{
	boolean floormoved;
	fixed_t oldfloorz = thing->floorz, oldz = thing->z;
	ffloor_t *oldfloorrover = thing->floorrover;
	ffloor_t *oldceilingrover = thing->ceilingrover;
	boolean onfloor = P_IsObjectOnGround(thing);//(thing->z <= thing->floorz);
	ffloor_t *rover = NULL;
	boolean bouncing;
	boolean hitfloor = false;

	if (thing->flags & MF_NOCLIPHEIGHT)
		return true;

	P_CheckPosition(thing, thing->x, thing->y);

	if (P_MobjWasRemoved(thing))
		return true;

	floormoved = (thing->eflags & MFE_VERTICALFLIP && tmceilingz != thing->ceilingz)
		|| (!(thing->eflags & MFE_VERTICALFLIP) && tmfloorz != thing->floorz);

	thing->floorz = tmfloorz;
	thing->ceilingz = tmceilingz;
	thing->floorrover = tmfloorrover;
	thing->ceilingrover = tmceilingrover;

	// Ugly hack?!?! As long as just ceilingz is the lowest,
	// you'll still get crushed, right?
	if (tmfloorz > oldfloorz+thing->height)
		return true;

	bouncing = thing->player && thing->state-states == S_PLAY_BOUNCE_LANDING && P_IsObjectOnGround(thing);

	if ((onfloor || bouncing) && !(thing->flags & MF_NOGRAVITY) && floormoved)
	{
		rover = (thing->eflags & MFE_VERTICALFLIP) ? oldceilingrover : oldfloorrover;

		// Match the Thing's old floorz to an FOF and check for FOF_EXISTS
		// If ~FOF_EXISTS, don't set mobj Z.
		if (!rover || ((rover->fofflags & FOF_EXISTS) && (rover->fofflags & FOF_SOLID)))
		{
			hitfloor = bouncing;
			if (!(thing->player) || !(thing->player->pflags & PF_JUMPED || bouncing))
			{
				if (thing->eflags & MFE_VERTICALFLIP)
					thing->pmomz = thing->ceilingz - (thing->z + thing->height);
				else
					thing->pmomz = thing->floorz - thing->z;
				thing->eflags |= MFE_APPLYPMOMZ;
			}

			if (thing->eflags & MFE_VERTICALFLIP)
				thing->z = thing->ceilingz - thing->height;
			else
				thing->z = thing->floorz;
		}
	}
	else if (!tmfloorthing)
	{
		// don't adjust a floating monster unless forced to
		if (thing->eflags & MFE_VERTICALFLIP)
		{
			if (!onfloor && thing->z < tmfloorz)
				thing->z = thing->floorz;
		}
		else if (!onfloor && thing->z + thing->height > tmceilingz)
			thing->z = thing->ceilingz - thing->height;
	}

	if ((P_MobjFlip(thing)*(thing->z - oldz) > 0 || hitfloor) && thing->player)
		P_PlayerHitFloor(thing->player, !onfloor);

	// debug: be sure it falls to the floor
	thing->eflags &= ~MFE_ONGROUND;

	if (thing->ceilingz - thing->floorz < thing->height && thing->z >= thing->floorz)
		// BP: i know that this code cause many trouble but this also fixes
		// a lot of problems, mainly this is implementation of the stepping
		// for mobj (walk on solid corpse without jumping or fake 3d bridge)
		// problem is imp into imp at map01 and monster going at top of others
		return false;

	return true;
}

//
// SLIDE MOVE
// Allows the player to slide along any angled walls.
//
static fixed_t bestslidefrac, secondslidefrac;
static line_t *bestslideline;
static line_t *secondslideline;
static mobj_t *slidemo;
static fixed_t tmxmove, tmymove;

//
// P_HitCameraSlideLine
//
static void P_HitCameraSlideLine(line_t *ld, camera_t *thiscam)
{
	INT32 side;
	angle_t lineangle, moveangle, deltaangle;
	fixed_t movelen, newlen;

	if (ld->slopetype == ST_HORIZONTAL)
	{
		tmymove = 0;
		return;
	}

	if (ld->slopetype == ST_VERTICAL)
	{
		tmxmove = 0;
		return;
	}

	side = P_PointOnLineSide(thiscam->x, thiscam->y, ld);
	lineangle = ld->angle;

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

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

	if (deltaangle > ANGLE_180)
		deltaangle += ANGLE_180;

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

	movelen = P_AproxDistance(tmxmove, tmymove);
	newlen = FixedMul(movelen, FINECOSINE(deltaangle));

	tmxmove = FixedMul(newlen, FINECOSINE(lineangle));
	tmymove = FixedMul(newlen, FINESINE(lineangle));
}

//
// P_HitSlideLine
// Adjusts the xmove / ymove
// so that the next move will slide along the wall.
//
static void P_HitSlideLine(line_t *ld)
{
	INT32 side;
	angle_t lineangle, moveangle, deltaangle;
	fixed_t movelen, newlen;

	if (ld->slopetype == ST_HORIZONTAL)
	{
		tmymove = 0;
		return;
	}

	if (ld->slopetype == ST_VERTICAL)
	{
		tmxmove = 0;
		return;
	}

	side = P_PointOnLineSide(slidemo->x, slidemo->y, ld);

	lineangle = ld->angle;

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

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

	if (deltaangle > ANGLE_180)
		deltaangle += ANGLE_180;

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

	movelen = R_PointToDist2(0, 0, tmxmove, tmymove);
	newlen = FixedMul(movelen, FINECOSINE(deltaangle));

	tmxmove = FixedMul(newlen, FINECOSINE(lineangle));
	tmymove = FixedMul(newlen, FINESINE(lineangle));
}

//
// P_HitBounceLine
//
// Adjusts the xmove / ymove so that the next move will bounce off the wall.
//
static void P_HitBounceLine(line_t *ld)
{
	angle_t lineangle, moveangle, deltaangle;
	fixed_t movelen;

	if (ld->slopetype == ST_HORIZONTAL)
	{
		tmymove = -tmymove;
		return;
	}

	if (ld->slopetype == ST_VERTICAL)
	{
		tmxmove = -tmxmove;
		return;
	}

	lineangle = ld->angle;

	if (lineangle >= ANGLE_180)
		lineangle -= ANGLE_180;

	moveangle = R_PointToAngle2(0, 0, tmxmove, tmymove);
	deltaangle = moveangle + 2*(lineangle - moveangle);

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

	movelen = P_AproxDistance(tmxmove, tmymove);

	tmxmove = FixedMul(movelen, FINECOSINE(deltaangle));
	tmymove = FixedMul(movelen, FINESINE(deltaangle));

	deltaangle = R_PointToAngle2(0, 0, tmxmove, tmymove);
}

//
// PTR_SlideCameraTraverse
//
static boolean PTR_SlideCameraTraverse(intercept_t *in)
{
	line_t *li;

	I_Assert(in->isaline);

	li = in->d.line;

	// one-sided linedef
	if (!li->backsector)
	{
		if (P_PointOnLineSide(mapcampointer->x, mapcampointer->y, li))
			return true; // don't hit the back side
		goto isblocking;
	}

	// set openrange, opentop, openbottom
	P_CameraLineOpening(li);

	if (openrange < mapcampointer->height)
		goto isblocking; // doesn't fit

	if (opentop - mapcampointer->z < mapcampointer->height)
		goto isblocking; // mobj is too high

	if (openbottom - mapcampointer->z > 0) // We don't want to make the camera step up.
		goto isblocking; // too big a step up

	// this line doesn't block movement
	return true;

	// the line does block movement,
	// see if it is closer than best so far
isblocking:
	{
		if (in->frac < bestslidefrac)
		{
			secondslidefrac = bestslidefrac;
			secondslideline = bestslideline;
			bestslidefrac = in->frac;
			bestslideline = li;
		}
	}

	return false; // stop
}

//
// P_IsClimbingValid
//
// Unlike P_DoClimbing, don't use when up against a one-sided linedef.
//
static boolean P_IsClimbingValid(player_t *player, angle_t angle)
{
	fixed_t platx, platy;
	sector_t *glidesector;
	fixed_t floorz, ceilingz;
	mobj_t *mo = player->mo;
	ffloor_t *rover;

	platx = P_ReturnThrustX(mo, angle, mo->radius + FixedMul(8*FRACUNIT, mo->scale));
	platy = P_ReturnThrustY(mo, angle, mo->radius + FixedMul(8*FRACUNIT, mo->scale));

	glidesector = R_PointInSubsector(mo->x + platx, mo->y + platy)->sector;

	floorz   = P_GetSectorFloorZAt  (glidesector, mo->x, mo->y);
	ceilingz = P_GetSectorCeilingZAt(glidesector, mo->x, mo->y);

	if (glidesector != mo->subsector->sector)
	{
		boolean floorclimb = false;
		fixed_t topheight, bottomheight;

		for (rover = glidesector->ffloors; rover; rover = rover->next)
		{
			if (!(rover->fofflags & FOF_EXISTS) || !(rover->fofflags & FOF_BLOCKPLAYER))
				continue;

			topheight    = P_GetFFloorTopZAt   (rover, mo->x, mo->y);
			bottomheight = P_GetFFloorBottomZAt(rover, mo->x, mo->y);

			floorclimb = true;

			if (mo->eflags & MFE_VERTICALFLIP)
			{
				if ((topheight < mo->z + mo->height) && ((mo->z + mo->height + mo->momz) < topheight))
					floorclimb = true;
				if (topheight < mo->z) // Waaaay below the ledge.
					floorclimb = false;
				if (bottomheight > mo->z + mo->height - FixedMul(16*FRACUNIT,mo->scale))
					floorclimb = false;
			}
			else
			{
				if ((bottomheight > mo->z) && ((mo->z - mo->momz) > bottomheight))
					floorclimb = true;
				if (bottomheight > mo->z + mo->height) // Waaaay below the ledge.
					floorclimb = false;
				if (topheight < mo->z + FixedMul(16*FRACUNIT,mo->scale))
					floorclimb = false;
			}

			if (floorclimb)
				break;
		}

		if (mo->eflags & MFE_VERTICALFLIP)
		{
			if ((floorz <= mo->z + mo->height)
				&& ((mo->z + mo->height - mo->momz) <= floorz))
				floorclimb = true;

			if ((floorz > mo->z)
				&& glidesector->floorpic == skyflatnum)
				return false;

			if ((mo->z + mo->height - FixedMul(16*FRACUNIT,mo->scale) > ceilingz)
				|| (mo->z + mo->height <= floorz))
				floorclimb = true;
		}
		else
		{
			if ((ceilingz >= mo->z)
				&& ((mo->z - mo->momz) >= ceilingz))
				floorclimb = true;

			if ((ceilingz < mo->z+mo->height)
				&& glidesector->ceilingpic == skyflatnum)
				return false;

			if ((mo->z + FixedMul(16*FRACUNIT,mo->scale) < floorz)
				|| (mo->z >= ceilingz))
				floorclimb = true;
		}

		if (!floorclimb)
			return false;

		return true;
	}

	return false;
}

//
// P_LineIsBlocking
//
// Determines if line would block mo's movement
//
boolean P_LineIsBlocking(mobj_t *mo, line_t *li)
{
	// one-sided linedefs are always solid to sliding movement.
	if (!li->backsector)
		return !P_PointOnLineSide(mo->x, mo->y, li);

	if (!(mo->flags & MF_MISSILE))
	{
		if (li->flags & ML_IMPASSIBLE)
			return true;

		if ((mo->flags & (MF_ENEMY|MF_BOSS)) && li->flags & ML_BLOCKMONSTERS)
			return true;
	}

	// set openrange, opentop, openbottom
	P_LineOpening(li, mo);

	if (openrange < mo->height)
		return true; // doesn't fit

	if (opentop - mo->z < mo->height)
		return true; // mobj is too high

	if (openbottom - mo->z > FixedMul(MAXSTEPMOVE, mo->scale))
		return true; // too big a step up

	if (mo->player
		&& openrange < P_GetPlayerHeight(mo->player)
		&& !P_PlayerCanEnterSpinGaps(mo->player))
			return true; // nonspin character should not take this path

	return false;
}

static void PTR_GlideClimbTraverse(line_t *li)
{
	line_t *checkline = li;
	ffloor_t *rover;
	fixed_t topheight, bottomheight;
	boolean fofline = false;
	sector_t *checksector = (li->backsector && !P_PointOnLineSide(slidemo->x, slidemo->y, li)) ? li->backsector : li->frontsector;

	if (checksector->ffloors)
	{
		for (rover = checksector->ffloors; rover; rover = rover->next)
		{
			if (!(rover->fofflags & FOF_EXISTS) || !(rover->fofflags & FOF_BLOCKPLAYER) || ((rover->fofflags & FOF_BUSTUP) && (slidemo->player->charflags & SF_CANBUSTWALLS)))
				continue;

			topheight    = P_GetFFloorTopZAt   (rover, slidemo->x, slidemo->y);
			bottomheight = P_GetFFloorBottomZAt(rover, slidemo->x, slidemo->y);

			if (topheight < slidemo->z)
				continue;

			if (bottomheight > slidemo->z + slidemo->height)
				continue;

			// Got this far, so I guess it's climbable. // TODO: Climbing check, also, better method to do this?
			if (rover->master->flags & ML_TFERLINE)
			{
				size_t linenum = li-checksector->lines[0];
				checkline = rover->master->frontsector->lines[0] + linenum;
				fofline = true;
			}

			break;
		}
	}

	// see about climbing on the wall
	if (!(checkline->flags & ML_NOCLIMB) && checkline->special != SPECIAL_HORIZON_LINE)
	{
		boolean canclimb;
		angle_t climbangle, climbline;
		INT32 whichside = P_PointOnLineSide(slidemo->x, slidemo->y, li);

		climbangle = climbline = R_PointToAngle2(li->v1->x, li->v1->y, li->v2->x, li->v2->y);

		if (whichside) // on second side?
			climbline += ANGLE_180;

		climbangle += (ANGLE_90 * (whichside ? -1 : 1));

		canclimb = (li->backsector ? P_IsClimbingValid(slidemo->player, climbangle) : true);

		if (((!slidemo->player->climbing && abs((signed)(slidemo->angle - ANGLE_90 - climbline)) < ANGLE_45)
			|| (slidemo->player->climbing == 1 && abs((signed)(slidemo->angle - climbline)) < ANGLE_135))
			&& canclimb)
		{
			slidemo->angle = climbangle;
			/*if (!demoplayback || P_ControlStyle(slidemo->player) == CS_LMAOGALOG)
				P_SetPlayerAngle(slidemo->player, slidemo->angle);*/

			if (!slidemo->player->climbing)
			{
				S_StartSound(slidemo, sfx_s3k4a);
				slidemo->player->climbing = 5;
				if (slidemo->player->powers[pw_super])
				{
					P_Earthquake(slidemo, slidemo, 256*FRACUNIT);
					S_StartSound(slidemo, sfx_s3k49);
				}
			}

			slidemo->player->pflags &= ~(PF_GLIDING|PF_SPINNING|PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED);
			slidemo->player->glidetime = 0;
			slidemo->player->secondjump = 0;

			if (slidemo->player->climbing > 1)
				slidemo->momz = slidemo->momx = slidemo->momy = 0;

			if (fofline)
				whichside = 0;

                        // Even if you attach to the second side of a linedef, we want to know the last hit.
			slidemo->player->lastsidehit = checkline->sidenum[whichside];
			slidemo->player->lastlinehit = (INT16)(checkline - lines);

			P_Thrust(slidemo, slidemo->angle, FixedMul(5*FRACUNIT, slidemo->scale));
		}
	}
}

static boolean PTR_SlideTraverse(intercept_t *in)
{
	line_t *li;

	I_Assert(in->isaline);

	li = in->d.line;

	if (!P_LineIsBlocking(slidemo, li))
		return true;

	// the line blocks movement,
	// see if it is closer than best so far
	if (li->polyobj && slidemo->player)
	{
		if ((li->polyobj->lines[0]->backsector->flags & MSF_TRIGGERSPECIAL_TOUCH) && !(li->polyobj->flags & POF_NOSPECIALS))
			P_ProcessSpecialSector(slidemo->player, slidemo->subsector->sector, li->polyobj->lines[0]->backsector);
	}

	if (slidemo->player)
	{
		if (slidemo->player->charability == CA_GLIDEANDCLIMB
			&& (slidemo->player->pflags & PF_GLIDING || slidemo->player->climbing))
			PTR_GlideClimbTraverse(li);
		else
		{
			slidemo->player->lastsidehit = li->sidenum[P_PointOnLineSide(slidemo->x, slidemo->y, li)];
			slidemo->player->lastlinehit = (INT16)(li - lines);
		}
	}

	if (in->frac < bestslidefrac && (!slidemo->player || !slidemo->player->climbing))
	{
		secondslidefrac = bestslidefrac;
		secondslideline = bestslideline;
		bestslidefrac = in->frac;
		bestslideline = li;
	}

	return false; // stop
}

//
// P_SlideCameraMove
//
// Tries to slide the camera along a wall.
//
void P_SlideCameraMove(camera_t *thiscam)
{
	fixed_t leadx, leady, trailx, traily, newx, newy;
	INT32 hitcount = 0;
	INT32 retval = 0;

	bestslideline = NULL;

retry:
	if (++hitcount == 3)
		goto stairstep; // don't loop forever

	// trace along the three leading corners
	if (thiscam->momx > 0)
	{
		leadx = thiscam->x + thiscam->radius;
		trailx = thiscam->x - thiscam->radius;
	}
	else
	{
		leadx = thiscam->x - thiscam->radius;
		trailx = thiscam->x + thiscam->radius;
	}

	if (thiscam->momy > 0)
	{
		leady = thiscam->y + thiscam->radius;
		traily = thiscam->y - thiscam->radius;
	}
	else
	{
		leady = thiscam->y - thiscam->radius;
		traily = thiscam->y + thiscam->radius;
	}

	bestslidefrac = FRACUNIT+1;

	mapcampointer = thiscam;

	P_PathTraverse(leadx, leady, leadx + thiscam->momx, leady + thiscam->momy,
		PT_ADDLINES, PTR_SlideCameraTraverse);
	P_PathTraverse(trailx, leady, trailx + thiscam->momx, leady + thiscam->momy,
		PT_ADDLINES, PTR_SlideCameraTraverse);
	P_PathTraverse(leadx, traily, leadx + thiscam->momx, traily + thiscam->momy,
		PT_ADDLINES, PTR_SlideCameraTraverse);

	// move up to the wall
	if (bestslidefrac == FRACUNIT+1)
	{
		retval = P_TryCameraMove(thiscam->x, thiscam->y + thiscam->momy, thiscam);
		// the move must have hit the middle, so stairstep
stairstep:
		if (!retval) // Allow things to drop off.
			P_TryCameraMove(thiscam->x + thiscam->momx, thiscam->y, thiscam);
		return;
	}

	// fudge a bit to make sure it doesn't hit
	bestslidefrac -= 0x800;
	if (bestslidefrac > 0)
	{
		newx = FixedMul(thiscam->momx, bestslidefrac);
		newy = FixedMul(thiscam->momy, bestslidefrac);

		retval = P_TryCameraMove(thiscam->x + newx, thiscam->y + newy, thiscam);

		if (!retval)
			goto stairstep;
	}

	// Now continue along the wall.
	// First calculate remainder.
	bestslidefrac = FRACUNIT - (bestslidefrac+0x800);

	if (bestslidefrac > FRACUNIT)
		bestslidefrac = FRACUNIT;

	if (bestslidefrac <= 0)
		return;

	tmxmove = FixedMul(thiscam->momx, bestslidefrac);
	tmymove = FixedMul(thiscam->momy, bestslidefrac);

	P_HitCameraSlideLine(bestslideline, thiscam); // clip the moves

	thiscam->momx = tmxmove;
	thiscam->momy = tmymove;

	retval = P_TryCameraMove(thiscam->x + tmxmove, thiscam->y + tmymove, thiscam);

	if (!retval)
		goto retry;
}

static void P_CheckLavaWall(mobj_t *mo, sector_t *sec)
{
	ffloor_t *rover;
	fixed_t topheight, bottomheight;

	for (rover = sec->ffloors; rover; rover = rover->next)
	{
		if (!(rover->fofflags & FOF_EXISTS))
			continue;

		if (!(rover->fofflags & FOF_SWIMMABLE))
			continue;

		if (rover->master->frontsector->damagetype != SD_LAVA)
			continue;

		topheight = P_GetFFloorTopZAt(rover, mo->x, mo->y);

		if (mo->eflags & MFE_VERTICALFLIP)
		{
			if (topheight < mo->z - mo->height)
				continue;
		}
		else
		{
			if (topheight < mo->z)
				continue;
		}

		bottomheight = P_GetFFloorBottomZAt(rover, mo->x, mo->y);

		if (mo->eflags & MFE_VERTICALFLIP)
		{
			if (bottomheight > mo->z)
				continue;
		}
		else
		{
			if (bottomheight > mo->z + mo->height)
				continue;
		}

		P_DamageMobj(mo, NULL, NULL, 1, DMG_FIRE);
		return;
	}
}

static inline void P_StairStepSlideMove(mobj_t *mo)
{
	if (!P_TryMove(mo, mo->x, mo->y + mo->momy, true) && !P_MobjWasRemoved(mo)) //Allow things to drop off.
		P_TryMove(mo, mo->x + mo->momx, mo->y, true);
}

//
// 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(mobj_t *mo)
{
	fixed_t leadx, leady, trailx, traily, newx, newy;
	INT16 hitcount = 0;
	boolean success = false;

	boolean papercol = false;
	vertex_t v1, v2; // fake vertexes
	static line_t junk; // fake linedef

	memset(&junk, 0x00, sizeof(junk));

	I_Assert(!P_MobjWasRemoved(mo));

	if (tmhitthing && mo->z + mo->height > tmhitthing->z && mo->z < tmhitthing->z + tmhitthing->height)
	{
		// Don't mess with your momentum if it's a pushable object. Pushables do their own crazy things already.
		if (tmhitthing->flags & MF_PUSHABLE)
			return;

		if (tmhitthing->flags & MF_PAPERCOLLISION)
		{
			fixed_t cosradius, sinradius, num, den;

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

			papercol = true;
			slidemo = mo;
			bestslideline = &junk;

			cosradius = FixedMul(tmhitthing->radius, FINECOSINE(tmhitthing->angle>>ANGLETOFINESHIFT));
			sinradius = FixedMul(tmhitthing->radius, FINESINE(tmhitthing->angle>>ANGLETOFINESHIFT));

			v1.x = tmhitthing->x - cosradius;
			v1.y = tmhitthing->y - sinradius;
			v2.x = tmhitthing->x + cosradius;
			v2.y = tmhitthing->y + sinradius;

			// Can we box collision our way into smooth movement..?
			if (sinradius && mo->y + mo->radius <= min(v1.y, v2.y))
			{
				mo->momy = 0;
				P_TryMove(mo, mo->x + mo->momx, min(v1.y, v2.y) - mo->radius, true);
				return;
			}
			else if (sinradius && mo->y - mo->radius >= max(v1.y, v2.y))
			{
				mo->momy = 0;
				P_TryMove(mo, mo->x + mo->momx, max(v1.y, v2.y) + mo->radius, true);
				return;
			}
			else if (cosradius && mo->x + mo->radius <= min(v1.x, v2.x))
			{
				mo->momx = 0;
				P_TryMove(mo, min(v1.x, v2.x) - mo->radius, mo->y + mo->momy, true);
				return;
			}
			else if (cosradius && mo->x - mo->radius >= max(v1.x, v2.x))
			{
				mo->momx = 0;
				P_TryMove(mo, max(v1.x, v2.x) + mo->radius, mo->y + mo->momy, true);
				return;
			}

			// nope, gotta fuck around with a fake linedef!
			junk.v1 = &v1;
			junk.v2 = &v2;
			junk.dx = 2*cosradius; // v2.x - v1.x;
			junk.dy = 2*sinradius; // v2.y - v1.y;

			junk.slopetype = !cosradius ? ST_VERTICAL : !sinradius ? ST_HORIZONTAL :
			((sinradius > 0) == (cosradius > 0)) ? ST_POSITIVE : ST_NEGATIVE;

			bestslidefrac = FRACUNIT+1;

			den = FixedMul(junk.dy>>8, mo->momx) - FixedMul(junk.dx>>8, mo->momy);

			if (!den)
				bestslidefrac = 0;
			else
			{
				fixed_t frac;
#define P_PaperTraverse(startx, starty) \
				num = FixedMul((v1.x - leadx)>>8, junk.dy) + FixedMul((leady - v1.y)>>8, junk.dx); \
				frac = FixedDiv(num, den); \
				if (frac < bestslidefrac) \
					bestslidefrac = frac
				P_PaperTraverse(leadx, leady);
				P_PaperTraverse(trailx, leady);
				P_PaperTraverse(leadx, traily);
#undef dowork
			}

			goto papercollision;
		}

		// Thankfully box collisions are a lot simpler than arbitrary lines. There's only four possible cases.
		if (mo->y + mo->radius <= tmhitthing->y - tmhitthing->radius)
		{
			mo->momy = 0;
			P_TryMove(mo, mo->x + mo->momx, tmhitthing->y - tmhitthing->radius - mo->radius, true);
		}
		else if (mo->y - mo->radius >= tmhitthing->y + tmhitthing->radius)
		{
			mo->momy = 0;
			P_TryMove(mo, mo->x + mo->momx, tmhitthing->y + tmhitthing->radius + mo->radius, true);
		}
		else if (mo->x + mo->radius <= tmhitthing->x - tmhitthing->radius)
		{
			mo->momx = 0;
			P_TryMove(mo, tmhitthing->x - tmhitthing->radius - mo->radius, mo->y + mo->momy, true);
		}
		else if (mo->x - mo->radius >= tmhitthing->x + tmhitthing->radius)
		{
			mo->momx = 0;
			P_TryMove(mo, tmhitthing->x + tmhitthing->radius + mo->radius, mo->y + mo->momy, true);
		}
		else
			mo->momx = mo->momy = 0;
		return;
	}

	slidemo = mo;
	bestslideline = NULL;

retry:
	if ((++hitcount == 3) || papercol)
	{
		P_StairStepSlideMove(mo);
		return;
	}

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

	if (bestslideline && mo->player && bestslideline->sidenum[1] != NO_SIDEDEF)
	{
		sector_t *sec = P_PointOnLineSide(mo->x, mo->y, bestslideline) ? bestslideline->frontsector : bestslideline->backsector;
		P_CheckLavaWall(mo, sec);
	}

	// Some walls are bouncy even if you're not
	if (bestslideline && bestslideline->flags & ML_BOUNCY)
	{
		P_BounceMove(mo);
		return;
	}

papercollision:
	// move up to the wall
	if (bestslidefrac == FRACUNIT+1)
	{
		// the move must have hit the middle, so stairstep
		P_StairStepSlideMove(mo);
		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);

		if (!P_TryMove(mo, mo->x + newx, mo->y + newy, true))
		{
			if (!P_MobjWasRemoved(mo))
				P_StairStepSlideMove(mo);
			return;
		}
		if (P_MobjWasRemoved(mo))
			return;
	}

	// Now continue along the wall.
	// First calculate remainder.
	bestslidefrac = FRACUNIT - (bestslidefrac+0x800);

	if (bestslidefrac > FRACUNIT)
		bestslidefrac = FRACUNIT;

	if (bestslidefrac <= 0)
		return;

	tmxmove = FixedMul(mo->momx, bestslidefrac);
	tmymove = FixedMul(mo->momy, bestslidefrac);

	P_HitSlideLine(bestslideline); // clip the moves

	if ((twodlevel || (mo->flags2 & MF2_TWOD)) && mo->player)
	{
		mo->momx = tmxmove;
		tmymove = 0;
	}
	else
	{
		mo->momx = tmxmove;
		mo->momy = tmymove;
	}

	do {
		if (tmxmove > mo->radius) {
			newx = mo->x + mo->radius;
			tmxmove -= mo->radius;
		} else if (tmxmove < -mo->radius) {
			newx = mo->x - mo->radius;
			tmxmove += mo->radius;
		} else {
			newx = mo->x + tmxmove;
			tmxmove = 0;
		}
		if (tmymove > mo->radius) {
			newy = mo->y + mo->radius;
			tmymove -= mo->radius;
		} else if (tmymove < -mo->radius) {
			newy = mo->y - mo->radius;
			tmymove += mo->radius;
		} else {
			newy = mo->y + tmymove;
			tmymove = 0;
		}
		if (!P_TryMove(mo, newx, newy, true)) {
			if (success || P_MobjWasRemoved(mo))
				return; // Good enough!!
			else
				goto retry;
		}
		if (P_MobjWasRemoved(mo))
			return;
		success = true;
	} while(tmxmove || tmymove);
}

//
// P_BounceMove
//
// The momx / momy move is bad, so try to bounce off a wall.
//
void P_BounceMove(mobj_t *mo)
{
	fixed_t leadx, leady;
	fixed_t trailx, traily;
	fixed_t newx, newy;
	INT32 hitcount;
	fixed_t mmomx = 0, mmomy = 0;

	slidemo = mo;
	hitcount = 0;

	do
	{
		if (++hitcount == 3)
			goto bounceback; // don't loop forever

		if (mo->player)
		{
			mmomx = mo->player->rmomx;
			mmomy = mo->player->rmomy;
		}
		else
		{
			mmomx = mo->momx;
			mmomy = mo->momy;
		}

		// 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 + mmomx, leady + mmomy, PT_ADDLINES, PTR_SlideTraverse);
		P_PathTraverse(trailx, leady, trailx + mmomx, leady + mmomy, PT_ADDLINES, PTR_SlideTraverse);
		P_PathTraverse(leadx, traily, leadx + mmomx, traily + mmomy, PT_ADDLINES, PTR_SlideTraverse);

		// move up to the wall
		if (bestslidefrac == FRACUNIT + 1)
		{
			// the move must have hit the middle, so bounce straight back
bounceback:
			if (P_TryMove(mo, mo->x - mmomx, mo->y - mmomy, true))
			{
				mo->momx *= -1;
				mo->momy *= -1;
				mo->momx = FixedMul(mo->momx, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
				mo->momy = FixedMul(mo->momy, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));

				if (mo->player)
				{
					mo->player->cmomx *= -1;
					mo->player->cmomy *= -1;
					mo->player->cmomx = FixedMul(mo->player->cmomx,
						(FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
					mo->player->cmomy = FixedMul(mo->player->cmomy,
						(FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
				}
			}
			return;
		}

		// fudge a bit to make sure it doesn't hit
		bestslidefrac -= 0x800;
		if (bestslidefrac > 0)
		{
			newx = FixedMul(mmomx, bestslidefrac);
			newy = FixedMul(mmomy, bestslidefrac);

			if (!P_TryMove(mo, mo->x + newx, mo->y + newy, true))
			{
				if (P_MobjWasRemoved(mo))
					return;
				goto bounceback;
			}
		}

		// Now continue along the wall.
		// First calculate remainder.
		bestslidefrac = FRACUNIT - bestslidefrac;

		if (bestslidefrac > FRACUNIT)
			bestslidefrac = FRACUNIT;

		if (bestslidefrac <= 0)
			return;

		if (mo->type == MT_SHELL)
		{
			tmxmove = mmomx;
			tmymove = mmomy;
		}
		else if (mo->type == MT_THROWNBOUNCE)
		{
			tmxmove = FixedMul(mmomx, (FRACUNIT - (FRACUNIT>>6) - (FRACUNIT>>5)));
			tmymove = FixedMul(mmomy, (FRACUNIT - (FRACUNIT>>6) - (FRACUNIT>>5)));
		}
		else if (mo->type == MT_THROWNGRENADE || mo->type == MT_CYBRAKDEMON_NAPALM_BOMB_LARGE)
		{
			// Quickly decay speed as it bounces
			tmxmove = FixedDiv(mmomx, 2*FRACUNIT);
			tmymove = FixedDiv(mmomy, 2*FRACUNIT);
		}
		else
		{
			tmxmove = FixedMul(mmomx, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
			tmymove = FixedMul(mmomy, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
		}

		P_HitBounceLine(bestslideline); // clip the moves

		mo->momx = tmxmove;
		mo->momy = tmymove;

		if (mo->player)
		{
			mo->player->cmomx = tmxmove;
			mo->player->cmomy = tmymove;
		}
	}
	while (!P_TryMove(mo, mo->x + tmxmove, mo->y + tmymove, true) && !P_MobjWasRemoved(mo));
}

//
// RADIUS ATTACK
//
static fixed_t bombdamage;
static mobj_t *bombsource;
static mobj_t *bombspot;
static UINT8 bombdamagetype;
static boolean bombsightcheck;

//
// PIT_RadiusAttack
// "bombsource" is the creature
// that caused the explosion at "bombspot".
//
static boolean PIT_RadiusAttack(mobj_t *thing)
{
	fixed_t dx, dy, dz, dist;

	if (thing == bombspot) // ignore the bomb itself (Deton fix)
		return true;

	if (bombsource && thing->type == bombsource->type && !(bombdamagetype & DMG_CANHURTSELF)) // ignore the type of guys who dropped the bomb (Jetty-Syn Bomber or Skim can bomb eachother, but not themselves.)
		return true;

	if (thing->type == MT_MINUS && !(thing->flags & (MF_SPECIAL|MF_SHOOTABLE)) && !bombsightcheck)
		thing->flags = (thing->flags & ~MF_NOCLIPTHING)|MF_SPECIAL|MF_SHOOTABLE;

	if (thing->type == MT_EGGGUARD && thing->tracer) //nuke Egg Guard's shield!
		P_KillMobj(thing->tracer, bombspot, bombsource, bombdamagetype);

	if ((thing->flags & (MF_MONITOR|MF_SHOOTABLE)) != MF_SHOOTABLE)
		return true;

	dx = abs(thing->x - bombspot->x);
	dy = abs(thing->y - bombspot->y);
	dz = abs(thing->z + (thing->height>>1) - bombspot->z);

	dist = P_AproxDistance(P_AproxDistance(dx, dy), dz);
	dist -= thing->radius;

	if (dist < 0)
		dist = 0;

	if (dist >= bombdamage)
		return true; // out of range

	if (thing->floorz > bombspot->z && bombspot->ceilingz < thing->z)
		return true;

	if (thing->ceilingz < bombspot->z && bombspot->floorz > thing->z)
		return true;

	if (!bombsightcheck || P_CheckSight(thing, bombspot))
	{	// must be in direct path
		P_DamageMobj(thing, bombspot, bombsource, 1, bombdamagetype); // Tails 01-11-2001
	}

	return true;
}

//
// P_RadiusAttack
// Source is the creature that caused the explosion at spot.
//
void P_RadiusAttack(mobj_t *spot, mobj_t *source, fixed_t damagedist, UINT8 damagetype, boolean sightcheck)
{
	INT32 xl, xh, yl, yh;
	fixed_t dist;

	dist = FixedMul(damagedist, spot->scale) + MAXRADIUS;
	yh = (unsigned)(spot->y + dist - bmaporgy)>>MAPBLOCKSHIFT;
	yl = (unsigned)(spot->y - dist - bmaporgy)>>MAPBLOCKSHIFT;
	xh = (unsigned)(spot->x + dist - bmaporgx)>>MAPBLOCKSHIFT;
	xl = (unsigned)(spot->x - dist - bmaporgx)>>MAPBLOCKSHIFT;

	BMBOUNDFIX(xl, xh, yl, yh);

	bombspot = spot;
	bombsource = source;
	bombdamage = FixedMul(damagedist, spot->scale);
	bombdamagetype = damagetype;
	bombsightcheck = sightcheck;

	P_DoBlockThingsIterate(xl, yl, xh, yh, PIT_RadiusAttack);
}

//
// SECTOR HEIGHT CHANGING
// After modifying a sectors floor or ceiling height,
// call this routine to adjust the positions
// of all things that touch the sector.
//
// If anything doesn't fit anymore, true will be returned.
// If crunch is true, they will take damage
//  as they are being crushed.
// If Crunch is false, you should set the sector height back
//  the way it was and call P_CheckSector (? was P_ChangeSector - Graue) again
//  to undo the changes.
//

//
// PIT_ChangeSector
//
static boolean PIT_ChangeSector(mobj_t *thing, boolean realcrush, boolean crunch)
{
	mobj_t *killer = NULL;
	//If a thing is both pushable and vulnerable, it doesn't block the crusher because it gets killed.
	boolean immunepushable = ((thing->flags & (MF_PUSHABLE | MF_SHOOTABLE)) == MF_PUSHABLE);

	if (P_ThingHeightClip(thing))
	{
		//thing fits, check next thing
		return true;
	}

	if (!(thing->flags & (MF_SHOOTABLE|MF_PUSHABLE))
	|| thing->flags & MF_NOCLIPHEIGHT)
	{
		//doesn't interact with the crusher, just ignore it
		return true;
	}

	// Thing doesn't fit. If thing is vulnerable, that means it's being crushed. If thing is pushable, it might
	// just be blocked by another object - check if it's really a ceiling!
	if (thing->z + thing->height > thing->ceilingz && thing->z <= thing->ceilingz)
	{
		if (immunepushable && thing->z + thing->height > thing->subsector->sector->ceilingheight)
			return false; //Thing is a pushable and blocks the moving ceiling

		//Check FOFs in the sector
		if (thing->subsector->sector->ffloors && (realcrush || immunepushable))
		{
			ffloor_t *rover;
			fixed_t topheight, bottomheight;
			fixed_t delta1, delta2;
			INT32 thingtop = thing->z + thing->height;

			for (rover = thing->subsector->sector->ffloors; rover; rover = rover->next)
			{
				thinker_t *think;

				if (!(rover->fofflags & FOF_EXISTS))
					continue;
				if (thing->player && !(rover->fofflags & FOF_BLOCKPLAYER))
					continue;
				if (!thing->player && !(rover->fofflags & FOF_BLOCKOTHERS))
					continue;

				topheight = *rover->topheight;
				bottomheight = *rover->bottomheight;

				if (bottomheight > thing->ceilingz)
					continue;

				delta1 = thing->z - (bottomheight + topheight)/2;
				delta2 = thingtop - (bottomheight + topheight)/2;
				if (abs(delta1) < abs(delta2))
					continue;

				if (immunepushable)
					return false; //FOF is blocked by pushable

				if (!realcrush)
					continue;

				//If the thing was crushed by a crumbling FOF, reward the player who made it crumble!
				for (think = thlist[THINK_MAIN].next; think != &thlist[THINK_MAIN]; think = think->next)
				{
					crumble_t *crumbler;

					if (think->function.acp1 != (actionf_p1)T_StartCrumble)
						continue;

					crumbler = (crumble_t *)think;

					if (!crumbler->player)
						continue;
					if (!crumbler->player->mo)
						continue;
					if (crumbler->player->mo == thing)
						continue;
					if (crumbler->actionsector != thing->subsector->sector)
						continue;
					if (crumbler->sector != rover->master->frontsector)
						continue;

					killer = crumbler->player->mo;
				}
			}
		}

		if (realcrush)
		{
			// Crush the object
			if (netgame && thing->player && thing->player->spectator)
				P_DamageMobj(thing, NULL, NULL, 1, DMG_SPECTATOR); // Respawn crushed spectators
			else
				P_DamageMobj(thing, killer, killer, 1, DMG_CRUSHED);
			return true;
		}
	}

	if (realcrush && crunch)
		P_DamageMobj(thing, NULL, NULL, 1, 0);

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

static boolean P_CheckSectorPolyObjects(sector_t *sector, boolean realcrush, boolean crunch)
{
	size_t i;

	// Sal: This stupid function chain is required to fix polyobjects not being able to crush.
	// Monster Iestyn: don't use P_CheckSector actually just look for objects in the blockmap instead
	validcount++;

	for (i = 0; i < sector->linecount; i++)
	{
		INT32 x, y;
		polyobj_t *po = sector->lines[i]->polyobj;

		if (!po)
			continue;
		if (po->validcount == validcount)
			continue; // skip if already checked
		if (!(po->flags & POF_SOLID))
			continue;
		if (po->lines[0]->backsector != sector) // Make sure you're currently checking the control sector
			continue;

		po->validcount = validcount;

		for (y = po->blockbox[BOXBOTTOM]; y <= po->blockbox[BOXTOP]; ++y)
		{
			for (x = po->blockbox[BOXLEFT]; x <= po->blockbox[BOXRIGHT]; ++x)
			{
				mobj_t *mo;
				blocknode_t *block;

				if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
					continue;

				block = blocklinks[y * bmapwidth + x];

				for (; block; block = block->mnext)
				{
					mo = block->mobj;

					// Monster Iestyn: do we need to check if a mobj has already been checked? ...probably not I suspect
					if (!P_MobjInsidePolyobj(po, mo))
						continue;

					if (!PIT_ChangeSector(mo, realcrush, crunch) && !realcrush)
						return false;
				}
			}
		}
	}

	return true;
}

static boolean P_CheckTouchingThinglist(sector_t *sector, boolean realcrush, boolean crunch)
{
	msecnode_t *n;

	for (n = sector->touching_thinglist; n; n = n->m_thinglist_next)
		n->visited = false;

	do
	{
		for (n = sector->touching_thinglist; n; n = n->m_thinglist_next) // go through list
		{
			if (n->visited)
				continue;

			n->visited = true; // mark thing as processed

			if (n->m_thing->flags & MF_NOBLOCKMAP) //jff 4/7/98 don't do these
				continue;

			if (!PIT_ChangeSector(n->m_thing, realcrush, crunch) && !realcrush) // process it
				return false;

			break; // exit and start over
		}
	} while (n); // repeat from scratch until all things left are marked valid

	return true;
}

static boolean P_CheckSectorFFloors(sector_t *sector, boolean realcrush, boolean crunch)
{
	sector_t *sec;
	size_t i;

	if (!sector->numattached)
		return true;

	for (i = 0; i < sector->numattached; i++)
	{
		sec = &sectors[sector->attached[i]];

		sec->moved = true;

		P_RecalcPrecipInSector(sec);

		if (!sector->attachedsolid[i])
			continue;

		if (!P_CheckTouchingThinglist(sec, realcrush, crunch))
			return false;
	}

	return true;
}

static boolean P_CheckSectorHelper(sector_t *sector, boolean realcrush, boolean crunch)
{
	if (!P_CheckSectorPolyObjects(sector, realcrush, crunch))
		return false;

	if (!P_CheckSectorFFloors(sector, realcrush, crunch))
		return false;

	// Mark all things invalid
	sector->moved = true;

	return P_CheckTouchingThinglist(sector, realcrush, crunch);
}

//
// P_CheckSector
//
boolean P_CheckSector(sector_t *sector, boolean crunch)
{
	// killough 4/4/98: scan list front-to-back until empty or exhausted,
	// restarting from beginning after each thing is processed. Avoids
	// crashes, and is sure to examine all things in the sector, and only
	// the things which are in the sector, until a steady-state is reached.
	// Things can arbitrarily be inserted and removed and it won't mess up.
	//
	// killough 4/7/98: simplified to avoid using complicated counter

	// First, let's see if anything will keep it from crushing.
	if (!P_CheckSectorHelper(sector, false, crunch))
		return true;

	// Nothing blocked us, so lets crush for real!
	P_CheckSectorHelper(sector, true, crunch);

	return false;
}

/*
 SoM: 3/15/2000
 Lots of new Boom functions that work faster and add functionality.
*/

static msecnode_t *headsecnode = NULL;
static mprecipsecnode_t *headprecipsecnode = NULL;

void P_Initsecnode(void)
{
	headsecnode = NULL;
	headprecipsecnode = NULL;
}

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

static msecnode_t *P_GetSecnode(void)
{
	msecnode_t *node;

	if (headsecnode)
	{
		node = headsecnode;
		headsecnode = headsecnode->m_thinglist_next;
	}
	else
		node = Z_Calloc(sizeof (*node), PU_LEVEL, NULL);
	return node;
}

static mprecipsecnode_t *P_GetPrecipSecnode(void)
{
	mprecipsecnode_t *node;

	if (headprecipsecnode)
	{
		node = headprecipsecnode;
		headprecipsecnode = headprecipsecnode->m_thinglist_next;
	}
	else
		node = Z_Calloc(sizeof (*node), PU_LEVEL, NULL);
	return node;
}

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

static inline void P_PutSecnode(msecnode_t *node)
{
	node->m_thinglist_next = headsecnode;
	headsecnode = node;
}

// Tails 08-25-2002
static inline void P_PutPrecipSecnode(mprecipsecnode_t *node)
{
	node->m_thinglist_next = headprecipsecnode;
	headprecipsecnode = node;
}

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

static msecnode_t *P_AddSecnode(sector_t *s, mobj_t *thing, msecnode_t *nextnode)
{
	msecnode_t *node;

	node = nextnode;
	while (node)
	{
		if (node->m_sector == s) // Already have a node for this sector?
		{
			node->m_thing = thing; // Yes. Setting m_thing says 'keep it'.
			return nextnode;
		}
		node = node->m_sectorlist_next;
	}

	// Couldn't find an existing node for this sector. Add one at the head
	// of the list.

	node = P_GetSecnode();

	// mark new nodes unvisited.
	node->visited = 0;

	node->m_sector = s; // sector
	node->m_thing = thing; // mobj
	node->m_sectorlist_prev = NULL; // prev node on Thing thread
	node->m_sectorlist_next = nextnode; // next node on Thing thread
	if (nextnode)
		nextnode->m_sectorlist_prev = node; // set back link on Thing

	// Add new node at head of sector thread starting at s->touching_thinglist

	node->m_thinglist_prev = NULL; // prev node on sector thread
	node->m_thinglist_next = s->touching_thinglist; // next node on sector thread
	if (s->touching_thinglist)
		node->m_thinglist_next->m_thinglist_prev = node;
	s->touching_thinglist = node;
	return node;
}

// More crazy crap Tails 08-25-2002
static mprecipsecnode_t *P_AddPrecipSecnode(sector_t *s, precipmobj_t *thing, mprecipsecnode_t *nextnode)
{
	mprecipsecnode_t *node;

	node = nextnode;
	while (node)
	{
		if (node->m_sector == s) // Already have a node for this sector?
		{
			node->m_thing = thing; // Yes. Setting m_thing says 'keep it'.
			return nextnode;
		}
		node = node->m_sectorlist_next;
	}

	// Couldn't find an existing node for this sector. Add one at the head
	// of the list.

	node = P_GetPrecipSecnode();

	// mark new nodes unvisited.
	node->visited = 0;

	node->m_sector = s; // sector
	node->m_thing = thing; // mobj
	node->m_sectorlist_prev = NULL; // prev node on Thing thread
	node->m_sectorlist_next = nextnode; // next node on Thing thread
	if (nextnode)
		nextnode->m_sectorlist_prev = node; // set back link on Thing

	// Add new node at head of sector thread starting at s->touching_thinglist

	node->m_thinglist_prev = NULL; // prev node on sector thread
	node->m_thinglist_next = s->touching_preciplist; // next node on sector thread
	if (s->touching_preciplist)
		node->m_thinglist_next->m_thinglist_prev = node;
	s->touching_preciplist = 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.

static msecnode_t *P_DelSecnode(msecnode_t *node)
{
	msecnode_t *tp; // prev node on thing thread
	msecnode_t *tn; // next node on thing thread
	msecnode_t *sp; // prev node on sector thread
	msecnode_t *sn; // next node on sector thread

	if (!node)
		return NULL;

	// Unlink from the Thing thread. The Thing thread begins at
	// sector_list and not from mobj_t->touching_sectorlist.

	tp = node->m_sectorlist_prev;
	tn = node->m_sectorlist_next;
	if (tp)
		tp->m_sectorlist_next = tn;
	if (tn)
		tn->m_sectorlist_prev = tp;

	// Unlink from the sector thread. This thread begins at
	// sector_t->touching_thinglist.

	sp = node->m_thinglist_prev;
	sn = node->m_thinglist_next;
	if (sp)
		sp->m_thinglist_next = sn;
	else
		node->m_sector->touching_thinglist = sn;
	if (sn)
		sn->m_thinglist_prev = sp;

	// Return this node to the freelist

	P_PutSecnode(node);
	return tn;
}

// Tails 08-25-2002
static mprecipsecnode_t *P_DelPrecipSecnode(mprecipsecnode_t *node)
{
	mprecipsecnode_t *tp; // prev node on thing thread
	mprecipsecnode_t *tn; // next node on thing thread
	mprecipsecnode_t *sp; // prev node on sector thread
	mprecipsecnode_t *sn; // next node on sector thread

	if (!node)
		return NULL;

	// Unlink from the Thing thread. The Thing thread begins at
	// sector_list and not from mobj_t->touching_sectorlist.

	tp = node->m_sectorlist_prev;
	tn = node->m_sectorlist_next;
	if (tp)
		tp->m_sectorlist_next = tn;
	if (tn)
		tn->m_sectorlist_prev = tp;

	// Unlink from the sector thread. This thread begins at
	// sector_t->touching_thinglist.

	sp = node->m_thinglist_prev;
	sn = node->m_thinglist_next;
	if (sp)
		sp->m_thinglist_next = sn;
	else
		node->m_sector->touching_preciplist = sn;
	if (sn)
		sn->m_thinglist_prev = sp;

	// Return this node to the freelist

	P_PutPrecipSecnode(node);
	return tn;
}

// Delete an entire sector list
void P_DelSeclist(msecnode_t *node)
{
	while (node)
		node = P_DelSecnode(node);
}

// Tails 08-25-2002
void P_DelPrecipSeclist(mprecipsecnode_t *node)
{
	while (node)
		node = P_DelPrecipSecnode(node);
}

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

static inline boolean PIT_GetSectors(line_t *ld)
{
	if (tmbbox[BOXRIGHT] <= ld->bbox[BOXLEFT] ||
		tmbbox[BOXLEFT] >= ld->bbox[BOXRIGHT] ||
		tmbbox[BOXTOP] <= ld->bbox[BOXBOTTOM] ||
		tmbbox[BOXBOTTOM] >= ld->bbox[BOXTOP])
	return true;

	if (P_BoxOnLineSide(tmbbox, ld) != -1)
		return true;

	if (ld->polyobj) // line belongs to a polyobject, don't add it
		return true;

	// This line crosses through the object.

	// Collect the sector(s) from the line and add to the
	// sector_list you're examining. If the Thing ends up being
	// allowed to move to this position, then the sector_list
	// will be attached to the Thing's mobj_t at touching_sectorlist.

	sector_list = P_AddSecnode(ld->frontsector,tmthing,sector_list);

	// Don't assume all lines are 2-sided, since some Things
	// like MT_TFOG are allowed regardless of whether their radius takes
	// them beyond an impassable linedef.

	// Use sidedefs instead of 2s flag to determine two-sidedness.
	if (ld->backsector)
		sector_list = P_AddSecnode(ld->backsector, tmthing, sector_list);

	return true;
}

// Tails 08-25-2002
static inline boolean PIT_GetPrecipSectors(line_t *ld)
{
	if (preciptmbbox[BOXRIGHT] <= ld->bbox[BOXLEFT] ||
		preciptmbbox[BOXLEFT] >= ld->bbox[BOXRIGHT] ||
		preciptmbbox[BOXTOP] <= ld->bbox[BOXBOTTOM] ||
		preciptmbbox[BOXBOTTOM] >= ld->bbox[BOXTOP])
	return true;

	if (P_BoxOnLineSide(preciptmbbox, ld) != -1)
		return true;

	if (ld->polyobj) // line belongs to a polyobject, don't add it
		return true;

	// This line crosses through the object.

	// Collect the sector(s) from the line and add to the
	// sector_list you're examining. If the Thing ends up being
	// allowed to move to this position, then the sector_list
	// will be attached to the Thing's mobj_t at touching_sectorlist.

	precipsector_list = P_AddPrecipSecnode(ld->frontsector, tmprecipthing, precipsector_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.

	// Use sidedefs instead of 2s flag to determine two-sidedness.
	if (ld->backsector)
		precipsector_list = P_AddPrecipSecnode(ld->backsector, tmprecipthing, precipsector_list);

	return true;
}

// P_CreateSecNodeList alters/creates the sector_list that shows what sectors
// the object resides in.

void P_CreateSecNodeList(mobj_t *thing, fixed_t x, fixed_t y)
{
	INT32 xl, xh, yl, yh, bx, by;
	msecnode_t *node = sector_list;
	mobj_t *saved_tmthing = tmthing; /* cph - see comment at func end */
	fixed_t saved_tmx = tmx, saved_tmy = tmy; /* ditto */

	// First, clear out the existing m_thing fields. As each node is
	// added or verified as needed, m_thing will be set properly. When
	// finished, delete all nodes where m_thing is still NULL. These
	// represent the sectors the Thing has vacated.

	while (node)
	{
		node->m_thing = NULL;
		node = node->m_sectorlist_next;
	}

	P_SetTarget(&tmthing, thing);
	tmflags = thing->flags;

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

	BMBOUNDFIX(xl, xh, yl, yh);

	for (bx = xl; bx <= xh; bx++)
		for (by = yl; by <= yh; by++)
			P_BlockLinesIterator(bx, by, PIT_GetSectors);

	// Add the sector of the (x, y) point to sector_list.
	sector_list = P_AddSecnode(thing->subsector->sector, thing, sector_list);

	// Now delete any nodes that won't be used. These are the ones where
	// m_thing is still NULL.
	node = sector_list;
	while (node)
	{
		if (!node->m_thing)
		{
			if (node == sector_list)
				sector_list = node->m_sectorlist_next;
			node = P_DelSecnode(node);
		}
		else
			node = node->m_sectorlist_next;
	}

	/* cph -
	* This is the strife we get into for using global variables. tmthing
	*  is being used by several different functions calling
	*  P_BlockThingIterator, including functions that can be called *from*
	*  P_BlockThingIterator. Using a global tmthing is not reentrant.
	* OTOH for Boom/MBF demos we have to preserve the buggy behavior.
	*  Fun. We restore its previous value unless we're in a Boom/MBF demo.
	*/
	P_SetTarget(&tmthing, saved_tmthing);

	/* And, duh, the same for tmx/y - cph 2002/09/22
	* And for tmbbox - cph 2003/08/10 */
	tmx = saved_tmx, tmy = saved_tmy;

	if (tmthing)
	{
		tmbbox[BOXTOP]  = tmy + tmthing->radius;
		tmbbox[BOXBOTTOM] = tmy - tmthing->radius;
		tmbbox[BOXRIGHT]  = tmx + tmthing->radius;
		tmbbox[BOXLEFT]   = tmx - tmthing->radius;
	}
}

// More crazy crap Tails 08-25-2002
void P_CreatePrecipSecNodeList(precipmobj_t *thing,fixed_t x,fixed_t y)
{
	INT32 xl, xh, yl, yh, bx, by;
	mprecipsecnode_t *node = precipsector_list;
	precipmobj_t *saved_tmthing = tmprecipthing; /* cph - see comment at func end */

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

	while (node)
	{
		node->m_thing = NULL;
		node = node->m_sectorlist_next;
	}

	tmprecipthing = thing;

	preciptmbbox[BOXTOP] = y + 2*FRACUNIT;
	preciptmbbox[BOXBOTTOM] = y - 2*FRACUNIT;
	preciptmbbox[BOXRIGHT] = x + 2*FRACUNIT;
	preciptmbbox[BOXLEFT] = x - 2*FRACUNIT;

	validcount++; // used to make sure we only process a line once

	xl = (unsigned)(preciptmbbox[BOXLEFT] - bmaporgx)>>MAPBLOCKSHIFT;
	xh = (unsigned)(preciptmbbox[BOXRIGHT] - bmaporgx)>>MAPBLOCKSHIFT;
	yl = (unsigned)(preciptmbbox[BOXBOTTOM] - bmaporgy)>>MAPBLOCKSHIFT;
	yh = (unsigned)(preciptmbbox[BOXTOP] - bmaporgy)>>MAPBLOCKSHIFT;

	BMBOUNDFIX(xl, xh, yl, yh);

	for (bx = xl; bx <= xh; bx++)
		for (by = yl; by <= yh; by++)
			P_BlockLinesIterator(bx, by, PIT_GetPrecipSectors);

	// Add the sector of the (x, y) point to sector_list.
	precipsector_list = P_AddPrecipSecnode(thing->subsector->sector, thing, precipsector_list);

	// Now delete any nodes that won't be used. These are the ones where
	// m_thing is still NULL.
	node = precipsector_list;
	while (node)
	{
		if (!node->m_thing)
		{
			if (node == precipsector_list)
				precipsector_list = node->m_sectorlist_next;
			node = P_DelPrecipSecnode(node);
		}
		else
			node = node->m_sectorlist_next;
	}

	/* cph -
	* This is the strife we get into for using global variables. tmthing
	*  is being used by several different functions calling
	*  P_BlockThingIterator, including functions that can be called *from*
	*  P_BlockThingIterator. Using a global tmthing is not reentrant.
	* OTOH for Boom/MBF demos we have to preserve the buggy behavior.
	*  Fun. We restore its previous value unless we're in a Boom/MBF demo.
	*/
	tmprecipthing = saved_tmthing;
}

/* cphipps 2004/08/30 -
 * Must clear tmthing at tic end, as it might contain a pointer to a removed thinker, or the level might have ended/been ended and we clear the objects it was pointing too. Hopefully we don't need to carry this between tics for sync. */
void P_MapStart(void)
{
	if (tmthing)
		I_Error("P_MapStart: tmthing set!");
}

void P_MapEnd(void)
{
	P_SetTarget(&tmthing, NULL);
}

// P_FloorzAtPos
// Returns the floorz of the XYZ position
// Tails 05-26-2003
fixed_t P_FloorzAtPos(fixed_t x, fixed_t y, fixed_t z, fixed_t height)
{
	sector_t *sec = R_PointInSubsector(x, y)->sector;
	fixed_t floorz = P_GetSectorFloorZAt(sec, x, y);

	// Intercept the stupid 'fall through 3dfloors' bug Tails 03-17-2002
	if (sec->ffloors)
	{
		ffloor_t *rover;
		fixed_t delta1, delta2, thingtop = z + height;

		for (rover = sec->ffloors; rover; rover = rover->next)
		{
			fixed_t topheight, bottomheight;
			if (!(rover->fofflags & FOF_EXISTS))
				continue;

			if ((!(rover->fofflags & FOF_SOLID || rover->fofflags & FOF_QUICKSAND) || (rover->fofflags & FOF_SWIMMABLE)))
				continue;

			topheight    = P_GetFFloorTopZAt   (rover, x, y);
			bottomheight = P_GetFFloorBottomZAt(rover, x, y);

			if (rover->fofflags & FOF_QUICKSAND)
			{
				if (z < topheight && bottomheight < thingtop)
				{
					if (floorz < z)
						floorz = z;
				}
				continue;
			}

			delta1 = z - (bottomheight + ((topheight - bottomheight)/2));
			delta2 = thingtop - (bottomheight + ((topheight - bottomheight)/2));
			if (topheight > floorz && abs(delta1) < abs(delta2))
				floorz = topheight;
		}
	}

	return floorz;
}

// P_CeilingZAtPos
// Returns the ceilingz of the XYZ position
fixed_t P_CeilingzAtPos(fixed_t x, fixed_t y, fixed_t z, fixed_t height)
{
	sector_t *sec = R_PointInSubsector(x, y)->sector;
	fixed_t ceilingz = P_GetSectorCeilingZAt(sec, x, y);

	if (sec->ffloors)
	{
		ffloor_t *rover;
		fixed_t delta1, delta2, thingtop = z + height;

		for (rover = sec->ffloors; rover; rover = rover->next)
		{
			fixed_t topheight, bottomheight;
			if (!(rover->fofflags & FOF_EXISTS))
				continue;

			if ((!(rover->fofflags & FOF_SOLID || rover->fofflags & FOF_QUICKSAND) || (rover->fofflags & FOF_SWIMMABLE)))
				continue;

			topheight    = P_GetFFloorTopZAt   (rover, x, y);
			bottomheight = P_GetFFloorBottomZAt(rover, x, y);

			if (rover->fofflags & FOF_QUICKSAND)
			{
				if (thingtop > bottomheight && topheight > z)
				{
					if (ceilingz > thingtop)
						ceilingz = thingtop;
				}
				continue;
			}

			delta1 = z - (bottomheight + ((topheight - bottomheight)/2));
			delta2 = thingtop - (bottomheight + ((topheight - bottomheight)/2));
			if (bottomheight < ceilingz && abs(delta1) > abs(delta2))
				ceilingz = bottomheight;
		}
	}

	return ceilingz;
}

INT32 P_GetSectorLightNumAt(sector_t *sector, fixed_t x, fixed_t y, fixed_t z)
{
	if (!sector->numlights)
		return -1;

	INT32 light = sector->numlights - 1;

	// R_GetPlaneLight won't work on sloped lights!
	for (INT32 lightnum = 1; lightnum < sector->numlights; lightnum++) {
		fixed_t h = P_GetLightZAt(&sector->lightlist[lightnum], x, y);
		if (h <= z) {
			light = lightnum - 1;
			break;
		}
	}

	return light;
}

INT32 P_GetLightLevelFromSectorAt(sector_t *sector, fixed_t x, fixed_t y, fixed_t z)
{
	if (sector->numlights)
		return *sector->lightlist[P_GetSectorLightNumAt(sector, x, y, z)].lightlevel;
	else
		return sector->lightlevel;
}

INT32 P_GetSectorLightLevelAt(fixed_t x, fixed_t y, fixed_t z)
{
	return P_GetLightLevelFromSectorAt(R_PointInSubsector(x, y)->sector, x, y, z);
}

extracolormap_t *P_GetColormapFromSectorAt(sector_t *sector, fixed_t x, fixed_t y, fixed_t z)
{
	if (sector->numlights)
		return *sector->lightlist[P_GetSectorLightNumAt(sector, x, y, z)].extra_colormap;
	else
		return sector->extra_colormap;
}

extracolormap_t *P_GetSectorColormapAt(fixed_t x, fixed_t y, fixed_t z)
{
	return P_GetColormapFromSectorAt(R_PointInSubsector(x, y)->sector, x, y, z);
}