From abc34b3f6fa33c7327f605207278190a32cb5e54 Mon Sep 17 00:00:00 2001 From: Kam Date: Mon, 23 Feb 2026 23:19:33 -0500 Subject: [PATCH] Fix Rendering Glitches --- .../client/renderer/RenderAbilityDisc.java | 15 +++++++---- .../client/renderer/RenderAbilityLaser.java | 3 --- .../client/renderer/RenderAbilityOrb.java | 15 +++++++---- .../npcs/entity/EntityAbilityDisc.java | 20 ++++++++++---- .../npcs/entity/EntityAbilityLaser.java | 17 +++++++++++- .../npcs/entity/EntityEnergyProjectile.java | 26 ++++++------------- .../npcs/entity/EntityEnergySlicer.java | 17 ++++++++---- 7 files changed, 71 insertions(+), 42 deletions(-) diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java index 30e59cf89..3e3bcb025 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java @@ -59,12 +59,17 @@ public class RenderAbilityDisc extends RenderEnergyAbility { double motionX = entity.motionX; double motionY = entity.motionY; double motionZ = entity.motionZ; - Vec3 motionVec = Vec3.createVectorHelper(motionX, motionY, motionZ); + double speedSq = motionX * motionX + motionY * motionY + motionZ * motionZ; - float motionYaw = MathUtil.getYaw(motionVec); - float motionPitch = MathUtil.getPitch(motionVec); - GL11.glRotatef(motionYaw, 0, 1, 0); - GL11.glRotatef(motionPitch, 1, 0, 0); + // Only apply motion-based facing when speed is meaningful. + // Near-zero motion makes atan2 unstable, producing jittery angles. + if (speedSq > 0.0001) { + Vec3 motionVec = Vec3.createVectorHelper(motionX, motionY, motionZ); + float motionYaw = MathUtil.getYaw(motionVec); + float motionPitch = MathUtil.getPitch(motionVec); + GL11.glRotatef(motionYaw, 0, 1, 0); + GL11.glRotatef(motionPitch, 1, 0, 0); + } // Apply Y-axis rotation for spin GL11.glRotatef(disc.getInterpolatedRotationY(partialTicks), 0.0f, 1.0f, 0.0f); diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java index 155c439b8..e38764bfb 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java @@ -23,9 +23,6 @@ public class RenderAbilityLaser extends RenderEnergyAbility { @Override public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) { - if (shouldSkipInitialActiveRender(entity)) { - return; - } EntityAbilityLaser laser = (EntityAbilityLaser) entity; setupRenderState(); diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityOrb.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityOrb.java index 1720737c1..d5a44a245 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityOrb.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityOrb.java @@ -46,12 +46,17 @@ public class RenderAbilityOrb extends RenderEnergyAbility { double motionX = entity.motionX; double motionY = entity.motionY; double motionZ = entity.motionZ; - Vec3 motionVec = Vec3.createVectorHelper(motionX, motionY, motionZ); + double speedSq = motionX * motionX + motionY * motionY + motionZ * motionZ; - float motionYaw = MathUtil.getYaw(motionVec); - float motionPitch = MathUtil.getPitch(motionVec); - GL11.glRotatef(motionYaw, 0, 1, 0); - GL11.glRotatef(motionPitch, 1, 0, 0); + // Only apply motion-based facing when speed is meaningful. + // Near-zero motion makes atan2 unstable, producing jittery angles. + if (speedSq > 0.0001) { + Vec3 motionVec = Vec3.createVectorHelper(motionX, motionY, motionZ); + float motionYaw = MathUtil.getYaw(motionVec); + float motionPitch = MathUtil.getPitch(motionVec); + GL11.glRotatef(motionYaw, 0, 1, 0); + GL11.glRotatef(motionPitch, 1, 0, 0); + } // Use entity's interpolated rotation for smooth spinning GL11.glRotatef(orb.getInterpolatedRotationX(partialTicks), 1.0f, 0.0f, 0.0f); diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java index 503d64f3d..148802267 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java @@ -39,6 +39,9 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { private int returnOwnerNullTicks = 0; private boolean vertical = false; // false = horizontal (flat), true = vertical (thin edge forward) + // Cached travel direction for smooth rendering when motion is near zero + private float lastTravelYaw = 0.0f; + // Charging state (disc-specific target sizes) private float targetDiscRadius = 1.0f; // Full radius to grow to during charging private float targetDiscThickness = 0.2f; // Full thickness to grow to during charging @@ -60,7 +63,7 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { super(world); // Initialize base properties - initProjectile(owner, target, x, y, z, discRadius, display, combat, lightning, lifespan, trajectory); + initProjectile(owner, target, x, y, z, 1.0f, display, combat, lightning, lifespan, trajectory); this.homingData = homing; @@ -316,7 +319,7 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { float progress = getChargeProgress(); this.discRadius = targetDiscRadius * progress; this.discThickness = targetDiscThickness * progress; - this.size = this.discRadius; // Base size for interpolation + this.size = 1.0f; // Size acts as a scaling factor; discRadius handles actual dimensions Entity owner = getOwnerEntity(); if (owner instanceof EntityLivingBase) { @@ -382,14 +385,15 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { if (isCharging()) { Entity owner = previewMode ? previewOwner : getOwnerEntity(); if (owner != null) { - return owner.rotationYaw; + lastTravelYaw = owner.rotationYaw; + return lastTravelYaw; } } double speed = Math.sqrt(motionX * motionX + motionZ * motionZ); if (speed > 0.001) { - return (float) (Math.atan2(-motionX, motionZ) * 180.0 / Math.PI); + lastTravelYaw = (float) (Math.atan2(-motionX, motionZ) * 180.0 / Math.PI); } - return 0.0f; + return lastTravelYaw; } // ==================== NBT ==================== @@ -406,6 +410,12 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { readChargingNBT(nbt); this.targetDiscRadius = nbt.hasKey("TargetDiscRadius") ? nbt.getFloat("TargetDiscRadius") : this.discRadius; this.targetDiscThickness = nbt.hasKey("TargetDiscThickness") ? nbt.getFloat("TargetDiscThickness") : this.discThickness; + + // Normalize size to 1.0 (scaling factor) — discRadius handles actual dimensions. + // Old saves may have size = discRadius baked in from the quadratic scaling bug. + this.size = 1.0f; + this.renderCurrentSize = 1.0f; + this.prevRenderSize = 1.0f; } @Override diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java index a632b16ee..668cccd65 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java @@ -6,6 +6,8 @@ import kamkeel.npcs.controllers.data.ability.data.energy.EnergyDisplayData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLifespanData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyLightningData; import kamkeel.npcs.controllers.data.ability.data.energy.EnergyTrajectoryData; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; @@ -103,6 +105,13 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { this.endZ = z; } + @Override + @SideOnly(Side.CLIENT) + public boolean isInRangeToRenderDist(double distance) { + double range = Math.max(128.0D, currentLength * 2.0D + 64.0D); + return distance < range * range; + } + @Override protected void updateRotation() { // Laser doesn't rotate @@ -179,7 +188,13 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { */ private void updateLaserOriginAndDirection() { Entity owner = getOwnerEntity(); - if (owner == null || !(owner instanceof EntityLivingBase)) return; + if (owner == null || !(owner instanceof EntityLivingBase)) { + // On client, fall back to entity tracker interpolation when owner not loaded + if (worldObj != null && worldObj.isRemote) { + handleClientInterpolation(); + } + return; + } EntityLivingBase livingOwner = (EntityLivingBase) owner; // Use owner look vector (head/eye aim) for direction updates so hit logic matches visuals. diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java index a1d858cf4..8a4cd369e 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java @@ -801,19 +801,21 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { /** * Handle client-side position interpolation. + * Only moves position during active interpolation (interpSteps > 0). + * Does NOT predict/drift between server updates — the motion values are + * derived from correction deltas, not actual entity velocity, so applying + * them between updates causes entities to fly off in wrong directions. + * This matches vanilla Minecraft behavior for non-player entities. */ protected void handleClientInterpolation() { if (this.interpSteps > 0) { - // For very slow / stationary projectiles, snap directly to avoid jitter double dx = this.interpTargetX - this.posX; double dy = this.interpTargetY - this.posY; double dz = this.interpTargetZ - this.posZ; double distSq = dx * dx + dy * dy + dz * dz; - if (distSq < 0.04) { + + if (distSq < 0.0001) { this.setPosition(this.interpTargetX, this.interpTargetY, this.interpTargetZ); - this.motionX = this.interpTargetMotionX; - this.motionY = this.interpTargetMotionY; - this.motionZ = this.interpTargetMotionZ; this.interpSteps = 0; return; } @@ -828,16 +830,6 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { this.setPosition(newX, newY, newZ); this.interpSteps--; - } else { - // Skip motion application for nearly-stationary projectiles to prevent drift - double speedSq = this.motionX * this.motionX + this.motionY * this.motionY + this.motionZ * this.motionZ; - if (speedSq < 0.0001) { - return; - } - this.posX += this.motionX; - this.posY += this.motionY; - this.posZ += this.motionZ; - this.setPosition(this.posX, this.posY, this.posZ); } } @@ -1542,9 +1534,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility { protected void setChargeOrigin(Vec3 pos) { if (pos == null) return; this.setPosition(pos.xCoord, pos.yCoord, pos.zCoord); - this.prevPosX = pos.xCoord; - this.prevPosY = pos.yCoord; - this.prevPosZ = pos.zCoord; + syncPositionState(pos.xCoord, pos.yCoord, pos.zCoord, true); this.startX = pos.xCoord; this.startY = pos.yCoord; this.startZ = pos.zCoord; diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java b/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java index bc429f939..963ca1523 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java +++ b/src/main/java/kamkeel/npcs/entity/EntityEnergySlicer.java @@ -24,6 +24,10 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { private float sliceWidth = 3.0f; private float sliceThickness = 0.15f; + // Cached travel direction for smooth rendering when motion is near zero + private float lastTravelYaw = 0.0f; + private float lastTravelPitch = 0.0f; + public EntityEnergySlicer(World world) { super(world); } @@ -166,13 +170,16 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { public float getTravelYaw() { if (isCharging()) { Entity owner = previewMode ? previewOwner : getOwnerEntity(); - if (owner != null) return owner.rotationYaw; + if (owner != null) { + lastTravelYaw = owner.rotationYaw; + return lastTravelYaw; + } } double speed = Math.sqrt(motionX * motionX + motionZ * motionZ); if (speed > 0.001) { - return (float) (Math.atan2(-motionX, motionZ) * 180.0 / Math.PI); + lastTravelYaw = (float) (Math.atan2(-motionX, motionZ) * 180.0 / Math.PI); } - return 0.0f; + return lastTravelYaw; } /** @@ -181,9 +188,9 @@ public class EntityEnergySlicer extends EntityEnergyProjectile { public float getTravelPitch() { double horizSpeed = Math.sqrt(motionX * motionX + motionZ * motionZ); if (horizSpeed > 0.001 || Math.abs(motionY) > 0.001) { - return (float) (-Math.atan2(motionY, horizSpeed) * 180.0 / Math.PI); + lastTravelPitch = (float) (-Math.atan2(motionY, horizSpeed) * 180.0 / Math.PI); } - return 0.0f; + return lastTravelPitch; } // ==================== GETTERS ====================