Start on custom skulls

Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com>
This commit is contained in:
Joshua Castle 2024-04-23 03:09:42 -07:00 committed by Camotoy
parent 1bdbcab4e8
commit 3f499e3ec0
No known key found for this signature in database
GPG key ID: 7EEFB66FE798081F
7 changed files with 96 additions and 54 deletions

View file

@ -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<TextureType, Texture> 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<String> 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<String> 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) {

View file

@ -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

View file

@ -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());
}

View file

@ -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<TextureType, Texture> 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;
}

View file

@ -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);

View file

@ -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<ClientboundSetE
switch (equipment.getSlot()) {
case HELMET -> {
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;

View file

@ -425,6 +425,7 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
bedrockBlockEntities.add(blockEntityTranslator.getBlockEntityTag(session, type, x + chunkBlockX, y, z + chunkBlockZ, tag, blockState));
// Check for custom skulls
// TODO: The tag layout follows new format (profille, etc...)
if (session.getPreferencesCache().showCustomSkulls() && type == BlockEntityType.SKULL && tag != null && tag.contains("SkullOwner")) {
BlockDefinition blockDefinition = SkullBlockEntityTranslator.translateSkull(session, tag, Vector3i.from(x + chunkBlockX, y, z + chunkBlockZ), blockState);
if (blockDefinition != null) {