Add particle visualization for wand selection
- Add WandParticleService to show particles around selected wand areas - Show green particles at corner 1 (pos1) - Show cyan particles at corner 2 (pos2) - Show yellow/green edge particles around the selected cuboid - Show gray outline for other ready slots (lower intensity) - Particles visible only to the player holding the wand - Start particles when selection is made or wand is held - Stop particles when player switches away or disconnects
This commit is contained in:
parent
7d0f6a283d
commit
9692965a3e
4 changed files with 431 additions and 331 deletions
|
|
@ -5,6 +5,7 @@ import dev.loki.lomines.core.Mines;
|
|||
import dev.loki.lomines.data.stats.StatsManager;
|
||||
import dev.loki.lomines.integration.IntegrationManager;
|
||||
import dev.loki.lomines.wand.GroupWandManager;
|
||||
import dev.loki.lomines.wand.WandParticleService;
|
||||
import dev.lolib.commands.CommandManager;
|
||||
import dev.lolib.core.LoPlugin;
|
||||
import dev.lolib.core.dependency.DependencyManager;
|
||||
|
|
@ -21,6 +22,7 @@ public final class LoMinesPlugin extends LoPlugin {
|
|||
private StatsManager statsManager;
|
||||
private CommandManager commandManager;
|
||||
private IntegrationManager integrationManager;
|
||||
private WandParticleService wandParticleService;
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
|
|
@ -36,6 +38,7 @@ public final class LoMinesPlugin extends LoPlugin {
|
|||
this.statsManager = components.statsManager();
|
||||
this.commandManager = components.commandManager();
|
||||
this.integrationManager = components.integrationManager();
|
||||
this.wandParticleService = new WandParticleService(this);
|
||||
|
||||
initializer.loadMines(mines);
|
||||
this.mineTicker = initializer.startTicker(mines);
|
||||
|
|
@ -78,6 +81,10 @@ public final class LoMinesPlugin extends LoPlugin {
|
|||
integrationManager.shutdown();
|
||||
}
|
||||
|
||||
if (wandParticleService != null) {
|
||||
wandParticleService.stopAll();
|
||||
}
|
||||
|
||||
loLogger().info("LoMines has been disabled!");
|
||||
} catch (Exception e) {
|
||||
loLogger().error("Error during plugin shutdown: " + e.getMessage());
|
||||
|
|
@ -111,4 +118,8 @@ public final class LoMinesPlugin extends LoPlugin {
|
|||
public IntegrationManager getIntegrationManager() {
|
||||
return integrationManager;
|
||||
}
|
||||
|
||||
public WandParticleService getWandParticleService() {
|
||||
return wandParticleService;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package dev.loki.lomines.gui;
|
|||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.core.Mine;
|
||||
import dev.loki.lomines.data.config.MineConfig;
|
||||
import dev.loki.lomines.data.config.RewardConfig;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
|
|
@ -26,7 +27,7 @@ public final class MineEditGui {
|
|||
private static final int SIZE = 54;
|
||||
private static final String TITLE_PREFIX = "Редактор шахты: ";
|
||||
|
||||
// Slot constants - organized in visual groups
|
||||
// Slot constants
|
||||
public static final int SLOT_BACK = 49;
|
||||
public static final int SLOT_BLOCKS = 10;
|
||||
public static final int SLOT_REGIONS = 12;
|
||||
|
|
@ -39,44 +40,28 @@ public final class MineEditGui {
|
|||
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 static final String SEP = "§8───────────────";
|
||||
private static final String EDIT = "§e▸ Нажмите для редактирования";
|
||||
private static final String CLOSE = "§c▸ Нажмите чтобы закрыть";
|
||||
private static final String SAVE = "§a▸ Нажмите для сохранения";
|
||||
private static final String DEL = "§c▸ Нажмите для удаления";
|
||||
|
||||
private MineEditGui() {
|
||||
}
|
||||
private MineEditGui() {}
|
||||
|
||||
/**
|
||||
* Opens the mine edit GUI for the specified mine.
|
||||
*
|
||||
* @param plugin the plugin instance
|
||||
* @param player the player to open for
|
||||
* @param mineName the name of the mine to edit
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
MineEditGuiHolder holder = new MineEditGuiHolder(player.getUniqueId(), mineName);
|
||||
Inventory inv = Bukkit.createInventory(holder, SIZE,
|
||||
Component.text(TITLE_PREFIX + mineName, NamedTextColor.DARK_GREEN));
|
||||
holder.setInventory(inv);
|
||||
|
||||
fill(plugin, mine, inv);
|
||||
player.openInventory(inv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the GUI contents.
|
||||
*/
|
||||
public static void refresh(LoMinesPlugin plugin, Player player, Inventory inv, String mineName) {
|
||||
Mine mine = plugin.getMines().find(mineName).orElse(null);
|
||||
if (mine == null) {
|
||||
|
|
@ -88,21 +73,13 @@ public final class MineEditGui {
|
|||
|
||||
private static void fill(LoMinesPlugin plugin, Mine mine, Inventory inv) {
|
||||
MineConfig config = mine.getConfig();
|
||||
|
||||
// Clear and fill background
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
inv.setItem(i, filler());
|
||||
}
|
||||
|
||||
// Main editors
|
||||
for (int i = 0; i < SIZE; i++) inv.setItem(i, filler());
|
||||
inv.setItem(SLOT_BLOCKS, blocksItem(config));
|
||||
inv.setItem(SLOT_REGIONS, regionsItem(config));
|
||||
inv.setItem(SLOT_RESET, resetItem(config));
|
||||
inv.setItem(SLOT_REWARDS, rewardsItem(config));
|
||||
inv.setItem(SLOT_TELEPORT, teleportItem(config));
|
||||
inv.setItem(SLOT_UI, uiItem(config));
|
||||
|
||||
// Actions
|
||||
inv.setItem(SLOT_SAVE, saveItem());
|
||||
inv.setItem(SLOT_DELETE, deleteItem(mine.getName()));
|
||||
inv.setItem(SLOT_INFO, infoItem(mine));
|
||||
|
|
@ -119,335 +96,193 @@ 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());
|
||||
private static String loc(Location l) {
|
||||
if (l == null || l.getWorld() == null) return "§7не задана";
|
||||
return String.format("§7%.0f§8/§7%.0f§8/§7%.0f", l.getX(), l.getY(), l.getZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 String trunc(String s, int max) {
|
||||
if (s == null || s.isEmpty()) return "§7нет";
|
||||
return s.length() > max ? s.substring(0, max - 3) + "..." : s;
|
||||
}
|
||||
|
||||
private static ItemStack create(Material mat, String name, String... loreLines) {
|
||||
ItemStack stack = new ItemStack(mat);
|
||||
ItemMeta meta = stack.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.displayName(Component.text(name, NamedTextColor.WHITE));
|
||||
List<Component> lore = new ArrayList<>();
|
||||
for (String line : loreLines) {
|
||||
lore.add(Component.text(line, NamedTextColor.GRAY));
|
||||
}
|
||||
meta.lore(lore);
|
||||
stack.setItemMeta(meta);
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
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§lБлоки", NamedTextColor.GREEN));
|
||||
|
||||
int blockCount = config.blocks().blockCount();
|
||||
String fillMode = config.blocks().fillMode().name();
|
||||
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)));
|
||||
var w = config.blocks().weights();
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add(SEP);
|
||||
lore.add("§7Настройки:");
|
||||
lore.add(" §fТипов: §7" + config.blocks().blockCount());
|
||||
lore.add(" §fРежим: §7" + config.blocks().fillMode().name());
|
||||
lore.add(" §fКастомные: " + (config.blocks().hasCustomBlocks() ? "§aДа" : "§7Нет"));
|
||||
if (!w.isEmpty()) {
|
||||
lore.add("");
|
||||
lore.add("§7Топ блоков:");
|
||||
w.entrySet().stream().sorted((a, b) -> Double.compare(b.getValue(), a.getValue())).limit(3)
|
||||
.forEach(e -> lore.add(String.format(" §8• §7%s §8(§7%.0f%%§8)", e.getKey().serialize(), e.getValue() * 100)));
|
||||
}
|
||||
|
||||
lore.add(Component.empty());
|
||||
lore.add(Component.text(CLICK_EDIT, NamedTextColor.YELLOW));
|
||||
|
||||
meta.lore(lore);
|
||||
stack.setItemMeta(meta);
|
||||
}
|
||||
return stack;
|
||||
lore.add("");
|
||||
lore.add(EDIT);
|
||||
return create(Material.DIAMOND_PICKAXE, "§a§lБлоки", lore.toArray(new String[0]));
|
||||
}
|
||||
|
||||
private static ItemStack regionsItem(MineConfig config) {
|
||||
ItemStack stack = new ItemStack(Material.COMPASS);
|
||||
ItemMeta meta = stack.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.displayName(Component.text("§a§lРегионы", NamedTextColor.GREEN));
|
||||
|
||||
int regionCount = config.region().regionCount();
|
||||
int volume = config.region().totalVolume();
|
||||
|
||||
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;
|
||||
return create(Material.COMPASS, "§a§lРегионы",
|
||||
SEP,
|
||||
"§7Настройки:",
|
||||
" §fКоличество: §7" + config.region().regionCount(),
|
||||
" §fОбщий объём: §7" + config.region().totalVolume() + " §8блоков",
|
||||
"",
|
||||
" §fМир: §7" + config.worldName(),
|
||||
"",
|
||||
"§8Изменяется через палочку"
|
||||
);
|
||||
}
|
||||
|
||||
private static ItemStack resetItem(MineConfig config) {
|
||||
ItemStack stack = new ItemStack(Material.CLOCK);
|
||||
ItemMeta meta = stack.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.displayName(Component.text("§a§lСброс", NamedTextColor.GREEN));
|
||||
|
||||
String interval = config.reset().intervalDisplay();
|
||||
double percent = config.reset().percentTrigger();
|
||||
boolean percentEnabled = config.reset().isPercentTriggerEnabled();
|
||||
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));
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add(SEP);
|
||||
lore.add("§7Настройки:");
|
||||
lore.add(" §fИнтервал: §7" + config.reset().intervalDisplay());
|
||||
lore.add(" §fТриггер по %: " + (config.reset().isPercentTriggerEnabled() ? "§a" + config.reset().percentTrigger() + "%" : "§7выкл"));
|
||||
lore.add(" §fКоманд: §7" + config.reset().commands().size());
|
||||
lore.add("");
|
||||
lore.add(" §fСообщение: " + (config.reset().broadcastMessage().isEmpty() ? "§7нет" : "§aесть"));
|
||||
if (!config.reset().broadcastMessage().isEmpty()) {
|
||||
lore.add(" §8" + trunc(config.reset().broadcastMessage(), 25));
|
||||
}
|
||||
|
||||
lore.add(Component.empty());
|
||||
lore.add(Component.text(CLICK_EDIT, NamedTextColor.YELLOW));
|
||||
|
||||
meta.lore(lore);
|
||||
stack.setItemMeta(meta);
|
||||
}
|
||||
return stack;
|
||||
lore.add("");
|
||||
lore.add(EDIT);
|
||||
return create(Material.CLOCK, "§a§lСброс", lore.toArray(new String[0]));
|
||||
}
|
||||
|
||||
private static ItemStack rewardsItem(MineConfig config) {
|
||||
ItemStack stack = new ItemStack(Material.GOLD_INGOT);
|
||||
ItemMeta meta = stack.getItemMeta();
|
||||
if (meta != null) {
|
||||
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));
|
||||
long items = entries.stream().flatMap(e -> e.items().stream()).count();
|
||||
long cmds = entries.stream().filter(e -> !e.commands().isEmpty()).count();
|
||||
long blockDrop = entries.stream().filter(RewardConfig.RewardEntry::preventVanillaDrops).count();
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add(SEP);
|
||||
lore.add("§7Настройки:");
|
||||
lore.add(" §fЗаписей: §7" + entries.size());
|
||||
if (!entries.isEmpty()) {
|
||||
lore.add("");
|
||||
lore.add("§7Типы наград:");
|
||||
lore.add(" §8• §fПредметов: §7" + items);
|
||||
lore.add(" §8• §fКоманд: §7" + cmds);
|
||||
if (blockDrop > 0) lore.add(" §8• §cБлокируют дроп: §7" + blockDrop);
|
||||
}
|
||||
}
|
||||
|
||||
lore.add(Component.empty());
|
||||
lore.add(Component.text(CLICK_EDIT, NamedTextColor.YELLOW));
|
||||
|
||||
meta.lore(lore);
|
||||
stack.setItemMeta(meta);
|
||||
}
|
||||
return stack;
|
||||
lore.add("");
|
||||
lore.add(EDIT);
|
||||
return create(Material.GOLD_INGOT, "§a§lНаграды", lore.toArray(new String[0]));
|
||||
}
|
||||
|
||||
private static ItemStack teleportItem(MineConfig config) {
|
||||
ItemStack stack = new ItemStack(Material.ENDER_PEARL);
|
||||
ItemMeta meta = stack.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.displayName(Component.text("§a§lТелепорт", NamedTextColor.GREEN));
|
||||
|
||||
boolean tpEnabled = config.teleport().enabled();
|
||||
boolean spawnEnabled = config.playerSpawn().enabled();
|
||||
|
||||
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)));
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add(SEP);
|
||||
lore.add("§7Настройки:");
|
||||
lore.add(" §fТелепорт: " + (config.teleport().enabled() ? "§aвкл" : "§7выкл"));
|
||||
if (config.teleport().enabled()) {
|
||||
config.teleport().getLocation().ifPresent(loc -> lore.add(" " + loc(loc)));
|
||||
}
|
||||
|
||||
// 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)));
|
||||
lore.add("");
|
||||
lore.add(" §fСпавн (застрявших): " + (config.playerSpawn().enabled() ? "§aвкл" : "§7выкл"));
|
||||
if (config.playerSpawn().enabled()) {
|
||||
config.playerSpawn().getLocation().ifPresent(loc -> lore.add(" " + loc(loc)));
|
||||
}
|
||||
|
||||
// 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;
|
||||
lore.add("");
|
||||
lore.add("§8Команды:");
|
||||
lore.add(" §8• §7/lm setteleport §f<mine>");
|
||||
lore.add(" §8• §7/lm setspawn §f<mine>");
|
||||
lore.add(" §8• §7/lm clearspawn §f<mine>");
|
||||
lore.add("");
|
||||
lore.add(EDIT);
|
||||
return create(Material.ENDER_PEARL, "§a§lТелепорт", lore.toArray(new String[0]));
|
||||
}
|
||||
|
||||
private static ItemStack uiItem(MineConfig config) {
|
||||
ItemStack stack = new ItemStack(Material.PAINTING);
|
||||
ItemMeta meta = stack.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.displayName(Component.text("§a§lИнтерфейс", NamedTextColor.GREEN));
|
||||
|
||||
boolean actionBarEnabled = config.ui().actionBarEnabled();
|
||||
double range = config.ui().actionBarRange();
|
||||
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;
|
||||
return create(Material.PAINTING, "§a§lИнтерфейс",
|
||||
SEP,
|
||||
"§7Настройки:",
|
||||
" §fAction Bar: " + (config.ui().actionBarEnabled() ? "§aвкл" : "§7выкл"),
|
||||
" §fРадиус: §7" + (int) config.ui().actionBarRange() + " §8блоков",
|
||||
"",
|
||||
"§7Формат ActionBar:",
|
||||
" §8" + trunc(config.ui().actionBarFormat(), 28),
|
||||
"",
|
||||
"§7Формат таймера: §8" + config.ui().timerFormat(),
|
||||
"",
|
||||
EDIT
|
||||
);
|
||||
}
|
||||
|
||||
private static ItemStack saveItem() {
|
||||
ItemStack stack = new ItemStack(Material.LIME_DYE);
|
||||
ItemMeta meta = stack.getItemMeta();
|
||||
if (meta != null) {
|
||||
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;
|
||||
return create(Material.LIME_DYE, "§a§lСохранить изменения",
|
||||
SEP,
|
||||
"§7Сохраняет конфигурацию шахты",
|
||||
"§7в файл на диске",
|
||||
"",
|
||||
SAVE
|
||||
);
|
||||
}
|
||||
|
||||
private static ItemStack deleteItem(String mineName) {
|
||||
ItemStack stack = new ItemStack(Material.TNT);
|
||||
ItemMeta meta = stack.getItemMeta();
|
||||
if (meta != null) {
|
||||
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;
|
||||
return create(Material.TNT, "§c§lУдалить шахту",
|
||||
SEP,
|
||||
"§c§lВнимание!",
|
||||
"§cЭто действие нельзя отменить!",
|
||||
"",
|
||||
"§7Шахта: §f" + mineName,
|
||||
"",
|
||||
DEL
|
||||
);
|
||||
}
|
||||
|
||||
private static ItemStack infoItem(Mine mine) {
|
||||
ItemStack stack = new ItemStack(Material.BOOK);
|
||||
ItemMeta meta = stack.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.displayName(Component.text("§b§lИнформация", NamedTextColor.AQUA));
|
||||
|
||||
int blocks = mine.getBlocks();
|
||||
int total = mine.getTotalVolume();
|
||||
double percent = mine.getPercentFilled();
|
||||
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;
|
||||
String c = percent > 50 ? "§a" : percent > 25 ? "§e" : "§c";
|
||||
int filled = (int) Math.round(percent / 100.0 * 10);
|
||||
String bar = "§a" + "█".repeat(filled) + "§8" + "░".repeat(10 - filled);
|
||||
return create(Material.BOOK, "§b§lИнформация",
|
||||
SEP,
|
||||
"§7Базовые данные:",
|
||||
" §fНазвание: §7" + mine.getName(),
|
||||
" §fМир: §7" + mine.getConfig().worldName(),
|
||||
"",
|
||||
"§7Состояние заполнения:",
|
||||
" §fБлоков: " + c + blocks + "§8/§7" + total,
|
||||
" §fПроцент: " + c + String.format("%.1f%%", percent),
|
||||
" " + bar
|
||||
);
|
||||
}
|
||||
|
||||
private static ItemStack backItem() {
|
||||
ItemStack stack = new ItemStack(Material.BARRIER);
|
||||
ItemMeta meta = stack.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.displayName(Component.text("§cЗакрыть", NamedTextColor.RED));
|
||||
stack.setItemMeta(meta);
|
||||
}
|
||||
return stack;
|
||||
return create(Material.ARROW, "§c§lЗакрыть",
|
||||
SEP,
|
||||
"§7Закрыть редактор",
|
||||
"",
|
||||
CLOSE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles clicks in the GUI.
|
||||
*
|
||||
* @return true if the event should be cancelled
|
||||
*/
|
||||
public static boolean handleClick(LoMinesPlugin plugin, Player player, int rawSlot, String mineName) {
|
||||
if (rawSlot < 0 || rawSlot >= SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rawSlot < 0 || rawSlot >= SIZE) return false;
|
||||
switch (rawSlot) {
|
||||
case SLOT_BACK -> {
|
||||
player.closeInventory();
|
||||
|
|
@ -455,27 +290,22 @@ public final class MineEditGui {
|
|||
}
|
||||
case SLOT_BLOCKS -> {
|
||||
player.sendMessage(Component.text("Открытие редактора блоков... (в разработке)", NamedTextColor.YELLOW));
|
||||
// TODO: Open BlockEditGui
|
||||
return true;
|
||||
}
|
||||
case SLOT_RESET -> {
|
||||
player.sendMessage(Component.text("Открытие редактора сброса... (в разработке)", NamedTextColor.YELLOW));
|
||||
// TODO: Open ResetEditGui
|
||||
return true;
|
||||
}
|
||||
case SLOT_REWARDS -> {
|
||||
player.sendMessage(Component.text("Открытие редактора наград... (в разработке)", NamedTextColor.YELLOW));
|
||||
// TODO: Open RewardEditGui
|
||||
return true;
|
||||
}
|
||||
case SLOT_TELEPORT -> {
|
||||
player.sendMessage(Component.text("Открытие редактора телепорта... (в разработке)", NamedTextColor.YELLOW));
|
||||
// TODO: Open TeleportEditGui
|
||||
return true;
|
||||
}
|
||||
case SLOT_UI -> {
|
||||
player.sendMessage(Component.text("Открытие редактора интерфейса... (в разработке)", NamedTextColor.YELLOW));
|
||||
// TODO: Open UIEditGui
|
||||
return true;
|
||||
}
|
||||
case SLOT_SAVE -> {
|
||||
|
|
@ -484,16 +314,13 @@ public final class MineEditGui {
|
|||
return true;
|
||||
}
|
||||
case SLOT_DELETE -> {
|
||||
// Open confirmation GUI
|
||||
ConfirmDeleteGui.open(plugin, player, mineName);
|
||||
return true;
|
||||
}
|
||||
case SLOT_INFO, SLOT_REGIONS -> {
|
||||
// Info only, no action
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true; // Cancel all other clicks
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,10 +13,13 @@ import org.bukkit.event.EventPriority;
|
|||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.event.player.PlayerItemHeldEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
|
||||
/**
|
||||
* Group wand: ЛКМ / ПКМ по блоку — углы; Shift+ПКМ — GUI группы.
|
||||
* Also starts/stops particle visualization when holding the wand.
|
||||
*/
|
||||
public final class PlayerInteractListener implements Listener {
|
||||
|
||||
|
|
@ -54,6 +57,9 @@ public final class PlayerInteractListener implements Listener {
|
|||
session.setPos1(slot, block.getLocation());
|
||||
player.sendMessage(Component.text(
|
||||
"Угол 1 для шахты " + (slot + 1) + " установлен.", NamedTextColor.GREEN));
|
||||
|
||||
// Start showing particles when selection is made
|
||||
plugin.getWandParticleService().startShowingParticles(player);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -68,6 +74,34 @@ public final class PlayerInteractListener implements Listener {
|
|||
session.setPos2(slot, block.getLocation());
|
||||
player.sendMessage(Component.text(
|
||||
"Угол 2 для шахты " + (slot + 1) + " установлен.", NamedTextColor.GREEN));
|
||||
|
||||
// Start showing particles when selection is made
|
||||
plugin.getWandParticleService().startShowingParticles(player);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts/stops particle visualization when player switches items.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onItemHeld(PlayerItemHeldEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
|
||||
// Check if player switched to wand
|
||||
if (GroupWandItem.isWand(plugin, player.getInventory().getItem(event.getNewSlot()))) {
|
||||
plugin.getWandParticleService().startShowingParticles(player);
|
||||
}
|
||||
// Check if player switched away from wand
|
||||
else if (GroupWandItem.isWand(plugin, player.getInventory().getItem(event.getPreviousSlot()))) {
|
||||
plugin.getWandParticleService().stopShowingParticles(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops particles when player disconnects.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
plugin.getWandParticleService().stopShowingParticles(event.getPlayer().getUniqueId());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
228
src/main/java/dev/loki/lomines/wand/WandParticleService.java
Normal file
228
src/main/java/dev/loki/lomines/wand/WandParticleService.java
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
package dev.loki.lomines.wand;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Shows particles around wand selection areas to visualize the selected region.
|
||||
* Particles are shown only to the player holding the wand.
|
||||
*/
|
||||
public final class WandParticleService {
|
||||
|
||||
private final LoMinesPlugin plugin;
|
||||
private final Map<UUID, BukkitRunnable> activeTasks = new ConcurrentHashMap<>();
|
||||
|
||||
// Particle colors for different states
|
||||
private static final Color COLOR_POS1 = Color.LIME; // Green for corner 1
|
||||
private static final Color COLOR_POS2 = Color.AQUA; // Cyan for corner 2
|
||||
private static final Color COLOR_EDGE = Color.YELLOW; // Yellow for edges
|
||||
private static final Color COLOR_READY = Color.GREEN; // Green when both corners set
|
||||
|
||||
public WandParticleService(LoMinesPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts showing particles for a player holding the wand.
|
||||
*/
|
||||
public void startShowingParticles(Player player) {
|
||||
UUID playerId = player.getUniqueId();
|
||||
|
||||
// Stop existing task if any
|
||||
stopShowingParticles(playerId);
|
||||
|
||||
BukkitRunnable task = new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!player.isOnline() || !isHoldingWand(player)) {
|
||||
cancel();
|
||||
activeTasks.remove(playerId);
|
||||
return;
|
||||
}
|
||||
|
||||
showParticles(player);
|
||||
}
|
||||
};
|
||||
|
||||
task.runTaskTimer(plugin, 0L, 10L); // Every 10 ticks (0.5 seconds)
|
||||
activeTasks.put(playerId, task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops showing particles for a player.
|
||||
*/
|
||||
public void stopShowingParticles(UUID playerId) {
|
||||
BukkitRunnable task = activeTasks.remove(playerId);
|
||||
if (task != null) {
|
||||
task.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops all particle tasks.
|
||||
*/
|
||||
public void stopAll() {
|
||||
for (BukkitRunnable task : activeTasks.values()) {
|
||||
task.cancel();
|
||||
}
|
||||
activeTasks.clear();
|
||||
}
|
||||
|
||||
private boolean isHoldingWand(Player player) {
|
||||
return GroupWandItem.isWand(plugin, player.getInventory().getItemInMainHand());
|
||||
}
|
||||
|
||||
private void showParticles(Player player) {
|
||||
GroupWandSession session = plugin.getGroupWandManager().getSession(player.getUniqueId());
|
||||
int activeSlot = session.getActiveSlot();
|
||||
|
||||
Location pos1 = session.getPos1(activeSlot);
|
||||
Location pos2 = session.getPos2(activeSlot);
|
||||
|
||||
// Show particles for corner 1 if set
|
||||
if (pos1 != null && pos1.getWorld() != null) {
|
||||
showCornerParticles(player, pos1, COLOR_POS1);
|
||||
}
|
||||
|
||||
// Show particles for corner 2 if set
|
||||
if (pos2 != null && pos2.getWorld() != null) {
|
||||
showCornerParticles(player, pos2, COLOR_POS2);
|
||||
}
|
||||
|
||||
// Show edge particles if both corners are set
|
||||
if (pos1 != null && pos2 != null && pos1.getWorld() != null && pos2.getWorld() != null) {
|
||||
if (pos1.getWorld().equals(pos2.getWorld())) {
|
||||
showEdgeParticles(player, pos1, pos2, session.isSlotReady(activeSlot) ? COLOR_READY : COLOR_EDGE);
|
||||
}
|
||||
}
|
||||
|
||||
// Also show other slots that are ready (with lower intensity)
|
||||
for (int i = 0; i < 9; i++) {
|
||||
if (i == activeSlot) continue;
|
||||
if (session.isSlotReady(i)) {
|
||||
Location p1 = session.getPos1(i);
|
||||
Location p2 = session.getPos2(i);
|
||||
if (p1 != null && p2 != null && p1.getWorld() != null && p1.getWorld().equals(player.getWorld())) {
|
||||
showOutlineParticles(player, p1, p2, Color.GRAY, 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showCornerParticles(Player player, Location loc, Color color) {
|
||||
World world = loc.getWorld();
|
||||
if (world == null) return;
|
||||
|
||||
Particle.DustOptions dustOptions = new Particle.DustOptions(color, 1.0f);
|
||||
|
||||
// Show particles in a small sphere around the corner
|
||||
for (int i = 0; i < 8; i++) {
|
||||
double angle = i * Math.PI / 4;
|
||||
double x = loc.getX() + 0.5 + Math.cos(angle) * 0.5;
|
||||
double z = loc.getZ() + 0.5 + Math.sin(angle) * 0.5;
|
||||
double y = loc.getY() + 0.5;
|
||||
|
||||
Location particleLoc = new Location(world, x, y, z);
|
||||
player.spawnParticle(Particle.DUST, particleLoc, 1, dustOptions);
|
||||
}
|
||||
}
|
||||
|
||||
private void showEdgeParticles(Player player, Location pos1, Location pos2, Color color) {
|
||||
World world = pos1.getWorld();
|
||||
if (world == null) return;
|
||||
|
||||
Particle.DustOptions dustOptions = new Particle.DustOptions(color, 0.8f);
|
||||
|
||||
int minX = Math.min(pos1.getBlockX(), pos2.getBlockX());
|
||||
int maxX = Math.max(pos1.getBlockX(), pos2.getBlockX());
|
||||
int minY = Math.min(pos1.getBlockY(), pos2.getBlockY());
|
||||
int maxY = Math.max(pos1.getBlockY(), pos2.getBlockY());
|
||||
int minZ = Math.min(pos1.getBlockZ(), pos2.getBlockZ());
|
||||
int maxZ = Math.max(pos1.getBlockZ(), pos2.getBlockZ());
|
||||
|
||||
// Show particles along the edges of the cuboid
|
||||
double step = 1.0; // Every block
|
||||
|
||||
// Bottom face edges
|
||||
spawnEdgeParticles(player, world, minX, minY, minZ, maxX, minY, minZ, step, dustOptions); // Bottom front
|
||||
spawnEdgeParticles(player, world, minX, minY, maxZ, maxX, minY, maxZ, step, dustOptions); // Bottom back
|
||||
spawnEdgeParticles(player, world, minX, minY, minZ, minX, minY, maxZ, step, dustOptions); // Bottom left
|
||||
spawnEdgeParticles(player, world, maxX, minY, minZ, maxX, minY, maxZ, step, dustOptions); // Bottom right
|
||||
|
||||
// Top face edges
|
||||
spawnEdgeParticles(player, world, minX, maxY, minZ, maxX, maxY, minZ, step, dustOptions); // Top front
|
||||
spawnEdgeParticles(player, world, minX, maxY, maxZ, maxX, maxY, maxZ, step, dustOptions); // Top back
|
||||
spawnEdgeParticles(player, world, minX, maxY, minZ, minX, maxY, maxZ, step, dustOptions); // Top left
|
||||
spawnEdgeParticles(player, world, maxX, maxY, minZ, maxX, maxY, maxZ, step, dustOptions); // Top right
|
||||
|
||||
// Vertical edges
|
||||
spawnEdgeParticles(player, world, minX, minY, minZ, minX, maxY, minZ, step, dustOptions); // Front left
|
||||
spawnEdgeParticles(player, world, maxX, minY, minZ, maxX, maxY, minZ, step, dustOptions); // Front right
|
||||
spawnEdgeParticles(player, world, minX, minY, maxZ, minX, maxY, maxZ, step, dustOptions); // Back left
|
||||
spawnEdgeParticles(player, world, maxX, minY, maxZ, maxX, maxY, maxZ, step, dustOptions); // Back right
|
||||
}
|
||||
|
||||
private void spawnEdgeParticles(Player player, World world,
|
||||
double x1, double y1, double z1,
|
||||
double x2, double y2, double z2,
|
||||
double step, Particle.DustOptions dustOptions) {
|
||||
double distance = Math.sqrt(
|
||||
Math.pow(x2 - x1, 2) +
|
||||
Math.pow(y2 - y1, 2) +
|
||||
Math.pow(z2 - z1, 2)
|
||||
);
|
||||
|
||||
int count = (int) (distance / step) + 1;
|
||||
|
||||
for (int i = 0; i <= count; i++) {
|
||||
double t = (double) i / count;
|
||||
double x = x1 + (x2 - x1) * t + 0.5;
|
||||
double y = y1 + (y2 - y1) * t + 0.5;
|
||||
double z = z1 + (z2 - z1) * t + 0.5;
|
||||
|
||||
Location loc = new Location(world, x, y, z);
|
||||
if (loc.getWorld() != null && loc.getWorld().equals(player.getWorld())) {
|
||||
player.spawnParticle(Particle.DUST, loc, 1, dustOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showOutlineParticles(Player player, Location pos1, Location pos2, Color color, int density) {
|
||||
World world = pos1.getWorld();
|
||||
if (world == null || !world.equals(player.getWorld())) return;
|
||||
|
||||
Particle.DustOptions dustOptions = new Particle.DustOptions(color, 0.5f);
|
||||
|
||||
int minX = Math.min(pos1.getBlockX(), pos2.getBlockX());
|
||||
int maxX = Math.max(pos1.getBlockX(), pos2.getBlockX());
|
||||
int minY = Math.min(pos1.getBlockY(), pos2.getBlockY());
|
||||
int maxY = Math.max(pos1.getBlockY(), pos2.getBlockY());
|
||||
int minZ = Math.min(pos1.getBlockZ(), pos2.getBlockZ());
|
||||
int maxZ = Math.max(pos1.getBlockZ(), pos2.getBlockZ());
|
||||
|
||||
// Just show corners for non-active slots (lighter)
|
||||
Location[] corners = {
|
||||
new Location(world, minX + 0.5, minY + 0.5, minZ + 0.5),
|
||||
new Location(world, maxX + 0.5, minY + 0.5, minZ + 0.5),
|
||||
new Location(world, minX + 0.5, minY + 0.5, maxZ + 0.5),
|
||||
new Location(world, maxX + 0.5, minY + 0.5, maxZ + 0.5),
|
||||
new Location(world, minX + 0.5, maxY + 0.5, minZ + 0.5),
|
||||
new Location(world, maxX + 0.5, maxY + 0.5, minZ + 0.5),
|
||||
new Location(world, minX + 0.5, maxY + 0.5, maxZ + 0.5),
|
||||
new Location(world, maxX + 0.5, maxY + 0.5, maxZ + 0.5)
|
||||
};
|
||||
|
||||
for (Location corner : corners) {
|
||||
player.spawnParticle(Particle.DUST, corner, 1, dustOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue