From f1a6d7488fc21dc6bea8493e6ad9c59d9feee824 Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 1 Mar 2026 01:59:44 -0500 Subject: [PATCH] Counter, Dodge, Guard Animation Hookup + Damage Fixes --- .../data/ability/type/AbilityCounter.java | 68 +++++++++++------- .../data/ability/type/AbilityDefend.java | 70 ++++++++++++++++--- .../data/ability/type/AbilityDodge.java | 38 +++++++++- .../data/ability/type/AbilityGuard.java | 6 +- .../noppes/npcs/AbstractDataAbilities.java | 18 +++++ 5 files changed, 156 insertions(+), 44 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCounter.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCounter.java index 9c6299356..f0d5ab4b6 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCounter.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCounter.java @@ -2,12 +2,17 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.addon.DBCAddon; +import noppes.npcs.controllers.AnimationController; import kamkeel.npcs.controllers.data.ability.enums.UserType; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldDefs; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.DamageSource; +import noppes.npcs.NpcDamageSource; +import noppes.npcs.controllers.data.Animation; +import noppes.npcs.entity.EntityNPCInterface; import noppes.npcs.api.ability.type.IAbilityCounter; import noppes.npcs.client.gui.builder.FieldDef; @@ -55,38 +60,35 @@ public class AbilityCounter extends AbilityDefend implements IAbilityCounter { } } - private transient boolean counterTriggered; - // ═══════════════════════════════════════════════════════════════════ // DEFEND HOOKS // ═══════════════════════════════════════════════════════════════════ @Override - protected void onDefendTick(EntityLivingBase caster, EntityLivingBase target, int tick) { - if (counterTriggered && lastAttacker != null && lastAttacker.isEntityAlive()) { - counterTriggered = false; - - // Calculate counter damage - float damage; - if (counterType == CounterType.FLAT) { - damage = counterValue; - } else { - damage = lastDamageTaken * (counterValue / 100.0f); - } - - // Hit back - DamageSource counterSource = caster instanceof EntityPlayer - ? DamageSource.causePlayerDamage((EntityPlayer) caster) - : DamageSource.causeMobDamage(caster); - lastAttacker.attackEntityFrom(counterSource, damage); - - signalCompletion(); + protected float performDefend(EntityLivingBase attacker, float amount) { + // Calculate counter damage + float damage; + if (counterType == CounterType.FLAT) { + damage = counterValue; + } else { + damage = amount * (counterValue / 100.0f); + } + + // Hit back + if (caster != null && attacker.isEntityAlive()) { + if (caster instanceof EntityNPCInterface && DBCAddon.instance.canDBCAttack((EntityNPCInterface) caster, damage, attacker)) { + // NPC with DBC: dummy hit for events/knockback, then apply DBC damage with NPC's stats + attacker.attackEntityFrom(new NpcDamageSource("mob", (EntityNPCInterface) caster), 0.001f); + DBCAddon.instance.doDBCDamage((EntityNPCInterface) caster, damage, attacker); + } else { + // Player or non-DBC NPC: normal damage + DamageSource counterSource = caster instanceof EntityPlayer + ? DamageSource.causePlayerDamage((EntityPlayer) caster) + : DamageSource.causeMobDamage(caster); + attacker.attackEntityFrom(counterSource, damage); + } } - } - @Override - protected float performDefend(float amount) { - counterTriggered = true; return 0; } @@ -99,6 +101,22 @@ public class AbilityCounter extends AbilityDefend implements IAbilityCounter { return true; } + @Override + protected Animation getDefendAnimation() { + return getCounterAnimation(); + } + + public Animation getCounterAnimation() { + if (AnimationController.Instance == null) return null; + if (counterAnimationId >= 0) { + return (Animation) AnimationController.Instance.get(counterAnimationId); + } + if (counterAnimationName != null && !counterAnimationName.isEmpty()) { + return (Animation) AnimationController.Instance.get(counterAnimationName, true); + } + return null; + } + // ═══════════════════════════════════════════════════════════════════ // NBT // ═══════════════════════════════════════════════════════════════════ diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDefend.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDefend.java index 1a3451285..c9f6e1e0a 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDefend.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDefend.java @@ -12,6 +12,7 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.DamageSource; import noppes.npcs.api.ability.type.IAbilityDefend; import noppes.npcs.client.gui.builder.FieldDef; +import noppes.npcs.controllers.data.Animation; import java.util.List; @@ -19,7 +20,7 @@ import java.util.List; * Abstract base class for defensive abilities (Guard, Counter, Dodge). *

* {@link #onDefend} handles shared checks (physical melee only) then delegates - * to {@link #performDefend(float)} which subclasses override for their specific behavior. + * to {@link #performDefend(EntityLivingBase, float)} which subclasses override for their specific behavior. */ public abstract class AbilityDefend extends Ability implements IAbilityDefend { @@ -34,9 +35,12 @@ public abstract class AbilityDefend extends Ability implements IAbilityDefend { // TRANSIENT RUNTIME STATE // ═══════════════════════════════════════════════════════════════════ + protected transient EntityLivingBase caster; protected transient int hitCount; protected transient EntityLivingBase lastAttacker; protected transient float lastDamageTaken; + protected transient Animation pendingDefendAnimation; + protected transient int defendAnimEndTick = -1; // ═══════════════════════════════════════════════════════════════════ // CONSTRUCTOR DEFAULTS @@ -57,16 +61,14 @@ public abstract class AbilityDefend extends Ability implements IAbilityDefend { @Override public void onExecute(EntityLivingBase caster, EntityLivingBase target) { + this.caster = caster; hitCount = 0; lastAttacker = null; lastDamageTaken = 0.0f; - onDefendStart(caster); } @Override public void onActiveTick(EntityLivingBase caster, EntityLivingBase target, int tick) { - onDefendTick(caster, target, tick); - if (tick >= durationTicks) { signalCompletion(); } @@ -75,9 +77,12 @@ public abstract class AbilityDefend extends Ability implements IAbilityDefend { @Override public void reset() { super.reset(); + caster = null; hitCount = 0; lastAttacker = null; lastDamageTaken = 0.0f; + pendingDefendAnimation = null; + defendAnimEndTick = -1; } // ═══════════════════════════════════════════════════════════════════ @@ -107,7 +112,10 @@ public abstract class AbilityDefend extends Ability implements IAbilityDefend { lastDamageTaken = amount; hitCount++; - float result = performDefend(amount); + float result = performDefend(attacker, amount); + + // Queue defend reaction animation (played on next tick by AbstractDataAbilities) + pendingDefendAnimation = getDefendAnimation(); // Auto-complete after max hits if (maxHitAmount > 0 && hitCount >= maxHitAmount) { @@ -120,19 +128,16 @@ public abstract class AbilityDefend extends Ability implements IAbilityDefend { /** * Subclass-specific defend behavior. Called only for physical melee hits while defending. * - * @param amount The incoming damage + * @param attacker The entity that hit the caster + * @param amount The incoming damage * @return The modified damage the caster should take */ - protected abstract float performDefend(float amount); + protected abstract float performDefend(EntityLivingBase attacker, float amount); // ═══════════════════════════════════════════════════════════════════ // SUBCLASS HOOKS // ═══════════════════════════════════════════════════════════════════ - protected void onDefendStart(EntityLivingBase caster) {} - - protected abstract void onDefendTick(EntityLivingBase caster, EntityLivingBase target, int tick); - /** * Subclass-specific NBT persistence. */ @@ -142,6 +147,49 @@ public abstract class AbilityDefend extends Ability implements IAbilityDefend { @SideOnly(Side.CLIENT) protected abstract void getTypeDefinitions(List defs); + // ═══════════════════════════════════════════════════════════════════ + // DEFEND ANIMATION + // ═══════════════════════════════════════════════════════════════════ + + /** + * Returns the animation to play when this defend ability reacts to a hit. + * Subclasses override to provide their specific animation (counter strike, dodge roll, etc). + * Guard returns null (no reaction animation). + */ + protected Animation getDefendAnimation() { + return null; + } + + /** + * Consume and return the pending defend animation (set during onDefend). + * Called by AbstractDataAbilities to play the animation on the next tick. + */ + public Animation consumeDefendAnimation() { + Animation anim = pendingDefendAnimation; + pendingDefendAnimation = null; + return anim; + } + + /** + * Schedule return to active animation after the defend animation finishes. + */ + public void scheduleReturnToActive(int currentTick, Animation defendAnim) { + if (defendAnim != null) { + defendAnimEndTick = currentTick + (int) defendAnim.getTotalTime(); + } + } + + /** + * Check if the defend animation has finished and we should return to the active animation. + */ + public boolean shouldReturnToActiveAnimation(int currentTick) { + if (defendAnimEndTick >= 0 && currentTick >= defendAnimEndTick) { + defendAnimEndTick = -1; + return true; + } + return false; + } + // ═══════════════════════════════════════════════════════════════════ // STATE QUERIES // ═══════════════════════════════════════════════════════════════════ diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDodge.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDodge.java index 97050d9c0..3c8ef4718 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDodge.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDodge.java @@ -2,12 +2,15 @@ package kamkeel.npcs.controllers.data.ability.type; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; +import noppes.npcs.controllers.AnimationController; import kamkeel.npcs.controllers.data.ability.enums.UserType; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; +import noppes.npcs.controllers.data.Animation; import noppes.npcs.api.ability.type.IAbilityDodge; import noppes.npcs.client.gui.builder.FieldDef; +import java.util.ArrayList; import java.util.List; /** @@ -40,12 +43,41 @@ public class AbilityDodge extends AbilityDefend implements IAbilityDodge { // ═══════════════════════════════════════════════════════════════════ @Override - protected void onDefendTick(EntityLivingBase caster, EntityLivingBase target, int tick) { + protected float performDefend(EntityLivingBase attacker, float amount) { + return 0; } @Override - protected float performDefend(float amount) { - return 0; + protected Animation getDefendAnimation() { + return getRandomDodgeAnimation(); + } + + /** + * Returns a random dodge animation from the configured slots (non-empty only). + */ + public Animation getRandomDodgeAnimation() { + if (AnimationController.Instance == null) return null; + + List available = new ArrayList<>(); + Animation a1 = getDodgeAnimation(dodgeAnimation1Id, dodgeAnimation1Name); + Animation a2 = getDodgeAnimation(dodgeAnimation2Id, dodgeAnimation2Name); + Animation a3 = getDodgeAnimation(dodgeAnimation3Id, dodgeAnimation3Name); + if (a1 != null) available.add(a1); + if (a2 != null) available.add(a2); + if (a3 != null) available.add(a3); + + if (available.isEmpty()) return null; + return available.get((int) (Math.random() * available.size())); + } + + private Animation getDodgeAnimation(int id, String name) { + if (id >= 0) { + return (Animation) AnimationController.Instance.get(id); + } + if (name != null && !name.isEmpty()) { + return (Animation) AnimationController.Instance.get(name, true); + } + return null; } // ═══════════════════════════════════════════════════════════════════ 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 66eb19d83..636c02f3c 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 @@ -34,11 +34,7 @@ public class AbilityGuard extends AbilityDefend implements IAbilityGuard { // ═══════════════════════════════════════════════════════════════════ @Override - protected void onDefendTick(EntityLivingBase caster, EntityLivingBase target, int tick) { - } - - @Override - protected float performDefend(float amount) { + protected float performDefend(EntityLivingBase attacker, float amount) { return Math.max(0, amount * (1.0f - damageReduction)); } diff --git a/src/main/java/noppes/npcs/AbstractDataAbilities.java b/src/main/java/noppes/npcs/AbstractDataAbilities.java index 7a842e15e..3f44394f3 100644 --- a/src/main/java/noppes/npcs/AbstractDataAbilities.java +++ b/src/main/java/noppes/npcs/AbstractDataAbilities.java @@ -869,6 +869,24 @@ public abstract class AbstractDataAbilities { currentAbility.onActiveTick(entity, target, currentAbility.getCurrentTick()); + // Play pending defend reaction animation + sound (counter strike, dodge roll, etc.) + if (currentAbility instanceof AbilityDefend) { + AbilityDefend defend = (AbilityDefend) currentAbility; + Animation defendAnim = defend.consumeDefendAnimation(); + if (defendAnim != null) { + playAbilityAnimation(defendAnim); + playAbilitySound(currentAbility.getActiveSound()); + defend.scheduleReturnToActive(currentAbility.getCurrentTick(), defendAnim); + } + // Return to active animation after defend animation finishes (Counter → guard stance) + if (defend.shouldReturnToActiveAnimation(currentAbility.getCurrentTick())) { + Animation activeAnim = currentAbility.getActiveAnimation(); + if (activeAnim != null) { + playAbilityAnimation(activeAnim); + } + } + } + // Check if ability completed during onActiveTick if (currentAbility.getPhase() == AbilityPhase.IDLE) { handleAbilityCompletion(target);