From 371384ca8a4d8aec11878761c75ac8a52253983c Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Wed, 3 Jun 2026 21:21:36 +0200 Subject: [PATCH] feat: custom damage system - cancel vanilla damage, state-based hits, killer/survivor HP always 20 --- .../java/dev/loki/phase/GameplayPhase.java | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/src/main/java/dev/loki/phase/GameplayPhase.java b/src/main/java/dev/loki/phase/GameplayPhase.java index 6bee55f..42005f6 100644 --- a/src/main/java/dev/loki/phase/GameplayPhase.java +++ b/src/main/java/dev/loki/phase/GameplayPhase.java @@ -3,6 +3,8 @@ 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; @@ -10,6 +12,7 @@ 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; @@ -21,14 +24,21 @@ 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 { @@ -49,6 +59,14 @@ public class GameplayPhase implements Phase { 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; + // Listener references grabbed from plugin private PlayerMoveListener moveListener; private PlayerInteractListener interactListener; @@ -87,6 +105,56 @@ public class GameplayPhase implements Phase { 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(); + // --- Wire listeners --- moveListener = plugin.getMoveListener(); interactListener = plugin.getInteractListener(); @@ -136,6 +204,28 @@ public class GameplayPhase implements Phase { chestOpening.cancelAll(); } + // Unregister damage listeners + if (attackListener != null) { + attackListener.unregister(); + } + if (vanillaDamageBlocker != null) { + HandlerList.unregisterAll(vanillaDamageBlocker); + vanillaDamageBlocker = null; + } + if (killerController != null) { + killerController.shutdown(); + } + 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)); } @@ -381,4 +471,24 @@ public class GameplayPhase implements Phase { } }); } + + // ------------------------------------------------------------------------- + // 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); + } + } }