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 be26cf47f..af26f29d3 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 @@ -33,7 +33,7 @@ import org.geysermc.geyser.session.GeyserSession; 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,9 +49,12 @@ 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(); - /*GameProfile profile = components.get(DataComponentTypes.PROFILE); + // 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.getName(); + String name = profile.getProfile().getName(); if (name != null) { // Add correct name of player skull String displayName = ChatColor.RESET + ChatColor.ESCAPE + rarity + @@ -62,6 +65,6 @@ public class PlayerHeadItem extends BlockItem { builder.setCustomName(ChatColor.RESET + ChatColor.ESCAPE + rarity + MinecraftLocale.getLocaleString("block.minecraft.player_head", session.locale())); } - }*/ // TODO 1.21.9 + } } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomSkullRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomSkullRegistryPopulator.java index ec7243396..170edec1e 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomSkullRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomSkullRegistryPopulator.java @@ -46,6 +46,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.function.Function; @@ -176,15 +177,14 @@ public class CustomSkullRegistryPopulator { */ private static @Nullable String getProfileFromUuid(String uuid) { try { - String uuidDigits = uuid.replace("-", ""); - if (uuidDigits.length() != 32) { - GeyserImpl.getInstance().getLogger().error("Invalid skull uuid " + uuid + " This skull will not be added as a custom block."); - return null; - } - return SkinProvider.requestTexturesFromUUID(uuid).get(); + UUID parsed = UUID.fromString(uuid); + return SkinProvider.requestTexturesFromUUID(parsed).get(); } catch (InterruptedException | ExecutionException e) { GeyserImpl.getInstance().getLogger().error("Unable to request skull textures for " + uuid + " This skull will not be added as a custom block.", e); return null; + } catch (IllegalArgumentException e) { + GeyserImpl.getInstance().getLogger().error("Invalid skull uuid " + uuid + " This skull will not be added as a custom block."); + return null; } } } diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 32c1610b3..ea291a2ee 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -213,6 +213,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.HandPreference; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerAction; +import org.geysermc.mcprotocollib.protocol.data.game.entity.player.ResolvableProfile; import org.geysermc.mcprotocollib.protocol.data.game.setting.ChatVisibility; import org.geysermc.mcprotocollib.protocol.data.game.setting.ParticleStatus; import org.geysermc.mcprotocollib.protocol.data.game.setting.SkinPart; @@ -398,7 +399,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { * A map of all players (and their heads) that are wearing a player head with a custom texture. * Our workaround for these players is to give them a custom skin and geometry to emulate wearing a custom skull. */ - private final Map playerWithCustomHeads = new Object2ObjectOpenHashMap<>(); + private final Map playerWithCustomHeads = new Object2ObjectOpenHashMap<>(); @Setter private boolean droppingLecternBook; diff --git a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java index 12f002025..2f143dd6e 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java @@ -43,17 +43,15 @@ import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.skin.SkinManager.GameProfileData; import org.geysermc.geyser.text.GeyserLocale; -import org.geysermc.mcprotocollib.auth.GameProfile; import org.geysermc.mcprotocollib.auth.GameProfile.Texture; import org.geysermc.mcprotocollib.auth.GameProfile.TextureModel; import org.geysermc.mcprotocollib.auth.GameProfile.TextureType; +import org.geysermc.mcprotocollib.protocol.data.game.entity.player.ResolvableProfile; import java.awt.*; import java.awt.image.BufferedImage; -import java.io.IOException; import java.util.Map; import java.util.Objects; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -104,70 +102,46 @@ public class FakeHeadProvider { } }); - public static void setHead(GeyserSession session, PlayerEntity entity, @Nullable GameProfile profile) { + public static void setHead(GeyserSession session, PlayerEntity entity, @Nullable ResolvableProfile profile) { if (profile == null) { return; } - GameProfile current = session.getPlayerWithCustomHeads().get(entity.getUuid()); + ResolvableProfile current = session.getPlayerWithCustomHeads().get(entity.getUuid()); if (profile.equals(current)) { // We already did this, no need to re-compute return; } - Map textures; - try { - textures = profile.getTextures(false); - } catch (IllegalStateException e) { - GeyserImpl.getInstance().getLogger().debug("Could not decode player head from profile %s, got: %s".formatted(profile, e.getMessage())); - textures = null; - } - - if (textures == null || textures.isEmpty()) { - loadHeadFromProfile(session, entity, profile); - return; - } - - Texture skinTexture = textures.get(TextureType.SKIN); - - if (skinTexture == null) { - return; - } - - Texture capeTexture = textures.get(TextureType.CAPE); - String capeUrl = capeTexture != null ? capeTexture.getURL() : null; - - boolean isAlex = skinTexture.getModel() == TextureModel.SLIM; - - loadHeadFromProfile(session, entity, new GameProfileData(skinTexture.getURL(), capeUrl, isAlex), profile); - } - - public static void loadHeadFromProfile(GeyserSession session, PlayerEntity entity, GameProfile profile) { - CompletableFuture texturesFuture; - if (profile.getId() != null) { - texturesFuture = SkinProvider.requestTexturesFromUUID(profile.getId().toString()); - } else { - texturesFuture = SkinProvider.requestTexturesFromUsername(profile.getName()); - } - - texturesFuture.whenCompleteAsync((encodedJson, throwable) -> { + SkinManager.resolveProfile(profile).whenCompleteAsync((resolved, throwable) -> { if (throwable != null) { GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), throwable); return; } + + Map textures; try { - SkinManager.GameProfileData gameProfileData = SkinManager.GameProfileData.loadFromJson(encodedJson); - if (gameProfileData == null) { - return; - } - loadHeadFromProfile(session, entity, gameProfileData, profile); - } catch (IOException e) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid(), e.getMessage())); + textures = resolved.getTextures(false); + } catch (IllegalStateException exception) { + // TODO translate? + GeyserImpl.getInstance().getLogger().error("Could not decode player head from profile %s, got: %s".formatted(profile, exception.getMessage())); + return; } + + Texture skinTexture = textures.get(TextureType.SKIN); + if (skinTexture == null) { + return; + } + + Texture capeTexture = textures.get(TextureType.CAPE); + String capeUrl = capeTexture != null ? capeTexture.getURL() : null; + + boolean isAlex = skinTexture.getModel() == TextureModel.SLIM; + loadHeadFromProfile(session, entity, new GameProfileData(skinTexture.getURL(), capeUrl, isAlex), profile); }); } - public static void loadHeadFromProfile(GeyserSession session, PlayerEntity entity, SkinManager.GameProfileData gameProfileData, GameProfile profile) { + private static void loadHeadFromProfile(GeyserSession session, PlayerEntity entity, SkinManager.GameProfileData gameProfileData, ResolvableProfile profile) { String fakeHeadSkinUrl = gameProfileData.skinUrl(); session.getPlayerWithCustomHeads().put(entity.getUuid(), profile); diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java index 2f506d10f..5138ea1c6 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java @@ -26,6 +26,8 @@ package org.geysermc.geyser.skin; import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtType; @@ -44,17 +46,27 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.auth.BedrockClientData; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; +import org.geysermc.mcprotocollib.auth.GameProfile; +import org.geysermc.mcprotocollib.protocol.data.game.entity.player.ResolvableProfile; import java.awt.*; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Base64; import java.util.List; import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; public class SkinManager { + private static final Cache RESOLVED_PROFILES_CACHE = CacheBuilder.newBuilder() + .expireAfterAccess(1, TimeUnit.HOURS) + .build(); + private static final UUID EMPTY_UUID = new UUID(0L, 0L); + static final String GEOMETRY = new String(FileUtils.readAllBytes("bedrock/geometries/geo.json"), StandardCharsets.UTF_8); /** @@ -190,6 +202,66 @@ public class SkinManager { .build(); } + public static CompletableFuture resolveProfile(ResolvableProfile profile) { + GameProfile partial = profile.getProfile(); + if (!profile.isDynamic()) { + // This is easy: the server has provided the entire profile for us (or however much it knew), + // and is asking us to use this + return CompletableFuture.completedFuture(partial); + } else if (!partial.getProperties().isEmpty() || (partial.getId() == null && partial.getName() == null)) { + // If properties have been provided to us, or no ID and no name have been provided, create a static profile from + // what we do know + // This replicates vanilla Java client behaviour + String name = partial.getName() == null ? "" : partial.getName(); + UUID uuid = partial.getName() == null ? EMPTY_UUID : createOfflinePlayerUUID(partial.getName()); + GameProfile completed = new GameProfile(uuid, name); + completed.setProperties(partial.getProperties()); + return CompletableFuture.completedFuture(completed); + } + + GameProfile cached = RESOLVED_PROFILES_CACHE.getIfPresent(profile); + if (cached != null) { + return CompletableFuture.completedFuture(cached); + } + + // The resolvable profile is dynamic - server wants the client (us) to retrieve the full GameProfile + // from Mojang's API + + // The partial profile *should* always have either a name or a UUID, not both + CompletableFuture completedProfileFuture; + if (partial.getName() != null) { + completedProfileFuture = SkinProvider.requestUUIDFromUsername(partial.getName()) + .thenApply(uuid -> new GameProfile(uuid, partial.getName())); + } else { + completedProfileFuture = SkinProvider.requestUsernameFromUUID(partial.getId()) + .thenApply(name -> new GameProfile(partial.getId(), name)); + } + + return completedProfileFuture + .thenCompose(nameAndUUID -> { + // Fallback to partial if anything goes wrong - should replicate vanilla Java client behaviour + if (nameAndUUID.getId() == null) { + return CompletableFuture.completedFuture(partial); + } + + return SkinProvider.requestTexturesFromUUID(nameAndUUID.getId()) + .thenApply(encoded -> { + if (encoded == null) { + return partial; + } + + List properties = new ArrayList<>(); + properties.add(new GameProfile.Property("textures", encoded)); + nameAndUUID.setProperties(properties); + return nameAndUUID; + }); + }) + .thenApply(resolved -> { + RESOLVED_PROFILES_CACHE.put(profile, resolved); + return resolved; + }); + } + public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSession session, Consumer skinAndCapeConsumer) { SkinProvider.requestSkinData(entity, session).whenCompleteAsync((skinData, throwable) -> { @@ -240,6 +312,10 @@ public class SkinManager { } } + public static UUID createOfflinePlayerUUID(String username) { + return UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8)); + } + public record GameProfileData(String skinUrl, String capeUrl, boolean isAlex) { /** * Generate the GameProfileData from the given CompoundTag representing a GameProfile diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index f3ad0be2f..88ade3547 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -476,16 +476,84 @@ public class SkinProvider { return data; } + public static @Nullable String shorthandUUID(@Nullable UUID uuid) { + if (uuid == null) { + return null; + } + + return uuid.toString().replace("-", ""); + } + + public static @Nullable UUID expandUUID(@Nullable String uuid) { + if (uuid == null) { + return null; + } + + long mostSignificant = Long.parseUnsignedLong(uuid.substring(0, 16), 16); + long leastSignificant = Long.parseUnsignedLong(uuid.substring(16), 16); + return new UUID(mostSignificant, leastSignificant); + } + + /** + * Request a player's username from their UUID + * + * @param uuid the player's UUID + * @return a completable username of the player + */ + public static CompletableFuture<@Nullable String> requestUsernameFromUUID(UUID uuid) { + return CompletableFuture.supplyAsync(() -> { + try { + JsonNode node = WebUtils.getJson("https://api.minecraftservices.com/minecraft/profile/lookup/" + shorthandUUID(uuid)); + JsonNode name = node.get("name"); + if (name == null) { + GeyserImpl.getInstance().getLogger().debug("No username found in Mojang response for " + uuid); + return null; + } + return name.asText(); + } catch (Exception e) { + if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + e.printStackTrace(); + } + return null; + } + }, getExecutorService()); + } + + /** + * Request a player's UUID from their username + * + * @param username the player's username + * @return a completable UUID of the player + */ + public static CompletableFuture<@Nullable UUID> requestUUIDFromUsername(String username) { + return CompletableFuture.supplyAsync(() -> { + try { + JsonNode node = WebUtils.getJson("https://api.mojang.com/users/profiles/minecraft/" + username); + JsonNode id = node.get("id"); + if (id == null) { + GeyserImpl.getInstance().getLogger().debug("No UUID found in Mojang response for " + username); + return null; + } + return expandUUID(id.asText()); + } catch (Exception e) { + if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + e.printStackTrace(); + } + return null; + } + }, getExecutorService()); + } + /** * Request textures from a player's UUID * - * @param uuid the player's UUID without any hyphens + * @param uuid the player's UUID * @return a completable GameProfile with textures included */ - public static CompletableFuture<@Nullable String> requestTexturesFromUUID(String uuid) { + public static CompletableFuture<@Nullable String> requestTexturesFromUUID(UUID uuid) { return CompletableFuture.supplyAsync(() -> { try { - JsonNode node = WebUtils.getJson("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid); + JsonNode node = WebUtils.getJson("https://sessionserver.mojang.com/session/minecraft/profile/" + shorthandUUID(uuid)); JsonNode properties = node.get("properties"); if (properties == null) { GeyserImpl.getInstance().getLogger().debug("No properties found in Mojang response for " + uuid); @@ -509,28 +577,13 @@ public class SkinProvider { * @return a completable GameProfile with textures included */ public static CompletableFuture<@Nullable String> requestTexturesFromUsername(String username) { - return CompletableFuture.supplyAsync(() -> { - try { - // Offline skin, or no present UUID - JsonNode node = WebUtils.getJson("https://api.mojang.com/users/profiles/minecraft/" + username); - JsonNode id = node.get("id"); - if (id == null) { - GeyserImpl.getInstance().getLogger().debug("No UUID found in Mojang response for " + username); - return null; + return requestUUIDFromUsername(username) + .thenCompose(uuid -> { + if (uuid == null) { + return CompletableFuture.completedFuture(null); } - return id.asText(); - } catch (Exception e) { - if (GeyserImpl.getInstance().getConfig().isDebugMode()) { - e.printStackTrace(); - } - return null; - } - }, getExecutorService()).thenCompose(uuid -> { - if (uuid == null) { - return CompletableFuture.completedFuture(null); - } - return requestTexturesFromUUID(uuid); - }); + return requestTexturesFromUUID(uuid); + }); } private static BufferedImage downloadImage(String imageUrl) throws IOException { diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java index 1b1bb1465..baf53487d 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java @@ -331,7 +331,7 @@ public abstract class InventoryTranslator { GeyserItemStack javaItem = inventory.getItem(sourceSlot); if (javaItem.asItem() == Items.PLAYER_HEAD && javaItem.hasNonBaseComponents()) { - //FakeHeadProvider.setHead(session, session.getPlayerEntity(), javaItem.getComponent(DataComponentTypes.PROFILE)); TODO 1.21.9 + FakeHeadProvider.setHead(session, session.getPlayerEntity(), javaItem.getComponent(DataComponentTypes.PROFILE)); } } else if (sourceSlot == 5) { // we are probably removing the head, so restore the original skin diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java index 5617b3c5e..d353e1fac 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java @@ -153,12 +153,11 @@ public class PlayerInventoryTranslator extends InventoryTranslator= 1 && slot <= 44) { 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 b8550636b..34d27da64 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,7 +38,6 @@ 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; @@ -59,12 +58,10 @@ 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.auth.GameProfile.Texture; -import org.geysermc.mcprotocollib.auth.GameProfile.TextureType; 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; +import org.geysermc.mcprotocollib.protocol.data.game.entity.player.ResolvableProfile; import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; import org.geysermc.mcprotocollib.protocol.data.game.item.component.AdventureModePredicate; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes; @@ -237,7 +234,7 @@ public final class ItemTranslator { } if (bedrockItem.getJavaItem().equals(Items.PLAYER_HEAD)) { - // translatePlayerHead(session, components.get(DataComponentTypes.PROFILE), builder); TODO 1.21.9 + translatePlayerHead(session, components.get(DataComponentTypes.PROFILE), builder); } translateCustomItem(components, builder, bedrockItem); @@ -623,11 +620,14 @@ public final class ItemTranslator { builder.blockDefinition(blockDefinition); } - private static @Nullable CustomSkull getCustomSkull(@Nullable GameProfile profile) { + private static @Nullable CustomSkull getCustomSkull(@Nullable ResolvableProfile profile) { if (profile == null) { 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; try { textures = profile.getTextures(false); @@ -649,9 +649,11 @@ public final class ItemTranslator { String skinHash = skinTexture.getURL().substring(skinTexture.getURL().lastIndexOf('/') + 1); return BlockRegistries.CUSTOM_SKULLS.get(skinHash); + */ + return null; } - private static void translatePlayerHead(GeyserSession session, GameProfile profile, ItemData.Builder builder) { + private static void translatePlayerHead(GeyserSession session, ResolvableProfile profile, ItemData.Builder builder) { CustomSkull customSkull = getCustomSkull(profile); if (customSkull != null) { CustomBlockData customBlockData = customSkull.getCustomBlockData(); diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java index 10d45658e..645510786 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java @@ -47,6 +47,7 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +// TODO 1.21.9 @BlockEntity(type = BlockEntityType.SKULL) public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { @Override @@ -81,8 +82,7 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements List properties = profile.getList("properties", NbtType.COMPOUND); if (properties.isEmpty()) { if (uuid != null && uuid.version() == 4) { - String uuidString = uuid.toString().replace("-", ""); - return SkinProvider.requestTexturesFromUUID(uuidString); + return SkinProvider.requestTexturesFromUUID(uuid); } else { String nameTag = profile.getString("name", null); if (nameTag != null) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEquipmentTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEquipmentTranslator.java index cc035f7ee..fe5742270 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEquipmentTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEquipmentTranslator.java @@ -34,8 +34,8 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.skin.FakeHeadProvider; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; -import org.geysermc.mcprotocollib.auth.GameProfile; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Equipment; +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.packet.ingame.clientbound.entity.ClientboundSetEquipmentPacket; @@ -61,12 +61,12 @@ public class JavaSetEquipmentTranslator extends PacketTranslator { - /*GameProfile profile = stack.getComponent(DataComponentTypes.PROFILE); + ResolvableProfile profile = stack.getComponent(DataComponentTypes.PROFILE); if (livingEntity instanceof PlayerEntity && stack.asItem() == Items.PLAYER_HEAD && profile != null) { FakeHeadProvider.setHead(session, (PlayerEntity) livingEntity, profile); } else { FakeHeadProvider.restoreOriginalSkin(session, livingEntity); - }*/ // TODO 1.21.9 + } livingEntity.setHelmet(stack); armorUpdated = true;