From b49316d9bb59b4138b81314f6530df702ad3b87e Mon Sep 17 00:00:00 2001 From: Kam Date: Sun, 1 Mar 2026 00:09:57 -0500 Subject: [PATCH] Counter, Dodge, Guard Abilities --- .../data/ability/type/AbilityCounter.java | 26 ++++++++++++++++- .../data/ability/type/AbilityDefend.java | 13 ++++++++- .../data/ability/type/AbilityDodge.java | 2 +- .../data/ability/type/AbilityGuard.java | 2 +- .../noppes/npcs/AbstractDataAbilities.java | 14 ++++++++++ .../noppes/npcs/ScriptPlayerEventHandler.java | 28 +++++++++++++++++++ .../npcs/entity/EntityNPCInterface.java | 18 ++++++++++++ 7 files changed, 99 insertions(+), 4 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 1ef1194ad..9c6299356 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 @@ -5,7 +5,9 @@ import cpw.mods.fml.relauncher.SideOnly; 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.api.ability.type.IAbilityCounter; import noppes.npcs.client.gui.builder.FieldDef; @@ -53,17 +55,39 @@ 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(); + } } @Override protected float performDefend(float amount) { - return amount; + counterTriggered = true; + return 0; } // ═══════════════════════════════════════════════════════════════════ 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 7cef96e5c..1a3451285 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 @@ -103,7 +103,18 @@ public abstract class AbilityDefend extends Ability implements IAbilityDefend { if (source.isMagicDamage() || source.isFireDamage() || source.isExplosion() || source.isProjectile()) return amount; - return performDefend(amount); + lastAttacker = attacker; + lastDamageTaken = amount; + hitCount++; + + float result = performDefend(amount); + + // Auto-complete after max hits + if (maxHitAmount > 0 && hitCount >= maxHitAmount) { + signalCompletion(); + } + + return result; } /** 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 1b90b00f8..97050d9c0 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 @@ -45,7 +45,7 @@ public class AbilityDodge extends AbilityDefend implements IAbilityDodge { @Override protected float performDefend(float amount) { - return amount; + return 0; } // ═══════════════════════════════════════════════════════════════════ 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 f9d0ab257..66eb19d83 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 @@ -39,7 +39,7 @@ public class AbilityGuard extends AbilityDefend implements IAbilityGuard { @Override protected float performDefend(float amount) { - return 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 80334cbe2..7a842e15e 100644 --- a/src/main/java/noppes/npcs/AbstractDataAbilities.java +++ b/src/main/java/noppes/npcs/AbstractDataAbilities.java @@ -2,6 +2,7 @@ package noppes.npcs; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.type.AbilityDefend; import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; import kamkeel.npcs.controllers.data.ability.data.ChainedAbility; import kamkeel.npcs.controllers.data.ability.data.entry.ChainedAbilityEntry; @@ -317,6 +318,19 @@ public abstract class AbstractDataAbilities { return currentAbility; } + /** + * Get the active defend ability if one is currently defending. + */ + public AbilityDefend getActiveDefend() { + if (currentAbility instanceof AbilityDefend) { + AbilityDefend defend = (AbilityDefend) currentAbility; + if (defend.isDefending()) { + return defend; + } + } + return null; + } + /** * Check if a chained ability is currently executing. */ diff --git a/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java b/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java index abb9939a7..27db9ae4e 100644 --- a/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java +++ b/src/main/java/noppes/npcs/ScriptPlayerEventHandler.java @@ -10,10 +10,15 @@ import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.AttributeController; import kamkeel.npcs.controllers.SyncController; import kamkeel.npcs.controllers.data.ability.Ability; +import kamkeel.npcs.controllers.data.ability.type.AbilityCounter; +import kamkeel.npcs.controllers.data.ability.type.AbilityDefend; +import kamkeel.npcs.controllers.data.ability.type.AbilityDodge; +import kamkeel.npcs.controllers.data.ability.type.AbilityGuard; import kamkeel.npcs.network.packets.data.ability.PlayerAbilitySyncPacket; import kamkeel.npcs.controllers.data.ability.enums.AbilityPhase; import kamkeel.npcs.util.AttributeAttackUtil; import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.inventory.ContainerPlayer; @@ -624,6 +629,22 @@ public class ScriptPlayerEventHandler { noppes.npcs.scripted.event.player.PlayerEvent.AttackEvent pevent1 = new noppes.npcs.scripted.event.player.PlayerEvent.AttackEvent((IPlayer) NpcAPI.Instance().getIEntity((EntityPlayer) event.source.getEntity()), event.entityLiving, attackAmount, event.source); cancel = cancel || EventHooks.onPlayerAttack(handler, pevent1); } + + // Dodge & Counter: cancel attack entirely + if (!cancel && event.entityLiving instanceof EntityPlayerMP) { + PlayerData pData = PlayerData.get((EntityPlayer) event.entityLiving); + if (pData != null && pData.abilityData != null) { + AbilityDefend defend = pData.abilityData.getActiveDefend(); + if (defend instanceof AbilityDodge || defend instanceof AbilityCounter) { + EntityLivingBase attacker = source instanceof EntityLivingBase ? (EntityLivingBase) source : null; + float result = defend.onDefend(attacker, event.source, event.ammount); + if (result != event.ammount) { + cancel = true; + } + } + } + } + event.setCanceled(cancel); } } @@ -653,6 +674,13 @@ public class ScriptPlayerEventHandler { event.ammount = 0; cancel = true; } else { + // Guard: reduce damage + AbilityDefend defend = pData.abilityData.getActiveDefend(); + if (defend instanceof AbilityGuard) { + EntityLivingBase attacker = source instanceof EntityLivingBase ? (EntityLivingBase) source : null; + event.ammount = defend.onDefend(attacker, event.source, event.ammount); + } + float modified = pData.abilityData.onDamage(event.source, event.ammount); event.ammount = modified; } diff --git a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java index a3dcfb84c..6045317d8 100644 --- a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java +++ b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java @@ -6,6 +6,9 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; import kamkeel.npcs.addon.DBCAddon; +import kamkeel.npcs.controllers.data.ability.type.AbilityCounter; +import kamkeel.npcs.controllers.data.ability.type.AbilityDefend; +import kamkeel.npcs.controllers.data.ability.type.AbilityDodge; import kamkeel.npcs.addon.client.DBCClient; import kamkeel.npcs.network.PacketHandler; import kamkeel.npcs.network.enums.EnumSoundOperation; @@ -709,6 +712,21 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti // Resistances i = stats.resistances.applyResistance(damagesource, i); + // Defend abilities + AbilityDefend defend = this.abilities != null ? this.abilities.getActiveDefend() : null; + if (defend != null) { + // Dodge & Counter: cancel attack entirely + if (defend instanceof AbilityDodge || defend instanceof AbilityCounter) { + float result = defend.onDefend(attackingEntity, damagesource, i); + if (result != i) { + return false; + } + } else { + // Guard: reduce damage + i = defend.onDefend(attackingEntity, damagesource, i); + } + } + NpcEvent.DamagedEvent event = new NpcEvent.DamagedEvent(this.wrappedNPC, attackingEntity, i, damagesource); if (EventHooks.onNPCDamaged(this, event) || isKilled()) return false;