diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java index 7b78cb8f9a..762232886d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java @@ -32,6 +32,8 @@ import org.jackhuang.hmcl.auth.microsoft.MicrosoftService; import org.jackhuang.hmcl.auth.offline.OfflineAccount; import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory; +import org.jackhuang.hmcl.auth.demo.DemoAccount; +import org.jackhuang.hmcl.auth.demo.DemoAccountFactory; import org.jackhuang.hmcl.auth.yggdrasil.RemoteAuthenticationException; import org.jackhuang.hmcl.game.OAuthServer; import org.jackhuang.hmcl.task.Schedulers; @@ -72,6 +74,7 @@ private Accounts() { public static final OAuthServer.Factory OAUTH_CALLBACK = new OAuthServer.Factory(); public static final OfflineAccountFactory FACTORY_OFFLINE = new OfflineAccountFactory(AUTHLIB_INJECTOR_DOWNLOADER); + public static final DemoAccountFactory FACTORY_DEMO = new DemoAccountFactory(); public static final AuthlibInjectorAccountFactory FACTORY_AUTHLIB_INJECTOR = new AuthlibInjectorAccountFactory(AUTHLIB_INJECTOR_DOWNLOADER, Accounts::getOrCreateAuthlibInjectorServer); public static final MicrosoftAccountFactory FACTORY_MICROSOFT = new MicrosoftAccountFactory(new MicrosoftService(OAUTH_CALLBACK)); public static final List> FACTORIES = immutableListOf(FACTORY_OFFLINE, FACTORY_MICROSOFT, FACTORY_AUTHLIB_INJECTOR); @@ -82,6 +85,7 @@ private Accounts() { static { type2factory.put("offline", FACTORY_OFFLINE); + type2factory.put("demo", FACTORY_DEMO); type2factory.put("authlibInjector", FACTORY_AUTHLIB_INJECTOR); type2factory.put("microsoft", FACTORY_MICROSOFT); @@ -112,6 +116,8 @@ public static BoundAuthlibInjectorAccountFactory getAccountFactoryByAuthlibInjec public static AccountFactory getAccountFactory(Account account) { if (account instanceof OfflineAccount) return FACTORY_OFFLINE; + else if (account instanceof DemoAccount) + return FACTORY_DEMO; else if (account instanceof AuthlibInjectorAccount) return FACTORY_AUTHLIB_INJECTOR; else if (account instanceof MicrosoftAccount) @@ -400,6 +406,7 @@ private static void removeDanglingAuthlibInjectorAccounts() { // ==== Login type name i18n === private static final Map, String> unlocalizedLoginTypeNames = mapOf( pair(Accounts.FACTORY_OFFLINE, "account.methods.offline"), + pair(Accounts.FACTORY_DEMO, "account.methods.demo"), pair(Accounts.FACTORY_AUTHLIB_INJECTOR, "account.methods.authlib_injector"), pair(Accounts.FACTORY_MICROSOFT, "account.methods.microsoft")); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java index 65bbabb6a8..7fd35ead5a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java @@ -33,6 +33,7 @@ import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; import org.jackhuang.hmcl.auth.offline.OfflineAccount; +import org.jackhuang.hmcl.auth.demo.DemoAccount; import org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile; import org.jackhuang.hmcl.auth.yggdrasil.TextureType; import org.jackhuang.hmcl.setting.Accounts; @@ -81,7 +82,7 @@ public AccountListItem(Account account) { } StringBinding characterName = Bindings.createStringBinding(account::getCharacter, account); - if (account instanceof OfflineAccount) { + if ((account instanceof OfflineAccount) || (account instanceof DemoAccount)) { title.bind(characterName); } else { title.bind( diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java index e0a8181821..528ec4a8e5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java @@ -41,6 +41,7 @@ import org.jackhuang.hmcl.auth.authlibinjector.BoundAuthlibInjectorAccountFactory; import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccountFactory; import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory; +import org.jackhuang.hmcl.auth.demo.DemoAccountFactory; import org.jackhuang.hmcl.auth.yggdrasil.GameProfile; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService; import org.jackhuang.hmcl.game.TexturesLoader; @@ -337,7 +338,7 @@ public AccountDetailsInputPane(AccountFactory factory, Runnable onAction) { int rowIndex = 0; - if (!IntegrityChecker.isOfficial() && !(factory instanceof OfflineAccountFactory)) { + if (!IntegrityChecker.isOfficial() && !(factory instanceof OfflineAccountFactory) && !(factory instanceof DemoAccountFactory)) { HintPane hintPane = new HintPane(MessageDialogPane.MessageType.WARNING); hintPane.setSegment(i18n("unofficial.hint")); GridPane.setColumnSpan(hintPane, 2); @@ -493,6 +494,15 @@ public AccountDetailsInputPane(AccountFactory factory, Runnable onAction) { rowIndex++; } + if (factory instanceof DemoAccountFactory) { + HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFO); + hintPane.setSegment(i18n("account.methods.demo.hint")); + GridPane.setColumnSpan(hintPane, 2); + add(hintPane, 0, rowIndex); + + rowIndex++; + } + valid = new BooleanBinding() { { if (cboServers != null) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/MicrosoftAccountLoginPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/MicrosoftAccountLoginPane.java index ce6f9c2e4d..4a12d21bf3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/MicrosoftAccountLoginPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/MicrosoftAccountLoginPane.java @@ -44,6 +44,7 @@ import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.TaskExecutor; import org.jackhuang.hmcl.theme.Themes; +import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.WeakListenerHolder; import org.jackhuang.hmcl.ui.construct.*; @@ -112,7 +113,14 @@ public MicrosoftAccountLoginPane(Account account, Consumer callback, R btnCancel.getStyleClass().add("dialog-cancel"); btnCancel.setOnAction(e -> onCancel()); - setActions(loginButtonSpinner, btnCancel); + JFXButton btnDemo = new JFXButton(i18n("account.demo")); + btnDemo.getStyleClass().add("dialog-accept"); + btnDemo.setOnAction(e -> { + onCancel(); + Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_DEMO)); + }); + + setActions(btnDemo, loginButtonSpinner, btnCancel); holder.registerWeak(Accounts.OAUTH_CALLBACK.onOpenBrowserAuthorizationCode, event -> Platform.runLater(() -> { if (step.get() instanceof Step.StartAuthorizationCodeLogin) diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 02d918b457..5a5703c9f5 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -58,6 +58,7 @@ account.choose=Choose a Player account.create=Add Account account.create.microsoft=Add a Microsoft Account account.create.offline=Add an Offline Account +account.create.demo=Add a Demo Account account.create.authlibInjector=Add an authlib-injector Account account.email=Email account.empty=No Accounts @@ -93,6 +94,7 @@ account.login.retry=Retry account.login.refresh=Log in again account.login.refresh.microsoft.hint=You need to log in to your Microsoft account again because the account authorization is invalid. account.login.restricted=Sign in to your Microsoft account to enable this feature +account.demo=Demo Mode account.logout=Logout account.register=Register account.manage=Account List @@ -141,6 +143,8 @@ account.methods.offline.uuid.hint=UUID is a unique identifier for Minecraft play \n\ This option is for advanced users only. We do not recommend changing this option unless you know what you are doing. account.methods.offline.uuid.malformed=Invalid format. +account.methods.demo=Demo Account +account.methods.demo.hint=The Demo Mode is a demo of Minecraft for users who have not purchased the game yet and would like to try it out before making a purchase. account.methods.ban_query=Ban Query account.missing=No Accounts account.missing.add=Click here to add one. diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 2bcfec2041..574f5ca515 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -57,6 +57,7 @@ account.choose=請選取角色 account.create=建立帳戶 account.create.microsoft=新增 Microsoft 帳戶 account.create.offline=新增離線模式帳戶 +account.create.demo=添加試玩模式帳戶 account.create.authlibInjector=新增 authlib-injector 帳戶 account.email=電子信箱 account.empty=沒有帳戶 @@ -96,6 +97,7 @@ account.login.retry=再次重新整理帳戶 account.login.refresh=重新登入 account.login.refresh.microsoft.hint=由於帳戶授權失效,你需要重新增加 Microsoft 帳戶 account.login.restricted=登入微軟帳戶以啟用此功能 +account.demo=試玩模式 account.logout=登出 account.register=註冊 account.manage=帳戶清單 @@ -139,6 +141,8 @@ account.methods.offline.name.invalid=遊戲使用者名稱建議僅使用英文 account.methods.offline.uuid=UUID account.methods.offline.uuid.hint=UUID 是 Minecraft 玩家的唯一標識符。每個啟動器生成 UUID 的方式可能不同。\n透過修改 UUID 選項至原啟動器所生成的 UUID,你可以保證在切換啟動器後,遊戲還能將你的遊戲角色識別為給定 UUID 所對應的角色,從而保留原來角色的背包物品。\nUUID 選項為進階選項。除非你知道你在做什麼,否則你不需要調整該選項。 account.methods.offline.uuid.malformed=格式錯誤 +account.methods.demo=試玩模式 +account.methods.demo.hint=演示模式是 Minecraft 的一個演示版本,供尚未購買遊戲且希望在購買前試玩的用戶使用。 account.missing=沒有遊戲帳戶 account.missing.add=按一下此處新增帳戶 account.move_to_global=轉換為全域帳戶\n該帳戶的資訊會儲存至系統目前使用者目錄的配置檔案中 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 ba95ad274e..c78d8b04d9 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -57,6 +57,7 @@ account.choose=选择一个角色 account.create=添加账户 account.create.microsoft=添加微软账户 account.create.offline=添加离线模式账户 +account.create.demo=添加试玩账户 account.create.authlibInjector=添加外置登录账户 (authlib-injector) account.email=邮箱 account.empty=没有账户 @@ -97,6 +98,7 @@ account.login.retry=再次刷新账户 account.login.refresh=重新登录 account.login.refresh.microsoft.hint=由于账户授权失效,你需要重新添加微软账户。 account.login.restricted=登录微软账户以启用此功能 +account.demo=试玩账户 account.logout=登出 account.register=注册 account.manage=账户列表 @@ -141,6 +143,8 @@ account.methods.offline.name.invalid=游戏用户名建议仅使用英文字母 account.methods.offline.uuid=UUID account.methods.offline.uuid.hint=UUID 是 Minecraft 玩家的唯一标识符。每个启动器生成 UUID 的方式可能不同。通过将 UUID 修改为原启动器所生成的 UUID,你可以保证在切换启动器后,游戏还能将你的游戏角色识别为给定 UUID 所对应的角色,从而保留原角色的背包物品。UUID 选项为高级选项。除非你知道你在做什么,否则你不需要调整该选项。 account.methods.offline.uuid.malformed=格式错误 +account.methods.demo=试玩账户 +account.methods.demo.hint=试玩模式是提供给尚未购买Minecraft,但想要试玩的玩家的版本。 account.missing=没有游戏账户 account.missing.add=点击此处添加账户 account.move_to_global=转换为全局账户\n该账户的信息会保存至系统当前用户文件夹的配置文件中 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/demo/DemoAccount.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/demo/DemoAccount.java new file mode 100644 index 0000000000..89c1a8f0c0 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/demo/DemoAccount.java @@ -0,0 +1,140 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2020 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.auth.demo; + +import javafx.beans.binding.ObjectBinding; +import org.jackhuang.hmcl.auth.Account; +import org.jackhuang.hmcl.auth.AuthInfo; +import org.jackhuang.hmcl.auth.AuthenticationException; +import org.jackhuang.hmcl.auth.yggdrasil.Texture; +import org.jackhuang.hmcl.auth.yggdrasil.TextureType; +import org.jackhuang.hmcl.game.Arguments; +import org.jackhuang.hmcl.game.LaunchOptions; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.ToStringBuilder; +import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter; + +import java.io.IOException; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +import static java.util.Objects.requireNonNull; +import static org.jackhuang.hmcl.util.Lang.mapOf; +import static org.jackhuang.hmcl.util.Pair.pair; + +/** + * + * @author huang + */ +public class DemoAccount extends Account { + + private final String username; + private final UUID uuid; + + protected DemoAccount(String username, UUID uuid) { + this.username = requireNonNull(username); + this.uuid = requireNonNull(uuid); + + if (StringUtils.isBlank(username)) { + throw new IllegalArgumentException("Username cannot be blank"); + } + } + + @Override + public UUID getUUID() { + return uuid; + } + + @Override + public String getUsername() { + return username; + } + + @Override + public String getCharacter() { + return username; + } + + @Override + public String getIdentifier() { + return "demo:" + username; + } + + public AuthInfo logIn() throws AuthenticationException { + // Using "legacy" user type here because "mojang" user type may cause "invalid session token" or "disconnected" when connecting to a game server. + return new DemoAuthInfo(username, uuid, UUIDTypeAdapter.fromUUID(UUID.randomUUID()), AuthInfo.USER_TYPE_MSA, "{}"); + } + + private class DemoAuthInfo extends AuthInfo { + public DemoAuthInfo(String username, UUID uuid, String accessToken, String userType, String userProperties) { + super(username, uuid, accessToken, userType, userProperties); + } + + @Override + public Arguments getLaunchArguments(LaunchOptions options) { + Arguments arguments = new Arguments(); + arguments = arguments.addGameArguments("--demo"); + return arguments; + } + + @Override + public void close() throws IOException { + // Do nothing. + } + } + + @Override + public AuthInfo playOffline() throws AuthenticationException { + return logIn(); + } + + @Override + public Map toStorage() { + return mapOf( + pair("uuid", UUIDTypeAdapter.fromUUID(uuid)), + pair("username", username) + ); + } + + @Override + public ObjectBinding>> getTextures() { + return super.getTextures(); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("username", username) + .append("uuid", uuid) + .toString(); + } + + @Override + public int hashCode() { + return username.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof DemoAccount)) + return false; + DemoAccount another = (DemoAccount) obj; + return isPortable() == another.isPortable() && username.equals(another.username); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/demo/DemoAccountFactory.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/demo/DemoAccountFactory.java new file mode 100644 index 0000000000..ebb5d3f96c --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/demo/DemoAccountFactory.java @@ -0,0 +1,70 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2020 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.auth.demo; + +import org.jackhuang.hmcl.auth.AccountFactory; +import org.jackhuang.hmcl.auth.CharacterSelector; +import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter; + +import java.util.Map; +import java.util.UUID; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.jackhuang.hmcl.util.Lang.tryCast; + +/** + * + * @author huangyuhui + */ +public final class DemoAccountFactory extends AccountFactory { + + public DemoAccountFactory() {} + + @Override + public AccountLoginType getLoginType() { + return AccountLoginType.NONE; + } + + public DemoAccount create(String username, UUID uuid) { + return new DemoAccount(username, uuid); + } + + @Override + public DemoAccount create(CharacterSelector selector, String username, String password, ProgressCallback progressCallback, Object additionalData) { + username = "Player"; + UUID uuid; + uuid = getUUIDFromUserName(username); + return new DemoAccount(username, uuid); + } + + @Override + public DemoAccount fromStorage(Map storage) { + String username = tryCast(storage.get("username"), String.class) + .orElseThrow(() -> new IllegalStateException("Demo account configuration malformed.")); + UUID uuid = tryCast(storage.get("uuid"), String.class) + .map(UUIDTypeAdapter::fromString) + .orElse(getUUIDFromUserName(username)); + + return new DemoAccount(username, uuid); + } + + public static UUID getUUIDFromUserName(String username) { + return UUID.nameUUIDFromBytes(("DemoPlayer:" + username).getBytes(UTF_8)); + } + +}