diff --git a/src/d_net.cpp b/src/d_net.cpp index d33f47e38..bc4bc7352 100644 --- a/src/d_net.cpp +++ b/src/d_net.cpp @@ -39,7 +39,6 @@ #include "cmdlib.h" #include "s_sound.h" #include "m_cheat.h" -#include "p_effect.h" #include "p_local.h" #include "c_dispatch.h" #include "sbar.h" @@ -670,6 +669,9 @@ void PlayerIsGone (int netnode, int netconsole) { int i; + if (!nodeingame[netnode]) + return; + for (i = netnode + 1; i < doomcom.numnodes; ++i) { if (nodeingame[i]) @@ -680,77 +682,40 @@ void PlayerIsGone (int netnode, int netconsole) doomcom.numnodes = netnode; } + if (playeringame[netconsole]) + { + players[netconsole].playerstate = PST_GONE; + } nodeingame[netnode] = false; - playeringame[netconsole] = false; nodejustleft[netnode] = false; - if (deathmatch) - { - Printf ("%s left the game with %d frags\n", - players[netconsole].userinfo.GetName(), - players[netconsole].fragcount); - } - else - { - Printf ("%s left the game\n", players[netconsole].userinfo.GetName()); - } - - // [RH] Revert each player to their own view if spying through the player who left - for (int ii = 0; ii < MAXPLAYERS; ++ii) - { - if (playeringame[ii] && players[ii].camera == players[netconsole].mo) - { - players[ii].camera = players[ii].mo; - if (ii == consoleplayer && StatusBar != NULL) - { - StatusBar->AttachToPlayer (&players[ii]); - } - } - } - - // [RH] Make the player disappear - FBehavior::StaticStopMyScripts (players[netconsole].mo); - if (players[netconsole].mo != NULL) - { - P_DisconnectEffect (players[netconsole].mo); - players[netconsole].mo->player = NULL; - players[netconsole].mo->Destroy (); - if (!(players[netconsole].mo->ObjectFlags & OF_EuthanizeMe)) - { // We just destroyed a morphed player, so now the original player - // has taken their place. Destroy that one too. - players[netconsole].mo->Destroy(); - } - players[netconsole].mo = NULL; - players[netconsole].camera = NULL; - } - // [RH] Let the scripts know the player left - FBehavior::StaticStartTypedScripts (SCRIPT_Disconnect, NULL, true, netconsole); if (netconsole == Net_Arbitrator) { - bglobal.RemoveAllBots (true); - Printf ("Removed all bots\n"); + bglobal.RemoveAllBots(true); + Printf("Removed all bots\n"); // Pick a new network arbitrator for (int i = 0; i < MAXPLAYERS; i++) { - if (playeringame[i] && !players[i].isbot) + if (i != netconsole && playeringame[i] && !players[i].isbot) { Net_Arbitrator = i; players[i].settings_controller = true; - Printf ("%s is the new arbitrator\n", players[i].userinfo.GetName()); + Printf("%s is the new arbitrator\n", players[i].userinfo.GetName()); break; } } - if (debugfile && NetMode == NET_PacketServer) + } + + if (debugfile && NetMode == NET_PacketServer) + { + if (Net_Arbitrator == consoleplayer) { - if (Net_Arbitrator == consoleplayer) - { - fprintf (debugfile, "I am the new master!\n"); - } - else - { - fprintf (debugfile, "Node %d is the new master!\n", nodeforplayer[Net_Arbitrator]); - } + fprintf(debugfile, "I am the new master!\n"); + } + else + { + fprintf(debugfile, "Node %d is the new master!\n", nodeforplayer[Net_Arbitrator]); } } @@ -1760,10 +1725,19 @@ void D_CheckNetGame (void) resendto[i] = 0; // which tic to start sending } + // Packet server has proven to be rather slow over the internet. Print a warning about it. + v = Args->CheckValue("-netmode"); + if (v != NULL && (atoi(v) != 0)) + { + Printf(TEXTCOLOR_YELLOW "Notice: Using PacketServer (netmode 1) over the internet is prone to running too slow on some internet configurations." + "\nIf the game is running well below excpected speeds, use netmode 0 (P2P) instead.\n"); + } + // I_InitNetwork sets doomcom and netgame if (I_InitNetwork ()) { - NetMode = NET_PacketServer; + // For now, stop auto selecting PacketServer, as it's more likely to cause confusion. + //NetMode = NET_PacketServer; } if (doomcom.id != DOOMCOM_ID) { diff --git a/src/d_player.h b/src/d_player.h index 070e4dba4..5affd6913 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -174,7 +174,8 @@ typedef enum PST_LIVE, // Playing or camping. PST_DEAD, // Dead on the ground, view follows killer. PST_REBORN, // Ready to restart/respawn??? - PST_ENTER // [BC] Entered the game + PST_ENTER, // [BC] Entered the game + PST_GONE // Player has left the game } playerstate_t; diff --git a/src/g_game.cpp b/src/g_game.cpp index 2058c2df0..95b0f587d 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -76,6 +76,7 @@ #include "d_net.h" #include "d_event.h" #include "p_acs.h" +#include "p_effect.h" #include "m_joy.h" #include "farchive.h" #include "r_renderer.h" @@ -1013,10 +1014,16 @@ void G_Ticker () // do player reborns if needed for (i = 0; i < MAXPLAYERS; i++) { - if (playeringame[i] && - (players[i].playerstate == PST_REBORN || players[i].playerstate == PST_ENTER)) + if (playeringame[i]) { - G_DoReborn (i, false); + if ((players[i].playerstate == PST_GONE)) + { + G_DoPlayerPop(i); + } + if ((players[i].playerstate == PST_REBORN || players[i].playerstate == PST_ENTER)) + { + G_DoReborn(i, false); + } } } @@ -1658,6 +1665,56 @@ void G_DoReborn (int playernum, bool freshbot) } } +// +// G_DoReborn +// +void G_DoPlayerPop(int playernum) +{ + playeringame[playernum] = false; + + if (deathmatch) + { + Printf("%s left the game with %d frags\n", + players[playernum].userinfo.GetName(), + players[playernum].fragcount); + } + else + { + Printf("%s left the game\n", players[playernum].userinfo.GetName()); + } + + // [RH] Revert each player to their own view if spying through the player who left + for (int ii = 0; ii < MAXPLAYERS; ++ii) + { + if (playeringame[ii] && players[ii].camera == players[playernum].mo) + { + players[ii].camera = players[ii].mo; + if (ii == consoleplayer && StatusBar != NULL) + { + StatusBar->AttachToPlayer(&players[ii]); + } + } + } + + // [RH] Make the player disappear + FBehavior::StaticStopMyScripts(players[playernum].mo); + if (players[playernum].mo != NULL) + { + P_DisconnectEffect(players[playernum].mo); + players[playernum].mo->player = NULL; + players[playernum].mo->Destroy(); + if (!(players[playernum].mo->ObjectFlags & OF_EuthanizeMe)) + { // We just destroyed a morphed player, so now the original player + // has taken their place. Destroy that one too. + players[playernum].mo->Destroy(); + } + players[playernum].mo = NULL; + players[playernum].camera = NULL; + } + // [RH] Let the scripts know the player left + FBehavior::StaticStartTypedScripts(SCRIPT_Disconnect, NULL, true, playernum); +} + void G_ScreenShot (char *filename) { shotfile = filename; diff --git a/src/g_game.h b/src/g_game.h index 051be86b1..4714d8b55 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -81,6 +81,7 @@ enum EFinishLevelType void G_PlayerFinishLevel (int player, EFinishLevelType mode, int flags); void G_DoReborn (int playernum, bool freshbot); +void G_DoPlayerPop(int playernum); // Adds pitch to consoleplayer's viewpitch and clamps it void G_AddViewPitch (int look); diff --git a/src/i_net.cpp b/src/i_net.cpp index 3ec9d7881..6fdcb52ba 100644 --- a/src/i_net.cpp +++ b/src/i_net.cpp @@ -110,6 +110,7 @@ const char *neterror (void); enum { PRE_CONNECT, // Sent from guest to host for initial connection + PRE_KEEPALIVE, PRE_DISCONNECT, // Sent from guest that aborts the game PRE_ALLHERE, // Sent from host to guest when everybody has connected PRE_CONACK, // Sent from host to guest to acknowledge PRE_CONNECT receipt @@ -548,10 +549,15 @@ bool Host_CheckForConnects (void *userdata) SendConAck (doomcom.numnodes, numplayers); } break; + + case PRE_KEEPALIVE: + break; } } if (doomcom.numnodes < numplayers) { + // Send message to everyone as a keepalive + SendConAck(doomcom.numnodes, numplayers); return false; } @@ -822,6 +828,10 @@ bool Guest_WaitForOthers (void *userdata) } } + packet.Fake = PRE_FAKE; + packet.Message = PRE_KEEPALIVE; + PreSend(&packet, 2, &sendaddress[1]); + return false; } diff --git a/src/p_local.h b/src/p_local.h index 8ba1dd40b..2dc8c773e 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -105,6 +105,7 @@ void P_FallingDamage (AActor *ent); void P_PlayerThink (player_t *player); void P_PredictPlayer (player_t *player); void P_UnPredictPlayer (); +void P_PredictionLerpReset(); // // P_MOBJ diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index 0cdefcf96..3e06561a9 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -4250,6 +4250,12 @@ APlayerPawn *P_SpawnPlayer (FPlayerStart *mthing, int playernum, int flags) if ((unsigned)playernum >= (unsigned)MAXPLAYERS || !playeringame[playernum]) return NULL; + // Old lerp data needs to go + if (playernum == consoleplayer) + { + P_PredictionLerpReset(); + } + p = &players[playernum]; if (p->cls == NULL) diff --git a/src/p_user.cpp b/src/p_user.cpp index ab9579dcd..6595f7309 100644 --- a/src/p_user.cpp +++ b/src/p_user.cpp @@ -63,6 +63,27 @@ static FRandom pr_skullpop ("SkullPop"); // Variables for prediction CVAR (Bool, cl_noprediction, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR(Bool, cl_predict_specials, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) + +CUSTOM_CVAR(Float, cl_predict_lerpscale, 0.05f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + P_PredictionLerpReset(); +} +CUSTOM_CVAR(Float, cl_predict_lerpthreshold, 2.00f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + if (self < 0.1f) + self = 0.1f; + P_PredictionLerpReset(); +} + +struct PredictPos +{ + int gametic; + FVector3 point; + fixed_t pitch; + fixed_t yaw; +} static PredictionLerpFrom, PredictionLerpResult, PredictionLast; +static int PredictionLerptics; + static player_t PredictionPlayerBackup; static BYTE PredictionActorBackup[sizeof(AActor)]; static TArray PredictionTouchingSectorsBackup; @@ -2636,6 +2657,22 @@ void P_PlayerThink (player_t *player) } } +void P_PredictionLerpReset() +{ + PredictionLerptics = PredictionLast.gametic = PredictionLerpFrom.gametic = PredictionLerpResult.gametic = 0; +} + +bool P_LerpCalculate(FVector3 from, FVector3 to, FVector3 &result, float scale) +{ + result = to - from; + result *= scale; + result = result + from; + FVector3 delta = result - to; + + // As a fail safe, assume extrapolation is the threshold. + return (delta.LengthSquared() > cl_predict_lerpthreshold && scale <= 1.00f); +} + void P_PredictPlayer (player_t *player) { int maxtic; @@ -2663,8 +2700,8 @@ void P_PredictPlayer (player_t *player) // Save original values for restoration later PredictionPlayerBackup = *player; - AActor *act = player->mo; - memcpy (PredictionActorBackup, &act->x, sizeof(AActor)-((BYTE *)&act->x-(BYTE *)act)); + APlayerPawn *act = player->mo; + memcpy(PredictionActorBackup, &act->x, sizeof(APlayerPawn) - ((BYTE *)&act->x - (BYTE *)act)); act->flags &= ~MF_PICKUP; act->flags2 &= ~MF2_PUSHWALL; @@ -2721,7 +2758,8 @@ void P_PredictPlayer (player_t *player) } act->BlockNode = NULL; - bool NoInterpolateOld = R_GetViewInterpolationStatus(); + // Values too small to be usable for lerping can be considered "off". + bool CanLerp = (!(cl_predict_lerpscale < 0.01f) && (ticdup == 1)), DoLerp = false, NoInterpolateOld = R_GetViewInterpolationStatus(); for (int i = gametic; i < maxtic; ++i) { if (!NoInterpolateOld) @@ -2730,6 +2768,47 @@ void P_PredictPlayer (player_t *player) player->cmd = localcmds[i % LOCALCMDTICS]; P_PlayerThink (player); player->mo->Tick (); + + if (CanLerp && PredictionLast.gametic > 0 && i == PredictionLast.gametic && !NoInterpolateOld) + { + // Z is not compared as lifts will alter this with no apparent change + DoLerp = (PredictionLast.point.X != FIXED2FLOAT(player->mo->x) || + PredictionLast.point.Y != FIXED2FLOAT(player->mo->y)); + } + } + + if (CanLerp) + { + if (NoInterpolateOld) + P_PredictionLerpReset(); + + else if (DoLerp) + { + // 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; + PredictionLast.point.X = FIXED2FLOAT(player->mo->x); + PredictionLast.point.Y = FIXED2FLOAT(player->mo->y); + PredictionLast.point.Z = FIXED2FLOAT(player->mo->z); + + if (PredictionLerptics > 0) + { + if (PredictionLerpFrom.gametic > 0 && + P_LerpCalculate(PredictionLerpFrom.point, PredictionLast.point, PredictionLerpResult.point, (float)PredictionLerptics * cl_predict_lerpscale)) + { + PredictionLerptics++; + player->mo->x = FLOAT2FIXED(PredictionLerpResult.point.X); + player->mo->y = FLOAT2FIXED(PredictionLerpResult.point.Y); + player->mo->z = FLOAT2FIXED(PredictionLerpResult.point.Z); + } + else + { + PredictionLerptics = 0; + } + } } } @@ -2742,9 +2821,12 @@ void P_UnPredictPlayer () if (player->cheats & CF_PREDICTING) { unsigned int i; - AActor *act = player->mo; + APlayerPawn *act = player->mo; AActor *savedcamera = player->camera; + TObjPtr InvSel = act->InvSel; + int inventorytics = player->inventorytics; + *player = PredictionPlayerBackup; // Restore the camera instead of using the backup's copy, because spynext/prev @@ -2752,7 +2834,7 @@ void P_UnPredictPlayer () player->camera = savedcamera; act->UnlinkFromWorld(); - memcpy(&act->x, PredictionActorBackup, sizeof(AActor)-((BYTE *)&act->x - (BYTE *)act)); + memcpy(&act->x, PredictionActorBackup, sizeof(APlayerPawn) - ((BYTE *)&act->x - (BYTE *)act)); // The blockmap ordering needs to remain unchanged, too. // Restore sector links and refrences. @@ -2857,6 +2939,9 @@ void P_UnPredictPlayer () } block = block->NextBlock; } + + act->InvSel = InvSel; + player->inventorytics = inventorytics; } } diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index 36ff3d8ab..13565da7f 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -1613,6 +1613,8 @@ OptionMenu NetworkOptions StaticText "Local options", 1 Option "Movement prediction", "cl_noprediction", "OffOn" Option "Predict line actions", "cl_predict_specials", "OnOff" + Slider "Prediction Lerp Scale", "cl_predict_lerpscale", 0.0, 0.5, 0.05 + Slider "Lerp Threshold", "cl_predict_lerpthreshold", 0.1, 16.0, 0.1 StaticText " " StaticText "Host options", 1 Option "Extra Tics", "net_extratic", "ExtraTicMode"