Prevent players from hiding Fog of War layers
Some checks are pending
Windows Release / windows-x86_64 (push) Waiting to run

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 <cursoragent@cursor.com>
This commit is contained in:
copilot 2026-11-07 15:42:58 +01:00
parent fea392b65d
commit 3140f9bb55
4 changed files with 31 additions and 3 deletions

View file

@ -20,6 +20,7 @@
#include "DrawingProgramLayerFolder.hpp"
#include <cereal/types/string.hpp>
#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<DisplayData>(w.netObjMan, NetworkingObjects::DelayUpdateSerializedClassManager::CustomConstructors<DisplayData>{
.readUpdate = [](DisplayData& o, cereal::PortableBinaryInputArchive& a, const std::shared_ptr<NetServer::ClientData>& 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();

View file

@ -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());
}
});
});