From 88ecd2f5d6a984db767eed214a2b17b00052eaef Mon Sep 17 00:00:00 2001 From: NepDisk Date: Sun, 17 Aug 2025 10:43:13 -0400 Subject: [PATCH] Update the director to the saturn version --- src/d_main.cpp | 4 +- src/deh_tables.c | 1 + src/g_game.c | 6 ++ src/g_input.c | 1 + src/g_input.h | 1 + src/k_director.c | 241 ++++++++++++++++++++++++++++++++++++++++++----- src/k_director.h | 21 ++--- src/st_stuff.c | 56 ++++++++++- src/st_stuff.h | 1 + 9 files changed, 288 insertions(+), 44 deletions(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index 3fea7c283..92101f90f 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -87,8 +87,8 @@ #define ASSET_HASH_TEXTURES_KART 0xb4211b2f32b6a291 #define ASSET_HASH_CHARS_KART 0x1e68a3e01aa5c68b #define ASSET_HASH_MAPS_KART 0x38558ed00da41ce9 -#define ASSET_HASH_MAIN_PK3 0x2152f84c950bca6b -#define ASSET_HASH_MAPPATCH_PK3 0x42de80e07ca851be +#define ASSET_HASH_MAIN_PK3 0x0b1ca4fd1a704d9c +#define ASSET_HASH_MAPPATCH_PK3 0x19ebc8123bd51191 #define ASSET_HASH_BONUSCHARS_KART 0x60e6f13d822a7461 #ifdef USE_PATCH_FILE #define ASSET_HASH_PATCH_PK3 0x0000000000000000 diff --git a/src/deh_tables.c b/src/deh_tables.c index 9040f1097..2e55da2ba 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -1448,6 +1448,7 @@ struct int_const_s const INT_CONST[] = { {"GC_CUSTOM2",gc_custom2}, {"GC_CUSTOM3",gc_custom3}, {"GC_RESPAWN",gc_respawn}, + {"GC_DIRECTOR",gc_director}, {"NUM_GAMECONTROLS",num_gamecontrols}, // screen.h constants diff --git a/src/g_game.c b/src/g_game.c index d0a3c5ce3..be69a69e2 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1784,6 +1784,12 @@ boolean G_Responder(event_t *ev) COM_ImmedExecute(commandname); } } + + if (G_ControlBoundToKey(i, gc_director, ev->data1, false)) + { + K_ToggleDirector(); + } + } return true; diff --git a/src/g_input.c b/src/g_input.c index 20ef4d199..b2fe32cbe 100644 --- a/src/g_input.c +++ b/src/g_input.c @@ -408,6 +408,7 @@ static const char *gamecontrolname[num_gamecontrols] = "custom2", "custom3", "respawn", + "director", }; #define NUMKEYNAMES (sizeof (keynames)/sizeof (keyname_t)) diff --git a/src/g_input.h b/src/g_input.h index 6d4790ce8..8f05eec71 100644 --- a/src/g_input.h +++ b/src/g_input.h @@ -86,6 +86,7 @@ typedef enum gc_custom2, // Lua scriptable gc_custom3, // Lua scriptable gc_respawn, + gc_director, num_gamecontrols } gamecontrols_e; diff --git a/src/k_director.c b/src/k_director.c index 7815c62d5..aa08edba5 100644 --- a/src/k_director.c +++ b/src/k_director.c @@ -1,24 +1,125 @@ // BLANKART //----------------------------------------------------------------------------- +// Copyright (C) 2024 by AJ "Tyron" Martinez. +// Copyright (C) 2024 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. +//----------------------------------------------------------------------------- /// \file k_director.c /// \brief SRB2kart automatic spectator camera. #include "k_kart.h" #include "doomdef.h" +#include "doomstat.h" #include "g_game.h" +#include "m_random.h" #include "v_video.h" #include "k_director.h" #include "d_netcmd.h" #include "p_local.h" +#include "st_stuff.h" + +#include "r_fps.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"? +#define BREAKAWAYDIST 2000 // how *far* until players considered far apart? +#define WALKBACKDIST 400 // how close should a trailing player be before we switch? +#define PINCHDIST 20000 // how close should the leader be to be considered "end of race"? -struct directorinfo directorinfo; +struct directorinfo +{ + player_t* viewplayer; + tic_t cooldown; // how long has it been since we last switched? + tic_t chaosleep;// how long did we watch the same player? + 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; + +static INT32 K_PlayersPlaying(void) +{ + INT32 num = 0, i; + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator || !players[i].mo) + continue; + + num++; + } + return num; +} + +static inline boolean race_rules(void) +{ + return (gametyperules & GTR_CIRCUIT); +} + +static fixed_t ScaleFromMap(fixed_t n, fixed_t scale) +{ + return FixedMul(n, FixedDiv(scale, mapobjectscale)); +} + +boolean K_DirectorIsAvailable(void) +{ + if (splitscreen || dedicated || (demo.playback && demo.title) || modeattacking) + return false; + return ((gamestate == GS_LEVEL) && ((demo.playback && !camera[0].freecam) || (players[consoleplayer].spectator && (K_PlayersPlaying() > 1)))); +} + +static boolean K_DirectorIsEnabled(void) +{ + return (cv_director.value && K_DirectorIsAvailable()); +} + +static mobj_t *finishmo = NULL; + +// scan for the waypoint on the finish line +// used to get a very approximate distance from player to finishline +// we probably should like, do some maths to find the middle point between multiplayer +// waypoints with the same values, but idk if its really worth it +static void K_SetupFinishMo(void) +{ + INT16 maxMoveCount = -1; + INT16 maxAngle = -1; + + finishmo = NULL; + + if (!(mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE)) // not a sprint map + { + // waypoint with angle 0 should always be at the finish line + for (finishmo = waypointcap; finishmo != NULL; finishmo = finishmo->tracer) + { + if (finishmo->spawnpoint->angle == 0) + break; + } + } + else // crappy optimization weeeee + { + // sprint maps finishline waypoint is the one with highest movecount AND angle + for (finishmo = waypointcap; finishmo != NULL; finishmo = finishmo->tracer) + { + if (finishmo->movecount > maxMoveCount) + maxMoveCount = finishmo->movecount; + if (finishmo->spawnpoint->angle > maxAngle) + maxAngle = finishmo->spawnpoint->angle; + } + + // now actually get the one + for (finishmo = waypointcap; finishmo != NULL; finishmo = finishmo->tracer) + { + if (finishmo->movecount == maxMoveCount && finishmo->spawnpoint->angle == maxAngle) + break; // found it + } + } +} void K_InitDirector(void) { @@ -28,6 +129,8 @@ void K_InitDirector(void) directorinfo.freeze = 0; directorinfo.attacker = 0; directorinfo.maxdist = 0; + directorinfo.viewplayer = NULL; + directorinfo.chaosleep = 0; for (playernum = 0; playernum < MAXPLAYERS; playernum++) { @@ -35,12 +138,25 @@ void K_InitDirector(void) directorinfo.gap[playernum] = INT32_MAX; directorinfo.boredom[playernum] = 0; } + + if (K_UsingLegacyCheckpoints()) + K_SetupFinishMo(); +} + +static fixed_t K_GetLegacyDistanceToFinish(player_t player) +{ + if (P_MobjWasRemoved(finishmo)) + return 0; + + return P_AproxDistance(P_AproxDistance(finishmo->x - player.mo->x, + finishmo->y - player.mo->y), + finishmo->z - player.mo->z) / FRACUNIT; } static fixed_t K_GetFinishGap(INT32 leader, INT32 follower) { - fixed_t dista = players[follower].distancetofinish; - fixed_t distb = players[leader].distancetofinish; + fixed_t dista = K_UsingLegacyCheckpoints() ? K_GetLegacyDistanceToFinish(players[follower]) : (fixed_t)players[follower].distancetofinish; + fixed_t distb = K_UsingLegacyCheckpoints() ? K_GetLegacyDistanceToFinish(players[leader]) : (fixed_t)players[leader].distancetofinish; if (players[follower].position < players[leader].position) { @@ -58,10 +174,10 @@ static void K_UpdateDirectorPositions(void) INT32 position; player_t* target; - memset(directorinfo.sortedplayers, -1, sizeof(directorinfo.sortedplayers)); - for (playernum = 0; playernum < MAXPLAYERS; playernum++) { + directorinfo.sortedplayers[playernum] = -1; + target = &players[playernum]; if (playeringame[playernum] && !target->spectator && target->position > 0) @@ -79,11 +195,11 @@ static void K_UpdateDirectorPositions(void) continue; } - directorinfo.gap[position] = P_ScaleFromMap(K_GetFinishGap(directorinfo.sortedplayers[position], directorinfo.sortedplayers[position + 1]), FRACUNIT); + directorinfo.gap[position] = ScaleFromMap(K_GetFinishGap(directorinfo.sortedplayers[position], directorinfo.sortedplayers[position + 1]), FRACUNIT); if (directorinfo.gap[position] >= BREAKAWAYDIST) { - directorinfo.boredom[position] = min(BOREDOMTIME * 2, directorinfo.boredom[position] + 1); + directorinfo.boredom[position] = (INT32)(min(BOREDOMTIME * 2, directorinfo.boredom[position] + 1)); } else if (directorinfo.boredom[position] > 0) { @@ -91,12 +207,17 @@ static void K_UpdateDirectorPositions(void) } } - directorinfo.maxdist = P_ScaleFromMap(players[directorinfo.sortedplayers[0]].distancetofinish, FRACUNIT); + if (directorinfo.sortedplayers[0] == -1) + { + directorinfo.maxdist = -1; + return; + } + + directorinfo.maxdist = ScaleFromMap(K_GetLegacyDistanceToFinish(players[directorinfo.sortedplayers[0]]), FRACUNIT); } static boolean K_CanSwitchDirector(void) { - if (directorinfo.cooldown > 0) { return false; @@ -107,7 +228,7 @@ static boolean K_CanSwitchDirector(void) static void K_DirectorSwitch(INT32 player, boolean force) { - if (P_IsDisplayPlayer(&players[player])) + if (!K_DirectorIsEnabled()) { return; } @@ -124,6 +245,7 @@ static void K_DirectorSwitch(INT32 player, boolean force) G_ResetView(1, player, true); directorinfo.cooldown = SWITCHTIME; + directorinfo.chaosleep = 0; } static void K_DirectorForceSwitch(INT32 player, INT32 time) @@ -135,11 +257,36 @@ static void K_DirectorForceSwitch(INT32 player, INT32 time) directorinfo.attacker = player; directorinfo.freeze = time; + directorinfo.chaosleep = 0; +} + +static void K_DirectorSwitchRandom(void) +{ + INT32 randomplayer = -1; + + // kinda dumb but just check if the random player is existing lmao + for (INT32 h = 0; h < MAXPLAYERS; h++) + { + randomplayer = directorinfo.sortedplayers[M_RandomRange(0, K_PlayersPlaying()-1)]; // switch to someone random + + if (randomplayer != -1 && randomplayer != displayplayers[0]) // dont switch to ourselves Zzz... + { + break; + } + } + + if (randomplayer != -1) + K_DirectorSwitch(randomplayer, true); } void K_DirectorFollowAttack(player_t *player, mobj_t *inflictor, mobj_t *source) { - if (!P_IsDisplayPlayer(player)) + if (!K_DirectorIsEnabled()) + { + return; + } + + if (directorinfo.viewplayer != player) { return; } @@ -172,6 +319,7 @@ void K_DrawDirectorDebugger(void) V_DrawThinString(120, 0, V_70TRANS, va("BORED")); V_DrawThinString(150, 0, V_70TRANS, va("COOLDOWN: %d", directorinfo.cooldown)); V_DrawThinString(230, 0, V_70TRANS, va("MAXDIST: %d", directorinfo.maxdist)); + V_DrawThinString(310, 0, V_70TRANS, va("SLEEPTIME: %d", directorinfo.chaosleep)); for (position = 0; position < MAXPLAYERS - 1; position++) { @@ -208,17 +356,18 @@ void K_DrawDirectorDebugger(void) void K_UpdateDirector(void) { - INT32 *displayplayerp = &displayplayers[0]; INT32 targetposition; + directorinfo.viewplayer = &players[displayplayers[0]]; - if (!cv_director.value) + if (!K_DirectorIsEnabled()) { return; } K_UpdateDirectorPositions(); - if (directorinfo.cooldown > 0) { + if (directorinfo.cooldown > 0) + { directorinfo.cooldown--; } @@ -231,6 +380,36 @@ void K_UpdateDirector(void) 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 || + // TODO: Battle; I just threw this together quick. Focus on leader. + !race_rules())) + { + K_DirectorSwitch(directorinfo.sortedplayers[0], false); + return; + } + + // insta switch if the player were watching finishes + if (players[displayplayers[0]].exiting) + { + K_DirectorSwitchRandom(); + return; + } + + // begin counting when cooldown wore off + if (!directorinfo.cooldown) + { + directorinfo.chaosleep++; + } + + // force switch 10 seconds after the cooldown has ended + // otherwise i fall asleeb zzz... + if (directorinfo.chaosleep > TICRATE*10) + { + K_DirectorSwitchRandom(); + 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 @@ -271,25 +450,19 @@ void K_UpdateDirector(void) target = directorinfo.sortedplayers[targetposition]; // stop here since we're already viewing this player - if (*displayplayerp == target) + if (displayplayers[0] == 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[target].positiondelay) + // even if we're not certain, if we're cetain we're watching the WRONG player, try to switch + if (directorinfo.viewplayer->position != targetposition+1 && !directorinfo.viewplayer->positiondelay) { K_DirectorSwitch(target, false); } @@ -297,3 +470,19 @@ void K_UpdateDirector(void) break; } } + +void K_ToggleDirector(void) +{ + if (!K_DirectorIsAvailable()) + return; + + if (!K_DirectorIsEnabled()) + { + G_AdjustView(1, 1, true); + directorinfo.cooldown = 0; // switch immediately + } + + directortoggletimer = 0; + + CV_SetValue(&cv_director, (cv_director.value ^ 1)); +} diff --git a/src/k_director.h b/src/k_director.h index 21a418036..d61f9fcd2 100644 --- a/src/k_director.h +++ b/src/k_director.h @@ -1,5 +1,12 @@ // BLANKART //----------------------------------------------------------------------------- +// Copyright (C) 2024 by AJ "Tyron" Martinez. +// Copyright (C) 2024 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. +//----------------------------------------------------------------------------- /// \file k_director.h /// \brief SRB2kart automatic spectator camera. @@ -10,22 +17,12 @@ extern "C" { #endif -extern struct directorinfo -{ - 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; - +boolean K_DirectorIsAvailable(void); 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(void); #ifdef __cplusplus } // extern "C" diff --git a/src/st_stuff.c b/src/st_stuff.c index fc96f3a03..810681d75 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -33,8 +33,10 @@ #include "m_anigif.h" // cv_gif_downscale #include "p_setup.h" // NiGHTS grading #include "k_grandprix.h" // we need to know grandprix status for titlecards +#include "k_director.h" #include "k_boss.h" #include "r_fps.h" +#include "g_input.h" //random index #include "m_random.h" @@ -64,6 +66,9 @@ consvar_t cv_stagetitle = CVAR_INIT ("maptitle", "On", CV_SAVE, CV_OnOff, NULL); UINT16 objectsdrawn = 0; +// dumb fade thing for director toggle +tic_t directortoggletimer = 0; + // // STATUS BAR DATA // @@ -725,6 +730,25 @@ void ST_preLevelTitleCardDrawer(void) I_UpdateNoBlit(); } +// returns the actual button name for any gamecontrol +// if unbound is set, it returns "Unbound" as a string should there be no button bound to a gamecontrol +// if gamectrl is set, it adds an extra "-" for cases where theres also a hardcoded button like accelerate +static const char *ST_GetButtonName(INT32 control, const char *inputtext, boolean unbound, boolean gamectrl) +{ + static char buttname[32] = ""; + const char *butt1 = (gamecontrol[0][control][0] != 0 ? G_KeynumToString(gamecontrol[0][control][0]) : NULL); + const char *butt2 = (gamecontrol[0][control][1] != 0 ? G_KeynumToString(gamecontrol[0][control][1]) : NULL); // alternative bind + + if (butt1 == NULL && butt2 == NULL) // not bound to a button + snprintf(buttname, 32, (unbound ? "%s - %s" : (gamectrl ? "-%s - %s" : "%s - %s")), (unbound ? "Unbound" : ""), inputtext); + else if (butt1 != NULL && butt2 != NULL) // bound to two buttons + snprintf(buttname, 32, "%s/%s - %s", butt1, butt2, inputtext); + else // bound to only one + snprintf(buttname, 32, (gamectrl ? "-%s - %s" : "%s - %s"), (butt1 != NULL ? butt1 : butt2 != NULL ? butt2 : ""), inputtext); + + return buttname; +} + // // Draw the status bar overlay, customisable: the user chooses which // kind of information to overlay @@ -748,6 +772,29 @@ static void ST_overlayDrawer(void) if (!hu_showscores) // hide the following if TAB is held { + // TODO: splitscreen support! + if (!splitscreen && !P_IsLocalPlayer(stplyr) && K_DirectorIsAvailable()) + { + char directortext[20] = {0}; + + snprintf(directortext, 20, "Director: %s", cv_director.value ? "On" : "Off"); + + if ((!demo.playback && directortoggletimer < 13*TICRATE) || (demo.playback && directortoggletimer < 4*TICRATE)) + { + if (renderisnewtic) + directortoggletimer++; + + if (directortoggletimer < 4*TICRATE) + V_DrawString(1, BASEVIDHEIGHT-8-1, V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANSHALF|V_ALLOWLOWERCASE, directortext); + else if (cv_translucenthud.value != 0) + V_DrawString(1, BASEVIDHEIGHT-8-1, V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_80TRANS|V_ALLOWLOWERCASE, directortext); // idk if V_80TRANS is good? + } + } + else + { + directortoggletimer = 0; + } + if (cv_showviewpointtext.value) { if (!demo.title && !P_IsLocalPlayer(stplyr) && !camera[viewnum].freecam) @@ -806,10 +853,11 @@ static void ST_overlayDrawer(void) } 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); - 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")); + V_DrawString(2, BASEVIDHEIGHT-50, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_HUDTRANSHALF|V_YELLOWMAP, M_GetText("- SPECTATING -")); + V_DrawString(2, BASEVIDHEIGHT-40, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_HUDTRANSHALF, itemtxt); + V_DrawString(2, BASEVIDHEIGHT-30, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_HUDTRANSHALF, va("Accelerate %s", ST_GetButtonName(gc_accelerate, "Float", false, true))); + V_DrawString(2, BASEVIDHEIGHT-20, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_HUDTRANSHALF, va("Brake %s", ST_GetButtonName(gc_brake, "Sink", false, true))); + V_DrawString(2, BASEVIDHEIGHT-10, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_HUDTRANSHALF, ST_GetButtonName(gc_director, "Toggle Director", true, false)); } } } diff --git a/src/st_stuff.h b/src/st_stuff.h index 16fb9766a..936b64f6e 100644 --- a/src/st_stuff.h +++ b/src/st_stuff.h @@ -90,6 +90,7 @@ extern lumpnum_t st_borderpatchnum; extern patch_t *faceprefix[MAXSKINS][NUMFACES]; extern UINT16 objectsdrawn; +extern tic_t directortoggletimer; // variable to stop mayonaka static from flickering extern consvar_t cv_lessflicker;