diff --git a/src/Sourcefile b/src/Sourcefile index 91851dc62..6c8cabbcd 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -113,3 +113,4 @@ k_grandprix.c k_hud.c k_terrain.c k_brightmap.c +k_director.c \ No newline at end of file diff --git a/src/d_netcmd.c b/src/d_netcmd.c index e17f5e826..a139e1719 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -433,6 +433,7 @@ consvar_t cv_kartdebugbotpredict = CVAR_INIT ("kartdebugbotpredict", "Off", CV_N consvar_t cv_kartdebugcheckpoint = CVAR_INIT ("kartdebugcheckpoint", "Off", CV_NOSHOWHELP, CV_OnOff, NULL); consvar_t cv_kartdebugnodes = CVAR_INIT ("kartdebugnodes", "Off", CV_NOSHOWHELP, CV_OnOff, NULL); consvar_t cv_kartdebugcolorize = CVAR_INIT ("kartdebugcolorize", "Off", CV_NOSHOWHELP, CV_OnOff, NULL); +consvar_t cv_kartdebugdirector = CVAR_INIT ("kartdebugdirector", "Off", CV_NOSHOWHELP, CV_OnOff, NULL); static CV_PossibleValue_t votetime_cons_t[] = {{10, "MIN"}, {3600, "MAX"}, {0, NULL}}; consvar_t cv_votetime = CVAR_INIT ("votetime", "20", CV_NETVAR, votetime_cons_t, NULL); @@ -509,6 +510,8 @@ static CV_PossibleValue_t perfstats_cons_t[] = { {0, "Off"}, {1, "Rendering"}, {2, "Logic"}, {3, "ThinkFrame"}, {0, NULL}}; consvar_t cv_perfstats = CVAR_INIT ("perfstats", "Off", 0, perfstats_cons_t, NULL); +consvar_t cv_director = CVAR_INIT ("director", "Off", 0, CV_OnOff, NULL); + char timedemo_name[256]; boolean timedemo_csv; char timedemo_csv_id[256]; @@ -741,6 +744,8 @@ void D_RegisterServerCommands(void) CV_RegisterVar(&cv_showping); CV_RegisterVar(&cv_showviewpointtext); + CV_RegisterVar(&cv_director); + CV_RegisterVar(&cv_dummyconsvar); #ifdef USE_STUN diff --git a/src/d_netcmd.h b/src/d_netcmd.h index e8d36e6f3..6612352ea 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -87,7 +87,7 @@ extern consvar_t cv_kartusepwrlv; extern consvar_t cv_votetime; extern consvar_t cv_kartdebugitem, cv_kartdebugamount, cv_kartallowgiveitem, cv_kartdebugdistribution, cv_kartdebughuddrop; -extern consvar_t cv_kartdebugcheckpoint, cv_kartdebugnodes, cv_kartdebugcolorize; +extern consvar_t cv_kartdebugcheckpoint, cv_kartdebugnodes, cv_kartdebugcolorize, cv_kartdebugdirector; extern consvar_t cv_kartdebugwaypoints, cv_kartdebugbotpredict; extern consvar_t cv_itemfinder; @@ -113,6 +113,8 @@ extern consvar_t cv_sleep; extern consvar_t cv_perfstats; +extern consvar_t cv_director; + extern char timedemo_name[256]; extern boolean timedemo_csv; extern char timedemo_csv_id[256]; diff --git a/src/k_director.c b/src/k_director.c new file mode 100644 index 000000000..a35c3da84 --- /dev/null +++ b/src/k_director.c @@ -0,0 +1,294 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +/// \file k_director.c +/// \brief SRB2kart automatic spectator camera. + +#include "k_kart.h" +#include "k_respawn.h" +#include "doomdef.h" +#include "g_game.h" +#include "v_video.h" +#include "k_director.h" +#include "d_netcmd.h" +#include "p_local.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"? + +struct directorinfo directorinfo; + +void K_InitDirector(void) +{ + INT32 playernum; + + 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; + } +} + +static fixed_t K_GetFinishGap(INT32 leader, INT32 follower) +{ + fixed_t dista = players[follower].distancetofinish; + fixed_t distb = players[leader].distancetofinish; + + if (players[follower].position < players[leader].position) + { + return distb - dista; + } + else + { + return dista - distb; + } +} + +static void K_UpdateDirectorPositions(void) +{ + INT32 playernum; + INT32 position; + player_t* target; + + memset(directorinfo.sortedplayers, -1, sizeof(directorinfo.sortedplayers)); + + for (playernum = 0; playernum < MAXPLAYERS; playernum++) + { + target = &players[playernum]; + + if (playeringame[playernum] && !target->spectator && target->position > 0) + { + directorinfo.sortedplayers[target->position - 1] = playernum; + } + } + + for (position = 0; position < MAXPLAYERS - 1; position++) + { + directorinfo.gap[position] = INT32_MAX; + + if (directorinfo.sortedplayers[position] == -1 || directorinfo.sortedplayers[position + 1] == -1) + { + continue; + } + + directorinfo.gap[position] = P_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); + } + else if (directorinfo.boredom[position] > 0) + { + directorinfo.boredom[position]--; + } + } + + directorinfo.maxdist = P_ScaleFromMap(players[directorinfo.sortedplayers[0]].distancetofinish, FRACUNIT); +} + +static boolean K_CanSwitchDirector(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 (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; +} + +void K_DirectorFollowAttack(player_t *player, mobj_t *inflictor, mobj_t *source) +{ + if (!P_IsDisplayPlayer(player)) + { + return; + } + + if (inflictor && inflictor->player) + { + K_DirectorForceSwitch(inflictor->player - players, TRANSFERTIME); + } + else if (source && source->player) + { + K_DirectorForceSwitch(source->player - players, TRANSFERTIME); + } +} + +void K_DrawDirectorDebugger(void) +{ + INT32 position; + INT32 leader; + INT32 follower; + INT32 ytxt; + + if (!cv_kartdebugdirector.value) + { + return; + } + + V_DrawThinString(10, 0, V_70TRANS, va("PLACE")); + V_DrawThinString(40, 0, V_70TRANS, va("CONF?")); + V_DrawThinString(80, 0, V_70TRANS, va("GAP")); + 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)); + + for (position = 0; position < MAXPLAYERS - 1; position++) + { + ytxt = 10 * (position + 1); + leader = directorinfo.sortedplayers[position]; + follower = directorinfo.sortedplayers[position + 1]; + + if (leader == -1 || follower == -1) + break; + + V_DrawThinString(10, ytxt, V_70TRANS, va("%d", position)); + V_DrawThinString(20, ytxt, V_70TRANS, va("%d", position + 1)); + + if (players[leader].positiondelay) + { + V_DrawThinString(40, ytxt, V_70TRANS, va("NG")); + } + + V_DrawThinString(80, ytxt, V_70TRANS, va("%d", directorinfo.gap[position])); + + if (directorinfo.boredom[position] >= BOREDOMTIME) + { + V_DrawThinString(120, ytxt, V_70TRANS, va("BORED")); + } + else + { + V_DrawThinString(120, ytxt, V_70TRANS, va("%d", directorinfo.boredom[position])); + } + + V_DrawThinString(150, ytxt, V_70TRANS, va("%s", player_names[leader])); + V_DrawThinString(230, ytxt, V_70TRANS, va("%s", player_names[follower])); + } +} + +void K_UpdateDirector(void) +{ + INT32 *displayplayerp = &displayplayers[0]; + INT32 targetposition; + + if (!cv_director.value) + { + return; + } + + K_UpdateDirectorPositions(); + + if (directorinfo.cooldown > 0) { + directorinfo.cooldown--; + } + + // handle pending forced switches + if (directorinfo.freeze > 0) + { + if (!(--directorinfo.freeze)) + K_DirectorSwitch(directorinfo.attacker, true); + + 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]; + + // if we're certain the back half of the pair is actually in this position, try to switch + if (*displayplayerp != target && !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) + { + K_DirectorSwitch(target, false); + } + + break; + } +} \ No newline at end of file diff --git a/src/k_director.h b/src/k_director.h new file mode 100644 index 000000000..db19fbe8b --- /dev/null +++ b/src/k_director.h @@ -0,0 +1,21 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +/// \file k_director.h +/// \brief SRB2kart automatic spectator camera. + +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; + +void K_InitDirector(void); +void K_UpdateDirector(void); +void K_DrawDirectorDebugger(void); +void K_DirectorFollowAttack(player_t *player, mobj_t *inflictor, mobj_t *source); \ No newline at end of file diff --git a/src/k_hud.c b/src/k_hud.c index fd83afd56..62253ba68 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -13,6 +13,7 @@ #include "k_kart.h" #include "k_battle.h" #include "k_color.h" +#include "k_director.h" #include "screen.h" #include "doomtype.h" #include "doomdef.h" @@ -4504,6 +4505,7 @@ void K_drawKartHUD(void) } K_DrawWaypointDebugger(); + K_DrawDirectorDebugger(); if (gametype == GT_BATTLE) { diff --git a/src/k_kart.c b/src/k_kart.c index 7f6a91525..99f2b1cac 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -34,6 +34,7 @@ #include "k_bot.h" #include "k_hud.h" #include "k_terrain.h" +#include "k_director.h" // SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H: // gamespeed is cc (0 for easy, 1 for normal, 2 for hard) @@ -259,6 +260,7 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartdebugcheckpoint); CV_RegisterVar(&cv_kartdebugnodes); CV_RegisterVar(&cv_kartdebugcolorize); + CV_RegisterVar(&cv_kartdebugdirector); } //} @@ -3287,6 +3289,8 @@ void K_SpinPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 typ (void)inflictor; (void)source; + K_DirectorFollowAttack(player, inflictor, source); + player->spinouttype = type; if (( player->spinouttype & KSPIN_THRUST )) @@ -3332,6 +3336,8 @@ void K_TumblePlayer(player_t *player, mobj_t *inflictor, mobj_t *source) fixed_t gravityadjust; (void)source; + K_DirectorFollowAttack(player, inflictor, source); + player->tumbleBounces = 1; if (player->tripWireState == TRIP_PASSED) @@ -3468,6 +3474,8 @@ INT32 K_ExplodePlayer(player_t *player, mobj_t *inflictor, mobj_t *source) // A (void)source; + K_DirectorFollowAttack(player, inflictor, source); + player->mo->momz = 18*mapobjectscale*P_MobjFlip(player->mo); // please stop forgetting mobjflip checks!!!! player->mo->momx = player->mo->momy = 0; diff --git a/src/p_setup.c b/src/p_setup.c index ba0e3825d..1a1583a0a 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -93,6 +93,7 @@ #include "k_grandprix.h" #include "k_terrain.h" // TRF_TRIPWIRE #include "k_brightmap.h" +#include "k_director.h" // K_InitDirector // Replay names have time #if !defined (UNDER_CE) @@ -4205,6 +4206,8 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) memset(localaiming, 0, sizeof(localaiming)); } + K_InitDirector(); + wantedcalcdelay = wantedfrequency*2; indirectitemcooldown = 0; hyubgone = 0; diff --git a/src/p_tick.c b/src/p_tick.c index 9a4baa092..69641d6be 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -34,6 +34,7 @@ #include "k_race.h" #include "k_battle.h" #include "k_waypoint.h" +#include "k_director.h" tic_t leveltime; @@ -706,6 +707,8 @@ void P_Ticker(boolean run) } } + K_UpdateDirector(); + // Always move the camera. for (i = 0; i <= r_splitscreen; i++) {