From da4b684a4b21d9315ed07d1de3bc7353970d6e01 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 14:49:00 +0100 Subject: [PATCH] Move Hull and generic HullHider into HiderCore Hull no longer depends on FightSystem: plugin, world, region, ground visibility and hidden blocks are passed in. HullRegion is the common block box hulls work on. The generic HullHider manages one hull per key and does the block, entity and REntity tracking; FightHullHider keeps the fight specific parts (team hulls, viewer rules, state dependent registration). Entities are now checked against the hull with block coordinates, so entities in the bottom layer and at the max z edge are handled correctly. --- .../de/steamwar/fightsystem/FightSystem.java | 10 +- .../commands/TechhiderbugCommand.java | 2 +- .../fightsystem/fight/FightWorld.java | 2 +- .../{HullHider.java => FightHullHider.java} | 119 ++++---------- .../de/steamwar/fightsystem/utils/Region.java | 5 + .../fightsystem/utils/TechHiderWrapper.java | 4 +- HiderCore/build.gradle.kts | 1 + .../src/de/steamwar/hullhider}/Hull.java | 59 ++++--- .../src/de/steamwar/hullhider/HullHider.java | 154 ++++++++++++++++++ .../src/de/steamwar/hullhider/HullRegion.java | 97 +++++++++++ 10 files changed, 334 insertions(+), 119 deletions(-) rename FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/{HullHider.java => FightHullHider.java} (59%) rename {FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils => HiderCore/src/de/steamwar/hullhider}/Hull.java (87%) create mode 100644 HiderCore/src/de/steamwar/hullhider/HullHider.java create mode 100644 HiderCore/src/de/steamwar/hullhider/HullRegion.java diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java index c676308e..be367444 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java @@ -32,8 +32,8 @@ import de.steamwar.fightsystem.record.GlobalRecorder; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.OneShotStateDependent; import de.steamwar.fightsystem.states.StateDependentListener; +import de.steamwar.fightsystem.utils.FightHullHider; import de.steamwar.fightsystem.utils.FightUI; -import de.steamwar.fightsystem.utils.HullHider; import de.steamwar.fightsystem.utils.SWSound; import de.steamwar.fightsystem.utils.TechHiderWrapper; import de.steamwar.linkage.AbstractLinker; @@ -58,7 +58,7 @@ public class FightSystem extends JavaPlugin { private FightTeam lastWinner; private String lastWinreason; private TechHiderWrapper techHider; - private HullHider hullHider; + private FightHullHider hullHider; private SpigotLinker spigotLinker; @@ -111,9 +111,9 @@ public class FightSystem extends JavaPlugin { new OneShotStateDependent(ArenaMode.Test, FightState.All, SteamEdit::init); Config.world.setGameRule(GameRule.REDUCED_DEBUG_INFO, ArenaMode.AntiTest.contains(Config.mode)); - hullHider = new HullHider(); + hullHider = new FightHullHider(); techHider = new TechHiderWrapper(hullHider); - FightSystem.getHullHider().getHullMap().values().forEach(hull -> hull.fill(true)); + hullHider.fillAll(true); FileSource.startReplay(); @@ -165,7 +165,7 @@ public class FightSystem extends JavaPlugin { return plugin.techHider; } - public static HullHider getHullHider() { + public static FightHullHider getHullHider() { return plugin.hullHider; } 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 f4ae7c73..09eb3e44 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/TechhiderbugCommand.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/TechhiderbugCommand.java @@ -56,7 +56,7 @@ public class TechhiderbugCommand extends FightSWCommand { writer.append('\n'); writer.append("HullHider regions: "); - FightSystem.getHullHider().getHullMap().forEach((t, h) -> writer.append(t.getName()).append(':').append(h.getRegion().toString()).append(' ')); + FightSystem.getHullHider().forEachHull((t, h) -> writer.append(t.getName()).append(':').append(h.getRegion().toString()).append(' ')); writer.append('\n'); writer.append("Hidden regions: "); diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightWorld.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightWorld.java index 83c15170..4c05125e 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightWorld.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightWorld.java @@ -84,7 +84,7 @@ public class FightWorld extends StateDependent { entities.forEach(Entity::remove); entities.clear(); - FightSystem.getHullHider().getHullMap().values().forEach(hull -> hull.fill(true)); + FightSystem.getHullHider().fillAll(true); Config.ArenaRegion.forEachChunk((x, z) -> { for (Player p : Bukkit.getOnlinePlayers()) { diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/HullHider.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightHullHider.java similarity index 59% rename from FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/HullHider.java rename to FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightHullHider.java index fe41631e..4175655b 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/HullHider.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightHullHider.java @@ -30,7 +30,8 @@ import de.steamwar.fightsystem.listener.Recording; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.fightsystem.states.StateDependentTask; -import lombok.Getter; +import de.steamwar.hullhider.Hull; +import de.steamwar.hullhider.HullHider; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.block.Block; @@ -38,43 +39,38 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -import org.bukkit.event.block.BlockPhysicsEvent; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.entity.EntitySpawnEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; -import java.util.HashMap; -import java.util.Map; import java.util.Objects; +import java.util.function.BiConsumer; -public class HullHider implements Listener { +/** + * Fight specific part of the hull hider: one hull per team schematic region, and who may view which hull. + */ +public class FightHullHider { - @Getter - private final Map hullMap = new HashMap<>(); - private final Hull[] hulls; + private final HullHider hullHider = new HullHider<>(); - public HullHider() { - if (!TechHiderWrapper.ENABLED) { - hulls = new Hull[0]; - return; - } - - Fight.teams().forEach(team -> hullMap.put(team, new Hull(team))); - hulls = hullMap.values().toArray(new Hull[0]); + public FightHullHider() { + if (!TechHiderWrapper.ENABLED) return; + 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)); + }); new StateDependentListener(TechHiderWrapper.ENABLED, FightState.All, new MembershipListener()); - new StateDependentListener(TechHiderWrapper.ENABLED, FightState.Schem, this); + new StateDependentListener(TechHiderWrapper.ENABLED, FightState.Schem, hullHider); new StateDependentTask(TechHiderWrapper.ENABLED, FightState.Schem, this::onTick, 0, 1); } public void initialize(FightTeam team) { if (!TechHiderWrapper.ENABLED) return; - Hull hull = hullMap.get(team); - hull.initialize(); + hullHider.initialize(team); // Chunks encoded while the hull was filled as hidden contain obfuscated blocks that are visible now, but no block update is generated for them + Hull hull = hullHider.getHull(team); for (Player player : Bukkit.getOnlinePlayers()) { if (!hull.isViewer(player)) { FightSystem.getTechHider().reloadChunks(player, team.getExtendRegion(), Region.EMPTY); @@ -84,7 +80,15 @@ public class HullHider implements Listener { public void fill(FightTeam team, boolean visible) { if (!TechHiderWrapper.ENABLED) return; - hullMap.get(team).fill(visible); + hullHider.fill(team, visible); + } + + public void fillAll(boolean visible) { + hullHider.fillAll(visible); + } + + public void forEachHull(BiConsumer consumer) { + hullHider.forEach(consumer); } // Registered in every state: players must be hidden from the hulls by default, no matter when they join. @@ -96,7 +100,7 @@ public class HullHider implements Listener { @EventHandler public void onLeave(PlayerQuitEvent e) { - forgetViewer(e.getPlayer()); + hullHider.forgetViewer(e.getPlayer()); } // A dead player loses the right to see the enemy hull again @@ -114,82 +118,27 @@ public class HullHider implements Listener { FightTeam team = Fight.getPlayerTeam(player); FightPlayer fp = Fight.getFightPlayer(player); - for (Map.Entry hull : hullMap.entrySet()) { - if (Config.isReferee(player) || hull.getKey() == team || (fp != null && fp.canEntern())) { - hull.getValue().addViewer(player); - } else { - hull.getValue().removeViewer(player); - } - } - } - - private void forgetViewer(Player player) { - for (Hull hull : hulls) { - hull.forgetViewer(player); - } - } - - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onBlockPhysic(BlockPhysicsEvent e) { - if (e.getBlock() == e.getSourceBlock() || e.getChangedType() == Material.AIR) { - blockUpdate(e.getBlock(), e.getChangedType()); - } + hullHider.forEach((hullTeam, hull) -> hullHider.setViewer(player, hullTeam, Config.isReferee(player) || hullTeam == team || (fp != null && fp.canEntern()))); } public void blockUpdate(Block block, Material changedType) { - for (Hull hull : hulls) { - hull.updateBlockVisibility(block, changedType); - } + hullHider.blockUpdate(block, changedType); } public boolean isBlockHidden(Player player, int x, int y, int z) { - if (!TechHiderWrapper.ENABLED) return false; - - for (Hull hull : hulls) { - if (hull.isBlockHidden(player, x, y, z)) { - return true; - } - } - - return false; - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onSpawn(EntitySpawnEvent e) { - for (Hull hull : hulls) { - hull.checkEntity(e.getEntity()); - } + return hullHider.isBlockHidden(player, x, y, z); } private void onTick() { - Recording.iterateOverEntities(Objects::nonNull, entity -> { - for (Hull hull : hulls) { - hull.checkEntity(entity); - } - }); - - for (Hull hull : hulls) { - hull.sendUncoveredBlocks(); - } - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onDeath(EntityDeathEvent e) { - for (Hull hull : hulls) { - hull.removeEntity(e.getEntity()); - } + Recording.iterateOverEntities(Objects::nonNull, hullHider::checkEntity); + hullHider.sendUncoveredBlocks(); } public void updateREntity(REntity e) { - for (Hull hull : hulls) { - hull.checkREntity(e); - } + hullHider.updateREntity(e); } public void despawnREntity(REntity e) { - for (Hull hull : hulls) { - hull.removeREntity(e); - } + hullHider.despawnREntity(e); } } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Region.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Region.java index c28257ef..d5de73be 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Region.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Region.java @@ -20,6 +20,7 @@ package de.steamwar.fightsystem.utils; import de.steamwar.core.ProtocolUtils; +import de.steamwar.hullhider.HullRegion; import lombok.AllArgsConstructor; import lombok.Getter; import org.bukkit.Location; @@ -155,6 +156,10 @@ public class Region { return in2dRegion(x, z) && minY <= y && y < maxY; } + public HullRegion toHullRegion() { + return new HullRegion(minX, minY, minZ, maxX, maxY, maxZ); + } + public Region intersection(Region other) { int x = Math.max(minX, other.minX); int y = Math.max(minY, other.minY); 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 abc5dd52..469159c3 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java @@ -67,9 +67,9 @@ public class TechHiderWrapper extends StateDependent implements Listener { @Getter private final ConcurrentHashMap hiddenRegion = new ConcurrentHashMap<>(); - private final HullHider hullHider; + private final FightHullHider hullHider; - public TechHiderWrapper(HullHider hullHider) { + public TechHiderWrapper(FightHullHider hullHider) { super(ENABLED, FightState.All); this.hullHider = hullHider; diff --git a/HiderCore/build.gradle.kts b/HiderCore/build.gradle.kts index 086bf56c..283cd19e 100644 --- a/HiderCore/build.gradle.kts +++ b/HiderCore/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { compileOnly(libs.nms) compileOnly(libs.netty) compileOnly(libs.fastutil) + compileOnly(libs.authlib) } widener { diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Hull.java b/HiderCore/src/de/steamwar/hullhider/Hull.java similarity index 87% rename from FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Hull.java rename to HiderCore/src/de/steamwar/hullhider/Hull.java index 16af9f4c..7344af14 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Hull.java +++ b/HiderCore/src/de/steamwar/hullhider/Hull.java @@ -17,15 +17,12 @@ * along with this program. If not, see . */ -package de.steamwar.fightsystem.utils; +package de.steamwar.hullhider; import com.comphenix.tinyprotocol.TinyProtocol; import com.mojang.authlib.GameProfile; import de.steamwar.core.ProtocolWrapper; import de.steamwar.entity.REntity; -import de.steamwar.fightsystem.Config; -import de.steamwar.fightsystem.FightSystem; -import de.steamwar.fightsystem.fight.FightTeam; import it.unimi.dsi.fastutil.shorts.Short2ObjectArrayMap; import lombok.AllArgsConstructor; import lombok.Getter; @@ -38,10 +35,12 @@ import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.craftbukkit.block.data.CraftBlockData; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -53,13 +52,12 @@ public class Hull { private static final IntVector[] NO_BRANCHES = new IntVector[0]; - private static boolean isOccluding(Material material) { - return material.isOccluding() || Config.GameModeConfig.Techhider.HiddenBlocks.contains(material); - } - + private final Plugin plugin; + private final World world; @Getter - private final Region region; + private final HullRegion region; private final boolean groundVisible; + private final Set hiddenBlocks; private final IntVector[] directions; private final BitSet occluding; @@ -72,9 +70,16 @@ public class Hull { private final HashSet entities = new HashSet<>(); private final HashSet rentities = new HashSet<>(); - public Hull(FightTeam team) { - this.region = team.getSchemRegion(); - this.groundVisible = region.getMinY() != Config.PlayerRegion.getMinY(); + /** + * @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 + */ + public Hull(Plugin plugin, World world, HullRegion region, boolean groundVisible, Set hiddenBlocks) { + this.plugin = plugin; + this.world = world; + this.region = region; + this.groundVisible = groundVisible; + this.hiddenBlocks = hiddenBlocks; this.occluding = new BitSet(region.volume()); this.visibility = new BitSet(region.volume()); @@ -87,8 +92,12 @@ public class Hull { } } + private boolean isOccluding(Material material) { + return material.isOccluding() || hiddenBlocks.contains(material); + } + 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())); + return region.contains(x, y, z) && !isViewer(player) && !visibility.get(region.toIndex(x, y, z)); } public boolean isViewer(Player player) { @@ -123,7 +132,7 @@ public class Hull { public void checkEntity(Entity entity) { Location location = entity.getLocation(); - if (region.inRegion(location) && !visibility.get(new IntVector(location).toId(region))) { + if (region.contains(location) && !visibility.get(new IntVector(location).toId(region))) { if (entities.add(entity)) { for (Player player : Bukkit.getOnlinePlayers()) { if (!isViewer(player) && !player.equals(entity)) untrackEntity(player, entity); @@ -143,11 +152,11 @@ public class Hull { TinyProtocol.instance.sendPacket(player, ProtocolWrapper.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.REMOVE, new GameProfile(entity.getUniqueId(), entity.getName()), GameMode.CREATIVE)); } - player.showEntity(FightSystem.getPlugin(), entity); + player.showEntity(plugin, entity); } public void untrackEntity(Player player, Entity entity) { - player.hideEntity(FightSystem.getPlugin(), entity); + player.hideEntity(plugin, entity); if (entity instanceof Player) { TinyProtocol.instance.sendPacket(player, ProtocolWrapper.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.ADD, new GameProfile(entity.getUniqueId(), entity.getName()), GameMode.CREATIVE)); @@ -159,8 +168,8 @@ public class Hull { } public void checkREntity(REntity entity) { - Location location = new Location(Config.world, entity.getX(), entity.getY(), entity.getZ()); - if (region.inRegion(location) && !visibility.get(new IntVector(location).toId(region))) { + Location location = new Location(world, entity.getX(), entity.getY(), entity.getZ()); + if (region.contains(location) && !visibility.get(new IntVector(location).toId(region))) { if (rentities.add(entity)) entity.hide(true); } else { if (rentities.remove(entity)) entity.hide(false); @@ -191,12 +200,12 @@ public class Hull { long start = System.currentTimeMillis(); region.forEach((x, y, z) -> { IntVector block = new IntVector(x, y, z); - if (isOccluding(Config.world.getBlockAt(x, y, z).getType())) { + if (isOccluding(world.getBlockAt(x, y, z).getType())) { occluding.set(block.toId(region)); } }); forEachBorder((root, direction) -> uncoverBlocks(new NullList<>(), root, direction)); - FightSystem.getPlugin().getLogger().log(Level.INFO, () -> "[HullHider] initialisation finished: " + (System.currentTimeMillis() - start) + " ms, visible blocks: " + visibility.cardinality()); + plugin.getLogger().log(Level.INFO, () -> "[HullHider] initialisation finished: " + (System.currentTimeMillis() - start) + " ms, visible blocks: " + visibility.cardinality()); } public void updateBlockVisibility(Block b, Material changedType) { @@ -235,7 +244,7 @@ public class Hull { Object[] blockdata = new Object[changes.size()]; for (int i = 0; i < blockdata.length; i++) { IntVector change = changes.get(i); - blockdata[i] = ((CraftBlockData) Config.world.getBlockData(change.getX(), change.getY(), change.getZ())).getState(); + blockdata[i] = ((CraftBlockData) world.getBlockData(change.getX(), change.getY(), change.getZ())).getState(); } if (changes.size() > 1) { @@ -332,12 +341,12 @@ public class Hull { this.z = location.getBlockZ(); } - public boolean notInRegion(Region region) { - return !region.inRegion(x, y, z); + public boolean notInRegion(HullRegion region) { + return !region.contains(x, y, z); } - public int toId(Region region) { - return ((y - region.getMinY()) * region.getSizeZ() + (z - region.getMinZ())) * region.getSizeX() + (x - region.getMinX()); + public int toId(HullRegion region) { + return region.toIndex(x, y, z); } public IntVector add(int x, int y, int z) { diff --git a/HiderCore/src/de/steamwar/hullhider/HullHider.java b/HiderCore/src/de/steamwar/hullhider/HullHider.java new file mode 100644 index 00000000..9bbc846f --- /dev/null +++ b/HiderCore/src/de/steamwar/hullhider/HullHider.java @@ -0,0 +1,154 @@ +/* + * 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 de.steamwar.entity.REntity; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockPhysicsEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntitySpawnEvent; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +/** + * Manages one {@link Hull} per key (e.g. a team or a region). + * Which players may view which hull, when hulls are initialised and when this listener is registered is up to the user. + */ +public class HullHider implements Listener { + + private final Map hullMap = new LinkedHashMap<>(); + // Read on netty threads, replaced on every change + private volatile Hull[] hulls = new Hull[0]; + + public void addHull(K key, Hull hull) { + hullMap.put(key, hull); + hulls = hullMap.values().toArray(new Hull[0]); + } + + public Hull getHull(K key) { + return hullMap.get(key); + } + + public void forEach(BiConsumer consumer) { + hullMap.forEach(consumer); + } + + public void initialize(K key) { + hullMap.get(key).initialize(); + } + + public void fill(K key, boolean visible) { + hullMap.get(key).fill(visible); + } + + public void fillAll(boolean visible) { + for (Hull hull : hulls) { + hull.fill(visible); + } + } + + public void setViewer(Player player, K key, boolean viewer) { + Hull hull = hullMap.get(key); + if (viewer) { + hull.addViewer(player); + } else { + hull.removeViewer(player); + } + } + + public void forgetViewer(Player player) { + for (Hull hull : hulls) { + hull.forgetViewer(player); + } + } + + public boolean isBlockHidden(Player player, int x, int y, int z) { + for (Hull hull : hulls) { + if (hull.isBlockHidden(player, x, y, z)) { + return true; + } + } + + return false; + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onBlockPhysic(BlockPhysicsEvent e) { + if (e.getBlock() == e.getSourceBlock() || e.getChangedType() == Material.AIR) { + blockUpdate(e.getBlock(), e.getChangedType()); + } + } + + public void blockUpdate(Block block, Material changedType) { + for (Hull hull : hulls) { + hull.updateBlockVisibility(block, changedType); + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onSpawn(EntitySpawnEvent e) { + checkEntity(e.getEntity()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onDeath(EntityDeathEvent e) { + for (Hull hull : hulls) { + hull.removeEntity(e.getEntity()); + } + } + + /** + * Has to be called every tick for every entity, as entities can move into or out of the hidden part of a hull. + */ + public void checkEntity(Entity entity) { + for (Hull hull : hulls) { + hull.checkEntity(entity); + } + } + + /** + * Has to be called every tick after the entities have been checked. + */ + public void sendUncoveredBlocks() { + for (Hull hull : hulls) { + hull.sendUncoveredBlocks(); + } + } + + public void updateREntity(REntity e) { + for (Hull hull : hulls) { + hull.checkREntity(e); + } + } + + public void despawnREntity(REntity e) { + for (Hull hull : hulls) { + hull.removeREntity(e); + } + } +} diff --git a/HiderCore/src/de/steamwar/hullhider/HullRegion.java b/HiderCore/src/de/steamwar/hullhider/HullRegion.java new file mode 100644 index 00000000..b8aacb0b --- /dev/null +++ b/HiderCore/src/de/steamwar/hullhider/HullRegion.java @@ -0,0 +1,97 @@ +/* + * 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 lombok.AllArgsConstructor; +import lombok.Getter; +import org.bukkit.Location; + +/** + * Immutable block box a {@link Hull} covers. The minimum is inclusive, the maximum exclusive. + */ +@Getter +@AllArgsConstructor +public final class HullRegion { + + public static HullRegion fromSize(int minX, int minY, int minZ, int sizeX, int sizeY, int sizeZ) { + return new HullRegion(minX, minY, minZ, minX + sizeX, minY + sizeY, minZ + sizeZ); + } + + public static HullRegion fromInclusive(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) { + return new HullRegion(minX, minY, minZ, maxX + 1, maxY + 1, maxZ + 1); + } + + private final int minX; + private final int minY; + private final int minZ; + private final int maxX; + private final int maxY; + private final int maxZ; + + public int getSizeX() { + return maxX - minX; + } + + public int getSizeY() { + return maxY - minY; + } + + public int getSizeZ() { + return maxZ - minZ; + } + + public int volume() { + return getSizeX() * getSizeY() * getSizeZ(); + } + + public boolean contains(int x, int y, int z) { + return minX <= x && x < maxX && minY <= y && y < maxY && minZ <= z && z < maxZ; + } + + public boolean contains(Location location) { + return contains(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + } + + /** + * @return the index of the block within a bitset of {@link #volume()} bits, only valid for contained blocks + */ + public int toIndex(int x, int y, int z) { + return ((y - minY) * getSizeZ() + (z - minZ)) * getSizeX() + (x - minX); + } + + public void forEach(BlockConsumer executor) { + for (int x = minX; x < maxX; x++) { + for (int y = minY; y < maxY; y++) { + for (int z = minZ; z < maxZ; z++) { + executor.accept(x, y, z); + } + } + } + } + + public interface BlockConsumer { + void accept(int x, int y, int z); + } + + @Override + public String toString() { + return minX + "," + minY + "," + minZ + "->" + maxX + "," + maxY + "," + maxZ; + } +}