Add skin parser used to populate metadata based on the image data

This commit is contained in:
Matthew Messinger 2018-08-25 22:40:07 -04:00
parent 1442dc51a3
commit e38856aee7
13 changed files with 186 additions and 57 deletions

View file

@ -74,6 +74,9 @@ dependencies {
compile('org.apache.httpcomponents:httpmime:4.3.2') {
transitive = false
}
compile('org.spongepowered:mixin:0.7.11-SNAPSHOT') {
transitive = false
}
}
manifest {
@ -117,6 +120,7 @@ shadowJar {
dependencies {
exclude dependency('deobf.com.mumfrey:liteloader:')
exclude dependency('deobf.org.ow2.asm:')
exclude dependency('org.spongepowered:mixin:')
exclude 'META-INF/**'
}

View file

@ -24,6 +24,7 @@ import com.voxelmodpack.hdskins.skins.LegacySkinServer;
import com.voxelmodpack.hdskins.skins.ServerType;
import com.voxelmodpack.hdskins.skins.SkinServer;
import com.voxelmodpack.hdskins.skins.ValhallaSkinServer;
import com.voxelmodpack.hdskins.util.ProfileTextureUtil;
import net.minecraft.client.Minecraft;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.client.network.NetworkPlayerInfo;
@ -79,6 +80,7 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
.build(CacheLoader.from(this::loadProfileData));
private List<ISkinModifier> skinModifiers = Lists.newArrayList();
private List<ISkinParser> skinParsers = Lists.newArrayList();
private SkinResourceManager resources = new SkinResourceManager();
// private ExecutorService executor = Executors.newCachedThreadPool();
@ -210,13 +212,8 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
FileUtils.deleteQuietly(new File(LiteLoader.getAssetsDirectory(), "hd"));
NetHandlerPlayClient connection = Minecraft.getMinecraft().getConnection();
if (connection != null) {
connection.getPlayerInfoMap().forEach(this::clearNetworkSkin);
}
skins.invalidateAll();
reloadSkins();
clearListeners = clearListeners.stream()
.filter(this::onSkinCacheCleared)
@ -224,7 +221,7 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
}
private void clearNetworkSkin(NetworkPlayerInfo player) {
((INetworkPlayerInfo) player).deleteTextures();
((INetworkPlayerInfo) player).reloadTextures();
}
private boolean onSkinCacheCleared(ISkinCacheClearListener callback) {
@ -240,6 +237,10 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
skinModifiers.add(modifier);
}
public void addSkinParser(ISkinParser parser) {
skinParsers.add(parser);
}
public ResourceLocation getConvertedSkin(ResourceLocation res) {
ResourceLocation loc = resources.getConvertedResource(res);
return loc == null ? res : loc;
@ -251,6 +252,30 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
}
}
public void reloadSkins() {
NetHandlerPlayClient playClient = Minecraft.getMinecraft().getConnection();
if (playClient != null) {
playClient.getPlayerInfoMap().forEach(this::clearNetworkSkin);
}
}
public void parseSkin(Type type, ResourceLocation resource, MinecraftProfileTexture texture) {
// grab the metadata object via reflection. Object is live.
Map<String, String> metadata = ProfileTextureUtil.getMetadata(texture);
boolean wasNull = metadata == null;
if (wasNull) {
metadata = new HashMap<>();
}
for (ISkinParser parser : skinParsers) {
parser.parse(type, resource, metadata);
}
if (wasNull && !metadata.isEmpty()) {
ProfileTextureUtil.setMetadata(texture, metadata);
}
}
@Override
public void onResourceManagerReload(IResourceManager resourceManager) {
this.resources.onResourceManagerReload(resourceManager);

View file

@ -11,5 +11,7 @@ public interface INetworkPlayerInfo {
Optional<MinecraftProfileTexture> getProfileTexture(MinecraftProfileTexture.Type type);
void deleteTextures();
void reloadTextures();
void setSkinType(String type);
}

View file

@ -0,0 +1,23 @@
package com.voxelmodpack.hdskins;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
import net.minecraft.util.ResourceLocation;
import java.util.Map;
/**
* SkinParser is used to parse metadata (e.g. trigger pixels) from a texture.
*/
@FunctionalInterface
public interface ISkinParser {
/**
* Parses the texture for metadata. Any discovered data should be put into
* the metadata Map parameter.
*
* @param type The texture type
* @param resource The texture location
* @param metadata The metadata previously parsed
*/
void parse(Type type, ResourceLocation resource, Map<String, String> metadata);
}

View file

@ -0,0 +1,39 @@
package com.voxelmodpack.hdskins.mixin;
import com.google.common.util.concurrent.Runnables;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
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.resources.SkinManager;
import net.minecraft.util.ResourceLocation;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.concurrent.CompletableFuture;
@Mixin(targets = "net.minecraft.client.network.NetworkPlayerInfo$1")
public abstract class MixinNetworkPlayerInfo$1 implements SkinManager.SkinAvailableCallback {
@Shadow(aliases = {"this$0", "field_177224_a", "a"}) @Final private NetworkPlayerInfo player;
@Inject(method = "skinAvailable", at = @At(value = "HEAD"))
private void skinAvailable(MinecraftProfileTexture.Type typeIn, ResourceLocation location, MinecraftProfileTexture profileTexture,
CallbackInfo ci) {
CompletableFuture.runAsync(Runnables.doNothing())
.thenAcceptAsync((v) -> {
// schedule parsing next tick, texture may not be uploaded at this point
HDSkinManager.INSTANCE.parseSkin(typeIn, location, profileTexture);
// re-set the skin-type because vanilla has already set it
String model = profileTexture.getMetadata("model");
((INetworkPlayerInfo) player).setSkinType(model != null ? model : "default");
}, Minecraft.getMinecraft()::addScheduledTask);
}
}

View file

@ -1,5 +1,6 @@
package com.voxelmodpack.hdskins.mixin;
import com.google.common.util.concurrent.Runnables;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
@ -7,7 +8,6 @@ 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.renderer.texture.TextureManager;
import net.minecraft.util.ResourceLocation;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@ -20,16 +20,15 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import java.util.concurrent.CompletableFuture;
@Mixin(NetworkPlayerInfo.class)
public abstract class MixinPlayerInfo implements INetworkPlayerInfo {
public abstract class MixinNetworkPlayerInfo implements INetworkPlayerInfo {
private Map<Type, ResourceLocation> customTextures = new HashMap<>();
private Map<Type, MinecraftProfileTexture> customProfiles = new HashMap<>();
@Shadow @Final private GameProfile gameProfile;
@Shadow Map<Type, ResourceLocation> playerTextures;
@Shadow private boolean playerTexturesLoaded;
@Shadow private String skinType;
@ -52,7 +51,6 @@ public abstract class MixinPlayerInfo implements INetworkPlayerInfo {
@Redirect(method = "getSkinType()Ljava/lang/String;",
at = @At(value = "FIELD", target = "Lnet/minecraft/client/network/NetworkPlayerInfo;skinType:Ljava/lang/String;"))
private String getTextureModel(NetworkPlayerInfo self) {
return getProfileTexture(Type.SKIN).map(profile -> {
String model = profile.getMetadata("model");
@ -71,6 +69,11 @@ public abstract class MixinPlayerInfo implements INetworkPlayerInfo {
HDSkinManager.INSTANCE.loadProfileTextures(this.gameProfile)
.thenAcceptAsync(m -> m.forEach((type, profile) -> {
HDSkinManager.INSTANCE.loadTexture(type, profile, (typeIn, location, profileTexture) -> {
CompletableFuture.runAsync(Runnables.doNothing())
// schedule parsing next tick
.thenAcceptAsync((v) -> {
HDSkinManager.INSTANCE.parseSkin(typeIn, location, profileTexture);
}, Minecraft.getMinecraft()::addScheduledTask);
customTextures.put(type, location);
customProfiles.put(type, profileTexture);
});
@ -88,16 +91,14 @@ public abstract class MixinPlayerInfo implements INetworkPlayerInfo {
}
@Override
public void deleteTextures() {
TextureManager tm = Minecraft.getMinecraft().getTextureManager();
Stream.concat(this.customTextures.values().stream(), this.playerTextures.values().stream())
.forEach(tm::deleteTexture);
this.customTextures.clear();
this.customProfiles.clear();
this.playerTextures.clear();
this.skinType = null;
public void reloadTextures() {
synchronized (this) {
this.playerTexturesLoaded = false;
}
}
@Override
public void setSkinType(String skinType) {
this.skinType = skinType;
}
}

View file

@ -0,0 +1,29 @@
package com.voxelmodpack.hdskins.util;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import org.apache.commons.lang3.reflect.FieldUtils;
import java.lang.reflect.Field;
import java.util.Map;
public class ProfileTextureUtil {
private static Field metadata = FieldUtils.getDeclaredField(MinecraftProfileTexture.class, "metadata", true);
@SuppressWarnings("unchecked")
public static Map<String, String> getMetadata(MinecraftProfileTexture texture) {
try {
return (Map<String, String>) FieldUtils.readField(metadata, texture);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to read metadata field", e);
}
}
public static void setMetadata(MinecraftProfileTexture texture, Map<String, String> meta) {
try {
FieldUtils.writeField(metadata, texture, meta);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to write metadata field", e);
}
}
}

View file

@ -7,7 +7,8 @@
"MixinMinecraft",
"MixinGuiMainMenu",
"MixinImageBufferDownload",
"MixinPlayerInfo",
"MixinNetworkPlayerInfo",
"MixinNetworkPlayerInfo$1",
"MixinSkullRenderer"
]
}

View file

@ -74,6 +74,7 @@ public class MineLittlePony {
// manager.setSkinUrl(SKIN_SERVER_URL);
// manager.setGatewayURL(GATEWAY_URL);
manager.addSkinModifier(new PonySkinModifier());
manager.addSkinParser(new PonySkinParser());
// logger.info("Set MineLP skin server URL.");
manager.addClearListener(ponyManager);

View file

@ -6,6 +6,7 @@ import com.minelittlepony.settings.SensibleConfig;
import com.mumfrey.liteloader.modconfig.ConfigStrategy;
import com.mumfrey.liteloader.modconfig.Exposable;
import com.mumfrey.liteloader.modconfig.ExposableOptions;
import com.voxelmodpack.hdskins.HDSkinManager;
/**
* Storage container for MineLP client settings.
@ -64,6 +65,7 @@ public class PonyConfig extends SensibleConfig implements Exposable {
*/
public void setPonyLevel(PonyLevel ponylevel) {
this.ponylevel = ponylevel;
HDSkinManager.INSTANCE.reloadSkins();
}
public float getGlobalScaleFactor() {

View file

@ -0,0 +1,25 @@
package com.minelittlepony;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.voxelmodpack.hdskins.ISkinParser;
import net.minecraft.util.ResourceLocation;
import java.util.Map;
public class PonySkinParser implements ISkinParser {
@Override
public void parse(MinecraftProfileTexture.Type type, ResourceLocation resource, Map<String, String> metadata) {
if (type == MinecraftProfileTexture.Type.SKIN) {
boolean slim = "slim".equals(metadata.get("model"));
// TODO use proper model metadata system
metadata.put("model", MineLittlePony.getInstance().getManager()
.getPony(resource, slim)
.getRace(false)
.getModel()
.getId(slim));
}
}
}

View file

@ -1,22 +0,0 @@
package com.minelittlepony.mixin;
import com.minelittlepony.MineLittlePony;
import com.voxelmodpack.hdskins.INetworkPlayerInfo;
import net.minecraft.client.network.NetworkPlayerInfo;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(NetworkPlayerInfo.class)
public abstract class MixinNetworkPlayerInfo implements INetworkPlayerInfo {
@Inject(method = "getSkinType()Ljava/lang/String;", at = @At("RETURN"), cancellable = true)
private void getSkinType(CallbackInfoReturnable<String> info) {
info.setReturnValue(MineLittlePony.getInstance().getManager()
.getPony((NetworkPlayerInfo) (Object) this)
.getRace(false)
.getModel()
.getId("slim".equals(info.getReturnValue())));
}
}

View file

@ -6,7 +6,6 @@
"compatibilityLevel": "JAVA_8",
"mixins": [
"MixinThreadDownloadImageData",
"MixinNetworkPlayerInfo",
"MixinRenderItem",
"MixinItemRenderer",
"MixinRenderManager",