From c7246956f441873f86271b343da0816afa33777b Mon Sep 17 00:00:00 2001 From: Bixilon Date: Wed, 26 Aug 2020 15:31:00 +0200 Subject: [PATCH] Launcher wip (1): basic server list --- pom.xml | 22 ++- .../java/de/bixilon/minosoft/Launcher.java | 49 +++++++ .../java/de/bixilon/minosoft/Minosoft.java | 14 +- .../de/bixilon/minosoft/PingCallback.java | 18 +++ .../de/bixilon/minosoft/ServerListPing.java | 11 +- .../minosoft/config/Configuration.java | 49 +++++-- .../bixilon/minosoft/gui/main/GUITools.java | 30 ++++ .../de/bixilon/minosoft/gui/main/Server.java | 101 ++++++++++++++ .../minosoft/gui/main/ServerListCell.java | 129 ++++++++++++++++++ .../minosoft/protocol/network/Connection.java | 36 ++++- .../minosoft/protocol/network/Network.java | 6 +- .../protocol/protocol/PacketHandler.java | 27 ++-- src/main/resources/config/game.yml | 5 +- src/main/resources/icons/windowIcon.png | Bin 0 -> 28544 bytes 14 files changed, 449 insertions(+), 48 deletions(-) create mode 100644 src/main/java/de/bixilon/minosoft/Launcher.java create mode 100644 src/main/java/de/bixilon/minosoft/PingCallback.java create mode 100644 src/main/java/de/bixilon/minosoft/gui/main/GUITools.java create mode 100644 src/main/java/de/bixilon/minosoft/gui/main/Server.java create mode 100644 src/main/java/de/bixilon/minosoft/gui/main/ServerListCell.java create mode 100644 src/main/resources/icons/windowIcon.png diff --git a/pom.xml b/pom.xml index 0d5610083..ec08c0e7e 100644 --- a/pom.xml +++ b/pom.xml @@ -33,10 +33,10 @@ - - 14 - 14 - + + 14 + 14 + @@ -54,11 +54,23 @@ guava 29.0-jre - dnsjava dnsjava 3.2.2 + + org.openjfx + javafx-controls + 14.0.1 + pom + + + org.openjfx + javafx-fxml + 14.0.1 + pom + + \ No newline at end of file diff --git a/src/main/java/de/bixilon/minosoft/Launcher.java b/src/main/java/de/bixilon/minosoft/Launcher.java new file mode 100644 index 000000000..88e6e3cde --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/Launcher.java @@ -0,0 +1,49 @@ +/* + * Codename 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; + +import de.bixilon.minosoft.gui.main.GUITools; +import de.bixilon.minosoft.gui.main.Server; +import de.bixilon.minosoft.gui.main.ServerListCell; +import javafx.application.Application; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.Scene; +import javafx.scene.control.ListView; +import javafx.scene.layout.BorderPane; +import javafx.stage.Stage; + + +public class Launcher extends Application { + + public static void main(String[] args) { + launch(args); + } + + @Override + public void start(Stage primaryStage) { + ListView listView = new ListView<>(); + listView.setCellFactory((lv) -> ServerListCell.newInstance()); + + ObservableList servers = FXCollections.observableArrayList(); + servers.addAll(Minosoft.serverList); + listView.setItems(servers); + + Scene scene = new Scene(new BorderPane(listView), 400, 450); + primaryStage.setScene(scene); + primaryStage.setTitle("Minosoft"); + primaryStage.getIcons().add(GUITools.logo); + primaryStage.show(); + } +} \ No newline at end of file diff --git a/src/main/java/de/bixilon/minosoft/Minosoft.java b/src/main/java/de/bixilon/minosoft/Minosoft.java index f12b839b7..64de9852e 100644 --- a/src/main/java/de/bixilon/minosoft/Minosoft.java +++ b/src/main/java/de/bixilon/minosoft/Minosoft.java @@ -15,12 +15,10 @@ package de.bixilon.minosoft; import de.bixilon.minosoft.config.Configuration; import de.bixilon.minosoft.config.GameConfiguration; -import de.bixilon.minosoft.game.datatypes.Player; import de.bixilon.minosoft.game.datatypes.objectLoader.versions.Versions; +import de.bixilon.minosoft.gui.main.Server; import de.bixilon.minosoft.logging.Log; import de.bixilon.minosoft.logging.LogLevels; -import de.bixilon.minosoft.protocol.network.Connection; -import de.bixilon.minosoft.protocol.protocol.ConnectionReasons; import de.bixilon.minosoft.util.FolderUtil; import de.bixilon.minosoft.util.OSUtil; import de.bixilon.minosoft.util.Util; @@ -33,7 +31,8 @@ import java.util.UUID; public class Minosoft { static Configuration config; - static ArrayList accountList; + public static ArrayList accountList; + public static ArrayList serverList; public static void main(String[] args) { // init log thread @@ -49,7 +48,7 @@ public class Minosoft { e.printStackTrace(); return; } - Log.info(String.format("Loaded config file (version=%s)", config.getInteger(GameConfiguration.CONFIG_VERSION))); + Log.info(String.format("Loaded config file (version=%s)", config.getInt(GameConfiguration.CONFIG_VERSION))); // set log level from config Log.setLevel(LogLevels.valueOf(config.getString(GameConfiguration.GENERAL_LOG_LEVEL))); Log.info(String.format("Logging info with level: %s", Log.getLevel())); @@ -83,8 +82,9 @@ public class Minosoft { } else { Log.mojang("Could not refresh session, you will not be able to join premium servers!"); } - Connection c = new Connection(1, config.getString("debug.host"), new Player(account)); - c.resolve(ConnectionReasons.CONNECT); // resolve dns address and connect + + serverList = config.getServers(); + Launcher.main(args); } /** diff --git a/src/main/java/de/bixilon/minosoft/PingCallback.java b/src/main/java/de/bixilon/minosoft/PingCallback.java new file mode 100644 index 000000000..1b67d90a8 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/PingCallback.java @@ -0,0 +1,18 @@ +/* + * Codename 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; + +public interface PingCallback { + void handle(ServerListPing ping); +} diff --git a/src/main/java/de/bixilon/minosoft/ServerListPing.java b/src/main/java/de/bixilon/minosoft/ServerListPing.java index d1c237967..1f0032dd4 100644 --- a/src/main/java/de/bixilon/minosoft/ServerListPing.java +++ b/src/main/java/de/bixilon/minosoft/ServerListPing.java @@ -15,6 +15,8 @@ package de.bixilon.minosoft; import com.google.gson.JsonObject; import de.bixilon.minosoft.game.datatypes.TextComponent; +import de.bixilon.minosoft.gui.main.GUITools; +import javafx.scene.image.Image; public class ServerListPing { final JsonObject raw; @@ -40,7 +42,14 @@ public class ServerListPing { } public String getBase64EncodedFavicon() { - return raw.get("favicon").getAsString(); + if (raw.has("favicon")) { + return raw.get("favicon").getAsString().replace("data:image/png;base64,", ""); + } + return null; + } + + public Image getFavicon() { + return GUITools.getImageFromBase64(getBase64EncodedFavicon()); } public TextComponent getMotd() { diff --git a/src/main/java/de/bixilon/minosoft/config/Configuration.java b/src/main/java/de/bixilon/minosoft/config/Configuration.java index 184a7c3ca..f5f2cff0b 100644 --- a/src/main/java/de/bixilon/minosoft/config/Configuration.java +++ b/src/main/java/de/bixilon/minosoft/config/Configuration.java @@ -14,6 +14,7 @@ package de.bixilon.minosoft.config; import de.bixilon.minosoft.Config; +import de.bixilon.minosoft.gui.main.Server; import de.bixilon.minosoft.util.mojang.api.MojangAccount; import org.yaml.snakeyaml.Yaml; @@ -56,12 +57,12 @@ public class Configuration { return getBoolean(config.getPath()); } - public int getInteger(String path) { + public int getInt(String path) { return (int) get(path); } - public int getInteger(ConfigEnum config) { - return getInteger(config.getPath()); + public int getInt(ConfigEnum config) { + return getInt(config.getPath()); } public String getString(String path) { @@ -80,11 +81,11 @@ public class Configuration { put(path, value); } - public void putInteger(ConfigEnum config, int value) { - putInteger(config.getPath(), value); + public void putInt(ConfigEnum config, int value) { + putInt(config.getPath(), value); } - public void putInteger(String path, int value) { + public void putInt(String path, int value) { put(path, value); } @@ -104,6 +105,17 @@ public class Configuration { putString(basePath + "playerName", account.getPlayerName()); } + public void putServer(Server server) { + String basePath = String.format("servers.%d.", server.getId()); + putString(basePath + "name", server.getName()); + putString(basePath + "address", server.getAddress()); + putString(basePath + "account", server.getAccount()); + putInt(basePath + "version", server.getDesiredVersion()); + if (server.getBase64Favicon() != null) { + putString(basePath + "favicon", server.getBase64Favicon()); + } + } + public Object get(String path) { if (path.contains(".")) { // split @@ -151,15 +163,34 @@ public class Configuration { public ArrayList getMojangAccounts() { ArrayList accounts = new ArrayList<>(); - LinkedHashMap objects = (LinkedHashMap) get("account.accounts"); + LinkedHashMap> objects = (LinkedHashMap>) get("account.accounts"); if (objects == null) { return accounts; } - for (Map.Entry set : objects.entrySet()) { - LinkedHashMap entry = (LinkedHashMap) set.getValue(); + for (Map.Entry> set : objects.entrySet()) { + LinkedHashMap entry = set.getValue(); accounts.add(new MojangAccount((String) entry.get("accessToken"), set.getKey(), UUID.fromString((String) entry.get("uuid")), (String) entry.get("playerName"), (String) entry.get("mojangUserName"))); } return accounts; } + + public ArrayList getServers() { + ArrayList servers = new ArrayList<>(); + LinkedHashMap> objects = (LinkedHashMap>) get("servers"); + if (objects == null) { + return servers; + } + for (Map.Entry> set : objects.entrySet()) { + LinkedHashMap entry = set.getValue(); + String favicon = null; + if (entry.containsKey("favicon")) { + favicon = (String) entry.get("favicon"); + } + servers.add(new Server(Integer.parseInt(set.getKey()), (String) entry.get("name"), (String) entry.get("address"), (String) entry.get("account"), (int) entry.get("version"), favicon)); + } + return servers; + } + } + diff --git a/src/main/java/de/bixilon/minosoft/gui/main/GUITools.java b/src/main/java/de/bixilon/minosoft/gui/main/GUITools.java new file mode 100644 index 000000000..7effef3c4 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/main/GUITools.java @@ -0,0 +1,30 @@ +/* + * Codename 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.gui.main; + +import javafx.scene.image.Image; + +import java.io.ByteArrayInputStream; +import java.util.Base64; + +public class GUITools { + public final static Image logo = new Image(GUITools.class.getResourceAsStream("/icons/windowIcon.png")); + + public static Image getImageFromBase64(String base64) { + if (base64 == null) { + return null; + } + return new Image(new ByteArrayInputStream(Base64.getDecoder().decode(base64))); + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/main/Server.java b/src/main/java/de/bixilon/minosoft/gui/main/Server.java new file mode 100644 index 000000000..41408b95b --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/main/Server.java @@ -0,0 +1,101 @@ +/* + * Codename 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.gui.main; + +import de.bixilon.minosoft.Config; +import de.bixilon.minosoft.Minosoft; +import javafx.scene.image.Image; + +import javax.annotation.Nullable; + +public class Server { + final int id; + String name; + String address; + String account; + int desiredVersion; + String favicon; + + public Server(int id, String name, String address, String account, int desiredVersion) { + this.id = id; + this.name = name; + this.address = address; + this.account = account; + this.desiredVersion = desiredVersion; + } + + public Server(int id, String name, String address, String account, int desiredVersion, String favicon) { + this.id = id; + this.name = name; + this.address = address; + this.account = account; + this.desiredVersion = desiredVersion; + this.favicon = favicon; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getAccount() { + return account; + } + + public void setAccount(String account) { + this.account = account; + } + + public int getDesiredVersion() { + return desiredVersion; + } + + public void setDesiredVersion(int desiredVersion) { + this.desiredVersion = desiredVersion; + } + + @Nullable + public String getBase64Favicon() { + return favicon; + } + + public void setBase64Favicon(String favicon) { + this.favicon = favicon; + } + + @Nullable + public Image getFavicon() { + return GUITools.getImageFromBase64(getBase64Favicon()); + } + + public int getId() { + return id; + } + + public void saveToConfig() { + Minosoft.getConfig().putServer(this); + Minosoft.getConfig().saveToFile(Config.configFileName); + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/main/ServerListCell.java b/src/main/java/de/bixilon/minosoft/gui/main/ServerListCell.java new file mode 100644 index 000000000..5525b53a6 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/main/ServerListCell.java @@ -0,0 +1,129 @@ +/* + * Codename 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.gui.main; + +import de.bixilon.minosoft.Minosoft; +import de.bixilon.minosoft.game.datatypes.Player; +import de.bixilon.minosoft.game.datatypes.objectLoader.versions.Version; +import de.bixilon.minosoft.game.datatypes.objectLoader.versions.Versions; +import de.bixilon.minosoft.protocol.network.Connection; +import de.bixilon.minosoft.protocol.protocol.ConnectionReasons; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.MenuItem; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; + +import java.io.IOException; +import java.net.URL; +import java.util.ResourceBundle; + +public class ServerListCell extends ListCell implements Initializable { + + @FXML + public ImageView icon; + @FXML + public Label motd; + @FXML + public Label version; + @FXML + public ImageView ping; //ToDo + @FXML + public Label players; + @FXML + public MenuItem optionsConnect; + @FXML + public MenuItem optionsEdit; + @FXML + public MenuItem optionsDelete; + @FXML + private Label serverName; + @FXML + private AnchorPane root; + private Server model; + + public static ServerListCell newInstance() { + FXMLLoader loader = new FXMLLoader(ServerListCell.class.getResource("/layout/cells/server.fxml")); + try { + loader.load(); + return loader.getController(); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + /** + * Initializes the controller class. + */ + @Override + public void initialize(URL url, ResourceBundle rb) { + updateSelected(false); + setGraphic(root); + } + + public AnchorPane getRoot() { + return root; + } + + @Override + protected void updateItem(Server server, boolean empty) { + super.updateItem(server, empty); + getRoot().getChildrenUnmodifiable().forEach(c -> c.setVisible(!empty)); + if (!empty && server != null && !server.equals(this.model)) { + serverName.setText(server.getName()); + Image favicon = server.getFavicon(); + if (favicon == null) { + favicon = GUITools.logo; + } + icon.setImage(favicon); + + Connection c = new Connection(server.getId(), server.getAddress(), new Player(Minosoft.accountList.get(0))); + c.addPingCallback(ping -> Platform.runLater(() -> { + if (ping == null) { + // Offline + players.setText(""); + version.setText("Offline"); + optionsConnect.setDisable(true); + return; + } + players.setText(String.format("%d/%d", ping.getPlayerOnline(), ping.getMaxPlayers())); + Version serverVersion = Versions.getVersionById(ping.getProtocolNumber()); + if (serverVersion == null) { + version.setText(String.format("UNKNOWN(%d)", ping.getProtocolNumber())); + } else { + version.setText(serverVersion.getVersionName()); + } + motd.setText(ping.getMotd().getRawMessage()); + if (ping.getFavicon() != null) { + server.setBase64Favicon(ping.getBase64EncodedFavicon()); + server.saveToConfig(); + icon.setImage(ping.getFavicon()); + } + })); + c.resolve(ConnectionReasons.PING); // resolve dns address and connect + } + this.model = server; + } + + @Override + public void updateSelected(boolean selected) { + super.updateSelected(selected); + } +} \ No newline at end of file diff --git a/src/main/java/de/bixilon/minosoft/protocol/network/Connection.java b/src/main/java/de/bixilon/minosoft/protocol/network/Connection.java index 216978c30..b434361bf 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/network/Connection.java +++ b/src/main/java/de/bixilon/minosoft/protocol/network/Connection.java @@ -14,6 +14,8 @@ package de.bixilon.minosoft.protocol.network; import de.bixilon.minosoft.Minosoft; +import de.bixilon.minosoft.PingCallback; +import de.bixilon.minosoft.ServerListPing; import de.bixilon.minosoft.config.GameConfiguration; import de.bixilon.minosoft.game.datatypes.Player; import de.bixilon.minosoft.game.datatypes.VelocityHandler; @@ -45,13 +47,15 @@ public class Connection { final PacketSender sender = new PacketSender(this); final ArrayList handlingQueue = new ArrayList<>(); final VelocityHandler velocityHandler = new VelocityHandler(this); + final ArrayList pingCallbacks = new ArrayList<>(); final int connectionId; + final Player player; + int desiredVersionNumber = -1; ServerAddress address; PluginChannelHandler pluginChannelHandler; Thread handleThread; Version version = Versions.getLowestVersionSupported(); // default final CustomMapping customMapping = new CustomMapping(version); - final Player player; ConnectionStates state = ConnectionStates.DISCONNECTED; ConnectionReasons reason; ConnectionReasons nextReason; @@ -166,8 +170,10 @@ public class Connection { Log.warn(String.format("Could not connect to %s, trying next hostname: %s", address, nextAddress)); this.address = nextAddress; resolve(address); + } else { + // no connection and no servers available anymore... sorry, but you can not play today :( + handleCallbacks(null); } - // else: failed break; } } @@ -295,7 +301,7 @@ public class Connection { public int getPacketCommand(Packets.Serverbound packet) { Integer command = null; - if (getReason() != ConnectionReasons.GET_VERSION) { + if (getReason() == ConnectionReasons.CONNECT) { command = version.getCommandByPacket(packet); } if (command == null) { @@ -306,7 +312,7 @@ public class Connection { public Packets.Clientbound getPacketByCommand(ConnectionStates state, int command) { Packets.Clientbound packet = null; - if (getReason() != ConnectionReasons.GET_VERSION) { + if (getReason() == ConnectionReasons.CONNECT) { packet = version.getPacketByCommand(state, command); } if (packet == null) { @@ -322,4 +328,26 @@ public class Connection { public int getConnectionId() { return connectionId; } + + public void addPingCallback(PingCallback pingCallback) { + pingCallbacks.add(pingCallback); + } + + public ArrayList getPingCallbacks() { + return pingCallbacks; + } + + public int getDesiredVersionNumber() { + return desiredVersionNumber; + } + + public void setDesiredVersionNumber(int desiredVersionNumber) { + this.desiredVersionNumber = desiredVersionNumber; + } + + public void handleCallbacks(ServerListPing ping) { + for (PingCallback callback : getPingCallbacks()) { + callback.handle(ping); + } + } } diff --git a/src/main/java/de/bixilon/minosoft/protocol/network/Network.java b/src/main/java/de/bixilon/minosoft/protocol/network/Network.java index 9f4386539..7b21fe2aa 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/network/Network.java +++ b/src/main/java/de/bixilon/minosoft/protocol/network/Network.java @@ -31,9 +31,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.SocketTimeoutException; +import java.net.*; import java.util.ArrayList; public class Network { @@ -201,7 +199,7 @@ public class Network { connection.setConnectionState(ConnectionStates.DISCONNECTED); } catch (IOException e) { // Could not connect - if (e instanceof SocketTimeoutException) { + if (e instanceof SocketTimeoutException || e instanceof ConnectException || e instanceof UnknownHostException) { connection.setConnectionState(ConnectionStates.FAILED); } e.printStackTrace(); diff --git a/src/main/java/de/bixilon/minosoft/protocol/protocol/PacketHandler.java b/src/main/java/de/bixilon/minosoft/protocol/protocol/PacketHandler.java index d50177e1d..3e3a332f3 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/protocol/PacketHandler.java +++ b/src/main/java/de/bixilon/minosoft/protocol/protocol/PacketHandler.java @@ -13,7 +13,6 @@ package de.bixilon.minosoft.protocol.protocol; -import de.bixilon.minosoft.Minosoft; import de.bixilon.minosoft.game.datatypes.GameModes; import de.bixilon.minosoft.game.datatypes.entities.Entity; import de.bixilon.minosoft.game.datatypes.entities.meta.HumanMetaData; @@ -56,21 +55,21 @@ public class PacketHandler { } public void handle(PacketStatusResponse pkg) { - if (connection.getReason() == ConnectionReasons.GET_VERSION) { - // now we know the version, set it, if the config allows it - Version version; - int versionId = Minosoft.getConfig().getInteger("debug.version"); - if (versionId == -1) { - versionId = pkg.getResponse().getProtocolNumber(); - } - version = Versions.getVersionById(versionId); - if (version == null) { - Log.fatal(String.format("Server is running on unknown version or a invalid version was forced (version=%d). Exiting...", versionId)); - System.exit(1); - } - Log.info(String.format("Status response received: %s/%s online. MotD: '%s'", pkg.getResponse().getPlayerOnline(), pkg.getResponse().getMaxPlayers(), pkg.getResponse().getMotd().getColoredMessage())); + // now we know the version, set it, if the config allows it + Version version; + int versionId = connection.getDesiredVersionNumber(); + if (versionId == -1) { + versionId = pkg.getResponse().getProtocolNumber(); + } + version = Versions.getVersionById(versionId); + if (version == null) { + Log.fatal(String.format("Server is running on unknown version or a invalid version was forced (version=%d). Disconnecting...", versionId)); + connection.disconnect(); + } else { connection.setVersion(version); } + Log.info(String.format("Status response received: %s/%s online. MotD: '%s'", pkg.getResponse().getPlayerOnline(), pkg.getResponse().getMaxPlayers(), pkg.getResponse().getMotd().getColoredMessage())); + connection.handleCallbacks(pkg.getResponse()); } public void handle(PacketStatusPong pkg) { diff --git a/src/main/resources/config/game.yml b/src/main/resources/config/game.yml index e657ebb87..1d87a0c57 100644 --- a/src/main/resources/config/game.yml +++ b/src/main/resources/config/game.yml @@ -15,7 +15,4 @@ account: clientToken: "randomGenerated" accounts: -# this will be removed soon, only for debugging (pre alpha stage): some features are not implemented yet -/- -debug: - server: "127.0.0.1" - version: -1 \ No newline at end of file +servers: diff --git a/src/main/resources/icons/windowIcon.png b/src/main/resources/icons/windowIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..1ae5d4d308dbaec0491913891716ca19de6884e6 GIT binary patch literal 28544 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%0DMm)LdY(~#YFUW*ny`2!2Im~Qh&65@x@Yh z#Q9i`7%4pPKI45Ga06Lfi?x<{{A?=#p8w|Aep1xyh<9c<1_ZAqXzr*BEQYak+eaSO zab#=^=#&c>(|=8>Inqua6YKFzDXhth@q202$-u6D*%N5XdPsL%6beyte(9EfqaV--}2FX za|Y(FkI#lKS3yW0W@Uf}GC)bzp=Hng#QW*lb^yG8AKY2pJ-U|TI`7M=*VR?qtn2B9gB)+0qacmzOCWtk*|EcddxwAp?yr3Mez4zCYt z2W*yVg%YFfA-@b*Q(e7?KrEaPCYfXV?M(KJxizaQN_M<006&Z>m9#Siss|!uY^?KR z;E^K3Z@F02J=4KURQ!*uw6w&NCf0UK_jjr8TdsZOy3jjbyHA0b?U1vPC@ zJi6g{af)su^^1pRGz0ow!{nlf@=>swfGToQ{IQQbGJN&<72_Q`D*%`Q#YX%d6ApB+ zvdy6-t+(IQ_sXdN;0#WQEG5_rYJ2AYS-X1Pwy`B@nUV5<;Nr~Y!f#^P?06F?eca3Z$95ZrQ@(YCY=p)qoD*KY>ESt7Ie=@huT4(P6% z>x*<<+n_qN6~y;goL!MwdSrkc<~Ba3(v$|9_c?GLJit6Dx`Vuta&UZbRw{YQ=T>$P z0JR0+Tuyw*kPjSB3J(O}>FvJ@59bk5@q@%a?H`OW)B#+b3{#FHQlwu?otZ`rLK--6 z^Wvugc#TzXgk5Pb0bt>lQN66l?M-i^!%4tRugppbKv{K4Hf?uIaLL5U+a^JWr{nG+ z2rBMp!@mLmutrftjk76;q4xN3!1?A0Ctete`4x1X9^BX*;D7P)^e84<_L*AC#xAh3*{ zgyvL0p175buI}L7#ZLq9{7*$0`@XKj(*fPLQG3D(`S{&r>DXKnt8GoAz{=EMQUv>Z zOpO~!1vSHq3+E67!?De2z$yViDJQmm+YMhfD{fuwJr0|jH#Cc)S3rUg7%nhzSNa4n z{ILaMq8AFsp(D${G8vj6lGO3v*1*39;MsMp#KOsJchOOkb?Q4fpim+2FOSn%H;d}w zA-geqRmtI71U%U{ni(GGx}^1Ru@pD$yGg}o zET`xTMm}hy#5i)>k7zjt{skCQZxfLu{_$Y^fdGIaE;Vk(g&%I?qMkLlMAT6y;Uow?uwoX!Kq=PXHrT1a4`N*0Pe2S%(VioeDkY`#>utMV^u5b zL2gAW`1LD0YfHSwoBipl^nx64I`?{FTGjRkJW@<~Qe|dn2s~E+U@2LmzC9fO)>(&C zP*hn~u{VHRVITg!FY>{l>x4;+)|uW$zSYJG<=yu$B>?&d*WbRp z1tDB1P)*AK;M8hRI5-I1$ncM>Q~qk;Sy2VSPAcK-m<)5K@5x)^-LuC4;AM$BgH`_d z$@>o+jgvOit#Jkq5JbjoFiYcAD$@WiL}w*a7I?o%;T#M(AI>Uj35}Ec06_4*viwPV zoB8cTj8EivcY?mpJVZl6CD5S_(%9$$3m!2#W)|xGQ+-xP7W_)B(0gs8WNXX}CQspq z=AefI@ZwKxY;u1Kl;r(~093JGYpRzFq4wI-)ElOr=9&8)l2y9vpd!`QStD50krOnf z@f|=70GP>YVeMMAWF7#B+7xKXP6}EE@+(|vo*^bK@@1`X^pN-G^yLUXjs)k-2~#Y} zD1T+#T)_vk9BCG)e|Z!Dp1qAdBQPRy`xNKh%GfU9Ml@Cs!q(-vSpk{XnvlsfP~t3G zfVk7@rdxB7I5Z!I9J}35HQg{g2$*C(ahit4~cOxHNvdcnkn8u5*awS>b^ICTBlivfh)VVhS^$90MeXtj0C47HQcn+dkzpHh9IK7F zG)GO{jhn4$Bsdk+5>^+oC5mt4cqi+K>_lTLynYk_&?+>6=irb7A9tXNIZdjAChLMc zMb@PObv>H;?IMu^v?d*6x`Xr)`Ml?9&<%8n`r=zUA9+ks)tY^L4U!L1M!G5w&!9OOkwKmef8y(?e_lKe z0MFibAbGJZFy+4jg->)QHLq6t_9Yw-oPti-nH7^Z4ZPG+yWhT)jwRcRpbkm$J0J{C zY0qKS0&vRAp)8LJ9}1lGz4orcaKA07hZ%-Fk93H~LRHtYB@EYstwTbd+Y^iz2XxwZ z{x^mH6M&2B3MYZo7Vw>5SS;s^n`^q(D%;thd(x%eV0N-F+BDGeS@(ZmuI(aaP`N8j zZA}!45`EaS40qMxcv(pdp__#m=_~1to`mgaea;<8jv4tc%1$FDl>U>*>Jx{=R=MfA zoXw9$52%3;0ssV2k&G4E4&s$DuJId&n01Ns+F@=n4oauQe%Q!?x|LM3?zh3ef6YMK zWJ#cC+>NDRaCT%e8V~^RuIHD;5Ii%?09KlB&@z~8Cr|kzz!I;<$rp6s6pri=oEbiD z`4WQwNn~GLa@HUH{(ua47yx${uSKPF4IL^_{whguQ%CrA>cz+esjC&FH`AlJJ4Dai zr1QOpm)05Pb=GOE=@I~(0k;X?BqQ2>n^c3ruyQR+$~jBkZNA(M2KAMPsKq>TQ+)Du z!e_%xwqKmUIqFq!tMlm}4|)Cv0&sWnOQt9bfO)yb(GYs3(!%B|xUUC!0E;y1FCoo^z*ie$HAyI!T--E2}4ff=VmGAfD-+y~{_ox8ey?pCw34YHiJY#M1(~+eqz|9)? z>{)GzviL7^SI^rvwnRhIG67ylw@^dyl5{}{A|;BVNx~vUPz3?%7g(;We_*I`<34b! zW*h5wq)AExLE0DykN^R8gU!pw;uZT7_-5{<`T246zyMSiukpZ-NH1%S=NUOL9a-*BJ?ZZH;h34) z2Q)Z6Pb@yX)GTx%r-gp@ML@`Nt!BZc+b4ipwKS+Pto_b(tbJ>_Y)3N))VkKXn^0mP z0ITay(XhV#*BCR%2J4L(gCacszl!kP0H_{6Avv6Xoz2HFR5}BAu23Imz60-A@(1rx z!1y@Ig(%eGmIAJ+ul)^Yw7VM-JypKYKK}~9nI*3ESlNocjiJfy5mBxac4j;9WA@Wn zgnzGRgsmdtM#jZadkUw^zO1Tm0zh?s_4}&Z%2rdGh4M(lQ4&8_;a`Wd5N1!gh?}aN zIN9Y02=Zg@w7OaB)d)~cw-ZnKD*z7FzgYbPsZ7_+V8i&lu{3Z?I z8!`HJHzg=n?-2_%Mx}gm{H*|}j?aJjvzrELK|Hw|44BiM7b=`oP7J5Ur#Gk9(1v)K zj(twZdw(%^np=5&0wyG~^c$Y@1_1Jv1ER4Q2-+K;(l$63IMnc(IMTPp@6dMXUO!qH zl%avP$ME;A{$}>S69AYC-xj*0##6aFrG_z{LRqF}sGQ<(-Sd|pRw^%Xku304?y5k; zCq!w+!@%nALEmSW+a&Pc0cgy0b0ZAc*_tsFXBRrgqLK2?7%_KU^9J!MG~%k zoB!SHe^&sii+>SM8C$E}obFN6iXopnG0*g}s=xS@)UsICZN^+JnQ~uqtGnV$2yoK6 zPjoBr)Y||o8#fLhXl0BW*(;=i@~!T@34TQoL_(wabXNQc-05(X_<{_3Lo9sodH{I;ViQ`GYM7LpzBt{z(7>Gr0Kl>kTGaqP=phS7)P!eNI*}m2!-SJ1D9d0W zfO;7u#iCh9x}bjH#~hzfxU~U*(8v5O06?*aimed*_zPahoi8HqdodSoDgYJvl>(8{+dH_;rlBR+}(t(VIpWXMa6`s=1ohC6|6b0yT(77ft= z!++M*arGSmfK)gg`6D`kK$j)W0sxYY9Y`KqaokanM?_Lk=T=S;1}!JDD7VEaoFOBO zL_KcyRTJa}^=OodGcwV{@plY2K@FlgHr;s_`>>&9b>cIwg&%G6=8k^1_kVW)pe*#G z5MF-XMQ=RQzL5OpfwO?LM4A<2=d@ySfG>%x{sB@v7zM=`{KmBw5L zHm6YT*xM=*_k}cOfBbUS=f4L4RdqFzWzT$(c2|?Vwq2s`7t-1Xof6IFLZE6;ap5W6 zN_WCI!~;S|&7Ed!Fcw03q*;9I0WQkD2?=l?!kW4P^6$A#+(b$fwEb{}&9UqGIE&FC z#KIq!cYlZ-eow9d01DYjL_t*h0C4$Ml0)|4Q;Bnd6N7~afToFI$xhT`TmmqurSrFp z7KscS1oRY&p?o=OZvZGWG5qZx?8Bu#w7Si_*W*Qnn!0A8sO1U(NhB14N^2;pc;?pB#CT)IC&rOnpA_t7TW!GZz|N0I0`uf-lkTR3d2H2XF?mB5BRNl1AwGp zgS3`9w2dB@nchv178W8ebWf7Hip-vHD9zmgU!2xnsiocso;lWT+9 zMIBM>AM|4WdP89jVf%ez+2Z{w9orLc-VFFqenvL{)aKaK<$KX*jJdjV#gAplXF z$TiUmzhDJkp8C~!_2U3QlH}v*$;o>h&ER%XoR>Q^nX@HLv z*5_*X765B$B)Z_4J20u`jOYMu^h#>t>?j*r zXuDF%-!Jd^`R@%tb$s#fcc*WjTBOfsXiCDT!l+$tnee_SYy{7py?gUMk-7)~)&^AT z-3kG*75r?e@^rT`+!UG}l`8IXKwrc`+YXRc&F&Ze4+8*%!Ec}6yqKoK{^{AMVN<)4 z%!MaTbinAL765!cJ^ApwQ*r3y{XBr+guVchzYqBU;uTsyF<4mgrH6;a<{`Yo#R4yXjm(0Nb|~yC02DqV zw8JHLG#P4~MCbbVjy;kR7U1`0?HL*T@EypHZv_DK$30t89YKqP898jTajQA1sn7z4 z1OR{z2LM+~oWA)uXmeWZ76207YaDEm|=&Por0t5Ao3- z1^~DZul{sY2w~83jiz&b9B#PjVpVZM#rsU z((m!Y2bv>8V>mvm=*9~i0(!UVdNHBp768#L%putcIhcPv$}H;Wu6c;o$zb3j6~x>& z8WEEx=haUK0F;FV^3pzMh;yQ)dHd16R?kSL284xzUt^dV*9d^j8Id`?sRPuGt(|SA zqj40fF{2bmbI4*RatWt`de|L0@w4OVCjLb#oz6;FjSJdF_T#l)GctO&_GHORaF zY46;56UU-BKD>hDE8@b9WnVjmLFQo$!4|`R=bONBtt`M|74$g9{=-qKp($>+AN)_@iK1%ARAD* z8hHTeLJd6>$`~@1OKmVruYdoX=V#}3!hZ|^cv=`b&>@CU$j`$Hdj2R@)F|pIbW$7# zsZ>$_EUYC`j%ROPfsPR^z(JO$A%H5{b)KjdaE2sR_xCT~i(3o-aR9)6F+ZWafiNLd z(eE%?p9QZC>Y0!)H$q#;#tU;@?6%}8i|w?Qd{^@`nu=`Yyeyskq6RP;Zff4h8qB9@X^qdcje#y zyVd8~6>xKFWIj1AEBZ=iMxc;kkLbE6G_8jZ{;lp1KDtYNes2BtY^ zA18l>!-IajW(N*{P45QLM>s%M_QEeiJoB_K%(c$t;GY08J zcpAU|y2bDx8vqd1B}9kRL$gKEC2R~ZBj|@A&lJF2tV}wM=NQn19!F+OX&o&Lbp%kS z3b_OugeE$hrv?=JAN<=7JIUi5vKi4 zqt*d?B-av4f@OmW08&83Dz4J0Sz_Qf^i*6ZzXI3P%;l%QX6%RnuqC8}2@{;)h>I~m zbx#X(zu!VRfJTJ$S$1*1i60vk;Zd%5`dBo@^uPzHc%8tGRA%xpWk&^IcAJNcr_P^h z#SL=Eu11KRy7w zG{SaBAX6Eo6QR+=0lD8oHEFI-$`OFMVJ0)^%5iQOLM14tPjdxbRbc4K;lvUCV+X)X zBTR@dh=GGZ#{``RxN{GLniLvE96$}-sWPylujnm`)vzk__oRiUP*R6JE#kY#I05j} z@TUadp;0jv;+24EemJecm%q#aMMIiQ%n}ijf6%*V#XjLDfKS_AE^U@H*a%8!KW6Mm z0kF~Hz6jncwuN1hMvuz*3k~G{t_TDzyHb+Hp-l#v%~pWnmzdxhzq5-g`3oQk1C}gi06pH}PIz+yHoK6r?oj zjDr6)HMLVOO+~mB$Z{AI@#^^e)kPP^oykn9tusmF`0qoTVoA?syrP80Ov~4CE1Jk z!{1H;u+hpbqOe|n0f3jmRs>*DL6@v6L-9VGn)230e+L5qG-W8Px9w+NAoyYg+TSGW zs!)bZNR$OkpX}vN!n_{2C3EC0H`Ou)rh?f0Hf<=&uXF- znpugQ?=0=@5OdZ1JPa0d)3|{Tv7YHhVB= zX&>3op#Wgh<;`rr_rGTVY{d5cd&l1R?-zhy0Db{@+rPg74$zmFF)_Ud00000NkvXX Hu0mjf=(=kZ literal 0 HcmV?d00001