From 20957106df388107b38976ee3800777adccabeab Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Sat, 9 Oct 2021 17:49:17 +0800 Subject: [PATCH] feat(skin): custom model. --- .../ui/account/OfflineAccountSkinPane.java | 25 ++++++++++++++----- .../resources/assets/lang/I18N.properties | 6 ++++- .../resources/assets/lang/I18N_zh.properties | 6 ++++- .../assets/lang/I18N_zh_CN.properties | 6 ++++- .../org/jackhuang/hmcl/auth/offline/Skin.java | 23 ++++++++++++++--- .../hmcl/auth/offline/YggdrasilServer.java | 11 +++++++- 6 files changed, 64 insertions(+), 13 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OfflineAccountSkinPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OfflineAccountSkinPane.java index 9bb9d5f96..847fad0bf 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OfflineAccountSkinPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OfflineAccountSkinPane.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.ui.account; import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXComboBox; import com.jfoenix.controls.JFXDialogLayout; import com.jfoenix.controls.JFXTextField; import javafx.application.Platform; @@ -46,6 +47,7 @@ import java.util.Arrays; import java.util.logging.Level; import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; +import static org.jackhuang.hmcl.ui.FXUtils.stringConverter; import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -54,6 +56,7 @@ public class OfflineAccountSkinPane extends StackPane { private final MultiFileItem skinItem = new MultiFileItem<>(); private final JFXTextField cslApiField = new JFXTextField(); + private final JFXComboBox modelCombobox = new JFXComboBox<>(); private final FileSelector skinSelector = new FileSelector(); private final FileSelector capeSelector = new FileSelector(); @@ -95,7 +98,7 @@ public class OfflineAccountSkinPane extends StackPane { } }); - TransitionPane skinOptionPane = new TransitionPane(); + StackPane skinOptionPane = new StackPane(); skinOptionPane.setMaxWidth(300); VBox optionPane = new VBox(skinItem, skinOptionPane); pane.setRight(optionPane); @@ -114,11 +117,16 @@ public class OfflineAccountSkinPane extends StackPane { new MultiFileItem.Option<>(i18n("account.skin.type.csl_api"), Skin.Type.CUSTOM_SKIN_LOADER_API) )); + modelCombobox.setConverter(stringConverter(model -> i18n("account.skin.model." + model.modelName))); + modelCombobox.getItems().setAll(TextureModel.STEVE, TextureModel.ALEX); + if (account.getSkin() == null) { skinItem.setSelectedData(Skin.Type.DEFAULT); + modelCombobox.setValue(TextureModel.STEVE); } else { skinItem.setSelectedData(account.getSkin().getType()); cslApiField.setText(account.getSkin().getCslApi()); + modelCombobox.setValue(account.getSkin().getTextureModel()); skinSelector.setValue(account.getSkin().getLocalSkinPath()); capeSelector.setValue(account.getSkin().getLocalCapePath()); } @@ -150,18 +158,23 @@ public class OfflineAccountSkinPane extends StackPane { case DEFAULT: case STEVE: case ALEX: + break; case LITTLE_SKIN: + HintPane hint = new HintPane(MessageDialogPane.MessageType.INFO); + hint.setText(i18n("account.skin.type.little_skin.hint")); + gridPane.addRow(0, hint); break; case LOCAL_FILE: - gridPane.addRow(0, new Label(i18n("account.skin")), skinSelector); - gridPane.addRow(1, new Label(i18n("account.cape")), capeSelector); + gridPane.addRow(0, new Label(i18n("account.skin.model")), modelCombobox); + gridPane.addRow(1, new Label(i18n("account.skin")), skinSelector); + gridPane.addRow(2, new Label(i18n("account.cape")), capeSelector); break; case CUSTOM_SKIN_LOADER_API: gridPane.addRow(0, new Label(i18n("account.skin.type.csl_api.location")), cslApiField); break; } - skinOptionPane.setContent(gridPane, ContainerAnimations.NONE.getAnimationProducer()); + skinOptionPane.getChildren().setAll(gridPane); }); JFXButton acceptButton = new JFXButton(i18n("button.ok")); @@ -171,7 +184,7 @@ public class OfflineAccountSkinPane extends StackPane { fireEvent(new DialogCloseEvent()); }); - JFXHyperlink littleSkinLink = new JFXHyperlink(i18n("account.skin.type.little_skin.hint")); + JFXHyperlink littleSkinLink = new JFXHyperlink(i18n("account.skin.type.little_skin")); littleSkinLink.setOnAction(e -> FXUtils.openLink("https://mcskin.littleservice.cn/")); JFXButton cancelButton = new JFXButton(i18n("button.cancel")); cancelButton.getStyleClass().add("dialog-cancel"); @@ -182,7 +195,7 @@ public class OfflineAccountSkinPane extends StackPane { } private Skin getSkin() { - return new Skin(skinItem.getSelectedData(), cslApiField.getText(), skinSelector.getValue(), capeSelector.getValue()); + return new Skin(skinItem.getSelectedData(), cslApiField.getText(), modelCombobox.getValue(), skinSelector.getValue(), capeSelector.getValue()); } private boolean isDefaultSlim() { diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 4e6ab1ff5..e49eb2bbf 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -113,10 +113,14 @@ account.not_logged_in=Not logged in account.password=Password account.skin=Skin account.skin.file=Skin image file +account.skin.model=Model +account.skin.model.default=Classic (Steve) +account.skin.model.slim=Slim (Alex) account.skin.type.csl_api=Blessing Skin account.skin.type.csl_api.location=Address account.skin.type.csl_api.location.hint=CustomSkinAPI -account.skin.type.little_skin.hint=LittleSkin +account.skin.type.little_skin=LittleSkin +account.skin.type.little_skin.hint=You need to create a character in skin website. And the skin of this offline account will be bound to the skin of the character in the skin website with the same name. account.skin.type.local_file=Local skin image file account.skin.upload=Upload skin account.skin.upload.failed=Failed to upload skin diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index a76c148bf..13626355f 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -113,10 +113,14 @@ account.not_logged_in=未登入 account.password=密碼 account.skin=皮膚 account.skin.file=皮膚圖片檔案 +account.skin.model=模型 +account.skin.model.default=經典(Steve) +account.skin.model.slim=苗條(Alex) account.skin.type.csl_api=Blessing Skin 伺服器 account.skin.type.csl_api.location=伺服器位址 account.skin.type.csl_api.location.hint=CustomSkinAPI 位址 -account.skin.type.little_skin.hint=LittleSkin 皮膚站 +account.skin.type.little_skin=LittleSkin 皮膚站 +account.skin.type.little_skin.hint=你需要在皮膚站中創建並使用和該離線帳戶角色同名角色,此時離線帳戶皮膚將為皮膚站上角色所設定的皮膚。 account.skin.type.local_file=本地皮膚圖片檔案 account.skin.upload=上傳皮膚 account.skin.upload.failed=皮膚上傳失敗 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 7205d3a32..9f27d89a5 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -113,10 +113,14 @@ account.not_logged_in=未登录 account.password=密码 account.skin=皮肤 account.skin.file=皮肤图片文件 +account.skin.model=模型 +account.skin.model.default=经典(Steve) +account.skin.model.slim=苗条(Alex) account.skin.type.csl_api=Blessing Skin 服务器 account.skin.type.csl_api.location=服务器地址 account.skin.type.csl_api.location.hint=CustomSkinAPI 地址 -account.skin.type.little_skin.hint=LittleSkin 皮肤站 +account.skin.type.little_skin=LittleSkin 皮肤站 +account.skin.type.little_skin.hint=你需要在皮肤站中创建并使用和该离线帐户角色同名角色,此时离线帐户皮肤将为皮肤站上角色所设定的皮肤。 account.skin.type.local_file=本地皮肤图片文件 account.skin.upload=上传皮肤 account.skin.upload.failed=皮肤上传失败 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/Skin.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/Skin.java index 6a641ea76..f1afd65b9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/Skin.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/Skin.java @@ -79,12 +79,14 @@ public class Skin { private final Type type; private final String cslApi; + private final TextureModel textureModel; private final String localSkinPath; private final String localCapePath; - public Skin(Type type, String cslApi, String localSkinPath, String localCapePath) { + public Skin(Type type, String cslApi, TextureModel textureModel, String localSkinPath, String localCapePath) { this.type = type; this.cslApi = cslApi; + this.textureModel = textureModel; this.localSkinPath = localSkinPath; this.localCapePath = localCapePath; } @@ -97,6 +99,10 @@ public class Skin { return cslApi; } + public TextureModel getTextureModel() { + return textureModel == null ? TextureModel.STEVE : textureModel; + } + public String getLocalSkinPath() { return localSkinPath; } @@ -120,7 +126,7 @@ public class Skin { Optional capePath = FileUtils.tryGetPath(localCapePath); if (skinPath.isPresent()) skin = Texture.loadTexture(Files.newInputStream(skinPath.get())); if (capePath.isPresent()) cape = Texture.loadTexture(Files.newInputStream(capePath.get())); - return new LoadedSkin(TextureModel.STEVE, skin, cape); + return new LoadedSkin(getTextureModel(), skin, cape); }); case LITTLE_SKIN: case CUSTOM_SKIN_LOADER_API: @@ -167,6 +173,7 @@ public class Skin { return mapOf( pair("type", type.name().toLowerCase(Locale.ROOT)), pair("cslApi", cslApi), + pair("textureModel", getTextureModel().modelName), pair("localSkinPath", localSkinPath), pair("localCapePath", localCapePath) ); @@ -178,10 +185,20 @@ public class Skin { Type type = tryCast(storage.get("type"), String.class).flatMap(t -> Optional.ofNullable(Type.fromStorage(t))) .orElse(Type.DEFAULT); String cslApi = tryCast(storage.get("cslApi"), String.class).orElse(null); + String textureModel = tryCast(storage.get("textureModel"), String.class).orElse("default"); String localSkinPath = tryCast(storage.get("localSkinPath"), String.class).orElse(null); String localCapePath = tryCast(storage.get("localCapePath"), String.class).orElse(null); - return new Skin(type, cslApi, localSkinPath, localCapePath); + TextureModel model; + if ("default".equals(textureModel)) { + model = TextureModel.STEVE; + } else if ("slim".equals(textureModel)) { + model = TextureModel.ALEX; + } else { + model = TextureModel.STEVE; + } + + return new Skin(type, cslApi, model, localSkinPath, localCapePath); } private static class FetchBytesTask extends FetchTask { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/YggdrasilServer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/YggdrasilServer.java index 10fa6d084..74cb52302 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/YggdrasilServer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/YggdrasilServer.java @@ -19,6 +19,7 @@ package org.jackhuang.hmcl.auth.offline; import com.google.gson.reflect.TypeToken; import org.jackhuang.hmcl.auth.yggdrasil.GameProfile; +import org.jackhuang.hmcl.auth.yggdrasil.TextureModel; import org.jackhuang.hmcl.util.KeyUtils; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.gson.JsonUtils; @@ -163,7 +164,15 @@ public class YggdrasilServer extends HttpServer { public Object toCompleteResponse(String rootUrl) { Map realTextures = new HashMap<>(); if (skin != null && skin.getSkin() != null) { - realTextures.put("SKIN", mapOf(pair("url", rootUrl + "/textures/" + skin.getSkin().getHash()))); + if (skin.getModel() == TextureModel.ALEX) { + realTextures.put("SKIN", mapOf( + pair("url", rootUrl + "/textures/" + skin.getSkin().getHash()), + pair("metadata", mapOf( + pair("model", "slim") + )))); + } else { + realTextures.put("SKIN", mapOf(pair("url", rootUrl + "/textures/" + skin.getSkin().getHash()))); + } } if (skin != null && skin.getCape() != null) { realTextures.put("CAPE", mapOf(pair("url", rootUrl + "/textures/" + skin.getSkin().getHash())));