From f7d0fbcf334f2b8773e5bff467680c3e84e15c58 Mon Sep 17 00:00:00 2001 From: Kam Date: Thu, 19 Feb 2026 19:27:56 -0500 Subject: [PATCH] Rendering Fixes and Logic Fixes for all Energy Abilities --- .../client/renderer/RenderAbilityBeam.java | 4 +- .../client/renderer/RenderAbilityLaser.java | 18 ++-- .../client/renderer/RenderAbilitySweeper.java | 20 +++-- .../client/renderer/RenderAbilityZone.java | 6 +- .../client/renderer/RenderEnergyDome.java | 5 +- .../lightning/AttachedLightningRenderer.java | 9 +- .../renderer/lightning/LightningBolt.java | 2 +- .../data/ability/data/EnergyBarrierData.java | 2 +- .../data/ability/data/EnergyCombatData.java | 19 +++-- .../data/ability/data/EnergyDisplayData.java | 12 +-- .../data/ability/data/EnergyHomingData.java | 13 +-- .../data/ability/data/EnergyLifespanData.java | 4 +- .../ability/data/EnergyLightningData.java | 11 +-- .../ability/data/EnergyTrajectoryData.java | 1 + .../npcs/entity/EntityAbilityDisc.java | 2 + .../npcs/entity/EntityAbilityLaser.java | 9 +- .../npcs/entity/EntityAbilitySweeper.java | 52 ++++++++++-- .../npcs/entity/EntityAbilityZone.java | 33 ++++---- .../npcs/entity/EntityEnergyBarrier.java | 1 + .../kamkeel/npcs/entity/EntityEnergyDome.java | 83 +++++++++++++++---- .../npcs/entity/EntityEnergyPanel.java | 48 ++++++++++- .../npcs/entity/EntityEnergyProjectile.java | 27 +++--- .../npcs/entity/EntityEnergySlicer.java | 4 +- 23 files changed, 276 insertions(+), 109 deletions(-) diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java index deced23a0..8f87ff331 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java @@ -262,8 +262,8 @@ public class RenderAbilityBeam extends RenderEnergyAbility { // Calculate alpha based on age (older = more faded) float age1 = i < ages.size() ? ages.get(i) : 0; float age2 = (i + 1) < ages.size() ? ages.get(i + 1) : 0; - float fade1 = 1.0f - Math.min(1.0f, age1 / fadeTime); - float fade2 = 1.0f - Math.min(1.0f, age2 / fadeTime); + float fade1 = fadeTime > 0 ? (1.0f - Math.min(1.0f, age1 / fadeTime)) : 0.0f; + float fade2 = fadeTime > 0 ? (1.0f - Math.min(1.0f, age2 / fadeTime)) : 0.0f; float alpha1 = baseAlpha * fade1; float alpha2 = baseAlpha * fade2; diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java index d24bc1bc9..e38764bfb 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java @@ -8,6 +8,9 @@ import net.minecraft.client.renderer.Tessellator; import net.minecraft.entity.Entity; import org.lwjgl.opengl.GL11; +import java.util.HashMap; +import java.util.Map; + /** * Renders the AbilityLaser entity as a rectangular beam with clear inner/outer colors. *

@@ -16,6 +19,8 @@ import org.lwjgl.opengl.GL11; @SideOnly(Side.CLIENT) public class RenderAbilityLaser extends RenderEnergyAbility { + private final Map tipLightningStates = new HashMap<>(); + @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { EntityAbilityLaser laser = (EntityAbilityLaser) entity; @@ -218,13 +223,16 @@ public class RenderAbilityLaser extends RenderEnergyAbility { /** * Get or create the tip lightning state for the laser. - * Stored as a secondary object in lightningState2. + * Persisted per entity ID so arcs accumulate and fade properly across frames. */ private AttachedLightningRenderer.LightningState getTipLightningState(EntityAbilityLaser laser) { - // Use a simple approach - store in a transient field - // Since lightningState is already used, we'll create a new state each time - // This works because we're updating and rendering immediately - return new AttachedLightningRenderer.LightningState(); + int id = laser.getEntityId(); + AttachedLightningRenderer.LightningState state = tipLightningStates.get(id); + if (state == null) { + state = new AttachedLightningRenderer.LightningState(); + tipLightningStates.put(id, state); + } + return state; } /** diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilitySweeper.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilitySweeper.java index 2dbeac3c0..b72be7a7c 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilitySweeper.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilitySweeper.java @@ -70,6 +70,7 @@ public class RenderAbilitySweeper extends Render { /** * Render beam using the same style as the Beam entity trail. * Three layers: outer glow, middle, inner core. + * outerColorWidth is an additive offset from inner width (consistent with other renderers). */ private void renderBeam(float length, float width, int innerColor, int outerColor, boolean outerColorEnabled, float outerColorWidth) { @@ -82,21 +83,28 @@ public class RenderAbilitySweeper extends Render { float innerG = ((innerColor >> 8) & 0xFF) / 255.0f; float innerB = (innerColor & 0xFF) / 255.0f; + // Inner scale defines the core width (consistent with other renderers) + float innerScale = 0.6f; + float innerWidth = width * innerScale; + // Render from origin (0,0,0) to (0,0,length) in local coords // After rotation this extends in the correct world direction - // Outer glow (wider, translucent) - like beam trail - only if enabled + // Outer glow (wider, translucent) - only if enabled + // outerColorWidth is additive offset from inner width if (outerColorEnabled) { + float outerWidth = innerWidth + outerColorWidth * width; GL11.glDepthMask(false); - renderBeamSegment(0, 0, 0, 0, 0, length, width * outerColorWidth, outerR, outerG, outerB, 0.3f); + renderBeamSegment(0, 0, 0, 0, 0, length, outerWidth, outerR, outerG, outerB, 0.3f); GL11.glDepthMask(true); - // Middle layer - renderBeamSegment(0, 0, 0, 0, 0, length, width * 1.3f, outerR, outerG, outerB, 0.6f); + // Middle layer (halfway between inner and outer) + float midWidth = innerWidth + (outerWidth - innerWidth) * 0.5f; + renderBeamSegment(0, 0, 0, 0, 0, length, midWidth, outerR, outerG, outerB, 0.6f); } - // Inner core (solid) - like beam trail inner - renderBeamSegment(0, 0, 0, 0, 0, length, width * 0.6f, innerR, innerG, innerB, 1.0f); + // Inner core (solid) + renderBeamSegment(0, 0, 0, 0, 0, length, innerWidth, innerR, innerG, innerB, 1.0f); } /** diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityZone.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityZone.java index ec21a268a..20d31c56d 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityZone.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityZone.java @@ -210,7 +210,7 @@ public class RenderAbilityZone extends Render { if (c.accents) { float aa = 0.5f * c.brightness * (0.3f + 0.2f * sin(c.pulse * 0.7f)); - AccentStyle style = AccentStyle.values()[Math.min(c.accentStyle, AccentStyle.values().length - 1)]; + AccentStyle style = AccentStyle.values()[Math.max(0, Math.min(c.accentStyle, AccentStyle.values().length - 1))]; switch (style) { case SWAYING: renderSwayingAccents(c, c.ir, c.ig, c.ib, aa, c.height); @@ -314,7 +314,7 @@ public class RenderAbilityZone extends Render { private void spawnDefaultParticles(Ctx c) { int count = Math.round(3 * c.particleDensity); - ParticleMotion motion = ParticleMotion.values()[Math.min(c.particleMotion, ParticleMotion.values().length - 1)]; + ParticleMotion motion = ParticleMotion.values()[Math.max(0, Math.min(c.particleMotion, ParticleMotion.values().length - 1))]; for (int i = 0; i < count; i++) { double[] pos = randomPosInZone(c); double mx, my, mz; @@ -350,7 +350,7 @@ public class RenderAbilityZone extends Render { } private double[] getMotionForStyle(int motionOrdinal) { - ParticleMotion motion = ParticleMotion.values()[Math.min(motionOrdinal, ParticleMotion.values().length - 1)]; + ParticleMotion motion = ParticleMotion.values()[Math.max(0, Math.min(motionOrdinal, ParticleMotion.values().length - 1))]; switch (motion) { case DRIFTING: return new double[]{ diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java index 8653bb27d..871d48461 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDome.java @@ -42,7 +42,6 @@ public class RenderEnergyDome extends RenderEnergyBarrier { float outerAlpha = dome.getOuterColorAlpha() * healthPercent; GL11.glDepthMask(false); renderSphere(dome.getOuterColor(), outerAlpha + flashAlpha, radius * outerScale, 8); - GL11.glDepthMask(true); } // TODO: Skybox Feature @@ -50,9 +49,9 @@ public class RenderEnergyDome extends RenderEnergyBarrier { // renderSkyboxSphere(dome.getSkyboxTexture(), radius, radius * 0.94f, 32, x, y, z); // } - // Render inner dome + GL11.glDepthMask(true); + // Render inner dome (depth write enabled so inner sphere properly occludes) renderSphere(dome.getInnerColor(), dome.getInnerAlpha(), radius * innerScale, 8); - GL11.glPopMatrix(); restoreRenderState(); diff --git a/src/main/java/kamkeel/npcs/client/renderer/lightning/AttachedLightningRenderer.java b/src/main/java/kamkeel/npcs/client/renderer/lightning/AttachedLightningRenderer.java index 5e9e2342b..4debb90d6 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/lightning/AttachedLightningRenderer.java +++ b/src/main/java/kamkeel/npcs/client/renderer/lightning/AttachedLightningRenderer.java @@ -3,7 +3,6 @@ package kamkeel.npcs.client.renderer.lightning; import net.minecraft.client.renderer.Tessellator; import org.lwjgl.opengl.GL11; -import java.awt.Color; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -37,6 +36,7 @@ public class AttachedLightningRenderer { public float getAlphaMultiplier() { // Fade out over lifetime + if (maxAge <= 0) return 0.0f; return 1.0f - ((float) age / (float) maxAge); } @@ -208,10 +208,9 @@ public class AttachedLightningRenderer { private static void renderBoltPath(Tessellator tess, List points, int color, float width, float alpha) { if (alpha <= 0.01f) return; - Color c = new Color(color); - int r = c.getRed(); - int g = c.getGreen(); - int b = c.getBlue(); + int r = (color >> 16) & 0xFF; + int g = (color >> 8) & 0xFF; + int b = color & 0xFF; int a = Math.min(255, Math.max(0, (int) (alpha * 255))); for (int i = 0; i < points.size() - 1; i++) { diff --git a/src/main/java/kamkeel/npcs/client/renderer/lightning/LightningBolt.java b/src/main/java/kamkeel/npcs/client/renderer/lightning/LightningBolt.java index e077189ab..beea63fd3 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/lightning/LightningBolt.java +++ b/src/main/java/kamkeel/npcs/client/renderer/lightning/LightningBolt.java @@ -56,7 +56,7 @@ public class LightningBolt { this.numSegments0 = 1; this.length = end.copy().subtract(start).mag(); - this.particleMaxAge = FADE_TIME + rand.nextInt(FADE_TIME) - FADE_TIME / 2; + this.particleMaxAge = Math.max(1, FADE_TIME + rand.nextInt(FADE_TIME) - FADE_TIME / 2); this.particleAge = -(int) (length * speed); segments.add(new Segment(start, end)); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java index f31466329..ef9bcff57 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyBarrierData.java @@ -78,7 +78,7 @@ public class EnergyBarrierData { } public void setMaxHealth(float maxHealth) { - this.maxHealth = Math.max(0, maxHealth); + this.maxHealth = Math.max(1.0f, maxHealth); } public boolean isUseHealth() { diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyCombatData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyCombatData.java index 2ecbf7cc5..ad3d603e5 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyCombatData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyCombatData.java @@ -75,7 +75,7 @@ public class EnergyCombatData implements IEnergyCombatData { @Override public void setExplosionRadius(float explosionRadius) { - this.explosionRadius = explosionRadius; + this.explosionRadius = Math.max(0, explosionRadius); } @Override @@ -98,12 +98,17 @@ public class EnergyCombatData implements IEnergyCombatData { } public void readNBT(NBTTagCompound nbt) { - damage = nbt.getFloat("damage"); - knockback = nbt.getFloat("knockback"); - knockbackUp = nbt.getFloat("knockbackUp"); - explosive = nbt.getBoolean("explosive"); - explosionRadius = nbt.getFloat("explosionRadius"); - explosionDamageFalloff = nbt.getFloat("explosionDamageFalloff"); + damage = nbt.hasKey("damage") ? nbt.getFloat("damage") : 7.0f; + knockback = nbt.hasKey("knockback") ? nbt.getFloat("knockback") : 1.0f; + knockbackUp = nbt.hasKey("knockbackUp") ? nbt.getFloat("knockbackUp") : 0.1f; + explosive = nbt.hasKey("explosive") && nbt.getBoolean("explosive"); + explosionRadius = nbt.hasKey("explosionRadius") ? nbt.getFloat("explosionRadius") : 3.0f; + explosionDamageFalloff = nbt.hasKey("explosionDamageFalloff") ? nbt.getFloat("explosionDamageFalloff") : 0.5f; + + // Sanitize + if (Float.isNaN(damage) || Float.isInfinite(damage)) damage = 7.0f; + if (Float.isNaN(knockback) || Float.isInfinite(knockback) || knockback < 0) knockback = 1.0f; + if (Float.isNaN(explosionRadius) || Float.isInfinite(explosionRadius) || explosionRadius < 0) explosionRadius = 3.0f; } public EnergyCombatData copy() { diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyDisplayData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyDisplayData.java index 3b0998602..6363a69cb 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyDisplayData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyDisplayData.java @@ -128,13 +128,13 @@ public class EnergyDisplayData implements IEnergyDisplayData { } public void readNBT(NBTTagCompound nbt) { - innerColor = nbt.getInteger("innerColor"); - outerColor = nbt.getInteger("outerColor"); - outerColorEnabled = nbt.getBoolean("outerColorEnabled"); - outerColorWidth = nbt.getFloat("outerColorWidth"); - outerColorAlpha = nbt.getFloat("outerColorAlpha"); + innerColor = nbt.hasKey("innerColor") ? nbt.getInteger("innerColor") : 0xFFFFFF; + outerColor = nbt.hasKey("outerColor") ? nbt.getInteger("outerColor") : 0x8888FF; + outerColorEnabled = !nbt.hasKey("outerColorEnabled") || nbt.getBoolean("outerColorEnabled"); + outerColorWidth = nbt.hasKey("outerColorWidth") ? nbt.getFloat("outerColorWidth") : 0.4f; + outerColorAlpha = nbt.hasKey("outerColorAlpha") ? nbt.getFloat("outerColorAlpha") : 0.5f; innerAlpha = nbt.hasKey("innerAlpha") ? nbt.getFloat("innerAlpha") : 1.0f; - rotationSpeed = nbt.getFloat("rotationSpeed"); + rotationSpeed = nbt.hasKey("rotationSpeed") ? nbt.getFloat("rotationSpeed") : 4.0f; // TODO: Skybox Feature // skyboxEnabled = nbt.hasKey("skyboxEnabled") && nbt.getBoolean("skyboxEnabled"); // skyboxTexture = nbt.hasKey("skyboxTexture") ? nbt.getString("skyboxTexture") : ""; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyHomingData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyHomingData.java index a03e70525..4f75c0841 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyHomingData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyHomingData.java @@ -31,7 +31,7 @@ public class EnergyHomingData implements IEnergyHomingData { @Override public void setSpeed(float speed) { - this.speed = speed; + this.speed = Math.max(0.01f, speed); } @Override @@ -72,10 +72,13 @@ public class EnergyHomingData implements IEnergyHomingData { } public void readNBT(NBTTagCompound nbt) { - speed = nbt.getFloat("speed"); - homing = nbt.getBoolean("homing"); - homingStrength = nbt.getFloat("homingStrength"); - homingRange = nbt.getFloat("homingRange"); + speed = nbt.hasKey("speed") ? nbt.getFloat("speed") : 0.5f; + homing = !nbt.hasKey("homing") || nbt.getBoolean("homing"); + homingStrength = nbt.hasKey("homingStrength") ? nbt.getFloat("homingStrength") : 0.15f; + homingRange = nbt.hasKey("homingRange") ? nbt.getFloat("homingRange") : 20.0f; + + // Sanitize speed to prevent stuck projectiles + if (Float.isNaN(speed) || Float.isInfinite(speed) || speed <= 0) speed = 0.5f; } public EnergyHomingData copy() { diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLifespanData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLifespanData.java index a0e6835f6..2c8d1f6a1 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLifespanData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLifespanData.java @@ -26,7 +26,7 @@ public class EnergyLifespanData implements IEnergyLifespanData { @Override public void setMaxDistance(float maxDistance) { - this.maxDistance = maxDistance; + this.maxDistance = Float.isNaN(maxDistance) || maxDistance <= 0 ? 150.0f : maxDistance; } @Override @@ -36,7 +36,7 @@ public class EnergyLifespanData implements IEnergyLifespanData { @Override public void setMaxLifetime(int maxLifetime) { - this.maxLifetime = maxLifetime; + this.maxLifetime = maxLifetime <= 0 ? 200 : maxLifetime; } public void writeNBT(NBTTagCompound nbt) { diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLightningData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLightningData.java index 1eb2b001a..b8423e7aa 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLightningData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyLightningData.java @@ -61,7 +61,7 @@ public class EnergyLightningData implements IEnergyLightningData { @Override public void setLightningFadeTime(int lightningFadeTime) { - this.lightningFadeTime = lightningFadeTime; + this.lightningFadeTime = Math.max(1, lightningFadeTime); } public void writeNBT(NBTTagCompound nbt) { @@ -72,10 +72,11 @@ public class EnergyLightningData implements IEnergyLightningData { } public void readNBT(NBTTagCompound nbt) { - lightningEffect = nbt.getBoolean("lightningEffect"); - lightningDensity = nbt.getFloat("lightningDensity"); - lightningRadius = nbt.getFloat("lightningRadius"); - lightningFadeTime = nbt.getInteger("lightningFadeTime"); + lightningEffect = nbt.hasKey("lightningEffect") && nbt.getBoolean("lightningEffect"); + lightningDensity = nbt.hasKey("lightningDensity") ? nbt.getFloat("lightningDensity") : 0.15f; + lightningRadius = nbt.hasKey("lightningRadius") ? nbt.getFloat("lightningRadius") : 0.5f; + lightningFadeTime = nbt.hasKey("lightningFadeTime") ? nbt.getInteger("lightningFadeTime") : 6; + if (lightningFadeTime <= 0) lightningFadeTime = 1; } public EnergyLightningData copy() { diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyTrajectoryData.java b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyTrajectoryData.java index ea7735e11..10014fb5f 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyTrajectoryData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/data/EnergyTrajectoryData.java @@ -187,6 +187,7 @@ public class EnergyTrajectoryData implements IEnergyTrajectoryData { public void readNBT(NBTTagCompound nbt) { this.currentPath = nbt.getInteger("currentPath"); + trajectory.clear(); int i = 0; while (nbt.hasKey("Path_" + i, Constants.NBT.TAG_COMPOUND)) { diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java index 1f6b8ba0c..d16fc7709 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java @@ -118,6 +118,8 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { this.discRadius = 0.01f; this.discThickness = 0.01f; this.size = 0.01f; + this.renderCurrentSize = 0.01f; + this.prevRenderSize = 0.01f; // Initial position at anchor point Vec3 pos = AnchorPointHelper.calculateAnchorPosition(owner, anchorData); diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java index 08aeb24d7..38808cad9 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java @@ -466,7 +466,8 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { */ public float getLingerAlpha() { if (!fullyExtended) return 1.0f; - return 1.0f - ((float) ticksSinceFullExtension / lingerTicks); + if (lingerTicks <= 0) return 0.0f; + return Math.max(0.0f, 1.0f - ((float) ticksSinceFullExtension / lingerTicks)); } // ==================== NBT ==================== @@ -475,8 +476,12 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { protected void readProjectileNBT(NBTTagCompound nbt) { this.laserWidth = sanitize(nbt.hasKey("LaserWidth") ? nbt.getFloat("LaserWidth") : 0.2f, 0.2f, MAX_ENTITY_SIZE); this.expansionSpeed = nbt.hasKey("ExpansionSpeed") ? nbt.getFloat("ExpansionSpeed") : 2.0f; + if (Float.isNaN(expansionSpeed) || Float.isInfinite(expansionSpeed) || expansionSpeed <= 0) expansionSpeed = 2.0f; this.lingerTicks = nbt.hasKey("LingerTicks") ? nbt.getInteger("LingerTicks") : 10; + if (lingerTicks <= 0) lingerTicks = 1; this.dieOnImpact = nbt.getBoolean("DieOnImpact"); + this.lockVerticalDirection = nbt.getBoolean("LockVerticalDir"); + this.ticksSinceFullExtension = nbt.getInteger("TicksSinceExtended"); this.dirX = nbt.getDouble("DirX"); this.dirY = nbt.getDouble("DirY"); this.dirZ = nbt.getDouble("DirZ"); @@ -495,6 +500,8 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { nbt.setFloat("ExpansionSpeed", expansionSpeed); nbt.setInteger("LingerTicks", lingerTicks); nbt.setBoolean("DieOnImpact", dieOnImpact); + nbt.setBoolean("LockVerticalDir", lockVerticalDirection); + nbt.setInteger("TicksSinceExtended", ticksSinceFullExtension); nbt.setDouble("DirX", dirX); nbt.setDouble("DirY", dirY); nbt.setDouble("DirZ", dirZ); diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java b/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java index af4616654..fd739876d 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java @@ -12,6 +12,9 @@ import net.minecraft.util.DamageSource; import net.minecraft.world.World; import noppes.npcs.LogWriter; import noppes.npcs.NpcDamageSource; +import noppes.npcs.controllers.PartyController; +import noppes.npcs.controllers.data.Party; +import noppes.npcs.controllers.data.PlayerData; import noppes.npcs.entity.EntityNPCInterface; import java.util.HashSet; @@ -160,9 +163,7 @@ public class EntityAbilitySweeper extends EntityEnergyAbility { if (currentAngle >= 360) { currentAngle -= 360; completedRotations++; - if (!worldObj.isRemote) { - LogWriter.info("[Sweeper] Completed rotation " + completedRotations + "/" + numberOfRotations); - } + // Rotation completed } // Server handles damage @@ -216,6 +217,7 @@ public class EntityAbilitySweeper extends EntityEnergyAbility { if (!(entity instanceof EntityLivingBase)) continue; if (entity == owner) continue; if (hitThisTick.contains(entity.getEntityId())) continue; + if (shouldIgnoreEntity((EntityLivingBase) entity, owner)) continue; EntityLivingBase livingEntity = (EntityLivingBase) entity; @@ -264,6 +266,32 @@ public class EntityAbilitySweeper extends EntityEnergyAbility { return entityFeetY < beamTopY; } + /** + * Check if an entity should be ignored for sweep damage. + * Same logic as EntityEnergyProjectile.shouldIgnoreEntity but adapted for sweeper. + */ + private boolean shouldIgnoreEntity(EntityLivingBase entity, Entity owner) { + if (entity instanceof EntityNPCInterface) { + EntityNPCInterface targetNpc = (EntityNPCInterface) entity; + if (targetNpc.faction.isPassive) return true; + if (owner instanceof EntityNPCInterface) { + if (((EntityNPCInterface) owner).faction.id == targetNpc.faction.id) return true; + } + if (owner instanceof EntityPlayer) { + if (targetNpc.faction.isFriendlyToPlayer((EntityPlayer) owner)) return true; + } + } + if (owner instanceof EntityPlayer && entity instanceof EntityPlayer) { + PlayerData ownerData = PlayerData.get((EntityPlayer) owner); + PlayerData targetData = PlayerData.get((EntityPlayer) entity); + if (ownerData.partyUUID != null && ownerData.partyUUID.equals(targetData.partyUUID)) { + Party party = PartyController.Instance().getParty(ownerData.partyUUID); + if (party != null && !party.friendlyFire()) return true; + } + } + return false; + } + private void applyDamage(EntityLivingBase target, Entity owner) { if (owner instanceof EntityNPCInterface) { target.attackEntityFrom(new NpcDamageSource("npc_ability", (EntityNPCInterface) owner), damage); @@ -339,17 +367,23 @@ public class EntityAbilitySweeper extends EntityEnergyAbility { this.beamLength = sanitize(nbt.getFloat("BeamLength"), 10.0f, MAX_ENTITY_SIZE); this.beamWidth = sanitize(nbt.getFloat("BeamWidth"), 0.3f, MAX_ENTITY_SIZE); this.beamHeight = sanitize(nbt.getFloat("BeamHeight"), 0.8f, MAX_ENTITY_SIZE); - this.sweepSpeed = nbt.getFloat("SweepSpeed"); - this.numberOfRotations = nbt.getInteger("NumRotations"); + this.sweepSpeed = nbt.hasKey("SweepSpeed") ? nbt.getFloat("SweepSpeed") : 3.0f; + if (Float.isNaN(sweepSpeed) || Float.isInfinite(sweepSpeed) || sweepSpeed <= 0) sweepSpeed = 3.0f; + this.numberOfRotations = nbt.hasKey("NumRotations") ? nbt.getInteger("NumRotations") : 2; + if (numberOfRotations <= 0) numberOfRotations = 1; this.completedRotations = nbt.getInteger("CompletedRotations"); - this.maxTicks = nbt.getInteger("MaxTicks"); + this.maxTicks = nbt.hasKey("MaxTicks") ? nbt.getInteger("MaxTicks") : 400; + if (maxTicks <= 0) maxTicks = 400; this.targetEntityId = nbt.getInteger("TargetId"); this.currentAngle = nbt.getFloat("CurrentAngle"); + this.prevAngle = currentAngle; this.baseYaw = nbt.getFloat("BaseYaw"); this.deathWorldTime = nbt.getLong("DeathWorldTime"); - this.damage = nbt.getFloat("Damage"); - this.damageInterval = nbt.getInteger("DamageInterval"); - this.piercing = nbt.getBoolean("Piercing"); + this.damage = nbt.hasKey("Damage") ? nbt.getFloat("Damage") : 5.0f; + if (Float.isNaN(damage) || Float.isInfinite(damage)) damage = 5.0f; + this.damageInterval = nbt.hasKey("DamageInterval") ? nbt.getInteger("DamageInterval") : 5; + if (damageInterval <= 0) damageInterval = 1; + this.piercing = !nbt.hasKey("Piercing") || nbt.getBoolean("Piercing"); this.lockOnTarget = nbt.getBoolean("LockOnTarget"); } diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java index 57445f8fc..a3aa494b4 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityZone.java @@ -716,7 +716,8 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD @Override protected void readEntityFromNBT(NBTTagCompound nbt) { - this.zoneType = ZoneType.values()[nbt.getInteger("ZoneType")]; + int zoneTypeOrd = nbt.getInteger("ZoneType"); + this.zoneType = (zoneTypeOrd >= 0 && zoneTypeOrd < ZoneType.values().length) ? ZoneType.values()[zoneTypeOrd] : ZoneType.TRAP; try { this.shape = ZoneShape.valueOf(nbt.getString("ShapeName")); } catch (Exception e) { @@ -724,17 +725,17 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD } this.ownerEntityId = nbt.getInteger("OwnerId"); - this.radius = nbt.getFloat("Radius"); - this.durationTicks = nbt.getInteger("Duration"); - this.maxTicks = nbt.getInteger("MaxTicks"); - this.innerColor = nbt.getInteger("InnerColor"); - this.outerColor = nbt.getInteger("OuterColor"); - this.outerColorEnabled = nbt.getBoolean("OuterColorEnabled"); - this.zoneHeight = nbt.getFloat("ZoneHeight"); - this.particleDensity = nbt.getFloat("ParticleDensity"); - this.particleScale = nbt.getFloat("ParticleScale"); - this.animSpeed = nbt.getFloat("AnimSpeed"); - this.lightningDensity = nbt.getFloat("LightningDensity"); + this.radius = nbt.hasKey("Radius") ? nbt.getFloat("Radius") : 4.0f; + this.durationTicks = nbt.hasKey("Duration") ? nbt.getInteger("Duration") : 200; + this.maxTicks = nbt.hasKey("MaxTicks") ? nbt.getInteger("MaxTicks") : 200; + this.innerColor = nbt.hasKey("InnerColor") ? nbt.getInteger("InnerColor") : 0xFF6600; + this.outerColor = nbt.hasKey("OuterColor") ? nbt.getInteger("OuterColor") : 0xFF0000; + this.outerColorEnabled = !nbt.hasKey("OuterColorEnabled") || nbt.getBoolean("OuterColorEnabled"); + this.zoneHeight = nbt.hasKey("ZoneHeight") ? nbt.getFloat("ZoneHeight") : 2.0f; + this.particleDensity = nbt.hasKey("ParticleDensity") ? nbt.getFloat("ParticleDensity") : 1.0f; + this.particleScale = nbt.hasKey("ParticleScale") ? nbt.getFloat("ParticleScale") : 1.0f; + this.animSpeed = nbt.hasKey("AnimSpeed") ? nbt.getFloat("AnimSpeed") : 1.0f; + this.lightningDensity = nbt.hasKey("LightningDensity") ? nbt.getFloat("LightningDensity") : 1.0f; this.deathWorldTime = nbt.getLong("DeathWorldTime"); // Visual layer fields @@ -764,8 +765,9 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD this.visible = nbt.getBoolean("Visible"); // Hazard - this.damagePerSecond = nbt.getFloat("DamagePerSecond"); - this.damageInterval = nbt.getInteger("DamageInterval"); + this.damagePerSecond = nbt.hasKey("DamagePerSecond") ? nbt.getFloat("DamagePerSecond") : 1.0f; + this.damageInterval = nbt.hasKey("DamageInterval") ? nbt.getInteger("DamageInterval") : 20; + if (damageInterval <= 0) damageInterval = 1; this.ignoreInvulnFrames = nbt.getBoolean("IgnoreInvuln"); this.affectsCaster = nbt.getBoolean("AffectsCaster"); @@ -893,7 +895,8 @@ public class EntityAbilityZone extends Entity implements IEntityAdditionalSpawnD @Override public void readSpawnData(ByteBuf buffer) { - this.zoneType = ZoneType.values()[buffer.readInt()]; + int zoneTypeOrd = buffer.readInt(); + this.zoneType = (zoneTypeOrd >= 0 && zoneTypeOrd < ZoneType.values().length) ? ZoneType.values()[zoneTypeOrd] : ZoneType.TRAP; String shapeName = ByteBufUtils.readUTF8String(buffer); try { this.shape = ZoneShape.valueOf(shapeName); diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java index afad7be9b..af6766b15 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyBarrier.java @@ -279,6 +279,7 @@ public abstract class EntityEnergyBarrier extends EntityEnergyAbility { readEnergyBaseNBT(nbt); this.ticksAlive = nbt.getInteger("TicksAlive"); this.currentHealth = nbt.getFloat("CurrentHealth"); + if (Float.isNaN(currentHealth) || Float.isInfinite(currentHealth) || currentHealth < 0) currentHealth = barrierData.maxHealth; this.charging = nbt.hasKey("Charging") && nbt.getBoolean("Charging"); this.chargeTick = nbt.getInteger("ChargeTick"); this.chargeDuration = nbt.getInteger("ChargeDuration"); diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java index 9b0c7cb27..782413435 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyDome.java @@ -9,6 +9,7 @@ import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; +import net.minecraft.util.DamageSource; import net.minecraft.world.World; import java.util.List; @@ -28,20 +29,20 @@ public class EntityEnergyDome extends EntityEnergyBarrier { public EntityEnergyDome(World world) { super(world); - this.setSize(1.0f, 1.0f); // Hitbox doesn't really matter, we use radius checks + this.setSize(1.0f, 1.0f); } public EntityEnergyDome(World world, EntityLivingBase owner, double x, double y, double z, float domeRadius, EnergyDisplayData display, EnergyLightningData lightning, EnergyBarrierData barrier) { this(world); - this.setPosition(x, y, z); this.ownerEntityId = owner.getEntityId(); this.domeRadius = domeRadius; this.displayData = display; this.lightningData = lightning; this.barrierData = barrier; this.currentHealth = barrier.maxHealth; + this.setPosition(x, y, z); } @Override @@ -103,14 +104,25 @@ public class EntityEnergyDome extends EntityEnergyBarrier { double dx = projectile.posX - this.posX; double dy = projectile.posY - this.posY; double dz = projectile.posZ - this.posZ; - double distSq = dx * dx + dy * dy + dz * dz; + double dist = Math.sqrt(dx * dx + dy * dy + dz * dz); - // Check if projectile is within dome radius - if (distSq > domeRadius * domeRadius) return false; + // Check if projectile is outside dome radius + if (dist > domeRadius) return false; - // Check if projectile is moving inward (dot product of velocity and position relative to center) + // Approximate the projectile's position before last tick's movement. + // checkBarrierCollision runs BEFORE updateProjectile, so posX is post-last-movement. + // posX - motionX gives the pre-movement position (works for moveEntity and setPosition). + double prevDx = (projectile.posX - projectile.motionX) - this.posX; + double prevDy = (projectile.posY - projectile.motionY) - this.posY; + double prevDz = (projectile.posZ - projectile.motionZ) - this.posZ; + double prevDist = Math.sqrt(prevDx * prevDx + prevDy * prevDy + prevDz * prevDz); + + // Only block projectiles that crossed the dome boundary from outside. + // Projectiles spawned inside (prevDist < radius) pass through freely. + if (prevDist < domeRadius) return false; + + // Projectile entered from outside — check it's still moving inward double dot = dx * projectile.motionX + dy * projectile.motionY + dz * projectile.motionZ; - // Negative dot = moving toward center = incoming return dot < 0; } @@ -172,17 +184,37 @@ public class EntityEnergyDome extends EntityEnergyBarrier { } } + // ==================== MELEE (spherical check) ==================== + + /** + * Reject melee hits that land on the cubic bounding box but are outside the actual sphere. + */ + @Override + public boolean attackEntityFrom(DamageSource source, float amount) { + if (source.getEntity() != null) { + Entity attacker = source.getEntity(); + double dx = attacker.posX - this.posX; + double dy = (attacker.posY + attacker.height * 0.5) - this.posY; + double dz = attacker.posZ - this.posZ; + double distSq = dx * dx + dy * dy + dz * dz; + // Allow hit only if attacker is within sphere + melee reach margin + double maxDist = domeRadius + 5.0; + if (distSq > maxDist * maxDist) return false; + } + return super.attackEntityFrom(source, amount); + } + // ==================== BOUNDING BOX ==================== + /** + * Expands the ray-trace targeting area so players can melee-hit the dome + * from anywhere near its surface, not just the tiny 1x1 center. + * This is used by EntityRenderer.getMouseOver() to expand the entity BB + * for crosshair targeting without modifying the actual bounding box. + */ @Override - public AxisAlignedBB getBoundingBox() { - if (barrierData.meleeEnabled) { - return AxisAlignedBB.getBoundingBox( - posX - domeRadius, posY - domeRadius, posZ - domeRadius, - posX + domeRadius, posY + domeRadius, posZ + domeRadius - ); - } - return null; + public float getCollisionBorderSize() { + return domeRadius; } @Override @@ -193,6 +225,27 @@ public class EntityEnergyDome extends EntityEnergyBarrier { return distance < d * d; } + // ==================== DISTANCE (for render sorting) ==================== + + /** + * Returns squared distance from the given point to the nearest point on the dome sphere surface. + * Uses absolute distance to surface: |centerDist - radius|. + *

+ * Inside: nearest surface is (radius - centerDist) away. + * Outside: nearest surface is (centerDist - radius) away. + * This ensures entities inside the dome sort correctly relative to the dome shell. + */ + @Override + public double getDistanceSq(double x, double y, double z) { + double dx = this.posX - x; + double dy = this.posY - y; + double dz = this.posZ - z; + double centerDist = Math.sqrt(dx * dx + dy * dy + dz * dz); + + double surfaceDist = Math.abs(centerDist - domeRadius); + return surfaceDist * surfaceDist; + } + // ==================== GETTERS ==================== public float getDomeRadius() { diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java index 7fd617afb..d3665e319 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyPanel.java @@ -167,6 +167,8 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { 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); } } @@ -188,6 +190,7 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { // Max lifetime check if (ticksAlive > 200) { + onBarrierDestroyed(); this.setDead(); } } @@ -341,6 +344,47 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { return distance < d * d; } + // ==================== DISTANCE (for render sorting) ==================== + + /** + * Returns squared distance from the given point to the closest point on the panel surface. + * Accounts for panel rotation (panelYaw), width, height, and thickness. + * This ensures correct transparency render ordering when the camera is near or behind the panel. + */ + @Override + public double getDistanceSq(double x, double y, double z) { + double dx = x - this.posX; + double dy = y - this.posY; + double dz = z - this.posZ; + + float yawRad = (float) Math.toRadians(panelYaw); + double cos = Math.cos(yawRad); + double sin = Math.sin(yawRad); + + // Transform to panel-local space + // Panel normal (forward): (-sin, 0, cos) + // Panel right (width axis): (cos, 0, sin) + double localForward = dx * (-sin) + dz * cos; + double localRight = dx * cos + dz * sin; + double localUp = dy; + + float halfW = panelData.panelWidth * 0.5f; + float halfH = panelData.panelHeight * 0.5f; + float halfThickness = 0.25f; + + // Clamp to panel bounds to find closest point on the panel volume + double clampedForward = Math.max(-halfThickness, Math.min(halfThickness, localForward)); + double clampedRight = Math.max(-halfW, Math.min(halfW, localRight)); + double clampedUp = Math.max(-halfH, Math.min(halfH, localUp)); + + // Distance from point to closest point on panel + double df = localForward - clampedForward; + double dr = localRight - clampedRight; + double du = localUp - clampedUp; + + return df * df + dr * dr + du * du; + } + // ==================== GETTERS ==================== public PanelMode getMode() { @@ -365,8 +409,8 @@ public class EntityEnergyPanel extends EntityEnergyBarrier { this.mode = (modeOrdinal >= 0 && modeOrdinal < PanelMode.values().length) ? PanelMode.values()[modeOrdinal] : PanelMode.PLACED; panelData.readNBT(nbt); - panelData.panelWidth = Math.min(panelData.panelWidth, MAX_ENTITY_SIZE); - panelData.panelHeight = Math.min(panelData.panelHeight, MAX_ENTITY_SIZE); + panelData.panelWidth = sanitize(panelData.panelWidth, 3.0f, MAX_ENTITY_SIZE); + panelData.panelHeight = sanitize(panelData.panelHeight, 3.0f, MAX_ENTITY_SIZE); this.targetPanelWidth = sanitize(nbt.hasKey("TargetPanelWidth") ? nbt.getFloat("TargetPanelWidth") : panelData.panelWidth, 3.0f, MAX_ENTITY_SIZE); this.targetPanelHeight = sanitize(nbt.hasKey("TargetPanelHeight") ? nbt.getFloat("TargetPanelHeight") : panelData.panelHeight, 3.0f, MAX_ENTITY_SIZE); } diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index 7ec72f783..4f0c16e50 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -318,8 +318,8 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { * @return true if projectile was absorbed (caller should stop processing) */ protected boolean checkBarrierCollision() { - // Search area for barriers - must cover large domes (up to 30 radius) - double searchRange = 32.0; + // Search range must cover the largest possible barrier (dome radius up to MAX_ENTITY_RADIUS) + double searchRange = MAX_ENTITY_RADIUS + 5; AxisAlignedBB searchBox = AxisAlignedBB.getBoundingBox( posX - searchRange, posY - searchRange, posZ - searchRange, posX + searchRange, posY + searchRange, posZ + searchRange @@ -458,13 +458,15 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { protected void doExplosion() { if (previewMode) return; // Skip explosion in preview mode + float explosionRad = getExplosionRadius(); + if (Float.isNaN(explosionRad) || explosionRad <= 0) return; worldObj.playSoundEffect(posX, posY, posZ, "random.explode", 1.0f, 1.0f); Entity owner = getOwnerEntity(); AxisAlignedBB explosionBox = AxisAlignedBB.getBoundingBox( - posX - getExplosionRadius(), posY - getExplosionRadius(), posZ - getExplosionRadius(), - posX + getExplosionRadius(), posY + getExplosionRadius(), posZ + getExplosionRadius() + posX - explosionRad, posY - explosionRad, posZ - explosionRad, + posX + explosionRad, posY + explosionRad, posZ + explosionRad ); @SuppressWarnings("unchecked") @@ -479,9 +481,8 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { Math.pow(target.posZ - posZ, 2) ); - float radius = getExplosionRadius(); - if (radius > 0 && dist <= radius) { - float falloff = 1.0f - (float) (dist / radius) * getExplosionDamageFalloff(); + if (dist <= explosionRad) { + float falloff = 1.0f - (float) (dist / explosionRad) * getExplosionDamageFalloff(); applyDamage(target, getDamage() * falloff, getKnockback() * falloff); } } @@ -966,14 +967,12 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { nbt.setFloat("Size", size); - // Write effects list - if (!effects.isEmpty()) { - NBTTagList effectsList = new NBTTagList(); - for (AbilityPotionEffect effect : effects) { - effectsList.appendTag(effect.writeNBT()); - } - nbt.setTag("Effects", effectsList); + // Write effects list (always write, even if empty, so readBaseNBT finds the key) + NBTTagList effectsList = new NBTTagList(); + for (AbilityPotionEffect effect : effects) { + effectsList.appendTag(effect.writeNBT()); } + nbt.setTag("Effects", effectsList); nbt.setLong("DeathWorldTime", deathWorldTime); diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java b/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java index c24a5ca66..29d0ac39a 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java @@ -331,8 +331,8 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { this.sliceWidth = sanitize(nbt.hasKey("SliceWidth") ? nbt.getFloat("SliceWidth") : 3.0f, 3.0f, MAX_ENTITY_SIZE); this.sliceThickness = sanitize(nbt.hasKey("SliceThickness") ? nbt.getFloat("SliceThickness") : 0.15f, 0.15f, MAX_ENTITY_SIZE); this.piercing = !nbt.hasKey("Piercing") || nbt.getBoolean("Piercing"); - this.targetSliceWidth = nbt.hasKey("TargetSliceWidth") ? nbt.getFloat("TargetSliceWidth") : sliceWidth; - this.targetSliceThickness = nbt.hasKey("TargetSliceThickness") ? nbt.getFloat("TargetSliceThickness") : sliceThickness; + this.targetSliceWidth = sanitize(nbt.hasKey("TargetSliceWidth") ? nbt.getFloat("TargetSliceWidth") : sliceWidth, sliceWidth, MAX_ENTITY_SIZE); + this.targetSliceThickness = sanitize(nbt.hasKey("TargetSliceThickness") ? nbt.getFloat("TargetSliceThickness") : sliceThickness, sliceThickness, MAX_ENTITY_SIZE); } @Override