Fixes errors related to skins

This commit is contained in:
Tim203 2019-11-19 21:31:24 +01:00
parent 51c1792d67
commit 068033aeaa
5 changed files with 30 additions and 27 deletions

View file

@ -15,7 +15,6 @@ public class BedrockPlayerInitializedTranslator extends PacketTranslator<SetLoca
for (PlayerEntity entity : session.getEntityCache().getEntitiesByType(PlayerEntity.class)) { for (PlayerEntity entity : session.getEntityCache().getEntitiesByType(PlayerEntity.class)) {
if (!entity.isValid()) { if (!entity.isValid()) {
entity.sendPlayer(session);
// async skin loading // async skin loading
SkinUtils.requestAndHandleSkinAndCape(entity, session, skinAndCape -> entity.sendPlayer(session)); SkinUtils.requestAndHandleSkinAndCape(entity, session, skinAndCape -> entity.sendPlayer(session));
} }

View file

@ -51,8 +51,9 @@ public class JavaSpawnPlayerTranslator extends PacketTranslator<ServerSpawnPlaye
entity.setPosition(position); entity.setPosition(position);
entity.setRotation(rotation); entity.setRotation(rotation);
entity.sendPlayer(session);
// async skin loading // async skin loading
if (session.getUpstream().isInitialized()) {
SkinUtils.requestAndHandleSkinAndCape(entity, session, skinAndCape -> entity.sendPlayer(session)); SkinUtils.requestAndHandleSkinAndCape(entity, session, skinAndCape -> entity.sendPlayer(session));
} }
}
} }

View file

@ -3,7 +3,6 @@ package org.geysermc.connector.utils;
import lombok.Getter; import lombok.Getter;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import java.awt.Color;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
@ -19,11 +18,11 @@ public class ProvidedSkin {
try { try {
for (int y = 0; y < image.getHeight(); y++) { for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) { for (int x = 0; x < image.getWidth(); x++) {
Color color = new Color(image.getRGB(x, y), true); int rgba = image.getRGB(x, y);
outputStream.write(color.getRed()); outputStream.write((rgba >> 16) & 0xFF); // Red
outputStream.write(color.getGreen()); outputStream.write((rgba >> 8) & 0xFF); // Green
outputStream.write(color.getBlue()); outputStream.write(rgba & 0xFF); // Blue
outputStream.write(color.getAlpha()); outputStream.write((rgba >> 24) & 0xFF); // Alpha
} }
} }
image.flush(); image.flush();

View file

@ -22,12 +22,12 @@ public class SkinProvider {
public static final boolean ALLOW_THIRD_PARTY_CAPES = ((GeyserConnector)Geyser.getConnector()).getConfig().isAllowThirdPartyCapes(); public static final boolean ALLOW_THIRD_PARTY_CAPES = ((GeyserConnector)Geyser.getConnector()).getConfig().isAllowThirdPartyCapes();
private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(ALLOW_THIRD_PARTY_CAPES ? 21 : 14); private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(ALLOW_THIRD_PARTY_CAPES ? 21 : 14);
public static final Skin EMPTY_SKIN = new Skin(-1, "");
public static final byte[] STEVE_SKIN = new ProvidedSkin("bedrock/skin/skin_steve.png").getSkin(); public static final byte[] STEVE_SKIN = new ProvidedSkin("bedrock/skin/skin_steve.png").getSkin();
public static final Skin EMPTY_SKIN = new Skin(-1, "steve", STEVE_SKIN);
private static Map<UUID, Skin> cachedSkins = new ConcurrentHashMap<>(); private static Map<UUID, Skin> cachedSkins = new ConcurrentHashMap<>();
private static Map<UUID, CompletableFuture<Skin>> requestedSkins = new ConcurrentHashMap<>(); private static Map<UUID, CompletableFuture<Skin>> requestedSkins = new ConcurrentHashMap<>();
public static final Cape EMPTY_CAPE = new Cape("", new byte[0], -1, true); public static final Cape EMPTY_CAPE = new Cape("", "no-cape", new byte[0], -1, true);
private static Map<String, Cape> cachedCapes = new ConcurrentHashMap<>(); private static Map<String, Cape> cachedCapes = new ConcurrentHashMap<>();
private static Map<String, CompletableFuture<Cape>> requestedCapes = new ConcurrentHashMap<>(); private static Map<String, CompletableFuture<Cape>> requestedCapes = new ConcurrentHashMap<>();
@ -149,8 +149,11 @@ public class SkinProvider {
cape = requestImage(capeUrl, true); cape = requestImage(capeUrl, true);
} catch (Exception ignored) {} // just ignore I guess } catch (Exception ignored) {} // just ignore I guess
String[] urlSection = capeUrl.split("/"); // A real url is expected at this stage
return new Cape( return new Cape(
capeUrl, capeUrl,
urlSection[urlSection.length - 1], // get the texture id and use it as cape id
cape.length > 0 ? cape : EMPTY_CAPE.getCapeData(), cape.length > 0 ? cape : EMPTY_CAPE.getCapeData(),
System.currentTimeMillis(), System.currentTimeMillis(),
cape.length == 0 cape.length == 0
@ -209,13 +212,14 @@ public class SkinProvider {
public static class Skin { public static class Skin {
private UUID skinOwner; private UUID skinOwner;
private String textureUrl; private String textureUrl;
private byte[] skinData = STEVE_SKIN; private byte[] skinData;
private long requestedOn; private long requestedOn;
private boolean updated; private boolean updated;
private Skin(long requestedOn, String textureUrl) { private Skin(long requestedOn, String textureUrl, byte[] skinData) {
this.requestedOn = requestedOn; this.requestedOn = requestedOn;
this.textureUrl = textureUrl; this.textureUrl = textureUrl;
this.skinData = skinData;
} }
} }
@ -223,6 +227,7 @@ public class SkinProvider {
@Getter @Getter
public static class Cape { public static class Cape {
private String textureUrl; private String textureUrl;
private String capeId;
private byte[] capeData; private byte[] capeData;
private long requestedOn; private long requestedOn;
private boolean failed; private boolean failed;

View file

@ -20,6 +20,7 @@ import java.util.function.Consumer;
public class SkinUtils { public class SkinUtils {
public static PlayerListPacket.Entry buildCachedEntry(GameProfile profile, long geyserId) { public static PlayerListPacket.Entry buildCachedEntry(GameProfile profile, long geyserId) {
GameProfileData data = GameProfileData.from(profile); GameProfileData data = GameProfileData.from(profile);
SkinProvider.Cape cape = SkinProvider.getCachedCape(data.getCapeUrl());
return buildEntryManually( return buildEntryManually(
profile.getId(), profile.getId(),
@ -27,7 +28,8 @@ public class SkinUtils {
geyserId, geyserId,
profile.getIdAsString(), profile.getIdAsString(),
SkinProvider.getCachedSkin(profile.getId()).getSkinData(), SkinProvider.getCachedSkin(profile.getId()).getSkinData(),
SkinProvider.getCachedCape(data.getCapeUrl()).getCapeData(), cape.getCapeId(),
cape.getCapeData(),
getLegacySkinGeometry("geometry.humanoid.custom" + (data.isAlex() ? "Slim" : "")), getLegacySkinGeometry("geometry.humanoid.custom" + (data.isAlex() ? "Slim" : "")),
"" ""
); );
@ -40,6 +42,7 @@ public class SkinUtils {
geyserId, geyserId,
profile.getIdAsString(), profile.getIdAsString(),
SkinProvider.STEVE_SKIN, SkinProvider.STEVE_SKIN,
SkinProvider.EMPTY_CAPE.getCapeId(),
SkinProvider.EMPTY_CAPE.getCapeData(), SkinProvider.EMPTY_CAPE.getCapeData(),
getLegacySkinGeometry("geometry.humanoid"), getLegacySkinGeometry("geometry.humanoid"),
"" ""
@ -47,18 +50,13 @@ public class SkinUtils {
} }
public static PlayerListPacket.Entry buildEntryManually(UUID uuid, String username, long geyserId, public static PlayerListPacket.Entry buildEntryManually(UUID uuid, String username, long geyserId,
String skinId, byte[] skinData, byte[] capeData, String skinId, byte[] skinData,
String capeId, byte[] capeData,
String geometryName, String geometryData) { String geometryName, String geometryData) {
if (skinData == null || skinData.length == 0) { SerializedSkin serializedSkin = SerializedSkin.of(
skinData = SkinProvider.EMPTY_SKIN.getSkinData(); skinId, geometryName, ImageData.of(skinData), Collections.emptyList(),
} ImageData.of(capeData), geometryData, "", true, false, false, capeId, uuid.toString()
);
if (capeData == null || capeData.length == 0) {
capeData = SkinProvider.EMPTY_CAPE.getCapeData();
}
SerializedSkin serializedSkin = SerializedSkin.of(skinId, geometryName, ImageData.of(64, 64, skinData),
Collections.emptyList(), ImageData.of(64, 32, capeData), geometryData, "", true, false, false, "", "");
PlayerListPacket.Entry entry = new PlayerListPacket.Entry(uuid); PlayerListPacket.Entry entry = new PlayerListPacket.Entry(uuid);
entry.setName(username); entry.setName(username);
@ -107,7 +105,7 @@ public class SkinUtils {
public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSession session, public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSession session,
Consumer<SkinProvider.SkinAndCape> skinAndCapeConsumer) { Consumer<SkinProvider.SkinAndCape> skinAndCapeConsumer) {
Geyser.getGeneralThreadPool().execute(() -> { Geyser.getGeneralThreadPool().execute(() -> {
SkinUtils.GameProfileData data = SkinUtils.GameProfileData.from(entity.getProfile()); GameProfileData data = GameProfileData.from(entity.getProfile());
SkinProvider.requestSkinAndCape(entity.getUuid(), data.getSkinUrl(), data.getCapeUrl()) SkinProvider.requestSkinAndCape(entity.getUuid(), data.getSkinUrl(), data.getCapeUrl())
.whenCompleteAsync((skinAndCape, throwable) -> { .whenCompleteAsync((skinAndCape, throwable) -> {
@ -126,12 +124,13 @@ public class SkinUtils {
entity.setLastSkinUpdate(skin.getRequestedOn()); entity.setLastSkinUpdate(skin.getRequestedOn());
if (session.getUpstream().isInitialized()) { if (session.getUpstream().isInitialized()) {
PlayerListPacket.Entry updatedEntry = SkinUtils.buildEntryManually( PlayerListPacket.Entry updatedEntry = buildEntryManually(
entity.getUuid(), entity.getUuid(),
entity.getUsername(), entity.getUsername(),
entity.getGeyserId(), entity.getGeyserId(),
entity.getUuid().toString(), entity.getUuid().toString(),
skin.getSkinData(), skin.getSkinData(),
cape.getCapeId(),
cape.getCapeData(), cape.getCapeData(),
getLegacySkinGeometry("geometry.humanoid.custom" + (data.isAlex() ? "Slim" : "")), getLegacySkinGeometry("geometry.humanoid.custom" + (data.isAlex() ? "Slim" : "")),
"" ""