Counter, Dodge, Guard Animation Hookup + Damage Fixes

This commit is contained in:
Kam committed 2026-03-01 01:59:44 -05:00
1 parent b49316d9bb
commit f1a6d7488f
5 files changed
+156 -44

No files matched your search

@@ -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
// ═══════════════════════════════════════════════════════════════════
@@ -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).
* <p>
* {@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<FieldDef> 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
// ═══════════════════════════════════════════════════════════════════
@@ -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<Animation> 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;
}
// ═══════════════════════════════════════════════════════════════════
@@ -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));
}
@@ -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);