mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-23 19:56:00 +02:00
Pull request 326 split into general plugin APIs and contrib/plugins/fit-check: - reskate.capture (start, stop, discard, state, compare, kept, save): the screen recorded before the overlay draws, cut out by the game's depth or by colour, saved as GIF, APNG, PNG or WebM with an optional card; the "capture" permission. - reskate.room.turn: a stand-in pad's stick held over to turn the menu's preview. - reskate.menu.game and reskate.menu.grid: what the game's menu and its item grid show. - reskate.outfit.worn, board and title: what the skater wears, by name. - reskate.files.reveal: a data folder opened in Explorer. The plugin finds where the turn comes round, eases the stick off, picks evenly spaced frames and saves them with the board's parts or the outfit on the card.
83 lines
2.9 KiB
C++
83 lines
2.9 KiB
C++
#include "scripted_pad.h"
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <mutex>
|
|
|
|
namespace dingosdk::overlay {
|
|
namespace {
|
|
struct State {
|
|
std::mutex mutex;
|
|
std::vector<PadStep> steps;
|
|
std::size_t current{};
|
|
std::uint32_t ticks{};
|
|
std::chrono::steady_clock::time_point last_tick, done_at;
|
|
bool active{};
|
|
DWORD packet{};
|
|
// A stick held (hold_stick).
|
|
int stick{};
|
|
SHORT value{};
|
|
std::chrono::steady_clock::time_point held_until{};
|
|
};
|
|
State &state() { static auto *value = new State; return *value; }
|
|
constexpr std::chrono::milliseconds linger{2000}, no_ticks{250}, tick_length{17};
|
|
// One game tick of the script. The caller holds s.mutex.
|
|
void advance(State &s, std::chrono::steady_clock::time_point now) {
|
|
++s.ticks;
|
|
s.last_tick = now;
|
|
while (s.current + 1 < s.steps.size() && s.steps[s.current + 1].tick <= s.ticks) {
|
|
++s.current;
|
|
if (s.current + 1 == s.steps.size()) s.done_at = now;
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
void play_pad(std::vector<PadStep> steps) {
|
|
auto &s = state();
|
|
std::lock_guard lock(s.mutex);
|
|
s.steps = std::move(steps);
|
|
s.current = 0;
|
|
s.ticks = 0;
|
|
s.last_tick = s.done_at = std::chrono::steady_clock::now();
|
|
s.active = !s.steps.empty();
|
|
}
|
|
void pad_tick() noexcept {
|
|
auto &s = state();
|
|
std::lock_guard lock(s.mutex);
|
|
if (s.active) advance(s, std::chrono::steady_clock::now());
|
|
}
|
|
void hold_stick(int stick, float value, unsigned milliseconds) noexcept {
|
|
auto &s = state();
|
|
std::lock_guard lock(s.mutex);
|
|
s.stick = stick < 0 || stick > 3 ? 2 : stick;
|
|
s.value = static_cast<SHORT>(std::lround(std::clamp(value, -1.0f, 1.0f) * 32767.0f));
|
|
s.held_until = value == 0 ? std::chrono::steady_clock::time_point{}
|
|
: std::chrono::steady_clock::now() + std::chrono::milliseconds(milliseconds);
|
|
}
|
|
bool scripted_pad(DWORD user, XINPUT_STATE &output) noexcept {
|
|
auto &s = state();
|
|
if (user != 0) return false;
|
|
std::lock_guard lock(s.mutex);
|
|
const auto now = std::chrono::steady_clock::now();
|
|
if (!s.active) {
|
|
if (now >= s.held_until) return false;
|
|
output.Gamepad = XINPUT_GAMEPAD{};
|
|
SHORT* axes[] = {&output.Gamepad.sThumbLX, &output.Gamepad.sThumbLY, &output.Gamepad.sThumbRX, &output.Gamepad.sThumbRY};
|
|
*axes[s.stick] = s.value;
|
|
output.dwPacketNumber = ++s.packet;
|
|
return true;
|
|
}
|
|
// With no game ticks, time alone moves the script on.
|
|
while (s.current + 1 < s.steps.size() && now - s.last_tick > no_ticks) advance(s, s.last_tick + tick_length);
|
|
const bool done = s.current + 1 == s.steps.size();
|
|
if (done && now - s.done_at > linger) {
|
|
s.active = false;
|
|
return false;
|
|
}
|
|
output.Gamepad = done ? XINPUT_GAMEPAD{} : s.steps[s.current].pad;
|
|
output.dwPacketNumber = ++s.packet;
|
|
return true;
|
|
}
|
|
} // namespace dingosdk::overlay
|