mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2024-11-21 03:10:58 +08:00
feat: WIP: multiplayer
This commit is contained in:
parent
c844a77f02
commit
d521d10a85
@ -44,6 +44,7 @@ mainClassName = 'org.jackhuang.hmcl.Main'
|
||||
|
||||
dependencies {
|
||||
implementation project(":HMCLCore")
|
||||
implementation project(":JSTUN")
|
||||
implementation rootProject.files("lib/JFoenix.jar")
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,7 @@ import org.jackhuang.hmcl.ui.download.DownloadPage;
|
||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.main.LauncherSettingsPage;
|
||||
import org.jackhuang.hmcl.ui.main.RootPage;
|
||||
import org.jackhuang.hmcl.ui.multiplayer.MultiplayerPage;
|
||||
import org.jackhuang.hmcl.ui.versions.GameListPage;
|
||||
import org.jackhuang.hmcl.ui.versions.ModDownloadListPage;
|
||||
import org.jackhuang.hmcl.ui.versions.VersionPage;
|
||||
@ -101,6 +102,7 @@ public final class Controllers {
|
||||
accountListPage.accountsProperty().bindContent(Accounts.accountsProperty());
|
||||
return accountListPage;
|
||||
});
|
||||
private static Lazy<MultiplayerPage> multiplayerPage = new Lazy<>(MultiplayerPage::new);
|
||||
private static Lazy<LauncherSettingsPage> settingsPage = new Lazy<>(LauncherSettingsPage::new);
|
||||
|
||||
private Controllers() {
|
||||
@ -141,6 +143,11 @@ public final class Controllers {
|
||||
return modDownloadListPage.get();
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static MultiplayerPage getMultiplayerPage() {
|
||||
return multiplayerPage.get();
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static LauncherSettingsPage getSettingsPage() {
|
||||
return settingsPage.get();
|
||||
|
@ -391,6 +391,12 @@ public final class SVG {
|
||||
fill, width, height);
|
||||
}
|
||||
|
||||
public static Node accountArrowRightOutline(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath(
|
||||
"M19,21V19H15V17H19V15L22,18L19,21M13,18C13,18.71 13.15,19.39 13.42,20H2V17C2,14.79 5.58,13 10,13C11,13 11.96,13.09 12.85,13.26C13.68,13.42 14.44,13.64 15.11,13.92C13.83,14.83 13,16.32 13,18M4,17V18H11C11,16.96 11.23,15.97 11.64,15.08L10,15C6.69,15 4,15.9 4,17M10,4A4,4 0 0,1 14,8A4,4 0 0,1 10,12A4,4 0 0,1 6,8A4,4 0 0,1 10,4M10,6A2,2 0 0,0 8,8A2,2 0 0,0 10,10A2,2 0 0,0 12,8A2,2 0 0,0 10,6Z",
|
||||
fill, width, height);
|
||||
}
|
||||
|
||||
public static Node styleOutline(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath(
|
||||
"M2.5 19.6L3.8 20.2V11.2L1.4 17C1 18.1 1.5 19.2 2.5 19.6M15.2 4.8L20.2 16.8L12.9 19.8L7.9 7.9V7.8L15.2 4.8M15.3 2.8C15 2.8 14.8 2.8 14.5 2.9L7.1 6C6.4 6.3 5.9 7 5.9 7.8C5.9 8 5.9 8.3 6 8.6L11 20.5C11.3 21.3 12 21.7 12.8 21.7C13.1 21.7 13.3 21.7 13.6 21.6L21 18.5C22 18.1 22.5 16.9 22.1 15.9L17.1 4C16.8 3.2 16 2.8 15.3 2.8M10.5 9.9C9.9 9.9 9.5 9.5 9.5 8.9S9.9 7.9 10.5 7.9C11.1 7.9 11.5 8.4 11.5 8.9S11.1 9.9 10.5 9.9M5.9 19.8C5.9 20.9 6.8 21.8 7.9 21.8H9.3L5.9 13.5V19.8Z",
|
||||
|
@ -107,6 +107,7 @@ public abstract class ToolbarListPageSkin<T extends ListPageBase<? extends Node>
|
||||
ret.setOnMouseClicked(e -> onClick.run());
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static JFXButton createDecoratorButton(String tooltip, SVG.SVGIcon creator, Runnable onClick) {
|
||||
JFXButton ret = new JFXButton();
|
||||
ret.getStyleClass().add("jfx-decorator-button");
|
||||
|
@ -176,6 +176,14 @@ public class RootPage extends DecoratorTabPage {
|
||||
downloadItem.setOnAction(e -> Controllers.navigate(Controllers.getDownloadPage()));
|
||||
|
||||
// fifth item in left sidebar
|
||||
AdvancedListItem multiplayerItem = new AdvancedListItem();
|
||||
multiplayerItem
|
||||
.setLeftGraphic(AdvancedListItem.createImageView(newImage("/assets/img/command.png")).getKey());
|
||||
multiplayerItem.setActionButtonVisible(false);
|
||||
multiplayerItem.setTitle(i18n("multiplayer"));
|
||||
multiplayerItem.setOnAction(e -> Controllers.navigate(Controllers.getMultiplayerPage()));
|
||||
|
||||
// sixth item in left sidebar
|
||||
AdvancedListItem launcherSettingsItem = new AdvancedListItem();
|
||||
launcherSettingsItem
|
||||
.setLeftGraphic(AdvancedListItem.createImageView(newImage("/assets/img/command.png")).getKey());
|
||||
@ -191,6 +199,8 @@ public class RootPage extends DecoratorTabPage {
|
||||
.add(gameListItem)
|
||||
.add(gameItem)
|
||||
.add(downloadItem)
|
||||
.startCategory(i18n("settings.launcher.general").toLowerCase())
|
||||
// .add(multiplayerItem)
|
||||
.add(launcherSettingsItem);
|
||||
|
||||
// the root page, with the sidebar in left, navigator in center.
|
||||
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.multiplayer;
|
||||
|
||||
public class MultiplayerManager {
|
||||
|
||||
enum State {
|
||||
DISCONNECTED,
|
||||
MASTER,
|
||||
SLAVE
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.multiplayer;
|
||||
|
||||
import de.javawi.jstun.test.DiscoveryInfo;
|
||||
import de.javawi.jstun.test.DiscoveryTest;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class MultiplayerPage extends Control implements DecoratorPage {
|
||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("multiplayer"), -1));
|
||||
|
||||
private final ObjectProperty<MultiplayerManager.State> multiplayerState = new SimpleObjectProperty<>();
|
||||
private final ReadOnlyObjectWrapper<DiscoveryInfo> natState = new ReadOnlyObjectWrapper<>();
|
||||
|
||||
public MultiplayerPage() {
|
||||
testNAT();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new MultiplayerPageSkin(this);
|
||||
}
|
||||
|
||||
public MultiplayerManager.State getMultiplayerState() {
|
||||
return multiplayerState.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<MultiplayerManager.State> multiplayerStateProperty() {
|
||||
return multiplayerState;
|
||||
}
|
||||
|
||||
public void setMultiplayerState(MultiplayerManager.State multiplayerState) {
|
||||
this.multiplayerState.set(multiplayerState);
|
||||
}
|
||||
|
||||
public DiscoveryInfo getNatState() {
|
||||
return natState.get();
|
||||
}
|
||||
|
||||
public ReadOnlyObjectProperty<DiscoveryInfo> natStateProperty() {
|
||||
return natState.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public void testNAT() {
|
||||
Task.supplyAsync(() -> {
|
||||
DiscoveryTest tester = new DiscoveryTest(null, 0, "stun.qq.com", 3478);
|
||||
return tester.test();
|
||||
}).whenComplete(Schedulers.javafx(), (info, exception) -> {
|
||||
if (exception == null) {
|
||||
natState.set(info);
|
||||
} else {
|
||||
natState.set(null);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state;
|
||||
}
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.multiplayer;
|
||||
|
||||
import de.javawi.jstun.test.DiscoveryInfo;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.*;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class MultiplayerPageSkin extends SkinBase<MultiplayerPage> {
|
||||
|
||||
/**
|
||||
* Constructor for all SkinBase instances.
|
||||
*
|
||||
* @param control The control for which this Skin should attach to.
|
||||
*/
|
||||
protected MultiplayerPageSkin(MultiplayerPage control) {
|
||||
super(control);
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.setPadding(new Insets(10));
|
||||
getChildren().setAll(root);
|
||||
{
|
||||
VBox roomPane = new VBox();
|
||||
{
|
||||
AdvancedListItem createRoomItem = new AdvancedListItem();
|
||||
createRoomItem.setTitle(i18n("multiplayer.room.create"));
|
||||
createRoomItem.setLeftGraphic(wrap(SVG.plusCircleOutline(null, 24, 24)));
|
||||
createRoomItem.setOnAction(e -> FXUtils.openLink(""));
|
||||
|
||||
AdvancedListItem joinRoomItem = new AdvancedListItem();
|
||||
joinRoomItem.setTitle(i18n("multiplayer.room.join"));
|
||||
joinRoomItem.setLeftGraphic(wrap(SVG.accountArrowRightOutline(null, 24, 24)));
|
||||
joinRoomItem.setOnAction(e -> FXUtils.openLink(""));
|
||||
|
||||
AdvancedListItem copyLinkItem = new AdvancedListItem();
|
||||
copyLinkItem.setTitle(i18n("multiplayer.room.copy_room_code"));
|
||||
copyLinkItem.setLeftGraphic(wrap(SVG.accountArrowRightOutline(null, 24, 24)));
|
||||
copyLinkItem.setOnAction(e -> FXUtils.openLink(""));
|
||||
|
||||
AdvancedListItem quitItem = new AdvancedListItem();
|
||||
quitItem.setTitle(i18n("multiplayer.room.quit"));
|
||||
quitItem.setLeftGraphic(wrap(SVG.closeCircle(null, 24, 24)));
|
||||
quitItem.setOnAction(e -> FXUtils.openLink(""));
|
||||
|
||||
AdvancedListItem closeRoomItem = new AdvancedListItem();
|
||||
closeRoomItem.setTitle(i18n("multiplayer.room.quit"));
|
||||
closeRoomItem.setLeftGraphic(wrap(SVG.closeCircle(null, 24, 24)));
|
||||
closeRoomItem.setOnAction(e -> FXUtils.openLink(""));
|
||||
|
||||
FXUtils.onChangeAndOperate(getSkinnable().multiplayerStateProperty(), state -> {
|
||||
if (state == MultiplayerManager.State.DISCONNECTED) {
|
||||
roomPane.getChildren().setAll(createRoomItem, joinRoomItem);
|
||||
} else if (state == MultiplayerManager.State.MASTER) {
|
||||
roomPane.getChildren().setAll(copyLinkItem);
|
||||
roomPane.getChildren().setAll(closeRoomItem);
|
||||
} else if (state == MultiplayerManager.State.SLAVE) {
|
||||
roomPane.getChildren().setAll(copyLinkItem);
|
||||
roomPane.getChildren().setAll(quitItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.startCategory("multiplayer.room")
|
||||
.add(roomPane)
|
||||
.startCategory("help")
|
||||
.addNavigationDrawerItem(settingsItem -> {
|
||||
settingsItem.setTitle(i18n("help"));
|
||||
settingsItem.setLeftGraphic(wrap(SVG.gamepad(null, 20, 20)));
|
||||
settingsItem.setOnAction(e -> FXUtils.openLink(""));
|
||||
});
|
||||
FXUtils.setLimitWidth(sideBar, 200);
|
||||
root.setLeft(sideBar);
|
||||
}
|
||||
|
||||
{
|
||||
VBox content = new VBox(16);
|
||||
content.setFillWidth(true);
|
||||
ScrollPane scrollPane = new ScrollPane(content);
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.setFitToHeight(true);
|
||||
root.setCenter(scrollPane);
|
||||
|
||||
ComponentList roomPane = new ComponentList();
|
||||
{
|
||||
VBox pane = new VBox();
|
||||
|
||||
}
|
||||
|
||||
ComponentList natDetectionPane = new ComponentList();
|
||||
{
|
||||
GridPane pane = new GridPane();
|
||||
ColumnConstraints title = new ColumnConstraints();
|
||||
ColumnConstraints value = new ColumnConstraints();
|
||||
pane.getColumnConstraints().setAll(title, value);
|
||||
value.setFillWidth(true);
|
||||
value.setHgrow(Priority.ALWAYS);
|
||||
pane.setHgap(16);
|
||||
pane.setVgap(8);
|
||||
|
||||
HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFORMATION);
|
||||
hintPane.setText(i18n("multiplayer.nat.hint"));
|
||||
GridPane.setColumnSpan(hintPane, 2);
|
||||
pane.addRow(0, hintPane);
|
||||
|
||||
Label natResult = new Label();
|
||||
natResult.textProperty().bind(BindingMapping.of(getSkinnable().natStateProperty())
|
||||
.map(MultiplayerPageSkin::getNATType));
|
||||
pane.addRow(1, new Label(i18n("multiplayer.nat.type")), natResult);
|
||||
|
||||
// Label natResult = new Label();
|
||||
// natResult.textProperty().bind(BindingMapping.of(getSkinnable().natStateProperty())
|
||||
// .map(MultiplayerPageSkin::getNATType));
|
||||
// pane.addRow(1, new Label(i18n("multiplayer.nat.latency")), natResult);
|
||||
|
||||
natDetectionPane.getContent().add(pane);
|
||||
}
|
||||
|
||||
content.getChildren().setAll(
|
||||
ComponentList.createComponentListTitle(i18n("multiplayer.room")),
|
||||
roomPane,
|
||||
ComponentList.createComponentListTitle(i18n("multiplayer.nat")),
|
||||
natDetectionPane
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getNATType(DiscoveryInfo info) {
|
||||
if (info == null) {
|
||||
return i18n("multiplayer.nat.testing");
|
||||
} else if (info.isBlockedUDP()) {
|
||||
return i18n("multiplayer.nat.type.blocked_udp");
|
||||
} else if (info.isFullCone()) {
|
||||
return i18n("multiplayer.nat.type.full_cone");
|
||||
} else if (info.isOpenAccess()) {
|
||||
return i18n("multiplayer.nat.type.open_access");
|
||||
} else if (info.isPortRestrictedCone()) {
|
||||
return i18n("multiplayer.nat.type.port_restricted_cone");
|
||||
} else if (info.isRestrictedCone()) {
|
||||
return i18n("multiplayer.nat.type.restricted_cone");
|
||||
} else if (info.isSymmetric()) {
|
||||
return i18n("multiplayer.nat.type.symmetric");
|
||||
} else if (info.isSymmetricUDPFirewall()) {
|
||||
return i18n("multiplayer.nat.type.symmetric_udp_firewall");
|
||||
} else {
|
||||
return i18n("multiplayer.nat.type.unknown");
|
||||
}
|
||||
}
|
||||
}
|
@ -450,6 +450,35 @@ mods.name=Name
|
||||
mods.not_modded=You should install a modloader first (Fabric, Forge or LiteLoader)
|
||||
mods.url=Official Page
|
||||
|
||||
multiplayer=Multiplayer
|
||||
multiplayer.nat=Network Type Detection
|
||||
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
|
||||
multiplayer.nat.packet_loss_ratio=Packet Loss Ratio
|
||||
multiplayer.nat.testing=Testing
|
||||
multiplayer.nat.type=NAT Type
|
||||
multiplayer.nat.type.blocked_udp=Very bad (Blocked UDP)
|
||||
multiplayer.nat.type.full_cone=Medium (Full Cone)
|
||||
multiplayer.nat.type.open_access=Good (Open Access)
|
||||
multiplayer.nat.type.port_restricted_cone=Medium (Port Restricted Cone)
|
||||
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=Unknownn
|
||||
multiplayer.room=Room
|
||||
multiplayer.room.name.format=%1$s's Room
|
||||
multiplayer.room.close=Close Room
|
||||
multiplayer.room.copy_room_code=Copy Invitation Code
|
||||
multiplayer.room.create=Create Room
|
||||
multiplayer.room.error.port=Cannot detect game port, you must click "Open LAN Server" in game to enable multiplayer functionality.
|
||||
multiplayer.room.hint=You must click "Open LAN Server" in game in order to enable multiplayer functionality.
|
||||
multiplayer.room.join=Join Room
|
||||
multiplayer.room.members=Room Members
|
||||
multiplayer.room.port.prompt=Enter game port
|
||||
multiplayer.room.quit=Quit Room
|
||||
multiplayer.room.username=Username
|
||||
|
||||
datapack=Datapacks
|
||||
datapack.add=Install datapack
|
||||
datapack.choose_datapack=Choose the datapack zip to import
|
||||
|
@ -426,6 +426,35 @@ mods.mangage=模組管理
|
||||
mods.name=名稱
|
||||
mods.not_modded=你需要先在自動安裝頁面安裝 Fabric、Forge 或 LiteLoader 才能進行模組管理。
|
||||
|
||||
multiplayer=聯機
|
||||
multiplayer.nat=網路檢測
|
||||
multiplayer.nat.hint=執行網路檢測可以讓你更清楚裡的網路狀況是否符合聯機功能的需求。不符合聯機功能運行條件的網路狀況將可能導致聯機失敗。
|
||||
multiplayer.nat.latency=延遲
|
||||
multiplayer.nat.not_yet_tested=尚未檢測
|
||||
multiplayer.nat.packet_loss_ratio=丟包率
|
||||
multiplayer.nat.testing=檢測中
|
||||
multiplayer.nat.type=NAT 類型
|
||||
multiplayer.nat.type.blocked_udp=極差(網路禁止 UDP 協議)
|
||||
multiplayer.nat.type.full_cone=中(完全圓錐型)
|
||||
multiplayer.nat.type.open_access=好(公網開放型)
|
||||
multiplayer.nat.type.port_restricted_cone=中(埠受限圓錐型)
|
||||
multiplayer.nat.type.restricted_cone=中(受限圓錐型)
|
||||
multiplayer.nat.type.symmetric=差(對稱型)
|
||||
multiplayer.nat.type.symmetric_udp_firewall=差(對稱型+防火牆)
|
||||
multiplayer.nat.type.unknown=未知
|
||||
multiplayer.room=房間
|
||||
multiplayer.room.name.format=%1$s 的房間
|
||||
multiplayer.room.close=關閉房間
|
||||
multiplayer.room.copy_room_code=複製邀請碼
|
||||
multiplayer.room.create=創建房間
|
||||
multiplayer.room.error.port=無法檢測遊戲埠號,你必須先啟動遊戲並在遊戲內打開對區域網路開放選項後才能啟動聯機。
|
||||
multiplayer.room.hint=你必須先啟動遊戲並在遊戲內打開對區域網路開放選項後才能創建房間
|
||||
multiplayer.room.join=加入房間
|
||||
multiplayer.room.members=房間成員
|
||||
multiplayer.room.port.prompt=輸入埠號
|
||||
multiplayer.room.quit=退出房間
|
||||
multiplayer.room.username=使用者名稱
|
||||
|
||||
datapack=資料包
|
||||
datapack.add=加入資料包
|
||||
datapack.choose_datapack=選擇要匯入的資料包壓縮檔
|
||||
|
@ -469,7 +469,26 @@ multiplayer.nat.not_yet_tested=尚未检测
|
||||
multiplayer.nat.packet_loss_ratio=丢包率
|
||||
multiplayer.nat.testing=检测中
|
||||
multiplayer.nat.type=NAT 类型
|
||||
multiplayer.nat.type.blocked_udp=极差(网络禁止 UDP 协议)
|
||||
multiplayer.nat.type.full_cone=中(完全圆锥型)
|
||||
multiplayer.nat.type.open_access=好(公网开放型)
|
||||
multiplayer.nat.type.port_restricted_cone=中(端口受限圆锥型)
|
||||
multiplayer.nat.type.restricted_cone=中(受限圆锥型)
|
||||
multiplayer.nat.type.symmetric=差(对称型)
|
||||
multiplayer.nat.type.symmetric_udp_firewall=差(对称型+防火墙)
|
||||
multiplayer.nat.type.unknown=未知
|
||||
multiplayer.room=房间
|
||||
multiplayer.room.name.format=%1$s 的房间
|
||||
multiplayer.room.close=关闭房间
|
||||
multiplayer.room.copy_room_code=复制邀请码
|
||||
multiplayer.room.create=创建房间
|
||||
multiplayer.room.error.port=无法检测游戏端口号,你必须先启动游戏并在游戏内打开对局域网开放选项后才能启动联机。
|
||||
multiplayer.room.hint=你必须先启动游戏并在游戏内打开对局域网开放选项后才能创建房间
|
||||
multiplayer.room.join=加入房间
|
||||
multiplayer.room.members=房间成员
|
||||
multiplayer.room.port.prompt=输入端口号
|
||||
multiplayer.room.quit=退出房间
|
||||
multiplayer.room.username=用户名
|
||||
|
||||
datapack=数据包
|
||||
datapack.add=添加数据包
|
||||
|
@ -28,6 +28,10 @@ subprojects {
|
||||
sourceSets = []
|
||||
}
|
||||
|
||||
tasks.withType(Checkstyle) {
|
||||
exclude 'de/javawi/jstun'
|
||||
}
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
compileJava.options.encoding = "UTF-8"
|
||||
compileTestJava.options.encoding = "UTF-8"
|
||||
|
@ -2,4 +2,4 @@ rootProject.name = 'HMCL3'
|
||||
include ':HMCL'
|
||||
include ':HMCLCore'
|
||||
include ':HMCLTransformerDiscoveryService'
|
||||
|
||||
include ':JSTUN'
|
||||
|
Loading…
Reference in New Issue
Block a user