mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-28 10:16:51 +01:00
Merge pull request #370 from ValveTextureFile/server-lua-api
Plugins: Enums (LogLevel, Align, GrindSide, Stance, Pop, Space, etc.)
This commit is contained in:
6 files changed
+60
-31
No files matched your search
@@ -2516,10 +2516,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")
|
||||
|
||||
@@ -231,7 +231,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;
|
||||
}
|
||||
@@ -710,21 +710,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}, {"Launch", launches},
|
||||
{"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;
|
||||
|
||||
@@ -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 {
|
||||
@@ -49,6 +53,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 launches[] = {"grind", "ollie", "no_comply", "boneless"}; // Enums.Launch
|
||||
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}, {"Delete", 46}, {"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);
|
||||
|
||||
@@ -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}, {"Delete", 46}, {"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");
|
||||
|
||||
@@ -366,7 +366,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) {
|
||||
|
||||
@@ -122,7 +122,7 @@ struct WorldApi {
|
||||
auto& plugin = plugin_of(L);
|
||||
need(L, plugin, "skater.control", "reskate.skater.shape_launch");
|
||||
const std::string from = luaL_checkstring(L, 1);
|
||||
if (from != "grind" && from != "ollie" && from != "no_comply" && from != "boneless")
|
||||
if (!one_of(launches, from))
|
||||
luaL_argerror(L, 1, "a launch is out of \"grind\", \"ollie\", \"no_comply\" or \"boneless\"");
|
||||
if (lua_isnoneornil(L, 2)) return result(L, game(plugin).shape_launch(plugin.id(), from, std::nullopt));
|
||||
luaL_checktype(L, 2, LUA_TTABLE);
|
||||
@@ -552,10 +552,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) {
|
||||
@@ -955,7 +954,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));
|
||||
@@ -1743,7 +1742,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) {
|
||||
@@ -2457,7 +2456,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");
|
||||
|
||||
Reference in new issue
Block a user