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