mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-18 03:44:54 -04:00
eros: multiple server types (custom + LAN servers)
This commit is contained in:
parent
43d8988ef9
commit
b951322066
@ -22,7 +22,7 @@ import java.util.*
|
|||||||
|
|
||||||
abstract class AbstractCard<T> : ListCell<T>(), Initializable {
|
abstract class AbstractCard<T> : ListCell<T>(), Initializable {
|
||||||
@FXML
|
@FXML
|
||||||
protected lateinit var root: HBox
|
lateinit var root: HBox
|
||||||
|
|
||||||
override fun initialize(url: URL?, resourceBundle: ResourceBundle?) {
|
override fun initialize(url: URL?, resourceBundle: ResourceBundle?) {
|
||||||
this.graphic = root
|
this.graphic = root
|
||||||
|
@ -19,12 +19,12 @@ import javafx.fxml.FXMLLoader
|
|||||||
import javafx.scene.control.ListCell
|
import javafx.scene.control.ListCell
|
||||||
|
|
||||||
interface CardFactory<T : ListCell<*>> {
|
interface CardFactory<T : ListCell<*>> {
|
||||||
val FXML: ResourceLocation
|
val LAYOUT: ResourceLocation
|
||||||
|
|
||||||
fun build(): T {
|
fun build(): T {
|
||||||
val loader = FXMLLoader()
|
val loader = FXMLLoader()
|
||||||
|
|
||||||
loader.load<Any>(Minosoft.MINOSOFT_ASSETS_MANAGER.readAssetAsStream(FXML))
|
loader.load<Any>(Minosoft.MINOSOFT_ASSETS_MANAGER.readAssetAsStream(LAYOUT))
|
||||||
|
|
||||||
return loader.getController()
|
return loader.getController()
|
||||||
}
|
}
|
||||||
|
@ -13,23 +13,30 @@
|
|||||||
|
|
||||||
package de.bixilon.minosoft.gui.eros.main.play
|
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.controller.EmbeddedJavaFXController
|
||||||
import de.bixilon.minosoft.gui.eros.main.play.server.Refreshable
|
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.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
|
||||||
import de.bixilon.minosoft.gui.eros.util.JavaFXUtil.text
|
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 de.bixilon.minosoft.util.KUtil.asResourceLocation
|
||||||
import javafx.fxml.FXML
|
import javafx.fxml.FXML
|
||||||
import javafx.scene.control.ListView
|
import javafx.scene.control.ListView
|
||||||
import javafx.scene.layout.AnchorPane
|
import javafx.scene.layout.AnchorPane
|
||||||
import javafx.scene.layout.Pane
|
import javafx.scene.layout.Pane
|
||||||
|
import org.kordamp.ikonli.fontawesome5.FontAwesomeSolid
|
||||||
|
|
||||||
class PlayMainController : EmbeddedJavaFXController<Pane>() {
|
class PlayMainController : EmbeddedJavaFXController<Pane>() {
|
||||||
@FXML
|
@FXML
|
||||||
private lateinit var playTypeContentFX: Pane
|
private lateinit var playTypeContentFX: Pane
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private lateinit var playTypeListViewFX: ListView<*>
|
private lateinit var playTypeListViewFX: ListView<ServerType>
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private lateinit var refreshPaneFX: AnchorPane
|
private lateinit var refreshPaneFX: AnchorPane
|
||||||
@ -38,12 +45,38 @@ class PlayMainController : EmbeddedJavaFXController<Pane>() {
|
|||||||
private lateinit var currentController: EmbeddedJavaFXController<*>
|
private lateinit var currentController: EmbeddedJavaFXController<*>
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
currentController = JavaFXUtil.loadEmbeddedController<ServerListController>(ServerListController.LAYOUT)
|
playTypeListViewFX.setCellFactory { ServerTypeCardController.build() }
|
||||||
playTypeContentFX.children.setAll(currentController.root)
|
playTypeListViewFX.items += ServerType(FontAwesomeSolid.SERVER, "Custom", "0 Servers", "") {
|
||||||
|
return@ServerType JavaFXUtil.loadEmbeddedController<ServerListController>(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>(ServerListController.LAYOUT).apply {
|
||||||
|
readOnly = true
|
||||||
|
customRefresh = {
|
||||||
|
LANServerListener.SERVERS.clear()
|
||||||
|
refreshList()
|
||||||
|
}
|
||||||
|
servers = LANServerListener.SERVERS.values
|
||||||
|
Minosoft.GLOBAL_EVENT_MASTER.registerEvent(JavaFXEventInvoker.of<LANServerDiscoverEvent> { refreshList() }) // ToDo: Unregister event when hiding pane
|
||||||
|
refreshList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
JavaFXUtil.loadEmbeddedController<ServerTypeCardController>(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)
|
refreshPaneFX.children.setAll(root)
|
||||||
iconFX.iconLiteral = "fas-sync-alt"
|
iconFX.iconLiteral = "fas-sync-alt"
|
||||||
|
iconFX.isVisible = true
|
||||||
|
|
||||||
headerFX.text = REFRESH_HEADER
|
headerFX.text = REFRESH_HEADER
|
||||||
text1FX.text = REFRESH_TEXT1
|
text1FX.text = REFRESH_TEXT1
|
||||||
text2FX.text = REFRESH_TEXT2
|
text2FX.text = REFRESH_TEXT2
|
||||||
|
@ -66,6 +66,16 @@ class ServerListController : EmbeddedJavaFXController<Pane>(), Refreshable {
|
|||||||
@FXML
|
@FXML
|
||||||
private lateinit var serverInfoFX: AnchorPane
|
private lateinit var serverInfoFX: AnchorPane
|
||||||
|
|
||||||
|
var customRefresh: (() -> Unit)? = null
|
||||||
|
|
||||||
|
var servers: MutableCollection<Server> = mutableListOf()
|
||||||
|
|
||||||
|
var readOnly: Boolean = false
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
addServerButtonFX.isVisible = !value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
serverListViewFX.setCellFactory { ServerCardController.build() }
|
serverListViewFX.setCellFactory { ServerCardController.build() }
|
||||||
@ -86,7 +96,7 @@ class ServerListController : EmbeddedJavaFXController<Pane>(), Refreshable {
|
|||||||
val selected = serverListViewFX.selectionModel.selectedItem
|
val selected = serverListViewFX.selectionModel.selectedItem
|
||||||
serverListViewFX.items.clear()
|
serverListViewFX.items.clear()
|
||||||
|
|
||||||
for (server in Minosoft.config.config.server.entries.values) {
|
for (server in servers) {
|
||||||
updateServer(server)
|
updateServer(server)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,43 +178,45 @@ class ServerListController : EmbeddedJavaFXController<Pane>(), Refreshable {
|
|||||||
it.columnConstraints += ColumnConstraints()
|
it.columnConstraints += ColumnConstraints()
|
||||||
it.columnConstraints += ColumnConstraints(0.0, -1.0, Double.POSITIVE_INFINITY, Priority.ALWAYS, HPos.LEFT, true)
|
it.columnConstraints += ColumnConstraints(0.0, -1.0, Double.POSITIVE_INFINITY, Priority.ALWAYS, HPos.LEFT, true)
|
||||||
|
|
||||||
it.add(Button("Delete").apply {
|
if (!readOnly) {
|
||||||
setOnAction {
|
it.add(Button("Delete").apply {
|
||||||
SimpleErosConfirmationDialog(
|
setOnAction {
|
||||||
confirmButtonText = "minosoft:general.delete".asResourceLocation(),
|
SimpleErosConfirmationDialog(
|
||||||
description = TranslatableComponents.EROS_DELETE_SERVER_CONFIRM_DESCRIPTION(Minosoft.LANGUAGE_MANAGER, serverCard.server.name, serverCard.server.address),
|
confirmButtonText = "minosoft:general.delete".asResourceLocation(),
|
||||||
onConfirm = {
|
description = TranslatableComponents.EROS_DELETE_SERVER_CONFIRM_DESCRIPTION(Minosoft.LANGUAGE_MANAGER, serverCard.server.name, serverCard.server.address),
|
||||||
Minosoft.config.config.server.entries.remove(serverCard.server.id)
|
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()
|
Minosoft.config.saveToFile()
|
||||||
Platform.runLater { refreshList() }
|
Platform.runLater { refreshList() }
|
||||||
}
|
}).show()
|
||||||
).show()
|
}
|
||||||
}
|
}, 2, 0)
|
||||||
}, 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)
|
|
||||||
|
|
||||||
it.add(Button("Refresh").apply {
|
it.add(Button("Refresh").apply {
|
||||||
setOnAction {
|
setOnAction {
|
||||||
@ -256,15 +268,20 @@ class ServerListController : EmbeddedJavaFXController<Pane>(), Refreshable {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
fun addServer() {
|
fun addServer() {
|
||||||
UpdateServerDialog(onUpdate = { name, address, focedVersion ->
|
UpdateServerDialog(onUpdate = { name, address, forcedVersion ->
|
||||||
val server = Server(name = ChatComponent.of(name), address = address, forcedVersion = focedVersion)
|
val server = Server(name = ChatComponent.of(name), address = address, forcedVersion = forcedVersion)
|
||||||
Minosoft.config.config.server.entries[server.id] = server
|
Minosoft.config.config.server.entries[server.id] = server // ToDo
|
||||||
Minosoft.config.saveToFile()
|
Minosoft.config.saveToFile()
|
||||||
Platform.runLater { refreshList() }
|
Platform.runLater { refreshList() }
|
||||||
}).show()
|
}).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun refresh() {
|
override fun refresh() {
|
||||||
|
customRefresh?.let {
|
||||||
|
it()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for (serverCard in serverListViewFX.items) {
|
for (serverCard in serverListViewFX.items) {
|
||||||
serverCard.server.ping?.let {
|
serverCard.server.ping?.let {
|
||||||
if (it.state != StatusConnectionStates.PING_DONE && it.state != StatusConnectionStates.ERROR) {
|
if (it.state != StatusConnectionStates.PING_DONE && it.state != StatusConnectionStates.ERROR) {
|
||||||
|
@ -127,6 +127,6 @@ class ServerCardController : AbstractCard<ServerCard>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object : CardFactory<ServerCardController> {
|
companion object : CardFactory<ServerCardController> {
|
||||||
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,30 +11,15 @@
|
|||||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
* 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.gui.eros.controller.EmbeddedJavaFXController
|
||||||
import de.bixilon.minosoft.util.KUtil.asResourceLocation
|
import org.kordamp.ikonli.fontawesome5.FontAwesomeSolid
|
||||||
import javafx.fxml.FXML
|
|
||||||
import javafx.scene.layout.HBox
|
|
||||||
import javafx.scene.text.TextFlow
|
|
||||||
import org.kordamp.ikonli.javafx.FontIcon
|
|
||||||
|
|
||||||
class ServerTypeCardController : EmbeddedJavaFXController<HBox>() {
|
data class ServerType(
|
||||||
@FXML
|
val icon: FontAwesomeSolid,
|
||||||
lateinit var iconFX: FontIcon
|
val header: Any?,
|
||||||
|
val text1: Any?,
|
||||||
@FXML
|
val text2: Any?,
|
||||||
lateinit var headerFX: TextFlow
|
val content: () -> EmbeddedJavaFXController<*>,
|
||||||
|
)
|
||||||
@FXML
|
|
||||||
lateinit var text1FX: TextFlow
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
lateinit var text2FX: TextFlow
|
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val LAYOUT = "minosoft:eros/main/play/server_type_card.fxml".asResourceLocation()
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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<ServerType>() {
|
||||||
|
@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<ServerTypeCardController> {
|
||||||
|
override val LAYOUT = "minosoft:eros/main/play/server_type_card.fxml".asResourceLocation()
|
||||||
|
}
|
||||||
|
}
|
@ -68,19 +68,19 @@ object JavaFXUtil {
|
|||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
var TextFlow.text: Any
|
var TextFlow.text: Any?
|
||||||
get() = TODO()
|
get() = TODO()
|
||||||
set(value) {
|
set(value) {
|
||||||
this.children.setAll(Minosoft.LANGUAGE_MANAGER.translate(value).javaFXText)
|
this.children.setAll(Minosoft.LANGUAGE_MANAGER.translate(value).javaFXText)
|
||||||
}
|
}
|
||||||
|
|
||||||
var TextField.placeholder: Any
|
var TextField.placeholder: Any?
|
||||||
get() = this.promptText
|
get() = this.promptText
|
||||||
set(value) {
|
set(value) {
|
||||||
this.promptText = Minosoft.LANGUAGE_MANAGER.translate(value).message
|
this.promptText = Minosoft.LANGUAGE_MANAGER.translate(value).message
|
||||||
}
|
}
|
||||||
|
|
||||||
var Labeled.ctext: Any
|
var Labeled.ctext: Any?
|
||||||
get() = this.text
|
get() = this.text
|
||||||
set(value) {
|
set(value) {
|
||||||
this.text = Minosoft.LANGUAGE_MANAGER.translate(value).message
|
this.text = Minosoft.LANGUAGE_MANAGER.translate(value).message
|
||||||
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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
|
@ -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 <https://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
* 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<InetAddress, Server> 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");
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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<InetAddress, Server> = 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
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@
|
|||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
<?import javafx.scene.text.*?>
|
<?import javafx.scene.text.*?>
|
||||||
<?import org.kordamp.ikonli.javafx.FontIcon?>
|
<?import org.kordamp.ikonli.javafx.FontIcon?>
|
||||||
<HBox xmlns:fx="http://javafx.com/fxml/1" prefHeight="80.0" prefWidth="190.0" xmlns="http://javafx.com/javafx/16" fx:controller="de.bixilon.minosoft.gui.eros.main.play.ServerTypeCardController">
|
<HBox xmlns:fx="http://javafx.com/fxml/1" fx:id="root" prefHeight="80.0" prefWidth="190.0" xmlns="http://javafx.com/javafx/16" fx:controller="de.bixilon.minosoft.gui.eros.main.play.server.type.ServerTypeCardController">
|
||||||
<GridPane HBox.hgrow="ALWAYS">
|
<GridPane HBox.hgrow="ALWAYS">
|
||||||
<columnConstraints>
|
<columnConstraints>
|
||||||
<ColumnConstraints hgrow="NEVER"/>
|
<ColumnConstraints hgrow="NEVER"/>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user