From e69ef0ce254d3a7d7c3cfa08544e29c8ce8b37fe Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Thu, 30 Sep 2021 15:01:56 +0800 Subject: [PATCH] feat(multiplayer): cato EULA. --- .../org/jackhuang/hmcl/setting/Config.java | 15 ++++++ .../org/jackhuang/hmcl/ui/Controllers.java | 11 ++++ .../java/org/jackhuang/hmcl/ui/FXUtils.java | 51 ++++++++++--------- .../hmcl/ui/construct/AnnouncementCard.java | 25 +-------- .../ui/multiplayer/MultiplayerManager.java | 1 + .../hmcl/ui/multiplayer/MultiplayerPage.java | 34 ++++++++++++- .../ui/multiplayer/MultiplayerPageSkin.java | 27 +++++++--- .../resources/assets/lang/I18N.properties | 4 +- .../resources/assets/lang/I18N_zh.properties | 4 +- .../assets/lang/I18N_zh_CN.properties | 4 +- 10 files changed, 117 insertions(+), 59 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java index 3918743ff..ea3a87c85 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java @@ -158,6 +158,9 @@ public final class Config implements Cloneable, Observable { @SerializedName("logLines") private IntegerProperty logLines = new SimpleIntegerProperty(100); + @SerializedName("multiplayerAgreementVersion") + private IntegerProperty multiplayerAgreementVersion = new SimpleIntegerProperty(0); + @SerializedName("multiplayerToken") private StringProperty multiplayerToken = new SimpleStringProperty(); @@ -584,6 +587,18 @@ public final class Config implements Cloneable, Observable { return preferredLoginType; } + public int getMultiplayerAgreementVersion() { + return multiplayerAgreementVersion.get(); + } + + public IntegerProperty multiplayerAgreementVersionProperty() { + return multiplayerAgreementVersion; + } + + public void setMultiplayerAgreementVersion(int multiplayerAgreementVersion) { + this.multiplayerAgreementVersion.set(multiplayerAgreementVersion); + } + public String getMultiplayerToken() { return multiplayerToken.get(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java index 9c0b682bf..d015b9dc3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -298,6 +298,17 @@ public final class Controllers { decorator.showToast(content); } + public static void onHyperlinkAction(String href) { + if (href.startsWith("hmcl://")) { + if ("hmcl://settings/feedback".equals(href)) { + Controllers.getSettingsPage().showFeedback(); + Controllers.navigate(Controllers.getSettingsPage()); + } + } else { + FXUtils.openLink(href); + } + } + public static boolean isStopped() { return decorator == null; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index 5a1553218..9e6627730 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -700,35 +700,40 @@ public final class FXUtils { Controllers.showToast(i18n("message.copied")); } - public static TextFlow segmentToTextFlow(final String segment, Consumer hyperlinkAction) throws ParserConfigurationException, IOException, SAXException { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - DocumentBuilder builder = factory.newDocumentBuilder(); - Document doc = builder.parse(new InputSource(new StringReader("" + segment + ""))); - Element r = doc.getDocumentElement(); + public static TextFlow segmentToTextFlow(final String segment, Consumer hyperlinkAction) { + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document doc = builder.parse(new InputSource(new StringReader("" + segment + ""))); + Element r = doc.getDocumentElement(); - NodeList children = r.getChildNodes(); - List texts = new ArrayList<>(); - for (int i = 0; i < children.getLength(); i++) { - org.w3c.dom.Node node = children.item(i); + NodeList children = r.getChildNodes(); + List texts = new ArrayList<>(); + for (int i = 0; i < children.getLength(); i++) { + org.w3c.dom.Node node = children.item(i); - if (node instanceof Element) { - Element element = (Element) node; - if ("a".equals(element.getTagName())) { - String href = element.getAttribute("href"); - JFXHyperlink hyperlink = new JFXHyperlink(element.getTextContent()); - hyperlink.setOnAction(e -> hyperlinkAction.accept(href)); - texts.add(hyperlink); - } else if ("br".equals(element.getTagName())) { - texts.add(new Text("\n")); + if (node instanceof Element) { + Element element = (Element) node; + if ("a".equals(element.getTagName())) { + String href = element.getAttribute("href"); + JFXHyperlink hyperlink = new JFXHyperlink(element.getTextContent()); + hyperlink.setOnAction(e -> hyperlinkAction.accept(href)); + texts.add(hyperlink); + } else if ("br".equals(element.getTagName())) { + texts.add(new Text("\n")); + } else { + throw new IllegalArgumentException("unsupported tag " + element.getTagName()); + } } else { - throw new IllegalArgumentException("unsupported tag " + element.getTagName()); + texts.add(new Text(node.getTextContent())); } - } else { - texts.add(new Text(node.getTextContent())); } + final TextFlow tf = new TextFlow(texts.toArray(new javafx.scene.Node[0])); + return tf; + } catch (SAXException | ParserConfigurationException | IOException e) { + LOG.log(Level.WARNING, "Failed to parse xml", e); + return new TextFlow(new Text(segment)); } - final TextFlow tf = new TextFlow(texts.toArray(new javafx.scene.Node[0])); - return tf; } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AnnouncementCard.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AnnouncementCard.java index e6536e749..7262bcadd 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AnnouncementCard.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AnnouncementCard.java @@ -19,26 +19,14 @@ package org.jackhuang.hmcl.ui.construct; import javafx.scene.control.Label; import javafx.scene.layout.VBox; -import javafx.scene.text.Text; import javafx.scene.text.TextFlow; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; -import java.util.logging.Level; - -import static org.jackhuang.hmcl.util.Logging.LOG; - public class AnnouncementCard extends VBox { public AnnouncementCard(String title, String content) { - TextFlow tf; - try { - tf = FXUtils.segmentToTextFlow(content, AnnouncementCard::onAction); - } catch (Exception e) { - LOG.log(Level.WARNING, "Failed to parse announcement content", e); - tf = new TextFlow(); - tf.getChildren().setAll(new Text(content)); - } + TextFlow tf = FXUtils.segmentToTextFlow(content, Controllers::onHyperlinkAction); Label label = new Label(title); label.getStyleClass().add("title"); @@ -46,15 +34,4 @@ public class AnnouncementCard extends VBox { setSpacing(14); getStyleClass().addAll("card", "announcement"); } - - private static void onAction(String href) { - if (href.startsWith("hmcl://")) { - if ("hmcl://settings/feedback".equals(href)) { - Controllers.getSettingsPage().showFeedback(); - Controllers.navigate(Controllers.getSettingsPage()); - } - } else { - FXUtils.openLink(href); - } - } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerManager.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerManager.java index d540f2d4d..7885c784f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerManager.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerManager.java @@ -62,6 +62,7 @@ public final class MultiplayerManager { private static final String CATO_DOWNLOAD_URL = "https://files.huangyuhui.net/maven/"; static final String CATO_VERSION = "1.0.9"; private static final String CATO_PATH = getCatoPath(); + public static final int CATO_AGREEMENT_VERSION = 2; private static final String REMOTE_ADDRESS = "127.0.0.1"; private static final String LOCAL_ADDRESS = "0.0.0.0"; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java index 7a18cf11a..4d7090fac 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java @@ -17,6 +17,8 @@ */ package org.jackhuang.hmcl.ui.multiplayer; +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXDialogLayout; import de.javawi.jstun.test.DiscoveryInfo; import de.javawi.jstun.test.DiscoveryTest; import javafx.application.Platform; @@ -24,7 +26,9 @@ import javafx.beans.property.*; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.control.Control; +import javafx.scene.control.Label; import javafx.scene.control.Skin; +import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.event.Event; import org.jackhuang.hmcl.setting.DownloadProviders; import org.jackhuang.hmcl.task.Schedulers; @@ -41,6 +45,7 @@ import java.util.function.Consumer; import java.util.logging.Level; import static org.jackhuang.hmcl.setting.ConfigHolder.config; +import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig; import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -65,7 +70,7 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware @Override public void onPageShown() { - downloadCatoIfNecessary(); + checkAgreement(() -> this.downloadCatoIfNecessary()); } @Override @@ -134,9 +139,34 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware }).start(); } + private void checkAgreement(Runnable runnable) { + if (config().getMultiplayerAgreementVersion() < MultiplayerManager.CATO_AGREEMENT_VERSION) { + JFXDialogLayout agreementPane = new JFXDialogLayout(); + agreementPane.setHeading(new Label(i18n("launcher.agreement"))); + agreementPane.setBody(new Label(i18n("multiplayer.agreement.prompt"))); + JFXHyperlink agreementLink = new JFXHyperlink(i18n("launcher.agreement")); + agreementLink.setOnAction(e -> FXUtils.openLink("https://noin.cn/agreement")); + JFXButton yesButton = new JFXButton(i18n("launcher.agreement.accept")); + yesButton.getStyleClass().add("dialog-accept"); + yesButton.setOnAction(e -> { + config().setMultiplayerAgreementVersion(MultiplayerManager.CATO_AGREEMENT_VERSION); + runnable.run(); + agreementPane.fireEvent(new DialogCloseEvent()); + }); + JFXButton noButton = new JFXButton(i18n("launcher.agreement.decline")); + noButton.getStyleClass().add("dialog-cancel"); + noButton.setOnAction(e -> { + agreementPane.fireEvent(new DialogCloseEvent()); + fireEvent(new PageCloseEvent()); + }); + agreementPane.setActions(agreementLink, yesButton, noButton); + Controllers.dialog(agreementPane); + } + } + private void downloadCatoIfNecessary() { if (StringUtils.isBlank(MultiplayerManager.getCatoPath())) { - Controllers.dialog(i18n("multiplayer.download."), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR); + Controllers.dialog(i18n("multiplayer.download.failed"), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR); fireEvent(new PageCloseEvent()); return; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPageSkin.java index 59422f594..b2229caa4 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPageSkin.java @@ -33,6 +33,7 @@ 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.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.animation.ContainerAnimations; @@ -249,21 +250,33 @@ public class MultiplayerPageSkin extends SkinBase { ComponentList thanksPane = new ComponentList(); { GridPane gridPane = new GridPane(); - gridPane.getColumnConstraints().setAll(new ColumnConstraints(), FXUtils.getColumnHgrowing()); + gridPane.getColumnConstraints().setAll(new ColumnConstraints(), FXUtils.getColumnHgrowing(), new ColumnConstraints()); gridPane.setVgap(8); gridPane.setHgap(16); JFXTextField tokenField = new JFXTextField(); tokenField.textProperty().bindBidirectional(config().multiplayerTokenProperty()); tokenField.setPromptText(i18n("multiplayer.session.create.token.prompt")); - gridPane.addRow(0, new Label(i18n("multiplayer.session.create.token")), tokenField); - BorderPane pane = new BorderPane(); - Label versionLabel = new Label("cato " + MultiplayerManager.CATO_VERSION); - pane.setLeft(versionLabel); + JFXHyperlink applyLink = new JFXHyperlink(i18n("multiplayer.session.create.token.apply")); + applyLink.setOnAction(e -> FXUtils.openLink("https://noin.cn/circle/386.html")); - Label label = new Label(i18n("multiplayer.powered_by")); - pane.setRight(label); + gridPane.addRow(0, new Label(i18n("multiplayer.session.create.token")), tokenField, applyLink); + + HBox pane = new HBox(); + pane.setAlignment(Pos.CENTER_LEFT); + + JFXHyperlink aboutLink = new JFXHyperlink(i18n("about")); + aboutLink.setOnAction(e -> FXUtils.openLink("https://noin.cn/71.html")); + + HBox placeholder = new HBox(); + HBox.setHgrow(placeholder, Priority.ALWAYS); + + pane.getChildren().setAll( + new Label("cato " + MultiplayerManager.CATO_VERSION), + aboutLink, + placeholder, + FXUtils.segmentToTextFlow(i18n("multiplayer.powered_by"), Controllers::onHyperlinkAction)); thanksPane.getContent().addAll(gridPane, pane); } diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index a06aa8874..d02e022f2 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -593,6 +593,7 @@ mods.not_modded=You should install a modloader first (Fabric, Forge or LiteLoade mods.url=Official Page multiplayer=Multiplayer +multiplayer.agreement.prompt=Before starting using the multiplayer service, you must agree the EULA first. multiplayer.download=Downloading dependencies for multiplayer multiplayer.download.failed=Failed to initialize multiplayer, some files cannot be downloaded multiplayer.download.success=Dependencies initialization succeeded @@ -616,7 +617,7 @@ multiplayer.nat.type.restricted_cone=Medium (Restricted Cone) multiplayer.nat.type.symmetric=Bad (Symmetric) multiplayer.nat.type.symmetric_udp_firewall=Bad (Symmetric with UDP Firewall) multiplayer.nat.type.unknown=Unknown -multiplayer.powered_by=Powered by cato +multiplayer.powered_by=Multiplayer service is provided by noin.cn. EULA multiplayer.report=Report multiplayer.session=Room multiplayer.session.name.format=%1$s's Room @@ -634,6 +635,7 @@ 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.apply=Apple for static 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. diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 084bcbe82..ba5188c03 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -593,6 +593,7 @@ mods.not_modded=你需要先在自動安裝頁面安裝 Fabric、Forge 或 LiteL mods.url=官方頁面 multiplayer=多人聯機 +multiplayer.agreement.prompt=使用多人聯機功能前,你需要先同意多人聯機服務提供方 ioi 系列作品的用戶協議與免責聲明。\n用戶使用聯機服務的行為,將被視為無條件同意該用戶協議與免責聲明。 multiplayer.download=正在下載相依元件 multiplayer.download.failed=初始化失敗,部分文件未能完成下載 multiplayer.download.success=多人聯機初始化完成 @@ -616,7 +617,7 @@ multiplayer.nat.type.restricted_cone=中(受限圓錐型) multiplayer.nat.type.symmetric=差(對稱型) multiplayer.nat.type.symmetric_udp_firewall=差(對稱型+防火牆) multiplayer.nat.type.unknown=未知 -multiplayer.powered_by=由 cato 提供技術支援 +multiplayer.powered_by=多人聯機服務由 這裡 (noin.cn) 提供。用戶協議與免責聲明 multiplayer.report=違法違規檢舉 multiplayer.session=房間 multiplayer.session.name.format=%1$s 的房間 @@ -634,6 +635,7 @@ multiplayer.session.create.name=房間名稱 multiplayer.session.create.port=埠號 multiplayer.session.create.port.error=無法檢測遊戲埠號,你必須先啟動遊戲並在遊戲內打開對區域網路開放選項後才能啟動聯機。 multiplayer.session.create.token=Token +multiplayer.session.create.token.apply=申請靜態 Token multiplayer.session.create.token.prompt=預設為臨時 Token。你可以在 noin.cn 上申請靜態 Token 並填寫至此處 multiplayer.session.expired=聯機會話連續使用時間超過了 3 小時,你需要重新創建/加入房間以繼續聯機。 multiplayer.session.join=加入房間 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 5e3823ec4..0c639d426 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -593,6 +593,7 @@ mods.not_modded=你需要先在自动安装页面安装 Fabric、Forge 或 LiteL mods.url=官方页面 multiplayer=多人联机 +multiplayer.agreement.prompt=使用多人联机功能前,你需要先同意多人联机服务提供方 ioi 系列作品的用户协议与免责声明。\n用户使用联机服务的行为,将被视为无条件同意该用户协议与免责声明。 multiplayer.download=正在下载依赖 multiplayer.download.failed=初始化失败,部分文件未能完成下载 multiplayer.download.success=多人联机初始化完成 @@ -616,7 +617,7 @@ multiplayer.nat.type.restricted_cone=中(受限圆锥型) multiplayer.nat.type.symmetric=差(对称型) multiplayer.nat.type.symmetric_udp_firewall=差(对称型+防火墙) multiplayer.nat.type.unknown=未知 -multiplayer.powered_by=多人联机服务由 这里 (noin.cn) 提供 +multiplayer.powered_by=多人联机服务由 这里 (noin.cn) 提供。用户协议与免责声明 multiplayer.report=违法违规举报 multiplayer.session=房间 multiplayer.session.name.format=%1$s 的房间 @@ -634,6 +635,7 @@ multiplayer.session.create.name=房间名称 multiplayer.session.create.port=端口号 multiplayer.session.create.port.error=无法检测游戏端口号,你必须先启动游戏并在游戏内打开对局域网开放选项后才能启动联机。 multiplayer.session.create.token=Token +multiplayer.session.create.token.apply=申请静态 Token multiplayer.session.create.token.prompt=默认为临时 Token。你可以在 noin.cn 上申请静态 Token 并填写至此处 multiplayer.session.expired=联机会话连续使用时间超过了 3 小时,你需要重新创建/加入房间以继续联机。 multiplayer.session.join=加入房间