diff --git a/src/main/java/com/hfstudio/guidenh/guide/compiler/Frontmatter.java b/src/main/java/com/hfstudio/guidenh/guide/compiler/Frontmatter.java index 1a72156e..357c965e 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/compiler/Frontmatter.java +++ b/src/main/java/com/hfstudio/guidenh/guide/compiler/Frontmatter.java @@ -9,6 +9,9 @@ import java.util.Locale; import java.util.Map; +import net.minecraft.nbt.JsonToNBT; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.ResourceLocation; import org.jetbrains.annotations.Nullable; @@ -80,54 +83,23 @@ public static Frontmatter parse(ResourceLocation pageId, String yamlText) { } var iconIdStr = getString(navigationMap, "icon"); var iconTextureStr = getString(navigationMap, "icon_texture"); - Map iconComponents = getCompound(navigationMap, "icon_components"); ResourceLocation parentId = null; if (parentIdStr != null) { parentId = IdUtils.resolveLink(parentIdStr, pageId); } - // Parse icon item id, supporting: - // modid:name - item with default damage 0 - // modid:name:meta - item with explicit damage value - // - strict form (angle brackets stripped) - // modid:name meta - space-separated damage (filter-expression style) int iconMeta = 0; String iconId = null; + NBTTagCompound iconNbt = null; if (iconIdStr != null) { - String s = iconIdStr.trim(); - if (s.startsWith("<") && s.endsWith(">")) { - s = s.substring(1, s.length() - 1) - .trim(); + var parsedIcon = parseIconEntryString(iconIdStr, pageId); + if (parsedIcon != null) { + iconId = parsedIcon.itemId(); + iconMeta = parsedIcon.meta(); + iconNbt = parsedIcon.nbt(); } - // Space-separated damage comes first (e.g. "minecraft:potion 16384") - int spaceIdx = s.indexOf(' '); - if (spaceIdx >= 0) { - String metaPart = s.substring(spaceIdx + 1) - .trim(); - s = s.substring(0, spaceIdx) - .trim(); - try { - iconMeta = Integer.parseInt(metaPart); - } catch (NumberFormatException ignored) {} - } else { - // Colon-separated damage: "modid:name:meta" - // ResourceLocation has exactly one colon; a second colon is the meta suffix. - int firstColon = s.indexOf(':'); - if (firstColon >= 0) { - int secondColon = s.indexOf(':', firstColon + 1); - if (secondColon >= 0) { - String metaPart = s.substring(secondColon + 1); - try { - iconMeta = Integer.parseInt(metaPart); - s = s.substring(0, secondColon); - } catch (NumberFormatException ignored) {} - } - } - } - iconId = IdUtils.rawRegistryKey(s, pageId.getResourceDomain()); } - ResourceLocation iconTextureId = null; if (iconTextureStr != null) { iconTextureId = IdUtils.resolveLink(iconTextureStr, pageId); @@ -136,9 +108,9 @@ public static Frontmatter parse(ResourceLocation pageId, String yamlText) { // Parse icons: list (cycling item icons, each entry uses same syntax as icon:) List iconEntries = null; Object iconsObj = navigationMap.get("icons"); - if (iconsObj instanceof List) { - iconEntries = new ArrayList<>(); - for (Object entry : (List) iconsObj) { + if (iconsObj instanceof ListiconsList) { + iconEntries = new ArrayList<>(iconsList.size()); + for (Object entry : iconsList) { NavigationIconEntry parsed = parseIconEntry(entry, pageId); if (parsed != null) { iconEntries.add(parsed); @@ -150,9 +122,9 @@ public static Frontmatter parse(ResourceLocation pageId, String yamlText) { // Parse icon_textures: list (cycling texture icons) List iconTextureEntries = null; Object iconTexturesObj = navigationMap.get("icon_textures"); - if (iconTexturesObj instanceof List) { - iconTextureEntries = new ArrayList<>(); - for (Object entry : (List) iconTexturesObj) { + if (iconTexturesObj instanceof ListiconTexturesList) { + iconTextureEntries = new ArrayList<>(iconTexturesList.size()); + for (Object entry : iconTexturesList) { if (entry instanceof String) { String texStr = ((String) entry).trim(); if (!texStr.isEmpty()) { @@ -169,13 +141,19 @@ public static Frontmatter parse(ResourceLocation pageId, String yamlText) { String requiredModSingle = getString(navigationMap, "required_mod"); Object requiredModsObj = navigationMap.get("required_mods"); if (requiredModSingle != null || requiredModsObj != null) { - requiredMods = new ArrayList<>(); + int expectedSize = requiredModSingle == null ? 0 : 1; + if (requiredModsObj instanceof ListrequiredModsList) { + expectedSize += requiredModsList.size(); + } else if (requiredModsObj instanceof String) { + expectedSize++; + } + requiredMods = new ArrayList<>(expectedSize); if (requiredModSingle != null && !requiredModSingle.trim() .isEmpty()) { requiredMods.add(requiredModSingle.trim()); } - if (requiredModsObj instanceof List) { - for (Object entry : (List) requiredModsObj) { + if (requiredModsObj instanceof ListrequiredModsList) { + for (Object entry : requiredModsList) { if (entry instanceof String) { String s = ((String) entry).trim(); if (!s.isEmpty()) { @@ -199,7 +177,7 @@ public static Frontmatter parse(ResourceLocation pageId, String yamlText) { recommend, iconId, iconMeta, - iconComponents, + iconNbt, iconTextureId, iconEntries, iconTextureEntries, @@ -251,6 +229,32 @@ public static int getInt(Map map, String key) { return mapValue; } + @Nullable + public static NBTTagCompound parseNavigationNbt(@Nullable Object value, String key) { + if (value == null) { + return null; + } + if (value instanceof MapmapValue) { + return YamlNbtConverter.toNbt(mapValue); + } + if (value instanceof String stringValue) { + String text = stringValue.trim(); + if (text.isEmpty()) { + return null; + } + try { + NBTBase parsed = JsonToNBT.func_150315_a(text); + if (parsed instanceof NBTTagCompound compound) { + return compound; + } + throw new IllegalArgumentException("Key " + key + " string value has to decode to a compound tag!"); + } catch (Exception exception) { + throw new IllegalArgumentException("Key " + key + " has invalid SNBT!", exception); + } + } + throw new IllegalArgumentException("Key " + key + " has to be a map or SNBT string!"); + } + /** * Parses {@code author}/{@code authors}, {@code date}, and {@code updated} from * {@link #additionalProperties()} into a {@link FrontmatterPageMeta} value object. @@ -310,7 +314,10 @@ private static NavigationIconEntry parseIconEntry(Object entry, ResourceLocation if (entryMap.containsKey("meta")) { meta = getInt(entryMap, "meta"); } - var nbt = getCompound(entryMap, "nbt"); + NBTTagCompound nbt = parsed.nbt(); + if (entryMap.containsKey("nbt")) { + nbt = parseNavigationNbt(entryMap.get("nbt"), "nbt"); + } return new NavigationIconEntry(parsed.itemId(), meta, nbt); } return null; @@ -324,30 +331,11 @@ private static NavigationIconEntry parseIconEntryString(String raw, ResourceLoca s = s.substring(1, s.length() - 1) .trim(); } - int meta = 0; - int spaceIdx = s.indexOf(' '); - if (spaceIdx >= 0) { - String metaPart = s.substring(spaceIdx + 1) - .trim(); - s = s.substring(0, spaceIdx) - .trim(); - try { - meta = Integer.parseInt(metaPart); - } catch (NumberFormatException ignored) {} - } else { - int firstColon = s.indexOf(':'); - if (firstColon >= 0) { - int secondColon = s.indexOf(':', firstColon + 1); - if (secondColon >= 0) { - String metaPart = s.substring(secondColon + 1); - try { - meta = Integer.parseInt(metaPart); - s = s.substring(0, secondColon); - } catch (NumberFormatException ignored) {} - } - } + var parsed = IdUtils.parseItemRef(s, pageId.getResourceDomain()); + if (parsed == null) { + return null; } - return new NavigationIconEntry(IdUtils.rawRegistryKey(s, pageId.getResourceDomain()), meta, null); + return new NavigationIconEntry(parsed.rawKey(), parsed.concreteMeta(), copyNbt(parsed.nbt())); } @Nullable @@ -368,4 +356,9 @@ private static String toDateString(@Nullable Object value) { } return value.toString(); } + + @Nullable + private static NBTTagCompound copyNbt(@Nullable NBTTagCompound nbt) { + return nbt == null ? null : (NBTTagCompound) nbt.copy(); + } } diff --git a/src/main/java/com/hfstudio/guidenh/guide/compiler/FrontmatterNavigation.java b/src/main/java/com/hfstudio/guidenh/guide/compiler/FrontmatterNavigation.java index de37d617..2dcff37e 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/compiler/FrontmatterNavigation.java +++ b/src/main/java/com/hfstudio/guidenh/guide/compiler/FrontmatterNavigation.java @@ -1,8 +1,8 @@ package com.hfstudio.guidenh.guide.compiler; import java.util.List; -import java.util.Map; +import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.ResourceLocation; import org.jetbrains.annotations.Nullable; @@ -24,7 +24,7 @@ public class FrontmatterNavigation { private final String iconItemId; private final int iconItemMeta; @Nullable - private final Map iconComponents; + private final NBTTagCompound iconNbt; @Nullable private final ResourceLocation iconTextureId; @Nullable @@ -36,7 +36,7 @@ public class FrontmatterNavigation { private final int loadPriority; public FrontmatterNavigation(String title, @Nullable ResourceLocation parent, int position, int recommend, - @Nullable String iconItemId, int iconItemMeta, @Nullable Map iconComponents, + @Nullable String iconItemId, int iconItemMeta, @Nullable NBTTagCompound iconNbt, @Nullable ResourceLocation iconTextureId, @Nullable List iconEntries, @Nullable List iconTextureEntries, @Nullable List requiredMods, int loadPriority) { this.title = title; @@ -45,7 +45,7 @@ public FrontmatterNavigation(String title, @Nullable ResourceLocation parent, in this.recommend = recommend; this.iconItemId = iconItemId; this.iconItemMeta = iconItemMeta; - this.iconComponents = iconComponents; + this.iconNbt = iconNbt; this.iconTextureId = iconTextureId; this.iconEntries = iconEntries; this.iconTextureEntries = iconTextureEntries; @@ -80,8 +80,8 @@ public int iconItemMeta() { } @Nullable - public Map iconComponents() { - return iconComponents; + public NBTTagCompound iconNbt() { + return iconNbt; } @Nullable diff --git a/src/main/java/com/hfstudio/guidenh/guide/compiler/NavigationIconEntry.java b/src/main/java/com/hfstudio/guidenh/guide/compiler/NavigationIconEntry.java index e1059641..a0005ad7 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/compiler/NavigationIconEntry.java +++ b/src/main/java/com/hfstudio/guidenh/guide/compiler/NavigationIconEntry.java @@ -1,6 +1,6 @@ package com.hfstudio.guidenh.guide.compiler; -import java.util.Map; +import net.minecraft.nbt.NBTTagCompound; import org.jetbrains.annotations.Nullable; @@ -11,4 +11,4 @@ * (mod ID casing preserved) used for item lookup. */ @Desugar -public record NavigationIconEntry(String itemId, int meta, @Nullable Map nbt) {} +public record NavigationIconEntry(String itemId, int meta, @Nullable NBTTagCompound nbt) {} diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/GuideLightweightReloadService.java b/src/main/java/com/hfstudio/guidenh/guide/internal/GuideLightweightReloadService.java index dd648e2e..33d0db5e 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/GuideLightweightReloadService.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/GuideLightweightReloadService.java @@ -127,8 +127,8 @@ public static void reloadGuides(IResourceManager resourceManager) { /** * Scans the guide folder tree and loads all markdown files under {@code assets///_/...}. */ - static Map loadPages(IResourceManager resourceManager, ResourceLocation guideId, - String folder, String defaultLanguage, @Nullable String currentLanguage) { + public static Map loadPages(IResourceManager resourceManager, + ResourceLocation guideId, String folder, String defaultLanguage, @Nullable String currentLanguage) { return loadPages( resourceManager, guideId, @@ -139,8 +139,8 @@ static Map loadPages(IResourceManager resourc DataDrivenGuideLoader.getActiveResourcePacks(resourceManager)); } - static Map loadPages(IResourceManager resourceManager, ResourceLocation guideId, - String folder, String defaultLanguage, @Nullable String currentLanguage, + public static Map loadPages(IResourceManager resourceManager, + ResourceLocation guideId, String folder, String defaultLanguage, @Nullable String currentLanguage, Map>> pagePathCache, Iterable activeResourcePacks) { long startedAt = System.nanoTime(); diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/editor/autocomplete/provider/FrontmatterValueProvider.java b/src/main/java/com/hfstudio/guidenh/guide/internal/editor/autocomplete/provider/FrontmatterValueProvider.java index 35d81f92..0cabe387 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/editor/autocomplete/provider/FrontmatterValueProvider.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/editor/autocomplete/provider/FrontmatterValueProvider.java @@ -15,10 +15,13 @@ public class FrontmatterValueProvider implements AutocompleteProvider { private static final Map HINTS = new LinkedHashMap<>(); + static { HINTS.put( "navigation", - new String[] { "\n title:", "\n parent:", "\n position:", "\n icon:", "\n icon_texture:" }); + new String[] { "\n title:", "\n parent:", "\n position:", + "\n icon: minecraft:book:0:{display:{Name:\"Custom Icon\"}}", + "\n icons:\n - minecraft:book:0:{display:{Name:\"Cycling Icon\"}}", "\n icon_texture:" }); // TODO: integrate with BetterQuesting for dynamic quest UUID lookup HINTS.put("quest_ids", new String[] { "\n - 00000000-0000-0000-0000-000000000000" }); } diff --git a/src/main/java/com/hfstudio/guidenh/guide/internal/util/NavigationUtil.java b/src/main/java/com/hfstudio/guidenh/guide/internal/util/NavigationUtil.java index d5c5380a..3df732ce 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/internal/util/NavigationUtil.java +++ b/src/main/java/com/hfstudio/guidenh/guide/internal/util/NavigationUtil.java @@ -14,7 +14,6 @@ import com.hfstudio.guidenh.guide.PageCollection; import com.hfstudio.guidenh.guide.compiler.NavigationIconEntry; import com.hfstudio.guidenh.guide.compiler.ParsedGuidePage; -import com.hfstudio.guidenh.guide.compiler.YamlNbtConverter; import com.hfstudio.guidenh.guide.render.GuidePageTexture; import com.hfstudio.guidenh.guide.scene.support.GuideDebugLog; @@ -32,7 +31,7 @@ public static GuidePageIcon createNavigationIcon(ParsedGuidePage page, @Nullable var iconTextureEntries = navigation.iconTextureEntries(); if (iconTextureEntries != null && !iconTextureEntries.isEmpty()) { - List cycleTextures = new ArrayList<>(); + List cycleTextures = new ArrayList<>(iconTextureEntries.size()); List cycleTextureIds = new ArrayList<>(iconTextureEntries); if (pages != null) { for (ResourceLocation texId : iconTextureEntries) { @@ -62,7 +61,7 @@ public static GuidePageIcon createNavigationIcon(ParsedGuidePage page, @Nullable var iconEntries = navigation.iconEntries(); if (iconEntries != null && !iconEntries.isEmpty()) { - List cycleItems = new ArrayList<>(); + List cycleItems = new ArrayList<>(iconEntries.size()); for (NavigationIconEntry entry : iconEntries) { var stack = resolveItemStack(page, entry.itemId(), entry.meta(), entry.nbt()); if (stack != null) { @@ -81,11 +80,7 @@ public static GuidePageIcon createNavigationIcon(ParsedGuidePage page, @Nullable return null; } - var stack = resolveItemStack( - page, - navigation.iconItemId(), - navigation.iconItemMeta(), - navigation.iconComponents()); + var stack = resolveItemStack(page, navigation.iconItemId(), navigation.iconItemMeta(), navigation.iconNbt()); if (stack == null) return null; return GuidePageIcon.item(stack); } @@ -104,7 +99,7 @@ public static GuidePageIcon createTextureIcon(ParsedGuidePage page, PageCollecti @Nullable private static ItemStack resolveItemStack(ParsedGuidePage page, String itemId, int meta, - @Nullable java.util.Map nbt) { + @Nullable NBTTagCompound nbt) { var item = (Item) Item.itemRegistry.getObject(itemId); if (item == null) { GuideDebugLog @@ -113,8 +108,7 @@ private static ItemStack resolveItemStack(ParsedGuidePage page, String itemId, i } var stack = new ItemStack(item, 1, meta); if (nbt != null) { - NBTTagCompound nbtTag = YamlNbtConverter.toNbt(nbt); - stack.setTagCompound(nbtTag); + stack.setTagCompound((NBTTagCompound) nbt.copy()); } return stack; } diff --git a/src/main/java/com/hfstudio/guidenh/guide/siteexport/PageJsonWriter.java b/src/main/java/com/hfstudio/guidenh/guide/siteexport/PageJsonWriter.java index ccfc2a9a..9b72efdf 100644 --- a/src/main/java/com/hfstudio/guidenh/guide/siteexport/PageJsonWriter.java +++ b/src/main/java/com/hfstudio/guidenh/guide/siteexport/PageJsonWriter.java @@ -32,10 +32,9 @@ * "title": "...", * "parent": "guidenh:category", * "position": 10, - * "iconItemId": "minecraft:chest", - * "iconItemMeta": 0, + * "icon": "minecraft:chest:0:{display:{Name:\"Custom Chest\"}}", * "iconTextureId":"guidenh:test1.png", - * "icons": [{"itemId":"minecraft:wool","meta":1},{"itemId":"minecraft:wool","meta":14}], + * "icons": ["minecraft:wool:1:{display:{Name:\"Orange\"}}","minecraft:wool:14"], * "iconTextures":["guidenh:tex1.png","guidenh:tex2.png"] * }, * "extra": { … additionalProperties … } @@ -91,8 +90,7 @@ public static Map frontmatterToJson(@Nullable Frontmatter fm) { : nav.parent() .toString()); n.put("position", nav.position()); - n.put("iconItemId", nav.iconItemId()); - n.put("iconItemMeta", nav.iconItemMeta()); + n.put("icon", formatIconValue(nav.iconItemId(), nav.iconItemMeta(), nav.iconNbt())); n.put( "iconTextureId", nav.iconTextureId() == null ? null @@ -101,12 +99,11 @@ public static Map frontmatterToJson(@Nullable Frontmatter fm) { if (nav.iconEntries() != null && !nav.iconEntries() .isEmpty()) { - List> iconsList = new ArrayList<>(); + List iconsList = new ArrayList<>( + nav.iconEntries() + .size()); for (NavigationIconEntry entry : nav.iconEntries()) { - Map e = new LinkedHashMap<>(); - e.put("itemId", entry.itemId()); - e.put("meta", entry.meta()); - iconsList.add(e); + iconsList.add(formatIconValue(entry.itemId(), entry.meta(), entry.nbt())); } n.put("icons", iconsList); } else { @@ -115,7 +112,9 @@ public static Map frontmatterToJson(@Nullable Frontmatter fm) { if (nav.iconTextureEntries() != null && !nav.iconTextureEntries() .isEmpty()) { - List texList = new ArrayList<>(); + List texList = new ArrayList<>( + nav.iconTextureEntries() + .size()); for (var texId : nav.iconTextureEntries()) { texList.add(texId.toString()); } @@ -129,4 +128,20 @@ public static Map frontmatterToJson(@Nullable Frontmatter fm) { out.put("extra", fm.additionalProperties() == null ? new LinkedHashMap<>() : fm.additionalProperties()); return out; } + + @Nullable + private static String formatIconValue(@Nullable String itemId, int meta, @Nullable Object nbt) { + if (itemId == null) { + return null; + } + StringBuilder value = new StringBuilder(itemId.length() + 16); + value.append(itemId) + .append(':') + .append(meta); + if (nbt != null) { + value.append(':') + .append(nbt); + } + return value.toString(); + } } diff --git a/wiki/Guide-Page-Format-zh-CN.md b/wiki/Guide-Page-Format-zh-CN.md index 210ebb9a..a6278480 100644 --- a/wiki/Guide-Page-Format-zh-CN.md +++ b/wiki/Guide-Page-Format-zh-CN.md @@ -288,11 +288,10 @@ GuideNH 会读取第一个 YAML frontmatter 块,并解析这些已知键: | `parent` | 否 | page id | 父页面 id;省略时为顶级节点 | | `position` | 否 | integer | 同级排序顺序,默认 `0`,数值越大越靠前 | | `priority` | 否 | integer | 同路径页面覆盖时的加载优先级;默认 `0`,数值更高者胜出,相同优先级时后处理的资源包条目覆盖先处理的 | -| `icon` | 否 | item id | 导航和搜索中显示的物品图标。支持 `modid:name`、`modid:name:meta`(冒号分隔的损伤值/子类型)、``(严格形式;meta `32767` 匹配所有子类型)以及 `modid:name meta`(空格分隔,过滤表达式风格)。 | -| `icons` | 否 | item id 列表 | 循环轮播的物品图标列表(每秒切换一次)。每项语法同 `icon`。存在时优先于 `icon` 使用。 | +| `icon` | 否 | item id | 导航和搜索中显示的物品图标。支持 `modid:name`、`modid:name:meta`、`modid:name:meta:{snbt}`。如需附加自定义名称等 NBT,推荐直接写在 `icon` 后面的内联 `:{snbt}` 尾部。 | +| `icons` | 否 | item id 列表 | 循环轮播的物品图标列表(每秒切换一次)。每一项语法同 `icon`,同样支持内联 `:{snbt}`。存在时优先于 `icon` 使用。 | | `icon_texture` | 否 | asset path | 纹理图标路径,按普通资源路径解析 | | `icon_textures` | 否 | asset path 列表 | 循环轮播的纹理图标列表(每秒切换一次)。存在时优先于 `icon_texture` 使用。 | -| `icon_components` | 否 | map | 以 YAML map 形式写入物品 NBT 数据(1.7.10 等价实现)。例如 `display.Name` 可自定义图标物品名称。仅作用于 `icon` 字段的单个物品,`icons` 列表不支持该字段。 | ### Frontmatter 示例 @@ -304,23 +303,16 @@ navigation: parent: index.md position: 10 priority: 0 - icon: minecraft:book - # 使用 meta/损伤値选择特定子类型: + icon: minecraft:book:0:{display:{Name:"我的自定义书"}} + # 使用 meta/损伤值选择特定子类型: # icon: minecraft:wool:1 (橙色羊毛,冒号写法) - # icon: (严格尖括号写法) - # icon: minecraft:wool 1 (空格写法,过滤表达式风格) # 循环图标列表——每秒切换一次: # icons: # - minecraft:wool:1 - # - minecraft:wool:4 + # - minecraft:wool:4:{display:{Name:"自定义绿色羊毛"}} # - minecraft:wool:14 - # 为图标物品附加 NBT(仅单 icon: 有效,icons: 不适用): - # icon_components: - # display: - # Name: "我的自定义书" - icon_texture: test1.png # 循环纹理列表: # icon_textures: diff --git a/wiki/Guide-Page-Format.md b/wiki/Guide-Page-Format.md index 5f97f7fa..d24e2938 100644 --- a/wiki/Guide-Page-Format.md +++ b/wiki/Guide-Page-Format.md @@ -299,11 +299,10 @@ GuideNH reads the first YAML frontmatter block and parses these known keys: | `parent` | no | page id | Parent page id; omitted means top-level node | | `position` | no | integer | Sibling sort order; default `0`, larger values appear earlier | | `priority` | no | integer | Load priority for same-path page overrides; default `0`, higher wins, equal priority lets the later resource pack entry win | -| `icon` | no | item id | Item icon shown in navigation/search. Accepts `modid:name`, `modid:name:meta` (colon-separated damage/subtype), `` (strict form; meta `32767` matches all subtypes), or `modid:name meta` (space-separated, filter-expression style). | -| `icons` | no | list of item ids | List of item icons for animated cycling (one per second). Each entry uses the same syntax as `icon`. When present takes priority over `icon`. | +| `icon` | no | item id | Item icon shown in navigation/search. Accepts `modid:name`, `modid:name:meta`, or `modid:name:meta:{snbt}`. The inline SNBT tail is the preferred way to attach NBT such as a custom display name. | +| `icons` | no | list of item ids | List of item icons for animated cycling (one per second). Each entry uses the same syntax as `icon`, including inline `:{snbt}` tails. When present takes priority over `icon`. | | `icon_texture` | no | asset path | Texture icon path resolved like any other asset link | | `icon_textures` | no | list of asset paths | List of texture icons for animated cycling (one per second). When present takes priority over `icon_texture`. | -| `icon_components` | no | map | YAML map applied as NBT data to the navigation icon item. Equivalent of 1.7.10 item NBT (e.g. `display.Name` for custom item names). Applied to the `icon` field item; ignored when `icons` list is used. | ### Example Frontmatter @@ -315,23 +314,16 @@ navigation: parent: index.md position: 10 priority: 0 - icon: minecraft:book + icon: minecraft:book:0:{display:{Name:"My Custom Book"}} # Use meta/damage to select a specific subtype: # icon: minecraft:wool:1 (orange wool, colon form) - # icon: (strict colon form) - # icon: minecraft:wool 1 (space form, filter-expression style) # Cycling icons list — cycles one per second: # icons: # - minecraft:wool:1 - # - minecraft:wool:4 + # - minecraft:wool:4:{display:{Name:"Custom Green Wool"}} # - minecraft:wool:14 - # Custom NBT on the icon item (only applies to single icon:, not icons:): - # icon_components: - # display: - # Name: "My Custom Book" - icon_texture: test1.png # Cycling textures: # icon_textures: diff --git a/wiki/Navigation-zh-CN.md b/wiki/Navigation-zh-CN.md index 763ba308..de1b0ff7 100644 --- a/wiki/Navigation-zh-CN.md +++ b/wiki/Navigation-zh-CN.md @@ -27,9 +27,10 @@ navigation: | `position` | 可选,同级排序提示 | | `recommend` | 可选,首页推荐优先级;缺省时不会出现在推荐面板中 | | `priority` | 可选,同路径页面覆盖时的加载优先级;默认 `0` | -| `icon` | 可选,物品图标 | +| `icon` | 可选,单个物品图标;支持内联 `mod:item:meta:{snbt}` 尾部 | +| `icons` | 可选,循环物品图标列表;每项可为带可选内联 `:{snbt}` 的普通物品 id,也可为 `{id, meta?, nbt?}` map | | `icon_texture` | 可选,从指南资源中解析的纹理图标 | -| `icon_components` | 会被解析,但当前内置渲染尚未使用 | +| `icon_textures` | 可选,循环纹理图标列表 | | `required_mod` | 可选,单个模组 id;该模组未加载时页面不可见 | | `required_mods` | 可选,模组 id 列表;列出的全部模组都加载时页面才可见 | @@ -108,9 +109,11 @@ navigation: GuideNH 会按以下顺序选择导航/搜索图标: -1. 若 `icon_texture` 能成功加载,则优先使用它 -2. 若 `icon` 对应的物品存在,则使用物品图标 -3. 两者都不可用时,不显示图标 +1. 如果配置了 `icon_textures`,优先使用循环纹理图标 +2. 否则若 `icon_texture` 能成功加载,则使用单张纹理图标 +3. 否则若 `icons` 中至少有一个物品可成功解析,则使用循环物品图标 +4. 否则若 `icon` 对应的物品存在,则使用单个物品图标 +5. 若都不可用,则不显示图标 纹理图标来自运行时资源,因此像 `test1.png` 这样的页面私有相对文件也能正常工作。 diff --git a/wiki/Navigation.md b/wiki/Navigation.md index 8a7ee0c9..c30c74b5 100644 --- a/wiki/Navigation.md +++ b/wiki/Navigation.md @@ -28,9 +28,10 @@ navigation: | `position` | Optional sibling ordering hint | | `recommend` | Optional home-page recommendation priority; absent means the page is not shown in the Recommended panel | | `priority` | Optional load priority for same-path page overrides; default `0` | -| `icon` | Optional item icon | +| `icon` | Optional single item icon; supports inline `mod:item:meta:{snbt}` tails | +| `icons` | Optional cycling item icons list; entries may be plain item ids with optional inline `:{snbt}` or `{id, meta?, nbt?}` maps | | `icon_texture` | Optional texture icon resolved from guide assets | -| `icon_components` | Parsed but not currently used by built-in rendering | +| `icon_textures` | Optional cycling texture icon list | | `required_mod` | Optional single mod id; page is hidden when this mod is not loaded | | `required_mods` | Optional list of mod ids; page is hidden unless all listed mods are loaded | @@ -110,9 +111,11 @@ resource-pack ordering. GuideNH chooses navigation/search icons in this order: -1. `icon_texture` if the texture file loads successfully -2. `icon` if the item exists -3. no icon if neither is usable +1. `icon_textures` if at least one texture entry is configured +2. `icon_texture` if the texture file loads successfully +3. `icons` if at least one configured item resolves successfully +4. `icon` if the item exists +5. no icon if neither is usable Texture icons are read from runtime assets, so relative page-local files such as `test1.png` work. diff --git a/wiki/resourcepack/assets/guidenh/guidenh/_en_us/images.md b/wiki/resourcepack/assets/guidenh/guidenh/_en_us/images.md index 08ba1d01..44b67a8d 100644 --- a/wiki/resourcepack/assets/guidenh/guidenh/_en_us/images.md +++ b/wiki/resourcepack/assets/guidenh/guidenh/_en_us/images.md @@ -11,10 +11,8 @@ categories: # - minecraft:wool:1 # - minecraft:wool:4 # - minecraft:wool:14 - # icon_components example (applies NBT to the icon: item): - # icon_components: - # display: - # Name: "Colored Wool Demo" + # Inline SNBT example on the icon item: + # icon: minecraft:wool:1:{display:{Name:"Colored Wool Demo"}} --- # Images diff --git a/wiki/resourcepack/assets/guidenh/guidenh/_en_us/navigation-guide.md b/wiki/resourcepack/assets/guidenh/guidenh/_en_us/navigation-guide.md index ab0c23d4..1a106f6a 100644 --- a/wiki/resourcepack/assets/guidenh/guidenh/_en_us/navigation-guide.md +++ b/wiki/resourcepack/assets/guidenh/guidenh/_en_us/navigation-guide.md @@ -109,7 +109,7 @@ An item ID shown next to the page title in the sidebar. ```yaml navigation: title: Recipes - icon: minecraft:crafting_table + icon: minecraft:crafting_table:0:{display:{Name:"Custom Crafting Table"}} ``` ### `navigation.icon_texture` @@ -131,7 +131,7 @@ navigation: title: Color Demo icons: - minecraft:wool:1 - - minecraft:wool:4 + - minecraft:wool:4:{display:{Name:"Custom Green Wool"}} - minecraft:wool:14 ``` diff --git a/wiki/resourcepack/assets/guidenh/guidenh/_zh_cn/images.md b/wiki/resourcepack/assets/guidenh/guidenh/_zh_cn/images.md index b30899ee..ae33979e 100644 --- a/wiki/resourcepack/assets/guidenh/guidenh/_zh_cn/images.md +++ b/wiki/resourcepack/assets/guidenh/guidenh/_zh_cn/images.md @@ -11,10 +11,8 @@ categories: # - minecraft:wool:1 # - minecraft:wool:4 # - minecraft:wool:14 - # icon_components 示例(为 icon: 物品附加 NBT): - # icon_components: - # display: - # Name: "彩色羊毛演示" + # icon 物品内联 SNBT 示例: + # icon: minecraft:wool:1:{display:{Name:"彩色羊毛演示"}} --- # 图片 diff --git a/wiki/resourcepack/assets/guidenh/guidenh/_zh_cn/navigation-guide.md b/wiki/resourcepack/assets/guidenh/guidenh/_zh_cn/navigation-guide.md index b41c0979..bde8f1f6 100644 --- a/wiki/resourcepack/assets/guidenh/guidenh/_zh_cn/navigation-guide.md +++ b/wiki/resourcepack/assets/guidenh/guidenh/_zh_cn/navigation-guide.md @@ -106,7 +106,7 @@ TXLoader `forceload` 中的文件会由 TXLoader 放到更靠后的资源包优 ```yaml navigation: title: 配方 - icon: minecraft:crafting_table + icon: minecraft:crafting_table:0:{display:{Name:"自定义工作台"}} ``` ### `navigation.icon_texture` @@ -128,7 +128,7 @@ navigation: title: 颜色演示 icons: - minecraft:wool:1 - - minecraft:wool:4 + - minecraft:wool:4:{display:{Name:"自定义绿色羊毛"}} - minecraft:wool:14 ```