diff --git a/src/main/java/dev/loki/command/DBDAdminCommand.java b/src/main/java/dev/loki/command/DBDAdminCommand.java index f21e6b8..236fc95 100644 --- a/src/main/java/dev/loki/command/DBDAdminCommand.java +++ b/src/main/java/dev/loki/command/DBDAdminCommand.java @@ -198,6 +198,17 @@ public class DBDAdminCommand implements TabExecutor { return selectingGenerator.contains(uuid); } + public void toggleGeneratorMode(org.bukkit.entity.Player player) { + if (selectingGenerator.contains(player.getUniqueId())) { + selectingGenerator.remove(player.getUniqueId()); + player.sendMessage(TextUtil.color("<#FFA500>Режим выбора генератора отключен")); + } else { + selectingGenerator.add(player.getUniqueId()); + player.sendMessage(TextUtil.color("<#00FF00>Режим выбора генератора включен!")); + player.sendMessage(TextUtil.color("<#FFD700>Сломайте блок чтобы установить генератор")); + } + } + public void shutdown() { if (actionBarTask != null) { actionBarTask.cancel(); diff --git a/src/main/java/dev/loki/command/SetupCommand.java b/src/main/java/dev/loki/command/SetupCommand.java index ced2efb..72802b8 100644 --- a/src/main/java/dev/loki/command/SetupCommand.java +++ b/src/main/java/dev/loki/command/SetupCommand.java @@ -18,6 +18,8 @@ import java.util.List; public class SetupCommand implements TabExecutor { private final mdbd plugin; private final java.util.Map selectionCorners; + private final java.util.Set selectingKillerSpawn = new java.util.HashSet<>(); + private final java.util.Set selectingSurvivorSpawn = new java.util.HashSet<>(); public SetupCommand(mdbd plugin) { this.plugin = plugin; @@ -31,36 +33,29 @@ public class SetupCommand implements TabExecutor { sender.sendMessage(Component.text("Нет прав!", NamedTextColor.RED)); return true; } - if (!(sender instanceof Player player)) { sender.sendMessage(Component.text("Только игроки!", NamedTextColor.RED)); return true; } - if (args.length == 0) { sendUsage(sender); return true; } - String sub = args[0].toLowerCase(); - return switch (sub) { - case "wand" -> giveWand(player); - case "pos1" -> setPos(player, 1); - case "pos2" -> setPos(player, 2); - case "save" -> saveTemplate(player, args); - case "load" -> loadTemplate(player, args); - case "list" -> listTemplates(sender); - case "set" -> setObject(player, args); + return switch (args[0].toLowerCase()) { + case "wand" -> giveWand(player); + case "pos1" -> setPos(player, 1); + case "pos2" -> setPos(player, 2); + case "set" -> setObject(player, args); case "build" -> buildMap(player); case "clear" -> clearMap(player); case "lobby" -> setLobby(player); - default -> { - sendUsage(sender); - yield true; - } + default -> { sendUsage(sender); yield true; } }; } + // ── wand ───────────────────────────────────────────────────────────────── + private boolean giveWand(Player player) { var wand = new org.bukkit.inventory.ItemStack(org.bukkit.Material.STICK); wand.editMeta(meta -> { @@ -69,9 +64,12 @@ public class SetupCommand implements TabExecutor { TextUtil.color("<#FFD700>ЛКМ — Первая точка"), TextUtil.color("<#FFD700>ПКМ — Вторая точка") )); + 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(TextUtil.color("<#00FF00>Получена палочка выделения!")); + player.sendMessage(TextUtil.color("<#00FF00>Получена палочка! ЛКМ=pos1 ПКМ=pos2")); return true; } @@ -83,59 +81,14 @@ public class SetupCommand implements TabExecutor { return true; } - /** Called by PlayerInteractListener when wand is used. */ + /** Called by PlayerInteractListener when wand is used on a block. */ public void setWandPos(Player player, int num, Location loc) { String key = player.getUniqueId().toString(); selectionCorners.put(key + "_pos" + num, loc); player.sendMessage(TextUtil.color("<#00FF00>[Wand] Точка " + num + ": " + formatLoc(loc))); } - private boolean saveTemplate(Player player, String[] args) { - if (args.length < 2) { - player.sendMessage(TextUtil.color("<#FF0000>Использование: /dbdsetup save ")); - return true; - } - String key = player.getUniqueId().toString(); - Location pos1 = selectionCorners.get(key + "_pos1"); - Location pos2 = selectionCorners.get(key + "_pos2"); - - if (pos1 == null || pos2 == null) { - player.sendMessage(TextUtil.color("<#FF0000>Сначала установите обе точки!")); - return true; - } - - plugin.getTemplateManager().saveTemplate(args[1], pos1, pos2); - player.sendMessage(TextUtil.color("<#00FF00>Шаблон '" + args[1] + "' сохранен!")); - return true; - } - - private boolean loadTemplate(Player player, String[] args) { - if (args.length < 2) { - player.sendMessage(TextUtil.color("<#FF0000>Использование: /dbdsetup load ")); - return true; - } - var template = plugin.getTemplateManager().getTemplate(args[1]); - if (template == null) { - player.sendMessage(TextUtil.color("<#FF0000>Шаблон не найден!")); - return true; - } - template.place(player.getLocation(), false); - player.sendMessage(TextUtil.color("<#00FF00>Шаблон '" + args[1] + "' загружен!")); - return true; - } - - private boolean listTemplates(CommandSender sender) { - var templates = plugin.getTemplateManager().getAllTemplates(); - sender.sendMessage(TextUtil.color("<#FFD700>=== Шаблоны ===")); - if (templates.isEmpty()) { - sender.sendMessage(TextUtil.color("<#808080>Нет шаблонов")); - } else { - templates.keySet().forEach(name -> - sender.sendMessage(TextUtil.color("<#00CED1>- " + name)) - ); - } - return true; - } + // ── set ──────────────────────────────────────────────────────────── private boolean setObject(Player player, String[] args) { if (args.length < 2) { @@ -143,55 +96,59 @@ public class SetupCommand implements TabExecutor { player.sendMessage(TextUtil.color("<#FFD700>Типы: generator, hook, pallet, gate, killer-spawn, survivor-spawn, lobby")); return true; } - // For spawns and hooks use exact player location (with yaw/pitch) - // For block-based objects (generator) use block location + String type = args[1].toLowerCase(); var locations = plugin.getConfigManager().getLocations(); switch (type) { case "generator" -> { - Location loc = player.getLocation().getBlock().getLocation(); - loc.add(0.5, 0, 0.5); // center of block - locations.addLocation("generators", loc); - plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig()); - player.sendMessage(TextUtil.color("<#00FF00>Генератор установлен на " + formatLoc(loc))); + plugin.getDbdAdminCommand().toggleGeneratorMode(player); + return true; + } + case "killer-spawn", "killer_spawn" -> { + if (selectingKillerSpawn.remove(player.getUniqueId())) { + player.sendMessage(TextUtil.color("<#FFA500>Режим спавна киллера отключен")); + } else { + selectingKillerSpawn.add(player.getUniqueId()); + player.sendMessage(TextUtil.color("<#FF4444>Режим спавна киллера включен!")); + player.sendMessage(TextUtil.color("<#FFD700>Сломайте блок для установки (блок не сломается)")); + } + return true; + } + case "survivor-spawn", "survivor_spawn" -> { + if (selectingSurvivorSpawn.remove(player.getUniqueId())) { + player.sendMessage(TextUtil.color("<#FFA500>Режим спавна выжившего отключен")); + } else { + selectingSurvivorSpawn.add(player.getUniqueId()); + int current = locations.getSurvivorSpawns().size(); + player.sendMessage(TextUtil.color("<#00FF00>Режим спавна выжившего включен! Уже: " + current)); + player.sendMessage(TextUtil.color("<#FFD700>Сломайте блок для установки (блок не сломается)")); + } + return true; } case "hook" -> { Location loc = player.getLocation(); locations.addLocation("hooks", loc); plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig()); - player.sendMessage(TextUtil.color("<#00FF00>Крюк установлен на " + formatLoc(loc))); + player.sendMessage(TextUtil.color("<#00FF00>Крюк установлен: " + formatLoc(loc))); } case "pallet" -> { Location loc = player.getLocation().getBlock().getLocation(); locations.addLocation("pallets", loc); plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig()); - player.sendMessage(TextUtil.color("<#00FF00>Паллет установлен на " + formatLoc(loc))); + player.sendMessage(TextUtil.color("<#00FF00>Паллет установлен: " + formatLoc(loc))); } case "gate" -> { Location loc = player.getLocation(); locations.addLocation("exitgates", loc); plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig()); - player.sendMessage(TextUtil.color("<#00FF00>Ворота установлены на " + formatLoc(loc))); - } - case "killer-spawn", "killer_spawn" -> { - Location loc = player.getLocation(); - locations.setKillerSpawn(loc); - plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig()); - player.sendMessage(TextUtil.color("<#FF4444>Спавн киллера установлен на " + formatLoc(loc))); - } - case "survivor-spawn", "survivor_spawn" -> { - Location loc = player.getLocation(); - locations.addLocation("survivor-spawns", loc); - plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig()); - int count = locations.getSurvivorSpawns().size(); - player.sendMessage(TextUtil.color("<#00FF00>Спавн выжившего #" + count + " установлен на " + formatLoc(loc))); + player.sendMessage(TextUtil.color("<#00FF00>Ворота установлены: " + formatLoc(loc))); } case "lobby" -> { Location loc = player.getLocation(); locations.setLobby(loc); plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig()); - player.sendMessage(TextUtil.color("<#00FF00>Лобби установлено на " + formatLoc(loc))); + player.sendMessage(TextUtil.color("<#00FF00>Лобби установлено: " + formatLoc(loc))); } default -> { player.sendMessage(TextUtil.color("<#FF0000>Неизвестный тип: " + type)); @@ -201,6 +158,33 @@ public class SetupCommand implements TabExecutor { return true; } + // ── spawn selection callbacks (called by SetupBlockBreakListener) ───────── + + public boolean isSelectingKillerSpawn(java.util.UUID uuid) { + return selectingKillerSpawn.contains(uuid); + } + + public boolean isSelectingSurvivorSpawn(java.util.UUID uuid) { + return selectingSurvivorSpawn.contains(uuid); + } + + public void onKillerSpawnSelected(Player player, Location loc) { + plugin.getConfigManager().getLocations().setKillerSpawn(loc); + plugin.getConfigManager().saveConfig("locations.yml", + plugin.getConfigManager().getLocations().getConfig()); + player.sendMessage(TextUtil.color("<#FF4444>Спавн киллера установлен: " + formatLoc(loc))); + } + + public void onSurvivorSpawnSelected(Player player, Location loc) { + var locations = plugin.getConfigManager().getLocations(); + locations.addLocation("survivor-spawns", loc); + plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig()); + int count = locations.getSurvivorSpawns().size(); + player.sendMessage(TextUtil.color("<#00FF00>Спавн выжившего #" + count + " добавлен: " + formatLoc(loc))); + } + + // ── build / clear / lobby ───────────────────────────────────────────────── + private boolean buildMap(Player player) { player.sendMessage(TextUtil.color("<#FFD700>Строительство карты...")); plugin.getMapBuilder().buildMap(plugin.getConfigManager().getLocations()); @@ -220,21 +204,20 @@ public class SetupCommand implements TabExecutor { plugin.getConfigManager().getLocations().setLobby(loc); plugin.getConfigManager().saveConfig("locations.yml", plugin.getConfigManager().getLocations().getConfig()); - player.sendMessage(TextUtil.color("<#00FF00>Лобби установлено на " + formatLoc(loc))); + player.sendMessage(TextUtil.color("<#00FF00>Лобби установлено: " + formatLoc(loc))); return true; } + // ── usage / helpers ─────────────────────────────────────────────────────── + private void sendUsage(CommandSender sender) { sender.sendMessage(TextUtil.color("<#FF6B00>=== DBD Setup ===")); - sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup wand — Получить палочку")); - sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup pos1/pos2 — Установить точки")); - sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup save — Сохранить шаблон")); - sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup load — Загрузить шаблон")); - sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup list — Список шаблонов")); - sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup set — Установить объект")); + sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup wand — Получить палочку выделения")); + sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup pos1/pos2 — Установить точки выделения")); + sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup set — Установить объект на карте")); sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup build — Построить карту")); sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup clear — Очистить карту")); - sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup lobby — Установить лобби")); + sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup lobby — Установить спавн лобби")); } private String formatLoc(Location loc) { @@ -244,26 +227,22 @@ public class SetupCommand implements TabExecutor { @Override public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, - @NotNull String label, @NotNull String[] args) { + @NotNull String label, @NotNull String[] args) { if (!sender.hasPermission("mdbd.admin")) return List.of(); if (args.length == 1) { - return Arrays.asList("wand", "pos1", "pos2", "save", "load", "list", - "set", "build", "clear", "lobby").stream() + return Arrays.asList("wand", "pos1", "pos2", "set", "build", "clear", "lobby") + .stream() .filter(s -> s.startsWith(args[0].toLowerCase())) .toList(); } if (args.length == 2 && args[0].equalsIgnoreCase("set")) { - return Arrays.asList("generator", "hook", "pallet", "gate", "killer-spawn", "survivor-spawn", "lobby") + return Arrays.asList("generator", "hook", "pallet", "gate", + "killer-spawn", "survivor-spawn", "lobby") .stream() .filter(s -> s.startsWith(args[1].toLowerCase())) .toList(); } - if (args.length == 2 && (args[0].equalsIgnoreCase("save") || args[0].equalsIgnoreCase("load"))) { - return plugin.getTemplateManager().getAllTemplates().keySet().stream() - .filter(s -> s.startsWith(args[1].toLowerCase())) - .toList(); - } return List.of(); } } diff --git a/src/main/java/dev/loki/listener/PlayerInteractListener.java b/src/main/java/dev/loki/listener/PlayerInteractListener.java index b71f312..4808fa5 100644 --- a/src/main/java/dev/loki/listener/PlayerInteractListener.java +++ b/src/main/java/dev/loki/listener/PlayerInteractListener.java @@ -5,7 +5,7 @@ import dev.loki.core.GameManager; import dev.loki.objective.GeneratorManager; import dev.loki.player.BasePlayer; import dev.loki.player.PlayerState; -import dev.loki.util.TextUtil; +import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -89,24 +89,24 @@ public class PlayerInteractListener implements Listener { if (item == null || item.getType() != Material.STICK) return false; if (!item.hasItemMeta()) return false; + // Reliable check: look for NBT persistent data tag instead of display name parsing var meta = item.getItemMeta(); - // Check it's the DBD wand by display name component - if (meta.displayName() == null) return false; - String plain = net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer.plainText() - .serialize(meta.displayName()); - if (!plain.contains("DBD Wand")) return false; + var key = new org.bukkit.NamespacedKey("mdbd", "dbd_wand"); + if (!meta.getPersistentDataContainer().has(key, org.bukkit.persistence.PersistentDataType.BYTE)) { + return false; + } event.setCancelled(true); Action action = event.getAction(); + Location loc = event.getClickedBlock() != null + ? event.getClickedBlock().getLocation() + : player.getLocation().getBlock().getLocation(); + if (action == Action.LEFT_CLICK_BLOCK || action == Action.LEFT_CLICK_AIR) { - setupCommand.setWandPos(player, 1, event.getClickedBlock() != null - ? event.getClickedBlock().getLocation() - : player.getLocation().getBlock().getLocation()); + setupCommand.setWandPos(player, 1, loc); } else if (action == Action.RIGHT_CLICK_BLOCK || action == Action.RIGHT_CLICK_AIR) { - setupCommand.setWandPos(player, 2, event.getClickedBlock() != null - ? event.getClickedBlock().getLocation() - : player.getLocation().getBlock().getLocation()); + setupCommand.setWandPos(player, 2, loc); } return true; } diff --git a/src/main/java/dev/loki/listener/SetupBlockBreakListener.java b/src/main/java/dev/loki/listener/SetupBlockBreakListener.java new file mode 100644 index 0000000..e41192d --- /dev/null +++ b/src/main/java/dev/loki/listener/SetupBlockBreakListener.java @@ -0,0 +1,45 @@ +package dev.loki.listener; + +import dev.loki.command.SetupCommand; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; + +/** + * Handles block-break selection for killer-spawn and survivor-spawn setup modes. + * Generator selection is handled by GeneratorSelectListener. + */ +public class SetupBlockBreakListener implements Listener { + + private final SetupCommand setupCommand; + + public SetupBlockBreakListener(SetupCommand setupCommand) { + this.setupCommand = setupCommand; + } + + @EventHandler + public void onBlockBreak(BlockBreakEvent event) { + Player player = event.getPlayer(); + + boolean killerMode = setupCommand.isSelectingKillerSpawn(player.getUniqueId()); + boolean survivorMode = setupCommand.isSelectingSurvivorSpawn(player.getUniqueId()); + + if (!killerMode && !survivorMode) return; + + event.setCancelled(true); + + // Center of block, standing on top + Location loc = event.getBlock().getLocation() + .add(0.5, 1.0, 0.5); + loc.setYaw(player.getLocation().getYaw()); + loc.setPitch(0f); + + if (killerMode) { + setupCommand.onKillerSpawnSelected(player, loc); + } else { + setupCommand.onSurvivorSpawnSelected(player, loc); + } + } +} diff --git a/src/main/java/dev/loki/role/RoleAssigner.java b/src/main/java/dev/loki/role/RoleAssigner.java index fa70bc3..3cac413 100644 --- a/src/main/java/dev/loki/role/RoleAssigner.java +++ b/src/main/java/dev/loki/role/RoleAssigner.java @@ -5,8 +5,6 @@ import dev.loki.core.GameSession; import dev.loki.player.BasePlayer; import dev.loki.player.Killer; import dev.loki.player.Survivor; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Player; @@ -32,52 +30,69 @@ public class RoleAssigner { public void assignRoles(GameSession session) { Map players = session.getPlayers(); - if (players.isEmpty()) return; + if (players.isEmpty()) { + session.getPlugin().getLogger().warning("[RoleAssigner] No players in session!"); + return; + } List uuids = new ArrayList<>(players.keySet()); UUID killerUuid = selectKiller(uuids); + Location killerSpawn = locations.getKillerSpawn(); List survivorSpawns = locations.getSurvivorSpawns(); + + if (killerSpawn == null) { + session.getPlugin().getLogger().warning("[RoleAssigner] killer-spawn not set! Using game world spawn."); + } + if (survivorSpawns.isEmpty()) { + session.getPlugin().getLogger().warning("[RoleAssigner] survivor-spawns not set! Using game world spawn."); + } + int spawnIndex = 0; for (UUID uuid : uuids) { BasePlayer newPlayer; if (uuid.equals(killerUuid)) { newPlayer = new Killer(uuid); - Location spawn = locations.getKillerSpawn(); - if (spawn != null) newPlayer.setSpawnLocation(spawn); + if (killerSpawn != null) newPlayer.setSpawnLocation(killerSpawn); } else { newPlayer = new Survivor(uuid); if (!survivorSpawns.isEmpty()) { - Location spawn = survivorSpawns.get(spawnIndex % survivorSpawns.size()); - newPlayer.setSpawnLocation(spawn); + newPlayer.setSpawnLocation(survivorSpawns.get(spawnIndex % survivorSpawns.size())); spawnIndex++; } } - // Actually replace the player object in session session.replacePlayer(uuid, newPlayer); - // Announce role and teleport Player bukkit = Bukkit.getPlayer(uuid); if (bukkit != null && bukkit.isOnline()) { if (newPlayer.isKiller()) { - bukkit.sendTitle("§4§lКИЛЛЕР", "§cОхоться на выживших!", 10, 60, 20); - bukkit.sendMessage(Component.text("[DBD] Ваша роль: Киллер", NamedTextColor.RED)); + bukkit.showTitle(net.kyori.adventure.title.Title.title( + net.kyori.adventure.text.Component.text("КИЛЛЕР").color(net.kyori.adventure.text.format.TextColor.color(0xFF2222)), + net.kyori.adventure.text.Component.text("Охоться на выживших!").color(net.kyori.adventure.text.format.NamedTextColor.RED) + )); + bukkit.sendMessage(net.kyori.adventure.text.Component.text("[DBD] Ваша роль: Киллер", net.kyori.adventure.text.format.NamedTextColor.RED)); } else { - bukkit.sendTitle("§a§lВЫЖИВШИЙ", "§eЧини генераторы и сбегай!", 10, 60, 20); - bukkit.sendMessage(Component.text("[DBD] Ваша роль: Выживший", NamedTextColor.GREEN)); + bukkit.showTitle(net.kyori.adventure.title.Title.title( + net.kyori.adventure.text.Component.text("ВЫЖИВШИЙ").color(net.kyori.adventure.text.format.TextColor.color(0x00FF00)), + net.kyori.adventure.text.Component.text("Чини генераторы и сбегай!").color(net.kyori.adventure.text.format.NamedTextColor.YELLOW) + )); + bukkit.sendMessage(net.kyori.adventure.text.Component.text("[DBD] Ваша роль: Выживший", net.kyori.adventure.text.format.NamedTextColor.GREEN)); } - // Teleport to game world spawn (or role spawn) Location spawn = newPlayer.getSpawnLocation(); if (spawn != null) { bukkit.teleport(spawn); + session.getPlugin().getLogger().info("[RoleAssigner] Teleported " + bukkit.getName() + " to " + spawn); } else { - // Fallback: game world spawn var gameWorld = session.getPlugin().getLobbyManager().getGameWorld(); if (gameWorld != null) { - bukkit.teleport(gameWorld.getSpawnLocation()); + Location fallback = gameWorld.getSpawnLocation(); + bukkit.teleport(fallback); + session.getPlugin().getLogger().info("[RoleAssigner] Teleported " + bukkit.getName() + " to game world spawn (no spawn set)"); + } else { + session.getPlugin().getLogger().severe("[RoleAssigner] Game world is null! Cannot teleport " + bukkit.getName()); } } }