From 111156da5a63d21cfa10f0ef6fd2811403d2a5d5 Mon Sep 17 00:00:00 2001 From: Kam Date: Fri, 6 Mar 2026 22:03:26 -0500 Subject: [PATCH] Completed Tag Support for Auras, Forms, Outlines, Effects, Linked Items, Abilities --- .../npcs/controllers/AbilityController.java | 14 ++ .../controllers/data/ability/Ability.java | 16 ++ .../category/CategoryItemsRequestPacket.java | 18 ++ .../npcs/client/gui/SubGuiEffectGeneral.java | 14 +- .../npcs/client/gui/SubGuiTagSelect.java | 169 ++++++++++++++++++ .../gui/advanced/SubGuiAbilityConfig.java | 10 ++ .../client/gui/item/SubGuiLinkedItem.java | 6 + .../gui/util/GuiCustomScrollTagged.java | 102 +++++++++++ .../gui/util/GuiDirectoryCategorized.java | 38 +++- .../controllers/CustomEffectController.java | 14 ++ .../controllers/LinkedItemController.java | 15 ++ .../npcs/controllers/TagController.java | 72 ++++++++ .../npcs/controllers/data/CustomEffect.java | 9 + .../npcs/controllers/data/LinkedItem.java | 10 ++ .../assets/customnpcs/lang/en_US.lang | 1 + 15 files changed, 503 insertions(+), 5 deletions(-) create mode 100644 src/main/java/noppes/npcs/client/gui/SubGuiTagSelect.java create mode 100644 src/main/java/noppes/npcs/client/gui/util/GuiCustomScrollTagged.java diff --git a/src/main/java/kamkeel/npcs/controllers/AbilityController.java b/src/main/java/kamkeel/npcs/controllers/AbilityController.java index 08586669f..7f7dac1c3 100644 --- a/src/main/java/kamkeel/npcs/controllers/AbilityController.java +++ b/src/main/java/kamkeel/npcs/controllers/AbilityController.java @@ -52,6 +52,7 @@ import noppes.npcs.api.ability.IChainedAbility; import noppes.npcs.api.handler.IAbilityHandler; import noppes.npcs.controllers.PlayerDataController; import noppes.npcs.controllers.CategoryManager; +import noppes.npcs.controllers.TagController; import noppes.npcs.controllers.data.PlayerData; import noppes.npcs.util.NBTJsonUtil; import kamkeel.npcs.util.FileNameHelper; @@ -369,6 +370,8 @@ public class AbilityController implements IAbilityHandler { } } + TagController.validateTagUUIDs(ability.getTagUUIDs()); + File dir = getCustomAbilityDir(name); File fileNew = new File(dir, name + ".json_new"); File fileCurrent = new File(dir, name + ".json"); @@ -526,6 +529,17 @@ public class AbilityController implements IAbilityHandler { return map; } + public HashMap> getCustomAbilityTagMapForCategory(int catId) { + HashMap> tagMap = new HashMap<>(); + for (Map.Entry entry : customAbilities.entrySet()) { + int assignedCat = customAbilityCatMap.getOrDefault(entry.getKey(), CategoryManager.UNCATEGORIZED_ID); + if (assignedCat == catId && !entry.getValue().getTagUUIDs().isEmpty()) { + tagMap.put(entry.getKey(), entry.getValue().getTagUUIDs()); + } + } + return tagMap; + } + public void moveCustomAbilityToCategory(String name, int destCatId) { Ability ability = customAbilities.get(name); if (ability == null) return; 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 95ef65ddb..2ed1e54a1 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/Ability.java @@ -47,6 +47,7 @@ import noppes.npcs.client.gui.builder.FieldDef; import noppes.npcs.client.gui.util.IAbilityConfigCallback; import noppes.npcs.controllers.AnimationController; import noppes.npcs.controllers.ScriptContainer; +import noppes.npcs.controllers.TagController; import noppes.npcs.controllers.data.Animation; import noppes.npcs.controllers.data.Frame; import noppes.npcs.controllers.data.AbilityScript; @@ -63,8 +64,10 @@ import noppes.npcs.scripted.event.AbilityEvent; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.function.Predicate; public abstract class Ability implements IAbility, IAbilityAction { @@ -129,6 +132,9 @@ public abstract class Ability implements IAbility, IAbilityAction { // Custom data for external mods protected NBTTagCompound customData = new NBTTagCompound(); + // Tags + protected HashSet tagUUIDs = new HashSet<>(); + // Magic data — defines magic types and damage splits for this ability protected MagicData magicData = new MagicData(); @@ -1526,6 +1532,7 @@ public abstract class Ability implements IAbility, IAbilityAction { nbt.setString("telegraphType", telegraphType.name()); nbt.setFloat("telegraphHeightOffset", telegraphHeightOffset); nbt.setTag("customData", (NBTTagCompound) customData.copy()); + TagController.writeTagUUIDs(nbt, "TagUUIDs", tagUUIDs); magicData.writeToNBT(nbt); nbt.setInteger("allowedBy", allowedBy.ordinal()); nbt.setBoolean("ignoreCooldown", ignoreCooldown); @@ -1625,6 +1632,7 @@ public abstract class Ability implements IAbility, IAbilityAction { } telegraphHeightOffset = nbt.getFloat("telegraphHeightOffset"); customData = (NBTTagCompound) nbt.getCompoundTag("customData").copy(); + tagUUIDs = TagController.readTagUUIDs(nbt, "TagUUIDs"); magicData.readToNBT(nbt); allowedBy = UserType.fromOrdinal(nbt.getInteger("allowedBy")); ignoreCooldown = nbt.getBoolean("ignoreCooldown"); @@ -2330,6 +2338,14 @@ public abstract class Ability implements IAbility, IAbilityAction { return customData; } + public HashSet getTagUUIDs() { + return tagUUIDs; + } + + public void setTagUUIDs(HashSet tagUUIDs) { + this.tagUUIDs = tagUUIDs; + } + public MagicData getMagicData() { return magicData; } diff --git a/src/main/java/kamkeel/npcs/network/packets/request/category/CategoryItemsRequestPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/category/CategoryItemsRequestPacket.java index ec7a52d97..9e06ab938 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/category/CategoryItemsRequestPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/category/CategoryItemsRequestPacket.java @@ -18,10 +18,13 @@ import noppes.npcs.constants.EnumScrollData; import noppes.npcs.controllers.AnimationController; import noppes.npcs.controllers.CustomEffectController; import noppes.npcs.controllers.LinkedItemController; +import noppes.npcs.controllers.TagController; import java.io.IOException; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.UUID; public final class CategoryItemsRequestPacket extends AbstractPacket { public static String packetName = "NPC|CatItems"; @@ -67,6 +70,12 @@ public final class CategoryItemsRequestPacket extends AbstractPacket { Map items = getItemsByCat(type, categoryId); ScrollDataPacket.sendScrollData((EntityPlayerMP) player, items, EnumScrollData.CATEGORY_GROUP); + + // Send tag map for the items + HashMap> tagMap = getTagMapByCat(type, categoryId); + if (tagMap != null && !tagMap.isEmpty()) { + TagController.sendCategoryTagMap((EntityPlayerMP) player, tagMap); + } } private static Map getItemsByCat(int type, int catId) { @@ -79,4 +88,13 @@ public final class CategoryItemsRequestPacket extends AbstractPacket { } return new HashMap<>(); } + + private static HashMap> getTagMapByCat(int type, int catId) { + switch (type) { + case EnumCategoryType.EFFECT: return CustomEffectController.getInstance().getItemTagMapForCategory(catId); + case EnumCategoryType.LINKED_ITEM: return LinkedItemController.getInstance().getItemTagMapForCategory(catId); + case EnumCategoryType.ABILITY: return AbilityController.Instance.getCustomAbilityTagMapForCategory(catId); + } + return null; + } } diff --git a/src/main/java/noppes/npcs/client/gui/SubGuiEffectGeneral.java b/src/main/java/noppes/npcs/client/gui/SubGuiEffectGeneral.java index 2aba49947..77d72442c 100644 --- a/src/main/java/noppes/npcs/client/gui/SubGuiEffectGeneral.java +++ b/src/main/java/noppes/npcs/client/gui/SubGuiEffectGeneral.java @@ -15,6 +15,8 @@ import noppes.npcs.client.gui.util.GuiNpcLabel; import noppes.npcs.client.gui.util.GuiNpcTextField; import noppes.npcs.client.gui.util.GuiScrollWindow; import noppes.npcs.client.gui.util.ITextfieldListener; +import noppes.npcs.client.gui.SubGuiTagSelect; +import noppes.npcs.client.gui.util.GuiNpcButton; import noppes.npcs.client.gui.util.SubGuiInterface; import noppes.npcs.client.renderer.ImageData; import noppes.npcs.controllers.data.CustomEffect; @@ -63,12 +65,14 @@ public class SubGuiEffectGeneral extends SubGuiInterface implements ITextfieldLi guiTop += 7; int y = guiTop + 7; int x = guiLeft + 4 + 4; - addTextField(new GuiNpcTextField(1, this, this.fontRendererObj, x + 36, y, 280, 20, effect.name)); + addTextField(new GuiNpcTextField(1, this, this.fontRendererObj, x + 36, y, 210, 20, effect.name)); getTextField(1).setMaxStringLength(40); addLabel(new GuiNpcLabel(1, "gui.name", x, y + 5)); - addLabel(new GuiNpcLabel(-1, "ID", x + 320, y + 1)); - addLabel(new GuiNpcLabel(-2, effect.id + "", x + 320, y + 11)); + addLabel(new GuiNpcLabel(-1, "ID", x + 250, y + 1)); + addLabel(new GuiNpcLabel(-2, effect.id + "", x + 250, y + 11)); + + addButton(new GuiNpcButton(40, getTextField(1).xPosition + getTextField(1).width + 30, y, 70, 20, "gui.tags")); y += 23; @@ -243,6 +247,10 @@ public class SubGuiEffectGeneral extends SubGuiInterface implements ITextfieldLi PacketClient.sendClient(new EffectSavePacket(effect.writeToNBT(false), originalName)); GuiScriptInterface.open(parent, new EffectScript(effect.id)); } + if (id == 40) { + setSubGui(new SubGuiTagSelect(effect.tagUUIDs)); + return; + } if (id == 10) { GuiNpcButtonYesNo button = (GuiNpcButtonYesNo) guibutton; effect.lossOnDeath = button.getBoolean(); diff --git a/src/main/java/noppes/npcs/client/gui/SubGuiTagSelect.java b/src/main/java/noppes/npcs/client/gui/SubGuiTagSelect.java new file mode 100644 index 000000000..2e424038d --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/SubGuiTagSelect.java @@ -0,0 +1,169 @@ +package noppes.npcs.client.gui; + +import kamkeel.npcs.network.PacketClient; +import kamkeel.npcs.network.packets.request.tags.TagsGetPacket; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.util.StatCollector; +import noppes.npcs.client.gui.util.GuiCustomScroll; +import noppes.npcs.client.gui.util.GuiNpcButton; +import noppes.npcs.client.gui.util.GuiNpcLabel; +import noppes.npcs.client.gui.util.GuiNpcTextField; +import noppes.npcs.client.gui.util.ICustomScrollListener; +import noppes.npcs.client.gui.util.IScrollData; +import noppes.npcs.client.gui.util.ITextfieldListener; +import noppes.npcs.client.gui.util.SubGuiInterface; +import noppes.npcs.constants.EnumScrollData; +import noppes.npcs.controllers.TagController; +import noppes.npcs.controllers.data.Tag; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.UUID; +import java.util.Vector; + +/** + * Reusable SubGui for selecting tags on any taggable object. + * Pass in the current tagUUIDs, get back the modified set on close. + */ +public class SubGuiTagSelect extends SubGuiInterface implements IScrollData, ICustomScrollListener, ITextfieldListener { + private GuiCustomScroll scrollAllTags; + private GuiCustomScroll scrollSelectedTags; + private final ArrayList allTagNames = new ArrayList<>(); + private final ArrayList selectedTagNames = new ArrayList<>(); + private final HashSet tagUUIDs; + private String search = ""; + + public SubGuiTagSelect(HashSet tagUUIDs) { + this.tagUUIDs = tagUUIDs; + setBackground("menubg.png"); + xSize = 430; + ySize = 220; + + // Populate selected tag names from UUIDs + TagController tc = TagController.getInstance(); + if (tc != null) { + for (UUID uuid : tagUUIDs) { + Tag tag = tc.getTagFromUUID(uuid); + if (tag != null) { + selectedTagNames.add(tag.name); + } + } + } + + PacketClient.sendClient(new TagsGetPacket()); + } + + public void initGui() { + super.initGui(); + + addLabel(new GuiNpcLabel(1, StatCollector.translateToLocal("tags.allTags"), guiLeft + 22, guiTop + 11)); + if (scrollAllTags == null) { + scrollAllTags = new GuiCustomScroll(this, 0); + scrollAllTags.setSize(150, 155); + } + scrollAllTags.guiLeft = guiLeft + 20; + scrollAllTags.guiTop = guiTop + 24; + this.addScroll(scrollAllTags); + addTextField(new GuiNpcTextField(4, this, fontRendererObj, guiLeft + 20, guiTop + 24 + 160, 150, 20, search)); + + addLabel(new GuiNpcLabel(2, StatCollector.translateToLocal("tags.selectedTags"), guiLeft + 252, guiTop + 11)); + if (scrollSelectedTags == null) { + scrollSelectedTags = new GuiCustomScroll(this, 1); + scrollSelectedTags.setSize(150, 180); + } + scrollSelectedTags.guiLeft = guiLeft + 250; + scrollSelectedTags.guiTop = guiTop + 24; + scrollSelectedTags.setList(selectedTagNames); + this.addScroll(scrollSelectedTags); + + addButton(new GuiNpcButton(10, guiLeft + 185, guiTop + 90, 55, 20, ">")); + addButton(new GuiNpcButton(11, guiLeft + 185, guiTop + 112, 55, 20, "<")); + addButton(new GuiNpcButton(12, guiLeft + 185, guiTop + 140, 55, 20, ">>")); + addButton(new GuiNpcButton(13, guiLeft + 185, guiTop + 162, 55, 20, "<<")); + } + + @Override + public void buttonEvent(GuiButton guibutton) { + if (guibutton.id == 10 && scrollAllTags.hasSelected() && !selectedTagNames.contains(scrollAllTags.getSelected())) { + selectedTagNames.add(scrollAllTags.getSelected()); + } + if (guibutton.id == 12) { + selectedTagNames.clear(); + selectedTagNames.addAll(allTagNames); + } + if (guibutton.id == 11 && scrollSelectedTags.hasSelected()) { + selectedTagNames.remove(scrollSelectedTags.getSelected()); + } + if (guibutton.id == 13) { + selectedTagNames.clear(); + } + initGui(); + } + + @Override + public void setData(Vector list, HashMap data, EnumScrollData type) { + allTagNames.clear(); + allTagNames.addAll(data.keySet()); + allTagNames.sort(String.CASE_INSENSITIVE_ORDER); + scrollAllTags.setList(getSearchList()); + initGui(); + } + + @Override + public void save() { + tagUUIDs.clear(); + TagController tc = TagController.getInstance(); + if (tc != null) { + for (String name : selectedTagNames) { + Tag tag = tc.getTagFromName(name); + if (tag != null) { + tagUUIDs.add(tag.uuid); + } + } + } + } + + public void mouseClicked(int i, int j, int k) { + super.mouseClicked(i, j, k); + if (k == 0 && scrollAllTags != null) + scrollAllTags.mouseClicked(i, j, k); + } + + @Override + public void setSelected(String selected) { + if (scrollAllTags != null) + scrollAllTags.setSelected(selected); + } + + @Override + public void customScrollClicked(int i, int j, int k, GuiCustomScroll guiCustomScroll) { + } + + public void keyTyped(char c, int i) { + super.keyTyped(c, i); + if (getTextField(4) != null) { + if (search.equals(getTextField(4).getText())) + return; + search = getTextField(4).getText().toLowerCase(); + scrollAllTags.setList(getSearchList()); + } + } + + @Override + public void unFocused(GuiNpcTextField guiNpcTextField) { + } + + private List getSearchList() { + if (search.isEmpty()) { + return new ArrayList<>(allTagNames); + } + List list = new ArrayList<>(); + for (String name : allTagNames) { + if (name.toLowerCase().contains(search)) + list.add(name); + } + return list; + } +} 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 485d59b87..6d101d3c4 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiAbilityConfig.java @@ -8,6 +8,7 @@ import noppes.npcs.controllers.data.AbilityScript; import net.minecraft.client.gui.GuiButton; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.StatCollector; +import noppes.npcs.client.gui.SubGuiTagSelect; import noppes.npcs.client.gui.builder.FieldDef; import noppes.npcs.client.gui.util.GuiMenuTopButton; import noppes.npcs.client.gui.util.GuiNpcButton; @@ -193,6 +194,11 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi } } + // Tags button on general tab + if (activeTab == TAB_GENERAL) { + sw.addButton(new GuiNpcButton(40, sw.clipWidth - 95, 5, 80, 20, "gui.tags")); + } + // Conditions on target tab — appended after field defs if (activeTab == TAB_TARGET) { int condY = renderConditions(sw, builder.getLastBuildY(), builder.getNextLabelId()); @@ -323,6 +329,10 @@ public class SubGuiAbilityConfig extends SubGuiInterface implements ITextfieldLi GuiScriptInterface.open(parent, new AbilityScript(ability.getId())); return; } + if (id == 40) { + setSubGui(new SubGuiTagSelect(ability.getTagUUIDs())); + return; + } if (id == -1000) { close(); return; diff --git a/src/main/java/noppes/npcs/client/gui/item/SubGuiLinkedItem.java b/src/main/java/noppes/npcs/client/gui/item/SubGuiLinkedItem.java index c431c9c01..defa42faa 100644 --- a/src/main/java/noppes/npcs/client/gui/item/SubGuiLinkedItem.java +++ b/src/main/java/noppes/npcs/client/gui/item/SubGuiLinkedItem.java @@ -12,6 +12,7 @@ import noppes.npcs.client.ClientCacheHandler; import noppes.npcs.client.CustomNpcResourceListener; import noppes.npcs.client.renderer.AnimationHelper; import noppes.npcs.client.gui.SubGuiColorSelector; +import noppes.npcs.client.gui.SubGuiTagSelect; import net.minecraft.client.gui.GuiScreen; import noppes.npcs.client.gui.script.GuiScriptInterface; import noppes.npcs.client.gui.util.GuiButtonBiDirectional; @@ -159,6 +160,8 @@ public class SubGuiLinkedItem extends SubGuiInterface implements ITextfieldListe } GuiButtonBiDirectional useActionButton = new GuiButtonBiDirectional(6, x + 120, y, 100, 20, useActions, useActionIndex); addButton(useActionButton); + + addButton(new GuiNpcButton(40, x + 230, y, 100, 20, "gui.tags")); y += spacing; // Armor Type: BiDirectional button (None, All, Head, Chestplate, Leggings, Boots) @@ -336,6 +339,9 @@ public class SubGuiLinkedItem extends SubGuiInterface implements ITextfieldListe PacketClient.sendClient(new LinkedItemSavePacket(linkedItem.writeToNBT(false), originalName)); GuiScriptInterface.open(this.parent, new LinkedItemScript(linkedItem.id)); return; + } else if (id == 40) { + setSubGui(new SubGuiTagSelect(linkedItem.tagUUIDs)); + return; } else if (id == 20) { // Open confirmation for version bump. GuiYesNo guiyesno = new GuiYesNo(this, "Confirm", "Bump version and update all linked items?", 20); diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiCustomScrollTagged.java b/src/main/java/noppes/npcs/client/gui/util/GuiCustomScrollTagged.java new file mode 100644 index 000000000..efeb3e6f5 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/util/GuiCustomScrollTagged.java @@ -0,0 +1,102 @@ +package noppes.npcs.client.gui.util; + +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.util.StatCollector; +import noppes.npcs.controllers.TagController; +import noppes.npcs.controllers.data.Tag; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.UUID; + +/** + * Scroll list that renders CNPC tags next to item names. + * Used in directory/fullscreen GUIs for Forms, Auras, Outlines, etc. + */ +public class GuiCustomScrollTagged extends GuiCustomScroll { + private HashMap> itemTagMap = new HashMap<>(); + private boolean showTags = true; + + public GuiCustomScrollTagged(GuiScreen parent, int id) { + super(parent, id); + } + + public void setItemTagMap(HashMap> tagMap) { + this.itemTagMap = tagMap != null ? tagMap : new HashMap<>(); + } + + public HashMap> getItemTagMap() { + return itemTagMap; + } + + public void setShowTags(boolean show) { + this.showTags = show; + } + + @Override + protected void drawItems() { + for (int i = 0; i < list.size(); i++) { + int j = 4; + int k = (14 * i + 4) - scrollY; + if (k >= 4 && k + 12 < ySize) { + int xOffset = scrollHeight < ySize - 8 ? 0 : 10; + String rawEntry = list.get(i); + String displayString = StatCollector.translateToLocal(rawEntry); + + String text = ""; + float maxWidth = showTags && itemTagMap.containsKey(rawEntry) + ? (xSize + xOffset - 8) * 0.5f + : (xSize + xOffset - 8) * 0.8f; + + if (fontRendererObj.getStringWidth(displayString) > maxWidth) { + for (int h = 0; h < displayString.length(); h++) { + char c = displayString.charAt(h); + text += c; + if (fontRendererObj.getStringWidth(text) > maxWidth) + break; + } + if (displayString.length() > text.length()) + text += "..."; + } else { + text = displayString; + } + + Integer customColor = colors.get(rawEntry); + if (customColor != null) { + if (!text.isEmpty()) + fontRendererObj.drawString(text, j, k, customColor); + } else if ((multipleSelection && selectedList.contains(text)) || (!multipleSelection && selected == i)) { + drawVerticalLine(j - 2, k - 4, k + 10, 0xffffffff); + drawVerticalLine(j + xSize - 18 + xOffset, k - 4, k + 10, 0xffffffff); + drawHorizontalLine(j - 2, j + xSize - 18 + xOffset, k - 3, 0xffffffff); + drawHorizontalLine(j - 2, j + xSize - 18 + xOffset, k + 10, 0xffffffff); + fontRendererObj.drawString(text, j, k, 0xffffff); + } else if (i == hover) { + fontRendererObj.drawString(text, j, k, 0x00ff00); + } else { + fontRendererObj.drawString(text, j, k, 0xffffff); + } + + // Draw tags after item name + if (showTags && itemTagMap.containsKey(rawEntry)) { + int tagStartX = j + fontRendererObj.getStringWidth(text) + 3; + int maxX = j + xSize - 18 + xOffset; + HashSet tagUUIDs = itemTagMap.get(rawEntry); + TagController tc = TagController.getInstance(); + if (tc != null && tagUUIDs != null) { + for (UUID uuid : tagUUIDs) { + Tag tag = tc.getTagFromUUID(uuid); + if (tag != null && !tag.hideTag) { + String tagText = "[" + tag.name + "]"; + int tagWidth = fontRendererObj.getStringWidth(tagText); + if (tagStartX + tagWidth > maxX) break; + fontRendererObj.drawString(tagText, tagStartX, k, tag.color); + tagStartX += tagWidth + 3; + } + } + } + } + } + } + } +} diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiDirectoryCategorized.java b/src/main/java/noppes/npcs/client/gui/util/GuiDirectoryCategorized.java index f8d3f316b..e663c5c31 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiDirectoryCategorized.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiDirectoryCategorized.java @@ -1,7 +1,9 @@ package noppes.npcs.client.gui.util; import net.minecraft.client.Minecraft; +import net.minecraft.nbt.NBTTagList; import net.minecraft.util.ResourceLocation; +import noppes.npcs.controllers.TagController; import noppes.npcs.controllers.data.Category; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiScreen; @@ -20,6 +22,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.UUID; import java.util.Vector; /** @@ -32,7 +35,7 @@ public abstract class GuiDirectoryCategorized extends GuiDirectory // ========== Scrolls ========== protected GuiCustomScrollIcons catScroll = new GuiCustomScrollIcons(this, 2); - protected GuiCustomScroll itemScroll = new GuiCustomScroll(this, 0); + protected GuiCustomScrollTagged itemScroll = new GuiCustomScrollTagged(this, 0); // ========== Data ========== protected HashMap catData = new HashMap<>(); @@ -463,8 +466,24 @@ public abstract class GuiDirectoryCategorized extends GuiDirectory if (selectedCatId < 0) return new ArrayList<>(); if (itemSearch.isEmpty()) return new ArrayList<>(itemData.keySet()); List list = new ArrayList<>(); + HashMap> tagMap = itemScroll.getItemTagMap(); for (String name : itemData.keySet()) { - if (name.toLowerCase().contains(itemSearch)) list.add(name); + if (name.toLowerCase().contains(itemSearch)) { + list.add(name); + } else if (tagMap != null && tagMap.containsKey(name)) { + // Search by tag name — match [tagname] or just tagname + HashSet uuids = tagMap.get(name); + TagController tc = TagController.getInstance(); + if (tc != null) { + for (UUID uuid : uuids) { + noppes.npcs.controllers.data.Tag tag = tc.getTagFromUUID(uuid); + if (tag != null && tag.name.toLowerCase().contains(itemSearch.replace("[", "").replace("]", ""))) { + list.add(name); + break; + } + } + } + } } return list; } @@ -693,6 +712,21 @@ public abstract class GuiDirectoryCategorized extends GuiDirectory @Override public void setGuiData(NBTTagCompound compound) { + if (compound.hasKey("CategoryTagMap")) { + // Tag map for category items + HashMap> tagMap = new HashMap<>(); + NBTTagList entries = compound.getTagList("CategoryTagMap", 10); + for (int i = 0; i < entries.tagCount(); i++) { + NBTTagCompound entry = entries.getCompoundTagAt(i); + String itemName = entry.getString("Name"); + HashSet uuids = TagController.readTagUUIDs(entry, "Tags"); + if (!uuids.isEmpty()) { + tagMap.put(itemName, uuids); + } + } + itemScroll.setItemTagMap(tagMap); + return; + } onItemReceived(compound); if (pendingNewItemCatId > 0 && hasSelectedItem() && getSelectedItemId() >= 0) { sendMovePacket(getSelectedItemId(), pendingNewItemCatId); diff --git a/src/main/java/noppes/npcs/controllers/CustomEffectController.java b/src/main/java/noppes/npcs/controllers/CustomEffectController.java index 0938c2aa1..528a3397d 100644 --- a/src/main/java/noppes/npcs/controllers/CustomEffectController.java +++ b/src/main/java/noppes/npcs/controllers/CustomEffectController.java @@ -25,6 +25,7 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -657,6 +658,18 @@ public class CustomEffectController implements ICustomEffectHandler { return map; } + public HashMap> getItemTagMapForCategory(int catId) { + HashMap> tagMap = new HashMap<>(); + List itemIds = categoryManager.getItemsInCategory(catId, getCustomEffects().keySet()); + for (int itemId : itemIds) { + CustomEffect effect = getCustomEffects().get(itemId); + if (effect != null && !effect.tagUUIDs.isEmpty()) { + tagMap.put(effect.name, effect.tagUUIDs); + } + } + return tagMap; + } + @Override public ICustomEffect saveEffect(ICustomEffect customEffect) { if (customEffect.getID() < 0) { @@ -668,6 +681,7 @@ public class CustomEffectController implements ICustomEffectHandler { while (hasOther(customEffect.getName(), customEffect.getID())) customEffect.setName(customEffect.getName() + "_"); + TagController.validateTagUUIDs(((CustomEffect) customEffect).tagUUIDs); getCustomEffects().remove(customEffect.getID()); getCustomEffects().put(customEffect.getID(), (CustomEffect) customEffect); diff --git a/src/main/java/noppes/npcs/controllers/LinkedItemController.java b/src/main/java/noppes/npcs/controllers/LinkedItemController.java index f8f6c4af6..54f676fc8 100644 --- a/src/main/java/noppes/npcs/controllers/LinkedItemController.java +++ b/src/main/java/noppes/npcs/controllers/LinkedItemController.java @@ -20,8 +20,10 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.zip.GZIPInputStream; public class LinkedItemController implements ILinkedItemHandler { @@ -215,6 +217,7 @@ public class LinkedItemController implements ILinkedItemHandler { while (hasOther(linkedItem.getName(), linkedItem.getId())) linkedItem.setName(linkedItem.getName() + "_"); + TagController.validateTagUUIDs(((LinkedItem) linkedItem).tagUUIDs); linkedItems.remove(linkedItem.getId()); linkedItems.put(linkedItem.getId(), (LinkedItem) linkedItem); saveLinkedItemsMap(); @@ -387,4 +390,16 @@ public class LinkedItemController implements ILinkedItemHandler { } return map; } + + public HashMap> getItemTagMapForCategory(int catId) { + HashMap> tagMap = new HashMap<>(); + List itemIds = categoryManager.getItemsInCategory(catId, linkedItems.keySet()); + for (int itemId : itemIds) { + LinkedItem item = linkedItems.get(itemId); + if (item != null && !item.tagUUIDs.isEmpty()) { + tagMap.put(item.name, item.tagUUIDs); + } + } + return tagMap; + } } diff --git a/src/main/java/noppes/npcs/controllers/TagController.java b/src/main/java/noppes/npcs/controllers/TagController.java index cbd2ed006..738418a13 100644 --- a/src/main/java/noppes/npcs/controllers/TagController.java +++ b/src/main/java/noppes/npcs/controllers/TagController.java @@ -1,5 +1,8 @@ package noppes.npcs.controllers; +import kamkeel.npcs.network.PacketHandler; +import kamkeel.npcs.network.packets.data.large.GuiDataPacket; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.CompressedStreamTools; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; @@ -9,6 +12,9 @@ import noppes.npcs.api.handler.ITagHandler; import noppes.npcs.api.handler.data.ITag; import noppes.npcs.controllers.data.Tag; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.nbt.NBTTagString; + import java.io.BufferedInputStream; import java.io.DataInputStream; import java.io.File; @@ -18,6 +24,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; @@ -234,4 +241,69 @@ public class TagController implements ITagHandler { public HashSet getAllTags() { return new HashSet(this.tags.values()); } + + /** + * Write a set of tag UUIDs to an NBT compound under the given key. + */ + public static void writeTagUUIDs(NBTTagCompound compound, String key, HashSet tagUUIDs) { + if (tagUUIDs != null && !tagUUIDs.isEmpty()) { + NBTTagList list = new NBTTagList(); + for (UUID uuid : tagUUIDs) { + list.appendTag(new NBTTagString(uuid.toString())); + } + compound.setTag(key, list); + } + } + + /** + * Read a set of tag UUIDs from an NBT compound under the given key. + */ + public static HashSet readTagUUIDs(NBTTagCompound compound, String key) { + HashSet uuids = new HashSet<>(); + if (compound.hasKey(key)) { + NBTTagList list = compound.getTagList(key, 8); + for (int i = 0; i < list.tagCount(); i++) { + try { + uuids.add(UUID.fromString(list.getStringTagAt(i))); + } catch (IllegalArgumentException ignored) {} + } + } + return uuids; + } + + /** + * Remove invalid tag UUIDs (those not in the tag registry). + */ + public static void validateTagUUIDs(HashSet tagUUIDs) { + TagController tc = getInstance(); + if (tc == null) return; + tagUUIDs.removeIf(uuid -> tc.getTagFromUUID(uuid) == null); + } + + /** + * Build and send a CategoryTagMap GuiData packet for a set of items. + * @param player the target player + * @param itemTags map of item name -> tag UUIDs (only validated tags are sent) + */ + public static void sendCategoryTagMap(EntityPlayerMP player, HashMap> itemTags) { + NBTTagCompound compound = new NBTTagCompound(); + NBTTagList entries = new NBTTagList(); + TagController tc = getInstance(); + for (Map.Entry> entry : itemTags.entrySet()) { + HashSet valid = new HashSet<>(); + for (UUID uuid : entry.getValue()) { + if (tc != null && tc.getTagFromUUID(uuid) != null) { + valid.add(uuid); + } + } + if (!valid.isEmpty()) { + NBTTagCompound itemEntry = new NBTTagCompound(); + itemEntry.setString("Name", entry.getKey()); + writeTagUUIDs(itemEntry, "Tags", valid); + entries.appendTag(itemEntry); + } + } + compound.setTag("CategoryTagMap", entries); + GuiDataPacket.sendGuiData(player, compound); + } } diff --git a/src/main/java/noppes/npcs/controllers/data/CustomEffect.java b/src/main/java/noppes/npcs/controllers/data/CustomEffect.java index aec4eaf6e..582098c87 100644 --- a/src/main/java/noppes/npcs/controllers/data/CustomEffect.java +++ b/src/main/java/noppes/npcs/controllers/data/CustomEffect.java @@ -9,8 +9,11 @@ import noppes.npcs.api.entity.IPlayer; import noppes.npcs.api.handler.data.ICustomEffect; import noppes.npcs.constants.EnumScriptType; import noppes.npcs.controllers.CustomEffectController; +import noppes.npcs.controllers.TagController; import noppes.npcs.scripted.event.player.PlayerEvent; +import java.util.HashSet; +import java.util.UUID; import java.util.function.Consumer; public class CustomEffect implements ICustomEffect { @@ -36,6 +39,8 @@ public class CustomEffect implements ICustomEffect { public int index = 0; + public HashSet tagUUIDs = new HashSet<>(); + public CustomEffect() { } @@ -261,6 +266,8 @@ public class CustomEffect implements ICustomEffect { compound.setInteger("iconFrameCount", frameCount); compound.setInteger("iconFrameTime", frametime); + TagController.writeTagUUIDs(compound, "TagUUIDs", tagUUIDs); + if (saveScripts) { NBTTagCompound scriptData = new NBTTagCompound(); EffectScript handler = getScriptHandler(); @@ -309,6 +316,8 @@ public class CustomEffect implements ICustomEffect { if (compound.hasKey("iconFrameTime")) frametime = Math.max(1, compound.getInteger("iconFrameTime")); + tagUUIDs = TagController.readTagUUIDs(compound, "TagUUIDs"); + if (compound.hasKey("ScriptData", Constants.NBT.TAG_COMPOUND)) { EffectScript handler = new EffectScript(); handler.readFromNBT(compound.getCompoundTag("ScriptData")); diff --git a/src/main/java/noppes/npcs/controllers/data/LinkedItem.java b/src/main/java/noppes/npcs/controllers/data/LinkedItem.java index 961c570f9..14081a85a 100644 --- a/src/main/java/noppes/npcs/controllers/data/LinkedItem.java +++ b/src/main/java/noppes/npcs/controllers/data/LinkedItem.java @@ -8,9 +8,13 @@ import noppes.npcs.EventHooks; import noppes.npcs.api.handler.data.ILinkedItem; import noppes.npcs.api.item.IItemStack; import noppes.npcs.controllers.LinkedItemController; +import noppes.npcs.controllers.TagController; import noppes.npcs.scripted.NpcAPI; import noppes.npcs.scripted.item.ScriptLinkedItem; +import java.util.HashSet; +import java.util.UUID; + public class LinkedItem implements ILinkedItem { public static final String LINKED_VERSION_VERSION_TAG = "LinkedVersion"; public static final String LINKED_DATA_NBT_TAG = "LinkedData"; @@ -33,6 +37,8 @@ public class LinkedItem implements ILinkedItem { public int armorType = -2; //-2: Fits in no armor slot, -1: Fits in all slots, 0 - 3: Fits in Head -> Boots slot respectively public int enchantability; + public HashSet tagUUIDs = new HashSet<>(); + public LinkedItem() { this("New"); } @@ -67,6 +73,8 @@ public class LinkedItem implements ILinkedItem { compound.setInteger("MaxItemUseDuration", this.maxItemUseDuration); compound.setInteger("ItemUseAction", this.itemUseAction); + TagController.writeTagUUIDs(compound, "TagUUIDs", tagUUIDs); + if (saveScripts) { NBTTagCompound scriptData = new NBTTagCompound(); LinkedItemScript handler = getScriptHandler(); @@ -97,6 +105,8 @@ public class LinkedItem implements ILinkedItem { this.maxItemUseDuration = compound.getInteger("MaxItemUseDuration"); this.itemUseAction = compound.getInteger("ItemUseAction"); + tagUUIDs = TagController.readTagUUIDs(compound, "TagUUIDs"); + if (compound.hasKey("ScriptData", Constants.NBT.TAG_COMPOUND)) { LinkedItemScript handler = new LinkedItemScript(); handler.readFromNBT(compound.getCompoundTag("ScriptData")); diff --git a/src/main/resources/assets/customnpcs/lang/en_US.lang b/src/main/resources/assets/customnpcs/lang/en_US.lang index 332919216..a2e6b7d5c 100644 --- a/src/main/resources/assets/customnpcs/lang/en_US.lang +++ b/src/main/resources/assets/customnpcs/lang/en_US.lang @@ -2208,6 +2208,7 @@ ability.icon.layer=Layer gui.both=Both gui.players=Players +gui.tags=Tags # Conditions condition.edit=Edit Condition