mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 21:17:32 +00:00
904 lines
41 KiB
Java
904 lines
41 KiB
Java
package noppes.npcs.client.gui.advanced;
|
|
|
|
import kamkeel.npcs.controllers.data.ability.Ability;
|
|
import kamkeel.npcs.controllers.data.ability.Condition;
|
|
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 net.minecraft.util.StatCollector;
|
|
import noppes.npcs.client.gui.SubGuiColorSelector;
|
|
import noppes.npcs.client.gui.select.GuiAnimationSelection;
|
|
import noppes.npcs.client.gui.select.GuiSoundSelection;
|
|
import noppes.npcs.client.gui.util.*;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* Base SubGui for editing ability configuration with tabbed interface.
|
|
* Uses GuiMenuTopButton for navigation tabs.
|
|
*
|
|
* Tabs:
|
|
* - General: Name, Weight, Lock Movement, Interruptible, Timing
|
|
* - Type: Type-specific settings per Ability (override in subclasses)
|
|
* - Target: Min/Max Range, Targeting Mode, Conditions (up to 3)
|
|
* - Effects: Sounds, Animations, Telegraph settings, Colors
|
|
*
|
|
* Subclasses should override initTypeTab() and handleTypeButton()/handleTypeTextField()
|
|
* to provide type-specific settings UI.
|
|
*/
|
|
public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldListener, ISubGuiListener {
|
|
|
|
// Tab constants
|
|
protected static final int TAB_GENERAL = 0;
|
|
protected static final int TAB_TYPE = 1;
|
|
protected static final int TAB_TARGET = 2;
|
|
protected static final int TAB_EFFECTS = 3;
|
|
|
|
// Core references
|
|
protected final Ability ability;
|
|
protected final IAbilityConfigCallback callback;
|
|
protected int activeTab = TAB_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;
|
|
protected List<Condition> conditions;
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// CACHED VALUES - Effects Tab
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
protected String windUpSound;
|
|
protected String activeSound;
|
|
protected int windUpAnimationId;
|
|
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;
|
|
protected int editingConditionIndex = -1;
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// ABILITY TYPE FEATURES
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
protected final boolean supportsTelegraph;
|
|
protected final boolean targetingModeLocked;
|
|
protected final boolean hasTypeSettings;
|
|
|
|
public SubGuiAbilityConfig(Ability ability, IAbilityConfigCallback callback) {
|
|
this.ability = ability;
|
|
this.callback = callback;
|
|
|
|
// Cache values from ability - General
|
|
this.name = ability.getName() != null ? ability.getName() : "";
|
|
this.enabled = ability.isEnabled();
|
|
this.weight = ability.getWeight();
|
|
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();
|
|
|
|
// Target
|
|
this.minRange = ability.getMinRange();
|
|
this.maxRange = ability.getMaxRange();
|
|
this.targetingMode = ability.getTargetingMode();
|
|
this.conditions = new ArrayList<>(ability.getConditions());
|
|
|
|
// 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;
|
|
this.targetingModeLocked = ability.isTargetingModeLocked();
|
|
this.hasTypeSettings = ability.hasTypeSettings();
|
|
|
|
setBackground("menubg.png");
|
|
xSize = 356;
|
|
ySize = 200;
|
|
}
|
|
|
|
@Override
|
|
public void initGui() {
|
|
super.initGui();
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════
|
|
// TOP TABS using GuiMenuTopButton
|
|
// ═══════════════════════════════════════════════════════════════════════
|
|
GuiMenuTopButton generalTab = new GuiMenuTopButton(90, guiLeft + 4, guiTop - 17, "menu.general");
|
|
generalTab.active = (activeTab == TAB_GENERAL);
|
|
addTopButton(generalTab);
|
|
|
|
GuiMenuTopButton lastTab = generalTab;
|
|
|
|
if (hasTypeSettings) {
|
|
GuiMenuTopButton typeTab = new GuiMenuTopButton(91, lastTab, "gui.type");
|
|
typeTab.active = (activeTab == TAB_TYPE);
|
|
addTopButton(typeTab);
|
|
lastTab = typeTab;
|
|
}
|
|
|
|
GuiMenuTopButton targetTab = new GuiMenuTopButton(92, lastTab, "script.target");
|
|
targetTab.active = (activeTab == TAB_TARGET);
|
|
addTopButton(targetTab);
|
|
lastTab = targetTab;
|
|
|
|
GuiMenuTopButton effectsTab = new GuiMenuTopButton(93, lastTab, "ability.tab.effects");
|
|
effectsTab.active = (activeTab == TAB_EFFECTS);
|
|
addTopButton(effectsTab);
|
|
|
|
// Close button (X) in top menu bar - use -1000 to avoid any ID conflicts
|
|
GuiMenuTopButton closeBtn = new GuiMenuTopButton(-1000, guiLeft + xSize - 22, guiTop - 17, "X");
|
|
addTopButton(closeBtn);
|
|
|
|
int contentY = guiTop + 5;
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════
|
|
// TAB CONTENT
|
|
// ═══════════════════════════════════════════════════════════════════════
|
|
switch (activeTab) {
|
|
case TAB_GENERAL:
|
|
initGeneralTab(contentY);
|
|
break;
|
|
case TAB_TYPE:
|
|
initTypeTab(contentY);
|
|
break;
|
|
case TAB_TARGET:
|
|
initTargetTab(contentY);
|
|
break;
|
|
case TAB_EFFECTS:
|
|
initEffectsTab(contentY);
|
|
break;
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════
|
|
// BOTTOM ROW - Enabled toggle
|
|
// ═══════════════════════════════════════════════════════════════════════
|
|
addLabel(new GuiNpcLabel(99, "gui.enabled", guiLeft + 8, guiTop + ySize - 18));
|
|
GuiNpcButton enabledBtn = new GuiNpcButton(2, guiLeft + 55, guiTop + ySize - 24, 40, 20, new String[]{"gui.no", "gui.yes"}, enabled ? 1 : 0);
|
|
enabledBtn.setTextColor(enabled ? 0x00FF00 : 0xFF0000);
|
|
addButton(enabledBtn);
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// GENERAL TAB
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
private void initGeneralTab(int startY) {
|
|
int y = startY;
|
|
int col1LabelX = guiLeft + 8;
|
|
int col1FieldX = guiLeft + 70;
|
|
int col2LabelX = guiLeft + 180;
|
|
int col2FieldX = guiLeft + 248;
|
|
|
|
// Row 1: Type (read-only) + Name
|
|
addLabel(new GuiNpcLabel(1, "gui.type", col1LabelX, y + 5));
|
|
addLabel(new GuiNpcLabel(2, ability.getTypeId(), col1FieldX, y + 5));
|
|
|
|
addLabel(new GuiNpcLabel(3, "gui.name", col2LabelX, y + 5));
|
|
GuiNpcTextField nameField = new GuiNpcTextField(1, this, fontRendererObj, col2FieldX, y, 100, 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, 40, 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, 40, 20, new String[]{"gui.no", "gui.yes"}, lockMovement ? 1 : 0));
|
|
|
|
y += 24;
|
|
|
|
// Row 3: Interruptible
|
|
addLabel(new GuiNpcLabel(6, "ability.interruptible", col1LabelX, y + 5));
|
|
addButton(new GuiNpcButton(14, guiLeft + 85, y, 40, 20, new String[]{"gui.no", "gui.yes"}, interruptible ? 1 : 0));
|
|
|
|
y += 28;
|
|
|
|
// Separator - Timing section header
|
|
addLabel(new GuiNpcLabel(7, "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, 40, 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, 40, 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, 40, 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, 40, 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
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
private void initTargetTab(int startY) {
|
|
int y = startY;
|
|
int col1LabelX = guiLeft + 8;
|
|
int col1FieldX = guiLeft + 70;
|
|
int col2LabelX = guiLeft + 180;
|
|
int col2FieldX = guiLeft + 248;
|
|
|
|
// Row 1: Min Range + Max Range
|
|
addLabel(new GuiNpcLabel(20, "ability.minRange", col1LabelX, y + 5));
|
|
GuiNpcTextField minRangeField = new GuiNpcTextField(5, this, fontRendererObj, col1FieldX, y, 40, 20, String.valueOf((int) minRange));
|
|
minRangeField.setIntegersOnly();
|
|
minRangeField.setMinMaxDefault(0, 100, 0);
|
|
addTextField(minRangeField);
|
|
|
|
addLabel(new GuiNpcLabel(21, "ability.maxRange", col2LabelX, y + 5));
|
|
GuiNpcTextField maxRangeField = new GuiNpcTextField(6, this, fontRendererObj, col2FieldX, y, 40, 20, String.valueOf((int) maxRange));
|
|
maxRangeField.setIntegersOnly();
|
|
maxRangeField.setMinMaxDefault(1, 100, 20);
|
|
addTextField(maxRangeField);
|
|
|
|
y += 24;
|
|
|
|
// Row 2: Targeting Mode (if not locked)
|
|
addLabel(new GuiNpcLabel(22, "ability.targeting", col1LabelX, y + 5));
|
|
if (!targetingModeLocked) {
|
|
String[] targetingModes = getAvailableTargetingModes();
|
|
int selectedIndex = getTargetingModeIndex(targetingMode);
|
|
addButton(new GuiNpcButton(4, col1FieldX, y, 90, 20, targetingModes, selectedIndex));
|
|
} else {
|
|
String modeName = "ability.target." + targetingMode.name().toLowerCase();
|
|
addLabel(new GuiNpcLabel(23, modeName, col1FieldX, y + 5));
|
|
}
|
|
|
|
y += 28;
|
|
|
|
// ─────────────────────────────────────────────────────────────────────
|
|
// CONDITIONS SECTION (up to 3)
|
|
// ─────────────────────────────────────────────────────────────────────
|
|
addLabel(new GuiNpcLabel(24, "ability.conditions", col1LabelX, y));
|
|
y += 14;
|
|
|
|
// Display existing conditions (similar to Marks pattern)
|
|
for (int i = 0; i < conditions.size() && i < 3; i++) {
|
|
Condition cond = conditions.get(i);
|
|
String condName = getConditionDisplayName(cond);
|
|
|
|
// Condition type button
|
|
addButton(new GuiNpcButton(50 + i * 10, col1LabelX, y, 140, 20, condName));
|
|
|
|
// Edit button
|
|
addButton(new GuiNpcButton(51 + i * 10, col1LabelX + 145, y, 40, 20, "gui.edit"));
|
|
|
|
// Delete button
|
|
addButton(new GuiNpcButton(52 + i * 10, col1LabelX + 190, y, 20, 20, "X"));
|
|
|
|
y += 22;
|
|
}
|
|
|
|
// Add button (if less than 3 conditions)
|
|
if (conditions.size() < 3) {
|
|
addButton(new GuiNpcButton(80, col1LabelX, y, 50, 20, "gui.add"));
|
|
}
|
|
}
|
|
|
|
private String getConditionDisplayName(Condition cond) {
|
|
if (cond == null) return "None";
|
|
String typeId = cond.getTypeId();
|
|
switch (typeId) {
|
|
case "hp_above":
|
|
return StatCollector.translateToLocal("condition.hp_above") + " " + (int)(getConditionThreshold(cond) * 100) + "%";
|
|
case "hp_below":
|
|
return StatCollector.translateToLocal("condition.hp_below") + " " + (int)(getConditionThreshold(cond) * 100) + "%";
|
|
case "target_hp_above":
|
|
return StatCollector.translateToLocal("condition.target_hp_above") + " " + (int)(getConditionThreshold(cond) * 100) + "%";
|
|
case "target_hp_below":
|
|
return StatCollector.translateToLocal("condition.target_hp_below") + " " + (int)(getConditionThreshold(cond) * 100) + "%";
|
|
case "hit_count":
|
|
Condition.ConditionHitCount hitCount = (Condition.ConditionHitCount) cond;
|
|
return StatCollector.translateToLocal("condition.hit_count") + ": " + hitCount.getRequiredHits() + "/" + hitCount.getWithinTicks() + "t";
|
|
default:
|
|
return typeId;
|
|
}
|
|
}
|
|
|
|
private float getConditionThreshold(Condition cond) {
|
|
// Read threshold from NBT since inner classes don't expose it
|
|
NBTTagCompound nbt = cond.writeNBT();
|
|
return nbt.hasKey("threshold") ? nbt.getFloat("threshold") : 0.5f;
|
|
}
|
|
|
|
private String[] getAvailableTargetingModes() {
|
|
TargetingMode[] modes = ability.getAllowedTargetingModes();
|
|
if (modes == null) {
|
|
modes = TargetingMode.values();
|
|
}
|
|
String[] result = new String[modes.length];
|
|
for (int i = 0; i < modes.length; i++) {
|
|
result[i] = "ability.target." + modes[i].name().toLowerCase();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private int getTargetingModeIndex(TargetingMode mode) {
|
|
TargetingMode[] modes = ability.getAllowedTargetingModes();
|
|
if (modes == null) {
|
|
return mode.ordinal();
|
|
}
|
|
for (int i = 0; i < modes.length; i++) {
|
|
if (modes[i] == mode) return i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// EFFECTS TAB
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
private void initEffectsTab(int startY) {
|
|
int y = startY;
|
|
int col1LabelX = guiLeft + 8;
|
|
int col1FieldX = guiLeft + 85;
|
|
int col1ClearX = guiLeft + 160;
|
|
int col2LabelX = guiLeft + 185;
|
|
int col2FieldX = guiLeft + 255;
|
|
|
|
// ─────────────────────────────────────────────────────────────────────
|
|
// SOUNDS SECTION
|
|
// ─────────────────────────────────────────────────────────────────────
|
|
addLabel(new GuiNpcLabel(30, "advanced.sounds", col1LabelX, y));
|
|
y += 14;
|
|
|
|
// Wind Up Sound
|
|
addLabel(new GuiNpcLabel(31, "ability.windUpSound", col1LabelX, y + 5));
|
|
String windUpSoundDisplay = windUpSound != null && !windUpSound.isEmpty() ? truncateString(windUpSound, 10) : StatCollector.translateToLocal("gui.none");
|
|
addButton(new GuiNpcButton(30, col1FieldX, y, 70, 20, windUpSoundDisplay));
|
|
addButton(new GuiNpcButton(35, col1ClearX, y, 20, 20, "X"));
|
|
|
|
// Active Sound
|
|
addLabel(new GuiNpcLabel(32, "ability.activeSound", col2LabelX, y + 5));
|
|
String activeSoundDisplay = activeSound != null && !activeSound.isEmpty() ? truncateString(activeSound, 10) : StatCollector.translateToLocal("gui.none");
|
|
addButton(new GuiNpcButton(31, col2FieldX, y, 70, 20, activeSoundDisplay));
|
|
addButton(new GuiNpcButton(36, col2FieldX + 75, y, 20, 20, "X"));
|
|
|
|
y += 26;
|
|
|
|
// ─────────────────────────────────────────────────────────────────────
|
|
// ANIMATIONS SECTION
|
|
// ─────────────────────────────────────────────────────────────────────
|
|
addLabel(new GuiNpcLabel(33, "menu.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, col1FieldX, y, 70, 20, windUpAnimName));
|
|
addButton(new GuiNpcButton(37, col1ClearX, y, 20, 20, "X"));
|
|
|
|
// Active Animation
|
|
addLabel(new GuiNpcLabel(35, "ability.activeAnim", col2LabelX, y + 5));
|
|
String activeAnimName = getAnimationName(activeAnimationId, false);
|
|
addButton(new GuiNpcButton(33, col2FieldX, y, 70, 20, activeAnimName));
|
|
addButton(new GuiNpcButton(38, col2FieldX + 75, y, 20, 20, "X"));
|
|
|
|
y += 26;
|
|
|
|
// ─────────────────────────────────────────────────────────────────────
|
|
// TELEGRAPH SECTION (only if supported)
|
|
// ─────────────────────────────────────────────────────────────────────
|
|
if (supportsTelegraph) {
|
|
addLabel(new GuiNpcLabel(40, "ability.telegraph", col1LabelX, y));
|
|
y += 14;
|
|
|
|
// Show Telegraph
|
|
addLabel(new GuiNpcLabel(41, "ability.showTelegraph", col1LabelX, y + 5));
|
|
addButton(new GuiNpcButton(20, guiLeft + 95, y, 40, 20, new String[]{"gui.no", "gui.yes"}, showTelegraph ? 1 : 0));
|
|
|
|
// Telegraph Type (separate column)
|
|
String typeKey = ability.getTelegraphType().name().toLowerCase();
|
|
addLabel(new GuiNpcLabel(42, "ability.telegraphType", guiLeft + 200, y + 5));
|
|
String telegraphLangKey = typeKey.equals("none") ? "gui.none" : "telegraph." + typeKey;
|
|
addLabel(new GuiNpcLabel(43, telegraphLangKey, guiLeft + 280, 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, guiLeft + 95, y, 55, 20, windUpHex);
|
|
windUpColorBtn.setEnabled(showTelegraph);
|
|
windUpColorBtn.setTextColor(windUpColor & 0xFFFFFF);
|
|
addButton(windUpColorBtn);
|
|
|
|
addLabel(new GuiNpcLabel(45, "ability.activeColor", guiLeft + 200, y + 5));
|
|
String activeHex = String.format("%06X", activeColor & 0xFFFFFF);
|
|
GuiNpcButton activeColorBtn = new GuiNpcButton(24, guiLeft + 280, y, 55, 20, activeHex);
|
|
activeColorBtn.setEnabled(showTelegraph);
|
|
activeColorBtn.setTextColor(activeColor & 0xFFFFFF);
|
|
addButton(activeColorBtn);
|
|
}
|
|
}
|
|
|
|
private String truncateString(String str, int maxLen) {
|
|
if (str == null) return "";
|
|
if (str.length() <= maxLen) return str;
|
|
int lastSlash = str.lastIndexOf('/');
|
|
if (lastSlash >= 0 && str.length() - lastSlash <= maxLen) {
|
|
return "..." + str.substring(lastSlash);
|
|
}
|
|
return "..." + str.substring(str.length() - maxLen + 3);
|
|
}
|
|
|
|
private String getAnimationName(int animId, boolean isWindUp) {
|
|
if (animId < 0) return StatCollector.translateToLocal("gui.none");
|
|
String cachedName = isWindUp ? windUpAnimationName : activeAnimationName;
|
|
if (cachedName != null && !cachedName.isEmpty()) {
|
|
return truncateString(cachedName, 8);
|
|
}
|
|
return "ID: " + animId;
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// BUTTON EVENTS
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
@Override
|
|
public void buttonEvent(GuiButton guibutton) {
|
|
int id = guibutton.id;
|
|
|
|
// Tab switching
|
|
if (id == 90) {
|
|
activeTab = TAB_GENERAL;
|
|
initGui();
|
|
return;
|
|
}
|
|
if (id == 91 && hasTypeSettings) {
|
|
activeTab = TAB_TYPE;
|
|
initGui();
|
|
return;
|
|
}
|
|
if (id == 92) {
|
|
activeTab = TAB_TARGET;
|
|
initGui();
|
|
return;
|
|
}
|
|
if (id == 93) {
|
|
activeTab = TAB_EFFECTS;
|
|
initGui();
|
|
return;
|
|
}
|
|
|
|
// Enabled button (bottom left)
|
|
if (id == 2) {
|
|
enabled = ((GuiNpcButton) guibutton).getValue() == 1;
|
|
initGui();
|
|
}
|
|
|
|
// 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();
|
|
if (modes != null && idx < modes.length) {
|
|
targetingMode = modes[idx];
|
|
} else {
|
|
targetingMode = TargetingMode.values()[idx];
|
|
}
|
|
}
|
|
|
|
// Close button (X in top menu) - use -1000 to avoid any ID conflicts
|
|
else if (id == -1000) {
|
|
close();
|
|
return;
|
|
}
|
|
|
|
// Target tab - Condition buttons (50-79)
|
|
else if (id >= 50 && id < 80) {
|
|
int condIndex = (id - 50) / 10;
|
|
int action = (id - 50) % 10;
|
|
|
|
if (action == 0 || action == 1) {
|
|
// Click on condition name or Edit button - open edit subgui
|
|
if (condIndex < conditions.size()) {
|
|
editingConditionIndex = condIndex;
|
|
setSubGui(new SubGuiConditionEdit(conditions.get(condIndex)));
|
|
}
|
|
} else if (action == 2) {
|
|
// Delete button
|
|
if (condIndex < conditions.size()) {
|
|
conditions.remove(condIndex);
|
|
initGui();
|
|
}
|
|
}
|
|
}
|
|
// Add condition button
|
|
else if (id == 80) {
|
|
if (conditions.size() < 3) {
|
|
editingConditionIndex = conditions.size();
|
|
setSubGui(new SubGuiConditionEdit(null));
|
|
}
|
|
}
|
|
|
|
// Effects tab - Telegraph
|
|
else if (id == 20) {
|
|
showTelegraph = ((GuiNpcButton) guibutton).getValue() == 1;
|
|
initGui();
|
|
}
|
|
else if (id == 22) {
|
|
editingColorId = 22;
|
|
setSubGui(new SubGuiColorSelector(windUpColor));
|
|
}
|
|
else if (id == 24) {
|
|
editingColorId = 24;
|
|
setSubGui(new SubGuiColorSelector(activeColor));
|
|
}
|
|
|
|
// Effects tab - Sound buttons
|
|
else if (id == 30) {
|
|
editingSoundId = 30;
|
|
setSubGui(new GuiSoundSelection(windUpSound));
|
|
}
|
|
else if (id == 31) {
|
|
editingSoundId = 31;
|
|
setSubGui(new GuiSoundSelection(activeSound));
|
|
}
|
|
// Sound clear buttons
|
|
else if (id == 35) {
|
|
windUpSound = "";
|
|
initGui();
|
|
}
|
|
else if (id == 36) {
|
|
activeSound = "";
|
|
initGui();
|
|
}
|
|
// Animation buttons
|
|
else if (id == 32) {
|
|
editingAnimationId = 32;
|
|
setSubGui(new GuiAnimationSelection(windUpAnimationId));
|
|
}
|
|
else if (id == 33) {
|
|
editingAnimationId = 33;
|
|
setSubGui(new GuiAnimationSelection(activeAnimationId));
|
|
}
|
|
// Animation clear buttons
|
|
else if (id == 37) {
|
|
windUpAnimationId = -1;
|
|
windUpAnimationName = null;
|
|
initGui();
|
|
}
|
|
else if (id == 38) {
|
|
activeAnimationId = -1;
|
|
activeAnimationName = null;
|
|
initGui();
|
|
}
|
|
|
|
// Type-specific buttons (100+) - delegate to subclass
|
|
else if (id >= 100) {
|
|
handleTypeButton(id, (GuiNpcButton) guibutton);
|
|
}
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// TEXT FIELD EVENTS
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
@Override
|
|
public void unFocused(GuiNpcTextField textField) {
|
|
int id = textField.id;
|
|
|
|
// General tab
|
|
if (id == 1) {
|
|
name = textField.getText();
|
|
} else if (id == 3) {
|
|
weight = textField.getInteger();
|
|
}
|
|
|
|
// Target tab
|
|
else if (id == 5) {
|
|
minRange = textField.getInteger();
|
|
} else if (id == 6) {
|
|
maxRange = textField.getInteger();
|
|
}
|
|
|
|
// Timing (General tab)
|
|
else if (id == 10) {
|
|
cooldownTicks = textField.getInteger();
|
|
} else if (id == 11) {
|
|
windUpTicks = textField.getInteger();
|
|
} else if (id == 12) {
|
|
activeTicks = textField.getInteger();
|
|
} else if (id == 13) {
|
|
recoveryTicks = textField.getInteger();
|
|
}
|
|
|
|
// Type-specific fields (100+) - delegate to subclass
|
|
else if (id >= 100) {
|
|
handleTypeTextField(id, textField);
|
|
}
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// SUBGUI CLOSED HANDLER
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
@Override
|
|
public void subGuiClosed(SubGuiInterface subgui) {
|
|
if (subgui instanceof SubGuiColorSelector) {
|
|
SubGuiColorSelector colorSelector = (SubGuiColorSelector) subgui;
|
|
int rgb = colorSelector.color & 0x00FFFFFF;
|
|
if (editingColorId == 22) {
|
|
windUpColor = 0x80000000 | rgb;
|
|
} else if (editingColorId == 24) {
|
|
activeColor = 0xC0000000 | rgb;
|
|
}
|
|
editingColorId = 0;
|
|
initGui();
|
|
} else if (subgui instanceof GuiSoundSelection) {
|
|
GuiSoundSelection soundSelector = (GuiSoundSelection) subgui;
|
|
if (soundSelector.selectedResource != null) {
|
|
String sound = soundSelector.selectedResource.toString();
|
|
if (editingSoundId == 30) {
|
|
windUpSound = sound;
|
|
} else if (editingSoundId == 31) {
|
|
activeSound = sound;
|
|
}
|
|
}
|
|
editingSoundId = 0;
|
|
initGui();
|
|
} else if (subgui instanceof GuiAnimationSelection) {
|
|
GuiAnimationSelection animSelector = (GuiAnimationSelection) subgui;
|
|
if (editingAnimationId == 32) {
|
|
windUpAnimationId = animSelector.selectedAnimationId;
|
|
windUpAnimationName = animSelector.getSelectedName();
|
|
} else if (editingAnimationId == 33) {
|
|
activeAnimationId = animSelector.selectedAnimationId;
|
|
activeAnimationName = animSelector.getSelectedName();
|
|
}
|
|
editingAnimationId = 0;
|
|
initGui();
|
|
} else if (subgui instanceof SubGuiConditionEdit) {
|
|
SubGuiConditionEdit condEdit = (SubGuiConditionEdit) subgui;
|
|
Condition result = condEdit.getResult();
|
|
if (result != null && editingConditionIndex >= 0) {
|
|
if (editingConditionIndex < conditions.size()) {
|
|
conditions.set(editingConditionIndex, result);
|
|
} else {
|
|
conditions.add(result);
|
|
}
|
|
}
|
|
editingConditionIndex = -1;
|
|
initGui();
|
|
}
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// CLOSE - Save before closing
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
@Override
|
|
public void close() {
|
|
// Apply all cached values to the ability before closing
|
|
applyToAbility();
|
|
|
|
// Notify callback that ability was saved
|
|
callback.onAbilitySaved(ability);
|
|
|
|
// Close the GUI
|
|
super.close();
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// APPLY TO ABILITY
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
protected void applyToAbility() {
|
|
// General
|
|
ability.setName(name);
|
|
ability.setEnabled(enabled);
|
|
ability.setWeight(weight);
|
|
ability.setLockMovement(lockMovement);
|
|
ability.setInterruptible(interruptible);
|
|
|
|
// Timing
|
|
ability.setCooldownTicks(cooldownTicks);
|
|
ability.setWindUpTicks(windUpTicks);
|
|
ability.setActiveTicks(activeTicks);
|
|
ability.setRecoveryTicks(recoveryTicks);
|
|
|
|
// Target
|
|
ability.setMinRange(minRange);
|
|
ability.setMaxRange(maxRange);
|
|
ability.setTargetingMode(targetingMode);
|
|
|
|
// Conditions
|
|
ability.getConditions().clear();
|
|
for (Condition c : conditions) {
|
|
ability.addCondition(c);
|
|
}
|
|
|
|
// Effects
|
|
ability.setWindUpSound(windUpSound);
|
|
ability.setActiveSound(activeSound);
|
|
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
|
|
}
|
|
|
|
/**
|
|
* Called when an ability is loaded from saved presets.
|
|
*/
|
|
public void loadAbility(Ability loadedAbility) {
|
|
if (loadedAbility == null) return;
|
|
|
|
// Copy all values from loaded ability to current ability
|
|
NBTTagCompound nbt = loadedAbility.writeNBT();
|
|
// Keep the current typeId since we can't change types mid-edit
|
|
nbt.setString("typeId", ability.getTypeId());
|
|
ability.readNBT(nbt);
|
|
|
|
// Update cached values
|
|
this.name = ability.getName() != null ? ability.getName() : "";
|
|
this.enabled = ability.isEnabled();
|
|
this.weight = ability.getWeight();
|
|
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.minRange = ability.getMinRange();
|
|
this.maxRange = ability.getMaxRange();
|
|
this.targetingMode = ability.getTargetingMode();
|
|
this.conditions = new ArrayList<>(ability.getConditions());
|
|
|
|
this.showTelegraph = ability.isShowTelegraph();
|
|
this.windUpColor = ability.getWindUpColor();
|
|
this.activeColor = ability.getActiveColor();
|
|
|
|
initGui();
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// HELPER METHODS FOR SUBCLASSES
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
/**
|
|
* Creates a float text field for type-specific settings.
|
|
*/
|
|
protected GuiNpcTextField createFloatField(int id, int x, int y, int width, float value) {
|
|
GuiNpcTextField field = new GuiNpcTextField(id, this, fontRendererObj, x, y, width, 20, String.format("%.1f", value));
|
|
return field;
|
|
}
|
|
|
|
/**
|
|
* Creates an integer text field for type-specific settings.
|
|
*/
|
|
protected GuiNpcTextField createIntField(int id, int x, int y, int width, int value) {
|
|
GuiNpcTextField field = new GuiNpcTextField(id, this, fontRendererObj, x, y, width, 20, String.valueOf(value));
|
|
field.setIntegersOnly();
|
|
return field;
|
|
}
|
|
|
|
/**
|
|
* Parses a float from a text field with a fallback default value.
|
|
*/
|
|
protected float parseFloat(GuiNpcTextField field, float defaultValue) {
|
|
try {
|
|
return Float.parseFloat(field.getText());
|
|
} catch (NumberFormatException e) {
|
|
return defaultValue;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the ability being edited.
|
|
*/
|
|
protected Ability getAbility() {
|
|
return ability;
|
|
}
|
|
}
|