mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-23 15:16:02 +02:00
PR 343's Filmer becomes a Luau plugin mod (kept in contrib/plugins/filmer) on general APIs instead of
its own C++:
- reskate.skater.carry: something held out in one or both hands and aimed, the body crouched (feet
kept) and hunched over it, turned on the spot after it (Extension/Skater/carry.cpp, from the PR's
filmer_hold.cpp, with tests)
- shown = "everyone" | "others" | "me" on skater.pose, reach and carry: the pose sent to the other
players and the one drawn here are split when asked
- reskate.camera.cycle and the camera_cycle event: a plugin's camera as the last step of the camera
height button's cycle, after first person
- reskate.session.nametags(false) holds the names over the other players off
- board.hide(bone, {others = true}) hides it in the board the others are sent too (a sent-pose filter)
- outfit.set takes hand wear slots; input.bind takes an action with no key of its own
125 lines
5.8 KiB
C++
125 lines
5.8 KiB
C++
#pragma once
|
|
#include "native_skater.h"
|
|
#include "Engine/Core/Platform/memory.h"
|
|
#include "Engine/Game/Build/addresses.h"
|
|
#include "Engine/Game/Build/20260929/native_skater.h"
|
|
#include "Engine/Game/Build/20260929/skater_entities.h"
|
|
#include <Windows.h>
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <format>
|
|
#include <mutex>
|
|
#include <optional>
|
|
#include <source_location>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
// Remote actor state shared by native_skater.cpp and native_skater_spawn.cpp.
|
|
namespace dingosdk::multiplayer::native_skater_detail {
|
|
namespace entities = addr::skater_entities;
|
|
namespace native = addr::native_skater;
|
|
struct Unavailable : std::runtime_error {
|
|
using std::runtime_error::runtime_error;
|
|
};
|
|
void require(bool value, const char *reason);
|
|
bool readable(std::uintptr_t address, void *out, std::size_t size);
|
|
template <class T>
|
|
T read(std::uintptr_t object, std::uintptr_t offset = 0,
|
|
const std::source_location &location = std::source_location::current()) {
|
|
T value{};
|
|
if (object < 0x10000 || object > memory::highest_user_address ||
|
|
offset > memory::highest_user_address - object ||
|
|
!readable(object + offset, &value, sizeof(value)))
|
|
throw Unavailable(
|
|
std::format("Native skater read failed: object={:#x}, offset={:#x}, bytes={}, line={}.", object,
|
|
offset, sizeof(value), location.line()));
|
|
return value;
|
|
}
|
|
std::uintptr_t ptr(std::uintptr_t object, std::uintptr_t offset = 0,
|
|
const std::source_location &location = std::source_location::current());
|
|
bool write(std::uintptr_t address, const void *data, std::size_t size);
|
|
struct alignas(16) NativeBone {
|
|
std::array<float, 4> scale, rotation, position;
|
|
};
|
|
static_assert(sizeof(NativeBone) == 0x30);
|
|
using AnimationUpdate = void (*)(std::uintptr_t, std::uintptr_t);
|
|
using DestroyEntity = void (*)(std::uintptr_t, std::uintptr_t);
|
|
using RenderPose = bool (*)(std::uintptr_t, std::uintptr_t);
|
|
struct HookState {
|
|
std::uintptr_t base{};
|
|
DWORD engine_thread{};
|
|
bool hooks{}, install_attempted{};
|
|
AnimationUpdate original_animation{};
|
|
DestroyEntity original_destroy{};
|
|
RenderPose original_render_pose{};
|
|
std::atomic<EntityDestroyed> destroyed_listener{};
|
|
std::atomic<AnimationEvaluated> evaluated_listener{};
|
|
std::atomic<LocalPoseFilter> pose_filter{};
|
|
std::atomic<LocalSentPoseFilter> sent_pose_filter{};
|
|
// A pose override of the local skater (Extension/Skater/skater_animation.cpp), after the one above.
|
|
std::atomic<AnimationEvaluated> override_listener{};
|
|
std::atomic<RenderPosePublished> override_render_listener{};
|
|
std::atomic<BoardPosePublished> override_board_listener{};
|
|
std::atomic<RenderPosePublished> render_listener{};
|
|
};
|
|
// Keys the per-entity hooks match, one contiguous array per kind in slot order.
|
|
// Every native skater, board and entity callback looks its key up here: a few
|
|
// cache lines up to the highest slot ever watched, not one Remote per slot.
|
|
using WatchedArray = std::array<std::atomic<std::uintptr_t>, max_remote_players>;
|
|
struct WatchedKeys {
|
|
WatchedArray component{}, entity{}, board{}, board_holder{};
|
|
// One past the highest slot that has stored a key. Only grows, and grows
|
|
// before the key is stored, so a lookup never stops short of a watched key.
|
|
std::atomic<std::size_t> bound{};
|
|
};
|
|
WatchedKeys &watched();
|
|
// Stores key for the current peer slot (0 stops watching).
|
|
void watch(WatchedArray &keys, std::uintptr_t key) noexcept;
|
|
// The slot whose key equals key, or max_remote_players.
|
|
std::size_t watching(const WatchedArray &keys, std::uintptr_t key) noexcept;
|
|
struct Remote {
|
|
std::uintptr_t entity{}, component{}, parent{}, context{}, local_entity{};
|
|
std::uintptr_t board_entity{}, board_component{}, board_parent{};
|
|
bool failed{};
|
|
std::mutex mutex;
|
|
Pose target;
|
|
// Bumped whenever target is replaced. The skeleton buffer last written and the
|
|
// target revision it holds let an animation callback that did not re-evaluate
|
|
// the native graph skip writing the same pose again.
|
|
std::uint64_t target_revision{}, written_revision{};
|
|
std::uintptr_t written_buffer{};
|
|
// Bumped whenever this slot's actor is created, removed or destroyed, so state
|
|
// cached for one actor (its sound binding) is never used for the next.
|
|
std::atomic<std::uint64_t> generation{};
|
|
// Frozen with entity placement, independent of newer network snapshots.
|
|
std::vector<Transform> board_render_pose;
|
|
std::atomic<bool> board_ready{};
|
|
std::atomic<std::uint64_t> applied{};
|
|
std::atomic<std::uint64_t> board_applied{};
|
|
std::atomic<bool> pose_driven{};
|
|
std::atomic<std::uint64_t> native_evaluated{}, native_skipped{}, native_evaluation_us{}, apply_us{};
|
|
ULONGLONG next_native_animation{}, next_safety_audit{}, next_entity_audit{},
|
|
next_board_safety_audit{}, next_board_status{}, next_status{};
|
|
// A skateboard that went away is not created again before this: one creation is a heavy
|
|
// native pass, and a player's board may come and go with every pose.
|
|
ULONGLONG next_board_create{};
|
|
bool board_failed{};
|
|
std::string board_issue;
|
|
std::string board_status;
|
|
std::string issue;
|
|
std::optional<CosmeticRecipe> skater_appearance, board_appearance;
|
|
// The last appearance the native copy or catalog check rejected, retried at
|
|
// next_appearance_retry instead of on every cosmetics pass. Client thread.
|
|
std::optional<Appearance> rejected_appearance;
|
|
ULONGLONG next_appearance_retry{};
|
|
};
|
|
HookState &shared();
|
|
Remote &remote();
|
|
void install(std::uintptr_t base);
|
|
void place_actor(std::uintptr_t base, std::uintptr_t entity, const Transform &root);
|
|
void place_board(std::uintptr_t base, std::uintptr_t entity, const Transform &root);
|
|
} // namespace dingosdk::multiplayer::native_skater_detail
|