From a4384b19b2347dc0235e7e8dc0010cc83dd5560f Mon Sep 17 00:00:00 2001 From: booky10 Date: Mon, 27 May 2024 03:47:31 +0200 Subject: [PATCH] Rewrite adventure item translation support --- ...translate-custom-item-names-and-lore.patch | 1 + ...tem-lore-lines-on-newline-characters.patch | 31 +-- .../0006-Add-more-detailed-brand-info.patch | 8 +- ...translate-custom-item-names-and-lore.patch | 252 ++++++++++++++++++ 4 files changed, 265 insertions(+), 27 deletions(-) create mode 100644 patches/server/0013-Add-option-to-translate-custom-item-names-and-lore.patch diff --git a/patches/removed/1.20.6/0006-Add-option-to-translate-custom-item-names-and-lore.patch b/patches/removed/1.20.6/0006-Add-option-to-translate-custom-item-names-and-lore.patch index 65dfd8f..15e6ddf 100644 --- a/patches/removed/1.20.6/0006-Add-option-to-translate-custom-item-names-and-lore.patch +++ b/patches/removed/1.20.6/0006-Add-option-to-translate-custom-item-names-and-lore.patch @@ -3,6 +3,7 @@ From: booky10 Date: Sun, 22 Aug 2021 20:30:16 +0200 Subject: [PATCH] Add option to translate custom item names and lore +This patch was completely rewritten with the 1.20.5/1.20.6 update diff --git a/src/main/java/dev/booky/cloudplane/CloudPlaneConfig.java b/src/main/java/dev/booky/cloudplane/CloudPlaneConfig.java index 26e393051d2c5fe84b8ae6a33b376efab6198816..b21f7a41581ddf36d61e206366c6398738a47813 100644 diff --git a/patches/server/0005-Split-item-lore-lines-on-newline-characters.patch b/patches/server/0005-Split-item-lore-lines-on-newline-characters.patch index d7573c8..d880dcb 100644 --- a/patches/server/0005-Split-item-lore-lines-on-newline-characters.patch +++ b/patches/server/0005-Split-item-lore-lines-on-newline-characters.patch @@ -6,26 +6,11 @@ Subject: [PATCH] Split item lore lines on newline characters The client doesn't respect newlines, so this has to be done at the network level -diff --git a/src/main/java/dev/booky/cloudplane/CloudPlaneConfig.java b/src/main/java/dev/booky/cloudplane/CloudPlaneConfig.java -index 26e393051d2c5fe84b8ae6a33b376efab6198816..4672e336eada51d1dc6b6b7415392a8598634c16 100644 ---- a/src/main/java/dev/booky/cloudplane/CloudPlaneConfig.java -+++ b/src/main/java/dev/booky/cloudplane/CloudPlaneConfig.java -@@ -107,4 +107,10 @@ public class CloudPlaneConfig { - private static Component getComponent(String path, Component def) { - return MiniMessage.miniMessage().deserialize(getString(path, MiniMessage.miniMessage().serialize(def))); - } -+ -+ private static void adventure() { -+ if (version <= 4) { -+ set("settings.settings.translate-items", null); -+ } -+ } - } -diff --git a/src/main/java/dev/booky/cloudplane/ComponentUtil.java b/src/main/java/dev/booky/cloudplane/ComponentUtil.java +diff --git a/src/main/java/dev/booky/cloudplane/ItemUtil.java b/src/main/java/dev/booky/cloudplane/ItemUtil.java new file mode 100644 -index 0000000000000000000000000000000000000000..11b8bdfc16620bbd8762d41c59642d552982c65b +index 0000000000000000000000000000000000000000..cc19d9e75233465cbf28f65aecfdf498d4339a63 --- /dev/null -+++ b/src/main/java/dev/booky/cloudplane/ComponentUtil.java ++++ b/src/main/java/dev/booky/cloudplane/ItemUtil.java @@ -0,0 +1,66 @@ +package dev.booky.cloudplane; + @@ -37,9 +22,9 @@ index 0000000000000000000000000000000000000000..11b8bdfc16620bbd8762d41c59642d55 +import java.util.Collections; +import java.util.List; + -+public final class ComponentUtil { ++public final class ItemUtil { + -+ private ComponentUtil() { ++ private ItemUtil() { + } + + private static List inlineComponent(Component component) { @@ -94,7 +79,7 @@ index 0000000000000000000000000000000000000000..11b8bdfc16620bbd8762d41c59642d55 + } +} diff --git a/src/main/java/net/minecraft/core/component/DataComponents.java b/src/main/java/net/minecraft/core/component/DataComponents.java -index 5632974af9c603d333ffc30a5a1b1e851821a3bb..dfc505afb0ef2ebb5673d61065d7d0eed5024a38 100644 +index 9b2a209cda955ef3e5d8ff3ed1b2249888c7d139..de0f853ca0090de9c4fb0f54a87d25ef783b533e 100644 --- a/src/main/java/net/minecraft/core/component/DataComponents.java +++ b/src/main/java/net/minecraft/core/component/DataComponents.java @@ -74,7 +74,7 @@ public class DataComponents { @@ -107,7 +92,7 @@ index 5632974af9c603d333ffc30a5a1b1e851821a3bb..dfc505afb0ef2ebb5673d61065d7d0ee public static final DataComponentType RARITY = register( "rarity", builder -> builder.persistent(Rarity.CODEC).networkSynchronized(Rarity.STREAM_CODEC) diff --git a/src/main/java/net/minecraft/world/item/component/ItemLore.java b/src/main/java/net/minecraft/world/item/component/ItemLore.java -index ce17a9256b7d7e649ee90769ab561917b20514f9..6ec394939ffb8c53f2ffb57d58e54336aae8f1b2 100644 +index ce17a9256b7d7e649ee90769ab561917b20514f9..e4bc72ffd982a06a71bfa1c20f4e66e13cf3234a 100644 --- a/src/main/java/net/minecraft/world/item/component/ItemLore.java +++ b/src/main/java/net/minecraft/world/item/component/ItemLore.java @@ -25,6 +25,25 @@ public record ItemLore(List lines, List styledLines) imple @@ -124,7 +109,7 @@ index ce17a9256b7d7e649ee90769ab561917b20514f9..6ec394939ffb8c53f2ffb57d58e54336 + List realLines = new java.util.ArrayList<>(lines.size() * 2); + for (Component line : lines) { + net.kyori.adventure.text.Component advLine = io.papermc.paper.adventure.PaperAdventure.asAdventure(line); -+ for (net.kyori.adventure.text.Component realLine : dev.booky.cloudplane.ComponentUtil.getLines(advLine)) { ++ for (net.kyori.adventure.text.Component realLine : dev.booky.cloudplane.ItemUtil.getLines(advLine)) { + realLines.add(io.papermc.paper.adventure.PaperAdventure.asVanilla(realLine)); + } + } diff --git a/patches/server/0006-Add-more-detailed-brand-info.patch b/patches/server/0006-Add-more-detailed-brand-info.patch index 7e8ccb6..77294e9 100644 --- a/patches/server/0006-Add-more-detailed-brand-info.patch +++ b/patches/server/0006-Add-more-detailed-brand-info.patch @@ -6,12 +6,12 @@ Subject: [PATCH] Add more detailed brand info Sends version info to player in brand packet diff --git a/src/main/java/dev/booky/cloudplane/CloudPlaneConfig.java b/src/main/java/dev/booky/cloudplane/CloudPlaneConfig.java -index 4672e336eada51d1dc6b6b7415392a8598634c16..7352b9094917b57cae64198570c8768d595a5123 100644 +index 26e393051d2c5fe84b8ae6a33b376efab6198816..b3402ff1789cf0e2ec661282a3ee7bad3f627041 100644 --- a/src/main/java/dev/booky/cloudplane/CloudPlaneConfig.java +++ b/src/main/java/dev/booky/cloudplane/CloudPlaneConfig.java -@@ -113,4 +113,14 @@ public class CloudPlaneConfig { - set("settings.settings.translate-items", null); - } +@@ -107,4 +107,14 @@ public class CloudPlaneConfig { + private static Component getComponent(String path, Component def) { + return MiniMessage.miniMessage().deserialize(getString(path, MiniMessage.miniMessage().serialize(def))); } + + public static boolean detailedBrand = true; diff --git a/patches/server/0013-Add-option-to-translate-custom-item-names-and-lore.patch b/patches/server/0013-Add-option-to-translate-custom-item-names-and-lore.patch new file mode 100644 index 0000000..c6c9931 --- /dev/null +++ b/patches/server/0013-Add-option-to-translate-custom-item-names-and-lore.patch @@ -0,0 +1,252 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: booky10 +Date: Mon, 27 May 2024 03:13:37 +0200 +Subject: [PATCH] Add option to translate custom item names and lore + + +diff --git a/src/main/java/dev/booky/cloudplane/CloudPlaneConfig.java b/src/main/java/dev/booky/cloudplane/CloudPlaneConfig.java +index b3402ff1789cf0e2ec661282a3ee7bad3f627041..ec8bdaa81a924e7d5eaf5c74b1c4cc2121fa3c8e 100644 +--- a/src/main/java/dev/booky/cloudplane/CloudPlaneConfig.java ++++ b/src/main/java/dev/booky/cloudplane/CloudPlaneConfig.java +@@ -117,4 +117,20 @@ public class CloudPlaneConfig { + } + detailedBrand = getBoolean("settings.detailed-brand-info", detailedBrand); + } ++ ++ public static boolean localizeItems = true; ++ private static void adventure() { ++ if (version <= 2) { ++ localizeItems = getBoolean("settings.localize.items", localizeItems); ++ set("settings.adventure.localize-items", localizeItems); ++ set("settings.localize", null); ++ } ++ if (version <= 3) { ++ localizeItems = getBoolean("settings.adventure.localize-items", localizeItems); ++ set("settings.translate-items", localizeItems); ++ set("settings.adventure", null); ++ } ++ ++ localizeItems = getBoolean("settings.translate-items", localizeItems); ++ } + } +diff --git a/src/main/java/dev/booky/cloudplane/ItemUtil.java b/src/main/java/dev/booky/cloudplane/ItemUtil.java +index cc19d9e75233465cbf28f65aecfdf498d4339a63..0117a2c5a714e9c9140380a51883b7fbc48bdf1e 100644 +--- a/src/main/java/dev/booky/cloudplane/ItemUtil.java ++++ b/src/main/java/dev/booky/cloudplane/ItemUtil.java +@@ -1,18 +1,99 @@ + package dev.booky.cloudplane; + ++import com.mojang.datafixers.util.Pair; ++import com.mojang.serialization.Codec; ++import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; ++import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; + import net.kyori.adventure.text.Component; + import net.kyori.adventure.text.TextComponent; ++import net.minecraft.core.component.DataComponentPatch; ++import net.minecraft.core.component.DataComponentType; ++import net.minecraft.core.component.DataComponents; ++import net.minecraft.core.registries.BuiltInRegistries; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.NbtOps; ++import net.minecraft.nbt.Tag; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.component.CustomData; + import org.apache.commons.lang3.StringUtils; + + import java.util.ArrayList; + import java.util.Collections; + import java.util.List; ++import java.util.Optional; ++import java.util.stream.Stream; ++ ++import static net.minecraft.core.component.DataComponents.CUSTOM_DATA; + + public final class ItemUtil { + ++ private static final List>> SAVEABLE_COMPONENT_TYPES = Stream.of( ++ DataComponents.ITEM_NAME, DataComponents.CUSTOM_NAME, DataComponents.LORE ++ ) ++ .>>map(type -> Pair.of(BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(type).toString(), type)) ++ .toList(); ++ private static final String SAVEABLE_COMPONENT_TAG_NAME = "CloudPlane$SavedData"; ++ + private ItemUtil() { + } + ++ @SuppressWarnings("unchecked") // not unchecked ++ public static void unpackPatchSaves(ItemStack stack) { ++ CustomData customData = stack.getOrDefault(CUSTOM_DATA, CustomData.EMPTY); ++ if (customData.isEmpty() || !customData.contains(SAVEABLE_COMPONENT_TAG_NAME)) { ++ return; // nothing to unpack ++ } ++ CompoundTag savedTag = customData.getUnsafe().getCompound(SAVEABLE_COMPONENT_TAG_NAME); ++ for (Pair> type : SAVEABLE_COMPONENT_TYPES) { ++ Tag encoded = savedTag.get(type.getFirst()); ++ if (encoded == null) { ++ continue; // nothing saved for this type, skip ++ } ++ Object decoded = type.getSecond().codecOrThrow() ++ .decode(NbtOps.INSTANCE, encoded) ++ .getOrThrow().getFirst(); ++ stack.set((DataComponentType) type.getSecond(), decoded); ++ } ++ // generally unsafe to do, but this probably won't cause any issues at this place ++ customData.getUnsafe().remove(SAVEABLE_COMPONENT_TAG_NAME); ++ } ++ ++ @SuppressWarnings("unchecked") // not unchecked ++ public static DataComponentPatch packPatchSaves(ItemStack stack) { ++ Reference2ObjectMap, Optional> patch = stack.components.patch; ++ if (patch == null || patch.isEmpty()) { ++ return DataComponentPatch.EMPTY; // no patches, skip complex logic ++ } ++ ++ CompoundTag savedTag = null; // lazy-loaded ++ for (Pair> type : SAVEABLE_COMPONENT_TYPES) { ++ Optional value = patch.get(type.getSecond()); ++ if (value == null || value.isEmpty()) { ++ continue; ++ } ++ if (savedTag == null) { ++ savedTag = new CompoundTag(); ++ } ++ Codec codec = ((DataComponentType) type.getSecond()).codecOrThrow(); ++ Tag encoded = codec.encodeStart(NbtOps.INSTANCE, value.get()).getOrThrow(); ++ savedTag.put(type.getFirst(), encoded); ++ } ++ if (savedTag == null) { // nothing found to be saved, return original ++ return new DataComponentPatch(patch); ++ } ++ ++ // add saved data to custom data component ++ CompoundTag tag = stack.getOrDefault(CUSTOM_DATA, CustomData.EMPTY).copyTag(); ++ tag.put(SAVEABLE_COMPONENT_TAG_NAME, savedTag); ++ ++ // copy patch map, prevents modification of original item stack ++ Reference2ObjectMap, Optional> newPatch = ++ new Reference2ObjectArrayMap<>(patch.size() + 1); ++ newPatch.putAll(patch); ++ newPatch.put(CUSTOM_DATA, Optional.of(CustomData.of(tag))); ++ return new DataComponentPatch(newPatch); ++ } ++ + private static List inlineComponent(Component component) { + component = component.compact(); + if (component.children().isEmpty()) { +diff --git a/src/main/java/net/minecraft/core/component/DataComponentPatch.java b/src/main/java/net/minecraft/core/component/DataComponentPatch.java +index b8977749d35dd7343021425f477445bec470d46b..c94e5ed9d61f1586a08ce1ec6ea032eb4d2603a4 100644 +--- a/src/main/java/net/minecraft/core/component/DataComponentPatch.java ++++ b/src/main/java/net/minecraft/core/component/DataComponentPatch.java +@@ -155,7 +155,7 @@ public final class DataComponentPatch { + private static final String REMOVED_PREFIX = "!"; + final Reference2ObjectMap, Optional> map; + +- DataComponentPatch(Reference2ObjectMap, Optional> changedComponents) { ++ public DataComponentPatch(Reference2ObjectMap, Optional> changedComponents) { // CloudPlane - package-private -> public + this.map = changedComponents; + } + +diff --git a/src/main/java/net/minecraft/core/component/PatchedDataComponentMap.java b/src/main/java/net/minecraft/core/component/PatchedDataComponentMap.java +index 22da75d8197de29a150c9eade7994deecae53a10..c026a6edf497c86be3b3f1b511bb285701173a88 100644 +--- a/src/main/java/net/minecraft/core/component/PatchedDataComponentMap.java ++++ b/src/main/java/net/minecraft/core/component/PatchedDataComponentMap.java +@@ -16,7 +16,7 @@ import javax.annotation.Nullable; + + public final class PatchedDataComponentMap implements DataComponentMap { + private final DataComponentMap prototype; +- private Reference2ObjectMap, Optional> patch; ++ public Reference2ObjectMap, Optional> patch; // CloudPlane - private -> public + private boolean copyOnWrite; + + public PatchedDataComponentMap(DataComponentMap baseComponents) { +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 58c38bc4361ddf24716f326b0c6fc626d434756e..24d035a40c675ce55893169c74101a9c392cac44 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -171,6 +171,11 @@ public final class ItemStack implements DataComponentHolder { + if (false && !datacomponentpatch.isEmpty()) { // Paper - This is no longer needed with raw NBT being handled in metadata + CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack)); + } ++ // CloudPlane start - item localization ++ if (dev.booky.cloudplane.CloudPlaneConfig.localizeItems) { ++ dev.booky.cloudplane.ItemUtil.unpackPatchSaves(itemstack); ++ } ++ // CloudPlane end - item localization + return itemstack; + // CraftBukkit end + } +@@ -186,15 +191,23 @@ public final class ItemStack implements DataComponentHolder { + // CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack)); // Paper - This is no longer with raw NBT being handled in metadata + // Spigot end + ITEM_STREAM_CODEC.encode(registryfriendlybytebuf, itemstack.getItemHolder()); // CraftBukkit - decompile error ++ // CloudPlane start - item localization ++ if (dev.booky.cloudplane.CloudPlaneConfig.localizeItems ++ && registryfriendlybytebuf.adventure$locale != null) { ++ DataComponentPatch.STREAM_CODEC.encode(registryfriendlybytebuf, ++ dev.booky.cloudplane.ItemUtil.packPatchSaves(itemstack)); ++ } else { ++ // CloudPlane end - item localization + // Paper start - adventure; conditionally render translatable components + boolean prev = net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.get(); + try { + net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.set(true); +- DataComponentPatch.STREAM_CODEC.encode(registryfriendlybytebuf, itemstack.components.asPatch()); ++ DataComponentPatch.STREAM_CODEC.encode(registryfriendlybytebuf, itemstack.components.asPatch()); // CloudPlane - item localization; diff on change + } finally { + net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.set(prev); + } + // Paper end - adventure; conditionally render translatable components ++ } // CloudPlane - item localization + } + } + }; +@@ -228,7 +241,7 @@ public final class ItemStack implements DataComponentHolder { + @Deprecated + @Nullable + private Item item; +- private PatchedDataComponentMap components; ++ public PatchedDataComponentMap components; // CloudPlane - private -> public + @Nullable + private Entity entityRepresentation; + +diff --git a/src/main/java/net/minecraft/world/item/component/ItemLore.java b/src/main/java/net/minecraft/world/item/component/ItemLore.java +index e4bc72ffd982a06a71bfa1c20f4e66e13cf3234a..cda383fb63889177a96e85f41151dce92a891692 100644 +--- a/src/main/java/net/minecraft/world/item/component/ItemLore.java ++++ b/src/main/java/net/minecraft/world/item/component/ItemLore.java +@@ -41,6 +41,36 @@ public record ItemLore(List lines, List styledLines) imple + } + return realLines; + }) ++ // CloudPlane start - item localization; line splitting support ++ .>apply(codec -> new StreamCodec<>() { ++ @Override ++ public List decode(RegistryFriendlyByteBuf buf) { ++ return codec.decode(buf); ++ } ++ ++ @Override ++ public void encode(RegistryFriendlyByteBuf buf, List value) { ++ if (dev.booky.cloudplane.CloudPlaneConfig.localizeItems && buf.adventure$locale != null) { ++ // pre-render translations here to work around line splitting not working anymore ++ boolean prevNoRender = ComponentSerialization.DONT_RENDER_TRANSLATABLES.get(); ++ try { ++ ComponentSerialization.DONT_RENDER_TRANSLATABLES.set(true); ++ List lines = Lists.newArrayListWithCapacity(value.size()); ++ for (Component line : value) { ++ net.kyori.adventure.text.Component advLine = io.papermc.paper.adventure.PaperAdventure.asAdventure(line); ++ net.kyori.adventure.text.Component i18nLine = net.kyori.adventure.translation.GlobalTranslator.render(advLine, buf.adventure$locale); ++ lines.add(io.papermc.paper.adventure.PaperAdventure.asVanilla(i18nLine)); ++ } ++ codec.encode(buf, lines); ++ } finally { ++ ComponentSerialization.DONT_RENDER_TRANSLATABLES.set(prevNoRender); ++ } ++ } else { ++ codec.encode(buf, value); ++ } ++ } ++ }) ++ // CloudPlane end - item localization; line splitting support + .map(ItemLore::new, ItemLore::lines); + // CloudPlane end - split lore lines +