Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-12-25 15:50:14 +01:00
make heads render when equipped
Dieser Commit ist enthalten in:
Ursprung
fbd157ccdf
Commit
231095e115
@ -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);
|
||||||
@ -155,4 +186,4 @@ public class FakeHeadProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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");
|
||||||
|
|
||||||
@ -312,4 +312,4 @@ public class SkinManager {
|
|||||||
return new GameProfileData(skinUrl, capeUrl, isAlex);
|
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.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)) {
|
||||||
@ -916,4 +932,4 @@ public abstract class InventoryTranslator {
|
|||||||
TRANSFER,
|
TRANSFER,
|
||||||
DONE
|
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.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());
|
||||||
}
|
}
|
||||||
|
@ -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 ?
|
||||||
@ -618,4 +643,4 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||||||
}, 150, TimeUnit.MILLISECONDS));
|
}, 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.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);
|
||||||
}
|
}
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren