mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
feat: Add automatic mining-speed and dig-duration handling
This commit is contained in:
commit
d5358d3d1a
6 changed files with 1965 additions and 4 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -436,5 +436,8 @@ FodyWeavers.xsd
|
||||||
# SpecStory files
|
# SpecStory files
|
||||||
/.specstory/
|
/.specstory/
|
||||||
/.vscode/settings.json
|
/.vscode/settings.json
|
||||||
|
|
||||||
|
# Other
|
||||||
/Sentry/
|
/Sentry/
|
||||||
|
/downloads/
|
||||||
server.pid
|
server.pid
|
||||||
|
|
|
||||||
1326
MinecraftClient/Mapping/BlockHardness.cs
Normal file
1326
MinecraftClient/Mapping/BlockHardness.cs
Normal file
File diff suppressed because it is too large
Load diff
572
MinecraftClient/Mapping/MiningCalculator.cs
Normal file
572
MinecraftClient/Mapping/MiningCalculator.cs
Normal file
|
|
@ -0,0 +1,572 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using MinecraftClient.Inventory;
|
||||||
|
using MinecraftClient.Protocol.Handlers;
|
||||||
|
using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_20_6;
|
||||||
|
using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_21_5;
|
||||||
|
using MinecraftClient.Protocol.Handlers.StructuredComponents.Components.Subcomponents._1_20_6;
|
||||||
|
|
||||||
|
namespace MinecraftClient.Mapping
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Computes dig duration in ticks for survival-style block breaking.
|
||||||
|
/// Version-aware across 1.8-1.21.11+, using tool speed, enchantments, effects, and attributes.
|
||||||
|
/// </summary>
|
||||||
|
public static class MiningCalculator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Compute the number of ticks required to break a block in survival mode.
|
||||||
|
/// Returns 0 for instant-break blocks, -1 for unbreakable blocks.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="blockMaterial">The block material to break</param>
|
||||||
|
/// <param name="heldItem">The item in the player's main hand (null for empty hand)</param>
|
||||||
|
/// <param name="helmetItem">The item in the player's helmet slot (null if empty, used for Aqua Affinity)</param>
|
||||||
|
/// <param name="effects">Currently active player effects</param>
|
||||||
|
/// <param name="playerAttributes">Cached player attribute values (from OnEntityProperties)</param>
|
||||||
|
/// <param name="isUnderwater">Whether the player's eyes are submerged in water</param>
|
||||||
|
/// <param name="isOnGround">Whether the player is on the ground</param>
|
||||||
|
/// <param name="protocolVersion">The Minecraft protocol version</param>
|
||||||
|
/// <returns>Ticks to break the block, 0 for instant, -1 for unbreakable</returns>
|
||||||
|
public static int ComputeDigTicks(
|
||||||
|
Material blockMaterial,
|
||||||
|
Item? heldItem,
|
||||||
|
Item? helmetItem,
|
||||||
|
Dictionary<Effects, EffectData> effects,
|
||||||
|
Dictionary<string, double> playerAttributes,
|
||||||
|
bool isUnderwater,
|
||||||
|
bool isOnGround,
|
||||||
|
int protocolVersion)
|
||||||
|
{
|
||||||
|
float hardness = BlockHardness.GetHardness(blockMaterial);
|
||||||
|
|
||||||
|
if (hardness < 0)
|
||||||
|
return -1; // Unbreakable
|
||||||
|
|
||||||
|
if (hardness == 0)
|
||||||
|
return 0; // Instant break
|
||||||
|
|
||||||
|
float destroySpeed = GetDestroySpeed(
|
||||||
|
blockMaterial, heldItem, helmetItem, effects, playerAttributes,
|
||||||
|
isUnderwater, isOnGround, protocolVersion);
|
||||||
|
|
||||||
|
bool correctTool = HasCorrectToolForDrops(blockMaterial, heldItem, protocolVersion);
|
||||||
|
int divisor = correctTool ? 30 : 100;
|
||||||
|
|
||||||
|
float destroyProgress = destroySpeed / hardness / divisor;
|
||||||
|
|
||||||
|
if (destroyProgress >= 1.0f)
|
||||||
|
return 0; // Instant break
|
||||||
|
|
||||||
|
return (int)MathF.Ceiling(1.0f / destroyProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compute the player's destroy speed for a given block, following vanilla formulas.
|
||||||
|
/// </summary>
|
||||||
|
private static float GetDestroySpeed(
|
||||||
|
Material blockMaterial,
|
||||||
|
Item? heldItem,
|
||||||
|
Item? helmetItem,
|
||||||
|
Dictionary<Effects, EffectData> effects,
|
||||||
|
Dictionary<string, double> playerAttributes,
|
||||||
|
bool isUnderwater,
|
||||||
|
bool isOnGround,
|
||||||
|
int protocolVersion)
|
||||||
|
{
|
||||||
|
float speed = GetToolSpeed(blockMaterial, heldItem, protocolVersion);
|
||||||
|
|
||||||
|
if (speed > 1.0f)
|
||||||
|
{
|
||||||
|
speed += GetEfficiencyBonus(heldItem, playerAttributes, protocolVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
int digSpeedAmplifier = GetDigSpeedAmplifier(effects);
|
||||||
|
if (digSpeedAmplifier >= 0)
|
||||||
|
speed *= 1.0f + (digSpeedAmplifier + 1) * 0.2f;
|
||||||
|
|
||||||
|
// Mining Fatigue
|
||||||
|
if (effects.TryGetValue(Effects.MiningFatigue, out var fatigueData))
|
||||||
|
{
|
||||||
|
float multiplier = fatigueData.Amplifier switch
|
||||||
|
{
|
||||||
|
0 => 0.3f,
|
||||||
|
1 => 0.09f,
|
||||||
|
2 => 0.0027f,
|
||||||
|
_ => 8.1E-4f
|
||||||
|
};
|
||||||
|
speed *= multiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attribute multipliers for modern versions
|
||||||
|
if (protocolVersion >= Protocol18Handler.MC_1_20_6_Version)
|
||||||
|
{
|
||||||
|
// BLOCK_BREAK_SPEED attribute (default 1.0)
|
||||||
|
if (playerAttributes.TryGetValue("player.block_break_speed", out double bbs))
|
||||||
|
speed *= (float)bbs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Underwater penalty
|
||||||
|
if (isUnderwater)
|
||||||
|
{
|
||||||
|
if (protocolVersion >= Protocol18Handler.MC_1_21_11_Version)
|
||||||
|
{
|
||||||
|
// 1.21.11+: Uses SUBMERGED_MINING_SPEED attribute (default 0.2)
|
||||||
|
double submergedSpeed = 0.2;
|
||||||
|
if (playerAttributes.TryGetValue("player.submerged_mining_speed", out double sms))
|
||||||
|
submergedSpeed = sms;
|
||||||
|
speed *= (float)submergedSpeed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Pre-1.21.11: /5 unless Aqua Affinity
|
||||||
|
bool hasAquaAffinity = GetEnchantmentLevel(helmetItem, Enchantments.AquaAffinity, protocolVersion) > 0;
|
||||||
|
if (!hasAquaAffinity)
|
||||||
|
speed /= 5.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Airborne penalty
|
||||||
|
if (!isOnGround)
|
||||||
|
speed /= 5.0f;
|
||||||
|
|
||||||
|
return speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the base tool mining speed for a block.
|
||||||
|
/// For 1.20.6+ with ToolComponent, uses structured component data.
|
||||||
|
/// For older versions, uses hardcoded tool speed tables.
|
||||||
|
/// </summary>
|
||||||
|
private static float GetToolSpeed(Material blockMaterial, Item? heldItem, int protocolVersion)
|
||||||
|
{
|
||||||
|
if (heldItem is null)
|
||||||
|
return 1.0f;
|
||||||
|
|
||||||
|
// Modern path: use ToolComponent from structured components
|
||||||
|
if (protocolVersion >= Protocol18Handler.MC_1_20_6_Version
|
||||||
|
&& TryGetToolRules(heldItem, out List<RuleSubComponent>? rules, out float defaultMiningSpeed))
|
||||||
|
{
|
||||||
|
foreach (var rule in rules)
|
||||||
|
{
|
||||||
|
if (rule.HasSpeed && MatchesBlockSet(rule.Blocks, blockMaterial))
|
||||||
|
return rule.Speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Structured tool data covers modern mining rules, but keep the legacy fallback for
|
||||||
|
// explicit block holder-sets that MCC cannot resolve yet (for example cobweb).
|
||||||
|
if (defaultMiningSpeed > 1.0f)
|
||||||
|
return defaultMiningSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy path: hardcoded tool speed tables
|
||||||
|
return GetLegacyToolSpeed(heldItem.Type, blockMaterial);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check whether the tool provides correct drops for a block.
|
||||||
|
/// </summary>
|
||||||
|
private static bool HasCorrectToolForDrops(Material blockMaterial, Item? heldItem, int protocolVersion)
|
||||||
|
{
|
||||||
|
if (!BlockHardness.RequiresCorrectTool(blockMaterial))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (heldItem is null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Modern path: check ToolComponent rules
|
||||||
|
if (protocolVersion >= Protocol18Handler.MC_1_20_6_Version
|
||||||
|
&& TryGetToolRules(heldItem, out List<RuleSubComponent>? rules, out _))
|
||||||
|
{
|
||||||
|
foreach (var rule in rules)
|
||||||
|
{
|
||||||
|
if (rule.HasCorrectDropForBlocks && MatchesBlockSet(rule.Blocks, blockMaterial))
|
||||||
|
return rule.CorrectDropForBlocks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy path, plus a modern fallback for direct block holder-sets MCC cannot resolve yet.
|
||||||
|
return IsCorrectToolLegacy(heldItem.Type, blockMaterial);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetToolRules(
|
||||||
|
Item heldItem,
|
||||||
|
[NotNullWhen(true)] out List<RuleSubComponent>? rules,
|
||||||
|
out float defaultMiningSpeed)
|
||||||
|
{
|
||||||
|
rules = null;
|
||||||
|
defaultMiningSpeed = 1.0f;
|
||||||
|
|
||||||
|
if (heldItem.Components is null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (heldItem.Components.OfType<ToolComponent>().FirstOrDefault() is ToolComponent toolComponent)
|
||||||
|
{
|
||||||
|
rules = toolComponent.Rules;
|
||||||
|
defaultMiningSpeed = toolComponent.DefaultMiningSpeed;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (heldItem.Components.OfType<ToolComponent1215>().FirstOrDefault() is ToolComponent1215 toolComponent1215)
|
||||||
|
{
|
||||||
|
rules = toolComponent1215.Rules;
|
||||||
|
defaultMiningSpeed = toolComponent1215.DefaultMiningSpeed;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Match a block material against a ToolComponent BlockSetSubcomponent.
|
||||||
|
/// </summary>
|
||||||
|
private static bool MatchesBlockSet(
|
||||||
|
Protocol.Handlers.StructuredComponents.Components.Subcomponents._1_20_6.BlockSetSubcomponent blockSet,
|
||||||
|
Material blockMaterial)
|
||||||
|
{
|
||||||
|
if (blockSet.BlockIds is not null)
|
||||||
|
{
|
||||||
|
// Check against explicit block state IDs
|
||||||
|
foreach (int blockId in blockSet.BlockIds)
|
||||||
|
{
|
||||||
|
if (Block.Palette.FromId(blockId) == blockMaterial)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockSet.TagName is not null)
|
||||||
|
{
|
||||||
|
// Match against tag name (e.g., "minecraft:mineable/pickaxe")
|
||||||
|
return MatchesBlockTag(blockSet.TagName, blockMaterial);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Approximate block tag matching using Material2Tool categories.
|
||||||
|
/// Tags like "minecraft:mineable/pickaxe" map to the appropriate tool categories.
|
||||||
|
/// </summary>
|
||||||
|
private static bool MatchesBlockTag(string tagName, Material blockMaterial)
|
||||||
|
{
|
||||||
|
// Normalize tag name
|
||||||
|
string tag = tagName.Replace("minecraft:", "");
|
||||||
|
|
||||||
|
ItemType[] tools = Material2Tool.GetCorrectToolForBlock(blockMaterial);
|
||||||
|
return tag switch
|
||||||
|
{
|
||||||
|
"mineable/pickaxe" => tools.Length > 0 && IsPickaxe(tools[0]),
|
||||||
|
"mineable/axe" => tools.Length > 0 && IsAxe(tools[0]),
|
||||||
|
"mineable/shovel" => tools.Length > 0 && IsShovel(tools[0]),
|
||||||
|
"mineable/hoe" => tools.Length > 0 && IsHoe(tools[0]),
|
||||||
|
"leaves" => IsLeaf(blockMaterial),
|
||||||
|
"wool" => IsWool(blockMaterial),
|
||||||
|
"incorrect_for_wooden_tool" => RequiresHigherTier(blockMaterial, 0),
|
||||||
|
"incorrect_for_gold_tool" => RequiresHigherTier(blockMaterial, 0),
|
||||||
|
"incorrect_for_stone_tool" => RequiresHigherTier(blockMaterial, 1),
|
||||||
|
"incorrect_for_copper_tool" => RequiresHigherTier(blockMaterial, 1),
|
||||||
|
"incorrect_for_iron_tool" => RequiresHigherTier(blockMaterial, 2),
|
||||||
|
"incorrect_for_diamond_tool" => RequiresHigherTier(blockMaterial, 3),
|
||||||
|
"incorrect_for_netherite_tool" => RequiresHigherTier(blockMaterial, 4),
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool RequiresHigherTier(Material blockMaterial, int tier)
|
||||||
|
{
|
||||||
|
ItemType[] recommended = Material2Tool.GetCorrectToolForBlock(blockMaterial);
|
||||||
|
if (recommended.Length == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return GetRequiredTier(blockMaterial, recommended) > tier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the enchantment level from an item, supporting both legacy NBT and modern structured components.
|
||||||
|
/// </summary>
|
||||||
|
public static int GetEnchantmentLevel(Item? item, Enchantments enchantment, int protocolVersion)
|
||||||
|
{
|
||||||
|
if (item is null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// Modern path: structured components (1.20.6+)
|
||||||
|
var enchList = item.EnchantmentList;
|
||||||
|
if (enchList is not null)
|
||||||
|
{
|
||||||
|
var ench = enchList.FirstOrDefault(e => e.Type == enchantment);
|
||||||
|
if (ench is not null)
|
||||||
|
return ench.Level;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy path: NBT data
|
||||||
|
if (item.NBT is not null &&
|
||||||
|
item.NBT.TryGetValue("Enchantments", out object? enchantments))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string enchNameLower = GetEnchantmentResourceName(enchantment);
|
||||||
|
foreach (Dictionary<string, object> enchEntry in (object[])enchantments)
|
||||||
|
{
|
||||||
|
string id = ((string)enchEntry["id"]).ToLowerInvariant();
|
||||||
|
if (id == enchNameLower || id == "minecraft:" + enchNameLower)
|
||||||
|
return (short)enchEntry["lvl"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// NBT parsing failure - return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Map Enchantments enum to Minecraft resource name (e.g., "efficiency").
|
||||||
|
/// </summary>
|
||||||
|
private static string GetEnchantmentResourceName(Enchantments enchantment)
|
||||||
|
{
|
||||||
|
return enchantment switch
|
||||||
|
{
|
||||||
|
Enchantments.AquaAffinity => "aqua_affinity",
|
||||||
|
Enchantments.BaneOfArthropods => "bane_of_arthropods",
|
||||||
|
Enchantments.BlastProtection => "blast_protection",
|
||||||
|
Enchantments.Efficiency => "efficiency",
|
||||||
|
Enchantments.FeatherFalling => "feather_falling",
|
||||||
|
Enchantments.FireAspect => "fire_aspect",
|
||||||
|
Enchantments.FireProtection => "fire_protection",
|
||||||
|
Enchantments.FrostWalker => "frost_walker",
|
||||||
|
Enchantments.LuckOfTheSea => "luck_of_the_sea",
|
||||||
|
Enchantments.ProjectileProtection => "projectile_protection",
|
||||||
|
Enchantments.QuickCharge => "quick_charge",
|
||||||
|
Enchantments.SilkTouch => "silk_touch",
|
||||||
|
Enchantments.SoulSpeed => "soul_speed",
|
||||||
|
Enchantments.SwiftSneak => "swift_sneak",
|
||||||
|
Enchantments.VanishingCurse => "vanishing_curse",
|
||||||
|
Enchantments.BindingCurse => "binding_curse",
|
||||||
|
Enchantments.WindBurst => "wind_burst",
|
||||||
|
_ => enchantment.ToString().ToUnderscoreCase()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Legacy Tool Speed Tables
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Legacy tool speed for pre-1.20.6 versions using hardcoded values.
|
||||||
|
/// </summary>
|
||||||
|
private static float GetLegacyToolSpeed(ItemType toolType, Material blockMaterial)
|
||||||
|
{
|
||||||
|
float specialToolSpeed = toolType switch
|
||||||
|
{
|
||||||
|
ItemType.Shears => GetShearsSpeed(blockMaterial),
|
||||||
|
_ when IsSword(toolType) && blockMaterial == Material.Cobweb => 15.0f,
|
||||||
|
_ => 1.0f
|
||||||
|
};
|
||||||
|
|
||||||
|
if (specialToolSpeed > 1.0f)
|
||||||
|
return specialToolSpeed;
|
||||||
|
|
||||||
|
ItemType[] recommended = Material2Tool.GetCorrectToolForBlock(blockMaterial);
|
||||||
|
if (recommended.Length == 0)
|
||||||
|
return 1.0f;
|
||||||
|
|
||||||
|
// Check if the held tool matches the recommended tool category
|
||||||
|
ToolCategory heldCategory = GetToolCategory(toolType);
|
||||||
|
ToolCategory neededCategory = GetToolCategory(recommended[0]);
|
||||||
|
|
||||||
|
if (heldCategory == ToolCategory.None || heldCategory != neededCategory)
|
||||||
|
return 1.0f;
|
||||||
|
|
||||||
|
return GetBaseToolSpeed(toolType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float GetBaseToolSpeed(ItemType toolType)
|
||||||
|
{
|
||||||
|
return toolType switch
|
||||||
|
{
|
||||||
|
// Wooden tools
|
||||||
|
ItemType.WoodenPickaxe or ItemType.WoodenAxe or ItemType.WoodenShovel or
|
||||||
|
ItemType.WoodenSword or ItemType.WoodenHoe => 2.0f,
|
||||||
|
|
||||||
|
// Stone tools
|
||||||
|
ItemType.StonePickaxe or ItemType.StoneAxe or ItemType.StoneShovel or
|
||||||
|
ItemType.StoneSword or ItemType.StoneHoe => 4.0f,
|
||||||
|
|
||||||
|
// Iron tools
|
||||||
|
ItemType.IronPickaxe or ItemType.IronAxe or ItemType.IronShovel or
|
||||||
|
ItemType.IronSword or ItemType.IronHoe => 6.0f,
|
||||||
|
|
||||||
|
// Diamond tools
|
||||||
|
ItemType.DiamondPickaxe or ItemType.DiamondAxe or ItemType.DiamondShovel or
|
||||||
|
ItemType.DiamondSword or ItemType.DiamondHoe => 8.0f,
|
||||||
|
|
||||||
|
// Netherite tools
|
||||||
|
ItemType.NetheritePickaxe or ItemType.NetheriteAxe or ItemType.NetheriteShovel or
|
||||||
|
ItemType.NetheriteSword or ItemType.NetheriteHoe => 9.0f,
|
||||||
|
|
||||||
|
// Golden tools
|
||||||
|
ItemType.GoldenPickaxe or ItemType.GoldenAxe or ItemType.GoldenShovel or
|
||||||
|
ItemType.GoldenSword or ItemType.GoldenHoe => 12.0f,
|
||||||
|
|
||||||
|
// Shears
|
||||||
|
ItemType.Shears => 2.0f,
|
||||||
|
|
||||||
|
_ => 1.0f
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the held tool is the correct tool for drops in legacy versions.
|
||||||
|
/// Uses Material2Tool's recommendations to determine correctness.
|
||||||
|
/// </summary>
|
||||||
|
private static bool IsCorrectToolLegacy(ItemType toolType, Material blockMaterial)
|
||||||
|
{
|
||||||
|
ItemType[] recommended = Material2Tool.GetCorrectToolForBlock(blockMaterial);
|
||||||
|
if (recommended.Length == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ToolCategory heldCategory = GetToolCategory(toolType);
|
||||||
|
ToolCategory neededCategory = GetToolCategory(recommended[0]);
|
||||||
|
|
||||||
|
if (heldCategory == ToolCategory.None || heldCategory != neededCategory)
|
||||||
|
{
|
||||||
|
if (toolType == ItemType.Shears && blockMaterial == Material.Cobweb)
|
||||||
|
return true;
|
||||||
|
if (IsSword(toolType) && blockMaterial == Material.Cobweb)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check tool tier requirement
|
||||||
|
int heldTier = GetToolTier(toolType);
|
||||||
|
int requiredTier = GetRequiredTier(blockMaterial, recommended);
|
||||||
|
|
||||||
|
return heldTier >= requiredTier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the minimum tool tier required for a block based on Material2Tool's recommendation ordering.
|
||||||
|
/// </summary>
|
||||||
|
private static int GetRequiredTier(Material blockMaterial, ItemType[] recommended)
|
||||||
|
{
|
||||||
|
if (recommended.Length == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// Material2Tool lists tools from highest to lowest tier.
|
||||||
|
// The last tool in the array is the minimum required tier.
|
||||||
|
return GetToolTier(recommended[^1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum ToolCategory
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Pickaxe,
|
||||||
|
Axe,
|
||||||
|
Shovel,
|
||||||
|
Hoe,
|
||||||
|
Sword,
|
||||||
|
Shears
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ToolCategory GetToolCategory(ItemType item)
|
||||||
|
{
|
||||||
|
if (IsPickaxe(item)) return ToolCategory.Pickaxe;
|
||||||
|
if (IsAxe(item)) return ToolCategory.Axe;
|
||||||
|
if (IsShovel(item)) return ToolCategory.Shovel;
|
||||||
|
if (IsHoe(item)) return ToolCategory.Hoe;
|
||||||
|
if (IsSword(item)) return ToolCategory.Sword;
|
||||||
|
if (item == ItemType.Shears) return ToolCategory.Shears;
|
||||||
|
return ToolCategory.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetToolTier(ItemType item)
|
||||||
|
{
|
||||||
|
string name = item.ToString();
|
||||||
|
if (name.StartsWith("Wooden")) return 0;
|
||||||
|
if (name.StartsWith("Golden")) return 0;
|
||||||
|
if (name.StartsWith("Stone")) return 1;
|
||||||
|
if (name.StartsWith("Iron")) return 2;
|
||||||
|
if (name.StartsWith("Diamond")) return 3;
|
||||||
|
if (name.StartsWith("Netherite")) return 4;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsPickaxe(ItemType item) =>
|
||||||
|
item is ItemType.WoodenPickaxe or ItemType.StonePickaxe or ItemType.IronPickaxe
|
||||||
|
or ItemType.GoldenPickaxe or ItemType.DiamondPickaxe or ItemType.NetheritePickaxe;
|
||||||
|
|
||||||
|
private static bool IsAxe(ItemType item) =>
|
||||||
|
item is ItemType.WoodenAxe or ItemType.StoneAxe or ItemType.IronAxe
|
||||||
|
or ItemType.GoldenAxe or ItemType.DiamondAxe or ItemType.NetheriteAxe;
|
||||||
|
|
||||||
|
private static bool IsShovel(ItemType item) =>
|
||||||
|
item is ItemType.WoodenShovel or ItemType.StoneShovel or ItemType.IronShovel
|
||||||
|
or ItemType.GoldenShovel or ItemType.DiamondShovel or ItemType.NetheriteShovel;
|
||||||
|
|
||||||
|
private static bool IsHoe(ItemType item) =>
|
||||||
|
item is ItemType.WoodenHoe or ItemType.StoneHoe or ItemType.IronHoe
|
||||||
|
or ItemType.GoldenHoe or ItemType.DiamondHoe or ItemType.NetheriteHoe;
|
||||||
|
|
||||||
|
private static bool IsSword(ItemType item) =>
|
||||||
|
item is ItemType.WoodenSword or ItemType.StoneSword or ItemType.IronSword
|
||||||
|
or ItemType.GoldenSword or ItemType.DiamondSword or ItemType.NetheriteSword;
|
||||||
|
|
||||||
|
private static float GetShearsSpeed(Material block)
|
||||||
|
{
|
||||||
|
return block switch
|
||||||
|
{
|
||||||
|
Material.Cobweb => 15.0f,
|
||||||
|
Material.Vine or Material.GlowLichen => 2.0f,
|
||||||
|
_ when IsLeaf(block) => 15.0f,
|
||||||
|
_ when IsWool(block) => 5.0f,
|
||||||
|
_ => 1.0f
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsShearable(Material block) =>
|
||||||
|
block == Material.Cobweb || IsLeaf(block) || IsWool(block) || block is Material.Vine or Material.GlowLichen;
|
||||||
|
|
||||||
|
private static bool IsLeaf(Material block) =>
|
||||||
|
block is Material.OakLeaves or Material.SpruceLeaves or Material.BirchLeaves
|
||||||
|
or Material.JungleLeaves or Material.AcaciaLeaves or Material.DarkOakLeaves
|
||||||
|
or Material.CherryLeaves or Material.MangroveLeaves or Material.AzaleaLeaves
|
||||||
|
or Material.FloweringAzaleaLeaves or Material.PaleOakLeaves;
|
||||||
|
|
||||||
|
private static bool IsWool(Material block) =>
|
||||||
|
block is Material.WhiteWool or Material.OrangeWool or Material.MagentaWool
|
||||||
|
or Material.LightBlueWool or Material.YellowWool or Material.LimeWool
|
||||||
|
or Material.PinkWool or Material.GrayWool or Material.LightGrayWool
|
||||||
|
or Material.CyanWool or Material.PurpleWool or Material.BlueWool
|
||||||
|
or Material.BrownWool or Material.GreenWool or Material.RedWool
|
||||||
|
or Material.BlackWool;
|
||||||
|
|
||||||
|
private static float GetEfficiencyBonus(Item? heldItem, Dictionary<string, double> playerAttributes, int protocolVersion)
|
||||||
|
{
|
||||||
|
if (protocolVersion >= Protocol18Handler.MC_1_21_11_Version
|
||||||
|
&& playerAttributes.TryGetValue("player.mining_efficiency", out double miningEfficiency)
|
||||||
|
&& miningEfficiency > 0.0)
|
||||||
|
{
|
||||||
|
return (float)miningEfficiency;
|
||||||
|
}
|
||||||
|
|
||||||
|
int efficiencyLevel = GetEnchantmentLevel(heldItem, Enchantments.Efficiency, protocolVersion);
|
||||||
|
return efficiencyLevel > 0 ? efficiencyLevel * efficiencyLevel + 1 : 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetDigSpeedAmplifier(Dictionary<Effects, EffectData> effects)
|
||||||
|
{
|
||||||
|
int amplifier = -1;
|
||||||
|
|
||||||
|
if (effects.TryGetValue(Effects.Haste, out var hasteData))
|
||||||
|
amplifier = Math.Max(amplifier, hasteData.Amplifier);
|
||||||
|
|
||||||
|
if (effects.TryGetValue(Effects.ConduitPower, out var conduitData))
|
||||||
|
amplifier = Math.Max(amplifier, conduitData.Amplifier);
|
||||||
|
|
||||||
|
return amplifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -110,6 +110,9 @@ namespace MinecraftClient
|
||||||
|
|
||||||
// player effects
|
// player effects
|
||||||
private readonly Dictionary<Effects, EffectData> playerEffects = new();
|
private readonly Dictionary<Effects, EffectData> playerEffects = new();
|
||||||
|
|
||||||
|
// player attributes (e.g., block_break_speed, mining_efficiency, submerged_mining_speed)
|
||||||
|
private readonly Dictionary<string, double> playerAttributes = new();
|
||||||
|
|
||||||
// Sneaking
|
// Sneaking
|
||||||
public bool IsSneaking { get; set; } = false;
|
public bool IsSneaking { get; set; } = false;
|
||||||
|
|
@ -2630,6 +2633,13 @@ namespace MinecraftClient
|
||||||
if (lookAtBlock)
|
if (lookAtBlock)
|
||||||
UpdateLocation(GetCurrentLocation(), location);
|
UpdateLocation(GetCurrentLocation(), location);
|
||||||
|
|
||||||
|
// Auto-compute dig duration for survival/adventure mode when not explicitly supplied
|
||||||
|
if (duration <= 0 && protocolversion >= Protocol18Handler.MC_1_8_Version
|
||||||
|
&& gamemode is 0 or 2) // Survival or Adventure
|
||||||
|
{
|
||||||
|
duration = ComputeAutoDigDuration(location);
|
||||||
|
}
|
||||||
|
|
||||||
// Send dig start and dig end, will need to wait for server response to know dig result
|
// Send dig start and dig end, will need to wait for server response to know dig result
|
||||||
// See https://wiki.vg/How_to_Write_a_Client#Digging for more details
|
// See https://wiki.vg/How_to_Write_a_Client#Digging for more details
|
||||||
bool result = handler.SendPlayerDigging(0, location, blockFace, sequenceId++)
|
bool result = handler.SendPlayerDigging(0, location, blockFace, sequenceId++)
|
||||||
|
|
@ -2647,6 +2657,52 @@ namespace MinecraftClient
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compute the automatic dig duration in seconds for a block, based on held tool,
|
||||||
|
/// enchantments, effects, attributes, and player state.
|
||||||
|
/// Returns 0 for instant-break blocks.
|
||||||
|
/// </summary>
|
||||||
|
private double ComputeAutoDigDuration(Location location)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Block block = world.GetBlock(location);
|
||||||
|
Material blockMaterial = block.Type;
|
||||||
|
|
||||||
|
if (blockMaterial == Material.Air)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// Get held item from player inventory
|
||||||
|
Item? heldItem = null;
|
||||||
|
Item? helmetItem = null;
|
||||||
|
if (inventories.TryGetValue(0, out var playerInv))
|
||||||
|
{
|
||||||
|
int hotbarSlot = 36 + CurrentSlot; // Hotbar slots are 36-44
|
||||||
|
playerInv.Items.TryGetValue(hotbarSlot, out heldItem);
|
||||||
|
playerInv.Items.TryGetValue(5, out helmetItem); // Slot 5 = helmet
|
||||||
|
}
|
||||||
|
|
||||||
|
int ticks = MiningCalculator.ComputeDigTicks(
|
||||||
|
blockMaterial,
|
||||||
|
heldItem,
|
||||||
|
helmetItem,
|
||||||
|
playerEffects,
|
||||||
|
playerAttributes,
|
||||||
|
playerPhysics.InWater,
|
||||||
|
playerPhysics.OnGround,
|
||||||
|
protocolversion);
|
||||||
|
|
||||||
|
if (ticks <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return (double)ticks / Settings.ClientTicksPerSecond;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Change active slot in the player inventory
|
/// Change active slot in the player inventory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -3751,6 +3807,9 @@ namespace MinecraftClient
|
||||||
{
|
{
|
||||||
if (EntityID == playerEntityID)
|
if (EntityID == playerEntityID)
|
||||||
{
|
{
|
||||||
|
foreach (var kvp in prop)
|
||||||
|
playerAttributes[kvp.Key] = kvp.Value;
|
||||||
|
|
||||||
DispatchBotEvent(bot => bot.OnPlayerProperty(prop));
|
DispatchBotEvent(bot => bot.OnPlayerProperty(prop));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2514,7 +2514,7 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
if (handler.GetEntityHandlingEnabled())
|
if (handler.GetEntityHandlingEnabled())
|
||||||
{
|
{
|
||||||
var entityId = dataTypes.ReadNextVarInt(packetData);
|
var entityId = dataTypes.ReadNextVarInt(packetData);
|
||||||
var effectId = protocolVersion >= MC_1_18_2_Version
|
var effectId = protocolVersion >= MC_1_20_4_Version
|
||||||
? dataTypes.ReadNextVarInt(packetData) + 1
|
? dataTypes.ReadNextVarInt(packetData) + 1
|
||||||
: dataTypes.ReadNextByte(packetData);
|
: dataTypes.ReadNextByte(packetData);
|
||||||
|
|
||||||
|
|
@ -2544,7 +2544,7 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
if (handler.GetEntityHandlingEnabled())
|
if (handler.GetEntityHandlingEnabled())
|
||||||
{
|
{
|
||||||
var entityId = dataTypes.ReadNextVarInt(packetData);
|
var entityId = dataTypes.ReadNextVarInt(packetData);
|
||||||
var effectId = protocolVersion >= MC_1_18_2_Version
|
var effectId = protocolVersion >= MC_1_20_4_Version
|
||||||
? dataTypes.ReadNextVarInt(packetData) + 1
|
? dataTypes.ReadNextVarInt(packetData) + 1
|
||||||
: dataTypes.ReadNextByte(packetData);
|
: dataTypes.ReadNextByte(packetData);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1097,9 +1097,10 @@ namespace MinecraftClient.Scripting
|
||||||
/// <param name="direction">Example: if your player is under a block that is being destroyed, use Down</param>
|
/// <param name="direction">Example: if your player is under a block that is being destroyed, use Down</param>
|
||||||
/// <param name="swingArms">Also perform the "arm swing" animation</param>
|
/// <param name="swingArms">Also perform the "arm swing" animation</param>
|
||||||
/// <param name="lookAtBlock">Also look at the block before digging</param>
|
/// <param name="lookAtBlock">Also look at the block before digging</param>
|
||||||
protected bool DigBlock(Location location, Direction direction, bool swingArms = true, bool lookAtBlock = true)
|
/// <param name="duration">Dig duration in seconds. 0 = auto-compute for survival, or instant for creative</param>
|
||||||
|
protected bool DigBlock(Location location, Direction direction, bool swingArms = true, bool lookAtBlock = true, double duration = 0)
|
||||||
{
|
{
|
||||||
return Handler.DigBlock(location, direction, swingArms, lookAtBlock);
|
return Handler.DigBlock(location, direction, swingArms, lookAtBlock, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue