diff --git a/MinecraftClient/ChatBots/ItemsCollector.cs b/MinecraftClient/ChatBots/ItemsCollector.cs new file mode 100644 index 00000000..d123b803 --- /dev/null +++ b/MinecraftClient/ChatBots/ItemsCollector.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Brigadier.NET.Builder; +using MinecraftClient.CommandHandler; +using MinecraftClient.CommandHandler.Patch; +using MinecraftClient.Inventory; +using MinecraftClient.Mapping; +using MinecraftClient.Scripting; +using Tomlet.Attributes; + +namespace MinecraftClient.ChatBots; + +public class ItemsCollector : ChatBot +{ + public const string CommandName = "itemscollector"; + public static Configs Config = new(); + + [TomlDoNotInlineObject] + public class Configs + { + [NonSerialized] private const string BotName = "ItemsCollector"; + + public bool Enabled = false; + + [TomlInlineComment("$ChatBot.ItemsCollector.Collect_All_Item_Types$")] + public bool Collect_All_Item_Types = true; + + [TomlInlineComment("$ChatBot.ItemsCollector.Items_Whitelist$")] + public List Items_Whitelist = new() { ItemType.Diamond, ItemType.NetheriteIngot }; + + [TomlInlineComment("$ChatBot.ItemsCollector.Delay_Between_Tasks$")] + public int Delay_Between_Tasks = 300; + + [TomlInlineComment("$ChatBot.ItemsCollector.Collection_Radius$")] + public double Collection_Radius = 30.0; + + [TomlInlineComment("$ChatBot.ItemsCollector.Always_Return_To_Start$")] + public bool Always_Return_To_Start = true; + + [TomlInlineComment("$ChatBot.ItemsCollector.Prioritize_Clusters$")] + public bool Prioritize_Clusters = false; + + public void OnSettingUpdate() + { + if (Delay_Between_Tasks < 100) + Delay_Between_Tasks = 100; + } + } + + private bool running = false; + private Thread? mainProcessThread; + + public override void Initialize() + { + if (!GetEntityHandlingEnabled()) + { + LogToConsole(Translations.extra_entity_required); + LogToConsole(Translations.general_bot_unload); + UnloadBot(); + return; + } + + if (!GetTerrainEnabled()) + { + LogToConsole(Translations.extra_terrainandmovement_required); + LogToConsole(Translations.general_bot_unload); + UnloadBot(); + return; + } + + McClient.dispatcher.Register(l => l.Literal("help") + .Then(l => l.Literal(CommandName) + .Executes(r => OnCommandHelp(r.Source, string.Empty))) + ); + + McClient.dispatcher.Register(l => l.Literal(CommandName) + .Then(l => l.Literal("start") + .Executes(r => OnCommandStart(r.Source))) + .Then(l => l.Literal("stop") + .Executes(r => OnCommandStop(r.Source))) + .Then(l => l.Literal("_help") + .Executes(r => OnCommandHelp(r.Source, string.Empty)) + .Redirect(McClient.dispatcher.GetRoot().GetChild("help").GetChild(CommandName))) + ); + } + + public override void OnUnload() + { + McClient.dispatcher.Unregister(CommandName); + McClient.dispatcher.GetRoot().GetChild("help").RemoveChild(CommandName); + } + + private int OnCommandHelp(CmdResult r, string? cmd) + { + return r.SetAndReturn(cmd switch + { +#pragma warning disable format // @formatter:off + _ => Translations.cmd_follow_desc + ": " + Translations.cmd_follow_usage + + '\n' + McClient.dispatcher.GetAllUsageString(CommandName, false), +#pragma warning restore format // @formatter:on + }); + } + + private int OnCommandStart(CmdResult r) + { + if (running) + return r.SetAndReturn(CmdResult.Status.Fail, "Already collecting items!"); + + StartTheMainProcess(); + return r.SetAndReturn(CmdResult.Status.Done, "Started collecting items!"); + } + + private int OnCommandStop(CmdResult r) + { + if (!running) + return r.SetAndReturn(CmdResult.Status.Fail, "Already not collecting items!"); + + StopTheMainProcess(); + return r.SetAndReturn(CmdResult.Status.Done, "Stopping collecting items..."); + } + + private void StartTheMainProcess() + { + running = true; + mainProcessThread = new Thread(MainProcess); + mainProcessThread.Start(); + } + + private void StopTheMainProcess() + { + running = false; + } + + private void MainProcess() + { + var startingLocation = GetCurrentLocation(); + + while (running) + { + var currentLocation = GetCurrentLocation(); + var items = GetEntities() + .Where(x => + x.Value.Type == EntityType.Item && + x.Value.Location.Distance(currentLocation) <= Config.Collection_Radius && + (Config.Collect_All_Item_Types || Config.Items_Whitelist.Contains(x.Value.Item.Type))) + .Select(x => x.Value) + .ToList(); + + if (Config.Prioritize_Clusters && items.Count > 1) + { + var centroid = new Location( + items.Average(x => x.Location.X), + items.Average(x => x.Location.Y), + items.Average(x => x.Location.Z) + ); + + items = items.OrderBy(x => x.Location.Distance(centroid) / items.Count).ToList(); + } + else items = items.OrderBy(x => x.Location.Distance(currentLocation)).ToList(); + + if (items.Any()) + { + foreach (var entity in items) + { + if (!running) + break; + + WaitForMoveToLocation(entity.Location); + } + } + else + { + if (Config.Always_Return_To_Start) + WaitForMoveToLocation(startingLocation); + } + + if (!running) + break; + + Thread.Sleep(Config.Delay_Between_Tasks); + } + + LogToConsole("Stopped collecting items!"); + } + + public override void AfterGameJoined() + { + StopTheMainProcess(); + } + + public override bool OnDisconnect(DisconnectReason reason, string message) + { + StopTheMainProcess(); + return true; + } + + private bool WaitForMoveToLocation(Location location, float tolerance = 1f) + { + if (!MoveToLocation(location)) return false; + while (GetCurrentLocation().Distance(location) > tolerance) + Thread.Sleep(200); + + return true; + } +} \ No newline at end of file diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index 5f3fc819..a970faea 100644 --- a/MinecraftClient/McClient.cs +++ b/MinecraftClient/McClient.cs @@ -296,6 +296,7 @@ namespace MinecraftClient if (Config.ChatBot.ReplayCapture.Enabled && reload) { BotLoad(new ReplayCapture()); } if (Config.ChatBot.ScriptScheduler.Enabled) { BotLoad(new ScriptScheduler()); } if (Config.ChatBot.TelegramBridge.Enabled) { BotLoad(new TelegramBridge()); } + if (Config.ChatBot.ItemsCollector.Enabled) { BotLoad(new ItemsCollector()); } //Add your ChatBot here by uncommenting and adapting //BotLoad(new ChatBots.YourBot()); } @@ -3405,7 +3406,9 @@ namespace MinecraftClient { Entity entity = entities[entityID]; entity.Metadata = metadata; - if (entity.Type.ContainsItem() && metadata.TryGetValue(7, out object? itemObj) && itemObj != null && itemObj.GetType() == typeof(Item)) + int itemEntityMetadataFieldIndex = protocolversion < Protocol18Handler.MC_1_17_Version ? 7 : 8; + + if (entity.Type.ContainsItem() && metadata.TryGetValue(itemEntityMetadataFieldIndex, out object? itemObj) && itemObj != null && itemObj.GetType() == typeof(Item)) { Item item = (Item)itemObj; if (item == null) diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index af5f8bbf..1186a228 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -1397,6 +1397,13 @@ namespace MinecraftClient get { return ChatBots.TelegramBridge.Config; } set { ChatBots.TelegramBridge.Config = value; ChatBots.TelegramBridge.Config.OnSettingUpdate(); } } + + [TomlPrecedingComment("$ChatBot.ItemsCollector$")] + public ChatBots.ItemsCollector.Configs ItemsCollector + { + get { return ChatBots.ItemsCollector.Config; } + set { ChatBots.ItemsCollector.Config = value; ChatBots.ItemsCollector.Config.OnSettingUpdate(); } + } } }