Park mods: fit to roles

- park-mod is a command a role can list: the admin role's "*" gives it, the
  default moderator role does not (spawning parks and changing the saved
  setup are the owner's call); command() checks the caller's role as for
  every other command, and custom votes still run it as the console
- The client's "/" menu shows the park-mod rows by role like the other
  server commands there, by the row's first word (resolved in the rebased
  client commit)
- README-roles.txt lists park-mod; README.txt says who may use it
- Tests: the admin role has park-mod and the moderator and player roles do
  not; in game a moderator is refused and a role listing park-mod is not

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2G4APEkhHgVs59RDL3XWD
This commit is contained in:
Robin MöllerandClaude Opus 5.5 committed 2026-10-11 17:52:49 +00:00
1 parent f8b01dc3d5
commit dbb5cf8caa
4 files changed
+41 -3

No files matched your search

+3
View File
@@ -148,6 +148,9 @@ Map and world
between and the rotation uses.
rotation rotation [<minutes>|off]: change the map on a timer.
park park <lot> <layout>, 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 <key> default|on|off: one world layer.
+3 -2
View File
@@ -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/<map>.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 <name>" 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 <name>" 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.
+4
View File
@@ -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}},
+31 -1
View File
@@ -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);