// BLANKART
//-----------------------------------------------------------------------------
// Copyright (C) 1998-2021 by ZDoom + GZDoom teams, and contributors
// Copyright (C) 2021 by Sally "TehRealSalt" Cochenour
// Copyright (C) 2021-2024 by Kart Krew
// Copyright (C) 2026 Blankart Team
//
// 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  k_terrain.c
/// \brief Implementation of TERRAIN lump from GZDoom codebase for DRRR.

#include "k_terrain.h"

#include "dehacked.h" // get_number
#include "deh_soc.h" // get_mobjtype
#include "doomdata.h"
#include "doomdef.h"
#include "doomstat.h"
#include "doomtype.h"
#include "m_fixed.h"
#include "m_random.h"
#include "m_misc.h"
#include "p_local.h"
#include "p_mobj.h"
#include "r_textures.h"
#include "r_main.h"
#include "w_wad.h"
#include "z_zone.h"

#include "k_kart.h" // on the chopping block...

#include "blan/b_soc.h"

static t_splash_t *splashDefs = NULL;
static size_t numSplashDefs = 0;

static t_footstep_t *footstepDefs = NULL;
static size_t numFootstepDefs = 0;

static t_overlay_t *overlayDefs = NULL;
static size_t numOverlayDefs = 0;

static terrain_t *terrainDefs = NULL;
static size_t numTerrainDefs = 0;

static t_floor_t *terrainFloorDefs = NULL;
static size_t numTerrainFloorDefs = 0;

static size_t defaultTerrain = SIZE_MAX;
static size_t defaultOffroadFootstep = SIZE_MAX;

/*--------------------------------------------------
	size_t K_GetSplashHeapIndex(t_splash_t *splash)

		See header file for description.
--------------------------------------------------*/
size_t K_GetSplashHeapIndex(t_splash_t *splash)
{
	if (splash == NULL)
	{
		return SIZE_MAX;
	}

	return (splash - splashDefs);
}

/*--------------------------------------------------
	size_t K_GetNumSplashDefs(void)

		See header file for description.
--------------------------------------------------*/
size_t K_GetNumSplashDefs(void)
{
	return numSplashDefs;
}

/*--------------------------------------------------
	t_splash_t *K_GetSplashByIndex(size_t checkIndex)

		See header file for description.
--------------------------------------------------*/
t_splash_t *K_GetSplashByIndex(size_t checkIndex)
{
	if (checkIndex >= numSplashDefs)
	{
		return NULL;
	}

	return &splashDefs[checkIndex];
}

/*--------------------------------------------------
	t_splash_t *K_GetSplashByName(const char *checkName)

		See header file for description.
--------------------------------------------------*/
t_splash_t *K_GetSplashByName(const char *checkName)
{
	UINT32 checkHash = FNV1a_QuickCaseHash(checkName, TERRAIN_NAME_LEN);
	size_t i;

	if (numSplashDefs == 0)
	{
		return NULL;
	}

	for (i = 0; i < numSplashDefs; i++)
	{
		t_splash_t *s = &splashDefs[i];

		if (checkHash == s->hash && !strncmp(checkName, s->name, TERRAIN_NAME_LEN))
		{
			// Name matches.
			return s;
		}
	}

	return NULL;
}

/*--------------------------------------------------
	size_t K_GetFootstepHeapIndex(t_footstep_t *footstep)

		See header file for description.
--------------------------------------------------*/
size_t K_GetFootstepHeapIndex(t_footstep_t *footstep)
{
	if (footstep == NULL)
	{
		return SIZE_MAX;
	}

	return (footstep - footstepDefs);
}

/*--------------------------------------------------
	size_t K_GetNumFootstepDefs(void)

		See header file for description.
--------------------------------------------------*/
size_t K_GetNumFootstepDefs(void)
{
	return numFootstepDefs;
}

/*--------------------------------------------------
	t_footstep_t *K_GetFootstepByIndex(size_t checkIndex)

		See header file for description.
--------------------------------------------------*/
t_footstep_t *K_GetFootstepByIndex(size_t checkIndex)
{
	if (checkIndex >= numFootstepDefs)
	{
		return NULL;
	}

	return &footstepDefs[checkIndex];
}

/*--------------------------------------------------
	t_footstep_t *K_GetFootstepByName(const char *checkName)

		See header file for description.
--------------------------------------------------*/
t_footstep_t *K_GetFootstepByName(const char *checkName)
{
	UINT32 checkHash = FNV1a_QuickCaseHash(checkName, TERRAIN_NAME_LEN);
	size_t i;

	if (numFootstepDefs == 0)
	{
		return NULL;
	}

	for (i = 0; i < numFootstepDefs; i++)
	{
		t_footstep_t *fs = &footstepDefs[i];

		if (checkHash == fs->hash && !strncmp(checkName, fs->name, TERRAIN_NAME_LEN))
		{
			// Name matches.
			return fs;
		}
	}

	return NULL;
}

/*--------------------------------------------------
	size_t K_GetOverlayHeapIndex(t_overlay_t *overlay)

		See header file for description.
--------------------------------------------------*/
size_t K_GetOverlayHeapIndex(t_overlay_t *overlay)
{
	if (overlay == NULL)
	{
		return SIZE_MAX;
	}

	return (overlay - overlayDefs);
}

/*--------------------------------------------------
	size_t K_GetNumOverlayDefs(void)

		See header file for description.
--------------------------------------------------*/
size_t K_GetNumOverlayDefs(void)
{
	return numOverlayDefs;
}

/*--------------------------------------------------
	t_overlay_t *K_GetOverlayByIndex(size_t checkIndex)

		See header file for description.
--------------------------------------------------*/
t_overlay_t *K_GetOverlayByIndex(size_t checkIndex)
{
	if (checkIndex >= numOverlayDefs)
	{
		return NULL;
	}

	return &overlayDefs[checkIndex];
}

/*--------------------------------------------------
	t_overlay_t *K_GetOverlayByName(const char *checkName)

		See header file for description.
--------------------------------------------------*/
t_overlay_t *K_GetOverlayByName(const char *checkName)
{
	UINT32 checkHash = FNV1a_QuickCaseHash(checkName, TERRAIN_NAME_LEN);
	size_t i;

	if (numOverlayDefs == 0)
	{
		return NULL;
	}

	for (i = 0; i < numOverlayDefs; i++)
	{
		t_overlay_t *o = &overlayDefs[i];

		if (checkHash == o->hash && !strncmp(checkName, o->name, TERRAIN_NAME_LEN))
		{
			// Name matches.
			return o;
		}
	}

	return NULL;
}

/*--------------------------------------------------
	size_t K_GetTerrainHeapIndex(terrain_t *terrain)

		See header file for description.
--------------------------------------------------*/
size_t K_GetTerrainHeapIndex(terrain_t *terrain)
{
	if (terrain == NULL)
	{
		return SIZE_MAX;
	}

	return (terrain - terrainDefs);
}

/*--------------------------------------------------
	size_t K_GetNumTerrainDefs(void)

		See header file for description.
--------------------------------------------------*/
size_t K_GetNumTerrainDefs(void)
{
	return numTerrainDefs;
}

/*--------------------------------------------------
	terrain_t *K_GetTerrainByIndex(size_t checkIndex)

		See header file for description.
--------------------------------------------------*/
terrain_t *K_GetTerrainByIndex(size_t checkIndex)
{
	if (checkIndex >= numTerrainDefs)
	{
		return NULL;
	}

	return &terrainDefs[checkIndex];
}

/*--------------------------------------------------
	terrain_t *K_GetTerrainByName(const char *checkName)

		See header file for description.
--------------------------------------------------*/
terrain_t *K_GetTerrainByName(const char *checkName)
{
	UINT32 checkHash = FNV1a_QuickCaseHash(checkName, TERRAIN_NAME_LEN);
	size_t i;

	if (numTerrainDefs > 0)
	{
		for (i = 0; i < numTerrainDefs; i++)
		{
			terrain_t *t = &terrainDefs[i];

			if (checkHash == t->hash && !strncmp(checkName, t->name, TERRAIN_NAME_LEN))
			{
				// Name matches.
				return t;
			}
		}
	}

	return NULL;
}

/*--------------------------------------------------
	terrain_t *K_GetDefaultTerrain(void)

		See header file for description.
--------------------------------------------------*/
terrain_t *K_GetDefaultTerrain(void)
{
	return K_GetTerrainByIndex(defaultTerrain);
}

/*--------------------------------------------------
	size_t K_GetDefaultTerrainID(void)

		See header file for description.
--------------------------------------------------*/
size_t K_GetDefaultTerrainID(void)
{
	return defaultTerrain;
}

/*--------------------------------------------------
	terrain_t *K_GetTerrainForTextureName(const char *checkName)

		See header file for description.
--------------------------------------------------*/
terrain_t *K_GetTerrainForTextureName(const char *checkName)
{
	UINT32 checkHash = FNV1a_QuickCaseHash(checkName, 8);
	size_t i;

	if (numTerrainFloorDefs > 0)
	{
		for (i = 0; i < numTerrainFloorDefs; i++)
		{
			t_floor_t *f = &terrainFloorDefs[i];

			if (checkHash == f->textureHash && !strncmp(checkName, f->textureName, 8))
			{
				return K_GetTerrainByIndex(f->terrainID);
			}
		}
	}

	// This texture doesn't have a terrain directly applied to it,
	// so we fallback to the default terrain.
	return K_GetDefaultTerrain();
}

/*--------------------------------------------------
	size_t K_GetTerrainIDForTextureName(const char *checkName)

		See header file for description.
--------------------------------------------------*/
size_t K_GetTerrainIDForTextureName(const char *checkName)
{
	UINT32 checkHash = FNV1a_QuickCaseHash(checkName, 8);
	size_t i;

	if (numTerrainFloorDefs > 0)
	{
		for (i = 0; i < numTerrainFloorDefs; i++)
		{
			t_floor_t *f = &terrainFloorDefs[i];

			if (checkHash == f->textureHash && !strncasecmp(checkName, f->textureName, 8))
			{
				return f->terrainID;
			}
		}
	}

	// This texture doesn't have a terrain directly applied to it,
	// so we fallback to the default terrain.
	return K_GetDefaultTerrainID();
}

/*--------------------------------------------------
	terrain_t *K_GetTerrainForTextureNum(INT32 textureNum)

		See header file for description.
--------------------------------------------------*/
terrain_t *K_GetTerrainForTextureNum(INT32 textureNum)
{
	if (textureNum >= 0 && textureNum < numtextures)
	{
		texture_t *tex = textures[textureNum];
		return K_GetTerrainByIndex(tex->terrainID);
	}

	// This texture doesn't have a terrain directly applied to it,
	// so we fallback to the default terrain.
	return K_GetDefaultTerrain();
}

/*--------------------------------------------------
	terrain_t *K_GetTerrainForFlatNum(INT32 flatID)

		See header file for description.
--------------------------------------------------*/
terrain_t *K_GetTerrainForFlatNum(INT32 flatID)
{
	if (flatID < 0 || flatID >= (signed)numlevelflats)
	{
		// Clearly invalid floor...
		return NULL;
	}

	return levelflats[flatID].terrain;
}

/*--------------------------------------------------
	void K_UpdateMobjTerrain(mobj_t *mo, INT32 flatID)

		See header file for description.
--------------------------------------------------*/
void K_UpdateMobjTerrain(mobj_t *mo, INT32 flatID)
{
	if (mo == NULL || P_MobjWasRemoved(mo) == true)
	{
		// Invalid object.
		return;
	}

	if (mo->flags & MF_NOCLIPHEIGHT)
	{
		// You can't collide with floors anyway!
		mo->terrain = NULL;
		return;
	}

	if (mo->player != NULL && mo->player->spectator == true)
	{
		// We don't want a terrain pointer for spectators.
		mo->terrain = NULL;
		return;
	}

	// Update the object's terrain pointer.
	mo->terrain = K_GetTerrainForFlatNum(flatID);
}

/*--------------------------------------------------
	void K_SetTerrainFriction(mobj_t *mo)

		Applies friction from Terrain definition.
	
		Input Arguments:-
		mo - The object to apply friction to.
		
--------------------------------------------------*/
static void K_SetTerrainFriction(mobj_t *mo)
{
	boolean isPlayer = false;
	fixed_t strength = mo->terrain->friction;
	terrain_t *terrain = NULL;
	fixed_t newFriction = INT32_MAX;
	fixed_t newMovefactor = INT32_MAX;

	if (mo == NULL || P_MobjWasRemoved(mo) == true)
	{
		// Invalid object.
		return;
	}

	isPlayer = (mo->player != NULL);
	terrain = mo->terrain;
	
	if (isPlayer && !((terrain->flags & TRF_BYPASSBOOST) 
		|| P_AllowFriction(mo)))
	{
		// I want this to be consistent with sector friction
		// so boosts bypass friction unless specficied otherwise
		return;
	}

	mo->friction = ORIG_FRICTION;

	if (isPlayer == true)
	{
		mo->movefactor = FRACUNIT;
	}

	if (strength > 0) // sludge
	{
		strength = strength * 2; // otherwise, the maximum sludginess value is +967...
	}

	// The following might seem odd. At the time of movement,
	// the move distance is multiplied by 'friction/0x10000', so a
	// higher friction value actually means 'less friction'.
	newFriction = ORIG_FRICTION - FixedMul(0x1EB8, strength) / 0x80; // ORIG_FRICTION is 0xE800

	if (newFriction > FRACUNIT)
	{
		newFriction = FRACUNIT;
	}

	if (newFriction < 0)
	{
		newFriction = 0;
	}

	mo->friction = newFriction;

	if (isPlayer == true)
	{
		newMovefactor = FixedDiv(ORIG_FRICTION, newFriction);

		if (newMovefactor < FRACUNIT)
		{
			newMovefactor = 19*newMovefactor - 18*FRACUNIT;
		}
		else
		{
			newMovefactor = FRACUNIT;
		}

		mo->movefactor = newMovefactor;
	}
}

/*--------------------------------------------------
	void K_ProcessTerrainEffect(mobj_t *mo)

		See header file for description.
--------------------------------------------------*/
void K_ProcessTerrainEffect(mobj_t *mo)
{
	player_t *player = NULL;
	terrain_t *terrain = NULL;

	if (mo == NULL || P_MobjWasRemoved(mo) == true)
	{
		// Invalid object.
		return;
	}

	if (mo->terrain == NULL)
	{
		// No terrain type.
		return;
	}

	if (!K_AffectingTerrainActive())
	{
		// Terrain has been disabled for this map.
		return;
	}

	terrain = mo->terrain;
	player = mo->player;
	
	if (terrain->friction) // Handle Friction set here so mobjs are affected as well.
		K_SetTerrainFriction(mo);

	if (player == NULL)
	{
		// maybe can support regualar mobjs later? :)
		return;
	}
	
	// Milky Way road effect
	player->outrun = terrain->outrun;
	
	if (terrain->outrun)
	{
		// This is a hack to make speedloss after outrun feel more natural
		//CONS_Printf("Adding outruntime!\n");
		player->outruntime = sneakertime;
	}

	// Damage effects
	if (terrain->damageType > 0)
	{
		UINT8 dmg = (terrain->damageType & 0xFF);

		if (dmg & DMG_FLIPOVER)
		{
			K_FlipPlayer(player, NULL, NULL);
			dmg &= ~DMG_FLIPOVER;
		}

		P_DamageMobj(mo, NULL, NULL, 1, dmg);
	}

	// Sneaker panel
	if (terrain->flags & TRF_SNEAKERPANEL)
	{
		if (player->floorboost == 0)
			player->floorboost = 3;
		else
			player->floorboost = 2;

		K_DoSneaker(player, SNEAKERTYPE_PANEL);
	}
	
	// WaterRun Panel
	if (terrain->flags & TRF_WATERRUNPANEL)
	{
		if (player->floorboost == 0)
			player->floorboost = 3;
		else
			player->floorboost = 2;

		K_DoSneaker(player, SNEAKERTYPE_WATERPANEL);
	}
	
	// Speed pad
	if (terrain->speedPad > 0)
	{
		if (player->floorboost != 0)
		{
			player->floorboost = 2;
		}
		else
		{
			fixed_t thrustSpeed = terrain->speedPad;
			angle_t thrustAngle = terrain->speedPadAngle;
			fixed_t playerSpeed = P_AproxDistance(player->mo->momx, player->mo->momy);

			// FIXME: come up with a better way to get the touched
			// texture's rotation to this function. At least this
			// will work for 90% of scenarios...

			if (player->mo->eflags & MFE_VERTICALFLIP)
			{
				if (player->mo->ceilingrover != NULL)
				{
					thrustAngle -= *player->mo->ceilingrover->bottomangle;
				}
				else
				{
					thrustAngle -= player->mo->subsector->sector->ceilingpic_angle;
				}
			}
			else
			{
				if (player->mo->floorrover != NULL)
				{
					thrustAngle -= *player->mo->floorrover->topangle;
				}
				else
				{
					thrustAngle -= player->mo->subsector->sector->floorpic_angle;
				}
			}

			// Map scale for Shrink, object scale for Grow.
			thrustSpeed = FixedMul(thrustSpeed, max(mapobjectscale, player->mo->scale));

			thrustAngle = K_ReflectAngle(
				K_MomentumAngle(player->mo), thrustAngle,
				playerSpeed, thrustSpeed
			);

			player->mo->angle = player->drawangle = thrustAngle;
			player->driftlock = TICRATE/8; // seems like a good value so its not noticable and you still get the right angle

			if (!demo.playback)
				P_ForceLocalAngle(player, player->mo->angle, true);

			player->angleturn = player->mo->angle;

			P_InstaThrust(player->mo, thrustAngle, max(thrustSpeed, 2*playerSpeed));

			player->dashpadcooldown = TICRATE/3;
			K_ResetPogoSpring(player);
			player->floorboost = 2;

			S_StartSound(player->mo, sfx_cdfm62);
		}
	}

	// Spring
	if (terrain->springStrength)
	{
		sector_t *sector = player->mo->subsector->sector;

		const pslope_t *slope;
		angle_t angle = 0;

		fixed_t co = FRACUNIT;
		fixed_t si = 0;

		// FIXME: come up with a better way to get the touched
		// texture's slope to this function. At least this
		// will work for 90% of scenarios...

		if (player->mo->eflags & MFE_VERTICALFLIP)
		{
			if (player->mo->ceilingrover != NULL)
			{
				slope = *player->mo->ceilingrover->b_slope;
			}
			else
			{
				slope = sector->c_slope;
			}
		}
		else
		{
			if (player->mo->floorrover != NULL)
			{
				slope = *player->mo->floorrover->t_slope;
			}
			else
			{
				slope = sector->f_slope;
			}
		}

		if (slope && (terrain->pogoSpring == 0))
		{
			const angle_t fa = (slope->zangle >> ANGLETOFINESHIFT);

			co = FINECOSINE(fa) * P_MobjFlip(player->mo);
			si = -(FINESINE(fa));

			angle = slope->xydirection;
		}

		P_DoSpringEx(player->mo, mapobjectscale,
				FixedMul(terrain->springStrength, co),
				FixedMul(terrain->springStrength, si),
				angle);

		sector->soundorg.z = player->mo->z;
		
		if (terrain->pogoSpring == 0)
			S_StartSound(&sector->soundorg, sfx_s3kb1); // Don't play two spring sounds at once thx!
	}
	
	if ((terrain->pogoSpring > 0) && terrain->springStrength) // Hack to allow spring strength to work with pogospring
		mo->eflags &= ~MFE_SPRUNG;
	
	// Pogospring panel
	if (terrain->pogoSpring > 0 && !(mo->eflags & MFE_SPRUNG))
	{
		// I'm not including the "minimum scale" rant here again. Look at P_DoSpring for that.
		// Just know this fixes things with Alt. Shrink.
		const fixed_t objscale = max(player->mo->scale, mapobjectscale);

		const fixed_t hscale = mapobjectscale + (mapobjectscale - objscale);
		fixed_t minspeed = terrain->pogoSpringMin*hscale;
		fixed_t maxspeed = terrain->pogoSpringMax*hscale;
		angle_t pushangle = FixedHypot(player->mo->momx, player->mo->momy) ? R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy) : player->mo->angle;

		if (terrain->pogoSpring == 2)
			player->pogospring = 2;
		else
			player->pogospring = 1;
		
		if ((player->speed > maxspeed) && terrain->pogoSpring == 2) // Prevent overshooting jumps
			P_InstaThrust(player->mo, pushangle, maxspeed);
		else if (player->speed < minspeed) // Push forward to prevent getting stuck
			P_InstaThrust(player->mo, pushangle, minspeed);
		
		if (!terrain->springStrength)
			K_DoPogoSpring(player->mo, 0, 1);
		else
			S_StartSound(player->mo, sfx_kc2f);
	}

	// (Offroad is handled elsewhere!)
}

/*--------------------------------------------------
	static void K_SpawnSplashParticles(mobj_t *mo, t_splash_t *s, fixed_t impact)

		Creates all of the splash particles for an object
		from a splash definition.

	Input Arguments:-
		mo - The object to spawn the splash particles for.
		s - The splash definition to use.
		impact - How hard the object hit the surface.

	Return:-
		N/A
--------------------------------------------------*/
static void K_SpawnSplashParticles(mobj_t *mo, t_splash_t *s, fixed_t impact)
{
	const UINT8 numParticles = s->numParticles;
	const angle_t particleSpread = ANGLE_MAX / numParticles;

	fixed_t momH = INT32_MAX;
	fixed_t momV = INT32_MAX;

	size_t i;

	momH = FixedMul(impact, s->pushH);
	momV = FixedMul(impact, s->pushV);

	for (i = 0; i < numParticles; i++)
	{
		mobj_t *dust = NULL;
		angle_t pushAngle = (particleSpread * i);
		
		fixed_t xOff = 0;
		fixed_t yOff = 0;

		if (numParticles == 1)
		{
			// Random angle.
			pushAngle = P_RandomRange(0, ANGLE_MAX);
		}

		if (s->spread > 0)
		{
			xOff = P_RandomRange(-s->spread / FRACUNIT, s->spread / FRACUNIT) * FRACUNIT;
			yOff = P_RandomRange(-s->spread / FRACUNIT, s->spread / FRACUNIT) * FRACUNIT;
		}

		if (s->cone > 0)
		{
			pushAngle += P_RandomRange(-s->cone / ANG1, s->cone / ANG1) * ANG1;
		}

		dust = P_SpawnMobjFromMobj(
			mo,
			xOff + (12 * FINECOSINE(pushAngle >> ANGLETOFINESHIFT)),
			yOff + (12 * FINESINE(pushAngle >> ANGLETOFINESHIFT)),
			0, //P_RandomRange(0, s->spread / FRACUNIT) * FRACUNIT,
			s->mobjType
		);

		P_SetTarget(&dust->target, mo);
		dust->angle = pushAngle;

		dust->destscale = FixedMul(mo->scale, s->scale);
		P_SetScale(dust, dust->destscale);

		dust->momx = mo->momx / 2;
		dust->momy = mo->momy / 2;
		dust->momz = 0;

		dust->momx += FixedMul(momH, FINECOSINE(pushAngle >> ANGLETOFINESHIFT));
		dust->momy += FixedMul(momH, FINESINE(pushAngle >> ANGLETOFINESHIFT));
		dust->momz += (momV / 16) * P_MobjFlip(mo);

		if (s->color != SKINCOLOR_NONE)
		{
			dust->color = s->color;
		}

		if (s->mobjType == MT_WIPEOUTTRAIL)
		{
			dust->renderflags = RF_GHOSTLY;
		}

		if (s->sfx != sfx_None)
		{
			S_StartSound(mo, s->sfx);
		}
	}
}

/*--------------------------------------------------
	void K_SpawnSplashForMobj(mobj_t *mo, fixed_t impact)

		See header file for description.
--------------------------------------------------*/
void K_SpawnSplashForMobj(mobj_t *mo, fixed_t impact)
{
	const fixed_t minImpact = mo->scale;
	t_splash_t *s = NULL;

	if (mo == NULL || P_MobjWasRemoved(mo) == true)
	{
		// Invalid object.
		return;
	}

	if (!(mo->flags & MF_APPLYTERRAIN))
	{
		// No TERRAIN effects for this object.
		return;
	}

	if (mo->terrain == NULL || mo->terrain->splashID == SIZE_MAX)
	{
		// No impact for this terrain type.
		return;
	}
	else
	{
		s = K_GetSplashByIndex(mo->terrain->splashID);
	}

	if (s == NULL || s->mobjType == MT_NULL || s->numParticles == 0)
	{
		// No particles to spawn.
		return;
	}

	impact /= 4;

	if (impact < minImpact)
	{
		impact = minImpact;
	}

	// Idea for later: if different spawning styles are desired,
	// we can put a switch case here!
	K_SpawnSplashParticles(mo, s, impact);
}

/*--------------------------------------------------
	static void K_SpawnFootstepParticle(mobj_t *mo, t_footstep_t *fs)

		Creates a new footstep particle for an object
		from a footstep definition.

	Input Arguments:-
		mo - The object to spawn the footstep particle for.
		fs - The footstep definition to use.
		timer - Spawning frequency timer.

	Return:-
		N/A
--------------------------------------------------*/
static void K_SpawnFootstepParticle(mobj_t *mo, t_footstep_t *fs, tic_t timer)
{
	mobj_t *dust = NULL;
	angle_t pushAngle = ANGLE_MAX;
	angle_t tireAngle = ANGLE_MAX;
	fixed_t momentum = INT32_MAX;
	fixed_t speedValue = INT32_MAX;
	fixed_t momH = INT32_MAX;
	fixed_t momV = INT32_MAX;
	fixed_t xOff = 0;
	fixed_t yOff = 0;

	if (timer % fs->frequency != 0)
	{
		return;
	}

	momentum = P_AproxDistance(mo->momx, mo->momy);

	if (mo->player != NULL)
	{
		boolean karmabomb = ((gametypes[gametype]->rules & GTR_BUMPERS) && mo->player->bumper <= 0);
		tireAngle = (mo->player->drawangle + ANGLE_180);
		speedValue = K_GetKartSpeedFromStat(mo->player->kartspeed, karmabomb);
	}
	else
	{
		tireAngle = (mo->angle + ANGLE_180);
		speedValue = K_GetKartSpeedFromStat(5, false);
	}

	speedValue = FixedMul(speedValue, mo->scale);
	speedValue = FixedMul(speedValue, fs->requiredSpeed);

	if (momentum < speedValue)
	{
		return;
	}

	pushAngle = K_MomentumAngle(mo) + ANGLE_180;

	if (((timer / fs->frequency) / 2) & 1)
	{
		tireAngle -= ANGLE_45;
		tireAngle -= P_RandomRange(0, fs->cone / ANG1) * ANG1;
		pushAngle -= P_RandomRange(0, fs->cone / ANG1) * ANG1;
	}
	else
	{
		tireAngle += ANGLE_45;
		tireAngle += P_RandomRange(0, fs->cone / ANG1) * ANG1;
		pushAngle += P_RandomRange(0, fs->cone / ANG1) * ANG1;
	}

	if (fs->spread > 0)
	{
		xOff = P_RandomRange(-fs->spread / FRACUNIT, fs->spread / FRACUNIT) * FRACUNIT;
		yOff = P_RandomRange(-fs->spread / FRACUNIT, fs->spread / FRACUNIT) * FRACUNIT;
	}

	dust = P_SpawnMobjFromMobj(
		mo,
		xOff + (24 * FINECOSINE(tireAngle >> ANGLETOFINESHIFT)),
		yOff + (24 * FINESINE(tireAngle >> ANGLETOFINESHIFT)),
		0, fs->mobjType
	);

	P_SetTarget(&dust->target, mo);
	dust->angle = K_MomentumAngle(mo);

	dust->destscale = FixedMul(mo->scale, fs->scale);
	P_SetScale(dust, dust->destscale);

	dust->momx = mo->momx;
	dust->momy = mo->momy;
	dust->momz = P_GetMobjZMovement(mo);

	momH = FixedMul(momentum, fs->pushH);
	momV = FixedMul(momentum, fs->pushV);

	dust->momx += FixedMul(momH, FINECOSINE(pushAngle >> ANGLETOFINESHIFT));
	dust->momy += FixedMul(momH, FINESINE(pushAngle >> ANGLETOFINESHIFT));
	dust->momz += (momV / 16) * P_MobjFlip(mo);

	if (fs->color != SKINCOLOR_NONE)
	{
		dust->color = fs->color;
	}

	// Make offroad dust transparent
	if (K_GetFootstepHeapIndex(fs) == defaultOffroadFootstep)
	{
		dust->renderflags |= RF_GHOSTLY;
	}

	if ((fs->sfx != sfx_None) && (fs->sfxFreq > 0) && (timer % fs->sfxFreq == 0))
	{
		S_StartSound(mo, fs->sfx);
	}
}

/*--------------------------------------------------
	void K_HandleFootstepParticles(mobj_t *mo)

		See header file for description.
--------------------------------------------------*/

#define INVALIDFOOTSTEP (fs == NULL || fs->mobjType == MT_NULL || fs->frequency <= 0)

void K_HandleFootstepParticles(mobj_t *mo)
{
	tic_t timer = leveltime;
	t_footstep_t *fs = NULL;

	if (mo == NULL || P_MobjWasRemoved(mo) == true)
	{
		// Invalid object.
		return;
	}

	if (mo->player)
	{
		// Offset the timer.
		timer += mo->player - players;
	}

	if (!(mo->flags & MF_APPLYTERRAIN) || mo->terrain == NULL)
	{
		// No TERRAIN effects for this object.
		return;
	}

	fs = K_GetFootstepByIndex(mo->terrain->footstepID);

	if (INVALIDFOOTSTEP)
	{
		// No particles to spawn.
		goto offroadhandle;
	}

	// Idea for later: if different spawning styles are desired,
	// we can put a switch case here!
	K_SpawnFootstepParticle(mo, fs, timer);

	return;

offroadhandle:
	// ...unless you're
	// - A player and
	if (mo->player == NULL)
	{
		return;
	}

	// - Being affected by offroad
	// Using acutal offroad check here to prevent lua boostpower modifications from spawning the offroad particles - Nep
	if (!(mo->player->bananadrag > TICRATE) && !(K_ApplyOffroad(mo->player) && mo->player->offroad > 0))
	{
		return;
	}

	// in which case make default offroad footstep!
	fs = K_GetFootstepByIndex(defaultOffroadFootstep);

	if (INVALIDFOOTSTEP)
	{
		// No particles to spawn.
		return;
	}

	K_SpawnFootstepParticle(mo, fs, timer);
}

#undef INVALIDFOOTSTEP

/*--------------------------------------------------
	static void K_CleanupTerrainOverlay(mobj_t *mo)

		Removes an object's terrain overlay.

	Input Arguments:-
		mo - The object to remove the overlay from.

	Return:-
		N/A
--------------------------------------------------*/
static void K_CleanupTerrainOverlay(mobj_t *mo)
{
	if (mo->terrainOverlay != NULL && P_MobjWasRemoved(mo->terrainOverlay) == false)
	{
		P_RemoveMobj(mo->terrainOverlay);
	}
}

/*--------------------------------------------------
	static boolean K_InitTerrainOverlay(mobj_t *mo)

		Creates a new terrain overlay for an object.

	Input Arguments:-
		mo - The object to give an overlay to.

	Return:-
		true if successful, otherwise false.
--------------------------------------------------*/
static boolean K_InitTerrainOverlay(mobj_t *mo)
{
	mobj_t *new = P_SpawnMobjFromMobj(mo, 0, 0, 0, MT_OVERLAY);

	// Tells the overlay that we haven't set up a state yet.
	new->extravalue1 = TOV_UNDEFINED;

	// Set up our pointers.
	P_SetTarget(&new->target, mo);
	P_SetTarget(&mo->terrainOverlay, new);

	return true;
}

/*--------------------------------------------------
	static t_overlay_state_t K_DesiredTerrainOverlayAction(mobj_t *mo)

		Figures out the overlay action to use for an object.

	Input Arguments:-
		mo - The object
		st - The terrain overlay state.

	Return:-
		The overlay action enum to use for the object.
--------------------------------------------------*/
static t_overlay_action_t K_DesiredTerrainOverlayAction(mobj_t *mo)
{
	const boolean moving = (P_AproxDistance(mo->momx, mo->momy) >= (mo->scale >> 1));

	if (moving == true)
	{
		return TOV_MOVING;
	}

	return TOV_STILL;
}

/*--------------------------------------------------
	static statenum_t K_GetTerrainOverlayState(t_overlay_t *o, t_overlay_action_t act)

		Converts our overlay's action enum into an actual state ID.

	Input Arguments:-
		o - The overlay properties.
		act - The terrain overlay action.

	Return:-
		The actual state ID, for use with P_SetMobjState.
--------------------------------------------------*/
static statenum_t K_GetTerrainOverlayState(t_overlay_t *o, t_overlay_action_t act)
{
	if (act >= 0 && act < TOV__MAX)
	{
		return o->states[act];
	}

	return S_NULL;
}

/*--------------------------------------------------
	static void K_SetTerrainOverlayState(mobj_t *mo, t_overlay_action_t act, statenum_t st)

		Updates our overlay's current state.

	Input Arguments:-
		o - The overlay properties.
		act - The terrain overlay action.
		st - The new object's state.

	Return:-
		N/A
--------------------------------------------------*/
static void K_SetTerrainOverlayState(mobj_t *mo, t_overlay_action_t act, statenum_t st)
{
	if (act == mo->terrainOverlay->extravalue1)
	{
		// Already set the state, so leave it alone.
		return;
	}

	P_SetMobjState(mo->terrainOverlay, st);
	mo->terrainOverlay->extravalue1 = act;
}

/*--------------------------------------------------
	static void K_UpdateTerrainOverlay(mobj_t *mo)

		See header file for description.
--------------------------------------------------*/
void K_UpdateTerrainOverlay(mobj_t *mo)
{
	t_overlay_t *o = NULL;
	t_overlay_action_t act = TOV_UNDEFINED;
	statenum_t st = S_NULL;

	if (mo == NULL || P_MobjWasRemoved(mo) == true)
	{
		// Invalid object.
		return;
	}

	if (!(mo->flags & MF_APPLYTERRAIN))
	{
		// No TERRAIN effects for this object.
		K_CleanupTerrainOverlay(mo);
		return;
	}

	if (mo->terrain == NULL || mo->terrain->overlayID == SIZE_MAX)
	{
		// No overlay for this terrain type.
		K_CleanupTerrainOverlay(mo);
		return;
	}
	else
	{
		o = K_GetOverlayByIndex(mo->terrain->overlayID);
	}

	if (o == NULL)
	{
		// No overlay to use.
		K_CleanupTerrainOverlay(mo);
		return;
	}

	// Determine the state to use. We want to do this before creating
	// the overlay, so that we keep it despawned if the state is S_NULL.
	act = K_DesiredTerrainOverlayAction(mo);
	st = K_GetTerrainOverlayState(o, act);

	if (st == S_NULL)
	{
		// No state to use for this action.
		K_CleanupTerrainOverlay(mo);
		return;
	}

	if (mo->terrainOverlay == NULL || P_MobjWasRemoved(mo->terrainOverlay) == true)
	{
		// Doesn't exist currently, so try to create
		// a new terrain overlay.

		if (K_InitTerrainOverlay(mo) == false)
		{
			// We were unsuccessful, get out of here.
			return;
		}
	}

	mo->terrainOverlay->spriteyoffset = -mo->terrain->floorClip;
	mo->terrainOverlay->color = o->color;
	mo->terrainOverlay->movefactor = o->scale;

	K_SetTerrainOverlayState(mo, act, st);

	if (mo->state->tics > 1 && o->speed > 0)
	{
		const fixed_t maxSpeed = 60 * mapobjectscale;
		fixed_t speed = P_AproxDistance(mo->momx, mo->momy);
		fixed_t speedDiv = FRACUNIT + FixedMul(FixedDiv(speed, maxSpeed), o->speed);
		tic_t animSpeed = max(FixedDiv(mo->state->tics, speedDiv), 1);

		mo->tics = min(mo->tics, (INT32)animSpeed);
	}
}

/*--------------------------------------------------
	static void K_FlagBoolean(UINT32 *inputFlags, UINT32 newFlag, char *val)

		Sets a flag to true or false depending on
		the string input.

	Input Arguments:-
		inputFlags - Pointer to flags value to modify.
		newFlag - The flag(s) to set / unset.
		val - The string input from the file.

	Return:-
		None
--------------------------------------------------*/
static void K_FlagBoolean(UINT32 *inputFlags, UINT32 newFlag, char *val)
{
	if (fasticmp(val, "true"))
	{
		*inputFlags |= newFlag;
	}
	else if (fasticmp(val, "false"))
	{
		*inputFlags &= ~newFlag;
	}
}

/*--------------------------------------------------
	static void K_SplashDefaults(t_splash_t *splash)

		Sets the defaults for a new Splash block.

	Input Arguments:-
		splash - Terrain Splash structure to default.

	Return:-
		None
--------------------------------------------------*/
static void K_SplashDefaults(t_splash_t *splash)
{
	splash->mobjType = MT_NULL;
	splash->sfx = sfx_None;
	splash->scale = FRACUNIT;
	splash->color = SKINCOLOR_NONE;

	splash->pushH = FRACUNIT/4;
	splash->pushV = FRACUNIT/64;
	splash->spread = 2*FRACUNIT;
	splash->cone = ANGLE_11hh;

	splash->numParticles = 8;
}

/*--------------------------------------------------
	static void K_NewSplashDefs(void)

		Increases the size of splashDefs by 1, and
		sets the new struct's values to their defaults.

	Input Arguments:-
		None

	Return:-
		None
--------------------------------------------------*/
static void K_NewSplashDefs(void)
{
	numSplashDefs++;
	splashDefs = (t_splash_t *)Z_Realloc(splashDefs, sizeof(t_splash_t) * (numSplashDefs + 1), PU_STATIC, NULL);
	K_SplashDefaults( &splashDefs[numSplashDefs - 1] );
}

/*--------------------------------------------------
	static void K_ParseSplashParameter(size_t i, char *param, char *val)

		Parser function for Splash blocks.

	Input Arguments:-
		i - Struct ID
		param - Parameter string
		val - Value string

	Return:-
		None
--------------------------------------------------*/
static void K_ParseSplashParameter(size_t i, char *param, char *val)
{
	t_splash_t *splash = &splashDefs[i];

	if (fasticmp(param, "mobjType"))
	{
		splash->mobjType = get_number(val);
	}
	else if (fasticmp(param, "sfx"))
	{
		splash->sfx = get_number(val);
	}
	else if (fasticmp(param, "scale"))
	{
		splash->scale = FLOAT_TO_FIXED(atof(val));
	}
	else if (fasticmp(param, "color"))
	{
		splash->color = get_number(val);
	}
	else if (fasticmp(param, "pushH"))
	{
		splash->pushH = FLOAT_TO_FIXED(atof(val));
	}
	else if (fasticmp(param, "pushV"))
	{
		splash->pushV = FLOAT_TO_FIXED(atof(val));
	}
	else if (fasticmp(param, "spread"))
	{
		splash->spread = FLOAT_TO_FIXED(atof(val));
	}
	else if (fasticmp(param, "cone"))
	{
		splash->cone = FloatToAngle(atof(val));
	}
	else if (fasticmp(param, "numParticles"))
	{
		splash->numParticles = (UINT8)atoi(val);
	}
}

/*--------------------------------------------------
	static void K_FootstepDefaults(t_footstep_t *footstep)

		Sets the defaults for a new Footstep block.

	Input Arguments:-
		footstep - Terrain Footstep structure to default.

	Return:-
		None
--------------------------------------------------*/
static void K_FootstepDefaults(t_footstep_t *footstep)
{
	footstep->mobjType = MT_NULL;
	footstep->sfx = sfx_None;
	footstep->scale = FRACUNIT;
	footstep->color = SKINCOLOR_NONE;

	footstep->pushH = FRACUNIT/2;
	footstep->pushV = FRACUNIT/32;
	footstep->spread = 2*FRACUNIT;
	footstep->cone = ANGLE_11hh;

	footstep->sfxFreq = 6;
	footstep->frequency = 1;
	footstep->requiredSpeed = 0;
}

/*--------------------------------------------------
	static void K_NewFootstepDefs(void)

		Increases the size of footstepDefs by 1, and
		sets the new struct's values to their defaults.

	Input Arguments:-
		None

	Return:-
		None
--------------------------------------------------*/
static void K_NewFootstepDefs(void)
{
	numFootstepDefs++;
	footstepDefs = (t_footstep_t *)Z_Realloc(footstepDefs, sizeof(t_footstep_t) * (numFootstepDefs + 1), PU_STATIC, NULL);
	K_FootstepDefaults( &footstepDefs[numFootstepDefs - 1] );
}

/*--------------------------------------------------
	static void K_ParseFootstepParameter(size_t i, char *param, char *val)

		Parser function for Footstep blocks.

	Input Arguments:-
		i - Struct ID
		param - Parameter string
		val - Value string

	Return:-
		None
--------------------------------------------------*/
static void K_ParseFootstepParameter(size_t i, char *param, char *val)
{
	t_footstep_t *footstep = &footstepDefs[i];

	if (fasticmp(param, "mobjType"))
	{
		footstep->mobjType = get_number(val);
	}
	else if (fasticmp(param, "sfx"))
	{
		footstep->sfx = get_number(val);
	}
	else if (fasticmp(param, "scale"))
	{
		footstep->scale = FLOAT_TO_FIXED(atof(val));
	}
	else if (fasticmp(param, "color"))
	{
		footstep->color = get_number(val);
	}
	else if (fasticmp(param, "pushH"))
	{
		footstep->pushH = FLOAT_TO_FIXED(atof(val));
	}
	else if (fasticmp(param, "pushV"))
	{
		footstep->pushV = FLOAT_TO_FIXED(atof(val));
	}
	else if (fasticmp(param, "spread"))
	{
		footstep->spread = FLOAT_TO_FIXED(atof(val));
	}
	else if (fasticmp(param, "cone"))
	{
		footstep->cone = FloatToAngle(atof(val));
	}
	else if (fasticmp(param, "sfxFreq"))
	{
		footstep->sfxFreq = (tic_t)atoi(val);
	}
	else if (fasticmp(param, "frequency"))
	{
		footstep->frequency = (tic_t)atoi(val);
	}
	else if (fasticmp(param, "requiredSpeed"))
	{
		footstep->requiredSpeed = FLOAT_TO_FIXED(atof(val));
	}
}

/*--------------------------------------------------
	static void K_OverlayDefaults(t_overlay_t *overlay)

		Sets the defaults for a new Overlay block.

	Input Arguments:-
		overlay - Terrain Overlay structure to default.

	Return:-
		None
--------------------------------------------------*/
static void K_OverlayDefaults(t_overlay_t *overlay)
{
	size_t i;

	for (i = 0; i < TOV__MAX; i++)
	{
		overlay->states[i] = S_NULL;
	}

	overlay->scale = FRACUNIT;
	overlay->color = SKINCOLOR_NONE;
	overlay->speed = FRACUNIT;
}

/*--------------------------------------------------
	static void K_NewOverlayDefs(void)

		Increases the size of overlayDefs by 1, and
		sets the new struct's values to their defaults.

	Input Arguments:-
		None

	Return:-
		None
--------------------------------------------------*/
static void K_NewOverlayDefs(void)
{
	numOverlayDefs++;
	overlayDefs = (t_overlay_t *)Z_Realloc(overlayDefs, sizeof(t_overlay_t) * (numOverlayDefs + 1), PU_STATIC, NULL);
	K_OverlayDefaults( &overlayDefs[numOverlayDefs - 1] );
}

/*--------------------------------------------------
	static void K_ParseOverlayParameter(size_t i, char *param, char *val)

		Parser function for Overlay blocks.

	Input Arguments:-
		i - Struct ID
		param - Parameter string
		val - Value string

	Return:-
		None
--------------------------------------------------*/
static void K_ParseOverlayParameter(size_t i, char *param, char *val)
{
	t_overlay_t *overlay = &overlayDefs[i];

	if (fasticmp(param, "stillState"))
	{
		overlay->states[TOV_STILL] = get_number(val);
	}
	else if (fasticmp(param, "movingState"))
	{
		overlay->states[TOV_MOVING] = get_number(val);
	}
	else if (fasticmp(param, "scale"))
	{
		overlay->scale = FLOAT_TO_FIXED(atof(val));
	}
	else if (fasticmp(param, "color"))
	{
		overlay->color = get_number(val);
	}
	else if (fasticmp(param, "speed"))
	{
		overlay->speed = FLOAT_TO_FIXED(atof(val));
	}
}

/*--------------------------------------------------
	static void K_TerrainDefaults(terrain_t *terrain)

		Sets the defaults for a new Terrain block.

	Input Arguments:-
		terrain - Terrain structure to default.

	Return:-
		None
--------------------------------------------------*/
static void K_TerrainDefaults(terrain_t *terrain)
{
	terrain->splashID = SIZE_MAX;
	terrain->footstepID = SIZE_MAX;
	terrain->overlayID = SIZE_MAX;

	terrain->friction = 0;
	terrain->offroad = 0;
	terrain->damageType = -1;
	terrain->pogoSpring = 0;
	terrain->pogoSpringMin = 24;
	terrain->pogoSpringMax = 28;
	terrain->speedPad = 0;
	terrain->speedPadAngle = 0;
	terrain->springStrength = 0;
	terrain->flags = TRF_REMAP;
}

/*--------------------------------------------------
	boolean K_TerrainHasAffect(terrain_t *terrain, boolean badonly, boolean followmaprules)

		See header file for description.
--------------------------------------------------*/

boolean K_TerrainHasAffect(terrain_t *terrain, boolean badonly, boolean followmaprules)
{
	if (followmaprules && !K_AffectingTerrainActive())
		return false;

	if (terrain->friction > 0
	|| terrain->offroad != 0
	|| terrain->damageType != -1)
		return true;

	if (badonly)
		return false;

	return (terrain->friction != 0
	|| terrain->speedPad != 0
	|| terrain->pogoSpring != 0
	|| terrain->springStrength != 0
	|| (terrain->flags & (TRF_LIQUID|TRF_SNEAKERPANEL|TRF_BYPASSBOOST|TRF_WATERRUNPANEL|TRF_TRIPWIRE)));
}

/*--------------------------------------------------
	static void K_NewTerrainDefs(void)

		Increases the size of terrainDefs by 1, and
		sets the new struct's values to their defaults.

	Input Arguments:-
		None

	Return:-
		None
--------------------------------------------------*/
static void K_NewTerrainDefs(void)
{
	numTerrainDefs++;
	terrainDefs = (terrain_t *)Z_Realloc(terrainDefs, sizeof(terrain_t) * (numTerrainDefs + 1), PU_STATIC, NULL);
	K_TerrainDefaults( &terrainDefs[numTerrainDefs - 1] );
}

/*--------------------------------------------------
	static void K_ParseTerrainParameter(size_t i, char *param, char *val)

		Parser function for Terrain blocks.

	Input Arguments:-
		i - Struct ID
		param - Parameter string
		val - Value string

	Return:-
		None
--------------------------------------------------*/
static void K_ParseTerrainParameter(size_t i, char *param, char *val)
{
	terrain_t *terrain = &terrainDefs[i];

	if (fasticmp(param, "splash"))
	{
		t_splash_t *splash = K_GetSplashByName(val);
		terrain->splashID = K_GetSplashHeapIndex(splash);
	}
	else if (fasticmp(param, "footstep"))
	{
		t_footstep_t *footstep = K_GetFootstepByName(val);
		terrain->footstepID = K_GetFootstepHeapIndex(footstep);
	}
	else if (fasticmp(param, "overlay"))
	{
		t_overlay_t *overlay = K_GetOverlayByName(val);
		terrain->overlayID = K_GetOverlayHeapIndex(overlay);
	}
	else if (fasticmp(param, "friction"))
	{
		terrain->friction = FLOAT_TO_FIXED(atof(val));
	}
	else if (fasticmp(param, "offroad"))
	{
		terrain->offroad = (UINT8)get_number(val); // offroad strength enum?
	}
	else if (fasticmp(param, "damageType"))
	{
		terrain->damageType = (INT16)get_number(val);
	}
	else if (fasticmp(param, "pogoSpring"))
	{
		terrain->pogoSpring = (UINT8)get_number(val);
	}
	else if (fasticmp(param, "pogoMinSpeed") || fasticmp(param, "pogoSpringMinSpeed"))
	{
		terrain->pogoSpringMin = (UINT8)get_number(val);
	}
	else if (fasticmp(param, "pogoMaxSpeed") || fasticmp(param, "pogoSpringMaxSpeed"))
	{
		terrain->pogoSpringMax = (UINT8)get_number(val);
	}
	else if (fasticmp(param, "speedPad"))
	{
		terrain->speedPad = FLOAT_TO_FIXED(atof(val));
	}
	else if (fasticmp(param, "speedPadAngle"))
	{
		terrain->speedPadAngle = FixedAngle(FLOAT_TO_FIXED(atof(val)));
	}
	else if (fasticmp(param, "springStrength"))
	{
		const double fval = atof(val);

		if (fpclassify(fval) == FP_ZERO)
		{
			terrain->springStrength = 0;
		}
		else
		{
			// Springs increase in stength by 1.6 times the
			// previous strength. Grey spring is 25 and
			// 25/1.6 = 15.625
			terrain->springStrength =
				FLOAT_TO_FIXED(15.625 * pow(1.6, fval));
		}
	}
	else if (fasticmp(param, "outrun") || fasticmp(param, "speedIncrease"))
	{
		terrain->outrun = FLOAT_TO_FIXED(atof(val));
	}
	else if (fasticmp(param, "floorClip"))
	{
		terrain->floorClip = FLOAT_TO_FIXED(atof(val));
	}
	else if (fasticmp(param, "liquid"))
	{
		K_FlagBoolean(&terrain->flags, TRF_LIQUID, val);
	}
	else if (fasticmp(param, "sneakerPanel"))
	{
		K_FlagBoolean(&terrain->flags, TRF_SNEAKERPANEL, val);
	}
	else if (fasticmp(param, "waterRunPanel") || fasticmp(param, "waterPanel"))
	{
		K_FlagBoolean(&terrain->flags, TRF_WATERRUNPANEL, val);
	}
	else if (fasticmp(param, "tripwire"))
	{
		K_FlagBoolean(&terrain->flags, TRF_TRIPWIRE, val);
	}
	else if (fasticmp(param, "remap"))
	{
		K_FlagBoolean(&terrain->flags, TRF_REMAP, val);
	}
	else if (fasticmp(param, "fricfix") || fasticmp(param, "frictionfix") || fasticmp(param, "boostbypass"))
	{
		K_FlagBoolean(&terrain->flags, TRF_BYPASSBOOST, val);
	}
}

/*--------------------------------------------------
	static void K_NewTerrainFloorDefs(void)

		Increases the size of numTerrainFloorDefs by 1.

	Input Arguments:-
		None

	Return:-
		None
--------------------------------------------------*/
static void K_NewTerrainFloorDefs(void)
{
	numTerrainFloorDefs++;
	terrainFloorDefs = (t_floor_t *)Z_Realloc(terrainFloorDefs, sizeof(t_floor_t) * (numTerrainFloorDefs + 1), PU_STATIC, NULL);
}

/*--------------------------------------------------
	static boolean K_DoTERRAINLumpParse(size_t num, void (*parser)(UINT32, char *, char *))

		Runs another parser function for the TERRAIN
		lump, handling the nitty-gritty parts of the
		token handling.

	Input Arguments:-
		num - Struct ID to modify. Which one it will modify depends on the parser function.
		parser - The parser function. Takes three inputs: Struct ID, Parameter String, and Value String.

	Return:-
		false if any errors occured, otherwise true.
--------------------------------------------------*/
static boolean K_DoTERRAINLumpParse(size_t num, void (*parser)(size_t, char *, char *))
{
	char *param, *val;

	param = M_GetToken(NULL);

	if (!fastcmp(param, "{"))
	{
		Z_Free(param);
		CONS_Alert(CONS_WARNING, "Invalid TERRAIN data capsule!\n");
		return false;
	}

	Z_Free(param);

	while (true)
	{
		param = M_GetToken(NULL);

		if (fastcmp(param, "}"))
		{
			Z_Free(param);
			break;
		}

		val = M_GetToken(NULL);
		parser(num, param, val);

		Z_Free(param);
		Z_Free(val);
	}

	return true;
}

/*--------------------------------------------------
	static boolean K_TERRAINLumpParser(char *data, size_t size)

		Parses inputted lump data as a TERRAIN lump.

	Input Arguments:-
		data - Pointer to lump data.
		size - The length of the lump data.

	Return:-
		false if any errors occured, otherwise true.
--------------------------------------------------*/
static boolean K_TERRAINLumpParser(char *data, size_t size)
{
	char *tkn = M_GetToken(data);
	UINT32 tknHash = 0;
	size_t pos = 0;
	size_t i;

	while (tkn && (pos = M_GetTokenPos()) < size)
	{
		boolean valid = true;

		// Avoid anything inside bracketed stuff, only look for external keywords.
		if (fastcmp(tkn, "{") || fastcmp(tkn, "}"))
		{
			CONS_Alert(CONS_ERROR, "Rogue bracket detected in TERRAIN lump.\n");
			valid = false;
		}
		// Check for valid fields.
		else if (fasticmp(tkn, "splash"))
		{
			Z_Free(tkn);
			tkn = M_GetToken(NULL);
			pos = M_GetTokenPos();

			if (tkn && pos <= size)
			{
				t_splash_t *s = NULL;

				tknHash = FNV1a_QuickCaseHash(tkn, TERRAIN_NAME_LEN);

				for (i = 0; i < numSplashDefs; i++)
				{
					s = &splashDefs[i];

					if (tknHash == s->hash && !strncmp(tkn, s->name, TERRAIN_NAME_LEN))
					{
						break;
					}
				}

				if (i == numSplashDefs)
				{
					K_NewSplashDefs();
					s = &splashDefs[i];

					strncpy(s->name, tkn, TERRAIN_NAME_LEN);
					s->hash = tknHash;

					CONS_Printf("Created new Splash type '%s'\n", s->name);
				}

				valid = K_DoTERRAINLumpParse(i, K_ParseSplashParameter);
			}
			else
			{
				CONS_Alert(CONS_ERROR, "No Splash type name.\n");
				valid = false;
			}
		}
		else if (fasticmp(tkn, "footstep"))
		{
			Z_Free(tkn);
			tkn = M_GetToken(NULL);
			pos = M_GetTokenPos();

			if (tkn && pos <= size)
			{
				t_footstep_t *fs = NULL;

				tknHash = FNV1a_QuickCaseHash(tkn, TERRAIN_NAME_LEN);

				for (i = 0; i < numFootstepDefs; i++)
				{
					fs = &footstepDefs[i];

					if (tknHash == fs->hash && !strncmp(tkn, fs->name, TERRAIN_NAME_LEN))
					{
						break;
					}
				}

				if (i == numFootstepDefs)
				{
					K_NewFootstepDefs();
					fs = &footstepDefs[i];

					strncpy(fs->name, tkn, TERRAIN_NAME_LEN);
					fs->hash = tknHash;

					CONS_Printf("Created new Footstep type '%s'\n", fs->name);
				}

				valid = K_DoTERRAINLumpParse(i, K_ParseFootstepParameter);
			}
			else
			{
				CONS_Alert(CONS_ERROR, "No Footstep type name.\n");
				valid = false;
			}
		}
		else if (fasticmp(tkn, "overlay"))
		{
			Z_Free(tkn);
			tkn = M_GetToken(NULL);
			pos = M_GetTokenPos();

			if (tkn && pos <= size)
			{
				t_overlay_t *o = NULL;

				tknHash = FNV1a_QuickCaseHash(tkn, TERRAIN_NAME_LEN);

				for (i = 0; i < numOverlayDefs; i++)
				{
					o = &overlayDefs[i];

					if (tknHash == o->hash && !strncmp(tkn, o->name, TERRAIN_NAME_LEN))
					{
						break;
					}
				}

				if (i == numOverlayDefs)
				{
					K_NewOverlayDefs();
					o = &overlayDefs[i];

					strncpy(o->name, tkn, TERRAIN_NAME_LEN);
					o->hash = tknHash;

					CONS_Printf("Created new Overlay type '%s'\n", o->name);
				}

				valid = K_DoTERRAINLumpParse(i, K_ParseOverlayParameter);
			}
			else
			{
				CONS_Alert(CONS_ERROR, "No Overlay type name.\n");
				valid = false;
			}
		}
		else if (fasticmp(tkn, "terrain"))
		{
			Z_Free(tkn);
			tkn = M_GetToken(NULL);
			pos = M_GetTokenPos();

			if (tkn && pos <= size)
			{
				terrain_t *t = NULL;

				tknHash = FNV1a_QuickCaseHash(tkn, TERRAIN_NAME_LEN);

				for (i = 0; i < numTerrainDefs; i++)
				{
					t = &terrainDefs[i];

					if (tknHash == t->hash && !strncmp(tkn, t->name, TERRAIN_NAME_LEN))
					{
						break;
					}
				}

				if (i == numTerrainDefs)
				{
					K_NewTerrainDefs();
					t = &terrainDefs[i];

					strncpy(t->name, tkn, TERRAIN_NAME_LEN);
					t->hash = tknHash;

					CONS_Printf("Created new Terrain type '%s'\n", t->name);
				}

				valid = K_DoTERRAINLumpParse(i, K_ParseTerrainParameter);
			}
			else
			{
				CONS_Alert(CONS_ERROR, "No Terrain type name.\n");
				valid = false;
			}
		}
		else if (fasticmp(tkn, "floor") || fasticmp(tkn, "texture"))
		{
			Z_Free(tkn);
			tkn = M_GetToken(NULL);
			pos = M_GetTokenPos();
			
			if (tkn && pos <= size)
			{
				if (fasticmp(tkn, "optional"))
				{
					// "optional" is ZDoom syntax
					// We don't use it, but we can ignore it.
					Z_Free(tkn);
					tkn = M_GetToken(NULL);
					pos = M_GetTokenPos();
				}

				if (tkn && pos <= size)
				{
					t_floor_t *f = NULL;

					tknHash = FNV1a_QuickCaseHash(tkn, 8);

					for (i = 0; i < numTerrainFloorDefs; i++)
					{
						f = &terrainFloorDefs[i];

						if (f->textureHash == tknHash && !strncmp(tkn, f->textureName, 8))
						{
							break;
						}
					}

					if (i == numTerrainFloorDefs)
					{
						K_NewTerrainFloorDefs();
						f = &terrainFloorDefs[i];

						strncpy(f->textureName, tkn, 8);
						f->textureHash = tknHash;
					}

					Z_Free(tkn);
					tkn = M_GetToken(NULL);
					pos = M_GetTokenPos();

					if (tkn && pos <= size)
					{
						terrain_t *t = K_GetTerrainByName(tkn);

						if (t == NULL)
						{
							CONS_Alert(CONS_ERROR, "Invalid Terrain type '%s'.\n", tkn);
							valid = false;
						}
						else
						{
							f->terrainID = K_GetTerrainHeapIndex(t);
							CONS_Printf("Texture '%s' set to Terrain '%s'\n", f->textureName, tkn);

							INT32 tex = R_CheckTextureNumForName(f->textureName, TEXTURETYPE_TEXTURE);

							if (tex == -1)
							{
								R_CheckTextureNumForName(f->textureName, TEXTURETYPE_FLAT);
							}

							if (tex != -1)
							{
								textures[tex]->terrainID = f->terrainID;
							}
						}
					}
					else
					{
						CONS_Alert(CONS_ERROR, "No terrain for floor definition.\n");
						valid = false;
					}
				}
				else
				{
					CONS_Alert(CONS_ERROR, "No texture for floor definition.\n");
					valid = false;
				}
			}
			else
			{
				CONS_Alert(CONS_ERROR, "No texture for floor definition.\n");
				valid = false;
			}
		}
		else if (fasticmp(tkn, "defaultTerrain"))
		{
			Z_Free(tkn);
			tkn = M_GetToken(NULL);
			pos = M_GetTokenPos();

			if (tkn && pos <= size)
			{
				terrain_t *t = NULL;

				tknHash = FNV1a_QuickCaseHash(tkn, TERRAIN_NAME_LEN);

				for (i = 0; i < numTerrainDefs; i++)
				{
					t = &terrainDefs[i];

					if (tknHash == t->hash && !strncmp(tkn, t->name, TERRAIN_NAME_LEN))
					{
						break;
					}
				}

				if (i == numTerrainDefs)
				{
					CONS_Alert(CONS_ERROR, "Invalid DefaultTerrain type.\n");
					valid = false;
				}
				else
				{
					defaultTerrain = i;
					CONS_Printf("DefaultTerrain set to '%s'\n", tkn);
				}
			}
			else
			{
				CONS_Alert(CONS_ERROR, "No DefaultTerrain type.\n");
				valid = false;
			}
		}
		else if (fasticmp(tkn, "defaultOffroadFootstep"))
		{
			Z_Free(tkn);
			tkn = M_GetToken(NULL);
			pos = M_GetTokenPos();

			if (tkn && pos <= size)
			{
				t_footstep_t *fs = NULL;

				tknHash = FNV1a_QuickCaseHash(tkn, TERRAIN_NAME_LEN);

				for (i = 0; i < numFootstepDefs; i++)
				{
					fs = &footstepDefs[i];

					if (tknHash == fs->hash && !strncmp(tkn, fs->name, TERRAIN_NAME_LEN))
					{
						break;
					}
				}

				if (i == numFootstepDefs)
				{
					CONS_Alert(CONS_ERROR, "Invalid DefaultOffroadFootstep type.\n");
					valid = false;
				}
				else
				{
					defaultOffroadFootstep = i;
					CONS_Printf("DefaultOffroadFootstep set to '%s'\n", tkn);
				}
			}
			else
			{
				CONS_Alert(CONS_ERROR, "No DefaultOffroadFootstep type.\n");
				valid = false;
			}
		}
		else
		{
			CONS_Alert(CONS_ERROR, "Unknown field '%s' found in TERRAIN lump.\n", tkn);
			valid = false;
		}

		Z_Free(tkn);

		if (valid == false)
		{
			return false;
		}

		tkn = M_GetToken(NULL);
	}

	Z_Free(tkn);
	return true;
}

/*--------------------------------------------------
	void K_InitTerrainPwad(UINT16 wadNum)

		See header file for description.
--------------------------------------------------*/
boolean K_InitTerrainPwad(UINT16 wadNum)
{
	UINT16 lumpNum;
	lumpinfo_t *lump_p = wadfiles[wadNum]->lumpinfo;
	boolean terrainLoaded = false;

	// Iterate through all lumps and compare the name individually.
	// In PK3 files, you can potentially have multiple TERRAIN differentiated by
	// their file extension.
	for (lumpNum = 0; lumpNum < wadfiles[wadNum]->numlumps; lumpNum++, lump_p++)
	{
		UINT8 *data;

		if (memcmp(lump_p->name, "TERRAIN", 8) != 0)
		{
			continue;
		}

		data = (UINT8 *)W_CacheLumpNumPwad(wadNum, lumpNum, PU_STATIC);

		// If that didn't exist, we have nothing to do here.
		if (data == NULL)
		{
			continue;
		}
		else
		{
			size_t size = W_LumpLengthPwad(wadNum, lumpNum);
			char *datacopy;

			size_t nameLength = strlen(wadfiles[wadNum]->filename) + 1 + strlen(lump_p->fullname); // length of file name, '|', and lump name
			char *name = malloc(nameLength + 1);

			sprintf(name, "%s|%s", wadfiles[wadNum]->filename, lump_p->fullname);
			name[nameLength] = '\0';

			size = W_LumpLengthPwad(wadNum, lumpNum);

			CONS_Printf(M_GetText("Loading TERRAIN from %s\n"), name);
			terrainLoaded = true;
			

			datacopy = (char *)Z_Malloc((size+1)*sizeof(char),PU_STATIC,NULL);
			memmove(datacopy,data,size);
			datacopy[size] = '\0';

			Z_Free(data);

			K_TERRAINLumpParser(datacopy, size);

			Z_Free(datacopy);

			free(name);
		}
	}

	R_ClearTextureNumCache(false);

	return terrainLoaded;
	
}

/*--------------------------------------------------
	void K_InitTerrain(void)

		See header file for description.
--------------------------------------------------*/
void K_InitTerrain(void)
{
	INT32 wadNum;

	for (wadNum = 0; wadNum < numwadfiles; wadNum++)
	{
		K_InitTerrainPwad(wadNum);
	}
}

// Map header toggle for Affecting Terrain Defs.
boolean K_AffectingTerrainActive(void)
{
	const mapheader_t *mapheader = mapheaderinfo[gamemap - 1];
	boolean value = false;

	if (mapnamespace == MNS_RINGRACERS)
		return true;

	if (mapheader->use_terrain == true)
		value = true;

	if (mapheader->use_terrain == false)
		value = false;

	return value;
}
