1
0
mirror of https://github.com/GeyserMC/Geyser.git synced 2025-12-19 14:59:27 +00:00

Work on proper ResolvableProfile resolving

This commit is contained in:
Eclipse
2025-09-23 18:57:56 +00:00
parent 0d4bc0f984
commit 1df98f961a
11 changed files with 208 additions and 100 deletions

View File

@@ -33,7 +33,7 @@ import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.text.MinecraftLocale;
import org.geysermc.geyser.translator.item.BedrockItemBuilder; 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.DataComponentTypes;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; 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 // Use the correct color, determined by the rarity of the item
char rarity = Rarity.fromId(components.getOrDefault(DataComponentTypes.RARITY, Rarity.COMMON.ordinal())).getColor(); 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) { if (profile != null) {
String name = profile.getName(); String name = profile.getProfile().getName();
if (name != null) { if (name != null) {
// Add correct name of player skull // Add correct name of player skull
String displayName = ChatColor.RESET + ChatColor.ESCAPE + rarity + String displayName = ChatColor.RESET + ChatColor.ESCAPE + rarity +
@@ -62,6 +65,6 @@ public class PlayerHeadItem extends BlockItem {
builder.setCustomName(ChatColor.RESET + ChatColor.ESCAPE + rarity + builder.setCustomName(ChatColor.RESET + ChatColor.ESCAPE + rarity +
MinecraftLocale.getLocaleString("block.minecraft.player_head", session.locale())); MinecraftLocale.getLocaleString("block.minecraft.player_head", session.locale()));
} }
}*/ // TODO 1.21.9 }
} }
} }

View File

@@ -46,6 +46,7 @@ import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.function.Function; import java.util.function.Function;
@@ -176,15 +177,14 @@ public class CustomSkullRegistryPopulator {
*/ */
private static @Nullable String getProfileFromUuid(String uuid) { private static @Nullable String getProfileFromUuid(String uuid) {
try { try {
String uuidDigits = uuid.replace("-", ""); UUID parsed = UUID.fromString(uuid);
if (uuidDigits.length() != 32) { return SkinProvider.requestTexturesFromUUID(parsed).get();
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();
} catch (InterruptedException | ExecutionException e) { } 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); GeyserImpl.getInstance().getLogger().error("Unable to request skull textures for " + uuid + " This skull will not be added as a custom block.", e);
return null; 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;
} }
} }
} }

View File

@@ -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.Hand;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.HandPreference; 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.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.ChatVisibility;
import org.geysermc.mcprotocollib.protocol.data.game.setting.ParticleStatus; import org.geysermc.mcprotocollib.protocol.data.game.setting.ParticleStatus;
import org.geysermc.mcprotocollib.protocol.data.game.setting.SkinPart; 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. * 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. * Our workaround for these players is to give them a custom skin and geometry to emulate wearing a custom skull.
*/ */
private final Map<UUID, GameProfile> playerWithCustomHeads = new Object2ObjectOpenHashMap<>(); private final Map<UUID, ResolvableProfile> playerWithCustomHeads = new Object2ObjectOpenHashMap<>();
@Setter @Setter
private boolean droppingLecternBook; private boolean droppingLecternBook;

View File

@@ -43,17 +43,15 @@ import org.geysermc.geyser.entity.type.player.PlayerEntity;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.skin.SkinManager.GameProfileData; import org.geysermc.geyser.skin.SkinManager.GameProfileData;
import org.geysermc.geyser.text.GeyserLocale; 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.Texture;
import org.geysermc.mcprotocollib.auth.GameProfile.TextureModel; import org.geysermc.mcprotocollib.auth.GameProfile.TextureModel;
import org.geysermc.mcprotocollib.auth.GameProfile.TextureType; import org.geysermc.mcprotocollib.auth.GameProfile.TextureType;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.ResolvableProfile;
import java.awt.*; import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -104,32 +102,33 @@ 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) { if (profile == null) {
return; return;
} }
GameProfile current = session.getPlayerWithCustomHeads().get(entity.getUuid()); ResolvableProfile current = session.getPlayerWithCustomHeads().get(entity.getUuid());
if (profile.equals(current)) { if (profile.equals(current)) {
// We already did this, no need to re-compute // We already did this, no need to re-compute
return; return;
} }
Map<TextureType, Texture> textures; SkinManager.resolveProfile(profile).whenCompleteAsync((resolved, throwable) -> {
try { if (throwable != null) {
textures = profile.getTextures(false); GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), throwable);
} catch (IllegalStateException e) { return;
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()) { Map<TextureType, Texture> textures;
loadHeadFromProfile(session, entity, profile); try {
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; return;
} }
Texture skinTexture = textures.get(TextureType.SKIN); Texture skinTexture = textures.get(TextureType.SKIN);
if (skinTexture == null) { if (skinTexture == null) {
return; return;
} }
@@ -138,36 +137,11 @@ public class FakeHeadProvider {
String capeUrl = capeTexture != null ? capeTexture.getURL() : null; String capeUrl = capeTexture != null ? capeTexture.getURL() : null;
boolean isAlex = skinTexture.getModel() == TextureModel.SLIM; boolean isAlex = skinTexture.getModel() == TextureModel.SLIM;
loadHeadFromProfile(session, entity, new GameProfileData(skinTexture.getURL(), capeUrl, isAlex), profile); loadHeadFromProfile(session, entity, new GameProfileData(skinTexture.getURL(), capeUrl, isAlex), profile);
}
public static void loadHeadFromProfile(GeyserSession session, PlayerEntity entity, GameProfile profile) {
CompletableFuture<String> texturesFuture;
if (profile.getId() != null) {
texturesFuture = SkinProvider.requestTexturesFromUUID(profile.getId().toString());
} else {
texturesFuture = SkinProvider.requestTexturesFromUsername(profile.getName());
}
texturesFuture.whenCompleteAsync((encodedJson, throwable) -> {
if (throwable != null) {
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), throwable);
return;
}
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()));
}
}); });
} }
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(); String fakeHeadSkinUrl = gameProfileData.skinUrl();
session.getPlayerWithCustomHeads().put(entity.getUuid(), profile); session.getPlayerWithCustomHeads().put(entity.getUuid(), profile);

View File

@@ -26,6 +26,8 @@
package org.geysermc.geyser.skin; package org.geysermc.geyser.skin;
import com.fasterxml.jackson.databind.JsonNode; 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.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtType; 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.session.auth.BedrockClientData;
import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils; 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.awt.*;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer; import java.util.function.Consumer;
public class SkinManager { public class SkinManager {
private static final Cache<ResolvableProfile, GameProfile> 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); static final String GEOMETRY = new String(FileUtils.readAllBytes("bedrock/geometries/geo.json"), StandardCharsets.UTF_8);
/** /**
@@ -190,6 +202,66 @@ public class SkinManager {
.build(); .build();
} }
public static CompletableFuture<GameProfile> 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<GameProfile> 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<GameProfile.Property> 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, public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSession session,
Consumer<SkinProvider.SkinAndCape> skinAndCapeConsumer) { Consumer<SkinProvider.SkinAndCape> skinAndCapeConsumer) {
SkinProvider.requestSkinData(entity, session).whenCompleteAsync((skinData, throwable) -> { 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) { public record GameProfileData(String skinUrl, String capeUrl, boolean isAlex) {
/** /**
* Generate the GameProfileData from the given CompoundTag representing a GameProfile * Generate the GameProfileData from the given CompoundTag representing a GameProfile

View File

@@ -476,16 +476,84 @@ public class SkinProvider {
return data; 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 * 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 * @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(() -> { return CompletableFuture.supplyAsync(() -> {
try { 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"); JsonNode properties = node.get("properties");
if (properties == null) { if (properties == null) {
GeyserImpl.getInstance().getLogger().debug("No properties found in Mojang response for " + uuid); GeyserImpl.getInstance().getLogger().debug("No properties found in Mojang response for " + uuid);
@@ -509,23 +577,8 @@ public class SkinProvider {
* @return a completable GameProfile with textures included * @return a completable GameProfile with textures included
*/ */
public static CompletableFuture<@Nullable String> requestTexturesFromUsername(String username) { public static CompletableFuture<@Nullable String> requestTexturesFromUsername(String username) {
return CompletableFuture.supplyAsync(() -> { return requestUUIDFromUsername(username)
try { .thenCompose(uuid -> {
// 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 id.asText();
} catch (Exception e) {
if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
e.printStackTrace();
}
return null;
}
}, getExecutorService()).thenCompose(uuid -> {
if (uuid == null) { if (uuid == null) {
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }

View File

@@ -331,7 +331,7 @@ public abstract class InventoryTranslator<Type extends Inventory> {
GeyserItemStack javaItem = inventory.getItem(sourceSlot); GeyserItemStack javaItem = inventory.getItem(sourceSlot);
if (javaItem.asItem() == Items.PLAYER_HEAD if (javaItem.asItem() == Items.PLAYER_HEAD
&& javaItem.hasNonBaseComponents()) { && 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) { } else if (sourceSlot == 5) {
// we are probably removing the head, so restore the original skin // we are probably removing the head, so restore the original skin

View File

@@ -153,12 +153,11 @@ public class PlayerInventoryTranslator extends InventoryTranslator<PlayerInvento
if (slot == 5) { if (slot == 5) {
// Check for custom skull // Check for custom skull
/*if (javaItem.asItem() == Items.PLAYER_HEAD if (javaItem.asItem() == Items.PLAYER_HEAD && javaItem.hasNonBaseComponents()) {
&& javaItem.hasNonBaseComponents()) {
FakeHeadProvider.setHead(session, session.getPlayerEntity(), javaItem.getComponent(DataComponentTypes.PROFILE)); FakeHeadProvider.setHead(session, session.getPlayerEntity(), javaItem.getComponent(DataComponentTypes.PROFILE));
} else { } else {
FakeHeadProvider.restoreOriginalSkin(session, session.getPlayerEntity()); FakeHeadProvider.restoreOriginalSkin(session, session.getPlayerEntity());
}*/ // TODO }
} }
if (slot >= 1 && slot <= 44) { if (slot >= 1 && slot <= 44) {

View File

@@ -38,7 +38,6 @@ import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; 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.api.block.custom.CustomBlockData;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.inventory.GeyserItemStack; 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.translator.text.MessageTranslator;
import org.geysermc.geyser.util.InventoryUtils; import org.geysermc.geyser.util.InventoryUtils;
import org.geysermc.geyser.util.MinecraftKey; 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.Effect;
import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType; 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.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.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.AdventureModePredicate; import org.geysermc.mcprotocollib.protocol.data.game.item.component.AdventureModePredicate;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes; 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)) { 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); translateCustomItem(components, builder, bedrockItem);
@@ -623,11 +620,14 @@ public final class ItemTranslator {
builder.blockDefinition(blockDefinition); builder.blockDefinition(blockDefinition);
} }
private static @Nullable CustomSkull getCustomSkull(@Nullable GameProfile profile) { private static @Nullable CustomSkull getCustomSkull(@Nullable ResolvableProfile profile) {
if (profile == null) { if (profile == null) {
return 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<TextureType, Texture> textures; Map<TextureType, Texture> textures;
try { try {
textures = profile.getTextures(false); textures = profile.getTextures(false);
@@ -649,9 +649,11 @@ public final class ItemTranslator {
String skinHash = skinTexture.getURL().substring(skinTexture.getURL().lastIndexOf('/') + 1); String skinHash = skinTexture.getURL().substring(skinTexture.getURL().lastIndexOf('/') + 1);
return BlockRegistries.CUSTOM_SKULLS.get(skinHash); 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); CustomSkull customSkull = getCustomSkull(profile);
if (customSkull != null) { if (customSkull != null) {
CustomBlockData customBlockData = customSkull.getCustomBlockData(); CustomBlockData customBlockData = customSkull.getCustomBlockData();

View File

@@ -47,6 +47,7 @@ import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
// TODO 1.21.9
@BlockEntity(type = BlockEntityType.SKULL) @BlockEntity(type = BlockEntityType.SKULL)
public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState {
@Override @Override
@@ -81,8 +82,7 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements
List<NbtMap> properties = profile.getList("properties", NbtType.COMPOUND); List<NbtMap> properties = profile.getList("properties", NbtType.COMPOUND);
if (properties.isEmpty()) { if (properties.isEmpty()) {
if (uuid != null && uuid.version() == 4) { if (uuid != null && uuid.version() == 4) {
String uuidString = uuid.toString().replace("-", ""); return SkinProvider.requestTexturesFromUUID(uuid);
return SkinProvider.requestTexturesFromUUID(uuidString);
} else { } else {
String nameTag = profile.getString("name", null); String nameTag = profile.getString("name", null);
if (nameTag != null) { if (nameTag != null) {

View File

@@ -34,8 +34,8 @@ import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.skin.FakeHeadProvider; import org.geysermc.geyser.skin.FakeHeadProvider;
import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator; 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.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.data.game.item.component.DataComponentTypes;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundSetEquipmentPacket; import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundSetEquipmentPacket;
@@ -61,12 +61,12 @@ public class JavaSetEquipmentTranslator extends PacketTranslator<ClientboundSetE
GeyserItemStack stack = GeyserItemStack.from(equipment.getItem()); GeyserItemStack stack = GeyserItemStack.from(equipment.getItem());
switch (equipment.getSlot()) { switch (equipment.getSlot()) {
case HELMET -> { case HELMET -> {
/*GameProfile profile = stack.getComponent(DataComponentTypes.PROFILE); ResolvableProfile profile = stack.getComponent(DataComponentTypes.PROFILE);
if (livingEntity instanceof PlayerEntity && stack.asItem() == Items.PLAYER_HEAD && profile != null) { if (livingEntity instanceof PlayerEntity && stack.asItem() == Items.PLAYER_HEAD && profile != null) {
FakeHeadProvider.setHead(session, (PlayerEntity) livingEntity, profile); FakeHeadProvider.setHead(session, (PlayerEntity) livingEntity, profile);
} else { } else {
FakeHeadProvider.restoreOriginalSkin(session, livingEntity); FakeHeadProvider.restoreOriginalSkin(session, livingEntity);
}*/ // TODO 1.21.9 }
livingEntity.setHelmet(stack); livingEntity.setHelmet(stack);
armorUpdated = true; armorUpdated = true;