3
0
Mirror von https://github.com/GeyserMC/Geyser.git synchronisiert 2024-12-26 00:00:41 +01:00

make heads render when equipped

Dieser Commit ist enthalten in:
onebeastchris 2023-05-17 01:38:49 +02:00
Ursprung fbd157ccdf
Commit 231095e115
6 geänderte Dateien mit 97 neuen und 23 gelöschten Zeilen

Datei anzeigen

@ -26,6 +26,8 @@
package org.geysermc.geyser.skin; package org.geysermc.geyser.skin;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag; 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.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache; import com.google.common.cache.LoadingCache;
@ -41,7 +43,9 @@ import org.geysermc.geyser.text.GeyserLocale;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.awt.*; import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.IOException;
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;
@ -91,21 +95,48 @@ public class FakeHeadProvider {
} }
}); });
public static void setHead(GeyserSession session, PlayerEntity entity, CompoundTag profileTag) { public static void setHead(GeyserSession session, PlayerEntity entity, Tag skullOwner) {
SkinManager.GameProfileData gameProfileData = SkinManager.GameProfileData.from(profileTag); if (skullOwner == null) {
if (gameProfileData == null) {
return; return;
} }
if (skullOwner instanceof CompoundTag profileTag) {
SkinManager.GameProfileData gameProfileData = SkinManager.GameProfileData.from(profileTag);
if (gameProfileData == null) {
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);
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()));
}
});
}
}
public static void loadHead(GeyserSession session, PlayerEntity entity, SkinManager.GameProfileData gameProfileData) {
String fakeHeadSkinUrl = gameProfileData.skinUrl(); String fakeHeadSkinUrl = gameProfileData.skinUrl();
session.getPlayerWithCustomHeads().add(entity.getUuid()); session.getPlayerWithCustomHeads().add(entity.getUuid());
String texturesProperty = entity.getTexturesProperty(); String texturesProperty = entity.getTexturesProperty();
SkinProvider.EXECUTOR_SERVICE.execute(() -> { SkinProvider.EXECUTOR_SERVICE.execute(() -> {
try { try {
SkinProvider.SkinData mergedSkinData = MERGED_SKINS_LOADING_CACHE.get(new FakeHeadEntry(texturesProperty, fakeHeadSkinUrl, entity)); SkinProvider.SkinData mergedSkinData = MERGED_SKINS_LOADING_CACHE.get(new FakeHeadEntry(texturesProperty, fakeHeadSkinUrl, entity));
SkinManager.sendSkinPacket(session, entity, mergedSkinData); SkinManager.sendSkinPacket(session, entity, mergedSkinData);
} catch (ExecutionException e) { } catch (ExecutionException e) {
GeyserImpl.getInstance().getLogger().error("Couldn't merge skin of " + entity.getUsername() + " with head skin url " + fakeHeadSkinUrl, e); GeyserImpl.getInstance().getLogger().error("Couldn't merge skin of " + entity.getUsername() + " with head skin url " + fakeHeadSkinUrl, e);

Datei anzeigen

@ -277,7 +277,7 @@ public class SkinManager {
return null; return null;
} }
private static GameProfileData loadFromJson(String encodedJson) throws IOException, IllegalArgumentException { static GameProfileData loadFromJson(String encodedJson) throws IOException, IllegalArgumentException {
JsonNode skinObject = GeyserImpl.JSON_MAPPER.readTree(new String(Base64.getDecoder().decode(encodedJson), StandardCharsets.UTF_8)); JsonNode skinObject = GeyserImpl.JSON_MAPPER.readTree(new String(Base64.getDecoder().decode(encodedJson), StandardCharsets.UTF_8));
JsonNode textures = skinObject.get("textures"); JsonNode textures = skinObject.get("textures");

Datei anzeigen

@ -49,7 +49,9 @@ import org.geysermc.geyser.inventory.click.ClickPlan;
import org.geysermc.geyser.inventory.recipe.GeyserRecipe; import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe; import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe; import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.skin.FakeHeadProvider;
import org.geysermc.geyser.translator.inventory.chest.DoubleChestInventoryTranslator; import org.geysermc.geyser.translator.inventory.chest.DoubleChestInventoryTranslator;
import org.geysermc.geyser.translator.inventory.chest.SingleChestInventoryTranslator; import org.geysermc.geyser.translator.inventory.chest.SingleChestInventoryTranslator;
import org.geysermc.geyser.translator.inventory.furnace.BlastFurnaceInventoryTranslator; import org.geysermc.geyser.translator.inventory.furnace.BlastFurnaceInventoryTranslator;
@ -216,6 +218,20 @@ public abstract class InventoryTranslator {
boolean isSourceCursor = isCursor(transferAction.getSource()); boolean isSourceCursor = isCursor(transferAction.getSource());
boolean isDestCursor = isCursor(transferAction.getDestination()); boolean isDestCursor = isCursor(transferAction.getDestination());
if ((this) instanceof PlayerInventoryTranslator) {
if (destSlot == 5) {
//only set the head if the destination is the head slot
GeyserItemStack javaItem = inventory.getItem(sourceSlot);
if (javaItem.asItem() == Items.PLAYER_HEAD
&& javaItem.getNbt() != null) {
FakeHeadProvider.setHead(session, session.getPlayerEntity(), javaItem.getNbt().get("SkullOwner"));
}
} else if (sourceSlot == 5) {
//we are probably removing the head, so restore the original skin
FakeHeadProvider.restoreOriginalSkin(session, session.getPlayerEntity());
}
}
if (shouldRejectItemPlace(session, inventory, transferAction.getSource().getContainer(), if (shouldRejectItemPlace(session, inventory, transferAction.getSource().getContainer(),
isSourceCursor ? -1 : sourceSlot, isSourceCursor ? -1 : sourceSlot,
transferAction.getDestination().getContainer(), isDestCursor ? -1 : destSlot)) { transferAction.getDestination().getContainer(), isDestCursor ? -1 : destSlot)) {

Datei anzeigen

@ -29,7 +29,6 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType; import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSetCreativeModeSlotPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSetCreativeModeSlotPacket;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import it.unimi.dsi.fastutil.ints.IntIterator; import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.ints.IntSet;
@ -94,7 +93,13 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
armorContentPacket.setContainerId(ContainerId.ARMOR); armorContentPacket.setContainerId(ContainerId.ARMOR);
contents = new ItemData[4]; contents = new ItemData[4];
for (int i = 5; i < 9; i++) { for (int i = 5; i < 9; i++) {
contents[i - 5] = inventory.getItem(i).getItemData(session); GeyserItemStack item = inventory.getItem(i);
contents[i - 5] = item.getItemData(session);
if (i == 5 &&
item.asItem() == Items.PLAYER_HEAD &&
item.getNbt() != null) {
FakeHeadProvider.setHead(session, session.getPlayerEntity(), item.getNbt().get("SkullOwner"));
}
} }
armorContentPacket.setContents(Arrays.asList(contents)); armorContentPacket.setContents(Arrays.asList(contents));
session.sendUpstreamPacket(armorContentPacket); session.sendUpstreamPacket(armorContentPacket);
@ -136,9 +141,8 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
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.getNbt() != null && javaItem.getNbt() != null) {
&& javaItem.getNbt().get("SkullOwner") instanceof CompoundTag profile) { FakeHeadProvider.setHead(session, session.getPlayerEntity(), javaItem.getNbt().get("SkullOwner"));
FakeHeadProvider.setHead(session, session.getPlayerEntity(), profile);
} else { } else {
FakeHeadProvider.restoreOriginalSkin(session, session.getPlayerEntity()); FakeHeadProvider.restoreOriginalSkin(session, session.getPlayerEntity());
} }

Datei anzeigen

@ -44,6 +44,7 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
import org.cloudburstmc.protocol.bedrock.data.inventory.transaction.InventoryActionData; import org.cloudburstmc.protocol.bedrock.data.inventory.transaction.InventoryActionData;
import org.cloudburstmc.protocol.bedrock.data.inventory.transaction.InventorySource; import org.cloudburstmc.protocol.bedrock.data.inventory.transaction.InventorySource;
import org.cloudburstmc.protocol.bedrock.data.inventory.transaction.LegacySetItemSlotData;
import org.cloudburstmc.protocol.bedrock.packet.ContainerOpenPacket; import org.cloudburstmc.protocol.bedrock.packet.ContainerOpenPacket;
import org.cloudburstmc.protocol.bedrock.packet.InventoryTransactionPacket; import org.cloudburstmc.protocol.bedrock.packet.InventoryTransactionPacket;
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket; import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
@ -63,8 +64,8 @@ import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.item.type.SpawnEggItem; import org.geysermc.geyser.item.type.SpawnEggItem;
import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.type.ItemMappings;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.skin.FakeHeadProvider;
import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.translator.inventory.item.ItemTranslator; import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator;
@ -73,7 +74,9 @@ import org.geysermc.geyser.util.BlockUtils;
import org.geysermc.geyser.util.CooldownUtils; import org.geysermc.geyser.util.CooldownUtils;
import org.geysermc.geyser.util.EntityUtils; import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InventoryUtils;
import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
@ -93,8 +96,6 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
// Send book updates before opening inventories // Send book updates before opening inventories
session.getBookEditCache().checkForSend(); session.getBookEditCache().checkForSend();
ItemMappings mappings = session.getItemMappings();
switch (packet.getTransactionType()) { switch (packet.getTransactionType()) {
case NORMAL: case NORMAL:
if (packet.getActions().size() == 2) { if (packet.getActions().size() == 2) {
@ -350,6 +351,30 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
ServerboundUseItemPacket useItemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND, session.getWorldCache().nextPredictionSequence()); ServerboundUseItemPacket useItemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND, session.getWorldCache().nextPredictionSequence());
session.sendDownstreamPacket(useItemPacket); session.sendDownstreamPacket(useItemPacket);
List<LegacySetItemSlotData> legacySlots = packet.getLegacySlots();
if (packet.getActions().size() == 1 && legacySlots.size() > 0) {
InventoryActionData actionData = packet.getActions().get(0);
LegacySetItemSlotData slotData = legacySlots.get(0);
if (slotData.getContainerId() == 6 && !actionData.getFromItem().isNull()) {
// The player is trying to swap out an armor piece that already has an item in it
// 1.19.4 brings this natively, but we need this specific case for custom head rendering to work
int bedrockHotbarSlot = packet.getHotbarSlot();
Click click = InventoryUtils.getClickForHotbarSwap(bedrockHotbarSlot);
if (click != null && slotData.getSlots().length != 0) {
Inventory playerInventory = session.getPlayerInventory();
// Bedrock sends us the index of the slot in the armor container; armor in Java
// Edition is offset by 5 in the player inventory
int armorSlot = slotData.getSlots()[0] + 5;
if (armorSlot == 5) {
GeyserItemStack armorSlotItem = playerInventory.getItem(armorSlot);
if (armorSlotItem.asItem() == Items.PLAYER_HEAD) {
FakeHeadProvider.restoreOriginalSkin(session, session.getPlayerEntity());
}
}
}
}
}
} }
case 2 -> { case 2 -> {
int blockState = session.getGameMode() == GameMode.CREATIVE ? int blockState = session.getGameMode() == GameMode.CREATIVE ?

Datei anzeigen

@ -28,7 +28,6 @@ package org.geysermc.geyser.translator.protocol.java.entity;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Equipment; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Equipment;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundSetEquipmentPacket; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundSetEquipmentPacket;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.LivingEntity; import org.geysermc.geyser.entity.type.LivingEntity;
@ -66,9 +65,8 @@ public class JavaSetEquipmentTranslator extends PacketTranslator<ClientboundSetE
if (livingEntity instanceof PlayerEntity if (livingEntity instanceof PlayerEntity
&& javaItem != null && javaItem != null
&& javaItem.getId() == Items.PLAYER_HEAD.javaId() && javaItem.getId() == Items.PLAYER_HEAD.javaId()
&& javaItem.getNbt() != null && javaItem.getNbt() != null) {
&& javaItem.getNbt().get("SkullOwner") instanceof CompoundTag profile) { FakeHeadProvider.setHead(session, (PlayerEntity) livingEntity, javaItem.getNbt().get("SkullOwner"));
FakeHeadProvider.setHead(session, (PlayerEntity) livingEntity, profile);
} else { } else {
FakeHeadProvider.restoreOriginalSkin(session, livingEntity); FakeHeadProvider.restoreOriginalSkin(session, livingEntity);
} }