diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index 7092c413..65a36a3c 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -741,7 +741,7 @@ namespace MinecraftClient /// private bool SendEntityAction(Protocol.EntityActionType entityAction) { - return Handler.sendEntityAction(entityAction); + return Handler.SendEntityAction(entityAction); } /// diff --git a/MinecraftClient/Commands/Sneak.cs b/MinecraftClient/Commands/Sneak.cs index d3dc3604..c5d71a84 100644 --- a/MinecraftClient/Commands/Sneak.cs +++ b/MinecraftClient/Commands/Sneak.cs @@ -15,14 +15,14 @@ namespace MinecraftClient.Commands { if (sneaking) { - var result = handler.sendEntityAction(Protocol.EntityActionType.StopSneaking); + var result = handler.SendEntityAction(Protocol.EntityActionType.StopSneaking); if (result) sneaking = false; return result ? "You aren't sneaking anymore" : "Fail"; } else { - var result = handler.sendEntityAction(Protocol.EntityActionType.StartSneaking); + var result = handler.SendEntityAction(Protocol.EntityActionType.StartSneaking); if (result) sneaking = true; return result ? "You are sneaking now" : "Fail"; diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index 4adc1630..a8d71884 100644 --- a/MinecraftClient/McClient.cs +++ b/MinecraftClient/McClient.cs @@ -326,7 +326,6 @@ namespace MinecraftClient } while (true); } - /// /// Perform an internal MCC command (not a server command, use SendText() instead for that!) @@ -337,7 +336,6 @@ namespace MinecraftClient /// 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) @@ -456,479 +454,6 @@ namespace MinecraftClient 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); - - if (inventoryHandlingEnabled) - { - inventories.Clear(); - inventories[0] = new Container(0, ContainerType.PlayerInventory, "Player Inventory"); - } - - foreach (ChatBot bot in bots.ToArray()) - { - try - { - bot.AfterGameJoined(); - } - catch (Exception e) - { - if (!(e is ThreadAbortException)) - { - ConsoleIO.WriteLogLine("AfterGameJoined: Got error from " + bot.ToString() + ": " + e.ToString()); - } - else throw; //ThreadAbortException should not be caught - } - } - - 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 (inventoryHandlingEnabled) - { - inventories.Clear(); - inventories[0] = new Container(0, ContainerType.PlayerInventory, "Player Inventory"); - } - - 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 thay may hurt the player: lava, cactus... - /// Allow non-vanilla small teleport instead of computing path, but may cause invalid moves and/or trigger anti-cheat plugins - /// True if a path has been found - public bool MoveTo(Location location, bool allowUnsafe = false, bool allowSmallTeleport = false) - { - lock (locationLock) - { - if (allowSmallTeleport && location.DistanceSquared(this.location) <= 16) - { - // Allow small teleport within a range of 4 blocks. 1-step path to the desired location without checking anything - path = null; - steps = new Queue(new[] { location }); - return true; - } - else - { - // Calculate path through pathfinding. Path contains a list of 1-block movement that will be divided into steps - 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.WriteLogLine("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.WriteLogLine("GetText: 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 /// @@ -1047,6 +572,245 @@ namespace MinecraftClient } } + #region Management: Load/Unload ChatBots and Enable/Disable settings + + /// + /// 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(); + } + + /// + /// 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; + } + } + + #endregion + + #region Getters: Retrieve data for use in other methods or ChatBots + + /// + /// 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); + } + + /// + /// 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; + } + + #endregion + + #region Action methods: Perform an action on the Server + + /// + /// Move to the specified location + /// + /// Location to reach + /// Allow possible but unsafe locations thay may hurt the player: lava, cactus... + /// Allow non-vanilla small teleport instead of computing path, but may cause invalid moves and/or trigger anti-cheat plugins + /// True if a path has been found + public bool MoveTo(Location location, bool allowUnsafe = false, bool allowSmallTeleport = false) + { + lock (locationLock) + { + if (allowSmallTeleport && location.DistanceSquared(this.location) <= 16) + { + // Allow small teleport within a range of 4 blocks. 1-step path to the desired location without checking anything + path = null; + steps = new Queue(new[] { location }); + return true; + } + else + { + // Calculate path through pathfinding. Path contains a list of 1-block movement that will be divided into steps + 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; + } + } + } + /// /// Send a chat message or command to the server /// @@ -1088,64 +852,6 @@ namespace MinecraftClient 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. /// @@ -1209,318 +915,15 @@ namespace MinecraftClient 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 an entity spawned - /// - public void OnSpawnEntity(Entity entity) - { - // The entity should not already exist, but if it does, let's consider the previous one is being destroyed - if (entities.ContainsKey(entity.ID)) - OnDestroyEntities(new[] { entity.ID }); - - entities.Add(entity.ID, entity); - - foreach (ChatBot bot in bots.ToArray()) - { - try - { - bot.OnEntitySpawn(entity); - } - catch (Exception e) - { - if (!(e is ThreadAbortException)) - { - ConsoleIO.WriteLogLine("OnEntitySpawn: Got error from " + bot.ToString() + ": " + e.ToString()); - } - else throw; //ThreadAbortException should not be caught - } - } - } - - /// - /// Called when a player spawns or enters the client's render distance - /// - public void OnSpawnPlayer(int entityID, Guid uuid, Location location, byte Yaw, byte Pitch) - { - string playerName = null; - if (onlinePlayers.ContainsKey(uuid)) - playerName = onlinePlayers[uuid]; - Entity playerEntity = new Entity(entityID, EntityType.Player, location, uuid, playerName); - OnSpawnEntity(playerEntity); - } - - /// - /// Called on Entity Equipment - /// - /// Entity ID - /// Equipment slot. 0: main hand, 1: off hand, 2–5: armor slot (2: boots, 3: leggings, 4: chestplate, 5: helmet) - /// Item) - public void OnEntityEquipment(int entityid, int slot, Item item) - { - foreach (ChatBot bot in bots.ToArray()) - { - try - { - if (entities.ContainsKey(entityid)) - bot.OnEntityEquipment(entities[entityid], slot, item); - } - catch (Exception e) - { - if (!(e is ThreadAbortException)) - { - ConsoleIO.WriteLogLine("OnEntityEquipment: Got error from " + bot.ToString() + ": " + e.ToString()); - } - else throw; //ThreadAbortException should not be caught - } - } - } - - /// - /// Called when the Game Mode has been updated for a player - /// - /// Player Name - /// Player UUID (Empty for initial gamemode on login) - /// New Game Mode (0: Survival, 1: Creative, 2: Adventure, 3: Spectator). - public void OnGamemodeUpdate(Guid uuid, int gamemode) - { - // Initial gamemode on login - if (uuid == Guid.Empty) - this.gamemode = gamemode; - - // Further regular gamemode change events - if (onlinePlayers.ContainsKey(uuid)) - { - string playerName = onlinePlayers[uuid]; - if (playerName == this.username) - this.gamemode = gamemode; - foreach (ChatBot bot in bots.ToArray()) - bot.OnGamemodeUpdate(playerName, uuid, gamemode); - } - } - - /// - /// 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()) - { - try - { - bot.OnEntityDespawn(entities[a]); - } - catch (Exception e) - { - if (!(e is ThreadAbortException)) - { - ConsoleIO.WriteLogLine("OnEntityDespawn: Got error from " + bot.ToString() + ": " + e.ToString()); - } - else throw; //ThreadAbortException should not be caught - } - } - 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()) - { - try - { - bot.OnEntityMove(entities[EntityID]); - } - catch (Exception e) - { - if (!(e is ThreadAbortException)) - { - ConsoleIO.WriteLogLine("OnEntityMove: Got error from " + bot.ToString() + ": " + e.ToString()); - } - else throw; //ThreadAbortException should not be caught - } - } - } - - } - - /// - /// 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()) - { - try - { - bot.OnEntityMove(entities[EntityID]); - } - catch (Exception e) - { - if (!(e is ThreadAbortException)) - { - ConsoleIO.WriteLogLine("OnEntityMove: Got error from " + bot.ToString() + ": " + e.ToString()); - } - else throw; //ThreadAbortException should not be caught - } - } - } - } - - /// - /// Called when received entity properties from server. - /// - /// - /// - public void OnEntityProperties(int EntityID, Dictionary prop) - { - if(EntityID == playerEntityID) - { - foreach (ChatBot bot in bots.ToArray()) - { - try - { - bot.OnPlayerProperty(prop); - } - catch (Exception e) - { - if (!(e is ThreadAbortException)) - { - ConsoleIO.WriteLogLine("OnPlayerProperty: Got error from " + bot.ToString() + ": " + e.ToString()); - } - else throw; //ThreadAbortException should not be caught - } - } - } - } - - /// - /// 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()) - { - try - { - bot.OnServerTpsUpdate(tps); - } - catch (Exception e) - { - if (!(e is ThreadAbortException)) - { - ConsoleIO.WriteLogLine("OnServerTpsUpdate: Got error from " + bot.ToString() + ": " + e.ToString()); - } - else throw; //ThreadAbortException should not be caught - } - } - } - } - 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; - } - /// /// Send the Entity Action packet with the Specified ID /// /// TRUE if the item was successfully used - public bool sendEntityAction(EntityActionType entityAction) + public bool SendEntityAction(EntityActionType entityAction) { - return handler.SendEntityAction(playerEntityID, (int) entityAction); + return handler.SendEntityAction(playerEntityID, (int)entityAction); } + /// /// Use the item currently in the player's hand /// @@ -1581,7 +984,7 @@ namespace MinecraftClient } return false; } - + /// /// Clean all inventory /// @@ -1594,9 +997,9 @@ namespace MinecraftClient inventories[0] = new Container(0, ContainerType.PlayerInventory, "Player Inventory"); return true; } - else { return false; } + else { return false; } } - + /// /// Interact with an entity /// @@ -1678,6 +1081,630 @@ namespace MinecraftClient } } + /// + /// Update sign text + /// + /// sign location + /// text one + /// text two + /// text three + /// text1 four + public bool UpdateSign(Location location, string line1, string line2, string line3, string line4) + { + if (line1.Length <= 23 & line2.Length <= 23 & line3.Length <= 23 & line4.Length <= 23) + return handler.SendUpdateSign(location, line1, line2, line3, line4); + else { return false; } + } + + #endregion + + #region Event handlers: An event occurs on the Server + + /// + /// 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); + + if (inventoryHandlingEnabled) + { + inventories.Clear(); + inventories[0] = new Container(0, ContainerType.PlayerInventory, "Player Inventory"); + } + + foreach (ChatBot bot in bots.ToArray()) + { + try + { + bot.AfterGameJoined(); + } + catch (Exception e) + { + if (!(e is ThreadAbortException)) + { + ConsoleIO.WriteLogLine("AfterGameJoined: Got error from " + bot.ToString() + ": " + e.ToString()); + } + else throw; //ThreadAbortException should not be caught + } + } + + 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 (inventoryHandlingEnabled) + { + inventories.Clear(); + inventories[0] = new Container(0, ContainerType.PlayerInventory, "Player Inventory"); + } + + if (terrainAndMovementsRequested) + { + terrainAndMovementsEnabled = true; + terrainAndMovementsRequested = false; + ConsoleIO.WriteLogLine("Terrain and Movements is now enabled."); + } + + if (terrainAndMovementsEnabled) + { + world.Clear(); + } + } + + /// + /// 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); + } + + /// + /// 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.WriteLogLine("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.WriteLogLine("GetText: 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; + } + } + + /// + /// Set client player's ID for later receiving player's own properties + /// + /// Player Entity ID + public void OnReceivePlayerEntityID(int EntityID) + { + playerEntityID = EntityID; + } + + /// + /// 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); + } + } + + /// + /// 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 an entity spawned + /// + public void OnSpawnEntity(Entity entity) + { + // The entity should not already exist, but if it does, let's consider the previous one is being destroyed + if (entities.ContainsKey(entity.ID)) + OnDestroyEntities(new[] { entity.ID }); + + entities.Add(entity.ID, entity); + + foreach (ChatBot bot in bots.ToArray()) + { + try + { + bot.OnEntitySpawn(entity); + } + catch (Exception e) + { + if (!(e is ThreadAbortException)) + { + ConsoleIO.WriteLogLine("OnEntitySpawn: Got error from " + bot.ToString() + ": " + e.ToString()); + } + else throw; //ThreadAbortException should not be caught + } + } + } + + /// + /// Called when a player spawns or enters the client's render distance + /// + public void OnSpawnPlayer(int entityID, Guid uuid, Location location, byte Yaw, byte Pitch) + { + string playerName = null; + if (onlinePlayers.ContainsKey(uuid)) + playerName = onlinePlayers[uuid]; + Entity playerEntity = new Entity(entityID, EntityType.Player, location, uuid, playerName); + OnSpawnEntity(playerEntity); + } + + /// + /// Called on Entity Equipment + /// + /// Entity ID + /// Equipment slot. 0: main hand, 1: off hand, 2–5: armor slot (2: boots, 3: leggings, 4: chestplate, 5: helmet) + /// Item) + public void OnEntityEquipment(int entityid, int slot, Item item) + { + foreach (ChatBot bot in bots.ToArray()) + { + try + { + if (entities.ContainsKey(entityid)) + bot.OnEntityEquipment(entities[entityid], slot, item); + } + catch (Exception e) + { + if (!(e is ThreadAbortException)) + { + ConsoleIO.WriteLogLine("OnEntityEquipment: Got error from " + bot.ToString() + ": " + e.ToString()); + } + else throw; //ThreadAbortException should not be caught + } + } + } + + /// + /// Called when the Game Mode has been updated for a player + /// + /// Player Name + /// Player UUID (Empty for initial gamemode on login) + /// New Game Mode (0: Survival, 1: Creative, 2: Adventure, 3: Spectator). + public void OnGamemodeUpdate(Guid uuid, int gamemode) + { + // Initial gamemode on login + if (uuid == Guid.Empty) + this.gamemode = gamemode; + + // Further regular gamemode change events + if (onlinePlayers.ContainsKey(uuid)) + { + string playerName = onlinePlayers[uuid]; + if (playerName == this.username) + this.gamemode = gamemode; + foreach (ChatBot bot in bots.ToArray()) + bot.OnGamemodeUpdate(playerName, uuid, gamemode); + } + } + + /// + /// 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()) + { + try + { + bot.OnEntityDespawn(entities[a]); + } + catch (Exception e) + { + if (!(e is ThreadAbortException)) + { + ConsoleIO.WriteLogLine("OnEntityDespawn: Got error from " + bot.ToString() + ": " + e.ToString()); + } + else throw; //ThreadAbortException should not be caught + } + } + 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()) + { + try + { + bot.OnEntityMove(entities[EntityID]); + } + catch (Exception e) + { + if (!(e is ThreadAbortException)) + { + ConsoleIO.WriteLogLine("OnEntityMove: Got error from " + bot.ToString() + ": " + e.ToString()); + } + else throw; //ThreadAbortException should not be caught + } + } + } + + } + + /// + /// 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()) + { + try + { + bot.OnEntityMove(entities[EntityID]); + } + catch (Exception e) + { + if (!(e is ThreadAbortException)) + { + ConsoleIO.WriteLogLine("OnEntityMove: Got error from " + bot.ToString() + ": " + e.ToString()); + } + else throw; //ThreadAbortException should not be caught + } + } + } + } + + /// + /// Called when received entity properties from server. + /// + /// + /// + public void OnEntityProperties(int EntityID, Dictionary prop) + { + if (EntityID == playerEntityID) + { + foreach (ChatBot bot in bots.ToArray()) + { + try + { + bot.OnPlayerProperty(prop); + } + catch (Exception e) + { + if (!(e is ThreadAbortException)) + { + ConsoleIO.WriteLogLine("OnPlayerProperty: Got error from " + bot.ToString() + ": " + e.ToString()); + } + else throw; //ThreadAbortException should not be caught + } + } + } + } + + /// + /// 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()) + { + try + { + bot.OnServerTpsUpdate(tps); + } + catch (Exception e) + { + if (!(e is ThreadAbortException)) + { + ConsoleIO.WriteLogLine("OnServerTpsUpdate: Got error from " + bot.ToString() + ": " + e.ToString()); + } + else throw; //ThreadAbortException should not be caught + } + } + } + } + else + { + lastAge = WorldAge; + lastTime = DateTime.Now; + } + } + /// /// Called when client player's health changed, e.g. getting attack /// @@ -1766,7 +1793,7 @@ namespace MinecraftClient } } } - + /// /// Called when Latency is updated /// @@ -1795,7 +1822,7 @@ namespace MinecraftClient } } } - + /// /// Called when held item change /// @@ -1819,7 +1846,7 @@ namespace MinecraftClient } CurrentSlot = slot; } - + /// /// Called map data /// @@ -1846,7 +1873,7 @@ namespace MinecraftClient } } } - + /// /// Received some Title from the server /// 0 = set title, 1 = set subtitle, 3 = set action bar, 4 = set times and display, 4 = hide, 5 = reset @@ -1875,20 +1902,7 @@ namespace MinecraftClient } } } - - /// - /// Update sign text - /// - /// sign location - /// text one - /// text two - /// text three - /// text1 four - public bool UpdateSign(Location location, string line1, string line2, string line3, string line4) - { - if (line1.Length <= 23 & line2.Length <= 23 & line3.Length <= 23 & line4.Length <= 23) - return handler.SendUpdateSign(location, line1, line2, line3, line4); - else { return false; } - } + + #endregion } } diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index a28b9818..3c9b2c06 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -216,7 +216,7 @@ namespace MinecraftClient.Protocol.Handlers case PacketIncomingType.JoinGame: handler.OnGameJoined(); int playerEntityID = dataTypes.ReadNextInt(packetData); - handler.SetPlayerEntityID(playerEntityID); + handler.OnReceivePlayerEntityID(playerEntityID); handler.OnGamemodeUpdate(Guid.Empty, dataTypes.ReadNextByte(packetData)); if (protocolversion >= MC191Version) this.currentDimension = dataTypes.ReadNextInt(packetData); diff --git a/MinecraftClient/Protocol/IMinecraftComHandler.cs b/MinecraftClient/Protocol/IMinecraftComHandler.cs index f9376ad6..69c70e4e 100644 --- a/MinecraftClient/Protocol/IMinecraftComHandler.cs +++ b/MinecraftClient/Protocol/IMinecraftComHandler.cs @@ -272,6 +272,6 @@ namespace MinecraftClient.Protocol /// Called when the Player entity ID has been received from the server /// /// Player entity ID - void SetPlayerEntityID(int EntityID); + void OnReceivePlayerEntityID(int EntityID); } }