feat: one-click join server button using manifest serverAddress

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
layue13andClaude Sonnet 5.5 committed 2026-11-08 05:29:26 +08:00
1 parent e8df66dcc0
commit 441ce7220f
9 files changed
+92 -2

No files matched your search

@@ -141,4 +141,11 @@ public final class Metadata {
return null;
}
/// Server address used when the installed modpack manifest does not provide one.
///
/// Can be set at build or run time with the `hmcl.server.address` system property; empty by default.
public static String getDefaultServerAddress() {
return System.getProperty("hmcl.server.address", "").trim();
}
}
@@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: Copyright (C) 2026 The HMCL Authors
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
package org.jackhuang.hmcl.game;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.modpack.ModpackConfiguration;
import org.jackhuang.hmcl.modpack.server.ServerModpackManifest;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jetbrains.annotations.NotNullByDefault;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
/// Resolves the server that the one-click join button connects to.
@NotNullByDefault
public final class ServerEndpoint {
private ServerEndpoint() {
}
/// Finds the server address for an instance.
///
/// The address stored in the installed modpack manifest wins, so it changes with modpack updates.
/// [Metadata#getDefaultServerAddress()] is used as the fallback.
///
/// @param configurationFile the instance's `modpack.json` path
/// @return the `host[:port]` address, or `null` when none is configured
public static @Nullable String resolve(Path configurationFile) {
if (Files.isRegularFile(configurationFile)) {
try {
ModpackConfiguration<ServerModpackManifest> configuration =
JsonUtils.fromJsonFile(configurationFile, ModpackConfiguration.typeOf(ServerModpackManifest.class));
String address = configuration != null ? configuration.getManifest().getServerAddress() : null;
if (StringUtils.isNotBlank(address)) {
return address.trim();
}
} catch (Exception e) {
LOG.warning("Unable to read server address from " + configurationFile, e);
}
}
String fallback = Metadata.getDefaultServerAddress();
return fallback.isEmpty() ? null : fallback;
}
}
@@ -329,6 +329,24 @@ public final class Instances {
});
}
/// Launches the instance and connects straight to the server configured for it.
///
/// When no server address is configured the game starts at the title screen.
///
/// @param gameInstance the instance to launch, or `null` when none is available
public static void joinServer(@Nullable HMCLGameInstance gameInstance) {
if (gameInstance == null) {
launch(null);
return;
}
@Nullable String address = ServerEndpoint.resolve(gameInstance.getModpackConfigurationFile());
if (address == null) {
launch(gameInstance);
} else {
launch(gameInstance, helper -> helper.setQuickPlayOption(new QuickPlayOption.MultiPlayer(address)));
}
}
public static void testGame(HMCLGameInstance gameInstance) {
launch(gameInstance, LauncherHelper::setTestMode);
}
@@ -119,7 +119,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
tooltip = new Tooltip(i18n("instance.launch.empty.tooltip"));
FXUtils.installFastTooltip(launchButton, tooltip);
} else {
launchLabel.setText(i18n("instance.launch"));
launchLabel.setText(i18n("server.join"));
currentLabel.setText(currentGame.getId().toString());
graphic.getChildren().setAll(launchLabel, currentLabel);
FXUtils.setOnActionWithCooldown(launchButton, MainPage.this::launch);
@@ -188,7 +188,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
private void launch() {
HMCLGameRepository repository = GameDirectoryManager.getSelectedRepository();
Instances.launch(repository.getSelectedInstance());
Instances.joinServer(repository.getSelectedInstance());
}
/// Opens the server modpack install/update flow when no instance is selected.
@@ -791,6 +791,7 @@ instance.game.support_status.unsupported=Unsupported
instance.game.support_status.untested=Untested
instance.game.type=Type
instance.launch=Launch Game
server.join=Join Server
instance.launch_and_enter_world=Play World
instance.launch.empty=Start Game
instance.launch.empty.installing=Installing Game
@@ -611,6 +611,7 @@ instance.game.snapshots=預版
# instance.game.support_status.untested=
instance.game.type=版類
instance.launch=戲
server.join=入服
# instance.launch_and_enter_world=
instance.launch.empty=啟戲
instance.launch.empty.installing=裝戲
@@ -614,6 +614,7 @@ instance.game.support_status.unsupported=不支持
instance.game.support_status.untested=未经测试
instance.game.type=版本类型
instance.launch=启动游戏
server.join=进入服务器
instance.launch_and_enter_world=进入世界
instance.launch.empty=开始游戏
instance.launch.empty.installing=安装游戏
@@ -608,6 +608,7 @@ instance.game.support_status.unsupported=不支援
instance.game.support_status.untested=未經測試
instance.game.type=版本類型
instance.launch=啟動遊戲
server.join=進入伺服器
instance.launch_and_enter_world=進入世界
instance.launch.empty=開始遊戲
instance.launch.empty.installing=安裝遊戲
@@ -29,12 +29,19 @@ public class ServerModpackManifest implements ModpackManifest, Validation {
private final String fileApi;
private final List<ModpackConfiguration.FileInformation> files;
private final List<Addon> addons;
/// The address players are connected to on launch, or `null` when the manifest does not specify one.
private final @Nullable String serverAddress;
public ServerModpackManifest() {
this("", "", "", "", "", Collections.emptyList(), Collections.emptyList());
}
public ServerModpackManifest(String name, String author, String version, String description, String fileApi, List<ModpackConfiguration.FileInformation> files, List<Addon> addons) {
this(name, author, version, description, fileApi, files, addons, null);
}
public ServerModpackManifest(String name, String author, String version, String description, String fileApi, List<ModpackConfiguration.FileInformation> files, List<Addon> addons, @Nullable String serverAddress) {
this.serverAddress = serverAddress;
this.name = name;
this.author = author;
this.version = version;
@@ -60,6 +67,13 @@ public class ServerModpackManifest implements ModpackManifest, Validation {
return description;
}
/// Returns the server address to connect to after launch.
///
/// @return the `host[:port]` address, or `null` when the manifest does not specify one
public @Nullable String getServerAddress() {
return serverAddress;
}
public String getFileApi() {
return fileApi;
}