mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-27 09:37:09 +01:00
Merge pull request #348 from moelrobi/plugins/server-roles
Plugins: server plugins know each player's role
This commit is contained in:
6 files changed
+118
-5
No files matched your search
@@ -546,6 +546,10 @@ struct FakeGame final : Game {
|
||||
calls.push_back(std::format("announce {} {}", plugin, text));
|
||||
return {};
|
||||
}
|
||||
std::optional<Role> role(std::uint64_t player) override {
|
||||
if (player == 1) return Role{"moderator", 50, true};
|
||||
return std::nullopt;
|
||||
}
|
||||
std::vector<ServerPark> server_parks() override { return {{"street", "San Vansterdam", true, true, 12, 5}, {"plaza", "Isle of Grom", false, false, 0, 0}}; }
|
||||
std::string spawn_park(std::string_view plugin, std::string_view name, unsigned minutes) override {
|
||||
calls.push_back(std::format("park spawn {} {} {}", plugin, name, minutes));
|
||||
@@ -2108,6 +2112,40 @@ void plugins_server_copies_spawn_park_mods() {
|
||||
"A plugin without server.parks used the park mods\n" + blind.all());
|
||||
}
|
||||
|
||||
// A player's role, for a server copy with server: read-only, nil for one with none. Not for a copy
|
||||
// without the permission, nor for a player's copy.
|
||||
void plugins_server_copies_see_roles() {
|
||||
TestEnvironment server, player, blind;
|
||||
for (auto* environment : {&server, &player, &blind}) environment->session_now = {true, false, {{1, "Ann", false, false}, {2, "Bob", false, false}}};
|
||||
const std::string main = R"(
|
||||
reskate.console.command("roles", "x", function()
|
||||
local ann, bob = reskate.session.players()[1], reskate.session.players()[2]
|
||||
local role = ann.role
|
||||
assert(role and role.name == "moderator" and role.rank == 50 and role.staff == true)
|
||||
assert(not pcall(function() role.rank = 1000 end), "read-only")
|
||||
assert(bob.role == nil, "nil with no role")
|
||||
reskate.log("roles done")
|
||||
end)
|
||||
)";
|
||||
const auto roles = [&](const char* id, const char* permissions, const char* side) {
|
||||
return package(id, {{manifest_file, manifest(permissions, side)}, {"plugin/main.lua", main}});
|
||||
};
|
||||
Host server_host(server, Side::server), player_host(player, Side::client), blind_host(blind, Side::server);
|
||||
server_host.apply({roles("roles-s", R"("console", "session.read", "server")", "server")});
|
||||
player_host.apply({roles("roles-p", R"("console", "session.read", "server")", "client")});
|
||||
blind_host.apply({roles("roles-b", R"("console", "session.read")", "server")});
|
||||
// a tick first: the host takes the session's players then (Host::follow_session), so they are there to ask
|
||||
ticks(server_host, server, 1);
|
||||
ticks(player_host, player, 1);
|
||||
ticks(blind_host, blind, 1);
|
||||
for (auto* host : {&server_host, &player_host, &blind_host}) host->run_console("roles");
|
||||
check(server.logged("[roles-s] roles done"), "A server copy could not read the players' roles\n" + server.all());
|
||||
check(!player.logged("roles done") && player.all().find("server copy") != std::string::npos,
|
||||
"A player's copy read the players' roles\n" + player.all());
|
||||
check(!blind.logged("roles done") && blind.all().find("\"server\"") != std::string::npos,
|
||||
"A plugin without server read the players' roles\n" + blind.all());
|
||||
}
|
||||
|
||||
// Clips on the skater's body: from the package or recorded, played, replacing a trick.
|
||||
void plugins_animate_the_skater() {
|
||||
TestEnvironment environment;
|
||||
@@ -3870,6 +3908,7 @@ int main() {
|
||||
run(animator_studio_records_and_saves);
|
||||
run(plugins_server_copies_are_the_authority);
|
||||
run(plugins_server_copies_spawn_park_mods);
|
||||
run(plugins_server_copies_see_roles);
|
||||
run(plugins_animate_the_skater);
|
||||
run(plugins_play_their_own_sounds);
|
||||
run(plugins_have_menu_panels);
|
||||
|
||||
@@ -2152,14 +2152,30 @@ struct WorldApi {
|
||||
const auto player = player_of(L, 1);
|
||||
return result(L, game(plugin).apply_permissions(plugin.id(), player, permissions_of(L, 2)));
|
||||
}
|
||||
// A player table's own names: permissions (read when asked, so always the server's now) and
|
||||
// apply_permissions. Anything else a player table does not have is nil.
|
||||
// player.role: {name, rank, staff}, read-only, or nil for a player with no role.
|
||||
static int player_role(lua_State* L) {
|
||||
auto& plugin = authority(L, "player.role");
|
||||
const auto role = game(plugin).role(player_of(L, 1));
|
||||
if (!role) return 0;
|
||||
lua_createtable(L, 0, 3);
|
||||
lua_pushlstring(L, role->name.data(), role->name.size());
|
||||
lua_setfield(L, -2, "name");
|
||||
lua_pushinteger(L, role->rank);
|
||||
lua_setfield(L, -2, "rank");
|
||||
lua_pushboolean(L, role->staff);
|
||||
lua_setfield(L, -2, "staff");
|
||||
lua_setreadonly(L, -1, true);
|
||||
return 1;
|
||||
}
|
||||
// A player table's own names: permissions and role (read when asked, so always the server's
|
||||
// now) and apply_permissions. Anything else a player table does not have is nil.
|
||||
static int player_index(lua_State* L) {
|
||||
const char* key = lua_type(L, 2) == LUA_TSTRING ? lua_tostring(L, 2) : "";
|
||||
if (std::string_view(key) == "apply_permissions") {
|
||||
lua_pushcfunction(L, player_apply_permissions, "apply_permissions");
|
||||
return 1;
|
||||
}
|
||||
if (std::string_view(key) == "role") return player_role(L);
|
||||
if (std::string_view(key) != "permissions") return 0;
|
||||
auto& plugin = authority(L, "player.permissions");
|
||||
const auto permissions = game(plugin).permissions(player_of(L, 1));
|
||||
|
||||
@@ -596,6 +596,15 @@ public:
|
||||
};
|
||||
virtual std::optional<Permissions> permissions(std::uint64_t) { return std::nullopt; }
|
||||
virtual std::string apply_permissions(std::string_view /*plugin*/, std::uint64_t, const Permissions&) { return unavailable; }
|
||||
// A player's role on a dedicated server (data/roles.json): its name, its rank (0 to 1000), and
|
||||
// whether it gives any of the server's commands. Nothing for a player with no role, nor where
|
||||
// there are no roles (a lobby's host).
|
||||
struct Role {
|
||||
std::string name;
|
||||
int rank{};
|
||||
bool staff{};
|
||||
};
|
||||
virtual std::optional<Role> role(std::uint64_t) { return std::nullopt; }
|
||||
// A dedicated server's park mods (server.parks): those it has set up, whether each fits the map
|
||||
// running now and is spawned (its objects, and minutes left; 0 until removed). Spawning one (for
|
||||
// `minutes`, 0 until removed) or removing it goes as the server's own park-mod does.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// The dedicated server's plugins (server_plugins.cpp) with the server's host, over a transport
|
||||
// that is not Steam's and simulated players: what a server plugin sees of the players' skaters.
|
||||
// that is not Steam's and simulated players: what a server plugin sees of the players' skaters
|
||||
// and roles.
|
||||
//
|
||||
// dingosdk_server_plugin_tests <Server/Test/fixtures/park_mods>
|
||||
#include "Server/server_host.h"
|
||||
@@ -177,6 +178,7 @@ int run(const std::filesystem::path &fixtures) {
|
||||
config.auto_update = false;
|
||||
config.activity_log = false;
|
||||
config.roles = default_roles();
|
||||
config.roles.players[76561198000000001ULL] = "moderator"; // the second player has the default role
|
||||
config.park_mods = {{"street", "popular skate 2 street park", "San Vansterdam", "bam"}, {"plaza", "mixed park", "Isle of Grom", "grom"}};
|
||||
check(config_error(config).empty(), "The test's config was refused: " + config_error(config));
|
||||
|
||||
@@ -251,6 +253,32 @@ int run(const std::filesystem::path &fixtures) {
|
||||
end)
|
||||
)");
|
||||
|
||||
// One that says each player's role, and one without server that may not know them.
|
||||
write(root / "Plugins" / "ranks" / "reskate-plugin.json",
|
||||
R"({"api": 1, "entry": "plugin/main.lua", "side": "server", "permissions": ["console", "session.read", "server"]})");
|
||||
write(root / "Plugins" / "ranks" / "plugin" / "main.lua", R"(
|
||||
reskate.console.command("ranks", "players' roles, for the test", function()
|
||||
for _, p in ipairs(reskate.session.players()) do
|
||||
local role = p.role
|
||||
if role then
|
||||
reskate.console.print(string.format("role %s %s rank %d staff %s", p.id, role.name, role.rank, tostring(role.staff)))
|
||||
reskate.console.print(pcall(function() role.rank = 1000 end) and "role changed" or "role read-only")
|
||||
elseif not p.server then
|
||||
reskate.console.print("role " .. p.id .. " none")
|
||||
end
|
||||
end
|
||||
end)
|
||||
)");
|
||||
write(root / "Plugins" / "rankless" / "reskate-plugin.json",
|
||||
R"({"api": 1, "entry": "plugin/main.lua", "side": "server", "permissions": ["console", "session.read"]})");
|
||||
write(root / "Plugins" / "rankless" / "plugin" / "main.lua", R"(
|
||||
reskate.console.command("rankless", "roles without server", function()
|
||||
local p = reskate.session.players()[1]
|
||||
local ok, why = pcall(function() return p.role end)
|
||||
reskate.console.print(ok and "rankless saw a role" or "rankless refused: " .. tostring(why))
|
||||
end)
|
||||
)");
|
||||
|
||||
std::vector<std::string> log;
|
||||
SteamTransport transport;
|
||||
Host host(config, transport, [&](const std::string &line) { log.push_back(line); });
|
||||
@@ -320,12 +348,24 @@ int run(const std::filesystem::path &fixtures) {
|
||||
run_for(1100000);
|
||||
check(has(log, "skater 76561198000000001 at 10.0 2.0 -30.0 facing 1.00, stale"), "A skater's age did not grow without poses");
|
||||
|
||||
// Park mods: listed with whether they fit this map; spawned and removed through park-mod's own
|
||||
// checks, logged by the plugin's name.
|
||||
// Roles: the one given (moderator), the server's default (player) for the other, read-only;
|
||||
// and nothing for a plugin without server.
|
||||
const auto plugin_command = [&](std::string_view line) {
|
||||
log.clear();
|
||||
check(host.command(line) == "Ran by a plugin.", std::string(line) + " did not reach the plugin");
|
||||
};
|
||||
plugin_command("ranks");
|
||||
check(has(log, "role 76561198000000001 moderator rank 50 staff true"), "The moderator's role is not what the server gave them");
|
||||
check(has(log, "role 76561198000000002 player rank 0 staff false"), "A player given no role does not have the default role");
|
||||
check(has(log, "role read-only") && !has(log, "role changed"), "A plugin changed a player's role table");
|
||||
host.command("role 76561198000000002 admin");
|
||||
plugin_command("ranks");
|
||||
check(has(log, "role 76561198000000002 admin rank 100 staff true"), "A role given while they are on was not seen");
|
||||
plugin_command("rankless");
|
||||
check(has(log, "rankless refused: ") && has(log, "server"), "A plugin without server saw a role");
|
||||
|
||||
// Park mods: listed with whether they fit this map; spawned and removed through park-mod's own
|
||||
// checks, logged by the plugin's name.
|
||||
plugin_command("parker list");
|
||||
check(has(log, "park street on San Vansterdam, here true, spawned false, 0 objects, 0 min left"), "street is not listed as it is");
|
||||
check(has(log, "park plaza on Isle of Grom, here false, spawned false"), "plaza is not listed as on another map");
|
||||
|
||||
@@ -85,6 +85,8 @@ class Host {
|
||||
std::uint8_t player_locks(std::uint64_t id) const { return locks_of(id); }
|
||||
std::uint8_t set_locks(std::uint64_t id, std::uint8_t locks, const std::string &source);
|
||||
std::optional<std::array<float, 3>> player_position(std::uint64_t steam_id) const;
|
||||
// The role a player has (data/roles.json: given them, or the default), here or not; or none.
|
||||
const Role *player_role(std::uint64_t id) const { return role_of(id); }
|
||||
// Where each player in the world last said their skater was (their latest pose's root), and
|
||||
// how long ago that arrived: what the server's plugins see of the skaters.
|
||||
struct SkaterPose {
|
||||
|
||||
@@ -80,6 +80,13 @@ class ServerGame final : public Game {
|
||||
host_.set_locks(player, locks, "plugin " + std::string(plugin));
|
||||
return {};
|
||||
}
|
||||
// The role they have (the one given them, or the server's default role), here or not.
|
||||
std::optional<Role> role(std::uint64_t player) override {
|
||||
if (!multiplayer::individual_steam_id(player)) return std::nullopt; // the server itself has none
|
||||
const auto *held = host_.player_role(player);
|
||||
if (!held) return std::nullopt;
|
||||
return Role{held->name, held->rank, server::staff(held)};
|
||||
}
|
||||
std::vector<ServerPark> server_parks() override {
|
||||
std::vector<ServerPark> result;
|
||||
for (auto &p : host_.park_status()) result.push_back({std::move(p.name), std::move(p.map), p.here, p.spawned, p.objects, p.minutes_left});
|
||||
|
||||
Reference in new issue
Block a user