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
This commit is contained in:
parent
53f3f6e9da
commit
82ed455cd2
20 changed files with 884 additions and 8 deletions
16
PLAN.md
16
PLAN.md
|
|
@ -175,14 +175,14 @@ src/main/java/com/dbd/plugin/
|
||||||
- [x] `GameConfig` — балансные цифры
|
- [x] `GameConfig` — балансные цифры
|
||||||
|
|
||||||
### Этап 1: Core Game Loop
|
### Этап 1: Core Game Loop
|
||||||
- [ ] `GameState` enum
|
- [x] `GameState` enum
|
||||||
- [ ] `GameSession` — хранит игроков, фазу, таймер
|
- [x] `GameSession` — хранит игроков, фазу, таймер
|
||||||
- [ ] `Phase` interface
|
- [x] `Phase` interface
|
||||||
- [ ] `LobbyPhase` — ожидание, минимум 5 игроков, таймер 60сек
|
- [x] `LobbyPhase` — ожидание, минимум 5 игроков, таймер 60сек
|
||||||
- [ ] `RoleAssigner` + `RoleQueue` — кто киллер
|
- [x] `RoleAssigner` + `RoleQueue` — кто киллер
|
||||||
- [ ] `RoleAssignPhase` — телепорты, задержка 5сек
|
- [x] `RoleAssignPhase` — телепорты, задержка 5сек
|
||||||
- [ ] `GameplayPhase` — заглушка
|
- [x] `GameplayPhase` — заглушка
|
||||||
- [ ] `/dbd join`, `/dbd leave`, `/dbd forcestart`
|
- [x] `/dbd join`, `/dbd leave`, `/dbd forcestart`
|
||||||
|
|
||||||
### Этап 2: Игроки и состояния
|
### Этап 2: Игроки и состояния
|
||||||
- [ ] `BasePlayer`, `Survivor`, `Killer`
|
- [ ] `BasePlayer`, `Survivor`, `Killer`
|
||||||
|
|
|
||||||
148
src/main/java/dev/loki/command/DBDCommand.java
Normal file
148
src/main/java/dev/loki/command/DBDCommand.java
Normal file
|
|
@ -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<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"));
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/main/java/dev/loki/core/GameEventBus.java
Normal file
30
src/main/java/dev/loki/core/GameEventBus.java
Normal file
|
|
@ -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<Class<?>, List<Consumer<Object>>> listeners = new HashMap<>();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> void on(Class<T> eventClass, Consumer<T> listener) {
|
||||||
|
listeners.computeIfAbsent(eventClass, k -> new ArrayList<>())
|
||||||
|
.add((Consumer<Object>) listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void emit(Object event) {
|
||||||
|
List<Consumer<Object>> eventListeners = listeners.get(event.getClass());
|
||||||
|
if (eventListeners != null) {
|
||||||
|
for (Consumer<Object> listener : eventListeners) {
|
||||||
|
listener.accept(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
listeners.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
68
src/main/java/dev/loki/core/GameManager.java
Normal file
68
src/main/java/dev/loki/core/GameManager.java
Normal file
|
|
@ -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<UUID, GameSession> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
128
src/main/java/dev/loki/core/GameSession.java
Normal file
128
src/main/java/dev/loki/core/GameSession.java
Normal file
|
|
@ -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<UUID, BasePlayer> 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<UUID, BasePlayer> 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) {}
|
||||||
|
}
|
||||||
18
src/main/java/dev/loki/core/GameState.java
Normal file
18
src/main/java/dev/loki/core/GameState.java
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
package dev.loki;
|
package dev.loki;
|
||||||
|
|
||||||
|
import dev.loki.command.DBDCommand;
|
||||||
import dev.loki.config.ConfigManager;
|
import dev.loki.config.ConfigManager;
|
||||||
|
import dev.loki.core.GameManager;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
public final class mdbd extends JavaPlugin {
|
public final class mdbd extends JavaPlugin {
|
||||||
private static mdbd instance;
|
private static mdbd instance;
|
||||||
private ConfigManager configManager;
|
private ConfigManager configManager;
|
||||||
|
private GameManager gameManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
|
|
@ -15,12 +18,20 @@ public final class mdbd extends JavaPlugin {
|
||||||
this.configManager = new ConfigManager(this);
|
this.configManager = new ConfigManager(this);
|
||||||
this.configManager.loadConfigs();
|
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!");
|
getLogger().info("Mdbd Plugin enabled successfully!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
getLogger().info("Mdbd Plugin disabling...");
|
getLogger().info("Mdbd Plugin disabling...");
|
||||||
|
if (gameManager != null) {
|
||||||
|
gameManager.endGame();
|
||||||
|
}
|
||||||
instance = null;
|
instance = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,4 +42,8 @@ public final class mdbd extends JavaPlugin {
|
||||||
public ConfigManager getConfigManager() {
|
public ConfigManager getConfigManager() {
|
||||||
return configManager;
|
return configManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GameManager getGameManager() {
|
||||||
|
return gameManager;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
31
src/main/java/dev/loki/phase/EndgamePhase.java
Normal file
31
src/main/java/dev/loki/phase/EndgamePhase.java
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/main/java/dev/loki/phase/GameplayPhase.java
Normal file
36
src/main/java/dev/loki/phase/GameplayPhase.java
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/main/java/dev/loki/phase/LobbyPhase.java
Normal file
74
src/main/java/dev/loki/phase/LobbyPhase.java
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/main/java/dev/loki/phase/Phase.java
Normal file
10
src/main/java/dev/loki/phase/Phase.java
Normal file
|
|
@ -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();
|
||||||
|
}
|
||||||
40
src/main/java/dev/loki/phase/RoleAssignPhase.java
Normal file
40
src/main/java/dev/loki/phase/RoleAssignPhase.java
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/main/java/dev/loki/player/BasePlayer.java
Normal file
49
src/main/java/dev/loki/player/BasePlayer.java
Normal file
|
|
@ -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();
|
||||||
|
}
|
||||||
25
src/main/java/dev/loki/player/Killer.java
Normal file
25
src/main/java/dev/loki/player/Killer.java
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/main/java/dev/loki/player/PlayerState.java
Normal file
21
src/main/java/dev/loki/player/PlayerState.java
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/main/java/dev/loki/player/Survivor.java
Normal file
25
src/main/java/dev/loki/player/Survivor.java
Normal file
|
|
@ -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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
65
src/main/java/dev/loki/role/RoleAssigner.java
Normal file
65
src/main/java/dev/loki/role/RoleAssigner.java
Normal file
|
|
@ -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<UUID, BasePlayer> players = session.getPlayers();
|
||||||
|
if (players.isEmpty()) return;
|
||||||
|
|
||||||
|
List<UUID> 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<UUID> 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) {}
|
||||||
|
}
|
||||||
59
src/main/java/dev/loki/role/RoleQueue.java
Normal file
59
src/main/java/dev/loki/role/RoleQueue.java
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
package dev.loki.role;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class RoleQueue {
|
||||||
|
private final Map<UUID, Integer> queuePosition;
|
||||||
|
private final List<UUID> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/main/java/dev/loki/role/SpawnPosition.java
Normal file
28
src/main/java/dev/loki/role/SpawnPosition.java
Normal file
|
|
@ -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<Location> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,12 @@ load: POSTWORLD
|
||||||
|
|
||||||
authors: [ loki ]
|
authors: [ loki ]
|
||||||
|
|
||||||
|
commands:
|
||||||
|
dbd:
|
||||||
|
description: "Главная команда DBD плагина"
|
||||||
|
usage: "/dbd <join|leave|create|forcestart|stop>"
|
||||||
|
permission: mdbd.player
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
mdbd.admin:
|
mdbd.admin:
|
||||||
description: "Полный доступ к админ-командам плагина"
|
description: "Полный доступ к админ-командам плагина"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue