This commit is contained in:
loki5512344 2026-06-10 23:00:12 +02:00
parent 89dc9eb00d
commit 957ab4a5a2
149 changed files with 5379 additions and 3263 deletions

View file

@ -4,54 +4,34 @@ import dev.loki.chest.ChestManager;
import dev.loki.chest.ChestOpening;
import dev.loki.config.GameConfig;
import dev.loki.core.GameSession;
import dev.loki.damage.AttackListener;
import dev.loki.damage.DamageHandler;
import dev.loki.gate.ExitGate;
import dev.loki.gate.GateManager;
import dev.loki.hook.Hook;
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.HookStage;
import dev.loki.hook.HookStruggle;
import dev.loki.item.ChestLoot;
import dev.loki.killer.GeneratorKick;
import dev.loki.killer.KillerController;
import dev.loki.listener.PlayerInteractListener;
import dev.loki.listener.PlayerJumpListener;
import dev.loki.listener.PlayerMoveListener;
import dev.loki.mdbd;
import dev.loki.objective.GeneratorManager;
import dev.loki.pallet.Pallet;
import dev.loki.pallet.PalletBreak;
import dev.loki.pallet.PalletDrop;
import dev.loki.pallet.PalletManager;
import dev.loki.pallet.PalletStun;
import dev.loki.player.BasePlayer;
import dev.loki.player.Killer;
import dev.loki.player.PlayerManager;
import dev.loki.player.PlayerState;
import dev.loki.player.Survivor;
import dev.loki.state.StateMachine;
import dev.loki.ui.ActionBar;
import dev.loki.ui.BossBarManager;
import dev.loki.ui.Sidebar;
import dev.loki.ui.TitleUI;
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.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 org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageEvent;
import java.time.Duration;
import java.util.Collection;
import java.util.stream.Collectors;
import java.util.ArrayList;
import java.util.List;
public class GameplayPhase implements Phase {
@ -60,221 +40,98 @@ public class GameplayPhase implements Phase {
private boolean shouldEnd;
private boolean allGensDone;
// Managers initialised in enter()
private PalletManager palletManager;
// 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 HookSacrifice hookSacrifice;
private PalletDrop palletDrop;
private PalletBreak palletBreak;
@SuppressWarnings("unused")
private PalletStun palletStun;
private HookSabotage hookSabotage;
private HookStruggle hookStruggle;
private TitleUI titleUI;
private ChestManager chestManager;
private ChestOpening chestOpening;
private GeneratorKick generatorKick;
private HookSabotage hookSabotage;
private BossBarManager bossBarManager;
private Sidebar sidebar;
// Damage system
private PlayerManager playerManager;
private StateMachine stateMachine;
private DamageHandler damageHandler;
private KillerController killerController;
private AttackListener attackListener;
private Listener vanillaDamageBlocker;
private dev.loki.item.ItemUseListener itemUseListener;
// Listeners
private PlayerMoveListener moveListener;
private PlayerInteractListener interactListener;
private PlayerJumpListener jumpListener;
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;
shouldEnd = false;
allGensDone = false;
winConditions = new GameplayWinConditions();
// --- Managers ---
palletManager = new PalletManager(plugin.getConfigManager().getLocations());
hookManager = new HookManager(plugin);
hookManager.loadHooks(plugin.getConfigManager().getLocations());
hookManager.setMaxHooks(cfg.getMaxHooks());
gateManager = new GateManager(plugin.getConfigManager().getLocations());
ActionBar ab = new ActionBar();
hookRescue = new HookRescue(plugin, ab, cfg);
hookSacrifice = new HookSacrifice(plugin, null);
palletDrop = new PalletDrop(palletManager);
palletBreak = new PalletBreak(cfg);
palletStun = new PalletStun(cfg);
chestManager = new ChestManager(plugin, plugin.getConfigManager().getLocations());
chestOpening = new ChestOpening(plugin);
initManagers(session, plugin, cfg);
initSubHandlers(session, plugin, cfg);
initListeners(session, plugin);
// GeneratorKick for the killer
generatorKick = new GeneratorKick(plugin, cfg, generatorManager);
// HookSabotage for survivors
hookSabotage = new HookSabotage(plugin, cfg, hookManager);
session.setPalletManager(palletManager);
session.setHookManager(hookManager);
session.setGateManager(gateManager);
// --- Damage system ---
playerManager = new PlayerManager();
for (BasePlayer bp : session.getPlayers().values()) {
playerManager.register(bp);
}
stateMachine = new StateMachine();
damageHandler = new DamageHandler(stateMachine);
damageHandler.setOnSurvivorInjured(survivor -> {
Player p = survivor.getPlayer();
if (p != null && p.isOnline()) {
p.showTitle(Title.title(
Component.text("РАНЕН!").color(TextColor.color(0xFF8C00)),
Component.text("Найдите аптечку или союзника").color(NamedTextColor.YELLOW)
));
p.playSound(p.getLocation(), Sound.ENTITY_PLAYER_HURT, 1.0f, 0.8f);
p.setHealth(20.0);
}
});
damageHandler.setOnSurvivorDowned(survivor -> {
Player p = survivor.getPlayer();
if (p != null && p.isOnline()) {
p.showTitle(Title.title(
Component.text("НОКДАУН!").color(TextColor.color(0xFF2222)),
Component.text("Ждите помощи...").color(NamedTextColor.RED)
));
p.playSound(p.getLocation(), Sound.ENTITY_PLAYER_HURT, 1.0f, 0.5f);
p.setHealth(20.0);
}
});
// KillerController — AttackListener зарегистрирован ТОЛЬКО здесь
killerController = new KillerController(plugin, playerManager, damageHandler, cfg);
attackListener = new AttackListener(playerManager, damageHandler,
(killer, survivor) -> killerController.tryAttack(killer, survivor));
attackListener.register(plugin);
// Block vanilla damage
vanillaDamageBlocker = new VanillaDamageBlocker(session);
Bukkit.getPluginManager().registerEvents(vanillaDamageBlocker, plugin);
// KillerController.initialize() does NOT re-register AttackListener
killerController.initialize();
// --- BossBar ---
Collection<Survivor> survivors = session.getPlayers().values().stream()
.filter(bp -> !bp.isKiller())
.map(bp -> (Survivor) bp)
.collect(Collectors.toList());
Killer killer = (Killer) session.getPlayers().values().stream()
.filter(BasePlayer::isKiller)
.findFirst().orElse(null);
bossBarManager = new BossBarManager(plugin, cfg);
if (killer != null) bossBarManager.setTargets(killer, survivors);
if (cfg.isBossBarEnabled()) bossBarManager.start();
// --- Sidebar ---
sidebar = new Sidebar();
for (BasePlayer bp : session.getPlayers().values()) {
Player p = bp.getPlayer();
if (p != null && p.isOnline()) sidebar.setupPlayer(p);
// Устанавливаем ночь на всех мирах
for (var world : Bukkit.getWorlds()) {
world.setGameRule(org.bukkit.GameRule.DO_DAYLIGHT_CYCLE, false);
world.setTime(18000); // Полночь
world.setStorm(false);
}
// --- Jump listener for carry escape ---
jumpListener = new PlayerJumpListener(plugin);
jumpListener.setOnJump(player -> {
BasePlayer bp = session.getPlayer(player.getUniqueId());
if (bp == null || bp.isKiller()) return;
killerController.getCarry().onSurvivorJump(player.getUniqueId());
});
jumpListener.register();
// --- Items ---
itemUseListener = new dev.loki.item.ItemUseListener(plugin, session);
itemUseListener.register();
giveStartingItems(session);
// Apply player scales via Attribute API
applyPlayerScales(session, cfg);
// --- Wire listeners ---
moveListener = plugin.getMoveListener();
interactListener = plugin.getInteractListener();
wireMoveListener(session);
wireInteractListener(session);
Bukkit.broadcast(Component.text("[DBD] Игра началась! Выжившие, чините генераторы!",
NamedTextColor.GREEN));
Bukkit.broadcast(Component.text(
"[DBD] Игра началась! Выжившие, чините генераторы!", NamedTextColor.GREEN));
}
@Override
public void tick(GameSession session) {
tickCounter++;
generatorManager.tick();
tickHooks(session);
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);
checkWinConditions(session);
updateSidebar(session);
winConditions.checkWinConditions(session);
if (winConditions.isShouldEnd() || timeExpired) shouldEnd = true;
ui.updateSidebar(session, generatorManager);
}
}
@Override
public void exit(GameSession session) {
if (moveListener != null) {
moveListener.setOnSurvivorMove(null);
}
if (interactListener != null) {
interactListener.setOnPalletInteract(null);
interactListener.setOnRescueAttempt(null);
interactListener.setOnChestInteract(null);
interactListener.setOnAttack(null);
interactListener.setOnKillerSneak(null);
}
if (jumpListener != null) {
jumpListener.unregister();
jumpListener = null;
}
if (hookManager != null) hookManager.clear();
if (hookRescue != null) hookRescue.clear();
if (chestOpening != null) chestOpening.cancelAll();
if (attackListener != null) attackListener.unregister();
if (vanillaDamageBlocker != null) {
HandlerList.unregisterAll(vanillaDamageBlocker);
vanillaDamageBlocker = null;
}
if (killerController != null) killerController.shutdown();
if (generatorKick != null) generatorKick.shutdown();
if (hookSabotage != null) hookSabotage.shutdown();
if (bossBarManager != null) bossBarManager.stop();
if (sidebar != null) { sidebar.clear(); sidebar = null; }
if (itemUseListener != null) itemUseListener.unregister();
if (playerManager != null) playerManager.clear();
// Restore modes and HP
for (BasePlayer bp : session.getPlayers().values()) {
Player p = bp.getPlayer();
if (p != null && p.isOnline()) {
p.setHealth(20.0);
p.setGameMode(GameMode.ADVENTURE);
}
}
clearListeners();
clearManagers(session);
ui.clear(session);
ui.resetPlayers(session);
damageSystem.clear();
damageSystem.clearManagers();
Bukkit.broadcast(Component.text("[DBD] Игровая фаза завершена!", NamedTextColor.GOLD));
}
@ -284,336 +141,207 @@ public class GameplayPhase implements Phase {
return shouldEnd;
}
// -------------------------------------------------------------------------
// Attribute-based player scaling
// -------------------------------------------------------------------------
private void applyPlayerScales(GameSession session, GameConfig cfg) {
for (BasePlayer bp : session.getPlayers().values()) {
Player p = bp.getPlayer();
if (p == null || !p.isOnline()) continue;
double scale = bp.isKiller() ? cfg.getKillerScale() : cfg.getSurvivorScale();
try {
var attr = p.getAttribute(org.bukkit.attribute.Attribute.GENERIC_SCALE);
if (attr != null) attr.setBaseValue(scale);
} catch (Exception ignored) {
// Attribute may not exist on older Paper builds — skip silently
}
}
}
private void resetPlayerScales(GameSession session) {
for (BasePlayer bp : session.getPlayers().values()) {
Player p = bp.getPlayer();
if (p == null || !p.isOnline()) continue;
try {
var attr = p.getAttribute(org.bukkit.attribute.Attribute.GENERIC_SCALE);
if (attr != null) attr.setBaseValue(1.0);
} catch (Exception ignored) {}
}
}
// -------------------------------------------------------------------------
// Sidebar update
// -------------------------------------------------------------------------
private void updateSidebar(GameSession session) {
if (sidebar == null) return;
for (BasePlayer bp : session.getPlayers().values()) {
Player p = bp.getPlayer();
if (p != null && p.isOnline()) {
sidebar.updateGameStats(p, session, generatorManager);
}
}
}
// -------------------------------------------------------------------------
// Starting items
// -------------------------------------------------------------------------
private void giveStartingItems(GameSession session) {
var loot = new dev.loki.item.ChestLoot();
for (BasePlayer bp : session.getPlayers().values()) {
if (bp.isKiller()) continue;
Player p = bp.getPlayer();
if (p == null || !p.isOnline()) continue;
p.getInventory().clear();
p.getInventory().setItem(0, loot.rollLoot());
p.sendMessage(Component.text("[DBD] Вы получили стартовый предмет!", NamedTextColor.YELLOW));
}
}
// -------------------------------------------------------------------------
// Hook ticking
// -------------------------------------------------------------------------
private void tickHooks(GameSession session) {
for (Hook hook : hookManager.getAllHooks()) {
if (!hook.isOccupied()) continue;
hook.tick();
Survivor victim = hook.getHookedSurvivor();
if (victim == null) continue;
Player vp = victim.getPlayer();
if (vp != null && vp.isOnline()) {
double pct = hook.getStageProgressPercent();
String label = hook.getCurrentStage() == HookStage.STAGE_2
? "§cStage 2 — вырывайтесь!" : "§eНа крюке";
vp.sendActionBar(Component.text(label + " " + (int) pct + "%"));
}
if (hook.getCurrentStage() == HookStage.SACRIFICE) {
hookSacrifice.sacrificeIfExpired(victim, hook);
// Перевод в SPECTATOR после смерти обрабатывается через setState
}
}
}
// -------------------------------------------------------------------------
// Spectator mode on death / after sacrifice
// -------------------------------------------------------------------------
/**
* Converts a dead survivor to SPECTATOR so they can watch the game.
* Returns the reason the gameplay phase ended, or null if not yet ended.
*/
private void onSurvivorDied(Survivor survivor, GameSession session) {
Player p = survivor.getPlayer();
if (p == null || !p.isOnline()) return;
p.setGameMode(GameMode.SPECTATOR);
p.sendMessage(Component.text("[DBD] Вы мертвы. Наблюдайте за матчем.", NamedTextColor.DARK_RED));
// Let spectator follow a random alive player
for (BasePlayer other : session.getPlayers().values()) {
if (other.getUuid().equals(survivor.getUuid())) continue;
Player op = other.getPlayer();
if (op != null && op.isOnline() && other.getState() != PlayerState.DEAD) {
p.setSpectatorTarget(op);
break;
}
}
public String getWinReason() {
return winConditions != null ? winConditions.getWinReason() : null;
}
// -------------------------------------------------------------------------
// Generator / win-condition checks
// -------------------------------------------------------------------------
// =========================================================================
// 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);
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));
spawnGameBlocks(plugin, cfg);
generatorKick = new GeneratorKick(plugin, cfg, generatorManager);
generatorRepair = new dev.loki.objective.GeneratorRepair(plugin, generatorManager);
// Голограммы ВСЕГДА включены
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();
}
// Register skill check GUI listener
dev.loki.listener.SkillCheckListener skillCheckListener =
new dev.loki.listener.SkillCheckListener(plugin,
generatorRepair.getSkillCheckGUI());
skillCheckListener.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(Component.text(
"[DBD] Все генераторы починены! Открывайте ворота!", NamedTextColor.GREEN));
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);
}
if (p != null) p.playSound(p.getLocation(), Sound.ENTITY_FIREWORK_ROCKET_BLAST, 1.0f, 1.0f);
}
}
}
private void checkWinConditions(GameSession session) {
int aliveSurvivors = 0;
int escapedSurvivors = 0;
int totalSurvivors = 0;
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);
}
for (BasePlayer bp : session.getPlayers().values()) {
if (bp.isKiller()) continue;
totalSurvivors++;
PlayerState st = bp.getState();
if (st == PlayerState.ESCAPED) {
escapedSurvivors++;
} else if (st != PlayerState.DEAD) {
aliveSurvivors++;
}
// Переводим мёртвых в SPECTATOR (если ещё не переведены)
if (st == PlayerState.DEAD) {
Player p = bp.getPlayer();
if (p != null && p.isOnline() && p.getGameMode() != GameMode.SPECTATOR) {
onSurvivorDied((Survivor) bp, session);
}
// Спавн блоков на местах сундуков + лут (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());
}
}
if (totalSurvivors == 0) return;
plugin.getLogger().info("[GameplayPhase] Spawned " + chestManager.getTotalChests() + " chests with loot");
// Маньяк побеждает — все мертвы и никто не сбежал
boolean killerWin = (aliveSurvivors == 0 && escapedSurvivors == 0);
// Выжившие побеждают — хотя бы один сбежал
boolean survivorsWin = (escapedSurvivors >= 1);
if (killerWin) {
announceKillerWin(session);
shouldEnd = true;
} else if (survivorsWin && aliveSurvivors == 0) {
// Все либо сбежали либо мертвы — конец матча
announceSurvivorWin(session);
shouldEnd = true;
// Спавн блоков на местах крюков
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");
}
private void announceKillerWin(GameSession session) {
Bukkit.broadcast(Component.text("☠ КИЛЛЕР ПОБЕДИЛ! ☠", NamedTextColor.DARK_RED));
Bukkit.broadcast(Component.text("Все выжившие уничтожены.", NamedTextColor.RED));
for (BasePlayer bp : session.getPlayers().values()) {
Player p = bp.getPlayer();
if (p == null || !p.isOnline()) continue;
if (bp.isKiller()) {
p.showTitle(Title.title(
Component.text("ПОБЕДА!").color(TextColor.color(0xFF2222)),
Component.text("Ты уничтожил всех выживших").color(NamedTextColor.RED)
));
p.playSound(p.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1.0f, 1.0f);
// =========================================================================
// Cleanup
// =========================================================================
private void showGameResults(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()) {
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 {
p.showTitle(Title.title(
Component.text("ПОРАЖЕНИЕ").color(TextColor.color(0x888888)),
Component.text("Киллер победил").color(NamedTextColor.GRAY)
));
p.playSound(p.getLocation(), Sound.ENTITY_WITHER_DEATH, 0.5f, 0.8f);
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 announceSurvivorWin(GameSession session) {
Bukkit.broadcast(Component.text("✔ ВЫЖИВШИЕ ПОБЕДИЛИ! ✔", NamedTextColor.GREEN));
Bukkit.broadcast(Component.text("Выжившие сбежали!", NamedTextColor.YELLOW));
for (BasePlayer bp : session.getPlayers().values()) {
Player p = bp.getPlayer();
if (p == null || !p.isOnline()) continue;
if (!bp.isKiller()) {
p.showTitle(Title.title(
Component.text("ПОБЕГ!").color(TextColor.color(0x00FF00)),
Component.text("Вы выбрались живыми!").color(NamedTextColor.YELLOW)
));
p.playSound(p.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1.0f, 1.2f);
} else {
p.showTitle(Title.title(
Component.text("ПОРАЖЕНИЕ").color(TextColor.color(0x888888)),
Component.text("Выжившие сбежали").color(NamedTextColor.GRAY)
));
p.playSound(p.getLocation(), Sound.ENTITY_WITHER_DEATH, 0.5f, 0.8f);
}
}
private void clearListeners() {
if (interaction != null) interaction.clear();
}
// -------------------------------------------------------------------------
// Listener wiring
// -------------------------------------------------------------------------
private void wireMoveListener(GameSession session) {
if (moveListener == null) return;
moveListener.setOnSurvivorMove((survivor, loc) -> {
if (!allGensDone) return;
if (survivor.getState() == PlayerState.ESCAPED) return;
ExitGate gate = gateManager.getGateAt(loc, 3.0);
if (gate != null && gate.isOpen()) {
survivor.setState(PlayerState.ESCAPED);
Player p = survivor.getPlayer();
if (p != null) {
p.playSound(loc, Sound.ENTITY_PLAYER_LEVELUP, 1.0f, 1.2f);
Bukkit.broadcast(Component.text(
"[DBD] " + p.getName() + " сбежал через ворота!", NamedTextColor.GOLD));
}
}
});
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();
session.getPlugin().getInteractListener().setGeneratorManager(null);
}
private void wireInteractListener(GameSession session) {
if (interactListener == null) return;
interactListener.setOnPalletInteract((player, isRightClick) -> {
BasePlayer bp = session.getPlayer(player.getUniqueId());
if (bp == null) return;
if (isRightClick && !bp.isKiller()) {
palletDrop.tryDrop((Survivor) bp);
} else if (!isRightClick && bp.isKiller()) {
Killer k = (Killer) bp;
Pallet nearby = palletManager.getPalletAt(player.getLocation(), 3.0);
if (nearby != null && nearby.isDropped()) {
palletBreak.startBreaking(k, nearby, session.getPlugin());
}
}
});
interactListener.setOnRescueAttempt((player, ignored) -> {
BasePlayer bp = session.getPlayer(player.getUniqueId());
if (bp == null || bp.isKiller()) return;
Survivor rescuer = (Survivor) bp;
if (rescuer.getState() == PlayerState.HOOKED) return;
Hook hook = hookManager.findNearestOccupiedHook(player.getLocation(), 3.0);
if (hook != null) hookRescue.startRescue(rescuer, hook);
});
interactListener.setOnAttack((player, isRightClick) -> {
if (!isRightClick) return;
BasePlayer bp = session.getPlayer(player.getUniqueId());
if (bp == null || !bp.isKiller()) return;
Killer killer = (Killer) bp;
if (!killer.isCarrying()) return;
for (BasePlayer other : session.getPlayers().values()) {
if (!other.isKiller() && other.getState() == PlayerState.CARRIED) {
Survivor carried = (Survivor) other;
if (hookManager.tryHookSurvivor(carried, player.getLocation())) {
killer.setCarrying(false);
Hook nearestHook = hookManager.findNearestOccupiedHook(player.getLocation(), 5.0);
if (nearestHook != null) {
Player vp = carried.getPlayer();
if (vp != null) vp.teleport(nearestHook.getLocation());
}
}
break;
}
}
});
// Sneak: killer → GeneratorKick, survivor → HookSabotage
interactListener.setOnKillerSneak((player, isSneaking) -> {
BasePlayer bp = session.getPlayer(player.getUniqueId());
if (bp == null) return;
if (bp.isKiller()) {
Killer killer = (Killer) bp;
if (isSneaking) {
generatorKick.startCharging(killer);
} else {
generatorKick.stopCharging(killer.getUuid());
}
} else {
// Survivor sneak → hook sabotage
Survivor surv = (Survivor) bp;
if (isSneaking) {
hookSabotage.startSabotage(surv);
} else {
hookSabotage.stopSabotage(surv.getUuid());
}
}
});
interactListener.setOnChestInteract((player, ignored) -> {
BasePlayer bp = session.getPlayer(player.getUniqueId());
if (bp == null || bp.isKiller()) return;
var chest = chestManager.getNearestChest(player.getLocation(), 3.0);
if (chest != null) chestOpening.startOpening(player, chest);
});
}
// -------------------------------------------------------------------------
// Inner class: blocks all vanilla damage for players in the session
// -------------------------------------------------------------------------
private static class VanillaDamageBlocker implements Listener {
private final GameSession session;
VanillaDamageBlocker(GameSession session) { this.session = session; }
@EventHandler(priority = EventPriority.HIGHEST)
public void onDamage(EntityDamageEvent event) {
if (!(event.getEntity() instanceof Player p)) return;
if (!session.getPlayers().containsKey(p.getUniqueId())) return;
event.setCancelled(true);
p.setHealth(20.0);
}
}
}
}