mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-25 01:16:18 +02:00
Hall of Meat (contrib/plugins/hall-of-meat) and grind exit triggers (contrib/plugins/grind-exits) are Lua plugins: the bail model, its Meat card, each map's best, the trigger rules, settings, console commands and menu panels are theirs, with their own checks that dingosdk_plugin_tests runs. What has to run in the game stays in core as plugin services: - every physics step of the skater (skater_physics, the physics_step event, permission events.physics); - the skater's x-ray (xray, xray_render, xray_skeleton; reskate.skater.xray and the draw handler's d:skeleton); - skate.'s bottom left HUD corner (hud_corner; reskate.hud.corner); - the game's slow motion, never in a session (game_speed; reskate.time.slow and speed, permission game.speed); - the side of the next pop out of a grind and pad controls kept from the game (grind_exit; reskate.skater.grind_exit, the grind_exit event, reskate.input.hide); - skate.'s own pictures by name (game_images; d:game_image), d:gradient and a font for drawn text. Removed: Extension/HallOfMeat, grind_exit_control and its commands, the HALL OF MEAT and GRIND EXITS add-on pages, home tile, hub and native menu rows, their menu models, and the hallofmeat and grindexit built-in commands.
143 lines
5.8 KiB
C++
143 lines
5.8 KiB
C++
#include "game_speed.h"
|
|
#include "skater_physics.h"
|
|
#include "Engine/Game/Multiplayer/session_tools.h"
|
|
#include "Engine/Game/Settings/simulation_time.h"
|
|
#include "Extension/Settings/named_settings.h"
|
|
#include <charconv>
|
|
#include <map>
|
|
#include <utility>
|
|
|
|
namespace dingosdk::game_speed {
|
|
namespace {
|
|
using skater_physics::set_step_length;
|
|
using skater_physics::step_length;
|
|
constexpr const char* time_scale = "SimulationTime.TimeScale";
|
|
constexpr const char* sim_rate = "SimulationTime.ForceSimRate";
|
|
constexpr const char* max_sim_fps = "SimulationTime.MaxSimFps";
|
|
// The steps first, then the clock: no step of the change runs at the old rate on the new clock.
|
|
constexpr std::array<const char*, 3> held_settings{sim_rate, max_sim_fps, time_scale};
|
|
constexpr std::size_t time_scale_index = 2;
|
|
|
|
bool failed(const std::string& result) { return result.starts_with("error: "); }
|
|
// A float as the settings read it back (named_setting_value), so what was written is recognised.
|
|
std::string text(float value) {
|
|
std::array<char, 32> buffer{};
|
|
const auto result = std::to_chars(buffer.data(), buffer.data() + buffer.size(), value);
|
|
return result.ec == std::errc{} ? std::string(buffer.data(), result.ptr) : std::string{};
|
|
}
|
|
std::optional<float> number(const std::string& value) {
|
|
float result{};
|
|
const auto parsed = std::from_chars(value.data(), value.data() + value.size(), result);
|
|
if (parsed.ec != std::errc{} || parsed.ptr != value.data() + value.size() || !std::isfinite(result)) return {};
|
|
return result;
|
|
}
|
|
}
|
|
|
|
bool SlowMotion::still_ours() const {
|
|
for (std::size_t index = 0; index < held_settings.size(); ++index)
|
|
if (!held_->ours[index].empty() && named_setting_value(held_settings[index]) != held_->ours[index]) return false;
|
|
return true;
|
|
}
|
|
|
|
bool SlowMotion::set(float speed) {
|
|
if (!(speed < 1.0f)) yielded_ = false; // this slow motion is over; the next break may slow the game again
|
|
if (!(speed < 1.0f) || yielded_ || multiplayer_session_active()) {
|
|
release();
|
|
return false;
|
|
}
|
|
// The game's own rate, as the skater's step still has it (it is the held rate's only while held).
|
|
if (!base_rate_) {
|
|
const auto own = simulation_time::rate_of(step_length());
|
|
if (!own) return false;
|
|
base_rate_ = own;
|
|
}
|
|
if (!held_) {
|
|
Held found;
|
|
for (std::size_t index = 0; index < held_settings.size(); ++index) {
|
|
auto value = named_setting_value(held_settings[index]);
|
|
if (!value) return false;
|
|
found.found[index] = std::move(*value);
|
|
}
|
|
held_ = std::move(found);
|
|
} else if (!still_ours()) {
|
|
yielded_ = true; // someone else's now: what still holds ours goes back
|
|
release();
|
|
return false;
|
|
}
|
|
const auto found = number(held_->found[time_scale_index]);
|
|
const auto scale = found ? slow_motion_time_scale(*found, speed) : std::nullopt;
|
|
if (!scale) {
|
|
release(); // the game already runs as slowly: the found speed stays, or comes back
|
|
return false;
|
|
}
|
|
const auto rate = slow_motion_rate(*base_rate_, *scale);
|
|
const std::array<std::string, 3> values{std::to_string(rate), std::to_string(rate), text(*scale)};
|
|
for (std::size_t index = 0; index < held_settings.size(); ++index) {
|
|
if (values[index] == held_->ours[index]) continue;
|
|
if (values[index].empty() || failed(change_named_setting(held_settings[index], values[index], false))) {
|
|
release(); // all three or none
|
|
return false;
|
|
}
|
|
held_->ours[index] = values[index];
|
|
}
|
|
// The skater steps as the simulation now does (a skater rebuilt meanwhile already does).
|
|
if (set_step_length(simulation_time::step_seconds(rate))) skater_changed_ = true;
|
|
return true;
|
|
}
|
|
|
|
void SlowMotion::release() {
|
|
if (held_) {
|
|
// Back to what was found; a setting that no longer holds ours was changed from elsewhere and stays.
|
|
// Written back like any change, so an override the slow motion found (the trainer's) stays theirs,
|
|
// and one it made itself ends where the value is the game's own again.
|
|
for (std::size_t index = held_settings.size(); index-- > 0;)
|
|
if (!held_->ours[index].empty() && named_setting_value(held_settings[index]) == held_->ours[index])
|
|
(void)change_named_setting(held_settings[index], held_->found[index], false);
|
|
held_.reset();
|
|
}
|
|
// The skater's own step back; while no skater can be reached (a teleport under way), the next call
|
|
// tries again, and the game's own rate is kept for it until then.
|
|
if (skater_changed_ && base_rate_ && set_step_length(simulation_time::step_seconds(*base_rate_))) skater_changed_ = false;
|
|
if (!skater_changed_) base_rate_.reset();
|
|
}
|
|
}
|
|
|
|
namespace dingosdk::game_speed {
|
|
namespace {
|
|
struct Asks {
|
|
std::map<std::string, float, std::less<>> by_owner;
|
|
SlowMotion motion;
|
|
bool running{};
|
|
};
|
|
Asks& asks() {
|
|
static auto* value = new Asks; // the game's settings may still be held at exit
|
|
return *value;
|
|
}
|
|
}
|
|
void ask(std::string_view owner, float speed) {
|
|
auto& a = asks();
|
|
if (!(speed < 1.0f) || !std::isfinite(speed)) {
|
|
if (const auto found = a.by_owner.find(owner); found != a.by_owner.end()) a.by_owner.erase(found);
|
|
return;
|
|
}
|
|
a.by_owner.insert_or_assign(std::string(owner), std::max(speed, slowest_speed));
|
|
}
|
|
void tick() noexcept {
|
|
auto& a = asks();
|
|
try {
|
|
float slowest = 1.0f;
|
|
for (const auto& [owner, speed] : a.by_owner) slowest = std::min(slowest, speed);
|
|
if (slowest < 1.0f || !a.motion.idle()) a.running = a.motion.set(slowest);
|
|
} catch (...) {
|
|
a.running = false;
|
|
}
|
|
}
|
|
float current() noexcept {
|
|
auto& a = asks();
|
|
if (!a.running) return 1.0f;
|
|
float slowest = 1.0f;
|
|
for (const auto& [owner, speed] : a.by_owner) slowest = std::min(slowest, speed);
|
|
return slowest;
|
|
}
|
|
}
|