Plugins: Enums (LogLevel, Align, GrindSide, Stance, Pop, Space, etc.)

This commit is contained in:
vtf committed 2026-10-25 22:34:48 -04:00
1 parent e134c089e6
commit b1e61caa75
6 files changed
+60 -31

No files matched your search

+7 -2
View File
@@ -1235,10 +1235,15 @@ void plugins_server_copies_are_the_authority() {
player.session_now = {true, false, {{2, "Bob", true, false}, {1, "Ann", false, true}}};
const std::string main = R"(
reskate.log("side " .. reskate.side)
if reskate.side == "server" then
local E = reskate.Enums
assert(E.Side.server == "server" and E.Event.tick == "tick" and E.Key.Enter == "Enter" and E.LogLevel.warn == "warn"
and E.Target.all == "all" and E.EntityStatus.ready == "ready" and E.Throwdown.spot_battle == "spot_battle")
assert(not pcall(function() E.Pop.huge = "huge" end), "an enum is read-only")
reskate.log("enum check", E.LogLevel.warn)
if reskate.side == E.Side.server then
reskate.on("message", function(from, text)
reskate.log("server heard " .. text .. " from " .. from.name)
assert(reskate.net.send("all", "state " .. text))
assert(reskate.net.send(reskate.Enums.Target.all, "state " .. text))
assert(reskate.server.kick(from, "testing"))
assert(reskate.server.say("round over"))
assert(not pcall(reskate.net.send, "server", "x"), "it is the server")
+20 -16
View File
@@ -230,7 +230,7 @@ struct ApiFunctions {
auto& plugin = plugin_of(L);
const auto message = text_of(L, 1);
const std::string level = luaL_optstring(L, 2, "info");
if (level != "info" && level != "warn" && level != "error") luaL_argerror(L, 2, "the level must be \"info\", \"warn\" or \"error\"");
if (!one_of(log_levels, level)) luaL_argerror(L, 2, "the level must be \"info\", \"warn\" or \"error\"");
plugin.log(level == "error" ? LogLevel::error : level == "warn" ? LogLevel::warn : LogLevel::info, message);
return 0;
}
@@ -675,21 +675,25 @@ void Api::install(Plugin& plugin, lua_State* L) {
lua_setfield(L, -2, "side");
// Named constants, read-only and built from the C++ lists so they cannot drift:
// reskate.Enums.Side.server is "server", reskate.Enums.Event.tick is "tick". Plain strings still work.
lua_createtable(L, 0, 2);
lua_createtable(L, 0, 3);
for (const auto side : {Side::client, Side::server, Side::both}) {
lua_pushstring(L, side_name(side));
lua_setfield(L, -2, side_name(side));
}
readonly();
lua_setfield(L, -2, "Side");
lua_createtable(L, 0, static_cast<int>(events.size()));
for (const auto& event : events) {
lua_pushlstring(L, event.name.data(), event.name.size());
lua_setfield(L, -2, std::string(event.name).c_str());
}
readonly();
lua_setfield(L, -2, "Event");
const auto push_enum = [&](const char* name, const auto& values, auto word) {
lua_createtable(L, 0, static_cast<int>(std::size(values)));
for (const auto& value : values) {
const std::string_view w = word(value);
lua_pushlstring(L, w.data(), w.size());
lua_setfield(L, -2, std::string(w).c_str());
}
readonly();
lua_setfield(L, -2, name);
};
const auto same = [](std::string_view w) { return w; };
lua_createtable(L, 0, 13);
push_enum("Side", std::array{Side::client, Side::server, Side::both}, [](Side side) { return std::string_view(side_name(side)); });
push_enum("Event", events, [](const Event& event) { return event.name; });
push_enum("Key", key_names, [](const auto& key) { return key.first; });
for (const auto& [name, words] : {std::pair<const char*, Words>{"LogLevel", log_levels}, {"Align", aligns}, {"GrindSide", grind_sides},
{"Stance", stances}, {"Pop", pops}, {"Space", spaces}, {"Throwdown", throwdown_modes},
{"Card", throwdown_cards}, {"EntityStatus", entity_statuses}, {"Target", send_targets}})
push_enum(name, words, same);
readonly();
lua_setfield(L, -2, "Enums");
using F = ApiFunctions;
+25
View File
@@ -1,7 +1,11 @@
#pragma once
#include "plugin_vm.h"
#include "lua.h"
#include <algorithm>
#include <initializer_list>
#include <span>
#include <string_view>
#include <utility>
// Inside the plugin host only: the reskate API a plugin's VM gets.
namespace dingosdk::plugins {
@@ -45,6 +49,27 @@ void add_functions(lua_State* L, std::initializer_list<ApiFunction> functions);
// Pushes a new read-only table of the functions, kept outside reskate (the draw and ui handles).
void push_functions(lua_State* L, std::initializer_list<ApiFunction> functions);
// ---- fixed words -------------------------------------------------------------------------------
// The words an API function takes or gives from a fixed set. Each list is also reskate.Enums.<name>
// (Api::install), so the check and the constant cannot drift apart.
using Words = std::span<const std::string_view>;
inline constexpr std::string_view log_levels[] = {"info", "warn", "error"}; // Enums.LogLevel
inline constexpr std::string_view aligns[] = {"left", "centre", "center", "right"}; // Enums.Align
inline constexpr std::string_view grind_sides[] = {"toe", "heel", "left", "right"}; // Enums.GrindSide
inline constexpr std::string_view stances[] = {"regular", "goofy"}; // Enums.Stance
inline constexpr std::string_view pops[] = {"low", "mid", "high"}; // Enums.Pop
inline constexpr std::string_view spaces[] = {"local", "world"}; // Enums.Space
inline constexpr std::string_view throwdown_modes[] = {"jam", "spot_battle", "skate"}; // Enums.Throwdown
inline constexpr std::string_view throwdown_cards[] = {"skate", "spot", "spot_battle", "jam"}; // Enums.Card
inline constexpr std::string_view entity_statuses[] = {"waiting", "loading", "missing", "ready", "hidden"}; // Enums.EntityStatus
inline constexpr std::string_view send_targets[] = {"all", "server"}; // Enums.Target
// Named keys for input.key and the like (letters and digits are their own character): Enums.Key.
inline constexpr std::pair<std::string_view, int> key_names[] = {
{"Enter", 13}, {"Backspace", 8}, {"Escape", 27}, {"End", 35}, {"Home", 36}, {"PageUp", 33}, {"PageDown", 34}, {"Space", 32},
{"Shift", 16}, {"Ctrl", 17}, {"Alt", 18}, {"Tab", 9}, {"Left", 37}, {"Up", 38}, {"Right", 39}, {"Down", 40},
{"[", 0xDB}, {"]", 0xDD}, {"LeftMouse", 1}, {"RightMouse", 2}, {"MiddleMouse", 4}};
inline bool one_of(Words words, std::string_view word) { return std::ranges::find(words, word) != words.end(); }
// ---- what most API functions start with --------------------------------------------------------
// The plugin calling, or a Lua error once it has been stopped.
Plugin& plugin_of(lua_State* L);
+1 -5
View File
@@ -146,11 +146,7 @@ int key_code(lua_State* L, int index) {
return vk;
}
const std::string name = luaL_checkstring(L, index);
static const std::pair<const char*, int> names[] = {
{"Enter", 13}, {"Backspace", 8}, {"Escape", 27}, {"End", 35}, {"Home", 36}, {"PageUp", 33}, {"PageDown", 34}, {"Space", 32},
{"Shift", 16}, {"Ctrl", 17}, {"Alt", 18}, {"Tab", 9}, {"Left", 37}, {"Up", 38}, {"Right", 39}, {"Down", 40},
{"[", 0xDB}, {"]", 0xDD}, {"LeftMouse", 1}, {"RightMouse", 2}, {"MiddleMouse", 4}};
for (const auto& [n, vk] : names)
for (const auto& [n, vk] : key_names)
if (name == n) return vk;
if (name.size() == 1 && ((name[0] >= 'A' && name[0] <= 'Z') || (name[0] >= '0' && name[0] <= '9'))) return name[0];
luaL_argerror(L, index, "a key name (A-Z, 0-9, Enter, Backspace, Escape, End, PageUp, PageDown, [, ], ...) or a virtual key");
+1 -1
View File
@@ -281,7 +281,7 @@ struct UiApi {
need(L, plugin, "overlay", "reskate.ui.toast");
const auto message = checked_text(L, 1, 200, "a toast");
const std::string level = luaL_optstring(L, 2, "info");
if (level != "info" && level != "warn" && level != "error") luaL_argerror(L, 2, "the level is \"info\", \"warn\" or \"error\"");
if (!one_of(log_levels, level)) luaL_argerror(L, 2, "the level is \"info\", \"warn\" or \"error\"");
const double now = plugin.services_.now_ms();
std::erase_if(plugin.toasts_, [&](double at) { return now - at >= 5000; });
if (static_cast<int>(plugin.toasts_.size()) >= plugin.limits_.toasts) {
+6 -7
View File
@@ -117,7 +117,7 @@ struct WorldApi {
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")
if (!one_of(grind_sides, wish.side))
luaL_error(L, "a grind exit's side is \"toe\", \"heel\", \"left\" or \"right\"");
lua_getfield(L, 1, "stance");
if (!lua_isnil(L, -1)) {
@@ -460,10 +460,9 @@ struct WorldApi {
static int entities_status(lua_State* L) {
auto& plugin = plugin_of(L);
need(L, plugin, "world.entities", "reskate.entities.status");
static constexpr const char* names[] = {nullptr, "waiting", "loading", "missing", "ready", "hidden"};
const auto status = game(plugin).entity_status(plugin.id(), entity_id(L, 1));
if (status <= 0 || status > 5) lua_pushnil(L);
else lua_pushstring(L, names[status]);
if (status <= 0 || status > static_cast<int>(std::size(entity_statuses))) lua_pushnil(L);
else lua_pushlstring(L, entity_statuses[status - 1].data(), entity_statuses[status - 1].size());
return 1;
}
static int entities_ready(lua_State* L) {
@@ -743,7 +742,7 @@ struct WorldApi {
lua_getfield(L, 3, "pop");
if (!lua_isnil(L, -1)) pop = luaL_checkstring(L, -1);
lua_pop(L, 1);
if (pop != "" && pop != "low" && pop != "mid" && pop != "high") luaL_argerror(L, 3, "a pop is \"low\", \"mid\" or \"high\"");
if (!pop.empty() && !one_of(pops, pop)) luaL_argerror(L, 3, "a pop is \"low\", \"mid\" or \"high\"");
}
const float speed = number_field(L, 3, "speed", 1, 0.1f, 4);
return result(L, game(plugin).replace_trick(plugin.id(), std::move(trick), std::move(pop), std::move(clip), speed));
@@ -867,7 +866,7 @@ struct WorldApi {
lua_getfield(L, 2, "space");
const std::string space = lua_isnil(L, -1) ? "local" : luaL_checkstring(L, -1);
lua_pop(L, 1);
if (space != "local" && space != "world") luaL_argerror(L, 2, "space is \"local\" or \"world\"");
if (!one_of(spaces, space)) luaL_argerror(L, 2, "space is \"local\" or \"world\"");
return result(L, game(plugin).board_set(plugin.id(), bone, value, space == "world"));
}
static int board_hide(lua_State* L) {
@@ -1284,7 +1283,7 @@ struct WorldApi {
mode.cursor_height = static_cast<float>(h);
}
lua_pop(L, 1);
const auto stock = [](const std::string& tile) { return tile == "skate" || tile == "spot" || tile == "spot_battle" || tile == "jam"; };
const auto stock = [](const std::string& tile) { return one_of(throwdown_cards, tile); };
if (!mode.replaces.empty() && !stock(mode.replaces)) luaL_argerror(L, 1, "replaces is \"skate\", \"spot\" or \"jam\"");
if (!stock(mode.card)) luaL_argerror(L, 1, "card is \"skate\", \"spot\" or \"jam\"");
if (mode.flag.size() > 256 || mode.art.size() > 256) luaL_argerror(L, 1, "art and flag are at most 256 characters");