package dev.loki.phase; import dev.loki.chest.ChestManager; import dev.loki.chest.ChestOpening; import dev.loki.config.GameConfig; import dev.loki.core.GameSession; import dev.loki.gate.ExitGate; import dev.loki.gate.GateManager; import dev.loki.gate.GateOpening; import dev.loki.hook.HookManager; import dev.loki.hook.HookRecovery; import dev.loki.hook.HookRescue; import dev.loki.hook.HookSabotage; import dev.loki.hook.HookSacrifice; import dev.loki.hook.HookStruggle; import dev.loki.item.ChestLoot; import dev.loki.killer.GeneratorKick; import dev.loki.mdbd; import dev.loki.minigames.GeneratorMinigame; import dev.loki.minigames.generator.GuiRouletteMinigame; import dev.loki.objective.GeneratorManager; import dev.loki.player.BasePlayer; import dev.loki.player.PlayerState; import dev.loki.player.Survivor; import dev.loki.ui.ActionBar; import dev.loki.ui.TitleUI; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Sound; import org.bukkit.block.Block; import org.bukkit.entity.Player; import java.util.ArrayList; import java.util.List; public class GameplayPhase implements Phase { private final GeneratorManager generatorManager; private int tickCounter; private boolean shouldEnd; private boolean allGensDone; // Sub-handlers private GameplayHookHandler hookHandler; private GameplayWinConditions winConditions; private GameplayDamageSystem damageSystem; private GameplayUI ui; private GameplayInteraction interaction; // Managers private HookManager hookManager; private GateManager gateManager; private GateOpening gateOpening; private HookRescue hookRescue; private HookSabotage hookSabotage; private HookStruggle hookStruggle; private TitleUI titleUI; private ChestManager chestManager; private ChestOpening chestOpening; private GeneratorKick generatorKick; private dev.loki.objective.GeneratorRepair generatorRepair; private dev.loki.objective.GeneratorHologram generatorHologram; private dev.loki.hook.HookHologram hookHologram; private dev.loki.chest.ChestHologram chestHologram; public GameplayPhase(GeneratorManager generatorManager) { this.generatorManager = generatorManager; } // ========================================================================= // Phase lifecycle // ========================================================================= @Override public void enter(GameSession session) { mdbd plugin = session.getPlugin(); GameConfig cfg = session.getConfig(); tickCounter = 0; shouldEnd = false; allGensDone = false; winConditions = new GameplayWinConditions(); initManagers(session, plugin, cfg); initSubHandlers(session, plugin, cfg); initListeners(session, plugin); // Устанавливаем ночь на всех мирах for (var world : Bukkit.getWorlds()) { world.setGameRule(org.bukkit.GameRule.DO_DAYLIGHT_CYCLE, false); world.setTime(18000); // Полночь world.setStorm(false); } Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.start")); } @Override public void tick(GameSession session) { tickCounter++; generatorManager.tick(); if (generatorRepair != null) generatorRepair.tick(); if (interaction != null) interaction.tickSurvivors(); hookHandler.tick(); boolean timeExpired = ui.tickMatchTimer(session); // Particle effects around generators (every 10 ticks) if (tickCounter % 10 == 0 && generatorManager != null) { for (var gen : generatorManager.getAllGenerators()) { if (gen.isCompleted()) continue; Location gloc = gen.getLocation().clone().add(0.5, 0.5, 0.5); gloc.getWorld().spawnParticle(org.bukkit.Particle.ELECTRIC_SPARK, gloc, 2, 0.8, 0.5, 0.8, 0.01); gloc.getWorld().spawnParticle(org.bukkit.Particle.END_ROD, gloc, 1, 0.6, 0.3, 0.6, 0.01); } } if (tickCounter % 20 == 0) { checkGeneratorProgress(session); winConditions.checkWinConditions(session); if (winConditions.isShouldEnd() || timeExpired) { shouldEnd = true; } ui.updateSidebar(session, generatorManager); } if (timeExpired) { showGameResults(session); teleportAllToLobby(session); session.stop(); return; } } @Override public void exit(GameSession session) { clearListeners(); clearManagers(session); ui.clear(session); ui.resetPlayers(session); damageSystem.clear(); damageSystem.clearManagers(); Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.end")); } @Override public boolean isComplete() { return shouldEnd; } /** * Returns the reason the gameplay phase ended, or null if not yet ended. */ public String getWinReason() { return winConditions != null ? winConditions.getWinReason() : null; } // ========================================================================= // Init helpers // ========================================================================= private void initManagers(GameSession session, mdbd plugin, GameConfig cfg) { hookManager = new HookManager(plugin); hookManager.loadHooks(plugin.getConfigManager().getLocations()); hookManager.setMaxHooks(cfg.getMaxHooks()); hookManager.startTicking(); gateManager = new GateManager(plugin.getConfigManager().getLocations()); gateOpening = new GateOpening(gateManager, cfg); ActionBar ab = new ActionBar(); HookRecovery hookRecovery = new HookRecovery(plugin, cfg); titleUI = new TitleUI(plugin); String genMinigameType = cfg.getGeneratorMinigame(); GeneratorMinigame genMinigame; if ("gui".equalsIgnoreCase(genMinigameType)) { genMinigame = new GuiRouletteMinigame(plugin); } else if ("text".equalsIgnoreCase(genMinigameType)) { genMinigame = new dev.loki.minigames.generator.TextSkillCheckMinigame(plugin); } else { plugin.getLogger().warning("Unknown generator minigame: " + genMinigameType + ", using gui"); genMinigame = new GuiRouletteMinigame(plugin); } hookStruggle = new HookStruggle(plugin, titleUI, cfg); HookSacrifice hookSacrifice = new HookSacrifice(plugin, null); hookSabotage = new HookSabotage(plugin, cfg, hookManager); hookRescue = new HookRescue(plugin, ab, cfg); hookHandler = new GameplayHookHandler(hookManager, hookSacrifice, hookStruggle, hookRecovery); hookRescue.setOnRescueComplete((rescuer, victim) -> hookHandler.onRescued(victim)); chestManager = new ChestManager(plugin, plugin.getConfigManager().getLocations(), cfg); chestManager.startLootResetTimer(); chestOpening = new ChestOpening(plugin); chestOpening.setChestLoot(new ChestLoot(cfg)); var gameWorld = plugin.getLobbyManager().getGameWorld(); if (gameWorld != null) { for (var h : hookManager.getAllHooks()) h.getLocation().setWorld(gameWorld); for (var c : chestManager.getAllChests()) c.getLocation().setWorld(gameWorld); for (var g : generatorManager.getAllGenerators()) g.getLocation().setWorld(gameWorld); } spawnGameBlocks(plugin, cfg); generatorKick = new GeneratorKick(plugin, cfg, generatorManager); generatorRepair = new dev.loki.objective.GeneratorRepair(plugin, generatorManager, genMinigame); // Голограммы ВСЕГДА включены generatorHologram = new dev.loki.objective.GeneratorHologram(plugin, generatorManager); generatorHologram.start(); plugin.getLogger().info("[GameplayPhase] Generator holograms started"); hookHologram = new dev.loki.hook.HookHologram(plugin, hookManager); hookHologram.start(); plugin.getLogger().info("[GameplayPhase] Hook holograms: " + hookManager.getAllHooks().size()); chestHologram = new dev.loki.chest.ChestHologram(plugin, chestManager); chestHologram.start(); plugin.getLogger().info("[GameplayPhase] Chest holograms: " + chestManager.getAllChests().size()); session.setHookManager(hookManager); session.setGateManager(gateManager); } private void initSubHandlers(GameSession session, mdbd plugin, GameConfig cfg) { damageSystem = new GameplayDamageSystem(); damageSystem.init(session, plugin, cfg); ui = new GameplayUI(); ui.init(session, plugin, cfg); ui.setWinConditions(winConditions); interaction = new GameplayInteraction(); interaction.setMoveListener(plugin.getMoveListener()); interaction.setInteractListener(plugin.getInteractListener()); // ВАЖНО: переустановить generatorManager, поскольку он может быть null после exit() plugin.getInteractListener().setGeneratorManager(generatorManager); interaction.setManagers(gateManager, hookManager, hookRescue, hookSabotage, generatorKick, damageSystem, chestManager, chestOpening, gateOpening, generatorRepair); } private void initListeners(GameSession session, mdbd plugin) { for (BasePlayer bp : session.getPlayers().values()) { Player p = bp.getPlayer(); if (p != null && p.isOnline()) p.getInventory().clear(); } GeneratorMinigame gm = generatorRepair.getMinigame(); if (gm instanceof GuiRouletteMinigame gui) { dev.loki.listener.SkillCheckListener listener = new dev.loki.listener.SkillCheckListener(plugin, gui); listener.register(); } interaction.wire(session); } // ========================================================================= // Tick helpers // ========================================================================= private void checkGeneratorProgress(GameSession session) { if (allGensDone) return; if (generatorManager.areAllRequiredCompleted()) { allGensDone = true; interaction.setAllGensDone(true); gateManager.activateEndgame(); Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("generator.repair.all-done")); for (BasePlayer bp : session.getPlayers().values()) { Player p = bp.getPlayer(); if (p != null) p.playSound(p.getLocation(), Sound.ENTITY_FIREWORK_ROCKET_BLAST, 1.0f, 1.0f); } } } private void spawnGameBlocks(mdbd plugin, GameConfig cfg) { // Спавн блоков на местах генераторов Material genMat = Material.matchMaterial(cfg.getGeneratorBlock()); if (genMat == null) genMat = Material.BLAST_FURNACE; for (var gen : generatorManager.getAllGenerators()) { Location loc = gen.getLocation(); if (loc.getWorld() == null) continue; Block b = loc.getBlock(); if (b.getType() != genMat) b.setType(genMat, false); } // Спавн блоков на местах сундуков + лут (resetAllChests уже устанавливает лут) Material chestMat = Material.matchMaterial(cfg.getChestBlock()); if (chestMat == null) chestMat = Material.CHEST; chestManager.resetAllChests(); // Убедимся, что у каждого сундука есть лут (не null и не AIR) for (dev.loki.chest.Chest chest : chestManager.getAllChests()) { Location loc = chest.getLocation(); if (loc.getWorld() == null) continue; Block block = loc.getBlock(); if (block.getType() != chestMat) block.setType(chestMat, false); if (!chest.hasLoot()) { ChestLoot chestLoot = new ChestLoot(cfg); chest.setLoot(chestLoot.rollLoot()); } } plugin.getLogger().info("[GameplayPhase] Spawned " + chestManager.getTotalChests() + " chests with loot"); // Спавн блоков на местах крюков Material hookMat = Material.matchMaterial(cfg.getHookBlock()); if (hookMat == null) hookMat = Material.IRON_BARS; for (var hook : hookManager.getAllHooks()) { Location loc = hook.getLocation(); if (loc.getWorld() == null) continue; Block b = loc.getBlock(); if (b.getType() != hookMat) b.setType(hookMat, false); } plugin.getLogger().info("[GameplayPhase] Spawned blocks for " + hookManager.getAllHooks().size() + " hooks"); // Спавн блоков на местах ворот (Hopper — для открытия через ПКМ) for (ExitGate gate : gateManager.getAllGates()) { gate.spawnGateBlock(); } plugin.getLogger().info("[GameplayPhase] Spawned blocks for " + gateManager.getAllGates().size() + " gates"); } // ========================================================================= // Cleanup // ========================================================================= private void showGameResults(GameSession session) { List results = new ArrayList<>(); results.add("§6§l=== РЕЗУЛЬТАТЫ ==="); int escaped = 0, died = 0; for (BasePlayer player : session.getPlayers().values()) { if (player.isKiller()) { Player p = player.getPlayer(); String name = p != null ? p.getName() : "Unknown"; results.add("§c☠ " + name + " — Маньяк"); continue; } Player p = player.getPlayer(); String name = p != null ? p.getName() : "Unknown"; if (player.getState() == PlayerState.ESCAPED) { results.add("§a✔ " + name + " — Сбежал"); escaped++; } else { results.add("§c✘ " + name + " — Погиб"); died++; } } results.add("§7Выживших: §a" + escaped + " §7/ Погибших: §c" + died); String msg = String.join("\n", results); for (BasePlayer player : session.getPlayers().values()) { Player p = player.getPlayer(); if (p != null) p.sendMessage(msg); } } private void teleportAllToLobby(GameSession session) { for (BasePlayer bp : session.getPlayers().values()) { Player p = bp.getPlayer(); if (p != null && p.isOnline()) { p.setGameMode(org.bukkit.GameMode.ADVENTURE); p.getInventory().clear(); p.setWalkSpeed(0.2f); p.setFlySpeed(0.1f); p.setHealth(20.0); p.setFoodLevel(20); session.getPlugin().getLobbyManager().teleportToLobby(p); } } } private void clearListeners() { if (interaction != null) interaction.clear(); } private void clearManagers(GameSession session) { if (hookHandler != null) hookHandler.clear(); if (hookRescue != null) hookRescue.clear(); if (hookSabotage != null) hookSabotage.shutdown(); if (hookManager != null) hookManager.clear(); if (chestOpening != null) chestOpening.cancelAll(); if (chestManager != null) chestManager.resetAllChests(); if (generatorKick != null) generatorKick.shutdown(); if (generatorHologram != null) generatorHologram.stop(); if (hookHologram != null) hookHologram.stop(); if (titleUI != null) titleUI.clearSkillChecks(); if (hookStruggle != null) hookStruggle.clear(); if (chestManager != null) chestManager.stopLootResetTimer(); if (chestHologram != null) chestHologram.stop(); // Clean up gate blocks if (gateManager != null) { for (ExitGate gate : gateManager.getAllGates()) { gate.removeGateBlock(); } gateManager.resetAll(); } session.getPlugin().getInteractListener().setGeneratorManager(null); } }