package dev.loki.phase; import dev.loki.core.GameSession; import dev.loki.gate.GateManager; import dev.loki.hatch.Hatch; import dev.loki.hatch.HatchCondition; import dev.loki.hatch.HatchEscape; import dev.loki.player.BasePlayer; import dev.loki.player.PlayerState; import dev.loki.player.Survivor; import dev.loki.ui.Sidebar; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.title.Title; import org.bukkit.Sound; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; import java.time.Duration; import java.util.ArrayList; import java.util.List; public class EndgamePhase implements Phase { private final int openTicks; private boolean complete; private boolean gatesActivated; private boolean killerWin; // true если маньяк убил всех — пропускаем геймплей эндгейма private GateManager gateManager; private Hatch hatch; private HatchCondition hatchCondition; private HatchEscape hatchEscape; private Sidebar sidebar; public EndgamePhase() { this.openTicks = 20 * 20; } public EndgamePhase(GateManager gateManager, Hatch hatch, HatchCondition hatchCondition) { this.openTicks = 20 * 20; this.gateManager = gateManager; this.hatch = hatch; this.hatchCondition = hatchCondition; this.hatchEscape = new HatchEscape(hatch); } // ------------------------------------------------------------------------- // Phase lifecycle // ------------------------------------------------------------------------- @Override public void enter(GameSession session) { this.complete = false; this.gatesActivated = false; this.killerWin = false; // Проверяем: если все выжившие мертвы/на крюке — это killer win, // показываем результаты сразу, без геймплея эндгейма int aliveCount = 0; for (BasePlayer bp : session.getPlayers().values()) { if (!bp.isKiller() && bp.getState() != PlayerState.DEAD && bp.getState() != PlayerState.ESCAPED && bp.getState() != PlayerState.DOWNED && bp.getState() != PlayerState.HOOKED) { aliveCount++; } } if (aliveCount == 0) { this.killerWin = true; this.complete = true; // Показываем результаты и телепортируем всех в лобби announceEndgameEnd(session); teleportAllToLobby(session); return; } // Pick up GateManager from session if not injected via constructor if (gateManager == null) { gateManager = session.getGateManager(); } // Set up sidebar sidebar = new Sidebar(); for (BasePlayer bp : session.getPlayers().values()) { Player p = bp.getPlayer(); if (p != null && p.isOnline()) sidebar.setupPlayer(p); } activateEndgame(session); announceEndgameStart(session); } @Override public void tick(GameSession session) { // Update sidebar stats every 40 ticks if (session.getTicksElapsed() % 40 == 0 && sidebar != null) { for (BasePlayer bp : session.getPlayers().values()) { Player p = bp.getPlayer(); if (p != null && p.isOnline()) { sidebar.updateGameStats(p, session, session.getGeneratorManager()); } } } updateGates(session); updateHatch(session); checkWinConditions(session); } @Override public void exit(GameSession session) { if (sidebar != null) { for (BasePlayer bp : session.getPlayers().values()) { Player p = bp.getPlayer(); if (p != null && p.isOnline()) { sidebar.disableTimerObjective(p); } } sidebar.clear(); sidebar = null; } if (hatch != null) { hatch.reset(); } // exit() НЕ вызывает session.stop() — это делает transitionToNextPhase() // Телепорт и результаты уже обработаны в enter() или будут в teleportAllToLobby } /** * Телепортирует всех игроков в лобби, сбрасывает атрибуты и останавливает сессию. */ private void teleportAllToLobby(GameSession session) { List playersToTeleport = new ArrayList<>(session.getPlayers().values()); new BukkitRunnable() { @Override public void run() { for (BasePlayer bp : playersToTeleport) { Player p = bp.getPlayer(); if (p != null && p.isOnline()) { resetPlayerAttributes(p); session.getPlugin().getLobbyManager().teleportToLobby(p); } } session.stop(); } }.runTaskLater(session.getPlugin(), 60L); } /** * Сбрасывает все атрибуты игрока на значения по умолчанию. */ private void resetPlayerAttributes(Player p) { p.setGameMode(org.bukkit.GameMode.ADVENTURE); p.getInventory().clear(); p.setWalkSpeed(0.2f); p.setFlySpeed(0.1f); p.setSwimming(false); p.setSprinting(false); p.setSneaking(false); p.setHealth(20.0); p.setFoodLevel(20); p.setSaturation(5); try { var scale = p.getAttribute(org.bukkit.attribute.Attribute.SCALE); if (scale != null) scale.setBaseValue(1.0); } catch (Exception ignored) {} try { var maxHp = p.getAttribute(org.bukkit.attribute.Attribute.MAX_HEALTH); if (maxHp != null) maxHp.setBaseValue(20.0); } catch (Exception ignored) {} try { var moveSpeed = p.getAttribute(org.bukkit.attribute.Attribute.MOVEMENT_SPEED); if (moveSpeed != null) moveSpeed.setBaseValue(0.1); } catch (Exception ignored) {} } @Override public boolean isComplete() { return complete; } // ------------------------------------------------------------------------- // Private helpers // ------------------------------------------------------------------------- private void activateEndgame(GameSession session) { if (gateManager != null && !gatesActivated) { gateManager.activateEndgame(); gatesActivated = true; } checkHatchConditions(session); session.getPlugin().getLogger().info("Endgame started!"); } private void checkHatchConditions(GameSession session) { if (hatch == null || hatchCondition == null) return; int repairedGens = countRepairedGenerators(session); hatchCondition.checkAndReveal(hatch, session.getPlayers().values(), repairedGens); if (hatchCondition.isLastSurvivor(session.getPlayers().values())) { hatchCondition.checkAndOpen(hatch, session.getPlayers().values(), repairedGens); if (hatch.isOpen() && hatchEscape != null) { hatchEscape.notifyNearbyPlayers(); } } } private int countRepairedGenerators(GameSession session) { if (session.getGeneratorManager() != null) { return session.getGeneratorManager().getCompletedCount(); } return 0; } private void announceEndgameStart(GameSession session) { for (BasePlayer bp : session.getPlayers().values()) { Player p = bp.getPlayer(); if (p == null) continue; Title title = Title.title( Component.text("ЭНДГЕЙМ!").color(TextColor.color(0xCC0000)), Component.text("Ворота открыты! Бегите!").color(NamedTextColor.GREEN), Title.Times.times(Duration.ofMillis(500), Duration.ofSeconds(3), Duration.ofMillis(500)) ); p.showTitle(title); p.playSound(p.getLocation(), Sound.ENTITY_WITHER_SPAWN, 1.0f, 0.8f); } } private void updateGates(GameSession session) { if (gateManager == null) return; gateManager.updateAllGates(1.0 / openTicks); } private void updateHatch(GameSession session) { if (hatch == null || !hatch.isOpen() || hatchEscape == null) return; for (BasePlayer player : session.getPlayers().values()) { if (player.isKiller()) continue; PlayerState st = player.getState(); if (st != PlayerState.HEALTHY && st != PlayerState.INJURED) continue; if (hatchEscape.canEscape((Survivor) player)) { hatchEscape.tryEscape((Survivor) player); } } } private void checkWinConditions(GameSession session) { int survivorsAlive = 0; for (BasePlayer player : session.getPlayers().values()) { if (player.isKiller()) continue; PlayerState st = player.getState(); if (st != PlayerState.DEAD && st != PlayerState.ESCAPED && st != PlayerState.DOWNED && st != PlayerState.HOOKED) { survivorsAlive++; } } if (survivorsAlive == 0) { complete = true; } } private void announceEndgameEnd(GameSession session) { List results = new ArrayList<>(); results.add("§6§l=== РЕЗУЛЬТАТЫ ==="); int escaped = 0, died = 0; for (BasePlayer player : session.getPlayers().values()) { if (player.isKiller()) 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); } } }