From 0ef702da9efa7b1e7c766df0e132388eaa1bd373 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Tue, 27 Oct 2026 09:23:31 +0100 Subject: [PATCH] Hide hulls from players by default in HullHider Hull kept a set of affected players that was only filled by a join listener active in the Schem states, so spectators joining earlier were never hidden from the hull. The set was also a plain HashSet read on netty threads, where a racing lookup meant "not hidden". Invert it into a concurrent set of viewers (referees, own team, boarded players); everyone else is hidden by default. - Hull: viewer UUID set with addViewer/removeViewer/forgetViewer, a null player counts as hidden - Hull: entities are hidden/shown when they enter/leave the hull for all non-viewer online players; uncovered blocks go to non-viewers - HullHider: join/quit handled in a listener active in every state; quit clears the viewer entry - HullHider: TeamDeathEvent re-hides the enemy hull and resends its chunks - FightPlayer: stop the enter countdown before firing TeamDeathEvent so listeners see canEntern() == false --- .../fightsystem/fight/FightPlayer.java | 2 +- .../de/steamwar/fightsystem/utils/Hull.java | 48 ++++++++++++------- .../steamwar/fightsystem/utils/HullHider.java | 37 +++++++++----- 3 files changed, 59 insertions(+), 28 deletions(-) diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightPlayer.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightPlayer.java index 0473f751..e3f2cd20 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightPlayer.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightPlayer.java @@ -69,8 +69,8 @@ public class FightPlayer { public void setOut() { isOut = true; + stopEnternCountdown(); // before the event, so listeners already see canEntern() == false Bukkit.getPluginManager().callEvent(new TeamDeathEvent(this)); - stopEnternCountdown(); ifAI(AI::stop); } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Hull.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Hull.java index 3ac2528d..91fe96c0 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Hull.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Hull.java @@ -34,6 +34,7 @@ import net.minecraft.core.SectionPos; import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket; import net.minecraft.world.level.block.state.BlockState; +import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; @@ -43,6 +44,7 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; import java.util.logging.Level; import java.util.stream.IntStream; @@ -65,7 +67,8 @@ public class Hull { private final HashMap visibilityDirections = new HashMap<>(); // Contains the visible directions of each occluding visible block private final HashSet uncoveredSurface = new HashSet<>(); - private final HashSet players = new HashSet<>(); + // Players that may see the hull contents (viewers). Everybody else is hidden from it (default deny). Concurrent as it is read on netty threads. + private final Set viewers = ConcurrentHashMap.newKeySet(); private final HashSet entities = new HashSet<>(); private final HashSet rentities = new HashSet<>(); @@ -87,27 +90,36 @@ public class Hull { } public boolean blockPrecise(Player player, int chunkX, int chunkY, int chunkZ) { - return players.contains(player) && !region.chunkSectionOutside(chunkX, chunkY, chunkZ); + return !isViewer(player) && !region.chunkSectionOutside(chunkX, chunkY, chunkZ); } public boolean isBlockHidden(Player player, int x, int y, int z) { - return region.inRegion(x, y, z) && players.contains(player) && !visibility.get(((y - region.getMinY()) * region.getSizeZ() + (z - region.getMinZ())) * region.getSizeX() + (x - region.getMinX())); + return region.inRegion(x, y, z) && !isViewer(player) && !visibility.get(((y - region.getMinY()) * region.getSizeZ() + (z - region.getMinZ())) * region.getSizeX() + (x - region.getMinX())); } public boolean isLocationHidden(Player player, Location location) { - return players.contains(player) && region.inRegion(location) && !visibility.get(new IntVector(location).toId(region)); + return !isViewer(player) && region.inRegion(location) && !visibility.get(new IntVector(location).toId(region)); } - public void addPlayer(Player player) { - if (players.add(player)) { - for (Entity entity : entities) { - untrackEntity(player, entity); - } + private boolean isViewer(Player player) { + return player != null && viewers.contains(player.getUniqueId()); + } + + /** + * Hides the hull from this player (removes the viewer), also hiding all entities currently inside it. + */ + public void removeViewer(Player player) { + viewers.remove(player.getUniqueId()); + for (Entity entity : entities) { + if (!player.equals(entity)) untrackEntity(player, entity); } } - public void removePlayer(Player player, boolean activeRemoval) { - if (players.remove(player) && activeRemoval) { + /** + * Allows this player to see the hull (adds a viewer). + */ + public void addViewer(Player player) { + if (viewers.add(player.getUniqueId())) { for (Entity entity : entities) { trackEntity(player, entity); } @@ -115,18 +127,22 @@ public class Hull { } } + public void forgetViewer(Player player) { + viewers.remove(player.getUniqueId()); + } + public void checkEntity(Entity entity) { Location location = entity.getLocation(); if (region.inRegion(location) && !visibility.get(new IntVector(location).toId(region))) { if (entities.add(entity)) { - for (Player player : players) { - untrackEntity(player, entity); + for (Player player : Bukkit.getOnlinePlayers()) { + if (!isViewer(player) && !player.equals(entity)) untrackEntity(player, entity); } } } else { if (entities.remove(entity)) { - for (Player player : players) { - trackEntity(player, entity); + for (Player player : Bukkit.getOnlinePlayers()) { + if (!isViewer(player) && !player.equals(entity)) trackEntity(player, entity); } } } @@ -252,7 +268,7 @@ public class Hull { result = new ClientboundBlockUpdatePacket(new BlockPos(pos.getX(), pos.getY(), pos.getZ()), (BlockState) blockdata[0]); } Object packet = result; - players.forEach(player -> TinyProtocol.instance.sendPacket(player, packet)); + Bukkit.getOnlinePlayers().stream().filter(player -> !isViewer(player)).forEach(player -> TinyProtocol.instance.sendPacket(player, packet)); } } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/HullHider.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/HullHider.java index 58bfce48..b7d5a54a 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/HullHider.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/HullHider.java @@ -21,6 +21,8 @@ package de.steamwar.fightsystem.utils; import de.steamwar.entity.REntity; import de.steamwar.fightsystem.Config; +import de.steamwar.fightsystem.FightSystem; +import de.steamwar.fightsystem.events.TeamDeathEvent; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.FightPlayer; import de.steamwar.fightsystem.fight.FightTeam; @@ -61,6 +63,7 @@ public class HullHider implements Listener { hulls = hullMap.values().toArray(new Hull[0]); + new StateDependentListener(TechHiderWrapper.ENABLED, FightState.All, new MembershipListener()); new StateDependentListener(TechHiderWrapper.ENABLED, FightState.Schem, this); new StateDependentTask(TechHiderWrapper.ENABLED, FightState.Schem, this::onTick, 0, 1); } @@ -75,14 +78,26 @@ public class HullHider implements Listener { hullMap.get(team).fill(visible); } - @EventHandler(priority = EventPriority.HIGH) - public void onJoin(PlayerJoinEvent e) { - updatePlayer(e.getPlayer()); - } + // Registered in every state: players must be hidden from the hulls by default, no matter when they join. + private class MembershipListener implements Listener { + @EventHandler(priority = EventPriority.HIGH) + public void onJoin(PlayerJoinEvent e) { + updatePlayer(e.getPlayer()); + } - @EventHandler - public void onLeave(PlayerQuitEvent e) { - removePlayer(e.getPlayer(), false); + @EventHandler + public void onLeave(PlayerQuitEvent e) { + forgetViewer(e.getPlayer()); + } + + // A dead player loses the right to see the enemy hull again + @EventHandler + public void onTeamDeath(TeamDeathEvent e) { + e.getFightPlayer().ifPlayer(player -> { + updatePlayer(player); + FightSystem.getTechHider().reloadChunks(player, Fight.getOpposite(e.getFightPlayer().getTeam()).getExtendRegion(), Region.EMPTY); + }); + } } public void updatePlayer(Player player) { @@ -92,16 +107,16 @@ public class HullHider implements Listener { FightPlayer fp = Fight.getFightPlayer(player); for (Map.Entry hull : hullMap.entrySet()) { if (Config.isReferee(player) || hull.getKey() == team || (fp != null && fp.canEntern())) { - hull.getValue().removePlayer(player, true); + hull.getValue().addViewer(player); } else { - hull.getValue().addPlayer(player); + hull.getValue().removeViewer(player); } } } - private void removePlayer(Player player, boolean activeRemoval) { + private void forgetViewer(Player player) { for (Hull hull : hulls) { - hull.removePlayer(player, activeRemoval); + hull.forgetViewer(player); } }