308 lines
13 KiB
Java
308 lines
13 KiB
Java
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<UUID, Long> killerLastSneakMs = new HashMap<>();
|
||
private final Map<UUID, SurvivorController> 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);
|
||
}
|
||
}
|
||
}
|