mirror of
https://github.com/GeyserMC/Geyser.git
synced 2025-12-19 14:59:27 +00:00
Fix skins not showing on 1.21.60
This commit is contained in:
@@ -35,7 +35,6 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
@@ -94,6 +93,7 @@ import org.cloudburstmc.protocol.bedrock.packet.ItemComponentPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEvent2Packet;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetCommandsEnabledPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetTimePacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.StartGamePacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SyncEntityPropertyPacket;
|
||||
@@ -331,10 +331,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
private final Map<Vector3i, ItemFrameEntity> itemFrameCache = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
/**
|
||||
* A list of all players that have a player head on with a custom texture.
|
||||
* A map of all players (and their heads) that are wearing a player head with a custom texture.
|
||||
* Our workaround for these players is to give them a custom skin and geometry to emulate wearing a custom skull.
|
||||
*/
|
||||
private final Set<UUID> playerWithCustomHeads = new ObjectOpenHashSet<>();
|
||||
private final Map<UUID, GameProfile> playerWithCustomHeads = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
@Setter
|
||||
private boolean droppingLecternBook;
|
||||
@@ -787,6 +787,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
playStatusPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN);
|
||||
upstream.sendPacket(playStatusPacket);
|
||||
|
||||
SetCommandsEnabledPacket setCommandsEnabledPacket = new SetCommandsEnabledPacket();
|
||||
setCommandsEnabledPacket.setCommandsEnabled(!geyser.getConfig().isXboxAchievementsEnabled());
|
||||
upstream.sendPacket(setCommandsEnabledPacket);
|
||||
|
||||
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
|
||||
attributesPacket.setRuntimeEntityId(getPlayerEntity().getGeyserId());
|
||||
// Default move speed
|
||||
|
||||
@@ -109,6 +109,12 @@ public class FakeHeadProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
GameProfile current = session.getPlayerWithCustomHeads().get(entity.getUuid());
|
||||
if (profile.equals(current)) {
|
||||
// We already did this, no need to re-compute
|
||||
return;
|
||||
}
|
||||
|
||||
Map<TextureType, Texture> textures;
|
||||
try {
|
||||
textures = profile.getTextures(false);
|
||||
@@ -118,7 +124,7 @@ public class FakeHeadProvider {
|
||||
}
|
||||
|
||||
if (textures == null || textures.isEmpty()) {
|
||||
loadHead(session, entity, profile.getName());
|
||||
loadHeadFromProfile(session, entity, profile);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -133,16 +139,18 @@ public class FakeHeadProvider {
|
||||
|
||||
boolean isAlex = skinTexture.getModel() == TextureModel.SLIM;
|
||||
|
||||
loadHead(session, entity, new GameProfileData(skinTexture.getURL(), capeUrl, isAlex));
|
||||
loadHeadFromProfile(session, entity, new GameProfileData(skinTexture.getURL(), capeUrl, isAlex), profile);
|
||||
}
|
||||
|
||||
public static void loadHead(GeyserSession session, PlayerEntity entity, String owner) {
|
||||
if (owner == null || owner.isEmpty()) {
|
||||
return;
|
||||
public static void loadHeadFromProfile(GeyserSession session, PlayerEntity entity, GameProfile profile) {
|
||||
CompletableFuture<String> texturesFuture;
|
||||
if (profile.getId() != null) {
|
||||
texturesFuture = SkinProvider.requestTexturesFromUUID(profile.getId().toString());
|
||||
} else {
|
||||
texturesFuture = SkinProvider.requestTexturesFromUsername(profile.getName());
|
||||
}
|
||||
|
||||
CompletableFuture<String> completableFuture = SkinProvider.requestTexturesFromUsername(owner);
|
||||
completableFuture.whenCompleteAsync((encodedJson, throwable) -> {
|
||||
texturesFuture.whenCompleteAsync((encodedJson, throwable) -> {
|
||||
if (throwable != null) {
|
||||
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), throwable);
|
||||
return;
|
||||
@@ -152,17 +160,17 @@ public class FakeHeadProvider {
|
||||
if (gameProfileData == null) {
|
||||
return;
|
||||
}
|
||||
loadHead(session, entity, gameProfileData);
|
||||
loadHeadFromProfile(session, entity, gameProfileData, profile);
|
||||
} 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) {
|
||||
public static void loadHeadFromProfile(GeyserSession session, PlayerEntity entity, SkinManager.GameProfileData gameProfileData, GameProfile profile) {
|
||||
String fakeHeadSkinUrl = gameProfileData.skinUrl();
|
||||
|
||||
session.getPlayerWithCustomHeads().add(entity.getUuid());
|
||||
session.getPlayerWithCustomHeads().put(entity.getUuid(), profile);
|
||||
String texturesProperty = entity.getTexturesProperty();
|
||||
SkinProvider.getExecutorService().execute(() -> {
|
||||
try {
|
||||
@@ -179,7 +187,7 @@ public class FakeHeadProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!session.getPlayerWithCustomHeads().remove(entity.getUuid())) {
|
||||
if (session.getPlayerWithCustomHeads().remove(entity.getUuid()) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ import org.geysermc.geyser.text.GeyserLocale;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
@@ -102,7 +101,7 @@ public class SkinManager {
|
||||
Skin skin,
|
||||
Cape cape,
|
||||
SkinGeometry geometry) {
|
||||
SerializedSkin serializedSkin = getSkin(skin.textureUrl(), skin, cape, geometry);
|
||||
SerializedSkin serializedSkin = getSkin(session, skin.textureUrl(), skin, cape, geometry);
|
||||
|
||||
// This attempts to find the XUID of the player so profile images show up for Xbox accounts
|
||||
String xuid = "";
|
||||
@@ -138,7 +137,6 @@ public class SkinManager {
|
||||
SkinGeometry geometry = skinData.geometry();
|
||||
|
||||
if (entity.getUuid().equals(session.getPlayerEntity().getUuid())) {
|
||||
// TODO is this special behavior needed?
|
||||
PlayerListPacket.Entry updatedEntry = buildEntryManually(
|
||||
session,
|
||||
entity.getUuid(),
|
||||
@@ -158,17 +156,24 @@ public class SkinManager {
|
||||
packet.setUuid(entity.getUuid());
|
||||
packet.setOldSkinName("");
|
||||
packet.setNewSkinName(skin.textureUrl());
|
||||
packet.setSkin(getSkin(skin.textureUrl(), skin, cape, geometry));
|
||||
packet.setSkin(getSkin(session, skin.textureUrl(), skin, cape, geometry));
|
||||
packet.setTrustedSkin(true);
|
||||
session.sendUpstreamPacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
private static SerializedSkin getSkin(String skinId, Skin skin, Cape cape, SkinGeometry geometry) {
|
||||
return SerializedSkin.of(skinId, "", geometry.geometryName(),
|
||||
ImageData.of(skin.skinData()), Collections.emptyList(),
|
||||
ImageData.of(cape.capeData()), geometry.geometryData(),
|
||||
"", true, false, false, cape.capeId(), skinId);
|
||||
private static SerializedSkin getSkin(GeyserSession session, String skinId, Skin skin, Cape cape, SkinGeometry geometry) {
|
||||
return SerializedSkin.builder()
|
||||
.skinId(skinId)
|
||||
.skinResourcePatch(geometry.geometryName())
|
||||
.skinData(ImageData.of(skin.skinData()))
|
||||
.capeData(ImageData.of(cape.capeData()))
|
||||
.geometryData(geometry.geometryData())
|
||||
.premium(true)
|
||||
.capeId(cape.capeId())
|
||||
.fullSkinId(skinId)
|
||||
.geometryDataEngineVersion(session.getClientData().getGameVersion())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSession session,
|
||||
@@ -334,4 +339,4 @@ public class SkinManager {
|
||||
|
||||
private static final String DEFAULT_FLOODGATE_STEVE = "https://textures.minecraft.net/texture/31f477eb1a7beee631c2ca64d06f8f68fa93a3386d04452ab27f43acdf1b60cb";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,20 +35,24 @@ import org.geysermc.geyser.entity.type.player.SkullPlayerEntity;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class SkullSkinManager extends SkinManager {
|
||||
|
||||
public static SerializedSkin buildSkullEntryManually(String skinId, byte[] skinData) {
|
||||
// Prevents https://cdn.discordapp.com/attachments/613194828359925800/779458146191147008/unknown.png
|
||||
public static SerializedSkin buildSkullEntryManually(GeyserSession session, String skinId, byte[] skinData) {
|
||||
skinId = skinId + "_skull";
|
||||
return SerializedSkin.of(
|
||||
skinId, "", SkinProvider.SKULL_GEOMETRY.geometryName(), ImageData.of(skinData), Collections.emptyList(),
|
||||
ImageData.of(SkinProvider.EMPTY_CAPE.capeData()), SkinProvider.SKULL_GEOMETRY.geometryData(),
|
||||
"", true, false, false, SkinProvider.EMPTY_CAPE.capeId(), skinId
|
||||
);
|
||||
return SerializedSkin.builder()
|
||||
.skinId(skinId)
|
||||
.skinResourcePatch(SkinProvider.SKULL_GEOMETRY.geometryName())
|
||||
.skinData(ImageData.of(skinData))
|
||||
.capeData(ImageData.of(SkinProvider.EMPTY_CAPE.capeData()))
|
||||
.geometryData(SkinProvider.SKULL_GEOMETRY.geometryData())
|
||||
.premium(true)
|
||||
.capeId(SkinProvider.EMPTY_CAPE.capeId())
|
||||
.fullSkinId(skinId)
|
||||
.geometryDataEngineVersion(session.getClientData().getGameVersion())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static void requestAndHandleSkin(SkullPlayerEntity entity, GeyserSession session,
|
||||
@@ -59,7 +63,7 @@ public class SkullSkinManager extends SkinManager {
|
||||
packet.setUuid(entity.getUuid());
|
||||
packet.setOldSkinName("");
|
||||
packet.setNewSkinName(skin.textureUrl());
|
||||
packet.setSkin(buildSkullEntryManually(skin.textureUrl(), skin.skinData()));
|
||||
packet.setSkin(buildSkullEntryManually(session, skin.textureUrl(), skin.skinData()));
|
||||
packet.setTrustedSkin(true);
|
||||
session.sendUpstreamPacket(packet);
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -76,7 +76,6 @@ import org.geysermc.geyser.util.InventoryUtils;
|
||||
import org.geysermc.geyser.util.ItemUtils;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.EmptySlotDisplay;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.SlotDisplay;
|
||||
|
||||
@@ -258,14 +257,14 @@ public abstract class InventoryTranslator {
|
||||
|
||||
if (this instanceof PlayerInventoryTranslator) {
|
||||
if (destSlot == 5) {
|
||||
//only set the head if the destination is the head slot
|
||||
// only set the head if the destination is the head slot
|
||||
GeyserItemStack javaItem = inventory.getItem(sourceSlot);
|
||||
if (javaItem.asItem() == Items.PLAYER_HEAD
|
||||
&& javaItem.hasNonBaseComponents()) {
|
||||
FakeHeadProvider.setHead(session, session.getPlayerEntity(), javaItem.getComponent(DataComponentTypes.PROFILE));
|
||||
}
|
||||
} else if (sourceSlot == 5) {
|
||||
//we are probably removing the head, so restore the original skin
|
||||
// we are probably removing the head, so restore the original skin
|
||||
FakeHeadProvider.restoreOriginalSkin(session, session.getPlayerEntity());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,9 +279,22 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
|
||||
return bundleResponse;
|
||||
}
|
||||
|
||||
int sourceSlot = bedrockSlotToJava(transferAction.getSource());
|
||||
int destSlot = bedrockSlotToJava(transferAction.getDestination());
|
||||
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.hasNonBaseComponents()) {
|
||||
FakeHeadProvider.setHead(session, session.getPlayerEntity(), javaItem.getComponent(DataComponentTypes.PROFILE));
|
||||
}
|
||||
} else if (sourceSlot == 5) {
|
||||
// we are probably removing the head, so restore the original skin
|
||||
FakeHeadProvider.restoreOriginalSkin(session, session.getPlayerEntity());
|
||||
}
|
||||
|
||||
int transferAmount = transferAction.getCount();
|
||||
if (isCursor(transferAction.getDestination())) {
|
||||
int sourceSlot = bedrockSlotToJava(transferAction.getSource());
|
||||
GeyserItemStack sourceItem = inventory.getItem(sourceSlot);
|
||||
if (playerInv.getCursor().isEmpty()) {
|
||||
playerInv.setCursor(sourceItem.copy(0), session);
|
||||
@@ -294,7 +307,6 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
|
||||
|
||||
affectedSlots.add(sourceSlot);
|
||||
} else if (isCursor(transferAction.getSource())) {
|
||||
int destSlot = bedrockSlotToJava(transferAction.getDestination());
|
||||
GeyserItemStack sourceItem = playerInv.getCursor();
|
||||
if (inventory.getItem(destSlot).isEmpty()) {
|
||||
inventory.setItem(destSlot, sourceItem.copy(0), session);
|
||||
@@ -307,8 +319,6 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
|
||||
|
||||
affectedSlots.add(destSlot);
|
||||
} else {
|
||||
int sourceSlot = bedrockSlotToJava(transferAction.getSource());
|
||||
int destSlot = bedrockSlotToJava(transferAction.getDestination());
|
||||
GeyserItemStack sourceItem = inventory.getItem(sourceSlot);
|
||||
if (inventory.getItem(destSlot).isEmpty()) {
|
||||
inventory.setItem(destSlot, sourceItem.copy(0), session);
|
||||
|
||||
Reference in New Issue
Block a user