From 3f499e3ec05592510a9cdeca0e642478a435cf17 Mon Sep 17 00:00:00 2001 From: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> Date: Tue, 23 Apr 2024 03:09:42 -0700 Subject: [PATCH] Start on custom skulls Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> --- .../geyser/skin/FakeHeadProvider.java | 86 ++++++++++++------- .../inventory/InventoryTranslator.java | 2 +- .../inventory/PlayerInventoryTranslator.java | 4 +- .../translator/item/ItemTranslator.java | 36 +++++--- .../entity/SkullBlockEntityTranslator.java | 1 + .../entity/JavaSetEquipmentTranslator.java | 20 +++-- .../JavaLevelChunkWithLightTranslator.java | 1 + 7 files changed, 96 insertions(+), 54 deletions(-) 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 c7bd235b2..2d6345bfa 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java @@ -25,9 +25,13 @@ package org.geysermc.geyser.skin; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.builtin.StringTag; -import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.github.steveice10.mc.auth.data.GameProfile; +import com.github.steveice10.mc.auth.data.GameProfile.Texture; +import com.github.steveice10.mc.auth.data.GameProfile.TextureModel; +import com.github.steveice10.mc.auth.data.GameProfile.TextureType; +import com.github.steveice10.mc.auth.exception.property.PropertyException; +import com.github.steveice10.mc.protocol.data.game.item.component.DataComponentType; +import com.github.steveice10.mc.protocol.data.game.item.component.DataComponents; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; @@ -39,11 +43,13 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.entity.type.LivingEntity; 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 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; @@ -95,38 +101,60 @@ public class FakeHeadProvider { } }); - public static void setHead(GeyserSession session, PlayerEntity entity, Tag skullOwner) { - if (skullOwner == null) { + public static void setHead(GeyserSession session, PlayerEntity entity, DataComponents components) { + GameProfile profile = components.get(DataComponentType.PROFILE); + + if (profile == null) { return; } - if (skullOwner instanceof CompoundTag profileTag) { - SkinManager.GameProfileData gameProfileData = SkinManager.GameProfileData.from(profileTag); - if (gameProfileData == null) { + + Map textures = null; + try { + textures = profile.getTextures(false); + } catch (PropertyException e) { + session.getGeyser().getLogger().debug("Failed to get textures from GameProfile: " + e); + } + + if (textures == null || textures.isEmpty()) { + loadHead(session, entity, profile.getName()); + 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; + + loadHead(session, entity, new GameProfileData(skinTexture.getURL(), capeUrl, isAlex)); + } + + public static void loadHead(GeyserSession session, PlayerEntity entity, String owner) { + if (owner == null || owner.isEmpty()) { + return; + } + + CompletableFuture completableFuture = SkinProvider.requestTexturesFromUsername(owner); + completableFuture.whenCompleteAsync((encodedJson, throwable) -> { + if (throwable != null) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), throwable); return; } - loadHead(session, entity, gameProfileData); - } else if (skullOwner instanceof StringTag ownerTag) { - String owner = ownerTag.getValue(); - if (owner.isEmpty()) { - return; - } - CompletableFuture completableFuture = SkinProvider.requestTexturesFromUsername(owner); - completableFuture.whenCompleteAsync((encodedJson, throwable) -> { - if (throwable != null) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), throwable); + try { + SkinManager.GameProfileData gameProfileData = SkinManager.GameProfileData.loadFromJson(encodedJson); + if (gameProfileData == null) { return; } - try { - SkinManager.GameProfileData gameProfileData = SkinManager.GameProfileData.loadFromJson(encodedJson); - if (gameProfileData == null) { - return; - } - loadHead(session, entity, gameProfileData); - } catch (IOException e) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid(), e.getMessage())); - } - }); - } + loadHead(session, entity, gameProfileData); + } catch (IOException e) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid(), e.getMessage())); + } + }); } public static void loadHead(GeyserSession session, PlayerEntity entity, SkinManager.GameProfileData gameProfileData) { 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 2d7a1ae5a..4a3e31b30 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 @@ -225,7 +225,7 @@ public abstract class InventoryTranslator { GeyserItemStack javaItem = inventory.getItem(sourceSlot); if (javaItem.asItem() == Items.PLAYER_HEAD && javaItem.getNbt() != null) { - FakeHeadProvider.setHead(session, session.getPlayerEntity(), javaItem.getNbt().get("SkullOwner")); + FakeHeadProvider.setHead(session, session.getPlayerEntity(), javaItem.getComponents()); } } 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 b461c7afc..36d10aa54 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 @@ -100,7 +100,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator { if (i == 5 && item.asItem() == Items.PLAYER_HEAD && item.getNbt() != null) { - FakeHeadProvider.setHead(session, session.getPlayerEntity(), item.getNbt().get("SkullOwner")); + FakeHeadProvider.setHead(session, session.getPlayerEntity(), item.getComponents()); } } armorContentPacket.setContents(Arrays.asList(contents)); @@ -144,7 +144,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator { // Check for custom skull if (javaItem.asItem() == Items.PLAYER_HEAD && javaItem.getNbt() != null) { - FakeHeadProvider.setHead(session, session.getPlayerEntity(), javaItem.getNbt().get("SkullOwner")); + FakeHeadProvider.setHead(session, session.getPlayerEntity(), javaItem.getComponents()); } else { FakeHeadProvider.restoreOriginalSkin(session, session.getPlayerEntity()); } 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 c96542fc1..a8e9e1c52 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 @@ -26,6 +26,9 @@ package org.geysermc.geyser.translator.item; import com.github.steveice10.mc.auth.data.GameProfile; +import com.github.steveice10.mc.auth.data.GameProfile.Texture; +import com.github.steveice10.mc.auth.data.GameProfile.TextureType; +import com.github.steveice10.mc.auth.exception.property.PropertyException; import com.github.steveice10.mc.protocol.data.game.Identifier; import com.github.steveice10.mc.protocol.data.game.entity.attribute.ModifierOperation; import com.github.steveice10.mc.protocol.data.game.item.ItemStack; @@ -553,21 +556,28 @@ public final class ItemTranslator { if (components == null) { return null; } - //TODO + GameProfile profile = components.get(DataComponentType.PROFILE); if (profile != null) { -// if (!(nbt.get("SkullOwner") instanceof CompoundTag skullOwner)) { -// // It's a username give up d: -// return null; -// } -// SkinManager.GameProfileData data = SkinManager.GameProfileData.from(skullOwner); -// if (data == null) { -// session.getGeyser().getLogger().debug("Not sure how to handle skull head item display. " + nbt); -// return null; -// } -// -// String skinHash = data.skinUrl().substring(data.skinUrl().lastIndexOf('/') + 1); -// return BlockRegistries.CUSTOM_SKULLS.get(skinHash); + Map textures = null; + try { + textures = profile.getTextures(false); + } catch (PropertyException e) { + session.getGeyser().getLogger().debug("Failed to get textures from GameProfile: " + e); + } + + if (textures == null || textures.isEmpty()) { + return null; + } + + Texture skinTexture = textures.get(TextureType.SKIN); + + if (skinTexture == null) { + return null; + } + + String skinHash = skinTexture.getURL().substring(skinTexture.getURL().lastIndexOf('/') + 1); + return BlockRegistries.CUSTOM_SKULLS.get(skinHash); } return null; } 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 9ac9126db..e9aceece5 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 @@ -101,6 +101,7 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements } public static @Nullable BlockDefinition translateSkull(GeyserSession session, CompoundTag tag, Vector3i blockPosition, int blockState) { + // TODO: The tag layout follows new format (profille, etc...) CompoundTag owner = tag.get("SkullOwner"); if (owner == null) { session.getSkullCache().removeSkull(blockPosition); 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 6178a51e8..b0168a4ce 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 @@ -31,7 +31,10 @@ import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.Client import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.LivingEntity; +import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.item.Items; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.skin.FakeHeadProvider; import org.geysermc.geyser.translator.item.ItemTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -59,15 +62,14 @@ public class JavaSetEquipmentTranslator extends PacketTranslator { ItemStack javaItem = equipment.getItem(); - // TODO -// if (livingEntity instanceof PlayerEntity -// && javaItem != null -// && javaItem.getId() == Items.PLAYER_HEAD.javaId() -// && javaItem.getNbt() != null) { -// FakeHeadProvider.setHead(session, (PlayerEntity) livingEntity, javaItem.getNbt().get("SkullOwner")); -// } else { -// FakeHeadProvider.restoreOriginalSkin(session, livingEntity); -// } + if (livingEntity instanceof PlayerEntity + && javaItem != null + && javaItem.getId() == Items.PLAYER_HEAD.javaId() + && javaItem.getDataComponents() != null) { + FakeHeadProvider.setHead(session, (PlayerEntity) livingEntity, javaItem.getDataComponents()); + } else { + FakeHeadProvider.restoreOriginalSkin(session, livingEntity); + } livingEntity.setHelmet(item); armorUpdated = true; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java index e34c0d96d..fc1d0316c 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelChunkWithLightTranslator.java @@ -425,6 +425,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator