From 1ea8f119d91e3fa4e2db812998fdaabd4825b005 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sun, 7 Feb 2016 14:24:01 -0800 Subject: [PATCH] Give ChatBots access to plugin channels. Chatbots may find it useful to send messages over plugin channels. This allows REGISTERing, UNREGISTERing, and sending over plugin channels, with built-in checking if the server also registered the channel (which can be disabled by the bot if needed). Unused channels are UNREGISTERed when a bot is disabled. --- MinecraftClient/ChatBot.cs | 55 ++++++++ MinecraftClient/McTcpClient.cs | 117 +++++++++++++++++- .../Protocol/Handlers/Protocol16.cs | 5 +- .../Protocol/Handlers/Protocol18.cs | 4 + .../Protocol/IMinecraftComHandler.cs | 37 +++++- 5 files changed, 215 insertions(+), 3 deletions(-) diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index ce65a3a9..c84ff57d 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -41,6 +41,7 @@ namespace MinecraftClient private McTcpClient Handler { get { return master != null ? master.Handler : _handler; } } private McTcpClient _handler = null; private ChatBot master = null; + private List registeredPluginChannels = new List(); private Queue chatQueue = new Queue(); private DateTime? lastMessageSentTime = DateTime.MinValue; private bool CanSendTextNow @@ -100,6 +101,17 @@ namespace MinecraftClient public virtual bool OnDisconnect(DisconnectReason reason, string message) { return false; } + /// + /// Called when a plugin channel message is received. + /// The given channel must have previously been registered with RegisterPluginChannel. + /// This can be used to communicate with server mods or plugins. See wiki.vg for more + /// information about plugin channels: http://wiki.vg/Plugin_channel + /// + /// The name of the channel + /// The payload for the message + + public virtual void OnPluginMessage(string channel, byte[] data) { } + /* =================================================================== */ /* ToolBox - Methods below might be useful while creating your bot. */ /* You should not need to interact with other classes of the program. */ @@ -596,5 +608,48 @@ namespace MinecraftClient return new string[0]; } } + + /// + /// Registers the given plugin channel for use by this chatbot. + /// + /// The name of the channel to register + + protected void RegisterPluginChannel(string channel) + { + this.registeredPluginChannels.Add(channel); + Handler.RegisterPluginChannel(channel, this); + } + + /// + /// Unregisters the given plugin channel, meaning this chatbot can no longer use it. + /// + /// The name of the channel to unregister + + protected void UnregisterPluginChannel(string channel) + { + this.registeredPluginChannels.RemoveAll(chan => chan == channel); + Handler.UnregisterPluginChannel(channel, this); + } + + /// + /// Sends the given plugin channel message to the server, if the channel has been registered. + /// See http://wiki.vg/Plugin_channel for more information about plugin channels. + /// + /// The channel to send the message on. + /// The data to send. + /// Should the message be sent even if it hasn't been registered by the server or this bot? (Some Minecraft channels aren't registered) + /// Whether the message was successfully sent. False if there was a network error or if the channel wasn't registered. + + protected bool SendPluginChannelMessage(string channel, byte[] data, bool sendEvenIfNotRegistered = false) + { + if (!sendEvenIfNotRegistered) + { + if (!this.registeredPluginChannels.Contains(channel)) + { + return false; + } + } + return Handler.SendPluginChannelMessage(channel, data, sendEvenIfNotRegistered); + } } } diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index b45d0a85..7e9c95b7 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -28,9 +28,21 @@ namespace MinecraftClient private readonly List bots = new List(); private static readonly List scripts_on_hold = new List(); public void BotLoad(ChatBot b) { b.SetHandler(this); bots.Add(b); b.Initialize(); Settings.SingleCommand = ""; } - public void BotUnLoad(ChatBot b) { bots.RemoveAll(item => object.ReferenceEquals(item, b)); } + public void BotUnLoad(ChatBot b) { + bots.RemoveAll(item => object.ReferenceEquals(item, b)); + + // ToList is needed to avoid an InvalidOperationException from modfiying the list while it's being iterated upon. + var botRegistrations = registeredBotPluginChannels.Where(entry => entry.Value.Contains(b)).ToList(); + foreach (var entry in botRegistrations) + { + UnregisterPluginChannel(entry.Key, b); + } + } public void BotClear() { bots.Clear(); } + private readonly Dictionary> registeredBotPluginChannels = new Dictionary>(); + private readonly List registeredServerPluginChannels = new List(); + private object locationLock = new object(); private bool locationReceived = false; private World world = new World(); @@ -573,5 +585,108 @@ namespace MinecraftClient return onlinePlayers.Values.Distinct().ToArray(); } } + + /// + /// Registers the given plugin channel for the given bot. + /// + /// The channel to register. + /// The bot to register the channel for. + + public void RegisterPluginChannel(string channel, ChatBot bot) + { + if (registeredBotPluginChannels.ContainsKey(channel)) + { + registeredBotPluginChannels[channel].Add(bot); + } + else + { + List bots = new List(); + bots.Add(bot); + registeredBotPluginChannels[channel] = bots; + SendPluginChannelMessage("REGISTER", Encoding.UTF8.GetBytes(channel), true); + } + } + + /// + /// Unregisters the given plugin channel for the given bot. + /// + /// The channel to unregister. + /// The bot to unregister the channel for. + + public void UnregisterPluginChannel(string channel, ChatBot bot) + { + if (registeredBotPluginChannels.ContainsKey(channel)) + { + List registeredBots = registeredBotPluginChannels[channel]; + registeredBots.RemoveAll(item => object.ReferenceEquals(item, bot)); + if (registeredBots.Count == 0) + { + registeredBotPluginChannels.Remove(channel); + SendPluginChannelMessage("UNREGISTER", Encoding.UTF8.GetBytes(channel), true); + } + } + } + + /// + /// Sends a plugin channel packet to the server. See http://wiki.vg/Plugin_channel for more information + /// about plugin channels. + /// + /// The channel to send the packet on. + /// The payload for the packet. + /// Whether the packet should be sent even if the server or the client hasn't registered it yet. + /// Whether the packet was sent: true if it was sent, false if there was a connection error or it wasn't registered. + + public bool SendPluginChannelMessage(string channel, byte[] data, bool sendEvenIfNotRegistered = false) + { + if (!sendEvenIfNotRegistered) + { + if (!registeredBotPluginChannels.ContainsKey(channel)) + { + return false; + } + if (!registeredServerPluginChannels.Contains(channel)) + { + return false; + } + } + return handler.SendPluginChannelPacket(channel, data); + } + + /// + /// Called when a plugin channel message was sent from the server. + /// + /// The channel the message was sent on + /// The data from the channel + + public void OnPluginChannelMessage(string channel, byte[] data) + { + if (channel == "REGISTER") + { + string[] channels = Encoding.UTF8.GetString(data).Split('\0'); + foreach (string chan in channels) + { + if (!registeredServerPluginChannels.Contains(chan)) + { + registeredServerPluginChannels.Add(chan); + } + } + } + if (channel == "UNREGISTER") + { + string[] channels = Encoding.UTF8.GetString(data).Split('\0'); + foreach (string chan in channels) + { + registeredServerPluginChannels.Remove(chan); + } + } + + if (registeredBotPluginChannels.ContainsKey(channel)) + { + foreach (ChatBot bot in registeredBotPluginChannels[channel]) + { + bot.OnPluginMessage(channel, data); + } + } + } } } diff --git a/MinecraftClient/Protocol/Handlers/Protocol16.cs b/MinecraftClient/Protocol/Handlers/Protocol16.cs index 07ee63dd..905ee97f 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -164,7 +164,10 @@ namespace MinecraftClient.Protocol.Handlers case 0xCF: if (protocolversion > 51) { readNextString(); readData(1); readNextString(); } readData(4); break; case 0xD0: if (protocolversion > 51) { readData(1); readNextString(); } break; case 0xD1: if (protocolversion > 51) { readNextTeamData(); } break; - case 0xFA: readNextString(); nbr = readNextShort(); readData(nbr); break; + case 0xFA: string channel = readNextString(); + byte[] payload = readNextByteArray(); + handler.OnPluginChannelMessage(channel, payload); + break; case 0xFF: string reason = readNextString(); handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, reason); break; default: return false; //unknown packet! diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 5bbd7da5..d6e8f880 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -303,6 +303,10 @@ namespace MinecraftClient.Protocol.Handlers readNextVarShort(packetData); } } + + // The remaining data in the array is the entire payload of the packet. + handler.OnPluginChannelMessage(channel, packetData.ToArray()); + if (forgeInfo != null) { if (channel == "FML|HS") diff --git a/MinecraftClient/Protocol/IMinecraftComHandler.cs b/MinecraftClient/Protocol/IMinecraftComHandler.cs index b9de09d8..7ca8cea6 100644 --- a/MinecraftClient/Protocol/IMinecraftComHandler.cs +++ b/MinecraftClient/Protocol/IMinecraftComHandler.cs @@ -14,7 +14,7 @@ namespace MinecraftClient.Protocol public interface IMinecraftComHandler { - /* The MinecraftCom Hanler must + /* The MinecraftCom Handler must * provide these getters */ int GetServerPort(); @@ -72,5 +72,40 @@ namespace MinecraftClient.Protocol /// void OnUpdate(); + + /// + /// Registers the given plugin channel for the given bot. + /// + /// The channel to register. + /// The bot to register the channel for. + + void RegisterPluginChannel(string channel, ChatBot bot); + + /// + /// Unregisters the given plugin channel for the given bot. + /// + /// The channel to unregister. + /// The bot to unregister the channel for. + + void UnregisterPluginChannel(string channel, ChatBot bot); + + /// + /// Sends a plugin channel packet to the server. See http://wiki.vg/Plugin_channel for more information + /// about plugin channels. + /// + /// The channel to send the packet on. + /// The payload for the packet. + /// Whether the packet should be sent even if the server or the client hasn't registered it yet. + /// Whether the packet was sent: true if it was sent, false if there was a connection error or it wasn't registered. + + bool SendPluginChannelMessage(string channel, byte[] data, bool sendEvenIfNotRegistered = false); + + /// + /// Called when a plugin channel message was sent from the server. + /// + /// The channel the message was sent on + /// The data from the channel + + void OnPluginChannelMessage(string channel, byte[] data); } }