From b47cff28054d28e6468548d066aa75ec56798235 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Tue, 27 Oct 2026 09:58:16 +0100 Subject: [PATCH] Make TechHider hidden regions consistent for spectators Spectators hid HiddenBlocks in the whole arena for block updates, but full chunk packets outside both extend regions were sent unfiltered, so the same blocks were hidden or visible depending on the packet type. Hidden regions are now a list: spectators hide both team extend regions, team members the opposite one. Block checks and the per-chunk bypass use the same regions, so everything outside the extend regions is visible and unfiltered. --- .../commands/TechhiderbugCommand.java | 2 +- .../fightsystem/utils/TechHiderWrapper.java | 56 ++++++++++++------- .../techhider/AccessPrivilegeProvider.java | 7 ++- .../src/de/steamwar/techhider/TechHider.java | 2 +- 4 files changed, 42 insertions(+), 25 deletions(-) diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/TechhiderbugCommand.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/TechhiderbugCommand.java index 5527176b..f4ae7c73 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/TechhiderbugCommand.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/TechhiderbugCommand.java @@ -60,7 +60,7 @@ public class TechhiderbugCommand extends FightSWCommand { writer.append('\n'); writer.append("Hidden regions: "); - FightSystem.getTechHider().getHiddenRegion().forEach((p, r) -> writer.append(p.getName()).append(':').append(r.toString()).append(' ')); + FightSystem.getTechHider().getHiddenRegion().forEach((p, r) -> writer.append(p.getName()).append(':').append(Arrays.toString(r)).append(' ')); writer.append('\n'); writer.append("TinyProtocol: "); diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java index c12fd38b..abc5dd52 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java @@ -51,6 +51,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; +import java.util.Arrays; import java.util.Collection; import java.util.Objects; import java.util.Set; @@ -62,8 +63,10 @@ public class TechHiderWrapper extends StateDependent implements Listener { public static final boolean ENABLED = !Config.OnlyPublicSchematics && !Config.test() && Config.GameModeConfig.Techhider.Active; + private static final Region[] NO_REGIONS = new Region[0]; + @Getter - private final ConcurrentHashMap hiddenRegion = new ConcurrentHashMap<>(); + private final ConcurrentHashMap hiddenRegion = new ConcurrentHashMap<>(); private final HullHider hullHider; public TechHiderWrapper(HullHider hullHider) { @@ -97,14 +100,9 @@ public class TechHiderWrapper extends StateDependent implements Listener { private AccessPrivilegeProvider newProvider(Set blockStatesToObfuscate, Set blocksToObfuscate, Set> blockEntityTypeToObfuscate) { return new AccessPrivilegeProvider() { - @Override - public boolean isEveryonePrivilegedToAccessAllDataWithinChunk(int chunkX, int chunkZ) { - return Fight.teams().stream().map(FightTeam::getExtendRegion).allMatch(region -> region.chunkOutside(chunkX, chunkZ)); - } - @Override public ForPlayer forPlayer(Player p) { - return new FightPlayerAccess(p, getHiddenRegion(p), blockStatesToObfuscate, blocksToObfuscate, blockEntityTypeToObfuscate); + return new FightPlayerAccess(p, getHiddenRegions(p), blockStatesToObfuscate, blocksToObfuscate, blockEntityTypeToObfuscate); } @Override @@ -119,24 +117,37 @@ public class TechHiderWrapper extends StateDependent implements Listener { */ private class FightPlayerAccess implements ForPlayer { private final Player player; - private final Region hiddenRegion; + private final Region[] hiddenRegions; private final Set blockStatesToObfuscate; private final Set blocksToObfuscate; private final Set> blockEntityTypeToObfuscate; - private FightPlayerAccess(Player player, Region hiddenRegion, Set blockStatesToObfuscate, Set blocksToObfuscate, Set> blockEntityTypeToObfuscate) { + private FightPlayerAccess(Player player, Region[] hiddenRegions, Set blockStatesToObfuscate, Set blocksToObfuscate, Set> blockEntityTypeToObfuscate) { this.player = player; - this.hiddenRegion = hiddenRegion; + this.hiddenRegions = hiddenRegions; this.blockStatesToObfuscate = blockStatesToObfuscate; this.blocksToObfuscate = blocksToObfuscate; this.blockEntityTypeToObfuscate = blockEntityTypeToObfuscate; } + private boolean isHidden(int blockX, int blockY, int blockZ) { + for (Region region : hiddenRegions) { + if (region.inRegion(blockX, blockY, blockZ)) return true; + } + return false; + } + @Override public Player getPlayer() { return player; } + @Override + public boolean canAccessAllDataWithinChunk(int chunkX, int chunkZ) { + // Hulls the player may not see lie within the hidden regions, everything outside of them is not critical + return Arrays.stream(hiddenRegions).allMatch(region -> region.chunkOutside(chunkX, chunkZ)); + } + @Override public boolean canAccessPosition(int blockX, int blockY, int blockZ) { return !hullHider.isBlockHidden(player, blockX, blockY, blockZ); @@ -144,12 +155,12 @@ public class TechHiderWrapper extends StateDependent implements Listener { @Override public boolean canAccessBlock(int blockX, int blockY, int blockZ, Block block) { - return !hiddenRegion.inRegion(blockX, blockY, blockZ) || !blocksToObfuscate.contains(block); + return !isHidden(blockX, blockY, blockZ) || !blocksToObfuscate.contains(block); } @Override public boolean canAccessBlockState(int blockX, int blockY, int blockZ, BlockState blockState) { - return !hiddenRegion.inRegion(blockX, blockY, blockZ) || !blockStatesToObfuscate.contains(blockState); + return !isHidden(blockX, blockY, blockZ) || !blockStatesToObfuscate.contains(blockState); } // TODO will require entity tracking on the netty thread to prevent future race conditions @@ -166,7 +177,7 @@ public class TechHiderWrapper extends StateDependent implements Listener { @Override public boolean canAccessBlockEntity(int blockX, int blockY, int blockZ, BlockEntityType type) { - return !hiddenRegion.inRegion(blockX, blockY, blockZ) || !blockEntityTypeToObfuscate.contains(type); + return !isHidden(blockX, blockY, blockZ) || !blockEntityTypeToObfuscate.contains(type); } } @@ -177,19 +188,19 @@ public class TechHiderWrapper extends StateDependent implements Listener { @EventHandler public void teamJoin(TeamSpawnEvent e) { - e.getFightPlayer().ifPlayer(player -> hiddenRegion.put(player, getHiddenRegion(player))); + e.getFightPlayer().ifPlayer(player -> hiddenRegion.put(player, getHiddenRegions(player))); } @EventHandler public void boarding(BoardingEvent e) { - e.getFightPlayer().ifPlayer(player -> hiddenRegion.put(player, getHiddenRegion(player))); + e.getFightPlayer().ifPlayer(player -> hiddenRegion.put(player, getHiddenRegions(player))); } @EventHandler public void teamLeave(TeamLeaveEvent e) { e.getFightPlayer().ifPlayer(player -> { if (player.isOnline()) { - hiddenRegion.put(player, getHiddenRegion(player)); + hiddenRegion.put(player, getHiddenRegions(player)); } }); } @@ -210,16 +221,19 @@ public class TechHiderWrapper extends StateDependent implements Listener { }); } - private Region getHiddenRegion(Player player) { - if (Config.isReferee(player)) return Region.EMPTY; + private Region[] getHiddenRegions(Player player) { + if (Config.isReferee(player)) return NO_REGIONS; // Single lookup: runs on netty threads, the player can leave the team at any time FightPlayer fightPlayer = Fight.getFightPlayer(player); - if (fightPlayer == null) return Config.ArenaRegion; + if (fightPlayer == null) { + // Spectators: both teams, the rest of the arena contains nothing critical + return Fight.teams().stream().map(FightTeam::getExtendRegion).toArray(Region[]::new); + } - if (fightPlayer.canEntern()) return Region.EMPTY; + if (fightPlayer.canEntern()) return NO_REGIONS; - return Fight.getOpposite(fightPlayer.getTeam()).getExtendRegion(); + return new Region[]{Fight.getOpposite(fightPlayer.getTeam()).getExtendRegion()}; } private Stream getWaterloggedBlockStates() { diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/AccessPrivilegeProvider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/AccessPrivilegeProvider.java index 740528a8..38b9b463 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/AccessPrivilegeProvider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/AccessPrivilegeProvider.java @@ -25,8 +25,6 @@ import net.minecraft.world.level.block.state.BlockState; import org.bukkit.entity.Player; public interface AccessPrivilegeProvider { - boolean isEveryonePrivilegedToAccessAllDataWithinChunk(int chunkX, int chunkZ); - /** * Resolves everything that can change concurrently about this player once. * The result is used for all decisions of one outgoing packet (including all packets of a bundle). @@ -41,6 +39,11 @@ public interface AccessPrivilegeProvider { interface ForPlayer { Player getPlayer(); + /** + * @return true if nothing within this chunk is restricted for this player, so the chunk may be sent unmodified + */ + boolean canAccessAllDataWithinChunk(int chunkX, int chunkZ); + boolean canAccessPosition(int blockX, int blockY, int blockZ); boolean canAccessBlock(int blockX, int blockY, int blockZ, Block block); diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java index c24fad71..b4424f30 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java @@ -530,7 +530,7 @@ public class TechHider { } private ClientboundLevelChunkWithLightPacket processChunkWithLight(ForPlayer access, ClientboundLevelChunkWithLightPacket packet) { - if (privilegeProvider.isEveryonePrivilegedToAccessAllDataWithinChunk(packet.getX(), packet.getZ())) { + if (access.canAccessAllDataWithinChunk(packet.getX(), packet.getZ())) { return packet; } else { return chunkHider.processLevelChunkWithLightPacket(access, packet);