From f54ba922a92b5f7cfde71a6173afd32f9022b23c Mon Sep 17 00:00:00 2001 From: Sal Date: Tue, 25 Apr 2023 01:50:51 +0000 Subject: [PATCH] Merge branch 'fix-reset-interpolation' into 'master' P_Ticker: update view interpolation at the start of a tic See merge request KartKrew/Kart!1192 --- src/g_game.c | 2 + src/g_party.cpp | 326 ++++++++++++++++++++++++++++++++++++++++++++++++ src/p_setup.c | 35 +++--- src/p_telept.c | 1 + src/p_tick.c | 29 ++++- src/p_user.c | 1 - 6 files changed, 375 insertions(+), 19 deletions(-) create mode 100644 src/g_party.cpp diff --git a/src/g_game.c b/src/g_game.c index 5c78614d1..825c3f75e 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1849,6 +1849,8 @@ void G_ResetView(UINT8 viewnum, INT32 playernum, boolean onlyactive) { camerap = &camera[viewnum-1]; P_ResetCamera(&players[(*displayplayerp)], camerap); + + R_ResetViewInterpolation(viewnum); } if (viewnum > splits) diff --git a/src/g_party.cpp b/src/g_party.cpp new file mode 100644 index 000000000..1c82a2d1d --- /dev/null +++ b/src/g_party.cpp @@ -0,0 +1,326 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include "core/static_vec.hpp" +#include "cxxutil.hpp" + +#include "d_clisrv.h" // playerconsole +#include "doomdef.h" // MAXPLAYERS +#include "doomstat.h" // consoleplayer +#include "g_game.h" // localangle +#include "g_party.h" +#include "g_state.h" +#include "p_local.h" +#include "r_fps.h" +#include "r_main.h" // R_ExecuteSetViewSize + +namespace +{ + +using playernum_t = uint8_t; + +class Party +{ +public: + class Console + { + public: + // The Console class is basically analogous to + // a playernum except local splitscreen players only + // resolve to one playernum. + // + // Local splitscreen players are always joined with + // each other, so this lets just one party to refer to + // that group. + + Console(playernum_t player) + { + SRB2_ASSERT(player >= 0 && player < MAXPLAYERS); + + console_ = playerconsole[player]; + + SRB2_ASSERT(console_ >= 0 && console_ < MAXPLAYERS); + } + + operator playernum_t() const { return console_; } + + private: + playernum_t console_; + }; + + // + // Write Access Methods + // + + // Add a single player. + void add(playernum_t player) { vec_.push_back(player); } + + // Add every player from another party. + void add(const Party& party) { std::copy(party.vec_.begin(), party.vec_.end(), std::back_inserter(vec_)); } + + // Remove every player whose console is the same. + void remove(Console console) + { + auto it = std::remove_if(vec_.begin(), vec_.end(), [console](Console other) { return other == console; }); + + while (it < vec_.end()) + { + vec_.pop_back(); + } + } + + // + // Read Access Methods + // + + std::size_t size() const { return vec_.size(); } + + // The player at this position in the party. + playernum_t at(std::size_t i) const { return vec_[i]; } + playernum_t operator[](std::size_t i) const { return at(i); } + + // C array access to the raw player numbers. + const playernum_t* data() const { return &vec_[0]; } + + // True if the player is a member of this party. + bool contains(playernum_t player) const { return std::find(vec_.begin(), vec_.end(), player) != vec_.end(); } + + // True if the consoleplayer is a member of this party. + bool local() const + { + // consoleplayer is not valid yet. + if (!addedtogame) + { + return false; + } + + return contains(consoleplayer); + } + + // Returns a party composed of only the unique consoles + // from this party. + Party consoles() const + { + Party party; + + std::unique_copy(vec_.begin(), vec_.end(), std::back_inserter(party.vec_), std::equal_to()); + + return party; + } + + // If the party is local, set the correct viewports. + void rebuild_displayplayers() const + { + if (!local()) + { + return; + } + + // Rendering stuff is not valid outside of levels. + if (!G_GamestateUsesLevel()) + { + return; + } + + for (std::size_t i = 0; i < size(); ++i) + { + const playernum_t player = at(i); + + displayplayers[i] = player; + + // The order of displayplayers can change, which + // would make localangle invalid now. + localangle[i] = players[player].angleturn; + + P_ResetCamera(&players[player], &camera[i]); + + // Make sure the viewport doesn't interpolate at + // all into its new position -- just snap + // instantly into place. + R_ResetViewInterpolation(1 + i); + } + + r_splitscreen = size() - 1; + + R_ExecuteSetViewSize(); // present viewport + } + + // + // Iterators + // + + // Returns an iterator to the player within this party if + // they are a member. Else returns the end() iterator. + auto find(playernum_t player) const + { + return std::find(vec_.begin(), vec_.end(), player); + } + + // Iterator to the beginning of the party. + auto begin() const { return vec_.begin(); } + + // Iterator to the end of the party. + auto end() const { return vec_.end(); } + +private: + srb2::StaticVec vec_; +}; + +class PartyManager +{ +public: + // To avoid copying the same party to each local + // splitscreen player, all lookups will use the + // consoleplayer. + Party& operator [](Party::Console console) { return pool_[console]; } + +protected: + std::array pool_; +} +local_party; + +class FinalPartyManager : public PartyManager +{ +public: + // Adds guest's entire local splitscreen party to the + // host's party. If the operation succeeds, host and guest + // parties are guaranteed to be identical and the + // viewports are updated for every player involved. + bool join(Party::Console host, Party::Console guest) + { + Party &party = pool_[host]; + + // Already in the same party. + if (party.contains(guest)) + { + return false; + } + + // Parties do not fit when merged. + if (party.size() + local_party[guest].size() > MAXSPLITSCREENPLAYERS) + { + return false; + } + + // If the host party includes players from a local + // party, iterating the unique consoles avoids + // duplicate insertions of the guest. + for (Party::Console other : party.consoles()) + { + pool_[other].add(local_party[guest]); + } + + reset(guest, party); // assign new party to guest + + return true; + } + + // Removes a player from another party and assigns a new + // party. Viewports are updated for all players involved. + void reset(Party::Console player, const Party &party) + { + SRB2_ASSERT(party.size() > 0); + + remove(player); + + pool_[player] = party; + + party.rebuild_displayplayers(); + } + +private: + // Removes a player from every party they're in. Updates + // viewports for the players left behind. + void remove(Party::Console player) + { + Party &party = pool_[player]; + + // Iterate a COPY of party because this very party + // will be modified. + for (Party::Console member : Party(party)) + { + pool_[member].remove(player); + } + + party.rebuild_displayplayers(); // restore viewports for left behind party + } +} +final_party; + +}; // namespace + +INT32 splitscreen_invitations[MAXPLAYERS]; + +void G_ObliterateParties(void) +{ + final_party = {}; + local_party = {}; +} + +void G_DestroyParty(UINT8 player) +{ + local_party[player] = {}; + final_party[player] = {}; +} + +void G_BuildLocalSplitscreenParty(UINT8 player) +{ + local_party[player].add(player); + final_party[player] = local_party[player]; +} + +void G_JoinParty(UINT8 host, UINT8 guest) +{ + final_party.join(host, guest); +} + +void G_LeaveParty(UINT8 player) +{ + final_party.reset(player, local_party[player]); +} + +UINT8 G_LocalSplitscreenPartySize(UINT8 player) +{ + return local_party[player].size(); +} + +UINT8 G_PartySize(UINT8 player) +{ + return final_party[player].size(); +} + +boolean G_IsPartyLocal(UINT8 player) +{ + return final_party[player].local(); +} + +UINT8 G_PartyMember(UINT8 player, UINT8 index) +{ + SRB2_ASSERT(index < final_party[player].size()); + + return final_party[player][index]; +} + +const UINT8* G_PartyArray(UINT8 player) +{ + return final_party[player].data(); +} + +UINT8 G_PartyPosition(UINT8 player) +{ + const Party& party = final_party[player]; + + return party.find(player) - party.begin(); +} diff --git a/src/p_setup.c b/src/p_setup.c index 1b7b12a25..695ed26b1 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -8097,28 +8097,29 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) P_MapEnd(); // just in case MapLoad modifies tmthing } - // No render mode or reloading gamestate, stop here. - if (rendermode == render_none || reloadinggamestate) - return true; - R_ResetViewInterpolation(0); - R_ResetViewInterpolation(0); - R_UpdateMobjInterpolators(); + if (rendermode != render_none && reloadinggamestate == false) + { - // Title card! - G_StartTitleCard(); + R_ResetViewInterpolation(0); + R_ResetViewInterpolation(0); + R_UpdateMobjInterpolators(); - // Can the title card actually run, though? - if (!WipeStageTitle) - return true; - if (ranspecialwipe == 2) - return true; + // Title card! + G_StartTitleCard(); - // If so... - // but not if joining because the fade may time us out - if (!fromnetsave) - G_PreLevelTitleCard(); + // Can the title card actually run, though? + if (!WipeStageTitle) + return true; + if (ranspecialwipe == 2) + return true; + // If so... + // but not if joining because the fade may time us out + if (!fromnetsave) + G_PreLevelTitleCard(); + } + return true; } diff --git a/src/p_telept.c b/src/p_telept.c index 85c6dc66d..1d33625c2 100644 --- a/src/p_telept.c +++ b/src/p_telept.c @@ -17,6 +17,7 @@ #include "r_state.h" #include "s_sound.h" #include "r_main.h" +#include "r_fps.h" /** \brief The P_MixUp function diff --git a/src/p_tick.c b/src/p_tick.c index 0c54d854b..6f8a815d1 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -24,7 +24,9 @@ #include "lua_hook.h" #include "m_perfstats.h" #include "i_system.h" // I_GetPreciseTime +#include "i_video.h" #include "r_fps.h" +#include "r_main.h" // Object place #include "m_cheat.h" @@ -555,6 +557,13 @@ void P_Ticker(boolean run) players[i].jointime++; } + if (run) + { + // Update old view state BEFORE ticking so resetting + // the old interpolation state from game logic works. + R_UpdateViewInterpolation(); + } + if (objectplacing) { if (OP_FreezeObjectplace()) @@ -781,7 +790,25 @@ void P_Ticker(boolean run) if (run) { R_UpdateLevelInterpolators(); - R_UpdateViewInterpolation(); + + // Hack: ensure newview is assigned every tic. + // Ensures view interpolation is T-1 to T in poor network conditions + // We need a better way to assign view state decoupled from game logic + if (rendermode != render_none) + { + for (i = 0; i <= r_splitscreen; i++) + { + player_t *player = &players[displayplayers[i]]; + if (!player->mo) + continue; + const boolean skybox = (player->skybox.viewpoint && cv_skybox.value); // True if there's a skybox object and skyboxes are on + if (skybox) + { + R_SkyboxFrame(i); + } + R_SetupFrame(i); + } + } } P_MapEnd(); diff --git a/src/p_user.c b/src/p_user.c index 8a7db95ef..a57782bc5 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -3628,7 +3628,6 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall { P_MoveChaseCamera(player, thiscam, false); R_ResetViewInterpolation(num + 1); - R_ResetViewInterpolation(num + 1); } return (x == thiscam->x && y == thiscam->y && z == thiscam->z && angle == thiscam->aiming);