diff --git a/build.gradle.kts b/build.gradle.kts index 2854b83..2aabf3f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ repositories { } dependencies { - compileOnly("io.papermc.paper:paper-api:1.20.6-R0.1-SNAPSHOT") + compileOnly("io.papermc.paper:paper-api:1.21.1-R0.1-SNAPSHOT") compileOnly("com.comphenix.protocol:ProtocolLib:5.3.0") } @@ -20,7 +20,7 @@ java { tasks { runServer { - minecraftVersion("1.20.6") + minecraftVersion("1.21.1") jvmArgs("-Xms2G", "-Xmx2G") } diff --git a/src/main/java/dev/loki/command/DBDAdminCommand.java b/src/main/java/dev/loki/command/DBDAdminCommand.java new file mode 100644 index 0000000..b5ff9d3 --- /dev/null +++ b/src/main/java/dev/loki/command/DBDAdminCommand.java @@ -0,0 +1,202 @@ +package dev.loki.command; + +import dev.loki.data.GeneratorDataManager; +import dev.loki.mdbd; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitTask; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +public class DBDAdminCommand implements TabExecutor { + + private final mdbd plugin; + private final GeneratorDataManager generatorData; + private final Set selectingGenerator; + private final MiniMessage mm; + private BukkitTask actionBarTask; + + public DBDAdminCommand(mdbd plugin, GeneratorDataManager generatorData) { + this.plugin = plugin; + this.generatorData = generatorData; + this.selectingGenerator = new HashSet<>(); + this.mm = MiniMessage.miniMessage(); + startActionBarTask(); + } + + private void startActionBarTask() { + this.actionBarTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> { + for (UUID uuid : selectingGenerator) { + Player p = Bukkit.getPlayer(uuid); + if (p != null && p.isOnline()) { + p.sendActionBar(mm.deserialize( + "&#FFA500⚒ Режим установки: �FF00сломайте блок для генератора &#FFA500⚒")); + } + } + }, 0L, 20L); + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, + @NotNull String label, @NotNull String[] args) { + if (!sender.hasPermission("mdbd.admin")) { + 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; + } + + return switch (args[0].toLowerCase()) { + case "create" -> handleCreate(player, args); + case "list" -> handleList(player); + case "remove" -> handleRemove(player, args); + case "clear" -> handleClear(player); + default -> { + sendUsage(sender); + yield true; + } + }; + } + + @SuppressWarnings("UnreachableBranchError") + private boolean handleCreate(Player player, String[] args) { + if (args.length < 2) { + player.sendMessage(Component.text("&#FF0000Использование: /dbdadmin create ")); + return true; + } + + String type = args[1].toLowerCase(); + switch (type) { + case "generator" -> { + if (selectingGenerator.contains(player.getUniqueId())) { + selectingGenerator.remove(player.getUniqueId()); + player.sendMessage(Component.text("&#FFA500Режим выбора генератора отключен")); + } else { + selectingGenerator.add(player.getUniqueId()); + player.sendMessage(Component.text("�FF00Режим выбора генератора включен!")); + player.sendMessage(Component.text("&#FFD700Сломайте блок, чтобы установить генератор")); + player.sendMessage(Component.text("&#FFD700(блок не сломается, а сохранятся его координаты)")); + player.sendMessage(Component.text("&#FFA500Введите /dbdadmin create generator снова, чтобы отключить режим")); + } + return true; + } + default -> { + player.sendMessage(Component.text("&#FF0000Неизвестный тип: " + type)); + return true; + } + } + } + + private boolean handleList(Player player) { + var entries = generatorData.getGeneratorEntries(); + player.sendMessage(Component.text("&#FFD700=== Генераторы (" + entries.size() + ") ===")); + if (entries.isEmpty()) { + player.sendMessage(Component.text("󅑀Нет установленных генераторов")); + } else { + for (int i = 0; i < entries.size(); i++) { + player.sendMessage(Component.text("�CED1[" + i + "] " + entries.get(i))); + } + } + return true; + } + + private boolean handleRemove(Player player, String[] args) { + if (args.length < 2) { + player.sendMessage(Component.text("&#FF0000Использование: /dbdadmin remove ")); + return true; + } + + try { + int id = Integer.parseInt(args[1]); + var entries = generatorData.getGeneratorEntries(); + if (id < 0 || id >= entries.size()) { + player.sendMessage(Component.text("&#FF0000Генератор с ID " + id + " не найден")); + return true; + } + String removed = entries.get(id); + generatorData.removeGenerator(id); + player.sendMessage(Component.text("�FF00Генератор удален: " + removed)); + } catch (NumberFormatException e) { + player.sendMessage(Component.text("&#FF0000ID должен быть числом")); + } + return true; + } + + private boolean handleClear(Player player) { + generatorData.clearGenerators(); + player.sendMessage(Component.text("&#FFA500Все генераторы удалены")); + return true; + } + + private void sendUsage(CommandSender sender) { + sender.sendMessage(Component.text("&#FF6B00=== DBD Admin ===")); + sender.sendMessage(Component.text("&#FFD700/dbdadmin create generator — Режим установки генератора")); + sender.sendMessage(Component.text("&#FFD700/dbdadmin list — Список генераторов")); + sender.sendMessage(Component.text("&#FFD700/dbdadmin remove — Удалить генератор")); + sender.sendMessage(Component.text("&#FFD700/dbdadmin clear — Очистить все генераторы")); + } + + public boolean isSelectingGenerator(UUID uuid) { + return selectingGenerator.contains(uuid); + } + + public void shutdown() { + if (actionBarTask != null) { + actionBarTask.cancel(); + actionBarTask = null; + } + selectingGenerator.clear(); + } + + @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 Arrays.asList("create", "list", "remove", "clear").stream() + .filter(s -> s.startsWith(args[0].toLowerCase())) + .toList(); + } + + if (args.length == 2 && args[0].equalsIgnoreCase("create")) { + return List.of("generator").stream() + .filter(s -> s.startsWith(args[1].toLowerCase())) + .toList(); + } + + if (args.length == 2 && args[0].equalsIgnoreCase("remove")) { + var entries = generatorData.getGeneratorEntries(); + List ids = new ArrayList<>(); + for (int i = 0; i < entries.size(); i++) { + ids.add(String.valueOf(i)); + } + return ids.stream() + .filter(s -> s.startsWith(args[1])) + .toList(); + } + + return List.of(); + } +} diff --git a/src/main/java/dev/loki/command/DBDCommand.java b/src/main/java/dev/loki/command/DBDCommand.java index d3259a1..f7906e2 100644 --- a/src/main/java/dev/loki/command/DBDCommand.java +++ b/src/main/java/dev/loki/command/DBDCommand.java @@ -1,5 +1,6 @@ package dev.loki.command; +import dev.loki.core.GameState; import dev.loki.core.GameManager; import dev.loki.mdbd; import net.kyori.adventure.text.Component; @@ -39,6 +40,7 @@ public class DBDCommand implements TabExecutor { case "forcestart" -> handleForceStart(sender); case "create" -> handleCreate(sender); case "stop" -> handleStop(sender); + case "reload" -> handleReload(sender); default -> { sendUsage(sender); yield true; @@ -82,9 +84,8 @@ public class DBDCommand implements TabExecutor { sender.sendMessage(Component.text("Нет активной игры!", NamedTextColor.RED)); return true; } - var phase = session.getCurrentPhase(); - if (phase instanceof dev.loki.phase.LobbyPhase lobby) { - lobby.forceStart(); + if (session.getState() == GameState.LOBBY) { + session.forceStart(); sender.sendMessage(Component.text("[DBD] Игра форсирована!", NamedTextColor.GREEN)); } else { sender.sendMessage(Component.text("Нельзя форсировать сейчас", NamedTextColor.RED)); @@ -105,6 +106,16 @@ public class DBDCommand implements TabExecutor { return true; } + private boolean handleReload(CommandSender sender) { + if (!sender.hasPermission("mdbd.admin")) { + sender.sendMessage(Component.text("Нет прав!", NamedTextColor.RED)); + return true; + } + plugin.reloadConfigs(); + sender.sendMessage(Component.text("[DBD] Конфиги перезагружены!", NamedTextColor.GREEN)); + return true; + } + private boolean handleStop(CommandSender sender) { if (!sender.hasPermission("mdbd.admin")) { sender.sendMessage(Component.text("Нет прав!", NamedTextColor.RED)); @@ -123,6 +134,7 @@ public class DBDCommand implements TabExecutor { sender.sendMessage(Component.text("/dbd create - создать игру", NamedTextColor.GRAY)); sender.sendMessage(Component.text("/dbd forcestart - форс старт", NamedTextColor.GRAY)); sender.sendMessage(Component.text("/dbd stop - остановить", NamedTextColor.GRAY)); + sender.sendMessage(Component.text("/dbd reload - перезагрузить конфиги", NamedTextColor.GRAY)); } } @@ -132,7 +144,7 @@ public class DBDCommand implements TabExecutor { if (args.length == 1) { List completions = new ArrayList<>(Arrays.asList("join", "leave")); if (sender.hasPermission("mdbd.admin")) { - completions.addAll(Arrays.asList("create", "forcestart", "stop")); + completions.addAll(Arrays.asList("create", "forcestart", "stop", "reload")); } return completions.stream() .filter(s -> s.startsWith(args[0].toLowerCase())) diff --git a/src/main/java/dev/loki/core/GameSession.java b/src/main/java/dev/loki/core/GameSession.java index ea2d51a..4b4f4e9 100644 --- a/src/main/java/dev/loki/core/GameSession.java +++ b/src/main/java/dev/loki/core/GameSession.java @@ -115,6 +115,12 @@ public class GameSession { return plugin.getConfigManager().getGame(); } + public void forceStart() { + if (currentPhase instanceof dev.loki.phase.LobbyPhase lobby) { + lobby.forceStart(); + } + } + public int getTicksElapsed() { return ticksElapsed; } diff --git a/src/main/java/dev/loki/data/GeneratorDataManager.java b/src/main/java/dev/loki/data/GeneratorDataManager.java new file mode 100644 index 0000000..7f11f38 --- /dev/null +++ b/src/main/java/dev/loki/data/GeneratorDataManager.java @@ -0,0 +1,148 @@ +package dev.loki.data; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class GeneratorDataManager { + + private static final double ZONE_RADIUS = 3.0; + + private final JavaPlugin plugin; + private final File dataFile; + private FileConfiguration data; + + public GeneratorDataManager(JavaPlugin plugin) { + this.plugin = plugin; + this.dataFile = new File(plugin.getDataFolder(), "generators.yml"); + load(); + } + + public void load() { + if (!dataFile.exists()) { + try { + dataFile.createNewFile(); + } catch (IOException e) { + plugin.getLogger().warning("Could not create generators.yml"); + } + } + this.data = YamlConfiguration.loadConfiguration(dataFile); + } + + public void save() { + try { + data.save(dataFile); + } catch (IOException e) { + plugin.getLogger().warning("Could not save generators.yml"); + } + } + + public void addGenerator(Location location) { + List list = data.getStringList("generators"); + String entry = formatLocation(location); + if (!list.contains(entry)) { + list.add(entry); + data.set("generators", list); + save(); + } + } + + public void removeGenerator(int index) { + List list = data.getStringList("generators"); + if (index >= 0 && index < list.size()) { + list.remove(index); + data.set("generators", list); + save(); + } + } + + public void clearGenerators() { + data.set("generators", null); + save(); + } + + public List getGenerators() { + List result = new ArrayList<>(); + List list = data.getStringList("generators"); + for (String entry : list) { + Location loc = parseLocation(entry); + if (loc != null) { + result.add(loc); + } + } + return result; + } + + public List getGeneratorEntries() { + return data.getStringList("generators"); + } + + public int getCount() { + return data.getStringList("generators").size(); + } + + public static String formatLocation(Location loc) { + return String.format("%s:%.0f,%.0f,%.0f", + loc.getWorld().getName(), + loc.getX(), + loc.getY(), + loc.getZ()); + } + + private Location parseLocation(String str) { + String[] parts = str.split(":"); + if (parts.length < 2) return null; + + World world = Bukkit.getWorld(parts[0].trim()); + if (world == null) return null; + + String[] coords = parts[1].split(","); + if (coords.length < 3) return null; + + try { + double x = Double.parseDouble(coords[0].trim()); + double y = Double.parseDouble(coords[1].trim()); + double z = Double.parseDouble(coords[2].trim()); + return new Location(world, x, y, z); + } catch (NumberFormatException e) { + return null; + } + } + + public static double getZoneRadius() { + return ZONE_RADIUS; + } + + public boolean isInZone(Location location, List generators) { + for (Location genLoc : generators) { + if (!genLoc.getWorld().equals(location.getWorld())) continue; + if (genLoc.distance(location) <= ZONE_RADIUS) { + return true; + } + } + return false; + } + + public Location getNearestGenerator(Location location, List generators) { + Location nearest = null; + double nearestDist = ZONE_RADIUS; + + for (Location genLoc : generators) { + if (!genLoc.getWorld().equals(location.getWorld())) continue; + double dist = genLoc.distance(location); + if (dist <= nearestDist) { + nearestDist = dist; + nearest = genLoc; + } + } + return nearest; + } +} diff --git a/src/main/java/dev/loki/data/PlayerStats.java b/src/main/java/dev/loki/data/PlayerStats.java index 0d2e777..a7e364e 100644 --- a/src/main/java/dev/loki/data/PlayerStats.java +++ b/src/main/java/dev/loki/data/PlayerStats.java @@ -24,6 +24,7 @@ public class PlayerStats { public UUID getPlayerId() { return playerId; } public int getTotalMatches() { return totalMatches; } public void incrementMatches() { this.totalMatches++; } + public void setTotalMatches(int matches) { this.totalMatches = matches; } public int getEscapes() { return escapes; } public void incrementEscapes() { this.escapes++; } public int getDeaths() { return deaths; } diff --git a/src/main/java/dev/loki/data/YamlStorage.java b/src/main/java/dev/loki/data/YamlStorage.java index 38d8a36..cb49af8 100644 --- a/src/main/java/dev/loki/data/YamlStorage.java +++ b/src/main/java/dev/loki/data/YamlStorage.java @@ -44,7 +44,7 @@ public class YamlStorage implements DataStorage { } FileConfiguration config = YamlConfiguration.loadConfiguration(file); PlayerStats stats = new PlayerStats(playerId); - stats.incrementMatches(config.getInt("matches", 0)); + stats.setTotalMatches(config.getInt("matches", 0)); for (int i = 0; i < config.getInt("escapes", 0); i++) stats.incrementEscapes(); for (int i = 0; i < config.getInt("deaths", 0); i++) stats.incrementDeaths(); for (int i = 0; i < config.getInt("kills", 0); i++) stats.incrementKills(); diff --git a/src/main/java/dev/loki/killer/KillerAttack.java b/src/main/java/dev/loki/killer/KillerAttack.java index e31529e..cf2fe5c 100644 --- a/src/main/java/dev/loki/killer/KillerAttack.java +++ b/src/main/java/dev/loki/killer/KillerAttack.java @@ -67,7 +67,7 @@ public class KillerAttack { } private void applyCooldown(Killer killer) { - cooldowns.put(killer.getUuid(), Bukkit.getCurrentTick()); + cooldowns.put(killer.getUuid(), (long) Bukkit.getCurrentTick()); } public double getCooldownProgress(Killer killer) { diff --git a/src/main/java/dev/loki/listener/GeneratorSelectListener.java b/src/main/java/dev/loki/listener/GeneratorSelectListener.java new file mode 100644 index 0000000..85f30e5 --- /dev/null +++ b/src/main/java/dev/loki/listener/GeneratorSelectListener.java @@ -0,0 +1,46 @@ +package dev.loki.listener; + +import dev.loki.command.DBDAdminCommand; +import dev.loki.data.GeneratorDataManager; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +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; + +public class GeneratorSelectListener implements Listener { + + private final DBDAdminCommand adminCommand; + private final GeneratorDataManager generatorData; + + public GeneratorSelectListener(DBDAdminCommand adminCommand, GeneratorDataManager generatorData) { + this.adminCommand = adminCommand; + this.generatorData = generatorData; + } + + @EventHandler + public void onBlockBreak(BlockBreakEvent event) { + Player player = event.getPlayer(); + if (!adminCommand.isSelectingGenerator(player.getUniqueId())) { + return; + } + + event.setCancelled(true); + + Location loc = event.getBlock().getLocation(); + Location genLoc = new Location(loc.getWorld(), + loc.getX() + 0.5, + loc.getY(), + loc.getZ() + 0.5); + + generatorData.addGenerator(genLoc); + + player.sendMessage(Component.text("�FF00Генератор установлен!")); + player.sendMessage(Component.text("�CED1Координаты: " + GeneratorDataManager.formatLocation(genLoc))); + player.sendMessage(Component.text("&#FFD700Всего генераторов: " + generatorData.getCount())); + player.sendMessage(Component.text("&#FFA500Режим выбора остается активным. Ломайте следующий блок или")); + player.sendMessage(Component.text("&#FFA500введите /dbdadmin create generator для отключения")); + } +} diff --git a/src/main/java/dev/loki/listener/PlayerInteractListener.java b/src/main/java/dev/loki/listener/PlayerInteractListener.java index 5b6faf4..aa5829c 100644 --- a/src/main/java/dev/loki/listener/PlayerInteractListener.java +++ b/src/main/java/dev/loki/listener/PlayerInteractListener.java @@ -1,6 +1,7 @@ package dev.loki.listener; import dev.loki.core.GameManager; +import dev.loki.objective.GeneratorManager; import dev.loki.player.BasePlayer; import dev.loki.player.PlayerState; import org.bukkit.entity.Player; @@ -19,6 +20,7 @@ import java.util.function.BiConsumer; public class PlayerInteractListener implements Listener { private final Plugin plugin; private final GameManager gameManager; + private final GeneratorManager generatorManager; private BiConsumer onGeneratorInteract; private BiConsumer onAttack; @@ -30,9 +32,10 @@ public class PlayerInteractListener implements Listener { private final Map lastClick = new ConcurrentHashMap<>(); private static final long COOLDOWN_MS = 200; - public PlayerInteractListener(Plugin plugin, GameManager gameManager) { + public PlayerInteractListener(Plugin plugin, GameManager gameManager, GeneratorManager generatorManager) { this.plugin = plugin; this.gameManager = gameManager; + this.generatorManager = generatorManager; } public void setOnGeneratorInteract(BiConsumer h) { this.onGeneratorInteract = h; } @@ -72,11 +75,20 @@ public class PlayerInteractListener implements Listener { private void handleRightClick(PlayerInteractEvent event, Player player, BasePlayer bp) { if (event.getClickedBlock() == null) { + if (!bp.isKiller() && onGeneratorInteract != null && generatorManager.isInAnyZone(player)) { + onGeneratorInteract.accept(player, event.getAction()); + return; + } if (onAttack != null && bp.isKiller()) onAttack.accept(player, true); return; } + + if (!bp.isKiller() && onGeneratorInteract != null && generatorManager.isInAnyZone(player)) { + onGeneratorInteract.accept(player, event.getAction()); + return; + } + String type = event.getClickedBlock().getType().toString(); - if (type.contains("TRAPDOOR") && onGeneratorInteract != null) onGeneratorInteract.accept(player, event.getAction()); if ((type.contains("FENCE") || type.contains("PLANKS")) && onPalletInteract != null && !bp.isKiller()) onPalletInteract.accept(player, true); if (!bp.isKiller() && onRescueAttempt != null) onRescueAttempt.accept(player, true); if (type.contains("CHEST") && onChestInteract != null) onChestInteract.accept(player, true); diff --git a/src/main/java/dev/loki/map/MapBuilder.java b/src/main/java/dev/loki/map/MapBuilder.java index 937f83d..360c7ae 100644 --- a/src/main/java/dev/loki/map/MapBuilder.java +++ b/src/main/java/dev/loki/map/MapBuilder.java @@ -1,5 +1,6 @@ package dev.loki.map; +import dev.loki.data.GeneratorDataManager; import dev.loki.mdbd; import dev.loki.config.LocationsConfig; import org.bukkit.Bukkit; @@ -76,6 +77,23 @@ public class MapBuilder { plugin.getLogger().info("Map built successfully!"); } + public void buildGenerators(GeneratorDataManager dataManager) { + List generators = dataManager.getGenerators(); + for (int i = 0; i < generators.size(); i++) { + Location loc = generators.get(i); + if (loc == null) continue; + placeGeneratorMarker(loc); + } + } + + public void placeGeneratorMarker(Location location) { + World world = location.getWorld(); + if (world == null) return; + + world.getBlockAt(location).setType(org.bukkit.Material.SEA_LANTERN); + world.getBlockAt(location.clone().add(0, 1, 0)).setType(org.bukkit.Material.REDSTONE_LAMP); + } + public void clearMap(LocationsConfig config) { // Clear all objects (set to air) clearLocations(config.getGenerators()); @@ -169,8 +187,8 @@ public class MapBuilder { world.getBlockAt(loc.clone().add(0, 1, 1)).setType(org.bukkit.Material.REDSTONE_LAMP); world.getBlockAt(loc.clone().add(0, 1, -1)).setType(org.bukkit.Material.REDSTONE_LAMP); // Control panel: Lever - world.getBlockAt(loc.clone().add(1, 0, 1)).setType(org.bukkit Material.OAK_PLANKS); - world.getBlockAt(loc.clone().add(1, 1, 1)).setType(org.bukkit Material.LEVER); + world.getBlockAt(loc.clone().add(1, 0, 1)).setType(org.bukkit.Material.OAK_PLANKS); + world.getBlockAt(loc.clone().add(1, 1, 1)).setType(org.bukkit.Material.LEVER); } private void buildDefaultHook(Location loc) { @@ -181,7 +199,7 @@ public class MapBuilder { world.getBlockAt(loc.clone().add(0, y, 0)).setType(org.bukkit.Material.IRON_BARS); } // Hook at top: Tripwire hook - world.getBlockAt(loc.clone().add(0, 3, 0)).setType(org.bukkit Material.TRIPWIRE_HOOK); + world.getBlockAt(loc.clone().add(0, 3, 0)).setType(org.bukkit.Material.TRIPWIRE_HOOK); } private void buildDefaultPallet(Location loc) { diff --git a/src/main/java/dev/loki/map/MapTemplate.java b/src/main/java/dev/loki/map/MapTemplate.java index 262b30e..0bd8f70 100644 --- a/src/main/java/dev/loki/map/MapTemplate.java +++ b/src/main/java/dev/loki/map/MapTemplate.java @@ -11,8 +11,7 @@ import org.bukkit.structure.StructureManager; import java.io.File; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; +import java.util.Random; import java.util.logging.Level; public class MapTemplate { @@ -27,7 +26,7 @@ public class MapTemplate { public boolean loadFromFile(File file) { try { - this.structure = structureManager.loadStructure(file.toPath()); + this.structure = structureManager.loadStructure(file); return true; } catch (IOException e) { Bukkit.getLogger().log(Level.WARNING, "Failed to load template: " + name, e); @@ -41,7 +40,7 @@ public class MapTemplate { if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } - structureManager.saveStructure(file.toPath(), structure); + structureManager.saveStructure(file, structure); } catch (IOException e) { Bukkit.getLogger().log(Level.WARNING, "Failed to save template: " + name, e); } @@ -59,23 +58,24 @@ public class MapTemplate { int maxZ = Math.max(corner1.getBlockZ(), corner2.getBlockZ()); Location origin = new Location(world, minX, minY, minZ); + Location farCorner = new Location(world, maxX, maxY, maxZ); int sizeX = maxX - minX + 1; int sizeY = maxY - minY + 1; int sizeZ = maxZ - minZ + 1; this.structure = structureManager.createStructure(); - structure.fill(origin, sizeX, sizeY, sizeZ, true); + structure.fill(origin, farCorner, true); } public void place(Location location, boolean includeEntities) { if (structure == null) return; structure.place(location, true, StructureRotation.NONE, Mirror.NONE, - 0, 1.0f, includeEntities ? 1.0f : 0.0f); + 0, 1.0f, new Random()); } public void placeWithRotation(Location location, StructureRotation rotation, Mirror mirror) { if (structure == null) return; - structure.place(location, true, rotation, mirror, 0, 1.0f, 0.0f); + structure.place(location, true, rotation, mirror, 0, 1.0f, new Random()); } public void remove(Location location) { @@ -83,15 +83,11 @@ public class MapTemplate { World world = location.getWorld(); if (world == null) return; - Location rel = structure.getRelativePosition(); - for (int x = 0; x < structure.getSize().getBlockX(); x++) { - for (int y = 0; y < structure.getSize().getBlockY(); y++) { - for (int z = 0; z < structure.getSize().getBlockZ(); z++) { - Location blockLoc = location.clone().add( - x - rel.getBlockX(), - y - rel.getBlockY(), - z - rel.getBlockZ() - ); + org.bukkit.util.Vector size = structure.getSize(); + for (int x = 0; x < size.getBlockX(); x++) { + for (int y = 0; y < size.getBlockY(); y++) { + for (int z = 0; z < size.getBlockZ(); z++) { + Location blockLoc = location.clone().add(x, y, z); world.getBlockAt(blockLoc).setType(org.bukkit.Material.AIR); } } diff --git a/src/main/java/dev/loki/mdbd.java b/src/main/java/dev/loki/mdbd.java index bfc6963..6cbd752 100644 --- a/src/main/java/dev/loki/mdbd.java +++ b/src/main/java/dev/loki/mdbd.java @@ -1,11 +1,15 @@ package dev.loki; +import dev.loki.command.DBDAdminCommand; import dev.loki.command.DBDCommand; import dev.loki.command.SetupCommand; import dev.loki.config.ConfigManager; import dev.loki.core.GameManager; +import dev.loki.data.GeneratorDataManager; +import dev.loki.listener.GeneratorSelectListener; import dev.loki.map.MapBuilder; import dev.loki.map.TemplateManager; +import dev.loki.world.LobbyManager; import org.bukkit.plugin.java.JavaPlugin; public final class mdbd extends JavaPlugin { @@ -14,6 +18,9 @@ public final class mdbd extends JavaPlugin { private GameManager gameManager; private TemplateManager templateManager; private MapBuilder mapBuilder; + private GeneratorDataManager generatorData; + private DBDAdminCommand dbdAdminCommand; + private LobbyManager lobbyManager; @Override public void onEnable() { @@ -29,6 +36,12 @@ public final class mdbd extends JavaPlugin { this.mapBuilder = new MapBuilder(this, templateManager); + this.generatorData = new GeneratorDataManager(this); + + this.lobbyManager = new LobbyManager(this); + this.lobbyManager.createLobbyWorld(); + this.lobbyManager.createGameWorld(); + this.gameManager = new GameManager(this); getCommand("dbd").setExecutor(new DBDCommand(this, gameManager)); @@ -36,8 +49,16 @@ public final class mdbd extends JavaPlugin { getCommand("dbdsetup").setExecutor(new SetupCommand(this)); getCommand("dbdsetup").setTabCompleter(new SetupCommand(this)); + this.dbdAdminCommand = new DBDAdminCommand(this, generatorData); + getCommand("dbdadmin").setExecutor(dbdAdminCommand); + getCommand("dbdadmin").setTabCompleter(dbdAdminCommand); + + getServer().getPluginManager().registerEvents( + new GeneratorSelectListener(dbdAdminCommand, generatorData), this); + getLogger().info("Mdbd Plugin enabled successfully!"); getLogger().info("Loaded " + templateManager.getAllTemplates().size() + " templates"); + getLogger().info("Loaded " + generatorData.getCount() + " generators from data"); } @Override @@ -49,6 +70,9 @@ public final class mdbd extends JavaPlugin { if (configManager != null) { configManager.saveAllConfigs(); } + if (generatorData != null) { + generatorData.save(); + } instance = null; } @@ -71,4 +95,23 @@ public final class mdbd extends JavaPlugin { public MapBuilder getMapBuilder() { return mapBuilder; } + + public GeneratorDataManager getGeneratorData() { + return generatorData; + } + + public DBDAdminCommand getDbdAdminCommand() { + return dbdAdminCommand; + } + + public LobbyManager getLobbyManager() { + return lobbyManager; + } + + public void reloadConfigs() { + configManager.loadConfigs(); + templateManager.loadAllTemplates(); + generatorData.load(); + getLogger().info("All configs reloaded!"); + } } diff --git a/src/main/java/dev/loki/mdbdBootstrap.java b/src/main/java/dev/loki/mdbdBootstrap.java index bd18ad1..100394a 100644 --- a/src/main/java/dev/loki/mdbdBootstrap.java +++ b/src/main/java/dev/loki/mdbdBootstrap.java @@ -3,7 +3,9 @@ package dev.loki; import io.papermc.paper.plugin.bootstrap.BootstrapContext; import io.papermc.paper.plugin.bootstrap.PluginBootstrap; -class mdbdBootstrap implements PluginBootstrap { +public class mdbdBootstrap implements PluginBootstrap { + + public mdbdBootstrap() {} @Override public void bootstrap(final BootstrapContext context) { diff --git a/src/main/java/dev/loki/mdbdLoader.java b/src/main/java/dev/loki/mdbdLoader.java index 994b13b..eb34ffc 100644 --- a/src/main/java/dev/loki/mdbdLoader.java +++ b/src/main/java/dev/loki/mdbdLoader.java @@ -3,7 +3,9 @@ package dev.loki; import io.papermc.paper.plugin.loader.PluginClasspathBuilder; import io.papermc.paper.plugin.loader.PluginLoader; -class mdbdLoader implements PluginLoader { +public class mdbdLoader implements PluginLoader { + + public mdbdLoader() {} @Override public void classloader(final PluginClasspathBuilder builder) { diff --git a/src/main/java/dev/loki/objective/Generator.java b/src/main/java/dev/loki/objective/Generator.java index 97a938d..c37ebad 100644 --- a/src/main/java/dev/loki/objective/Generator.java +++ b/src/main/java/dev/loki/objective/Generator.java @@ -1,12 +1,16 @@ package dev.loki.objective; +import dev.loki.data.GeneratorDataManager; import org.bukkit.Location; import org.bukkit.block.Block; +import org.bukkit.entity.Player; + import java.util.UUID; public class Generator { private final int id; private final Location location; + private final double zoneRadius; private double progress; private boolean isCompleted; private boolean isBlocked; @@ -15,8 +19,13 @@ public class Generator { private boolean wasKicked; public Generator(int id, Location location) { + this(id, location, GeneratorDataManager.getZoneRadius()); + } + + public Generator(int id, Location location, double zoneRadius) { this.id = id; this.location = location; + this.zoneRadius = zoneRadius; this.progress = 0.0; this.isCompleted = false; this.isBlocked = false; @@ -33,6 +42,20 @@ public class Generator { return location; } + public double getZoneRadius() { + return zoneRadius; + } + + public boolean isPlayerInZone(Player player) { + if (!player.getWorld().equals(location.getWorld())) return false; + return player.getLocation().distance(location) <= zoneRadius; + } + + public boolean isLocationInZone(Location loc) { + if (!loc.getWorld().equals(location.getWorld())) return false; + return loc.distance(location) <= zoneRadius; + } + public double getProgress() { return progress; } @@ -107,6 +130,7 @@ public class Generator { return !isCompleted && !isBlocked; } + @Deprecated public Block getBlock() { return location.getBlock(); } diff --git a/src/main/java/dev/loki/objective/GeneratorManager.java b/src/main/java/dev/loki/objective/GeneratorManager.java index e37f7db..be1b5b7 100644 --- a/src/main/java/dev/loki/objective/GeneratorManager.java +++ b/src/main/java/dev/loki/objective/GeneratorManager.java @@ -1,7 +1,8 @@ package dev.loki.objective; -import dev.loki.config.LocationsConfig; +import dev.loki.data.GeneratorDataManager; import org.bukkit.Location; +import org.bukkit.entity.Player; import java.util.ArrayList; import java.util.HashMap; @@ -13,19 +14,17 @@ public class GeneratorManager { private final int requiredCount; private int completedCount; - public GeneratorManager(LocationsConfig locationsConfig, int requiredCount) { + public GeneratorManager(GeneratorDataManager dataManager, int requiredCount) { this.generators = new HashMap<>(); this.requiredCount = requiredCount; this.completedCount = 0; - loadGenerators(locationsConfig); + loadGenerators(dataManager); } - private void loadGenerators(LocationsConfig config) { - List locations = config.getGenerators(); - int id = 0; - for (Location loc : locations) { - generators.put(id, new Generator(id, loc)); - id++; + private void loadGenerators(GeneratorDataManager dataManager) { + List locations = dataManager.getGenerators(); + for (int i = 0; i < locations.size(); i++) { + generators.put(i, new Generator(i, locations.get(i))); } } @@ -35,13 +34,32 @@ public class GeneratorManager { public Generator getGeneratorAt(Location location) { for (Generator gen : generators.values()) { - if (gen.getLocation().distance(location) < 2.0) { + if (gen.isLocationInZone(location)) { return gen; } } return null; } + public Generator getNearestGenerator(Location location, double maxDistance) { + Generator nearest = null; + double nearestDist = maxDistance; + + for (Generator gen : generators.values()) { + if (!gen.getLocation().getWorld().equals(location.getWorld())) continue; + double dist = gen.getDistance(location); + if (dist < nearestDist) { + nearestDist = dist; + nearest = gen; + } + } + return nearest; + } + + public boolean isInAnyZone(Player player) { + return getGeneratorAt(player.getLocation()) != null; + } + public List getAllGenerators() { return new ArrayList<>(generators.values()); } diff --git a/src/main/java/dev/loki/objective/GeneratorRepair.java b/src/main/java/dev/loki/objective/GeneratorRepair.java index f25012e..07ebcde 100644 --- a/src/main/java/dev/loki/objective/GeneratorRepair.java +++ b/src/main/java/dev/loki/objective/GeneratorRepair.java @@ -25,7 +25,7 @@ public class GeneratorRepair { this.plugin = plugin; this.manager = manager; this.sessions = new HashMap<>(); - this.skillCheck = new SkillCheck(plugin); + this.skillCheck = new SkillCheck(plugin, this); } public void startRepair(Player player, Generator generator) { diff --git a/src/main/java/dev/loki/objective/SkillCheck.java b/src/main/java/dev/loki/objective/SkillCheck.java index a723489..eff6f52 100644 --- a/src/main/java/dev/loki/objective/SkillCheck.java +++ b/src/main/java/dev/loki/objective/SkillCheck.java @@ -9,13 +9,15 @@ import java.util.UUID; public class SkillCheck { private final mdbd plugin; + private final GeneratorRepair generatorRepair; private final Map activeChecks; private static final double ZONE_SIZE = 0.15; private static final double ROTATION_SPEED = 3.0; - public SkillCheck(mdbd plugin) { + public SkillCheck(mdbd plugin, GeneratorRepair generatorRepair) { this.plugin = plugin; + this.generatorRepair = generatorRepair; this.activeChecks = new HashMap<>(); } @@ -58,26 +60,16 @@ public class SkillCheck { private void onSuccess(CheckState state) { activeChecks.remove(state.playerId); - GeneratorRepair repair = getGeneratorRepair(); - if (repair != null) { - Bukkit.getScheduler().runTask(plugin, () -> - repair.onSkillCheckResult(state.playerId, true, state.generator) - ); - } + Bukkit.getScheduler().runTask(plugin, () -> + generatorRepair.onSkillCheckResult(state.playerId, true, state.generator) + ); } private void onFail(CheckState state) { activeChecks.remove(state.playerId); - GeneratorRepair repair = getGeneratorRepair(); - if (repair != null) { - Bukkit.getScheduler().runTask(plugin, () -> - repair.onSkillCheckResult(state.playerId, false, state.generator) - ); - } - } - - private GeneratorRepair getGeneratorRepair() { - return null; + Bukkit.getScheduler().runTask(plugin, () -> + generatorRepair.onSkillCheckResult(state.playerId, false, state.generator) + ); } public boolean isActive(UUID playerId) { diff --git a/src/main/java/dev/loki/perk/killer/CorruptInterventionPerk.java b/src/main/java/dev/loki/perk/killer/CorruptInterventionPerk.java index 1988ec8..db7950f 100644 --- a/src/main/java/dev/loki/perk/killer/CorruptInterventionPerk.java +++ b/src/main/java/dev/loki/perk/killer/CorruptInterventionPerk.java @@ -53,7 +53,7 @@ public class CorruptInterventionPerk extends BasePerk { private void spawnBlockEffect(Player player) { Location loc = player.getLocation(); - player.getWorld().spawnParticle(Particle.BARRIER, loc.add(0, 1, 0), 5, 0.5, 0.5, 0.5, 0); + player.getWorld().spawnParticle(Particle.WITCH, loc.add(0, 1, 0), 5, 0.5, 0.5, 0.5, 0); } private void startEffectTimer() { diff --git a/src/main/java/dev/loki/perk/survivor/BorrowedTimePerk.java b/src/main/java/dev/loki/perk/survivor/BorrowedTimePerk.java index a464ed3..dc5619d 100644 --- a/src/main/java/dev/loki/perk/survivor/BorrowedTimePerk.java +++ b/src/main/java/dev/loki/perk/survivor/BorrowedTimePerk.java @@ -38,7 +38,7 @@ public class BorrowedTimePerk extends BasePerk { if (bukkitPlayer == null) return; bukkitPlayer.addPotionEffect(new PotionEffect( - PotionEffectType.DAMAGE_RESISTANCE, + PotionEffectType.RESISTANCE, ENDURANCE_DURATION_SECONDS * 20, 4, false, false, true)); @@ -65,7 +65,7 @@ public class BorrowedTimePerk extends BasePerk { public void onRemove(BasePlayer player) { Player bukkitPlayer = player.getPlayer(); if (bukkitPlayer != null) { - bukkitPlayer.removePotionEffect(PotionEffectType.DAMAGE_RESISTANCE); + bukkitPlayer.removePotionEffect(PotionEffectType.RESISTANCE); } sendPerkMessage(player, "Перк деактивирован"); } diff --git a/src/main/java/dev/loki/util/LocationUtil.java b/src/main/java/dev/loki/util/LocationUtil.java index 24cc89d..d747a44 100644 --- a/src/main/java/dev/loki/util/LocationUtil.java +++ b/src/main/java/dev/loki/util/LocationUtil.java @@ -57,7 +57,7 @@ public final class LocationUtil { public static boolean hasEntityLineOfSight(Entity from, Entity to, double maxDistance) { if (from == null || to == null) return false; - return from.hasLineOfSight(to) && distanceSquared(from.getLocation(), to.getLocation()) <= maxDistance * maxDistance; + return hasLineOfSight(from.getLocation(), to.getLocation(), maxDistance); } public static boolean isLookingAt(Entity entity, Location target, double angleThreshold) { diff --git a/src/main/java/dev/loki/util/ParticleUtil.java b/src/main/java/dev/loki/util/ParticleUtil.java index 7f72bc9..1f8498c 100644 --- a/src/main/java/dev/loki/util/ParticleUtil.java +++ b/src/main/java/dev/loki/util/ParticleUtil.java @@ -80,7 +80,7 @@ public final class ParticleUtil { public static void blood(Location loc) { World w = loc.getWorld(); if (w == null) return; - w.spawnParticle(Particle.BLOCK_CRUMBLE, loc, 20, 0.2, 0.2, 0.2, 0, + w.spawnParticle(Particle.BLOCK, loc, 20, 0.2, 0.2, 0.2, 0, org.bukkit.Material.REDSTONE_BLOCK.createBlockData()); } diff --git a/src/main/java/dev/loki/world/LobbyManager.java b/src/main/java/dev/loki/world/LobbyManager.java new file mode 100644 index 0000000..f0a8706 --- /dev/null +++ b/src/main/java/dev/loki/world/LobbyManager.java @@ -0,0 +1,149 @@ +package dev.loki.world; + +import dev.loki.mdbd; +import org.bukkit.Bukkit; +import org.bukkit.GameRule; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.WorldCreator; +import org.bukkit.WorldType; + +import java.io.File; + +public class LobbyManager { + private final mdbd plugin; + private World lobbyWorld; + private World gameWorld; + + public LobbyManager(mdbd plugin) { + this.plugin = plugin; + } + + public void createLobbyWorld() { + World existing = Bukkit.getWorld("lobby"); + if (existing != null) { + lobbyWorld = existing; + plugin.getLogger().info("Lobby world already loaded, using existing"); + return; + } + + boolean isNew = !worldFolderExists("lobby"); + + WorldCreator creator = new WorldCreator("lobby"); + creator.generator(new VoidWorldGenerator()); + creator.type(WorldType.FLAT); + creator.environment(World.Environment.NORMAL); + creator.generateStructures(false); + + lobbyWorld = creator.createWorld(); + if (lobbyWorld == null) { + plugin.getLogger().severe("Failed to create lobby world!"); + return; + } + + lobbyWorld.setAutoSave(false); + lobbyWorld.setMonsterSpawnLimit(0); + lobbyWorld.setAnimalSpawnLimit(0); + lobbyWorld.setGameRule(GameRule.DO_MOB_SPAWNING, false); + lobbyWorld.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false); + lobbyWorld.setGameRule(GameRule.DO_WEATHER_CYCLE, false); + lobbyWorld.setGameRule(GameRule.KEEP_INVENTORY, true); + lobbyWorld.setGameRule(GameRule.DO_FIRE_TICK, false); + lobbyWorld.setGameRule(GameRule.ANNOUNCE_ADVANCEMENTS, false); + lobbyWorld.setTime(6000); + + if (isNew) { + buildPlatform(); + } + + lobbyWorld.setSpawnLocation(0, 65, 0); + + if (isNew) { + plugin.getLogger().info("Lobby world created with stone platform!"); + } else { + plugin.getLogger().info("Lobby world loaded (platform already exists)"); + } + } + + public void createGameWorld() { + World existing = Bukkit.getWorld("game"); + if (existing != null) { + gameWorld = existing; + plugin.getLogger().info("Game world already loaded, using existing"); + return; + } + + WorldCreator creator = new WorldCreator("game"); + creator.environment(World.Environment.NORMAL); + creator.generateStructures(false); + + gameWorld = creator.createWorld(); + if (gameWorld == null) { + plugin.getLogger().severe("Failed to create game world!"); + return; + } + + gameWorld.setAutoSave(false); + gameWorld.setMonsterSpawnLimit(0); + gameWorld.setAnimalSpawnLimit(0); + gameWorld.setGameRule(GameRule.DO_MOB_SPAWNING, false); + gameWorld.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false); + gameWorld.setGameRule(GameRule.DO_WEATHER_CYCLE, false); + gameWorld.setGameRule(GameRule.KEEP_INVENTORY, true); + gameWorld.setGameRule(GameRule.DO_FIRE_TICK, false); + gameWorld.setGameRule(GameRule.ANNOUNCE_ADVANCEMENTS, false); + gameWorld.setTime(6000); + + if (worldFolderExists("game")) { + plugin.getLogger().info("Game world loaded"); + } else { + plugin.getLogger().info("Game world created!"); + } + } + + private boolean worldFolderExists(String worldName) { + File worldDir = new File(Bukkit.getWorldContainer(), worldName); + return worldDir.isDirectory(); + } + + private void buildPlatform() { + int y = 64; + + for (int x = -3; x <= 3; x++) { + for (int z = -3; z <= 3; z++) { + lobbyWorld.getBlockAt(x, y, z).setType(Material.STONE); + } + } + + for (int x = -3; x <= 3; x++) { + for (int z = -3; z <= 3; z++) { + lobbyWorld.getBlockAt(x, y - 1, z).setType(Material.BLACK_STAINED_GLASS); + } + } + + for (int x = -4; x <= 4; x++) { + lobbyWorld.getBlockAt(x, y, -4).setType(Material.OAK_FENCE); + lobbyWorld.getBlockAt(x, y, 4).setType(Material.OAK_FENCE); + } + for (int z = -4; z <= 4; z++) { + lobbyWorld.getBlockAt(-4, y, z).setType(Material.OAK_FENCE); + lobbyWorld.getBlockAt(4, y, z).setType(Material.OAK_FENCE); + } + + lobbyWorld.getBlockAt(0, y + 1, 0).setType(Material.GLOWSTONE); + } + + public World getLobbyWorld() { + return lobbyWorld; + } + + public World getGameWorld() { + return gameWorld; + } + + public void teleportToLobby(org.bukkit.entity.Player player) { + if (lobbyWorld != null) { + player.teleport(lobbyWorld.getSpawnLocation()); + } + } +} diff --git a/src/main/java/dev/loki/world/VoidWorldGenerator.java b/src/main/java/dev/loki/world/VoidWorldGenerator.java new file mode 100644 index 0000000..605076f --- /dev/null +++ b/src/main/java/dev/loki/world/VoidWorldGenerator.java @@ -0,0 +1,14 @@ +package dev.loki.world; + +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.generator.WorldInfo; +import org.jetbrains.annotations.NotNull; + +import java.util.Random; + +public class VoidWorldGenerator extends ChunkGenerator { + + @Override + public void generateNoise(@NotNull WorldInfo worldInfo, @NotNull Random random, int chunkX, int chunkZ, @NotNull ChunkData chunkData) { + } +} diff --git a/src/main/resources/locations.yml b/src/main/resources/locations.yml index 7733a27..d52f930 100644 --- a/src/main/resources/locations.yml +++ b/src/main/resources/locations.yml @@ -12,12 +12,8 @@ lobby: yaw: 0.0 pitch: 0.0 -# Генераторы (7 штук) -# Формат: "world,x,y,yaw,pitch" -generators: [] -# Пример: -# - "world,100.5,64.0,200.5,0.0,0.0" -# - "world,150.5,64.0,250.5,90.0,0.0" +# Генераторы — настраиваются через /dbdadmin create generator +# и сохраняются в data/generators.yml # Крюки (обычно 4-6) hooks: [] @@ -49,11 +45,14 @@ survivor-spawns: [] # ============================================ # Команды для настройки: -# /dbdsetup set generator - Установить генератор -# /dbdsetup set hook - Установить крюк -# /dbdsetup set pallet - Установить паллет -# /dbdsetup set chest - Установить сундук -# /dbdsetup set totem - Установить тотем -# /dbdsetup set gate - Установить ворота -# /dbdsetup set lobby - Установить лобби +# /dbdadmin create generator - Войти в режим выбора генератора (сломайте блок) +# /dbdadmin list - Список всех генераторов +# /dbdadmin remove - Удалить генератор по ID +# /dbdadmin clear - Очистить все генераторы +# /dbdsetup set hook - Установить крюк +# /dbdsetup set pallet - Установить паллет +# /dbdsetup set chest - Установить сундук +# /dbdsetup set totem - Установить тотем +# /dbdsetup set gate - Установить ворота +# /dbdsetup set lobby - Установить лобби # ============================================ diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/plugin.yml similarity index 67% rename from src/main/resources/paper-plugin.yml rename to src/main/resources/plugin.yml index e99b207..8f168aa 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,23 +1,21 @@ name: Mdbd version: '${version}' - main: dev.loki.mdbd -bootstrapper: dev.loki.mdbdBootstrap -loader: dev.loki.mdbdLoader -api-version: '1.20.6' -load: POSTWORLD - -authors: [ loki ] +api-version: '1.21' commands: dbd: description: "Главная команда DBD плагина" - usage: "/dbd " + usage: "/dbd " permission: mdbd.player dbdsetup: description: "Команды настройки карты и объектов" usage: "/dbdsetup " permission: mdbd.admin + dbdadmin: + description: "Админ-команды для управления генераторами и объектами" + usage: "/dbdadmin " + permission: mdbd.admin permissions: mdbd.admin: