From 3140f9bb55b2db75ca413e8503e44adf0a5b80b4 Mon Sep 17 00:00:00 2001 From: copilot Date: Sat, 7 Nov 2026 15:42:58 +0100 Subject: [PATCH] Prevent players from hiding Fog of War layers Clients could toggle fog visibility (and fade alpha); DisplayData synced that to everyone and defeated FoW. Block client set_visible/set_alpha, clamp host-side DisplayData updates, and ignore eye toggles for fog. Co-authored-by: Cursor --- FORK.md | 2 +- linuxinstall/install-local.sh | 0 .../Layers/DrawingProgramLayerListItem.cpp | 25 +++++++++++++++++++ .../Layers/DrawingProgramLayerManagerGUI.cpp | 7 ++++-- 4 files changed, 31 insertions(+), 3 deletions(-) mode change 100644 => 100755 linuxinstall/install-local.sh diff --git a/FORK.md b/FORK.md index efa2328..4f62652 100644 --- a/FORK.md +++ b/FORK.md @@ -33,4 +33,4 @@ Not automatic on import. Choose **Paste Image + Freistellen** from the canvas ri ## Fog of War / layer lock (host) -In the layer panel (host / local file only): mark a leaf layer as **Fog of War layer** (must sit **above** the content it covers), then paint fog with brush/shapes on that layer. Players receive a **stripped** world — only objects **fully** covered by opaque fog (not merely overlapping) are omitted from join sync and from their save. Partially covered images stay. Moving or reshaping fog (or content under it) live-updates clients: newly covered objects are unloaded, revealed ones are sent again with their resources. The host file stays complete. Fog draws at the layer’s real alpha (1.0 = fully opaque for host and players). **Lock for players** blocks client edits on a normal layer; fog layers are always locked for players (move/edit/erase rejected on the host). +In the layer panel (host / local file only): mark a leaf layer as **Fog of War layer** (must sit **above** the content it covers), then paint fog with brush/shapes on that layer. Players receive a **stripped** world — only objects **fully** covered by opaque fog (not merely overlapping) are omitted from join sync and from their save. Partially covered images stay. Moving or reshaping fog (or content under it) live-updates clients: newly covered objects are unloaded, revealed ones are sent again with their resources. The host file stays complete. Fog draws at the layer’s real alpha (1.0 = fully opaque for host and players). **Lock for players** blocks client edits on a normal layer; fog layers are always locked for players (move/edit/erase rejected on the host). Players also **cannot hide or fade** a fog layer (eye / alpha); only the host can toggle fog visibility, and the host rejects client DisplayData that would strip FoW. diff --git a/linuxinstall/install-local.sh b/linuxinstall/install-local.sh old mode 100644 new mode 100755 diff --git a/src/DrawingProgram/Layers/DrawingProgramLayerListItem.cpp b/src/DrawingProgram/Layers/DrawingProgramLayerListItem.cpp index 2937aa6..6c5d419 100644 --- a/src/DrawingProgram/Layers/DrawingProgramLayerListItem.cpp +++ b/src/DrawingProgram/Layers/DrawingProgramLayerListItem.cpp @@ -20,6 +20,7 @@ #include "DrawingProgramLayerFolder.hpp" #include #include "DrawingProgramLayer.hpp" +#include "FogStripper.hpp" #include "../../World.hpp" #include "../../MainProgram.hpp" #include "SerializedBlendMode.hpp" @@ -258,6 +259,9 @@ const std::string& DrawingProgramLayerListItem::get_name() const { } void DrawingProgramLayerListItem::set_alpha(DrawingProgramLayerManager& layerMan, float newAlpha) const { + // Players must not fade fog out — same authority as visibility. + if(is_fog_layer() && !FogStripper::world_is_host(layerMan.drawP.world)) + return; if(displayData && displayData->alpha != newAlpha) { displayData->alpha = newAlpha; layerMan.drawP.drawCache.clear_own_cached_surfaces(); @@ -270,6 +274,9 @@ float DrawingProgramLayerListItem::get_alpha() const { } void DrawingProgramLayerListItem::set_visible(DrawingProgramLayerManager& layerMan, bool newVisible) const { + // Fog of War is host-controlled; clients hiding it would sync and strip FoW for everyone. + if(is_fog_layer() && !FogStripper::world_is_host(layerMan.drawP.world)) + return; if(displayData && displayData->visible != newVisible) { displayData->visible = newVisible; layerMan.drawP.drawCache.clear_own_cached_surfaces(); @@ -393,6 +400,24 @@ void DrawingProgramLayerListItem::register_class(World& w) { } }); w.delayedUpdateObjectManager.register_class(w.netObjMan, NetworkingObjects::DelayUpdateSerializedClassManager::CustomConstructors{ + .readUpdate = [](DisplayData& o, cereal::PortableBinaryInputArchive& a, const std::shared_ptr& c) { + DisplayData incoming; + a(incoming); + // Client→host: never allow stripping fog, unlocking, hiding, or fading fog. + if(c) { + if(o.fogLayer || incoming.fogLayer) { + incoming.fogLayer = true; + incoming.playerLocked = true; + incoming.visible = true; + incoming.alpha = o.alpha; + } + else { + incoming.fogLayer = false; + incoming.playerLocked = o.playerLocked; + } + } + o = incoming; + }, .postUpdateFunc = [&](DisplayData& o) { (void)o; w.drawProg.drawCache.clear_own_cached_surfaces(); diff --git a/src/DrawingProgram/Layers/DrawingProgramLayerManagerGUI.cpp b/src/DrawingProgram/Layers/DrawingProgramLayerManagerGUI.cpp index 30f4bfd..8d44030 100644 --- a/src/DrawingProgram/Layers/DrawingProgramLayerManagerGUI.cpp +++ b/src/DrawingProgram/Layers/DrawingProgramLayerManagerGUI.cpp @@ -144,11 +144,14 @@ void DrawingProgramLayerManagerGUI::setup_list_gui() { }); } gui.set_z_index_keep_clipping_region(gui.get_z_index() + 1, [&] { + // Fog stays visible for players — eye is informational only on clients. + const bool fogVisibilityLocked = layer->is_fog_layer() && !FogStripper::world_is_host(world); svg_icon_button(gui, "visible button", layer->get_visible() ? "data/icons/eyeopen.svg" : "data/icons/eyeclose.svg", { .drawType = SelectableButton::DrawType::TRANSPARENT_ALL, .size = TreeListing::ENTRY_HEIGHT, - .onClick = [&, layer] { - layer->set_visible(layerMan, !layer->get_visible()); + .onClick = [&, layer, fogVisibilityLocked] { + if(!fogVisibilityLocked) + layer->set_visible(layerMan, !layer->get_visible()); } }); });