Simplify ability interrupt logic and reorganize GUI

- Remove interruptThreshold field entirely - interrupts now based on damage source type
- Update canInterrupt() to only allow interrupts from direct physical hits (not magic/fire/explosions)
- Interruption only occurs during WINDUP phase, not ACTIVE
- Reorganize SubGuiAbilityConfig into 4 tabs: General, Type, Target, Effects
- Increase GUI size to 420x216 for better layout
This commit is contained in:
Claude committed 2026-01-10 10:32:24 +00:00
1 parent b40fec2270
commit c7d04cb9d3
3 files changed
+314 -286

No files matched your search

@@ -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; }
+1 -1
View File
@@ -442,7 +442,7 @@ public class DataAbilities {
return false;
}
if (currentAbility.canInterrupt(amount)) {
if (currentAbility.canInterrupt(source)) {
interruptCurrentAbility(source, amount);
return true;
}
@@ -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();