diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index 4081c18dd..7e5a24495 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -31,7 +31,6 @@ import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.math.vector.Vector3i; -import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.protocol.bedrock.data.Ability; import org.cloudburstmc.protocol.bedrock.data.AbilityLayer; import org.cloudburstmc.protocol.bedrock.data.GameType; @@ -180,8 +179,8 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity { this.initializeMetadata(); // Explicitly reset all metadata not handled by initializeMetadata - setParrot(null, true); - setParrot(null, false); + setParrot(OptionalInt.empty(), true); + setParrot(OptionalInt.empty(), false); } public void sendPlayer() { diff --git a/core/src/main/java/org/geysermc/geyser/item/type/PlayerHeadItem.java b/core/src/main/java/org/geysermc/geyser/item/type/PlayerHeadItem.java index af26f29d3..5184e7ac6 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/PlayerHeadItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/PlayerHeadItem.java @@ -30,9 +30,11 @@ import org.geysermc.geyser.item.TooltipOptions; import org.geysermc.geyser.item.components.Rarity; import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.skin.SkinManager; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.translator.item.BedrockItemBuilder; +import org.geysermc.mcprotocollib.auth.GameProfile; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.ResolvableProfile; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; @@ -49,21 +51,25 @@ public class PlayerHeadItem extends BlockItem { // Use the correct color, determined by the rarity of the item char rarity = Rarity.fromId(components.getOrDefault(DataComponentTypes.RARITY, Rarity.COMMON.ordinal())).getColor(); - // Ideally we'd resolve the profile here and show the resolved name if it's a dynamic profile - // but, resolving is done async, which isn't really possible here - // TODO FIXME 1.21.9? also see comment in ItemTranslator ResolvableProfile profile = components.get(DataComponentTypes.PROFILE); if (profile != null) { - String name = profile.getProfile().getName(); - if (name != null) { - // Add correct name of player skull - String displayName = ChatColor.RESET + ChatColor.ESCAPE + rarity + + // Ideally we'd update the item once the profile is resolved, + // but there's no good way of doing this as we don't know where the item is in an inventory after we have translated it + // So, we request a resolve here, and if the profile has already been resolved it will be returned instantly from cache. + // If not, the next time the item will be translated the profile will probably have been resolved + GameProfile resolved = SkinManager.resolveProfile(profile).getNow(null); + if (resolved != null) { + String name = resolved.getName(); + if (name != null) { + // Add correct name of player skull + String displayName = ChatColor.RESET + ChatColor.ESCAPE + rarity + MinecraftLocale.getLocaleString("block.minecraft.player_head.named", session.locale()).replace("%s", name); - builder.setCustomName(displayName); - } else { - // No name found so default to "Player Head" - builder.setCustomName(ChatColor.RESET + ChatColor.ESCAPE + rarity + + builder.setCustomName(displayName); + } else { + // No name found so default to "Player Head" + builder.setCustomName(ChatColor.RESET + ChatColor.ESCAPE + rarity + MinecraftLocale.getLocaleString("block.minecraft.player_head", session.locale())); + } } } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java index 34d27da64..f5b6fb042 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java @@ -38,6 +38,7 @@ import org.cloudburstmc.nbt.NbtMapBuilder; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; +import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.block.custom.CustomBlockData; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.inventory.GeyserItemStack; @@ -53,11 +54,13 @@ import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.CustomSkull; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.skin.SkinManager; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.InventoryUtils; import org.geysermc.geyser.util.MinecraftKey; +import org.geysermc.mcprotocollib.auth.GameProfile; import org.geysermc.mcprotocollib.protocol.data.game.entity.Effect; import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType; import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.ModifierOperation; @@ -625,32 +628,32 @@ public final class ItemTranslator { return null; } - // TODO FIXME 1.21.9 - maybe if dynamic first send vanilla player head, then once resolved resend the proper player head?? - // TODO could also work with the head name (see PlayerHeadItem) - /* - Map textures; + // Ideally we'd update the item once the profile has been resolved, but this isn't really possible, + // also see comments in PlayerHeadItem for full explanation + GameProfile resolved = SkinManager.resolveProfile(profile).getNow(null); + if (resolved == null) { + return null; + } + + Map textures; try { - textures = profile.getTextures(false); + textures = resolved.getTextures(false); } catch (IllegalStateException e) { - GeyserImpl.getInstance().getLogger().debug("Could not decode player head from profile %s, got: %s".formatted(profile, e.getMessage())); + GeyserImpl.getInstance().getLogger().debug("Could not decode player head from profile %s, got: %s".formatted(resolved, e.getMessage())); return null; } if (textures == null || textures.isEmpty()) { - // TODO the java client looks up the texture properties here and updates the item return null; } - Texture skinTexture = textures.get(TextureType.SKIN); - + GameProfile.Texture skinTexture = textures.get(GameProfile.TextureType.SKIN); if (skinTexture == null) { return null; } String skinHash = skinTexture.getURL().substring(skinTexture.getURL().lastIndexOf('/') + 1); return BlockRegistries.CUSTOM_SKULLS.get(skinHash); - */ - return null; } private static void translatePlayerHead(GeyserSession session, ResolvableProfile profile, ItemData.Builder builder) {