From 9dae4bae55f846912561530827a3e0b09560f204 Mon Sep 17 00:00:00 2001 From: Kam Date: Thu, 12 Feb 2026 18:53:40 -0500 Subject: [PATCH] feat: enhance damage handling for abilities and implement guard counter mechanics --- .../data/ability/type/AbilityGuard.java | 33 +++++++------ .../data/ability/type/AbilityTeleport.java | 5 -- .../noppes/npcs/ScriptPlayerEventHandler.java | 6 +++ .../controllers/data/PlayerAbilityData.java | 48 +++++++++++++++++++ 4 files changed, 73 insertions(+), 19 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java index 477be2cc4..461fef2bd 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java @@ -65,11 +65,6 @@ public class AbilityGuard extends Ability implements IAbilityGuard { this.counterAnimationName = "Ability_Guard_Counter"; } - @Override - public boolean hasDamage() { - return false; - } - @Override public boolean allowBurst() { return false; @@ -133,16 +128,22 @@ public class AbilityGuard extends Ability implements IAbilityGuard { lastAttacker = attacker; lastDamageTaken = damage; - float reduction = ValueUtil.clamp(getDamageReductionFactor(), 0, damage); - float newHealth = ValueUtil.clamp(caster.getHealth() + reduction, 0, caster.getMaxHealth()); - - caster.setHealth(newHealth); - - if (!canCounter || !counterEligible || counterTriggered || !isDirectHit(source)) { - caster.setHealth(newHealth); + // Players: damage reduction handled via LivingHurtEvent (event.ammount modification) + // NPCs: called AFTER damageEntity() so health was already reduced — heal back here + if (caster instanceof EntityPlayer) { + if (canCounter && counterEligible && !counterTriggered && isDirectHit(source)) { + counterTriggered = true; + } } else { - caster.setHealth(ValueUtil.clamp(caster.getHealth() + damage, 0, caster.getMaxHealth())); - counterTriggered = true; + float reduction = ValueUtil.clamp(getDamageReductionFactor(), 0, damage); + float newHealth = ValueUtil.clamp(caster.getHealth() + reduction, 0, caster.getMaxHealth()); + + if (canCounter && counterEligible && !counterTriggered && isDirectHit(source)) { + caster.setHealth(ValueUtil.clamp(caster.getHealth() + damage, 0, caster.getMaxHealth())); + counterTriggered = true; + } else { + caster.setHealth(newHealth); + } } } @@ -342,6 +343,10 @@ public class AbilityGuard extends Ability implements IAbilityGuard { this.counterAnimationId = counterAnimationId; } + public boolean isCounterTriggered() { + return counterTriggered; + } + public String getCounterAnimationName() { return counterAnimationName; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java index 4b19bc91d..29eb73832 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTeleport.java @@ -86,11 +86,6 @@ public class AbilityTeleport extends Ability implements IAbilityTeleport { this.activeSound = "mob.endermen.portal"; } - @Override - public boolean hasDamage() { - return false; - } - @Override public boolean isTargetingModeLocked() { return true; diff --git a/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java b/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java index 0541f64c4..c77083fa1 100644 --- a/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java +++ b/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java @@ -596,6 +596,12 @@ public class ScriptPlayerEventHandler { } if (event.entityLiving instanceof EntityPlayerMP) { + // Handle ability damage interactions (Guard counter, damage reduction) + PlayerData pData = PlayerData.get((EntityPlayer) event.entityLiving); + if (pData != null && pData.abilityData != null && pData.abilityData.isExecutingAbility()) { + event.ammount = pData.abilityData.onDamage(event.source, event.ammount); + } + PlayerDataScript handler = ScriptController.Instance.getPlayerScripts((EntityPlayer) event.entityLiving); noppes.npcs.scripted.event.player.PlayerEvent.DamagedEvent pevent = new noppes.npcs.scripted.event.player.PlayerEvent.DamagedEvent((IPlayer) NpcAPI.Instance().getIEntity((EntityPlayer) event.entityLiving), source, event.ammount, event.source); cancel = EventHooks.onPlayerDamaged(handler, pevent); diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java index a653e2a94..3e6489b1c 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java @@ -4,6 +4,7 @@ import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityController; import kamkeel.npcs.controllers.data.ability.AbilityPhase; import kamkeel.npcs.controllers.data.ability.UserType; +import kamkeel.npcs.controllers.data.ability.type.AbilityGuard; import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.network.packets.data.telegraph.TelegraphRemovePacket; import kamkeel.npcs.network.packets.data.telegraph.TelegraphSpawnPacket; @@ -417,6 +418,53 @@ public class PlayerAbilityData implements IPlayerAbilityData { } } + /** + * Handle damage taken while an ability is executing. + * Called from LivingHurtEvent BEFORE damage is applied to health. + * Triggers Guard counter detection and handles ability interruption. + * + * @param source The damage source + * @param amount The damage amount (post-armor) + * @return The modified damage amount (reduced by guard, or 0 if counter absorbs) + */ + public float onDamage(DamageSource source, float amount) { + if (currentAbility == null || !currentAbility.isExecuting()) { + return amount; + } + + net.minecraft.entity.Entity sourceEntity = source.getEntity(); + EntityLivingBase attacker = sourceEntity instanceof EntityLivingBase ? (EntityLivingBase) sourceEntity : null; + + // Track counter state before calling onDamageTaken + boolean wasCounterTriggered = false; + if (currentAbility instanceof AbilityGuard) { + wasCounterTriggered = ((AbilityGuard) currentAbility).isCounterTriggered(); + } + + currentAbility.onDamageTaken(playerData.player, attacker, source, amount); + + // Guard: handle damage reduction and counter absorption + if (currentAbility instanceof AbilityGuard) { + AbilityGuard guard = (AbilityGuard) currentAbility; + if (guard.isGuarding()) { + // Counter just triggered — absorb full damage + if (!wasCounterTriggered && guard.isCounterTriggered()) { + return 0; + } + // Normal guard — apply flat damage reduction + float reduction = guard.getDamageReductionFactor(); + return Math.max(0, amount - reduction); + } + } + + // Check for ability interruption + if (currentAbility != null && currentAbility.canInterrupt(source)) { + interruptCurrentAbility(); + } + + return amount; + } + @Override public boolean isOnCooldown() { EntityPlayer player = playerData.player;