feat(multiplayer): custom token.

This commit is contained in:
huanghongxun 2021-09-26 00:34:18 +08:00
parent 54954f276b
commit 7c76c919bc
12 changed files with 108 additions and 33 deletions

View File

@ -99,6 +99,10 @@ public final class LauncherHelper {
showLogs = true;
}
public void setKeep() {
launcherVisibility = LauncherVisibility.KEEP;
}
public void launch() {
Logging.LOG.info("Launching game version: " + selectedVersion);

View File

@ -35,8 +35,9 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class DialogPane extends JFXDialogLayout {
private final StringProperty title = new SimpleStringProperty();
private final BooleanProperty valid = new SimpleBooleanProperty();
private final SpinnerPane acceptPane = new SpinnerPane();
private final Label warningLabel = new Label();
protected final SpinnerPane acceptPane = new SpinnerPane();
protected final JFXButton cancelButton = new JFXButton();
protected final Label warningLabel = new Label();
private final JFXProgressBar progressBar = new JFXProgressBar();
public DialogPane() {
@ -57,7 +58,7 @@ public class DialogPane extends JFXDialogLayout {
acceptPane.getStyleClass().add("small-spinner-pane");
acceptPane.setContent(acceptButton);
JFXButton cancelButton = new JFXButton(i18n("button.cancel"));
cancelButton.setText(i18n("button.cancel"));
cancelButton.setOnAction(e -> onCancel());
cancelButton.getStyleClass().add("dialog-cancel");
onEscPressed(this, cancelButton::fire);

View File

@ -228,8 +228,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
}
private void launch() {
Profile profile = Profiles.getSelectedProfile();
Versions.launch(profile, profile.getSelectedVersion());
Versions.launch(Profiles.getSelectedProfile());
}
private void onMenu() {

View File

@ -17,6 +17,9 @@
*/
package org.jackhuang.hmcl.ui.multiplayer;
import com.jfoenix.controls.JFXTextField;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.ColumnConstraints;
@ -24,10 +27,7 @@ import javafx.scene.layout.GridPane;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.DialogAware;
import org.jackhuang.hmcl.ui.construct.DialogPane;
import org.jackhuang.hmcl.ui.construct.HintPane;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.util.FutureCallback;
import java.util.Objects;
@ -38,12 +38,13 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class CreateMultiplayerRoomDialog extends DialogPane implements DialogAware {
private final FutureCallback<LocalServerDetector.PingResponse> callback;
private final StringProperty token = new SimpleStringProperty();
private final FutureCallback<CreationResult> callback;
private final LocalServerDetector lanServerDetectorThread;
private LocalServerDetector.PingResponse server;
CreateMultiplayerRoomDialog(FutureCallback<LocalServerDetector.PingResponse> callback) {
CreateMultiplayerRoomDialog(FutureCallback<CreationResult> callback) {
this.callback = callback;
setTitle(i18n("multiplayer.session.create"));
@ -62,23 +63,34 @@ public class CreateMultiplayerRoomDialog extends DialogPane implements DialogAwa
body.addRow(0, hintPane);
JFXTextField tokenField = new JFXTextField();
tokenField.textProperty().bindBidirectional(token);
tokenField.setPromptText(i18n("multiplayer.session.create.token.prompt"));
body.addRow(1, new Label(i18n("multiplayer.session.create.token")), tokenField);
Label nameField = new Label();
nameField.setText(Optional.ofNullable(Accounts.getSelectedAccount())
.map(Account::getUsername)
.map(username -> i18n("multiplayer.session.name.format", username))
.orElse(""));
body.addRow(1, new Label(i18n("multiplayer.session.create.name")), nameField);
body.addRow(2, new Label(i18n("multiplayer.session.create.name")), nameField);
Label portLabel = new Label(i18n("multiplayer.nat.testing"));
portLabel.setText(i18n("multiplayer.nat.testing"));
body.addRow(2, new Label(i18n("multiplayer.session.create.port")), portLabel);
body.addRow(3, new Label(i18n("multiplayer.session.create.port")), portLabel);
setValid(false);
JFXHyperlink noinLink = new JFXHyperlink();
noinLink.setText("noin.cn");
noinLink.setOnAction(e -> FXUtils.openLink("https://noin.cn"));
setActions(warningLabel, noinLink, acceptPane, cancelButton);
lanServerDetectorThread = new LocalServerDetector(3);
lanServerDetectorThread.onDetectedLanServer().register(event -> {
runInFX(() -> {
if (event.getLanServer().isValid()) {
if (event.getLanServer() != null && event.getLanServer().isValid()) {
nameField.setText(event.getLanServer().getMotd());
portLabel.setText(event.getLanServer().getAd().toString());
setValid(true);
@ -86,6 +98,7 @@ public class CreateMultiplayerRoomDialog extends DialogPane implements DialogAwa
nameField.setText("");
portLabel.setText("");
onFailure(i18n("multiplayer.session.create.port.error"));
setValid(false);
}
server = event.getLanServer();
body.setDisable(false);
@ -98,7 +111,7 @@ public class CreateMultiplayerRoomDialog extends DialogPane implements DialogAwa
protected void onAccept() {
setLoading();
callback.call(Objects.requireNonNull(server), () -> {
callback.call(new CreationResult(token.get(), Objects.requireNonNull(server)), () -> {
runInFX(this::onSuccess);
}, msg -> {
runInFX(() -> onFailure(msg));
@ -116,4 +129,22 @@ public class CreateMultiplayerRoomDialog extends DialogPane implements DialogAwa
public void onDialogClosed() {
lanServerDetectorThread.interrupt();
}
public static class CreationResult {
private final String token;
private final LocalServerDetector.PingResponse server;
public CreationResult(String token, LocalServerDetector.PingResponse server) {
this.token = token;
this.server = server;
}
public String getToken() {
return token;
}
public LocalServerDetector.PingResponse getServer() {
return server;
}
}
}

View File

@ -25,6 +25,7 @@ import org.jackhuang.hmcl.launch.StreamPump;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task;
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.NetworkUtils;
import org.jackhuang.hmcl.util.platform.Architecture;
@ -76,7 +77,7 @@ public final class MultiplayerManager {
return Metadata.HMCL_DIRECTORY.resolve("libraries").resolve(CATO_PATH);
}
public static CatoSession joinSession(String version, String sessionName, String peer, int remotePort, int localPort) throws IOException, IncompatibleCatoVersionException {
public static CatoSession joinSession(String token, String version, String sessionName, String peer, int remotePort, int localPort) throws IOException, IncompatibleCatoVersionException {
if (!CATO_VERSION.equals(version)) {
throw new IncompatibleCatoVersionException(version, CATO_VERSION);
}
@ -85,7 +86,12 @@ public final class MultiplayerManager {
if (!Files.isRegularFile(exe)) {
throw new IllegalStateException("Cato file not found");
}
String[] commands = new String[]{exe.toString(), "--token", "new", "--peer", peer, "--local", String.format("127.0.0.1:%d", localPort), "--remote", String.format("127.0.0.1:%d", remotePort)};
String[] commands = new String[]{exe.toString(),
"--token", StringUtils.isBlank(token) ? "new" : token,
"--peer", peer,
"--local", String.format("127.0.0.1:%d", localPort),
"--remote", String.format("127.0.0.1:%d", remotePort),
"--mode", "relay"};
Process process = new ProcessBuilder()
.command(commands)
.start();
@ -95,7 +101,7 @@ public final class MultiplayerManager {
return session;
}
public static CatoSession createSession(String sessionName, int port) throws IOException {
public static CatoSession createSession(String token, String sessionName, int port) throws IOException {
Path exe = getCatoExecutable();
if (!Files.isRegularFile(exe)) {
throw new IllegalStateException("Cato file not found");
@ -104,7 +110,10 @@ public final class MultiplayerManager {
// MultiplayerServer server = new MultiplayerServer(port);
// server.start();
String[] commands = new String[]{exe.toString(), "--token", "new", "--allows", String.format("127.0.0.1:%d", port)};
String[] commands = new String[]{exe.toString(),
"--token", StringUtils.isBlank(token) ? "new" : token,
"--allows", String.format("127.0.0.1:%d", port),
"--mode", "relay"};
Process process = new ProcessBuilder()
.command(commands)
.start();

View File

@ -169,9 +169,9 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware
}
Controllers.dialog(new CreateMultiplayerRoomDialog((result, resolve, reject) -> {
int port = result.getAd();
int port = result.getServer().getAd();
try {
initCatoSession(MultiplayerManager.createSession(result.getMotd(), port));
initCatoSession(MultiplayerManager.createSession(result.getToken(), result.getServer().getMotd(), port));
} catch (Exception e) {
LOG.log(Level.WARNING, "Failed to create session", e);
reject.accept(i18n("multiplayer.session.create.error"));
@ -190,7 +190,8 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware
}
Controllers.prompt(new PromptDialogPane.Builder(i18n("multiplayer.session.join"), (result, resolve, reject) -> {
String invitationCode = ((PromptDialogPane.Builder.StringQuestion) result.get(1)).getValue();
String token = ((PromptDialogPane.Builder.StringQuestion) result.get(1)).getValue();
String invitationCode = ((PromptDialogPane.Builder.StringQuestion) result.get(2)).getValue();
MultiplayerManager.Invitation invitation;
try {
invitation = MultiplayerManager.parseInvitationCode(invitationCode);
@ -209,7 +210,7 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware
}
try {
initCatoSession(MultiplayerManager.joinSession(invitation.getVersion(), invitation.getSessionName(), invitation.getId(), invitation.getGamePort(), localPort));
initCatoSession(MultiplayerManager.joinSession(token, invitation.getVersion(), invitation.getSessionName(), invitation.getId(), invitation.getGamePort(), localPort));
} catch (MultiplayerManager.IncompatibleCatoVersionException e) {
reject.accept(i18n("multiplayer.session.join.invitation_code.version"));
return;
@ -223,6 +224,7 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware
resolve.run();
})
.addQuestion(new PromptDialogPane.Builder.HintQuestion(i18n("multiplayer.session.join.hint")))
.addQuestion(new PromptDialogPane.Builder.StringQuestion(i18n("multiplayer.session.create.token"), ""))
.addQuestion(new PromptDialogPane.Builder.StringQuestion(i18n("multiplayer.session.join.invitation_code"), "", new RequiredValidator())));
}

View File

@ -25,11 +25,15 @@ import javafx.scene.control.ScrollPane;
import javafx.scene.control.SkinBase;
import javafx.scene.layout.*;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.game.LauncherHelper;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Profiles;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionPane;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.ui.versions.Versions;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
@ -53,31 +57,37 @@ public class MultiplayerPageSkin extends SkinBase<MultiplayerPage> {
AdvancedListItem createRoomItem = new AdvancedListItem();
createRoomItem.setTitle(i18n("multiplayer.session.create"));
createRoomItem.setLeftGraphic(wrap(SVG::plusCircleOutline));
createRoomItem.setActionButtonVisible(false);
createRoomItem.setOnAction(e -> control.createRoom());
AdvancedListItem joinRoomItem = new AdvancedListItem();
joinRoomItem.setTitle(i18n("multiplayer.session.join"));
joinRoomItem.setLeftGraphic(wrap(SVG::accountArrowRightOutline));
joinRoomItem.setActionButtonVisible(false);
joinRoomItem.setOnAction(e -> control.joinRoom());
AdvancedListItem copyLinkItem = new AdvancedListItem();
copyLinkItem.setTitle(i18n("multiplayer.session.copy_room_code"));
copyLinkItem.setLeftGraphic(wrap(SVG::accountArrowRightOutline));
copyLinkItem.setActionButtonVisible(false);
copyLinkItem.setOnAction(e -> control.copyInvitationCode());
AdvancedListItem cancelItem = new AdvancedListItem();
cancelItem.setTitle(i18n("button.cancel"));
cancelItem.setLeftGraphic(wrap(SVG::closeCircle));
cancelItem.setActionButtonVisible(false);
cancelItem.setOnAction(e -> control.cancelRoom());
AdvancedListItem quitItem = new AdvancedListItem();
quitItem.setTitle(i18n("multiplayer.session.quit"));
quitItem.setLeftGraphic(wrap(SVG::closeCircle));
quitItem.setActionButtonVisible(false);
quitItem.setOnAction(e -> control.quitRoom());
AdvancedListItem closeRoomItem = new AdvancedListItem();
closeRoomItem.setTitle(i18n("multiplayer.session.close"));
closeRoomItem.setLeftGraphic(wrap(SVG::closeCircle));
closeRoomItem.setActionButtonVisible(false);
closeRoomItem.setOnAction(e -> control.closeRoom());
FXUtils.onChangeAndOperate(getSkinnable().multiplayerStateProperty(), state -> {
@ -94,6 +104,14 @@ public class MultiplayerPageSkin extends SkinBase<MultiplayerPage> {
}
AdvancedListBox sideBar = new AdvancedListBox()
.addNavigationDrawerItem(item -> {
item.setTitle(i18n("version.launch"));
item.setLeftGraphic(wrap(SVG::rocketLaunchOutline));
item.setOnAction(e -> {
Profile profile = Profiles.getSelectedProfile();
Versions.launch(profile, profile.getSelectedVersion(), LauncherHelper::setKeep);
});
})
.startCategory(i18n("multiplayer.session"))
.add(roomPane)
.startCategory(i18n("help"))

View File

@ -208,22 +208,27 @@ public final class Versions {
});
}
public static void launch(Profile profile) {
launch(profile, profile.getSelectedVersion());
}
public static void launch(Profile profile, String id) {
launch(profile, id, null);
}
public static void launch(Profile profile, String id, Consumer<LauncherHelper> injecter) {
if (!checkVersionForLaunching(profile, id))
return;
ensureSelectedAccount(account -> {
new LauncherHelper(profile, account, id).launch();
LauncherHelper launcherHelper = new LauncherHelper(profile, account, id);
if (injecter != null)
injecter.accept(launcherHelper);
launcherHelper.launch();
});
}
public static void testGame(Profile profile, String id) {
if (!checkVersionForLaunching(profile, id))
return;
ensureSelectedAccount(account -> {
LauncherHelper helper = new LauncherHelper(profile, account, id);
helper.setTestMode();
helper.launch();
});
launch(profile, id, LauncherHelper::setTestMode);
}
private static boolean checkVersionForLaunching(Profile profile, String id) {

View File

@ -620,6 +620,8 @@ multiplayer.session.create.hint=Before creating multiplayer room, you must click
multiplayer.session.create.name=Session Name
multiplayer.session.create.port=Port
multiplayer.session.create.port.error=Cannot detect game port, you must click "Open LAN Server" in game to enable multiplayer functionality.
multiplayer.session.create.token=Token
multiplayer.session.create.token.prompt=Default randomized. You can apply for your own token at noin.cn (Chinese website).
multiplayer.session.expired=Multiplayer session has expired. You should re-create or re-join a room to continue.
multiplayer.session.hint=You must click "Open LAN Server" in game in order to enable multiplayer functionality.
multiplayer.session.join=Join Room

View File

@ -620,6 +620,8 @@ multiplayer.session.create.hint=創建聯機房間前,你需要先在正在運
multiplayer.session.create.name=房間名稱
multiplayer.session.create.port=埠號
multiplayer.session.create.port.error=無法檢測遊戲埠號,你必須先啟動遊戲並在遊戲內打開對區域網路開放選項後才能啟動聯機。
multiplayer.session.create.token=Token
multiplayer.session.create.token.prompt=預設為臨時 Token。你可以在 noin.cn 上申請靜態 Token 並填寫至此處
multiplayer.session.expired=聯機會話連續使用時間超過了 3 小時,你需要重新創建/加入房間以繼續聯機。
multiplayer.session.join=加入房間
multiplayer.session.join.hint=你需要向已經創建好房間的玩家索要邀請碼以便加入多人聯機房間

View File

@ -616,10 +616,12 @@ multiplayer.session.close.warning=关闭房间后,已经加入联机房间的
multiplayer.session.copy_room_code=复制邀请码
multiplayer.session.create=创建房间
multiplayer.session.create.error=创建联机房间失败。
multiplayer.session.create.hint=创建联机房间前,你需要先在正在运行的游戏内的游戏菜单中选择 对局域网开放 选项,然后在下方的输入框中输入游戏内提示的端口号(通常是 5 位的数字)
multiplayer.session.create.hint=创建联机房间前,你需要先在正在运行的游戏内的游戏菜单中选择 对局域网开放 选项,然后在下方的输入框中确认游戏内提示的端口号(通常是 5 位的数字)
multiplayer.session.create.name=房间名称
multiplayer.session.create.port=端口号
multiplayer.session.create.port.error=无法检测游戏端口号,你必须先启动游戏并在游戏内打开对局域网开放选项后才能启动联机。
multiplayer.session.create.token=Token
multiplayer.session.create.token.prompt=默认为临时 Token。你可以在 noin.cn 上申请静态 Token 并填写至此处
multiplayer.session.expired=联机会话连续使用时间超过了 3 小时,你需要重新创建/加入房间以继续联机。
multiplayer.session.join=加入房间
multiplayer.session.join.hint=你需要向已经创建好房间的玩家索要邀请码以便加入多人联机房间

View File

@ -62,7 +62,7 @@ public class YggdrasilServer extends HttpServer {
"localhost"
)),
pair("meta", mapOf(
pair("serverName", "HMCL Offline Account Skin/Cape Server"),
pair("serverName", "HMCL"),
pair("implementationName", "HMCL"),
pair("implementationVersion", "1.0"),
pair("feature.non_email_login", true)