mdbd/src/main/java/dev/loki/phase/GameplayPhase.java

521 lines
21 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}
}