mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
Merge remote-tracking branch 'upstream/master' into feature/blocky
Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com>
This commit is contained in:
commit
d71d9c142b
6 changed files with 125 additions and 77 deletions
|
@ -26,17 +26,20 @@
|
||||||
package org.geysermc.geyser.entity.type.player;
|
package org.geysermc.geyser.entity.type.player;
|
||||||
|
|
||||||
import com.nukkitx.math.vector.Vector3f;
|
import com.nukkitx.math.vector.Vector3f;
|
||||||
|
import com.nukkitx.math.vector.Vector3i;
|
||||||
import com.nukkitx.protocol.bedrock.data.GameType;
|
import com.nukkitx.protocol.bedrock.data.GameType;
|
||||||
import com.nukkitx.protocol.bedrock.data.PlayerPermission;
|
import com.nukkitx.protocol.bedrock.data.PlayerPermission;
|
||||||
import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
|
import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
|
||||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||||
import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket;
|
import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket;
|
||||||
|
import lombok.Getter;
|
||||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.session.cache.SkullCache;
|
import org.geysermc.geyser.session.cache.SkullCache;
|
||||||
import org.geysermc.geyser.skin.SkullSkinManager;
|
import org.geysermc.geyser.skin.SkullSkinManager;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ -46,6 +49,12 @@ import java.util.concurrent.TimeUnit;
|
||||||
*/
|
*/
|
||||||
public class SkullPlayerEntity extends PlayerEntity {
|
public class SkullPlayerEntity extends PlayerEntity {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private UUID skullUUID;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private Vector3i skullPosition;
|
||||||
|
|
||||||
public SkullPlayerEntity(GeyserSession session, long geyserId) {
|
public SkullPlayerEntity(GeyserSession session, long geyserId) {
|
||||||
super(session, 0, geyserId, UUID.randomUUID(), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, "", null);
|
super(session, 0, geyserId, UUID.randomUUID(), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, "", null);
|
||||||
}
|
}
|
||||||
|
@ -102,11 +111,14 @@ public class SkullPlayerEntity extends PlayerEntity {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateSkull(SkullCache.Skull skull) {
|
public void updateSkull(SkullCache.Skull skull) {
|
||||||
if (!skull.getTexturesProperty().equals(getTexturesProperty())) {
|
skullPosition = skull.getPosition();
|
||||||
|
|
||||||
|
if (!Objects.equals(skull.getTexturesProperty(), getTexturesProperty()) || !Objects.equals(skullUUID, skull.getUuid())) {
|
||||||
// Make skull invisible as we change skins
|
// Make skull invisible as we change skins
|
||||||
setFlag(EntityFlag.INVISIBLE, true);
|
setFlag(EntityFlag.INVISIBLE, true);
|
||||||
updateBedrockMetadata();
|
updateBedrockMetadata();
|
||||||
|
|
||||||
|
skullUUID = skull.getUuid();
|
||||||
setTexturesProperty(skull.getTexturesProperty());
|
setTexturesProperty(skull.getTexturesProperty());
|
||||||
|
|
||||||
SkullSkinManager.requestAndHandleSkin(this, session, (skin -> session.scheduleInEventLoop(() -> {
|
SkullSkinManager.requestAndHandleSkin(this, session, (skin -> session.scheduleInEventLoop(() -> {
|
||||||
|
|
|
@ -78,8 +78,9 @@ public class SkullCache {
|
||||||
this.skullRenderDistanceSquared = distance * distance;
|
this.skullRenderDistanceSquared = distance * distance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Skull putSkull(Vector3i position, String texturesProperty, int blockState) {
|
public Skull putSkull(Vector3i position, UUID uuid, String texturesProperty, int blockState) {
|
||||||
Skull skull = skulls.computeIfAbsent(position, Skull::new);
|
Skull skull = skulls.computeIfAbsent(position, Skull::new);
|
||||||
|
skull.uuid = uuid;
|
||||||
if (!texturesProperty.equals(skull.texturesProperty)) {
|
if (!texturesProperty.equals(skull.texturesProperty)) {
|
||||||
skull.texturesProperty = texturesProperty;
|
skull.texturesProperty = texturesProperty;
|
||||||
skull.skinHash = null;
|
skull.skinHash = null;
|
||||||
|
@ -147,7 +148,7 @@ public class SkullCache {
|
||||||
public Skull updateSkull(Vector3i position, int blockState) {
|
public Skull updateSkull(Vector3i position, int blockState) {
|
||||||
Skull skull = skulls.get(position);
|
Skull skull = skulls.get(position);
|
||||||
if (skull != null) {
|
if (skull != null) {
|
||||||
putSkull(position, skull.texturesProperty, blockState);
|
putSkull(position, skull.uuid, skull.texturesProperty, blockState);
|
||||||
}
|
}
|
||||||
return skull;
|
return skull;
|
||||||
}
|
}
|
||||||
|
@ -266,6 +267,7 @@ public class SkullCache {
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Data
|
@Data
|
||||||
public static class Skull {
|
public static class Skull {
|
||||||
|
private UUID uuid;
|
||||||
private String texturesProperty;
|
private String texturesProperty;
|
||||||
private String skinHash;
|
private String skinHash;
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ import com.nukkitx.protocol.bedrock.packet.PlayerListPacket;
|
||||||
import com.nukkitx.protocol.bedrock.packet.PlayerSkinPacket;
|
import com.nukkitx.protocol.bedrock.packet.PlayerSkinPacket;
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
||||||
|
import org.geysermc.geyser.entity.type.player.SkullPlayerEntity;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.session.auth.BedrockClientData;
|
import org.geysermc.geyser.session.auth.BedrockClientData;
|
||||||
import org.geysermc.geyser.text.GeyserLocale;
|
import org.geysermc.geyser.text.GeyserLocale;
|
||||||
|
@ -69,7 +70,7 @@ public class SkinManager {
|
||||||
// The server either didn't have a texture to send, or we didn't have the texture ID cached.
|
// The server either didn't have a texture to send, or we didn't have the texture ID cached.
|
||||||
// Let's see if this player is a Bedrock player, and if so, let's pull their skin.
|
// Let's see if this player is a Bedrock player, and if so, let's pull their skin.
|
||||||
// Otherwise, grab the default player skin
|
// Otherwise, grab the default player skin
|
||||||
SkinProvider.SkinData fallbackSkinData = SkinProvider.determineFallbackSkinData(playerEntity);
|
SkinProvider.SkinData fallbackSkinData = SkinProvider.determineFallbackSkinData(playerEntity.getUuid());
|
||||||
if (skin == null) {
|
if (skin == null) {
|
||||||
skin = fallbackSkinData.skin();
|
skin = fallbackSkinData.skin();
|
||||||
geometry = fallbackSkinData.geometry();
|
geometry = fallbackSkinData.geometry();
|
||||||
|
@ -255,24 +256,28 @@ public class SkinManager {
|
||||||
* @return The built GameProfileData
|
* @return The built GameProfileData
|
||||||
*/
|
*/
|
||||||
public static @Nullable GameProfileData from(PlayerEntity entity) {
|
public static @Nullable GameProfileData from(PlayerEntity entity) {
|
||||||
try {
|
String texturesProperty = entity.getTexturesProperty();
|
||||||
String texturesProperty = entity.getTexturesProperty();
|
if (texturesProperty == null) {
|
||||||
|
// Likely offline mode
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (texturesProperty == null) {
|
try {
|
||||||
// Likely offline mode
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return loadFromJson(texturesProperty);
|
return loadFromJson(texturesProperty);
|
||||||
} catch (IOException exception) {
|
} catch (Exception exception) {
|
||||||
GeyserImpl.getInstance().getLogger().debug("Something went wrong while processing skin for " + entity.getUsername());
|
if (entity instanceof SkullPlayerEntity skullEntity) {
|
||||||
|
GeyserImpl.getInstance().getLogger().debug("Something went wrong while processing skin for skull at " + skullEntity.getSkullPosition() + " with Value: " + texturesProperty);
|
||||||
|
} else {
|
||||||
|
GeyserImpl.getInstance().getLogger().debug("Something went wrong while processing skin for " + entity.getUsername() + " with Value: " + texturesProperty);
|
||||||
|
}
|
||||||
if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
|
if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
|
||||||
exception.printStackTrace();
|
exception.printStackTrace();
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GameProfileData loadFromJson(String encodedJson) throws IOException {
|
public 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");
|
||||||
|
|
||||||
|
@ -285,14 +290,23 @@ public class SkinManager {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String skinUrl = skinTexture.get("url").asText().replace("http://", "https://");
|
String skinUrl;
|
||||||
|
JsonNode skinUrlNode = skinTexture.get("url");
|
||||||
|
if (skinUrlNode != null && skinUrlNode.isTextual()) {
|
||||||
|
skinUrl = skinUrlNode.asText().replace("http://", "https://");
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
boolean isAlex = skinTexture.has("metadata");
|
boolean isAlex = skinTexture.has("metadata");
|
||||||
|
|
||||||
String capeUrl = null;
|
String capeUrl = null;
|
||||||
JsonNode capeTexture = textures.get("CAPE");
|
JsonNode capeTexture = textures.get("CAPE");
|
||||||
if (capeTexture != null) {
|
if (capeTexture != null) {
|
||||||
capeUrl = capeTexture.get("url").asText().replace("http://", "https://");
|
JsonNode capeUrlNode = capeTexture.get("url");
|
||||||
|
if (capeUrlNode != null && capeUrlNode.isTextual()) {
|
||||||
|
capeUrl = capeUrlNode.asText().replace("http://", "https://");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new GameProfileData(skinUrl, capeUrl, isAlex);
|
return new GameProfileData(skinUrl, capeUrl, isAlex);
|
||||||
|
|
|
@ -26,9 +26,6 @@
|
||||||
package org.geysermc.geyser.skin;
|
package org.geysermc.geyser.skin;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
|
||||||
import com.github.steveice10.opennbt.tag.builtin.IntArrayTag;
|
|
||||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
|
||||||
import com.google.common.cache.Cache;
|
import com.google.common.cache.Cache;
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import it.unimi.dsi.fastutil.bytes.ByteArrays;
|
import it.unimi.dsi.fastutil.bytes.ByteArrays;
|
||||||
|
@ -172,14 +169,13 @@ public class SkinProvider {
|
||||||
/**
|
/**
|
||||||
* If skin data fails to apply, or there is no skin data to apply, determine what skin we should give as a fallback.
|
* If skin data fails to apply, or there is no skin data to apply, determine what skin we should give as a fallback.
|
||||||
*/
|
*/
|
||||||
static SkinData determineFallbackSkinData(PlayerEntity entity) {
|
static SkinData determineFallbackSkinData(UUID uuid) {
|
||||||
Skin skin = null;
|
Skin skin = null;
|
||||||
Cape cape = null;
|
Cape cape = null;
|
||||||
SkinGeometry geometry = SkinGeometry.WIDE;
|
SkinGeometry geometry = SkinGeometry.WIDE;
|
||||||
|
|
||||||
if (GeyserImpl.getInstance().getConfig().getRemote().authType() != AuthType.ONLINE) {
|
if (GeyserImpl.getInstance().getConfig().getRemote().authType() != AuthType.ONLINE) {
|
||||||
// Let's see if this player is a Bedrock player, and if so, let's pull their skin.
|
// Let's see if this player is a Bedrock player, and if so, let's pull their skin.
|
||||||
UUID uuid = entity.getUuid();
|
|
||||||
GeyserSession session = GeyserImpl.getInstance().connectionByUuid(uuid);
|
GeyserSession session = GeyserImpl.getInstance().connectionByUuid(uuid);
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
String skinId = session.getClientData().getSkinId();
|
String skinId = session.getClientData().getSkinId();
|
||||||
|
@ -192,7 +188,7 @@ public class SkinProvider {
|
||||||
|
|
||||||
if (skin == null) {
|
if (skin == null) {
|
||||||
// We don't have a skin for the player right now. Fall back to a default.
|
// We don't have a skin for the player right now. Fall back to a default.
|
||||||
ProvidedSkins.ProvidedSkin providedSkin = ProvidedSkins.getDefaultPlayerSkin(entity.getUuid());
|
ProvidedSkins.ProvidedSkin providedSkin = ProvidedSkins.getDefaultPlayerSkin(uuid);
|
||||||
skin = providedSkin.getData();
|
skin = providedSkin.getData();
|
||||||
geometry = providedSkin.isSlim() ? SkinProvider.SkinGeometry.SLIM : SkinProvider.SkinGeometry.WIDE;
|
geometry = providedSkin.isSlim() ? SkinProvider.SkinGeometry.SLIM : SkinProvider.SkinGeometry.WIDE;
|
||||||
}
|
}
|
||||||
|
@ -232,7 +228,7 @@ public class SkinProvider {
|
||||||
SkinManager.GameProfileData data = SkinManager.GameProfileData.from(entity);
|
SkinManager.GameProfileData data = SkinManager.GameProfileData.from(entity);
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
// This player likely does not have a textures property
|
// This player likely does not have a textures property
|
||||||
return CompletableFuture.completedFuture(determineFallbackSkinData(entity));
|
return CompletableFuture.completedFuture(determineFallbackSkinData(entity.getUuid()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return requestSkinAndCape(entity.getUuid(), data.skinUrl(), data.capeUrl())
|
return requestSkinAndCape(entity.getUuid(), data.skinUrl(), data.capeUrl())
|
||||||
|
|
|
@ -29,11 +29,12 @@ import com.nukkitx.protocol.bedrock.data.skin.ImageData;
|
||||||
import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin;
|
import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin;
|
||||||
import com.nukkitx.protocol.bedrock.packet.PlayerSkinPacket;
|
import com.nukkitx.protocol.bedrock.packet.PlayerSkinPacket;
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
import org.geysermc.geyser.entity.type.player.SkullPlayerEntity;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.text.GeyserLocale;
|
import org.geysermc.geyser.text.GeyserLocale;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class SkullSkinManager extends SkinManager {
|
public class SkullSkinManager extends SkinManager {
|
||||||
|
@ -48,28 +49,37 @@ public class SkullSkinManager extends SkinManager {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void requestAndHandleSkin(PlayerEntity entity, GeyserSession session,
|
public static void requestAndHandleSkin(SkullPlayerEntity entity, GeyserSession session,
|
||||||
Consumer<SkinProvider.Skin> skinConsumer) {
|
Consumer<SkinProvider.Skin> skinConsumer) {
|
||||||
|
BiConsumer<SkinProvider.Skin, Throwable> applySkin = (skin, throwable) -> {
|
||||||
|
try {
|
||||||
|
PlayerSkinPacket packet = new PlayerSkinPacket();
|
||||||
|
packet.setUuid(entity.getUuid());
|
||||||
|
packet.setOldSkinName("");
|
||||||
|
packet.setNewSkinName(skin.getTextureUrl());
|
||||||
|
packet.setSkin(buildSkullEntryManually(skin.getTextureUrl(), skin.getSkinData()));
|
||||||
|
packet.setTrustedSkin(true);
|
||||||
|
session.sendUpstreamPacket(packet);
|
||||||
|
} catch (Exception e) {
|
||||||
|
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skinConsumer != null) {
|
||||||
|
skinConsumer.accept(skin);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
GameProfileData data = GameProfileData.from(entity);
|
GameProfileData data = GameProfileData.from(entity);
|
||||||
|
if (data == null) {
|
||||||
SkinProvider.requestSkin(entity.getUuid(), data.skinUrl(), true)
|
GeyserImpl.getInstance().getLogger().debug("Using fallback skin for skull at " + entity.getSkullPosition() +
|
||||||
.whenCompleteAsync((skin, throwable) -> {
|
" with texture value: " + entity.getTexturesProperty() + " and UUID: " + entity.getSkullUUID());
|
||||||
try {
|
// No texture available, fallback using the UUID
|
||||||
PlayerSkinPacket packet = new PlayerSkinPacket();
|
SkinProvider.SkinData fallback = SkinProvider.determineFallbackSkinData(entity.getSkullUUID());
|
||||||
packet.setUuid(entity.getUuid());
|
applySkin.accept(fallback.skin(), null);
|
||||||
packet.setOldSkinName("");
|
} else {
|
||||||
packet.setNewSkinName(skin.getTextureUrl());
|
SkinProvider.requestSkin(entity.getUuid(), data.skinUrl(), true)
|
||||||
packet.setSkin(buildSkullEntryManually(skin.getTextureUrl(), skin.getSkinData()));
|
.whenCompleteAsync(applySkin);
|
||||||
packet.setTrustedSkin(true);
|
}
|
||||||
session.sendUpstreamPacket(packet);
|
|
||||||
} catch (Exception e) {
|
|
||||||
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skinConsumer != null) {
|
|
||||||
skinConsumer.accept(skin);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,9 @@ import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.session.cache.SkullCache;
|
import org.geysermc.geyser.session.cache.SkullCache;
|
||||||
import org.geysermc.geyser.skin.SkinProvider;
|
import org.geysermc.geyser.skin.SkinProvider;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
@ -59,41 +61,52 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements
|
||||||
builder.put("SkullType", skullVariant);
|
builder.put("SkullType", skullVariant);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CompletableFuture<String> getTextures(CompoundTag tag) {
|
private static UUID getUUID(CompoundTag owner) {
|
||||||
CompoundTag owner = tag.get("SkullOwner");
|
if (owner.get("Id") instanceof IntArrayTag uuidTag && uuidTag.length() == 4) {
|
||||||
if (owner != null) {
|
int[] uuidAsArray = uuidTag.getValue();
|
||||||
CompoundTag properties = owner.get("Properties");
|
// thank u viaversion
|
||||||
if (properties == null) {
|
return new UUID((long) uuidAsArray[0] << 32 | ((long) uuidAsArray[1] & 0xFFFFFFFFL),
|
||||||
if (owner.get("Id") instanceof IntArrayTag uuidTag) {
|
(long) uuidAsArray[2] << 32 | ((long) uuidAsArray[3] & 0xFFFFFFFFL));
|
||||||
int[] uuidAsArray = uuidTag.getValue();
|
|
||||||
// thank u viaversion
|
|
||||||
UUID uuid = new UUID((long) uuidAsArray[0] << 32 | ((long) uuidAsArray[1] & 0xFFFFFFFFL),
|
|
||||||
(long) uuidAsArray[2] << 32 | ((long) uuidAsArray[3] & 0xFFFFFFFFL));
|
|
||||||
if (uuid.version() == 4) {
|
|
||||||
String uuidString = uuid.toString().replace("-", "");
|
|
||||||
return SkinProvider.requestTexturesFromUUID(uuidString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (owner.get("Name") instanceof StringTag nameTag) {
|
|
||||||
// Fall back to username if UUID was missing or was an offline mode UUID
|
|
||||||
return SkinProvider.requestTexturesFromUsername(nameTag.getValue());
|
|
||||||
}
|
|
||||||
return CompletableFuture.completedFuture(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
ListTag textures = properties.get("textures");
|
|
||||||
LinkedHashMap<?,?> tag1 = (LinkedHashMap<?,?>) textures.get(0).getValue();
|
|
||||||
StringTag texture = (StringTag) tag1.get("Value");
|
|
||||||
return CompletableFuture.completedFuture(texture.getValue());
|
|
||||||
}
|
}
|
||||||
return CompletableFuture.completedFuture(null);
|
// Convert username to an offline UUID
|
||||||
|
String username = null;
|
||||||
|
if (owner.get("Name") instanceof StringTag nameTag) {
|
||||||
|
username = nameTag.getValue().toLowerCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
return UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CompletableFuture<String> getTextures(CompoundTag owner, UUID uuid) {
|
||||||
|
CompoundTag properties = owner.get("Properties");
|
||||||
|
if (properties == null) {
|
||||||
|
if (uuid != null && uuid.version() == 4) {
|
||||||
|
String uuidString = uuid.toString().replace("-", "");
|
||||||
|
return SkinProvider.requestTexturesFromUUID(uuidString);
|
||||||
|
} else if (owner.get("Name") instanceof StringTag nameTag) {
|
||||||
|
// Fall back to username if UUID was missing or was an offline mode UUID
|
||||||
|
return SkinProvider.requestTexturesFromUsername(nameTag.getValue());
|
||||||
|
}
|
||||||
|
return CompletableFuture.completedFuture(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
ListTag textures = properties.get("textures");
|
||||||
|
LinkedHashMap<?,?> tag1 = (LinkedHashMap<?,?>) textures.get(0).getValue();
|
||||||
|
StringTag texture = (StringTag) tag1.get("Value");
|
||||||
|
return CompletableFuture.completedFuture(texture.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int translateSkull(GeyserSession session, CompoundTag tag, Vector3i blockPosition, int blockState) {
|
public static int translateSkull(GeyserSession session, CompoundTag tag, Vector3i blockPosition, int blockState) {
|
||||||
CompletableFuture<String> texturesFuture = getTextures(tag);
|
CompoundTag owner = tag.get("SkullOwner");
|
||||||
|
if (owner == null) {
|
||||||
|
session.getSkullCache().removeSkull(blockPosition);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
UUID uuid = getUUID(owner);
|
||||||
|
|
||||||
|
CompletableFuture<String> texturesFuture = getTextures(owner, uuid);
|
||||||
if (texturesFuture.isDone()) {
|
if (texturesFuture.isDone()) {
|
||||||
try {
|
try {
|
||||||
SkullCache.Skull skull = session.getSkullCache().putSkull(blockPosition, texturesFuture.get(), blockState);
|
SkullCache.Skull skull = session.getSkullCache().putSkull(blockPosition, uuid, texturesFuture.get(), blockState);
|
||||||
return skull.getCustomRuntimeId();
|
return skull.getCustomRuntimeId();
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
session.getGeyser().getLogger().debug("Failed to acquire textures for custom skull: " + blockPosition + " " + tag);
|
session.getGeyser().getLogger().debug("Failed to acquire textures for custom skull: " + blockPosition + " " + tag);
|
||||||
|
@ -111,17 +124,18 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (session.getEventLoop().inEventLoop()) {
|
if (session.getEventLoop().inEventLoop()) {
|
||||||
putSkull(session, blockPosition, texturesProperty, blockState);
|
putSkull(session, blockPosition, uuid, texturesProperty, blockState);
|
||||||
} else {
|
} else {
|
||||||
session.executeInEventLoop(() -> putSkull(session, blockPosition, texturesProperty, blockState));
|
session.executeInEventLoop(() -> putSkull(session, blockPosition, uuid, texturesProperty, blockState));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// We don't have the textures yet, so we can't determine if a custom block was defined for this skull
|
// We don't have the textures yet, so we can't determine if a custom block was defined for this skull
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void putSkull(GeyserSession session, Vector3i blockPosition, String texturesProperty, int blockState) {
|
private static void putSkull(GeyserSession session, Vector3i blockPosition, UUID uuid, String texturesProperty, int blockState) {
|
||||||
SkullCache.Skull skull = session.getSkullCache().putSkull(blockPosition, texturesProperty, blockState);
|
SkullCache.Skull skull = session.getSkullCache().putSkull(blockPosition, uuid, texturesProperty, blockState);
|
||||||
if (skull.getCustomRuntimeId() != -1) {
|
if (skull.getCustomRuntimeId() != -1) {
|
||||||
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
|
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
|
||||||
updateBlockPacket.setDataLayer(0);
|
updateBlockPacket.setDataLayer(0);
|
||||||
|
|
Loading…
Reference in a new issue