feat: one-click join server button using manifest serverAddress
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
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;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user