mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-19 04:15:26 +02:00
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)
141 lines
7.3 KiB
C++
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
|