From c54a3f2053f39efca9d5ed70dade77d723f799d2 Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Sun, 19 Sep 2021 01:31:00 +0800 Subject: [PATCH] feat(multiplayer): broadcast lan server. --- .../multiplayer/LocalServerBroadcaster.java | 70 +++++++++++++++++++ .../ui/multiplayer/MultiplayerManager.java | 4 +- .../hmcl/ui/multiplayer/MultiplayerPage.java | 1 + .../ui/multiplayer/MultiplayerPageSkin.java | 20 ++++-- .../resources/assets/lang/I18N.properties | 5 +- .../resources/assets/lang/I18N_zh.properties | 5 +- .../assets/lang/I18N_zh_CN.properties | 5 +- .../hmcl/util/platform/ManagedProcess.java | 4 +- 8 files changed, 102 insertions(+), 12 deletions(-) create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/LocalServerBroadcaster.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/LocalServerBroadcaster.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/LocalServerBroadcaster.java new file mode 100644 index 000000000..8a7c2bd72 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/LocalServerBroadcaster.java @@ -0,0 +1,70 @@ +/* + * 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.ui.multiplayer; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.SocketException; +import java.nio.charset.StandardCharsets; +import java.util.logging.Level; + +import static org.jackhuang.hmcl.util.Logging.LOG; +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public class LocalServerBroadcaster implements Runnable { + private final int port; + private final MultiplayerManager.CatoSession session; + + public LocalServerBroadcaster(int port, MultiplayerManager.CatoSession session) { + this.port = port; + this.session = session; + } + + public int getPort() { + return port; + } + + @Override + public void run() { + DatagramSocket socket; + try { + socket = new DatagramSocket(); + } catch (SocketException e) { + LOG.log(Level.WARNING, "Failed to create datagram socket", e); + return; + } + + while (session.isRunning()) { + try { + byte[] data = String.format("[MOTD]%s[/MOTD][AD]%d[/AD]", i18n("multiplayer.session.name.motd", session.getName()), port).getBytes(StandardCharsets.UTF_8); + DatagramPacket packet = new DatagramPacket(data, 0, data.length, InetAddress.getByName("224.0.2.60"), 4445); + socket.send(packet); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to send motd packet", e); + } + + try { + Thread.sleep(1500); + } catch (InterruptedException ignored) { + return; + } + } + } +} 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 f81c07c9b..c7df025a0 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 @@ -86,7 +86,9 @@ public final class MultiplayerManager { .command(commands) .start(); - return new CatoSession(sessionName, process, Arrays.asList(commands)); + CatoSession session = new CatoSession(sessionName, process, Arrays.asList(commands)); + session.addRelatedThread(Lang.thread(new LocalServerBroadcaster(localPort, session), "LocalServerBroadcaster", true)); + return session; } public static CatoSession createSession(String sessionName) throws IOException { 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 1cdaa367e..b1c59605d 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 @@ -196,6 +196,7 @@ public class MultiplayerPage extends Control implements DecoratorPage { return; } + port.set(localPort); setMultiplayerState(MultiplayerManager.State.SLAVE); resolve.run(); }) 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 5c8ca6976..4c242215c 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 @@ -80,7 +80,7 @@ public class MultiplayerPageSkin extends SkinBase { } else if (state == MultiplayerManager.State.MASTER) { roomPane.getChildren().setAll(copyLinkItem, closeRoomItem); } else if (state == MultiplayerManager.State.SLAVE) { - roomPane.getChildren().setAll(copyLinkItem, quitItem); + roomPane.getChildren().setAll(quitItem); } }); } @@ -107,9 +107,13 @@ public class MultiplayerPageSkin extends SkinBase { scrollPane.setFitToHeight(true); root.setCenter(scrollPane); + HintPane hint = new HintPane(MessageDialogPane.MessageType.INFORMATION); + hint.setText(i18n("multiplayer.hint")); + ComponentList roomPane = new ComponentList(); { TransitionPane transitionPane = new TransitionPane(); + roomPane.getContent().setAll(transitionPane); VBox disconnectedPane = new VBox(8); { @@ -121,7 +125,7 @@ public class MultiplayerPageSkin extends SkinBase { disconnectedPane.getChildren().setAll(hintPane, label); } - VBox masterPane = new VBox(); + VBox masterPane = new VBox(8); { Label label = new Label(i18n("multiplayer.state.master")); label.textProperty().bind(Bindings.createStringBinding(() -> @@ -130,13 +134,16 @@ public class MultiplayerPageSkin extends SkinBase { masterPane.getChildren().setAll(label); } - StackPane slavePane = new StackPane(); + VBox slavePane = new VBox(8); { + HintPane slaveHintPane = new HintPane(); + slaveHintPane.setText(i18n("multiplayer.state.slave.hint")); + Label label = new Label(); label.textProperty().bind(Bindings.createStringBinding(() -> - i18n("multiplayer.state.slave", control.getSession() == null ? "" : control.getSession().getName()), - control.sessionProperty())); - slavePane.getChildren().setAll(label); + i18n("multiplayer.state.slave", control.getSession() == null ? "" : control.getSession().getName(), control.getPort()), + control.sessionProperty(), control.portProperty())); + slavePane.getChildren().setAll(slaveHintPane, label); } FXUtils.onChangeAndOperate(getSkinnable().multiplayerStateProperty(), state -> { @@ -180,6 +187,7 @@ public class MultiplayerPageSkin extends SkinBase { } content.getChildren().setAll( + hint, ComponentList.createComponentListTitle(i18n("multiplayer.session")), roomPane, ComponentList.createComponentListTitle(i18n("multiplayer.nat")), diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 9e07ef53e..cd72fe0e9 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -560,6 +560,7 @@ multiplayer.nat.type.unknown=Unknown multiplayer.powered_by=Powered by cato multiplayer.session=Room multiplayer.session.name.format=%1$s's Room +multiplayer.session.name.motd=HMCL Multiplayer Session - %s multiplayer.session.close=Close Room multiplayer.session.close.warning=After closing room, all players joined the room will lost connection. Continue? multiplayer.session.copy_room_code=Copy Invitation Code @@ -572,6 +573,7 @@ multiplayer.session.create.port.error=Cannot detect game port, you must click "O 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 +multiplayer.session.join.hint=You must obtain the invitation code from the gamer who has already created a multiplayer room. multiplayer.session.join.invitation_code=Invitation code multiplayer.session.join.invitation_code.error=Incorrect invitation code. Please obtain invitation code from the player who creates the multiplayer room. multiplayer.session.join.port.error=Cannot find available local network port for listening. Please ensure that HMCL has the permission to listen on a port. @@ -581,7 +583,8 @@ multiplayer.session.username=Username multiplayer.state.disconnected=Not created/entered a multiplayer session multiplayer.state.disconnected.hint=Someone should create a multiplayer session, and others join the session to play the game together. multiplayer.state.master=Created room: %1$s, port: %2$d -multiplayer.state.slave=Joined room: %s +multiplayer.state.slave=Joined room: %1$s, address: %2$s +multiplayer.state.slave.hint=After joining multiplayer room, you should get to multiplayer page in Minecraft and connect to the "HMCL Multiplayer Session" server, or manually add a server with address shown below. datapack=Datapacks datapack.add=Install datapack diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 37ee6dd11..3e30047f1 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -560,6 +560,7 @@ multiplayer.nat.type.unknown=未知 multiplayer.powered_by=由 cato 提供技術支援 multiplayer.session=房間 multiplayer.session.name.format=%1$s 的房間 +multiplayer.session.name.motd=HMCL 多人聯機房間 - %s multiplayer.session.close=關閉房間 multiplayer.session.close.warning=關閉房間後,已經加入聯機房間的玩家將會斷開連接,是否繼續? multiplayer.session.copy_room_code=複製邀請碼 @@ -571,6 +572,7 @@ multiplayer.session.create.port=埠號 multiplayer.session.create.port.error=無法檢測遊戲埠號,你必須先啟動遊戲並在遊戲內打開對區域網路開放選項後才能啟動聯機。 multiplayer.session.expired=聯機會話連續使用時間超過了 3 小時,你需要重新創建/加入房間以繼續聯機。 multiplayer.session.join=加入房間 +multiplayer.session.join.hint=你需要向已經創建好房間的玩家索要邀請碼以便加入多人聯機房間 multiplayer.session.join.invitation_code=邀請碼 multiplayer.session.join.invitation_code.error=邀請碼不正確,請向開服玩家獲取邀請碼 multiplayer.session.join.port.error=無法找到可用的本地網路埠,請確保 HMCL 擁有綁定本地埠的權限。 @@ -580,7 +582,8 @@ multiplayer.session.username=使用者名稱 multiplayer.state.disconnected=未創建/加入房間 multiplayer.state.disconnected.hint=多人聯機功能需要先有一位玩家創建房間後,其他玩家加入房間後繼續遊戲。 multiplayer.state.master=你已創建房間:%1$s,埠號 %2$d -multiplayer.state.slave=你已加入房間: %s +multiplayer.state.slave=你已加入房間: %1$s,地址為 %2$s +multiplayer.state.slave.hint=加入房間後,你需要在 Minecraft 的多人遊戲頁面選擇 HMCL 多人聯機房間伺服器,或者手動添加下方的地址的伺服器。 datapack=資料包 datapack.add=加入資料包 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 0d772b97b..306aa3542 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -560,6 +560,7 @@ multiplayer.nat.type.unknown=未知 multiplayer.powered_by=由 cato 提供技术支持 multiplayer.session=房间 multiplayer.session.name.format=%1$s 的房间 +multiplayer.session.name.motd=HMCL 多人联机房间 - %s multiplayer.session.close=关闭房间 multiplayer.session.close.warning=关闭房间后,已经加入联机房间的玩家将会断开连接,是否继续? multiplayer.session.copy_room_code=复制邀请码 @@ -571,6 +572,7 @@ multiplayer.session.create.port=端口号 multiplayer.session.create.port.error=无法检测游戏端口号,你必须先启动游戏并在游戏内打开对局域网开放选项后才能启动联机。 multiplayer.session.expired=联机会话连续使用时间超过了 3 小时,你需要重新创建/加入房间以继续联机。 multiplayer.session.join=加入房间 +multiplayer.session.join.hint=你需要向已经创建好房间的玩家索要邀请码以便加入多人联机房间 multiplayer.session.join.invitation_code=邀请码 multiplayer.session.join.invitation_code.error=邀请码不正确,请向开服玩家获取邀请码 multiplayer.session.join.port.error=无法找到可用的本地网络端口,请确保 HMCL 拥有绑定本地端口的权限。 @@ -580,7 +582,8 @@ multiplayer.session.username=用户名 multiplayer.state.disconnected=未创建/加入房间 multiplayer.state.disconnected.hint=多人联机功能需要先有一位玩家创建房间后,其他玩家加入房间后继续游戏。 multiplayer.state.master=你已创建房间:%1$s,端口号 %2$d -multiplayer.state.slave=你已加入房间: %s +multiplayer.state.slave=你已加入房间: %1$s,地址为 %2$s +multiplayer.state.slave.hint=加入房间后,你需要在 Minecraft 的多人游戏页面选择 HMCL 多人联机房间服务器,或者手动添加下方的地址的服务器。 datapack=数据包 datapack.add=添加数据包 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/ManagedProcess.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/ManagedProcess.java index 3b91e97f5..ad3207307 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/ManagedProcess.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/ManagedProcess.java @@ -101,9 +101,9 @@ public class ManagedProcess { public boolean isRunning() { try { process.exitValue(); - return true; - } catch (IllegalThreadStateException e) { return false; + } catch (IllegalThreadStateException e) { + return true; } }