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 84e4312fa..f9b126336 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -51,7 +51,6 @@ public abstract class Ability implements IAbility { // Interruption protected boolean interruptible = true; - protected float interruptThreshold = 10.0f; // Feedback protected boolean lockMovement = true; @@ -451,9 +450,36 @@ public abstract class Ability implements IAbility { return phase != AbilityPhase.IDLE; } - /** Check if can be interrupted by this damage */ - public boolean canInterrupt(float damage) { - return interruptible && damage >= interruptThreshold && phase == AbilityPhase.WINDUP; + /** + * Check if can be interrupted by this damage source. + * Only direct physical hits (not magic, fire, or other indirect damage) can interrupt. + * Interruption only occurs during WINDUP phase - if ability is already active, it completes. + * + * @param source The damage source + * @return true if the ability should be interrupted + */ + public boolean canInterrupt(DamageSource source) { + if (!interruptible || phase != AbilityPhase.WINDUP) { + return false; + } + + // Only direct physical hits can interrupt, not magic, fire, or other indirect damage + if (source == null) { + return false; + } + + // Reject indirect damage types + if (source.isMagicDamage() || source.isFireDamage() || source.isExplosion()) { + return false; + } + + // Reject damage without a direct attacker entity + if (source.getEntity() == null) { + return false; + } + + // Direct hit from an entity - can interrupt + return true; } /** Reset all execution state (called on combat end) */ @@ -507,7 +533,6 @@ public abstract class Ability implements IAbility { nbt.setInteger("active", activeTicks); nbt.setInteger("recovery", recoveryTicks); nbt.setBoolean("interruptible", interruptible); - nbt.setFloat("interruptThreshold", interruptThreshold); nbt.setBoolean("lockMovement", lockMovement); nbt.setInteger("windUpColor", windUpColor); nbt.setInteger("activeColor", activeColor); @@ -547,7 +572,6 @@ public abstract class Ability implements IAbility { activeTicks = nbt.getInteger("active"); recoveryTicks = nbt.getInteger("recovery"); interruptible = nbt.getBoolean("interruptible"); - interruptThreshold = nbt.getFloat("interruptThreshold"); lockMovement = nbt.getBoolean("lockMovement"); // Support old telegraphColor key for backwards compatibility if (nbt.hasKey("windUpColor")) { @@ -643,9 +667,6 @@ public abstract class Ability implements IAbility { public boolean isInterruptible() { return interruptible; } public void setInterruptible(boolean interruptible) { this.interruptible = interruptible; } - public float getInterruptThreshold() { return interruptThreshold; } - public void setInterruptThreshold(float interruptThreshold) { this.interruptThreshold = interruptThreshold; } - public boolean isLockMovement() { return lockMovement; } public void setLockMovement(boolean lockMovement) { this.lockMovement = lockMovement; } diff --git a/src/main/java/noppes/npcs/DataAbilities.java b/src/main/java/noppes/npcs/DataAbilities.java index 33cedf966..4e77611de 100644 --- a/src/main/java/noppes/npcs/DataAbilities.java +++ b/src/main/java/noppes/npcs/DataAbilities.java @@ -442,7 +442,7 @@ public class DataAbilities { return false; } - if (currentAbility.canInterrupt(amount)) { + if (currentAbility.canInterrupt(source)) { interruptCurrentAbility(source, amount); return true; } diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java index 18a5c2e26..b5d31d70f 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java @@ -3,18 +3,22 @@ package noppes.npcs.client.gui.advanced; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.TargetingMode; import kamkeel.npcs.controllers.data.ability.telegraph.TelegraphType; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.client.gui.SubGuiColorSelector; import noppes.npcs.client.gui.select.GuiAnimationSelection; import noppes.npcs.client.gui.select.GuiSoundSelection; -import net.minecraft.client.gui.GuiButton; -import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.client.gui.util.*; /** * Base SubGui for editing ability configuration with tabbed interface. * Uses GuiMenuTopButton for navigation tabs. * - * Tabs: General, Type (if hasTypeSettings), Timing, Effects, Telegraph (if supported) + * Tabs: + * - General: Type ID, Name, Weight, Lock Movement, Interruptible, Timing (Windup/Active/Recovery/Cooldown) + * - Type: Type-specific settings per Ability (override in subclasses) + * - Target: Min/Max Range, Targeting Mode, Conditions + * - Effects: Sounds, Animations, Telegraph settings, Colors * * Subclasses should override initTypeTab() and handleTypeButton()/handleTypeTextField() * to provide type-specific settings UI. @@ -24,9 +28,8 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi // Tab constants protected static final int TAB_GENERAL = 0; protected static final int TAB_TYPE = 1; - protected static final int TAB_TIMING = 2; + protected static final int TAB_TARGET = 2; protected static final int TAB_EFFECTS = 3; - protected static final int TAB_TELEGRAPH = 4; // Core references protected final Ability ability; @@ -34,36 +37,29 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi protected int activeTab = TAB_GENERAL; // ═══════════════════════════════════════════════════════════════════════════ - // CACHED VALUES - General + // CACHED VALUES - General Tab // ═══════════════════════════════════════════════════════════════════════════ protected String name; protected boolean enabled; protected int weight; + protected boolean lockMovement; + protected boolean interruptible; + + // Timing + protected int cooldownTicks; + protected int windUpTicks; + protected int activeTicks; + protected int recoveryTicks; + + // ═══════════════════════════════════════════════════════════════════════════ + // CACHED VALUES - Target Tab + // ═══════════════════════════════════════════════════════════════════════════ protected float minRange; protected float maxRange; protected TargetingMode targetingMode; // ═══════════════════════════════════════════════════════════════════════════ - // CACHED VALUES - Timing - // ═══════════════════════════════════════════════════════════════════════════ - protected int cooldownTicks; - protected int windUpTicks; - protected int activeTicks; - protected int recoveryTicks; - protected boolean interruptible; - protected float interruptThreshold; - protected boolean lockMovement; - - // ═══════════════════════════════════════════════════════════════════════════ - // CACHED VALUES - Telegraph - // ═══════════════════════════════════════════════════════════════════════════ - protected boolean showTelegraph; - protected int windUpColor; - protected int activeColor; - protected int editingColorId = 0; - - // ═══════════════════════════════════════════════════════════════════════════ - // CACHED VALUES - Effects + // CACHED VALUES - Effects Tab // ═══════════════════════════════════════════════════════════════════════════ protected String windUpSound; protected String activeSound; @@ -71,6 +67,14 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi protected int activeAnimationId; protected String windUpAnimationName = null; protected String activeAnimationName = null; + + // Telegraph + protected boolean showTelegraph; + protected int windUpColor; + protected int activeColor; + + // Editing state + protected int editingColorId = 0; protected int editingSoundId = 0; protected int editingAnimationId = 0; @@ -85,31 +89,35 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi this.ability = ability; this.callback = callback; - // Cache values from ability + // Cache values from ability - General this.name = ability.getName() != null ? ability.getName() : ""; this.enabled = ability.isEnabled(); this.weight = ability.getWeight(); - this.minRange = ability.getMinRange(); - this.maxRange = ability.getMaxRange(); - this.targetingMode = ability.getTargetingMode(); + this.lockMovement = ability.isLockMovement(); + this.interruptible = ability.isInterruptible(); + // Timing this.cooldownTicks = ability.getCooldownTicks(); this.windUpTicks = ability.getWindUpTicks(); this.activeTicks = ability.getActiveTicks(); this.recoveryTicks = ability.getRecoveryTicks(); - this.interruptible = ability.isInterruptible(); - this.interruptThreshold = ability.getInterruptThreshold(); - this.lockMovement = ability.isLockMovement(); - this.showTelegraph = ability.isShowTelegraph(); - this.windUpColor = ability.getWindUpColor(); - this.activeColor = ability.getActiveColor(); + // Target + this.minRange = ability.getMinRange(); + this.maxRange = ability.getMaxRange(); + this.targetingMode = ability.getTargetingMode(); + // Effects this.windUpSound = ability.getWindUpSound(); this.activeSound = ability.getActiveSound(); this.windUpAnimationId = ability.getWindUpAnimationId(); this.activeAnimationId = ability.getActiveAnimationId(); + // Telegraph + this.showTelegraph = ability.isShowTelegraph(); + this.windUpColor = ability.getWindUpColor(); + this.activeColor = ability.getActiveColor(); + // Determine ability type features TelegraphType defaultType = ability.getTelegraphType(); this.supportsTelegraph = defaultType != null && defaultType != TelegraphType.NONE; @@ -117,7 +125,7 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi this.hasTypeSettings = ability.hasTypeSettings(); setBackground("menubg.png"); - xSize = 276; + xSize = 420; ySize = 216; } @@ -141,21 +149,14 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi lastTab = typeTab; } - GuiMenuTopButton timingTab = new GuiMenuTopButton(92, lastTab, "ability.tab.timing"); - timingTab.active = (activeTab == TAB_TIMING); - addTopButton(timingTab); - lastTab = timingTab; + GuiMenuTopButton targetTab = new GuiMenuTopButton(92, lastTab, "ability.tab.target"); + targetTab.active = (activeTab == TAB_TARGET); + addTopButton(targetTab); + lastTab = targetTab; - GuiMenuTopButton effectsTab = new GuiMenuTopButton(94, lastTab, "ability.tab.effects"); + GuiMenuTopButton effectsTab = new GuiMenuTopButton(93, lastTab, "ability.tab.effects"); effectsTab.active = (activeTab == TAB_EFFECTS); addTopButton(effectsTab); - lastTab = effectsTab; - - if (supportsTelegraph) { - GuiMenuTopButton telegraphTab = new GuiMenuTopButton(93, lastTab, "ability.tab.telegraph"); - telegraphTab.active = (activeTab == TAB_TELEGRAPH); - addTopButton(telegraphTab); - } // Close button on top right GuiMenuTopButton closeBtn = new GuiMenuTopButton(67, guiLeft + xSize - 22, guiTop - 17, "X"); @@ -173,15 +174,12 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi case TAB_TYPE: initTypeTab(contentY); break; - case TAB_TIMING: - initTimingTab(contentY); + case TAB_TARGET: + initTargetTab(contentY); break; case TAB_EFFECTS: initEffectsTab(contentY); break; - case TAB_TELEGRAPH: - initTelegraphTab(contentY); - break; } // ═══════════════════════════════════════════════════════════════════════ @@ -197,35 +195,130 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi // ═══════════════════════════════════════════════════════════════════════════ // GENERAL TAB + // Type ID (label), Name, Weight, Lock Movement, Interruptible, + // Ticks: Windup, Active, Recovery, Cooldown // ═══════════════════════════════════════════════════════════════════════════ private void initGeneralTab(int startY) { int y = startY; - int labelX = guiLeft + 8; - int fieldX = guiLeft + 85; - int col2LabelX = guiLeft + 145; - int col2FieldX = guiLeft + 205; + int col1LabelX = guiLeft + 8; + int col1FieldX = guiLeft + 75; + int col2LabelX = guiLeft + 215; + int col2FieldX = guiLeft + 290; - // Row 1: Name + Weight - addLabel(new GuiNpcLabel(1, "gui.name", labelX, y + 5)); - addTextField(new GuiNpcTextField(1, this, fontRendererObj, fieldX, y, 100, 20, name)); + // Row 1: Type ID (read-only) + Name + addLabel(new GuiNpcLabel(1, "ability.typeId", col1LabelX, y + 5)); + addLabel(new GuiNpcLabel(2, ability.getTypeId(), col1FieldX, y + 5)); - addLabel(new GuiNpcLabel(3, "ability.weight", col2LabelX, y + 5)); - GuiNpcTextField weightField = new GuiNpcTextField(3, this, fontRendererObj, col2FieldX, y, 50, 20, String.valueOf(weight)); + addLabel(new GuiNpcLabel(3, "gui.name", col2LabelX, y + 5)); + GuiNpcTextField nameField = new GuiNpcTextField(1, this, fontRendererObj, col2FieldX, y, 120, 20, name); + addTextField(nameField); + + y += 24; + + // Row 2: Weight + Lock Movement + addLabel(new GuiNpcLabel(4, "ability.weight", col1LabelX, y + 5)); + GuiNpcTextField weightField = new GuiNpcTextField(3, this, fontRendererObj, col1FieldX, y, 50, 20, String.valueOf(weight)); weightField.setIntegersOnly(); weightField.setMinMaxDefault(1, 1000, 10); addTextField(weightField); + addLabel(new GuiNpcLabel(5, "ability.lockMove", col2LabelX, y + 5)); + addButton(new GuiNpcButton(16, col2FieldX, y, 50, 20, new String[]{"gui.no", "gui.yes"}, lockMovement ? 1 : 0)); + y += 24; - // Row 2: Min Range + Max Range - addLabel(new GuiNpcLabel(5, "ability.minRange", labelX, y + 5)); - GuiNpcTextField minRangeField = new GuiNpcTextField(5, this, fontRendererObj, fieldX, y, 50, 20, String.valueOf((int) minRange)); + // Row 3: Interruptible (no threshold anymore - just a toggle) + addLabel(new GuiNpcLabel(6, "ability.interruptible", col1LabelX, y + 5)); + addButton(new GuiNpcButton(14, col1FieldX, y, 50, 20, new String[]{"gui.no", "gui.yes"}, interruptible ? 1 : 0)); + + y += 30; + + // Separator - Timing section header + addLabel(new GuiNpcLabel(7, "§n" + "ability.timing", col1LabelX, y)); + y += 14; + + // Row 4: Windup Ticks + Active Ticks + addLabel(new GuiNpcLabel(10, "ability.windup", col1LabelX, y + 5)); + GuiNpcTextField windupField = new GuiNpcTextField(11, this, fontRendererObj, col1FieldX, y, 50, 20, String.valueOf(windUpTicks)); + windupField.setIntegersOnly(); + windupField.setMinMaxDefault(0, 1000, 20); + addTextField(windupField); + + addLabel(new GuiNpcLabel(11, "ability.active", col2LabelX, y + 5)); + GuiNpcTextField activeField = new GuiNpcTextField(12, this, fontRendererObj, col2FieldX, y, 50, 20, String.valueOf(activeTicks)); + activeField.setIntegersOnly(); + activeField.setMinMaxDefault(1, 1000, 10); + addTextField(activeField); + + y += 24; + + // Row 5: Recovery Ticks + Cooldown Ticks + addLabel(new GuiNpcLabel(12, "ability.recovery", col1LabelX, y + 5)); + GuiNpcTextField recoveryField = new GuiNpcTextField(13, this, fontRendererObj, col1FieldX, y, 50, 20, String.valueOf(recoveryTicks)); + recoveryField.setIntegersOnly(); + recoveryField.setMinMaxDefault(0, 1000, 20); + addTextField(recoveryField); + + addLabel(new GuiNpcLabel(13, "ability.cooldown", col2LabelX, y + 5)); + GuiNpcTextField cooldownField = new GuiNpcTextField(10, this, fontRendererObj, col2FieldX, y, 50, 20, String.valueOf(cooldownTicks)); + cooldownField.setIntegersOnly(); + cooldownField.setMinMaxDefault(0, 10000, 100); + addTextField(cooldownField); + } + + // ═══════════════════════════════════════════════════════════════════════════ + // TYPE TAB - Override in subclasses for type-specific settings + // ═══════════════════════════════════════════════════════════════════════════ + + /** + * Override this method to render type-specific settings in the Type tab. + * Use the helper methods createFloatField(), createIntField(), etc. + * Button/TextField IDs should start at 100. + * + * @param startY The Y position to start rendering from + */ + protected void initTypeTab(int startY) { + // Default: show message that no type settings are available + addLabel(new GuiNpcLabel(100, "ability.noTypeSettings", guiLeft + 8, startY + 5)); + } + + /** + * Override this method to handle button clicks for type-specific buttons. + * Button IDs 100+ are reserved for type-specific use. + */ + protected void handleTypeButton(int id, GuiNpcButton button) { + // Default: no-op + } + + /** + * Override this method to handle text field changes for type-specific fields. + * Field IDs 100+ are reserved for type-specific use. + */ + protected void handleTypeTextField(int id, GuiNpcTextField field) { + // Default: no-op + } + + // ═══════════════════════════════════════════════════════════════════════════ + // TARGET TAB + // Min/Max Range, Targeting Mode, Conditions + // ═══════════════════════════════════════════════════════════════════════════ + + private void initTargetTab(int startY) { + int y = startY; + int col1LabelX = guiLeft + 8; + int col1FieldX = guiLeft + 85; + int col2LabelX = guiLeft + 215; + int col2FieldX = guiLeft + 290; + + // Row 1: Min Range + Max Range + addLabel(new GuiNpcLabel(20, "ability.minRange", col1LabelX, y + 5)); + GuiNpcTextField minRangeField = new GuiNpcTextField(5, this, fontRendererObj, col1FieldX, y, 50, 20, String.valueOf((int) minRange)); minRangeField.setIntegersOnly(); minRangeField.setMinMaxDefault(0, 100, 0); addTextField(minRangeField); - addLabel(new GuiNpcLabel(6, "ability.maxRange", col2LabelX, y + 5)); + addLabel(new GuiNpcLabel(21, "ability.maxRange", col2LabelX, y + 5)); GuiNpcTextField maxRangeField = new GuiNpcTextField(6, this, fontRendererObj, col2FieldX, y, 50, 20, String.valueOf((int) maxRange)); maxRangeField.setIntegersOnly(); maxRangeField.setMinMaxDefault(1, 100, 20); @@ -233,13 +326,26 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi y += 24; - // Row 3: Targeting Mode (if not locked) + // Row 2: Targeting Mode (if not locked) + addLabel(new GuiNpcLabel(22, "ability.targeting", col1LabelX, y + 5)); if (!targetingModeLocked) { - addLabel(new GuiNpcLabel(4, "ability.targeting", labelX, y + 5)); String[] targetingModes = getAvailableTargetingModes(); int selectedIndex = getTargetingModeIndex(targetingMode); - addButton(new GuiNpcButton(4, fieldX, y, 80, 20, targetingModes, selectedIndex)); + addButton(new GuiNpcButton(4, col1FieldX, y, 100, 20, targetingModes, selectedIndex)); + } else { + // Show as label if locked + String modeName = "ability.target." + targetingMode.name().toLowerCase(); + addLabel(new GuiNpcLabel(23, modeName, col1FieldX, y + 5)); } + + y += 30; + + // Separator - Conditions section header + addLabel(new GuiNpcLabel(24, "§n" + "ability.conditions", col1LabelX, y)); + y += 14; + + // TODO: Add conditions UI here + addLabel(new GuiNpcLabel(25, "ability.conditionsNote", col1LabelX, y + 5)); } private String[] getAvailableTargetingModes() { @@ -265,142 +371,91 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi return 0; } - // ═══════════════════════════════════════════════════════════════════════════ - // TYPE TAB - Override in subclasses for type-specific settings - // ═══════════════════════════════════════════════════════════════════════════ - - /** - * Override this method to render type-specific settings in the Type tab. - * Use the helper methods createFloatField(), createIntField(), etc. - * Button/TextField IDs should start at 100. - * - * @param startY The Y position to start rendering from - */ - protected void initTypeTab(int startY) { - // Default: show message that no type settings are available - addLabel(new GuiNpcLabel(100, "ability.noTypeSettings", guiLeft + 8, startY + 5)); - } - - /** - * Override this method to handle button clicks for type-specific buttons. - * Button IDs 100+ are reserved for type-specific use. - * - * @param id The button ID - * @param button The button that was clicked - */ - protected void handleTypeButton(int id, GuiNpcButton button) { - // Default: no-op - } - - /** - * Override this method to handle text field changes for type-specific fields. - * Field IDs 100+ are reserved for type-specific use. - * - * @param id The field ID - * @param field The text field that changed - */ - protected void handleTypeTextField(int id, GuiNpcTextField field) { - // Default: no-op - } - - // ═══════════════════════════════════════════════════════════════════════════ - // TIMING TAB - // ═══════════════════════════════════════════════════════════════════════════ - - private void initTimingTab(int startY) { - int y = startY; - int labelX = guiLeft + 8; - int fieldX = guiLeft + 85; - int col2LabelX = guiLeft + 145; - int col2FieldX = guiLeft + 205; - - // Row 1: Cooldown + Windup - addLabel(new GuiNpcLabel(10, "ability.cooldown", labelX, y + 5)); - GuiNpcTextField cooldownField = new GuiNpcTextField(10, this, fontRendererObj, fieldX, y, 50, 20, String.valueOf(cooldownTicks)); - cooldownField.setIntegersOnly(); - cooldownField.setMinMaxDefault(0, 10000, 100); - addTextField(cooldownField); - - addLabel(new GuiNpcLabel(11, "ability.windup", col2LabelX, y + 5)); - GuiNpcTextField windupField = new GuiNpcTextField(11, this, fontRendererObj, col2FieldX, y, 50, 20, String.valueOf(windUpTicks)); - windupField.setIntegersOnly(); - windupField.setMinMaxDefault(0, 1000, 20); - addTextField(windupField); - - y += 24; - - // Row 2: Active + Recovery - addLabel(new GuiNpcLabel(12, "ability.active", labelX, y + 5)); - GuiNpcTextField activeField = new GuiNpcTextField(12, this, fontRendererObj, fieldX, y, 50, 20, String.valueOf(activeTicks)); - activeField.setIntegersOnly(); - activeField.setMinMaxDefault(1, 1000, 10); - addTextField(activeField); - - addLabel(new GuiNpcLabel(13, "ability.recovery", col2LabelX, y + 5)); - GuiNpcTextField recoveryField = new GuiNpcTextField(13, this, fontRendererObj, col2FieldX, y, 50, 20, String.valueOf(recoveryTicks)); - recoveryField.setIntegersOnly(); - recoveryField.setMinMaxDefault(0, 1000, 20); - addTextField(recoveryField); - - y += 28; - - // Row 3: Interruptible + Threshold - addLabel(new GuiNpcLabel(14, "ability.interruptible", labelX, y + 5)); - addButton(new GuiNpcButton(14, fieldX, y, 50, 20, new String[]{"gui.no", "gui.yes"}, interruptible ? 1 : 0)); - - addLabel(new GuiNpcLabel(15, "ability.threshold", col2LabelX, y + 5)); - GuiNpcTextField thresholdField = new GuiNpcTextField(15, this, fontRendererObj, col2FieldX, y, 50, 20, String.valueOf((int) interruptThreshold)); - thresholdField.setIntegersOnly(); - thresholdField.setMinMaxDefault(0, 1000, 10); - thresholdField.setEnabled(interruptible); - addTextField(thresholdField); - - y += 28; - - // Row 4: Lock Movement - addLabel(new GuiNpcLabel(16, "ability.lockMove", labelX, y + 5)); - addButton(new GuiNpcButton(16, fieldX, y, 50, 20, new String[]{"gui.no", "gui.yes"}, lockMovement ? 1 : 0)); - } - // ═══════════════════════════════════════════════════════════════════════════ // EFFECTS TAB + // Sounds, Animations, Telegraph, Colors // ═══════════════════════════════════════════════════════════════════════════ private void initEffectsTab(int startY) { int y = startY; - int labelX = guiLeft + 8; - int fieldX = guiLeft + 100; - int btnX = guiLeft + 215; + int col1LabelX = guiLeft + 8; + int col1FieldX = guiLeft + 100; + int col1ClearX = guiLeft + 185; + int col2LabelX = guiLeft + 215; + int col2FieldX = guiLeft + 290; - // Row 1: Wind Up Sound - addLabel(new GuiNpcLabel(30, "ability.windUpSound", labelX, y + 5)); - String windUpSoundDisplay = windUpSound != null && !windUpSound.isEmpty() ? truncateString(windUpSound, 15) : "None"; - addButton(new GuiNpcButton(30, fieldX, y, 110, 20, windUpSoundDisplay)); - addButton(new GuiNpcButton(35, btnX, y, 20, 20, "X")); + // ───────────────────────────────────────────────────────────────────── + // SOUNDS SECTION + // ───────────────────────────────────────────────────────────────────── + addLabel(new GuiNpcLabel(30, "§n" + "ability.sounds", col1LabelX, y)); + y += 14; - y += 25; + // Wind Up Sound + addLabel(new GuiNpcLabel(31, "ability.windUpSound", col1LabelX, y + 5)); + String windUpSoundDisplay = windUpSound != null && !windUpSound.isEmpty() ? truncateString(windUpSound, 12) : "None"; + addButton(new GuiNpcButton(30, col1FieldX, y, 80, 20, windUpSoundDisplay)); + addButton(new GuiNpcButton(35, col1ClearX, y, 20, 20, "X")); - // Row 2: Active Sound - addLabel(new GuiNpcLabel(31, "ability.activeSound", labelX, y + 5)); - String activeSoundDisplay = activeSound != null && !activeSound.isEmpty() ? truncateString(activeSound, 15) : "None"; - addButton(new GuiNpcButton(31, fieldX, y, 110, 20, activeSoundDisplay)); - addButton(new GuiNpcButton(36, btnX, y, 20, 20, "X")); + // Active Sound + addLabel(new GuiNpcLabel(32, "ability.activeSound", col2LabelX, y + 5)); + String activeSoundDisplay = activeSound != null && !activeSound.isEmpty() ? truncateString(activeSound, 12) : "None"; + addButton(new GuiNpcButton(31, col2FieldX, y, 80, 20, activeSoundDisplay)); + addButton(new GuiNpcButton(36, col2FieldX + 85, y, 20, 20, "X")); - y += 30; + y += 28; - // Row 3: Wind Up Animation - addLabel(new GuiNpcLabel(32, "ability.windUpAnim", labelX, y + 5)); + // ───────────────────────────────────────────────────────────────────── + // ANIMATIONS SECTION + // ───────────────────────────────────────────────────────────────────── + addLabel(new GuiNpcLabel(33, "§n" + "ability.animations", col1LabelX, y)); + y += 14; + + // Wind Up Animation + addLabel(new GuiNpcLabel(34, "ability.windUpAnim", col1LabelX, y + 5)); String windUpAnimName = getAnimationName(windUpAnimationId, true); - addButton(new GuiNpcButton(32, fieldX, y, 110, 20, windUpAnimName)); - addButton(new GuiNpcButton(37, btnX, y, 20, 20, "X")); + addButton(new GuiNpcButton(32, col1FieldX, y, 80, 20, windUpAnimName)); + addButton(new GuiNpcButton(37, col1ClearX, y, 20, 20, "X")); - y += 25; - - // Row 4: Active Animation - addLabel(new GuiNpcLabel(33, "ability.activeAnim", labelX, y + 5)); + // Active Animation + addLabel(new GuiNpcLabel(35, "ability.activeAnim", col2LabelX, y + 5)); String activeAnimName = getAnimationName(activeAnimationId, false); - addButton(new GuiNpcButton(33, fieldX, y, 110, 20, activeAnimName)); - addButton(new GuiNpcButton(38, btnX, y, 20, 20, "X")); + addButton(new GuiNpcButton(33, col2FieldX, y, 80, 20, activeAnimName)); + addButton(new GuiNpcButton(38, col2FieldX + 85, y, 20, 20, "X")); + + y += 28; + + // ───────────────────────────────────────────────────────────────────── + // TELEGRAPH SECTION (only if supported) + // ───────────────────────────────────────────────────────────────────── + if (supportsTelegraph) { + addLabel(new GuiNpcLabel(40, "§n" + "ability.telegraph", col1LabelX, y)); + y += 14; + + // Show Telegraph + Type + addLabel(new GuiNpcLabel(41, "ability.showTelegraph", col1LabelX, y + 5)); + addButton(new GuiNpcButton(20, col1FieldX, y, 50, 20, new String[]{"gui.no", "gui.yes"}, showTelegraph ? 1 : 0)); + + String typeKey = ability.getTelegraphType().name().toLowerCase(); + addLabel(new GuiNpcLabel(42, "ability.telegraphType", col2LabelX, y + 5)); + addLabel(new GuiNpcLabel(43, "ability.telegraph." + typeKey, col2FieldX, y + 5)); + + y += 24; + + // Colors + addLabel(new GuiNpcLabel(44, "ability.windUpColor", col1LabelX, y + 5)); + String windUpHex = String.format("%06X", windUpColor & 0xFFFFFF); + GuiNpcButton windUpColorBtn = new GuiNpcButton(22, col1FieldX, y, 60, 20, windUpHex); + windUpColorBtn.setEnabled(showTelegraph); + windUpColorBtn.setTextColor(windUpColor & 0xFFFFFF); + addButton(windUpColorBtn); + + addLabel(new GuiNpcLabel(45, "ability.activeColor", col2LabelX, y + 5)); + String activeHex = String.format("%06X", activeColor & 0xFFFFFF); + GuiNpcButton activeColorBtn = new GuiNpcButton(24, col2FieldX, y, 60, 20, activeHex); + activeColorBtn.setEnabled(showTelegraph); + activeColorBtn.setTextColor(activeColor & 0xFFFFFF); + addButton(activeColorBtn); + } } private String truncateString(String str, int maxLen) { @@ -417,57 +472,11 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi if (animId < 0) return "None"; String cachedName = isWindUp ? windUpAnimationName : activeAnimationName; if (cachedName != null && !cachedName.isEmpty()) { - return cachedName; + return truncateString(cachedName, 10); } return "ID: " + animId; } - // ═══════════════════════════════════════════════════════════════════════════ - // TELEGRAPH TAB - // ═══════════════════════════════════════════════════════════════════════════ - - private void initTelegraphTab(int startY) { - int y = startY; - int labelX = guiLeft + 8; - int fieldX = guiLeft + 110; - - // Row 1: Show Telegraph - addLabel(new GuiNpcLabel(20, "ability.showTelegraph", labelX, y + 5)); - addButton(new GuiNpcButton(20, fieldX, y, 50, 20, new String[]{"gui.no", "gui.yes"}, showTelegraph ? 1 : 0)); - - y += 25; - - // Row 2: Wind Up Color - addLabel(new GuiNpcLabel(21, "ability.windUpColor", labelX, y + 5)); - String windUpHex = String.format("%06X", windUpColor & 0xFFFFFF); - GuiNpcButton windUpBtn = new GuiNpcButton(22, fieldX, y, 70, 20, windUpHex); - windUpBtn.setEnabled(showTelegraph); - windUpBtn.setTextColor(windUpColor & 0xFFFFFF); - addButton(windUpBtn); - - y += 25; - - // Row 3: Active Color - addLabel(new GuiNpcLabel(23, "ability.activeColor", labelX, y + 5)); - String activeHex = String.format("%06X", activeColor & 0xFFFFFF); - GuiNpcButton activeBtn = new GuiNpcButton(24, fieldX, y, 70, 20, activeHex); - activeBtn.setEnabled(showTelegraph); - activeBtn.setTextColor(activeColor & 0xFFFFFF); - addButton(activeBtn); - - y += 30; - - // Info: Telegraph type - String typeKey = "ability.telegraph." + ability.getTelegraphType().name().toLowerCase(); - addLabel(new GuiNpcLabel(25, "ability.telegraphType", labelX, y)); - addLabel(new GuiNpcLabel(26, typeKey, fieldX, y)); - - y += 18; - - // Info note - addLabel(new GuiNpcLabel(27, "ability.telegraphSizeNote", labelX, y)); - } - // ═══════════════════════════════════════════════════════════════════════════ // BUTTON EVENTS // ═══════════════════════════════════════════════════════════════════════════ @@ -488,16 +497,11 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi return; } if (id == 92) { - activeTab = TAB_TIMING; + activeTab = TAB_TARGET; initGui(); return; } - if (id == 93 && supportsTelegraph) { - activeTab = TAB_TELEGRAPH; - initGui(); - return; - } - if (id == 94) { + if (id == 93) { activeTab = TAB_EFFECTS; initGui(); return; @@ -508,7 +512,16 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi enabled = ((GuiNpcButton) guibutton).getValue() == 1; initGui(); } - // Targeting mode + + // General tab buttons + else if (id == 14) { + interruptible = ((GuiNpcButton) guibutton).getValue() == 1; + } + else if (id == 16) { + lockMovement = ((GuiNpcButton) guibutton).getValue() == 1; + } + + // Target tab - Targeting mode else if (id == 4) { TargetingMode[] modes = ability.getAllowedTargetingModes(); int idx = ((GuiNpcButton) guibutton).getValue(); @@ -519,15 +532,7 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi } } - // Timing tab buttons - else if (id == 14) { - interruptible = ((GuiNpcButton) guibutton).getValue() == 1; - initGui(); - } else if (id == 16) { - lockMovement = ((GuiNpcButton) guibutton).getValue() == 1; - } - - // Telegraph tab buttons + // Effects tab - Telegraph else if (id == 20) { showTelegraph = ((GuiNpcButton) guibutton).getValue() == 1; initGui(); @@ -610,13 +615,16 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi name = textField.getText(); } else if (id == 3) { weight = textField.getInteger(); - } else if (id == 5) { + } + + // Target tab + else if (id == 5) { minRange = textField.getInteger(); } else if (id == 6) { maxRange = textField.getInteger(); } - // Timing tab + // Timing (General tab) else if (id == 10) { cooldownTicks = textField.getInteger(); } else if (id == 11) { @@ -625,8 +633,6 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi activeTicks = textField.getInteger(); } else if (id == 13) { recoveryTicks = textField.getInteger(); - } else if (id == 15) { - interruptThreshold = textField.getInteger(); } // Type-specific fields (100+) - delegate to subclass @@ -686,23 +692,19 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi ability.setName(name); ability.setEnabled(enabled); ability.setWeight(weight); - ability.setMinRange(minRange); - ability.setMaxRange(maxRange); - ability.setTargetingMode(targetingMode); + ability.setLockMovement(lockMovement); + ability.setInterruptible(interruptible); // Timing ability.setCooldownTicks(cooldownTicks); ability.setWindUpTicks(windUpTicks); ability.setActiveTicks(activeTicks); ability.setRecoveryTicks(recoveryTicks); - ability.setInterruptible(interruptible); - ability.setInterruptThreshold(interruptThreshold); - ability.setLockMovement(lockMovement); - // Telegraph - ability.setShowTelegraph(showTelegraph); - ability.setWindUpColor(windUpColor); - ability.setActiveColor(activeColor); + // Target + ability.setMinRange(minRange); + ability.setMaxRange(maxRange); + ability.setTargetingMode(targetingMode); // Effects ability.setWindUpSound(windUpSound); @@ -710,6 +712,11 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi ability.setWindUpAnimationId(windUpAnimationId); ability.setActiveAnimationId(activeAnimationId); + // Telegraph + ability.setShowTelegraph(showTelegraph); + ability.setWindUpColor(windUpColor); + ability.setActiveColor(activeColor); + // Type-specific values are applied directly in handleTypeTextField and handleTypeButton } @@ -729,17 +736,17 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi this.name = ability.getName() != null ? ability.getName() : ""; this.enabled = ability.isEnabled(); this.weight = ability.getWeight(); - this.minRange = ability.getMinRange(); - this.maxRange = ability.getMaxRange(); - this.targetingMode = ability.getTargetingMode(); + this.lockMovement = ability.isLockMovement(); + this.interruptible = ability.isInterruptible(); this.cooldownTicks = ability.getCooldownTicks(); this.windUpTicks = ability.getWindUpTicks(); this.activeTicks = ability.getActiveTicks(); this.recoveryTicks = ability.getRecoveryTicks(); - this.interruptible = ability.isInterruptible(); - this.interruptThreshold = ability.getInterruptThreshold(); - this.lockMovement = ability.isLockMovement(); + + this.minRange = ability.getMinRange(); + this.maxRange = ability.getMaxRange(); + this.targetingMode = ability.getTargetingMode(); this.showTelegraph = ability.isShowTelegraph(); this.windUpColor = ability.getWindUpColor();