mdbd/src/main/java/dev/loki/command/SetupCommand.java

532 lines
23 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<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;
}
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("<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(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<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 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();
}
}