Merge origin/master into feat/mcp-server

This commit is contained in:
Anon 2026-04-03 20:03:37 +02:00
commit f4c160979c
86 changed files with 16765 additions and 2758 deletions

View file

@ -5,7 +5,9 @@ using System.Threading;
using Brigadier.NET.Builder;
using MinecraftClient.CommandHandler;
using MinecraftClient.CommandHandler.Patch;
using MinecraftClient.Inventory;
using MinecraftClient.Mapping;
using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_20_6;
using MinecraftClient.Scripting;
using Tomlet.Attributes;
@ -25,15 +27,12 @@ namespace MinecraftClient.ChatBots
public bool Enabled = false;
[NonSerialized]
[TomlInlineComment("$ChatBot.AutoDig.Auto_Tool_Switch$")]
public bool Auto_Tool_Switch = false;
[NonSerialized]
[TomlInlineComment("$ChatBot.AutoDig.Durability_Limit$")]
public int Durability_Limit = 2;
[NonSerialized]
[TomlInlineComment("$ChatBot.AutoDig.Drop_Low_Durability_Tools$")]
public bool Drop_Low_Durability_Tools = false;
@ -65,6 +64,8 @@ namespace MinecraftClient.ChatBots
public void OnSettingUpdate()
{
Durability_Limit = Math.Max(0, Durability_Limit);
if (Auto_Start_Delay >= 0)
Auto_Start_Delay = Math.Max(0.1, Auto_Start_Delay);
@ -225,6 +226,102 @@ namespace MinecraftClient.ChatBots
}
}
private static int GetLegacyMaxDamage(ItemType itemType)
{
return itemType switch
{
ItemType.WoodenPickaxe or ItemType.WoodenAxe or ItemType.WoodenShovel or ItemType.WoodenSword or ItemType.WoodenHoe => 59,
ItemType.StonePickaxe or ItemType.StoneAxe or ItemType.StoneShovel or ItemType.StoneSword or ItemType.StoneHoe => 131,
ItemType.IronPickaxe or ItemType.IronAxe or ItemType.IronShovel or ItemType.IronSword or ItemType.IronHoe => 250,
ItemType.GoldenPickaxe or ItemType.GoldenAxe or ItemType.GoldenShovel or ItemType.GoldenSword or ItemType.GoldenHoe => 32,
ItemType.DiamondPickaxe or ItemType.DiamondAxe or ItemType.DiamondShovel or ItemType.DiamondSword or ItemType.DiamondHoe => 1561,
ItemType.NetheritePickaxe or ItemType.NetheriteAxe or ItemType.NetheriteShovel or ItemType.NetheriteSword or ItemType.NetheriteHoe => 2031,
ItemType.Shears => 238,
_ => 0
};
}
private static int GetMaxDamage(Item item)
{
if (item.Components is not null)
{
var maxDamageComponent = item.Components.OfType<MaxDamageComponent>().FirstOrDefault();
if (maxDamageComponent is not null)
return maxDamageComponent.MaxDamage;
}
return GetLegacyMaxDamage(item.Type);
}
private static int GetRemainingDurability(Item item)
{
int maxDamage = GetMaxDamage(item);
return maxDamage > 0 ? maxDamage - item.Damage : int.MaxValue;
}
private bool HasEnoughDurability(Item item)
{
return Config.Durability_Limit <= 0 || GetRemainingDurability(item) >= Config.Durability_Limit;
}
private bool IsBelowDurabilityLimit(Item? item)
{
return item is not null && Config.Durability_Limit > 0 && GetRemainingDurability(item) < Config.Durability_Limit;
}
private static bool IsRecommendedTool(Item? item, ItemType[] recommendedTools)
{
return item is not null && recommendedTools.Contains(item.Type);
}
private bool SwapToolIntoHand(int sourceSlot, int handSlot)
{
return WindowAction(0, sourceSlot, WindowActionType.LeftClick)
&& WindowAction(0, handSlot, WindowActionType.LeftClick)
&& WindowAction(0, sourceSlot, WindowActionType.LeftClick);
}
private bool EnsureSuitableTool(Material blockType)
{
if (!inventoryEnabled || !Config.Auto_Tool_Switch)
return true;
ItemType[] recommendedTools = Material2Tool.GetCorrectToolForBlock(blockType);
if (recommendedTools.Length == 0)
return true;
Container container = GetPlayerInventory();
int handSlot = 36 + GetCurrentSlot();
container.Items.TryGetValue(handSlot, out Item? currentTool);
if (currentTool is not null && IsRecommendedTool(currentTool, recommendedTools) && HasEnoughDurability(currentTool))
return true;
foreach (ItemType recommendedTool in recommendedTools)
{
foreach ((int slot, Item item) in container.Items)
{
if (slot == handSlot || item.Type != recommendedTool || !HasEnoughDurability(item))
continue;
if (!SwapToolIntoHand(slot, handSlot))
return false;
LogToConsole(GetTimestamp() + ": " + string.Format(Translations.bot_autodig_switch, item.GetTypeString(), slot));
if (Config.Drop_Low_Durability_Tools && IsBelowDurabilityLimit(currentTool) &&
WindowAction(0, slot, WindowActionType.DropItemStack))
{
LogToConsole(GetTimestamp() + ": " + string.Format(Translations.bot_autodig_drop_low_durability, currentTool!.GetTypeString(), slot));
}
return true;
}
}
return !IsBelowDurabilityLimit(currentTool);
}
public override void Update()
{
lock (stateLock)
@ -293,6 +390,9 @@ namespace MinecraftClient.ChatBots
if (Config.Mode == Configs.ModeType.lookat ||
(Config.Mode == Configs.ModeType.both && Config._Locations.Contains(blockLoc)))
{
if (!EnsureSuitableTool(block.Type))
return false;
if (DigBlock(blockLoc, Direction.Down, lookAtBlock: false))
{
currentDig = blockLoc;
@ -354,6 +454,9 @@ namespace MinecraftClient.ChatBots
if (minDistance <= 6.0)
{
if (!EnsureSuitableTool(targetBlock.Type))
return false;
if (DigBlock(target, Direction.Down, lookAtBlock: true))
{
currentDig = target;
@ -388,6 +491,9 @@ namespace MinecraftClient.ChatBots
((Config.List_Type == Configs.ListType.whitelist && Config.Blocks.Contains(block.Type)) ||
(Config.List_Type == Configs.ListType.blacklist && !Config.Blocks.Contains(block.Type))))
{
if (!EnsureSuitableTool(block.Type))
return false;
if (DigBlock(blockLoc, Direction.Down, lookAtBlock: true))
{
currentDig = blockLoc;

View file

@ -62,6 +62,21 @@ namespace MinecraftClient.ChatBots
[TomlInlineComment("$ChatBot.AutoFishing.Hook_Threshold$")]
public double Hook_Threshold = 0.2;
[TomlInlineComment("$ChatBot.AutoFishing.Enable_Velocity_Detection$")]
public bool Enable_Velocity_Detection = true;
[TomlInlineComment("$ChatBot.AutoFishing.Velocity_Hook_Threshold$")]
public double Velocity_Hook_Threshold = -0.2;
[TomlInlineComment("$ChatBot.AutoFishing.Enable_Sound_Detection$")]
public bool Enable_Sound_Detection = true;
[TomlInlineComment("$ChatBot.AutoFishing.Sound_Distance$")]
public double Sound_Distance = 5.0;
[TomlInlineComment("$ChatBot.AutoFishing.Detection_Warmup$")]
public double Detection_Warmup = 1.0;
[TomlInlineComment("$ChatBot.AutoFishing.Log_Fish_Bobber$")]
public bool Log_Fish_Bobber = false;
@ -97,6 +112,15 @@ namespace MinecraftClient.ChatBots
if (Hook_Threshold < 0)
Hook_Threshold = -Hook_Threshold;
if (Velocity_Hook_Threshold > 0)
Velocity_Hook_Threshold = -Velocity_Hook_Threshold;
if (Sound_Distance < 0)
Sound_Distance = -Sound_Distance;
if (Detection_Warmup < 0)
Detection_Warmup = 0;
}
public struct LocationConfig
@ -171,6 +195,7 @@ namespace MinecraftClient.ChatBots
private Entity? fishingBobber;
private Location LastPos = Location.Zero;
private DateTime CaughtTime = DateTime.Now;
private DateTime BobberSpawnTime = DateTime.MinValue;
private int fishItemCounter = 15;
private Dictionary<ItemType, uint> fishItemCnt = new();
private Entity fishItem = new(-1, EntityType.Item, Location.Zero);
@ -464,6 +489,7 @@ namespace MinecraftClient.ChatBots
fishingBobber = entity;
LastPos = entity.Location;
isFishing = true;
BobberSpawnTime = DateTime.Now;
castTimeout = 24;
counter = 0;
@ -500,7 +526,7 @@ namespace MinecraftClient.ChatBots
public override void OnEntityMove(Entity entity)
{
if (isFishing && entity is not null && fishingBobber!.ID == entity.ID &&
(state == FishingState.WaitingFishToBite || state == FishingState.WaitingFishingBobber))
state == FishingState.WaitingFishToBite)
{
Location Pos = entity.Location;
double Dx = LastPos.X - Pos.X;
@ -515,13 +541,7 @@ namespace MinecraftClient.ChatBots
Math.Abs(Dz) < Math.Abs(Config.Stationary_Threshold) &&
Math.Abs(Dy) > Math.Abs(Config.Hook_Threshold))
{
// prevent triggering multiple time
if ((DateTime.Now - CaughtTime).TotalSeconds > 1)
{
isFishing = false;
CaughtTime = DateTime.Now;
OnCaughtFish();
}
TryCatchFish();
}
}
}
@ -540,6 +560,38 @@ namespace MinecraftClient.ChatBots
}
}
public override void OnEntityVelocity(Entity entity, double velocityX, double velocityY, double velocityZ)
{
if (!Config.Enable_Velocity_Detection || !CanUseAdvancedDetection())
return;
if (fishingBobber is null || entity.ID != fishingBobber.ID)
return;
if (velocityY <= Config.Velocity_Hook_Threshold)
TryCatchFish();
}
public override void OnSoundEffect(string? soundName, Location? location, int category, float volume, float pitch,
Entity? sourceEntity)
{
if (!Config.Enable_Sound_Detection || !CanUseAdvancedDetection())
return;
if (!IsFishingBobberSplashSound(soundName))
return;
Location? soundLocation = location;
if (soundLocation is null && sourceEntity is not null)
soundLocation = sourceEntity.Location;
if (soundLocation is null || fishingBobber is null)
return;
if (soundLocation.Value.Distance(fishingBobber.Location) <= Config.Sound_Distance)
TryCatchFish();
}
public override void AfterGameJoined()
{
StartFishing();
@ -562,10 +614,42 @@ namespace MinecraftClient.ChatBots
fishingBobber = null;
LastPos = Location.Zero;
CaughtTime = DateTime.Now;
BobberSpawnTime = DateTime.MinValue;
return base.OnDisconnect(reason, message);
}
private bool CanUseAdvancedDetection()
{
if (!isFishing || fishingBobber is null || state != FishingState.WaitingFishToBite)
return false;
return (DateTime.Now - BobberSpawnTime).TotalSeconds >= Config.Detection_Warmup;
}
private void TryCatchFish()
{
if (!CanUseAdvancedDetection())
return;
// Prevent repeated catches from multiple packets of the same bite.
if ((DateTime.Now - CaughtTime).TotalSeconds <= 1)
return;
isFishing = false;
CaughtTime = DateTime.Now;
OnCaughtFish();
}
private static bool IsFishingBobberSplashSound(string? soundName)
{
return string.Equals(soundName, "minecraft:entity.fishing_bobber.splash",
StringComparison.OrdinalIgnoreCase)
|| string.Equals(soundName, "entity.fishing_bobber.splash", StringComparison.OrdinalIgnoreCase)
|| string.Equals(soundName, "minecraft:entity.bobber.splash", StringComparison.OrdinalIgnoreCase)
|| string.Equals(soundName, "entity.bobber.splash", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Called when detected a fish is caught
/// </summary>

View file

@ -1,4 +1,4 @@
using System;
using System;
using MinecraftClient.Scripting;
using Tomlet.Attributes;
@ -95,6 +95,11 @@ namespace MinecraftClient.ChatBots
_Initialize();
}
public override void AfterGameJoined()
{
Configs._BotRecoAttempts = 0;
}
private void _Initialize()
{
McClient.ReconnectionAttemptsLeft = Config.Retries;
@ -144,10 +149,17 @@ namespace MinecraftClient.ChatBots
{
double delay = random.NextDouble() * (Config.Delay.max - Config.Delay.min) + Config.Delay.min;
LogDebugToConsole(string.Format(string.IsNullOrEmpty(msg) ? Translations.bot_autoRelog_reconnect_always : Translations.bot_autoRelog_reconnect, msg));
// TODO: Change this translation string to add the retries left text
LogToConsole(string.Format(Translations.bot_autoRelog_wait, delay) + $" ({Config.Retries - Configs._BotRecoAttempts} retries left)");
ReconnectToTheServer(Config.Retries - Configs._BotRecoAttempts, (int)Math.Floor(delay), true);
int retriesLeft = Config.Retries - Configs._BotRecoAttempts;
if (retriesLeft < 0)
retriesLeft = 0;
string retriesDisplay = Config.Retries == int.MaxValue
? Translations.bot_autoRelog_retries_unlimited
: retriesLeft.ToString();
LogToConsole(string.Format(Translations.bot_autoRelog_wait_with_retries, delay, retriesDisplay));
ReconnectToTheServer(retriesLeft, (int)Math.Floor(delay), true);
}
public static bool OnDisconnectStatic(DisconnectReason reason, string message)

View file

@ -1,8 +1,11 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Brigadier.NET.Builder;
using DSharpPlus;
@ -34,6 +37,9 @@ namespace MinecraftClient.ChatBots
private DiscordChannel? discordChannel;
private BridgeDirection bridgeDirection = BridgeDirection.Both;
private readonly ConcurrentQueue<string> aggregationBuffer = new();
private Timer? aggregationTimer;
public static Configs Config = new();
[TomlDoNotInlineObject]
@ -62,6 +68,12 @@ namespace MinecraftClient.ChatBots
[TomlInlineComment("$ChatBot.DiscordBridge.AllowOtherBotMessages$")]
public bool Allow_Other_Bot_Messages = false;
[TomlInlineComment("$ChatBot.DiscordBridge.RelayAllMessages$")]
public bool Relay_All_Messages = false;
[TomlInlineComment("$ChatBot.DiscordBridge.MessageAggregationInterval$")]
public double Message_Aggregation_Interval = 3.0;
[TomlPrecedingComment("$ChatBot.DiscordBridge.Formats$")]
public string PrivateMessageFormat = "**[Private Message]** {username}: {message}";
public string PublicMessageFormat = "{username}: {message}";
@ -70,6 +82,8 @@ namespace MinecraftClient.ChatBots
public void OnSettingUpdate()
{
Message_Send_Timeout = Message_Send_Timeout <= 0 ? 3 : Message_Send_Timeout;
if (Message_Aggregation_Interval < 0)
Message_Aggregation_Interval = 0;
}
}
@ -100,6 +114,12 @@ namespace MinecraftClient.ChatBots
.Redirect(McClient.dispatcher.GetRoot().GetChild("help").GetChild(CommandName)))
);
if (Config.Message_Aggregation_Interval > 0)
{
var intervalMs = (int)(Config.Message_Aggregation_Interval * 1000);
aggregationTimer = new Timer(_ => FlushAggregationBuffer(), null, intervalMs, intervalMs);
}
Task.Run(async () => await MainAsync());
}
@ -107,6 +127,7 @@ namespace MinecraftClient.ChatBots
{
McClient.dispatcher.Unregister(CommandName);
McClient.dispatcher.GetRoot().GetChild("help").RemoveChild(CommandName);
StopAggregation();
Disconnect();
}
@ -147,6 +168,40 @@ namespace MinecraftClient.ChatBots
return r.SetAndReturn(CmdResult.Status.Done, string.Format(Translations.bot_DiscordBridge_direction, bridgeName));
}
private void FlushAggregationBuffer()
{
if (aggregationBuffer.IsEmpty || !CanSendMessages())
return;
var sb = new StringBuilder();
while (aggregationBuffer.TryDequeue(out var line))
{
if (sb.Length + line.Length + 1 > 1900)
{
SendMessage(sb.ToString());
sb.Clear();
}
if (sb.Length > 0)
sb.AppendLine();
sb.Append(line);
}
if (sb.Length > 0)
SendMessage(sb.ToString());
}
private void StopAggregation()
{
if (aggregationTimer is not null)
{
aggregationTimer.Dispose();
aggregationTimer = null;
}
FlushAggregationBuffer();
}
~DiscordBridge()
{
Disconnect();
@ -188,7 +243,6 @@ namespace MinecraftClient.ChatBots
text = GetVerbatim(text).Trim();
// Stop the crash when an empty text is recived somehow
if (string.IsNullOrEmpty(text))
return;
@ -205,7 +259,10 @@ namespace MinecraftClient.ChatBots
message = Config.TeleportRequestMessageFormat.Replace("{username}", username).Replace("{timestamp}", GetTimestamp()).Trim();
teleportRequest = true;
}
else message = text;
else if (Config.Relay_All_Messages)
message = text;
else
return;
if (teleportRequest)
{
@ -223,7 +280,13 @@ namespace MinecraftClient.ChatBots
SendMessage(messageBuilder);
return;
}
else SendMessage(GetDiscordText(message));
string discordText = GetDiscordText(message);
if (Config.Message_Aggregation_Interval > 0)
aggregationBuffer.Enqueue(discordText);
else
SendMessage(discordText);
}
/// <summary>