From 453c1f5c1f5c79db2bbacec58350dd99fbb8dcae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=91=95=E1=96=87EE=E1=91=ADY=E1=91=95=E1=96=87EE?= =?UTF-8?q?=E1=91=ADE=E1=96=87?= <3404705272@qq.com> Date: Sat, 19 Jul 2025 00:28:55 +0800 Subject: [PATCH 1/4] feat: split lore components --- .../factory/ComponentItemFactory1_21_5.java | 6 +---- .../craftengine/core/item/ItemFactory.java | 3 +-- .../core/util/AdventureHelper.java | 25 +++++++++++++++++++ 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_5.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_5.java index af0981b60..af576f13d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_5.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_5.java @@ -90,11 +90,7 @@ public class ComponentItemFactory1_21_5 extends ComponentItemFactory1_21_4 { if (lore == null || lore.isEmpty()) { item.resetComponent(ComponentTypes.LORE); } else { - List loreTags = new ArrayList<>(); - for (Component component : lore) { - loreTags.add(AdventureHelper.componentToTag(component)); - } - item.setSparrowNBTComponent(ComponentTypes.LORE, new ListTag(loreTags)); + item.setSparrowNBTComponent(ComponentTypes.LORE, new ListTag(lore.stream().map(AdventureHelper::split).flatMap(List::stream).map(AdventureHelper::componentToTag).toList())); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemFactory.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemFactory.java index d403ba310..996db1af9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemFactory.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemFactory.java @@ -16,7 +16,6 @@ import net.momirealms.sparrow.nbt.Tag; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.stream.Collectors; public abstract class ItemFactory, I> { protected final CraftEngine plugin; @@ -112,7 +111,7 @@ public abstract class ItemFactory, I> { protected void loreComponent(W item, List component) { if (component != null && !component.isEmpty()) { - loreJson(item, component.stream().map(AdventureHelper::componentToJson).collect(Collectors.toList())); + loreJson(item, component.stream().map(AdventureHelper::split).flatMap(List::stream).map(AdventureHelper::componentToJson).toList()); } else { loreJson(item, null); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java b/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java index 3ed1f6048..92d81f1da 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java @@ -1,11 +1,17 @@ package net.momirealms.craftengine.core.util; import com.google.gson.JsonElement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.key.Key; import net.kyori.adventure.sound.Sound; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentIteratorType; import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.TextReplacementConfig; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; @@ -29,6 +35,7 @@ public class AdventureHelper { private final MiniMessage miniMessageCustom; private final GsonComponentSerializer gsonComponentSerializer; private final NBTComponentSerializer nbtComponentSerializer; + private static final TextReplacementConfig REPLACE_LF = TextReplacementConfig.builder().matchLiteral("\n").replacement(Component.newline()).build(); private AdventureHelper() { this.miniMessage = MiniMessage.builder().build(); @@ -209,6 +216,24 @@ public class AdventureHelper { return getNBT().deserialize(tag); } + public static List split(Component component) { + List result = new ArrayList<>(1); + Component line = Component.empty(); + for (Iterator it = component.replaceText(REPLACE_LF).iterator(ComponentIteratorType.DEPTH_FIRST); it.hasNext(); ) { + Component child = it.next().children(Collections.emptyList()); + if (Component.EQUALS.test(child, Component.newline())) { + result.add(line); + line = Component.empty(); + } else { + line = line.append(child); + } + } + if (Component.IS_NOT_EMPTY.test(line)) { + result.add(line); + } + return result; + } + /** * Checks if a character is a legacy color code. * From 17b531c56ca3b26bd22ce8823a25c91360ad7ae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=91=95=E1=96=87EE=E1=91=ADY=E1=91=95=E1=96=87EE?= =?UTF-8?q?=E1=91=ADE=E1=96=87?= <3404705272@qq.com> Date: Sat, 19 Jul 2025 22:32:37 +0800 Subject: [PATCH 2/4] feat: better split lore impl --- .../factory/ComponentItemFactory1_21_5.java | 6 ++- .../core/item/AbstractItemManager.java | 23 +++++++--- .../craftengine/core/item/ItemFactory.java | 3 +- .../item/modifier/DynamicLoreModifier.java | 13 +++--- .../core/item/modifier/LoreModification.java | 45 +++++++++++++++++++ .../core/item/modifier/LoreModifier.java | 28 ++++-------- .../core/util/AdventureHelper.java | 2 +- .../core/util/ResourceConfigUtils.java | 11 +++++ 8 files changed, 97 insertions(+), 34 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/item/modifier/LoreModification.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_5.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_5.java index af576f13d..af0981b60 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_5.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_5.java @@ -90,7 +90,11 @@ public class ComponentItemFactory1_21_5 extends ComponentItemFactory1_21_4 { if (lore == null || lore.isEmpty()) { item.resetComponent(ComponentTypes.LORE); } else { - item.setSparrowNBTComponent(ComponentTypes.LORE, new ListTag(lore.stream().map(AdventureHelper::split).flatMap(List::stream).map(AdventureHelper::componentToTag).toList())); + List loreTags = new ArrayList<>(); + for (Component component : lore) { + loreTags.add(AdventureHelper.componentToTag(component)); + } + item.setSparrowNBTComponent(ComponentTypes.LORE, new ListTag(loreTags)); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java index 6b75dc648..5aebb5f74 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java @@ -534,15 +534,26 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl return new CustomNameModifier<>(name); }, "custom-name", "item-name", "display-name"); } + Function> loreModificationListParser = (obj) -> { + if (obj instanceof List list) { + List modifications = new ArrayList<>(list.size()); + for (Object entry : list) { + if (entry instanceof Map map) { + modifications.add(new LoreModification(ResourceConfigUtils.getAsEnumOrDefault(map.get("action").toString().toUpperCase(Locale.ROOT), LoreModification.Action.class, LoreModification.Action.SET), ResourceConfigUtils.getAsInt(map.get("priority"), "priority"), ResourceConfigUtils.getAsBoolean(map.get("split-lines"), "split-lines"), MiscUtils.getAsStringList(map.get("content")))); + } else { + modifications.add(new LoreModification(List.of(entry.toString()))); + } + } + return modifications; + } + return Collections.emptyList(); + }; + registerDataType((obj) -> new LoreModifier<>(loreModificationListParser.apply(obj)), "lore", "display-lore", "description"); registerDataType((obj) -> { - List lore = MiscUtils.getAsStringList(obj); - return new LoreModifier<>(lore); - }, "lore", "display-lore", "description"); - registerDataType((obj) -> { - Map> dynamicLore = new LinkedHashMap<>(); + Map> dynamicLore = new LinkedHashMap<>(); if (obj instanceof Map map) { for (Map.Entry entry : map.entrySet()) { - dynamicLore.put(entry.getKey().toString(), MiscUtils.getAsStringList(entry.getValue())); + dynamicLore.put(entry.getKey().toString(), loreModificationListParser.apply(entry.getValue())); } } return new DynamicLoreModifier<>(dynamicLore); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemFactory.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemFactory.java index 996db1af9..bed14fa59 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemFactory.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemFactory.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.core.item; import com.google.gson.JsonElement; +import java.util.stream.Collectors; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.item.data.Enchantment; import net.momirealms.craftengine.core.item.data.FireworkExplosion; @@ -111,7 +112,7 @@ public abstract class ItemFactory, I> { protected void loreComponent(W item, List component) { if (component != null && !component.isEmpty()) { - loreJson(item, component.stream().map(AdventureHelper::split).flatMap(List::stream).map(AdventureHelper::componentToJson).toList()); + loreJson(item, component.stream().map(AdventureHelper::componentToJson).collect(Collectors.toList())); } else { loreJson(item, null); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/DynamicLoreModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/DynamicLoreModifier.java index 65392a1b8..fd7bd99d3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/DynamicLoreModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/DynamicLoreModifier.java @@ -1,10 +1,11 @@ package net.momirealms.craftengine.core.item.modifier; +import java.util.stream.Stream; +import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.item.ComponentKeys; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; import net.momirealms.craftengine.core.item.NetworkItemHandler; -import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.Tag; @@ -16,15 +17,15 @@ import java.util.Optional; public class DynamicLoreModifier implements ItemDataModifier { public static final String CONTEXT_TAG_KEY = "craftengine:display_context"; - private final Map> displayContexts; + private final Map> displayContexts; private final String defaultContext; - public DynamicLoreModifier(Map> displayContexts) { + public DynamicLoreModifier(Map> displayContexts) { this.defaultContext = displayContexts.keySet().iterator().next(); this.displayContexts = displayContexts; } - public Map> displayContexts() { + public Map> displayContexts() { return Collections.unmodifiableMap(this.displayContexts); } @@ -36,11 +37,11 @@ public class DynamicLoreModifier implements ItemDataModifier { @Override public Item apply(Item item, ItemBuildContext context) { String displayContext = Optional.ofNullable(item.getJavaTag(CONTEXT_TAG_KEY)).orElse(this.defaultContext).toString(); - List lore = this.displayContexts.get(displayContext); + List lore = this.displayContexts.get(displayContext); if (lore == null) { lore = this.displayContexts.get(this.defaultContext); } - item.loreComponent(lore.stream().map(it -> AdventureHelper.miniMessage().deserialize(it, context.tagResolvers())).toList()); + item.loreComponent(lore.stream().reduce(Stream.empty(), (stream, modification) -> modification.apply(stream, context), Stream::concat).toList()); return item; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/LoreModification.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/LoreModification.java new file mode 100644 index 000000000..45ecd76ae --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/LoreModification.java @@ -0,0 +1,45 @@ +package net.momirealms.craftengine.core.item.modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; +import net.kyori.adventure.text.Component; +import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.util.AdventureHelper; +public record LoreModification(Action action, int priority, boolean split, List content) implements Comparable { + public LoreModification(List content) { + this(Action.SET, 0, false, content); + } + public LoreModification(Action action, int priority, boolean split, List content) { + this.action = action; + this.priority = priority; + this.split = split; + if (Config.addNonItalicTag()) { + List processed = new ArrayList<>(content.size()); + for (String arg : content) { + processed.add(arg.startsWith("") ? arg : "" + arg); + } + this.content = processed; + } else { + this.content = content; + } + } + public Stream apply(Stream lore, ItemBuildContext context) { + return switch (action) { + case PREPEND -> Stream.concat(parseContent(context), lore); + case APPEND -> Stream.concat(lore, parseContent(context)); + default -> parseContent(context); + }; + } + private Stream parseContent(ItemBuildContext context) { + Stream parsed = content.stream().map(string -> AdventureHelper.miniMessage().deserialize(string, context.tagResolvers())); + return split ? parsed.map(AdventureHelper::splitLines).flatMap(List::stream) : parsed; + } + @Override + public int compareTo(LoreModification other) { + return Integer.compare(priority, other.priority); + } + public enum Action { + SET, PREPEND, APPEND + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/LoreModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/LoreModifier.java index 6c5c88a59..f76c1bb60 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/LoreModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/LoreModifier.java @@ -1,35 +1,25 @@ package net.momirealms.craftengine.core.item.modifier; +import java.util.ArrayList; +import java.util.stream.Stream; +import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.item.ComponentKeys; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; import net.momirealms.craftengine.core.item.NetworkItemHandler; -import net.momirealms.craftengine.core.plugin.config.Config; -import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.Tag; -import java.util.ArrayList; import java.util.List; public class LoreModifier implements ItemDataModifier { - private final List argument; + private final List argument; - public LoreModifier(List argument) { - if (Config.addNonItalicTag()) { - List processed = new ArrayList<>(argument.size()); - for (String arg : argument) { - if (arg.startsWith("")) { - processed.add(arg); - } else { - processed.add("" + arg); - } - } - this.argument = processed; - } else { - this.argument = argument; - } + public LoreModifier(List argument) { + argument = new ArrayList<>(argument); + argument.sort(null); + this.argument = List.copyOf(argument); } @Override @@ -39,7 +29,7 @@ public class LoreModifier implements ItemDataModifier { @Override public Item apply(Item item, ItemBuildContext context) { - item.loreComponent(this.argument.stream().map(it -> AdventureHelper.miniMessage().deserialize(it, context.tagResolvers())).toList()); + item.loreComponent(argument.stream().reduce(Stream.empty(), (stream, modification) -> modification.apply(stream, context), Stream::concat).toList()); return item; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java b/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java index 92d81f1da..f58769a92 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java @@ -216,7 +216,7 @@ public class AdventureHelper { return getNBT().deserialize(tag); } - public static List split(Component component) { + public static List splitLines(Component component) { List result = new ArrayList<>(1); Component line = Component.empty(); for (Iterator it = component.replaceText(REPLACE_LF).iterator(ComponentIteratorType.DEPTH_FIRST); it.hasNext(); ) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java index b6a40aa27..a9671f7de 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java @@ -20,6 +20,17 @@ public final class ResourceConfigUtils { return raw != null ? function.apply(raw) : defaultValue; } + public static > E getAsEnumOrDefault(Object o, Class clazz, E defaultValue) { + if (o == null) { + return defaultValue; + } + try { + return Enum.valueOf(clazz, o.toString()); + } catch (IllegalArgumentException e) { + return defaultValue; + } + } + public static T requireNonNullOrThrow(T obj, String node) { if (obj == null) throw new LocalizedResourceConfigException(node); From f68432b761487eb9ae923d2286d8eec130a18a90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=91=95=E1=96=87EE=E1=91=ADY=E1=91=95=E1=96=87EE?= =?UTF-8?q?=E1=91=ADE=E1=96=87?= <3404705272@qq.com> Date: Sat, 19 Jul 2025 22:44:12 +0800 Subject: [PATCH 3/4] refractor: import --- .../java/net/momirealms/craftengine/core/item/ItemFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemFactory.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemFactory.java index bed14fa59..d403ba310 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemFactory.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemFactory.java @@ -1,7 +1,6 @@ package net.momirealms.craftengine.core.item; import com.google.gson.JsonElement; -import java.util.stream.Collectors; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.item.data.Enchantment; import net.momirealms.craftengine.core.item.data.FireworkExplosion; @@ -17,6 +16,7 @@ import net.momirealms.sparrow.nbt.Tag; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; public abstract class ItemFactory, I> { protected final CraftEngine plugin; From 70925ee2c98af2acab9acf29ccc51f003bafb8ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=91=95=E1=96=87EE=E1=91=ADY=E1=91=95=E1=96=87EE?= =?UTF-8?q?=E1=91=ADE=E1=96=87?= <3404705272@qq.com> Date: Sun, 20 Jul 2025 00:07:32 +0800 Subject: [PATCH 4/4] fix: style loss --- .../core/util/AdventureHelper.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java b/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java index f58769a92..f3af38fc8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java @@ -36,6 +36,20 @@ public class AdventureHelper { private final GsonComponentSerializer gsonComponentSerializer; private final NBTComponentSerializer nbtComponentSerializer; private static final TextReplacementConfig REPLACE_LF = TextReplacementConfig.builder().matchLiteral("\n").replacement(Component.newline()).build(); + /** + * This iterator slices a component into individual parts that + *
    + *
  • Can be used individually without style loss
  • + *
  • Can be concatenated to form the original component, given that children are dropped
  • + *
+ * Any {@link net.kyori.adventure.text.ComponentIteratorFlag}s are ignored. + */ + private static final ComponentIteratorType SLICER = (component, deque, flags) -> { + final List children = component.children(); + for (int i = children.size() - 1; i >= 0; i--) { + deque.addFirst(children.get(i).applyFallbackStyle(component.style())); + } + }; private AdventureHelper() { this.miniMessage = MiniMessage.builder().build(); @@ -219,17 +233,17 @@ public class AdventureHelper { public static List splitLines(Component component) { List result = new ArrayList<>(1); Component line = Component.empty(); - for (Iterator it = component.replaceText(REPLACE_LF).iterator(ComponentIteratorType.DEPTH_FIRST); it.hasNext(); ) { + for (Iterator it = component.replaceText(REPLACE_LF).iterator(SLICER); it.hasNext(); ) { Component child = it.next().children(Collections.emptyList()); - if (Component.EQUALS.test(child, Component.newline())) { - result.add(line); + if (child instanceof TextComponent text && text.content().equals(Component.newline().content())) { + result.add(line.compact()); line = Component.empty(); } else { line = line.append(child); } } if (Component.IS_NOT_EMPTY.test(line)) { - result.add(line); + result.add(line.compact()); } return result; }