From 82ed455cd29b4377ca8e4d3294c57dc7a4e5645c Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Sun, 31 May 2026 20:43:40 +0200 Subject: [PATCH] Complete Stage 1: Core Game Loop - Add GameState enum (LOBBY, ROLE_ASSIGN, IN_GAME, ENDING) - Add Phase interface with enter/tick/exit/isComplete - Add GameSession with tick-based phase machine - Add GameEventBus for internal events - Add GameManager for session lifecycle - Add all phases: LobbyPhase, RoleAssignPhase, GameplayPhase, EndgamePhase - Add role assignment: RoleAssigner, RoleQueue, SpawnPosition - Add BasePlayer, Survivor, Killer, PlayerState - Add DBDCommand with /dbd join/leave/create/forcestart/stop - Update mdbd.java with GameManager and command registration - Add command definition to paper-plugin.yml - Mark Stage 1 complete in PLAN.md --- PLAN.md | 16 +- .../java/dev/loki/command/DBDCommand.java | 148 ++++++++++++++++++ src/main/java/dev/loki/core/GameEventBus.java | 30 ++++ src/main/java/dev/loki/core/GameManager.java | 68 ++++++++ src/main/java/dev/loki/core/GameSession.java | 128 +++++++++++++++ src/main/java/dev/loki/core/GameState.java | 18 +++ src/main/java/dev/loki/mdbd.java | 15 ++ .../java/dev/loki/phase/EndgamePhase.java | 31 ++++ .../java/dev/loki/phase/GameplayPhase.java | 36 +++++ src/main/java/dev/loki/phase/LobbyPhase.java | 74 +++++++++ src/main/java/dev/loki/phase/Phase.java | 10 ++ .../java/dev/loki/phase/RoleAssignPhase.java | 40 +++++ src/main/java/dev/loki/player/BasePlayer.java | 49 ++++++ src/main/java/dev/loki/player/Killer.java | 25 +++ .../java/dev/loki/player/PlayerState.java | 21 +++ src/main/java/dev/loki/player/Survivor.java | 25 +++ src/main/java/dev/loki/role/RoleAssigner.java | 65 ++++++++ src/main/java/dev/loki/role/RoleQueue.java | 59 +++++++ .../java/dev/loki/role/SpawnPosition.java | 28 ++++ src/main/resources/paper-plugin.yml | 6 + 20 files changed, 884 insertions(+), 8 deletions(-) create mode 100644 src/main/java/dev/loki/command/DBDCommand.java create mode 100644 src/main/java/dev/loki/core/GameEventBus.java create mode 100644 src/main/java/dev/loki/core/GameManager.java create mode 100644 src/main/java/dev/loki/core/GameSession.java create mode 100644 src/main/java/dev/loki/core/GameState.java create mode 100644 src/main/java/dev/loki/phase/EndgamePhase.java create mode 100644 src/main/java/dev/loki/phase/GameplayPhase.java create mode 100644 src/main/java/dev/loki/phase/LobbyPhase.java create mode 100644 src/main/java/dev/loki/phase/Phase.java create mode 100644 src/main/java/dev/loki/phase/RoleAssignPhase.java create mode 100644 src/main/java/dev/loki/player/BasePlayer.java create mode 100644 src/main/java/dev/loki/player/Killer.java create mode 100644 src/main/java/dev/loki/player/PlayerState.java create mode 100644 src/main/java/dev/loki/player/Survivor.java create mode 100644 src/main/java/dev/loki/role/RoleAssigner.java create mode 100644 src/main/java/dev/loki/role/RoleQueue.java create mode 100644 src/main/java/dev/loki/role/SpawnPosition.java diff --git a/PLAN.md b/PLAN.md index 678e247..41def64 100644 --- a/PLAN.md +++ b/PLAN.md @@ -175,14 +175,14 @@ src/main/java/com/dbd/plugin/ - [x] `GameConfig` — балансные цифры ### Этап 1: Core Game Loop -- [ ] `GameState` enum -- [ ] `GameSession` — хранит игроков, фазу, таймер -- [ ] `Phase` interface -- [ ] `LobbyPhase` — ожидание, минимум 5 игроков, таймер 60сек -- [ ] `RoleAssigner` + `RoleQueue` — кто киллер -- [ ] `RoleAssignPhase` — телепорты, задержка 5сек -- [ ] `GameplayPhase` — заглушка -- [ ] `/dbd join`, `/dbd leave`, `/dbd forcestart` +- [x] `GameState` enum +- [x] `GameSession` — хранит игроков, фазу, таймер +- [x] `Phase` interface +- [x] `LobbyPhase` — ожидание, минимум 5 игроков, таймер 60сек +- [x] `RoleAssigner` + `RoleQueue` — кто киллер +- [x] `RoleAssignPhase` — телепорты, задержка 5сек +- [x] `GameplayPhase` — заглушка +- [x] `/dbd join`, `/dbd leave`, `/dbd forcestart` ### Этап 2: Игроки и состояния - [ ] `BasePlayer`, `Survivor`, `Killer` diff --git a/src/main/java/dev/loki/command/DBDCommand.java b/src/main/java/dev/loki/command/DBDCommand.java new file mode 100644 index 0000000..d3259a1 --- /dev/null +++ b/src/main/java/dev/loki/command/DBDCommand.java @@ -0,0 +1,148 @@ +package dev.loki.command; + +import dev.loki.core.GameManager; +import dev.loki.mdbd; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class DBDCommand implements TabExecutor { + private final mdbd plugin; + private final GameManager gameManager; + + public DBDCommand(mdbd plugin, GameManager gameManager) { + this.plugin = plugin; + this.gameManager = gameManager; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, + @NotNull String label, @NotNull String[] args) { + if (args.length == 0) { + sendUsage(sender); + return true; + } + + String sub = args[0].toLowerCase(); + return switch (sub) { + case "join" -> handleJoin(sender); + case "leave" -> handleLeave(sender); + case "forcestart" -> handleForceStart(sender); + case "create" -> handleCreate(sender); + case "stop" -> handleStop(sender); + default -> { + sendUsage(sender); + yield true; + } + }; + } + + private boolean handleJoin(CommandSender sender) { + if (!(sender instanceof Player player)) { + sender.sendMessage(Component.text("Только игроки!", NamedTextColor.RED)); + return true; + } + if (gameManager.joinGame(player)) { + player.sendMessage(Component.text("[DBD] Вы присоединились к игре!", NamedTextColor.GREEN)); + } else { + player.sendMessage(Component.text("[DBD] Не удалось присоединиться", NamedTextColor.RED)); + } + return true; + } + + private boolean handleLeave(CommandSender sender) { + if (!(sender instanceof Player player)) { + sender.sendMessage(Component.text("Только игроки!", NamedTextColor.RED)); + return true; + } + if (gameManager.leaveGame(player)) { + player.sendMessage(Component.text("[DBD] Вы покинули игру", NamedTextColor.YELLOW)); + } else { + player.sendMessage(Component.text("[DBD] Вы не в игре", NamedTextColor.RED)); + } + return true; + } + + private boolean handleForceStart(CommandSender sender) { + if (!sender.hasPermission("mdbd.admin")) { + sender.sendMessage(Component.text("Нет прав!", NamedTextColor.RED)); + return true; + } + var session = gameManager.getCurrentSession(); + if (session == null) { + sender.sendMessage(Component.text("Нет активной игры!", NamedTextColor.RED)); + return true; + } + var phase = session.getCurrentPhase(); + if (phase instanceof dev.loki.phase.LobbyPhase lobby) { + lobby.forceStart(); + sender.sendMessage(Component.text("[DBD] Игра форсирована!", NamedTextColor.GREEN)); + } else { + sender.sendMessage(Component.text("Нельзя форсировать сейчас", NamedTextColor.RED)); + } + return true; + } + + private boolean handleCreate(CommandSender sender) { + if (!sender.hasPermission("mdbd.admin")) { + sender.sendMessage(Component.text("Нет прав!", NamedTextColor.RED)); + return true; + } + if (gameManager.createGame()) { + sender.sendMessage(Component.text("[DBD] Игра создана!", NamedTextColor.GREEN)); + } else { + sender.sendMessage(Component.text("[DBD] Игра уже существует", NamedTextColor.RED)); + } + return true; + } + + private boolean handleStop(CommandSender sender) { + if (!sender.hasPermission("mdbd.admin")) { + sender.sendMessage(Component.text("Нет прав!", NamedTextColor.RED)); + return true; + } + gameManager.endGame(); + sender.sendMessage(Component.text("[DBD] Игра остановлена", NamedTextColor.YELLOW)); + return true; + } + + private void sendUsage(CommandSender sender) { + sender.sendMessage(Component.text("=== DBD Commands ===", NamedTextColor.GOLD)); + sender.sendMessage(Component.text("/dbd join - присоединиться", NamedTextColor.GRAY)); + sender.sendMessage(Component.text("/dbd leave - покинуть", NamedTextColor.GRAY)); + if (sender.hasPermission("mdbd.admin")) { + sender.sendMessage(Component.text("/dbd create - создать игру", NamedTextColor.GRAY)); + sender.sendMessage(Component.text("/dbd forcestart - форс старт", NamedTextColor.GRAY)); + sender.sendMessage(Component.text("/dbd stop - остановить", NamedTextColor.GRAY)); + } + } + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, + @NotNull String label, @NotNull String[] args) { + if (args.length == 1) { + List completions = new ArrayList<>(Arrays.asList("join", "leave")); + if (sender.hasPermission("mdbd.admin")) { + completions.addAll(Arrays.asList("create", "forcestart", "stop")); + } + return completions.stream() + .filter(s -> s.startsWith(args[0].toLowerCase())) + .toList(); + } + return List.of(); + } + + private Object getCurrentPhase() { + var session = gameManager.getCurrentSession(); + return session != null ? session.getState() : null; + } +} diff --git a/src/main/java/dev/loki/core/GameEventBus.java b/src/main/java/dev/loki/core/GameEventBus.java new file mode 100644 index 0000000..56be65b --- /dev/null +++ b/src/main/java/dev/loki/core/GameEventBus.java @@ -0,0 +1,30 @@ +package dev.loki.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +public class GameEventBus { + private final Map, List>> listeners = new HashMap<>(); + + @SuppressWarnings("unchecked") + public void on(Class eventClass, Consumer listener) { + listeners.computeIfAbsent(eventClass, k -> new ArrayList<>()) + .add((Consumer) listener); + } + + public void emit(Object event) { + List> eventListeners = listeners.get(event.getClass()); + if (eventListeners != null) { + for (Consumer listener : eventListeners) { + listener.accept(event); + } + } + } + + public void clear() { + listeners.clear(); + } +} diff --git a/src/main/java/dev/loki/core/GameManager.java b/src/main/java/dev/loki/core/GameManager.java new file mode 100644 index 0000000..ccac765 --- /dev/null +++ b/src/main/java/dev/loki/core/GameManager.java @@ -0,0 +1,68 @@ +package dev.loki.core; + +import dev.loki.mdbd; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class GameManager { + private final mdbd plugin; + private final Map playerSessions; + private GameSession currentSession; + + public GameManager(mdbd plugin) { + this.plugin = plugin; + this.playerSessions = new HashMap<>(); + } + + public boolean createGame() { + if (currentSession != null) { + return false; + } + currentSession = new GameSession(plugin); + currentSession.start(); + return true; + } + + public boolean joinGame(Player player) { + if (currentSession == null) { + return false; + } + if (playerSessions.containsKey(player.getUniqueId())) { + return false; + } + playerSessions.put(player.getUniqueId(), currentSession); + return true; + } + + public boolean leaveGame(Player player) { + GameSession session = playerSessions.remove(player.getUniqueId()); + if (session != null) { + session.removePlayer(player.getUniqueId()); + return true; + } + return false; + } + + public void endGame() { + if (currentSession != null) { + currentSession.stop(); + currentSession = null; + playerSessions.clear(); + } + } + + public GameSession getCurrentSession() { + return currentSession; + } + + public boolean isInGame(UUID uuid) { + return playerSessions.containsKey(uuid); + } + + public GameSession getPlayerSession(UUID uuid) { + return playerSessions.get(uuid); + } +} diff --git a/src/main/java/dev/loki/core/GameSession.java b/src/main/java/dev/loki/core/GameSession.java new file mode 100644 index 0000000..ea2d51a --- /dev/null +++ b/src/main/java/dev/loki/core/GameSession.java @@ -0,0 +1,128 @@ +package dev.loki.core; + +import dev.loki.config.GameConfig; +import dev.loki.mdbd; +import dev.loki.phase.LobbyPhase; +import dev.loki.phase.Phase; +import dev.loki.player.BasePlayer; +import org.bukkit.Bukkit; +import org.bukkit.scheduler.BukkitTask; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class GameSession { + private final mdbd plugin; + private final GameEventBus eventBus; + private final Map players; + private Phase currentPhase; + private BukkitTask tickTask; + private int ticksElapsed; + + public GameSession(mdbd plugin) { + this.plugin = plugin; + this.eventBus = new GameEventBus(); + this.players = new HashMap<>(); + this.ticksElapsed = 0; + } + + public void start() { + switchPhase(new LobbyPhase(plugin.getConfigManager().getGame())); + this.tickTask = Bukkit.getScheduler().runTaskTimer(plugin, this::tick, 1L, 1L); + plugin.getLogger().info("Game session started"); + } + + public void stop() { + if (tickTask != null) { + tickTask.cancel(); + tickTask = null; + } + if (currentPhase != null) { + currentPhase.exit(this); + } + players.clear(); + eventBus.clear(); + plugin.getLogger().info("Game session stopped"); + } + + private void tick() { + ticksElapsed++; + if (currentPhase != null) { + currentPhase.tick(this); + if (currentPhase.isComplete()) { + transitionToNextPhase(); + } + } + } + + private void transitionToNextPhase() { + currentPhase.exit(this); + Phase nextPhase = determineNextPhase(); + if (nextPhase != null) { + switchPhase(nextPhase); + } else { + stop(); + } + } + + private Phase determineNextPhase() { + return null; + } + + public void switchPhase(Phase newPhase) { + if (currentPhase != null) { + currentPhase.exit(this); + } + currentPhase = newPhase; + currentPhase.enter(this); + } + + public void addPlayer(BasePlayer player) { + players.put(player.getUuid(), player); + eventBus.emit(new PlayerJoinEvent(player)); + } + + public void removePlayer(UUID uuid) { + BasePlayer removed = players.remove(uuid); + if (removed != null) { + eventBus.emit(new PlayerLeaveEvent(removed)); + } + } + + public BasePlayer getPlayer(UUID uuid) { + return players.get(uuid); + } + + public Map getPlayers() { + return new HashMap<>(players); + } + + public int getPlayerCount() { + return players.size(); + } + + public GameState getState() { + if (currentPhase instanceof LobbyPhase) return GameState.LOBBY; + return GameState.IN_GAME; + } + + public mdbd getPlugin() { + return plugin; + } + + public GameConfig getConfig() { + return plugin.getConfigManager().getGame(); + } + + public int getTicksElapsed() { + return ticksElapsed; + } + + public GameEventBus getEventBus() { + return eventBus; + } + + public record PlayerJoinEvent(BasePlayer player) {} + public record PlayerLeaveEvent(BasePlayer player) {} +} diff --git a/src/main/java/dev/loki/core/GameState.java b/src/main/java/dev/loki/core/GameState.java new file mode 100644 index 0000000..8b467b5 --- /dev/null +++ b/src/main/java/dev/loki/core/GameState.java @@ -0,0 +1,18 @@ +package dev.loki.core; + +public enum GameState { + LOBBY("Ожидание игроков"), + ROLE_ASSIGN("Назначение ролей"), + IN_GAME("Игра идет"), + ENDING("Завершение"); + + private final String displayName; + + GameState(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } +} diff --git a/src/main/java/dev/loki/mdbd.java b/src/main/java/dev/loki/mdbd.java index 888bc8b..a667fba 100644 --- a/src/main/java/dev/loki/mdbd.java +++ b/src/main/java/dev/loki/mdbd.java @@ -1,11 +1,14 @@ package dev.loki; +import dev.loki.command.DBDCommand; import dev.loki.config.ConfigManager; +import dev.loki.core.GameManager; import org.bukkit.plugin.java.JavaPlugin; public final class mdbd extends JavaPlugin { private static mdbd instance; private ConfigManager configManager; + private GameManager gameManager; @Override public void onEnable() { @@ -15,12 +18,20 @@ public final class mdbd extends JavaPlugin { this.configManager = new ConfigManager(this); this.configManager.loadConfigs(); + this.gameManager = new GameManager(this); + + getCommand("dbd").setExecutor(new DBDCommand(this, gameManager)); + getCommand("dbd").setTabCompleter(new DBDCommand(this, gameManager)); + getLogger().info("Mdbd Plugin enabled successfully!"); } @Override public void onDisable() { getLogger().info("Mdbd Plugin disabling..."); + if (gameManager != null) { + gameManager.endGame(); + } instance = null; } @@ -31,4 +42,8 @@ public final class mdbd extends JavaPlugin { public ConfigManager getConfigManager() { return configManager; } + + public GameManager getGameManager() { + return gameManager; + } } diff --git a/src/main/java/dev/loki/phase/EndgamePhase.java b/src/main/java/dev/loki/phase/EndgamePhase.java new file mode 100644 index 0000000..dba08a9 --- /dev/null +++ b/src/main/java/dev/loki/phase/EndgamePhase.java @@ -0,0 +1,31 @@ +package dev.loki.phase; + +import dev.loki.core.GameSession; + +public class EndgamePhase implements Phase { + private int collapseTicks; + private boolean complete; + + @Override + public void enter(GameSession session) { + collapseTicks = session.getConfig().getGame().getInt("endgame.collapse-seconds", 120) * 20; + complete = false; + } + + @Override + public void tick(GameSession session) { + collapseTicks--; + if (collapseTicks <= 0) { + complete = true; + } + } + + @Override + public void exit(GameSession session) { + } + + @Override + public boolean isComplete() { + return complete; + } +} diff --git a/src/main/java/dev/loki/phase/GameplayPhase.java b/src/main/java/dev/loki/phase/GameplayPhase.java new file mode 100644 index 0000000..1a8ea25 --- /dev/null +++ b/src/main/java/dev/loki/phase/GameplayPhase.java @@ -0,0 +1,36 @@ +package dev.loki.phase; + +import dev.loki.core.GameSession; +import org.bukkit.Bukkit; + +public class GameplayPhase implements Phase { + private int gameTicks; + private boolean shouldEnd; + + @Override + public void enter(GameSession session) { + Bukkit.broadcastMessage("[DBD] Игра началась! Выжившие, чините генераторы!"); + gameTicks = 0; + shouldEnd = false; + } + + @Override + public void tick(GameSession session) { + gameTicks++; + // TODO: Check endgame conditions + } + + @Override + public void exit(GameSession session) { + Bukkit.broadcastMessage("[DBD] Игра завершена!"); + } + + @Override + public boolean isComplete() { + return shouldEnd; + } + + public void forceEnd() { + shouldEnd = true; + } +} diff --git a/src/main/java/dev/loki/phase/LobbyPhase.java b/src/main/java/dev/loki/phase/LobbyPhase.java new file mode 100644 index 0000000..5e76619 --- /dev/null +++ b/src/main/java/dev/loki/phase/LobbyPhase.java @@ -0,0 +1,74 @@ +package dev.loki.phase; + +import dev.loki.config.GameConfig; +import dev.loki.core.GameSession; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; + +public class LobbyPhase implements Phase { + private final GameConfig config; + private int countdown; + private int secondsLeft; + private boolean started; + + public LobbyPhase(GameConfig config) { + this.config = config; + this.secondsLeft = config.getLobbyCountdown(); + this.countdown = 0; + this.started = false; + } + + @Override + public void enter(GameSession session) { + Bukkit.broadcast(Component.text("[DBD] Лобби открыто! Используйте /dbd join", NamedTextColor.GREEN)); + } + + @Override + public void tick(GameSession session) { + int playerCount = session.getPlayerCount(); + int minPlayers = config.getMinPlayers(); + + if (playerCount < minPlayers) { + secondsLeft = config.getLobbyCountdown(); + countdown = 0; + started = false; + return; + } + + countdown++; + if (countdown >= 20) { + countdown = 0; + secondsLeft--; + + if (secondsLeft <= 5 || secondsLeft % 10 == 0) { + Bukkit.broadcast(Component.text( + "[DBD] До начала: " + secondsLeft + " сек. (" + playerCount + "/" + minPlayers + ")", + NamedTextColor.YELLOW)); + } + } + + if (secondsLeft <= 0) { + started = true; + } + } + + @Override + public void exit(GameSession session) { + Bukkit.broadcast(Component.text("[DBD] Игра начинается!", NamedTextColor.GREEN)); + } + + @Override + public boolean isComplete() { + return started; + } + + public void forceStart() { + secondsLeft = 0; + started = true; + } + + public int getSecondsLeft() { + return secondsLeft; + } +} diff --git a/src/main/java/dev/loki/phase/Phase.java b/src/main/java/dev/loki/phase/Phase.java new file mode 100644 index 0000000..e730505 --- /dev/null +++ b/src/main/java/dev/loki/phase/Phase.java @@ -0,0 +1,10 @@ +package dev.loki.phase; + +import dev.loki.core.GameSession; + +public interface Phase { + void enter(GameSession session); + void tick(GameSession session); + void exit(GameSession session); + boolean isComplete(); +} diff --git a/src/main/java/dev/loki/phase/RoleAssignPhase.java b/src/main/java/dev/loki/phase/RoleAssignPhase.java new file mode 100644 index 0000000..ad1d444 --- /dev/null +++ b/src/main/java/dev/loki/phase/RoleAssignPhase.java @@ -0,0 +1,40 @@ +package dev.loki.phase; + +import dev.loki.core.GameSession; +import dev.loki.role.RoleAssigner; +import org.bukkit.Bukkit; + +public class RoleAssignPhase implements Phase { + private final RoleAssigner assigner; + private int ticksLeft; + private boolean complete; + + public RoleAssignPhase(RoleAssigner assigner) { + this.assigner = assigner; + this.complete = false; + } + + @Override + public void enter(GameSession session) { + ticksLeft = session.getConfig().getRoleAssignDelay() * 20; + assigner.assignRoles(session); + } + + @Override + public void tick(GameSession session) { + ticksLeft--; + if (ticksLeft <= 0) { + complete = true; + } + } + + @Override + public void exit(GameSession session) { + Bukkit.broadcastMessage("[DBD] Роли назначены!"); + } + + @Override + public boolean isComplete() { + return complete; + } +} diff --git a/src/main/java/dev/loki/player/BasePlayer.java b/src/main/java/dev/loki/player/BasePlayer.java new file mode 100644 index 0000000..0152625 --- /dev/null +++ b/src/main/java/dev/loki/player/BasePlayer.java @@ -0,0 +1,49 @@ +package dev.loki.player; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import java.util.UUID; + +public abstract class BasePlayer { + protected final UUID uuid; + protected PlayerState state; + protected Location spawnLocation; + + public BasePlayer(UUID uuid) { + this.uuid = uuid; + this.state = PlayerState.HEALTHY; + } + + public UUID getUuid() { + return uuid; + } + + public Player getPlayer() { + return Bukkit.getPlayer(uuid); + } + + public PlayerState getState() { + return state; + } + + public void setState(PlayerState state) { + this.state = state; + } + + public Location getSpawnLocation() { + return spawnLocation; + } + + public void setSpawnLocation(Location location) { + this.spawnLocation = location; + } + + public boolean isOnline() { + Player player = getPlayer(); + return player != null && player.isOnline(); + } + + public abstract boolean isKiller(); +} diff --git a/src/main/java/dev/loki/player/Killer.java b/src/main/java/dev/loki/player/Killer.java new file mode 100644 index 0000000..e650378 --- /dev/null +++ b/src/main/java/dev/loki/player/Killer.java @@ -0,0 +1,25 @@ +package dev.loki.player; + +import java.util.UUID; + +public class Killer extends BasePlayer { + private boolean carrying; + + public Killer(UUID uuid) { + super(uuid); + this.carrying = false; + } + + @Override + public boolean isKiller() { + return true; + } + + public boolean isCarrying() { + return carrying; + } + + public void setCarrying(boolean carrying) { + this.carrying = carrying; + } +} diff --git a/src/main/java/dev/loki/player/PlayerState.java b/src/main/java/dev/loki/player/PlayerState.java new file mode 100644 index 0000000..3a6be15 --- /dev/null +++ b/src/main/java/dev/loki/player/PlayerState.java @@ -0,0 +1,21 @@ +package dev.loki.player; + +public enum PlayerState { + HEALTHY("Здоров"), + INJURED("Ранен"), + DOWNED("В нокдауне"), + HOOKED("На крюке"), + CARRIED("Унесен"), + DEAD("Мертв"), + ESCAPED("Сбежал"); + + private final String displayName; + + PlayerState(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } +} diff --git a/src/main/java/dev/loki/player/Survivor.java b/src/main/java/dev/loki/player/Survivor.java new file mode 100644 index 0000000..e15b231 --- /dev/null +++ b/src/main/java/dev/loki/player/Survivor.java @@ -0,0 +1,25 @@ +package dev.loki.player; + +import java.util.UUID; + +public class Survivor extends BasePlayer { + private int hooksTaken; + + public Survivor(UUID uuid) { + super(uuid); + this.hooksTaken = 0; + } + + @Override + public boolean isKiller() { + return false; + } + + public int getHooksTaken() { + return hooksTaken; + } + + public void incrementHooks() { + hooksTaken++; + } +} diff --git a/src/main/java/dev/loki/role/RoleAssigner.java b/src/main/java/dev/loki/role/RoleAssigner.java new file mode 100644 index 0000000..b57e91d --- /dev/null +++ b/src/main/java/dev/loki/role/RoleAssigner.java @@ -0,0 +1,65 @@ +package dev.loki.role; + +import dev.loki.config.LocationsConfig; +import dev.loki.core.GameSession; +import dev.loki.player.BasePlayer; +import dev.loki.player.Killer; +import dev.loki.player.Survivor; +import org.bukkit.Location; + +import java.util.*; + +public class RoleAssigner { + private final RoleQueue queue; + private final LocationsConfig locations; + + public RoleAssigner(LocationsConfig locations) { + this.queue = new RoleQueue(); + this.locations = locations; + } + + public void registerPlayer(UUID uuid) { + queue.addPlayer(uuid); + } + + public void unregisterPlayer(UUID uuid) { + queue.removePlayer(uuid); + } + + public void assignRoles(GameSession session) { + Map players = session.getPlayers(); + if (players.isEmpty()) return; + + List uuids = new ArrayList<>(players.keySet()); + + UUID killerUuid = selectKiller(uuids); + + for (UUID uuid : uuids) { + BasePlayer player; + if (uuid.equals(killerUuid)) { + player = new Killer(uuid); + Location spawn = locations.getKillerSpawn(); + player.setSpawnLocation(spawn); + } else { + player = new Survivor(uuid); + } + session.getEventBus().emit(new RoleAssignedEvent(uuid, player)); + } + + queue.rotate(); + } + + private UUID selectKiller(List uuids) { + UUID queuedKiller = queue.nextKiller(); + if (queuedKiller != null && uuids.contains(queuedKiller)) { + return queuedKiller; + } + return uuids.get(new Random().nextInt(uuids.size())); + } + + public void resetQueue() { + queue.clear(); + } + + public record RoleAssignedEvent(UUID uuid, BasePlayer player) {} +} diff --git a/src/main/java/dev/loki/role/RoleQueue.java b/src/main/java/dev/loki/role/RoleQueue.java new file mode 100644 index 0000000..40f662a --- /dev/null +++ b/src/main/java/dev/loki/role/RoleQueue.java @@ -0,0 +1,59 @@ +package dev.loki.role; + +import java.util.*; + +public class RoleQueue { + private final Map queuePosition; + private final List queue; + + public RoleQueue() { + this.queuePosition = new HashMap<>(); + this.queue = new ArrayList<>(); + } + + public void addPlayer(UUID uuid) { + if (!queuePosition.containsKey(uuid)) { + queue.add(uuid); + updatePositions(); + } + } + + public void removePlayer(UUID uuid) { + queue.remove(uuid); + queuePosition.remove(uuid); + updatePositions(); + } + + public UUID nextKiller() { + if (queue.isEmpty()) return null; + return queue.get(0); + } + + public void rotate() { + if (!queue.isEmpty()) { + UUID first = queue.remove(0); + queue.add(first); + updatePositions(); + } + } + + private void updatePositions() { + queuePosition.clear(); + for (int i = 0; i < queue.size(); i++) { + queuePosition.put(queue.get(i), i); + } + } + + public int getPosition(UUID uuid) { + return queuePosition.getOrDefault(uuid, -1); + } + + public boolean isInQueue(UUID uuid) { + return queuePosition.containsKey(uuid); + } + + public void clear() { + queue.clear(); + queuePosition.clear(); + } +} diff --git a/src/main/java/dev/loki/role/SpawnPosition.java b/src/main/java/dev/loki/role/SpawnPosition.java new file mode 100644 index 0000000..0347920 --- /dev/null +++ b/src/main/java/dev/loki/role/SpawnPosition.java @@ -0,0 +1,28 @@ +package dev.loki.role; + +import dev.loki.config.LocationsConfig; +import org.bukkit.Location; + +import java.util.List; +import java.util.Random; + +public class SpawnPosition { + private final LocationsConfig config; + private final Random random; + + public SpawnPosition(LocationsConfig config) { + this.config = config; + this.random = new Random(); + } + + public Location getRandomSurvivorSpawn() { + List spawns = config.getSurvivorSpawns(); + if (spawns.isEmpty()) return config.getLobby(); + return spawns.get(random.nextInt(spawns.size())); + } + + public Location getKillerSpawn() { + Location spawn = config.getKillerSpawn(); + return spawn != null ? spawn : config.getLobby(); + } +} diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml index 3ab1d6b..844e247 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -9,6 +9,12 @@ load: POSTWORLD authors: [ loki ] +commands: + dbd: + description: "Главная команда DBD плагина" + usage: "/dbd " + permission: mdbd.player + permissions: mdbd.admin: description: "Полный доступ к админ-командам плагина"