From 721ed9e64d482ca2a1a7982b87d27725ebc6349c Mon Sep 17 00:00:00 2001 From: AyeZeeBB Date: Wed, 21 Oct 2026 21:41:04 -0500 Subject: [PATCH] Plugins: a general launch API in place of grind_exit; grind exits is all Lua, and 701 is a grind The SDK knew grind exits: reskate.skater.grind_exit took a toe/heel/left/right wish, and C++ worked out the rail's direction, toe and heel from the stance and facing, the 1.8 m/s and the strength. That logic is now the grind-exits mod's own Lua (plugin/exits.lua, kept on this machine). What has to run on the physics thread is general: - Extension/Skater/launches: every launch of the local skater (ollie, grind, no_comply, boneless), classified by its call site and the state before it, with the board's direction of travel; the `launch` event for plugins with events.skater; reskate.skater.shape_launch(from, {across, along, up}) sets those speeds against the travel, one plugin a kind. - reskate.skater.get() gains `right` (the root's right axis) and `switch` (the game's stance flag). - Extension/Skater/pad_hide: reskate.input.hide's pad filter, out of the old grind exit file. Removed: reskate.skater.grind_exit, the grind_exit event, GrindExitWish and GrindPop. Physics state 701 is a grind too (the game checks it with 400 in about a dozen places; the 2026-10-11 measurement only reached the 400s): pops out of it were dropped, and skater_state called it "other". Launches, skater_state ("grinding") and the trainer's grind pop all count it now. Tests: the grind-exits plugin test drives the new path (11 checks of its own). --- Engine/Game/Build/20260929/grind_exit.h | 42 --- Engine/Game/Build/20260929/launches.h | 50 ++++ Extension/Plugins/Test/plugin_tests.cpp | 27 +- Extension/Plugins/plugin_api.cpp | 10 +- Extension/Plugins/plugin_api_world.cpp | 58 ++-- Extension/Plugins/plugin_game.h | 32 +-- Extension/Plugins/plugin_game_runtime.cpp | 36 +-- Extension/Plugins/plugin_host.cpp | 15 +- Extension/Plugins/plugin_runtime.cpp | 8 +- Extension/Plugins/plugin_vm.cpp | 29 ++ Extension/Plugins/plugin_vm.h | 4 + Extension/Skater/grind_exit.cpp | 311 ---------------------- Extension/Skater/grind_exit.h | 65 ----- Extension/Skater/launches.cpp | 287 ++++++++++++++++++++ Extension/Skater/launches.h | 63 +++++ Extension/Skater/no_bail.h | 2 +- Extension/Skater/pad_hide.cpp | 68 +++++ Extension/Skater/pad_hide.h | 20 ++ Extension/Trainer/trainer_jump.cpp | 5 +- Extension/Trainer/trainer_jump.h | 2 +- Extension/UI/Overlay/overlay.h | 2 +- Runtime/runtime_init.cpp | 6 +- STRUCTURE.md | 2 +- cmake/Runtime.cmake | 3 +- 24 files changed, 630 insertions(+), 517 deletions(-) delete mode 100644 Engine/Game/Build/20260929/grind_exit.h create mode 100644 Engine/Game/Build/20260929/launches.h delete mode 100644 Extension/Skater/grind_exit.cpp delete mode 100644 Extension/Skater/grind_exit.h create mode 100644 Extension/Skater/launches.cpp create mode 100644 Extension/Skater/launches.h create mode 100644 Extension/Skater/pad_hide.cpp create mode 100644 Extension/Skater/pad_hide.h diff --git a/Engine/Game/Build/20260929/grind_exit.h b/Engine/Game/Build/20260929/grind_exit.h deleted file mode 100644 index 9d7d14af..00000000 --- a/Engine/Game/Build/20260929/grind_exit.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once -#include - -namespace dingosdk::game::build::v20260929::grind_exit { -// Supported SHA-256 fbce74d5e28ef525dbba2cb4adbebc13405bdbd88f31bc940bca45e4ae88b8f9. -// Grind exits as measured with a logging-only recorder of physics steps, pad and launch calls -// (not part of this change) on 2026-10-11: 48 pops out of grinds, rails and a ledge, a goofy -// skater riding normal. -// -// The physics states of a grind are the 400s: 401 the grind itself (all but a few steps of every -// grind), 403 at a rail's end and between grinds, 402 and 404 briefly. A pop out of a grind goes -// from 401 straight to 200, the same pop state an ollie enters from its crouch (103). -inline constexpr std::uint32_t grind_states_first = 400, grind_states_end = 500; -inline constexpr std::uint32_t pop_state = 200; -// -// Every one of the 48 pops called the jump-trajectory setter (Extension/Trainer/trainer_jump.cpp, -// set_trajectory 0x4752aa0) from this call site, about 1 ms before the selector left 401. It is -// the call no complies launch through as well (from 103); flat-ground ollies launch from another -// (0x4824f7f). The trainer verifies its bytes (e8 55 34 f2 ff) before it hooks anything. -inline constexpr std::uintptr_t grind_pop_call = 0x482f646; -// -// The launch velocity it passes (parameters + 0x80): along the rail about 0.93 x the grind speed; -// up a fixed 2.03; across the rail one of three values the game chose before the call: about -// +1.8 (toward the skater root's right axis), about -1.5 (the other way), or about 0 (no lean). -// The side follows the left stick's x held up to the pop (let go 0.05 s or more before the pop: -// straight); a lean takes about 0.2 s to count in full. -inline constexpr float measured_side_speed = 1.8f; -// -// Measured 2026-10-11 (Stage 1): the lean exit starts in the grind's last steps, before the pop: -// the left stick's x held as little as 0.05 s before the pop already slides the board off the -// rail toward it, and the launch then carries about 1.4 m/s that way. Pushed only at the pop (or -// after), the stick still turns the body (FS/BS 180s of 130-155 degrees) without any lean exit. -// -// Which foot leads (for toe/heel): context + 0x13c4, bit 5 (mask 0x20). A goofy skater: 0 riding -// normal or fakie, 1 riding switch; it changed exactly at each change of stance in a scan of the -// skater's state memory, never in a grind, a pop or a 180 (which lands fakie), and -// +0x13cf bit 2 and +0x1807 bit 7 follow it. Whether it means "switch" or "left foot leads" is -// the same thing for a goofy skater; a regular skater's scan would tell them apart. The grind exit -// control takes it as "switch" and the player's stance as a setting. -inline constexpr std::uintptr_t stance_flags_offset = 0x13c4; // context -inline constexpr std::uint8_t stance_switch_mask = 0x20; -} diff --git a/Engine/Game/Build/20260929/launches.h b/Engine/Game/Build/20260929/launches.h new file mode 100644 index 00000000..8ffa0df1 --- /dev/null +++ b/Engine/Game/Build/20260929/launches.h @@ -0,0 +1,50 @@ +#pragma once +#include + +namespace dingosdk::game::build::v20260929::launches { +// Supported SHA-256 fbce74d5e28ef525dbba2cb4adbebc13405bdbd88f31bc940bca45e4ae88b8f9. +// The local skater's launches into the air (Extension/Skater/launches.h), as measured with a +// logging-only recorder of physics steps, pad and launch calls on 2026-10-11: 48 pops out of +// grinds, rails and a ledge, a goofy skater riding normal; and the trainer's jump measurements +// (Extension/Trainer/trainer_jump.cpp). +// +// The physics states of a grind are the 400s: 401 the grind itself (all but a few steps of every +// grind), 403 at a rail's end and between grinds, 402 and 404 briefly. A pop out of a grind goes +// from 401 straight to 200, the same pop state an ollie enters from its crouch (103). +inline constexpr std::uint32_t grind_states_first = 400, grind_states_end = 500; +// 701 is a grind as well, one the 48 never reached (2026-10-21, grinds in a park): the selector +// pops out of it to 200 through the same call, and the game's own checks take "previous state 400 +// or state 701" together in about a dozen places. +inline constexpr std::uint32_t other_grind_state = 701; +inline constexpr bool is_grind_state(std::uint32_t state) noexcept { + return (state >= grind_states_first && state < grind_states_end) || state == other_grind_state; +} +inline constexpr std::uint32_t pop_state = 200; +// +// Every launch calls the jump-trajectory setter (Extension/Trainer/trainer_jump.cpp, +// set_trajectory 0x4752aa0), from a call site of its kind. Every one of the 48 pops out of a grind +// called it from grind_pop_call, about 1 ms before the selector left 401. It is the call no +// complies launch through as well (from the crouch, 103); flat-ground ollies (riding or out of a +// manual) launch from ollie_call, a boneless from boneless_call while the foot is planted (602). +// The trainer verifies their bytes before it hooks anything. +inline constexpr std::uintptr_t grind_pop_call = 0x482f646, ollie_call = 0x4824f7f, boneless_call = 0x47691f8; +// +// The launch velocity it passes (parameters + 0x80): out of a grind, along the rail about 0.93 x +// the grind speed; up a fixed 2.03; across the rail one of three values the game chose before the +// call: about +1.8 (toward the skater root's right axis), about -1.5 (the other way), or about 0 +// (no lean). The side follows the left stick's x held up to the pop (let go 0.05 s or more before +// the pop: straight); a lean takes about 0.2 s to count in full. +// +// Measured 2026-10-11 (Stage 1): the lean exit starts in the grind's last steps, before the pop: +// the left stick's x held as little as 0.05 s before the pop already slides the board off the +// rail toward it, and the launch then carries about 1.4 m/s that way. Pushed only at the pop (or +// after), the stick still turns the body (FS/BS 180s of 130-155 degrees) without any lean exit. +// +// Which foot leads: context + 0x13c4, bit 5 (mask 0x20). A goofy skater: 0 riding normal or +// fakie, 1 riding switch; it changed exactly at each change of stance in a scan of the skater's +// state memory, never in a grind, a pop or a 180 (which lands fakie), and +0x13cf bit 2 and +// +0x1807 bit 7 follow it. Whether it means "switch" or "left foot leads" is the same thing for a +// goofy skater; a regular skater's scan would tell them apart. Plugins get it as "switch". +inline constexpr std::uintptr_t stance_flags_offset = 0x13c4; // context +inline constexpr std::uint8_t stance_switch_mask = 0x20; +} diff --git a/Extension/Plugins/Test/plugin_tests.cpp b/Extension/Plugins/Test/plugin_tests.cpp index fb4295d0..2babdd90 100644 --- a/Extension/Plugins/Test/plugin_tests.cpp +++ b/Extension/Plugins/Test/plugin_tests.cpp @@ -129,12 +129,12 @@ struct FakeGame final : Game { calls.push_back(std::format("slow {} {:.2f}", plugin, speed)); return {}; } - std::string grind_exit(std::string_view plugin, std::optional wish) override { - calls.push_back(wish ? std::format("grind {} {} {} {:.2f}", plugin, wish->side, wish->stance, wish->strength) : std::format("grind {} none", plugin)); + std::string shape_launch(std::string_view plugin, std::string_view from, std::optional shape) override { + calls.push_back(std::format("launch {} {} {}", plugin, from, shape && shape->across ? std::format("{:.2f}", *shape->across) : "none")); return {}; } - std::vector pops; - std::vector take_grind_pops(std::string_view) override { return std::exchange(pops, {}); } + std::vector launches; + std::vector take_launches() override { return std::exchange(launches, {}); } std::string hide_pad(std::string_view plugin, const PadHidden& h) override { calls.push_back(std::format("hide {} {} {} {}", plugin, h.left_trigger, h.right_trigger, h.left_x_grinding) + (h.right_stick || h.left_stick_click || h.right_stick_click @@ -3362,10 +3362,13 @@ void grind_exits_pick_the_side() { host.apply({contributed("grind-exits")}); running(host, "grind-exits", environment); check(host.run_console("grindexit test"), "grindexit test"); - check(environment.printed("grindexit: 7 passed, 0 failed") && !environment.printed("FAIL"), + check(environment.printed("grindexit: 11 passed, 0 failed") && !environment.printed("FAIL"), "Grind exits' own checks pass:\n" + printed_lines(environment) + environment.all()); - check(game.called("grind grind-exits none"), "It hears its pops from the start"); check(host.run_console("grindexit stance goofy"), "grindexit stance"); + // The board travels along (1, 3) (the fake's board velocity), whose left is (0.95, -0.32); the + // root's right axis points that way, and a goofy skater riding normal has the toes there. + environment.local_skater = SkaterState{{0, 0, 0}, 0, 3, 0, false, 0}; + environment.local_skater->right = Vec3{1, 0, 0}; game.pad = PadInput{}; game.state = 100; ticks(host, environment, 2); @@ -3373,16 +3376,18 @@ void grind_exits_pick_the_side() { ticks(host, environment, 2); game.pad->right_trigger = 1; ticks(host, environment, 2); - check(game.called("grind grind-exits heel 2 1.00"), "RT asks for the heel side\n" + environment.all()); + check(game.called("launch grind-exits grind -1.80"), "RT asks for the heel side, the right of travel\n" + environment.all()); check(game.called("hide grind-exits false true true"), "and keeps RT and the stick's lean from the game\n" + environment.all()); - game.pops.push_back({true, "applied", "toe_heel", -1, 1.5f, -1.5f}); + const std::array travel{0.316f, 0.949f}; + game.launches.push_back({"grind", {1.708f, 2, -0.569f}, {-1.708f, 2, 0.569f}, travel, 3.2f, "applied", "grind-exits"}); + game.launches.push_back({"ollie", {0, 3, 4}, {0, 3, 4}, travel, 4, "none", ""}); ticks(host, environment, 1); game.pad->right_trigger = 0; ticks(host, environment, 10); - check(game.called("grind grind-exits none") && game.called("hide grind-exits false false false"), "Let go, the game's own again"); + check(game.called("launch grind-exits grind none") && game.called("hide grind-exits false false false"), "Let go, the game's own again"); environment.lines.clear(); - check(host.run_console("grindexit") && environment.printed("sides set: 1. Last: right of travel (toe_heel)"), - "It counts the pop:\n" + printed_lines(environment)); + check(host.run_console("grindexit") && environment.printed("seen: 1, sides set: 1. Last: right of travel (toe_heel)"), + "It counts the pop out of the grind, not the ollie:\n" + printed_lines(environment)); } void fpv_drone_flies_and_follows() { TestEnvironment environment; diff --git a/Extension/Plugins/plugin_api.cpp b/Extension/Plugins/plugin_api.cpp index 39b465c0..ad1a46d4 100644 --- a/Extension/Plugins/plugin_api.cpp +++ b/Extension/Plugins/plugin_api.cpp @@ -24,7 +24,7 @@ constexpr std::array events{{ {"skater_state", "events.skater"}, {"bail", "events.skater"}, {"chat", "chat.read"}, {"track_changed", "music"}, {"throwdown", "throwdowns"}, {"action", "input"}, {"screen_closed", "ui.screen"}, {"board_landed", "events.skater"}, {"body_hit", "events.skater"}, {"bail_end", "events.skater"}, - {"physics_step", "events.physics"}, {"grind_exit", "events.skater"}, {"trick_notes", "events.tricks"}, + {"physics_step", "events.physics"}, {"launch", "events.skater"}, {"trick_notes", "events.tricks"}, {"throwdown_ui", "throwdowns"}, {"touch", "session.skaters"}, {"touch_end", "session.skaters"}, }}; constexpr std::size_t maximum_key = 128; @@ -496,7 +496,7 @@ struct ApiFunctions { lua_pushnil(L); return 1; } - lua_createtable(L, 0, 6); + lua_createtable(L, 0, 8); push_position(L, s->position); lua_setfield(L, -2, "position"); lua_pushnumber(L, s->heading); @@ -509,6 +509,12 @@ struct ApiFunctions { lua_setfield(L, -2, "airborne"); lua_pushnumber(L, s->air_time); lua_setfield(L, -2, "air_time"); + if (s->right) { + push_position(L, *s->right); + lua_setfield(L, -2, "right"); + lua_pushboolean(L, s->switch_stance); + lua_setfield(L, -2, "switch"); + } return 1; } static int skater_position(lua_State* L) { diff --git a/Extension/Plugins/plugin_api_world.cpp b/Extension/Plugins/plugin_api_world.cpp index b3aa47f9..fa4bdf95 100644 --- a/Extension/Plugins/plugin_api_world.cpp +++ b/Extension/Plugins/plugin_api_world.cpp @@ -50,7 +50,8 @@ const char* skater_state_name(std::uint32_t state) { if (state >= 100 && state < 200) return "riding"; if (state >= 200 && state < 300) return "air"; if (state >= 300 && state < 400) return "wipeout"; - if (state >= 400 && state < 500) return "grinding"; + // 701 is a grind too (Engine/Game/Build/20260929/launches.h, other_grind_state). + if ((state >= 400 && state < 500) || state == 701) return "grinding"; if (state >= 500 && state < 600) return "off_board"; if (state == 602) return "planted"; return state ? "other" : "none"; @@ -107,35 +108,32 @@ struct WorldApi { need(L, plugin, "overlay", "reskate.skater.xray"); return result(L, game(plugin).xray(plugin.id(), lua_toboolean(L, 1) != 0)); } - // skater.grind_exit(wish): the next pop out of a grind, {side = "toe"|"heel"|"left"|"right", - // stance = "regular"|"goofy", strength = 0.1 to 1}; nil the game's own exit. - static int grind_exit(lua_State* L) { + // skater.shape_launch(from, shape): the launches out of `from` ("grind", "ollie", "no_comply" or + // "boneless") from now on, {across =, along =, up =} in m/s, each set if given (across the + // direction of travel, + its left); nil the game's own. + static int shape_launch(lua_State* L) { auto& plugin = plugin_of(L); - need(L, plugin, "skater.control", "reskate.skater.grind_exit"); - if (lua_isnoneornil(L, 1)) return result(L, game(plugin).grind_exit(plugin.id(), std::nullopt)); - luaL_checktype(L, 1, LUA_TTABLE); - GrindExitWish wish; - lua_getfield(L, 1, "side"); - wish.side = luaL_checkstring(L, -1); - lua_pop(L, 1); - if (wish.side != "toe" && wish.side != "heel" && wish.side != "left" && wish.side != "right") - luaL_error(L, "a grind exit's side is \"toe\", \"heel\", \"left\" or \"right\""); - lua_getfield(L, 1, "stance"); - if (!lua_isnil(L, -1)) { - const std::string stance = luaL_checkstring(L, -1); - if (stance == "regular") wish.stance = 1; - else if (stance == "goofy") wish.stance = 2; - else luaL_error(L, "a stance is \"regular\" or \"goofy\""); - } - lua_pop(L, 1); - lua_getfield(L, 1, "strength"); - if (!lua_isnil(L, -1)) { - const double strength = luaL_checknumber(L, -1); - if (!(strength >= 0.1 && strength <= 1)) luaL_error(L, "a grind exit's strength is 0.1 to 1"); - wish.strength = static_cast(strength); - } - lua_pop(L, 1); - return result(L, game(plugin).grind_exit(plugin.id(), std::move(wish))); + need(L, plugin, "skater.control", "reskate.skater.shape_launch"); + const std::string from = luaL_checkstring(L, 1); + if (from != "grind" && from != "ollie" && from != "no_comply" && from != "boneless") + luaL_argerror(L, 1, "a launch is out of \"grind\", \"ollie\", \"no_comply\" or \"boneless\""); + if (lua_isnoneornil(L, 2)) return result(L, game(plugin).shape_launch(plugin.id(), from, std::nullopt)); + luaL_checktype(L, 2, LUA_TTABLE); + LaunchShape shape; + const auto speed = [&](const char* field, std::optional& out) { + lua_getfield(L, 2, field); + if (!lua_isnil(L, -1)) { + const double value = luaL_checknumber(L, -1); + if (!(std::abs(value) <= 50)) luaL_error(L, "a launch's %s speed is -50 to 50 m/s", field); + out = static_cast(value); + } + lua_pop(L, 1); + }; + speed("across", shape.across); + speed("along", shape.along); + speed("up", shape.up); + if (!shape.across && !shape.along && !shape.up) luaL_argerror(L, 2, "a launch's shape sets across, along or up"); + return result(L, game(plugin).shape_launch(plugin.id(), from, shape)); } // board.motion(): the board's velocity in the last physics step, or nil. static int board_motion(lua_State* L) { @@ -2354,7 +2352,7 @@ void WorldApi::install(Plugin& plugin, lua_State* L) { lua_setfield(L, -2, "__metatable"); lua_setfield(L, LUA_REGISTRYINDEX, "reskate.permissions"); add_functions(L, "skater", {{"state", state}, {"noclip", noclip}, {"boost", boost}, {"freeze", freeze}, {"wipeout", wipeout}, {"xray", xray}, - {"grind_exit", grind_exit}, {"reach", reach}, {"hand", hand}, {"joint", skater_joint}, {"pose", skater_pose}}); + {"shape_launch", shape_launch}, {"reach", reach}, {"hand", hand}, {"joint", skater_joint}, {"pose", skater_pose}}); add_functions(L, "board", {{"motion", board_motion}, {"bones", board_bones}, {"set", board_set}, {"hide", board_hide}, {"clear", board_clear}, {"park", board_park}, {"fit", board_fit}, {"toss", board_toss}}); add_functions(L, "camera", {{"look", camera_look}, {"set", camera_set}, {"follow", camera_follow}, {"release", camera_release}, {"held", camera_held}, {"get", camera_get}}); diff --git a/Extension/Plugins/plugin_game.h b/Extension/Plugins/plugin_game.h index 60fbcec6..77144712 100644 --- a/Extension/Plugins/plugin_game.h +++ b/Extension/Plugins/plugin_game.h @@ -127,19 +127,18 @@ struct PhysicsStep { }; std::vector bodies; }; -// What a plugin asks of the next pop out of a grind: "toe", "heel", "left" or "right" (of travel), -// the player's stance (0 not set, 1 regular, 2 goofy: what toe and heel need), and 0.1 to 1 of the -// full sideways speed. And a pop as it went (Extension/Skater/grind_exit.h). -struct GrindExitWish { - std::string side; - int stance{}; - float strength{1}; +// What a plugin sets of a launch into the air, m/s, each if given: across the direction of travel +// (+ its left), along it, and up. And a launch as it went (Extension/Skater/launches.h). +struct LaunchShape { + std::optional across, along, up; }; -struct GrindPop { - bool applied{}; - std::string reason, basis; - int toward{}; // +1 the left of travel, -1 the right, 0 the game's own - float before{}, after{}; +struct LaunchNews { + std::string from; // "grind", "ollie", "no_comply", "boneless" or "other" + Vec3 game{}, velocity{}; // as the game made it, as it left + std::optional> travel; // the horizontal direction of travel (x, z), when moving + float travel_speed{}; + std::string reason; // "applied", "none", "slow", "not_ours" + std::string by; // the plugin whose shape applied }; // The last slam: its force and the game's cause number, and how long ago (ms). struct SlamNews { @@ -730,10 +729,11 @@ public: // multiplayer session. And the speed it runs at for plugins now. virtual std::string game_speed(std::string_view /*plugin*/, float) { return unavailable; } virtual float game_speed_now() { return 1; } - // The next pop out of a grind (Extension/Skater/grind_exit.h); empty: the game's own exit. One - // plugin at a time. Its pops come back from take_grind_pops. - virtual std::string grind_exit(std::string_view /*plugin*/, std::optional) { return unavailable; } - virtual std::vector take_grind_pops(std::string_view /*plugin*/) { return {}; } + // The local skater's launches out of `from` ("grind", "ollie", "no_comply" or "boneless") from + // now on (Extension/Skater/launches.h); empty: the game's own. One plugin a kind at a time. Every + // launch comes back from take_launches. + virtual std::string shape_launch(std::string_view /*plugin*/, std::string_view /*from*/, std::optional) { return unavailable; } + virtual std::vector take_launches() { return {}; } // Pad controls kept from the game while any plugin asks: the triggers, and the left stick's // left/right while the skater grinds. // The skater carried up to a speed (m/s, 0 off) for the next 300 ms, eased down to it with may_slow. diff --git a/Extension/Plugins/plugin_game_runtime.cpp b/Extension/Plugins/plugin_game_runtime.cpp index 68ec403c..11735083 100644 --- a/Extension/Plugins/plugin_game_runtime.cpp +++ b/Extension/Plugins/plugin_game_runtime.cpp @@ -23,7 +23,8 @@ #include #include "Extension/Assets/live_mods.h" #include "Extension/Skater/game_speed.h" -#include "Extension/Skater/grind_exit.h" +#include "Extension/Skater/launches.h" +#include "Extension/Skater/pad_hide.h" #include "Extension/Skater/xray.h" #include "Extension/UI/hud_corner.h" #include "Extension/Throwdowns/custom_modes.h" @@ -573,14 +574,12 @@ public: return {}; } float game_speed_now() override { return game_speed::current(); } - std::string grind_exit(std::string_view plugin, std::optional wish) override { - std::optional w; - if (wish) { - using Side = grind_exit::Wish::Side; - w = grind_exit::Wish{wish->side == "toe" ? Side::toe : wish->side == "heel" ? Side::heel : wish->side == "right" ? Side::right : Side::left, - wish->stance, wish->strength}; - } - return grind_exit::wish(plugin, w); + std::string shape_launch(std::string_view plugin, std::string_view from, std::optional shape) override { + const auto kind = launches::from_name(from); + if (!kind) return "a launch is out of a grind, ollie, no_comply or boneless"; + std::optional s; + if (shape) s = launches::Shape{shape->across, shape->along, shape->up}; + return launches::shape(plugin, *kind, s); } std::string set_throwdown_mode(std::string_view plugin, const ThrowdownMode& m) override { multiplayer::custom_throwdowns::Mode mode; @@ -745,9 +744,13 @@ public: void remove_throwdown_mode(std::string_view plugin, std::string_view id) override { multiplayer::custom_throwdowns::remove_mode(plugin, id); } - std::vector take_grind_pops(std::string_view plugin) override { - std::vector out; - for (auto& p : grind_exit::take_pops(plugin)) out.push_back({p.applied, std::move(p.reason), std::move(p.basis), p.toward, p.before, p.after}); + std::vector take_launches() override { + std::vector out; + for (auto& l : launches::take()) { + LaunchNews news{launches::name(l.from), l.game, l.velocity, std::nullopt, l.travel_speed, std::move(l.reason), std::move(l.by)}; + if (l.travel[0] != 0 || l.travel[1] != 0) news.travel = l.travel; + out.push_back(std::move(news)); + } return out; } // A speed boost: off where the session's host turned boosts off, and while a throwdown runs. @@ -764,8 +767,8 @@ public: return {}; } std::string hide_pad(std::string_view plugin, const PadHidden& h) override { - if (!grind_exit::available()) return "hiding pad controls is unavailable for this game build"; - grind_exit::hide(plugin, {h.left_trigger, h.right_trigger, h.left_x_grinding, h.right_stick, h.left_stick_click, h.right_stick_click}); + if (!pad_hide::available()) return "hiding pad controls is unavailable for this game build"; + pad_hide::hide(plugin, {h.left_trigger, h.right_trigger, h.left_x_grinding, h.right_stick, h.left_stick_click, h.right_stick_click}); return {}; } std::optional board_velocity() override { @@ -1045,7 +1048,7 @@ public: xray::tick(); hud_corner::tick(); game_speed::tick(); - grind_exit::tick(client); + launches::tick(client); outfit_overlay::tick(); board_pose::tick(base, local_skater_component(base, client)); const bool allowed = !multiplayer::local_throwdown_active(); @@ -1557,7 +1560,8 @@ public: hud_corner::release(plugin); game_speed::ask(plugin, 1.0f); held_settings::release(plugin); - grind_exit::release(plugin); + launches::release(plugin); + pad_hide::release(plugin); trick_rewrite::release(plugin); trainer::release_plugin_tunes(plugin); if (reacher_ == plugin) { diff --git a/Extension/Plugins/plugin_host.cpp b/Extension/Plugins/plugin_host.cpp index a79530ad..5827101a 100644 --- a/Extension/Plugins/plugin_host.cpp +++ b/Extension/Plugins/plugin_host.cpp @@ -314,19 +314,8 @@ void Host::follow_game() { data["bodies"] = std::move(bodies); each([&](Plugin& plugin, const std::string&) { plugin.game_event("physics_step", "events.physics", data); }); } - if (wants("skater.control")) - each([&](Plugin& plugin, const std::string& id) { - for (const auto& pop : game.take_grind_pops(id)) { - auto data = Json::object(); - data["applied"] = pop.applied; - data["reason"] = pop.reason; - if (!pop.basis.empty()) data["basis"] = pop.basis; - data["side"] = pop.toward > 0 ? "left" : pop.toward < 0 ? "right" : ""; - data["before"] = pop.before; - data["after"] = pop.after; - plugin.game_event("grind_exit", "events.skater", data); - } - }); + if (wants("events.skater")) + for (const auto& launch : game.take_launches()) each([&](Plugin& plugin, const std::string&) { plugin.launched(launch); }); // Touching (reskate.session.touch): the bodies read once for every plugin that has it on. { std::optional> list; diff --git a/Extension/Plugins/plugin_runtime.cpp b/Extension/Plugins/plugin_runtime.cpp index c96635c3..919ba1e7 100644 --- a/Extension/Plugins/plugin_runtime.cpp +++ b/Extension/Plugins/plugin_runtime.cpp @@ -12,6 +12,7 @@ #include "Extension/UI/Overlay/overlay.h" #include "Extension/Console/commands.h" #include "Extension/Profile/local_profile_runtime.h" +#include "Extension/Skater/launches.h" #include "Extension/Skater/score_hud.h" #include "Extension/Skater/trick_watch.h" #include "Extension/Trainer/trainer.h" @@ -70,7 +71,12 @@ public: const auto t = trainer::telemetry(); if (!t.skater) return std::nullopt; const float heading = std::remainder(t.heading * std::numbers::pi_v / 180, 2 * std::numbers::pi_v); - return SkaterState{t.position, heading, t.speed, t.vertical, t.airborne, t.air_time}; + SkaterState state{t.position, heading, t.speed, t.vertical, t.airborne, t.air_time}; + if (const auto facing = launches::facing()) { + state.right = facing->right; + state.switch_stance = facing->switch_stance; + } + return state; } // As the console's tp: refused in a session whose host has turned teleporting (and noclip) off. Teleport teleport(std::string_view plugin, const Vec3& position, float heading) override { diff --git a/Extension/Plugins/plugin_vm.cpp b/Extension/Plugins/plugin_vm.cpp index ab23b289..b9eb0b41 100644 --- a/Extension/Plugins/plugin_vm.cpp +++ b/Extension/Plugins/plugin_vm.cpp @@ -580,6 +580,35 @@ void Plugin::skater_changed(const char* state, const char* previous) { }); } +// A launch into the air (events.skater), with its velocities as vectors. +void Plugin::launched(const LaunchNews& launch) { + if (state_ != State::running || !has("events.skater")) return; + fire_with("launch", [&](lua_State* L) { + lua_createtable(L, 0, 8); + lua_pushlstring(L, launch.from.data(), launch.from.size()); + lua_setfield(L, -2, "from"); + push_position(L, launch.velocity); + lua_setfield(L, -2, "velocity"); + push_position(L, launch.game); + lua_setfield(L, -2, "game"); + if (launch.travel) { + push_position(L, Vec3{(*launch.travel)[0], 0, (*launch.travel)[1]}); + lua_setfield(L, -2, "travel"); + } + lua_pushnumber(L, launch.travel_speed); + lua_setfield(L, -2, "travel_speed"); + lua_pushboolean(L, launch.reason == "applied"); + lua_setfield(L, -2, "shaped"); + lua_pushlstring(L, launch.reason.data(), launch.reason.size()); + lua_setfield(L, -2, "reason"); + if (!launch.by.empty()) { + lua_pushlstring(L, launch.by.data(), launch.by.size()); + lua_setfield(L, -2, "by"); + } + return 1; + }); +} + void Plugin::game_event(std::string_view event, std::string_view permission, const Json& data) { if (state_ != State::running || !has(permission)) return; fire_with(event, [&](lua_State* L) { diff --git a/Extension/Plugins/plugin_vm.h b/Extension/Plugins/plugin_vm.h index 12bef132..59bc5a83 100644 --- a/Extension/Plugins/plugin_vm.h +++ b/Extension/Plugins/plugin_vm.h @@ -76,6 +76,9 @@ struct SkaterState { float vertical{}; // metres per second, up bool airborne{}; float air_time{}; // seconds into the jump going on + // The skater root's right axis, and whether it rides switch (Extension/Skater/launches.h), when known. + std::optional right; + bool switch_stance{}; }; enum class Teleport { sent, unavailable, forbidden }; // A trick of the local skater, read from the score HUD (Extension/Skater/trick_watch.h). @@ -217,6 +220,7 @@ public: // The wider set's events, each to a plugin with its permission and a handler for it. void skater_changed(const char* state, const char* previous); void bailed(std::uint64_t wipeouts, std::optional slam); + void launched(const LaunchNews& launch); // A game event with plain data (board_landed, body_hit, bail_end), to a plugin with `permission`. void game_event(std::string_view event, std::string_view permission, const Json& data); void chat_line(const ChatLine& line); diff --git a/Extension/Skater/grind_exit.cpp b/Extension/Skater/grind_exit.cpp deleted file mode 100644 index 0e5d0a40..00000000 --- a/Extension/Skater/grind_exit.cpp +++ /dev/null @@ -1,311 +0,0 @@ -#include "grind_exit.h" -#include "client_source_spawn_internal.h" -#include "no_bail.h" -#include "Engine/Core/Log/logging.h" -#include "Engine/Core/Platform/memory.h" -#include "Engine/Game/Build/addresses.h" -#include "Engine/Game/Build/20260929/grind_exit.h" -#include "Engine/Game/Build/20260929/skater_state.h" -#include "Extension/Trainer/trainer_jump.h" -#include "Extension/UI/Overlay/overlay.h" -#include -#include -#include -#include -#include -#include -#include - -namespace dingosdk::grind_exit { -namespace { -namespace sstate = addr::skater_state; -namespace gx = addr::grind_exit; - -constexpr double fresh_seconds = 0.1; // a grind step this recent means "grinding now" -constexpr double axes_fresh_seconds = 0.25; // the skater's facing, sampled on the client tick -constexpr std::size_t rail_steps = 6; // grind steps the rail's direction is averaged over -constexpr float min_rail_speed = 0.5f, min_kept_side_speed = 1.0f; -constexpr float min_toe_across = 0.35f; // below this the toe points along the rail (slides): left/right of travel instead -constexpr std::size_t most_pops = 32; - -struct Control { - std::atomic base{}; - std::atomic started{}, listening{}, modifying{}, hooked{}; - // Physics thread -> the launch hook and the pad filter. - std::atomic state{}; - std::atomic step_qpc{}; - std::atomic rail_x{}, rail_z{}, rail_speed{}; - std::atomic switch_stance{}; - // Client tick -> the launch hook: the skater root's right axis, flattened. - std::atomic right_x{}, right_z{}; - std::atomic axes_qpc{}; - std::uint64_t next_axes_try{}; - // Physics thread only. - std::array, rail_steps> rail{}; - std::size_t rail_head{}, rail_count{}; - // The plugin's wish (client thread -> the launch hook), and the pad's hidden controls. - std::mutex mutex; - std::string wisher; - std::optional wanted; - std::map> hidden; - std::atomic hide_lt{}, hide_rt{}, hide_left_x{}, hide_right_stick{}, hide_l3{}, hide_r3{}; - std::vector pops; // under mutex - std::atomic side_speed{gx::measured_side_speed}; -}; -Control& control() { - static auto* value = new Control; // process lifetime: hooks may call in until exit - return *value; -} -std::int64_t qpc_now() noexcept { - LARGE_INTEGER value{}; - QueryPerformanceCounter(&value); - return value.QuadPart; -} -double seconds(std::int64_t ticks) noexcept { - static const double frequency = [] { - LARGE_INTEGER value{}; - QueryPerformanceFrequency(&value); - return static_cast(value.QuadPart); - }(); - return static_cast(ticks) / frequency; -} -bool grind_state(std::uint32_t state) noexcept { return state >= gx::grind_states_first && state < gx::grind_states_end; } -bool grinding_now(const Control& c, std::int64_t now) noexcept { - return grind_state(c.state.load(std::memory_order_acquire)) && seconds(now - c.step_qpc.load(std::memory_order_acquire)) < fresh_seconds; -} - -// ---- physics side -------------------------------------------------------------------------- -// The board's root body velocity (as skater_events.cpp reads it). -bool board_velocity(const NoBailSkater& skater, std::array& velocity) noexcept { - std::uintptr_t holder{}, physics{}, type{}, bodies{}, owner{}; - std::uint32_t count{}; - if (!memory::peek(skater.core + sstate::board_holder_offset, holder) || !memory::peek(holder + sstate::board_physics_offset, physics) || - !memory::peek(physics, type) || type != skater.base + sstate::board_physics_vtable || - !memory::peek(physics + sstate::physics_bodies_offset, bodies) || !memory::peek(bodies, count) || count != sstate::board_body_count) - return false; - const auto body = bodies + sstate::board_root_body * sstate::physics_body_size; - if (!memory::peek(body + sstate::body_owner_offset, owner) || owner != physics || !memory::peek(body + sstate::body_velocity_offset, velocity)) - return false; - return std::all_of(velocity.begin(), velocity.end(), [](float v) { return std::isfinite(v) && std::abs(v) < 1000; }); -} -// Physics thread, every step of the local skater. -void on_step(const NoBailSkater& skater, float, bool) noexcept { - auto& c = control(); - std::uint32_t state{}; - if (!memory::peek(skater.context + sstate::physics_state_offset, state)) return; - const auto now = qpc_now(); - std::uint8_t flags{}; - if (memory::peek(skater.context + gx::stance_flags_offset, flags)) - c.switch_stance.store((flags & gx::stance_switch_mask) != 0, std::memory_order_relaxed); - if (grind_state(state)) { - std::array v{}; - if (board_velocity(skater, v)) { - c.rail[c.rail_head] = {v[0], v[2]}; - c.rail_head = (c.rail_head + 1) % rail_steps; - c.rail_count = std::min(c.rail_count + 1, rail_steps); - float x{}, z{}; - for (std::size_t i = 0; i < c.rail_count; ++i) { - x += c.rail[i][0]; - z += c.rail[i][1]; - } - x /= static_cast(c.rail_count); - z /= static_cast(c.rail_count); - const float speed = std::hypot(x, z); - if (speed > 0.0f) { - c.rail_x.store(x / speed, std::memory_order_relaxed); - c.rail_z.store(z / speed, std::memory_order_relaxed); - } - c.rail_speed.store(speed, std::memory_order_relaxed); - } - } else { - c.rail_count = c.rail_head = 0; - } - c.step_qpc.store(now, std::memory_order_release); - c.state.store(state, std::memory_order_release); -} - -// ---- input side ---------------------------------------------------------------------------- -// Any thread the game polls its pad from. -void filter_pad(unsigned, overlay::GamepadValues& pad) noexcept { - auto& c = control(); - if (c.hide_lt.load(std::memory_order_acquire)) pad.left_trigger = 0; - if (c.hide_rt.load(std::memory_order_acquire)) pad.right_trigger = 0; - if (c.hide_left_x.load(std::memory_order_acquire) && grinding_now(c, qpc_now())) pad.left_x = 0; - if (c.hide_right_stick.load(std::memory_order_acquire)) pad.right_x = pad.right_y = 0; - if (c.hide_l3.load(std::memory_order_acquire)) pad.buttons &= static_cast(~0x0040u); // XINPUT_GAMEPAD_LEFT_THUMB - if (c.hide_r3.load(std::memory_order_acquire)) pad.buttons &= static_cast(~0x0080u); // XINPUT_GAMEPAD_RIGHT_THUMB -} -void publish_hidden(Control& c) { - Hidden all; - for (const auto& [owner, h] : c.hidden) { - all.left_trigger = all.left_trigger || h.left_trigger; - all.right_trigger = all.right_trigger || h.right_trigger; - all.left_x_grinding = all.left_x_grinding || h.left_x_grinding; - all.right_stick = all.right_stick || h.right_stick; - all.left_stick_click = all.left_stick_click || h.left_stick_click; - all.right_stick_click = all.right_stick_click || h.right_stick_click; - } - c.hide_lt.store(all.left_trigger, std::memory_order_release); - c.hide_rt.store(all.right_trigger, std::memory_order_release); - c.hide_left_x.store(all.left_x_grinding, std::memory_order_release); - c.hide_right_stick.store(all.right_stick, std::memory_order_release); - c.hide_l3.store(all.left_stick_click, std::memory_order_release); - c.hide_r3.store(all.right_stick_click, std::memory_order_release); - DingoSDKOverlaySetGamepadFilter(c.hidden.empty() ? nullptr : &filter_pad); -} - -// ---- the launch ---------------------------------------------------------------------------- -// Which way across the rail (+1 the left of travel, n = (tz, -tx); -1 the right) the wish points. -int toward_side(Control& c, const Wish& w, float nx, float nz, std::int64_t now, Pop& pop) noexcept { - using Side = Wish::Side; - const int fallback = w.side == Side::toe || w.side == Side::left ? 1 : -1; - if (w.side == Side::left || w.side == Side::right) { pop.basis = "travel"; return fallback; } - if (w.stance != 1 && w.stance != 2) { pop.basis = "no_stance"; return fallback; } - if (seconds(now - c.axes_qpc.load(std::memory_order_acquire)) > axes_fresh_seconds) { pop.basis = "no_facing"; return fallback; } - float rx = c.right_x.load(std::memory_order_relaxed), rz = c.right_z.load(std::memory_order_relaxed); - const float length = std::hypot(rx, rz); - if (length < 0.2f) { pop.basis = "no_facing"; return fallback; } - rx /= length; - rz /= length; - // The toes point to the root's right axis when the right foot leads (goofy riding normal or - // fakie: measured), to the other side when the left foot does (regular, or goofy switch). - const bool right_foot_leads = (w.stance == 2) != c.switch_stance.load(std::memory_order_relaxed); - const float toe_x = right_foot_leads ? rx : -rx, toe_z = right_foot_leads ? rz : -rz; - const float across = toe_x * nx + toe_z * nz; - if (std::abs(across) < min_toe_across) { pop.basis = "slide"; return fallback; } - pop.basis = "toe_heel"; - const int toe = across > 0 ? 1 : -1; - return w.side == Side::toe ? toe : -toe; -} -void record(Control& c, Pop pop) { - std::lock_guard lock(c.mutex); - if (c.pops.size() < most_pops) c.pops.push_back(std::move(pop)); -} -// Physics thread: every call of the jump-trajectory setter. -void modify_launch(std::uintptr_t caller_rva, std::uintptr_t, float (&v)[3]) noexcept { - auto& c = control(); - if (caller_rva != gx::grind_pop_call) return; - const auto now = qpc_now(); - if (!grinding_now(c, now)) return; // no complies and pops from a manual launch from the same call - try { - std::optional wish; - { - std::lock_guard lock(c.mutex); - if (c.wisher.empty()) return; - wish = c.wanted; - } - Pop pop; - const float tx = c.rail_x.load(std::memory_order_relaxed), tz = c.rail_z.load(std::memory_order_relaxed); - const float speed = c.rail_speed.load(std::memory_order_relaxed); - const float nx = tz, nz = -tx; - const float along = v[0] * tx + v[2] * tz; - pop.before = pop.after = v[0] * nx + v[2] * nz; - if (!wish) { pop.reason = "none"; return record(c, std::move(pop)); } - if (speed < min_rail_speed) { pop.reason = "slow"; return record(c, std::move(pop)); } - // The launch must be this skater's: mostly along its rail, at about its speed (measured 0.93x). - const float horizontal = std::hypot(v[0], v[2]); - if (!(along > 0.5f * speed && along < 1.6f * speed && horizontal < 2.0f * speed + 3.0f)) { - pop.reason = "not_ours"; - return record(c, std::move(pop)); - } - pop.toward = toward_side(c, *wish, nx, nz, now, pop); - const float side = static_cast(pop.toward); - // Strength scales the sideways speed; at full strength the game's own exit toward the chosen - // side is kept as it is. - const float strength = std::clamp(wish->strength, 0.1f, 1.0f); - if (strength >= 1.0f && pop.before * side >= min_kept_side_speed) { pop.reason = "kept"; return record(c, std::move(pop)); } - const float full = std::abs(pop.before) >= min_kept_side_speed ? std::abs(pop.before) : c.side_speed.load(std::memory_order_relaxed); - const float magnitude = full * strength; - const float change = side * magnitude - pop.before; - v[0] += change * nx; - v[2] += change * nz; - pop.after = side * magnitude; - pop.applied = true; - pop.reason = "applied"; - record(c, std::move(pop)); - } catch (...) { - } -} - -// The step listener and the launch hook, from the first use on. -void listen(Control& c) { - if (!c.listening.load(std::memory_order_acquire) && add_no_bail_step_listener(&on_step)) c.listening.store(true, std::memory_order_release); - if (!c.modifying.exchange(true)) trainer::set_trajectory_modifier(&modify_launch); -} -} // namespace - -bool start(std::uintptr_t image_base) noexcept { - auto& c = control(); - if (c.started.load(std::memory_order_acquire)) return true; - if (!no_bail_available()) { - logging::write(logging::Level::warning, logging::Channel::skater, - "Grind exits for plugins are unavailable: they follow the skater's physics steps through No Bail's hooks, which did not start."); - return false; - } - c.base.store(image_base, std::memory_order_release); - c.started.store(true, std::memory_order_release); - return true; -} -bool available() noexcept { return control().started.load(std::memory_order_acquire); } - -std::string wish(std::string_view owner, std::optional w) { - auto& c = control(); - if (!c.started.load(std::memory_order_acquire)) return "grind exits are unavailable for this game build"; - std::lock_guard lock(c.mutex); - if (!c.wisher.empty() && c.wisher != owner) return c.wisher + " picks the grind exits"; - c.wisher = owner; - c.wanted = w; - listen(c); - return {}; -} -void hide(std::string_view owner, Hidden hidden) { - auto& c = control(); - if (!c.started.load(std::memory_order_acquire)) return; - std::lock_guard lock(c.mutex); - if (hidden.any()) c.hidden.insert_or_assign(std::string(owner), hidden); - else if (const auto found = c.hidden.find(owner); found != c.hidden.end()) c.hidden.erase(found); - if (hidden.left_x_grinding) listen(c); - publish_hidden(c); -} -void release(std::string_view owner) { - auto& c = control(); - std::lock_guard lock(c.mutex); - if (c.wisher == owner) { - c.wisher.clear(); - c.wanted.reset(); - c.pops.clear(); - } - if (const auto found = c.hidden.find(owner); found != c.hidden.end()) { - c.hidden.erase(found); - publish_hidden(c); - } -} -std::vector take_pops(std::string_view owner) { - auto& c = control(); - std::lock_guard lock(c.mutex); - if (c.wisher != owner) return {}; - return std::exchange(c.pops, {}); -} -bool grinding() noexcept { return grinding_now(control(), qpc_now()); } - -void tick(std::uintptr_t client) noexcept { - auto& c = control(); - if (!c.started.load(std::memory_order_acquire) || !c.modifying.load(std::memory_order_acquire)) return; - const auto base = c.base.load(std::memory_order_acquire); - if (!c.hooked.load(std::memory_order_acquire) && trainer::start_trick_heights(base)) c.hooked.store(true, std::memory_order_release); - // The skater's facing (rows: right, up, forward, position), for toe and heel. - const auto now = GetTickCount64(); - if (now < c.next_axes_try) return; - try { - overlay::DebugModel skater; - const auto matrix = client_source::detail::debug_skater(base, client, skater); - if (skater.skater_position_valid && std::isfinite(matrix[0]) && std::isfinite(matrix[2])) { - c.right_x.store(matrix[0], std::memory_order_relaxed); - c.right_z.store(matrix[2], std::memory_order_relaxed); - c.axes_qpc.store(qpc_now(), std::memory_order_release); - } - } catch (...) { - c.next_axes_try = now + 250; // no skater: menus, loading, a respawn - } -} -} diff --git a/Extension/Skater/grind_exit.h b/Extension/Skater/grind_exit.h deleted file mode 100644 index 72604047..00000000 --- a/Extension/Skater/grind_exit.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include - -// Which side the local skater pops out of a grind to, as a plugin asks (reskate.skater.grind_exit; -// the grind-exits mod picks it with LT / RT), and pad controls kept from the game meanwhile -// (reskate.input.hide). What decides the side, when and why, is the plugin's; this is what has to -// happen in the game's own threads. -// -// At the pop the game calls its jump-trajectory setter with the launch velocity; the trainer hooks -// that call (Extension/Trainer/trainer_jump.cpp). For a pop out of a grind (its call site, measured: -// Engine/Game/Build/20260929/grind_exit.h) only the sideways part of the launch is replaced, across -// the rail: the game's own sideways speed when it had one, mirrored to the chosen side, else -// `side_speed` (1.8 m/s, the game's own), scaled by the wish's strength. Along the rail, the pop's -// height, the trick, the flip and the body's rotation stay the game's. -// -// Toe and heel come from which foot leads (the game's stance flag, grind_exit.h, and the player's -// stance as the wish gives it) and the skater root's right axis: the toes face it when the right foot -// leads (measured). On slides, where toe and heel point along the rail, and without a stance, toe is -// the left of travel and heel the right. -// -// The pad: a hidden trigger reads as let go; the left stick's left/right, hidden "while grinding", -// reads as centred only while the skater grinds, so it reaches the game again at the pop. -namespace dingosdk::grind_exit { -// Requires the validated build and No Bail started. -bool start(std::uintptr_t image_base) noexcept; -bool available() noexcept; - -struct Wish { - enum class Side : std::uint8_t { toe, heel, left, right } side{Side::left}; - int stance{}; // 0 not set, 1 regular, 2 goofy: what toe and heel need - float strength{1.0f}; // 0.1 to 1 of the full sideways speed -}; -// Client thread: what the next pop out of a grind does (empty: the game's own exit). One plugin at a -// time: empty when taken, else why not. -std::string wish(std::string_view owner, std::optional wish); -struct Hidden { - bool left_trigger{}, right_trigger{}, left_x_grinding{}; - // The right stick (reads centred) and the stick clicks (read up), whatever the skater does. - bool right_stick{}, left_stick_click{}, right_stick_click{}; - bool any() const noexcept { return left_trigger || right_trigger || left_x_grinding || right_stick || left_stick_click || right_stick_click; } -}; -void hide(std::string_view owner, Hidden hidden); -// Whatever the plugin held. -void release(std::string_view owner); - -// A pop out of a grind, as it went: whether the side was set, and why (or why not). -struct Pop { - bool applied{}; - std::string reason; // "applied", "kept", "none", "slow", "not_ours" - std::string basis; // "toe_heel", "slide", "no_stance", "no_facing", "travel" - int toward{}; // +1 the left of travel, -1 the right, 0 the game's own - float before{}, after{}; // the sideways speed, m/s (+ the left) -}; -// Client thread: the pops since the last call, for the plugin that wished. -std::vector take_pops(std::string_view owner); -// The skater is grinding now (as the physics steps last said). -bool grinding() noexcept; -// Client thread, every tick, with the local client: the launch hook made ready while used, the -// skater's facing sampled. -void tick(std::uintptr_t client) noexcept; -} diff --git a/Extension/Skater/launches.cpp b/Extension/Skater/launches.cpp new file mode 100644 index 00000000..2cb59372 --- /dev/null +++ b/Extension/Skater/launches.cpp @@ -0,0 +1,287 @@ +#include "launches.h" +#include "client_source_spawn_internal.h" +#include "no_bail.h" +#include "Engine/Core/Log/logging.h" +#include "Engine/Core/Platform/memory.h" +#include "Engine/Game/Build/addresses.h" +#include "Engine/Game/Build/20260929/launches.h" +#include "Engine/Game/Build/20260929/skater_state.h" +#include "Extension/Trainer/trainer_jump.h" +#include "Extension/UI/Overlay/overlay.h" +#include +#include +#include +#include +#include +#include +#include + +namespace dingosdk::launches { +namespace { +namespace sstate = addr::skater_state; +namespace gl = addr::launches; + +constexpr double fresh_seconds = 0.1; // a physics step this recent means "the state now" +constexpr double axes_fresh_seconds = 0.25; // the skater's facing, sampled on the client tick +constexpr std::size_t travel_steps = 6; // physics steps the direction of travel is averaged over +constexpr float min_travel_speed = 0.5f; +constexpr std::size_t most_launches = 32; +constexpr std::size_t kinds = 5; + +struct Control { + std::atomic base{}; + std::atomic started{}, listening{}, modifying{}, hooked{}; + // Physics thread -> the launch hook. + std::atomic state{}; + std::atomic step_qpc{}; + std::atomic travel_x{}, travel_z{}, travel_speed{}; + std::atomic switch_stance{}; + // Client tick -> reskate.skater.get: the skater root's right axis. + std::atomic right_x{}, right_y{}, right_z{}; + std::atomic axes_qpc{}; + std::atomic facing_asked{}; + std::uint64_t next_axes_try{}; + // Physics thread only. + std::array, travel_steps> travel{}; + std::size_t travel_head{}, travel_count{}; + // The plugins' shapes by kind (client thread -> the launch hook), and the launches seen. + std::mutex mutex; + std::array shaper; + std::array, kinds> shapes; + std::vector launches; // under mutex +}; +Control& control() { + static auto* value = new Control; // process lifetime: hooks may call in until exit + return *value; +} +std::int64_t qpc_now() noexcept { + LARGE_INTEGER value{}; + QueryPerformanceCounter(&value); + return value.QuadPart; +} +double seconds(std::int64_t ticks) noexcept { + static const double frequency = [] { + LARGE_INTEGER value{}; + QueryPerformanceFrequency(&value); + return static_cast(value.QuadPart); + }(); + return static_cast(ticks) / frequency; +} +bool grinding_now(const Control& c, std::int64_t now) noexcept { + return gl::is_grind_state(c.state.load(std::memory_order_acquire)) && seconds(now - c.step_qpc.load(std::memory_order_acquire)) < fresh_seconds; +} + +// ---- physics side -------------------------------------------------------------------------- +// The board's root body velocity (as skater_events.cpp reads it). +bool board_velocity(const NoBailSkater& skater, std::array& velocity) noexcept { + std::uintptr_t holder{}, physics{}, type{}, bodies{}, owner{}; + std::uint32_t count{}; + if (!memory::peek(skater.core + sstate::board_holder_offset, holder) || !memory::peek(holder + sstate::board_physics_offset, physics) || + !memory::peek(physics, type) || type != skater.base + sstate::board_physics_vtable || + !memory::peek(physics + sstate::physics_bodies_offset, bodies) || !memory::peek(bodies, count) || count != sstate::board_body_count) + return false; + const auto body = bodies + sstate::board_root_body * sstate::physics_body_size; + if (!memory::peek(body + sstate::body_owner_offset, owner) || owner != physics || !memory::peek(body + sstate::body_velocity_offset, velocity)) + return false; + return std::all_of(velocity.begin(), velocity.end(), [](float v) { return std::isfinite(v) && std::abs(v) < 1000; }); +} +// Physics thread, every step of the local skater. +void on_step(const NoBailSkater& skater, float, bool) noexcept { + auto& c = control(); + std::uint32_t state{}; + if (!memory::peek(skater.context + sstate::physics_state_offset, state)) return; + const auto now = qpc_now(); + std::uint8_t flags{}; + if (memory::peek(skater.context + gl::stance_flags_offset, flags)) + c.switch_stance.store((flags & gl::stance_switch_mask) != 0, std::memory_order_relaxed); + // The direction of travel: the board's horizontal velocity over the last steps on the board. + std::array v{}; + if (state != gl::pop_state && board_velocity(skater, v)) { + c.travel[c.travel_head] = {v[0], v[2]}; + c.travel_head = (c.travel_head + 1) % travel_steps; + c.travel_count = std::min(c.travel_count + 1, travel_steps); + float x{}, z{}; + for (std::size_t i = 0; i < c.travel_count; ++i) { + x += c.travel[i][0]; + z += c.travel[i][1]; + } + x /= static_cast(c.travel_count); + z /= static_cast(c.travel_count); + const float speed = std::hypot(x, z); + if (speed > 0.0f) { + c.travel_x.store(x / speed, std::memory_order_relaxed); + c.travel_z.store(z / speed, std::memory_order_relaxed); + } + c.travel_speed.store(speed, std::memory_order_relaxed); + } + c.step_qpc.store(now, std::memory_order_release); + c.state.store(state, std::memory_order_release); +} + +// ---- the launch ---------------------------------------------------------------------------- +From classify(Control& c, std::uintptr_t caller_rva, std::int64_t now) noexcept { + if (caller_rva == gl::ollie_call) return From::ollie; + if (caller_rva == gl::boneless_call) return From::boneless; + // The grind pop's call is also the no comply's (from the crouch). + if (caller_rva == gl::grind_pop_call) return grinding_now(c, now) ? From::grind : From::no_comply; + return From::other; +} +void record(Control& c, Launch launch) { + std::lock_guard lock(c.mutex); + if (c.launches.size() < most_launches) c.launches.push_back(std::move(launch)); +} +// Physics thread: every call of the jump-trajectory setter. +void modify_launch(std::uintptr_t caller_rva, std::uintptr_t, float (&v)[3]) noexcept { + auto& c = control(); + try { + Launch launch; + launch.from = classify(c, caller_rva, qpc_now()); + launch.game = launch.velocity = {v[0], v[1], v[2]}; + const float tx = c.travel_x.load(std::memory_order_relaxed), tz = c.travel_z.load(std::memory_order_relaxed); + launch.travel_speed = c.travel_speed.load(std::memory_order_relaxed); + if (launch.travel_speed >= min_travel_speed) launch.travel = {tx, tz}; + std::optional shape; + { + std::lock_guard lock(c.mutex); + const auto kind = static_cast(launch.from); + shape = c.shapes[kind]; + if (shape) launch.by = c.shaper[kind]; + } + if (!shape) { + launch.reason = "none"; + return record(c, std::move(launch)); + } + if (shape->up) v[1] = *shape->up; + if (shape->across || shape->along) { + if (launch.travel_speed < min_travel_speed) { + launch.reason = "slow"; + launch.by.clear(); + return record(c, std::move(launch)); + } + // A launch along its travel, at about its speed (out of a grind, measured 0.93x): a + // launch of something else through the same call is left alone. + const float along = v[0] * tx + v[2] * tz; + const float horizontal = std::hypot(v[0], v[2]); + if (!(along > 0.5f * launch.travel_speed && along < 1.6f * launch.travel_speed && horizontal < 2.0f * launch.travel_speed + 3.0f)) { + launch.reason = "not_ours"; + launch.by.clear(); + return record(c, std::move(launch)); + } + const float nx = tz, nz = -tx; // the left of travel + const float across_now = v[0] * nx + v[2] * nz; + const float new_along = shape->along.value_or(along), new_across = shape->across.value_or(across_now); + v[0] = new_along * tx + new_across * nx; + v[2] = new_along * tz + new_across * nz; + } + launch.velocity = {v[0], v[1], v[2]}; + launch.reason = "applied"; + record(c, std::move(launch)); + } catch (...) { + } +} + +// The step listener and the launch hook, from the first use on. +void listen(Control& c) { + if (!c.listening.load(std::memory_order_acquire) && add_no_bail_step_listener(&on_step)) c.listening.store(true, std::memory_order_release); + if (!c.modifying.exchange(true)) trainer::set_trajectory_modifier(&modify_launch); +} +} // namespace + +bool start(std::uintptr_t image_base) noexcept { + auto& c = control(); + if (c.started.load(std::memory_order_acquire)) return true; + if (!no_bail_available()) { + logging::write(logging::Level::warning, logging::Channel::skater, + "Launches for plugins are unavailable: they follow the skater's physics steps through No Bail's hooks, which did not start."); + return false; + } + c.base.store(image_base, std::memory_order_release); + c.started.store(true, std::memory_order_release); + return true; +} +bool available() noexcept { return control().started.load(std::memory_order_acquire); } + +const char* name(From from) noexcept { + switch (from) { + case From::grind: return "grind"; + case From::ollie: return "ollie"; + case From::no_comply: return "no_comply"; + case From::boneless: return "boneless"; + default: return "other"; + } +} +std::optional from_name(std::string_view name) noexcept { + for (const auto from : {From::grind, From::ollie, From::no_comply, From::boneless}) + if (name == launches::name(from)) return from; + return std::nullopt; +} + +std::string shape(std::string_view owner, From from, std::optional s) { + auto& c = control(); + if (!c.started.load(std::memory_order_acquire)) return "launches are unavailable for this game build"; + std::lock_guard lock(c.mutex); + const auto kind = static_cast(from); + if (!c.shaper[kind].empty() && c.shaper[kind] != owner) return c.shaper[kind] + " shapes " + name(from) + " launches"; + c.shaper[kind] = s ? std::string(owner) : std::string{}; + c.shapes[kind] = s; + listen(c); + return {}; +} +std::vector take() { + auto& c = control(); + if (!c.started.load(std::memory_order_acquire)) return {}; + std::lock_guard lock(c.mutex); + listen(c); + return std::exchange(c.launches, {}); +} +void release(std::string_view owner) { + auto& c = control(); + std::lock_guard lock(c.mutex); + for (std::size_t kind = 0; kind < kinds; ++kind) + if (c.shaper[kind] == owner) { + c.shaper[kind].clear(); + c.shapes[kind].reset(); + } +} +std::optional facing() noexcept { + auto& c = control(); + c.facing_asked.store(true, std::memory_order_relaxed); + if (!c.started.load(std::memory_order_acquire) || seconds(qpc_now() - c.axes_qpc.load(std::memory_order_acquire)) > axes_fresh_seconds) + return std::nullopt; + return Facing{{c.right_x.load(std::memory_order_relaxed), c.right_y.load(std::memory_order_relaxed), c.right_z.load(std::memory_order_relaxed)}, + c.switch_stance.load(std::memory_order_relaxed)}; +} +bool grinding() noexcept { + auto& c = control(); + if (!c.started.load(std::memory_order_acquire)) return false; + if (!c.listening.load(std::memory_order_acquire)) { + std::lock_guard lock(c.mutex); + listen(c); + } + return grinding_now(c, qpc_now()); +} + +void tick(std::uintptr_t client) noexcept { + auto& c = control(); + if (!c.started.load(std::memory_order_acquire)) return; + const auto base = c.base.load(std::memory_order_acquire); + if (c.modifying.load(std::memory_order_acquire) && !c.hooked.load(std::memory_order_acquire) && trainer::start_trick_heights(base)) + c.hooked.store(true, std::memory_order_release); + // The skater's facing (rows: right, up, forward, position), once it is asked for. + if (!c.facing_asked.load(std::memory_order_relaxed)) return; + const auto now = GetTickCount64(); + if (now < c.next_axes_try) return; + try { + overlay::DebugModel skater; + const auto matrix = client_source::detail::debug_skater(base, client, skater); + if (skater.skater_position_valid && std::isfinite(matrix[0]) && std::isfinite(matrix[1]) && std::isfinite(matrix[2])) { + c.right_x.store(matrix[0], std::memory_order_relaxed); + c.right_y.store(matrix[1], std::memory_order_relaxed); + c.right_z.store(matrix[2], std::memory_order_relaxed); + c.axes_qpc.store(qpc_now(), std::memory_order_release); + } + } catch (...) { + c.next_axes_try = now + 250; // no skater: menus, loading, a respawn + } +} +} diff --git a/Extension/Skater/launches.h b/Extension/Skater/launches.h new file mode 100644 index 00000000..dad9e623 --- /dev/null +++ b/Extension/Skater/launches.h @@ -0,0 +1,63 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +// The local skater's launches into the air, for plugins: each launch as the game made it (the +// `launch` event), and a launch's speed across, along or up from the direction of travel set by a +// plugin (reskate.skater.shape_launch). What a plugin makes of them (the grind-exits mod picks the +// side of a pop out of a grind with LT / RT) is the plugin's; this is what has to happen in the +// game's own threads. +// +// Every launch calls the game's jump-trajectory setter with the launch velocity; the trainer hooks +// that call (Extension/Trainer/trainer_jump.cpp) and hands each one here first. Its call site and +// the physics state before it say what it launched out of (Engine/Game/Build/20260929/launches.h). +// The direction of travel is the board's horizontal velocity over the last few physics steps. +// +// The skater's facing (its root's right axis, sampled on the client tick) and whether it rides +// switch (the game's stance flag) are read here too, for reskate.skater.get. +namespace dingosdk::launches { +// Requires the validated build and No Bail started (its physics-step listeners). +bool start(std::uintptr_t image_base) noexcept; +bool available() noexcept; + +enum class From : std::uint8_t { grind, ollie, no_comply, boneless, other }; +const char* name(From from) noexcept; +std::optional from_name(std::string_view name) noexcept; + +// m/s, each one set if given: across the direction of travel (+ its left), along it, and up. +struct Shape { + std::optional across, along, up; +}; +// Client thread: the launches out of `from` from now on (empty: the game's own). One plugin shapes +// each kind at a time: empty when taken, else why not. +std::string shape(std::string_view owner, From from, std::optional shape); + +struct Launch { + From from{From::other}; + std::array game{}, velocity{}; // as the game made it, as it left + std::array travel{}; // the horizontal direction of travel (x, z), unit; 0 without one + float travel_speed{}; // m/s + std::string reason; // "applied", "none" (no shape), "slow" (too slow to tell the travel), "not_ours" + std::string by; // the plugin whose shape applied +}; +// Client thread: the launches since the last call. Launches are watched from the first call on. +std::vector take(); + +struct Facing { + std::array right{}; // the skater root's right axis + bool switch_stance{}; +}; +// Client thread: the facing, when it was sampled in the last quarter of a second. +std::optional facing() noexcept; +// The skater is grinding now (as the physics steps last said). Watching starts with the first call. +bool grinding() noexcept; +// Client thread, every tick, with the local client: the launch hook made ready while used, the +// skater's facing sampled. +void tick(std::uintptr_t client) noexcept; +// Whatever the plugin shaped. +void release(std::string_view owner); +} diff --git a/Extension/Skater/no_bail.h b/Extension/Skater/no_bail.h index 8b39f416..f59a1f9a 100644 --- a/Extension/Skater/no_bail.h +++ b/Extension/Skater/no_bail.h @@ -197,7 +197,7 @@ struct DarkPopLast { unsigned freeze_writes{}, freeze_held{}, freeze_lost{}; }; DarkPopLast dark_pop_last() noexcept; -// Further listeners on the same steps (skater_events.h, grind_exit.h), independent of the observer +// Further listeners on the same steps (skater_events.h, launches.h), independent of the observer // above so none displaces another. Same thread and timing. Adding one already there, or removing one that is not, does nothing; // false when all slots are taken. inline constexpr std::size_t no_bail_step_listener_slots = 4; diff --git a/Extension/Skater/pad_hide.cpp b/Extension/Skater/pad_hide.cpp new file mode 100644 index 00000000..4d813582 --- /dev/null +++ b/Extension/Skater/pad_hide.cpp @@ -0,0 +1,68 @@ +#include "pad_hide.h" +#include "launches.h" +#include "Extension/UI/Overlay/overlay.h" +#include +#include +#include +#include + +namespace dingosdk::pad_hide { +namespace { +struct Control { + std::mutex mutex; + std::map> hidden; + std::atomic lt{}, rt{}, left_x{}, right_stick{}, l3{}, r3{}; +}; +Control& control() { + static auto* value = new Control; // process lifetime: the game polls its pad until exit + return *value; +} +// Any thread the game polls its pad from. +void filter_pad(unsigned, overlay::GamepadValues& pad) noexcept { + auto& c = control(); + if (c.lt.load(std::memory_order_acquire)) pad.left_trigger = 0; + if (c.rt.load(std::memory_order_acquire)) pad.right_trigger = 0; + if (c.left_x.load(std::memory_order_acquire) && launches::grinding()) pad.left_x = 0; + if (c.right_stick.load(std::memory_order_acquire)) pad.right_x = pad.right_y = 0; + if (c.l3.load(std::memory_order_acquire)) pad.buttons &= static_cast(~0x0040u); // XINPUT_GAMEPAD_LEFT_THUMB + if (c.r3.load(std::memory_order_acquire)) pad.buttons &= static_cast(~0x0080u); // XINPUT_GAMEPAD_RIGHT_THUMB +} +void publish(Control& c) { + Hidden all; + for (const auto& [owner, h] : c.hidden) { + all.left_trigger = all.left_trigger || h.left_trigger; + all.right_trigger = all.right_trigger || h.right_trigger; + all.left_x_grinding = all.left_x_grinding || h.left_x_grinding; + all.right_stick = all.right_stick || h.right_stick; + all.left_stick_click = all.left_stick_click || h.left_stick_click; + all.right_stick_click = all.right_stick_click || h.right_stick_click; + } + c.lt.store(all.left_trigger, std::memory_order_release); + c.rt.store(all.right_trigger, std::memory_order_release); + c.left_x.store(all.left_x_grinding, std::memory_order_release); + c.right_stick.store(all.right_stick, std::memory_order_release); + c.l3.store(all.left_stick_click, std::memory_order_release); + c.r3.store(all.right_stick_click, std::memory_order_release); + DingoSDKOverlaySetGamepadFilter(c.hidden.empty() ? nullptr : &filter_pad); +} +} // namespace + +bool available() noexcept { return launches::available(); } +void hide(std::string_view owner, Hidden hidden) { + if (!available()) return; + if (hidden.left_x_grinding) (void)launches::grinding(); // the physics steps followed from now on + auto& c = control(); + std::lock_guard lock(c.mutex); + if (hidden.any()) c.hidden.insert_or_assign(std::string(owner), hidden); + else if (const auto found = c.hidden.find(owner); found != c.hidden.end()) c.hidden.erase(found); + publish(c); +} +void release(std::string_view owner) { + auto& c = control(); + std::lock_guard lock(c.mutex); + if (const auto found = c.hidden.find(owner); found != c.hidden.end()) { + c.hidden.erase(found); + publish(c); + } +} +} diff --git a/Extension/Skater/pad_hide.h b/Extension/Skater/pad_hide.h new file mode 100644 index 00000000..6591a842 --- /dev/null +++ b/Extension/Skater/pad_hide.h @@ -0,0 +1,20 @@ +#pragma once +#include + +// Pad controls kept from the game, as plugins ask (reskate.input.hide). A hidden trigger reads to +// the game as let go; the left stick's left/right, hidden "while grinding", reads as centred only +// while the skater grinds (launches.h), so it reaches the game again at the pop; the right stick +// reads centred and the stick clicks up, whatever the skater does. ReSkate's own reads, and so the +// plugins' reskate.input.gamepad, still see them all. +namespace dingosdk::pad_hide { +struct Hidden { + bool left_trigger{}, right_trigger{}, left_x_grinding{}; + bool right_stick{}, left_stick_click{}, right_stick_click{}; + bool any() const noexcept { return left_trigger || right_trigger || left_x_grinding || right_stick || left_stick_click || right_stick_click; } +}; +// Requires launches::available(). +bool available() noexcept; +void hide(std::string_view owner, Hidden hidden); +// Whatever the plugin hid. +void release(std::string_view owner); +} diff --git a/Extension/Trainer/trainer_jump.cpp b/Extension/Trainer/trainer_jump.cpp index e7a7b4ee..3cfc837f 100644 --- a/Extension/Trainer/trainer_jump.cpp +++ b/Extension/Trainer/trainer_jump.cpp @@ -1,6 +1,7 @@ #include "trainer_jump.h" #include "Engine/Core/Hooks/hooks.h" #include "Engine/Core/Log/logging.h" +#include "Engine/Game/Build/20260929/launches.h" #include "Engine/Game/Multiplayer/session_tools.h" #include "Extension/Skater/client_source_spawn.h" #include "Extension/Skater/no_bail.h" @@ -20,7 +21,7 @@ namespace { // - a no comply calls it from 0x482f646 on entering the pop (state 200) after a crouch // (state 103) of about 0.20 s; an ollie crouches for 0.08 s and launches elsewhere; // - a boneless calls it from 0x47691f8 while the foot is planted (state 602); -// - a flat-ground ollie calls it from 0x4824f7f (Engine/Game/Build/20260929/grind_exit.h). +// - a flat-ground ollie calls it from 0x4824f7f (Engine/Game/Build/20260929/launches.h). constexpr std::uintptr_t set_trajectory = 0x4752aa0, no_comply_call = 0x482f646, boneless_call = 0x47691f8, ollie_call = 0x4824f7f; constexpr std::array set_trajectory_prefix{0x40, 0x53, 0x48, 0x83, 0xec, 0x20, 0x80, 0xb9, 0x88, 0xbb, 0x00, 0x00, 0x00, 0x48, 0x8b, 0xd9, 0x0f, 0x85, 0xf6, 0x00, 0x00, 0x00, @@ -153,7 +154,7 @@ void set_trajectory_hook(std::uintptr_t trajectory, float *parameters) { bool grind = false; if (parameters && caller == no_comply_call + 5) { const auto watch = watched_physics_state(); - grind = watch.valid && watch.state == pop_state && watch.previous >= 400 && watch.previous < 500; + grind = watch.valid && watch.state == pop_state && game::build::v20260929::launches::is_grind_state(watch.previous); } if (grind) { const float tx = h.travel_x.load(std::memory_order_relaxed), tz = h.travel_z.load(std::memory_order_relaxed); diff --git a/Extension/Trainer/trainer_jump.h b/Extension/Trainer/trainer_jump.h index 6892906b..1d3909e2 100644 --- a/Extension/Trainer/trainer_jump.h +++ b/Extension/Trainer/trainer_jump.h @@ -45,7 +45,7 @@ bool raise_flight(float up, float &was, float &time) noexcept; int take_flight_raise() noexcept; // The scripted launches since the last call, oldest first; false when there are none. bool take_trick_launch(TrickLaunch &out) noexcept; -// Grind exits (Extension/Skater/grind_exit.h): may change the launch velocity +// Plugins' launches (Extension/Skater/launches.h): may change the launch velocity // (x, y, z at parameters + 0x80) of any call of the game's jump-trajectory setter before the game // uses it, given the caller's address relative to the game image (the return address minus 5) and // the trajectory object it fills. Runs before the trainer's own trick multipliers. Physics thread; diff --git a/Extension/UI/Overlay/overlay.h b/Extension/UI/Overlay/overlay.h index 7443afc2..36b9c7e9 100644 --- a/Extension/UI/Overlay/overlay.h +++ b/Extension/UI/Overlay/overlay.h @@ -164,7 +164,7 @@ struct FlightInput { // May change what the GAME reads from an XInput pad (never what ReSkate itself reads). `user` is // the XInput slot. Called on whatever thread the game polls from, once per poll that returns a -// pad. Plugins' hidden pad controls (Extension/Skater/grind_exit.h, reskate.input.hide). +// pad. Plugins' hidden pad controls (Extension/Skater/pad_hide.h, reskate.input.hide). struct GamepadValues { std::uint16_t buttons = 0; std::uint8_t left_trigger = 0, right_trigger = 0; diff --git a/Runtime/runtime_init.cpp b/Runtime/runtime_init.cpp index 8dd6d214..bfa51f62 100644 --- a/Runtime/runtime_init.cpp +++ b/Runtime/runtime_init.cpp @@ -41,7 +41,7 @@ #include "Extension/UI/hud_corner.h" #include "Extension/UI/Overlay/game_images.h" #include "Extension/Plugins/plugin_runtime.h" -#include "Extension/Skater/grind_exit.h" +#include "Extension/Skater/launches.h" #include "Extension/Skater/trick_rewrite.h" #include "Extension/Skater/camera_observer.h" #include "Extension/Progression/entitlement_request_hook.h" @@ -481,8 +481,8 @@ extern "C" __declspec(dllexport) BOOL WINAPI DingoSDKDebugInitialize() { dingosdk::overlay::prepare_game_images(); record("{\"event\":\"skater_physics_initialized\",\"active\":" + std::string(physics ? "true" : "false") + ",\"xray\":" + std::string(xray ? "true" : "false") + "}"); - const bool grind_exits = dingosdk::grind_exit::start(r.base); - record("{\"event\":\"grind_exit_initialized\",\"active\":" + std::string(grind_exits ? "true" : "false") + "}"); + const bool launch_hooks = dingosdk::launches::start(r.base); + record("{\"event\":\"launches_initialized\",\"active\":" + std::string(launch_hooks ? "true" : "false") + "}"); const bool trick_rewrites = dingosdk::trick_rewrite::start(r.base); record("{\"event\":\"trick_rewrite_initialized\",\"active\":" + std::string(trick_rewrites ? "true" : "false") + "}"); const bool noclip_velocity = dingosdk::start_client_noclip_velocity(r.base); diff --git a/STRUCTURE.md b/STRUCTURE.md index b55a431a..80cd452e 100644 --- a/STRUCTURE.md +++ b/STRUCTURE.md @@ -147,7 +147,7 @@ commands and a `Test/` folder. | `ReplayKeyframes/` | replay keyframe curves and settings | | `Scripting/` | Lua startup, custom script loader, native blocking | | `Settings/` | gameplay/native/user settings, input bindings, engine tweaks, replay drafts | -| `Skater/` | first person, noclip, free flight, physics tuning, no-bail, AI skaters, x-ray, breakable deck, grind exits, trick watch | +| `Skater/` | first person, noclip, free flight, physics tuning, no-bail, AI skaters, x-ray, breakable deck, launches and pad hiding for plugins, trick watch | | `Throwdowns/` | native throwdowns, custom (plugin) modes, relay and wire format | | `Trainer/` | trick trainer: waypoints, jumps, landings, presets | | `Travel/` | location travel | diff --git a/cmake/Runtime.cmake b/cmake/Runtime.cmake index d0661f6f..84a8828e 100644 --- a/cmake/Runtime.cmake +++ b/cmake/Runtime.cmake @@ -146,7 +146,8 @@ add_library(dingosdk_runtime SHARED Extension/Skater/xray_render.cpp Extension/Skater/xray_skeleton.cpp Extension/Skater/game_speed.cpp - Extension/Skater/grind_exit.cpp + Extension/Skater/launches.cpp + Extension/Skater/pad_hide.cpp Extension/Skater/trick_rewrite.cpp Extension/Skater/arm_reach.cpp Extension/UI/hud_corner.cpp