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 13d0dc383..69da6c29a 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 @@ -38,7 +38,9 @@ import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.util.HMCLService; +import org.jackhuang.hmcl.util.Result; import org.jackhuang.hmcl.util.StringUtils; +import org.jetbrains.annotations.Nullable; import java.util.concurrent.CancellationException; import java.util.concurrent.atomic.AtomicBoolean; @@ -58,7 +60,7 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP private final ObjectProperty multiplayerState = new SimpleObjectProperty<>(MultiplayerManager.State.DISCONNECTED); private final ReadOnlyStringWrapper token = new ReadOnlyStringWrapper(); - private final ReadOnlyObjectWrapper natState = new ReadOnlyObjectWrapper<>(); + private final ReadOnlyObjectWrapper<@Nullable Result> natState = new ReadOnlyObjectWrapper<>(); private final ReadOnlyIntegerWrapper gamePort = new ReadOnlyIntegerWrapper(-1); private final ReadOnlyObjectWrapper session = new ReadOnlyObjectWrapper<>(); private final ObservableList clients = FXCollections.observableArrayList(); @@ -97,11 +99,11 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP this.multiplayerState.set(multiplayerState); } - public DiscoveryInfo getNatState() { + public Result getNatState() { return natState.get(); } - public ReadOnlyObjectProperty natStateProperty() { + public ReadOnlyObjectProperty> natStateProperty() { return natState.getReadOnlyProperty(); } @@ -134,12 +136,12 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP DiscoveryTest tester = new DiscoveryTest(null, 0, "stun.stunprotocol.org", 3478); return tester.test(); }).whenComplete(Schedulers.javafx(), (info, exception) -> { - LOG.log(Level.INFO, "Nat test result " + MultiplayerPageSkin.getNATType(info), exception); if (exception == null) { - natState.set(info); + natState.set(Result.ok(info)); } else { - natState.set(null); + natState.set(Result.error()); } + LOG.log(Level.INFO, "Nat test result " + MultiplayerPageSkin.getNATType(natState.get()), exception); }).start(); } @@ -506,4 +508,5 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP public ReadOnlyObjectProperty stateProperty() { return state; } + } 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 8ade64b65..38a58f79b 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 @@ -41,8 +41,10 @@ import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage; import org.jackhuang.hmcl.ui.versions.Versions; import org.jackhuang.hmcl.util.HMCLService; +import org.jackhuang.hmcl.util.Result; import org.jackhuang.hmcl.util.javafx.BindingMapping; import org.jackhuang.hmcl.util.javafx.MappedObservableList; +import org.jetbrains.annotations.Nullable; import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig; import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap; @@ -295,22 +297,24 @@ public class MultiplayerPageSkin extends DecoratorAnimatedPage.DecoratorAnimated } } - public static String getNATType(DiscoveryInfo info) { + public static String getNATType(@Nullable Result info) { if (info == null) { return i18n("multiplayer.nat.testing"); - } else if (info.isBlockedUDP()) { + } else if (info.isError()) { + return i18n("multiplayer.nat.failed"); + } else if (info.get().isBlockedUDP()) { return i18n("multiplayer.nat.type.blocked_udp"); - } else if (info.isFullCone()) { + } else if (info.get().isFullCone()) { return i18n("multiplayer.nat.type.full_cone"); - } else if (info.isOpenAccess()) { + } else if (info.get().isOpenAccess()) { return i18n("multiplayer.nat.type.open_access"); - } else if (info.isPortRestrictedCone()) { + } else if (info.get().isPortRestrictedCone()) { return i18n("multiplayer.nat.type.port_restricted_cone"); - } else if (info.isRestrictedCone()) { + } else if (info.get().isRestrictedCone()) { return i18n("multiplayer.nat.type.restricted_cone"); - } else if (info.isSymmetric()) { + } else if (info.get().isSymmetric()) { return i18n("multiplayer.nat.type.symmetric"); - } else if (info.isSymmetricUDPFirewall()) { + } else if (info.get().isSymmetricUDPFirewall()) { return i18n("multiplayer.nat.type.symmetric_udp_firewall"); } else { return i18n("multiplayer.nat.type.unknown"); diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index c34c5d183..228d97214 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -654,6 +654,7 @@ multiplayer.exit.timeout.static_token=Failed to connect to multiplayer server. P multiplayer.exit.timeout.dynamic_token=Failed to connect to multiplayer server. multiplayer.hint=Multiplayer functionality is experimental. Please give feedback. multiplayer.nat=Network Type Detection +multiplayer.nat.failed=Failed to detect network type. multiplayer.nat.hint=Network type detection will make it clear whether your network fulfills our requirement for multiplayer mode. multiplayer.nat.latency=Latency multiplayer.nat.not_yet_tested=Not yet testsed diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index ae2f50474..d5cfcab9c 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -654,6 +654,7 @@ multiplayer.exit.timeout.static_token=無法連接多人聯機服務,請你切 multiplayer.exit.timeout.dynamic_token=無法連接多人聯機服務,你可以在多人聯機頁面的回饋中回饋問題。 multiplayer.hint=多人聯機功能處於實驗階段,如果有問題請回饋。 multiplayer.nat=網路檢測 +multiplayer.nat.failed=檢測失敗,但你仍然可以繼續使用聯機功能。 multiplayer.nat.hint=執行網路檢測可以讓你更清楚你的網路狀況是否符合聯機功能的需求。不符合聯機功能運行條件的網路狀況將可能導致聯機失敗。 multiplayer.nat.latency=延遲 multiplayer.nat.not_yet_tested=尚未檢測 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 35f942fb3..54b4a6d5b 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -654,6 +654,7 @@ multiplayer.exit.timeout.static_token=无法连接多人联机服务,请你切 multiplayer.exit.timeout.dynamic_token=无法连接多人联机服务,你可以在多人联机页面的反馈中反馈问题。 multiplayer.hint=多人联机功能处于实验阶段,如果有问题请反馈。 multiplayer.nat=网络检测 +multiplayer.nat.failed=检测失败,但你仍然可以继续使用联机功能。 multiplayer.nat.hint=执行网络检测可以让你更清楚你的网络状况是否符合联机功能的需求。检测结果为差的网络可能导致联机失败。 multiplayer.nat.latency=延迟 multiplayer.nat.not_yet_tested=尚未检测 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Result.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Result.java new file mode 100644 index 000000000..c8574c9dc --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Result.java @@ -0,0 +1,67 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.util; + +import java.util.Objects; + +public abstract class Result { + + public T get() { + throw new IllegalStateException("TriState not ok"); + } + + public boolean isOK() { + return false; + } + + public boolean isError() { + return false; + } + + public static Result ok(T result) { + return new OK<>(Objects.requireNonNull(result)); + } + + @SuppressWarnings("unchecked") + public static Result error() { + return (Result) Error.INSTANCE; + } + + private static class OK extends Result { + private final T result; + + public OK(T result) { + this.result = result; + } + + @Override + public T get() { + return result; + } + } + + private static class Error extends Result { + public static final Error INSTANCE = new Error<>(); + + @Override + public boolean isError() { + return true; + } + } + +} \ No newline at end of file