mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
make heads render when equipped
This commit is contained in:
parent
fbd157ccdf
commit
231095e115
6 changed files with 97 additions and 23 deletions
|
@ -26,6 +26,8 @@
|
|||
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.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
|
@ -41,7 +43,9 @@ import org.geysermc.geyser.text.GeyserLocale;
|
|||
import javax.annotation.Nonnull;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
@ -91,21 +95,48 @@ public class FakeHeadProvider {
|
|||
}
|
||||
});
|
||||
|
||||
public static void setHead(GeyserSession session, PlayerEntity entity, CompoundTag profileTag) {
|
||||
SkinManager.GameProfileData gameProfileData = SkinManager.GameProfileData.from(profileTag);
|
||||
if (gameProfileData == null) {
|
||||
public static void setHead(GeyserSession session, PlayerEntity entity, Tag skullOwner) {
|
||||
if (skullOwner == null) {
|
||||
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();
|
||||
|
||||
session.getPlayerWithCustomHeads().add(entity.getUuid());
|
||||
|
||||
String texturesProperty = entity.getTexturesProperty();
|
||||
|
||||
SkinProvider.EXECUTOR_SERVICE.execute(() -> {
|
||||
try {
|
||||
SkinProvider.SkinData mergedSkinData = MERGED_SKINS_LOADING_CACHE.get(new FakeHeadEntry(texturesProperty, fakeHeadSkinUrl, entity));
|
||||
|
||||
SkinManager.sendSkinPacket(session, entity, mergedSkinData);
|
||||
} catch (ExecutionException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Couldn't merge skin of " + entity.getUsername() + " with head skin url " + fakeHeadSkinUrl, e);
|
||||
|
@ -155,4 +186,4 @@ public class FakeHeadProvider {
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -277,7 +277,7 @@ public class SkinManager {
|
|||
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 textures = skinObject.get("textures");
|
||||
|
||||
|
@ -312,4 +312,4 @@ public class SkinManager {
|
|||
return new GameProfileData(skinUrl, capeUrl, isAlex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -49,7 +49,9 @@ import org.geysermc.geyser.inventory.click.ClickPlan;
|
|||
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
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.SingleChestInventoryTranslator;
|
||||
import org.geysermc.geyser.translator.inventory.furnace.BlastFurnaceInventoryTranslator;
|
||||
|
@ -216,6 +218,20 @@ public abstract class InventoryTranslator {
|
|||
boolean isSourceCursor = isCursor(transferAction.getSource());
|
||||
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(),
|
||||
isSourceCursor ? -1 : sourceSlot,
|
||||
transferAction.getDestination().getContainer(), isDestCursor ? -1 : destSlot)) {
|
||||
|
@ -916,4 +932,4 @@ public abstract class InventoryTranslator {
|
|||
TRANSFER,
|
||||
DONE
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.inventory.ContainerType;
|
||||
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.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
|
@ -94,7 +93,13 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
|
|||
armorContentPacket.setContainerId(ContainerId.ARMOR);
|
||||
contents = new ItemData[4];
|
||||
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));
|
||||
session.sendUpstreamPacket(armorContentPacket);
|
||||
|
@ -136,9 +141,8 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
|
|||
if (slot == 5) {
|
||||
// Check for custom skull
|
||||
if (javaItem.asItem() == Items.PLAYER_HEAD
|
||||
&& javaItem.getNbt() != null
|
||||
&& javaItem.getNbt().get("SkullOwner") instanceof CompoundTag profile) {
|
||||
FakeHeadProvider.setHead(session, session.getPlayerEntity(), profile);
|
||||
&& javaItem.getNbt() != null) {
|
||||
FakeHeadProvider.setHead(session, session.getPlayerEntity(), javaItem.getNbt().get("SkullOwner"));
|
||||
} else {
|
||||
FakeHeadProvider.restoreOriginalSkin(session, session.getPlayerEntity());
|
||||
}
|
||||
|
|
|
@ -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.transaction.InventoryActionData;
|
||||
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.InventoryTransactionPacket;
|
||||
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.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.type.ItemMappings;
|
||||
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.item.ItemTranslator;
|
||||
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.EntityUtils;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
|
@ -93,8 +96,6 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||
// Send book updates before opening inventories
|
||||
session.getBookEditCache().checkForSend();
|
||||
|
||||
ItemMappings mappings = session.getItemMappings();
|
||||
|
||||
switch (packet.getTransactionType()) {
|
||||
case NORMAL:
|
||||
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());
|
||||
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 -> {
|
||||
int blockState = session.getGameMode() == GameMode.CREATIVE ?
|
||||
|
@ -618,4 +643,4 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||
}, 150, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.ItemStack;
|
||||
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.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.type.LivingEntity;
|
||||
|
@ -66,9 +65,8 @@ public class JavaSetEquipmentTranslator extends PacketTranslator<ClientboundSetE
|
|||
if (livingEntity instanceof PlayerEntity
|
||||
&& javaItem != null
|
||||
&& javaItem.getId() == Items.PLAYER_HEAD.javaId()
|
||||
&& javaItem.getNbt() != null
|
||||
&& javaItem.getNbt().get("SkullOwner") instanceof CompoundTag profile) {
|
||||
FakeHeadProvider.setHead(session, (PlayerEntity) livingEntity, profile);
|
||||
&& javaItem.getNbt() != null) {
|
||||
FakeHeadProvider.setHead(session, (PlayerEntity) livingEntity, javaItem.getNbt().get("SkullOwner"));
|
||||
} else {
|
||||
FakeHeadProvider.restoreOriginalSkin(session, livingEntity);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue