diff --git a/DebugTools/MccMcpStdioHarness/Program.cs b/DebugTools/MccMcpStdioHarness/Program.cs index bb085f27..a6eca2d1 100644 --- a/DebugTools/MccMcpStdioHarness/Program.cs +++ b/DebugTools/MccMcpStdioHarness/Program.cs @@ -409,6 +409,9 @@ internal sealed class DeterministicCapabilities : IMccMcpCapabilities playerLocation = new { x = C(0.5), y = C(80.0), z = C(0.5) } }); + public Task DigBlockAsync(double x, double y, double z, double durationSeconds) => + Task.FromResult(DigBlock(x, y, z, durationSeconds)); + public MccMcpResult PlaceBlock(int x, int y, int z, string face, string hand, bool lookAtBlock) => MccMcpResult.Ok(new { success = true, x, y, z, face, hand, lookAtBlock, action = "place_block" }); @@ -797,6 +800,9 @@ internal sealed class DeterministicCapabilities : IMccMcpCapabilities preferStack }); + public Task DropInventoryItemAsync(string itemType, int count, int inventoryId, bool preferStack) => + Task.FromResult(DropInventoryItem(itemType, count, inventoryId, preferStack)); + public MccMcpResult DepositContainerItem(string itemType, int count, int inventoryId, bool preferLargestStack) => MccMcpResult.Ok(new { @@ -815,6 +821,9 @@ internal sealed class DeterministicCapabilities : IMccMcpCapabilities touchedTargetSlots = new[] { 0 } }); + public Task DepositContainerItemAsync(string itemType, int count, int inventoryId, bool preferLargestStack) => + Task.FromResult(DepositContainerItem(itemType, count, inventoryId, preferLargestStack)); + public MccMcpResult WithdrawContainerItem(string itemType, int count, int inventoryId, bool preferLargestStack) => MccMcpResult.Ok(new { @@ -833,6 +842,9 @@ internal sealed class DeterministicCapabilities : IMccMcpCapabilities touchedTargetSlots = new[] { 36 } }); + public Task WithdrawContainerItemAsync(string itemType, int count, int inventoryId, bool preferLargestStack) => + Task.FromResult(WithdrawContainerItem(itemType, count, inventoryId, preferLargestStack)); + public MccMcpResult QueryEntities(int maxCount) => MccMcpResult.Ok(new { diff --git a/MinecraftClient/Mcp/IMccMcpCapabilities.cs b/MinecraftClient/Mcp/IMccMcpCapabilities.cs index 1054bfb6..273a37cb 100644 --- a/MinecraftClient/Mcp/IMccMcpCapabilities.cs +++ b/MinecraftClient/Mcp/IMccMcpCapabilities.cs @@ -35,6 +35,7 @@ public interface IMccMcpCapabilities MccMcpResult SelectHotbarItem(string itemType, bool preferLowestSlot); MccMcpResult UseItemOnBlock(double x, double y, double z); MccMcpResult DigBlock(double x, double y, double z, double durationSeconds); + Task DigBlockAsync(double x, double y, double z, double durationSeconds); MccMcpResult PlaceBlock(int x, int y, int z, string face, string hand, bool lookAtBlock); MccMcpResult InteractEntity(int entityId, string interaction, string hand); MccMcpResult AttackEntity(int entityId); @@ -60,8 +61,11 @@ public interface IMccMcpCapabilities Task CloseContainerAsync(int inventoryId, int timeoutMs); MccMcpResult InventoryWindowAction(int inventoryId, int slotId, string actionType); MccMcpResult DropInventoryItem(string itemType, int count, int inventoryId, bool preferStack); + Task DropInventoryItemAsync(string itemType, int count, int inventoryId, bool preferStack); MccMcpResult DepositContainerItem(string itemType, int count, int inventoryId, bool preferLargestStack); + Task DepositContainerItemAsync(string itemType, int count, int inventoryId, bool preferLargestStack); MccMcpResult WithdrawContainerItem(string itemType, int count, int inventoryId, bool preferLargestStack); + Task WithdrawContainerItemAsync(string itemType, int count, int inventoryId, bool preferLargestStack); MccMcpResult QueryEntities(int maxCount); MccMcpResult ListEntities(int maxCount, string? typeFilter, double radius); MccMcpResult GetEntityInfo(int entityId, bool includeMetadata, bool includeEquipment, bool includeEffects); diff --git a/MinecraftClient/Mcp/MccMcpCapabilities.cs b/MinecraftClient/Mcp/MccMcpCapabilities.cs index a1127d18..3f4fa060 100644 --- a/MinecraftClient/Mcp/MccMcpCapabilities.cs +++ b/MinecraftClient/Mcp/MccMcpCapabilities.cs @@ -861,6 +861,11 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities } public MccMcpResult DigBlock(double x, double y, double z, double durationSeconds) + { + return DigBlockAsync(x, y, z, durationSeconds).GetAwaiter().GetResult(); + } + + public async Task DigBlockAsync(double x, double y, double z, double durationSeconds) { if (!IsCategoryEnabled(t => t.Movement)) return MccMcpResult.Fail("capability_disabled"); @@ -923,7 +928,10 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities if (!accepted) continue; - if (WaitForBlockChange(client, target, beforeBlock, GetDigVerifyWaitMs(attemptDuration), out afterBlock)) + (bool blockChanged, Block updatedBlock) = + await WaitForBlockChangeAsync(client, target, beforeBlock, GetDigVerifyWaitMs(attemptDuration)); + afterBlock = updatedBlock; + if (blockChanged) { changed = true; break; @@ -1590,6 +1598,11 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities } public MccMcpResult DropInventoryItem(string itemType, int count, int inventoryId, bool preferStack) + { + return DropInventoryItemAsync(itemType, count, inventoryId, preferStack).GetAwaiter().GetResult(); + } + + public async Task DropInventoryItemAsync(string itemType, int count, int inventoryId, bool preferStack) { if (!IsCategoryEnabled(t => t.Inventory)) return MccMcpResult.Fail("capability_disabled"); @@ -1612,10 +1625,10 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities }); } - return client.InvokeOnMainThread(() => + return await Task.Run(async () => { - Dictionary inventories = client.GetInventories(); - if (!inventories.TryGetValue(inventoryId, out Container? inventory)) + Container? inventory = client.GetInventory(inventoryId); + if (inventory is null) return MccMcpResult.Fail("invalid_state"); int cursorCount = GetCursorItemCount(inventory, parsedItemType); @@ -1659,7 +1672,7 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities int dropFromSlot = Math.Min(remaining, currentItem.Count); touchedSlots.Add(entry.slot); - bool ok = TryDropInventorySlotItems(client, inventoryId, inventory, entry.slot, parsedItemType, dropFromSlot, out int droppedFromSlot); + (bool ok, int droppedFromSlot) = await TryDropInventorySlotItemsAsync(client, inventoryId, inventory, entry.slot, parsedItemType, dropFromSlot); if (!ok) { @@ -1725,12 +1738,22 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities public MccMcpResult DepositContainerItem(string itemType, int count, int inventoryId, bool preferLargestStack) { - return TransferContainerItem(itemType, count, inventoryId, preferLargestStack, InventoryTransferDirection.Deposit); + return DepositContainerItemAsync(itemType, count, inventoryId, preferLargestStack).GetAwaiter().GetResult(); + } + + public Task DepositContainerItemAsync(string itemType, int count, int inventoryId, bool preferLargestStack) + { + return TransferContainerItemAsync(itemType, count, inventoryId, preferLargestStack, InventoryTransferDirection.Deposit); } public MccMcpResult WithdrawContainerItem(string itemType, int count, int inventoryId, bool preferLargestStack) { - return TransferContainerItem(itemType, count, inventoryId, preferLargestStack, InventoryTransferDirection.Withdraw); + return WithdrawContainerItemAsync(itemType, count, inventoryId, preferLargestStack).GetAwaiter().GetResult(); + } + + public Task WithdrawContainerItemAsync(string itemType, int count, int inventoryId, bool preferLargestStack) + { + return TransferContainerItemAsync(itemType, count, inventoryId, preferLargestStack, InventoryTransferDirection.Withdraw); } public MccMcpResult QueryEntities(int maxCount) @@ -1957,6 +1980,11 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities } private MccMcpResult TransferContainerItem(string itemType, int count, int inventoryId, bool preferLargestStack, InventoryTransferDirection direction) + { + return TransferContainerItemAsync(itemType, count, inventoryId, preferLargestStack, direction).GetAwaiter().GetResult(); + } + + private async Task TransferContainerItemAsync(string itemType, int count, int inventoryId, bool preferLargestStack, InventoryTransferDirection direction) { if (!IsCategoryEnabled(t => t.Inventory)) return MccMcpResult.Fail("capability_disabled"); @@ -2078,7 +2106,18 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities if (direction == InventoryTransferDirection.Withdraw) { - if (!WaitForRangeCount(client, resolvedInventoryId, parsedItemType, sourceStart, sourceEnd, countAfterShift => countAfterShift < beforeSourceCount, DefaultInventoryActionWaitMs, out Container? afterShift, out int afterSourceCount)) + Container? afterShift = null; + int afterSourceCount = beforeSourceCount; + if (!await WaitForRangeCountAsync(client, resolvedInventoryId, parsedItemType, sourceStart, sourceEnd, countAfterShift => countAfterShift < beforeSourceCount, DefaultInventoryActionWaitMs, result => + { + afterShift = result.Inventory; + afterSourceCount = result.ItemCount; + }, + onInitialize: () => + { + afterShift = null; + afterSourceCount = beforeSourceCount; + })) { afterShift = client.InvokeOnMainThread(() => client.GetInventory(resolvedInventoryId)); afterSourceCount = afterShift is null ? beforeSourceCount : CountItemInRange(afterShift, parsedItemType, sourceStart, sourceEnd); @@ -2088,7 +2127,18 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities } else { - if (!WaitForRangeCount(client, resolvedInventoryId, parsedItemType, targetStart, targetEnd, countAfterShift => countAfterShift > beforeTargetCount, DefaultInventoryActionWaitMs, out Container? afterShift, out int afterTargetCount)) + Container? afterShift = null; + int afterTargetCount = beforeTargetCount; + if (!await WaitForRangeCountAsync(client, resolvedInventoryId, parsedItemType, targetStart, targetEnd, countAfterShift => countAfterShift > beforeTargetCount, DefaultInventoryActionWaitMs, result => + { + afterShift = result.Inventory; + afterTargetCount = result.ItemCount; + }, + onInitialize: () => + { + afterShift = null; + afterTargetCount = beforeTargetCount; + })) { afterShift = client.InvokeOnMainThread(() => client.GetInventory(resolvedInventoryId)); afterTargetCount = afterShift is null ? beforeTargetCount : CountItemInRange(afterShift, parsedItemType, targetStart, targetEnd); @@ -2100,7 +2150,7 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities if (direction == InventoryTransferDirection.Withdraw && movedCount > remaining) { int excessCount = movedCount - remaining; - MccMcpResult returnExcess = TransferContainerItem(parsedItemType.ToString(), excessCount, resolvedInventoryId, preferLargestStack, InventoryTransferDirection.Deposit); + MccMcpResult returnExcess = await TransferContainerItemAsync(parsedItemType.ToString(), excessCount, resolvedInventoryId, preferLargestStack, InventoryTransferDirection.Deposit); if (!returnExcess.Success) { return MccMcpResult.Fail("action_incomplete", data: new @@ -2121,7 +2171,7 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities } else { - movedCount = TransferPartialFromSlot( + movedCount = await TransferPartialFromSlotAsync( client, resolvedInventoryId, slot, @@ -2405,16 +2455,10 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities private static bool TryDropInventorySlotItems(McClient client, int inventoryId, Container inventory, int slotId, ItemType itemType, int dropCount, out int droppedCount) { - droppedCount = 0; - if (!inventory.Items.TryGetValue(slotId, out Item? currentItem) || currentItem.Type != itemType || currentItem.Count <= 0) - return false; - - if (inventoryId == 0 && inventory.IsHotbar(slotId, out int hotbarSlot)) - { - return TryDropHotbarSlotItems(client, slotId, hotbarSlot, itemType, dropCount, currentItem.Count, out droppedCount); - } - - return TryDropWindowSlotItems(client, inventoryId, slotId, itemType, dropCount, currentItem.Count, out droppedCount); + (bool success, int actualDroppedCount) = + TryDropInventorySlotItemsAsync(client, inventoryId, inventory, slotId, itemType, dropCount).GetAwaiter().GetResult(); + droppedCount = actualDroppedCount; + return success; } private static int CountItemInRange(Container inventory, ItemType itemType, int startSlot, int endSlot) @@ -2438,7 +2482,7 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities .ToArray(); } - private static int TransferPartialFromSlot(McClient client, int inventoryId, int sourceSlot, ItemType itemType, int requestedCount, int sourceStart, int sourceEnd, int targetStart, int targetEnd, List touchedTargetSlots) + private static async Task TransferPartialFromSlotAsync(McClient client, int inventoryId, int sourceSlot, ItemType itemType, int requestedCount, int sourceStart, int sourceEnd, int targetStart, int targetEnd, List touchedTargetSlots) { Container? inventory = client.InvokeOnMainThread(() => client.GetInventory(inventoryId)); if (inventory is null || !inventory.Items.TryGetValue(sourceSlot, out Item? sourceItem) || sourceItem.Count <= 0) @@ -2448,7 +2492,7 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities if (!client.DoWindowAction(inventoryId, sourceSlot, WindowActionType.LeftClick)) return 0; - if (!WaitForCursorItem(client, itemType, DefaultInventoryActionWaitMs, out _)) + if (!await WaitForCursorItemAsync(client, itemType, DefaultInventoryActionWaitMs)) return 0; int moved = 0; @@ -2467,7 +2511,7 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities if (step <= 0 || !PlaceItemsFromCursor(client, inventoryId, targetSlot, step)) break; - if (!WaitForPlacement(client, inventoryId, targetSlot, itemType, beforeTargetCount, beforeCursorCount, step)) + if (!await WaitForPlacementAsync(client, inventoryId, targetSlot, itemType, beforeTargetCount, beforeCursorCount, step)) break; touchedTargetSlots.Add(targetSlot); @@ -2484,7 +2528,7 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities if (!client.DoWindowAction(inventoryId, returnSlot, WindowActionType.LeftClick)) return 0; - if (!WaitForCursorClear(client, DefaultInventoryActionWaitMs)) + if (!await WaitForCursorClearAsync(client, DefaultInventoryActionWaitMs)) return 0; } @@ -2570,27 +2614,27 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities return inventory.Items.TryGetValue(slot, out Item? item) && item.Type == itemType ? item.Count : 0; } - private static bool TryDropHotbarSlotItems(McClient client, int slotId, int hotbarSlot, ItemType itemType, int dropCount, int availableInSlot, out int droppedCount) + private static async Task<(bool Success, int DroppedCount)> TryDropHotbarSlotItemsAsync(McClient client, int slotId, int hotbarSlot, ItemType itemType, int dropCount, int availableInSlot) { - droppedCount = 0; - byte previousSlot = client.GetCurrentSlot(); + int droppedCount = 0; + byte previousSlot = client.InvokeOnMainThread(client.GetCurrentSlot); bool restoreSlot = previousSlot != hotbarSlot; if (restoreSlot && !client.ChangeSlot((short)hotbarSlot)) - return false; + return (false, 0); try { if (dropCount >= availableInSlot) { if (!client.DropSelectedItem(dropEntireStack: true)) - return false; + return (false, droppedCount); - if (!WaitForSlotItemCount(client, 0, slotId, itemType, count => count == 0, DefaultInventoryActionWaitMs, out _, out _)) - return false; + if (!await WaitForSlotItemCountAsync(client, 0, slotId, itemType, count => count == 0, DefaultInventoryActionWaitMs)) + return (false, droppedCount); droppedCount = availableInSlot; - return true; + return (true, droppedCount); } int remaining = dropCount; @@ -2598,23 +2642,23 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities { Container? currentInventory = client.GetInventory(0); if (currentInventory is null) - return false; + return (false, droppedCount); int beforeSlotCount = GetSlotItemCount(currentInventory, slotId, itemType); if (beforeSlotCount <= 0) break; if (!client.DropSelectedItem(dropEntireStack: false)) - return false; + return (false, droppedCount); - if (!WaitForSlotItemCount(client, 0, slotId, itemType, count => count <= beforeSlotCount - 1, DefaultInventoryActionWaitMs, out _, out _)) - return false; + if (!await WaitForSlotItemCountAsync(client, 0, slotId, itemType, count => count <= beforeSlotCount - 1, DefaultInventoryActionWaitMs)) + return (false, droppedCount); remaining--; droppedCount++; } - return remaining == 0; + return (remaining == 0, droppedCount); } finally { @@ -2623,20 +2667,20 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities } } - private static bool TryDropWindowSlotItems(McClient client, int inventoryId, int slotId, ItemType itemType, int dropCount, int availableInSlot, out int droppedCount) + private static async Task<(bool Success, int DroppedCount)> TryDropWindowSlotItemsAsync(McClient client, int inventoryId, int slotId, ItemType itemType, int dropCount, int availableInSlot) { - droppedCount = 0; + int droppedCount = 0; if (dropCount >= availableInSlot) { if (!client.DoWindowAction(inventoryId, slotId, WindowActionType.DropItemStack)) - return false; + return (false, droppedCount); - if (!WaitForSlotItemCount(client, inventoryId, slotId, itemType, count => count == 0, DefaultInventoryActionWaitMs, out _, out _)) - return false; + if (!await WaitForSlotItemCountAsync(client, inventoryId, slotId, itemType, count => count == 0, DefaultInventoryActionWaitMs)) + return (false, droppedCount); droppedCount = availableInSlot; - return true; + return (true, droppedCount); } int remaining = dropCount; @@ -2644,23 +2688,143 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities { Container? currentInventory = client.GetInventory(inventoryId); if (currentInventory is null) - return false; + return (false, droppedCount); int beforeSlotCount = GetSlotItemCount(currentInventory, slotId, itemType); if (beforeSlotCount <= 0) break; if (!client.DoWindowAction(inventoryId, slotId, WindowActionType.DropItem)) - return false; + return (false, droppedCount); - if (!WaitForSlotItemCount(client, inventoryId, slotId, itemType, count => count <= beforeSlotCount - 1, DefaultInventoryActionWaitMs, out _, out _)) - return false; + if (!await WaitForSlotItemCountAsync(client, inventoryId, slotId, itemType, count => count <= beforeSlotCount - 1, DefaultInventoryActionWaitMs)) + return (false, droppedCount); remaining--; droppedCount++; } - return remaining == 0; + return (remaining == 0, droppedCount); + } + + private static Task<(bool Success, int DroppedCount)> TryDropInventorySlotItemsAsync(McClient client, int inventoryId, Container inventory, int slotId, ItemType itemType, int dropCount) + { + if (!inventory.Items.TryGetValue(slotId, out Item? currentItem) || currentItem.Type != itemType || currentItem.Count <= 0) + return Task.FromResult((false, 0)); + + if (inventoryId == 0 && inventory.IsHotbar(slotId, out int hotbarSlot)) + return TryDropHotbarSlotItemsAsync(client, slotId, hotbarSlot, itemType, dropCount, currentItem.Count); + + return TryDropWindowSlotItemsAsync(client, inventoryId, slotId, itemType, dropCount, currentItem.Count); + } + + private readonly record struct InventoryCountState(Container? Inventory, int ItemCount); + + private static async Task WaitForCursorItemAsync(McClient client, ItemType itemType, int waitMs) + { + DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs); + while (true) + { + if (TryGetCursorItem(client, out Item? cursorItem) && cursorItem is not null && cursorItem.Type == itemType) + return true; + + if (DateTime.UtcNow >= deadline) + return false; + + await Task.Delay(ArrivalPollIntervalMs); + } + } + + private static async Task WaitForCursorClearAsync(McClient client, int waitMs) + { + DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs); + while (true) + { + if (!TryGetCursorItem(client, out _)) + return true; + + if (DateTime.UtcNow >= deadline) + return false; + + await Task.Delay(ArrivalPollIntervalMs); + } + } + + private static async Task WaitForPlacementAsync(McClient client, int inventoryId, int targetSlot, ItemType itemType, int beforeTargetCount, int beforeCursorCount, int placedCount) + { + DateTime deadline = DateTime.UtcNow.AddMilliseconds(DefaultInventoryActionWaitMs); + while (true) + { + bool targetUpdated = false; + bool cursorUpdated = false; + + Container? inventory = client.GetInventory(inventoryId); + if (inventory is not null) + { + int currentTargetCount = GetSlotItemCount(inventory, targetSlot, itemType); + targetUpdated = currentTargetCount >= beforeTargetCount + placedCount; + } + + if (placedCount >= beforeCursorCount) + { + cursorUpdated = !TryGetCursorItem(client, out _); + } + else if (TryGetCursorItem(client, out Item? cursorItem) && cursorItem is not null && cursorItem.Type == itemType) + { + cursorUpdated = cursorItem.Count <= beforeCursorCount - placedCount; + } + + if (targetUpdated && cursorUpdated) + return true; + + if (DateTime.UtcNow >= deadline) + return false; + + await Task.Delay(ArrivalPollIntervalMs); + } + } + + private static async Task WaitForSlotItemCountAsync(McClient client, int inventoryId, int slotId, ItemType itemType, Func predicate, int waitMs) + { + DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs); + while (true) + { + Container? inventory = client.GetInventory(inventoryId); + if (inventory is not null) + { + int itemCount = GetSlotItemCount(inventory, slotId, itemType); + if (predicate(itemCount)) + return true; + } + + if (DateTime.UtcNow >= deadline) + return false; + + await Task.Delay(ArrivalPollIntervalMs); + } + } + + private static async Task WaitForRangeCountAsync(McClient client, int inventoryId, ItemType itemType, int startSlot, int endSlot, Func predicate, int waitMs, Action onObserved, Action onInitialize) + { + onInitialize(); + DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs); + while (true) + { + Container? inventory = client.GetInventory(inventoryId); + int itemCount = 0; + if (inventory is not null) + { + itemCount = CountItemInRange(inventory, itemType, startSlot, endSlot); + onObserved(new InventoryCountState(inventory, itemCount)); + if (predicate(itemCount)) + return true; + } + + if (DateTime.UtcNow >= deadline) + return false; + + await Task.Delay(ArrivalPollIntervalMs); + } } private static bool WaitForCursorItem(McClient client, ItemType itemType, int waitMs, out Item? cursorItem) @@ -3040,6 +3204,24 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities } } + private static async Task<(bool Changed, Block AfterBlock)> WaitForBlockChangeAsync(McClient client, Location target, Block beforeBlock, int waitMs) + { + Block afterBlock = beforeBlock; + DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs); + while (true) + { + Block current = client.InvokeOnMainThread(() => client.GetWorld().GetBlock(target)); + afterBlock = current; + if (!AreEquivalentBlocks(current, beforeBlock)) + return (true, afterBlock); + + if (DateTime.UtcNow >= deadline) + return (false, afterBlock); + + await Task.Delay(ArrivalPollIntervalMs); + } + } + private static bool AreEquivalentBlocks(Block left, Block right) { return left.BlockId == right.BlockId diff --git a/MinecraftClient/Mcp/MccMcpToolSet.cs b/MinecraftClient/Mcp/MccMcpToolSet.cs index 46fe955e..0be91c01 100644 --- a/MinecraftClient/Mcp/MccMcpToolSet.cs +++ b/MinecraftClient/Mcp/MccMcpToolSet.cs @@ -203,9 +203,9 @@ public sealed class MccMcpToolSet } [McpServerTool(Name = "mcc_dig_block"), Description("Dig a block at target location.")] - public object DigBlock(double x, double y, double z, double durationSeconds = 0) + public async Task DigBlock(double x, double y, double z, double durationSeconds = 0) { - return capabilities.DigBlock(x, y, z, durationSeconds); + return await capabilities.DigBlockAsync(x, y, z, durationSeconds); } [McpServerTool(Name = "mcc_place_block"), Description("Place the currently held block/item at a target block location.")] @@ -329,33 +329,33 @@ public sealed class MccMcpToolSet } [McpServerTool(Name = "mcc_inventory_drop_item"), Description("Drop an exact item count from an inventory by item type.")] - public object InventoryDropItem( + public async Task InventoryDropItem( [Description("Item type enum name (e.g. Diamond).")] string itemType, [Description("Exact number of items to drop.")] int count, [Description("Inventory ID. 0 is the player inventory.")] int inventoryId = 0, [Description("Prefer dropping from larger stacks first when true.")] bool preferStack = false) { - return capabilities.DropInventoryItem(itemType, count, inventoryId, preferStack); + return await capabilities.DropInventoryItemAsync(itemType, count, inventoryId, preferStack); } [McpServerTool(Name = "mcc_container_deposit_item"), Description("Move an exact item count from the player inventory into an open container and verify the transfer.")] - public object ContainerDepositItem( + public async Task ContainerDepositItem( [Description("Item type enum name (e.g. Diamond).")] string itemType, [Description("Exact number of items to move into the container.")] int count, [Description("Container inventory ID, or -1 for the active non-player container.")] int inventoryId = -1, [Description("Prefer larger source stacks first when true.")] bool preferLargestStack = true) { - return capabilities.DepositContainerItem(itemType, count, inventoryId, preferLargestStack); + return await capabilities.DepositContainerItemAsync(itemType, count, inventoryId, preferLargestStack); } [McpServerTool(Name = "mcc_container_withdraw_item"), Description("Move an exact item count from an open container into the player inventory and verify the transfer.")] - public object ContainerWithdrawItem( + public async Task ContainerWithdrawItem( [Description("Item type enum name (e.g. Diamond).")] string itemType, [Description("Exact number of items to move into the player inventory.")] int count, [Description("Container inventory ID, or -1 for the active non-player container.")] int inventoryId = -1, [Description("Prefer larger source stacks first when true.")] bool preferLargestStack = true) { - return capabilities.WithdrawContainerItem(itemType, count, inventoryId, preferLargestStack); + return await capabilities.WithdrawContainerItemAsync(itemType, count, inventoryId, preferLargestStack); } [McpServerTool(Name = "mcc_entities_query"), Description("Query tracked entities.")] diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index f554a35a..1765a3be 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -3974,6 +3974,11 @@ namespace MinecraftClient.Protocol.Handlers /// /// True if login successful public bool Login(PlayerKeyPair? playerKeyPair, SessionToken session, bool isTransfer = false) + { + return LoginAsync(playerKeyPair, session, isTransfer).GetAwaiter().GetResult(); + } + + private async Task LoginAsync(PlayerKeyPair? playerKeyPair, SessionToken session, bool isTransfer = false) { int nextState = isTransfer && protocolVersion >= MC_1_20_6_Version ? 3 : 2; @@ -4052,7 +4057,7 @@ namespace MinecraftClient.Protocol.Handlers // 3. Encryption Request - 9. Login Acknowledged while (true) { - var (packetId, packetData) = ReadNextPacket(); + var (packetId, packetData) = await ReadNextPacketAsync(CancellationToken.None); switch (packetId) { @@ -4075,7 +4080,7 @@ namespace MinecraftClient.Protocol.Handlers if (protocolVersion >= MC_1_20_6_Version) shouldAuthetnicate = dataTypes.ReadNextBool(packetData); - return StartEncryption(handler.GetUserUuidStr(), handler.GetSessionID(), + return await StartEncryptionAsync(handler.GetUserUuidStr(), handler.GetSessionID(), Config.Main.General.AccountType, token, serverId, serverPublicKey, playerKeyPair, session, shouldAuthetnicate); } @@ -4091,7 +4096,7 @@ namespace MinecraftClient.Protocol.Handlers if (protocolVersion >= MC_1_20_2_Version) SendPacket(0x03, new List()); - if (!pForge.CompleteForgeHandshake()) + if (!await pForge.CompleteForgeHandshakeAsync()) { log.Error($"§8{Translations.error_forge}"); return false; @@ -4112,7 +4117,7 @@ namespace MinecraftClient.Protocol.Handlers /// Start network encryption. Automatically called by Login() if the server requests encryption. /// /// True if encryption was successful - private bool StartEncryption(string uuid, string sessionID, LoginType type, byte[] token, string serverIDhash, + private async Task StartEncryptionAsync(string uuid, string sessionID, LoginType type, byte[] token, string serverIDhash, byte[] serverPublicKey, PlayerKeyPair? playerKeyPair, SessionToken session, bool shouldAuthetnicate) { var RSAService = CryptoHandler.DecodeRSAPublicKey(serverPublicKey)!; @@ -4140,7 +4145,7 @@ namespace MinecraftClient.Protocol.Handlers if (needCheckSession) { var serverHash = CryptoHandler.GetServerHash(serverIDhash, serverPublicKey, secretKey); - if (ProtocolHandler.SessionCheck(uuid, sessionID, serverHash, type)) + if (await ProtocolHandler.SessionCheckAsync(uuid, sessionID, serverHash, type)) { session.ServerIDhash = serverIDhash; session.ServerPublicKey = serverPublicKey; @@ -4190,7 +4195,7 @@ namespace MinecraftClient.Protocol.Handlers int loopPrevention = ushort.MaxValue; while (true) { - var (packetId, packetData) = ReadNextPacket(); + var (packetId, packetData) = await ReadNextPacketAsync(CancellationToken.None); if (packetId < 0 || loopPrevention-- < 0) // Failed to read packet or too many iterations (issue #1150) { handler.OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, @@ -4241,7 +4246,7 @@ namespace MinecraftClient.Protocol.Handlers handler.OnLoginSuccess(uuidReceived, userName, playerProperty); - if (!pForge.CompleteForgeHandshake()) + if (!await pForge.CompleteForgeHandshakeAsync()) { log.Error($"§8{Translations.error_forge_encrypt}"); return false; diff --git a/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs b/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs index 7359d200..40b748f3 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; +using System.Threading.Tasks; using MinecraftClient.Protocol.Handlers.Forge; using MinecraftClient.Protocol.Message; using MinecraftClient.Scripting; @@ -21,6 +22,7 @@ namespace MinecraftClient.Protocol.Handlers private readonly ForgeInfo? forgeInfo = forgeInfo; private FMLHandshakeClientState fmlHandshakeState = FMLHandshakeClientState.START; + private DateTime? pendingServerDataAckAt; private bool ForgeEnabled() { return forgeInfo is not null; } /// @@ -40,12 +42,17 @@ namespace MinecraftClient.Protocol.Handlers /// /// Whether the handshake was successful. public bool CompleteForgeHandshake() + { + return CompleteForgeHandshakeAsync().GetAwaiter().GetResult(); + } + + public async Task CompleteForgeHandshakeAsync(CancellationToken cancellationToken = default) { if (ForgeEnabled() && forgeInfo!.Version == FMLVersion.FML) { while (fmlHandshakeState != FMLHandshakeClientState.DONE) { - (int packetID, Queue packetData) = protocol18.ReadNextPacket(); + (int packetID, Queue packetData) = await protocol18.ReadNextPacketAsync(cancellationToken); if (packetID == 0x40) // Disconnect { @@ -56,12 +63,31 @@ namespace MinecraftClient.Protocol.Handlers { // Send back regular packet to the vanilla protocol handler protocol18.HandlePacket(packetID, packetData); + await FlushPendingHandshakeActionsAsync(cancellationToken); } } } return true; } + private async Task FlushPendingHandshakeActionsAsync(CancellationToken cancellationToken) + { + if (!pendingServerDataAckAt.HasValue) + return; + + TimeSpan delay = pendingServerDataAckAt.Value - DateTime.UtcNow; + if (delay > TimeSpan.Zero) + await Task.Delay(delay, cancellationToken); + + if (Settings.Config.Logging.DebugMessages) + ConsoleIO.WriteLineFormatted("§8" + Translations.forge_accept, acceptnewlines: true); + + SendForgeHandshakePacket(FMLHandshakeDiscriminator.HandshakeAck, + new byte[] { (byte)FMLHandshakeClientState.WAITINGSERVERDATA }); + + pendingServerDataAckAt = null; + } + /// /// Read Forge VarShort field /// @@ -145,16 +171,9 @@ namespace MinecraftClient.Protocol.Handlers if (discriminator != FMLHandshakeDiscriminator.ModList) return false; - Thread.Sleep(2000); - - if (Settings.Config.Logging.DebugMessages) - ConsoleIO.WriteLineFormatted("§8" + Translations.forge_accept, acceptnewlines: true); // Tell the server that yes, we are OK with the mods it has // even though we don't actually care what mods it has. - - SendForgeHandshakePacket(FMLHandshakeDiscriminator.HandshakeAck, - new byte[] { (byte)FMLHandshakeClientState.WAITINGSERVERDATA }); - + pendingServerDataAckAt = DateTime.UtcNow.AddSeconds(2); fmlHandshakeState = FMLHandshakeClientState.WAITINGSERVERCOMPLETE; return false; case FMLHandshakeClientState.WAITINGSERVERCOMPLETE: