From 75a58f7e74b4b44d793fb51059d39d631b43bc47 Mon Sep 17 00:00:00 2001 From: Kam Date: Sat, 21 Feb 2026 03:03:42 -0500 Subject: [PATCH] Condition Registry [2/2] --- .../npcs/controllers/AbilityController.java | 2 + .../ability/conditions/AbilityCondition.java | 18 ++ .../conditions/ConditionHPThreshold.java | 99 +--------- .../ability/conditions/ConditionHitCount.java | 9 +- .../ability/conditions/ConditionItem.java | 14 ++ .../conditions/ConditionQuestCompleted.java | 86 +++++++++ .../conditions/ConditionThreshold.java | 171 ++++++++++++++++++ .../gui/advanced/SubGuiAbilityConfig.java | 4 +- .../gui/advanced/SubGuiConditionEdit.java | 62 +++++-- .../client/gui/builder/GuiFieldBuilder.java | 3 + .../assets/customnpcs/lang/en_US.lang | 11 ++ 11 files changed, 367 insertions(+), 112 deletions(-) create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionThreshold.java diff --git a/src/main/java/kamkeel/npcs/controllers/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/AbilityController.java index a2a4d8b33..b29bbb668 100644 --- a/src/main/java/kamkeel/npcs/controllers/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/AbilityController.java @@ -5,6 +5,7 @@ import kamkeel.npcs.controllers.data.ability.conditions.AbilityCondition; import kamkeel.npcs.controllers.data.ability.conditions.ConditionHPThreshold; import kamkeel.npcs.controllers.data.ability.conditions.ConditionHitCount; import kamkeel.npcs.controllers.data.ability.conditions.ConditionItem; +import kamkeel.npcs.controllers.data.ability.conditions.ConditionQuestCompleted; import kamkeel.npcs.controllers.data.ability.type.AbilityCharge; import kamkeel.npcs.controllers.data.ability.type.AbilityCutter; import kamkeel.npcs.controllers.data.ability.type.AbilityDash; @@ -154,6 +155,7 @@ public class AbilityController implements IAbilityHandler { registerCondition(ConditionHPThreshold::new); registerCondition(ConditionHitCount::new); registerCondition(ConditionItem::new); + registerCondition(ConditionQuestCompleted::new); } // ═══════════════════════════════════════════════════════════════════════════ diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/AbilityCondition.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/AbilityCondition.java index 6f5299ca4..479776930 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/AbilityCondition.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/AbilityCondition.java @@ -6,6 +6,7 @@ import kamkeel.npcs.controllers.AbilityController; import kamkeel.npcs.controllers.data.ability.UserType; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.StatCollector; import noppes.npcs.LogWriter; import noppes.npcs.client.gui.builder.FieldDef; @@ -40,6 +41,14 @@ public abstract class AbilityCondition { return false; } + /** + * Returns true if all required fields are filled in. + * Used to disable the Done button in SubGuiConditionEdit when incomplete. + */ + public boolean isConfigured() { + return true; + } + public UserType getUserType() { return userType; } @@ -63,10 +72,19 @@ public abstract class AbilityCondition { @SideOnly(Side.CLIENT) public abstract void getConditionDefinitions(List defs); + /** + * Returns a human-readable summary of this condition's configuration. + * Used for tooltips when hovering over condition buttons. + */ + @SideOnly(Side.CLIENT) + public abstract String getConditionSummary(); + @SideOnly(Side.CLIENT) public final List getAllDefinitions() { List defs = new ArrayList<>(); + defs.add(FieldDef.labelField("ability.validFor", () -> + "\u00A7e" + StatCollector.translateToLocal("ability.userType." + getUserType().name()))); defs.add(FieldDef.enumField("condition.filter", ConditionFilter.class, this::getFilter, this::setFilter)); getConditionDefinitions(defs); return defs; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHPThreshold.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHPThreshold.java index fe77de17d..dfb997526 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHPThreshold.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionHPThreshold.java @@ -1,60 +1,8 @@ package kamkeel.npcs.controllers.data.ability.conditions; import net.minecraft.entity.EntityLivingBase; -import net.minecraft.nbt.NBTTagCompound; -import noppes.npcs.client.gui.builder.FieldDef; -import java.util.List; - -public class ConditionHPThreshold extends AbilityCondition { - private float threshold = 0.5f; // 50% - private boolean percent = true; - private ThresholdType thresholdType = ThresholdType.ABOVE; - - public enum ThresholdType { - ABOVE { - @Override - public boolean test(float value, float threshold) { - return value > threshold; - } - }, - BELOW { - @Override - public boolean test(float value, float threshold) { - return value < threshold; - } - }, - EQUAL { - @Override - public boolean test(float value, float threshold) { - return value == threshold; - } - }; - - public abstract boolean test(float value, float threshold); - - public static ThresholdType fromOrdinal(int ordinal) { - ThresholdType[] values = values(); - if (ordinal >= 0 && ordinal < values.length) { - return values[ordinal]; - } - return ABOVE; - } - - @Override - public String toString() { - switch (this) { - case ABOVE: - return "condition.hp_above"; - case BELOW: - return "condition.hp_below"; - case EQUAL: - return "condition.hp_equal"; - default: - return name(); - } - } - } +public class ConditionHPThreshold extends ConditionThreshold { public ConditionHPThreshold() { this.typeId = "condition.cnpc.hp_threshold"; @@ -62,50 +10,17 @@ public class ConditionHPThreshold extends AbilityCondition { } @Override - protected boolean checkEntity(EntityLivingBase entity) { - float entityHP = isPercent() ? entity.getHealth() / entity.getMaxHealth() : entity.getHealth(); - return thresholdType.test(entityHP, getThreshold()); + protected float getEntityValue(EntityLivingBase entity) { + return entity.getHealth(); } @Override - public void getConditionDefinitions(List defs) { - defs.add(FieldDef.floatField("condition.threshold", this::getThreshold, this::setThreshold).min(0)); - defs.add(FieldDef.boolField("condition.percent", this::isPercent, this::setIsPercent)); - defs.add(FieldDef.enumField("condition.threshold_type", ThresholdType.class, - this::getThresholdType, this::setThresholdType)); + protected float getEntityMaxValue(EntityLivingBase entity) { + return entity.getMaxHealth(); } @Override - public void writeTypeNBT(NBTTagCompound nbt) { - nbt.setFloat("threshold", threshold); - } - - @Override - public void readTypeNBT(NBTTagCompound nbt) { - threshold = nbt.getFloat("threshold"); - } - - public float getThreshold() { - return threshold; - } - - public void setThreshold(float threshold) { - this.threshold = Math.max(0, threshold); - } - - public ThresholdType getThresholdType() { - return thresholdType; - } - - public void setThresholdType(ThresholdType thresholdType) { - this.thresholdType = thresholdType; - } - - public boolean isPercent() { - return percent; - } - - public void setIsPercent(boolean percent) { - this.percent = percent; + protected String getStatName() { + return "HP"; } } 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 1c8dfaabf..dbd71922b 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 @@ -1,8 +1,8 @@ package kamkeel.npcs.controllers.data.ability.conditions; -import kamkeel.npcs.controllers.data.ability.UserType; import net.minecraft.entity.EntityLivingBase; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.StatCollector; import noppes.npcs.client.gui.builder.FieldDef; import noppes.npcs.entity.EntityNPCInterface; @@ -15,7 +15,6 @@ public class ConditionHitCount extends AbilityCondition { public ConditionHitCount() { this.typeId = "condition.cnpc.hit_count"; this.name = "condition.hit_count"; - this.userType = UserType.NPC_ONLY; } @Override @@ -59,6 +58,12 @@ public class ConditionHitCount extends AbilityCondition { defs.add(FieldDef.intField("condition.within_ticks", this::getWithinTicks, this::setWithinTicks).min(0)); } + @Override + public String getConditionSummary() { + String filterLabel = StatCollector.translateToLocal("condition.filter." + getFilter().name().toLowerCase()); + return "[" + filterLabel + "] Hit " + requiredHits + " times in " + withinTicks + " ticks"; + } + @Override public void writeTypeNBT(NBTTagCompound nbt) { nbt.setInteger("requiredHits", requiredHits); diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionItem.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionItem.java index d8deece10..9bb445043 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionItem.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionItem.java @@ -4,6 +4,7 @@ import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.StatCollector; import noppes.npcs.client.gui.builder.FieldDef; import noppes.npcs.entity.EntityNPCInterface; @@ -149,6 +150,14 @@ public class ConditionItem extends AbilityCondition { // .hover("condition.hover.required_count")); } + @Override + public String getConditionSummary() { + String filterLabel = StatCollector.translateToLocal("condition.filter." + getFilter().name().toLowerCase()); + String usage = usageType.name(); + String item = itemName.isEmpty() ? "None" : itemName; + return "[" + filterLabel + "] " + usage + ": " + item; + } + @Override public void writeTypeNBT(NBTTagCompound nbt) { nbt.setString("itemName", itemName); @@ -166,6 +175,11 @@ public class ConditionItem extends AbilityCondition { } + @Override + public boolean isConfigured() { + return itemName != null && !itemName.isEmpty(); + } + public UsageType getUsageType() { return usageType; } public void setUsageType(UsageType usageType) { this.usageType = usageType; } diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java new file mode 100644 index 000000000..04c1ec16a --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionQuestCompleted.java @@ -0,0 +1,86 @@ +package kamkeel.npcs.controllers.data.ability.conditions; + +import kamkeel.npcs.controllers.data.ability.UserType; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.StatCollector; +import noppes.npcs.client.gui.builder.FieldDef; +import noppes.npcs.client.gui.select.GuiQuestSelection; +import noppes.npcs.client.gui.util.SubGuiInterface; +import noppes.npcs.controllers.QuestController; +import noppes.npcs.controllers.data.PlayerData; +import noppes.npcs.controllers.data.Quest; + +import java.util.List; + +public class ConditionQuestCompleted extends AbilityCondition { + private int questId = -1; + + public ConditionQuestCompleted() { + this.typeId = "condition.cnpc.quest_completed"; + this.name = "condition.quest_completed"; + this.userType = UserType.PLAYER_ONLY; + } + + @Override + protected boolean checkEntity(EntityLivingBase entity) { + if (!(entity instanceof EntityPlayer)) return false; + if (questId < 0) return false; + + EntityPlayer player = (EntityPlayer) entity; + return PlayerData.get(player).questData.hasFinishedQuest(questId); + } + + @Override + public void getConditionDefinitions(List defs) { + defs.add(FieldDef.subGuiField("condition.select_quest", + () -> new GuiQuestSelection(questId), + (SubGuiInterface gui) -> { + GuiQuestSelection sel = (GuiQuestSelection) gui; + if (sel.selectedQuest != null) { + questId = sel.selectedQuest.id; + } + }) + .buttonLabel(() -> { + if (questId < 0) return "None"; + Quest q = QuestController.Instance.quests.get(questId); + return q != null ? q.title : "ID:" + questId; + }) + .clearable(() -> questId = -1)); + } + + @Override + public String getConditionSummary() { + String filterLabel = StatCollector.translateToLocal("condition.filter." + getFilter().name().toLowerCase()); + String questName = "None"; + if (questId >= 0) { + Quest q = QuestController.Instance.quests.get(questId); + questName = q != null ? q.title : "ID:" + questId; + } + return "[" + filterLabel + "] Quest: " + questName; + } + + @Override + public void writeTypeNBT(NBTTagCompound nbt) { + nbt.setInteger("questId", questId); + } + + @Override + public void readTypeNBT(NBTTagCompound nbt) { + questId = nbt.getInteger("questId"); + } + + @Override + public boolean isConfigured() { + return questId >= 0; + } + + public int getQuestId() { + return questId; + } + + public void setQuestId(int questId) { + this.questId = questId; + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionThreshold.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionThreshold.java new file mode 100644 index 000000000..fda42ce6a --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionThreshold.java @@ -0,0 +1,171 @@ +package kamkeel.npcs.controllers.data.ability.conditions; + +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.StatCollector; +import noppes.npcs.client.gui.builder.FieldDef; + +import java.util.List; + +/** + * Abstract base for threshold-based conditions (HP, Ki, etc.). + * Provides shared percent/flat toggle, ThresholdType enum, and FieldDef generation. + */ +public abstract class ConditionThreshold extends AbilityCondition { + + public enum ThresholdType { + ABOVE { + @Override + public boolean test(float value, float threshold) { + return value >= threshold; + } + }, + BELOW { + @Override + public boolean test(float value, float threshold) { + return value <= threshold; + } + }, + EQUAL { + @Override + public boolean test(float value, float threshold) { + return value == threshold; + } + }; + + public abstract boolean test(float value, float threshold); + + public static ThresholdType fromOrdinal(int ordinal) { + ThresholdType[] values = values(); + if (ordinal >= 0 && ordinal < values.length) { + return values[ordinal]; + } + return ABOVE; + } + + @Override + public String toString() { + switch (this) { + case ABOVE: + return "condition.above"; + case BELOW: + return "condition.below"; + case EQUAL: + return "condition.equal"; + default: + return name(); + } + } + } + + protected float thresholdFlat = 100f; + protected float thresholdPercent = 0.5f; + protected boolean percent = true; + protected ThresholdType thresholdType = ThresholdType.ABOVE; + + /** + * Returns the raw stat value for the entity (e.g. current HP, current Ki). + */ + protected abstract float getEntityValue(EntityLivingBase entity); + + /** + * Returns the maximum stat value for the entity (e.g. max HP, max Ki). + * Used to compute the percentage ratio. + */ + protected abstract float getEntityMaxValue(EntityLivingBase entity); + + /** + * Returns the display name for the stat being checked (e.g. "HP", "Ki"). + * Used in getConditionSummary(). + */ + protected abstract String getStatName(); + + @Override + protected boolean checkEntity(EntityLivingBase entity) { + float value; + float threshold; + if (percent) { + float max = getEntityMaxValue(entity); + value = max > 0 ? getEntityValue(entity) / max : 0; + threshold = thresholdPercent; + } else { + value = getEntityValue(entity); + threshold = thresholdFlat; + } + return thresholdType.test(value, threshold); + } + + @Override + public void getConditionDefinitions(List defs) { + defs.add(FieldDef.floatField("condition.threshold_flat", this::getThresholdFlat, this::setThresholdFlat) + .min(0).visibleWhen(() -> !isPercent())); + defs.add(FieldDef.floatField("condition.threshold_percent", this::getThresholdPercent, this::setThresholdPercent) + .min(0).max(1).visibleWhen(this::isPercent)); + defs.add(FieldDef.boolField("condition.percent", this::isPercent, this::setIsPercent)); + defs.add(FieldDef.enumField("condition.threshold_type", ThresholdType.class, + this::getThresholdType, this::setThresholdType)); + } + + @Override + public void writeTypeNBT(NBTTagCompound nbt) { + nbt.setFloat("thresholdFlat", thresholdFlat); + nbt.setFloat("thresholdPercent", thresholdPercent); + nbt.setBoolean("percent", percent); + nbt.setInteger("thresholdType", thresholdType.ordinal()); + } + + @Override + public void readTypeNBT(NBTTagCompound nbt) { + thresholdFlat = nbt.getFloat("thresholdFlat"); + thresholdPercent = nbt.getFloat("thresholdPercent"); + percent = nbt.getBoolean("percent"); + thresholdType = ThresholdType.fromOrdinal(nbt.getInteger("thresholdType")); + } + + @Override + public String getConditionSummary() { + String filterLabel = StatCollector.translateToLocal("condition.filter." + getFilter().name().toLowerCase()); + String typeName = getStatName(); + String typeLabel = StatCollector.translateToLocal(thresholdType.toString()); + String value = percent + ? String.format("%.0f%%", thresholdPercent * 100) + : String.format("%.0f", thresholdFlat); + return "[" + filterLabel + "] " + typeName + " " + typeLabel + " " + value; + } + + // ═══════════════════════════════════════════════════════════════════ + // GETTERS / SETTERS + // ═══════════════════════════════════════════════════════════════════ + + public float getThresholdFlat() { + return thresholdFlat; + } + + public void setThresholdFlat(float value) { + this.thresholdFlat = Math.max(0, value); + } + + public float getThresholdPercent() { + return thresholdPercent; + } + + public void setThresholdPercent(float value) { + this.thresholdPercent = Math.max(0, Math.min(1, value)); + } + + public boolean isPercent() { + return percent; + } + + public void setIsPercent(boolean percent) { + this.percent = percent; + } + + public ThresholdType getThresholdType() { + return thresholdType; + } + + public void setThresholdType(ThresholdType type) { + this.thresholdType = type; + } +} diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java index a3cee0443..544a7fefd 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java @@ -245,7 +245,9 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi for (int i = 0; i < conditions.size() && i < MAX_CONDITIONS; i++) { AbilityCondition cond = conditions.get(i); String condName = getConditionDisplayName(cond); - sw.addButton(new GuiNpcButton(COND_BASE + i * COND_STRIDE, L_LABEL_X, y, 140, 20, condName)); + GuiNpcButton condBtn = new GuiNpcButton(COND_BASE + i * COND_STRIDE, L_LABEL_X, y, 140, 20, condName); + condBtn.hoverableText = cond.getConditionSummary(); + sw.addButton(condBtn); sw.addButton(new GuiNpcButton(COND_BASE + i * COND_STRIDE + 1, L_LABEL_X + 145, y, 40, 20, "gui.edit")); sw.addButton(new GuiNpcButton(COND_BASE + i * COND_STRIDE + 2, L_LABEL_X + 190, y, 20, 20, "X")); y += 22; 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 bca83c150..b0ed1d635 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiConditionEdit.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiConditionEdit.java @@ -31,6 +31,7 @@ public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldLi private static final int BTN_NAMESPACE = 1; private static final int BTN_CANCEL = 10; private static final int BTN_DONE = 11; + private static final int TF_SEARCH = 12; // Left panel private static final int LEFT_WIDTH = 110; @@ -39,6 +40,8 @@ public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldLi // Right panel private static final int RIGHT_GAP = 5; + private String searchText = ""; + private AbilityCondition condition; private AbilityCondition result; private String selectedTypeId; @@ -158,11 +161,11 @@ public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldLi GuiNpcTextField.unfocus(); super.initGui(); - int y = guiTop + 5; + int leftX = guiLeft + LEFT_MARGIN; + int btnY = guiTop + ySize - 26; - // Namespace filter button (full width at top) - addButton(new GuiNpcButton(BTN_NAMESPACE, guiLeft + LEFT_MARGIN, y, xSize - LEFT_MARGIN * 2, 20, getNamespaceFilterLabel())); - y += 24; + // Namespace filter button (above left scroll only) + addButton(new GuiNpcButton(BTN_NAMESPACE, leftX, guiTop + 5, LEFT_WIDTH, 20, getNamespaceFilterLabel())); // Build filtered type list filteredTypeIds = getFilteredTypes(); @@ -171,22 +174,28 @@ public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldLi for (String typeId : filteredTypeIds) { Supplier factory = AbilityController.Instance.getConditionType(typeId); String displayName = factory != null ? I18n.format(factory.get().getName()) : typeId; + if (!searchText.isEmpty() && !displayName.toLowerCase().contains(searchText.toLowerCase())) continue; 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; + // Left panel: condition type scroll list (between namespace button and search bar) + int scrollTop = guiTop + 29; + int scrollBottom = btnY - 24; + int scrollHeight = scrollBottom - scrollTop; + if (scroll == null) { + scroll = new GuiCustomScroll(this, 0); + } + scroll.guiLeft = leftX; + scroll.guiTop = scrollTop; 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)) { + for (int i = 0; i < scrollList.size(); i++) { + String typeId = displayNameToTypeId.get(scrollList.get(i)); + if (typeId != null && typeId.equals(selectedTypeId)) { scroll.selected = i; break; } @@ -194,9 +203,16 @@ public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldLi } addScroll(scroll); - // Right panel: condition config via FieldDef - int rightX = guiLeft + LEFT_MARGIN + LEFT_WIDTH + RIGHT_GAP; + // Search text field (below left scroll) + GuiNpcTextField searchField = new GuiNpcTextField(TF_SEARCH, this, fontRendererObj, leftX, scrollBottom + 2, LEFT_WIDTH, 20, searchText); + searchField.setHoverText("gui.search"); + addTextField(searchField); + + // Right panel: condition config via FieldDef (full height from top to buttons) + int rightX = leftX + LEFT_WIDTH + RIGHT_GAP; int rightW = xSize - LEFT_MARGIN * 2 - LEFT_WIDTH - RIGHT_GAP; + int rightTop = guiTop + 5; + int rightHeight = btnY - rightTop - 4; List fields = new ArrayList<>(); if (condition != null) { @@ -205,13 +221,17 @@ public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldLi builder = new AbilityFieldBuilder(this, fontRendererObj); builder.startIds(DECLARATIVE_ID_START, CLEAR_ID_START, LABEL_ID_START); + builder.contentRight(rightW); builder.startY(5); - builder.buildScrollWindow(fields, rightX, y, rightW, scrollHeight); + builder.buildScrollWindow(fields, rightX, rightTop, rightW, rightHeight); // Bottom buttons - int btnY = guiTop + ySize - 26; - 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")); + addButton(new GuiNpcButton(BTN_CANCEL, leftX, btnY, 60, 20, "gui.cancel")); + GuiNpcButton doneBtn = new GuiNpcButton(BTN_DONE, guiLeft + xSize - LEFT_MARGIN - 60, btnY, 60, 20, "gui.done"); + if (condition == null || !condition.isConfigured()) { + doneBtn.setEnabled(false); + } + addButton(doneBtn); } @Override @@ -263,6 +283,14 @@ public class SubGuiConditionEdit extends SubGuiInterface implements ITextfieldLi @Override public void unFocused(GuiNpcTextField textField) { + if (textField.id == TF_SEARCH) { + String newText = textField.getText(); + if (!newText.equals(searchText)) { + searchText = newText; + initGui(); + } + return; + } if (textField.id < DECLARATIVE_ID_START) return; if (builder != null && builder.handleTextFieldEvent(textField.id, textField)) { initGui(); diff --git a/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java b/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java index bb069b8f5..8d7d88d61 100644 --- a/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java +++ b/src/main/java/noppes/npcs/client/gui/builder/GuiFieldBuilder.java @@ -344,6 +344,9 @@ public class GuiFieldBuilder { String translated = StatCollector.translateToLocal(def.getLabel()); int labelW = fontRenderer.getStringWidth(translated); int fieldX = colLLabel + labelW + labelPadding; + // Clamp field width so it doesn't extend under the scrollbar + int maxW = contentRight - fieldX - 15; + if (maxW > 0 && fieldW > maxW) fieldW = maxW; renderFieldAt(def, colLLabel, fieldX, fieldW, y); } diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index 2e222fae7..cc3f289e0 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -2099,6 +2099,17 @@ condition.full_armor=Full Armor condition.item_count=Item Count condition.item_name=Item Name +# Condition Threshold +condition.above=Above +condition.below=Below +condition.equal=Equal +condition.threshold_flat=Threshold +condition.threshold_percent=Threshold % + +# Condition Quest Completed +condition.quest_completed=Quest Completed +condition.select_quest=Select Quest + # Condition Hover Tooltips condition.hover.type=Condition type that must be met for ability to activate condition.hover.hpAbove=Activates when NPC health is above this percentage