Skip to content

Instantly share code, notes, and snippets.

@tcmal
Last active April 11, 2020 01:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tcmal/da20a55d173f9d5b82575984c4761683 to your computer and use it in GitHub Desktop.
Save tcmal/da20a55d173f9d5b82575984c4761683 to your computer and use it in GitHub Desktop.
Spigot server bridge
/*
* Copyright 2020 tcmal
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
package xyz.tcmal.mc.experiment;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import java.io.Serializable;
/**
* Convenience class that makes the inheritor Serializable and allows it to be dispatched as an event.
*
* @see ServerBridge
* This is based off the stub at https://www.spigotmc.org/wiki/using-the-event-api
*/
public abstract class RemoteEvent extends Event implements Serializable {
private static final HandlerList HANDLERS = new HandlerList();
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
public static HandlerList getHandlerList() {
return HANDLERS;
}
}
/*
* Copyright 2020 tcmal
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
package xyz.tcmal.mc.experiment;
import com.google.common.collect.Iterables;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.messaging.PluginMessageListener;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* Allows sending objects over the BungeeCord plugin messaging channel, as well as recieving incoming ones.
*
* To send an event, make a class that extends RemoteEvent, register it with registerSubChannel, then use forwardEvent.
*
* Received events are dispatched using Bukkit's inbuilt event system.
*
* @author tcmal
*/
public class ServerBridge implements PluginMessageListener {
/**
* The plugin using this bridge.
*/
JavaPlugin plugin;
/**
* Maps the name of the subchannel to the class that we expect to be sent over this subchannel
*/
Map<String, Class<? extends RemoteEvent>> subChannelMap;
/**
* subChannelMap, but in reverse
*/
Map<Class<? extends RemoteEvent>, String> reverseSubChannelMap;
/**
* Registers this bridge as an event listener
* @param plugin Your plugin, for using Bukkit's API.
*/
public ServerBridge(JavaPlugin plugin) {
this.plugin = plugin;
this.subChannelMap = new HashMap<>();
this.reverseSubChannelMap = new HashMap<>();
this.plugin.getServer().getMessenger().registerOutgoingPluginChannel(this.plugin, "BungeeCord");
this.plugin.getServer().getMessenger().registerIncomingPluginChannel(this.plugin, "BungeeCord", this);
}
/**
* Specify the type expected to come down the given sub-channel.
* You need to do this before you can receive events for that sub-channel.
* @param subChannel The name of the sub-channel. Consider keeping this as a constant somewhere, or just using the class name.
* @param clazz The class that's going to be going over this subchannel
*/
public void registerSubChannel(String subChannel, Class<? extends RemoteEvent> clazz) {
subChannelMap.put(subChannel, clazz);
reverseSubChannelMap.put(clazz, subChannel);
}
/**
* Forward an event to the specified server, using the sender to communicate with BungeeCord.
* @param event The event to send. Must be registered with registerSubChannel
* @param targetServer The name of the target on the bungee instance, or "ALL"
* @param sender The player to send the plugin message through
* @return If sending succeeded.
*/
public boolean forwardEvent(RemoteEvent event, String targetServer, Player sender) {
// Which channel to send it down
String subchannel = reverseSubChannelMap.get(event.getClass());
if (subchannel == null)
return false;
try {
// Prepare packet to send
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(event);
byte[] msg = baos.toByteArray();
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Forward");
out.writeUTF(targetServer);
out.writeUTF(subchannel);
out.writeShort(msg.length);
out.write(msg);
// Do the sending
sender.sendPluginMessage(this.plugin, "BungeeCord", out.toByteArray());
return true;
} catch (IOException e) {
// TODO
e.printStackTrace();
}
return false;
}
/**
* See above. Player is the first one to join the server
*/
public boolean forwardEvent(RemoteEvent event, String targetServer) {
return this.forwardEvent(event, targetServer, Objects.requireNonNull(Iterables.getFirst(Bukkit.getOnlinePlayers(), null)));
}
/**
* See above. targetServer is ALL (which excludes the current one)
*/
public boolean forwardEvent(RemoteEvent event) {
return this.forwardEvent(event, "ALL");
}
@Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
// This shouldn't happen, but just in case.
if (!channel.equals("BungeeCord")) {
return;
}
// Read which sub-channel is being used.
ByteArrayDataInput in = ByteStreams.newDataInput(message);
String subchannel = in.readUTF();
// Get which class we expect it to be
Class<? extends RemoteEvent> targetClass = subChannelMap.get(subchannel);
if (targetClass == null) {
return; // Not been registered, so it's probably for someone else.
}
try {
// Deserialise the object
short len = in.readShort();
byte[] obj = new byte[len];
in.readFully(obj);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(obj));
RemoteEvent event = targetClass.cast(ois.readObject());
// Call the event
Bukkit.getPluginManager().callEvent(event);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
@GabrielAtlas
Copy link

The Bukkit.getPluginManager (). CallEvent (event); doesn't work when the onPluginMessageReceived method is called

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment