feat(multiplayer): cato EULA.

This commit is contained in:
huanghongxun 2021-09-30 15:01:56 +08:00
parent e01c31fb1d
commit e69ef0ce25
10 changed files with 117 additions and 59 deletions

View File

@ -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();
}

View File

@ -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;
}

View File

@ -700,35 +700,40 @@ public final class FXUtils {
Controllers.showToast(i18n("message.copied"));
}
public static TextFlow segmentToTextFlow(final String segment, Consumer<String> hyperlinkAction) throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new InputSource(new StringReader("<body>" + segment + "</body>")));
Element r = doc.getDocumentElement();
public static TextFlow segmentToTextFlow(final String segment, Consumer<String> hyperlinkAction) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new InputSource(new StringReader("<body>" + segment + "</body>")));
Element r = doc.getDocumentElement();
NodeList children = r.getChildNodes();
List<javafx.scene.Node> texts = new ArrayList<>();
for (int i = 0; i < children.getLength(); i++) {
org.w3c.dom.Node node = children.item(i);
NodeList children = r.getChildNodes();
List<javafx.scene.Node> 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;
}
}

View File

@ -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);
}
}
}

View File

@ -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";

View File

@ -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;
}

View File

@ -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<MultiplayerPage> {
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);
}

View File

@ -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 <a href="https://noin.cn">noin.cn</a>. <a href="https://noin.cn/agreement">EULA</a>
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.

View File

@ -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=多人聯機服務由 這裡 (<a href="https://noin.cn">noin.cn</a>) 提供。<a href="https://noin.cn/agreement">用戶協議與免責聲明</a>
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=加入房間

View File

@ -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=多人联机服务由 这里 (<a href="https://noin.cn">noin.cn</a>) 提供。<a href="https://noin.cn/agreement">用户协议与免责声明</a>
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=加入房间