Files
ReSkate/Extension/Skater/game_speed.cpp
T
AyeZeeBB db52508bdb Plugins: Hall of Meat and grind exits are plugin mods now; their C++ is gone
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.
2026-10-19 16:51:28 -05:00

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