From 2a2b9f4b5b18e6e7de1be2ebe2cf08df79c171eb Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Sun, 31 May 2026 21:05:25 +0200 Subject: [PATCH] Add map templates, multi-config system, &#rrggbb colors - MapTemplate: Save/load structures via Bukkit Structure API (.nbt files) - TemplateManager: Manage templates and presets - MapBuilder: Build/clear map with generators, hooks, pallets, etc. - SetupCommand: /dbdsetup wand/save/load/set/build for map editing - MessagesConfig: All messages with &#rrggbb color support via MiniMessage - SkillCheckConfig: Skill check colors, zones, rotation settings - Update GameConfig with color codes - ConfigManager: Load/save all 4 config files - Update plugin.yml with dbdsetup command - Add methods to Generator for template integration --- .../java/dev/loki/command/SetupCommand.java | 221 ++++++++++++++++++ .../java/dev/loki/config/ConfigManager.java | 19 ++ src/main/java/dev/loki/config/GameConfig.java | 11 +- .../java/dev/loki/config/MessagesConfig.java | 126 ++++++++++ .../dev/loki/config/SkillCheckConfig.java | 91 ++++++++ src/main/java/dev/loki/map/MapBuilder.java | 220 +++++++++++++++++ src/main/java/dev/loki/map/MapTemplate.java | 116 +++++++++ .../java/dev/loki/map/TemplateManager.java | 119 ++++++++++ src/main/java/dev/loki/mdbd.java | 25 ++ src/main/resources/paper-plugin.yml | 4 + 10 files changed, 951 insertions(+), 1 deletion(-) create mode 100644 src/main/java/dev/loki/command/SetupCommand.java create mode 100644 src/main/java/dev/loki/config/MessagesConfig.java create mode 100644 src/main/java/dev/loki/config/SkillCheckConfig.java create mode 100644 src/main/java/dev/loki/map/MapBuilder.java create mode 100644 src/main/java/dev/loki/map/MapTemplate.java create mode 100644 src/main/java/dev/loki/map/TemplateManager.java diff --git a/src/main/java/dev/loki/command/SetupCommand.java b/src/main/java/dev/loki/command/SetupCommand.java new file mode 100644 index 0000000..b6bf11c --- /dev/null +++ b/src/main/java/dev/loki/command/SetupCommand.java @@ -0,0 +1,221 @@ +package dev.loki.command; + +import dev.loki.mdbd; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Location; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; +import org.bukkit.util.BlockVector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class SetupCommand implements TabExecutor { + private final mdbd plugin; + private final java.util.Map selectionCorners; + + public SetupCommand(mdbd plugin) { + this.plugin = plugin; + this.selectionCorners = new java.util.HashMap<>(); + } + + @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; + } + + 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); + case "build" -> buildMap(player); + case "clear" -> clearMap(player); + case "lobby" -> setLobby(player); + default -> { + sendUsage(sender); + yield true; + } + }; + } + + private boolean giveWand(Player player) { + var wand = new org.bukkit.inventory.ItemStack(org.bukkit.Material.STICK); + wand.editMeta(meta -> { + meta.displayName(Component.text("&#FF6B00DBD Wand").append(Component.text(" (Выделение)"))); + meta.lore(List.of( + Component.text("&#FFD700ЛКМ — Первая точка"), + Component.text("&#FFD700ПКМ — Вторая точка") + )); + }); + player.getInventory().addItem(wand); + player.sendMessage(Component.text("�FF00Получена палочка выделения!", NamedTextColor.GREEN)); + 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(Component.text("�FF00Установлена точка " + num + ": " + formatLoc(loc))); + return true; + } + + private boolean saveTemplate(Player player, String[] args) { + if (args.length < 2) { + player.sendMessage(Component.text("&#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(Component.text("&#FF0000Сначала установите обе точки!")); + return true; + } + + plugin.getTemplateManager().saveTemplate(args[1], pos1, pos2); + player.sendMessage(Component.text("�FF00Шаблон '" + args[1] + "' сохранен!")); + return true; + } + + private boolean loadTemplate(Player player, String[] args) { + if (args.length < 2) { + player.sendMessage(Component.text("&#FF0000Использование: /dbdsetup load ")); + return true; + } + var template = plugin.getTemplateManager().getTemplate(args[1]); + if (template == null) { + player.sendMessage(Component.text("&#FF0000Шаблон не найден!")); + return true; + } + template.place(player.getLocation(), false); + player.sendMessage(Component.text("�FF00Шаблон '" + args[1] + "' загружен!")); + return true; + } + + private boolean listTemplates(CommandSender sender) { + var templates = plugin.getTemplateManager().getAllTemplates(); + sender.sendMessage(Component.text("&#FFD700=== Шаблоны ===")); + if (templates.isEmpty()) { + sender.sendMessage(Component.text("󅒐Нет шаблонов")); + } else { + templates.keySet().forEach(name -> + sender.sendMessage(Component.text("�CED1- " + name)) + ); + } + return true; + } + + private boolean setObject(Player player, String[] args) { + if (args.length < 2) { + player.sendMessage(Component.text("&#FF0000Использование: /dbdsetup set ")); + return true; + } + Location loc = player.getLocation().getBlock().getLocation(); + String type = args[1].toLowerCase(); + var locations = plugin.getConfigManager().getLocations(); + + switch (type) { + case "generator" -> locations.addLocation("generators", loc); + case "hook" -> locations.addLocation("hooks", loc); + case "pallet" -> locations.addLocation("pallets", loc); + case "chest" -> locations.addLocation("chests", loc); + case "totem" -> locations.addLocation("totems", loc); + case "gate" -> locations.addLocation("exitgates", loc); + case "lobby" -> locations.setLobby(loc); + default -> { + player.sendMessage(Component.text("&#FF0000Неизвестный тип: " + type)); + return true; + } + } + plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig()); + player.sendMessage(Component.text("�FF00Установлен: " + type + " на " + formatLoc(loc))); + return true; + } + + private boolean buildMap(Player player) { + player.sendMessage(Component.text("&#FFD700Строительство карты...")); + plugin.getMapBuilder().buildMap(plugin.getConfigManager().getLocations()); + player.sendMessage(Component.text("�FF00Карта построена!")); + return true; + } + + private boolean clearMap(Player player) { + player.sendMessage(Component.text("&#FFA500Очистка карты...")); + plugin.getMapBuilder().clearMap(plugin.getConfigManager().getLocations()); + player.sendMessage(Component.text("�FF00Карта очищена!")); + 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(Component.text("�FF00Лобби установлено на " + formatLoc(loc))); + return true; + } + + private void sendUsage(CommandSender sender) { + sender.sendMessage(Component.text("&#FF6B00=== DBD Setup ===")); + sender.sendMessage(Component.text("&#FFD700/dbdsetup wand — Получить палочку")); + sender.sendMessage(Component.text("&#FFD700/dbdsetup pos1/pos2 — Установить точки")); + sender.sendMessage(Component.text("&#FFD700/dbdsetup save — Сохранить шаблон")); + sender.sendMessage(Component.text("&#FFD700/dbdsetup load — Загрузить шаблон")); + sender.sendMessage(Component.text("&#FFD700/dbdsetup list — Список шаблонов")); + sender.sendMessage(Component.text("&#FFD700/dbdsetup set — Установить объект")); + sender.sendMessage(Component.text("&#FFD700/dbdsetup build — Построить карту")); + sender.sendMessage(Component.text("&#FFD700/dbdsetup clear — Очистить карту")); + sender.sendMessage(Component.text("&#FFD700/dbdsetup lobby — Установить лобби")); + } + + private String formatLoc(Location loc) { + return String.format("[%s, %.0f, %.0f, %.0f]", + loc.getWorld().getName(), loc.getX(), loc.getY(), loc.getZ()); + } + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, + @NotNull String label, @NotNull String[] args) { + if (!sender.hasPermission("mdbd.admin")) return List.of(); + + if (args.length == 1) { + return Arrays.asList("wand", "pos1", "pos2", "save", "load", "list", + "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", "chest", "totem", "gate", "lobby") + .stream() + .filter(s -> s.startsWith(args[1].toLowerCase())) + .toList(); + } + return List.of(); + } +} diff --git a/src/main/java/dev/loki/config/ConfigManager.java b/src/main/java/dev/loki/config/ConfigManager.java index 996541f..0b74072 100644 --- a/src/main/java/dev/loki/config/ConfigManager.java +++ b/src/main/java/dev/loki/config/ConfigManager.java @@ -15,6 +15,8 @@ public class ConfigManager { private LocationsConfig locationsConfig; private GameConfig gameConfig; + private MessagesConfig messagesConfig; + private SkillCheckConfig skillCheckConfig; public ConfigManager(mdbd plugin) { this.plugin = plugin; @@ -27,9 +29,18 @@ public class ConfigManager { public void loadConfigs() { this.locationsConfig = new LocationsConfig(loadConfig("locations.yml")); this.gameConfig = new GameConfig(loadConfig("game.yml")); + this.messagesConfig = new MessagesConfig(loadConfig("messages.yml")); + this.skillCheckConfig = new SkillCheckConfig(loadConfig("skillcheck.yml")); plugin.getLogger().info("Configs loaded successfully"); } + public void saveAllConfigs() { + saveConfig("locations.yml", locationsConfig.getConfig()); + saveConfig("game.yml", gameConfig.getConfig()); + saveConfig("messages.yml", messagesConfig.getConfig()); + saveConfig("skillcheck.yml", skillCheckConfig.getConfig()); + } + private FileConfiguration loadConfig(String fileName) { File file = new File(dataFolder, fileName); if (!file.exists()) { @@ -65,4 +76,12 @@ public class ConfigManager { public GameConfig getGame() { return gameConfig; } + + public MessagesConfig getMessages() { + return messagesConfig; + } + + public SkillCheckConfig getSkillCheck() { + return skillCheckConfig; + } } diff --git a/src/main/java/dev/loki/config/GameConfig.java b/src/main/java/dev/loki/config/GameConfig.java index ac2fe0a..ee89075 100644 --- a/src/main/java/dev/loki/config/GameConfig.java +++ b/src/main/java/dev/loki/config/GameConfig.java @@ -25,12 +25,21 @@ public class GameConfig { setDefault("generators.kick-regression-rate", 2.5); setDefault("generators.kick-duration-seconds", 30); - // Skill check settings + // Skill check settings (legacy - moved to skillcheck.yml) setDefault("skillcheck.zone-size", 0.15); setDefault("skillcheck.rotation-speed", 3.0); setDefault("skillcheck.success-bonus", 5.0); setDefault("skillcheck.fail-penalty", 10.0); + // Colors for UI (&#rrggbb format support) + setDefault("colors.progress-bar-complete", "�FF00"); + setDefault("colors.progress-bar-incomplete", "&#FF0000"); + setDefault("colors.progress-bar-background", "񑘕"); + setDefault("colors.hp-healthy", "�FF00"); + setDefault("colors.hp-injured", "&#FFA500"); + setDefault("colors.hp-downed", "&#FF0000"); + setDefault("colors.hp-dead", "B0000"); + // Hook settings setDefault("hooks.stage1-seconds", 60); setDefault("hooks.stage2-seconds", 60); diff --git a/src/main/java/dev/loki/config/MessagesConfig.java b/src/main/java/dev/loki/config/MessagesConfig.java new file mode 100644 index 0000000..9676e64 --- /dev/null +++ b/src/main/java/dev/loki/config/MessagesConfig.java @@ -0,0 +1,126 @@ +package dev.loki.config; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.bukkit.configuration.file.FileConfiguration; + +public class MessagesConfig { + private final FileConfiguration config; + private final MiniMessage miniMessage; + + public MessagesConfig(FileConfiguration config) { + this.config = config; + this.miniMessage = MiniMessage.miniMessage(); + setDefaults(); + } + + private void setDefaults() { + // Prefix (orange) + setDefault("prefix", "&#FF6B00&l[DBD]&r "); + + // Game messages + setDefault("game.start", "�FF00Игра началась! Бегите от Киллера!"); + setDefault("game.end.victory-escape", "&#FFD700Выжившие сбежали!"); + setDefault("game.end.victory-killer", "&#DC143CКиллер победил!"); + setDefault("game.waiting", "&#FFFF00Ожидание игроков... (%s/%s)"); + setDefault("game.countdown", "&#FFA500До начала: %s сек"); + + // Generator messages + setDefault("generator.repair.start", "�CED1Вы начали чинить генератор..."); + setDefault("generator.repair.stop", "WCEEBВы остановились."); + setDefault("generator.repair.complete", "�FF00✓ Генератор активирован!"); + setDefault("generator.skillcheck.success", "�FF00Отлично!"); + setDefault("generator.skillcheck.fail", "&#FF0000✗ Провал!"); + setDefault("generator.explosion", "&#FF4500⚠ Взрыв генератора!"); + setDefault("generator.regression", "&#FF6347Генератор откатывается..."); + setDefault("generator.progress", "&#FFD700Генераторы: %s/%s"); + + // Killer messages + setDefault("killer.terror-near", "B0000Сердце колотится..."); + setDefault("killer.chase.start", "&#FF0000⚠ Киллер преследует вас!"); + setDefault("killer.chase.stop", "�FF00Вы ушли от погони."); + setDefault("killer.hit", "&#DC143CВы ранены!"); + setDefault("killer.down", "B0000Вы в нокдауне!"); + setDefault("killer.pickup", "󃕐Киллер поднял вас..."); + setDefault("killer.hook", "B0082Вас повесили на крюк!"); + + // Survivor messages + setDefault("survivor.heal.start", "�FA9AВы лечите..."); + setDefault("survivor.heal.complete", "�FF00Вылечено!"); + setDefault("survivor.heal.interrupt", "&#FFD700Лечение прервано!"); + setDefault("survivor.sprint.exhausted", "&#FF8C00Вы устали..."); + setDefault("survivor.pallet.drop", "&#DEB887Вы бросили паллет!"); + setDefault("survivor.pallet.stun", "&#FFD700Киллер оглушен!"); + setDefault("survivor.vault.fast", "�CED1Быстрый прыжок!"); + setDefault("survivor.vault.slow", "WCEEBМедленный прыжок..."); + + // Hook messages + setDefault("hook.stage1", "&#FFFF00Крюк: Фаза 1"); + setDefault("hook.stage2", "&#FFA500Крюк: Фаза 2 — БОРИТЕСЬ!"); + setDefault("hook.struggle.success", "�FF00Успех!"); + setDefault("hook.struggle.fail", "&#FF0000Провал!"); + setDefault("hook.rescue.start", "�CED1Спасение..."); + setDefault("hook.rescue.complete", "�FF00Спасены!"); + setDefault("hook.sacrifice", "B0000☠ Вы жертвованы..."); + + // Item messages + setDefault("item.found", "&#FFD700Найдено: %s"); + setDefault("item.medkit.use", "�FA9AИспользуете аптечку..."); + setDefault("item.flashlight.blind", "&#FFFF00Ослепили киллера!"); + setDefault("item.key.hatch", "�CED1Ключ открывает люк!"); + setDefault("item.map.reveal", "WCEEBОбъекты показаны на карте"); + + // Endgame + setDefault("endgame.gates.powered", "�FF00Ворота запитаны!"); + setDefault("endgame.gates.opening", "&#FFD700Открываете ворота..."); + setDefault("endgame.hatch.open", "B0082Люк открыт!"); + setDefault("endgame.collapse.warning", "&#FF0000⚠ КОЛЛАПС ЧЕРЕЗ %s СЕК!"); + setDefault("endgame.collapse.start", "B0000КОЛЛАПС НАЧАЛСЯ!"); + setDefault("endgame.escape", "�FF00✓ ПОБЕГ!"); + + // Error messages + setDefault("error.no-permission", "&#FF0000Нет прав!"); + setDefault("error.not-in-game", "&#FF6347Вы не в игре!"); + setDefault("error.already-in-game", "&#FF6347Вы уже в игре!"); + setDefault("error.game-full", "&#FF6347Игра заполнена!"); + setDefault("error.no-game", "&#FF6347Нет активной игры!"); + } + + private void setDefault(String path, String value) { + if (!config.contains(path)) { + config.set(path, value); + } + } + + public Component get(String path, Object... args) { + String raw = config.getString(path, "&cMissing: " + path); + String withPrefix = config.getString("prefix", "") + raw; + + // Replace &#RRGGBB with MiniMessage <#RRGGBB> + String mini = withPrefix.replaceAll("&#([0-9A-Fa-f]{6})", "<#$1>"); + + // Replace %s with arguments + if (args.length > 0) { + for (Object arg : args) { + mini = mini.replaceFirst("%s", String.valueOf(arg)); + } + } + + return miniMessage.deserialize(mini); + } + + public Component getRaw(String path, Object... args) { + String raw = config.getString(path, "&cMissing: " + path); + String mini = raw.replaceAll("&#([0-9A-Fa-f]{6})", "<#$1>"); + if (args.length > 0) { + for (Object arg : args) { + mini = mini.replaceFirst("%s", String.valueOf(arg)); + } + } + return miniMessage.deserialize(mini); + } + + public FileConfiguration getConfig() { + return config; + } +} diff --git a/src/main/java/dev/loki/config/SkillCheckConfig.java b/src/main/java/dev/loki/config/SkillCheckConfig.java new file mode 100644 index 0000000..fa94950 --- /dev/null +++ b/src/main/java/dev/loki/config/SkillCheckConfig.java @@ -0,0 +1,91 @@ +package dev.loki.config; + +import org.bukkit.configuration.file.FileConfiguration; + +public class SkillCheckConfig { + private final FileConfiguration config; + + public SkillCheckConfig(FileConfiguration config) { + this.config = config; + setDefaults(); + } + + private void setDefaults() { + // Colors (&#rrggbb format) + setDefault("colors.zone-success", "�FF00"); // Green zone + setDefault("colors.zone-super", "&#FFD700"); // Gold zone (great skill check) + setDefault("colors.arrow", "&#FFFFFF"); // White arrow + setDefault("colors.background", "񑘕"); // Dark gray track + setDefault("colors.text-title", "&#FFFF00"); // Yellow title + setDefault("colors.text-subtitle", "&#FFFFFF"); // White subtitle + + // Zone sizes (degrees) + setDefault("zones.success-size", 15); // ±15° + setDefault("zones.super-size", 5); // ±5° (smaller, bonus) + + // Rotation + setDefault("rotation.speed", 3.0); // rotations per second + setDefault("rotation.direction", "CLOCKWISE"); // or COUNTER_CLOCKWISE + setDefault("rotation.variation", true); // randomize direction + + // Timing + setDefault("timing.min-interval", 80); // ticks between skill checks (4s min) + setDefault("timing.max-interval", 200); // ticks between skill checks (10s max) + setDefault("timing.reaction-window", 20); // ticks to press (1s) + + // Rewards/Penalties + setDefault("rewards.success-bonus", 5.0); // +5% progress + setDefault("rewards.super-bonus", 10.0); // +10% progress + setDefault("penalties.fail-penalty", 10.0); // -10% progress + setDefault("penalties.fail-alert-range", 16); // blocks + setDefault("penalties.fail-explosion", true); // visual explosion + + // Visual + setDefault("visual.show-track", true); + setDefault("visual.show-arrow", true); + setDefault("visual.use-boss-bar", false); // alternative display + setDefault("visual.screenshake-on-fail", true); + } + + private void setDefault(String path, Object value) { + if (!config.contains(path)) { + config.set(path, value); + } + } + + // Getters with defaults + public String getZoneSuccessColor() { return config.getString("colors.zone-success", "�FF00"); } + public String getZoneSuperColor() { return config.getString("colors.zone-super", "&#FFD700"); } + public String getArrowColor() { return config.getString("colors.arrow", "&#FFFFFF"); } + public String getBackgroundColor() { return config.getString("colors.background", "񑘕"); } + public String getTextTitleColor() { return config.getString("colors.text-title", "&#FFFF00"); } + public String getTextSubtitleColor() { return config.getString("colors.text-subtitle", "&#FFFFFF"); } + + public double getSuccessZoneSize() { return config.getDouble("zones.success-size", 15); } + public double getSuperZoneSize() { return config.getDouble("zones.super-size", 5); } + + public double getRotationSpeed() { return config.getDouble("rotation.speed", 3.0); } + public boolean isClockwise() { + return config.getString("rotation.direction", "CLOCKWISE").equals("CLOCKWISE"); + } + public boolean hasVariation() { return config.getBoolean("rotation.variation", true); } + + public int getMinInterval() { return config.getInt("timing.min-interval", 80); } + public int getMaxInterval() { return config.getInt("timing.max-interval", 200); } + public int getReactionWindow() { return config.getInt("timing.reaction-window", 20); } + + public double getSuccessBonus() { return config.getDouble("rewards.success-bonus", 5.0); } + public double getSuperBonus() { return config.getDouble("rewards.super-bonus", 10.0); } + public double getFailPenalty() { return config.getDouble("penalties.fail-penalty", 10.0); } + public int getFailAlertRange() { return config.getInt("penalties.fail-alert-range", 16); } + public boolean hasFailExplosion() { return config.getBoolean("penalties.fail-explosion", true); } + + public boolean showTrack() { return config.getBoolean("visual.show-track", true); } + public boolean showArrow() { return config.getBoolean("visual.show-arrow", true); } + public boolean useBossBar() { return config.getBoolean("visual.use-boss-bar", false); } + public boolean hasScreenshake() { return config.getBoolean("visual.screenshake-on-fail", true); } + + public FileConfiguration getConfig() { + return config; + } +} diff --git a/src/main/java/dev/loki/map/MapBuilder.java b/src/main/java/dev/loki/map/MapBuilder.java new file mode 100644 index 0000000..937f83d --- /dev/null +++ b/src/main/java/dev/loki/map/MapBuilder.java @@ -0,0 +1,220 @@ +package dev.loki.map; + +import dev.loki.mdbd; +import dev.loki.config.LocationsConfig; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.structure.StructureRotation; + +import java.util.List; + +public class MapBuilder { + private final mdbd plugin; + private final TemplateManager templateManager; + + public MapBuilder(mdbd plugin, TemplateManager templateManager) { + this.plugin = plugin; + this.templateManager = templateManager; + } + + public void buildMap(LocationsConfig config) { + World world = Bukkit.getWorlds().get(0); + if (world == null) return; + + // Place generators + List generators = config.getGenerators(); + for (int i = 0; i < generators.size(); i++) { + Location loc = generators.get(i); + if (loc == null) continue; + loc.setWorld(world); + placeGenerator(loc, i, StructureRotation.NONE); + } + + // Place hooks + List hooks = config.getHooks(); + for (int i = 0; i < hooks.size(); i++) { + Location loc = hooks.get(i); + if (loc == null) continue; + loc.setWorld(world); + placeHook(loc); + } + + // Place pallets + List pallets = config.getPallets(); + for (Location loc : pallets) { + if (loc == null) continue; + loc.setWorld(world); + placePallet(loc); + } + + // Place chests + List chests = config.getChests(); + for (Location loc : chests) { + if (loc == null) continue; + loc.setWorld(world); + placeChest(loc); + } + + // Place totems + List totems = config.getTotems(); + for (Location loc : totems) { + if (loc == null) continue; + loc.setWorld(world); + placeTotem(loc); + } + + // Place exit gates + List gates = config.getExitGates(); + for (int i = 0; i < gates.size(); i++) { + Location loc = gates.get(i); + if (loc == null) continue; + loc.setWorld(world); + placeExitGate(loc, i); + } + + plugin.getLogger().info("Map built successfully!"); + } + + public void clearMap(LocationsConfig config) { + // Clear all objects (set to air) + clearLocations(config.getGenerators()); + clearLocations(config.getHooks()); + clearLocations(config.getPallets()); + clearLocations(config.getChests()); + clearLocations(config.getTotems()); + clearLocations(config.getExitGates()); + } + + private void clearLocations(List locations) { + for (Location loc : locations) { + if (loc == null || loc.getWorld() == null) continue; + // Clear 3x3x3 area around each object + for (int x = -1; x <= 1; x++) { + for (int y = 0; y <= 2; y++) { + for (int z = -1; z <= 1; z++) { + Location clearLoc = loc.clone().add(x, y, z); + clearLoc.getBlock().setType(org.bukkit.Material.AIR); + } + } + } + } + } + + public boolean placeGenerator(Location location, int id, StructureRotation rotation) { + // Try custom template first + if (templateManager.hasTemplate("generator")) { + return templateManager.placeObject("generator", location, rotation); + } + // Default: build generator structure + buildDefaultGenerator(location, id); + return true; + } + + public void placeHook(Location location) { + if (templateManager.hasTemplate("hook")) { + templateManager.placeObject("hook", location); + return; + } + buildDefaultHook(location); + } + + public void placePallet(Location location) { + if (templateManager.hasTemplate("pallet")) { + templateManager.placeObject("pallet", location); + return; + } + buildDefaultPallet(location); + } + + public void placeChest(Location location) { + if (templateManager.hasTemplate("chest")) { + templateManager.placeObject("chest", location); + return; + } + location.getBlock().setType(org.bukkit.Material.CHEST); + } + + public void placeTotem(Location location) { + if (templateManager.hasTemplate("totem")) { + templateManager.placeObject("totem", location); + return; + } + buildDefaultTotem(location); + } + + public void placeExitGate(Location location, int id) { + if (templateManager.hasTemplate("exitgate")) { + templateManager.placeObject("exitgate", location); + return; + } + buildDefaultExitGate(location, id); + } + + private void buildDefaultGenerator(Location loc, int id) { + World world = loc.getWorld(); + if (world == null) return; + + // Base: Iron blocks 3x3 + for (int x = -1; x <= 1; x++) { + for (int z = -1; z <= 1; z++) { + world.getBlockAt(loc.clone().add(x, 0, z)).setType(org.bukkit.Material.IRON_BLOCK); + } + } + // Engine: Pistons in center + world.getBlockAt(loc.clone().add(0, 1, 0)).setType(org.bukkit.Material.PISTON); + // Lights: Redstone lamps around + world.getBlockAt(loc.clone().add(1, 1, 0)).setType(org.bukkit.Material.REDSTONE_LAMP); + world.getBlockAt(loc.clone().add(-1, 1, 0)).setType(org.bukkit.Material.REDSTONE_LAMP); + 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); + } + + private void buildDefaultHook(Location loc) { + World world = loc.getWorld(); + if (world == null) return; + // Pole: 3 blocks high + for (int y = 0; y < 3; y++) { + 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); + } + + private void buildDefaultPallet(Location loc) { + World world = loc.getWorld(); + if (world == null) return; + // Horizontal pallet: 3 oak planks + for (int x = -1; x <= 1; x++) { + world.getBlockAt(loc.clone().add(x, 1, 0)).setType(org.bukkit.Material.OAK_PLANKS); + } + // Supports: Fences + world.getBlockAt(loc.clone().add(-1, 0, 0)).setType(org.bukkit.Material.OAK_FENCE); + world.getBlockAt(loc.clone().add(1, 0, 0)).setType(org.bukkit.Material.OAK_FENCE); + } + + private void buildDefaultTotem(Location loc) { + World world = loc.getWorld(); + if (world == null) return; + // Base: Mossy stone + world.getBlockAt(loc).setType(org.bukkit.Material.MOSSY_COBBLESTONE); + // Skull on top + world.getBlockAt(loc.clone().add(0, 1, 0)).setType(org.bukkit.Material.PLAYER_HEAD); + } + + private void buildDefaultExitGate(Location loc, int id) { + World world = loc.getWorld(); + if (world == null) return; + // Door frame: Iron bars + for (int x = -1; x <= 1; x++) { + for (int y = 0; y < 3; y++) { + world.getBlockAt(loc.clone().add(x, y, 0)).setType(org.bukkit.Material.IRON_BARS); + } + } + // Switch: Button + world.getBlockAt(loc.clone().add(1, 1, 1)).setType(org.bukkit.Material.STONE_BUTTON); + } +} diff --git a/src/main/java/dev/loki/map/MapTemplate.java b/src/main/java/dev/loki/map/MapTemplate.java new file mode 100644 index 0000000..262b30e --- /dev/null +++ b/src/main/java/dev/loki/map/MapTemplate.java @@ -0,0 +1,116 @@ +package dev.loki.map; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.block.structure.Mirror; +import org.bukkit.block.structure.StructureRotation; +import org.bukkit.structure.Structure; +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.logging.Level; + +public class MapTemplate { + private final String name; + private final StructureManager structureManager; + private Structure structure; + + public MapTemplate(String name) { + this.name = name; + this.structureManager = Bukkit.getStructureManager(); + } + + public boolean loadFromFile(File file) { + try { + this.structure = structureManager.loadStructure(file.toPath()); + return true; + } catch (IOException e) { + Bukkit.getLogger().log(Level.WARNING, "Failed to load template: " + name, e); + return false; + } + } + + public void saveToFile(File file) { + if (structure == null) return; + try { + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + structureManager.saveStructure(file.toPath(), structure); + } catch (IOException e) { + Bukkit.getLogger().log(Level.WARNING, "Failed to save template: " + name, e); + } + } + + public void saveArea(Location corner1, Location corner2) { + World world = corner1.getWorld(); + if (world == null || !world.equals(corner2.getWorld())) return; + + int minX = Math.min(corner1.getBlockX(), corner2.getBlockX()); + int minY = Math.min(corner1.getBlockY(), corner2.getBlockY()); + int minZ = Math.min(corner1.getBlockZ(), corner2.getBlockZ()); + int maxX = Math.max(corner1.getBlockX(), corner2.getBlockX()); + int maxY = Math.max(corner1.getBlockY(), corner2.getBlockY()); + int maxZ = Math.max(corner1.getBlockZ(), corner2.getBlockZ()); + + Location origin = new Location(world, minX, minY, minZ); + 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); + } + + 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); + } + + public void placeWithRotation(Location location, StructureRotation rotation, Mirror mirror) { + if (structure == null) return; + structure.place(location, true, rotation, mirror, 0, 1.0f, 0.0f); + } + + public void remove(Location location) { + if (structure == null) return; + 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() + ); + world.getBlockAt(blockLoc).setType(org.bukkit.Material.AIR); + } + } + } + } + + public boolean isLoaded() { + return structure != null; + } + + public String getName() { + return name; + } + + public org.bukkit.util.Vector getSize() { + return structure != null ? structure.getSize() : new org.bukkit.util.Vector(0, 0, 0); + } + + public static NamespacedKey getKey(String name) { + return new NamespacedKey("mdbd", name.toLowerCase()); + } +} diff --git a/src/main/java/dev/loki/map/TemplateManager.java b/src/main/java/dev/loki/map/TemplateManager.java new file mode 100644 index 0000000..079a60e --- /dev/null +++ b/src/main/java/dev/loki/map/TemplateManager.java @@ -0,0 +1,119 @@ +package dev.loki.map; + +import dev.loki.mdbd; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; + +public class TemplateManager { + private final mdbd plugin; + private final File templatesFolder; + private final Map templates; + private final Map presets; + + public TemplateManager(mdbd plugin) { + this.plugin = plugin; + this.templatesFolder = new File(plugin.getDataFolder(), "templates"); + this.templates = new HashMap<>(); + this.presets = new HashMap<>(); + if (!templatesFolder.exists()) { + templatesFolder.mkdirs(); + } + } + + public void loadAllTemplates() { + templates.clear(); + File[] files = templatesFolder.listFiles((dir, name) -> name.endsWith(".nbt")); + if (files == null) return; + + for (File file : files) { + String name = file.getName().replace(".nbt", ""); + MapTemplate template = new MapTemplate(name); + if (template.loadFromFile(file)) { + templates.put(name, template); + plugin.getLogger().info("Loaded template: " + name); + } + } + } + + public MapTemplate getTemplate(String name) { + return templates.get(name); + } + + public boolean hasTemplate(String name) { + return templates.containsKey(name); + } + + public void saveTemplate(String name, Location corner1, Location corner2) { + MapTemplate template = new MapTemplate(name); + template.saveArea(corner1, corner2); + File file = new File(templatesFolder, name + ".nbt"); + template.saveToFile(file); + templates.put(name, template); + plugin.getLogger().info("Saved template: " + name); + } + + public Map getAllTemplates() { + return new HashMap<>(templates); + } + + public void registerPreset(String name, PresetMap preset) { + presets.put(name, preset); + } + + public PresetMap getPreset(String name) { + return presets.get(name); + } + + public void loadDefaultPresets() { + // Generator presets (3x3x3 structure) + presets.put("generator_default", new PresetMap("generator_default", + "generator", new Location(null, 1, 0, 1))); + + // Hook preset (2x3x2) + presets.put("hook_default", new PresetMap("hook_default", + "hook", new Location(null, 1, 0, 1))); + + // Pallet preset (3x2x1) + presets.put("pallet_default", new PresetMap("pallet_default", + "pallet", new Location(null, 1, 0, 0))); + + // Chest preset (1x1x1) + presets.put("chest_default", new PresetMap("chest_default", + "chest", new Location(null, 0, 0, 0))); + + // Totem preset (1x2x1) + presets.put("totem_default", new PresetMap("totem_default", + "totem", new Location(null, 0, 0, 0))); + + // Hatch preset (2x1x2) + presets.put("hatch_default", new PresetMap("hatch_default", + "hatch", new Location(null, 1, 0, 1))); + } + + public boolean placeObject(String templateName, Location location) { + MapTemplate template = templates.get(templateName); + if (template == null || !template.isLoaded()) return false; + + template.place(location, false); + return true; + } + + public boolean placeObject(String templateName, Location location, + org.bukkit.block.structure.StructureRotation rotation) { + MapTemplate template = templates.get(templateName); + if (template == null || !template.isLoaded()) return false; + + template.placeWithRotation(location, rotation, + org.bukkit.block.structure.Mirror.NONE); + return true; + } + + public record PresetMap(String name, String templateName, Location offset) {} +} diff --git a/src/main/java/dev/loki/mdbd.java b/src/main/java/dev/loki/mdbd.java index a667fba..bfc6963 100644 --- a/src/main/java/dev/loki/mdbd.java +++ b/src/main/java/dev/loki/mdbd.java @@ -1,14 +1,19 @@ package dev.loki; import dev.loki.command.DBDCommand; +import dev.loki.command.SetupCommand; import dev.loki.config.ConfigManager; import dev.loki.core.GameManager; +import dev.loki.map.MapBuilder; +import dev.loki.map.TemplateManager; import org.bukkit.plugin.java.JavaPlugin; public final class mdbd extends JavaPlugin { private static mdbd instance; private ConfigManager configManager; private GameManager gameManager; + private TemplateManager templateManager; + private MapBuilder mapBuilder; @Override public void onEnable() { @@ -18,12 +23,21 @@ public final class mdbd extends JavaPlugin { this.configManager = new ConfigManager(this); this.configManager.loadConfigs(); + this.templateManager = new TemplateManager(this); + this.templateManager.loadAllTemplates(); + this.templateManager.loadDefaultPresets(); + + this.mapBuilder = new MapBuilder(this, templateManager); + this.gameManager = new GameManager(this); getCommand("dbd").setExecutor(new DBDCommand(this, gameManager)); getCommand("dbd").setTabCompleter(new DBDCommand(this, gameManager)); + getCommand("dbdsetup").setExecutor(new SetupCommand(this)); + getCommand("dbdsetup").setTabCompleter(new SetupCommand(this)); getLogger().info("Mdbd Plugin enabled successfully!"); + getLogger().info("Loaded " + templateManager.getAllTemplates().size() + " templates"); } @Override @@ -32,6 +46,9 @@ public final class mdbd extends JavaPlugin { if (gameManager != null) { gameManager.endGame(); } + if (configManager != null) { + configManager.saveAllConfigs(); + } instance = null; } @@ -46,4 +63,12 @@ public final class mdbd extends JavaPlugin { public GameManager getGameManager() { return gameManager; } + + public TemplateManager getTemplateManager() { + return templateManager; + } + + public MapBuilder getMapBuilder() { + return mapBuilder; + } } diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml index 844e247..e99b207 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -14,6 +14,10 @@ commands: description: "Главная команда DBD плагина" usage: "/dbd " permission: mdbd.player + dbdsetup: + description: "Команды настройки карты и объектов" + usage: "/dbdsetup " + permission: mdbd.admin permissions: mdbd.admin: