//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;
#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;
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;
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
{
// 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;
}
#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;
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
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)"};
===============================================================================
*/
-/*
-============
-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
/*
================
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;
===============================================================================
*/
-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
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))
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;
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;
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;
}
}