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/BauSystem.properties b/BauSystem/BauSystem_Main/src/BauSystem.properties index 08312320..5ddd82ad 100644 --- a/BauSystem/BauSystem_Main/src/BauSystem.properties +++ b/BauSystem/BauSystem_Main/src/BauSystem.properties @@ -35,8 +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_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 @@ -1046,6 +1045,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 b61c1f9a..f58c3502 100644 --- a/BauSystem/BauSystem_Main/src/BauSystem_de.properties +++ b/BauSystem/BauSystem_Main/src/BauSystem_de.properties @@ -35,8 +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_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 @@ -978,6 +977,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/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/hullhider/HullHiderCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullHiderCommand.java new file mode 100644 index 00000000..08669a80 --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullHiderCommand.java @@ -0,0 +1,177 @@ +/* + * 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.HidingRule; +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.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.linkage.LinkedTimer; +import de.steamwar.techhider.ObfuscationConfig; +import net.md_5.bungee.api.ChatMessageType; +import org.bukkit.Bukkit; +import org.bukkit.World; +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 { + + public HullHiderCommand() { + super("hullhider"); + } + + @Linked + // 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 hidingRules = 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()); + + // 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, obfuscation, new Viewers.VisibleByDefault()); + hullHider.addHull(region, hull); + hulls.put(region, new HiddenHull(hull, obfuscation.getObfuscationState())); + hidingRules.put(region, HidingRule.of(region.getBuildArea(), obfuscation)); + } + + 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); + hidingRules.remove(region); + hullHider.removeHull(region); + } else { + hullHider.removeViewer(player, region); + hullHider.resendChunks(player, region); + } + } + + 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); + if (hiddenHull != null && players.contains(player)) { + hiddenHulls.add(hiddenHull); + } + }); + } + + @LinkedTimer(delay = 1, period = 1) + private void onTick() { + if (hidden.isEmpty()) return; + + 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()); + } + } + + 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/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) { 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/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/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/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(); 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/techhider/BauTechHider.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/BauTechHider.java new file mode 100644 index 00000000..99a7f255 --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/BauTechHider.java @@ -0,0 +1,161 @@ +/* + * 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.hullhider.HullHiderCommand; +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 and the hulls of /hullhider per player. + * Entities are never hidden. + */ +@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 + public XrayCommand xrayCommand; + @LinkedInstance + public TechHiderCommand techHiderCommand; + @LinkedInstance + public SpectatorListener spectatorListener; + @LinkedInstance + public HullHiderCommand hullHiderCommand; + + public BauTechHider() { + new TechHider(this); + } + + @Override + public ForPlayer forPlayer(Player p) { + 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 + List rules = new ArrayList<>(); + xrayCommand.addHidingRules(p, rules); + techHiderCommand.addHidingRules(p, rules); + hullHiderCommand.addHidingRules(p, rules); + spectatorListener.addHidingRules(p, rules); + + List hulls = new ArrayList<>(); + hullHiderCommand.addHiddenHulls(p, hulls); + return new BauPlayerAccess(p, rules.toArray(NO_RULES), hulls.toArray(NO_HULLS)); + } + + @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, HiddenHull[] hulls) 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; + } + 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; + } + + @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; + } + + // 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 + 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(); + } + 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/slaves/laufbau/BoundingBoxLoader.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/HiddenHull.java similarity index 68% rename from BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/BoundingBoxLoader.java rename to BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/HiddenHull.java index 899c951c..e2cff0c8 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/BoundingBoxLoader.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/HiddenHull.java @@ -1,7 +1,7 @@ /* * This file is a part of the SteamWar software. * - * Copyright (C) 2025 SteamWar.de-Serverteam + * 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 @@ -17,9 +17,13 @@ * along with this program. If not, see . */ -package de.steamwar.bausystem.features.slaves.laufbau; +package de.steamwar.bausystem.features.techhider; -public interface BoundingBoxLoader { +import de.steamwar.hullhider.Hull; +import net.minecraft.world.level.block.state.BlockState; - void load(); +/** + * 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/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..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 @@ -21,17 +21,17 @@ 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; -import de.steamwar.bausystem.utils.ScoreboardElement; import de.steamwar.command.PreviousArguments; import de.steamwar.command.SWCommand; 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,21 +41,26 @@ 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 -public class TechHiderCommand extends SWCommand implements Listener, ScoreboardElement { +public class TechHiderCommand extends SWCommand implements Listener { public TechHiderCommand() { 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; + @LinkedInstance + public HullHiderCommand hullHiderCommand; + @Register(description = "TECHHIDER_HELP") public void toggleHider(@Validator Player player) { Region region = Region.getRegion(player.getLocation()); @@ -64,26 +69,20 @@ 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; } 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); @@ -128,6 +127,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); @@ -135,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/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/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/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..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 @@ -21,14 +21,15 @@ 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; -import de.steamwar.bausystem.utils.ScoreboardElement; 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,22 +44,27 @@ 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 -public class XrayCommand extends SWCommand implements Listener, ScoreboardElement { +public class XrayCommand extends SWCommand implements Listener { public XrayCommand() { 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; + @LinkedInstance + public HullHiderCommand hullHiderCommand; + @Register(description = "XRAY_HELP") public void toggleHider(@Validator Player player) { Region region = Region.getRegion(player.getLocation()); @@ -67,23 +73,24 @@ 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; } 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); @@ -139,13 +146,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 @@ -160,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); } } 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; diff --git a/CommonCore/Linkage/src/de/steamwar/linkage/AbstractLinker.java b/CommonCore/Linkage/src/de/steamwar/linkage/AbstractLinker.java index 4a2db53d..9959ecef 100644 --- a/CommonCore/Linkage/src/de/steamwar/linkage/AbstractLinker.java +++ b/CommonCore/Linkage/src/de/steamwar/linkage/AbstractLinker.java @@ -22,12 +22,18 @@ 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; import java.util.List; import java.util.Map; @@ -38,6 +44,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 +97,7 @@ public abstract class AbstractLinker { ((Enable) any).enable(); } linkObject(any); + linkFields(any); }); } catch (SecurityException e) { Throwable cause = e.getCause(); @@ -117,6 +125,70 @@ 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) { + 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() { @@ -126,6 +198,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) { @@ -152,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/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 { } 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/CommonCore/SQL/src/de/steamwar/sql/GameModeConfig.java b/CommonCore/SQL/src/de/steamwar/sql/GameModeConfig.java index 7c6d369a..7036d7b8 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/GameModeConfig.java +++ b/CommonCore/SQL/src/de/steamwar/sql/GameModeConfig.java @@ -515,6 +515,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); @@ -535,6 +549,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/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/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/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/FightSystem.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java index c676308e..c4346a1e 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java @@ -21,8 +21,8 @@ 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; import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.listener.ClickAnalyzer; @@ -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; @@ -79,6 +79,11 @@ public class FightSystem extends JavaPlugin { Core.setServerName("Replay"); } + String arenaMap = System.getProperty("arenaMap"); + if (arenaMap != null) { + WorldIdentifier.setArena(arenaMap); + } + message = new Message("de.steamwar.fightsystem.FightSystem", FightSystem.class.getClassLoader()); spigotLinker = new SpigotLinker(this, message) { @@ -111,9 +116,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 +170,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 5527176b..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,11 +56,11 @@ 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: "); - 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/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/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/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(); + } } }); } 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..c50eb60d 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; @@ -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 new file mode 100644 index 00000000..fd961d96 --- /dev/null +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightHullHider.java @@ -0,0 +1,147 @@ +/* + * 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.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; +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 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; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.Objects; +import java.util.function.BiConsumer; + +/** + * Fight specific part of the hull hider: one hull per team schematic region, and who may view which hull. + */ +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(), obfuscation, new Viewers.HiddenByDefault())); + }); + + new StateDependentListener(TechHiderWrapper.ENABLED, FightState.All, new MembershipListener()); + 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; + hullHider.initialize(team); + } + + public void fill(FightTeam team, boolean visible) { + if (!TechHiderWrapper.ENABLED) return; + 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. + private class MembershipListener implements Listener { + @EventHandler(priority = EventPriority.HIGH) + public void onJoin(PlayerJoinEvent e) { + updatePlayer(e.getPlayer()); + } + + @EventHandler + public void onLeave(PlayerQuitEvent e) { + hullHider.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) { + if (!TechHiderWrapper.ENABLED) return; + + FightTeam team = Fight.getPlayerTeam(player); + FightPlayer fp = Fight.getFightPlayer(player); + 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, BlockData changedData) { + hullHider.blockUpdate(block, changedData); + } + + public boolean isBlockHidden(Player player, int x, int y, int z) { + return hullHider.isBlockHidden(player, x, y, z); + } + + private void onTick() { + Recording.iterateOverEntities(Objects::nonNull, hullHider::checkEntity); + hullHider.sendUncoveredBlocks(); + } + + public void updateREntity(REntity e) { + hullHider.updateREntity(e); + } + + public void despawnREntity(REntity e) { + hullHider.despawnREntity(e); + } +} diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Hull.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Hull.java deleted file mode 100644 index 3ac2528d..00000000 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Hull.java +++ /dev/null @@ -1,386 +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.fightsystem.utils; - -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; -import net.minecraft.core.BlockPos; -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.GameMode; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.craftbukkit.block.data.CraftBlockData; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; - -import java.util.*; -import java.util.function.BiConsumer; -import java.util.logging.Level; -import java.util.stream.IntStream; - -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); - } - - @Getter - private final Region region; - private final boolean groundVisible; - private final IntVector[] directions; - - 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 HashSet uncoveredSurface = new HashSet<>(); - - private final HashSet players = new HashSet<>(); - 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(); - 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) { - visibilityDirections.put(direction, new BitSet(region.volume())); - } - } - - public boolean blockPrecise(Player player, int chunkX, int chunkY, int chunkZ) { - return players.contains(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())); - } - - public boolean isLocationHidden(Player player, Location location) { - return players.contains(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); - } - } - } - - public void removePlayer(Player player, boolean activeRemoval) { - if (players.remove(player) && activeRemoval) { - for (Entity entity : entities) { - trackEntity(player, entity); - } - // techhider triggers block change sending - } - } - - 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); - } - } - } else { - if (entities.remove(entity)) { - for (Player player : players) { - trackEntity(player, entity); - } - } - } - } - - public void trackEntity(Player player, Entity entity) { - if (entity instanceof Player) { - TinyProtocol.instance.sendPacket(player, ProtocolWrapper.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.REMOVE, new GameProfile(entity.getUniqueId(), entity.getName()), GameMode.CREATIVE)); - } - - player.showEntity(FightSystem.getPlugin(), entity); - } - - public void untrackEntity(Player player, Entity entity) { - player.hideEntity(FightSystem.getPlugin(), entity); - - if (entity instanceof Player) { - TinyProtocol.instance.sendPacket(player, ProtocolWrapper.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.ADD, new GameProfile(entity.getUniqueId(), entity.getName()), GameMode.CREATIVE)); - } - } - - public void removeEntity(Entity entity) { - entities.remove(entity); - } - - 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))) { - if (rentities.add(entity)) entity.hide(true); - } else { - if (rentities.remove(entity)) entity.hide(false); - } - } - - public void removeREntity(REntity entity) { - rentities.remove(entity); - } - - public void fill(boolean visible) { - visibility.set(0, visibility.size(), visible); - occluding.set(0, occluding.size(), !visible); - uncoveredSurface.clear(); - for (BitSet directionalVisibility : visibilityDirections.values()) { - directionalVisibility.set(0, directionalVisibility.size(), visible); - } - } - - 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) -> { - IntVector block = new IntVector(x, y, z); - if (isOccluding(Config.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()); - } - - public void updateBlockVisibility(Block b, Material changedType) { - 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; - - 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()); - } - } - - uncoveredSurface.addAll(new HashSet<>(uncovered)); - uncoveredSurface.remove(root); - } - - public void sendUncoveredBlocks() { - Map> sectionWise = new HashMap<>(); - - for (IntVector uncovered : uncoveredSurface) { - sectionWise.computeIfAbsent(new IntVector(uncovered.x >> 4, uncovered.y >> 4, uncovered.z >> 4), section -> new ArrayList<>()).add(uncovered); - } - uncoveredSurface.clear(); - - for (Map.Entry> entry : sectionWise.entrySet()) { - Object result; - List changes = entry.getValue(); - 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(); - } - - if (changes.size() > 1) { - IntVector section = changes.get(0); - section = new IntVector(section.getX() >> 4, section.getY() >> 4, section.getZ() >> 4); - int xOffset = 16 * section.getX(); - int yOffset = 16 * section.getY(); - int zOffset = 16 * section.getZ(); - - short[] pos = new short[changes.size()]; - for (int i = 0; i < changes.size(); i++) { - IntVector change = changes.get(i); - - pos[i] = (short) (((change.getX() - xOffset) << 8) + ((change.getZ() - zOffset) << 4) + (change.getY() - yOffset)); - } - - result = new ClientboundSectionBlocksUpdatePacket(SectionPos.of(section.getX(), section.getY(), section.getZ()), new Short2ObjectArrayMap<>(pos, blockdata, blockdata.length)); - } else { - IntVector pos = changes.get(0); - 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)); - } - } - - private void forEachBorder(BiConsumer f) { - for (int x = region.getMinX(); x < region.getMaxX(); x++) { - for (int z = region.getMinZ(); z < region.getMaxZ(); z++) { - if (groundVisible) { - f.accept(new IntVector(x, region.getMinY(), z), new IntVector(0, 1, 0)); - } - f.accept(new IntVector(x, region.getMaxY() - 1, z), new IntVector(0, -1, 0)); - } - } - - for (int x = region.getMinX(); x < region.getMaxX(); x++) { - for (int y = region.getMinY(); y < region.getMaxY(); y++) { - f.accept(new IntVector(x, y, region.getMinZ()), new IntVector(0, 0, 1)); - f.accept(new IntVector(x, y, region.getMaxZ() - 1), new IntVector(0, 0, -1)); - } - } - - for (int z = region.getMinZ(); z < region.getMaxZ(); z++) { - for (int y = region.getMinY(); y < region.getMaxY(); y++) { - f.accept(new IntVector(region.getMinX(), y, z), new IntVector(1, 0, 0)); - f.accept(new IntVector(region.getMaxX() - 1, y, z), new IntVector(-1, 0, 0)); - } - } - } - - 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 - ); - } - - 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; - - uncoverBlocks(uncovered, block.add(direction), direction, directionalVisibility, branchDirections); - for (IntVector branchDirection : branchDirections) { - uncoverBlocks(uncovered, block.add(branchDirection), branchDirection, visibilityDirections.get(branchDirection), NO_BRANCHES); - } - } - - - @Getter - @AllArgsConstructor - public static class IntVector { - private final int x; - private final int y; - private final int z; - - public IntVector(Location location) { - this.x = location.getBlockX(); - this.y = location.getBlockY(); - this.z = location.getBlockZ(); - } - - public boolean notInRegion(Region region) { - return !region.inRegion(x, y, z); - } - - public int toId(Region region) { - return ((y - region.getMinY()) * region.getSizeZ() + (z - region.getMinZ())) * region.getSizeX() + (x - region.getMinX()); - } - - public IntVector add(int x, int y, int z) { - return new IntVector(this.x + x, this.y + y, this.z + z); - } - - public IntVector add(IntVector v) { - return add(v.x, v.y, v.z); - } - - public int sqLength() { - return x * x + y * y + z * z; - } - - @Override - public int hashCode() { - return y << 24 ^ x << 12 ^ z; - } - - @Override - public boolean equals(Object o) { - if (o == null || this.getClass() != o.getClass()) { - return false; - } - - IntVector v = (IntVector) o; - return x == v.x && y == v.y && z == v.z; - } - - @Override - public String toString() { - return x + "," + y + "," + z; - } - } - - private static class NullList extends AbstractList { - @Override - public void add(int index, E element) { - // Straight to /dev/null! - } - - @Override - public E get(int index) { - return null; - } - - @Override - public int size() { - return 0; - } - } -} diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/HullHider.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/HullHider.java deleted file mode 100644 index 58bfce48..00000000 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/HullHider.java +++ /dev/null @@ -1,171 +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.fightsystem.utils; - -import de.steamwar.entity.REntity; -import de.steamwar.fightsystem.Config; -import de.steamwar.fightsystem.fight.Fight; -import de.steamwar.fightsystem.fight.FightPlayer; -import de.steamwar.fightsystem.fight.FightTeam; -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 org.bukkit.Material; -import org.bukkit.block.Block; -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; - -public class HullHider implements Listener { - - @Getter - private final Map hullMap = new HashMap<>(); - private final Hull[] hulls; - - 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]); - - - new StateDependentListener(TechHiderWrapper.ENABLED, FightState.Schem, this); - new StateDependentTask(TechHiderWrapper.ENABLED, FightState.Schem, this::onTick, 0, 1); - } - - public void initialize(FightTeam team) { - if (!TechHiderWrapper.ENABLED) return; - hullMap.get(team).initialize(); - } - - public void fill(FightTeam team, boolean visible) { - if (!TechHiderWrapper.ENABLED) return; - hullMap.get(team).fill(visible); - } - - @EventHandler(priority = EventPriority.HIGH) - public void onJoin(PlayerJoinEvent e) { - updatePlayer(e.getPlayer()); - } - - @EventHandler - public void onLeave(PlayerQuitEvent e) { - removePlayer(e.getPlayer(), false); - } - - public void updatePlayer(Player player) { - if (!TechHiderWrapper.ENABLED) return; - - 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().removePlayer(player, true); - } else { - hull.getValue().addPlayer(player); - } - } - } - - private void removePlayer(Player player, boolean activeRemoval) { - for (Hull hull : hulls) { - hull.removePlayer(player, activeRemoval); - } - } - - - @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); - } - } - - 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()); - } - } - - 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()); - } - } - - 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/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Region.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/Region.java index 7d75e02a..d5de73be 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,8 @@ package de.steamwar.fightsystem.utils; -import de.steamwar.techhider.ProtocolUtils; +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 7c4c9d77..534e7cee 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java @@ -25,46 +25,40 @@ 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; 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.Collection; -import java.util.Objects; -import java.util.Set; +import java.util.Arrays; import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; -import java.util.stream.Stream; public class TechHiderWrapper extends StateDependent implements Listener { public static final boolean ENABLED = !Config.OnlyPublicSchematics && !Config.test() && Config.GameModeConfig.Techhider.Active; - @Getter - private final ConcurrentHashMap hiddenRegion = new ConcurrentHashMap<>(); - private final HullHider hullHider; + private static final Region[] NO_REGIONS = new Region[0]; - public TechHiderWrapper(HullHider hullHider) { + @Getter + private final ConcurrentHashMap hiddenRegion = new ConcurrentHashMap<>(); + private final FightHullHider hullHider; + + public TechHiderWrapper(FightHullHider hullHider) { super(ENABLED, FightState.All); this.hullHider = hullHider; @@ -75,64 +69,91 @@ public class TechHiderWrapper extends StateDependent implements Listener { @Override public void enable() { - Set blockStatesToObfuscate = getBlockStatesToHideFromMaterials(Config.GameModeConfig.Techhider.HiddenBlocks); + new TechHider(newProvider(hullHider.getObfuscation())); + } - 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), new AccessPrivilegeProvider() { + private AccessPrivilegeProvider newProvider(ObfuscationConfig obfuscation) { + 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, getHiddenRegions(p), obfuscation); } @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[] hiddenRegions; + private final ObfuscationConfig obfuscation; + + private FightPlayerAccess(Player player, Region[] hiddenRegions, ObfuscationConfig obfuscation) { + this.player = player; + this.hiddenRegions = hiddenRegions; + this.obfuscation = obfuscation; + } + + 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); + } + + @Override + public boolean canAccessBlock(int blockX, int blockY, int blockZ, Block 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) || !obfuscation.hidesBlockState(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 !isHidden(blockX, blockY, blockZ) || !obfuscation.hidesBlockEntity(type); + } + + @Override + public BlockState getObfuscation(int blockX, int blockY, int blockZ) { + return obfuscation.getObfuscationState(); + } } @Override @@ -142,19 +163,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)); } }); } @@ -175,37 +196,18 @@ 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; - FightTeam team = Fight.getPlayerTeam(player); - if (team == null) return Config.ArenaRegion; - - if (team.canPlayerEntern(player)) return Region.EMPTY; - - return Fight.getOpposite(team).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()); + // Single lookup: runs on netty threads, the player can leave the team at any time + FightPlayer fightPlayer = Fight.getFightPlayer(player); + 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 NO_REGIONS; + + return new Region[]{Fight.getOpposite(fightPlayer.getTeam()).getExtendRegion()}; } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/converter/LaufbauConfigV1ToV2Converter.java b/HiderCore/build.gradle.kts similarity index 54% rename from BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/converter/LaufbauConfigV1ToV2Converter.java rename to HiderCore/build.gradle.kts index 6e1c7aac..a0b27617 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/laufbau/converter/LaufbauConfigV1ToV2Converter.java +++ b/HiderCore/build.gradle.kts @@ -17,24 +17,27 @@ * 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; - } +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) + compileOnly(libs.authlib) + compileOnly(libs.datafixer) +} + +widener { + fromCatalog(libs.nms) } diff --git a/HiderCore/src/de/steamwar/hullhider/Hull.java b/HiderCore/src/de/steamwar/hullhider/Hull.java new file mode 100644 index 00000000..02f8c56c --- /dev/null +++ b/HiderCore/src/de/steamwar/hullhider/Hull.java @@ -0,0 +1,628 @@ +/* + * 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.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 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.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; +import org.bukkit.Chunk; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.CraftWorld; +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.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 ObfuscationConfig obfuscation; + private final IntVector[] directions; + + 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<>(); // 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; + 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 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, ObfuscationConfig obfuscation, Viewers viewers) { + this.plugin = plugin; + this.world = world; + this.region = region; + this.groundVisible = groundVisible; + this.obfuscation = obfuscation; + this.viewers = viewers; + this.fullFaces = new byte[region.volume()]; + this.visibility = new BitSet(region.volume()); + + directions = IntStream.range(0, 27) + .mapToObj(v -> new IntVector(v % 3 - 1, (v / 3) % 3 - 1, v / 9 - 1)) + .toArray(IntVector[]::new); + + 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); + } + stepsBeside = computeStepsBeside(); + } + + /** + * @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 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]; + } + + // 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; + + 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)) { + faces |= face(direction.getStepX(), direction.getStepY(), direction.getStepZ()); + } + } + return faces; + } + + 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)); + } + + public boolean isViewer(Player player) { + return player != null && viewers.isViewer(player); + } + + /** + * Adds the player to the {@link Viewers}, which decide whether the player may see the hull contents afterwards. + */ + public void addViewer(Player player) { + 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.removeViewer(player); + } + + public void checkEntity(Entity entity) { + Location location = entity.getLocation(); + 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); + } + } + } else { + if (entities.remove(entity)) { + for (Player player : Bukkit.getOnlinePlayers()) { + if (!isViewer(player) && !player.equals(entity)) trackEntity(player, entity); + } + } + } + } + + public void trackEntity(Player player, Entity entity) { + if (entity instanceof Player) { + TinyProtocol.instance.sendPacket(player, ProtocolWrapper.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.REMOVE, new GameProfile(entity.getUniqueId(), entity.getName()), GameMode.CREATIVE)); + } + + player.showEntity(plugin, entity); + } + + public void untrackEntity(Player player, Entity 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)); + } + } + + public void removeEntity(Entity entity) { + entities.remove(entity); + } + + public void checkREntity(REntity entity) { + 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); + } + } + + public void removeREntity(REntity entity) { + 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); + Arrays.fill(fullFaces, (byte) (visible ? 0 : ALL_FACES)); + uncoveredSurface.clear(); + for (BitSet directionalVisibility : visibilityDirections.values()) { + directionalVisibility.set(0, directionalVisibility.size(), visible); + } + } + + public void initialize() { + visibility.clear(); + uncoveredSurface.clear(); + for (BitSet directionalVisibility : visibilityDirections.values()) { + directionalVisibility.clear(); + } + + long start = System.currentTimeMillis(); + 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; + + int id = root.toId(region); + // 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<>(); + // 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()); + } + } + // Rays that could not get into the block or past it + uncoverBeside(uncovered, root); + forEachBorderStart(root, direction -> startRay(uncovered, root, direction)); + + 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. + */ + private void uncoverBeside(List uncovered, IntVector root) { + 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; + + 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); + } + + 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<>(); + + for (IntVector uncovered : uncoveredSurface) { + sectionWise.computeIfAbsent(new IntVector(uncovered.x >> 4, uncovered.y >> 4, uncovered.z >> 4), section -> new ArrayList<>()).add(uncovered); + } + uncoveredSurface.clear(); + + for (Map.Entry> entry : sectionWise.entrySet()) { + Object result; + List changes = entry.getValue(); + Object[] blockdata = new Object[changes.size()]; + for (int i = 0; i < blockdata.length; i++) { + IntVector change = changes.get(i); + blockdata[i] = ((CraftBlockData) world.getBlockData(change.getX(), change.getY(), change.getZ())).getState(); + } + + if (changes.size() > 1) { + IntVector section = changes.get(0); + section = new IntVector(section.getX() >> 4, section.getY() >> 4, section.getZ() >> 4); + int xOffset = 16 * section.getX(); + int yOffset = 16 * section.getY(); + int zOffset = 16 * section.getZ(); + + short[] pos = new short[changes.size()]; + for (int i = 0; i < changes.size(); i++) { + IntVector change = changes.get(i); + + pos[i] = (short) (((change.getX() - xOffset) << 8) + ((change.getZ() - zOffset) << 4) + (change.getY() - yOffset)); + } + + result = new ClientboundSectionBlocksUpdatePacket(SectionPos.of(section.getX(), section.getY(), section.getZ()), new Short2ObjectArrayMap<>(pos, blockdata, blockdata.length)); + } else { + IntVector pos = changes.get(0); + result = new ClientboundBlockUpdatePacket(new BlockPos(pos.getX(), pos.getY(), pos.getZ()), (BlockState) blockdata[0]); + } + Object packet = result; + Bukkit.getOnlinePlayers().stream().filter(player -> !isViewer(player)).forEach(player -> TinyProtocol.instance.sendPacket(player, packet)); + } + } + + // 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++) { + if (groundVisible) { + f.accept(new IntVector(x, region.getMinY(), z), new IntVector(0, 1, 0)); + } + f.accept(new IntVector(x, region.getMaxY() - 1, z), new IntVector(0, -1, 0)); + } + } + + for (int x = region.getMinX(); x < region.getMaxX(); x++) { + for (int y = region.getMinY(); y < region.getMaxY(); y++) { + f.accept(new IntVector(x, y, region.getMinZ()), new IntVector(0, 0, 1)); + f.accept(new IntVector(x, y, region.getMaxZ() - 1), new IntVector(0, 0, -1)); + } + } + + for (int z = region.getMinZ(); z < region.getMaxZ(); z++) { + for (int y = region.getMinY(); y < region.getMaxY(); y++) { + f.accept(new IntVector(region.getMinX(), y, z), new IntVector(1, 0, 0)); + f.accept(new IntVector(region.getMaxX() - 1, y, z), new IntVector(-1, 0, 0)); + } + } + } + + /** + * 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); + reveal(uncovered, block, id); + + uncoverNext(uncovered, block, direction, directionalVisibility, branchDirections); + for (IntVector branchDirection : branchDirections) { + 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))) return; + + 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 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 canMove(int x, int y, int z, int dx, int dy, int dz, boolean enter) { + if (dx == 0 && dy == 0 && dz == 0) return true; + + // 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)); + } + + /** + * @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)); + } + + 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; + } + + + @Getter + @AllArgsConstructor + public static class IntVector { + private final int x; + private final int y; + private final int z; + + public IntVector(Location location) { + this.x = location.getBlockX(); + this.y = location.getBlockY(); + this.z = location.getBlockZ(); + } + + public boolean notInRegion(HullRegion region) { + return !region.contains(x, y, z); + } + + public int toId(HullRegion region) { + return region.toIndex(x, y, z); + } + + public IntVector add(int x, int y, int z) { + return new IntVector(this.x + x, this.y + y, this.z + z); + } + + public IntVector add(IntVector v) { + return add(v.x, v.y, v.z); + } + + public int sqLength() { + return x * x + y * y + z * z; + } + + @Override + public int hashCode() { + return y << 24 ^ x << 12 ^ z; + } + + @Override + public boolean equals(Object o) { + if (o == null || this.getClass() != o.getClass()) { + return false; + } + + IntVector v = (IntVector) o; + return x == v.x && y == v.y && z == v.z; + } + + @Override + public String toString() { + return x + "," + y + "," + z; + } + } + + private static class NullList extends AbstractList { + @Override + public void add(int index, E element) { + // Straight to /dev/null! + } + + @Override + public E get(int index) { + return null; + } + + @Override + public int size() { + return 0; + } + } +} diff --git a/HiderCore/src/de/steamwar/hullhider/HullHider.java b/HiderCore/src/de/steamwar/hullhider/HullHider.java new file mode 100644 index 00000000..c2a5d1b7 --- /dev/null +++ b/HiderCore/src/de/steamwar/hullhider/HullHider.java @@ -0,0 +1,211 @@ +/* + * 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.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; +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 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. + */ + 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); + } + + public void forEach(BiConsumer consumer) { + hullMap.forEach(consumer); + } + + public void initialize(K key) { + 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) { + hullMap.get(key).fill(visible); + } + + public void fillAll(boolean visible) { + for (Hull hull : hulls) { + hull.fill(visible); + } + } + + /** + * @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) { + 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.getChangedBlockData()); + } + } + + public void blockUpdate(Block block, BlockData changedData) { + for (Hull hull : hulls) { + hull.updateBlockVisibility(block, changedData); + } + } + + @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) { + if (!hideEntities) return; + + 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) { + if (!hideEntities) return; + + 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) { + 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/hullhider/HullRegion.java b/HiderCore/src/de/steamwar/hullhider/HullRegion.java new file mode 100644 index 00000000..7b5d30ab --- /dev/null +++ b/HiderCore/src/de/steamwar/hullhider/HullRegion.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.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()); + } + + 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 + */ + 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 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; + } +} 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()); + } + } +} diff --git a/HiderCore/src/de/steamwar/techhider/AccessPrivilegeProvider.java b/HiderCore/src/de/steamwar/techhider/AccessPrivilegeProvider.java new file mode 100644 index 00000000..ff39a9fe --- /dev/null +++ b/HiderCore/src/de/steamwar/techhider/AccessPrivilegeProvider.java @@ -0,0 +1,69 @@ +/* + * 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 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.entity.Player; + +public interface AccessPrivilegeProvider { + /** + * 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(); + + /** + * @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); + + boolean canAccessBlockState(int blockX, int blockY, int blockZ, BlockState blockState); + + 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); + + /** + * @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/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java b/HiderCore/src/de/steamwar/techhider/ChunkHider.java similarity index 76% rename from SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java rename to HiderCore/src/de/steamwar/techhider/ChunkHider.java index bd21f357..ab4eb45e 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java +++ b/HiderCore/src/de/steamwar/techhider/ChunkHider.java @@ -19,8 +19,11 @@ 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; +import net.minecraft.nbt.CompoundTag; import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; import net.minecraft.util.SimpleBitStorage; @@ -29,6 +32,8 @@ 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; @@ -43,14 +48,6 @@ public class ChunkHider { private final int BLOCKS_PER_SECTION = 4096; private final int BIOMES_PER_SECTION = 64; - private final int blockIdUsedForHiding; - private final AccessPrivilegeProvider accessPrivilegeProvider; - - public ChunkHider(Block blockUsedForObfuscation, AccessPrivilegeProvider accessPrivilegeProvider) { - blockIdUsedForHiding = Block.BLOCK_STATE_REGISTRY.getId(blockUsedForObfuscation.defaultBlockState()); - this.accessPrivilegeProvider = accessPrivilegeProvider; - } - private int getLongsRequiredToEncodeEntries(int bitsPerEntry, int entryCount) { int entriesPerLong = Long.SIZE / bitsPerEntry; int dataLengthAsLongCount = (entryCount + entriesPerLong - 1) / entriesPerLong; @@ -69,7 +66,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(); @@ -88,9 +86,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); @@ -104,7 +115,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 +160,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 +183,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,10 +205,10 @@ 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; + obfuscatedData[blockDataIndex] = Block.getId(access.getObfuscation(worldX, worldY, worldZ)); } } } @@ -206,6 +217,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); @@ -241,24 +262,33 @@ 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() - .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 accessPrivilegeProvider.isPlayerPrivilegedToAccessPosition(player, worldX, worldY, worldZ) && accessPrivilegeProvider.isPlayerPrivilegedToAccessBlockEntity(player, 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/de/steamwar/techhider/ObfuscationConfig.java b/HiderCore/src/de/steamwar/techhider/ObfuscationConfig.java new file mode 100644 index 00000000..554abaa3 --- /dev/null +++ b/HiderCore/src/de/steamwar/techhider/ObfuscationConfig.java @@ -0,0 +1,115 @@ +/* + * 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 BlockState obfuscationState; + 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.obfuscationState = this.obfuscateWith.defaultBlockState(); + + 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()); + } + } +} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java b/HiderCore/src/de/steamwar/techhider/TechHider.java similarity index 73% rename from SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java rename to HiderCore/src/de/steamwar/techhider/TechHider.java index 73db4439..8bf4cb1d 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java +++ b/HiderCore/src/de/steamwar/techhider/TechHider.java @@ -19,14 +19,14 @@ 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; @@ -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, @@ -54,18 +58,16 @@ 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 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, privilegeProvider); + public TechHider(AccessPrivilegeProvider privilegeProvider) { + this.chunkHider = new ChunkHider(); this.privilegeProvider = privilegeProvider; this.bypassingPackets = new HashSet<>(List.of( @@ -176,11 +178,17 @@ 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 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) @@ -193,16 +201,16 @@ 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. - 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. @@ -234,43 +242,41 @@ 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)); - // 7.1.76 Remove Entities: entity id visibility side channel. - processors.put(ClientboundRemoveEntitiesPacket.class, (p, packet) -> this.processRemoveEntitiesPacket(p, (ClientboundRemoveEntitiesPacket) packet)); + processors.put(ClientboundRotateHeadPacket.class, (access, packet) -> this.processRotateHeadPacket(access, (ClientboundRotateHeadPacket) 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,26 +296,29 @@ 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; - 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(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 +327,7 @@ public class TechHider { return new ClientboundBundlePacket(processedPackets); } else { - return processPacket(player, rawPacket); + return processPacket(access, rawPacket); } } catch (Throwable t) { t.printStackTrace(); @@ -330,89 +339,103 @@ 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 { + if (loggedUnhandledPackets.add(packet.getClass())) { + Bukkit.getLogger().log(Level.WARNING, "[TechHider] Dropping unhandled packet type " + packet.getClass().getName()); + } 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.canAccessEntitySpawn(packet.getId(), Mth.floor(packet.getX()), Mth.floor(packet.getY()), Mth.floor(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) { - IntList entityIdsToRemove = packet.getEntityIds(); - - IntList filteredEntitiesToRemove = entityIdsToRemove.intStream().filter((id) -> privilegeProvider.isPlayerPrivilegedToAccessEntity(player, 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)) { - return new ClientboundBlockUpdatePacket(blockPos, blockStateUsedForObfuscation); + } else if (access.canAccessPosition(blockX, blockY, blockZ)) { + return new ClientboundBlockUpdatePacket(blockPos, access.getObfuscation(blockX, blockY, blockZ)); } 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,12 +494,12 @@ 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); + filteredStates.add(access.getObfuscation(worldX, worldY, worldZ)); } } @@ -494,43 +517,43 @@ public class TechHider { return new ClientboundSectionBlocksUpdatePacket(sectionPos, ShortSets.unmodifiable(new ShortArraySet(newPos)), newStates); } - private Packet processLevelParticlesPacket(Player player, ClientboundLevelParticlesPacket packet) { - int blockX = (int) packet.getX(); - int blockY = (int) packet.getY(); - int blockZ = (int) packet.getZ(); + private Packet processLevelParticlesPacket(ForPlayer access, ClientboundLevelParticlesPacket packet) { + int blockX = Mth.floor(packet.getX()); + int blockY = Mth.floor(packet.getY()); + int blockZ = Mth.floor(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) { - if (privilegeProvider.isEveryonePrivilegedToAccessAllDataWithinChunk(packet.getX(), packet.getZ())) { + private ClientboundLevelChunkWithLightPacket processChunkWithLight(ForPlayer access, ClientboundLevelChunkWithLightPacket packet) { + if (access.canAccessAllDataWithinChunk(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); }; } } diff --git a/HiderCore/src/hidercore.accesswidener b/HiderCore/src/hidercore.accesswidener new file mode 100644 index 00000000..9a266fac --- /dev/null +++ b/HiderCore/src/hidercore.accesswidener @@ -0,0 +1,28 @@ +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 +accessible method net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo (IILnet/minecraft/world/level/block/entity/BlockEntityType;Lnet/minecraft/nbt/CompoundTag;)V 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/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..ccce02ef 100644 --- a/MissileWars/src/de/steamwar/misslewars/MissileWars.java +++ b/MissileWars/src/de/steamwar/misslewars/MissileWars.java @@ -19,6 +19,7 @@ package de.steamwar.misslewars; +import de.steamwar.core.WorldIdentifier; import de.steamwar.linkage.AbstractLinker; import de.steamwar.linkage.SpigotLinker; import de.steamwar.misslewars.items.CustomItem; @@ -56,6 +57,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.setArena(arenaMap); + } + SpigotLinker spigotLinker = new SpigotLinker(this, null) { @Override protected void linkObject(Object any) { @@ -75,8 +81,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/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/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/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/WorldIdentifier.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java index 2fa27b1e..658729af 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java @@ -36,6 +36,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() + "/" + map); + } + public WorldIdentifier() { TinyProtocol.instance.addFilter(ClientboundLoginPacket.class, (player, packet) -> { if (resourceKey == null) return packet; 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/core/worldedit/WorldEditRenderer.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/worldedit/WorldEditRenderer.java index b66c67ad..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,13 +56,13 @@ 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()) { - 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/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/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/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/AccessPrivilegeProvider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/AccessPrivilegeProvider.java deleted file mode 100644 index b04748c6..00000000 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/AccessPrivilegeProvider.java +++ /dev/null @@ -1,41 +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.techhider; - -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.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); - - boolean isPlayerPrivilegedToPerformAction(Player p); -} 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 9aaa9b40..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.techhider.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 dfa0a5f3..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.techhider.BlockIds; -import de.steamwar.techhider.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 68d0aa82..210a5faf 100644 --- a/SpigotCore/SpigotCore_Main/src/spigotcore.accesswidener +++ b/SpigotCore/SpigotCore_Main/src/spigotcore.accesswidener @@ -33,39 +33,5 @@ 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 -## 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 TechHider -accessible field net/minecraft/network/protocol/game/ClientboundMoveEntityPacket entityId I -## 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; - -# 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/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt b/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt index 588b6864..a6954d1e 100644 --- a/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt +++ b/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt @@ -19,6 +19,7 @@ package de.steamwar.tntleague +import de.steamwar.core.WorldIdentifier import de.steamwar.message.Message import de.steamwar.tntleague.command.* import de.steamwar.tntleague.events.GlobalListener @@ -43,6 +44,11 @@ class TNTLeague : JavaPlugin() { logger.info("TNTLeague enabled") + val arenaMap = System.getProperty("arenaMap") + if (arenaMap != null) { + WorldIdentifier.setArena(arenaMap) + } + ReadyCommand.register() InviteCommand.register() AcceptCommand.register() 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..e2e3a997 100644 --- a/TowerRun/src/de/steamwar/towerrun/TowerRun.java +++ b/TowerRun/src/de/steamwar/towerrun/TowerRun.java @@ -20,6 +20,7 @@ package de.steamwar.towerrun; import de.steamwar.command.SWCommand; +import de.steamwar.core.WorldIdentifier; import de.steamwar.linkage.AbstractLinker; import de.steamwar.linkage.LinkedInstance; import de.steamwar.linkage.SpigotLinker; @@ -56,6 +57,11 @@ public class TowerRun extends JavaPlugin { towerGenerator = new TowerGenerator(WorldConfig.TOWER_GENERATOR_CONFIG); } + String arenaMap = System.getProperty("arenaMap"); + if (arenaMap != null) { + WorldIdentifier.setArena(arenaMap); + } + SpigotLinker spigotLinker = new SpigotLinker(this, message) { @Override protected void linkObject(Object any) { @@ -64,6 +70,7 @@ public class TowerRun extends JavaPlugin { } } }; + try { spigotLinker.link(); } catch (AbstractLinker.LinkException e) { @@ -71,7 +78,5 @@ public class TowerRun extends JavaPlugin { Bukkit.shutdown(); return; } - - Bukkit.getScheduler().runTaskTimer(this, new FightInfoPacketSender(), 20, 20); } } diff --git a/VelocityCore/src/de/steamwar/messages/BungeeCore.properties b/VelocityCore/src/de/steamwar/messages/BungeeCore.properties index 8e47001e..e4ad39d4 100644 --- a/VelocityCore/src/de/steamwar/messages/BungeeCore.properties +++ b/VelocityCore/src/de/steamwar/messages/BungeeCore.properties @@ -573,16 +573,21 @@ 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» 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} @@ -594,6 +599,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 +623,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 6f23220f..ffb5ce7a 100644 --- a/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties +++ b/VelocityCore/src/de/steamwar/messages/BungeeCore_de.properties @@ -538,16 +538,21 @@ 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» 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} @@ -559,6 +564,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 +588,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/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/ServerStarter.java b/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java index eab63787..696048d5 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", 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)))) { diff --git a/VelocityCore/src/de/steamwar/velocitycore/VelocityCore.java b/VelocityCore/src/de/steamwar/velocitycore/VelocityCore.java index 1701a10d..936628f2 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; @@ -132,7 +133,6 @@ public class VelocityCore implements ReloadablePlugin { return tabCompleter.apply(sender, s); } }); - schedule(TabCompletionCache::invalidateOldEntries).repeat(1, TimeUnit.SECONDS).schedule(); initStaticServers(); @@ -164,6 +164,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) { @@ -182,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 { @@ -198,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/chat/ChatChannel.java b/VelocityCore/src/de/steamwar/velocitycore/chat/ChatChannel.java index 4bc9d919..fce2b1b6 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/chat/ChatChannel.java +++ b/VelocityCore/src/de/steamwar/velocitycore/chat/ChatChannel.java @@ -65,11 +65,18 @@ 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 */ protected String suggestion(Chatter receiver) { - return null; + return "/chat "; } /** @@ -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/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..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; @@ -102,9 +106,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(); @@ -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; } 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/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"); diff --git a/VelocityCore/src/de/steamwar/velocitycore/commands/ChatCommand.java b/VelocityCore/src/de/steamwar/velocitycore/commands/ChatCommand.java index 106042f4..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; @@ -61,16 +62,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"); @@ -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. */ 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())) { 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")