diff --git a/README.md b/README.md index 7b71f4ad2..a993e9a15 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Depenizen - **Support Discord**: https://discord.gg/Q6pZGSR - **Builds (Download)**: https://ci.citizensnpcs.co/job/Depenizen/ - **Supported Plugin List**: [Docs/BukkitPlugins.md](Docs/BukkitPlugins.md) +- **DepenizenBungee**: https://github.com/DenizenScript/DepenizenBungee ### Version 2.x notice: @@ -13,8 +14,8 @@ Depenizen version 2.x is a rewrite of many of the core functions of Depenizen. If you need the 1.x (legacy) version, you can find the last official build of that here: https://ci.citizensnpcs.co/job/Depenizen/476/ -**Status:** Depenizen 2.x rewrite has rewritten all the basic core functionality, but the Bungee bridge rebuild is pending. -The plan for this bungee bridge is to be significantly different from a usage perspective, so you **will** have to change configuration details when this is available. +**Status:** Depenizen 2.x rewrite has rewritten all the basic core functionality, but the Bungee bridge rebuild is incomplete. +The new bungee bridge is significantly different from a usage perspective, so you **will** have to change configuration details when this is available. ### Licensing pre-note: diff --git a/pom.xml b/pom.xml index aead90085..a89871e2c 100644 --- a/pom.xml +++ b/pom.xml @@ -67,6 +67,14 @@ jar provided + + + io.netty + netty-all + 4.1.34.Final + jar + provided + com.github.TheComputerGeek2 diff --git a/src/main/java/com/denizenscript/depenizen/bukkit/Depenizen.java b/src/main/java/com/denizenscript/depenizen/bukkit/Depenizen.java index f4560c3b7..49a6997bc 100644 --- a/src/main/java/com/denizenscript/depenizen/bukkit/Depenizen.java +++ b/src/main/java/com/denizenscript/depenizen/bukkit/Depenizen.java @@ -1,8 +1,10 @@ package com.denizenscript.depenizen.bukkit; import com.denizenscript.depenizen.bukkit.bridges.*; +import com.denizenscript.depenizen.bukkit.bungee.BungeeBridge; import com.denizenscript.depenizen.bukkit.utilities.BridgeLoadException; import net.aufdemrand.denizen.utilities.debugging.dB; +import net.aufdemrand.denizencore.utilities.CoreUtilities; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; @@ -22,12 +24,30 @@ public class Depenizen extends JavaPlugin { @Override public void onEnable() { dB.log("Depenizen loading..."); + saveDefaultConfig(); instance = this; registerCoreBridges(); for (Map.Entry> bridge : allBridges.entrySet()) { loadBridge(bridge.getKey(), bridge.getValue()); } - dB.log("Depenizen loaded! " + loadedBridges.size() + " bridges loaded (of " + allBridges.size() + " available)"); + try { + checkLoadBungeeBridge(); + } + catch (Throwable ex) { + dB.echoError("Cannot load Depenizen-Bungee bridge: Internal exception was thrown!"); + dB.echoError(ex); + } + dB.log("Depenizen loaded! " + loadedBridges.size() + " plugin bridge(s) loaded (of " + allBridges.size() + " available)"); + } + + public void checkLoadBungeeBridge() { + String bungeeServer = getConfig().getString("Bungee server address", "none"); + if (CoreUtilities.toLowerCase(bungeeServer).equals("none")) { + dB.log("Depenizen will not load bungee bridge."); + return; + } + new BungeeBridge().init(bungeeServer, getConfig().getInt("Bungee server port", 25565)); + dB.log("Depenizen loaded bungee bridge!"); } public void loadBridge(String name, Supplier bridgeSupplier) { diff --git a/src/main/java/com/denizenscript/depenizen/bukkit/bungee/BungeeBridge.java b/src/main/java/com/denizenscript/depenizen/bukkit/bungee/BungeeBridge.java new file mode 100644 index 000000000..49eccfc1f --- /dev/null +++ b/src/main/java/com/denizenscript/depenizen/bukkit/bungee/BungeeBridge.java @@ -0,0 +1,187 @@ +package com.denizenscript.depenizen.bukkit.bungee; + +import com.denizenscript.depenizen.bukkit.properties.bungee.BungeePlayerProperties; +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import net.aufdemrand.denizen.objects.dPlayer; +import net.aufdemrand.denizen.utilities.debugging.dB; +import net.aufdemrand.denizencore.objects.properties.PropertyParser; + +import java.net.SocketAddress; + +public class BungeeBridge { + + public static BungeeBridge instance; + + public static class NettyExceptionHandler extends ChannelDuplexHandler { + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + dB.echoError(cause); + } + + @Override + public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) { + ctx.connect(remoteAddress, localAddress, promise.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) { + if (!future.isSuccess()) { + dB.echoError(future.cause()); + } + } + })); + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) { + ctx.write(msg, promise.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) { + if (!future.isSuccess()) { + dB.echoError(future.cause()); + } + } + })); + } + } + + public static class ClientHandler extends ChannelInboundHandlerAdapter { + + public static final byte[] FAKE_HANDSHAKE = new byte[] { + 5 + 5 + 3, // Packet length prefix + 0, // Packet ID 0 + 20, // "Protocol 20" + 1 + 1 + 5, // 1-byte host name, 1 byte null, 5 bytes 'depen' + 68, // Host name is the letter 'D' + 0, // null byte to sneak in our identifier + (byte)'d', (byte)'e', (byte)'p', (byte)'e', (byte)'n', // Special identifier 'depen' + 80, 0, // "We're connecting to port 80" + 1 // We request protocol 1: ping status (to avoid uneeded proxy functionality) + }; + + public ByteBuf tmp; + + public void fail(String reason) { + dB.echoError("Depenizen-Bungee connection failed: " + reason); + channel.close(); + } + + public static enum Stage { + AWAIT_HEADER, + AWAIT_DATA + } + + public Channel channel; + + public int waitingLength; + + public int packetId; + + public Stage currentStage = Stage.AWAIT_HEADER; + + @Override + public void handlerAdded(ChannelHandlerContext ctx) { + tmp = ctx.alloc().buffer(4); + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) { + dB.log("Depenizen-Bungee connection ended."); + tmp.release(); + tmp = null; + // TODO: trigger reconnect (after a delay) + } + + @Override + public void channelActive(ChannelHandlerContext ctx) { + dB.log("Depenizen-Bungee bridge sending handshake..."); + // Send a fake minecraft handshake to get us in + ByteBuf handshake = ctx.alloc().buffer(FAKE_HANDSHAKE.length); + handshake.writeBytes(FAKE_HANDSHAKE); + ctx.writeAndFlush(handshake); // Will release handshake + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + ByteBuf m = (ByteBuf) msg; + tmp.writeBytes(m); + m.release(); + if (currentStage == Stage.AWAIT_HEADER) { + if (tmp.readableBytes() >= 8) { + waitingLength = tmp.readInt(); + packetId = tmp.readInt(); + currentStage = Stage.AWAIT_DATA; + /*if (!DepenizenBungee.instance.packets.containsKey(packetId)) { + fail("Invalid packet id: " + packetId); + return; + }*/ + } + } + if (currentStage == Stage.AWAIT_DATA) { + if (tmp.readableBytes() >= waitingLength) { + try { + //DepenizenBungee.instance.packets.get(packetId).process(this, tmp); + currentStage = Stage.AWAIT_HEADER; + } + catch (Throwable ex) { + ex.printStackTrace(); + fail("Internal exception."); + return; + } + } + } + } + } + + public Channel channel; + + public NioEventLoopGroup workerGroup; + + public String address; + + public int port; + + public void sendPacket(Packet packet) { + ByteBuf buf = channel.alloc().buffer(); + packet.writeTo(buf); + ByteBuf header = channel.alloc().buffer(); + header.writeInt(buf.writerIndex()); + header.writeInt(packet.getPacketId()); + channel.write(header); + channel.writeAndFlush(buf); + } + + public void init(String address, int port) { + this.address = address; + this.port = port; + workerGroup = new NioEventLoopGroup(); + connect(); + successInit(); + } + + public void connect() { + Bootstrap b = new Bootstrap(); + b.group(workerGroup); + b.channel(NioSocketChannel.class); + b.option(ChannelOption.SO_KEEPALIVE, true); + b.handler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) { + ClientHandler handler = new ClientHandler(); + handler.channel = ch; + ch.pipeline().addLast(handler).addLast(new NettyExceptionHandler()); + channel = ch; + } + }); + b.connect(address, port); + } + + public void successInit() { + instance = this; + PropertyParser.registerProperty(BungeePlayerProperties.class, dPlayer.class); + } +} diff --git a/src/main/java/com/denizenscript/depenizen/bukkit/bungee/BungeeHelpers.java b/src/main/java/com/denizenscript/depenizen/bukkit/bungee/BungeeHelpers.java new file mode 100644 index 000000000..fa944df64 --- /dev/null +++ b/src/main/java/com/denizenscript/depenizen/bukkit/bungee/BungeeHelpers.java @@ -0,0 +1,15 @@ +package com.denizenscript.depenizen.bukkit.bungee; + +import com.denizenscript.depenizen.bukkit.bungee.packets.out.SendPlayerPacket; + +import java.util.UUID; + +public class BungeeHelpers { + + public static void sendPlayer(UUID id, String server) { + SendPlayerPacket packet = new SendPlayerPacket(); + packet.playerToSend = id; + packet.serverTarget = server; + BungeeBridge.instance.sendPacket(packet); + } +} diff --git a/src/main/java/com/denizenscript/depenizen/bukkit/bungee/Packet.java b/src/main/java/com/denizenscript/depenizen/bukkit/bungee/Packet.java new file mode 100644 index 000000000..31c7e488c --- /dev/null +++ b/src/main/java/com/denizenscript/depenizen/bukkit/bungee/Packet.java @@ -0,0 +1,10 @@ +package com.denizenscript.depenizen.bukkit.bungee; + +import io.netty.buffer.ByteBuf; + +public abstract class Packet { + + public abstract int getPacketId(); + + public abstract void writeTo(ByteBuf buf); +} diff --git a/src/main/java/com/denizenscript/depenizen/bukkit/bungee/packets/out/SendPlayerPacket.java b/src/main/java/com/denizenscript/depenizen/bukkit/bungee/packets/out/SendPlayerPacket.java new file mode 100644 index 000000000..b1f92e6a2 --- /dev/null +++ b/src/main/java/com/denizenscript/depenizen/bukkit/bungee/packets/out/SendPlayerPacket.java @@ -0,0 +1,28 @@ +package com.denizenscript.depenizen.bukkit.bungee.packets.out; + +import com.denizenscript.depenizen.bukkit.bungee.Packet; +import com.google.common.base.Charsets; +import io.netty.buffer.ByteBuf; + +import java.util.UUID; + +public class SendPlayerPacket extends Packet { + + public UUID playerToSend; + + public String serverTarget; + + @Override + public int getPacketId() { + return 10; + } + + @Override + public void writeTo(ByteBuf buf) { + buf.writeLong(playerToSend.getMostSignificantBits()); + buf.writeLong(playerToSend.getLeastSignificantBits()); + byte[] serverTargetBytes = serverTarget.getBytes(Charsets.UTF_8); + buf.writeInt(serverTargetBytes.length); + buf.writeBytes(serverTargetBytes); + } +} diff --git a/src/main/java/com/denizenscript/depenizen/bukkit/properties/bungee/BungeePlayerProperties.java b/src/main/java/com/denizenscript/depenizen/bukkit/properties/bungee/BungeePlayerProperties.java new file mode 100644 index 000000000..8646afd91 --- /dev/null +++ b/src/main/java/com/denizenscript/depenizen/bukkit/properties/bungee/BungeePlayerProperties.java @@ -0,0 +1,81 @@ +package com.denizenscript.depenizen.bukkit.properties.bungee; + +import com.denizenscript.depenizen.bukkit.bungee.BungeeHelpers; +import net.aufdemrand.denizen.objects.dPlayer; +import net.aufdemrand.denizencore.objects.Mechanism; +import net.aufdemrand.denizencore.objects.dObject; +import net.aufdemrand.denizencore.objects.properties.Property; +import net.aufdemrand.denizencore.tags.Attribute; + +public class BungeePlayerProperties implements Property { + + public static boolean describes(dObject entity) { + return entity instanceof dPlayer; + } + + public static BungeePlayerProperties getFrom(dObject player) { + if (!describes(player)) { + return null; + } + else { + return new BungeePlayerProperties((dPlayer) player); + } + } + + public static final String[] handledTags = new String[] { + }; + + public static final String[] handledMechs = new String[] { + "send_to" + }; + + + /////////////////// + // Instance Fields and Methods + ///////////// + + private BungeePlayerProperties(dPlayer plr) { + player = plr; + } + + dPlayer player; + + ///////// + // Property Methods + /////// + + @Override + public String getPropertyString() { + return null; + } + + @Override + public String getPropertyId() { + return "BungeePlayer"; + } + + + /////////// + // dObject Attributes + //////// + + @Override + public String getAttribute(Attribute attribute) { + return null; + } + + @Override + public void adjust(Mechanism mechanism) { + + // <--[mechanism] + // @object dPlayer + // @name send_to + // @input Element + // @description + // Sends the player to the specified Bungee server. + // --> + if ((mechanism.matches("send_to") && mechanism.hasValue())) { + BungeeHelpers.sendPlayer(player.getOfflinePlayer().getUniqueId(), mechanism.getValue().asString()); + } + } + } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 000000000..b73e613c9 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,8 @@ +# Depenizen config + +# Bungee server address (if any). Exclude the port. +# For example: "localhost" +# Use "none" to disable Bungee support functionality. +Bungee server address: none +# Set to the bungee server's primary port (if in use). +Bungee server port: 22565