package dev.loki.phase; import dev.loki.core.GameSession; import dev.loki.gate.ExitGate; import dev.loki.gate.GateManager; import dev.loki.gate.GateOpening; import dev.loki.hook.Hook; import dev.loki.hook.HookManager; import dev.loki.hook.HookRescue; import dev.loki.hook.HookSabotage; import dev.loki.killer.GeneratorKick; import dev.loki.listener.PlayerInteractListener; import dev.loki.listener.PlayerMoveListener; import dev.loki.player.BasePlayer; import dev.loki.player.Killer; import dev.loki.player.PlayerState; import dev.loki.player.Survivor; import dev.loki.survivor.SurvivorController; 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 java.util.HashMap; import java.util.Map; import java.util.UUID; /** * Handles listener wiring for player interactions. */ public class GameplayInteraction { private PlayerMoveListener moveListener; private PlayerInteractListener interactListener; private GateManager gateManager; private HookManager hookManager; private HookRescue hookRescue; private HookSabotage hookSabotage; private GeneratorKick generatorKick; private GameplayDamageSystem damageSystem; private dev.loki.chest.ChestManager chestManager; private dev.loki.chest.ChestOpening chestOpening; private GateOpening gateOpening; private dev.loki.objective.GeneratorRepair generatorRepair; private boolean allGensDone; private final Map killerLastSneakMs = new HashMap<>(); private final Map survivorControllers = new HashMap<>(); /** * Wire all listeners. */ public void wire(GameSession session) { wireMoveListener(session); wireInteractListener(session); wireSurvivorHealing(session); } /** * Initialize SurvivorControllers with a survivorLookup so healing * can find ANY player's Survivor instance (for healing other survivors). */ private void wireSurvivorHealing(GameSession session) { survivorControllers.clear(); for (BasePlayer bp : session.getPlayers().values()) { if (bp.isKiller()) continue; Survivor survivor = (Survivor) bp; SurvivorController controller = new SurvivorController(survivor, session.getConfig()); controller.getHealing().setSurvivorLookup(player -> { BasePlayer found = session.getPlayer(player.getUniqueId()); if (found == null || found.isKiller()) return null; return (Survivor) found; }); survivorControllers.put(survivor.getUuid(), controller); } } public void setMoveListener(PlayerMoveListener moveListener) { this.moveListener = moveListener; } public void setInteractListener(PlayerInteractListener interactListener) { this.interactListener = interactListener; } public void setManagers(GateManager gateManager, HookManager hookManager, HookRescue hookRescue, HookSabotage hookSabotage, GeneratorKick generatorKick, GameplayDamageSystem damageSystem, dev.loki.chest.ChestManager chestManager, dev.loki.chest.ChestOpening chestOpening, GateOpening gateOpening, dev.loki.objective.GeneratorRepair generatorRepair) { this.gateManager = gateManager; this.hookManager = hookManager; this.hookRescue = hookRescue; this.hookSabotage = hookSabotage; this.generatorKick = generatorKick; this.damageSystem = damageSystem; this.chestManager = chestManager; this.chestOpening = chestOpening; this.gateOpening = gateOpening; this.generatorRepair = generatorRepair; } public void setAllGensDone(boolean allGensDone) { this.allGensDone = allGensDone; } 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 wireInteractListener(GameSession session) { if (interactListener == null) return; // Rescue interactListener.setOnRescueAttempt((player, ignored) -> { BasePlayer bp = session.getPlayer(player.getUniqueId()); if (bp == null || bp.isKiller()) return; Survivor rescuer = (Survivor) bp; // Cannot rescue if you're hooked, downed, or carried if (rescuer.getState() == PlayerState.HOOKED || rescuer.getState() == PlayerState.DOWNED || rescuer.getState() == PlayerState.CARRIED) return; Hook hook = hookManager.findNearestOccupiedHook(player.getLocation(), 3.0); if (hook != null) hookRescue.startRescue(rescuer, hook); }); // Right-click attack (hook carrying / pickup downed) 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()) { 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 nearest = hookManager.findNearestOccupiedHook(player.getLocation(), 5.0); if (nearest != null) { Player vp = carried.getPlayer(); if (vp != null) vp.teleport(nearest.getLocation()); } } break; } } } else { Survivor target = damageSystem.getPlayerManager().getNearestSurvivorInState( player, PlayerState.DOWNED, 3.0); if (target != null) { boolean picked = damageSystem.getKillerController().tryPickup(killer, target); if (!picked) player.sendMessage(Component.text( "[DBD] Не удалось подобрать выжившего.", NamedTextColor.RED)); } } }); // Sneak: killer → Hook / GeneratorKick / carry pickup, survivor → Sabotage interactListener.setOnKillerSneak((player, isSneaking) -> { BasePlayer bp = session.getPlayer(player.getUniqueId()); if (bp == null) return; if (bp.isKiller()) { Killer killer = (Killer) bp; if (isSneaking) { // #1: Hook carried survivor if near a hook if (killer.isCarrying()) { 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 nearest = hookManager.findNearestOccupiedHook(player.getLocation(), 5.0); if (nearest != null) { Player vp = carried.getPlayer(); if (vp != null) vp.teleport(nearest.getLocation()); } return; } break; } } return; } // #2: Double-sneak to pickup downed survivor long now = System.currentTimeMillis(); Long last = killerLastSneakMs.get(player.getUniqueId()); if (last != null && now - last < 450 && !killer.isCarrying()) { Survivor target = damageSystem.getPlayerManager().getNearestSurvivorInState( player, PlayerState.DOWNED, 3.0); if (target != null) { damageSystem.getKillerController().tryPickup(killer, target); } } killerLastSneakMs.put(player.getUniqueId(), now); generatorKick.startCharging(killer); } else { generatorKick.stopCharging(killer.getUuid()); } } else { Survivor surv = (Survivor) bp; if (surv.getState() == PlayerState.HOOKED) { // Hook recovery is handled elsewhere } else { if (isSneaking) hookSabotage.startSabotage(surv); else hookSabotage.stopSabotage(surv.getUuid()); } } }); // Generator repair interactListener.setOnGeneratorInteract((player, action) -> { BasePlayer bp = session.getPlayer(player.getUniqueId()); if (bp == null || bp.isKiller()) return; if (generatorRepair == null) return; var genManager = session.getGeneratorManager(); if (genManager == null) return; double range = session.getConfig().getGeneratorInteractionRange(); var generator = genManager.getNearestGenerator(player.getLocation(), range); if (generator != null) { generatorRepair.startRepair(player, generator); } }); // Chest 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); }); // Healing: survivor sneaks near injured/downed survivor interactListener.setOnHealSneak((healer, target) -> { SurvivorController controller = survivorControllers.get(healer.getUniqueId()); if (controller != null) { controller.onHealAttempt(target); } }); // #11: Carried survivor shift-spam to escape from killer interactListener.setOnCarriedShift((player, isSneaking) -> { if (!isSneaking) return; BasePlayer bp = session.getPlayer(player.getUniqueId()); if (bp == null || bp.isKiller()) return; Survivor survivor = (Survivor) bp; if (survivor.getState() != PlayerState.CARRIED) return; damageSystem.getKillerController().getCarry().onSurvivorShift(survivor.getUuid()); }); // Gate (uses hatch callback - triggered on PORTAL/HOPPER interaction) interactListener.setOnHatchInteract((player, ignored) -> { BasePlayer bp = session.getPlayer(player.getUniqueId()); if (bp == null || bp.isKiller()) return; Survivor survivor = (Survivor) bp; ExitGate gate = gateManager.getGateAt(player.getLocation(), 3.0); if (gate != null && !gate.isOpen() && gateOpening != null) { gateOpening.tryStartOpening(survivor, session.getPlugin()); } }); } /** * Tick all survivor controllers (healing, movement, etc.) */ public void tickSurvivors() { for (SurvivorController controller : survivorControllers.values()) { controller.tick(); } } /** * Clear all listener callbacks. */ public void clear() { survivorControllers.clear(); if (moveListener != null) moveListener.setOnSurvivorMove(null); if (interactListener != null) { interactListener.setOnRescueAttempt(null); interactListener.setOnChestInteract(null); interactListener.setOnAttack(null); interactListener.setOnKillerSneak(null); interactListener.setOnHealSneak(null); interactListener.setOnCarriedShift(null); interactListener.setOnGeneratorInteract(null); interactListener.setOnHatchInteract(null); } } }