mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-24 20:36:19 +02:00
Hall of Meat (contrib/plugins/hall-of-meat) and grind exit triggers (contrib/plugins/grind-exits) are Lua plugins: the bail model, its Meat card, each map's best, the trigger rules, settings, console commands and menu panels are theirs, with their own checks that dingosdk_plugin_tests runs. What has to run in the game stays in core as plugin services: - every physics step of the skater (skater_physics, the physics_step event, permission events.physics); - the skater's x-ray (xray, xray_render, xray_skeleton; reskate.skater.xray and the draw handler's d:skeleton); - skate.'s bottom left HUD corner (hud_corner; reskate.hud.corner); - the game's slow motion, never in a session (game_speed; reskate.time.slow and speed, permission game.speed); - the side of the next pop out of a grind and pad controls kept from the game (grind_exit; reskate.skater.grind_exit, the grind_exit event, reskate.input.hide); - skate.'s own pictures by name (game_images; d:game_image), d:gradient and a font for drawn text. Removed: Extension/HallOfMeat, grind_exit_control and its commands, the HALL OF MEAT and GRIND EXITS add-on pages, home tile, hub and native menu rows, their menu models, and the hallofmeat and grindexit built-in commands.
178 lines
8.3 KiB
C++
178 lines
8.3 KiB
C++
#include "skater_physics.h"
|
|
#include "Engine/Core/Log/logging.h"
|
|
#include "Engine/Core/Platform/memory.h"
|
|
#include "Engine/Game/Build/addresses.h"
|
|
#include "Engine/Game/Build/20260929/no_bail.h"
|
|
#include "Engine/Game/Build/20260929/skater_body.h"
|
|
#include "Engine/Game/Build/20260929/skater_state.h"
|
|
#include <Windows.h>
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
|
|
namespace dingosdk::skater_physics {
|
|
namespace {
|
|
namespace body = addr::skater_body;
|
|
namespace state = addr::skater_state;
|
|
using game::Vec3;
|
|
static_assert(body::body_bone_count == skater_body::count);
|
|
|
|
std::atomic<bool>& ready() {
|
|
static std::atomic<bool> value{};
|
|
return value;
|
|
}
|
|
|
|
// What each body touched in the step: the contact struct the rig's contact processing fills.
|
|
using Records = std::array<unsigned char, body::body_bone_count * body::bone_record_size>;
|
|
template <class T> T field(const Records& records, std::size_t index, std::uintptr_t offset) noexcept {
|
|
T value{};
|
|
std::memcpy(&value, records.data() + index * body::bone_record_size + offset, sizeof(T));
|
|
return value;
|
|
}
|
|
Vec3 vec3(const std::array<float, 4>& value) noexcept {
|
|
for (std::size_t i = 0; i < 3; ++i)
|
|
if (!std::isfinite(value[i])) return {};
|
|
return {value[0], value[1], value[2]};
|
|
}
|
|
float speed(float value) noexcept { return std::isfinite(value) && value > 0 && value < 1000 ? value : 0; }
|
|
bool read_contacts(const NoBailSkater& skater, skater_body::Contacts& result) noexcept {
|
|
std::uintptr_t holder{}, contacts{};
|
|
Records records{};
|
|
std::array<std::uint8_t, skater_body::count> touching{};
|
|
if (!memory::peek(skater.rig + body::contact_holder_offset, holder) || !memory::peek(holder + body::contact_struct_offset, contacts) ||
|
|
!memory::peek(contacts + body::bone_records_offset, records) || !memory::peek(contacts + body::bone_touching_offset, touching))
|
|
return false;
|
|
for (std::size_t index = 0; index < skater_body::count; ++index) {
|
|
auto& b = result.bodies[index];
|
|
b.touching = touching[index] != 0;
|
|
const float ordinary = speed(field<float>(records, index, body::bone_peak_offset));
|
|
const float tracked = speed(field<float>(records, index, body::bone_tracked_peak_offset));
|
|
b.impact = std::max(ordinary, tracked);
|
|
// The slide of the contact that set the peak: one near a tracked point keeps its own.
|
|
const std::uintptr_t side = tracked > ordinary ? body::bone_tracked_offset : 0;
|
|
b.slide = vec3(field<std::array<float, 4>>(records, index, body::bone_slide_offset + side));
|
|
const auto hit = [&](std::uintptr_t offset) { return field<std::uint8_t>(records, index, offset) != 0; };
|
|
b.hit = {hit(body::bone_hit_board_offset), hit(body::bone_hit_vehicle_offset), hit(body::bone_hit_world_offset),
|
|
hit(body::bone_hit_kind_5_offset), hit(body::bone_hit_kind_11_offset)};
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// The offboard state's flags: the body in a ragdoll, and in the air.
|
|
struct Offboard {
|
|
bool ragdoll{}, in_the_air{};
|
|
};
|
|
bool read_offboard(const NoBailSkater& skater, Offboard& flags) noexcept {
|
|
std::uintptr_t trick_state{}, offboard{};
|
|
std::uint8_t ragdoll{}, in_the_air{};
|
|
if (!memory::peek(skater.core + state::trick_state_offset, trick_state) ||
|
|
!memory::peek(trick_state + state::offboard_state_offset, offboard) ||
|
|
!memory::peek(offboard + state::ragdoll_offset, ragdoll) || !memory::peek(offboard + state::in_the_air_offset, in_the_air))
|
|
return false;
|
|
flags = {ragdoll != 0, in_the_air != 0};
|
|
return true;
|
|
}
|
|
|
|
bool finite(const Vec3& v) noexcept {
|
|
return std::all_of(v.begin(), v.end(), [](float value) { return std::isfinite(value) && std::abs(value) < 1000; });
|
|
}
|
|
// Body `index` of the board's or the skeleton's physics, when the layout holds: its velocity, and
|
|
// its spin when asked for.
|
|
bool body_motion(std::uintptr_t physics, std::uintptr_t vtable, std::uint32_t count, std::size_t index, Vec3& velocity,
|
|
Vec3* spin = nullptr) noexcept {
|
|
std::uintptr_t type{}, bodies{}, owner{};
|
|
std::uint32_t found{};
|
|
const auto at = [&] { return bodies + index * state::physics_body_size; };
|
|
if (!memory::peek(physics, type) || type != vtable || !memory::peek(physics + state::physics_bodies_offset, bodies) ||
|
|
!memory::peek(bodies, found) || found != count || index >= count ||
|
|
!memory::peek(at() + state::body_owner_offset, owner) || owner != physics ||
|
|
!memory::peek(at() + state::body_velocity_offset, velocity) || (spin && !memory::peek(at() + state::body_spin_offset, *spin)))
|
|
return false;
|
|
return finite(velocity) && (!spin || finite(*spin));
|
|
}
|
|
// The board's root, and the pelvis with its spin.
|
|
bool read_motion(const NoBailSkater& skater, Vec3& board, Vec3& pelvis, Vec3& spin) noexcept {
|
|
std::uintptr_t holder{}, board_physics{}, rig_physics{};
|
|
return memory::peek(skater.core + state::board_holder_offset, holder) &&
|
|
memory::peek(holder + state::board_physics_offset, board_physics) &&
|
|
memory::peek(skater.rig + state::rig_physics_offset, rig_physics) &&
|
|
body_motion(board_physics, skater.base + state::board_physics_vtable, state::board_body_count, state::board_root_body, board) &&
|
|
body_motion(rig_physics, skater.base + state::rig_physics_vtable, state::rig_body_count,
|
|
skater_body::index(skater_body::Bone::hips), pelvis, &spin);
|
|
}
|
|
|
|
// Where the local skater's physics step length is kept now (a float, seconds of game time), 0 while
|
|
// there is no local skater.
|
|
std::uintptr_t step_length_address() noexcept {
|
|
NoBailSkater skater;
|
|
std::uintptr_t input{};
|
|
if (!no_bail_skater(skater) || !memory::peek(skater.core + body::core_step_input_offset, input) || !input) return 0;
|
|
return input + body::step_input_length_offset;
|
|
}
|
|
}
|
|
|
|
bool start(std::uintptr_t base) noexcept {
|
|
if (ready().load(std::memory_order_acquire)) return true;
|
|
for (const auto& contract : body::contracts) {
|
|
std::array<unsigned char, 32> actual{};
|
|
if (!memory::peek(base + contract.rva, actual) || actual != contract.bytes) {
|
|
logging::log(logging::Level::warning, logging::Channel::skater,
|
|
"Plugins cannot read the skater's body: the native contract at 0x{:x} did not match.", contract.rva);
|
|
return false;
|
|
}
|
|
}
|
|
if (!no_bail_available()) {
|
|
logging::write(logging::Level::warning, logging::Channel::skater,
|
|
"The skater's physics steps are unavailable: they come through No Bail's hooks, which did not start.");
|
|
return false;
|
|
}
|
|
ready().store(true, std::memory_order_release);
|
|
return true;
|
|
}
|
|
|
|
bool available() noexcept { return ready().load(std::memory_order_acquire); }
|
|
|
|
Step read_step(const NoBailSkater& skater, float seconds, bool wipeout) noexcept {
|
|
Step step;
|
|
step.seconds = seconds;
|
|
step.wipeout = wipeout;
|
|
std::uint32_t physics_state{};
|
|
if (memory::peek(skater.context + state::physics_state_offset, physics_state)) {
|
|
// On the board by the physics state; off it (on foot, and through a whole bail) by the offboard state.
|
|
const bool offboard = physics_state == addr::no_bail::offboard_physics_state;
|
|
Offboard flags;
|
|
const bool known = offboard && read_offboard(skater, flags);
|
|
step.airborne = std::find(state::board_air_states.begin(), state::board_air_states.end(), physics_state) !=
|
|
state::board_air_states.end() || (known && flags.in_the_air);
|
|
if (!offboard || known) step.ragdoll = known && flags.ragdoll;
|
|
Vec3 board{}, pelvis{}, spin{};
|
|
if (read_motion(skater, board, pelvis, spin)) {
|
|
step.velocity = offboard ? pelvis : board;
|
|
step.spin = game::length(spin);
|
|
}
|
|
}
|
|
(void)read_contacts(skater, step.body);
|
|
return step;
|
|
}
|
|
|
|
float step_length() noexcept {
|
|
const auto address = step_length_address();
|
|
float seconds{};
|
|
return address && memory::peek(address, seconds) ? seconds : 0.0f;
|
|
}
|
|
|
|
bool set_step_length(float seconds) noexcept {
|
|
const auto address = step_length_address();
|
|
float current{};
|
|
if (!address || !memory::peek(address, current)) return false;
|
|
if (current == seconds) return true;
|
|
__try {
|
|
*reinterpret_cast<volatile float*>(address) = seconds; // read by the physics thread each step
|
|
return true;
|
|
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|