285 lines
10 KiB
Java
285 lines
10 KiB
Java
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<BasePlayer> 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<String> 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);
|
||
}
|
||
}
|
||
}
|