mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
Moved more stuff to async
This commit is contained in:
parent
3a20f2e235
commit
297d7dbc30
8 changed files with 389 additions and 44 deletions
|
|
@ -567,6 +567,9 @@ internal sealed class DeterministicCapabilities : IMccMcpCapabilities
|
|||
timeoutMs
|
||||
});
|
||||
|
||||
public Task<MccMcpResult> MoveToAsync(double x, double y, double z, bool allowUnsafe, bool allowDirectTeleport, int maxOffset, int minOffset, int timeoutMs) =>
|
||||
Task.FromResult(MoveTo(x, y, z, allowUnsafe, allowDirectTeleport, maxOffset, minOffset, timeoutMs));
|
||||
|
||||
public MccMcpResult MoveToPlayer(string playerName, bool allowUnsafe, bool allowDirectTeleport, int maxOffset, int minOffset, int timeoutMs) =>
|
||||
MccMcpResult.Ok(new
|
||||
{
|
||||
|
|
@ -593,6 +596,9 @@ internal sealed class DeterministicCapabilities : IMccMcpCapabilities
|
|||
timeoutMs
|
||||
});
|
||||
|
||||
public Task<MccMcpResult> MoveToPlayerAsync(string playerName, bool allowUnsafe, bool allowDirectTeleport, int maxOffset, int minOffset, int timeoutMs) =>
|
||||
Task.FromResult(MoveToPlayer(playerName, allowUnsafe, allowDirectTeleport, maxOffset, minOffset, timeoutMs));
|
||||
|
||||
public MccMcpResult LookAt(double x, double y, double z) =>
|
||||
MccMcpResult.Ok(new { looked = true, x = C(x), y = C(y), z = C(z) });
|
||||
|
||||
|
|
@ -755,6 +761,9 @@ internal sealed class DeterministicCapabilities : IMccMcpCapabilities
|
|||
});
|
||||
}
|
||||
|
||||
public Task<MccMcpResult> OpenContainerAtAsync(int x, int y, int z, int timeoutMs, bool closeCurrent) =>
|
||||
Task.FromResult(OpenContainerAt(x, y, z, timeoutMs, closeCurrent));
|
||||
|
||||
public MccMcpResult CloseContainer(int inventoryId, int timeoutMs)
|
||||
{
|
||||
int resolvedInventoryId = inventoryId <= 0 ? 1 : inventoryId;
|
||||
|
|
@ -768,6 +777,9 @@ internal sealed class DeterministicCapabilities : IMccMcpCapabilities
|
|||
});
|
||||
}
|
||||
|
||||
public Task<MccMcpResult> CloseContainerAsync(int inventoryId, int timeoutMs) =>
|
||||
Task.FromResult(CloseContainer(inventoryId, timeoutMs));
|
||||
|
||||
public MccMcpResult InventoryWindowAction(int inventoryId, int slotId, string actionType) =>
|
||||
MccMcpResult.Ok(new { success = true, inventoryId, slotId, actionType });
|
||||
|
||||
|
|
@ -963,6 +975,9 @@ internal sealed class DeterministicCapabilities : IMccMcpCapabilities
|
|||
}
|
||||
});
|
||||
|
||||
public Task<MccMcpResult> PickupItemsAsync(string itemType, double radius, int maxItems, bool allowUnsafe, int timeoutMs) =>
|
||||
Task.FromResult(PickupItems(itemType, radius, maxItems, allowUnsafe, timeoutMs));
|
||||
|
||||
public MccMcpResult Respawn()
|
||||
{
|
||||
health = 20.0f;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MinecraftClient
|
||||
{
|
||||
|
|
@ -22,6 +23,11 @@ namespace MinecraftClient
|
|||
return Perform(action, TimeSpan.FromMilliseconds(timeout));
|
||||
}
|
||||
|
||||
public static Task<bool> PerformAsync(Action action, int timeout, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return PerformAsync(action, TimeSpan.FromMilliseconds(timeout), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform the specified action with specified timeout
|
||||
/// </summary>
|
||||
|
|
@ -30,14 +36,26 @@ namespace MinecraftClient
|
|||
/// <returns>True if the action finished whithout timing out</returns>
|
||||
public static bool Perform(Action action, TimeSpan timeout)
|
||||
{
|
||||
Thread thread = new(new ThreadStart(action));
|
||||
thread.Start();
|
||||
return PerformAsync(action, timeout).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
bool success = thread.Join(timeout);
|
||||
if (!success)
|
||||
thread.Interrupt();
|
||||
public static async Task<bool> PerformAsync(Action action, TimeSpan timeout, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(action);
|
||||
|
||||
return success;
|
||||
try
|
||||
{
|
||||
await Task.Run(action, cancellationToken).WaitAsync(timeout, cancellationToken);
|
||||
return true;
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace MinecraftClient.Mcp;
|
||||
|
||||
public interface IMccMcpCapabilities
|
||||
|
|
@ -43,7 +45,9 @@ public interface IMccMcpCapabilities
|
|||
MccMcpResult FindNearestEntity(string? typeFilter, string? nameFilter, double radius, bool includePlayers);
|
||||
MccMcpResult CanReachPosition(double x, double y, double z, bool allowUnsafe, int maxOffset, int minOffset, int timeoutMs);
|
||||
MccMcpResult MoveTo(double x, double y, double z, bool allowUnsafe, bool allowDirectTeleport, int maxOffset, int minOffset, int timeoutMs);
|
||||
Task<MccMcpResult> MoveToAsync(double x, double y, double z, bool allowUnsafe, bool allowDirectTeleport, int maxOffset, int minOffset, int timeoutMs);
|
||||
MccMcpResult MoveToPlayer(string playerName, bool allowUnsafe, bool allowDirectTeleport, int maxOffset, int minOffset, int timeoutMs);
|
||||
Task<MccMcpResult> MoveToPlayerAsync(string playerName, bool allowUnsafe, bool allowDirectTeleport, int maxOffset, int minOffset, int timeoutMs);
|
||||
MccMcpResult LookAt(double x, double y, double z);
|
||||
MccMcpResult LookDirection(string direction);
|
||||
MccMcpResult LookAngles(float yaw, float pitch);
|
||||
|
|
@ -51,7 +55,9 @@ public interface IMccMcpCapabilities
|
|||
MccMcpResult GetInventorySnapshot(int inventoryId);
|
||||
MccMcpResult SearchInventories(string query, int maxCount, bool exactMatch, bool includeContainers);
|
||||
MccMcpResult OpenContainerAt(int x, int y, int z, int timeoutMs, bool closeCurrent);
|
||||
Task<MccMcpResult> OpenContainerAtAsync(int x, int y, int z, int timeoutMs, bool closeCurrent);
|
||||
MccMcpResult CloseContainer(int inventoryId, int timeoutMs);
|
||||
Task<MccMcpResult> CloseContainerAsync(int inventoryId, int timeoutMs);
|
||||
MccMcpResult InventoryWindowAction(int inventoryId, int slotId, string actionType);
|
||||
MccMcpResult DropInventoryItem(string itemType, int count, int inventoryId, bool preferStack);
|
||||
MccMcpResult DepositContainerItem(string itemType, int count, int inventoryId, bool preferLargestStack);
|
||||
|
|
@ -62,5 +68,6 @@ public interface IMccMcpCapabilities
|
|||
MccMcpResult FindSigns(string text, bool exactMatch, int radius, int maxCount, bool includeBackText);
|
||||
MccMcpResult ListItemEntities(string? itemType, double radius, int maxCount);
|
||||
MccMcpResult PickupItems(string itemType, double radius, int maxItems, bool allowUnsafe, int timeoutMs);
|
||||
Task<MccMcpResult> PickupItemsAsync(string itemType, double radius, int maxItems, bool allowUnsafe, int timeoutMs);
|
||||
MccMcpResult GetWorldBlockAt(int x, int y, int z);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,8 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities
|
|||
public required double Distance { get; init; }
|
||||
}
|
||||
|
||||
private readonly record struct ContainerOpenState(int InventoryId, Container? Inventory);
|
||||
|
||||
private enum InventoryTransferDirection
|
||||
{
|
||||
Deposit,
|
||||
|
|
@ -1274,6 +1276,11 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities
|
|||
}
|
||||
|
||||
public MccMcpResult MoveTo(double x, double y, double z, bool allowUnsafe, bool allowDirectTeleport, int maxOffset, int minOffset, int timeoutMs)
|
||||
{
|
||||
return MoveToAsync(x, y, z, allowUnsafe, allowDirectTeleport, maxOffset, minOffset, timeoutMs).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<MccMcpResult> MoveToAsync(double x, double y, double z, bool allowUnsafe, bool allowDirectTeleport, int maxOffset, int minOffset, int timeoutMs)
|
||||
{
|
||||
if (!IsCategoryEnabled(t => t.Movement))
|
||||
return MccMcpResult.Fail("capability_disabled");
|
||||
|
|
@ -1302,9 +1309,12 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities
|
|||
|
||||
int verifyWaitMs = GetArrivalWaitMs(timeoutMs);
|
||||
double tolerance = GetArrivalTolerance(maxOffset, minOffset);
|
||||
Location? finalLocation = null;
|
||||
bool arrived = pathFound && WaitForArrival(client, goal, verifyWaitMs, tolerance, out finalLocation);
|
||||
finalLocation ??= client.InvokeOnMainThread(client.GetCurrentLocation);
|
||||
Location finalLocation = client.InvokeOnMainThread(client.GetCurrentLocation);
|
||||
bool arrived = false;
|
||||
if (pathFound)
|
||||
{
|
||||
(arrived, finalLocation) = await WaitForArrivalAsync(client, goal, verifyWaitMs, tolerance);
|
||||
}
|
||||
object resultData = new
|
||||
{
|
||||
pathFound,
|
||||
|
|
@ -1313,9 +1323,9 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities
|
|||
verifyWaitMs,
|
||||
target = ToCoordinate(goal),
|
||||
startLocation = ToCoordinate(startLocation),
|
||||
finalLocation = ToCoordinate(finalLocation.Value),
|
||||
finalDistance = GetDistance(finalLocation.Value, goal),
|
||||
distanceMoved = GetDistance(startLocation, finalLocation.Value),
|
||||
finalLocation = ToCoordinate(finalLocation),
|
||||
finalDistance = GetDistance(finalLocation, goal),
|
||||
distanceMoved = GetDistance(startLocation, finalLocation),
|
||||
allowUnsafe,
|
||||
allowDirectTeleport,
|
||||
maxOffset,
|
||||
|
|
@ -1329,10 +1339,15 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities
|
|||
}
|
||||
|
||||
public MccMcpResult MoveToPlayer(string playerName, bool allowUnsafe, bool allowDirectTeleport, int maxOffset, int minOffset, int timeoutMs)
|
||||
{
|
||||
return MoveToPlayerAsync(playerName, allowUnsafe, allowDirectTeleport, maxOffset, minOffset, timeoutMs).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<MccMcpResult> MoveToPlayerAsync(string playerName, bool allowUnsafe, bool allowDirectTeleport, int maxOffset, int minOffset, int timeoutMs)
|
||||
{
|
||||
if (!IsCategoryEnabled(t => t.Movement))
|
||||
return MccMcpResult.Fail("capability_disabled");
|
||||
return ToMcpResult(game.MoveToPlayer(playerName, allowUnsafe, allowDirectTeleport, maxOffset, minOffset, timeoutMs));
|
||||
return ToMcpResult(await game.MoveToPlayerAsync(playerName, allowUnsafe, allowDirectTeleport, maxOffset, minOffset, timeoutMs));
|
||||
}
|
||||
|
||||
public MccMcpResult LookAt(double x, double y, double z)
|
||||
|
|
@ -1464,6 +1479,11 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities
|
|||
}
|
||||
|
||||
public MccMcpResult OpenContainerAt(int x, int y, int z, int timeoutMs, bool closeCurrent)
|
||||
{
|
||||
return OpenContainerAtAsync(x, y, z, timeoutMs, closeCurrent).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<MccMcpResult> OpenContainerAtAsync(int x, int y, int z, int timeoutMs, bool closeCurrent)
|
||||
{
|
||||
if (!IsCategoryEnabled(t => t.Inventory))
|
||||
return MccMcpResult.Fail("capability_disabled");
|
||||
|
|
@ -1495,10 +1515,15 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities
|
|||
});
|
||||
}
|
||||
|
||||
return OpenContainerCore(client, location, state.block, state.activeContainerId, waitMs, closeCurrent);
|
||||
return await OpenContainerCoreAsync(client, location, state.block, state.activeContainerId, waitMs, closeCurrent);
|
||||
}
|
||||
|
||||
public MccMcpResult CloseContainer(int inventoryId, int timeoutMs)
|
||||
{
|
||||
return CloseContainerAsync(inventoryId, timeoutMs).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<MccMcpResult> CloseContainerAsync(int inventoryId, int timeoutMs)
|
||||
{
|
||||
if (!IsCategoryEnabled(t => t.Inventory))
|
||||
return MccMcpResult.Fail("capability_disabled");
|
||||
|
|
@ -1527,7 +1552,7 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities
|
|||
}
|
||||
|
||||
bool closeAccepted = client.CloseInventory(resolvedInventoryId);
|
||||
bool closed = closeAccepted && WaitForContainerClose(client, resolvedInventoryId, waitMs);
|
||||
bool closed = closeAccepted && await WaitForContainerCloseAsync(client, resolvedInventoryId, waitMs);
|
||||
var resultData = new
|
||||
{
|
||||
success = closeAccepted && closed,
|
||||
|
|
@ -1824,10 +1849,15 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities
|
|||
}
|
||||
|
||||
public MccMcpResult PickupItems(string itemType, double radius, int maxItems, bool allowUnsafe, int timeoutMs)
|
||||
{
|
||||
return PickupItemsAsync(itemType, radius, maxItems, allowUnsafe, timeoutMs).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<MccMcpResult> PickupItemsAsync(string itemType, double radius, int maxItems, bool allowUnsafe, int timeoutMs)
|
||||
{
|
||||
if (!IsCategoryEnabled(t => t.EntityWorld) || !IsCategoryEnabled(t => t.Movement))
|
||||
return MccMcpResult.Fail("capability_disabled");
|
||||
return ToMcpResult(game.PickupItems(itemType, radius, maxItems, allowUnsafe, timeoutMs));
|
||||
return ToMcpResult(await game.PickupItemsAsync(itemType, radius, maxItems, allowUnsafe, timeoutMs));
|
||||
}
|
||||
|
||||
public MccMcpResult GetWorldBlockAt(int x, int y, int z)
|
||||
|
|
@ -1858,7 +1888,7 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities
|
|||
});
|
||||
}
|
||||
|
||||
private static MccMcpResult OpenContainerCore(McClient client, Location location, Block block, int activeContainerId, int waitMs, bool closeCurrent)
|
||||
private static async Task<MccMcpResult> OpenContainerCoreAsync(McClient client, Location location, Block block, int activeContainerId, int waitMs, bool closeCurrent)
|
||||
{
|
||||
if (activeContainerId > 0)
|
||||
{
|
||||
|
|
@ -1876,7 +1906,7 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities
|
|||
}
|
||||
|
||||
bool closeAccepted = client.CloseInventory(activeContainerId);
|
||||
bool closed = closeAccepted && WaitForContainerClose(client, activeContainerId, waitMs);
|
||||
bool closed = closeAccepted && await WaitForContainerCloseAsync(client, activeContainerId, waitMs);
|
||||
if (!closeAccepted || !closed)
|
||||
{
|
||||
return MccMcpResult.Fail("action_incomplete", data: new
|
||||
|
|
@ -1894,7 +1924,11 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities
|
|||
int openedInventoryId = 0;
|
||||
Container? openedInventory = null;
|
||||
bool openAccepted = client.InvokeOnMainThread(() => client.PlaceBlock(location, Direction.Down, Hand.MainHand, lookAtBlock: true));
|
||||
bool opened = openAccepted && WaitForContainerOpen(client, beforeIds, waitMs, out openedInventoryId, out openedInventory);
|
||||
bool opened = openAccepted && await WaitForContainerOpenAsync(client, beforeIds, waitMs, result =>
|
||||
{
|
||||
openedInventoryId = result.InventoryId;
|
||||
openedInventory = result.Inventory;
|
||||
});
|
||||
var resultData = new
|
||||
{
|
||||
success = openAccepted && opened && openedInventory is not null,
|
||||
|
|
@ -2233,6 +2267,31 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities
|
|||
}
|
||||
}
|
||||
|
||||
private static async Task<bool> WaitForContainerOpenAsync(McClient client, ISet<int> beforeIds, int waitMs, Action<ContainerOpenState> onOpened)
|
||||
{
|
||||
DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs);
|
||||
while (true)
|
||||
{
|
||||
(int activeId, Container? activeInventory) state = client.InvokeOnMainThread(() =>
|
||||
{
|
||||
int activeId = GetActiveContainerId(client);
|
||||
Container? activeInventory = activeId > 0 ? client.GetInventory(activeId) : null;
|
||||
return (activeId, activeInventory);
|
||||
});
|
||||
|
||||
if (state.activeId > 0 && (!beforeIds.Contains(state.activeId) || beforeIds.Count == 0) && state.activeInventory is not null)
|
||||
{
|
||||
onOpened(new ContainerOpenState(state.activeId, state.activeInventory));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (DateTime.UtcNow >= deadline)
|
||||
return false;
|
||||
|
||||
await Task.Delay(ArrivalPollIntervalMs);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool WaitForContainerClose(McClient client, int inventoryId, int waitMs)
|
||||
{
|
||||
DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs);
|
||||
|
|
@ -2249,6 +2308,22 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities
|
|||
}
|
||||
}
|
||||
|
||||
private static async Task<bool> WaitForContainerCloseAsync(McClient client, int inventoryId, int waitMs)
|
||||
{
|
||||
DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs);
|
||||
while (true)
|
||||
{
|
||||
bool stillOpen = client.InvokeOnMainThread(() => client.GetInventories().ContainsKey(inventoryId));
|
||||
if (!stillOpen)
|
||||
return true;
|
||||
|
||||
if (DateTime.UtcNow >= deadline)
|
||||
return false;
|
||||
|
||||
await Task.Delay(ArrivalPollIntervalMs);
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetContainerWaitMs(int timeoutMs)
|
||||
{
|
||||
if (timeoutMs <= 0)
|
||||
|
|
@ -2901,6 +2976,24 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities
|
|||
}
|
||||
}
|
||||
|
||||
private static async Task<(bool Arrived, Location FinalLocation)> WaitForArrivalAsync(McClient client, Location goal, int waitMs, double tolerance)
|
||||
{
|
||||
DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs);
|
||||
Location finalLocation = client.InvokeOnMainThread(client.GetCurrentLocation);
|
||||
while (true)
|
||||
{
|
||||
finalLocation = client.InvokeOnMainThread(client.GetCurrentLocation);
|
||||
double distance = GetDistance(finalLocation, goal);
|
||||
if (distance <= tolerance)
|
||||
return (true, finalLocation);
|
||||
|
||||
if (DateTime.UtcNow >= deadline)
|
||||
return (false, finalLocation);
|
||||
|
||||
await Task.Delay(ArrivalPollIntervalMs);
|
||||
}
|
||||
}
|
||||
|
||||
private static double GetDistance(Location from, Location to)
|
||||
{
|
||||
double dx = from.X - to.X;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
namespace MinecraftClient.Mcp;
|
||||
|
|
@ -262,15 +263,15 @@ public sealed class MccMcpToolSet
|
|||
}
|
||||
|
||||
[McpServerTool(Name = "mcc_move_to"), Description("Request movement/pathing to a world coordinate and verify arrival.")]
|
||||
public object MoveTo(double x, double y, double z, bool allowUnsafe = false, bool allowDirectTeleport = false, int maxOffset = 0, int minOffset = 0, int timeoutMs = 0)
|
||||
public async Task<object> MoveTo(double x, double y, double z, bool allowUnsafe = false, bool allowDirectTeleport = false, int maxOffset = 0, int minOffset = 0, int timeoutMs = 0)
|
||||
{
|
||||
return capabilities.MoveTo(x, y, z, allowUnsafe, allowDirectTeleport, maxOffset, minOffset, timeoutMs);
|
||||
return await capabilities.MoveToAsync(x, y, z, allowUnsafe, allowDirectTeleport, maxOffset, minOffset, timeoutMs);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "mcc_move_to_player"), Description("Locate a tracked player entity, request movement/pathing, and verify arrival.")]
|
||||
public object MoveToPlayer(string playerName, bool allowUnsafe = false, bool allowDirectTeleport = false, int maxOffset = 0, int minOffset = 0, int timeoutMs = 0)
|
||||
public async Task<object> MoveToPlayer(string playerName, bool allowUnsafe = false, bool allowDirectTeleport = false, int maxOffset = 0, int minOffset = 0, int timeoutMs = 0)
|
||||
{
|
||||
return capabilities.MoveToPlayer(playerName, allowUnsafe, allowDirectTeleport, maxOffset, minOffset, timeoutMs);
|
||||
return await capabilities.MoveToPlayerAsync(playerName, allowUnsafe, allowDirectTeleport, maxOffset, minOffset, timeoutMs);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "mcc_look_at"), Description("Rotate player view toward world coordinates.")]
|
||||
|
|
@ -310,15 +311,15 @@ public sealed class MccMcpToolSet
|
|||
}
|
||||
|
||||
[McpServerTool(Name = "mcc_container_open_at"), Description("Open an interactable container block at world coordinates and wait for the container inventory to appear.")]
|
||||
public object ContainerOpenAt(int x, int y, int z, int timeoutMs = 0, bool closeCurrent = true)
|
||||
public async Task<object> ContainerOpenAt(int x, int y, int z, int timeoutMs = 0, bool closeCurrent = true)
|
||||
{
|
||||
return capabilities.OpenContainerAt(x, y, z, timeoutMs, closeCurrent);
|
||||
return await capabilities.OpenContainerAtAsync(x, y, z, timeoutMs, closeCurrent);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "mcc_container_close"), Description("Close an open non-player container. Use inventoryId=-1 to close the active container.")]
|
||||
public object ContainerClose([Description("Container inventory ID, or -1 for the active non-player container.")] int inventoryId = -1, int timeoutMs = 0)
|
||||
public async Task<object> ContainerClose([Description("Container inventory ID, or -1 for the active non-player container.")] int inventoryId = -1, int timeoutMs = 0)
|
||||
{
|
||||
return capabilities.CloseContainer(inventoryId, timeoutMs);
|
||||
return await capabilities.CloseContainerAsync(inventoryId, timeoutMs);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "mcc_inventory_window_action"), Description("Perform a window action on an inventory slot.")]
|
||||
|
|
@ -388,9 +389,9 @@ public sealed class MccMcpToolSet
|
|||
}
|
||||
|
||||
[McpServerTool(Name = "mcc_items_pickup"), Description("Move to and pick up nearby dropped items of a given item type.")]
|
||||
public object ItemsPickup(string itemType, double radius = 32, int maxItems = 20, bool allowUnsafe = false, int timeoutMs = 0)
|
||||
public async Task<object> ItemsPickup(string itemType, double radius = 32, int maxItems = 20, bool allowUnsafe = false, int timeoutMs = 0)
|
||||
{
|
||||
return capabilities.PickupItems(itemType, radius, maxItems, allowUnsafe, timeoutMs);
|
||||
return await capabilities.PickupItemsAsync(itemType, radius, maxItems, allowUnsafe, timeoutMs);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "mcc_world_block_at"), Description("Get block information at world coordinates.")]
|
||||
|
|
|
|||
|
|
@ -763,11 +763,15 @@ namespace MinecraftClient
|
|||
ConsoleIO.WriteLine(Translations.mcc_forge);
|
||||
else
|
||||
ConsoleIO.WriteLine(Translations.mcc_retrieve);
|
||||
if (!ProtocolHandler.GetServerInfo(InternalConfig.ServerIP, InternalConfig.ServerPort, ref protocolversion, ref forgeInfo))
|
||||
var serverInfo = await ProtocolHandler.GetServerInfoAsync(InternalConfig.ServerIP, InternalConfig.ServerPort, protocolversion);
|
||||
if (!serverInfo.Success)
|
||||
{
|
||||
HandleFailure(Translations.error_ping, true, ChatBot.DisconnectReason.ConnectionLost);
|
||||
return;
|
||||
}
|
||||
|
||||
protocolversion = serverInfo.ProtocolVersion;
|
||||
forgeInfo = serverInfo.ForgeInfo;
|
||||
}
|
||||
|
||||
if ((Config.Main.General.AccountType == LoginType.microsoft || Config.Main.General.AccountType == LoginType.yggdrasil)
|
||||
|
|
@ -927,6 +931,11 @@ namespace MinecraftClient
|
|||
}
|
||||
|
||||
public static void DoExit(int exitcode = 0)
|
||||
{
|
||||
DoExitAsync(exitcode).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private static Task DoExitAsync(int exitcode = 0)
|
||||
{
|
||||
WriteBackSettings();
|
||||
ConsoleIO.WriteLineFormatted("§a" + string.Format(Translations.config_saving, settingsIniPath));
|
||||
|
|
@ -945,6 +954,7 @@ namespace MinecraftClient
|
|||
if (Config.Main.Advanced.PlayerHeadAsIcon) { ConsoleIcon.RevertToMCCIcon(); }
|
||||
ConsoleIO.Backend?.Shutdown();
|
||||
Environment.Exit(exitcode);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -952,7 +962,7 @@ namespace MinecraftClient
|
|||
/// </summary>
|
||||
public static void Exit(int exitcode = 0)
|
||||
{
|
||||
StartLifecycleTask(Task.Run(() => DoExit(exitcode)));
|
||||
StartLifecycleTask(DoExitAsync(exitcode));
|
||||
}
|
||||
|
||||
private static void StartLifecycleTask(Task lifecycleTask)
|
||||
|
|
|
|||
|
|
@ -93,11 +93,30 @@ namespace MinecraftClient.Protocol
|
|||
/// <returns>TRUE if ping was successful</returns>
|
||||
public static bool GetServerInfo(string serverIP, ushort serverPort, ref int protocolversion,
|
||||
ref ForgeInfo? forgeInfo)
|
||||
{
|
||||
(bool success, int resolvedProtocolVersion, ForgeInfo? resolvedForgeInfo) =
|
||||
GetServerInfoAsync(serverIP, serverPort, protocolversion).GetAwaiter().GetResult();
|
||||
|
||||
if (!success)
|
||||
return false;
|
||||
|
||||
if (protocolversion != 0 && protocolversion != resolvedProtocolVersion)
|
||||
ConsoleIO.WriteLineFormatted("§8" + Translations.error_version_different, acceptnewlines: true);
|
||||
if (protocolversion == 0 && resolvedProtocolVersion <= 1)
|
||||
ConsoleIO.WriteLineFormatted("§8" + Translations.error_no_version_report, acceptnewlines: true);
|
||||
if (protocolversion == 0)
|
||||
protocolversion = resolvedProtocolVersion;
|
||||
|
||||
forgeInfo = resolvedForgeInfo;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static async Task<(bool Success, int ProtocolVersion, ForgeInfo? ForgeInfo)> GetServerInfoAsync(string serverIP, ushort serverPort, int protocolversion)
|
||||
{
|
||||
bool success = false;
|
||||
int protocolversionTmp = 0;
|
||||
ForgeInfo? forgeInfoTmp = null;
|
||||
if (AutoTimeout.Perform(() =>
|
||||
if (await AutoTimeout.PerformAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -120,19 +139,15 @@ namespace MinecraftClient.Protocol
|
|||
? 10
|
||||
: 30)))
|
||||
{
|
||||
if (protocolversion != 0 && protocolversion != protocolversionTmp)
|
||||
ConsoleIO.WriteLineFormatted("§8" + Translations.error_version_different, acceptnewlines: true);
|
||||
if (protocolversion == 0 && protocolversionTmp <= 1)
|
||||
ConsoleIO.WriteLineFormatted("§8" + Translations.error_no_version_report, acceptnewlines: true);
|
||||
if (protocolversion == 0)
|
||||
protocolversion = protocolversionTmp;
|
||||
forgeInfo = forgeInfoTmp;
|
||||
return success;
|
||||
|
||||
return (success, protocolversion, forgeInfoTmp);
|
||||
}
|
||||
else
|
||||
{
|
||||
ConsoleIO.WriteLineFormatted("§8" + Translations.error_connection_timeout, acceptnewlines: true);
|
||||
return false;
|
||||
return (false, protocolversion, forgeInfoTmp);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -704,9 +704,76 @@ public sealed class MccGameApi
|
|||
/// <summary>
|
||||
/// Run <see cref="MoveToPlayer"/> on a worker thread so ChatBot callbacks can poll the result without blocking MCC updates.
|
||||
/// </summary>
|
||||
public Task<MccGameResult<MccMoveToPlayerResult>> MoveToPlayerAsync(string playerName, bool allowUnsafe = false, bool allowDirectTeleport = false, int maxOffset = 0, int minOffset = 0, int timeoutMs = 0)
|
||||
public async Task<MccGameResult<MccMoveToPlayerResult>> MoveToPlayerAsync(string playerName, bool allowUnsafe = false, bool allowDirectTeleport = false, int maxOffset = 0, int minOffset = 0, int timeoutMs = 0)
|
||||
{
|
||||
return Task.Run(() => MoveToPlayer(playerName, allowUnsafe, allowDirectTeleport, maxOffset, minOffset, timeoutMs));
|
||||
if (string.IsNullOrWhiteSpace(playerName))
|
||||
return MccGameResult<MccMoveToPlayerResult>.Fail("invalid_args");
|
||||
|
||||
if (!AreValidPathOffsets(maxOffset, minOffset) || timeoutMs < 0)
|
||||
return MccGameResult<MccMoveToPlayerResult>.Fail("invalid_args");
|
||||
|
||||
McClient? client = clientProvider();
|
||||
if (client is null)
|
||||
return NotConnected<MccMoveToPlayerResult>();
|
||||
|
||||
if (!client.GetTerrainEnabled() || !client.GetEntityHandlingEnabled())
|
||||
return MccGameResult<MccMoveToPlayerResult>.Fail("feature_disabled");
|
||||
|
||||
string nameFilter = playerName.Trim();
|
||||
NearbyPlayerSnapshot? target = client.InvokeOnMainThread(() =>
|
||||
{
|
||||
return BuildTrackedPlayerSnapshots(client, includeSelf: false)
|
||||
.Where(player => PlayerNameMatches(player, nameFilter))
|
||||
.OrderBy(player => player.Distance)
|
||||
.FirstOrDefault();
|
||||
});
|
||||
|
||||
if (target is null)
|
||||
return MccGameResult<MccMoveToPlayerResult>.Fail("invalid_state");
|
||||
|
||||
Location goal = new(target.X, target.Y, target.Z);
|
||||
Location startLocation = client.InvokeOnMainThread(client.GetCurrentLocation);
|
||||
TimeSpan? timeout = timeoutMs > 0 ? TimeSpan.FromMilliseconds(timeoutMs) : null;
|
||||
bool pathFound = client.InvokeOnMainThread(() => client.MoveTo(goal, allowUnsafe, allowDirectTeleport, maxOffset, minOffset, timeout));
|
||||
|
||||
int verifyWaitMs = GetArrivalWaitMs(timeoutMs);
|
||||
double tolerance = GetArrivalTolerance(maxOffset, minOffset);
|
||||
Location finalLocation = client.InvokeOnMainThread(client.GetCurrentLocation);
|
||||
bool arrived = false;
|
||||
|
||||
if (pathFound)
|
||||
{
|
||||
(arrived, finalLocation) = await WaitForArrivalAsync(client, goal, verifyWaitMs, tolerance);
|
||||
}
|
||||
|
||||
MccMoveToPlayerResult resultData = new()
|
||||
{
|
||||
PathFound = pathFound,
|
||||
Arrived = arrived,
|
||||
Tolerance = tolerance,
|
||||
VerifyWaitMs = verifyWaitMs,
|
||||
Target = new MccMoveToPlayerTarget
|
||||
{
|
||||
PlayerName = target.Name,
|
||||
EntityId = target.EntityId,
|
||||
X = MccGameCommon.RoundCoordinate(target.X),
|
||||
Y = MccGameCommon.RoundCoordinate(target.Y),
|
||||
Z = MccGameCommon.RoundCoordinate(target.Z)
|
||||
},
|
||||
StartLocation = MccGameCommon.ToCoordinate(startLocation),
|
||||
FinalLocation = MccGameCommon.ToCoordinate(finalLocation),
|
||||
FinalDistance = MccGameCommon.GetDistance(finalLocation, goal),
|
||||
DistanceMoved = MccGameCommon.GetDistance(startLocation, finalLocation),
|
||||
AllowUnsafe = allowUnsafe,
|
||||
AllowDirectTeleport = allowDirectTeleport,
|
||||
MaxOffset = maxOffset,
|
||||
MinOffset = minOffset,
|
||||
TimeoutMs = timeoutMs
|
||||
};
|
||||
|
||||
return pathFound && arrived
|
||||
? MccGameResult<MccMoveToPlayerResult>.Ok(resultData)
|
||||
: MccGameResult<MccMoveToPlayerResult>.Fail("action_incomplete", data: resultData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1055,9 +1122,94 @@ public sealed class MccGameApi
|
|||
/// <summary>
|
||||
/// Run <see cref="PickupItems"/> on a worker thread so ChatBot callbacks can poll the result without blocking MCC updates.
|
||||
/// </summary>
|
||||
public Task<MccGameResult<MccPickupItemsResult>> PickupItemsAsync(string itemType, double radius = 16, int maxItems = 10, bool allowUnsafe = false, int timeoutMs = 0)
|
||||
public async Task<MccGameResult<MccPickupItemsResult>> PickupItemsAsync(string itemType, double radius = 16, int maxItems = 10, bool allowUnsafe = false, int timeoutMs = 0)
|
||||
{
|
||||
return Task.Run(() => PickupItems(itemType, radius, maxItems, allowUnsafe, timeoutMs));
|
||||
if (string.IsNullOrWhiteSpace(itemType) || radius <= 0 || radius > 1024 || maxItems < 1 || timeoutMs < 0)
|
||||
return MccGameResult<MccPickupItemsResult>.Fail("invalid_args");
|
||||
|
||||
if (!MccGameCommon.TryParseItemType(itemType.Trim(), out ItemType parsedItemType))
|
||||
return MccGameResult<MccPickupItemsResult>.Fail("invalid_args");
|
||||
|
||||
McClient? client = clientProvider();
|
||||
if (client is null)
|
||||
return NotConnected<MccPickupItemsResult>();
|
||||
|
||||
if (!client.GetTerrainEnabled() || !client.GetEntityHandlingEnabled())
|
||||
return MccGameResult<MccPickupItemsResult>.Fail("feature_disabled");
|
||||
|
||||
int limit = Math.Clamp(maxItems, 1, 50);
|
||||
NearbyItemSnapshot[] targets = client.InvokeOnMainThread(() => BuildNearbyItemSnapshots(client, parsedItemType, radius, limit));
|
||||
if (targets.Length == 0)
|
||||
return MccGameResult<MccPickupItemsResult>.Fail("invalid_state");
|
||||
|
||||
bool inventoryEnabled = client.GetInventoryEnabled();
|
||||
int beforeCount = inventoryEnabled ? client.InvokeOnMainThread(() => GetInventoryItemCount(client, parsedItemType)) : 0;
|
||||
int initialCount = beforeCount;
|
||||
int verifyWaitMs = timeoutMs > 0 ? Math.Clamp(timeoutMs, MinArrivalWaitMs, MaxArrivalWaitMs) : 2500;
|
||||
List<MccPickupAttempt> attempts = new(targets.Length);
|
||||
int successfulPickups = 0;
|
||||
|
||||
foreach (NearbyItemSnapshot target in targets)
|
||||
{
|
||||
Location targetLocation = new(target.X, target.Y, target.Z);
|
||||
Location startLocation = client.InvokeOnMainThread(client.GetCurrentLocation);
|
||||
TimeSpan? moveTimeout = timeoutMs > 0 ? TimeSpan.FromMilliseconds(timeoutMs) : null;
|
||||
bool pathFound = client.InvokeOnMainThread(() => client.MoveTo(targetLocation, allowUnsafe, false, 0, 0, moveTimeout));
|
||||
|
||||
Location finalLocation = client.InvokeOnMainThread(client.GetCurrentLocation);
|
||||
bool arrived = false;
|
||||
if (pathFound)
|
||||
{
|
||||
(arrived, finalLocation) = await WaitForArrivalAsync(client, targetLocation, verifyWaitMs, 2.0);
|
||||
}
|
||||
|
||||
bool entityGone = await WaitForEntityRemovalAsync(client, target.EntityId, verifyWaitMs);
|
||||
int afterCount = inventoryEnabled ? client.InvokeOnMainThread(() => GetInventoryItemCount(client, parsedItemType)) : beforeCount;
|
||||
int inventoryDelta = inventoryEnabled ? Math.Max(0, afterCount - beforeCount) : 0;
|
||||
bool pickedUp = entityGone || inventoryDelta > 0;
|
||||
if (pickedUp)
|
||||
successfulPickups++;
|
||||
|
||||
attempts.Add(new MccPickupAttempt
|
||||
{
|
||||
EntityId = target.EntityId,
|
||||
ItemType = target.ItemType.ToString(),
|
||||
TypeLabel = target.TypeLabel,
|
||||
ExpectedCount = target.Count,
|
||||
Target = MccGameCommon.ToCoordinate(target.X, target.Y, target.Z),
|
||||
PathFound = pathFound,
|
||||
Arrived = arrived,
|
||||
EntityGone = entityGone,
|
||||
InventoryDelta = inventoryDelta,
|
||||
StartLocation = MccGameCommon.ToCoordinate(startLocation),
|
||||
FinalLocation = MccGameCommon.ToCoordinate(finalLocation),
|
||||
FinalDistance = MccGameCommon.GetDistance(finalLocation, targetLocation)
|
||||
});
|
||||
|
||||
beforeCount = afterCount;
|
||||
}
|
||||
|
||||
int remainingNearby = client.InvokeOnMainThread(() => BuildNearbyItemSnapshots(client, parsedItemType, radius, 1000).Length);
|
||||
int collectedCount = inventoryEnabled ? Math.Max(0, beforeCount - initialCount) : successfulPickups;
|
||||
MccPickupItemsResult resultData = new()
|
||||
{
|
||||
ItemType = parsedItemType.ToString(),
|
||||
Radius = radius,
|
||||
MaxItems = limit,
|
||||
AllowUnsafe = allowUnsafe,
|
||||
TimeoutMs = verifyWaitMs,
|
||||
Attempted = attempts.Count,
|
||||
SuccessfulPickups = successfulPickups,
|
||||
CollectedCount = collectedCount,
|
||||
InitialInventoryCount = inventoryEnabled ? initialCount : null,
|
||||
FinalInventoryCount = inventoryEnabled ? beforeCount : null,
|
||||
RemainingNearby = remainingNearby,
|
||||
Attempts = attempts.ToArray()
|
||||
};
|
||||
|
||||
return successfulPickups > 0
|
||||
? MccGameResult<MccPickupItemsResult>.Ok(resultData)
|
||||
: MccGameResult<MccPickupItemsResult>.Fail("action_incomplete", data: resultData);
|
||||
}
|
||||
|
||||
private static MccGameResult<T> NotConnected<T>()
|
||||
|
|
@ -1132,6 +1284,24 @@ public sealed class MccGameApi
|
|||
}
|
||||
}
|
||||
|
||||
private static async Task<(bool Arrived, Location FinalLocation)> WaitForArrivalAsync(McClient client, Location goal, int waitMs, double tolerance)
|
||||
{
|
||||
DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs);
|
||||
Location finalLocation = client.InvokeOnMainThread(client.GetCurrentLocation);
|
||||
|
||||
while (true)
|
||||
{
|
||||
finalLocation = client.InvokeOnMainThread(client.GetCurrentLocation);
|
||||
if (MccGameCommon.GetDistance(finalLocation, goal) <= tolerance)
|
||||
return (true, finalLocation);
|
||||
|
||||
if (DateTime.UtcNow >= deadline)
|
||||
return (false, finalLocation);
|
||||
|
||||
await Task.Delay(ArrivalPollIntervalMs);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool WaitForEntityRemoval(McClient client, int entityId, int waitMs)
|
||||
{
|
||||
DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs);
|
||||
|
|
@ -1148,6 +1318,22 @@ public sealed class MccGameApi
|
|||
}
|
||||
}
|
||||
|
||||
private static async Task<bool> WaitForEntityRemovalAsync(McClient client, int entityId, int waitMs)
|
||||
{
|
||||
DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs);
|
||||
while (true)
|
||||
{
|
||||
bool exists = client.InvokeOnMainThread(() => client.GetEntities().ContainsKey(entityId));
|
||||
if (!exists)
|
||||
return true;
|
||||
|
||||
if (DateTime.UtcNow >= deadline)
|
||||
return false;
|
||||
|
||||
await Task.Delay(ArrivalPollIntervalMs);
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetInventoryItemCount(McClient client, ItemType itemType)
|
||||
{
|
||||
Container? inventory = client.GetInventory(0);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue