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:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<UUID, GameProfile> playerWithCustomHeads = new Object2ObjectOpenHashMap<>();
|
||||
private final Map<UUID, ResolvableProfile> playerWithCustomHeads = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
@Setter
|
||||
private boolean droppingLecternBook;
|
||||
|
||||
@@ -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,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) {
|
||||
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<TextureType, Texture> 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;
|
||||
SkinManager.resolveProfile(profile).whenCompleteAsync((resolved, throwable) -> {
|
||||
if (throwable != null) {
|
||||
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), throwable);
|
||||
return;
|
||||
}
|
||||
|
||||
if (textures == null || textures.isEmpty()) {
|
||||
loadHeadFromProfile(session, entity, profile);
|
||||
Map<TextureType, Texture> textures;
|
||||
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;
|
||||
}
|
||||
|
||||
Texture skinTexture = textures.get(TextureType.SKIN);
|
||||
|
||||
if (skinTexture == null) {
|
||||
return;
|
||||
}
|
||||
@@ -138,36 +137,11 @@ public class FakeHeadProvider {
|
||||
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<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();
|
||||
|
||||
session.getPlayerWithCustomHeads().put(entity.getUuid(), profile);
|
||||
|
||||
@@ -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<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);
|
||||
|
||||
/**
|
||||
@@ -190,6 +202,66 @@ public class SkinManager {
|
||||
.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,
|
||||
Consumer<SkinProvider.SkinAndCape> 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
|
||||
|
||||
@@ -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,23 +577,8 @@ 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 id.asText();
|
||||
} catch (Exception e) {
|
||||
if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}, getExecutorService()).thenCompose(uuid -> {
|
||||
return requestUUIDFromUsername(username)
|
||||
.thenCompose(uuid -> {
|
||||
if (uuid == null) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@@ -331,7 +331,7 @@ public abstract class InventoryTranslator<Type extends Inventory> {
|
||||
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
|
||||
|
||||
@@ -153,12 +153,11 @@ public class PlayerInventoryTranslator extends InventoryTranslator<PlayerInvento
|
||||
|
||||
if (slot == 5) {
|
||||
// Check for custom skull
|
||||
/*if (javaItem.asItem() == Items.PLAYER_HEAD
|
||||
&& javaItem.hasNonBaseComponents()) {
|
||||
if (javaItem.asItem() == Items.PLAYER_HEAD && javaItem.hasNonBaseComponents()) {
|
||||
FakeHeadProvider.setHead(session, session.getPlayerEntity(), javaItem.getComponent(DataComponentTypes.PROFILE));
|
||||
} else {
|
||||
FakeHeadProvider.restoreOriginalSkin(session, session.getPlayerEntity());
|
||||
}*/ // TODO
|
||||
}
|
||||
}
|
||||
|
||||
if (slot >= 1 && slot <= 44) {
|
||||
|
||||
@@ -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<TextureType, Texture> 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();
|
||||
|
||||
@@ -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<NbtMap> 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) {
|
||||
|
||||
@@ -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<ClientboundSetE
|
||||
GeyserItemStack stack = GeyserItemStack.from(equipment.getItem());
|
||||
switch (equipment.getSlot()) {
|
||||
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) {
|
||||
FakeHeadProvider.setHead(session, (PlayerEntity) livingEntity, profile);
|
||||
} else {
|
||||
FakeHeadProvider.restoreOriginalSkin(session, livingEntity);
|
||||
}*/ // TODO 1.21.9
|
||||
}
|
||||
|
||||
livingEntity.setHelmet(stack);
|
||||
armorUpdated = true;
|
||||
|
||||
Reference in New Issue
Block a user