diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java index 554b07e31..2fcb58d54 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java @@ -68,8 +68,12 @@ public class RenderAbilityBeam extends RenderAbilityProjectile { GL11.glPushMatrix(); GL11.glTranslated(renderOriginX, renderOriginY, renderOriginZ); + // 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 - if (beam.shouldRenderTailOrb()) { + // 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()); } diff --git a/src/main/java/kamkeel/npcs/client/renderer/quadric/QuadricRenderer.java b/src/main/java/kamkeel/npcs/client/renderer/quadric/QuadricRenderer.java deleted file mode 100644 index 13c994adc..000000000 --- a/src/main/java/kamkeel/npcs/client/renderer/quadric/QuadricRenderer.java +++ /dev/null @@ -1,138 +0,0 @@ -package kamkeel.npcs.client.renderer.quadric; - -import cpw.mods.fml.relauncher.Side; -import cpw.mods.fml.relauncher.SideOnly; -import net.minecraft.client.renderer.GLAllocation; -import org.lwjgl.opengl.GL11; - -/** - * Base class for rendering GLU quadric shapes (spheres, cylinders, etc.) - * Uses display lists for efficient rendering. - * - * Design inspired by LouisXIV's quadric rendering system. - */ -@SideOnly(Side.CLIENT) -public abstract class QuadricRenderer { - protected int displayList; - private boolean compiled; - - public final float radius; - public final int slices; - public final int stacks; - - // Current render state - protected float scaleX = 1.0f; - protected float scaleY = 1.0f; - protected float scaleZ = 1.0f; - - protected float rotationX = 0.0f; - protected float rotationY = 0.0f; - protected float rotationZ = 0.0f; - - protected int color = 0xFFFFFF; - protected float alpha = 1.0f; - - public QuadricRenderer(float radius, int slices, int stacks) { - this.radius = radius; - this.slices = slices; - this.stacks = stacks; - } - - public QuadricRenderer(float radius, int resolution) { - this(radius, resolution, resolution); - } - - public QuadricRenderer() { - this(0.5f, 16); - } - - /** - * Render the quadric shape with current transform and color settings. - */ - public void render(float partialTicks) { - if (!compiled) { - displayList = GLAllocation.generateDisplayLists(1); - GL11.glNewList(displayList, GL11.GL_COMPILE); - compile(); - GL11.glEndList(); - compiled = true; - } - - GL11.glPushMatrix(); - - // Apply scale - GL11.glScalef(scaleX, scaleY, scaleZ); - - // Apply rotation - GL11.glRotatef(rotationX, 1.0f, 0.0f, 0.0f); - GL11.glRotatef(rotationY, 0.0f, 1.0f, 0.0f); - GL11.glRotatef(rotationZ, 0.0f, 0.0f, 1.0f); - - // Apply color - float r = ((color >> 16) & 0xFF) / 255.0f; - float g = ((color >> 8) & 0xFF) / 255.0f; - float b = (color & 0xFF) / 255.0f; - GL11.glColor4f(r, g, b, alpha); - - GL11.glCallList(displayList); - - GL11.glPopMatrix(); - } - - /** - * Compile the quadric geometry into the display list. - * Called once, then cached. - */ - protected abstract void compile(); - - // Setters for render state - public void setScale(float scale) { - this.scaleX = this.scaleY = this.scaleZ = scale; - } - - public void setScale(float x, float y, float z) { - this.scaleX = x; - this.scaleY = y; - this.scaleZ = z; - } - - public void setRotation(float x, float y, float z) { - this.rotationX = x; - this.rotationY = y; - this.rotationZ = z; - } - - public void setColor(int color) { - this.color = color; - } - - public void setAlpha(float alpha) { - this.alpha = alpha; - } - - /** - * Linear interpolation helper. - */ - public static float lerp(float current, float target, float factor) { - return current + (target - current) * factor; - } - - /** - * Color interpolation helper. - */ - public static int lerpColor(int from, int to, float factor) { - int r1 = (from >> 16) & 0xFF; - int g1 = (from >> 8) & 0xFF; - int b1 = from & 0xFF; - - int r2 = (to >> 16) & 0xFF; - int g2 = (to >> 8) & 0xFF; - int b2 = to & 0xFF; - - int r = (int) (r1 + (r2 - r1) * factor); - int g = (int) (g1 + (g2 - g1) * factor); - int b = (int) (b1 + (b2 - b1) * factor); - - return (r << 16) | (g << 8) | b; - } -} diff --git a/src/main/java/kamkeel/npcs/client/renderer/quadric/SphereRenderer.java b/src/main/java/kamkeel/npcs/client/renderer/quadric/SphereRenderer.java deleted file mode 100644 index dc72779a8..000000000 --- a/src/main/java/kamkeel/npcs/client/renderer/quadric/SphereRenderer.java +++ /dev/null @@ -1,37 +0,0 @@ -package kamkeel.npcs.client.renderer.quadric; - -import cpw.mods.fml.relauncher.Side; -import cpw.mods.fml.relauncher.SideOnly; -import org.lwjgl.util.glu.GLU; -import org.lwjgl.util.glu.Sphere; - -/** - * Renders a sphere using GLU quadrics. - * - * Design inspired by LouisXIV's quadric rendering system. - */ -@SideOnly(Side.CLIENT) -public class SphereRenderer extends QuadricRenderer { - - public SphereRenderer(float radius, int slices, int stacks) { - super(radius, slices, stacks); - } - - public SphereRenderer(float radius, int resolution) { - super(radius, resolution); - } - - public SphereRenderer() { - super(); - } - - @Override - protected void compile() { - Sphere sphere = new Sphere(); - sphere.setTextureFlag(true); // Enable texture coordinates - sphere.setDrawStyle(GLU.GLU_FILL); - sphere.setNormals(GLU.GLU_NONE); // No normals for self-illuminated rendering - sphere.setOrientation(GLU.GLU_OUTSIDE); - sphere.draw(radius, slices, stacks); - } -} diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java index ab62c4ca9..34030ab3f 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java @@ -360,18 +360,10 @@ public class EntityAbilityBeam extends EntityAbilityProjectile { // Client: interpolate entity position handleClientInterpolation(); - // Client: update origin if attached to owner (mirrors server logic) - if (attachedToOwner) { - Entity owner = getOwner(); - if (owner != null) { - startX = owner.posX; - startY = owner.posY + owner.getEyeHeight() * 0.7; - startZ = owner.posZ; - } - } - // Client: derive head offset from entity position and origin // Entity position = head world position, so headOffset = pos - origin + // Note: During charging, updateCharging() handles origin positioning + // After firing, origin is fixed at the charged position headOffsetX = posX - startX; headOffsetY = posY - startY; headOffsetZ = posZ - startZ; diff --git a/src/main/java/noppes/npcs/DataAbilities.java b/src/main/java/noppes/npcs/DataAbilities.java index 8875508b1..246ebed46 100644 --- a/src/main/java/noppes/npcs/DataAbilities.java +++ b/src/main/java/noppes/npcs/DataAbilities.java @@ -78,6 +78,16 @@ public class DataAbilities { */ private transient List recentHitTimes = new ArrayList<>(); + /** + * Locked rotation values for ACTIVE phase with lockMovement. + * Stored when entering ACTIVE phase, applied every tick to prevent rotation changes. + */ + private transient boolean rotationLocked = false; + private transient float lockedYaw = 0; + private transient float lockedYawHead = 0; + private transient float lockedRenderYawOffset = 0; + private transient float lockedPitch = 0; + // ═══════════════════════════════════════════════════════════════════ // CONSTRUCTOR // ═══════════════════════════════════════════════════════════════════ @@ -136,6 +146,12 @@ public class DataAbilities { // Remove telegraph - it has served its purpose removeTelegraph(currentAbility); + // Lock rotation if lockMovement is true + // Capture current rotation values to force them every tick + if (currentAbility.isLockMovement()) { + captureLockedRotation(); + } + // Play active sound and animation playAbilitySound(currentAbility.getActiveSound()); playAbilityAnimation(currentAbility.getActiveAnimationId()); @@ -186,9 +202,9 @@ public class DataAbilities { } } - // Lock look direction at target during ACTIVE phase only - if (shouldLockLookDirection() && target != null && target.isEntityAlive()) { - npc.getLookHelper().setLookPositionWithEntity(target, 30.0F, 30.0F); + // Unlock rotation when leaving ACTIVE phase + if (rotationLocked && currentAbility.getPhase() != AbilityPhase.ACTIVE) { + releaseLockedRotation(); } } } @@ -202,6 +218,9 @@ public class DataAbilities { // Stop any ability animation stopAbilityAnimation(); + // Release rotation lock + releaseLockedRotation(); + // Calculate cooldown: random(min, max) + ability offset int baseCooldown = minCooldown; if (maxCooldown > minCooldown) { @@ -526,6 +545,9 @@ public class DataAbilities { // Stop any ability animation stopAbilityAnimation(); + // Release rotation lock + releaseLockedRotation(); + // Fire interrupt event AbilityEvent.InterruptEvent interruptEvent = new AbilityEvent.InterruptEvent( npc.wrappedNPC, currentAbility, lastTarget, source, damage); @@ -546,6 +568,7 @@ public class DataAbilities { if (currentAbility != null) { removeTelegraph(currentAbility); stopAbilityAnimation(); + releaseLockedRotation(); currentAbility.interrupt(); currentAbility = null; lastTarget = null; @@ -737,6 +760,60 @@ public class DataAbilities { return phase == AbilityPhase.WINDUP || phase == AbilityPhase.ACTIVE; } + // ═══════════════════════════════════════════════════════════════════ + // ROTATION LOCKING + // ═══════════════════════════════════════════════════════════════════ + + /** + * Capture current rotation values to lock NPC's look direction. + * Called when entering ACTIVE phase with lockMovement enabled. + */ + private void captureLockedRotation() { + lockedYaw = npc.rotationYaw; + lockedYawHead = npc.rotationYawHead; + lockedRenderYawOffset = npc.renderYawOffset; + lockedPitch = npc.rotationPitch; + rotationLocked = true; + } + + /** + * Release the rotation lock. + * Called when leaving ACTIVE phase or ability completes. + */ + private void releaseLockedRotation() { + rotationLocked = false; + } + + /** + * Apply locked rotation values to the NPC. + * Called AFTER super.onLivingUpdate() in EntityNPCInterface to override + * any rotation changes made by the look helper or AI tasks. + */ + public void applyLockedRotation() { + if (!rotationLocked) { + return; + } + + // Force rotation back to locked values + npc.rotationYaw = lockedYaw; + npc.rotationYawHead = lockedYawHead; + npc.renderYawOffset = lockedRenderYawOffset; + npc.rotationPitch = lockedPitch; + + // Also set prev values to prevent interpolation jitter + npc.prevRotationYaw = lockedYaw; + npc.prevRotationYawHead = lockedYawHead; + npc.prevRenderYawOffset = lockedRenderYawOffset; + npc.prevRotationPitch = lockedPitch; + } + + /** + * Check if rotation is currently locked. + */ + public boolean isRotationLocked() { + return rotationLocked; + } + // ═══════════════════════════════════════════════════════════════════ // NBT SERIALIZATION // ═══════════════════════════════════════════════════════════════════ diff --git a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java index 20d2f6d5c..ae292d59d 100644 --- a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java +++ b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java @@ -481,6 +481,9 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti super.onLivingUpdate(); + // Apply locked rotation after super.onLivingUpdate() to override look helper changes + abilities.applyLockedRotation(); + handleMountRiderState(); if (worldObj.isRemote) {