Refactored to share the new utilities between the MCP and the Chat Bot API

This commit is contained in:
Anon 2026-04-03 19:06:43 +02:00
parent a88ca3cb71
commit dfef24ad10
12 changed files with 2225 additions and 1027 deletions

View file

@ -87,7 +87,7 @@ namespace MinecraftClient.ChatBots
public override bool OnDisconnect(DisconnectReason reason, string message)
{
MccMcpRecentEventStore.Add("disconnect", new
MccObservedStateStore.AddRecentEvent("disconnect", new
{
reason = reason.ToString(),
message
@ -128,7 +128,7 @@ namespace MinecraftClient.ChatBots
message = parsedMessage;
}
MccMcpChatHistoryStore.Add(new MccMcpChatHistoryEntry
MccObservedStateStore.AddChatHistoryEntry(new MccChatHistoryEntry
{
TimestampUtc = DateTimeOffset.UtcNow,
Kind = kind,
@ -141,34 +141,34 @@ namespace MinecraftClient.ChatBots
public override void OnTimeUpdate(long WorldAge, long TimeOfDay)
{
MccMcpRuntimeStateStore.SetTime(WorldAge, TimeOfDay);
MccObservedStateStore.SetTime(WorldAge, TimeOfDay);
}
public override void OnRainLevelChange(float level)
{
MccMcpRuntimeStateStore.SetRainLevel(level);
MccMcpRecentEventStore.Add("weather_rain", new { level });
MccObservedStateStore.SetRainLevel(level);
MccObservedStateStore.AddRecentEvent("weather_rain", new { level });
}
public override void OnThunderLevelChange(float level)
{
MccMcpRuntimeStateStore.SetThunderLevel(level);
MccMcpRecentEventStore.Add("weather_thunder", new { level });
MccObservedStateStore.SetThunderLevel(level);
MccObservedStateStore.AddRecentEvent("weather_thunder", new { level });
}
public override void OnDeath()
{
MccMcpRecentEventStore.Add("death");
MccObservedStateStore.AddRecentEvent("death");
}
public override void OnRespawn()
{
MccMcpRecentEventStore.Add("respawn");
MccObservedStateStore.AddRecentEvent("respawn");
}
public override void OnPlayerJoin(Guid uuid, string name)
{
MccMcpRecentEventStore.Add("player_join", new
MccObservedStateStore.AddRecentEvent("player_join", new
{
uuid,
name
@ -177,7 +177,7 @@ namespace MinecraftClient.ChatBots
public override void OnPlayerLeave(Guid uuid, string? name)
{
MccMcpRecentEventStore.Add("player_leave", new
MccObservedStateStore.AddRecentEvent("player_leave", new
{
uuid,
name
@ -186,19 +186,19 @@ namespace MinecraftClient.ChatBots
public override void OnInventoryOpen(int inventoryId)
{
MccMcpRecentEventStore.Add("inventory_open", new { inventoryId });
MccObservedStateStore.AddRecentEvent("inventory_open", new { inventoryId });
}
public override void OnInventoryClose(int inventoryId)
{
MccMcpRecentEventStore.Add("inventory_close", new { inventoryId });
MccObservedStateStore.AddRecentEvent("inventory_close", new { inventoryId });
}
public override void OnTitle(int action, string titletext, string subtitletext, string actionbartext, int fadein, int stay, int fadeout, string json)
{
if (action == 2)
{
MccMcpRecentEventStore.Add("actionbar", new
MccObservedStateStore.AddRecentEvent("actionbar", new
{
action,
text = actionbartext,
@ -212,7 +212,7 @@ namespace MinecraftClient.ChatBots
if (action is 0 or 1)
{
MccMcpRecentEventStore.Add("title", new
MccObservedStateStore.AddRecentEvent("title", new
{
action,
titleText = titletext,
@ -227,7 +227,7 @@ namespace MinecraftClient.ChatBots
public override void OnBlockBreakAnimation(Entity entity, Location location, byte stage)
{
MccMcpRecentEventStore.Add("block_break_animation", new
MccObservedStateStore.AddRecentEvent("block_break_animation", new
{
entityId = entity.ID,
entityType = entity.Type.ToString(),
@ -243,7 +243,7 @@ namespace MinecraftClient.ChatBots
public override void OnEntityAnimation(Entity entity, byte animation)
{
MccMcpRecentEventStore.Add("entity_animation", new
MccObservedStateStore.AddRecentEvent("entity_animation", new
{
entityId = entity.ID,
entityType = entity.Type.ToString(),
@ -266,9 +266,7 @@ namespace MinecraftClient.ChatBots
private static void ClearStores()
{
MccMcpChatHistoryStore.Clear();
MccMcpRuntimeStateStore.Clear();
MccMcpRecentEventStore.Clear();
MccObservedStateStore.ClearAll();
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MinecraftClient.Scripting;
namespace MinecraftClient.Mcp;
@ -16,34 +16,36 @@ public sealed class MccMcpChatHistoryEntry
public static class MccMcpChatHistoryStore
{
private static readonly object historyLock = new();
private static readonly List<MccMcpChatHistoryEntry> history = new();
private const int MaxEntries = 500;
public static void Add(MccMcpChatHistoryEntry entry)
{
lock (historyLock)
MccObservedStateStore.AddChatHistoryEntry(new MccChatHistoryEntry
{
history.Add(entry);
if (history.Count > MaxEntries)
history.RemoveRange(0, history.Count - MaxEntries);
}
TimestampUtc = entry.TimestampUtc,
Kind = entry.Kind,
Text = entry.Text,
Sender = entry.Sender,
Message = entry.Message,
Json = entry.Json
});
}
public static MccMcpChatHistoryEntry[] GetLatest(int maxCount)
{
int count = Math.Clamp(maxCount, 1, MaxEntries);
lock (historyLock)
{
return history.TakeLast(count).ToArray();
}
return MccObservedStateStore.GetLatestChatHistory(maxCount)
.Select(entry => new MccMcpChatHistoryEntry
{
TimestampUtc = entry.TimestampUtc,
Kind = entry.Kind,
Text = entry.Text,
Sender = entry.Sender,
Message = entry.Message,
Json = entry.Json
})
.ToArray();
}
public static void Clear()
{
lock (historyLock)
{
history.Clear();
}
MccObservedStateStore.ClearChatHistory();
}
}

View file

@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MinecraftClient.Scripting;
namespace MinecraftClient.Mcp;
@ -14,62 +14,31 @@ public sealed class MccMcpRecentEventEntry
public static class MccMcpRecentEventStore
{
private static readonly object historyLock = new();
private static readonly List<MccMcpRecentEventEntry> history = new();
private const int MaxEntries = 500;
private static long nextId = 1;
public static long Add(string type, object? data = null)
{
ArgumentException.ThrowIfNullOrEmpty(type);
lock (historyLock)
{
long id = nextId++;
history.Add(new MccMcpRecentEventEntry
{
Id = id,
TimestampUtc = DateTimeOffset.UtcNow,
Type = type,
Data = data
});
if (history.Count > MaxEntries)
history.RemoveRange(0, history.Count - MaxEntries);
return id;
}
return MccObservedStateStore.AddRecentEvent(type, data);
}
public static long GetLatestId()
{
lock (historyLock)
{
return history.Count > 0 ? history[^1].Id : 0;
}
return MccObservedStateStore.GetLatestRecentEventId();
}
public static MccMcpRecentEventEntry[] GetAfter(long afterId, int maxCount, string? typeFilter = null)
{
int count = Math.Clamp(maxCount, 1, MaxEntries);
string? normalizedFilter = string.IsNullOrWhiteSpace(typeFilter) ? null : typeFilter.Trim();
lock (historyLock)
{
return history
.Where(entry => entry.Id > afterId)
.Where(entry => normalizedFilter is null
|| entry.Type.Contains(normalizedFilter, StringComparison.OrdinalIgnoreCase))
.Take(count)
.ToArray();
}
return MccObservedStateStore.GetRecentEventsAfter(afterId, maxCount, typeFilter)
.Select(entry => new MccMcpRecentEventEntry
{
Id = entry.Id,
TimestampUtc = entry.TimestampUtc,
Type = entry.Type,
Data = entry.Data
})
.ToArray();
}
public static void Clear()
{
lock (historyLock)
{
history.Clear();
}
MccObservedStateStore.ClearRecentEvents();
}
}

View file

@ -1,4 +1,5 @@
using System;
using MinecraftClient.Scripting;
namespace MinecraftClient.Mcp;
@ -12,59 +13,35 @@ public sealed class MccMcpRuntimeStateSnapshot
public static class MccMcpRuntimeStateStore
{
private static readonly object stateLock = new();
private static long? worldAge;
private static long? timeOfDay;
private static float? rainLevel;
private static float? thunderLevel;
public static void SetTime(long newWorldAge, long newTimeOfDay)
{
lock (stateLock)
{
worldAge = newWorldAge;
timeOfDay = newTimeOfDay;
}
MccObservedStateStore.SetTime(newWorldAge, newTimeOfDay);
}
public static void SetRainLevel(float level)
{
lock (stateLock)
{
rainLevel = level;
}
MccObservedStateStore.SetRainLevel(level);
}
public static void SetThunderLevel(float level)
{
lock (stateLock)
{
thunderLevel = level;
}
MccObservedStateStore.SetThunderLevel(level);
}
public static MccMcpRuntimeStateSnapshot GetSnapshot()
{
lock (stateLock)
MccRuntimeStateSnapshot snapshot = MccObservedStateStore.GetRuntimeStateSnapshot();
return new MccMcpRuntimeStateSnapshot
{
return new MccMcpRuntimeStateSnapshot
{
WorldAge = worldAge,
TimeOfDay = timeOfDay,
RainLevel = rainLevel,
ThunderLevel = thunderLevel
};
}
WorldAge = snapshot.WorldAge,
TimeOfDay = snapshot.TimeOfDay,
RainLevel = snapshot.RainLevel,
ThunderLevel = snapshot.ThunderLevel
};
}
public static void Clear()
{
lock (stateLock)
{
worldAge = null;
timeOfDay = null;
rainLevel = null;
thunderLevel = null;
}
MccObservedStateStore.ClearRuntimeState();
}
}

View file

@ -215,6 +215,11 @@ namespace MinecraftClient.Scripting
this.localVars = localVars;
}
/// <summary>
/// Access the shared MCC gameplay API used by bots and the embedded MCP server.
/// </summary>
new public MccGameApi Game => base.Game;
/* == Wrappers for ChatBot API with public visibility and call limit to one per tick for safety == */
/// <summary>

View file

@ -48,6 +48,7 @@ namespace MinecraftClient.Scripting
private readonly List<string> registeredChatBotCommands = new();
private readonly Lock delayTasksLock = new();
private readonly List<TaskWithDelay> delayedTasks = new();
private MccGameApi? _game;
protected McClient Handler
{
get
@ -60,6 +61,11 @@ namespace MinecraftClient.Scripting
}
}
/// <summary>
/// Shared gameplay and observed-state API used by MCP and available to built-in bots and scripts.
/// </summary>
protected MccGameApi Game => _game ??= new MccGameApi(() => Handler);
/// <summary>
/// Will be called every client tick (~50ms at 20 TPS).
/// </summary>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,216 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using MinecraftClient.Inventory;
using MinecraftClient.Mapping;
namespace MinecraftClient.Scripting;
/// <summary>
/// Represents a world coordinate rounded for tool and script consumption.
/// </summary>
public sealed class MccCoordinate
{
public required double X { get; init; }
public required double Y { get; init; }
public required double Z { get; init; }
}
/// <summary>
/// Represents a block state snapshot.
/// </summary>
public sealed class MccBlockStateSnapshot
{
public required string Material { get; init; }
public required string TypeLabel { get; init; }
public required int BlockId { get; init; }
public required int BlockMeta { get; init; }
}
/// <summary>
/// Represents a simple item stack snapshot.
/// </summary>
public sealed class MccItemStackSnapshot
{
public required string Type { get; init; }
public required int Count { get; init; }
}
/// <summary>
/// Shared normalization and formatting helpers for gameplay operations.
/// </summary>
public static class MccGameCommon
{
private const int CoordinateRoundingPrecision = 2;
public static bool TryParseItemType(string rawItemType, out ItemType itemType)
{
if (Enum.TryParse(rawItemType, true, out itemType) && itemType is not (ItemType.Unknown or ItemType.Null))
return true;
string normalized = NormalizeToken(rawItemType);
if (normalized.Length == 0)
{
itemType = ItemType.Unknown;
return false;
}
foreach (ItemType candidate in Enum.GetValues<ItemType>())
{
if (candidate is ItemType.Unknown or ItemType.Null)
continue;
if (NormalizeToken(candidate.ToString()) == normalized)
{
itemType = candidate;
return true;
}
}
itemType = ItemType.Unknown;
return false;
}
public static string NormalizeToken(string value)
{
if (string.IsNullOrWhiteSpace(value))
return string.Empty;
char[] buffer = value
.Where(char.IsLetterOrDigit)
.Select(char.ToLowerInvariant)
.ToArray();
return new string(buffer);
}
public static bool TextEqualsFilter(string text, string filter)
{
return text.Equals(filter, StringComparison.OrdinalIgnoreCase)
|| NormalizeToken(text) == NormalizeToken(filter);
}
public static bool TextMatchesFilter(string text, string filter)
{
if (text.Contains(filter, StringComparison.OrdinalIgnoreCase))
return true;
string normalizedFilter = NormalizeToken(filter);
if (normalizedFilter.Length == 0)
return false;
return NormalizeToken(text).Contains(normalizedFilter, StringComparison.Ordinal);
}
public static double GetDistance(Location from, Location to)
{
double dx = from.X - to.X;
double dy = from.Y - to.Y;
double dz = from.Z - to.Z;
return Math.Sqrt(dx * dx + dy * dy + dz * dz);
}
public static double RoundCoordinate(double value)
{
return Math.Round(value, CoordinateRoundingPrecision, MidpointRounding.AwayFromZero);
}
public static MccCoordinate ToCoordinate(Location location)
{
return ToCoordinate(location.X, location.Y, location.Z);
}
public static MccCoordinate ToCoordinate(double x, double y, double z)
{
return new MccCoordinate
{
X = RoundCoordinate(x),
Y = RoundCoordinate(y),
Z = RoundCoordinate(z)
};
}
public static Location ToBlockLocation(double x, double y, double z)
{
return new Location(Math.Floor(x), Math.Floor(y), Math.Floor(z));
}
public static MccBlockStateSnapshot ToBlockState(Block block)
{
return new MccBlockStateSnapshot
{
Material = block.Type.ToString(),
TypeLabel = block.GetTypeString(),
BlockId = block.BlockId,
BlockMeta = block.BlockMeta
};
}
public static object? DescribeMetadataValue(object? value)
{
return value switch
{
null => null,
string s => s,
bool b => b,
byte b => b,
sbyte b => b,
short s => s,
ushort s => s,
int i => i,
uint i => i,
long l => l,
ulong l => l,
float f => f,
double d => d,
decimal d => d,
Enum e => e.ToString(),
Location location => ToCoordinate(location),
Item item => ToItemStack(item),
byte[] data => new { bytes = data.Length },
_ => value.ToString()
};
}
public static MccItemStackSnapshot ToItemStack(Item item)
{
return new MccItemStackSnapshot
{
Type = item.Type.ToString(),
Count = item.Count
};
}
public static bool TryParseBlockQuery(string? query, out int? blockId, out int? blockMeta)
{
blockId = null;
blockMeta = null;
if (string.IsNullOrWhiteSpace(query))
return false;
string trimmed = query.Trim();
int separator = trimmed.IndexOf(':');
if (separator >= 0)
{
string idPart = trimmed[..separator].Trim();
string metaPart = trimmed[(separator + 1)..].Trim();
if (int.TryParse(idPart, NumberStyles.Integer, CultureInfo.InvariantCulture, out int parsedId))
{
blockId = parsedId;
if (int.TryParse(metaPart, NumberStyles.Integer, CultureInfo.InvariantCulture, out int parsedMeta))
blockMeta = parsedMeta;
return true;
}
return false;
}
if (int.TryParse(trimmed, NumberStyles.Integer, CultureInfo.InvariantCulture, out int blockStateId))
{
blockId = blockStateId;
return true;
}
return false;
}
}

View file

@ -0,0 +1,319 @@
using System;
using System.Collections.Generic;
namespace MinecraftClient.Scripting;
public sealed class MccRecentEventsResult
{
public required long AfterId { get; init; }
public required long LatestId { get; init; }
public required int Count { get; init; }
public required MccRecentEventEntry[] Events { get; init; }
}
public sealed class MccChatHistoryResult
{
public required int Count { get; init; }
public required MccChatHistoryEntry[] Entries { get; init; }
}
public sealed class MccPathPreviewResult
{
public required bool PathFound { get; init; }
public required bool ExactReachable { get; init; }
public required MccCoordinate Target { get; init; }
public required MccCoordinate StartLocation { get; init; }
public MccCoordinate? FinalWaypoint { get; init; }
public double? FinalDistance { get; init; }
public required int WaypointCount { get; init; }
public required bool Truncated { get; init; }
public required MccCoordinate[] Waypoints { get; init; }
public required bool AllowUnsafe { get; init; }
public required int MaxOffset { get; init; }
public required int MinOffset { get; init; }
public required int TimeoutMs { get; init; }
}
public sealed class MccReachabilityResult
{
public required bool Reachable { get; init; }
public required bool ExactReachable { get; init; }
public required MccCoordinate Target { get; init; }
public required MccCoordinate StartLocation { get; init; }
public MccCoordinate? FinalWaypoint { get; init; }
public double? FinalDistance { get; init; }
public required int WaypointCount { get; init; }
public required bool AllowUnsafe { get; init; }
public required int MaxOffset { get; init; }
public required int MinOffset { get; init; }
public required int TimeoutMs { get; init; }
}
public sealed class MccPlayersDetailedEntry
{
public string? Name { get; init; }
public required Guid Uuid { get; init; }
public required int Ping { get; init; }
public required int Gamemode { get; init; }
public required bool Listed { get; init; }
public string? DisplayName { get; init; }
public int? EntityId { get; init; }
public double? X { get; init; }
public double? Y { get; init; }
public double? Z { get; init; }
}
public sealed class MccPlayersDetailedResult
{
public required bool IncludeSelf { get; init; }
public required bool IncludeCoordinates { get; init; }
public required int Count { get; init; }
public required MccPlayersDetailedEntry[] Players { get; init; }
}
public sealed class MccNearbyPlayerEntry
{
public required int EntityId { get; init; }
public required Guid Uuid { get; init; }
public string? Name { get; init; }
public string? CustomName { get; init; }
public required double X { get; init; }
public required double Y { get; init; }
public required double Z { get; init; }
public required double Distance { get; init; }
public required int Latency { get; init; }
}
public sealed class MccPlayerNearbyResult
{
public required double Radius { get; init; }
public string? PlayerName { get; init; }
public required bool IncludeSelf { get; init; }
public required bool AnyNearby { get; init; }
public required int Count { get; init; }
public required MccNearbyPlayerEntry[] Players { get; init; }
}
public sealed class MccLocatedPlayerResult
{
public required string PlayerName { get; init; }
public string? MatchedName { get; init; }
public required int EntityId { get; init; }
public required Guid Uuid { get; init; }
public required double X { get; init; }
public required double Y { get; init; }
public required double Z { get; init; }
public required double Distance { get; init; }
}
public sealed class MccEntitySummary
{
public required int Id { get; init; }
public required string Type { get; init; }
public required string TypeLabel { get; init; }
public required Guid Uuid { get; init; }
public string? Name { get; init; }
public string? CustomName { get; init; }
public required double X { get; init; }
public required double Y { get; init; }
public required double Z { get; init; }
public double? Distance { get; init; }
public float? Health { get; init; }
public string? Pose { get; init; }
public int? Latency { get; init; }
}
public sealed class MccQueryEntitiesResult
{
public required int Count { get; init; }
public required MccEntitySummary[] Entities { get; init; }
}
public sealed class MccListEntitiesResult
{
public required int TotalTracked { get; init; }
public required int Count { get; init; }
public required MccEntitySummary[] Entities { get; init; }
}
public sealed class MccEntityEquipmentEntry
{
public required int Slot { get; init; }
public required string Type { get; init; }
public required int Count { get; init; }
}
public sealed class MccEffectSnapshot
{
public required string Id { get; init; }
public string? Name { get; init; }
public required int Amplifier { get; init; }
public required int RemainingSeconds { get; init; }
public required bool IsInfinite { get; init; }
}
public sealed class MccEntityInfoResult
{
public required int Id { get; init; }
public required string Type { get; init; }
public required string TypeLabel { get; init; }
public required Guid Uuid { get; init; }
public string? Name { get; init; }
public string? CustomName { get; init; }
public required bool CustomNameVisible { get; init; }
public required double X { get; init; }
public required double Y { get; init; }
public required double Z { get; init; }
public required float Yaw { get; init; }
public required float Pitch { get; init; }
public required float Health { get; init; }
public string? Pose { get; init; }
public int? Latency { get; init; }
public int? ObjectData { get; init; }
public Dictionary<string, object?>? Metadata { get; init; }
public MccEntityEquipmentEntry[]? Equipment { get; init; }
public MccEffectSnapshot[]? ActiveEffects { get; init; }
}
public sealed class MccMoveToPlayerTarget
{
public string? PlayerName { get; init; }
public required int EntityId { get; init; }
public required double X { get; init; }
public required double Y { get; init; }
public required double Z { get; init; }
}
public sealed class MccMoveToPlayerResult
{
public required bool PathFound { get; init; }
public required bool Arrived { get; init; }
public required double Tolerance { get; init; }
public required int VerifyWaitMs { get; init; }
public required MccMoveToPlayerTarget Target { get; init; }
public required MccCoordinate StartLocation { get; init; }
public required MccCoordinate FinalLocation { get; init; }
public required double FinalDistance { get; init; }
public required double DistanceMoved { get; init; }
public required bool AllowUnsafe { get; init; }
public required bool AllowDirectTeleport { get; init; }
public required int MaxOffset { get; init; }
public required int MinOffset { get; init; }
public required int TimeoutMs { get; init; }
}
public sealed class MccHotbarSelectionResult
{
public required bool Success { get; init; }
public required string ItemType { get; init; }
public required int InventorySlot { get; init; }
public required int SelectedSlot { get; init; }
public required int Count { get; init; }
}
public sealed class MccInventorySnapshotSlot
{
public required int Slot { get; init; }
public required string Type { get; init; }
public required int Count { get; init; }
}
public sealed class MccInventorySnapshotResult
{
public required int Id { get; init; }
public required string Type { get; init; }
public required string Title { get; init; }
public required int SlotCount { get; init; }
public required MccInventorySnapshotSlot[] Slots { get; init; }
public MccItemStackSnapshot? Cursor { get; init; }
}
public sealed class MccInventorySearchMatch
{
public required int InventoryId { get; init; }
public required string InventoryType { get; init; }
public required string InventoryTitle { get; init; }
public required int Slot { get; init; }
public required string ItemType { get; init; }
public required string TypeLabel { get; init; }
public required int Count { get; init; }
public required bool IsPlayerInventory { get; init; }
public int? HotbarSlot { get; init; }
}
public sealed class MccInventorySearchResult
{
public required string Query { get; init; }
public required bool ExactMatch { get; init; }
public required bool IncludeContainers { get; init; }
public required int Count { get; init; }
public required MccInventorySearchMatch[] Matches { get; init; }
}
public sealed class MccInventoryListEntry
{
public required int Id { get; init; }
public required string Type { get; init; }
public required string Title { get; init; }
public required int SlotCount { get; init; }
public required int NonEmptySlots { get; init; }
public required bool Active { get; init; }
}
public sealed class MccInventoryListResult
{
public required int Count { get; init; }
public required MccInventoryListEntry[] Inventories { get; init; }
}
public sealed class MccItemEntityEntry
{
public required int EntityId { get; init; }
public required string ItemType { get; init; }
public required string TypeLabel { get; init; }
public required int Count { get; init; }
public required double X { get; init; }
public required double Y { get; init; }
public required double Z { get; init; }
public required double Distance { get; init; }
}
public sealed class MccItemEntitiesResult
{
public string? ItemType { get; init; }
public required double Radius { get; init; }
public required int Count { get; init; }
public required MccItemEntityEntry[] Items { get; init; }
}
public sealed class MccPickupAttempt
{
public required int EntityId { get; init; }
public required string ItemType { get; init; }
public required string TypeLabel { get; init; }
public required int ExpectedCount { get; init; }
public required MccCoordinate Target { get; init; }
public required bool PathFound { get; init; }
public required bool Arrived { get; init; }
public required bool EntityGone { get; init; }
public required int InventoryDelta { get; init; }
public required MccCoordinate StartLocation { get; init; }
public required MccCoordinate FinalLocation { get; init; }
public required double FinalDistance { get; init; }
}
public sealed class MccPickupItemsResult
{
public required string ItemType { get; init; }
public required double Radius { get; init; }
public required int MaxItems { get; init; }
public required bool AllowUnsafe { get; init; }
public required int TimeoutMs { get; init; }
public required int Attempted { get; init; }
public required int SuccessfulPickups { get; init; }
public required int CollectedCount { get; init; }
public int? InitialInventoryCount { get; init; }
public int? FinalInventoryCount { get; init; }
public required int RemainingNearby { get; init; }
public required MccPickupAttempt[] Attempts { get; init; }
}

View file

@ -0,0 +1,63 @@
using System;
namespace MinecraftClient.Scripting;
/// <summary>
/// Represents the outcome of a shared MCC game operation.
/// </summary>
public class MccGameResult
{
public bool Success { get; init; }
public string? ErrorCode { get; init; }
public string? Message { get; init; }
public static MccGameResult Ok(string? message = null)
{
return new MccGameResult
{
Success = true,
Message = message
};
}
public static MccGameResult Fail(string errorCode, string? message = null)
{
ArgumentException.ThrowIfNullOrEmpty(errorCode);
return new MccGameResult
{
Success = false,
ErrorCode = errorCode,
Message = message
};
}
}
/// <summary>
/// Represents the outcome of a shared MCC game operation with typed payload data.
/// </summary>
public sealed class MccGameResult<T> : MccGameResult
{
public T? Data { get; init; }
public static MccGameResult<T> Ok(T? data, string? message = null)
{
return new MccGameResult<T>
{
Success = true,
Data = data,
Message = message
};
}
public static MccGameResult<T> Fail(string errorCode, string? message = null, T? data = default)
{
ArgumentException.ThrowIfNullOrEmpty(errorCode);
return new MccGameResult<T>
{
Success = false,
ErrorCode = errorCode,
Message = message,
Data = data
};
}
}

View file

@ -0,0 +1,205 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace MinecraftClient.Scripting;
/// <summary>
/// Represents a recent high-signal runtime event observed by MCC.
/// </summary>
public sealed class MccRecentEventEntry
{
public required long Id { get; init; }
public required DateTimeOffset TimestampUtc { get; init; }
public required string Type { get; init; }
public object? Data { get; init; }
}
/// <summary>
/// Represents a recent chat or system line observed by MCC.
/// </summary>
public sealed class MccChatHistoryEntry
{
public required DateTimeOffset TimestampUtc { get; init; }
public required string Kind { get; init; }
public required string Text { get; init; }
public string? Sender { get; init; }
public string? Message { get; init; }
public string? Json { get; init; }
}
/// <summary>
/// Represents the latest observed world time and weather values.
/// </summary>
public sealed class MccRuntimeStateSnapshot
{
public long? WorldAge { get; init; }
public long? TimeOfDay { get; init; }
public float? RainLevel { get; init; }
public float? ThunderLevel { get; init; }
}
/// <summary>
/// Shared observed-state store populated from ChatBot callbacks and consumed by MCP and bots/scripts.
/// </summary>
public static class MccObservedStateStore
{
private const int MaxEntries = 500;
private static readonly Lock s_recentEventsLock = new();
private static readonly Lock s_chatHistoryLock = new();
private static readonly Lock s_runtimeStateLock = new();
private static readonly List<MccRecentEventEntry> s_recentEvents = [];
private static readonly List<MccChatHistoryEntry> s_chatHistory = [];
private static long s_nextRecentEventId = 1;
private static long? s_worldAge;
private static long? s_timeOfDay;
private static float? s_rainLevel;
private static float? s_thunderLevel;
public static long AddRecentEvent(string type, object? data = null)
{
ArgumentException.ThrowIfNullOrEmpty(type);
lock (s_recentEventsLock)
{
long id = s_nextRecentEventId++;
s_recentEvents.Add(new MccRecentEventEntry
{
Id = id,
TimestampUtc = DateTimeOffset.UtcNow,
Type = type,
Data = data
});
TrimToMaxEntries(s_recentEvents);
return id;
}
}
public static long GetLatestRecentEventId()
{
lock (s_recentEventsLock)
{
return s_recentEvents.Count > 0 ? s_recentEvents[^1].Id : 0;
}
}
public static MccRecentEventEntry[] GetRecentEventsAfter(long afterId, int maxCount, string? typeFilter = null)
{
int count = Math.Clamp(maxCount, 1, MaxEntries);
string? normalizedFilter = string.IsNullOrWhiteSpace(typeFilter) ? null : typeFilter.Trim();
lock (s_recentEventsLock)
{
return s_recentEvents
.Where(entry => entry.Id > afterId)
.Where(entry => normalizedFilter is null
|| entry.Type.Contains(normalizedFilter, StringComparison.OrdinalIgnoreCase))
.Take(count)
.ToArray();
}
}
public static void AddChatHistoryEntry(MccChatHistoryEntry entry)
{
ArgumentNullException.ThrowIfNull(entry);
lock (s_chatHistoryLock)
{
s_chatHistory.Add(entry);
TrimToMaxEntries(s_chatHistory);
}
}
public static MccChatHistoryEntry[] GetLatestChatHistory(int maxCount)
{
int count = Math.Clamp(maxCount, 1, MaxEntries);
lock (s_chatHistoryLock)
{
return s_chatHistory.TakeLast(count).ToArray();
}
}
public static void SetTime(long worldAge, long timeOfDay)
{
lock (s_runtimeStateLock)
{
s_worldAge = worldAge;
s_timeOfDay = timeOfDay;
}
}
public static void SetRainLevel(float level)
{
lock (s_runtimeStateLock)
{
s_rainLevel = level;
}
}
public static void SetThunderLevel(float level)
{
lock (s_runtimeStateLock)
{
s_thunderLevel = level;
}
}
public static MccRuntimeStateSnapshot GetRuntimeStateSnapshot()
{
lock (s_runtimeStateLock)
{
return new MccRuntimeStateSnapshot
{
WorldAge = s_worldAge,
TimeOfDay = s_timeOfDay,
RainLevel = s_rainLevel,
ThunderLevel = s_thunderLevel
};
}
}
public static void ClearRecentEvents()
{
lock (s_recentEventsLock)
{
s_recentEvents.Clear();
}
}
public static void ClearChatHistory()
{
lock (s_chatHistoryLock)
{
s_chatHistory.Clear();
}
}
public static void ClearRuntimeState()
{
lock (s_runtimeStateLock)
{
s_worldAge = null;
s_timeOfDay = null;
s_rainLevel = null;
s_thunderLevel = null;
}
}
public static void ClearAll()
{
ClearChatHistory();
ClearRuntimeState();
ClearRecentEvents();
}
private static void TrimToMaxEntries<T>(List<T> entries)
{
if (entries.Count > MaxEntries)
entries.RemoveRange(0, entries.Count - MaxEntries);
}
}