Plugins: trick events (trick_started, trick_landed, line_broken) and reskate.score.current

- With events.tricks a plugin hears the local skater's tricks: trick_started (where), trick_landed
  (points, multiplier, where it started and landed, how long it took) and line_broken (a slam or
  a wipeout); reskate.score.current() is the score HUD's line, multiplier and whether a trick is
  going on. Without the permission the events cannot be registered.
- The score HUD is read into the trick watch (Extension/Skater/score_hud.h, trick_watch.h) on the
  client tick only while a running plugin has events.tricks; a new level starts a new line.
  plugins::start takes the image base for it.
- reskate-test has a tricks module (skipped until a trick lands) and ptest trick, which waits 30 s
  for one and prints what its events said; reskate-hostile has trick-loop.

Not yet checked against real tricks in game.
This commit is contained in:
AyeZeeBB committed 2026-10-19 12:43:44 -05:00
1 parent 373c141381
commit f37abef426
13 files changed
+333 -15

No files matched your search

+35 -3
View File
@@ -13,7 +13,14 @@
// luaL_error, or a C++ exception, which Luau turns into a Lua error with its message.
namespace dingosdk::plugins {
namespace {
constexpr std::array<std::string_view, 5> events{"load", "unload", "tick", "level_loaded", "level_unloading"};
// Events and the permission each needs (none for the base set).
struct Event {
std::string_view name, permission;
};
constexpr std::array<Event, 8> events{{
{"load", {}}, {"unload", {}}, {"tick", {}}, {"level_loaded", {}}, {"level_unloading", {}},
{"trick_started", "events.tricks"}, {"trick_landed", "events.tricks"}, {"line_broken", "events.tricks"},
}};
constexpr std::size_t maximum_key = 128;
constexpr std::size_t maximum_command = 48;
constexpr std::size_t maximum_listing = 1000;
@@ -197,8 +204,10 @@ struct ApiFunctions {
auto& plugin = plugin_of(L);
const std::string event = luaL_checkstring(L, 1);
luaL_checktype(L, 2, LUA_TFUNCTION);
if (std::find(events.begin(), events.end(), event) == events.end())
luaL_error(L, "there is no event \"%s\" in this ReSkate", event.c_str());
const auto known = std::find_if(events.begin(), events.end(), [&](const Event& e) { return e.name == event; });
if (known == events.end()) luaL_error(L, "there is no event \"%s\" in this ReSkate", event.c_str());
if (!known->permission.empty() && !plugin.has(known->permission))
luaL_error(L, "the %s event needs the \"%s\" permission in reskate-plugin.json", event.c_str(), std::string(known->permission).c_str());
auto& list = plugin.handlers_[event];
if (list.size() >= plugin.limits_.handlers) luaL_error(L, "more than %d handlers for one event", static_cast<int>(plugin.limits_.handlers));
lua_pushvalue(L, 2);
@@ -493,6 +502,24 @@ struct ApiFunctions {
else lua_pushnil(L);
return 1;
}
// events.tricks: the score HUD now (the line's score, its multiplier, a trick going on), or nil.
static int score(lua_State* L) {
auto& plugin = plugin_of(L);
need(L, plugin, "events.tricks", "reskate.score.current");
const auto now = plugin.services_.score();
if (!now) {
lua_pushnil(L);
return 1;
}
lua_createtable(L, 0, 3);
lua_pushnumber(L, now->line);
lua_setfield(L, -2, "line");
lua_pushnumber(L, now->multiplier);
lua_setfield(L, -2, "multiplier");
lua_pushboolean(L, now->in_trick);
lua_setfield(L, -2, "in_trick");
return 1;
}
// Modules come from the plugin's own package, beside its entry: require("./rules") or
// require("rules") is <entry folder>/rules.luau or rules.lua. Each runs once.
static int require(lua_State* L) {
@@ -620,6 +647,11 @@ void Api::install(Plugin& plugin, lua_State* L) {
readonly();
lua_setfield(L, -2, "skater");
lua_createtable(L, 0, 1);
set_function(L, "current", ApiFunctions::score);
readonly();
lua_setfield(L, -2, "score");
lua_createtable(L, 0, 1);
set_function(L, "raycast", ApiFunctions::raycast);
readonly();