Initial commit

Added base server and a static server list
This commit is contained in:
rtm516 2020-06-14 02:07:00 +01:00
commit 4a3119f730
14 changed files with 900 additions and 0 deletions

222
.gitignore vendored Normal file
View file

@ -0,0 +1,222 @@
# Created by https://www.gitignore.io/api/git,java,maven,eclipse,netbeans,jetbrains+all
# Edit at https://www.gitignore.io/?templates=git,java,maven,eclipse,netbeans,jetbrains+all
### Eclipse ###
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
.recommenders
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# PyDev specific (Python IDE for Eclipse)
*.pydevproject
# CDT-specific (C/C++ Development Tooling)
.cproject
# CDT- autotools
.autotools
# Java annotation processor (APT)
.factorypath
# PDT-specific (PHP Development Tools)
.buildpath
# sbteclipse plugin
.target
# Tern plugin
.tern-project
# TeXlipse plugin
.texlipse
# STS (Spring Tool Suite)
.springBeans
# Code Recommenders
.recommenders/
# Annotation Processing
.apt_generated/
# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet
### Eclipse Patch ###
# Eclipse Core
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath
# Annotation Processing
.apt_generated
.sts4-cache/
### Git ###
# Created by git for backups. To disable backups in Git:
# $ git config --global mergetool.keepBackup false
*.orig
# Created by git when using merge tools for conflicts
*.BACKUP.*
*.BASE.*
*.LOCAL.*
*.REMOTE.*
*_BACKUP_*.txt
*_BASE_*.txt
*_LOCAL_*.txt
*_REMOTE_*.txt
### Java ###
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
### JetBrains+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### JetBrains+all Patch ###
# Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
.idea/
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
*.iml
modules.xml
.idea/misc.xml
*.ipr
# Sonarlint plugin
.idea/sonarlint
### Maven ###
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
### NetBeans ###
**/nbproject/private/
**/nbproject/Makefile-*.mk
**/nbproject/Package-*.bash
build/
nbbuild/
dist/
nbdist/
.nb-gradle/
# End of https://www.gitignore.io/api/git,java,maven,eclipse,netbeans,jetbrains+all

79
pom.xml Normal file
View file

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.geysermc</groupId>
<artifactId>geyser-multi</artifactId>
<version>1.0-SNAPSHOT</version>
<name>GeyserMulti</name>
<description>Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers via a masterServer list.</description>
<url>https://geysermc.org</url>
<properties>
<outputName>GeyserMulti</outputName>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<repository>
<id>nukkitx-release-repo</id>
<url>https://repo.nukkitx.com/maven-releases/</url>
</repository>
<repository>
<id>nukkitx-snapshot-repo</id>
<url>https://repo.nukkitx.com/maven-snapshots/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>connector</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.nukkitx.protocol</groupId>
<artifactId>bedrock-v390</artifactId>
<version>2.5.6</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.minecrell</groupId>
<artifactId>terminalconsoleappender</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>org.geysermc.multi.GeyserMulti</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,16 @@
package org.geysermc.multi;
import com.nukkitx.protocol.bedrock.BedrockPacketCodec;
import com.nukkitx.protocol.bedrock.v390.Bedrock_v390;
public class GeyserMulti {
public static final BedrockPacketCodec CODEC = Bedrock_v390.V390_CODEC;
private static MasterServer masterServer;
public static void main(String[] args) {
new MasterServer();
}
}

View file

@ -0,0 +1,75 @@
package org.geysermc.multi;
import lombok.extern.log4j.Log4j2;
import net.minecrell.terminalconsole.SimpleTerminalConsole;
import org.apache.logging.log4j.core.config.Configurator;
import org.geysermc.common.ChatColor;
@Log4j2
public class Logger extends SimpleTerminalConsole {
private boolean colored = true;
@Override
protected boolean isRunning() {
return !MasterServer.getInstance().isShuttingDown();
}
@Override
protected void runCommand(String line) {
// Dont do anything rn
}
@Override
protected void shutdown() {
MasterServer.getInstance().shutdown();
}
public void severe(String message) {
log.fatal(printConsole(ChatColor.DARK_RED + message, colored));
}
public void severe(String message, Throwable error) {
log.fatal(printConsole(ChatColor.DARK_RED + message, colored), error);
}
public void error(String message) {
log.error(printConsole(ChatColor.RED + message, colored));
}
public void error(String message, Throwable error) {
log.error(printConsole(ChatColor.RED + message, colored), error);
}
public void warning(String message) {
log.warn(printConsole(ChatColor.YELLOW + message, colored));
}
public void info(String message) {
log.info(printConsole(ChatColor.WHITE + message, colored));
}
public void debug(String message) {
log.debug(printConsole(ChatColor.GRAY + message, colored));
}
public static String printConsole(String message, boolean colors) {
return colors ? ChatColor.toANSI(message + ChatColor.RESET) : ChatColor.stripColors(message + ChatColor.RESET);
}
public void setDebug(boolean debug) {
Configurator.setLevel(log.getName(), debug ? org.apache.logging.log4j.Level.DEBUG : log.getLevel());
}
public String getName() {
return "CONSOLE";
}
public void sendMessage(String message) {
info(message);
}
public boolean isConsole() {
return true;
}
}

View file

@ -0,0 +1,97 @@
package org.geysermc.multi;
import com.nukkitx.protocol.bedrock.BedrockPong;
import com.nukkitx.protocol.bedrock.BedrockServer;
import com.nukkitx.protocol.bedrock.BedrockServerEventHandler;
import com.nukkitx.protocol.bedrock.BedrockServerSession;
import lombok.Getter;
import java.net.InetSocketAddress;
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;
public class MasterServer {
private final Timer timer;
private BedrockServer bdServer;
private BedrockPong bdPong;
@Getter
private boolean shuttingDown = false;
@Getter
private static MasterServer instance;
@Getter
private final Logger logger;
@Getter
private final ScheduledExecutorService generalThreadPool;
@Getter
private final Map<InetSocketAddress, Player> players = new HashMap<>();
public MasterServer() {
logger = new Logger();
logger.setDebug(true);
this.instance = this;
this.generalThreadPool = Executors.newScheduledThreadPool(32);
// Start a timer to keep the thread running
timer = new Timer();
TimerTask task = new TimerTask() { public void run() { } };
timer.scheduleAtFixedRate(task, 0L, 1000L);
start(19132);
logger.start();
}
private void start(int port) {
logger.info("Starting...");
InetSocketAddress bindAddress = new InetSocketAddress("0.0.0.0", port);
bdServer = new BedrockServer(bindAddress);
bdPong = new BedrockPong();
bdPong.setEdition("MCPE");
bdPong.setMotd("My Server");
bdPong.setPlayerCount(0);
bdPong.setMaximumPlayerCount(1337);
bdPong.setGameType("Survival");
bdPong.setIpv4Port(port);
bdPong.setProtocolVersion(GeyserMulti.CODEC.getProtocolVersion());
bdPong.setVersion(GeyserMulti.CODEC.getMinecraftVersion());
bdServer.setHandler(new BedrockServerEventHandler() {
@Override
public boolean onConnectionRequest(InetSocketAddress address) {
return true; // Connection will be accepted
}
@Override
public BedrockPong onQuery(InetSocketAddress address) {
return bdPong;
}
@Override
public void onSessionCreation(BedrockServerSession session) {
session.setPacketHandler(new PacketHandler(session, instance));
}
});
// Start server up
bdServer.bind().join();
logger.info("Server started on 0.0.0.0:" + port);
}
public void shutdown() {
shuttingDown = true;
generalThreadPool.shutdown();
}
}

View file

@ -0,0 +1,154 @@
package org.geysermc.multi;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.crypto.factories.DefaultJWSVerifierFactory;
import com.nukkitx.network.util.DisconnectReason;
import com.nukkitx.protocol.bedrock.BedrockServerSession;
import com.nukkitx.protocol.bedrock.handler.BedrockPacketHandler;
import com.nukkitx.protocol.bedrock.packet.*;
import com.nukkitx.protocol.bedrock.util.EncryptionUtils;
import org.geysermc.multi.UI.UIHandler;
import java.io.IOException;
import java.security.interfaces.ECPublicKey;
import java.util.ArrayList;
import java.util.List;
public class PacketHandler implements BedrockPacketHandler {
private BedrockServerSession session;
private MasterServer masterServer;
private Player player;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);;
public PacketHandler(BedrockServerSession session, MasterServer masterServer) {
this.session = session;
this.masterServer = masterServer;
session.addDisconnectHandler((reason) -> disconnect(reason));
}
public void disconnect(DisconnectReason reason) {
if (player != null) {
masterServer.getLogger().info(player.getDisplayName() + " has disconnected (" + reason + ")");
masterServer.getPlayers().remove(session.getAddress());
}
}
@Override
public boolean handle(LoginPacket packet) {
masterServer.getLogger().debug("Login: " + packet.toString());
// Check the protocol version is correct
int protocol = packet.getProtocolVersion();
if (protocol != GeyserMulti.CODEC.getProtocolVersion()) {
PlayStatusPacket status = new PlayStatusPacket();
if (protocol > GeyserMulti.CODEC.getProtocolVersion()) {
status.setStatus(PlayStatusPacket.Status.FAILED_SERVER);
} else {
status.setStatus(PlayStatusPacket.Status.FAILED_CLIENT);
}
session.sendPacket(status);
}
// Set the session codec
session.setPacketCodec(GeyserMulti.CODEC);
JsonNode rawChainData;
try {
rawChainData = OBJECT_MAPPER.readTree(packet.getChainData().toByteArray());
} catch (IOException e) {
throw new AssertionError("Unable to read chain data!");
}
JsonNode chainData = rawChainData.get("chain");
if (chainData.getNodeType() != JsonNodeType.ARRAY) {
throw new AssertionError("Invalid chain data!");
}
try {
// Parse the signed jws object
JWSObject jwsObject;
jwsObject = JWSObject.parse(chainData.get(chainData.size() - 1).asText());
JsonNode payload = OBJECT_MAPPER.readTree(jwsObject.getPayload().toBytes());
if (payload.get("identityPublicKey").getNodeType() != JsonNodeType.STRING) {
throw new AssertionError("Missing identity public key!");
}
ECPublicKey identityPublicKey = EncryptionUtils.generateKey(payload.get("identityPublicKey").textValue());
JWSObject skinData = JWSObject.parse(packet.getSkinData().toString());
if (skinData.verify(new DefaultJWSVerifierFactory().createJWSVerifier(skinData.getHeader(), identityPublicKey))) {
if (payload.get("extraData").getNodeType() != JsonNodeType.OBJECT) {
throw new AssertionError("Missing client data");
}
JsonNode extraData = payload.get("extraData");
player = new Player(extraData, session);
masterServer.getPlayers().put(session.getAddress(), player);
// Tell the client we have logged in successfully
PlayStatusPacket playStatusPacket = new PlayStatusPacket();
playStatusPacket.setStatus(PlayStatusPacket.Status.LOGIN_SUCCESS);
session.sendPacket(playStatusPacket);
// Tell the client there are no resourcepacks
ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket();
session.sendPacket(resourcePacksInfo);
} else {
throw new AssertionError("Invalid identity public key!");
}
} catch (Exception e) {
// Disconnect the client
session.disconnect("disconnectionScreen.internalError.cantConnect");
throw new AssertionError("Failed to login", e);
}
return false;
}
@Override
public boolean handle(ResourcePackClientResponsePacket packet) {
switch (packet.getStatus()) {
case COMPLETED:
masterServer.getLogger().info("Logged in " + player.getDisplayName() + " (" + player.getXuid() + ")");
player.sendStartGame();
break;
case HAVE_ALL_PACKS:
ResourcePackStackPacket stack = new ResourcePackStackPacket();
stack.setExperimental(false);
stack.setForcedToAccept(false);
stack.setGameVersion("*");
session.sendPacket(stack);
break;
default:
session.disconnect("disconnectionScreen.resourcePack");
break;
}
return true;
}
@Override
public boolean handle(SetLocalPlayerAsInitializedPacket packet) {
masterServer.getLogger().debug("Player initialized: " + player.getDisplayName());
player.sendServerList();
/*TransferPacket transferPacket = new TransferPacket();
transferPacket.setAddress("81.174.164.211");
transferPacket.setPort(27040);
session.sendPacket(transferPacket);*/
return false;
}
}

View file

@ -0,0 +1,62 @@
package org.geysermc.multi;
import com.nukkitx.nbt.CompoundTagBuilder;
import com.nukkitx.nbt.NbtUtils;
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;
import java.io.IOException;
import java.io.InputStream;
/**
* This class is mostly copied from core Geyser
*/
public class PalleteManger {
public static final ListTag<CompoundTag> BLOCK_PALLETE;
public static final CompoundTag BIOMES_PALLETE;
public static final byte[] EMPTY_LEVEL_CHUNK_DATA;
private static final com.nukkitx.nbt.tag.CompoundTag EMPTY_TAG = CompoundTagBuilder.builder().buildRootTag();
static {
/* Load block palette */
InputStream stream = FileUtils.getResource("runtime_block_states.dat");
try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(stream)) {
BLOCK_PALLETE = (ListTag<CompoundTag>) nbtInputStream.readTag();
} catch (Exception e) {
throw new AssertionError("Unable to get blocks from runtime block states", e);
}
/* Load biomes */
stream = FileUtils.getResource("biome_definitions.dat");
try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(stream)){
BIOMES_PALLETE = (CompoundTag) nbtInputStream.readTag();
} catch (Exception e) {
throw new AssertionError("Failed to get biomes from biome definitions", e);
}
/* Create empty chunk data */
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
outputStream.write(new byte[258]); // Biomes + Border Size + Extra Data Size
try (NBTOutputStream nbtOutputStream = NbtUtils.createNetworkWriter(outputStream)) {
nbtOutputStream.write(EMPTY_TAG);
}
EMPTY_LEVEL_CHUNK_DATA = outputStream.toByteArray();
}catch (IOException e) {
throw new AssertionError("Unable to generate empty level chunk data");
}
}
public static void init() {
// no-op
}
}

View file

@ -0,0 +1,120 @@
package org.geysermc.multi;
import com.fasterxml.jackson.databind.JsonNode;
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.packet.*;
import lombok.Getter;
import org.geysermc.multi.UI.UIHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Getter
public class Player {
private String xuid;
private UUID identity;
private String displayName;
private BedrockServerSession session;
private final List<Server> servers = new ArrayList<>();
public Player(JsonNode extraData, BedrockServerSession session) {
this.xuid = extraData.get("XUID").asText();
this.identity = UUID.fromString(extraData.get("identity").asText());
this.displayName = extraData.get("displayName").asText();
this.session = session;
// Should fetch the servers from some form of db
servers.add(new Server("mc.hypixel.net"));
servers.add(new Server("81.174.164.211", 25580));
}
public void sendStartGame() {
StartGamePacket startGamePacket = new StartGamePacket();
startGamePacket.setUniqueEntityId(1);
startGamePacket.setRuntimeEntityId(1);
startGamePacket.setPlayerGamemode(0);
startGamePacket.setPlayerPosition(Vector3f.from(0, 64 + 2, 0));
startGamePacket.setRotation(Vector2f.ONE);
startGamePacket.setSeed(-1);
startGamePacket.setDimensionId(2);
startGamePacket.setGeneratorId(1);
startGamePacket.setLevelGamemode(1);
startGamePacket.setDifficulty(0);
startGamePacket.setDefaultSpawn(Vector3i.ZERO);
startGamePacket.setAchievementsDisabled(true);
startGamePacket.setTime(-1);
startGamePacket.setEduEditionOffers(0);
startGamePacket.setEduFeaturesEnabled(false);
startGamePacket.setRainLevel(0);
startGamePacket.setLightningLevel(0);
startGamePacket.setMultiplayerGame(true);
startGamePacket.setBroadcastingToLan(true);
startGamePacket.getGamerules().add(new GameRuleData<>("showcoordinates", true));
startGamePacket.setPlatformBroadcastMode(GamePublishSetting.PUBLIC);
startGamePacket.setXblBroadcastMode(GamePublishSetting.PUBLIC);
startGamePacket.setCommandsEnabled(true);
startGamePacket.setTexturePacksRequired(false);
startGamePacket.setBonusChestEnabled(false);
startGamePacket.setStartingWithMap(false);
startGamePacket.setTrustingPlayers(true);
startGamePacket.setDefaultPlayerPermission(PlayerPermission.VISITOR);
startGamePacket.setServerChunkTickRange(4);
startGamePacket.setBehaviorPackLocked(false);
startGamePacket.setResourcePackLocked(false);
startGamePacket.setFromLockedWorldTemplate(false);
startGamePacket.setUsingMsaGamertagsOnly(false);
startGamePacket.setFromWorldTemplate(false);
startGamePacket.setWorldTemplateOptionLocked(false);
startGamePacket.setLevelId("");
startGamePacket.setWorldName("GeyserMulti");
startGamePacket.setPremiumWorldTemplateId("");
startGamePacket.setCurrentTick(0);
startGamePacket.setEnchantmentSeed(0);
startGamePacket.setMultiplayerCorrelationId("");
startGamePacket.setBlockPalette(PalleteManger.BLOCK_PALLETE);
//startGamePacket.setItemEntries(ItemRegistry.ITEMS);
startGamePacket.setVanillaVersion("*");
// startGamePacket.setMovementServerAuthoritative(true);
session.sendPacket(startGamePacket);
// Send an empty chunk
LevelChunkPacket data = new LevelChunkPacket();
data.setChunkX(0);
data.setChunkZ(0);
data.setSubChunksLength(0);
data.setData(PalleteManger.EMPTY_LEVEL_CHUNK_DATA);
data.setCachingEnabled(false);
session.sendPacket(data);
// Send the biomes
BiomeDefinitionListPacket biomeDefinitionListPacket = new BiomeDefinitionListPacket();
biomeDefinitionListPacket.setTag(PalleteManger.BIOMES_PALLETE);
session.sendPacket(biomeDefinitionListPacket);
// Let the client know the player can spawn
PlayStatusPacket playStatusPacket = new PlayStatusPacket();
playStatusPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN);
session.sendPacket(playStatusPacket);
// Freeze the player
SetEntityMotionPacket setEntityMotionPacket = new SetEntityMotionPacket();
setEntityMotionPacket.setRuntimeEntityId(1);
setEntityMotionPacket.setMotion(Vector3f.ZERO);
session.sendPacket(setEntityMotionPacket);
}
public void sendServerList() {
session.sendPacket(UIHandler.getServerListFormPacket(servers));
}
}

View file

@ -0,0 +1,16 @@
package org.geysermc.multi;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@AllArgsConstructor
public class Server {
private String address;
private int port;
public Server(String address) {
this(address, 25565);
}
}

View file

@ -0,0 +1,9 @@
package org.geysermc.multi.UI;
public enum FormID {
MAIN,
DIRECT_CONNECT,
ADD_SERVER,
REMOVE_SERVER,
ERROR;
}

View file

@ -0,0 +1,29 @@
package org.geysermc.multi.UI;
import com.nukkitx.protocol.bedrock.packet.ModalFormRequestPacket;
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.multi.Server;
import java.util.List;
public class UIHandler {
public static ModalFormRequestPacket getServerListFormPacket(List<Server> servers) {
SimpleFormWindow window = new SimpleFormWindow("Servers", "");
for (Server server : servers) {
window.getButtons().add(new FormButton(server.getAddress(), new FormImage(FormImage.FormImageType.URL, "https://eu.mc-api.net/v3/server/favicon/" + server.getAddress() + ":" + server.getPort() + ".png")));
}
return generatePacket(FormID.MAIN, window);
}
private static ModalFormRequestPacket generatePacket(FormID id, FormWindow form) {
ModalFormRequestPacket modalFormRequestPacket = new ModalFormRequestPacket();
modalFormRequestPacket.setFormId(id.ordinal());
modalFormRequestPacket.setFormData(form.getJSONData()); // This fixes a bug in Geyser
return modalFormRequestPacket;
}
}

Binary file not shown.

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<TerminalConsole name="Console">
<PatternLayout pattern="[%d{HH:mm:ss} %style{%highlight{%level}{FATAL=red dark, ERROR=red, WARN=yellow bright, INFO=cyan bright, DEBUG=green, TRACE=white}}] %minecraftFormatting{%msg}%n"/>
</TerminalConsole>
<RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="%d{yyy-MM-dd HH:mm:ss.SSS} [%t] %level{length=1} - %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<OnStartupTriggeringPolicy/>
</Policies>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="Console"/>
<AppenderRef ref="File"/>
</Root>
</Loggers>
</Configuration>

Binary file not shown.