Reworked clientside lerping

Now acts as a rubberbanding effect. The result is that movement is now considered correct and adjusted towards the real position if not rather than cautiously moving towards the predicted position.
This commit is contained in:
Boondorl 2024-04-26 15:12:47 -04:00 committed by Ricardo Luís Vaz Silva
parent 22fd9b66ad
commit 71c40432e5

View file

@ -103,16 +103,26 @@ CVAR(Bool, sv_singleplayerrespawn, false, CVAR_SERVERINFO | CVAR_CHEAT)
// Variables for prediction // Variables for prediction
CVAR (Bool, cl_noprediction, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (Bool, cl_noprediction, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR(Bool, cl_predict_specials, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) CVAR(Bool, cl_predict_specials, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
// Deprecated
CVAR(Float, cl_predict_lerpscale, 0.05f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Float, cl_predict_lerpthreshold, 2.00f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CUSTOM_CVAR(Float, cl_predict_lerpscale, 0.05f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) CUSTOM_CVAR(Float, cl_rubberband_scale, 0.3f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{ {
P_PredictionLerpReset(); if (self < 0.0f)
self = 0.0f;
else if (self > 1.0f)
self = 1.0f;
} }
CUSTOM_CVAR(Float, cl_predict_lerpthreshold, 2.00f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) CUSTOM_CVAR(Float, cl_rubberband_threshold, 20.0f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
if (self < 0.1f)
self = 0.1f;
}
CUSTOM_CVAR(Float, cl_rubberband_minmove, 20.0f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{ {
if (self < 0.1f) if (self < 0.1f)
self = 0.1f; self = 0.1f;
P_PredictionLerpReset();
} }
ColorSetList ColorSets; ColorSetList ColorSets;
@ -125,13 +135,9 @@ CUSTOM_CVAR(Float, fov, 90.f, CVAR_ARCHIVE | CVAR_USERINFO | CVAR_NOINITCALL)
p->SetFOV(fov); p->SetFOV(fov);
} }
struct PredictPos static DVector3 LastPredictedPosition;
{ static int LastPredictedPortalGroup;
int gametic; static int LastPredictedTic;
DVector3 pos;
DRotator angles;
} static PredictionLerpFrom, PredictionLerpResult, PredictionLast;
static int PredictionLerptics;
static player_t PredictionPlayerBackup; static player_t PredictionPlayerBackup;
static AActor *PredictionActor; static AActor *PredictionActor;
@ -1290,23 +1296,25 @@ void P_PlayerThink (player_t *player)
void P_PredictionLerpReset() void P_PredictionLerpReset()
{ {
PredictionLerptics = PredictionLast.gametic = PredictionLerpFrom.gametic = PredictionLerpResult.gametic = 0; LastPredictedPosition = DVector3{};
LastPredictedPortalGroup = 0;
LastPredictedTic = -1;
} }
bool P_LerpCalculate(AActor *pmo, PredictPos from, PredictPos to, PredictPos &result, float scale) void P_LerpCalculate(AActor* pmo, const DVector3& from, DVector3 &result, float scale, float threshold, float minMove)
{ {
DVector3 vecFrom = from.pos; DVector3 diff = pmo->Pos() - from;
DVector3 vecTo = to.pos; diff.XY() += pmo->Level->Displacements.getOffset(pmo->Sector->PortalGroup, pmo->Level->PointInSector(from.XY())->PortalGroup);
DVector3 vecResult; double dist = diff.Length();
vecResult = vecTo - vecFrom; if (dist <= max<float>(threshold, minMove))
vecResult *= scale; {
vecResult = vecResult + vecFrom; result = pmo->Pos();
DVector3 delta = vecResult - vecTo; return;
}
result.pos = pmo->Vec3Offset(vecResult - to.pos); diff /= dist;
diff *= min<double>(dist * (1.0f - scale), dist - minMove);
// As a fail safe, assume extrapolation is the threshold. result = pmo->Vec3Offset(-diff.X, -diff.Y, -diff.Z);
return (delta.LengthSquared() > cl_predict_lerpthreshold && scale <= 1.00f);
} }
template<class nodetype, class linktype> template<class nodetype, class linktype>
@ -1472,10 +1480,12 @@ void P_PredictPlayer (player_t *player)
} }
act->BlockNode = NULL; act->BlockNode = NULL;
// Values too small to be usable for lerping can be considered "off".
bool CanLerp = (!(cl_predict_lerpscale < 0.01f) && (ticdup == 1)), DoLerp = false;
// This essentially acts like a mini P_Ticker where only the stuff relevant to the client is actually // This essentially acts like a mini P_Ticker where only the stuff relevant to the client is actually
// called. Call order is preserved. // called. Call order is preserved.
bool rubberband = false;
DVector3 rubberbandPos = {};
const bool canRubberband = LastPredictedTic >= 0 && cl_rubberband_scale > 0.0f && cl_rubberband_scale < 1.0f;
const double rubberbandThreshold = max<float>(cl_rubberband_minmove, cl_rubberband_threshold);
for (int i = gametic; i < maxtic; ++i) for (int i = gametic; i < maxtic; ++i)
{ {
// Make sure any portal paths have been cleared from the previous movement. // Make sure any portal paths have been cleared from the previous movement.
@ -1484,58 +1494,52 @@ void P_PredictPlayer (player_t *player)
// Because we're always predicting, this will get set by teleporters and then can never unset itself in the renderer properly. // Because we're always predicting, this will get set by teleporters and then can never unset itself in the renderer properly.
player->mo->renderflags &= ~RF_NOINTERPOLATEVIEW; player->mo->renderflags &= ~RF_NOINTERPOLATEVIEW;
// Got snagged on something. Start correcting towards the player's final predicted position. We're
// being intentionally generous here by not really caring how the player got to that position, only
// that they ended up in the same spot on the same tick.
if (canRubberband && LastPredictedTic == i)
{
DVector3 diff = player->mo->Pos() - LastPredictedPosition;
diff += player->mo->Level->Displacements.getOffset(player->mo->Sector->PortalGroup, LastPredictedPortalGroup);
double dist = diff.LengthSquared();
if (dist >= EQUAL_EPSILON * EQUAL_EPSILON && dist > rubberbandThreshold * rubberbandThreshold)
{
rubberband = true;
rubberbandPos = player->mo->Pos();
}
}
player->cmd = localcmds[i % LOCALCMDTICS]; player->cmd = localcmds[i % LOCALCMDTICS];
player->mo->ClearInterpolation(); player->mo->ClearInterpolation();
player->mo->ClearFOVInterpolation(); player->mo->ClearFOVInterpolation();
P_PlayerThink (player); P_PlayerThink (player);
player->mo->Tick (); player->mo->Tick ();
if (CanLerp && PredictionLast.gametic > 0 && i == PredictionLast.gametic)
{
// Z is not compared as lifts will alter this with no apparent change
// Make lerping less picky by only testing whole units
DoLerp = (int)PredictionLast.pos.X != (int)player->mo->X() || (int)PredictionLast.pos.Y != (int)player->mo->Y();
// Aditional Debug information
if (developer >= DMSG_NOTIFY && DoLerp)
{
DPrintf(DMSG_NOTIFY, "Lerp! Ltic (%d) && Ptic (%d) | Lx (%f) && Px (%f) | Ly (%f) && Py (%f)\n",
PredictionLast.gametic, i,
(PredictionLast.pos.X), (player->mo->X()),
(PredictionLast.pos.Y), (player->mo->Y()));
}
}
} }
// TODO: This should be changed to a proper rubberbanding solution in the near future (only rubberband if there was if (rubberband)
// a mismatch between client's last predicted pos and current predicted pos).
if (CanLerp)
{ {
if (DoLerp) R_ClearInterpolationPath();
{ player->mo->renderflags &= ~RF_NOINTERPOLATEVIEW;
// If lerping is already in effect, use the previous camera postion so the view doesn't suddenly snap
PredictionLerpFrom = (PredictionLerptics == 0) ? PredictionLast : PredictionLerpResult;
PredictionLerptics = 1;
}
PredictionLast.gametic = maxtic - 1; DPrintf(DMSG_NOTIFY, "Prediction mismatch at (%.3f, %.3f, %.3f)\nExpected: (%.3f, %.3f, %.3f)\nCorrecting to (%.3f, %.3f, %.3f)\n",
PredictionLast.pos = player->mo->Pos(); LastPredictedPosition.X, LastPredictedPosition.Y, LastPredictedPosition.Z,
//PredictionLast.portalgroup = player->mo->Sector->PortalGroup; rubberbandPos.X, rubberbandPos.Y, rubberbandPos.Z,
player->mo->X(), player->mo->Y(), player->mo->Z());
if (PredictionLerptics > 0) DVector3 snapPos = {};
{ P_LerpCalculate(player->mo, LastPredictedPosition, snapPos, cl_rubberband_scale, cl_rubberband_threshold, cl_rubberband_minmove);
if (PredictionLerpFrom.gametic > 0 && player->mo->PrevPortalGroup = LastPredictedPortalGroup;
P_LerpCalculate(player->mo, PredictionLerpFrom, PredictionLast, PredictionLerpResult, (float)PredictionLerptics * cl_predict_lerpscale)) player->mo->Prev = LastPredictedPosition;
{ const double zOfs = player->viewz - player->mo->Z();
PredictionLerptics++; player->mo->SetXYZ(snapPos);
player->mo->SetXYZ(PredictionLerpResult.pos); player->viewz = snapPos.Z + zOfs;
}
else
{
PredictionLerptics = 0;
}
}
} }
// This is intentionally done after rubberbanding starts since it'll automatically smooth itself towards
// the right spot until it reaches it.
LastPredictedTic = maxtic;
LastPredictedPosition = player->mo->Pos();
LastPredictedPortalGroup = player->mo->Level->PointInSector(LastPredictedPosition)->PortalGroup;
} }
void P_UnPredictPlayer () void P_UnPredictPlayer ()