diff --git a/src/main/java/kamkeel/npcs/controllers/data/energycharge/EnergyChargePreviewManager.java b/src/main/java/kamkeel/npcs/controllers/data/energycharge/EnergyChargePreviewManager.java index 2349f1975..558399de1 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/energycharge/EnergyChargePreviewManager.java +++ b/src/main/java/kamkeel/npcs/controllers/data/energycharge/EnergyChargePreviewManager.java @@ -13,6 +13,9 @@ import java.util.concurrent.ConcurrentHashMap; */ public class EnergyChargePreviewManager { + /** Hard age cap for preview entities — last-resort safety net (60 seconds). */ + private static final int MAX_PREVIEW_AGE = 1200; + public static EnergyChargePreviewManager ClientInstance; private final ConcurrentHashMap previews = new ConcurrentHashMap(); @@ -59,6 +62,14 @@ public class EnergyChargePreviewManager { iterator.remove(); continue; } + + // Hard age cap: remove previews that have existed far too long + if (entity.ticksExisted > MAX_PREVIEW_AGE) { + entity.setDead(); + iterator.remove(); + continue; + } + entity.onUpdate(); if (entity.isDead) { iterator.remove(); diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java index 01b39f80d..7922f7191 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyAbility.java @@ -21,6 +21,7 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit // ==================== SAFETY CONSTANTS ==================== protected static final int CHARGE_TIMEOUT_GRACE = 60; // Ticks of grace after charge should have ended + protected static final int PREVIEW_CHARGE_GRACE = 3; // Ticks of grace for client-only charge previews protected static final int HARD_LIFETIME_CAP = 1200; // 60 seconds absolute max for projectiles/sweepers protected static final int BARRIER_HARD_LIFETIME_CAP = 12000; // 10 minutes absolute max for barriers protected static final float MAX_ENTITY_SIZE = 100.0f; // Max size/width/length for any energy entity @@ -94,7 +95,7 @@ public abstract class EntityEnergyAbility extends Entity implements IEntityAddit public float getInterpolatedChargeProgress(float partialTicks) { if (chargeDuration <= 0) return 1.0f; - float prevProgress = Math.max(0, (float) (chargeTick - 1) / chargeDuration); + float prevProgress = Math.min(1.0f, Math.max(0, (float) (chargeTick - 1) / chargeDuration)); float currProgress = Math.min(1.0f, (float) chargeTick / chargeDuration); return prevProgress + (currProgress - prevProgress) * partialTicks; } diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index 091af5086..02a124ae3 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -232,6 +232,18 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { // Preview entities skip super.onUpdate() but still need ticksExisted // for renderer pulsing effects (e.g., sin(ticksExisted + partialTicks)). this.ticksExisted++; + + // Preview-specific safety: self-destruct if charging has exceeded duration + grace + if (isCharging() && chargeDuration > 0 && chargeTick > chargeDuration + PREVIEW_CHARGE_GRACE) { + this.setDead(); + return; + } + + // Preview hard lifetime cap (mirrors real entity HARD_LIFETIME_CAP) + if (ticksExisted > HARD_LIFETIME_CAP) { + this.setDead(); + return; + } } if (!previewMode) {