diff --git a/Server/README-roles.txt b/Server/README-roles.txt index 2a2fd4d..a9ffe80 100644 --- a/Server/README-roles.txt +++ b/Server/README-roles.txt @@ -148,6 +148,9 @@ Map and world between and the rotation uses. rotation rotation [|off]: change the map on a timer. park park , park random: the park layouts. + park-mod The park mods (see Park mods in README.txt): list, add, + remove, clear and reload spawned parks, and "park-mod + config add|remove" to set one up (saved to the config). layer-sync layer-sync on|off: force the server's world layers on everyone. layer layer default|on|off: one world layer. diff --git a/Server/README.txt b/Server/README.txt index 65b3af1..1ddd9b1 100644 --- a/Server/README.txt +++ b/Server/README.txt @@ -38,8 +38,9 @@ A park built in the game's Park Editor and saved as a mod (a folder with manifest.json and parks/.park.json) can be put up by the server for everyone. Copy the mod's folder into the Mods folder next to the server, list it in maps.park_mods with a name and the map it was built on, and spawn it with -"park-mod add " while that map runs: from the console, as an admin in chat -(/park-mod add street 30), or by a player vote (see votes.custom). With minutes +"park-mod add " while that map runs: from the console, in chat by a player +whose role has park-mod (/park-mod add street 30; the admin role does, see +README-roles.txt), or by a player vote (see votes.custom). With minutes (1-1440) it goes by itself when the time is up; players are told a minute before and when it goes. Without, it stays until removed. Every park goes when the map changes, and none come back after a restart. diff --git a/Server/Test/server_config_tests.cpp b/Server/Test/server_config_tests.cpp index b71c9c3..35a6ccf 100644 --- a/Server/Test/server_config_tests.cpp +++ b/Server/Test/server_config_tests.cpp @@ -528,6 +528,10 @@ int run() { turned_down([](ServerConfig &c) { c.park_limits.objects = 1025; }, "More park objects than one owner can have accepted"); turned_down([](ServerConfig &c) { c.park_mods.resize(33, c.park_mods[0]); }, "More than 32 park mods accepted"); check(!custom_command_name_free("park-mod"), "A custom command may be called park-mod"); + // A command a role can be given: the admin role's "*" has it, the moderator's list does not. + const auto roles = default_roles(); + check(allows(roles.find("admin"), "park-mod") && !allows(roles.find("moderator"), "park-mod") && !allows(roles.find("player"), "park-mod"), + "park-mod is not the admin role's alone among the default roles"); // The vote README.txt shows: "park" is a server command, and still a fine vote name. auto voting = parks; voting.votes.custom = {{"park", "Spawn a park for 30 min", "park-mod add {arg} 30", {"street", "plaza-2"}, {true, 60}}, diff --git a/Server/Test/server_park_tests.cpp b/Server/Test/server_park_tests.cpp index b78b521..3fa5770 100644 --- a/Server/Test/server_park_tests.cpp +++ b/Server/Test/server_park_tests.cpp @@ -272,8 +272,10 @@ int run(const std::filesystem::path &fixtures) { }; const auto logged = [&](std::string_view text) { return has(log, text); }; + // Roles (Server/roles.h): the player is an admin, whose "*" gives them park-mod. const std::uint64_t admin_id = 76561198000000001ULL; - config.admins = {admin_id}; + config.roles = default_roles(); + config.roles.players[admin_id] = "admin"; auto &a = add_game(admin_id, "Robin"); a.hello(now); run_for(2000000); @@ -307,6 +309,34 @@ int run(const std::filesystem::path &fixtures) { reply.find("2 min left") != std::string::npos && reply.find("until removed") != std::string::npos, "list: " + reply); + // Roles: a moderator's role does not list park-mod; a role that lists it gives it alone. + { + const std::uint64_t mod_id = 76561198000000003ULL; + config.roles.players[mod_id] = "moderator"; + auto &m = add_game(mod_id, "Moddy"); + m.hello(now); + run_for(2000000); + m.say("/park-mod list", now); + run_for(500000); + check(has(m.chat, "Your role (moderator) cannot use park-mod."), "A moderator could use park-mod"); + Role builder; + builder.name = "builder"; + builder.rank = 20; + builder.commands = {"park-mod"}; + config.roles.list.push_back(builder); + config.roles.settle(); + config.roles.players[mod_id] = "builder"; + m.say("/park-mod", now); // not the same line again: chat turns repeats away + run_for(500000); + check(has(m.chat, "Park mods (2 of 2 spawned"), "A role listing park-mod could not use it"); + check(!has(m.chat, "Your role (builder) cannot use"), "A role listing park-mod was refused it"); + wire().status.peers.erase(std::remove_if(wire().status.peers.begin(), wire().status.peers.end(), + [&](const auto &peer) { return peer.id == mod_id; }), + wire().status.peers.end()); + games.erase(mod_id); + run_for(1000000); + } + // An admin in game: by their name. a.say("/park-mod remove street2", now); run_for(500000);