fix: colors, /dbd join flow, world creation, role assign, action bar lobby, /dbdadmin world, tab complete

This commit is contained in:
loki5512344 2026-06-03 19:34:10 +02:00
parent 327ad9b906
commit 1adb8c84fd
19 changed files with 874 additions and 112 deletions

View file

@ -2,6 +2,7 @@ package dev.loki.command;
import dev.loki.data.GeneratorDataManager;
import dev.loki.mdbd;
import dev.loki.util.TextUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.minimessage.MiniMessage;
@ -43,7 +44,7 @@ public class DBDAdminCommand implements TabExecutor {
Player p = Bukkit.getPlayer(uuid);
if (p != null && p.isOnline()) {
p.sendActionBar(mm.deserialize(
"&#FFA500⚒ Режим установки: &#00FF00сломайте блок для генератора &#FFA500⚒"));
"<#FFA500>⚒ Режим установки: <#00FF00>сломайте блок для генератора <#FFA500>⚒"));
}
}
}, 0L, 20L);
@ -72,6 +73,7 @@ public class DBDAdminCommand implements TabExecutor {
case "list" -> handleList(player);
case "remove" -> handleRemove(player, args);
case "clear" -> handleClear(player);
case "world" -> handleWorld(player, args);
default -> {
sendUsage(sender);
yield true;
@ -82,7 +84,7 @@ public class DBDAdminCommand implements TabExecutor {
@SuppressWarnings("UnreachableBranchError")
private boolean handleCreate(Player player, String[] args) {
if (args.length < 2) {
player.sendMessage(Component.text("&#FF0000Использование: /dbdadmin create <generator>"));
player.sendMessage(TextUtil.color("<#FF0000>Использование: /dbdadmin create <generator>"));
return true;
}
@ -91,18 +93,18 @@ public class DBDAdminCommand implements TabExecutor {
case "generator" -> {
if (selectingGenerator.contains(player.getUniqueId())) {
selectingGenerator.remove(player.getUniqueId());
player.sendMessage(Component.text("&#FFA500Режим выбора генератора отключен"));
player.sendMessage(TextUtil.color("<#FFA500>Режим выбора генератора отключен"));
} else {
selectingGenerator.add(player.getUniqueId());
player.sendMessage(Component.text("&#00FF00Режим выбора генератора включен!"));
player.sendMessage(Component.text("&#FFD700Сломайте блок, чтобы установить генератор"));
player.sendMessage(Component.text("&#FFD700(блок не сломается, а сохранятся его координаты)"));
player.sendMessage(Component.text("&#FFA500Введите /dbdadmin create generator снова, чтобы отключить режим"));
player.sendMessage(TextUtil.color("<#00FF00>Режим выбора генератора включен!"));
player.sendMessage(TextUtil.color("<#FFD700>Сломайте блок, чтобы установить генератор"));
player.sendMessage(TextUtil.color("<#FFD700>(блок не сломается, а сохранятся его координаты)"));
player.sendMessage(TextUtil.color("<#FFA500>Введите /dbdadmin create generator снова, чтобы отключить режим"));
}
return true;
}
default -> {
player.sendMessage(Component.text("&#FF0000Неизвестный тип: " + type));
player.sendMessage(TextUtil.color("<#FF0000>Неизвестный тип: " + type));
return true;
}
}
@ -110,12 +112,12 @@ public class DBDAdminCommand implements TabExecutor {
private boolean handleList(Player player) {
var entries = generatorData.getGeneratorEntries();
player.sendMessage(Component.text("&#FFD700=== Генераторы (" + entries.size() + ") ==="));
player.sendMessage(TextUtil.color("<#FFD700>=== Генераторы (" + entries.size() + ") ==="));
if (entries.isEmpty()) {
player.sendMessage(Component.text("&#808000Нет установленных генераторов"));
player.sendMessage(TextUtil.color("<#808000>Нет установленных генераторов"));
} else {
for (int i = 0; i < entries.size(); i++) {
player.sendMessage(Component.text("&#00CED1[" + i + "] " + entries.get(i)));
player.sendMessage(TextUtil.color("<#00CED1>[" + i + "] " + entries.get(i)));
}
}
return true;
@ -123,7 +125,7 @@ public class DBDAdminCommand implements TabExecutor {
private boolean handleRemove(Player player, String[] args) {
if (args.length < 2) {
player.sendMessage(Component.text("&#FF0000Использование: /dbdadmin remove <id>"));
player.sendMessage(TextUtil.color("<#FF0000>Использование: /dbdadmin remove <id>"));
return true;
}
@ -131,30 +133,65 @@ public class DBDAdminCommand implements TabExecutor {
int id = Integer.parseInt(args[1]);
var entries = generatorData.getGeneratorEntries();
if (id < 0 || id >= entries.size()) {
player.sendMessage(Component.text("&#FF0000Генератор с ID " + id + " не найден"));
player.sendMessage(TextUtil.color("<#FF0000>Генератор с ID " + id + " не найден"));
return true;
}
String removed = entries.get(id);
generatorData.removeGenerator(id);
player.sendMessage(Component.text("&#00FF00Генератор удален: " + removed));
player.sendMessage(TextUtil.color("<#00FF00>Генератор удален: " + removed));
} catch (NumberFormatException e) {
player.sendMessage(Component.text("&#FF0000ID должен быть числом"));
player.sendMessage(TextUtil.color("<#FF0000>ID должен быть числом"));
}
return true;
}
private boolean handleClear(Player player) {
generatorData.clearGenerators();
player.sendMessage(Component.text("&#FFA500Все генераторы удалены"));
player.sendMessage(TextUtil.color("<#FFA500>Все генераторы удалены"));
return true;
}
private boolean handleWorld(Player player, String[] args) {
if (args.length < 2) {
player.sendMessage(TextUtil.color("<#FF0000>Использование: /dbdadmin world <lobby|game|<название мира>>"));
return true;
}
String worldArg = args[1].toLowerCase();
org.bukkit.World target;
if (worldArg.equals("lobby")) {
target = plugin.getLobbyManager().getLobbyWorld();
if (target == null) {
player.sendMessage(TextUtil.color("<#FF0000>Мир lobby не загружен!"));
return true;
}
} else if (worldArg.equals("game")) {
target = plugin.getLobbyManager().getGameWorld();
if (target == null) {
player.sendMessage(TextUtil.color("<#FF0000>Мир game не загружен!"));
return true;
}
} else {
target = org.bukkit.Bukkit.getWorld(args[1]);
if (target == null) {
player.sendMessage(TextUtil.color("<#FF0000>Мир не найден: " + args[1]));
return true;
}
}
player.teleport(target.getSpawnLocation());
player.sendMessage(TextUtil.color("<#00FF00>Телепортирован в мир: <#FFD700>" + target.getName()));
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 <id> — Удалить генератор"));
sender.sendMessage(Component.text("&#FFD700/dbdadmin clear — Очистить все генераторы"));
sender.sendMessage(TextUtil.color("<#FF6B00>=== DBD Admin ==="));
sender.sendMessage(TextUtil.color("<#FFD700>/dbdadmin create generator — Режим установки генератора"));
sender.sendMessage(TextUtil.color("<#FFD700>/dbdadmin list — Список генераторов"));
sender.sendMessage(TextUtil.color("<#FFD700>/dbdadmin remove <id> — Удалить генератор"));
sender.sendMessage(TextUtil.color("<#FFD700>/dbdadmin clear — Очистить все генераторы"));
sender.sendMessage(TextUtil.color("<#FFD700>/dbdadmin world <lobby|game|мир> — Телепорт в мир"));
}
public boolean isSelectingGenerator(UUID uuid) {
@ -175,7 +212,7 @@ public class DBDAdminCommand implements TabExecutor {
if (!sender.hasPermission("mdbd.admin")) return List.of();
if (args.length == 1) {
return Arrays.asList("create", "list", "remove", "clear").stream()
return Arrays.asList("create", "list", "remove", "clear", "world").stream()
.filter(s -> s.startsWith(args[0].toLowerCase()))
.toList();
}
@ -186,6 +223,12 @@ public class DBDAdminCommand implements TabExecutor {
.toList();
}
if (args.length == 2 && args[0].equalsIgnoreCase("world")) {
return Arrays.asList("lobby", "game").stream()
.filter(s -> s.startsWith(args[1].toLowerCase()))
.toList();
}
if (args.length == 2 && args[0].equalsIgnoreCase("remove")) {
var entries = generatorData.getGeneratorEntries();
List<String> ids = new ArrayList<>();

View file

@ -173,7 +173,7 @@ public class DBDCommand implements TabExecutor {
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String label, @NotNull String[] args) {
if (args.length == 1) {
List<String> completions = new ArrayList<>(Arrays.asList("join", "leave"));
List<String> completions = new ArrayList<>(Arrays.asList("join", "leave", "lobby"));
if (sender.hasPermission("mdbd.admin")) {
completions.addAll(Arrays.asList("create", "forcestart", "stop", "reload", "setlobby"));
}

View file

@ -1,6 +1,7 @@
package dev.loki.command;
import dev.loki.mdbd;
import dev.loki.util.TextUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Location;
@ -8,11 +9,9 @@ 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;
@ -65,14 +64,14 @@ public class SetupCommand implements TabExecutor {
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.displayName(TextUtil.color("<#FF6B00>DBD Wand <white>(Выделение)"));
meta.lore(List.of(
Component.text("&#FFD700ЛКМ — Первая точка"),
Component.text("&#FFD700ПКМ — Вторая точка")
TextUtil.color("<#FFD700>ЛКМ — Первая точка"),
TextUtil.color("<#FFD700>ПКМ — Вторая точка")
));
});
player.getInventory().addItem(wand);
player.sendMessage(Component.text("&#00FF00Получена палочка выделения!", NamedTextColor.GREEN));
player.sendMessage(TextUtil.color("<#00FF00>Получена палочка выделения!"));
return true;
}
@ -80,13 +79,13 @@ public class SetupCommand implements TabExecutor {
String key = player.getUniqueId().toString();
Location loc = player.getLocation().getBlock().getLocation();
selectionCorners.put(key + "_pos" + num, loc);
player.sendMessage(Component.text("&#00FF00Установлена точка " + num + ": " + formatLoc(loc)));
player.sendMessage(TextUtil.color("<#00FF00>Установлена точка " + num + ": " + formatLoc(loc)));
return true;
}
private boolean saveTemplate(Player player, String[] args) {
if (args.length < 2) {
player.sendMessage(Component.text("&#FF0000Использование: /dbdsetup save <name>"));
player.sendMessage(TextUtil.color("<#FF0000>Использование: /dbdsetup save <name>"));
return true;
}
String key = player.getUniqueId().toString();
@ -94,38 +93,38 @@ public class SetupCommand implements TabExecutor {
Location pos2 = selectionCorners.get(key + "_pos2");
if (pos1 == null || pos2 == null) {
player.sendMessage(Component.text("&#FF0000Сначала установите обе точки!"));
player.sendMessage(TextUtil.color("<#FF0000>Сначала установите обе точки!"));
return true;
}
plugin.getTemplateManager().saveTemplate(args[1], pos1, pos2);
player.sendMessage(Component.text("&#00FF00Шаблон '" + args[1] + "' сохранен!"));
player.sendMessage(TextUtil.color("<#00FF00>Шаблон '" + args[1] + "' сохранен!"));
return true;
}
private boolean loadTemplate(Player player, String[] args) {
if (args.length < 2) {
player.sendMessage(Component.text("&#FF0000Использование: /dbdsetup load <name>"));
player.sendMessage(TextUtil.color("<#FF0000>Использование: /dbdsetup load <name>"));
return true;
}
var template = plugin.getTemplateManager().getTemplate(args[1]);
if (template == null) {
player.sendMessage(Component.text("&#FF0000Шаблон не найден!"));
player.sendMessage(TextUtil.color("<#FF0000>Шаблон не найден!"));
return true;
}
template.place(player.getLocation(), false);
player.sendMessage(Component.text("&#00FF00Шаблон '" + args[1] + "' загружен!"));
player.sendMessage(TextUtil.color("<#00FF00>Шаблон '" + args[1] + "' загружен!"));
return true;
}
private boolean listTemplates(CommandSender sender) {
var templates = plugin.getTemplateManager().getAllTemplates();
sender.sendMessage(Component.text("&#FFD700=== Шаблоны ==="));
sender.sendMessage(TextUtil.color("<#FFD700>=== Шаблоны ==="));
if (templates.isEmpty()) {
sender.sendMessage(Component.text("&#808080Нет шаблонов"));
sender.sendMessage(TextUtil.color("<#808080>Нет шаблонов"));
} else {
templates.keySet().forEach(name ->
sender.sendMessage(Component.text("&#00CED1- " + name))
sender.sendMessage(TextUtil.color("<#00CED1>- " + name))
);
}
return true;
@ -133,7 +132,7 @@ public class SetupCommand implements TabExecutor {
private boolean setObject(Player player, String[] args) {
if (args.length < 2) {
player.sendMessage(Component.text("&#FF0000Использование: /dbdsetup set <generator|hook|pallet|chest|totem|gate|lobby>"));
player.sendMessage(TextUtil.color("<#FF0000>Использование: /dbdsetup set <generator|hook|pallet|chest|totem|gate|lobby>"));
return true;
}
Location loc = player.getLocation().getBlock().getLocation();
@ -149,26 +148,26 @@ public class SetupCommand implements TabExecutor {
case "gate" -> locations.addLocation("exitgates", loc);
case "lobby" -> locations.setLobby(loc);
default -> {
player.sendMessage(Component.text("&#FF0000Неизвестный тип: " + type));
player.sendMessage(TextUtil.color("<#FF0000>Неизвестный тип: " + type));
return true;
}
}
plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig());
player.sendMessage(Component.text("&#00FF00Установлен: " + type + " на " + formatLoc(loc)));
player.sendMessage(TextUtil.color("<#00FF00>Установлен: " + type + " на " + formatLoc(loc)));
return true;
}
private boolean buildMap(Player player) {
player.sendMessage(Component.text("&#FFD700Строительство карты..."));
player.sendMessage(TextUtil.color("<#FFD700>Строительство карты..."));
plugin.getMapBuilder().buildMap(plugin.getConfigManager().getLocations());
player.sendMessage(Component.text("&#00FF00Карта построена!"));
player.sendMessage(TextUtil.color("<#00FF00>Карта построена!"));
return true;
}
private boolean clearMap(Player player) {
player.sendMessage(Component.text("&#FFA500Очистка карты..."));
player.sendMessage(TextUtil.color("<#FFA500>Очистка карты..."));
plugin.getMapBuilder().clearMap(plugin.getConfigManager().getLocations());
player.sendMessage(Component.text("&#00FF00Карта очищена!"));
player.sendMessage(TextUtil.color("<#00FF00>Карта очищена!"));
return true;
}
@ -177,21 +176,21 @@ public class SetupCommand implements TabExecutor {
plugin.getConfigManager().getLocations().setLobby(loc);
plugin.getConfigManager().saveConfig("locations.yml",
plugin.getConfigManager().getLocations().getConfig());
player.sendMessage(Component.text("&#00FF00Лобби установлено на " + formatLoc(loc)));
player.sendMessage(TextUtil.color("<#00FF00>Лобби установлено на " + 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 <name> — Сохранить шаблон"));
sender.sendMessage(Component.text("&#FFD700/dbdsetup load <name> — Загрузить шаблон"));
sender.sendMessage(Component.text("&#FFD700/dbdsetup list — Список шаблонов"));
sender.sendMessage(Component.text("&#FFD700/dbdsetup set <type> — Установить объект"));
sender.sendMessage(Component.text("&#FFD700/dbdsetup build — Построить карту"));
sender.sendMessage(Component.text("&#FFD700/dbdsetup clear — Очистить карту"));
sender.sendMessage(Component.text("&#FFD700/dbdsetup lobby — Установить лобби"));
sender.sendMessage(TextUtil.color("<#FF6B00>=== DBD Setup ==="));
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup wand — Получить палочку"));
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup pos1/pos2 — Установить точки"));
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup save <name> — Сохранить шаблон"));
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup load <name> — Загрузить шаблон"));
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup list — Список шаблонов"));
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup set <type> — Установить объект"));
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup build — Построить карту"));
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup clear — Очистить карту"));
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup lobby — Установить лобби"));
}
private String formatLoc(Location loc) {
@ -216,6 +215,11 @@ public class SetupCommand implements TabExecutor {
.filter(s -> s.startsWith(args[1].toLowerCase()))
.toList();
}
if (args.length == 2 && (args[0].equalsIgnoreCase("save") || args[0].equalsIgnoreCase("load"))) {
return plugin.getTemplateManager().getAllTemplates().keySet().stream()
.filter(s -> s.startsWith(args[1].toLowerCase()))
.toList();
}
return List.of();
}
}

View file

@ -1,6 +1,7 @@
package dev.loki.core;
import dev.loki.mdbd;
import dev.loki.player.Survivor;
import org.bukkit.entity.Player;
import java.util.HashMap;
@ -33,7 +34,14 @@ public class GameManager {
if (playerSessions.containsKey(player.getUniqueId())) {
return false;
}
// Register the player in the session as a Survivor (role assigned later)
Survivor survivor = new Survivor(player.getUniqueId());
currentSession.addPlayer(survivor);
playerSessions.put(player.getUniqueId(), currentSession);
// Notify session that player joined (teleport to lobby etc)
currentSession.notifyPlayerJoined(player);
return true;
}
@ -41,6 +49,8 @@ public class GameManager {
GameSession session = playerSessions.remove(player.getUniqueId());
if (session != null) {
session.removePlayer(player.getUniqueId());
// Teleport after removing from session
plugin.getLobbyManager().teleportToLobby(player);
return true;
}
return false;

View file

@ -100,6 +100,11 @@ public class GameSession {
eventBus.emit(new PlayerJoinEvent(player));
}
/** Replaces an existing player entry with a new role-assigned instance (Killer/Survivor). */
public void replacePlayer(UUID uuid, BasePlayer newPlayer) {
players.put(uuid, newPlayer);
}
public void removePlayer(UUID uuid) {
BasePlayer removed = players.remove(uuid);
if (removed != null) {
@ -154,6 +159,13 @@ public class GameSession {
return eventBus;
}
public void notifyPlayerJoined(org.bukkit.entity.Player player) {
// Called when player joins from command, updates UI
if (currentPhase != null) {
currentPhase.onPlayerJoined(this, player);
}
}
public record PlayerJoinEvent(BasePlayer player) {}
public record PlayerLeaveEvent(BasePlayer player) {}
}

View file

@ -2,8 +2,7 @@ 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 dev.loki.util.TextUtil;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@ -37,10 +36,10 @@ public class GeneratorSelectListener implements Listener {
generatorData.addGenerator(genLoc);
player.sendMessage(Component.text("&#00FF00Генератор установлен!"));
player.sendMessage(Component.text("&#00CED1Координаты: " + GeneratorDataManager.formatLocation(genLoc)));
player.sendMessage(Component.text("&#FFD700Всего генераторов: " + generatorData.getCount()));
player.sendMessage(Component.text("&#FFA500Режим выбора остается активным. Ломайте следующий блок или"));
player.sendMessage(Component.text("&#FFA500введите /dbdadmin create generator для отключения"));
player.sendMessage(TextUtil.color("<#00FF00>Генератор установлен!"));
player.sendMessage(TextUtil.color("<#00CED1>Координаты: " + GeneratorDataManager.formatLocation(genLoc)));
player.sendMessage(TextUtil.color("<#FFD700>Всего генераторов: " + generatorData.getCount()));
player.sendMessage(TextUtil.color("<#FFA500>Режим выбора остается активным. Ломайте следующий блок или"));
player.sendMessage(TextUtil.color("<#FFA500>введите /dbdadmin create generator для отключения"));
}
}

View file

@ -50,7 +50,9 @@ public class PlayerInteractListener implements Listener {
if (event.getHand() != EquipmentSlot.HAND) return;
Player player = event.getPlayer();
if (!gameManager.isInGame(player.getUniqueId())) return;
BasePlayer bp = gameManager.getPlayerSession(player.getUniqueId()).getPlayer(player.getUniqueId());
var session = gameManager.getPlayerSession(player.getUniqueId());
if (session == null) return;
BasePlayer bp = session.getPlayer(player.getUniqueId());
if (bp == null || isIncapacitated(bp)) { event.setCancelled(true); return; }
Action action = event.getAction();

View file

@ -7,6 +7,8 @@ import dev.loki.config.ConfigManager;
import dev.loki.core.GameManager;
import dev.loki.data.GeneratorDataManager;
import dev.loki.listener.GeneratorSelectListener;
import dev.loki.listener.PlayerInteractListener;
import dev.loki.listener.PlayerQuitListener;
import dev.loki.map.MapBuilder;
import dev.loki.map.TemplateManager;
import dev.loki.world.LobbyManager;
@ -19,7 +21,9 @@ public final class mdbd extends JavaPlugin {
private TemplateManager templateManager;
private MapBuilder mapBuilder;
private GeneratorDataManager generatorData;
private DBDCommand dbdCommand;
private DBDAdminCommand dbdAdminCommand;
private SetupCommand setupCommand;
private LobbyManager lobbyManager;
@Override
@ -44,10 +48,13 @@ public final class mdbd extends JavaPlugin {
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));
this.dbdCommand = new DBDCommand(this, gameManager);
getCommand("dbd").setExecutor(dbdCommand);
getCommand("dbd").setTabCompleter(dbdCommand);
this.setupCommand = new SetupCommand(this);
getCommand("dbdsetup").setExecutor(setupCommand);
getCommand("dbdsetup").setTabCompleter(setupCommand);
this.dbdAdminCommand = new DBDAdminCommand(this, generatorData);
getCommand("dbdadmin").setExecutor(dbdAdminCommand);
@ -55,6 +62,10 @@ public final class mdbd extends JavaPlugin {
getServer().getPluginManager().registerEvents(
new GeneratorSelectListener(dbdAdminCommand, generatorData), this);
getServer().getPluginManager().registerEvents(
new PlayerQuitListener(this, gameManager), this);
getServer().getPluginManager().registerEvents(
new PlayerInteractListener(this, gameManager, null), this);
getLogger().info("Mdbd Plugin enabled successfully!");
getLogger().info("Loaded " + templateManager.getAllTemplates().size() + " templates");

View file

@ -49,7 +49,7 @@ public class GeneratorRepair {
generator.setCurrentRepairer(uuid);
RepairSession session = new RepairSession(player, generator);
sessions.put(uuid, session);
generator.setBlocked(true);
// Do NOT set blocked here - blocked means nobody can repair it (e.g. after a kick)
player.sendMessage(MiniMessage.miniMessage().deserialize("<gray>Зажмите <yellow>ПКМ</yellow> для починки"));
}
@ -60,7 +60,10 @@ public class GeneratorRepair {
if (session != null) {
session.generator.setCurrentRepairer(null);
session.generator.setBlocked(false);
// Only unblock if it was blocked due to an ongoing repair (not a kick)
if (!session.generator.isUnderRegression()) {
session.generator.setBlocked(false);
}
player.sendActionBar(Component.empty());
}
}

View file

@ -1,10 +1,11 @@
package dev.loki.phase;
import dev.loki.core.GameSession;
import dev.loki.objective.Generator;
import dev.loki.objective.GeneratorManager;
import dev.loki.player.BasePlayer;
import dev.loki.player.PlayerState;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
@ -24,14 +25,14 @@ public class GameplayPhase implements Phase {
@Override
public void enter(GameSession session) {
Bukkit.broadcastMessage("[DBD] Игра началась! Выжившие, чините генераторы!");
Bukkit.broadcast(Component.text("[DBD] Игра началась! Выжившие, чините генераторы!", NamedTextColor.GREEN));
tickCounter = 0;
// Teleport is handled by RoleAssigner.assignRoles — no double-teleport needed here
}
@Override
public void tick(GameSession session) {
tickCounter++;
generatorManager.tick();
if (tickCounter % 20 == 0) {
@ -45,7 +46,8 @@ public class GameplayPhase implements Phase {
if (generatorManager.areAllRequiredCompleted()) {
allGensDone = true;
Bukkit.broadcastMessage("[DBD] §a§lВсе генераторы починены! Открывайте ворота!");
Bukkit.broadcast(Component.text(
"[DBD] Все генераторы починены! Открывайте ворота!", NamedTextColor.GREEN));
for (BasePlayer bp : session.getPlayers().values()) {
Player p = bp.getPlayer();
if (p != null) {
@ -58,13 +60,11 @@ public class GameplayPhase implements Phase {
private void checkWinConditions(GameSession session) {
int aliveSurvivors = 0;
int escapedSurvivors = 0;
boolean killerDead = true;
int totalSurvivors = 0;
for (BasePlayer bp : session.getPlayers().values()) {
if (bp.isKiller()) {
killerDead = false;
continue;
}
if (bp.isKiller()) continue;
totalSurvivors++;
PlayerState state = bp.getState();
if (state == PlayerState.ESCAPED) {
escapedSurvivors++;
@ -73,16 +73,17 @@ public class GameplayPhase implements Phase {
}
}
boolean allSurvivorsDead = (aliveSurvivors == 0 && escapedSurvivors == 0);
boolean allSurvivorsDead = (aliveSurvivors == 0 && escapedSurvivors == 0 && totalSurvivors > 0);
boolean allEscaped = (escapedSurvivors == totalSurvivors && totalSurvivors > 0);
if (allSurvivorsDead || escapedSurvivors == session.getPlayerCount() - 1) {
if (allSurvivorsDead || allEscaped) {
shouldEnd = true;
}
}
@Override
public void exit(GameSession session) {
Bukkit.broadcastMessage("[DBD] §6Игровая фаза завершена!");
Bukkit.broadcast(Component.text("[DBD] Игровая фаза завершена!", NamedTextColor.GOLD));
}
@Override

View file

@ -2,9 +2,11 @@ package dev.loki.phase;
import dev.loki.config.GameConfig;
import dev.loki.core.GameSession;
import dev.loki.player.BasePlayer;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
public class LobbyPhase implements Phase {
private final GameConfig config;
@ -22,6 +24,14 @@ public class LobbyPhase implements Phase {
@Override
public void enter(GameSession session) {
Bukkit.broadcast(Component.text("[DBD] Лобби открыто! Используйте /dbd join", NamedTextColor.GREEN));
// Teleport already-in-session players to lobby (e.g. on restart)
var lobbyManager = session.getPlugin().getLobbyManager();
for (var player : session.getPlayers().values()) {
var bukkitPlayer = player.getPlayer();
if (bukkitPlayer != null && bukkitPlayer.isOnline()) {
lobbyManager.teleportToLobby(bukkitPlayer);
}
}
}
@Override
@ -33,6 +43,9 @@ public class LobbyPhase implements Phase {
secondsLeft = config.getLobbyCountdown();
countdown = 0;
started = false;
// Show waiting message in action bar to all players in lobby
sendActionBarToAll(session,
Component.text("⏳ Ожидание игроков: " + playerCount + "/" + minPlayers, NamedTextColor.YELLOW));
return;
}
@ -40,12 +53,18 @@ public class LobbyPhase implements Phase {
if (countdown >= 20) {
countdown = 0;
secondsLeft--;
}
if (secondsLeft <= 5 || secondsLeft % 10 == 0) {
Bukkit.broadcast(Component.text(
"[DBD] До начала: " + secondsLeft + " сек. (" + playerCount + "/" + minPlayers + ")",
NamedTextColor.YELLOW));
}
// Action bar: показываем каждый тик (обновляется плавно)
String bar = buildCountdownBar(secondsLeft, config.getLobbyCountdown());
sendActionBarToAll(session,
Component.text("▶ Старт через: " + secondsLeft + "с " + bar +
" (" + playerCount + "/" + minPlayers + ")", NamedTextColor.GREEN));
// Chat-announce на ключевых отметках
if (countdown == 0 && (secondsLeft <= 5 || secondsLeft == 10 || secondsLeft == 30)) {
Bukkit.broadcast(Component.text(
"[DBD] До начала: " + secondsLeft + " сек.", NamedTextColor.YELLOW));
}
if (secondsLeft <= 0) {
@ -53,11 +72,41 @@ public class LobbyPhase implements Phase {
}
}
private void sendActionBarToAll(GameSession session, Component msg) {
for (BasePlayer bp : session.getPlayers().values()) {
Player p = bp.getPlayer();
if (p != null && p.isOnline()) {
p.sendActionBar(msg);
}
}
}
private String buildCountdownBar(int current, int total) {
int filled = total > 0 ? (int) (10.0 * current / total) : 0;
int empty = 10 - filled;
return "§a" + "█".repeat(Math.max(0, filled)) + "§7" + "░".repeat(Math.max(0, empty));
}
@Override
public void exit(GameSession session) {
// Clear action bar for all
for (BasePlayer bp : session.getPlayers().values()) {
Player p = bp.getPlayer();
if (p != null && p.isOnline()) p.sendActionBar(Component.empty());
}
Bukkit.broadcast(Component.text("[DBD] Игра начинается!", NamedTextColor.GREEN));
}
@Override
public void onPlayerJoined(GameSession session, Player player) {
session.getPlugin().getLobbyManager().teleportToLobby(player);
int playerCount = session.getPlayerCount();
int minPlayers = config.getMinPlayers();
Bukkit.broadcast(Component.text(
"[DBD] " + player.getName() + " присоединился к очереди! (" + playerCount + "/" + minPlayers + ")",
NamedTextColor.YELLOW));
}
@Override
public boolean isComplete() {
return started;

View file

@ -1,10 +1,12 @@
package dev.loki.phase;
import dev.loki.core.GameSession;
import org.bukkit.entity.Player;
public interface Phase {
void enter(GameSession session);
void tick(GameSession session);
void exit(GameSession session);
boolean isComplete();
default void onPlayerJoined(GameSession session, Player player) {}
}

View file

@ -2,6 +2,8 @@ package dev.loki.phase;
import dev.loki.core.GameSession;
import dev.loki.role.RoleAssigner;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
public class RoleAssignPhase implements Phase {
@ -30,7 +32,7 @@ public class RoleAssignPhase implements Phase {
@Override
public void exit(GameSession session) {
Bukkit.broadcastMessage("[DBD] Роли назначены!");
Bukkit.broadcast(Component.text("[DBD] Роли назначены!", NamedTextColor.GREEN));
}
@Override

View file

@ -5,7 +5,11 @@ import dev.loki.core.GameSession;
import dev.loki.player.BasePlayer;
import dev.loki.player.Killer;
import dev.loki.player.Survivor;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import java.util.*;
@ -31,19 +35,54 @@ public class RoleAssigner {
if (players.isEmpty()) return;
List<UUID> uuids = new ArrayList<>(players.keySet());
UUID killerUuid = selectKiller(uuids);
List<Location> survivorSpawns = locations.getSurvivorSpawns();
int spawnIndex = 0;
for (UUID uuid : uuids) {
BasePlayer player;
BasePlayer newPlayer;
if (uuid.equals(killerUuid)) {
player = new Killer(uuid);
newPlayer = new Killer(uuid);
Location spawn = locations.getKillerSpawn();
player.setSpawnLocation(spawn);
if (spawn != null) newPlayer.setSpawnLocation(spawn);
} else {
player = new Survivor(uuid);
newPlayer = new Survivor(uuid);
if (!survivorSpawns.isEmpty()) {
Location spawn = survivorSpawns.get(spawnIndex % survivorSpawns.size());
newPlayer.setSpawnLocation(spawn);
spawnIndex++;
}
}
session.getEventBus().emit(new RoleAssignedEvent(uuid, player));
// Actually replace the player object in session
session.replacePlayer(uuid, newPlayer);
// Announce role and teleport
Player bukkit = Bukkit.getPlayer(uuid);
if (bukkit != null && bukkit.isOnline()) {
if (newPlayer.isKiller()) {
bukkit.sendTitle("§4§lКИЛЛЕР", "§cОхоться на выживших!", 10, 60, 20);
bukkit.sendMessage(Component.text("[DBD] Ваша роль: Киллер", NamedTextColor.RED));
} else {
bukkit.sendTitle("§a§lВЫЖИВШИЙ", "§eЧини генераторы и сбегай!", 10, 60, 20);
bukkit.sendMessage(Component.text("[DBD] Ваша роль: Выживший", NamedTextColor.GREEN));
}
// Teleport to game world spawn (or role spawn)
Location spawn = newPlayer.getSpawnLocation();
if (spawn != null) {
bukkit.teleport(spawn);
} else {
// Fallback: game world spawn
var gameWorld = session.getPlugin().getLobbyManager().getGameWorld();
if (gameWorld != null) {
bukkit.teleport(gameWorld.getSpawnLocation());
}
}
}
session.getEventBus().emit(new RoleAssignedEvent(uuid, newPlayer));
}
queue.rotate();

View file

@ -0,0 +1,29 @@
package dev.loki.util;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
/**
* Utility for converting legacy &#RRGGBB color codes to MiniMessage format.
* Usage: TextUtil.color("&#FFD700Hello") → renders as gold colored text.
*/
public final class TextUtil {
private static final MiniMessage MM = MiniMessage.miniMessage();
private TextUtil() {}
/**
* Converts &#RRGGBB color codes to MiniMessage <#RRGGBB> and deserializes.
*/
public static Component color(String text) {
return MM.deserialize(toMiniMessage(text));
}
/**
* Converts &#RRGGBB to MiniMessage <#RRGGBB> syntax without deserializing.
*/
public static String toMiniMessage(String text) {
// &#RRGGBB → <#RRGGBB>
return text.replaceAll("&#([0-9A-Fa-f]{6})", "<#$1>");
}
}

View file

@ -6,7 +6,6 @@ import org.bukkit.GameRule;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.WorldCreator;
import org.bukkit.WorldType;
import java.io.File;
@ -31,7 +30,6 @@ public class LobbyManager {
WorldCreator creator = new WorldCreator("lobby");
creator.generator(new VoidWorldGenerator());
creator.type(WorldType.FLAT);
creator.environment(World.Environment.NORMAL);
creator.generateStructures(false);
@ -143,7 +141,14 @@ public class LobbyManager {
public void teleportToLobby(org.bukkit.entity.Player player) {
if (lobbyWorld != null) {
player.teleport(lobbyWorld.getSpawnLocation());
// Use the configured lobby location from locations.yml if available,
// otherwise fall back to spawn location on the platform
org.bukkit.Location configLobby = plugin.getConfigManager().getLocations().getLobby();
if (configLobby != null && configLobby.getWorld() != null) {
player.teleport(configLobby);
} else {
player.teleport(lobbyWorld.getSpawnLocation());
}
}
}
}

View file

@ -5,22 +5,22 @@ api-version: '1.21'
commands:
dbd:
description: "Главная команда DBD плагина"
description: "Main DBD command"
usage: "/dbd <join|leave|create|forcestart|stop|reload|lobby|setlobby>"
permission: mdbd.player
dbdsetup:
description: "Команды настройки карты и объектов"
description: "Map setup commands"
usage: "/dbdsetup <wand|pos1|pos2|save|load|set|build|clear|lobby>"
permission: mdbd.admin
dbdadmin:
description: "Админ-команды для управления генераторами и объектами"
usage: "/dbdadmin <create|list|remove|clear>"
description: "Admin commands for generators and objects"
usage: "/dbdadmin <create|list|remove|clear|world>"
permission: mdbd.admin
permissions:
mdbd.admin:
description: "Полный доступ к админ-командам плагина"
description: "Full access to admin commands"
default: op
mdbd.player:
description: "Базовые команды для игроков"
description: "Basic player commands"
default: true