Skip to content

Commit

Permalink
nudgeoutofsolid: implement fallback for when cliphulls are in use
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
bones-was-here committed Aug 5, 2024
1 parent 9f8bfeb commit bca5b75
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 156 deletions.
3 changes: 2 additions & 1 deletion dpdefs/dpextensions.qc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
150 changes: 138 additions & 12 deletions phys.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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;
}
19 changes: 18 additions & 1 deletion phys.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;


Expand Down
6 changes: 0 additions & 6 deletions server.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion sv_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)"};
Expand Down
Loading

0 comments on commit bca5b75

Please sign in to comment.