Revert "Cleanup sourcesets. hdskins is not here."

This reverts commit 2f9d69b386.
This commit is contained in:
Sollace 2019-03-25 17:33:58 +02:00
parent 2f9d69b386
commit 3b1f346426
87 changed files with 5502 additions and 1 deletions

View file

@ -59,11 +59,19 @@ sourceSets {
compileClasspath += main.compileClasspath
}
hdskins {
// HDSkins.
// TODO: Move to a separate project
compileClasspath += main.compileClasspath
compileClasspath += common.output
ext.refMap = 'hdskins.mixin.refmap.json'
}
client {
// Client-only code
compileClasspath += main.compileClasspath
compileClasspath += main.output
compileClasspath += common.output
compileClasspath += hdskins.output
ext.refMap = 'minelp.mixin.refmap.json'
}
main {
@ -79,13 +87,24 @@ sourceSets {
compileClasspath += main.output
compileClasspath += client.output
}
hdskinslitemod {
compileClasspath += main.compileClasspath
compileClasspath += litemod.output
compileClasspath += hdskins.output
}
fml {
compileClasspath += main.compileClasspath
compileClasspath += main.output
compileClasspath += client.output
}
hdskinsfml {
compileClasspath += main.compileClasspath
compileClasspath += litemod.output
compileClasspath += hdskins.output
}
}
minecraft {
@ -103,6 +122,7 @@ minecraft {
mods {
minelittlepony {
source sourceSets.common
source sourceSets.hdskins
source sourceSets.client
source sourceSets.main
@ -127,6 +147,10 @@ repositories {
dependencies {
minecraft 'net.minecraftforge:forge:1.13.2-25.0.90'
// use the same version as httpclient
compile('org.apache.httpcomponents:httpmime:4.3.2') {
transitive = false
}
compile('org.spongepowered:mixin:0.7.11-SNAPSHOT') {
transitive = false
}
@ -140,6 +164,9 @@ jar {
from sourceSets.common.output
from sourceSets.main.output
from sourceSets.hdskins.output
from sourceSets.hdskinsfml.output
from sourceSets.client.output
from sourceSets.fml.output

View file

@ -0,0 +1,26 @@
package mcp;
import javax.annotation.Nonnull;
import javax.annotation.meta.TypeQualifierDefault;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* This annotation can be applied to a package, class or method to indicate that
* the method in that element are nonnull by default unless there is:
* <ul>
* <li>An explicit nullness annotation
* <li>The method overrides a method in a superclass (in which case the
* annotation of the corresponding method in the superclass applies)
* <li> there is a default parameter annotation applied to a more tightly nested
* element.
* </ul>
*
*/
@Documented
@Nonnull
@TypeQualifierDefault(ElementType.METHOD) // Note: This is a copy of javax.annotation.ParametersAreNonnullByDefault with target changed to METHOD
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodsReturnNonnullByDefault {}

View file

@ -0,0 +1,20 @@
package net.minecraftforge.client;
import net.minecraft.client.renderer.entity.model.ModelBiped;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.item.ItemStack;
// stub
public class ForgeHooksClient {
public static String getArmorTexture(Entity entity, ItemStack armor, String def, EntityEquipmentSlot slot, String type) {
return def;
}
public static ModelBiped getArmorModel(EntityLivingBase entity, ItemStack item, EntityEquipmentSlot slot, ModelBiped def) {
return def;
}
}

View file

@ -0,0 +1,343 @@
package com.minelittlepony.hdskins;
import com.google.common.base.Preconditions;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Streams;
import com.minelittlepony.common.util.MoreStreams;
import com.minelittlepony.hdskins.ducks.INetworkPlayerInfo;
import com.minelittlepony.hdskins.gui.GuiSkins;
import com.minelittlepony.hdskins.resources.SkinResourceManager;
import com.minelittlepony.hdskins.resources.TextureLoader;
import com.minelittlepony.hdskins.resources.texture.ImageBufferDownloadHD;
import com.minelittlepony.hdskins.server.BethlehemSkinServer;
import com.minelittlepony.hdskins.server.LegacySkinServer;
import com.minelittlepony.hdskins.server.ServerType;
import com.minelittlepony.hdskins.server.SkinServer;
import com.minelittlepony.hdskins.server.ValhallaSkinServer;
import com.minelittlepony.hdskins.util.CallableFutures;
import com.minelittlepony.hdskins.util.PlayerUtil;
import com.minelittlepony.hdskins.util.ProfileTextureUtil;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.AbstractClientPlayer;
import net.minecraft.client.network.NetworkPlayerInfo;
import net.minecraft.client.renderer.texture.ThreadDownloadImageData;
import net.minecraft.client.renderer.texture.ITextureObject;
import net.minecraft.client.resources.DefaultPlayerSkin;
import net.minecraft.resources.IResourceManager;
import net.minecraft.resources.IResourceManagerReloadListener;
import net.minecraft.client.resources.SkinManager;
import net.minecraft.util.ResourceLocation;
import org.apache.commons.io.FileUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.annotation.Nullable;
public final class HDSkinManager implements IResourceManagerReloadListener {
private static final Logger logger = LogManager.getLogger();
public static final ExecutorService skinUploadExecutor = Executors.newSingleThreadExecutor();
public static final ExecutorService skinDownloadExecutor = Executors.newFixedThreadPool(8);
public static final CloseableHttpClient httpClient = HttpClients.createSystem();
public static final HDSkinManager INSTANCE = new HDSkinManager();
private List<ISkinCacheClearListener> clearListeners = Lists.newArrayList();
private BiMap<String, Class<? extends SkinServer>> skinServerTypes = HashBiMap.create(2);
private List<SkinServer> skinServers = Lists.newArrayList();
private LoadingCache<GameProfile, CompletableFuture<Map<Type, MinecraftProfileTexture>>> skins = CacheBuilder.newBuilder()
.expireAfterAccess(15, TimeUnit.SECONDS)
.build(CacheLoader.from(this::loadProfileData));
private List<ISkinModifier> skinModifiers = Lists.newArrayList();
private List<ISkinParser> skinParsers = Lists.newArrayList();
private SkinResourceManager resources = new SkinResourceManager();
private Function<List<SkinServer>, GuiSkins> skinsGuiFunc = GuiSkins::new;
private HDSkinManager() {
// register default skin server types
addSkinServerType(LegacySkinServer.class);
addSkinServerType(ValhallaSkinServer.class);
addSkinServerType(BethlehemSkinServer.class);
}
public void setSkinsGui(Function<List<SkinServer>, GuiSkins> skinsGuiFunc) {
Preconditions.checkNotNull(skinsGuiFunc, "skinsGuiFunc");
this.skinsGuiFunc = skinsGuiFunc;
}
public GuiSkins createSkinsGui() {
return skinsGuiFunc.apply(ImmutableList.copyOf(this.skinServers));
}
private CompletableFuture<Map<Type, MinecraftProfileTexture>> loadProfileData(GameProfile profile) {
return CompletableFuture.supplyAsync(() -> {
if (profile.getId() == null) {
return Collections.emptyMap();
}
Map<Type, MinecraftProfileTexture> textureMap = Maps.newEnumMap(Type.class);
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 {
// try to recreate a broken gameprofile
// happens when server sends a random profile with skin and displayname
Property textures = Iterables.getFirst(profile.getProperties().get("textures"), null);
if (textures != null) {
String json = new String(Base64.getDecoder().decode(textures.getValue()), StandardCharsets.UTF_8);
MinecraftTexturesPayload texturePayload = SkinServer.gson.fromJson(json, MinecraftTexturesPayload.class);
if (texturePayload != null) {
String name = texturePayload.getProfileName(); // name is optional
UUID uuid = texturePayload.getProfileId();
if (uuid != null) {
profile = new GameProfile(uuid, name); // uuid is required
}
// probably uses this texture for a reason. Don't mess with it.
if (!texturePayload.getTextures().isEmpty() && texturePayload.getProfileId() == null) {
return CompletableFuture.completedFuture(Collections.emptyMap());
}
}
}
} catch (Exception e) {
if (profile.getId() == null) { // Something broke server-side probably
logger.warn("{} had a null UUID and was unable to recreate it from texture profile.", profile.getName(), e);
return CompletableFuture.completedFuture(Collections.emptyMap());
}
}
return skins.getUnchecked(profile);
}
public void fetchAndLoadSkins(GameProfile profile, SkinManager.SkinAvailableCallback callback) {
loadProfileTextures(profile).thenAcceptAsync(m -> m.forEach((type, pp) -> {
loadTexture(type, pp, (typeIn, location, profileTexture) -> {
parseSkin(profile, typeIn, location, profileTexture)
.thenRun(() -> callback.onSkinTextureAvailable(typeIn, location, profileTexture));
});
}), Minecraft.getInstance()::addScheduledTask);
}
public ResourceLocation loadTexture(Type type, MinecraftProfileTexture texture, @Nullable SkinManager.SkinAvailableCallback callback) {
String skinDir = type.toString().toLowerCase() + "s/";
final ResourceLocation resource = new ResourceLocation("hdskins", skinDir + texture.getHash());
ITextureObject texObj = Minecraft.getInstance().getTextureManager().getTexture(resource);
//noinspection ConstantConditions
if (texObj != null) {
if (callback != null) {
callback.onSkinTextureAvailable(type, resource, texture);
}
} else {
// schedule texture loading on the main thread.
TextureLoader.loadTexture(resource, new ThreadDownloadImageData(
new File(HDSkins.getInstance().getAssetsDirectory(), "hd/" + skinDir + texture.getHash().substring(0, 2) + "/" + texture.getHash()),
texture.getUrl(),
DefaultPlayerSkin.getDefaultSkinLegacy(),
new ImageBufferDownloadHD(type, () -> {
if (callback != null) {
callback.onSkinTextureAvailable(type, resource, texture);
}
})));
}
return resource;
}
public Map<Type, ResourceLocation> getTextures(GameProfile profile) {
Map<Type, ResourceLocation> map = new HashMap<>();
for (Map.Entry<Type, MinecraftProfileTexture> e : loadProfileTextures(profile).getNow(Collections.emptyMap()).entrySet()) {
map.put(e.getKey(), loadTexture(e.getKey(), e.getValue(), null));
}
return map;
}
private void addSkinServerType(Class<? extends SkinServer> type) {
Preconditions.checkArgument(!type.isInterface(), "type cannot be an interface");
Preconditions.checkArgument(!Modifier.isAbstract(type.getModifiers()), "type cannot be abstract");
ServerType st = type.getAnnotation(ServerType.class);
if (st == null) {
throw new IllegalArgumentException("class is not annotated with @ServerType");
}
this.skinServerTypes.put(st.value(), type);
}
public Class<? extends SkinServer> getSkinServerClass(String type) {
return this.skinServerTypes.get(type);
}
void addSkinServer(SkinServer skinServer) {
this.skinServers.add(skinServer);
}
public void addClearListener(ISkinCacheClearListener listener) {
clearListeners.add(listener);
}
public void clearSkinCache() {
logger.info("Clearing local player skin cache");
FileUtils.deleteQuietly(new File(HDSkins.getInstance().getAssetsDirectory(), "hd"));
skins.invalidateAll();
parseSkins();
clearListeners.removeIf(this::onSkinCacheCleared);
}
private boolean onSkinCacheCleared(ISkinCacheClearListener callback) {
try {
return !callback.onSkinCacheCleared();
} catch (Exception e) {
logger.warn("Exception encountered calling skin listener '{}'. It will be removed.", callback.getClass().getName(), e);
return true;
}
}
public void addSkinModifier(ISkinModifier modifier) {
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;
}
public void convertSkin(ISkinModifier.IDrawer drawer) {
for (ISkinModifier skin : skinModifiers) {
skin.convertSkin(drawer);
}
}
public void parseSkins() {
Minecraft mc = Minecraft.getInstance();
Streams.concat(getNPCs(mc), getPlayers(mc))
// filter nulls
.filter(Objects::nonNull)
.map(INetworkPlayerInfo.class::cast)
.distinct()
// and clear skins
.forEach(INetworkPlayerInfo::reloadTextures);
}
private Stream<NetworkPlayerInfo> getNPCs(Minecraft mc) {
return MoreStreams.ofNullable(mc.world)
.flatMap(w -> w.playerEntities.stream())
.filter(AbstractClientPlayer.class::isInstance)
.map(AbstractClientPlayer.class::cast)
.map(PlayerUtil::getInfo);
}
private Stream<NetworkPlayerInfo> getPlayers(Minecraft mc) {
return MoreStreams.ofNullable(mc.getConnection())
.flatMap(a -> a.getPlayerInfoMap().stream());
}
public CompletableFuture<Void> parseSkin(GameProfile profile, Type type, ResourceLocation resource, MinecraftProfileTexture texture) {
return CallableFutures.scheduleTask(() -> {
// 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<>();
} else if (metadata.containsKey("model")) {
// try to reset the model.
metadata.put("model", VanillaModels.of(metadata.get("model")));
}
for (ISkinParser parser : skinParsers) {
try {
parser.parse(profile, type, resource, metadata);
} catch (Throwable t) {
logger.error("Exception thrown while parsing skin: ", t);
}
}
if (wasNull && !metadata.isEmpty()) {
ProfileTextureUtil.setMetadata(texture, metadata);
}
});
}
@Override
public void onResourceManagerReload(IResourceManager resourceManager) {
this.resources.onResourceManagerReload(resourceManager);
}
}

View file

@ -0,0 +1,68 @@
package com.minelittlepony.hdskins;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.resources.IReloadableResourceManager;
import net.minecraft.entity.Entity;
import com.google.gson.annotations.Expose;
import com.minelittlepony.hdskins.gui.EntityPlayerModel;
import com.minelittlepony.hdskins.gui.RenderPlayerModel;
import com.minelittlepony.hdskins.server.SkinServer;
import com.minelittlepony.hdskins.upload.GLWindow;
import java.io.File;
import java.util.List;
import java.util.function.Function;
public abstract class HDSkins {
public static final String MOD_NAME = "HD Skins";
public static final String VERSION = "4.0.0";
private static HDSkins instance;
public static HDSkins getInstance() {
return instance;
}
public HDSkins() {
instance = this;
}
@Expose
public List<SkinServer> skin_servers = SkinServer.defaultServers;
@Expose
public boolean experimentalSkinDrop = false;
@Expose
public String lastChosenFile = "";
public void init() {
IReloadableResourceManager irrm = (IReloadableResourceManager) Minecraft.getInstance().getResourceManager();
irrm.addReloadListener(HDSkinManager.INSTANCE);
}
public abstract File getAssetsDirectory();
public abstract void saveConfig();
protected abstract <T extends Entity> void addRenderer(Class<T> type, Function<RenderManager, Render<T>> renderer);
public void initComplete() {
addRenderer(EntityPlayerModel.class, RenderPlayerModel::new);
// register skin servers.
skin_servers.forEach(HDSkinManager.INSTANCE::addSkinServer);
if (experimentalSkinDrop) {
GLWindow.create();
}
}
public void onToggledFullScreen(boolean fullScreen) {
GLWindow.current().refresh(fullScreen);
}
}

View file

@ -0,0 +1,6 @@
package com.minelittlepony.hdskins;
@FunctionalInterface
public interface ISkinCacheClearListener {
boolean onSkinCacheCleared();
}

View file

@ -0,0 +1,17 @@
package com.minelittlepony.hdskins;
import net.minecraft.client.renderer.texture.NativeImage;
@FunctionalInterface
public interface ISkinModifier {
void convertSkin(IDrawer drawer);
interface IDrawer {
NativeImage getImage();
void draw(int scale,
/*destination: */ int dx1, int dy1, int dx2, int dy2,
/*source: */ int sx1, int sy1, int sx2, int sy2);
}
}

View file

@ -0,0 +1,25 @@
package com.minelittlepony.hdskins;
import com.mojang.authlib.GameProfile;
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 profile The profile whose skin is being parsed.
* @param type The texture type
* @param resource The texture location
* @param metadata The metadata previously parsed
*/
void parse(GameProfile profile, Type type, ResourceLocation resource, Map<String, String> metadata);
}

View file

@ -0,0 +1,127 @@
package com.minelittlepony.hdskins;
import com.minelittlepony.hdskins.upload.IFileDialog;
import com.minelittlepony.hdskins.upload.ThreadOpenFilePNG;
import com.minelittlepony.hdskins.upload.ThreadSaveFilePNG;
import com.minelittlepony.hdskins.util.MoreHttpResponses;
import net.minecraft.client.Minecraft;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import javax.annotation.Nullable;
import javax.imageio.ImageIO;
import javax.swing.UIManager;
public class SkinChooser {
public static final int MAX_SKIN_DIMENSION = 1024;
public static final String ERR_UNREADABLE = "hdskins.error.unreadable";
public static final String ERR_EXT = "hdskins.error.ext";
public static final String ERR_OPEN = "hdskins.error.open";
public static final String ERR_INVALID = "hdskins.error.invalid";
public static final String MSG_CHOOSE = "hdskins.choose";
static {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}
}
private static boolean isPowerOfTwo(int number) {
return number != 0 && (number & number - 1) == 0;
}
private IFileDialog openFileThread;
private final SkinUploader uploader;
private volatile String status = MSG_CHOOSE;
public SkinChooser(SkinUploader uploader) {
this.uploader = uploader;
}
public boolean pickingInProgress() {
return openFileThread != null;
}
public String getStatus() {
return status;
}
public void openBrowsePNG(Minecraft mc, String title) {
openFileThread = new ThreadOpenFilePNG(mc, title, (file, dialogResult) -> {
openFileThread = null;
if (dialogResult == 0) {
selectFile(file);
}
});
openFileThread.start();
}
public void openSavePNG(Minecraft mc, String title) {
openFileThread = new ThreadSaveFilePNG(mc, title, mc.getSession().getUsername() + ".png", (file, dialogResult) -> {
if (dialogResult == 0) {
try (MoreHttpResponses response = uploader.downloadSkin().get()) {
if (response.ok()) {
FileUtils.copyInputStreamToFile(response.getInputStream(), file);
}
} catch (IOException | InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
openFileThread = null;
});
openFileThread.start();
}
public void selectFile(File skinFile) {
status = evaluateAndSelect(skinFile);
}
@Nullable
private String evaluateAndSelect(File skinFile) {
if (!skinFile.exists()) {
return ERR_UNREADABLE;
}
if (!FilenameUtils.isExtension(skinFile.getName(), new String[]{"png", "PNG"})) {
return ERR_EXT;
}
try {
BufferedImage chosenImage = ImageIO.read(skinFile);
if (chosenImage == null) {
return ERR_OPEN;
}
if (!acceptsSkinDimensions(chosenImage.getWidth(), chosenImage.getHeight())) {
return ERR_INVALID;
}
uploader.setLocalSkin(skinFile);
return null;
} catch (IOException e) {
e.printStackTrace();
}
return ERR_OPEN;
}
protected boolean acceptsSkinDimensions(int w, int h) {
return isPowerOfTwo(w) && w == h * 2 || w == h && w <= MAX_SKIN_DIMENSION && h <= MAX_SKIN_DIMENSION;
}
}

View file

@ -0,0 +1,291 @@
package com.minelittlepony.hdskins;
import net.minecraft.client.Minecraft;
import net.minecraft.init.Items;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
import com.minelittlepony.hdskins.gui.EntityPlayerModel;
import com.minelittlepony.hdskins.gui.Feature;
import com.minelittlepony.hdskins.resources.PreviewTextureManager;
import com.minelittlepony.hdskins.server.SkinServer;
import com.minelittlepony.hdskins.server.SkinUpload;
import com.minelittlepony.hdskins.util.MoreHttpResponses;
import com.minelittlepony.hdskins.util.NetClient;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.exceptions.AuthenticationException;
import com.mojang.authlib.exceptions.AuthenticationUnavailableException;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
public class SkinUploader implements Closeable {
private static final Logger logger = LogManager.getLogger();
private final Iterator<SkinServer> skinServers;
public static final String ERR_NO_SERVER = "hdskins.error.noserver";
public static final String ERR_OFFLINE = "hdskins.error.offline";
public static final String ERR_MOJANG = "hdskins.error.mojang";
public static final String ERR_WAIT = "hdskins.error.mojang.wait";
public static final String STATUS_FETCH = "hdskins.fetch";
private SkinServer gateway;
private String status;
private Type skinType;
private Map<String, String> skinMetadata = new HashMap<String, String>();
private volatile boolean fetchingSkin = false;
private volatile boolean throttlingNeck = false;
private volatile boolean offline = false;
private volatile boolean sendingSkin = false;
private int reloadCounter = 0;
private int retries = 1;
private final EntityPlayerModel remotePlayer;
private final EntityPlayerModel localPlayer;
private final Object skinLock = new Object();
private File pendingLocalSkin;
private File localSkin;
private final ISkinUploadHandler listener;
private final Minecraft mc = Minecraft.getInstance();
private static <T> Iterator<T> cycle(List<T> list, Predicate<T> filter) {
return Iterables.cycle(Iterables.filter(list, filter::test)).iterator();
}
public SkinUploader(List<SkinServer> servers, EntityPlayerModel local, EntityPlayerModel remote, ISkinUploadHandler listener) {
localPlayer = local;
remotePlayer = remote;
skinType = Type.SKIN;
skinMetadata.put("model", "default");
this.listener = listener;
skinServers = cycle(servers, SkinServer::verifyGateway);
cycleGateway();
}
public void cycleGateway() {
if (skinServers.hasNext()) {
gateway = skinServers.next();
fetchRemote();
} else {
setError(ERR_NO_SERVER);
}
}
public String getGateway() {
return gateway == null ? "" : gateway.toString();
}
public boolean supportsFeature(Feature feature) {
return gateway != null && gateway.supportsFeature(feature);
}
protected void setError(String er) {
status = er;
sendingSkin = false;
}
public void setSkinType(Type type) {
skinType = type;
ItemStack stack = type == Type.ELYTRA ? new ItemStack(Items.ELYTRA) : ItemStack.EMPTY;
// put on or take off the elytra
localPlayer.setItemStackToSlot(EntityEquipmentSlot.CHEST, stack);
remotePlayer.setItemStackToSlot(EntityEquipmentSlot.CHEST, stack);
listener.onSkinTypeChanged(type);
}
public boolean uploadInProgress() {
return sendingSkin;
}
public boolean downloadInProgress() {
return fetchingSkin;
}
public boolean isThrottled() {
return throttlingNeck;
}
public boolean isOffline() {
return offline;
}
public int getRetries() {
return retries;
}
public boolean canUpload() {
return !isOffline() && !hasStatus() && !uploadInProgress() && pendingLocalSkin == null && localSkin != null && localPlayer.isUsingLocalTexture();
}
public boolean canClear() {
return !isOffline() && !hasStatus() && !downloadInProgress() && remotePlayer.isUsingRemoteTexture();
}
public boolean hasStatus() {
return status != null;
}
public String getStatusMessage() {
return hasStatus() ? status : "";
}
public void setMetadataField(String field, String value) {
localPlayer.releaseTextures();
skinMetadata.put(field, value);
}
public String getMetadataField(String field) {
return skinMetadata.getOrDefault(field, "");
}
public Type getSkinType() {
return skinType;
}
public boolean tryClearStatus() {
if (!hasStatus() || !uploadInProgress()) {
status = null;
return true;
}
return false;
}
public CompletableFuture<Void> uploadSkin(String statusMsg) {
sendingSkin = true;
status = statusMsg;
return gateway.uploadSkin(new SkinUpload(mc.getSession(), skinType, localSkin == null ? null : localSkin.toURI(), skinMetadata)).handle((response, throwable) -> {
if (throwable == null) {
logger.info("Upload completed with: %s", response);
setError(null);
} else {
setError(Throwables.getRootCause(throwable).toString());
}
fetchRemote();
return null;
});
}
public CompletableFuture<MoreHttpResponses> downloadSkin() {
String loc = remotePlayer.getLocal(skinType).getRemote().getUrl();
return new NetClient("GET", loc).async(HDSkinManager.skinDownloadExecutor);
}
protected void fetchRemote() {
fetchingSkin = true;
throttlingNeck = false;
offline = false;
remotePlayer.reloadRemoteSkin(this, (type, location, profileTexture) -> {
fetchingSkin = false;
listener.onSetRemoteSkin(type, location, profileTexture);
}).handle((a, throwable) -> {
fetchingSkin = false;
if (throwable != null) {
throwable = throwable.getCause();
throwable.printStackTrace();
if (throwable instanceof AuthenticationUnavailableException) {
offline = true;
} else if (throwable instanceof AuthenticationException) {
throttlingNeck = true;
} else {
setError(throwable.toString());
}
}
return a;
});
}
@Override
public void close() throws IOException {
localPlayer.releaseTextures();
remotePlayer.releaseTextures();
}
public void setLocalSkin(File skinFile) {
mc.addScheduledTask(localPlayer::releaseTextures);
synchronized (skinLock) {
pendingLocalSkin = skinFile;
}
}
public void update() {
localPlayer.updateModel();
remotePlayer.updateModel();
synchronized (skinLock) {
if (pendingLocalSkin != null) {
System.out.println("Set " + skinType + " " + pendingLocalSkin);
localPlayer.setLocalTexture(pendingLocalSkin, skinType);
localSkin = pendingLocalSkin;
pendingLocalSkin = null;
listener.onSetLocalSkin(skinType);
}
}
if (isThrottled()) {
reloadCounter = (reloadCounter + 1) % (200 * retries);
if (reloadCounter == 0) {
retries++;
fetchRemote();
}
}
}
public CompletableFuture<PreviewTextureManager> loadTextures(GameProfile profile) {
return gateway.getPreviewTextures(profile).thenApply(PreviewTextureManager::new);
}
public interface ISkinUploadHandler {
default void onSetRemoteSkin(Type type, ResourceLocation location, MinecraftProfileTexture profileTexture) {
}
default void onSetLocalSkin(Type type) {
}
default void onSkinTypeChanged(Type newType) {
}
}
}

View file

@ -0,0 +1,22 @@
package com.minelittlepony.hdskins;
public class VanillaModels {
public static final String SLIM = "slim";
public static final String DEFAULT = "default";
public static String of(String model) {
return model != null && model.contains(SLIM) ? SLIM : DEFAULT;
}
public static String nonNull(String model) {
return model == null ? DEFAULT : SLIM;
}
public static boolean isSlim(String model) {
return SLIM.equals(model);
}
public static boolean isFat(String model) {
return DEFAULT.equals(model);
}
}

View file

@ -0,0 +1,6 @@
package com.minelittlepony.hdskins.ducks;
public interface INetworkPlayerInfo {
void reloadTextures();
}

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package com.minelittlepony.hdskins.ducks;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,231 @@
package com.minelittlepony.hdskins.gui;
import static net.minecraft.client.renderer.GlStateManager.*;
import org.lwjgl.opengl.GL11;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.GlStateManager.DestFactor;
import net.minecraft.client.renderer.GlStateManager.SourceFactor;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
/**
* @deprecated Replace with RenderSkybox
*/
@Deprecated
public class CubeMap {
private int updateCounter = 0;
private float lastPartialTick;
private float zLevel;
private ResourceLocation viewportTexture;
private ResourceLocation[] cubemapTextures;
private final Minecraft mc = Minecraft.getInstance();
private final GuiScreen owner;
public CubeMap(GuiScreen owner) {
this.owner = owner;
}
public float getDelta(float partialTick) {
return updateCounter + partialTick - lastPartialTick;
}
public void setSource(String source) {
cubemapTextures = new ResourceLocation[] {
new ResourceLocation(String.format(source, 0)),
new ResourceLocation(String.format(source, 1)),
new ResourceLocation(String.format(source, 2)),
new ResourceLocation(String.format(source, 3)),
new ResourceLocation(String.format(source, 4)),
new ResourceLocation(String.format(source, 5))
};
}
public void init() {
viewportTexture = mc.getTextureManager().getDynamicTextureLocation("skinpanorama", new DynamicTexture(256, 256, true));
}
public void update() {
updateCounter++;
}
public void render(float partialTick, float z) {
zLevel = z;
lastPartialTick = updateCounter + partialTick;
disableFog();
mc.entityRenderer.disableLightmap();
disableAlphaTest();
renderPanorama(partialTick);
enableAlphaTest();
}
private void setupCubemapCamera() {
matrixMode(GL11.GL_PROJECTION);
pushMatrix();
loadIdentity();
// Project.gluPerspective(120, 1, 0.05F, 10);
matrixMode(GL11.GL_MODELVIEW);
pushMatrix();
loadIdentity();
}
private void revertPanoramaMatrix() {
matrixMode(GL11.GL_PROJECTION);
popMatrix();
matrixMode(GL11.GL_MODELVIEW);
popMatrix();
}
private void renderCubeMapTexture(float partialTick) {
this.setupCubemapCamera();
color4f(1, 1, 1, 1);
rotatef(180, 1, 0, 0);
enableBlend();
disableAlphaTest();
disableCull();
depthMask(false);
blendFuncSeparate(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA, SourceFactor.ONE, DestFactor.ZERO);
byte blendIterations = 8;
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder vb = tessellator.getBuffer();
for (int blendPass = 0; blendPass < blendIterations * blendIterations; ++blendPass) {
pushMatrix();
float offsetX = ((float) (blendPass % blendIterations) / (float) blendIterations - 0.5F) / 64;
float offsetY = ((float) (blendPass / blendIterations) / (float) blendIterations - 0.5F) / 64;
translatef(offsetX, offsetY, 0);
rotatef(MathHelper.sin(lastPartialTick / 400) * 25 + 20, 1, 0, 0);
rotatef(-lastPartialTick / 10, 0, 1, 0);
for (int cubeSide = 0; cubeSide < 6; ++cubeSide) {
pushMatrix();
if (cubeSide == 1) {
rotatef(90, 0, 1, 0);
}
if (cubeSide == 2) {
rotatef(180, 0, 1, 0);
}
if (cubeSide == 3) {
rotatef(-90, 0, 1, 0);
}
if (cubeSide == 4) {
rotatef(90, 1, 0, 0);
}
if (cubeSide == 5) {
rotatef(-90, 1, 0, 0);
}
mc.getTextureManager().bindTexture(cubemapTextures[cubeSide]);
vb.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR);
int l = 255 / (blendPass + 1);
vb.pos(-1, -1, 1).tex(0, 0).color(255, 255, 255, l).endVertex();
vb.pos(1, -1, 1).tex(1, 0).color(255, 255, 255, l).endVertex();
vb.pos(1, 1, 1).tex(1, 1).color(255, 255, 255, l).endVertex();
vb.pos(-1, 1, 1).tex(0, 1).color(255, 255, 255, l).endVertex();
tessellator.draw();
popMatrix();
}
popMatrix();
colorMask(true, true, true, false);
}
vb.setTranslation(0.0D, 0.0D, 0.0D);
colorMask(true, true, true, true);
depthMask(true);
enableCull();
enableAlphaTest();
enableDepthTest();
revertPanoramaMatrix();
}
private void rotateAndBlurCubemap() {
mc.getTextureManager().bindTexture(viewportTexture);
GL11.glTexParameteri(3553, 10241, 9729);
GL11.glTexParameteri(3553, 10240, 9729);
GL11.glCopyTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, 0, 0, 256, 256);
enableBlend();
blendFuncSeparate(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA, SourceFactor.ONE, DestFactor.ZERO);
colorMask(true, true, true, false);
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder vb = tessellator.getBuffer();
vb.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR);
disableAlphaTest();
byte blurPasses = 3;
for (int blurPass = 0; blurPass < blurPasses; ++blurPass) {
float f = 1 / (float)(blurPass + 1);
float var7 = (blurPass - 1) / 256F;
vb.pos(owner.width, owner.height, zLevel).tex(var7, 1).color(1, 1, 1, f).endVertex();
vb.pos(owner.width, 0, zLevel).tex(1 + var7, 1).color(1, 1, 1, f).endVertex();
vb.pos(0, 0, zLevel).tex(1 + var7, 0).color(1, 1, 1, f).endVertex();
vb.pos(0, owner.height, zLevel).tex(var7, 0).color(1, 1, 1, f).endVertex();
}
tessellator.draw();
enableAlphaTest();
colorMask(true, true, true, true);
}
private void renderPanorama(float partialTicks) {
mc.getFramebuffer().unbindFramebuffer();
viewport(0, 0, 256, 256);
renderCubeMapTexture(partialTicks);
for (int tessellator = 0; tessellator < 8; ++tessellator) {
rotateAndBlurCubemap();
}
mc.getFramebuffer().bindFramebuffer(true);
viewport(0, 0, mc.mainWindow.getWidth(), mc.mainWindow.getHeight());
float aspect = owner.width > owner.height ? 120F / owner.width : 120F / owner.height;
float uSample = owner.height * aspect / 256F;
float vSample = owner.width * aspect / 256F;
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder vb = tessellator.getBuffer();
vb.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
vb.pos(0, owner.height, zLevel).tex(0.5F - uSample, 0.5F + vSample).endVertex();
vb.pos(owner.width, owner.height, zLevel).tex(0.5F - uSample, 0.5F - vSample).endVertex();
vb.pos(owner.width, 0, zLevel).tex(0.5F + uSample, 0.5F - vSample).endVertex();
vb.pos(0, 0, zLevel).tex(0.5F + uSample, 0.5F + vSample).endVertex();
tessellator.draw();
}
}

View file

@ -0,0 +1,98 @@
package com.minelittlepony.hdskins.gui;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.fluid.Fluid;
import net.minecraft.init.Blocks;
import net.minecraft.item.crafting.RecipeManager;
import net.minecraft.scoreboard.Scoreboard;
import net.minecraft.tags.NetworkTagManager;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.EmptyTickList;
import net.minecraft.world.GameType;
import net.minecraft.world.ITickList;
import net.minecraft.world.World;
import net.minecraft.world.WorldSettings;
import net.minecraft.world.WorldType;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.ChunkPrimer;
import net.minecraft.world.chunk.IChunkProvider;
import net.minecraft.world.chunk.UpgradeData;
import net.minecraft.world.dimension.OverworldDimension;
import net.minecraft.world.storage.WorldInfo;
public class DummyWorld extends World {
public static final World INSTANCE = new DummyWorld();
private final Chunk chunk = new Chunk(this, new ChunkPrimer(new ChunkPos(0, 0), UpgradeData.EMPTY), 0, 0);
private DummyWorld() {
super(null, null,
new WorldInfo(new WorldSettings(0, GameType.NOT_SET, false, false, WorldType.DEFAULT), "MpServer"),
new OverworldDimension(),
null,
true);
}
@Override
protected IChunkProvider createChunkProvider() {
return null;
}
@Override
public boolean isAreaLoaded(int p_175663_1_, int p_175663_2_, int p_175663_3_, int p_175663_4_, int p_175663_5_, int p_175663_6_, boolean p_175663_7_) {
return true;
}
@Override
public Chunk getChunk(int chunkX, int chunkZ) {
return chunk;
}
@Override
public IBlockState getBlockState(BlockPos pos) {
return Blocks.AIR.getDefaultState();
}
@Override
public float getBrightness(BlockPos pos) {
return 15;
}
@Override
public BlockPos getSpawnPoint() {
return BlockPos.ORIGIN;
}
@Override
public ITickList<Block> getPendingBlockTicks() {
return EmptyTickList.get();
}
@Override
public ITickList<Fluid> getPendingFluidTicks() {
return EmptyTickList.get();
}
@Override
public boolean isChunkLoaded(int var1, int var2, boolean var3) {
return true;
}
@Override
public Scoreboard getScoreboard() {
return null;
}
@Override
public RecipeManager getRecipeManager() {
return null;
}
@Override
public NetworkTagManager getTags() {
return null;
}
}

View file

@ -0,0 +1,190 @@
package com.minelittlepony.hdskins.gui;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.minelittlepony.hdskins.SkinUploader;
import com.minelittlepony.hdskins.resources.LocalTexture;
import com.minelittlepony.hdskins.resources.LocalTexture.IBlankSkinSupplier;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.SkinManager;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.EntityType;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumHandSide;
import net.minecraft.util.ResourceLocation;
import java.io.File;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@SuppressWarnings("EntityConstructor")
public class EntityPlayerModel extends EntityLivingBase implements IBlankSkinSupplier {
public static final ResourceLocation NO_SKIN = new ResourceLocation("hdskins", "textures/mob/noskin.png");
public static final ResourceLocation NO_ELYTRA = new ResourceLocation("textures/entity/elytra.png");
private final Map<EntityEquipmentSlot, ItemStack> armour = Maps.newEnumMap(ImmutableMap.of(
EntityEquipmentSlot.HEAD, ItemStack.EMPTY,
EntityEquipmentSlot.CHEST, ItemStack.EMPTY,
EntityEquipmentSlot.LEGS, ItemStack.EMPTY,
EntityEquipmentSlot.FEET, ItemStack.EMPTY,
EntityEquipmentSlot.MAINHAND, ItemStack.EMPTY
));
protected final LocalTexture skin;
protected final LocalTexture elytra;
private final GameProfile profile;
protected boolean previewThinArms = false;
protected boolean previewSleeping = false;
protected boolean previewRiding = false;
public EntityPlayerModel(GameProfile gameprofile) {
super(EntityType.PLAYER, DummyWorld.INSTANCE);
profile = gameprofile;
skin = new LocalTexture(profile, Type.SKIN, this);
elytra = new LocalTexture(profile, Type.ELYTRA, this);
}
public CompletableFuture<Void> reloadRemoteSkin(SkinUploader uploader, SkinManager.SkinAvailableCallback listener) {
return uploader.loadTextures(profile).thenAcceptAsync(ptm -> {
skin.setRemote(ptm, listener);
elytra.setRemote(ptm, listener);
}, Minecraft.getInstance()::addScheduledTask); // run on main thread
}
public void setLocalTexture(File skinTextureFile, Type type) {
if (type == Type.SKIN) {
skin.setLocal(skinTextureFile);
} else if (type == Type.ELYTRA) {
elytra.setLocal(skinTextureFile);
}
}
@Override
public ResourceLocation getBlankSkin(Type type) {
return type == Type.SKIN ? NO_SKIN : NO_ELYTRA;
}
public boolean isUsingLocalTexture() {
return skin.usingLocal() || elytra.usingLocal();
}
public boolean isTextureSetupComplete() {
return skin.uploadComplete() && elytra.uploadComplete();
}
public boolean isUsingRemoteTexture() {
return skin.hasRemoteTexture() || elytra.hasRemoteTexture();
}
public void releaseTextures() {
skin.clearLocal();
elytra.clearLocal();
}
public LocalTexture getLocal(Type type) {
return type == Type.SKIN ? skin : elytra;
}
public void setPreviewThinArms(boolean thinArms) {
previewThinArms = thinArms;
}
public boolean usesThinSkin() {
if (skin.uploadComplete() && skin.getRemote().hasModel()) {
return skin.getRemote().usesThinArms();
}
return previewThinArms;
}
public void setSleeping(boolean sleep) {
previewSleeping = sleep;
}
public void setRiding(boolean ride) {
previewRiding = ride;
}
@Override
public Entity getRidingEntity() {
return previewRiding ? this : null;
}
@Override
public boolean isPlayerSleeping() {
return !previewRiding && previewSleeping;
}
@Override
public boolean isSneaking() {
return !previewRiding && !previewSleeping && super.isSneaking();
}
public void updateModel() {
prevSwingProgress = swingProgress;
if (isSwingInProgress) {
++swingProgressInt;
if (swingProgressInt >= 8) {
swingProgressInt = 0;
isSwingInProgress = false;
}
} else {
swingProgressInt = 0;
}
swingProgress = swingProgressInt / 8F;
motionY *= 0.98;
if (Math.abs(motionY) < 0.003) {
motionY = 0;
}
if (posY == 0 && isJumping && !previewSleeping && !previewRiding) {
jump();
}
motionY -= 0.08D;
motionY *= 0.9800000190734863D;
posY += motionY;
if (posY < 0) {
posY = 0;
}
onGround = posY == 0;
ticksExisted++;
}
@Override
public EnumHandSide getPrimaryHand() {
return Minecraft.getInstance().gameSettings.mainHand;
}
@Override
public Iterable<ItemStack> getArmorInventoryList() {
return armour.values();
}
@Override
public ItemStack getItemStackFromSlot(EntityEquipmentSlot slotIn) {
return armour.get(slotIn);
}
@Override
public void setItemStackToSlot(EntityEquipmentSlot slotIn, ItemStack stack) {
armour.put(slotIn, stack);
}
}

View file

@ -0,0 +1,50 @@
package com.minelittlepony.hdskins.gui;
/**
* Represents the possible features that a skin server can implement.
*/
public enum Feature {
/**
* Whether a server has write access.
* i.e. If the server allows for users to upload a new skin.
*/
UPLOAD_USER_SKIN,
/**
* Whether a server allows for downloading and saving a user's skin.
* Most servers should support this.
*/
DOWNLOAD_USER_SKIN,
/**
* Whether a server has delete access.
* i.e. If the server allows a user to deleted a previously uploaded skin.
*/
DELETE_USER_SKIN,
/**
* Whether a server can send a full list of skins for a given profile.
* Typically used for servers that keep a record of past uploads
* and/or allow for switching between past skins.
*/
FETCH_SKIN_LIST,
/**
* Whether a server supports thin (Alex) skins or just default (Steve) skins.
* Servers without this will typically fall back to using the player's uuid on the client side.
*
* (unused)
*/
MODEL_VARIANTS,
/**
* Whether a server allows for uploading alternative skin types. i.e. Cape, Elytra, Hats and wears.
*/
MODEL_TYPES,
/**
* Whether a server will accept arbitrary extra metadata values with skin uploads.
*
* (unused)
*/
MODEL_METADATA,
/**
* Whether a server can provide a link to view a user's profile online,
* typically through a web-portal.
*/
LINK_PROFILE
}

View file

@ -0,0 +1,595 @@
package com.minelittlepony.hdskins.gui;
import com.google.common.base.Splitter;
import com.minelittlepony.common.client.gui.Button;
import com.minelittlepony.common.client.gui.GameGui;
import com.minelittlepony.common.client.gui.IGuiAction;
import com.minelittlepony.common.client.gui.IconicButton;
import com.minelittlepony.common.client.gui.IconicToggle;
import com.minelittlepony.common.client.gui.Label;
import com.minelittlepony.common.client.gui.Style;
import com.minelittlepony.hdskins.HDSkinManager;
import com.minelittlepony.hdskins.SkinChooser;
import com.minelittlepony.hdskins.SkinUploader;
import com.minelittlepony.hdskins.VanillaModels;
import com.minelittlepony.hdskins.SkinUploader.ISkinUploadHandler;
import com.minelittlepony.hdskins.server.SkinServer;
import com.minelittlepony.hdskins.upload.GLWindow;
import com.minelittlepony.hdskins.util.CallableFutures;
import com.minelittlepony.hdskins.util.Edge;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiMainMenu;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.client.util.InputMappings;
import net.minecraft.init.Items;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.MathHelper;
import org.lwjgl.BufferUtils;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL11;
import java.io.IOException;
import java.nio.DoubleBuffer;
import java.util.List;
import static net.minecraft.client.renderer.GlStateManager.*;
public class GuiSkins extends GameGui implements ISkinUploadHandler {
private int updateCounter = 0;
private Button btnBrowse;
private FeatureButton btnUpload;
private FeatureButton btnDownload;
private FeatureButton btnClear;
private FeatureSwitch btnModeSteve;
private FeatureSwitch btnModeAlex;
private FeatureSwitch btnModeSkin;
private FeatureSwitch btnModeElytra;
protected EntityPlayerModel localPlayer;
protected EntityPlayerModel remotePlayer;
private DoubleBuffer doubleBuffer;
private float msgFadeOpacity = 0;
private double lastMouseX = 0;
private boolean jumpState = false;
private boolean sneakState = false;
protected final SkinUploader uploader;
protected final SkinChooser chooser;
protected final CubeMap panorama;
private final Edge ctrlKey = new Edge(this::ctrlToggled) {
@Override
protected boolean nextState() {
return GuiScreen.isCtrlKeyDown();
}
};
private final Edge jumpKey = new Edge(this::jumpToggled) {
@Override
protected boolean nextState() {
return InputMappings.isKeyDown(GLFW.GLFW_KEY_SPACE);
}
};
private final Edge sneakKey = new Edge(this::sneakToggled) {
@Override
protected boolean nextState() {
return GuiScreen.isShiftKeyDown();
}
};
public GuiSkins(List<SkinServer> servers) {
mc = Minecraft.getInstance();
GameProfile profile = mc.getSession().getProfile();
localPlayer = getModel(profile);
remotePlayer = getModel(profile);
RenderManager rm = mc.getRenderManager();
rm.textureManager = mc.getTextureManager();
rm.options = mc.gameSettings;
rm.renderViewEntity = localPlayer;
uploader = new SkinUploader(servers, localPlayer, remotePlayer, this);
chooser = new SkinChooser(uploader);
panorama = new CubeMap(this);
initPanorama();
}
protected void initPanorama() {
panorama.setSource("hdskins:textures/cubemaps/cubemap0_%d.png");
}
protected EntityPlayerModel getModel(GameProfile profile) {
return new EntityPlayerModel(profile);
}
@Override
public void tick() {
if (!(InputMappings.isKeyDown(GLFW.GLFW_KEY_LEFT) || InputMappings.isKeyDown(GLFW.GLFW_KEY_RIGHT))) {
updateCounter++;
}
panorama.update();
uploader.update();
updateButtons();
}
@Override
public void initGui() {
GLWindow.current().setDropTargetListener(files -> {
files.stream().findFirst().ifPresent(file -> {
chooser.selectFile(file);
updateButtons();
});
});
panorama.init();
addButton(new Label(width / 2, 10, "hdskins.manager", 0xffffff, true));
addButton(new Label(34, 34, "hdskins.local", 0xffffff));
addButton(new Label(width / 2 + 34, 34, "hdskins.server", 0xffffff));
addButton(btnBrowse = new Button(width / 2 - 150, height - 27, 90, 20, "hdskins.options.browse", sender ->
chooser.openBrowsePNG(mc, format("hdskins.open.title"))))
.setEnabled(!mc.mainWindow.isFullscreen());
addButton(btnUpload = new FeatureButton(width / 2 - 24, height / 2 - 20, 48, 20, "hdskins.options.chevy", sender -> {
if (uploader.canUpload()) {
punchServer("hdskins.upload");
}
})).setEnabled(uploader.canUpload())
.setTooltip("hdskins.options.chevy.title");
addButton(btnDownload = new FeatureButton(width / 2 - 24, height / 2 + 20, 48, 20, "hdskins.options.download", sender -> {
if (uploader.canClear()) {
chooser.openSavePNG(mc, format("hdskins.save.title"));
}
})).setEnabled(uploader.canClear())
.setTooltip("hdskins.options.download.title");
addButton(btnClear = new FeatureButton(width / 2 + 60, height - 27, 90, 20, "hdskins.options.clear", sender -> {
if (uploader.canClear()) {
punchServer("hdskins.request");
}
})).setEnabled(uploader.canClear());
addButton(new Button(width / 2 - 50, height - 25, 100, 20, "hdskins.options.close", sender ->
mc.displayGuiScreen(new GuiMainMenu())));
addButton(btnModeSteve = new FeatureSwitch(width - 25, 32, sender -> switchSkinMode(VanillaModels.DEFAULT)))
.setIcon(new ItemStack(Items.LEATHER_LEGGINGS), 0x3c5dcb)
.setEnabled(VanillaModels.isSlim(uploader.getMetadataField("model")))
.setTooltip("hdskins.mode.steve")
.setTooltipOffset(0, 10);
addButton(btnModeAlex = new FeatureSwitch(width - 25, 51, sender -> switchSkinMode(VanillaModels.SLIM)))
.setIcon(new ItemStack(Items.LEATHER_LEGGINGS), 0xfff500)
.setEnabled(VanillaModels.isFat(uploader.getMetadataField("model")))
.setTooltip("hdskins.mode.alex")
.setTooltipOffset(0, 10);
addButton(btnModeSkin = new FeatureSwitch(width - 25, 75, sender -> uploader.setSkinType(Type.SKIN)))
.setIcon(new ItemStack(Items.LEATHER_CHESTPLATE))
.setEnabled(uploader.getSkinType() == Type.ELYTRA)
.setTooltip(format("hdskins.mode." + Type.SKIN.name().toLowerCase()))
.setTooltipOffset(0, 10);
addButton(btnModeElytra = new FeatureSwitch(width - 25, 94, sender -> uploader.setSkinType(Type.ELYTRA)))
.setIcon(new ItemStack(Items.ELYTRA))
.setEnabled(uploader.getSkinType() == Type.SKIN)
.setTooltip(format("hdskins.mode." + Type.ELYTRA.name().toLowerCase()))
.setTooltipOffset(0, 10);
addButton(new IconicToggle(width - 25, 118, 3, sender -> {
playSound(SoundEvents.BLOCK_BREWING_STAND_BREW);
boolean sleep = sender.getValue() == 1;
boolean ride = sender.getValue() == 2;
localPlayer.setSleeping(sleep);
remotePlayer.setSleeping(sleep);
localPlayer.setRiding(ride);
remotePlayer.setRiding(ride);
}))
.setValue(localPlayer.isPlayerSleeping() ? 1 : 0)
.setStyle(new Style().setIcon(new ItemStack(Items.IRON_BOOTS, 1)).setTooltip("hdskins.mode.stand"), 0)
.setStyle(new Style().setIcon(new ItemStack(Items.CLOCK, 1)).setTooltip("hdskins.mode.sleep"), 1)
.setStyle(new Style().setIcon(new ItemStack(Items.OAK_BOAT, 1)).setTooltip("hdskins.mode.ride"), 2)
.setTooltipOffset(0, 10);
addButton(new Button(width - 25, height - 65, 20, 20, "?", sender -> {
uploader.cycleGateway();
playSound(SoundEvents.ENTITY_VILLAGER_YES);
sender.setTooltip(uploader.getGateway());
}))
.setTooltip(uploader.getGateway())
.setTooltipOffset(0, 10);
}
@Override
public void onGuiClosed() {
super.onGuiClosed();
try {
uploader.close();
} catch (IOException e) {
e.printStackTrace();
}
HDSkinManager.INSTANCE.clearSkinCache();
GLWindow.current().clearDropTargetListener();
}
@Override
public void onSkinTypeChanged(Type newType) {
playSound(SoundEvents.BLOCK_BREWING_STAND_BREW);
btnModeSkin.enabled = newType == Type.ELYTRA;
btnModeElytra.enabled = newType == Type.SKIN;
}
protected void switchSkinMode(String model) {
playSound(SoundEvents.BLOCK_BREWING_STAND_BREW);
boolean thinArmType = VanillaModels.isSlim(model);
btnModeSteve.enabled = thinArmType;
btnModeAlex.enabled = !thinArmType;
uploader.setMetadataField("model", model);
localPlayer.setPreviewThinArms(thinArmType);
remotePlayer.setPreviewThinArms(thinArmType);
}
protected boolean canTakeEvents() {
return !chooser.pickingInProgress() && uploader.tryClearStatus() && msgFadeOpacity == 0;
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
lastMouseX = mouseX;
if (canTakeEvents() && super.mouseClicked(mouseX, mouseY, button)) {
int bottom = height - 40;
int mid = width / 2;
if ((mouseX > 30 && mouseX < mid - 30 || mouseX > mid + 30 && mouseX < width - 30) && mouseY > 30 && mouseY < bottom) {
localPlayer.swingArm(EnumHand.MAIN_HAND);
remotePlayer.swingArm(EnumHand.MAIN_HAND);
}
return true;
}
return false;
}
@Override
public boolean mouseDragged(double mouseX, double mouseY, int button, double changeX, double changeY) {
lastMouseX = mouseX;
if (canTakeEvents() && super.mouseDragged(mouseX, mouseY, button, changeX, changeY)) {
updateCounter -= (lastMouseX - mouseX);
return true;
}
return false;
}
@Override
public boolean keyPressed(int mouseX, int mouseY, int keyCode) {
if (canTakeEvents()) {
if (keyCode == GLFW.GLFW_KEY_LEFT) {
updateCounter -= 5;
} else if (keyCode == GLFW.GLFW_KEY_RIGHT) {
updateCounter += 5;
}
if (!chooser.pickingInProgress() && !uploader.uploadInProgress()) {
return super.keyPressed(mouseX, mouseY, keyCode);
}
}
return false;
}
private void jumpToggled(boolean jumping) {
if (jumping && ctrlKey.getState()) {
jumpState = !jumpState;
}
jumping |= jumpState;
localPlayer.setJumping(jumping);
remotePlayer.setJumping(jumping);
}
private void sneakToggled(boolean sneaking) {
if (sneaking && ctrlKey.getState()) {
sneakState = !sneakState;
}
sneaking |= sneakState;
localPlayer.setSneaking(sneaking);
remotePlayer.setSneaking(sneaking);
}
private void ctrlToggled(boolean ctrl) {
if (ctrl) {
if (sneakKey.getState()) {
sneakState = !sneakState;
}
if (jumpKey.getState()) {
jumpState = !jumpState;
}
}
}
@Override
protected void drawContents(int mouseX, int mouseY, float partialTick) {
ctrlKey.update();
jumpKey.update();
sneakKey.update();
float deltaTime = panorama.getDelta(partialTick);
panorama.render(partialTick, zLevel);
int bottom = height - 40;
int mid = width / 2;
int horizon = height / 2 + height / 5;
GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
drawRect(30, 30, mid - 30, bottom, Integer.MIN_VALUE);
drawRect(mid + 30, 30, width - 30, bottom, Integer.MIN_VALUE);
drawGradientRect(30, horizon, mid - 30, bottom, 0x80FFFFFF, 0xffffff);
drawGradientRect(mid + 30, horizon, width - 30, bottom, 0x80FFFFFF, 0xffffff);
super.drawContents(mouseX, mouseY, partialTick);
enableClipping(bottom);
float yPos = height * 0.75F;
float xPos1 = width / 4;
float xPos2 = width * 0.75F;
float scale = height / 4;
renderPlayerModel(localPlayer, xPos1, yPos, scale, horizon - mouseY, mouseX, partialTick);
renderPlayerModel(remotePlayer, xPos2, yPos, scale, horizon - mouseY, mouseX, partialTick);
disableClipping();
if (chooser.getStatus() != null && !uploader.canUpload()) {
drawRect(40, height / 2 - 12, width / 2 - 40, height / 2 + 12, 0xB0000000);
drawCenteredString(fontRenderer, format(chooser.getStatus()), (int) xPos1, height / 2 - 4, 0xffffff);
}
if (uploader.downloadInProgress() || uploader.isThrottled() || uploader.isOffline()) {
int lineHeight = uploader.isThrottled() ? 18 : 12;
drawRect((int) (xPos2 - width / 4 + 40), height / 2 - lineHeight, width - 40, height / 2 + lineHeight, 0xB0000000);
if (uploader.isThrottled()) {
drawCenteredString(fontRenderer, format(SkinUploader.ERR_MOJANG), (int) xPos2, height / 2 - 10, 0xff5555);
drawCenteredString(fontRenderer, format(SkinUploader.ERR_WAIT, uploader.getRetries()), (int) xPos2, height / 2 + 2, 0xff5555);
} else if (uploader.isOffline()) {
drawCenteredString(fontRenderer, format(SkinUploader.ERR_OFFLINE), (int) xPos2, height / 2 - 4, 0xff5555);
} else {
drawCenteredString(fontRenderer, format(SkinUploader.STATUS_FETCH), (int) xPos2, height / 2 - 4, 0xffffff);
}
}
boolean uploadInProgress = uploader.uploadInProgress();
boolean showError = uploader.hasStatus();
if (uploadInProgress || showError || msgFadeOpacity > 0) {
if (!uploadInProgress && !showError) {
msgFadeOpacity -= deltaTime / 10;
} else if (msgFadeOpacity < 1) {
msgFadeOpacity += deltaTime / 10;
}
msgFadeOpacity = MathHelper.clamp(msgFadeOpacity, 0, 1);
}
if (msgFadeOpacity > 0) {
int opacity = (Math.min(180, (int) (msgFadeOpacity * 180)) & 255) << 24;
drawRect(0, 0, width, height, opacity);
String errorMsg = format(uploader.getStatusMessage());
if (uploadInProgress) {
drawCenteredString(fontRenderer, errorMsg, width / 2, height / 2, 0xffffff);
} else if (showError) {
int blockHeight = (height - fontRenderer.getWordWrappedHeight(errorMsg, width - 10)) / 2;
drawCenteredString(fontRenderer, format("hdskins.failed"), width / 2, blockHeight - fontRenderer.FONT_HEIGHT * 2, 0xffff55);
fontRenderer.drawSplitString(errorMsg, 5, blockHeight, width - 10, 0xff5555);
}
}
depthMask(true);
enableDepthTest();
}
private void renderPlayerModel(EntityPlayerModel thePlayer, float xPosition, float yPosition, float scale, float mouseY, float mouseX, float partialTick) {
mc.getTextureManager().bindTexture(thePlayer.getLocal(Type.SKIN).getTexture());
enableColorMaterial();
pushMatrix();
translatef(xPosition, yPosition, 300);
scalef(scale, scale, scale);
rotatef(-15, 1, 0, 0);
RenderHelper.enableStandardItemLighting();
float rot = ((updateCounter + partialTick) * 2.5F) % 360;
rotatef(rot, 0, 1, 0);
float lookFactor = (float)Math.sin((rot * (Math.PI / 180)) + 45);
float lookX = (float) Math.atan((xPosition - mouseX) / 20) * 30;
thePlayer.rotationYawHead = lookX * lookFactor;
thePlayer.rotationPitch = (float) Math.atan(mouseY / 40) * -20;
mc.getRenderManager().renderEntity(thePlayer, 0, 0, 0, 0, 1, false);
popMatrix();
RenderHelper.disableStandardItemLighting();
disableColorMaterial();
}
/*
* / |
* 1/ |o Q = t + q
* /q | x = xPosition - mouseX
* *-----* sin(q) = o cos(q) = x tan(q) = o/x
* --|--x------------------------------------
* |
* mouseX
*/
private void enableClipping(int yBottom) {
GL11.glPopAttrib();
if (doubleBuffer == null) {
doubleBuffer = BufferUtils.createByteBuffer(32).asDoubleBuffer();
}
doubleBuffer.clear();
doubleBuffer.put(0).put(1).put(0).put(-30).flip();
GL11.glClipPlane(GL11.GL_CLIP_PLANE0, doubleBuffer);
doubleBuffer.clear();
doubleBuffer.put(0).put(-1).put(0).put(yBottom).flip();
GL11.glClipPlane(GL11.GL_CLIP_PLANE1, doubleBuffer);
GL11.glEnable(GL11.GL_CLIP_PLANE0);
GL11.glEnable(GL11.GL_CLIP_PLANE1);
}
private void disableClipping() {
GL11.glDisable(GL11.GL_CLIP_PLANE1);
GL11.glDisable(GL11.GL_CLIP_PLANE0);
disableDepthTest();
enableBlend();
depthMask(false);
}
private void punchServer(String uploadMsg) {
uploader.uploadSkin(uploadMsg).handle(CallableFutures.callback(this::updateButtons));
updateButtons();
}
private void updateButtons() {
btnClear.enabled = uploader.canClear();
btnUpload.enabled = uploader.canUpload() && uploader.supportsFeature(Feature.UPLOAD_USER_SKIN);
btnDownload.enabled = uploader.canClear() && !chooser.pickingInProgress();
btnBrowse.enabled = !chooser.pickingInProgress();
boolean types = !uploader.supportsFeature(Feature.MODEL_TYPES);
boolean variants = !uploader.supportsFeature(Feature.MODEL_VARIANTS);
btnModeSkin.setLocked(types);
btnModeElytra.setLocked(types);
btnModeSteve.setLocked(variants);
btnModeAlex.setLocked(variants);
btnClear.setLocked(!uploader.supportsFeature(Feature.DELETE_USER_SKIN));
btnUpload.setLocked(!uploader.supportsFeature(Feature.UPLOAD_USER_SKIN));
btnDownload.setLocked(!uploader.supportsFeature(Feature.DOWNLOAD_USER_SKIN));
}
protected class FeatureButton extends Button {
private List<String> disabledTooltip = Splitter.onPattern("\r?\n|\\\\n").splitToList(format("hdskins.warning.disabled.description"));
protected boolean locked;
public FeatureButton(int x, int y, int width, int height, String label, IGuiAction<? extends Button> callback) {
super(x, y, width, height, label, callback);
}
@Override
protected List<String> getTooltip() {
if (locked) {
return disabledTooltip;
}
return super.getTooltip();
}
@Override
public Button setTooltip(String tooltip) {
disabledTooltip = Splitter.onPattern("\r?\n|\\\\n").splitToList(
format("hdskins.warning.disabled.title",
format(tooltip),
format("hdskins.warning.disabled.description")));
return super.setTooltip(tooltip);
}
public void setLocked(boolean lock) {
locked = lock;
enabled &= !lock;
}
}
protected class FeatureSwitch extends IconicButton {
private List<String> disabledTooltip = null;
protected boolean locked;
public FeatureSwitch(int x, int y, IGuiAction<? extends IconicButton> callback) {
super(x, y, callback);
}
@Override
protected List<String> getTooltip() {
if (locked) {
return disabledTooltip;
}
return super.getTooltip();
}
@Override
public Button setTooltip(String tooltip) {
disabledTooltip = Splitter.onPattern("\r?\n|\\\\n").splitToList(
format("hdskins.warning.disabled.title",
format(tooltip),
format("hdskins.warning.disabled.description")));
return super.setTooltip(tooltip);
}
public void setLocked(boolean lock) {
locked = lock;
enabled &= !lock;
}
}
}

View file

@ -0,0 +1,33 @@
package com.minelittlepony.hdskins.gui;
import com.minelittlepony.common.client.gui.Checkbox;
import com.minelittlepony.common.client.gui.GuiHost;
import com.minelittlepony.common.client.gui.IGuiGuest;
import com.minelittlepony.hdskins.HDSkins;
import com.minelittlepony.hdskins.upload.GLWindow;
public class HDSkinsConfigPanel implements IGuiGuest {
@Override
public void initGui(GuiHost host) {
final HDSkins mod = HDSkins.getInstance();
host.addButton(new Checkbox(40, 40, "hdskins.options.skindrops", mod.experimentalSkinDrop, checked -> {
mod.experimentalSkinDrop = checked;
mod.saveConfig();
if (checked) {
GLWindow.create();
} else {
GLWindow.dispose();
}
return checked;
})).setTooltip(host.formatMultiLine("hdskins.warning.experimental", 250));
}
@Override
public String getTitle() {
return "HD Skins Settings";
}
}

View file

@ -0,0 +1,219 @@
package com.minelittlepony.hdskins.gui;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.entity.model.ModelBiped.ArmPose;
import net.minecraft.client.renderer.entity.model.ModelElytra;
import net.minecraft.client.renderer.entity.model.ModelPlayer;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderLivingBase;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.client.renderer.entity.layers.LayerRenderer;
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityBoat;
import net.minecraft.entity.player.EnumPlayerModelParts;
import net.minecraft.init.Blocks;
import net.minecraft.init.Items;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntityBed;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
import java.util.Set;
import static net.minecraft.client.renderer.GlStateManager.*;
public class RenderPlayerModel<M extends EntityPlayerModel> extends RenderLivingBase<M> {
/**
* The basic Elytra texture.
*/
protected final ResourceLocation TEXTURE_ELYTRA = new ResourceLocation("textures/entity/elytra.png");
private static final ModelPlayer FAT = new ModelPlayer(0, false);
private static final ModelPlayer THIN = new ModelPlayer(0, true);
public RenderPlayerModel(RenderManager renderer) {
super(renderer, FAT, 0);
this.addLayer(this.getElytraLayer());
}
protected LayerRenderer<? extends EntityLivingBase> getElytraLayer() {
final ModelElytra modelElytra = new ModelElytra();
return new LayerRenderer<EntityLivingBase>() {
@Override
public void render(EntityLivingBase entityBase, float limbSwing, float limbSwingAmount, float partialTicks, float ageInTicks, float netHeadYaw, float headPitch, float scale) {
EntityPlayerModel entity = (EntityPlayerModel) entityBase;
ItemStack itemstack = entity.getItemStackFromSlot(EntityEquipmentSlot.CHEST);
if (itemstack.getItem() == Items.ELYTRA) {
GlStateManager.color4f(1, 1, 1, 1);
GlStateManager.enableBlend();
GlStateManager.blendFunc(GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO);
bindTexture(entity.getLocal(Type.ELYTRA).getTexture());
GlStateManager.pushMatrix();
GlStateManager.translatef(0, 0, 0.125F);
modelElytra.setRotationAngles(limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch, scale, entity);
modelElytra.render(entity, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch, scale);
GlStateManager.disableBlend();
GlStateManager.popMatrix();
}
}
@Override
public boolean shouldCombineTextures() {
return false;
}
};
}
@Override
protected ResourceLocation getEntityTexture(M entity) {
return entity.getLocal(Type.SKIN).getTexture();
}
@Override
protected boolean canRenderName(M entity) {
return Minecraft.getInstance().player != null && super.canRenderName(entity);
}
@Override
protected boolean setBrightness(M entity, float partialTicks, boolean combineTextures) {
return Minecraft.getInstance().world != null && super.setBrightness(entity, partialTicks, combineTextures);
}
public ModelPlayer getEntityModel(M entity) {
return entity.usesThinSkin() ? THIN : FAT;
}
@Override
public void doRender(M entity, double x, double y, double z, float entityYaw, float partialTicks) {
if (entity.isPlayerSleeping()) {
BedHead.instance.render(entity);
}
if (entity.getRidingEntity() != null) {
MrBoaty.instance.render();
}
ModelPlayer player = getEntityModel(entity);
mainModel = player;
Set<EnumPlayerModelParts> parts = Minecraft.getInstance().gameSettings.getModelParts();
player.bipedHeadwear.isHidden = !parts.contains(EnumPlayerModelParts.HAT);
player.bipedBodyWear.isHidden = !parts.contains(EnumPlayerModelParts.JACKET);
player.bipedLeftLegwear.isHidden = !parts.contains(EnumPlayerModelParts.LEFT_PANTS_LEG);
player.bipedRightLegwear.isHidden = !parts.contains(EnumPlayerModelParts.RIGHT_PANTS_LEG);
player.bipedLeftArmwear.isHidden = !parts.contains(EnumPlayerModelParts.LEFT_SLEEVE);
player.bipedRightArmwear.isHidden = !parts.contains(EnumPlayerModelParts.RIGHT_SLEEVE);
player.isSneak = entity.isSneaking();
player.leftArmPose = ArmPose.EMPTY;
player.rightArmPose = ArmPose.EMPTY;
GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
double offset = entity.getYOffset() + entity.posY;
if (entity.isPlayerSleeping()) {
y--;
z += 0.5F;
} else if (player.isSneak) {
y -= 0.125D;
}
pushMatrix();
enableBlend();
color4f(1, 1, 1, 0.3F);
translated(0, offset, 0);
if (entity.isPlayerSleeping()) {
rotatef(-90, 1, 0, 0);
}
super.doRender(entity, x, y, z, entityYaw, partialTicks);
color4f(1, 1, 1, 1);
disableBlend();
popMatrix();
GL11.glPopAttrib();
GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
pushMatrix();
scalef(1, -1, 1);
translated(0.001, offset, 0.001);
if (entity.isPlayerSleeping()) {
rotatef(-90, 1, 0, 0);
}
super.doRender(entity, x, y, z, entityYaw, partialTicks);
popMatrix();
GL11.glPopAttrib();
}
static class BedHead extends TileEntityBed {
public static BedHead instance = new BedHead(Blocks.RED_BED.getDefaultState());
public IBlockState state;
public BedHead(IBlockState state) {
this.state = state;
}
@Override
public IBlockState getBlockState() {
return state;
}
public void render(Entity entity) {
GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
pushMatrix();
scalef(-1, -1, -1);
TileEntityRendererDispatcher dispatcher = TileEntityRendererDispatcher.instance;
dispatcher.prepare(entity.getEntityWorld(), Minecraft.getInstance().getTextureManager(), Minecraft.getInstance().getRenderManager().getFontRenderer(), entity, null, 0);
dispatcher.getRenderer(this).render(BedHead.instance, -0.5F, 0, 0, 0, -1);
popMatrix();
GL11.glPopAttrib();
}
}
static class MrBoaty extends EntityBoat {
public static MrBoaty instance = new MrBoaty();
public MrBoaty() {
super(null);
}
public void render() {
GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
pushMatrix();
scalef(-1, -1, -1);
Render<EntityBoat> render = Minecraft.getInstance().getRenderManager().getEntityRenderObject(this);
render.doRender(this, 0, 0, 0, 0, 0);
popMatrix();
GL11.glPopAttrib();
}
}
}

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package com.minelittlepony.hdskins.gui;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,24 @@
package com.minelittlepony.hdskins.mixin;
import com.minelittlepony.common.client.gui.IconicButton;
import com.minelittlepony.hdskins.HDSkinManager;
import net.minecraft.client.gui.GuiMainMenu;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
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.CallbackInfo;
@Mixin(GuiMainMenu.class)
public class MixinGuiMainMenu extends GuiScreen {
@Inject(method = "initGui()V", at = @At("RETURN"))
private void onInit(CallbackInfo ci) {
addButton(new IconicButton(width - 50, height - 50, sender -> {
mc.displayGuiScreen(HDSkinManager.INSTANCE.createSkinsGui());
}).setIcon(new ItemStack(Items.LEATHER_LEGGINGS), 0x3c5dcb));
}
}

View file

@ -0,0 +1,30 @@
package com.minelittlepony.hdskins.mixin;
import net.minecraft.client.renderer.IImageBuffer;
import net.minecraft.client.renderer.ImageBufferDownload;
import net.minecraft.client.renderer.texture.NativeImage;
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;
import com.minelittlepony.hdskins.HDSkinManager;
import com.minelittlepony.hdskins.resources.texture.SimpleDrawer;
@Mixin(ImageBufferDownload.class)
public abstract class MixinImageBufferDownload implements IImageBuffer {
@Inject(
method = "parseUserSkin(Lnet/minecraft/client/renderer/texture/NativeImage;)Lnet/minecraft/client/renderer/texture/NativeImage;",
at = @At("RETURN"),
cancellable = true)
private void update(NativeImage image, CallbackInfoReturnable<NativeImage> ci) {
// convert skins from mojang server
NativeImage image2 = ci.getReturnValue();
boolean isLegacy = image.getHeight() == 32;
if (isLegacy) {
HDSkinManager.INSTANCE.convertSkin((SimpleDrawer)(() -> image2));
}
}
}

View file

@ -0,0 +1,26 @@
/*package com.minelittlepony.hdskins.mixin;
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.CallbackInfo;
import com.minelittlepony.hdskins.upload.GLWindow;
import net.minecraft.client.Minecraft;
import net.minecraft.crash.CrashReport;
/**
* I removed it. :T
*
* /
@Mixin(value = Minecraft.class, priority = 9000)
public abstract class MixinMinecraft {
//public void displayCrashReport(CrashReport crashReportIn)
@Inject(method = "displayCrashReport(Lnet/minecraft/crash/CrashReport;)V", at = @At("HEAD"))
private void onGameCrash(CrashReport report, CallbackInfo info) {
GLWindow.dispose();
}
}
*/

View file

@ -0,0 +1,117 @@
package com.minelittlepony.hdskins.mixin;
import com.minelittlepony.hdskins.HDSkinManager;
import com.minelittlepony.hdskins.ducks.INetworkPlayerInfo;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
@Mixin(NetworkPlayerInfo.class)
public abstract class MixinNetworkPlayerInfo implements INetworkPlayerInfo {
private Map<Type, ResourceLocation> customTextures = new HashMap<>();
private Map<Type, MinecraftProfileTexture> customProfiles = new HashMap<>();
private Map<Type, MinecraftProfileTexture> vanillaProfiles = new HashMap<>();
@Shadow
@Final
private GameProfile gameProfile;
@Shadow
Map<Type, ResourceLocation> playerTextures;
@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;", remap = false))
// synthetic
private Object getSkin(Map<Type, ResourceLocation> playerTextures, Object key) {
return getSkin(playerTextures, (Type) key);
}
// with generics
private ResourceLocation getSkin(Map<Type, ResourceLocation> playerTextures, Type type) {
if (this.customTextures.containsKey(type)) {
return this.customTextures.get(type);
}
return playerTextures.get(type);
}
@Nullable
@Redirect(method = "getSkinType",
at = @At(value = "FIELD", target = "Lnet/minecraft/client/network/NetworkPlayerInfo;skinType:Ljava/lang/String;"))
private String getTextureModel(NetworkPlayerInfo self) {
String model = getModelFrom(customProfiles);
if (model != null) {
return model;
}
return getModelFrom(vanillaProfiles);
}
@Nullable
private static String getModelFrom(Map<Type, MinecraftProfileTexture> texture) {
if (texture.containsKey(Type.SKIN)) {
String model = texture.get(Type.SKIN).getMetadata("model");
return model != null ? model : "default";
}
return null;
}
@Inject(method = "loadPlayerTextures",
at = @At(value = "INVOKE",
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.fetchAndLoadSkins(gameProfile, (type, location, profileTexture) -> {
customTextures.put(type, location);
customProfiles.put(type, profileTexture);
});
}
@Redirect(method = "loadPlayerTextures",
at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/resources/SkinManager;loadProfileTextures("
+ "Lcom/mojang/authlib/GameProfile;"
+ "Lnet/minecraft/client/resources/SkinManager$SkinAvailableCallback;"
+ "Z)V"))
private void redirectLoadPlayerTextures(SkinManager skinManager, GameProfile profile, SkinManager.SkinAvailableCallback callback, boolean requireSecure) {
skinManager.loadProfileTextures(profile, (typeIn, location, profileTexture) -> {
HDSkinManager.INSTANCE.parseSkin(profile, typeIn, location, profileTexture).thenAccept(v -> {
playerTextures.put(typeIn, location);
vanillaProfiles.put(typeIn, profileTexture);
});
}, requireSecure);
}
@Override
public void reloadTextures() {
synchronized (this) {
for (Map.Entry<Type, MinecraftProfileTexture> entry : customProfiles.entrySet()) {
HDSkinManager.INSTANCE.parseSkin(gameProfile, entry.getKey(), customTextures.get(entry.getKey()), entry.getValue());
}
for (Map.Entry<Type, MinecraftProfileTexture> entry : vanillaProfiles.entrySet()) {
HDSkinManager.INSTANCE.parseSkin(gameProfile, entry.getKey(), playerTextures.get(entry.getKey()), entry.getValue());
}
}
}
}

View file

@ -0,0 +1,37 @@
package com.minelittlepony.hdskins.mixin;
import com.minelittlepony.hdskins.HDSkinManager;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
import net.minecraft.client.renderer.tileentity.TileEntitySkullRenderer;
import net.minecraft.client.renderer.tileentity.TileEntityRenderer;
import net.minecraft.tileentity.TileEntitySkull;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.ResourceLocation;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import javax.annotation.Nullable;
@Mixin(TileEntitySkullRenderer.class)
public abstract class MixinSkullRenderer extends TileEntityRenderer<TileEntitySkull> {
@Redirect(method = "renderSkull",
at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/renderer/tileentity/TileEntitySkullRenderer;bindTexture(Lnet/minecraft/util/ResourceLocation;)V",
ordinal = 4))
private void onBindTexture(TileEntitySkullRenderer tesr, ResourceLocation rl, float x, float y, float z, EnumFacing facing, float rotation, int meta, @Nullable GameProfile profile, int p_180543_8_, float ticks) {
if (profile != null) {
ResourceLocation skin = HDSkinManager.INSTANCE.getTextures(profile).get(Type.SKIN);
if (skin != null) {
rl = skin;
}
}
bindTexture(rl);
}
}

View file

@ -0,0 +1,17 @@
package com.minelittlepony.hdskins.mixin;
import net.minecraft.client.renderer.texture.ThreadDownloadImageData;
import net.minecraft.client.renderer.texture.NativeImage;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import com.minelittlepony.hdskins.resources.texture.IBufferedTexture;
@Mixin(ThreadDownloadImageData.class)
public interface MixinThreadDownloadImageData extends IBufferedTexture {
@Accessor("bufferedImage")
@Override
NativeImage getBufferedImage();
}

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package com.minelittlepony.hdskins.mixin;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package com.minelittlepony.hdskins;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,42 @@
package com.minelittlepony.hdskins.resources;
import com.google.common.cache.CacheLoader;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListenableFutureTask;
import java.util.Map;
import java.util.concurrent.Executor;
public class AsyncCacheLoader<K, V> extends CacheLoader<K, V> {
public static <K, V> AsyncCacheLoader<K, V> create(CacheLoader<K, V> loader, V placeholder, Executor executor) {
return new AsyncCacheLoader<>(loader, placeholder, executor);
}
private final CacheLoader<K, V> loader;
private final V placeholder;
private final Executor executor;
private AsyncCacheLoader(CacheLoader<K, V> loader, V placeholder, Executor executor) {
this.executor = executor;
this.placeholder = placeholder;
this.loader = loader;
}
@Override
public V load(K key) {
return placeholder;
}
@Override
public ListenableFuture<V> reload(final K key, final V oldValue) {
ListenableFutureTask<V> task = ListenableFutureTask.create(() -> loader.reload(key, oldValue).get());
executor.execute(task);
return task;
}
@Override
public Map<K, V> loadAll(Iterable<? extends K> keys) throws Exception {
return loader.loadAll(keys);
}
}

View file

@ -0,0 +1,69 @@
package com.minelittlepony.hdskins.resources;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.NativeImage;
import net.minecraft.util.ResourceLocation;
import com.minelittlepony.hdskins.resources.texture.DynamicTextureImage;
import com.minelittlepony.hdskins.resources.texture.ImageBufferDownloadHD;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;
import javax.annotation.Nullable;
public class ImageLoader implements Supplier<ResourceLocation> {
private static Minecraft mc = Minecraft.getInstance();
private final ResourceLocation original;
public ImageLoader(ResourceLocation loc) {
this.original = loc;
}
@Override
@Nullable
public ResourceLocation get() {
NativeImage image = getImage(original);
final NativeImage updated = new ImageBufferDownloadHD().parseUserSkin(image);
if (updated == null) {
return null;
}
if (updated == image) {
// don't load a new image
return this.original;
}
return addTaskAndGet(() -> loadSkin(updated));
}
private static <V> V addTaskAndGet(Callable<V> callable) {
try {
return mc.addScheduledTask(callable).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@Nullable
private static NativeImage getImage(ResourceLocation res) {
try (InputStream in = mc.getResourceManager().getResource(res).getInputStream()) {
return NativeImage.read(in);
} catch (IOException e) {
return null;
}
}
@Nullable
private ResourceLocation loadSkin(NativeImage image) {
ResourceLocation conv = new ResourceLocation(original.getNamespace() + "-converted", original.getPath());
boolean success = mc.getTextureManager().loadTexture(conv, new DynamicTextureImage(image));
return success ? conv : null;
}
}

View file

@ -0,0 +1,133 @@
package com.minelittlepony.hdskins.resources;
import com.minelittlepony.hdskins.resources.texture.DynamicTextureImage;
import com.minelittlepony.hdskins.resources.texture.ImageBufferDownloadHD;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.client.renderer.texture.NativeImage;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.resources.SkinManager.SkinAvailableCallback;
import net.minecraft.util.ResourceLocation;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class LocalTexture {
private final TextureManager textureManager = Minecraft.getInstance().getTextureManager();
private DynamicTexture local;
private PreviewTexture remote;
private ResourceLocation remoteResource;
private ResourceLocation localResource;
private final IBlankSkinSupplier blank;
private final Type type;
private boolean remoteLoaded = false;
public LocalTexture(GameProfile profile, Type type, IBlankSkinSupplier blank) {
this.blank = blank;
this.type = type;
String file = String.format("%s/preview_%s.png", type.name().toLowerCase(), profile.getName());
remoteResource = new ResourceLocation(file);
textureManager.deleteTexture(remoteResource);
reset();
}
public ResourceLocation getTexture() {
if (hasRemote()) {
return remoteResource;
}
return localResource;
}
public void reset() {
localResource = blank.getBlankSkin(type);
}
public boolean hasRemote() {
return remote != null;
}
public boolean hasLocal() {
return local != null;
}
public boolean hasRemoteTexture() {
return uploadComplete() && remoteLoaded;
}
public boolean usingLocal() {
return !hasRemote() && hasLocal();
}
public boolean uploadComplete() {
return hasRemote() && remote.isTextureUploaded();
}
public PreviewTexture getRemote() {
return remote;
}
public void setRemote(PreviewTextureManager ptm, SkinAvailableCallback callback) {
clearRemote();
remote = ptm.getPreviewTexture(remoteResource, type, blank.getBlankSkin(type), (type, location, profileTexture) -> {
if (callback != null) {
callback.onSkinTextureAvailable(type, location, profileTexture);
}
remoteLoaded = true;
});
}
public void setLocal(File file) {
if (!file.exists()) {
return;
}
clearLocal();
try (FileInputStream input = new FileInputStream(file)) {
NativeImage image = NativeImage.read(input);
NativeImage bufferedImage = new ImageBufferDownloadHD().parseUserSkin(image);
local = new DynamicTextureImage(bufferedImage);
localResource = textureManager.getDynamicTextureLocation("localSkinPreview", local);
} catch (IOException e) {
e.printStackTrace();
}
}
private void clearRemote() {
remoteLoaded = false;
if (hasRemote()) {
remote = null;
textureManager.deleteTexture(remoteResource);
}
}
public void clearLocal() {
if (hasLocal()) {
local = null;
textureManager.deleteTexture(localResource);
localResource = blank.getBlankSkin(type);
}
}
public interface IBlankSkinSupplier {
ResourceLocation getBlankSkin(Type type);
}
}

View file

@ -0,0 +1,47 @@
package com.minelittlepony.hdskins.resources;
import net.minecraft.client.renderer.IImageBuffer;
import net.minecraft.client.renderer.texture.ThreadDownloadImageData;
import net.minecraft.util.ResourceLocation;
import com.minelittlepony.hdskins.VanillaModels;
import javax.annotation.Nullable;
public class PreviewTexture extends ThreadDownloadImageData {
private boolean uploaded;
private String model;
private String fileUrl;
public PreviewTexture(@Nullable String model, String url, ResourceLocation fallbackTexture, @Nullable IImageBuffer imageBuffer) {
super(null, url, fallbackTexture, imageBuffer);
this.model = VanillaModels.nonNull(model);
this.fileUrl = url;
}
public boolean isTextureUploaded() {
return uploaded && getGlTextureId() > -1;
}
public String getUrl() {
return fileUrl;
}
@Override
public void deleteGlTexture() {
super.deleteGlTexture();
uploaded = true;
}
public boolean hasModel() {
return model != null;
}
public boolean usesThinArms() {
return VanillaModels.isSlim(model);
}
}

View file

@ -0,0 +1,47 @@
package com.minelittlepony.hdskins.resources;
import com.google.common.collect.Maps;
import com.minelittlepony.hdskins.resources.texture.ISkinAvailableCallback;
import com.minelittlepony.hdskins.resources.texture.ImageBufferDownloadHD;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import net.minecraft.client.resources.SkinManager;
import net.minecraft.util.ResourceLocation;
import java.util.Map;
import javax.annotation.Nullable;
/**
* Manager for fetching preview textures. This ensures that multiple calls
* to the skin server aren't done when fetching preview textures.
*/
public class PreviewTextureManager {
private final Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> textures;
public PreviewTextureManager(MinecraftTexturesPayload payload) {
this.textures = payload.getTextures();
}
@Nullable
public PreviewTexture getPreviewTexture(ResourceLocation location, MinecraftProfileTexture.Type type, ResourceLocation def, @Nullable SkinManager.SkinAvailableCallback callback) {
if (!textures.containsKey(type)) {
return null;
}
MinecraftProfileTexture texture = textures.get(type);
ISkinAvailableCallback buff = new ImageBufferDownloadHD(type, () -> {
if (callback != null) {
callback.onSkinTextureAvailable(type, location, new MinecraftProfileTexture(texture.getUrl(), Maps.newHashMap()));
}
});
PreviewTexture skinTexture = new PreviewTexture(texture.getMetadata("model"), texture.getUrl(), def, buff);
TextureLoader.loadTexture(location, skinTexture);
return skinTexture;
}
}

View file

@ -0,0 +1,24 @@
package com.minelittlepony.hdskins.resources;
import net.minecraft.util.ResourceLocation;
import java.util.List;
import java.util.UUID;
@SuppressWarnings("unused")
class SkinData {
List<Skin> skins;
}
@SuppressWarnings("unused")
class Skin {
String name;
UUID uuid;
String skin;
public ResourceLocation getTexture() {
return new ResourceLocation("hdskins", String.format("textures/skins/%s.png", skin));
}
}

View file

@ -0,0 +1,131 @@
package com.minelittlepony.hdskins.resources;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
import net.minecraft.resources.IResource;
import net.minecraft.resources.IResourceManager;
import net.minecraft.resources.IResourceManagerReloadListener;
import net.minecraft.util.ResourceLocation;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class SkinResourceManager implements IResourceManagerReloadListener {
private static final Logger logger = LogManager.getLogger();
private ExecutorService executor = Executors.newSingleThreadExecutor();
private Map<UUID, Skin> uuidSkins = Maps.newHashMap();
private Map<String, Skin> namedSkins = Maps.newHashMap();
private Map<ResourceLocation, Future<ResourceLocation>> inProgress = Maps.newHashMap();
private Map<ResourceLocation, ResourceLocation> converted = Maps.newHashMap();
@Override
public void onResourceManagerReload(IResourceManager resourceManager) {
uuidSkins.clear();
namedSkins.clear();
executor.shutdownNow();
executor = Executors.newSingleThreadExecutor();
inProgress.clear();
converted.clear();
for (String domain : resourceManager.getResourceNamespaces()) {
try {
for (IResource res : resourceManager.getAllResources(new ResourceLocation(domain, "textures/skins/skins.json"))) {
try {
SkinData data = getSkinData(res.getInputStream());
for (Skin s : data.skins) {
if (s.uuid != null) {
uuidSkins.put(s.uuid, s);
}
if (s.name != null) {
namedSkins.put(s.name, s);
}
}
} catch (JsonParseException je) {
logger.warn("Invalid skins.json in " + res.getPackName(), je);
}
}
} catch (IOException e) {
// ignore
}
}
}
private SkinData getSkinData(InputStream stream) {
try {
return new Gson().fromJson(new InputStreamReader(stream), SkinData.class);
} finally {
IOUtils.closeQuietly(stream);
}
}
@Nullable
public ResourceLocation getPlayerTexture(GameProfile profile, Type type) {
if (type != Type.SKIN)
// not supported
return null;
Skin skin = getSkin(profile);
if (skin != null) {
final ResourceLocation res = skin.getTexture();
return getConvertedResource(res);
}
return null;
}
/**
* Convert older resources to a newer format.
*
* @param res The skin resource to convert
* @return The converted resource
*/
@Nullable
public ResourceLocation getConvertedResource(@Nullable ResourceLocation res) {
loadSkinResource(res);
return converted.get(res);
}
private void loadSkinResource(@Nullable final ResourceLocation res) {
if (res != null) {
// read and convert in a new thread
this.inProgress.computeIfAbsent(res, r -> CompletableFuture.supplyAsync(new ImageLoader(r), executor)
.whenComplete((loc, t) -> {
if (loc != null)
converted.put(res, loc);
else {
LogManager.getLogger().warn("Errored while processing {}. Using original.", res, t);
converted.put(res, res);
}
}));
}
}
@Nullable
private Skin getSkin(GameProfile profile) {
Skin skin = this.uuidSkins.get(profile.getId());
if (skin == null) {
skin = this.namedSkins.get(profile.getName());
}
return skin;
}
}

View file

@ -0,0 +1,14 @@
package com.minelittlepony.hdskins.resources;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.ITextureObject;
import net.minecraft.util.ResourceLocation;
public class TextureLoader {
private static Minecraft mc = Minecraft.getInstance();
public static void loadTexture(final ResourceLocation textureLocation, final ITextureObject textureObj) {
mc.addScheduledTask((Runnable) () -> mc.getTextureManager().loadTexture(textureLocation, textureObj));
}
}

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package com.minelittlepony.hdskins.resources;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,26 @@
package com.minelittlepony.hdskins.resources.texture;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.client.renderer.texture.NativeImage;
public class DynamicTextureImage extends DynamicTexture implements IBufferedTexture {
private NativeImage image;
public DynamicTextureImage(NativeImage bufferedImage) {
super(bufferedImage);
this.image = bufferedImage;
}
@Override
public NativeImage getBufferedImage() {
return image;
}
@Override
public void deleteGlTexture() {
super.deleteGlTexture();
this.image = null;
}
}

View file

@ -0,0 +1,11 @@
package com.minelittlepony.hdskins.resources.texture;
import net.minecraft.client.renderer.texture.NativeImage;
import javax.annotation.Nullable;
public interface IBufferedTexture {
@Nullable
NativeImage getBufferedImage();
}

View file

@ -0,0 +1,12 @@
package com.minelittlepony.hdskins.resources.texture;
import net.minecraft.client.renderer.IImageBuffer;
import net.minecraft.client.renderer.texture.NativeImage;
@FunctionalInterface
public interface ISkinAvailableCallback extends IImageBuffer {
@Override
default NativeImage parseUserSkin(NativeImage image) {
return image;
}
}

View file

@ -0,0 +1,89 @@
package com.minelittlepony.hdskins.resources.texture;
import net.minecraft.client.renderer.texture.NativeImage;
import com.minelittlepony.hdskins.HDSkinManager;
import com.minelittlepony.hdskins.ISkinModifier;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
import javax.annotation.Nullable;
import java.awt.Graphics;
public class ImageBufferDownloadHD implements SimpleDrawer, ISkinAvailableCallback, ISkinModifier.IDrawer {
private int scale;
private Graphics graphics;
private NativeImage image;
private ISkinAvailableCallback callback = null;
private Type skinType = Type.SKIN;
public ImageBufferDownloadHD() {
}
public ImageBufferDownloadHD(Type type, ISkinAvailableCallback callback) {
this.callback = callback;
this.skinType = type;
}
@Override
@Nullable
@SuppressWarnings({"SuspiciousNameCombination", "NullableProblems"})
public NativeImage parseUserSkin(@Nullable NativeImage downloadedImage) {
// TODO: Do we want to convert other skin types?
if (downloadedImage == null || skinType != Type.SKIN) {
return downloadedImage;
}
int imageWidth = downloadedImage.getWidth();
int imageHeight = downloadedImage.getHeight();
if (imageHeight == imageWidth) {
return downloadedImage;
}
scale = imageWidth / 64;
image = new NativeImage(imageWidth, imageWidth, true);
image.copyImageData(downloadedImage);
// copy layers
// leg
draw(scale, 24, 48, 20, 52, 4, 16, 8, 20); // top
draw(scale, 28, 48, 24, 52, 8, 16, 12, 20); // bottom
draw(scale, 20, 52, 16, 64, 8, 20, 12, 32); // inside
draw(scale, 24, 52, 20, 64, 4, 20, 8, 32); // front
draw(scale, 28, 52, 24, 64, 0, 20, 4, 32); // outside
draw(scale, 32, 52, 28, 64, 12, 20, 16, 32); // back
// arm
draw(scale, 40, 48, 36, 52, 44, 16, 48, 20); // top
draw(scale, 44, 48, 40, 52, 48, 16, 52, 20); // bottom
draw(scale, 36, 52, 32, 64, 48, 20, 52, 32);
draw(scale, 40, 52, 36, 64, 44, 20, 48, 32);
draw(scale, 44, 52, 40, 64, 40, 20, 44, 32);
draw(scale, 48, 52, 44, 64, 52, 20, 56, 32);
// mod things
HDSkinManager.INSTANCE.convertSkin(this);
graphics.dispose();
if (callback != null) {
return callback.parseUserSkin(image);
}
return image;
}
@Override
public void skinAvailable() {
if (callback != null) {
callback.skinAvailable();
}
}
@Override
public NativeImage getImage() {
return image;
}
}

View file

@ -0,0 +1,41 @@
package com.minelittlepony.hdskins.resources.texture;
import com.minelittlepony.hdskins.ISkinModifier;
public interface SimpleDrawer extends ISkinModifier.IDrawer {
@Override
default void draw(int scale,
/*destination: */ int dx1, int dy1, int dx2, int dy2,
/*source: */ int sx1, int sy1, int sx2, int sy2) {
int srcX = sx1 * scale;
int srcY = sy1 * scale;
int dstX = dx1 * scale;
int dstY = dy1 * scale;
int width = (sx2 - sx1) * scale;
int height = (sy2 - sy1) * scale;
getImage().copyAreaRGBA(srcX, srcY, dstX, dstY, width, height, false, false);
}
/*public void copyAreaRGBA(int srcX, int srcY, int dstX, int dstY, int width, int height, boolean flipX, boolean flipY) {
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
int shiftX = flipX ? width - 1 - x : x;
int shiftY = flipY ? height - 1 - y : y;
int value = image.getPixelRGBA(srcX + x, srcY + y);
image.setPixelRGBA(
srcX + dstX + shiftX,
srcY + dstY + shiftY,
value
);
}
}
}*/
}

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package com.minelittlepony.hdskins.resources.texture;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,101 @@
package com.minelittlepony.hdskins.server;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.gson.annotations.Expose;
import com.minelittlepony.hdskins.gui.Feature;
import com.minelittlepony.hdskins.util.IndentedToStringStyle;
import com.minelittlepony.hdskins.util.MoreHttpResponses;
import com.minelittlepony.hdskins.util.NetClient;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.exceptions.AuthenticationException;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.mojang.util.UUIDTypeAdapter;
import java.io.IOException;
import java.util.Locale;
import java.util.Map;
@ServerType("bethlehem")
public class BethlehemSkinServer implements SkinServer {
private static final String SERVER_ID = "7853dfddc358333843ad55a2c7485c4aa0380a51";
@Expose
private final String address;
private BethlehemSkinServer(String address) {
this.address = address;
}
@Override
public MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException {
try (MoreHttpResponses response = new NetClient("GET", getPath(profile)).send()) {
if (!response.ok()) {
throw new IOException(response.getResponse().getStatusLine().getReasonPhrase());
}
return response.json(MinecraftTexturesPayload.class);
}
}
@Override
public SkinUploadResponse performSkinUpload(SkinUpload upload) throws IOException, AuthenticationException {
SkinServer.verifyServerConnection(upload.getSession(), SERVER_ID);
NetClient client = new NetClient("POST", address);
client.putHeaders(createHeaders(upload));
if (upload.getImage() != null) {
client.putFile(upload.getType().toString().toLowerCase(Locale.US), "image/png", upload.getImage());
}
try (MoreHttpResponses response = client.send()) {
if (!response.ok()) {
throw new IOException(response.text());
}
return new SkinUploadResponse(response.text());
}
}
protected Map<String, ?> createHeaders(SkinUpload upload) {
Builder<String, Object> builder = ImmutableMap.<String, Object>builder()
.put("accessToken", upload.getSession().getToken())
.put("user", upload.getSession().getUsername())
.put("uuid", UUIDTypeAdapter.fromUUID(upload.getSession().getProfile().getId()))
.put("type", upload.getType().toString().toLowerCase(Locale.US));
if (upload.getImage() == null) {
builder.put("clear", "1");
} else {
builder.put("model", upload.getMetadata().getOrDefault("model", "default"));
}
return builder.build();
}
private String getPath(GameProfile profile) {
return String.format("%s/profile/%s", address, UUIDTypeAdapter.fromUUID(profile.getId()));
}
@Override
public String toString() {
return new IndentedToStringStyle.Builder(this)
.append("address", address)
.build();
}
@Override
public boolean supportsFeature(Feature feature) {
switch (feature) {
case DOWNLOAD_USER_SKIN:
case UPLOAD_USER_SKIN:
case MODEL_VARIANTS:
case MODEL_TYPES:
case LINK_PROFILE:
return true;
default: return false;
}
}
}

View file

@ -0,0 +1,188 @@
package com.minelittlepony.hdskins.server;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.gson.annotations.Expose;
import com.minelittlepony.hdskins.HDSkinManager;
import com.minelittlepony.hdskins.gui.Feature;
import com.minelittlepony.hdskins.util.CallableFutures;
import com.minelittlepony.hdskins.util.IndentedToStringStyle;
import com.minelittlepony.hdskins.util.MoreHttpResponses;
import com.minelittlepony.hdskins.util.NetClient;
import com.minelittlepony.hdskins.util.TexturesPayloadBuilder;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.exceptions.AuthenticationException;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.mojang.util.UUIDTypeAdapter;
import net.minecraft.client.Minecraft;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpHeaders;
import org.apache.http.client.methods.HttpHead;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.util.Date;
import java.util.EnumMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import javax.annotation.Nullable;
@ServerType("legacy")
public class LegacySkinServer implements SkinServer {
private static final String SERVER_ID = "7853dfddc358333843ad55a2c7485c4aa0380a51";
private static final Logger logger = LogManager.getLogger();
@Expose
private final String address;
@Expose
private final String gateway;
public LegacySkinServer(String address, @Nullable String gateway) {
this.address = address;
this.gateway = gateway;
}
@Override
public CompletableFuture<MinecraftTexturesPayload> getPreviewTextures(GameProfile profile) {
return CallableFutures.asyncFailableFuture(() -> {
SkinServer.verifyServerConnection(Minecraft.getInstance().getSession(), SERVER_ID);
if (Strings.isNullOrEmpty(gateway)) {
throw gatewayUnsupported();
}
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> map = new EnumMap<>(MinecraftProfileTexture.Type.class);
for (MinecraftProfileTexture.Type type : MinecraftProfileTexture.Type.values()) {
map.put(type, new MinecraftProfileTexture(getPath(gateway, type, profile), null));
}
return TexturesPayloadBuilder.createTexturesPayload(profile, map);
}, HDSkinManager.skinDownloadExecutor);
}
@Override
public MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException {
ImmutableMap.Builder<MinecraftProfileTexture.Type, MinecraftProfileTexture> builder = ImmutableMap.builder();
for (MinecraftProfileTexture.Type type : MinecraftProfileTexture.Type.values()) {
String url = getPath(address, type, profile);
try {
builder.put(type, loadProfileTexture(profile, url));
} catch (IOException e) {
logger.trace("Couldn't find texture for {} at {}. Does it exist?", profile.getName(), url, e);
}
}
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> map = builder.build();
if (map.isEmpty()) {
throw new IOException(String.format("No textures found for %s at %s", profile, this.address));
}
return TexturesPayloadBuilder.createTexturesPayload(profile, map);
}
private MinecraftProfileTexture loadProfileTexture(GameProfile profile, String url) throws IOException {
try (MoreHttpResponses resp = MoreHttpResponses.execute(HDSkinManager.httpClient, new HttpHead(url))) {
if (!resp.ok()) {
throw new IOException("Bad response code: " + resp.getResponseCode() + ". URL: " + url);
}
logger.debug("Found skin for {} at {}", profile.getName(), url);
Header eTagHeader = resp.getResponse().getFirstHeader(HttpHeaders.ETAG);
final String eTag = eTagHeader == null ? "" : StringUtils.strip(eTagHeader.getValue(), "\"");
// Add the ETag onto the end of the texture hash. Should properly cache the textures.
return new MinecraftProfileTexture(url, null) {
@Override
public String getHash() {
return super.getHash() + eTag;
}
};
}
}
@Override
public SkinUploadResponse performSkinUpload(SkinUpload upload) throws IOException, AuthenticationException {
if (Strings.isNullOrEmpty(gateway)) {
throw gatewayUnsupported();
}
SkinServer.verifyServerConnection(upload.getSession(), SERVER_ID);
NetClient client = new NetClient("POST", gateway);
client.putFormData(createHeaders(upload), "image/png");
if (upload.getImage() != null) {
client.putFile(upload.getType().toString().toLowerCase(Locale.US), "image/png", upload.getImage());
}
String response = client.send().text();
if (response.startsWith("ERROR: ")) {
response = response.substring(7);
}
if (!response.equalsIgnoreCase("OK") && !response.endsWith("OK")) {
throw new IOException(response);
}
return new SkinUploadResponse(response);
}
private UnsupportedOperationException gatewayUnsupported() {
return new UnsupportedOperationException("Server does not have a gateway.");
}
private Map<String, ?> createHeaders(SkinUpload upload) {
Builder<String, Object> builder = ImmutableMap.<String, Object>builder()
.put("user", upload.getSession().getUsername())
.put("uuid", UUIDTypeAdapter.fromUUID(upload.getSession().getProfile().getId()))
.put("type", upload.getType().toString().toLowerCase(Locale.US));
if (upload.getImage() == null) {
builder.put("clear", "1");
}
return builder.build();
}
private static String getPath(String address, MinecraftProfileTexture.Type type, GameProfile profile) {
String uuid = UUIDTypeAdapter.fromUUID(profile.getId());
String path = type.toString().toLowerCase() + "s";
return String.format("%s/%s/%s.png?%s", address, path, uuid, Long.toString(new Date().getTime() / 1000));
}
@Override
public boolean verifyGateway() {
return !Strings.isNullOrEmpty(gateway);
}
@Override
public boolean supportsFeature(Feature feature) {
switch (feature) {
case DOWNLOAD_USER_SKIN:
case UPLOAD_USER_SKIN:
case DELETE_USER_SKIN:
return true;
default: return false;
}
}
@Override
public String toString() {
return new IndentedToStringStyle.Builder(this)
.append("address", address)
.append("gateway", gateway)
.build();
}
}

View file

@ -0,0 +1,21 @@
package com.minelittlepony.hdskins.server;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* The remote server API level that this skin server implements.
*
* Current values are:
* - legacy
* - valhalla
* - bethlehem
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ServerType {
String value();
}

View file

@ -0,0 +1,99 @@
package com.minelittlepony.hdskins.server;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.minelittlepony.hdskins.HDSkinManager;
import com.minelittlepony.hdskins.gui.Feature;
import com.minelittlepony.hdskins.util.CallableFutures;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.exceptions.AuthenticationException;
import com.mojang.authlib.minecraft.MinecraftSessionService;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.mojang.util.UUIDTypeAdapter;
import net.minecraft.client.Minecraft;
import net.minecraft.util.Session;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public interface SkinServer {
Gson gson = new GsonBuilder()
.registerTypeAdapter(UUID.class, new UUIDTypeAdapter())
.create();
List<SkinServer> defaultServers = Lists.newArrayList(new LegacySkinServer(
"http://skins.voxelmodpack.com",
"http://skinmanager.voxelmodpack.com")
);
/**
* Returns true for any features that this skin server supports.
*/
boolean supportsFeature(Feature feature);
/**
* Synchronously loads texture information for the provided profile.
*
* @return The parsed server response as a textures payload.
*
* @throws IOException If any authenticaiton or network error occurs.
*/
MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException;
/**
* Synchronously uploads a skin to this server.
*
* @param upload The payload to send.
*
* @return A server response object.
*
* @throws IOException
* @throws AuthenticationException
*/
SkinUploadResponse performSkinUpload(SkinUpload upload) throws IOException, AuthenticationException;
/**
* Asynchronously uploads a skin to the server.
*
* Returns an incomplete future for chaining other actions to be performed after this method completes.
* Actions are dispatched to the default skinUploadExecutor
*
* @param upload The payload to send.
*/
default CompletableFuture<SkinUploadResponse> uploadSkin(SkinUpload upload) {
return CallableFutures.asyncFailableFuture(() -> performSkinUpload(upload), HDSkinManager.skinUploadExecutor);
}
/**
* Asynchronously loads texture information for the provided profile.
*
* Returns an incomplete future for chaining other actions to be performed after this method completes.
* Actions are dispatched to the default skinDownloadExecutor
*/
default CompletableFuture<MinecraftTexturesPayload> getPreviewTextures(GameProfile profile) {
return CallableFutures.asyncFailableFuture(() -> loadProfileData(profile), HDSkinManager.skinDownloadExecutor);
}
/**
* Called to validate this skin server's state.
* Any servers with an invalid gateway format will not be loaded and generate an exception.
*/
default boolean verifyGateway() {
return true;
}
/**
* Joins with the Mojang API to verify the current user's session.
* @throws AuthenticationException if authentication failed or the session is invalid.
*/
static void verifyServerConnection(Session session, String serverId) throws AuthenticationException {
MinecraftSessionService service = Minecraft.getInstance().getSessionService();
service.joinServer(session.getProfile(), session.getToken(), serverId);
}
}

View file

@ -0,0 +1,37 @@
package com.minelittlepony.hdskins.server;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonIOException;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.minelittlepony.hdskins.HDSkinManager;
import java.lang.reflect.Type;
public class SkinServerSerializer implements JsonSerializer<SkinServer>, JsonDeserializer<SkinServer> {
@Override
public JsonElement serialize(SkinServer src, Type typeOfSrc, JsonSerializationContext context) {
ServerType serverType = src.getClass().getAnnotation(ServerType.class);
if (serverType == null) {
throw new JsonIOException("Skin server class did not have a type: " + typeOfSrc);
}
JsonObject obj = context.serialize(src).getAsJsonObject();
obj.addProperty("type", serverType.value());
return obj;
}
@Override
public SkinServer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
String type = json.getAsJsonObject().get("type").getAsString();
return context.deserialize(json, HDSkinManager.INSTANCE.getSkinServerClass(type));
}
}

View file

@ -0,0 +1,49 @@
package com.minelittlepony.hdskins.server;
import net.minecraft.util.Session;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
import java.net.URI;
import java.util.Map;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
public class SkinUpload {
private final Session session;
private final URI image;
private final Map<String, String> metadata;
private final Type type;
public SkinUpload(Session session, Type type, @Nullable URI image, Map<String, String> metadata) {
this.session = session;
this.image = image;
this.metadata = metadata;
this.type = type;
}
public Session getSession() {
return session;
}
@Nullable
public URI getImage() {
return image;
}
public Map<String, String> getMetadata() {
return metadata;
}
public MinecraftProfileTexture.Type getType() {
return type;
}
public String getSchemaAction() {
return image == null ? "none" : image.getScheme();
}
}

View file

@ -0,0 +1,23 @@
package com.minelittlepony.hdskins.server;
import com.google.common.base.MoreObjects;
public class SkinUploadResponse {
private final String message;
public SkinUploadResponse(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("message", message)
.toString();
}
}

View file

@ -0,0 +1,216 @@
package com.minelittlepony.hdskins.server;
import com.google.common.base.Preconditions;
import com.google.gson.annotations.Expose;
import com.minelittlepony.hdskins.HDSkinManager;
import com.minelittlepony.hdskins.gui.Feature;
import com.minelittlepony.hdskins.util.IndentedToStringStyle;
import com.minelittlepony.hdskins.util.MoreHttpResponses;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.exceptions.AuthenticationException;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.mojang.util.UUIDTypeAdapter;
import net.minecraft.client.Minecraft;
import net.minecraft.util.Session;
import org.apache.http.HttpHeaders;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.Locale;
import java.util.UUID;
@ServerType("valhalla")
public class ValhallaSkinServer implements SkinServer {
@Expose
private final String address;
private transient String accessToken;
public ValhallaSkinServer(String address) {
this.address = address;
}
@Override
public MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException {
try (MoreHttpResponses response = MoreHttpResponses.execute(HDSkinManager.httpClient, new HttpGet(getTexturesURI(profile)))) {
if (response.ok()) {
return response.unwrapAsJson(MinecraftTexturesPayload.class);
}
throw new IOException("Server sent non-ok response code: " + response.getResponseCode());
}
}
@Override
public SkinUploadResponse performSkinUpload(SkinUpload upload) throws IOException, AuthenticationException {
try {
return uploadPlayerSkin(upload);
} catch (IOException e) {
if (e.getMessage().equals("Authorization failed")) {
accessToken = null;
return uploadPlayerSkin(upload);
}
throw e;
}
}
private SkinUploadResponse uploadPlayerSkin(SkinUpload upload) throws IOException, AuthenticationException {
authorize(upload.getSession());
switch (upload.getSchemaAction()) {
case "none":
return resetSkin(upload);
case "file":
return uploadFile(upload);
case "http":
case "https":
return uploadUrl(upload);
default:
throw new IOException("Unsupported URI scheme: " + upload.getSchemaAction());
}
}
private SkinUploadResponse resetSkin(SkinUpload upload) throws IOException {
return upload(RequestBuilder.delete()
.setUri(buildUserTextureUri(upload.getSession().getProfile(), upload.getType()))
.addHeader(HttpHeaders.AUTHORIZATION, this.accessToken)
.build());
}
private SkinUploadResponse uploadFile(SkinUpload upload) throws IOException {
final File file = new File(upload.getImage());
MultipartEntityBuilder b = MultipartEntityBuilder.create()
.addBinaryBody("file", file, ContentType.create("image/png"), file.getName());
upload.getMetadata().forEach(b::addTextBody);
return upload(RequestBuilder.put()
.setUri(buildUserTextureUri(upload.getSession().getProfile(), upload.getType()))
.addHeader(HttpHeaders.AUTHORIZATION, this.accessToken)
.setEntity(b.build())
.build());
}
private SkinUploadResponse uploadUrl(SkinUpload upload) throws IOException {
return upload(RequestBuilder.post()
.setUri(buildUserTextureUri(upload.getSession().getProfile(), upload.getType()))
.addHeader(HttpHeaders.AUTHORIZATION, this.accessToken)
.addParameter("file", upload.getImage().toString())
.addParameters(MoreHttpResponses.mapAsParameters(upload.getMetadata()))
.build());
}
private SkinUploadResponse upload(HttpUriRequest request) throws IOException {
try (MoreHttpResponses response = MoreHttpResponses.execute(HDSkinManager.httpClient, request)) {
return response.unwrapAsJson(SkinUploadResponse.class);
}
}
private void authorize(Session session) throws IOException, AuthenticationException {
if (this.accessToken != null) {
return;
}
GameProfile profile = session.getProfile();
String token = session.getToken();
AuthHandshake handshake = authHandshake(profile.getName());
if (handshake.offline) {
return;
}
// join the session server
Minecraft.getInstance().getSessionService().joinServer(profile, token, handshake.serverId);
AuthResponse response = authResponse(profile.getName(), handshake.verifyToken);
if (!response.userId.equals(profile.getId())) {
throw new IOException("UUID mismatch!"); // probably won't ever throw
}
this.accessToken = response.accessToken;
}
private AuthHandshake authHandshake(String name) throws IOException {
try (MoreHttpResponses resp = MoreHttpResponses.execute(HDSkinManager.httpClient, RequestBuilder.post()
.setUri(getHandshakeURI())
.addParameter("name", name)
.build())) {
return resp.unwrapAsJson(AuthHandshake.class);
}
}
private AuthResponse authResponse(String name, long verifyToken) throws IOException {
try (MoreHttpResponses resp = MoreHttpResponses.execute(HDSkinManager.httpClient, RequestBuilder.post()
.setUri(getResponseURI())
.addParameter("name", name)
.addParameter("verifyToken", String.valueOf(verifyToken))
.build())) {
return resp.unwrapAsJson(AuthResponse.class);
}
}
private URI buildUserTextureUri(GameProfile profile, MinecraftProfileTexture.Type textureType) {
String user = UUIDTypeAdapter.fromUUID(profile.getId());
String skinType = textureType.name().toLowerCase(Locale.US);
return URI.create(String.format("%s/user/%s/%s", this.address, user, skinType));
}
private URI getTexturesURI(GameProfile profile) {
Preconditions.checkNotNull(profile.getId(), "profile id required for skins");
return URI.create(String.format("%s/user/%s", this.address, UUIDTypeAdapter.fromUUID(profile.getId())));
}
private URI getHandshakeURI() {
return URI.create(String.format("%s/auth/handshake", this.address));
}
private URI getResponseURI() {
return URI.create(String.format("%s/auth/response", this.address));
}
@Override
public boolean supportsFeature(Feature feature) {
switch (feature) {
case DOWNLOAD_USER_SKIN:
case UPLOAD_USER_SKIN:
case DELETE_USER_SKIN:
case MODEL_VARIANTS:
case MODEL_TYPES:
return true;
default: return false;
}
}
@Override
public String toString() {
return new IndentedToStringStyle.Builder(this)
.append("address", address)
.toString();
}
@SuppressWarnings("WeakerAccess")
private static class AuthHandshake {
private boolean offline;
private String serverId;
private long verifyToken;
}
@SuppressWarnings("WeakerAccess")
private static class AuthResponse {
private String accessToken;
private UUID userId;
}
}

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package com.minelittlepony.hdskins.server;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,48 @@
package com.minelittlepony.hdskins.upload;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.io.File;
import java.io.IOException;
import java.util.List;
@FunctionalInterface
public interface FileDropListener extends DropTargetListener {
@Override
default void dragEnter(DropTargetDragEvent dtde) {
}
@Override
default void dragOver(DropTargetDragEvent dtde) {
}
@Override
default void dropActionChanged(DropTargetDragEvent dtde) {
}
@Override
default void dragExit(DropTargetEvent dte) {
}
@SuppressWarnings("unchecked")
@Override
default void drop(DropTargetDropEvent dtde) {
dtde.acceptDrop(DnDConstants.ACTION_LINK);
try {
onDrop((List<File>) dtde.getTransferable().getTransferData(DataFlavor.javaFileListFlavor));
dtde.getDropTargetContext().dropComplete(true);
} catch (UnsupportedFlavorException | IOException e) {
e.printStackTrace();
}
}
void onDrop(List<File> files);
}

View file

@ -0,0 +1,78 @@
package com.minelittlepony.hdskins.upload;
import net.minecraft.client.MainWindow;
import net.minecraft.client.Minecraft;
import java.awt.Color;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetListener;
import java.util.TooManyListenersException;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.SwingConstants;
public class FileDropper extends JFrame {
private static final long serialVersionUID = -2945117328826695659L;
private static FileDropper instance = null;
public static FileDropper getAWTContext() {
if (instance == null) {
instance = new FileDropper();
}
return instance;
}
private final DropTarget dt;
public FileDropper() {
super("Skin Drop");
setType(Type.UTILITY);
setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
setResizable(false);
setTitle("Skin Drop");
setSize(256, 256);
setAlwaysOnTop(true);
getRootPane().setWindowDecorationStyle(JRootPane.NONE);
getContentPane().setLayout(null);
JPanel panel = new JPanel();
panel.setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, Color.GRAY));
panel.setBounds(10, 11, 230, 205);
getContentPane().add(panel);
JLabel txtInst = new JLabel("Drop skin files here");
txtInst.setHorizontalAlignment(SwingConstants.CENTER);
txtInst.setVerticalAlignment(SwingConstants.CENTER);
panel.add(txtInst);
dt = new DropTarget();
setDropTarget(dt);
if (InternalDialog.hiddenFrame == null) {
InternalDialog.hiddenFrame = this;
}
}
public void show(DropTargetListener dtl) throws TooManyListenersException {
dt.addDropTargetListener(dtl);
setVisible(true);
requestFocusInWindow();
MainWindow window = Minecraft.getInstance().mainWindow;
setLocation(window.getWindowX(), window.getWindowY());
}
public void hide(DropTargetListener dtl) {
dt.removeDropTargetListener(dtl);
setVisible(false);
}
}

View file

@ -0,0 +1,291 @@
package com.minelittlepony.hdskins.upload;
import com.google.common.collect.Lists;
import net.minecraft.client.Minecraft;
import net.minecraft.util.ResourceLocation;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.awt.Canvas;
import java.awt.Frame;
import java.awt.Image;
import java.awt.Window;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.util.List;
import java.util.TooManyListenersException;
import javax.annotation.Nullable;
import javax.imageio.ImageIO;
import javax.swing.*;
/**
* Experimental window to control file drop. It kind of sucks.
*
* @deprecated TODO: Merge GLFW branch
*/
@Deprecated
public class GLWindow extends DropTarget {
// Serial version because someone decided to extend DropTarget
private static final long serialVersionUID = -8891327070920541481L;
@Nullable
private static GLWindow instance = null;
private static final Logger logger = LogManager.getLogger();
/**
* Gets or creates the current GLWindow context.
*/
public static synchronized GLWindow current() {
if (instance == null) {
instance = new GLWindow();
}
return instance;
}
public static void create() {
try {
current().open();
} catch (LWJGLException e) {
throw new RuntimeException(e);
}
}
/**
* Destroys the current GLWindow context and restores default behaviour.
*/
public static synchronized void dispose() {
if (instance != null) {
instance.close();
}
}
private static int getScaledPixelUnit(int i) {
return Math.max((int)Math.round(i * Display.getPixelScaleFactor()), 0);
}
private final Minecraft mc = Minecraft.getInstance();
private JFrame frame;
private Canvas canvas;
private volatile DropTargetListener dropListener = null;
private int windowState = 0;
private boolean isFullscreen;
private boolean ready = false;
private boolean closeRequested = false;
private GLWindow() {
}
public JFrame getFrame() {
return frame;
}
private int frameFactorX;
private int frameFactorY;
private synchronized void open() throws LWJGLException {
// Dimensions from LWJGL may have a non 1:1 scale on high DPI monitors.
int x = getScaledPixelUnit(Display.getX());
int y = getScaledPixelUnit(Display.getY());
int w = getScaledPixelUnit(Display.getWidth());
int h = getScaledPixelUnit(Display.getHeight());
isFullscreen = mc.isFullScreen();
canvas = new Canvas();
frame = new JFrame(Display.getTitle());
frame.add(canvas);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent windowEvent) {
if (!closeRequested) {
for (Window w : Frame.getWindows()) {
w.dispose();
}
mc.shutdown();
}
closeRequested = false;
}
@Override
public void windowStateChanged(WindowEvent event) {
windowState = event.getNewState();
onResize();
}
@Override
public void windowOpened(WindowEvent e) {
// Once the window has opened compare the content and window dimensions to get
// the OS's frame size then reassign adjusted dimensions to match LWJGL's window.
frameFactorX = frame.getWidth() - frame.getContentPane().getWidth();
frameFactorY = frame.getHeight() - frame.getContentPane().getHeight();
frame.setSize(w + frameFactorX, h + frameFactorY);
}
});
frame.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent componentEvent) {
onResize();
}
});
// TODO: (Unconfirmed) reports say the icon appears small on some OSs.
// I've yet to reproduce this.
setIcons();
// Order here is important. Size is set _before_ displaying but
// after events to ensure the window and canvas both get correct dimensions.
frame.setResizable(Display.isResizable());
frame.setLocation(x, y);
frame.setSize(w, h);
frame.setVisible(true);
Display.setParent(canvas);
Display.setFullscreen(isFullscreen);
ready = true;
}
private synchronized void close() {
if (frame == null) {
String msg = "GLClose was called in an illegal state! You cannot close the GLWindow before it has been opened.";
logger.fatal(new IllegalStateException(msg));
return;
}
closeRequested = true;
try {
Display.setParent(null);
} catch (LWJGLException e) {
e.printStackTrace();
}
try {
if (isFullscreen) {
Display.setFullscreen(true);
} else {
if ((windowState & JFrame.MAXIMIZED_BOTH) == JFrame.MAXIMIZED_BOTH) {
Display.setLocation(0, 0);
Display.setDisplayMode(Display.getDesktopDisplayMode());
} else {
Display.setDisplayMode(new DisplayMode(frame.getContentPane().getWidth(), frame.getContentPane().getHeight()));
Display.setLocation(Math.max(0, frame.getX() + frameFactorX/3), Math.max(0, frame.getY() + frameFactorY/7));
}
// https://bugs.mojang.com/browse/MC-68754
Display.setResizable(false);
Display.setResizable(true);
}
} catch (LWJGLException e) {
e.printStackTrace();
}
frame.setVisible(false);
frame.dispose();
for (Window w : Frame.getWindows()) {
w.dispose();
}
instance = null;
}
private void setIcons() {
// VanillaTweakInjector.loadIconsOnFrames();
try {
//
// The icons are stored in Display#cached_icons. However they're not the _original_ values.
// LWJGL copies the initial byte streams and then reverses them. The result is a stream that's not
// only already consumed, but somehow invalid when you try to parse it through ImageIO.read.
//
DefaultResourcePack pack = (DefaultResourcePack) mc.getResourcePackRepository().rprDefaultResourcePack;
List<Image> images = Lists.newArrayList(
ImageIO.read(pack.getInputStreamAssets(new ResourceLocation("icons/icon_16x16.png"))),
ImageIO.read(pack.getInputStreamAssets(new ResourceLocation("icons/icon_32x32.png")))
);
Frame[] frames = Frame.getFrames();
if (frames != null) {
for (Frame frame : frames) {
try {
frame.setIconImages(images);
} catch (Throwable t) {}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void onResize() {
canvas.setBounds(0, 0, frame.getContentPane().getWidth(), frame.getContentPane().getHeight());
}
public void refresh(boolean fullscreen) {
if (ready && fullscreen != isFullscreen) {
// Repaint the canvas, not the window.
// The former strips the window of its state. The latter fixes a viewport scaling bug.
canvas.setBounds(0, 0, 0, 0);
onResize();
isFullscreen = fullscreen;
}
}
public synchronized void clearDropTargetListener() {
SwingUtilities.invokeLater(this::syncClearDropTargetListener);
}
private void syncClearDropTargetListener() {
if (dropListener != null) {
if (!ready) {
FileDropper.getAWTContext().hide(dropListener);
} else {
frame.setDropTarget(null);
removeDropTargetListener(dropListener);
}
dropListener = null;
}
}
public synchronized void setDropTargetListener(FileDropListener dtl) {
SwingUtilities.invokeLater(() -> syncSetDropTargetListener(dtl));
}
private void syncSetDropTargetListener(FileDropListener dtl) {
try {
syncClearDropTargetListener();
dropListener = dtl;
if (!ready) {
FileDropper.getAWTContext().show(dtl);
return;
}
frame.setDropTarget(this);
addDropTargetListener(dtl);
} catch (TooManyListenersException ignored) { }
}
}

View file

@ -0,0 +1,8 @@
package com.minelittlepony.hdskins.upload;
import java.io.File;
@FunctionalInterface
public interface IFileCallback {
void onDialogClosed(File file, int dialogResults);
}

View file

@ -0,0 +1,5 @@
package com.minelittlepony.hdskins.upload;
public interface IFileDialog extends Runnable {
void start();
}

View file

@ -0,0 +1,38 @@
package com.minelittlepony.hdskins.upload;
import javax.swing.JFrame;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
final class InternalDialog {
private static final Logger LOGGER = LogManager.getLogger();
static JFrame hiddenFrame;
public static JFrame getAWTContext() {
JFrame context = GLWindow.current().getFrame();
if (context != null) {
return context;
}
if (hiddenFrame == null) {
hiddenFrame = new JFrame("InternalDialogue");
hiddenFrame.setVisible(false);
hiddenFrame.requestFocusInWindow();
hiddenFrame.requestFocus();
try {
if (hiddenFrame.isAlwaysOnTopSupported()) {
hiddenFrame.setAlwaysOnTop(true);
}
} catch (SecurityException e) {
LOGGER.fatal("Could not set window on top state. This is probably Forge's fault.", e);
}
}
return hiddenFrame;
}
}

View file

@ -0,0 +1,76 @@
package com.minelittlepony.hdskins.upload;
import net.minecraft.client.Minecraft;
import java.io.File;
import javax.swing.JFileChooser;
import javax.swing.filechooser.FileFilter;
import org.apache.commons.lang3.StringUtils;
import com.minelittlepony.hdskins.HDSkins;
/**
* Base class for "open file" dialog threads
*
* @author Adam Mummery-Smith
*/
public abstract class ThreadOpenFile extends Thread implements IFileDialog {
protected String dialogTitle;
/**
* Delegate to call back when the dialog box is closed
*/
protected final IFileCallback parentScreen;
protected ThreadOpenFile(Minecraft minecraft, String dialogTitle, IFileCallback callback) throws IllegalStateException {
if (minecraft.mainWindow.isFullscreen()) {
throw new IllegalStateException("Cannot open an awt window whilst minecraft is in full screen mode!");
}
this.parentScreen = callback;
this.dialogTitle = dialogTitle;
}
@Override
public void run() {
JFileChooser fileDialog = new JFileChooser();
fileDialog.setDialogTitle(dialogTitle);
String last = HDSkins.getInstance().lastChosenFile;
if (!StringUtils.isBlank(last)) {
fileDialog.setSelectedFile(new File(last));
}
fileDialog.setFileFilter(getFileFilter());
int dialogResult = showDialog(fileDialog);
File f = fileDialog.getSelectedFile();
if (f != null) {
HDSkins.getInstance().lastChosenFile = f.getAbsolutePath();
HDSkins.getInstance().saveConfig();
if (!f.exists() && f.getName().indexOf('.') == -1) {
f = appendMissingExtension(f);
}
}
parentScreen.onDialogClosed(f, dialogResult);
}
protected int showDialog(JFileChooser chooser) {
return chooser.showOpenDialog(InternalDialog.getAWTContext());
}
/**
* Subclasses should override this to return a file filter
*/
protected abstract FileFilter getFileFilter();
protected File appendMissingExtension(File file) {
return file;
}
}

View file

@ -0,0 +1,33 @@
package com.minelittlepony.hdskins.upload;
import net.minecraft.client.Minecraft;
import javax.swing.filechooser.FileFilter;
import java.io.File;
/**
* Opens an awt "Open File" dialog with a PNG file filter
*
* @author Adam Mummery-Smith
*/
public class ThreadOpenFilePNG extends ThreadOpenFile {
public ThreadOpenFilePNG(Minecraft minecraft, String dialogTitle, IFileCallback callback) throws IllegalStateException {
super(minecraft, dialogTitle, callback);
}
@Override
protected FileFilter getFileFilter() {
return new FileFilter() {
@Override
public String getDescription() {
return "PNG Files (*.png)";
}
@Override
public boolean accept(File f) {
return f.isDirectory() || f.getName().toLowerCase().endsWith(".png");
}
};
}
}

View file

@ -0,0 +1,47 @@
package com.minelittlepony.hdskins.upload;
import net.minecraft.client.Minecraft;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import java.io.File;
/**
* Opens an awt "Save File" dialog
*/
public abstract class ThreadSaveFile extends ThreadOpenFile {
protected String filename;
protected ThreadSaveFile(Minecraft minecraft, String dialogTitle, String initialFilename, IFileCallback callback) throws IllegalStateException {
super(minecraft, dialogTitle, callback);
this.filename = initialFilename;
}
@Override
protected int showDialog(JFileChooser chooser) {
do {
chooser.setSelectedFile(new File(filename));
int result = chooser.showSaveDialog(InternalDialog.getAWTContext());
File f = chooser.getSelectedFile();
if (result == 0 && f != null && f.exists()) {
if (JOptionPane.showConfirmDialog(chooser,
"A file named \"" + f.getName() + "\" already exists. Do you want to replace it?", "Confirm",
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) != JOptionPane.YES_OPTION) {
continue;
}
f.delete();
}
return result;
} while (true);
}
}

View file

@ -0,0 +1,34 @@
package com.minelittlepony.hdskins.upload;
import net.minecraft.client.Minecraft;
import javax.swing.filechooser.FileFilter;
import java.io.File;
public class ThreadSaveFilePNG extends ThreadSaveFile {
public ThreadSaveFilePNG(Minecraft minecraft, String dialogTitle, String filename, IFileCallback callback) {
super(minecraft, dialogTitle, filename, callback);
}
@Override
protected FileFilter getFileFilter() {
return new FileFilter() {
@Override
public String getDescription() {
return "PNG Files (*.png)";
}
@Override
public boolean accept(File f) {
return f.isDirectory() || f.getName().toLowerCase().endsWith(".png");
}
};
}
@Override
protected File appendMissingExtension(File file) {
return new File(file.getParentFile(), file.getName() + ".png");
}
}

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package com.minelittlepony.hdskins.upload;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,56 @@
package com.minelittlepony.hdskins.util;
import com.google.common.util.concurrent.Runnables;
import net.minecraft.client.Minecraft;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
public class CallableFutures {
private static final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
public static <T> CompletableFuture<T> asyncFailableFuture(Callable<T> call, Executor exec) {
CompletableFuture<T> ret = new CompletableFuture<>();
exec.execute(() -> {
try {
ret.complete(call.call());
} catch (Throwable e) {
ret.completeExceptionally(e);
}
});
return ret;
}
public static <T> CompletableFuture<T> failedFuture(Exception e) {
CompletableFuture<T> ret = new CompletableFuture<>();
ret.completeExceptionally(e);
return ret;
}
public static <T> BiFunction<? super T, Throwable, Void> callback(Runnable c) {
return (o, t) -> {
if (t != null) {
t.printStackTrace();
} else {
c.run();
}
return null;
};
}
public static CompletableFuture<Void> scheduleTask(Runnable task) {
// schedule a task for next tick.
return CompletableFuture.runAsync(Runnables.doNothing(), delayed(50, TimeUnit.MILLISECONDS))
.handleAsync(callback(task), Minecraft.getInstance()::addScheduledTask);
}
private static Executor delayed(long time, TimeUnit unit) {
return (task) -> executor.schedule(task, time, unit);
}
}

View file

@ -0,0 +1,32 @@
package com.minelittlepony.hdskins.util;
public abstract class Edge {
private boolean previousState;
private Callback callback;
public Edge(Callback callback) {
this.callback = callback;
}
public void update() {
boolean state = nextState();
if (state != previousState) {
previousState = state;
callback.call(state);
}
}
public boolean getState() {
return previousState;
}
protected abstract boolean nextState();
@FunctionalInterface
public interface Callback {
void call(boolean state);
}
}

View file

@ -0,0 +1,35 @@
package com.minelittlepony.hdskins.util;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import static net.minecraft.util.text.TextFormatting.*;
public class IndentedToStringStyle extends ToStringStyle {
private static final long serialVersionUID = 2031593562293731492L;
private static final ToStringStyle INSTANCE = new IndentedToStringStyle();
private IndentedToStringStyle() {
this.setFieldNameValueSeparator(": " + RESET + ITALIC);
this.setContentStart(null);
this.setFieldSeparator(SystemUtils.LINE_SEPARATOR + " " + RESET + YELLOW);
this.setFieldSeparatorAtStart(true);
this.setContentEnd(null);
this.setUseIdentityHashCode(false);
this.setUseShortClassName(true);
}
public static class Builder extends ToStringBuilder {
public Builder(Object o) {
super(o, IndentedToStringStyle.INSTANCE);
}
@Override
public String build() {
return YELLOW + super.build();
}
}
}

View file

@ -0,0 +1,111 @@
package com.minelittlepony.hdskins.util;
import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams;
import com.google.gson.JsonObject;
import com.minelittlepony.hdskins.server.SkinServer;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.stream.Stream;
/**
* Utility class for getting different response types from a http response.
*/
@FunctionalInterface
public interface MoreHttpResponses extends AutoCloseable {
CloseableHttpResponse getResponse();
default boolean ok() {
return getResponseCode() == HttpStatus.SC_OK;
}
default int getResponseCode() {
return getResponse().getStatusLine().getStatusCode();
}
default String getContentType() {
return getResponse().getEntity().getContentType().getValue();
}
default InputStream getInputStream() throws IOException {
return getResponse().getEntity().getContent();
}
default BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));
}
default byte[] bytes() throws IOException {
try (InputStream input = getInputStream()) {
return ByteStreams.toByteArray(input);
}
}
default String text() throws IOException {
try (BufferedReader reader = getReader()) {
return CharStreams.toString(reader);
}
}
default Stream<String> lines() throws IOException {
try (BufferedReader reader = getReader()) {
return reader.lines();
}
}
default <T> T json(Class<T> type) throws IOException {
try (BufferedReader reader = getReader()) {
return SkinServer.gson.fromJson(reader, type);
}
}
default <T> T json(Type type) throws IOException {
try (BufferedReader reader = getReader()) {
return SkinServer.gson.fromJson(reader, type);
}
}
default <T> T unwrapAsJson(Type type) throws IOException {
if (!"application/json".equals(getContentType())) {
throw new IOException("Server returned a non-json response!");
}
if (ok()) {
return json(type);
}
throw new IOException(json(JsonObject.class).get("message").getAsString());
}
@Override
default void close() throws IOException {
this.getResponse().close();
}
static MoreHttpResponses execute(CloseableHttpClient client, HttpUriRequest request) throws IOException {
CloseableHttpResponse response = client.execute(request);
return () -> response;
}
static NameValuePair[] mapAsParameters(Map<String, String> parameters) {
return parameters.entrySet().stream()
.map(entry ->
new BasicNameValuePair(entry.getKey(), entry.getValue())
)
.toArray(NameValuePair[]::new);
}
}

View file

@ -0,0 +1,81 @@
package com.minelittlepony.hdskins.util;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import com.minelittlepony.hdskins.HDSkinManager;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
/**
* Ew. Why so many builders? >.<
*/
public class NetClient {
private final RequestBuilder rqBuilder;
private MultipartEntityBuilder entityBuilder;
private Map<String, ?> headers;
public NetClient(String method, String uri) {
rqBuilder = RequestBuilder.create(method).setUri(uri);
}
public NetClient putFile(String key, String contentType, URI file) {
if (entityBuilder == null) {
entityBuilder = MultipartEntityBuilder.create();
}
File f = new File(file);
HttpEntity entity = entityBuilder.addBinaryBody(key, f, ContentType.create(contentType), f.getName()).build();
rqBuilder.setEntity(entity);
return this;
}
public NetClient putFormData(Map<String, ?> data, String contentTypes) {
if (entityBuilder == null) {
entityBuilder = MultipartEntityBuilder.create();
}
for (Map.Entry<String, ?> i : data.entrySet()) {
entityBuilder.addTextBody(i.getKey(), i.getValue().toString());
}
return this;
}
public NetClient putHeaders(Map<String, ?> headers) {
this.headers = headers;
return this;
}
public MoreHttpResponses send() throws IOException {
if (entityBuilder != null) {
rqBuilder.setEntity(entityBuilder.build());
}
HttpUriRequest request = rqBuilder.build();
if (headers != null) {
for (Map.Entry<String, ?> parameter : headers.entrySet()) {
request.addHeader(parameter.getKey(), parameter.getValue().toString());
}
}
return MoreHttpResponses.execute(HDSkinManager.httpClient, request);
}
public CompletableFuture<MoreHttpResponses> async(Executor exec) {
return CallableFutures.asyncFailableFuture(this::send, exec);
}
}

View file

@ -0,0 +1,24 @@
package com.minelittlepony.hdskins.util;
import net.minecraft.client.entity.AbstractClientPlayer;
import net.minecraft.client.network.NetworkPlayerInfo;
import org.apache.commons.lang3.reflect.FieldUtils;
import java.lang.reflect.Field;
public class PlayerUtil {
private static final Field playerInfo = FieldUtils.getAllFields(AbstractClientPlayer.class)[0];
public static NetworkPlayerInfo getInfo(AbstractClientPlayer player) {
try {
if (!playerInfo.isAccessible()) {
playerInfo.setAccessible(true);
}
return (NetworkPlayerInfo) FieldUtils.readField(playerInfo, player);
} catch (IllegalAccessException e) {
e.printStackTrace();
return null;
}
}
}

View file

@ -0,0 +1,39 @@
package com.minelittlepony.hdskins.util;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.client.renderer.texture.NativeImage;
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);
}
}
public static NativeImage getDynamicBufferedImage(int width, int height, DynamicTexture texture) {
NativeImage image = new NativeImage(16, 16, true);
image.copyImageData(texture.getTextureData());
return image;
}
}

View file

@ -0,0 +1,50 @@
package com.minelittlepony.hdskins.util;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.mojang.util.UUIDTypeAdapter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Use this to build a {@link MinecraftTexturesPayload} object. This is
* required because it has no useful constructor. This uses reflection
* via Gson to create a new instance and populate the fields.
*/
@SuppressWarnings("unused")
public class TexturesPayloadBuilder {
private static Gson gson = new GsonBuilder().registerTypeAdapter(UUID.class, new UUIDTypeAdapter()).create();
public static MinecraftTexturesPayload createTexturesPayload(GameProfile profile, Map<Type, MinecraftProfileTexture> textures) {
// This worked fine as is before I started using sub-classes.
MinecraftTexturesPayload payload = gson.fromJson(gson.toJson(new TexturesPayloadBuilder(profile)), MinecraftTexturesPayload.class);
payload.getTextures().putAll(textures);
return payload;
}
private long timestamp;
private UUID profileId;
private String profileName;
private boolean isPublic;
private Map<Type, MinecraftProfileTexture> textures;
private TexturesPayloadBuilder(GameProfile profile) {
profileId = profile.getId();
profileName = profile.getName();
timestamp = System.currentTimeMillis();
isPublic = true;
this.textures = new HashMap<>();
}
}

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package com.minelittlepony.hdskins.util;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,41 @@
{
"hdskins.choose": "Suche die datei aus",
"hdskins.manager": "Skin Verwalter",
"hdskins.error.unreadable": "Datei nicht lesbar",
"hdskins.error.ext": "Keine PNG Datei",
"hdskins.error.open": "Fehler beim öffnen der skin Datei",
"hdskins.error.invalid": "Das ist doch kein skin du scherzkeks",
"hdskins.error.select": "Bitte erst den skin auswählen",
"hdskins.error.mojang": "Mojang API Fehler",
"hdskins.error.mojang.wait": "Bitte warte %d minuten",
"hdskins.error.noserver": "Es ist kein gültiges Skin Server verfügbar",
"hdskins.error.offline": "- Server Offline -",
"hdskins.open.title": "Suche dir ein skin aus",
"hdskins.save.title": "Wo soll dieses gegen gespeichert werden",
"hdskins.fetch": "Rufe Skin ab...",
"hdskins.failed": "Hochladen des Skins fehlgeschlagen",
"hdskins.request": "Sende Anfrage zu Server bitte warten...",
"hdskins.upload": "Skin wird hochgeladen bitte warten...",
"hdskins.local": "Lokaler Skin",
"hdskins.server": "Server Skin",
"hdskins.mode.steve": "Steve Model",
"hdskins.mode.alex": "Alex Model",
"hdskins.mode.skin": "Spieler Texture",
"hdskins.mode.cape": "Cape Texture",
"hdskins.mode.elytra": "Elytra Texture",
"hdskins.mode.stand": "Stehen",
"hdskins.mode.sleep": "Schlafen",
"hdskins.mode.ride": "Reiten",
"hdskins.options.chevy": ">>",
"hdskins.options.chevy.title": "Skin Hochladen",
"hdskins.options.download": "<<",
"hdskins.options.download.title": "Skin Speichern",
"hdskins.options.close": "Zurück",
"hdskins.options.clear": "Entfernen",
"hdskins.options.browse": "Suchen",
"hdskins.options.skindrops": "Experimental Skin Drop",
"hdskins.options.cache": "Bereinige Temporären Skin speicher (cache)",
"hdskins.warning.experimental": "§6WARNUNG: diese Funktion ist §4experimental§6, Das heißt falls es passt nicht richtig funktioniert, oder kaputt geht bist du selber verantwortlich",
"hdskins.warning.disabled.title": "§c%s %s",
"hdskins.warning.disabled.description": "§4(DEAKTIVIERT)\n§7diese Funktion wird von deinem aktuellen Skin Server nicht unterstützt.\n§7Bitte suche dir einen anderen Server um Fortzufahren"
}

View file

@ -0,0 +1,41 @@
{
"hdskins.choose": "Choose a file",
"hdskins.manager": "Skin Manager",
"hdskins.error.unreadable": "File not readable",
"hdskins.error.ext": "File not PNG",
"hdskins.error.open": "Error opening skin file",
"hdskins.error.invalid": "Not a valid skin file",
"hdskins.error.select": "Please select a skin first",
"hdskins.error.mojang": "Mojang API Error",
"hdskins.error.mojang.wait": "Please wait %d minutes",
"hdskins.error.noserver": "There was no valid skin server available",
"hdskins.error.offline": "- Server Offline -",
"hdskins.open.title": "Choose skin",
"hdskins.save.title": "Choose save location",
"hdskins.fetch": "Fetching skin...",
"hdskins.failed": "Uploading skin failed",
"hdskins.request": "Sending request to server please wait...",
"hdskins.upload": "Uploading skin please wait...",
"hdskins.local": "Local Skin",
"hdskins.server": "Server Skin",
"hdskins.mode.steve": "Steve Model",
"hdskins.mode.alex": "Alex Model",
"hdskins.mode.skin": "Player Texture",
"hdskins.mode.cape": "Cape Texture",
"hdskins.mode.elytra": "Elytra Texture",
"hdskins.mode.stand": "Standing",
"hdskins.mode.sleep": "Sleeping",
"hdskins.mode.ride": "Riding",
"hdskins.options.chevy": ">>",
"hdskins.options.chevy.title": "Upload Skin",
"hdskins.options.download": "<<",
"hdskins.options.download.title": "Save Skin",
"hdskins.options.close": "Close",
"hdskins.options.clear": "Clear",
"hdskins.options.browse": "Browse",
"hdskins.options.skindrops": "Experimental Skin Drop",
"hdskins.options.cache": "Clear Skin Cache",
"hdskins.warning.experimental": "§6WARNING: This feature is §4experimental§6, meaning things may break or derp or even slurp. Enabling this means you accept responsibility for what may happen to your chickens.",
"hdskins.warning.disabled.title": "§c%s %s",
"hdskins.warning.disabled.description": "§4(DISABLED)\n§7This feature is not supported by your current skin server.\n§7Please choose a different one to proceed."
}

View file

@ -0,0 +1,38 @@
{
"hdskins.choose": "Choisissez un fichier",
"hdskins.manager": "Skin Manager HD",
"hdskins.error.unreadable": "Fichier non lisible",
"hdskins.error.ext": "Fichier non PNG",
"hdskins.error.open": "Erreur d'ouverture du fichier",
"hdskins.error.invalid": "Pas un fichier valide",
"hdskins.error.select": "S.V.P. s<>lectionner un skin en premier",
"hdskins.error.mojang": "Erreur d'API Mojang",
"hdskins.error.mojang.wait": "Please wait %d minutes",
"hdskins.open.title": "Choisissez un skin",
"hdskins.fetch": "Chargement du skin...",
"hdskins.failed": "Ajout du skin <20>chou<6F>",
"hdskins.request": "Envoi de la requ<71>te au serveur S.V.P. patientez ...",
"hdskins.upload": "Ajout du skin, S.V.P. patientez ...",
"hdskins.local": "Local Skin",
"hdskins.server": "Server Skin",
"hdskins.mode.steve": "Steve Model",
"hdskins.mode.alex": "Alex Model",
"hdskins.mode.skin": "Player Texture",
"hdskins.mode.cape": "Cape Texture",
"hdskins.mode.elytra": "Elytra Texture",
"hdskins.mode.stand": "Standing",
"hdskins.mode.sleep": "Sleeping",
"hdskins.mode.ride": "Riding",
"hdskins.options.chevy": ">>",
"hdskins.options.chevy.title": "Upload Skin",
"hdskins.options.download": "<<",
"hdskins.options.download.title": "Save Skin",
"hdskins.options.close": "Close",
"hdskins.options.clear": "Clear",
"hdskins.options.browse": "Browse",
"hdskins.options.skindrops": "Experimental Skin Drop",
"hdskins.options.cache": "Clear Skin Cache",
"hdskins.warning.experimental": "§6WARNING: This feature is §4experimental§6, meaning things may break or derp or even slurp. Enabling this means you accept responsibility for what may happen to your chickens.",
"hdskins.warning.disabled.title": "§c%s %s",
"hdskins.warning.disabled.description": "§4(DISABLED)\n§7This feature is not supported by your current skin server.\n§7Please choose a different one to proceed."
}

View file

@ -0,0 +1,41 @@
{
"hdskins.choose": "Выберите файл",
"hdskins.manager": "Помощник по работе со скинами",
"hdskins.error.unreadable": "Не удалось прочитать файл",
"hdskins.error.ext": "Файл должен быть в формате .png",
"hdskins.error.open": "Ошибка при открытии файла скина",
"hdskins.error.invalid": "Неверный файл скина",
"hdskins.error.select": "Пожалуйста, сначала выберите скин",
"hdskins.error.mojang": "Ошибка Mojang API",
"hdskins.error.mojang.wait": "Пожалуйста, подождите %d минут(ы)...",
"hdskins.error.noserver": "Не найдено ни одного скин-сервера",
"hdskins.error.offline": "- Сервер не в сети -",
"hdskins.open.title": "Выберите ваш скин",
"hdskins.save.title": "Выберите имя файла для сохранения...",
"hdskins.fetch": "Скачивание скина...",
"hdskins.failed": "Загрузка скина не удалась",
"hdskins.request": "Отправка запроса на сервер, пожалуйста, подождите.",
"hdskins.upload": "Загрузка скина, пожалуйста, подождите.",
"hdskins.local": "Локальный скин",
"hdskins.server": "Скин на сервере",
"hdskins.mode.steve": "Стандартная модель",
"hdskins.mode.alex": "Тонконогая модель",
"hdskins.mode.skin": "Скин игрока",
"hdskins.mode.cape": "Текстура плаща",
"hdskins.mode.elytra": "Текстура надкрылий",
"hdskins.mode.stand": "Обычная поза",
"hdskins.mode.sleep": "Поза сна",
"hdskins.mode.ride": "Сидячая поза",
"hdskins.options.chevy": ">>",
"hdskins.options.chevy.title": "Загрузить скин",
"hdskins.options.download": "<<",
"hdskins.options.download.title": "Скачать скин",
"hdskins.options.close": "Закрыть",
"hdskins.options.clear": "Удалить скин",
"hdskins.options.browse": "Выбрать",
"hdskins.options.skindrops": "Экспериментальное перетаскивание скина",
"hdskins.options.cache": "Очистить кеш скинов",
"hdskins.warning.experimental": "§6ВНИМАНИЕ: Эта функция §4экспериментальная§6, что означает, что вещи могут начать ломаться, извиваться или даже захлёбываться. При её включении авторы сего текста не несут ответственности за возможные последствия.",
"hdskins.warning.disabled.title": "§c%s %s",
"hdskins.warning.disabled.description": "§4(ОТКЛЮЧЕНО)\n§7Данная опция не поддерживается текущим скин-сервером.\n§7Пожалуйста, выберите другой скин-сервер."
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,14 @@
{
"required": true,
"minVersion": "0.7",
"package": "com.minelittlepony.hdskins.mixin",
"refmap": "hdskins.mixin.refmap.json",
"mixins": [
"MixinMinecraft",
"MixinGuiMainMenu",
"MixinImageBufferDownload",
"MixinNetworkPlayerInfo",
"MixinSkullRenderer",
"MixinThreadDownloadImageData"
]
}

View file

@ -0,0 +1,105 @@
package com.minelittlepony.hdskins.litemod;
import com.google.gson.GsonBuilder;
import com.minelittlepony.common.client.gui.GuiLiteHost;
import com.minelittlepony.hdskins.HDSkinManager;
import com.minelittlepony.hdskins.HDSkins;
import com.minelittlepony.hdskins.gui.HDSkinsConfigPanel;
import com.minelittlepony.hdskins.server.SkinServer;
import com.minelittlepony.hdskins.server.SkinServerSerializer;
import com.mumfrey.liteloader.Configurable;
import com.mumfrey.liteloader.InitCompleteListener;
import com.mumfrey.liteloader.ViewportListener;
import com.mumfrey.liteloader.core.LiteLoader;
import com.mumfrey.liteloader.modconfig.AdvancedExposable;
import com.mumfrey.liteloader.modconfig.ConfigPanel;
import com.mumfrey.liteloader.modconfig.ConfigStrategy;
import com.mumfrey.liteloader.modconfig.ExposableOptions;
import com.mumfrey.liteloader.util.ModUtilities;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.entity.Entity;
import java.io.File;
import java.util.function.Function;
@ExposableOptions(strategy = ConfigStrategy.Unversioned, filename = "hdskins")
public class LiteModHDSkins extends HDSkins implements InitCompleteListener, ViewportListener, Configurable, AdvancedExposable {
@Override
public String getName() {
return HDSkins.MOD_NAME;
}
@Override
public String getVersion() {
return HDSkins.VERSION;
}
@Override
public void saveConfig() {
LiteLoader.getInstance().writeConfig(this);
}
@Override
public void init(File configPath) {
// register config
LiteLoader.getInstance().registerExposable(this, null);
super.init();
}
@Override
public void upgradeSettings(String version, File configPath, File oldConfigPath) {
HDSkinManager.INSTANCE.clearSkinCache();
}
@Override
public void setupGsonSerialiser(GsonBuilder gsonBuilder) {
gsonBuilder.registerTypeAdapter(SkinServer.class, new SkinServerSerializer());
}
@Override
public File getConfigFile(File configFile, File configFileLocation, String defaultFileName) {
return null;
}
@Override
public Class<? extends ConfigPanel> getConfigPanelClass() {
return Panel.class;
}
@Override
public void onInitCompleted(Minecraft minecraft, LiteLoader loader) {
initComplete();
}
@Override
public void onViewportResized(ScaledResolution resolution, int displayWidth, int displayHeight) {
}
@Override
public void onFullScreenToggled(boolean fullScreen) {
super.onToggledFullScreen(fullScreen);
}
@Override
protected <T extends Entity> void addRenderer(Class<T> type, Function<RenderManager, Render<T>> renderer) {
ModUtilities.addRenderer(type, renderer.apply(Minecraft.getMinecraft().getRenderManager()));
}
@Override
public File getAssetsDirectory() {
return LiteLoader.getAssetsDirectory();
}
public static class Panel extends GuiLiteHost {
public Panel() {
super(new HDSkinsConfigPanel());
}
}
}

View file

@ -0,0 +1,7 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package com.minelittlepony.hdskins.litemod;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;