mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-13 19:34:09 +00:00
Counter, Dodge, Guard Animation Hookup + Damage Fixes
This commit is contained in:
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);
|
||||
|
||||
Reference in new issue
Block a user