From f02f356b2bf1847157855750a990ad2deb7fd5cb Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 15:32:18 +0100 Subject: [PATCH] Add /hullhider to BauSystem /hullhider hides the inside of the build area hull of a region from the players who toggle it, like the hull hider of a fight. Each region gets its own hull while somebody uses it; it is rebuilt every time somebody enables it, as WorldEdit and Axiom changes do not update it. /techhider, /xray and /hullhider exclude each other per player and region. Which players may see the hull contents is now decided by Viewers, a sealed class with HiddenByDefault (fight) and VisibleByDefault (bau). Added players are stored in a set; isViewer alone defines what being added means. --- .../BauSystem_Main/src/BauSystem.properties | 6 + .../src/BauSystem_de.properties | 6 + .../features/hullhider/HullHiderCommand.java | 179 ++++++++++++++++++ .../features/techhider/BauTechHider.java | 34 +++- .../features/techhider/HiddenHull.java | 29 +++ .../features/techhider/TechHiderCommand.java | 5 + .../bausystem/features/xray/XrayCommand.java | 5 + .../fightsystem/utils/FightHullHider.java | 11 +- HiderCore/src/de/steamwar/hullhider/Hull.java | 61 +++--- .../src/de/steamwar/hullhider/HullHider.java | 19 +- .../src/de/steamwar/hullhider/Viewers.java | 75 ++++++++ 11 files changed, 391 insertions(+), 39 deletions(-) create mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullHiderCommand.java create mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/HiddenHull.java create mode 100644 HiderCore/src/de/steamwar/hullhider/Viewers.java diff --git a/BauSystem/BauSystem_Main/src/BauSystem.properties b/BauSystem/BauSystem_Main/src/BauSystem.properties index c5adfb70..80e591d9 100644 --- a/BauSystem/BauSystem_Main/src/BauSystem.properties +++ b/BauSystem/BauSystem_Main/src/BauSystem.properties @@ -37,6 +37,7 @@ SCOREBOARD_TPS_FROZEN = §eFrozen SCOREBOARD_TRACE_TICKS = Ticks SCOREBOARD_TECHHIDER = TechHider§8: §aOn SCOREBOARD_XRAY = XRay§8: §aOn +SCOREBOARD_HULLHIDER = HullHider§8: §aOn SCOREBOARD_LOCK_TEAM = Bau Lock§8: §eTeam SCOREBOARD_LOCK_SUPERVISOR = Bau Lock§8: §eSupervisor SCOREBOARD_LOCK_TEAM_AND_SERVERTEAM = Bau Lock§8: §e(Server) Team @@ -1033,6 +1034,11 @@ XRAY_HELP = §8/§exray §8- §7Toggle Xray XRAY_GLOBAL = §cNo xray in global region XRAY_ON = §aXray activated XRAY_OFF = §cXray deactivated +# HULLHIDER +HULLHIDER_HELP = §8/§ehullhider §8- §7Toggle Hullhider +HULLHIDER_GLOBAL = §cNo hullhider in global region +HULLHIDER_ON = §aHullhider activated +HULLHIDER_OFF = §cHullhider deactivated # WorldEdit COLORREPLACE_HELP = §8//§ecolorreplace §8[§7color§8] §8[§7color§8] §8- §7Replace all blocks of one color with another TYPEREPLACE_HELP = §8//§etypereplace §8[§7type§8] §8[§7type§8] §8- §7Replace all blocks of one type with another diff --git a/BauSystem/BauSystem_Main/src/BauSystem_de.properties b/BauSystem/BauSystem_Main/src/BauSystem_de.properties index 9bee037c..e08803a9 100644 --- a/BauSystem/BauSystem_Main/src/BauSystem_de.properties +++ b/BauSystem/BauSystem_Main/src/BauSystem_de.properties @@ -37,6 +37,7 @@ SCOREBOARD_TPS_FROZEN = §eEingefroren SCOREBOARD_TRACE_TICKS = Ticks SCOREBOARD_TECHHIDER = TechHider§8: §aAn SCOREBOARD_XRAY = XRay§8: §aAn +SCOREBOARD_HULLHIDER = HullHider§8: §aAn SCOREBOARD_LOCK_TEAM = Bau Lock§8: §eTeam SCOREBOARD_LOCK_TEAM_AND_SERVERTEAM = Bau Lock§8: §e(Server-) Team SCOREBOARD_LOCK_SERVERTEAM = Bau Lock§8: §eServerteam @@ -966,6 +967,11 @@ XRAY_HELP = §8/§exray §8- §7Xray umschalten XRAY_GLOBAL = §cKein Xray in der globalen region XRAY_ON = §aXray aktiviert XRAY_OFF = §cXray deaktiviert +# HULLHIDER +HULLHIDER_HELP = §8/§ehullhider §8- §7Hullhider umschalten +HULLHIDER_GLOBAL = §cKein Hullhider in der globalen region +HULLHIDER_ON = §aHullhider aktiviert +HULLHIDER_OFF = §cHullhider deaktiviert # WorldEdit COLORREPLACE_HELP = §8//§ecolorreplace §8[§7color§8] §8[§7color§8] §8- §7Ersetzt eine Farbe mit einer anderen TYPEREPLACE_HELP = §8//§etyreplace §8[§7type§8] §8[§7type§8] §8- §7Ersetzt einen Blockgruppe mit einer anderen diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullHiderCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullHiderCommand.java new file mode 100644 index 00000000..b8ad87de --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullHiderCommand.java @@ -0,0 +1,179 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.features.hullhider; + +import de.steamwar.bausystem.BauSystem; +import de.steamwar.bausystem.features.techhider.HiddenHull; +import de.steamwar.bausystem.features.techhider.TechHiderCommand; +import de.steamwar.bausystem.features.xray.XrayCommand; +import de.steamwar.bausystem.region.Point; +import de.steamwar.bausystem.region.Region; +import de.steamwar.bausystem.utils.ScoreboardElement; +import de.steamwar.command.SWCommand; +import de.steamwar.hullhider.Hull; +import de.steamwar.hullhider.HullHider; +import de.steamwar.hullhider.HullRegion; +import de.steamwar.hullhider.Viewers; +import de.steamwar.linkage.Linked; +import de.steamwar.linkage.LinkedInstance; +import de.steamwar.techhider.ObfuscationConfig; +import net.md_5.bungee.api.ChatMessageType; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Hides the inside of the build area hull of a region from the players who toggle it, as the hull hider of a fight would. + */ +@Linked +public class HullHiderCommand extends SWCommand implements Listener, ScoreboardElement { + + public HullHiderCommand() { + super("hullhider"); + + Bukkit.getPluginManager().registerEvents(hullHider, BauSystem.getInstance()); + Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), this::onTick, 1, 1); + } + + private final HullHider hullHider = new HullHider<>(); + // Read on netty threads by the BauTechHider + private final Map hulls = new ConcurrentHashMap<>(); + private final Map> hidden = new ConcurrentHashMap<>(); + + @LinkedInstance + public TechHiderCommand techHiderCommand; + + @LinkedInstance + public XrayCommand xrayCommand; + + @Register(description = "HULLHIDER_HELP") + public void toggleHider(@Validator Player player) { + Region region = Region.getRegion(player.getLocation()); + if (region.getType().isGlobal()) { + BauSystem.MESSAGE.send("HULLHIDER_GLOBAL", player); + return; + } + if (!region.getGameModeConfig().Techhider.Active) { + return; + } + + techHiderCommand.disable(region, player); + xrayCommand.disable(region, player); + if (hidden.getOrDefault(region, Collections.emptySet()).contains(player)) { + disable(region, player); + BauSystem.MESSAGE.sendPrefixless("HULLHIDER_OFF", player, ChatMessageType.ACTION_BAR); + } else { + enable(region, player); + BauSystem.MESSAGE.sendPrefixless("HULLHIDER_ON", player, ChatMessageType.ACTION_BAR); + } + } + + private void enable(Region region, Player player) { + if (!hulls.containsKey(region)) { + Point min = region.getBuildArea().getMinPoint(true); + Point max = region.getBuildArea().getMaxPoint(true); + HullRegion hullRegion = HullRegion.fromInclusive(min.getX(), min.getY(), min.getZ(), max.getX(), max.getY(), max.getZ()); + + // Only the hidden blocks of an active techhider occlude, the hull hider is used without one + Hull hull = new Hull(BauSystem.getInstance(), world(), hullRegion, false, Collections.emptySet(), new Viewers.VisibleByDefault()); + hullHider.addHull(region, hull); + hulls.put(region, new HiddenHull(hull, ObfuscationConfig.of(region.getGameModeConfig().Techhider).getObfuscationState())); + } + + hidden.computeIfAbsent(region, rg -> ConcurrentHashMap.newKeySet()).add(player); + // Added players see the hull blocks of a hull visible by default + hullHider.addViewer(player, region); + // Block changes by WorldEdit or Axiom do not update the hull, so it is rebuilt every time somebody enables it. + // This also resends the chunks to the player. + hullHider.initialize(region); + } + + /** + * Stops hiding the hull of the region from the player, removing the hull if nobody uses it anymore. + */ + public void disable(Region region, Player player) { + Set players = hidden.get(region); + if (players == null || !players.remove(player)) return; + + if (players.isEmpty()) { + hidden.remove(region); + hulls.remove(region); + hullHider.removeHull(region); + } else { + hullHider.removeViewer(player, region); + hullHider.resendChunks(player, region); + } + } + + public void addHiddenHulls(Player player, List hiddenHulls) { + hidden.forEach((region, players) -> { + HiddenHull hiddenHull = hulls.get(region); + if (hiddenHull != null && players.contains(player)) { + hiddenHulls.add(hiddenHull); + } + }); + } + + private void onTick() { + if (hidden.isEmpty()) return; + + for (Entity entity : world().getEntities()) { + hullHider.checkEntity(entity); + } + hullHider.sendUncoveredBlocks(); + } + + private World world() { + return Bukkit.getWorlds().get(0); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + for (Region region : hidden.keySet()) { + disable(region, event.getPlayer()); + } + } + + @Override + public ScoreboardGroup getGroup() { + return ScoreboardGroup.OTHER; + } + + @Override + public int order() { + return 0; + } + + @Override + public String get(Region region, Player p) { + if (!hidden.getOrDefault(region, Collections.emptySet()).contains(p)) return null; + return "§e" + BauSystem.MESSAGE.parse("SCOREBOARD_HULLHIDER", p); + } +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/BauTechHider.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/BauTechHider.java index a4fd51bf..46867bc2 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/BauTechHider.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/BauTechHider.java @@ -19,6 +19,7 @@ package de.steamwar.bausystem.features.techhider; +import de.steamwar.bausystem.features.hullhider.HullHiderCommand; import de.steamwar.bausystem.features.world.SpectatorListener; import de.steamwar.bausystem.features.xray.XrayCommand; import de.steamwar.linkage.Linked; @@ -29,18 +30,20 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; +import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.entity.Player; import java.util.ArrayList; import java.util.List; /** - * The single TechHider of the server. It combines the hiding rules of the spectator techhider, /techhider and /xray per player. + * The single TechHider of the server. It combines the hiding rules of the spectator techhider, /techhider and /xray and the hulls of /hullhider per player. */ @Linked public class BauTechHider implements AccessPrivilegeProvider { private static final HidingRule[] NO_RULES = new HidingRule[0]; + private static final HiddenHull[] NO_HULLS = new HiddenHull[0]; // Injected after construction, until then nothing is hidden @LinkedInstance @@ -49,6 +52,8 @@ public class BauTechHider implements AccessPrivilegeProvider { public TechHiderCommand techHiderCommand; @LinkedInstance public SpectatorListener spectatorListener; + @LinkedInstance + public HullHiderCommand hullHiderCommand; public BauTechHider() { new TechHider(this); @@ -56,8 +61,8 @@ public class BauTechHider implements AccessPrivilegeProvider { @Override public ForPlayer forPlayer(Player p) { - if (xrayCommand == null || techHiderCommand == null || spectatorListener == null) { - return new BauPlayerAccess(p, NO_RULES); + if (xrayCommand == null || techHiderCommand == null || spectatorListener == null || hullHiderCommand == null) { + return new BauPlayerAccess(p, NO_RULES, NO_HULLS); } // Region rules first: where rules overlap, the first one decides the obfuscation block @@ -65,7 +70,10 @@ public class BauTechHider implements AccessPrivilegeProvider { xrayCommand.addHidingRules(p, rules); techHiderCommand.addHidingRules(p, rules); spectatorListener.addHidingRules(p, rules); - return new BauPlayerAccess(p, rules.toArray(NO_RULES)); + + List hulls = new ArrayList<>(); + hullHiderCommand.addHiddenHulls(p, hulls); + return new BauPlayerAccess(p, rules.toArray(NO_RULES), hulls.toArray(NO_HULLS)); } @Override @@ -76,7 +84,7 @@ public class BauTechHider implements AccessPrivilegeProvider { /** * Snapshot of the hiding rules of one player, taken once per outgoing packet on the netty thread. */ - private record BauPlayerAccess(Player player, HidingRule[] rules) implements ForPlayer { + private record BauPlayerAccess(Player player, HidingRule[] rules, HiddenHull[] hulls) implements ForPlayer { @Override public Player getPlayer() { @@ -88,11 +96,17 @@ public class BauTechHider implements AccessPrivilegeProvider { for (HidingRule rule : rules) { if (rule.coversChunk(chunkX, chunkZ)) return false; } + for (HiddenHull hull : hulls) { + if (!hull.hull().getRegion().chunkOutside(chunkX, chunkZ)) return false; + } return true; } @Override public boolean canAccessPosition(int blockX, int blockY, int blockZ) { + for (HiddenHull hull : hulls) { + if (hull.hull().isBlockHidden(player, blockX, blockY, blockZ)) return false; + } return true; } @@ -114,7 +128,10 @@ public class BauTechHider implements AccessPrivilegeProvider { @Override public boolean canAccessEntity(int entityId) { - return true; + if (hulls.length == 0) return true; + + net.minecraft.world.entity.Entity nmsEntity = ((CraftWorld) player.getWorld()).getHandle().moonrise$getEntityLookup().get(entityId); + return nmsEntity == null || canAccessPosition(nmsEntity.getBlockX(), nmsEntity.getBlockY(), nmsEntity.getBlockZ()); } @Override @@ -130,7 +147,10 @@ public class BauTechHider implements AccessPrivilegeProvider { for (HidingRule rule : rules) { if (rule.covers(blockX, blockY, blockZ)) return rule.obfuscation().getObfuscationState(); } - // Only asked for positions a rule hides + for (HiddenHull hull : hulls) { + if (hull.hull().getRegion().contains(blockX, blockY, blockZ)) return hull.obfuscation(); + } + // Only asked for positions a rule or hull hides return Blocks.END_STONE.defaultBlockState(); } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/HiddenHull.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/HiddenHull.java new file mode 100644 index 00000000..e2cff0c8 --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/HiddenHull.java @@ -0,0 +1,29 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.bausystem.features.techhider; + +import de.steamwar.hullhider.Hull; +import net.minecraft.world.level.block.state.BlockState; + +/** + * A hull a player may not view, and the block sent instead of its hidden blocks. + */ +public record HiddenHull(Hull hull, BlockState obfuscation) { +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java index b6fd7d8a..89956fe2 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java @@ -21,6 +21,7 @@ package de.steamwar.bausystem.features.techhider; import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.Permission; +import de.steamwar.bausystem.features.hullhider.HullHiderCommand; import de.steamwar.bausystem.features.world.SpectatorListener; import de.steamwar.bausystem.features.xray.XrayCommand; import de.steamwar.bausystem.region.Region; @@ -58,6 +59,9 @@ public class TechHiderCommand extends SWCommand implements Listener, ScoreboardE @LinkedInstance public XrayCommand xrayCommand; + @LinkedInstance + public HullHiderCommand hullHiderCommand; + @Register(description = "TECHHIDER_HELP") public void toggleHider(@Validator Player player) { Region region = Region.getRegion(player.getLocation()); @@ -79,6 +83,7 @@ public class TechHiderCommand extends SWCommand implements Listener, ScoreboardE } xrayCommand.disable(region, player); + hullHiderCommand.disable(region, player); if (hidden.get(region).contains(player)) { hidden.get(region).remove(player); BauSystem.MESSAGE.sendPrefixless("TECHHIDER_OFF", player, ChatMessageType.ACTION_BAR); diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java index 0b334429..9e0742d0 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java @@ -21,6 +21,7 @@ package de.steamwar.bausystem.features.xray; import com.comphenix.tinyprotocol.TinyProtocol; import de.steamwar.bausystem.BauSystem; +import de.steamwar.bausystem.features.hullhider.HullHiderCommand; import de.steamwar.bausystem.features.techhider.HidingRule; import de.steamwar.bausystem.features.techhider.TechHiderCommand; import de.steamwar.bausystem.region.Region; @@ -62,6 +63,9 @@ public class XrayCommand extends SWCommand implements Listener, ScoreboardElemen @LinkedInstance public TechHiderCommand techHiderCommand; + @LinkedInstance + public HullHiderCommand hullHiderCommand; + @Register(description = "XRAY_HELP") public void toggleHider(@Validator Player player) { Region region = Region.getRegion(player.getLocation()); @@ -87,6 +91,7 @@ public class XrayCommand extends SWCommand implements Listener, ScoreboardElemen } techHiderCommand.disable(region, player); + hullHiderCommand.disable(region, player); if (hidden.get(region).contains(player)) { hidden.get(region).remove(player); BauSystem.MESSAGE.sendPrefixless("XRAY_OFF", player, ChatMessageType.ACTION_BAR); diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightHullHider.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightHullHider.java index 6c1d9d1b..cc5074ce 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightHullHider.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightHullHider.java @@ -32,6 +32,7 @@ import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.fightsystem.states.StateDependentTask; import de.steamwar.hullhider.Hull; import de.steamwar.hullhider.HullHider; +import de.steamwar.hullhider.Viewers; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.entity.Player; @@ -56,7 +57,7 @@ public class FightHullHider { Fight.teams().forEach(team -> { Region region = team.getSchemRegion(); - hullHider.addHull(team, new Hull(FightSystem.getPlugin(), Config.world, region.toHullRegion(), region.getMinY() != Config.PlayerRegion.getMinY(), Config.GameModeConfig.Techhider.HiddenBlocks)); + hullHider.addHull(team, new Hull(FightSystem.getPlugin(), Config.world, region.toHullRegion(), region.getMinY() != Config.PlayerRegion.getMinY(), Config.GameModeConfig.Techhider.HiddenBlocks, new Viewers.HiddenByDefault())); }); new StateDependentListener(TechHiderWrapper.ENABLED, FightState.All, new MembershipListener()); @@ -109,7 +110,13 @@ public class FightHullHider { FightTeam team = Fight.getPlayerTeam(player); FightPlayer fp = Fight.getFightPlayer(player); - hullHider.forEach((hullTeam, hull) -> hullHider.setViewer(player, hullTeam, Config.isReferee(player) || hullTeam == team || (fp != null && fp.canEntern()))); + hullHider.forEach((hullTeam, hull) -> { + if (Config.isReferee(player) || hullTeam == team || (fp != null && fp.canEntern())) { + hullHider.addViewer(player, hullTeam); + } else { + hullHider.removeViewer(player, hullTeam); + } + }); } public void blockUpdate(Block block, Material changedType) { diff --git a/HiderCore/src/de/steamwar/hullhider/Hull.java b/HiderCore/src/de/steamwar/hullhider/Hull.java index 04826401..745d5d88 100644 --- a/HiderCore/src/de/steamwar/hullhider/Hull.java +++ b/HiderCore/src/de/steamwar/hullhider/Hull.java @@ -45,7 +45,6 @@ import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; import java.util.logging.Level; import java.util.stream.IntStream; @@ -67,21 +66,22 @@ public class Hull { private final HashMap visibilityDirections = new HashMap<>(); // Contains the visible directions of each occluding visible block private final HashSet uncoveredSurface = 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 Viewers viewers; private final HashSet entities = new HashSet<>(); private final HashSet rentities = new HashSet<>(); /** * @param groundVisible whether the bottom layer of the region is uncovered (if the region does not reach the ground) * @param hiddenBlocks blocks that occlude like solid blocks, as the techhider replaces them by an occluding block + * @param viewers which players may see the hull contents, a new instance for every hull */ - public Hull(Plugin plugin, World world, HullRegion region, boolean groundVisible, Set hiddenBlocks) { + public Hull(Plugin plugin, World world, HullRegion region, boolean groundVisible, Set hiddenBlocks, Viewers viewers) { this.plugin = plugin; this.world = world; this.region = region; this.groundVisible = groundVisible; this.hiddenBlocks = hiddenBlocks; + this.viewers = viewers; this.occluding = new BitSet(region.volume()); this.visibility = new BitSet(region.volume()); @@ -103,33 +103,48 @@ public class Hull { } public boolean isViewer(Player player) { - return player != null && viewers.contains(player.getUniqueId()); + return player != null && viewers.isViewer(player); } /** - * 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); - } - } - - /** - * Allows this player to see the hull (adds a viewer). + * Adds the player to the {@link Viewers}, which decide whether the player may see the hull contents afterwards. */ public void addViewer(Player player) { - if (viewers.add(player.getUniqueId())) { - for (Entity entity : entities) { - trackEntity(player, entity); - } - // techhider triggers block change sending - } + boolean wasViewer = isViewer(player); + viewers.addViewer(player); + updateEntities(player, wasViewer); } + /** + * Removes the player from the {@link Viewers}, which decide whether the player may see the hull contents afterwards. + */ + public void removeViewer(Player player) { + boolean wasViewer = isViewer(player); + viewers.removeViewer(player); + updateEntities(player, wasViewer); + } + + private void updateEntities(Player player, boolean wasViewer) { + if (isViewer(player)) { + if (!wasViewer) { + for (Entity entity : entities) { + trackEntity(player, entity); + } + } + } else { + // Also hides the entities from players that joined after they were hidden + for (Entity entity : entities) { + if (!player.equals(entity)) untrackEntity(player, entity); + } + } + // techhider triggers block change sending + } + + /** + * Resets the player to the default viewer state, e.g. when leaving. + */ public void forgetViewer(Player player) { - viewers.remove(player.getUniqueId()); + viewers.removeViewer(player); } public void checkEntity(Entity entity) { diff --git a/HiderCore/src/de/steamwar/hullhider/HullHider.java b/HiderCore/src/de/steamwar/hullhider/HullHider.java index fd37baa8..92ecb24f 100644 --- a/HiderCore/src/de/steamwar/hullhider/HullHider.java +++ b/HiderCore/src/de/steamwar/hullhider/HullHider.java @@ -107,13 +107,18 @@ public class HullHider implements Listener { } } - public void setViewer(Player player, K key, boolean viewer) { - Hull hull = hullMap.get(key); - if (viewer) { - hull.addViewer(player); - } else { - hull.removeViewer(player); - } + /** + * @see Viewers + */ + public void addViewer(Player player, K key) { + hullMap.get(key).addViewer(player); + } + + /** + * @see Viewers + */ + public void removeViewer(Player player, K key) { + hullMap.get(key).removeViewer(player); } public void forgetViewer(Player player) { diff --git a/HiderCore/src/de/steamwar/hullhider/Viewers.java b/HiderCore/src/de/steamwar/hullhider/Viewers.java new file mode 100644 index 00000000..8467d396 --- /dev/null +++ b/HiderCore/src/de/steamwar/hullhider/Viewers.java @@ -0,0 +1,75 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.hullhider; + +import org.bukkit.entity.Player; + +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +/** + * The players of a {@link Hull}. Whether an added player may see the hull contents or sees the hull blocks instead is decided by {@link #isViewer(Player)}. + * Stores the players, so every hull needs its own instance. + */ +public abstract sealed class Viewers { + + // Concurrent as it is read on netty threads + protected final Set players = ConcurrentHashMap.newKeySet(); + + /** + * @return whether the player may see the hull contents instead of the hull blocks + */ + public abstract boolean isViewer(Player player); + + /** + * @return whether the player was not added before + */ + public boolean addViewer(Player player) { + return players.add(player.getUniqueId()); + } + + /** + * @return whether the player was added before + */ + public boolean removeViewer(Player player) { + return players.remove(player.getUniqueId()); + } + + /** + * Hidden from everybody, only added players may see the hull contents. + */ + public static final class HiddenByDefault extends Viewers { + @Override + public boolean isViewer(Player player) { + return players.contains(player.getUniqueId()); + } + } + + /** + * Visible to everybody, only added players see the hull blocks instead of the hull contents. + */ + public static final class VisibleByDefault extends Viewers { + @Override + public boolean isViewer(Player player) { + return !players.contains(player.getUniqueId()); + } + } +}