mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
Refactored to share the new utilities between the MCP and the Chat Bot API
This commit is contained in:
parent
a88ca3cb71
commit
dfef24ad10
12 changed files with 2225 additions and 1027 deletions
|
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
1310
MinecraftClient/Scripting/MccGameApi.cs
Normal file
1310
MinecraftClient/Scripting/MccGameApi.cs
Normal file
File diff suppressed because it is too large
Load diff
216
MinecraftClient/Scripting/MccGameCommon.cs
Normal file
216
MinecraftClient/Scripting/MccGameCommon.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
319
MinecraftClient/Scripting/MccGameModels.cs
Normal file
319
MinecraftClient/Scripting/MccGameModels.cs
Normal 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; }
|
||||
}
|
||||
63
MinecraftClient/Scripting/MccGameResult.cs
Normal file
63
MinecraftClient/Scripting/MccGameResult.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
205
MinecraftClient/Scripting/MccObservedState.cs
Normal file
205
MinecraftClient/Scripting/MccObservedState.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue