diff --git a/src/api b/src/api index 7f95b90fc..34a7ea020 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit 7f95b90fc2ff1238df3e902fd55c804ffbdaacbc +Subproject commit 34a7ea020b380358c5f64a2e6d371f343942c6ef diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/ProjectileData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/ProjectileData.java index acb16a2a9..a54f526ee 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/ProjectileData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/ProjectileData.java @@ -26,10 +26,12 @@ public class ProjectileData { * If colorOverride is true, returns a copy with overridden inner/outer colors. */ public EnergyDisplayData resolveDisplay(EnergyDisplayData primary) { - if (!colorOverride) return primary; + // Always return a copy so entity mutations never bleed back into the ability template. EnergyDisplayData resolved = primary.copy(); - resolved.innerColor = this.innerColor; - resolved.outerColor = this.outerColor; + if (colorOverride) { + resolved.innerColor = this.innerColor; + resolved.outerColor = this.outerColor; + } return resolved; } diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java index e62a52232..a3f5f874a 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java @@ -147,7 +147,7 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { // Beam-specific properties from homing data - this.homingData = homing; + this.homingData = homing != null ? homing.copy() : new EnergyHomingData(); this.beamWidth = beamWidth; this.headSize = headSize; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java index 56b3c6b7d..44ba4c69e 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java @@ -64,7 +64,7 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { // Initialize base properties initProjectile(owner, target, x, y, z, 1.0f, display, combat, lightning, lifespan); - this.homingData = homing; + this.homingData = homing != null ? homing.copy() : new EnergyHomingData(); this.boomerang = boomerang; this.boomerangDelay = boomerangDelay; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityDome.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityDome.java index 53bfe8358..383f07246 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityDome.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityDome.java @@ -50,10 +50,11 @@ public class EntityAbilityDome extends EntityAbilityBarrier { this.stepHeight = 0.0F; this.ownerEntityId = owner.getEntityId(); this.setDomeRadius(domeRadius); - this.displayData = display; - this.lightningData = lightning; - this.barrierData = barrier; - this.currentHealth = barrier.maxHealth; + // Defensive copy: entities must never share data objects with the source ability. + this.displayData = display != null ? display.copy() : new EnergyDisplayData(); + this.lightningData = lightning != null ? lightning.copy() : new EnergyLightningData(); + this.barrierData = barrier != null ? barrier.copy() : new EnergyBarrierData(); + this.currentHealth = this.barrierData.maxHealth; this.setPosition(x, y, z); } diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java index a7f4f2af9..f7b9cd7e7 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java @@ -38,7 +38,7 @@ public class EntityAbilityOrb extends EntityEnergyProjectile { // Initialize base properties initProjectile(owner, target, x, y, z, orbSize, display, combat, lightning, lifespan); - this.homingData = homing; + this.homingData = homing != null ? homing.copy() : new EnergyHomingData(); // Calculate initial velocity toward target calculateInitialVelocity(owner, target, x, y, z); diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java index b34502e8a..200fe922b 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityPanel.java @@ -56,11 +56,12 @@ public class EntityAbilityPanel extends EntityAbilityBarrier { this.panelYaw = yaw; this.prevPanelYaw = yaw; this.mode = mode; - this.displayData = display; - this.lightningData = lightning; - this.barrierData = barrier; - this.panelData = panel; - this.currentHealth = barrier.maxHealth; + // Defensive copy: entities must never share data objects with the source ability. + this.displayData = display != null ? display.copy() : new EnergyDisplayData(); + this.lightningData = lightning != null ? lightning.copy() : new EnergyLightningData(); + this.barrierData = barrier != null ? barrier.copy() : new EnergyBarrierData(); + this.panelData = panel != null ? panel.copy() : new EnergyPanelData(); + this.currentHealth = this.barrierData.maxHealth; // For launched panels, set initial velocity if (mode == PanelMode.LAUNCHED) { diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java b/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java index 65cb1ce98..f03324618 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java @@ -79,7 +79,7 @@ public class EntityAbilitySweeper extends EntityEnergyAbility { this.beamLength = beamLength; this.beamWidth = beamWidth; this.beamHeight = beamHeight; - this.displayData = displayData; + this.displayData = displayData != null ? displayData.copy() : new EnergyDisplayData(); this.sweepSpeed = sweepSpeed; this.numberOfRotations = numberOfRotations; this.damage = damage; diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index ff7a66c5e..bec0da39e 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -183,10 +183,13 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { // Visual this.size = size; - this.displayData = display; - this.combatData = combat; - this.lifespanData = lifespan; // deathWorldTime will be set on first tick when world is available - this.lightningData = lightning; + // Defensive copy: entities must never share data objects with the source ability. + // Without copies, runtime mutations (e.g. barrier reflection changing colors/damage) + // bleed back into the ability template and corrupt future projectiles. + this.displayData = display != null ? display.copy() : new EnergyDisplayData(); + this.combatData = combat != null ? combat.copy() : new EnergyCombatData(); + this.lifespanData = lifespan != null ? lifespan.copy() : new EnergyLifespanData(); + this.lightningData = lightning != null ? lightning.copy() : new EnergyLightningData(); syncProjectileColorWatchers(); // Initialize render size @@ -911,7 +914,15 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { * Set the effects list from the ability's configured effects. */ public void setEffects(List effects) { - this.effects = effects != null ? effects : new ArrayList<>(); + if (effects == null || effects.isEmpty()) { + this.effects = new ArrayList<>(); + } else { + // Deep copy: entity must not share the ability's effects list or its entries. + this.effects = new ArrayList<>(effects.size()); + for (AbilityPotionEffect effect : effects) { + this.effects.add(effect.copy()); + } + } } protected void doExplosion() { @@ -1518,8 +1529,8 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { int chargeDuration) { this.setPreviewMode(true); this.setPreviewOwner(owner); - this.displayData = display; - this.lightningData = lightning; + this.displayData = display != null ? display.copy() : new EnergyDisplayData(); + this.lightningData = lightning != null ? lightning.copy() : new EnergyLightningData(); setupChargingState(anchor, chargeDuration); } diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java b/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java index 8a6d1cb96..96f53f8ca 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java @@ -43,7 +43,7 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { super(world); initProjectile(owner, target, x, y, z, sliceWidth, display, combat, lightning, lifespan); - this.homingData = homing; + this.homingData = homing != null ? homing.copy() : new EnergyHomingData(); this.sliceWidth = sliceWidth; this.sliceThickness = sliceThickness; diff --git a/src/main/java/noppes/npcs/scripted/event/AbilityEvent.java b/src/main/java/noppes/npcs/scripted/event/AbilityEvent.java index c7a43cfe7..769add405 100644 --- a/src/main/java/noppes/npcs/scripted/event/AbilityEvent.java +++ b/src/main/java/noppes/npcs/scripted/event/AbilityEvent.java @@ -18,6 +18,7 @@ import noppes.npcs.scripted.NpcAPI; public class AbilityEvent extends NpcEvent implements IAbilityEvent { protected final Ability ability; protected final IEntityLivingBase target; + private transient IAbility abilityCopy; public AbilityEvent(ICustomNpc npc, Ability ability, EntityLivingBase target) { super(npc); @@ -27,7 +28,10 @@ public class AbilityEvent extends NpcEvent implements IAbilityEvent { @Override public IAbility getAbility() { - return ability; + if (abilityCopy == null && ability != null) { + abilityCopy = ability.deepCopy(); + } + return abilityCopy; } @Override diff --git a/src/main/java/noppes/npcs/scripted/event/player/PlayerAbilityEvent.java b/src/main/java/noppes/npcs/scripted/event/player/PlayerAbilityEvent.java index e5d46fd15..95bda90e6 100644 --- a/src/main/java/noppes/npcs/scripted/event/player/PlayerAbilityEvent.java +++ b/src/main/java/noppes/npcs/scripted/event/player/PlayerAbilityEvent.java @@ -18,6 +18,7 @@ import noppes.npcs.scripted.NpcAPI; public class PlayerAbilityEvent extends PlayerEvent implements IPlayerAbilityEvent { protected final Ability ability; protected final IEntityLivingBase target; + private transient IAbility abilityCopy; public PlayerAbilityEvent(IPlayer player, Ability ability, EntityLivingBase target) { super(player); @@ -27,7 +28,10 @@ public class PlayerAbilityEvent extends PlayerEvent implements IPlayerAbilityEve @Override public IAbility getAbility() { - return ability; + if (abilityCopy == null && ability != null) { + abilityCopy = ability.deepCopy(); + } + return abilityCopy; } @Override diff --git a/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java b/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java index 8e0bda906..d475c40ef 100644 --- a/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java +++ b/src/main/java/noppes/npcs/scripted/wrapper/ScriptDataAbilities.java @@ -36,7 +36,12 @@ public class ScriptDataAbilities implements IDataAbilities { @Override public IAbility[] getAbilities() { - return data.getAbilities().toArray(new IAbility[0]); + List live = data.getAbilities(); + IAbility[] copies = new IAbility[live.size()]; + for (int i = 0; i < live.size(); i++) { + copies[i] = live.get(i).deepCopy(); + } + return copies; } @Override @@ -73,7 +78,8 @@ public class ScriptDataAbilities implements IDataAbilities { @Override public IAbility getAbility(String abilityId) { - return data.getAbility(abilityId); + Ability a = data.getAbility(abilityId); + return a != null ? a.deepCopy() : null; } @Override @@ -115,6 +121,17 @@ public class ScriptDataAbilities implements IDataAbilities { @Override public IAbility getCurrentAbility() { + Ability a = data.getCurrentAbility(); + return a != null ? a.deepCopy() : null; + } + + @Override + public IAbility getSourceAbility(String abilityId) { + return data.getAbility(abilityId); + } + + @Override + public IAbility getSourceCurrentAbility() { return data.getCurrentAbility(); } diff --git a/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java b/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java index 70d915537..1860aa259 100644 --- a/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java +++ b/src/main/java/noppes/npcs/scripted/wrapper/ScriptPlayerAbilityData.java @@ -85,7 +85,8 @@ public class ScriptPlayerAbilityData implements IPlayerAbilityData { @Override public IAbility getCurrentAbility() { - return data.getCurrentAbility(); + Ability a = data.getCurrentAbility(); + return a != null ? a.deepCopy() : null; } @Override