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 015f6361b..3d49c689e 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -662,13 +662,19 @@ public abstract class Ability implements IAbility { // ═══════════════════════════════════════════════════════════════════ /** - * Start executing this ability + * Start executing this ability. + * If windUpTicks is 0, skips WINDUP and goes directly to ACTIVE. */ public void start(EntityLivingBase target) { - this.phase = AbilityPhase.WINDUP; this.currentTick = 0; this.currentTarget = target; this.executionStartTime = System.currentTimeMillis(); + + if (windUpTicks <= 0) { + this.phase = AbilityPhase.ACTIVE; + } else { + this.phase = AbilityPhase.WINDUP; + } } /** diff --git a/src/main/java/noppes/npcs/DataAbilities.java b/src/main/java/noppes/npcs/DataAbilities.java index 635b741ac..1d91dad97 100644 --- a/src/main/java/noppes/npcs/DataAbilities.java +++ b/src/main/java/noppes/npcs/DataAbilities.java @@ -385,18 +385,49 @@ public class DataAbilities { lastTarget = target; ability.start(target); - // Spawn telegraph and send to clients - spawnTelegraph(ability, target); - - // Play wind up sound if configured - playAbilitySound(ability.getWindUpSound()); - - // Play wind up animation if configured - playAbilityAnimation(ability.getWindUpAnimation()); + if (ability.getPhase() == AbilityPhase.ACTIVE) { + // Windup was 0 — skip telegraph/windup and go straight to active + executeImmediate(ability, target); + } else { + // Normal windup flow + spawnTelegraph(ability, target); + playAbilitySound(ability.getWindUpSound()); + playAbilityAnimation(ability.getWindUpAnimation()); + } return true; } + /** + * Execute an ability immediately (no windup). + * Called when windUpTicks is 0. + */ + private void executeImmediate(Ability ability, EntityLivingBase target) { + // Lock rotation if movement is locked during ACTIVE phase + if (ability.isMovementLockedDuringActive()) { + captureLockedRotation(); + } + + // Play active sound and animation + playAbilitySound(ability.getActiveSound()); + playAbilityAnimation(ability.getActiveAnimation()); + + // Fire execute event (cancelable) + AbilityEvent.ExecuteEvent executeEvent = new AbilityEvent.ExecuteEvent( + npc.wrappedNPC, ability, target); + if (NpcAPI.EVENT_BUS.post(executeEvent)) { + return; + } + + // Call onExecute + ability.onExecute(npc, target, npc.worldObj); + + // Check if ability completed during onExecute + if (ability.getPhase() == AbilityPhase.IDLE) { + handleAbilityCompletion(target); + } + } + /** * Play a sound at the NPC's location. */ diff --git a/src/main/java/noppes/npcs/client/gui/util/AbilityPreviewExecutor.java b/src/main/java/noppes/npcs/client/gui/util/AbilityPreviewExecutor.java index 3dce761d5..90204fdc4 100644 --- a/src/main/java/noppes/npcs/client/gui/util/AbilityPreviewExecutor.java +++ b/src/main/java/noppes/npcs/client/gui/util/AbilityPreviewExecutor.java @@ -81,7 +81,6 @@ public class AbilityPreviewExecutor { this.previewAbility = ability; this.previewNpc = npc; - this.phase = AbilityPhase.WINDUP; this.currentTick = 0; this.playing = true; this.paused = false; @@ -103,21 +102,30 @@ public class AbilityPreviewExecutor { parentGui.startTrackingMovement(); } - // Create telegraph for preview - previewTelegraph = ability.createTelegraph(npc, fakeTarget); + if (ability.getWindUpTicks() <= 0) { + // No windup — go straight to active + this.phase = AbilityPhase.ACTIVE; + transitionToActive(); + } else { + // Normal windup flow + this.phase = AbilityPhase.WINDUP; - // Start windup animation - Animation windUpAnim = ability.getWindUpAnimation(); - if (windUpAnim != null) { - AnimationData data = npc.display.animationData; - data.setEnabled(true); - data.setAnimation(windUpAnim); - data.animation.paused = false; - } + // Create telegraph for preview + previewTelegraph = ability.createTelegraph(npc, fakeTarget); - // Spawn preview entity on first tick if ability spawns during windup - if (ability.spawnPreviewDuringWindup()) { - spawnPreviewEntity(); + // Start windup animation + Animation windUpAnim = ability.getWindUpAnimation(); + if (windUpAnim != null) { + AnimationData data = npc.display.animationData; + data.setEnabled(true); + data.setAnimation(windUpAnim); + data.animation.paused = false; + } + + // Spawn preview entity on first tick if ability spawns during windup + if (ability.spawnPreviewDuringWindup()) { + spawnPreviewEntity(); + } } } diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java index 724b054c0..6d265de27 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java @@ -229,16 +229,37 @@ public class PlayerAbilityData implements IPlayerAbilityData { currentTarget = null; // Players don't have auto-targets ability.start(null); - // Spawn telegraph - spawnTelegraph(ability, player, null); - - // Play wind up sound and animation - playAbilitySound(player, ability.getWindUpSound()); - playAbilityAnimation(ability.getWindUpAnimation()); + if (ability.getPhase() == AbilityPhase.ACTIVE) { + // Windup was 0 — skip telegraph/windup and go straight to active + executeImmediate(ability, player); + } else { + // Normal windup flow + spawnTelegraph(ability, player, null); + playAbilitySound(player, ability.getWindUpSound()); + playAbilityAnimation(ability.getWindUpAnimation()); + } return true; } + /** + * Execute an ability immediately (no windup). + * Called when windUpTicks is 0. + */ + private void executeImmediate(Ability ability, EntityPlayer player) { + // Play active sound and animation + playAbilitySound(player, ability.getActiveSound()); + playAbilityAnimation(ability.getActiveAnimation()); + + // Call onExecute + ability.onExecute(player, currentTarget, player.worldObj); + + // Check if ability completed during onExecute + if (ability.getPhase() == AbilityPhase.IDLE) { + handleAbilityCompletion(player); + } + } + // ═══════════════════════════════════════════════════════════════════ // CLIENT SYNC // ═══════════════════════════════════════════════════════════════════