diff --git a/src/main/java/org/geysermc/multi/GeyserMultiConfig.java b/src/main/java/org/geysermc/multi/GeyserMultiConfig.java index 0d4a381..e0ee894 100644 --- a/src/main/java/org/geysermc/multi/GeyserMultiConfig.java +++ b/src/main/java/org/geysermc/multi/GeyserMultiConfig.java @@ -4,11 +4,14 @@ package org.geysermc.multi; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; +import org.geysermc.multi.utils.PlayerStorageManager; +import org.geysermc.multi.utils.Server; + +import java.util.List; @Getter @JsonIgnoreProperties(ignoreUnknown = true) public class GeyserMultiConfig { - private GeyserConfigSection geyser; private int port; @@ -20,11 +23,29 @@ public class GeyserMultiConfig { @JsonProperty("debug-mode") private boolean debugMode; + private GeyserConfigSection geyser; + + private List servers; + + @JsonProperty("custom-servers") + private CustomServersSection customServers; + @Getter public static class GeyserConfigSection { + private int port; @JsonProperty("debug-mode") private boolean debugMode; } + + @Getter + public static class CustomServersSection { + + private boolean enabled; + private int max; + + @JsonProperty("storage-type") + private PlayerStorageManager.StorageType storageType; + } } diff --git a/src/main/java/org/geysermc/multi/MasterServer.java b/src/main/java/org/geysermc/multi/MasterServer.java index 33fbdf5..a23a7fc 100644 --- a/src/main/java/org/geysermc/multi/MasterServer.java +++ b/src/main/java/org/geysermc/multi/MasterServer.java @@ -7,11 +7,15 @@ import org.geysermc.connector.utils.FileUtils; import org.geysermc.multi.proxy.GeyserProxyBootstrap; import org.geysermc.multi.utils.Logger; import org.geysermc.multi.utils.Player; +import org.geysermc.multi.utils.PlayerStorageManager; import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; -import java.util.*; +import java.util.HashMap; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -45,6 +49,8 @@ public class MasterServer { private GeyserMultiConfig geyserMultiConfig; public MasterServer() { + this.instance = this; + logger = new Logger(); try { @@ -57,7 +63,6 @@ public class MasterServer { logger.setDebug(geyserMultiConfig.isDebugMode()); - this.instance = this; this.generalThreadPool = Executors.newScheduledThreadPool(32); // Start a timer to keep the thread running @@ -65,6 +70,8 @@ public class MasterServer { TimerTask task = new TimerTask() { public void run() { } }; timer.scheduleAtFixedRate(task, 0L, 1000L); + PlayerStorageManager.setupStorage(); + start(geyserMultiConfig.getPort()); logger.start(); diff --git a/src/main/java/org/geysermc/multi/PacketHandler.java b/src/main/java/org/geysermc/multi/PacketHandler.java index 3d31e85..3285410 100644 --- a/src/main/java/org/geysermc/multi/PacketHandler.java +++ b/src/main/java/org/geysermc/multi/PacketHandler.java @@ -12,6 +12,7 @@ import com.nukkitx.protocol.bedrock.handler.BedrockPacketHandler; import com.nukkitx.protocol.bedrock.packet.*; import com.nukkitx.protocol.bedrock.util.EncryptionUtils; import org.geysermc.common.window.FormWindow; +import org.geysermc.common.window.response.CustomFormResponse; import org.geysermc.common.window.response.SimpleFormResponse; import org.geysermc.multi.ui.FormID; import org.geysermc.multi.ui.UIHandler; @@ -130,7 +131,7 @@ public class PacketHandler implements BedrockPacketHandler { public boolean handle(ResourcePackClientResponsePacket packet) { switch (packet.getStatus()) { case COMPLETED: - masterServer.getLogger().info("Logged in " + player.getDisplayName() + " (" + player.getXuid() + ")"); + masterServer.getLogger().info("Logged in " + player.getDisplayName() + " (" + player.getXuid() + ", " + player.getIdentity() + ")"); player.sendStartGame(); break; case HAVE_ALL_PACKS: @@ -152,7 +153,7 @@ public class PacketHandler implements BedrockPacketHandler { public boolean handle(SetLocalPlayerAsInitializedPacket packet) { masterServer.getLogger().debug("Player initialized: " + player.getDisplayName()); - player.sendWindow(FormID.MAIN, UIHandler.getServerListFormPacket(player.getServers()));; + player.sendWindow(FormID.MAIN, UIHandler.getServerList(player.getServers()));; return false; } @@ -169,7 +170,7 @@ public class PacketHandler implements BedrockPacketHandler { window.setResponse(packet.getFormData().trim()); // Resend the form if they closed it - if (window.getResponse() == null) { + if (window.getResponse() == null && id != FormID.DIRECT_CONNECT) { player.resendWindow(); } else { // Send the response to the correct response function @@ -178,6 +179,10 @@ public class PacketHandler implements BedrockPacketHandler { UIHandler.handleServerListResponse(player, (SimpleFormResponse) window.getResponse()); break; + case DIRECT_CONNECT: + UIHandler.handleDirectConnectResponse(player, (CustomFormResponse) window.getResponse()); + break; + default: player.resendWindow(); break; diff --git a/src/main/java/org/geysermc/multi/proxy/GeyserProxyBootstrap.java b/src/main/java/org/geysermc/multi/proxy/GeyserProxyBootstrap.java index a4b1676..c543ba3 100644 --- a/src/main/java/org/geysermc/multi/proxy/GeyserProxyBootstrap.java +++ b/src/main/java/org/geysermc/multi/proxy/GeyserProxyBootstrap.java @@ -2,21 +2,20 @@ package org.geysermc.multi.proxy; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import org.apache.logging.log4j.core.util.IOUtils; import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; -import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.command.CommandManager; -import org.geysermc.connector.ping.IGeyserPingPassthrough; +import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; -import org.geysermc.connector.utils.FileUtils; +import org.geysermc.connector.ping.IGeyserPingPassthrough; import org.geysermc.multi.GeyserMultiConfig; import org.geysermc.multi.MasterServer; -import org.geysermc.multi.utils.Logger; -import org.geysermc.multi.utils.Server; -import java.io.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.stream.Collectors; diff --git a/src/main/java/org/geysermc/multi/proxy/GeyserProxyConfiguration.java b/src/main/java/org/geysermc/multi/proxy/GeyserProxyConfiguration.java index 0eb7e70..6c0aa5f 100644 --- a/src/main/java/org/geysermc/multi/proxy/GeyserProxyConfiguration.java +++ b/src/main/java/org/geysermc/multi/proxy/GeyserProxyConfiguration.java @@ -3,7 +3,6 @@ package org.geysermc.multi.proxy; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; -import lombok.Setter; import org.geysermc.connector.configuration.GeyserJacksonConfiguration; import java.nio.file.Path; diff --git a/src/main/java/org/geysermc/multi/proxy/ProxyConnectorServerEventHandler.java b/src/main/java/org/geysermc/multi/proxy/ProxyConnectorServerEventHandler.java index f2dfec4..23bca27 100644 --- a/src/main/java/org/geysermc/multi/proxy/ProxyConnectorServerEventHandler.java +++ b/src/main/java/org/geysermc/multi/proxy/ProxyConnectorServerEventHandler.java @@ -18,6 +18,8 @@ public class ProxyConnectorServerEventHandler extends ConnectorServerEventHandle @Override public void onSessionCreation(BedrockServerSession bedrockServerSession) { + bedrockServerSession.setPacketCodec(GeyserConnector.BEDROCK_PACKET_CODEC); // Only done here as it allows us to disconnect the player + Player player = MasterServer.getInstance().getPlayers().get(bedrockServerSession.getAddress()); if (player == null) { bedrockServerSession.disconnect("Please connect to the master server and pick a server first!"); @@ -28,7 +30,6 @@ public class ProxyConnectorServerEventHandler extends ConnectorServerEventHandle // This doesn't clean up the old packet handler, so may cause a memory leak? bedrockServerSession.setPacketHandler(new UpstreamPacketHandler(connector, new GeyserProxySession(connector, bedrockServerSession))); - bedrockServerSession.setPacketCodec(GeyserConnector.BEDROCK_PACKET_CODEC); // Only done here as it sometimes gets cleared // Add another disconnect handler to remove the player on final disconnect bedrockServerSession.addDisconnectHandler(disconnectReason -> { diff --git a/src/main/java/org/geysermc/multi/ui/UIHandler.java b/src/main/java/org/geysermc/multi/ui/UIHandler.java index f4c621d..83d93c6 100644 --- a/src/main/java/org/geysermc/multi/ui/UIHandler.java +++ b/src/main/java/org/geysermc/multi/ui/UIHandler.java @@ -1,14 +1,19 @@ package org.geysermc.multi.ui; +import org.geysermc.common.window.CustomFormBuilder; +import org.geysermc.common.window.CustomFormWindow; import org.geysermc.common.window.FormWindow; import org.geysermc.common.window.SimpleFormWindow; import org.geysermc.common.window.button.FormButton; import org.geysermc.common.window.button.FormImage; +import org.geysermc.common.window.component.InputComponent; +import org.geysermc.common.window.response.CustomFormResponse; import org.geysermc.common.window.response.SimpleFormResponse; import org.geysermc.multi.MasterServer; import org.geysermc.multi.utils.Player; import org.geysermc.multi.utils.Server; +import java.util.ArrayList; import java.util.List; public class UIHandler { @@ -18,14 +23,24 @@ public class UIHandler { * @param servers A list of {@link Server} objects * @return A {@link SimpleFormWindow} object */ - public static FormWindow getServerListFormPacket(List servers) { + public static FormWindow getServerList(List servers) { SimpleFormWindow window = new SimpleFormWindow("Servers", ""); - // Add a button for each server with the server icon as the image - for (Server server : servers) { + window.getButtons().add(new FormButton("Direct connect")); + window.getButtons().add(new FormButton("Edit servers")); + + // Add a button for each global server + for (Server server : MasterServer.getInstance().getGeyserMultiConfig().getServers()) { window.getButtons().add(new FormButton(server.toString(), new FormImage(FormImage.FormImageType.URL, "https://eu.mc-api.net/v3/server/favicon/" + server.getAddress() + ":" + server.getPort() + ".png"))); } + // Add a button for each personal server + if (MasterServer.getInstance().getGeyserMultiConfig().getCustomServers().isEnabled()) { + for (Server server : servers) { + window.getButtons().add(new FormButton(server.toString(), new FormImage(FormImage.FormImageType.URL, "https://eu.mc-api.net/v3/server/favicon/" + server.getAddress() + ":" + server.getPort() + ".png"))); + } + } + return window; } @@ -40,6 +55,19 @@ public class UIHandler { return window; } + /** + * Create a direct connect form + * + * @return A {@link SimpleFormWindow} object + */ + public static FormWindow getDirectConnect() { + CustomFormWindow window = new CustomFormBuilder("Direct Connect") + .addComponent(new InputComponent("IP", "play.cubecraft.net", "")) + .addComponent(new InputComponent("Port", "25565", "25565")) + .build(); + return window; + } + /** * Handle the server list response * @@ -47,18 +75,30 @@ public class UIHandler { * @param data The form response data */ public static void handleServerListResponse(Player player, SimpleFormResponse data) { - // Get the server - Server server = player.getServers().get(data.getClickedButtonId()); + switch (data.getClickedButtonId()) { + case 0: + player.sendWindow(FormID.DIRECT_CONNECT, getDirectConnect()); + break; + case 1: + break; + default: + // Get the server + List servers = new ArrayList<>(MasterServer.getInstance().getGeyserMultiConfig().getServers()); + servers.addAll(player.getServers()); + Server server = servers.get(data.getClickedButtonId() - 2); - // Tell the user we are connecting them - // this wont show up in alot of cases as the client connects quite quickly - player.sendWindow(FormID.CONNECTING, getWaitingScreen(server)); + player.sendToServer(server); + break; + } + } - // Create the Geyser instance if its not already running - MasterServer.getInstance().createGeyserProxy(); + public static void handleDirectConnectResponse(Player player, CustomFormResponse data) { + // Take them back to the main menu if they close the direct connect window + if (data == null) { + player.sendWindow(FormID.MAIN, getServerList(player.getServers()));; + return; + } - // Send the user over to the server - player.setCurrentServer(server); - player.connectToProxy(); + player.sendToServer(new Server(data.getInputResponses().get(0), Integer.valueOf(data.getInputResponses().get(1)))); } } diff --git a/src/main/java/org/geysermc/multi/utils/Logger.java b/src/main/java/org/geysermc/multi/utils/Logger.java index 88f3b6c..d6356de 100644 --- a/src/main/java/org/geysermc/multi/utils/Logger.java +++ b/src/main/java/org/geysermc/multi/utils/Logger.java @@ -3,14 +3,10 @@ package org.geysermc.multi.utils; import lombok.extern.log4j.Log4j2; import net.minecrell.terminalconsole.SimpleTerminalConsole; import org.apache.logging.log4j.core.config.Configurator; -import org.apache.logging.log4j.message.Message; import org.geysermc.common.ChatColor; import org.geysermc.connector.GeyserLogger; import org.geysermc.multi.MasterServer; -import java.io.IOException; -import java.util.logging.Level; - @Log4j2 public class Logger extends SimpleTerminalConsole implements GeyserLogger { diff --git a/src/main/java/org/geysermc/multi/utils/PalleteManger.java b/src/main/java/org/geysermc/multi/utils/PalleteManger.java index 3ff35f7..ed5f86d 100644 --- a/src/main/java/org/geysermc/multi/utils/PalleteManger.java +++ b/src/main/java/org/geysermc/multi/utils/PalleteManger.java @@ -6,7 +6,6 @@ import com.nukkitx.nbt.stream.NBTInputStream; import com.nukkitx.nbt.stream.NBTOutputStream; import com.nukkitx.nbt.tag.CompoundTag; import com.nukkitx.nbt.tag.ListTag; -import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.utils.FileUtils; import java.io.ByteArrayOutputStream; diff --git a/src/main/java/org/geysermc/multi/utils/Player.java b/src/main/java/org/geysermc/multi/utils/Player.java index 25125e8..bfea51f 100644 --- a/src/main/java/org/geysermc/multi/utils/Player.java +++ b/src/main/java/org/geysermc/multi/utils/Player.java @@ -5,13 +5,16 @@ import com.nukkitx.math.vector.Vector2f; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.BedrockServerSession; -import com.nukkitx.protocol.bedrock.data.*; +import com.nukkitx.protocol.bedrock.data.GamePublishSetting; +import com.nukkitx.protocol.bedrock.data.GameRuleData; +import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.packet.*; import lombok.Getter; import lombok.Setter; import org.geysermc.common.window.FormWindow; import org.geysermc.multi.MasterServer; import org.geysermc.multi.ui.FormID; +import org.geysermc.multi.ui.UIHandler; import java.util.ArrayList; import java.util.List; @@ -42,8 +45,9 @@ public class Player { this.session = session; // Should fetch the servers from some form of db - servers.add(new Server("play.cubecraft.net")); - servers.add(new Server("81.174.164.211", 25580)); + if (MasterServer.getInstance().getGeyserMultiConfig().getCustomServers().isEnabled()) { + servers.addAll(PlayerStorageManager.loadServers(this)); + } } /** @@ -155,4 +159,17 @@ public class Player { transferPacket.setPort(MasterServer.getInstance().getGeyserProxy().getGeyserConfig().getBedrock().getPort()); session.sendPacket(transferPacket); } + + public void sendToServer(Server server) { + // Tell the user we are connecting them + // this wont show up in alot of cases as the client connects quite quickly + sendWindow(FormID.CONNECTING, UIHandler.getWaitingScreen(server)); + + // Create the Geyser instance if its not already running + MasterServer.getInstance().createGeyserProxy(); + + // Send the user over to the server + setCurrentServer(server); + connectToProxy(); + } } diff --git a/src/main/java/org/geysermc/multi/utils/PlayerStorageManager.java b/src/main/java/org/geysermc/multi/utils/PlayerStorageManager.java new file mode 100644 index 0000000..1b34b19 --- /dev/null +++ b/src/main/java/org/geysermc/multi/utils/PlayerStorageManager.java @@ -0,0 +1,51 @@ +package org.geysermc.multi.utils; + +import com.fasterxml.jackson.annotation.JsonValue; +import org.geysermc.multi.MasterServer; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class PlayerStorageManager { + + public static void setupStorage() { + if (!MasterServer.getInstance().getGeyserMultiConfig().getCustomServers().isEnabled()) { + return; + } + + switch (MasterServer.getInstance().getGeyserMultiConfig().getCustomServers().getStorageType()) { + case JSON: + File playersFolder = new File("players/"); + if (!playersFolder.exists()) { + playersFolder.mkdirs(); + } + break; + case SQLITE: + throw new NotImplementedException(); + } + } + + public static void saveServers(Player player, List servers) { + + } + + public static List loadServers(Player player) { + List servers = new ArrayList<>(); + servers.add(new Server("81.174.164.211", 25580)); + return servers; + } + + public enum StorageType { + JSON("json"), + SQLITE("sqlite"); + + @JsonValue + private String name; + + StorageType(String name) { + this.name = name; + } + } +} diff --git a/src/main/java/org/geysermc/multi/utils/Server.java b/src/main/java/org/geysermc/multi/utils/Server.java index 9add4a2..2818956 100644 --- a/src/main/java/org/geysermc/multi/utils/Server.java +++ b/src/main/java/org/geysermc/multi/utils/Server.java @@ -7,7 +7,12 @@ import lombok.Getter; @AllArgsConstructor public class Server { private String address; - private int port; + private int port = 25565; + + // Added so we can load from config + public Server() { + super(); + } public Server(String address) { this(address, 25565); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 60134c3..de7829f 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -21,3 +21,20 @@ geyser: # If debug messages should be sent through console, has to be enabled in both places to work debug-mode: false + +# A global list of servers sent to all clients +servers: + - address: "play.cubecraft.net" + - address: "127.0.0.1" + port: 25565 + +custom-servers: + # Should custom servers be enabled for users + enabled: false + + # Max amount of custom servers per user + max: 10 + + # Storage engine for custom servers + # Can be json, sqlite + storage-type: json