From 606bc093df39ca544490efd5fab84604594331c9 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 02:36:41 -0500 Subject: [PATCH] Condition Registry [1/2] --- .../npcs/controllers/AbilityController.java | 22 ++ .../ability/conditions/ConditionHitCount.java | 6 + src/main/java/kamkeel/npcs/util/Register.java | 33 +++ .../gui/advanced/SubGuiConditionEdit.java | 219 ++++++++++++++---- 4 files changed, 232 insertions(+), 48 deletions(-) diff --git a/src/main/java/kamkeel/npcs/controllers/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/AbilityController.java index 1da844451..a2a4d8b33 100644 --- a/src/main/java/kamkeel/npcs/controllers/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/AbilityController.java @@ -903,6 +903,28 @@ public class AbilityController implements IAbilityHandler { return conditionTypes.keySet().toArray(new String[0]); } + public List getConditionNamespaces() { + LinkedHashSet seen = new LinkedHashSet<>(); + for (String typeId : conditionTypes.keySet()) { + String[] parts = typeId.split("\\.", 3); + if (parts.length >= 2) { + seen.add(parts[1]); + } + } + return new ArrayList<>(seen); + } + + public String[] getConditionTypesByNamespace(String namespace) { + String prefix = "condition." + namespace + "."; + List result = new ArrayList<>(); + for (String typeId : conditionTypes.keySet()) { + if (typeId.startsWith(prefix)) { + result.add(typeId); + } + } + return result.toArray(new String[0]); + } + public void registerFlightChecker(Predicate checker) { flightCheckers.add(checker); } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHitCount.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHitCount.java index 22465b593..1c8dfaabf 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHitCount.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHitCount.java @@ -18,6 +18,12 @@ public class ConditionHitCount extends AbilityCondition { this.userType = UserType.NPC_ONLY; } + @Override + protected boolean checkEntity(EntityLivingBase entity) { + // Not used — this condition overrides check() directly + return false; + } + @Override public boolean check(EntityLivingBase caster, EntityLivingBase target) { if (caster instanceof EntityNPCInterface) { diff --git a/src/main/java/kamkeel/npcs/util/Register.java b/src/main/java/kamkeel/npcs/util/Register.java index eab517ce9..75184778d 100644 --- a/src/main/java/kamkeel/npcs/util/Register.java +++ b/src/main/java/kamkeel/npcs/util/Register.java @@ -4,6 +4,7 @@ import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.Ability; import kamkeel.npcs.controllers.data.ability.AbilityVariant; import kamkeel.npcs.controllers.data.ability.IEffectAction; +import kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition; import noppes.npcs.LogWriter; import noppes.npcs.controllers.AnimationController; import noppes.npcs.controllers.data.Animation; @@ -207,4 +208,36 @@ public class Register { return new Register.EffectActions(namespace); } } + + public static class Conditions extends Register { + + private Conditions(String namespace) { + super("condition", namespace); + } + + public AbilityCondition register(String name, Supplier factory) { + entries.put(registryKey + "." + namespace + "." + name, factory); + return factory.get(); + } + + public void register() { + for (Map.Entry> entry : entries.entrySet()) { + AbilityController.Instance.registerCondition(entry.getValue()); + } + } + + public static Register.Conditions create(String namespace, String displayName) { + if (!REGISTERED_NAMESPACES.containsKey("condition")) + REGISTERED_NAMESPACES.put("condition", new ArrayList<>()); + + if (REGISTERED_NAMESPACES.get("condition").contains(namespace)) { + LogWriter.error("REGISTER CONDITIONS: Namespace " + namespace + " already registered!"); + } + + REGISTERED_NAMESPACES.get("condition").add(namespace); + NAMESPACE_DISPLAY_NAMES.put(namespace, displayName); + + return new Register.Conditions(namespace); + } + } } diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiConditionEdit.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiConditionEdit.java index d90f0b6d6..bca83c150 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiConditionEdit.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiConditionEdit.java @@ -2,12 +2,15 @@ package noppes.npcs.client.gui.advanced; import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition; +import kamkeel.npcs.util.Register; import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.resources.I18n; import noppes.npcs.client.gui.builder.FieldDef; import noppes.npcs.client.gui.util.*; import kamkeel.npcs.controllers.data.ability.gui.AbilityFieldBuilder; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Supplier; @@ -15,59 +18,138 @@ import java.util.function.Supplier; /** * SubGui for editing a single AbilityCondition. * - * Type selection stays manual (we need to swap the condition object). - * Field rendering is fully delegated to AbilityCondition#getAbilityDefinitions(), - * mirroring the pattern used in SubGuiAbilityConfig. + * Split-panel layout: + * Left – Mod namespace filter + scrollable condition type list + * Right – FieldDef configuration for the selected condition */ -public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldListener, ISubGuiListener { +public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldListener, ISubGuiListener, ICustomScrollListener { private static final int DECLARATIVE_ID_START = 1000; private static final int CLEAR_ID_START = 2000; private static final int LABEL_ID_START = 3000; - private static final int BTN_TYPE = 1; + private static final int BTN_NAMESPACE = 1; private static final int BTN_CANCEL = 10; private static final int BTN_DONE = 11; + // Left panel + private static final int LEFT_WIDTH = 110; + private static final int LEFT_MARGIN = 5; + + // Right panel + private static final int RIGHT_GAP = 5; + private AbilityCondition condition; private AbilityCondition result; + private String selectedTypeId; - private final String[] conditionTypeIds; - private final String[] conditionTypeNames; - private int selectedTypeIndex = 0; + // Namespace filter + private int namespaceFilter = 0; // 0 = ALL, 1 = cnpc, 2+ = modded namespaces + private List modNamespaces; + + // Type list + private GuiCustomScroll scroll; + private final HashMap displayNameToTypeId = new HashMap<>(); + private String[] filteredTypeIds; private AbilityFieldBuilder builder; public SubGuiConditionEdit(AbilityCondition existing) { this.result = null; - - conditionTypeIds = AbilityController.Instance.getConditionTypes(); - conditionTypeNames = new String[conditionTypeIds.length]; - for (int i = 0; i < conditionTypeIds.length; i++) { - Supplier factory = AbilityController.Instance.getConditionType(conditionTypeIds[i]); - conditionTypeNames[i] = factory != null ? factory.get().getName() : conditionTypeIds[i]; - } + this.modNamespaces = getModNamespaces(); if (existing != null) { - for (int i = 0; i < conditionTypeIds.length; i++) { - if (conditionTypeIds[i].equals(existing.getTypeId())) { - selectedTypeIndex = i; - break; - } - } this.condition = existing; + this.selectedTypeId = existing.getTypeId(); + // Set namespace filter to match existing condition + setNamespaceForTypeId(selectedTypeId); } else { - this.condition = spawnCondition(selectedTypeIndex); + // Default to first available condition + String[] allTypes = AbilityController.Instance.getConditionTypes(); + if (allTypes.length > 0) { + this.selectedTypeId = allTypes[0]; + this.condition = spawnCondition(selectedTypeId); + } } - setBackground("menubg.png"); - xSize = 240; - ySize = 180; + setBackground("menubg.png", 217); + xSize = 356; + ySize = 200; } - private AbilityCondition spawnCondition(int typeIndex) { - if (typeIndex < 0 || typeIndex >= conditionTypeIds.length) return null; - Supplier factory = AbilityController.Instance.getConditionType(conditionTypeIds[typeIndex]); + private List getModNamespaces() { + List result = new ArrayList<>(); + List registered = Register.REGISTERED_NAMESPACES.get("condition"); + if (registered != null) { + result.addAll(registered); + } + return result; + } + + private void setNamespaceForTypeId(String typeId) { + if (typeId == null) return; + String[] parts = typeId.split("\\.", 3); + if (parts.length < 2) return; + String ns = parts[1]; + + if ("cnpc".equals(ns)) { + namespaceFilter = 1; + } else { + for (int i = 0; i < modNamespaces.size(); i++) { + if (modNamespaces.get(i).equals(ns)) { + namespaceFilter = 2 + i; + return; + } + } + namespaceFilter = 0; // Fallback to ALL + } + } + + private String getNamespaceFilterLabel() { + if (namespaceFilter == 0) return "ALL"; + if (namespaceFilter == 1) return "CNPC"; + int modIdx = namespaceFilter - 2; + if (modIdx >= 0 && modIdx < modNamespaces.size()) { + String ns = modNamespaces.get(modIdx); + String display = Register.NAMESPACE_DISPLAY_NAMES.get(ns); + return display != null ? display : ns.toUpperCase(); + } + return "ALL"; + } + + private void cycleNamespaceFilter() { + namespaceFilter++; + // 0=ALL, 1=CNPC, 2..2+N-1=mod namespaces, then wrap + int max = 2 + modNamespaces.size(); + if (namespaceFilter >= max) { + namespaceFilter = 0; + } + // Skip empty mod namespace slots + if (namespaceFilter >= 2) { + int modIdx = namespaceFilter - 2; + if (modIdx >= modNamespaces.size()) { + namespaceFilter = 0; + } + } + } + + private String[] getFilteredTypes() { + if (namespaceFilter == 0) { + return AbilityController.Instance.getConditionTypes(); + } else if (namespaceFilter == 1) { + return AbilityController.Instance.getConditionTypesByNamespace("cnpc"); + } else { + int modIdx = namespaceFilter - 2; + if (modIdx >= 0 && modIdx < modNamespaces.size()) { + return AbilityController.Instance.getConditionTypesByNamespace(modNamespaces.get(modIdx)); + } + return AbilityController.Instance.getConditionTypes(); + } + } + + private AbilityCondition spawnCondition(String typeId) { + if (typeId == null) return null; + Supplier factory = AbilityController.Instance.getConditionType(typeId); return factory != null ? factory.get() : null; } @@ -76,18 +158,45 @@ public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldLi GuiNpcTextField.unfocus(); super.initGui(); - int swX = guiLeft + 4; - int swY = guiTop + 5; - int swW = xSize - 8; - int swH = ySize - 35; + int y = guiTop + 5; - int labelY = guiTop + 8; - addLabel(new GuiNpcLabel(0, "gui.type", guiLeft + 8, labelY + 5)); - GuiNpcButton typeBtn = new GuiNpcButton(BTN_TYPE, guiLeft + 80, labelY, 155, 20, conditionTypeNames, selectedTypeIndex); - addButton(typeBtn); + // Namespace filter button (full width at top) + addButton(new GuiNpcButton(BTN_NAMESPACE, guiLeft + LEFT_MARGIN, y, xSize - LEFT_MARGIN * 2, 20, getNamespaceFilterLabel())); + y += 24; - swY += 26; - swH -= 26; + // Build filtered type list + filteredTypeIds = getFilteredTypes(); + displayNameToTypeId.clear(); + List scrollList = new ArrayList<>(); + for (String typeId : filteredTypeIds) { + Supplier factory = AbilityController.Instance.getConditionType(typeId); + String displayName = factory != null ? I18n.format(factory.get().getName()) : typeId; + scrollList.add(displayName); + displayNameToTypeId.put(displayName, typeId); + } + + // Left panel: condition type scroll list + int scrollHeight = ySize - 58; + scroll = new GuiCustomScroll(this, 0); + scroll.guiLeft = guiLeft + LEFT_MARGIN; + scroll.guiTop = y; + scroll.setSize(LEFT_WIDTH, scrollHeight); + scroll.setUnsortedList(scrollList); + + // Pre-select current condition type in scroll + if (selectedTypeId != null) { + for (int i = 0; i < filteredTypeIds.length; i++) { + if (filteredTypeIds[i].equals(selectedTypeId)) { + scroll.selected = i; + break; + } + } + } + addScroll(scroll); + + // Right panel: condition config via FieldDef + int rightX = guiLeft + LEFT_MARGIN + LEFT_WIDTH + RIGHT_GAP; + int rightW = xSize - LEFT_MARGIN * 2 - LEFT_WIDTH - RIGHT_GAP; List fields = new ArrayList<>(); if (condition != null) { @@ -97,24 +206,20 @@ public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldLi builder = new AbilityFieldBuilder(this, fontRendererObj); builder.startIds(DECLARATIVE_ID_START, CLEAR_ID_START, LABEL_ID_START); builder.startY(5); + builder.buildScrollWindow(fields, rightX, y, rightW, scrollHeight); - builder.buildScrollWindow(fields, swX, swY, swW, swH); - + // Bottom buttons int btnY = guiTop + ySize - 26; - addButton(new GuiNpcButton(BTN_CANCEL, guiLeft + 8, btnY, 60, 20, "gui.cancel")); - addButton(new GuiNpcButton(BTN_DONE, guiLeft + xSize - 68, btnY, 60, 20, "gui.done")); + addButton(new GuiNpcButton(BTN_CANCEL, guiLeft + LEFT_MARGIN, btnY, 60, 20, "gui.cancel")); + addButton(new GuiNpcButton(BTN_DONE, guiLeft + xSize - LEFT_MARGIN - 60, btnY, 60, 20, "gui.done")); } @Override public void buttonEvent(GuiButton guibutton) { int id = guibutton.id; - if (id == BTN_TYPE) { - int newIndex = ((GuiNpcButton) guibutton).getValue(); - if (newIndex != selectedTypeIndex) { - selectedTypeIndex = newIndex; - condition = spawnCondition(selectedTypeIndex); - } + if (id == BTN_NAMESPACE) { + cycleNamespaceFilter(); initGui(); return; } @@ -138,6 +243,24 @@ public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldLi } } + @Override + public void customScrollClicked(int i, int j, int k, GuiCustomScroll guiCustomScroll) { + if (guiCustomScroll.id == 0 && scroll.hasSelected()) { + String displayName = scroll.getSelected(); + String typeId = displayNameToTypeId.get(displayName); + if (typeId != null && !typeId.equals(selectedTypeId)) { + selectedTypeId = typeId; + condition = spawnCondition(selectedTypeId); + initGui(); + } + } + } + + @Override + public void customScrollDoubleClicked(String selection, GuiCustomScroll guiCustomScroll) { + // Same as single click — user still needs to configure fields + } + @Override public void unFocused(GuiNpcTextField textField) { if (textField.id < DECLARATIVE_ID_START) return;