diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-networking-plugin/pom.xml b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-networking-plugin/pom.xml
new file mode 100644
index 0000000000..52ed212472
--- /dev/null
+++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-networking-plugin/pom.xml
@@ -0,0 +1,54 @@
+
+
+
+
+ commandapi-bukkit
+ dev.jorel
+ 9.4.0-SNAPSHOT
+
+
+ 4.0.0
+
+ commandapi-bukkit-networking-plugin
+
+
+
+ dev.jorel
+ commandapi-core
+ ${project.version}
+ compile
+
+
+ org.spigotmc
+ spigot-api
+ ${paper.version}
+ provided
+
+
+
+
+
+
+ src/main/resources
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+
+
+ package
+
+ shade
+
+
+ true
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-networking-plugin/src/main/java/dev/jorel/commandapi/CommandAPINetworkingMain.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-networking-plugin/src/main/java/dev/jorel/commandapi/CommandAPINetworkingMain.java
new file mode 100644
index 0000000000..490ff1a7d6
--- /dev/null
+++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-networking-plugin/src/main/java/dev/jorel/commandapi/CommandAPINetworkingMain.java
@@ -0,0 +1,24 @@
+package dev.jorel.commandapi;
+
+import org.bukkit.plugin.java.JavaPlugin;
+
+import dev.jorel.commandapi.network.BukkitNetworkingCommandAPIMessenger;
+
+/**
+ * Main CommandAPI networking plugin entrypoint
+ */
+public class CommandAPINetworkingMain extends JavaPlugin {
+ private BukkitNetworkingCommandAPIMessenger messenger;
+
+ @Override
+ public void onEnable() {
+ messenger = new BukkitNetworkingCommandAPIMessenger(this);
+ }
+
+ /**
+ * @return The {@link BukkitNetworkingCommandAPIMessenger} handling packets.
+ */
+ public BukkitNetworkingCommandAPIMessenger getMessenger() {
+ return messenger;
+ }
+}
diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-networking-plugin/src/main/java/dev/jorel/commandapi/network/BukkitHandshakePacketHandler.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-networking-plugin/src/main/java/dev/jorel/commandapi/network/BukkitHandshakePacketHandler.java
new file mode 100644
index 0000000000..6c3a660b0c
--- /dev/null
+++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-networking-plugin/src/main/java/dev/jorel/commandapi/network/BukkitHandshakePacketHandler.java
@@ -0,0 +1,21 @@
+package dev.jorel.commandapi.network;
+
+import dev.jorel.commandapi.CommandAPINetworkingMain;
+import dev.jorel.commandapi.network.packets.SetVersionPacket;
+import org.bukkit.entity.Player;
+
+/**
+ * A {@link HandshakePacketHandler} for handling {@link CommandAPIPacket}s sent to Bukkit by {@link Player} connections.
+ */
+public class BukkitHandshakePacketHandler implements HandshakePacketHandler {
+ private final CommandAPINetworkingMain plugin;
+
+ protected BukkitHandshakePacketHandler(CommandAPINetworkingMain plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void handleSetVersionPacket(Player sender, SetVersionPacket packet) {
+ plugin.getMessenger().setProtocolVersion(sender, packet.protocolVersion());
+ }
+}
diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-networking-plugin/src/main/java/dev/jorel/commandapi/network/BukkitNetworkingCommandAPIMessenger.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-networking-plugin/src/main/java/dev/jorel/commandapi/network/BukkitNetworkingCommandAPIMessenger.java
new file mode 100644
index 0000000000..3eafe0154e
--- /dev/null
+++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-networking-plugin/src/main/java/dev/jorel/commandapi/network/BukkitNetworkingCommandAPIMessenger.java
@@ -0,0 +1,106 @@
+package dev.jorel.commandapi.network;
+
+import dev.jorel.commandapi.CommandAPINetworkingMain;
+import dev.jorel.commandapi.network.packets.SetVersionPacket;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.bukkit.event.player.PlayerRegisterChannelEvent;
+import org.bukkit.plugin.messaging.Messenger;
+import org.bukkit.plugin.messaging.PluginMessageListener;
+
+import java.util.HashMap;
+import java.util.Map;
+
+// Based on https://www.spigotmc.org/wiki/bukkit-bungee-plugin-messaging-channel/
+/**
+ * A {@link CommandAPIMessenger} for sending and receiving messages to and from CommandAPIBukkit and {@link Player}s
+ * connected to the Bukkit server.
+ */
+public class BukkitNetworkingCommandAPIMessenger extends CommandAPIMessenger implements PluginMessageListener, Listener {
+ private final CommandAPINetworkingMain plugin;
+ private final Map protocolVersionPerPlayer;
+
+ /**
+ * Creates a new {@link BukkitNetworkingCommandAPIMessenger}.
+ *
+ * @param plugin The plugin sending and receiving messages.
+ */
+ public BukkitNetworkingCommandAPIMessenger(CommandAPINetworkingMain plugin) {
+ super(new BukkitNetworkingPacketHandlerProvider(plugin));
+ this.plugin = plugin;
+
+ this.protocolVersionPerPlayer = new HashMap<>();
+
+ // Register to listen for and send plugin messages on each channel
+ Messenger messenger = Bukkit.getServer().getMessenger();
+ for (String channelIdentifier : CommandAPIProtocol.getAllChannelIdentifiers()) {
+ messenger.registerIncomingPluginChannel(this.plugin, channelIdentifier, this);
+ messenger.registerOutgoingPluginChannel(this.plugin, channelIdentifier);
+ }
+ // Register to listen for player join and leave
+ Bukkit.getPluginManager().registerEvents(this, this.plugin);
+ }
+
+ @EventHandler
+ public void onPlayerRegisterChannel(PlayerRegisterChannelEvent event) {
+ // Run once the handshake channel is registered
+ // We can't send messages until the channel is registered, so this is the earliest this can happen
+ if (!event.getChannel().equals(CommandAPIProtocol.HANDSHAKE.getChannelIdentifier())) return;
+
+ // Send SetVersionPacket to inform player of our capabilities
+ this.sendPacket(event.getPlayer(), new SetVersionPacket(CommandAPIProtocol.PROTOCOL_VERSION));
+ }
+
+ @EventHandler
+ public void onPlayerLeave(PlayerQuitEvent event) {
+ // Remove player from our protocol version map, so we don't keep track of useless data
+ this.protocolVersionPerPlayer.remove(event.getPlayer());
+ }
+
+ @Override
+ public void close() {
+ // Unregister this listener
+ Messenger messenger = Bukkit.getServer().getMessenger();
+ for (String channelIdentifier : CommandAPIProtocol.getAllChannelIdentifiers()) {
+ messenger.unregisterIncomingPluginChannel(this.plugin, channelIdentifier);
+ messenger.unregisterOutgoingPluginChannel(this.plugin, channelIdentifier);
+ }
+ HandlerList.unregisterAll(this);
+ }
+
+ /**
+ * Sets the {@link CommandAPIProtocol#PROTOCOL_VERSION} being used by the given player. This is only intended to be
+ * called by {@link BukkitHandshakePacketHandler#handleSetVersionPacket(Player, SetVersionPacket)} when a
+ * {@link SetVersionPacket} is received.
+ *
+ * @param sender The player that sent the {@link SetVersionPacket}.
+ * @param protocolVersion The {@link CommandAPIProtocol#PROTOCOL_VERSION} in the {@link SetVersionPacket}.
+ */
+ public void setProtocolVersion(Player sender, int protocolVersion) {
+ this.protocolVersionPerPlayer.put(sender, protocolVersion);
+ }
+
+ @Override
+ public int getConnectionProtocolVersion(Player target) {
+ return this.protocolVersionPerPlayer.getOrDefault(target, 0);
+ }
+
+ @Override
+ public void onPluginMessageReceived(String channel, Player player, byte[] message) {
+ // A plugin message was sent to Bukkit, check if it is for us
+ CommandAPIProtocol protocol = CommandAPIProtocol.getProtocolForChannel(channel);
+ if (protocol == null) return;
+
+ // Handle the message
+ messageReceived(protocol, player, message);
+ }
+
+ @Override
+ public void sendRawBytes(CommandAPIProtocol protocol, Player target, byte[] bytes) {
+ target.sendPluginMessage(this.plugin, protocol.getChannelIdentifier(), bytes);
+ }
+}
diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-networking-plugin/src/main/java/dev/jorel/commandapi/network/BukkitNetworkingPacketHandlerProvider.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-networking-plugin/src/main/java/dev/jorel/commandapi/network/BukkitNetworkingPacketHandlerProvider.java
new file mode 100644
index 0000000000..ffcfd913d3
--- /dev/null
+++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-networking-plugin/src/main/java/dev/jorel/commandapi/network/BukkitNetworkingPacketHandlerProvider.java
@@ -0,0 +1,28 @@
+package dev.jorel.commandapi.network;
+
+import org.bukkit.entity.Player;
+
+import dev.jorel.commandapi.CommandAPINetworkingMain;
+
+/**
+ * A {@link CommandAPIPacketHandlerProvider} for the barebones netowrking plugin for Bukkit.
+ */
+public class BukkitNetworkingPacketHandlerProvider implements CommandAPIPacketHandlerProvider {
+ private final BukkitHandshakePacketHandler handshakePacketHandler;
+ private final BukkitPlayPacketHandler playPacketHandler;
+
+ protected BukkitNetworkingPacketHandlerProvider(CommandAPINetworkingMain plugin) {
+ handshakePacketHandler = new BukkitHandshakePacketHandler(plugin);
+ playPacketHandler = new BukkitPlayPacketHandler(plugin);
+ }
+
+ @Override
+ public BukkitHandshakePacketHandler getHandshakePacketHandler() {
+ return handshakePacketHandler;
+ }
+
+ @Override
+ public BukkitPlayPacketHandler getPlayPacketHandler() {
+ return playPacketHandler;
+ }
+}
diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-networking-plugin/src/main/java/dev/jorel/commandapi/network/BukkitPlayPacketHandler.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-networking-plugin/src/main/java/dev/jorel/commandapi/network/BukkitPlayPacketHandler.java
new file mode 100644
index 0000000000..0f7d952b97
--- /dev/null
+++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-networking-plugin/src/main/java/dev/jorel/commandapi/network/BukkitPlayPacketHandler.java
@@ -0,0 +1,21 @@
+package dev.jorel.commandapi.network;
+
+import dev.jorel.commandapi.CommandAPINetworkingMain;
+import dev.jorel.commandapi.network.packets.UpdateRequirementsPacket;
+import org.bukkit.entity.Player;
+
+/**
+ * A {@link PlayPacketHandler} for handling {@link CommandAPIPacket}s sent to Bukkit by {@link Player} connections.
+ */
+public class BukkitPlayPacketHandler implements PlayPacketHandler {
+ private final CommandAPINetworkingMain plugin;
+
+ protected BukkitPlayPacketHandler(CommandAPINetworkingMain plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void handleUpdateRequirementsPacket(Player sender, UpdateRequirementsPacket packet) {
+ sender.updateCommands();
+ }
+}
diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-networking-plugin/src/main/resources/plugin.yml b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-networking-plugin/src/main/resources/plugin.yml
new file mode 100644
index 0000000000..8fd4476485
--- /dev/null
+++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-networking-plugin/src/main/resources/plugin.yml
@@ -0,0 +1,9 @@
+name: CommandAPINetworking
+main: dev.jorel.commandapi.CommandAPINetworkingMain
+version: ${project.version}
+description: A barebones plugin that only handles plugin messages sent by the CommandAPI
+authors:
+ - Will Kroboth
+website: https://www.jorel.dev/CommandAPI/
+api-version: 1.13
+folia-supported: true
diff --git a/commandapi-platforms/commandapi-bukkit/pom.xml b/commandapi-platforms/commandapi-bukkit/pom.xml
index a40f8eda87..f80e48be44 100644
--- a/commandapi-platforms/commandapi-bukkit/pom.xml
+++ b/commandapi-platforms/commandapi-bukkit/pom.xml
@@ -27,6 +27,7 @@
commandapi-bukkit-plugin
commandapi-bukkit-test
commandapi-bukkit-shade
+ commandapi-bukkit-networking-plugin
commandapi-bukkit-plugin-mojang-mapped
commandapi-bukkit-shade-mojang-mapped