From 40a02b73e2f456d0fc2de3a1fe17fc23625582c6 Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 18 Feb 2026 15:31:33 -0500 Subject: [PATCH] Fixes to some GUI and Chained Abiltiy Logic for Clearing States --- .../controllers/data/ability/Ability.java | 22 ++- .../data/ability/type/AbilityCharge.java | 6 + .../data/ability/type/AbilityDash.java | 6 + .../data/ability/type/AbilityMovement.java | 6 + .../type/energy/AbilityEnergyProjectile.java | 1 + .../noppes/npcs/AbstractDataAbilities.java | 29 ++-- .../client/gui/advanced/GuiNPCAbilities.java | 35 ++-- .../advanced/SubGuiChainedAbilityConfig.java | 156 ++++++++++++++---- 8 files changed, 196 insertions(+), 65 deletions(-) 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 c44f1bd47..f93a5c772 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -14,6 +14,7 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.DamageSource; +import net.minecraft.util.StatCollector; import net.minecraft.world.World; import noppes.npcs.DataAbilities; import noppes.npcs.NpcDamageSource; @@ -281,6 +282,17 @@ public abstract class Ability implements IAbility, IAbilityAction { public void onComplete(EntityLivingBase caster, EntityLivingBase target) { } + /** + * Called when the ability enters BURST_DELAY between burst iterations. + * Override in subclasses to reset transient state that should not carry + * across burst repetitions (e.g., movement direction, hit entity lists). + *

+ * Unlike {@link #cleanup()}, this does NOT kill spawned entities (they may + * still be in flight for overlap mode). + */ + public void resetForBurst() { + } + public void onDamageTaken(EntityLivingBase caster, EntityLivingBase attacker, DamageSource source, float damage) { } @@ -535,6 +547,10 @@ public abstract class Ability implements IAbility, IAbilityAction { .tab("General")); defs.add(FieldDef.stringField("gui.displayName", this::getRawDisplayName, this::setDisplayName) .tab("General")); + defs.add(FieldDef.labelField("ability.validFor", () -> + StatCollector.translateToLocal("ability.validFor") + ": \u00A7e" + + StatCollector.translateToLocal("ability.userType." + getAllowedBy().name())) + .tab("General")); defs.add(FieldDef.row( FieldDef.intField("ability.weight", this::getWeight, this::setWeight).range(1, 1000), FieldDef.boolField("gui.enabled", this::isEnabled, this::setEnabled) @@ -979,9 +995,9 @@ public abstract class Ability implements IAbility, IAbilityAction { if (phase == AbilityPhase.ACTIVE) { // Check if more burst iterations remain if (burstEnabled && burstAmount > 0 && burstIndex < burstAmount) { - // Don't cleanup here - entities may still be in flight - // For non-overlap: cleaned up when next burst starts (in onExecute) - // For overlap: entities continue flying indefinitely + // Reset transient state for next burst iteration (e.g., movement direction, hit lists) + // Does NOT kill entities - they may still be in flight (overlap mode) + resetForBurst(); burstIndex++; phase = AbilityPhase.BURST_DELAY; currentTick = 0; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java index 9f063ff72..120f6190d 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityCharge.java @@ -171,6 +171,12 @@ public class AbilityCharge extends AbilityMovement implements IAbilityCharge { hitEntities.clear(); } + @Override + public void resetForBurst() { + super.resetForBurst(); + hitEntities.clear(); + } + @Override public float getTelegraphLength() { return maxRange; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java index 84df55642..fffbf6ca5 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityDash.java @@ -196,6 +196,12 @@ public class AbilityDash extends AbilityMovement implements IAbilityDash { chosenDirection = null; } + @Override + public void resetForBurst() { + super.resetForBurst(); + chosenDirection = null; + } + @Override public int getMaxPreviewDuration() { return (int) Math.ceil(dashDistance / dashSpeed) + 5; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java index de2090cc3..d5b8d8c52 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/AbilityMovement.java @@ -53,6 +53,12 @@ public abstract class AbilityMovement extends Ability { maxActiveTicks = 0; } + @Override + public void resetForBurst() { + movementDirection = null; + maxActiveTicks = 0; + } + // ═══════════════════════════════════════════════════════════════════ // DIRECTION HELPERS // ═══════════════════════════════════════════════════════════════════ diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java index 6575b29c0..2286919d6 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/type/energy/AbilityEnergyProjectile.java @@ -68,6 +68,7 @@ public abstract class AbilityEnergyProjectile this.homingData = homingData; this.lifespanData = lifespanData; this.trajectoryData = new EnergyTrajectoryData(); + this.burstOverlap = true; initProjectiles(1); } diff --git a/src/main/java/noppes/npcs/AbstractDataAbilities.java b/src/main/java/noppes/npcs/AbstractDataAbilities.java index c31154bc3..13d3095c9 100644 --- a/src/main/java/noppes/npcs/AbstractDataAbilities.java +++ b/src/main/java/noppes/npcs/AbstractDataAbilities.java @@ -616,8 +616,10 @@ public abstract class AbstractDataAbilities { return; } - // Auto-complete for burst overlap mode + // Auto-complete for burst overlap mode only. + // Non-overlap waits for all entities to die naturally (via allDead check in onActiveTick). if (currentAbility.isBurstEnabled() + && currentAbility.isBurstOverlap() && currentAbility.getBurstIndex() < currentAbility.getBurstAmount() && currentAbility.getPhase() == AbilityPhase.ACTIVE && currentAbility.isReadyForBurstCompletion(currentAbility.getCurrentTick())) { @@ -682,10 +684,12 @@ public abstract class AbstractDataAbilities { // Chain mode: advance to next entry instead of completing if (currentChain != null) { + // AFTER semantics: delay from the CURRENT (just-completed) entry + ChainedAbilityEntry completedEntry = currentChain.getEntries().get(chainEntryIndex); + int delay = completedEntry.getDelayTicks(); + chainEntryIndex++; if (chainEntryIndex < currentChain.getEntries().size()) { - ChainedAbilityEntry nextEntry = currentChain.getEntries().get(chainEntryIndex); - // Check if target died and retarget if (target != null && target.isDead) { target = retargetForChain(); @@ -695,13 +699,9 @@ public abstract class AbstractDataAbilities { } } - int delay = nextEntry.getDelayTicks(); - if (delay > 0) { - chainDelayRemaining = delay; - currentAbility = null; - } else { - startChainEntry(target); - } + // Enforce minimum 1-tick delay between chain entries + chainDelayRemaining = Math.max(1, delay); + currentAbility = null; return; } // All entries complete @@ -819,14 +819,7 @@ public abstract class AbstractDataAbilities { chainEntryIndex = 0; chainDelayRemaining = -1; - // Check if first entry has a delay - ChainedAbilityEntry firstEntry = chain.getEntries().get(0); - int delay = firstEntry.getDelayTicks(); - if (delay > 0) { - chainDelayRemaining = delay; - return true; - } - + // AFTER semantics: no delay before first entry (delay applies after each entry completes) return startChainEntry(target); } diff --git a/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java b/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java index e95b2c97b..0a83895ed 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/GuiNPCAbilities.java @@ -100,6 +100,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC // ── Pending: chain editing ──────────────────────────────────────────────── private ChainedAbility pendingChain = null; + private int pendingChainSlotIdx = -1; // ── Pending: inline chain-entry editing ────────────────────────────────── private boolean editingChainEntry = false; @@ -233,17 +234,22 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC // Check if selected slot is editable boolean selectedIsBuiltIn = false; boolean selectedIsChainEntry = selectedEntryIndex >= 0; - if (selectedSlotIndex >= 0 && selectedSlotIndex < npcSlots.size() && !selectedIsChainEntry) { + boolean selectedIsRefChainEntry = false; + if (selectedSlotIndex >= 0 && selectedSlotIndex < npcSlots.size()) { AbilityAction sel = npcSlots.get(selectedSlotIndex); - if (!sel.isChain()) { + if (!selectedIsChainEntry && !sel.isChain()) { Ability a = sel.getAbility(); if (a != null && a.isBuiltIn()) selectedIsBuiltIn = true; } + // Sub-entries of reference chains are not directly editable from the NPC list + if (selectedIsChainEntry && sel.isChainReference()) { + selectedIsRefChainEntry = true; + } } // Right side buttons: Edit and Up/Down carrots with Save button addButton(new GuiNpcButton(BTN_EDIT, guiLeft + 210, y + 145, 55, 20, "gui.edit")); - getButton(BTN_EDIT).setEnabled(selectedSlotIndex >= 0 && !selectedIsBuiltIn); + getButton(BTN_EDIT).setEnabled(selectedSlotIndex >= 0 && !selectedIsBuiltIn && !selectedIsRefChainEntry); // Up/Down carrot buttons addButton(new GuiNpcButton(BTN_MOVE_UP, guiLeft + 270, y + 145, 20, 20, "<")); @@ -354,6 +360,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC ChainedAbility chain = slot.getInlineChain(); if (chain != null) { pendingChain = chain; + pendingChainSlotIdx = selectedSlotIndex; setSubGui(new SubGuiChainedAbilityConfig(chain, this, true, npcSlots)); } } @@ -484,6 +491,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC newChain.setId(UUID.randomUUID().toString()); newChain.setName("NewChain"); pendingChain = newChain; + pendingChainSlotIdx = -1; setSubGui(new SubGuiChainedAbilityConfig(newChain, this, true, npcSlots)); return; } @@ -837,8 +845,10 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC @Override public void customScrollDoubleClicked(String selection, GuiCustomScroll scroll) { if (scroll == npcAbilitiesScroll && selectedSlotIndex >= 0 && selectedSlotIndex < npcSlots.size()) { - // Double-click mirrors Edit button logic - buttonEvent(new GuiNpcButton(BTN_EDIT, 0, 0, 0, 0, "")); + GuiNpcButton editBtn = getButton(BTN_EDIT); + if (editBtn != null && editBtn.enabled) { + buttonEvent(new GuiNpcButton(BTN_EDIT, 0, 0, 0, 0, "")); + } } } @@ -918,22 +928,17 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC private void handleChainConfigClosed() { if (pendingChain != null) { - boolean found = false; - for (int i = 0; i < npcSlots.size(); i++) { - AbilityAction slot = npcSlots.get(i); - if (slot.isInlineChain() && slot.getInlineChain() == pendingChain) { - found = true; - break; - } - } - if (!found) { + if (pendingChainSlotIdx < 0) { + // New chain — add to NPC slots npcSlots.add(AbilityAction.inlineChain(pendingChain)); selectedSlotIndex = npcSlots.size() - 1; selectedEntryIndex = -1; selectedAbilityIndex = selectedSlotIndex; if (npcAbilitiesScroll != null) npcAbilitiesScroll.resetScroll(); } + // Existing slot (pendingChainSlotIdx >= 0): already updated in-place, nothing to add pendingChain = null; + pendingChainSlotIdx = -1; } updateNpcAbilitiesList(); save(); @@ -953,6 +958,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC ChainedAbility chain = slot.getInlineChain(); if (chain != null) { pendingChain = chain; + pendingChainSlotIdx = selectedSlotIndex; setSubGui(new SubGuiChainedAbilityConfig(chain, this, true, npcSlots)); } updateNpcAbilitiesList(); @@ -962,6 +968,7 @@ public class GuiNPCAbilities extends GuiNPCInterface2 implements IScrollData, IC ChainedAbility chain = slot.getChainedAbility(); if (chain != null) { pendingChain = chain; + pendingChainSlotIdx = selectedSlotIndex; setSubGui(new SubGuiChainedAbilityConfig(chain, this, true, npcSlots)); } } diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java index aa2df26f9..ba3bfb955 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java @@ -3,11 +3,14 @@ package noppes.npcs.client.gui.advanced; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityAction; +import kamkeel.npcs.controllers.data.ability.AbilityVariant; import kamkeel.npcs.controllers.data.ability.ChainedAbility; import kamkeel.npcs.controllers.data.ability.ChainedAbilityEntry; import kamkeel.npcs.controllers.data.ability.Condition; import kamkeel.npcs.controllers.data.ability.IChainedAbilityFieldProvider; import kamkeel.npcs.controllers.data.ability.UserType; +import kamkeel.npcs.network.PacketClient; +import kamkeel.npcs.network.packets.request.ability.CustomAbilitySavePacket; import net.minecraft.client.gui.GuiButton; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.StatCollector; @@ -79,6 +82,7 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText private final ChainedAbility chain; private final IChainedAbilityConfigCallback callback; private final boolean npcContext; + private final boolean readOnlyEntries; private final List npcSlots; private int activeTab = TAB_GENERAL; @@ -94,6 +98,8 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText // Entry editing private int editingEntryIndex = -1; + private String pendingTypeId; + private boolean editingParentAbility = false; // Condition editing private int editingConditionIndex = -1; @@ -105,14 +111,20 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText private final List consumedSlotIndices = new ArrayList<>(); public SubGuiChainedAbilityConfig(ChainedAbility chain, IChainedAbilityConfigCallback callback) { - this(chain, callback, false, null); + this(chain, callback, false, null, false); } public SubGuiChainedAbilityConfig(ChainedAbility chain, IChainedAbilityConfigCallback callback, boolean npcContext, List npcSlots) { + this(chain, callback, npcContext, npcSlots, false); + } + + public SubGuiChainedAbilityConfig(ChainedAbility chain, IChainedAbilityConfigCallback callback, + boolean npcContext, List npcSlots, boolean readOnlyEntries) { this.chain = chain; this.callback = callback; this.npcContext = npcContext; + this.readOnlyEntries = readOnlyEntries; this.npcSlots = npcSlots; this.entries = new ArrayList<>(chain.getEntries()); this.conditions = new ArrayList<>(chain.getConditions()); @@ -129,6 +141,11 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText fieldDefs.add(FieldDef.stringField("gui.name", chain::getName, chain::setName).tab("General")); fieldDefs.add(FieldDef.stringField("gui.displayName", chain::getRawDisplayName, chain::setDisplayName).tab("General")); + // Computed "Valid For" label (derived from child abilities) + fieldDefs.add(FieldDef.labelField("ability.validFor", () -> { + UserType ut = computeAllowedBy(); + return StatCollector.translateToLocal("ability.validFor") + ": \u00A7e" + StatCollector.translateToLocal("ability.userType." + ut.name()); + }).tab("General")); fieldDefs.add(FieldDef.boolField("gui.enabled", chain::isEnabled, chain::setEnabled).tab("General")); fieldDefs.add(FieldDef.intField("ability.weight", chain::getWeight, chain::setWeight).range(1, 100).tab("General")); fieldDefs.add(FieldDef.boolField("ability.windUpAll", chain::isWindUpAll, chain::setWindUpAll).tab("General")); @@ -140,12 +157,6 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText FieldDef.floatField("ability.maxRange", chain::getMaxRange, chain::setMaxRange).range(0, 64) ).tab("Target")); - // Computed "Valid For" label (derived from child abilities) - fieldDefs.add(FieldDef.labelField("ability.validFor", () -> { - UserType ut = computeAllowedBy(); - return StatCollector.translateToLocal("ability.validFor") + ": \u00A7e" + StatCollector.translateToLocal("ability.userType." + ut.name()); - }).tab("Target")); - // External field providers (e.g., DBC Addon injecting an "Icon" tab) if (AbilityController.Instance != null) { for (IChainedAbilityFieldProvider provider : AbilityController.Instance.getChainedFieldProviders()) { @@ -307,34 +318,46 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText btnLabel = "\u00A7e> " + (resolved != null ? resolved.getDisplayName() : refName); } } - sw.addButton(new GuiNpcButton(ENTRY_BASE + i * ENTRY_STRIDE, L_LABEL_X + 15, y, 140, 20, btnLabel)); - - // Delay label + field - sw.addLabel(new GuiNpcLabel(labelCounter++, "ability.delay", L_LABEL_X + 160, y + 5, 0xAAAAAA)); - GuiNpcTextField delayField = new GuiNpcTextField(ENTRY_BASE + i * ENTRY_STRIDE + 1, this, fontRendererObj, - L_LABEL_X + 195, y, 40, 20, String.valueOf(entry.getDelayTicks())); - delayField.setIntegersOnly(); - delayField.setMinMaxDefault(0, 6000, 0); - sw.addTextField(delayField); - - // Up button - if (i > 0) { - sw.addButton(new GuiNpcButton(ENTRY_BASE + i * ENTRY_STRIDE + 2, L_LABEL_X + 240, y, 20, 20, "\u2191")); + GuiNpcButton entryBtn = new GuiNpcButton(ENTRY_BASE + i * ENTRY_STRIDE, L_LABEL_X + 15, y, 140, 20, btnLabel); + if (readOnlyEntries) { + entryBtn.setEnabled(false); } + sw.addButton(entryBtn); - // Down button - if (i < entries.size() - 1) { - sw.addButton(new GuiNpcButton(ENTRY_BASE + i * ENTRY_STRIDE + 3, L_LABEL_X + 263, y, 20, 20, "\u2193")); + if (!readOnlyEntries) { + // Delay label + field + sw.addLabel(new GuiNpcLabel(labelCounter++, "ability.delay", L_LABEL_X + 160, y + 5, 0xAAAAAA)); + GuiNpcTextField delayField = new GuiNpcTextField(ENTRY_BASE + i * ENTRY_STRIDE + 1, this, fontRendererObj, + L_LABEL_X + 195, y, 40, 20, String.valueOf(entry.getDelayTicks())); + delayField.setIntegersOnly(); + delayField.setMinMaxDefault(0, 6000, 0); + sw.addTextField(delayField); + + // Up button + if (i > 0) { + sw.addButton(new GuiNpcButton(ENTRY_BASE + i * ENTRY_STRIDE + 2, L_LABEL_X + 240, y, 20, 20, "\u2191")); + } + + // Down button + if (i < entries.size() - 1) { + sw.addButton(new GuiNpcButton(ENTRY_BASE + i * ENTRY_STRIDE + 3, L_LABEL_X + 263, y, 20, 20, "\u2193")); + } + + // Delete button + sw.addButton(new GuiNpcButton(ENTRY_BASE + i * ENTRY_STRIDE + 4, L_LABEL_X + 288, y, 20, 20, "X")); + } else { + // Read-only: show delay as label only + int delay = entry.getDelayTicks(); + if (delay > 0) { + sw.addLabel(new GuiNpcLabel(labelCounter++, delay + "t", L_LABEL_X + 165, y + 5, 0x888888)); + } } - // Delete button - sw.addButton(new GuiNpcButton(ENTRY_BASE + i * ENTRY_STRIDE + 4, L_LABEL_X + 288, y, 20, 20, "X")); - y += ROW_H; } - // Add entry button - if (entries.size() < MAX_ENTRIES) { + // Add entry button (hidden in read-only mode) + if (!readOnlyEntries && entries.size() < MAX_ENTRIES) { sw.addButton(new GuiNpcButton(BTN_ADD_ENTRY, L_LABEL_X, y, 80, 20, "gui.add")); y += ROW_H; } @@ -512,11 +535,11 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText if (entryIndex < 0 || entryIndex >= entries.size()) return false; switch (action) { - case 0: // Entry name button — reference: select ability; inline: edit config + case 0: // Entry name button — reference: clone/modify dialog; inline: edit config ChainedAbilityEntry clickedEntry = entries.get(entryIndex); if (clickedEntry.isReference()) { editingEntryIndex = entryIndex; - setSubGui(new SubGuiAbilitySelect()); + setSubGui(new SubGuiAbilityEditMode()); } else if (clickedEntry.isInline() && clickedEntry.getInlineAbility() != null) { editingEntryIndex = entryIndex; setSubGui(new SubGuiAbilityConfig(clickedEntry.getInlineAbility(), this)); @@ -626,13 +649,24 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText initGui(); return; } + if (subgui instanceof SubGuiAbilityVariantSelect) { + handleVariantSelectClosed((SubGuiAbilityVariantSelect) subgui); + if (!hasSubGui()) initGui(); + return; + } if (subgui instanceof SubGuiAbilityTypeSelect) { handleTypeSelectClosed((SubGuiAbilityTypeSelect) subgui); if (!hasSubGui()) initGui(); return; } + if (subgui instanceof SubGuiAbilityEditMode) { + handleEditModeClosed((SubGuiAbilityEditMode) subgui); + if (!hasSubGui()) initGui(); + return; + } if (subgui instanceof SubGuiAbilityConfig) { editingEntryIndex = -1; + editingParentAbility = false; initGui(); return; } @@ -687,8 +721,17 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText private void handleTypeSelectClosed(SubGuiAbilityTypeSelect gui) { String typeId = gui.getSelectedTypeId(); if (typeId != null) { + List variants = AbilityController.Instance.getVariantsForType(typeId); + if (variants.size() > 1) { + pendingTypeId = typeId; + setSubGui(new SubGuiAbilityVariantSelect(variants)); + return; + } Ability newAbility = AbilityController.Instance.create(typeId); if (newAbility != null) { + if (variants.size() == 1) { + variants.get(0).apply(newAbility); + } newAbility.setId(java.util.UUID.randomUUID().toString()); entries.add(ChainedAbilityEntry.inline(newAbility, 0)); editingEntryIndex = entries.size() - 1; @@ -697,6 +740,53 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText } } + private void handleVariantSelectClosed(SubGuiAbilityVariantSelect gui) { + int idx = gui.getSelectedIndex(); + if (idx >= 0 && pendingTypeId != null) { + Ability newAbility = AbilityController.Instance.create(pendingTypeId); + if (newAbility != null) { + gui.getVariants().get(idx).apply(newAbility); + newAbility.setId(java.util.UUID.randomUUID().toString()); + entries.add(ChainedAbilityEntry.inline(newAbility, 0)); + editingEntryIndex = entries.size() - 1; + pendingTypeId = null; + setSubGui(new SubGuiAbilityConfig(newAbility, this)); + return; + } + } + pendingTypeId = null; + } + + private void handleEditModeClosed(SubGuiAbilityEditMode gui) { + int mode = gui.getResult(); + if (mode < 0 || editingEntryIndex < 0 || editingEntryIndex >= entries.size()) { + editingEntryIndex = -1; + return; + } + + ChainedAbilityEntry entry = entries.get(editingEntryIndex); + + if (mode == SubGuiAbilityEditMode.MODE_CLONE_MODIFY) { + if (entry.convertToInline()) { + Ability a = entry.getInlineAbility(); + if (a != null) { + a.setId(java.util.UUID.randomUUID().toString()); + setSubGui(new SubGuiAbilityConfig(a, this)); + return; + } + } + editingEntryIndex = -1; + } else if (mode == SubGuiAbilityEditMode.MODE_MODIFY_PARENT) { + Ability resolved = entry.resolve(); + if (resolved != null) { + editingParentAbility = true; + setSubGui(resolved.createConfigGui(this)); + return; + } + editingEntryIndex = -1; + } + } + private void handleConditionEditClosed(SubGuiConditionEdit gui) { Condition result = gui.getResult(); if (result != null && editingConditionIndex >= 0) { @@ -715,6 +805,12 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText @Override public void onAbilitySaved(Ability ability) { + if (editingParentAbility) { + // Persist the global parent ability to the server + PacketClient.sendClient(new CustomAbilitySavePacket(ability.writeNBT())); + editingParentAbility = false; + return; + } // Inline ability mutated in-place; entry already holds the reference }