From bca5b75201307881fb7fcf0b28f8beceadc8b903 Mon Sep 17 00:00:00 2001
From: bones_was_here <bones_was_here@xonotic.au>
Date: Tue, 6 Aug 2024 01:50:34 +1000
Subject: [PATCH] nudgeoutofsolid: implement fallback for when cliphulls are in
 use

Fixes sv_gameplayfix_droptofloorstartsolid_nudgetocorrect and
DP_QC_NUDGEOUTOFSOLID doing nothing on Q1BSP.

Fixes the Q1BSP condition in PHYS_NudgeOutOfSolid() to handle the exact
issue (so mod_q1bsp_polygoncollisions is properly supported).

Migrates unsticking code from server-specific to common.

Updates documentation.

Signed-off-by: bones_was_here <bones_was_here@xonotic.au>
---
 dpdefs/dpextensions.qc |   3 +-
 phys.c                 | 150 +++++++++++++++++++++++++++++++++++++----
 phys.h                 |  19 +++++-
 server.h               |   6 --
 sv_main.c              |   2 +-
 sv_phys.c              | 141 ++------------------------------------
 6 files changed, 165 insertions(+), 156 deletions(-)

diff --git a/dpdefs/dpextensions.qc b/dpdefs/dpextensions.qc
index 42c8a2a2..d72666dc 100644
--- a/dpdefs/dpextensions.qc
+++ b/dpdefs/dpextensions.qc
@@ -2663,7 +2663,8 @@ float(entity ent) nudgeoutofsolid = #567;
 //description:
 //Attempts to move a stuck entity out of solid brushes, returning 1 if successful, 0 if it remains stuck, -1 if it wasn't stuck.
 //Note: makes only one tracebox call if the entity isn't stuck, so don't call tracebox just to see if you should call nudgeoutofsolid.
-//Currently has no effect on Q1BSP unless mod_q1bsp_polygoncollisions is enabled.
+//Uses a "smart" method considering surface properties and supporting multiple surfaces,
+//except on Q1BSP with mod_q1bsp_polygoncollisions 0 (there it falls back to the unsticking method).
 
 
 float(float dividend, float divisor) mod = #245;
diff --git a/phys.c b/phys.c
index 8d71b868..9aca309d 100644
--- a/phys.c
+++ b/phys.c
@@ -6,7 +6,134 @@
 #include "cl_collision.h"
 
 
-int PHYS_NudgeOutOfSolid(prvm_prog_t *prog, prvm_edict_t *ent)
+// TODO handle this in a nicer way...
+static inline trace_t PHYS_TraceBox(prvm_prog_t *prog, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask, int skipsupercontentsmask, int skipmaterialflagsmask, float extend, qbool hitnetworkbrushmodels, qbool hitnetworkplayers, int *hitnetworkentity, qbool hitcsqcentities)
+{
+	return (prog == SVVM_prog)
+		? SV_TraceBox(start, mins, maxs, end, type, passedict, hitsupercontentsmask, skipsupercontentsmask, skipmaterialflagsmask, extend)
+		: CL_TraceBox(start, mins, maxs, end, type, passedict, hitsupercontentsmask, skipsupercontentsmask, skipmaterialflagsmask, extend, hitnetworkbrushmodels, hitnetworkplayers, hitnetworkentity, hitcsqcentities);
+}
+
+/*
+============
+PHYS_TestEntityPosition
+
+returns true if the entity is in solid currently
+============
+*/
+qbool PHYS_TestEntityPosition (prvm_prog_t *prog, prvm_edict_t *ent, vec3_t offset)
+{
+	int hitsupercontentsmask = SV_GenericHitSuperContentsMask(ent);
+	int skipsupercontentsmask = 0;
+	int skipmaterialflagsmask = 0;
+	vec3_t org, entorigin, entmins, entmaxs;
+	trace_t trace;
+
+	VectorAdd(PRVM_serveredictvector(ent, origin), offset, org);
+	VectorCopy(PRVM_serveredictvector(ent, origin), entorigin);
+	VectorCopy(PRVM_serveredictvector(ent, mins), entmins);
+	VectorCopy(PRVM_serveredictvector(ent, maxs), entmaxs);
+	trace = PHYS_TraceBox(prog, org, entmins, entmaxs, entorigin, ((PRVM_serveredictfloat(ent, movetype) == MOVETYPE_FLY_WORLDONLY) ? MOVE_WORLDONLY : MOVE_NOMONSTERS), ent, hitsupercontentsmask, skipsupercontentsmask, skipmaterialflagsmask, collision_extendmovelength.value, true, false, NULL, false);
+	if (trace.startsupercontents & hitsupercontentsmask)
+		return true;
+	else
+	{
+		if (sv.worldmodel->brushq1.numclipnodes && !VectorCompare(PRVM_serveredictvector(ent, mins), PRVM_serveredictvector(ent, maxs)))
+		{
+			// q1bsp/hlbsp use hulls and if the entity does not exactly match
+			// a hull size it is incorrectly tested, so this code tries to
+			// 'fix' it slightly...
+			// FIXME: this breaks entities larger than the hull size
+			int i;
+			vec3_t v, m1, m2, s;
+			VectorAdd(org, entmins, m1);
+			VectorAdd(org, entmaxs, m2);
+			VectorSubtract(m2, m1, s);
+#define EPSILON (1.0f / 32.0f)
+			if (s[0] >= EPSILON*2) {m1[0] += EPSILON;m2[0] -= EPSILON;}
+			if (s[1] >= EPSILON*2) {m1[1] += EPSILON;m2[1] -= EPSILON;}
+			if (s[2] >= EPSILON*2) {m1[2] += EPSILON;m2[2] -= EPSILON;}
+			for (i = 0;i < 8;i++)
+			{
+				v[0] = (i & 1) ? m2[0] : m1[0];
+				v[1] = (i & 2) ? m2[1] : m1[1];
+				v[2] = (i & 4) ? m2[2] : m1[2];
+				if (SV_PointSuperContents(v) & hitsupercontentsmask)
+					return true;
+			}
+		}
+	}
+	// if the trace found a better position for the entity, move it there
+	if (VectorDistance2(trace.endpos, PRVM_serveredictvector(ent, origin)) >= 0.0001)
+	{
+#if 0
+		// please switch back to this code when trace.endpos sometimes being in solid bug is fixed
+		VectorCopy(trace.endpos, PRVM_serveredictvector(ent, origin));
+#else
+		// verify if the endpos is REALLY outside solid
+		VectorCopy(trace.endpos, org);
+		trace = PHYS_TraceBox(prog, org, entmins, entmaxs, org, MOVE_NOMONSTERS, ent, hitsupercontentsmask, skipsupercontentsmask, skipmaterialflagsmask, collision_extendmovelength.value, true, false, NULL, false);
+		if(trace.startsolid)
+			Con_Printf("PHYS_TestEntityPosition: trace.endpos detected to be in solid. NOT using it.\n");
+		else
+			VectorCopy(org, PRVM_serveredictvector(ent, origin));
+#endif
+	}
+	return false;
+}
+
+static float unstickoffsets[] =
+{
+	// poutting -/+z changes first as they are least weird
+	 0,  0,  -1,
+	 0,  0,  1,
+	 // x or y changes
+	-1,  0,  0,
+	 1,  0,  0,
+	 0, -1,  0,
+	 0,  1,  0,
+	 // x and y changes
+	-1, -1,  0,
+	 1, -1,  0,
+	-1,  1,  0,
+	 1,  1,  0,
+};
+
+unstickresult_t PHYS_UnstickEntityReturnOffset (prvm_prog_t *prog, prvm_edict_t *ent, vec3_t offset)
+{
+	int i, maxunstick;
+
+	// if not stuck in a bmodel, just return
+	if (!PHYS_TestEntityPosition(prog, ent, vec3_origin))
+		return UNSTICK_GOOD;
+
+	for (i = 0;i < (int)(sizeof(unstickoffsets) / sizeof(unstickoffsets[0]));i += 3)
+	{
+		if (!PHYS_TestEntityPosition(prog, ent, unstickoffsets + i))
+		{
+			VectorCopy(unstickoffsets + i, offset);
+			return UNSTICK_UNSTUCK;
+		}
+	}
+
+	maxunstick = (int) ((PRVM_serveredictvector(ent, maxs)[2] - PRVM_serveredictvector(ent, mins)[2]) * 0.36);
+	// magic number 0.36 allows unsticking by up to 17 units with the largest supported bbox
+
+	for(i = 2; i <= maxunstick; ++i)
+	{
+		VectorClear(offset);
+		offset[2] = -i;
+		if (!PHYS_TestEntityPosition(prog, ent, offset))
+			return UNSTICK_UNSTUCK;
+		offset[2] = i;
+		if (!PHYS_TestEntityPosition(prog, ent, offset))
+			return UNSTICK_UNSTUCK;
+	}
+
+	return UNSTICK_STUCK;
+}
+
+unstickresult_t PHYS_NudgeOutOfSolid(prvm_prog_t *prog, prvm_edict_t *ent)
 {
 	int bump, pass;
 	trace_t stucktrace;
@@ -30,8 +157,13 @@ int PHYS_NudgeOutOfSolid(prvm_prog_t *prog, prvm_edict_t *ent)
 
 	VectorCopy(PRVM_serveredictvector(ent, mins), stuckmins);
 	VectorCopy(PRVM_serveredictvector(ent, maxs), stuckmaxs);
-	if (worldmodel && worldmodel->brushq1.numclipnodes)
+	if (worldmodel && worldmodel->TraceBox != Mod_CollisionBIH_TraceBox)
+	{
 		separation = 0.0f; // when using hulls, it can not be enlarged
+
+		// FIXME: Mod_Q1BSP_TraceBox() doesn't support startdepth and startdepthnormal
+		return PHYS_UnstickEntityReturnOffset(prog, ent, testorigin); // fallback
+	}
 	else
 	{
 		stuckmins[0] -= separation;
@@ -49,10 +181,7 @@ int PHYS_NudgeOutOfSolid(prvm_prog_t *prog, prvm_edict_t *ent)
 		VectorCopy(PRVM_serveredictvector(ent, origin), testorigin);
 		for (bump = 0;bump < 10;bump++)
 		{
-			if (prog == SVVM_prog) // TODO: can we refactor to use a shared TraceBox or at least a func ptr for these cases?
-				stucktrace = SV_TraceBox(testorigin, stuckmins, stuckmaxs, testorigin, pass ? MOVE_WORLDONLY : MOVE_NOMONSTERS, ent, SV_GenericHitSuperContentsMask(ent), 0, 0, collision_extendmovelength.value);
-			else
-				stucktrace = CL_TraceBox(testorigin, stuckmins, stuckmaxs, testorigin, pass ? MOVE_WORLDONLY : MOVE_NOMONSTERS, ent, SV_GenericHitSuperContentsMask(ent), 0, 0, collision_extendmovelength.value, pass ? false : true, false, NULL, false);
+			stucktrace = PHYS_TraceBox(prog, testorigin, stuckmins, stuckmaxs, testorigin, pass ? MOVE_WORLDONLY : MOVE_NOMONSTERS, ent, SV_GenericHitSuperContentsMask(ent), 0, 0, collision_extendmovelength.value, pass ? false : true, false, NULL, false);
 
 			// Separation compared here to ensure a good location will be recognised reliably.
 			if (-stucktrace.startdepth <= separation
@@ -61,20 +190,17 @@ int PHYS_NudgeOutOfSolid(prvm_prog_t *prog, prvm_edict_t *ent)
 			{
 				// found a good location, use it
 				VectorCopy(testorigin, PRVM_serveredictvector(ent, origin));
-				return bump || pass ? 1 : -1; // -1 means it wasn't stuck
+				return bump || pass ? UNSTICK_UNSTUCK : UNSTICK_GOOD;
 			}
 
 			VectorMA(testorigin, -stucktrace.startdepth, stucktrace.startdepthnormal, targetorigin);
 			// Trace to targetorigin so we don't set it out of the world in complex cases.
-			if (prog == SVVM_prog)
-				stucktrace = SV_TraceBox(testorigin, stuckmins, stuckmaxs, targetorigin, pass ? MOVE_WORLDONLY : MOVE_NOMONSTERS, ent, SV_GenericHitSuperContentsMask(ent), 0, 0, collision_extendmovelength.value);
-			else
-				stucktrace = CL_TraceBox(testorigin, stuckmins, stuckmaxs, targetorigin, pass ? MOVE_WORLDONLY : MOVE_NOMONSTERS, ent, SV_GenericHitSuperContentsMask(ent), 0, 0, collision_extendmovelength.value, pass ? false : true, false, NULL, false);
+			stucktrace = PHYS_TraceBox(prog, testorigin, stuckmins, stuckmaxs, targetorigin, pass ? MOVE_WORLDONLY : MOVE_NOMONSTERS, ent, SV_GenericHitSuperContentsMask(ent), 0, 0, collision_extendmovelength.value, pass ? false : true, false, NULL, false);
 			if (stucktrace.fraction)
 				VectorCopy(stucktrace.endpos, testorigin);
 			else
 				break; // Can't move it so no point doing more iterations on this pass.
 		}
 	}
-	return 0;
+	return UNSTICK_STUCK;
 }
diff --git a/phys.h b/phys.h
index d18cfb92..cbb3d2cc 100644
--- a/phys.h
+++ b/phys.h
@@ -4,10 +4,27 @@
 #include "quakedef.h"
 
 
+qbool PHYS_TestEntityPosition (prvm_prog_t *prog, prvm_edict_t *ent, vec3_t offset);
+
+typedef enum unstickresult_e
+{
+	// matching the DP_QC_NUDGEOUTOFSOLID return values
+	UNSTICK_STUCK = 0,
+	UNSTICK_GOOD = -1, ///< didn't need to be unstuck
+	UNSTICK_UNSTUCK = 1
+}
+unstickresult_t;
+/*! move an entity that is stuck by small amounts in various directions to try to nudge it back into the collision hull
+ * returns 1 if it found a better place, 0 if it remains stuck, -1 if it wasn't stuck.
+ * Replaces SV_TryUnstick() and SV_CheckStuck() which in Quake applied to players only.
+ */
+unstickresult_t PHYS_UnstickEntityReturnOffset (prvm_prog_t *prog, prvm_edict_t *ent, vec3_t offset);
 /*! move an entity that is stuck out of the surface it is stuck in (can move large amounts)
+ * with consideration to the properties of the surface and support for multiple surfaces.
  * returns 1 if it found a better place, 0 if it remains stuck, -1 if it wasn't stuck.
+ * Replaces PHYS_UnstickEntityReturnOffset() but falls back to it when using cliphulls.
  */
-int PHYS_NudgeOutOfSolid(prvm_prog_t *prog, prvm_edict_t *ent);
+unstickresult_t PHYS_NudgeOutOfSolid(prvm_prog_t *prog, prvm_edict_t *ent);
 extern cvar_t cl_gameplayfix_nudgeoutofsolid_separation;
 
 
diff --git a/server.h b/server.h
index 4378e558..26ba7e69 100644
--- a/server.h
+++ b/server.h
@@ -563,12 +563,6 @@ void SV_LinkEdict(prvm_edict_t *ent);
 void SV_LinkEdict_TouchAreaGrid(prvm_edict_t *ent);
 void SV_LinkEdict_TouchAreaGrid_Call(prvm_edict_t *touch, prvm_edict_t *ent); // if we detected a touch from another source
 
-/*! move an entity that is stuck by small amounts in various directions to try to nudge it back into the collision hull
- * returns true if it found a better place
- * Replaces SV_TryUnstick() and SV_CheckStuck() which in Quake applied to players only.
- */
-qbool SV_UnstickEntity (prvm_edict_t *ent);
-
 /// calculates hitsupercontentsmask for a generic qc entity
 int SV_GenericHitSuperContentsMask(const prvm_edict_t *edict);
 /// traces a box move against worldmodel and all entities in the specified area
diff --git a/sv_main.c b/sv_main.c
index 23649b51..0743e0ed 100644
--- a/sv_main.c
+++ b/sv_main.c
@@ -115,7 +115,7 @@ cvar_t sv_gameplayfix_impactbeforeonground = {CF_SERVER, "sv_gameplayfix_impactb
 cvar_t sv_gameplayfix_multiplethinksperframe = {CF_SERVER, "sv_gameplayfix_multiplethinksperframe", "1", "allows entities to think more often than the server framerate, primarily useful for very high fire rate weapons"};
 cvar_t sv_gameplayfix_noairborncorpse = {CF_SERVER, "sv_gameplayfix_noairborncorpse", "1", "causes entities (corpses, items, etc) sitting ontop of moving entities (players) to fall when the moving entity (player) is no longer supporting them"};
 cvar_t sv_gameplayfix_noairborncorpse_allowsuspendeditems = {CF_SERVER, "sv_gameplayfix_noairborncorpse_allowsuspendeditems", "1", "causes entities sitting ontop of objects that are instantaneously remove to float in midair (special hack to allow a common level design trick for floating items)"};
-cvar_t sv_gameplayfix_nudgeoutofsolid = {CF_SERVER, "sv_gameplayfix_nudgeoutofsolid", "0", "attempts to fix physics errors where an object ended up in solid for some reason, better than sv_gameplayfix_unstick* but currently has no effect on Q1BSP (unless mod_q1bsp_polygoncollisions is enabled)"};
+cvar_t sv_gameplayfix_nudgeoutofsolid = {CF_SERVER, "sv_gameplayfix_nudgeoutofsolid", "0", "attempts to fix physics errors where an object ended up in solid for some reason, smarter than sv_gameplayfix_unstick* except on Q1BSP with mod_q1bsp_polygoncollisions disabled (there it falls back to the unsticking method)"};
 cvar_t sv_gameplayfix_nudgeoutofsolid_separation = {CF_SERVER, "sv_gameplayfix_nudgeoutofsolid_separation", "0.03125", "keep objects this distance apart to prevent collision issues on seams"};
 cvar_t sv_gameplayfix_q2airaccelerate = {CF_SERVER, "sv_gameplayfix_q2airaccelerate", "0", "Quake2-style air acceleration"};
 cvar_t sv_gameplayfix_nogravityonground = {CF_SERVER, "sv_gameplayfix_nogravityonground", "0", "turn off gravity when on ground (to get rid of sliding)"};
diff --git a/sv_phys.c b/sv_phys.c
index a61bc2f8..dbfc1baa 100644
--- a/sv_phys.c
+++ b/sv_phys.c
@@ -915,74 +915,6 @@ Utility functions
 ===============================================================================
 */
 
-/*
-============
-SV_TestEntityPosition
-
-returns true if the entity is in solid currently
-============
-*/
-static int SV_TestEntityPosition (prvm_edict_t *ent, vec3_t offset)
-{
-	prvm_prog_t *prog = SVVM_prog;
-	int hitsupercontentsmask = SV_GenericHitSuperContentsMask(ent);
-	int skipsupercontentsmask = 0;
-	int skipmaterialflagsmask = 0;
-	vec3_t org, entorigin, entmins, entmaxs;
-	trace_t trace;
-	VectorAdd(PRVM_serveredictvector(ent, origin), offset, org);
-	VectorCopy(PRVM_serveredictvector(ent, origin), entorigin);
-	VectorCopy(PRVM_serveredictvector(ent, mins), entmins);
-	VectorCopy(PRVM_serveredictvector(ent, maxs), entmaxs);
-	trace = SV_TraceBox(org, entmins, entmaxs, entorigin, ((PRVM_serveredictfloat(ent, movetype) == MOVETYPE_FLY_WORLDONLY) ? MOVE_WORLDONLY : MOVE_NOMONSTERS), ent, hitsupercontentsmask, skipsupercontentsmask, skipmaterialflagsmask, collision_extendmovelength.value);
-	if (trace.startsupercontents & hitsupercontentsmask)
-		return true;
-	else
-	{
-		if (sv.worldmodel->brushq1.numclipnodes && !VectorCompare(PRVM_serveredictvector(ent, mins), PRVM_serveredictvector(ent, maxs)))
-		{
-			// q1bsp/hlbsp use hulls and if the entity does not exactly match
-			// a hull size it is incorrectly tested, so this code tries to
-			// 'fix' it slightly...
-			// FIXME: this breaks entities larger than the hull size
-			int i;
-			vec3_t v, m1, m2, s;
-			VectorAdd(org, entmins, m1);
-			VectorAdd(org, entmaxs, m2);
-			VectorSubtract(m2, m1, s);
-#define EPSILON (1.0f / 32.0f)
-			if (s[0] >= EPSILON*2) {m1[0] += EPSILON;m2[0] -= EPSILON;}
-			if (s[1] >= EPSILON*2) {m1[1] += EPSILON;m2[1] -= EPSILON;}
-			if (s[2] >= EPSILON*2) {m1[2] += EPSILON;m2[2] -= EPSILON;}
-			for (i = 0;i < 8;i++)
-			{
-				v[0] = (i & 1) ? m2[0] : m1[0];
-				v[1] = (i & 2) ? m2[1] : m1[1];
-				v[2] = (i & 4) ? m2[2] : m1[2];
-				if (SV_PointSuperContents(v) & hitsupercontentsmask)
-					return true;
-			}
-		}
-	}
-	// if the trace found a better position for the entity, move it there
-	if (VectorDistance2(trace.endpos, PRVM_serveredictvector(ent, origin)) >= 0.0001)
-	{
-#if 0
-		// please switch back to this code when trace.endpos sometimes being in solid bug is fixed
-		VectorCopy(trace.endpos, PRVM_serveredictvector(ent, origin));
-#else
-		// verify if the endpos is REALLY outside solid
-		VectorCopy(trace.endpos, org);
-		trace = SV_TraceBox(org, entmins, entmaxs, org, MOVE_NOMONSTERS, ent, hitsupercontentsmask, skipsupercontentsmask, skipmaterialflagsmask, collision_extendmovelength.value);
-		if(trace.startsolid)
-			Con_Printf("SV_TestEntityPosition: trace.endpos detected to be in solid. NOT using it.\n");
-		else
-			VectorCopy(org, PRVM_serveredictvector(ent, origin));
-#endif
-	}
-	return false;
-}
-
 // DRESK - Support for Entity Contents Transition Event
 /*
 ================
@@ -1585,6 +1517,7 @@ The trace struct is filled with the trace that has been done.
 Returns true if the push did not result in the entity being teleported by QC code.
 ============
 */
+static qbool SV_UnstickEntity (prvm_edict_t *ent);
 static qbool SV_PushEntity (trace_t *trace, prvm_edict_t *ent, vec3_t push, qbool dotouch, qbool checkstuck)
 {
 	prvm_prog_t *prog = SVVM_prog;
@@ -2007,67 +1940,6 @@ CLIENT MOVEMENT
 ===============================================================================
 */
 
-static float unstickoffsets[] =
-{
-	// poutting -/+z changes first as they are least weird
-	 0,  0,  -1,
-	 0,  0,  1,
-	 // x or y changes
-	-1,  0,  0,
-	 1,  0,  0,
-	 0, -1,  0,
-	 0,  1,  0,
-	 // x and y changes
-	-1, -1,  0,
-	 1, -1,  0,
-	-1,  1,  0,
-	 1,  1,  0,
-};
-
-typedef enum unstickresult_e
-{
-	// matching the DP_QC_NUDGEOUTOFSOLID return values
-	UNSTICK_STUCK = 0,
-	UNSTICK_GOOD = -1, ///< didn't need to be unstuck
-	UNSTICK_UNSTUCK = 1
-}
-unstickresult_t;
-
-static unstickresult_t SV_UnstickEntityReturnOffset (prvm_edict_t *ent, vec3_t offset)
-{
-	prvm_prog_t *prog = SVVM_prog;
-	int i, maxunstick;
-
-	// if not stuck in a bmodel, just return
-	if (!SV_TestEntityPosition(ent, vec3_origin))
-		return UNSTICK_GOOD;
-
-	for (i = 0;i < (int)(sizeof(unstickoffsets) / sizeof(unstickoffsets[0]));i += 3)
-	{
-		if (!SV_TestEntityPosition(ent, unstickoffsets + i))
-		{
-			VectorCopy(unstickoffsets + i, offset);
-			return UNSTICK_UNSTUCK;
-		}
-	}
-
-	maxunstick = (int) ((PRVM_serveredictvector(ent, maxs)[2] - PRVM_serveredictvector(ent, mins)[2]) * 0.36);
-	// magic number 0.36 allows unsticking by up to 17 units with the largest supported bbox
-
-	for(i = 2; i <= maxunstick; ++i)
-	{
-		VectorClear(offset);
-		offset[2] = -i;
-		if (!SV_TestEntityPosition(ent, offset))
-			return UNSTICK_UNSTUCK;
-		offset[2] = i;
-		if (!SV_TestEntityPosition(ent, offset))
-			return UNSTICK_UNSTUCK;
-	}
-
-	return UNSTICK_STUCK;
-}
-
 /*
 =============
 SV_CheckStuck
@@ -2076,13 +1948,12 @@ This is a big hack to try and fix the rare case of getting stuck in the world
 clipping hull.
 =============
 */
-qbool SV_UnstickEntity (prvm_edict_t *ent)
+static qbool SV_UnstickEntity (prvm_edict_t *ent)
 {
 	prvm_prog_t *prog = SVVM_prog;
 	vec3_t offset;
 
-	if (sv_gameplayfix_nudgeoutofsolid.integer && sv_gameplayfix_nudgeoutofsolid_separation.value >= 0
-	&& sv.worldmodel->TraceBox == Mod_CollisionBIH_TraceBox) // Mod_Q1BSP_TraceBox doesn't support startdepth
+	if (sv_gameplayfix_nudgeoutofsolid.integer && sv_gameplayfix_nudgeoutofsolid_separation.value >= 0)
 	{
 		VectorCopy(PRVM_serveredictvector(ent, origin), offset);
 		switch (PHYS_NudgeOutOfSolid(prog, ent))
@@ -2105,7 +1976,7 @@ qbool SV_UnstickEntity (prvm_edict_t *ent)
 	if (!(PRVM_NUM_FOR_EDICT(ent) <= svs.maxclients ? sv_gameplayfix_unstickplayers : sv_gameplayfix_unstickentities).integer)
 		return false;
 
-	switch(SV_UnstickEntityReturnOffset(ent, offset))
+	switch(PHYS_UnstickEntityReturnOffset(prog, ent, offset))
 	{
 		case UNSTICK_GOOD:
 			return true;
@@ -2114,7 +1985,7 @@ qbool SV_UnstickEntity (prvm_edict_t *ent)
 			return true;
 		case UNSTICK_STUCK:
 			VectorSubtract(PRVM_serveredictvector(ent, oldorigin), PRVM_serveredictvector(ent, origin), offset);
-			if (!SV_TestEntityPosition(ent, offset))
+			if (!PHYS_TestEntityPosition(prog, ent, offset))
 			{
 				Con_DPrintf("Unstuck entity %i (classname \"%s\") by restoring oldorigin.\n", (int)PRVM_EDICT_TO_PROG(ent), PRVM_GetString(prog, PRVM_serveredictstring(ent, classname)));
 				return true;
@@ -2122,7 +1993,7 @@ qbool SV_UnstickEntity (prvm_edict_t *ent)
 			Con_DPrintf(CON_WARN "Stuck entity %i (classname \"%s\").\n", (int)PRVM_EDICT_TO_PROG(ent), PRVM_GetString(prog, PRVM_serveredictstring(ent, classname)));
 			return false;
 		default:
-			Con_Printf("SV_UnstickEntityReturnOffset returned a value outside its enum.\n");
+			Con_Printf("UnstickEntityReturnOffset returned a value outside its enum.\n");
 			return false;
 	}
 }
-- 
2.39.5