Server: object scaling can be turned off

Server: object scaling can be turned off

A dedicated server can stop players resizing the objects they place.

- "allow_object_scaling" in the config's "players" section (default true),
  and "object-scaling on|off" from the console or an admin. A change is
  saved and applies at once.
- Off: the server shares every player's objects at their own size, whatever
  that player's game sent. Admins can still resize theirs, as with the
  object limit.
- The roster tells each game. In the park editor of a player on such a
  server the SCALE mode is not offered, its Y shortcut does nothing, the
  scale box is disabled with a note saying why, and what is placed or edited
  is its own size.
- An object resized before the setting was turned off is its own size for
  everyone else straight away; its owner sees it resized until they edit it.
This commit is contained in:
AyeZeeBB committed 2026-10-08 22:21:00 -05:00
1 parent c8c6cf16f2
commit 9ab985e778
19 files changed
+1625 -1606

No files matched your search

+3
View File
@@ -153,6 +153,9 @@ struct MultiplayerModel {
// Objects each player may have placed in this session (0: no limit), the limit this
// player is held to (0 for the host and a server's admins), and how many they have placed.
unsigned object_limit{}, object_limit_own{}, objects_placed{};
// This player may resize the objects they place (a dedicated server can turn it off for its
// players; its admins always may).
bool object_scaling{true};
// Host setting: whether guests may use noclip / No Bail (the host and server admins always may).
bool guest_noclip{true}, guest_no_bail{true}, guest_boosts{true};
// Host setting: guests skate with the host's physics tuning (on a dedicated server: the
+4
View File
@@ -619,6 +619,7 @@ std::vector<std::uint8_t> encode(const Packet &p, bool compact_pose, std::uint32
w.integer(static_cast<std::uint8_t>(p.object_placement), 1);
if (!valid_object_limit(p.object_limit)) throw std::invalid_argument("Invalid object limit");
w.integer(p.object_limit, 2);
w.integer(p.object_scaling ? 1 : 0, 1);
w.integer(p.force_world_layers, 1);
// One mode per world-layer catalog row; both peers read the same catalog
// from the same game build.
@@ -945,6 +946,9 @@ std::optional<Packet> decode(std::span<const std::uint8_t> bytes) noexcept {
const auto object_limit = r.integer(2);
if (!valid_object_limit(object_limit)) return {};
p.object_limit = static_cast<unsigned>(object_limit);
const auto scaling = r.integer(1);
if (scaling > 1) return {};
p.object_scaling = scaling != 0;
const auto forced = r.integer(1);
if (forced > 1) return {};
p.force_world_layers = forced != 0;
+3
View File
@@ -188,6 +188,9 @@ struct Packet {
ObjectPlacement object_placement = ObjectPlacement::everyone;
// Objects each player may have placed (object_placement.h); 0: no limit.
unsigned object_limit{};
// Roster: players may place objects at another size than their own. Off: a dedicated server
// shares every player's objects at their own size (its admins' excepted).
bool object_scaling{true};
// Bumped each time the host deletes all guest objects. Guests delete their
// own session objects when it changes after their first roster.
std::uint32_t object_clears{};
@@ -74,6 +74,7 @@ void stop(Session &s, std::string reason) {
s.tps = multiplayer_default_tps;
s.object_placement = ObjectPlacement::everyone;
s.object_limit = 0;
s.object_scaling = true;
set_lobby_object_limit(0);
s.server_admin = false;
s.server_bans.clear();
@@ -155,6 +155,7 @@ struct Session {
MultiplayerDistances distances;
ObjectPlacement object_placement = ObjectPlacement::everyone;
unsigned object_limit{}; // objects each player may have placed; 0: no limit
bool object_scaling{true}; // the dedicated server lets players resize what they place
// What guests may use: the host's choice, or the host's roster for a guest.
bool guest_noclip = true, guest_no_bail = true, guest_boosts = true;
// Host: guests skate with its physics tuning. Guest: the host's roster says so (a
@@ -273,6 +273,7 @@ void apply_roster(Session &s, const Packet &p, std::uint64_t now) {
}
apply_object_placement(s, p.object_placement);
apply_object_limit(s, p.object_limit); // after server_admin, which exempts an admin
s.object_scaling = !dedicated_host(s) || p.object_scaling;
apply_guest_tools(s, p.guest_noclip, p.guest_no_bail, p.guest_boosts);
s.enforce_tuning = p.enforce_tuning;
s.server_votes = dedicated_host(s) ? p.server_votes : 0;
@@ -151,6 +151,7 @@ void publish(Session &s, const NativeFrame *local) {
view.distances = s.distances;
view.object_placement = s.object_placement;
view.object_limit = s.object_limit;
view.object_scaling = s.object_scaling || s.server_admin || s.mode != Mode::join;
view.object_limit_own = s.mode == Mode::host || s.server_admin ? 0 : s.object_limit; // as apply_object_limit gives this game
view.objects_placed = s.mode == Mode::off ? 0 : static_cast<unsigned>(s.local_objects.objects().size());
view.guest_noclip = s.guest_noclip;
@@ -585,6 +585,9 @@ void object_codec() {
const auto listed = decode(encode(served));
check(listed && listed->members.size() == 34 && listed->capacity == 32, "A server's roster past its limit failed to round-trip");
check(listed && listed->vote == served.vote, "A server's vote failed to round-trip");
served.object_scaling = false;
const auto fixed = decode(encode(served));
check(fixed && !fixed->object_scaling && listed && listed->object_scaling, "The roster's object scaling rule failed to round-trip");
served.vote = {};
const auto quiet = decode(encode(served));
check(quiet && quiet->vote == ServerVote{}, "A roster without a vote did not come back without one");
+9 -1
View File
@@ -120,6 +120,13 @@ bool draw_park_editor(ParkEditorUI &ui, const Model &model, const CallbacksV3 &c
} else
leave(ui, callbacks, visible, escape_closes); // Esc goes straight back to the game
}
// A server that has turned object scaling off shares everything at its own size: nothing is
// resized here either, so what this player sees is what everyone else does.
ui.scaling_allowed = model.multiplayer.object_scaling;
if (!ui.scaling_allowed) {
ui.scale = 1;
if (ui.mode == ParkTransformMode::scale) ui.mode = ParkTransformMode::move;
}
namespace skate = dingosdk::skate_theme;
const float k = std::clamp(ui.ui_scale, .5f, 3.0f);
const auto P = [k](float value) { return value * k; };
@@ -216,6 +223,7 @@ bool draw_park_editor(ParkEditorUI &ui, const Model &model, const CallbacksV3 &c
for (std::size_t i = 0; i < modes.size(); ++i) {
if (i)
ImGui::SameLine(0, P(6));
if (modes[i].second == ParkTransformMode::scale && !ui.scaling_allowed) continue; // the server has it off
if (mode_tile(frame, bold_font, modes[i].first, ui.mode == modes[i].second, k, static_cast<unsigned>(41 + i))) {
ui.mode = modes[i].second;
ui.dragging = -1;
@@ -493,7 +501,7 @@ bool draw_park_editor(ParkEditorUI &ui, const Model &model, const CallbacksV3 &c
ui.mode = ParkTransformMode::move;
if (ImGui::IsKeyPressed(ImGuiKey_R, false))
ui.mode = ParkTransformMode::rotate;
if (ImGui::IsKeyPressed(ImGuiKey_Y, false))
if (ImGui::IsKeyPressed(ImGuiKey_Y, false) && ui.scaling_allowed)
ui.mode = ParkTransformMode::scale;
}
if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_C, false))
+1
View File
@@ -18,6 +18,7 @@ struct ParkEditorUI {
std::string category, placing, feedback, queued_status;
editor::Vec3 position{}, angles{}, drag_start{}, drag_axis{};
editor::Quat drag_rotation{0, 0, 0, 1};
bool scaling_allowed{true}; // off: the server shares objects at their own size, so none is resized here
float scale{1}, grid{.5f}, angle_snap{15}, scale_snap{.1f}, plane_height{}, distance{10},
drag_parameter{}, drag_angle{}, drag_scale{1}, drag_gizmo_scale{1};
ParkTransformMode mode{ParkTransformMode::move};
@@ -194,9 +194,12 @@ void inspector(ParkEditorUI &ui, const Model &model, const CallbacksV3 &callback
ui.inspector_dirty = true;
ImGui::TextUnformatted(ui.selection.size() > 1 ? "Scale multiplier" : "Uniform scale");
ImGui::SetNextItemWidth(-1);
ImGui::BeginDisabled(!ui.scaling_allowed);
if (ImGui::DragFloat("##scale", &ui.scale, .01f, .01f, 100, "%.3f",
ImGuiSliderFlags_AlwaysClamp))
ui.inspector_dirty = true;
ImGui::EndDisabled();
if (!ui.scaling_allowed) ImGui::TextDisabled("This server has object scaling off.");
ImGui::BeginDisabled(!selected && ui.surface_mode && !ui.surface_valid && !ui.inspector_dirty);
skate_theme::push_primary_button();
ImGui::PushFont(font_or(ui.bold_font));
+6
View File
@@ -121,6 +121,11 @@ object_placement everyone, admins (only admins can build), or nobody.
object_limit How many objects each player may have placed, 1-1024
(default 100), or 0 for no limit. Admins are not limited.
A player at the limit deletes one to place another.
allow_object_scaling Let players place objects bigger or smaller than their
own size (default true). false shares every player's
objects at their own size and turns the size controls
off in their park editor. Admins can still resize
theirs. Console: object-scaling on|off.
announce_throwdowns Tell everyone in chat when a throwdown drop is placed
(default true).
@@ -266,6 +271,7 @@ and change voice, distances, placement and kicks from the Multiplayer menu.
distances <full> <half> <half-return> <low>
placement everyone|admins|nobody clear-objects
objects <number>|off How many objects each player may have placed.
object-scaling on|off Whether players may resize the objects they place.
noclip on|off nobail on|off boosts on|off
What players may use (admins always can).
tuning on|off Everyone on the game's own physics tuning.
+11
View File
@@ -192,6 +192,17 @@ int run() {
"A config in sections did not read back as it was written");
std::filesystem::remove_all(folder / "data");
}
// Object scaling: allowed unless turned off, and kept in the file.
{
ServerConfig sized;
check(sized.object_scaling, "Object scaling is not allowed by default");
sized.file = folder / "sized.json";
sized.object_scaling = false;
save_config(sized);
check(!load_config(sized.file).object_scaling && text(sized.file).find("\"allow_object_scaling\": false") != std::string::npos,
"The object scaling setting was not kept");
std::filesystem::remove_all(folder / "data");
}
// The server's chat colours: violet and lavender unless set, red first as written.
{
ServerConfig colours;
+9 -1
View File
@@ -16,7 +16,7 @@ constexpr std::string_view help_text =
"status | net [player] | players | say <text> | msg <player> <text> | msg-party <player> <text> | msg-admins <text> | kick <player> | ban <player or SteamID64> [name] | unban <SteamID64> | bans\n"
"map <name, e.g. San Vansterdam> | maps | name <text> | password <text|off> | welcome <text|off> | listed on|off\n"
"voice on|off | voice-range <50-1000> | distances <full> <half> <half-return> <low> | crowd <n>|off | rate <KB/s> | bone-scale <1-8>|off\n"
"placement everyone|admins|nobody | objects <number>|off | clear-objects | noclip on|off | nobail on|off | boosts on|off | tuning on|off\n"
"placement everyone|admins|nobody | objects <number>|off | object-scaling on|off | clear-objects | noclip on|off | nobail on|off | boosts on|off | tuning on|off\n"
"tpall [player] | tphere <player> | votes [map|kick|tod on|off|<percent>] | vote-cancel\n"
"map-pool [add|remove <map>|clear] | rotation [<minutes>|off]\n"
"park <lot> <layout> | park random | layer-sync on|off | layer <key> default|on|off | tod <time|default>\n"
@@ -438,6 +438,14 @@ std::string Host::command(std::string_view line, std::uint64_t admin) {
return changed(*limit ? "Each player can place up to " + std::to_string(*limit) + " objects. Admins are not limited."
: std::string("Players can place as many objects as they like."));
}
if (name == "object-scaling") {
const auto value = on_off(argument);
if (!value) return std::string("object-scaling on|off (now ") + (config_.object_scaling ? "on" : "off") + ")";
config_.object_scaling = *value;
for (auto &[id, guest] : guests_) guest->shared_from = 0; // look at every layout again
return changed(*value ? "Players can resize the objects they place."
: "Placed objects are their own size for everyone. Admins can still resize theirs.");
}
if (name == "votes") {
// votes | votes <map|kick|tod> on|off|<percent> | votes seconds|cooldown <n>
const auto [what_text, value_text] = split(argument);
+2
View File
@@ -103,6 +103,7 @@ Layout layout(const ServerConfig &c) {
players.set("voice_range", static_cast<double>(c.voice_range));
players.set("object_placement", placement_text(c.object_placement));
players.set("object_limit", c.object_limit);
players.set("allow_object_scaling", c.object_scaling);
players.set("announce_throwdowns", c.announce_throwdowns);
auto &anti_cheat = root.section("anti_cheat");
@@ -256,6 +257,7 @@ ServerConfig load_config(const std::filesystem::path &file, std::vector<std::str
c.object_placement = placement == "nobody" ? ObjectPlacement::nobody
: placement == "admins" || placement == "host" ? ObjectPlacement::host_only : ObjectPlacement::everyone;
c.object_limit = get("players", "object_limit", c.object_limit);
c.object_scaling = get("players", "allow_object_scaling", c.object_scaling);
c.announce_throwdowns = get("players", "announce_throwdowns", c.announce_throwdowns);
c.speed_check = get("anti_cheat", "speed_hack", c.speed_check, {"speed_check"});
+3
View File
@@ -100,6 +100,9 @@ struct ServerConfig {
ObjectPlacement object_placement = ObjectPlacement::everyone;
// Objects each player may have placed (object_placement.h); 0: no limit. Admins are not held to it.
unsigned object_limit = default_object_limit;
// Players may place objects at another size than their own. Off: every player's objects
// are shared at their own size; admins may still resize theirs.
bool object_scaling = true;
// Whether players may use noclip (and teleport) / No Bail / the boosts (admins always may).
bool noclip = true, no_bail = true, boosts = true;
// Players skate with the game's own physics tuning, not copies they edited.
+5
View File
@@ -576,6 +576,7 @@ void Host::send_roster() {
p.distances = config_.distances;
p.object_placement = config_.object_placement;
p.object_limit = config_.object_limit;
p.object_scaling = config_.object_scaling;
p.guest_noclip = config_.noclip;
p.guest_no_bail = config_.no_bail;
p.guest_boosts = config_.boosts;
@@ -1113,6 +1114,10 @@ void Host::sync_objects() {
std::erase_if(layout, [&](const auto &object) { return guest->cleared.contains(object.id); });
// No more of a player's objects than the server allows each of them; admins are not limited.
if (!is_admin(id)) layout = limited_layout(std::move(layout), guest->shared.objects(), config_.object_limit);
// With scaling off, a player's objects reach everyone else at their own size, whatever
// that player's game made of them.
if (!config_.object_scaling && !is_admin(id))
for (auto &object : layout) object.scale = 1.f;
if (config_.activity_log) activity_.objects(id, guest->shared.layout(), layout);
guest->shared.replace(layout);
guest->shared_from = guest->objects.revision();
+1559 -1604
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB