Files
ReSkate/Extension/Objects/ParkEditor/park_editor_lobby.cpp
T
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

116 lines
4.8 KiB
C++

#include "park_editor_runtime.h"
#include "park_editor_internal.h"
#include "Extension/Objects/local_placements_runtime.h"
#include "Extension/Objects/network_object_runtime.h"
#include <atomic>
namespace dingosdk {
namespace {
std::atomic<bool> placement_allowed{true};
std::atomic<unsigned> object_limit{};
}
bool lobby_object_placement_allowed() noexcept { return placement_allowed.load(std::memory_order_acquire); }
void set_lobby_object_limit(unsigned limit) noexcept { object_limit.store(limit, std::memory_order_release); }
unsigned lobby_object_limit() noexcept { return object_limit.load(std::memory_order_acquire); }
using namespace profile_runtime;
using namespace profile_runtime::park_editor_detail;
// Not for callers that hold the native mutex: they have the layout, and count it themselves.
std::size_t lobby_object_count() {
std::lock_guard lock(local_runtime().native_mutex);
auto &r = placements_runtime();
const auto found = r.document.maps.find(r.map);
return found == r.document.maps.end() ? 0 : found->second.size();
}
bool lobby_object_limit_reached() {
const auto limit = lobby_object_limit();
return limit && lobby_object_count() >= limit;
}
void set_lobby_object_guest(bool guest) {
std::lock_guard lock(local_runtime().native_mutex);
auto &r = placements_runtime();
if (guest == r.personal_document.has_value()) return;
auto &e = editor_state();
// Transfer visible copies to the deletion queue; this changes no saved IDs.
// Preview entities may not have reached rows/the committed layout yet.
for (const auto &row : r.rows)
retire_local_network_object(row.entity, row.object);
if (e.transaction)
for (const auto &step : e.transaction->steps)
retire_local_network_object(step.entity, step.object);
if (e.late_create)
retire_local_network_object(e.late_create->entity, e.late_create->object);
r.creation.reset();
r.creates.clear(); // Reject requests queued in the previous lobby scope.
reset_park_editor();
r.tracked.clear();
r.restore.clear();
r.late_restores.clear();
r.inflight.reset();
r.rows.clear();
r.teleport_token = 0;
r.restore_failed = false;
r.clearing = false;
r.clear_entities.clear();
if (guest)
r.personal_document.emplace(std::exchange(r.document, {}));
else {
r.document = std::move(*r.personal_document);
r.personal_document.reset();
}
queue_placement_layout(r);
r.status = guest ? "Using the host's saved objects. Your session edits are temporary."
: "Restoring your saved objects.";
}
bool clear_lobby_guest_objects() {
std::lock_guard lock(local_runtime().native_mutex);
auto &r = placements_runtime();
if (!r.personal_document) return true; // Only guests hold temporary session layouts.
if (!local_runtime().active || !placement_session_ready()) return false;
std::set<std::uint64_t> tokens;
for (const auto &row : r.rows) tokens.insert(row.token);
if (!tokens.empty() && !begin_placement_delete(tokens)) return false;
// Anything not yet spawned has no row; drop it from the session layout too.
r.document.maps[r.map].clear();
r.restore.clear();
r.late_restores.clear();
r.status = "The host deleted all guest objects.";
return true;
}
void set_lobby_object_placement_allowed(bool enabled) {
std::lock_guard lock(local_runtime().native_mutex);
placement_allowed.store(enabled, std::memory_order_release);
if (enabled) return;
auto &e = editor_state();
e.selection.clear();
e.probe.reset();
e.surface = {};
if (e.transaction && e.transaction->preview && !e.transaction->preview->cancelled)
cancel_preview(*e.transaction);
}
std::optional<NetworkObjectSnapshot> capture_local_network_objects() {
std::lock_guard lock(local_runtime().native_mutex);
auto &r = placements_runtime();
auto &e = editor_state();
if (!local_runtime().active || !placement_session_ready() || !r.restore.empty() || r.inflight ||
r.clearing || r.restore_failed)
return {};
auto layout = r.document.maps[r.map];
if (e.transaction && e.transaction->preview)
for (const auto &step : e.transaction->steps) {
if (!step.entity || step.kind == Kind::erase)
continue;
auto found = std::find_if(layout.begin(), layout.end(),
[&](const auto &object) { return object.id == step.object.id; });
if (found == layout.end())
layout.push_back(step.object);
else
*found = step.object;
}
NetworkObjectSnapshot result;
result.map = r.map;
for (const auto &object : layout)
result.objects.push_back({object.id, object.item, object.position, object.rotation, object.scale});
return result;
}
} // namespace dingosdk