Untitled

mail@pastecode.io avatar
unknown
c_cpp
3 years ago
22 kB
1
Indexable
Never
/*
 * MIT License
 *
 * Copyright (c) 2021 ETJump team <zero@etjump.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

// etj_pmove.cpp - reimplementation of relevants parts of bg_pmove.cpp
// for calculation of pmove-centric parts of cgame code

#include "etj_pmove.h"

namespace ETJump
{
	void Pmove::InitPmove(void)
	{
		p.pm_ps = cg.predictedPlayerState;
		p.pm.tracemask = p.pm_ps.pm_type == PM_DEAD ? MASK_PLAYERSOLID & ~CONTENTS_BODY : MASK_PLAYERSOLID;
		Pmove::PmoveSingle();
	}


	void Pmove::PmoveSingle(void)
	{
		auto const uCmdScale = p.pm_ps.stats[STAT_USERCMD_BUTTONS] & (BUTTON_WALKING << 8) ? 64 : 127;
		if (!cg.demoPlayback && !(p.pm_ps.pm_flags & PMF_FOLLOW))
		{
			auto const cmdNum = trap_GetCurrentCmdNumber();
			trap_GetUserCmd(cmdNum, &p.pm.cmd);
		}
		else
		{
			p.pm.cmd.forwardmove = uCmdScale * (!!(p.pm_ps.stats[STAT_USERCMD_MOVE] & UMOVE_FORWARD) -
				!!(p.pm_ps.stats[STAT_USERCMD_MOVE] & UMOVE_BACKWARD));
			p.pm.cmd.rightmove = uCmdScale * (!!(p.pm_ps.stats[STAT_USERCMD_MOVE] & UMOVE_RIGHT) -
				!!(p.pm_ps.stats[STAT_USERCMD_MOVE] & UMOVE_LEFT));
			p.pm.cmd.upmove = uCmdScale * (!!(p.pm_ps.stats[STAT_USERCMD_MOVE] & UMOVE_UP) -
				!!(p.pm_ps.stats[STAT_USERCMD_MOVE] & UMOVE_DOWN));
		}
		// CG_Printf("%d %d %d\n", p.pm.cmd.forwardmove, p.pm.cmd.rightmove, p.pm.cmd.upmove);

		// clear all pmove local vars
		memset(&p.pml, 0, sizeof(p.pml));

		// these were in pml previously, make sure they are cleared too
		p.pm.walking = qfalse;
		p.pm.groundPlane = qfalse;
		memset(&p.pm.groundTrace, 0, sizeof(p.pm.groundTrace));

		// save old velocity for crashlanding
		VectorCopy(p.pm_ps.velocity, p.pml.previous_velocity);

		AngleVectors(p.pm_ps.viewangles, p.pml.forward, p.pml.right, p.pml.up);

		if (p.pm.cmd.upmove < 10)
		{
			// not holding jump
			p.pm_ps.pm_flags &= ~PMF_JUMP_HELD;
		}

		if (p.pm_ps.pm_type >= PM_DEAD || p.pm_ps.pm_flags & (PMF_LIMBO | PMF_TIME_LOCKPLAYER))             // DHM - Nerve
		{
			p.pm.cmd.forwardmove = 0;
			p.pm.cmd.rightmove = 0;
			p.pm.cmd.upmove = 0;
		}

		// use default key combination when no user input
		if (!p.pm.cmd.forwardmove && !p.pm.cmd.rightmove)
		{
			p.pm.cmd.forwardmove = uCmdScale;
			p.pm.cmd.rightmove = uCmdScale;
		}

		// set mins, maxs, and viewheight
		if (!PM_CheckProne(&p.pm, &p.pm_ps))
		{
			PM_CheckDuck(&p.pm, &p.pm_ps);
		}

		// set watertype, and waterlevel
		PM_SetWaterLevel(&p.pm, &p.pm_ps);

		// set groundentity
		PM_GroundTrace(&p.pm, &p.pm_ps, &p.pml);

		// for simplicity, we omit ladders and water
		// and only consider ground, air and ice movement
		if (p.pml.ladder)
		{
			return;
		}
		else if (p.pm_ps.pm_flags & PMF_TIME_WATERJUMP)
		{
			return;
		}
		else if (p.pm.waterlevel > 1)
		{
			// swimming
			return;
		}
		else if (p.pm.walking && !(p.pm_ps.eFlags & EF_MOUNTEDTANK))
		{
			// walking on ground
			PM_WalkMove(&p.pm, &p.pm_ps, &p.pml);
		}
		else if (!(p.pm_ps.eFlags & EF_MOUNTEDTANK))
		{
			// airborne
			PM_AirMove(&p.pm, &p.pm_ps, &p.pml);
		}
	}

	void Pmove::PM_AirMove(pmove_t* pm, playerState_t* pm_ps, pml_t* pml)
	{
		PM_Friction(pm, pm_ps, pml);

		auto const scale = p.PM_CmdScale(pm_ps, &pm->cmd);

		// project moves down to flat plane
		pml->forward[2] = 0;
		pml->right[2] = 0;
		VectorNormalize(pml->forward);
		VectorNormalize(pml->right);


		p.PM_UpdateWishVel(pm, pml);

		auto const wishspeed = scale * VectorLength2(p.wishvel);

		// TODO: this is where we are going to seperate different
		// cgame functions that use pmove calculations
		// PM_Accelerate(wishdir, wishspeed, pm_airaccelerate);
	}

	void Pmove::PM_WalkMove(pmove_t* pm, playerState_t* pm_ps, pml_t* pml)
	{
		if (pm->waterlevel > 2 && DotProduct(pml->forward, pm->groundTrace.plane.normal) > 0)
		{
			// begin swimming
			// PM_WaterMove();
			return;
		}

		if (PM_CheckJump(pm, pm_ps))
		{
			// jumped away
			if (pm->waterlevel > 1)
			{
				// PM_WaterMove();
			}
			else
			{
				PM_AirMove(pm, pm_ps, pml);
			}

			if (!(pm->cmd.serverTime - p.pmext.jumpTime < ETJump::JUMP_DELAY_TIME))
			{

				p.pmext.sprintTime -= 2500;
				if (p.pmext.sprintTime < 0)
				{
					p.pmext.sprintTime = 0;
				}
			}

			p.pmext.jumpTime = pm->cmd.serverTime;

			// JPW NERVE
			pm_ps->jumpTime = pm->cmd.serverTime;  // Arnout: NOTE : TEMP DEBUG

			return;
		}

		PM_Friction(pm, pm_ps, pml);

		auto const scale = p.PM_CmdScale(pm_ps, &pm->cmd);

		// project moves down to flat plane
		pml->forward[2] = 0;
		pml->right[2] = 0;

		// FIXME: only flat ground is correct
		// project the forward and right directions onto the ground plane
		// PM_ClipVelocity(pml->forward, pm->groundTrace.plane.normal, pml->forward, OVERCLIP);
		// PM_ClipVelocity(pml->right, pm->groundTrace.plane.normal, pml->right, OVERCLIP);
		//
		VectorNormalize(pml->forward);
		VectorNormalize(pml->right);

		PM_UpdateWishVel(pm, pml);

		float wishspeed = scale * VectorLength2(p.wishvel);

		// clamp the speed lower if prone
		if (pm_ps->eFlags & EF_PRONE)
		{
			if (wishspeed > pm_ps->speed * pm_proneSpeedScale)
			{
				wishspeed = pm_ps->speed * pm_proneSpeedScale;
			}
		}
		else if (pm_ps->pm_flags & PMF_DUCKED)       // clamp the speed lower if ducking
		{
			if (wishspeed > pm_ps->speed * pm_ps->crouchSpeedScale)
			{
				wishspeed = pm_ps->speed * pm_ps->crouchSpeedScale;
			}
		}

		// clamp the speed lower if wading or walking on the bottom
		if (pm->waterlevel)
		{
			float waterScale;

			waterScale = pm->waterlevel / 3.0;
			if (pm->watertype == CONTENTS_SLIME)    //----(SA)	slag
			{
				waterScale = 1.0 - (1.0 - pm_slagSwimScale) * waterScale;
			}
			else
			{
				waterScale = 1.0 - (1.0 - pm_waterSwimScale) * waterScale;
			}

			if (wishspeed > pm_ps->speed * waterScale)
			{
				wishspeed = pm_ps->speed * waterScale;
			}
		}

#if 0
		// when a player gets hit, they temporarily lose
		// full control, which allows them to be moved a bit
		// TODO: PM_Accelerate
		if ((pm->groundTrace.surfaceFlags & SURF_SLICK) || pm_ps->pm_flags & PMF_TIME_KNOCKBACK)
		{
			PM_Accelerate(wishdir, wishspeed, pm_airaccelerate);
		}
		else
		{
			PM_Accelerate(wishdir, wishspeed, pm_accelerate);
		}
#endif
	}

	void Pmove::PM_Friction(pmove_t* pm, playerState_t* pm_ps, pml_t* pml)
	{
		// ignore slope movement
		auto const speed = pm->walking ? VectorLength2(pm_ps->velocity) : VectorLength(pm_ps->velocity);

		// rain - #179 don't do this for PM_SPECTATOR/PM_NOCLIP, we always want them to stop
		if (speed < 1 && pm_ps->pm_type != PM_SPECTATOR && pm_ps->pm_type != PM_NOCLIP)
		{
			pm_ps->velocity[0] = 0;
			pm_ps->velocity[1] = 0;     // allow sinking underwater
			// FIXME: still have z friction underwater?
			return;
		}

		float drop = 0;

		// apply ground friction
		if (pm->waterlevel <= 1)
		{
			if (pm->walking && !(pm->groundTrace.surfaceFlags & SURF_SLICK))
			{
				// if getting knocked back, no friction
				if (!(pm_ps->pm_flags & PMF_TIME_KNOCKBACK))
				{
					auto const control = speed < pm_stopspeed ? pm_stopspeed : speed;
					drop += control * pm_friction * pml->frametime;
				}
			}
		}

		// apply water friction even if just wading
		if (pm->waterlevel)
		{
			if (pm->watertype == CONTENTS_SLIME)    //----(SA)	slag
			{
				drop += speed * pm_slagfriction * pm->waterlevel * pml->frametime;
			}
			else
			{
				drop += speed * pm_waterfriction * pm->waterlevel * pml->frametime;
			}
		}

		if (pm_ps->pm_type == PM_SPECTATOR)
		{
			drop += speed * pm_spectatorfriction * pml->frametime;
		}

		// apply ladder strafe friction
		if (pml->ladder)
		{
			drop += speed * pm_ladderfriction * pml->frametime;
		}

		// scale the velocity
		auto newspeed = speed - drop;
		if (newspeed < 0)
		{
			newspeed = 0;
		}
		newspeed /= speed;

		// rain - if we're barely moving and barely slowing down, we want to
		// help things along--we don't want to end up getting snapped back to
		// our previous speed
		if (pm_ps->pm_type == PM_SPECTATOR || pm_ps->pm_type == PM_NOCLIP)
		{
			if (drop < 1.0f && speed < 3.0f)
			{
				newspeed = 0.0;
			}
		}

		// rain - used VectorScale instead of multiplying by hand
		VectorScale(pm_ps->velocity, newspeed, pm_ps->velocity);
	}

	void Pmove::PM_UpdateWishVel(pmove_t* pm, pml_t* pml)
	{
		for (auto i = 0; i < 2; i++)
		{
			p.wishvel[i] = pml->forward[i] * pm->cmd.forwardmove + pml->right[i] * pm->cmd.rightmove;
		}
	}

	float Pmove::PM_CmdScale(playerState_t const* pm_ps, usercmd_t const* cmd)
	{
		auto max = abs(cmd->forwardmove);
		if (abs(cmd->rightmove) > max)
		{
			max = abs(cmd->rightmove);
		}
		if (abs(cmd->upmove) > max)
		{
			max = abs(cmd->upmove);
		}
		if (!max)
		{
			return 0;
		}

		auto const total = sqrtf(cmd->forwardmove * cmd->forwardmove + cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove);
		auto const scale = (float)pm_ps->speed * max / (127.f * total);

		if (pm_ps->stats[STAT_USERCMD_BUTTONS] & (BUTTON_SPRINT << 8) && p.pmext.sprintTime > 50)
		{
			return scale * pm_ps->sprintSpeedScale;
		}
		else
		{
			return scale * pm_ps->runSpeedScale;
		}
	}

	bool Pmove::PM_CheckJump(pmove_t* pm, playerState_t* pm_ps)
	{
		// no jumpin when prone
		if (pm_ps->eFlags & EF_PRONE)
		{
			return false;
		}

		// JPW NERVE -- jumping in multiplayer uses and requires sprint juice (to prevent turbo skating, sprint + jumps)
		// don't allow jump accel

		// rain - revert to using pmext for this since pmext is fixed now.
		// fix for #166
		if (pm->cmd.serverTime - p.pmext.jumpTime < ETJump::JUMP_DELAY_TIME)
		{
			if (ETJump::hasJustStoodUp())
			{
				return false;
			}
			if (pm->shared & BG_LEVEL_NO_JUMPDELAY)
			{
				if (pm->groundTrace.surfaceFlags & SURF_NOJUMPDELAY)
				{
					return false;
				}
			}
			else
			{
				if (!(pm->groundTrace.surfaceFlags & SURF_NOJUMPDELAY))
				{
					return false;
				}
			}
		}

		if (pm_ps->pm_flags & PMF_RESPAWNED)
		{
			return false;      // don't allow jump until all buttons are up
		}

		if (pm->cmd.upmove < 10)
		{
			// not holding jump
			return false;
		}

		// must wait for jump to be released
		if (pm_ps->pm_flags & PMF_JUMP_HELD)
		{
			// clear upmove so cmdscale doesn't lower running speed
			pm->cmd.upmove = 0;
			return false;
		}

		pm->groundPlane = qfalse;         // jumping away
		pm->walking = qfalse;
		pm_ps->pm_flags |= PMF_JUMP_HELD;
		p.pmext.isJumpLand = true;

		pm_ps->groundEntityNum = ENTITYNUM_NONE;
		pm_ps->velocity[2] = JUMP_VELOCITY;

		return true;
	}

	void Pmove::PM_CheckDuck(pmove_t* pm, playerState_t* pm_ps)
	{
		trace_t trace;

		// Ridah, modified this for configurable bounding boxes
		pm->mins[0] = pm_ps->mins[0];
		pm->mins[1] = pm_ps->mins[1];

		pm->maxs[0] = pm_ps->maxs[0];
		pm->maxs[1] = pm_ps->maxs[1];

		pm->mins[2] = pm_ps->mins[2];

		if (pm_ps->pm_type == PM_DEAD)
		{
			pm->maxs[2] = pm_ps->maxs[2];   // NOTE: must set death bounding box in game code
			pm_ps->viewheight = pm_ps->deadViewHeight;
			return;
		}

		if ((pm->cmd.upmove < 0 && !(pm_ps->eFlags & EF_MOUNTEDTANK) && !(pm_ps->pm_flags & PMF_LADDER)) || pm_ps->weapon == WP_MORTAR_SET)
		{   // duck
			pm_ps->pm_flags |= PMF_DUCKED;
		}
		else
		{   // stand up if possible
			if (pm_ps->pm_flags & PMF_DUCKED)
			{
				// try to stand up
				pm->maxs[2] = pm_ps->maxs[2];
				PM_TraceAll(&trace, pm_ps->origin, pm_ps->origin);
				if (!trace.allsolid)
				{
					pm_ps->pm_flags &= ~PMF_DUCKED;
				}
			}
		}

		if (pm_ps->pm_flags & PMF_DUCKED)
		{
			pm->maxs[2] = pm_ps->crouchMaxZ;
			pm_ps->viewheight = pm_ps->crouchViewHeight;
		}
		else
		{
			pm->maxs[2] = pm_ps->maxs[2];
			pm_ps->viewheight = pm_ps->standViewHeight;
		}
	}

	bool Pmove::PM_CheckProne(pmove_t* pm, playerState_t* pm_ps)
	{
		trace_t trace;
		//pm->trace(&trace, pm_ps->origin, pm_ps->mins, pm_ps->maxs, pm_ps->origin, pm_ps->clientNum, CONTENTS_NOPRONE);
		trap_CM_CapsuleTrace(&trace, pm_ps->origin, pm_ps->mins, pm_ps->maxs, pm_ps->origin, 0, CONTENTS_NOPRONE);

		if (pm->shared & BG_LEVEL_NO_PRONE)
		{
			if (trace.fraction == 1.0f)
			{
				pm_ps->eFlags &= ~EF_PRONE;
				pm_ps->eFlags &= ~EF_PRONE_MOVING;
				return false;
			}
		}
		else
		{
			if (trace.fraction != 1.0f)
			{
				pm_ps->eFlags &= ~EF_PRONE;
				pm_ps->eFlags &= ~EF_PRONE_MOVING;
				return false;
			}
		}

		if (!(pm_ps->eFlags & EF_PRONE))
		{
			// can't go prone on ladders
			if (pm_ps->pm_flags & PMF_LADDER)
			{
				return false;
			}

			// no prone when using mg42's
			if (pm_ps->persistant[PERS_HWEAPON_USE] || pm_ps->eFlags & EF_MOUNTEDTANK)
			{
				return false;
			}

			if (pm_ps->weaponDelay && pm_ps->weapon == WP_PANZERFAUST)
			{
				return false;
			}

			if (pm_ps->weapon == WP_MORTAR_SET)
			{
				return false;
			}

			// can't go prone while swimming
			if (pm->waterlevel > 1)
			{
				return false;
			}

			if (((pm_ps->pm_flags & PMF_DUCKED && pm->cmd.doubleTap == DT_FORWARD) ||
				(pm->cmd.wbuttons & WBUTTON_PRONE)) && pm->cmd.serverTime - p.pmext.proneTime > ETJump::PRONE_DELAY_TIME)
			{
				trace_t trace;

				pm->mins[0] = pm_ps->mins[0];
				pm->mins[1] = pm_ps->mins[1];

				pm->maxs[0] = pm_ps->maxs[0];
				pm->maxs[1] = pm_ps->maxs[1];

				pm->mins[2] = pm_ps->mins[2];
				pm->maxs[2] = pm_ps->crouchMaxZ;

				pm_ps->eFlags |= EF_PRONE;
				PM_TraceAll(&trace, pm_ps->origin, pm_ps->origin);
				pm_ps->eFlags &= ~EF_PRONE;

				if (!trace.startsolid && !trace.allsolid)
				{
					// go prone
					pm_ps->pm_flags |= PMF_DUCKED; // crouched as well
					pm_ps->eFlags |= EF_PRONE;
					p.pmext.proneTime = pm->cmd.serverTime; // timestamp 'go prone'
					p.pmext.proneGroundTime = pm->cmd.serverTime;
				}
			}
		}

		if (pm_ps->eFlags & EF_PRONE)
		{
			if (pm->waterlevel > 1 ||
				pm_ps->pm_type == PM_DEAD ||
				pm_ps->eFlags & EF_MOUNTEDTANK ||
				// zinx - what was the reason for this, anyway? removing fixes bug 424
				//			pm->cmd.serverTime - pm->pmext->proneGroundTime > 450 ||
				((pm->cmd.doubleTap == DT_BACK || pm->cmd.upmove > 10 || pm->cmd.wbuttons & WBUTTON_PRONE) && pm->cmd.serverTime - p.pmext.proneTime > ETJump::PRONE_DELAY_TIME))
			{
				trace_t trace;

				// see if we have the space to stop prone
				pm->mins[0] = pm_ps->mins[0];
				pm->mins[1] = pm_ps->mins[1];

				pm->maxs[0] = pm_ps->maxs[0];
				pm->maxs[1] = pm_ps->maxs[1];

				pm->mins[2] = pm_ps->mins[2];
				pm->maxs[2] = pm_ps->crouchMaxZ;

				pm_ps->eFlags &= ~EF_PRONE;
				PM_TraceAll(&trace, pm_ps->origin, pm_ps->origin);
				pm_ps->eFlags |= EF_PRONE;

				if (!trace.allsolid)
				{
					// crouch for a bit
					pm_ps->pm_flags |= PMF_DUCKED;

					// stop prone
					pm_ps->eFlags &= ~EF_PRONE;
					pm_ps->eFlags &= ~EF_PRONE_MOVING;
					p.pmext.proneTime = pm->cmd.serverTime; // timestamp 'stop prone'

					// don't jump for a bit
					p.pmext.jumpTime = pm->cmd.serverTime - ETJump::PRONE_JUMP_DELAY_TIME;
					pm_ps->jumpTime = pm->cmd.serverTime - ETJump::PRONE_JUMP_DELAY_TIME;
				}
			}
		}

		if (pm_ps->eFlags & EF_PRONE)
		{
			// See if we are moving
			float    spd = VectorLength(pm_ps->velocity);
			bool userinput = abs(pm->cmd.forwardmove) + abs(pm->cmd.rightmove) > 10 ? true : false;

			if (userinput && spd > 40.f && !(pm_ps->eFlags & EF_PRONE_MOVING))
			{
				pm_ps->eFlags |= EF_PRONE_MOVING;
			}
			else if (!userinput && spd < 20.0f && (pm_ps->eFlags & EF_PRONE_MOVING))
			{
				pm_ps->eFlags &= ~EF_PRONE_MOVING;
			}

			pm->mins[0] = pm_ps->mins[0];
			pm->mins[1] = pm_ps->mins[1];

			pm->maxs[0] = pm_ps->maxs[0];
			pm->maxs[1] = pm_ps->maxs[1];

			pm->mins[2] = pm_ps->mins[2];

			pm->maxs[2] = pm_ps->maxs[2] - pm_ps->standViewHeight - PRONE_VIEWHEIGHT;
			pm_ps->viewheight = PRONE_VIEWHEIGHT;

			return true;
		}

		return false;
	}

	void Pmove::PM_GroundTrace(pmove_t* pm, playerState_t* pm_ps, pml_t* pml)
	{
		vec3_t  point;
		trace_t trace;

		point[0] = pm_ps->origin[0];
		point[1] = pm_ps->origin[1];
		point[2] = pm_ps->origin[2] - 0.25f;

		//PM_TraceAllLegs(&trace, &pm->pmext->proneLegsOffset, pm_ps->origin, point);
		trap_CM_CapsuleTrace(&trace, pm_ps->origin, point, pm->mins, pm->maxs, 0, pm->tracemask);
		pm->groundTrace = trace;

		// do something corrective if the trace starts in a solid...
		if (trace.allsolid && !(pm_ps->eFlags & EF_MOUNTEDTANK))
		{
			if (!PM_CorrectAllSolid(&trace, pm, pm_ps))
			{
				return;
			}
		}

		// if the trace didn't hit anything, we are in free fall
		if (trace.fraction == 1.0)
		{
			PM_GroundTraceMissed(pm, pm_ps);
			return;
		}

		// check if getting thrown off the ground
		if (pm_ps->velocity[2] > 0 && DotProduct(pm_ps->velocity, trace.plane.normal) > 10)
		{
			pm_ps->groundEntityNum = ENTITYNUM_NONE;
			pm->groundPlane = qfalse;
			pm->walking = qfalse;
			return;
		}

		// slopes that are too steep will not be considered onground
		if (trace.plane.normal[2] < MIN_WALK_NORMAL)
		{
			// FIXME: if they can't slide down the slope, let them
			// walk (sharp crevices)
			pm_ps->groundEntityNum = ENTITYNUM_NONE;
			pm->groundPlane = qtrue;
			pm->walking = qfalse;
			return;
		}

		pm->groundPlane = qtrue;
		pm->walking = qtrue;

		// hitting solid ground will end a waterjump
		if (pm_ps->pm_flags & PMF_TIME_WATERJUMP)
		{
			pm_ps->pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND);
			pm_ps->pm_time = 0;
		}

		if (pm_ps->groundEntityNum == ENTITYNUM_NONE)
		{
			// just hit the ground
			// TODO: do we need this..?
			//Feen:
			// PM_CheckPortal();

			// don't do landing time if we were just going down a slope
			if (pml->previous_velocity[2] < -200)
			{
				// don't allow another jump for a little while
				pm_ps->pm_flags |= PMF_TIME_LAND;
				pm_ps->pm_time = 250;
			}
		}

		pm_ps->groundEntityNum = trace.entityNum;
	}

	void Pmove::PM_GroundTraceMissed(pmove_t* pm, playerState_t* pm_ps)
	{
		// If we've never yet touched the ground, it's because we're spawning, so don't
		// set to "in air"
		if (pm_ps->groundEntityNum != -1)
		{
			// Signify that we're in mid-air
			pm_ps->groundEntityNum = ENTITYNUM_NONE;

		} // if (pm_ps->groundEntityNum != -1)...
		pm->groundPlane = qfalse;
		pm->walking = qfalse;
	}

	bool Pmove::PM_CorrectAllSolid(trace_t* trace, pmove_t *pm, playerState_t* pm_ps)
	{
		vec3_t point;

		// jitter around
		for (auto i = -1; i <= 1; i++)
		{
			for (auto j = -1; j <= 1; j++)
			{
				for (auto k = -1; k <= 1; k++)
				{
					VectorCopy(pm_ps->origin, point);
					point[0] += (float)i;
					point[1] += (float)j;
					point[2] += (float)k;
					PM_TraceAll(trace, point, point);
					if (!trace->allsolid)
					{
						point[0] = pm_ps->origin[0];
						point[1] = pm_ps->origin[1];
						point[2] = pm_ps->origin[2] - 0.25;

						PM_TraceAll(trace, pm_ps->origin, point);
						pm->groundTrace = *trace;
						return true;
					}
				}
			}
		}

		pm_ps->groundEntityNum = ENTITYNUM_NONE;
		pm->groundPlane = qfalse;
		pm->walking = qfalse;

		return false;
	}

	void Pmove::PM_SetWaterLevel(pmove_t* pm, playerState_t* pm_ps)
	{
		vec3_t point;
		int    cont;
		int    sample1;
		int    sample2;

		// get waterlevel, accounting for ducking
		pm->waterlevel = 0;
		pm->watertype = 0;

		// Ridah, modified this
		point[0] = pm_ps->origin[0];
		point[1] = pm_ps->origin[1];
		point[2] = pm_ps->origin[2] + pm_ps->mins[2] + 1;
		//cont = pm->pointcontents(point, pm_ps->clientNum);
		cont = trap_CM_PointContents(point, 0);

		if (cont & MASK_WATER)
		{
			sample2 = pm_ps->viewheight - pm_ps->mins[2];
			sample1 = sample2 / 2;

			pm->watertype = cont;
			pm->waterlevel = 1;
			point[2] = pm_ps->origin[2] + pm_ps->mins[2] + sample1;
			cont = trap_CM_PointContents(point, 0);
			if (cont & MASK_WATER)
			{
				pm->waterlevel = 2;
				point[2] = pm_ps->origin[2] + pm_ps->mins[2] + sample2;
				cont = trap_CM_PointContents(point, 0);
				if (cont & MASK_WATER)
				{
					pm->waterlevel = 3;
				}
			}
		}
	}
}