diff --git a/src/d_ticcmd.h b/src/d_ticcmd.h index 4b264a68a..4994cc206 100644 --- a/src/d_ticcmd.h +++ b/src/d_ticcmd.h @@ -50,6 +50,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 7915c0648..21467e592 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -871,6 +871,78 @@ 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; + +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) +{ + angle_t angleChange = 0; + angle_t destAngle = player->angleturn; + angle_t diff = 0; + + localtic = cmd->latency; + + if (player->pflags & PF_DRIFTEND) + { + // 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; + + realtics--; + } + } + + // 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]; + + if (diff > ANGLE_180) + { + diff = InvAngle(InvAngle(diff) / 2); + } + else + { + diff /= 2; + } + + localangle[ssplayer - 1] += diff; +} + void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) { const UINT8 forplayer = ssplayer-1; @@ -896,8 +968,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? @@ -1153,7 +1223,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) @@ -1171,6 +1241,8 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) else if (cmd->throwdir < -KART_FULLTURN) cmd->throwdir = -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) @@ -1936,7 +2008,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..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 @@ -117,6 +118,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 5f4531228..81374d9c6 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -8233,31 +8233,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 c0bd71462..20c8cd33f 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -98,7 +98,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 145835e48..2bd75f0c4 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2003,13 +2003,46 @@ 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 <= splitscreen; i++) + { + if (player == &players[g_localplayers[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->mo->angle = player->angleturn; + 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. + + while (lateTic != clearTic) + { + localdelta[p] -= localstoredeltas[p][lateTic]; + localstoredeltas[p][lateTic] = 0; + + lateTic = (lateTic - 1) & TICCMD_LATENCYMASK; + } + } if (!cv_allowmlook.value || player->spectator == false) { @@ -2021,13 +2054,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; } } @@ -4866,6 +4895,7 @@ void P_ForceLocalAngle(player_t *player, angle_t angle) if (player == &players[displayplayers[i]]) { localangle[i] = angle; + G_ResetAnglePrediction(player); break; } }