Add Holograms integration and Sub-GUI editors

Holograms:
- HologramManager with auto-detection of HolographicDisplays/DecentHolograms
- HologramProvider interface and implementations
- HologramConfig for per-mine configuration
- /lm hologram <mine> [on|off] command
- Updates every second showing mine name, fill %, progress bar, and timer

Sub-GUI Editors:
- BlocksGui - edit block weights with +/- clicks
- BlockMaterialSelectionGui - paginated material picker
- ResetGui - configure reset interval, percent trigger, message, commands
- RewardsGui - manage reward entries
- All holders moved to gui/mine/holder/
- Editors moved to gui/mine/edit/
- Main GUI (MineEditGui) moved to gui/mine/main/

Package structure reorganized to maintain max 6 files per folder.
This commit is contained in:
loki5512344 2026-06-01 10:38:00 +02:00
parent 46a9c98af6
commit 11f31e5b7a
22 changed files with 1682 additions and 45 deletions

View file

@ -4,6 +4,7 @@ import dev.loki.lomines.core.mine.MineTicker;
import dev.loki.lomines.core.mine.Mines; import dev.loki.lomines.core.mine.Mines;
import dev.loki.lomines.data.stats.StatsManager; import dev.loki.lomines.data.stats.StatsManager;
import dev.loki.lomines.integration.IntegrationManager; import dev.loki.lomines.integration.IntegrationManager;
import dev.loki.lomines.integration.hologram.HologramManager;
import dev.loki.lomines.wand.WandParticleService; import dev.loki.lomines.wand.WandParticleService;
import dev.loki.lomines.wand.group.GroupWandManager; import dev.loki.lomines.wand.group.GroupWandManager;
import dev.lolib.commands.CommandManager; import dev.lolib.commands.CommandManager;
@ -23,6 +24,7 @@ public final class LoMinesPlugin extends LoPlugin {
private CommandManager commandManager; private CommandManager commandManager;
private IntegrationManager integrationManager; private IntegrationManager integrationManager;
private WandParticleService wandParticleService; private WandParticleService wandParticleService;
private HologramManager hologramManager;
@Override @Override
protected void enable() { protected void enable() {
@ -39,6 +41,7 @@ public final class LoMinesPlugin extends LoPlugin {
this.commandManager = components.commandManager(); this.commandManager = components.commandManager();
this.integrationManager = components.integrationManager(); this.integrationManager = components.integrationManager();
this.wandParticleService = new WandParticleService(this); this.wandParticleService = new WandParticleService(this);
this.hologramManager = new HologramManager(this);
initializer.loadMines(mines); initializer.loadMines(mines);
this.mineTicker = initializer.startTicker(mines); this.mineTicker = initializer.startTicker(mines);
@ -85,6 +88,10 @@ public final class LoMinesPlugin extends LoPlugin {
wandParticleService.stopAll(); wandParticleService.stopAll();
} }
if (hologramManager != null) {
hologramManager.shutdown();
}
loLogger().info("LoMines has been disabled!"); loLogger().info("LoMines has been disabled!");
} catch (Exception e) { } catch (Exception e) {
loLogger().error("Error during plugin shutdown: " + e.getMessage()); loLogger().error("Error during plugin shutdown: " + e.getMessage());
@ -122,4 +129,8 @@ public final class LoMinesPlugin extends LoPlugin {
public WandParticleService getWandParticleService() { public WandParticleService getWandParticleService() {
return wandParticleService; return wandParticleService;
} }
public HologramManager getHologramManager() {
return hologramManager;
}
} }

View file

@ -1,6 +1,7 @@
package dev.loki.lomines; package dev.loki.lomines;
import dev.loki.lomines.command.admin.AdminCommands; 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.MaskCommands;
import dev.loki.lomines.command.admin.StatsCommands; import dev.loki.lomines.command.admin.StatsCommands;
import dev.loki.lomines.command.common.LoMinesTabCompleter; import dev.loki.lomines.command.common.LoMinesTabCompleter;
@ -29,6 +30,7 @@ final class RegistrationManager {
commandManager.register(new PlayerCommands(plugin)); commandManager.register(new PlayerCommands(plugin));
commandManager.register(new StatsCommands(plugin)); commandManager.register(new StatsCommands(plugin));
commandManager.register(new MaskCommands(plugin)); commandManager.register(new MaskCommands(plugin));
commandManager.register(new HologramCommands(plugin));
plugin.loLogger().info("Commands registered"); plugin.loLogger().info("Commands registered");
} }

View file

@ -3,7 +3,7 @@ package dev.loki.lomines.command.admin;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.core.mine.Mine; import dev.loki.lomines.core.mine.Mine;
import dev.loki.lomines.core.mine.Mines; 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.ErrorHandler;
import dev.loki.lomines.util.MessageFormatter; import dev.loki.lomines.util.MessageFormatter;
import dev.lolib.commands.annotation.Arg; import dev.lolib.commands.annotation.Arg;

View file

@ -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 <mine> [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выключена")));
}
}

View file

@ -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<String> 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(
"<gold><bold>{mine}",
"{bar} <yellow>{percent}%",
"<gray>Сброс через: <yellow>{time}"
),
2.5
);
}
}

View file

@ -12,9 +12,14 @@ public record UIConfig(
boolean actionBarEnabled, boolean actionBarEnabled,
String actionBarFormat, String actionBarFormat,
double actionBarRange, double actionBarRange,
String timerFormat String timerFormat,
HologramConfig hologram
) { ) {
public UIConfig {
hologram = hologram != null ? hologram : HologramConfig.disabled();
}
public static final String DEFAULT_ACTIONBAR_FORMAT = "<green>{mine}</green> <gray>{percent}%</gray> <dark_gray>({time})"; public static final String DEFAULT_ACTIONBAR_FORMAT = "<green>{mine}</green> <gray>{percent}%</gray> <dark_gray>({time})";
public static final String DEFAULT_TIMER_FORMAT = "mm:ss"; public static final String DEFAULT_TIMER_FORMAT = "mm:ss";
@ -70,13 +75,13 @@ public record UIConfig(
* Default configuration. * Default configuration.
*/ */
public static UIConfig defaults() { 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). * Disabled configuration (no UI).
*/ */
public static UIConfig disabled() { public static UIConfig disabled() {
return new UIConfig(false, "", 0, DEFAULT_TIMER_FORMAT); return new UIConfig(false, "", 0, DEFAULT_TIMER_FORMAT, HologramConfig.disabled());
} }
} }

View file

@ -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<Material> 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;
}
}

View file

@ -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<BlockKey, Double> 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<Map.Entry<BlockKey, Double>> sortedEntries = new ArrayList<>(weights.entrySet());
sortedEntries.sort((a, b) -> Double.compare(b.getValue(), a.getValue()));
int slot = 0;
for (Map.Entry<BlockKey, Double> 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<BlockKey, Double> 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<Map.Entry<BlockKey, Double>> 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;
}
}

View file

@ -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);
}
}

View file

@ -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<RewardConfig.RewardEntry> 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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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.Inventory;
import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.InventoryHolder;

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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.LoMinesPlugin;
import dev.loki.lomines.core.mine.Mine; import dev.loki.lomines.core.mine.Mine;
@ -81,23 +81,23 @@ public final class MineEditGui {
return true; return true;
} }
case SLOT_BLOCKS -> { case SLOT_BLOCKS -> {
player.sendMessage(Component.text("Открытие редактора блоков... (в разработке)", NamedTextColor.YELLOW)); BlocksGui.open(plugin, player, mineName);
return true; return true;
} }
case SLOT_RESET -> { case SLOT_RESET -> {
player.sendMessage(Component.text("Открытие редактора сброса... (в разработке)", NamedTextColor.YELLOW)); ResetGui.open(plugin, player, mineName);
return true; return true;
} }
case SLOT_REWARDS -> { case SLOT_REWARDS -> {
player.sendMessage(Component.text("Открытие редактора наград... (в разработке)", NamedTextColor.YELLOW)); RewardsGui.open(plugin, player, mineName);
return true; return true;
} }
case SLOT_TELEPORT -> { case SLOT_TELEPORT -> {
player.sendMessage(Component.text("Открытие редактора телепорта... (в разработке)", NamedTextColor.YELLOW)); player.sendMessage(Component.text("Используйте команды /lm setteleport и /lm setspawn", NamedTextColor.YELLOW));
return true; return true;
} }
case SLOT_UI -> { case SLOT_UI -> {
player.sendMessage(Component.text("Открытие редактора интерфейса... (в разработке)", NamedTextColor.YELLOW)); player.sendMessage(Component.text("Настройки UI редактируются в конфиге", NamedTextColor.YELLOW));
return true; return true;
} }
case SLOT_SAVE -> { case SLOT_SAVE -> {
@ -115,4 +115,3 @@ public final class MineEditGui {
} }
return true; return true;
} }
}

View file

@ -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.core.mine.Mine;
import dev.loki.lomines.data.config.MineConfig; import dev.loki.lomines.data.config.MineConfig;

View file

@ -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<String, UUID> 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<String> 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<String> 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<String> formatLines(List<String> format, Mine mine) {
List<String> 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("<green>");
bar.append("█".repeat(Math.max(0, filled)));
bar.append("<gray>");
bar.append("░".repeat(Math.max(0, empty)));
bar.append("</gray></green>");
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;
}
}

View file

@ -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<String> 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<String> 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();
}

View file

@ -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<String, Hologram> 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<String> 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<String> 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";
}
}

View file

@ -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<String, Hologram> 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<String> 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<String> 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";
}
}

View file

@ -3,18 +3,27 @@ package dev.loki.lomines.listener;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.gui.confirm.ConfirmDeleteGui; import dev.loki.lomines.gui.confirm.ConfirmDeleteGui;
import dev.loki.lomines.gui.confirm.ConfirmDeleteGuiHolder; import dev.loki.lomines.gui.confirm.ConfirmDeleteGuiHolder;
import dev.loki.lomines.gui.mine.MineEditGui; import dev.loki.lomines.gui.mine.edit.BlockMaterialSelectionGui;
import dev.loki.lomines.gui.mine.MineEditGuiHolder; 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.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryDragEvent; import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.InventoryView;
/** /**
* Handles clicks in the mine editor GUIs. * Handles clicks in all mine editor GUIs.
* Covers MineEditGui and ConfirmDeleteGui. * Covers MineEditGui, BlocksGui, ResetGui, RewardsGui, ConfirmDeleteGui, and BlockMaterialSelectionGui.
*/ */
public final class MineEditGuiListener implements Listener { public final class MineEditGuiListener implements Listener {
@ -30,44 +39,138 @@ public final class MineEditGuiListener implements Listener {
// Handle MineEditGui // Handle MineEditGui
if (view.getTopInventory().getHolder() instanceof MineEditGuiHolder holder) { if (view.getTopInventory().getHolder() instanceof MineEditGuiHolder holder) {
if (!(event.getWhoClicked() instanceof Player player)) { handleMineEditGui(event, holder);
return; return;
} }
if (!holder.getPlayerId().equals(player.getUniqueId())) {
event.setCancelled(true); // Handle BlocksGui
return; if (view.getTopInventory().getHolder() instanceof BlocksGuiHolder holder) {
} handleBlocksGui(event, holder);
event.setCancelled(true); return;
int raw = event.getRawSlot(); }
if (raw >= view.getTopInventory().getSize()) {
return; // Handle BlockMaterialSelectionGui
} if (view.getTopInventory().getHolder() instanceof BlockMaterialSelectionGuiHolder holder) {
MineEditGui.handleClick(plugin, player, raw, holder.getMineName()); 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; return;
} }
// Handle ConfirmDeleteGui // Handle ConfirmDeleteGui
if (view.getTopInventory().getHolder() instanceof ConfirmDeleteGuiHolder holder) { if (view.getTopInventory().getHolder() instanceof ConfirmDeleteGuiHolder holder) {
if (!(event.getWhoClicked() instanceof Player player)) { handleConfirmDeleteGui(event, holder);
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());
} }
} }
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 @EventHandler
public void onInventoryDrag(InventoryDragEvent event) { public void onInventoryDrag(InventoryDragEvent event) {
if (event.getInventory().getHolder() instanceof MineEditGuiHolder || var holder = event.getInventory().getHolder();
event.getInventory().getHolder() instanceof ConfirmDeleteGuiHolder) { if (holder instanceof MineEditGuiHolder ||
holder instanceof BlocksGuiHolder ||
holder instanceof BlockMaterialSelectionGuiHolder ||
holder instanceof ResetGuiHolder ||
holder instanceof RewardsGuiHolder ||
holder instanceof ConfirmDeleteGuiHolder) {
event.setCancelled(true); event.setCancelled(true);
} }
} }