Files
AyeZeeBB 07fef3820d Limit how many objects each player can place (protocol 43)
Limit how many objects each player can place (protocol 43)

- Servers and lobby hosts set how many objects each player may have
  placed: object_limit in ReSkateServer.json and the "objects" command,
  "Objects per player" in the host options. Default 100; 0 is no limit
- The host and a server's admins are not limited
- At the limit a player's game refuses another object, from the native
  object tool and the park editor, and the menu shows how many they have
  placed
- Enforced by the host or server, which shows everyone no more than the
  limit of each player's layout; objects already shown keep their place
- The limit travels in the roster, which takes the protocol to version
  43: builds before this cannot join sessions with builds after it
- The protocol tests link again (the server browser needs the identity
  lists since the official servers change)
2026-10-07 10:03:54 -05:00

141 lines
7.3 KiB
C++

#include "park_editor_runtime.h"
#include "park_editor_internal.h"
#include "Extension/Objects/local_placements_runtime.h"
#include "park_document.h"
#include "park_mods.h"
#include "Engine/Vfs/mod_list.h"
namespace dingosdk {
using namespace profile_runtime;
using namespace profile_runtime::park_editor_detail;
namespace {
std::filesystem::path mods_root() {
return editor_state().data_root / mods::mods_folder;
}
const editor::ParkMod *find_park_mod(std::string_view folder) {
const auto &mods = editor_state().park_mods;
const auto it = std::find_if(mods.begin(), mods.end(), [&](const auto &mod) { return mod.folder == folder; });
return it == mods.end() ? nullptr : &*it;
}
} // namespace
std::string edit_local_park(std::string_view operation, std::string_view map, std::uint64_t generation,
std::uint64_t revision, std::string_view argument,
const profile::PlacedObject &supplied, const std::vector<std::string> &texts) {
std::lock_guard lock(local_runtime().native_mutex);
auto &r = placements_runtime();
auto &e = editor_state();
try {
if (!lobby_object_placement_allowed()) return "error: The host has disabled object placement for you.";
if (!local_runtime().active || !placement_session_ready() || r.map != map ||
generation != e.generation || revision != e.revision)
return "error: Editor selection expired; wait for the current level.";
if (!idle())
return "error: Wait for the current edit or reload after an unacknowledged edit.";
auto target = r.document.maps[r.map];
const auto details = [&](std::size_t first) {
if (texts.size() < first + 4) throw std::runtime_error("Park mod details are incomplete.");
return editor::ParkModDetails{texts[first], texts[first + 1], texts[first + 2], texts[first + 3]};
};
const auto title_of = [&](std::string_view folder) {
const auto *mod = find_park_mod(folder);
return mod && !mod->details.title.empty() ? mod->details.title : std::string(folder);
};
// ---- park mods: files only, the layout in the world is unchanged
if (operation == "mods-refresh") {
refresh_park_mods();
e.status = "Park mods refreshed.";
++e.revision;
return e.status;
}
if (operation == "mod-save" || operation == "mod-create") {
std::string folder(argument);
if (operation == "mod-create") folder = editor::create_park_mod(mods_root(), details(0));
else if (!find_park_mod(folder)) throw std::runtime_error("That park mod is not installed.");
editor::save_mod_park(mods_root(), folder, {r.map, target});
e.project = folder;
refresh_park_mods();
e.status = (operation == "mod-create" ? "Created park mod " : "Saved to ") + title_of(folder) +
" (" + std::to_string(target.size()) + " objects on " + r.map + ").";
++e.revision;
return e.status;
}
if (operation == "mod-details") {
if (!find_park_mod(argument)) throw std::runtime_error("That park mod is not installed.");
editor::save_park_mod_details(mods_root(), argument, details(0));
refresh_park_mods();
e.status = "Saved the details of " + title_of(argument) + ".";
++e.revision;
return e.status;
}
if (operation == "mod-convert") {
const auto park = editor::load_park(e.directory, argument);
const auto folder = editor::create_park_mod(
mods_root(), {std::string(argument), "", "", "Converted from a park saved before park mods."});
editor::save_mod_park(mods_root(), folder, park);
refresh_park_mods();
e.status = "Converted " + std::string(argument) + " into the park mod " + folder + " (" + park.map + ").";
++e.revision;
return e.status;
}
// ---- operations that change the layout
if (operation == "mod-open" || operation == "mod-load") {
if (is_lobby_guest())
throw std::runtime_error("In multiplayer only the host can load park mods.");
if (!find_park_mod(argument)) throw std::runtime_error("That park mod is not installed.");
target = editor::load_mod_park(mods_root(), argument, r.map).objects;
// Editing a mod saves back into it; loading a preset leaves the project alone.
if (operation == "mod-open") e.project = argument;
} else if (operation == "new") {
target.clear();
e.project.clear();
}
else if (operation == "undo" || operation == "redo") {
const bool undo = operation == "undo";
const auto &history = undo ? e.undo : e.redo;
if (history.empty())
throw std::runtime_error("No history available.");
if (!same_layout(target, undo ? history.back().after : history.back().before)) {
e.undo.clear();
e.redo.clear();
throw std::runtime_error("Objects changed outside the editor. History was cleared.");
}
begin(undo ? history.back().before : history.back().after, undo ? -1 : 1);
return e.status;
} else if (operation == "place") {
auto object = supplied;
if (r.next_id == UINT64_MAX || target.size() >= 1024)
throw std::runtime_error("Park object limit reached.");
// The session's own limit on each player (the mutex is held: count here).
if (const auto limit = lobby_object_limit(); limit && target.size() >= limit)
throw std::runtime_error(object_limit_notice);
object.id = r.next_id++;
if (!e.assets || std::none_of(e.assets->begin(), e.assets->end(),
[&](const auto &item) { return item.key == object.item; }))
throw std::runtime_error("Object is not available in the installed Build Kit catalog.");
target.push_back(std::move(object));
} else if (operation == "move" || operation == "delete") {
const auto found = std::find_if(target.begin(), target.end(),
[&](const auto &x) { return x.id == supplied.id; });
if (found == target.end())
throw std::runtime_error("The selected object no longer exists.");
if (operation == "delete")
target.erase(found);
else {
found->position = supplied.position;
found->rotation = supplied.rotation;
found->scale = supplied.scale;
}
} else if (operation != "mod-open" && operation != "mod-load" && operation != "new")
throw std::runtime_error("Unknown park editor operation.");
begin(std::move(target), 0);
if (operation == "mod-open" || operation == "mod-load")
e.status = "Loading " + title_of(argument) + "...";
return e.status;
} catch (const std::exception &error) {
e.status = std::string("error: ") + error.what();
++e.revision;
return e.status;
}
}
} // namespace dingosdk