Slightly rewrite texture loading so it is better adaptable.

Also exposes metadata more
This commit is contained in:
Matthew Messinger 2018-08-24 21:55:45 -04:00
parent 72324feaf3
commit 981cd002b3
8 changed files with 183 additions and 193 deletions

View file

@ -19,18 +19,19 @@ import com.mumfrey.liteloader.core.LiteLoader;
import com.mumfrey.liteloader.util.log.LiteLoaderLogger; import com.mumfrey.liteloader.util.log.LiteLoaderLogger;
import com.voxelmodpack.hdskins.gui.GuiSkins; import com.voxelmodpack.hdskins.gui.GuiSkins;
import com.voxelmodpack.hdskins.resource.SkinResourceManager; import com.voxelmodpack.hdskins.resource.SkinResourceManager;
import com.voxelmodpack.hdskins.skins.AsyncCacheLoader;
import com.voxelmodpack.hdskins.skins.BethlehemSkinServer; import com.voxelmodpack.hdskins.skins.BethlehemSkinServer;
import com.voxelmodpack.hdskins.skins.LegacySkinServer; import com.voxelmodpack.hdskins.skins.LegacySkinServer;
import com.voxelmodpack.hdskins.skins.ServerType; import com.voxelmodpack.hdskins.skins.ServerType;
import com.voxelmodpack.hdskins.skins.SkinServer; import com.voxelmodpack.hdskins.skins.SkinServer;
import com.voxelmodpack.hdskins.skins.ValhallaSkinServer; import com.voxelmodpack.hdskins.skins.ValhallaSkinServer;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.client.network.NetworkPlayerInfo;
import net.minecraft.client.renderer.texture.ITextureObject;
import net.minecraft.client.resources.DefaultPlayerSkin; import net.minecraft.client.resources.DefaultPlayerSkin;
import net.minecraft.client.resources.IResourceManager; import net.minecraft.client.resources.IResourceManager;
import net.minecraft.client.resources.IResourceManagerReloadListener; import net.minecraft.client.resources.IResourceManagerReloadListener;
import net.minecraft.client.resources.SkinManager.SkinAvailableCallback; import net.minecraft.client.resources.SkinManager;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
@ -46,15 +47,17 @@ import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Base64; import java.util.Base64;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.annotation.Nullable;
public final class HDSkinManager implements IResourceManagerReloadListener { public final class HDSkinManager implements IResourceManagerReloadListener {
@ -64,24 +67,16 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
public static final ExecutorService skinDownloadExecutor = Executors.newFixedThreadPool(8); public static final ExecutorService skinDownloadExecutor = Executors.newFixedThreadPool(8);
public static final CloseableHttpClient httpClient = HttpClients.createSystem(); public static final CloseableHttpClient httpClient = HttpClients.createSystem();
private static final ResourceLocation LOADING = new ResourceLocation("LOADING");
public static final HDSkinManager INSTANCE = new HDSkinManager(); public static final HDSkinManager INSTANCE = new HDSkinManager();
private boolean enabled = true;
private List<ISkinCacheClearListener> clearListeners = Lists.newArrayList(); private List<ISkinCacheClearListener> clearListeners = Lists.newArrayList();
private BiMap<String, Class<? extends SkinServer>> skinServerTypes = HashBiMap.create(2); private BiMap<String, Class<? extends SkinServer>> skinServerTypes = HashBiMap.create(2);
private List<SkinServer> skinServers = Lists.newArrayList(); private List<SkinServer> skinServers = Lists.newArrayList();
private Map<UUID, Map<Type, ResourceLocation>> skinCache = Maps.newHashMap(); private LoadingCache<GameProfile, CompletableFuture<Map<Type, MinecraftProfileTexture>>> skins = CacheBuilder.newBuilder()
.expireAfterAccess(15, TimeUnit.SECONDS)
private LoadingCache<GameProfile, Map<Type, MinecraftProfileTexture>> skins = CacheBuilder.newBuilder() .build(CacheLoader.from(this::loadProfileData));
.initialCapacity(20)
.maximumSize(100)
.expireAfterWrite(4, TimeUnit.HOURS)
.build(AsyncCacheLoader.create(CacheLoader.from(this::loadProfileData), Collections.emptyMap(), skinDownloadExecutor));
private List<ISkinModifier> skinModifiers = Lists.newArrayList(); private List<ISkinModifier> skinModifiers = Lists.newArrayList();
@ -107,19 +102,30 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
return skinsGuiFunc.apply(ImmutableList.copyOf(this.skinServers)); return skinsGuiFunc.apply(ImmutableList.copyOf(this.skinServers));
} }
public Optional<ResourceLocation> getSkinLocation(GameProfile profile1, final Type type, boolean loadIfAbsent) { private CompletableFuture<Map<Type, MinecraftProfileTexture>> loadProfileData(GameProfile profile) {
if (!enabled) {
return Optional.empty();
}
ResourceLocation skin = this.resources.getPlayerTexture(profile1, type); return CompletableFuture.supplyAsync(() -> {
if (skin != null) { Map<Type, MinecraftProfileTexture> textureMap = Maps.newEnumMap(Type.class);
return Optional.of(skin);
}
for (SkinServer server : skinServers) {
try {
server.loadProfileData(profile).getTextures().forEach(textureMap::putIfAbsent);
if (textureMap.size() == Type.values().length) {
break;
}
} catch (IOException e) {
logger.trace(e);
}
}
return textureMap;
}, skinDownloadExecutor);
}
public CompletableFuture<Map<Type, MinecraftProfileTexture>> loadProfileTextures(GameProfile profile) {
// try to recreate a broken gameprofile // try to recreate a broken gameprofile
// happens when server sends a random profile with skin and displayname // happens when server sends a random profile with skin and displayname
Property textures = Iterables.getFirst(profile1.getProperties().get("textures"), null); Property textures = Iterables.getFirst(profile.getProperties().get("textures"), null);
if (textures != null) { if (textures != null) {
String json = new String(Base64.getDecoder().decode(textures.getValue()), StandardCharsets.UTF_8); String json = new String(Base64.getDecoder().decode(textures.getValue()), StandardCharsets.UTF_8);
MinecraftTexturesPayload texturePayload = SkinServer.gson.fromJson(json, MinecraftTexturesPayload.class); MinecraftTexturesPayload texturePayload = SkinServer.gson.fromJson(json, MinecraftTexturesPayload.class);
@ -129,86 +135,55 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
UUID uuid = texturePayload.getProfileId(); UUID uuid = texturePayload.getProfileId();
// uuid is required // uuid is required
if (uuid != null) { if (uuid != null) {
profile1 = new GameProfile(uuid, name); profile = new GameProfile(uuid, name);
} }
// probably uses this texture for a reason. Don't mess with it. // probably uses this texture for a reason. Don't mess with it.
if (!texturePayload.getTextures().isEmpty() && texturePayload.getProfileId() == null) { if (!texturePayload.getTextures().isEmpty() && texturePayload.getProfileId() == null) {
return Optional.empty(); return CompletableFuture.completedFuture(Collections.emptyMap());
} }
} }
} }
final GameProfile profile = profile1; return skins.getUnchecked(profile);
// cannot get texture without id!
if (profile.getId() == null) {
return Optional.empty();
}
if (!this.skinCache.containsKey(profile.getId())) {
this.skinCache.put(profile.getId(), Maps.newHashMap());
}
skin = this.skinCache.get(profile.getId()).get(type);
if (skin == null) {
if (loadIfAbsent && getProfileData(profile).containsKey(type)) {
skinCache.get(profile.getId()).put(type, LOADING);
loadTexture(profile, type, (t, loc, tex) -> skinCache.get(profile.getId()).put(t, loc));
}
return Optional.empty();
}
return skin == LOADING ? Optional.empty() : Optional.of(skin);
} }
private void loadTexture(GameProfile profile, final Type type, final SkinAvailableCallback callback) { public ResourceLocation loadTexture(Type type, MinecraftProfileTexture texture, @Nullable SkinManager.SkinAvailableCallback callback) {
if (profile.getId() == null) {
return;
}
String skinDir = type.toString().toLowerCase() + "s/"; String skinDir = type.toString().toLowerCase() + "s/";
final MinecraftProfileTexture texture = getProfileData(profile).get(type);
final ResourceLocation resource = new ResourceLocation("hdskins", skinDir + texture.getHash()); final ResourceLocation resource = new ResourceLocation("hdskins", skinDir + texture.getHash());
ITextureObject texObj = Minecraft.getMinecraft().getTextureManager().getTexture(resource);
ISkinAvailableCallback buffs = new ImageBufferDownloadHD(type, () -> { //noinspection ConstantConditions
callback.skinAvailable(type, resource, texture); if (texObj != null) {
}); if (callback != null) {
callback.skinAvailable(type, resource, texture);
// schedule texture loading on the main thread.
TextureLoader.loadTexture(resource, new ThreadDownloadImageETag(
new File(LiteLoader.getAssetsDirectory(), "hd/" + skinDir + texture.getHash().substring(0, 2) + "/" + texture.getHash()),
texture.getUrl(),
DefaultPlayerSkin.getDefaultSkinLegacy(),
buffs));
}
private Map<Type, MinecraftProfileTexture> loadProfileData(GameProfile profile) {
Map<Type, MinecraftProfileTexture> textures = Maps.newEnumMap(Type.class);
for (SkinServer server : skinServers) {
try {
server.loadProfileData(profile).getTextures().forEach(textures::putIfAbsent);
if (textures.size() == Type.values().length) {
break;
}
} catch (IOException e) {
logger.trace(e);
} }
} else {
// schedule texture loading on the main thread.
TextureLoader.loadTexture(resource, new ThreadDownloadImageETag(
new File(LiteLoader.getAssetsDirectory(), "hd/" + skinDir + texture.getHash().substring(0, 2) + "/" + texture.getHash()),
texture.getUrl(),
DefaultPlayerSkin.getDefaultSkinLegacy(),
new ImageBufferDownloadHD(type, () -> {
if (callback != null) {
callback.skinAvailable(type, resource, texture);
}
})));
} }
return textures; return resource;
} }
public Map<Type, MinecraftProfileTexture> getProfileData(GameProfile profile) { public Map<Type, ResourceLocation> getTextures(GameProfile profile) {
boolean was = !skins.asMap().containsKey(profile);
Map<Type, MinecraftProfileTexture> textures = skins.getUnchecked(profile); Map<Type, ResourceLocation> map = new HashMap<>();
// This is the initial value. Refreshing will load it asynchronously. for (Map.Entry<Type, MinecraftProfileTexture> e : loadProfileTextures(profile).getNow(Collections.emptyMap()).entrySet()) {
if (was) { map.put(e.getKey(), loadTexture(e.getKey(), e.getValue(), null));
skins.refresh(profile);
} }
return textures; return map;
} }
public void addSkinServerType(Class<? extends SkinServer> type) { private void addSkinServerType(Class<? extends SkinServer> type) {
Preconditions.checkArgument(!type.isInterface(), "type cannot be an interface"); Preconditions.checkArgument(!type.isInterface(), "type cannot be an interface");
Preconditions.checkArgument(!Modifier.isAbstract(type.getModifiers()), "type cannot be abstract"); Preconditions.checkArgument(!Modifier.isAbstract(type.getModifiers()), "type cannot be abstract");
ServerType st = type.getAnnotation(ServerType.class); ServerType st = type.getAnnotation(ServerType.class);
@ -222,14 +197,10 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
return this.skinServerTypes.get(type); return this.skinServerTypes.get(type);
} }
public void addSkinServer(SkinServer skinServer) { void addSkinServer(SkinServer skinServer) {
this.skinServers.add(skinServer); this.skinServers.add(skinServer);
} }
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public void addClearListener(ISkinCacheClearListener listener) { public void addClearListener(ISkinCacheClearListener listener) {
clearListeners.add(listener); clearListeners.add(listener);
} }
@ -237,18 +208,14 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
public void clearSkinCache() { public void clearSkinCache() {
LiteLoaderLogger.info("Clearing local player skin cache"); LiteLoaderLogger.info("Clearing local player skin cache");
try { FileUtils.deleteQuietly(new File(LiteLoader.getAssetsDirectory(), "hd"));
FileUtils.deleteDirectory(new File(LiteLoader.getAssetsDirectory(), "skins"));
FileUtils.deleteDirectory(new File(LiteLoader.getAssetsDirectory(), "hd")); NetHandlerPlayClient connection = Minecraft.getMinecraft().getConnection();
} catch (IOException e) {
e.printStackTrace(); if (connection != null) {
connection.getPlayerInfoMap().forEach(this::clearNetworkSkin);
} }
TextureManager textures = Minecraft.getMinecraft().getTextureManager();
skinCache.values().stream()
.flatMap(m -> m.values().stream())
.forEach(textures::deleteTexture);
skinCache.clear();
skins.invalidateAll(); skins.invalidateAll();
clearListeners = clearListeners.stream() clearListeners = clearListeners.stream()
@ -256,6 +223,10 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private void clearNetworkSkin(NetworkPlayerInfo player) {
((INetworkPlayerInfo) player).deleteTextures();
}
private boolean onSkinCacheCleared(ISkinCacheClearListener callback) { private boolean onSkinCacheCleared(ISkinCacheClearListener callback) {
try { try {
return callback.onSkinCacheCleared(); return callback.onSkinCacheCleared();

View file

@ -0,0 +1,15 @@
package com.voxelmodpack.hdskins;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import net.minecraft.util.ResourceLocation;
import java.util.Optional;
public interface INetworkPlayerInfo {
Optional<ResourceLocation> getResourceLocation(MinecraftProfileTexture.Type type);
Optional<MinecraftProfileTexture> getProfileTexture(MinecraftProfileTexture.Type type);
void deleteTextures();
}

View file

@ -4,65 +4,92 @@ import com.mojang.authlib.GameProfile;
import com.mojang.authlib.minecraft.MinecraftProfileTexture; import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type; import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
import com.voxelmodpack.hdskins.HDSkinManager; import com.voxelmodpack.hdskins.HDSkinManager;
import com.voxelmodpack.hdskins.INetworkPlayerInfo;
import net.minecraft.client.Minecraft;
import net.minecraft.client.network.NetworkPlayerInfo; import net.minecraft.client.network.NetworkPlayerInfo;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
@Mixin(NetworkPlayerInfo.class) @Mixin(NetworkPlayerInfo.class)
public abstract class MixinPlayerInfo { public abstract class MixinPlayerInfo implements INetworkPlayerInfo {
@Shadow
public abstract GameProfile getGameProfile();
@Inject( private Map<Type, ResourceLocation> customTextures = new HashMap<>();
method = "getLocationSkin", private Map<Type, MinecraftProfileTexture> customProfiles = new HashMap<>();
cancellable = true,
at = @At("RETURN")) @Shadow @Final private GameProfile gameProfile;
private void getLocationSkin(CallbackInfoReturnable<ResourceLocation> ci) {
getTextureLocation(ci, Type.SKIN); @Shadow public abstract String getSkinType();
@SuppressWarnings("InvalidMemberReference") // mc-dev bug?
@Redirect(
method = {
"getLocationSkin",
"getLocationCape",
"getLocationElytra"
}, at = @At(value = "INVOKE", target = "Ljava/util/Map;get(Ljava/lang/Object;)Ljava/lang/Object;"))
// synthetic
private Object getSkin(Map<Type, ResourceLocation> playerTextures, Object key) {
return getSkin(playerTextures, (Type) key);
} }
@Inject( // with generics
method = "getLocationCape", private ResourceLocation getSkin(Map<Type, ResourceLocation> playerTextures, Type type) {
cancellable = true, return getResourceLocation(type).orElseGet(() -> playerTextures.get(type));
at = @At("RETURN"))
private void getLocationCape(CallbackInfoReturnable<ResourceLocation> ci) {
getTextureLocation(ci, Type.CAPE);
} }
@Inject( @Inject(method = "getSkinType", at = @At("RETURN"), cancellable = true)
method = "getLocationElytra", private void getTextureModel(CallbackInfoReturnable<String> cir) {
cancellable = true, getProfileTexture(Type.SKIN).ifPresent(profile -> {
at = @At("RETURN")) String model = profile.getMetadata("model");
private void getLocationElytra(CallbackInfoReturnable<ResourceLocation> ci) { cir.setReturnValue(model != null ? model : "default");
getTextureLocation(ci, Type.ELYTRA); });
} }
private void getTextureLocation(CallbackInfoReturnable<ResourceLocation> ci, Type type) { @Inject(method = "loadPlayerTextures",
Optional<ResourceLocation> texture = HDSkinManager.INSTANCE.getSkinLocation(getGameProfile(), type, true); at = @At(value = "INVOKE",
texture.ifPresent(ci::setReturnValue); target = "Lnet/minecraft/client/resources/SkinManager;loadProfileTextures("
+ "Lcom/mojang/authlib/GameProfile;"
+ "Lnet/minecraft/client/resources/SkinManager$SkinAvailableCallback;"
+ "Z)V",
shift = At.Shift.BEFORE))
private void onLoadTexture(CallbackInfo ci) {
HDSkinManager.INSTANCE.loadProfileTextures(this.gameProfile)
.thenAcceptAsync(m -> m.forEach((type, profile) -> {
HDSkinManager.INSTANCE.loadTexture(type, profile, (typeIn, location, profileTexture) -> {
customTextures.put(type, location);
customProfiles.put(type, profileTexture);
});
}), Minecraft.getMinecraft()::addScheduledTask);
} }
@Inject( @Override
method = "getSkinType", public Optional<ResourceLocation> getResourceLocation(Type type) {
cancellable = true, return Optional.ofNullable(this.customTextures.get(type));
at = @At("RETURN")) }
private void getSkinType(CallbackInfoReturnable<String> ci) {
MinecraftProfileTexture skin = HDSkinManager.INSTANCE.getProfileData(getGameProfile()).get(Type.SKIN);
if (skin != null) {
String type = skin.getMetadata("model");
if (type == null)
type = "default";
String type1 = type;
Optional<ResourceLocation> texture = HDSkinManager.INSTANCE.getSkinLocation(getGameProfile(), Type.SKIN, false);
texture.ifPresent((res) -> ci.setReturnValue(type1)); @Override
} public Optional<MinecraftProfileTexture> getProfileTexture(Type type) {
return Optional.ofNullable(this.customProfiles.get(type));
}
@Override
public void deleteTextures() {
TextureManager tm = Minecraft.getMinecraft().getTextureManager();
this.customTextures.values().forEach(tm::deleteTexture);
this.customTextures.clear();
this.customProfiles.clear();
} }
} }

View file

@ -13,7 +13,6 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.Redirect;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Optional;
@Mixin(TileEntitySkullRenderer.class) @Mixin(TileEntitySkullRenderer.class)
public abstract class MixinSkullRenderer extends TileEntitySpecialRenderer<TileEntitySkull> { public abstract class MixinSkullRenderer extends TileEntitySpecialRenderer<TileEntitySkull> {
@ -24,16 +23,15 @@ public abstract class MixinSkullRenderer extends TileEntitySpecialRenderer<TileE
value = "INVOKE", value = "INVOKE",
target = "Lnet/minecraft/client/renderer/tileentity/TileEntitySkullRenderer;bindTexture(Lnet/minecraft/util/ResourceLocation;)V", target = "Lnet/minecraft/client/renderer/tileentity/TileEntitySkullRenderer;bindTexture(Lnet/minecraft/util/ResourceLocation;)V",
ordinal = 4)) ordinal = 4))
private void onBindTexture(TileEntitySkullRenderer tesr, ResourceLocation rl, float x, float y, float z, EnumFacing facing, float rotation, int meta, private void onBindTexture(TileEntitySkullRenderer tesr, ResourceLocation rl, float x, float y, float z, EnumFacing facing, float rotation,
@Nullable GameProfile profile, int p_180543_8_, float ticks) { int meta,
@Nullable GameProfile profile, int p_180543_8_, float ticks) {
if (profile != null) { if (profile != null) {
Optional<ResourceLocation> skin = HDSkinManager.INSTANCE.getSkinLocation(profile, Type.SKIN, true); ResourceLocation skin = HDSkinManager.INSTANCE.getTextures(profile).get(Type.SKIN);
if (skin.isPresent()) if (skin != null) {
// rebind rl = skin;
bindTexture(skin.get()); }
else }
bindTexture(rl); bindTexture(rl);
} else
bindTexture(rl);
} }
} }

View file

@ -6,9 +6,7 @@ import com.google.gson.Gson;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import com.minelittlepony.pony.data.Pony; import com.minelittlepony.pony.data.Pony;
import com.minelittlepony.pony.data.PonyLevel; import com.minelittlepony.pony.data.PonyLevel;
import com.voxelmodpack.hdskins.HDSkinManager;
import com.voxelmodpack.hdskins.ISkinCacheClearListener; import com.voxelmodpack.hdskins.ISkinCacheClearListener;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.AbstractClientPlayer; import net.minecraft.client.entity.AbstractClientPlayer;
import net.minecraft.client.network.NetworkPlayerInfo; import net.minecraft.client.network.NetworkPlayerInfo;
@ -83,8 +81,6 @@ public class PonyManager implements IResourceManagerReloadListener, ISkinCacheCl
} }
public Pony getPony(NetworkPlayerInfo playerInfo) { public Pony getPony(NetworkPlayerInfo playerInfo) {
// force load HDSkins if they're not available
HDSkinManager.INSTANCE.getProfileData(playerInfo.getGameProfile());
ResourceLocation skin = playerInfo.getLocationSkin(); ResourceLocation skin = playerInfo.getLocationSkin();
UUID uuid = playerInfo.getGameProfile().getId(); UUID uuid = playerInfo.getGameProfile().getId();

View file

@ -1,17 +0,0 @@
package com.minelittlepony.ducks;
import net.minecraft.client.network.NetworkPlayerInfo;
public interface IPlayerInfo {
/**
* Returns true if the vanilla skin (the one returned by NetworkPlayerInfo.getSkinLocation) uses the ALEX model type.
*/
boolean usesSlimArms();
/**
* Quick cast back to the original type.
*/
default NetworkPlayerInfo unwrap() {
return (NetworkPlayerInfo)this;
}
}

View file

@ -1,41 +1,42 @@
package com.minelittlepony.mixin; package com.minelittlepony.mixin;
import com.minelittlepony.MineLittlePony;
import com.minelittlepony.PonyManager;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
import com.voxelmodpack.hdskins.INetworkPlayerInfo;
import net.minecraft.client.network.NetworkPlayerInfo;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import com.minelittlepony.MineLittlePony; @Mixin(value = NetworkPlayerInfo.class, priority = 999)
import com.minelittlepony.PonyManager; public abstract class MixinNetworkPlayerInfo implements INetworkPlayerInfo {
import com.minelittlepony.ducks.IPlayerInfo;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
import com.voxelmodpack.hdskins.HDSkinManager;
import net.minecraft.client.network.NetworkPlayerInfo; @Shadow private String skinType;
@Mixin(NetworkPlayerInfo.class) @Shadow @Final private GameProfile gameProfile;
public abstract class MixinNetworkPlayerInfo implements IPlayerInfo {
@Shadow
private String skinType;
@Inject(method = "getSkinType()Ljava/lang/String;", at = @At("RETURN"), cancellable = true) @Inject(method = "getSkinType()Ljava/lang/String;", at = @At("RETURN"), cancellable = true)
private void getSkinType(CallbackInfoReturnable<String> info) { private void getSkinType(CallbackInfoReturnable<String> info) {
info.setReturnValue(MineLittlePony.getInstance().getManager().getPony(unwrap()).getRace(false).getModel().getId(usesSlimArms())); info.setReturnValue(MineLittlePony.getInstance().getManager()
.getPony((NetworkPlayerInfo) (Object) this)
.getRace(false)
.getModel()
.getId(usesSlimArms()));
} }
@Override private boolean usesSlimArms() {
public boolean usesSlimArms() {
if (skinType == null) { if (skinType == null) {
MinecraftProfileTexture skin = HDSkinManager.INSTANCE.getProfileData(unwrap().getGameProfile()).get(Type.SKIN);
if (skin != null) { return getProfileTexture(Type.SKIN)
return "slim".equals(skin.getMetadata("model")); .map(profile -> profile.getMetadata("model"))
} .filter("slim"::equals)
.isPresent() || PonyManager.isSlimSkin(this.gameProfile.getId());
return PonyManager.isSlimSkin(unwrap().getGameProfile().getId());
} }
return "slim".equals(skinType); return "slim".equals(skinType);

View file

@ -34,7 +34,6 @@ import net.minecraft.util.EnumHandSide;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import java.util.Map; import java.util.Map;
import java.util.Optional;
public class RenderPonyPlayer extends RenderPlayer implements IRenderPony<AbstractClientPlayer> { public class RenderPonyPlayer extends RenderPlayer implements IRenderPony<AbstractClientPlayer> {
@ -58,9 +57,9 @@ public class RenderPonyPlayer extends RenderPlayer implements IRenderPony<Abstra
if (profile != null) { if (profile != null) {
deadMau5.setVisible("deadmau5".equals(profile.getName())); deadMau5.setVisible("deadmau5".equals(profile.getName()));
Optional<ResourceLocation> skin = HDSkinManager.INSTANCE.getSkinLocation(profile, Type.SKIN, true); ResourceLocation skin = HDSkinManager.INSTANCE.getTextures(profile).get(Type.SKIN);
if (skin.isPresent()) { if (skin != null) {
return skin.get(); return skin;
} }
Minecraft minecraft = Minecraft.getMinecraft(); Minecraft minecraft = Minecraft.getMinecraft();