// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2024 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_maputl.c
/// \brief Movement/collision utility functions, as used by functions in p_map.c
///        Blockmap iterator functions, and some PIT_* functions to use for iteration

#include "doomdef.h"
#include "doomstat.h"

#include "p_local.h"
#include "r_main.h"
#include "r_data.h"
#include "r_textures.h"
#include "p_maputl.h"
#include "p_polyobj.h"
#include "p_slopes.h"
#include "z_zone.h"

//
// P_AproxDistance
// Gives an estimation of distance (not exact)
//
fixed_t P_AproxDistance(fixed_t dx, fixed_t dy)
{
	dx = abs(dx);
	dy = abs(dy);
	if (dx < dy)
		return dx + dy - (dx>>1);
	return dx + dy - (dy>>1);
}

//
// P_ClosestPointOnLine
// Finds the closest point on a given line to the supplied point
//
void P_ClosestPointOnLine(fixed_t x, fixed_t y, line_t *line, vertex_t *result)
{
	fixed_t startx = line->v1->x;
	fixed_t starty = line->v1->y;
	fixed_t dx = line->dx;
	fixed_t dy = line->dy;

	// Determine t (the length of the vector from �Line[0]� to �p�)
	fixed_t cx, cy;
	fixed_t vx, vy;
	fixed_t magnitude;
	fixed_t t;

	//Sub (p, &Line[0], &c);
	cx = x - startx;
	cy = y - starty;

	//Sub (&Line[1], &Line[0], &V);
	vx = dx;
	vy = dy;

	//Normalize (&V, &V);
	magnitude = R_PointToDist2(line->v2->x, line->v2->y, startx, starty);
	vx = FixedDiv(vx, magnitude);
	vy = FixedDiv(vy, magnitude);

	t = (FixedMul(vx, cx) + FixedMul(vy, cy));

	// Return the point between �Line[0]� and �Line[1]�
	vx = FixedMul(vx, t);
	vy = FixedMul(vy, t);

	//Add (&Line[0], &V, out);
	result->x = startx + vx;
	result->y = starty + vy;
	return;
}

/// Similar to FV3_ClosestPointOnLine() except it actually works.
void P_ClosestPointOnLine3D(const vector3_t *p, const vector3_t *Line, vector3_t *result)
{
	const vector3_t* v1 = &Line[0];
	const vector3_t* v2 = &Line[1];
	vector3_t c, V, n;
	fixed_t t, d;
	FV3_SubEx(v2, v1, &V);
	FV3_SubEx(p, v1, &c);

	d = R_PointToDist2(0, v2->z, R_PointToDist2(v2->x, v2->y, v1->x, v1->y), v1->z);
	FV3_Copy(&n, &V);
	FV3_Divide(&n, d);

	t = FV3_Dot(&n, &c);

	// Set closest point to the end if it extends past -Red
	if (t <= 0)
	{
		FV3_Copy(result, v1);
		return;
	}
	else if (t >= d)
	{
		FV3_Copy(result, v2);
		return;
	}

	FV3_Mul(&n, t);

	FV3_AddEx(v1, &n, result);
	return;
}

//
// P_PointOnLineSide
// Returns 0 or 1
//
INT32 P_PointOnLineSide(fixed_t x, fixed_t y, line_t *line)
{
	const vertex_t *v1 = line->v1;
	fixed_t dx, dy, left, right;

	if (!line->dx)
	{
		if (x <= v1->x)
			return (line->dy > 0);

		return (line->dy < 0);
	}
	if (!line->dy)
	{
		if (y <= v1->y)
			return (line->dx < 0);

		return (line->dx > 0);
	}

	dx = (x - v1->x);
	dy = (y - v1->y);

	left = FixedMul(line->dy>>FRACBITS, dx);
	right = FixedMul(dy, line->dx>>FRACBITS);

	if (right < left)
		return 0; // front side
	return 1; // back side
}

//
// P_BoxOnLineSide
// Considers the line to be infinite
// Returns side 0 or 1, -1 if box crosses the line.
//
INT32 P_BoxOnLineSide(fixed_t *tmbox, line_t *ld)
{
	INT32 p1, p2;

	switch (ld->slopetype)
	{
		case ST_HORIZONTAL:
			p1 = tmbox[BOXTOP] > ld->v1->y;
			p2 = tmbox[BOXBOTTOM] > ld->v1->y;
			if (ld->dx < 0)
			{
				p1 ^= 1;
				p2 ^= 1;
			}
			break;

		case ST_VERTICAL:
			p1 = tmbox[BOXRIGHT] < ld->v1->x;
			p2 = tmbox[BOXLEFT] < ld->v1->x;
			if (ld->dy < 0)
			{
				p1 ^= 1;
				p2 ^= 1;
			}
			break;

		case ST_POSITIVE:
			p1 = P_PointOnLineSide(tmbox[BOXLEFT], tmbox[BOXTOP], ld);
			p2 = P_PointOnLineSide(tmbox[BOXRIGHT], tmbox[BOXBOTTOM], ld);
			break;

		case ST_NEGATIVE:
			p1 = P_PointOnLineSide(tmbox[BOXRIGHT], tmbox[BOXTOP], ld);
			p2 = P_PointOnLineSide(tmbox[BOXLEFT], tmbox[BOXBOTTOM], ld);
			break;

		default:
			I_Error("P_BoxOnLineSide: unknown slopetype %d\n", ld->slopetype);
			return -1;
	}

	if (p1 == p2)
		return p1;
	return -1;
}

//
// P_PointOnDivlineSide
// Returns 0 or 1.
//
static INT32 P_PointOnDivlineSide(fixed_t x, fixed_t y, divline_t *line)
{
	fixed_t dx, dy, left, right;

	if (!line->dx)
	{
		if (x <= line->x)
			return line->dy > 0;

		return line->dy < 0;
	}
	if (!line->dy)
	{
		if (y <= line->y)
			return line->dx < 0;

		return line->dx > 0;
	}

	dx = (x - line->x);
	dy = (y - line->y);

	// try to quickly decide by looking at sign bits
	if ((line->dy ^ line->dx ^ dx ^ dy) & 0x80000000)
	{
		if ((line->dy ^ dx) & 0x80000000)
			return 1; // left is negative
		return 0;
	}

	left = FixedMul(line->dy>>8, dx>>8);
	right = FixedMul(dy>>8, line->dx>>8);

	if (right < left)
		return 0; // front side
	return 1; // back side
}

//
// P_MakeDivline
//
void P_MakeDivline(line_t *li, divline_t *dl)
{
	dl->x = li->v1->x;
	dl->y = li->v1->y;
	dl->dx = li->dx;
	dl->dy = li->dy;
}

//
// P_InterceptVector
// Returns the fractional intercept point along the first divline.
// This is only called by the addthings and addlines traversers.
//
fixed_t P_InterceptVector(divline_t *v2, divline_t *v1)
{
	fixed_t frac, num, den;

	den = FixedMul(v1->dy>>8, v2->dx) - FixedMul(v1->dx>>8, v2->dy);

	if (!den)
		return 0;

	num = FixedMul((v1->x - v2->x)>>8, v1->dy) + FixedMul((v2->y - v1->y)>>8, v1->dx);
	frac = FixedDiv(num, den);

	return frac;
}

//
// P_LineOpening
// Sets opentop and openbottom to the window through a two sided line.
// OPTIMIZE: keep this precalculated
//
fixed_t opentop, openbottom, openrange, lowfloor, highceiling;
pslope_t *opentopslope, *openbottomslope;
ffloor_t *openfloorrover, *openceilingrover;

// P_CameraLineOpening
// P_LineOpening, but for camera
// Tails 09-29-2002
void P_CameraLineOpening(line_t *linedef)
{
	sector_t *front;
	sector_t *back;
	fixed_t frontfloor, frontceiling, backfloor, backceiling;

	if (linedef->sidenum[1] == NO_SIDEDEF)
	{
		// single sided line
		openrange = 0;
		return;
	}

	front = linedef->frontsector;
	back = linedef->backsector;

	// 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 (front->camsec >= 0)
	{
		// SRB2CBTODO: ESLOPE (sectors[front->heightsec].f_slope)
		frontfloor   = P_GetSectorFloorZAt  (&sectors[front->camsec], camera.x, camera.y);
		frontceiling = P_GetSectorCeilingZAt(&sectors[front->camsec], camera.x, camera.y);

	}
	else if (front->heightsec >= 0)
	{
		// SRB2CBTODO: ESLOPE (sectors[front->heightsec].f_slope)
		frontfloor   = P_GetSectorFloorZAt  (&sectors[front->heightsec], camera.x, camera.y);
		frontceiling = P_GetSectorCeilingZAt(&sectors[front->heightsec], camera.x, camera.y);
	}
	else
	{
		frontfloor   = P_CameraGetFloorZ  (mapcampointer, front, tmx, tmy, linedef);
		frontceiling = P_CameraGetCeilingZ(mapcampointer, front, tmx, tmy, linedef);
	}
	if (back->camsec >= 0)
	{
		// SRB2CBTODO: ESLOPE (sectors[back->heightsec].f_slope)
		backfloor   = P_GetSectorFloorZAt  (&sectors[back->camsec], camera.x, camera.y);
		backceiling = P_GetSectorCeilingZAt(&sectors[back->camsec], camera.x, camera.y);
	}
	else if (back->heightsec >= 0)
	{
		// SRB2CBTODO: ESLOPE (sectors[back->heightsec].f_slope)
		backfloor   = P_GetSectorFloorZAt  (&sectors[back->heightsec], camera.x, camera.y);
		backceiling = P_GetSectorCeilingZAt(&sectors[back->heightsec], camera.x, camera.y);
	}
	else
	{
		backfloor = P_CameraGetFloorZ(mapcampointer, back, tmx, tmy, linedef);
		backceiling = P_CameraGetCeilingZ(mapcampointer, back, tmx, tmy, linedef);
	}

	{
		fixed_t thingtop = mapcampointer->z + mapcampointer->height;

		if (frontceiling < backceiling)
		{
			opentop = frontceiling;
			highceiling = backceiling;
		}
		else
		{
			opentop = backceiling;
			highceiling = frontceiling;
		}

		if (frontfloor > backfloor)
		{
			openbottom = frontfloor;
			lowfloor = backfloor;
		}
		else
		{
			openbottom = backfloor;
			lowfloor = frontfloor;
		}

		// Check for fake floors in the sector.
		if (front->ffloors || back->ffloors)
		{
			ffloor_t *rover;
			fixed_t delta1, delta2;

			// Check for frontsector's fake floors
			if (front->ffloors)
				for (rover = front->ffloors; rover; rover = rover->next)
				{
					fixed_t topheight, bottomheight;
					if (!(rover->fofflags & FOF_BLOCKOTHERS) || !(rover->fofflags & FOF_RENDERALL) || !(rover->fofflags & FOF_EXISTS) || (rover->master->frontsector->flags & MSF_NOCLIPCAMERA))
						continue;

					topheight = P_CameraGetFOFTopZ(mapcampointer, front, rover, tmx, tmy, linedef);
					bottomheight = P_CameraGetFOFBottomZ(mapcampointer, front, rover, tmx, tmy, linedef);

					delta1 = abs(mapcampointer->z - (bottomheight + ((topheight - bottomheight)/2)));
					delta2 = abs(thingtop - (bottomheight + ((topheight - bottomheight)/2)));
					if (bottomheight < opentop && delta1 >= delta2)
						opentop = bottomheight;
					else if (bottomheight < highceiling && delta1 >= delta2)
						highceiling = bottomheight;

					if (topheight > openbottom && delta1 < delta2)
						openbottom = topheight;
					else if (topheight > lowfloor && delta1 < delta2)
						lowfloor = topheight;
				}

			// Check for backsectors fake floors
			if (back->ffloors)
				for (rover = back->ffloors; rover; rover = rover->next)
				{
					fixed_t topheight, bottomheight;
					if (!(rover->fofflags & FOF_BLOCKOTHERS) || !(rover->fofflags & FOF_RENDERALL) || !(rover->fofflags & FOF_EXISTS) || (rover->master->frontsector->flags & MSF_NOCLIPCAMERA))
						continue;

					topheight = P_CameraGetFOFTopZ(mapcampointer, back, rover, tmx, tmy, linedef);
					bottomheight = P_CameraGetFOFBottomZ(mapcampointer, back, rover, tmx, tmy, linedef);

					delta1 = abs(mapcampointer->z - (bottomheight + ((topheight - bottomheight)/2)));
					delta2 = abs(thingtop - (bottomheight + ((topheight - bottomheight)/2)));
					if (bottomheight < opentop && delta1 >= delta2)
						opentop = bottomheight;
					else if (bottomheight < highceiling && delta1 >= delta2)
						highceiling = bottomheight;

					if (topheight > openbottom && delta1 < delta2)
						openbottom = topheight;
					else if (topheight > lowfloor && delta1 < delta2)
						lowfloor = topheight;
				}
		}
		openrange = opentop - openbottom;
		return;
	}
}

void P_LineOpening(line_t *linedef, mobj_t *mobj)
{
	sector_t *front, *back;

	if (linedef->sidenum[1] == NO_SIDEDEF)
	{
		// single sided line
		openrange = 0;
		return;
	}

	front = linedef->frontsector;
	back = linedef->backsector;

	I_Assert(front != NULL);
	I_Assert(back != NULL);

	openfloorrover = openceilingrover = NULL;
	if (linedef->polyobj)
	{
		// set these defaults so that polyobjects don't interfere with collision above or below them
		opentop = INT32_MAX;
		openbottom = INT32_MIN;
		highceiling = INT32_MIN;
		lowfloor = INT32_MAX;
		opentopslope = openbottomslope = NULL;
	}
	else
	{ // Set open and high/low values here
		fixed_t frontheight, backheight;

		frontheight = P_GetCeilingZ(mobj, front, tmx, tmy, linedef);
		backheight = P_GetCeilingZ(mobj, back, tmx, tmy, linedef);

		if (frontheight < backheight)
		{
			opentop = frontheight;
			highceiling = backheight;
			opentopslope = front->c_slope;
		}
		else
		{
			opentop = backheight;
			highceiling = frontheight;
			opentopslope = back->c_slope;
		}

		frontheight = P_GetFloorZ(mobj, front, tmx, tmy, linedef);
		backheight = P_GetFloorZ(mobj, back, tmx, tmy, linedef);

		if (frontheight > backheight)
		{
			openbottom = frontheight;
			lowfloor = backheight;
			openbottomslope = front->f_slope;
		}
		else
		{
			openbottom = backheight;
			lowfloor = frontheight;
			openbottomslope = back->f_slope;
		}
	}

	if (mobj)
	{
		fixed_t thingtop = mobj->z + mobj->height;

		// Check for collision with front side's midtexture if Effect 4 is set
		if (linedef->flags & ML_MIDSOLID
			&& !linedef->polyobj // don't do anything for polyobjects! ...for now
			) {
			side_t *side = &sides[linedef->sidenum[0]];
			fixed_t textop, texbottom, texheight;
			fixed_t texmid, delta1, delta2;
			INT32 texnum = R_GetTextureNum(side->midtexture); // make sure the texture is actually valid

			if (texnum) {
				fixed_t scaley = abs(side->scaley_mid);
				fixed_t offsetvalue = FixedDiv(side->rowoffset + side->offsety_mid, scaley);
				fixed_t midopentop, midopenbottom;

				if (linedef->flags & ML_NOSKEW)
				{
					// Use the sector's actual heights if the midtexture is not skewed
					midopentop = min(front->ceilingheight, back->ceilingheight);
					midopenbottom = max(front->floorheight, back->floorheight);
				}
				else
				{
					midopentop = opentop;
					midopenbottom = openbottom;
				}

				// Get the midtexture's height
				texheight = FixedDiv(textureheight[texnum], scaley);

				// Set texbottom and textop to the Z coordinates of the texture's boundaries
#if 0
				// don't remove this code unless solid midtextures
				// on non-solid polyobjects should NEVER happen in the future
				if (linedef->polyobj && (linedef->polyobj->flags & POF_TESTHEIGHT)) {
					if (linedef->flags & ML_WRAPMIDTEX && !side->repeatcnt) { // "infinite" repeat
						texbottom = back->floorheight + offsetvalue;
						textop = back->ceilingheight + offsetvalue;
					} else if (linedef->flags & ML_MIDTEX) {
						texbottom = back->floorheight + offsetvalue;
						textop = texbottom + texheight*(side->repeatcnt+1);
					} else {
						textop = back->ceilingheight + offsetvalue;
						texbottom = textop - texheight*(side->repeatcnt+1);
					}
				} else
#endif
				{
					if (linedef->flags & ML_WRAPMIDTEX && !side->repeatcnt) { // "infinite" repeat
						texbottom = midopenbottom + offsetvalue;
						textop = midopentop + offsetvalue;
					} else if (linedef->flags & ML_MIDPEG) {
						texbottom = midopenbottom + offsetvalue;
						textop = texbottom + texheight*(side->repeatcnt+1);
					} else {
						textop = midopentop + offsetvalue;
						texbottom = textop - texheight*(side->repeatcnt+1);
					}
				}

				texmid = texbottom+(textop-texbottom)/2;

				delta1 = abs(mobj->z - texmid);
				delta2 = abs(thingtop - texmid);

				if (delta1 > delta2) { // Below
					if (opentop > texbottom) {
						opentop = texbottom;
						if (linedef->flags & ML_NOSKEW)
							opentopslope = NULL; // Object is not actually on a slope
						else
							opentopslope = linedef->midtexslope;
					}
				} else { // Above
					if (openbottom < textop) {
						openbottom = textop;
						if (linedef->flags & ML_NOSKEW)
							openbottomslope = NULL; // Object is not actually on a slope
						else
							openbottomslope = linedef->midtexslope;
					}
				}
			}
		}
		if (linedef->polyobj)
		{
			// Treat polyobj's backsector like a 3D Floor
			if (linedef->polyobj->flags & POF_TESTHEIGHT)
			{
				const sector_t *polysec = linedef->backsector;
				fixed_t polytop, polybottom;
				fixed_t delta1, delta2;

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

				delta1 = abs(mobj->z - (polybottom + ((polytop - polybottom)/2)));
				delta2 = abs(thingtop - (polybottom + ((polytop - polybottom)/2)));

				if (polybottom < opentop && delta1 >= delta2)
					opentop = polybottom;
				else if (polybottom < highceiling && delta1 >= delta2)
					highceiling = polybottom;

				if (polytop > openbottom && delta1 < delta2)
					openbottom = polytop;
				else if (polytop > lowfloor && delta1 < delta2)
					lowfloor = polytop;
			}
			// otherwise don't do anything special, pretend there's nothing else there
		}
		else
		{
			// Check for fake floors in the sector.
			if (front->ffloors || back->ffloors)
			{
				ffloor_t *rover;
				fixed_t delta1, delta2;

				// Check for frontsector's fake floors
				for (rover = front->ffloors; rover; rover = rover->next)
				{
					fixed_t topheight, bottomheight;
					if (!(rover->fofflags & FOF_EXISTS))
						continue;

					if (mobj->player && (P_CheckSolidLava(rover) || P_CanRunOnWater(mobj->player, rover)))
						;
					else if (!((rover->fofflags & FOF_BLOCKPLAYER && mobj->player)
						|| (rover->fofflags & FOF_BLOCKOTHERS && !mobj->player)))
						continue;

					topheight = P_GetFOFTopZ(mobj, front, rover, tmx, tmy, linedef);
					bottomheight = P_GetFOFBottomZ(mobj, front, rover, tmx, tmy, linedef);

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

					if (delta1 >= delta2 && (rover->fofflags & FOF_INTANGIBLEFLATS) != FOF_PLATFORM) // thing is below FOF
					{
						if (bottomheight < opentop) {
							opentop = bottomheight;
							opentopslope = *rover->b_slope;
							openceilingrover = rover;
						}
						else if (bottomheight < highceiling)
							highceiling = bottomheight;
					}

					if (delta1 < delta2 && (rover->fofflags & FOF_INTANGIBLEFLATS) != FOF_REVERSEPLATFORM) // thing is above FOF
					{
						if (topheight > openbottom) {
							openbottom = topheight;
							openbottomslope = *rover->t_slope;
							openfloorrover = rover;
						}
						else if (topheight > lowfloor)
							lowfloor = topheight;
					}
				}

				// Check for backsectors fake floors
				for (rover = back->ffloors; rover; rover = rover->next)
				{
					fixed_t topheight, bottomheight;
					if (!(rover->fofflags & FOF_EXISTS))
						continue;

					if (mobj->player && (P_CheckSolidLava(rover) || P_CanRunOnWater(mobj->player, rover)))
						;
					else if (!((rover->fofflags & FOF_BLOCKPLAYER && mobj->player)
						|| (rover->fofflags & FOF_BLOCKOTHERS && !mobj->player)))
						continue;

					topheight = P_GetFOFTopZ(mobj, back, rover, tmx, tmy, linedef);
					bottomheight = P_GetFOFBottomZ(mobj, back, rover, tmx, tmy, linedef);

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

					if (delta1 >= delta2 && (rover->fofflags & FOF_INTANGIBLEFLATS) != FOF_PLATFORM) // thing is below FOF
					{
						if (bottomheight < opentop) {
							opentop = bottomheight;
							opentopslope = *rover->b_slope;
							openceilingrover = rover;
						}
						else if (bottomheight < highceiling)
							highceiling = bottomheight;
					}

					if (delta1 < delta2 && (rover->fofflags & FOF_INTANGIBLEFLATS) != FOF_REVERSEPLATFORM) // thing is above FOF
					{
						if (topheight > openbottom) {
							openbottom = topheight;
							openbottomslope = *rover->t_slope;
							openfloorrover = rover;
						}
						else if (topheight > lowfloor)
							lowfloor = topheight;
					}
				}
			}
		}
	}

	openrange = opentop - openbottom;
}

static blocknode_t *freeblocks;

static blocknode_t *P_CreateBlockNode(mobj_t *thing, int x, int y)
{
	blocknode_t *block;

	if (freeblocks != NULL)
	{
		block = freeblocks;
		freeblocks = block->bnext;
	}
	else
		block = Z_Malloc(sizeof(blocknode_t), PU_LEVEL, NULL);

	block->blockindex = x + y*bmapwidth;
	block->mobj = thing;
	block->mnext = NULL;
	block->mprev = NULL;
	block->bprev = NULL;
	block->bnext = NULL;

	return block;
}

static void P_ReleaseBlockNode(blocknode_t *node)
{
	node->bnext = freeblocks;
	freeblocks = node;
}

//
// THING POSITION SETTING
//

//
// P_UnsetThingPosition
// Unlinks a thing from block map and sectors.
// On each position change, BLOCKMAP and other
// lookups maintaining lists ot things inside
// these structures need to be updated.
//
void P_UnsetThingPosition(mobj_t *thing)
{
	I_Assert(thing != NULL);
	I_Assert(!P_MobjWasRemoved(thing));

	if (!(thing->flags & MF_NOSECTOR))
	{
		/* invisible things don't need to be in sector list
		* unlink from subsector
		*
		* killough 8/11/98: simpler scheme using pointers-to-pointers for prev
		* pointers, allows head node pointers to be treated like everything else
		*/

		mobj_t **sprev = thing->sprev;
		mobj_t  *snext = thing->snext;
		if ((*sprev = snext) != NULL)  // unlink from sector list
			snext->sprev = sprev;

		// phares 3/14/98
		//
		// Save the sector list pointed to by touching_sectorlist.
		// In P_SetThingPosition, we'll keep any nodes that represent
		// sectors the Thing still touches. We'll add new ones then, and
		// delete any nodes for sectors the Thing has vacated. Then we'll
		// put it back into touching_sectorlist. It's done this way to
		// avoid a lot of deleting/creating for nodes, when most of the
		// time you just get back what you deleted anyway.
		//
		// If this Thing is being removed entirely, then the calling
		// routine will clear out the nodes in sector_list.

		sector_list = thing->touching_sectorlist;
		thing->touching_sectorlist = NULL; //to be restored by P_SetThingPosition
	}

	if (!(thing->flags & MF_NOBLOCKMAP))
	{
		// [RH] Unlink from all blocks this actor uses
		blocknode_t *block = thing->blocknode;

		while (block != NULL)
		{
			if (block->mnext != NULL)
				block->mnext->mprev = block->mprev;
			*(block->mprev) = block->mnext;
			blocknode_t *next = block->bnext;
			P_ReleaseBlockNode(block);
			block = next;
		}

		thing->blocknode = NULL;
	}
}

void P_UnsetPrecipThingPosition(precipmobj_t *thing)
{
	precipmobj_t **sprev = thing->sprev;
	precipmobj_t  *snext = thing->snext;
	if ((*sprev = snext) != NULL)  // unlink from sector list
		snext->sprev = sprev;

	precipsector_list = thing->touching_sectorlist;
	thing->touching_sectorlist = NULL; //to be restored by P_SetPrecipThingPosition
}

//
// P_SetThingPosition
// Links a thing into both a block and a subsector
// based on it's x y.
// Sets thing->subsector properly
//
void P_SetThingPosition(mobj_t *thing)
{                                                      // link into subsector
	subsector_t *ss;
	sector_t *oldsec = NULL;
	fixed_t tfloorz, tceilz;

	I_Assert(thing != NULL);
	I_Assert(!P_MobjWasRemoved(thing));

	if (thing->player && thing->z <= thing->floorz && thing->subsector)
		oldsec = thing->subsector->sector;

	ss = thing->subsector = R_PointInSubsector(thing->x, thing->y);

	if (!(thing->flags & MF_NOSECTOR))
	{
		// invisible things don't go into the sector links

		// killough 8/11/98: simpler scheme using pointer-to-pointer prev
		// pointers, allows head nodes to be treated like everything else

		mobj_t **link = &ss->sector->thinglist;
		mobj_t *snext = *link;
		if ((thing->snext = snext) != NULL)
			snext->sprev = &thing->snext;
		thing->sprev = link;
		*link = thing;

		// phares 3/16/98
		//
		// If sector_list isn't NULL, it has a collection of sector
		// nodes that were just removed from this Thing.

		// Collect the sectors the object will live in by looking at
		// the existing sector_list and adding new nodes and deleting
		// obsolete ones.

		// When a node is deleted, its sector links (the links starting
		// at sector_t->touching_thinglist) are broken. When a node is
		// added, new sector links are created.

		P_CreateSecNodeList(thing,thing->x,thing->y);
		thing->touching_sectorlist = sector_list; // Attach to Thing's mobj_t
		sector_list = NULL; // clear for next time
	}

	// link into blockmap
	if (!(thing->flags & MF_NOBLOCKMAP))
	{
		// inert things don't need to be in blockmap
		INT32 x1 = (unsigned)(thing->x - thing->radius - bmaporgx)>>MAPBLOCKSHIFT;
		INT32 y1 = (unsigned)(thing->y - thing->radius - bmaporgy)>>MAPBLOCKSHIFT;
		INT32 x2 = (unsigned)(thing->x + thing->radius - bmaporgx)>>MAPBLOCKSHIFT;
		INT32 y2 = (unsigned)(thing->y + thing->radius - bmaporgy)>>MAPBLOCKSHIFT;

		thing->blocknode = NULL;

		blocknode_t **alink = &thing->blocknode;

		if (!(x1 >= bmapwidth || x2 < 0 || y1 >= bmapheight || y2 < 0))
		{
			// [RH] Link into every block this actor touches, not just the center one
			x1 = max(0, x1);
			y1 = max(0, y1);
			x2 = min(bmapwidth - 1, x2);
			y2 = min(bmapheight - 1, y2);
			for (int y = y1; y <= y2; ++y)
			{
				for (int x = x1; x <= x2; ++x)
				{
					blocknode_t **link = &blocklinks[y*bmapwidth + x];
					blocknode_t *node = P_CreateBlockNode(thing, x, y);

					// Link in to block
					if ((node->mnext = *link) != NULL)
					{
						(*link)->mprev = &node->mnext;
					}
					node->mprev = link;
					*link = node;

					// Link in to actor
					node->bprev = alink;
					node->bnext = NULL;
					(*alink) = node;
					alink = &node->bnext;
				}
			}
		}
	}

	// Allows you to 'step' on a new linedef exec when the previous
	// sector's floor is the same height.
	if (thing->player && oldsec != NULL && thing->subsector && oldsec != thing->subsector->sector)
	{
		tfloorz = P_GetFloorZ(thing, ss->sector, thing->x, thing->y, NULL);
		tceilz = P_GetCeilingZ(thing, ss->sector, thing->x, thing->y, NULL);

		if (thing->eflags & MFE_VERTICALFLIP)
		{
			if (thing->z + thing->height >= tceilz)
				thing->eflags |= MFE_JUSTSTEPPEDDOWN;
		}
		else if (thing->z <= tfloorz)
			thing->eflags |= MFE_JUSTSTEPPEDDOWN;
	}
}

//
// P_SetUnderlayPosition
// Links a thing into a subsector at the other end of the stack,
// so it appears behind all other sprites in that subsector.
// Sets thing->subsector properly
//
void P_SetUnderlayPosition(mobj_t *thing)
{                                                      // link into subsector
	subsector_t *ss;
	mobj_t **link, *lend;
	I_Assert(thing);

	ss = thing->subsector = R_PointInSubsector(thing->x, thing->y);
	link = &ss->sector->thinglist;
	for (lend = *link; lend && lend->snext; lend = lend->snext)
		;
	thing->snext = NULL;
	if (!lend)
	{
		thing->sprev = link;
		*link = thing;
	}
	else
	{
		thing->sprev = &lend->snext;
		lend->snext = thing;
	}

	P_CreateSecNodeList(thing,thing->x,thing->y);
	thing->touching_sectorlist = sector_list; // Attach to Thing's mobj_t
	sector_list = NULL; // clear for next time
}

void P_SetPrecipitationThingPosition(precipmobj_t *thing)
{
	subsector_t *ss = thing->subsector = R_PointInSubsector(thing->x, thing->y);

	precipmobj_t **link = &ss->sector->preciplist;
	precipmobj_t *snext = *link;
	if ((thing->snext = snext) != NULL)
		snext->sprev = &thing->snext;
	thing->sprev = link;
	*link = thing;

	P_CreatePrecipSecNodeList(thing, thing->x, thing->y);
	thing->touching_sectorlist = precipsector_list; // Attach to Thing's precipmobj_t
	precipsector_list = NULL; // clear for next time
}

//
// BLOCK MAP ITERATORS
// For each line/thing in the given mapblock,
// call the passed PIT_* function.
// If the function returns false,
// exit with false without checking anything else.
//


//
// P_BlockLinesIterator
// The validcount flags are used to avoid checking lines
// that are marked in multiple mapblocks,
// so increment validcount before the first call
// to P_BlockLinesIterator, then make one or more calls
// to it.
//
boolean P_BlockLinesIterator(INT32 x, INT32 y, boolean (*func)(line_t *))
{
	INT32 offset;
	const INT32 *list; // Big blockmap
	polymaplink_t *plink; // haleyjd 02/22/06
	line_t *ld;

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

	offset = y*bmapwidth + x;

	// 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
		{
			size_t i;
			po->validcount = validcount;

			for (i = 0; i < po->numLines; ++i)
			{
				if (po->lines[i]->validcount == validcount) // line has been checked
					continue;
				po->lines[i]->validcount = validcount;
				if (!func(po->lines[i]))
					return false;
			}
		}
		plink = (polymaplink_t *)(plink->link.next);
	}

	offset = *(blockmap + offset); // offset = blockmap[y*bmapwidth+x];

	// First index is really empty, so +1 it.
	for (list = blockmaplump + offset + 1; *list != -1; list++)
	{
		ld = &lines[*list];

		if (ld->validcount == validcount)
			continue; // Line has already been checked.

		ld->validcount = validcount;

		if (!func(ld))
			return false;
	}
	return true; // Everything was checked.
}

//
// P_BlockThingsIterator
//
boolean P_BlockThingsIterator(INT32 x, INT32 y, boolean (*func)(mobj_t *))
{
	blocknode_t *block, *next = NULL;

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

	// Check interaction with the objects in the blockmap.
	for (block = blocklinks[y*bmapwidth + x]; block != NULL; block = next)
	{
		next = block->mnext; // We want to note our reference to mnext here!

		if (!func(block->mobj))
			return false;

		if (P_MobjWasRemoved(tmthing)) // func just popped our tmthing, cannot continue.
			return true;
	}

	return true;
}

boolean P_DoBlockThingsIterate(int x1, int y1, int x2, int y2, boolean (*func)(mobj_t *))
{
	boolean status = true;

	for (INT32 bx = x1; bx <= x2; bx++)
		for (INT32 by = y1; by <= y2; by++)
			if (!P_BlockThingsIterator(bx, by, func))
				status = false;

	return status;
}

static bthingit_hash_entry_t *GetHashEntryForIterator(bthingit_t *it, int i)
{
	if (i < NUM_BTHINGIT_FIXEDHASH)
		return &it->fixedhash[i];
	else
		return &it->dynhash[i - NUM_BTHINGIT_FIXEDHASH];
}

static blocknode_t *GetBlockmapBlock(int x, int y)
{
	if (x >= 0 && y >= 0 && x < bmapwidth && y < bmapheight)
	{
		return blocklinks[y*bmapwidth + x];
	}
	else
	{
		// invalid block
		return NULL;
	}
}

static bthingit_t *freeiters;

bthingit_t *P_NewBlockThingsIterator(int x1, int y1, int x2, int y2)
{
	bthingit_t *it;
	blocknode_t *block;

	x1 = max(0, x1);
	y1 = max(0, y1);
	x2 = min(bmapwidth - 1, x2);
	y2 = min(bmapheight - 1, y2);

	if (x1 > x2 || y1 > y2)
		return NULL;

	block = GetBlockmapBlock(x1, y1);

	if (freeiters != NULL)
	{
		it = freeiters;
		freeiters = it->freechain;
	}
	else
		it = Z_Calloc(sizeof(bthingit_t), PU_LEVEL, NULL);

	it->x1 = x1;
	it->y1 = y1;
	it->x2 = x2;
	it->y2 = y2;
	it->curx = x1;
	it->cury = y1;
	it->block = block;
	it->freechain = NULL;
	it->numfixedhash = 0;
	it->dynhashcount = 0;

	for (size_t i = 0; i < NUM_BTHINGIT_BUCKETS; i++)
		it->buckets[i] = -1;

	return it;
}

mobj_t *P_BlockThingsIteratorNext(bthingit_t *it, boolean centeronly)
{
	for (;;)
	{
		while (it->block != NULL)
		{
			mobj_t *mobj = it->block->mobj;
			blocknode_t *node = it->block;

			it->block = it->block->mnext;

			// Don't recheck things that were already checked
			if (node->bnext == NULL && node->bprev == &mobj->blocknode)
			{
				// This actor doesn't span blocks, so we know it can only ever be checked once.
				return mobj;
			}
			else
			{
				// Block boundaries for compatibility mode
				if (centeronly)
				{
					fixed_t blockleft = (it->curx * MAPBLOCKUNITS) + bmaporgx;
					fixed_t blockright = blockleft + MAPBLOCKUNITS;
					fixed_t blockbottom = (it->cury * MAPBLOCKUNITS) + bmaporgy;
					fixed_t blocktop = blockbottom + MAPBLOCKUNITS;

					// only return actors with the center in this block
					if (mobj->x >= blockleft && mobj->x < blockright &&
						mobj->y >= blockbottom && mobj->y < blocktop)
					{
						return mobj;
					}
				}
				else
				{
					bthingit_hash_entry_t *entry;
					int i;
					size_t hash = ((size_t)mobj >> 3) % NUM_BTHINGIT_BUCKETS;

					for (i = it->buckets[hash]; i >= 0; )
					{
						entry = GetHashEntryForIterator(it, i);
						if (entry->mobj == mobj)
						{
							// I've already been checked. Skip to the next mobj.
							break;
						}
						i = entry->next;
					}

					if (i < 0)
					{
						// Add mobj to the hash table and return it.
						if (it->numfixedhash < NUM_BTHINGIT_FIXEDHASH)
						{
							entry = &it->fixedhash[it->numfixedhash];
							entry->next = it->buckets[hash];
							it->buckets[hash] = it->numfixedhash++;
						}
						else
						{
							if (!it->dynhash)
							{
								it->dynhashcapacity = 50;
								Z_Calloc(it->dynhashcapacity * sizeof(*it->dynhash), PU_LEVEL, &it->dynhash);
							}
							if (it->dynhashcount == it->dynhashcapacity)
							{
								it->dynhashcapacity *= 2;
								it->dynhash = Z_Realloc(it->dynhash, it->dynhashcapacity * sizeof(*it->dynhash), PU_LEVEL, &it->dynhash);
							}
							i = (int)it->dynhashcount;
							it->dynhashcount++;
							entry = &it->dynhash[i];
							entry->next = it->buckets[hash];
							it->buckets[hash] = i + NUM_BTHINGIT_FIXEDHASH;
						}

						entry->mobj = mobj;
						return mobj;
					}
				}
			}
		}

		if (++it->curx > it->x2)
		{
			it->curx = it->x1;
			if (++it->cury > it->y2)
				return NULL;
		}

		it->block = GetBlockmapBlock(it->curx, it->cury);
	}

	return NULL;
}

void P_FreeBlockThingsIterator(bthingit_t *it)
{
	if (it)
	{
		it->freechain = freeiters;
		freeiters = it;
	}
}

void P_ClearBlockNodes(void)
{
	freeblocks = NULL;
	freeiters = NULL;
}

//
// INTERCEPT ROUTINES
//

//SoM: 4/6/2000: Limit removal
static intercept_t *intercepts = NULL;
static intercept_t *intercept_p = NULL;

divline_t trace;
static boolean earlyout;

//SoM: 4/6/2000: Remove limit on intercepts.
static void P_CheckIntercepts(void)
{
	static size_t max_intercepts = 0;
	size_t count = intercept_p - intercepts;

	if (max_intercepts <= count)
	{
		if (!max_intercepts)
			max_intercepts = 128;
		else
			max_intercepts *= 2;

		intercepts = Z_Realloc(intercepts, sizeof (*intercepts) * max_intercepts, PU_STATIC, NULL);

		intercept_p = intercepts + count;
	}
}

//
// PIT_AddLineIntercepts.
// Looks for lines in the given block
// that intercept the given trace
// to add to the intercepts list.
//
// A line is crossed if its endpoints
// are on opposite sides of the trace.
// Returns true if earlyout and a solid line hit.
//
static boolean PIT_AddLineIntercepts(line_t *ld)
{
	INT32 s1, s2;
	fixed_t frac;
	divline_t dl;

	// avoid precision problems with two routines
	if (trace.dx > FRACUNIT*16 || trace.dy > FRACUNIT*16
		|| trace.dx < -FRACUNIT*16 || trace.dy < -FRACUNIT*16)
	{
		// Hurdler: crash here with phobia when you shoot
		// on the door next the stone bridge
		// stack overflow???
		s1 = P_PointOnDivlineSide(ld->v1->x, ld->v1->y, &trace);
		s2 = P_PointOnDivlineSide(ld->v2->x, ld->v2->y, &trace);
	}
	else
	{
		s1 = P_PointOnLineSide(trace.x, trace.y, ld);
		s2 = P_PointOnLineSide(trace.x+trace.dx, trace.y+trace.dy, ld);
	}

	if (s1 == s2)
		return true; // Line isn't crossed.

	// Hit the line.
	P_MakeDivline(ld, &dl);
	frac = P_InterceptVector(&trace, &dl);

	if (frac < 0)
		return true; // Behind source.

	// Try to take an early out of the check.
	if (earlyout && frac < FRACUNIT && !ld->backsector)
		return false; // stop checking

	P_CheckIntercepts();

	intercept_p->frac = frac;
	intercept_p->isaline = true;
	intercept_p->d.line = ld;
	intercept_p++;

	return true; // continue
}

//
// PIT_AddThingIntercepts
//
static boolean PIT_AddThingIntercepts(mobj_t *thing)
{
	fixed_t px1, py1, px2, py2, frac;
	INT32 s1, s2;
	boolean tracepositive;
	divline_t dl;

	tracepositive = (trace.dx ^ trace.dy) > 0;

	// check a corner to corner crossection for hit
	if (tracepositive)
	{
		px1 = thing->x - thing->radius;
		py1 = thing->y + thing->radius;

		px2 = thing->x + thing->radius;
		py2 = thing->y - thing->radius;
	}
	else
	{
		px1 = thing->x - thing->radius;
		py1 = thing->y - thing->radius;

		px2 = thing->x + thing->radius;
		py2 = thing->y + thing->radius;
	}

	s1 = P_PointOnDivlineSide(px1, py1, &trace);
	s2 = P_PointOnDivlineSide(px2, py2, &trace);

	if (s1 == s2)
		return true; // Line isn't crossed.

	dl.x = px1;
	dl.y = py1;
	dl.dx = px2 - px1;
	dl.dy = py2 - py1;

	frac = P_InterceptVector(&trace, &dl);

	if (frac < 0)
		return true; // Behind source.

	P_CheckIntercepts();

	intercept_p->frac = frac;
	intercept_p->isaline = false;
	intercept_p->d.thing = thing;
	intercept_p++;

	return true; // Keep going.
}

//
// P_TraverseIntercepts
// Returns true if the traverser function returns true
// for all lines.
//
static boolean P_TraverseIntercepts(traverser_t func, fixed_t maxfrac)
{
	size_t count;
	fixed_t dist;
	intercept_t *scan, *in = NULL;

	count = intercept_p - intercepts;

	while (count--)
	{
		dist = INT32_MAX;
		for (scan = intercepts; scan < intercept_p; scan++)
		{
			if (scan->frac < dist)
			{
				dist = scan->frac;
				in = scan;
			}
		}

		if (dist > maxfrac)
			return true; // Checked everything in range.

		if (!func(in))
			return false; // Don't bother going farther.

		in->frac = INT32_MAX;
	}

	return true; // Everything was traversed.
}

//
// P_PathTraverse
// Traces a line from x1, y1 to x2, y2,
// calling the traverser function for each.
// Returns true if the traverser function returns true
// for all lines.
//
boolean P_PathTraverse(fixed_t px1, fixed_t py1, fixed_t px2, fixed_t py2,
	INT32 flags, traverser_t trav)
{
	fixed_t xt1, yt1, xt2, yt2;
	fixed_t xstep, ystep, partial, xintercept, yintercept;
	INT32 mapx, mapy, mapxstep, mapystep, count;

	earlyout = flags & PT_EARLYOUT;

	validcount++;
	intercept_p = intercepts;

	if (((px1 - bmaporgx) & (MAPBLOCKSIZE-1)) == 0)
		px1 += FRACUNIT; // Don't side exactly on a line.

	if (((py1 - bmaporgy) & (MAPBLOCKSIZE-1)) == 0)
		py1 += FRACUNIT; // Don't side exactly on a line.

	trace.x = px1;
	trace.y = py1;
	trace.dx = px2 - px1;
	trace.dy = py2 - py1;

	px1 -= bmaporgx;
	py1 -= bmaporgy;
	xt1 = (unsigned)px1>>MAPBLOCKSHIFT;
	yt1 = (unsigned)py1>>MAPBLOCKSHIFT;

	px2 -= bmaporgx;
	py2 -= bmaporgy;
	xt2 = (unsigned)px2>>MAPBLOCKSHIFT;
	yt2 = (unsigned)py2>>MAPBLOCKSHIFT;

	if (xt2 > xt1)
	{
		mapxstep = 1;
		partial = FRACUNIT - ((px1>>MAPBTOFRAC) & FRACMASK);
		ystep = FixedDiv(py2 - py1, abs(px2 - px1));
	}
	else if (xt2 < xt1)
	{
		mapxstep = -1;
		partial = (px1>>MAPBTOFRAC) & FRACMASK;
		ystep = FixedDiv(py2 - py1, abs(px2 - px1));
	}
	else
	{
		mapxstep = 0;
		partial = FRACUNIT;
		ystep = 256*FRACUNIT;
	}

	yintercept = (py1>>MAPBTOFRAC) + FixedMul(partial, ystep);

	if (yt2 > yt1)
	{
		mapystep = 1;
		partial = FRACUNIT - ((py1>>MAPBTOFRAC) & FRACMASK);
		xstep = FixedDiv(px2 - px1, abs(py2 - py1));
	}
	else if (yt2 < yt1)
	{
		mapystep = -1;
		partial = (py1>>MAPBTOFRAC) & FRACMASK;
		xstep = FixedDiv(px2 - px1, abs(py2 - py1));
	}
	else
	{
		mapystep = 0;
		partial = FRACUNIT;
		xstep = 256*FRACUNIT;
	}
	xintercept = (px1>>MAPBTOFRAC) + FixedMul(partial, xstep);

	// Step through map blocks.
	// Count is present to prevent a round off error
	// from skipping the break.
	mapx = xt1;
	mapy = yt1;

	for (count = 0; count < 64; count++)
	{
		if (flags & PT_ADDLINES)
			if (!P_BlockLinesIterator(mapx, mapy, PIT_AddLineIntercepts))
				return false; // early out

		if (flags & PT_ADDTHINGS)
			if (!P_BlockThingsIterator(mapx, mapy, PIT_AddThingIntercepts))
				return false; // early out

		if (mapx == xt2 && mapy == yt2)
			break;

		if ((yintercept >> FRACBITS) == mapy)
		{
			yintercept += ystep;
			mapx += mapxstep;
		}
		else if ((xintercept >> FRACBITS) == mapx)
		{
			xintercept += xstep;
			mapy += mapystep;
		}
	}
	// Go through the sorted list
	return P_TraverseIntercepts(trav, FRACUNIT);
}


// =========================================================================
//                                                        BLOCKMAP ITERATORS
// =========================================================================

// blockmap iterator for all sorts of use
// your routine must return FALSE to exit the loop earlier
// returns FALSE if the loop exited early after a false return
// value from your user function

//abandoned, maybe I'll need it someday..
/*
boolean P_RadiusLinesCheck(fixed_t radius, fixed_t x, fixed_t y,
	boolean (*func)(line_t *))
{
	INT32 xl, xh, yl, yh;
	INT32 bx, by;

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

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

	for (bx = xl; bx <= xh; bx++)
		for (by = yl; by <= yh; by++)
			if (!P_BlockLinesIterator(bx, by, func))
				return false;
	return true;
}
*/
