521 lines
21 KiB
Java
521 lines
21 KiB
Java
package dev.loki.phase;
|
||
|
||
import dev.loki.chest.ChestManager;
|
||
import dev.loki.chest.ChestOpening;
|
||
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.hook.HookManager;
|
||
import dev.loki.hook.HookRescue;
|
||
import dev.loki.hook.HookSacrifice;
|
||
import dev.loki.hook.HookStage;
|
||
import dev.loki.killer.KillerController;
|
||
import dev.loki.listener.PlayerInteractListener;
|
||
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 net.kyori.adventure.text.Component;
|
||
import net.kyori.adventure.text.format.NamedTextColor;
|
||
import org.bukkit.Bukkit;
|
||
import org.bukkit.Sound;
|
||
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;
|
||
|
||
public class GameplayPhase implements Phase {
|
||
|
||
private final GeneratorManager generatorManager;
|
||
private int tickCounter;
|
||
private boolean shouldEnd;
|
||
private boolean allGensDone;
|
||
|
||
// Managers initialised in enter()
|
||
private PalletManager palletManager;
|
||
private HookManager hookManager;
|
||
private GateManager gateManager;
|
||
private HookRescue hookRescue;
|
||
private HookSacrifice hookSacrifice;
|
||
private PalletDrop palletDrop;
|
||
private PalletBreak palletBreak;
|
||
private PalletStun palletStun;
|
||
private ChestManager chestManager;
|
||
private ChestOpening chestOpening;
|
||
|
||
// 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;
|
||
|
||
// Listener references grabbed from plugin
|
||
private PlayerMoveListener moveListener;
|
||
private PlayerInteractListener interactListener;
|
||
|
||
public GameplayPhase(GeneratorManager generatorManager) {
|
||
this.generatorManager = generatorManager;
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Phase lifecycle
|
||
// -------------------------------------------------------------------------
|
||
|
||
@Override
|
||
public void enter(GameSession session) {
|
||
mdbd plugin = session.getPlugin();
|
||
tickCounter = 0;
|
||
shouldEnd = false;
|
||
allGensDone = false;
|
||
|
||
// --- Initialise managers ---
|
||
palletManager = new PalletManager(plugin.getConfigManager().getLocations());
|
||
hookManager = new HookManager(plugin);
|
||
hookManager.loadHooks(plugin.getConfigManager().getLocations());
|
||
gateManager = new GateManager(plugin.getConfigManager().getLocations());
|
||
ActionBar ab = new ActionBar();
|
||
hookRescue = new HookRescue(plugin, ab);
|
||
hookSacrifice = new HookSacrifice(plugin, null);
|
||
palletDrop = new PalletDrop(palletManager);
|
||
palletBreak = new PalletBreak(session.getConfig());
|
||
palletStun = new PalletStun(session.getConfig());
|
||
chestManager = new ChestManager(plugin, plugin.getConfigManager().getLocations());
|
||
chestOpening = new ChestOpening(plugin);
|
||
|
||
// Store managers on session so other code can reach them
|
||
session.setPalletManager(palletManager);
|
||
session.setHookManager(hookManager);
|
||
session.setGateManager(gateManager);
|
||
|
||
// --- Damage system ---
|
||
// Build PlayerManager from current session players
|
||
playerManager = new PlayerManager();
|
||
for (BasePlayer bp : session.getPlayers().values()) {
|
||
playerManager.register(bp);
|
||
}
|
||
|
||
stateMachine = new StateMachine();
|
||
damageHandler = new DamageHandler(stateMachine);
|
||
|
||
// When survivor is injured — show title + sound
|
||
damageHandler.setOnSurvivorInjured(survivor -> {
|
||
Player p = survivor.getPlayer();
|
||
if (p != null && p.isOnline()) {
|
||
p.showTitle(net.kyori.adventure.title.Title.title(
|
||
Component.text("РАНЕН!", net.kyori.adventure.text.format.TextColor.color(0xFF8C00)),
|
||
Component.text("Найдите аптечку или союзника", NamedTextColor.YELLOW)
|
||
));
|
||
p.playSound(p.getLocation(), Sound.ENTITY_PLAYER_HURT, 1.0f, 0.8f);
|
||
// Keep HP at 20 always — damage is state-based not HP-based
|
||
p.setHealth(20.0);
|
||
}
|
||
});
|
||
|
||
// When survivor is downed — show title + sound
|
||
damageHandler.setOnSurvivorDowned(survivor -> {
|
||
Player p = survivor.getPlayer();
|
||
if (p != null && p.isOnline()) {
|
||
p.showTitle(net.kyori.adventure.title.Title.title(
|
||
Component.text("НОКДАУН!", net.kyori.adventure.text.format.TextColor.color(0xFF2222)),
|
||
Component.text("Ждите помощи...", NamedTextColor.RED)
|
||
));
|
||
p.playSound(p.getLocation(), Sound.ENTITY_PLAYER_HURT, 1.0f, 0.5f);
|
||
p.setHealth(20.0);
|
||
}
|
||
});
|
||
|
||
killerController = new KillerController(plugin, playerManager, damageHandler, session.getConfig());
|
||
|
||
// AttackListener — intercepts EntityDamageByEntityEvent, cancels vanilla damage, applies custom
|
||
attackListener = new AttackListener(playerManager, damageHandler,
|
||
(killer, survivor) -> killerController.tryAttack(killer, survivor));
|
||
attackListener.register(plugin);
|
||
|
||
// Block ALL vanilla damage for players in this session (fall, fire, mobs, etc.)
|
||
vanillaDamageBlocker = new VanillaDamageBlocker(session);
|
||
Bukkit.getPluginManager().registerEvents(vanillaDamageBlocker, plugin);
|
||
|
||
killerController.initialize();
|
||
|
||
// --- Give starting items to survivors & register item use ---
|
||
itemUseListener = new dev.loki.item.ItemUseListener(plugin, session);
|
||
itemUseListener.register();
|
||
|
||
giveStartingItems(session);
|
||
|
||
// --- Wire listeners ---
|
||
moveListener = plugin.getMoveListener();
|
||
interactListener = plugin.getInteractListener();
|
||
|
||
wireMoveListener(session);
|
||
wireInteractListener(session);
|
||
|
||
Bukkit.broadcast(Component.text("[DBD] Игра началась! Выжившие, чините генераторы!",
|
||
NamedTextColor.GREEN));
|
||
}
|
||
|
||
@Override
|
||
public void tick(GameSession session) {
|
||
tickCounter++;
|
||
generatorManager.tick();
|
||
|
||
// Tick hook timers every game tick
|
||
tickHooks(session);
|
||
|
||
if (tickCounter % 20 == 0) {
|
||
checkGeneratorProgress(session);
|
||
checkWinConditions(session);
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void exit(GameSession session) {
|
||
// Clean up listeners
|
||
if (moveListener != null) {
|
||
moveListener.setOnSurvivorMove(null);
|
||
}
|
||
if (interactListener != null) {
|
||
interactListener.setOnPalletInteract(null);
|
||
interactListener.setOnRescueAttempt(null);
|
||
interactListener.setOnChestInteract(null);
|
||
interactListener.setOnAttack(null);
|
||
}
|
||
|
||
// Stop hooks
|
||
if (hookManager != null) {
|
||
hookManager.clear();
|
||
}
|
||
if (hookRescue != null) {
|
||
hookRescue.clear();
|
||
}
|
||
if (chestOpening != null) {
|
||
chestOpening.cancelAll();
|
||
}
|
||
|
||
// Unregister damage listeners
|
||
if (attackListener != null) {
|
||
attackListener.unregister();
|
||
}
|
||
if (vanillaDamageBlocker != null) {
|
||
HandlerList.unregisterAll(vanillaDamageBlocker);
|
||
vanillaDamageBlocker = null;
|
||
}
|
||
if (killerController != null) {
|
||
killerController.shutdown();
|
||
}
|
||
if (itemUseListener != null) {
|
||
itemUseListener.unregister();
|
||
}
|
||
if (playerManager != null) {
|
||
playerManager.clear();
|
||
}
|
||
// Restore HP for all players
|
||
for (BasePlayer bp : session.getPlayers().values()) {
|
||
Player p = bp.getPlayer();
|
||
if (p != null && p.isOnline()) {
|
||
p.setHealth(20.0);
|
||
}
|
||
}
|
||
|
||
Bukkit.broadcast(Component.text("[DBD] Игровая фаза завершена!", NamedTextColor.GOLD));
|
||
}
|
||
|
||
@Override
|
||
public boolean isComplete() {
|
||
return shouldEnd;
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// 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;
|
||
// Clear hotbar slots 0-3, give a random item in slot 0
|
||
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;
|
||
|
||
// Show action bar progress to the hooked survivor
|
||
Player vp = victim.getPlayer();
|
||
if (vp != null && vp.isOnline()) {
|
||
// ActionBar shows stage progress
|
||
double pct = hook.getStageProgressPercent();
|
||
String label = hook.getCurrentStage() == HookStage.STAGE_2
|
||
? "§cStage 2 - struggle!"
|
||
: "§eOn hook";
|
||
vp.sendActionBar(Component.text(label + " " + (int) pct + "%"));
|
||
}
|
||
|
||
// Sacrifice when timer expires
|
||
if (hook.getCurrentStage() == HookStage.SACRIFICE) {
|
||
hookSacrifice.sacrificeIfExpired(victim, hook);
|
||
}
|
||
}
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Generator / win-condition checks
|
||
// -------------------------------------------------------------------------
|
||
|
||
private void checkGeneratorProgress(GameSession session) {
|
||
if (allGensDone) return;
|
||
|
||
if (generatorManager.areAllRequiredCompleted()) {
|
||
allGensDone = 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);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private void checkWinConditions(GameSession session) {
|
||
int aliveSurvivors = 0;
|
||
int escapedSurvivors = 0;
|
||
int totalSurvivors = 0;
|
||
|
||
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) {
|
||
// dead, counted below
|
||
} else {
|
||
// HEALTHY, INJURED, DOWNED, CARRIED, HOOKED — still in game
|
||
aliveSurvivors++;
|
||
}
|
||
}
|
||
|
||
if (totalSurvivors == 0) return; // no survivors registered yet
|
||
|
||
boolean killerWin = (aliveSurvivors == 0 && escapedSurvivors == 0);
|
||
boolean survivorsWin = (escapedSurvivors == totalSurvivors);
|
||
|
||
if (killerWin) {
|
||
announceKillerWin(session);
|
||
shouldEnd = true;
|
||
} else if (survivorsWin) {
|
||
announceSurvivorWin(session);
|
||
shouldEnd = true;
|
||
}
|
||
}
|
||
|
||
private void announceKillerWin(GameSession session) {
|
||
Bukkit.broadcast(Component.text("", NamedTextColor.RED));
|
||
Bukkit.broadcast(Component.text("☠ КИЛЛЕР ПОБЕДИЛ! ☠", NamedTextColor.DARK_RED));
|
||
Bukkit.broadcast(Component.text("Все выжившие уничтожены.", NamedTextColor.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(net.kyori.adventure.title.Title.title(
|
||
Component.text("ПОБЕДА!", net.kyori.adventure.text.format.TextColor.color(0xFF2222)),
|
||
Component.text("Ты уничтожил всех выживших", NamedTextColor.RED)
|
||
));
|
||
p.playSound(p.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1.0f, 1.0f);
|
||
} else {
|
||
p.showTitle(net.kyori.adventure.title.Title.title(
|
||
Component.text("ПОРАЖЕНИЕ", net.kyori.adventure.text.format.TextColor.color(0x888888)),
|
||
Component.text("Киллер победил", NamedTextColor.GRAY)
|
||
));
|
||
p.playSound(p.getLocation(), Sound.ENTITY_WITHER_DEATH, 0.5f, 0.8f);
|
||
}
|
||
}
|
||
}
|
||
|
||
private void announceSurvivorWin(GameSession session) {
|
||
Bukkit.broadcast(Component.text("", NamedTextColor.GREEN));
|
||
Bukkit.broadcast(Component.text("✔ ВЫЖИВШИЕ ПОБЕДИЛИ! ✔", NamedTextColor.GREEN));
|
||
Bukkit.broadcast(Component.text("Все выжившие сбежали!", NamedTextColor.YELLOW));
|
||
Bukkit.broadcast(Component.text("", NamedTextColor.GREEN));
|
||
|
||
for (BasePlayer bp : session.getPlayers().values()) {
|
||
Player p = bp.getPlayer();
|
||
if (p == null || !p.isOnline()) continue;
|
||
if (!bp.isKiller()) {
|
||
p.showTitle(net.kyori.adventure.title.Title.title(
|
||
Component.text("ПОБЕГ!", net.kyori.adventure.text.format.TextColor.color(0x00FF00)),
|
||
Component.text("Вы выбрались живыми!", NamedTextColor.YELLOW)
|
||
));
|
||
p.playSound(p.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1.0f, 1.2f);
|
||
} else {
|
||
p.showTitle(net.kyori.adventure.title.Title.title(
|
||
Component.text("ПОРАЖЕНИЕ", net.kyori.adventure.text.format.TextColor.color(0x888888)),
|
||
Component.text("Все выжившие сбежали", NamedTextColor.GRAY)
|
||
));
|
||
p.playSound(p.getLocation(), Sound.ENTITY_WITHER_DEATH, 0.5f, 0.8f);
|
||
}
|
||
}
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Listener wiring
|
||
// -------------------------------------------------------------------------
|
||
|
||
private void wireMoveListener(GameSession session) {
|
||
if (moveListener == null) return;
|
||
|
||
// Gate escape: survivor walks into open gate zone after all gens done
|
||
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 wireInteractListener(GameSession session) {
|
||
if (interactListener == null) return;
|
||
|
||
// Pallet interact: survivor right-click = drop, killer left-click = break
|
||
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 killer = (Killer) bp;
|
||
Pallet nearby = palletManager.getPalletAt(player.getLocation(), 3.0);
|
||
if (nearby != null && nearby.isDropped()) {
|
||
palletBreak.startBreaking(killer, nearby, session.getPlugin());
|
||
}
|
||
}
|
||
});
|
||
|
||
// Rescue attempt: survivor right-clicks near a hook
|
||
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);
|
||
}
|
||
});
|
||
|
||
// Killer right-click: hook a carried survivor
|
||
interactListener.setOnAttack((player, isRightClick) -> {
|
||
if (!isRightClick) return; // left-click handled by damage system
|
||
BasePlayer bp = session.getPlayer(player.getUniqueId());
|
||
if (bp == null || !bp.isKiller()) return;
|
||
Killer killer = (Killer) bp;
|
||
|
||
if (killer.getState() == PlayerState.CARRIED || killer.isCarrying()) {
|
||
// Find the carried survivor
|
||
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);
|
||
// Teleport survivor to hook
|
||
Hook nearestHook = hookManager.findNearestOccupiedHook(
|
||
player.getLocation(), 5.0);
|
||
if (nearestHook != null) {
|
||
Player vp = carried.getPlayer();
|
||
if (vp != null) {
|
||
vp.teleport(nearestHook.getLocation());
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
// Chest interact
|
||
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);
|
||
}
|
||
}
|
||
}
|