Ability Wheel Accepts - Chained Abilities

Lang File Cleanup / Merging
Chained Ability Icon System
This commit is contained in:
Kam committed 2026-02-18 04:57:28 -05:00
1 parent 4510b3bbd1
commit ca5461c94f
6 files changed
+179 -50

No files matched your search

@@ -7,6 +7,7 @@ import kamkeel.npcs.controllers.data.ability.ChainedAbility;
import kamkeel.npcs.controllers.data.ability.IAbilityAction;
import kamkeel.npcs.controllers.data.ability.IAbilityExtender;
import kamkeel.npcs.controllers.data.ability.IAbilityFieldProvider;
import kamkeel.npcs.controllers.data.ability.IChainedAbilityFieldProvider;
import kamkeel.npcs.controllers.data.ability.UserType;
import kamkeel.npcs.controllers.data.ability.type.*;
import kamkeel.npcs.controllers.data.ability.type.energy.AbilityBeam;
@@ -46,6 +47,7 @@ public class AbilityController implements IAbilityHandler {
// ── Extension Points ─────────────────────────────────────────────────────
private final Map<String, List<AbilityVariant>> externalVariants = new LinkedHashMap<>();
private final List<IAbilityFieldProvider> fieldProviders = new ArrayList<>();
private final List<IChainedAbilityFieldProvider> chainedFieldProviders = new ArrayList<>();
private final List<IAbilityExtender> extenders = new ArrayList<>();
private final List<Predicate<EntityPlayer>> flightCheckers = new ArrayList<>();
@@ -807,6 +809,14 @@ public class AbilityController implements IAbilityHandler {
return fieldProviders;
}
public void registerChainedFieldProvider(IChainedAbilityFieldProvider provider) {
chainedFieldProviders.add(provider);
}
public List<IChainedAbilityFieldProvider> getChainedFieldProviders() {
return chainedFieldProviders;
}
public void registerExtender(IAbilityExtender extender) {
extenders.add(extender);
}
@@ -44,15 +44,15 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction {
/** Maximum range for NPC eligibility check. */
private float maxRange = 20;
/** Which entity types can use this chained ability. */
private UserType allowedBy = UserType.BOTH;
/** Chain-level conditions (individual ability conditions are ignored). */
private List<Condition> conditions = new ArrayList<>();
/** Ordered list of ability entries to execute. */
private List<ChainedAbilityEntry> entries = new ArrayList<>();
/** Extension data for external mods (e.g., icons, DBC stats). */
private NBTTagCompound customData = new NBTTagCompound();
public ChainedAbility() {}
public ChainedAbility(String name) {
@@ -145,12 +145,27 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction {
this.maxRange = Math.max(0, maxRange);
}
/**
* Compute the allowed user type from child abilities.
* A chain allows players if ALL resolved entries allow players.
* A chain allows NPCs if ALL resolved entries allow NPCs.
* Unresolvable entries are skipped (don't restrict).
*/
public UserType getAllowedBy() {
return allowedBy;
}
public void setAllowedBy(UserType allowedBy) {
this.allowedBy = allowedBy != null ? allowedBy : UserType.BOTH;
if (entries.isEmpty()) return UserType.BOTH;
boolean allAllowPlayer = true;
boolean allAllowNpc = true;
for (ChainedAbilityEntry entry : entries) {
Ability a = entry.resolve();
if (a == null) continue;
UserType ut = a.getAllowedBy();
if (!ut.allowsPlayer()) allAllowPlayer = false;
if (!ut.allowsNpc()) allAllowNpc = false;
}
if (allAllowPlayer && allAllowNpc) return UserType.BOTH;
if (allAllowPlayer) return UserType.PLAYER_ONLY;
if (allAllowNpc) return UserType.NPC_ONLY;
return UserType.NPC_ONLY;
}
public List<Condition> getConditions() {
@@ -169,6 +184,10 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction {
this.entries = entries != null ? entries : new ArrayList<>();
}
public NBTTagCompound getCustomData() {
return customData;
}
// ═══════════════════════════════════════════════════════════════════
// IChainedAbility INTERFACE
// ═══════════════════════════════════════════════════════════════════
@@ -343,7 +362,6 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction {
nbt.setInteger("CooldownTicks", cooldownTicks);
nbt.setFloat("MinRange", minRange);
nbt.setFloat("MaxRange", maxRange);
nbt.setInteger("AllowedBy", allowedBy.ordinal());
// Conditions
NBTTagList condList = new NBTTagList();
@@ -359,6 +377,9 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction {
}
nbt.setTag("Entries", entryList);
// Custom data (for external mods)
nbt.setTag("customData", (NBTTagCompound) customData.copy());
return nbt;
}
@@ -372,7 +393,7 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction {
cooldownTicks = nbt.hasKey("CooldownTicks") ? nbt.getInteger("CooldownTicks") : 100;
minRange = nbt.getFloat("MinRange");
maxRange = nbt.hasKey("MaxRange") ? nbt.getFloat("MaxRange") : 20;
allowedBy = UserType.fromOrdinal(nbt.getInteger("AllowedBy"));
// AllowedBy is computed from child abilities — old NBT data silently ignored
// Conditions
conditions.clear();
@@ -393,5 +414,8 @@ public class ChainedAbility implements IChainedAbility, IAbilityAction {
entries.add(entry);
}
}
// Custom data (for external mods)
customData = nbt.getCompoundTag("customData");
}
}
@@ -0,0 +1,25 @@
package kamkeel.npcs.controllers.data.ability;
import noppes.npcs.client.gui.builder.FieldDef;
import java.util.List;
/**
* External field definition provider for chained abilities.
* Called during GUI initialization to allow mods to inject custom tabs/fields
* into the chained ability configuration GUI.
* <p>
* Note: Although this interface references FieldDef (client-only), it is safe to
* register on both sides because the generic type is erased at bytecode level.
* The addFieldDefinitions method is only ever called from client-side GUI code.
*/
public interface IChainedAbilityFieldProvider {
/**
* Add field definitions to the chained ability's GUI.
* Fields added here should use .tab() to assign them to a custom tab.
*
* @param chain The chained ability being configured
* @param defs The mutable list of field definitions to add to
*/
void addFieldDefinitions(ChainedAbility chain, List<FieldDef> defs);
}
@@ -6,6 +6,7 @@ import kamkeel.npcs.controllers.data.ability.AbilityAction;
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 net.minecraft.client.gui.GuiButton;
import net.minecraft.nbt.NBTTagCompound;
@@ -25,11 +26,14 @@ import noppes.npcs.client.gui.util.SubGuiInterface;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* SubGui for editing a {@link ChainedAbility}.
* Three tabs: General, Entries, Conditions.
* Core tabs: General, Entries, Conditions.
* Additional tabs are injected dynamically by {@link IChainedAbilityFieldProvider}s.
*/
public class SubGuiChainedAbilityConfig extends SubGuiInterface implements ITextfieldListener, ISubGuiListener, IAbilityConfigCallback {
@@ -38,15 +42,17 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText
private static final int CLEAR_ID_START = 2000;
private static final int LABEL_ID_START = 3000;
// ── Tab constants ─────────────────────────────────────────────────────────
// ── Tab constants (core tabs) ────────────────────────────────────────────
private static final int TAB_GENERAL = 0;
private static final int TAB_ENTRIES = 1;
private static final int TAB_CONDITIONS = 2;
private static final int CORE_TAB_COUNT = 3;
// ── Tab button IDs ────────────────────────────────────────────────────────
private static final int BTN_TAB_GENERAL = 90;
private static final int BTN_TAB_ENTRIES = 91;
private static final int BTN_TAB_CONDITIONS = 92;
private static final int BTN_TAB_EXTRA_BASE = 93;
private static final int BTN_CLOSE = -1000;
// ── Entries tab: per-entry ID encoding ─────────────────────────────────────
@@ -80,8 +86,11 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText
private List<ChainedAbilityEntry> entries;
private List<Condition> conditions;
// Dynamic tabs injected by field providers (e.g., "Icon")
private List<String> extraTabs = new ArrayList<>();
private GuiFieldBuilder builder;
private float[] tabScrollY = new float[3];
private float[] tabScrollY;
// Entry editing
private int editingEntryIndex = -1;
@@ -128,7 +137,57 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText
FieldDef.floatField("ability.minRange", chain::getMinRange, chain::setMinRange).range(0, 64),
FieldDef.floatField("ability.maxRange", chain::getMaxRange, chain::setMaxRange).range(0, 64)
).tab("General"));
fieldDefs.add(FieldDef.enumField("ability.allowedBy", UserType.class, chain::getAllowedBy, chain::setAllowedBy).tab("General"));
// Computed compatibility label (derived from child abilities)
fieldDefs.add(FieldDef.labelField("ability.compatible", () -> {
UserType ut = computeAllowedBy();
return StatCollector.translateToLocal("ability.compatible") + ": \u00A7e" + ut.name();
}).tab("General"));
// External field providers (e.g., DBC Addon injecting an "Icon" tab)
if (AbilityController.Instance != null) {
for (IChainedAbilityFieldProvider provider : AbilityController.Instance.getChainedFieldProviders()) {
provider.addFieldDefinitions(chain, fieldDefs);
}
}
// Collect extra tab names from provider-injected fields
Set<String> tabNames = new LinkedHashSet<>();
for (FieldDef def : fieldDefs) {
String tab = def.getTab();
if (tab != null && !tab.isEmpty() && !"General".equals(tab)) {
tabNames.add(tab);
}
}
extraTabs = new ArrayList<>(tabNames);
// Size scroll array for all tabs
tabScrollY = new float[CORE_TAB_COUNT + extraTabs.size()];
}
// ═══════════════════════════════════════════════════════════════════════════
// COMPUTED USERTYPE
// ═══════════════════════════════════════════════════════════════════════════
/**
* Compute the allowed UserType from the current (possibly modified) entries list.
* Mirrors ChainedAbility.getAllowedBy() but uses the local entries.
*/
private UserType computeAllowedBy() {
if (entries.isEmpty()) return UserType.BOTH;
boolean allAllowPlayer = true;
boolean allAllowNpc = true;
for (ChainedAbilityEntry entry : entries) {
Ability a = entry.resolve();
if (a == null) continue;
UserType ut = a.getAllowedBy();
if (!ut.allowsPlayer()) allAllowPlayer = false;
if (!ut.allowsNpc()) allAllowNpc = false;
}
if (allAllowPlayer && allAllowNpc) return UserType.BOTH;
if (allAllowPlayer) return UserType.PLAYER_ONLY;
if (allAllowNpc) return UserType.NPC_ONLY;
return UserType.NPC_ONLY;
}
// ═══════════════════════════════════════════════════════════════════════════
@@ -145,7 +204,7 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText
super.initGui();
// Tab buttons
// Tab buttons — core tabs
GuiMenuTopButton generalTab = new GuiMenuTopButton(BTN_TAB_GENERAL, guiLeft + 4, guiTop - 17, "menu.general");
generalTab.active = (activeTab == TAB_GENERAL);
addTopButton(generalTab);
@@ -158,6 +217,16 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText
conditionsTab.active = (activeTab == TAB_CONDITIONS);
addTopButton(conditionsTab);
// Dynamic extra tabs from field providers
GuiMenuTopButton prevTab = conditionsTab;
for (int i = 0; i < extraTabs.size(); i++) {
int tabIndex = CORE_TAB_COUNT + i;
GuiMenuTopButton extraTab = new GuiMenuTopButton(BTN_TAB_EXTRA_BASE + i, prevTab, extraTabs.get(i));
extraTab.active = (activeTab == tabIndex);
addTopButton(extraTab);
prevTab = extraTab;
}
GuiMenuTopButton closeBtn = new GuiMenuTopButton(BTN_CLOSE, guiLeft + xSize - 22, guiTop - 17, "X");
addTopButton(closeBtn);
@@ -168,22 +237,27 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText
int swH = ySize - 10;
if (activeTab == TAB_GENERAL) {
buildGeneralTab(swX, swY, swW, swH);
buildFieldDefTab("General", swX, swY, swW, swH);
} else if (activeTab == TAB_ENTRIES) {
buildEntriesTab(swX, swY, swW, swH);
} else if (activeTab == TAB_CONDITIONS) {
buildConditionsTab(swX, swY, swW, swH);
} else if (activeTab >= CORE_TAB_COUNT) {
int extraIndex = activeTab - CORE_TAB_COUNT;
if (extraIndex < extraTabs.size()) {
buildFieldDefTab(extraTabs.get(extraIndex), swX, swY, swW, swH);
}
}
}
// ═══════════════════════════════════════════════════════════════════════════
// GENERAL TAB
// FIELDDEF-BASED TAB (General + dynamic provider tabs)
// ═══════════════════════════════════════════════════════════════════════════
private void buildGeneralTab(int swX, int swY, int swW, int swH) {
private void buildFieldDefTab(String tabName, int swX, int swY, int swW, int swH) {
List<FieldDef> tabFields = new ArrayList<>();
for (FieldDef def : fieldDefs) {
if ("General".equals(def.getTab()) && def.isVisible()) {
if (tabName.equals(def.getTab()) && def.isVisible()) {
tabFields.add(def);
}
}
@@ -342,12 +416,19 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText
public void buttonEvent(GuiButton guibutton) {
int id = guibutton.id;
// Tab switching
// Tab switching — core tabs
if (id == BTN_TAB_GENERAL) { activeTab = TAB_GENERAL; initGui(); return; }
if (id == BTN_TAB_ENTRIES) { activeTab = TAB_ENTRIES; initGui(); return; }
if (id == BTN_TAB_CONDITIONS) { activeTab = TAB_CONDITIONS; initGui(); return; }
if (id == BTN_CLOSE) { saveOnClose = true; close(); return; }
// Tab switching — extra tabs
if (id >= BTN_TAB_EXTRA_BASE && id < BTN_TAB_EXTRA_BASE + extraTabs.size()) {
activeTab = CORE_TAB_COUNT + (id - BTN_TAB_EXTRA_BASE);
initGui();
return;
}
// Entries tab
if (activeTab == TAB_ENTRIES) {
if (handleEntryButton(id)) return;
@@ -358,8 +439,8 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText
if (handleConditionButton(id)) return;
}
// General tab — declarative field handling
if (activeTab == TAB_GENERAL && builder != null) {
// FieldDef-based tabs (General + extra tabs) — declarative field handling
if (isFieldDefTab() && builder != null) {
if (builder.handleButtonEvent(id, guibutton)) {
if (!hasSubGui()) {
initGui();
@@ -369,6 +450,13 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText
}
}
/**
* Whether the current active tab uses the declarative FieldDef system.
*/
private boolean isFieldDefTab() {
return activeTab == TAB_GENERAL || activeTab >= CORE_TAB_COUNT;
}
private boolean handleEntryButton(int id) {
// Add entry
if (id == BTN_ADD_ENTRY) {
@@ -456,8 +544,8 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText
public void unFocused(GuiNpcTextField textField) {
int id = textField.id;
// General tab — declarative fields
if (activeTab == TAB_GENERAL && builder != null && id >= DECLARATIVE_ID_START) {
// FieldDef-based tabs — declarative fields
if (isFieldDefTab() && builder != null && id >= DECLARATIVE_ID_START) {
if (builder.handleTextFieldEvent(id, textField)) {
initGui();
}
@@ -479,8 +567,8 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText
@Override
public void subGuiClosed(SubGuiInterface subgui) {
// General tab — declarative sub-gui handling
if (activeTab == TAB_GENERAL && builder != null) {
// FieldDef-based tabs — declarative sub-gui handling
if (isFieldDefTab() && builder != null) {
if (builder.handleSubGuiClosed(subgui)) {
initGui();
return;
@@ -78,7 +78,7 @@ public class SubGuiAuctionBid extends SubGuiInterface implements ITextfieldListe
int totalBtnWidth = btnWidth * 2 + btnSpacing;
int btnX = centerX - totalBtnWidth / 2;
addButton(new GuiNpcButton(btnBidId, btnX, y, btnWidth, 20, "auction.bid.placeBid"));
addButton(new GuiNpcButton(btnBidId, btnX, y, btnWidth, 20, "auction.bid.place"));
addButton(new GuiNpcButton(btnCancelId, btnX + btnWidth + btnSpacing, y, btnWidth, 20, "gui.cancel"));
}