mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2026-01-08 16:03:26 +00:00
Merge branch 'splitscreen-spectator' into 'master'
Complete splitscreen spectator support (parties not tested) -- Director working, freecam working, HUD working and polished See merge request KartKrew/Kart!1541
This commit is contained in:
commit
f495b9ff7d
24 changed files with 823 additions and 566 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1645,7 +1645,7 @@ static void Command_View_f(void)
|
|||
return;
|
||||
}
|
||||
|
||||
if (demo.freecam)
|
||||
if (camera[viewnum-1].freecam)
|
||||
return;
|
||||
|
||||
displayplayerp = &displayplayers[viewnum-1];
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -253,13 +255,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 +274,7 @@ class TiccmdBuilder
|
|||
|
||||
if (M_MenuButtonPressed(pid, MBT_R))
|
||||
{
|
||||
K_ToggleDirector(true);
|
||||
K_ToggleDirector(forplayer(), true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -283,7 +285,7 @@ class TiccmdBuilder
|
|||
|
||||
bool spectator_analog_input()
|
||||
{
|
||||
if (!player()->spectator && !objectplacing && !demo.freecam)
|
||||
if (!player()->spectator && !objectplacing && !freecam())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -387,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
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
11
src/g_demo.c
11
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;
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
target_sources(SRB2SDL2 PRIVATE
|
||||
powerup.cpp
|
||||
spectator.cpp
|
||||
timer.cpp
|
||||
)
|
||||
|
|
|
|||
247
src/hud/spectator.cpp
Normal file
247
src/hud/spectator.cpp
Normal file
|
|
@ -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 <initializer_list>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#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<bool> 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<bool> pressed_;
|
||||
};
|
||||
|
||||
List(int x, int y) : row_(split_draw(x, y, left_)) {}
|
||||
|
||||
void insert(std::initializer_list<Field> 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}});
|
||||
}
|
||||
}
|
||||
331
src/k_director.c
331
src/k_director.c
|
|
@ -1,331 +0,0 @@
|
|||
// 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"
|
||||
#include "g_party.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.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;
|
||||
}
|
||||
}
|
||||
|
||||
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 (!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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
void K_ToggleDirector(boolean active)
|
||||
{
|
||||
if (directorinfo.active != active)
|
||||
{
|
||||
directorinfo.cooldown = 0; // switch immediately
|
||||
}
|
||||
|
||||
directorinfo.active = active;
|
||||
}
|
||||
|
||||
boolean K_DirectorIsAvailable(UINT8 viewnum)
|
||||
{
|
||||
return viewnum <= r_splitscreen && viewnum < G_PartySize(consoleplayer) &&
|
||||
displayplayers[viewnum] != G_PartyMember(consoleplayer, viewnum);
|
||||
}
|
||||
384
src/k_director.cpp
Normal file
384
src/k_director.cpp
Normal file
|
|
@ -0,0 +1,384 @@
|
|||
// SONIC ROBO BLAST 2 KART
|
||||
//-----------------------------------------------------------------------------
|
||||
/// \file k_director.c
|
||||
/// \brief SRB2kart automatic spectator camera.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
#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"
|
||||
|
||||
extern "C" consvar_t cv_devmode_screen;
|
||||
|
||||
#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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
struct DirectorInfo
|
||||
{
|
||||
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?
|
||||
|
||||
struct PlayerStat
|
||||
{
|
||||
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];
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void force_switch(INT32 player, INT32 time)
|
||||
{
|
||||
if (players[player].exiting)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
attacker = player;
|
||||
freeze = time;
|
||||
}
|
||||
|
||||
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<DirectorInfo, MAXSPLITSCREENPLAYERS> info_{0, 1, 2, 3};
|
||||
}
|
||||
g_directorinfo;
|
||||
|
||||
void K_InitDirector(void)
|
||||
{
|
||||
g_directorinfo = {};
|
||||
}
|
||||
|
||||
void K_DirectorFollowAttack(player_t *player, mobj_t *inflictor, mobj_t *source)
|
||||
{
|
||||
for (DirectorInfo& directorinfo : g_directorinfo)
|
||||
{
|
||||
if (directorinfo.viewplayer() != player)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inflictor && inflictor->player)
|
||||
{
|
||||
directorinfo.force_switch(inflictor->player - players, TRANSFERTIME);
|
||||
}
|
||||
else if (source && source->player)
|
||||
{
|
||||
directorinfo.force_switch(source->player - players, TRANSFERTIME);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void K_DrawDirectorDebugger(void)
|
||||
{
|
||||
INT32 position;
|
||||
INT32 leader;
|
||||
INT32 follower;
|
||||
INT32 ytxt;
|
||||
|
||||
if (!cv_kartdebugdirector.value)
|
||||
{
|
||||
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"));
|
||||
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.playerstat[position].sorted;
|
||||
follower = directorinfo.playerstat[position + 1].sorted;
|
||||
|
||||
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.playerstat[position].gap));
|
||||
|
||||
if (directorinfo.playerstat[position].boredom >= BOREDOMTIME)
|
||||
{
|
||||
V_DrawThinString(120, ytxt, V_70TRANS, va("BORED"));
|
||||
}
|
||||
else
|
||||
{
|
||||
V_DrawThinString(120, ytxt, V_70TRANS, va("%d", directorinfo.playerstat[position].boredom));
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
for (DirectorInfo& directorinfo : g_directorinfo)
|
||||
{
|
||||
directorinfo.update();
|
||||
}
|
||||
}
|
||||
|
||||
void K_ToggleDirector(UINT8 viewnum, boolean active)
|
||||
{
|
||||
DirectorInfo& directorinfo = g_directorinfo[viewnum];
|
||||
|
||||
if (directorinfo.active != active)
|
||||
{
|
||||
directorinfo.cooldown = 0; // switch immediately
|
||||
}
|
||||
|
||||
directorinfo.active = active;
|
||||
}
|
||||
|
||||
boolean K_DirectorIsEnabled(UINT8 viewnum)
|
||||
{
|
||||
return g_directorinfo[viewnum].active;
|
||||
}
|
||||
|
||||
boolean K_DirectorIsAvailable(UINT8 viewnum)
|
||||
{
|
||||
return viewnum <= r_splitscreen && viewnum < G_PartySize(consoleplayer) &&
|
||||
displayplayers[viewnum] != G_PartyMember(consoleplayer, viewnum);
|
||||
}
|
||||
|
|
@ -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);
|
||||
void K_ToggleDirector(UINT8 viewnum, boolean active);
|
||||
boolean K_DirectorIsEnabled(UINT8 viewnum);
|
||||
boolean K_DirectorIsAvailable(UINT8 viewnum);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
|
|||
87
src/k_hud.c
87
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);
|
||||
|
|
@ -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,
|
||||
(directorinfo.active ? 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};
|
||||
|
|
@ -5455,8 +5384,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 +5460,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
|
||||
{
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
36
src/p_mobj.c
36
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;
|
||||
|
||||
|
|
@ -12220,22 +12221,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)
|
||||
{
|
||||
camera[G_PartyPosition(playernum)].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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
75
src/p_user.c
75
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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -2885,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;
|
||||
|
|
@ -2914,27 +2911,31 @@ 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)
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 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);
|
||||
|
|
@ -2954,7 +2955,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));
|
||||
|
|
@ -2995,17 +2996,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3014,7 +3017,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)
|
||||
|
|
@ -3100,7 +3103,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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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<bool> 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(
|
||||
|
|
|
|||
|
|
@ -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<bool> press = {}) const { button(type, 0, press); }
|
||||
void small_button(Button type, std::optional<bool> 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<bool> press = {}) const;
|
||||
|
||||
friend Draw;
|
||||
};
|
||||
|
|
@ -222,6 +243,7 @@ public:
|
|||
VOID_METHOD(patch);
|
||||
VOID_METHOD(thumbnail);
|
||||
VOID_METHOD(fill);
|
||||
VOID_METHOD(button);
|
||||
|
||||
#undef VOID_METHOD
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue