From 0ef702da9efa7b1e7c766df0e132388eaa1bd373 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Tue, 27 Oct 2026 09:23:31 +0100 Subject: [PATCH 1/9] 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); } } From 4943682a66d8df6b085b374884c4df48f044cee6 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Tue, 27 Oct 2026 09:24:45 +0100 Subject: [PATCH 2/9] Filter entity event packets by entity id in TechHider The processor for ClientboundEntityEventPacket used getEventId(), the status byte (2 = hurt, 3 = death, ...), as the entity id. Events of hidden entities therefore passed the filter, and events of visible entities could be dropped when an unrelated hidden entity had the same id. Use the entityId field instead. --- .../SpigotCore_Main/src/de/steamwar/techhider/TechHider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java index 73db4439..7f432b96 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java @@ -202,7 +202,7 @@ public class TechHider { // 7.1.3 Entity Animation: entity id based signal, keep blocked until entity visibility is modeled. processors.put(ClientboundAnimatePacket.class, this.buildEntityPacketProcessor(ClientboundAnimatePacket::getId)); // 7.1.35 Entity Event: entity id based signal. - processors.put(ClientboundEntityEventPacket.class, this.buildEntityPacketProcessor(ClientboundEntityEventPacket::getEventId)); + processors.put(ClientboundEntityEventPacket.class, this.buildEntityPacketProcessor(packet -> packet.entityId)); // 7.1.36 Teleport Entity: entity id and absolute position. processors.put(ClientboundTeleportEntityPacket.class, this.buildEntityPacketProcessor(ClientboundTeleportEntityPacket::id)); // 7.1.42 Hurt Animation: entity id based signal. From 8381cacdf8ec3d09d025cd39bd6f63189361d71d Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Tue, 27 Oct 2026 09:28:05 +0100 Subject: [PATCH 3/9] Avoid NPE race when computing the TechHider hidden region getHiddenRegion looked the player up in FightTeam.players twice (team, then FightPlayer) on netty threads. A concurrent reset or removePlayer made the second lookup return null, the NPE was swallowed by the global filter and the packet was dropped. Resolve the FightPlayer once and derive team and canEntern from it. --- .../steamwar/fightsystem/utils/TechHiderWrapper.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 7c4c9d77..e614cda7 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java @@ -25,6 +25,7 @@ import de.steamwar.fightsystem.events.BoardingEvent; import de.steamwar.fightsystem.events.TeamLeaveEvent; import de.steamwar.fightsystem.events.TeamSpawnEvent; import de.steamwar.fightsystem.fight.Fight; +import de.steamwar.fightsystem.fight.FightPlayer; import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependent; @@ -178,12 +179,13 @@ public class TechHiderWrapper extends StateDependent implements Listener { private Region getHiddenRegion(Player player) { if (Config.isReferee(player)) return Region.EMPTY; - FightTeam team = Fight.getPlayerTeam(player); - if (team == null) return Config.ArenaRegion; + // 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 (team.canPlayerEntern(player)) return Region.EMPTY; + if (fightPlayer.canEntern()) return Region.EMPTY; - return Fight.getOpposite(team).getExtendRegion(); + return Fight.getOpposite(fightPlayer.getTeam()).getExtendRegion(); } private Stream getWaterloggedBlockStates() { From 7cd2e540efe0538e2a6f8d4cef6adba8efd14cb0 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Tue, 27 Oct 2026 09:50:59 +0100 Subject: [PATCH 4/9] Make TechHider access checks player-scoped AccessPrivilegeProvider#forPlayer returns a PlayerAccess that resolves the player's hidden region once. TechHider creates it once per outer packet (shared across bundle sub-packets) and passes it to every processor, so all decisions within a packet are consistent and the region/referee lookup no longer runs per block on the netty thread. --- .../src/de/steamwar/fightsystem/Config.java | 12 +- .../fightsystem/utils/TechHiderWrapper.java | 96 ++++++++---- .../techhider/AccessPrivilegeProvider.java | 31 ++-- .../src/de/steamwar/techhider/ChunkHider.java | 22 +-- .../src/de/steamwar/techhider/TechHider.java | 147 ++++++++++-------- 5 files changed, 192 insertions(+), 116 deletions(-) diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/Config.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/Config.java index c8a5883c..13f7e18a 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/Config.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/Config.java @@ -34,9 +34,11 @@ import org.bukkit.util.Vector; import java.io.File; import java.util.Collections; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.logging.Level; +import java.util.stream.Collectors; public class Config { @@ -80,7 +82,7 @@ public class Config { //event parameter public static final EventFight EventKampf; - private static final Set Referees; + private static final Set Referees; public static final int EventTeamBlueID; public static final int EventTeamRedID; public static final boolean BothTeamsPublic; @@ -237,7 +239,11 @@ public class Config { BothTeamsPublic = EventTeamRedID == 0 && EventTeamBlueID == 0; SpectatePort = EventKampf.getSpectatePort() == null ? 0 : EventKampf.getSpectatePort(); LiveReplay = SpectatePort != 0; - Referees = Referee.get(Config.EventKampf.getEventID()); + Referees = Referee.get(Config.EventKampf.getEventID()).stream() + .map(SteamwarUser::byId) + .filter(Objects::nonNull) + .map(SteamwarUser::getUUID) + .collect(Collectors.toUnmodifiableSet()); Event event = Event.byId(EventKampf.getEventID()); if (BothTeamsPublic) { @@ -305,6 +311,6 @@ public class Config { } public static boolean isReferee(Player player) { - return Referees.contains(SteamwarUser.get(player.getUniqueId()).getId()); + return Referees.contains(player.getUniqueId()); } } 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 e614cda7..c12fd38b 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java @@ -31,6 +31,7 @@ import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependent; import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.techhider.AccessPrivilegeProvider; +import de.steamwar.techhider.AccessPrivilegeProvider.ForPlayer; import de.steamwar.techhider.TechHider; import lombok.Getter; import net.minecraft.core.Holder; @@ -91,49 +92,82 @@ public class TechHiderWrapper extends StateDependent implements Listener { .map(Holder.Reference::value) .collect(Collectors.toUnmodifiableSet()); - new TechHider(CraftMagicNumbers.getBlock(Config.GameModeConfig.Techhider.ObfuscateWith), new AccessPrivilegeProvider() { + new TechHider(CraftMagicNumbers.getBlock(Config.GameModeConfig.Techhider.ObfuscateWith), newProvider(blockStatesToObfuscate, blocksToObfuscate, blockEntityTypeToObfuscate)); + } + + 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 boolean isPlayerPrivilegedToAccessPosition(Player p, int blockX, int blockY, int blockZ) { - return !hullHider.isBlockHidden(p, blockX, blockY, blockZ); - } - - @Override - public boolean isPlayerPrivilegedToAccessBlock(Player p, int blockX, int blockY, int blockZ, Block block) { - return !getHiddenRegion(p).inRegion(blockX, blockY, blockZ) || !blocksToObfuscate.contains(block); - } - - @Override - public boolean isPlayerPrivilegedToAccessBlockState(Player p, int blockX, int blockY, int blockZ, BlockState blockState) { - return !getHiddenRegion(p).inRegion(blockX, blockY, blockZ) || !blockStatesToObfuscate.contains(blockState); - } - - // TODO will require entity tracking on the netty thread to prevent future race conditions - @Override - public boolean isPlayerPrivilegedToAccessEntity(Player p, int entityId) { - net.minecraft.world.entity.Entity nmsEntity = ((CraftWorld) p.getWorld()).getHandle().moonrise$getEntityLookup().get(entityId); - - if (nmsEntity != null) { - return !hullHider.isBlockHidden(p, nmsEntity.getBlockX(), nmsEntity.getBlockY(), nmsEntity.getBlockZ()); - } else { - return true; - } - } - - @Override - public boolean isPlayerPrivilegedToAccessBlockEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType type) { - return !getHiddenRegion(p).inRegion(blockX, blockY, blockZ) || !blockEntityTypeToObfuscate.contains(type); + public ForPlayer forPlayer(Player p) { + return new FightPlayerAccess(p, getHiddenRegion(p), blockStatesToObfuscate, blocksToObfuscate, blockEntityTypeToObfuscate); } @Override public boolean isPlayerPrivilegedToPerformAction(Player p) { return p.getGameMode() != GameMode.SPECTATOR; } - }); + }; + } + + /** + * Snapshot of what one player may access, taken once per outgoing packet on the netty thread. + */ + private class FightPlayerAccess implements ForPlayer { + private final Player player; + private final Region hiddenRegion; + private final Set blockStatesToObfuscate; + private final Set blocksToObfuscate; + private final Set> blockEntityTypeToObfuscate; + + private FightPlayerAccess(Player player, Region hiddenRegion, Set blockStatesToObfuscate, Set blocksToObfuscate, Set> blockEntityTypeToObfuscate) { + this.player = player; + this.hiddenRegion = hiddenRegion; + this.blockStatesToObfuscate = blockStatesToObfuscate; + this.blocksToObfuscate = blocksToObfuscate; + this.blockEntityTypeToObfuscate = blockEntityTypeToObfuscate; + } + + @Override + public Player getPlayer() { + return player; + } + + @Override + public boolean canAccessPosition(int blockX, int blockY, int blockZ) { + return !hullHider.isBlockHidden(player, blockX, blockY, blockZ); + } + + @Override + public boolean canAccessBlock(int blockX, int blockY, int blockZ, Block block) { + return !hiddenRegion.inRegion(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); + } + + // TODO will require entity tracking on the netty thread to prevent future race conditions + @Override + public boolean canAccessEntity(int entityId) { + net.minecraft.world.entity.Entity nmsEntity = ((CraftWorld) player.getWorld()).getHandle().moonrise$getEntityLookup().get(entityId); + + if (nmsEntity != null) { + return !hullHider.isBlockHidden(player, nmsEntity.getBlockX(), nmsEntity.getBlockY(), nmsEntity.getBlockZ()); + } else { + return true; + } + } + + @Override + public boolean canAccessBlockEntity(int blockX, int blockY, int blockZ, BlockEntityType type) { + return !hiddenRegion.inRegion(blockX, blockY, blockZ) || !blockEntityTypeToObfuscate.contains(type); + } } @Override diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/AccessPrivilegeProvider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/AccessPrivilegeProvider.java index b04748c6..740528a8 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/AccessPrivilegeProvider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/AccessPrivilegeProvider.java @@ -27,15 +27,28 @@ import org.bukkit.entity.Player; public interface AccessPrivilegeProvider { boolean isEveryonePrivilegedToAccessAllDataWithinChunk(int chunkX, int chunkZ); - boolean isPlayerPrivilegedToAccessPosition(Player p, int blockX, int blockY, int blockZ); - - boolean isPlayerPrivilegedToAccessBlock(Player p, int blockX, int blockY, int blockZ, Block block); - - boolean isPlayerPrivilegedToAccessBlockState(Player p, int blockX, int blockY, int blockZ, BlockState blockState); - - boolean isPlayerPrivilegedToAccessEntity(Player p, int entityId); - - boolean isPlayerPrivilegedToAccessBlockEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType type); + /** + * 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). + */ + ForPlayer forPlayer(Player p); boolean isPlayerPrivilegedToPerformAction(Player p); + + /** + * What a single player may access, valid for the processing of one outgoing packet. + */ + interface ForPlayer { + Player getPlayer(); + + boolean canAccessPosition(int blockX, int blockY, int blockZ); + + boolean canAccessBlock(int blockX, int blockY, int blockZ, Block block); + + boolean canAccessBlockState(int blockX, int blockY, int blockZ, BlockState blockState); + + boolean canAccessEntity(int entityId); + + boolean canAccessBlockEntity(int blockX, int blockY, int blockZ, BlockEntityType type); + } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java index bd21f357..caa9c96b 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java @@ -19,6 +19,7 @@ package de.steamwar.techhider; +import de.steamwar.techhider.AccessPrivilegeProvider.ForPlayer; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; @@ -44,11 +45,9 @@ public class ChunkHider { private final int BIOMES_PER_SECTION = 64; private final int blockIdUsedForHiding; - private final AccessPrivilegeProvider accessPrivilegeProvider; - public ChunkHider(Block blockUsedForObfuscation, AccessPrivilegeProvider accessPrivilegeProvider) { + public ChunkHider(Block blockUsedForObfuscation) { blockIdUsedForHiding = Block.BLOCK_STATE_REGISTRY.getId(blockUsedForObfuscation.defaultBlockState()); - this.accessPrivilegeProvider = accessPrivilegeProvider; } private int getLongsRequiredToEncodeEntries(int bitsPerEntry, int entryCount) { @@ -69,7 +68,8 @@ public class ChunkHider { return dataArray; } - public ClientboundLevelChunkWithLightPacket processLevelChunkWithLightPacket(Player player, ClientboundLevelChunkWithLightPacket packet) { + public ClientboundLevelChunkWithLightPacket processLevelChunkWithLightPacket(ForPlayer access, ClientboundLevelChunkWithLightPacket packet) { + Player player = access.getPlayer(); int chunkX = packet.getX(); int chunkZ = packet.getZ(); ClientboundLevelChunkPacketData chunkData = packet.getChunkData(); @@ -104,7 +104,7 @@ public class ChunkHider { resolvedData[i] = pallet[palletReference]; } - int[] obfuscatedBlockData = obfuscateBlockDataArray(player, chunkX, chunkZ, yOffset, resolvedData); + int[] obfuscatedBlockData = obfuscateBlockDataArray(access, chunkX, chunkZ, yOffset, resolvedData); long[] reEncodedData = encodeDirectBlockDataArray(obfuscatedBlockData); @@ -149,7 +149,7 @@ public class ChunkHider { int[] blockData = resolveDirectBlockDataArray(rawData, bitsPerBlock); - int[] obfuscatedBlockData = obfuscateBlockDataArray(player, chunkX, chunkZ, yOffset, blockData); + int[] obfuscatedBlockData = obfuscateBlockDataArray(access, chunkX, chunkZ, yOffset, blockData); long[] reEncodedData = encodeDirectBlockDataArray(obfuscatedBlockData); @@ -172,13 +172,13 @@ public class ChunkHider { out.readBytes(data); List blockEntities = chunkData.blockEntitiesData; - List filteredBlockEntities = filterBlockEntities(player, blockEntities, chunkX, chunkZ); + List filteredBlockEntities = filterBlockEntities(access, blockEntities, chunkX, chunkZ); return buildNewChunkPacket(packet, data, filteredBlockEntities); } - private int[] obfuscateBlockDataArray(Player player, int chunkX, int chunkZ, int yOffset, int[] blockDataArray) { + private int[] obfuscateBlockDataArray(ForPlayer access, int chunkX, int chunkZ, int yOffset, int[] blockDataArray) { int[] obfuscatedData = new int[BLOCKS_PER_SECTION]; for (int sectionY = 0; sectionY < SECTION_SPAN_SIZE; sectionY++) { for (int sectionZ = 0; sectionZ < SECTION_SPAN_SIZE; sectionZ++) { @@ -194,7 +194,7 @@ public class ChunkHider { Block block = blockState.getBlock(); - if (accessPrivilegeProvider.isPlayerPrivilegedToAccessPosition(player, worldX, worldY, worldZ) && accessPrivilegeProvider.isPlayerPrivilegedToAccessBlock(player, worldX, worldY, worldZ, block) && accessPrivilegeProvider.isPlayerPrivilegedToAccessBlockState(player, worldX, worldY, worldZ, blockState)) { + if (access.canAccessPosition(worldX, worldY, worldZ) && access.canAccessBlock(worldX, worldY, worldZ, block) && access.canAccessBlockState(worldX, worldY, worldZ, blockState)) { obfuscatedData[blockDataIndex] = blockId; } else { obfuscatedData[blockDataIndex] = blockIdUsedForHiding; @@ -241,7 +241,7 @@ public class ChunkHider { return clonedPacket; } - private List filterBlockEntities(Player player, List blockEntities, int chunkX, int chunkZ) { + private List filterBlockEntities(ForPlayer access, List blockEntities, int chunkX, int chunkZ) { int fourBitBitmask = 0b0000_1111; return blockEntities.stream() @@ -257,7 +257,7 @@ public class ChunkHider { int worldY = blockEntityInfo.y; - return accessPrivilegeProvider.isPlayerPrivilegedToAccessPosition(player, worldX, worldY, worldZ) && accessPrivilegeProvider.isPlayerPrivilegedToAccessBlockEntity(player, worldX, worldY, worldZ, type); + return access.canAccessPosition(worldX, worldY, worldZ) && access.canAccessBlockEntity(worldX, worldY, worldZ, type); }).toList(); } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java index 7f432b96..c24fad71 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java @@ -19,6 +19,7 @@ package de.steamwar.techhider; +import de.steamwar.techhider.AccessPrivilegeProvider.ForPlayer; import com.comphenix.tinyprotocol.TinyProtocol; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; @@ -54,7 +55,7 @@ import java.util.function.ToIntFunction; */ public class TechHider { private final Set> bypassingPackets; - private final Map>, BiFunction, Packet>> packetProcessors; + private final Map>, BiFunction, Packet>> packetProcessors; private final BlockState blockStateUsedForObfuscation; private final ChunkHider chunkHider; @@ -65,7 +66,7 @@ public class TechHider { public TechHider(Block blockUsedForObfuscation, AccessPrivilegeProvider privilegeProvider) { this.blockStateUsedForObfuscation = blockUsedForObfuscation.defaultBlockState(); - this.chunkHider = new ChunkHider(blockUsedForObfuscation, privilegeProvider); + this.chunkHider = new ChunkHider(blockUsedForObfuscation); this.privilegeProvider = privilegeProvider; this.bypassingPackets = new HashSet<>(List.of( @@ -193,12 +194,12 @@ public class TechHider { ClientboundContainerSetSlotPacket.class // 7.1.21 Set Container Slot )); - BiFunction, Packet> tossPacket = (p, packet) -> null; - Map>, BiFunction, Packet>> processors = new HashMap<>(); + BiFunction, Packet> tossPacket = (access, packet) -> null; + Map>, BiFunction, Packet>> processors = new HashMap<>(); // --- Entity packets - // 7.1.2 Spawn Entity: entity type and position can reveal hidden contraptions. - processors.put(ClientboundAddEntityPacket.class, (p, packet) -> this.processAddEntityPacket(p, (ClientboundAddEntityPacket) packet)); + processors.put(ClientboundAddEntityPacket.class, (access, packet) -> this.processAddEntityPacket(access, (ClientboundAddEntityPacket) packet)); // 7.1.3 Entity Animation: entity id based signal, keep blocked until entity visibility is modeled. processors.put(ClientboundAnimatePacket.class, this.buildEntityPacketProcessor(ClientboundAnimatePacket::getId)); // 7.1.35 Entity Event: entity id based signal. @@ -234,43 +235,43 @@ public class TechHider { // 7.1.67 Combat Death: entity/player ids and death message context. processors.put(ClientboundPlayerCombatKillPacket.class, this.buildEntityPacketProcessor(ClientboundPlayerCombatKillPacket::playerId)); // 7.1.52/53/55 Update Entity Position/Rotation: entity id and movement signal. - processors.put(ClientboundMoveEntityPacket.class, (p, packet) -> this.processMoveEntityPacket(p, (ClientboundMoveEntityPacket) packet)); - processors.put(ClientboundMoveEntityPacket.Pos.class, (p, packet) -> this.processMoveEntityPacket(p, (ClientboundMoveEntityPacket.Pos) packet)); - processors.put(ClientboundMoveEntityPacket.Rot.class, (p, packet) -> this.processMoveEntityPacket(p, (ClientboundMoveEntityPacket.Rot) packet)); - processors.put(ClientboundMoveEntityPacket.PosRot.class, (p, packet) -> this.processMoveEntityPacket(p, (ClientboundMoveEntityPacket.PosRot) packet)); + processors.put(ClientboundMoveEntityPacket.class, (access, packet) -> this.processMoveEntityPacket(access, (ClientboundMoveEntityPacket) packet)); + processors.put(ClientboundMoveEntityPacket.Pos.class, (access, packet) -> this.processMoveEntityPacket(access, (ClientboundMoveEntityPacket.Pos) packet)); + processors.put(ClientboundMoveEntityPacket.Rot.class, (access, packet) -> this.processMoveEntityPacket(access, (ClientboundMoveEntityPacket.Rot) packet)); + processors.put(ClientboundMoveEntityPacket.PosRot.class, (access, packet) -> this.processMoveEntityPacket(access, (ClientboundMoveEntityPacket.PosRot) packet)); // 7.1.82 Set Head Rotation: entity id and rotation. - processors.put(ClientboundRotateHeadPacket.class, (p, packet) -> this.processRotateHeadPacket(p, (ClientboundRotateHeadPacket) packet)); + processors.put(ClientboundRotateHeadPacket.class, (access, packet) -> this.processRotateHeadPacket(access, (ClientboundRotateHeadPacket) packet)); // 7.1.76 Remove Entities: entity id visibility side channel. - processors.put(ClientboundRemoveEntitiesPacket.class, (p, packet) -> this.processRemoveEntitiesPacket(p, (ClientboundRemoveEntitiesPacket) packet)); + processors.put(ClientboundRemoveEntitiesPacket.class, (access, packet) -> this.processRemoveEntitiesPacket(access, (ClientboundRemoveEntitiesPacket) packet)); // 7.1.99 Link Entities: entity relationship signal. - processors.put(ClientboundSetEntityLinkPacket.class, (p, packet) -> this.processSetEntityLinkPacket(p, (ClientboundSetEntityLinkPacket) packet)); + processors.put(ClientboundSetEntityLinkPacket.class, (access, packet) -> this.processSetEntityLinkPacket(access, (ClientboundSetEntityLinkPacket) packet)); // --- Block entity packets --- // 7.1.7 Block Entity Data: currently filtered by block position and allowed tile-entity actions. - processors.put(ClientboundBlockEntityDataPacket.class, (p, packet) -> processBlockEntityDataPacket(p, (ClientboundBlockEntityDataPacket) packet)); + processors.put(ClientboundBlockEntityDataPacket.class, (access, packet) -> processBlockEntityDataPacket(access, (ClientboundBlockEntityDataPacket) packet)); // 7.1.122 Tag Query Response: block-entity query result. processors.put(ClientboundTagQueryPacket.class, tossPacket); // --- Block packets --- // 7.1.6 Set Block Destroy Stage: block position and mining progress can reveal activity. - processors.put(ClientboundBlockDestructionPacket.class, (p, packet) -> proccessBlockDestructionPacket(p, (ClientboundBlockDestructionPacket) packet)); + processors.put(ClientboundBlockDestructionPacket.class, (access, packet) -> proccessBlockDestructionPacket(access, (ClientboundBlockDestructionPacket) packet)); // 7.1.8 Block Action: currently filtered by block position. - processors.put(ClientboundBlockEventPacket.class, (p, packet) -> processBlockEventPacket(p, (ClientboundBlockEventPacket) packet)); + processors.put(ClientboundBlockEventPacket.class, (access, packet) -> processBlockEventPacket(access, (ClientboundBlockEventPacket) packet)); // 7.1.9 Block Update: currently filtered/obfuscated by block position and block id. - processors.put(ClientboundBlockUpdatePacket.class, (p, packet) -> processBlockUpdatePacket(p, (ClientboundBlockUpdatePacket) packet)); + processors.put(ClientboundBlockUpdatePacket.class, (access, packet) -> processBlockUpdatePacket(access, (ClientboundBlockUpdatePacket) packet)); // --- Chunk packets --- // 7.1.45 Chunk Data and Update Light: currently filtered/obfuscated by chunk data processor. - processors.put(ClientboundLevelChunkWithLightPacket.class, (p, packet) -> processChunkWithLight(p, (ClientboundLevelChunkWithLightPacket) packet)); + processors.put(ClientboundLevelChunkWithLightPacket.class, (access, packet) -> processChunkWithLight(access, (ClientboundLevelChunkWithLightPacket) packet)); // --- Section packets --- // 7.1.83 Update Section Blocks: currently filtered/obfuscated by section block processor. - processors.put(ClientboundSectionBlocksUpdatePacket.class, (p, packet) -> processSectionUpdate(p, (ClientboundSectionBlocksUpdatePacket) packet)); + processors.put(ClientboundSectionBlocksUpdatePacket.class, (access, packet) -> processSectionUpdate(access, (ClientboundSectionBlocksUpdatePacket) packet)); // --- Particle packets --- // 7.1.47 Particle: particle type and position can reveal hidden machinery. - processors.put(ClientboundLevelParticlesPacket.class, (p, packet) -> processLevelParticlesPacket(p, (ClientboundLevelParticlesPacket) packet)); + processors.put(ClientboundLevelParticlesPacket.class, (access, packet) -> processLevelParticlesPacket(access, (ClientboundLevelParticlesPacket) packet)); // --- Sound packets --- // 7.1.115 Entity Sound Effect: entity id and sound. @@ -290,7 +291,7 @@ public class TechHider { // 7.1.59 Open Sign Editor: block position. processors.put(ClientboundOpenSignEditorPacket.class, buildPositionBasedPacketProcessor(ClientboundOpenSignEditorPacket::getPos)); // 7.1.37 Explosion: position, affected blocks, and knockback can reveal TNT/tech. - processors.put(ClientboundExplodePacket.class, (p, rawPacket) -> { + processors.put(ClientboundExplodePacket.class, (access, rawPacket) -> { ClientboundExplodePacket packet = (ClientboundExplodePacket) rawPacket; Vec3 pos = packet.center(); int blockX = (int) pos.x; @@ -298,18 +299,21 @@ public class TechHider { int blockZ = (int) pos.z; - return proccessPositionBasedPacket(p, blockX, blockY, blockZ, packet); + return proccessPositionBasedPacket(access, blockX, blockY, blockZ, packet); }); this.packetProcessors = processors; TinyProtocol.instance.addGlobalClientboundFilter((player, rawPacket) -> { try { + // Resolved lazily (login packets arrive before the player is known) and once per outer packet + AccessHolder access = new AccessHolder(player); + if (rawPacket instanceof ClientboundBundlePacket bundlePacket) { List> processedPackets = new ArrayList<>(); for (Packet packet : bundlePacket.subPackets()) { - Packet processedPacket = (Packet) processPacket(player, packet); + Packet processedPacket = (Packet) processPacket(access, packet); if (processedPacket != null) { processedPackets.add(processedPacket); @@ -318,7 +322,7 @@ public class TechHider { return new ClientboundBundlePacket(processedPackets); } else { - return processPacket(player, rawPacket); + return processPacket(access, rawPacket); } } catch (Throwable t) { t.printStackTrace(); @@ -330,89 +334,108 @@ public class TechHider { TinyProtocol.instance.addFilter(ServerboundInteractPacket.class, (p, packet) -> privilegeProvider.isPlayerPrivilegedToPerformAction(p) ? packet : null); } - private Packet processPacket(Player player, Packet packet) { + private final class AccessHolder { + private final Player player; + private ForPlayer access; + + private AccessHolder(Player player) { + this.player = player; + } + + private ForPlayer get() { + if (access == null) { + access = privilegeProvider.forPlayer(player); + } + return access; + } + } + + private Packet processPacket(AccessHolder access, Packet packet) { if (bypassingPackets.contains(packet.getClass())) { return packet; - } else if (packetProcessors.containsKey(packet.getClass())) { - return packetProcessors.get(packet.getClass()).apply(player, packet); + } + + BiFunction, Packet> processor = packetProcessors.get(packet.getClass()); + if (processor != null) { + return processor.apply(access.get(), packet); } else { return null; } } - private Packet processAddEntityPacket(Player player, ClientboundAddEntityPacket packet) { - if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, packet.getId()) && privilegeProvider.isPlayerPrivilegedToAccessPosition(player, (int) packet.getX(), (int) packet.getY(), (int) packet.getZ())) { + private Packet processAddEntityPacket(ForPlayer access, ClientboundAddEntityPacket packet) { + if (access.canAccessEntity(packet.getId()) && access.canAccessPosition((int) packet.getX(), (int) packet.getY(), (int) packet.getZ())) { return packet; } else { return null; } } - private Packet processEntityPacket(Player player, int entityId, Packet packet) { - if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, entityId)) { + private Packet processEntityPacket(ForPlayer access, int entityId, Packet packet) { + if (access.canAccessEntity(entityId)) { return packet; } else { return null; } } - private > BiFunction, Packet> buildEntityPacketProcessor(ToIntFunction entityIdExtractor) { - return (p, rawPacket) -> { + private > BiFunction, Packet> buildEntityPacketProcessor(ToIntFunction entityIdExtractor) { + return (access, rawPacket) -> { TTargetPacket packet = (TTargetPacket) rawPacket; - return processEntityPacket(p, entityIdExtractor.applyAsInt(packet), packet); + return processEntityPacket(access, entityIdExtractor.applyAsInt(packet), packet); }; } - private Packet processMoveEntityPacket(Player player, ClientboundMoveEntityPacket packet) { - if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, packet.entityId)) { + private Packet processMoveEntityPacket(ForPlayer access, ClientboundMoveEntityPacket packet) { + if (access.canAccessEntity(packet.entityId)) { return packet; } else { return null; } } - private Packet processRotateHeadPacket(Player player, ClientboundRotateHeadPacket packet) { - if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, packet.entityId)) { + private Packet processRotateHeadPacket(ForPlayer access, ClientboundRotateHeadPacket packet) { + if (access.canAccessEntity(packet.entityId)) { return packet; } else { return null; } } - private Packet processRemoveEntitiesPacket(Player player, ClientboundRemoveEntitiesPacket packet) { + private Packet processRemoveEntitiesPacket(ForPlayer access, ClientboundRemoveEntitiesPacket packet) { IntList entityIdsToRemove = packet.getEntityIds(); - IntList filteredEntitiesToRemove = entityIdsToRemove.intStream().filter((id) -> privilegeProvider.isPlayerPrivilegedToAccessEntity(player, id)).collect(IntArrayList::new, IntList::add, IntList::addAll); + IntList filteredEntitiesToRemove = entityIdsToRemove.intStream().filter((id) -> access.canAccessEntity(id)).collect(IntArrayList::new, IntList::add, IntList::addAll); return new ClientboundRemoveEntitiesPacket(filteredEntitiesToRemove); } - public Packet processSetEntityLinkPacket(Player player, ClientboundSetEntityLinkPacket packet) { + public Packet processSetEntityLinkPacket(ForPlayer access, ClientboundSetEntityLinkPacket packet) { int fromEntityId = packet.getSourceId(); int toEntityId = packet.getDestId(); - if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, fromEntityId) && privilegeProvider.isPlayerPrivilegedToAccessEntity(player, toEntityId)) { + if (access.canAccessEntity(fromEntityId) && access.canAccessEntity(toEntityId)) { return packet; } else { return null; } } - private Packet processBlockEventPacket(Player player, ClientboundBlockEventPacket packet) { + private Packet processBlockEventPacket(ForPlayer access, ClientboundBlockEventPacket packet) { BlockPos blockPos = packet.getPos(); Block block = packet.getBlock(); int blockX = blockPos.getX(); int blockY = blockPos.getY(); int blockZ = blockPos.getZ(); - if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ) && privilegeProvider.isPlayerPrivilegedToAccessBlock(player, blockX, blockY, blockZ, block)) { + if (access.canAccessPosition(blockX, blockY, blockZ) && access.canAccessBlock(blockX, blockY, blockZ, block)) { return packet; } else { return null; } } - private Packet processBlockUpdatePacket(Player player, ClientboundBlockUpdatePacket packet) { + private Packet processBlockUpdatePacket(ForPlayer access, ClientboundBlockUpdatePacket packet) { BlockPos blockPos = packet.getPos(); BlockState blockState = packet.getBlockState(); Block block = blockState.getBlock(); @@ -420,41 +443,41 @@ public class TechHider { int blockY = blockPos.getY(); int blockZ = blockPos.getZ(); - if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ) && privilegeProvider.isPlayerPrivilegedToAccessBlock(player, blockX, blockY, blockZ, block) && privilegeProvider.isPlayerPrivilegedToAccessBlockState(player, blockX, blockY, blockZ, blockState)) { + if (access.canAccessPosition(blockX, blockY, blockZ) && access.canAccessBlock(blockX, blockY, blockZ, block) && access.canAccessBlockState(blockX, blockY, blockZ, blockState)) { return packet; - } else if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) { + } else if (access.canAccessPosition(blockX, blockY, blockZ)) { return new ClientboundBlockUpdatePacket(blockPos, blockStateUsedForObfuscation); } else { return null; } } - private Packet processBlockEntityDataPacket(Player player, ClientboundBlockEntityDataPacket packet) { + private Packet processBlockEntityDataPacket(ForPlayer access, ClientboundBlockEntityDataPacket packet) { BlockPos blockPos = packet.getPos(); BlockEntityType blockEntityType = packet.getType(); int blockX = blockPos.getX(); int blockY = blockPos.getY(); int blockZ = blockPos.getZ(); - if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ) && privilegeProvider.isPlayerPrivilegedToAccessBlockEntity(player, blockX, blockY, blockZ, blockEntityType)) { + if (access.canAccessPosition(blockX, blockY, blockZ) && access.canAccessBlockEntity(blockX, blockY, blockZ, blockEntityType)) { return packet; } else { return null; } } - private Packet proccessBlockDestructionPacket(Player player, ClientboundBlockDestructionPacket packet) { + private Packet proccessBlockDestructionPacket(ForPlayer access, ClientboundBlockDestructionPacket packet) { BlockPos blockPos = packet.getPos(); int entityBreakingBlockId = packet.getId(); - if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, entityBreakingBlockId) && privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockPos.getX(), blockPos.getY(), blockPos.getZ())) { + if (access.canAccessEntity(entityBreakingBlockId) && access.canAccessPosition(blockPos.getX(), blockPos.getY(), blockPos.getZ())) { return packet; } else { return null; } } - private ClientboundSectionBlocksUpdatePacket processSectionUpdate(Player p, ClientboundSectionBlocksUpdatePacket packet) { + private ClientboundSectionBlocksUpdatePacket processSectionUpdate(ForPlayer access, ClientboundSectionBlocksUpdatePacket packet) { SectionPos sectionPos = packet.sectionPos; short[] oldPos = packet.positions; BlockState[] oldStates = packet.states; @@ -471,10 +494,10 @@ public class TechHider { int worldY = sectionPos.relativeToBlockY(posShort); int worldZ = sectionPos.relativeToBlockZ(posShort); - if (privilegeProvider.isPlayerPrivilegedToAccessPosition(p, worldX, worldY, worldZ) && privilegeProvider.isPlayerPrivilegedToAccessBlock(p, worldX, worldY, worldZ, block) && privilegeProvider.isPlayerPrivilegedToAccessBlockState(p, worldX, worldY, worldZ, state)) { + if (access.canAccessPosition(worldX, worldY, worldZ) && access.canAccessBlock(worldX, worldY, worldZ, block) && access.canAccessBlockState(worldX, worldY, worldZ, state)) { filteredPos.add(posShort); filteredStates.add(state); - } else if (privilegeProvider.isPlayerPrivilegedToAccessPosition(p, worldX, worldY, worldZ)) { + } else if (access.canAccessPosition(worldX, worldY, worldZ)) { filteredPos.add(posShort); filteredStates.add(blockStateUsedForObfuscation); } @@ -494,43 +517,43 @@ public class TechHider { return new ClientboundSectionBlocksUpdatePacket(sectionPos, ShortSets.unmodifiable(new ShortArraySet(newPos)), newStates); } - private Packet processLevelParticlesPacket(Player player, ClientboundLevelParticlesPacket packet) { + private Packet processLevelParticlesPacket(ForPlayer access, ClientboundLevelParticlesPacket packet) { int blockX = (int) packet.getX(); int blockY = (int) packet.getY(); int blockZ = (int) packet.getZ(); - if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) { + if (access.canAccessPosition(blockX, blockY, blockZ)) { return packet; } else { return null; } } - private ClientboundLevelChunkWithLightPacket processChunkWithLight(Player p, ClientboundLevelChunkWithLightPacket packet) { + private ClientboundLevelChunkWithLightPacket processChunkWithLight(ForPlayer access, ClientboundLevelChunkWithLightPacket packet) { if (privilegeProvider.isEveryonePrivilegedToAccessAllDataWithinChunk(packet.getX(), packet.getZ())) { return packet; } else { - return chunkHider.processLevelChunkWithLightPacket(p, packet); + return chunkHider.processLevelChunkWithLightPacket(access, packet); } } - private Packet proccessPositionBasedPacket(Player player, int blockX, int blockY, int blockZ, Packet packet) { - if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) { + private Packet proccessPositionBasedPacket(ForPlayer access, int blockX, int blockY, int blockZ, Packet packet) { + if (access.canAccessPosition(blockX, blockY, blockZ)) { return packet; } else { return null; } } - private > BiFunction, Packet> buildPositionBasedPacketProcessor(Function positionExtractor) { - return (p, rawPacket) -> { + private > BiFunction, Packet> buildPositionBasedPacketProcessor(Function positionExtractor) { + return (access, rawPacket) -> { TTargetPacket packet = (TTargetPacket) rawPacket; BlockPos pos = positionExtractor.apply(packet); int blockX = pos.getX(); int blockY = pos.getY(); int blockZ = pos.getZ(); - return proccessPositionBasedPacket(p, blockX, blockY, blockZ, packet); + return proccessPositionBasedPacket(access, blockX, blockY, blockZ, packet); }; } } From b47cff28054d28e6468548d066aa75ec56798235 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Tue, 27 Oct 2026 09:58:16 +0100 Subject: [PATCH 5/9] 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); From 865d28645a889831f234a9723c1f564f14dc74e6 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Tue, 27 Oct 2026 10:10:09 +0100 Subject: [PATCH 6/9] Stop filtering remove-entity packets in TechHider Stripping ids of hidden entities left ghost entities on clients once the entity became visible and was later removed. Removing an unknown entity id is harmless, so the packet is passed through. --- .../src/de/steamwar/techhider/TechHider.java | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java index b4424f30..dc9f5b5c 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java @@ -182,6 +182,9 @@ public class TechHider { ClientboundSetChunkCacheCenterPacket.class, // 7.1.93 Set Center Chunk ClientboundForgetLevelChunkPacket.class, // 7.1.38 Unload Chunk ClientboundUpdateTagsPacket.class, // 7.1.133 Update Tags (play) + // 7.1.76 Remove Entities: must not be filtered, otherwise Player#hideEntity leaves ghosts on the client. + // Entities that are already gone from the world pass the entity checks anyway, so no additional information is leaked. + ClientboundRemoveEntitiesPacket.class, ClientboundPlayerInfoRemovePacket.class, // 7.1.68 Player Info Remove ClientboundPlayerInfoUpdatePacket.class, // 7.1.69 Player Info Update ClientboundMoveVehiclePacket.class, // 7.1.56 Move Vehicle (vehicle the player is in) @@ -242,8 +245,6 @@ public class TechHider { // 7.1.82 Set Head Rotation: entity id and rotation. processors.put(ClientboundRotateHeadPacket.class, (access, packet) -> this.processRotateHeadPacket(access, (ClientboundRotateHeadPacket) packet)); - // 7.1.76 Remove Entities: entity id visibility side channel. - processors.put(ClientboundRemoveEntitiesPacket.class, (access, packet) -> this.processRemoveEntitiesPacket(access, (ClientboundRemoveEntitiesPacket) packet)); // 7.1.99 Link Entities: entity relationship signal. processors.put(ClientboundSetEntityLinkPacket.class, (access, packet) -> this.processSetEntityLinkPacket(access, (ClientboundSetEntityLinkPacket) packet)); @@ -402,14 +403,6 @@ public class TechHider { } } - private Packet processRemoveEntitiesPacket(ForPlayer access, ClientboundRemoveEntitiesPacket packet) { - IntList entityIdsToRemove = packet.getEntityIds(); - - IntList filteredEntitiesToRemove = entityIdsToRemove.intStream().filter((id) -> access.canAccessEntity(id)).collect(IntArrayList::new, IntList::add, IntList::addAll); - - return new ClientboundRemoveEntitiesPacket(filteredEntitiesToRemove); - } - public Packet processSetEntityLinkPacket(ForPlayer access, ClientboundSetEntityLinkPacket packet) { int fromEntityId = packet.getSourceId(); int toEntityId = packet.getDestId(); From ac7dc2d2d57d203d9bf41c99ea02c86c9c5076cc Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Tue, 27 Oct 2026 10:10:18 +0100 Subject: [PATCH 7/9] Use Mth.floor instead of int casts for TechHider coordinates (int) truncates toward zero, so negative coordinates were checked against the wrong block. Matches the server's block position math. --- .../src/de/steamwar/techhider/TechHider.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java index dc9f5b5c..eee198e6 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java @@ -21,13 +21,12 @@ package de.steamwar.techhider; import de.steamwar.techhider.AccessPrivilegeProvider.ForPlayer; import com.comphenix.tinyprotocol.TinyProtocol; -import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.shorts.ShortArraySet; import it.unimi.dsi.fastutil.shorts.ShortSets; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.network.PacketListener; +import net.minecraft.util.Mth; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.common.*; import net.minecraft.network.protocol.configuration.ClientboundFinishConfigurationPacket; @@ -295,9 +294,9 @@ public class TechHider { processors.put(ClientboundExplodePacket.class, (access, rawPacket) -> { ClientboundExplodePacket packet = (ClientboundExplodePacket) rawPacket; Vec3 pos = packet.center(); - int blockX = (int) pos.x; - int blockY = (int) pos.y; - int blockZ = (int) pos.z; + int blockX = Mth.floor(pos.x); + int blockY = Mth.floor(pos.y); + int blockZ = Mth.floor(pos.z); return proccessPositionBasedPacket(access, blockX, blockY, blockZ, packet); @@ -365,7 +364,7 @@ public class TechHider { } private Packet processAddEntityPacket(ForPlayer access, ClientboundAddEntityPacket packet) { - if (access.canAccessEntity(packet.getId()) && access.canAccessPosition((int) packet.getX(), (int) packet.getY(), (int) packet.getZ())) { + if (access.canAccessEntity(packet.getId()) && access.canAccessPosition(Mth.floor(packet.getX()), Mth.floor(packet.getY()), Mth.floor(packet.getZ()))) { return packet; } else { return null; @@ -511,9 +510,9 @@ public class TechHider { } private Packet processLevelParticlesPacket(ForPlayer access, ClientboundLevelParticlesPacket packet) { - int blockX = (int) packet.getX(); - int blockY = (int) packet.getY(); - int blockZ = (int) packet.getZ(); + int blockX = Mth.floor(packet.getX()); + int blockY = Mth.floor(packet.getY()); + int blockZ = Mth.floor(packet.getZ()); if (access.canAccessPosition(blockX, blockY, blockZ)) { return packet; From 6c99f1f9c942c52add500e1fdc75cc472dc5b32d Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Tue, 27 Oct 2026 10:12:07 +0100 Subject: [PATCH 8/9] Resend hull chunks to non-viewers after hull initialisation Chunks encoded while the hull was filled as hidden contain obfuscated blocks that become visible after initialize, and no block update is generated for them. Reload the team's extend region for all players that cannot see the hull. --- .../src/de/steamwar/fightsystem/utils/Hull.java | 2 +- .../src/de/steamwar/fightsystem/utils/HullHider.java | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) 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 91fe96c0..0248f46b 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Hull.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Hull.java @@ -101,7 +101,7 @@ public class Hull { return !isViewer(player) && region.inRegion(location) && !visibility.get(new IntVector(location).toId(region)); } - private boolean isViewer(Player player) { + public boolean isViewer(Player player) { return player != null && viewers.contains(player.getUniqueId()); } 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 b7d5a54a..fe41631e 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/HullHider.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/HullHider.java @@ -31,6 +31,7 @@ import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.fightsystem.states.StateDependentTask; import lombok.Getter; +import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.entity.Player; @@ -70,7 +71,15 @@ public class HullHider implements Listener { public void initialize(FightTeam team) { if (!TechHiderWrapper.ENABLED) return; - hullMap.get(team).initialize(); + Hull hull = hullMap.get(team); + hull.initialize(); + + // Chunks encoded while the hull was filled as hidden contain obfuscated blocks that are visible now, but no block update is generated for them + for (Player player : Bukkit.getOnlinePlayers()) { + if (!hull.isViewer(player)) { + FightSystem.getTechHider().reloadChunks(player, team.getExtendRegion(), Region.EMPTY); + } + } } public void fill(FightTeam team, boolean visible) { From fc090c2b48fcc6220663d15c756874b6a377b244 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 12:49:28 +0100 Subject: [PATCH 9/9] Remove Hull primary direction --- .../src/de/steamwar/fightsystem/utils/Hull.java | 10 ---------- 1 file changed, 10 deletions(-) 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 0248f46b..16af9f4c 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Hull.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Hull.java @@ -78,10 +78,8 @@ public class Hull { this.occluding = new BitSet(region.volume()); this.visibility = new BitSet(region.volume()); - IntVector primaryDirection = new IntVector(0, 0, team.isBlue() == (Config.BlueToRedZ > 0) ? -1 : 1); directions = IntStream.range(0, 27) .mapToObj(v -> new IntVector(v % 3 - 1, (v / 3) % 3 - 1, v / 9 - 1)) - .filter(v -> v.sqLength() == 1 || v.x * primaryDirection.x + v.y * primaryDirection.y + v.z * primaryDirection.z == 1) // Not pointing away from primary direction .toArray(IntVector[]::new); for (IntVector direction : directions) { @@ -89,18 +87,10 @@ public class Hull { } } - public boolean blockPrecise(Player player, int chunkX, int chunkY, int 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) && !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 !isViewer(player) && region.inRegion(location) && !visibility.get(new IntVector(location).toId(region)); - } - public boolean isViewer(Player player) { return player != null && viewers.contains(player.getUniqueId()); }