From 776b0b1f9f6965b44200204a3e1ff43928205283 Mon Sep 17 00:00:00 2001 From: Jakob Schulz <55949993+Lordikak@users.noreply.github.com> Date: Wed, 20 May 2026 22:53:34 +0200 Subject: [PATCH 01/46] fix: allow ISE in team prefix command for improved flexibility --- .../src/de/steamwar/velocitycore/commands/TeamCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VelocityCore/src/de/steamwar/velocitycore/commands/TeamCommand.java b/VelocityCore/src/de/steamwar/velocitycore/commands/TeamCommand.java index ef46d9b0..502694bd 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/commands/TeamCommand.java +++ b/VelocityCore/src/de/steamwar/velocitycore/commands/TeamCommand.java @@ -366,7 +366,7 @@ public class TeamCommand extends SWCommand { } @Register(value = "prefix", description = "TEAM_PREFIX_USAGE") - public void prefix(@Validator("canUseTeamPrefix") Chatter sender, @StaticValue(value = {SW, STEAM_WAR, "Team"}, falseValues = {2}) boolean useSWTeamTag) { + public void prefix(@Validator("canUseTeamPrefix") Chatter sender, @StaticValue(value = {SW, STEAM_WAR, "Team"}, falseValues = {2}, allowISE = true) boolean useSWTeamTag) { SteamwarPrefix.setSWPrefix(sender.user(), useSWTeamTag); sender.system("TEAM_PREFIX_SET", useSWTeamTag ? STEAM_WAR : sender.parse("TEAM_PREFIX_TEAM")); } From 71108651fc067cbc46ec6c0465cc86244aae6014 Mon Sep 17 00:00:00 2001 From: Jakob Schulz <55949993+Lordikak@users.noreply.github.com> Date: Wed, 2 Sep 2026 00:10:03 +0200 Subject: [PATCH 02/46] add toggle for per region Wind Charge limit and blocker for leaving the region --- .../src/de/steamwar/sql/GameModeConfig.java | 16 ++++++ FightSystem/FightSystem_Core/src/config.yml | 4 ++ .../listener/WindchargeStopper.java | 56 ++++++++++++++++--- 3 files changed, 68 insertions(+), 8 deletions(-) diff --git a/CommonCore/SQL/src/de/steamwar/sql/GameModeConfig.java b/CommonCore/SQL/src/de/steamwar/sql/GameModeConfig.java index 17bfccb5..82d07ef5 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/GameModeConfig.java +++ b/CommonCore/SQL/src/de/steamwar/sql/GameModeConfig.java @@ -509,6 +509,20 @@ public final class GameModeConfig { */ public final boolean WindchargesDestroyWater; + /** + * Allows Wind Charges to leave the Region. + * + * @implSpec {@code true} by default + */ + public final boolean WindchargesCanLeaveRegion; + + /** + * Limits the amount of Wind Charges in each extended region. -1 is no limit + * + * @implSpec {@code -1} by default + */ + public final int WindchargeLimit; + private ArenaConfig(YMLWrapper loader, SchematicConfig.SizeConfig Size, List EnterStages) { loaded = loader.canLoad(); WaterDepth = loader.getInt("WaterDepth", 0); @@ -529,6 +543,8 @@ public final class GameModeConfig { WindchargesCanCrossMiddle = loader.getBoolean("WindchargesCanCrossMiddle", false); WindchargesInteractWithBlocks = loader.getBoolean("WindchargesInteractWithBlocks", true); WindchargesDestroyWater = loader.getBoolean("WindchargesDestroyWater", false); + WindchargesCanLeaveRegion = loader.getBoolean("WindchargesCanLeaveRegion", true); + WindchargeLimit = loader.getInt("WindchargeLimit", -1); } @ToString diff --git a/FightSystem/FightSystem_Core/src/config.yml b/FightSystem/FightSystem_Core/src/config.yml index dbaf3c33..756f4c2a 100644 --- a/FightSystem/FightSystem_Core/src/config.yml +++ b/FightSystem/FightSystem_Core/src/config.yml @@ -68,6 +68,10 @@ Arena: WindchargesCanCrossMiddle: false # defaults to false if missing # Allows Wind Charge interaction with Blocks. WindchargesInteractWithBlocks: false # defaults to false if missing + # Allows Wind Charges to leave the extended region. + WindchargesCanLeaveRegion: true # defaults to true + # Limits the amount of Wind Charges in each extended region. -1 is no limit + WindchargeLimit: -1 # defaults to -1 Schematic: # The size of the schematics diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/WindchargeStopper.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/WindchargeStopper.java index a750a1b9..d81cd7a9 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/WindchargeStopper.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/WindchargeStopper.java @@ -22,31 +22,71 @@ package de.steamwar.fightsystem.listener; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependentTask; +import de.steamwar.fightsystem.utils.Region; import de.steamwar.linkage.Linked; import net.minecraft.world.entity.projectile.windcharge.WindCharge; import org.bukkit.Location; @Linked public class WindchargeStopper { - public WindchargeStopper() { - new StateDependentTask(!Config.GameModeConfig.Arena.WindchargesCanCrossMiddle, FightState.Running, this::run, 1, 1); + canCrossMiddle = Config.GameModeConfig.Arena.WindchargesCanCrossMiddle; + canLeaveRegion = Config.GameModeConfig.Arena.WindchargesCanLeaveRegion; + limit = Config.GameModeConfig.Arena.WindchargeLimit; + + boolean enabled = limit >= 0 || !canCrossMiddle || !canLeaveRegion; + + new StateDependentTask(enabled, FightState.Running, this::run, 1, 1); } private static final int middleLine = Config.SpecSpawn.getBlockZ(); - private static final Class windChargeClass = WindCharge.class; + private final boolean canCrossMiddle; + private final boolean canLeaveRegion; + private final int limit; + + private int blueCount; + private int redCount; + private void run() { + redCount = 0; + blueCount = 0; + Recording.iterateOverEntities(windChargeClass::isInstance, entity -> { - Location nextlocation = entity.getLocation().add(entity.getVelocity()); Location location = entity.getLocation(); + Location nextlocation = location.clone().add(entity.getVelocity()); - boolean passedMiddle = nextlocation.getBlockZ() >= middleLine && location.getBlockZ() <= middleLine || - nextlocation.getBlockZ() <= middleLine && location.getBlockZ() >= middleLine; + if (!canCrossMiddle) { + boolean passedMiddle = nextlocation.getBlockZ() >= middleLine && location.getBlockZ() <= middleLine || + nextlocation.getBlockZ() <= middleLine && location.getBlockZ() >= middleLine; + if (passedMiddle) { + entity.remove(); + return; + } + } - if (passedMiddle) { - entity.remove(); + Region region = Config.BlueExtendRegion.inRegion(location) ? Config.BlueExtendRegion + : Config.RedExtendRegion.inRegion(location) ? Config.RedExtendRegion + : null; + + if (!canLeaveRegion) { + if (region == null) { + entity.remove(); + return; + } + if (!region.inRegion(nextlocation)) { + entity.remove(); + return; + } + } + + if (limit >= 0 && region != null) { + if (region == Config.BlueExtendRegion) { + if (++blueCount > limit) entity.remove(); + } else { + if (++redCount > limit) entity.remove(); + } } }); } From 99f94c527833d5bf6182081664fa01a7dd1609a1 Mon Sep 17 00:00:00 2001 From: Jakob Schulz <55949993+Lordikak@users.noreply.github.com> Date: Fri, 16 Oct 2026 23:00:38 +0200 Subject: [PATCH 03/46] Add arena map name to world identifier --- .../src/de/steamwar/fightsystem/FightSystem.java | 9 +++++++++ .../src/de/steamwar/velocitycore/ServerStarter.java | 1 + 2 files changed, 10 insertions(+) diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java index c676308e..5da7c23a 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java @@ -21,6 +21,7 @@ package de.steamwar.fightsystem; import com.comphenix.tinyprotocol.TinyProtocol; import de.steamwar.core.Core; +import de.steamwar.core.WorldIdentifier; import de.steamwar.core.worldedit.SteamEdit; import de.steamwar.core.worldedit.WorldEditRendererCUIEditor; import de.steamwar.fightsystem.fight.Fight; @@ -49,6 +50,9 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerPickupArrowEvent; import org.bukkit.plugin.java.JavaPlugin; +import java.nio.charset.StandardCharsets; +import java.util.HexFormat; + public class FightSystem extends JavaPlugin { @Getter @@ -79,6 +83,11 @@ public class FightSystem extends JavaPlugin { Core.setServerName("Replay"); } + String arenaMap = System.getProperty("arenaMap"); + if (arenaMap != null) { + WorldIdentifier.set("fight/" + HexFormat.of().formatHex(arenaMap.getBytes(StandardCharsets.UTF_8))); + } + message = new Message("de.steamwar.fightsystem.FightSystem", FightSystem.class.getClassLoader()); spigotLinker = new SpigotLinker(this, message) { diff --git a/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java b/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java index a4dc58fc..44932fdf 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java +++ b/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java @@ -91,6 +91,7 @@ public class ServerStarter { gameMode = mode.configFile.getName().replace(".yml", ""); directory = new File(SERVER_PATH, mode.Server.Folder); arguments.put("config", mode.configFile.getName()); + arguments.put("arenaMap", gameMode + "/" + map); tempWorld(SERVER_PATH + mode.Server.Folder + "/arenas/" + map); startCondition = () -> { if (playersToSend.stream().anyMatch(player -> Subserver.isArena(Subserver.getSubserver(player)))) { From a4e06bcfd5328711a443db6d7967efbe57a45d0b Mon Sep 17 00:00:00 2001 From: Sakziea Date: Mon, 26 Oct 2026 13:23:07 +0100 Subject: [PATCH 04/46] Fix Bubble Colums get removed by WaterRemover --- .../src/de/steamwar/fightsystem/listener/WaterRemover.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/WaterRemover.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/WaterRemover.java index bded9613..71805d75 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/WaterRemover.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/WaterRemover.java @@ -108,7 +108,7 @@ public class WaterRemover implements Listener { //checks for water and removes it, if present boolean result = true; Material type = b.getType(); - if (type == Material.WATER || type == Material.LAVA) { + if (type == Material.WATER || type == Material.LAVA || type == Material.BUBBLE_COLUMN) { b.setType(Material.AIR); } else { BlockData data = b.getBlockData(); From d32f5738fd2c4e156f62a7aaa78259c191a4a63f Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Mon, 26 Oct 2026 15:22:35 +0100 Subject: [PATCH 05/46] Rework chat icon and name colors - Mark the receiver's default channel with a green icon - Show the TeamChat icon in yellow, all other channel icons in gray - Use the rank prefix and rank color for sender names in TeamChat and ServerTeamChat, like in the global chat --- .../src/de/steamwar/messages/BungeeCore.properties | 4 ++-- .../src/de/steamwar/messages/BungeeCore_de.properties | 4 ++-- .../src/de/steamwar/velocitycore/chat/ChatChannel.java | 10 +++++++++- .../src/de/steamwar/velocitycore/chat/TeamChannel.java | 5 +++++ 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/VelocityCore/src/de/steamwar/messages/BungeeCore.properties b/VelocityCore/src/de/steamwar/messages/BungeeCore.properties index 8e47001e..b3d563e4 100644 --- a/VelocityCore/src/de/steamwar/messages/BungeeCore.properties +++ b/VelocityCore/src/de/steamwar/messages/BungeeCore.properties @@ -573,11 +573,11 @@ CHAT_BC_USAGE = §8/§7bc §8[§emessage§8] CHAT_NO_RECEIVER = §cNobody receives your message CHAT_EMPTY = §cDon\'t write meaningless empty messages. -CHAT_SERVERTEAM_SENDER = §e{0} +CHAT_SERVERTEAM_SENDER = {4}{5}{6}{0} CHAT_SERVERTEAM = §8» §f{2} CHAT_GLOBAL_SENDER = {4}{5}{6}{0} CHAT_GLOBAL = §8» {7}{2} -CHAT_TEAM_SENDER = §e{0} +CHAT_TEAM_SENDER = {4}{5}{6}{0} CHAT_TEAM = §8» §f{2} CHAT_MSG_SENDER = §e{0} CHAT_MSG_SEPARATOR = §8» diff --git a/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties b/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties index 6f23220f..ee6ab6a6 100644 --- a/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties +++ b/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties @@ -538,11 +538,11 @@ CHAT_BC_USAGE = §8/§7bc §8[§eNachricht§8] CHAT_NO_RECEIVER = §cNiemand empfängt deine Nachricht CHAT_EMPTY = §cSchreibe keine inhaltslosen Nachrichten. -CHAT_SERVERTEAM_SENDER = §e{0} +CHAT_SERVERTEAM_SENDER = {4}{5}{6}{0} CHAT_SERVERTEAM = §8» §f{2} CHAT_GLOBAL_SENDER = {4}{5}{6}{0} CHAT_GLOBAL = §8» {7}{2} -CHAT_TEAM_SENDER = §e{0} +CHAT_TEAM_SENDER = {4}{5}{6}{0} CHAT_TEAM = §8» §f{2} CHAT_MSG_SENDER = §e{0} CHAT_MSG_SEPARATOR = §8» diff --git a/VelocityCore/src/de/steamwar/velocitycore/chat/ChatChannel.java b/VelocityCore/src/de/steamwar/velocitycore/chat/ChatChannel.java index 4bc9d919..bb4cf45e 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/chat/ChatChannel.java +++ b/VelocityCore/src/de/steamwar/velocitycore/chat/ChatChannel.java @@ -65,6 +65,13 @@ public abstract class ChatChannel { return null; } + /** + * @return the color of the channel icon if it is not the default channel of the receiver + */ + protected String iconColor() { + return "§7"; + } + /** * @return the text put into the chat input when clicking the channel icon to write a single message, null if there is none */ @@ -131,7 +138,8 @@ public abstract class ChatChannel { Component msg = ChatFormat.withMentionHovers(receiver, format(receiver, sender, coloredMessage)); if (icon(receiver) != null) { - Component icon = iconComponent(receiver, ChatManager.getDefault(receiver) == this ? "§e" : "§7"); + // The default channel of the receiver is marked by a green icon + Component icon = iconComponent(receiver, ChatManager.getDefault(receiver) == this ? "§a" : iconColor()); msg = Component.text().append(icon).append(Component.text(" ")).append(msg).build(); } receiver.sendMessage(msg); diff --git a/VelocityCore/src/de/steamwar/velocitycore/chat/TeamChannel.java b/VelocityCore/src/de/steamwar/velocitycore/chat/TeamChannel.java index 4603c3d5..13adcf88 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/chat/TeamChannel.java +++ b/VelocityCore/src/de/steamwar/velocitycore/chat/TeamChannel.java @@ -49,6 +49,11 @@ public class TeamChannel extends ChatChannel { return new Message("CHAT_CHANNEL_TEAM"); } + @Override + protected String iconColor() { + return "§e"; + } + @Override protected Message icon(Chatter receiver) { return new Message("CHAT_ICON_TEAM"); From 2ea09eadb27985a13548f5a60059969eb26d2f6b Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Tue, 27 Oct 2026 07:40:06 +0100 Subject: [PATCH 06/46] Fix command errors being hidden by generic help (#513) Commands ending in a String... parameter are treated as help commands. When such a command failed with an @ErrorMessage (e.g. /msg or /kick with an offline player), the generic help matched next, printed the usage and the collected error was never shown. Stop before generic help (noTabComplete) if an earlier help command already collected errors, and print those instead. --- .../src/de/steamwar/command/AbstractSWCommand.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CommandFramework/src/de/steamwar/command/AbstractSWCommand.java b/CommandFramework/src/de/steamwar/command/AbstractSWCommand.java index 7ec88017..5062319f 100644 --- a/CommandFramework/src/de/steamwar/command/AbstractSWCommand.java +++ b/CommandFramework/src/de/steamwar/command/AbstractSWCommand.java @@ -91,9 +91,13 @@ public abstract class AbstractSWCommand { if (commandList.stream().noneMatch(s -> s.invoke(errors::add, sender, alias, args))) { errors.forEach(Runnable::run); } else return; - if (errors.isEmpty() && helpCommandList.stream().noneMatch(s -> s.invoke(errors::add, sender, alias, args))) { - errors.forEach(Runnable::run); + if (!errors.isEmpty()) return; + for (SubCommand help : helpCommandList) { + // Generic help must not hide the error of a more specific command + if (help.noTabComplete && !errors.isEmpty()) break; + if (help.invoke(errors::add, sender, alias, args)) return; } + errors.forEach(Runnable::run); } catch (CommandFrameworkException e) { commandSystemError(sender, e); throw e; From 0ef702da9efa7b1e7c766df0e132388eaa1bd373 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Tue, 27 Oct 2026 09:23:31 +0100 Subject: [PATCH 07/46] 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 08/46] 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 09/46] 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 10/46] 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 11/46] 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 12/46] 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 13/46] 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 14/46] 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 15/46] 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()); } From b8f61658f4203f05021b5334d981912aadf30790 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 10:12:52 +0100 Subject: [PATCH 16/46] Cleanup Laufbau --- .../src/de/steamwar/bausystem/BauSystem.java | 8 +- .../slaves/laufbau/BlockBoundingBox.java | 328 +++++------------ .../slaves/laufbau/BoundingBoxLoader.java | 25 -- .../features/slaves/laufbau/Cuboid.java | 10 + .../features/slaves/laufbau/Laufbau.java | 41 +-- .../slaves/laufbau/LaufbauConfig.java | 13 +- .../slaves/laufbau/LaufbauConfigV1.java | 65 ---- .../slaves/laufbau/LaufbauSettings.java | 110 ++---- .../features/slaves/laufbau/LaufbauUtils.java | 99 ----- .../boundingboxes/AmethystBoundingBox.java | 261 ------------- .../boundingboxes/AzaleaBoundingBox.java | 46 --- .../boundingboxes/BellBoundingBox.java | 149 -------- .../BrewingStandBoundingBox.java | 46 --- .../boundingboxes/CandleBoundingBox.java | 62 ---- .../boundingboxes/ChainBoundingBox.java | 69 ---- .../boundingboxes/ChorusPlantBoundingBox.java | 89 ----- .../boundingboxes/DragonEggBoundingBox.java | 45 --- .../boundingboxes/DripLeafBoundingBox.java | 59 --- .../boundingboxes/FencesBoundingBox.java | 75 ---- .../boundingboxes/GrindstoneBoundingBox.java | 157 -------- .../boundingboxes/HopperBoundingBox.java | 97 ----- .../boundingboxes/IronBarBoundingBox.java | 75 ---- .../boundingboxes/LanternBoundingBox.java | 61 ---- .../boundingboxes/LecternBoundingBox.java | 46 --- .../boundingboxes/StairBoundingBox.java | 343 ------------------ .../boundingboxes/WallBoundingBox.java | 79 ---- .../LaufbauConfigV1ToV2Converter.java | 40 -- .../laufbau/states/CreatingLaufState.java | 1 - 28 files changed, 143 insertions(+), 2356 deletions(-) delete mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/BoundingBoxLoader.java delete mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauConfigV1.java delete mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauUtils.java delete mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/AmethystBoundingBox.java delete mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/AzaleaBoundingBox.java delete mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/BellBoundingBox.java delete mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/BrewingStandBoundingBox.java delete mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/CandleBoundingBox.java delete mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/ChainBoundingBox.java delete mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/ChorusPlantBoundingBox.java delete mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/DragonEggBoundingBox.java delete mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/DripLeafBoundingBox.java delete mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/FencesBoundingBox.java delete mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/GrindstoneBoundingBox.java delete mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/HopperBoundingBox.java delete mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/IronBarBoundingBox.java delete mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/LanternBoundingBox.java delete mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/LecternBoundingBox.java delete mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/StairBoundingBox.java delete mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/WallBoundingBox.java delete mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/converter/LaufbauConfigV1ToV2Converter.java diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java index 041e4d83..c524c4d0 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/BauSystem.java @@ -24,7 +24,6 @@ import de.steamwar.bausystem.config.ConfigStore; import de.steamwar.bausystem.features.gui.BauGUI; import de.steamwar.bausystem.features.script.lua.SteamWarLuaPlugin; import de.steamwar.bausystem.features.script.lua.libs.LuaLib; -import de.steamwar.bausystem.features.slaves.laufbau.BoundingBoxLoader; import de.steamwar.bausystem.features.slaves.panzern.Panzern; import de.steamwar.bausystem.features.slaves.panzern.PanzernAlgorithm; import de.steamwar.bausystem.features.tracer.TraceManager; @@ -41,7 +40,6 @@ import de.steamwar.core.CRIUSleepEvent; import de.steamwar.core.Core; import de.steamwar.core.WorldIdentifier; import de.steamwar.core.worldedit.SteamEdit; -import de.steamwar.core.worldedit.WorldEditRendererCUIEditor; import de.steamwar.linkage.AbstractLinker; import de.steamwar.linkage.SpigotLinker; import de.steamwar.message.Message; @@ -110,9 +108,9 @@ public class BauSystem extends JavaPlugin implements Listener { if (any instanceof PanzernAlgorithm) { Panzern.add((PanzernAlgorithm) any); } - if (any instanceof BoundingBoxLoader) { - ((BoundingBoxLoader) any).load(); - } + // if (any instanceof BoundingBoxLoader) { + // ((BoundingBoxLoader) any).load(); + // } } }; try { diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/BlockBoundingBox.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/BlockBoundingBox.java index 0df055af..a9d1cbc4 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/BlockBoundingBox.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/BlockBoundingBox.java @@ -19,48 +19,97 @@ package de.steamwar.bausystem.features.slaves.laufbau; -import com.sk89q.worldedit.blocks.SkullBlock; -import com.sk89q.worldedit.world.block.BaseBlock; -import de.steamwar.inventory.SWItem; +import de.steamwar.bausystem.BauSystem; +import de.steamwar.linkage.Linked; +import de.steamwar.linkage.api.Enable; import lombok.Getter; import lombok.ToString; +import net.minecraft.core.BlockPos; +import net.minecraft.locale.Language; +import net.minecraft.world.level.EmptyBlockGetter; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.phys.shapes.CollisionContext; import org.bukkit.Material; -import org.bukkit.block.BlockFace; -import org.bukkit.block.data.Bisected; +import org.bukkit.Registry; import org.bukkit.block.data.BlockData; -import org.bukkit.block.data.Directional; import org.bukkit.block.data.Waterlogged; -import org.bukkit.block.data.type.*; +import org.bukkit.block.data.type.Bed; +import org.bukkit.block.data.type.Door; +import org.bukkit.craftbukkit.block.CraftBlockType; +import org.bukkit.craftbukkit.block.data.CraftBlockData; +import org.bukkit.craftbukkit.util.CraftVoxelShape; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.VoxelShape; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; import java.util.List; -import java.util.Random; -import java.util.function.Consumer; - -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.createItem; -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.pixelCuboid; +import java.util.stream.Collectors; @ToString @Getter public class BlockBoundingBox { - private static List randomPlayerHead = new ArrayList<>(); - private static Random random = new Random(); + @Linked + public static class Loader implements Enable { - static { - randomPlayerHead.add("zOnlyKroks"); - randomPlayerHead.add("YoyoNow"); - randomPlayerHead.add("Konstruktix"); - randomPlayerHead.add("Merling03"); - randomPlayerHead.add("Lord_Loading"); - /* - randomPlayerHead.add(UUID.fromString("1623d4b1-b21c-41d3-93c2-eee2845b8497")); - randomPlayerHead.add(UUID.fromString("f75632be-e3ec-4069-9bec-d13ac6891177")); - randomPlayerHead.add(UUID.fromString("80382970-424b-4299-9296-d956beab9e7b")); - randomPlayerHead.add(UUID.fromString("f395ce89-096d-48e9-b796-a2d88722c22b")); - randomPlayerHead.add(UUID.fromString("db516394-d999-4b77-8d13-73cceec77dfe")); - */ + @Override + public void enable() { + long time = System.currentTimeMillis(); + Registry.BLOCK.forEach(blockType -> { + Block block = CraftBlockType.bukkitToMinecraftNew(blockType); + Material material = blockType.asMaterial(); + if (!material.isItem()) return; + if (!material.isBlock()) return; + if (material == Material.WATER) return; + if (material.getBlastResistance() <= 0.0) return; + if (material.getBlastResistance() > 9.0) return; + + block.getStateDefinition().getPossibleStates().forEach(blockState -> { + VoxelShape voxelShape = new CraftVoxelShape(blockState.getCollisionShape(EmptyBlockGetter.INSTANCE, BlockPos.ZERO, CollisionContext.empty())); + Collection boundingBoxes = voxelShape.getBoundingBoxes(); + if (boundingBoxes.isEmpty()) return; + + double minX = Double.MAX_VALUE; + double minY = Double.MAX_VALUE; + double minZ = Double.MAX_VALUE; + double maxX = Double.MIN_VALUE; + double maxY = Double.MIN_VALUE; + double maxZ = Double.MIN_VALUE; + for (BoundingBox boundingBox : boundingBoxes) { + minX = Math.min(minX, boundingBox.getMinX()); + minY = Math.min(minY, boundingBox.getMinY()); + minZ = Math.min(minZ, boundingBox.getMinZ()); + + maxX = Math.max(maxX, boundingBox.getMaxX()); + maxY = Math.max(maxY, boundingBox.getMaxY()); + maxZ = Math.max(maxZ, boundingBox.getMaxZ()); + } + + // Ignore Full Blocks + if (maxX - minX == 1 && maxY - minY == 1 && maxZ - minZ == 1) return; + + BlockData blockData = CraftBlockData.createData(blockState); + if (blockData instanceof Door) return; // Doors dont seem to be 2 blocks tall? + if (blockData instanceof Bed) return; // Beds dont seem to be 2 blocks tall? + if (blockData instanceof Waterlogged waterlogged && waterlogged.isWaterlogged()) return; + + String key = material.isItem() + ? material.getItemTranslationKey() // "item.minecraft.diamond" / "block.minecraft.stone" + : material.getBlockTranslationKey(); // for block-only materials like WATER + + new BlockBoundingBox( + blockData, + boundingBoxes.stream().map(Cuboid::new).collect(Collectors.toList()), + material, + "§f" + Language.getInstance().getOrDefault(key) + ); + }); + }); + BauSystem.getInstance().getLogger().info("Laufbau init > " + (System.currentTimeMillis() - time) + "ms " + elements.size() + " blocks"); + elements.sort(Comparator.comparingDouble(value -> value.material.getBlastResistance()).thenComparing(value -> value.name)); + } } public static List elements = new ArrayList<>(); @@ -68,221 +117,32 @@ public class BlockBoundingBox { public BlockData blockData; public List cuboidList; private double volume; - public SWItem swItem; + public Material material; + public String name; - public Consumer blockConsumer = null; - - public BlockBoundingBox(BlockData blockData, List cuboidList, SWItem swItem) { + public BlockBoundingBox(BlockData blockData, List cuboidList, Material material, String name) { this.blockData = blockData; this.cuboidList = cuboidList; - this.swItem = swItem; + this.material = material; + this.name = name; volume = cuboidList.stream().mapToDouble(Cuboid::volume).sum(); - elements.add(this); - } - public BlockBoundingBox(BlockData blockData, List cuboidList, SWItem swItem, Consumer blockConsumer) { - this.blockData = blockData; - this.cuboidList = cuboidList; - this.swItem = swItem; - volume = cuboidList.stream().mapToDouble(Cuboid::volume).sum(); + // Override/Ignore items with worse/better blast resistance + for (int i = 0; i < elements.size(); i++) { + BlockBoundingBox other = elements.get(i); + if (other.cuboidList.equals(cuboidList)) { + if (other.material.getBlastResistance() < material.getBlastResistance()) { + elements.remove(i); + break; + } else { + return; + } + } + } elements.add(this); - this.blockConsumer = blockConsumer; } public double volume() { return volume; } - - static { - addPixel(Material.AIR.createBlockData(), 0, 0, 0, 0, 0, 0, null); - // addPixel(Material.COBWEB.createBlockData(), 0, 0, 0, 0, 0, 0, createItem("LAUFBAU_BLOCK_COBWEB", Material.COBWEB)); - addPixel(Material.END_STONE.createBlockData(), 0, 0, 0, 16, 16, 16, null); - - addPixel(Material.DIRT_PATH.createBlockData(), 0, 0, 0, 16, 15, 16, createItem("LAUFBAU_BLOCK_GRASS_PATH", Material.DIRT_PATH)); - addPixel(Material.MUD.createBlockData(), 0, 0, 0, 16, 14, 16, createItem("LAUFBAU_BLOCK_SOUL_SAND", Material.SOUL_SAND)); - - Cocoa cocoaNorth = (Cocoa) Material.COCOA.createBlockData(); - cocoaNorth.setAge(2); - cocoaNorth.setFacing(BlockFace.NORTH); - addPixel(cocoaNorth, 4, 3, 1, 8, 9, 8, createItem("LAUFBAU_BLOCK_COCOA", Material.COCOA_BEANS, "LAUFBAU_FACING_NORTH")); - - Cocoa cocoaSouth = (Cocoa) Material.COCOA.createBlockData(); - cocoaSouth.setAge(2); - cocoaSouth.setFacing(BlockFace.SOUTH); - addPixel(cocoaSouth, 4, 3, 7, 8, 9, 8, createItem("LAUFBAU_BLOCK_COCOA", Material.COCOA_BEANS, "LAUFBAU_FACING_SOUTH")); - - Cocoa cocoaWest = (Cocoa) Material.COCOA.createBlockData(); - cocoaWest.setAge(2); - cocoaWest.setFacing(BlockFace.WEST); - addPixel(cocoaWest, 1, 3, 4, 8, 9, 8, createItem("LAUFBAU_BLOCK_COCOA", Material.COCOA_BEANS, "LAUFBAU_FACING_WEST")); - - Cocoa cocoaEast = (Cocoa) Material.COCOA.createBlockData(); - cocoaEast.setAge(2); - cocoaEast.setFacing(BlockFace.EAST); - addPixel(cocoaEast, 7, 3, 4, 8, 9, 8, createItem("LAUFBAU_BLOCK_COCOA", Material.COCOA_BEANS, "LAUFBAU_FACING_EAST")); - - TurtleEgg singleTurtleEgg = (TurtleEgg) Material.TURTLE_EGG.createBlockData(); - singleTurtleEgg.setEggs(1); - singleTurtleEgg.setHatch(0); - addPixel(singleTurtleEgg, 4, 0, 3, 9, 7, 9, createItem("LAUFBAU_BLOCK_TURTLE_EGG", Material.TURTLE_EGG, "LAUFBAU_COUNT_1")); - - TurtleEgg doubleTurtleEgg = (TurtleEgg) Material.TURTLE_EGG.createBlockData(); - doubleTurtleEgg.setEggs(2); - doubleTurtleEgg.setHatch(0); - addPixel(doubleTurtleEgg, 1, 0, 1, 14, 7, 14, createItem("LAUFBAU_BLOCK_TURTLE_EGG", Material.TURTLE_EGG, "LAUFBAU_COUNT_2")); - - addPixel(Material.CHEST.createBlockData(), 1, 0, 1, 14, 14, 14, createItem("LAUFBAU_BLOCK_CHEST", Material.CHEST)); - - Snow snowLayers8 = (Snow) Material.SNOW.createBlockData(); - snowLayers8.setLayers(8); - addPixel(snowLayers8, 0, 0, 0, 16, 14, 16, createItem("LAUFBAU_BLOCK_SNOW", Material.SNOW, "LAUFBAU_LAYERS_8")); - - Directional headNorth = (Directional) Material.PLAYER_WALL_HEAD.createBlockData(); - headNorth.setFacing(BlockFace.NORTH); - addPixel(headNorth, 4, 4, 8, 8, 8, 8, createItem("LAUFBAU_BLOCK_PLAYER_WALL_HEAD", Material.PLAYER_HEAD, "LAUFBAU_FACING_NORTH"), BlockBoundingBox::randomPlayerHead); - - Directional headSouth = (Directional) Material.PLAYER_WALL_HEAD.createBlockData(); - headSouth.setFacing(BlockFace.SOUTH); - addPixel(headSouth, 4, 4, 0, 8, 8, 8, createItem("LAUFBAU_BLOCK_PLAYER_WALL_HEAD", Material.PLAYER_HEAD, "LAUFBAU_FACING_SOUTH"), BlockBoundingBox::randomPlayerHead); - - Directional headWest = (Directional) Material.PLAYER_WALL_HEAD.createBlockData(); - headWest.setFacing(BlockFace.WEST); - addPixel(headWest, 8, 4, 4, 8, 8, 8, createItem("LAUFBAU_BLOCK_PLAYER_WALL_HEAD", Material.PLAYER_HEAD, "LAUFBAU_FACING_WEST"), BlockBoundingBox::randomPlayerHead); - - Directional headEast = (Directional) Material.PLAYER_WALL_HEAD.createBlockData(); - headEast.setFacing(BlockFace.EAST); - addPixel(headEast, 0, 4, 4, 8, 8, 8, createItem("LAUFBAU_BLOCK_PLAYER_WALL_HEAD", Material.PLAYER_HEAD, "LAUFBAU_FACING_EAST"), BlockBoundingBox::randomPlayerHead); - - Snow snowLayers7 = (Snow) Material.SNOW.createBlockData(); - snowLayers7.setLayers(7); - addPixel(snowLayers7, 0, 0, 0, 16, 12, 16, createItem("LAUFBAU_BLOCK_SNOW", Material.SNOW, "LAUFBAU_LAYERS_7")); - - Snow snowLayers6 = (Snow) Material.SNOW.createBlockData(); - snowLayers6.setLayers(6); - addPixel(snowLayers6, 0, 0, 0, 16, 10, 16, createItem("LAUFBAU_BLOCK_SNOW", Material.SNOW, "LAUFBAU_LAYERS_6")); - - addPixel(Material.STONECUTTER.createBlockData(), 0, 0, 0, 16, 9, 16, createItem("LAUFBAU_BLOCK_STONECUTTER", Material.STONECUTTER)); - - addPixel(Material.PLAYER_HEAD.createBlockData(), 4, 0, 4, 8, 8, 8, createItem("LAUFBAU_BLOCK_PLAYER_HEAD", Material.PLAYER_HEAD), BlockBoundingBox::randomPlayerHead); - - addPixel(Material.CAKE.createBlockData(), 1, 0, 1, 14, 8, 14, createItem("LAUFBAU_BLOCK_CAKE", Material.CAKE)); - - Slab bottomSlab = (Slab) Material.END_STONE_BRICK_SLAB.createBlockData(); - bottomSlab.setType(Slab.Type.BOTTOM); - addPixel(bottomSlab, 0, 0, 0, 16, 8, 16, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_SLAB", Material.END_STONE_BRICK_SLAB, "LAUFBAU_TYPE_BOTTOM")); - - Slab topSlab = (Slab) Material.END_STONE_BRICK_SLAB.createBlockData(); - topSlab.setType(Slab.Type.TOP); - addPixel(topSlab, 0, 8, 0, 16, 8, 16, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_SLAB", Material.END_STONE_BRICK_SLAB, "LAUFBAU_TYPE_TOP")); - - SeaPickle quadSeaPickle = (SeaPickle) Material.SEA_PICKLE.createBlockData(); - quadSeaPickle.setWaterlogged(false); - quadSeaPickle.setPickles(4); - addPixel(quadSeaPickle, 2, 0, 2, 12, 7, 12, createItem("LAUFBAU_BLOCK_SEA_PICKLE", Material.SEA_PICKLE, "LAUFBAU_COUNT_4")); - - Campfire campfire = (Campfire) Material.CAMPFIRE.createBlockData(); - campfire.setSignalFire(false); - campfire.setLit(false); - addPixel(campfire, 0, 0, 0, 16, 7, 16, createItem("LAUFBAU_BLOCK_CAMPFIRE", Material.CAMPFIRE)); - - SeaPickle trippleSeaPickle = (SeaPickle) Material.SEA_PICKLE.createBlockData(); - trippleSeaPickle.setWaterlogged(false); - trippleSeaPickle.setPickles(3); - addPixel(trippleSeaPickle, 2, 0, 2, 12, 6, 12, createItem("LAUFBAU_BLOCK_SEA_PICKLE", Material.SEA_PICKLE, "LAUFBAU_COUNT_3")); - - SeaPickle doubleSeaPickle = (SeaPickle) Material.SEA_PICKLE.createBlockData(); - doubleSeaPickle.setWaterlogged(false); - doubleSeaPickle.setPickles(2); - addPixel(doubleSeaPickle, 3, 0, 3, 10, 6, 10, createItem("LAUFBAU_BLOCK_SEA_PICKLE", Material.SEA_PICKLE, "LAUFBAU_COUNT_2")); - - SeaPickle singleSeaPickle = (SeaPickle) Material.SEA_PICKLE.createBlockData(); - singleSeaPickle.setWaterlogged(false); - singleSeaPickle.setPickles(1); - addPixel(singleSeaPickle, 6, 0, 6, 4, 6, 4, createItem("LAUFBAU_BLOCK_SEA_PICKLE", Material.SEA_PICKLE, "LAUFBAU_COUNT_1")); - - addPixel(Material.FLOWER_POT.createBlockData(), 5, 0, 5, 6, 6, 6, createItem("LAUFBAU_BLOCK_FLOWER_POT", Material.FLOWER_POT)); - - Snow snowLayers3 = (Snow) Material.SNOW.createBlockData(); - snowLayers3.setLayers(3); - addPixel(snowLayers3, 0, 0, 0, 16, 4, 16, createItem("LAUFBAU_BLOCK_SNOW", Material.SNOW, "LAUFBAU_LAYERS_3")); - - TrapDoor bottomTrapDoor = (TrapDoor) Material.IRON_TRAPDOOR.createBlockData(); - bottomTrapDoor.setHalf(Bisected.Half.BOTTOM); - addPixel(bottomTrapDoor, 0, 0, 0, 16, 3, 16, createItem("LAUFBAU_BLOCK_IRON_TRAPDOOR", Material.IRON_TRAPDOOR, "LAUFBAU_HALF_BOTTOM")); - - TrapDoor topTrapDoor = (TrapDoor) Material.IRON_TRAPDOOR.createBlockData(); - topTrapDoor.setHalf(Bisected.Half.TOP); - addPixel(topTrapDoor, 0, 13, 0, 16, 3, 16, createItem("LAUFBAU_BLOCK_IRON_TRAPDOOR", Material.IRON_TRAPDOOR, "LAUFBAU_HALF_TOP")); - - Snow snowLayers2 = (Snow) Material.SNOW.createBlockData(); - snowLayers2.setLayers(2); - addPixel(snowLayers2, 0, 0, 0, 16, 2, 16, createItem("LAUFBAU_BLOCK_SNOW", Material.SNOW, "LAUFBAU_LAYERS_2")); - - addPixel(Material.LILY_PAD.createBlockData(), 1, 0, 1, 14, 1.5, 14, createItem("LAUFBAU_BLOCK_LILY_PAD", Material.LILY_PAD)); - addPixel(Material.WHITE_CARPET.createBlockData(), 0, 0, 0, 16, 1, 16, createItem("LAUFBAU_BLOCK_WHITE_CARPET", Material.WHITE_CARPET)); - - Directional endRodBottomTop = (Directional) Material.END_ROD.createBlockData(); - endRodBottomTop.setFacing(BlockFace.UP); - addPixel(endRodBottomTop, 6, 0, 6, 4, 16, 4, createItem("LAUFBAU_BLOCK_END_ROD", Material.END_ROD, "LAUFBAU_FACING_UP", "LAUFBAU_FACING_DOWN")); - - Directional endRodNorthSouth = (Directional) Material.END_ROD.createBlockData(); - endRodNorthSouth.setFacing(BlockFace.NORTH); - addPixel(endRodNorthSouth, 6, 6, 0, 4, 4, 16, createItem("LAUFBAU_BLOCK_END_ROD", Material.END_ROD, "LAUFBAU_FACING_NORTH", "LAUFBAU_FACING_SOUTH")); - - Directional endRodEastWest = (Directional) Material.END_ROD.createBlockData(); - endRodEastWest.setFacing(BlockFace.EAST); - addPixel(endRodEastWest, 0, 6, 6, 16, 4, 4, createItem("LAUFBAU_BLOCK_END_ROD", Material.END_ROD, "LAUFBAU_FACING_EAST", "LAUFBAU_FACING_WEST")); - - Directional lightningRodBottomTop = (Directional) Material.LIGHTNING_ROD.createBlockData(); - lightningRodBottomTop.setFacing(BlockFace.UP); - addPixel(lightningRodBottomTop, 6, 0, 6, 4, 16, 4, createItem("LAUFBAU_BLOCK_LIGHTNING_ROD", Material.LIGHTNING_ROD, "LAUFBAU_FACING_UP", "LAUFBAU_FACING_DOWN")); - - Directional lightningRodNorthSouth = (Directional) Material.LIGHTNING_ROD.createBlockData(); - lightningRodNorthSouth.setFacing(BlockFace.NORTH); - addPixel(lightningRodNorthSouth, 6, 6, 0, 4, 4, 16, createItem("LAUFBAU_BLOCK_LIGHTNING_ROD", Material.LIGHTNING_ROD, "LAUFBAU_FACING_NORTH", "LAUFBAU_FACING_SOUTH")); - - Directional lightningRodEastWest = (Directional) Material.LIGHTNING_ROD.createBlockData(); - lightningRodEastWest.setFacing(BlockFace.EAST); - addPixel(lightningRodEastWest, 0, 6, 6, 16, 4, 4, createItem("LAUFBAU_BLOCK_LIGHTNING_ROD", Material.LIGHTNING_ROD, "LAUFBAU_FACING_EAST", "LAUFBAU_FACING_WEST")); - - Waterlogged conduit = (Waterlogged) Material.CONDUIT.createBlockData(); - conduit.setWaterlogged(false); - addPixel(conduit, 5, 5, 5, 6, 6, 6, createItem("LAUFBAU_BLOCK_CONDUIT", Material.CONDUIT)); - - TrapDoor northTrapDoor = (TrapDoor) Material.IRON_TRAPDOOR.createBlockData(); - northTrapDoor.setFacing(BlockFace.NORTH); - northTrapDoor.setOpen(true); - addPixel(northTrapDoor, 0, 0, 13, 16, 16, 3, createItem("LAUFBAU_BLOCK_IRON_TRAPDOOR", Material.IRON_TRAPDOOR, "LAUFBAU_FACING_NORTH", "LAUFBAU_OPEN")); - - TrapDoor southTrapDoor = (TrapDoor) Material.IRON_TRAPDOOR.createBlockData(); - southTrapDoor.setFacing(BlockFace.SOUTH); - southTrapDoor.setOpen(true); - addPixel(southTrapDoor, 0, 0, 0, 16, 16, 3, createItem("LAUFBAU_BLOCK_IRON_TRAPDOOR", Material.IRON_TRAPDOOR, "LAUFBAU_FACING_SOUTH", "LAUFBAU_OPEN")); - - TrapDoor westTrapDoor = (TrapDoor) Material.IRON_TRAPDOOR.createBlockData(); - westTrapDoor.setFacing(BlockFace.WEST); - westTrapDoor.setOpen(true); - addPixel(westTrapDoor, 13, 0, 0, 3, 16, 16, createItem("LAUFBAU_BLOCK_IRON_TRAPDOOR", Material.IRON_TRAPDOOR, "LAUFBAU_FACING_WEST", "LAUFBAU_OPEN")); - - TrapDoor eastTrapDoor = (TrapDoor) Material.IRON_TRAPDOOR.createBlockData(); - eastTrapDoor.setFacing(BlockFace.EAST); - eastTrapDoor.setOpen(true); - addPixel(eastTrapDoor, 0, 0, 0, 3, 16, 16, createItem("LAUFBAU_BLOCK_IRON_TRAPDOOR", Material.IRON_TRAPDOOR, "LAUFBAU_FACING_EAST", "LAUFBAU_OPEN")); - } - - private static void addPixel(BlockData blockData, double xPixel, double yPixel, double zPixel, double dxPixel, double dyPixel, double dzPixel, SWItem swItem) { - new BlockBoundingBox(blockData, Arrays.asList(pixelCuboid(xPixel, yPixel, zPixel, dxPixel, dyPixel, dzPixel)), swItem); - } - - private static void addPixel(BlockData blockData, double xPixel, double yPixel, double zPixel, double dxPixel, double dyPixel, double dzPixel, SWItem swItem, Consumer blockConsumer) { - new BlockBoundingBox(blockData, Arrays.asList(pixelCuboid(xPixel, yPixel, zPixel, dxPixel, dyPixel, dzPixel)), swItem, blockConsumer); - } - - @SuppressWarnings("deprecation") - private static void randomPlayerHead(BaseBlock block) { - if (block instanceof SkullBlock) { - SkullBlock skullBlock = (SkullBlock) block; - skullBlock.setOwner(randomPlayerHead.get(random.nextInt(randomPlayerHead.size())).toString()); - } - } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/BoundingBoxLoader.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/BoundingBoxLoader.java deleted file mode 100644 index 899c951c..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/BoundingBoxLoader.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 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.slaves.laufbau; - -public interface BoundingBoxLoader { - - void load(); -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/Cuboid.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/Cuboid.java index ec959a69..877fef60 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/Cuboid.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/Cuboid.java @@ -22,6 +22,7 @@ package de.steamwar.bausystem.features.slaves.laufbau; import de.steamwar.bausystem.region.Point; import lombok.AllArgsConstructor; import lombok.Data; +import org.bukkit.util.BoundingBox; @Data @AllArgsConstructor @@ -33,6 +34,15 @@ public class Cuboid { private double dy; private double dz; + public Cuboid(BoundingBox boundingBox) { + this.x = boundingBox.getMinX(); + this.y = boundingBox.getMinY(); + this.z = boundingBox.getMinZ(); + this.dx = boundingBox.getWidthX(); + this.dy = boundingBox.getHeight(); + this.dz = boundingBox.getWidthZ(); + } + public boolean intersects(Cuboid cuboid) { double minx = x - cuboid.dx; double miny = y - cuboid.dy; diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/Laufbau.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/Laufbau.java index 9a82f3a5..311f907b 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/Laufbau.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/Laufbau.java @@ -29,11 +29,15 @@ import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.util.Vector; +import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; public class Laufbau { + private static final Comparator BLAST_RESISTANCE = Comparator.comparingDouble(value -> -value.material.getBlastResistance()); + private static final Comparator VOLUME = Comparator.comparingDouble(value -> -value.volume()); + private World world; private Location pos1; private Location pos2; @@ -61,26 +65,10 @@ public class Laufbau { editSession = WorldEditUtils.getEditSession(player); elements = BlockBoundingBox.elements.stream().filter(blockBoundingBox -> { - return !LaufbauUtils.isDeactivated(player, blockBoundingBox); + return !LaufbauConfig.STORE.get(player).getDeactivated().contains(blockBoundingBox.getMaterial()); }).collect(Collectors.toList()); - if (preferingBlastResistance) { - elements.sort((o1, o2) -> { - int compared = Double.compare(o1.blockData.getMaterial().getBlastResistance(), o2.blockData.getMaterial().getBlastResistance()); - if (compared != 0) { - return -compared; - } - return -Double.compare(o1.volume(), o2.volume()); - }); - } else { - elements.sort((o1, o2) -> { - int compared = Double.compare(o1.volume(), o2.volume()); - if (compared != 0) { - return -compared; - } - return -Double.compare(o1.blockData.getMaterial().getBlastResistance(), o2.blockData.getMaterial().getBlastResistance()); - }); - } + elements.sort(preferingBlastResistance ? BLAST_RESISTANCE.thenComparing(VOLUME) : VOLUME.thenComparing(BLAST_RESISTANCE)); active = new ProcessingTracesState(world, this::inRegion, editSession, elements, factor); } @@ -107,19 +95,8 @@ public class Laufbau { } private boolean inRegion(Vector location, int expansion) { - if (location.getBlockX() >= pos1.getBlockX() - expansion) { - if (location.getBlockY() >= pos1.getBlockY() - expansion) { - if (location.getBlockZ() >= pos1.getBlockZ() - expansion) { - if (location.getBlockX() <= pos2.getBlockX() + expansion) { - if (location.getBlockY() <= pos2.getBlockY() + expansion) { - if (location.getBlockZ() <= pos2.getBlockZ() + expansion) { - return true; - } - } - } - } - } - } - return false; + return location.getBlockX() >= pos1.getBlockX() - expansion && location.getBlockX() <= pos2.getBlockX() + expansion + && location.getBlockY() >= pos1.getBlockY() - expansion && location.getBlockY() <= pos2.getBlockY() + expansion + && location.getBlockZ() >= pos1.getBlockZ() - expansion && location.getBlockZ() <= pos2.getBlockZ() + expansion; } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauConfig.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauConfig.java index c422807c..0fe7223e 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauConfig.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauConfig.java @@ -21,10 +21,10 @@ package de.steamwar.bausystem.features.slaves.laufbau; import de.steamwar.bausystem.config.ConfigStore; import de.steamwar.bausystem.config.VersionedConfig; -import de.steamwar.bausystem.features.slaves.laufbau.converter.LaufbauConfigV1ToV2Converter; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; +import org.bukkit.Material; import java.util.HashSet; import java.util.Set; @@ -43,14 +43,9 @@ import java.util.Set; @Setter public class LaufbauConfig implements VersionedConfig { - public static final int VERSION = 2; + public static final int VERSION = 3; - /** - * LaufbauConfigV1 is stored as Base64 of gzipped YAPION, the current version as JSON - */ public static final ConfigStore STORE = new ConfigStore<>(LaufbauConfig.class, VERSION, LaufbauConfig::new) - .legacy(s -> !s.startsWith("{"), LaufbauConfigV1::parse) - .converter(new LaufbauConfigV1ToV2Converter()) .userConfig("bausystem-laufbau") .removeIf(config -> config.getDeactivated().isEmpty()); @@ -58,7 +53,7 @@ public class LaufbauConfig implements VersionedConfig { protected int version = VERSION; /** - * Identifiers of the deactivated {@link BlockBoundingBox}es, see {@link LaufbauUtils#identifier(BlockBoundingBox)} + * Materials of the deactivated {@link BlockBoundingBox}es */ - protected Set deactivated = new HashSet<>(); + protected Set deactivated = new HashSet<>(); } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauConfigV1.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauConfigV1.java deleted file mode 100644 index d372ed5d..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauConfigV1.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.slaves.laufbau; - -import de.steamwar.bausystem.config.VersionedConfig; -import de.steamwar.bausystem.config.legacy.YapionReader; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.Setter; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.HashSet; -import java.util.Set; -import java.util.zip.GZIPInputStream; - -/** - * The legacy laufbau config that was stored as Base64 of the gzipped YAPION, - * an object with the deactivated identifiers as keys. - */ -@Getter -@Setter -@Deprecated -public class LaufbauConfigV1 implements VersionedConfig { - - public static final int VERSION = 1; - - @Setter(AccessLevel.NONE) - protected int version = VERSION; - - protected Set deactivated = new HashSet<>(); - - public static LaufbauConfigV1 parse(String base64) { - String yapion; - try (InputStream inputStream = new GZIPInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(base64)))) { - yapion = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - LaufbauConfigV1 config = new LaufbauConfigV1(); - config.deactivated.addAll(YapionReader.parse(yapion).keySet()); - return config; - } -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauSettings.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauSettings.java index 88c91fc2..b15c51bd 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauSettings.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauSettings.java @@ -21,7 +21,6 @@ package de.steamwar.bausystem.features.slaves.laufbau; import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.shared.Pair; -import de.steamwar.data.CMDs; import de.steamwar.inventory.SWItem; import de.steamwar.inventory.SWListInv; import org.bukkit.Material; @@ -32,13 +31,11 @@ import java.util.*; public class LaufbauSettings { - private static Map, List> groupMap = new LinkedHashMap<>(); + private static Set> groups = new LinkedHashSet<>(); static { BlockBoundingBox.elements.forEach(blockBoundingBox -> { - if (blockBoundingBox.getSwItem() == null) return; - String displayName = blockBoundingBox.getSwItem().getItemMeta().getDisplayName(); - groupMap.computeIfAbsent(new Pair<>(blockBoundingBox.getSwItem().getItemStack().getType(), displayName), k -> new ArrayList<>()).add(blockBoundingBox); + groups.add(new Pair<>(blockBoundingBox.getMaterial(), blockBoundingBox.getName())); }); } @@ -50,96 +47,35 @@ public class LaufbauSettings { } public void open() { - List, List>>> list = new ArrayList<>(); - for (Map.Entry, List> entry : groupMap.entrySet()) { - SWItem swItem = createItem(entry.getKey().getKey(), entry.getKey().getValue(), entry.getValue()); - list.add(new SWListInv.SWListEntry<>(swItem, entry)); + List>> list = new ArrayList<>(); + for (Pair group : groups) { + list.add(new SWListInv.SWListEntry<>(createItem(group.getKey(), group.getValue()), group)); } - SWListInv, List>> inv = new SWListInv<>(p, BauSystem.MESSAGE.parse("LAUFBAU_SETTINGS_GUI_NAME", p), false, list, (clickType, entry) -> { - if (entry.getValue().size() == 1) { - LaufbauUtils.toggle(p, entry.getValue().get(0)); - open(); - return; - } - if (clickType.isLeftClick()) { - open(entry.getKey()); - return; - } - toggle(entry.getValue()); + SWListInv> inv = new SWListInv<>(p, BauSystem.MESSAGE.parse("LAUFBAU_SETTINGS_GUI_NAME", p), false, list, (clickType, group) -> { + toggle(group.getKey()); open(); }); inv.open(); } - public void open(Pair key) { - List blockBoundingBoxes = groupMap.get(key); - List> list = new ArrayList<>(); - for (BlockBoundingBox bb : blockBoundingBoxes) { - SWItem swItem = LaufbauUtils.translateItem(bb.getSwItem(), p); - ItemMeta itemMeta = swItem.getItemMeta(); - List lore = new ArrayList<>(); - if (itemMeta.getLore() != null) { - lore = itemMeta.getLore(); - } - lore.add(""); - lore.add(BauSystem.MESSAGE.parse(LaufbauUtils.isDeactivated(p, bb) ? "LAUFBAU_SETTINGS_INACTIVE" : "LAUFBAU_SETTINGS_ACTIVE", p)); - itemMeta.setLore(lore); - swItem.setItemMeta(itemMeta); - list.add(new SWListInv.SWListEntry<>(swItem, bb)); + public SWItem createItem(Material material, String name) { + SWItem swItem = new SWItem(material, name); + ItemMeta itemMeta = swItem.getItemMeta(); + List lore = new ArrayList<>(); + if (itemMeta != null && itemMeta.getLore() != null) { + lore = itemMeta.getLore(); } - SWListInv inv = new SWListInv<>(p, BauSystem.MESSAGE.parse("LAUFBAU_SETTINGS_GUI_NAME", p), false, list, (clickType, blockBoundingBox) -> { - LaufbauUtils.toggle(p, blockBoundingBox); - open(key); - }); - inv.setItem(49, new SWItem(Material.ARROW, BauSystem.MESSAGE.parse("LAUFBAU_SETTINGS_GUI_BACK", p), clickType -> { - open(); - }).setCustomModelData(CMDs.BACK)); - inv.open(); + lore.add(""); + lore.add(BauSystem.MESSAGE.parse(LaufbauConfig.STORE.get(p).getDeactivated().contains(material) ? "LAUFBAU_SETTINGS_INACTIVE" : "LAUFBAU_SETTINGS_ACTIVE", p)); + lore.add(""); + lore.add(BauSystem.MESSAGE.parse("LAUFBAU_SETTINGS_TOGGLE", p)); + if (itemMeta != null) itemMeta.setLore(lore); + swItem.setItemMeta(itemMeta); + return swItem; } - public SWItem createItem(Material material, String group, List blockBoundingBoxes) { - if (blockBoundingBoxes.size() == 1) { - BlockBoundingBox bb = blockBoundingBoxes.get(0); - SWItem swItem = LaufbauUtils.translateItem(bb.getSwItem(), p); - ItemMeta itemMeta = swItem.getItemMeta(); - List lore = new ArrayList<>(); - if (itemMeta.getLore() != null) { - lore = itemMeta.getLore(); - } - lore.add(""); - lore.add(BauSystem.MESSAGE.parse(LaufbauUtils.isDeactivated(p, bb) ? "LAUFBAU_SETTINGS_INACTIVE" : "LAUFBAU_SETTINGS_ACTIVE", p)); - lore.add(""); - lore.add(BauSystem.MESSAGE.parse("LAUFBAU_SETTINGS_TOGGLE", p)); - itemMeta.setLore(lore); - swItem.setItemMeta(itemMeta); - return swItem; - } else { - long count = blockBoundingBoxes.stream().filter(bb -> LaufbauUtils.isDeactivated(p, bb)).count(); - if (count == 0) { - return new SWItem(material, BauSystem.MESSAGE.parse(group, p), Arrays.asList("", BauSystem.MESSAGE.parse("LAUFBAU_SETTINGS_ACTIVE", p), "", BauSystem.MESSAGE.parse("LAUFBAU_SETTINGS_ADVANCED", p), BauSystem.MESSAGE.parse("LAUFBAU_SETTINGS_TOGGLE", p)), false, clickType -> { - }); - } - if (count == blockBoundingBoxes.size()) { - return new SWItem(material, BauSystem.MESSAGE.parse(group, p), Arrays.asList("", BauSystem.MESSAGE.parse("LAUFBAU_SETTINGS_INACTIVE", p), "", BauSystem.MESSAGE.parse("LAUFBAU_SETTINGS_ADVANCED", p), BauSystem.MESSAGE.parse("LAUFBAU_SETTINGS_TOGGLE", p)), false, clickType -> { - }); - } - return new SWItem(material, BauSystem.MESSAGE.parse(group, p), Arrays.asList("", BauSystem.MESSAGE.parse("LAUFBAU_SETTINGS_MIXED", p, blockBoundingBoxes.size() - count, blockBoundingBoxes.size()), "", BauSystem.MESSAGE.parse("LAUFBAU_SETTINGS_ADVANCED", p), BauSystem.MESSAGE.parse("LAUFBAU_SETTINGS_TOGGLE", p)), false, clickType -> { - }); - } - } - - public void toggle(List blockBoundingBoxes) { - long count = blockBoundingBoxes.stream().filter(bb -> LaufbauUtils.isDeactivated(p, bb)).count(); - if (count != blockBoundingBoxes.size()) { - blockBoundingBoxes.forEach(bb -> { - if (!LaufbauUtils.isDeactivated(p, bb)) { - LaufbauUtils.toggle(p, bb); - } - }); - } else { - blockBoundingBoxes.forEach(bb -> { - LaufbauUtils.toggle(p, bb); - }); - } + public void toggle(Material material) { + Set deactivated = LaufbauConfig.STORE.get(p).getDeactivated(); + if (!deactivated.remove(material)) deactivated.add(material); } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauUtils.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauUtils.java deleted file mode 100644 index 45b94482..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/LaufbauUtils.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 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.slaves.laufbau; - -import de.steamwar.bausystem.BauSystem; -import de.steamwar.inventory.SWItem; -import lombok.SneakyThrows; -import lombok.experimental.UtilityClass; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.inventory.meta.ItemMeta; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -@UtilityClass -public class LaufbauUtils { - - public static Cuboid pixelCuboid(double pixelX, double pixelY, double pixelZ, double pixelDX, double pixelDY, double pixelDZ) { - return new Cuboid(pixelToBlock(pixelX), pixelToBlock(pixelY), pixelToBlock(pixelZ), pixelToBlock(pixelDX), pixelToBlock(pixelDY), pixelToBlock(pixelDZ)); - } - - public static double pixelToBlock(double pixel) { - return 1.0 / 16 * pixel; - } - - public static SWItem createItem(String name, Material material, String... lore) { - return new SWItem(material, name, Arrays.asList(lore), false, clickType -> { - }); - } - - public static SWItem translateItem(SWItem item, Player p) { - ItemMeta itemMeta = item.getItemMeta(); - List lore = new ArrayList<>(); - if (itemMeta.getLore() != null) { - lore = itemMeta.getLore().stream().map(s -> BauSystem.MESSAGE.parse(s, p)).collect(Collectors.toList()); - } - return new SWItem(item.getItemStack().getType(), BauSystem.MESSAGE.parse(itemMeta.getDisplayName(), p), lore, false, clickType -> { - }); - } - - public static String identifier(BlockBoundingBox bb) { - if (bb.getSwItem() == null) { - return null; - } - SWItem item = bb.getSwItem(); - ItemMeta itemMeta = item.getItemMeta(); - List lore = new ArrayList<>(); - if (itemMeta.getLore() != null) { - lore = itemMeta.getLore() - .stream() - .map(s -> s.substring(8)) - .collect(Collectors.toList()); - } - String name = itemMeta.getDisplayName().substring(14); - return name + "[" + String.join(",", lore) + "]"; - } - - @SneakyThrows - public static boolean isDeactivated(Player p, BlockBoundingBox bb) { - if (bb.getSwItem() == null) { - return false; - } - String identifier = identifier(bb); - return LaufbauConfig.STORE.get(p).getDeactivated().contains(identifier); - } - - @SneakyThrows - public static void toggle(Player p, BlockBoundingBox bb) { - if (bb.getSwItem() == null) { - return; - } - String identifier = identifier(bb); - Set deactivated = LaufbauConfig.STORE.get(p).getDeactivated(); - if (!deactivated.remove(identifier)) { - deactivated.add(identifier); - } - } -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/AmethystBoundingBox.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/AmethystBoundingBox.java deleted file mode 100644 index d7aab7b2..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/AmethystBoundingBox.java +++ /dev/null @@ -1,261 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 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.slaves.laufbau.boundingboxes; - -import de.steamwar.bausystem.features.slaves.laufbau.BlockBoundingBox; -import de.steamwar.bausystem.features.slaves.laufbau.BoundingBoxLoader; -import de.steamwar.bausystem.features.slaves.laufbau.Cuboid; -import de.steamwar.linkage.Linked; -import org.bukkit.Material; -import org.bukkit.block.BlockFace; -import org.bukkit.block.data.type.AmethystCluster; - -import java.util.ArrayList; -import java.util.List; - -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.createItem; -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.pixelCuboid; - -@Linked -public class AmethystBoundingBox implements BoundingBoxLoader { - - @Override - public void load() { - floorSmallAmethystBud(); - ceilingSmallAmethystBud(); - northSmallAmethystBud(); - southSmallAmethystBud(); - eastSmallAmethystBud(); - westSmallAmethystBud(); - - floorMediumAmethystBud(); - ceilingMediumAmethystBud(); - northMediumAmethystBud(); - southMediumAmethystBud(); - eastMediumAmethystBud(); - westMediumAmethystBud(); - - floorLargeAmethystBud(); - ceilingLargeAmethystBud(); - northLargeAmethystBud(); - southLargeAmethystBud(); - eastLargeAmethystBud(); - westLargeAmethystBud(); - - floorAmethystCluster(); - ceilingAmethystCluster(); - northAmethystCluster(); - southAmethystCluster(); - eastAmethystCluster(); - westAmethystCluster(); - } - - private void floorSmallAmethystBud() { - AmethystCluster cluster = (AmethystCluster) Material.SMALL_AMETHYST_BUD.createBlockData(); - cluster.setFacing(BlockFace.UP); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(4, 0, 4, 8, 3, 8)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_SMALL_AMETHYST_BUD", Material.SMALL_AMETHYST_BUD, "LAUFBAU_FACING_UP")); - } - - private void ceilingSmallAmethystBud() { - AmethystCluster cluster = (AmethystCluster) Material.SMALL_AMETHYST_BUD.createBlockData(); - cluster.setFacing(BlockFace.DOWN); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(4, 13, 4, 8, 3, 8)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_SMALL_AMETHYST_BUD", Material.SMALL_AMETHYST_BUD, "LAUFBAU_FACING_DOWN")); - } - - private void northSmallAmethystBud() { - AmethystCluster cluster = (AmethystCluster) Material.SMALL_AMETHYST_BUD.createBlockData(); - cluster.setFacing(BlockFace.NORTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(4, 4, 13, 8, 8, 3)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_SMALL_AMETHYST_BUD", Material.SMALL_AMETHYST_BUD, "LAUFBAU_FACING_NORTH")); - } - - private void southSmallAmethystBud() { - AmethystCluster cluster = (AmethystCluster) Material.SMALL_AMETHYST_BUD.createBlockData(); - cluster.setFacing(BlockFace.SOUTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(4, 4, 0, 8, 8, 3)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_SMALL_AMETHYST_BUD", Material.SMALL_AMETHYST_BUD, "LAUFBAU_FACING_SOUTH")); - } - - private void eastSmallAmethystBud() { - AmethystCluster cluster = (AmethystCluster) Material.SMALL_AMETHYST_BUD.createBlockData(); - cluster.setFacing(BlockFace.EAST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 4, 4, 3, 8, 8)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_SMALL_AMETHYST_BUD", Material.SMALL_AMETHYST_BUD, "LAUFBAU_FACING_EAST")); - } - - private void westSmallAmethystBud() { - AmethystCluster cluster = (AmethystCluster) Material.SMALL_AMETHYST_BUD.createBlockData(); - cluster.setFacing(BlockFace.WEST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(13, 4, 4, 3, 8, 8)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_SMALL_AMETHYST_BUD", Material.SMALL_AMETHYST_BUD, "LAUFBAU_FACING_WEST")); - } - - private void floorMediumAmethystBud() { - AmethystCluster cluster = (AmethystCluster) Material.MEDIUM_AMETHYST_BUD.createBlockData(); - cluster.setFacing(BlockFace.UP); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(3, 0, 3, 10, 4, 10)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_MEDIUM_AMETHYST_BUD", Material.MEDIUM_AMETHYST_BUD, "LAUFBAU_FACING_UP")); - } - - private void ceilingMediumAmethystBud() { - AmethystCluster cluster = (AmethystCluster) Material.MEDIUM_AMETHYST_BUD.createBlockData(); - cluster.setFacing(BlockFace.DOWN); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(3, 12, 3, 10, 4, 10)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_MEDIUM_AMETHYST_BUD", Material.MEDIUM_AMETHYST_BUD, "LAUFBAU_FACING_DOWN")); - } - - private void northMediumAmethystBud() { - AmethystCluster cluster = (AmethystCluster) Material.MEDIUM_AMETHYST_BUD.createBlockData(); - cluster.setFacing(BlockFace.NORTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(3, 3, 12, 10, 10, 4)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_MEDIUM_AMETHYST_BUD", Material.MEDIUM_AMETHYST_BUD, "LAUFBAU_FACING_NORTH")); - } - - private void southMediumAmethystBud() { - AmethystCluster cluster = (AmethystCluster) Material.MEDIUM_AMETHYST_BUD.createBlockData(); - cluster.setFacing(BlockFace.SOUTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(3, 3, 0, 10, 10, 4)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_MEDIUM_AMETHYST_BUD", Material.MEDIUM_AMETHYST_BUD, "LAUFBAU_FACING_SOUTH")); - } - - private void eastMediumAmethystBud() { - AmethystCluster cluster = (AmethystCluster) Material.MEDIUM_AMETHYST_BUD.createBlockData(); - cluster.setFacing(BlockFace.EAST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 3, 3, 4, 10, 10)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_MEDIUM_AMETHYST_BUD", Material.MEDIUM_AMETHYST_BUD, "LAUFBAU_FACING_EAST")); - } - - private void westMediumAmethystBud() { - AmethystCluster cluster = (AmethystCluster) Material.MEDIUM_AMETHYST_BUD.createBlockData(); - cluster.setFacing(BlockFace.WEST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(12, 3, 3, 4, 10, 10)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_MEDIUM_AMETHYST_BUD", Material.MEDIUM_AMETHYST_BUD, "LAUFBAU_FACING_WEST")); - } - - private void floorLargeAmethystBud() { - AmethystCluster cluster = (AmethystCluster) Material.LARGE_AMETHYST_BUD.createBlockData(); - cluster.setFacing(BlockFace.UP); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(3, 0, 3, 10, 5, 10)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_LARGE_AMETHYST_BUD", Material.LARGE_AMETHYST_BUD, "LAUFBAU_FACING_UP")); - } - - private void ceilingLargeAmethystBud() { - AmethystCluster cluster = (AmethystCluster) Material.LARGE_AMETHYST_BUD.createBlockData(); - cluster.setFacing(BlockFace.DOWN); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(3, 11, 3, 10, 5, 10)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_LARGE_AMETHYST_BUD", Material.LARGE_AMETHYST_BUD, "LAUFBAU_FACING_DOWN")); - } - - private void northLargeAmethystBud() { - AmethystCluster cluster = (AmethystCluster) Material.LARGE_AMETHYST_BUD.createBlockData(); - cluster.setFacing(BlockFace.NORTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(3, 3, 11, 10, 10, 5)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_LARGE_AMETHYST_BUD", Material.LARGE_AMETHYST_BUD, "LAUFBAU_FACING_NORTH")); - } - - private void southLargeAmethystBud() { - AmethystCluster cluster = (AmethystCluster) Material.LARGE_AMETHYST_BUD.createBlockData(); - cluster.setFacing(BlockFace.SOUTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(3, 3, 0, 10, 10, 5)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_LARGE_AMETHYST_BUD", Material.LARGE_AMETHYST_BUD, "LAUFBAU_FACING_SOUTH")); - } - - private void eastLargeAmethystBud() { - AmethystCluster cluster = (AmethystCluster) Material.LARGE_AMETHYST_BUD.createBlockData(); - cluster.setFacing(BlockFace.EAST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 3, 3, 5, 10, 10)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_LARGE_AMETHYST_BUD", Material.LARGE_AMETHYST_BUD, "LAUFBAU_FACING_EAST")); - } - - private void westLargeAmethystBud() { - AmethystCluster cluster = (AmethystCluster) Material.LARGE_AMETHYST_BUD.createBlockData(); - cluster.setFacing(BlockFace.WEST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(11, 3, 3, 5, 10, 10)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_LARGE_AMETHYST_BUD", Material.LARGE_AMETHYST_BUD, "LAUFBAU_FACING_WEST")); - } - - private void floorAmethystCluster() { - AmethystCluster cluster = (AmethystCluster) Material.AMETHYST_CLUSTER.createBlockData(); - cluster.setFacing(BlockFace.UP); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(3, 0, 3, 10, 7, 10)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_AMETHYST_CLUSTER", Material.AMETHYST_CLUSTER, "LAUFBAU_FACING_UP")); - } - - private void ceilingAmethystCluster() { - AmethystCluster cluster = (AmethystCluster) Material.AMETHYST_CLUSTER.createBlockData(); - cluster.setFacing(BlockFace.DOWN); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(3, 9, 3, 10, 7, 10)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_AMETHYST_CLUSTER", Material.AMETHYST_CLUSTER, "LAUFBAU_FACING_DOWN")); - } - - private void northAmethystCluster() { - AmethystCluster cluster = (AmethystCluster) Material.AMETHYST_CLUSTER.createBlockData(); - cluster.setFacing(BlockFace.NORTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(3, 3, 9, 10, 10, 7)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_AMETHYST_CLUSTER", Material.AMETHYST_CLUSTER, "LAUFBAU_FACING_NORTH")); - } - - private void southAmethystCluster() { - AmethystCluster cluster = (AmethystCluster) Material.AMETHYST_CLUSTER.createBlockData(); - cluster.setFacing(BlockFace.SOUTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(3, 3, 0, 10, 10, 7)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_AMETHYST_CLUSTER", Material.AMETHYST_CLUSTER, "LAUFBAU_FACING_SOUTH")); - } - - private void eastAmethystCluster() { - AmethystCluster cluster = (AmethystCluster) Material.AMETHYST_CLUSTER.createBlockData(); - cluster.setFacing(BlockFace.EAST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 3, 3, 7, 10, 10)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_AMETHYST_CLUSTER", Material.AMETHYST_CLUSTER, "LAUFBAU_FACING_EAST")); - } - - private void westAmethystCluster() { - AmethystCluster cluster = (AmethystCluster) Material.AMETHYST_CLUSTER.createBlockData(); - cluster.setFacing(BlockFace.WEST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(9, 3, 3, 7, 10, 10)); - new BlockBoundingBox(cluster, cuboidList, createItem("LAUFBAU_BLOCK_AMETHYST_CLUSTER", Material.AMETHYST_CLUSTER, "LAUFBAU_FACING_WEST")); - } -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/AzaleaBoundingBox.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/AzaleaBoundingBox.java deleted file mode 100644 index cc2368e7..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/AzaleaBoundingBox.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 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.slaves.laufbau.boundingboxes; - -import de.steamwar.bausystem.features.slaves.laufbau.BlockBoundingBox; -import de.steamwar.bausystem.features.slaves.laufbau.BoundingBoxLoader; -import de.steamwar.bausystem.features.slaves.laufbau.Cuboid; -import de.steamwar.linkage.Linked; -import org.bukkit.Material; -import org.bukkit.block.data.BlockData; - -import java.util.ArrayList; -import java.util.List; - -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.createItem; -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.pixelCuboid; - -@Linked -public class AzaleaBoundingBox implements BoundingBoxLoader { - - @Override - public void load() { - BlockData blockData = Material.FLOWERING_AZALEA.createBlockData(); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 8, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(6, 0, 6, 4, 8, 4)); - new BlockBoundingBox(blockData, cuboidList, createItem("LAUFBAU_BLOCK_AZALEA", Material.FLOWERING_AZALEA)); - } -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/BellBoundingBox.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/BellBoundingBox.java deleted file mode 100644 index eb202645..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/BellBoundingBox.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 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.slaves.laufbau.boundingboxes; - -import de.steamwar.bausystem.features.slaves.laufbau.BlockBoundingBox; -import de.steamwar.bausystem.features.slaves.laufbau.BoundingBoxLoader; -import de.steamwar.bausystem.features.slaves.laufbau.Cuboid; -import de.steamwar.linkage.Linked; -import org.bukkit.Material; -import org.bukkit.block.BlockFace; -import org.bukkit.block.data.type.Bell; - -import java.util.ArrayList; -import java.util.List; - -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.createItem; -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.pixelCuboid; - -@Linked -public class BellBoundingBox implements BoundingBoxLoader { - - @Override - public void load() { - ceilingBell(); - - floorNorthBell(); - floorEastBell(); - - doubleWallNorthBell(); - doubleWallEastBell(); - - singleWallNorthBell(); - singleWallSouthBell(); - singleWallEastBell(); - singleWallWestBell(); - } - - public void ceilingBell() { - Bell bell = (Bell) Material.BELL.createBlockData(); - bell.setAttachment(Bell.Attachment.CEILING); - bell.setFacing(BlockFace.NORTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(4, 4, 4, 8, 2, 8)); - cuboidList.add(pixelCuboid(5, 6, 5, 6, 8, 6)); - cuboidList.add(pixelCuboid(7, 13, 7, 2, 3, 2)); - new BlockBoundingBox(bell, cuboidList, createItem("LAUFBAU_BLOCK_BELL", Material.BELL, "LAUFBAU_ATTACHMENT_CEILING")); - } - - public void floorNorthBell() { - Bell bell = (Bell) Material.BELL.createBlockData(); - bell.setAttachment(Bell.Attachment.FLOOR); - bell.setFacing(BlockFace.NORTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 0, 4, 16, 16, 8)); - new BlockBoundingBox(bell, cuboidList, createItem("LAUFBAU_BLOCK_BELL", Material.BELL, "LAUFBAU_ATTACHMENT_FLOOR", "LAUFBAU_FACING_NORTH", "LAUFBAU_FACING_SOUTH")); - } - - public void floorEastBell() { - Bell bell = (Bell) Material.BELL.createBlockData(); - bell.setAttachment(Bell.Attachment.FLOOR); - bell.setFacing(BlockFace.EAST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(4, 0, 0, 8, 16, 16)); - new BlockBoundingBox(bell, cuboidList, createItem("LAUFBAU_BLOCK_BELL", Material.BELL, "LAUFBAU_ATTACHMENT_FLOOR", "LAUFBAU_FACING_EAST", "LAUFBAU_FACING_WEST")); - } - - public void doubleWallNorthBell() { - Bell bell = (Bell) Material.BELL.createBlockData(); - bell.setAttachment(Bell.Attachment.DOUBLE_WALL); - bell.setFacing(BlockFace.NORTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(4, 4, 4, 8, 2, 8)); - cuboidList.add(pixelCuboid(5, 6, 5, 6, 8, 6)); - cuboidList.add(pixelCuboid(7, 13, 0, 2, 2, 16)); - new BlockBoundingBox(bell, cuboidList, createItem("LAUFBAU_BLOCK_BELL", Material.BELL, "LAUFBAU_ATTACHMENT_DOUBLE_WALL", "LAUFBAU_FACING_NORTH")); - } - - public void doubleWallEastBell() { - Bell bell = (Bell) Material.BELL.createBlockData(); - bell.setAttachment(Bell.Attachment.DOUBLE_WALL); - bell.setFacing(BlockFace.EAST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(4, 4, 4, 8, 2, 8)); - cuboidList.add(pixelCuboid(5, 6, 5, 6, 8, 6)); - cuboidList.add(pixelCuboid(0, 13, 7, 16, 2, 2)); - new BlockBoundingBox(bell, cuboidList, createItem("LAUFBAU_BLOCK_BELL", Material.BELL, "LAUFBAU_ATTACHMENT_DOUBLE_WALL", "LAUFBAU_FACING_EAST")); - } - - public void singleWallNorthBell() { - Bell bell = (Bell) Material.BELL.createBlockData(); - bell.setAttachment(Bell.Attachment.SINGLE_WALL); - bell.setFacing(BlockFace.NORTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(4, 4, 4, 8, 2, 8)); - cuboidList.add(pixelCuboid(5, 6, 5, 6, 8, 6)); - cuboidList.add(pixelCuboid(7, 13, 0, 2, 2, 13)); - new BlockBoundingBox(bell, cuboidList, createItem("LAUFBAU_BLOCK_BELL", Material.BELL, "LAUFBAU_ATTACHMENT_SINGLE_WALL", "LAUFBAU_FACING_NORTH")); - } - - public void singleWallSouthBell() { - Bell bell = (Bell) Material.BELL.createBlockData(); - bell.setAttachment(Bell.Attachment.SINGLE_WALL); - bell.setFacing(BlockFace.SOUTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(4, 4, 4, 8, 2, 8)); - cuboidList.add(pixelCuboid(5, 6, 5, 6, 8, 6)); - cuboidList.add(pixelCuboid(7, 13, 3, 2, 2, 13)); - new BlockBoundingBox(bell, cuboidList, createItem("LAUFBAU_BLOCK_BELL", Material.BELL, "LAUFBAU_ATTACHMENT_SINGLE_WALL", "LAUFBAU_FACING_SOUTH")); - } - - public void singleWallEastBell() { - Bell bell = (Bell) Material.BELL.createBlockData(); - bell.setAttachment(Bell.Attachment.SINGLE_WALL); - bell.setFacing(BlockFace.EAST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(4, 4, 4, 8, 2, 8)); - cuboidList.add(pixelCuboid(5, 6, 5, 6, 8, 6)); - cuboidList.add(pixelCuboid(3, 13, 7, 13, 2, 2)); - new BlockBoundingBox(bell, cuboidList, createItem("LAUFBAU_BLOCK_BELL", Material.BELL, "LAUFBAU_ATTACHMENT_SINGLE_WALL", "LAUFBAU_FACING_EAST")); - } - - public void singleWallWestBell() { - Bell bell = (Bell) Material.BELL.createBlockData(); - bell.setAttachment(Bell.Attachment.SINGLE_WALL); - bell.setFacing(BlockFace.WEST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(4, 4, 4, 8, 2, 8)); - cuboidList.add(pixelCuboid(5, 6, 5, 6, 8, 6)); - cuboidList.add(pixelCuboid(0, 13, 7, 13, 2, 2)); - new BlockBoundingBox(bell, cuboidList, createItem("LAUFBAU_BLOCK_BELL", Material.BELL, "LAUFBAU_ATTACHMENT_SINGLE_WALL", "LAUFBAU_FACING_WEST")); - } -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/BrewingStandBoundingBox.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/BrewingStandBoundingBox.java deleted file mode 100644 index 8e42712a..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/BrewingStandBoundingBox.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 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.slaves.laufbau.boundingboxes; - -import de.steamwar.bausystem.features.slaves.laufbau.BlockBoundingBox; -import de.steamwar.bausystem.features.slaves.laufbau.BoundingBoxLoader; -import de.steamwar.bausystem.features.slaves.laufbau.Cuboid; -import de.steamwar.linkage.Linked; -import org.bukkit.Material; -import org.bukkit.block.data.type.BrewingStand; - -import java.util.ArrayList; -import java.util.List; - -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.createItem; -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.pixelCuboid; - -@Linked -public class BrewingStandBoundingBox implements BoundingBoxLoader { - - @Override - public void load() { - BrewingStand brewingStand = (BrewingStand) Material.BREWING_STAND.createBlockData(); - List cuboids = new ArrayList<>(); - cuboids.add(pixelCuboid(1, 0, 1, 14, 2, 14)); - cuboids.add(pixelCuboid(7, 2, 7, 2, 12, 2)); - new BlockBoundingBox(brewingStand, cuboids, createItem("LAUFBAU_BLOCK_BREWING_STAND", Material.BREWING_STAND)); - } -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/CandleBoundingBox.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/CandleBoundingBox.java deleted file mode 100644 index e9916f6f..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/CandleBoundingBox.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 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.slaves.laufbau.boundingboxes; - -import de.steamwar.bausystem.features.slaves.laufbau.BlockBoundingBox; -import de.steamwar.bausystem.features.slaves.laufbau.BoundingBoxLoader; -import de.steamwar.bausystem.features.slaves.laufbau.Cuboid; -import de.steamwar.linkage.Linked; -import org.bukkit.Material; -import org.bukkit.block.data.Lightable; -import org.bukkit.block.data.type.Candle; - -import java.util.ArrayList; -import java.util.List; - -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.createItem; -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.pixelCuboid; - -@Linked -public class CandleBoundingBox implements BoundingBoxLoader { - - @Override - public void load() { - caked(); - - single(); - } - - private void caked() { - Lightable candleCake = (Lightable) Material.CYAN_CANDLE_CAKE.createBlockData(); - candleCake.setLit(true); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 0, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(7, 8, 7, 2, 6, 2)); - new BlockBoundingBox(candleCake, cuboidList, createItem("LAUFBAU_BLOCK_CANDLE_CAKE", Material.CAKE)); - } - - private void single() { - Candle candle = (Candle) Material.CYAN_CANDLE.createBlockData(); - candle.setCandles(1); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(7, 0, 7, 2, 6, 2)); - new BlockBoundingBox(candle, cuboidList, createItem("LAUFBAU_BLOCK_CANDLE", Material.CYAN_CANDLE, "LAUFBAU_COUNT_1")); - } -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/ChainBoundingBox.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/ChainBoundingBox.java deleted file mode 100644 index 6d81b0e5..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/ChainBoundingBox.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 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.slaves.laufbau.boundingboxes; - -import de.steamwar.bausystem.features.slaves.laufbau.BlockBoundingBox; -import de.steamwar.bausystem.features.slaves.laufbau.BoundingBoxLoader; -import de.steamwar.bausystem.features.slaves.laufbau.Cuboid; -import de.steamwar.linkage.Linked; -import org.bukkit.Axis; -import org.bukkit.Material; -import org.bukkit.block.data.Orientable; - -import java.util.ArrayList; -import java.util.List; - -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.createItem; -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.pixelCuboid; - -@Linked -public class ChainBoundingBox implements BoundingBoxLoader { - - @Override - public void load() { - bottomTopChain(); - northSouthChain(); - eastWestChain(); - } - - private void bottomTopChain() { - Orientable chainBottomTop = (Orientable) Material.CHAIN.createBlockData(); - chainBottomTop.setAxis(Axis.Y); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(6.5, 0, 6.5, 3, 16, 3)); - new BlockBoundingBox(chainBottomTop, cuboidList, createItem("LAUFBAU_BLOCK_CHAIN", Material.CHAIN, "LAUFBAU_FACING_UP", "LAUFBAU_FACING_DOWN")); - } - - private void northSouthChain() { - Orientable chainBottomTop = (Orientable) Material.CHAIN.createBlockData(); - chainBottomTop.setAxis(Axis.Z); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(6.5, 6.5, 0, 3, 3, 16)); - new BlockBoundingBox(chainBottomTop, cuboidList, createItem("LAUFBAU_BLOCK_CHAIN", Material.CHAIN, "LAUFBAU_FACING_NORTH", "LAUFBAU_FACING_SOUTH")); - } - - private void eastWestChain() { - Orientable chainBottomTop = (Orientable) Material.CHAIN.createBlockData(); - chainBottomTop.setAxis(Axis.X); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 6.5, 6.5, 16, 3, 3)); - new BlockBoundingBox(chainBottomTop, cuboidList, createItem("LAUFBAU_BLOCK_CHAIN", Material.CHAIN, "LAUFBAU_FACING_EAST", "LAUFBAU_FACING_WEST")); - } -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/ChorusPlantBoundingBox.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/ChorusPlantBoundingBox.java deleted file mode 100644 index e1ace28e..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/ChorusPlantBoundingBox.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 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.slaves.laufbau.boundingboxes; - -import de.steamwar.bausystem.features.slaves.laufbau.BlockBoundingBox; -import de.steamwar.bausystem.features.slaves.laufbau.BoundingBoxLoader; -import de.steamwar.bausystem.features.slaves.laufbau.Cuboid; -import de.steamwar.linkage.Linked; -import org.bukkit.Material; -import org.bukkit.block.BlockFace; -import org.bukkit.block.data.MultipleFacing; - -import java.util.ArrayList; -import java.util.List; - -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.createItem; -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.pixelCuboid; - -@Linked -public class ChorusPlantBoundingBox implements BoundingBoxLoader { - - @Override - public void load() { - for (int nx = 0; nx < 2; nx++) { - for (int ny = 0; ny < 2; ny++) { - for (int nz = 0; nz < 2; nz++) { - for (int px = 0; px < 2; px++) { - for (int py = 0; py < 2; py++) { - for (int pz = 0; pz < 2; pz++) { - MultipleFacing chorusPlant = (MultipleFacing) Material.CHORUS_PLANT.createBlockData(); - List lore = new ArrayList<>(); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(3, 3, 3, 10, 10, 10)); - if (nx == 1) { - lore.add("LAUFBAU_CONNECTION_WEST"); - chorusPlant.setFace(BlockFace.WEST, true); - cuboidList.add(pixelCuboid(0, 3, 3, 3, 10, 10)); - } - if (ny == 1) { - lore.add("LAUFBAU_CONNECTION_DOWN"); - chorusPlant.setFace(BlockFace.DOWN, true); - cuboidList.add(pixelCuboid(3, 0, 3, 10, 3, 10)); - } - if (nz == 1) { - lore.add("LAUFBAU_CONNECTION_NORTH"); - chorusPlant.setFace(BlockFace.NORTH, true); - cuboidList.add(pixelCuboid(3, 3, 0, 10, 10, 3)); - } - if (px == 1) { - lore.add("LAUFBAU_CONNECTION_EAST"); - chorusPlant.setFace(BlockFace.EAST, true); - cuboidList.add(pixelCuboid(13, 3, 3, 3, 10, 10)); - } - if (py == 1) { - lore.add("LAUFBAU_CONNECTION_UP"); - chorusPlant.setFace(BlockFace.UP, true); - cuboidList.add(pixelCuboid(3, 13, 3, 10, 3, 10)); - } - if (pz == 1) { - lore.add("LAUFBAU_CONNECTION_SOUTH"); - chorusPlant.setFace(BlockFace.SOUTH, true); - cuboidList.add(pixelCuboid(3, 3, 13, 10, 10, 3)); - } - new BlockBoundingBox(chorusPlant, cuboidList, createItem("LAUFBAU_BLOCK_CHORUS_PLANT", Material.CHORUS_PLANT, lore.toArray(new String[0]))); - } - } - } - } - } - } - } -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/DragonEggBoundingBox.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/DragonEggBoundingBox.java deleted file mode 100644 index 19a709f5..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/DragonEggBoundingBox.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 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.slaves.laufbau.boundingboxes; - -import de.steamwar.bausystem.features.slaves.laufbau.BlockBoundingBox; -import de.steamwar.bausystem.features.slaves.laufbau.BoundingBoxLoader; -import de.steamwar.bausystem.features.slaves.laufbau.Cuboid; -import de.steamwar.linkage.Linked; -import org.bukkit.Material; -import org.bukkit.block.data.BlockData; - -import java.util.ArrayList; -import java.util.List; - -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.createItem; -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.pixelCuboid; - -@Linked -public class DragonEggBoundingBox implements BoundingBoxLoader { - - @Override - public void load() { - BlockData blockData = Material.DRAGON_EGG.createBlockData(); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(1, 0, 1, 14, 16, 14)); - new BlockBoundingBox(blockData, cuboidList, createItem("LAUFBAU_BLOCK_DRAGON_EGG", Material.DRAGON_EGG)); - } -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/DripLeafBoundingBox.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/DripLeafBoundingBox.java deleted file mode 100644 index dae1eb06..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/DripLeafBoundingBox.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 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.slaves.laufbau.boundingboxes; - -import de.steamwar.bausystem.features.slaves.laufbau.BlockBoundingBox; -import de.steamwar.bausystem.features.slaves.laufbau.BoundingBoxLoader; -import de.steamwar.bausystem.features.slaves.laufbau.Cuboid; -import de.steamwar.linkage.Linked; -import org.bukkit.Material; -import org.bukkit.block.data.type.BigDripleaf; - -import java.util.ArrayList; -import java.util.List; - -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.createItem; -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.pixelCuboid; - -@Linked -public class DripLeafBoundingBox implements BoundingBoxLoader { - - @Override - public void load() { - tiltNone(); - tiltPartial(); - } - - private void tiltNone() { - BigDripleaf bigDripleaf = (BigDripleaf) Material.BIG_DRIPLEAF.createBlockData(); - bigDripleaf.setTilt(BigDripleaf.Tilt.NONE); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 11, 0, 16, 4, 16)); - new BlockBoundingBox(bigDripleaf, cuboidList, createItem("LAUFBAU_BLOCK_BIG_DRIP_LEAF", Material.BIG_DRIPLEAF, "LAUFBAU_TILT_NONE")); - } - - private void tiltPartial() { - BigDripleaf bigDripleaf = (BigDripleaf) Material.BIG_DRIPLEAF.createBlockData(); - bigDripleaf.setTilt(BigDripleaf.Tilt.PARTIAL); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 11, 0, 16, 2, 16)); - new BlockBoundingBox(bigDripleaf, cuboidList, createItem("LAUFBAU_BLOCK_BIG_DRIP_LEAF", Material.BIG_DRIPLEAF, "LAUFBAU_TILT_PARTIAL")); - } -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/FencesBoundingBox.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/FencesBoundingBox.java deleted file mode 100644 index e19a72b5..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/FencesBoundingBox.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 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.slaves.laufbau.boundingboxes; - -import de.steamwar.bausystem.features.slaves.laufbau.BlockBoundingBox; -import de.steamwar.bausystem.features.slaves.laufbau.BoundingBoxLoader; -import de.steamwar.bausystem.features.slaves.laufbau.Cuboid; -import de.steamwar.linkage.Linked; -import org.bukkit.Material; -import org.bukkit.block.BlockFace; -import org.bukkit.block.data.type.Fence; - -import java.util.ArrayList; -import java.util.List; - -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.createItem; -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.pixelCuboid; - -@Linked -public class FencesBoundingBox implements BoundingBoxLoader { - - @Override - public void load() { - for (int nx = 0; nx < 2; nx++) { - for (int nz = 0; nz < 2; nz++) { - for (int px = 0; px < 2; px++) { - for (int pz = 0; pz < 2; pz++) { - Fence fence = (Fence) Material.NETHER_BRICK_FENCE.createBlockData(); - List lore = new ArrayList<>(); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(6, 0, 6, 4, 24, 4)); - if (nz == 1) { - lore.add("LAUFBAU_CONNECTION_NORTH"); - fence.setFace(BlockFace.NORTH, true); - cuboidList.add(pixelCuboid(6, 0, 0, 4, 24, 6)); - } - if (pz == 1) { - lore.add("LAUFBAU_CONNECTION_SOUTH"); - fence.setFace(BlockFace.SOUTH, true); - cuboidList.add(pixelCuboid(6, 0, 10, 4, 24, 6)); - } - if (nx == 1) { - lore.add("LAUFBAU_CONNECTION_WEST"); - fence.setFace(BlockFace.WEST, true); - cuboidList.add(pixelCuboid(0, 0, 6, 6, 24, 4)); - } - if (px == 1) { - lore.add("LAUFBAU_CONNECTION_EAST"); - fence.setFace(BlockFace.EAST, true); - cuboidList.add(pixelCuboid(10, 0, 6, 6, 24, 4)); - } - new BlockBoundingBox(fence, cuboidList, createItem("LAUFBAU_BLOCK_NETHER_BRICK_FENCE", Material.NETHER_BRICK_FENCE, lore.toArray(new String[0]))); - } - } - } - } - } -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/GrindstoneBoundingBox.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/GrindstoneBoundingBox.java deleted file mode 100644 index 59d6310d..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/GrindstoneBoundingBox.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 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.slaves.laufbau.boundingboxes; - -import de.steamwar.bausystem.features.slaves.laufbau.BlockBoundingBox; -import de.steamwar.bausystem.features.slaves.laufbau.BoundingBoxLoader; -import de.steamwar.bausystem.features.slaves.laufbau.Cuboid; -import de.steamwar.linkage.Linked; -import org.bukkit.Material; -import org.bukkit.block.BlockFace; -import org.bukkit.block.data.FaceAttachable; -import org.bukkit.block.data.type.Grindstone; - -import java.util.ArrayList; -import java.util.List; - -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.createItem; -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.pixelCuboid; - -@Linked -public class GrindstoneBoundingBox implements BoundingBoxLoader { - - @Override - public void load() { - floorNorthGrindstone(); - floorEastGrindstone(); - - ceilingNorthGrindstone(); - ceilingEastGrindstone(); - - wallNorthGrindstone(); - wallSouthGrindstone(); - wallEastGrindstone(); - wallWestGrindstone(); - } - - public void floorNorthGrindstone() { - Grindstone grindstone = (Grindstone) Material.GRINDSTONE.createBlockData(); - grindstone.setAttachedFace(FaceAttachable.AttachedFace.FLOOR); - grindstone.setFacing(BlockFace.NORTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(4, 4, 2, 8, 12, 12)); - cuboidList.add(pixelCuboid(2, 0, 6, 2, 7, 4)); - cuboidList.add(pixelCuboid(12, 0, 6, 2, 7, 4)); - cuboidList.add(pixelCuboid(2, 7, 5, 2, 6, 6)); - cuboidList.add(pixelCuboid(12, 7, 5, 2, 6, 6)); - new BlockBoundingBox(grindstone, cuboidList, createItem("LAUFBAU_BLOCK_GRINDSTONE", Material.GRINDSTONE, "LAUFBAU_ATTACHMENT_FLOOR", "LAUFBAU_FACING_NORTH", "LAUFBAU_FACING_SOUTH")); - } - - public void floorEastGrindstone() { - Grindstone grindstone = (Grindstone) Material.GRINDSTONE.createBlockData(); - grindstone.setAttachedFace(FaceAttachable.AttachedFace.FLOOR); - grindstone.setFacing(BlockFace.EAST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(2, 4, 4, 12, 12, 8)); - cuboidList.add(pixelCuboid(6, 0, 2, 4, 7, 2)); - cuboidList.add(pixelCuboid(6, 0, 12, 4, 7, 2)); - cuboidList.add(pixelCuboid(5, 7, 2, 6, 6, 2)); - cuboidList.add(pixelCuboid(5, 7, 12, 6, 6, 2)); - new BlockBoundingBox(grindstone, cuboidList, createItem("LAUFBAU_BLOCK_GRINDSTONE", Material.GRINDSTONE, "LAUFBAU_ATTACHMENT_FLOOR", "LAUFBAU_FACING_EAST", "LAUFBAU_FACING_WEST")); - } - - public void ceilingNorthGrindstone() { - Grindstone grindstone = (Grindstone) Material.GRINDSTONE.createBlockData(); - grindstone.setAttachedFace(FaceAttachable.AttachedFace.CEILING); - grindstone.setFacing(BlockFace.NORTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(4, 0, 2, 8, 12, 12)); - cuboidList.add(pixelCuboid(2, 9, 6, 2, 7, 4)); - cuboidList.add(pixelCuboid(12, 9, 6, 2, 7, 4)); - cuboidList.add(pixelCuboid(2, 3, 5, 2, 6, 6)); - cuboidList.add(pixelCuboid(12, 3, 5, 2, 6, 6)); - new BlockBoundingBox(grindstone, cuboidList, createItem("LAUFBAU_BLOCK_GRINDSTONE", Material.GRINDSTONE, "LAUFBAU_ATTACHMENT_CEILING", "LAUFBAU_FACING_NORTH", "LAUFBAU_FACING_SOUTH")); - } - - public void ceilingEastGrindstone() { - Grindstone grindstone = (Grindstone) Material.GRINDSTONE.createBlockData(); - grindstone.setAttachedFace(FaceAttachable.AttachedFace.CEILING); - grindstone.setFacing(BlockFace.EAST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(2, 0, 4, 12, 12, 8)); - cuboidList.add(pixelCuboid(6, 9, 2, 4, 7, 2)); - cuboidList.add(pixelCuboid(6, 9, 12, 4, 7, 2)); - cuboidList.add(pixelCuboid(5, 3, 2, 6, 6, 2)); - cuboidList.add(pixelCuboid(5, 3, 12, 6, 6, 2)); - new BlockBoundingBox(grindstone, cuboidList, createItem("LAUFBAU_BLOCK_GRINDSTONE", Material.GRINDSTONE, "LAUFBAU_ATTACHMENT_CEILING", "LAUFBAU_FACING_EAST", "LAUFBAU_FACING_WEST")); - } - - public void wallNorthGrindstone() { - Grindstone grindstone = (Grindstone) Material.GRINDSTONE.createBlockData(); - grindstone.setAttachedFace(FaceAttachable.AttachedFace.WALL); - grindstone.setFacing(BlockFace.NORTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(4, 2, 0, 8, 12, 12)); - cuboidList.add(pixelCuboid(2, 6, 9, 2, 4, 7)); - cuboidList.add(pixelCuboid(12, 6, 9, 2, 4, 7)); - cuboidList.add(pixelCuboid(2, 5, 3, 2, 6, 6)); - cuboidList.add(pixelCuboid(12, 5, 3, 2, 6, 6)); - new BlockBoundingBox(grindstone, cuboidList, createItem("LAUFBAU_BLOCK_GRINDSTONE", Material.GRINDSTONE, "LAUFBAU_ATTACHMENT_WALL", "LAUFBAU_FACING_NORTH")); - } - - public void wallSouthGrindstone() { - Grindstone grindstone = (Grindstone) Material.GRINDSTONE.createBlockData(); - grindstone.setAttachedFace(FaceAttachable.AttachedFace.WALL); - grindstone.setFacing(BlockFace.SOUTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(4, 2, 4, 8, 12, 12)); - cuboidList.add(pixelCuboid(2, 6, 0, 2, 4, 7)); - cuboidList.add(pixelCuboid(12, 6, 0, 2, 4, 7)); - cuboidList.add(pixelCuboid(2, 5, 7, 2, 6, 6)); - cuboidList.add(pixelCuboid(12, 5, 7, 2, 6, 6)); - new BlockBoundingBox(grindstone, cuboidList, createItem("LAUFBAU_BLOCK_GRINDSTONE", Material.GRINDSTONE, "LAUFBAU_ATTACHMENT_WALL", "LAUFBAU_FACING_SOUTH")); - } - - public void wallEastGrindstone() { - Grindstone grindstone = (Grindstone) Material.GRINDSTONE.createBlockData(); - grindstone.setAttachedFace(FaceAttachable.AttachedFace.WALL); - grindstone.setFacing(BlockFace.EAST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(4, 2, 4, 12, 12, 8)); - cuboidList.add(pixelCuboid(0, 6, 2, 7, 4, 2)); - cuboidList.add(pixelCuboid(0, 6, 12, 7, 4, 2)); - cuboidList.add(pixelCuboid(7, 5, 2, 6, 6, 2)); - cuboidList.add(pixelCuboid(7, 5, 12, 6, 6, 2)); - new BlockBoundingBox(grindstone, cuboidList, createItem("LAUFBAU_BLOCK_GRINDSTONE", Material.GRINDSTONE, "LAUFBAU_ATTACHMENT_WALL", "LAUFBAU_FACING_EAST")); - } - - public void wallWestGrindstone() { - Grindstone grindstone = (Grindstone) Material.GRINDSTONE.createBlockData(); - grindstone.setAttachedFace(FaceAttachable.AttachedFace.WALL); - grindstone.setFacing(BlockFace.WEST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 2, 4, 12, 12, 8)); - cuboidList.add(pixelCuboid(9, 6, 2, 7, 4, 2)); - cuboidList.add(pixelCuboid(9, 6, 12, 7, 4, 2)); - cuboidList.add(pixelCuboid(3, 5, 2, 6, 6, 2)); - cuboidList.add(pixelCuboid(3, 5, 12, 6, 6, 2)); - new BlockBoundingBox(grindstone, cuboidList, createItem("LAUFBAU_BLOCK_GRINDSTONE", Material.GRINDSTONE, "LAUFBAU_ATTACHMENT_WALL", "LAUFBAU_FACING_WEST")); - } -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/HopperBoundingBox.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/HopperBoundingBox.java deleted file mode 100644 index 655ad055..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/HopperBoundingBox.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 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.slaves.laufbau.boundingboxes; - -import de.steamwar.bausystem.features.slaves.laufbau.BlockBoundingBox; -import de.steamwar.bausystem.features.slaves.laufbau.BoundingBoxLoader; -import de.steamwar.bausystem.features.slaves.laufbau.Cuboid; -import de.steamwar.linkage.Linked; -import org.bukkit.Material; -import org.bukkit.block.BlockFace; -import org.bukkit.block.data.type.Hopper; - -import java.util.ArrayList; -import java.util.List; - -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.createItem; -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.pixelCuboid; - -@Linked -public class HopperBoundingBox implements BoundingBoxLoader { - - @Override - public void load() { - downHopper(); - northHopper(); - southHopper(); - eastHopper(); - westHopper(); - } - - public void downHopper() { - Hopper hopper = (Hopper) Material.HOPPER.createBlockData(); - hopper.setFacing(BlockFace.DOWN); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 10, 0, 16, 6, 16)); - cuboidList.add(pixelCuboid(4, 4, 4, 8, 6, 8)); - cuboidList.add(pixelCuboid(6, 0, 6, 4, 4, 4)); - new BlockBoundingBox(hopper, cuboidList, createItem("LAUFBAU_BLOCK_HOPPER", Material.HOPPER, "LAUFBAU_CONNECTION_FLOOR")); - } - - public void northHopper() { - Hopper hopper = (Hopper) Material.HOPPER.createBlockData(); - hopper.setFacing(BlockFace.NORTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 10, 0, 16, 6, 16)); - cuboidList.add(pixelCuboid(4, 4, 4, 8, 6, 8)); - cuboidList.add(pixelCuboid(6, 4, 0, 4, 4, 4)); - new BlockBoundingBox(hopper, cuboidList, createItem("LAUFBAU_BLOCK_HOPPER", Material.HOPPER, "LAUFBAU_CONNECTION_NORTH")); - } - - public void southHopper() { - Hopper hopper = (Hopper) Material.HOPPER.createBlockData(); - hopper.setFacing(BlockFace.SOUTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 10, 0, 16, 6, 16)); - cuboidList.add(pixelCuboid(4, 4, 4, 8, 6, 8)); - cuboidList.add(pixelCuboid(6, 4, 12, 4, 4, 4)); - new BlockBoundingBox(hopper, cuboidList, createItem("LAUFBAU_BLOCK_HOPPER", Material.HOPPER, "LAUFBAU_CONNECTION_SOUTH")); - } - - public void eastHopper() { - Hopper hopper = (Hopper) Material.HOPPER.createBlockData(); - hopper.setFacing(BlockFace.EAST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 10, 0, 16, 6, 16)); - cuboidList.add(pixelCuboid(4, 4, 4, 8, 6, 8)); - cuboidList.add(pixelCuboid(12, 4, 6, 4, 4, 4)); - new BlockBoundingBox(hopper, cuboidList, createItem("LAUFBAU_BLOCK_HOPPER", Material.HOPPER, "LAUFBAU_CONNECTION_EAST")); - } - - public void westHopper() { - Hopper hopper = (Hopper) Material.HOPPER.createBlockData(); - hopper.setFacing(BlockFace.WEST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 10, 0, 16, 6, 16)); - cuboidList.add(pixelCuboid(4, 4, 4, 8, 6, 8)); - cuboidList.add(pixelCuboid(0, 4, 6, 4, 4, 4)); - new BlockBoundingBox(hopper, cuboidList, createItem("LAUFBAU_BLOCK_HOPPER", Material.HOPPER, "LAUFBAU_CONNECTION_WEST")); - } -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/IronBarBoundingBox.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/IronBarBoundingBox.java deleted file mode 100644 index e75afb93..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/IronBarBoundingBox.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 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.slaves.laufbau.boundingboxes; - -import de.steamwar.bausystem.features.slaves.laufbau.BlockBoundingBox; -import de.steamwar.bausystem.features.slaves.laufbau.BoundingBoxLoader; -import de.steamwar.bausystem.features.slaves.laufbau.Cuboid; -import de.steamwar.linkage.Linked; -import org.bukkit.Material; -import org.bukkit.block.BlockFace; -import org.bukkit.block.data.type.Fence; - -import java.util.ArrayList; -import java.util.List; - -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.createItem; -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.pixelCuboid; - -@Linked -public class IronBarBoundingBox implements BoundingBoxLoader { - - @Override - public void load() { - for (int nx = 0; nx < 2; nx++) { - for (int nz = 0; nz < 2; nz++) { - for (int px = 0; px < 2; px++) { - for (int pz = 0; pz < 2; pz++) { - Fence fence = (Fence) Material.IRON_BARS.createBlockData(); - List lore = new ArrayList<>(); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(7, 0, 7, 2, 16, 2)); - if (nz == 1) { - lore.add("LAUFBAU_CONNECTION_NORTH"); - fence.setFace(BlockFace.NORTH, true); - cuboidList.add(pixelCuboid(7, 0, 0, 2, 16, 7)); - } - if (pz == 1) { - lore.add("LAUFBAU_CONNECTION_SOUTH"); - fence.setFace(BlockFace.SOUTH, true); - cuboidList.add(pixelCuboid(7, 0, 9, 2, 16, 7)); - } - if (nx == 1) { - lore.add("LAUFBAU_CONNECTION_WEST"); - fence.setFace(BlockFace.WEST, true); - cuboidList.add(pixelCuboid(0, 0, 7, 7, 16, 2)); - } - if (px == 1) { - lore.add("LAUFBAU_CONNECTION_EAST"); - fence.setFace(BlockFace.EAST, true); - cuboidList.add(pixelCuboid(9, 0, 7, 7, 16, 2)); - } - new BlockBoundingBox(fence, cuboidList, createItem("LAUFBAU_BLOCK_IRON_BARS", Material.IRON_BARS, lore.toArray(new String[0]))); - } - } - } - } - } -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/LanternBoundingBox.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/LanternBoundingBox.java deleted file mode 100644 index 2320fbcc..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/LanternBoundingBox.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 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.slaves.laufbau.boundingboxes; - -import de.steamwar.bausystem.features.slaves.laufbau.BlockBoundingBox; -import de.steamwar.bausystem.features.slaves.laufbau.BoundingBoxLoader; -import de.steamwar.bausystem.features.slaves.laufbau.Cuboid; -import de.steamwar.linkage.Linked; -import org.bukkit.Material; -import org.bukkit.block.data.type.Lantern; - -import java.util.ArrayList; -import java.util.List; - -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.createItem; -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.pixelCuboid; - -@Linked -public class LanternBoundingBox implements BoundingBoxLoader { - - @Override - public void load() { - lantern(); - hangingLantern(); - } - - public void lantern() { - Lantern lantern = (Lantern) Material.LANTERN.createBlockData(); - lantern.setHanging(false); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(5, 0, 5, 6, 7, 6)); - cuboidList.add(pixelCuboid(6, 7, 6, 4, 2, 4)); - new BlockBoundingBox(lantern, cuboidList, createItem("LAUFBAU_BLOCK_LANTERN", Material.LANTERN)); - } - - public void hangingLantern() { - Lantern lantern = (Lantern) Material.LANTERN.createBlockData(); - lantern.setHanging(true); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(5, 1, 5, 6, 7, 6)); - cuboidList.add(pixelCuboid(6, 8, 6, 4, 2, 4)); - new BlockBoundingBox(lantern, cuboidList, createItem("LAUFBAU_BLOCK_LANTERN", Material.LANTERN, "LAUFBAU_HANGING")); - } -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/LecternBoundingBox.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/LecternBoundingBox.java deleted file mode 100644 index ab49f3ab..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/LecternBoundingBox.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 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.slaves.laufbau.boundingboxes; - -import de.steamwar.bausystem.features.slaves.laufbau.BlockBoundingBox; -import de.steamwar.bausystem.features.slaves.laufbau.BoundingBoxLoader; -import de.steamwar.bausystem.features.slaves.laufbau.Cuboid; -import de.steamwar.linkage.Linked; -import org.bukkit.Material; -import org.bukkit.block.data.BlockData; - -import java.util.ArrayList; -import java.util.List; - -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.createItem; -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.pixelCuboid; - -@Linked -public class LecternBoundingBox implements BoundingBoxLoader { - - @Override - public void load() { - BlockData blockData = Material.LECTERN.createBlockData(); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 0, 0, 16, 2, 16)); - cuboidList.add(pixelCuboid(4, 2, 4, 8, 12, 8)); - new BlockBoundingBox(blockData, cuboidList, createItem("LAUFBAU_BLOCK_LECTERN", Material.LECTERN)); - } -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/StairBoundingBox.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/StairBoundingBox.java deleted file mode 100644 index ff14d1b8..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/StairBoundingBox.java +++ /dev/null @@ -1,343 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 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.slaves.laufbau.boundingboxes; - -import de.steamwar.bausystem.features.slaves.laufbau.BlockBoundingBox; -import de.steamwar.bausystem.features.slaves.laufbau.BoundingBoxLoader; -import de.steamwar.bausystem.features.slaves.laufbau.Cuboid; -import de.steamwar.linkage.Linked; -import org.bukkit.Material; -import org.bukkit.block.BlockFace; -import org.bukkit.block.data.type.Stairs; - -import java.util.ArrayList; -import java.util.List; - -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.createItem; -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.pixelCuboid; - -@Linked -public class StairBoundingBox implements BoundingBoxLoader { - - @Override - public void load() { - straightBottomNorthStair(); - straightBottomSouthStair(); - straightBottomEastStair(); - straightBottomWestStair(); - - straightTopNorthStair(); - straightTopSouthStair(); - straightTopEastStair(); - straightTopWestStair(); - - outerLeftBottomNorthStair(); - outerLeftBottomSouthStair(); - outerLeftBottomEastStair(); - outerLeftBottomWestStair(); - - outerLeftTopNorthStair(); - outerLeftTopSouthStair(); - outerLeftTopEastStair(); - outerLeftTopWestStair(); - - innerLeftBottomNorthStair(); - innerLeftBottomSouthStair(); - innerLeftBottomEastStair(); - innerLeftBottomWestStair(); - - innerLeftTopNorthStair(); - innerLeftTopSouthStair(); - innerLeftTopEastStair(); - innerLeftTopWestStair(); - } - - public void straightBottomNorthStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.STRAIGHT); - stairs.setHalf(Stairs.Half.BOTTOM); - stairs.setFacing(BlockFace.NORTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 0, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(0, 8, 0, 16, 8, 8)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_STRAIGHT", "LAUFBAU_HALF_BOTTOM", "LAUFBAU_FACING_NORTH")); - } - - public void straightBottomSouthStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.STRAIGHT); - stairs.setHalf(Stairs.Half.BOTTOM); - stairs.setFacing(BlockFace.SOUTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 0, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(0, 8, 8, 16, 8, 8)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_STRAIGHT", "LAUFBAU_HALF_BOTTOM", "LAUFBAU_FACING_SOUTH")); - } - - public void straightBottomEastStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.STRAIGHT); - stairs.setHalf(Stairs.Half.BOTTOM); - stairs.setFacing(BlockFace.EAST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 0, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(8, 8, 0, 8, 8, 16)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_STRAIGHT", "LAUFBAU_HALF_BOTTOM", "LAUFBAU_FACING_EAST")); - } - - public void straightBottomWestStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.STRAIGHT); - stairs.setHalf(Stairs.Half.BOTTOM); - stairs.setFacing(BlockFace.WEST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 0, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(0, 8, 0, 8, 8, 16)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_STRAIGHT", "LAUFBAU_HALF_BOTTOM", "LAUFBAU_FACING_WEST")); - } - - public void straightTopNorthStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.STRAIGHT); - stairs.setHalf(Stairs.Half.TOP); - stairs.setFacing(BlockFace.NORTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 8, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(0, 0, 0, 16, 8, 8)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_STRAIGHT", "LAUFBAU_HALF_TOP", "LAUFBAU_FACING_NORTH")); - } - - public void straightTopSouthStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.STRAIGHT); - stairs.setHalf(Stairs.Half.TOP); - stairs.setFacing(BlockFace.SOUTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 8, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(0, 0, 8, 16, 8, 8)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_STRAIGHT", "LAUFBAU_HALF_TOP", "LAUFBAU_FACING_SOUTH")); - } - - public void straightTopEastStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.STRAIGHT); - stairs.setHalf(Stairs.Half.TOP); - stairs.setFacing(BlockFace.EAST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 8, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(8, 0, 0, 8, 8, 16)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_STRAIGHT", "LAUFBAU_HALF_TOP", "LAUFBAU_FACING_EAST")); - } - - public void straightTopWestStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.STRAIGHT); - stairs.setHalf(Stairs.Half.TOP); - stairs.setFacing(BlockFace.WEST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 8, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(0, 0, 0, 8, 8, 16)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_STRAIGHT", "LAUFBAU_HALF_TOP", "LAUFBAU_FACING_WEST")); - } - - public void outerLeftBottomNorthStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.OUTER_LEFT); - stairs.setHalf(Stairs.Half.BOTTOM); - stairs.setFacing(BlockFace.NORTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 0, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(0, 8, 0, 8, 8, 8)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_OUTER_LEFT", "LAUFBAU_HALF_BOTTOM", "LAUFBAU_FACING_NORTH")); - } - - public void outerLeftBottomSouthStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.OUTER_LEFT); - stairs.setHalf(Stairs.Half.BOTTOM); - stairs.setFacing(BlockFace.SOUTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 0, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(8, 8, 8, 8, 8, 8)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_OUTER_LEFT", "LAUFBAU_HALF_BOTTOM", "LAUFBAU_FACING_SOUTH")); - } - - public void outerLeftBottomEastStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.OUTER_LEFT); - stairs.setHalf(Stairs.Half.BOTTOM); - stairs.setFacing(BlockFace.EAST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 0, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(8, 8, 0, 8, 8, 8)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_OUTER_LEFT", "LAUFBAU_HALF_BOTTOM", "LAUFBAU_FACING_EAST")); - } - - public void outerLeftBottomWestStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.OUTER_LEFT); - stairs.setHalf(Stairs.Half.BOTTOM); - stairs.setFacing(BlockFace.WEST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 0, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(0, 8, 8, 8, 8, 8)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_OUTER_LEFT", "LAUFBAU_HALF_BOTTOM", "LAUFBAU_FACING_WEST")); - } - - public void outerLeftTopNorthStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.OUTER_LEFT); - stairs.setHalf(Stairs.Half.TOP); - stairs.setFacing(BlockFace.NORTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 8, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(0, 0, 0, 8, 8, 8)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_OUTER_LEFT", "LAUFBAU_HALF_TOP", "LAUFBAU_FACING_NORTH")); - } - - public void outerLeftTopSouthStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.OUTER_LEFT); - stairs.setHalf(Stairs.Half.TOP); - stairs.setFacing(BlockFace.SOUTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 8, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(8, 0, 8, 8, 8, 8)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_OUTER_LEFT", "LAUFBAU_HALF_TOP", "LAUFBAU_FACING_SOUTH")); - } - - public void outerLeftTopEastStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.OUTER_LEFT); - stairs.setHalf(Stairs.Half.TOP); - stairs.setFacing(BlockFace.EAST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 8, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(8, 0, 0, 8, 8, 8)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_OUTER_LEFT", "LAUFBAU_HALF_TOP", "LAUFBAU_FACING_EAST")); - } - - public void outerLeftTopWestStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.OUTER_LEFT); - stairs.setHalf(Stairs.Half.TOP); - stairs.setFacing(BlockFace.WEST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 8, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(0, 0, 8, 8, 8, 8)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_OUTER_LEFT", "LAUFBAU_HALF_TOP", "LAUFBAU_FACING_WEST")); - } - - public void innerLeftBottomNorthStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.INNER_LEFT); - stairs.setHalf(Stairs.Half.BOTTOM); - stairs.setFacing(BlockFace.NORTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 0, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(0, 8, 0, 16, 8, 8)); - cuboidList.add(pixelCuboid(0, 8, 8, 8, 8, 8)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_INNER_LEFT", "LAUFBAU_HALF_BOTTOM", "LAUFBAU_FACING_NORTH")); - } - - public void innerLeftBottomSouthStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.INNER_LEFT); - stairs.setHalf(Stairs.Half.BOTTOM); - stairs.setFacing(BlockFace.SOUTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 0, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(0, 8, 8, 16, 8, 8)); - cuboidList.add(pixelCuboid(8, 8, 0, 8, 8, 8)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_INNER_LEFT", "LAUFBAU_HALF_BOTTOM", "LAUFBAU_FACING_SOUTH")); - } - - public void innerLeftBottomEastStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.INNER_LEFT); - stairs.setHalf(Stairs.Half.BOTTOM); - stairs.setFacing(BlockFace.EAST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 0, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(0, 8, 0, 16, 8, 8)); - cuboidList.add(pixelCuboid(8, 8, 8, 8, 8, 8)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_INNER_LEFT", "LAUFBAU_HALF_BOTTOM", "LAUFBAU_FACING_EAST")); - } - - public void innerLeftBottomWestStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.INNER_LEFT); - stairs.setHalf(Stairs.Half.BOTTOM); - stairs.setFacing(BlockFace.WEST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 0, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(0, 8, 8, 16, 8, 8)); - cuboidList.add(pixelCuboid(0, 8, 0, 8, 8, 8)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_INNER_LEFT", "LAUFBAU_HALF_BOTTOM", "LAUFBAU_FACING_WEST")); - } - - public void innerLeftTopNorthStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.INNER_LEFT); - stairs.setHalf(Stairs.Half.TOP); - stairs.setFacing(BlockFace.NORTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 8, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(0, 0, 0, 16, 8, 8)); - cuboidList.add(pixelCuboid(0, 0, 8, 8, 8, 8)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_INNER_LEFT", "LAUFBAU_HALF_TOP", "LAUFBAU_FACING_NORTH")); - } - - public void innerLeftTopSouthStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.INNER_LEFT); - stairs.setHalf(Stairs.Half.TOP); - stairs.setFacing(BlockFace.SOUTH); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 8, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(0, 0, 8, 16, 8, 8)); - cuboidList.add(pixelCuboid(8, 0, 0, 8, 8, 8)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_INNER_LEFT", "LAUFBAU_HALF_TOP", "LAUFBAU_FACING_SOUTH")); - } - - public void innerLeftTopEastStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.INNER_LEFT); - stairs.setHalf(Stairs.Half.TOP); - stairs.setFacing(BlockFace.EAST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 8, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(0, 0, 0, 16, 8, 8)); - cuboidList.add(pixelCuboid(8, 0, 8, 8, 8, 8)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_INNER_LEFT", "LAUFBAU_HALF_TOP", "LAUFBAU_FACING_EAST")); - } - - public void innerLeftTopWestStair() { - Stairs stairs = (Stairs) Material.END_STONE_BRICK_STAIRS.createBlockData(); - stairs.setShape(Stairs.Shape.INNER_LEFT); - stairs.setHalf(Stairs.Half.TOP); - stairs.setFacing(BlockFace.WEST); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(0, 8, 0, 16, 8, 16)); - cuboidList.add(pixelCuboid(0, 0, 8, 16, 8, 8)); - cuboidList.add(pixelCuboid(0, 0, 0, 8, 8, 8)); - new BlockBoundingBox(stairs, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_STAIRS", Material.END_STONE_BRICK_STAIRS, "LAUFBAU_SHAPE_INNER_LEFT", "LAUFBAU_HALF_TOP", "LAUFBAU_FACING_WEST")); - } -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/WallBoundingBox.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/WallBoundingBox.java deleted file mode 100644 index dffe873d..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/boundingboxes/WallBoundingBox.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 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.slaves.laufbau.boundingboxes; - -import de.steamwar.bausystem.features.slaves.laufbau.BlockBoundingBox; -import de.steamwar.bausystem.features.slaves.laufbau.BoundingBoxLoader; -import de.steamwar.bausystem.features.slaves.laufbau.Cuboid; -import de.steamwar.linkage.Linked; -import org.bukkit.Material; -import org.bukkit.block.BlockFace; -import org.bukkit.block.data.type.Wall; - -import java.util.ArrayList; -import java.util.List; - -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.createItem; -import static de.steamwar.bausystem.features.slaves.laufbau.LaufbauUtils.pixelCuboid; - -@Linked -public class WallBoundingBox implements BoundingBoxLoader { - - @Override - public void load() { - v18(); - } - - private void v18() { - for (int nx = 0; nx < 2; nx++) { - for (int nz = 0; nz < 2; nz++) { - for (int px = 0; px < 2; px++) { - for (int pz = 0; pz < 2; pz++) { - Wall fence = (Wall) Material.END_STONE_BRICK_WALL.createBlockData(); - List lore = new ArrayList<>(); - List cuboidList = new ArrayList<>(); - cuboidList.add(pixelCuboid(4, 0, 4, 8, 24, 8)); - if (nz == 1) { - lore.add("LAUFBAU_CONNECTION_NORTH"); - fence.setHeight(BlockFace.NORTH, Wall.Height.LOW); - cuboidList.add(pixelCuboid(5, 0, 0, 6, 24, 4)); - } - if (pz == 1) { - lore.add("LAUFBAU_CONNECTION_SOUTH"); - fence.setHeight(BlockFace.SOUTH, Wall.Height.LOW); - cuboidList.add(pixelCuboid(5, 0, 12, 6, 24, 4)); - } - if (nx == 1) { - lore.add("LAUFBAU_CONNECTION_WEST"); - fence.setHeight(BlockFace.WEST, Wall.Height.LOW); - cuboidList.add(pixelCuboid(0, 0, 5, 4, 24, 6)); - } - if (px == 1) { - lore.add("LAUFBAU_CONNECTION_EAST"); - fence.setHeight(BlockFace.EAST, Wall.Height.LOW); - cuboidList.add(pixelCuboid(12, 0, 5, 4, 24, 6)); - } - new BlockBoundingBox(fence, cuboidList, createItem("LAUFBAU_BLOCK_END_STONE_BRICK_WALL", Material.END_STONE_BRICK_WALL, lore.toArray(new String[0]))); - } - } - } - } - } -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/converter/LaufbauConfigV1ToV2Converter.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/converter/LaufbauConfigV1ToV2Converter.java deleted file mode 100644 index 6e1c7aac..00000000 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/converter/LaufbauConfigV1ToV2Converter.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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.slaves.laufbau.converter; - -import de.steamwar.bausystem.config.ConfigConverter; -import de.steamwar.bausystem.features.slaves.laufbau.LaufbauConfig; -import de.steamwar.bausystem.features.slaves.laufbau.LaufbauConfigV1; - -@Deprecated -public class LaufbauConfigV1ToV2Converter implements ConfigConverter { - - @Override - public int version() { - return LaufbauConfigV1.VERSION; - } - - @Override - public LaufbauConfig convert(LaufbauConfigV1 old) { - LaufbauConfig config = new LaufbauConfig(); - config.getDeactivated().addAll(old.getDeactivated()); - return config; - } -} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/states/CreatingLaufState.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/states/CreatingLaufState.java index 82b0f5f7..ff2b6d3b 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/states/CreatingLaufState.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/states/CreatingLaufState.java @@ -145,7 +145,6 @@ public class CreatingLaufState implements LaufbauState { } try { BaseBlock block = BukkitAdapter.adapt(highestBlockBoundingBox.blockData).toBaseBlock(); - if (highestBlockBoundingBox.blockConsumer != null) highestBlockBoundingBox.blockConsumer.accept(block); editSession.setBlock(BukkitAdapter.asBlockVector(location), block); } catch (MaxChangedBlocksException e) { e.printStackTrace(); From a3a97ee102af7ec7621ac49a26f5e15318d73e60 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 14:44:55 +0100 Subject: [PATCH 17/46] Move TechHider into new HiderCore module HiderCore holds the TechHider, ChunkHider and AccessPrivilegeProvider so that FightSystem and BauSystem can both shade it. BlockIds and ProtocolUtils stay in SpigotCore (used by AntiNocom and RFallingBlockEntity) and move to de.steamwar.core to avoid a split package. The access widener entries the TechHider needs are duplicated into hidercore.accesswidener; the legacy TechHider still needs its own copies in SpigotCore. --- FightSystem/FightSystem_Core/build.gradle.kts | 1 + .../fightsystem/record/PacketProcessor.java | 2 +- .../de/steamwar/fightsystem/utils/Region.java | 2 +- HiderCore/build.gradle.kts | 41 +++++++++++++++++++ .../techhider/AccessPrivilegeProvider.java | 0 .../src/de/steamwar/techhider/ChunkHider.java | 1 + .../src/de/steamwar/techhider/TechHider.java | 0 HiderCore/src/hidercore.accesswidener | 27 ++++++++++++ .../{techhider => core}/BlockIds.java | 2 +- .../{techhider => core}/ProtocolUtils.java | 2 +- .../de/steamwar/core/events/AntiNocom.java | 2 +- .../steamwar/entity/RFallingBlockEntity.java | 2 +- .../steamwar/techhider/legacy/ChunkHider.java | 2 +- .../steamwar/techhider/legacy/TechHider.java | 4 +- .../src/spigotcore.accesswidener | 7 +--- settings.gradle.kts | 2 + 16 files changed, 83 insertions(+), 14 deletions(-) create mode 100644 HiderCore/build.gradle.kts rename {SpigotCore/SpigotCore_Main => HiderCore}/src/de/steamwar/techhider/AccessPrivilegeProvider.java (100%) rename {SpigotCore/SpigotCore_Main => HiderCore}/src/de/steamwar/techhider/ChunkHider.java (99%) rename {SpigotCore/SpigotCore_Main => HiderCore}/src/de/steamwar/techhider/TechHider.java (100%) create mode 100644 HiderCore/src/hidercore.accesswidener rename SpigotCore/SpigotCore_Main/src/de/steamwar/{techhider => core}/BlockIds.java (98%) rename SpigotCore/SpigotCore_Main/src/de/steamwar/{techhider => core}/ProtocolUtils.java (99%) diff --git a/FightSystem/FightSystem_Core/build.gradle.kts b/FightSystem/FightSystem_Core/build.gradle.kts index 9b2f746b..dfe91b4f 100644 --- a/FightSystem/FightSystem_Core/build.gradle.kts +++ b/FightSystem/FightSystem_Core/build.gradle.kts @@ -30,6 +30,7 @@ dependencies { compileOnly(libs.classindex) annotationProcessor(libs.classindex) compileOnly(project(":SpigotCore", "default")) + implementation(project(":HiderCore")) compileOnly(libs.netty) compileOnly(libs.paperapi) diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java index 72d728c6..c0b6738f 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java @@ -20,6 +20,7 @@ package de.steamwar.fightsystem.record; import com.sk89q.worldedit.extent.clipboard.Clipboard; +import de.steamwar.core.BlockIds; import de.steamwar.core.worldedit.WorldEditWrapper; import de.steamwar.entity.REntity; import de.steamwar.entity.REntityServer; @@ -40,7 +41,6 @@ import de.steamwar.fightsystem.utils.TechHiderWrapper; import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SteamwarUser; import de.steamwar.sql.Team; -import de.steamwar.techhider.BlockIds; import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.TextComponent; 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 7d75e02a..c28257ef 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Region.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Region.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.utils; -import de.steamwar.techhider.ProtocolUtils; +import de.steamwar.core.ProtocolUtils; import lombok.AllArgsConstructor; import lombok.Getter; import org.bukkit.Location; diff --git a/HiderCore/build.gradle.kts b/HiderCore/build.gradle.kts new file mode 100644 index 00000000..086bf56c --- /dev/null +++ b/HiderCore/build.gradle.kts @@ -0,0 +1,41 @@ +/* + * 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 . + */ + +plugins { + steamwar.java + widener +} + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +dependencies { + compileOnly(project(":SpigotCore", "default")) + + compileOnly(libs.paperapi) + compileOnly(libs.nms) + compileOnly(libs.netty) + compileOnly(libs.fastutil) +} + +widener { + fromCatalog(libs.nms) +} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/AccessPrivilegeProvider.java b/HiderCore/src/de/steamwar/techhider/AccessPrivilegeProvider.java similarity index 100% rename from SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/AccessPrivilegeProvider.java rename to HiderCore/src/de/steamwar/techhider/AccessPrivilegeProvider.java diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java b/HiderCore/src/de/steamwar/techhider/ChunkHider.java similarity index 99% rename from SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java rename to HiderCore/src/de/steamwar/techhider/ChunkHider.java index caa9c96b..9ad25a4e 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java +++ b/HiderCore/src/de/steamwar/techhider/ChunkHider.java @@ -19,6 +19,7 @@ package de.steamwar.techhider; +import de.steamwar.core.ProtocolUtils; import de.steamwar.techhider.AccessPrivilegeProvider.ForPlayer; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java b/HiderCore/src/de/steamwar/techhider/TechHider.java similarity index 100% rename from SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java rename to HiderCore/src/de/steamwar/techhider/TechHider.java diff --git a/HiderCore/src/hidercore.accesswidener b/HiderCore/src/hidercore.accesswidener new file mode 100644 index 00000000..347067a1 --- /dev/null +++ b/HiderCore/src/hidercore.accesswidener @@ -0,0 +1,27 @@ +accessWidener v2 named + +# For TechHider +accessible field net/minecraft/network/protocol/game/ClientboundEntityEventPacket entityId I +accessible field net/minecraft/network/protocol/game/ClientboundMoveEntityPacket entityId I +accessible field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket entityId I +accessible field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket sectionPos Lnet/minecraft/core/SectionPos; +accessible field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket positions [S +accessible field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket states [Lnet/minecraft/world/level/block/state/BlockState; + +# For ChunkHider +## transitive-extendable means that a public no args constructor is added without any super initialization +transitive-extendable class net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket +accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket chunkData Lnet/minecraft/network/protocol/game/ClientboundLevelChunkPacketData; +mutable field net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket chunkData Lnet/minecraft/network/protocol/game/ClientboundLevelChunkPacketData; + +## transitive-extendable means that a public no args constructor is added without any super initialization +transitive-extendable class net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData +accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData buffer [B +mutable field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData buffer [B +accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData blockEntitiesData Ljava/util/List; +mutable field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData blockEntitiesData Ljava/util/List; + +accessible class net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo +accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo type Lnet/minecraft/world/level/block/entity/BlockEntityType; +accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo packedXZ I +accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo y I diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/BlockIds.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/BlockIds.java similarity index 98% rename from SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/BlockIds.java rename to SpigotCore/SpigotCore_Main/src/de/steamwar/core/BlockIds.java index b13295bf..c9b41de9 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/BlockIds.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/BlockIds.java @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -package de.steamwar.techhider; +package de.steamwar.core; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.world.level.block.Block; diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/ProtocolUtils.java similarity index 99% rename from SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java rename to SpigotCore/SpigotCore_Main/src/de/steamwar/core/ProtocolUtils.java index 70d86008..58e8145e 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/ProtocolUtils.java @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -package de.steamwar.techhider; +package de.steamwar.core; import io.netty.buffer.ByteBuf; diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/events/AntiNocom.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/events/AntiNocom.java index d3625026..6bce3aa4 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/events/AntiNocom.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/events/AntiNocom.java @@ -21,9 +21,9 @@ package de.steamwar.core.events; import com.comphenix.tinyprotocol.TinyProtocol; import de.steamwar.core.Core; +import de.steamwar.core.ProtocolUtils; import de.steamwar.linkage.Linked; import de.steamwar.sql.SWException; -import de.steamwar.techhider.ProtocolUtils; import net.minecraft.core.BlockPos; import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket; diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RFallingBlockEntity.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RFallingBlockEntity.java index 50226dfa..b52187f2 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RFallingBlockEntity.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RFallingBlockEntity.java @@ -19,7 +19,7 @@ package de.steamwar.entity; -import de.steamwar.techhider.BlockIds; +import de.steamwar.core.BlockIds; import lombok.Getter; import lombok.Setter; import org.bukkit.Location; diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java index 9aaa9b40..bca91a11 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java @@ -19,7 +19,7 @@ package de.steamwar.techhider.legacy; -import de.steamwar.techhider.ProtocolUtils; +import de.steamwar.core.ProtocolUtils; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java index dfa0a5f3..5c9cd169 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java @@ -20,8 +20,8 @@ package de.steamwar.techhider.legacy; import com.comphenix.tinyprotocol.TinyProtocol; -import de.steamwar.techhider.BlockIds; -import de.steamwar.techhider.ProtocolUtils; +import de.steamwar.core.BlockIds; +import de.steamwar.core.ProtocolUtils; import lombok.Getter; import net.minecraft.core.BlockPos; import net.minecraft.core.Vec3i; diff --git a/SpigotCore/SpigotCore_Main/src/spigotcore.accesswidener b/SpigotCore/SpigotCore_Main/src/spigotcore.accesswidener index 68d0aa82..99170241 100644 --- a/SpigotCore/SpigotCore_Main/src/spigotcore.accesswidener +++ b/SpigotCore/SpigotCore_Main/src/spigotcore.accesswidener @@ -33,11 +33,10 @@ mutable field net/minecraft/network/protocol/game/ClientboundEntityEventPacket e transitive-extendable class net/minecraft/network/protocol/game/ClientboundRotateHeadPacket accessible field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket yHeadRot B mutable field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket yHeadRot B -## + TechHider accessible field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket entityId I mutable field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket entityId I -# For ChunkHider +# For legacy/ChunkHider ## transitive-extendable means that a public no args constructor is added without any super initialization transitive-extendable class net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket chunkData Lnet/minecraft/network/protocol/game/ClientboundLevelChunkPacketData; @@ -55,8 +54,7 @@ accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacket accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo packedXZ I accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo y I -# For TechHider -accessible field net/minecraft/network/protocol/game/ClientboundMoveEntityPacket entityId I +# For legacy/TechHider ## transitive-extendable means that a public no args constructor is added without any super initialization transitive-extendable class net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket accessible field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket sectionPos Lnet/minecraft/core/SectionPos; @@ -65,7 +63,6 @@ mutable field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdate accessible field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket states [Lnet/minecraft/world/level/block/state/BlockState; mutable field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket states [Lnet/minecraft/world/level/block/state/BlockState; -# For legacy/TechHider ## transitive-extendable means that a public no args constructor is added without any super initialization transitive-extendable class net/minecraft/network/protocol/game/ClientboundBlockUpdatePacket mutable field net/minecraft/network/protocol/game/ClientboundBlockUpdatePacket blockState Lnet/minecraft/world/level/block/state/BlockState; \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 63ec80e5..1b360abf 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -196,6 +196,8 @@ include( "FightSystem:FightSystem_Standalone" ) +include("HiderCore") + include("KotlinCore") include("LobbySystem") From da4b684a4b21d9315ed07d1de3bc7353970d6e01 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 14:49:00 +0100 Subject: [PATCH 18/46] 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; + } +} From 7d4252325a21ef59ffa2fda4a65866a1e77d9db1 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 14:50:32 +0100 Subject: [PATCH 19/46] Extract obfuscation config into HiderCore ObfuscationConfig resolves the hidden blocks, block states (including all waterlogged states when water is hidden) and block entity types plus the obfuscation block, either from a gamemode's techhider config or from plain materials. TechHiderWrapper now only contains the fight specific access rules. --- .../fightsystem/utils/TechHiderWrapper.java | 74 ++---------- HiderCore/build.gradle.kts | 1 + .../steamwar/techhider/ObfuscationConfig.java | 113 ++++++++++++++++++ 3 files changed, 125 insertions(+), 63 deletions(-) create mode 100644 HiderCore/src/de/steamwar/techhider/ObfuscationConfig.java 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 469159c3..1ccaad8e 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java @@ -32,32 +32,21 @@ 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.ObfuscationConfig; import de.steamwar.techhider.TechHider; import lombok.Getter; -import net.minecraft.core.Holder; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.material.FluidState; -import net.minecraft.world.level.material.Fluids; import org.bukkit.GameMode; -import org.bukkit.Material; import org.bukkit.craftbukkit.CraftWorld; -import org.bukkit.craftbukkit.util.CraftMagicNumbers; import org.bukkit.entity.Player; 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; import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; -import java.util.stream.Stream; public class TechHiderWrapper extends StateDependent implements Listener { @@ -80,29 +69,15 @@ public class TechHiderWrapper extends StateDependent implements Listener { @Override public void enable() { - Set blockStatesToObfuscate = getBlockStatesToHideFromMaterials(Config.GameModeConfig.Techhider.HiddenBlocks); - - Set blocksToObfuscate = Config.GameModeConfig.Techhider.HiddenBlocks.stream() - .map(CraftMagicNumbers::getBlock) - .collect(Collectors.toUnmodifiableSet()); - - Set> blockEntityTypeToObfuscate = Config.GameModeConfig.Techhider.HiddenBlockEntities.stream() - .map(id -> { - ResourceLocation loc = ResourceLocation.parse(id); - return BuiltInRegistries.BLOCK_ENTITY_TYPE.get(loc).orElse(null); - }) - .filter(Objects::nonNull) - .map(Holder.Reference::value) - .collect(Collectors.toUnmodifiableSet()); - - new TechHider(CraftMagicNumbers.getBlock(Config.GameModeConfig.Techhider.ObfuscateWith), newProvider(blockStatesToObfuscate, blocksToObfuscate, blockEntityTypeToObfuscate)); + ObfuscationConfig obfuscation = ObfuscationConfig.of(Config.GameModeConfig.Techhider); + new TechHider(obfuscation.getObfuscateWith(), newProvider(obfuscation)); } - private AccessPrivilegeProvider newProvider(Set blockStatesToObfuscate, Set blocksToObfuscate, Set> blockEntityTypeToObfuscate) { + private AccessPrivilegeProvider newProvider(ObfuscationConfig obfuscation) { return new AccessPrivilegeProvider() { @Override public ForPlayer forPlayer(Player p) { - return new FightPlayerAccess(p, getHiddenRegions(p), blockStatesToObfuscate, blocksToObfuscate, blockEntityTypeToObfuscate); + return new FightPlayerAccess(p, getHiddenRegions(p), obfuscation); } @Override @@ -118,16 +93,12 @@ public class TechHiderWrapper extends StateDependent implements Listener { private class FightPlayerAccess implements ForPlayer { private final Player player; private final Region[] hiddenRegions; - private final Set blockStatesToObfuscate; - private final Set blocksToObfuscate; - private final Set> blockEntityTypeToObfuscate; + private final ObfuscationConfig obfuscation; - private FightPlayerAccess(Player player, Region[] hiddenRegions, Set blockStatesToObfuscate, Set blocksToObfuscate, Set> blockEntityTypeToObfuscate) { + private FightPlayerAccess(Player player, Region[] hiddenRegions, ObfuscationConfig obfuscation) { this.player = player; this.hiddenRegions = hiddenRegions; - this.blockStatesToObfuscate = blockStatesToObfuscate; - this.blocksToObfuscate = blocksToObfuscate; - this.blockEntityTypeToObfuscate = blockEntityTypeToObfuscate; + this.obfuscation = obfuscation; } private boolean isHidden(int blockX, int blockY, int blockZ) { @@ -155,12 +126,12 @@ public class TechHiderWrapper extends StateDependent implements Listener { @Override public boolean canAccessBlock(int blockX, int blockY, int blockZ, Block block) { - return !isHidden(blockX, blockY, blockZ) || !blocksToObfuscate.contains(block); + return !isHidden(blockX, blockY, blockZ) || !obfuscation.hidesBlock(block); } @Override public boolean canAccessBlockState(int blockX, int blockY, int blockZ, BlockState blockState) { - return !isHidden(blockX, blockY, blockZ) || !blockStatesToObfuscate.contains(blockState); + return !isHidden(blockX, blockY, blockZ) || !obfuscation.hidesBlockState(blockState); } // TODO will require entity tracking on the netty thread to prevent future race conditions @@ -177,7 +148,7 @@ public class TechHiderWrapper extends StateDependent implements Listener { @Override public boolean canAccessBlockEntity(int blockX, int blockY, int blockZ, BlockEntityType type) { - return !isHidden(blockX, blockY, blockZ) || !blockEntityTypeToObfuscate.contains(type); + return !isHidden(blockX, blockY, blockZ) || !obfuscation.hidesBlockEntity(type); } } @@ -235,27 +206,4 @@ public class TechHiderWrapper extends StateDependent implements Listener { return new Region[]{Fight.getOpposite(fightPlayer.getTeam()).getExtendRegion()}; } - - private Stream getWaterloggedBlockStates() { - FluidState waterFluidState = Fluids.WATER.getSource(false); - - return BuiltInRegistries.BLOCK.stream() - .map((block) -> block.getStateDefinition().getPossibleStates()) - .flatMap(Collection::stream) - .filter((blockState -> blockState.getFluidState() == waterFluidState)); - } - - private Set getBlockStatesToHideFromMaterials(Set materials) { - Stream allStatesFromMaterials = materials.stream() - .map(CraftMagicNumbers::getBlock) - .map((block) -> block.getStateDefinition().getPossibleStates()) - .flatMap(Collection::stream); - - if(materials.contains(Material.WATER)) { - return Stream.concat(allStatesFromMaterials, getWaterloggedBlockStates()).collect(Collectors.toUnmodifiableSet()); - } - else { - return allStatesFromMaterials.collect(Collectors.toUnmodifiableSet()); - } - } } diff --git a/HiderCore/build.gradle.kts b/HiderCore/build.gradle.kts index 283cd19e..a0b27617 100644 --- a/HiderCore/build.gradle.kts +++ b/HiderCore/build.gradle.kts @@ -35,6 +35,7 @@ dependencies { compileOnly(libs.netty) compileOnly(libs.fastutil) compileOnly(libs.authlib) + compileOnly(libs.datafixer) } widener { diff --git a/HiderCore/src/de/steamwar/techhider/ObfuscationConfig.java b/HiderCore/src/de/steamwar/techhider/ObfuscationConfig.java new file mode 100644 index 00000000..cc8310a5 --- /dev/null +++ b/HiderCore/src/de/steamwar/techhider/ObfuscationConfig.java @@ -0,0 +1,113 @@ +/* + * 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.techhider; + +import de.steamwar.sql.GameModeConfig; +import lombok.Getter; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; +import org.bukkit.Material; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; + +import java.util.Collection; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Which blocks, block states and block entities to hide, and the block to replace them with. + */ +@Getter +public final class ObfuscationConfig { + + public static ObfuscationConfig of(GameModeConfig.TechhiderConfig config) { + return new ObfuscationConfig(config.ObfuscateWith, config.HiddenBlocks, config.HiddenBlockEntities); + } + + private final Block obfuscateWith; + private final Set blocks; + private final Set blockStates; + private final Set> blockEntityTypes; + + /** + * @param hiddenBlocks "water" also hides all waterlogged block states + * @param hiddenBlockEntities block entity type ids, e.g. "minecraft:sign" + */ + public ObfuscationConfig(Material obfuscateWith, Set hiddenBlocks, Set hiddenBlockEntities) { + this.obfuscateWith = CraftMagicNumbers.getBlock(obfuscateWith); + + this.blocks = hiddenBlocks.stream() + .map(CraftMagicNumbers::getBlock) + .collect(Collectors.toUnmodifiableSet()); + + this.blockStates = getBlockStatesToHideFromMaterials(hiddenBlocks); + + this.blockEntityTypes = hiddenBlockEntities.stream() + .map(id -> { + ResourceLocation loc = ResourceLocation.parse(id); + return BuiltInRegistries.BLOCK_ENTITY_TYPE.get(loc).orElse(null); + }) + .filter(Objects::nonNull) + .map(Holder.Reference::value) + .collect(Collectors.toUnmodifiableSet()); + } + + public boolean hidesBlock(Block block) { + return blocks.contains(block); + } + + public boolean hidesBlockState(BlockState blockState) { + return blockStates.contains(blockState); + } + + public boolean hidesBlockEntity(BlockEntityType type) { + return blockEntityTypes.contains(type); + } + + private static Stream getWaterloggedBlockStates() { + FluidState waterFluidState = Fluids.WATER.getSource(false); + + return BuiltInRegistries.BLOCK.stream() + .map((block) -> block.getStateDefinition().getPossibleStates()) + .flatMap(Collection::stream) + .filter((blockState -> blockState.getFluidState() == waterFluidState)); + } + + private static Set getBlockStatesToHideFromMaterials(Set materials) { + Stream allStatesFromMaterials = materials.stream() + .map(CraftMagicNumbers::getBlock) + .map((block) -> block.getStateDefinition().getPossibleStates()) + .flatMap(Collection::stream); + + if(materials.contains(Material.WATER)) { + return Stream.concat(allStatesFromMaterials, getWaterloggedBlockStates()).collect(Collectors.toUnmodifiableSet()); + } + else { + return allStatesFromMaterials.collect(Collectors.toUnmodifiableSet()); + } + } +} From ce2a23f4b850fd0b9cfc82a5a2a9c3df1799898b Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 15:00:37 +0100 Subject: [PATCH 20/46] Migrate BauSystem to the new TechHider and remove the legacy TechHider The new TechHider registers a permanent global filter, so BauSystem now uses a single BauTechHider that combines the hiding rules of the spectator techhider, /techhider and /xray per player. To support the different obfuscation blocks of these rules, ForPlayer now provides the obfuscation block per position. The ChunkHider now also checks single valued chunk sections, which were sent unfiltered before, and recalculates their block count if they change. Unhandled packet types are logged once; statistics, debug samples and ping responses are passed through. The legacy TechHider and its access widener entries are removed. --- BauSystem/BauSystem_Main/build.gradle.kts | 1 + .../features/techhider/BauTechHider.java | 137 ++++++++ .../features/techhider/HidingRule.java | 52 +++ .../features/techhider/TechHiderCommand.java | 31 +- .../features/world/SpectatorListener.java | 30 +- .../bausystem/features/xray/XrayCommand.java | 30 +- .../fightsystem/utils/TechHiderWrapper.java | 7 +- .../src/de/steamwar/hullhider/HullRegion.java | 4 + .../techhider/AccessPrivilegeProvider.java | 5 + .../src/de/steamwar/techhider/ChunkHider.java | 38 ++- .../steamwar/techhider/ObfuscationConfig.java | 2 + .../src/de/steamwar/techhider/TechHider.java | 22 +- .../steamwar/techhider/legacy/ChunkHider.java | 301 ------------------ .../techhider/legacy/ProtocolWrapper.java | 79 ----- .../steamwar/techhider/legacy/TechHider.java | 169 ---------- .../src/spigotcore.accesswidener | 31 -- 16 files changed, 295 insertions(+), 644 deletions(-) create mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/BauTechHider.java create mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/HidingRule.java delete mode 100644 SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java delete mode 100644 SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolWrapper.java delete mode 100644 SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java diff --git a/BauSystem/BauSystem_Main/build.gradle.kts b/BauSystem/BauSystem_Main/build.gradle.kts index c387bc36..c320e674 100644 --- a/BauSystem/BauSystem_Main/build.gradle.kts +++ b/BauSystem/BauSystem_Main/build.gradle.kts @@ -36,6 +36,7 @@ dependencies { annotationProcessor(libs.classindex) compileOnly(project(":SpigotCore", "default")) compileOnly(project(":KotlinCore", "default")) + implementation(project(":HiderCore")) compileOnly(libs.axiom) compileOnly(libs.authlib) 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 new file mode 100644 index 00000000..a4fd51bf --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/BauTechHider.java @@ -0,0 +1,137 @@ +/* + * 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.bausystem.features.world.SpectatorListener; +import de.steamwar.bausystem.features.xray.XrayCommand; +import de.steamwar.linkage.Linked; +import de.steamwar.linkage.LinkedInstance; +import de.steamwar.techhider.AccessPrivilegeProvider; +import de.steamwar.techhider.TechHider; +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.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. + */ +@Linked +public class BauTechHider implements AccessPrivilegeProvider { + + private static final HidingRule[] NO_RULES = new HidingRule[0]; + + // Injected after construction, until then nothing is hidden + @LinkedInstance + public XrayCommand xrayCommand; + @LinkedInstance + public TechHiderCommand techHiderCommand; + @LinkedInstance + public SpectatorListener spectatorListener; + + public BauTechHider() { + new TechHider(this); + } + + @Override + public ForPlayer forPlayer(Player p) { + if (xrayCommand == null || techHiderCommand == null || spectatorListener == null) { + return new BauPlayerAccess(p, NO_RULES); + } + + // Region rules first: where rules overlap, the first one decides the obfuscation block + List rules = new ArrayList<>(); + xrayCommand.addHidingRules(p, rules); + techHiderCommand.addHidingRules(p, rules); + spectatorListener.addHidingRules(p, rules); + return new BauPlayerAccess(p, rules.toArray(NO_RULES)); + } + + @Override + public boolean isPlayerPrivilegedToPerformAction(Player p) { + return true; + } + + /** + * 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 { + + @Override + public Player getPlayer() { + return player; + } + + @Override + public boolean canAccessAllDataWithinChunk(int chunkX, int chunkZ) { + for (HidingRule rule : rules) { + if (rule.coversChunk(chunkX, chunkZ)) return false; + } + return true; + } + + @Override + public boolean canAccessPosition(int blockX, int blockY, int blockZ) { + return true; + } + + @Override + public boolean canAccessBlock(int blockX, int blockY, int blockZ, Block block) { + for (HidingRule rule : rules) { + if (rule.covers(blockX, blockY, blockZ) && rule.obfuscation().hidesBlock(block)) return false; + } + return true; + } + + @Override + public boolean canAccessBlockState(int blockX, int blockY, int blockZ, BlockState blockState) { + for (HidingRule rule : rules) { + if (rule.covers(blockX, blockY, blockZ) && rule.obfuscation().hidesBlockState(blockState)) return false; + } + return true; + } + + @Override + public boolean canAccessEntity(int entityId) { + return true; + } + + @Override + public boolean canAccessBlockEntity(int blockX, int blockY, int blockZ, BlockEntityType type) { + for (HidingRule rule : rules) { + if (rule.covers(blockX, blockY, blockZ) && rule.obfuscation().hidesBlockEntity(type)) return false; + } + return true; + } + + @Override + public BlockState getObfuscation(int blockX, int blockY, int blockZ) { + for (HidingRule rule : rules) { + if (rule.covers(blockX, blockY, blockZ)) return rule.obfuscation().getObfuscationState(); + } + // Only asked for positions a rule hides + return Blocks.END_STONE.defaultBlockState(); + } + } +} diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/HidingRule.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/HidingRule.java new file mode 100644 index 00000000..a7010e5a --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/HidingRule.java @@ -0,0 +1,52 @@ +/* + * 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.bausystem.region.Point; +import de.steamwar.bausystem.region.Region; +import de.steamwar.hullhider.HullRegion; +import de.steamwar.techhider.ObfuscationConfig; + +/** + * Hides the blocks of an obfuscation config within an area, or everywhere if the area is null. + */ +public record HidingRule(HullRegion area, ObfuscationConfig obfuscation) { + + public static HidingRule everywhere(ObfuscationConfig obfuscation) { + return new HidingRule(null, obfuscation); + } + + /** + * Covers the area including its extension. + */ + public static HidingRule of(Region.Area area, ObfuscationConfig obfuscation) { + Point min = area.getMinPoint(true); + Point max = area.getMaxPoint(true); + return new HidingRule(HullRegion.fromInclusive(min.getX(), min.getY(), min.getZ(), max.getX(), max.getY(), max.getZ()), obfuscation); + } + + public boolean covers(int x, int y, int z) { + return area == null || area.contains(x, y, z); + } + + public boolean coversChunk(int chunkX, int chunkZ) { + return area == null || !area.chunkOutside(chunkX, chunkZ); + } +} 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 2f401a59..b6fd7d8a 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 @@ -31,7 +31,7 @@ import de.steamwar.command.TypeMapper; import de.steamwar.core.CraftbukkitWrapper; import de.steamwar.linkage.Linked; import de.steamwar.linkage.LinkedInstance; -import de.steamwar.techhider.legacy.TechHider; +import de.steamwar.techhider.ObfuscationConfig; import net.md_5.bungee.api.ChatMessageType; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; @@ -41,6 +41,7 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @Linked @@ -50,8 +51,9 @@ public class TechHiderCommand extends SWCommand implements Listener, ScoreboardE super("techhider", "hider", "obfuscate", "hide", "hide-tech"); } - private Map> techHiders = new HashMap<>(); - private Map> hidden = new HashMap<>(); + // Read on netty threads by the BauTechHider + private Map> hidingRules = new ConcurrentHashMap<>(); + private Map> hidden = new ConcurrentHashMap<>(); @LinkedInstance public XrayCommand xrayCommand; @@ -64,22 +66,15 @@ public class TechHiderCommand extends SWCommand implements Listener, ScoreboardE return; } - Optional techHider = techHiders.computeIfAbsent(region, rg -> { + Optional hidingRule = hidingRules.computeIfAbsent(region, rg -> { if (!region.getGameModeConfig().Techhider.Active) { return Optional.empty(); } - hidden.put(rg, new HashSet<>()); - - TechHider current = new TechHider((p, cX, cY) -> { - if (rg.getBuildArea().isChunkOutside(cX, cY)) return true; - return !hidden.get(rg).contains(p); - }, region.getGameModeConfig().Techhider.ObfuscateWith, region.getGameModeConfig().Techhider.HiddenBlocks, region.getGameModeConfig().Techhider.HiddenBlockEntities); - current.enable(); - - return Optional.of(current); + hidden.put(rg, ConcurrentHashMap.newKeySet()); + return Optional.of(HidingRule.of(rg.getBuildArea(), ObfuscationConfig.of(rg.getGameModeConfig().Techhider))); }); - if (!techHider.isPresent()) { + if (!hidingRule.isPresent()) { return; } @@ -128,6 +123,14 @@ public class TechHiderCommand extends SWCommand implements Listener, ScoreboardE hidden.values().forEach(set -> set.remove(event.getPlayer())); } + public void addHidingRules(Player player, List rules) { + hidden.forEach((region, players) -> { + if (players.contains(player)) { + hidingRules.getOrDefault(region, Optional.empty()).ifPresent(rules::add); + } + }); + } + public void disable(Region region, Player player) { hidden.computeIfPresent(region, (rg, set) -> { set.remove(player); diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java index 645600cd..ae32e0c1 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java @@ -22,10 +22,11 @@ package de.steamwar.bausystem.features.world; import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.Permission; import de.steamwar.bausystem.config.BauServer; +import de.steamwar.bausystem.features.techhider.HidingRule; import de.steamwar.bausystem.utils.BauMemberUpdateEvent; import de.steamwar.linkage.Linked; import de.steamwar.sql.BauweltMember; -import de.steamwar.techhider.legacy.TechHider; +import de.steamwar.techhider.ObfuscationConfig; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; @@ -41,14 +42,18 @@ import org.bukkit.event.entity.EntityPickupItemEvent; import org.bukkit.event.player.*; import org.bukkit.util.Vector; +import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; @Linked public class SpectatorListener implements Listener { - private static final TechHider techHider; - private static final Set NO_TECHHIDER = new HashSet<>(); + private static final HidingRule SPECTATOR_RULE; + // Read on netty threads by the BauTechHider + private static final Set NO_TECHHIDER = ConcurrentHashMap.newKeySet(); static { Set materials = new HashSet<>(); @@ -99,19 +104,12 @@ public class SpectatorListener implements Listener { materials.add(Material.WATER); materials.remove(Material.BARRIER); materials.remove(Material.STONE); - techHider = new TechHider((TechHider.LocationEvaluator) (player, i, i1) -> { - return Permission.BUILD.hasPermission(player) || NO_TECHHIDER.contains(player); - }, Material.END_STONE, materials, new HashSet<>()); + SPECTATOR_RULE = HidingRule.everywhere(new ObfuscationConfig(Material.END_STONE, materials, Collections.emptySet())); } - private static void enableOrDisableTechhider() { - boolean enable = Bukkit.getOnlinePlayers() - .stream() - .anyMatch(player -> !(Permission.BUILD.hasPermission(player) || NO_TECHHIDER.contains(player))); - if (enable) { - techHider.enable(); - } else { - techHider.disable(); + public void addHidingRules(Player player, List rules) { + if (!(Permission.BUILD.hasPermission(player) || NO_TECHHIDER.contains(player))) { + rules.add(SPECTATOR_RULE); } } @@ -122,7 +120,6 @@ public class SpectatorListener implements Listener { NO_TECHHIDER.add(player); } resendChunks(player); - enableOrDisableTechhider(); } private static void resendChunks(Player player) { @@ -141,7 +138,6 @@ public class SpectatorListener implements Listener { @EventHandler public void onPlayerJoin(PlayerJoinEvent event) { - enableOrDisableTechhider(); if (BauSystem.DEV_SERVER) return; if (event.getPlayer().getUniqueId().equals(BauServer.getInstance().getOwner())) { return; @@ -163,7 +159,6 @@ public class SpectatorListener implements Listener { @EventHandler public void onBauMemberUpdate(BauMemberUpdateEvent event) { - enableOrDisableTechhider(); if (!anySupervisorOnline(null) && !BauSystem.DEV_SERVER) { Bukkit.getOnlinePlayers().forEach(player -> { player.kickPlayer(""); @@ -180,7 +175,6 @@ public class SpectatorListener implements Listener { @EventHandler public void onPlayerQuit(PlayerQuitEvent event) { NO_TECHHIDER.remove(event.getPlayer()); - Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), SpectatorListener::enableOrDisableTechhider, 1); if (BauSystem.DEV_SERVER) return; if (!anySupervisorOnline(event.getPlayer())) { Bukkit.getOnlinePlayers().forEach(player -> { 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 aa90081c..0b334429 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.techhider.HidingRule; import de.steamwar.bausystem.features.techhider.TechHiderCommand; import de.steamwar.bausystem.region.Region; import de.steamwar.bausystem.utils.ScoreboardElement; @@ -28,7 +29,7 @@ import de.steamwar.command.SWCommand; import de.steamwar.core.CraftbukkitWrapper; import de.steamwar.linkage.Linked; import de.steamwar.linkage.LinkedInstance; -import de.steamwar.techhider.legacy.TechHider; +import de.steamwar.techhider.ObfuscationConfig; import net.md_5.bungee.api.ChatMessageType; import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; import net.minecraft.server.level.ServerPlayer; @@ -43,6 +44,7 @@ import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerQuitEvent; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiFunction; @Linked @@ -52,9 +54,10 @@ public class XrayCommand extends SWCommand implements Listener, ScoreboardElemen super("xray"); } - private Map> techHiders = new HashMap<>(); + // Read on netty threads by the BauTechHider and the movement filter + private Map> hidingRules = new ConcurrentHashMap<>(); private Map> xrayedBlocks = new HashMap<>(); - private Map> hidden = new HashMap<>(); + private Map> hidden = new ConcurrentHashMap<>(); @LinkedInstance public TechHiderCommand techHiderCommand; @@ -67,19 +70,19 @@ public class XrayCommand extends SWCommand implements Listener, ScoreboardElemen return; } - Optional techHider = techHiders.computeIfAbsent(region, rg -> { + Optional hidingRule = hidingRules.computeIfAbsent(region, rg -> { if (!region.getGameModeConfig().Techhider.Active) { return Optional.empty(); } - hidden.put(rg, new HashSet<>()); + hidden.put(rg, ConcurrentHashMap.newKeySet()); Set blocks = new HashSet<>(Arrays.asList(region.getGameModeConfig().Techhider.ObfuscateWith)); if (blocks.contains(Material.END_STONE)) blocks.add(Material.END_STONE_BRICKS); xrayedBlocks.put(region, blocks); - return Optional.of(createXray(rg, blocks)); + return Optional.of(HidingRule.of(rg.getBuildArea(), new ObfuscationConfig(Material.STRUCTURE_VOID, blocks, Collections.emptySet()))); }); - if (!techHider.isPresent()) { + if (!hidingRule.isPresent()) { return; } @@ -139,13 +142,12 @@ public class XrayCommand extends SWCommand implements Listener, ScoreboardElemen } } - private TechHider createXray(Region rg, Set obfuscate) { - TechHider current = new TechHider((TechHider.LocationEvaluator) (p, cX, cY) -> { - if (rg.getBuildArea().isChunkOutside(cX, cY)) return true; - return !hidden.get(rg).contains(p); - }, Material.STRUCTURE_VOID, obfuscate, new HashSet<>()); - current.enable(); - return current; + public void addHidingRules(Player player, List rules) { + hidden.forEach((region, players) -> { + if (players.contains(player)) { + hidingRules.getOrDefault(region, Optional.empty()).ifPresent(rules::add); + } + }); } @EventHandler 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 1ccaad8e..6f0b1290 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java @@ -70,7 +70,7 @@ public class TechHiderWrapper extends StateDependent implements Listener { @Override public void enable() { ObfuscationConfig obfuscation = ObfuscationConfig.of(Config.GameModeConfig.Techhider); - new TechHider(obfuscation.getObfuscateWith(), newProvider(obfuscation)); + new TechHider(newProvider(obfuscation)); } private AccessPrivilegeProvider newProvider(ObfuscationConfig obfuscation) { @@ -150,6 +150,11 @@ public class TechHiderWrapper extends StateDependent implements Listener { public boolean canAccessBlockEntity(int blockX, int blockY, int blockZ, BlockEntityType type) { return !isHidden(blockX, blockY, blockZ) || !obfuscation.hidesBlockEntity(type); } + + @Override + public BlockState getObfuscation(int blockX, int blockY, int blockZ) { + return obfuscation.getObfuscationState(); + } } @Override diff --git a/HiderCore/src/de/steamwar/hullhider/HullRegion.java b/HiderCore/src/de/steamwar/hullhider/HullRegion.java index b8aacb0b..0006a2af 100644 --- a/HiderCore/src/de/steamwar/hullhider/HullRegion.java +++ b/HiderCore/src/de/steamwar/hullhider/HullRegion.java @@ -69,6 +69,10 @@ public final class HullRegion { return contains(location.getBlockX(), location.getBlockY(), location.getBlockZ()); } + public boolean chunkOutside(int chunkX, int chunkZ) { + return chunkX < (minX >> 4) || chunkX > ((maxX - 1) >> 4) || chunkZ < (minZ >> 4) || chunkZ > ((maxZ - 1) >> 4); + } + /** * @return the index of the block within a bitset of {@link #volume()} bits, only valid for contained blocks */ diff --git a/HiderCore/src/de/steamwar/techhider/AccessPrivilegeProvider.java b/HiderCore/src/de/steamwar/techhider/AccessPrivilegeProvider.java index 38b9b463..be0599cb 100644 --- a/HiderCore/src/de/steamwar/techhider/AccessPrivilegeProvider.java +++ b/HiderCore/src/de/steamwar/techhider/AccessPrivilegeProvider.java @@ -53,5 +53,10 @@ public interface AccessPrivilegeProvider { boolean canAccessEntity(int entityId); boolean canAccessBlockEntity(int blockX, int blockY, int blockZ, BlockEntityType type); + + /** + * @return the block state sent instead of a block this player may not access at this position + */ + BlockState getObfuscation(int blockX, int blockY, int blockZ); } } diff --git a/HiderCore/src/de/steamwar/techhider/ChunkHider.java b/HiderCore/src/de/steamwar/techhider/ChunkHider.java index 9ad25a4e..6cabee7b 100644 --- a/HiderCore/src/de/steamwar/techhider/ChunkHider.java +++ b/HiderCore/src/de/steamwar/techhider/ChunkHider.java @@ -31,6 +31,7 @@ import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import org.bukkit.entity.Player; +import java.util.Arrays; import java.util.List; import java.util.function.UnaryOperator; @@ -45,12 +46,6 @@ public class ChunkHider { private final int BLOCKS_PER_SECTION = 4096; private final int BIOMES_PER_SECTION = 64; - private final int blockIdUsedForHiding; - - public ChunkHider(Block blockUsedForObfuscation) { - blockIdUsedForHiding = Block.BLOCK_STATE_REGISTRY.getId(blockUsedForObfuscation.defaultBlockState()); - } - private int getLongsRequiredToEncodeEntries(int bitsPerEntry, int entryCount) { int entriesPerLong = Long.SIZE / bitsPerEntry; int dataLengthAsLongCount = (entryCount + entriesPerLong - 1) / entriesPerLong; @@ -89,9 +84,22 @@ public class ChunkHider { if (bitsPerBlock == 0) { int sectionBlockId = ProtocolUtils.readVarInt(in); - out.writeShort(blockCount); - out.writeByte(bitsPerBlock); - ProtocolUtils.writeVarInt(out, sectionBlockId); + int[] blockData = new int[BLOCKS_PER_SECTION]; + Arrays.fill(blockData, sectionBlockId); + int[] obfuscatedBlockData = obfuscateBlockDataArray(access, chunkX, chunkZ, yOffset, blockData); + + if (Arrays.equals(blockData, obfuscatedBlockData)) { + out.writeShort(blockCount); + out.writeByte(bitsPerBlock); + ProtocolUtils.writeVarInt(out, sectionBlockId); + } else { + // The client skips sections without non-air blocks, so the count has to include the obfuscation blocks + out.writeShort(countNonAirBlocks(obfuscatedBlockData)); + out.writeByte(15); + for (long rawDataSegment : encodeDirectBlockDataArray(obfuscatedBlockData)) { + out.writeLong(rawDataSegment); + } + } } else if (bitsPerBlock <= BIT_PER_BLOCK_INDIRECTION_LIMIT) { int palletLength = ProtocolUtils.readVarInt(in); int[] pallet = ProtocolUtils.readVarIntArray(in, palletLength); @@ -198,7 +206,7 @@ public class ChunkHider { 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; + obfuscatedData[blockDataIndex] = Block.getId(access.getObfuscation(worldX, worldY, worldZ)); } } } @@ -207,6 +215,16 @@ public class ChunkHider { return obfuscatedData; } + private int countNonAirBlocks(int[] blockDataArray) { + int count = 0; + for (int blockId : blockDataArray) { + if (!Block.BLOCK_STATE_REGISTRY.byId(blockId).isAir()) { + count++; + } + } + return count; + } + private int[] resolveDirectBlockDataArray(long[] rawDataArray, byte bitsPerBlock) { SimpleBitStorage data = new SimpleBitStorage(bitsPerBlock, BLOCKS_PER_SECTION, rawDataArray); diff --git a/HiderCore/src/de/steamwar/techhider/ObfuscationConfig.java b/HiderCore/src/de/steamwar/techhider/ObfuscationConfig.java index cc8310a5..554abaa3 100644 --- a/HiderCore/src/de/steamwar/techhider/ObfuscationConfig.java +++ b/HiderCore/src/de/steamwar/techhider/ObfuscationConfig.java @@ -49,6 +49,7 @@ public final class ObfuscationConfig { } private final Block obfuscateWith; + private final BlockState obfuscationState; private final Set blocks; private final Set blockStates; private final Set> blockEntityTypes; @@ -59,6 +60,7 @@ public final class ObfuscationConfig { */ public ObfuscationConfig(Material obfuscateWith, Set hiddenBlocks, Set hiddenBlockEntities) { this.obfuscateWith = CraftMagicNumbers.getBlock(obfuscateWith); + this.obfuscationState = this.obfuscateWith.defaultBlockState(); this.blocks = hiddenBlocks.stream() .map(CraftMagicNumbers::getBlock) diff --git a/HiderCore/src/de/steamwar/techhider/TechHider.java b/HiderCore/src/de/steamwar/techhider/TechHider.java index eee198e6..af640d18 100644 --- a/HiderCore/src/de/steamwar/techhider/TechHider.java +++ b/HiderCore/src/de/steamwar/techhider/TechHider.java @@ -36,16 +36,20 @@ import net.minecraft.network.protocol.configuration.ClientboundUpdateEnabledFeat import net.minecraft.network.protocol.cookie.ClientboundCookieRequestPacket; import net.minecraft.network.protocol.game.*; import net.minecraft.network.protocol.login.*; +import net.minecraft.network.protocol.ping.ClientboundPongResponsePacket; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; +import org.bukkit.Bukkit; import org.bukkit.entity.Player; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.ToIntFunction; +import java.util.logging.Level; /** * The TechHider follows the default-deny security principle, @@ -56,16 +60,14 @@ public class TechHider { private final Set> bypassingPackets; private final Map>, BiFunction, Packet>> packetProcessors; - private final BlockState blockStateUsedForObfuscation; + private final Set> loggedUnhandledPackets = ConcurrentHashMap.newKeySet(); private final ChunkHider chunkHider; private final AccessPrivilegeProvider privilegeProvider; // TODO handle packet bundle - public TechHider(Block blockUsedForObfuscation, AccessPrivilegeProvider privilegeProvider) { - this.blockStateUsedForObfuscation = blockUsedForObfuscation.defaultBlockState(); - - this.chunkHider = new ChunkHider(blockUsedForObfuscation); + public TechHider(AccessPrivilegeProvider privilegeProvider) { + this.chunkHider = new ChunkHider(); this.privilegeProvider = privilegeProvider; this.bypassingPackets = new HashSet<>(List.of( @@ -176,6 +178,9 @@ public class TechHider { ClientboundBlockChangedAckPacket.class, // 7.1.5 Acknowledge Block Change ClientboundChunkBatchFinishedPacket.class, // 7.1.12 Chunk Batch Finished (Delimiter) ClientboundChunkBatchStartPacket.class, // 7.1.13 Chunk Batch Start (Delimiter) + ClientboundAwardStatsPacket.class, // 7.1.4 Award Statistics: the player's own statistics + ClientboundDebugSamplePacket.class, // 7.1.28 Debug Sample: server tick timings + ClientboundPongResponsePacket.class, // Ping Response: answer to the client's own ping ClientboundChunksBiomesPacket.class, // 7.1.14 Chunk Biomes ClientboundContainerClosePacket.class, // 7.1.18 Close Container ClientboundSetChunkCacheCenterPacket.class, // 7.1.93 Set Center Chunk @@ -359,6 +364,9 @@ public class TechHider { if (processor != null) { return processor.apply(access.get(), packet); } else { + if (loggedUnhandledPackets.add(packet.getClass())) { + Bukkit.getLogger().log(Level.WARNING, "[TechHider] Dropping unhandled packet type " + packet.getClass().getName()); + } return null; } } @@ -438,7 +446,7 @@ public class TechHider { if (access.canAccessPosition(blockX, blockY, blockZ) && access.canAccessBlock(blockX, blockY, blockZ, block) && access.canAccessBlockState(blockX, blockY, blockZ, blockState)) { return packet; } else if (access.canAccessPosition(blockX, blockY, blockZ)) { - return new ClientboundBlockUpdatePacket(blockPos, blockStateUsedForObfuscation); + return new ClientboundBlockUpdatePacket(blockPos, access.getObfuscation(blockX, blockY, blockZ)); } else { return null; } @@ -491,7 +499,7 @@ public class TechHider { filteredStates.add(state); } else if (access.canAccessPosition(worldX, worldY, worldZ)) { filteredPos.add(posShort); - filteredStates.add(blockStateUsedForObfuscation); + filteredStates.add(access.getObfuscation(worldX, worldY, worldZ)); } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java deleted file mode 100644 index bca91a11..00000000 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java +++ /dev/null @@ -1,301 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 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.techhider.legacy; - -import de.steamwar.core.ProtocolUtils; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import lombok.Getter; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; -import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; -import net.minecraft.util.SimpleBitStorage; -import net.minecraft.world.level.block.entity.BlockEntityType; -import org.bukkit.entity.Player; - -import java.util.Collections; -import java.util.Set; -import java.util.function.BiFunction; -import java.util.function.UnaryOperator; -import java.util.stream.Collectors; - -@Deprecated -public class ChunkHider { - public static final ChunkHider impl = new ChunkHider(); - - private static final UnaryOperator chunkPacketCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkWithLightPacket::new); - private static final UnaryOperator chunkDataCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class, ClientboundLevelChunkPacketData::new); - - public BiFunction chunkHiderGenerator(TechHider techHider) { - return (p, packet) -> { - int chunkX = packet.getX(); - int chunkZ = packet.getZ(); - if (techHider.getLocationEvaluator().skipChunk(p, chunkX, chunkZ)) { - return packet; - } - - packet = chunkPacketCloner.apply(packet); - ClientboundLevelChunkPacketData dataWrapper = chunkDataCloner.apply(packet.getChunkData()); - - Set hiddenBlockEntities = techHider.getHiddenBlockEntities(); - dataWrapper.blockEntitiesData = dataWrapper.blockEntitiesData.stream() - .filter(te -> tileEntityVisible(hiddenBlockEntities, te)) - .collect(Collectors.toList()); - - ByteBuf in = Unpooled.wrappedBuffer(dataWrapper.buffer); - ByteBuf out = Unpooled.buffer(in.readableBytes() + 64); - for (int yOffset = p.getWorld().getMinHeight(); yOffset < p.getWorld().getMaxHeight(); yOffset += 16) { - SectionHider section = new SectionHider(p, techHider, in, out, chunkX, yOffset / 16, chunkZ); - section.copyBlockCount(); - - blocks(section); - biomes(section); - } - - if (in.readableBytes() != 0) { - throw new IllegalStateException("ChunkHider21: Incomplete chunk data, " + in.readableBytes() + " bytes left"); - } - - byte[] data = new byte[out.readableBytes()]; - out.readBytes(data); - dataWrapper.buffer = data; - - packet.chunkData = dataWrapper; - return packet; - }; - } - - protected boolean tileEntityVisible(Set hiddenBlockEntities, ClientboundLevelChunkPacketData.BlockEntityInfo tile) { - BlockEntityType type = tile.type; - String path = BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(type).getPath(); - return !hiddenBlockEntities.contains(path); - } - - private void blocks(SectionHider section) { - section.copyBitsPerBlock(); - - boolean singleValued = section.getBitsPerBlock() == 0; - if (singleValued) { - int value = ProtocolUtils.readVarInt(section.getIn()); - ProtocolUtils.writeVarInt(section.getOut(), !section.isSkipSection() && section.getObfuscate().contains(value) ? section.getTarget() : value); - return; - } else if (section.getBitsPerBlock() < 9) { - // Indirect (paletted) storage – only present when bitsPerBlock < 9 in 1.21+ - section.processPalette(); - } - - if (section.isSkipSection() || (!section.blockPrecise() && section.isPaletted())) { - section.skipNewDataArray(4096); - return; - } - - SimpleBitStorage values = new SimpleBitStorage(section.getBitsPerBlock(), 4096, section.readNewDataArray(4096)); - - for (int y = 0; y < 16; y++) { - for (int z = 0; z < 16; z++) { - for (int x = 0; x < 16; x++) { - int pos = (((y * 16) + z) * 16) + x; - - TechHider.State test = section.test(x, y, z); - - switch (test) { - case SKIP: - break; - case CHECK: - if (!section.getObfuscate().contains(values.get(pos))) { - break; - } - case HIDE: - values.set(pos, section.getTarget()); - break; - case HIDE_AIR: - default: - values.set(pos, section.getAir()); - } - } - } - } - - section.writeDataArray(values.getRaw()); - } - - private void biomes(SectionHider section) { - section.copyBitsPerBlock(); - if (section.getBitsPerBlock() == 0) { - section.copyVarInt(); - } else if (section.getBitsPerBlock() < 6) { - section.skipPalette(); - section.skipNewDataArray(64); - } else { - // Direct (global) biome IDs – no palette present - section.skipNewDataArray(64); - } - } - - @Getter - class SectionHider { - private final Player player; - private final TechHider techHider; - private final ByteBuf in; - private final ByteBuf out; - - private final int chunkX; - private final int chunkY; - private final int chunkZ; - private final int offsetX; - private final int offsetY; - private final int offsetZ; - - private final boolean skipSection; - - private boolean paletted; - private int bitsPerBlock; - private int blockCount; - private int air; - private int target; - private Set obfuscate; - - public SectionHider(Player player, TechHider techHider, ByteBuf in, ByteBuf out, int chunkX, int chunkY, int chunkZ) { - this.player = player; - this.techHider = techHider; - this.in = in; - this.out = out; - this.chunkX = chunkX; - this.chunkY = chunkY; - this.chunkZ = chunkZ; - this.offsetX = 16 * chunkX; - this.offsetY = 16 * chunkY; - this.offsetZ = 16 * chunkZ; - this.skipSection = techHider.getLocationEvaluator().skipChunkSection(player, chunkX, chunkY, chunkZ); - - this.paletted = false; - this.bitsPerBlock = 0; - this.air = TechHider.AIR_ID; - this.target = techHider.getObfuscationTargetId(); - this.obfuscate = techHider.getObfuscateIds(); - } - - public boolean blockPrecise() { - return techHider.getLocationEvaluator().blockPrecise(player, chunkX, chunkY, chunkZ); - } - - public TechHider.State test(int x, int y, int z) { - return techHider.getLocationEvaluator().check(player, offsetX + x, offsetY + y, offsetZ + z); - } - - public void copyBlockCount() { - this.blockCount = in.readShort(); - out.writeShort(blockCount); - } - - public void copyBitsPerBlock() { - bitsPerBlock = in.readByte(); - out.writeByte(bitsPerBlock); - } - - public int copyVarInt() { - int value = ProtocolUtils.readVarInt(in); - ProtocolUtils.writeVarInt(out, value); - return value; - } - - public void skipPalette() { - int paletteLength = copyVarInt(); - for (int i = 0; i < paletteLength; i++) { - copyVarInt(); - } - } - - public void processPalette() { - if (skipSection) { - skipPalette(); - return; - } - - int paletteLength = copyVarInt(); - if (paletteLength == 0) return; - - paletted = true; - air = 0; - target = 0; - - for (int i = 0; i < paletteLength; i++) { - int entry = ProtocolUtils.readVarInt(in); - if (obfuscate.contains(entry)) { - entry = techHider.getObfuscationTargetId(); - } - - if (entry == TechHider.AIR_ID) { - air = i; - } else if (entry == techHider.getObfuscationTargetId()) { - target = i; - } - - ProtocolUtils.writeVarInt(out, entry); - } - obfuscate = Collections.emptySet(); - } - - public void skipDataArray() { - int dataArrayLength = copyVarInt(); - out.writeBytes(in, dataArrayLength * 8); - } - - public void skipNewDataArray(int entries) { - if (bitsPerBlock == 0) { - return; - } - - char valuesPerLong = (char) (64 / bitsPerBlock); - int i1 = (entries + valuesPerLong - 1) / valuesPerLong; - out.writeBytes(in, i1 * Long.BYTES); - } - - public long[] readDataArray() { - long[] array = new long[copyVarInt()]; - for (int i = 0; i < array.length; i++) { - array[i] = in.readLong(); - } - - return array; - } - - public long[] readNewDataArray(int entries) { - if (bitsPerBlock == 0) { - return new long[entries]; - } - - char valuesPerLong = (char) (64 / bitsPerBlock); - int i1 = (entries + valuesPerLong - 1) / valuesPerLong; - long[] array = new long[i1]; - for (int i = 0; i < i1; i++) { - array[i] = in.readLong(); - } - - return array; - } - - public void writeDataArray(long[] array) { - for (long l : array) { - out.writeLong(l); - } - } - } -} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolWrapper.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolWrapper.java deleted file mode 100644 index cb7b8d05..00000000 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolWrapper.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 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.techhider.legacy; - -import net.minecraft.core.SectionPos; -import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket; -import net.minecraft.world.level.block.state.BlockState; -import org.bukkit.entity.Player; - -import java.util.ArrayList; -import java.util.function.BiFunction; - -@Deprecated -public class ProtocolWrapper { - public static final ProtocolWrapper impl = new ProtocolWrapper(); - - public BiFunction multiBlockChangeGenerator(TechHider techHider) { - return (p, packet) -> { - TechHider.LocationEvaluator locationEvaluator = techHider.getLocationEvaluator(); - SectionPos chunkCoords = packet.sectionPos; - int chunkX = chunkCoords.getX(); - int chunkY = chunkCoords.getY(); - int chunkZ = chunkCoords.getZ(); - if (locationEvaluator.skipChunkSection(p, chunkX, chunkY, chunkZ)) { - return packet; - } - - packet = TechHider.multiBlockChangeCloner.apply(packet); - final short[] oldPos = packet.positions; - final BlockState[] oldBlocks = packet.states; - ArrayList poss = new ArrayList<>(oldPos.length); - ArrayList blocks = new ArrayList<>(oldPos.length); - for (int i = 0; i < oldPos.length; i++) { - short pos = oldPos[i]; - BlockState block = oldBlocks[i]; - switch (locationEvaluator.check(p, 16 * chunkX + (pos >> 8 & 0xF), 16 * chunkY + (pos & 0xF), 16 * chunkZ + (pos >> 4 & 0xF))) { - case SKIP: - poss.add(pos); - blocks.add(block); - break; - case CHECK: - poss.add(pos); - blocks.add(techHider.iBlockDataHidden(block) ? (BlockState) techHider.getObfuscationTarget() : block); - break; - default: - break; - } - } - - if (blocks.isEmpty()) return null; - - short[] newPos = new short[poss.size()]; - for (int i = 0; i < newPos.length; i++) { - newPos[i] = poss.get(i); - } - - packet.positions = newPos; - packet.states = blocks.toArray(BlockState[]::new); - return packet; - }; - } -} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java deleted file mode 100644 index 5c9cd169..00000000 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 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.techhider.legacy; - -import com.comphenix.tinyprotocol.TinyProtocol; -import de.steamwar.core.BlockIds; -import de.steamwar.core.ProtocolUtils; -import lombok.Getter; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Vec3i; -import net.minecraft.network.protocol.game.*; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.entity.BlockEntityType; -import net.minecraft.world.level.block.state.BlockState; -import org.bukkit.Material; -import org.bukkit.craftbukkit.util.CraftMagicNumbers; -import org.bukkit.entity.Player; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.function.BiFunction; -import java.util.function.UnaryOperator; -import java.util.stream.Collectors; - -@Deprecated -public class TechHider { - - public static final Class blockPosition = BlockPos.class; - - public static final Class iBlockData = BlockState.class; - public static final Class block = Block.class; - - public boolean iBlockDataHidden(BlockState iBlockData) { - return obfuscateIds.contains(BlockIds.impl.getCombinedId(iBlockData)); - } - - public static final Object AIR = CraftMagicNumbers.getBlock(Material.AIR).defaultBlockState(); - public static final int AIR_ID = BlockIds.impl.materialToId(Material.AIR); - - private final Map, BiFunction> techhiders = new HashMap<>(); - @Getter - private final LocationEvaluator locationEvaluator; - @Getter - private final Object obfuscationTarget; - @Getter - private final int obfuscationTargetId; - @Getter - private final Set obfuscateIds; - @Getter - private final Set hiddenBlockEntities; - - public TechHider(LocationEvaluator locationEvaluator, Material obfuscationTarget, Set obfuscate, Set hiddenBlockEntities) { - this.locationEvaluator = locationEvaluator; - this.obfuscateIds = obfuscate.stream().flatMap(m -> BlockIds.impl.materialToAllIds(m).stream()).collect(Collectors.toSet()); - this.hiddenBlockEntities = hiddenBlockEntities; - this.obfuscationTarget = CraftMagicNumbers.getBlock(obfuscationTarget).defaultBlockState(); - this.obfuscationTargetId = BlockIds.impl.materialToId(obfuscationTarget); - - techhiders.put(ClientboundBlockEventPacket.class, (player, o) -> this.blockActionHider(player, (ClientboundBlockEventPacket) o)); - techhiders.put(ClientboundBlockUpdatePacket.class, (player, o) -> this.blockChangeHider(player, (ClientboundBlockUpdatePacket) o)); - techhiders.put(ClientboundBlockEntityDataPacket.class, (player, o) -> this.tileEntityDataHider(player, (ClientboundBlockEntityDataPacket) o)); - techhiders.put(ClientboundSectionBlocksUpdatePacket.class, (BiFunction) ProtocolWrapper.impl.multiBlockChangeGenerator(this)); - techhiders.put(ClientboundLevelChunkWithLightPacket.class, (BiFunction) ChunkHider.impl.chunkHiderGenerator(this)); - techhiders.put(ServerboundUseItemOnPacket.class, (p, packet) -> locationEvaluator.suppressInteractions(p) ? null : packet); - techhiders.put(ServerboundInteractPacket.class, (p, packet) -> locationEvaluator.suppressInteractions(p) ? null : packet); - - } - - public void enable() { - techhiders.forEach((type, playerObjectBiFunction) -> TinyProtocol.instance.addFilter((Class) type, playerObjectBiFunction)); - } - - public void disable() { - techhiders.forEach(TinyProtocol.instance::removeFilter); - } - - public static final UnaryOperator multiBlockChangeCloner = ProtocolUtils.shallowCloneGenerator(ClientboundSectionBlocksUpdatePacket.class, ClientboundSectionBlocksUpdatePacket::new); - - private static final UnaryOperator blockChangeCloner = ProtocolUtils.shallowCloneGenerator(ClientboundBlockUpdatePacket.class, ClientboundBlockUpdatePacket::new); - - private ClientboundBlockUpdatePacket blockChangeHider(Player p, ClientboundBlockUpdatePacket packet) { - switch (locationEvaluator.checkBlockPos(p, packet.getPos())) { - case SKIP: - return packet; - case CHECK: - if (!iBlockDataHidden(packet.blockState)) { - return packet; - } - case HIDE: - packet = blockChangeCloner.apply(packet); - packet.blockState = (BlockState) obfuscationTarget; - return packet; - case HIDE_AIR: - default: - packet = blockChangeCloner.apply(packet); - packet.blockState = (BlockState) AIR; - return packet; - } - } - - private Object blockActionHider(Player p, ClientboundBlockEventPacket packet) { - if (locationEvaluator.checkBlockPos(p, packet.getPos()) == State.SKIP) { - return packet; - } - return null; - } - - private ClientboundBlockEntityDataPacket tileEntityDataHider(Player p, ClientboundBlockEntityDataPacket packet) { - switch (locationEvaluator.checkBlockPos(p, packet.getPos())) { - case SKIP: - return packet; - case CHECK: - if (packet.getType() != BlockEntityType.SIGN) { - return packet; - } - default: - return null; - } - } - - public enum State { - SKIP, - CHECK, - HIDE, - HIDE_AIR - } - - public interface LocationEvaluator { - default boolean suppressInteractions(Player player) { - return false; - } - - boolean skipChunk(Player player, int x, int z); - - default boolean skipChunkSection(Player player, int x, int y, int z) { - return skipChunk(player, x, z); - } - - default State check(Player player, int x, int y, int z) { - return skipChunkSection(player, ProtocolUtils.posToChunk(x), ProtocolUtils.posToChunk(y), ProtocolUtils.posToChunk(z)) ? State.SKIP : State.CHECK; - } - - default State checkBlockPos(Player player, Vec3i pos) { - return check(player, pos.getX(), pos.getY(), pos.getZ()); - } - - default boolean blockPrecise(Player player, int x, int y, int z) { - return false; - } - } -} diff --git a/SpigotCore/SpigotCore_Main/src/spigotcore.accesswidener b/SpigotCore/SpigotCore_Main/src/spigotcore.accesswidener index 99170241..210a5faf 100644 --- a/SpigotCore/SpigotCore_Main/src/spigotcore.accesswidener +++ b/SpigotCore/SpigotCore_Main/src/spigotcore.accesswidener @@ -35,34 +35,3 @@ accessible field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket mutable field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket yHeadRot B accessible field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket entityId I mutable field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket entityId I - -# For legacy/ChunkHider -## transitive-extendable means that a public no args constructor is added without any super initialization -transitive-extendable class net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket -accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket chunkData Lnet/minecraft/network/protocol/game/ClientboundLevelChunkPacketData; -mutable field net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket chunkData Lnet/minecraft/network/protocol/game/ClientboundLevelChunkPacketData; - -## transitive-extendable means that a public no args constructor is added without any super initialization -transitive-extendable class net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData -accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData buffer [B -mutable field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData buffer [B -accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData blockEntitiesData Ljava/util/List; -mutable field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData blockEntitiesData Ljava/util/List; - -accessible class net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo -accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo type Lnet/minecraft/world/level/block/entity/BlockEntityType; -accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo packedXZ I -accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo y I - -# For legacy/TechHider -## transitive-extendable means that a public no args constructor is added without any super initialization -transitive-extendable class net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket -accessible field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket sectionPos Lnet/minecraft/core/SectionPos; -accessible field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket positions [S -mutable field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket positions [S -accessible field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket states [Lnet/minecraft/world/level/block/state/BlockState; -mutable field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket states [Lnet/minecraft/world/level/block/state/BlockState; - -## transitive-extendable means that a public no args constructor is added without any super initialization -transitive-extendable class net/minecraft/network/protocol/game/ClientboundBlockUpdatePacket -mutable field net/minecraft/network/protocol/game/ClientboundBlockUpdatePacket blockState Lnet/minecraft/world/level/block/state/BlockState; \ No newline at end of file From d1fb4ca67ea81c2f312cf4bafb1f14c734e87be7 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 15:09:04 +0100 Subject: [PATCH 21/46] Allow removing hulls and resend their chunks HullHider#removeHull stops hiding a hull: it shows the entities and REntities the hull hid and resends its chunks to everybody who could not view it. A hull is enabled again by adding and initialising it. Hull#resendChunks only resends chunks the player has loaded. HullHider now resends the chunks to non-viewers after initialising a hull itself, so FightHullHider no longer does it. --- .../fightsystem/utils/FightHullHider.java | 9 ----- HiderCore/src/de/steamwar/hullhider/Hull.java | 34 +++++++++++++++++ .../src/de/steamwar/hullhider/HullHider.java | 37 ++++++++++++++++++- .../src/de/steamwar/hullhider/HullRegion.java | 12 ++++++ 4 files changed, 82 insertions(+), 10 deletions(-) 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 4175655b..6c1d9d1b 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightHullHider.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightHullHider.java @@ -32,7 +32,6 @@ import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.fightsystem.states.StateDependentTask; import de.steamwar.hullhider.Hull; import de.steamwar.hullhider.HullHider; -import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.entity.Player; @@ -68,14 +67,6 @@ public class FightHullHider { public void initialize(FightTeam team) { if (!TechHiderWrapper.ENABLED) return; 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); - } - } } public void fill(FightTeam team, boolean visible) { diff --git a/HiderCore/src/de/steamwar/hullhider/Hull.java b/HiderCore/src/de/steamwar/hullhider/Hull.java index 7344af14..04826401 100644 --- a/HiderCore/src/de/steamwar/hullhider/Hull.java +++ b/HiderCore/src/de/steamwar/hullhider/Hull.java @@ -21,6 +21,7 @@ package de.steamwar.hullhider; import com.comphenix.tinyprotocol.TinyProtocol; import com.mojang.authlib.GameProfile; +import de.steamwar.core.CraftbukkitWrapper; import de.steamwar.core.ProtocolWrapper; import de.steamwar.entity.REntity; import it.unimi.dsi.fastutil.shorts.Short2ObjectArrayMap; @@ -32,6 +33,7 @@ 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.Chunk; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; @@ -180,6 +182,38 @@ public class Hull { rentities.remove(entity); } + /** + * Shows all entities hidden by this hull again, used when the hull is removed. + */ + public void reveal() { + for (Player player : Bukkit.getOnlinePlayers()) { + if (isViewer(player)) continue; + + for (Entity entity : entities) { + if (!player.equals(entity)) trackEntity(player, entity); + } + } + entities.clear(); + + for (REntity entity : rentities) { + entity.hide(false); + } + rentities.clear(); + } + + /** + * Resends the chunks of this hull the player has loaded, so they are encoded with the current visibility. + */ + public void resendChunks(Player player) { + if (player.getWorld() != world) return; + + region.forEachChunk((chunkX, chunkZ) -> { + if (player.isChunkSent(Chunk.getChunkKey(chunkX, chunkZ))) { + CraftbukkitWrapper.sendChunk(player, chunkX, chunkZ); + } + }); + } + public void fill(boolean visible) { visibility.set(0, visibility.size(), visible); occluding.set(0, occluding.size(), !visible); diff --git a/HiderCore/src/de/steamwar/hullhider/HullHider.java b/HiderCore/src/de/steamwar/hullhider/HullHider.java index 9bbc846f..fd37baa8 100644 --- a/HiderCore/src/de/steamwar/hullhider/HullHider.java +++ b/HiderCore/src/de/steamwar/hullhider/HullHider.java @@ -20,6 +20,7 @@ package de.steamwar.hullhider; import de.steamwar.entity.REntity; +import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.entity.Entity; @@ -45,11 +46,30 @@ public class HullHider implements Listener { // Read on netty threads, replaced on every change private volatile Hull[] hulls = new Hull[0]; + /** + * The hull hides everything from non-viewers until it is initialised. + */ public void addHull(K key, Hull hull) { hullMap.put(key, hull); hulls = hullMap.values().toArray(new Hull[0]); } + /** + * Stops hiding the hull: shows the entities it hid and resends its chunks to everybody who could not view it. + */ + public void removeHull(K key) { + Hull hull = hullMap.remove(key); + if (hull == null) return; + hulls = hullMap.values().toArray(new Hull[0]); + + hull.reveal(); + for (Player player : Bukkit.getOnlinePlayers()) { + if (!hull.isViewer(player)) { + hull.resendChunks(player); + } + } + } + public Hull getHull(K key) { return hullMap.get(key); } @@ -59,7 +79,22 @@ public class HullHider implements Listener { } public void initialize(K key) { - hullMap.get(key).initialize(); + Hull hull = hullMap.get(key); + 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)) { + hull.resendChunks(player); + } + } + } + + /** + * Has to be called after the player became a viewer of the hull or stopped being one. + */ + public void resendChunks(Player player, K key) { + hullMap.get(key).resendChunks(player); } public void fill(K key, boolean visible) { diff --git a/HiderCore/src/de/steamwar/hullhider/HullRegion.java b/HiderCore/src/de/steamwar/hullhider/HullRegion.java index 0006a2af..7b5d30ab 100644 --- a/HiderCore/src/de/steamwar/hullhider/HullRegion.java +++ b/HiderCore/src/de/steamwar/hullhider/HullRegion.java @@ -90,10 +90,22 @@ public final class HullRegion { } } + public void forEachChunk(ChunkConsumer executor) { + for (int chunkX = minX >> 4; chunkX <= (maxX - 1) >> 4; chunkX++) { + for (int chunkZ = minZ >> 4; chunkZ <= (maxZ - 1) >> 4; chunkZ++) { + executor.accept(chunkX, chunkZ); + } + } + } + public interface BlockConsumer { void accept(int x, int y, int z); } + public interface ChunkConsumer { + void accept(int chunkX, int chunkZ); + } + @Override public String toString() { return minX + "," + minY + "," + minZ + "->" + maxX + "," + maxY + "," + maxZ; From f02f356b2bf1847157855750a990ad2deb7fd5cb Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 15:32:18 +0100 Subject: [PATCH 22/46] 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()); + } + } +} From a9fbb4cd8d35e4fa60ff09e47144f930c1af8f71 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 15:36:02 +0100 Subject: [PATCH 23/46] Combine techhider, xray and hullhider scoreboard lines As only one of /techhider, /xray and /hullhider can be active per region, the scoreboard shows them in a single "View" line naming the active one. --- .../BauSystem_Main/src/BauSystem.properties | 4 +- .../src/BauSystem_de.properties | 4 +- .../features/hullhider/HullHiderCommand.java | 19 +---- .../features/techhider/TechHiderCommand.java | 19 +---- .../techhider/ViewScoreboardElement.java | 70 +++++++++++++++++++ .../bausystem/features/xray/XrayCommand.java | 19 +---- 6 files changed, 81 insertions(+), 54 deletions(-) create mode 100644 BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/ViewScoreboardElement.java diff --git a/BauSystem/BauSystem_Main/src/BauSystem.properties b/BauSystem/BauSystem_Main/src/BauSystem.properties index 80e591d9..fdbe8ff3 100644 --- a/BauSystem/BauSystem_Main/src/BauSystem.properties +++ b/BauSystem/BauSystem_Main/src/BauSystem.properties @@ -35,9 +35,7 @@ SCOREBOARD_LOADER = Loader SCOREBOARD_TPS = TPS SCOREBOARD_TPS_FROZEN = §eFrozen SCOREBOARD_TRACE_TICKS = Ticks -SCOREBOARD_TECHHIDER = TechHider§8: §aOn -SCOREBOARD_XRAY = XRay§8: §aOn -SCOREBOARD_HULLHIDER = HullHider§8: §aOn +SCOREBOARD_VIEW = View§8: §a{0} 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 diff --git a/BauSystem/BauSystem_Main/src/BauSystem_de.properties b/BauSystem/BauSystem_Main/src/BauSystem_de.properties index e08803a9..3fb95eb8 100644 --- a/BauSystem/BauSystem_Main/src/BauSystem_de.properties +++ b/BauSystem/BauSystem_Main/src/BauSystem_de.properties @@ -35,9 +35,7 @@ SCOREBOARD_LOADER = Loader SCOREBOARD_TPS = TPS SCOREBOARD_TPS_FROZEN = §eEingefroren SCOREBOARD_TRACE_TICKS = Ticks -SCOREBOARD_TECHHIDER = TechHider§8: §aAn -SCOREBOARD_XRAY = XRay§8: §aAn -SCOREBOARD_HULLHIDER = HullHider§8: §aAn +SCOREBOARD_VIEW = Ansicht§8: §a{0} 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 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 index b8ad87de..76f333d1 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullHiderCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullHiderCommand.java @@ -25,7 +25,6 @@ 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; @@ -53,7 +52,7 @@ 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 class HullHiderCommand extends SWCommand implements Listener { public HullHiderCommand() { super("hullhider"); @@ -161,19 +160,7 @@ public class HullHiderCommand extends SWCommand implements Listener, ScoreboardE } } - @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); + public boolean isActive(Region region, Player player) { + return hidden.getOrDefault(region, Collections.emptySet()).contains(player); } } 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 89956fe2..0548e99a 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 @@ -25,7 +25,6 @@ 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; -import de.steamwar.bausystem.utils.ScoreboardElement; import de.steamwar.command.PreviousArguments; import de.steamwar.command.SWCommand; import de.steamwar.command.TypeMapper; @@ -46,7 +45,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @Linked -public class TechHiderCommand extends SWCommand implements Listener, ScoreboardElement { +public class TechHiderCommand extends SWCommand implements Listener { public TechHiderCommand() { super("techhider", "hider", "obfuscate", "hide", "hide-tech"); @@ -143,19 +142,7 @@ public class TechHiderCommand extends SWCommand implements Listener, ScoreboardE }); } - @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_TECHHIDER", p); + public boolean isActive(Region region, Player player) { + return hidden.getOrDefault(region, Collections.emptySet()).contains(player); } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/ViewScoreboardElement.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/ViewScoreboardElement.java new file mode 100644 index 00000000..6b9dab13 --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/ViewScoreboardElement.java @@ -0,0 +1,70 @@ +/* + * 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.bausystem.BauSystem; +import de.steamwar.bausystem.features.hullhider.HullHiderCommand; +import de.steamwar.bausystem.features.xray.XrayCommand; +import de.steamwar.bausystem.region.Region; +import de.steamwar.bausystem.utils.ScoreboardElement; +import de.steamwar.linkage.Linked; +import de.steamwar.linkage.LinkedInstance; +import org.bukkit.entity.Player; + +/** + * Shows which of /techhider, /xray and /hullhider is active, at most one of them can be per region. + */ +@Linked +public class ViewScoreboardElement implements ScoreboardElement { + + @LinkedInstance + public TechHiderCommand techHiderCommand; + + @LinkedInstance + public XrayCommand xrayCommand; + + @LinkedInstance + public HullHiderCommand hullHiderCommand; + + @Override + public ScoreboardGroup getGroup() { + return ScoreboardGroup.OTHER; + } + + @Override + public int order() { + return 0; + } + + @Override + public String get(Region region, Player p) { + String view; + if (techHiderCommand.isActive(region, p)) { + view = "TechHider"; + } else if (xrayCommand.isActive(region, p)) { + view = "XRay"; + } else if (hullHiderCommand.isActive(region, p)) { + view = "HullHider"; + } else { + return null; + } + return "§e" + BauSystem.MESSAGE.parse("SCOREBOARD_VIEW", p, view); + } +} 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 9e0742d0..c8f01679 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 @@ -25,7 +25,6 @@ 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; -import de.steamwar.bausystem.utils.ScoreboardElement; import de.steamwar.command.SWCommand; import de.steamwar.core.CraftbukkitWrapper; import de.steamwar.linkage.Linked; @@ -49,7 +48,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiFunction; @Linked -public class XrayCommand extends SWCommand implements Listener, ScoreboardElement { +public class XrayCommand extends SWCommand implements Listener { public XrayCommand() { super("xray"); @@ -167,19 +166,7 @@ public class XrayCommand extends SWCommand implements Listener, ScoreboardElemen }); } - @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_XRAY", p); + public boolean isActive(Region region, Player player) { + return hidden.getOrDefault(region, Collections.emptySet()).contains(player); } } From abf44b2306635f594ed196bac3fff097fb46866e Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 15:43:35 +0100 Subject: [PATCH 24/46] Allow @Linked on fields The value of a @Linked field of a linked class is linked like a linked class itself, e.g. registered as listener, and unlinked again on unlink. The field has to be initialised when linking. HullHiderCommand and TPSSystem use it instead of registering their listeners by hand. --- .../features/hullhider/HullHiderCommand.java | 2 +- .../features/tpslimit/TPSSystem.java | 4 ++- .../de/steamwar/linkage/AbstractLinker.java | 34 +++++++++++++++++++ .../src/de/steamwar/linkage/Linked.java | 8 +++-- 4 files changed, 44 insertions(+), 4 deletions(-) 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 index 76f333d1..fc7a606c 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullHiderCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullHiderCommand.java @@ -57,10 +57,10 @@ public class HullHiderCommand extends SWCommand implements Listener { public HullHiderCommand() { super("hullhider"); - Bukkit.getPluginManager().registerEvents(hullHider, BauSystem.getInstance()); Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), this::onTick, 1, 1); } + @Linked private final HullHider hullHider = new HullHider<>(); // Read on netty threads by the BauTechHider private final Map hulls = new ConcurrentHashMap<>(); diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tpslimit/TPSSystem.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tpslimit/TPSSystem.java index 2789c4bf..7c503168 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tpslimit/TPSSystem.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tpslimit/TPSSystem.java @@ -50,6 +50,9 @@ import java.util.Arrays; @Linked public class TPSSystem implements Listener { + @Linked + private final TickManager tickManager = TickManager.impl; + public TPSSystem() { if (TickManager.impl.canFreeze()) { new TPSFreezeCommand(); @@ -68,7 +71,6 @@ public class TPSSystem implements Listener { new TickDefaultCommand(); new TPSBaseCommand(); new TickBaseCommand(); - Bukkit.getPluginManager().registerEvents(TickManager.impl, BauSystem.getInstance()); } @EventHandler diff --git a/CommonCore/Linkage/src/de/steamwar/linkage/AbstractLinker.java b/CommonCore/Linkage/src/de/steamwar/linkage/AbstractLinker.java index 4a2db53d..00d4b43a 100644 --- a/CommonCore/Linkage/src/de/steamwar/linkage/AbstractLinker.java +++ b/CommonCore/Linkage/src/de/steamwar/linkage/AbstractLinker.java @@ -28,6 +28,7 @@ import java.io.BufferedReader; import java.io.InputStreamReader; import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -38,6 +39,7 @@ public abstract class AbstractLinker { protected final T plugin; private Map, Object> instances = new HashMap<>(); + private final List fieldInstances = new ArrayList<>(); protected AbstractLinker(@NonNull T plugin) { this.plugin = plugin; @@ -90,6 +92,7 @@ public abstract class AbstractLinker { ((Enable) any).enable(); } linkObject(any); + linkFields(any); }); } catch (SecurityException e) { Throwable cause = e.getCause(); @@ -119,6 +122,31 @@ public abstract class AbstractLinker { } } + private void linkFields(Object any) { + for (Field field : any.getClass().getDeclaredFields()) { + if (field.getAnnotation(Linked.class) == null) continue; + + Object value; + try { + field.setAccessible(true); + value = field.get(Modifier.isStatic(field.getModifiers()) ? null : any); + } catch (IllegalAccessException e) { + throw new SecurityException(e.getMessage(), e); + } + + if (value == null) { + String message = "Linked field " + any.getClass().getName() + "." + field.getName() + " is not initialised"; + throw new SecurityException(message, new IllegalStateException(message)); + } + + fieldInstances.add(value); + if (value instanceof Enable) { + ((Enable) value).enable(); + } + linkObject(value); + } + } + public final void unlink() { instances.forEach((aClass, any) -> { unlinkObject(any); @@ -126,6 +154,12 @@ public abstract class AbstractLinker { ((Disable) any).disable(); } }); + fieldInstances.forEach(any -> { + unlinkObject(any); + if (any instanceof Disable) { + ((Disable) any).disable(); + } + }); } public final void addLinkableInstance(@NonNull Object instance) { diff --git a/CommonCore/Linkage/src/de/steamwar/linkage/Linked.java b/CommonCore/Linkage/src/de/steamwar/linkage/Linked.java index 465600b1..6e1c6a8f 100644 --- a/CommonCore/Linkage/src/de/steamwar/linkage/Linked.java +++ b/CommonCore/Linkage/src/de/steamwar/linkage/Linked.java @@ -26,8 +26,12 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * On a class: the class is instantiated and linked. + * On a field of a linked class: the (already initialised) value of the field is linked as well, e.g. registered as listener. + */ @IndexAnnotated -@Retention(RetentionPolicy.SOURCE) -@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.FIELD}) public @interface Linked { } From edb13fb7df4b868f1fb8def976ca9ad3f7a76fe4 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 15:43:35 +0100 Subject: [PATCH 25/46] Stop registering linked listeners twice ModifyCommand and WorldEditRenderer registered themselves as listener although the SpigotLinker already does, so all their event handlers ran twice. --- LobbySystem/src/de/steamwar/lobby/command/ModifyCommand.java | 2 -- .../src/de/steamwar/core/worldedit/WorldEditRenderer.java | 1 - 2 files changed, 3 deletions(-) diff --git a/LobbySystem/src/de/steamwar/lobby/command/ModifyCommand.java b/LobbySystem/src/de/steamwar/lobby/command/ModifyCommand.java index 8e8f9d99..7d0840d4 100644 --- a/LobbySystem/src/de/steamwar/lobby/command/ModifyCommand.java +++ b/LobbySystem/src/de/steamwar/lobby/command/ModifyCommand.java @@ -25,7 +25,6 @@ import de.steamwar.lobby.LobbySystem; import de.steamwar.lobby.listener.PlayerSpawn; import de.steamwar.sql.SteamwarUser; import de.steamwar.sql.UserPerm; -import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; @@ -47,7 +46,6 @@ public class ModifyCommand extends SWCommand implements Listener { public ModifyCommand() { super("modify"); - Bukkit.getPluginManager().registerEvents(this, LobbySystem.getInstance()); } @Register diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/worldedit/WorldEditRenderer.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/worldedit/WorldEditRenderer.java index b66c67ad..f11aec11 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/worldedit/WorldEditRenderer.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/worldedit/WorldEditRenderer.java @@ -55,7 +55,6 @@ public class WorldEditRenderer implements Listener { public WorldEditRenderer() { INSTANCE = this; we = WorldEditWrapper.getWorldEditPlugin(); - Bukkit.getPluginManager().registerEvents(this, Core.getInstance()); Bukkit.getScheduler().runTaskTimer(Core.getInstance(), () -> { for (Player player : Bukkit.getOnlinePlayers()) { From d76c3962683280ff123cba9e408158a713119434 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 15:57:25 +0100 Subject: [PATCH 26/46] Do not hide entities with the BauSystem hullhider HullHider gets a hideEntities parameter: the fight still hides entities inside hidden hulls, /hullhider only hides blocks. As the TechHider also dropped spawn packets at hidden positions, ForPlayer#canAccessEntitySpawn decides about them now, which the BauTechHider always allows. --- .../features/hullhider/HullHiderCommand.java | 7 ++----- .../features/techhider/BauTechHider.java | 11 +++++++---- .../fightsystem/utils/FightHullHider.java | 2 +- .../src/de/steamwar/hullhider/HullHider.java | 16 ++++++++++++++++ .../techhider/AccessPrivilegeProvider.java | 7 +++++++ .../src/de/steamwar/techhider/TechHider.java | 2 +- 6 files changed, 34 insertions(+), 11 deletions(-) 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 index fc7a606c..b5c2a3cf 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullHiderCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullHiderCommand.java @@ -36,7 +36,6 @@ 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; @@ -61,7 +60,8 @@ public class HullHiderCommand extends SWCommand implements Listener { } @Linked - private final HullHider hullHider = new HullHider<>(); + // Only blocks are hidden, entities stay visible + private final HullHider hullHider = new HullHider<>(false); // Read on netty threads by the BauTechHider private final Map hulls = new ConcurrentHashMap<>(); private final Map> hidden = new ConcurrentHashMap<>(); @@ -143,9 +143,6 @@ public class HullHiderCommand extends SWCommand implements Listener { private void onTick() { if (hidden.isEmpty()) return; - for (Entity entity : world().getEntities()) { - hullHider.checkEntity(entity); - } hullHider.sendUncoveredBlocks(); } 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 46867bc2..9b91d6a4 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 @@ -30,7 +30,6 @@ 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; @@ -38,6 +37,7 @@ import java.util.List; /** * 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. + * Entities are never hidden. */ @Linked public class BauTechHider implements AccessPrivilegeProvider { @@ -128,10 +128,13 @@ public class BauTechHider implements AccessPrivilegeProvider { @Override public boolean canAccessEntity(int entityId) { - if (hulls.length == 0) return true; + 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()); + // Entities are not hidden, even if they spawn inside a hidden hull + @Override + public boolean canAccessEntitySpawn(int entityId, int blockX, int blockY, int blockZ) { + return true; } @Override 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 cc5074ce..d667ba45 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightHullHider.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightHullHider.java @@ -50,7 +50,7 @@ import java.util.function.BiConsumer; */ public class FightHullHider { - private final HullHider hullHider = new HullHider<>(); + private final HullHider hullHider = new HullHider<>(true); public FightHullHider() { if (!TechHiderWrapper.ENABLED) return; diff --git a/HiderCore/src/de/steamwar/hullhider/HullHider.java b/HiderCore/src/de/steamwar/hullhider/HullHider.java index 92ecb24f..177d9ffe 100644 --- a/HiderCore/src/de/steamwar/hullhider/HullHider.java +++ b/HiderCore/src/de/steamwar/hullhider/HullHider.java @@ -42,10 +42,18 @@ import java.util.function.BiConsumer; */ public class HullHider implements Listener { + private final boolean hideEntities; private final Map hullMap = new LinkedHashMap<>(); // Read on netty threads, replaced on every change private volatile Hull[] hulls = new Hull[0]; + /** + * @param hideEntities whether entities and REntities inside the hidden part of a hull are hidden from non-viewers as well + */ + public HullHider(boolean hideEntities) { + this.hideEntities = hideEntities; + } + /** * The hull hides everything from non-viewers until it is initialised. */ @@ -157,6 +165,8 @@ public class HullHider implements Listener { @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onDeath(EntityDeathEvent e) { + if (!hideEntities) return; + for (Hull hull : hulls) { hull.removeEntity(e.getEntity()); } @@ -166,6 +176,8 @@ public class HullHider implements Listener { * 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) { + if (!hideEntities) return; + for (Hull hull : hulls) { hull.checkEntity(entity); } @@ -181,12 +193,16 @@ public class HullHider implements Listener { } public void updateREntity(REntity e) { + if (!hideEntities) return; + for (Hull hull : hulls) { hull.checkREntity(e); } } public void despawnREntity(REntity e) { + if (!hideEntities) return; + for (Hull hull : hulls) { hull.removeREntity(e); } diff --git a/HiderCore/src/de/steamwar/techhider/AccessPrivilegeProvider.java b/HiderCore/src/de/steamwar/techhider/AccessPrivilegeProvider.java index be0599cb..ff39a9fe 100644 --- a/HiderCore/src/de/steamwar/techhider/AccessPrivilegeProvider.java +++ b/HiderCore/src/de/steamwar/techhider/AccessPrivilegeProvider.java @@ -52,6 +52,13 @@ public interface AccessPrivilegeProvider { boolean canAccessEntity(int entityId); + /** + * @return whether the player may see the entity spawning at this position + */ + default boolean canAccessEntitySpawn(int entityId, int blockX, int blockY, int blockZ) { + return canAccessEntity(entityId) && canAccessPosition(blockX, blockY, blockZ); + } + boolean canAccessBlockEntity(int blockX, int blockY, int blockZ, BlockEntityType type); /** diff --git a/HiderCore/src/de/steamwar/techhider/TechHider.java b/HiderCore/src/de/steamwar/techhider/TechHider.java index af640d18..8bf4cb1d 100644 --- a/HiderCore/src/de/steamwar/techhider/TechHider.java +++ b/HiderCore/src/de/steamwar/techhider/TechHider.java @@ -372,7 +372,7 @@ public class TechHider { } private Packet processAddEntityPacket(ForPlayer access, ClientboundAddEntityPacket packet) { - if (access.canAccessEntity(packet.getId()) && access.canAccessPosition(Mth.floor(packet.getX()), Mth.floor(packet.getY()), Mth.floor(packet.getZ()))) { + if (access.canAccessEntitySpawn(packet.getId(), Mth.floor(packet.getX()), Mth.floor(packet.getY()), Mth.floor(packet.getZ()))) { return packet; } else { return null; From b78fab3172a2423f1618e420940e2b30870a9c7f Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 16:02:31 +0100 Subject: [PATCH 27/46] Stop the loader task and listener when a loader ends Every loader started a timer and registered itself as listener, but neither was stopped, so each loader kept running every tick until the server restarted. Both are now stopped when the loader is stopped or replaced by a new one. --- .../bausystem/features/loader/Loader.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/loader/Loader.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/loader/Loader.java index dd6e3f6d..398436db 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/loader/Loader.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/loader/Loader.java @@ -36,9 +36,11 @@ import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.inventory.ClickType; import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.scheduler.BukkitTask; import java.util.ArrayList; import java.util.List; @@ -63,13 +65,14 @@ public class Loader implements Listener, SWPlayer.Component { private List elements = new ArrayList<>(); private int currentElement = 0; private long waitTime = 0; + private final BukkitTask task; public Loader(Player p) { this.p = p; this.recorder = new LoaderRecorder(p, elements); Bukkit.getPluginManager().registerEvents(this, BauSystem.getInstance()); - BauSystem.runTaskTimer(BauSystem.getInstance(), () -> { + task = BauSystem.runTaskTimer(BauSystem.getInstance(), () -> { if (stage != Stage.RUNNING && stage != Stage.SINGLE) return; if (!Permission.BUILD.hasPermission(p)) return; if (waitTime > 0) { @@ -136,13 +139,27 @@ public class Loader implements Listener, SWPlayer.Component { } public void stop() { + end(); + if (getLoader(p) == this) { + SWPlayer.of(p).removeComponent(Loader.class); + } + } + + // Also called when the loader is replaced by a new one + @Override + public void onUnmount(SWPlayer player) { + end(); + } + + private void end() { stage = Stage.END; if (recorder != null) { recorder.stop(); recorder = null; } elements.clear(); - SWPlayer.of(p).removeComponent(Loader.class); + task.cancel(); + HandlerList.unregisterAll(this); } public boolean setTicksBetweenShots(int delay) { From 9598b1c961ca31497bcb6915d05c7df09feb0b73 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 16:11:28 +0100 Subject: [PATCH 28/46] Add @LinkedTimer A method of a linked class annotated with @LinkedTimer(delay, period) is run repeatedly by the linker, on Spigot and Velocity. The method must not have parameters, its return value is ignored. It is called through a MethodHandle bound once when linking instead of reflection; LambdaMetafactory cannot be used as the linker and the linked classes are in different class loaders. The unit defaults to ticks and can be set to milliseconds up to hours; the Unit enum converts it to ticks for Spigot and to a TimeUnit for Velocity. HullHiderCommand uses it for its tick task. --- .../features/hullhider/HullHiderCommand.java | 4 +- .../de/steamwar/linkage/AbstractLinker.java | 52 ++++++++++++ .../src/de/steamwar/linkage/LinkedTimer.java | 81 +++++++++++++++++++ .../src/de/steamwar/linkage/SpigotLinker.java | 5 ++ .../steamwar/velocitycore/VelocityCore.java | 6 ++ 5 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 CommonCore/Linkage/src/de/steamwar/linkage/LinkedTimer.java 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 index b5c2a3cf..2b2a99aa 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullHiderCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullHiderCommand.java @@ -32,6 +32,7 @@ import de.steamwar.hullhider.HullRegion; import de.steamwar.hullhider.Viewers; import de.steamwar.linkage.Linked; import de.steamwar.linkage.LinkedInstance; +import de.steamwar.linkage.LinkedTimer; import de.steamwar.techhider.ObfuscationConfig; import net.md_5.bungee.api.ChatMessageType; import org.bukkit.Bukkit; @@ -55,8 +56,6 @@ public class HullHiderCommand extends SWCommand implements Listener { public HullHiderCommand() { super("hullhider"); - - Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), this::onTick, 1, 1); } @Linked @@ -140,6 +139,7 @@ public class HullHiderCommand extends SWCommand implements Listener { }); } + @LinkedTimer(delay = 1, period = 1) private void onTick() { if (hidden.isEmpty()) return; diff --git a/CommonCore/Linkage/src/de/steamwar/linkage/AbstractLinker.java b/CommonCore/Linkage/src/de/steamwar/linkage/AbstractLinker.java index 00d4b43a..9959ecef 100644 --- a/CommonCore/Linkage/src/de/steamwar/linkage/AbstractLinker.java +++ b/CommonCore/Linkage/src/de/steamwar/linkage/AbstractLinker.java @@ -22,11 +22,16 @@ package de.steamwar.linkage; import de.steamwar.linkage.api.Disable; import de.steamwar.linkage.api.Enable; import lombok.NonNull; +import lombok.SneakyThrows; import lombok.experimental.StandardException; import java.io.BufferedReader; import java.io.InputStreamReader; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashMap; @@ -120,6 +125,45 @@ public abstract class AbstractLinker { Throwable cause = e.getCause(); throw new LinkException(cause.getMessage(), cause); } + + // After the injection, as timers may run immediately on another thread + for (Object any : instances.values()) { + scheduleTimers(any); + } + for (Object any : fieldInstances) { + scheduleTimers(any); + } + } + + private void scheduleTimers(Object any) throws LinkException { + for (Method method : any.getClass().getDeclaredMethods()) { + LinkedTimer timer = method.getAnnotation(LinkedTimer.class); + if (timer == null) continue; + + String name = any.getClass().getName() + "." + method.getName(); + if (method.getParameterCount() != 0) { + throw new LinkException("Linked timer " + name + " must not have parameters"); + } + + MethodHandle handle; + try { + handle = MethodHandles.privateLookupIn(method.getDeclaringClass(), MethodHandles.lookup()).unreflect(method); + } catch (IllegalAccessException e) { + throw new LinkException("Could not access linked timer " + name, e); + } + if (!Modifier.isStatic(method.getModifiers())) { + handle = handle.bindTo(any); + } + // Discards the return value + MethodHandle timerHandle = handle.asType(MethodType.methodType(void.class)); + + scheduleTimer(() -> runTimer(timerHandle), timer.delay(), timer.period(), timer.unit()); + } + } + + @SneakyThrows + private static void runTimer(MethodHandle timer) { + timer.invokeExact(); } private void linkFields(Object any) { @@ -186,6 +230,14 @@ public abstract class AbstractLinker { protected void linkObject(Object any) { } + /** + * Runs the runnable repeatedly for a {@link LinkedTimer}. + * + * @param delay time until the first run, in unit + * @param period time between two runs, in unit + */ + protected abstract void scheduleTimer(Runnable runnable, long delay, long period, LinkedTimer.Unit unit); + /** * There is no need in calling {@link Disable#disable()} ()} by this method. */ diff --git a/CommonCore/Linkage/src/de/steamwar/linkage/LinkedTimer.java b/CommonCore/Linkage/src/de/steamwar/linkage/LinkedTimer.java new file mode 100644 index 00000000..fdf96607 --- /dev/null +++ b/CommonCore/Linkage/src/de/steamwar/linkage/LinkedTimer.java @@ -0,0 +1,81 @@ +/* + * 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.linkage; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.concurrent.TimeUnit; + +/** + * Runs the annotated method of a linked class (or of the value of a {@link Linked} field) repeatedly. + * The method must not have parameters, its return value is ignored. It may be static. + * The timer is not stopped on unlink, as the server stops anyway. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface LinkedTimer { + + /** + * Time until the first run, in {@link #unit()}. + */ + long delay(); + + /** + * Time between two runs, in {@link #unit()}. + */ + long period(); + + Unit unit() default Unit.TICKS; + + @AllArgsConstructor + enum Unit { + /** + * 1 tick = 50 ms + */ + TICKS(50, TimeUnit.MILLISECONDS), + MILLISECONDS(1, TimeUnit.MILLISECONDS), + SECONDS(1, TimeUnit.SECONDS), + MINUTES(1, TimeUnit.MINUTES), + HOURS(1, TimeUnit.HOURS); + + private final long factor; + @Getter + private final TimeUnit timeUnit; + + /** + * @return the value in ticks, rounded down + */ + public long toTicks(long value) { + return timeUnit.toMillis(value * factor) / 50; + } + + /** + * @return the value in {@link #getTimeUnit()} + */ + public long toTimeUnit(long value) { + return value * factor; + } + } +} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/linkage/SpigotLinker.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/linkage/SpigotLinker.java index 37ccaae3..36c0532f 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/linkage/SpigotLinker.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/linkage/SpigotLinker.java @@ -61,6 +61,11 @@ public class SpigotLinker extends AbstractLinker { } } + @Override + protected void scheduleTimer(Runnable runnable, long delay, long period, LinkedTimer.Unit unit) { + Bukkit.getScheduler().runTaskTimer(plugin, runnable, unit.toTicks(delay), unit.toTicks(period)); + } + @Override protected void unlinkObject(Object any) { if (any instanceof Listener) { diff --git a/VelocityCore/src/de/steamwar/velocitycore/VelocityCore.java b/VelocityCore/src/de/steamwar/velocitycore/VelocityCore.java index 1701a10d..d94e8e3d 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/VelocityCore.java +++ b/VelocityCore/src/de/steamwar/velocitycore/VelocityCore.java @@ -32,6 +32,7 @@ import de.steamwar.command.*; import de.steamwar.linkage.AbstractLinker; import de.steamwar.linkage.EventMode; import de.steamwar.linkage.LinkedInstance; +import de.steamwar.linkage.LinkedTimer; import de.steamwar.messages.Chatter; import de.steamwar.network.packets.PacketHandler; import de.steamwar.persistent.ReloadablePlugin; @@ -164,6 +165,11 @@ public class VelocityCore implements ReloadablePlugin { } } + @Override + protected void scheduleTimer(Runnable runnable, long delay, long period, LinkedTimer.Unit unit) { + schedule(runnable).delay(unit.toTimeUnit(delay), unit.getTimeUnit()).repeat(unit.toTimeUnit(period), unit.getTimeUnit()).schedule(); + } + @Override protected void unlinkObject(Object any) { if (any instanceof PacketHandler) { From 9fd2bd17d4b6cfb2237dc3a5edd1907e4bfa4ab0 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 16:25:20 +0100 Subject: [PATCH 29/46] Use @LinkedTimer for the timers of linked classes The repeating timers that linked classes and the plugin main classes started once for the whole runtime now use @LinkedTimer, on Spigot (BauSystem, SpigotCore, LobbySystem, MissileWars, TowerRun, Teamserver) and Velocity. The FightInfoPacketSenders of MissileWars and TowerRun are linked themselves instead of being started by the main class. --- .../observer/ObserverTracerListener.java | 14 ++-- .../rayvisualizer/RayVisualizerCommand.java | 18 ++-- .../features/script/event/EventListener.java | 14 ++-- .../features/simulator/SimulatorCursor.java | 11 ++- .../team/boundary/BoundaryViewer.java | 24 +++--- .../features/world/AFKStopperListener.java | 20 ++--- .../steamwar/lobby/jumpandrun/JumpAndRun.java | 84 +++++++++---------- .../lobby/listener/PlayerSeatListener.java | 17 ++-- .../lobby/particle/ParticleListener.java | 39 +++++---- .../de/steamwar/lobby/team/TeamPlayer.java | 84 ++++++++++--------- .../misslewars/FightInfoPacketSender.java | 4 + .../de/steamwar/misslewars/MissileWars.java | 2 - .../src/de/steamwar/core/Core.java | 20 ++++- .../core/worldedit/WorldEditRenderer.java | 12 +-- .../de/steamwar/cursor/CursorListener.java | 14 ++-- .../teamserver/listener/FreezeListener.java | 14 ++-- .../towerrun/FightInfoPacketSender.java | 4 + .../src/de/steamwar/towerrun/TowerRun.java | 2 - .../steamwar/velocitycore/EventStarter.java | 4 +- .../steamwar/velocitycore/VelocityCore.java | 19 +++-- .../velocitycore/commands/CheckCommand.java | 9 +- .../velocitycore/mods/FabricModSender.java | 25 +++--- .../velocitycore/tablist/TablistManager.java | 4 +- 23 files changed, 241 insertions(+), 217 deletions(-) diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/observer/ObserverTracerListener.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/observer/ObserverTracerListener.java index d823bff4..2cf8e639 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/observer/ObserverTracerListener.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/observer/ObserverTracerListener.java @@ -24,6 +24,7 @@ import de.steamwar.bausystem.Permission; import de.steamwar.bausystem.utils.BauMemberUpdateEvent; import de.steamwar.core.SWPlayer; import de.steamwar.linkage.Linked; +import de.steamwar.linkage.LinkedTimer; import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Material; @@ -43,13 +44,12 @@ public class ObserverTracerListener implements Listener { static Set enabled = new HashSet<>(); - public ObserverTracerListener() { - Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> { - SWPlayer.allWithSingleComponent(ObserverTracer.class).forEach(pair -> { - if (pair.getSwPlayer().getPlayer().getGameMode() != GameMode.SPECTATOR) return; - pair.getComponent().show(); - }); - }, 15L, 15L); + @LinkedTimer(delay = 15, period = 15) + private void showTracers() { + SWPlayer.allWithSingleComponent(ObserverTracer.class).forEach(pair -> { + if (pair.getSwPlayer().getPlayer().getGameMode() != GameMode.SPECTATOR) return; + pair.getComponent().show(); + }); } @EventHandler diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/rayvisualizer/RayVisualizerCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/rayvisualizer/RayVisualizerCommand.java index af5d4eb0..469db717 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/rayvisualizer/RayVisualizerCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/rayvisualizer/RayVisualizerCommand.java @@ -28,6 +28,7 @@ import de.steamwar.command.SWCommand; import de.steamwar.entity.CRay; import de.steamwar.entity.REntityServer; import de.steamwar.linkage.Linked; +import de.steamwar.linkage.LinkedTimer; import org.bukkit.*; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; @@ -104,15 +105,16 @@ public class RayVisualizerCommand extends SWCommand implements Listener { public RayVisualizerCommand() { super("rayvisualizer"); + } - Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> { - if (server.getPlayers().isEmpty()) return; - List primedList = WORLD.getEntitiesByClass(TNTPrimed.class) - .stream().toList(); - rayData.reset(); - if (primedList.isEmpty()) return; - calculateRays(primedList, primedList); - }, 1, 1); + @LinkedTimer(delay = 1, period = 1) + private void updateRays() { + if (server.getPlayers().isEmpty()) return; + List primedList = WORLD.getEntitiesByClass(TNTPrimed.class) + .stream().toList(); + rayData.reset(); + if (primedList.isEmpty()) return; + calculateRays(primedList, primedList); } private void calculateRays(List fromTNTs, List toTNTs) { diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/script/event/EventListener.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/script/event/EventListener.java index 6265cec4..995583b3 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/script/event/EventListener.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/script/event/EventListener.java @@ -27,6 +27,7 @@ import de.steamwar.bausystem.features.script.lua.libs.StorageLib; import de.steamwar.bausystem.region.Region; import de.steamwar.core.SWPlayer; import de.steamwar.linkage.Linked; +import de.steamwar.linkage.LinkedTimer; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.entity.EntityType; @@ -47,13 +48,12 @@ import org.luaj.vm2.LuaValue; @Linked public class EventListener implements Listener { - static { - Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> { - long millis = System.currentTimeMillis(); - SWPlayer.allWithSingleComponent(ScriptRunner.ScriptData.class) - .filter(pair -> millis - pair.getComponent().getLastF() > 200) - .forEach(pair -> pair.getComponent().setLastF(Long.MAX_VALUE)); - }, 1, 1); + @LinkedTimer(delay = 1, period = 1) + private static void resetLastF() { + long millis = System.currentTimeMillis(); + SWPlayer.allWithSingleComponent(ScriptRunner.ScriptData.class) + .filter(pair -> millis - pair.getComponent().getLastF() > 200) + .forEach(pair -> pair.getComponent().setLastF(Long.MAX_VALUE)); } @EventHandler(priority = EventPriority.MONITOR) diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCursor.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCursor.java index 6f674a71..271316f8 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCursor.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCursor.java @@ -44,9 +44,9 @@ import de.steamwar.entity.REntity; import de.steamwar.entity.REntityServer; import de.steamwar.inventory.SWAnvilInv; import de.steamwar.linkage.Linked; +import de.steamwar.linkage.LinkedTimer; import lombok.AllArgsConstructor; import lombok.Getter; -import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.Player; @@ -136,11 +136,10 @@ public class SimulatorCursor implements Listener { private static final Map LAST_SNEAKS = new HashMap<>(); - static { - Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> { - long millis = System.currentTimeMillis(); - LAST_SNEAKS.entrySet().removeIf(entry -> millis - entry.getValue() > 200); - }, 1, 1); + @LinkedTimer(delay = 1, period = 1) + private static void clearLastSneaks() { + long millis = System.currentTimeMillis(); + LAST_SNEAKS.entrySet().removeIf(entry -> millis - entry.getValue() > 200); } @EventHandler(priority = EventPriority.HIGH) diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/team/boundary/BoundaryViewer.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/team/boundary/BoundaryViewer.java index fe242037..139155ef 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/team/boundary/BoundaryViewer.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/team/boundary/BoundaryViewer.java @@ -19,11 +19,10 @@ package de.steamwar.bausystem.features.team.boundary; -import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.region.Point; import de.steamwar.bausystem.region.Region; import de.steamwar.linkage.Linked; -import org.bukkit.Bukkit; +import de.steamwar.linkage.LinkedTimer; import org.bukkit.Particle; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -38,17 +37,16 @@ public class BoundaryViewer implements Listener { public static Set viewers = new HashSet<>(); - { - Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> { - if (viewers.isEmpty()) { - return; - } - viewers.forEach(player -> { - Region region = Region.getRegion(player.getLocation()); - if (region.getType().isGlobal()) return; - showRegion(region, player); - }); - }, 20, 20); + @LinkedTimer(delay = 20, period = 20) + private void showBoundaries() { + if (viewers.isEmpty()) { + return; + } + viewers.forEach(player -> { + Region region = Region.getRegion(player.getLocation()); + if (region.getType().isGlobal()) return; + showRegion(region, player); + }); } private void showRegion(Region region, Player player) { diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AFKStopperListener.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AFKStopperListener.java index 5c061545..097ef2bc 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AFKStopperListener.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/AFKStopperListener.java @@ -23,6 +23,7 @@ import de.steamwar.bausystem.BauSystem; import de.steamwar.core.CRIUWakeupEvent; import de.steamwar.core.CheckpointUtils; import de.steamwar.linkage.Linked; +import de.steamwar.linkage.LinkedTimer; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Player; @@ -38,17 +39,16 @@ public class AFKStopperListener implements Listener { private long lastMovementTime = System.currentTimeMillis(); - public AFKStopperListener() { - Bukkit.getScheduler().runTaskTimer(BauSystem.getInstance(), () -> { - long currentTime = System.currentTimeMillis(); - if (currentTime - lastMovementTime > 10 * 60000) { // 10 Minutes - for (Player p : Bukkit.getOnlinePlayers()) { - p.kickPlayer(BauSystem.MESSAGE.parse("AFK_KICK_MESSAGE", p)); - } - } else if (currentTime - lastMovementTime > 9 * 60000) { - BauSystem.MESSAGE.broadcast("AFK_WARNING_MESSAGE"); + @LinkedTimer(delay = 1, period = 1, unit = LinkedTimer.Unit.MINUTES) + private void checkAfk() { + long currentTime = System.currentTimeMillis(); + if (currentTime - lastMovementTime > 10 * 60000) { // 10 Minutes + for (Player p : Bukkit.getOnlinePlayers()) { + p.kickPlayer(BauSystem.MESSAGE.parse("AFK_KICK_MESSAGE", p)); } - }, 1200, 1200); //every minute + } else if (currentTime - lastMovementTime > 9 * 60000) { + BauSystem.MESSAGE.broadcast("AFK_WARNING_MESSAGE"); + } } @EventHandler diff --git a/LobbySystem/src/de/steamwar/lobby/jumpandrun/JumpAndRun.java b/LobbySystem/src/de/steamwar/lobby/jumpandrun/JumpAndRun.java index 87b29fe5..4f129be5 100644 --- a/LobbySystem/src/de/steamwar/lobby/jumpandrun/JumpAndRun.java +++ b/LobbySystem/src/de/steamwar/lobby/jumpandrun/JumpAndRun.java @@ -20,6 +20,7 @@ package de.steamwar.lobby.jumpandrun; import de.steamwar.linkage.Linked; +import de.steamwar.linkage.LinkedTimer; import de.steamwar.lobby.LobbySystem; import de.steamwar.lobby.listener.PlayerSpawn; import de.steamwar.lobby.util.LeaderboardManager; @@ -60,52 +61,51 @@ public class JumpAndRun implements Listener { private static final LeaderboardManager LEADERBOARD = new LeaderboardManager(LobbySystem.getEntityServer(false), JUMP_AND_RUN_CONFIG, new Location(Bukkit.getWorlds().get(0), 2338.5, 42.5, 1231.5)); - { - Bukkit.getScheduler().runTaskTimer(LobbySystem.getInstance(), () -> { - Set toReset = new HashSet<>(); - CURRENT_POS.forEach((player, index) -> { - if (System.currentTimeMillis() - CLICKED.getOrDefault(player, 0L) > 500) { - CLICKED.remove(player); - CLICKED_COUNT.remove(player); - } else { - CLICKED_COUNT.put(player, CLICKED_COUNT.getOrDefault(player, -1) + 1); - } + @LinkedTimer(delay = 1, period = 1) + private void updatePlayers() { + Set toReset = new HashSet<>(); + CURRENT_POS.forEach((player, index) -> { + if (System.currentTimeMillis() - CLICKED.getOrDefault(player, 0L) > 500) { + CLICKED.remove(player); + CLICKED_COUNT.remove(player); + } else { + CLICKED_COUNT.put(player, CLICKED_COUNT.getOrDefault(player, -1) + 1); + } - Location location = player.getLocation(); - Vector point = points.get(index); - if (index < points.size() - 1) { - Vector nextPoint = points.get(index + 1); - double y = Math.min(point.getY(), nextPoint.getY()) - 2; - if (location.getY() < y) { - location.setX(point.getX()); - location.setY(point.getY()); - location.setZ(point.getZ()); - player.teleport(location); - player.playSound(location, Sound.ENTITY_ENDERMAN_TELEPORT, 1, 1); - FAILS.put(player, FAILS.getOrDefault(player, 0) + 1); - } + Location location = player.getLocation(); + Vector point = points.get(index); + if (index < points.size() - 1) { + Vector nextPoint = points.get(index + 1); + double y = Math.min(point.getY(), nextPoint.getY()) - 2; + if (location.getY() < y) { + location.setX(point.getX()); + location.setY(point.getY()); + location.setZ(point.getZ()); + player.teleport(location); + player.playSound(location, Sound.ENTITY_ENDERMAN_TELEPORT, 1, 1); + FAILS.put(player, FAILS.getOrDefault(player, 0) + 1); } + } - int count = CLICKED_COUNT.getOrDefault(player, -1); - if (count >= 0) { - if (count > 20) { - toReset.add(player); - return; - } - count = Math.min(count / 2, 30); - LobbySystem.getMessage().sendPrefixless("JUMP_AND_RUN_CANCEL", player, ChatMessageType.ACTION_BAR, "§e" + BAR_EMPTY.substring(0, count) + "§7" + BAR_EMPTY.substring(count)); - } else { - long time = System.currentTimeMillis() - START.get(player); - SimpleDateFormat format = new SimpleDateFormat(LobbySystem.getMessage().parse("JUMP_AND_RUN_TIME", player), Locale.ROOT); - String parsed = format.format(new Date(time)); - LobbySystem.getMessage().sendPrefixless("JUMP_AND_RUN_PROGRESS", player, ChatMessageType.ACTION_BAR, index + 1, points.size(), FAILS.get(player), parsed); + int count = CLICKED_COUNT.getOrDefault(player, -1); + if (count >= 0) { + if (count > 20) { + toReset.add(player); + return; } - }); - toReset.forEach(player -> { - reset(player); - player.teleport(Bukkit.getWorlds().get(0).getSpawnLocation().clone().add(0.5, 0, 0.5)); - }); - }, 1, 1); + count = Math.min(count / 2, 30); + LobbySystem.getMessage().sendPrefixless("JUMP_AND_RUN_CANCEL", player, ChatMessageType.ACTION_BAR, "§e" + BAR_EMPTY.substring(0, count) + "§7" + BAR_EMPTY.substring(count)); + } else { + long time = System.currentTimeMillis() - START.get(player); + SimpleDateFormat format = new SimpleDateFormat(LobbySystem.getMessage().parse("JUMP_AND_RUN_TIME", player), Locale.ROOT); + String parsed = format.format(new Date(time)); + LobbySystem.getMessage().sendPrefixless("JUMP_AND_RUN_PROGRESS", player, ChatMessageType.ACTION_BAR, index + 1, points.size(), FAILS.get(player), parsed); + } + }); + toReset.forEach(player -> { + reset(player); + player.teleport(Bukkit.getWorlds().get(0).getSpawnLocation().clone().add(0.5, 0, 0.5)); + }); } public static boolean isPlayerInJumpAndRun(Player player) { diff --git a/LobbySystem/src/de/steamwar/lobby/listener/PlayerSeatListener.java b/LobbySystem/src/de/steamwar/lobby/listener/PlayerSeatListener.java index 3e452b30..0b66cc8c 100644 --- a/LobbySystem/src/de/steamwar/lobby/listener/PlayerSeatListener.java +++ b/LobbySystem/src/de/steamwar/lobby/listener/PlayerSeatListener.java @@ -20,7 +20,7 @@ package de.steamwar.lobby.listener; import de.steamwar.linkage.Linked; -import de.steamwar.lobby.LobbySystem; +import de.steamwar.linkage.LinkedTimer; import org.bukkit.*; import org.bukkit.block.data.Bisected; import org.bukkit.block.data.type.Stairs; @@ -34,7 +34,6 @@ import org.bukkit.event.block.Action; import org.bukkit.event.entity.EntityDismountEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.scheduler.BukkitRunnable; import java.util.HashSet; import java.util.Set; @@ -46,15 +45,11 @@ public class PlayerSeatListener implements Listener { private Set seats = new HashSet<>(); - static { - new BukkitRunnable() { - @Override - public void run() { - for (Arrow arrow : world.getEntitiesByClass(Arrow.class)) { - arrow.setTicksLived(1); - } - } - }.runTaskTimer(LobbySystem.getInstance(), 20 * 60, 20 * 60); + @LinkedTimer(delay = 1, period = 1, unit = LinkedTimer.Unit.MINUTES) + private static void keepArrows() { + for (Arrow arrow : world.getEntitiesByClass(Arrow.class)) { + arrow.setTicksLived(1); + } } @EventHandler diff --git a/LobbySystem/src/de/steamwar/lobby/particle/ParticleListener.java b/LobbySystem/src/de/steamwar/lobby/particle/ParticleListener.java index 52261647..1e1eed30 100644 --- a/LobbySystem/src/de/steamwar/lobby/particle/ParticleListener.java +++ b/LobbySystem/src/de/steamwar/lobby/particle/ParticleListener.java @@ -20,7 +20,7 @@ package de.steamwar.lobby.particle; import de.steamwar.linkage.Linked; -import de.steamwar.lobby.LobbySystem; +import de.steamwar.linkage.LinkedTimer; import de.steamwar.lobby.jumpandrun.JumpAndRun; import de.steamwar.lobby.listener.PlayerSpawn; import de.steamwar.lobby.util.LobbyPlayer; @@ -49,25 +49,24 @@ public class ParticleListener implements Listener { private static Map movingPlayers = new HashMap<>(); - public ParticleListener() { - Bukkit.getScheduler().runTaskTimer(LobbySystem.getInstance(), () -> { - movingPlayers.replaceAll((player, integer) -> integer - 1); - movingPlayers.entrySet().removeIf(entry -> entry.getValue() <= 0); - deg += 0.1; - if (deg > 360) deg = 0; - Bukkit.getOnlinePlayers().forEach(player -> { - if (disabled(player)) return; - if (JumpAndRun.isPlayerInJumpAndRun(player)) return; - LobbyPlayer lobbyPlayer = LobbyPlayer.getLobbyPlayer(player.getUniqueId()); - ParticleEnum particle = lobbyPlayer.getParticle(); - if (particle == null) return; - ParticleData particleData = particle.getParticle(); - ParticleElement particleElement = particleData.getParticleElement(); - if (particleElement.tickType() == ParticleTickType.ALWAYS) { - particleElement.tick(new ParticleTickData(player.getWorld(), player, deg, movingPlayers.containsKey(player))); - } - }); - }, 0, 1); + @LinkedTimer(delay = 0, period = 1) + private void tickParticles() { + movingPlayers.replaceAll((player, integer) -> integer - 1); + movingPlayers.entrySet().removeIf(entry -> entry.getValue() <= 0); + deg += 0.1; + if (deg > 360) deg = 0; + Bukkit.getOnlinePlayers().forEach(player -> { + if (disabled(player)) return; + if (JumpAndRun.isPlayerInJumpAndRun(player)) return; + LobbyPlayer lobbyPlayer = LobbyPlayer.getLobbyPlayer(player.getUniqueId()); + ParticleEnum particle = lobbyPlayer.getParticle(); + if (particle == null) return; + ParticleData particleData = particle.getParticle(); + ParticleElement particleElement = particleData.getParticleElement(); + if (particleElement.tickType() == ParticleTickType.ALWAYS) { + particleElement.tick(new ParticleTickData(player.getWorld(), player, deg, movingPlayers.containsKey(player))); + } + }); } @EventHandler(priority = EventPriority.NORMAL) diff --git a/LobbySystem/src/de/steamwar/lobby/team/TeamPlayer.java b/LobbySystem/src/de/steamwar/lobby/team/TeamPlayer.java index 8a294914..987fa29f 100644 --- a/LobbySystem/src/de/steamwar/lobby/team/TeamPlayer.java +++ b/LobbySystem/src/de/steamwar/lobby/team/TeamPlayer.java @@ -20,6 +20,7 @@ package de.steamwar.lobby.team; import de.steamwar.linkage.Linked; +import de.steamwar.linkage.LinkedTimer; import de.steamwar.lobby.LobbySystem; import de.steamwar.lobby.display.NPC; import de.steamwar.sql.SteamwarUser; @@ -76,55 +77,58 @@ public class TeamPlayer implements Listener { entities.put(name, npc); } + private final AtomicInteger count = new AtomicInteger(); + { SteamwarUser.getServerTeam().forEach(user -> { spawnTeamPlayer(world, user); }); - AtomicInteger count = new AtomicInteger(); LobbySystem.getInstance().getLogger().log(Level.INFO, "Loaded " + entities.size() + " team players"); - Bukkit.getScheduler().runTaskTimer(LobbySystem.getInstance(), () -> { - Collection active = world.getEntitiesByClass(Villager.class); - if (active.size() > entities.size()) { - Set duplicates = new HashSet<>(); - active.stream() - .filter(villager -> !duplicates.add(villager.getCustomName())) - .forEach(Entity::remove); - } + } - count.incrementAndGet(); - if (count.get() % (20 * 60 * 60) == 0) { - count.set(0); - List steamwarUsers = SteamwarUser.getServerTeam(); - AtomicInteger added = new AtomicInteger(); - steamwarUsers.forEach(user -> { - if (!entities.containsKey(user.getUserName())) { - spawnTeamPlayer(world, user); - added.incrementAndGet(); - } - }); - AtomicInteger removed = new AtomicInteger(); - List names = steamwarUsers.stream().map(SteamwarUser::getUserName).collect(Collectors.toList()); - new HashSet<>(entities.keySet()).stream().filter(name -> !names.contains(name)).forEach(name -> { - villagers.remove(name).remove(); - entities.remove(name).delete(); - removed.incrementAndGet(); - }); - if (added.get() > 0 || removed.get() > 0) { - LobbySystem.getInstance().getLogger().log(Level.INFO, "Loaded " + added.get() + " team players, removed " + removed.get() + " team players"); - } - } - world.getEntitiesByClasses(Villager.class).forEach(entity -> { - NPC npc = entities.get(entity.getName()); - if (npc != null) { - if (illegalLocation(entity.getLocation())) { - entity.teleport(npc.getLocation()); - return; - } - npc.setLocation(entity.getLocation()); + @LinkedTimer(delay = 1, period = 1) + private void updateTeamPlayers() { + Collection active = world.getEntitiesByClass(Villager.class); + if (active.size() > entities.size()) { + Set duplicates = new HashSet<>(); + active.stream() + .filter(villager -> !duplicates.add(villager.getCustomName())) + .forEach(Entity::remove); + } + + count.incrementAndGet(); + if (count.get() % (20 * 60 * 60) == 0) { + count.set(0); + List steamwarUsers = SteamwarUser.getServerTeam(); + AtomicInteger added = new AtomicInteger(); + steamwarUsers.forEach(user -> { + if (!entities.containsKey(user.getUserName())) { + spawnTeamPlayer(world, user); + added.incrementAndGet(); } }); - }, 1L, 1L); + AtomicInteger removed = new AtomicInteger(); + List names = steamwarUsers.stream().map(SteamwarUser::getUserName).collect(Collectors.toList()); + new HashSet<>(entities.keySet()).stream().filter(name -> !names.contains(name)).forEach(name -> { + villagers.remove(name).remove(); + entities.remove(name).delete(); + removed.incrementAndGet(); + }); + if (added.get() > 0 || removed.get() > 0) { + LobbySystem.getInstance().getLogger().log(Level.INFO, "Loaded " + added.get() + " team players, removed " + removed.get() + " team players"); + } + } + world.getEntitiesByClasses(Villager.class).forEach(entity -> { + NPC npc = entities.get(entity.getName()); + if (npc != null) { + if (illegalLocation(entity.getLocation())) { + entity.teleport(npc.getLocation()); + return; + } + npc.setLocation(entity.getLocation()); + } + }); } private boolean illegalLocation(Location location) { diff --git a/MissileWars/src/de/steamwar/misslewars/FightInfoPacketSender.java b/MissileWars/src/de/steamwar/misslewars/FightInfoPacketSender.java index a2bcd52e..a4f80b21 100644 --- a/MissileWars/src/de/steamwar/misslewars/FightInfoPacketSender.java +++ b/MissileWars/src/de/steamwar/misslewars/FightInfoPacketSender.java @@ -19,6 +19,8 @@ package de.steamwar.misslewars; +import de.steamwar.linkage.Linked; +import de.steamwar.linkage.LinkedTimer; import de.steamwar.network.NetworkSender; import de.steamwar.network.packets.common.FightInfoPacket; import de.steamwar.sql.SteamwarUser; @@ -28,6 +30,7 @@ import org.bukkit.World; import java.util.List; import java.util.stream.Collectors; +@Linked public class FightInfoPacketSender implements Runnable { private final World world = Bukkit.getWorlds().get(0); @@ -41,6 +44,7 @@ public class FightInfoPacketSender implements Runnable { private final int redLeader = Config.RedLeader != null ? SteamwarUser.get(Config.RedLeader).getId() : 0; @Override + @LinkedTimer(delay = 1, period = 1, unit = LinkedTimer.Unit.SECONDS) public void run() { if (Config.test()) { return; diff --git a/MissileWars/src/de/steamwar/misslewars/MissileWars.java b/MissileWars/src/de/steamwar/misslewars/MissileWars.java index 7734cc74..6323e850 100644 --- a/MissileWars/src/de/steamwar/misslewars/MissileWars.java +++ b/MissileWars/src/de/steamwar/misslewars/MissileWars.java @@ -75,8 +75,6 @@ public class MissileWars extends JavaPlugin { StateDependent.setupState(fightState); - Bukkit.getScheduler().runTaskTimer(this, new FightInfoPacketSender(), 20, 20); - Bukkit.getWorlds().forEach(world -> { world.setGameRule(GameRule.KEEP_INVENTORY, true); world.setGameRule(GameRule.MOB_GRIEFING, true); diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/Core.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/Core.java index 8f076c0a..f14144e0 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/Core.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/Core.java @@ -26,6 +26,7 @@ import de.steamwar.command.TabCompletionCache; import de.steamwar.command.TypeMapper; import de.steamwar.core.authlib.SteamwarGameProfileRepository; import de.steamwar.linkage.AbstractLinker; +import de.steamwar.linkage.LinkedTimer; import de.steamwar.linkage.SpigotLinker; import de.steamwar.message.Message; import de.steamwar.network.NetworkReceiver; @@ -119,10 +120,6 @@ public class Core extends JavaPlugin { TinyProtocol.init(); CheckpointUtils.signalHandler(); - Bukkit.getScheduler().runTaskTimer(this, TabCompletionCache::invalidateOldEntries, 20, 20); - Bukkit.getScheduler().runTaskTimer(Core.getInstance(), SteamwarUser::clear, 72000, 72000); - Bukkit.getScheduler().runTaskTimer(Core.getInstance(), SchematicNode::clear, 20L * 30, 20L * 30); - try { getLogger().log(Level.INFO, "Running on: " + new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec("hostname").getInputStream())).readLine()); } catch (IOException e) { @@ -130,6 +127,21 @@ public class Core extends JavaPlugin { } } + @LinkedTimer(delay = 1, period = 1, unit = LinkedTimer.Unit.SECONDS) + private void invalidateTabCompletions() { + TabCompletionCache.invalidateOldEntries(); + } + + @LinkedTimer(delay = 1, period = 1, unit = LinkedTimer.Unit.HOURS) + private void clearUserCache() { + SteamwarUser.clear(); + } + + @LinkedTimer(delay = 30, period = 30, unit = LinkedTimer.Unit.SECONDS) + private void clearSchematicCache() { + SchematicNode.clear(); + } + @Override public void onDisable() { TinyProtocol.instance.close(); diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/worldedit/WorldEditRenderer.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/worldedit/WorldEditRenderer.java index f11aec11..964890e7 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/worldedit/WorldEditRenderer.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/worldedit/WorldEditRenderer.java @@ -30,6 +30,7 @@ import com.sk89q.worldedit.regions.RegionSelector; import com.sk89q.worldedit.world.World; import de.steamwar.core.Core; import de.steamwar.linkage.Linked; +import de.steamwar.linkage.LinkedTimer; import de.steamwar.linkage.PluginCheck; import org.bukkit.Bukkit; import org.bukkit.Material; @@ -55,12 +56,13 @@ public class WorldEditRenderer implements Listener { public WorldEditRenderer() { INSTANCE = this; we = WorldEditWrapper.getWorldEditPlugin(); + } - Bukkit.getScheduler().runTaskTimer(Core.getInstance(), () -> { - for (Player player : Bukkit.getOnlinePlayers()) { - renderPlayer(player, true); - } - }, 20, 20); + @LinkedTimer(delay = 20, period = 20) + private void renderPlayers() { + for (Player player : Bukkit.getOnlinePlayers()) { + renderPlayer(player, true); + } } public static void renderPlayer(Player player) { diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/cursor/CursorListener.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/cursor/CursorListener.java index 99e286ca..0b919898 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/cursor/CursorListener.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/cursor/CursorListener.java @@ -1,13 +1,12 @@ package de.steamwar.cursor; import com.comphenix.tinyprotocol.TinyProtocol; -import de.steamwar.core.Core; import de.steamwar.core.SWPlayer; import de.steamwar.linkage.Linked; +import de.steamwar.linkage.LinkedTimer; import lombok.Getter; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -29,12 +28,13 @@ public class CursorListener implements Listener { TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.Pos.class, this::updateCursorFromPacket); TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.Rot.class, this::updateCursorFromPacket); TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.PosRot.class, this::updateCursorFromPacket); + } - Bukkit.getScheduler().runTaskTimer(Core.getInstance(), () -> { - SWPlayer.allWithSingleComponent(Cursor.class) - .map(SWPlayer.SWPlayerWithComponent::getComponent) - .forEach(Cursor::renderDeduplicated); - }, 1, 1); + @LinkedTimer(delay = 1, period = 1) + private void renderCursors() { + SWPlayer.allWithSingleComponent(Cursor.class) + .map(SWPlayer.SWPlayerWithComponent::getComponent) + .forEach(Cursor::renderDeduplicated); } public Packet updateCursorFromPacket(Player player, Packet packet) { diff --git a/Teamserver/src/de/steamwar/teamserver/listener/FreezeListener.java b/Teamserver/src/de/steamwar/teamserver/listener/FreezeListener.java index 12c279c7..c040942c 100644 --- a/Teamserver/src/de/steamwar/teamserver/listener/FreezeListener.java +++ b/Teamserver/src/de/steamwar/teamserver/listener/FreezeListener.java @@ -20,6 +20,7 @@ package de.steamwar.teamserver.listener; import de.steamwar.linkage.Linked; +import de.steamwar.linkage.LinkedTimer; import de.steamwar.teamserver.Builder; import net.md_5.bungee.api.ChatMessageType; import org.bukkit.Bukkit; @@ -41,13 +42,12 @@ public class FreezeListener implements Listener { public static boolean freeze = true; - public FreezeListener() { - Bukkit.getScheduler().runTaskTimer(Builder.getInstance(), () -> { - if (!freeze) return; - Bukkit.getOnlinePlayers().forEach(player -> { - Builder.MESSAGE.sendPrefixless("REGION_FREEZE_ENABLED", player, ChatMessageType.ACTION_BAR); - }); - }, 1, 1); + @LinkedTimer(delay = 1, period = 1) + private void showFreeze() { + if (!freeze) return; + Bukkit.getOnlinePlayers().forEach(player -> { + Builder.MESSAGE.sendPrefixless("REGION_FREEZE_ENABLED", player, ChatMessageType.ACTION_BAR); + }); } @EventHandler diff --git a/TowerRun/src/de/steamwar/towerrun/FightInfoPacketSender.java b/TowerRun/src/de/steamwar/towerrun/FightInfoPacketSender.java index 764e1eea..43c9d882 100644 --- a/TowerRun/src/de/steamwar/towerrun/FightInfoPacketSender.java +++ b/TowerRun/src/de/steamwar/towerrun/FightInfoPacketSender.java @@ -19,6 +19,8 @@ package de.steamwar.towerrun; +import de.steamwar.linkage.Linked; +import de.steamwar.linkage.LinkedTimer; import de.steamwar.network.NetworkSender; import de.steamwar.network.packets.common.FightInfoPacket; import de.steamwar.sql.SteamwarUser; @@ -33,6 +35,7 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +@Linked public class FightInfoPacketSender implements Runnable { private final World world = Bukkit.getWorlds().get(0); @@ -44,6 +47,7 @@ public class FightInfoPacketSender implements Runnable { private final String redName = "§cAlive"; @Override + @LinkedTimer(delay = 1, period = 1, unit = LinkedTimer.Unit.SECONDS) public void run() { if (Config.test()) { return; diff --git a/TowerRun/src/de/steamwar/towerrun/TowerRun.java b/TowerRun/src/de/steamwar/towerrun/TowerRun.java index 10298547..1fdc8ae3 100644 --- a/TowerRun/src/de/steamwar/towerrun/TowerRun.java +++ b/TowerRun/src/de/steamwar/towerrun/TowerRun.java @@ -71,7 +71,5 @@ public class TowerRun extends JavaPlugin { Bukkit.shutdown(); return; } - - Bukkit.getScheduler().runTaskTimer(this, new FightInfoPacketSender(), 20, 20); } } diff --git a/VelocityCore/src/de/steamwar/velocitycore/EventStarter.java b/VelocityCore/src/de/steamwar/velocitycore/EventStarter.java index 0c2c0aa8..35cf8a4d 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/EventStarter.java +++ b/VelocityCore/src/de/steamwar/velocitycore/EventStarter.java @@ -20,6 +20,7 @@ package de.steamwar.velocitycore; import de.steamwar.linkage.Linked; +import de.steamwar.linkage.LinkedTimer; import de.steamwar.messages.Chatter; import de.steamwar.messages.Message; import de.steamwar.persistent.Subserver; @@ -35,7 +36,6 @@ import java.util.HashSet; import java.util.Map; import java.util.Queue; import java.util.Set; -import java.util.concurrent.TimeUnit; import static de.steamwar.persistent.Storage.eventServer; @@ -58,13 +58,13 @@ public class EventStarter { public EventStarter() { EventFight.loadAllComingFights(); - VelocityCore.schedule(this::run).repeat(10, TimeUnit.SECONDS).schedule(); } public static Map getEventServer() { return eventServer; } + @LinkedTimer(delay = 0, period = 10, unit = LinkedTimer.Unit.SECONDS) private void run() { if (eventServer.entrySet().removeIf(entry -> Subserver.getSubserver(entry.getValue().getServer()) == null)) { EventFight.loadAllComingFights(); diff --git a/VelocityCore/src/de/steamwar/velocitycore/VelocityCore.java b/VelocityCore/src/de/steamwar/velocitycore/VelocityCore.java index d94e8e3d..936628f2 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/VelocityCore.java +++ b/VelocityCore/src/de/steamwar/velocitycore/VelocityCore.java @@ -133,7 +133,6 @@ public class VelocityCore implements ReloadablePlugin { return tabCompleter.apply(sender, s); } }); - schedule(TabCompletionCache::invalidateOldEntries).repeat(1, TimeUnit.SECONDS).schedule(); initStaticServers(); @@ -188,12 +187,6 @@ public class VelocityCore implements ReloadablePlugin { return; } - schedule(() -> { - SteamwarUser.clear(); - Team.clear(); - SteamwarPrefix.clearCache(); - }).repeat(1, TimeUnit.HOURS).schedule(); - DiscordConfig discordConfig = DiscordConfig.load(); if (discordConfig != null) { try { @@ -204,6 +197,18 @@ public class VelocityCore implements ReloadablePlugin { } } + @LinkedTimer(delay = 0, period = 1, unit = LinkedTimer.Unit.SECONDS) + private void invalidateTabCompletions() { + TabCompletionCache.invalidateOldEntries(); + } + + @LinkedTimer(delay = 0, period = 1, unit = LinkedTimer.Unit.HOURS) + private void clearCaches() { + SteamwarUser.clear(); + Team.clear(); + SteamwarPrefix.clearCache(); + } + @Subscribe @Override public void onProxyShutdown(ProxyShutdownEvent event) { diff --git a/VelocityCore/src/de/steamwar/velocitycore/commands/CheckCommand.java b/VelocityCore/src/de/steamwar/velocitycore/commands/CheckCommand.java index b130a2d7..5f87b699 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/commands/CheckCommand.java +++ b/VelocityCore/src/de/steamwar/velocitycore/commands/CheckCommand.java @@ -24,6 +24,7 @@ import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ServerConnection; import de.steamwar.command.SWCommand; import de.steamwar.linkage.Linked; +import de.steamwar.linkage.LinkedTimer; import de.steamwar.messages.Chatter; import de.steamwar.messages.Message; import de.steamwar.messages.PlayerChatter; @@ -41,10 +42,8 @@ import net.kyori.adventure.text.format.NamedTextColor; import java.awt.*; import java.sql.Timestamp; import java.time.Instant; -import java.time.format.DateTimeFormatter; import java.util.*; import java.util.List; -import java.util.concurrent.TimeUnit; import java.util.function.BooleanSupplier; import java.util.logging.Level; @@ -76,7 +75,11 @@ public class CheckCommand extends SWCommand { public CheckCommand() { super("check"); - VelocityCore.schedule(() -> Chatter.allStream().forEach(CheckCommand::sendReminder)).delay(10, TimeUnit.MINUTES).repeat(10, TimeUnit.MINUTES).schedule(); + } + + @LinkedTimer(delay = 10, period = 10, unit = LinkedTimer.Unit.MINUTES) + private static void sendReminders() { + Chatter.allStream().forEach(CheckCommand::sendReminder); } @Override diff --git a/VelocityCore/src/de/steamwar/velocitycore/mods/FabricModSender.java b/VelocityCore/src/de/steamwar/velocitycore/mods/FabricModSender.java index cf32fc53..e64107c5 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/mods/FabricModSender.java +++ b/VelocityCore/src/de/steamwar/velocitycore/mods/FabricModSender.java @@ -29,11 +29,11 @@ import com.velocitypowered.api.event.player.ServerConnectedEvent; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.proxy.protocol.ProtocolUtils; import de.steamwar.linkage.Linked; +import de.steamwar.linkage.LinkedTimer; import de.steamwar.persistent.Storage; import de.steamwar.sql.Mod; import de.steamwar.sql.SWException; import de.steamwar.sql.SteamwarUser; -import de.steamwar.velocitycore.VelocityCore; import de.steamwar.velocitycore.listeners.BasicListener; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; @@ -55,20 +55,21 @@ public class FabricModSender extends BasicListener { neededFabricMods.add("fabricloader"); neededQuiltMods.add("quilt_loader"); + } - VelocityCore.schedule(() -> { - synchronized (Storage.fabricExpectPluginMessage) { - for (Map.Entry entry : Storage.fabricExpectPluginMessage.entrySet()) { - if (!Storage.fabricCheckedPlayers.containsKey(entry.getKey())) { - continue; - } - if (System.currentTimeMillis() - entry.getValue() > TimeUnit.SECONDS.toMillis(20)) { - Storage.fabricExpectPluginMessage.remove(entry.getKey()); - return; - } + @LinkedTimer(delay = 0, period = 1, unit = LinkedTimer.Unit.SECONDS) + private void removeExpiredExpectations() { + synchronized (Storage.fabricExpectPluginMessage) { + for (Map.Entry entry : Storage.fabricExpectPluginMessage.entrySet()) { + if (!Storage.fabricCheckedPlayers.containsKey(entry.getKey())) { + continue; + } + if (System.currentTimeMillis() - entry.getValue() > TimeUnit.SECONDS.toMillis(20)) { + Storage.fabricExpectPluginMessage.remove(entry.getKey()); + return; } } - }).repeat(1, TimeUnit.SECONDS).schedule(); + } } public void handlePluginMessage(PluginMessageEvent e) { diff --git a/VelocityCore/src/de/steamwar/velocitycore/tablist/TablistManager.java b/VelocityCore/src/de/steamwar/velocitycore/tablist/TablistManager.java index 9631720d..057c9966 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/tablist/TablistManager.java +++ b/VelocityCore/src/de/steamwar/velocitycore/tablist/TablistManager.java @@ -27,6 +27,7 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.server.RegisteredServer; import de.steamwar.linkage.Linked; +import de.steamwar.linkage.LinkedTimer; import de.steamwar.linkage.api.Disable; import de.steamwar.network.packets.common.FightInfoPacket; import de.steamwar.persistent.Storage; @@ -38,7 +39,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.TimeUnit; @Linked public class TablistManager extends BasicListener implements Disable { @@ -63,7 +63,6 @@ public class TablistManager extends BasicListener implements Disable { private int seconds = 0; public TablistManager() { - VelocityCore.schedule(this::updateTablist).repeat(1, TimeUnit.SECONDS).schedule(); synchronized (tablists) { VelocityCore.getProxy().getAllPlayers().forEach(player -> tablists.computeIfAbsent(player, Tablist::new).onServerSwitch()); } @@ -102,6 +101,7 @@ public class TablistManager extends BasicListener implements Disable { } } + @LinkedTimer(delay = 0, period = 1, unit = LinkedTimer.Unit.SECONDS) private void updateTablist() { List subservers = new ArrayList<>(); for (RegisteredServer server : new ArrayList<>(VelocityCore.getProxy().getAllServers())) { From 5651455a5fd2a2ce423c1ceac51d34fe9e7f7d49 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 17:07:18 +0100 Subject: [PATCH 30/46] Stop hull rays from passing between occluding blocks A ray only moves diagonally if it could also reach the block by moving along the single axes of the direction one after another without passing an occluding block, so it no longer squeezes between blocks touching at an edge or corner. When a block stops occluding, the diagonal rays it blocked are continued, so incremental updates match a full initialisation. Double slabs now occlude as well, as slab materials do not; block updates pass the block data instead of the material for that. To keep the initialisation fast, a ray only checks a diagonal step after the cheaper checks, and the branch directions are computed once per hull. --- .../fightsystem/record/PacketProcessor.java | 4 +- .../fightsystem/utils/FightHullHider.java | 6 +- HiderCore/src/de/steamwar/hullhider/Hull.java | 130 +++++++++++++++--- .../src/de/steamwar/hullhider/HullHider.java | 7 +- 4 files changed, 119 insertions(+), 28 deletions(-) diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java index c0b6738f..c50eb60d 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java @@ -51,7 +51,7 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; import org.bukkit.*; import org.bukkit.craftbukkit.CraftWorld; -import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.craftbukkit.block.data.CraftBlockData; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; @@ -473,7 +473,7 @@ public class PacketProcessor implements Listener { level.removeBlockEntity(pos); level.setBlock(pos, blockData, blockState1); level.getChunkSource().blockChanged(pos); - FightSystem.getHullHider().blockUpdate(Config.world.getBlockAt(x, y, z), CraftMagicNumbers.getMaterial(Block.stateById(blockState)).getItemType()); + FightSystem.getHullHider().blockUpdate(Config.world.getBlockAt(x, y, z), CraftBlockData.fromData(Block.stateById(blockState))); }); } 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 d667ba45..8c7eee13 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightHullHider.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightHullHider.java @@ -33,8 +33,8 @@ 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.block.data.BlockData; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -119,8 +119,8 @@ public class FightHullHider { }); } - public void blockUpdate(Block block, Material changedType) { - hullHider.blockUpdate(block, changedType); + public void blockUpdate(Block block, BlockData changedData) { + hullHider.blockUpdate(block, changedData); } public boolean isBlockHidden(Player player, int x, int y, int z) { diff --git a/HiderCore/src/de/steamwar/hullhider/Hull.java b/HiderCore/src/de/steamwar/hullhider/Hull.java index 745d5d88..b39a215e 100644 --- a/HiderCore/src/de/steamwar/hullhider/Hull.java +++ b/HiderCore/src/de/steamwar/hullhider/Hull.java @@ -37,8 +37,11 @@ import org.bukkit.Chunk; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.Tag; import org.bukkit.World; import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.type.Slab; import org.bukkit.craftbukkit.block.data.CraftBlockData; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; @@ -64,6 +67,7 @@ public class Hull { private final BitSet occluding; private final BitSet visibility; private final HashMap visibilityDirections = new HashMap<>(); // Contains the visible directions of each occluding visible block + private final HashMap branches = new HashMap<>(); // The diagonal directions a ray along a single axis branches into private final HashSet uncoveredSurface = new HashSet<>(); private final Viewers viewers; @@ -91,6 +95,9 @@ public class Hull { for (IntVector direction : directions) { visibilityDirections.put(direction, new BitSet(region.volume())); + branches.put(direction, direction.sqLength() == 1 + ? Arrays.stream(directions).filter(v -> v.sqLength() >= 2 && dot(v, direction) == 1).toArray(IntVector[]::new) + : NO_BRANCHES); } } @@ -98,6 +105,15 @@ public class Hull { return material.isOccluding() || hiddenBlocks.contains(material); } + private boolean isOccluding(BlockData data) { + return isOccluding(data.getMaterial()) || isDoubleSlab(data); + } + + // Double slabs are full blocks, but slab materials do not occlude + private static boolean isDoubleSlab(BlockData data) { + return data instanceof Slab slab && slab.getType() == Slab.Type.DOUBLE; + } + public boolean isBlockHidden(Player player, int x, int y, int z) { return region.contains(x, y, z) && !isViewer(player) && !visibility.get(region.toIndex(x, y, z)); } @@ -248,37 +264,88 @@ public class Hull { long start = System.currentTimeMillis(); region.forEach((x, y, z) -> { - IntVector block = new IntVector(x, y, z); - if (isOccluding(world.getBlockAt(x, y, z).getType())) { - occluding.set(block.toId(region)); + Block block = world.getBlockAt(x, y, z); + Material type = block.getType(); + // Only slabs need their block data + if (isOccluding(type) || Tag.SLABS.isTagged(type) && isDoubleSlab(block.getBlockData())) { + occluding.set(region.toIndex(x, y, z)); } }); forEachBorder((root, direction) -> uncoverBlocks(new NullList<>(), root, direction)); plugin.getLogger().log(Level.INFO, () -> "[HullHider] initialisation finished: " + (System.currentTimeMillis() - start) + " ms, visible blocks: " + visibility.cardinality()); } - public void updateBlockVisibility(Block b, Material changedType) { + public void updateBlockVisibility(Block b, BlockData changedData) { IntVector root = new IntVector(b.getX(), b.getY(), b.getZ()); if (root.notInRegion(region)) return; int id = root.toId(region); - if (!occluding.get(id) || isOccluding(changedType)) return; + if (!occluding.get(id) || isOccluding(changedData)) return; occluding.clear(id); - if (!visibility.get(id)) return; List uncovered = new ArrayList<>(); - for (Map.Entry directionalVisibility : visibilityDirections.entrySet()) { - if (directionalVisibility.getValue().get(id)) { - directionalVisibility.getValue().clear(id); - uncoverBlocks(uncovered, root, directionalVisibility.getKey()); + if (visibility.get(id)) { + for (Map.Entry directionalVisibility : visibilityDirections.entrySet()) { + if (directionalVisibility.getValue().get(id)) { + directionalVisibility.getValue().clear(id); + uncoverBlocks(uncovered, root, directionalVisibility.getKey()); + } } } + // Also if the block was not visible itself, it might have blocked diagonal rays passing it + uncoverDiagonalsBeside(uncovered, root); uncoveredSurface.addAll(new HashSet<>(uncovered)); uncoveredSurface.remove(root); } + /** + * Continues the diagonal rays that could not pass the block while it occluded. + * The block lies between the start and the end of a diagonal step if it is the start moved along some, but not all, axes of the direction. + */ + private void uncoverDiagonalsBeside(List uncovered, IntVector root) { + for (IntVector diagonal : directions) { + if (diagonal.sqLength() < 2) continue; + + for (IntVector part : directions) { + if (part.sqLength() == 0 || part.sqLength() >= diagonal.sqLength() || !isPartOf(part, diagonal)) continue; + + IntVector from = root.add(-part.x, -part.y, -part.z); + if (from.notInRegion(region)) continue; + + int fromId = from.toId(region); + if (!occluding.get(fromId) && reachedBy(fromId, diagonal) && canPass(from, diagonal)) { + uncoverBlocks(uncovered, from.add(diagonal), diagonal, visibilityDirections.get(diagonal), NO_BRANCHES); + } + } + } + } + + private static boolean isPartOf(IntVector part, IntVector direction) { + return (part.x == 0 || part.x == direction.x) + && (part.y == 0 || part.y == direction.y) + && (part.z == 0 || part.z == direction.z); + } + + /** + * @return whether a ray moving in the diagonal direction reached the block: a diagonal ray or a branch of a cardinal ray + */ + private boolean reachedBy(int id, IntVector diagonal) { + if (visibilityDirections.get(diagonal).get(id)) return true; + + for (IntVector cardinal : directions) { + if (cardinal.sqLength() == 1 && dot(cardinal, diagonal) == 1 && visibilityDirections.get(cardinal).get(id)) { + return true; + } + } + return false; + } + + private static int dot(IntVector a, IntVector b) { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + public void sendUncoveredBlocks() { Map> sectionWise = new HashMap<>(); @@ -346,14 +413,7 @@ public class Hull { } private void uncoverBlocks(List uncovered, IntVector block, IntVector direction) { - uncoverBlocks( - uncovered, block, direction, visibilityDirections.get(direction), - direction.sqLength() == 1 - ? Arrays.stream(directions) - .filter(v -> v.sqLength() >= 2 && v.x * direction.x + v.y * direction.y + v.z * direction.z == 1) - .toArray(IntVector[]::new) - : NO_BRANCHES - ); + uncoverBlocks(uncovered, block, direction, visibilityDirections.get(direction), branches.get(direction)); } private void uncoverBlocks(List uncovered, IntVector block, IntVector direction, BitSet directionalVisibility, IntVector[] branchDirections) { @@ -370,12 +430,42 @@ public class Hull { if (occluding.get(id)) return; - uncoverBlocks(uncovered, block.add(direction), direction, directionalVisibility, branchDirections); + uncoverNext(uncovered, block, direction, directionalVisibility, branchDirections); for (IntVector branchDirection : branchDirections) { - uncoverBlocks(uncovered, block.add(branchDirection), branchDirection, visibilityDirections.get(branchDirection), NO_BRANCHES); + uncoverNext(uncovered, block, branchDirection, visibilityDirections.get(branchDirection), NO_BRANCHES); } } + private void uncoverNext(List uncovered, IntVector block, IntVector direction, BitSet directionalVisibility, IntVector[] branchDirections) { + int x = block.x + direction.x; + int y = block.y + direction.y; + int z = block.z + direction.z; + // The cheap checks first: most blocks were already reached from this direction + if (!region.contains(x, y, z) || directionalVisibility.get(region.toIndex(x, y, z)) || !canPass(block, direction)) return; + + uncoverBlocks(uncovered, new IntVector(x, y, z), direction, directionalVisibility, branchDirections); + } + + /** + * A ray can only move diagonally if it could also get there by moving along the single axes of the direction one after another, + * in any order, without passing an occluding block. Otherwise, it would pass between blocks that only touch at an edge or corner. + */ + private boolean canPass(IntVector block, IntVector direction) { + if (direction.sqLength() <= 1) return true; + // The ray ends there anyway; inside the region, all blocks in between are inside as well + if (!region.contains(block.x + direction.x, block.y + direction.y, block.z + direction.z)) return true; + + return canPass(block.x, block.y, block.z, direction.x, direction.y, direction.z); + } + + private boolean canPass(int x, int y, int z, int dx, int dy, int dz) { + if (Math.abs(dx) + Math.abs(dy) + Math.abs(dz) <= 1) return true; + + return (dx != 0 && !occluding.get(region.toIndex(x + dx, y, z)) && canPass(x + dx, y, z, 0, dy, dz)) + || (dy != 0 && !occluding.get(region.toIndex(x, y + dy, z)) && canPass(x, y + dy, z, dx, 0, dz)) + || (dz != 0 && !occluding.get(region.toIndex(x, y, z + dz)) && canPass(x, y, z + dz, dx, dy, 0)); + } + @Getter @AllArgsConstructor diff --git a/HiderCore/src/de/steamwar/hullhider/HullHider.java b/HiderCore/src/de/steamwar/hullhider/HullHider.java index 177d9ffe..c2a5d1b7 100644 --- a/HiderCore/src/de/steamwar/hullhider/HullHider.java +++ b/HiderCore/src/de/steamwar/hullhider/HullHider.java @@ -23,6 +23,7 @@ import de.steamwar.entity.REntity; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -148,13 +149,13 @@ public class HullHider implements Listener { @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()); + blockUpdate(e.getBlock(), e.getChangedBlockData()); } } - public void blockUpdate(Block block, Material changedType) { + public void blockUpdate(Block block, BlockData changedData) { for (Hull hull : hulls) { - hull.updateBlockVisibility(block, changedType); + hull.updateBlockVisibility(block, changedData); } } From e1ff37bea2f533340cd4292fb3bf7c450bf08af0 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 20:09:45 +0100 Subject: [PATCH 31/46] Apply the gamemode techhider with /hullhider Players using /hullhider also get the techhider of the region's gamemode in the build area, like in a fight: its hidden blocks are replaced and occlude in the hull. --- .../features/hullhider/HullHiderCommand.java | 20 ++++++++++++++++--- .../features/techhider/BauTechHider.java | 1 + 2 files changed, 18 insertions(+), 3 deletions(-) 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 index 2b2a99aa..f0ee5ecf 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullHiderCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullHiderCommand.java @@ -21,6 +21,7 @@ package de.steamwar.bausystem.features.hullhider; import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.features.techhider.HiddenHull; +import de.steamwar.bausystem.features.techhider.HidingRule; import de.steamwar.bausystem.features.techhider.TechHiderCommand; import de.steamwar.bausystem.features.xray.XrayCommand; import de.steamwar.bausystem.region.Point; @@ -63,6 +64,7 @@ public class HullHiderCommand extends SWCommand implements Listener { private final HullHider hullHider = new HullHider<>(false); // Read on netty threads by the BauTechHider private final Map hulls = new ConcurrentHashMap<>(); + private final Map hidingRules = new ConcurrentHashMap<>(); private final Map> hidden = new ConcurrentHashMap<>(); @LinkedInstance @@ -99,10 +101,12 @@ public class HullHiderCommand extends SWCommand implements Listener { 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()); + // The techhider of the gamemode is applied as well, its hidden blocks occlude as they are replaced by the obfuscation block + ObfuscationConfig obfuscation = ObfuscationConfig.of(region.getGameModeConfig().Techhider); + Hull hull = new Hull(BauSystem.getInstance(), world(), hullRegion, false, region.getGameModeConfig().Techhider.HiddenBlocks, new Viewers.VisibleByDefault()); hullHider.addHull(region, hull); - hulls.put(region, new HiddenHull(hull, ObfuscationConfig.of(region.getGameModeConfig().Techhider).getObfuscationState())); + hulls.put(region, new HiddenHull(hull, obfuscation.getObfuscationState())); + hidingRules.put(region, HidingRule.of(region.getBuildArea(), obfuscation)); } hidden.computeIfAbsent(region, rg -> ConcurrentHashMap.newKeySet()).add(player); @@ -123,6 +127,7 @@ public class HullHiderCommand extends SWCommand implements Listener { if (players.isEmpty()) { hidden.remove(region); hulls.remove(region); + hidingRules.remove(region); hullHider.removeHull(region); } else { hullHider.removeViewer(player, region); @@ -130,6 +135,15 @@ public class HullHiderCommand extends SWCommand implements Listener { } } + public void addHidingRules(Player player, List rules) { + hidden.forEach((region, players) -> { + HidingRule rule = hidingRules.get(region); + if (rule != null && players.contains(player)) { + rules.add(rule); + } + }); + } + public void addHiddenHulls(Player player, List hiddenHulls) { hidden.forEach((region, players) -> { HiddenHull hiddenHull = hulls.get(region); 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 9b91d6a4..99a7f255 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 @@ -69,6 +69,7 @@ public class BauTechHider implements AccessPrivilegeProvider { List rules = new ArrayList<>(); xrayCommand.addHidingRules(p, rules); techHiderCommand.addHidingRules(p, rules); + hullHiderCommand.addHidingRules(p, rules); spectatorListener.addHidingRules(p, rules); List hulls = new ArrayList<>(); From baa4cf34e52161ce981a1b1750398422b7ac5545 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 21:18:24 +0100 Subject: [PATCH 32/46] Calculate hull visibility on block faces Instead of a block occluding as a whole, the hull stores which of its six faces are full, using the occlusion shapes the client culls faces with. A ray inside a block can only leave it through an open face and enter the next block through its open opposite face; if only that face is full, the ray reaches its surface, so the block is visible, but stops there. Slabs, stairs and stacked slabs now hide what is behind them; double slabs need no special case anymore. Faces only open up on block updates, after which the rays that could not get into, out of or past the block are continued. Hidden blocks occlude by the same decision as the techhider, so waterlogged blocks count as well when water is hidden. The hull therefore takes the ObfuscationConfig, which the fight shares with its TechHiderWrapper. --- .../features/hullhider/HullHiderCommand.java | 2 +- .../fightsystem/utils/FightHullHider.java | 7 +- .../fightsystem/utils/TechHiderWrapper.java | 3 +- HiderCore/src/de/steamwar/hullhider/Hull.java | 215 +++++++++++------- 4 files changed, 147 insertions(+), 80 deletions(-) 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 index f0ee5ecf..08669a80 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullHiderCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullHiderCommand.java @@ -103,7 +103,7 @@ public class HullHiderCommand extends SWCommand implements Listener { // The techhider of the gamemode is applied as well, its hidden blocks occlude as they are replaced by the obfuscation block ObfuscationConfig obfuscation = ObfuscationConfig.of(region.getGameModeConfig().Techhider); - Hull hull = new Hull(BauSystem.getInstance(), world(), hullRegion, false, region.getGameModeConfig().Techhider.HiddenBlocks, new Viewers.VisibleByDefault()); + Hull hull = new Hull(BauSystem.getInstance(), world(), hullRegion, false, obfuscation, new Viewers.VisibleByDefault()); hullHider.addHull(region, hull); hulls.put(region, new HiddenHull(hull, obfuscation.getObfuscationState())); hidingRules.put(region, HidingRule.of(region.getBuildArea(), obfuscation)); 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 8c7eee13..fd961d96 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightHullHider.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightHullHider.java @@ -33,6 +33,8 @@ import de.steamwar.fightsystem.states.StateDependentTask; import de.steamwar.hullhider.Hull; import de.steamwar.hullhider.HullHider; import de.steamwar.hullhider.Viewers; +import de.steamwar.techhider.ObfuscationConfig; +import lombok.Getter; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; @@ -51,13 +53,16 @@ import java.util.function.BiConsumer; public class FightHullHider { private final HullHider hullHider = new HullHider<>(true); + // Shared with the TechHiderWrapper + @Getter + private final ObfuscationConfig obfuscation = ObfuscationConfig.of(Config.GameModeConfig.Techhider); 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 Viewers.HiddenByDefault())); + hullHider.addHull(team, new Hull(FightSystem.getPlugin(), Config.world, region.toHullRegion(), region.getMinY() != Config.PlayerRegion.getMinY(), obfuscation, new Viewers.HiddenByDefault())); }); new StateDependentListener(TechHiderWrapper.ENABLED, FightState.All, new MembershipListener()); 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 6f0b1290..534e7cee 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java @@ -69,8 +69,7 @@ public class TechHiderWrapper extends StateDependent implements Listener { @Override public void enable() { - ObfuscationConfig obfuscation = ObfuscationConfig.of(Config.GameModeConfig.Techhider); - new TechHider(newProvider(obfuscation)); + new TechHider(newProvider(hullHider.getObfuscation())); } private AccessPrivilegeProvider newProvider(ObfuscationConfig obfuscation) { diff --git a/HiderCore/src/de/steamwar/hullhider/Hull.java b/HiderCore/src/de/steamwar/hullhider/Hull.java index b39a215e..3c37ff26 100644 --- a/HiderCore/src/de/steamwar/hullhider/Hull.java +++ b/HiderCore/src/de/steamwar/hullhider/Hull.java @@ -24,24 +24,26 @@ import com.mojang.authlib.GameProfile; import de.steamwar.core.CraftbukkitWrapper; import de.steamwar.core.ProtocolWrapper; import de.steamwar.entity.REntity; +import de.steamwar.techhider.ObfuscationConfig; import it.unimi.dsi.fastutil.shorts.Short2ObjectArrayMap; import lombok.AllArgsConstructor; import lombok.Getter; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import net.minecraft.core.SectionPos; import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.shapes.VoxelShape; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.GameMode; import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Tag; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; -import org.bukkit.block.data.type.Slab; +import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.block.data.CraftBlockData; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; @@ -49,24 +51,28 @@ import org.bukkit.plugin.Plugin; import java.util.*; import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.logging.Level; import java.util.stream.IntStream; public class Hull { private static final IntVector[] NO_BRANCHES = new IntVector[0]; + private static final int ALL_FACES = 0b111111; + private static final byte UNKNOWN_FACES = -1; private final Plugin plugin; private final World world; @Getter private final HullRegion region; private final boolean groundVisible; - private final Set hiddenBlocks; + private final ObfuscationConfig obfuscation; private final IntVector[] directions; - private final BitSet occluding; + private final byte[] fullFaces; // The faces of each block that are fully covered, see face() + private byte[] fullFacesByState; // Cache, per hull as the hidden blocks differ private final BitSet visibility; - private final HashMap visibilityDirections = new HashMap<>(); // Contains the visible directions of each occluding visible block + private final HashMap visibilityDirections = new HashMap<>(); // The blocks the rays of each direction got inside private final HashMap branches = new HashMap<>(); // The diagonal directions a ray along a single axis branches into private final HashSet uncoveredSurface = new HashSet<>(); @@ -76,17 +82,17 @@ public class Hull { /** * @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 obfuscation the techhider applied to the hull, the blocks it hides occlude as they are replaced 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, Viewers viewers) { + public Hull(Plugin plugin, World world, HullRegion region, boolean groundVisible, ObfuscationConfig obfuscation, Viewers viewers) { this.plugin = plugin; this.world = world; this.region = region; this.groundVisible = groundVisible; - this.hiddenBlocks = hiddenBlocks; + this.obfuscation = obfuscation; this.viewers = viewers; - this.occluding = new BitSet(region.volume()); + this.fullFaces = new byte[region.volume()]; this.visibility = new BitSet(region.volume()); directions = IntStream.range(0, 27) @@ -101,17 +107,42 @@ public class Hull { } } - private boolean isOccluding(Material material) { - return material.isOccluding() || hiddenBlocks.contains(material); + /** + * @return the bit of the face of a block in the single-axis direction + */ + private static int face(int dx, int dy, int dz) { + if (dx != 0) return dx > 0 ? 1 : 1 << 1; + if (dy != 0) return dy > 0 ? 1 << 2 : 1 << 3; + return dz > 0 ? 1 << 4 : 1 << 5; } - private boolean isOccluding(BlockData data) { - return isOccluding(data.getMaterial()) || isDoubleSlab(data); + private int fullFacesOf(BlockState state) { + if (fullFacesByState == null) { + fullFacesByState = new byte[net.minecraft.world.level.block.Block.BLOCK_STATE_REGISTRY.size()]; + Arrays.fill(fullFacesByState, UNKNOWN_FACES); + } + + int id = net.minecraft.world.level.block.Block.getId(state); + if (fullFacesByState[id] == UNKNOWN_FACES) { + fullFacesByState[id] = (byte) computeFullFaces(state); + } + return fullFacesByState[id]; } - // Double slabs are full blocks, but slab materials do not occlude - private static boolean isDoubleSlab(BlockData data) { - return data instanceof Slab slab && slab.getType() == Slab.Type.DOUBLE; + // The faces that hide what is behind them, as the client culls them + private int computeFullFaces(BlockState state) { + // Hidden blocks are replaced by an occluding block, the same decision as the techhider's + if (obfuscation.hidesBlock(state.getBlock()) || obfuscation.hidesBlockState(state)) return ALL_FACES; + if (!state.canOcclude()) return 0; + + VoxelShape shape = state.getOcclusionShape(); + int faces = 0; + for (Direction direction : Direction.values()) { + if (net.minecraft.world.level.block.Block.isFaceFull(shape, direction)) { + faces |= face(direction.getStepX(), direction.getStepY(), direction.getStepZ()); + } + } + return faces; } public boolean isBlockHidden(Player player, int x, int y, int z) { @@ -247,7 +278,7 @@ public class Hull { public void fill(boolean visible) { visibility.set(0, visibility.size(), visible); - occluding.set(0, occluding.size(), !visible); + Arrays.fill(fullFaces, (byte) (visible ? 0 : ALL_FACES)); uncoveredSurface.clear(); for (BitSet directionalVisibility : visibilityDirections.values()) { directionalVisibility.set(0, directionalVisibility.size(), visible); @@ -256,67 +287,59 @@ public class Hull { public void initialize() { visibility.clear(); - occluding.clear(); uncoveredSurface.clear(); for (BitSet directionalVisibility : visibilityDirections.values()) { directionalVisibility.clear(); } long start = System.currentTimeMillis(); - region.forEach((x, y, z) -> { - Block block = world.getBlockAt(x, y, z); - Material type = block.getType(); - // Only slabs need their block data - if (isOccluding(type) || Tag.SLABS.isTagged(type) && isDoubleSlab(block.getBlockData())) { - occluding.set(region.toIndex(x, y, z)); - } - }); - forEachBorder((root, direction) -> uncoverBlocks(new NullList<>(), root, direction)); + ServerLevel level = ((CraftWorld) world).getHandle(); + BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); + region.forEach((x, y, z) -> fullFaces[region.toIndex(x, y, z)] = (byte) fullFacesOf(level.getBlockState(pos.set(x, y, z)))); + forEachBorder((root, direction) -> startRay(new NullList<>(), root, direction)); plugin.getLogger().log(Level.INFO, () -> "[HullHider] initialisation finished: " + (System.currentTimeMillis() - start) + " ms, visible blocks: " + visibility.cardinality()); } public void updateBlockVisibility(Block b, BlockData changedData) { IntVector root = new IntVector(b.getX(), b.getY(), b.getZ()); - if (root.notInRegion(region)) return; + if (root.notInRegion(region)) return; int id = root.toId(region); - if (!occluding.get(id) || isOccluding(changedData)) return; - - occluding.clear(id); + // Faces only open up, the hull never hides anything again + int faces = fullFaces[id] & fullFacesOf(((CraftBlockData) changedData).getState()); + if (faces == fullFaces[id]) return; + fullFaces[id] = (byte) faces; List uncovered = new ArrayList<>(); - if (visibility.get(id)) { - for (Map.Entry directionalVisibility : visibilityDirections.entrySet()) { - if (directionalVisibility.getValue().get(id)) { - directionalVisibility.getValue().clear(id); - uncoverBlocks(uncovered, root, directionalVisibility.getKey()); - } + // Rays inside the block that could not leave it + for (Map.Entry directionalVisibility : visibilityDirections.entrySet()) { + if (directionalVisibility.getValue().get(id)) { + directionalVisibility.getValue().clear(id); + uncoverBlocks(uncovered, root, directionalVisibility.getKey()); } } - // Also if the block was not visible itself, it might have blocked diagonal rays passing it - uncoverDiagonalsBeside(uncovered, root); + // Rays that could not get into the block or past it + uncoverBeside(uncovered, root); + forEachBorderStart(root, direction -> startRay(uncovered, root, direction)); uncoveredSurface.addAll(new HashSet<>(uncovered)); uncoveredSurface.remove(root); } /** - * Continues the diagonal rays that could not pass the block while it occluded. - * The block lies between the start and the end of a diagonal step if it is the start moved along some, but not all, axes of the direction. + * Continues the rays inside the blocks in front of the block, or of which a diagonal ray moves past the block. + * The block lies in front of or beside the start of a step if it is the start moved along some or all axes of the direction. */ - private void uncoverDiagonalsBeside(List uncovered, IntVector root) { - for (IntVector diagonal : directions) { - if (diagonal.sqLength() < 2) continue; + private void uncoverBeside(List uncovered, IntVector root) { + for (IntVector direction : directions) { + if (direction.sqLength() == 0) continue; for (IntVector part : directions) { - if (part.sqLength() == 0 || part.sqLength() >= diagonal.sqLength() || !isPartOf(part, diagonal)) continue; + if (part.sqLength() == 0 || !isPartOf(part, direction)) continue; IntVector from = root.add(-part.x, -part.y, -part.z); - if (from.notInRegion(region)) continue; - - int fromId = from.toId(region); - if (!occluding.get(fromId) && reachedBy(fromId, diagonal) && canPass(from, diagonal)) { - uncoverBlocks(uncovered, from.add(diagonal), diagonal, visibilityDirections.get(diagonal), NO_BRANCHES); + if (!from.notInRegion(region) && isInside(from.toId(region), direction)) { + uncoverNext(uncovered, from, direction, visibilityDirections.get(direction), branches.get(direction)); } } } @@ -329,13 +352,13 @@ public class Hull { } /** - * @return whether a ray moving in the diagonal direction reached the block: a diagonal ray or a branch of a cardinal ray + * @return whether a ray moving in the direction is inside the block: a ray of this direction or a branch of a ray along a single axis */ - private boolean reachedBy(int id, IntVector diagonal) { - if (visibilityDirections.get(diagonal).get(id)) return true; + private boolean isInside(int id, IntVector direction) { + if (visibilityDirections.get(direction).get(id)) return true; for (IntVector cardinal : directions) { - if (cardinal.sqLength() == 1 && dot(cardinal, diagonal) == 1 && visibilityDirections.get(cardinal).get(id)) { + if (cardinal.sqLength() == 1 && dot(cardinal, direction) == 1 && visibilityDirections.get(cardinal).get(id)) { return true; } } @@ -387,6 +410,16 @@ public class Hull { } } + // The rays forEachBorder starts at the block + private void forEachBorderStart(IntVector block, Consumer f) { + if (groundVisible && block.y == region.getMinY()) f.accept(new IntVector(0, 1, 0)); + if (block.y == region.getMaxY() - 1) f.accept(new IntVector(0, -1, 0)); + if (block.z == region.getMinZ()) f.accept(new IntVector(0, 0, 1)); + if (block.z == region.getMaxZ() - 1) f.accept(new IntVector(0, 0, -1)); + if (block.x == region.getMinX()) f.accept(new IntVector(1, 0, 0)); + if (block.x == region.getMaxX() - 1) f.accept(new IntVector(-1, 0, 0)); + } + private void forEachBorder(BiConsumer f) { for (int x = region.getMinX(); x < region.getMaxX(); x++) { for (int z = region.getMinZ(); z < region.getMaxZ(); z++) { @@ -412,23 +445,31 @@ public class Hull { } } + /** + * Starts a ray at the border of the region, coming from outside. + */ + private void startRay(List uncovered, IntVector block, IntVector direction) { + if (faceFull(block.x, block.y, block.z, -direction.x, -direction.y, -direction.z)) { + reveal(uncovered, block, block.toId(region)); + } else { + uncoverBlocks(uncovered, block, direction); + } + } + private void uncoverBlocks(List uncovered, IntVector block, IntVector direction) { uncoverBlocks(uncovered, block, direction, visibilityDirections.get(direction), branches.get(direction)); } + /** + * Continues a ray that got inside the block. + */ private void uncoverBlocks(List uncovered, IntVector block, IntVector direction, BitSet directionalVisibility, IntVector[] branchDirections) { if (block.notInRegion(region)) return; int id = block.toId(region); if (directionalVisibility.get(id)) return; directionalVisibility.set(id); - - if (!visibility.get(id)) { - visibility.set(id); - uncovered.add(block); - } - - if (occluding.get(id)) return; + reveal(uncovered, block, id); uncoverNext(uncovered, block, direction, directionalVisibility, branchDirections); for (IntVector branchDirection : branchDirections) { @@ -441,29 +482,51 @@ public class Hull { int y = block.y + direction.y; int z = block.z + direction.z; // The cheap checks first: most blocks were already reached from this direction - if (!region.contains(x, y, z) || directionalVisibility.get(region.toIndex(x, y, z)) || !canPass(block, direction)) return; + if (!region.contains(x, y, z) || directionalVisibility.get(region.toIndex(x, y, z))) return; - uncoverBlocks(uncovered, new IntVector(x, y, z), direction, directionalVisibility, branchDirections); + if (canMove(block.x, block.y, block.z, direction.x, direction.y, direction.z, true)) { + uncoverBlocks(uncovered, new IntVector(x, y, z), direction, directionalVisibility, branchDirections); + return; + } + + // The ray might still reach the face of the next block, mostly it is visible already + int id = region.toIndex(x, y, z); + if (!visibility.get(id) && canMove(block.x, block.y, block.z, direction.x, direction.y, direction.z, false)) { + reveal(uncovered, new IntVector(x, y, z), id); + } + } + + private void reveal(List uncovered, IntVector block, int id) { + if (!visibility.get(id)) { + visibility.set(id); + uncovered.add(block); + } } /** - * A ray can only move diagonally if it could also get there by moving along the single axes of the direction one after another, - * in any order, without passing an occluding block. Otherwise, it would pass between blocks that only touch at an edge or corner. + * A ray moves along the single axes of its direction one after another, in any order that does not pass a full face. + * So a diagonal ray does not pass between blocks that only touch at an edge or corner. + * + * @param enter whether the ray has to get inside the target block, otherwise reaching its face suffices */ - private boolean canPass(IntVector block, IntVector direction) { - if (direction.sqLength() <= 1) return true; - // The ray ends there anyway; inside the region, all blocks in between are inside as well - if (!region.contains(block.x + direction.x, block.y + direction.y, block.z + direction.z)) return true; + private boolean canMove(int x, int y, int z, int dx, int dy, int dz, boolean enter) { + if (dx == 0 && dy == 0 && dz == 0) return true; - return canPass(block.x, block.y, block.z, direction.x, direction.y, direction.z); + // The blocks in between have to be entered + return (dx != 0 && canCross(x, y, z, dx, 0, 0, enter || dy != 0 || dz != 0) && canMove(x + dx, y, z, 0, dy, dz, enter)) + || (dy != 0 && canCross(x, y, z, 0, dy, 0, enter || dx != 0 || dz != 0) && canMove(x, y + dy, z, dx, 0, dz, enter)) + || (dz != 0 && canCross(x, y, z, 0, 0, dz, enter || dx != 0 || dy != 0) && canMove(x, y, z + dz, dx, dy, 0, enter)); } - private boolean canPass(int x, int y, int z, int dx, int dy, int dz) { - if (Math.abs(dx) + Math.abs(dy) + Math.abs(dz) <= 1) return true; + /** + * @return whether a ray inside the block can leave it to the neighbouring block in the single-axis direction (and enter it) + */ + private boolean canCross(int x, int y, int z, int dx, int dy, int dz, boolean enter) { + return !faceFull(x, y, z, dx, dy, dz) && (!enter || !faceFull(x + dx, y + dy, z + dz, -dx, -dy, -dz)); + } - return (dx != 0 && !occluding.get(region.toIndex(x + dx, y, z)) && canPass(x + dx, y, z, 0, dy, dz)) - || (dy != 0 && !occluding.get(region.toIndex(x, y + dy, z)) && canPass(x, y + dy, z, dx, 0, dz)) - || (dz != 0 && !occluding.get(region.toIndex(x, y, z + dz)) && canPass(x, y, z + dz, dx, dy, 0)); + private boolean faceFull(int x, int y, int z, int dx, int dy, int dz) { + return (fullFaces[region.toIndex(x, y, z)] & face(dx, dy, dz)) != 0; } From d7d452ebe60f5041dfd0671fb935211a042e9258 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 21:37:08 +0100 Subject: [PATCH 33/46] Let doors and trapdoors occlude in hulls Doors and trapdoors do not occlude for the client, so they had no full face in the hull and rays passed through closed trapdoors, revealing blocks behind them along diagonals. Their face masks now come from their shape, so the face their panel covers is full. --- HiderCore/src/de/steamwar/hullhider/Hull.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/HiderCore/src/de/steamwar/hullhider/Hull.java b/HiderCore/src/de/steamwar/hullhider/Hull.java index 3c37ff26..081e47fe 100644 --- a/HiderCore/src/de/steamwar/hullhider/Hull.java +++ b/HiderCore/src/de/steamwar/hullhider/Hull.java @@ -34,6 +34,9 @@ import net.minecraft.core.SectionPos; import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket; import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.EmptyBlockGetter; +import net.minecraft.world.level.block.DoorBlock; +import net.minecraft.world.level.block.TrapDoorBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.shapes.VoxelShape; import org.bukkit.Bukkit; @@ -129,13 +132,21 @@ public class Hull { return fullFacesByState[id]; } - // The faces that hide what is behind them, as the client culls them + // The faces that hide what is behind them, mostly as the client culls them private int computeFullFaces(BlockState state) { // Hidden blocks are replaced by an occluding block, the same decision as the techhider's if (obfuscation.hidesBlock(state.getBlock()) || obfuscation.hidesBlockState(state)) return ALL_FACES; - if (!state.canOcclude()) return 0; - VoxelShape shape = state.getOcclusionShape(); + VoxelShape shape; + if (state.canOcclude()) { + shape = state.getOcclusionShape(); + } else if (state.getBlock() instanceof DoorBlock || state.getBlock() instanceof TrapDoorBlock) { + // Their panel covers a face, although the client does not cull the faces behind them + shape = state.getShape(EmptyBlockGetter.INSTANCE, BlockPos.ZERO); + } else { + return 0; + } + int faces = 0; for (Direction direction : Direction.values()) { if (net.minecraft.world.level.block.Block.isFaceFull(shape, direction)) { From 4dbe2ccac71bc57e94b008ffc215a2d4473f0ea4 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 21:58:48 +0100 Subject: [PATCH 34/46] Speed up uncovering blocks in hulls The steps that can lead into or past a changed block are computed once per hull instead of searching all directions and parts on every block update, which makes an update about three times faster. --- HiderCore/src/de/steamwar/hullhider/Hull.java | 62 ++++++++++++------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/HiderCore/src/de/steamwar/hullhider/Hull.java b/HiderCore/src/de/steamwar/hullhider/Hull.java index 081e47fe..02f8c56c 100644 --- a/HiderCore/src/de/steamwar/hullhider/Hull.java +++ b/HiderCore/src/de/steamwar/hullhider/Hull.java @@ -77,6 +77,7 @@ public class Hull { private final BitSet visibility; private final HashMap visibilityDirections = new HashMap<>(); // The blocks the rays of each direction got inside private final HashMap branches = new HashMap<>(); // The diagonal directions a ray along a single axis branches into + private final StepBeside[] stepsBeside; // The steps that can lead into or past a block, see uncoverBeside() private final HashSet uncoveredSurface = new HashSet<>(); private final Viewers viewers; @@ -108,6 +109,7 @@ public class Hull { ? Arrays.stream(directions).filter(v -> v.sqLength() >= 2 && dot(v, direction) == 1).toArray(IntVector[]::new) : NO_BRANCHES); } + stepsBeside = computeStepsBeside(); } /** @@ -333,49 +335,61 @@ public class Hull { uncoverBeside(uncovered, root); forEachBorderStart(root, direction -> startRay(uncovered, root, direction)); - uncoveredSurface.addAll(new HashSet<>(uncovered)); + uncoveredSurface.addAll(uncovered); uncoveredSurface.remove(root); } /** * Continues the rays inside the blocks in front of the block, or of which a diagonal ray moves past the block. - * The block lies in front of or beside the start of a step if it is the start moved along some or all axes of the direction. */ private void uncoverBeside(List uncovered, IntVector root) { - for (IntVector direction : directions) { - if (direction.sqLength() == 0) continue; + for (StepBeside step : stepsBeside) { + int x = root.x - step.part.x; + int y = root.y - step.part.y; + int z = root.z - step.part.z; + if (!region.contains(x, y, z)) continue; - for (IntVector part : directions) { - if (part.sqLength() == 0 || !isPartOf(part, direction)) continue; - - IntVector from = root.add(-part.x, -part.y, -part.z); - if (!from.notInRegion(region) && isInside(from.toId(region), direction)) { - uncoverNext(uncovered, from, direction, visibilityDirections.get(direction), branches.get(direction)); + int id = region.toIndex(x, y, z); + for (BitSet ray : step.rays) { + if (ray.get(id)) { + uncoverNext(uncovered, new IntVector(x, y, z), step.direction, step.directionalVisibility, step.branchDirections); + break; } } } } + /** + * The block lies in front of or beside the start of a step if it is the start moved along some or all axes of the direction. + * The rays of the direction and the rays along a single axis branching into it take the step. + */ + private StepBeside[] computeStepsBeside() { + List steps = new ArrayList<>(); + for (IntVector direction : directions) { + if (direction.sqLength() == 0) continue; + + BitSet[] rays = Arrays.stream(directions) + .filter(ray -> ray.equals(direction) || ray.sqLength() == 1 && direction.sqLength() > 1 && dot(ray, direction) == 1) + .map(visibilityDirections::get) + .toArray(BitSet[]::new); + for (IntVector part : directions) { + if (part.sqLength() > 0 && isPartOf(part, direction)) { + steps.add(new StepBeside(direction, part, visibilityDirections.get(direction), branches.get(direction), rays)); + } + } + } + return steps.toArray(new StepBeside[0]); + } + + private record StepBeside(IntVector direction, IntVector part, BitSet directionalVisibility, IntVector[] branchDirections, BitSet[] rays) { + } + private static boolean isPartOf(IntVector part, IntVector direction) { return (part.x == 0 || part.x == direction.x) && (part.y == 0 || part.y == direction.y) && (part.z == 0 || part.z == direction.z); } - /** - * @return whether a ray moving in the direction is inside the block: a ray of this direction or a branch of a ray along a single axis - */ - private boolean isInside(int id, IntVector direction) { - if (visibilityDirections.get(direction).get(id)) return true; - - for (IntVector cardinal : directions) { - if (cardinal.sqLength() == 1 && dot(cardinal, direction) == 1 && visibilityDirections.get(cardinal).get(id)) { - return true; - } - } - return false; - } - private static int dot(IntVector a, IntVector b) { return a.x * b.x + a.y * b.y + a.z * b.z; } From 9eea5f5e252e3ae31c5996ea89ae5ca2d4a49a1f Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 22:24:51 +0100 Subject: [PATCH 35/46] Keep hidden block entities in chunks without their data Hidden block entities were removed from chunk data, so the client did not render blocks like shulker boxes at all. They are now sent with empty data, hiding their contents but not the block itself. --- .../src/de/steamwar/techhider/ChunkHider.java | 33 ++++++++++++------- HiderCore/src/hidercore.accesswidener | 1 + 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/HiderCore/src/de/steamwar/techhider/ChunkHider.java b/HiderCore/src/de/steamwar/techhider/ChunkHider.java index 6cabee7b..ab4eb45e 100644 --- a/HiderCore/src/de/steamwar/techhider/ChunkHider.java +++ b/HiderCore/src/de/steamwar/techhider/ChunkHider.java @@ -23,6 +23,7 @@ import de.steamwar.core.ProtocolUtils; import de.steamwar.techhider.AccessPrivilegeProvider.ForPlayer; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import net.minecraft.nbt.CompoundTag; import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; import net.minecraft.util.SimpleBitStorage; @@ -31,6 +32,7 @@ import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import org.bukkit.entity.Player; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.UnaryOperator; @@ -263,21 +265,30 @@ public class ChunkHider { private List filterBlockEntities(ForPlayer access, List blockEntities, int chunkX, int chunkZ) { int fourBitBitmask = 0b0000_1111; - return blockEntities.stream() - .filter((blockEntityInfo) -> { - BlockEntityType type = blockEntityInfo.type; - int packedXZ = blockEntityInfo.packedXZ; + List filtered = new ArrayList<>(blockEntities.size()); + for (ClientboundLevelChunkPacketData.BlockEntityInfo blockEntityInfo : blockEntities) { + BlockEntityType type = blockEntityInfo.type; + int packedXZ = blockEntityInfo.packedXZ; - int localX = (packedXZ >> 4) & fourBitBitmask; - int localZ = packedXZ & fourBitBitmask; + int localX = (packedXZ >> 4) & fourBitBitmask; + int localZ = packedXZ & fourBitBitmask; - int worldX = (chunkX * SECTION_SPAN_SIZE) + localX; - int worldZ = (chunkZ * SECTION_SPAN_SIZE) + localZ; + int worldX = (chunkX * SECTION_SPAN_SIZE) + localX; + int worldZ = (chunkZ * SECTION_SPAN_SIZE) + localZ; - int worldY = blockEntityInfo.y; + int worldY = blockEntityInfo.y; - return access.canAccessPosition(worldX, worldY, worldZ) && access.canAccessBlockEntity(worldX, worldY, worldZ, type); - }).toList(); + // A hidden position is obfuscated as a whole + if (!access.canAccessPosition(worldX, worldY, worldZ)) continue; + + if (access.canAccessBlockEntity(worldX, worldY, worldZ, type)) { + filtered.add(blockEntityInfo); + } else { + // Without its block entity, the client would not render blocks like shulker boxes, so only their data is hidden + filtered.add(new ClientboundLevelChunkPacketData.BlockEntityInfo(packedXZ, worldY, type, new CompoundTag())); + } + } + return filtered; } private void copyOverSectionBiomeData(ByteBuf oldData, ByteBuf newData) { diff --git a/HiderCore/src/hidercore.accesswidener b/HiderCore/src/hidercore.accesswidener index 347067a1..9a266fac 100644 --- a/HiderCore/src/hidercore.accesswidener +++ b/HiderCore/src/hidercore.accesswidener @@ -25,3 +25,4 @@ accessible class net/minecraft/network/protocol/game/ClientboundLevelChunkPacket accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo type Lnet/minecraft/world/level/block/entity/BlockEntityType; accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo packedXZ I accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo y I +accessible method net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo (IILnet/minecraft/world/level/block/entity/BlockEntityType;Lnet/minecraft/nbt/CompoundTag;)V From 460d016295a9fe33dd6158a881f1245b96bfe5c1 Mon Sep 17 00:00:00 2001 From: Jakob Schulz <55949993+Lordikak@users.noreply.github.com> Date: Sat, 31 Oct 2026 22:35:42 +0100 Subject: [PATCH 36/46] Change naming to fight// --- .../src/de/steamwar/fightsystem/FightSystem.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java index 5da7c23a..e6238387 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java @@ -85,7 +85,7 @@ public class FightSystem extends JavaPlugin { String arenaMap = System.getProperty("arenaMap"); if (arenaMap != null) { - WorldIdentifier.set("fight/" + HexFormat.of().formatHex(arenaMap.getBytes(StandardCharsets.UTF_8))); + WorldIdentifier.set("fight/" + Core.getVersion() + "/" + arenaMap); } message = new Message("de.steamwar.fightsystem.FightSystem", FightSystem.class.getClassLoader()); From d196f49ba2b800395c590054eea460aab9ead9d2 Mon Sep 17 00:00:00 2001 From: Jakob Schulz <55949993+Lordikak@users.noreply.github.com> Date: Sat, 31 Oct 2026 22:45:32 +0100 Subject: [PATCH 37/46] Add arena map world name identifier for TowerRun and TNTLeague --- TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt | 7 +++++++ TowerRun/src/de/steamwar/towerrun/TowerRun.java | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt b/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt index 588b6864..5dc3b8b6 100644 --- a/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt +++ b/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt @@ -19,6 +19,8 @@ package de.steamwar.tntleague +import de.steamwar.core.Core +import de.steamwar.core.WorldIdentifier import de.steamwar.message.Message import de.steamwar.tntleague.command.* import de.steamwar.tntleague.events.GlobalListener @@ -43,6 +45,11 @@ class TNTLeague : JavaPlugin() { logger.info("TNTLeague enabled") + val arenaMap = System.getProperty("arenaMap") + if (arenaMap != null) { + WorldIdentifier.set("fight/" + Core.getVersion() + "/" + arenaMap) + } + ReadyCommand.register() InviteCommand.register() AcceptCommand.register() diff --git a/TowerRun/src/de/steamwar/towerrun/TowerRun.java b/TowerRun/src/de/steamwar/towerrun/TowerRun.java index 10298547..30dd4046 100644 --- a/TowerRun/src/de/steamwar/towerrun/TowerRun.java +++ b/TowerRun/src/de/steamwar/towerrun/TowerRun.java @@ -20,6 +20,8 @@ package de.steamwar.towerrun; import de.steamwar.command.SWCommand; +import de.steamwar.core.Core; +import de.steamwar.core.WorldIdentifier; import de.steamwar.linkage.AbstractLinker; import de.steamwar.linkage.LinkedInstance; import de.steamwar.linkage.SpigotLinker; @@ -56,6 +58,11 @@ public class TowerRun extends JavaPlugin { towerGenerator = new TowerGenerator(WorldConfig.TOWER_GENERATOR_CONFIG); } + String arenaMap = System.getProperty("arenaMap"); + if (arenaMap != null) { + WorldIdentifier.set("fight/" + Core.getVersion() + "/" + arenaMap); + } + SpigotLinker spigotLinker = new SpigotLinker(this, message) { @Override protected void linkObject(Object any) { @@ -64,6 +71,7 @@ public class TowerRun extends JavaPlugin { } } }; + try { spigotLinker.link(); } catch (AbstractLinker.LinkException e) { From 641962ff01f75d1b1cd4c4fb5219742c459d71c6 Mon Sep 17 00:00:00 2001 From: Jakob Schulz <55949993+Lordikak@users.noreply.github.com> Date: Sat, 31 Oct 2026 22:47:18 +0100 Subject: [PATCH 38/46] change arena map identifier to arena/<... --- .../src/de/steamwar/fightsystem/FightSystem.java | 2 +- MissileWars/src/de/steamwar/misslewars/MissileWars.java | 7 +++++++ TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt | 2 +- TowerRun/src/de/steamwar/towerrun/TowerRun.java | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java index e6238387..2e6f83e9 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java @@ -85,7 +85,7 @@ public class FightSystem extends JavaPlugin { String arenaMap = System.getProperty("arenaMap"); if (arenaMap != null) { - WorldIdentifier.set("fight/" + Core.getVersion() + "/" + arenaMap); + WorldIdentifier.set("arena/" + Core.getVersion() + "/" + arenaMap); } message = new Message("de.steamwar.fightsystem.FightSystem", FightSystem.class.getClassLoader()); diff --git a/MissileWars/src/de/steamwar/misslewars/MissileWars.java b/MissileWars/src/de/steamwar/misslewars/MissileWars.java index 7734cc74..44467069 100644 --- a/MissileWars/src/de/steamwar/misslewars/MissileWars.java +++ b/MissileWars/src/de/steamwar/misslewars/MissileWars.java @@ -19,6 +19,8 @@ package de.steamwar.misslewars; +import de.steamwar.core.Core; +import de.steamwar.core.WorldIdentifier; import de.steamwar.linkage.AbstractLinker; import de.steamwar.linkage.SpigotLinker; import de.steamwar.misslewars.items.CustomItem; @@ -56,6 +58,11 @@ public class MissileWars extends JavaPlugin { redTeam = new MWTeam(Config.TeamRedColor, Config.RedSpawn, Config.TeamRedName, Config.RedPortalZ); blueTeam = new MWTeam(Config.TeamBlueColor, Config.BlueSpawn, Config.TeamBlueName, Config.BluePortalZ); + String arenaMap = System.getProperty("arenaMap"); + if (arenaMap != null) { + WorldIdentifier.set("arena/" + Core.getVersion() + "/" + arenaMap); + } + SpigotLinker spigotLinker = new SpigotLinker(this, null) { @Override protected void linkObject(Object any) { diff --git a/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt b/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt index 5dc3b8b6..f5a3893d 100644 --- a/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt +++ b/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt @@ -47,7 +47,7 @@ class TNTLeague : JavaPlugin() { val arenaMap = System.getProperty("arenaMap") if (arenaMap != null) { - WorldIdentifier.set("fight/" + Core.getVersion() + "/" + arenaMap) + WorldIdentifier.set("arena/" + Core.getVersion() + "/" + arenaMap) } ReadyCommand.register() diff --git a/TowerRun/src/de/steamwar/towerrun/TowerRun.java b/TowerRun/src/de/steamwar/towerrun/TowerRun.java index 30dd4046..f75a6747 100644 --- a/TowerRun/src/de/steamwar/towerrun/TowerRun.java +++ b/TowerRun/src/de/steamwar/towerrun/TowerRun.java @@ -60,7 +60,7 @@ public class TowerRun extends JavaPlugin { String arenaMap = System.getProperty("arenaMap"); if (arenaMap != null) { - WorldIdentifier.set("fight/" + Core.getVersion() + "/" + arenaMap); + WorldIdentifier.set("arena/" + Core.getVersion() + "/" + arenaMap); } SpigotLinker spigotLinker = new SpigotLinker(this, message) { From ded46c28a6aed920a79930450d3b645cabf85990 Mon Sep 17 00:00:00 2001 From: Jakob Schulz <55949993+Lordikak@users.noreply.github.com> Date: Sat, 31 Oct 2026 22:50:54 +0100 Subject: [PATCH 39/46] change arena map identifier to not use gamemode --- VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java b/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java index 44932fdf..4070ea6f 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java +++ b/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java @@ -91,7 +91,7 @@ public class ServerStarter { gameMode = mode.configFile.getName().replace(".yml", ""); directory = new File(SERVER_PATH, mode.Server.Folder); arguments.put("config", mode.configFile.getName()); - arguments.put("arenaMap", gameMode + "/" + map); + arguments.put("arenaMap", map); tempWorld(SERVER_PATH + mode.Server.Folder + "/arenas/" + map); startCondition = () -> { if (playersToSend.stream().anyMatch(player -> Subserver.isArena(Subserver.getSubserver(player)))) { From e22317f1fa9c267329231b631c42045bb27151d2 Mon Sep 17 00:00:00 2001 From: Jakob Schulz <55949993+Lordikak@users.noreply.github.com> Date: Sat, 31 Oct 2026 23:02:23 +0100 Subject: [PATCH 40/46] optimize imports --- .../src/de/steamwar/fightsystem/FightSystem.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java index 2e6f83e9..66cc8fff 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java @@ -23,7 +23,6 @@ import com.comphenix.tinyprotocol.TinyProtocol; import de.steamwar.core.Core; import de.steamwar.core.WorldIdentifier; import de.steamwar.core.worldedit.SteamEdit; -import de.steamwar.core.worldedit.WorldEditRendererCUIEditor; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.listener.ClickAnalyzer; @@ -50,9 +49,6 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerPickupArrowEvent; import org.bukkit.plugin.java.JavaPlugin; -import java.nio.charset.StandardCharsets; -import java.util.HexFormat; - public class FightSystem extends JavaPlugin { @Getter From d4f4be78bbbfd86e09d1dfdd89eb705c9883a181 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 23:24:00 +0100 Subject: [PATCH 41/46] Add /whois click on names and /chat - Clicking a player name or @mention suggests /whois ; the hover explains the click - /chat writes once into a channel and replaces /chat msg - Clicking the Global or Local/Fight icon suggests /chat, for foreign arenas /chat --- .../steamwar/messages/BungeeCore.properties | 4 +-- .../messages/BungeeCore_de.properties | 4 +-- .../velocitycore/chat/ChatChannel.java | 2 +- .../velocitycore/chat/ChatFormat.java | 27 +++++++++++-------- .../velocitycore/chat/DirectChannel.java | 4 +-- .../velocitycore/chat/GlobalChannel.java | 5 ---- .../velocitycore/chat/LocalChannel.java | 5 ++-- .../velocitycore/commands/ChatCommand.java | 10 +++---- 8 files changed, 31 insertions(+), 30 deletions(-) diff --git a/VelocityCore/src/de/steamwar/messages/BungeeCore.properties b/VelocityCore/src/de/steamwar/messages/BungeeCore.properties index b3d563e4..eef7b3de 100644 --- a/VelocityCore/src/de/steamwar/messages/BungeeCore.properties +++ b/VelocityCore/src/de/steamwar/messages/BungeeCore.properties @@ -594,6 +594,7 @@ CHAT_FIGHT_TEAM = §8» §f{0} CHAT_ICON = {0}{1} CHAT_HOVER_TEAM = §7Team: §e{0}\n§7Prefix: §r{1}{2} CHAT_HOVER_TEAM_CLICK = §7Team: §e{0}\n§7Prefix: §r{1}{2}\n§8Click to insert §7/team info +CHAT_HOVER_WHOIS = §8Click to insert §7/whois CHAT_HOVER_LOCAL = {0}\n§7Server: §e{1} CHAT_ICON_GLOBAL = 🌍 CHAT_ICON_LOCAL = ⌂ @@ -617,8 +618,7 @@ CHAT_CHANNEL_NOT_SWITCHABLE = §cYou cannot write into this channel by default. CHAT_NO_WRITE = §cYou cannot write into this channel. CHAT_FIGHT_NO_TEAM = §cYou are not in a fight team. GLOBAL_USAGE = §8/§7global §8[§emessage§8] -CHAT_USAGE = §8/§7chat §8[§echannel§8] [§ejoin§8|§eleave§8] -CHAT_MSG_USAGE = §8/§7chat §8[§echannel§8] §7msg §8[§emessage§8] +CHAT_USAGE = §8/§7chat §8[§echannel§8] [§ejoin§8|§eleave§8|§emessage§8] CHAT_ARENAS_USAGE = §8/§7chat §7arenas §8[§ejoin§8|§eleave§8] CHAT_ARENAS_JOINED = §7You now read the chats of all fights. CHAT_ARENAS_LEFT = §7You no longer read the chats of all fights. diff --git a/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties b/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties index ee6ab6a6..d64f4919 100644 --- a/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties +++ b/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties @@ -559,6 +559,7 @@ CHAT_FIGHT_TEAM = §8» §f{0} CHAT_ICON = {0}{1} CHAT_HOVER_TEAM = §7Team: §e{0}\n§7Kürzel: §r{1}{2} CHAT_HOVER_TEAM_CLICK = §7Team: §e{0}\n§7Kürzel: §r{1}{2}\n§8Klicke, um §7/team info §8einzufügen +CHAT_HOVER_WHOIS = §8Klicke, um §7/whois §8einzufügen CHAT_HOVER_LOCAL = {0}\n§7Server: §e{1} CHAT_ICON_GLOBAL = 🌍 CHAT_ICON_LOCAL = ⌂ @@ -582,8 +583,7 @@ CHAT_CHANNEL_NOT_SWITCHABLE = §cIn diesen Kanal kannst du nicht standardmäßig CHAT_NO_WRITE = §cDu kannst nicht in diesen Kanal schreiben. CHAT_FIGHT_NO_TEAM = §cDu bist in keinem Kampfteam. GLOBAL_USAGE = §8/§7global §8[§eNachricht§8] -CHAT_USAGE = §8/§7chat §8[§eKanal§8] [§ejoin§8|§eleave§8] -CHAT_MSG_USAGE = §8/§7chat §8[§eKanal§8] §7msg §8[§eNachricht§8] +CHAT_USAGE = §8/§7chat §8[§eKanal§8] [§ejoin§8|§eleave§8|§eNachricht§8] CHAT_ARENAS_USAGE = §8/§7chat §7arenas §8[§ejoin§8|§eleave§8] CHAT_ARENAS_JOINED = §7Du liest jetzt die Chats aller Fights mit. CHAT_ARENAS_LEFT = §7Du liest die Chats aller Fights nicht mehr mit. diff --git a/VelocityCore/src/de/steamwar/velocitycore/chat/ChatChannel.java b/VelocityCore/src/de/steamwar/velocitycore/chat/ChatChannel.java index bb4cf45e..fce2b1b6 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/chat/ChatChannel.java +++ b/VelocityCore/src/de/steamwar/velocitycore/chat/ChatChannel.java @@ -76,7 +76,7 @@ public abstract class ChatChannel { * @return the text put into the chat input when clicking the channel icon to write a single message, null if there is none */ protected String suggestion(Chatter receiver) { - return null; + return "/chat "; } /** diff --git a/VelocityCore/src/de/steamwar/velocitycore/chat/ChatFormat.java b/VelocityCore/src/de/steamwar/velocitycore/chat/ChatFormat.java index 1921bec8..f335340f 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/chat/ChatFormat.java +++ b/VelocityCore/src/de/steamwar/velocitycore/chat/ChatFormat.java @@ -82,7 +82,7 @@ public class ChatFormat { prefix.getChatPrefix().length() == 0 ? "§f" : prefix.getChatPrefix() + " ", chatColorCode}; - Component senderPart = withTeamHover(receiver, user, receiver.parse(new Message(format + "_SENDER", params))); + Component senderPart = withPlayerInfo(receiver, user, receiver.parse(new Message(format + "_SENDER", params))); return Component.text().append(teamPrefix).append(senderPart).append(receiver.parse(new Message(format, params))).build(); } @@ -104,22 +104,27 @@ public class ChatFormat { nameColor = coloredTeamName.length() >= 2 && coloredTeamName.charAt(0) == '§' ? coloredTeamName.substring(0, 2) : ""; } - line.append(withTeamHover(receiver, sender.user(), receiver.parse(new Message(senderFormat, nameColor, sender.user())))); + line.append(withPlayerInfo(receiver, sender.user(), receiver.parse(new Message(senderFormat, nameColor, sender.user())))); line.append(receiver.parse(new Message(format, message))); return line.build(); } /** - * Shows the team of the user when hovering the component, the SteamWar team for server team members using the SW prefix. + * Makes a player name suggest {@code /whois} on click and show the team of the player on hover, + * the SteamWar team for server team members using the SW prefix. The hover also explains the click. */ - public static Component withTeamHover(Chatter receiver, SteamwarUser user, Component component) { - // Players of the public team (id 0) get no hover - if (user.getTeam() == 0) return component; - if (useSwPrefix(user)) { - return component.hoverEvent(HoverEvent.showText(receiver.parse(new Message("CHAT_HOVER_TEAM", "SteamWar", "§e", "S§8W")))); + public static Component withPlayerInfo(Chatter receiver, SteamwarUser user, Component component) { + Component hover = receiver.parse(new Message("CHAT_HOVER_WHOIS")); + // Players of the public team (id 0) show no team + if (useSwPrefix(user) && user.getTeam() != 0) { + hover = Component.text().append(receiver.parse(new Message("CHAT_HOVER_TEAM", "SteamWar", "§e", "S§8W"))).append(Component.newline()).append(hover).build(); + } else if (user.getTeam() != 0) { + hover = Component.text().append(teamHover(receiver, Team.byId(user.getTeam()))).append(Component.newline()).append(hover).build(); } - return component.hoverEvent(HoverEvent.showText(teamHover(receiver, Team.byId(user.getTeam())))); + return component + .hoverEvent(HoverEvent.showText(hover)) + .clickEvent(ClickEvent.suggestCommand("/whois " + user.getUserName())); } /** @@ -152,14 +157,14 @@ public class ChatFormat { } /** - * Shows the team of every mentioned player ({@code @name}) on hover, like at the sender name. + * Gives every mentioned player ({@code @name}) the same hover and click as the sender name. */ public static Component withMentionHovers(Chatter receiver, Component component) { return component.replaceText(TextReplacementConfig.builder() .match(MENTION) .replacement((match, builder) -> { SteamwarUser user = SteamwarUser.get(match.group(1)); - return user == null ? builder : withTeamHover(receiver, user, builder.build()); + return user == null ? builder : withPlayerInfo(receiver, user, builder.build()); }) .build()); } diff --git a/VelocityCore/src/de/steamwar/velocitycore/chat/DirectChannel.java b/VelocityCore/src/de/steamwar/velocitycore/chat/DirectChannel.java index 5b89b8ba..5f31e756 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/chat/DirectChannel.java +++ b/VelocityCore/src/de/steamwar/velocitycore/chat/DirectChannel.java @@ -102,9 +102,9 @@ public class DirectChannel extends ChatChannel { protected Component format(Chatter receiver, Chatter sender, String message) { SteamwarUser partner = SteamwarUser.get(partner(sender.user().getUUID())); return Component.text() - .append(ChatFormat.withTeamHover(receiver, sender.user(), receiver.parse(new Message("CHAT_MSG_SENDER", sender.user())))) + .append(ChatFormat.withPlayerInfo(receiver, sender.user(), receiver.parse(new Message("CHAT_MSG_SENDER", sender.user())))) .append(receiver.parse(new Message("CHAT_MSG_SEPARATOR"))) - .append(ChatFormat.withTeamHover(receiver, partner, receiver.parse(new Message("CHAT_MSG_RECEIVER", partner)))) + .append(ChatFormat.withPlayerInfo(receiver, partner, receiver.parse(new Message("CHAT_MSG_RECEIVER", partner)))) .append(Component.text(" ")) .append(receiver.parse(new Message("CHAT_MSG", ChatFormat.highlightMentions(message, "§7", receiver)))) .build(); diff --git a/VelocityCore/src/de/steamwar/velocitycore/chat/GlobalChannel.java b/VelocityCore/src/de/steamwar/velocitycore/chat/GlobalChannel.java index 67730da7..cc2219cd 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/chat/GlobalChannel.java +++ b/VelocityCore/src/de/steamwar/velocitycore/chat/GlobalChannel.java @@ -44,11 +44,6 @@ public class GlobalChannel extends ChatChannel { return new Message("CHAT_ICON_GLOBAL"); } - @Override - protected String suggestion(Chatter receiver) { - return "/global "; - } - @Override public Stream readers() { return Stream.concat(Stream.of(Chatter.console()), Chatter.allPlayers().filter(ChatManager::readsGlobal).map(Chatter::of).filter(chatter -> ChatManager.reads(chatter, persistId()))); diff --git a/VelocityCore/src/de/steamwar/velocitycore/chat/LocalChannel.java b/VelocityCore/src/de/steamwar/velocitycore/chat/LocalChannel.java index e4d6c131..b71d5468 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/chat/LocalChannel.java +++ b/VelocityCore/src/de/steamwar/velocitycore/chat/LocalChannel.java @@ -81,10 +81,11 @@ public class LocalChannel extends ChatChannel { @Override protected String suggestion(Chatter receiver) { + // Moderators reading a foreign arena write into it directly if (isArena() && !ChatManager.onServer(receiver, server)) { - return "/chat " + server.getServerInfo().getName() + " msg "; + return "/chat " + server.getServerInfo().getName() + " "; } - return "/local "; + return super.suggestion(receiver); } @Override diff --git a/VelocityCore/src/de/steamwar/velocitycore/commands/ChatCommand.java b/VelocityCore/src/de/steamwar/velocitycore/commands/ChatCommand.java index 106042f4..fcb3f456 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/commands/ChatCommand.java +++ b/VelocityCore/src/de/steamwar/velocitycore/commands/ChatCommand.java @@ -61,16 +61,16 @@ public class ChatCommand extends SWCommand { } } + @Register + public void write(PlayerChatter sender, ChatChannel channel, @ErrorMessage(value = "CHAT_USAGE", allowEAs = false) String... message) { + channel.send(sender, String.join(" ", message)); + } + @Register(value = "arenas", description = "CHAT_ARENAS_USAGE") public void arenas(@Validator("moderator") PlayerChatter sender, @StaticValue(value = {"leave", "join"}, allowISE = true) boolean join) { ChatManager.setReadAllArenas(sender, join); } - @Register(description = "CHAT_MSG_USAGE") - public void msg(@Validator("moderator") PlayerChatter sender, ChatChannel channel, @StaticValue("msg") String msg, @ErrorMessage(value = "CHAT_MSG_USAGE", allowEAs = false) String... message) { - channel.send(sender, String.join(" ", message)); - } - @Validator(value = "moderator", local = true) public TypeValidator moderatorValidator() { return (sender, value, messageSender) -> !messageSender.send(!value.user().hasPerm(UserPerm.MODERATION), "CHAT_NO_WRITE"); From e550ee73f4bcb2d2f9c2fc4217016854b2d45fac Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sat, 31 Oct 2026 23:42:43 +0100 Subject: [PATCH 42/46] Answer private messages to yourself with an easter egg Messaging yourself gets a random answer from your other self after ten seconds, formatted like a normal private message, instead of "nobody receives your message". --- .../de/steamwar/messages/BungeeCore.properties | 5 +++++ .../de/steamwar/messages/BungeeCore_de.properties | 5 +++++ .../steamwar/velocitycore/chat/DirectChannel.java | 15 +++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/VelocityCore/src/de/steamwar/messages/BungeeCore.properties b/VelocityCore/src/de/steamwar/messages/BungeeCore.properties index eef7b3de..e4ad39d4 100644 --- a/VelocityCore/src/de/steamwar/messages/BungeeCore.properties +++ b/VelocityCore/src/de/steamwar/messages/BungeeCore.properties @@ -583,6 +583,11 @@ CHAT_MSG_SENDER = §e{0} CHAT_MSG_SEPARATOR = §8» CHAT_MSG_RECEIVER = §e{0} CHAT_MSG = §7{0} +CHAT_MSG_SELF_1 = I know. +CHAT_MSG_SELF_2 = Who are you telling? +CHAT_MSG_SELF_3 = Interesting. Tell me more. +CHAT_MSG_SELF_4 = Shh, the others can hear us! +CHAT_MSG_SELF_5 = Finally someone who understands me. CHAT_SPECIAL_ALERT = §e{0}§8»§e{1} §7{2} CHAT_FIGHT_PARTICIPANT_SENDER = {0}{1} CHAT_FIGHT_PARTICIPANT = §8» §7{0} diff --git a/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties b/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties index d64f4919..ffb5ce7a 100644 --- a/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties +++ b/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties @@ -548,6 +548,11 @@ CHAT_MSG_SENDER = §e{0} CHAT_MSG_SEPARATOR = §8» CHAT_MSG_RECEIVER = §e{0} CHAT_MSG = §7{0} +CHAT_MSG_SELF_1 = Ich weiß. +CHAT_MSG_SELF_2 = Wem erzählst du das? +CHAT_MSG_SELF_3 = Interessant. Erzähl mir mehr. +CHAT_MSG_SELF_4 = Psst, die anderen hören uns! +CHAT_MSG_SELF_5 = Endlich jemand, der mich versteht. CHAT_SPECIAL_ALERT = §e{0}§8»§e{1} §7{2} CHAT_FIGHT_PARTICIPANT_SENDER = {0}{1} CHAT_FIGHT_PARTICIPANT = §8» §7{0} diff --git a/VelocityCore/src/de/steamwar/velocitycore/chat/DirectChannel.java b/VelocityCore/src/de/steamwar/velocitycore/chat/DirectChannel.java index 5f31e756..20d3584b 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/chat/DirectChannel.java +++ b/VelocityCore/src/de/steamwar/velocitycore/chat/DirectChannel.java @@ -30,6 +30,8 @@ import de.steamwar.velocitycore.VelocityCore; import net.kyori.adventure.text.Component; import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; import java.util.stream.Stream; import static de.steamwar.persistent.Storage.lastChats; @@ -39,6 +41,8 @@ import static de.steamwar.persistent.Storage.lastChats; */ public class DirectChannel extends ChatChannel { + private static final int SELF_ANSWERS = 5; + private final UUID first; private final UUID second; @@ -118,6 +122,17 @@ public class DirectChannel extends ChatChannel { lastChats.put(senderPlayer, partnerPlayer); lastChats.put(partnerPlayer, senderPlayer); } + + if (first.equals(second)) { + // Easter egg: messaging yourself gets an answer from your other self instead of "nobody receives your message" + Message answer = new Message("CHAT_MSG_SELF_" + (ThreadLocalRandom.current().nextInt(SELF_ANSWERS) + 1)); + VelocityCore.schedule(() -> sender.sendMessage(Component.text() + .append(iconComponent(sender, iconColor())) + .append(Component.text(" ")) + .append(format(sender, sender, sender.parseToLegacy(answer))) + .build())).delay(10, TimeUnit.SECONDS).schedule(); + return true; + } return false; } From 1389f81c7cb04b96159603f5b761e60a3a3bcab7 Mon Sep 17 00:00:00 2001 From: Jakob Schulz <55949993+Lordikak@users.noreply.github.com> Date: Sun, 1 Nov 2026 00:46:13 +0100 Subject: [PATCH 43/46] make world identifier valid by hex encoding them --- .../src/de/steamwar/fightsystem/FightSystem.java | 2 +- MissileWars/src/de/steamwar/misslewars/MissileWars.java | 3 +-- .../src/de/steamwar/core/WorldIdentifier.java | 7 +++++++ TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt | 3 +-- TowerRun/src/de/steamwar/towerrun/TowerRun.java | 3 +-- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java index 66cc8fff..94b45ea4 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java @@ -81,7 +81,7 @@ public class FightSystem extends JavaPlugin { String arenaMap = System.getProperty("arenaMap"); if (arenaMap != null) { - WorldIdentifier.set("arena/" + Core.getVersion() + "/" + arenaMap); + WorldIdentifier.setArena(arenaMap); } message = new Message("de.steamwar.fightsystem.FightSystem", FightSystem.class.getClassLoader()); diff --git a/MissileWars/src/de/steamwar/misslewars/MissileWars.java b/MissileWars/src/de/steamwar/misslewars/MissileWars.java index 44467069..f360f616 100644 --- a/MissileWars/src/de/steamwar/misslewars/MissileWars.java +++ b/MissileWars/src/de/steamwar/misslewars/MissileWars.java @@ -19,7 +19,6 @@ package de.steamwar.misslewars; -import de.steamwar.core.Core; import de.steamwar.core.WorldIdentifier; import de.steamwar.linkage.AbstractLinker; import de.steamwar.linkage.SpigotLinker; @@ -60,7 +59,7 @@ public class MissileWars extends JavaPlugin { String arenaMap = System.getProperty("arenaMap"); if (arenaMap != null) { - WorldIdentifier.set("arena/" + Core.getVersion() + "/" + arenaMap); + WorldIdentifier.setArena(arenaMap); } SpigotLinker spigotLinker = new SpigotLinker(this, null) { diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java index 2fa27b1e..fb02eab9 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java @@ -27,6 +27,9 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.Level; +import java.nio.charset.StandardCharsets; +import java.util.HexFormat; + @Linked public class WorldIdentifier { @@ -36,6 +39,10 @@ public class WorldIdentifier { resourceKey = new ResourceKey<>(new ResourceLocation("minecraft", "dimension"), new ResourceLocation("steamwar", name)); } + public static void setArena(String map) { + set("arena/" + Core.getVersion() + "/" + HexFormat.of().formatHex(map.getBytes(StandardCharsets.UTF_8))); + } + public WorldIdentifier() { TinyProtocol.instance.addFilter(ClientboundLoginPacket.class, (player, packet) -> { if (resourceKey == null) return packet; diff --git a/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt b/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt index f5a3893d..a6954d1e 100644 --- a/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt +++ b/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt @@ -19,7 +19,6 @@ package de.steamwar.tntleague -import de.steamwar.core.Core import de.steamwar.core.WorldIdentifier import de.steamwar.message.Message import de.steamwar.tntleague.command.* @@ -47,7 +46,7 @@ class TNTLeague : JavaPlugin() { val arenaMap = System.getProperty("arenaMap") if (arenaMap != null) { - WorldIdentifier.set("arena/" + Core.getVersion() + "/" + arenaMap) + WorldIdentifier.setArena(arenaMap) } ReadyCommand.register() diff --git a/TowerRun/src/de/steamwar/towerrun/TowerRun.java b/TowerRun/src/de/steamwar/towerrun/TowerRun.java index f75a6747..5519d79a 100644 --- a/TowerRun/src/de/steamwar/towerrun/TowerRun.java +++ b/TowerRun/src/de/steamwar/towerrun/TowerRun.java @@ -20,7 +20,6 @@ package de.steamwar.towerrun; import de.steamwar.command.SWCommand; -import de.steamwar.core.Core; import de.steamwar.core.WorldIdentifier; import de.steamwar.linkage.AbstractLinker; import de.steamwar.linkage.LinkedInstance; @@ -60,7 +59,7 @@ public class TowerRun extends JavaPlugin { String arenaMap = System.getProperty("arenaMap"); if (arenaMap != null) { - WorldIdentifier.set("arena/" + Core.getVersion() + "/" + arenaMap); + WorldIdentifier.setArena(arenaMap); } SpigotLinker spigotLinker = new SpigotLinker(this, message) { From 65958b1fdb5c722e9b05e5c90d039d9229352297 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sun, 1 Nov 2026 07:12:13 +0100 Subject: [PATCH 44/46] Fix WorldIdentifier --- .../src/de/steamwar/core/WorldIdentifier.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java index fb02eab9..a3c575d1 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java @@ -27,8 +27,7 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.Level; -import java.nio.charset.StandardCharsets; -import java.util.HexFormat; +import java.util.Locale; @Linked public class WorldIdentifier { @@ -40,7 +39,7 @@ public class WorldIdentifier { } public static void setArena(String map) { - set("arena/" + Core.getVersion() + "/" + HexFormat.of().formatHex(map.getBytes(StandardCharsets.UTF_8))); + set("arena/" + Core.getVersion() + "/" + map.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9._-]", "")); } public WorldIdentifier() { From b36d74da0e5ae11d94f2ee38e2ca5b3f35316bcc Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sun, 1 Nov 2026 07:35:56 +0100 Subject: [PATCH 45/46] Fix WorldIdentifier --- .../SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java | 4 +--- VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java index a3c575d1..658729af 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java @@ -27,8 +27,6 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.Level; -import java.util.Locale; - @Linked public class WorldIdentifier { @@ -39,7 +37,7 @@ public class WorldIdentifier { } public static void setArena(String map) { - set("arena/" + Core.getVersion() + "/" + map.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9._-]", "")); + set("arena/" + Core.getVersion() + "/" + map); } public WorldIdentifier() { diff --git a/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java b/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java index 371f3e73..696048d5 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java +++ b/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java @@ -91,7 +91,7 @@ public class ServerStarter { gameMode = mode.configFile.getName().replace(".yml", ""); directory = new File(SERVER_PATH, mode.Server.Folder); arguments.put("config", mode.configFile.getName()); - arguments.put("arenaMap", map); + arguments.put("arenaMap", map.toLowerCase(Locale.ROOT).replace("[^a-z0-9._-]", "")); tempWorld(SERVER_PATH + mode.Server.Folder + "/arenas/" + map); startCondition = () -> { if (playersToSend.stream().anyMatch(player -> Subserver.isArena(Subserver.getSubserver(player)))) { From dfec29dac594a4411430db9fb9b1e95cbe37abb7 Mon Sep 17 00:00:00 2001 From: YoyoNow Date: Sun, 1 Nov 2026 07:43:39 +0100 Subject: [PATCH 46/46] Offer fight instead of local in /chat on arenas --- .../steamwar/velocitycore/commands/ChatCommand.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/VelocityCore/src/de/steamwar/velocitycore/commands/ChatCommand.java b/VelocityCore/src/de/steamwar/velocitycore/commands/ChatCommand.java index fcb3f456..e0994512 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/commands/ChatCommand.java +++ b/VelocityCore/src/de/steamwar/velocitycore/commands/ChatCommand.java @@ -30,6 +30,7 @@ import de.steamwar.messages.PlayerChatter; import de.steamwar.sql.UserPerm; import de.steamwar.velocitycore.chat.ChatChannel; import de.steamwar.velocitycore.chat.ChatManager; +import de.steamwar.velocitycore.chat.LocalChannel; import java.util.ArrayList; import java.util.Collection; @@ -84,6 +85,7 @@ public class ChatCommand extends SWCommand { return switch (s.toLowerCase()) { case "global" -> ChatManager.getGlobal(); case "local" -> sender.getPlayer() == null ? null : ChatManager.currentLocal(sender.getPlayer()); + case "fight" -> onArena(sender) ? ChatManager.currentLocal(sender.getPlayer()) : null; case "team", "tc" -> sender.user().getTeam() != 0 ? ChatManager.team(sender.user().getTeam()) : null; case "serverteam", "stc" -> sender.user().hasPerm(UserPerm.TEAM) ? ChatManager.getServerTeam() : null; default -> arena(sender, s); @@ -97,7 +99,8 @@ public class ChatCommand extends SWCommand { @Override public Collection tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) { - List channels = new ArrayList<>(List.of("global", "local")); + // On arenas the local chat is the fight chat + List channels = new ArrayList<>(List.of("global", onArena(sender) ? "fight" : "local")); if (sender.user().getTeam() != 0) channels.add("team"); if (sender.user().hasPerm(UserPerm.TEAM)) channels.add("serverteam"); if (sender.user().hasPerm(UserPerm.MODERATION)) { @@ -108,6 +111,13 @@ public class ChatCommand extends SWCommand { }; } + private static boolean onArena(Chatter sender) { + if (sender.getPlayer() == null) return false; + + LocalChannel local = ChatManager.currentLocal(sender.getPlayer()); + return local != null && local.isArena(); + } + /** * Moderators can address the fight chat of every fight server by its server name. */