mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-23 19:56:00 +02:00
Trainer: revert speed boost, ported from AutoRevertBoost by Sivaes, jaq and OVM
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
8e03e0cdea
commit
afcdfafe2f
10 files changed
+309
-10
No files matched your search
@@ -303,12 +303,108 @@ void trainer_push_speed(std::uintptr_t core) noexcept {
|
||||
}
|
||||
} catch (...) {}
|
||||
}
|
||||
// ---- revert boost ------------------------------------------------------------------------------
|
||||
// When the game auto reverts a landing the board is still well out of line with the body and the
|
||||
// game swings it round. Earlier builds of the game gained speed there; this gives it back: a
|
||||
// forward speed along the direction of travel on the step of that landing. The measuring, the
|
||||
// thresholds and the size of the boost are AutoRevertBoost's (Sivaes, jaq and OVM,
|
||||
// github.com/Sivaes/AutoRevertBoost, GPL-3.0), taken from their play logs: clean and short 180s
|
||||
// land with the board 1-3 degrees off the body, auto reverts 71-130 degrees off.
|
||||
constexpr float revert_min_spin = 90.0f; // degrees the skater turned in the air
|
||||
constexpr float revert_min_board_offset = 40.0f; // board out of line with the body (forward or fakie) at touchdown
|
||||
constexpr float revert_max_speed = 40.0f; // m/s (144 km/h): no boost above this
|
||||
constexpr ULONGLONG revert_min_air_ms = 250, revert_max_age_ms = 400, revert_cooldown_ms = 500;
|
||||
// How much, at strength 1, from how far the board turned relative to the body over the flight:
|
||||
// a board bend (up to 140 degrees) gives +2 m/s at 40 degrees rising to +2.6 at 140; an auto
|
||||
// revert (past 140) a flat +1.5 m/s, less than any bend.
|
||||
float revert_boost_amount(float board_rotation) {
|
||||
if (board_rotation > 140.0f) return 1.5f;
|
||||
return 2.0f + 2.0f * std::clamp((board_rotation - 40.0f) / 320.0f, 0.0f, 1.0f);
|
||||
}
|
||||
struct RevertBoost {
|
||||
std::atomic<float> strength{}; // 0: off
|
||||
std::uintptr_t client{}, entity{};
|
||||
std::atomic<ULONGLONG> expires{};
|
||||
std::atomic<std::uint64_t> fired{};
|
||||
std::atomic<float> last_added{};
|
||||
// Physics thread only.
|
||||
bool primed{}; // the landing count has been read once since the boost was switched on
|
||||
std::uint64_t seen{}; // the last landing handled
|
||||
ULONGLONG cooldown_until{};
|
||||
};
|
||||
RevertBoost& revert_boost() { static auto* value = new RevertBoost; return *value; }
|
||||
void trainer_revert_boost(std::uintptr_t core) noexcept {
|
||||
auto& r = revert_boost();
|
||||
const float strength = r.strength.load(std::memory_order_relaxed);
|
||||
const auto now = GetTickCount64();
|
||||
if (!(strength > 0) || now >= r.expires.load(std::memory_order_acquire)) {
|
||||
r.primed = false; // nothing is read while it is off
|
||||
return;
|
||||
}
|
||||
SourceLastError error;
|
||||
auto& state = source_state();
|
||||
if (!state.initialized.load(std::memory_order_acquire) || state.busy.test_and_set(std::memory_order_acquire)) return;
|
||||
SourceBusyScope scope{state.busy};
|
||||
try {
|
||||
const auto bodies = debug_noclip_bodies(state.trial.base, r.client, r.entity);
|
||||
if (bodies.core != core || bodies.parts.empty()) return;
|
||||
SourceReader reader;
|
||||
const auto selector = reader.pointer(bodies.core, 0x440);
|
||||
source_require(reader.pointer(selector, 8) == bodies.context, "Skater physics selector changed.");
|
||||
// The skater's world matrix, where debug_skater reads it.
|
||||
const auto collection = reader.pointer(r.entity, 0x70);
|
||||
source_require(reader.pointer(collection) == r.entity, "Skater transform owner changed.");
|
||||
const auto first = reader.value<std::uint8_t>(collection, 9), extra = reader.value<std::uint8_t>(collection, 10);
|
||||
source_require(reader.value<std::uint8_t>(collection, 8) <= 128 && first <= 128 && extra <= 32, "Skater transform layout changed.");
|
||||
std::array<std::array<float, 3>, 32> velocities{};
|
||||
std::array<std::uint32_t, 32> flags{};
|
||||
const auto count = std::min<std::size_t>(bodies.parts.size(), velocities.size());
|
||||
for (std::size_t i = 0; i < count; ++i) {
|
||||
velocities[i] = reader.value<std::array<float, 3>>(bodies.parts[i], 0x70);
|
||||
flags[i] = reader.value<std::uint32_t>(bodies.parts[i], 0x60);
|
||||
}
|
||||
reader.verify();
|
||||
watch_revert_spin(selector, collection + std::uintptr_t{0x10} + (std::uintptr_t{first} + 2 * std::uintptr_t{extra}) * 0x20, bodies.parts[0]);
|
||||
const auto landing = last_revert_landing();
|
||||
if (!r.primed) { // landings from before it was switched on are not ours
|
||||
r.primed = true;
|
||||
r.seen = landing.sequence;
|
||||
return;
|
||||
}
|
||||
if (landing.sequence == r.seen) return;
|
||||
r.seen = landing.sequence;
|
||||
float board_offset = std::fmod(std::abs(landing.board_offset_degrees), 180.0f);
|
||||
board_offset = std::min(board_offset, 180.0f - board_offset); // 0: lined up, forward or fakie
|
||||
const bool revert = landing.board_valid && std::abs(landing.spin_degrees) >= revert_min_spin && board_offset >= revert_min_board_offset;
|
||||
const bool landed = landing.to >= 100 && landing.to < 200 && !bodies.offboard; // riding
|
||||
const bool fresh = now >= landing.landed_at && now - landing.landed_at <= revert_max_age_ms;
|
||||
const float speed = std::hypot(velocities[0][0], velocities[0][2]);
|
||||
if (!revert || !landed || !fresh || landing.air_ms < revert_min_air_ms || now < r.cooldown_until || !std::isfinite(speed) || speed < 1.0f ||
|
||||
speed >= revert_max_speed)
|
||||
return;
|
||||
const float board_rotation = std::abs(landing.board_spin_degrees - landing.spin_degrees);
|
||||
const float added = std::min(revert_boost_amount(board_rotation) * strength, revert_max_speed - speed);
|
||||
if (!(added > 0.005f)) return;
|
||||
const float scale = (speed + added) / speed;
|
||||
// Like the native velocity writers: XYZ at +70 and the dirty bit 8 at +60.
|
||||
for (std::size_t i = 0; i < count; ++i) {
|
||||
velocities[i][0] *= scale;
|
||||
velocities[i][2] *= scale;
|
||||
body_write(bodies.parts[i] + 0x70, velocities[i]);
|
||||
body_write(bodies.parts[i] + 0x60, flags[i] | 8u);
|
||||
}
|
||||
r.cooldown_until = now + revert_cooldown_ms;
|
||||
r.last_added.store(added, std::memory_order_relaxed);
|
||||
r.fired.fetch_add(1, std::memory_order_release);
|
||||
} catch (...) {}
|
||||
}
|
||||
void noclip_physics_update(std::uintptr_t core) {
|
||||
const auto original = source_state().velocity_update_original.load(std::memory_order_acquire);
|
||||
if (original) original(core);
|
||||
noclip_apply_velocity(core);
|
||||
trainer_apply_jump_scale(core);
|
||||
trainer_push_speed(core);
|
||||
trainer_revert_boost(core);
|
||||
}
|
||||
bool noclip_motion_target(std::uintptr_t rig, std::uintptr_t context,
|
||||
const std::array<float,16>* supplied, std::array<float,16>& target) noexcept {
|
||||
@@ -398,6 +494,17 @@ void set_push_speed(std::uintptr_t client, std::uintptr_t entity, float factor,
|
||||
p.factor.store(factor > 0.02f && factor <= 50 ? factor : 1.0f, std::memory_order_relaxed);
|
||||
p.expires.store(GetTickCount64() + 500, std::memory_order_release);
|
||||
}
|
||||
void set_revert_boost(std::uintptr_t client, std::uintptr_t entity, float strength) noexcept {
|
||||
auto& r = revert_boost();
|
||||
r.client = client;
|
||||
r.entity = entity;
|
||||
r.strength.store(std::isfinite(strength) && strength > 0 ? std::min(strength, 1000.0f) : 0.0f, std::memory_order_relaxed);
|
||||
r.expires.store(GetTickCount64() + 500, std::memory_order_release);
|
||||
}
|
||||
RevertBoostCount revert_boosts() noexcept {
|
||||
auto& r = revert_boost();
|
||||
return {r.fired.load(std::memory_order_acquire), r.last_added.load(std::memory_order_relaxed)};
|
||||
}
|
||||
JumpScaleResult take_jump_scale_result() noexcept {
|
||||
auto& j = jump_scale();
|
||||
if (j.pending.load(std::memory_order_acquire)) return {};
|
||||
|
||||
@@ -25,6 +25,15 @@ JumpScaleResult take_jump_scale_result() noexcept;
|
||||
// rolling skater that is not braking gains speed up to it (m/s). Publish every client tick: it
|
||||
// expires after 500 ms.
|
||||
void set_push_speed(std::uintptr_t client, std::uintptr_t entity, float factor, float stock, float cruise) noexcept;
|
||||
// The trainer's revert boost: speed given back on a landing the game auto reverts (client_noclip.cpp).
|
||||
// `strength` multiplies the boost (1: about +1.5 to +2.6 m/s a landing), 0 switches it off. Call
|
||||
// every tick: it lapses half a second after the last call.
|
||||
void set_revert_boost(std::uintptr_t client, std::uintptr_t entity, float strength) noexcept;
|
||||
struct RevertBoostCount {
|
||||
std::uint64_t fired{}; // boosts given since the game started
|
||||
float last_added{}; // m/s
|
||||
};
|
||||
RevertBoostCount revert_boosts() noexcept;
|
||||
// Engine-thread-only interactive controls. Presentation callbacks only queue requests.
|
||||
overlay::DebugModel on_client_debug_tick(std::uintptr_t base, std::uintptr_t client,
|
||||
bool can_control, bool camera_phase_observed, const overlay::DebugRequest* request = nullptr,
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
#include "Engine/Game/Build/addresses.h"
|
||||
#include "Engine/Game/Build/20260929/engine.h"
|
||||
#include "Engine/Game/Build/20260929/no_bail.h"
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <intrin.h>
|
||||
|
||||
namespace dingosdk {
|
||||
@@ -68,6 +70,93 @@ struct StateWatch {
|
||||
std::atomic<std::int64_t> since{}, previous_ticks{}; // performance-counter ticks
|
||||
};
|
||||
StateWatch& state_watch() { static auto* value = new StateWatch; return *value; }
|
||||
// The turn of one skater over each flight (no_bail.h). The published fields are atomics; the
|
||||
// rest is only touched by that skater's physics selector.
|
||||
struct SpinWatch {
|
||||
std::atomic<std::uintptr_t> selector{}, transform{}, board{};
|
||||
std::atomic<std::uint64_t> until{};
|
||||
bool airborne{}, have_yaw{}, board_ok{};
|
||||
float yaw{}, spin{}, board_yaw{}, board_spin{};
|
||||
std::uint32_t steps{}, last_air{};
|
||||
std::uint64_t took_off{};
|
||||
SRWLOCK lock = SRWLOCK_INIT;
|
||||
RevertLanding last;
|
||||
};
|
||||
SpinWatch& spin_watch() { static auto* value = new SpinWatch; return *value; }
|
||||
void track_spin(std::uintptr_t selector, std::uint32_t chosen) noexcept {
|
||||
constexpr float pi = 3.14159265f;
|
||||
auto& s = spin_watch();
|
||||
const auto now = GetTickCount64();
|
||||
if (selector != s.selector.load(std::memory_order_acquire)) return;
|
||||
if (now >= s.until.load(std::memory_order_acquire)) {
|
||||
s.airborne = s.have_yaw = false;
|
||||
return;
|
||||
}
|
||||
std::array<float, 16> m{};
|
||||
const auto transform = s.transform.load(std::memory_order_acquire);
|
||||
if (!transform || !read(transform, m) || !std::isfinite(m[8]) || !std::isfinite(m[10]) || std::abs(m[8]) + std::abs(m[10]) < 1e-3f) {
|
||||
s.airborne = s.have_yaw = false;
|
||||
return;
|
||||
}
|
||||
const float yaw = std::atan2(m[8], m[10]);
|
||||
// The deck's heading, read the same way from its three rotation rows; only a set of unit rows counts.
|
||||
std::array<float, 12> b{};
|
||||
float board_yaw{};
|
||||
bool board_ok = false;
|
||||
if (const auto board = s.board.load(std::memory_order_acquire); board && read(board + 0x20, b)) {
|
||||
const auto length = [&](int r) { return b[r * 4] * b[r * 4] + b[r * 4 + 1] * b[r * 4 + 1] + b[r * 4 + 2] * b[r * 4 + 2]; };
|
||||
board_ok = std::isfinite(length(0) + length(1) + length(2)) && std::abs(length(0) - 1) < 0.05f && std::abs(length(1) - 1) < 0.05f &&
|
||||
std::abs(length(2) - 1) < 0.05f && std::abs(b[8]) + std::abs(b[10]) > 1e-3f;
|
||||
if (board_ok) board_yaw = std::atan2(b[8], b[10]);
|
||||
}
|
||||
const auto wrap = [](float d) {
|
||||
while (d > pi) d -= 2 * pi;
|
||||
while (d < -pi) d += 2 * pi;
|
||||
return d;
|
||||
};
|
||||
const bool air = chosen >= 200 && chosen <= 299;
|
||||
if (!s.airborne) {
|
||||
if (air && s.have_yaw) {
|
||||
// Counted from the last ground pose, so the turn begun on the takeoff step is kept.
|
||||
s.airborne = true;
|
||||
s.spin = wrap(yaw - s.yaw);
|
||||
s.board_spin = board_ok && s.board_ok ? wrap(board_yaw - s.board_yaw) : 0.0f;
|
||||
s.board_ok = board_ok && s.board_ok;
|
||||
s.steps = 1;
|
||||
s.took_off = now;
|
||||
s.last_air = chosen;
|
||||
}
|
||||
} else {
|
||||
s.spin += wrap(yaw - s.yaw);
|
||||
if (s.board_ok && board_ok) s.board_spin += wrap(board_yaw - s.board_yaw);
|
||||
else s.board_ok = false;
|
||||
if (air) {
|
||||
++s.steps;
|
||||
s.last_air = chosen;
|
||||
} else {
|
||||
// The pose read here is the one the flight ended with, before the game's revert turns it.
|
||||
RevertLanding landing;
|
||||
landing.spin_degrees = s.spin * 180.0f / pi;
|
||||
landing.board_spin_degrees = s.board_spin * 180.0f / pi;
|
||||
landing.board_valid = s.board_ok;
|
||||
landing.board_offset_degrees = wrap(board_yaw - yaw) * 180.0f / pi;
|
||||
landing.from = s.last_air;
|
||||
landing.to = chosen;
|
||||
landing.steps = s.steps;
|
||||
landing.air_ms = now - s.took_off;
|
||||
landing.landed_at = now;
|
||||
AcquireSRWLockExclusive(&s.lock);
|
||||
landing.sequence = s.last.sequence + 1;
|
||||
s.last = landing;
|
||||
ReleaseSRWLockExclusive(&s.lock);
|
||||
s.airborne = false;
|
||||
}
|
||||
}
|
||||
s.yaw = yaw;
|
||||
s.have_yaw = true;
|
||||
if (!s.airborne) s.board_ok = board_ok; // on the ground: whether the next takeoff has a board pose
|
||||
s.board_yaw = board_yaw;
|
||||
}
|
||||
|
||||
// Recheck live local ownership at use time. A retained physics address alone
|
||||
// must never protect another skater after a respawn or level change.
|
||||
@@ -208,6 +297,7 @@ std::uint32_t choose_state(std::uintptr_t selector, std::uint32_t current) {
|
||||
if (chosen == wipeout_physics_state) w.wipeouts.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
track_spin(selector, chosen);
|
||||
return chosen;
|
||||
}
|
||||
void skeleton_response(std::uintptr_t rig, float seconds, bool wipeout) {
|
||||
@@ -412,6 +502,20 @@ PhysicsStateWatch watched_physics_state() noexcept {
|
||||
result.wipeouts = w.wipeouts.load(std::memory_order_relaxed);
|
||||
return result;
|
||||
}
|
||||
void watch_revert_spin(std::uintptr_t selector, std::uintptr_t transform, std::uintptr_t board) noexcept {
|
||||
auto& s = spin_watch();
|
||||
s.transform.store(transform, std::memory_order_release);
|
||||
s.board.store(board, std::memory_order_release);
|
||||
s.selector.store(selector, std::memory_order_release);
|
||||
s.until.store(GetTickCount64() + 500, std::memory_order_release);
|
||||
}
|
||||
RevertLanding last_revert_landing() noexcept {
|
||||
auto& s = spin_watch();
|
||||
AcquireSRWLockShared(&s.lock);
|
||||
const auto landing = s.last;
|
||||
ReleaseSRWLockShared(&s.lock);
|
||||
return landing;
|
||||
}
|
||||
void clear_no_bail_flight() noexcept {
|
||||
auto& p = protection();
|
||||
AcquireSRWLockExclusive(&p.lock);
|
||||
|
||||
@@ -27,4 +27,21 @@ void watch_physics_state(std::uintptr_t client, std::uintptr_t entity) noexcept;
|
||||
PhysicsStateWatch watched_physics_state() noexcept;
|
||||
// Stopping flight must not discard the independent manual preference.
|
||||
void clear_no_bail_flight() noexcept;
|
||||
// The local skater's turn over each flight, for the trainer's revert boost: measured in the
|
||||
// physics state hook, every physics step, so the start and the end of the turn are not lost.
|
||||
// From AutoRevertBoost by Sivaes, jaq and OVM (github.com/Sivaes/AutoRevertBoost, GPL-3.0).
|
||||
struct RevertLanding {
|
||||
std::uint64_t sequence{}; // 0: no landing yet; one more per finished flight
|
||||
float spin_degrees{}; // signed turn about the vertical axis from takeoff to landing
|
||||
float board_spin_degrees{}; // the same for the board's deck (if board_valid)
|
||||
float board_offset_degrees{}; // the deck's heading minus the skater's at touchdown (if board_valid)
|
||||
bool board_valid{};
|
||||
std::uint32_t from{}, to{}; // the last air state, and the state chosen on landing
|
||||
std::uint32_t steps{}; // physics steps in the air
|
||||
std::uint64_t air_ms{}, landed_at{}; // flight time, and GetTickCount64() at the landing
|
||||
};
|
||||
// Which skater to measure: its physics selector, its world matrix and the deck's physics body
|
||||
// (rotation rows from +0x20). Asked again at least twice a second, or the measuring stops.
|
||||
void watch_revert_spin(std::uintptr_t selector, std::uintptr_t transform, std::uintptr_t board) noexcept;
|
||||
RevertLanding last_revert_landing() noexcept;
|
||||
}
|
||||
@@ -32,6 +32,9 @@ can be tried: tell us what it does.
|
||||
curves it used to leave alone, which is why x0.4 only slowed a flip by a third. And below 1 it
|
||||
stretches the flip catch times by the same factor: the game hurries any flip that would not
|
||||
finish inside its catch time, so slowed flips used to snap back to full speed.
|
||||
- **Revert speed boost** (TRICKS). Land a spin the game has to auto revert and get speed back, as
|
||||
earlier builds of the game did; chain reverts to build speed. 0 is off. The measuring and the
|
||||
numbers are AutoRevertBoost's, by Sivaes, jaq and OVM (github.com/Sivaes/AutoRevertBoost).
|
||||
- **No limits of the trainer's own.** A slider's ends are only where the slider stops: every value,
|
||||
dial, trick slider and camera number takes any typed number (flip trick speed 0.01 to 100, camera
|
||||
numbers have a box beside the slider).
|
||||
|
||||
@@ -8,7 +8,7 @@ telemetry HUD. It ships no game data: the list of values is built at run time fr
|
||||
|
||||
| Tab | What you get |
|
||||
|---|---|
|
||||
| **TUNE** | Three lists: REALISTIC, FUN and EVERYTHING. Each opens on the dials: every built-in preset as one slider (Ollie height, pop out of grinds, on-board gravity, landings, revert friction, pumping, Push speed, Body flip and spin speed, Bail resistance, Grind lock-on and friction, on-foot jump and sprint, glide, torpedo), where 1 is the game's own and the preset's button jumps to the preset; then the switches (Auto Push, No Speed Wobble, Smooth Surfaces, Long Wheelbase, Never bail), the trick sliders (flip trick speed, no comply, boneless, hippy and off-board jump height) and the values themselves: a short plainly named list, or under EVERYTHING the whole table (the physics tuning's values, one multiplier per curve and graph, and the 314 values of the game's other tuning classes) with groups, "only what I changed", locks, your own saved presets and a preset a map applies every time it loads. One search box finds dials, trick sliders and values on every list; pointing at a named value says what it does. The built-in presets include Skate 3 (and its Hardcore and Easy): Skate 3's numbers for every value this game still shares with it. "Share" copies one of your presets as a line of text and "Import from clipboard" adds one you were sent. Changes apply while you skate. Values the game was never found or seen reading are hidden unless you ask for them. **Reset everything**, above the tabs, puts the game back as it shipped. |
|
||||
| **TUNE** | Three lists: REALISTIC, FUN and EVERYTHING. Each opens on the dials: every built-in preset as one slider (Ollie height, pop out of grinds, on-board gravity, landings, revert friction, pumping, Push speed, Body flip and spin speed, Bail resistance, Grind lock-on and friction, on-foot jump and sprint, glide, torpedo), where 1 is the game's own and the preset's button jumps to the preset; then the switches (Auto Push, No Speed Wobble, Smooth Surfaces, Long Wheelbase, Never bail), the trick sliders (flip trick speed, no comply, boneless, hippy and off-board jump height, and a revert speed boost: speed back on a landing the game auto reverts, from AutoRevertBoost by Sivaes, jaq and OVM) and the values themselves: a short plainly named list, or under EVERYTHING the whole table (the physics tuning's values, one multiplier per curve and graph, and the 314 values of the game's other tuning classes) with groups, "only what I changed", locks, your own saved presets and a preset a map applies every time it loads. One search box finds dials, trick sliders and values on every list; pointing at a named value says what it does. The built-in presets include Skate 3 (and its Hardcore and Easy): Skate 3's numbers for every value this game still shares with it. "Share" copies one of your presets as a line of text and "Import from clipboard" adds one you were sent. Changes apply while you skate. Values the game was never found or seen reading are hidden unless you ask for them. **Reset everything**, above the tabs, puts the game back as it shipped. |
|
||||
| **PRACTICE** | Game speed and pause, five marker slots per map (save / go / clear), return to the marker after a bail, teleport to coordinates, copy your position (game or Blender axes). |
|
||||
| **CAMERA** | Move the game's own cameras: distance, height, camera-only height and side for the low camera, the high camera and the on-foot camera, with buttons for SunJay's Low Cam numbers and a low camera that shows more of the skater. The cameras keep their smoothing and stay out of walls. Below it, an experimental camera of your own that replaces the game's (it does not avoid walls). |
|
||||
| **MAP & HUD** | Speed and air-time HUD, a read-out after every jump (takeoff speed and angle, height, distance, drop, landing speed, spin and flip), telemetry recording to CSV, and whatever the map's author ships for the trainer. |
|
||||
|
||||
@@ -130,6 +130,8 @@ struct State {
|
||||
const char *boost_name{""};
|
||||
// The no comply and the boneless are launched by the trick scripts too (trainer_jump.cpp).
|
||||
float nocomply_height{1}, boneless_height{1};
|
||||
float revert_boost{}; // 0: off
|
||||
std::uint64_t revert_boosts_seen{};
|
||||
std::uint32_t last_state{};
|
||||
float boost_factor{}; // velocity factor of the jump now starting, 0: none
|
||||
std::uint64_t boost_until{};
|
||||
@@ -300,6 +302,7 @@ void load_store() {
|
||||
s.offboard_height = std::clamp(o.value("offboard_height", 1.0f), height_low, height_high);
|
||||
s.flip_speed = std::clamp(o.value("flip_speed", 1.0f), flip_low, flip_high);
|
||||
s.boneless_height = std::clamp(o.value("boneless_height", 1.0f), height_low, height_high);
|
||||
s.revert_boost = std::clamp(o.value("revert_boost", 0.0f), 0.0f, revert_boost_high);
|
||||
s.slot = std::clamp(o.value("slot", 0), 0, static_cast<int>(marker_slots) - 1);
|
||||
}
|
||||
if (json->contains("values") && json->at("values").is_object())
|
||||
@@ -366,6 +369,7 @@ void save_store() {
|
||||
options["offboard_height"] = s.offboard_height;
|
||||
options["flip_speed"] = s.flip_speed;
|
||||
options["boneless_height"] = s.boneless_height;
|
||||
options["revert_boost"] = s.revert_boost;
|
||||
options["slot"] = s.slot;
|
||||
json["options"] = std::move(options);
|
||||
Json values = Json::object();
|
||||
@@ -891,18 +895,23 @@ std::string apply_dial(std::string_view name, double factor) {
|
||||
}
|
||||
// A preset of the player's own also carries the trick sliders that are off 1, under these keys.
|
||||
constexpr std::string_view trick_prefix = "trick.";
|
||||
constexpr std::string_view trick_names[]{"flip_speed", "hippy_height", "nocomply_height", "boneless_height", "offboard_height"};
|
||||
constexpr std::string_view trick_names[]{"flip_speed", "hippy_height", "nocomply_height", "boneless_height", "offboard_height", "revert_boost"};
|
||||
// What a trick slider is when the game is left alone: the multipliers 1, the revert boost off.
|
||||
constexpr float trick_stock(std::string_view name) { return name == "revert_boost" ? 0.0f : 1.0f; }
|
||||
float *trick_option(std::string_view name) {
|
||||
auto &s = state();
|
||||
return name == "flip_speed" ? &s.flip_speed : name == "hippy_height" ? &s.hippy_height : name == "nocomply_height" ? &s.nocomply_height
|
||||
: name == "boneless_height" ? &s.boneless_height : name == "offboard_height" ? &s.offboard_height : nullptr;
|
||||
: name == "boneless_height" ? &s.boneless_height : name == "offboard_height" ? &s.offboard_height
|
||||
: name == "revert_boost" ? &s.revert_boost : nullptr;
|
||||
}
|
||||
// Sets a trick slider named by a preset key; false when the key is not one.
|
||||
bool set_trick_key(std::string_view key, double value) {
|
||||
if (!key.starts_with(trick_prefix)) return false;
|
||||
const auto name = key.substr(trick_prefix.size());
|
||||
if (auto *option = trick_option(name))
|
||||
*option = name == "flip_speed" ? std::clamp(static_cast<float>(value), flip_low, flip_high) : std::clamp(static_cast<float>(value), height_low, height_high);
|
||||
*option = name == "flip_speed" ? std::clamp(static_cast<float>(value), flip_low, flip_high)
|
||||
: name == "revert_boost" ? std::clamp(static_cast<float>(value), 0.0f, revert_boost_high)
|
||||
: std::clamp(static_cast<float>(value), height_low, height_high);
|
||||
return true;
|
||||
}
|
||||
std::string remove_preset(std::string_view name) {
|
||||
@@ -927,7 +936,10 @@ std::string remove_preset(std::string_view name) {
|
||||
for (const auto &[user_name, values] : s.user) {
|
||||
if (lower(user_name) != wanted) continue;
|
||||
for (const auto &saved : values) {
|
||||
if (set_trick_key(saved.first, 1.0)) ++count;
|
||||
if (saved.first.starts_with(trick_prefix)) {
|
||||
(void)set_trick_key(saved.first, trick_stock(std::string_view(saved.first).substr(trick_prefix.size())));
|
||||
++count;
|
||||
}
|
||||
else if (auto *e = find_entry(saved.first)) put_back(*e);
|
||||
}
|
||||
removed = user_name;
|
||||
@@ -1488,6 +1500,7 @@ void build_view() {
|
||||
next->offboard_height = s.offboard_height;
|
||||
next->flip_speed = s.flip_speed;
|
||||
next->boneless_height = s.boneless_height;
|
||||
next->revert_boost = s.revert_boost;
|
||||
next->return_delay = s.return_delay;
|
||||
next->pad_shortcuts = s.pad_shortcuts;
|
||||
next->pad_menu = s.pad_menu;
|
||||
@@ -1507,7 +1520,7 @@ void build_view() {
|
||||
next->camera_game = std::format("{:.1f} m behind, {:.1f} m up, tilt {:.0f} degrees, FOV {:.0f}", profile.distance, profile.height, profile.pitch, profile.fov);
|
||||
next->open_serial = s.open_serial;
|
||||
next->stock = !s.camera_on && s.rigs == std::array<RigSetting, camera_rigs>{} && !next->touched && s.active.empty() && std::ranges::none_of(s.entries, &Entry::frozen) &&
|
||||
std::ranges::all_of(trick_names, [](std::string_view name) { return *trick_option(name) == 1.0f; });
|
||||
std::ranges::all_of(trick_names, [](std::string_view name) { return *trick_option(name) == trick_stock(name); });
|
||||
next->share_text = s.share_text;
|
||||
next->share_serial = s.share_serial;
|
||||
next->open_tab = s.open_tab;
|
||||
@@ -1563,7 +1576,7 @@ std::string export_preset(const std::string &name) {
|
||||
for (const auto &e : s.entries)
|
||||
if (e.touched) values[e.key] = e.value;
|
||||
for (const auto trick : trick_names)
|
||||
if (const auto *option = trick_option(trick); *option != 1.0f) values[std::string(trick_prefix) + std::string(trick)] = *option;
|
||||
if (const auto *option = trick_option(trick); *option != trick_stock(trick)) values[std::string(trick_prefix) + std::string(trick)] = *option;
|
||||
title = "My setup";
|
||||
} else {
|
||||
for (const auto &[user_name, saved] : s.user)
|
||||
@@ -1727,6 +1740,12 @@ void tick(std::uintptr_t base, std::uintptr_t client, bool playing, const std::s
|
||||
// pushing speed, which only gates whether a push may start).
|
||||
const auto *top_speed = find_entry("physicspush.maxpushablespeed");
|
||||
set_push_speed(client, s.entity, 1.0f, top_speed ? static_cast<float>(top_speed->stock) : 0.0f, s.boosts.cruise);
|
||||
// The revert boost adds speed, so it keeps to the session's rule for boosts and to a host's physics.
|
||||
set_revert_boost(client, s.entity, s.enforced || (multiplayer_session_active() && !session_boosts_allowed()) ? 0.0f : s.revert_boost);
|
||||
if (const auto boosts = revert_boosts(); boosts.fired != s.revert_boosts_seen) {
|
||||
s.revert_boosts_seen = boosts.fired;
|
||||
if (s.hud_jump || s.logging) say(logging::Level::info, std::format("Trainer revert boost: +{:.1f} m/s.", boosts.last_added));
|
||||
}
|
||||
for (TrickLaunch launch; take_trick_launch(launch);)
|
||||
if (launch.factor != 1.0f)
|
||||
say(logging::Level::info, std::format("Trainer trick: {} launched at {:.2f} m/s up, x{:.2f}.",
|
||||
@@ -1867,6 +1886,7 @@ std::string run(std::string_view verb, const std::vector<std::string> &a) {
|
||||
}
|
||||
if (v == "reset" && lower(arg(0)) == "tricks") {
|
||||
s.hippy_height = s.nocomply_height = s.boneless_height = s.offboard_height = s.flip_speed = 1.0f;
|
||||
s.revert_boost = 0.0f;
|
||||
changed();
|
||||
return "Trick heights and flip trick speed are the game's own again.";
|
||||
}
|
||||
@@ -1894,6 +1914,7 @@ std::string run(std::string_view verb, const std::vector<std::string> &a) {
|
||||
s.camera_on = false;
|
||||
s.rigs = {};
|
||||
s.hippy_height = s.nocomply_height = s.boneless_height = s.offboard_height = s.flip_speed = 1.0f;
|
||||
s.revert_boost = 0.0f;
|
||||
changed();
|
||||
return std::format("Everything is the game's own again: {} values put back, locks cleared, presets off, trick sliders at 1.", count);
|
||||
}
|
||||
@@ -1958,7 +1979,7 @@ std::string run(std::string_view verb, const std::vector<std::string> &a) {
|
||||
for (const auto &e : s.entries)
|
||||
if (e.touched) values[e.key] = e.value;
|
||||
for (const auto trick : trick_names)
|
||||
if (const auto *option = trick_option(trick); *option != 1.0f) values[std::string(trick_prefix) + std::string(trick)] = *option;
|
||||
if (const auto *option = trick_option(trick); *option != trick_stock(trick)) values[std::string(trick_prefix) + std::string(trick)] = *option;
|
||||
if (values.empty()) return "error: nothing is changed, so there is nothing to save.";
|
||||
const auto count = values.size();
|
||||
s.user[name] = std::move(values);
|
||||
@@ -2030,6 +2051,11 @@ std::string run(std::string_view verb, const std::vector<std::string> &a) {
|
||||
if (!editable(&why)) return "error: " + why;
|
||||
(name == "hippy_height" ? s.hippy_height : name == "nocomply_height" ? s.nocomply_height : name == "offboard_height" ? s.offboard_height : s.boneless_height) =
|
||||
std::clamp(static_cast<float>(*value), height_low, height_high);
|
||||
} else if (name == "revert_boost") {
|
||||
const auto value = number(arg(1));
|
||||
if (!value) return "error: revert_boost needs a strength: 0 is off, 1 gives back about 2 m/s on an auto revert.";
|
||||
if (!editable(&why)) return "error: " + why;
|
||||
s.revert_boost = std::clamp(static_cast<float>(*value), 0.0f, revert_boost_high);
|
||||
} else if (name == "return_delay") {
|
||||
const auto value = number(arg(1));
|
||||
if (!value) return "error: return_delay needs seconds.";
|
||||
|
||||
@@ -85,6 +85,7 @@ struct View {
|
||||
float hippy_height{1};
|
||||
// The same for the no comply and the boneless.
|
||||
float nocomply_height{1}, boneless_height{1};
|
||||
float revert_boost{}; // strength of the speed given back on an auto revert; 0: off
|
||||
float offboard_height{1}; // a jump on foot
|
||||
float flip_speed{1}; // board flip tricks: x of the game's own speed
|
||||
// HUD
|
||||
|
||||
@@ -31,6 +31,8 @@ struct Page {
|
||||
std::array<float, 3> teleport{};
|
||||
double speed_edit{-1}, speed_until{};
|
||||
float hippy_edit{1}, nocomply_edit{1}, boneless_edit{1}, offboard_edit{1}, flip_edit{1};
|
||||
float revert_edit{};
|
||||
bool revert_editing{};
|
||||
bool hippy_editing{}, nocomply_editing{}, boneless_editing{}, offboard_editing{}, flip_editing{};
|
||||
std::uint64_t open_serial{}; // the last `trainer open` acted on
|
||||
std::uint64_t share_serial{}; // the last `preset export` put on the clipboard
|
||||
@@ -382,7 +384,8 @@ void tune_tab(SkateMenu &menu, const Model &model, const CallbacksV3 &callbacks,
|
||||
}
|
||||
|
||||
void trick_heights(SkateMenu &menu, const CallbacksV3 &callbacks, Page &p, const trainer::View &view, const std::string &words) {
|
||||
static constexpr const char *labels[]{"Flip trick speed", "No comply height", "Boneless height", "Hippy jump height", "Off-board jump height"};
|
||||
static constexpr const char *labels[]{"Flip trick speed", "No comply height", "Boneless height", "Hippy jump height", "Off-board jump height",
|
||||
"Revert speed boost"};
|
||||
const auto wanted = [&](const char *label) { return words.empty() || contains_words(lower(std::string(label) + " tricks"), words); };
|
||||
if (std::ranges::none_of(labels, wanted)) return;
|
||||
begin_card(menu, "trick-heights", "TRICKS", "1.0 is the game's own");
|
||||
@@ -417,7 +420,35 @@ void trick_heights(SkateMenu &menu, const CallbacksV3 &callbacks, Page &p, const
|
||||
slider("Boneless height", "boneless_height", view.boneless_height, p.boneless_edit, p.boneless_editing);
|
||||
slider("Hippy jump height", "hippy_height", view.hippy_height, p.hippy_edit, p.hippy_editing);
|
||||
slider("Off-board jump height", "offboard_height", view.offboard_height, p.offboard_edit, p.offboard_editing);
|
||||
if (wanted("Revert speed boost")) {
|
||||
// Not a multiplier: 0 is the game's own (no boost).
|
||||
field(menu, "Revert speed boost");
|
||||
ImGui::PushID("revert_boost");
|
||||
float shown = p.revert_editing ? p.revert_edit : view.revert_boost;
|
||||
const float box = px(64);
|
||||
ImGui::SetNextItemWidth(std::max(px(70), ImGui::GetContentRegionAvail().x - box - ImGui::GetStyle().ItemSpacing.x));
|
||||
if (ImGui::SliderFloat("##strength", &shown, 0.0f, 5.0f, shown <= 0.0f ? "off" : "x %.2f")) {
|
||||
p.revert_edit = shown;
|
||||
p.revert_editing = true;
|
||||
}
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()) trainer_command(menu, callbacks, std::format("option revert_boost {:.2f}", p.revert_edit));
|
||||
if (!ImGui::IsItemActive() && p.revert_editing && std::abs(view.revert_boost - p.revert_edit) < 0.006f) p.revert_editing = false;
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("Land a spin the game has to auto revert (the board well out of line) and get speed back, as earlier builds did.\n"
|
||||
"x 1 gives about +1.5 to +2.6 m/s a landing; chain reverts to build speed. 0 is off, the game's own.\n"
|
||||
"From AutoRevertBoost by Sivaes, jaq and OVM.");
|
||||
ImGui::SameLine();
|
||||
float typed = view.revert_boost;
|
||||
ImGui::SetNextItemWidth(box);
|
||||
if (ImGui::InputFloat("##typed", &typed, 0, 0, "%.6g", ImGuiInputTextFlags_EnterReturnsTrue) && typed >= 0) {
|
||||
p.revert_editing = false;
|
||||
trainer_command(menu, callbacks, std::format("option revert_boost {}", typed));
|
||||
}
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Type any strength and press Enter: the slider's end is not a limit.");
|
||||
ImGui::PopID();
|
||||
}
|
||||
if (ImGui::Button("Reset tricks")) {
|
||||
p.revert_editing = false;
|
||||
p.hippy_editing = p.nocomply_editing = p.boneless_editing = p.offboard_editing = p.flip_editing = false;
|
||||
trainer_command(menu, callbacks, "reset tricks");
|
||||
}
|
||||
@@ -716,7 +747,7 @@ void trainer_page(SkateMenu &menu, const Model &model, const CallbacksV3 &callba
|
||||
const auto view = trainer::view();
|
||||
ImGui::BeginDisabled(!callbacks.queue_console_command);
|
||||
if (ImGui::Button("RESET EVERYTHING")) {
|
||||
p.hippy_editing = p.nocomply_editing = p.boneless_editing = p.offboard_editing = p.flip_editing = false;
|
||||
p.hippy_editing = p.nocomply_editing = p.boneless_editing = p.offboard_editing = p.flip_editing = p.revert_editing = false;
|
||||
trainer_command(menu, callbacks, "reset everything");
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace dingosdk::trainer {
|
||||
// What the trick multipliers may be, here and in the trainer's own options.
|
||||
inline constexpr float height_low = 0.001f, height_high = 1.0e6f; // heights; the sliders stop far short, a typed number need not
|
||||
inline constexpr float flip_low = 0.01f, flip_high = 100.0f; // board flip speed; the slider stops far short, a typed number need not
|
||||
inline constexpr float revert_boost_high = 1000.0f; // revert boost strength; 0 is off
|
||||
inline constexpr float cruise_high = 100.0f; // auto push, m/s
|
||||
|
||||
struct SessionExtras {
|
||||
|
||||
Reference in new issue
Block a user