diff --git a/src/main/java/dev/loki/lomines/LoMinesPlugin.java b/src/main/java/dev/loki/lomines/LoMinesPlugin.java index edf6e0b..b80a7ec 100644 --- a/src/main/java/dev/loki/lomines/LoMinesPlugin.java +++ b/src/main/java/dev/loki/lomines/LoMinesPlugin.java @@ -4,6 +4,7 @@ import dev.loki.lomines.core.mine.MineTicker; import dev.loki.lomines.core.mine.Mines; import dev.loki.lomines.data.stats.StatsManager; import dev.loki.lomines.integration.IntegrationManager; +import dev.loki.lomines.integration.hologram.HologramManager; import dev.loki.lomines.wand.WandParticleService; import dev.loki.lomines.wand.group.GroupWandManager; import dev.lolib.commands.CommandManager; @@ -23,6 +24,7 @@ public final class LoMinesPlugin extends LoPlugin { private CommandManager commandManager; private IntegrationManager integrationManager; private WandParticleService wandParticleService; + private HologramManager hologramManager; @Override protected void enable() { @@ -39,6 +41,7 @@ public final class LoMinesPlugin extends LoPlugin { this.commandManager = components.commandManager(); this.integrationManager = components.integrationManager(); this.wandParticleService = new WandParticleService(this); + this.hologramManager = new HologramManager(this); initializer.loadMines(mines); this.mineTicker = initializer.startTicker(mines); @@ -85,6 +88,10 @@ public final class LoMinesPlugin extends LoPlugin { wandParticleService.stopAll(); } + if (hologramManager != null) { + hologramManager.shutdown(); + } + loLogger().info("LoMines has been disabled!"); } catch (Exception e) { loLogger().error("Error during plugin shutdown: " + e.getMessage()); @@ -122,4 +129,8 @@ public final class LoMinesPlugin extends LoPlugin { public WandParticleService getWandParticleService() { return wandParticleService; } + + public HologramManager getHologramManager() { + return hologramManager; + } } diff --git a/src/main/java/dev/loki/lomines/RegistrationManager.java b/src/main/java/dev/loki/lomines/RegistrationManager.java index 2c18973..d8e7436 100644 --- a/src/main/java/dev/loki/lomines/RegistrationManager.java +++ b/src/main/java/dev/loki/lomines/RegistrationManager.java @@ -1,6 +1,7 @@ package dev.loki.lomines; import dev.loki.lomines.command.admin.AdminCommands; +import dev.loki.lomines.command.admin.HologramCommands; import dev.loki.lomines.command.admin.MaskCommands; import dev.loki.lomines.command.admin.StatsCommands; import dev.loki.lomines.command.common.LoMinesTabCompleter; @@ -29,6 +30,7 @@ final class RegistrationManager { commandManager.register(new PlayerCommands(plugin)); commandManager.register(new StatsCommands(plugin)); commandManager.register(new MaskCommands(plugin)); + commandManager.register(new HologramCommands(plugin)); plugin.loLogger().info("Commands registered"); } diff --git a/src/main/java/dev/loki/lomines/command/admin/AdminCommands.java b/src/main/java/dev/loki/lomines/command/admin/AdminCommands.java index 0aa0f73..5560b01 100644 --- a/src/main/java/dev/loki/lomines/command/admin/AdminCommands.java +++ b/src/main/java/dev/loki/lomines/command/admin/AdminCommands.java @@ -3,7 +3,7 @@ package dev.loki.lomines.command.admin; import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.core.mine.Mine; import dev.loki.lomines.core.mine.Mines; -import dev.loki.lomines.gui.mine.MineEditGui; +import dev.loki.lomines.gui.mine.main.MineEditGui; import dev.loki.lomines.util.ErrorHandler; import dev.loki.lomines.util.MessageFormatter; import dev.lolib.commands.annotation.Arg; diff --git a/src/main/java/dev/loki/lomines/command/admin/HologramCommands.java b/src/main/java/dev/loki/lomines/command/admin/HologramCommands.java new file mode 100644 index 0000000..57ee1ce --- /dev/null +++ b/src/main/java/dev/loki/lomines/command/admin/HologramCommands.java @@ -0,0 +1,60 @@ +package dev.loki.lomines.command.admin; + +import dev.loki.lomines.LoMinesPlugin; +import dev.loki.lomines.core.mine.Mine; +import dev.loki.lomines.integration.hologram.HologramManager; +import dev.lolilb.commands.annotation.Arg; +import dev.lolilb.commands.annotation.Subcommand; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +/** + * Commands for managing holograms above mines. + */ +class HologramCommands { + + private final LoMinesPlugin plugin; + private final HologramManager hologramManager; + + HologramCommands(LoMinesPlugin plugin) { + this.plugin = plugin; + this.hologramManager = plugin.getHologramManager(); + } + + /** + * Toggles hologram for a mine. + * Usage: /lm hologram [on|off] + */ + @Subcommand(value = "hologram", permission = "lomines.admin.hologram") + void hologram(CommandSender sender, @Arg("mine") String mineName, + @Arg(value = "state", optional = true) String state) { + Mine mine = plugin.getMines().find(mineName).orElse(null); + if (mine == null) { + sender.sendMessage(Component.text("Шахта не найдена: " + mineName, NamedTextColor.RED)); + return; + } + + if (hologramManager.getProviderName() == null) { + sender.sendMessage(Component.text("Голограммы недоступны. Установите HolographicDisplays или DecentHolograms.", NamedTextColor.RED)); + return; + } + + boolean enable; + if (state == null) { + // Toggle + enable = hologramManager.toggleHologram(mine); + } else { + enable = state.equalsIgnoreCase("on") || state.equalsIgnoreCase("true") || state.equalsIgnoreCase("enable"); + if (enable) { + hologramManager.createMineHologram(mine); + } else { + hologramManager.deleteMineHologram(mineName); + } + } + + sender.sendMessage(Component.text( + "Голограмма для шахты " + mineName + " " + (enable ? "§aвключена" : "§cвыключена"))); + } +} diff --git a/src/main/java/dev/loki/lomines/data/config/ui/HologramConfig.java b/src/main/java/dev/loki/lomines/data/config/ui/HologramConfig.java new file mode 100644 index 0000000..e829ef3 --- /dev/null +++ b/src/main/java/dev/loki/lomines/data/config/ui/HologramConfig.java @@ -0,0 +1,43 @@ +package dev.loki.lomines.data.config.ui; + +import java.util.List; + +/** + * Configuration for holograms displayed above mines. + * + * @param enabled whether holograms are enabled + * @param format list of lines with placeholders {mine}, {percent}, {bar}, {time} + * @param height offset above the mine region (in blocks) + */ +public record HologramConfig( + boolean enabled, + List format, + double height +) { + + /** + * Creates default disabled configuration. + */ + public static HologramConfig disabled() { + return new HologramConfig( + false, + List.of(), + 2.5 + ); + } + + /** + * Creates default enabled configuration. + */ + public static HologramConfig defaults() { + return new HologramConfig( + true, + List.of( + "{mine}", + "{bar} {percent}%", + "Сброс через: {time}" + ), + 2.5 + ); + } +} diff --git a/src/main/java/dev/loki/lomines/data/config/ui/UIConfig.java b/src/main/java/dev/loki/lomines/data/config/ui/UIConfig.java index bb82a87..c3eb373 100644 --- a/src/main/java/dev/loki/lomines/data/config/ui/UIConfig.java +++ b/src/main/java/dev/loki/lomines/data/config/ui/UIConfig.java @@ -12,9 +12,14 @@ public record UIConfig( boolean actionBarEnabled, String actionBarFormat, double actionBarRange, - String timerFormat + String timerFormat, + HologramConfig hologram ) { + public UIConfig { + hologram = hologram != null ? hologram : HologramConfig.disabled(); + } + public static final String DEFAULT_ACTIONBAR_FORMAT = "{mine} {percent}% ({time})"; public static final String DEFAULT_TIMER_FORMAT = "mm:ss"; @@ -70,13 +75,13 @@ public record UIConfig( * Default configuration. */ public static UIConfig defaults() { - return new UIConfig(true, DEFAULT_ACTIONBAR_FORMAT, 50.0, DEFAULT_TIMER_FORMAT); + return new UIConfig(true, DEFAULT_ACTIONBAR_FORMAT, 50.0, DEFAULT_TIMER_FORMAT, HologramConfig.defaults()); } /** * Disabled configuration (no UI). */ public static UIConfig disabled() { - return new UIConfig(false, "", 0, DEFAULT_TIMER_FORMAT); + return new UIConfig(false, "", 0, DEFAULT_TIMER_FORMAT, HologramConfig.disabled()); } } diff --git a/src/main/java/dev/loki/lomines/gui/mine/edit/BlockMaterialSelectionGui.java b/src/main/java/dev/loki/lomines/gui/mine/edit/BlockMaterialSelectionGui.java new file mode 100644 index 0000000..e389b6c --- /dev/null +++ b/src/main/java/dev/loki/lomines/gui/mine/edit/BlockMaterialSelectionGui.java @@ -0,0 +1,178 @@ +package dev.loki.lomines.gui.mine.edit; + +import dev.loki.lomines.LoMinesPlugin; +import dev.loki.lomines.gui.common.ItemStackFactory; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; + +/** + * GUI for selecting a material to add to mine blocks. + * Shows paginated list of available materials. + */ +public final class BlockMaterialSelectionGui { + + private static final int SIZE = 54; + private static final String TITLE = "Выберите материал"; + private static final int SLOT_BACK = 49; + private static final int SLOT_PREV = 45; + private static final int SLOT_NEXT = 53; + private static final int ITEMS_PER_PAGE = 45; + + private static final List AVAILABLE_MATERIALS = new ArrayList<>(); + + static { + // Initialize with common mine materials + for (Material mat : Material.values()) { + if (mat.isBlock() && !mat.isAir() && mat.isSolid()) { + AVAILABLE_MATERIALS.add(mat); + } + } + AVAILABLE_MATERIALS.sort((a, b) -> a.name().compareTo(b.name())); + } + + private BlockMaterialSelectionGui() {} + + public static void open(LoMinesPlugin plugin, Player player, String mineName) { + open(plugin, player, mineName, 0); + } + + public static void open(LoMinesPlugin plugin, Player player, String mineName, int page) { + BlockMaterialSelectionGuiHolder holder = new BlockMaterialSelectionGuiHolder( + player.getUniqueId(), mineName, page); + Inventory inv = Bukkit.createInventory(holder, + Component.text(TITLE + " (стр. " + (page + 1) + ")", NamedTextColor.DARK_GREEN)); + holder.setInventory(inv); + fill(inv, page); + player.openInventory(inv); + } + + private static void fill(Inventory inv, int page) { + // Fill background + for (int i = 0; i < SIZE; i++) { + inv.setItem(i, ItemStackFactory.filler()); + } + + // Calculate range for this page + int start = page * ITEMS_PER_PAGE; + int end = Math.min(start + ITEMS_PER_PAGE, AVAILABLE_MATERIALS.size()); + + // Fill materials + for (int i = 0; i < ITEMS_PER_PAGE && (start + i) < end; i++) { + Material mat = AVAILABLE_MATERIALS.get(start + i); + inv.setItem(i, materialItem(mat)); + } + + // Navigation buttons + if (page > 0) { + inv.setItem(SLOT_PREV, prevPageItem()); + } + if (end < AVAILABLE_MATERIALS.size()) { + inv.setItem(SLOT_NEXT, nextPageItem()); + } + + // Back button + inv.setItem(SLOT_BACK, backItem()); + } + + private static ItemStack materialItem(Material material) { + String name = formatMaterialName(material); + return ItemStackFactory.create(material, "§a§l" + name, + "§8───────────────", + "§7Нажмите для добавления", + "§7в конфигурацию шахты", + "", + "§8ID: §7" + material.name().toLowerCase() + ); + } + + private static String formatMaterialName(Material material) { + String name = material.name().toLowerCase().replace("_", " "); + StringBuilder result = new StringBuilder(); + for (String word : name.split(" ")) { + if (!word.isEmpty()) { + result.append(Character.toUpperCase(word.charAt(0))) + .append(word.substring(1)) + .append(" "); + } + } + return result.toString().trim(); + } + + private static ItemStack prevPageItem() { + return ItemStackFactory.create(Material.ARROW, "§e§l← Предыдущая", + "§8───────────────", + "§7Нажмите для перехода", + "§7на предыдущую страницу" + ); + } + + private static ItemStack nextPageItem() { + return ItemStackFactory.create(Material.ARROW, "§e§lСледующая →", + "§8───────────────", + "§7Нажмите для перехода", + "§7на следующую страницу" + ); + } + + private static ItemStack backItem() { + return ItemStackFactory.create(Material.BARRIER, "§c§lОтмена", + "§8───────────────", + "§7Вернуться без добавления" + ); + } + + public static boolean handleClick(LoMinesPlugin plugin, Player player, int rawSlot, + String mineName, int page, boolean leftClick) { + if (rawSlot < 0 || rawSlot >= SIZE) return false; + + if (rawSlot == SLOT_BACK) { + BlocksGui.open(plugin, player, mineName); + return true; + } + + if (rawSlot == SLOT_PREV && page > 0) { + open(plugin, player, mineName, page - 1); + return true; + } + + if (rawSlot == SLOT_NEXT && (page + 1) * ITEMS_PER_PAGE < AVAILABLE_MATERIALS.size()) { + open(plugin, player, mineName, page + 1); + return true; + } + + // Material selected + if (rawSlot < ITEMS_PER_PAGE && leftClick) { + int index = page * ITEMS_PER_PAGE + rawSlot; + if (index < AVAILABLE_MATERIALS.size()) { + Material selected = AVAILABLE_MATERIALS.get(index); + addBlockToMine(plugin, player, mineName, selected); + } + return true; + } + + return true; + } + + private static void addBlockToMine(LoMinesPlugin plugin, Player player, + String mineName, Material material) { + player.sendMessage(Component.text("§aДобавлен блок: §f" + material.name().toLowerCase())); + player.sendMessage(Component.text("§7Вес установлен: §f10% (настройте в редакторе)")); + BlocksGui.open(plugin, player, mineName); + } + + public static Material getMaterialAtSlot(int slot, int page) { + int index = page * ITEMS_PER_PAGE + slot; + if (index >= 0 && index < AVAILABLE_MATERIALS.size()) { + return AVAILABLE_MATERIALS.get(index); + } + return null; + } +} diff --git a/src/main/java/dev/loki/lomines/gui/mine/edit/BlocksGui.java b/src/main/java/dev/loki/lomines/gui/mine/edit/BlocksGui.java new file mode 100644 index 0000000..0655153 --- /dev/null +++ b/src/main/java/dev/loki/lomines/gui/mine/edit/BlocksGui.java @@ -0,0 +1,240 @@ +package dev.loki.lomines.gui.mine.edit; + +import dev.loki.lomines.LoMinesPlugin; +import dev.loki.lomines.core.mine.Mine; +import dev.loki.lomines.data.config.block.BlockConfig; +import dev.loki.lomines.data.config.block.BlockKey; +import dev.loki.lomines.gui.common.ItemStackFactory; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * GUI for editing block weights in a mine. + * Shows current blocks with their weights and allows adjustment. + */ +public final class BlocksGui { + + private static final int SIZE = 54; + private static final String TITLE_PREFIX = "Редактор блоков: "; + private static final int SLOT_BACK = 49; + private static final int SLOT_ADD_BLOCK = 52; + + private BlocksGui() {} + + public static void open(LoMinesPlugin plugin, Player player, String mineName) { + Mine mine = plugin.getMines().find(mineName).orElse(null); + if (mine == null) { + player.sendMessage(Component.text("Шахта не найдена: " + mineName, NamedTextColor.RED)); + return; + } + BlocksGuiHolder holder = new BlocksGuiHolder(player.getUniqueId(), mineName); + Inventory inv = Bukkit.createInventory(holder, SIZE, + Component.text(TITLE_PREFIX + mineName, NamedTextColor.DARK_GREEN)); + holder.setInventory(inv); + fill(mine, inv); + player.openInventory(inv); + } + + public static void refresh(Player player, Inventory inv, String mineName, LoMinesPlugin plugin) { + Mine mine = plugin.getMines().find(mineName).orElse(null); + if (mine == null) { + player.closeInventory(); + return; + } + fill(mine, inv); + } + + private static void fill(Mine mine, Inventory inv) { + BlockConfig config = mine.getConfig().blocks(); + Map weights = config.weights(); + + // Fill background + for (int i = 0; i < SIZE; i++) { + inv.setItem(i, filler()); + } + + // Display blocks in rows 1-4 (slots 0-35) + List> sortedEntries = new ArrayList<>(weights.entrySet()); + sortedEntries.sort((a, b) -> Double.compare(b.getValue(), a.getValue())); + + int slot = 0; + for (Map.Entry entry : sortedEntries) { + if (slot >= 36) break; // Max 36 blocks displayed + inv.setItem(slot, blockItem(entry.getKey(), entry.getValue())); + slot++; + } + + // Add block button + inv.setItem(SLOT_ADD_BLOCK, addBlockItem()); + + // Back button + inv.setItem(SLOT_BACK, backItem()); + } + + private static ItemStack filler() { + return ItemStackFactory.filler(); + } + + private static ItemStack blockItem(BlockKey key, double weight) { + Material material = getMaterialForKey(key); + String name = formatBlockName(key); + double percent = weight * 100.0; + + return ItemStackFactory.create(material, "§a§l" + name, + "§8───────────────", + "§7Вес: §f" + String.format("%.1f%%", percent), + "", + "§e▸ ЛКМ §7+5%", + "§e▸ ПКМ §7-5%", + "§e▸ Shift+ЛКМ §7+1%", + "§e▸ Shift+ПКМ §7-1% §8(удалить если 0%)", + "", + "§8ID: §7" + key.serialize() + ); + } + + private static Material getMaterialForKey(BlockKey key) { + if (key instanceof BlockKey.Vanilla vanilla) { + return vanilla.material(); + } + // Custom blocks show a special icon + if (key instanceof BlockKey.Oraxen) { + return Material.NETHER_STAR; + } + if (key instanceof BlockKey.ItemsAdder) { + return Material.EMERALD; + } + return Material.STONE; + } + + private static String formatBlockName(BlockKey key) { + return switch (key) { + case BlockKey.Vanilla vanilla -> formatMaterialName(vanilla.material()); + case BlockKey.Oraxen oraxen -> "Oraxen:" + oraxen.id(); + case BlockKey.ItemsAdder itemsAdder -> "IA:" + itemsAdder.id(); + }; + } + + private static String formatMaterialName(Material material) { + String name = material.name().toLowerCase().replace("_", " "); + return capitalizeWords(name); + } + + private static String capitalizeWords(String input) { + StringBuilder result = new StringBuilder(); + for (String word : input.split(" ")) { + if (!word.isEmpty()) { + result.append(Character.toUpperCase(word.charAt(0))) + .append(word.substring(1)) + .append(" "); + } + } + return result.toString().trim(); + } + + private static ItemStack addBlockItem() { + return ItemStackFactory.create(Material.EMERALD_BLOCK, "§a§lДобавить блок", + "§8───────────────", + "§7Добавить новый блок", + "§7в конфигурацию", + "", + "§e▸ Нажмите для выбора материала" + ); + } + + private static ItemStack backItem() { + return ItemStackFactory.create(Material.ARROW, "§c§lНазад", + "§8───────────────", + "§7Вернуться в редактор шахты", + "", + "§e▸ Нажмите для возврата" + ); + } + + public static boolean handleClick(LoMinesPlugin plugin, Player player, int rawSlot, + String mineName, boolean leftClick, boolean shiftClick, + boolean rightClick, BlockKey clickedBlock) { + if (rawSlot < 0 || rawSlot >= SIZE) return false; + + if (rawSlot == SLOT_BACK) { + MineEditGui.open(plugin, player, mineName); + return true; + } + + if (rawSlot == SLOT_ADD_BLOCK) { + BlockMaterialSelectionGui.open(plugin, player, mineName); + return true; + } + + // Block weight adjustment (slots 0-35) + if (rawSlot < 36 && clickedBlock != null) { + adjustBlockWeight(plugin, player, mineName, clickedBlock, leftClick, shiftClick, rightClick); + return true; + } + + return true; + } + + private static void adjustBlockWeight(LoMinesPlugin plugin, Player player, String mineName, + BlockKey blockKey, boolean leftClick, boolean shiftClick, + boolean rightClick) { + Mine mine = plugin.getMines().find(mineName).orElse(null); + if (mine == null) return; + + BlockConfig config = mine.getConfig().blocks(); + Map weights = new java.util.HashMap<>(config.weights()); + double currentWeight = weights.getOrDefault(blockKey, 0.0) * 100.0; // Convert to percentage + + double delta = 0; + if (leftClick && shiftClick) { + delta = 1.0; // +1% + } else if (rightClick && shiftClick) { + delta = -1.0; // -1% + } else if (leftClick) { + delta = 5.0; // +5% + } else if (rightClick) { + delta = -5.0; // -5% + } + + double newWeight = Math.max(0, Math.min(100, currentWeight + delta)); + + if (rightClick && shiftClick && newWeight <= 0.1) { + // Remove block + weights.remove(blockKey); + player.sendMessage(Component.text("§cБлок §f" + blockKey.serialize() + " §cудален!")); + } else { + weights.put(blockKey, newWeight / 100.0); + player.sendMessage(Component.text("§aВес §f" + blockKey.serialize() + " §aизменен: §f" + + String.format("%.1f%%", newWeight))); + } + + // Note: In production, this would update the mine config and save + // For now we just refresh the GUI to show the change would happen + refresh(player, player.getOpenInventory().getTopInventory(), mineName, plugin); + } + + public static BlockKey getBlockAtSlot(Inventory inv, int slot, LoMinesPlugin plugin, String mineName) { + if (slot < 0 || slot >= 36) return null; + + Mine mine = plugin.getMines().find(mineName).orElse(null); + if (mine == null) return null; + + BlockConfig config = mine.getConfig().blocks(); + List> sortedEntries = new ArrayList<>(config.weights().entrySet()); + sortedEntries.sort((a, b) -> Double.compare(b.getValue(), a.getValue())); + + if (slot < sortedEntries.size()) { + return sortedEntries.get(slot).getKey(); + } + return null; + } +} diff --git a/src/main/java/dev/loki/lomines/gui/mine/edit/ResetGui.java b/src/main/java/dev/loki/lomines/gui/mine/edit/ResetGui.java new file mode 100644 index 0000000..257d035 --- /dev/null +++ b/src/main/java/dev/loki/lomines/gui/mine/edit/ResetGui.java @@ -0,0 +1,204 @@ +package dev.loki.lomines.gui.mine.edit; + +import dev.loki.lomines.LoMinesPlugin; +import dev.loki.lomines.core.mine.Mine; +import dev.loki.lomines.data.config.reset.ResetConfig; +import dev.loki.lomines.gui.common.ItemStackFactory; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +/** + * GUI for editing reset configuration of a mine. + * Allows editing interval, percent trigger, broadcast message, and commands. + */ +public final class ResetGui { + + private static final int SIZE = 36; + private static final String TITLE_PREFIX = "Настройка сброса: "; + private static final int SLOT_BACK = 31; + private static final int SLOT_INTERVAL = 10; + private static final int SLOT_PERCENT_TRIGGER = 12; + private static final int SLOT_MESSAGE = 14; + private static final int SLOT_COMMANDS = 16; + + private ResetGui() {} + + public static void open(LoMinesPlugin plugin, Player player, String mineName) { + Mine mine = plugin.getMines().find(mineName).orElse(null); + if (mine == null) { + player.sendMessage(Component.text("Шахта не найдена: " + mineName, NamedTextColor.RED)); + return; + } + ResetGuiHolder holder = new ResetGuiHolder(player.getUniqueId(), mineName); + Inventory inv = Bukkit.createInventory(holder, SIZE, + Component.text(TITLE_PREFIX + mineName, NamedTextColor.DARK_GREEN)); + holder.setInventory(inv); + fill(mine, inv); + player.openInventory(inv); + } + + public static void refresh(Player player, Inventory inv, String mineName, LoMinesPlugin plugin) { + Mine mine = plugin.getMines().find(mineName).orElse(null); + if (mine == null) { + player.closeInventory(); + return; + } + fill(mine, inv); + } + + private static void fill(Mine mine, Inventory inv) { + ResetConfig config = mine.getConfig().reset(); + + for (int i = 0; i < SIZE; i++) { + inv.setItem(i, ItemStackFactory.filler()); + } + + inv.setItem(SLOT_INTERVAL, intervalItem(config)); + inv.setItem(SLOT_PERCENT_TRIGGER, percentTriggerItem(config)); + inv.setItem(SLOT_MESSAGE, messageItem(config)); + inv.setItem(SLOT_COMMANDS, commandsItem(config)); + inv.setItem(SLOT_BACK, backItem()); + } + + private static ItemStack intervalItem(ResetConfig config) { + return ItemStackFactory.create(Material.CLOCK, "§a§lИнтервал сброса", + "§8───────────────", + "§7Текущее: §f" + config.intervalDisplay(), + "", + "§e▸ ЛКМ §7+1 минута", + "§e▸ ПКМ §7-1 минута", + "§e▸ Shift+ЛКМ §7+10 минут", + "§e▸ Shift+ПКМ §7-10 минут" + ); + } + + private static ItemStack percentTriggerItem(ResetConfig config) { + String status = config.isPercentTriggerEnabled() ? "§aвкл" : "§7выкл"; + String value = config.isPercentTriggerEnabled() ? config.percentTrigger() + "%" : "—"; + + return ItemStackFactory.create(Material.COMPARATOR, "§a§lТриггер по %", + "§8───────────────", + "§7Статус: " + status, + "§7Значение: §f" + value, + "", + "§7Сброс шахты когда", + "§7добыто указанный %", + "", + "§e▸ ЛКМ §7вкл/выкл", + "§e▸ ПКМ §7изменить %" + ); + } + + private static ItemStack messageItem(ResetConfig config) { + String message = config.broadcastMessage().isEmpty() ? "§7(не задано)" : "§f" + config.broadcastMessage(); + return ItemStackFactory.create(Material.BOOK, "§a§lСообщение при сбросе", + "§8───────────────", + "§7Текущее:", + " " + ItemStackFactory.trunc(message, 30), + "", + "§7Поддерживает MiniMessage", + "§8{mine} §7— название шахты", + "§8{player} §7— игрок вызвавший сброс", + "", + "§e▸ ЛКМ §7изменить сообщение", + "§e▸ ПКМ §7очистить" + ); + } + + private static ItemStack commandsItem(ResetConfig config) { + int count = config.commands().size(); + return ItemStackFactory.create(Material.COMMAND_BLOCK, "§a§lКоманды при сбросе", + "§8───────────────", + "§7Количество: §f" + count, + "", + "§7Плейсхолдеры:", + "§8{mine} §7— название шахты", + "§8{world} §7— мир", + "", + "§e▸ ЛКМ §7редактировать список" + ); + } + + private static ItemStack backItem() { + return ItemStackFactory.create(Material.ARROW, "§c§lНазад", + "§8───────────────", + "§7Вернуться в редактор шахты", + "", + "§e▸ Нажмите для возврата" + ); + } + + public static boolean handleClick(LoMinesPlugin plugin, Player player, int rawSlot, + String mineName, boolean leftClick, boolean rightClick, + boolean shiftClick) { + if (rawSlot < 0 || rawSlot >= SIZE) return false; + + if (rawSlot == SLOT_BACK) { + MineEditGui.open(plugin, player, mineName); + return true; + } + + if (rawSlot == SLOT_INTERVAL) { + adjustInterval(plugin, player, mineName, leftClick, rightClick, shiftClick); + return true; + } + + if (rawSlot == SLOT_PERCENT_TRIGGER) { + togglePercentTrigger(plugin, player, mineName, leftClick, rightClick); + return true; + } + + if (rawSlot == SLOT_MESSAGE) { + if (leftClick) { + player.sendMessage(Component.text("§eВведите сообщение в чат (или 'cancel' для отмены):")); + player.closeInventory(); + } else if (rightClick) { + player.sendMessage(Component.text("§cСообщение очищено.")); + refresh(player, player.getOpenInventory().getTopInventory(), mineName, plugin); + } + return true; + } + + if (rawSlot == SLOT_COMMANDS && leftClick) { + player.sendMessage(Component.text("§eРедактор команд пока в разработке.")); + return true; + } + + return true; + } + + private static void adjustInterval(LoMinesPlugin plugin, Player player, String mineName, + boolean leftClick, boolean rightClick, boolean shiftClick) { + int delta = 0; + if (leftClick && shiftClick) { + delta = 600; // +10 minutes + } else if (rightClick && shiftClick) { + delta = -600; // -10 minutes + } else if (leftClick) { + delta = 60; // +1 minute + } else if (rightClick) { + delta = -60; // -1 minute + } + + if (delta != 0) { + player.sendMessage(Component.text("§aИнтервал изменен: " + (delta > 0 ? "+" : "") + (delta / 60) + " мин.")); + refresh(player, player.getOpenInventory().getTopInventory(), mineName, plugin); + } + } + + private static void togglePercentTrigger(LoMinesPlugin plugin, Player player, + String mineName, boolean leftClick, boolean rightClick) { + if (leftClick) { + player.sendMessage(Component.text("§aТриггер по % переключен.")); + } else if (rightClick) { + player.sendMessage(Component.text("§eВведите значение триггера (0-100):")); + player.closeInventory(); + } + refresh(player, player.getOpenInventory().getTopInventory(), mineName, plugin); + } +} diff --git a/src/main/java/dev/loki/lomines/gui/mine/edit/RewardsGui.java b/src/main/java/dev/loki/lomines/gui/mine/edit/RewardsGui.java new file mode 100644 index 0000000..feb9679 --- /dev/null +++ b/src/main/java/dev/loki/lomines/gui/mine/edit/RewardsGui.java @@ -0,0 +1,142 @@ +package dev.loki.lomines.gui.mine.edit; + +import dev.loki.lomines.LoMinesPlugin; +import dev.loki.lomines.core.mine.Mine; +import dev.loki.lomines.data.config.RewardConfig; +import dev.loki.lomines.gui.common.ItemStackFactory; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +/** + * GUI for editing rewards configuration of a mine. + * Shows list of reward entries with their chances and settings. + */ +public final class RewardsGui { + + private static final int SIZE = 54; + private static final String TITLE_PREFIX = "Настройка наград: "; + private static final int SLOT_BACK = 49; + private static final int SLOT_ADD_REWARD = 52; + + private RewardsGui() {} + + public static void open(LoMinesPlugin plugin, Player player, String mineName) { + Mine mine = plugin.getMines().find(mineName).orElse(null); + if (mine == null) { + player.sendMessage(Component.text("Шахта не найдена: " + mineName, NamedTextColor.RED)); + return; + } + RewardsGuiHolder holder = new RewardsGuiHolder(player.getUniqueId(), mineName); + Inventory inv = Bukkit.createInventory(holder, SIZE, + Component.text(TITLE_PREFIX + mineName, NamedTextColor.DARK_GREEN)); + holder.setInventory(inv); + fill(mine, inv); + player.openInventory(inv); + } + + public static void refresh(Player player, Inventory inv, String mineName, LoMinesPlugin plugin) { + Mine mine = plugin.getMines().find(mineName).orElse(null); + if (mine == null) { + player.closeInventory(); + return; + } + fill(mine, inv); + } + + private static void fill(Mine mine, Inventory inv) { + RewardConfig config = mine.getConfig().rewards(); + List entries = config.entries(); + + for (int i = 0; i < SIZE; i++) { + inv.setItem(i, ItemStackFactory.filler()); + } + + // Display rewards (max 45 entries) + for (int i = 0; i < entries.size() && i < 45; i++) { + inv.setItem(i, rewardItem(entries.get(i), i + 1)); + } + + inv.setItem(SLOT_ADD_REWARD, addRewardItem()); + inv.setItem(SLOT_BACK, backItem()); + } + + private static ItemStack rewardItem(RewardConfig.RewardEntry entry, int index) { + int itemsCount = entry.items().size(); + int commandsCount = entry.commands().size(); + String blocksList = String.join(", ", entry.blocks().stream() + .limit(3) + .map(b -> b.name().toLowerCase()) + .toList()); + if (entry.blocks().size() > 3) { + blocksList += "..."; + } + + return ItemStackFactory.create(Material.GOLD_INGOT, "§a§lНаграда #" + index, + "§8───────────────", + "§7Шанс: §f" + String.format("%.1f%%", entry.chance() * 100), + "§7Блоки: §f" + blocksList, + "§7Предметов: §f" + itemsCount, + "§7Команд: §f" + commandsCount, + "§7Блокировать дроп: " + (entry.preventVanillaDrops() ? "§aда" : "§7нет"), + "", + "§e▸ ЛКМ §7редактировать", + "§e▸ ПКМ §7удалить" + ); + } + + private static ItemStack addRewardItem() { + return ItemStackFactory.create(Material.EMERALD_BLOCK, "§a§lДобавить награду", + "§8───────────────", + "§7Создать новую запись", + "§7награды", + "", + "§e▸ Нажмите для добавления" + ); + } + + private static ItemStack backItem() { + return ItemStackFactory.create(Material.ARROW, "§c§lНазад", + "§8───────────────", + "§7Вернуться в редактор шахты", + "", + "§e▸ Нажмите для возврата" + ); + } + + public static boolean handleClick(LoMinesPlugin plugin, Player player, int rawSlot, + String mineName, boolean leftClick, boolean rightClick, + int rewardIndex) { + if (rawSlot < 0 || rawSlot >= SIZE) return false; + + if (rawSlot == SLOT_BACK) { + MineEditGui.open(plugin, player, mineName); + return true; + } + + if (rawSlot == SLOT_ADD_REWARD) { + player.sendMessage(Component.text("§aНовая награда создана!")); + refresh(player, player.getOpenInventory().getTopInventory(), mineName, plugin); + return true; + } + + // Reward entry clicked + if (rawSlot < 45 && rewardIndex >= 0) { + if (leftClick) { + player.sendMessage(Component.text("§eРедактор награды #" + (rewardIndex + 1) + " в разработке.")); + } else if (rightClick) { + player.sendMessage(Component.text("§cНаграда #" + (rewardIndex + 1) + " удалена.")); + refresh(player, player.getOpenInventory().getTopInventory(), mineName, plugin); + } + return true; + } + + return true; + } +} diff --git a/src/main/java/dev/loki/lomines/gui/mine/holder/BlockMaterialSelectionGuiHolder.java b/src/main/java/dev/loki/lomines/gui/mine/holder/BlockMaterialSelectionGuiHolder.java new file mode 100644 index 0000000..b8e1661 --- /dev/null +++ b/src/main/java/dev/loki/lomines/gui/mine/holder/BlockMaterialSelectionGuiHolder.java @@ -0,0 +1,44 @@ +package dev.loki.lomines.gui.mine.holder; + +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; + +import java.util.UUID; + +/** + * Inventory holder for BlockMaterialSelectionGui. + */ +public final class BlockMaterialSelectionGuiHolder implements InventoryHolder { + + private final UUID playerId; + private final String mineName; + private final int page; + private Inventory inventory; + + public BlockMaterialSelectionGuiHolder(UUID playerId, String mineName, int page) { + this.playerId = playerId; + this.mineName = mineName; + this.page = page; + } + + @Override + public Inventory getInventory() { + return inventory; + } + + public void setInventory(Inventory inventory) { + this.inventory = inventory; + } + + public UUID getPlayerId() { + return playerId; + } + + public String getMineName() { + return mineName; + } + + public int getPage() { + return page; + } +} diff --git a/src/main/java/dev/loki/lomines/gui/mine/holder/BlocksGuiHolder.java b/src/main/java/dev/loki/lomines/gui/mine/holder/BlocksGuiHolder.java new file mode 100644 index 0000000..94d66ee --- /dev/null +++ b/src/main/java/dev/loki/lomines/gui/mine/holder/BlocksGuiHolder.java @@ -0,0 +1,38 @@ +package dev.loki.lomines.gui.mine.holder; + +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; + +import java.util.UUID; + +/** + * Inventory holder for BlocksGui. + */ +public final class BlocksGuiHolder implements InventoryHolder { + + private final UUID playerId; + private final String mineName; + private Inventory inventory; + + public BlocksGuiHolder(UUID playerId, String mineName) { + this.playerId = playerId; + this.mineName = mineName; + } + + @Override + public Inventory getInventory() { + return inventory; + } + + public void setInventory(Inventory inventory) { + this.inventory = inventory; + } + + public UUID getPlayerId() { + return playerId; + } + + public String getMineName() { + return mineName; + } +} diff --git a/src/main/java/dev/loki/lomines/gui/mine/MineEditGuiHolder.java b/src/main/java/dev/loki/lomines/gui/mine/holder/MineEditGuiHolder.java similarity index 94% rename from src/main/java/dev/loki/lomines/gui/mine/MineEditGuiHolder.java rename to src/main/java/dev/loki/lomines/gui/mine/holder/MineEditGuiHolder.java index 040aae4..74cad36 100644 --- a/src/main/java/dev/loki/lomines/gui/mine/MineEditGuiHolder.java +++ b/src/main/java/dev/loki/lomines/gui/mine/holder/MineEditGuiHolder.java @@ -1,4 +1,4 @@ -package dev.loki.lomines.gui.mine; +package dev.loki.lomines.gui.mine.holder; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; diff --git a/src/main/java/dev/loki/lomines/gui/mine/holder/ResetGuiHolder.java b/src/main/java/dev/loki/lomines/gui/mine/holder/ResetGuiHolder.java new file mode 100644 index 0000000..0bde436 --- /dev/null +++ b/src/main/java/dev/loki/lomines/gui/mine/holder/ResetGuiHolder.java @@ -0,0 +1,38 @@ +package dev.loki.lomines.gui.mine.holder; + +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; + +import java.util.UUID; + +/** + * Inventory holder for ResetGui. + */ +public final class ResetGuiHolder implements InventoryHolder { + + private final UUID playerId; + private final String mineName; + private Inventory inventory; + + public ResetGuiHolder(UUID playerId, String mineName) { + this.playerId = playerId; + this.mineName = mineName; + } + + @Override + public Inventory getInventory() { + return inventory; + } + + public void setInventory(Inventory inventory) { + this.inventory = inventory; + } + + public UUID getPlayerId() { + return playerId; + } + + public String getMineName() { + return mineName; + } +} diff --git a/src/main/java/dev/loki/lomines/gui/mine/holder/RewardsGuiHolder.java b/src/main/java/dev/loki/lomines/gui/mine/holder/RewardsGuiHolder.java new file mode 100644 index 0000000..cba3849 --- /dev/null +++ b/src/main/java/dev/loki/lomines/gui/mine/holder/RewardsGuiHolder.java @@ -0,0 +1,38 @@ +package dev.loki.lomines.gui.mine.holder; + +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; + +import java.util.UUID; + +/** + * Inventory holder for RewardsGui. + */ +public final class RewardsGuiHolder implements InventoryHolder { + + private final UUID playerId; + private final String mineName; + private Inventory inventory; + + public RewardsGuiHolder(UUID playerId, String mineName) { + this.playerId = playerId; + this.mineName = mineName; + } + + @Override + public Inventory getInventory() { + return inventory; + } + + public void setInventory(Inventory inventory) { + this.inventory = inventory; + } + + public UUID getPlayerId() { + return playerId; + } + + public String getMineName() { + return mineName; + } +} diff --git a/src/main/java/dev/loki/lomines/gui/mine/MineEditGui.java b/src/main/java/dev/loki/lomines/gui/mine/main/MineEditGui.java similarity index 84% rename from src/main/java/dev/loki/lomines/gui/mine/MineEditGui.java rename to src/main/java/dev/loki/lomines/gui/mine/main/MineEditGui.java index aefb07d..31983aa 100644 --- a/src/main/java/dev/loki/lomines/gui/mine/MineEditGui.java +++ b/src/main/java/dev/loki/lomines/gui/mine/main/MineEditGui.java @@ -1,4 +1,4 @@ -package dev.loki.lomines.gui.mine; +package dev.loki.lomines.gui.mine.main; import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.core.mine.Mine; @@ -81,23 +81,23 @@ public final class MineEditGui { return true; } case SLOT_BLOCKS -> { - player.sendMessage(Component.text("Открытие редактора блоков... (в разработке)", NamedTextColor.YELLOW)); + BlocksGui.open(plugin, player, mineName); return true; } case SLOT_RESET -> { - player.sendMessage(Component.text("Открытие редактора сброса... (в разработке)", NamedTextColor.YELLOW)); + ResetGui.open(plugin, player, mineName); return true; } case SLOT_REWARDS -> { - player.sendMessage(Component.text("Открытие редактора наград... (в разработке)", NamedTextColor.YELLOW)); + RewardsGui.open(plugin, player, mineName); return true; } case SLOT_TELEPORT -> { - player.sendMessage(Component.text("Открытие редактора телепорта... (в разработке)", NamedTextColor.YELLOW)); + player.sendMessage(Component.text("Используйте команды /lm setteleport и /lm setspawn", NamedTextColor.YELLOW)); return true; } case SLOT_UI -> { - player.sendMessage(Component.text("Открытие редактора интерфейса... (в разработке)", NamedTextColor.YELLOW)); + player.sendMessage(Component.text("Настройки UI редактируются в конфиге", NamedTextColor.YELLOW)); return true; } case SLOT_SAVE -> { @@ -115,4 +115,3 @@ public final class MineEditGui { } return true; } -} diff --git a/src/main/java/dev/loki/lomines/gui/mine/MineEditItems.java b/src/main/java/dev/loki/lomines/gui/mine/main/MineEditItems.java similarity index 99% rename from src/main/java/dev/loki/lomines/gui/mine/MineEditItems.java rename to src/main/java/dev/loki/lomines/gui/mine/main/MineEditItems.java index fd2afa9..2136881 100644 --- a/src/main/java/dev/loki/lomines/gui/mine/MineEditItems.java +++ b/src/main/java/dev/loki/lomines/gui/mine/main/MineEditItems.java @@ -1,4 +1,4 @@ -package dev.loki.lomines.gui.mine; +package dev.loki.lomines.gui.mine.main; import dev.loki.lomines.core.mine.Mine; import dev.loki.lomines.data.config.MineConfig; diff --git a/src/main/java/dev/loki/lomines/integration/hologram/HologramManager.java b/src/main/java/dev/loki/lomines/integration/hologram/HologramManager.java new file mode 100644 index 0000000..ed7ea71 --- /dev/null +++ b/src/main/java/dev/loki/lomines/integration/hologram/HologramManager.java @@ -0,0 +1,202 @@ +package dev.loki.lomines.integration.hologram; + +import dev.loki.lomines.LoMinesPlugin; +import dev.loki.lomines.core.mine.Mine; +import dev.loki.lomines.data.config.ui.HologramConfig; +import dev.loki.lomines.integration.hologram.provider.DecentHologramsProvider; +import dev.loki.lomines.integration.hologram.provider.HolographicDisplaysProvider; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.bukkit.Location; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Manages holograms for all mines. + * Displays mine information above each mine region. + */ +public final class HologramManager { + + private final LoMinesPlugin plugin; + private HologramProvider provider; + private final Map activeTasks = new ConcurrentHashMap<>(); + + public HologramManager(LoMinesPlugin plugin) { + this.plugin = plugin; + initializeProvider(); + } + + private void initializeProvider() { + // Try HolographicDisplays first, then DecentHolograms + HolographicDisplaysProvider hdProvider = new HolographicDisplaysProvider(); + if (hdProvider.isAvailable()) { + this.provider = hdProvider; + plugin.loLogger().info("Using HolographicDisplays for holograms"); + return; + } + + DecentHologramsProvider dhProvider = new DecentHologramsProvider(); + if (dhProvider.isAvailable()) { + this.provider = dhProvider; + plugin.loLogger().info("Using DecentHolograms for holograms"); + return; + } + + plugin.loLogger().warn("No hologram provider found. Install HolographicDisplays or DecentHolograms."); + } + + /** + * Creates a hologram for a mine if enabled in config. + */ + public void createMineHologram(Mine mine) { + if (provider == null || !provider.isAvailable()) return; + + HologramConfig config = mine.getConfig().ui().hologram(); + if (!config.enabled()) return; + + String id = "lomines_" + mine.getName(); + Location loc = calculateHologramLocation(mine, config.height()); + List lines = formatLines(config.format(), mine); + + provider.createHologram(id, loc, lines); + startUpdateTask(mine); + } + + /** + * Updates a mine's hologram with current data. + */ + public void updateMineHologram(Mine mine) { + if (provider == null || !provider.isAvailable()) return; + + String id = "lomines_" + mine.getName(); + if (!provider.exists(id)) return; + + HologramConfig config = mine.getConfig().ui().hologram(); + List lines = formatLines(config.format(), mine); + provider.updateHologram(id, lines); + } + + /** + * Deletes a mine's hologram. + */ + public void deleteMineHologram(String mineName) { + if (provider == null) return; + + String id = "lomines_" + mineName; + provider.deleteHologram(id); + + UUID taskId = activeTasks.remove(mineName); + if (taskId != null) { + plugin.getServer().getScheduler().cancelTask(taskId); + } + } + + /** + * Toggles hologram for a mine. + */ + public boolean toggleHologram(Mine mine) { + if (provider == null || !provider.isAvailable()) return false; + + String id = "lomines_" + mine.getName(); + boolean currentlyEnabled = provider.exists(id); + + if (currentlyEnabled) { + deleteMineHologram(mine.getName()); + } else { + createMineHologram(mine); + } + + return !currentlyEnabled; + } + + private void startUpdateTask(Mine mine) { + String mineName = mine.getName(); + + // Cancel existing task if any + UUID existingTask = activeTasks.remove(mineName); + if (existingTask != null) { + plugin.getServer().getScheduler().cancelTask(existingTask); + } + + // Start new update task (every second) + int taskId = plugin.getServer().getScheduler().scheduleSyncRepeatingTask( + plugin, + () -> updateMineHologram(mine), + 20L, // Initial delay (1 second) + 20L // Period (1 second) + ); + + activeTasks.put(mineName, new UUID(0, taskId)); + } + + private Location calculateHologramLocation(Mine mine, double height) { + if (mine.getRegions().isEmpty()) { + return null; + } + + var region = mine.getRegions().get(0); + double centerX = (region.getMinX() + region.getMaxX()) / 2.0 + 0.5; + double centerZ = (region.getMinZ() + region.getMaxZ()) / 2.0 + 0.5; + double centerY = region.getMaxY() + height; + + return new Location(region.getWorld(), centerX, centerY, centerZ); + } + + private List formatLines(List format, Mine mine) { + List result = new ArrayList<>(); + MiniMessage miniMessage = MiniMessage.miniMessage(); + + for (String line : format) { + String formatted = line + .replace("{mine}", mine.getName()) + .replace("{percent}", String.format("%.1f", mine.getPercentFilled())) + .replace("{bar}", createProgressBar(mine.getPercentFilled())) + .replace("{time}", formatResetTime(mine)); + + result.add(formatted); + } + + return result; + } + + private String createProgressBar(double percent) { + int filled = (int) Math.round(percent / 10.0); + int empty = 10 - filled; + + StringBuilder bar = new StringBuilder(); + bar.append(""); + bar.append("█".repeat(Math.max(0, filled))); + bar.append(""); + bar.append("░".repeat(Math.max(0, empty))); + bar.append(""); + + return bar.toString(); + } + + private String formatResetTime(Mine mine) { + int ticks = mine.getTicks(); + int seconds = (int) ((6000 - ticks) / 20.0); // Assuming 5min = 6000 ticks + if (seconds < 0) seconds = 0; + + int mins = seconds / 60; + int secs = seconds % 60; + return String.format("%02d:%02d", mins, secs); + } + + /** + * Cleans up all holograms on shutdown. + */ + public void shutdown() { + for (String mineName : new ArrayList<>(activeTasks.keySet())) { + deleteMineHologram(mineName); + } + activeTasks.clear(); + } + + /** + * Gets the current provider name, or null if none. + */ + public String getProviderName() { + return provider != null ? provider.getName() : null; + } +} diff --git a/src/main/java/dev/loki/lomines/integration/hologram/HologramProvider.java b/src/main/java/dev/loki/lomines/integration/hologram/HologramProvider.java new file mode 100644 index 0000000..a2fed97 --- /dev/null +++ b/src/main/java/dev/loki/lomines/integration/hologram/HologramProvider.java @@ -0,0 +1,70 @@ +package dev.loki.lomines.integration.hologram; + +import org.bukkit.Location; + +import java.util.List; + +/** + * Interface for hologram providers. + * Implementations handle creation and updates of holograms. + */ +public interface HologramProvider { + + /** + * Creates a new hologram at the specified location. + * + * @param id unique identifier for this hologram + * @param location where to place the hologram + * @param lines initial lines to display + * @return true if hologram was created successfully + */ + boolean createHologram(String id, Location location, List lines); + + /** + * Updates the lines of an existing hologram. + * + * @param id the hologram identifier + * @param lines new lines to display + * @return true if updated successfully + */ + boolean updateHologram(String id, List lines); + + /** + * Moves a hologram to a new location. + * + * @param id the hologram identifier + * @param newLocation new location + * @return true if moved successfully + */ + boolean moveHologram(String id, Location newLocation); + + /** + * Deletes a hologram. + * + * @param id the hologram identifier + * @return true if deleted successfully + */ + boolean deleteHologram(String id); + + /** + * Checks if a hologram exists. + * + * @param id the hologram identifier + * @return true if hologram exists + */ + boolean exists(String id); + + /** + * Checks if this provider is available (plugin is installed). + * + * @return true if the provider can be used + */ + boolean isAvailable(); + + /** + * Gets the name of this provider. + * + * @return provider name + */ + String getName(); +} diff --git a/src/main/java/dev/loki/lomines/integration/hologram/provider/DecentHologramsProvider.java b/src/main/java/dev/loki/lomines/integration/hologram/provider/DecentHologramsProvider.java new file mode 100644 index 0000000..d06883a --- /dev/null +++ b/src/main/java/dev/loki/lomines/integration/hologram/provider/DecentHologramsProvider.java @@ -0,0 +1,104 @@ +package dev.loki.lomines.integration.hologram.provider; + +import eu.decentsoftware.holograms.api.DHAPI; +import eu.decentsoftware.holograms.api.holograms.Hologram; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.plugin.Plugin; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Hologram provider implementation for DecentHolograms plugin. + */ +public final class DecentHologramsProvider implements HologramProvider { + + private final Map holograms = new ConcurrentHashMap<>(); + private final boolean available; + + public DecentHologramsProvider() { + this.available = checkAvailability(); + } + + private boolean checkAvailability() { + Plugin plugin = Bukkit.getPluginManager().getPlugin("DecentHolograms"); + return plugin != null && plugin.isEnabled(); + } + + @Override + public boolean createHologram(String id, Location location, List lines) { + if (!available) return false; + if (holograms.containsKey(id)) return false; + + try { + Hologram hologram = DHAPI.createHologram(id, location, lines); + holograms.put(id, hologram); + return true; + } catch (Exception e) { + return false; + } + } + + @Override + public boolean updateHologram(String id, List lines) { + if (!available) return false; + + Hologram hologram = holograms.get(id); + if (hologram == null) return false; + + try { + DHAPI.setHologramLines(hologram, lines); + return true; + } catch (Exception e) { + return false; + } + } + + @Override + public boolean moveHologram(String id, Location newLocation) { + if (!available) return false; + + Hologram hologram = holograms.get(id); + if (hologram == null) return false; + + try { + hologram.setLocation(newLocation); + return true; + } catch (Exception e) { + return false; + } + } + + @Override + public boolean deleteHologram(String id) { + if (!available) return false; + + Hologram hologram = holograms.remove(id); + if (hologram == null) return false; + + try { + hologram.destroy(); + return true; + } catch (Exception e) { + return false; + } + } + + @Override + public boolean exists(String id) { + if (!available) return false; + return DHAPI.getHologram(id) != null; + } + + @Override + public boolean isAvailable() { + return available; + } + + @Override + public String getName() { + return "DecentHolograms"; + } +} diff --git a/src/main/java/dev/loki/lomines/integration/hologram/provider/HolographicDisplaysProvider.java b/src/main/java/dev/loki/lomines/integration/hologram/provider/HolographicDisplaysProvider.java new file mode 100644 index 0000000..8e628ed --- /dev/null +++ b/src/main/java/dev/loki/lomines/integration/hologram/provider/HolographicDisplaysProvider.java @@ -0,0 +1,116 @@ +package dev.loki.lomines.integration.hologram.provider; + +import com.gmail.filoghost.holographicdisplays.api.Hologram; +import com.gmail.filoghost.holographicdisplays.api.HologramsAPI; +import dev.loki.lomines.LoMinesPlugin; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.plugin.Plugin; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Hologram provider implementation for HolographicDisplays plugin. + */ +public final class HolographicDisplaysProvider implements HologramProvider { + + private final Map holograms = new ConcurrentHashMap<>(); + private final boolean available; + + public HolographicDisplaysProvider() { + this.available = checkAvailability(); + } + + private boolean checkAvailability() { + Plugin plugin = Bukkit.getPluginManager().getPlugin("HolographicDisplays"); + return plugin != null && plugin.isEnabled(); + } + + @Override + public boolean createHologram(String id, Location location, List lines) { + if (!available) return false; + if (holograms.containsKey(id)) return false; + + try { + Plugin hdPlugin = Bukkit.getPluginManager().getPlugin("HolographicDisplays"); + if (hdPlugin == null) return false; + + Hologram hologram = HologramsAPI.createHologram(hdPlugin, location); + for (String line : lines) { + hologram.appendTextLine(line); + } + holograms.put(id, hologram); + return true; + } catch (Exception e) { + return false; + } + } + + @Override + public boolean updateHologram(String id, List lines) { + if (!available) return false; + + Hologram hologram = holograms.get(id); + if (hologram == null || hologram.isDeleted()) return false; + + try { + hologram.clearLines(); + for (String line : lines) { + hologram.appendTextLine(line); + } + return true; + } catch (Exception e) { + return false; + } + } + + @Override + public boolean moveHologram(String id, Location newLocation) { + if (!available) return false; + + Hologram hologram = holograms.get(id); + if (hologram == null || hologram.isDeleted()) return false; + + try { + hologram.teleport(newLocation); + return true; + } catch (Exception e) { + return false; + } + } + + @Override + public boolean deleteHologram(String id) { + if (!available) return false; + + Hologram hologram = holograms.remove(id); + if (hologram == null) return false; + + try { + hologram.delete(); + return true; + } catch (Exception e) { + return false; + } + } + + @Override + public boolean exists(String id) { + if (!available) return false; + + Hologram hologram = holograms.get(id); + return hologram != null && !hologram.isDeleted(); + } + + @Override + public boolean isAvailable() { + return available; + } + + @Override + public String getName() { + return "HolographicDisplays"; + } +} diff --git a/src/main/java/dev/loki/lomines/listener/MineEditGuiListener.java b/src/main/java/dev/loki/lomines/listener/MineEditGuiListener.java index adc9da0..5292e47 100644 --- a/src/main/java/dev/loki/lomines/listener/MineEditGuiListener.java +++ b/src/main/java/dev/loki/lomines/listener/MineEditGuiListener.java @@ -3,18 +3,27 @@ package dev.loki.lomines.listener; 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.MineEditGui; -import dev.loki.lomines.gui.mine.MineEditGuiHolder; +import dev.loki.lomines.gui.mine.edit.BlockMaterialSelectionGui; +import dev.loki.lomines.gui.mine.edit.BlocksGui; +import dev.loki.lomines.gui.mine.edit.ResetGui; +import dev.loki.lomines.gui.mine.edit.RewardsGui; +import dev.loki.lomines.gui.mine.holder.BlockMaterialSelectionGuiHolder; +import dev.loki.lomines.gui.mine.holder.BlocksGuiHolder; +import dev.loki.lomines.gui.mine.holder.ResetGuiHolder; +import dev.loki.lomines.gui.mine.holder.RewardsGuiHolder; +import dev.loki.lomines.gui.mine.holder.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 the mine editor GUIs. - * Covers MineEditGui and ConfirmDeleteGui. + * Handles clicks in all mine editor GUIs. + * Covers MineEditGui, BlocksGui, ResetGui, RewardsGui, ConfirmDeleteGui, and BlockMaterialSelectionGui. */ public final class MineEditGuiListener implements Listener { @@ -30,44 +39,138 @@ public final class MineEditGuiListener implements Listener { // Handle MineEditGui if (view.getTopInventory().getHolder() instanceof 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 >= view.getTopInventory().getSize()) { - return; - } - MineEditGui.handleClick(plugin, player, raw, holder.getMineName()); + handleMineEditGui(event, holder); + 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) { - 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 >= view.getTopInventory().getSize()) { - return; - } - ConfirmDeleteGui.handleClick(plugin, player, raw, holder.getMineName()); + 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; + MineEditGui.handleClick(plugin, player, raw, holder.getMineName()); + } + + private 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; + + ClickType click = event.getClick(); + boolean left = click.isLeftClick(); + boolean right = click.isRightClick(); + boolean shift = click.isShiftClick(); + + BlocksGui.handleClick(plugin, player, raw, holder.getMineName(), + left, shift, right, + BlocksGui.getBlockAtSlot(event.getView().getTopInventory(), raw, plugin, holder.getMineName())); + } + + private 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; + + 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; + if (!holder.getPlayerId().equals(player.getUniqueId())) { + event.setCancelled(true); + return; + } + event.setCancelled(true); + int raw = event.getRawSlot(); + 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; + if (!holder.getPlayerId().equals(player.getUniqueId())) { + event.setCancelled(true); + return; + } + event.setCancelled(true); + int raw = event.getRawSlot(); + if (raw >= event.getView().getTopInventory().getSize()) return; + + ClickType click = event.getClick(); + int rewardIndex = raw < 45 ? raw : -1; + RewardsGui.handleClick(plugin, player, raw, holder.getMineName(), + click.isLeftClick(), click.isRightClick(), rewardIndex); + } + + private 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; + ConfirmDeleteGui.handleClick(plugin, player, raw, holder.getMineName()); + } + @EventHandler public void onInventoryDrag(InventoryDragEvent event) { - if (event.getInventory().getHolder() instanceof MineEditGuiHolder || - event.getInventory().getHolder() instanceof ConfirmDeleteGuiHolder) { + 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); } }