flags) {
- for (String flagEntry : flags) {
- flagParser.parseAndSetFlag(region, flagEntry);
- }
- }
-
public boolean isWorldGuardEnabled() {
return worldGuardEnabled;
}
diff --git a/src/main/java/dev/loki/lomines/listener/BlockBreakListener.java b/src/main/java/dev/loki/lomines/listener/block/BlockBreakListener.java
similarity index 92%
rename from src/main/java/dev/loki/lomines/listener/BlockBreakListener.java
rename to src/main/java/dev/loki/lomines/listener/block/BlockBreakListener.java
index fd3b312..bf8a051 100644
--- a/src/main/java/dev/loki/lomines/listener/BlockBreakListener.java
+++ b/src/main/java/dev/loki/lomines/listener/block/BlockBreakListener.java
@@ -1,7 +1,7 @@
-package dev.loki.lomines.listener;
+package dev.loki.lomines.listener.block;
import dev.loki.lomines.LoMinesPlugin;
-import dev.loki.lomines.core.mine.Mine;
+import dev.loki.lomines.core.mine.model.Mine;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
diff --git a/src/main/java/dev/loki/lomines/listener/GroupGuiListener.java b/src/main/java/dev/loki/lomines/listener/gui/GroupGuiListener.java
similarity index 97%
rename from src/main/java/dev/loki/lomines/listener/GroupGuiListener.java
rename to src/main/java/dev/loki/lomines/listener/gui/GroupGuiListener.java
index ca94f44..1668b3d 100644
--- a/src/main/java/dev/loki/lomines/listener/GroupGuiListener.java
+++ b/src/main/java/dev/loki/lomines/listener/gui/GroupGuiListener.java
@@ -1,4 +1,4 @@
-package dev.loki.lomines.listener;
+package dev.loki.lomines.listener.gui;
import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.gui.group.GroupCreateGui;
diff --git a/src/main/java/dev/loki/lomines/listener/MineEditGuiListener.java b/src/main/java/dev/loki/lomines/listener/gui/GuiActionHandler.java
similarity index 51%
rename from src/main/java/dev/loki/lomines/listener/MineEditGuiListener.java
rename to src/main/java/dev/loki/lomines/listener/gui/GuiActionHandler.java
index 9d23a49..80cc2c6 100644
--- a/src/main/java/dev/loki/lomines/listener/MineEditGuiListener.java
+++ b/src/main/java/dev/loki/lomines/listener/gui/GuiActionHandler.java
@@ -1,95 +1,59 @@
-package dev.loki.lomines.listener;
+package dev.loki.lomines.listener.gui;
import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.gui.confirm.ConfirmDeleteGui;
import dev.loki.lomines.gui.confirm.ConfirmDeleteGuiHolder;
-import dev.loki.lomines.gui.mine.edit.blocks.BlockMaterialSelectionGui;
-import dev.loki.lomines.gui.mine.edit.blocks.BlocksGui;
+import dev.loki.lomines.gui.mine.edit.blocks.select.BlockMaterialSelectionGui;
+import dev.loki.lomines.gui.mine.edit.blocks.view.BlocksGui;
import dev.loki.lomines.gui.mine.edit.reset.ResetGui;
import dev.loki.lomines.gui.mine.edit.rewards.RewardsGui;
-import dev.loki.lomines.gui.mine.holder.*;
+import dev.loki.lomines.gui.mine.holder.edit.BlockMaterialSelectionGuiHolder;
+import dev.loki.lomines.gui.mine.holder.edit.BlocksGuiHolder;
+import dev.loki.lomines.gui.mine.holder.edit.reset.ResetGuiHolder;
+import dev.loki.lomines.gui.mine.holder.edit.rewards.RewardsGuiHolder;
+import dev.loki.lomines.gui.mine.holder.main.MineEditGuiHolder;
import dev.loki.lomines.gui.mine.main.MineEditGui;
import org.bukkit.entity.Player;
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.Listener;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
-import org.bukkit.event.inventory.InventoryDragEvent;
-import org.bukkit.inventory.InventoryView;
-/**
- * Handles clicks in all mine editor GUIs.
- * Covers MineEditGui, BlocksGui, ResetGui, RewardsGui, ConfirmDeleteGui, and BlockMaterialSelectionGui.
- */
-public final class MineEditGuiListener implements Listener {
+public final class GuiActionHandler {
private final LoMinesPlugin plugin;
- public MineEditGuiListener(LoMinesPlugin plugin) {
+ public GuiActionHandler(LoMinesPlugin plugin) {
this.plugin = plugin;
}
- @EventHandler
- public void onInventoryClick(InventoryClickEvent event) {
- InventoryView view = event.getView();
-
- // Handle MineEditGui
- if (view.getTopInventory().getHolder() instanceof MineEditGuiHolder holder) {
- handleMineEditGui(event, holder);
+ public void handleMineEditGui(InventoryClickEvent event, MineEditGuiHolder holder) {
+ if (!(event.getWhoClicked() instanceof Player player)) {
return;
}
-
- // Handle BlocksGui
- if (view.getTopInventory().getHolder() instanceof BlocksGuiHolder holder) {
- handleBlocksGui(event, holder);
- return;
- }
-
- // Handle BlockMaterialSelectionGui
- if (view.getTopInventory().getHolder() instanceof BlockMaterialSelectionGuiHolder holder) {
- handleBlockMaterialSelectionGui(event, holder);
- return;
- }
-
- // Handle ResetGui
- if (view.getTopInventory().getHolder() instanceof ResetGuiHolder holder) {
- handleResetGui(event, holder);
- return;
- }
-
- // Handle RewardsGui
- if (view.getTopInventory().getHolder() instanceof RewardsGuiHolder holder) {
- handleRewardsGui(event, holder);
- return;
- }
-
- // Handle ConfirmDeleteGui
- if (view.getTopInventory().getHolder() instanceof ConfirmDeleteGuiHolder holder) {
- handleConfirmDeleteGui(event, holder);
- }
- }
-
- private void handleMineEditGui(InventoryClickEvent event, MineEditGuiHolder holder) {
- if (!(event.getWhoClicked() instanceof Player player)) return;
if (!holder.getPlayerId().equals(player.getUniqueId())) {
event.setCancelled(true);
return;
}
event.setCancelled(true);
int raw = event.getRawSlot();
- if (raw >= event.getView().getTopInventory().getSize()) return;
+ if (raw >= event.getView().getTopInventory().getSize()) {
+ return;
+ }
MineEditGui.handleClick(plugin, player, raw, holder.getMineName());
}
- private void handleBlocksGui(InventoryClickEvent event, BlocksGuiHolder holder) {
- if (!(event.getWhoClicked() instanceof Player player)) return;
+ public void handleBlocksGui(InventoryClickEvent event, BlocksGuiHolder holder) {
+ if (!(event.getWhoClicked() instanceof Player player)) {
+ return;
+ }
if (!holder.getPlayerId().equals(player.getUniqueId())) {
event.setCancelled(true);
return;
}
event.setCancelled(true);
int raw = event.getRawSlot();
- if (raw >= event.getView().getTopInventory().getSize()) return;
+ if (raw >= event.getView().getTopInventory().getSize()) {
+ return;
+ }
ClickType click = event.getClick();
boolean left = click.isLeftClick();
@@ -101,44 +65,56 @@ public final class MineEditGuiListener implements Listener {
BlocksGui.getBlockAtSlot(event.getView().getTopInventory(), raw, plugin, holder.getMineName()));
}
- private void handleBlockMaterialSelectionGui(InventoryClickEvent event, BlockMaterialSelectionGuiHolder holder) {
- if (!(event.getWhoClicked() instanceof Player player)) return;
+ public void handleBlockMaterialSelectionGui(InventoryClickEvent event, BlockMaterialSelectionGuiHolder holder) {
+ if (!(event.getWhoClicked() instanceof Player player)) {
+ return;
+ }
if (!holder.getPlayerId().equals(player.getUniqueId())) {
event.setCancelled(true);
return;
}
event.setCancelled(true);
int raw = event.getRawSlot();
- if (raw >= event.getView().getTopInventory().getSize()) return;
+ if (raw >= event.getView().getTopInventory().getSize()) {
+ return;
+ }
BlockMaterialSelectionGui.handleClick(plugin, player, raw, holder.getMineName(),
holder.getPage(), event.getClick().isLeftClick());
}
- private void handleResetGui(InventoryClickEvent event, ResetGuiHolder holder) {
- if (!(event.getWhoClicked() instanceof Player player)) return;
+ public void handleResetGui(InventoryClickEvent event, ResetGuiHolder holder) {
+ if (!(event.getWhoClicked() instanceof Player player)) {
+ return;
+ }
if (!holder.getPlayerId().equals(player.getUniqueId())) {
event.setCancelled(true);
return;
}
event.setCancelled(true);
int raw = event.getRawSlot();
- if (raw >= event.getView().getTopInventory().getSize()) return;
+ if (raw >= event.getView().getTopInventory().getSize()) {
+ return;
+ }
ClickType click = event.getClick();
ResetGui.handleClick(plugin, player, raw, holder.getMineName(),
click.isLeftClick(), click.isRightClick(), click.isShiftClick());
}
- private void handleRewardsGui(InventoryClickEvent event, RewardsGuiHolder holder) {
- if (!(event.getWhoClicked() instanceof Player player)) return;
+ public void handleRewardsGui(InventoryClickEvent event, RewardsGuiHolder holder) {
+ if (!(event.getWhoClicked() instanceof Player player)) {
+ return;
+ }
if (!holder.getPlayerId().equals(player.getUniqueId())) {
event.setCancelled(true);
return;
}
event.setCancelled(true);
int raw = event.getRawSlot();
- if (raw >= event.getView().getTopInventory().getSize()) return;
+ if (raw >= event.getView().getTopInventory().getSize()) {
+ return;
+ }
ClickType click = event.getClick();
int rewardIndex = raw < 45 ? raw : -1;
@@ -146,28 +122,19 @@ public final class MineEditGuiListener implements Listener {
click.isLeftClick(), click.isRightClick(), rewardIndex);
}
- private void handleConfirmDeleteGui(InventoryClickEvent event, ConfirmDeleteGuiHolder holder) {
- if (!(event.getWhoClicked() instanceof Player player)) return;
+ public void handleConfirmDeleteGui(InventoryClickEvent event, ConfirmDeleteGuiHolder holder) {
+ if (!(event.getWhoClicked() instanceof Player player)) {
+ return;
+ }
if (!holder.getPlayerId().equals(player.getUniqueId())) {
event.setCancelled(true);
return;
}
event.setCancelled(true);
int raw = event.getRawSlot();
- if (raw >= event.getView().getTopInventory().getSize()) return;
+ if (raw >= event.getView().getTopInventory().getSize()) {
+ return;
+ }
ConfirmDeleteGui.handleClick(plugin, player, raw, holder.getMineName());
}
-
- @EventHandler
- public void onInventoryDrag(InventoryDragEvent event) {
- var holder = event.getInventory().getHolder();
- if (holder instanceof MineEditGuiHolder ||
- holder instanceof BlocksGuiHolder ||
- holder instanceof BlockMaterialSelectionGuiHolder ||
- holder instanceof ResetGuiHolder ||
- holder instanceof RewardsGuiHolder ||
- holder instanceof ConfirmDeleteGuiHolder) {
- event.setCancelled(true);
- }
- }
}
diff --git a/src/main/java/dev/loki/lomines/listener/gui/MineEditGuiListener.java b/src/main/java/dev/loki/lomines/listener/gui/MineEditGuiListener.java
new file mode 100644
index 0000000..4ba594d
--- /dev/null
+++ b/src/main/java/dev/loki/lomines/listener/gui/MineEditGuiListener.java
@@ -0,0 +1,70 @@
+package dev.loki.lomines.listener.gui;
+
+import dev.loki.lomines.LoMinesPlugin;
+import dev.loki.lomines.gui.confirm.ConfirmDeleteGuiHolder;
+import dev.loki.lomines.gui.mine.holder.edit.BlockMaterialSelectionGuiHolder;
+import dev.loki.lomines.gui.mine.holder.edit.BlocksGuiHolder;
+import dev.loki.lomines.gui.mine.holder.edit.reset.ResetGuiHolder;
+import dev.loki.lomines.gui.mine.holder.edit.rewards.RewardsGuiHolder;
+import dev.loki.lomines.gui.mine.holder.main.MineEditGuiHolder;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.event.inventory.InventoryDragEvent;
+import org.bukkit.inventory.InventoryView;
+
+public final class MineEditGuiListener implements Listener {
+
+ private final GuiActionHandler actionHandler;
+
+ public MineEditGuiListener(LoMinesPlugin plugin) {
+ this.actionHandler = new GuiActionHandler(plugin);
+ }
+
+ @EventHandler
+ public void onInventoryClick(InventoryClickEvent event) {
+ InventoryView view = event.getView();
+
+ if (view.getTopInventory().getHolder() instanceof MineEditGuiHolder holder) {
+ actionHandler.handleMineEditGui(event, holder);
+ return;
+ }
+
+ if (view.getTopInventory().getHolder() instanceof BlocksGuiHolder holder) {
+ actionHandler.handleBlocksGui(event, holder);
+ return;
+ }
+
+ if (view.getTopInventory().getHolder() instanceof BlockMaterialSelectionGuiHolder holder) {
+ actionHandler.handleBlockMaterialSelectionGui(event, holder);
+ return;
+ }
+
+ if (view.getTopInventory().getHolder() instanceof ResetGuiHolder holder) {
+ actionHandler.handleResetGui(event, holder);
+ return;
+ }
+
+ if (view.getTopInventory().getHolder() instanceof RewardsGuiHolder holder) {
+ actionHandler.handleRewardsGui(event, holder);
+ return;
+ }
+
+ if (view.getTopInventory().getHolder() instanceof ConfirmDeleteGuiHolder holder) {
+ actionHandler.handleConfirmDeleteGui(event, holder);
+ }
+ }
+
+ @EventHandler
+ public void onInventoryDrag(InventoryDragEvent event) {
+ var holder = event.getInventory().getHolder();
+ if (holder instanceof MineEditGuiHolder ||
+ holder instanceof BlocksGuiHolder ||
+ holder instanceof BlockMaterialSelectionGuiHolder ||
+ holder instanceof ResetGuiHolder ||
+ holder instanceof RewardsGuiHolder ||
+ holder instanceof ConfirmDeleteGuiHolder) {
+ event.setCancelled(true);
+ }
+ }
+}
diff --git a/src/main/java/dev/loki/lomines/listener/PlayerInteractListener.java b/src/main/java/dev/loki/lomines/listener/player/PlayerInteractListener.java
similarity index 95%
rename from src/main/java/dev/loki/lomines/listener/PlayerInteractListener.java
rename to src/main/java/dev/loki/lomines/listener/player/PlayerInteractListener.java
index 2c36951..fda2c80 100644
--- a/src/main/java/dev/loki/lomines/listener/PlayerInteractListener.java
+++ b/src/main/java/dev/loki/lomines/listener/player/PlayerInteractListener.java
@@ -1,4 +1,4 @@
-package dev.loki.lomines.listener;
+package dev.loki.lomines.listener.player;
import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.gui.group.GroupCreateGui;
@@ -90,9 +90,7 @@ public final class PlayerInteractListener implements Listener {
// Check if player switched to wand
if (GroupWandItem.isWand(plugin, player.getInventory().getItem(event.getNewSlot()))) {
plugin.getWandParticleService().startShowingParticles(player);
- }
- // Check if player switched away from wand
- else if (GroupWandItem.isWand(plugin, player.getInventory().getItem(event.getPreviousSlot()))) {
+ } else if (GroupWandItem.isWand(plugin, player.getInventory().getItem(event.getPreviousSlot()))) { // Check if player switched away from wand
plugin.getWandParticleService().stopShowingParticles(player.getUniqueId());
}
}
diff --git a/src/main/java/dev/loki/lomines/listener/PlayerJoinListener.java b/src/main/java/dev/loki/lomines/listener/player/PlayerJoinListener.java
similarity index 94%
rename from src/main/java/dev/loki/lomines/listener/PlayerJoinListener.java
rename to src/main/java/dev/loki/lomines/listener/player/PlayerJoinListener.java
index 699a1cd..9584891 100644
--- a/src/main/java/dev/loki/lomines/listener/PlayerJoinListener.java
+++ b/src/main/java/dev/loki/lomines/listener/player/PlayerJoinListener.java
@@ -1,4 +1,4 @@
-package dev.loki.lomines.listener;
+package dev.loki.lomines.listener.player;
import dev.loki.lomines.LoMinesPlugin;
import org.bukkit.event.EventHandler;
diff --git a/src/main/java/dev/loki/lomines/util/ErrorHandler.java b/src/main/java/dev/loki/lomines/util/ErrorHandler.java
index 39b1567..2fcd715 100644
--- a/src/main/java/dev/loki/lomines/util/ErrorHandler.java
+++ b/src/main/java/dev/loki/lomines/util/ErrorHandler.java
@@ -6,7 +6,7 @@ import java.util.logging.Level;
import java.util.logging.Logger;
/**
- * Унифицированная обработка ошибок и отправка сообщений
+ * Унифицированная обработка ошибок и отправка сообщений.
*/
public class ErrorHandler {
private final Logger logger;
@@ -18,35 +18,35 @@ public class ErrorHandler {
}
/**
- * Отправить сообщение об ошибке игроку
+ * Отправить сообщение об ошибке игроку.
*/
public void sendError(CommandSender sender, String message) {
sender.sendMessage(MessageFormatter.error(message));
}
/**
- * Отправить сообщение об ошибке игроку с причиной
+ * Отправить сообщение об ошибке игроку с причиной.
*/
public void sendError(CommandSender sender, String message, String reason) {
sender.sendMessage(MessageFormatter.error(message + ": " + reason));
}
/**
- * Залогировать ошибку
+ * Залогировать ошибку.
*/
public void logError(String message) {
logger.log(Level.SEVERE, prefix + message);
}
/**
- * Залогировать ошибку с исключением
+ * Залогировать ошибку с исключением.
*/
public void logError(String message, Throwable throwable) {
logger.log(Level.SEVERE, prefix + message, throwable);
}
/**
- * Отправить ошибку игроку и залогировать
+ * Отправить ошибку игроку и залогировать.
*/
public void handleError(CommandSender sender, String userMessage, String logMessage) {
sendError(sender, userMessage);
@@ -54,7 +54,7 @@ public class ErrorHandler {
}
/**
- * Отправить ошибку игроку и залогировать с исключением
+ * Отправить ошибку игроку и залогировать с исключением.
*/
public void handleError(CommandSender sender, String userMessage, String logMessage, Throwable throwable) {
sendError(sender, userMessage);
@@ -62,21 +62,21 @@ public class ErrorHandler {
}
/**
- * Обработать ошибку валидации
+ * Обработать ошибку валидации.
*/
public void handleValidationError(CommandSender sender, String fieldName, String value) {
sendError(sender, "Некорректное значение для " + fieldName + ": " + value);
}
/**
- * Обработать ошибку "не найдено"
+ * Обработать ошибку "не найдено".
*/
public void handleNotFound(CommandSender sender, String entityType, String identifier) {
sendError(sender, entityType + " '" + identifier + "' не найден");
}
/**
- * Обработать ошибку доступа
+ * Обработать ошибку доступа.
*/
public void handlePermissionDenied(CommandSender sender, String action) {
sendError(sender, "У вас нет прав для: " + action);
diff --git a/src/main/java/dev/loki/lomines/util/block/BlockUpdateUtil.java b/src/main/java/dev/loki/lomines/util/block/BlockUpdateUtil.java
index 6486f9d..c2ec926 100644
--- a/src/main/java/dev/loki/lomines/util/block/BlockUpdateUtil.java
+++ b/src/main/java/dev/loki/lomines/util/block/BlockUpdateUtil.java
@@ -8,26 +8,15 @@ import org.bukkit.entity.Player;
import java.util.Collection;
-/**
- * Utility for sending block updates to clients to prevent ghost blocks.
- *
- * Ghost blocks occur when the server sets blocks without notifying clients,
- * causing a desync where blocks appear invisible or behave strangely on the client.
- * This utility forces block updates to be sent to all relevant players.
- */
public final class BlockUpdateUtil {
private BlockUpdateUtil() {
}
- /**
- * Sends a block update to all players who can see the specified chunk.
- * This prevents ghost blocks by forcing the server to send the block state to clients.
- *
- * @param block the block to update
- */
public static void sendBlockUpdate(Block block) {
- if (block == null) return;
+ if (block == null) {
+ return;
+ }
Chunk chunk = block.getChunk();
World world = block.getWorld();
@@ -39,18 +28,6 @@ public final class BlockUpdateUtil {
}
}
- /**
- * Sends block updates for all blocks in a cuboid region.
- * Uses batching to minimize packet overhead.
- *
- * @param world the world containing the blocks
- * @param minX minimum X coordinate
- * @param minY minimum Y coordinate
- * @param minZ minimum Z coordinate
- * @param maxX maximum X coordinate
- * @param maxY maximum Y coordinate
- * @param maxZ maximum Z coordinate
- */
public static void sendRegionUpdate(World world, int minX, int minY, int minZ,
int maxX, int maxY, int maxZ) {
Collection players = world.getPlayers();
@@ -69,17 +46,15 @@ public final class BlockUpdateUtil {
}
}
- /**
- * Sends updates for a list of locations. More efficient than region update
- * when only specific positions need updating.
- *
- * @param locations the locations to update
- */
public static void sendLocationsUpdate(java.util.List locations) {
- if (locations == null || locations.isEmpty()) return;
+ if (locations == null || locations.isEmpty()) {
+ return;
+ }
for (Location loc : locations) {
- if (loc.getWorld() == null) continue;
+ if (loc.getWorld() == null) {
+ continue;
+ }
Block block = loc.getBlock();
for (Player player : loc.getWorld().getPlayers()) {
if (isLocationVisibleToPlayer(player, loc)) {
@@ -89,16 +64,6 @@ public final class BlockUpdateUtil {
}
}
- /**
- * Refreshes chunks for all players in a region.
- * This is a heavier operation but ensures complete chunk resync.
- *
- * @param world the world
- * @param minX minimum block X
- * @param minZ minimum block Z
- * @param maxX maximum block X
- * @param maxZ maximum block Z
- */
public static void refreshChunks(World world, int minX, int minZ, int maxX, int maxZ) {
int minChunkX = minX >> 4;
int minChunkZ = minZ >> 4;
@@ -110,15 +75,17 @@ public final class BlockUpdateUtil {
Chunk chunk = world.getChunkAt(chunkX, chunkZ);
if (chunk.isLoaded()) {
for (Player player : world.getPlayers()) {
- player.getWorld().refreshChunk(chunk.getX(), chunk.getZ());
- }
+ player.getWorld().refreshChunk(chunk.getX(), chunk.getZ());
+ }
}
}
}
}
private static boolean isChunkVisibleToPlayer(Player player, Chunk chunk) {
- if (!player.getWorld().equals(chunk.getWorld())) return false;
+ if (!player.getWorld().equals(chunk.getWorld())) {
+ return false;
+ }
int renderDistance = player.getClientViewDistance();
int playerChunkX = player.getLocation().getBlockX() >> 4;
@@ -131,30 +98,11 @@ public final class BlockUpdateUtil {
}
private static boolean isLocationVisibleToPlayer(Player player, Location loc) {
- if (!player.getWorld().equals(loc.getWorld())) return false;
+ if (!player.getWorld().equals(loc.getWorld())) {
+ return false;
+ }
double renderDistance = player.getClientViewDistance() * 16;
return player.getLocation().distanceSquared(loc) <= renderDistance * renderDistance;
}
-
- /**
- * Finds a safe teleport location around the target by searching upward.
- * A location is safe when feet and head blocks are non-solid.
- */
- public static Location findSafeTeleportLocation(Location target, int maxBlocksUp) {
- if (target == null || target.getWorld() == null) {
- return target;
- }
-
- Location base = target.clone();
- for (int i = 0; i <= Math.max(0, maxBlocksUp); i++) {
- Location candidate = base.clone().add(0, i, 0);
- Block feet = candidate.getBlock();
- Block head = candidate.clone().add(0, 1, 0).getBlock();
- if (!feet.getType().isSolid() && !head.getType().isSolid()) {
- return candidate;
- }
- }
- return base;
- }
}
diff --git a/src/main/java/dev/loki/lomines/util/block/SafeTeleportFinder.java b/src/main/java/dev/loki/lomines/util/block/SafeTeleportFinder.java
new file mode 100644
index 0000000..a59168c
--- /dev/null
+++ b/src/main/java/dev/loki/lomines/util/block/SafeTeleportFinder.java
@@ -0,0 +1,27 @@
+package dev.loki.lomines.util.block;
+
+import org.bukkit.Location;
+import org.bukkit.block.Block;
+
+public final class SafeTeleportFinder {
+
+ private SafeTeleportFinder() {
+ }
+
+ public static Location findSafeTeleportLocation(Location target, int maxBlocksUp) {
+ if (target == null || target.getWorld() == null) {
+ return target;
+ }
+
+ Location base = target.clone();
+ for (int i = 0; i <= Math.max(0, maxBlocksUp); i++) {
+ Location candidate = base.clone().add(0, i, 0);
+ Block feet = candidate.getBlock();
+ Block head = candidate.clone().add(0, 1, 0).getBlock();
+ if (!feet.getType().isSolid() && !head.getType().isSolid()) {
+ return candidate;
+ }
+ }
+ return base;
+ }
+}
diff --git a/src/main/java/dev/loki/lomines/util/format/ChunkRefresher.java b/src/main/java/dev/loki/lomines/util/format/ChunkRefresher.java
new file mode 100644
index 0000000..585b35e
--- /dev/null
+++ b/src/main/java/dev/loki/lomines/util/format/ChunkRefresher.java
@@ -0,0 +1,84 @@
+package dev.loki.lomines.util.format;
+
+import dev.loki.lomines.util.location.geo.Cuboid;
+import org.bukkit.Chunk;
+import org.bukkit.World;
+
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Set;
+
+public final class ChunkRefresher {
+
+ private ChunkRefresher() {
+ }
+
+ public static void refreshChunks(Cuboid region, boolean isPaper, Method sendChunkChangeMethod) {
+ Set chunks = getAffectedChunks(region);
+ World world = region.getWorld();
+
+ for (ChunkCoordinate coord : chunks) {
+ Chunk chunk = world.getChunkAt(coord.x, coord.z);
+ updateChunk(chunk, isPaper, sendChunkChangeMethod);
+ }
+ }
+
+ private static void updateChunk(Chunk chunk, boolean isPaper, Method sendChunkChangeMethod) {
+ if (isPaper && sendChunkChangeMethod != null) {
+ updateChunkPaper(chunk, sendChunkChangeMethod);
+ } else {
+ updateChunkBukkit(chunk);
+ }
+ }
+
+ private static void updateChunkPaper(Chunk chunk, Method sendChunkChangeMethod) {
+ try {
+ sendChunkChangeMethod.invoke(chunk);
+ } catch (Exception e) {
+ updateChunkBukkit(chunk);
+ }
+ }
+
+ private static void updateChunkBukkit(Chunk chunk) {
+ World world = chunk.getWorld();
+ try {
+ Method refreshChunk = World.class.getMethod("refreshChunk", int.class, int.class);
+ refreshChunk.invoke(world, chunk.getX(), chunk.getZ());
+ } catch (Exception e) {
+ // chunks will update naturally
+ }
+ }
+
+ private static Set getAffectedChunks(Cuboid region) {
+ Set chunks = new HashSet<>();
+ int minChunkX = region.getMinX() >> 4;
+ int maxChunkX = region.getMaxX() >> 4;
+ int minChunkZ = region.getMinZ() >> 4;
+ int maxChunkZ = region.getMaxZ() >> 4;
+
+ for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) {
+ for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) {
+ chunks.add(new ChunkCoordinate(chunkX, chunkZ));
+ }
+ }
+ return chunks;
+ }
+
+ private record ChunkCoordinate(int x, int z) {
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof ChunkCoordinate(int x1, int z1))) {
+ return false;
+ }
+ return x == x1 && z == z1;
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(x, z);
+ }
+ }
+}
diff --git a/src/main/java/dev/loki/lomines/util/format/ChunkUtils.java b/src/main/java/dev/loki/lomines/util/format/ChunkUtils.java
index 8632e95..78be928 100644
--- a/src/main/java/dev/loki/lomines/util/format/ChunkUtils.java
+++ b/src/main/java/dev/loki/lomines/util/format/ChunkUtils.java
@@ -1,20 +1,10 @@
package dev.loki.lomines.util.format;
-import dev.loki.lomines.util.location.Cuboid;
+import dev.loki.lomines.util.location.geo.Cuboid;
import org.bukkit.Chunk;
-import org.bukkit.World;
import java.lang.reflect.Method;
-import java.util.HashSet;
-import java.util.Set;
-/**
- * Utility class for updating chunks after block modifications.
- * Automatically detects Paper API availability and uses the most efficient method.
- *
- * Paper detection is done through method reflection rather than Class.forName
- * to provide a more reliable mechanism as per requirement 21.4.
- */
public final class ChunkUtils {
private static final boolean IS_PAPER = detectPaper();
@@ -24,37 +14,15 @@ public final class ChunkUtils {
throw new UnsupportedOperationException("Utility class");
}
- /**
- * Updates all chunks within the given cuboid region.
- * Uses Paper API if available for better performance, otherwise falls back to Bukkit API.
- *
- * @param region The cuboid region whose chunks should be updated
- * @throws IllegalArgumentException if region is null
- */
public static void updateChunks(Cuboid region) {
if (region == null) {
throw new IllegalArgumentException("Region cannot be null");
}
-
- Set chunks = getAffectedChunks(region);
- World world = region.getWorld();
-
- for (ChunkCoordinate coord : chunks) {
- Chunk chunk = world.getChunkAt(coord.x, coord.z);
- updateChunk(chunk);
- }
+ ChunkRefresher.refreshChunks(region, IS_PAPER, SEND_CHUNK_CHANGE_METHOD);
}
- /**
- * Detects if Paper API is available by checking for Paper-specific methods.
- * This is more reliable than Class.forName as it checks for actual functionality.
- *
- * @return true if Paper API is available, false otherwise
- */
private static boolean detectPaper() {
try {
- // Check for Paper-specific method in Chunk class
- // Paper adds sendChunkChange methods that Bukkit doesn't have
Class> chunkClass = Chunk.class;
chunkClass.getMethod("getPluginChunkTickets");
return true;
@@ -63,112 +31,15 @@ public final class ChunkUtils {
}
}
- /**
- * Finds the sendChunkChange method if running on Paper.
- *
- * @return The sendChunkChange method, or null if not available
- */
private static Method findSendChunkChangeMethod() {
if (!IS_PAPER) {
return null;
}
-
try {
Class> chunkClass = Chunk.class;
- // Paper 1.21 has sendChunkChange() method
return chunkClass.getMethod("sendChunkChange");
} catch (NoSuchMethodException e) {
return null;
}
}
-
- /**
- * Updates a single chunk using the appropriate API.
- *
- * @param chunk The chunk to update
- */
- private static void updateChunk(Chunk chunk) {
- if (IS_PAPER && SEND_CHUNK_CHANGE_METHOD != null) {
- updateChunkPaper(chunk);
- } else {
- updateChunkBukkit(chunk);
- }
- }
-
- /**
- * Updates a chunk using Paper API.
- *
- * @param chunk The chunk to update
- */
- private static void updateChunkPaper(Chunk chunk) {
- try {
- SEND_CHUNK_CHANGE_METHOD.invoke(chunk);
- } catch (Exception e) {
- // Fall back to Bukkit if Paper method fails
- updateChunkBukkit(chunk);
- }
- }
-
- /**
- * Updates a chunk using Bukkit API.
- * Forces chunk to be resent to all players by marking it as modified.
- *
- * @param chunk The chunk to update
- */
- private static void updateChunkBukkit(Chunk chunk) {
- // Bukkit doesn't have a direct chunk update method
- // The chunk will be automatically sent to players when they are nearby
- // We can force a refresh by unloading and reloading, but that's expensive
- // Instead, we rely on the natural chunk update mechanism
- // Players will see the changes when the chunk is next sent to them
-
- // For Bukkit, we can use the World's refreshChunk method if available
- World world = chunk.getWorld();
- try {
- // Try to use refreshChunk if it exists (some Bukkit versions have it)
- Method refreshChunk = World.class.getMethod("refreshChunk", int.class, int.class);
- refreshChunk.invoke(world, chunk.getX(), chunk.getZ());
- } catch (Exception e) {
- // If refreshChunk doesn't exist, chunks will update naturally
- // This is acceptable as blocks are already set
- }
- }
-
- /**
- * Calculates all chunk coordinates that are affected by the given region.
- *
- * @param region The cuboid region
- * @return Set of chunk coordinates
- */
- private static Set getAffectedChunks(Cuboid region) {
- Set chunks = new HashSet<>();
-
- // Convert block coordinates to chunk coordinates
- int minChunkX = region.getMinX() >> 4;
- int maxChunkX = region.getMaxX() >> 4;
- int minChunkZ = region.getMinZ() >> 4;
- int maxChunkZ = region.getMaxZ() >> 4;
-
- for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) {
- for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) {
- chunks.add(new ChunkCoordinate(chunkX, chunkZ));
- }
- }
-
- return chunks;
- }
-
- /**
- * Simple immutable holder for chunk coordinates.
- */
- private record ChunkCoordinate(int x, int z) {
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
- if (!(obj instanceof ChunkCoordinate(int x1, int z1))) return false;
- return x == x1 && z == z1;
- }
-
- }
}
diff --git a/src/main/java/dev/loki/lomines/util/format/ColorUtils.java b/src/main/java/dev/loki/lomines/util/format/ColorUtils.java
deleted file mode 100644
index ef7eb57..0000000
--- a/src/main/java/dev/loki/lomines/util/format/ColorUtils.java
+++ /dev/null
@@ -1,207 +0,0 @@
-package dev.loki.lomines.util.format;
-
-import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.minimessage.MiniMessage;
-import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Utility for converting color formats.
- * Supports: RRGGBB (HEX), &x&R&R&G&G&B&B (Bukkit HEX), MiniMessage
- */
-public final class ColorUtils {
-
- private static final Pattern HEX_PATTERN = Pattern.compile("([0-9A-Fa-f]{6})");
- private static final Pattern LEGACY_HEX_PATTERN = Pattern.compile("&x([&0-9A-Fa-f]){12}");
-
- private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
- private static final LegacyComponentSerializer LEGACY_SERIALIZER = LegacyComponentSerializer.builder()
- .hexColors()
- .useUnusualXRepeatedCharacterHexFormat()
- .build();
-
- private ColorUtils() {
- // Utility class
- }
-
- /**
- * Converts a string with color codes to a Component.
- * Supports:
- * - RRGGBB - HEX colors
- * - &x&R&R&G&G&B&B - Legacy Bukkit HEX
- * - &a, &b, ... - Legacy colors
- * - , <#RRGGBB> - MiniMessage (pass-through)
- */
- public static Component format(String text) {
- if (text == null || text.isEmpty()) {
- return Component.empty();
- }
-
- // Check if it's already MiniMessage format
- if (text.contains("<") && text.contains(">")) {
- try {
- return MINI_MESSAGE.deserialize(text);
- } catch (Exception e) {
- // Fall through to legacy conversion
- }
- }
-
- // Convert RRGGBB to MiniMessage <#RRGGBB>
- String miniMessageText = convertHexToMiniMessage(text);
-
- // Convert legacy &x format if present
- miniMessageText = convertLegacyHexToMiniMessage(miniMessageText);
-
- // Convert remaining legacy codes (&a, &l, etc.) to MiniMessage
- miniMessageText = convertLegacyToMiniMessage(miniMessageText);
-
- try {
- return MINI_MESSAGE.deserialize(miniMessageText);
- } catch (Exception e) {
- // Fallback: use legacy serializer
- return LEGACY_SERIALIZER.deserialize(text);
- }
- }
-
- /**
- * Converts RRGGBB to <#RRGGBB> for MiniMessage.
- */
- private static String convertHexToMiniMessage(String text) {
- Matcher matcher = HEX_PATTERN.matcher(text);
- StringBuffer result = new StringBuffer();
-
- while (matcher.find()) {
- String hex = matcher.group(1);
- matcher.appendReplacement(result, "<#" + hex + ">");
- }
- matcher.appendTail(result);
-
- return result.toString();
- }
-
- /**
- * Converts &x&R&R&G&G&B&B to <#RRGGBB> for MiniMessage.
- */
- private static String convertLegacyHexToMiniMessage(String text) {
- if (!text.contains("&x")) {
- return text;
- }
-
- // Pattern for &x&R&R&G&G&B&B
- Pattern bukkitHex = Pattern.compile("&x(&[0-9A-Fa-f])(&[0-9A-Fa-f])(&[0-9A-Fa-f])(&[0-9A-Fa-f])(&[0-9A-Fa-f])(&[0-9A-Fa-f])");
- Matcher matcher = bukkitHex.matcher(text);
- StringBuffer result = new StringBuffer();
-
- while (matcher.find()) {
- String r1 = matcher.group(1).substring(1);
- String r2 = matcher.group(2).substring(1);
- String g1 = matcher.group(3).substring(1);
- String g2 = matcher.group(4).substring(1);
- String b1 = matcher.group(5).substring(1);
- String b2 = matcher.group(6).substring(1);
- String hex = r1 + r2 + g1 + g2 + b1 + b2;
- matcher.appendReplacement(result, "<#" + hex + ">");
- }
- matcher.appendTail(result);
-
- return result.toString();
- }
-
- /**
- * Converts legacy & codes to MiniMessage tags.
- */
- private static String convertLegacyToMiniMessage(String text) {
- return text
- .replace("&0", "")
- .replace("&1", "")
- .replace("&2", "")
- .replace("&3", "")
- .replace("&4", "")
- .replace("&5", "")
- .replace("&6", "")
- .replace("&7", "")
- .replace("&8", "")
- .replace("&9", "")
- .replace("&a", "")
- .replace("&b", "