From 4b149c103c004489f3058544370035c3c54058f1 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Sat, 21 May 2022 03:11:37 -0400 Subject: [PATCH 1/7] Bring back camera turn prediction REALLY complicated, because just emulating player turning in ticcmd code is broken. --- src/d_ticcmd.h | 3 +++ src/g_game.c | 37 +++++++++++++++++++++++++++++++++---- src/g_game.h | 4 ++++ src/k_kart.c | 13 ++++++++----- src/k_kart.h | 2 +- src/p_user.c | 39 +++++++++++++++++++++++++++++++-------- 6 files changed, 80 insertions(+), 18 deletions(-) diff --git a/src/d_ticcmd.h b/src/d_ticcmd.h index 226c8f4d8..1e9bd1612 100644 --- a/src/d_ticcmd.h +++ b/src/d_ticcmd.h @@ -52,6 +52,9 @@ typedef enum // ticcmd turning bits #define TICCMD_REDUCE 16 +// ticcmd latency mask +#define TICCMD_LATENCYMASK 0xFF + // ticcmd flags #define TICCMD_RECEIVED 1 #define TICCMD_TYPING 2/* chat window or console open */ diff --git a/src/g_game.c b/src/g_game.c index 8c42e4532..623811d78 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -871,6 +871,35 @@ static void G_HandleAxisDeadZone(UINT8 splitnum, joystickvector2_t *joystickvect INT32 localaiming[MAXSPLITSCREENPLAYERS]; angle_t localangle[MAXSPLITSCREENPLAYERS]; +INT32 localsteering[MAXSPLITSCREENPLAYERS]; +INT32 localdelta[MAXSPLITSCREENPLAYERS]; +INT32 localstoredeltas[MAXSPLITSCREENPLAYERS][TICCMD_LATENCYMASK + 1]; +UINT8 localtic; + +// Turning was removed from G_BuildTiccmd to prevent easy client hacking. +// This brings back the camera prediction that was lost. +static void G_DoAnglePrediction(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer, player_t *player) +{ + INT32 angleChange = 0; + + localtic = cmd->latency; + + while (realtics > 0) + { + localsteering[ssplayer - 1] = K_UpdateSteeringValue(localsteering[ssplayer - 1], cmd->turning); + angleChange = K_GetKartTurnValue(player, localsteering[ssplayer - 1]) << TICCMD_REDUCE; + + // Store the angle we applied to this tic, so we can revert it later. + // If we trust the camera to do all of the work, then it can get out of sync fast. + localstoredeltas[ssplayer - 1][cmd->latency] += angleChange; + localdelta[ssplayer - 1] += angleChange; + + realtics--; + } + + localangle[ssplayer - 1] = player->angleturn + localdelta[ssplayer - 1]; +} + void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) { const UINT8 forplayer = ssplayer-1; @@ -896,8 +925,6 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) boolean *rd = &resetdown[forplayer]; const boolean mouseaiming = player->spectator; - (void)realtics; - if (demo.playback) return; // Is there any reason this can't just be I_BaseTiccmd? @@ -1148,7 +1175,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) // Send leveltime when this tic was generated to the server for control lag calculations. // Only do this when in a level. Also do this after the hook, so that it can't overwrite this. - cmd->latency = (leveltime & 0xFF); + cmd->latency = (leveltime & TICCMD_LATENCYMASK); } if (cmd->forwardmove > MAXPLMOVE) @@ -1161,6 +1188,8 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) else if (cmd->turning < -KART_FULLTURN) cmd->turning = -KART_FULLTURN; + G_DoAnglePrediction(cmd, realtics, ssplayer, player); + // Reset away view if a command is given. if ((cmd->forwardmove || cmd->buttons) && !r_splitscreen && displayplayers[0] != consoleplayer && ssplayer == 1) @@ -1925,7 +1954,7 @@ void G_Ticker(boolean run) else { //@TODO add a cvar to allow setting this max - cmd->latency = min(((leveltime & 0xFF) - cmd->latency) & 0xFF, MAXPREDICTTICS-1); + cmd->latency = min(((leveltime & TICCMD_LATENCYMASK) - cmd->latency) & TICCMD_LATENCYMASK, MAXPREDICTTICS-1); } } } diff --git a/src/g_game.h b/src/g_game.h index cb0cae127..8b9aed2fd 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -117,6 +117,10 @@ INT32 PlayerJoyAxis(UINT8 player, axis_input_e axissel); extern angle_t localangle[MAXSPLITSCREENPLAYERS]; extern INT32 localaiming[MAXSPLITSCREENPLAYERS]; // should be an angle_t but signed +extern INT32 localsteering[MAXSPLITSCREENPLAYERS]; +extern INT32 localdelta[MAXSPLITSCREENPLAYERS]; +extern INT32 localstoredeltas[MAXSPLITSCREENPLAYERS][TICCMD_LATENCYMASK + 1]; +extern UINT8 localtic; // // GAME diff --git a/src/k_kart.c b/src/k_kart.c index 1071f0371..6844c08ae 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -8089,31 +8089,34 @@ static INT16 K_GetKartDriftValue(player_t *player, fixed_t countersteer) return basedrift + (FixedMul(driftadjust * FRACUNIT, countersteer) / FRACUNIT); } -void K_UpdateSteeringValue(player_t *player, INT16 destSteering) +INT16 K_UpdateSteeringValue(INT16 inputSteering, INT16 destSteering) { // player->steering is the turning value, but with easing applied. // Keeps micro-turning from old easing, but isn't controller dependent. const INT16 amount = KART_FULLTURN/4; - INT16 diff = destSteering - player->steering; + INT16 diff = destSteering - inputSteering; + INT16 outputSteering = inputSteering; if (abs(diff) <= amount) { // Reached the intended value, set instantly. - player->steering = destSteering; + outputSteering = destSteering; } else { // Go linearly towards the value we wanted. if (diff < 0) { - player->steering -= amount; + outputSteering -= amount; } else { - player->steering += amount; + outputSteering += amount; } } + + return outputSteering; } INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue) diff --git a/src/k_kart.h b/src/k_kart.h index aef18b19e..41465bc5e 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -97,7 +97,7 @@ player_t *K_FindJawzTarget(mobj_t *actor, player_t *source); INT32 K_GetKartRingPower(player_t *player, boolean boosted); void K_UpdateDistanceFromFinishLine(player_t *const player); boolean K_CheckPlayersRespawnColliding(INT32 playernum, fixed_t x, fixed_t y); -void K_UpdateSteeringValue(player_t *player, INT16 destSteering); +INT16 K_UpdateSteeringValue(INT16 inputSteering, INT16 destSteering); INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue); INT32 K_GetUnderwaterTurnAdjust(player_t *player); INT32 K_GetKartDriftSparkValue(player_t *player); diff --git a/src/p_user.c b/src/p_user.c index 8a5d9459c..de4dbb6e6 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1988,14 +1988,41 @@ static void P_3dMovement(player_t *player) static void P_UpdatePlayerAngle(player_t *player) { angle_t angleChange = ANGLE_MAX; + UINT8 p = UINT8_MAX; UINT8 i; - K_UpdateSteeringValue(player, player->cmd.turning); + for (i = 0; i <= r_splitscreen; i++) + { + if (player == &players[displayplayers[i]]) + { + p = i; + break; + } + } + + player->steering = K_UpdateSteeringValue(player->steering, player->cmd.turning); angleChange = K_GetKartTurnValue(player, player->steering) << TICCMD_REDUCE; - P_SetPlayerAngle(player, player->angleturn + angleChange); + player->angleturn += angleChange; player->mo->angle = player->angleturn; + if (p != UINT8_MAX) + { + UINT8 lateTic = ((leveltime - player->cmd.latency) & TICCMD_LATENCYMASK); + UINT8 clearTic = ((localtic + 1) & TICCMD_LATENCYMASK); + + // Undo the ticcmd's old emulated angle, + // now that we added the actual game logic angle. + + while (lateTic != clearTic) + { + localdelta[p] -= localstoredeltas[p][lateTic]; + localstoredeltas[p][lateTic] = 0; + + lateTic = (lateTic - 1) & TICCMD_LATENCYMASK; + } + } + if (!cv_allowmlook.value || player->spectator == false) { player->aiming = 0; @@ -2006,13 +2033,9 @@ static void P_UpdatePlayerAngle(player_t *player) player->aiming = G_ClipAimingPitch((INT32 *)&player->aiming); } - for (i = 0; i <= r_splitscreen; i++) + if (p != UINT8_MAX) { - if (player == &players[displayplayers[i]]) - { - localaiming[i] = player->aiming; - break; - } + localaiming[p] = player->aiming; } } From 0dc472a2556b4da0fbc2221ddf6b574bb5d17bff Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Sun, 22 May 2022 15:38:08 -0400 Subject: [PATCH 2/7] Try resetting driftend --- src/g_game.c | 42 ++++++++++++++++++++++++++++++++++-------- src/g_game.h | 1 + src/p_user.c | 1 + 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 1020883cf..37cfa2f65 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -876,6 +876,24 @@ INT32 localdelta[MAXSPLITSCREENPLAYERS]; INT32 localstoredeltas[MAXSPLITSCREENPLAYERS][TICCMD_LATENCYMASK + 1]; UINT8 localtic; +void G_ResetAnglePrediction(player_t *player) +{ + UINT16 i, j; + + for (i = 0; i <= r_splitscreen; i++) + { + if (&players[displayplayers[i]] == player) + { + localdelta[i] = 0; + for (j = 0; j < TICCMD_LATENCYMASK; j++) + { + localstoredeltas[i][j] = 0; + } + break; + } + } +} + // Turning was removed from G_BuildTiccmd to prevent easy client hacking. // This brings back the camera prediction that was lost. static void G_DoAnglePrediction(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer, player_t *player) @@ -884,17 +902,25 @@ static void G_DoAnglePrediction(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer, p localtic = cmd->latency; - while (realtics > 0) + if (player->pflags & PF_DRIFTEND) { - localsteering[ssplayer - 1] = K_UpdateSteeringValue(localsteering[ssplayer - 1], cmd->turning); - angleChange = K_GetKartTurnValue(player, localsteering[ssplayer - 1]) << TICCMD_REDUCE; + // Otherwise, your angle slingshots off to the side violently... + G_ResetAnglePrediction(player); + } + else + { + while (realtics > 0) + { + localsteering[ssplayer - 1] = K_UpdateSteeringValue(localsteering[ssplayer - 1], cmd->turning); + angleChange = K_GetKartTurnValue(player, localsteering[ssplayer - 1]) << TICCMD_REDUCE; - // Store the angle we applied to this tic, so we can revert it later. - // If we trust the camera to do all of the work, then it can get out of sync fast. - localstoredeltas[ssplayer - 1][cmd->latency] += angleChange; - localdelta[ssplayer - 1] += angleChange; + // Store the angle we applied to this tic, so we can revert it later. + // If we trust the camera to do all of the work, then it can get out of sync fast. + localstoredeltas[ssplayer - 1][cmd->latency] += angleChange; + localdelta[ssplayer - 1] += angleChange; - realtics--; + realtics--; + } } localangle[ssplayer - 1] = player->angleturn + localdelta[ssplayer - 1]; diff --git a/src/g_game.h b/src/g_game.h index 8b9aed2fd..52c7c7a0d 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -84,6 +84,7 @@ extern consvar_t cv_resume; // build an internal map name MAPxx from map number const char *G_BuildMapName(INT32 map); +void G_ResetAnglePrediction(player_t *player); void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer); // copy ticcmd_t to and fro the normal way diff --git a/src/p_user.c b/src/p_user.c index 6e08127d3..0d8f3e7ab 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -4874,6 +4874,7 @@ void P_ForceLocalAngle(player_t *player, angle_t angle) if (player == &players[displayplayers[i]]) { localangle[i] = angle; + G_ResetAnglePrediction(player); break; } } From 986bcddbfc2e6f8437d8264fe64ff79f5243b79d Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Sun, 22 May 2022 16:21:07 -0400 Subject: [PATCH 3/7] Ease prediction angle Might feel laggier, might feel better because it gets rid of jitter? idk --- src/g_game.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/g_game.c b/src/g_game.c index 37cfa2f65..03467a23f 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -899,6 +899,7 @@ void G_ResetAnglePrediction(player_t *player) static void G_DoAnglePrediction(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer, player_t *player) { INT32 angleChange = 0; + angle_t destAngle = player->angleturn; localtic = cmd->latency; @@ -923,7 +924,8 @@ static void G_DoAnglePrediction(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer, p } } - localangle[ssplayer - 1] = player->angleturn + localdelta[ssplayer - 1]; + destAngle = player->angleturn + localdelta[ssplayer - 1]; + localangle[ssplayer - 1] += (destAngle - localangle[ssplayer - 1]) / 2; } void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) From c9e685be66b2dd33f5f24c5ad38e8e972d2691a9 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Sun, 22 May 2022 16:30:27 -0400 Subject: [PATCH 4/7] Nope, that didn't work, move by your max turn speed until you reach it --- src/g_game.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 03467a23f..47bc94789 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -898,8 +898,10 @@ void G_ResetAnglePrediction(player_t *player) // This brings back the camera prediction that was lost. static void G_DoAnglePrediction(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer, player_t *player) { + const angle_t maxTurn = K_GetKartTurnValue(player, KART_FULLTURN) << TICCMD_REDUCE; INT32 angleChange = 0; - angle_t destAngle = player->angleturn; + INT32 destAngle = player->angleturn; + INT32 diff = 0; localtic = cmd->latency; @@ -925,7 +927,22 @@ static void G_DoAnglePrediction(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer, p } destAngle = player->angleturn + localdelta[ssplayer - 1]; - localangle[ssplayer - 1] += (destAngle - localangle[ssplayer - 1]) / 2; + diff = destAngle - localangle[ssplayer - 1]; + if (abs(diff) <= maxTurn) + { + localangle[ssplayer - 1] = destAngle; + } + else + { + if (diff > 0) + { + localangle[ssplayer - 1] += maxTurn; + } + else + { + localangle[ssplayer - 1] -= maxTurn; + } + } } void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) From ada93361373207d6f4340909da54078b8418111f Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Sun, 22 May 2022 17:49:34 -0400 Subject: [PATCH 5/7] OK go back to easing, but now actually work --- src/g_game.c | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 47bc94789..c6afebd0d 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -898,10 +898,9 @@ void G_ResetAnglePrediction(player_t *player) // This brings back the camera prediction that was lost. static void G_DoAnglePrediction(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer, player_t *player) { - const angle_t maxTurn = K_GetKartTurnValue(player, KART_FULLTURN) << TICCMD_REDUCE; - INT32 angleChange = 0; - INT32 destAngle = player->angleturn; - INT32 diff = 0; + angle_t angleChange = 0; + angle_t destAngle = player->angleturn; + angle_t diff = 0; localtic = cmd->latency; @@ -928,21 +927,17 @@ static void G_DoAnglePrediction(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer, p destAngle = player->angleturn + localdelta[ssplayer - 1]; diff = destAngle - localangle[ssplayer - 1]; - if (abs(diff) <= maxTurn) + + if (diff > ANGLE_180) { - localangle[ssplayer - 1] = destAngle; + diff = InvAngle(InvAngle(diff) / 2); } else { - if (diff > 0) - { - localangle[ssplayer - 1] += maxTurn; - } - else - { - localangle[ssplayer - 1] -= maxTurn; - } + diff /= 2; } + + localangle[ssplayer - 1] += diff; } void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) From 85aa7361c23857e1ca259feb1ff81a22aca889d7 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Sun, 22 May 2022 18:03:32 -0400 Subject: [PATCH 6/7] Put a comment about camera easing --- src/g_game.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/g_game.c b/src/g_game.c index c6afebd0d..21467e592 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -925,6 +925,9 @@ static void G_DoAnglePrediction(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer, p } } + // We COULD set it to destAngle directly... + // but this causes incredible jittering when the prediction turns out to be wrong. So we ease into it. + // Slight increased camera lag in all scenarios > Mostly lagless camera but with jittering destAngle = player->angleturn + localdelta[ssplayer - 1]; diff = destAngle - localangle[ssplayer - 1]; From 6f42d84b2afb1377eff6a059dec4dc90b096699d Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Sun, 22 May 2022 19:49:33 -0400 Subject: [PATCH 7/7] Fix turn prediction in F12 --- src/p_user.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/p_user.c b/src/p_user.c index de42228a7..2bd75f0c4 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2006,9 +2006,9 @@ static void P_UpdatePlayerAngle(player_t *player) UINT8 p = UINT8_MAX; UINT8 i; - for (i = 0; i <= r_splitscreen; i++) + for (i = 0; i <= splitscreen; i++) { - if (player == &players[displayplayers[i]]) + if (player == &players[g_localplayers[i]]) { p = i; break; @@ -2018,14 +2018,20 @@ static void P_UpdatePlayerAngle(player_t *player) player->steering = K_UpdateSteeringValue(player->steering, player->cmd.turning); angleChange = K_GetKartTurnValue(player, player->steering) << TICCMD_REDUCE; - player->angleturn += angleChange; - player->mo->angle = player->angleturn; - - if (p != UINT8_MAX) + if (p == UINT8_MAX) + { + // When F12ing players, set local angle directly. + P_SetPlayerAngle(player, player->angleturn + angleChange); + player->mo->angle = player->angleturn; + } + else { UINT8 lateTic = ((leveltime - player->cmd.latency) & TICCMD_LATENCYMASK); UINT8 clearTic = ((localtic + 1) & TICCMD_LATENCYMASK); + player->angleturn += angleChange; + player->mo->angle = player->angleturn; + // Undo the ticcmd's old emulated angle, // now that we added the actual game logic angle.