This commit is contained in:
loki5512344 2026-06-10 23:00:12 +02:00
parent 89dc9eb00d
commit 957ab4a5a2
149 changed files with 5379 additions and 3263 deletions

View file

@ -1,29 +1,55 @@
package dev.loki.command;
import dev.loki.config.LocationsConfig;
import dev.loki.mdbd;
import dev.loki.util.SpawnUtil;
import dev.loki.util.TextUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
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.Arrays;
import java.util.List;
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;
private final java.util.Map<String, Location> selectionCorners;
private final java.util.Set<java.util.UUID> selectingKillerSpawn = new java.util.HashSet<>();
private final java.util.Set<java.util.UUID> selectingSurvivorSpawn = new java.util.HashSet<>();
// Wand positions for gates (LMB=pos1, RMB=pos2)
private final Map<UUID, Location> pos1 = new HashMap<>();
private final Map<UUID, Location> 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<UUID, SetupData> setupMode = new HashMap<>();
public SetupCommand(mdbd plugin) {
this.plugin = plugin;
this.selectionCorners = new java.util.HashMap<>();
}
@Override
@ -37,191 +63,306 @@ public class SetupCommand implements TabExecutor {
sender.sendMessage(Component.text("Только игроки!", NamedTextColor.RED));
return true;
}
if (args.length == 0) {
sendUsage(sender);
return true;
}
if (args.length == 0) { sendUsage(sender); return true; }
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; }
case "wand" -> giveWand(player);
case "set" -> handleSet(player, args);
case "build" -> buildMap(player);
case "clear" -> clearMap(player);
case "clear-spawns" -> clearSpawns(player);
case "lobby" -> setLobby(player);
default -> { sendUsage(sender); yield true; }
};
}
// ── wand ─────────────────────────────────────────────────────────────────
// ── Wand ─────────────────────────────────────────────────────────────────
private boolean giveWand(Player player) {
var wand = new org.bukkit.inventory.ItemStack(org.bukkit.Material.STICK);
ItemStack wand = new ItemStack(Material.WOODEN_AXE);
wand.editMeta(meta -> {
meta.displayName(TextUtil.color("<#FF6B00>DBD Wand <white>(Выделение)"));
meta.displayName(TextUtil.color("<#FF6B00>DBD Setup Wand"));
meta.lore(List.of(
TextUtil.color("<#FFD700>ЛКМ — Первая точка"),
TextUtil.color("<#FFD700>ПКМ — Вторая точка")
TextUtil.color("<#FFD700>ЛКМ по блоку — Точка 1 (для ворот)"),
TextUtil.color("<#FFD700>ПКМ по блоку — Точка 2 (для ворот)"),
TextUtil.color("<gray>Для остального: /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(TextUtil.color("<#00FF00>Получена палочка! <gray>ЛКМ=pos1 ПКМ=pos2"));
player.sendMessage(TextUtil.color("<#00FF00>Получен DBD Setup Wand"));
return true;
}
private boolean setPos(Player player, int num) {
String key = player.getUniqueId().toString();
Location loc = player.getLocation().getBlock().getLocation();
selectionCorners.put(key + "_pos" + num, loc);
player.sendMessage(TextUtil.color("<#00FF00>Установлена точка " + num + ": " + formatLoc(loc)));
return true;
}
// ── Set mode ──────────────────────────────────────────────────────────────
/** 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)));
}
// ── set <type> ────────────────────────────────────────────────────────────
private boolean setObject(Player player, String[] args) {
private boolean handleSet(Player player, String[] args) {
if (args.length < 2) {
player.sendMessage(TextUtil.color("<#FF0000>Использование: /dbdsetup set <тип>"));
player.sendMessage(TextUtil.color("<#FFD700>Типы: generator, hook, pallet, gate, killer-spawn, survivor-spawn, lobby"));
player.sendMessage(TextUtil.color("<#FF0000>Использование: /dbdsetup set <тип> [номер]"));
player.sendMessage(TextUtil.color(
"<#FFD700>Типы: hook, chest, generator, killer-spawn, survivor-spawn, gate"));
player.sendMessage(TextUtil.color(
"<#FFD700>Для спавнов можно указать номер: /dbdsetup set killer-spawn 0"));
return true;
}
String type = args[1].toLowerCase();
var locations = plugin.getConfigManager().getLocations();
UUID uuid = player.getUniqueId();
switch (type) {
case "generator" -> {
plugin.getDbdAdminCommand().toggleGeneratorMode(player);
if (type.equals("gate")) {
if (args.length < 3) {
player.sendMessage(TextUtil.color("<#FF0000>Укажи: /dbdsetup set gate A (или B)"));
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>Сломайте блок для установки (блок не сломается)"));
String gateId = args[2].toUpperCase();
if (!gateId.equals("A") && !gateId.equals("B")) {
player.sendMessage(TextUtil.color("<#FF0000>Ворота могут быть только A или B"));
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(TextUtil.color("<#FF0000>Неизвестный тип: " + type));
return true;
}
// Parse optional spawn index
int spawnIndex = -1;
if (args.length >= 3 && (mode.equals("killer-spawn") || mode.equals("survivor-spawn"))) {
try {
spawnIndex = Integer.parseInt(args[2]);
if (spawnIndex < 0) {
player.sendMessage(TextUtil.color("<#FF0000>Номер спавна должен быть >= 0"));
return true;
}
} catch (NumberFormatException e) {
player.sendMessage(TextUtil.color("<#FF0000>Номер спавна должен быть числом: " + args[2]));
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)));
}
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)));
}
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 "lobby" -> {
Location loc = player.getLocation();
locations.setLobby(loc);
plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig());
player.sendMessage(TextUtil.color("<#00FF00>Лобби установлено: " + formatLoc(loc)));
}
default -> {
player.sendMessage(TextUtil.color("<#FF0000>Неизвестный тип: " + type));
player.sendMessage(TextUtil.color("<#FFD700>Типы: generator, hook, pallet, gate, killer-spawn, survivor-spawn, lobby"));
}
}
// Toggle: если уже в этом режиме с тем же типом — выключаем
SetupData currentData = setupMode.get(uuid);
if (currentData != null && mode.equals(currentData.mode)) {
setupMode.remove(uuid);
player.sendMessage(TextUtil.color("<#FFA500>Режим установки <#FFD700>" + mode + " <#FFA500>выключен"));
return true;
}
setupMode.put(uuid, new SetupData(mode, spawnIndex));
String indexMsg = spawnIndex >= 0 ? " #" + spawnIndex : "";
if (mode.contains("spawn")) {
player.sendMessage(TextUtil.color(
"<#00FF00>Режим установки: <#FFD700>" + mode + indexMsg +
"\n<#00FF00>Сломай блок чтобы установить спавн (блок восстановится)" +
"\n<#FFA500>Повторно введи команду для выключения"));
} else {
player.sendMessage(TextUtil.color(
"<#00FF00>Режим установки: <#FFD700>" + mode + indexMsg +
"\n<#00FF00>Сломай блок чтобы установить (блок восстановится)" +
"\n<#FFA500>Введи команду снова чтобы выключить режим"));
}
return true;
}
// ── spawn selection callbacks (called by SetupBlockBreakListener) ─────────
private boolean placeGate(Player player, String gateId) {
Location p1 = pos1.get(player.getUniqueId());
Location p2 = pos2.get(player.getUniqueId());
public boolean isSelectingKillerSpawn(java.util.UUID uuid) {
return selectingKillerSpawn.contains(uuid);
}
if (p1 == null || p2 == null) {
player.sendMessage(TextUtil.color(
"<#FF0000>Сначала выбери оба угла топориком! (ЛКМ=точка 1, ПКМ=точка 2)"));
return true;
}
public boolean isSelectingSurvivorSpawn(java.util.UUID uuid) {
return selectingSurvivorSpawn.contains(uuid);
}
if (!p1.getWorld().equals(p2.getWorld())) {
player.sendMessage(TextUtil.color("<#FF0000>Точки должны быть в одном мире!"));
return true;
}
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)));
}
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
);
public void onSurvivorSpawnSelected(Player player, Location loc) {
var locations = plugin.getConfigManager().getLocations();
locations.addLocation("survivor-spawns", loc);
locations.setGate(gateId, center);
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>Ворота <#FFFFFF>" + gateId + " <#00FF00>установлены: " + formatLoc(center)));
return true;
}
// ── build / clear / lobby ─────────────────────────────────────────────────
// ── 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" -> {
locations.addLocation("hooks", blockLoc.clone().add(0.5, 0, 0.5));
player.sendMessage(TextUtil.color("<#00FF00>Крюк установлен: " + formatLoc(loc)));
}
case "chest" -> {
locations.addLocation("chests", blockLoc.clone().add(0.5, 0, 0.5));
player.sendMessage(TextUtil.color("<#00FF00>Сундук добавлен: " + formatLoc(loc)));
}
case "generator" -> {
locations.addLocation("generators", blockLoc.clone().add(0.5, 0, 0.5));
plugin.getGeneratorData().addGenerator(blockLoc.clone().add(0.5, 0, 0.5));
player.sendMessage(TextUtil.color("<#00FF00>Генератор добавлен: " + formatLoc(loc)));
}
case "killer-spawn" -> {
locations.setSingleLocation("killer-spawn", loc);
player.sendMessage(TextUtil.color("<#FF4444>Спавн киллера установлен: " + formatLoc(loc)));
data.spawnIndex = -1;
}
case "survivor-spawn" -> {
int currentCount = locations.getSurvivorSpawns().size();
if (currentCount >= 4) {
player.sendMessage(TextUtil.color("<#FF0000>Достигнут лимит спавнов выживших (4)! Используй /dbdsetup clear-spawns для сброса."));
break;
}
saveSpawn(locations, "survivor-spawns", loc, data.spawnIndex);
int newCount = locations.getSurvivorSpawns().size();
String label = switch (newCount) {
case 1 -> "первого";
case 2 -> "второго";
case 3 -> "третьего";
case 4 -> "четвёртого";
default -> "#" + newCount;
};
player.sendMessage(TextUtil.color("<#00FF00>Спавн для <#FFD700>" + label + " <#00FF00>выжившего установлен! (всего: " + newCount + "/4) " + 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());
});
}
// НЕ выходим из режима — чтобы можно было установить несколько подряд
}
/**
* Saves a spawn location at a specific index, or appends if index is -1.
*/
private void saveSpawn(LocationsConfig locations, String path, Location loc, int index) {
if (index >= 0) {
locations.setLocationAtIndex(path, index, loc);
} else {
locations.addLocation(path, loc);
}
}
// ── 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(TextUtil.color("<#00FF00>[Wand] Точка " + 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;
}
// ── Build / clear / lobby ─────────────────────────────────────────────────
private boolean buildMap(Player player) {
player.sendMessage(TextUtil.color("<#FFD700>Строительство карты..."));
plugin.getMapBuilder().buildMap(plugin.getConfigManager().getLocations());
player.sendMessage(TextUtil.color("<#00FF00>Карта построена!"));
return true;
}
private boolean clearMap(Player player) {
player.sendMessage(TextUtil.color("<#FFA500>Очистка карты..."));
plugin.getMapBuilder().clearMap(plugin.getConfigManager().getLocations());
player.sendMessage(TextUtil.color("<#00FF00>Карта очищена!"));
return true;
}
private boolean setLobby(Player player) {
Location loc = player.getLocation();
plugin.getConfigManager().getLocations().setLobby(loc);
plugin.getConfigManager().saveConfig("locations.yml",
plugin.getConfigManager().getLocations().getConfig());
player.sendMessage(TextUtil.color("<#00FF00>Лобби установлено: " + formatLoc(loc)));
private boolean clearSpawns(Player player) {
var locations = plugin.getConfigManager().getLocations();
locations.clearLocationList("killer-spawn");
locations.clearLocationList("survivor-spawns");
plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig());
player.sendMessage(TextUtil.color("<#00FF00>Все спавны (киллер + выжившие) очищены!"));
return true;
}
// ── usage / helpers ───────────────────────────────────────────────────────
private boolean setLobby(Player player) {
plugin.getConfigManager().getLocations().setLobby(player.getLocation());
plugin.getConfigManager().saveConfig("locations.yml",
plugin.getConfigManager().getLocations().getConfig());
player.sendMessage(TextUtil.color("<#00FF00>Лобби установлено: " + formatLoc(player.getLocation())));
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 set <type> — Установить объект на карте"));
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 wand — Получить ванд (ЛКМ/ПКМ для ворот)"));
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup set hook — Установить крюк (ломай блок)"));
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup set chest — Установить сундук"));
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup set generator — Установить генератор"));
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup set killer-spawn — Спавн киллера (только 1)"));
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup set survivor-spawn — Спавн выжившего (до 4)"));
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup set gate A|B — Установить ворота (из точек ванда)"));
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup clear-spawns — Очистить все спавны"));
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup lobby — Установить точку лобби"));
}
private String formatLoc(Location loc) {
return String.format("[%s, %.0f, %.0f, %.0f]",
return String.format("[%s, %.1f, %.1f, %.1f]",
loc.getWorld().getName(), loc.getX(), loc.getY(), loc.getZ());
}
@ -229,19 +370,13 @@ public class SetupCommand implements TabExecutor {
public @Nullable List<String> 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 Arrays.asList("wand", "pos1", "pos2", "set", "build", "clear", "lobby")
.stream()
.filter(s -> s.startsWith(args[0].toLowerCase()))
.toList();
return List.of("wand", "set", "build", "clear", "clear-spawns", "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")
.stream()
.filter(s -> s.startsWith(args[1].toLowerCase()))
.toList();
return List.of("hook", "chest", "generator", "killer-spawn", "survivor-spawn", "gate").stream()
.filter(s -> s.startsWith(args[1].toLowerCase())).toList();
}
return List.of();
}