feat(skin): custom model.
This commit is contained in:
@@ -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<Skin.Type> skinItem = new MultiFileItem<>();
|
||||
private final JFXTextField cslApiField = new JFXTextField();
|
||||
private final JFXComboBox<TextureModel> 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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=皮膚上傳失敗
|
||||
|
||||
@@ -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=皮肤上传失败
|
||||
|
||||
@@ -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<Path> 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<InputStream> {
|
||||
|
||||
@@ -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<String, Object> 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())));
|
||||
|
||||
Reference in New Issue
Block a user