diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java index fa3f43206..b99d5ed62 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java @@ -25,20 +25,20 @@ import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.event.Event; import org.jackhuang.hmcl.event.EventManager; +import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.mod.ModAdviser; import org.jackhuang.hmcl.mod.Modpack; import org.jackhuang.hmcl.mod.ModpackConfiguration; import org.jackhuang.hmcl.mod.ModpackProvider; import org.jackhuang.hmcl.setting.Profile; -import org.jackhuang.hmcl.util.FileSaver; import org.jackhuang.hmcl.setting.VersionIconType; import org.jackhuang.hmcl.setting.VersionSetting; import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.util.FileSaver; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.FileUtils; -import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.platform.SystemInfo; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; @@ -55,8 +55,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import static org.jackhuang.hmcl.setting.ConfigHolder.config; -import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.Pair.pair; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; public final class HMCLGameRepository extends DefaultGameRepository { private final Profile profile; @@ -418,7 +418,6 @@ public final class HMCLGameRepository extends DefaultGameRepository { .setWidth(vs.getWidth()) .setHeight(vs.getHeight()) .setFullscreen(vs.isFullscreen()) - .setServerIp(vs.getServerIp()) .setWrapper(vs.getWrapper()) .setPreLaunchCommand(vs.getPreLaunchCommand()) .setPostExitCommand(vs.getPostExitCommand()) @@ -435,6 +434,10 @@ public final class HMCLGameRepository extends DefaultGameRepository { .setJavaAgents(javaAgents) .setJavaArguments(javaArguments); + if (StringUtils.isNotBlank(vs.getServerIp())) { + builder.setQuickPlayOption(new QuickPlayOption.MultiPlayer(vs.getServerIp())); + } + if (config().hasProxy()) { builder.setProxyType(config().getProxyType()); builder.setProxyHost(config().getProxyHost()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index 51ed1d5de..65e34feb1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -52,6 +52,7 @@ import org.jackhuang.hmcl.util.versioning.VersionNumber; import java.io.FileNotFoundException; import java.io.IOException; +import java.lang.ref.WeakReference; import java.net.SocketTimeoutException; import java.net.URI; import java.nio.file.AccessDeniedException; @@ -61,7 +62,6 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; -import java.lang.ref.WeakReference; import static javafx.application.Platform.runLater; import static javafx.application.Platform.setImplicitExit; @@ -82,6 +82,7 @@ public final class LauncherHelper { private final VersionSetting setting; private LauncherVisibility launcherVisibility; private boolean showLogs; + private QuickPlayOption quickPlayOption; private boolean disableOfflineSkin = false; public LauncherHelper(Profile profile, Account account, String selectedVersion) { @@ -113,6 +114,10 @@ public final class LauncherHelper { launcherVisibility = LauncherVisibility.KEEP; } + public void setQuickPlayOption(QuickPlayOption quickPlayOption) { + this.quickPlayOption = quickPlayOption; + } + public void setDisableOfflineSkin() { disableOfflineSkin = true; } @@ -199,6 +204,9 @@ public final class LauncherHelper { if (disableOfflineSkin) { launchOptionsBuilder.setDaemon(false); } + if (quickPlayOption != null) { + launchOptionsBuilder.setQuickPlayOption(quickPlayOption); + } LaunchOptions launchOptions = launchOptionsBuilder.create(); LOG.info("Here's the structure of game mod directory:\n" + FileUtils.printFileStructure(repository.getModsDirectory(selectedVersion), 10)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java index 95833a79b..a13bde94e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java @@ -373,9 +373,8 @@ public class DecoratorController { if (navigator.getCurrentPage() instanceof DecoratorPage) { DecoratorPage page = (DecoratorPage) navigator.getCurrentPage(); - // FIXME: Get WorldPage working first, and revisit this later - page.closePage(); if (page.isPageCloseable()) { + page.closePage(); return; } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java index 785fd5c17..817d099a4 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java @@ -346,7 +346,7 @@ public final class MainPage extends StackPane implements DecoratorPage { private void launch() { Profile profile = Profiles.getSelectedProfile(); - Versions.launch(profile, profile.getSelectedVersion(), null); + Versions.launch(profile, profile.getSelectedVersion()); } private void launchNoGame() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItem.java index 79820e5c9..bd6751451 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItem.java @@ -85,10 +85,14 @@ public class GameListItem extends Control { Versions.openFolder(profile, version); } - public void launch() { + public void testGame() { Versions.testGame(profile, version); } + public void launch() { + Versions.launch(profile, version); + } + public void modifyGameSettings() { Versions.modifyGameSettings(profile, version); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java index f49a901e5..70b6e8c01 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java @@ -44,7 +44,7 @@ public class GameListItemSkin extends SkinBase { JFXPopup popup = new JFXPopup(menu); menu.getContent().setAll( - new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n("version.launch.test"), () -> currentSkinnable.launch(), popup), + new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n("version.launch.test"), () -> currentSkinnable.testGame(), popup), new IconedMenuItem(SVG.SCRIPT, i18n("version.launch_script"), () -> currentSkinnable.generateLaunchScript(), popup), new MenuSeparator(), new IconedMenuItem(SVG.SETTINGS, i18n("version.manage.manage"), () -> currentSkinnable.modifyGameSettings(), popup), @@ -87,7 +87,7 @@ public class GameListItemSkin extends SkinBase { { JFXButton btnLaunch = new JFXButton(); - btnLaunch.setOnAction(e -> skinnable.launch()); + btnLaunch.setOnAction(e -> skinnable.testGame()); btnLaunch.getStyleClass().add("toggle-icon4"); BorderPane.setAlignment(btnLaunch, Pos.CENTER); btnLaunch.setGraphic(FXUtils.limitingSize(SVG.ROCKET_LAUNCH.createIcon(24), 24, 24)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java index 9eccce3bc..90781fd4d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java @@ -23,10 +23,7 @@ import javafx.stage.FileChooser; import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; import org.jackhuang.hmcl.download.game.GameAssetDownloadTask; -import org.jackhuang.hmcl.game.GameDirectoryType; -import org.jackhuang.hmcl.game.GameRepository; -import org.jackhuang.hmcl.game.HMCLGameRepository; -import org.jackhuang.hmcl.game.LauncherHelper; +import org.jackhuang.hmcl.game.*; import org.jackhuang.hmcl.mod.RemoteMod; import org.jackhuang.hmcl.setting.*; import org.jackhuang.hmcl.task.*; @@ -200,7 +197,8 @@ public final class Versions { } } - public static void generateLaunchScript(Profile profile, String id) { + @SafeVarargs + public static void generateLaunchScript(Profile profile, String id, Consumer... injecters) { if (!checkVersionForLaunching(profile, id)) return; ensureSelectedAccount(account -> { @@ -219,18 +217,25 @@ public final class Versions { : new FileChooser.ExtensionFilter(i18n("extension.sh"), "*.sh")); chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.ps1"), "*.ps1")); Path file = FileUtils.toPath(chooser.showSaveDialog(Controllers.getStage())); - if (file != null) - new LauncherHelper(profile, account, id).makeLaunchScript(file); + if (file != null) { + LauncherHelper launcherHelper = new LauncherHelper(profile, account, id); + for (Consumer injecter : injecters) { + injecter.accept(launcherHelper); + } + launcherHelper.makeLaunchScript(file); + } }); } - public static void launch(Profile profile, String id, Consumer injecter) { + @SafeVarargs + public static void launch(Profile profile, String id, Consumer... injecters) { if (!checkVersionForLaunching(profile, id)) return; ensureSelectedAccount(account -> { LauncherHelper launcherHelper = new LauncherHelper(profile, account, id); - if (injecter != null) + for (Consumer injecter : injecters) { injecter.accept(launcherHelper); + } launcherHelper.launch(); }); } @@ -239,6 +244,16 @@ public final class Versions { launch(profile, id, LauncherHelper::setTestMode); } + public static void launchAndEnterWorld(Profile profile, String id, String worldFolderName) { + launch(profile, id, launcherHelper -> + launcherHelper.setQuickPlayOption(new QuickPlayOption.SinglePlayer(worldFolderName))); + } + + public static void generateLaunchScriptForQuickEnterWorld(Profile profile, String id, String worldFolderName) { + generateLaunchScript(profile, id, launcherHelper -> + launcherHelper.setQuickPlayOption(new QuickPlayOption.SinglePlayer(worldFolderName))); + } + private static boolean checkVersionForLaunching(Profile profile, String id) { if (id == null || !profile.getRepository().isLoaded() || !profile.getRepository().hasVersion(id)) { JFXButton gotoDownload = new JFXButton(i18n("version.empty.launch.goto_download")); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItem.java index 5927c4414..7e8932bc8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItem.java @@ -22,6 +22,7 @@ import javafx.scene.control.Skin; import javafx.stage.FileChooser; import org.jackhuang.hmcl.game.World; import org.jackhuang.hmcl.game.WorldLockedException; +import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; @@ -39,11 +40,15 @@ public final class WorldListItem extends Control { private final World world; private final Path backupsDir; private final WorldListPage parent; + private final Profile profile; + private final String id; - public WorldListItem(WorldListPage parent, World world, Path backupsDir) { + public WorldListItem(WorldListPage parent, World world, Path backupsDir, Profile profile, String id) { this.world = world; this.backupsDir = backupsDir; this.parent = parent; + this.profile = profile; + this.id = id; } @Override @@ -91,6 +96,14 @@ public final class WorldListItem extends Control { } public void showManagePage() { - Controllers.navigate(new WorldManagePage(world, backupsDir)); + Controllers.navigate(new WorldManagePage(world, backupsDir, profile, id)); + } + + public void launch() { + Versions.launchAndEnterWorld(profile, id, world.getFileName()); + } + + public void generateLaunchScript() { + Versions.generateLaunchScriptForQuickEnterWorld(profile, id, world.getFileName()); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItemSkin.java index d9dabadb3..ee1dfa184 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItemSkin.java @@ -115,8 +115,19 @@ public final class WorldListItemSkin extends SkinBase { WorldListItem item = getSkinnable(); World world = item.getWorld(); - popupMenu.getContent().addAll( - new IconedMenuItem(SVG.SETTINGS, i18n("world.manage.button"), item::showManagePage, popup)); + if (world.getGameVersion() != null && world.getGameVersion().isAtLeast("1.20", "23w14a")) { + + IconedMenuItem launchItem = new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n("version.launch_and_enter_world"), item::launch, popup); + launchItem.setDisable(world.isLocked()); + popupMenu.getContent().add(launchItem); + + popupMenu.getContent().addAll( + new IconedMenuItem(SVG.SCRIPT, i18n("version.launch_script"), item::generateLaunchScript, popup), + new MenuSeparator() + ); + } + + popupMenu.getContent().add(new IconedMenuItem(SVG.SETTINGS, i18n("world.manage.button"), item::showManagePage, popup)); if (ChunkBaseApp.isSupported(world)) { popupMenu.getContent().addAll( diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java index 7715bfc15..30d3dd5f9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java @@ -61,7 +61,7 @@ public final class WorldListPage extends ListPageBase implements if (worlds != null) itemsProperty().setAll(worlds.stream() .filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion)) - .map(world -> new WorldListItem(this, world, backupsDir)).toList()); + .map(world -> new WorldListItem(this, world, backupsDir, profile, id)).toList()); }); } @@ -100,7 +100,7 @@ public final class WorldListPage extends ListPageBase implements if (exception == null) { itemsProperty().setAll(result.stream() .filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion)) - .map(world -> new WorldListItem(this, world, backupsDir)) + .map(world -> new WorldListItem(this, world, backupsDir, profile, id)) .collect(Collectors.toList())); } else { LOG.warning("Failed to load world list page", exception); @@ -131,7 +131,7 @@ public final class WorldListPage extends ListPageBase implements Controllers.prompt(i18n("world.name.enter"), (name, resolve, reject) -> { Task.runAsync(() -> world.install(savesDir, name)) .whenComplete(Schedulers.javafx(), () -> { - itemsProperty().add(new WorldListItem(this, new World(savesDir.resolve(name)), backupsDir)); + itemsProperty().add(new WorldListItem(this, new World(savesDir.resolve(name)), backupsDir, profile, id)); resolve.run(); }, e -> { if (e instanceof FileAlreadyExistsException) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManagePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManagePage.java index 04c2fc74b..3729e8f8a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManagePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManagePage.java @@ -26,6 +26,7 @@ import javafx.scene.layout.BorderPane; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import org.jackhuang.hmcl.game.World; +import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.animation.TransitionPane; @@ -49,6 +50,8 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco private final ObjectProperty state; private final World world; private final Path backupsDir; + private final Profile profile; + private final String id; private final TabHeader header; private final TabHeader.Tab worldInfoTab = new TabHeader.Tab<>("worldInfoPage"); @@ -59,10 +62,13 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco private FileChannel sessionLockChannel; - public WorldManagePage(World world, Path backupsDir) { + public WorldManagePage(World world, Path backupsDir, Profile profile, String id) { this.world = world; this.backupsDir = backupsDir; + this.profile = profile; + this.id = id; + this.worldInfoTab.setNodeSupplier(() -> new WorldInfoPage(this)); this.worldBackupsTab.setNodeSupplier(() -> new WorldBackupsPage(this)); this.datapackTab.setNodeSupplier(() -> new DatapackListPage(this)); @@ -71,6 +77,13 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco this.header = new TabHeader(transitionPane, worldInfoTab, worldBackupsTab); header.select(worldInfoTab); + // Does it need to be done in the background? + try { + sessionLockChannel = world.lock(); + LOG.info("Acquired lock on world " + world.getFileName()); + } catch (IOException ignored) { + } + setCenter(transitionPane); BorderPane left = new BorderPane(); @@ -91,6 +104,12 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco left.setTop(sideBar); AdvancedListBox toolbar = new AdvancedListBox(); + + if (world.getGameVersion() != null && world.getGameVersion().isAtLeast("1.20", "23w14a")) { + toolbar.addNavigationDrawerItem(i18n("version.launch"), SVG.ROCKET_LAUNCH, this::launch, null); + toolbar.addNavigationDrawerItem(i18n("version.launch_script"), SVG.SCRIPT, this::generateLaunchScript, null); + } + if (ChunkBaseApp.isSupported(world)) { PopupMenu popupMenu = new PopupMenu(); JFXPopup popup = new JFXPopup(popupMenu); @@ -117,12 +136,7 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco BorderPane.setMargin(toolbar, new Insets(0, 0, 12, 0)); left.setBottom(toolbar); - // Does it need to be done in the background? - try { - sessionLockChannel = world.lock(); - LOG.info("Acquired lock on world " + world.getFileName()); - } catch (IOException ignored) { - } + this.addEventHandler(Navigator.NavigationEvent.EXITED, this::onExited); } @Override @@ -142,14 +156,7 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco return sessionLockChannel == null; } - @Override - public boolean back() { - closePage(); - return true; - } - - @Override - public void closePage() { + public void onExited(Navigator.NavigationEvent event) { if (sessionLockChannel != null) { try { sessionLockChannel.close(); @@ -161,4 +168,13 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco sessionLockChannel = null; } } + + public void launch() { + fireEvent(new PageCloseEvent()); + Versions.launchAndEnterWorld(profile, id, world.getFileName()); + } + + public void generateLaunchScript() { + Versions.generateLaunchScriptForQuickEnterWorld(profile, id, world.getFileName()); + } } diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index ed938bc1f..7756776a5 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -1560,6 +1560,7 @@ version.game.support_status.unsupported=Unsupported version.game.support_status.untested=Untested version.game.type=Type version.launch=Launch Game +version.launch_and_enter_world=Play World version.launch.empty=Start Game version.launch.empty.installing=Installing Game version.launch.empty.tooltip=Install and launch the latest official release diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index edd551b5a..0a16962b1 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -1344,6 +1344,7 @@ version.game.support_status.unsupported=不支援 version.game.support_status.untested=未經測試 version.game.type=版本類型 version.launch=啟動遊戲 +version.launch_and_enter_world=進入世界 version.launch.empty=開始遊戲 version.launch.empty.installing=安裝遊戲 version.launch.empty.tooltip=安裝並啟動最新正式版遊戲 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 3652fca85..9386d7647 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -1354,6 +1354,7 @@ version.game.support_status.unsupported=不支持 version.game.support_status.untested=未经测试 version.game.type=版本类型 version.launch=启动游戏 +version.launch_and_enter_world=进入世界 version.launch.empty=开始游戏 version.launch.empty.installing=安装游戏 version.launch.empty.tooltip=安装并启动最新正式版游戏 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.java index fb46fd127..dacf26181 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.java @@ -47,7 +47,7 @@ public class LaunchOptions implements Serializable { private Integer width; private Integer height; private boolean fullscreen; - private String serverIp; + private QuickPlayOption quickPlayOption; private String wrapper; private Proxy.Type proxyType; private String proxyHost; @@ -181,11 +181,11 @@ public class LaunchOptions implements Serializable { return fullscreen; } - /** - * The server ip that will connect to when enter game main menu. - */ - public String getServerIp() { - return serverIp; + /// The quick play option. + /// + /// @see Quick Play - Minecraft Wiki + public QuickPlayOption getQuickPlayOption() { + return quickPlayOption; } /** @@ -412,8 +412,8 @@ public class LaunchOptions implements Serializable { return this; } - public Builder setServerIp(String serverIp) { - options.serverIp = serverIp; + public Builder setQuickPlayOption(QuickPlayOption quickPlayOption) { + options.quickPlayOption = quickPlayOption; return this; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/QuickPlayOption.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/QuickPlayOption.java new file mode 100644 index 000000000..f2d9cc061 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/QuickPlayOption.java @@ -0,0 +1,32 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 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.game; + +/// The quick play option. +/// +/// @see Quick Play - Minecraft Wiki +public sealed interface QuickPlayOption { + record SinglePlayer(String worldFolderName) implements QuickPlayOption { + } + + record MultiPlayer(String serverIP) implements QuickPlayOption { + } + + record Realm(String realmID) implements QuickPlayOption { + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java index d02fd574a..3f4ba0098 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java @@ -304,23 +304,31 @@ public class DefaultLauncher extends Launcher { if (argumentsFromAuthInfo != null && argumentsFromAuthInfo.getGame() != null && !argumentsFromAuthInfo.getGame().isEmpty()) res.addAll(Arguments.parseArguments(argumentsFromAuthInfo.getGame(), configuration, features)); - if (StringUtils.isNotBlank(options.getServerIp())) { - String address = options.getServerIp(); + if (options.getQuickPlayOption() instanceof QuickPlayOption.MultiPlayer multiPlayer) { + String address = multiPlayer.serverIP(); try { ServerAddress parsed = ServerAddress.parse(address); - if (GameVersionNumber.asGameVersion(gameVersion).compareTo("1.20") < 0) { + if (GameVersionNumber.asGameVersion(gameVersion).isAtLeast("1.20", "23w14a")) { + res.add("--quickPlayMultiplayer"); + res.add(parsed.getPort() >= 0 ? address : parsed.getHost() + ":25565"); + } else { res.add("--server"); res.add(parsed.getHost()); res.add("--port"); res.add(parsed.getPort() >= 0 ? String.valueOf(parsed.getPort()) : "25565"); - } else { - res.add("--quickPlayMultiplayer"); - res.add(parsed.getPort() < 0 ? address + ":25565" : address); } } catch (IllegalArgumentException e) { LOG.warning("Invalid server address: " + address, e); } + } else if (options.getQuickPlayOption() instanceof QuickPlayOption.SinglePlayer singlePlayer + && GameVersionNumber.asGameVersion(gameVersion).isAtLeast("1.20", "23w14a")) { + res.add("--quickPlaySingleplayer"); + res.add(singlePlayer.worldFolderName()); + } else if (options.getQuickPlayOption() instanceof QuickPlayOption.Realm realm + && GameVersionNumber.asGameVersion(gameVersion).isAtLeast("1.20", "23w14a")) { + res.add("--quickPlayRealms"); + res.add(realm.realmID()); } if (options.isFullscreen())