From 651f2d0d84927903f2f92f80e05845d650041965 Mon Sep 17 00:00:00 2001 From: Kam Date: Mon, 26 Jan 2026 04:42:23 -0500 Subject: [PATCH] More Abilities --- .../client/renderer/RenderAbilityBeam.java | 45 +++--- .../client/renderer/RenderAbilityDisc.java | 11 +- .../client/renderer/RenderAbilityLaser.java | 27 ++-- .../client/renderer/RenderAbilityOrb.java | 16 +- .../client/renderer/RenderAbilitySweeper.java | 21 ++- .../controllers/data/ability/Ability.java | 147 +++++++++++------ .../data/ability/type/AbilityDisc.java | 37 ++++- .../data/ability/type/AbilityEnergyBeam.java | 35 ++++- .../data/ability/type/AbilityGuard.java | 13 +- .../data/ability/type/AbilityHeal.java | 5 + .../data/ability/type/AbilityLaserShot.java | 12 +- .../data/ability/type/AbilityOrb.java | 35 ++++- .../data/ability/type/AbilitySlam.java | 8 +- .../data/ability/type/AbilitySweeper.java | 2 +- .../data/telegraph/TelegraphInstance.java | 33 +++- .../npcs/entity/EntityAbilityBeam.java | 5 +- .../npcs/entity/EntityAbilityDisc.java | 5 +- .../npcs/entity/EntityAbilityLaser.java | 3 +- .../kamkeel/npcs/entity/EntityAbilityOrb.java | 5 +- .../npcs/entity/EntityAbilityProjectile.java | 19 ++- .../npcs/entity/EntityAbilitySweeper.java | 21 +++ src/main/java/noppes/npcs/DataAbilities.java | 148 +++++++++++------- .../client/gui/advanced/GuiNPCAbilities.java | 53 +++++-- .../ability/SubGuiAbilityLaserShot.java | 74 +++++++-- 24 files changed, 560 insertions(+), 220 deletions(-) diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java index 49cb4fb6c..204be9d7e 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityBeam.java @@ -61,10 +61,10 @@ public class RenderAbilityBeam extends RenderAbilityProjectile { GL11.glTranslated(renderOriginX, renderOriginY, renderOriginZ); // Render tail orb at origin (0,0,0) - static, no rotation - renderTailOrb(headSize * 0.8f, innerColor, outerColor); + renderTailOrb(headSize * 0.8f, innerColor, outerColor, beam.isOuterColorEnabled()); // Render trail segments with smooth connections - renderSmoothTrail(trail, beamWidth, innerColor, outerColor); + renderSmoothTrail(trail, beamWidth, innerColor, outerColor, beam.isOuterColorEnabled(), beam.getOuterColorWidth()); // Render head at interpolated offset position renderHead(beam, headOffsetX, headOffsetY, headOffsetZ, headSize, innerColor, outerColor, partialTicks); @@ -78,17 +78,19 @@ public class RenderAbilityBeam extends RenderAbilityProjectile { * Render the tail orb at the origin (where the beam emanates from). * This orb does not rotate - it's a stable anchor point. */ - private void renderTailOrb(float size, int innerColor, int outerColor) { + private void renderTailOrb(float size, int innerColor, int outerColor, boolean outerColorEnabled) { GL11.glPushMatrix(); // Already at origin (0,0,0) - // Render outer glow - GL11.glDepthMask(false); - GL11.glPushMatrix(); - GL11.glScalef(size, size, size); - renderCube(outerColor, 0.4f, 0.5f); - GL11.glPopMatrix(); - GL11.glDepthMask(true); + // Render outer glow only if enabled + if (outerColorEnabled) { + GL11.glDepthMask(false); + GL11.glPushMatrix(); + GL11.glScalef(size, size, size); + renderCube(outerColor, 0.4f, 0.5f); + GL11.glPopMatrix(); + GL11.glDepthMask(true); + } // Render inner core (smaller, solid) GL11.glPushMatrix(); @@ -103,7 +105,8 @@ public class RenderAbilityBeam extends RenderAbilityProjectile { * Render the beam trail with smooth connections between segments. * Uses averaged perpendicular vectors at connection points to prevent gaps. */ - private void renderSmoothTrail(List trail, float width, int innerColor, int outerColor) { + private void renderSmoothTrail(List trail, float width, int innerColor, int outerColor, + boolean outerColorEnabled, float outerColorWidth) { if (trail.size() < 2) return; int trailSize = trail.size(); @@ -112,10 +115,12 @@ public class RenderAbilityBeam extends RenderAbilityProjectile { // At connection points, average the perpendiculars for smooth transitions List perpFrames = computePerpendiculars(trail); - // Render outer glow trail (wider, translucent) - GL11.glDepthMask(false); - renderTrailTube(trail, perpFrames, width * 1.8f, outerColor, 0.3f, true); - GL11.glDepthMask(true); + // Render outer glow trail (wider, translucent) - only if enabled + if (outerColorEnabled) { + GL11.glDepthMask(false); + renderTrailTube(trail, perpFrames, width * outerColorWidth, outerColor, 0.3f, true); + GL11.glDepthMask(true); + } // Render inner core trail renderTrailTube(trail, perpFrames, width * 0.6f, innerColor, 0.9f, false); @@ -331,10 +336,12 @@ public class RenderAbilityBeam extends RenderAbilityProjectile { GL11.glPushMatrix(); GL11.glScalef(scale, scale, scale); - // Render outer glow - GL11.glDepthMask(false); - renderCube(outerColor, 0.5f, 0.5f); - GL11.glDepthMask(true); + // Render outer glow only if enabled + if (beam.isOuterColorEnabled()) { + GL11.glDepthMask(false); + renderCube(outerColor, 0.5f, 0.5f); + GL11.glDepthMask(true); + } // Render inner core float innerScale = 0.6f; diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java index 93049fad5..13de38b50 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityDisc.java @@ -39,10 +39,13 @@ public class RenderAbilityDisc extends RenderAbilityProjectile { // Apply ONLY Y-axis rotation for flat spin GL11.glRotatef(disc.getInterpolatedRotationY(partialTicks), 0.0f, 1.0f, 0.0f); - // Render outer square (translucent) - GL11.glDepthMask(false); - renderFlatSquare(disc.getOuterColor(), 0.5f, radius, thickness); - GL11.glDepthMask(true); + // Render outer square (translucent) - only if enabled + if (disc.isOuterColorEnabled()) { + float outerScale = disc.getOuterColorWidth() / 1.8f; // Normalize to default width + GL11.glDepthMask(false); + renderFlatSquare(disc.getOuterColor(), 0.5f, radius * outerScale, thickness); + GL11.glDepthMask(true); + } // Render inner square (solid, smaller) float innerScale = 0.6f; diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java index 0c1d4a1e8..fce85d2db 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java @@ -84,19 +84,22 @@ public class RenderAbilityLaser extends RenderAbilityProjectile { double vertY = dz * horzX - dx * horzZ; double vertZ = dx * horzY - dy * horzX; - // Render outer rectangle (larger, outer color, translucent) - GL11.glDepthMask(false); - renderBeamRectangle(renderStartX, renderStartY, renderStartZ, - renderEndX, renderEndY, renderEndZ, - horzX, horzY, horzZ, vertX, vertY, vertZ, - width * 1.8f, laser.getOuterColor(), alpha * 0.4f); + // Render outer rectangle (larger, outer color, translucent) - only if enabled + if (laser.isOuterColorEnabled()) { + float outerScale = laser.getOuterColorWidth(); + GL11.glDepthMask(false); + renderBeamRectangle(renderStartX, renderStartY, renderStartZ, + renderEndX, renderEndY, renderEndZ, + horzX, horzY, horzZ, vertX, vertY, vertZ, + width * outerScale, laser.getOuterColor(), alpha * 0.4f); - // Render middle layer - renderBeamRectangle(renderStartX, renderStartY, renderStartZ, - renderEndX, renderEndY, renderEndZ, - horzX, horzY, horzZ, vertX, vertY, vertZ, - width * 1.3f, laser.getOuterColor(), alpha * 0.7f); - GL11.glDepthMask(true); + // Render middle layer + renderBeamRectangle(renderStartX, renderStartY, renderStartZ, + renderEndX, renderEndY, renderEndZ, + horzX, horzY, horzZ, vertX, vertY, vertZ, + width * 1.3f, laser.getOuterColor(), alpha * 0.7f); + GL11.glDepthMask(true); + } // Render inner core (smaller, inner color, solid) renderBeamRectangle(renderStartX, renderStartY, renderStartZ, diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityOrb.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityOrb.java index 3a09e7b6a..39048648d 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityOrb.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityOrb.java @@ -69,11 +69,17 @@ public class RenderAbilityOrb extends Render { GL11.glRotatef(orb.getInterpolatedRotationY(partialTicks), 0.0f, 1.0f, 0.0f); GL11.glRotatef(orb.getInterpolatedRotationZ(partialTicks), 0.0f, 0.0f, 1.0f); - // Render outer sphere (translucent) - GL11.glDepthMask(false); - this.bindTexture(WHITE_TEXTURE); - renderSphere(orb.getOuterColor(), 0.6f); - GL11.glDepthMask(true); + // Render outer sphere (translucent) - only if enabled + if (orb.isOuterColorEnabled()) { + GL11.glDepthMask(false); + this.bindTexture(WHITE_TEXTURE); + float outerScale = orb.getOuterColorWidth() / 1.8f; // Normalize to default width + GL11.glPushMatrix(); + GL11.glScalef(outerScale, outerScale, outerScale); + renderSphere(orb.getOuterColor(), 0.6f); + GL11.glPopMatrix(); + GL11.glDepthMask(true); + } // Render inner sphere (more opaque) float innerScale = 0.75f; diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilitySweeper.java b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilitySweeper.java index 735c1e183..0a24f1773 100644 --- a/src/main/java/kamkeel/npcs/client/renderer/RenderAbilitySweeper.java +++ b/src/main/java/kamkeel/npcs/client/renderer/RenderAbilitySweeper.java @@ -26,6 +26,8 @@ public class RenderAbilitySweeper extends Render { float beamWidth = sweeper.getBeamWidth(); int innerColor = sweeper.getInnerColor(); int outerColor = sweeper.getOuterColor(); + boolean outerColorEnabled = sweeper.isOuterColorEnabled(); + float outerColorWidth = sweeper.getOuterColorWidth(); float angle = sweeper.getInterpolatedAngle(partialTicks); GL11.glPushMatrix(); @@ -35,7 +37,7 @@ public class RenderAbilitySweeper extends Render { GL11.glRotatef(-angle, 0, 1, 0); // Negative for correct direction // Render the beam extending in the +Z direction (like beam trail segments) - renderBeam(beamLength, beamWidth, innerColor, outerColor); + renderBeam(beamLength, beamWidth, innerColor, outerColor, outerColorEnabled, outerColorWidth); GL11.glPopMatrix(); @@ -65,7 +67,8 @@ public class RenderAbilitySweeper extends Render { * Render beam using the same style as the Beam entity trail. * Three layers: outer glow, middle, inner core. */ - private void renderBeam(float length, float width, int innerColor, int outerColor) { + private void renderBeam(float length, float width, int innerColor, int outerColor, + boolean outerColorEnabled, float outerColorWidth) { // Extract colors float outerR = ((outerColor >> 16) & 0xFF) / 255.0f; float outerG = ((outerColor >> 8) & 0xFF) / 255.0f; @@ -78,13 +81,15 @@ public class RenderAbilitySweeper extends Render { // 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 - GL11.glDepthMask(false); - renderBeamSegment(0, 0, 0, 0, 0, length, width * 1.8f, outerR, outerG, outerB, 0.3f); - GL11.glDepthMask(true); + // Outer glow (wider, translucent) - like beam trail - only if enabled + if (outerColorEnabled) { + GL11.glDepthMask(false); + renderBeamSegment(0, 0, 0, 0, 0, length, width * outerColorWidth, 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 + renderBeamSegment(0, 0, 0, 0, 0, length, width * 1.3f, 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); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java index 18406c313..3f2165718 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -53,6 +53,10 @@ public abstract class Ability implements IAbility { // Interruption protected boolean interruptible = true; + // Wait for completion - if true, ability controls when active phase ends (ignores activeTicks) + // Used for abilities like Slam that need to wait for landing, or Beam that runs until entity dies + protected boolean waitForCompletion = false; + // Feedback protected boolean lockMovement = true; protected int windUpColor = 0x80FF4400; // Telegraph color during wind up @@ -133,7 +137,9 @@ public abstract class Ability implements IAbility { */ protected boolean applyAbilityDamage(EntityNPCInterface npc, EntityLivingBase hitEntity, float damage, float knockback) { - return applyAbilityDamage(npc, hitEntity, damage, knockback, 0.0f); + double dx = hitEntity.posX - npc.posX; + double dz = hitEntity.posZ - npc.posZ; + return applyAbilityDamageInternal(npc, hitEntity, damage, knockback, 0.0f, dx, dz); } /** @@ -149,6 +155,36 @@ public abstract class Ability implements IAbility { */ protected boolean applyAbilityDamage(EntityNPCInterface npc, EntityLivingBase hitEntity, float damage, float knockback, float knockbackUp) { + double dx = hitEntity.posX - npc.posX; + double dz = hitEntity.posZ - npc.posZ; + return applyAbilityDamageInternal(npc, hitEntity, damage, knockback, knockbackUp, dx, dz); + } + + /** + * Apply damage to an entity with ability hit event support and custom knockback direction. + * Fires the abilityHit script event, allowing scripts to modify or cancel the damage. + * + * @param npc The NPC executing the ability + * @param hitEntity The entity being hit + * @param damage The damage amount + * @param knockback The horizontal knockback + * @param knockbackDirX The X component of knockback direction + * @param knockbackDirZ The Z component of knockback direction + * @return true if damage was applied (not cancelled), false if cancelled + */ + protected boolean applyAbilityDamageWithDirection(EntityNPCInterface npc, EntityLivingBase hitEntity, + float damage, float knockback, + double knockbackDirX, double knockbackDirZ) { + return applyAbilityDamageInternal(npc, hitEntity, damage, knockback, 0.0f, knockbackDirX, knockbackDirZ); + } + + /** + * Internal unified damage application method. + * All public damage methods delegate to this. + */ + private boolean applyAbilityDamageInternal(EntityNPCInterface npc, EntityLivingBase hitEntity, + float damage, float knockback, float knockbackUp, + double knockbackDirX, double knockbackDirZ) { DataAbilities dataAbilities = npc.abilities; AbilityEvent.HitEvent event = dataAbilities.fireHitEvent( this, currentTarget, hitEntity, damage, knockback, knockbackUp); @@ -168,49 +204,6 @@ public abstract class Ability implements IAbility { } // Apply knockback if any - if (finalKnockback > 0 || finalKnockbackUp > 0) { - double dx = hitEntity.posX - npc.posX; - double dz = hitEntity.posZ - npc.posZ; - applyNpcKnockback(hitEntity, dx, dz, finalKnockback, finalKnockbackUp); - } - - return true; - } - - /** - * Apply damage to an entity with ability hit event support and custom knockback direction. - * Fires the abilityHit script event, allowing scripts to modify or cancel the damage. - * - * @param npc The NPC executing the ability - * @param hitEntity The entity being hit - * @param damage The damage amount - * @param knockback The horizontal knockback - * @param knockbackDirX The X component of knockback direction (normalized) - * @param knockbackDirZ The Z component of knockback direction (normalized) - * @return true if damage was applied (not cancelled), false if cancelled - */ - protected boolean applyAbilityDamageWithDirection(EntityNPCInterface npc, EntityLivingBase hitEntity, - float damage, float knockback, - double knockbackDirX, double knockbackDirZ) { - DataAbilities dataAbilities = npc.abilities; - AbilityEvent.HitEvent event = dataAbilities.fireHitEvent( - this, currentTarget, hitEntity, damage, knockback, 0.0f); - - if (event == null) { - return false; // Cancelled - } - - // Apply damage with potentially modified values - float finalDamage = event.getDamage(); - float finalKnockback = event.getKnockback(); - float finalKnockbackUp = event.getKnockbackUp(); - - // Apply damage - use NpcDamageSource for consistency - if (finalDamage > 0) { - hitEntity.attackEntityFrom(new NpcDamageSource("mob", npc), finalDamage); - } - - // Apply knockback in specified direction if (finalKnockback > 0 || finalKnockbackUp > 0) { applyNpcKnockback(hitEntity, knockbackDirX, knockbackDirZ, finalKnockback, finalKnockbackUp); } @@ -383,6 +376,11 @@ public abstract class Ability implements IAbility { if (positionAtNpc) { // AOE_SELF abilities: telegraph follows NPC during windup instance.setEntityIdToFollow(npc.getEntityId()); + + // For LINE/CONE telegraphs: track target direction during windup + if ((telegraphType == TelegraphType.LINE || telegraphType == TelegraphType.CONE) && target != null) { + instance.setTargetEntityId(target.getEntityId()); + } } else if (target != null) { // AOE_TARGET abilities: telegraph follows target during windup instance.setEntityIdToFollow(target.getEntityId()); @@ -487,8 +485,15 @@ public abstract class Ability implements IAbility { } /** - * Tick the ability. Returns true if phase changed. - * Note: Cooldown is managed by DataAbilities when the ability completes. + * Tick the ability forward. Called every game tick while executing. + *

+ * Normal flow: WINDUP -> ACTIVE -> IDLE (no recovery in normal completion) + * Interrupted flow: WINDUP -> RECOVERY -> IDLE (recovery only on interrupt) + *

+ * If waitForCompletion is true, the ability controls when active ends + * by calling signalCompletion() instead of using activeTicks. + * + * @return true if phase changed this tick */ public boolean tick() { if (phase == AbilityPhase.IDLE) return false; @@ -504,16 +509,20 @@ public abstract class Ability implements IAbility { } break; case ACTIVE: - if (currentTick >= activeTicks) { - phase = AbilityPhase.RECOVERY; + // If waitForCompletion is true, ability must call signalCompletion() + // Otherwise, auto-complete when activeTicks is reached + if (!waitForCompletion && currentTick >= activeTicks) { + phase = AbilityPhase.IDLE; currentTick = 0; return true; } break; case RECOVERY: + // Recovery phase is only entered on interrupt + // Count down recoveryTicks then go to IDLE if (currentTick >= recoveryTicks) { phase = AbilityPhase.IDLE; - // Cooldown is managed by DataAbilities.onAbilityComplete() + currentTick = 0; return true; } break; @@ -522,11 +531,35 @@ public abstract class Ability implements IAbility { } /** - * Interrupt this ability + * Signal that the ability has completed its active phase. + * Called by abilities with waitForCompletion=true when they are done. + * For example, AbilitySlam calls this when the NPC lands. + * + * @return true if phase changed to IDLE + */ + public boolean signalCompletion() { + if (phase == AbilityPhase.ACTIVE) { + phase = AbilityPhase.IDLE; + currentTick = 0; + return true; + } + return false; + } + + /** + * Interrupt this ability. Transitions to RECOVERY phase if interruptible. + * Called when NPC takes direct damage during WINDUP. */ public void interrupt() { - phase = AbilityPhase.IDLE; - currentTick = 0; + if (interruptible && phase == AbilityPhase.WINDUP) { + // Interrupted during windup - go to recovery (dazed state) + phase = AbilityPhase.RECOVERY; + currentTick = 0; + } else { + // Force stop - go directly to IDLE + phase = AbilityPhase.IDLE; + currentTick = 0; + } currentTarget = null; telegraphInstance = null; } @@ -611,6 +644,7 @@ public abstract class Ability implements IAbility { nbt.setInteger("active", activeTicks); nbt.setInteger("recovery", recoveryTicks); nbt.setBoolean("interruptible", interruptible); + nbt.setBoolean("waitForCompletion", waitForCompletion); nbt.setBoolean("lockMovement", lockMovement); nbt.setInteger("windUpColor", windUpColor); nbt.setInteger("activeColor", activeColor); @@ -650,6 +684,7 @@ public abstract class Ability implements IAbility { activeTicks = nbt.getInteger("active"); recoveryTicks = nbt.getInteger("recovery"); interruptible = nbt.getBoolean("interruptible"); + waitForCompletion = nbt.getBoolean("waitForCompletion"); lockMovement = nbt.getBoolean("lockMovement"); // Support old telegraphColor key for backwards compatibility if (nbt.hasKey("windUpColor")) { @@ -807,6 +842,14 @@ public abstract class Ability implements IAbility { this.interruptible = interruptible; } + public boolean isWaitForCompletion() { + return waitForCompletion; + } + + public void setWaitForCompletion(boolean waitForCompletion) { + this.waitForCompletion = waitForCompletion; + } + public boolean isLockMovement() { return lockMovement; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDisc.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDisc.java index 97d69e19c..0db44ea8a 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDisc.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDisc.java @@ -4,6 +4,8 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.telegraph.Telegraph; +import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import kamkeel.npcs.entity.EntityAbilityDisc; import net.minecraft.entity.EntityLivingBase; @@ -69,8 +71,8 @@ public class AbilityDisc extends Ability { this.windUpTicks = 20; this.activeTicks = 1; this.recoveryTicks = 10; - this.telegraphType = TelegraphType.POINT; - this.showTelegraph = false; + this.telegraphType = TelegraphType.CIRCLE; + this.showTelegraph = true; } @Override @@ -105,7 +107,7 @@ public class AbilityDisc extends Ability { EntityAbilityDisc disc = new EntityAbilityDisc( world, npc, target, spawnX, spawnY, spawnZ, - discRadius, discThickness, innerColor, outerColor, rotationSpeed, + discRadius, discThickness, innerColor, outerColor, outerColorEnabled, outerColorWidth, rotationSpeed, damage, knockback, knockbackUp, speed, homing, homingStrength, homingRange, boomerang, boomerangDelay, @@ -129,6 +131,33 @@ public class AbilityDisc extends Ability { public void onInterrupt(EntityNPCInterface npc, DamageSource source, float damage) { } + @Override + public TelegraphInstance createTelegraph(EntityNPCInterface npc, EntityLivingBase target) { + if (!showTelegraph || telegraphType == TelegraphType.NONE || target == null) { + return null; + } + + // Create circle telegraph at target position + Telegraph telegraph = Telegraph.circle(discRadius * 1.5f); + telegraph.setDurationTicks(windUpTicks); + telegraph.setColor(windUpColor); + telegraph.setWarningColor(activeColor); + telegraph.setWarningStartTick(Math.max(5, windUpTicks / 4)); + telegraph.setHeightOffset(telegraphHeightOffset); + + // Position at target and follow target during windup + TelegraphInstance instance = new TelegraphInstance(telegraph, target.posX, target.posY, target.posZ, npc.rotationYaw); + instance.setCasterEntityId(npc.getEntityId()); + instance.setEntityIdToFollow(target.getEntityId()); + + return instance; + } + + @Override + public float getTelegraphRadius() { + return discRadius * 1.5f; + } + @Override public void writeTypeNBT(NBTTagCompound nbt) { nbt.setFloat("speed", speed); @@ -182,7 +211,7 @@ public class AbilityDisc extends Ability { this.outerColor = nbt.hasKey("outerColor") ? nbt.getInteger("outerColor") : 0xFF8800; this.outerColorWidth = nbt.hasKey("outerColorWidth") ? nbt.getFloat("outerColorWidth") : 1.8f; this.outerColorEnabled = !nbt.hasKey("outerColorEnabled") || nbt.getBoolean("outerColorEnabled"); - this.rotationSpeed = nbt.hasKey("rotationSpeed") ? nbt.getFloat("rotationSpeed") : 8.0f; + this.rotationSpeed = nbt.hasKey("rotationSpeed") ? nbt.getFloat("rotationSpeed") : 5.0f; } // Getters & Setters diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyBeam.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyBeam.java index 2790eafd8..58a224c64 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyBeam.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityEnergyBeam.java @@ -4,6 +4,8 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.telegraph.Telegraph; +import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import kamkeel.npcs.entity.EntityAbilityBeam; import net.minecraft.entity.EntityLivingBase; @@ -68,8 +70,8 @@ public class AbilityEnergyBeam extends Ability { this.windUpTicks = 40; this.activeTicks = 200; // Match maxLifetime to keep NPC locked while beam is active this.recoveryTicks = 5; - this.telegraphType = TelegraphType.POINT; - this.showTelegraph = false; + this.telegraphType = TelegraphType.CIRCLE; + this.showTelegraph = true; } @Override @@ -104,7 +106,7 @@ public class AbilityEnergyBeam extends Ability { EntityAbilityBeam beam = new EntityAbilityBeam( world, npc, target, spawnX, spawnY, spawnZ, - beamWidth, headSize, innerColor, outerColor, rotationSpeed, + beamWidth, headSize, innerColor, outerColor, outerColorEnabled, outerColorWidth, rotationSpeed, damage, knockback, knockbackUp, speed, homing, homingStrength, homingRange, explosive, explosionRadius, explosionDamageFalloff, @@ -127,6 +129,33 @@ public class AbilityEnergyBeam extends Ability { public void onInterrupt(EntityNPCInterface npc, DamageSource source, float damage) { } + @Override + public TelegraphInstance createTelegraph(EntityNPCInterface npc, EntityLivingBase target) { + if (!showTelegraph || telegraphType == TelegraphType.NONE || target == null) { + return null; + } + + // Create circle telegraph at target position + Telegraph telegraph = Telegraph.circle(headSize * 2.0f); + telegraph.setDurationTicks(windUpTicks); + telegraph.setColor(windUpColor); + telegraph.setWarningColor(activeColor); + telegraph.setWarningStartTick(Math.max(5, windUpTicks / 4)); + telegraph.setHeightOffset(telegraphHeightOffset); + + // Position at target and follow target during windup + TelegraphInstance instance = new TelegraphInstance(telegraph, target.posX, target.posY, target.posZ, npc.rotationYaw); + instance.setCasterEntityId(npc.getEntityId()); + instance.setEntityIdToFollow(target.getEntityId()); + + return instance; + } + + @Override + public float getTelegraphRadius() { + return headSize * 2.0f; + } + @Override public void writeTypeNBT(NBTTagCompound nbt) { nbt.setFloat("speed", speed); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java index 6a98b643a..404589c6e 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityGuard.java @@ -128,17 +128,8 @@ public class AbilityGuard extends Ability { if (wasHit && counterSound != null && !counterSound.isEmpty()) { world.playSoundAtEntity(npc, counterSound, 1.0f, 1.2f); } - if (counterAnimationId >= 0) { - if (noppes.npcs.controllers.AnimationController.Instance != null) { - noppes.npcs.controllers.data.Animation animation = - noppes.npcs.controllers.AnimationController.Instance.animations.get(counterAnimationId); - if (animation != null) { - npc.display.animationData.setEnabled(true); - npc.display.animationData.setAnimation(animation); - npc.display.animationData.updateClient(); - } - } - } + // Use shared animation utility + npc.abilities.playAbilityAnimation(counterAnimationId); counterTriggered = false; lastAttacker = null; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeal.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeal.java index 2e23a1e56..3cf04db2c 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeal.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityHeal.java @@ -184,6 +184,11 @@ public class AbilityHeal extends Ability { } } + @Override + public void onInterrupt(EntityNPCInterface npc, net.minecraft.util.DamageSource source, float damage) { + healedAllies.clear(); + } + @Override public void reset() { super.reset(); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityLaserShot.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityLaserShot.java index 2b224e400..64a3da8ad 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityLaserShot.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityLaserShot.java @@ -46,6 +46,8 @@ public class AbilityLaserShot extends Ability { // Visual properties private int innerColor = 0xFFFFFF; private int outerColor = 0xFF0000; + private float outerColorWidth = 1.8f; + private boolean outerColorEnabled = true; public AbilityLaserShot() { this.typeId = "ability.cnpc.laser_shot"; @@ -103,7 +105,7 @@ public class AbilityLaserShot extends Ability { EntityAbilityLaser laser = new EntityAbilityLaser( world, npc, target, spawnX, spawnY, spawnZ, - laserWidth, innerColor, outerColor, + laserWidth, innerColor, outerColor, outerColorEnabled, outerColorWidth, damage, knockback, knockbackUp, expansionSpeed, lingerTicks, explosive, explosionRadius, explosionDamageFalloff, @@ -144,6 +146,8 @@ public class AbilityLaserShot extends Ability { nbt.setInteger("slowLevel", slowLevel); nbt.setInteger("innerColor", innerColor); nbt.setInteger("outerColor", outerColor); + nbt.setFloat("outerColorWidth", outerColorWidth); + nbt.setBoolean("outerColorEnabled", outerColorEnabled); } @Override @@ -164,6 +168,8 @@ public class AbilityLaserShot extends Ability { this.slowLevel = nbt.hasKey("slowLevel") ? nbt.getInteger("slowLevel") : 0; this.innerColor = nbt.hasKey("innerColor") ? nbt.getInteger("innerColor") : 0xFFFFFF; this.outerColor = nbt.hasKey("outerColor") ? nbt.getInteger("outerColor") : 0xFF0000; + this.outerColorWidth = nbt.hasKey("outerColorWidth") ? nbt.getFloat("outerColorWidth") : 1.8f; + this.outerColorEnabled = !nbt.hasKey("outerColorEnabled") || nbt.getBoolean("outerColorEnabled"); } // Getters & Setters @@ -199,4 +205,8 @@ public class AbilityLaserShot extends Ability { public void setInnerColor(int innerColor) { this.innerColor = innerColor; } public int getOuterColor() { return outerColor; } public void setOuterColor(int outerColor) { this.outerColor = outerColor; } + public float getOuterColorWidth() { return outerColorWidth; } + public void setOuterColorWidth(float outerColorWidth) { this.outerColorWidth = outerColorWidth; } + public boolean isOuterColorEnabled() { return outerColorEnabled; } + public void setOuterColorEnabled(boolean outerColorEnabled) { this.outerColorEnabled = outerColorEnabled; } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrb.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrb.java index ff03ac39f..7b6f34b68 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrb.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityOrb.java @@ -4,6 +4,8 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.TargetingMode; +import kamkeel.npcs.controllers.data.telegraph.Telegraph; +import kamkeel.npcs.controllers.data.telegraph.TelegraphInstance; import kamkeel.npcs.controllers.data.telegraph.TelegraphType; import kamkeel.npcs.entity.EntityAbilityOrb; import net.minecraft.entity.EntityLivingBase; @@ -65,8 +67,8 @@ public class AbilityOrb extends Ability { this.windUpTicks = 30; this.activeTicks = 1; // Minimal - just spawn and done this.recoveryTicks = 10; - this.telegraphType = TelegraphType.POINT; - this.showTelegraph = false; + this.telegraphType = TelegraphType.CIRCLE; + this.showTelegraph = true; } @Override @@ -103,7 +105,7 @@ public class AbilityOrb extends Ability { EntityAbilityOrb orb = new EntityAbilityOrb( world, npc, target, spawnX, spawnY, spawnZ, - orbSize, innerColor, outerColor, rotationSpeed, + orbSize, innerColor, outerColor, outerColorEnabled, outerColorWidth, rotationSpeed, damage, knockback, knockbackUp, orbSpeed, homing, homingStrength, homingRange, explosive, explosionRadius, explosionDamageFalloff, @@ -129,6 +131,33 @@ public class AbilityOrb extends Ability { // Nothing to clean up - entity manages itself } + @Override + public TelegraphInstance createTelegraph(EntityNPCInterface npc, EntityLivingBase target) { + if (!showTelegraph || telegraphType == TelegraphType.NONE || target == null) { + return null; + } + + // Create small circle telegraph at target position + Telegraph telegraph = Telegraph.circle(orbSize * 1.5f); + telegraph.setDurationTicks(windUpTicks); + telegraph.setColor(windUpColor); + telegraph.setWarningColor(activeColor); + telegraph.setWarningStartTick(Math.max(5, windUpTicks / 4)); + telegraph.setHeightOffset(telegraphHeightOffset); + + // Position at target and follow target during windup + TelegraphInstance instance = new TelegraphInstance(telegraph, target.posX, target.posY, target.posZ, npc.rotationYaw); + instance.setCasterEntityId(npc.getEntityId()); + instance.setEntityIdToFollow(target.getEntityId()); + + return instance; + } + + @Override + public float getTelegraphRadius() { + return orbSize * 1.5f; + } + @Override public void writeTypeNBT(NBTTagCompound nbt) { nbt.setFloat("orbSpeed", orbSpeed); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java index 6068b269f..d2c17a3e8 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySlam.java @@ -49,10 +49,11 @@ public class AbilitySlam extends Ability { this.name = "Slam"; this.targetingMode = TargetingMode.AOE_SELF; // Can also be AOE_TARGET to leap to target this.windUpTicks = 30; - this.activeTicks = 60; // Max time in air + landing + this.activeTicks = 60; // Max time in air (used as timeout if waitForCompletion fails) this.recoveryTicks = 10; this.cooldownTicks = 100; this.lockMovement = true; + this.waitForCompletion = true; // Ability completes when NPC lands, not by activeTicks this.minRange = 2.0f; this.maxRange = 15.0f; this.telegraphType = TelegraphType.CIRCLE; @@ -258,12 +259,15 @@ public class AbilitySlam extends Ability { } /** - * Called when NPC lands - deal AOE damage. + * Called when NPC lands - deal AOE damage and signal completion. */ private void onLanding(EntityNPCInterface npc, World world) { hasLanded = true; npc.setNpcJumpingState(false); + // Signal that the ability has completed its active phase + signalCompletion(); + // Reset fall distance to prevent fall damage on landing npc.fallDistance = 0; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySweeper.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySweeper.java index d1a67eb3a..b7ca2aa22 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySweeper.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilitySweeper.java @@ -92,7 +92,7 @@ public class AbilitySweeper extends Ability { // Spawn the entity that handles BOTH visuals AND damage activeEntity = new EntityAbilitySweeper(world, npc, target, beamLength, beamWidth, beamHeight, - innerColor, outerColor, + innerColor, outerColor, outerColorEnabled, outerColorWidth, sweepSpeed, numberOfRotations, damage, damageInterval, piercing, lockOnTarget); diff --git a/src/main/java/kamkeel/npcs/controllers/data/telegraph/TelegraphInstance.java b/src/main/java/kamkeel/npcs/controllers/data/telegraph/TelegraphInstance.java index 47fdf8cbb..71d63f9c6 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/telegraph/TelegraphInstance.java +++ b/src/main/java/kamkeel/npcs/controllers/data/telegraph/TelegraphInstance.java @@ -26,6 +26,7 @@ public class TelegraphInstance { // Entity tracking private int entityIdToFollow = -1; private int casterEntityId = -1; + private int targetEntityId = -1; // For LINE telegraphs: entity to face (yaw tracking) // Timing private int remainingTicks; @@ -81,6 +82,16 @@ public class TelegraphInstance { } } + // Update yaw to face target entity (for LINE telegraphs) + if (targetEntityId >= 0 && world != null) { + Entity target = world.getEntityByID(targetEntityId); + if (target != null) { + double dx = target.posX - this.x; + double dz = target.posZ - this.z; + this.yaw = (float) (Math.atan2(-dx, dz) * 180.0 / Math.PI); + } + } + // Check for warning phase transition if (!isWarning && telegraph != null) { if (remainingTicks <= telegraph.getWarningStartTick()) { @@ -108,7 +119,7 @@ public class TelegraphInstance { } /** - * Get alpha-modulated color for pulsing animation. + * Get alpha-modulated color for breathing animation. */ public int getAnimatedColor(float partialTicks) { int baseColor = getCurrentColor(); @@ -116,9 +127,12 @@ public class TelegraphInstance { return baseColor; } - // Pulsing animation: modulate alpha based on time + // Breathing animation: slow, smooth modulation of alpha float time = (totalTicks - remainingTicks) + partialTicks; - float pulse = (float) (Math.sin(time * 0.3) * 0.3 + 0.7); // 0.4 to 1.0 range + // Slower breathing: 0.08 for ~4 second cycle, smoother sine curve + float breathPhase = (float) Math.sin(time * 0.08); + // Smooth easing: square the sine for more organic feel, range 0.5 to 1.0 + float pulse = 0.5f + (breathPhase * breathPhase * 0.5f * Math.signum(breathPhase)); int alpha = (baseColor >> 24) & 0xFF; int r = (baseColor >> 16) & 0xFF; @@ -149,6 +163,7 @@ public class TelegraphInstance { nbt.setInteger("entityIdToFollow", entityIdToFollow); nbt.setInteger("casterEntityId", casterEntityId); + nbt.setInteger("targetEntityId", targetEntityId); nbt.setInteger("remainingTicks", remainingTicks); nbt.setInteger("totalTicks", totalTicks); @@ -173,6 +188,7 @@ public class TelegraphInstance { this.entityIdToFollow = nbt.getInteger("entityIdToFollow"); this.casterEntityId = nbt.getInteger("casterEntityId"); + this.targetEntityId = nbt.getInteger("targetEntityId"); this.remainingTicks = nbt.getInteger("remainingTicks"); this.totalTicks = nbt.getInteger("totalTicks"); @@ -241,11 +257,12 @@ public class TelegraphInstance { } /** - * Locks the telegraph at its current position. + * Locks the telegraph at its current position and direction. * Stops following any entity - used when ability commits to action. */ public void lockPosition() { this.entityIdToFollow = -1; + this.targetEntityId = -1; } public int getCasterEntityId() { @@ -256,6 +273,14 @@ public class TelegraphInstance { this.casterEntityId = casterEntityId; } + public int getTargetEntityId() { + return targetEntityId; + } + + public void setTargetEntityId(int targetEntityId) { + this.targetEntityId = targetEntityId; + } + public int getRemainingTicks() { return remainingTicks; } diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java index 586e57ae9..b0c3579e7 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java @@ -59,7 +59,8 @@ public class EntityAbilityBeam extends EntityAbilityProjectile { */ public EntityAbilityBeam(World world, EntityNPCInterface owner, EntityLivingBase target, double x, double y, double z, - float beamWidth, float headSize, int innerColor, int outerColor, float rotationSpeed, + float beamWidth, float headSize, int innerColor, int outerColor, + boolean outerColorEnabled, float outerColorWidth, float rotationSpeed, float damage, float knockback, float knockbackUp, float speed, boolean homing, float homingStrength, float homingRange, boolean explosive, float explosionRadius, float explosionDamageFalloff, @@ -69,7 +70,7 @@ public class EntityAbilityBeam extends EntityAbilityProjectile { // Initialize base properties initProjectile(owner, target, x, y, z, - headSize, innerColor, outerColor, rotationSpeed, + headSize, innerColor, outerColor, outerColorEnabled, outerColorWidth, rotationSpeed, damage, knockback, knockbackUp, explosive, explosionRadius, explosionDamageFalloff, stunDuration, slowDuration, slowLevel, diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java index e46542d8c..ef513f80d 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java @@ -44,7 +44,8 @@ public class EntityAbilityDisc extends EntityAbilityProjectile { */ public EntityAbilityDisc(World world, EntityNPCInterface owner, EntityLivingBase target, double x, double y, double z, - float discRadius, float discThickness, int innerColor, int outerColor, float rotationSpeed, + float discRadius, float discThickness, int innerColor, int outerColor, + boolean outerColorEnabled, float outerColorWidth, float rotationSpeed, float damage, float knockback, float knockbackUp, float speed, boolean homing, float homingStrength, float homingRange, boolean boomerang, int boomerangDelay, @@ -55,7 +56,7 @@ public class EntityAbilityDisc extends EntityAbilityProjectile { // Initialize base properties (use discRadius as size for base) initProjectile(owner, target, x, y, z, - discRadius, innerColor, outerColor, rotationSpeed, + discRadius, innerColor, outerColor, outerColorEnabled, outerColorWidth, rotationSpeed, damage, knockback, knockbackUp, explosive, explosionRadius, explosionDamageFalloff, stunDuration, slowDuration, slowLevel, diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java index 02b797524..78668377a 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java @@ -50,6 +50,7 @@ public class EntityAbilityLaser extends EntityAbilityProjectile { public EntityAbilityLaser(World world, EntityNPCInterface owner, EntityLivingBase target, double x, double y, double z, float laserWidth, int innerColor, int outerColor, + boolean outerColorEnabled, float outerColorWidth, float damage, float knockback, float knockbackUp, float expansionSpeed, int lingerTicks, boolean explosive, float explosionRadius, float explosionDamageFalloff, @@ -59,7 +60,7 @@ public class EntityAbilityLaser extends EntityAbilityProjectile { // Initialize base properties initProjectile(owner, target, x, y, z, - laserWidth, innerColor, outerColor, 0.0f, // No rotation for laser + laserWidth, innerColor, outerColor, outerColorEnabled, outerColorWidth, 0.0f, // No rotation for laser damage, knockback, knockbackUp, explosive, explosionRadius, explosionDamageFalloff, stunDuration, slowDuration, slowLevel, diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java index c218007a0..de447333b 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityOrb.java @@ -34,7 +34,8 @@ public class EntityAbilityOrb extends EntityAbilityProjectile { */ public EntityAbilityOrb(World world, EntityNPCInterface owner, EntityLivingBase target, double x, double y, double z, - float orbSize, int innerColor, int outerColor, float rotationSpeed, + float orbSize, int innerColor, int outerColor, + boolean outerColorEnabled, float outerColorWidth, float rotationSpeed, float damage, float knockback, float knockbackUp, float speed, boolean homing, float homingStrength, float homingRange, boolean explosive, float explosionRadius, float explosionDamageFalloff, @@ -44,7 +45,7 @@ public class EntityAbilityOrb extends EntityAbilityProjectile { // Initialize base properties initProjectile(owner, target, x, y, z, - orbSize, innerColor, outerColor, rotationSpeed, + orbSize, innerColor, outerColor, outerColorEnabled, outerColorWidth, rotationSpeed, damage, knockback, knockbackUp, explosive, explosionRadius, explosionDamageFalloff, stunDuration, slowDuration, slowLevel, diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityProjectile.java index fa84d933f..eed12d10b 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilityProjectile.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityProjectile.java @@ -29,6 +29,8 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA protected float size = 1.0f; protected int innerColor = 0xFFFFFF; protected int outerColor = 0x8888FF; + protected boolean outerColorEnabled = true; + protected float outerColorWidth = 1.8f; protected float rotationSpeed = 4.0f; // ==================== COMBAT PROPERTIES ==================== @@ -85,7 +87,8 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA */ protected void initProjectile(EntityNPCInterface owner, EntityLivingBase target, double x, double y, double z, - float size, int innerColor, int outerColor, float rotationSpeed, + float size, int innerColor, int outerColor, + boolean outerColorEnabled, float outerColorWidth, float rotationSpeed, float damage, float knockback, float knockbackUp, boolean explosive, float explosionRadius, float explosionDamageFalloff, int stunDuration, int slowDuration, int slowLevel, @@ -102,6 +105,8 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA this.size = size; this.innerColor = innerColor; this.outerColor = outerColor; + this.outerColorEnabled = outerColorEnabled; + this.outerColorWidth = outerColorWidth; this.rotationSpeed = rotationSpeed; // Combat @@ -384,6 +389,14 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA return outerColor; } + public boolean isOuterColorEnabled() { + return outerColorEnabled; + } + + public float getOuterColorWidth() { + return outerColorWidth; + } + public float getRotationSpeed() { return rotationSpeed; } @@ -438,6 +451,8 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA this.size = nbt.getFloat("Size"); this.innerColor = nbt.getInteger("InnerColor"); this.outerColor = nbt.getInteger("OuterColor"); + this.outerColorEnabled = !nbt.hasKey("OuterColorEnabled") || nbt.getBoolean("OuterColorEnabled"); + this.outerColorWidth = nbt.hasKey("OuterColorWidth") ? nbt.getFloat("OuterColorWidth") : 1.8f; this.rotationSpeed = nbt.hasKey("RotationSpeed") ? nbt.getFloat("RotationSpeed") : 4.0f; this.damage = nbt.getFloat("Damage"); @@ -480,6 +495,8 @@ public abstract class EntityAbilityProjectile extends Entity implements IEntityA nbt.setFloat("Size", size); nbt.setInteger("InnerColor", innerColor); nbt.setInteger("OuterColor", outerColor); + nbt.setBoolean("OuterColorEnabled", outerColorEnabled); + nbt.setFloat("OuterColorWidth", outerColorWidth); nbt.setFloat("RotationSpeed", rotationSpeed); nbt.setFloat("Damage", damage); diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java b/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java index a5758a2ed..6ec4f8b38 100644 --- a/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java +++ b/src/main/java/kamkeel/npcs/entity/EntityAbilitySweeper.java @@ -32,6 +32,8 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa private float beamHeight = 0.8f; private int innerColor = 0xFF6600; private int outerColor = 0xFF0000; + private boolean outerColorEnabled = true; + private float outerColorWidth = 1.8f; // Combat properties private float damage = 5.0f; @@ -70,6 +72,7 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa public EntityAbilitySweeper(World world, EntityNPCInterface owner, EntityLivingBase target, float beamLength, float beamWidth, float beamHeight, int innerColor, int outerColor, + boolean outerColorEnabled, float outerColorWidth, float sweepSpeed, int numberOfRotations, float damage, int damageInterval, boolean piercing, boolean lockOnTarget) { @@ -82,6 +85,8 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa this.beamHeight = beamHeight; this.innerColor = innerColor; this.outerColor = outerColor; + this.outerColorEnabled = outerColorEnabled; + this.outerColorWidth = outerColorWidth; this.sweepSpeed = sweepSpeed; this.numberOfRotations = numberOfRotations; this.damage = damage; @@ -323,6 +328,14 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa return outerColor; } + public boolean isOuterColorEnabled() { + return outerColorEnabled; + } + + public float getOuterColorWidth() { + return outerColorWidth; + } + /** * Get the full interpolated angle INCLUDING base yaw for rendering. */ @@ -353,6 +366,8 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa this.beamHeight = nbt.getFloat("BeamHeight"); this.innerColor = nbt.getInteger("InnerColor"); this.outerColor = nbt.getInteger("OuterColor"); + this.outerColorEnabled = !nbt.hasKey("OuterColorEnabled") || nbt.getBoolean("OuterColorEnabled"); + this.outerColorWidth = nbt.hasKey("OuterColorWidth") ? nbt.getFloat("OuterColorWidth") : 1.8f; this.sweepSpeed = nbt.getFloat("SweepSpeed"); this.numberOfRotations = nbt.getInteger("NumRotations"); this.completedRotations = nbt.getInteger("CompletedRotations"); @@ -375,6 +390,8 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa nbt.setFloat("BeamHeight", beamHeight); nbt.setInteger("InnerColor", innerColor); nbt.setInteger("OuterColor", outerColor); + nbt.setBoolean("OuterColorEnabled", outerColorEnabled); + nbt.setFloat("OuterColorWidth", outerColorWidth); nbt.setFloat("SweepSpeed", sweepSpeed); nbt.setInteger("NumRotations", numberOfRotations); nbt.setInteger("CompletedRotations", completedRotations); @@ -399,6 +416,8 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa buffer.writeFloat(beamHeight); buffer.writeInt(innerColor); buffer.writeInt(outerColor); + buffer.writeBoolean(outerColorEnabled); + buffer.writeFloat(outerColorWidth); buffer.writeFloat(sweepSpeed); buffer.writeInt(numberOfRotations); buffer.writeInt(completedRotations); @@ -420,6 +439,8 @@ public class EntityAbilitySweeper extends Entity implements IEntityAdditionalSpa this.beamHeight = buffer.readFloat(); this.innerColor = buffer.readInt(); this.outerColor = buffer.readInt(); + this.outerColorEnabled = buffer.readBoolean(); + this.outerColorWidth = buffer.readFloat(); this.sweepSpeed = buffer.readFloat(); this.numberOfRotations = buffer.readInt(); this.completedRotations = buffer.readInt(); diff --git a/src/main/java/noppes/npcs/DataAbilities.java b/src/main/java/noppes/npcs/DataAbilities.java index 51d2831ac..8875508b1 100644 --- a/src/main/java/noppes/npcs/DataAbilities.java +++ b/src/main/java/noppes/npcs/DataAbilities.java @@ -18,9 +18,7 @@ import noppes.npcs.scripted.NpcAPI; import noppes.npcs.scripted.event.AbilityEvent; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Random; /** @@ -47,9 +45,14 @@ public class DataAbilities { public boolean enabled = false; /** - * Minimum ticks between ability selections (global cooldown) + * Minimum cooldown ticks between abilities (global minimum) */ - public int globalCooldown = 20; + public int minCooldown = 80; + + /** + * Maximum cooldown ticks between abilities (global maximum) + */ + public int maxCooldown = 200; // ═══════════════════════════════════════════════════════════════════ // RUNTIME STATE (not saved) @@ -61,14 +64,9 @@ public class DataAbilities { private transient Ability currentAbility; /** - * Per-ability cooldown timers (ability ID -> world time when cooldown ends) + * World time when NPC cooldown ends (can select next ability) */ - private transient Map cooldowns = new HashMap<>(); - - /** - * Ticks until next ability can be selected (global cooldown timer) - */ - private transient int globalCooldownTimer = 0; + private transient long cooldownEndTime = 0; /** * Last target used for ability execution @@ -101,11 +99,6 @@ public class DataAbilities { return; } - // Tick global cooldown - if (globalCooldownTimer > 0) { - globalCooldownTimer--; - } - // Tick current ability if executing if (currentAbility != null && currentAbility.isExecuting()) { tickCurrentAbility(); @@ -192,24 +185,33 @@ public class DataAbilities { npc.motionZ = 0; } } + + // Lock look direction at target during ACTIVE phase only + if (shouldLockLookDirection() && target != null && target.isEntityAlive()) { + npc.getLookHelper().setLookPositionWithEntity(target, 30.0F, 30.0F); + } } } /** * Called when an ability completes. + * Calculates cooldown as: random(minCooldown, maxCooldown) + ability's cooldown offset */ private void onAbilityComplete() { if (currentAbility != null) { // Stop any ability animation stopAbilityAnimation(); - // Start per-ability cooldown - startCooldown(currentAbility); + // Calculate cooldown: random(min, max) + ability offset + int baseCooldown = minCooldown; + if (maxCooldown > minCooldown) { + baseCooldown = minCooldown + random.nextInt(maxCooldown - minCooldown + 1); + } + int totalCooldown = baseCooldown + currentAbility.getCooldownTicks(); + cooldownEndTime = npc.worldObj.getTotalWorldTime() + totalCooldown; + currentAbility = null; lastTarget = null; - - // Start global cooldown - globalCooldownTimer = globalCooldown; } } @@ -247,7 +249,8 @@ public class DataAbilities { if (currentAbility != null && currentAbility.isExecuting()) { return false; } - if (globalCooldownTimer > 0) { + // Check if still on cooldown + if (npc.worldObj.getTotalWorldTime() < cooldownEndTime) { return false; } return true; @@ -293,11 +296,6 @@ public class DataAbilities { return false; } - // Check cooldown - if (isOnCooldown(ability)) { - return false; - } - // Check if already executing if (ability.isExecuting()) { return false; @@ -358,8 +356,11 @@ public class DataAbilities { /** * Play an animation on the NPC by ID. + * Public so abilities can trigger animations directly if needed. + * + * @param animationId The global animation ID to play, or -1 for none */ - private void playAbilityAnimation(int animationId) { + public void playAbilityAnimation(int animationId) { if (animationId < 0) return; if (AnimationController.Instance == null) return; @@ -407,37 +408,25 @@ public class DataAbilities { // ═══════════════════════════════════════════════════════════════════ /** - * Check if an ability is on cooldown. + * Check if NPC is on cooldown (cannot use any ability). */ - public boolean isOnCooldown(Ability ability) { - Long endTime = cooldowns.get(ability.getId()); - if (endTime == null) { - return false; - } - return npc.worldObj.getTotalWorldTime() < endTime; + public boolean isOnCooldown() { + return npc.worldObj.getTotalWorldTime() < cooldownEndTime; } /** - * Start cooldown for an ability. + * Get remaining cooldown ticks. */ - private void startCooldown(Ability ability) { - long endTime = npc.worldObj.getTotalWorldTime() + ability.getCooldownTicks(); - cooldowns.put(ability.getId(), endTime); + public long getRemainingCooldown() { + long remaining = cooldownEndTime - npc.worldObj.getTotalWorldTime(); + return remaining > 0 ? remaining : 0; } /** - * Reset cooldown for a specific ability. + * Reset cooldown (allow immediate ability use). */ - public void resetCooldown(String abilityId) { - cooldowns.remove(abilityId); - } - - /** - * Reset all cooldowns. - */ - public void resetAllCooldowns() { - cooldowns.clear(); - globalCooldownTimer = 0; + public void resetCooldown() { + cooldownEndTime = 0; } // ═══════════════════════════════════════════════════════════════════ @@ -489,9 +478,18 @@ public class DataAbilities { /** * Record a hit for the hit count condition. + * Includes periodic cleanup to prevent memory leak. */ private void recordHit() { - recentHitTimes.add(npc.worldObj.getTotalWorldTime()); + long currentTime = npc.worldObj.getTotalWorldTime(); + recentHitTimes.add(currentTime); + + // Cleanup old entries periodically to prevent unbounded growth + // Keep only hits from last 5 minutes (6000 ticks) max + if (recentHitTimes.size() > 50) { + long cutoff = currentTime - 6000; + recentHitTimes.removeIf(time -> time < cutoff); + } } /** @@ -517,6 +515,8 @@ public class DataAbilities { /** * Interrupt the currently executing ability. + * The ability will transition to RECOVERY phase (dazed state) and continue ticking. + * When RECOVERY completes, the ability will move to IDLE and onAbilityComplete() is called. */ public void interruptCurrentAbility(DamageSource source, float damage) { if (currentAbility != null) { @@ -532,9 +532,10 @@ public class DataAbilities { NpcAPI.EVENT_BUS.post(interruptEvent); currentAbility.onInterrupt(npc, source, damage); - currentAbility.interrupt(); - currentAbility = null; - lastTarget = null; + currentAbility.interrupt(); // This now transitions to RECOVERY, not IDLE + + // Don't clear currentAbility - let it tick through RECOVERY phase + // When RECOVERY ends and phase becomes IDLE, onAbilityComplete() will be called } } @@ -605,7 +606,7 @@ public class DataAbilities { */ public void reset() { stopCurrentAbility(); - resetAllCooldowns(); + resetCooldown(); // Reset execution state on all abilities for (Ability ability : abilities) { @@ -707,6 +708,24 @@ public class DataAbilities { } } + /** + * Check if NPC's look direction should be locked to target. + * Only locks during ACTIVE phase when lockMovement is true. + * This allows the NPC to freely track target during WINDUP and RECOVERY. + */ + public boolean shouldLockLookDirection() { + if (currentAbility == null || !currentAbility.isExecuting()) { + return false; + } + + // Only lock look direction during ACTIVE phase + if (currentAbility.getPhase() != AbilityPhase.ACTIVE) { + return false; + } + + return currentAbility.isLockMovement(); + } + /** * Check if NPC should skip normal attacks during ability execution. */ @@ -724,7 +743,8 @@ public class DataAbilities { public NBTTagCompound writeToNBT(NBTTagCompound compound) { compound.setBoolean("AbilitiesEnabled", enabled); - compound.setInteger("AbilityGlobalCooldown", globalCooldown); + compound.setInteger("AbilityMinCooldown", minCooldown); + compound.setInteger("AbilityMaxCooldown", maxCooldown); NBTTagList abilityList = new NBTTagList(); for (Ability ability : abilities) { @@ -737,8 +757,20 @@ public class DataAbilities { public void readFromNBT(NBTTagCompound compound) { enabled = compound.getBoolean("AbilitiesEnabled"); - globalCooldown = compound.hasKey("AbilityGlobalCooldown") ? - compound.getInteger("AbilityGlobalCooldown") : 20; + + // Support old globalCooldown for backwards compatibility + if (compound.hasKey("AbilityMinCooldown")) { + minCooldown = compound.getInteger("AbilityMinCooldown"); + maxCooldown = compound.getInteger("AbilityMaxCooldown"); + } else if (compound.hasKey("AbilityGlobalCooldown")) { + // Migrate old single cooldown to min/max range + int oldCooldown = compound.getInteger("AbilityGlobalCooldown"); + minCooldown = oldCooldown; + maxCooldown = oldCooldown * 2; // Give some range + } else { + minCooldown = 20; + maxCooldown = 60; + } abilities.clear(); NBTTagList abilityList = compound.getTagList("Abilities", 10); diff --git a/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java b/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java index 0f41347d4..c0fb15596 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java @@ -51,7 +51,8 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC // Settings private boolean abilitiesEnabled = false; - private int globalCooldown = 20; + private int minCooldown = 20; + private int maxCooldown = 60; private String search = ""; private int selectedAbilityIndex = -1; @@ -72,12 +73,18 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC addLabel(new GuiNpcLabel(100, "gui.enabled", guiLeft + 5, y + 5)); addButton(new GuiNpcButton(100, guiLeft + 55, y, 50, 20, new String[]{"gui.no", "gui.yes"}, abilitiesEnabled ? 1 : 0)); - // Global cooldown (spaced out since combat only is removed) - addLabel(new GuiNpcLabel(101, "ability.globalCooldown", guiLeft + 140, y + 5)); - GuiNpcTextField cooldownField = new GuiNpcTextField(101, this, fontRendererObj, guiLeft + 230, y, 50, 20, String.valueOf(globalCooldown)); - cooldownField.setIntegersOnly(); - cooldownField.setMinMaxDefault(0, 10000, 20); - addTextField(cooldownField); + // Cooldown range: Min Cooldown and Max Cooldown + addLabel(new GuiNpcLabel(101, "ability.minCooldown", guiLeft + 115, y + 5)); + GuiNpcTextField minField = new GuiNpcTextField(101, this, fontRendererObj, guiLeft + 185, y, 40, 20, String.valueOf(minCooldown)); + minField.setIntegersOnly(); + minField.setMinMaxDefault(0, 10000, 20); + addTextField(minField); + + addLabel(new GuiNpcLabel(102, "ability.maxCooldown", guiLeft + 235, y + 5)); + GuiNpcTextField maxField = new GuiNpcTextField(102, this, fontRendererObj, guiLeft + 305, y, 40, 20, String.valueOf(maxCooldown)); + maxField.setIntegersOnly(); + maxField.setMinMaxDefault(0, 10000, 60); + addTextField(maxField); y += 28; @@ -254,7 +261,19 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC @Override public void setGuiData(NBTTagCompound compound) { abilitiesEnabled = compound.getBoolean("AbilitiesEnabled"); - globalCooldown = compound.hasKey("AbilityGlobalCooldown") ? compound.getInteger("AbilityGlobalCooldown") : 20; + + // Support old globalCooldown for backwards compatibility + if (compound.hasKey("AbilityMinCooldown")) { + minCooldown = compound.getInteger("AbilityMinCooldown"); + maxCooldown = compound.getInteger("AbilityMaxCooldown"); + } else if (compound.hasKey("AbilityGlobalCooldown")) { + int oldCooldown = compound.getInteger("AbilityGlobalCooldown"); + minCooldown = oldCooldown; + maxCooldown = oldCooldown * 2; + } else { + minCooldown = 20; + maxCooldown = 60; + } npcAbilities.clear(); NBTTagList abilityList = compound.getTagList("Abilities", 10); @@ -352,7 +371,8 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC public void save() { NBTTagCompound compound = new NBTTagCompound(); compound.setBoolean("AbilitiesEnabled", abilitiesEnabled); - compound.setInteger("AbilityGlobalCooldown", globalCooldown); + compound.setInteger("AbilityMinCooldown", minCooldown); + compound.setInteger("AbilityMaxCooldown", maxCooldown); NBTTagList abilityList = new NBTTagList(); for (Ability ability : npcAbilities) { @@ -421,7 +441,20 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC @Override public void unFocused(GuiNpcTextField textField) { if (textField.id == 101) { - globalCooldown = textField.getInteger(); + minCooldown = textField.getInteger(); + // Ensure min <= max + if (minCooldown > maxCooldown) { + maxCooldown = minCooldown; + initGui(); + } + save(); + } else if (textField.id == 102) { + maxCooldown = textField.getInteger(); + // Ensure min <= max + if (maxCooldown < minCooldown) { + minCooldown = maxCooldown; + initGui(); + } save(); } } diff --git a/src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityLaserShot.java b/src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityLaserShot.java index 6f5c8ea57..47385f7cb 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityLaserShot.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/ability/SubGuiAbilityLaserShot.java @@ -15,13 +15,18 @@ import noppes.npcs.client.gui.util.SubGuiInterface; public class SubGuiAbilityLaserShot extends SubGuiAbilityConfig { private final AbilityLaserShot laser; - private int editingTypeColorId = 0; + private int editingVisualColorId = 0; public SubGuiAbilityLaserShot(AbilityLaserShot ability, IAbilityConfigCallback callback) { super(ability, callback); this.laser = ability; } + @Override + protected boolean hasVisualSettings() { + return true; + } + @Override protected void initTypeTab(int startY) { int y = startY; @@ -72,21 +77,40 @@ public class SubGuiAbilityLaserShot extends SubGuiAbilityConfig { addLabel(new GuiNpcLabel(109, "ability.knockbackUp", col2LabelX, y + 5)); addTextField(createFloatField(109, col2FieldX, y, 50, laser.getKnockbackUp())); + } - y += 24; + @Override + protected void initVisualTab(int startY) { + int y = startY; + int labelX = guiLeft + 8; + int fieldX = guiLeft + 85; + int col2LabelX = guiLeft + 180; + int col2FieldX = guiLeft + 260; - // Row 6: Inner Color + Outer Color - addLabel(new GuiNpcLabel(120, "ability.innerColor", labelX, y + 5)); + // Row 1: Inner Color + Outer Color + addLabel(new GuiNpcLabel(200, "ability.innerColor", labelX, y + 5)); String innerHex = String.format("%06X", laser.getInnerColor() & 0xFFFFFF); - GuiNpcButton innerColorBtn = new GuiNpcButton(120, fieldX, y, 50, 20, innerHex); + GuiNpcButton innerColorBtn = new GuiNpcButton(200, fieldX, y, 55, 20, innerHex); innerColorBtn.setTextColor(laser.getInnerColor() & 0xFFFFFF); addButton(innerColorBtn); - addLabel(new GuiNpcLabel(121, "ability.outerColor", col2LabelX, y + 5)); + addLabel(new GuiNpcLabel(201, "ability.outerColor", col2LabelX, y + 5)); String outerHex = String.format("%06X", laser.getOuterColor() & 0xFFFFFF); - GuiNpcButton outerColorBtn = new GuiNpcButton(121, col2FieldX, y, 50, 20, outerHex); + GuiNpcButton outerColorBtn = new GuiNpcButton(201, col2FieldX, y, 55, 20, outerHex); outerColorBtn.setTextColor(laser.getOuterColor() & 0xFFFFFF); + outerColorBtn.setEnabled(laser.isOuterColorEnabled()); addButton(outerColorBtn); + + y += 24; + + // Row 2: Outer Color Enabled + Outer Color Width + addLabel(new GuiNpcLabel(202, "ability.outerEnabled", labelX, y + 5)); + addButton(new GuiNpcButton(202, fieldX, y, 50, 20, new String[]{"gui.no", "gui.yes"}, laser.isOuterColorEnabled() ? 1 : 0)); + + addLabel(new GuiNpcLabel(203, "ability.outerWidth", col2LabelX, y + 5)); + GuiNpcTextField widthField = createFloatField(203, col2FieldX, y, 55, laser.getOuterColorWidth()); + widthField.setEnabled(laser.isOuterColorEnabled()); + addTextField(widthField); } @Override @@ -96,34 +120,54 @@ public class SubGuiAbilityLaserShot extends SubGuiAbilityConfig { case 106: laser.setExplosive(value == 1); break; - case 120: - editingTypeColorId = 120; + } + } + + @Override + protected void handleVisualButton(int id, GuiNpcButton button) { + int value = button.getValue(); + switch (id) { + case 200: + editingVisualColorId = 200; setSubGui(new SubGuiColorSelector(laser.getInnerColor())); break; - case 121: - editingTypeColorId = 121; + case 201: + editingVisualColorId = 201; setSubGui(new SubGuiColorSelector(laser.getOuterColor())); break; + case 202: + laser.setOuterColorEnabled(value == 1); + initGui(); + break; } } @Override public void subGuiClosed(SubGuiInterface subgui) { - if (subgui instanceof SubGuiColorSelector && editingTypeColorId != 0) { + if (subgui instanceof SubGuiColorSelector && editingVisualColorId != 0) { SubGuiColorSelector colorSelector = (SubGuiColorSelector) subgui; int rgb = colorSelector.color & 0x00FFFFFF; - if (editingTypeColorId == 120) { + if (editingVisualColorId == 200) { laser.setInnerColor(rgb); - } else if (editingTypeColorId == 121) { + } else if (editingVisualColorId == 201) { laser.setOuterColor(rgb); } - editingTypeColorId = 0; + editingVisualColorId = 0; initGui(); } else { super.subGuiClosed(subgui); } } + @Override + protected void handleVisualTextField(int id, GuiNpcTextField field) { + switch (id) { + case 203: + laser.setOuterColorWidth(parseFloat(field, laser.getOuterColorWidth())); + break; + } + } + @Override protected void handleTypeTextField(int id, GuiNpcTextField field) { switch (id) {