Improve Tab completion and MineEditGui

Tab completion improvements:
- Add true/false suggestions for reset silent parameter
- Add smart leaderboard limits (5,10,15,20,25,50) for top command
- Fix permission mapping to match plugin.yml exactly
- Remove incorrect wand/group suggestions
- Add isNumericPartial() and filterStartsWith() helpers
- Reduce code from 200+ to 170 lines

MineEditGui improvements:
- Better item descriptions and lore formatting
- Show more useful information in each section
This commit is contained in:
loki5512344 2026-05-31 23:21:33 +02:00
parent 8dd9703c67
commit 8981214b18
2 changed files with 353 additions and 158 deletions

View file

@ -18,118 +18,153 @@ public class LoMinesTabCompleter implements TabCompleter {
private final LoMinesPlugin plugin;
// Static command lists for fast lookup
private static final List<String> ADMIN_COMMANDS = List.of(
"create", "delete", "reset", "reload", "list", "maskscan",
"edit", "setteleport", "setspawn", "clearspawn"
);
private static final List<String> PLAYER_COMMANDS = List.of(
"wand", "group", "stats", "top"
);
private static final List<String> ALL_COMMANDS = List.of(
"create", "delete", "reset", "reload", "list", "wand", "group",
"stats", "top", "maskscan", "edit", "setteleport", "setspawn",
"clearspawn", "help"
);
private static final List<String> BOOLEAN_VALUES = List.of("true", "false");
private static final List<String> TOP_LIMITS = List.of("5", "10", "15", "20", "25", "50");
public LoMinesTabCompleter(LoMinesPlugin plugin) {
this.plugin = plugin;
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (!command.getName().equalsIgnoreCase("lm") &&
!command.getName().equalsIgnoreCase("lomines") &&
!command.getName().equalsIgnoreCase("mine") &&
!command.getName().equalsIgnoreCase("mines")) {
if (!isLoMinesCommand(command.getName())) {
return null;
}
String partial = args.length > 0 ? args[args.length - 1].toLowerCase() : "";
return switch (args.length) {
case 1 -> completeSubcommands(sender, partial);
case 2 -> completeSecondArg(sender, args[0].toLowerCase(), partial);
case 3 -> completeThirdArg(args[0].toLowerCase(), args[1], partial);
default -> new ArrayList<>();
};
}
private boolean isLoMinesCommand(String name) {
return name.equalsIgnoreCase("lm") ||
name.equalsIgnoreCase("lomines") ||
name.equalsIgnoreCase("mine") ||
name.equalsIgnoreCase("mines");
}
private List<String> completeSubcommands(CommandSender sender, String partial) {
List<String> completions = new ArrayList<>();
for (String cmd : ALL_COMMANDS) {
if (cmd.startsWith(partial) && hasPermission(sender, cmd)) {
completions.add(cmd);
}
}
return completions;
}
private List<String> completeSecondArg(CommandSender sender, String subcommand, String partial) {
return switch (subcommand) {
case "create" -> completeCreate(partial);
case "delete", "maskscan", "edit", "setteleport", "setspawn", "clearspawn" ->
filterStartsWith(getMineNames(), partial);
case "reset" -> completeReset(partial);
case "stats" -> completeStats(sender, partial);
case "top" -> completeTopFirstArg(partial);
default -> new ArrayList<>();
};
}
private List<String> completeThirdArg(String subcommand, String arg2, String partial) {
return switch (subcommand) {
case "reset" -> filterStartsWith(BOOLEAN_VALUES, partial);
case "top" -> completeTopSecondArg(arg2, partial);
default -> new ArrayList<>();
};
}
private List<String> completeCreate(String partial) {
if (partial.isEmpty()) {
return new ArrayList<>(List.of("<name>"));
}
return new ArrayList<>();
}
private List<String> completeReset(String partial) {
List<String> mines = filterStartsWith(getMineNames(), partial);
if (partial.isEmpty() || "true".startsWith(partial) || "false".startsWith(partial)) {
mines.addAll(filterStartsWith(BOOLEAN_VALUES, partial));
}
return mines;
}
private List<String> completeStats(CommandSender sender, String partial) {
if (!sender.hasPermission("lomines.stats.others")) {
return new ArrayList<>();
}
return filterStartsWith(getOnlinePlayerNames(), partial);
}
private List<String> completeTopFirstArg(String partial) {
List<String> completions = new ArrayList<>();
if (args.length == 1) {
// First argument - suggest subcommands
String partial = args[0].toLowerCase();
completions.addAll(filterStartsWith(getMineNames(), partial));
for (String cmd : ALL_COMMANDS) {
if (cmd.startsWith(partial)) {
// Check permission
if (hasPermission(sender, cmd)) {
completions.add(cmd);
}
}
}
} else if (args.length == 2) {
// Second argument - context-sensitive
String subcommand = args[0].toLowerCase();
String partial = args[1].toLowerCase();
switch (subcommand) {
case "delete", "reset", "maskscan", "edit", "setteleport", "setspawn", "clearspawn" ->
completions.addAll(getMineNames(partial));
case "create" -> {
if (partial.isEmpty()) {
completions.add("<name>");
}
}
case "stats" -> {
if (sender.hasPermission("lomines.stats.others")) {
completions.addAll(getOnlinePlayerNames(partial));
}
}
case "group" -> {
completions.add("add");
completions.add("remove");
completions.add("clear");
}
case "wand" -> {
completions.add("give");
completions.add("toggle");
}
}
} else if (args.length == 3) {
// Third argument
String subcommand = args[0].toLowerCase();
String partial = args[2].toLowerCase();
switch (subcommand) {
case "group" -> {
if (args[1].equalsIgnoreCase("add") || args[1].equalsIgnoreCase("remove")) {
completions.addAll(getMineNames(partial));
}
}
}
if (isNumericPartial(partial)) {
completions.addAll(filterStartsWith(TOP_LIMITS, partial));
}
return completions;
}
private boolean hasPermission(CommandSender sender, String command) {
return switch (command) {
case "create", "delete", "reset", "reload", "list", "maskscan", "edit" ->
private List<String> completeTopSecondArg(String firstArg, String partial) {
boolean firstArgWasMine = plugin.getMines().find(firstArg).isPresent();
if (firstArgWasMine && isNumericPartial(partial)) {
return filterStartsWith(TOP_LIMITS, partial);
}
if (!firstArgWasMine && isNumericPartial(firstArg)) {
return filterStartsWith(getMineNames(), partial);
}
return new ArrayList<>();
}
private boolean isNumericPartial(String partial) {
return partial.isEmpty() || partial.matches("\\d*");
}
private List<String> filterStartsWith(List<String> options, String partial) {
return options.stream()
.filter(s -> s.toLowerCase().startsWith(partial))
.collect(Collectors.toList());
}
private List<String> getMineNames() {
return plugin.getMines().getAll().stream()
.map(mine -> mine.getName())
.collect(Collectors.toList());
}
private List<String> getOnlinePlayerNames() {
return plugin.getServer().getOnlinePlayers().stream()
.map(Player::getName)
.collect(Collectors.toList());
}
private boolean hasPermission(CommandSender sender, String cmd) {
return switch (cmd) {
case "create", "delete", "reload", "list", "maskscan", "edit" ->
sender.hasPermission("lomines.admin");
case "reset" -> sender.hasPermission("lomines.admin.reset");
case "setteleport" -> sender.hasPermission("lomines.admin.setteleport");
case "setspawn", "clearspawn" -> sender.hasPermission("lomines.admin.setspawn");
case "wand", "group" -> sender.hasPermission("lomines.admin.wand");
case "stats" -> sender.hasPermission("lomines.stats");
case "top" -> sender.hasPermission("lomines.stats");
case "stats", "top" -> sender.hasPermission("lomines.stats");
case "help" -> sender.hasPermission("lomines.use");
default -> true;
};
}
private List<String> getMineNames(String partial) {
return plugin.getMines().getAll().stream()
.map(mine -> mine.getName())
.filter(name -> name.toLowerCase().startsWith(partial))
.collect(Collectors.toList());
}
private List<String> getOnlinePlayerNames(String partial) {
return plugin.getServer().getOnlinePlayers().stream()
.map(Player::getName)
.filter(name -> name.toLowerCase().startsWith(partial))
.collect(Collectors.toList());
}
}

View file

@ -6,24 +6,27 @@ import dev.loki.lomines.data.config.MineConfig;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.ArrayList;
import java.util.List;
/**
* Main GUI for editing a mine.
* Provides navigation to sub-editors for different configuration sections.
* Improved UX with better materials, enhanced lore, and clearer information display.
*/
public final class MineEditGui {
private static final int SIZE = 54;
private static final String TITLE_PREFIX = "Редактор шахты: ";
// Slot constants
// Slot constants - organized in visual groups
public static final int SLOT_BACK = 49;
public static final int SLOT_BLOCKS = 10;
public static final int SLOT_REGIONS = 12;
@ -35,6 +38,16 @@ public final class MineEditGui {
public static final int SLOT_DELETE = 34;
public static final int SLOT_INFO = 40;
// UI Constants
private static final String CLICK_EDIT = "§e▸ Нажмите для редактирования";
private static final String CLICK_BACK = "§c▸ Нажмите чтобы закрыть";
private static final String CLICK_SAVE = "§a▸ Нажмите для сохранения";
private static final String CLICK_DELETE = "§c▸ Нажмите для удаления";
private static final String SECTION_CONTENT = "§7Содержимое:";
private static final String SECTION_SETTINGS = "§7Настройки:";
private static final String SEPARATOR = "§8────────────────";
private static final String COMMANDS_HEADER = "§8Команды:";
private MineEditGui() {
}
@ -106,37 +119,87 @@ public final class MineEditGui {
return pane;
}
/**
* Formats location as short readable string.
*/
private static String formatLocation(Location loc) {
if (loc == null) return "§7не задана";
return String.format("§7%.0f §8/§7 %.0f §8/§7 %.0f §8(§7%s§8)",
loc.getX(), loc.getY(), loc.getZ(), loc.getWorld().getName());
}
/**
* Truncates string for preview display.
*/
private static String truncate(String str, int maxLen) {
if (str == null || str.isEmpty()) return "§7не задан";
if (str.length() <= maxLen) return str;
return str.substring(0, maxLen - 3) + "...";
}
private static ItemStack blocksItem(MineConfig config) {
ItemStack stack = new ItemStack(Material.DIAMOND_PICKAXE);
ItemMeta meta = stack.getItemMeta();
if (meta != null) {
meta.displayName(Component.text("§aБлоки", NamedTextColor.GREEN));
meta.displayName(Component.text("§a§lБлоки", NamedTextColor.GREEN));
int blockCount = config.blocks().blockCount();
String fillMode = config.blocks().fillMode().name();
meta.lore(List.of(
Component.text("Типов блоков: " + blockCount, NamedTextColor.GRAY),
Component.text("Режим заполнения: " + fillMode, NamedTextColor.GRAY),
Component.empty(),
Component.text("§eНажмите для редактирования", NamedTextColor.YELLOW)
));
boolean hasCustom = config.blocks().hasCustomBlocks();
List<Component> lore = new ArrayList<>();
lore.add(Component.text(SEPARATOR, NamedTextColor.DARK_GRAY));
lore.add(Component.text(SECTION_SETTINGS, NamedTextColor.GRAY));
lore.add(Component.text(" §fТипов: §7" + blockCount, NamedTextColor.WHITE));
lore.add(Component.text(" §fРежим: §7" + fillMode, NamedTextColor.WHITE));
lore.add(Component.text(" §fКастомные: " + (hasCustom ? "§aДа" : "§7Нет"), NamedTextColor.WHITE));
// Show first few blocks as preview
var weights = config.blocks().weights();
if (!weights.isEmpty()) {
lore.add(Component.empty());
lore.add(Component.text("§7Топ блоков:", NamedTextColor.GRAY));
weights.entrySet().stream()
.sorted((a, b) -> Double.compare(b.getValue(), a.getValue()))
.limit(3)
.forEach(e -> lore.add(Component.text(
String.format(" §8• §7%s §8(§7%.0f%%§8)",
e.getKey().serialize(), e.getValue() * 100),
NamedTextColor.GRAY)));
}
lore.add(Component.empty());
lore.add(Component.text(CLICK_EDIT, NamedTextColor.YELLOW));
meta.lore(lore);
stack.setItemMeta(meta);
}
return stack;
}
private static ItemStack regionsItem(MineConfig config) {
ItemStack stack = new ItemStack(Material.GRASS_BLOCK);
ItemStack stack = new ItemStack(Material.COMPASS);
ItemMeta meta = stack.getItemMeta();
if (meta != null) {
meta.displayName(Component.text("§aРегионы", NamedTextColor.GREEN));
meta.displayName(Component.text("§a§lРегионы", NamedTextColor.GREEN));
int regionCount = config.region().regionCount();
int volume = config.region().totalVolume();
meta.lore(List.of(
Component.text("Количество регионов: " + regionCount, NamedTextColor.GRAY),
Component.text("Общий объём: " + volume + " блоков", NamedTextColor.GRAY),
Component.empty(),
Component.text("§7Только просмотр (изменяется через палочку)", NamedTextColor.GRAY)
));
List<Component> lore = new ArrayList<>();
lore.add(Component.text(SEPARATOR, NamedTextColor.DARK_GRAY));
lore.add(Component.text(SECTION_SETTINGS, NamedTextColor.GRAY));
lore.add(Component.text(" §fКоличество: §7" + regionCount, NamedTextColor.WHITE));
lore.add(Component.text(" §fОбщий объём: §7" + volume + " §8блоков", NamedTextColor.WHITE));
// Show world info
lore.add(Component.empty());
lore.add(Component.text(" §fМир: §7" + config.worldName(), NamedTextColor.WHITE));
lore.add(Component.empty());
lore.add(Component.text("§8Изменяется через палочку", NamedTextColor.DARK_GRAY));
meta.lore(lore);
stack.setItemMeta(meta);
}
return stack;
@ -146,17 +209,32 @@ public final class MineEditGui {
ItemStack stack = new ItemStack(Material.CLOCK);
ItemMeta meta = stack.getItemMeta();
if (meta != null) {
meta.displayName(Component.text("§aСброс", NamedTextColor.GREEN));
meta.displayName(Component.text("§a§lСброс", NamedTextColor.GREEN));
String interval = config.reset().intervalDisplay();
double percent = config.reset().percentTrigger();
boolean percentEnabled = config.reset().isPercentTriggerEnabled();
meta.lore(List.of(
Component.text("Интервал: " + interval, NamedTextColor.GRAY),
Component.text("Триггер по %: " + (percentEnabled ? percent + "%" : "выключен"), NamedTextColor.GRAY),
Component.text("Команд при сбросе: " + config.reset().commands().size(), NamedTextColor.GRAY),
Component.empty(),
Component.text("§eНажмите для редактирования", NamedTextColor.YELLOW)
));
int cmdCount = config.reset().commands().size();
String broadcast = config.reset().broadcastMessage();
List<Component> lore = new ArrayList<>();
lore.add(Component.text(SEPARATOR, NamedTextColor.DARK_GRAY));
lore.add(Component.text(SECTION_SETTINGS, NamedTextColor.GRAY));
lore.add(Component.text(" §fИнтервал: §7" + interval, NamedTextColor.WHITE));
lore.add(Component.text(" §fТриггер по %: " + (percentEnabled ? "§a" + percent + "%" : "§7выключен"), NamedTextColor.WHITE));
lore.add(Component.text(" §fКоманд: §7" + cmdCount, NamedTextColor.WHITE));
// Show broadcast status
lore.add(Component.empty());
lore.add(Component.text(" §fСообщение: " + (broadcast.isEmpty() ? "§7нет" : "§aесть"), NamedTextColor.WHITE));
if (!broadcast.isEmpty()) {
lore.add(Component.text(" §8" + truncate(broadcast, 25), NamedTextColor.DARK_GRAY));
}
lore.add(Component.empty());
lore.add(Component.text(CLICK_EDIT, NamedTextColor.YELLOW));
meta.lore(lore);
stack.setItemMeta(meta);
}
return stack;
@ -166,13 +244,38 @@ public final class MineEditGui {
ItemStack stack = new ItemStack(Material.GOLD_INGOT);
ItemMeta meta = stack.getItemMeta();
if (meta != null) {
meta.displayName(Component.text("§aНаграды", NamedTextColor.GREEN));
int rewardCount = config.rewards().entries().size();
meta.lore(List.of(
Component.text("Настроено наград: " + rewardCount, NamedTextColor.GRAY),
Component.empty(),
Component.text("§eНажмите для редактирования", NamedTextColor.YELLOW)
));
meta.displayName(Component.text("§a§lНаграды", NamedTextColor.GREEN));
var entries = config.rewards().entries();
int rewardCount = entries.size();
// Count reward types
long itemRewards = entries.stream()
.flatMap(e -> e.items().stream()).count();
long cmdRewards = entries.stream()
.filter(e -> !e.commands().isEmpty()).count();
long preventDrops = entries.stream()
.filter(RewardConfig.RewardEntry::preventVanillaDrops).count();
List<Component> lore = new ArrayList<>();
lore.add(Component.text(SEPARATOR, NamedTextColor.DARK_GRAY));
lore.add(Component.text(SECTION_SETTINGS, NamedTextColor.GRAY));
lore.add(Component.text(" §fЗаписей: §7" + rewardCount, NamedTextColor.WHITE));
if (rewardCount > 0) {
lore.add(Component.empty());
lore.add(Component.text("§7Типы наград:", NamedTextColor.GRAY));
lore.add(Component.text(" §8• §fПредметов: §7" + itemRewards, NamedTextColor.WHITE));
lore.add(Component.text(" §8• §fКоманд: §7" + cmdRewards, NamedTextColor.WHITE));
if (preventDrops > 0) {
lore.add(Component.text(" §8• §cБлокируют дроп: §7" + preventDrops, NamedTextColor.WHITE));
}
}
lore.add(Component.empty());
lore.add(Component.text(CLICK_EDIT, NamedTextColor.YELLOW));
meta.lore(lore);
stack.setItemMeta(meta);
}
return stack;
@ -182,87 +285,144 @@ public final class MineEditGui {
ItemStack stack = new ItemStack(Material.ENDER_PEARL);
ItemMeta meta = stack.getItemMeta();
if (meta != null) {
meta.displayName(Component.text("§aТелепорт", NamedTextColor.GREEN));
boolean enabled = config.teleport().enabled();
meta.displayName(Component.text("§a§lТелепорт", NamedTextColor.GREEN));
boolean tpEnabled = config.teleport().enabled();
boolean spawnEnabled = config.playerSpawn().enabled();
meta.lore(List.of(
Component.text("Включено: " + (enabled ? "§aДа" : "§cНет"), NamedTextColor.GRAY),
Component.text("Точка спавна (застрявших): " + (spawnEnabled ? "§aДа" : "§7Нет"), NamedTextColor.GRAY),
Component.empty(),
Component.text("§eНажмите для редактирования", NamedTextColor.YELLOW),
Component.empty(),
Component.text("§7Команды:", NamedTextColor.DARK_GRAY),
Component.text("§7/lm setteleport <mine>", NamedTextColor.GRAY),
Component.text("§7/lm setspawn <mine>", NamedTextColor.GRAY),
Component.text("§7/lm clearspawn <mine>", NamedTextColor.GRAY)
));
List<Component> lore = new ArrayList<>();
lore.add(Component.text(SEPARATOR, NamedTextColor.DARK_GRAY));
lore.add(Component.text(SECTION_SETTINGS, NamedTextColor.GRAY));
// Teleport location
lore.add(Component.text(" §fТелепорт: " + (tpEnabled ? "§aвкл" : "§7выкл"), NamedTextColor.WHITE));
if (tpEnabled) {
config.teleport().getLocation().ifPresent(loc ->
lore.add(Component.text(" " + formatLocation(loc), NamedTextColor.WHITE)));
}
// Spawn location (for stuck players)
lore.add(Component.empty());
lore.add(Component.text(" §fСпавн (застрявших): " + (spawnEnabled ? "§aвкл" : "§7выкл"), NamedTextColor.WHITE));
if (spawnEnabled) {
config.playerSpawn().getLocation().ifPresent(loc ->
lore.add(Component.text(" " + formatLocation(loc), NamedTextColor.WHITE)));
}
// Command hints
lore.add(Component.empty());
lore.add(Component.text(COMMANDS_HEADER, NamedTextColor.DARK_GRAY));
lore.add(Component.text(" §8• §7/lm setteleport §f<mine>", NamedTextColor.GRAY));
lore.add(Component.text(" §8• §7/lm setspawn §f<mine>", NamedTextColor.GRAY));
lore.add(Component.text(" §8• §7/lm clearspawn §f<mine>", NamedTextColor.GRAY));
lore.add(Component.empty());
lore.add(Component.text(CLICK_EDIT, NamedTextColor.YELLOW));
meta.lore(lore);
stack.setItemMeta(meta);
}
return stack;
}
private static ItemStack uiItem(MineConfig config) {
ItemStack stack = new ItemStack(Material.OAK_SIGN);
ItemStack stack = new ItemStack(Material.PAINTING);
ItemMeta meta = stack.getItemMeta();
if (meta != null) {
meta.displayName(Component.text("§aИнтерфейс", NamedTextColor.GREEN));
meta.displayName(Component.text("§a§lИнтерфейс", NamedTextColor.GREEN));
boolean actionBarEnabled = config.ui().actionBarEnabled();
double range = config.ui().actionBarRange();
meta.lore(List.of(
Component.text("Action Bar: " + (actionBarEnabled ? "§aВкл" : "§cВыкл"), NamedTextColor.GRAY),
Component.text("Радиус: " + range, NamedTextColor.GRAY),
Component.empty(),
Component.text("§eНажмите для редактирования", NamedTextColor.YELLOW)
));
String format = config.ui().actionBarFormat();
String timerFmt = config.ui().timerFormat();
List<Component> lore = new ArrayList<>();
lore.add(Component.text(SEPARATOR, NamedTextColor.DARK_GRAY));
lore.add(Component.text(SECTION_SETTINGS, NamedTextColor.GRAY));
lore.add(Component.text(" §fAction Bar: " + (actionBarEnabled ? "§aвкл" : "§7выкл"), NamedTextColor.WHITE));
lore.add(Component.text(" §fРадиус: §7" + (int) range + " §8блоков", NamedTextColor.WHITE));
// Show format previews
lore.add(Component.empty());
lore.add(Component.text("§7Формат ActionBar:", NamedTextColor.GRAY));
lore.add(Component.text(" §8" + truncate(format, 28), NamedTextColor.DARK_GRAY));
lore.add(Component.empty());
lore.add(Component.text("§7Формат таймера: §8" + timerFmt, NamedTextColor.GRAY));
lore.add(Component.empty());
lore.add(Component.text(CLICK_EDIT, NamedTextColor.YELLOW));
meta.lore(lore);
stack.setItemMeta(meta);
}
return stack;
}
private static ItemStack saveItem() {
ItemStack stack = new ItemStack(Material.LIME_CONCRETE);
ItemStack stack = new ItemStack(Material.LIME_DYE);
ItemMeta meta = stack.getItemMeta();
if (meta != null) {
meta.displayName(Component.text("§aСохранить изменения", NamedTextColor.GREEN));
meta.lore(List.of(
Component.text("§7Сохраняет текущую конфигурацию", NamedTextColor.GRAY),
Component.text("§7в файл шахты", NamedTextColor.GRAY)
));
meta.displayName(Component.text("§a§lСохранить изменения", NamedTextColor.GREEN));
List<Component> lore = new ArrayList<>();
lore.add(Component.text(SEPARATOR, NamedTextColor.DARK_GRAY));
lore.add(Component.text("§7Сохраняет конфигурацию шахты", NamedTextColor.GRAY));
lore.add(Component.text("§7в файл на диске", NamedTextColor.GRAY));
lore.add(Component.empty());
lore.add(Component.text(CLICK_SAVE, NamedTextColor.GREEN));
meta.lore(lore);
stack.setItemMeta(meta);
}
return stack;
}
private static ItemStack deleteItem(String mineName) {
ItemStack stack = new ItemStack(Material.RED_CONCRETE);
ItemStack stack = new ItemStack(Material.TNT);
ItemMeta meta = stack.getItemMeta();
if (meta != null) {
meta.displayName(Component.text("§cУдалить шахту", NamedTextColor.RED));
meta.lore(List.of(
Component.text("§cВнимание! Это действие нельзя отменить!", NamedTextColor.RED),
Component.empty(),
Component.text("§7Шахта: " + mineName, NamedTextColor.GRAY)
));
meta.displayName(Component.text("§c§lУдалить шахту", NamedTextColor.RED));
List<Component> lore = new ArrayList<>();
lore.add(Component.text(SEPARATOR, NamedTextColor.DARK_GRAY));
lore.add(Component.text("§c§lВнимание!", NamedTextColor.RED));
lore.add(Component.text("§cЭто действие нельзя отменить!", NamedTextColor.RED));
lore.add(Component.empty());
lore.add(Component.text("§7Шахта: §f" + mineName, NamedTextColor.GRAY));
lore.add(Component.empty());
lore.add(Component.text(CLICK_DELETE, NamedTextColor.RED));
meta.lore(lore);
stack.setItemMeta(meta);
}
return stack;
}
private static ItemStack infoItem(Mine mine) {
ItemStack stack = new ItemStack(Material.PAPER);
ItemStack stack = new ItemStack(Material.BOOK);
ItemMeta meta = stack.getItemMeta();
if (meta != null) {
meta.displayName(Component.text("§bИнформация", NamedTextColor.AQUA));
meta.displayName(Component.text("§b§lИнформация", NamedTextColor.AQUA));
int blocks = mine.getBlocks();
int total = mine.getTotalVolume();
double percent = mine.getPercentFilled();
meta.lore(List.of(
Component.text("Название: " + mine.getName(), NamedTextColor.GRAY),
Component.text("Мир: " + mine.getConfig().worldName(), NamedTextColor.GRAY),
Component.empty(),
Component.text("Заполнено: " + blocks + "/" + total, NamedTextColor.GRAY),
Component.text("Процент: " + String.format("%.1f%%", percent), NamedTextColor.GRAY)
));
String statusColor = percent > 50 ? "§a" : percent > 25 ? "§e" : "§c";
List<Component> lore = new ArrayList<>();
lore.add(Component.text(SEPARATOR, NamedTextColor.DARK_GRAY));
lore.add(Component.text("§7Базовые данные:", NamedTextColor.GRAY));
lore.add(Component.text(" §fНазвание: §7" + mine.getName(), NamedTextColor.WHITE));
lore.add(Component.text(" §fМир: §7" + mine.getConfig().worldName(), NamedTextColor.WHITE));
lore.add(Component.empty());
lore.add(Component.text("§7Состояние заполнения:", NamedTextColor.GRAY));
lore.add(Component.text(" §fБлоков: " + statusColor + blocks + "§8/§7" + total, NamedTextColor.WHITE));
lore.add(Component.text(" §fПроцент: " + statusColor + String.format("%.1f%%", percent), NamedTextColor.WHITE));
// Visual percentage bar
int barLen = 10;
int filled = (int) Math.round(percent / 100.0 * barLen);
String bar = "§a" + "█".repeat(filled) + "§8" + "░".repeat(barLen - filled);
lore.add(Component.text(" " + bar, NamedTextColor.WHITE));
meta.lore(lore);
stack.setItemMeta(meta);
}
return stack;