├── jitpack.yml ├── jbridge-common ├── src │ └── main │ │ └── java │ │ └── me │ │ └── josscoder │ │ └── jbridge │ │ ├── packet │ │ ├── AsyncPacket.java │ │ ├── PacketHandler.java │ │ ├── base │ │ │ └── ServiceCacheUpdatePacket.java │ │ ├── DataPacket.java │ │ └── PacketManager.java │ │ ├── logger │ │ └── ILogger.java │ │ ├── service │ │ ├── ServiceInfo.java │ │ └── ServiceHandler.java │ │ └── JBridgeCore.java └── pom.xml ├── jbridge-nukkit ├── src │ └── main │ │ ├── resources │ │ ├── plugin.yml │ │ └── config.yml │ │ └── java │ │ └── me │ │ └── josscoder │ │ └── jbridge │ │ └── nukkit │ │ ├── NukkitLogger.java │ │ ├── command │ │ ├── WhereAmICommand.java │ │ └── TransferCommand.java │ │ ├── task │ │ └── ServicePingTask.java │ │ └── JBridgeNukkit.java └── pom.xml ├── jbridge-waterdogpe ├── src │ └── main │ │ ├── resources │ │ ├── plugin.yml │ │ └── config.yml │ │ └── java │ │ └── me │ │ └── josscoder │ │ └── jbridge │ │ └── waterdogpe │ │ ├── WaterdogPELogger.java │ │ ├── command │ │ ├── WhereAmICommand.java │ │ └── ServerListCommand.java │ │ ├── task │ │ └── ServicePongTask.java │ │ └── JBridgeWaterdogPE.java └── pom.xml ├── jbridge-lobby ├── jbridge-lobby-waterdogpe │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ ├── plugin.yml │ │ │ └── config.yml │ │ │ └── java │ │ │ └── me │ │ │ └── josscoder │ │ │ └── jbridge │ │ │ └── waterdogpe │ │ │ └── lobby │ │ │ ├── proxyhandler │ │ │ ├── JoinHandler.java │ │ │ └── ReconnectHandler.java │ │ │ └── JBridgeLobby.java │ └── pom.xml └── jbridge-lobby-nukkit │ ├── src │ └── main │ │ ├── resources │ │ ├── plugin.yml │ │ └── config.yml │ │ └── java │ │ └── me │ │ └── josscoder │ │ └── jbridge │ │ └── nukkit │ │ └── lobby │ │ ├── command │ │ └── LobbyCommand.java │ │ └── JBridgeLobby.java │ └── pom.xml ├── .github ├── dependabot.yml └── workflows │ └── maven.yml ├── pom.xml ├── .gitignore ├── LICENSE └── README.md /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk17 -------------------------------------------------------------------------------- /jbridge-common/src/main/java/me/josscoder/jbridge/packet/AsyncPacket.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.packet; 2 | 3 | public interface AsyncPacket {} 4 | -------------------------------------------------------------------------------- /jbridge-nukkit/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: JBridge 2 | main: me.josscoder.jbridge.nukkit.JBridgeNukkit 3 | api: 1.0.0 4 | version: ${project.version} 5 | author: Josscoder -------------------------------------------------------------------------------- /jbridge-waterdogpe/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: JBridge 2 | main: me.josscoder.jbridge.waterdogpe.JBridgeWaterdogPE 3 | version: ${project.version} 4 | author: Josscoder -------------------------------------------------------------------------------- /jbridge-lobby/jbridge-lobby-waterdogpe/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: JBridgeLobby 2 | main: me.josscoder.jbridge.waterdogpe.lobby.JBridgeLobby 3 | version: ${project.version} 4 | author: Josscoder 5 | depend: ["JBridge"] 6 | loadbefore: ["JBridge"] -------------------------------------------------------------------------------- /jbridge-lobby/jbridge-lobby-nukkit/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: JBridgeLobby 2 | main: me.josscoder.jbridge.nukkit.lobby.JBridgeLobby 3 | api: 1.0.0 4 | version: ${project.version} 5 | author: Josscoder 6 | depend: ["JBridge"] 7 | loadbefore: ["JBridge"] -------------------------------------------------------------------------------- /jbridge-waterdogpe/src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | debug: true 2 | 3 | redis: 4 | hostname: "localhost" 5 | port: 6379 6 | password: "yourpasswordhere" 7 | 8 | service: 9 | id: "proxy-1" 10 | region: "us" 11 | branch: "dev" 12 | handling-interval: 5 -------------------------------------------------------------------------------- /jbridge-nukkit/src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | debug: true 2 | 3 | redis: 4 | hostname: "localhost" 5 | port: 6379 6 | password: "yourpasswordhere" 7 | 8 | service: 9 | id: "hub-1" 10 | group: "hub" 11 | region: "us" 12 | branch: "dev" 13 | interval-to-send-update: 1 -------------------------------------------------------------------------------- /jbridge-common/src/main/java/me/josscoder/jbridge/logger/ILogger.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.logger; 2 | 3 | public interface ILogger { 4 | 5 | void info(String message); 6 | void warn(String message); 7 | void debug(String message); 8 | void error(String message); 9 | } 10 | -------------------------------------------------------------------------------- /jbridge-lobby/jbridge-lobby-nukkit/src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | lobby-groups: 2 | - hub 3 | - lobby 4 | - hub-main 5 | - lobby-main 6 | 7 | #Modes: 8 | #LOWEST: will balance the servers to have the same amount of players all 9 | #FULL: will successively fill each server 10 | #ANY: will get any lobby for the player 11 | sort-mode: LOWEST -------------------------------------------------------------------------------- /jbridge-lobby/jbridge-lobby-waterdogpe/src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | lobby-groups: 2 | - hub 3 | - lobby 4 | - hub-main 5 | - lobby-main 6 | 7 | #Modes: 8 | #LOWEST: will balance the servers to have the same amount of players all 9 | #FULL: will successively fill each server 10 | #ANY: will get any lobby for the player 11 | sort-mode: LOWEST -------------------------------------------------------------------------------- /jbridge-common/src/main/java/me/josscoder/jbridge/packet/PacketHandler.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.packet; 2 | 3 | /** 4 | * Interface class which new PacketHandler classes should implement 5 | */ 6 | public interface PacketHandler { 7 | 8 | /** 9 | * Method called when encoding and sending the packet 10 | */ 11 | void onSend(DataPacket packet); 12 | 13 | 14 | /** 15 | * Method called when decoding and receiving the packet 16 | */ 17 | void onReceive(DataPacket packet); 18 | } 19 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "maven" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" -------------------------------------------------------------------------------- /jbridge-lobby/jbridge-lobby-waterdogpe/src/main/java/me/josscoder/jbridge/waterdogpe/lobby/proxyhandler/JoinHandler.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.waterdogpe.lobby.proxyhandler; 2 | 3 | import dev.waterdog.waterdogpe.ProxyServer; 4 | import dev.waterdog.waterdogpe.network.serverinfo.ServerInfo; 5 | import dev.waterdog.waterdogpe.player.ProxiedPlayer; 6 | import dev.waterdog.waterdogpe.utils.types.IJoinHandler; 7 | import me.josscoder.jbridge.waterdogpe.lobby.JBridgeLobby; 8 | 9 | public class JoinHandler implements IJoinHandler { 10 | @Override 11 | public ServerInfo determineServer(ProxiedPlayer player) { 12 | String sortedLobbyService = JBridgeLobby.getInstance().getSortedLobbyServiceShortId(); 13 | return ProxyServer.getInstance().getServerInfo(sortedLobbyService); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /jbridge-common/src/main/java/me/josscoder/jbridge/packet/base/ServiceCacheUpdatePacket.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.packet.base; 2 | 3 | import com.google.common.io.ByteArrayDataInput; 4 | import com.google.common.io.ByteArrayDataOutput; 5 | import me.josscoder.jbridge.packet.DataPacket; 6 | 7 | /** 8 | * Sample packet, used to keep jbridge clients and servers in sync 9 | */ 10 | public class ServiceCacheUpdatePacket extends DataPacket { 11 | 12 | public String cache; 13 | 14 | public ServiceCacheUpdatePacket() { 15 | super((byte) 0x01); 16 | } 17 | 18 | @Override 19 | public void encode(ByteArrayDataOutput output) { 20 | output.writeUTF(cache); 21 | } 22 | 23 | @Override 24 | public void decode(ByteArrayDataInput input) { 25 | cache = input.readUTF(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /jbridge-nukkit/src/main/java/me/josscoder/jbridge/nukkit/NukkitLogger.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.nukkit; 2 | 3 | import cn.nukkit.plugin.PluginLogger; 4 | import cn.nukkit.utils.TextFormat; 5 | import me.josscoder.jbridge.logger.ILogger; 6 | 7 | public class NukkitLogger implements ILogger { 8 | 9 | private final PluginLogger logger = JBridgeNukkit.getInstance().getLogger(); 10 | 11 | @Override 12 | public void info(String message) { 13 | logger.info(TextFormat.GREEN + message); 14 | } 15 | 16 | @Override 17 | public void warn(String message) { 18 | logger.warning(TextFormat.AQUA + message); 19 | } 20 | 21 | @Override 22 | public void debug(String message) { 23 | logger.info(TextFormat.DARK_BLUE + "[DEBUG] " + TextFormat.WHITE + message); 24 | } 25 | 26 | @Override 27 | public void error(String message) { 28 | logger.error(message); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /jbridge-nukkit/src/main/java/me/josscoder/jbridge/nukkit/command/WhereAmICommand.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.nukkit.command; 2 | 3 | import cn.nukkit.command.Command; 4 | import cn.nukkit.command.CommandSender; 5 | import cn.nukkit.utils.TextFormat; 6 | import me.josscoder.jbridge.JBridgeCore; 7 | 8 | public class WhereAmICommand extends Command { 9 | 10 | public WhereAmICommand() { 11 | super("whereami", 12 | "Provide information about the server you are on", 13 | "/whereami", 14 | new String[]{"connection"} 15 | ); 16 | } 17 | 18 | @Override 19 | public boolean execute(CommandSender sender, String s, String[] strings) { 20 | String serviceId = JBridgeCore.getInstance().getCurrentServiceInfo().getGroupAndId(); 21 | sender.sendMessage(TextFormat.GOLD + "You are connected to server " + serviceId); 22 | return true; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /jbridge-waterdogpe/src/main/java/me/josscoder/jbridge/waterdogpe/WaterdogPELogger.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.waterdogpe; 2 | 3 | import me.josscoder.jbridge.logger.ILogger; 4 | import dev.waterdog.waterdogpe.logger.Color; 5 | import org.apache.logging.log4j.Logger; 6 | 7 | public class WaterdogPELogger implements ILogger { 8 | 9 | private final Logger pluginLogger = JBridgeWaterdogPE.getInstance().getLogger(); 10 | 11 | @Override 12 | public void info(String message) { 13 | pluginLogger.info(Color.GREEN + message); 14 | } 15 | 16 | @Override 17 | public void warn(String message) { 18 | pluginLogger.warn(Color.AQUA + message); 19 | } 20 | 21 | @Override 22 | public void debug(String message) { 23 | pluginLogger.info(Color.DARK_BLUE + "[DEBUG] " + Color.WHITE + message); 24 | } 25 | 26 | @Override 27 | public void error(String message) { 28 | pluginLogger.error(message); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /jbridge-nukkit/src/main/java/me/josscoder/jbridge/nukkit/task/ServicePingTask.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.nukkit.task; 2 | 3 | import cn.nukkit.Server; 4 | import cn.nukkit.scheduler.Task; 5 | import me.josscoder.jbridge.JBridgeCore; 6 | import me.josscoder.jbridge.packet.base.ServiceCacheUpdatePacket; 7 | import me.josscoder.jbridge.service.ServiceInfo; 8 | 9 | public class ServicePingTask extends Task { 10 | @Override 11 | public void onRun(int i) { 12 | ServiceInfo serviceInfo = JBridgeCore.getInstance().getCurrentServiceInfo(); 13 | 14 | serviceInfo.getPlayers().clear(); 15 | Server.getInstance().getOnlinePlayers().values().forEach(player -> serviceInfo.addPlayer(player.getName())); 16 | 17 | JBridgeCore jBridgeCore = JBridgeCore.getInstance(); 18 | jBridgeCore.getPacketManager().publishPacket(new ServiceCacheUpdatePacket(){{ 19 | cache = jBridgeCore.getGson().toJson(serviceInfo); 20 | }}); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /jbridge-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | me.josscoder.jbridge 8 | JBridge 9 | 1.0.7 10 | 11 | 12 | jbridge-common 13 | 14 | 15 | 16 | redis.clients 17 | jedis 18 | 4.3.1 19 | 20 | 21 | com.google.guava 22 | guava 23 | 31.1-jre 24 | 25 | 26 | -------------------------------------------------------------------------------- /jbridge-lobby/jbridge-lobby-waterdogpe/src/main/java/me/josscoder/jbridge/waterdogpe/lobby/proxyhandler/ReconnectHandler.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.waterdogpe.lobby.proxyhandler; 2 | 3 | import dev.waterdog.waterdogpe.ProxyServer; 4 | import dev.waterdog.waterdogpe.network.serverinfo.ServerInfo; 5 | import dev.waterdog.waterdogpe.player.ProxiedPlayer; 6 | import dev.waterdog.waterdogpe.utils.types.IReconnectHandler; 7 | import me.josscoder.jbridge.waterdogpe.lobby.JBridgeLobby; 8 | 9 | public class ReconnectHandler implements IReconnectHandler { 10 | @Override 11 | public ServerInfo getFallbackServer(ProxiedPlayer player, ServerInfo oldServer, String kickMessage) { 12 | player.sendMessage(String.format("§8Unexpected? Report this §7(%s): §c%s" + 13 | "\n" + 14 | "§aWe will connect you to a lobby shortly...", 15 | oldServer.getServerName(), 16 | kickMessage 17 | )); 18 | 19 | String sortedLobbyService = JBridgeLobby.getInstance().getSortedLobbyServiceShortId(); 20 | return ProxyServer.getInstance().getServerInfo(sortedLobbyService); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /jbridge-lobby/jbridge-lobby-nukkit/src/main/java/me/josscoder/jbridge/nukkit/lobby/command/LobbyCommand.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.nukkit.lobby.command; 2 | 3 | import cn.nukkit.Player; 4 | import cn.nukkit.command.Command; 5 | import cn.nukkit.command.CommandSender; 6 | import cn.nukkit.utils.TextFormat; 7 | import me.josscoder.jbridge.nukkit.JBridgeNukkit; 8 | import me.josscoder.jbridge.nukkit.lobby.JBridgeLobby; 9 | 10 | public class LobbyCommand extends Command { 11 | 12 | public LobbyCommand() { 13 | super("lobby", 14 | "Return to lobby", 15 | "/lobby", 16 | new String[]{"hub"} 17 | ); 18 | } 19 | 20 | @Override 21 | public boolean execute(CommandSender sender, String label, String[] args) { 22 | if (!sender.isPlayer()) return false; 23 | Player player = (Player) sender; 24 | 25 | String sortedLobbyService = JBridgeLobby.getInstance().geSortedLobbyServiceShortId(); 26 | if (sortedLobbyService.isEmpty()) { 27 | player.sendMessage(TextFormat.RED + "No rotating lobby servers!"); 28 | return false; 29 | } 30 | 31 | JBridgeNukkit.getInstance().transferPlayer(player, sortedLobbyService); 32 | return true; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /jbridge-common/src/main/java/me/josscoder/jbridge/packet/DataPacket.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.packet; 2 | 3 | import com.google.common.io.ByteArrayDataInput; 4 | import com.google.common.io.ByteArrayDataOutput; 5 | import lombok.Data; 6 | import me.josscoder.jbridge.JBridgeCore; 7 | 8 | /** 9 | * Abstract class from which new packets should inherit their methods 10 | */ 11 | @Data 12 | public abstract class DataPacket { 13 | 14 | /** 15 | * Packet identifier 16 | */ 17 | private final byte pid; 18 | 19 | private String sender; 20 | 21 | public void mainEncode(ByteArrayDataOutput output) { 22 | output.writeUTF(sender); 23 | encode(output); 24 | } 25 | 26 | /** 27 | * Method to encode data using the output variable 28 | */ 29 | public abstract void encode(ByteArrayDataOutput output); 30 | 31 | public void mainDecode(ByteArrayDataInput input) { 32 | sender = input.readUTF(); 33 | decode(input); 34 | } 35 | 36 | /** 37 | * Method to decode data using the input variable 38 | */ 39 | public abstract void decode(ByteArrayDataInput input); 40 | 41 | public boolean senderIsOneSelf() { 42 | return sender.equalsIgnoreCase(JBridgeCore.getInstance().getCurrentServiceInfo().getShortId()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | me.josscoder.jbridge 8 | JBridge 9 | 1.0.7 10 | 11 | pom 12 | 13 | JBridge 14 | 15 | 16 | jbridge-common 17 | jbridge-nukkit 18 | jbridge-waterdogpe 19 | jbridge-lobby/jbridge-lobby-nukkit 20 | jbridge-lobby/jbridge-lobby-waterdogpe 21 | 22 | 23 | 24 | 1.8 25 | 1.8 26 | UTF-8 27 | 28 | 29 | 30 | 31 | org.projectlombok 32 | lombok 33 | 1.18.26 34 | provided 35 | 36 | 37 | -------------------------------------------------------------------------------- /jbridge-common/src/main/java/me/josscoder/jbridge/service/ServiceInfo.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.service; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | 8 | @Data 9 | public class ServiceInfo { 10 | 11 | private final String id, address, group, region, branch; 12 | private final int maxPlayers; 13 | private final Set players = new HashSet<>(); 14 | 15 | public String getShortId() { 16 | return id.substring(0, 5); 17 | } 18 | 19 | public String getGroupAndId() { 20 | return String.format("%s-%s", group, id); 21 | } 22 | 23 | public String getRegionGroupAndId() { 24 | return region + "-" + getGroupAndId(); 25 | } 26 | 27 | public String getGroupAndShortId() { 28 | return String.format("%s-%s", group, getShortId()); 29 | } 30 | 31 | public String getRegionGroupAndShortId() { 32 | return region + "-" + getGroupAndShortId(); 33 | } 34 | 35 | public void addPlayer(String player) { 36 | players.add(player); 37 | } 38 | 39 | public boolean containsPlayer(String player) { 40 | return players.contains(player); 41 | } 42 | 43 | public int getPlayersOnline() { 44 | return players.size(); 45 | } 46 | 47 | public boolean isFull() { 48 | return getPlayersOnline() >= maxPlayers; 49 | } 50 | 51 | public static ServiceInfo empty() { 52 | return new ServiceInfo("?", "?", "?", "?", "?", 0); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /jbridge-nukkit/src/main/java/me/josscoder/jbridge/nukkit/command/TransferCommand.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.nukkit.command; 2 | 3 | import cn.nukkit.Player; 4 | import cn.nukkit.command.Command; 5 | import cn.nukkit.command.CommandSender; 6 | import cn.nukkit.utils.TextFormat; 7 | import me.josscoder.jbridge.JBridgeCore; 8 | import me.josscoder.jbridge.nukkit.JBridgeNukkit; 9 | import me.josscoder.jbridge.service.ServiceHandler; 10 | import me.josscoder.jbridge.service.ServiceInfo; 11 | 12 | public class TransferCommand extends Command { 13 | 14 | public TransferCommand() { 15 | super("transfer", 16 | "Transfer to a specific server", 17 | TextFormat.RED + "Usage: /transfer " 18 | ); 19 | setPermission("jbrdige.command.transfer"); 20 | } 21 | 22 | @Override 23 | public boolean execute(CommandSender sender, String label, String[] args) { 24 | if (!sender.isPlayer() || !testPermission(sender)) return false; 25 | Player player = (Player) sender; 26 | 27 | if (args.length == 0) { 28 | player.sendMessage(getUsage()); 29 | return true; 30 | } 31 | 32 | String serverName = args[0]; 33 | 34 | ServiceHandler serviceHandler = JBridgeCore.getInstance().getServiceHandler(); 35 | ServiceInfo service = serviceHandler.getService(serverName); 36 | 37 | if (service == null) { 38 | player.sendMessage(TextFormat.RED + "That server is currently not in rotation"); 39 | return true; 40 | } 41 | 42 | JBridgeNukkit.getInstance().transferPlayer(player, service.getShortId()); 43 | return true; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /jbridge-lobby/jbridge-lobby-nukkit/src/main/java/me/josscoder/jbridge/nukkit/lobby/JBridgeLobby.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.nukkit.lobby; 2 | 3 | import cn.nukkit.plugin.PluginBase; 4 | import lombok.Getter; 5 | import me.josscoder.jbridge.JBridgeCore; 6 | import me.josscoder.jbridge.nukkit.lobby.command.LobbyCommand; 7 | import me.josscoder.jbridge.service.ServiceHandler; 8 | import me.josscoder.jbridge.service.ServiceInfo; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | @Getter 14 | public class JBridgeLobby extends PluginBase { 15 | 16 | @Getter 17 | private static JBridgeLobby instance; 18 | 19 | private List lobbyGroups; 20 | private ServiceHandler.SortMode sortMode; 21 | 22 | @Override 23 | public void onLoad() { 24 | instance = this; 25 | } 26 | 27 | @Override 28 | public void onEnable() { 29 | saveDefaultConfig(); 30 | 31 | lobbyGroups = getConfig().getStringList("lobby-groups"); 32 | sortMode = ServiceHandler.SortMode.valueOf(getConfig().getString("sort-mode")); 33 | 34 | getServer().getCommandMap().register("lobby", new LobbyCommand()); 35 | } 36 | 37 | public List getLobbyServices() { 38 | List services = new ArrayList<>(); 39 | lobbyGroups.forEach(lobbyGroup -> services.addAll( 40 | JBridgeCore.getInstance().getServiceHandler().getGroupServices(lobbyGroup) 41 | )); 42 | 43 | return services; 44 | } 45 | 46 | public ServiceInfo getSortedLobbyService() { 47 | return JBridgeCore.getInstance() 48 | .getServiceHandler() 49 | .getSortedServiceFromList(getLobbyServices(), sortMode); 50 | } 51 | 52 | public String geSortedLobbyServiceShortId() { 53 | ServiceInfo serviceInfo = getSortedLobbyService(); 54 | return serviceInfo == null ? "" : serviceInfo.getShortId(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ "release" ] 9 | pull_request: 10 | branches: [ "release" ] 11 | 12 | jobs: 13 | build: 14 | name: Create Build 15 | if: "startsWith(github.event.head_commit.message, '[Release]')" 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | - name: Set up JDK 17 22 | uses: actions/setup-java@v3 23 | with: 24 | java-version: '17' 25 | distribution: 'corretto' 26 | cache: maven 27 | 28 | - name: Build 29 | run: mvn clean install 30 | 31 | - name: Get Maven version 32 | run: | 33 | VERSION=$(mvn -q -Dexec.executable='echo' -Dexec.args='${project.version}' --non-recursive exec:exec) 34 | echo "VERSION=${VERSION}" >> $GITHUB_ENV 35 | 36 | - name: Upload Artifact 37 | uses: ncipollo/release-action@v1.10.0 38 | with: 39 | artifacts: ${{ github.workspace }}/jbridge-common/target/*.jar, ${{ github.workspace }}/jbridge-nukkit/target/*.jar, ${{ github.workspace }}/jbridge-waterdogpe/target/*.jar, ${{ github.workspace }}/jbridge-lobby/jbridge-lobby-nukkit/target/*.jar, ${{ github.workspace }}/jbridge-lobby/jbridge-lobby-waterdogpe/target/*.jar 40 | draft: false 41 | name: Release v${{ env.VERSION }} 42 | tag: ${{ env.VERSION }} 43 | token: ${{ secrets.GITHUB_TOKEN }} 44 | body: "The artifacts are kept up to date with the branch release." 45 | commit: ${{ github.sha }} 46 | allowUpdates: true 47 | removeArtifacts: true 48 | replacesArtifacts: true 49 | -------------------------------------------------------------------------------- /jbridge-waterdogpe/src/main/java/me/josscoder/jbridge/waterdogpe/command/WhereAmICommand.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.waterdogpe.command; 2 | 3 | import dev.waterdog.waterdogpe.command.Command; 4 | import dev.waterdog.waterdogpe.command.CommandSender; 5 | import dev.waterdog.waterdogpe.command.CommandSettings; 6 | import dev.waterdog.waterdogpe.logger.Color; 7 | import dev.waterdog.waterdogpe.network.serverinfo.ServerInfo; 8 | import dev.waterdog.waterdogpe.player.ProxiedPlayer; 9 | import me.josscoder.jbridge.JBridgeCore; 10 | import me.josscoder.jbridge.service.ServiceInfo; 11 | 12 | public class WhereAmICommand extends Command { 13 | 14 | public WhereAmICommand() { 15 | super("whereami", CommandSettings.builder() 16 | .setDescription("Provide information about the proxy you are on") 17 | .setAliases(new String[]{"connection"}) 18 | .build() 19 | ); 20 | } 21 | 22 | @Override 23 | public boolean onExecute(CommandSender sender, String alias, String[] args) { 24 | String proxyId = JBridgeCore.getInstance().getCurrentServiceInfo().getGroupAndId(); 25 | 26 | if (!sender.isPlayer()) { 27 | sender.sendMessage(Color.GOLD + "You are connected to proxy " + proxyId); 28 | return true; 29 | } 30 | 31 | String serviceId = "?"; 32 | 33 | ProxiedPlayer player = (ProxiedPlayer) sender; 34 | 35 | ServerInfo serverInfo = player.getServerInfo(); 36 | if (serverInfo != null) serviceId = serverInfo.getServerName(); 37 | 38 | ServiceInfo serviceInfo = JBridgeCore.getInstance().getServiceHandler().getService(serviceId); 39 | if (serverInfo != null) serviceId = serviceInfo.getGroupAndId(); 40 | 41 | sender.sendMessage(Color.GOLD + "You are connected to proxy " + proxyId + 42 | "\n" + 43 | "You are connected to server " + serviceId 44 | ); 45 | return true; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /jbridge-lobby/jbridge-lobby-waterdogpe/src/main/java/me/josscoder/jbridge/waterdogpe/lobby/JBridgeLobby.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.waterdogpe.lobby; 2 | 3 | import dev.waterdog.waterdogpe.plugin.Plugin; 4 | import lombok.Getter; 5 | import me.josscoder.jbridge.JBridgeCore; 6 | import me.josscoder.jbridge.service.ServiceHandler; 7 | import me.josscoder.jbridge.service.ServiceInfo; 8 | import me.josscoder.jbridge.waterdogpe.lobby.proxyhandler.JoinHandler; 9 | import me.josscoder.jbridge.waterdogpe.lobby.proxyhandler.ReconnectHandler; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | @Getter 15 | public class JBridgeLobby extends Plugin { 16 | 17 | @Getter 18 | private static JBridgeLobby instance; 19 | 20 | private List lobbyGroups; 21 | private ServiceHandler.SortMode sortMode; 22 | 23 | @Override 24 | public void onStartup() { 25 | instance = this; 26 | } 27 | 28 | @Override 29 | public void onEnable() { 30 | loadConfig(); 31 | 32 | lobbyGroups = getConfig().getStringList("lobby-groups"); 33 | sortMode = ServiceHandler.SortMode.valueOf(getConfig().getString("sort-mode")); 34 | 35 | getProxy().setJoinHandler(new JoinHandler()); 36 | getProxy().setReconnectHandler(new ReconnectHandler()); 37 | } 38 | 39 | public List getLobbyServices() { 40 | List services = new ArrayList<>(); 41 | lobbyGroups.forEach(lobbyGroup -> services.addAll( 42 | JBridgeCore.getInstance().getServiceHandler().getGroupServices(lobbyGroup) 43 | )); 44 | 45 | return services; 46 | } 47 | 48 | public ServiceInfo getSortedLobbyService() { 49 | return JBridgeCore.getInstance() 50 | .getServiceHandler() 51 | .getSortedServiceFromList(getLobbyServices(), sortMode); 52 | } 53 | 54 | public String getSortedLobbyServiceShortId() { 55 | ServiceInfo serviceInfo = getSortedLobbyService(); 56 | return serviceInfo == null ? "" : serviceInfo.getShortId(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific stuff 2 | .idea/ 3 | 4 | *.iml 5 | *.ipr 6 | *.iws 7 | 8 | # IntelliJ 9 | out/ 10 | 11 | # Compiled class file 12 | *.class 13 | 14 | # Log file 15 | *.log 16 | 17 | # BlueJ files 18 | *.ctxt 19 | 20 | # Package Files # 21 | *.jar 22 | *.war 23 | *.nar 24 | *.ear 25 | *.zip 26 | *.tar.gz 27 | *.rar 28 | 29 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 30 | hs_err_pid* 31 | 32 | *~ 33 | 34 | # temporary files which can be created if a process still has a handle open of a deleted file 35 | .fuse_hidden* 36 | 37 | # KDE directory preferences 38 | .directory 39 | 40 | # Linux trash folder which might appear on any partition or disk 41 | .Trash-* 42 | 43 | # .nfs files are created when an open file is removed but is still being accessed 44 | .nfs* 45 | 46 | # General 47 | .DS_Store 48 | .AppleDouble 49 | .LSOverride 50 | 51 | # Icon must end with two \r 52 | Icon 53 | 54 | # Thumbnails 55 | ._* 56 | 57 | # Files that might appear in the root of a volume 58 | .DocumentRevisions-V100 59 | .fseventsd 60 | .Spotlight-V100 61 | .TemporaryItems 62 | .Trashes 63 | .VolumeIcon.icns 64 | .com.apple.timemachine.donotpresent 65 | 66 | # Directories potentially created on remote AFP share 67 | .AppleDB 68 | .AppleDesktop 69 | Network Trash Folder 70 | Temporary Items 71 | .apdisk 72 | 73 | # Windows thumbnail cache files 74 | Thumbs.db 75 | Thumbs.db:encryptable 76 | ehthumbs.db 77 | ehthumbs_vista.db 78 | 79 | # Dump file 80 | *.stackdump 81 | 82 | # Folder config file 83 | [Dd]esktop.ini 84 | 85 | # Recycle Bin used on file shares 86 | $RECYCLE.BIN/ 87 | 88 | # Windows Installer files 89 | *.cab 90 | *.msi 91 | *.msix 92 | *.msm 93 | *.msp 94 | 95 | # Windows shortcuts 96 | *.lnk 97 | 98 | target/ 99 | 100 | pom.xml.tag 101 | pom.xml.releaseBackup 102 | pom.xml.versionsBackup 103 | pom.xml.next 104 | 105 | release.properties 106 | dependency-reduced-pom.xml 107 | buildNumber.properties 108 | .mvn/timing.properties 109 | .mvn/wrapper/maven-wrapper.jar 110 | .flattened-pom.xml 111 | 112 | # Common working directory 113 | run/ 114 | -------------------------------------------------------------------------------- /jbridge-lobby/jbridge-lobby-waterdogpe/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | me.josscoder.jbridge 8 | JBridge 9 | 1.0.7 10 | ../../pom.xml 11 | 12 | 13 | me.josscoder.jbridge.waterdogpe.lobby 14 | jbridge-lobby-waterdogpe 15 | 16 | 17 | 18 | 19 | org.apache.maven.plugins 20 | maven-compiler-plugin 21 | 3.11.0 22 | 23 | 24 | org.apache.maven.plugins 25 | maven-shade-plugin 26 | 3.4.1 27 | 28 | 29 | install 30 | 31 | shade 32 | 33 | 34 | 35 | 36 | false 37 | 38 | 39 | 40 | 41 | 42 | src/main/resources 43 | true 44 | 45 | 46 | 47 | 48 | 49 | 50 | waterdog-repo 51 | https://repo.waterdog.dev/artifactory/main 52 | 53 | 54 | 55 | 56 | 57 | me.josscoder.jbridge.waterdogpe 58 | jbridge-waterdogpe 59 | 1.0.7 60 | provided 61 | 62 | 63 | dev.waterdog.waterdogpe 64 | waterdog 65 | 1.2.3 66 | provided 67 | 68 | 69 | -------------------------------------------------------------------------------- /jbridge-lobby/jbridge-lobby-nukkit/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | me.josscoder.jbridge 8 | JBridge 9 | 1.0.7 10 | ../../pom.xml 11 | 12 | 13 | me.josscoder.jbridge.nukkit.lobby 14 | jbridge-lobby-nukkit 15 | 16 | 17 | 18 | 19 | org.apache.maven.plugins 20 | maven-compiler-plugin 21 | 3.11.0 22 | 23 | 24 | org.apache.maven.plugins 25 | maven-shade-plugin 26 | 3.4.1 27 | 28 | 29 | package 30 | 31 | shade 32 | 33 | 34 | false 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | src/main/resources 43 | true 44 | 45 | 46 | 47 | 48 | 49 | 50 | opencollab-repo-release 51 | https://repo.opencollab.dev/maven-releases/ 52 | 53 | 54 | opencollab-repo-snapshot 55 | https://repo.opencollab.dev/maven-snapshots/ 56 | 57 | 58 | 59 | 60 | 61 | me.josscoder.jbridge.nukkit 62 | jbridge-nukkit 63 | 1.0.7 64 | provided 65 | 66 | 67 | cn.nukkit 68 | nukkit 69 | 1.0-SNAPSHOT 70 | provided 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /jbridge-waterdogpe/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | me.josscoder.jbridge 8 | JBridge 9 | 1.0.7 10 | 11 | 12 | me.josscoder.jbridge.waterdogpe 13 | jbridge-waterdogpe 14 | 15 | 16 | 17 | 18 | org.apache.maven.plugins 19 | maven-compiler-plugin 20 | 3.11.0 21 | 22 | 23 | org.apache.maven.plugins 24 | maven-shade-plugin 25 | 3.4.1 26 | 27 | 28 | install 29 | 30 | shade 31 | 32 | 33 | 34 | 35 | 36 | 37 | *:* 38 | 39 | module-info.class 40 | META-INF/* 41 | 42 | 43 | 44 | false 45 | 46 | 47 | 48 | 49 | 50 | src/main/resources 51 | true 52 | 53 | 54 | 55 | 56 | 57 | 58 | waterdog-repo 59 | https://repo.waterdog.dev/artifactory/main 60 | 61 | 62 | 63 | 64 | 65 | me.josscoder.jbridge 66 | jbridge-common 67 | 1.0.7 68 | compile 69 | 70 | 71 | dev.waterdog.waterdogpe 72 | waterdog 73 | 1.2.3 74 | provided 75 | 76 | 77 | -------------------------------------------------------------------------------- /jbridge-nukkit/src/main/java/me/josscoder/jbridge/nukkit/JBridgeNukkit.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.nukkit; 2 | 3 | import cn.nukkit.Player; 4 | import cn.nukkit.command.SimpleCommandMap; 5 | import cn.nukkit.network.protocol.TransferPacket; 6 | import cn.nukkit.plugin.PluginBase; 7 | import cn.nukkit.utils.Config; 8 | import lombok.Getter; 9 | import me.josscoder.jbridge.JBridgeCore; 10 | import me.josscoder.jbridge.nukkit.command.TransferCommand; 11 | import me.josscoder.jbridge.nukkit.command.WhereAmICommand; 12 | import me.josscoder.jbridge.service.ServiceInfo; 13 | import me.josscoder.jbridge.nukkit.task.ServicePingTask; 14 | 15 | import java.util.UUID; 16 | 17 | public class JBridgeNukkit extends PluginBase { 18 | 19 | @Getter 20 | private static JBridgeNukkit instance; 21 | 22 | @Override 23 | public void onLoad() { 24 | instance = this; 25 | } 26 | 27 | @Override 28 | public void onEnable() { 29 | saveDefaultConfig(); 30 | 31 | Config config = getConfig(); 32 | 33 | JBridgeCore jBridgeCore = new JBridgeCore(); 34 | jBridgeCore.boot(config.getString("redis.hostname"), 35 | config.getInt("redis.port"), 36 | config.getString("redis.password"), 37 | config.getBoolean("debug"), 38 | new NukkitLogger() 39 | ); 40 | 41 | if (!config.exists("service.id")) { 42 | config.set("service.id", UUID.randomUUID().toString().substring(0, 8)); 43 | config.save(); 44 | } 45 | 46 | String branch = config.getString("service.branch", "dev"); 47 | String address = config.getString("service.address", getServer().getIp()); 48 | 49 | String finalAddress = (branch.startsWith("dev") ? "127.0.0.1" : address); 50 | 51 | ServiceInfo serviceInfo = new ServiceInfo( 52 | config.getString("service.id"), 53 | finalAddress + ":" + getServer().getPort(), 54 | config.getString("service.group", "hub"), 55 | config.getString("service.region", "us"), 56 | branch, 57 | getServer().getMaxPlayers() 58 | ); 59 | jBridgeCore.setCurrentServiceInfo(serviceInfo); 60 | 61 | registerCommands(); 62 | 63 | int intervalToSendUpdate = config.getInt("service.interval-to-send-update", 1); 64 | 65 | getServer().getScheduler().scheduleRepeatingTask(new ServicePingTask(), 66 | 20 * intervalToSendUpdate, 67 | true 68 | ); 69 | } 70 | 71 | private void registerCommands() { 72 | SimpleCommandMap map = getServer().getCommandMap(); 73 | map.register("transfer", new TransferCommand()); 74 | map.register("whereami", new WhereAmICommand()); 75 | } 76 | 77 | public void transferPlayer(Player player, String serverName) { 78 | TransferPacket packet = new TransferPacket(); 79 | packet.address = serverName; 80 | packet.port = 0; 81 | 82 | player.dataPacket(packet); 83 | } 84 | 85 | @Override 86 | public void onDisable() { 87 | JBridgeCore.getInstance().shutdown(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /jbridge-nukkit/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | me.josscoder.jbridge 8 | JBridge 9 | 1.0.7 10 | 11 | 12 | me.josscoder.jbridge.nukkit 13 | jbridge-nukkit 14 | 15 | 16 | 17 | 18 | org.apache.maven.plugins 19 | maven-compiler-plugin 20 | 3.11.0 21 | 22 | 23 | org.apache.maven.plugins 24 | maven-shade-plugin 25 | 3.4.1 26 | 27 | 28 | package 29 | 30 | shade 31 | 32 | 33 | 34 | 35 | *:* 36 | 37 | module-info.class 38 | META-INF/* 39 | 40 | 41 | 42 | false 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | src/main/resources 51 | true 52 | 53 | 54 | 55 | 56 | 57 | 58 | opencollab-repo-release 59 | https://repo.opencollab.dev/maven-releases/ 60 | 61 | 62 | opencollab-repo-snapshot 63 | https://repo.opencollab.dev/maven-snapshots/ 64 | 65 | 66 | 67 | 68 | 69 | me.josscoder.jbridge 70 | jbridge-common 71 | 1.0.7 72 | compile 73 | 74 | 75 | cn.nukkit 76 | nukkit 77 | 1.0-SNAPSHOT 78 | provided 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /jbridge-waterdogpe/src/main/java/me/josscoder/jbridge/waterdogpe/command/ServerListCommand.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.waterdogpe.command; 2 | 3 | import dev.waterdog.waterdogpe.command.Command; 4 | import dev.waterdog.waterdogpe.command.CommandSender; 5 | import dev.waterdog.waterdogpe.command.CommandSettings; 6 | import dev.waterdog.waterdogpe.logger.Color; 7 | import dev.waterdog.waterdogpe.network.serverinfo.ServerInfo; 8 | import dev.waterdog.waterdogpe.player.ProxiedPlayer; 9 | import me.josscoder.jbridge.JBridgeCore; 10 | import me.josscoder.jbridge.service.ServiceHandler; 11 | import me.josscoder.jbridge.service.ServiceInfo; 12 | 13 | import java.util.ArrayList; 14 | import java.util.Comparator; 15 | import java.util.List; 16 | import java.util.StringJoiner; 17 | 18 | public class ServerListCommand extends Command { 19 | 20 | private final ServiceHandler serviceHandler = JBridgeCore.getInstance().getServiceHandler(); 21 | 22 | public ServerListCommand() { 23 | super("wdlist", CommandSettings.builder() 24 | .setDescription("waterdog.command.list.description") 25 | .setUsageMessage("waterdog.command.list.usage") 26 | .setPermission("waterdog.command.list.permission") 27 | .setAliases(new String[]{"servicelist", "services", "servers", "glist", "rglist"}) 28 | .build() 29 | ); 30 | } 31 | 32 | @Override 33 | public boolean onExecute(CommandSender sender, String alias, String[] args) { 34 | if (args.length >= 1) { 35 | ServerInfo serverInfo = sender.getProxy().getServerInfo(args[0]); 36 | sender.sendMessage(serverInfo == null 37 | ? Color.RED + "Server not found!" 38 | : buildServerList(serverInfo) 39 | ); 40 | return true; 41 | } 42 | 43 | List servers = new ArrayList<>(sender.getProxy().getServers()); 44 | servers.sort(Comparator.comparing(ServerInfo::getServerName)); 45 | 46 | StringBuilder builder = new StringBuilder("§l§6Showing all servers:\n§r"); 47 | for (ServerInfo serverInfo : servers) { 48 | builder.append(buildServerList(serverInfo)).append("\n").append(Color.RESET); 49 | } 50 | 51 | builder.append(Color.GRAY + "Total online players: ") 52 | .append(sender.getProxy().getPlayers().size()) 53 | .append("/") 54 | .append(serviceHandler.getMaxPlayers()); 55 | sender.sendMessage(builder.toString()); 56 | return true; 57 | } 58 | 59 | private String buildServerList(ServerInfo serverInfo) { 60 | StringJoiner joiner = new StringJoiner(","); 61 | for (ProxiedPlayer player : serverInfo.getPlayers()) { 62 | joiner.add(player.getName()); 63 | } 64 | 65 | ServiceInfo service = serviceHandler.getService(serverInfo.getServerName()); 66 | 67 | return String.format("§3[%s] §c%s §a%s-%s %s(%s/%s): §f%s", 68 | serverInfo.getServerName(), 69 | service == null ? "?" : service.getGroup().toUpperCase(), 70 | service == null ? "?" : service.getRegion().toUpperCase(), 71 | service == null ? "?" : service.getBranch().toUpperCase(), 72 | service == null || !service.isFull() ? "§b" : "§5", 73 | serverInfo.getPlayers().size(), 74 | service == null ? "?" : service.getMaxPlayers(), 75 | joiner 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /jbridge-waterdogpe/src/main/java/me/josscoder/jbridge/waterdogpe/task/ServicePongTask.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.waterdogpe.task; 2 | 3 | import dev.waterdog.waterdogpe.ProxyServer; 4 | import dev.waterdog.waterdogpe.network.serverinfo.BedrockServerInfo; 5 | import dev.waterdog.waterdogpe.network.serverinfo.ServerInfo; 6 | import me.josscoder.jbridge.JBridgeCore; 7 | import me.josscoder.jbridge.service.ServiceInfo; 8 | import me.josscoder.jbridge.waterdogpe.JBridgeWaterdogPE; 9 | import org.apache.logging.log4j.Logger; 10 | 11 | import java.net.InetSocketAddress; 12 | import java.util.Map; 13 | 14 | public class ServicePongTask implements Runnable { 15 | 16 | private final ProxyServer proxy = ProxyServer.getInstance(); 17 | private final Logger logger = JBridgeWaterdogPE.getInstance().getLogger(); 18 | 19 | @Override 20 | public void run() { 21 | Map cacheServers = JBridgeCore.getInstance() 22 | .getServiceHandler() 23 | .getServiceInfoMapCache(); 24 | 25 | proxy.getServers().forEach(bedrockServer -> { 26 | if (cacheServers.containsKey(bedrockServer.getServerName())) { 27 | ServiceInfo service = cacheServers.get(bedrockServer.getServerName()); 28 | String address = bedrockServer.getAddress().getHostString() + ":" + bedrockServer.getAddress().getPort(); 29 | 30 | if (!service.getAddress().equalsIgnoreCase(address)) { 31 | bedrockServer.getPlayers().forEach(player -> 32 | player.disconnect("Server IP changes while online, duplicate server?") 33 | ); 34 | proxy.removeServerInfo(bedrockServer.getServerName()); 35 | registerService(service, AddType.UPDATE_ADDRESS); 36 | } 37 | } else { 38 | bedrockServer.getPlayers().forEach(proxiedPlayer -> 39 | proxiedPlayer.disconnect("Server timed out, broken connection?") 40 | ); 41 | proxy.removeServerInfo(bedrockServer.getServerName()); 42 | logger.info(String.format("Removed %s due to timeout...", bedrockServer.getServerName())); 43 | } 44 | }); 45 | 46 | cacheServers.values() 47 | .stream() 48 | .filter(service -> proxy.getServerInfo(service.getShortId()) == null) 49 | .forEach(service -> registerService(service, AddType.NORMAL)); 50 | } 51 | 52 | private enum AddType { 53 | NORMAL, 54 | UPDATE_ADDRESS 55 | } 56 | 57 | private void registerService(ServiceInfo service, AddType addType) { 58 | String[] newAddress = service.getAddress().split(":"); 59 | InetSocketAddress socketAddress = new InetSocketAddress(newAddress[0], Integer.parseInt(newAddress[1])); 60 | 61 | ServerInfo serverInfo = new BedrockServerInfo(service.getShortId(), socketAddress, socketAddress); 62 | boolean registered = proxy.registerServerInfo(serverInfo); 63 | 64 | if (registered) { 65 | switch (addType) { 66 | case NORMAL: 67 | logger.info(String.format("Added %s (%s:%s)", service.getRegionGroupAndShortId(), 68 | newAddress[0], newAddress[1]) 69 | ); 70 | break; 71 | case UPDATE_ADDRESS: 72 | logger.warn(String.format("Server IP for \"%s\" updated!", service.getRegionGroupAndShortId())); 73 | break; 74 | } 75 | } else { 76 | logger.warn(String.format("Could not add server %s because it already exists", 77 | service.getRegionGroupAndShortId() 78 | )); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /jbridge-common/src/main/java/me/josscoder/jbridge/JBridgeCore.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge; 2 | 3 | import com.google.common.cache.Cache; 4 | import com.google.common.cache.CacheBuilder; 5 | import com.google.gson.Gson; 6 | import com.google.gson.GsonBuilder; 7 | import lombok.Getter; 8 | import lombok.Setter; 9 | import me.josscoder.jbridge.logger.ILogger; 10 | import me.josscoder.jbridge.packet.PacketManager; 11 | import me.josscoder.jbridge.service.ServiceHandler; 12 | import me.josscoder.jbridge.service.ServiceInfo; 13 | import redis.clients.jedis.BinaryJedisPubSub; 14 | import redis.clients.jedis.Jedis; 15 | import redis.clients.jedis.JedisPool; 16 | 17 | import java.nio.charset.StandardCharsets; 18 | import java.util.concurrent.ForkJoinPool; 19 | import java.util.concurrent.TimeUnit; 20 | import org.apache.commons.pool2.impl.GenericObjectPoolConfig; 21 | 22 | @Getter 23 | @Setter 24 | public class JBridgeCore { 25 | 26 | @Getter 27 | private static JBridgeCore instance; 28 | 29 | public static final byte[] PACKET_CHANNEL = "jbridge-packet-channel".getBytes(StandardCharsets.UTF_8); 30 | 31 | private boolean debug; 32 | private ILogger logger; 33 | 34 | private JedisPool jedisPool = null; 35 | private Gson gson; 36 | 37 | private BinaryJedisPubSub packetPubSub = null; 38 | 39 | private PacketManager packetManager; 40 | private ServiceHandler serviceHandler; 41 | 42 | private ServiceInfo currentServiceInfo; 43 | 44 | private final Cache serviceInfoCache = CacheBuilder.newBuilder() 45 | .expireAfterWrite(10, TimeUnit.SECONDS) 46 | .build(); 47 | 48 | public JBridgeCore() { 49 | instance = this; 50 | } 51 | 52 | public void boot(String hostname, int port, String password, boolean debug, final ILogger logger) { 53 | if (jedisPool != null) return; 54 | 55 | if (password != null && password.trim().length() > 0) { 56 | jedisPool = new JedisPool(new GenericObjectPoolConfig<>(), hostname, port, 5000, password); 57 | } else { 58 | jedisPool = new JedisPool(new GenericObjectPoolConfig<>(), hostname, port, 5000); 59 | } 60 | 61 | this.debug = debug; 62 | this.logger = logger; 63 | gson = new GsonBuilder().create(); 64 | 65 | packetManager = new PacketManager(); 66 | 67 | ForkJoinPool.commonPool().execute(() -> { 68 | try { 69 | try (Jedis jedis = jedisPool.getResource()) { 70 | jedis.subscribe(packetPubSub = new BinaryJedisPubSub(){ 71 | @Override 72 | public void onMessage(byte[] channel, byte[] message) { 73 | packetManager.handlePacketDecoding(message); 74 | } 75 | 76 | @Override 77 | public void onSubscribe(byte[] channel, int subscribedChannels) { 78 | logger.info("JBridge packet pubSub Started!"); 79 | } 80 | 81 | @Override 82 | public void onUnsubscribe(byte[] channel, int subscribedChannels) { 83 | logger.info("JBridge packet pubSub Stopped!"); 84 | } 85 | }, PACKET_CHANNEL); 86 | } 87 | } catch (Exception e) { 88 | e.printStackTrace(); 89 | try { 90 | logger.info("Sleeping for 1 second before reconnecting"); 91 | Thread.sleep(1000); 92 | } catch (InterruptedException ignored) {} 93 | } 94 | }); 95 | 96 | serviceHandler = new ServiceHandler(); 97 | } 98 | 99 | public ServiceInfo getCurrentServiceInfo() { 100 | return currentServiceInfo == null ? ServiceInfo.empty() : currentServiceInfo; 101 | } 102 | 103 | public void shutdown() { 104 | if (packetPubSub != null) packetPubSub.unsubscribe(); 105 | if (jedisPool != null) jedisPool.close(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /jbridge-waterdogpe/src/main/java/me/josscoder/jbridge/waterdogpe/JBridgeWaterdogPE.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.waterdogpe; 2 | 3 | import dev.waterdog.waterdogpe.command.CommandMap; 4 | import dev.waterdog.waterdogpe.event.EventManager; 5 | import dev.waterdog.waterdogpe.event.defaults.PreTransferEvent; 6 | import dev.waterdog.waterdogpe.event.defaults.ProxyPingEvent; 7 | import dev.waterdog.waterdogpe.event.defaults.ProxyQueryEvent; 8 | import dev.waterdog.waterdogpe.logger.Color; 9 | import dev.waterdog.waterdogpe.network.serverinfo.ServerInfo; 10 | import dev.waterdog.waterdogpe.player.ProxiedPlayer; 11 | import dev.waterdog.waterdogpe.plugin.Plugin; 12 | import dev.waterdog.waterdogpe.utils.config.Configuration; 13 | import lombok.Getter; 14 | import me.josscoder.jbridge.JBridgeCore; 15 | import me.josscoder.jbridge.service.ServiceInfo; 16 | import me.josscoder.jbridge.waterdogpe.command.ServerListCommand; 17 | import me.josscoder.jbridge.waterdogpe.command.WhereAmICommand; 18 | import me.josscoder.jbridge.waterdogpe.task.ServicePongTask; 19 | 20 | import java.util.UUID; 21 | 22 | public class JBridgeWaterdogPE extends Plugin { 23 | 24 | @Getter 25 | private static JBridgeWaterdogPE instance; 26 | 27 | @Override 28 | public void onStartup() { 29 | instance = this; 30 | } 31 | 32 | @Override 33 | public void onEnable() { 34 | loadConfig(); 35 | 36 | Configuration config = getConfig(); 37 | 38 | JBridgeCore jBridgeCore = new JBridgeCore(); 39 | jBridgeCore.boot(config.getString("redis.hostname"), 40 | config.getInt("redis.port"), 41 | config.getString("redis.password"), 42 | config.getBoolean("debug"), 43 | new WaterdogPELogger() 44 | ); 45 | 46 | if (!config.exists("service.id")) { 47 | config.set("service.id", UUID.randomUUID().toString().substring(0, 8)); 48 | config.save(); 49 | } 50 | 51 | ServiceInfo serviceInfo = new ServiceInfo( 52 | config.getString("service.id"), 53 | "", 54 | config.getString("service.group", "proxy"), 55 | config.getString("service.region", "us"), 56 | config.getString("service.branch", "dev"), 57 | -1 58 | ); 59 | jBridgeCore.setCurrentServiceInfo(serviceInfo); 60 | 61 | handleCommands(); 62 | subscribeEvents(); 63 | 64 | int servicesHandlingInterval = config.getInt("service.handling-interval", 5); 65 | 66 | getProxy().getScheduler().scheduleRepeating(new ServicePongTask(), 67 | 20 * servicesHandlingInterval, 68 | true 69 | ); 70 | } 71 | 72 | private void handleCommands() { 73 | CommandMap map = getProxy().getCommandMap(); 74 | map.unregisterCommand("wdlist"); 75 | map.registerCommand(new WhereAmICommand()); 76 | map.registerCommand(new ServerListCommand()); 77 | } 78 | 79 | private void subscribeEvents() { 80 | EventManager manager = getProxy().getEventManager(); 81 | manager.subscribe(ProxyPingEvent.class, this::onPing); 82 | manager.subscribe(ProxyQueryEvent.class, this::onQuery); 83 | manager.subscribe(PreTransferEvent.class, this::onTransfer); 84 | } 85 | 86 | private void onPing(ProxyPingEvent event) { 87 | event.setMaximumPlayerCount(JBridgeCore.getInstance().getServiceHandler().getMaxPlayers()); 88 | } 89 | 90 | private void onQuery(ProxyQueryEvent event) { 91 | event.setMaximumPlayerCount(JBridgeCore.getInstance().getServiceHandler().getMaxPlayers()); 92 | } 93 | 94 | private void onTransfer(PreTransferEvent event) { 95 | ProxiedPlayer player = event.getPlayer(); 96 | ServerInfo targetServer = event.getTargetServer(); 97 | 98 | if (player.getServerInfo() == null || 99 | targetServer == null || 100 | player.getServerInfo().getServerName().equalsIgnoreCase(targetServer.getServerName()) 101 | ) return; 102 | 103 | player.sendMessage(Color.GRAY + "Connecting you to " + targetServer.getServerName()); 104 | } 105 | 106 | @Override 107 | public void onDisable() { 108 | JBridgeCore.getInstance().shutdown(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /jbridge-common/src/main/java/me/josscoder/jbridge/service/ServiceHandler.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.service; 2 | 3 | import me.josscoder.jbridge.JBridgeCore; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Comparator; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.function.Predicate; 10 | import java.util.stream.Collectors; 11 | 12 | public class ServiceHandler { 13 | 14 | public Map getServiceInfoMapCache() { 15 | return JBridgeCore.getInstance().getServiceInfoCache().asMap(); 16 | } 17 | 18 | public ServiceInfo getService(String id) { 19 | return filterServices(service -> service.getId().startsWith(id)) 20 | .stream() 21 | .findFirst() 22 | .orElse(null); 23 | } 24 | 25 | public boolean containsService(String id) { 26 | return getService(id) != null; 27 | } 28 | 29 | public List filterServices(Predicate predicate) { 30 | return getServiceInfoMapCache().values() 31 | .stream() 32 | .filter(predicate) 33 | .collect(Collectors.toList()); 34 | } 35 | 36 | public List getGroupServices(String group) { 37 | return filterServices(service -> service.getGroup().equalsIgnoreCase(group)); 38 | } 39 | 40 | public boolean groupHasServices(String group) { 41 | return getGroupServices(group).size() > 0; 42 | } 43 | 44 | public boolean groupHasService(String group, String id) { 45 | return getGroupServices(group).stream() 46 | .anyMatch(service -> service.getId().startsWith(id)); 47 | } 48 | 49 | public boolean groupHasPlayer(String group, String player) { 50 | return getGroupServices(group).stream() 51 | .anyMatch(service -> service.containsPlayer(player)); 52 | } 53 | 54 | public int getPlayersOnline(String group) { 55 | return getGroupServices(group) 56 | .stream() 57 | .mapToInt(ServiceInfo::getPlayersOnline) 58 | .sum(); 59 | } 60 | 61 | public int getPlayersOnline() { 62 | return getServiceInfoMapCache().values() 63 | .stream() 64 | .mapToInt(ServiceInfo::getPlayersOnline) 65 | .sum(); 66 | } 67 | 68 | public int getMaxPlayers(String group) { 69 | return getGroupServices(group) 70 | .stream() 71 | .mapToInt(ServiceInfo::getMaxPlayers) 72 | .sum(); 73 | } 74 | 75 | public int getMaxPlayers() { 76 | return getServiceInfoMapCache().values() 77 | .stream() 78 | .mapToInt(ServiceInfo::getMaxPlayers) 79 | .sum(); 80 | } 81 | 82 | public enum SortMode { 83 | RANDOM, 84 | LOWEST, 85 | FILL 86 | } 87 | 88 | public ServiceInfo getSortedServiceFromGroups(List groups, SortMode sortMode) { 89 | List serviceList = new ArrayList<>(); 90 | 91 | groups.forEach(group -> serviceList.addAll(getGroupServices(group))); 92 | 93 | return getSortedServiceFromList(serviceList, sortMode); 94 | } 95 | 96 | public ServiceInfo getSortedServiceFromGroup(String group, SortMode sortMode) { 97 | return getSortedServiceFromList(getGroupServices(group), sortMode); 98 | } 99 | 100 | public ServiceInfo getSortedServiceFromList(List serviceList, SortMode sortMode) { 101 | ServiceInfo serviceInfo = null; 102 | 103 | switch (sortMode) { 104 | case RANDOM: 105 | serviceInfo = serviceList.stream() 106 | .findAny() 107 | .orElse(null); 108 | break; 109 | case LOWEST: 110 | serviceInfo = serviceList.stream() 111 | .min(Comparator.comparingInt(ServiceInfo::getPlayersOnline)) 112 | .orElse(null); 113 | break; 114 | case FILL: 115 | serviceInfo = serviceList.stream() 116 | .filter(service -> !service.isFull()) 117 | .max(Comparator.comparingInt(ServiceInfo::getPlayersOnline)) 118 | .orElse(null); 119 | break; 120 | } 121 | 122 | return serviceInfo; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /jbridge-common/src/main/java/me/josscoder/jbridge/packet/PacketManager.java: -------------------------------------------------------------------------------- 1 | package me.josscoder.jbridge.packet; 2 | 3 | import com.google.common.io.ByteArrayDataInput; 4 | import com.google.common.io.ByteArrayDataOutput; 5 | import com.google.common.io.ByteStreams; 6 | import lombok.Getter; 7 | import me.josscoder.jbridge.JBridgeCore; 8 | import me.josscoder.jbridge.packet.base.ServiceCacheUpdatePacket; 9 | import me.josscoder.jbridge.service.ServiceInfo; 10 | import redis.clients.jedis.Jedis; 11 | 12 | import java.util.*; 13 | import java.util.concurrent.CompletableFuture; 14 | 15 | public class PacketManager { 16 | 17 | @Getter 18 | private final Map> registeredPackets = new HashMap<>(); 19 | 20 | @Getter 21 | private final List packetHandlers = new ArrayList<>(); 22 | 23 | public PacketManager() { 24 | subscribePacket(new ServiceCacheUpdatePacket()); 25 | addPacketHandler(new PacketHandler() { 26 | @Override 27 | public void onSend(DataPacket packet) {} 28 | 29 | @Override 30 | public void onReceive(DataPacket packet) { 31 | if (!(packet instanceof ServiceCacheUpdatePacket)) return; 32 | 33 | JBridgeCore core = JBridgeCore.getInstance(); 34 | ServiceInfo cache = core.getGson().fromJson(((ServiceCacheUpdatePacket) packet).cache, ServiceInfo.class); 35 | core.getServiceInfoCache().put(cache.getShortId(), cache); 36 | } 37 | }); 38 | } 39 | 40 | public void subscribePacket(DataPacket ...packets) { 41 | Arrays.stream(packets).forEach(packet -> { 42 | JBridgeCore core = JBridgeCore.getInstance(); 43 | 44 | if (!registeredPackets.containsKey(packet.getPid())) { 45 | registeredPackets.put(packet.getPid(), packet.getClass()); 46 | 47 | if (core.isDebug()) { 48 | core.getLogger().debug(String.format("DataPacket \"%s:%s\" has subscribed!", 49 | packet.getPid(), 50 | packet.getClass().getSimpleName() 51 | )); 52 | } 53 | 54 | return; 55 | } 56 | 57 | core.getLogger().warn(String.format("DataPacket \"%s\" is already subscribed!", 58 | packet.getPid() 59 | )); 60 | }); 61 | } 62 | 63 | public void publishPacket(DataPacket ...packets) { 64 | Arrays.stream(packets).forEach(packet -> CompletableFuture.runAsync(() -> handlePacketEncoding(packet))); 65 | } 66 | 67 | public void handlePacketEncoding(DataPacket packet) { 68 | JBridgeCore core = JBridgeCore.getInstance(); 69 | packet.setSender(core.getCurrentServiceInfo().getShortId()); 70 | 71 | try (Jedis jedis = core.getJedisPool().getResource()) { 72 | ByteArrayDataOutput output = ByteStreams.newDataOutput(); 73 | 74 | output.writeByte(packet.getPid()); 75 | packet.mainEncode(output); 76 | 77 | jedis.publish(JBridgeCore.PACKET_CHANNEL, output.toByteArray()); 78 | packetHandlers.forEach(packetHandler -> packetHandler.onSend(packet)); 79 | 80 | if (!core.isDebug()) return; 81 | 82 | core.getLogger().debug(String.format("DataPacket \"%s:%s\" was encoded and sent!", 83 | packet.getPid(), 84 | packet.getClass().getSimpleName() 85 | )); 86 | } 87 | } 88 | 89 | public DataPacket getPacket(byte pid) { 90 | Class classInstance = registeredPackets.get(pid); 91 | if (classInstance == null) return null; 92 | 93 | try { 94 | return classInstance.getDeclaredConstructor().newInstance(); 95 | } catch (Exception e) { 96 | e.printStackTrace(); 97 | } 98 | return null; 99 | } 100 | 101 | public void handlePacketDecoding(byte[] message) { 102 | ByteArrayDataInput input = ByteStreams.newDataInput(message); 103 | byte pid = input.readByte(); 104 | DataPacket packet = getPacket(pid); 105 | 106 | JBridgeCore core = JBridgeCore.getInstance(); 107 | 108 | if (packet == null) { 109 | if (core.isDebug()) { 110 | core.getLogger().debug(String.format("DataPacket \"%s\" is not subscribed!", pid)); 111 | } 112 | return; 113 | } 114 | 115 | packet.mainDecode(input); 116 | packetHandlers.forEach(packetHandler -> { 117 | Runnable runnable = () -> packetHandler.onReceive(packet); 118 | 119 | if (packet instanceof AsyncPacket) { 120 | CompletableFuture.runAsync(runnable); 121 | } else { 122 | runnable.run(); 123 | } 124 | }); 125 | 126 | if (!core.isDebug()) return; 127 | 128 | core.getLogger().debug(String.format("DataPacket \"%s:%s\" was decoded and handled!", 129 | packet.getPid(), 130 | packet.getClass().getSimpleName() 131 | )); 132 | } 133 | 134 | public void addPacketHandler(PacketHandler packetHandler) { 135 | packetHandlers.add(packetHandler); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://i.imgur.com/SJJEDF4.png) 2 | 3 | 4 |

5 | JBridge facilitates communication in minecraft bedrock networks, between Nukkit servers and WaterdogPE proxies using Redis 6 | 7 | ## Features 📃 8 | 9 | - [x] Server management system available for both clients 10 | 11 | 12 | ![](https://i.imgur.com/ymmYZm9.png) 13 | Example: This code is used in the same way for jbridge-lobby-nukkit and jbridge-lobby-waterdogpe. 14 | 15 | as the first parameter a list with the group of services for the lobby and as the second parameter the sort mode, in this case LOWEST, the algorithm will choose the lobby server with the lowest number of players to balance the lobby servers 16 | 17 | 18 | - [x] Packet system using message broker 19 | 20 | We have a packet system in which you can create your own packets and manage them in a simple way, of course if you are a developer, as an additional note you can create async packets, especially for tasks that can cause lag, 21 | when a packet arrives at a service, it will execute the async by calling the method "onReceive(DataPacket packet);" of your PacketHandler 22 | 23 | - [x] Lobby System, [JBirdgeLobby](https://github.com/Josscoder/JBridge/tree/release/jbridge-lobby) 24 | 25 | As an example to use jbridge, we have a lobby balancing system, compatible with nukkit and waterdogpe, from nukkit you will have the /hub command available, 26 | From waterdogpe you will have a Custom JoinHandler (which will look for a balanced server in the lobby service list and send the player) and a Custom ReconnectHandler (which will be used when the player is disconnected from an old service for some reason, and will be sent to a balanced lobby). 27 | 28 | - [x] Custom commands to manage servers/services 29 | 30 | By WaterdogPE we have the commands: 31 | 32 | - /wdlist: command waterdgope, overwritten for more compatibility with JBridge, will show the list of servers, with their ids, players, group, region and branch (PERMISSION: waterdog.command.list.permission, ALIASES: "servicelist", "services", "servers", "glist", "rglist") 33 | - /whereami: Provide information about the proxy you are on (ALIASES: "connection") 34 | 35 | By Nukkit we have the commands: 36 | 37 | - /transfer: It will send you to the server that you write, you can write the first two letters of the server and it will autocomplete (PERMISSION: jbrdige.command.transfer) 38 | - /whereami: Provide information about the server you are on (ALIASES: "connection") 39 | 40 | 41 | - [x] Custom Query and Ping events 42 | 43 | Each time a new service is added, the new available slots are added 44 | 45 | ![](https://i.imgur.com/G2e9aKF.png) 46 | ![](https://i.imgur.com/YQJrVsA.png) 47 | ![](https://i.imgur.com/qBUSlX8.png) 48 | ![](https://i.imgur.com/EzED4Oi.png) 49 | 50 | - [x] Server Cluster System 51 | 52 | As I mentioned before, jbridge can manage services by groups, you can see more about it [here](https://github.com/Josscoder/JBridge/blob/release/jbridge-common/src/main/java/me/josscoder/jbridge/service/ServiceHandler.java) 53 | 54 | - [x] Automatic server registration at WaterdogPE without the need to specify servers manually in the Waterdog config. 55 | 56 | ![](https://i.imgur.com/Tt0j4w7.png) 57 | ![](https://i.imgur.com/gpkr0M6.png) 58 | 59 | - [ ] Multi-group support 60 | 61 | ## Download and setup 🛒 62 | 63 | [![Java CI with Maven](https://github.com/Josscoder/JBridge/actions/workflows/maven.yml/badge.svg)](https://github.com/Josscoder/JBridge/actions/workflows/maven.yml) 64 | 65 |

66 | Nukkit 67 | 68 | 1) Download the latest jbridge-nukkit.jar [here](https://github.com/Josscoder/JBridge/releases/latest) 69 | 2) Place the .jar in the plugins/ folder of your server 70 | 3) Start your server 71 | 4) Configure the service and your redis client in plugins/JBridge/config.yml 72 |
73 | 74 |
75 | Waterdog 76 | 77 | 1) Download the latest jbridge-waterdogpe.jar [here](https://github.com/Josscoder/JBridge/releases/latest) 78 | 2) Place the .jar in the plugins/ folder of your server 79 | 3) Start your server 80 | 4) Configure the service and your redis client in plugins/JBridge/config.yml 81 |
82 | 83 |
84 | How to configure 85 | 86 | ```yml 87 | debug: true #Just for development, to show the internal process of JBridge commons 88 | #recommendation to set this to false to avoid saturating the console with logs 89 | 90 | redis: #the configuration of your redis 91 | hostname: "localhost" 92 | port: 6379 93 | password: "yourpasswordhere" 94 | 95 | service: #configuration of your service 96 | id: "hub-1" #the id of your service, if you remove this section, the system will generate a custom id each time the plugin is enabled 97 | group: "hub" #the group your service belongs to 98 | region: "us" #the region to which your service belongs 99 | branch: "dev" #the branch of your service, if it is "dev" the address will automatically change to "127.0.0.1" 100 | address: "0.0.0.0" #the address of your service, if you remove this section you will get the address that is in server.propierties 101 | ``` 102 |
103 | 104 | ## For developers 🧑‍💻 105 | ### Add JBridge to your project 106 | 107 | [![](https://jitpack.io/v/Josscoder/JBridge.svg)](https://jitpack.io/#Josscoder/JBridge) 108 | 109 | ### Common 110 | ```xml 111 | 112 | 113 | jitpack.io 114 | https://jitpack.io 115 | 116 | 117 | 118 | 119 | 120 | com.github.Josscoder.JBridge 121 | jbridge-common 122 | TAG 123 | 124 | 125 | ``` 126 | 127 | ### Nukkit 128 | ```xml 129 | 130 | 131 | jitpack.io 132 | https://jitpack.io 133 | 134 | 135 | 136 | 137 | 138 | com.github.Josscoder.JBridge 139 | jbridge-nukkit 140 | TAG 141 | 142 | 143 | ``` 144 | 145 | ### WaterdogPE 146 | ```xml 147 | 148 | 149 | jitpack.io 150 | https://jitpack.io 151 | 152 | 153 | 154 | 155 | 156 | com.github.Josscoder.JBridge 157 | jbridge-waterdogpe 158 | TAG 159 | 160 | 161 | ``` 162 | 163 | ### Code examples 164 | 165 |
166 | Register packets 167 | 168 | ```java 169 | import com.google.common.io.ByteArrayDataInput; 170 | import com.google.common.io.ByteArrayDataOutput; 171 | import me.josscoder.jbridge.JBridgeCore; 172 | import me.josscoder.jbridge.packet.DataPacket; 173 | import me.josscoder.jbridge.packet.PacketManager; 174 | 175 | public class Test { 176 | 177 | static class HelloWorldPacket extends DataPacket { 178 | 179 | public String message; 180 | 181 | public HelloWorldPacket() { 182 | super((byte) 0x53); 183 | } 184 | 185 | @Override 186 | public void encode(ByteArrayDataOutput output) { 187 | output.writeUTF(message); 188 | } 189 | 190 | @Override 191 | public void decode(ByteArrayDataInput input) { 192 | message = input.readUTF(); 193 | } 194 | } 195 | 196 | public static void main(String[] args) { 197 | PacketManager packetManager = JBridgeCore.getInstance().getPacketManager(); 198 | 199 | packetManager.subscribePacket(new HelloWorldPacket()); 200 | } 201 | } 202 | ``` 203 |
204 |
205 | Register PacketHandlers 206 | 207 | ```java 208 | import com.google.common.io.ByteArrayDataInput; 209 | import com.google.common.io.ByteArrayDataOutput; 210 | import me.josscoder.jbridge.JBridgeCore; 211 | import me.josscoder.jbridge.packet.DataPacket; 212 | import me.josscoder.jbridge.packet.PacketHandler; 213 | import me.josscoder.jbridge.packet.PacketManager; 214 | 215 | public class Test { 216 | 217 | static class HelloWorldPacket extends DataPacket { 218 | 219 | public String message; 220 | 221 | public HelloWorldPacket() { 222 | super((byte) 0x53); 223 | } 224 | 225 | @Override 226 | public void encode(ByteArrayDataOutput output) { 227 | output.writeUTF(message); 228 | } 229 | 230 | @Override 231 | public void decode(ByteArrayDataInput input) { 232 | message = input.readUTF(); 233 | } 234 | } 235 | 236 | public static void main(String[] args) { 237 | PacketManager packetManager = JBridgeCore.getInstance().getPacketManager(); 238 | 239 | packetManager.subscribePacket(new HelloWorldPacket()); 240 | 241 | packetManager.addPacketHandler(new PacketHandler() { 242 | @Override 243 | public void onSend(DataPacket packet) { 244 | if (packet instanceof HelloWorldPacket) { 245 | System.out.println("Sending hello world message!!"); 246 | } 247 | } 248 | 249 | @Override 250 | public void onReceive(DataPacket packet) { 251 | if (packet instanceof HelloWorldPacket) { 252 | System.out.println(((HelloWorldPacket) packet).message); 253 | } 254 | } 255 | }); 256 | } 257 | } 258 | ``` 259 |
260 |
261 | Using AsyncPacket 262 | 263 | ```java 264 | import com.google.common.io.ByteArrayDataInput; 265 | import com.google.common.io.ByteArrayDataOutput; 266 | import me.josscoder.jbridge.JBridgeCore; 267 | import me.josscoder.jbridge.packet.AsyncPacket; 268 | import me.josscoder.jbridge.packet.DataPacket; 269 | import me.josscoder.jbridge.packet.PacketHandler; 270 | import me.josscoder.jbridge.packet.PacketManager; 271 | 272 | public class Test { 273 | 274 | static class HelloWorldPacket extends DataPacket implements AsyncPacket { 275 | 276 | public String message; 277 | 278 | public HelloWorldPacket() { 279 | super((byte) 0x53); 280 | } 281 | 282 | @Override 283 | public void encode(ByteArrayDataOutput output) { 284 | output.writeUTF(message); 285 | } 286 | 287 | @Override 288 | public void decode(ByteArrayDataInput input) { 289 | message = input.readUTF(); 290 | } 291 | } 292 | 293 | public static void main(String[] args) { 294 | PacketManager packetManager = JBridgeCore.getInstance().getPacketManager(); 295 | 296 | packetManager.subscribePacket(new HelloWorldPacket()); 297 | 298 | packetManager.addPacketHandler(new PacketHandler() { 299 | @Override 300 | public void onSend(DataPacket packet) { 301 | if (packet instanceof HelloWorldPacket) { 302 | System.out.println("Sending hello world message!!"); 303 | } 304 | } 305 | 306 | @Override 307 | public void onReceive(DataPacket packet) { 308 | if (packet instanceof HelloWorldPacket) { 309 | System.out.println("HI!! receiving async packet " + ((HelloWorldPacket) packet).message); 310 | } 311 | } 312 | }); 313 | } 314 | } 315 | ``` 316 |
317 | 318 | ### Included libraries 🛠️ 319 | 320 | - [Lombok: Used as Annotation library](https://projectlombok.org/) 321 | - [Jedis: Used as Message Broker](https://github.com/redis/jedis) 322 | - [Google Guava: Used as coder, decoder & cache handler](https://github.com/google/guava) 323 | - [WaterdogPE: Minecraft Bedrock Proxy](https://github.com/WaterdogPE/WaterdogPE) 324 | - [Nukkit: Minecraft Bedrock Software](https://github.com/CloudburstMC/Nukkit) 325 | 326 | ## Credits 🙋‍♂️🙋‍♀️ 327 | 328 | - [DinamycServers: cache handler & redis implementation](https://github.com/theminecoder/DynamicServers) 329 | - [PacketListenerAPI: packet handler system](https://www.spigotmc.org/resources/api-packetlistenerapi.2930/) 330 | - [NetherGames: Inspiration](https://forums.nethergames.org/threads/netsys-network-communication-system.10514/) 331 | 332 | ## License 🚩 333 | JBridge is licensed under the permissive Apache License 2.0 LICENSE. Please see [`LICENSE`](https://github.com/Josscoder/JBridge/blob/release/LICENSE) for more information. 334 | --------------------------------------------------------------------------------