Disable buttons on the interface if a skin server doesn't support/permit that particular functionality

This commit is contained in:
Sollace 2018-10-31 14:54:00 +02:00
parent 26fb289be1
commit 664f8ac6a4
10 changed files with 257 additions and 21 deletions

View file

@ -36,8 +36,14 @@ public class Button extends GuiButton implements IActionable, IGuiTooltipped<But
return this; return this;
} }
protected List<String> getTooltip() {
return tooltip;
}
@Override @Override
public void renderToolTip(Minecraft mc, int mouseX, int mouseY) { public void renderToolTip(Minecraft mc, int mouseX, int mouseY) {
List<String> tooltip = getTooltip();
if (visible && isMouseOver() && tooltip != null) { if (visible && isMouseOver() && tooltip != null) {
mc.currentScreen.drawHoveringText(tooltip, mouseX + tipX, mouseY + tipY); mc.currentScreen.drawHoveringText(tooltip, mouseX + tipX, mouseY + tipY);
} }

View file

@ -15,6 +15,7 @@ import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type; import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
import com.mumfrey.liteloader.util.log.LiteLoaderLogger; import com.mumfrey.liteloader.util.log.LiteLoaderLogger;
import com.voxelmodpack.hdskins.gui.EntityPlayerModel; import com.voxelmodpack.hdskins.gui.EntityPlayerModel;
import com.voxelmodpack.hdskins.gui.Feature;
import com.voxelmodpack.hdskins.resources.PreviewTextureManager; import com.voxelmodpack.hdskins.resources.PreviewTextureManager;
import com.voxelmodpack.hdskins.server.SkinServer; import com.voxelmodpack.hdskins.server.SkinServer;
import com.voxelmodpack.hdskins.server.SkinUpload; import com.voxelmodpack.hdskins.server.SkinUpload;
@ -102,6 +103,10 @@ public class SkinUploader implements Closeable {
return gateway == null ? "" : gateway.toString(); return gateway == null ? "" : gateway.toString();
} }
public boolean supportsFeature(Feature feature) {
return gateway != null && gateway.supportsFeature(feature);
}
protected void setError(String er) { protected void setError(String er) {
status = er; status = er;
sendingSkin = false; sendingSkin = false;

View file

@ -0,0 +1,50 @@
package com.voxelmodpack.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

@ -1,7 +1,9 @@
package com.voxelmodpack.hdskins.gui; package com.voxelmodpack.hdskins.gui;
import com.google.common.base.Splitter;
import com.minelittlepony.gui.Button; import com.minelittlepony.gui.Button;
import com.minelittlepony.gui.GameGui; import com.minelittlepony.gui.GameGui;
import com.minelittlepony.gui.IGuiAction;
import com.minelittlepony.gui.IconicButton; import com.minelittlepony.gui.IconicButton;
import com.minelittlepony.gui.IconicToggle; import com.minelittlepony.gui.IconicToggle;
import com.minelittlepony.gui.Label; import com.minelittlepony.gui.Label;
@ -44,15 +46,15 @@ public class GuiSkins extends GameGui implements ISkinUploadHandler {
private int updateCounter = 0; private int updateCounter = 0;
private Button btnBrowse; private Button btnBrowse;
private Button btnUpload; private FeatureButton btnUpload;
private Button btnDownload; private FeatureButton btnDownload;
private Button btnClear; private FeatureButton btnClear;
private Button btnModeSteve; private FeatureSwitch btnModeSteve;
private Button btnModeAlex; private FeatureSwitch btnModeAlex;
private Button btnModeSkin; private FeatureSwitch btnModeSkin;
private Button btnModeElytra; private FeatureSwitch btnModeElytra;
protected EntityPlayerModel localPlayer; protected EntityPlayerModel localPlayer;
protected EntityPlayerModel remotePlayer; protected EntityPlayerModel remotePlayer;
@ -148,21 +150,21 @@ public class GuiSkins extends GameGui implements ISkinUploadHandler {
chooser.openBrowsePNG(mc, format("hdskins.open.title")))) chooser.openBrowsePNG(mc, format("hdskins.open.title"))))
.setEnabled(!mc.isFullScreen()); .setEnabled(!mc.isFullScreen());
addButton(btnUpload = new Button(width / 2 - 24, height / 2 - 20, 48, 20, "hdskins.options.chevy", sender -> { addButton(btnUpload = new FeatureButton(width / 2 - 24, height / 2 - 20, 48, 20, "hdskins.options.chevy", sender -> {
if (uploader.canUpload()) { if (uploader.canUpload()) {
punchServer("hdskins.upload"); punchServer("hdskins.upload");
} }
})).setEnabled(uploader.canUpload()) })).setEnabled(uploader.canUpload())
.setTooltip("hdskins.options.chevy.title"); .setTooltip("hdskins.options.chevy.title");
addButton(btnDownload = new Button(width / 2 - 24, height / 2 + 20, 48, 20, "hdskins.options.download", sender -> { addButton(btnDownload = new FeatureButton(width / 2 - 24, height / 2 + 20, 48, 20, "hdskins.options.download", sender -> {
if (uploader.canClear()) { if (uploader.canClear()) {
chooser.openSavePNG(mc, format("hdskins.save.title")); chooser.openSavePNG(mc, format("hdskins.save.title"));
} }
})).setEnabled(uploader.canClear()) })).setEnabled(uploader.canClear())
.setTooltip("hdskins.options.download.title"); .setTooltip("hdskins.options.download.title");
addButton(btnClear = new Button(width / 2 + 60, height - 27, 90, 20, "hdskins.options.clear", sender -> { addButton(btnClear = new FeatureButton(width / 2 + 60, height - 27, 90, 20, "hdskins.options.clear", sender -> {
if (uploader.canClear()) { if (uploader.canClear()) {
punchServer("hdskins.request"); punchServer("hdskins.request");
} }
@ -171,26 +173,26 @@ public class GuiSkins extends GameGui implements ISkinUploadHandler {
addButton(new Button(width / 2 - 50, height - 25, 100, 20, "hdskins.options.close", sender -> addButton(new Button(width / 2 - 50, height - 25, 100, 20, "hdskins.options.close", sender ->
mc.displayGuiScreen(new GuiMainMenu()))); mc.displayGuiScreen(new GuiMainMenu())));
addButton(btnModeSteve = new IconicButton(width - 25, 32, sender -> switchSkinMode("default")) addButton(btnModeSteve = new FeatureSwitch(width - 25, 32, sender -> switchSkinMode("default")))
.setIcon(new ItemStack(Items.LEATHER_LEGGINGS), 0x3c5dcb)) .setIcon(new ItemStack(Items.LEATHER_LEGGINGS), 0x3c5dcb)
.setEnabled("slim".equals(uploader.getMetadataField("model"))) .setEnabled("slim".equals(uploader.getMetadataField("model")))
.setTooltip("hdskins.mode.steve") .setTooltip("hdskins.mode.steve")
.setTooltipOffset(0, 10); .setTooltipOffset(0, 10);
addButton(btnModeAlex = new IconicButton(width - 25, 51, sender -> switchSkinMode("slim")) addButton(btnModeAlex = new FeatureSwitch(width - 25, 51, sender -> switchSkinMode("slim")))
.setIcon(new ItemStack(Items.LEATHER_LEGGINGS), 0xfff500)) .setIcon(new ItemStack(Items.LEATHER_LEGGINGS), 0xfff500)
.setEnabled("default".equals(uploader.getMetadataField("model"))) .setEnabled("default".equals(uploader.getMetadataField("model")))
.setTooltip("hdskins.mode.alex") .setTooltip("hdskins.mode.alex")
.setTooltipOffset(0, 10); .setTooltipOffset(0, 10);
addButton(btnModeSkin = new IconicButton(width - 25, 75, sender -> uploader.setSkinType(Type.SKIN)) addButton(btnModeSkin = new FeatureSwitch(width - 25, 75, sender -> uploader.setSkinType(Type.SKIN)))
.setIcon(new ItemStack(Items.LEATHER_CHESTPLATE))) .setIcon(new ItemStack(Items.LEATHER_CHESTPLATE))
.setEnabled(uploader.getSkinType() == Type.ELYTRA) .setEnabled(uploader.getSkinType() == Type.ELYTRA)
.setTooltip(format("hdskins.mode.skin", toTitleCase(Type.SKIN.name()))) .setTooltip(format("hdskins.mode.skin", toTitleCase(Type.SKIN.name())))
.setTooltipOffset(0, 10); .setTooltipOffset(0, 10);
addButton(btnModeElytra = new IconicButton(width - 25, 94, sender -> uploader.setSkinType(Type.ELYTRA)) addButton(btnModeElytra = new FeatureSwitch(width - 25, 94, sender -> uploader.setSkinType(Type.ELYTRA)))
.setIcon(new ItemStack(Items.ELYTRA))) .setIcon(new ItemStack(Items.ELYTRA))
.setEnabled(uploader.getSkinType() == Type.SKIN) .setEnabled(uploader.getSkinType() == Type.SKIN)
.setTooltip(format("hdskins.mode.skin", toTitleCase(Type.ELYTRA.name()))) .setTooltip(format("hdskins.mode.skin", toTitleCase(Type.ELYTRA.name())))
.setTooltipOffset(0, 10); .setTooltipOffset(0, 10);
@ -506,8 +508,85 @@ public class GuiSkins extends GameGui implements ISkinUploadHandler {
private void updateButtons() { private void updateButtons() {
btnClear.enabled = uploader.canClear(); btnClear.enabled = uploader.canClear();
btnUpload.enabled = uploader.canUpload(); btnUpload.enabled = uploader.canUpload() && uploader.supportsFeature(Feature.UPLOAD_USER_SKIN);
btnDownload.enabled = uploader.canClear() && !chooser.pickingInProgress(); btnDownload.enabled = uploader.canClear() && !chooser.pickingInProgress();
btnBrowse.enabled = !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

@ -7,6 +7,7 @@ import com.mojang.authlib.GameProfile;
import com.mojang.authlib.exceptions.AuthenticationException; import com.mojang.authlib.exceptions.AuthenticationException;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload; import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.mojang.util.UUIDTypeAdapter; import com.mojang.util.UUIDTypeAdapter;
import com.voxelmodpack.hdskins.gui.Feature;
import com.voxelmodpack.hdskins.util.IndentedToStringStyle; import com.voxelmodpack.hdskins.util.IndentedToStringStyle;
import com.voxelmodpack.hdskins.util.MoreHttpResponses; import com.voxelmodpack.hdskins.util.MoreHttpResponses;
import com.voxelmodpack.hdskins.util.NetClient; import com.voxelmodpack.hdskins.util.NetClient;
@ -84,4 +85,17 @@ public class BethlehemSkinServer implements SkinServer {
.append("address", address) .append("address", address)
.build(); .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

@ -10,6 +10,7 @@ import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload; import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.mojang.util.UUIDTypeAdapter; import com.mojang.util.UUIDTypeAdapter;
import com.voxelmodpack.hdskins.HDSkinManager; import com.voxelmodpack.hdskins.HDSkinManager;
import com.voxelmodpack.hdskins.gui.Feature;
import com.voxelmodpack.hdskins.util.CallableFutures; import com.voxelmodpack.hdskins.util.CallableFutures;
import com.voxelmodpack.hdskins.util.IndentedToStringStyle; import com.voxelmodpack.hdskins.util.IndentedToStringStyle;
import com.voxelmodpack.hdskins.util.MoreHttpResponses; import com.voxelmodpack.hdskins.util.MoreHttpResponses;
@ -164,6 +165,17 @@ public class LegacySkinServer implements SkinServer {
return !Strings.isNullOrEmpty(gateway); 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 @Override
public String toString() { public String toString() {
return new IndentedToStringStyle.Builder(this) return new IndentedToStringStyle.Builder(this)

View file

@ -5,6 +5,14 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/**
* The remote server API level that this skin server implements.
*
* Current values are:
* - legacy
* - valhalla
* - bethlehem
*/
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface ServerType { public @interface ServerType {

View file

@ -10,6 +10,7 @@ import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.mojang.util.UUIDTypeAdapter; import com.mojang.util.UUIDTypeAdapter;
import com.mumfrey.liteloader.modconfig.Exposable; import com.mumfrey.liteloader.modconfig.Exposable;
import com.voxelmodpack.hdskins.HDSkinManager; import com.voxelmodpack.hdskins.HDSkinManager;
import com.voxelmodpack.hdskins.gui.Feature;
import com.voxelmodpack.hdskins.util.CallableFutures; import com.voxelmodpack.hdskins.util.CallableFutures;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.util.Session; import net.minecraft.util.Session;
@ -30,22 +31,67 @@ public interface SkinServer extends Exposable {
"http://skinmanager.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; 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; 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) { default CompletableFuture<SkinUploadResponse> uploadSkin(SkinUpload upload) {
return CallableFutures.asyncFailableFuture(() -> performSkinUpload(upload), HDSkinManager.skinUploadExecutor); 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) { default CompletableFuture<MinecraftTexturesPayload> getPreviewTextures(GameProfile profile) {
return CallableFutures.asyncFailableFuture(() -> loadProfileData(profile), HDSkinManager.skinDownloadExecutor); 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() { default boolean verifyGateway() {
return true; 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 { static void verifyServerConnection(Session session, String serverId) throws AuthenticationException {
MinecraftSessionService service = Minecraft.getMinecraft().getSessionService(); MinecraftSessionService service = Minecraft.getMinecraft().getSessionService();
service.joinServer(session.getProfile(), session.getToken(), serverId); service.joinServer(session.getProfile(), session.getToken(), serverId);

View file

@ -8,6 +8,7 @@ import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload; import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.mojang.util.UUIDTypeAdapter; import com.mojang.util.UUIDTypeAdapter;
import com.voxelmodpack.hdskins.HDSkinManager; import com.voxelmodpack.hdskins.HDSkinManager;
import com.voxelmodpack.hdskins.gui.Feature;
import com.voxelmodpack.hdskins.util.IndentedToStringStyle; import com.voxelmodpack.hdskins.util.IndentedToStringStyle;
import com.voxelmodpack.hdskins.util.MoreHttpResponses; import com.voxelmodpack.hdskins.util.MoreHttpResponses;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
@ -176,6 +177,19 @@ public class ValhallaSkinServer implements SkinServer {
return URI.create(String.format("%s/auth/response", this.address)); 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 @Override
public String toString() { public String toString() {
return new IndentedToStringStyle.Builder(this) return new IndentedToStringStyle.Builder(this)

View file

@ -25,7 +25,7 @@ hdskins.server=Server Skin
hdskins.mode.steve=Steve Model hdskins.mode.steve=Steve Model
hdskins.mode.alex=Alex Model hdskins.mode.alex=Alex Model
hdskins.mode.skin=%s hdskins.mode.skin=%s Texture
hdskins.mode.stand=Standing hdskins.mode.stand=Standing
hdskins.mode.sleep=Sleeping hdskins.mode.sleep=Sleeping
@ -43,4 +43,6 @@ hdskins.options.browse=Browse
hdskins.options.skindrops=Experimental Skin Drop hdskins.options.skindrops=Experimental Skin Drop
hdskins.options.cache=Clear Skin Cache 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.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.