From a7d780565f42a0db508af7447b5624a64d960674 Mon Sep 17 00:00:00 2001 From: Kam Date: Tue, 10 Mar 2026 23:01:54 -0400 Subject: [PATCH] Fix Sizing Changes Mid Firing --- .../client/renderer/RenderEnergyBeam.java | 33 ++++++++-------- .../npcs/entity/EntityAbilityBeam.java | 38 ++++++++++++------- .../npcs/entity/EntityAbilityDisc.java | 37 +++++++++++------- .../npcs/entity/EntityAbilityLaser.java | 23 +++++++---- 4 files changed, 80 insertions(+), 51 deletions(-) diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyBeam.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyBeam.java index 17316fcf2..decd15ef8 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyBeam.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyBeam.java @@ -4,6 +4,7 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.entity.EntityAbilityBeam; import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.entity.Entity; import net.minecraft.util.Vec3; import org.lwjgl.opengl.GL11; @@ -51,19 +52,20 @@ public class RenderEnergyBeam extends RenderEnergy { return; } - // Get interpolated head offset + // Get interpolated head offset (for head/trail rendering relative to origin) double headOffsetX = beam.getInterpolatedHeadOffsetX(partialTicks); double headOffsetY = beam.getInterpolatedHeadOffsetY(partialTicks); double headOffsetZ = beam.getInterpolatedHeadOffsetZ(partialTicks); - // The render system passes x,y,z as the position to render the entity at - // Entity position = origin + headOffset (in world coords) - // So origin render position = x,y,z - headOffset - double renderOriginX = x - headOffsetX; - double renderOriginY = y - headOffsetY; - double renderOriginZ = z - headOffsetZ; + // Compute render origin directly from the world origin and camera position. + // We can't derive it from MC's x,y,z because syncPositionStateToCurrent() snaps + // lastTickPos = posX on every sync packet, killing MC's position interpolation. + // The headOffset interpolation then mismatches, causing the origin to wobble. + double renderOriginX = beam.getOriginX() - RenderManager.renderPosX; + double renderOriginY = beam.getOriginY() - RenderManager.renderPosY; + double renderOriginZ = beam.getOriginZ() - RenderManager.renderPosZ; - // Get beam properties (interpolated for smooth size transitions) + // Get beam properties (lerp-smoothed for smooth size transitions) float beamWidth = beam.getInterpolatedBeamWidth(partialTicks); float headSize = beam.getInterpolatedHeadSize(partialTicks); int innerColor = beam.getInnerColor(); @@ -77,21 +79,22 @@ public class RenderEnergyBeam extends RenderEnergy { // Calculate head distance from origin (for overlap check) double headDistFromOrigin = Math.sqrt(headOffsetX * headOffsetX + headOffsetY * headOffsetY + headOffsetZ * headOffsetZ); - // Render tail orb at origin (0,0,0) - only in anchored mode - // Don't render if head is too close to origin (would cause overlap/extra orb appearance) - if (beam.shouldRenderTailOrb() && headDistFromOrigin > headSize * 0.5) { - renderTailOrb(headSize * 0.8f, innerColor, outerColor, beam.isOuterColorEnabled(), beam.getOuterColorWidth(), outerAlpha, beam.getInnerAlpha() * proximityAlpha); - } - float innerAlpha = beam.getInnerAlpha() * proximityAlpha; - // Render trail segments with smooth connections (with fading for non-anchored) + // Render trail segments first (with fading for non-anchored) if (beam.hasFadingTrail()) { renderFadingTrail(beam, trail, beamWidth, innerColor, outerColor, beam.isOuterColorEnabled(), beam.getOuterColorWidth(), innerAlpha); } else { renderSmoothTrail(trail, beamWidth, innerColor, outerColor, beam.isOuterColorEnabled(), beam.getOuterColorWidth(), innerAlpha); } + // Render tail orb at origin (0,0,0) AFTER trail so it draws on top + // Don't render if head is too close to origin (would cause overlap/extra orb appearance) + // Use target headSize (not lerped) so the threshold isn't inflated by lerp lag + if (beam.shouldRenderTailOrb() && headDistFromOrigin > beam.getHeadSize() * 0.5) { + renderTailOrb(beam.getHeadSize() * 0.8f, innerColor, outerColor, beam.isOuterColorEnabled(), beam.getOuterColorWidth(), outerAlpha, innerAlpha); + } + // Render head at interpolated offset position renderHead(beam, headOffsetX, headOffsetY, headOffsetZ, headSize, innerColor, outerColor, outerAlpha, innerAlpha, partialTicks); diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java index 4e5e2e3cb..646cbbd9a 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java @@ -38,11 +38,15 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { FREE_TRAIL } - // Beam shape properties + // Beam shape properties (target values - set by sync packets/scripts) private float beamWidth = 0.3f; private float headSize = 0.5f; - private float prevBeamWidth = 0.3f; - private float prevHeadSize = 0.5f; + + // Lerp-smoothed render values (approach target each tick) + private float renderBeamWidth = 0.3f; + private float renderHeadSize = 0.5f; + private float prevRenderBeamWidth = 0.3f; + private float prevRenderHeadSize = 0.5f; // Trail - list of points showing beam path (RELATIVE to origin!) private List trailPoints = new ArrayList<>(); @@ -236,18 +240,26 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { @Override protected void updateProjectile() { + // Save previous render values for sub-tick interpolation + prevRenderBeamWidth = renderBeamWidth; + prevRenderHeadSize = renderHeadSize; + // Handle charging state (windup phase) - use isCharging() for synced value if (isCharging()) { + // During charging, snap render values to target (size grows smoothly already) + renderBeamWidth = beamWidth; + renderHeadSize = headSize; updateCharging(); return; } - // Store previous values for interpolation + // Lerp render values toward target (smooth out sync packet jumps) + renderBeamWidth += (beamWidth - renderBeamWidth) * 0.15f; + renderHeadSize += (headSize - renderHeadSize) * 0.15f; + prevHeadOffsetX = headOffsetX; prevHeadOffsetY = headOffsetY; prevHeadOffsetZ = headOffsetZ; - prevBeamWidth = beamWidth; - prevHeadSize = headSize; // Age trail points for fading effect if (isFadingMode()) { @@ -585,11 +597,10 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { } public float getInterpolatedBeamWidth(float partialTicks) { - return prevBeamWidth + (beamWidth - prevBeamWidth) * partialTicks; + return prevRenderBeamWidth + (renderBeamWidth - prevRenderBeamWidth) * partialTicks; } public void setBeamWidth(float beamWidth) { - this.prevBeamWidth = this.beamWidth; this.beamWidth = beamWidth; } @@ -598,11 +609,10 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { } public float getInterpolatedHeadSize(float partialTicks) { - return prevHeadSize + (headSize - prevHeadSize) * partialTicks; + return prevRenderHeadSize + (renderHeadSize - prevRenderHeadSize) * partialTicks; } public void setHeadSize(float headSize) { - this.prevHeadSize = this.headSize; this.headSize = headSize; } @@ -761,8 +771,6 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { @Override protected void applyProjectileClientSyncData(NBTTagCompound nbt) { - prevBeamWidth = beamWidth; - prevHeadSize = headSize; beamWidth = nbt.getFloat("BeamWidth"); headSize = nbt.getFloat("HeadSize"); setAttachedToOwner(nbt.getBoolean("AttachedToOwner")); @@ -774,8 +782,10 @@ public class EntityAbilityBeam extends EntityEnergyProjectile { protected void readProjectileNBT(NBTTagCompound nbt) { this.beamWidth = sanitize(nbt.hasKey("BeamWidth") ? nbt.getFloat("BeamWidth") : 0.3f, 0.3f, MAX_ENTITY_SIZE); this.headSize = sanitize(nbt.hasKey("HeadSize") ? nbt.getFloat("HeadSize") : 0.5f, 0.5f, MAX_ENTITY_SIZE); - this.prevBeamWidth = this.beamWidth; - this.prevHeadSize = this.headSize; + this.renderBeamWidth = this.beamWidth; + this.renderHeadSize = this.headSize; + this.prevRenderBeamWidth = this.beamWidth; + this.prevRenderHeadSize = this.headSize; this.headOffsetX = nbt.hasKey("HeadOffsetX") ? nbt.getDouble("HeadOffsetX") : 0; this.headOffsetY = nbt.hasKey("HeadOffsetY") ? nbt.getDouble("HeadOffsetY") : 0; this.headOffsetZ = nbt.hasKey("HeadOffsetZ") ? nbt.getDouble("HeadOffsetZ") : 0; diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java index db5e2eb2c..7edc1dae1 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java @@ -30,11 +30,15 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { private boolean returning = false; private int ticksSinceMiss = 0; - // Disc shape properties + // Disc shape properties (target values) private float discRadius = 1.0f; // Width of disc private float discThickness = 0.2f; // Height of disc - private float prevDiscRadius = 1.0f; - private float prevDiscThickness = 0.2f; + + // Lerp-smoothed render values + private float renderDiscRadius = 1.0f; + private float renderDiscThickness = 0.2f; + private float prevRenderDiscRadius = 1.0f; + private float prevRenderDiscThickness = 0.2f; // Boomerang owner-gone tracking private int returnOwnerNullTicks = 0; @@ -136,16 +140,23 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { @Override protected void updateProjectile() { - // Store previous values for interpolation - prevDiscRadius = discRadius; - prevDiscThickness = discThickness; + // Save previous render values for sub-tick interpolation + prevRenderDiscRadius = renderDiscRadius; + prevRenderDiscThickness = renderDiscThickness; // Handle charging state (windup phase) if (isCharging()) { + // During charging, snap render values to target (size grows smoothly already) + renderDiscRadius = discRadius; + renderDiscThickness = discThickness; updateCharging(); return; } + // Lerp render values toward target (smooth out sync packet jumps) + renderDiscRadius += (discRadius - renderDiscRadius) * 0.15f; + renderDiscThickness += (discThickness - renderDiscThickness) * 0.15f; + // In preview mode, run movement on client (no server) if (previewMode) { updatePreviewMovement(); @@ -354,11 +365,10 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { } public float getInterpolatedDiscRadius(float partialTicks) { - return prevDiscRadius + (discRadius - prevDiscRadius) * partialTicks; + return prevRenderDiscRadius + (renderDiscRadius - prevRenderDiscRadius) * partialTicks; } public void setDiscRadius(float radius) { - this.prevDiscRadius = this.discRadius; this.discRadius = radius; } @@ -367,11 +377,10 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { } public float getInterpolatedDiscThickness(float partialTicks) { - return prevDiscThickness + (discThickness - prevDiscThickness) * partialTicks; + return prevRenderDiscThickness + (renderDiscThickness - prevRenderDiscThickness) * partialTicks; } public void setDiscThickness(float thickness) { - this.prevDiscThickness = this.discThickness; this.discThickness = thickness; } @@ -459,8 +468,6 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { @Override protected void applyProjectileClientSyncData(NBTTagCompound nbt) { - prevDiscRadius = discRadius; - prevDiscThickness = discThickness; discRadius = nbt.getFloat("DiscRadius"); discThickness = nbt.getFloat("DiscThickness"); vertical = nbt.getBoolean("Vertical"); @@ -474,8 +481,10 @@ public class EntityAbilityDisc extends EntityEnergyProjectile { this.boomerangDelay = nbt.hasKey("BoomerangDelay") ? nbt.getInteger("BoomerangDelay") : 40; this.discRadius = sanitize(nbt.hasKey("DiscRadius") ? nbt.getFloat("DiscRadius") : 1.0f, 1.0f, MAX_ENTITY_SIZE); this.discThickness = sanitize(nbt.hasKey("DiscThickness") ? nbt.getFloat("DiscThickness") : 0.2f, 0.2f, MAX_ENTITY_SIZE); - this.prevDiscRadius = this.discRadius; - this.prevDiscThickness = this.discThickness; + this.renderDiscRadius = this.discRadius; + this.renderDiscThickness = this.discThickness; + this.prevRenderDiscRadius = this.discRadius; + this.prevRenderDiscThickness = this.discThickness; this.vertical = nbt.hasKey("Vertical") ? nbt.getBoolean("Vertical") : false; this.returning = nbt.hasKey("Returning") && nbt.getBoolean("Returning"); // Charging state (common fields handled by base) diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java index f1eb9c617..546457f4d 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java @@ -31,9 +31,12 @@ import java.util.List; */ public class EntityAbilityLaser extends EntityEnergyProjectile { - // Laser-specific properties + // Laser-specific properties (target value) private float laserWidth = 0.2f; - private float prevLaserWidth = 0.2f; + + // Lerp-smoothed render values + private float renderLaserWidth = 0.2f; + private float prevRenderLaserWidth = 0.2f; private float expansionSpeed = 2.0f; // Blocks per tick private float maxLength = 32.0f; // Maximum beam reach in blocks @@ -145,14 +148,19 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { @Override protected void updateProjectile() { - // Store previous values for interpolation - prevLaserWidth = laserWidth; + // Save previous render value for sub-tick interpolation + prevRenderLaserWidth = renderLaserWidth; if (isCharging()) { + // During charging, snap render value to target + renderLaserWidth = laserWidth; updateCharging(); return; } + // Lerp render value toward target (smooth out sync packet jumps) + renderLaserWidth += (laserWidth - renderLaserWidth) * 0.15f; + // Save previous positions for partial tick interpolation prevStartX = startX; prevStartY = startY; @@ -708,11 +716,10 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { } public float getInterpolatedLaserWidth(float partialTicks) { - return prevLaserWidth + (laserWidth - prevLaserWidth) * partialTicks; + return prevRenderLaserWidth + (renderLaserWidth - prevRenderLaserWidth) * partialTicks; } public void setLaserWidth(float width) { - this.prevLaserWidth = this.laserWidth; this.laserWidth = width; } @@ -888,7 +895,6 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { @Override protected void applyProjectileClientSyncData(NBTTagCompound nbt) { - prevLaserWidth = laserWidth; laserWidth = nbt.getFloat("LaserWidth"); expansionSpeed = nbt.getFloat("ExpansionSpeed"); maxLength = nbt.getFloat("MaxLength"); @@ -902,7 +908,8 @@ public class EntityAbilityLaser extends EntityEnergyProjectile { @Override protected void readProjectileNBT(NBTTagCompound nbt) { this.laserWidth = sanitize(nbt.hasKey("LaserWidth") ? nbt.getFloat("LaserWidth") : 0.2f, 0.2f, MAX_ENTITY_SIZE); - this.prevLaserWidth = this.laserWidth; + this.renderLaserWidth = this.laserWidth; + this.prevRenderLaserWidth = this.laserWidth; this.expansionSpeed = sanitize(nbt.hasKey("ExpansionSpeed") ? nbt.getFloat("ExpansionSpeed") : 2.0f, 0.1f, MAX_ENTITY_SIZE); this.maxLength = sanitize(nbt.hasKey("MaxLength") ? nbt.getFloat("MaxLength") : 32.0f, 1.0f, MAX_ENTITY_SIZE); this.dirX = nbt.getDouble("DirX");