mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-23 18:45:57 +02:00
When a plugin mode's flag goes down in a session, the relay offers it to the other players as that mode's: a new mode message (kind 17) names it, its flag and its player count, and its offer's series is one older versions do not show. A player whose plugin has the same mode gets a copy with the mode's own flag mesh and joins it natively; the copies are held at ten players so none fills and starts a Spot Battle, and the mode's player count decides who may still join. throwdown_mode events now carry host, leader, player and players: the host hears joined and left, a player in another's flag hears joined, left, closed and start, and start names who plays, the leader first. The plugins run the match from there. A mode can take a stock throwdown's place on the picker (replaces = "skate", "spot_battle" or "jam"), so his R.E.S.K.A.T.E. sits where S.K.A.T.E. does.
143 lines
6.5 KiB
C++
143 lines
6.5 KiB
C++
#include "custom_modes.h"
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <mutex>
|
|
|
|
namespace dingosdk::multiplayer::custom_throwdowns {
|
|
namespace {
|
|
struct Registry {
|
|
std::mutex mutex;
|
|
std::vector<Mode> modes;
|
|
std::map<std::string, std::map<std::string, int>, std::less<>> settings;
|
|
std::map<std::string, std::vector<News>, std::less<>> news;
|
|
std::atomic<std::uint64_t> revision{1};
|
|
};
|
|
Registry& registry() { static auto* r = new Registry; return *r; }
|
|
std::map<std::string, int> defaults(const Mode& mode) {
|
|
std::map<std::string, int> result{{"players", mode.players}};
|
|
if (mode.timer) result["timer"] = mode.timer->value;
|
|
for (const auto& row : mode.rows)
|
|
if (row.kind != Row::Kind::button) result[row.id] = row.value;
|
|
return result;
|
|
}
|
|
std::string plugin_of(std::string_view key) {
|
|
const auto at = key.find('/');
|
|
return std::string(at == std::string_view::npos ? key : key.substr(0, at));
|
|
}
|
|
} // namespace
|
|
|
|
std::string set_mode(Mode mode) {
|
|
if (mode.plugin.empty() || mode.id.empty() || mode.id.size() > 32 || mode.id.find('/') != std::string::npos)
|
|
return "a mode needs an id of 1 to 32 characters, without /";
|
|
if (mode.name.empty() || mode.name.size() > 32) return "a mode needs a name of 1 to 32 characters";
|
|
if (mode.description.size() > 240) return "a description is at most 240 characters";
|
|
if (!mode.replaces.empty() && mode.replaces != "skate" && mode.replaces != "spot_battle" && mode.replaces != "jam")
|
|
return "replaces is \"skate\", \"spot_battle\" or \"jam\"";
|
|
if (mode.min_players < 1 || mode.max_players > 10 || mode.min_players > mode.max_players)
|
|
return "players are 1 to 10, min no more than max";
|
|
mode.players = std::clamp(mode.players, mode.min_players, mode.max_players);
|
|
if (mode.rows.size() > 8) return "a mode has at most 8 rows";
|
|
std::vector<std::string> ids{"players", "timer"};
|
|
for (auto& row : mode.rows) {
|
|
if (row.id.empty() || std::find(ids.begin(), ids.end(), row.id) != ids.end())
|
|
return "each row needs its own id (not players or timer)";
|
|
ids.push_back(row.id);
|
|
if (row.caption.empty() || row.caption.size() > 32) return "a row's caption is 1 to 32 characters";
|
|
if (row.kind == Row::Kind::choice) {
|
|
if (row.options.empty() || row.options.size() > 12) return "a choice has 1 to 12 options";
|
|
row.value = std::clamp(row.value, 0, static_cast<int>(row.options.size()) - 1);
|
|
}
|
|
}
|
|
const auto slider = [](Row& row) {
|
|
if (row.step < 1 || row.minimum > row.maximum || (row.maximum - row.minimum) / row.step > 600) return false;
|
|
row.value = std::clamp(row.value, row.minimum, row.maximum);
|
|
return true;
|
|
};
|
|
if (mode.timer && !slider(*mode.timer)) return "the timer runs min to max in at most 600 steps";
|
|
for (auto& row : mode.rows)
|
|
if (row.kind == Row::Kind::slider && !slider(row)) return "a slider runs min to max in at most 600 steps";
|
|
auto& r = registry();
|
|
std::lock_guard lock(r.mutex);
|
|
const auto key = mode.key();
|
|
const auto found = std::find_if(r.modes.begin(), r.modes.end(), [&](const Mode& m) { return m.key() == key; });
|
|
if (found == r.modes.end() && r.modes.size() >= 32) return "32 modes are registered already";
|
|
// An update keeps the values already chosen for rows it still has.
|
|
auto values = defaults(mode);
|
|
if (found != r.modes.end())
|
|
for (auto& [row, value] : values)
|
|
if (const auto kept = r.settings[key].find(row); kept != r.settings[key].end()) value = kept->second;
|
|
r.settings[key] = std::move(values);
|
|
if (found != r.modes.end()) *found = std::move(mode);
|
|
else r.modes.push_back(std::move(mode));
|
|
r.revision.fetch_add(1);
|
|
return {};
|
|
}
|
|
void remove_mode(std::string_view plugin, std::string_view id) {
|
|
auto& r = registry();
|
|
std::lock_guard lock(r.mutex);
|
|
if (std::erase_if(r.modes, [&](const Mode& m) { return m.plugin == plugin && m.id == id; })) r.revision.fetch_add(1);
|
|
}
|
|
void remove_plugin(std::string_view plugin) {
|
|
auto& r = registry();
|
|
std::lock_guard lock(r.mutex);
|
|
if (std::erase_if(r.modes, [&](const Mode& m) { return m.plugin == plugin; })) r.revision.fetch_add(1);
|
|
if (const auto found = r.news.find(plugin); found != r.news.end()) r.news.erase(found);
|
|
}
|
|
std::vector<Mode> modes() {
|
|
auto& r = registry();
|
|
std::lock_guard lock(r.mutex);
|
|
return r.modes;
|
|
}
|
|
std::optional<Mode> find(std::string_view key) {
|
|
auto& r = registry();
|
|
std::lock_guard lock(r.mutex);
|
|
for (const auto& mode : r.modes)
|
|
if (mode.key() == key) return mode;
|
|
return std::nullopt;
|
|
}
|
|
std::uint64_t revision() noexcept { return registry().revision.load(); }
|
|
|
|
std::map<std::string, int> settings(std::string_view key) {
|
|
auto& r = registry();
|
|
std::lock_guard lock(r.mutex);
|
|
const auto found = r.settings.find(key);
|
|
return found == r.settings.end() ? std::map<std::string, int>{} : found->second;
|
|
}
|
|
void reset_settings(std::string_view key) {
|
|
auto& r = registry();
|
|
std::lock_guard lock(r.mutex);
|
|
for (const auto& mode : r.modes)
|
|
if (mode.key() == key) r.settings[std::string(key)] = defaults(mode);
|
|
}
|
|
void set_setting(std::string_view key, std::string_view row, int value) {
|
|
auto& r = registry();
|
|
std::lock_guard lock(r.mutex);
|
|
auto& values = r.settings[std::string(key)];
|
|
const auto found = values.find(std::string(row));
|
|
if (found != values.end() && found->second == value) return;
|
|
values[std::string(row)] = value;
|
|
const auto at = key.find('/');
|
|
auto& queue = r.news[plugin_of(key)];
|
|
if (queue.size() >= 64) queue.erase(queue.begin());
|
|
queue.push_back({std::string(key.substr(at + 1)), "setting", std::string(row), value, values});
|
|
}
|
|
void post(std::string_view key, News news) {
|
|
auto& r = registry();
|
|
std::lock_guard lock(r.mutex);
|
|
const auto at = key.find('/');
|
|
news.mode = std::string(at == std::string_view::npos ? key : key.substr(at + 1));
|
|
if (const auto found = r.settings.find(key); found != r.settings.end() && news.settings.empty())
|
|
news.settings = found->second;
|
|
auto& queue = r.news[plugin_of(key)];
|
|
if (queue.size() >= 64) queue.erase(queue.begin());
|
|
queue.push_back(std::move(news));
|
|
}
|
|
std::vector<News> take(std::string_view plugin) {
|
|
auto& r = registry();
|
|
std::lock_guard lock(r.mutex);
|
|
const auto found = r.news.find(plugin);
|
|
if (found == r.news.end()) return {};
|
|
return std::exchange(found->second, {});
|
|
}
|
|
} // namespace dingosdk::multiplayer::custom_throwdowns
|