From 17b7a2f16d234d17e446b5492a42d9b59477c32f Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 3 Oct 2023 18:27:22 -0700 Subject: [PATCH 1/8] k_director.h: remove directorinfo extern, add K_DirectorIsEnabled --- src/k_director.c | 19 ++++++++++++++++++- src/k_director.h | 14 +------------- src/k_hud.c | 2 +- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/k_director.c b/src/k_director.c index d2d82212c..e229b46de 100644 --- a/src/k_director.c +++ b/src/k_director.c @@ -20,7 +20,18 @@ #define WALKBACKDIST 600 // how close should a trailing player be before we switch? #define PINCHDIST 30000 // how close should the leader be to be considered "end of race"? -struct directorinfo directorinfo; +struct directorinfo +{ + boolean active; // is view point switching enabled? + tic_t cooldown; // how long has it been since we last switched? + tic_t freeze; // when nonzero, fixed switch pending, freeze logic! + INT32 attacker; // who to switch to when freeze delay elapses + INT32 maxdist; // how far is the closest player from finishing? + + INT32 sortedplayers[MAXPLAYERS]; // position-1 goes in, player index comes out. + INT32 gap[MAXPLAYERS]; // gap between a given position and their closest pursuer + INT32 boredom[MAXPLAYERS]; // how long has a given position had no credible attackers? +} directorinfo; void K_InitDirector(void) { @@ -324,6 +335,12 @@ void K_ToggleDirector(boolean active) directorinfo.active = active; } +boolean K_DirectorIsEnabled(UINT8 viewnum) +{ + (void)viewnum; + return directorinfo.active; +} + boolean K_DirectorIsAvailable(UINT8 viewnum) { return viewnum <= r_splitscreen && viewnum < G_PartySize(consoleplayer) && diff --git a/src/k_director.h b/src/k_director.h index 297bff72e..0de45dbc6 100644 --- a/src/k_director.h +++ b/src/k_director.h @@ -10,24 +10,12 @@ extern "C" { #endif -extern struct directorinfo -{ - boolean active; // is view point switching enabled? - tic_t cooldown; // how long has it been since we last switched? - tic_t freeze; // when nonzero, fixed switch pending, freeze logic! - INT32 attacker; // who to switch to when freeze delay elapses - INT32 maxdist; // how far is the closest player from finishing? - - INT32 sortedplayers[MAXPLAYERS]; // position-1 goes in, player index comes out. - INT32 gap[MAXPLAYERS]; // gap between a given position and their closest pursuer - INT32 boredom[MAXPLAYERS]; // how long has a given position had no credible attackers? -} directorinfo; - void K_InitDirector(void); void K_UpdateDirector(void); void K_DrawDirectorDebugger(void); void K_DirectorFollowAttack(player_t *player, mobj_t *inflictor, mobj_t *source); void K_ToggleDirector(boolean active); +boolean K_DirectorIsEnabled(UINT8 viewnum); boolean K_DirectorIsAvailable(UINT8 viewnum); #ifdef __cplusplus diff --git a/src/k_hud.c b/src/k_hud.c index a5dc2b1d8..4ca34618a 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -5225,7 +5225,7 @@ static void K_drawDirectorHUD(void) // TODO: this is too close to the screen bottom K_DrawDirectorButton(offs + 2, "Director", kp_button_r, - (directorinfo.active ? V_YELLOWMAP : 0)); + (K_DirectorIsEnabled(viewnum) ? V_YELLOWMAP : 0)); if (players[p].flashing) itemtxt = ". . ."; From 412b37a8de1a7f97238b817aa8616fcd4453d48b Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 3 Oct 2023 18:35:29 -0700 Subject: [PATCH 2/8] Convert k_director.c to k_director.cpp --- src/CMakeLists.txt | 2 +- src/{k_director.c => k_director.cpp} | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) rename src/{k_director.c => k_director.cpp} (98%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b7cce51f6..0f157f735 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -137,7 +137,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 k_terrain.c k_brightmap.c k_terrain.c - k_director.c + k_director.cpp k_follower.c k_profiles.c k_specialstage.c diff --git a/src/k_director.c b/src/k_director.cpp similarity index 98% rename from src/k_director.c rename to src/k_director.cpp index e229b46de..64f82cb59 100644 --- a/src/k_director.c +++ b/src/k_director.cpp @@ -3,6 +3,8 @@ /// \file k_director.c /// \brief SRB2kart automatic spectator camera. +#include + #include "k_kart.h" #include "k_respawn.h" #include "doomdef.h" @@ -97,7 +99,7 @@ static void K_UpdateDirectorPositions(void) if (directorinfo.gap[position] >= BREAKAWAYDIST) { - directorinfo.boredom[position] = min(BOREDOMTIME * 2, directorinfo.boredom[position] + 1); + directorinfo.boredom[position] = std::min(BOREDOMTIME * 2, directorinfo.boredom[position] + 1); } else if (directorinfo.boredom[position] > 0) { From 49898abfebd8003198f14bfa90555bc165bb4f84 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 3 Oct 2023 20:18:54 -0700 Subject: [PATCH 3/8] Let Director operate on separate splitscreens --- src/g_build_ticcmd.cpp | 6 +- src/k_director.cpp | 472 ++++++++++++++++++++++------------------- src/k_director.h | 2 +- src/p_mobj.c | 36 ++-- src/p_user.c | 5 +- 5 files changed, 281 insertions(+), 240 deletions(-) diff --git a/src/g_build_ticcmd.cpp b/src/g_build_ticcmd.cpp index b81c205e4..1e1f0bee5 100644 --- a/src/g_build_ticcmd.cpp +++ b/src/g_build_ticcmd.cpp @@ -253,13 +253,13 @@ class TiccmdBuilder if (M_MenuButtonPressed(pid, MBT_A)) { G_AdjustView(ssplayer, 1, true); - K_ToggleDirector(false); + K_ToggleDirector(forplayer(), false); } if (M_MenuButtonPressed(pid, MBT_X)) { G_AdjustView(ssplayer, -1, true); - K_ToggleDirector(false); + K_ToggleDirector(forplayer(), false); } if (player()->spectator == true) @@ -272,7 +272,7 @@ class TiccmdBuilder if (M_MenuButtonPressed(pid, MBT_R)) { - K_ToggleDirector(true); + K_ToggleDirector(forplayer(), true); } } diff --git a/src/k_director.cpp b/src/k_director.cpp index 64f82cb59..f04d2dfca 100644 --- a/src/k_director.cpp +++ b/src/k_director.cpp @@ -4,54 +4,30 @@ /// \brief SRB2kart automatic spectator camera. #include +#include + +#include "cxxutil.hpp" #include "k_kart.h" #include "k_respawn.h" #include "doomdef.h" +#include "doomstat.h" #include "g_game.h" #include "v_video.h" #include "k_director.h" #include "d_netcmd.h" #include "p_local.h" #include "g_party.h" +#include "command.h" -#define SWITCHTIME TICRATE * 5 // cooldown between unforced switches -#define BOREDOMTIME 3 * TICRATE / 2 // how long until players considered far apart? -#define TRANSFERTIME TICRATE // how long to delay reaction shots? -#define BREAKAWAYDIST 4000 // how *far* until players considered far apart? -#define WALKBACKDIST 600 // how close should a trailing player be before we switch? -#define PINCHDIST 30000 // how close should the leader be to be considered "end of race"? +extern "C" consvar_t cv_devmode_screen; -struct directorinfo -{ - boolean active; // is view point switching enabled? - tic_t cooldown; // how long has it been since we last switched? - tic_t freeze; // when nonzero, fixed switch pending, freeze logic! - INT32 attacker; // who to switch to when freeze delay elapses - INT32 maxdist; // how far is the closest player from finishing? - - INT32 sortedplayers[MAXPLAYERS]; // position-1 goes in, player index comes out. - INT32 gap[MAXPLAYERS]; // gap between a given position and their closest pursuer - INT32 boredom[MAXPLAYERS]; // how long has a given position had no credible attackers? -} directorinfo; - -void K_InitDirector(void) -{ - INT32 playernum; - - directorinfo.active = false; - directorinfo.cooldown = SWITCHTIME; - directorinfo.freeze = 0; - directorinfo.attacker = 0; - directorinfo.maxdist = 0; - - for (playernum = 0; playernum < MAXPLAYERS; playernum++) - { - directorinfo.sortedplayers[playernum] = -1; - directorinfo.gap[playernum] = INT32_MAX; - directorinfo.boredom[playernum] = 0; - } -} +#define SWITCHTIME TICRATE * 5 // cooldown between unforced switches +#define BOREDOMTIME 3 * TICRATE / 2 // how long until players considered far apart? +#define TRANSFERTIME TICRATE // how long to delay reaction shots? +#define BREAKAWAYDIST 4000 // how *far* until players considered far apart? +#define WALKBACKDIST 600 // how close should a trailing player be before we switch? +#define PINCHDIST 30000 // how close should the leader be to be considered "end of race"? static fixed_t K_GetFinishGap(INT32 leader, INT32 follower) { @@ -68,116 +44,257 @@ static fixed_t K_GetFinishGap(INT32 leader, INT32 follower) } } -static void K_UpdateDirectorPositions(void) +struct DirectorInfo { - INT32 playernum; - INT32 position; - player_t* target; + UINT8 viewnum; // which screen does this director apply to? + boolean active = false; // is view point switching enabled? + tic_t cooldown = SWITCHTIME; // how long has it been since we last switched? + tic_t freeze = 0; // when nonzero, fixed switch pending, freeze logic! + INT32 attacker = 0; // who to switch to when freeze delay elapses + INT32 maxdist = 0; // how far is the closest player from finishing? - memset(directorinfo.sortedplayers, -1, sizeof(directorinfo.sortedplayers)); - - for (playernum = 0; playernum < MAXPLAYERS; playernum++) + struct PlayerStat { - target = &players[playernum]; + INT32 sorted = -1; // position-1 goes in, player index comes out. + INT32 gap = INT32_MAX; // gap between a given position and their closest pursuer + INT32 boredom = 0; // how long has a given position had no credible attackers? + } + playerstat[MAXPLAYERS]; - if (playeringame[playernum] && !target->spectator && target->position > 0) + DirectorInfo(UINT8 viewnum_) : viewnum(viewnum_) {} + + INT32 viewplayernum() const { return displayplayers[viewnum]; } + player_t* viewplayer() const { return &players[viewplayernum()]; } + + void update() + { + INT32 targetposition; + + update_positions(); + + if (cooldown > 0) { + cooldown--; + } + + // handle pending forced switches + if (freeze > 0) { - directorinfo.sortedplayers[target->position - 1] = playernum; + if (!(--freeze)) + change(attacker, true); + + return; + } + + // if there's only one player left in the list, just switch to that player + if (playerstat[0].sorted != -1 && playerstat[1].sorted == -1) + { + change(playerstat[0].sorted, false); + return; + } + + // aaight, time to walk through the standings to find the first interesting pair + // NB: targetposition/PlayerStat::sorted is 0-indexed, aiming at the "back half" of a given pair by default. + // we adjust for this when comparing to player->position or when looking at the leading player, Don't Freak Out + for (targetposition = 1; targetposition < MAXPLAYERS; targetposition++) + { + INT32 target; + + // you are out of players, try again + if (playerstat[targetposition].sorted == -1) + { + break; + } + + // pair too far apart? try the next one + if (playerstat[targetposition - 1].boredom >= BOREDOMTIME) + { + continue; + } + + // pair finished? try the next one + if (players[playerstat[targetposition].sorted].exiting) + { + continue; + } + + // don't risk switching away from forward pairs at race end, might miss something! + if (maxdist > PINCHDIST) + { + // if the "next" player is close enough, they should be able to see everyone fine! + // walk back through the standings to find a vantage that gets everyone in frame. + // (also creates a pretty cool effect w/ overtakes at speed) + while (targetposition < MAXPLAYERS && playerstat[targetposition].gap < WALKBACKDIST) + { + targetposition++; + } + } + + target = playerstat[targetposition].sorted; + + // stop here since we're already viewing this player + if (viewplayernum() == target) + { + break; + } + + // if this is a splitscreen player, try next pair + if (P_IsDisplayPlayer(&players[target])) + { + continue; + } + + // if we're certain the back half of the pair is actually in this position, try to switch + if (!players[target].positiondelay) + { + change(target, false); + } + + // even if we're not certain, if we're certain we're watching the WRONG player, try to switch + if (viewplayer()->position != targetposition+1 && !viewplayer()->positiondelay) + { + change(target, false); + } + + break; } } - for (position = 0; position < MAXPLAYERS - 1; position++) + void force_switch(INT32 player, INT32 time) { - directorinfo.gap[position] = INT32_MAX; - - if (directorinfo.sortedplayers[position] == -1 || directorinfo.sortedplayers[position + 1] == -1) + if (players[player].exiting) { - continue; + return; } - directorinfo.gap[position] = P_ScaleFromMap(K_GetFinishGap(directorinfo.sortedplayers[position], directorinfo.sortedplayers[position + 1]), FRACUNIT); - - if (directorinfo.gap[position] >= BREAKAWAYDIST) - { - directorinfo.boredom[position] = std::min(BOREDOMTIME * 2, directorinfo.boredom[position] + 1); - } - else if (directorinfo.boredom[position] > 0) - { - directorinfo.boredom[position]--; - } + attacker = player; + freeze = time; } - directorinfo.maxdist = P_ScaleFromMap(players[directorinfo.sortedplayers[0]].distancetofinish, FRACUNIT); +private: + void update_positions() + { + INT32 playernum; + INT32 position; + player_t* target; + + for (PlayerStat& stat : playerstat) + { + stat.sorted = -1; + } + + for (playernum = 0; playernum < MAXPLAYERS; playernum++) + { + target = &players[playernum]; + + if (playeringame[playernum] && !target->spectator && target->position > 0) + { + playerstat[target->position - 1].sorted = playernum; + } + } + + for (position = 0; position < MAXPLAYERS - 1; position++) + { + playerstat[position].gap = INT32_MAX; + + if (playerstat[position].sorted == -1 || playerstat[position + 1].sorted == -1) + { + continue; + } + + playerstat[position].gap = P_ScaleFromMap( + K_GetFinishGap(playerstat[position].sorted, playerstat[position + 1].sorted), + FRACUNIT + ); + + if (playerstat[position].gap >= BREAKAWAYDIST) + { + playerstat[position].boredom = std::min(BOREDOMTIME * 2, playerstat[position].boredom + 1); + } + else if (playerstat[position].boredom > 0) + { + playerstat[position].boredom--; + } + } + + maxdist = P_ScaleFromMap(players[playerstat[0].sorted].distancetofinish, FRACUNIT); + } + + bool can_change() const + { + if (viewplayer()->trickpanel > 0) + { + return false; + } + + if (cooldown > 0) + { + return false; + } + + return true; + } + + void change(INT32 player, boolean force) + { + if (!active) + { + return; + } + + if (players[player].exiting) + { + return; + } + + if (!force && !can_change()) + { + return; + } + + G_ResetView(1 + viewnum, player, true); + cooldown = SWITCHTIME; + } +}; + +struct DirectorInfoManager +{ + DirectorInfo& operator [](UINT8 viewnum) + { + SRB2_ASSERT(viewnum < MAXSPLITSCREENPLAYERS); + return info_[viewnum]; + } + + auto begin() { return info_.begin(); } + auto end() { return std::next(begin(), r_splitscreen + 1); } + +private: + static_assert(MAXSPLITSCREENPLAYERS == 4); + std::array info_{0, 1, 2, 3}; } +g_directorinfo; -static boolean K_CanSwitchDirector(void) +void K_InitDirector(void) { - INT32 *displayplayerp = &displayplayers[0]; - - if (players[*displayplayerp].trickpanel > 0) - { - return false; - } - - if (directorinfo.cooldown > 0) - { - return false; - } - - return true; -} - -static void K_DirectorSwitch(INT32 player, boolean force) -{ - if (!directorinfo.active) - { - return; - } - - if (P_IsDisplayPlayer(&players[player])) - { - return; - } - - if (players[player].exiting) - { - return; - } - - if (!force && !K_CanSwitchDirector()) - { - return; - } - - G_ResetView(1, player, true); - directorinfo.cooldown = SWITCHTIME; -} - -static void K_DirectorForceSwitch(INT32 player, INT32 time) -{ - if (players[player].exiting) - { - return; - } - - directorinfo.attacker = player; - directorinfo.freeze = time; + g_directorinfo = {}; } void K_DirectorFollowAttack(player_t *player, mobj_t *inflictor, mobj_t *source) { - if (!P_IsDisplayPlayer(player)) + for (DirectorInfo& directorinfo : g_directorinfo) { - return; - } + if (directorinfo.viewplayer() != player) + { + continue; + } - if (inflictor && inflictor->player) - { - K_DirectorForceSwitch(inflictor->player - players, TRANSFERTIME); - } - else if (source && source->player) - { - K_DirectorForceSwitch(source->player - players, TRANSFERTIME); + if (inflictor && inflictor->player) + { + directorinfo.force_switch(inflictor->player - players, TRANSFERTIME); + } + else if (source && source->player) + { + directorinfo.force_switch(source->player - players, TRANSFERTIME); + } } } @@ -193,6 +310,8 @@ void K_DrawDirectorDebugger(void) return; } + DirectorInfo& directorinfo = g_directorinfo[cv_devmode_screen.value]; + V_DrawThinString(10, 0, V_70TRANS, va("PLACE")); V_DrawThinString(40, 0, V_70TRANS, va("CONF?")); V_DrawThinString(80, 0, V_70TRANS, va("GAP")); @@ -203,8 +322,8 @@ void K_DrawDirectorDebugger(void) for (position = 0; position < MAXPLAYERS - 1; position++) { ytxt = 10 * (position + 1); - leader = directorinfo.sortedplayers[position]; - follower = directorinfo.sortedplayers[position + 1]; + leader = directorinfo.playerstat[position].sorted; + follower = directorinfo.playerstat[position + 1].sorted; if (leader == -1 || follower == -1) break; @@ -217,15 +336,15 @@ void K_DrawDirectorDebugger(void) V_DrawThinString(40, ytxt, V_70TRANS, va("NG")); } - V_DrawThinString(80, ytxt, V_70TRANS, va("%d", directorinfo.gap[position])); + V_DrawThinString(80, ytxt, V_70TRANS, va("%d", directorinfo.playerstat[position].gap)); - if (directorinfo.boredom[position] >= BOREDOMTIME) + if (directorinfo.playerstat[position].boredom >= BOREDOMTIME) { V_DrawThinString(120, ytxt, V_70TRANS, va("BORED")); } else { - V_DrawThinString(120, ytxt, V_70TRANS, va("%d", directorinfo.boredom[position])); + V_DrawThinString(120, ytxt, V_70TRANS, va("%d", directorinfo.playerstat[position].boredom)); } V_DrawThinString(150, ytxt, V_70TRANS, va("%s", player_names[leader])); @@ -235,100 +354,16 @@ void K_DrawDirectorDebugger(void) void K_UpdateDirector(void) { - INT32 *displayplayerp = &displayplayers[0]; - INT32 targetposition; - - K_UpdateDirectorPositions(); - - if (directorinfo.cooldown > 0) { - directorinfo.cooldown--; - } - - // handle pending forced switches - if (directorinfo.freeze > 0) + for (DirectorInfo& directorinfo : g_directorinfo) { - if (!(--directorinfo.freeze)) - K_DirectorSwitch(directorinfo.attacker, true); - - return; - } - - // if there's only one player left in the list, just switch to that player - if (directorinfo.sortedplayers[0] != -1 && directorinfo.sortedplayers[1] == -1) - { - K_DirectorSwitch(directorinfo.sortedplayers[0], false); - return; - } - - // aaight, time to walk through the standings to find the first interesting pair - // NB: targetposition/sortedplayers is 0-indexed, aiming at the "back half" of a given pair by default. - // we adjust for this when comparing to player->position or when looking at the leading player, Don't Freak Out - for (targetposition = 1; targetposition < MAXPLAYERS; targetposition++) - { - INT32 target; - - // you are out of players, try again - if (directorinfo.sortedplayers[targetposition] == -1) - { - break; - } - - // pair too far apart? try the next one - if (directorinfo.boredom[targetposition - 1] >= BOREDOMTIME) - { - continue; - } - - // pair finished? try the next one - if (players[directorinfo.sortedplayers[targetposition]].exiting) - { - continue; - } - - // don't risk switching away from forward pairs at race end, might miss something! - if (directorinfo.maxdist > PINCHDIST) - { - // if the "next" player is close enough, they should be able to see everyone fine! - // walk back through the standings to find a vantage that gets everyone in frame. - // (also creates a pretty cool effect w/ overtakes at speed) - while (targetposition < MAXPLAYERS && directorinfo.gap[targetposition] < WALKBACKDIST) - { - targetposition++; - } - } - - target = directorinfo.sortedplayers[targetposition]; - - // stop here since we're already viewing this player - if (*displayplayerp == target) - { - break; - } - - // if this is a splitscreen player, try next pair - if (P_IsDisplayPlayer(&players[target])) - { - continue; - } - - // if we're certain the back half of the pair is actually in this position, try to switch - if (!players[target].positiondelay) - { - K_DirectorSwitch(target, false); - } - - // even if we're not certain, if we're certain we're watching the WRONG player, try to switch - if (players[*displayplayerp].position != targetposition+1 && !players[*displayplayerp].positiondelay) - { - K_DirectorSwitch(target, false); - } - - break; + directorinfo.update(); } } -void K_ToggleDirector(boolean active) +void K_ToggleDirector(UINT8 viewnum, boolean active) { + DirectorInfo& directorinfo = g_directorinfo[viewnum]; + if (directorinfo.active != active) { directorinfo.cooldown = 0; // switch immediately @@ -339,8 +374,7 @@ void K_ToggleDirector(boolean active) boolean K_DirectorIsEnabled(UINT8 viewnum) { - (void)viewnum; - return directorinfo.active; + return g_directorinfo[viewnum].active; } boolean K_DirectorIsAvailable(UINT8 viewnum) diff --git a/src/k_director.h b/src/k_director.h index 0de45dbc6..eeefb3e02 100644 --- a/src/k_director.h +++ b/src/k_director.h @@ -14,7 +14,7 @@ void K_InitDirector(void); void K_UpdateDirector(void); void K_DrawDirectorDebugger(void); void K_DirectorFollowAttack(player_t *player, mobj_t *inflictor, mobj_t *source); -void K_ToggleDirector(boolean active); +void K_ToggleDirector(UINT8 viewnum, boolean active); boolean K_DirectorIsEnabled(UINT8 viewnum); boolean K_DirectorIsAvailable(UINT8 viewnum); diff --git a/src/p_mobj.c b/src/p_mobj.c index 12fe75e93..d68232db3 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -50,6 +50,7 @@ #include "k_director.h" #include "m_easing.h" #include "k_podium.h" +#include "g_party.h" actioncache_t actioncachehead; @@ -12217,22 +12218,29 @@ void P_SpawnPlayer(INT32 playernum) } } - // Spectating when there is literally any other player in - // the level enables director cam. Or if the first player - // enters the game, spectate them. - // TODO: how do we support splitscreen? - if (playernum == consoleplayer || pcount == 1) - { - K_ToggleDirector(players[consoleplayer].spectator && pcount > 0); - } + boolean director = p->spectator && pcount > 0; - // TODO: handle splitscreen - // Spectators can switch to freecam. This should be - // disabled when they enter the race, or when the level - // changes. - if (playernum == consoleplayer && !demo.playback) + if (G_IsPartyLocal(playernum)) { - demo.freecam = false; + // Spectating when there is literally any other + // player in the level enables director cam. + K_ToggleDirector(G_PartyPosition(playernum), director); + + // Spectators can switch to freecam. This should be + // disabled when they enter the race, or when the level + // changes. + if (!demo.playback) + { + demo.freecam = false; + } + } + else if (pcount == 1) + { + // If the first player enters the game, view them. + for (i = 0; i <= r_splitscreen; ++i) + { + K_ToggleDirector(i, director); + } } } diff --git a/src/p_user.c b/src/p_user.c index 30266cb02..feab05130 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2802,11 +2802,10 @@ static void P_DeathThink(player_t *player) } } - // TODO: support splitscreen // Spectate another player after 2 seconds - if (player == &players[consoleplayer] && playerGone == true && (gametyperules & GTR_BUMPERS) && player->deadtimer == 2*TICRATE) + if (G_IsPartyLocal(player - players) && playerGone == true && (gametyperules & GTR_BUMPERS) && player->deadtimer == 2*TICRATE) { - K_ToggleDirector(true); + K_ToggleDirector(G_PartyPosition(player - players), true); } // Keep time rolling From fa89576f34898d1ec0fb928292db0e0dafa8c0dc Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 3 Oct 2023 20:20:27 -0700 Subject: [PATCH 4/8] Move freecam state to camera_t, lets splitscreen players use freecam independently of each other --- src/d_netcmd.c | 2 +- src/g_build_ticcmd.cpp | 12 ++++---- src/g_demo.c | 11 ++++--- src/g_game.c | 2 +- src/k_hud.c | 8 ++--- src/k_kart.c | 2 +- src/k_roulette.c | 6 ++-- src/menus/transient/pause-replay.c | 6 +++- src/p_local.h | 15 ++++------ src/p_mobj.c | 2 +- src/p_user.c | 48 +++++++++++++++--------------- src/r_main.c | 6 ++-- src/r_main.h | 2 +- src/s_sound.c | 16 +++++----- src/st_stuff.c | 2 +- 15 files changed, 73 insertions(+), 67 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index e5f461985..acb00e589 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1645,7 +1645,7 @@ static void Command_View_f(void) return; } - if (demo.freecam) + if (camera[viewnum-1].freecam) return; displayplayerp = &displayplayers[viewnum-1]; diff --git a/src/g_build_ticcmd.cpp b/src/g_build_ticcmd.cpp index 1e1f0bee5..72dbe83f4 100644 --- a/src/g_build_ticcmd.cpp +++ b/src/g_build_ticcmd.cpp @@ -86,6 +86,8 @@ class TiccmdBuilder UINT8 forplayer() const { return ssplayer - 1; } player_t* player() const { return &players[g_localplayers[forplayer()]]; } + bool freecam() const { return camera[forplayer()].freecam; } + UINT8 swap_ssplayer() const { if (ssplayer == cv_1pswap.value) @@ -239,13 +241,13 @@ class TiccmdBuilder { if (M_MenuButtonPressed(pid, MBT_C)) { - P_ToggleDemoCamera(); + P_ToggleDemoCamera(forplayer()); } } bool director_input() { - if (demo.freecam || !K_DirectorIsAvailable(viewnum)) + if (freecam() || !K_DirectorIsAvailable(viewnum)) { return false; } @@ -283,7 +285,7 @@ class TiccmdBuilder bool spectator_analog_input() { - if (!player()->spectator && !objectplacing && !demo.freecam) + if (!player()->spectator && !objectplacing && !freecam()) { return false; } @@ -406,7 +408,7 @@ public: common_button_input(); }; - if (demo.playback || demo.freecam || player()->spectator) + if (demo.playback || freecam() || player()->spectator) { // freecam is controllable even while paused @@ -416,7 +418,7 @@ public: { regular_input(); - if (demo.freecam) + if (freecam()) { toggle_freecam_input(); } diff --git a/src/g_demo.c b/src/g_demo.c index 95f5a0ac3..5c5c58fe0 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -1653,9 +1653,6 @@ void G_ConfirmRewind(tic_t rewindtime) COM_BufInsertText("renderview on\n"); - if (demo.freecam) - return; // don't touch from there - splitscreen = oldss; displayplayers[0] = olddp1; displayplayers[1] = olddp2; @@ -4126,7 +4123,13 @@ void G_StopDemo(void) demo.timing = false; singletics = false; - demo.freecam = false; + { + UINT8 i; + for (i = 0; i < MAXSPLITSCREENPLAYERS; ++i) + { + camera[i].freecam = false; + } + } Z_Free(demo.skinlist); demo.skinlist = NULL; diff --git a/src/g_game.c b/src/g_game.c index f6587e492..2ecb144e1 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1364,7 +1364,7 @@ boolean G_Responder(event_t *ev) } } - if (gamestate == GS_LEVEL && ev->type == ev_keydown && multiplayer && demo.playback && !demo.freecam) + if (gamestate == GS_LEVEL && ev->type == ev_keydown && multiplayer && demo.playback) { // Allow pausing if ( diff --git a/src/k_hud.c b/src/k_hud.c index 4ca34618a..ed033d3d9 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -3381,7 +3381,7 @@ static boolean K_ShowPlayerNametag(player_t *p) return false; } - if (demo.playback == true && demo.freecam == true) + if (demo.playback == true && camera[R_GetViewNumber()].freecam == true) { return true; } @@ -3762,7 +3762,7 @@ static void K_drawKartNameTags(void) if (result.onScreen == true) { - if (!(demo.playback == true && demo.freecam == true) && P_IsDisplayPlayer(ntplayer) && + if (!(demo.playback == true && camera[cnum].freecam == true) && P_IsDisplayPlayer(ntplayer) && ntplayer != &players[displayplayers[cnum]]) { localindicator = G_PartyPosition(ntplayer - players); @@ -5455,8 +5455,8 @@ static void K_DrawGPRankDebugger(void) void K_drawKartHUD(void) { boolean islonesome = false; - boolean freecam = demo.freecam; //disable some hud elements w/ freecam UINT8 viewnum = R_GetViewNumber(); + boolean freecam = camera[viewnum].freecam; //disable some hud elements w/ freecam // Define the X and Y for each drawn object // This is handled by console/menu values @@ -5531,7 +5531,7 @@ void K_drawKartHUD(void) } } - if (!stplyr->spectator && !demo.freecam) // Bottom of the screen elements, don't need in spectate mode + if (!stplyr->spectator && !freecam) // Bottom of the screen elements, don't need in spectate mode { if (demo.title) // Draw title logo instead in demo.titles { diff --git a/src/k_kart.c b/src/k_kart.c index 65f7baf2b..cb2b5f538 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -12279,7 +12279,7 @@ void K_EggmanTransfer(player_t *source, player_t *victim) victim->eggmanexplode = 6*TICRATE; K_StopRoulette(&victim->itemRoulette); - if (P_IsDisplayPlayer(victim) && !demo.freecam) + if (P_IsDisplayPlayer(victim)) S_StartSound(NULL, sfx_itrole); K_AddHitLag(source->mo, 2, true); diff --git a/src/k_roulette.c b/src/k_roulette.c index 8868286e5..1b378aeae 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -1633,7 +1633,7 @@ void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd) //player->karthud[khud_itemblinkmode] = 1; //player->karthud[khud_rouletteoffset] = K_GetRouletteOffset(roulette, FRACUNIT); - if (P_IsDisplayPlayer(player) && !demo.freecam) + if (P_IsDisplayPlayer(player)) { S_StartSound(NULL, sfx_itrole); } @@ -1680,7 +1680,7 @@ void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd) player->karthud[khud_itemblinkmode] = 0; player->karthud[khud_rouletteoffset] = K_GetRouletteOffset(roulette, FRACUNIT); - if (P_IsDisplayPlayer(player) && !demo.freecam) + if (P_IsDisplayPlayer(player)) { if (roulette->ringbox) { @@ -1714,7 +1714,7 @@ void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd) // This makes the roulette produce the random noises. roulette->sound = (roulette->sound + 1) % 8; - if (P_IsDisplayPlayer(player) && !demo.freecam) + if (P_IsDisplayPlayer(player)) { if (roulette->ringbox) S_StartSound(NULL, sfx_s240); diff --git a/src/menus/transient/pause-replay.c b/src/menus/transient/pause-replay.c index 68b4859dc..c04b8a2aa 100644 --- a/src/menus/transient/pause-replay.c +++ b/src/menus/transient/pause-replay.c @@ -236,7 +236,11 @@ void M_PlaybackToggleFreecam(INT32 choice) splitscreen = 0; R_ExecuteSetViewSize(); - P_ToggleDemoCamera(); + UINT8 i; + for (i = 0; i <= r_splitscreen; ++i) + { + P_ToggleDemoCamera(i); + } } void M_PlaybackQuit(INT32 choice) diff --git a/src/p_local.h b/src/p_local.h index 15b338f9d..4cf3fbd7c 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -92,6 +92,8 @@ void P_UnlinkThinker(thinker_t *thinker); struct camera_t { boolean chase; + boolean freecam; + angle_t aiming; // Things used by FS cameras. @@ -126,6 +128,9 @@ struct camera_t // SRB2Kart: camera pitches on slopes angle_t pitch; + // Freecam: A button was held since entering from menu, so don't move camera + UINT8 button_a_held; + // Freecam: aiming needs to be reset after switching from chasecam boolean reset_aiming; @@ -134,14 +139,6 @@ struct camera_t angle_t old_angle, old_aiming; }; -// demo freecam or something before i commit die -struct demofreecam_s { - - UINT8 button_a_held; // A button was held since entering from menu, so don't move camera -}; - -extern struct demofreecam_s democam; - extern camera_t camera[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_cam_dist[MAXSPLITSCREENPLAYERS], cv_cam_still[MAXSPLITSCREENPLAYERS], cv_cam_height[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_cam_speed[MAXSPLITSCREENPLAYERS], cv_cam_rotate[MAXSPLITSCREENPLAYERS]; @@ -156,7 +153,7 @@ boolean P_TryCameraMove(fixed_t x, fixed_t y, camera_t *thiscam); void P_SlideCameraMove(camera_t *thiscam); void P_DemoCameraMovement(camera_t *cam, UINT8 num); boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcalled); -void P_ToggleDemoCamera(void); +void P_ToggleDemoCamera(UINT8 viewnum); boolean P_PlayerInPain(player_t *player); void P_ResetPlayer(player_t *player); diff --git a/src/p_mobj.c b/src/p_mobj.c index d68232db3..46b0990f9 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -12231,7 +12231,7 @@ void P_SpawnPlayer(INT32 playernum) // changes. if (!demo.playback) { - demo.freecam = false; + camera[G_PartyPosition(playernum)].freecam = false; } } else if (pcount == 1) diff --git a/src/p_user.c b/src/p_user.c index feab05130..1dedec3f0 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1077,17 +1077,17 @@ boolean P_IsDisplayPlayer(player_t *player) return false; } - // Freecam still techically has a player in - // displayplayers. But since the camera is detached, it - // would be weird if sounds were heard from that player's - // perspective. - if (demo.freecam) - { - return false; - } - for (i = 0; i <= r_splitscreen; i++) // DON'T skip P1 { + if (camera[i].freecam) + { + // Freecam still techically has a player in + // displayplayers. But since the camera is + // detached, it would be weird if sounds were + // heard from that player's perspective. + continue; + } + if (player == &players[displayplayers[i]]) return true; } @@ -2884,8 +2884,6 @@ fixed_t t_cam_dist[MAXSPLITSCREENPLAYERS] = {-42,-42,-42,-42}; fixed_t t_cam_height[MAXSPLITSCREENPLAYERS] = {-42,-42,-42,-42}; fixed_t t_cam_rotate[MAXSPLITSCREENPLAYERS] = {-42,-42,-42,-42}; -struct demofreecam_s democam; - void P_DemoCameraMovement(camera_t *cam, UINT8 num) { ticcmd_t *cmd; @@ -2913,7 +2911,7 @@ void P_DemoCameraMovement(camera_t *cam, UINT8 num) cam->angle += turning; // camera movement: - if (!democam.button_a_held) + if (!cam->button_a_held) { if (cmd->buttons & BT_ACCELERATE) { @@ -2927,13 +2925,13 @@ void P_DemoCameraMovement(camera_t *cam, UINT8 num) } } - if (!(cmd->buttons & (BT_ACCELERATE | BT_DRIFT)) && democam.button_a_held) + if (!(cmd->buttons & (BT_ACCELERATE | BT_DRIFT)) && cam->button_a_held) { - democam.button_a_held--; + cam->button_a_held--; } // if you hold item, you will lock on to displayplayer. (The last player you were ""f12-ing"") - if (demo.freecam && cmd->buttons & BT_ATTACK) + if (cam->freecam && cmd->buttons & BT_ATTACK) { lastp = &players[displayplayers[0]]; // Fun fact, I was trying displayplayers[0]->mo as if it was Lua like an absolute idiot. cam->angle = R_PointToAngle2(cam->x, cam->y, lastp->mo->x, lastp->mo->y); @@ -2953,7 +2951,7 @@ void P_DemoCameraMovement(camera_t *cam, UINT8 num) // forward/back will have a slope. So, as long as democam // controls haven't been used to alter the vertical angle, // slowly reset it to flat. - if ((cam->reset_aiming && moving) || ((cmd->buttons & BT_DRIFT) && !democam.button_a_held)) + if ((cam->reset_aiming && moving) || ((cmd->buttons & BT_DRIFT) && !cam->button_a_held)) { INT32 aiming = cam->aiming; INT32 smooth = FixedMul(ANGLE_11hh / 4, FCOS(cam->aiming)); @@ -2994,17 +2992,19 @@ void P_DemoCameraMovement(camera_t *cam, UINT8 num) cam->subsector = R_PointInSubsector(cam->x, cam->y); } -void P_ToggleDemoCamera(void) +void P_ToggleDemoCamera(UINT8 viewnum) { - if (!demo.freecam) // toggle on + camera_t *cam = &camera[viewnum]; + + if (!cam->freecam) // toggle on { - demo.freecam = true; - democam.button_a_held = 2; - camera[0].reset_aiming = true; + cam->freecam = true; + cam->button_a_held = 2; + cam->reset_aiming = true; } else // toggle off { - demo.freecam = false; + cam->freecam = false; } } @@ -3013,7 +3013,7 @@ void P_ResetCamera(player_t *player, camera_t *thiscam) tic_t tries = 0; fixed_t x, y, z; - if (demo.freecam) + if (thiscam->freecam) return; // do not reset the camera there. if (!player->mo) @@ -3099,7 +3099,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall num = 0; } - if (demo.freecam || player->spectator) + if (thiscam->freecam || player->spectator) { P_DemoCameraMovement(thiscam, num); return true; diff --git a/src/r_main.c b/src/r_main.c index 3dc986b4a..a8bf00d4e 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -906,7 +906,7 @@ void R_ApplyViewMorph(int s) width*vid.bpp, height, width*vid.bpp, vid.width); } -angle_t R_ViewRollAngle(const player_t *player) +angle_t R_ViewRollAngle(const player_t *player, UINT8 viewnum) { angle_t roll = 0; @@ -927,7 +927,7 @@ angle_t R_ViewRollAngle(const player_t *player) if (cv_tilting.value) { - if (!player->spectator && !demo.freecam) + if (!player->spectator && !camera[viewnum].freecam) { roll += player->tilt; } @@ -1169,7 +1169,7 @@ R_SetupCommonFrame newview->y += offset.y; newview->z += offset.z; - newview->roll = R_ViewRollAngle(player); + newview->roll = R_ViewRollAngle(player, viewnum); if (subsector) newview->sector = subsector->sector; diff --git a/src/r_main.h b/src/r_main.h index e7d0f8a87..f3687dd8c 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -168,7 +168,7 @@ void R_Init(void); void R_CheckViewMorph(int split); void R_ApplyViewMorph(int split); -angle_t R_ViewRollAngle(const player_t *player); +angle_t R_ViewRollAngle(const player_t *player, UINT8 viewnum); // just sets setsizeneeded true extern boolean setsizeneeded; diff --git a/src/s_sound.c b/src/s_sound.c index d69e48e72..759674f1a 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -444,7 +444,7 @@ void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume) listenmobj[i] = player->mo; } - if (origin && origin == listenmobj[i] && !demo.freecam) + if (origin && origin == listenmobj[i] && !camera[i].freecam) { itsUs = true; } @@ -755,15 +755,15 @@ void S_UpdateSounds(void) { boolean itsUs = false; - if (!demo.freecam) + for (i = r_splitscreen; i >= 0; i--) { - for (i = r_splitscreen; i >= 0; i--) - { - if (c->origin != listenmobj[i]) - continue; + if (camera[i].freecam) + continue; - itsUs = true; - } + if (c->origin != listenmobj[i]) + continue; + + itsUs = true; } if (itsUs == false) diff --git a/src/st_stuff.c b/src/st_stuff.c index 153e2a48b..0ccddfc31 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -1192,7 +1192,7 @@ static void ST_overlayDrawer(void) { if (cv_showviewpointtext.value) { - if (!demo.title && !P_IsLocalPlayer(stplyr) && !demo.freecam) + if (!demo.title && !P_IsLocalPlayer(stplyr) && !camera[viewnum].freecam) { if (!r_splitscreen) { From 29feceda88c78737bfe34ce92752262d30a12bba Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 9 Oct 2023 18:47:20 -0700 Subject: [PATCH 5/8] P_DemoCameraMovement: cancel out up/down movement --- src/p_user.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/p_user.c b/src/p_user.c index 1dedec3f0..2b0e03e9f 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2913,15 +2913,19 @@ void P_DemoCameraMovement(camera_t *cam, UINT8 num) // camera movement: if (!cam->button_a_held) { - if (cmd->buttons & BT_ACCELERATE) + int dir = ((cmd->buttons & BT_ACCELERATE) ? 1 : 0) + ((cmd->buttons & BT_BRAKE) ? -1 : 0); + + switch (dir) { - cam->z += 32*mapobjectscale; - moving = true; - } - else if (cmd->buttons & BT_BRAKE) - { - cam->z -= 32*mapobjectscale; - moving = true; + case 1: + cam->z += 32*mapobjectscale; + moving = true; + break; + + case -1: + cam->z -= 32*mapobjectscale; + moving = true; + break; } } From dadb150b2544f33cfa11dc019f311816d007e128 Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 9 Oct 2023 18:50:05 -0700 Subject: [PATCH 6/8] Freecam: use Y button to focus player --- src/g_build_ticcmd.cpp | 2 +- src/p_user.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/g_build_ticcmd.cpp b/src/g_build_ticcmd.cpp index 72dbe83f4..7f41eb865 100644 --- a/src/g_build_ticcmd.cpp +++ b/src/g_build_ticcmd.cpp @@ -389,7 +389,7 @@ class TiccmdBuilder map(gc_item, BT_ATTACK); // fire map(gc_lookback, BT_LOOKBACK); // rear view - map(gc_respawn, BT_RESPAWN | BT_EBRAKEMASK); // respawn + map(gc_respawn, BT_RESPAWN | (freecam() ? 0 : BT_EBRAKEMASK)); // respawn map(gc_vote, BT_VOTE); // mp general function button // lua buttons a thru c diff --git a/src/p_user.c b/src/p_user.c index 2b0e03e9f..ed8f1a215 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2934,8 +2934,8 @@ void P_DemoCameraMovement(camera_t *cam, UINT8 num) cam->button_a_held--; } - // if you hold item, you will lock on to displayplayer. (The last player you were ""f12-ing"") - if (cam->freecam && cmd->buttons & BT_ATTACK) + // if you hold Y, you will lock on to displayplayer. (The last player you were ""f12-ing"") + if (cam->freecam && cmd->buttons & BT_RESPAWN) { lastp = &players[displayplayers[0]]; // Fun fact, I was trying displayplayers[0]->mo as if it was Lua like an absolute idiot. cam->angle = R_PointToAngle2(cam->x, cam->y, lastp->mo->x, lastp->mo->y); From 9fa2e3da5ffe46fd9f61bd90993639c99bf6b1fb Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 3 Oct 2023 20:24:41 -0700 Subject: [PATCH 7/8] srb2::Draw: add button and small_button methods --- src/v_draw.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ src/v_draw.hpp | 22 ++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/src/v_draw.cpp b/src/v_draw.cpp index 37cbd7568..9ad6193ce 100644 --- a/src/v_draw.cpp +++ b/src/v_draw.cpp @@ -10,6 +10,7 @@ #include "doomdef.h" // skincolornum_t #include "doomtype.h" #include "hu_stuff.h" +#include "i_time.h" #include "k_hud.h" #include "m_fixed.h" #include "r_draw.h" @@ -84,6 +85,53 @@ void Chain::string(const char* str, INT32 flags, Font font) const V_DrawStringScaled(x, y, FloatToFixed(scale_), FRACUNIT, FRACUNIT, flags, colormap_, font_to_fontno(font), str); } +namespace +{ + +patch_t** get_button_patch(Draw::Button type, int ver) +{ + switch (type) + { +#define X(x) \ + case Draw::Button::x:\ + return kp_button_ ## x + + X(a)[ver]; + X(b)[ver]; + X(c)[ver]; + X(x)[ver]; + X(y)[ver]; + X(z)[ver]; + X(start); + X(l); + X(r); + X(up); + X(down); + X(right); + X(left); + +#undef X + } + + return nullptr; +}; + +}; // namespace + +void Chain::button(Button type, int ver, std::optional press) const +{ + const auto _ = Clipper(*this); + + if (press) + { + K_drawButton(FloatToFixed(x_), FloatToFixed(y_), flags_, get_button_patch(type, ver), *press); + } + else + { + K_drawButtonAnim(x_, y_, flags_, get_button_patch(type, ver), I_GetTime()); + } +} + Chain::Clipper::Clipper(const Chain& chain) { V_SetClipRect( diff --git a/src/v_draw.hpp b/src/v_draw.hpp index aef98fc33..bc57241c0 100644 --- a/src/v_draw.hpp +++ b/src/v_draw.hpp @@ -54,6 +54,23 @@ public: kBoth, }; + enum class Button + { + a, + b, + c, + x, + y, + z, + start, + l, + r, + up, + down, + right, + left, + }; + class TextElement { public: @@ -146,6 +163,9 @@ public: void fill(UINT8 color) const; + void button(Button type, std::optional press = {}) const { button(type, 0, press); } + void small_button(Button type, std::optional press = {}) const { button(type, 1, press); } + private: constexpr Chain() {} explicit Chain(float x, float y) : x_(x), y_(y) {} @@ -178,6 +198,7 @@ public: const UINT8* colormap_ = nullptr; void string(const char* str, INT32 flags, Font font) const; + void button(Button type, int ver, std::optional press = {}) const; friend Draw; }; @@ -222,6 +243,7 @@ public: VOID_METHOD(patch); VOID_METHOD(thumbnail); VOID_METHOD(fill); + VOID_METHOD(button); #undef VOID_METHOD From 1f3a9a4758f4e6fdb5753ed5324884ead5c95d40 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 3 Oct 2023 20:25:02 -0700 Subject: [PATCH 8/8] Splitscreen Director HUD; replace freecam HUD --- src/hud/CMakeLists.txt | 1 + src/hud/spectator.cpp | 247 +++++++++++++++++++++++++++++++++++++++++ src/k_hud.c | 79 +------------ src/k_hud.h | 1 + src/st_stuff.c | 49 -------- 5 files changed, 253 insertions(+), 124 deletions(-) create mode 100644 src/hud/spectator.cpp diff --git a/src/hud/CMakeLists.txt b/src/hud/CMakeLists.txt index b30d1156e..8331e2871 100644 --- a/src/hud/CMakeLists.txt +++ b/src/hud/CMakeLists.txt @@ -1,4 +1,5 @@ target_sources(SRB2SDL2 PRIVATE powerup.cpp + spectator.cpp timer.cpp ) diff --git a/src/hud/spectator.cpp b/src/hud/spectator.cpp new file mode 100644 index 000000000..2f4298e1a --- /dev/null +++ b/src/hud/spectator.cpp @@ -0,0 +1,247 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Kart Krew +// +// 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 "../v_draw.hpp" + +#include "../command.h" +#include "../d_clisrv.h" +#include "../d_player.h" +#include "../doomdef.h" +#include "../doomstat.h" +#include "../g_game.h" +#include "../g_party.h" +#include "../i_time.h" +#include "../k_director.h" +#include "../k_hud.h" +#include "../p_local.h" +#include "../r_fps.h" + +extern "C" consvar_t cv_maxplayers; + +using srb2::Draw; + +namespace +{ + +struct List +{ + struct Field + { + Field(const char* label, Draw::Button button, std::optional pressed = {}) : + label_(Draw::TextElement(label).font(Draw::Font::kThin)), + button_(button), + pressed_(pressed) + { + } + + int width() const { return label_.width() + kButtonWidth + kButtonMargin + kFieldSpacing; } + + void draw(const Draw& row, bool left) const + { + Draw col = row.x(left ? kButtonWidth + kButtonMargin : kFieldSpacing); + + col.text(label_); + col = col.x(left ? -(kButtonWidth + kButtonMargin) : width() - (kButtonWidth + kFieldSpacing)); + + if (r_splitscreen) + { + auto small_button_offset = [&] + { + switch (button_) + { + case Draw::Button::l: + case Draw::Button::r: + return -4; + + default: + return -2; + } + }; + + col.y(small_button_offset()).small_button(button_, pressed_); + } + else + { + col.y(-4).button(button_, pressed_); + } + } + + private: + static constexpr int kButtonWidth = 14; + static constexpr int kButtonMargin = 2; + static constexpr int kFieldSpacing = 8; + + Draw::TextElement label_; + Draw::Button button_; + std::optional pressed_; + }; + + List(int x, int y) : row_(split_draw(x, y, left_)) {} + + void insert(std::initializer_list fields) + { + auto total_width = [&fields] + { + int width = 0; + + for (const Field& field : fields) + { + width += field.width(); + } + + return width; + }; + + Draw col = left_ ? row_ : row_.x(-total_width()); + + for (const Field& field : fields) + { + field.draw(col, left_); + col = col.x(field.width()); + } + + row_ = row_.y(r_splitscreen ? -13 : -17); + } + +private: + bool left_ = r_splitscreen > 1 && R_GetViewNumber() & 1; + Draw row_; + + static Draw split_draw(int x, int y, bool left) + { + return Draw( + left ? x : (BASEVIDWIDTH / (r_splitscreen > 1 ? 2 : 1)) - x, + (BASEVIDHEIGHT / (r_splitscreen ? 2 : 1)) - y + ) + .align(Draw::Align::kLeft) + .flags( + V_SNAPTOBOTTOM | + (left ? V_SNAPTOLEFT : V_SNAPTORIGHT) | + (r_splitscreen > 1 ? V_HUDTRANS : V_SLIDEIN) | + V_SPLITSCREEN + ); + } +}; + +}; // namespace + +void K_drawSpectatorHUD(boolean director) +{ + const UINT8 viewnum = R_GetViewNumber(); + + UINT8 numingame = 0; + + for (UINT8 i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator) + { + numingame++; + } + } + + player_t* player = [viewnum]() -> player_t* + { + if (viewnum >= G_PartySize(consoleplayer)) + { + return nullptr; + } + + UINT8 p = G_PartyMember(consoleplayer, viewnum); + + if (!playeringame[p] || players[p].spectator == false) + { + return nullptr; + } + + return &players[p]; + }(); + + List list = [director] + { + switch (r_splitscreen) + { + case 0: + return List(director ? 80 : 20, 20); + + case 1: + return List(40, 20); + + default: + return List(10, 20); + } + }(); + + if (player) + { + std::string label = [player] + { + if (player->flashing) + { + return ". . ."; + } + else if (player->pflags & PF_WANTSTOJOIN) + { + return "Cancel Join"; + } + else + { + return "Join"; + } + }(); + + if (cv_maxplayers.value) + { + label += fmt::format(" [{}/{}]", numingame, cv_maxplayers.value); + } + + list.insert({{label.c_str(), Draw::Button::l}}); + } + + if (director || camera[viewnum].freecam) + { + // Not locked into freecam -- can toggle it. + if (director) + { + list.insert({{"Freecam", Draw::Button::c}}); + } + else + { + bool press = D_LocalTiccmd(viewnum)->buttons & BT_RESPAWN; + const char* label = (press && I_GetTime() % 16 < 8) ? "> <" : ">< "; + + list.insert({{label, Draw::Button::y, press}, {"Exit", Draw::Button::c}}); + } + } + + if (director) + { + if (numingame > 1) + { + list.insert({{"+", Draw::Button::a}, {"-", Draw::Button::x}}); + } + + if (player) + { + list.insert({{K_DirectorIsEnabled(viewnum) ? "\x82" "Director" : "Director", Draw::Button::r}}); + } + } + else + { + auto bt = D_LocalTiccmd(viewnum)->buttons; + + list.insert({{"", Draw::Button::r, bt & BT_DRIFT}, {"Pivot", Draw::Button::b, bt & BT_LOOKBACK}}); + list.insert({{"+", Draw::Button::a, bt & BT_ACCELERATE}, {"-", Draw::Button::x, bt & BT_BRAKE}}); + } +} diff --git a/src/k_hud.c b/src/k_hud.c index ed033d3d9..d3fc3cd14 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -5169,77 +5169,6 @@ void K_drawButtonAnim(INT32 x, INT32 y, INT32 flags, patch_t *button[2], tic_t a K_drawButton(x << FRACBITS, y << FRACBITS, flags, button, anim); } -static void K_DrawDirectorButton(INT32 idx, const char *label, patch_t *kp[2], INT32 textflags) -{ - INT32 flags = V_SNAPTORIGHT | V_SLIDEIN | V_SPLITSCREEN; - INT32 x = (BASEVIDWIDTH/2) - 10; - INT32 y = (idx * 16); - - if (r_splitscreen <= 1) - { - x = BASEVIDWIDTH - 60; - if (r_splitscreen == 0) - { - y += BASEVIDHEIGHT - 78; - } - } - - textflags |= flags; - - K_drawButtonAnim(x, y - 4, flags, kp, leveltime); - V_DrawRightAlignedThinString(x - 2, y, textflags, label); -} - -static void K_drawDirectorHUD(void) -{ - const UINT8 viewnum = R_GetViewNumber(); - const INT32 p = viewnum < G_PartySize(consoleplayer) ? G_PartyMember(consoleplayer, viewnum) : -1; - const char *itemtxt = "Join"; - UINT8 offs = 0; - - UINT8 numingame = 0; - UINT8 i; - - if (!LUA_HudEnabled(hud_textspectator)) - { - return; - } - - for (i = 0; i < MAXPLAYERS; i++) - if (playeringame[i] && !players[i].spectator) - numingame++; - - if (numingame > 1 && r_splitscreen == 0) // simplifies things a lot - { - K_DrawDirectorButton(1, "Next Player", kp_button_a[0], 0); - K_DrawDirectorButton(2, "Prev Player", kp_button_x[0], 0); - offs = 2; - } - - K_DrawDirectorButton(offs + 1, "Freecam", kp_button_c[0], 0); - - if (p == -1 || !playeringame[p] || players[p].spectator == false) - { - return; - } - - // TODO: this is too close to the screen bottom - K_DrawDirectorButton(offs + 2, "Director", kp_button_r, - (K_DirectorIsEnabled(viewnum) ? V_YELLOWMAP : 0)); - - if (players[p].flashing) - itemtxt = ". . ."; - else if (players[p].pflags & PF_WANTSTOJOIN) - itemtxt = "Cancel Join"; - - if (cv_maxplayers.value) - { - itemtxt = va("%s [%d/%d]", itemtxt, numingame, cv_maxplayers.value); - } - - K_DrawDirectorButton(0, itemtxt, kp_button_l, 0); -} - static void K_drawDistributionDebugger(void) { itemroulette_t rouletteData = {0}; @@ -5666,9 +5595,9 @@ void K_drawKartHUD(void) if (stplyr->karthud[khud_trickcool]) K_drawTrickCool(); - if (freecam) + if ((freecam || stplyr->spectator) && LUA_HudEnabled(hud_textspectator)) { - K_DrawDirectorButton(3, "Freecam", kp_button_c[0], 0); + K_drawSpectatorHUD(false); } if (modeattacking || freecam) // everything after here is MP and debug only @@ -5687,9 +5616,9 @@ void K_drawKartHUD(void) K_drawKartPowerUps(); - if (K_DirectorIsAvailable(viewnum) == true) + if (K_DirectorIsAvailable(viewnum) == true && LUA_HudEnabled(hud_textspectator)) { - K_drawDirectorHUD(); + K_drawSpectatorHUD(true); } if (cv_kartdebugdistribution.value) diff --git a/src/k_hud.h b/src/k_hud.h index b16c15e2c..c5efb621b 100644 --- a/src/k_hud.h +++ b/src/k_hud.h @@ -43,6 +43,7 @@ void K_LoadKartHUDGraphics(void); void K_drawKartHUD(void); void K_drawKartFreePlay(void); void K_drawKartPowerUps(void); +void K_drawSpectatorHUD(boolean director); void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT32 splitflags, UINT8 mode); void K_drawKart2PTimestamp(void); void K_drawKart4PTimestamp(void); diff --git a/src/st_stuff.c b/src/st_stuff.c index 0ccddfc31..451de0f16 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -1215,55 +1215,6 @@ static void ST_overlayDrawer(void) } } - if (!hu_showscores && netgame && !mapreset) - { - if (stplyr->spectator && displayplayers[viewnum] == g_localplayers[viewnum] && LUA_HudEnabled(hud_textspectator)) - { - const char *itemtxt = M_GetText("Item - Join Game"); - - if (stplyr->flashing) - itemtxt = M_GetText("Item - . . ."); - else if (stplyr->pflags & PF_WANTSTOJOIN) - itemtxt = M_GetText("Item - Cancel Join"); - else if (G_GametypeHasTeams()) - itemtxt = M_GetText("Item - Join Team"); - - if (cv_maxplayers.value) - { - UINT8 numingame = 0; - UINT8 i; - - for (i = 0; i < MAXPLAYERS; i++) - if (playeringame[i] && !players[i].spectator) - numingame++; - - itemtxt = va("%s (%s: %d)", itemtxt, M_GetText("Slots left"), max(0, cv_maxplayers.value - numingame)); - } - - // SRB2kart: changed positions & text - if (r_splitscreen) - { - V_DrawThinString(2, (BASEVIDHEIGHT/2)-20, V_YELLOWMAP|V_HUDTRANSHALF|V_SPLITSCREEN|V_SNAPTOLEFT|V_SNAPTOBOTTOM, M_GetText("- SPECTATING -")); - V_DrawThinString(2, (BASEVIDHEIGHT/2)-10, V_HUDTRANSHALF|V_SPLITSCREEN|V_SNAPTOLEFT|V_SNAPTOBOTTOM, itemtxt); - } - else - { - V_DrawString(2, BASEVIDHEIGHT-40, V_HUDTRANSHALF|V_SPLITSCREEN|V_YELLOWMAP|V_SNAPTOLEFT|V_SNAPTOBOTTOM, M_GetText("- SPECTATING -")); - V_DrawString(2, BASEVIDHEIGHT-30, V_HUDTRANSHALF|V_SPLITSCREEN|V_SNAPTOLEFT|V_SNAPTOBOTTOM, itemtxt); - if (stplyr->cmd.buttons & BT_LOOKBACK) - { - V_DrawString(2, BASEVIDHEIGHT-20, V_HUDTRANSHALF|V_SPLITSCREEN|V_SNAPTOLEFT|V_SNAPTOBOTTOM, M_GetText("Lookback - Camera pivot")); - V_DrawString(2, BASEVIDHEIGHT-10, V_HUDTRANSHALF|V_SPLITSCREEN|V_SNAPTOLEFT|V_SNAPTOBOTTOM, M_GetText("Aim forward/backward")); - } - else - { - V_DrawString(2, BASEVIDHEIGHT-20, V_HUDTRANSHALF|V_SPLITSCREEN|V_SNAPTOLEFT|V_SNAPTOBOTTOM, M_GetText("Accelerate - Float")); - V_DrawString(2, BASEVIDHEIGHT-10, V_HUDTRANSHALF|V_SPLITSCREEN|V_SNAPTOLEFT|V_SNAPTOBOTTOM, M_GetText("Brake - Sink")); - } - } - } - } - K_DrawMidVote(); K_DrawDialogue();