package dev.loki.command; import dev.loki.mdbd; import dev.loki.util.SpawnUtil; import dev.loki.util.TextUtil; import dev.loki.config.LocationsConfig; import dev.loki.config.MessagesConfig; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.TabExecutor; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; /** * /dbdsetup — настройка карты. * * Система: * 1. Ванд: /dbdsetup wand (ЛКМ=pos1, ПКМ=pos2 — для ворот) * 2. Обычные объекты: /dbdsetup set <тип> [номер] → сломай блок * 3. Ворота: /dbdsetup set gate A|B (из pos1/pos2 ванда) * * Типы: hook, chest, generator, killer-spawn, survivor-spawn, gate * Для спавнов можно указать номер: /dbdsetup set killer-spawn 0 */ public class SetupCommand implements TabExecutor { private final mdbd plugin; // Wand positions for gates (LMB=pos1, RMB=pos2) private final Map pos1 = new HashMap<>(); private final Map pos2 = new HashMap<>(); // Setup mode data: mode + optional spawn index private static final class SetupData { final String mode; int spawnIndex; // -1 means "not specified, append"; resets to -1 after first use SetupData(String mode, int spawnIndex) { this.mode = mode; this.spawnIndex = spawnIndex; } } private final Map setupMode = new HashMap<>(); public SetupCommand(mdbd plugin) { this.plugin = plugin; } private MessagesConfig messages() { return plugin.getConfigManager().getMessages(); } @Override public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { if (!sender.hasPermission("mdbd.admin")) { sender.sendMessage(messages().get("cmd.no-permission")); return true; } if (!(sender instanceof Player player)) { sender.sendMessage(messages().get("cmd.players-only")); return true; } if (args.length == 0) { sendUsage(sender); return true; } return switch (args[0].toLowerCase()) { case "wand" -> giveWand(player); case "set" -> handleSet(player, args); case "clear" -> handleClear(player, args); case "delete" -> handleDelete(player, args); case "clear-spawns" -> clearSpawns(player); case "lobby" -> setLobby(player); default -> { sendUsage(sender); yield true; } }; } // ── Wand ───────────────────────────────────────────────────────────────── private boolean giveWand(Player player) { ItemStack wand = new ItemStack(Material.WOODEN_AXE); wand.editMeta(meta -> { meta.displayName(TextUtil.color("<#FF6B00>DBD Setup Wand")); meta.lore(List.of( TextUtil.color("<#FFD700>ЛКМ по блоку — Точка 1 (для ворот)"), TextUtil.color("<#FFD700>ПКМ по блоку — Точка 2 (для ворот)"), TextUtil.color("Для остального: /dbdsetup set <тип> и ломай блок") )); var key = new org.bukkit.NamespacedKey(plugin, "dbd_wand"); meta.getPersistentDataContainer().set(key, org.bukkit.persistence.PersistentDataType.BYTE, (byte) 1); }); player.getInventory().addItem(wand); player.sendMessage(messages().get("setup.wand-given")); return true; } // ── Set mode ────────────────────────────────────────────────────────────── private boolean handleSet(Player player, String[] args) { if (args.length < 2) { player.sendMessage(messages().get("setup.set-usage")); player.sendMessage(messages().get("setup.set-types")); player.sendMessage(messages().get("setup.set-spawn-info")); return true; } String type = args[1].toLowerCase(); UUID uuid = player.getUniqueId(); if (type.equals("gate")) { if (args.length < 3) { player.sendMessage(messages().get("setup.gate-usage")); return true; } String gateId = args[2].toUpperCase(); if (!gateId.equals("A") && !gateId.equals("B")) { player.sendMessage(messages().get("setup.gate-invalid")); return true; } return placeGate(player, gateId); } String mode = switch (type) { case "killer_spawn" -> "killer-spawn"; case "survivor_spawn" -> "survivor-spawn"; default -> type; }; if (!List.of("hook", "chest", "generator", "killer-spawn", "survivor-spawn").contains(mode)) { player.sendMessage(messages().get("setup.unknown-type", type)); return true; } // Parse optional spawn index int spawnIndex = -1; if (args.length >= 3 && (mode.equals("killer-spawn") || mode.equals("survivor-spawn"))) { try { int raw = Integer.parseInt(args[2]); if (mode.equals("survivor-spawn")) { if (raw < 1 || raw > 4) { player.sendMessage(messages().get("setup.survivor-spawn-range")); return true; } spawnIndex = raw; // 1-4, храним как есть } else { if (raw < 0) { player.sendMessage(messages().get("setup.killer-spawn-range")); return true; } spawnIndex = raw; } } catch (NumberFormatException e) { player.sendMessage(messages().get("setup.invalid-number", args[2])); return true; } } // Toggle: если уже в этом режиме с тем же типом — выключаем SetupData currentData = setupMode.get(uuid); if (currentData != null && mode.equals(currentData.mode)) { setupMode.remove(uuid); player.sendMessage(messages().get("setup.mode-off", mode)); return true; } setupMode.put(uuid, new SetupData(mode, spawnIndex)); String indexMsg = spawnIndex >= 0 ? " #" + spawnIndex : ""; if (mode.contains("spawn")) { player.sendMessage(messages().get("setup.mode-on-spawn", mode, indexMsg)); } else { player.sendMessage(messages().get("setup.mode-on", mode, indexMsg)); } return true; } private boolean placeGate(Player player, String gateId) { Location p1 = pos1.get(player.getUniqueId()); Location p2 = pos2.get(player.getUniqueId()); if (p1 == null || p2 == null) { player.sendMessage(messages().get("setup.gate-need-points")); return true; } if (!p1.getWorld().equals(p2.getWorld())) { player.sendMessage(messages().get("setup.gate-same-world")); return true; } Location center = new Location( p1.getWorld(), (p1.getX() + p2.getX()) / 2.0, Math.min(p1.getY(), p2.getY()), (p1.getZ() + p2.getZ()) / 2.0, player.getLocation().getYaw(), 0f ); var locations = plugin.getConfigManager().getLocations(); locations.setGate(gateId, center); locations.setGateZone(gateId, p1, p2); plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig()); player.sendMessage(messages().get("setup.gate-placed", gateId, formatLoc(center))); return true; } // ── Block break callback ─────────────────────────────────────────────────── /** * Called when a block is broken in setup mode. * Saves the location and restores the original block. * * @param player The player who broke the block * @param blockLoc Location of the broken block * @param restoreBlock The material to restore the block to (null = don't restore) */ public void onBlockBroken(Player player, Location blockLoc, Material restoreBlock) { UUID uuid = player.getUniqueId(); SetupData data = setupMode.get(uuid); if (data == null) return; String mode = data.mode; var locations = plugin.getConfigManager().getLocations(); Location loc = SpawnUtil.blockBreakSpawn(blockLoc, player.getLocation().getYaw()); switch (mode) { case "hook" -> { Location hLoc = blockLoc.clone().add(0.5, 0, 0.5); if (locations.hasLocation("hooks", hLoc)) { player.sendMessage(messages().get("setup.duplicate")); break; } locations.addLocation("hooks", hLoc); player.sendMessage(messages().get("setup.hook-placed", formatLoc(loc))); } case "chest" -> { Location cLoc = blockLoc.clone().add(0.5, 0, 0.5); if (locations.hasLocation("chests", cLoc)) { player.sendMessage(messages().get("setup.duplicate")); break; } locations.addLocation("chests", cLoc); player.sendMessage(messages().get("setup.chest-placed", formatLoc(loc))); } case "generator" -> { Location gLoc = blockLoc.clone().add(0.5, 0, 0.5); if (locations.hasLocation("generators", gLoc)) { player.sendMessage(messages().get("setup.duplicate")); break; } locations.addLocation("generators", gLoc); plugin.getGeneratorData().addGenerator(gLoc); player.sendMessage(messages().get("setup.generator-placed", formatLoc(loc))); } case "killer-spawn" -> { if (locations.getKillerSpawn() != null) { player.sendMessage(messages().get("setup.killer-spawn-exists")); break; } locations.setSingleLocation("killer-spawn", loc); player.sendMessage(messages().get("setup.killer-spawn-placed", formatLoc(loc))); data.spawnIndex = -1; } case "survivor-spawn" -> { int slot = data.spawnIndex >= 1 ? data.spawnIndex : locations.firstFreeSurvivorSlot(); if (slot == -1) { player.sendMessage(messages().get("setup.survivor-spawn-limit")); break; } locations.setSurvivorSpawn(slot, loc); int newCount = locations.getSurvivorSpawns().size(); String label = switch (slot) { case 1 -> "первого"; case 2 -> "второго"; case 3 -> "третьего"; case 4 -> "четвёртого"; default -> "#" + slot; }; player.sendMessage(messages().get("setup.survivor-spawn-placed", label, newCount, formatLoc(loc))); data.spawnIndex = -1; } } plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig()); // Restore the block to its original material + send block update to client if (restoreBlock != null) { Material finalRestore = restoreBlock; plugin.getServer().getScheduler().runTask(plugin, () -> { blockLoc.getBlock().setType(finalRestore, false); player.sendBlockChange(blockLoc, finalRestore.createBlockData()); }); } // НЕ выходим из режима — чтобы можно было установить несколько подряд } // ── Wand position callbacks ──────────────────────────────────────────────── public void setWandPos(Player player, int num, Location loc) { if (num == 1) pos1.put(player.getUniqueId(), loc); else pos2.put(player.getUniqueId(), loc); player.sendMessage(messages().get("setup.wand-point", num, formatLoc(loc))); } public boolean isInSetupMode(UUID uuid) { return setupMode.containsKey(uuid); } public String getSetupMode(UUID uuid) { SetupData data = setupMode.get(uuid); return data != null ? data.mode : null; } /** * Returns the spawn index for the current setup mode, or -1 if not specified. */ public int getSpawnIndex(UUID uuid) { SetupData data = setupMode.get(uuid); return data != null ? data.spawnIndex : -1; } // ── Clear / lobby ───────────────────────────────────────────────── private boolean clearMap(Player player) { plugin.getMapBuilder().clearMap(plugin.getConfigManager().getLocations()); player.sendMessage(messages().get("setup.map-cleared")); return true; } // ── Clear ──────────────────────────────────────────────────────── private boolean handleClear(Player player, String[] args) { var locations = plugin.getConfigManager().getLocations(); if (args.length >= 2) { String type = args[1].toLowerCase(); return clearObjectsByType(player, locations, type); } // Clear ALL object data from config (no block destruction) locations.clearLocationList("generators"); locations.clearLocationList("hooks"); locations.clearLocationList("chests"); locations.clearLocationList("killer-spawn"); for (String id : List.of("a", "b")) { locations.getConfig().set("gate-" + id, null); locations.getConfig().set("gate-" + id + "-corner1", null); locations.getConfig().set("gate-" + id + "-corner2", null); } locations.clearSurvivorSpawns(); plugin.getGeneratorData().clearGenerators(); plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig()); player.sendMessage(messages().get("setup.cleared")); return true; } private boolean clearObjectsByType(Player player, LocationsConfig locations, String type) { String path = switch (type) { case "generator" -> "generators"; case "hook" -> "hooks"; case "chest" -> "chests"; case "gate" -> null; case "killer-spawn" -> "killer-spawn"; case "survivor-spawn" -> null; default -> { player.sendMessage(messages().get("setup.unknown-type", type)); yield null; } }; if (path == null) { if (type.equals("gate")) { for (String id : List.of("a", "b")) { locations.getConfig().set("gate-" + id, null); locations.getConfig().set("gate-" + id + "-corner1", null); locations.getConfig().set("gate-" + id + "-corner2", null); } } else if (type.equals("survivor-spawn")) { locations.clearSurvivorSpawns(); } else { return true; } } else { locations.clearLocationList(path); if (type.equals("generator")) { plugin.getGeneratorData().clearGenerators(); } } plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig()); player.sendMessage(messages().get("setup.cleared-type", type)); return true; } // ── Delete ─────────────────────────────────────────────────────── private boolean handleDelete(Player player, String[] args) { if (args.length < 3) { player.sendMessage(messages().get("setup.delete-usage")); return true; } String type = args[1].toLowerCase(); int index; try { index = Integer.parseInt(args[2]); } catch (NumberFormatException e) { player.sendMessage(messages().get("setup.invalid-number", args[2])); return true; } var locations = plugin.getConfigManager().getLocations(); switch (type) { case "generator" -> { var gens = locations.getGenerators(); if (index < 0 || index >= gens.size()) { player.sendMessage(messages().get("setup.out-of-range", index)); return true; } locations.removeLocationAtIndex("generators", index); plugin.getGeneratorData().removeGenerator(index); player.sendMessage(messages().get("setup.deleted", "generator #" + index)); } case "hook" -> { var hooks = locations.getHooks(); if (index < 0 || index >= hooks.size()) { player.sendMessage(messages().get("setup.out-of-range", index)); return true; } locations.removeLocationAtIndex("hooks", index); player.sendMessage(messages().get("setup.deleted", "hook #" + index)); } case "chest" -> { var chests = locations.getChests(); if (index < 0 || index >= chests.size()) { player.sendMessage(messages().get("setup.out-of-range", index)); return true; } locations.removeLocationAtIndex("chests", index); player.sendMessage(messages().get("setup.deleted", "chest #" + index)); } case "killer-spawn" -> { locations.clearLocationList("killer-spawn"); player.sendMessage(messages().get("setup.deleted", "killer-spawn")); } case "survivor-spawn" -> { if (index < 1 || index > 4) { player.sendMessage(messages().get("setup.out-of-range", index)); return true; } locations.clearSurvivorSpawn(index); player.sendMessage(messages().get("setup.deleted", "survivor-spawn #" + index)); } case "gate" -> { String id = index == 1 ? "a" : index == 2 ? "b" : null; if (id == null) { player.sendMessage(messages().get("setup.out-of-range", index)); return true; } locations.getConfig().set("gate-" + id, null); locations.getConfig().set("gate-" + id + "-corner1", null); locations.getConfig().set("gate-" + id + "-corner2", null); player.sendMessage(messages().get("setup.deleted", "gate-" + id)); } default -> { player.sendMessage(messages().get("setup.unknown-type", type)); return true; } } plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig()); return true; } private boolean clearSpawns(Player player) { var locations = plugin.getConfigManager().getLocations(); locations.clearLocationList("killer-spawn"); locations.clearSurvivorSpawns(); plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig()); player.sendMessage(messages().get("setup.spawns-cleared")); return true; } private boolean setLobby(Player player) { plugin.getConfigManager().getLocations().setLobby(player.getLocation()); plugin.getConfigManager().saveConfig("locations.yml", plugin.getConfigManager().getLocations().getConfig()); player.sendMessage(messages().get("setup.lobby-set", formatLoc(player.getLocation()))); return true; } // ── Usage / helpers ─────────────────────────────────────────────────────── private void sendUsage(CommandSender sender) { sender.sendMessage(messages().get("usage.setup-header")); sender.sendMessage(messages().get("usage.setup-wand")); sender.sendMessage(messages().get("usage.setup-hook")); sender.sendMessage(messages().get("usage.setup-chest")); sender.sendMessage(messages().get("usage.setup-generator")); sender.sendMessage(messages().get("usage.setup-killer-spawn")); sender.sendMessage(messages().get("usage.setup-survivor-spawn")); sender.sendMessage(messages().get("usage.setup-gate")); sender.sendMessage(messages().get("usage.setup-clear")); sender.sendMessage(messages().get("usage.setup-delete")); sender.sendMessage(messages().get("usage.setup-clear-spawns")); sender.sendMessage(messages().get("usage.setup-lobby")); } private String formatLoc(Location loc) { return String.format("[%s, %.1f, %.1f, %.1f]", loc.getWorld().getName(), loc.getX(), loc.getY(), loc.getZ()); } @Override public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { if (!sender.hasPermission("mdbd.admin")) return List.of(); if (args.length == 1) { return List.of("wand", "set", "clear", "delete", "clear-spawns", "lobby").stream() .filter(s -> s.startsWith(args[0].toLowerCase())).toList(); } if (args.length == 2) { if (args[0].equalsIgnoreCase("set") || args[0].equalsIgnoreCase("clear") || args[0].equalsIgnoreCase("delete")) { return List.of("generator", "hook", "chest", "killer-spawn", "survivor-spawn", "gate").stream() .filter(s -> s.startsWith(args[1].toLowerCase())).toList(); } } return List.of(); } }