Files
ReSkate/Extension/Throwdowns/throwdown_kit.h
T
AyeZeeBB bf693dbe5c Throwdowns: fixes the docs review found in the kit and its services
- match.teleports lets through only the publishing plugin's own teleports, and only while its
  match runs (its HUD is active, or the native flag is one of its modes); it was merged across
  every plugin and checked no match.
- A mode's timer of 0 or past an hour no longer makes its card do nothing: the native flag takes
  it as 1 to 3600 s. flag.begin's seconds are checked as 1 to 3600, as the flag takes them.
- skater.push and hold(true) return false with a reason without the stock physics, where they
  were dropped unseen.
- telemetry's slam and struck are news since each plugin's own last read; reading them cleared
  them for every plugin.
- A banner or callout of a new match shows whatever serial it starts from, and a document without
  the slot no longer resets its serial.
- SmokeyDawg58's placement no longer divides input.wheel's whole notches by WHEEL_DELTA again.
2026-10-20 13:30:27 -05:00

273 lines
11 KiB
C++

#pragma once
#include <array>
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
// The throwdown kit: throwdown modes made by plugins, shown with skate.'s native widgets. A plugin
// registers its modes (custom_modes.h) and publishes documents (the setup page, the waiting card,
// the in-game HUD, the match panel, its flags and what a running match asks of the game); the
// native UI renders whichever documents are current and reports what the player does as events
// for the plugin. Nothing here knows any particular mode: a mode is an id and the data its plugin
// sends. Documents arrive on the plugin thread and are read on the UI thread: every accessor
// returns a copy.
namespace dingosdk {
class Json;
}
namespace dingosdk::throwdowns::kit {
using Vec3 = std::array<float, 3>;
// ---- documents --------------------------------------------------------------------------------
struct SetupRow {
std::string id, kind; // "slider" | "choice" | "button" | "info"
std::string caption, label;
float min{}, max{}, step{1}, value{};
std::vector<std::string> options; // choice
int selected{}; // choice: 0-based
};
struct SetupSlider {
bool present{};
std::string caption;
float min{}, max{}, step{1}, value{};
};
struct Setup {
std::string mode; // the registered mode id ("<plugin>/<id>", custom_modes Mode::key())
std::string title, art, description, back;
SetupSlider players, timer;
std::vector<SetupRow> rows;
std::vector<std::string> hide_native{"rounds"};
};
struct Waiting {
std::string mode, title, art, status;
unsigned enrolled{}, capacity{};
bool can_start{};
bool countdown{};
std::uint32_t remaining_ms{}, total_ms{};
};
struct Mark {
std::string text;
bool on{};
};
// A clock the native HUD ticks itself between publications.
struct Clock {
bool present{};
std::uint32_t remaining_ms{}, total_ms{};
std::uint64_t sampled_ms{}; // GetTickCount64 when remaining_ms was true
bool ticking{}, up{}; // up: counts up from remaining_ms (an elapsed time) to total_ms
};
struct BoardRow {
std::string rank, name, value;
std::uint32_t color{0xffffffffU};
bool self{}, out{};
std::vector<Mark> marks;
Clock clock; // present: the value is this clock as mm:ss.mmm, ticked by the HUD
};
struct TimerText {
std::string caption, text;
Clock clock; // present: the text is this clock as m:ss.cc
};
struct Prompt {
std::vector<std::string> inputs; // "UI_DpadLeft", "UI_ShoulderL", ...
std::string caption, glyph;
float hold{}; // 0..1: a hold button's progress
};
struct Marker {
Vec3 at{};
std::string text;
bool distance{};
};
struct Hud {
bool active{};
std::string theme;
struct Header {
std::string title, art, status;
std::array<float, 2> offset{};
unsigned icon_size{}, frame_width{}; // 0: the theme's measured header
} header;
std::string layout; // "side" | "bottom"
std::vector<std::string> counters;
float score_width{480};
std::string score_art; // the counters' widget family ("" = HUD_<theme>_Score)
Clock clock;
struct Board {
bool present{};
std::string title, variant; // variant: "stamps" (a name and a stamp per mark) | ""
std::string style; // "outlined" | "plain"
std::string name_art, out_art; // widget families of the name cell ("" = HUD_<theme>_Name)
// The rank and value cells' widget families ("" = HUD_<theme>_Rank / HUD_<theme>_Value), the
// value cell's least width (0: 224 px) and a text its values may carry before a clock
// ("DNF "), reserved in its width.
std::string rank_art, value_art, reserve;
float value_width{};
std::string heading; // "list": a label row over the counters and rows, all one list; "shared"
std::vector<std::string> columns;
std::vector<BoardRow> rows;
} board;
struct Results {
bool present{};
std::string title, notice, caption;
// The heading's widget family ("" = ResultsHeading) and its title's width (0: fitted). The
// rows take the board's cell families (board.name_art, rank_art, value_art, ...).
std::string heading_art;
float title_width{};
std::string style; // "outlined" (as the board's: clean-outline cells, the red glow on tinted values) | "plain"
std::vector<BoardRow> rows;
} results;
struct Intro {
bool present{};
std::string title, art_key, rules;
std::uint64_t until_ms{};
} intro;
Clock countdown; // 3-2-1-GO
struct Text {
std::string text, style;
std::uint64_t serial{};
std::uint32_t ms{};
std::string subtitle, art; // the banner's (its text is the title); a warning's art is its widget family
} banner, callout, warning;
struct Card {
bool present{};
std::string caption, title, image;
} card;
struct List {
bool present{};
std::string caption;
std::vector<std::string> items;
} list;
struct Position {
bool present{};
unsigned place{};
std::string medal;
std::string art; // the widget family, "<art>_<Medal>_Widget" ("" = HUD_<theme>_Position)
} position;
std::vector<TimerText> timers;
std::string timer_art; // the timers' widget family ("" = HUD_<theme>_Elapsed)
struct Compass {
bool present{};
Vec3 at{};
std::string art;
} compass;
std::vector<Marker> markers;
struct Blackout {
bool present{};
std::string text;
} blackout;
struct Editor {
bool present{};
std::string title, variant; // variant: "builder" | "compact"
std::vector<Prompt> prompts;
std::string info_title;
std::vector<std::pair<std::string, std::string>> info;
std::string toast;
float toast_alpha{};
std::string modal;
std::vector<Prompt> modal_actions;
} editor;
};
struct PanelSetting {
std::string icon, caption, value;
};
struct PanelPlayer {
std::uint64_t id{};
std::string name, value, rank, outcome, portrait;
bool self{};
};
struct Panel {
bool present{};
std::string title, description, art, score_caption, quit;
std::vector<PanelSetting> settings;
std::vector<Prompt> prompts; // each prompt's glyph field carries its command
std::vector<PanelPlayer> players;
};
struct Flag {
std::string id, mode;
std::uint64_t leader{};
std::uint32_t game{};
Vec3 at{};
float yaw{};
unsigned players{1}, capacity{6};
bool open{true};
std::vector<std::pair<std::string, std::string>> stats;
};
struct Match {
bool markers{}, input_lock{}, offboard{}, teleports{}, in_activity{};
std::vector<std::uint64_t> hidden;
};
struct Documents {
std::string plugin;
std::optional<Setup> setup;
std::optional<Waiting> waiting;
Hud hud;
Panel panel;
std::vector<Flag> flags;
Match match;
std::uint64_t revision{};
};
// ---- the store --------------------------------------------------------------------------------
// A plugin's documents (JSON with the field names above; Steam IDs as strings). Empty when taken,
// else why not.
std::string publish(std::string_view plugin, std::string_view json_text);
// The same, already parsed (the plugin API has the table as JSON).
std::string publish(std::string_view plugin, const Json& document);
// The plugin stopped: its documents and its pending events go.
void release(std::string_view plugin);
// Every plugin's current documents (the UI reads the one whose mode is open or playing).
std::vector<Documents> documents();
// The documents of the plugin that registered `mode` ("<plugin>/<id>", custom_modes Mode::key()), if it published any.
std::optional<Documents> documents_for(std::string_view mode);
// The plugin whose match is running now (its `match` asks something, or its hud is active).
std::optional<Documents> active();
// What every running match asks, merged (any plugin's `true` holds).
Match match() noexcept;
// Whether `plugin`'s own match lets its teleports through where the session turned noclip off: its
// `match.teleports`, while that match runs (its HUD is up, or `flag_mine`: the native flag is one of
// its modes, which the caller knows from flag::state()).
bool allows_teleports(std::string_view plugin, bool flag_mine) noexcept;
bool hidden(std::uint64_t player) noexcept;
// ---- events -----------------------------------------------------------------------------------
struct Event {
std::string plugin, action, mode;
std::vector<std::string> args;
};
// From the native UI (any thread): queued for the plugin that registered `mode` (or `plugin`).
void post(std::string_view mode, std::string action, std::vector<std::string> args = {});
void post_to(std::string_view plugin, std::string action, std::string mode, std::vector<std::string> args = {});
// What a plugin should hear since the last call (the plugin host delivers them as `throwdown_ui`).
std::vector<Event> take(std::string_view plugin);
// The mode whose setup page or flag the player is working with now (set by the native UI on
// `open`, cleared when the page closes and no flag is down).
void set_current(std::string_view mode);
std::string current();
// ---- the native flag ---------------------------------------------------------------------------
// One native flag registration at a time, for one plugin mode: the stock Spot Battle setup page
// (relabelled from the mode's `setup` document), native flag placement, then the waiting card,
// until the match starts or the owner leaves. Events go to the mode's plugin: setup_confirm,
// flag_placed {game, x, y, z, yaw}, setup_back, start, leave, flag_left.
namespace flag {
struct Options {
unsigned players = 4, seconds = 120;
bool setup_page = true; // false: straight to placement (a re-placed start flag)
float cursor_height = 0; // > 0: the placement cursor may rise this far (metres)
};
// False when another registration is busy.
bool begin(std::string_view mode, std::uint32_t game, const Options& options);
struct State {
bool owned{}, waiting{}, placing{}, busy{};
std::string mode;
std::uint32_t game{};
};
State state() noexcept;
// The owner is done with it (refused, finished, cancelled): the registration goes.
void leave() noexcept;
// A guest joined another player's flag through the stock Join (throwdown_relay): the registration
// is adopted so the native waiting card and Leave stay in charge.
bool adopt(std::string_view mode, std::uint32_t game, std::uint32_t mmid, const Vec3& at, float yaw, unsigned seconds, unsigned capacity);
} // namespace flag
} // namespace dingosdk::throwdowns::kit