diff --git a/src/main/java/de/bixilon/minosoft/gui/eros/card/AbstractCard.kt b/src/main/java/de/bixilon/minosoft/gui/eros/card/AbstractCard.kt index 1572095f4..3b4261788 100644 --- a/src/main/java/de/bixilon/minosoft/gui/eros/card/AbstractCard.kt +++ b/src/main/java/de/bixilon/minosoft/gui/eros/card/AbstractCard.kt @@ -22,7 +22,7 @@ import java.util.* abstract class AbstractCard : ListCell(), Initializable { @FXML - protected lateinit var root: HBox + lateinit var root: HBox override fun initialize(url: URL?, resourceBundle: ResourceBundle?) { this.graphic = root diff --git a/src/main/java/de/bixilon/minosoft/gui/eros/card/CardFactory.kt b/src/main/java/de/bixilon/minosoft/gui/eros/card/CardFactory.kt index ba17d6a35..a5fce89e4 100644 --- a/src/main/java/de/bixilon/minosoft/gui/eros/card/CardFactory.kt +++ b/src/main/java/de/bixilon/minosoft/gui/eros/card/CardFactory.kt @@ -19,12 +19,12 @@ import javafx.fxml.FXMLLoader import javafx.scene.control.ListCell interface CardFactory> { - val FXML: ResourceLocation + val LAYOUT: ResourceLocation fun build(): T { val loader = FXMLLoader() - loader.load(Minosoft.MINOSOFT_ASSETS_MANAGER.readAssetAsStream(FXML)) + loader.load(Minosoft.MINOSOFT_ASSETS_MANAGER.readAssetAsStream(LAYOUT)) return loader.getController() } diff --git a/src/main/java/de/bixilon/minosoft/gui/eros/main/play/PlayMainController.kt b/src/main/java/de/bixilon/minosoft/gui/eros/main/play/PlayMainController.kt index 15906f2cd..c6a868ff7 100644 --- a/src/main/java/de/bixilon/minosoft/gui/eros/main/play/PlayMainController.kt +++ b/src/main/java/de/bixilon/minosoft/gui/eros/main/play/PlayMainController.kt @@ -13,23 +13,30 @@ package de.bixilon.minosoft.gui.eros.main.play +import de.bixilon.minosoft.Minosoft import de.bixilon.minosoft.gui.eros.controller.EmbeddedJavaFXController import de.bixilon.minosoft.gui.eros.main.play.server.Refreshable import de.bixilon.minosoft.gui.eros.main.play.server.ServerListController +import de.bixilon.minosoft.gui.eros.main.play.server.type.ServerType +import de.bixilon.minosoft.gui.eros.main.play.server.type.ServerTypeCardController +import de.bixilon.minosoft.gui.eros.modding.invoker.JavaFXEventInvoker import de.bixilon.minosoft.gui.eros.util.JavaFXUtil import de.bixilon.minosoft.gui.eros.util.JavaFXUtil.text +import de.bixilon.minosoft.modding.event.events.LANServerDiscoverEvent +import de.bixilon.minosoft.protocol.protocol.LANServerListener import de.bixilon.minosoft.util.KUtil.asResourceLocation import javafx.fxml.FXML import javafx.scene.control.ListView import javafx.scene.layout.AnchorPane import javafx.scene.layout.Pane +import org.kordamp.ikonli.fontawesome5.FontAwesomeSolid class PlayMainController : EmbeddedJavaFXController() { @FXML private lateinit var playTypeContentFX: Pane @FXML - private lateinit var playTypeListViewFX: ListView<*> + private lateinit var playTypeListViewFX: ListView @FXML private lateinit var refreshPaneFX: AnchorPane @@ -38,12 +45,38 @@ class PlayMainController : EmbeddedJavaFXController() { private lateinit var currentController: EmbeddedJavaFXController<*> override fun init() { - currentController = JavaFXUtil.loadEmbeddedController(ServerListController.LAYOUT) - playTypeContentFX.children.setAll(currentController.root) + playTypeListViewFX.setCellFactory { ServerTypeCardController.build() } + playTypeListViewFX.items += ServerType(FontAwesomeSolid.SERVER, "Custom", "0 Servers", "") { + return@ServerType JavaFXUtil.loadEmbeddedController(ServerListController.LAYOUT).apply { + servers = Minosoft.config.config.server.entries.values + refreshList() + } + } + playTypeListViewFX.items += ServerType(FontAwesomeSolid.NETWORK_WIRED, "LAN", "12 Servers", "") { + return@ServerType JavaFXUtil.loadEmbeddedController(ServerListController.LAYOUT).apply { + readOnly = true + customRefresh = { + LANServerListener.SERVERS.clear() + refreshList() + } + servers = LANServerListener.SERVERS.values + Minosoft.GLOBAL_EVENT_MASTER.registerEvent(JavaFXEventInvoker.of { refreshList() }) // ToDo: Unregister event when hiding pane + refreshList() + } + } - JavaFXUtil.loadEmbeddedController(ServerTypeCardController.LAYOUT).apply { + playTypeListViewFX.selectionModel.selectedItemProperty().addListener { _, _, new -> + currentController = new.content() + playTypeContentFX.children.setAll(currentController.root) + } + + playTypeListViewFX.selectionModel.select(0) + + ServerTypeCardController.build().apply { refreshPaneFX.children.setAll(root) iconFX.iconLiteral = "fas-sync-alt" + iconFX.isVisible = true + headerFX.text = REFRESH_HEADER text1FX.text = REFRESH_TEXT1 text2FX.text = REFRESH_TEXT2 diff --git a/src/main/java/de/bixilon/minosoft/gui/eros/main/play/server/ServerListController.kt b/src/main/java/de/bixilon/minosoft/gui/eros/main/play/server/ServerListController.kt index c788276d1..6ae51fe1c 100644 --- a/src/main/java/de/bixilon/minosoft/gui/eros/main/play/server/ServerListController.kt +++ b/src/main/java/de/bixilon/minosoft/gui/eros/main/play/server/ServerListController.kt @@ -66,6 +66,16 @@ class ServerListController : EmbeddedJavaFXController(), Refreshable { @FXML private lateinit var serverInfoFX: AnchorPane + var customRefresh: (() -> Unit)? = null + + var servers: MutableCollection = mutableListOf() + + var readOnly: Boolean = false + set(value) { + field = value + addServerButtonFX.isVisible = !value + } + override fun init() { serverListViewFX.setCellFactory { ServerCardController.build() } @@ -86,7 +96,7 @@ class ServerListController : EmbeddedJavaFXController(), Refreshable { val selected = serverListViewFX.selectionModel.selectedItem serverListViewFX.items.clear() - for (server in Minosoft.config.config.server.entries.values) { + for (server in servers) { updateServer(server) } @@ -168,43 +178,45 @@ class ServerListController : EmbeddedJavaFXController(), Refreshable { it.columnConstraints += ColumnConstraints() it.columnConstraints += ColumnConstraints(0.0, -1.0, Double.POSITIVE_INFINITY, Priority.ALWAYS, HPos.LEFT, true) - it.add(Button("Delete").apply { - setOnAction { - SimpleErosConfirmationDialog( - confirmButtonText = "minosoft:general.delete".asResourceLocation(), - description = TranslatableComponents.EROS_DELETE_SERVER_CONFIRM_DESCRIPTION(Minosoft.LANGUAGE_MANAGER, serverCard.server.name, serverCard.server.address), - onConfirm = { - Minosoft.config.config.server.entries.remove(serverCard.server.id) + if (!readOnly) { + it.add(Button("Delete").apply { + setOnAction { + SimpleErosConfirmationDialog( + confirmButtonText = "minosoft:general.delete".asResourceLocation(), + description = TranslatableComponents.EROS_DELETE_SERVER_CONFIRM_DESCRIPTION(Minosoft.LANGUAGE_MANAGER, serverCard.server.name, serverCard.server.address), + onConfirm = { + Minosoft.config.config.server.entries.remove(serverCard.server.id) + Minosoft.config.saveToFile() + Platform.runLater { refreshList() } + } + ).show() + } + }, 1, 0) + it.add(Button("Edit").apply { + setOnAction { + val server = serverCard.server + UpdateServerDialog(server = server, onUpdate = { name, address, forcedVersion -> + server.name = ChatComponent.of(name) + server.forcedVersion = forcedVersion + if (server.address != address) { + server.favicon = null + + server.address = address + + // disconnect all ping connections, re ping + // ToDo: server.connections.clear() + + serverCard.unregister() + server.ping?.disconnect() + server.ping = null + server.ping() + } Minosoft.config.saveToFile() Platform.runLater { refreshList() } - } - ).show() - } - }, 1, 0) - it.add(Button("Edit").apply { - setOnAction { - val server = serverCard.server - UpdateServerDialog(server = server, onUpdate = { name, address, forcedVersion -> - server.name = ChatComponent.of(name) - server.forcedVersion = forcedVersion - if (server.address != address) { - server.favicon = null - - server.address = address - - // disconnect all ping connections, re ping - // ToDo: server.connections.clear() - - serverCard.unregister() - server.ping?.disconnect() - server.ping = null - server.ping() - } - Minosoft.config.saveToFile() - Platform.runLater { refreshList() } - }).show() - } - }, 2, 0) + }).show() + } + }, 2, 0) + } it.add(Button("Refresh").apply { setOnAction { @@ -256,15 +268,20 @@ class ServerListController : EmbeddedJavaFXController(), Refreshable { @FXML fun addServer() { - UpdateServerDialog(onUpdate = { name, address, focedVersion -> - val server = Server(name = ChatComponent.of(name), address = address, forcedVersion = focedVersion) - Minosoft.config.config.server.entries[server.id] = server + UpdateServerDialog(onUpdate = { name, address, forcedVersion -> + val server = Server(name = ChatComponent.of(name), address = address, forcedVersion = forcedVersion) + Minosoft.config.config.server.entries[server.id] = server // ToDo Minosoft.config.saveToFile() Platform.runLater { refreshList() } }).show() } override fun refresh() { + customRefresh?.let { + it() + return + } + for (serverCard in serverListViewFX.items) { serverCard.server.ping?.let { if (it.state != StatusConnectionStates.PING_DONE && it.state != StatusConnectionStates.ERROR) { diff --git a/src/main/java/de/bixilon/minosoft/gui/eros/main/play/server/card/ServerCardController.kt b/src/main/java/de/bixilon/minosoft/gui/eros/main/play/server/card/ServerCardController.kt index 3f4c829ea..d242e903f 100644 --- a/src/main/java/de/bixilon/minosoft/gui/eros/main/play/server/card/ServerCardController.kt +++ b/src/main/java/de/bixilon/minosoft/gui/eros/main/play/server/card/ServerCardController.kt @@ -127,6 +127,6 @@ class ServerCardController : AbstractCard() { } companion object : CardFactory { - override val FXML: ResourceLocation = "minosoft:eros/main/play/server/server_card.fxml".asResourceLocation() + override val LAYOUT: ResourceLocation = "minosoft:eros/main/play/server/server_card.fxml".asResourceLocation() } } diff --git a/src/main/java/de/bixilon/minosoft/gui/eros/main/play/ServerTypeCardController.kt b/src/main/java/de/bixilon/minosoft/gui/eros/main/play/server/type/ServerType.kt similarity index 58% rename from src/main/java/de/bixilon/minosoft/gui/eros/main/play/ServerTypeCardController.kt rename to src/main/java/de/bixilon/minosoft/gui/eros/main/play/server/type/ServerType.kt index 417ac8a39..230e88d47 100644 --- a/src/main/java/de/bixilon/minosoft/gui/eros/main/play/ServerTypeCardController.kt +++ b/src/main/java/de/bixilon/minosoft/gui/eros/main/play/server/type/ServerType.kt @@ -11,30 +11,15 @@ * This software is not affiliated with Mojang AB, the original developer of Minecraft. */ -package de.bixilon.minosoft.gui.eros.main.play +package de.bixilon.minosoft.gui.eros.main.play.server.type import de.bixilon.minosoft.gui.eros.controller.EmbeddedJavaFXController -import de.bixilon.minosoft.util.KUtil.asResourceLocation -import javafx.fxml.FXML -import javafx.scene.layout.HBox -import javafx.scene.text.TextFlow -import org.kordamp.ikonli.javafx.FontIcon +import org.kordamp.ikonli.fontawesome5.FontAwesomeSolid -class ServerTypeCardController : EmbeddedJavaFXController() { - @FXML - lateinit var iconFX: FontIcon - - @FXML - lateinit var headerFX: TextFlow - - @FXML - lateinit var text1FX: TextFlow - - @FXML - lateinit var text2FX: TextFlow - - - companion object { - val LAYOUT = "minosoft:eros/main/play/server_type_card.fxml".asResourceLocation() - } -} +data class ServerType( + val icon: FontAwesomeSolid, + val header: Any?, + val text1: Any?, + val text2: Any?, + val content: () -> EmbeddedJavaFXController<*>, +) diff --git a/src/main/java/de/bixilon/minosoft/gui/eros/main/play/server/type/ServerTypeCardController.kt b/src/main/java/de/bixilon/minosoft/gui/eros/main/play/server/type/ServerTypeCardController.kt new file mode 100644 index 000000000..fa07f808b --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/eros/main/play/server/type/ServerTypeCardController.kt @@ -0,0 +1,60 @@ +/* + * Minosoft + * Copyright (C) 2021 Moritz Zwerger + * + * 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.gui.eros.main.play.server.type + +import de.bixilon.minosoft.gui.eros.card.AbstractCard +import de.bixilon.minosoft.gui.eros.card.CardFactory +import de.bixilon.minosoft.gui.eros.util.JavaFXUtil.text +import de.bixilon.minosoft.util.KUtil.asResourceLocation +import javafx.fxml.FXML +import javafx.scene.text.TextFlow +import org.kordamp.ikonli.javafx.FontIcon + +class ServerTypeCardController : AbstractCard() { + @FXML + lateinit var iconFX: FontIcon + + @FXML + lateinit var headerFX: TextFlow + + @FXML + lateinit var text1FX: TextFlow + + @FXML + lateinit var text2FX: TextFlow + + override fun updateItem(item: ServerType?, empty: Boolean) { + super.updateItem(item, empty) + item ?: return + + + iconFX.isVisible = true + iconFX.iconCode = item.icon + headerFX.text = item.header + text1FX.text = item.text1 + text2FX.text = item.text2 + } + + + override fun clear() { + iconFX.isVisible = false + headerFX.children.clear() + text1FX.children.clear() + text2FX.children.clear() + } + + companion object : CardFactory { + override val LAYOUT = "minosoft:eros/main/play/server_type_card.fxml".asResourceLocation() + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/eros/util/JavaFXUtil.kt b/src/main/java/de/bixilon/minosoft/gui/eros/util/JavaFXUtil.kt index bbdc4240e..45f3f31bf 100644 --- a/src/main/java/de/bixilon/minosoft/gui/eros/util/JavaFXUtil.kt +++ b/src/main/java/de/bixilon/minosoft/gui/eros/util/JavaFXUtil.kt @@ -68,19 +68,19 @@ object JavaFXUtil { return controller } - var TextFlow.text: Any + var TextFlow.text: Any? get() = TODO() set(value) { this.children.setAll(Minosoft.LANGUAGE_MANAGER.translate(value).javaFXText) } - var TextField.placeholder: Any + var TextField.placeholder: Any? get() = this.promptText set(value) { this.promptText = Minosoft.LANGUAGE_MANAGER.translate(value).message } - var Labeled.ctext: Any + var Labeled.ctext: Any? get() = this.text set(value) { this.text = Minosoft.LANGUAGE_MANAGER.translate(value).message diff --git a/src/main/java/de/bixilon/minosoft/modding/event/events/LANServerDiscoverEvent.kt b/src/main/java/de/bixilon/minosoft/modding/event/events/LANServerDiscoverEvent.kt new file mode 100644 index 000000000..353776e59 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/modding/event/events/LANServerDiscoverEvent.kt @@ -0,0 +1,22 @@ +/* + * Minosoft + * Copyright (C) 2021 Moritz Zwerger + * + * 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.modding.event.events + +import de.bixilon.minosoft.config.server.Server +import java.net.InetAddress + +class LANServerDiscoverEvent( + val remoteAddress: InetAddress, + val serer: Server, +) : Event(), CancelableEvent diff --git a/src/main/java/de/bixilon/minosoft/protocol/protocol/LANServerListener.java b/src/main/java/de/bixilon/minosoft/protocol/protocol/LANServerListener.java deleted file mode 100644 index 5bfefe0d9..000000000 --- a/src/main/java/de/bixilon/minosoft/protocol/protocol/LANServerListener.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Minosoft - * Copyright (C) 2020 Moritz Zwerger - * - * 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 . - * - * This software is not affiliated with Mojang AB, the original developer of Minecraft. - */ - -package de.bixilon.minosoft.protocol.protocol; - -import com.google.common.collect.HashBiMap; -import de.bixilon.minosoft.config.server.Server; -import de.bixilon.minosoft.util.Util; -import de.bixilon.minosoft.util.logging.Log; -import de.bixilon.minosoft.util.logging.LogLevels; -import de.bixilon.minosoft.util.logging.LogMessageType; - -import java.net.*; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.CountDownLatch; - -public class LANServerListener { - public static final HashBiMap SERVER_MAP = HashBiMap.create(); - private static final String MOTD_BEGIN_STRING = "[MOTD]"; - private static final String MOTD_END_STRING = "[/MOTD]"; - private static final String PORT_START_STRING = "[AD]"; - private static final String PORT_END_STRING = "[/AD]"; - private static final String[] BROADCAST_MUST_CONTAIN = {MOTD_BEGIN_STRING, MOTD_END_STRING, PORT_START_STRING, PORT_END_STRING}; - - public static void listen() throws InterruptedException { - CountDownLatch latch = new CountDownLatch(1); - new Thread(() -> { - try { - MulticastSocket socket = new MulticastSocket(ProtocolDefinition.LAN_SERVER_BROADCAST_PORT); - socket.joinGroup(new InetSocketAddress(ProtocolDefinition.LAN_SERVER_BROADCAST_INET_ADDRESS, ProtocolDefinition.LAN_SERVER_BROADCAST_PORT), NetworkInterface.getByInetAddress(ProtocolDefinition.LAN_SERVER_BROADCAST_INET_ADDRESS)); - byte[] buf = new byte[256]; // this should be enough, if the packet is longer, it is probably invalid - Log.log(LogMessageType.NETWORK_STATUS, LogLevels.INFO, () -> "Listening for LAN servers"); - latch.countDown(); - while (true) { - try { - DatagramPacket packet = new DatagramPacket(buf, buf.length); - socket.receive(packet); - Log.protocol(String.format("LAN UDP Broadcast from %s:%s -> %s", packet.getAddress().getHostAddress(), packet.getPort(), new String(buf))); - InetAddress sender = packet.getAddress(); - if (SERVER_MAP.containsKey(sender)) { - // This guy sent us already a server, maybe just the regular 1.5 second interval, a duplicate or a DOS attack...We don't care - continue; - } - Server server = getServerByBroadcast(sender, packet.getData()); - if (SERVER_MAP.containsValue(server)) { - continue; - } - if (SERVER_MAP.size() > ProtocolDefinition.LAN_SERVER_MAXIMUM_SERVERS) { - continue; - } - SERVER_MAP.put(sender, server); - Log.debug(String.format("Discovered new LAN Server: %s", server)); - } catch (Exception ignored) { - } - } - - } catch (Exception e) { - e.printStackTrace(); - latch.countDown(); - } - SERVER_MAP.clear(); - Log.warn("Stopping LAN Server Listener Thread"); - }, "LAN Server Listener").start(); - latch.await(); - } - - private static Server getServerByBroadcast(InetAddress address, byte[] broadcast) { - String parsed = new String(broadcast, StandardCharsets.UTF_8); // example: [MOTD]Bixilon - New World[/MOTD][AD]41127[/AD] - for (String mustContain : BROADCAST_MUST_CONTAIN) { - if (!parsed.contains(mustContain)) { - throw new IllegalArgumentException("Broadcast is invalid!"); - } - } - String rawAddress = Util.getStringBetween(parsed, PORT_START_STRING, PORT_END_STRING); - if (rawAddress.contains(":")) { - // weird, just extract the port - rawAddress = rawAddress.split(":")[1]; - } - int port = Integer.parseInt(rawAddress); - if (port < 0 || port > 65535) { - throw new IllegalArgumentException(String.format("Invalid port: %d", port)); - } - // return new Server(new ServerAddress(address.getHostAddress(), port)); - throw new RuntimeException("TODO"); - } -} diff --git a/src/main/java/de/bixilon/minosoft/protocol/protocol/LANServerListener.kt b/src/main/java/de/bixilon/minosoft/protocol/protocol/LANServerListener.kt new file mode 100644 index 000000000..21cdb66bf --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/protocol/protocol/LANServerListener.kt @@ -0,0 +1,94 @@ +/* + * Minosoft + * Copyright (C) 2020 Moritz Zwerger + * + * 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ +package de.bixilon.minosoft.protocol.protocol + +import com.google.common.collect.HashBiMap +import de.bixilon.minosoft.Minosoft +import de.bixilon.minosoft.config.server.Server +import de.bixilon.minosoft.modding.event.events.LANServerDiscoverEvent +import de.bixilon.minosoft.util.Util +import de.bixilon.minosoft.util.logging.Log +import de.bixilon.minosoft.util.logging.LogLevels +import de.bixilon.minosoft.util.logging.LogMessageType +import java.net.* +import java.nio.charset.StandardCharsets +import java.util.concurrent.CountDownLatch + +object LANServerListener { + val SERVERS: HashBiMap = HashBiMap.create() + private const val MOTD_BEGIN_STRING = "[MOTD]" + private const val MOTD_END_STRING = "[/MOTD]" + private const val PORT_START_STRING = "[AD]" + private const val PORT_END_STRING = "[/AD]" + private val BROADCAST_MUST_CONTAIN = arrayOf(MOTD_BEGIN_STRING, MOTD_END_STRING, PORT_START_STRING, PORT_END_STRING) + + fun listen() { + val latch = CountDownLatch(1) + Thread({ + try { + val socket = MulticastSocket(ProtocolDefinition.LAN_SERVER_BROADCAST_PORT) + socket.joinGroup(InetSocketAddress(ProtocolDefinition.LAN_SERVER_BROADCAST_INET_ADDRESS, ProtocolDefinition.LAN_SERVER_BROADCAST_PORT), NetworkInterface.getByInetAddress(ProtocolDefinition.LAN_SERVER_BROADCAST_INET_ADDRESS)) + val buffer = ByteArray(256) // this should be enough, if the packet is longer, it is probably invalid + Log.log(LogMessageType.NETWORK_STATUS, LogLevels.INFO) { "Listening for LAN servers..." } + latch.countDown() + while (true) { + try { + val packet = DatagramPacket(buffer, buffer.size) + socket.receive(packet) + val broadcast = String(buffer, 0, packet.length, StandardCharsets.UTF_8) + Log.log(LogMessageType.NETWORK_PACKETS_IN, LogLevels.INFO) { "Received LAN servers broadcast (${packet.address.hostAddress}:${packet.port}): $broadcast" } + val sender = packet.address + if (SERVERS.containsKey(sender)) { + // This guy sent us already a server, maybe just the regular 1.5 second interval, a duplicate or a DOS attack...We don't care + continue + } + val server = getServerByBroadcast(sender, broadcast) + if (SERVERS.containsValue(server)) { + continue + } + if (SERVERS.size > ProtocolDefinition.LAN_SERVER_MAXIMUM_SERVERS) { + continue + } + if (Minosoft.GLOBAL_EVENT_MASTER.fireEvent(LANServerDiscoverEvent(packet.address, server))) { + continue + } + SERVERS[sender] = server + Log.log(LogMessageType.NETWORK_PACKETS_IN, LogLevels.INFO) { "Discovered LAN servers: $server" } + } catch (ignored: Throwable) { + } + } + } catch (exception: Exception) { + exception.printStackTrace() + latch.countDown() + } + SERVERS.clear() + Log.log(LogMessageType.NETWORK_STATUS, LogLevels.INFO) { "Stop listening for LAN servers..." } + }, "LAN Server Listener").start() + latch.await() + } + + private fun getServerByBroadcast(address: InetAddress, broadcast: String): Server { + // example: [MOTD]Bixilon - New World[/MOTD][AD]41127[/AD] + for (mustContain in BROADCAST_MUST_CONTAIN) { + require(broadcast.contains(mustContain)) { "Broadcast is invalid!" } + } + var rawAddress = Util.getStringBetween(broadcast, PORT_START_STRING, PORT_END_STRING) + if (rawAddress.contains(":")) { + // weird, just extract the port + rawAddress = rawAddress.split(":").toTypedArray()[1] + } + val port = rawAddress.toInt() + require(!(port < 0 || port > 65535)) { String.format("Invalid port: %d", port) } + return Server(address = address.hostAddress + ":" + rawAddress) // ToDo: Name + } +} diff --git a/src/main/resources/assets/minosoft/eros/main/play/server_type_card.fxml b/src/main/resources/assets/minosoft/eros/main/play/server_type_card.fxml index 364f5934b..6d9b5a463 100644 --- a/src/main/resources/assets/minosoft/eros/main/play/server_type_card.fxml +++ b/src/main/resources/assets/minosoft/eros/main/play/server_type_card.fxml @@ -4,7 +4,7 @@ - +