Add custom servers base and direct connect

This commit is contained in:
rtm516 2020-06-16 18:30:01 +01:00
parent fc3b792e84
commit 3679fd5034
13 changed files with 194 additions and 37 deletions

View file

@ -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<Server> 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;
}
}

View file

@ -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();

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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 -> {

View file

@ -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,13 +23,23 @@ public class UIHandler {
* @param servers A list of {@link Server} objects
* @return A {@link SimpleFormWindow} object
*/
public static FormWindow getServerListFormPacket(List<Server> servers) {
public static FormWindow getServerList(List<Server> servers) {
SimpleFormWindow window = new SimpleFormWindow("Servers", "");
// Add a button for each server with the server icon as the image
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) {
switch (data.getClickedButtonId()) {
case 0:
player.sendWindow(FormID.DIRECT_CONNECT, getDirectConnect());
break;
case 1:
break;
default:
// Get the server
Server server = player.getServers().get(data.getClickedButtonId());
List<Server> 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))));
}
}

View file

@ -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 {

View file

@ -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;

View file

@ -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();
}
}

View file

@ -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<Server> servers) {
}
public static List<Server> loadServers(Player player) {
List<Server> 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;
}
}
}

View file

@ -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);

View file

@ -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