Merge branch 'director-cam' into 'master'

Automatic spectator "director" cam

See merge request KartKrew/Kart!537
This commit is contained in:
toaster 2022-01-23 22:43:25 +00:00
commit 3823feb7d6
9 changed files with 340 additions and 1 deletions

View file

@ -113,3 +113,4 @@ k_grandprix.c
k_hud.c
k_terrain.c
k_brightmap.c
k_director.c

View file

@ -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

View file

@ -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];

294
src/k_director.c Normal file
View file

@ -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;
}
}

21
src/k_director.h Normal file
View file

@ -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);

View file

@ -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)
{

View file

@ -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;

View file

@ -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;

View file

@ -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++)
{