using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net.Sockets; using System.Threading; using System.IO; using System.Net; using MinecraftClient.Protocol; using MinecraftClient.Proxy; using MinecraftClient.Protocol.Handlers.Forge; using MinecraftClient.Mapping; using MinecraftClient.Inventory; using System.Threading.Tasks; namespace MinecraftClient { /// /// The main client class, used to connect to a Minecraft server. /// public class McTcpClient : IMinecraftComHandler { public static int ReconnectionAttemptsLeft = 0; private static readonly List cmd_names = new List(); private static readonly Dictionary cmds = new Dictionary(); private readonly Dictionary onlinePlayers = new Dictionary(); private readonly List bots = new List(); private static readonly List botsOnHold = new List(); private static Dictionary inventories = new Dictionary(); private readonly Dictionary> registeredBotPluginChannels = new Dictionary>(); private readonly List registeredServerPluginChannels = new List(); private bool terrainAndMovementsEnabled; private bool terrainAndMovementsRequested = false; private bool inventoryHandlingEnabled; private bool inventoryHandlingRequested = false; private bool entityHandlingEnabled; private object locationLock = new object(); private bool locationReceived = false; private World world = new World(); private Queue steps; private Queue path; private Location location; private float? yaw; private float? pitch; private double motionY; private string host; private int port; private string username; private string uuid; private string sessionid; private DateTime lastKeepAlive; private object lastKeepAliveLock = new object(); private int playerEntityID; // not really understand the Inventory Class // so I use a Dict instead for player inventory //private Dictionary playerItems; // Entity handling private Dictionary entities = new Dictionary(); // server TPS private long lastAge = 0; private DateTime lastTime; private Double serverTPS = 0; public int GetServerPort() { return port; } public string GetServerHost() { return host; } public string GetUsername() { return username; } public string GetUserUUID() { return uuid; } public string GetSessionID() { return sessionid; } public Location GetCurrentLocation() { return location; } public World GetWorld() { return world; } public Double GetServerTPS() { return serverTPS; } // get bots list for unloading them by commands public List GetLoadedChatBots() { return bots; } TcpClient client; IMinecraftCom handler; Thread cmdprompt; Thread timeoutdetector; /// /// Starts the main chat client /// /// The chosen username of a premium Minecraft Account /// The player's UUID for online-mode authentication /// A valid sessionID obtained after logging in /// The server IP /// The server port to use /// Minecraft protocol version to use public McTcpClient(string username, string uuid, string sessionID, int protocolversion, ForgeInfo forgeInfo, string server_ip, ushort port) { StartClient(username, uuid, sessionID, server_ip, port, protocolversion, forgeInfo, false, ""); } /// /// Starts the main chat client in single command sending mode /// /// The chosen username of a premium Minecraft Account /// The player's UUID for online-mode authentication /// A valid sessionID obtained after logging in /// The server IP /// The server port to use /// Minecraft protocol version to use /// The text or command to send. public McTcpClient(string username, string uuid, string sessionID, string server_ip, ushort port, int protocolversion, ForgeInfo forgeInfo, string command) { StartClient(username, uuid, sessionID, server_ip, port, protocolversion, forgeInfo, true, command); } /// /// Starts the main chat client, wich will login to the server using the MinecraftCom class. /// /// The chosen username of a premium Minecraft Account /// A valid sessionID obtained with MinecraftCom.GetLogin() /// The server IP /// The server port to use /// Minecraft protocol version to use /// The player's UUID for online-mode authentication /// If set to true, the client will send a single command and then disconnect from the server /// The text or command to send. Will only be sent if singlecommand is set to true. private void StartClient(string user, string uuid, string sessionID, string server_ip, ushort port, int protocolversion, ForgeInfo forgeInfo, bool singlecommand, string command) { terrainAndMovementsEnabled = Settings.TerrainAndMovements; inventoryHandlingEnabled = Settings.InventoryHandling; entityHandlingEnabled = Settings.EntityHandling; if (inventoryHandlingEnabled) { inventories.Clear(); inventories[0] = new Container(0, ContainerType.PlayerInventory, "Player Inventory"); } bool retry = false; this.sessionid = sessionID; this.uuid = uuid; this.username = user; this.host = server_ip; this.port = port; if (!singlecommand) { if (botsOnHold.Count == 0) { if (Settings.AntiAFK_Enabled) { BotLoad(new ChatBots.AntiAFK(Settings.AntiAFK_Delay)); } if (Settings.Hangman_Enabled) { BotLoad(new ChatBots.HangmanGame(Settings.Hangman_English)); } if (Settings.Alerts_Enabled) { BotLoad(new ChatBots.Alerts()); } if (Settings.ChatLog_Enabled) { BotLoad(new ChatBots.ChatLog(Settings.ExpandVars(Settings.ChatLog_File), Settings.ChatLog_Filter, Settings.ChatLog_DateTime)); } if (Settings.PlayerLog_Enabled) { BotLoad(new ChatBots.PlayerListLogger(Settings.PlayerLog_Delay, Settings.ExpandVars(Settings.PlayerLog_File))); } if (Settings.AutoRelog_Enabled) { BotLoad(new ChatBots.AutoRelog(Settings.AutoRelog_Delay, Settings.AutoRelog_Retries)); } if (Settings.ScriptScheduler_Enabled) { BotLoad(new ChatBots.ScriptScheduler(Settings.ExpandVars(Settings.ScriptScheduler_TasksFile))); } if (Settings.RemoteCtrl_Enabled) { BotLoad(new ChatBots.RemoteControl()); } if (Settings.AutoRespond_Enabled) { BotLoad(new ChatBots.AutoRespond(Settings.AutoRespond_Matches)); } if (Settings.AutoAttack_Enabled) { BotLoad(new ChatBots.AutoAttack()); } if (Settings.AutoFishing_Enabled) { BotLoad(new ChatBots.AutoFishing()); } //Add your ChatBot here by uncommenting and adapting //BotLoad(new ChatBots.YourBot()); } } try { client = ProxyHandler.newTcpClient(host, port); client.ReceiveBufferSize = 1024 * 1024; handler = Protocol.ProtocolHandler.GetProtocolHandler(client, protocolversion, forgeInfo, this); Console.WriteLine("Version is supported.\nLogging in..."); try { if (handler.Login()) { if (singlecommand) { handler.SendChatMessage(command); ConsoleIO.WriteLineFormatted("§7Command §8" + command + "§7 sent."); Thread.Sleep(5000); handler.Disconnect(); Thread.Sleep(1000); } else { foreach (ChatBot bot in botsOnHold) BotLoad(bot, false); botsOnHold.Clear(); Console.WriteLine("Server was successfully joined.\nType '" + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + "quit' to leave the server."); cmdprompt = new Thread(new ThreadStart(CommandPrompt)); cmdprompt.Name = "MCC Command prompt"; cmdprompt.Start(); timeoutdetector = new Thread(new ThreadStart(TimeoutDetector)); timeoutdetector.Name = "MCC Connection timeout detector"; timeoutdetector.Start(); } } } catch (Exception e) { ConsoleIO.WriteLineFormatted("§8" + e.Message); Console.WriteLine("Failed to join this server."); retry = true; } } catch (SocketException e) { ConsoleIO.WriteLineFormatted("§8" + e.Message); Console.WriteLine("Failed to connect to this IP."); retry = true; } if (retry) { if (ReconnectionAttemptsLeft > 0) { ConsoleIO.WriteLogLine("Waiting 5 seconds (" + ReconnectionAttemptsLeft + " attempts left)..."); Thread.Sleep(5000); ReconnectionAttemptsLeft--; Program.Restart(); } else if (!singlecommand && Settings.interactiveMode) { Program.HandleFailure(); } } } /// /// Allows the user to send chat messages, commands, and to leave the server. /// private void CommandPrompt() { try { string text = ""; Thread.Sleep(500); handler.SendRespawnPacket(); while (client.Client.Connected) { text = ConsoleIO.ReadLine(); if (ConsoleIO.BasicIO && text.Length > 0 && text[0] == (char)0x00) { //Process a request from the GUI string[] command = text.Substring(1).Split((char)0x00); switch (command[0].ToLower()) { case "autocomplete": if (command.Length > 1) { ConsoleIO.WriteLine((char)0x00 + "autocomplete" + (char)0x00 + handler.AutoComplete(command[1])); } else Console.WriteLine((char)0x00 + "autocomplete" + (char)0x00); break; } } else { text = text.Trim(); if (text.Length > 0) { if (Settings.internalCmdChar == ' ' || text[0] == Settings.internalCmdChar) { string response_msg = ""; string command = Settings.internalCmdChar == ' ' ? text : text.Substring(1); if (!PerformInternalCommand(Settings.ExpandVars(command), ref response_msg) && Settings.internalCmdChar == '/') { SendText(text); } else if (response_msg.Length > 0) { ConsoleIO.WriteLineFormatted("§8MCC: " + response_msg); } } else SendText(text); } } } } catch (IOException) { } catch (NullReferenceException) { } } /// /// Periodically checks for server keepalives and consider that connection has been lost if the last received keepalive is too old. /// private void TimeoutDetector() { lock (lastKeepAliveLock) { lastKeepAlive = DateTime.Now; } do { Thread.Sleep(TimeSpan.FromSeconds(15)); lock (lastKeepAliveLock) { if (lastKeepAlive.AddSeconds(30) < DateTime.Now) { OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, "Connection Timeout"); } } } while (true); } /// /// Perform an internal MCC command (not a server command, use SendText() instead for that!) /// /// The command /// May contain a confirmation or error message after processing the command, or "" otherwise. /// Local variables passed along with the command /// TRUE if the command was indeed an internal MCC command public bool PerformInternalCommand(string command, ref string response_msg, Dictionary localVars = null) { /* Load commands from the 'Commands' namespace */ if (cmds.Count == 0) { Type[] cmds_classes = Program.GetTypesInNamespace("MinecraftClient.Commands"); foreach (Type type in cmds_classes) { if (type.IsSubclassOf(typeof(Command))) { try { Command cmd = (Command)Activator.CreateInstance(type); cmds[cmd.CMDName.ToLower()] = cmd; cmd_names.Add(cmd.CMDName.ToLower()); foreach (string alias in cmd.getCMDAliases()) cmds[alias.ToLower()] = cmd; } catch (Exception e) { ConsoleIO.WriteLine(e.Message); } } } } /* Process the provided command */ string command_name = command.Split(' ')[0].ToLower(); if (command_name == "help") { if (Command.hasArg(command)) { string help_cmdname = Command.getArgs(command)[0].ToLower(); if (help_cmdname == "help") { response_msg = "help : show brief help about a command."; } else if (cmds.ContainsKey(help_cmdname)) { response_msg = cmds[help_cmdname].CMDDesc; } else response_msg = "Unknown command '" + command_name + "'. Use 'help' for command list."; } else response_msg = "help . Available commands: " + String.Join(", ", cmd_names.ToArray()) + ". For server help, use '" + Settings.internalCmdChar + "send /help' instead."; } else if (cmds.ContainsKey(command_name)) { response_msg = cmds[command_name].Run(this, command, localVars); } else { response_msg = "Unknown command '" + command_name + "'. Use '" + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + "help' for help."; return false; } return true; } /// /// Disconnect the client from the server (initiated from MCC) /// public void Disconnect() { foreach (ChatBot bot in bots.ToArray()) bot.OnDisconnect(ChatBot.DisconnectReason.UserLogout, ""); botsOnHold.Clear(); botsOnHold.AddRange(bots); if (handler != null) { handler.Disconnect(); handler.Dispose(); } if (cmdprompt != null) cmdprompt.Abort(); if (timeoutdetector != null) { timeoutdetector.Abort(); timeoutdetector = null; } Thread.Sleep(1000); if (client != null) client.Close(); } /// /// Load a new bot /// public void BotLoad(ChatBot b, bool init = true) { b.SetHandler(this); bots.Add(b); if (init) b.Initialize(); if (this.handler != null) b.AfterGameJoined(); Settings.SingleCommand = ""; } /// /// Unload a bot /// 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); } } /// /// Clear bots /// public void BotClear() { bots.Clear(); } /// /// Called when a server was successfully joined /// public void OnGameJoined() { if (!String.IsNullOrWhiteSpace(Settings.BrandInfo)) handler.SendBrandInfo(Settings.BrandInfo.Trim()); if (Settings.MCSettings_Enabled) handler.SendClientSettings( Settings.MCSettings_Locale, Settings.MCSettings_RenderDistance, Settings.MCSettings_Difficulty, Settings.MCSettings_ChatMode, Settings.MCSettings_ChatColors, Settings.MCSettings_Skin_All, Settings.MCSettings_MainHand); foreach (ChatBot bot in bots.ToArray()) bot.AfterGameJoined(); if (inventoryHandlingRequested) { inventoryHandlingRequested = false; inventoryHandlingEnabled = true; ConsoleIO.WriteLogLine("Inventory handling is now enabled."); } } /// /// Called when the player respawns, which happens on login, respawn and world change. /// public void OnRespawn() { if (terrainAndMovementsRequested) { terrainAndMovementsEnabled = true; terrainAndMovementsRequested = false; ConsoleIO.WriteLogLine("Terrain and Movements is now enabled."); } if (terrainAndMovementsEnabled) { world.Clear(); } } /// /// Get Terrain and Movements status. /// public bool GetTerrainEnabled() { return terrainAndMovementsEnabled; } /// /// Get Inventory Handling Mode /// public bool GetInventoryEnabled() { return inventoryHandlingEnabled; } /// /// Enable or disable Terrain and Movements. /// Please note that Enabling will be deferred until next relog, respawn or world change. /// /// Enabled /// TRUE if the setting was applied immediately, FALSE if delayed. public bool SetTerrainEnabled(bool enabled) { if (enabled) { if (!terrainAndMovementsEnabled) { terrainAndMovementsRequested = true; return false; } } else { terrainAndMovementsEnabled = false; terrainAndMovementsRequested = false; locationReceived = false; world.Clear(); } return true; } /// /// Enable or disable Inventories. /// Please note that Enabling will be deferred until next relog. /// /// Enabled /// TRUE if the setting was applied immediately, FALSE if delayed. public bool SetInventoryEnabled(bool enabled) { if (enabled) { if (!inventoryHandlingEnabled) { inventoryHandlingRequested = true; return false; } } else { inventoryHandlingEnabled = false; inventoryHandlingRequested = false; inventories.Clear(); } return true; } /// /// Get entity handling status /// /// /// Entity Handling cannot be enabled in runtime (or after joining server) public bool GetEntityHandlingEnabled() { return entityHandlingEnabled; } /// /// Enable or disable Entity handling. /// Please note that Enabling will be deferred until next relog. /// /// Enabled /// TRUE if the setting was applied immediately, FALSE if delayed. public bool SetEntityHandlingEnabled(bool enabled) { if (!enabled) { if (entityHandlingEnabled) { entityHandlingEnabled = false; return true; } else { return false; } } else { // Entity Handling cannot be enabled in runtime (or after joining server) return false; } } /// /// Get all inventories. ID 0 is the player inventory. /// /// All inventories public Dictionary GetInventories() { return inventories; } /// /// Get client player's inventory items /// /// Window ID of the requested inventory /// Item Dictionary indexed by Slot ID (Check wiki.vg for slot ID) public Container GetInventory(int inventoryID) { if (inventories.ContainsKey(inventoryID)) return inventories[inventoryID]; return null; } /// /// Get client player's inventory items /// /// Item Dictionary indexed by Slot ID (Check wiki.vg for slot ID) public Container GetPlayerInventory() { return GetInventory(0); } /// /// Called when the server sends a new player location, /// or if a ChatBot whishes to update the player's location. /// /// The new location /// If true, the location is relative to the current location public void UpdateLocation(Location location, bool relative) { lock (locationLock) { if (relative) { this.location += location; } else this.location = location; locationReceived = true; } } /// /// Called when the server sends a new player location, /// or if a ChatBot whishes to update the player's location. /// /// The new location /// Yaw to look at /// Pitch to look at public void UpdateLocation(Location location, float yaw, float pitch) { this.yaw = yaw; this.pitch = pitch; UpdateLocation(location, false); } /// /// Called when the server sends a new player location, /// or if a ChatBot whishes to update the player's location. /// /// The new location /// Block coordinates to look at public void UpdateLocation(Location location, Location lookAtLocation) { double dx = lookAtLocation.X - (location.X - 0.5); double dy = lookAtLocation.Y - (location.Y + 1); double dz = lookAtLocation.Z - (location.Z - 0.5); double r = Math.Sqrt(dx * dx + dy * dy + dz * dz); float yaw = Convert.ToSingle(-Math.Atan2(dx, dz) / Math.PI * 180); float pitch = Convert.ToSingle(-Math.Asin(dy / r) / Math.PI * 180); if (yaw < 0) yaw += 360; UpdateLocation(location, yaw, pitch); } /// /// Called when the server sends a new player location, /// or if a ChatBot whishes to update the player's location. /// /// The new location /// Direction to look at public void UpdateLocation(Location location, Direction direction) { float yaw = 0; float pitch = 0; switch (direction) { case Direction.Up: pitch = -90; break; case Direction.Down: pitch = 90; break; case Direction.East: yaw = 270; break; case Direction.West: yaw = 90; break; case Direction.North: yaw = 180; break; case Direction.South: break; default: throw new ArgumentException("Unknown direction", "direction"); } UpdateLocation(location, yaw, pitch); } /// /// Move to the specified location /// /// Location to reach /// Allow possible but unsafe locations /// True if a path has been found public bool MoveTo(Location location, bool allowUnsafe = false) { lock (locationLock) { if (Movement.GetAvailableMoves(world, this.location, allowUnsafe).Contains(location)) path = new Queue(new[] { location }); else path = Movement.CalculatePath(world, this.location, location, allowUnsafe); return path != null; } } /// /// Received some text from the server /// /// Text received /// TRUE if the text is JSON-Encoded public void OnTextReceived(string text, bool isJson) { lock (lastKeepAliveLock) { lastKeepAlive = DateTime.Now; } List links = new List(); string json = null; if (isJson) { json = text; text = ChatParser.ParseText(json, links); } ConsoleIO.WriteLineFormatted(text, true); if (Settings.DisplayChatLinks) foreach (string link in links) ConsoleIO.WriteLineFormatted("§8MCC: Link: " + link, false); foreach (ChatBot bot in bots.ToArray()) { try { bot.GetText(text); if (bots.Contains(bot)) bot.GetText(text, json); } catch (Exception e) { if (!(e is ThreadAbortException)) { ConsoleIO.WriteLineFormatted("§8GetText: Got error from " + bot.ToString() + ": " + e.ToString()); } else throw; //ThreadAbortException should not be caught } } } /// /// Received a connection keep-alive from the server /// public void OnServerKeepAlive() { lock (lastKeepAliveLock) { lastKeepAlive = DateTime.Now; } } /// /// When an inventory is opened /// /// Location to reach public void OnInventoryOpen(int inventoryID, Container inventory) { inventories[inventoryID] = inventory; if (inventoryID != 0) { ConsoleIO.WriteLogLine("Inventory # " + inventoryID + " opened: " + inventory.Title); ConsoleIO.WriteLogLine("Use /inventory to interact with it."); } } /// /// When an inventory is close /// /// Location to reach public void OnInventoryClose(int inventoryID) { if (inventories.ContainsKey(inventoryID)) inventories.Remove(inventoryID); if (inventoryID != 0) ConsoleIO.WriteLogLine("Inventory # " + inventoryID + " closed."); } /// /// When received window items from server. /// /// Inventory ID /// Item list, key = slot ID, value = Item information public void OnWindowItems(byte inventoryID, Dictionary itemList) { if (inventories.ContainsKey(inventoryID)) inventories[inventoryID].Items = itemList; } /// /// When a slot is set inside window items /// /// Window ID /// Slot ID /// Item (may be null for empty slot) public void OnSetSlot(byte inventoryID, short slotID, Item item) { if (inventories.ContainsKey(inventoryID)) { if (item == null || item.IsEmpty) { if (inventories[inventoryID].Items.ContainsKey(slotID)) inventories[inventoryID].Items.Remove(slotID); } else inventories[inventoryID].Items[slotID] = item; } } /// /// When connection has been lost, login was denied or played was kicked from the server /// public void OnConnectionLost(ChatBot.DisconnectReason reason, string message) { world.Clear(); if (timeoutdetector != null) { timeoutdetector.Abort(); timeoutdetector = null; } bool will_restart = false; switch (reason) { case ChatBot.DisconnectReason.ConnectionLost: message = "Connection has been lost."; ConsoleIO.WriteLine(message); break; case ChatBot.DisconnectReason.InGameKick: ConsoleIO.WriteLine("Disconnected by Server :"); ConsoleIO.WriteLineFormatted(message); break; case ChatBot.DisconnectReason.LoginRejected: ConsoleIO.WriteLine("Login failed :"); ConsoleIO.WriteLineFormatted(message); break; case ChatBot.DisconnectReason.UserLogout: throw new InvalidOperationException("User-initiated logout should be done by calling Disconnect()"); } foreach (ChatBot bot in bots.ToArray()) will_restart |= bot.OnDisconnect(reason, message); if (!will_restart) Program.HandleFailure(); } /// /// Called ~10 times per second by the protocol handler /// public void OnUpdate() { foreach (var bot in bots.ToArray()) { try { bot.Update(); bot.ProcessQueuedText(); } catch (Exception e) { if (!(e is ThreadAbortException)) { ConsoleIO.WriteLineFormatted("§8Update: Got error from " + bot.ToString() + ": " + e.ToString()); } else throw; //ThreadAbortException should not be caught } } if (terrainAndMovementsEnabled && locationReceived) { lock (locationLock) { for (int i = 0; i < 2; i++) //Needs to run at 20 tps; MCC runs at 10 tps { if (yaw == null || pitch == null) { if (steps != null && steps.Count > 0) { location = steps.Dequeue(); } else if (path != null && path.Count > 0) { Location next = path.Dequeue(); steps = Movement.Move2Steps(location, next, ref motionY); UpdateLocation(location, next + new Location(0, 1, 0)); // Update yaw and pitch to look at next step } else { location = Movement.HandleGravity(world, location, ref motionY); } } handler.SendLocationUpdate(location, Movement.IsOnGround(world, location), yaw, pitch); } // First 2 updates must be player position AND look, and player must not move (to conform with vanilla) // Once yaw and pitch have been sent, switch back to location-only updates (without yaw and pitch) yaw = null; pitch = null; } } } /// /// Send a chat message or command to the server /// /// Text to send to the server /// True if the text was sent with no error public bool SendText(string text) { int maxLength = handler.GetMaxChatMessageLength(); if (text.Length > maxLength) //Message is too long? { if (text[0] == '/') { //Send the first 100/256 chars of the command text = text.Substring(0, maxLength); return handler.SendChatMessage(text); } else { //Send the message splitted into several messages while (text.Length > maxLength) { handler.SendChatMessage(text.Substring(0, maxLength)); text = text.Substring(maxLength, text.Length - maxLength); if (Settings.splitMessageDelay.TotalSeconds > 0) Thread.Sleep(Settings.splitMessageDelay); } return handler.SendChatMessage(text); } } else return handler.SendChatMessage(text); } /// /// Allow to respawn after death /// /// True if packet successfully sent public bool SendRespawnPacket() { return handler.SendRespawnPacket(); } /// /// Triggered when a new player joins the game /// /// UUID of the player /// Name of the player public void OnPlayerJoin(Guid uuid, string name) { //Ignore placeholders eg 0000tab# from TabListPlus if (!ChatBot.IsValidName(name)) return; lock (onlinePlayers) { onlinePlayers[uuid] = name; } } /// /// Triggered when a player has left the game /// /// UUID of the player public void OnPlayerLeave(Guid uuid) { lock (onlinePlayers) { onlinePlayers.Remove(uuid); } } /// /// Get a set of online player names /// /// Online player names public string[] GetOnlinePlayers() { lock (onlinePlayers) { return onlinePlayers.Values.Distinct().ToArray(); } } /// /// Get a dictionary of online player names and their corresponding UUID /// /// Dictionay of online players, key is UUID, value is Player name public Dictionary GetOnlinePlayersWithUUID() { Dictionary uuid2Player = new Dictionary(); lock (onlinePlayers) { foreach (Guid key in onlinePlayers.Keys) { uuid2Player.Add(key.ToString(), onlinePlayers[key]); } } return uuid2Player; } /// /// 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); } } } /// /// Called when a non-living entity spawned (fishing hook, minecart, etc) /// /// /// /// /// public void OnSpawnEntity(int EntityID, int TypeID, Guid UUID, Location location) { if (entities.ContainsKey(EntityID)) return; Entity entity = new Entity(EntityID, TypeID, EntityType.NonLivingThings, location); entities.Add(EntityID, entity); foreach (ChatBot bot in bots.ToArray()) bot.OnEntitySpawn(entity); } /// /// Called when an Entity was created/spawned. /// /// /// /// /// /// Cannot determine is a Mob or a Cuty Animal public void OnSpawnLivingEntity(int EntityID, int TypeID, Guid UUID, Location location) { if (entities.ContainsKey(EntityID)) return; Entity entity = new Entity(EntityID, TypeID, EntityType.MobAndAnimal, location); entities.Add(EntityID, entity); foreach (ChatBot bot in bots.ToArray()) bot.OnEntitySpawn(entity); } /// /// Called when a player was spawned/in the render distance /// /// /// /// /// /// public void OnSpawnPlayer(int EntityID, Guid UUID, Location location, byte Yaw, byte Pitch) { if (entities.ContainsKey(EntityID)) return; Entity entity = new Entity(EntityID, EntityType.Player, location); entities.Add(EntityID, entity); foreach (ChatBot bot in bots.ToArray()) bot.OnEntitySpawn(entity); } /// /// Called when entities dead/despawn. /// /// public void OnDestroyEntities(int[] Entities) { foreach (int a in Entities) { if (entities.ContainsKey(a)) { foreach (ChatBot bot in bots.ToArray()) bot.OnEntityDespawn(new Entity(entities[a].ID, entities[a].TypeID, entities[a].Type, entities[a].Location)); entities.Remove(a); } } } /// /// Called when an entity's position changed within 8 block of its previous position. /// /// /// /// /// /// public void OnEntityPosition(int EntityID, Double Dx, Double Dy, Double Dz,bool onGround) { if (entities.ContainsKey(EntityID)) { Location L = entities[EntityID].Location; L.X += Dx; L.Y += Dy; L.Z += Dz; entities[EntityID].Location = L; foreach (ChatBot bot in bots.ToArray()) bot.OnEntityMove(new Entity(entities[EntityID].ID, entities[EntityID].TypeID, entities[EntityID].Type, entities[EntityID].Location)); } } /// /// Called when an entity moved over 8 block. /// /// /// /// /// /// public void OnEntityTeleport(int EntityID, Double X, Double Y, Double Z, bool onGround) { if (entities.ContainsKey(EntityID)) { Location location = new Location(X, Y, Z); entities[EntityID].Location = location; foreach (ChatBot bot in bots.ToArray()) bot.OnEntityMove(new Entity(entities[EntityID].ID, entities[EntityID].TypeID, entities[EntityID].Type, entities[EntityID].Location)); } } /// /// Called when received entity properties from server. /// /// /// public void OnEntityProperties(int EntityID, Dictionary prop) { if(EntityID == playerEntityID) { foreach (ChatBot bot in bots.ToArray()) bot.OnPlayerProperty(prop); } } /// /// Called when server sent a Time Update packet. /// /// /// public void OnTimeUpdate(long WorldAge, long TimeOfDay) { // calculate server tps if (lastAge != 0) { DateTime currentTime = DateTime.Now; long tickDiff = WorldAge - lastAge; Double tps = tickDiff / (currentTime - lastTime).TotalSeconds; lastAge = WorldAge; lastTime = currentTime; if (tps <= 20.0 && tps >= 0.0 && serverTPS != tps) { serverTPS = tps; // invoke ChatBot foreach (ChatBot bot in bots.ToArray()) bot.OnServerTpsUpdate(tps); } } else { lastAge = WorldAge; lastTime = DateTime.Now; } } /// /// Set client player's ID for later receiving player's own properties /// /// Player Entity ID public void SetPlayerEntityID(int EntityID) { playerEntityID = EntityID; } /// /// Use the item currently in the player's hand /// /// TRUE if the item was successfully used public bool UseItemOnHand() { return handler.SendUseItem(0); } /// /// Click a slot in the specified window /// /// TRUE if the slot was successfully clicked public bool ClickWindowSlot(int windowId, int slotId) { Item item = null; if (inventories.ContainsKey(windowId) && inventories[windowId].Items.ContainsKey(slotId)) item = inventories[windowId].Items[slotId]; return handler.SendClickWindow(windowId, slotId, item); } /// /// Close the specified inventory window /// /// Window ID /// TRUE if the window was successfully closed public bool CloseInventory(int windowId) { if (windowId != 0 && inventories.ContainsKey(windowId)) { inventories.Remove(windowId); return handler.SendCloseWindow(windowId); } return false; } /// /// Interact with an entity /// /// /// 0: interact, 1: attack, 2: interact at /// TRUE if interaction succeeded public bool InteractEntity(int EntityID, int type) { return handler.SendInteractEntity(EntityID, type); } /// /// Place the block at hand in the Minecraft world /// /// Location to place block to /// TRUE if successfully placed public bool PlaceBlock(Location location) { //WORK IN PROGRESS. MAY NOT WORK YET ConsoleIO.WriteLine(location.ToString()); return handler.SendPlayerBlockPlacement(0, location, 1, 0.5f, 0.5f, 0.5f, false); } /// /// Change active slot in the player inventory /// /// Slot to activate (0 to 8) /// TRUE if the slot was changed public bool ChangeSlot(short slot) { if (slot >= 0 && slot <= 8) { return handler.SendHeldItemChange(slot); } else { return false; } } /// /// Called when client player's health changed, e.g. getting attack /// /// Player current health public void OnUpdateHealth(float health) { if (Settings.AutoRespawn) { if (health <= 0) { ConsoleIO.WriteLine("Client player dead."); ConsoleIO.WriteLine("Respawn after 1 second..."); Task.Factory.StartNew(delegate { // wait before respawn Thread.Sleep(1000); SendRespawnPacket(); }); } } } } }