diff --git a/src/api b/src/api index f969b9f54..22135344e 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit f969b9f5403836489c815cef025d0a55b821eaf3 +Subproject commit 22135344e85a368af2cacc622bc7f50f32505b72 diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java index 70e286298..270d4ede7 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -85,6 +85,7 @@ public abstract class Ability implements IAbility, IAbilityAction { // Interruption protected boolean interruptible = true; protected InvulnerableMode invulnerableMode = InvulnerableMode.NONE; + protected boolean ignoreIFrames = false; // Feedback protected LockMode lockMovement = LockMode.WINDUP; @@ -365,22 +366,11 @@ public abstract class Ability implements IAbility, IAbilityAction { return true; } - public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, int tick) { - } + public void onWindUpTick(EntityLivingBase caster, EntityLivingBase target, int tick) {} - /** - * Whether this execution is a burst refire (not the first execution). - * Useful in onExecute() to detect when windup was skipped and entities need fresh creation. - */ - public boolean isBurstRefire() { - return burstEnabled && burstIndex > 0; - } + public void onInterrupt(EntityLivingBase caster, DamageSource source, float damage) {} - public void onInterrupt(EntityLivingBase caster, DamageSource source, float damage) { - } - - public void onComplete(EntityLivingBase caster, EntityLivingBase target) { - } + public void onComplete(EntityLivingBase caster, EntityLivingBase target) {} /** * Called when the ability enters BURST_DELAY between burst iterations. @@ -409,24 +399,6 @@ public abstract class Ability implements IAbility, IAbilityAction { return applyAbilityDamageInternal(caster, hitEntity, damage, knockback, 0.0f, dx, dz); } - /** - * Apply damage to an entity with ability hit event support and vertical knockback. - * Fires the abilityHit script event, allowing scripts to modify or cancel the damage. - * - * @param caster The entity executing the ability (NPC or Player) - * @param hitEntity The entity being hit - * @param damage The damage amount - * @param knockback The horizontal knockback - * @param knockbackUp The vertical knockback - * @return true if damage was applied (not cancelled), false if cancelled - */ - protected boolean applyAbilityDamage(EntityLivingBase caster, EntityLivingBase hitEntity, - float damage, float knockback, float knockbackUp) { - double dx = hitEntity.posX - caster.posX; - double dz = hitEntity.posZ - caster.posZ; - return applyAbilityDamageInternal(caster, hitEntity, damage, knockback, knockbackUp, dx, dz); - } - /** * Apply damage to an entity with ability hit event support and custom knockback direction. * Fires the abilityHit script event, allowing scripts to modify or cancel the damage. @@ -445,6 +417,28 @@ public abstract class Ability implements IAbility, IAbilityAction { return applyAbilityDamageInternal(caster, hitEntity, damage, knockback, 0.0f, knockbackDirX, knockbackDirZ); } + /** + * Clears hurt resistance before damage and returns the previous value so it can be restored later. + */ + public static int clearHurtResistanceIfNeeded(EntityLivingBase target, boolean ignoreIFrames) { + if (!ignoreIFrames || target == null) { + return -1; + } + int previous = target.hurtResistantTime; + target.hurtResistantTime = 0; + return previous; + } + + /** + * Restores hurt resistance after damage when {@link #clearHurtResistanceIfNeeded} was used. + */ + public static void restoreHurtResistanceIfNeeded(EntityLivingBase target, boolean ignoreIFrames, int previous) { + if (!ignoreIFrames || target == null || previous < 0) { + return; + } + target.hurtResistantTime = previous; + } + /** * Internal unified damage application method. * All public damage methods delegate to this. @@ -470,19 +464,24 @@ public abstract class Ability implements IAbility, IAbilityAction { // Apply damage if (damage > 0) { - // Check for ability extenders (e.g., DBC Addon damage routing) - boolean handled = AbilityController.Instance.fireOnAbilityDamage( - this, caster, hitEntity, damage, knockback, knockbackUp, - knockbackDirX, knockbackDirZ, 1.0f); - if (!handled) { - // Default damage path - if (caster instanceof EntityNPCInterface) { - hitEntity.attackEntityFrom(new NpcDamageSource("mob", (EntityNPCInterface) caster), damage); - } else if (caster instanceof EntityPlayer) { - hitEntity.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) caster), damage); - } else { - hitEntity.attackEntityFrom(DamageSource.causeMobDamage(caster), damage); + int previousHurtResistantTime = clearHurtResistanceIfNeeded(hitEntity, ignoreIFrames); + try { + // Check for ability extenders (e.g., DBC Addon damage routing) + boolean handled = AbilityController.Instance.fireOnAbilityDamage( + this, caster, hitEntity, damage, knockback, knockbackUp, + knockbackDirX, knockbackDirZ, 1.0f); + if (!handled) { + // Default damage path + if (caster instanceof EntityNPCInterface) { + hitEntity.attackEntityFrom(new NpcDamageSource("mob", (EntityNPCInterface) caster), damage); + } else if (caster instanceof EntityPlayer) { + hitEntity.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) caster), damage); + } else { + hitEntity.attackEntityFrom(DamageSource.causeMobDamage(caster), damage); + } } + } finally { + restoreHurtResistanceIfNeeded(hitEntity, ignoreIFrames, previousHurtResistantTime); } } @@ -591,14 +590,6 @@ public abstract class Ability implements IAbility, IAbilityAction { return null; } - /** - * Returns true if the telegraph type for this ability is locked and cannot be changed. - * Telegraph type is inherent to how the ability works (e.g., Charge uses LINE, Slam uses CIRCLE). - */ - public boolean isTelegraphTypeLocked() { - return true; - } - /** * Add field definitions for this ability's GUI. * Override in subclasses to add type-specific fields. The list already contains @@ -716,6 +707,10 @@ public abstract class Ability implements IAbility, IAbilityAction { }) .hover("ability.hover.invulnerable") .tab("General")); + defs.add(FieldDef.boolField("ability.ignoreIFrames", this::isIgnoreIFrames, this::setIgnoreIFrames) + .hover("ability.hover.ignoreIFrames") + .visibleWhen(this::hasDamage) + .tab("General")); // ── Burst section ──────────────────────────────────────────── if (allowBurst()) { @@ -1035,43 +1030,6 @@ public abstract class Ability implements IAbility, IAbilityAction { return startY - 0.5; } - /** - * Calculates offset position near the given coordinates. - * Used to place effects near a target rather than exactly on them. - * - * @param baseX Base X coordinate - * @param baseY Base Y coordinate - * @param baseZ Base Z coordinate - * @param minOffset Minimum offset distance - * @param maxOffset Maximum offset distance - * @param randomOffset Whether to use random offset within range, or fixed at max - * @param random Random instance to use - * @return Array of [x, y, z] with offset applied - */ - public static double[] calculateOffsetPosition(double baseX, double baseY, double baseZ, - float minOffset, float maxOffset, - boolean randomOffset, java.util.Random random) { - if (maxOffset <= 0) { - return new double[]{baseX, baseY, baseZ}; - } - - double offsetDist; - double offsetAngle; - - if (randomOffset) { - offsetDist = minOffset + random.nextDouble() * (maxOffset - minOffset); - offsetAngle = random.nextDouble() * Math.PI * 2; - } else { - offsetDist = maxOffset; - offsetAngle = random.nextDouble() * Math.PI * 2; - } - - double offsetX = Math.cos(offsetAngle) * offsetDist; - double offsetZ = Math.sin(offsetAngle) * offsetDist; - - return new double[]{baseX + offsetX, baseY, baseZ + offsetZ}; - } - /** * Get the current telegraph instances. */ @@ -1426,6 +1384,7 @@ public abstract class Ability implements IAbility, IAbilityAction { nbt.setInteger("recovery", dazedTicks); nbt.setBoolean("interruptible", interruptible); nbt.setInteger("invulnerableMode", invulnerableMode.ordinal()); + nbt.setBoolean("ignoreIFrames", ignoreIFrames); nbt.setInteger("lockMovement", lockMovement.ordinal()); nbt.setInteger("rotationMode", rotationMode.ordinal()); nbt.setInteger("rotationPhase", rotationPhase.ordinal()); @@ -1519,6 +1478,7 @@ public abstract class Ability implements IAbility, IAbilityAction { invulnerableMode = nbt.hasKey("invulnerableMode") ? InvulnerableMode.fromOrdinal(nbt.getInteger("invulnerableMode")) : InvulnerableMode.NONE; + ignoreIFrames = nbt.hasKey("ignoreIFrames") && nbt.getBoolean("ignoreIFrames"); lockMovement = LockMode.fromOrdinal(nbt.getInteger("lockMovement")); rotationMode = RotationMode.fromOrdinal(nbt.getInteger("rotationMode")); rotationPhase = LockMode.fromOrdinal(nbt.getInteger("rotationPhase")); @@ -1823,6 +1783,16 @@ public abstract class Ability implements IAbility, IAbilityAction { return invulnerableMode.invulnerableDuringActive(); } + @Override + public boolean isIgnoreIFrames() { + return ignoreIFrames; + } + + @Override + public void setIgnoreIFrames(boolean ignore) { + this.ignoreIFrames = ignore; + } + public LockMode getLockMovement() { return lockMovement; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java index 23ff298db..a37df738e 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHazard.java @@ -20,7 +20,6 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard { private float radius = 2.0f; private float damagePerSecond = 1.0f; private int damageInterval = 20; - private boolean ignoreInvulnFrames = false; private boolean affectsCaster = false; public AbilityHazard() { @@ -75,7 +74,7 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard { zoneShape, radius, damagePerSecond, damageInterval, - ignoreInvulnFrames, affectsCaster, + isIgnoreIFrames(), affectsCaster, durationTicks, colorData.innerColor, colorData.outerColor, colorData.outerColorEnabled, zoneHeight, @@ -99,7 +98,6 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard { nbt.setFloat("radius", radius); nbt.setFloat("damagePerSecond", damagePerSecond); nbt.setInteger("damageInterval", damageInterval); - nbt.setBoolean("ignoreInvulnFrames", ignoreInvulnFrames); nbt.setBoolean("affectsCaster", affectsCaster); } @@ -109,7 +107,6 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard { this.radius = nbt.getFloat("radius"); this.damagePerSecond = nbt.getFloat("damagePerSecond"); this.damageInterval = nbt.getInteger("damageInterval"); - this.ignoreInvulnFrames = nbt.getBoolean("ignoreInvulnFrames"); this.affectsCaster = nbt.getBoolean("affectsCaster"); } @@ -137,14 +134,6 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard { this.damageInterval = damageInterval; } - public boolean isIgnoreInvulnFrames() { - return ignoreInvulnFrames; - } - - public void setIgnoreInvulnFrames(boolean ignoreInvulnFrames) { - this.ignoreInvulnFrames = ignoreInvulnFrames; - } - public boolean isAffectsCaster() { return affectsCaster; } @@ -176,7 +165,6 @@ public class AbilityHazard extends AbilityZone implements IAbilityHazard { FieldDef.floatField("gui.dps", this::getDamagePerSecond, this::setDamagePerSecond), FieldDef.intField("gui.interval", this::getDamageInterval, this::setDamageInterval) ), - FieldDef.boolField("ability.ignoreInvulnFrames", this::isIgnoreInvulnFrames, this::setIgnoreInvulnFrames), FieldDef.boolField("ability.affectsCaster", this::isAffectsCaster, this::setAffectsCaster) .hover("ability.hover.affectsCaster"), AbilityFieldDefs.effectsListField("ability.effects", this::getEffects, this::setEffects) diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java index 21b5ff0e7..1ed357d94 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityTrap.java @@ -80,6 +80,7 @@ public class AbilityTrap extends AbilityZone implements IAbilityTrap { zoneShape, triggerRadius, armTime, maxTriggers, triggerCooldown, damage, damageRadius, knockback, durationTicks, + isIgnoreIFrames(), colorData.innerColor, colorData.outerColor, colorData.outerColorEnabled, zoneHeight, particleDensity, particleScale, animSpeed, lightningDensity, diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java index d6d813a63..03c54ef71 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityVortex.java @@ -263,7 +263,6 @@ public class AbilityVortex extends Ability implements IAbilityVortex { state.lastZ = entity.posZ; if (shouldDealPullDamage) { - entity.hurtResistantTime = 0; applyAbilityDamage(caster, entity, pullDamage * 0.5f, 0); } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java index 80dfb8a9b..0e08fab02 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java @@ -212,6 +212,7 @@ public abstract class AbilityEnergyProjectile E entity = createEntity(caster, target, spawnPos, resolved, index); entity.setAnchorData(projectiles[index].anchor); entity.setEffects(this.effects); + entity.setIgnoreIFrames(this.isIgnoreIFrames()); entity.setSourceAbility(this); return entity; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java index f87561174..3fc833ff4 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilitySweeper.java @@ -104,6 +104,8 @@ public class AbilitySweeper extends AbilityEnergy implements IAbilitySweeper { sweepSpeed, numberOfRotations, damage, damageInterval, piercing, lockOnTarget); + activeEntity.setIgnoreIFrames(this.isIgnoreIFrames()); + activeEntity.setSourceAbility(this); if (isPreview()) { activeEntity.setupPreview(caster); diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java index 4d83945e4..ba2352b31 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java @@ -156,7 +156,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD private float damagePerSecond = 1.0f; private int damageInterval = 20; - private boolean ignoreInvulnFrames = false; + private boolean ignoreIFrames = false; private boolean affectsCaster = false; // ═══════════════════════════════════════════════════════════════════ @@ -203,6 +203,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD float triggerRadius, int armTime, int maxTriggers, int triggerCooldown, float damage, float damageRadius, float knockback, int durationTicks, + boolean ignoreIFrames, int innerColor, int outerColor, boolean outerColorEnabled, float zoneHeight, float particleDensity, float particleScale, @@ -221,6 +222,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD zone.knockback = knockback; zone.durationTicks = durationTicks; zone.maxTicks = durationTicks; + zone.ignoreIFrames = ignoreIFrames; zone.innerColor = innerColor; zone.outerColor = outerColor; zone.outerColorEnabled = outerColorEnabled; @@ -244,7 +246,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD ZoneShape shape, float radius, float damagePerSecond, int damageInterval, - boolean ignoreInvulnFrames, boolean affectsCaster, + boolean ignoreIFrames, boolean affectsCaster, int durationTicks, int innerColor, int outerColor, boolean outerColorEnabled, float zoneHeight, @@ -256,7 +258,7 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD zone.radius = radius; zone.damagePerSecond = damagePerSecond; zone.damageInterval = damageInterval; - zone.ignoreInvulnFrames = ignoreInvulnFrames; + zone.ignoreIFrames = ignoreIFrames; zone.affectsCaster = affectsCaster; zone.durationTicks = durationTicks; zone.maxTicks = durationTicks; @@ -472,9 +474,6 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD if (!isInZone(entity)) continue; if (damagePerSecond > 0) { - if (ignoreInvulnFrames) { - entity.hurtResistantTime = 0; - } applyDamage(entity, owner, damagePerSecond); } @@ -504,14 +503,19 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD private boolean applyDamage(EntityLivingBase target, Entity owner, float dmg) { if (dmg <= 0) return false; - if (owner instanceof EntityNPCInterface) { - return target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), dmg); - } else if (owner instanceof EntityPlayer) { - return target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), dmg); - } else if (owner instanceof EntityLivingBase) { - return target.attackEntityFrom(DamageSource.causeMobDamage((EntityLivingBase) owner), dmg); - } else { - return target.attackEntityFrom(new NpcDamageSource("npc_ability", null), dmg); + int previousHurtResistantTime = Ability.clearHurtResistanceIfNeeded(target, ignoreIFrames); + try { + if (owner instanceof EntityNPCInterface) { + return target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), dmg); + } else if (owner instanceof EntityPlayer) { + return target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), dmg); + } else if (owner instanceof EntityLivingBase) { + return target.attackEntityFrom(DamageSource.causeMobDamage((EntityLivingBase) owner), dmg); + } else { + return target.attackEntityFrom(new NpcDamageSource("npc_ability", null), dmg); + } + } finally { + Ability.restoreHurtResistanceIfNeeded(target, ignoreIFrames, previousHurtResistantTime); } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java index d3fac4faf..cb7c92534 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java @@ -43,6 +43,7 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit * The ability that spawned this entity. Transient, not saved to NBT. */ protected transient Ability sourceAbility = null; + protected boolean ignoreIFrames = false; protected boolean previewMode = false; protected EntityLivingBase previewOwner = null; @@ -140,6 +141,14 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit this.sourceAbility = ability; } + public boolean isIgnoreIFrames() { + return ignoreIFrames; + } + + public void setIgnoreIFrames(boolean ignoreIFrames) { + this.ignoreIFrames = ignoreIFrames; + } + // ==================== PREVIEW MODE ==================== public boolean isPreviewMode() { @@ -313,6 +322,7 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit */ protected void writeEnergyBaseNBT(NBTTagCompound nbt) { nbt.setInteger("OwnerId", ownerEntityId); + nbt.setBoolean("IgnoreIFrames", ignoreIFrames); displayData.writeNBT(nbt); lightningData.writeNBT(nbt); } @@ -323,6 +333,7 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit */ protected void readEnergyBaseNBT(NBTTagCompound nbt) { this.ownerEntityId = nbt.getInteger("OwnerId"); + this.ignoreIFrames = nbt.getBoolean("IgnoreIFrames"); displayData.readNBT(nbt); lightningData.readNBT(nbt); } diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java index 8ed770f8a..6a0ac0db5 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java @@ -2,6 +2,7 @@ package kamkeel.npcs.entity; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyBarrierData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; @@ -206,6 +207,7 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { @SuppressWarnings("unchecked") List entities = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, hitBox); Entity owner = getOwnerEntity(); + boolean ignoreIFrames = sourceAbility != null && sourceAbility.isIgnoreIFrames(); for (EntityLivingBase target : entities) { if (target.getEntityId() == ownerEntityId) continue; @@ -213,12 +215,17 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { // Apply damage if (panelData.launchDamage > 0) { - if (owner instanceof EntityNPCInterface) { - target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), panelData.launchDamage); - } else if (owner instanceof EntityPlayer) { - target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), panelData.launchDamage); - } else { - target.attackEntityFrom(DamageSource.generic, panelData.launchDamage); + int previousHurtResistantTime = Ability.clearHurtResistanceIfNeeded(target, ignoreIFrames); + try { + if (owner instanceof EntityNPCInterface) { + target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), panelData.launchDamage); + } else if (owner instanceof EntityPlayer) { + target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), panelData.launchDamage); + } else { + target.attackEntityFrom(DamageSource.generic, panelData.launchDamage); + } + } finally { + Ability.restoreHurtResistanceIfNeeded(target, ignoreIFrames, previousHurtResistantTime); } } diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index ab5303f28..df3d320c0 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -3,6 +3,7 @@ package kamkeel.npcs.entity; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.AbilityController; +import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.type.energy.AbilityEnergyProjectile; import kamkeel.npcs.controllers.data.ability.data.effect.AbilityPotionEffect; import kamkeel.npcs.controllers.data.ability.enums.AnchorPoint; @@ -895,23 +896,24 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { // ==================== DAMAGE & EFFECTS ==================== - protected void applyDamage(EntityLivingBase target) { - if (previewMode) return; // Skip damage in preview mode - applyDamage(target, this.getDamage(), this.getKnockback()); + protected boolean applyDamage(EntityLivingBase target) { + if (previewMode) return false; // Skip damage in preview mode + return applyDamage(target, this.getDamage(), this.getKnockback()); } - protected void applyDamage(EntityLivingBase target, float dmg, float kb) { - if (previewMode) return; // Skip damage in preview mode - if (target == null || shouldIgnoreEntity(target)) return; + protected boolean applyDamage(EntityLivingBase target, float dmg, float kb) { + if (previewMode) return false; // Skip damage in preview mode + if (target == null || shouldIgnoreEntity(target)) return false; // Fire entity impact event (may cancel or modify damage) if (!worldObj.isRemote) { float result = EventHooks.onEnergyProjectileEntityImpact(this, target, dmg); - if (result < 0) return; // Event was cancelled + if (result < 0) return false; // Event was cancelled dmg = result; } Entity owner = getOwnerEntity(); + boolean ignoreIFrames = isIgnoreIFrames(); // Check for ability extenders (e.g., DBC Addon damage routing) boolean handled = false; @@ -925,21 +927,26 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { dmg, kb, kbUp, dx, dz, damageMultiplier); } - if (!handled) { - // Default damage path - if (owner instanceof EntityNPCInterface) { - defaultDamageApplied = target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), dmg); - } else if (owner instanceof EntityPlayer) { - defaultDamageApplied = target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), dmg); - } else if (owner instanceof EntityLivingBase) { - defaultDamageApplied = target.attackEntityFrom(DamageSource.causeMobDamage((EntityLivingBase) owner), dmg); - } else { - defaultDamageApplied = target.attackEntityFrom(new NpcDamageSource("npc_ability", null), dmg); + int previousHurtResistantTime = Ability.clearHurtResistanceIfNeeded(target, ignoreIFrames); + try { + if (!handled) { + // Default damage path + if (owner instanceof EntityNPCInterface) { + defaultDamageApplied = target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), dmg); + } else if (owner instanceof EntityPlayer) { + defaultDamageApplied = target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), dmg); + } else if (owner instanceof EntityLivingBase) { + defaultDamageApplied = target.attackEntityFrom(DamageSource.causeMobDamage((EntityLivingBase) owner), dmg); + } else { + defaultDamageApplied = target.attackEntityFrom(new NpcDamageSource("npc_ability", null), dmg); + } } + } finally { + Ability.restoreHurtResistanceIfNeeded(target, ignoreIFrames, previousHurtResistantTime); } boolean allowSecondaryEffects = handled || dmg <= 0 || defaultDamageApplied; - if (!allowSecondaryEffects) return; + if (!allowSecondaryEffects) return false; if (kb > 0) { double dx = target.posX - posX; @@ -955,6 +962,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { } applyEffects(target); + return true; } protected void applyEffects(EntityLivingBase target) { @@ -982,11 +990,11 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { } } - protected void doExplosion() { - if (previewMode) return; // Skip explosion in preview mode - if (worldObj.isRemote) return; // Explosions are server-side only + protected boolean doExplosion() { + if (previewMode) return false; // Skip explosion in preview mode + if (worldObj.isRemote) return false; // Explosions are server-side only float explosionRad = getExplosionRadius(); - if (Float.isNaN(explosionRad) || explosionRad <= 0) return; + if (Float.isNaN(explosionRad) || explosionRad <= 0) return false; final double explosionRadSq = explosionRad * explosionRad; final float baseDamage = getDamage(); final float baseKnockback = getKnockback(); @@ -1004,6 +1012,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { @SuppressWarnings("unchecked") List targets = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, explosionBox); + boolean anyDamaged = false; for (EntityLivingBase target : targets) { if (target == owner) continue; @@ -1028,10 +1037,13 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { float dist = (float) Math.sqrt(distSq); falloff = 1.0f - (dist / explosionRad) * damageFalloff; } - applyDamage(target, baseDamage * falloff, baseKnockback * falloff); + if (applyDamage(target, baseDamage * falloff, baseKnockback * falloff)) { + anyDamaged = true; + } } tryDestroyTerrain(explosionRad); + return anyDamaged; } /** @@ -2114,7 +2126,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { protected boolean processEntityHit(EntityLivingBase entity, double impactX, double impactY, double impactZ) { if (entity == null || shouldIgnoreEntity(entity) || !canHitEntityNow(entity)) return false; - recordEntityHit(entity); + boolean successfulHit; if (isExplosive()) { Vec3 impactPoint = resolveEntityImpactPoint(entity, impactX, impactY, impactZ); double oldX = posX; @@ -2123,15 +2135,19 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { posX = impactPoint.xCoord; posY = impactPoint.yCoord; posZ = impactPoint.zCoord; - doExplosion(); + successfulHit = doExplosion(); posX = oldX; posY = oldY; posZ = oldZ; } else { - applyDamage(entity); + successfulHit = applyDamage(entity); } - if (shouldTerminateAfterHit()) { + if (successfulHit) { + recordEntityHit(entity); + } + + if ((successfulHit && shouldTerminateAfterHit()) || (!successfulHit && combatData.hitType == HitType.SINGLE)) { hasHit = true; this.setDead(); return true; diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergySweeper.java b/src/main/java/kamkeel/npcs/entity/EntityEnergySweeper.java index e64b0f6eb..1b4992152 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergySweeper.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergySweeper.java @@ -2,6 +2,7 @@ package kamkeel.npcs.entity; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; +import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import kamkeel.npcs.util.CNPCDebug; import net.minecraft.entity.Entity; @@ -308,14 +309,20 @@ public class EntityEnergySweeper extends EntityEnergyAbility { } private void applyDamage(EntityLivingBase target, Entity owner) { - if (owner instanceof EntityNPCInterface) { - target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), damage); - } else if (owner instanceof EntityPlayer) { - target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), damage); - } else if (owner instanceof EntityLivingBase) { - target.attackEntityFrom(DamageSource.causeMobDamage((EntityLivingBase) owner), damage); - } else { - target.attackEntityFrom(new NpcDamageSource("npc_ability", null), damage); + boolean ignoreIFrames = isIgnoreIFrames(); + int previousHurtResistantTime = Ability.clearHurtResistanceIfNeeded(target, ignoreIFrames); + try { + if (owner instanceof EntityNPCInterface) { + target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), damage); + } else if (owner instanceof EntityPlayer) { + target.attackEntityFrom(DamageSource.causePlayerDamage((EntityPlayer) owner), damage); + } else if (owner instanceof EntityLivingBase) { + target.attackEntityFrom(DamageSource.causeMobDamage((EntityLivingBase) owner), damage); + } else { + target.attackEntityFrom(new NpcDamageSource("npc_ability", null), damage); + } + } finally { + Ability.restoreHurtResistanceIfNeeded(target, ignoreIFrames, previousHurtResistantTime); } // Small upward knockback to help escape diff --git a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyAbility.java b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyAbility.java index 86bae44b3..4983b59b2 100644 --- a/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyAbility.java +++ b/src/main/java/noppes/npcs/scripted/entity/ScriptEnergyAbility.java @@ -120,4 +120,12 @@ public abstract class ScriptEnergyAbility extends public float getChargeProgress() { return entity.getChargeProgress(); } + + public boolean isIgnoreIFrames() { + return entity.isIgnoreIFrames(); + } + + public void setIgnoreIFrames(boolean ignore) { + entity.setIgnoreIFrames(ignore); + } } diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index 4fc5f8aa7..e7683fc77 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -1650,7 +1650,7 @@ ability.particleMotion.sparks=Sparks ability.section.zone=Zone Placement ability.telegraphSize=Telegraph Size ability.coneAngle=Cone Angle -ability.ignoreInvulnFrames=Ignore Invuln Frames +ability.ignoreIFrames=Ignore I-Frames ability.visible=Visible # Visual Layer Section Headers @@ -2051,6 +2051,7 @@ ability.hover.lockMovement=Prevents NPC pathfinding during the selected phases ( ability.hover.rotationMode=Free: normal AI rotation. Locked: freeze facing direction. Track: force face target for precise aiming. ability.hover.rotationPhase=When the rotation mode applies (Windup, Active, or Both) ability.hover.interruptible=If enabled, ability can be interrupted by taking damage +ability.hover.ignoreIFrames=Temporarily clears target hurt resistance before applying ability damage, then restores it. ability.hover.dazed=Stun duration in ticks if ability is interrupted (20 ticks = 1 second) ability.hover.windup=Charge-up time before ability activates (20 ticks = 1 second) ability.hover.cooldown=Time before ability can be used again (20 ticks = 1 second)