diff --git a/src/main/java/org/spongepowered/common/util/Constants.java b/src/main/java/org/spongepowered/common/util/Constants.java index 06129547c6d..eaa06be2048 100644 --- a/src/main/java/org/spongepowered/common/util/Constants.java +++ b/src/main/java/org/spongepowered/common/util/Constants.java @@ -518,6 +518,9 @@ public static final class Item { public static final String ITEM_STORED_ENCHANTMENTS_LIST = "StoredEnchantments"; public static final String ITEM_DISPLAY = "display"; public static final String ITEM_LORE = "Lore"; + public static final String ITEM_NAME = "Name"; + public static final String ITEM_ORIGINAL_LORE = "SpongeOriginalLore"; + public static final String ITEM_ORIGINAL_NAME = "SpongeOriginalName"; public static final String ITEM_ENCHANTMENT_ID = "id"; public static final String ITEM_ENCHANTMENT_LEVEL = "lvl"; public static final String ITEM_BREAKABLE_BLOCKS = "CanDestroy"; diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/network/FriendlyByteBufMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/network/FriendlyByteBufMixin.java index 78835227a29..5db505f81e4 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/network/FriendlyByteBufMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/network/FriendlyByteBufMixin.java @@ -24,17 +24,23 @@ */ package org.spongepowered.common.mixin.core.network; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.world.item.ItemStack; import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.api.util.locale.Locales; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.common.adventure.NativeComponentRenderer; import org.spongepowered.common.bridge.network.FriendlyByteBufBridge; import java.util.Locale; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; +import org.spongepowered.common.util.Constants; @Mixin(FriendlyByteBuf.class) public abstract class FriendlyByteBufMixin implements FriendlyByteBufBridge { @@ -43,7 +49,89 @@ public abstract class FriendlyByteBufMixin implements FriendlyByteBufBridge { @ModifyVariable(method = "writeComponent", at = @At("HEAD"), argsOnly = true) private Component localizeComponent(final Component input) { - return NativeComponentRenderer.apply(input, this.impl$locale == null ? Locales.EN_US : this.impl$locale); + return NativeComponentRenderer.apply(input, this.impl$locale == null ? Locales.DEFAULT : this.impl$locale); + } + + @Redirect(method = "writeItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;getTag()Lnet/minecraft/nbt/CompoundTag;")) + public CompoundTag renderItemComponents(final ItemStack stack) { + CompoundTag tag = stack.getTag(); + if (tag == null || !tag.contains(Constants.Item.ITEM_DISPLAY, 10)) { + return tag; + } + + final Locale locale = this.impl$locale == null ? Locales.DEFAULT : this.impl$locale; + CompoundTag display = tag.getCompound(Constants.Item.ITEM_DISPLAY); + boolean copy = true; + + if (display.contains(Constants.Item.ITEM_NAME, 8)) { + final String nameStr = display.getString(Constants.Item.ITEM_NAME); + final Component name = Component.Serializer.fromJson(nameStr); + final Component renderedName = NativeComponentRenderer.apply(name, locale); + + if (!renderedName.equals(name)) { + if (copy) { + tag = tag.copy(); + display = tag.getCompound(Constants.Item.ITEM_DISPLAY); + copy = false; + } + + display.putString(Constants.Item.ITEM_ORIGINAL_NAME, nameStr); + display.putString(Constants.Item.ITEM_NAME, Component.Serializer.toJson(renderedName)); + } + } + + if (display.contains(Constants.Item.ITEM_LORE, 9)) { + final ListTag lore = display.getList(Constants.Item.ITEM_LORE, 8); + + final Component[] renderedLines = new Component[lore.size()]; + boolean equal = true; + + for (int i = 0; i < renderedLines.length; i++) { + final String lineStr = lore.getString(i); + final Component line = Component.Serializer.fromJson(lineStr); + final Component renderedLine = NativeComponentRenderer.apply(line, locale); + + renderedLines[i] = renderedLine; + equal = equal && renderedLine.equals(line); + } + + if (!equal) { + if (copy) { + tag = tag.copy(); + display = tag.getCompound(Constants.Item.ITEM_DISPLAY); + copy = false; + } + + final ListTag newLore = new ListTag(); + for (Component renderedLine : renderedLines) { + newLore.add(StringTag.valueOf(Component.Serializer.toJson(renderedLine))); + } + + display.put(Constants.Item.ITEM_ORIGINAL_LORE, lore); + display.put(Constants.Item.ITEM_LORE, newLore); + } + } + return tag; + } + + @Redirect(method = "readItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;setTag(Lnet/minecraft/nbt/CompoundTag;)V")) + public void restoreItemComponents(final ItemStack stack, final CompoundTag tag) { + if (tag != null && tag.contains(Constants.Item.ITEM_DISPLAY, 10)) { + final CompoundTag display = tag.getCompound(Constants.Item.ITEM_DISPLAY); + + if (display.contains(Constants.Item.ITEM_ORIGINAL_NAME, 8)) { + final String name = display.getString(Constants.Item.ITEM_ORIGINAL_NAME); + display.remove(Constants.Item.ITEM_ORIGINAL_NAME); + display.putString(Constants.Item.ITEM_NAME, name); + } + + if (display.contains(Constants.Item.ITEM_ORIGINAL_LORE, 9)) { + final ListTag lore = display.getList(Constants.Item.ITEM_ORIGINAL_LORE, 8); + display.remove(Constants.Item.ITEM_ORIGINAL_LORE); + display.put(Constants.Item.ITEM_LORE, lore); + } + } + stack.setTag(tag); } @Override diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ServerPlayerMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ServerPlayerMixin.java index 8b91251360e..a763725f534 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ServerPlayerMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ServerPlayerMixin.java @@ -63,6 +63,7 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.ResultSlot; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.ChunkPos; @@ -171,11 +172,12 @@ public abstract class ServerPlayerMixin extends PlayerMixin implements SubjectBr @Shadow protected abstract void shadow$createEndPlatform(ServerLevel p_241206_1_, BlockPos blockPos); @Shadow protected abstract void shadow$triggerDimensionChangeTriggers(ServerLevel serverworld); @Shadow public abstract void shadow$doCloseContainer(); + @Shadow public abstract void shadow$refreshContainer(AbstractContainerMenu container); // @formatter:on private net.minecraft.network.chat.@Nullable Component impl$connectionMessage; - private Locale impl$language = Locales.EN_US; + private Locale impl$language = Locales.DEFAULT; private Scoreboard impl$scoreboard = Sponge.game().server().serverScoreboard().get(); @Nullable private Boolean impl$keepInventory = null; // Used to restore original item received in a packet after canceling an event @@ -265,17 +267,21 @@ public abstract class ServerPlayerMixin extends PlayerMixin implements SubjectBr @Override public void bridge$setLanguage(final Locale language) { + this.impl$language = language; + // Update locale on Channel, used for sending localized messages if (this.connection != null) { final Channel channel = ((ConnectionAccessor) this.connection.connection).accessor$channel(); channel.attr(SpongeAdventure.CHANNEL_LOCALE).set(language); + SpongeAdventure.forEachBossBar(bar -> { if (bar.getPlayers().contains(this)) { this.connection.send(new ClientboundBossEventPacket(ClientboundBossEventPacket.Operation.UPDATE_NAME, bar)); } }); + + this.shadow$refreshContainer(this.containerMenu); } - this.impl$language = language; } @Override diff --git a/testplugins/src/main/java/org/spongepowered/test/chat/ChatTest.java b/testplugins/src/main/java/org/spongepowered/test/chat/ChatTest.java index e696ff76f0e..4d2bbfa53e2 100644 --- a/testplugins/src/main/java/org/spongepowered/test/chat/ChatTest.java +++ b/testplugins/src/main/java/org/spongepowered/test/chat/ChatTest.java @@ -43,6 +43,7 @@ import org.spongepowered.api.adventure.SpongeComponents; import org.spongepowered.api.command.Command; import org.spongepowered.api.command.CommandResult; +import org.spongepowered.api.command.exception.CommandException; import org.spongepowered.api.command.parameter.CommandContext; import org.spongepowered.api.command.parameter.Parameter; import org.spongepowered.api.data.Keys; @@ -55,12 +56,15 @@ import org.spongepowered.api.event.lifecycle.RegisterCommandEvent; import org.spongepowered.api.event.message.PlayerChatEvent; import org.spongepowered.api.event.network.ServerSideConnectionEvent; +import org.spongepowered.api.item.ItemTypes; +import org.spongepowered.api.item.inventory.ItemStack; import org.spongepowered.api.util.locale.Locales; import org.spongepowered.plugin.PluginContainer; import org.spongepowered.plugin.builtin.jvm.Plugin; import org.spongepowered.test.LoadableModule; import java.util.Arrays; +import java.util.Collections; import java.util.Locale; import java.util.ResourceBundle; @@ -124,6 +128,20 @@ public void registerCommands(final RegisterCommandEvent e return CommandResult.success(); }).build(), "sendbook"); + event.register(this.container, Command.builder() + .permission("chattest.giveitem") + .executor(ctx -> { + final ServerPlayer player = ctx.cause().first(ServerPlayer.class) + .orElseThrow(() -> new CommandException(Component.text("You must be a player to use this command!"))); + + final ItemStack itemStack = ItemStack.builder().itemType(ItemTypes.PAPER) + .add(Keys.CUSTOM_NAME, Component.translatable("chattest.item.name")) + .add(Keys.LORE, Collections.singletonList(Component.translatable("chattest.item.lore"))).build(); + + player.inventory().offer(itemStack); + return CommandResult.success(); + }).build(), "giveitem"); + final Parameter.Value targetArg = Parameter.player().key("target").build(); final Parameter.Value messageArg = Parameter.jsonText().key("message").build(); diff --git a/testplugins/src/main/resources/org/spongepowered/test/chat/messages.properties b/testplugins/src/main/resources/org/spongepowered/test/chat/messages.properties index 1360d559865..54190c9254b 100644 --- a/testplugins/src/main/resources/org/spongepowered/test/chat/messages.properties +++ b/testplugins/src/main/resources/org/spongepowered/test/chat/messages.properties @@ -2,4 +2,6 @@ chattest.bars.info=Have you eaten your FLARD yet? chattest.response.chat=Message "{0}" sent by {1} chattest.book.1=First page of the book! chattest.book.2=Second page of the book! -chattest.response=A test response! \ No newline at end of file +chattest.response=A test response! +chattest.item.name=Item name +chattest.item.lore=Item lore \ No newline at end of file diff --git a/testplugins/src/main/resources/org/spongepowered/test/chat/messages_en_UD.properties b/testplugins/src/main/resources/org/spongepowered/test/chat/messages_en_UD.properties index c4dd0fe4834..8daf92cdcf1 100644 --- a/testplugins/src/main/resources/org/spongepowered/test/chat/messages_en_UD.properties +++ b/testplugins/src/main/resources/org/spongepowered/test/chat/messages_en_UD.properties @@ -3,3 +3,5 @@ chattest.response.chat={1} ʎq ʇuǝs „{0}„ ǝƃɐssǝꟽ chattest.book.1=¡ʞooq ǝɥʇ ⅎo ǝƃɐd ʇsɹᴉᖵ chattest.book.2=¡ʞooq ǝɥʇ ⅎo ǝƃɐd puoɔǝS chattest.response=a test but upside down (but not, because i''m lazy) +chattest.item.name=ǝɯɐu ɯǝʇI +chattest.item.lore=ǝɹoʅ ɯǝʇI