mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
Added bot creation skill
This commit is contained in:
parent
918f1a560d
commit
a1c1dbc182
6 changed files with 1030 additions and 7 deletions
107
.skills/mcc-chatbot-authoring/SKILL.md
Normal file
107
.skills/mcc-chatbot-authoring/SKILL.md
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
---
|
||||
name: mcc-chatbot-authoring
|
||||
description: Create, modify, repair, and wire Minecraft Console Client ChatBots and standalone `/script` bots. Use this whenever the user wants an MCC bot, C# script bot, chat or event handlers, periodic automation, movement logic, inventory logic, plugin-channel handling, or asks to fix or port an existing bot; default to standalone `//MCCScript` bots unless the user explicitly asks for a built-in MCC bot or repo wiring.
|
||||
---
|
||||
|
||||
# MCC ChatBot Authoring
|
||||
|
||||
Implement MCC chat bots against the bundled MCC authoring reference. Do not invent methods, lifecycle hooks, or registration steps.
|
||||
|
||||
Always read:
|
||||
- `references/authoring-reference.md`
|
||||
|
||||
Load only as needed:
|
||||
- `references/pattern-cookbook.md` for concrete standalone examples
|
||||
- `assets/script-chatbot-template.cs` for the default standalone `/script` path
|
||||
- `assets/builtin-chatbot-template.cs` only when the user explicitly requests a built-in bot
|
||||
|
||||
If the current workspace contains an MCC checkout, verify final names and signatures against local sources before editing. The skill should still work without those files.
|
||||
If there is no MCC checkout available, rely on the bundled reference and cookbook as the full source of truth for authoring patterns.
|
||||
|
||||
## Choose the bot type first
|
||||
|
||||
1. Default to a standalone script bot loaded with `/script`.
|
||||
2. Only choose a built-in bot when the user explicitly asks for a compiled MCC bot, repo wiring, automatic config loading, or changes under the built-in bot system.
|
||||
3. If the prompt is ambiguous, infer the likely target from commands, requested output files, or phrasing, state the assumption briefly, and proceed.
|
||||
4. If a user says only "make a bot", do not create a built-in bot.
|
||||
|
||||
## Source priority
|
||||
|
||||
When the local MCC checkout is available, prefer these sources in this order:
|
||||
1. `MinecraftClient/Scripting/ChatBot.cs` and current files under `MinecraftClient/ChatBots/`
|
||||
2. the bundled `references/authoring-reference.md`
|
||||
3. the bundled `references/pattern-cookbook.md`
|
||||
4. older `MinecraftClient/config/` sample bots only for ideas, not as the default scaffold
|
||||
|
||||
If an older sample conflicts with the current built-in bots, follow the current built-in bots.
|
||||
If the local checkout is not available, do not block on missing repo files. Use the bundled references directly.
|
||||
|
||||
## Hard rules
|
||||
|
||||
- Only use lifecycle hooks and helpers documented in the bundled reference or verified in the target codebase.
|
||||
- Do not send chat from `Initialize()`. Use `AfterGameJoined()` once the session can send messages.
|
||||
- Prefer the current Brigadier command-registration pattern for built-in bots. Do not introduce `ChatBotCommand` unless the surrounding code already uses it.
|
||||
- For message parsing, normalize with `GetVerbatim(text)` before `IsChatMessage(...)` or `IsPrivateMessage(...)`.
|
||||
- Clean up everything you register or start: commands, plugin channels, threads, timers, and movement locks.
|
||||
- If a built-in bot or long-running automation controls movement, follow a movement-lock pattern and release it on every stop path. Do not add `BotMovementLock` to a simple standalone `/script` bot unless the prompt or surrounding code explicitly needs shared movement coordination.
|
||||
- For built-in bots, follow the host codebase's localization and config-comment conventions instead of scattering hardcoded user-facing text.
|
||||
- For new code, prefer `Initialize()` over constructors for prerequisite checks and unload decisions.
|
||||
- In this repo, built-in bot wiring usually means edits in `MinecraftClient/Settings.cs` and `MinecraftClient/McClient.cs` in addition to the bot class.
|
||||
- For repair tasks, preserve the existing bot type and file layout unless the user explicitly asks for a conversion or restructure.
|
||||
|
||||
## Standalone script bots
|
||||
|
||||
Use the exact MCC metadata format from the bundled reference.
|
||||
This is the default path for new work.
|
||||
|
||||
The script should usually:
|
||||
- keep `Initialize()` for cheap setup only
|
||||
- use `GetText(...)`, `AfterGameJoined()`, and other event hooks for live behavior
|
||||
- log with `LogToConsole(...)`
|
||||
- send server chat or commands with `SendText(...)`
|
||||
- use `PerformInternalCommand(...)` only for MCC internal commands
|
||||
- add `//using MinecraftClient.Inventory` in metadata when the script uses inventory types explicitly
|
||||
- reuse the standalone snippets in `references/pattern-cookbook.md` before inventing new scaffolding
|
||||
- keep load instructions explicit, usually `/script FileName.cs`
|
||||
|
||||
## Built-in bots
|
||||
|
||||
Built-in bots usually need three pieces:
|
||||
- the bot class itself
|
||||
- config wiring in the chat-bot config model
|
||||
- bot registration in the load flow
|
||||
|
||||
If the codebase exposes commands, follow the built-in command and unload pattern from the bundled reference. If it exposes new settings or status text, follow the codebase's localization and config-comment patterns.
|
||||
|
||||
When working in this checkout, built-in bot delivery usually needs:
|
||||
- a new file under `MinecraftClient/ChatBots/`
|
||||
- a config property inside `Settings.ChatBotConfigHealper.ChatBotConfig`
|
||||
- a `BotLoad(new YourBot())` line inside `McClient.RegisterBots(...)`
|
||||
- literal code snippets or patch hunks for the `Settings.cs` property and the `McClient.cs` registration line, not only prose notes
|
||||
|
||||
## Repair flow
|
||||
|
||||
When the user asks to fix or debug a bot:
|
||||
- identify whether it is standalone or built-in and keep that shape unless told otherwise
|
||||
- remove the broken pattern first, then preserve the intended behavior
|
||||
- check especially for these regressions: `SendText(...)` in `Initialize()`, raw formatted chat parsing, inventory snapshot mutation, missing command unregister, missing plugin-channel unregister, and unreleased movement locks
|
||||
- reuse the local repo's modern pattern instead of patching around a legacy helper when the helper is no longer current
|
||||
|
||||
## Delivery checklist
|
||||
|
||||
Before finishing, verify:
|
||||
- the class inherits `ChatBot`
|
||||
- the chosen overrides exist in the MCC ChatBot API
|
||||
- standalone script metadata is exact if this is a `/script` bot
|
||||
- built-in bots are fully wired into config and registration if needed
|
||||
- all command registrations, background work, and movement locks are released
|
||||
- files and namespaces match the surrounding codebase
|
||||
|
||||
## Output
|
||||
|
||||
When you implement or modify a bot:
|
||||
- state whether it is a standalone script bot or built-in bot
|
||||
- list the files you changed
|
||||
- mention any required config keys or the MCC command used to load it
|
||||
- when built-in wiring is involved, show the exact inserted code lines or patch hunks for `Settings.cs` and `McClient.cs`
|
||||
- call out assumptions briefly if the user did not specify bot type or trigger behavior
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
// Use this template only when the user explicitly requests a built-in MCC bot.
|
||||
|
||||
using MinecraftClient.Scripting;
|
||||
using Tomlet.Attributes;
|
||||
|
||||
namespace MinecraftClient.ChatBots
|
||||
{
|
||||
public class ExampleBot : ChatBot
|
||||
{
|
||||
private const string BotName = "ExampleBot";
|
||||
|
||||
public static Configs Config = new();
|
||||
|
||||
[TomlDoNotInlineObject]
|
||||
public class Configs
|
||||
{
|
||||
public bool Enabled = false;
|
||||
|
||||
public void OnSettingUpdate()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
LogToConsole(BotName, "Initialized.");
|
||||
}
|
||||
|
||||
public override void AfterGameJoined()
|
||||
{
|
||||
}
|
||||
|
||||
public override void GetText(string text)
|
||||
{
|
||||
text = GetVerbatim(text);
|
||||
|
||||
string message = "";
|
||||
string username = "";
|
||||
|
||||
if (IsPrivateMessage(text, ref message, ref username))
|
||||
{
|
||||
}
|
||||
else if (IsChatMessage(text, ref message, ref username))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnUnload()
|
||||
{
|
||||
}
|
||||
|
||||
public override bool OnDisconnect(DisconnectReason reason, string message)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
//MCCScript 1.0
|
||||
|
||||
MCC.LoadBot(new ExampleScriptBot());
|
||||
|
||||
//MCCScript Extensions
|
||||
|
||||
public class ExampleScriptBot : ChatBot
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
LogToConsole("ExampleScriptBot initialized.");
|
||||
}
|
||||
|
||||
public override void AfterGameJoined()
|
||||
{
|
||||
// Safe place for startup chat or commands.
|
||||
}
|
||||
|
||||
public override void GetText(string text)
|
||||
{
|
||||
text = GetVerbatim(text);
|
||||
|
||||
string message = "";
|
||||
string username = "";
|
||||
|
||||
if (IsPrivateMessage(text, ref message, ref username))
|
||||
{
|
||||
LogToConsole("PM from " + username + ": " + message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsChatMessage(text, ref message, ref username))
|
||||
{
|
||||
LogToConsole("Chat from " + username + ": " + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
492
.skills/mcc-chatbot-authoring/references/authoring-reference.md
Normal file
492
.skills/mcc-chatbot-authoring/references/authoring-reference.md
Normal file
|
|
@ -0,0 +1,492 @@
|
|||
# MCC ChatBot Reference
|
||||
|
||||
Self-contained authoring notes for Minecraft Console Client chat bots.
|
||||
|
||||
## Bot types
|
||||
|
||||
MCC supports two common authoring paths:
|
||||
- standalone script bots loaded at runtime with `/script`
|
||||
- built-in bots compiled into the MCC codebase
|
||||
|
||||
Default to a standalone `/script` bot unless the user explicitly asks for a built-in bot or repo wiring.
|
||||
|
||||
## Embedded current patterns
|
||||
|
||||
This skill is intended to work even without an MCC checkout. The patterns below capture the important behavior that would otherwise be borrowed from current repo examples.
|
||||
|
||||
If the local repo is available, you can verify against files such as `TestBot.cs`, `RemoteControl.cs`, `FollowPlayer.cs`, `ItemsCollector.cs`, and `Farmer.cs`. If it is not available, use the embedded patterns here directly.
|
||||
|
||||
### Minimal chat parsing pattern
|
||||
|
||||
Use this as the baseline for public/private chat handling:
|
||||
|
||||
```csharp
|
||||
public override void GetText(string text)
|
||||
{
|
||||
string message = "";
|
||||
string sender = "";
|
||||
text = GetVerbatim(text);
|
||||
|
||||
if (IsPrivateMessage(text, ref message, ref sender))
|
||||
{
|
||||
LogToConsole("PM from " + sender + ": " + message);
|
||||
}
|
||||
else if (IsChatMessage(text, ref message, ref sender))
|
||||
{
|
||||
LogToConsole("Chat from " + sender + ": " + message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
What matters:
|
||||
- normalize first with `GetVerbatim(text)`
|
||||
- handle PMs before public chat if both matter
|
||||
- keep simple chat bots deterministic and small
|
||||
|
||||
### Owner-gated PM control pattern
|
||||
|
||||
Use this when a bot owner should be able to whisper MCC internal commands:
|
||||
|
||||
```csharp
|
||||
public override void GetText(string text)
|
||||
{
|
||||
text = GetVerbatim(text).Trim();
|
||||
string command = "";
|
||||
string sender = "";
|
||||
|
||||
if (IsPrivateMessage(text, ref command, ref sender)
|
||||
&& Settings.Config.Main.Advanced.BotOwners.Contains(sender.ToLowerInvariant()))
|
||||
{
|
||||
CmdResult result = new();
|
||||
PerformInternalCommand(command, ref result);
|
||||
SendPrivateMessage(sender, result.ToString());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
What matters:
|
||||
- `PerformInternalCommand(...)` is for MCC commands, not server chat commands
|
||||
- owner gating should use `Settings.Config.Main.Advanced.BotOwners`
|
||||
- if `CmdResult` is used in a standalone script, add `//using MinecraftClient.CommandHandler`
|
||||
|
||||
### Periodic work pattern
|
||||
|
||||
Use `Update()` plus a counter or timestamp for simple repeated work:
|
||||
|
||||
```csharp
|
||||
private int count = 0;
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
count++;
|
||||
if (count < Settings.DoubleToTick(60))
|
||||
return;
|
||||
|
||||
count = 0;
|
||||
SendText("/list");
|
||||
}
|
||||
```
|
||||
|
||||
What matters:
|
||||
- avoid a worker thread for simple periodic loops
|
||||
- avoid `Thread.Sleep(...)` inside `Update()`
|
||||
- if sending chat, do it from a join-safe path like `Update()` or `AfterGameJoined()`, not `Initialize()`
|
||||
|
||||
### Built-in Brigadier command pattern
|
||||
|
||||
Use this for built-in command bots:
|
||||
|
||||
```csharp
|
||||
public override void Initialize()
|
||||
{
|
||||
McClient.dispatcher.Register(l => l.Literal("help")
|
||||
.Then(l => l.Literal(CommandName)
|
||||
.Executes(r => OnCommandHelp(r.Source, string.Empty))
|
||||
)
|
||||
);
|
||||
|
||||
McClient.dispatcher.Register(l => l.Literal(CommandName)
|
||||
.Then(l => l.Literal("stop")
|
||||
.Executes(r => OnCommandStop(r.Source)))
|
||||
.Then(l => l.Literal("_help")
|
||||
.Executes(r => OnCommandHelp(r.Source, string.Empty))
|
||||
.Redirect(McClient.dispatcher.GetRoot().GetChild("help").GetChild(CommandName)))
|
||||
);
|
||||
}
|
||||
|
||||
public override void OnUnload()
|
||||
{
|
||||
McClient.dispatcher.Unregister(CommandName);
|
||||
McClient.dispatcher.GetRoot().GetChild("help").RemoveChild(CommandName);
|
||||
}
|
||||
```
|
||||
|
||||
What matters:
|
||||
- register commands in `Initialize()`
|
||||
- unregister the command tree in `OnUnload()`
|
||||
- remove the help child you added in `OnUnload()`
|
||||
- prefer this over legacy command wrappers for new built-in work
|
||||
|
||||
### Built-in config and wiring pattern
|
||||
|
||||
Use this as the default built-in shape:
|
||||
|
||||
```csharp
|
||||
public class ExampleBot : ChatBot
|
||||
{
|
||||
public static Configs Config = new();
|
||||
|
||||
[TomlDoNotInlineObject]
|
||||
public class Configs
|
||||
{
|
||||
public bool Enabled = false;
|
||||
|
||||
public void OnSettingUpdate()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Typical host wiring shape:
|
||||
|
||||
```csharp
|
||||
[TomlPrecedingComment("$ChatBot.ExampleBot$")]
|
||||
public ChatBots.ExampleBot.Configs ExampleBot
|
||||
{
|
||||
get { return ChatBots.ExampleBot.Config; }
|
||||
set { ChatBots.ExampleBot.Config = value; ChatBots.ExampleBot.Config.OnSettingUpdate(); }
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
if (Config.ChatBot.ExampleBot.Enabled) { BotLoad(new ExampleBot()); }
|
||||
```
|
||||
|
||||
What matters:
|
||||
- built-in configurable bots default to `Enabled = false`
|
||||
- `OnSettingUpdate()` is the place to normalize config values
|
||||
- built-in delivery is incomplete without both config wiring and load registration
|
||||
|
||||
### Movement gating pattern
|
||||
|
||||
Use this shape when a built-in bot owns movement:
|
||||
|
||||
```csharp
|
||||
public override void Initialize()
|
||||
{
|
||||
if (!GetEntityHandlingEnabled())
|
||||
{
|
||||
LogToConsole("Entity handling is required.");
|
||||
UnloadBot();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GetTerrainEnabled())
|
||||
{
|
||||
LogToConsole("Terrain handling is required.");
|
||||
UnloadBot();
|
||||
return;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
var movementLock = BotMovementLock.Instance;
|
||||
if (movementLock is { IsLocked: true })
|
||||
return;
|
||||
|
||||
movementLock?.Lock("Example Bot");
|
||||
```
|
||||
|
||||
```csharp
|
||||
public override void OnUnload()
|
||||
{
|
||||
BotMovementLock.Instance?.UnLock("Example Bot");
|
||||
}
|
||||
```
|
||||
|
||||
What matters:
|
||||
- guard terrain and entity handling before movement logic
|
||||
- built-in movement bots should use `BotMovementLock`
|
||||
- release the lock on every stop path, including unload and disconnect-sensitive flows
|
||||
|
||||
### Dropped-item collector pattern
|
||||
|
||||
Use this as the standalone item-search baseline:
|
||||
|
||||
```csharp
|
||||
private DateTime nextScan = DateTime.MinValue;
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
if (now < nextScan || ClientIsMoving())
|
||||
return;
|
||||
|
||||
nextScan = now.AddSeconds(1);
|
||||
|
||||
var here = GetCurrentLocation();
|
||||
var target = GetEntities().Values
|
||||
.Where(entity => entity.Type == EntityType.Item && entity.Location.Distance(here) <= 15)
|
||||
.OrderBy(entity => entity.Location.Distance(here))
|
||||
.FirstOrDefault();
|
||||
|
||||
if (target != null)
|
||||
MoveToLocation(target.Location);
|
||||
}
|
||||
```
|
||||
|
||||
What matters:
|
||||
- simple standalone collectors do not need a worker thread
|
||||
- simple standalone collectors also do not need `BotMovementLock` by default
|
||||
- `GetEntities()` plus distance ordering is the core search pattern
|
||||
|
||||
### Inventory selection pattern
|
||||
|
||||
Use this as the default hotbar-switch pattern:
|
||||
|
||||
```csharp
|
||||
private bool TrySwitchToItem(ItemType itemType)
|
||||
{
|
||||
var inventory = GetPlayerInventory();
|
||||
|
||||
var hotbarSlots = inventory.SearchItem(itemType)
|
||||
.Where(slot => slot >= 36 && slot <= 44)
|
||||
.ToArray();
|
||||
|
||||
if (hotbarSlots.Length == 0)
|
||||
return false;
|
||||
|
||||
ChangeSlot((short)(hotbarSlots[0] - 36));
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
What matters:
|
||||
- guard with `GetInventoryEnabled()`
|
||||
- search inventory snapshots, but mutate real server state with helpers like `ChangeSlot(...)`
|
||||
- do not treat local `Container.Items` mutation as real inventory manipulation
|
||||
|
||||
Use the older config examples only for ideas, not as primary scaffolding.
|
||||
|
||||
## Standalone script format
|
||||
|
||||
A standalone script bot has two parts in this order:
|
||||
1. metadata block
|
||||
2. one or more C# classes, with the main bot class inheriting `ChatBot`
|
||||
|
||||
Required metadata rules:
|
||||
- line 1 must be exactly `//MCCScript 1.0`
|
||||
- metadata must include `MCC.LoadBot(new BotClassName());`
|
||||
- metadata ends with `//MCCScript Extensions`
|
||||
- optional metadata directives use `//using Namespace` and `//dll SomeLibrary.dll`
|
||||
- do not insert a space after `//` in metadata directives
|
||||
|
||||
Typical runtime flow:
|
||||
- place the script file beside MCC
|
||||
- connect to a server
|
||||
- load it with `/script YourBotFile.cs`
|
||||
|
||||
### Namespace linking for inventory code
|
||||
|
||||
If a standalone script uses inventory-specific types such as `Container`, `ItemType`, `WindowActionType`, or `ItemMovingHelper`, add this metadata import:
|
||||
|
||||
```csharp
|
||||
//using MinecraftClient.Inventory
|
||||
```
|
||||
|
||||
For built-in bots, use a normal C# import:
|
||||
|
||||
```csharp
|
||||
using MinecraftClient.Inventory;
|
||||
```
|
||||
|
||||
## Lifecycle summary
|
||||
|
||||
Common lifecycle hooks:
|
||||
- `Initialize()`
|
||||
called once when the bot loads; use it for cheap setup only
|
||||
- `AfterGameJoined()`
|
||||
called after the server has been joined successfully, and again after reconnecting; use it when chat can be sent
|
||||
- `Update()`
|
||||
called roughly every 100 ms
|
||||
- `OnUnload()`
|
||||
called when the bot unloads; release resources here
|
||||
- `OnDisconnect(DisconnectReason reason, string message)`
|
||||
called on disconnect; stop background work and clean up reconnect-sensitive state here
|
||||
|
||||
Important rule:
|
||||
- do not send chat from `Initialize()`; use `AfterGameJoined()` instead
|
||||
- prefer `Initialize()` over constructors for environment checks and resource setup
|
||||
|
||||
## Common event hooks
|
||||
|
||||
Useful event hooks include:
|
||||
- `GetText(string text)`
|
||||
- `GetText(string text, string? json)`
|
||||
- `OnPlayerJoin(Guid uuid, string name)`
|
||||
- `OnPlayerLeave(Guid uuid, string? name)`
|
||||
- `OnEntitySpawn(Entity entity)`
|
||||
- `OnEntityDespawn(Entity entity)`
|
||||
- `OnEntityMove(Entity entity)`
|
||||
- `OnHealthUpdate(float health, int food)`
|
||||
- `OnMapData(...)`
|
||||
- `OnInventoryUpdate(int inventoryId)`
|
||||
- `OnPluginMessage(string channel, byte[] data)`
|
||||
- `OnNetworkPacket(int packetID, List<byte> packetData, bool isLogin, bool isInbound)`
|
||||
|
||||
Only override hooks that actually exist in the target MCC ChatBot API.
|
||||
|
||||
## Common helpers
|
||||
|
||||
Text and messaging helpers:
|
||||
- `GetVerbatim(text)` strips Minecraft formatting codes
|
||||
- `IsChatMessage(text, ref message, ref sender)` parses public chat
|
||||
- `IsPrivateMessage(text, ref message, ref sender)` parses private chat
|
||||
- `IsValidName(username)` validates a Minecraft username
|
||||
- `SendText(text)` sends chat or server commands
|
||||
- `SendPrivateMessage(player, message)` sends a private message
|
||||
- `PerformInternalCommand(command, ...)` runs an internal MCC command, not a server command
|
||||
- `LogToConsole(text)` writes a bot-prefixed console message
|
||||
|
||||
Lifecycle and threading helpers:
|
||||
- `InvokeOnMainThread(...)`
|
||||
- `ScheduleOnMainThread(...)`
|
||||
- `ReconnectToTheServer(...)`
|
||||
- `UnloadBot()`
|
||||
- `BotLoad(chatBot)`
|
||||
- `RunScript(filename, ...)`
|
||||
|
||||
World and player-state helpers:
|
||||
- `GetWorld()`
|
||||
- `GetEntities()`
|
||||
- `GetCurrentLocation()`
|
||||
- `ClientIsMoving()`
|
||||
- `GetOnlinePlayers()`
|
||||
- `GetOnlinePlayersWithUUID()`
|
||||
- `GetServerTPS()`
|
||||
- `GetProtocolVersion()`
|
||||
|
||||
Movement and inventory helpers:
|
||||
- `MoveToLocation(...)`
|
||||
- `LookAtLocation(...)`
|
||||
- `GetInventoryEnabled()`
|
||||
- `GetPlayerInventory()`
|
||||
- `GetInventories()`
|
||||
- `GetItemMovingHelper(...)`
|
||||
- `WindowAction(...)`
|
||||
- `ChangeSlot(...)`
|
||||
- `GetCurrentSlot()`
|
||||
- `UseItemInHand()`
|
||||
- `UseItemInLeftHand()`
|
||||
- `CloseInventory(...)`
|
||||
- `DigBlock(...)`
|
||||
- `InteractEntity(...)`
|
||||
|
||||
## Inventory notes
|
||||
|
||||
Inventory handling is optional in MCC. Check `GetInventoryEnabled()` before relying on inventory state or mutation.
|
||||
|
||||
Important behavior:
|
||||
- `GetPlayerInventory()` returns a snapshot copy of the player's inventory
|
||||
- `GetInventories()` returns current container snapshots
|
||||
- writing to those `Container` objects locally does not update the server
|
||||
- to actually change inventory state, use `ChangeSlot(...)`, `WindowAction(...)`, `GetItemMovingHelper(...)`, `UseItemInHand()`, or related helpers
|
||||
|
||||
Useful practical facts:
|
||||
- hotbar selection uses `ChangeSlot(0..8)`
|
||||
- hotbar slots are commonly `36..44` in inventory slot numbering
|
||||
- the offhand slot is commonly `45`
|
||||
- `Container.SearchItem(...)` is the normal way to locate items by type
|
||||
|
||||
Good inventory workflow:
|
||||
1. guard with `GetInventoryEnabled()`
|
||||
2. read the current container using `GetPlayerInventory()`
|
||||
3. locate slots with `SearchItem(...)` or `Items`
|
||||
4. mutate server state using `ChangeSlot(...)`, `WindowAction(...)`, or `ItemMovingHelper`
|
||||
5. if needed, react to `OnInventoryUpdate(...)`, `OnInventoryOpen(...)`, or `OnInventoryClose(...)`
|
||||
|
||||
Plugins and channels:
|
||||
- `RegisterPluginChannel(channel)`
|
||||
- `UnregisterPluginChannel(channel)`
|
||||
- `SendPluginChannelMessage(channel, data, ...)`
|
||||
|
||||
## Built-in bot pattern
|
||||
|
||||
A built-in bot usually follows this shape:
|
||||
- a class that inherits `ChatBot`
|
||||
- an optional static `Config` field
|
||||
- a nested `[TomlDoNotInlineObject]` `Configs` class for settings
|
||||
- an `Enabled = false` setting by default
|
||||
- `OnSettingUpdate()` to normalize or validate config values
|
||||
|
||||
If the bot is configurable, the host codebase usually also needs:
|
||||
- config wiring in the chat-bot config model
|
||||
- load registration so enabled bots are instantiated automatically
|
||||
|
||||
In this MCC checkout, the usual built-in wiring points are:
|
||||
- `MinecraftClient/Settings.cs` inside `Settings.ChatBotConfigHealper.ChatBotConfig`
|
||||
- `MinecraftClient/McClient.cs` inside `RegisterBots(...)`
|
||||
|
||||
Match the surrounding `[TomlPrecedingComment(...)]`, property-forwarding, and `BotLoad(new YourBot())` style instead of inventing a different config path.
|
||||
When presenting built-in wiring, prefer literal code snippets or patch hunks for those two edits so the wiring can be checked directly.
|
||||
|
||||
If the bot adds user-facing settings or messages, follow the host codebase's localization and config-comment conventions instead of scattering hardcoded strings.
|
||||
|
||||
## Command pattern
|
||||
|
||||
For standalone script bots, prefer chat or PM handling in `GetText(...)` unless the user explicitly asks for built-in command registration.
|
||||
|
||||
For built-in commands, prefer the current Brigadier dispatcher pattern:
|
||||
- register commands in `Initialize()`
|
||||
- add a help entry if the bot exposes commands
|
||||
- unregister the command tree in `OnUnload()`
|
||||
- remove any help child added during registration in `OnUnload()`
|
||||
|
||||
Avoid using legacy command wrappers if the current codebase uses direct dispatcher registration.
|
||||
In this checkout, treat direct `McClient.dispatcher.Register(...)` usage in current built-in bots as the source of truth.
|
||||
|
||||
## Concurrency and cleanup
|
||||
|
||||
If the bot starts background work:
|
||||
- stop it in `OnUnload()`
|
||||
- stop it in `OnDisconnect(...)`
|
||||
- consider resetting state in `AfterGameJoined()` after relog
|
||||
- prefer `Update()` plus counters or timestamps over unmanaged threads when the task is simple periodic work
|
||||
|
||||
If the bot controls movement:
|
||||
- use a movement-lock discipline
|
||||
- release the lock on every stop path
|
||||
- avoid fighting other movement bots
|
||||
- `BotMovementLock` is mainly for built-in bots or shared long-running automation; a simple standalone script that just calls `MoveToLocation(...)` does not need it by default
|
||||
|
||||
When interacting with client state from background logic, use the main-thread helpers when required by the codebase.
|
||||
|
||||
## Practical defaults
|
||||
|
||||
For simple chat bots:
|
||||
- normalize text with `GetVerbatim(text)`
|
||||
- inspect private chat first if the bot listens for whispers
|
||||
- then inspect public chat
|
||||
- keep response logic small and deterministic
|
||||
|
||||
For long-running automation bots:
|
||||
- guard prerequisites early, such as entity handling or terrain support
|
||||
- fail fast with a clear log message if prerequisites are missing
|
||||
- release all ongoing work cleanly on unload and disconnect
|
||||
|
||||
## Common pitfalls
|
||||
|
||||
- Incorrect metadata line 1 will break standalone script loading.
|
||||
- Missing `MCC.LoadBot(new BotClassName())` will prevent standalone script registration.
|
||||
- Sending chat in `Initialize()` is too early.
|
||||
- Doing prerequisite checks or unloading from the constructor is harder to reason about than using `Initialize()`.
|
||||
- Parsing raw formatted text without `GetVerbatim()` causes brittle chat matching.
|
||||
- Inventing methods not present in the MCC ChatBot API leads to dead code.
|
||||
- Built-in bot work is incomplete if config or registration wiring is missing.
|
||||
- Command bots are incomplete if they register commands but do not unregister them.
|
||||
- `RegisterChatBotCommand(...)` comes from older samples and is not a reliable current pattern for this checkout.
|
||||
- `ChatBotCommand` exists, but the current built-in bots use Brigadier directly; do not prefer `ChatBotCommand` for new work.
|
||||
- Blocking `Thread.Sleep(...)` inside `Update()` is a bad default. Prefer timers, counters, or timestamp-based scheduling.
|
||||
- Mutating the `Container` returned by `GetPlayerInventory()` does not change the server. Use inventory actions instead.
|
||||
330
.skills/mcc-chatbot-authoring/references/pattern-cookbook.md
Normal file
330
.skills/mcc-chatbot-authoring/references/pattern-cookbook.md
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
# MCC Pattern Cookbook
|
||||
|
||||
Concrete patterns for standalone MCC `/script` bots. Use these before inventing new scaffolding.
|
||||
|
||||
## Periodic task without threads
|
||||
|
||||
Use `Update()` plus a timestamp or counter. This comes from the old `sample-script-with-task.cs` example and still holds up well.
|
||||
|
||||
```csharp
|
||||
public class PeriodicTaskBot : ChatBot
|
||||
{
|
||||
private DateTime nextRun = DateTime.MinValue;
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
if (now < nextRun)
|
||||
return;
|
||||
|
||||
nextRun = now.AddSeconds(30);
|
||||
LogDebugToConsole("Running periodic task");
|
||||
SendText("/ping");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Why this pattern is good:
|
||||
- stays on MCC's normal tick flow
|
||||
- avoids background threads for simple periodic work
|
||||
- keeps the bot responsive to unload and disconnect
|
||||
|
||||
## Chat and PM handling
|
||||
|
||||
This combines the useful parts of `TestBot`, `sample-script-pm-forwarder.cs`, and `RemoteControl.cs`.
|
||||
|
||||
```csharp
|
||||
public override void GetText(string text)
|
||||
{
|
||||
text = GetVerbatim(text);
|
||||
|
||||
string message = "";
|
||||
string sender = "";
|
||||
|
||||
if (IsPrivateMessage(text, ref message, ref sender))
|
||||
{
|
||||
LogToConsole("PM from " + sender + ": " + message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsChatMessage(text, ref message, ref sender))
|
||||
{
|
||||
LogToConsole("Chat from " + sender + ": " + message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Owner-gated internal command handling:
|
||||
|
||||
Add `//using MinecraftClient.CommandHandler` in the script metadata if you use `CmdResult`.
|
||||
|
||||
```csharp
|
||||
public override void GetText(string text)
|
||||
{
|
||||
text = GetVerbatim(text).Trim();
|
||||
|
||||
string command = "";
|
||||
string sender = "";
|
||||
|
||||
if (IsPrivateMessage(text, ref command, ref sender)
|
||||
&& Settings.Config.Main.Advanced.BotOwners.Contains(sender.ToLowerInvariant()))
|
||||
{
|
||||
CmdResult result = new();
|
||||
PerformInternalCommand(command, ref result);
|
||||
SendPrivateMessage(sender, result.ToString());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Movement with prerequisite checks
|
||||
|
||||
Modern movement code should copy the guard style from current built-in bots, not the older constructor-heavy scripts.
|
||||
|
||||
```csharp
|
||||
public override void Initialize()
|
||||
{
|
||||
if (!GetEntityHandlingEnabled() || !GetTerrainEnabled())
|
||||
{
|
||||
LogToConsole("Entity handling and terrain handling are required.");
|
||||
UnloadBot();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Simple "look at nearest player" logic adapted from `AutoLook.cs`:
|
||||
|
||||
```csharp
|
||||
private Entity? trackedPlayer = null;
|
||||
|
||||
public override void OnEntitySpawn(Entity entity)
|
||||
{
|
||||
TryTrack(entity);
|
||||
}
|
||||
|
||||
public override void OnEntityDespawn(Entity entity)
|
||||
{
|
||||
if (trackedPlayer != null && entity.ID == trackedPlayer.ID)
|
||||
trackedPlayer = null;
|
||||
}
|
||||
|
||||
public override void OnEntityMove(Entity entity)
|
||||
{
|
||||
if (!TryTrack(entity))
|
||||
return;
|
||||
|
||||
LookAtLocation(entity.Location);
|
||||
}
|
||||
|
||||
private bool TryTrack(Entity entity)
|
||||
{
|
||||
if (entity.Type != EntityType.Player)
|
||||
return false;
|
||||
|
||||
if (trackedPlayer == null)
|
||||
{
|
||||
trackedPlayer = entity;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (GetCurrentLocation().Distance(entity.Location) < GetCurrentLocation().Distance(trackedPlayer.Location))
|
||||
trackedPlayer = entity;
|
||||
|
||||
return trackedPlayer.ID == entity.ID;
|
||||
}
|
||||
```
|
||||
|
||||
## Search for dropped items and move to them
|
||||
|
||||
This is the safest pattern to preserve from `ItemsCollector.cs` for standalone scripts.
|
||||
|
||||
```csharp
|
||||
public class NearbyItemsBot : ChatBot
|
||||
{
|
||||
private DateTime nextScan = DateTime.MinValue;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
if (!GetEntityHandlingEnabled() || !GetTerrainEnabled())
|
||||
{
|
||||
LogToConsole("Entity handling and terrain handling are required.");
|
||||
UnloadBot();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
if (now < nextScan || ClientIsMoving())
|
||||
return;
|
||||
|
||||
nextScan = now.AddSeconds(1);
|
||||
|
||||
var here = GetCurrentLocation();
|
||||
var target = GetEntities().Values
|
||||
.Where(entity => entity.Type == EntityType.Item && entity.Location.Distance(here) <= 15)
|
||||
.OrderBy(entity => entity.Location.Distance(here))
|
||||
.FirstOrDefault();
|
||||
|
||||
if (target != null)
|
||||
MoveToLocation(target.Location);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Why this version is better than older farming scripts:
|
||||
- no unmanaged worker thread
|
||||
- no busy wait loop around movement
|
||||
- uses the current `GetEntities()` pattern
|
||||
|
||||
## Search for blocks or crops in the world
|
||||
|
||||
The old sugar cane and mining scripts still contain a useful search idea: use `GetWorld().FindBlock(...)`, then filter and sort.
|
||||
|
||||
```csharp
|
||||
var targets = GetWorld()
|
||||
.FindBlock(GetCurrentLocation(), Material.SugarCane, 16)
|
||||
.Where(block =>
|
||||
GetWorld().GetBlock(new Location(block.X, block.Y - 1, block.Z)).Type == Material.SugarCane)
|
||||
.OrderBy(block => block.Distance(GetCurrentLocation()))
|
||||
.ToList();
|
||||
```
|
||||
|
||||
Use this as a search primitive. Then decide separately how to move, dig, or harvest.
|
||||
|
||||
## Inventory access and manipulation
|
||||
|
||||
If a standalone script uses inventory types directly, add this import in the metadata block:
|
||||
|
||||
```csharp
|
||||
//using MinecraftClient.Inventory
|
||||
```
|
||||
|
||||
For built-in bots, add:
|
||||
|
||||
```csharp
|
||||
using MinecraftClient.Inventory;
|
||||
```
|
||||
|
||||
Always guard inventory logic first:
|
||||
|
||||
```csharp
|
||||
public override void Initialize()
|
||||
{
|
||||
if (!GetInventoryEnabled())
|
||||
{
|
||||
LogToConsole("Inventory handling is required.");
|
||||
UnloadBot();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Important rule:
|
||||
- `GetPlayerInventory()` returns a snapshot copy, so editing its `Items` dictionary does not change the server
|
||||
- actual changes must go through `ChangeSlot(...)`, `WindowAction(...)`, `GetItemMovingHelper(...)`, `UseItemInHand()`, and related helpers
|
||||
|
||||
### Search inventory for an item
|
||||
|
||||
This combines the useful current logic from `Farmer.cs` and `AutoEat.cs`.
|
||||
|
||||
```csharp
|
||||
private bool TrySwitchToItem(ItemType itemType)
|
||||
{
|
||||
var inventory = GetPlayerInventory();
|
||||
|
||||
if (inventory.Items.TryGetValue(GetCurrentSlot() - 36, out var held) && held.Type == itemType)
|
||||
return true;
|
||||
|
||||
var hotbarSlots = inventory.SearchItem(itemType)
|
||||
.Where(slot => slot >= 36 && slot <= 44)
|
||||
.ToArray();
|
||||
|
||||
if (hotbarSlots.Length == 0)
|
||||
return false;
|
||||
|
||||
ChangeSlot((short)(hotbarSlots[0] - 36));
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
Use this for simple hotbar selection. For deeper inventory reshuffling, built-in bots usually need more helper logic.
|
||||
|
||||
### Move an item into the hotbar
|
||||
|
||||
Use this when the item exists in inventory but is not already on the hotbar.
|
||||
|
||||
```csharp
|
||||
private bool TryMoveItemToHotbar(ItemType itemType, short targetHotbarSlot = 0)
|
||||
{
|
||||
var inventory = GetPlayerInventory();
|
||||
var matches = inventory.SearchItem(itemType);
|
||||
|
||||
if (matches.Length == 0)
|
||||
return false;
|
||||
|
||||
var targetInventorySlot = 36 + targetHotbarSlot;
|
||||
|
||||
if (matches[0] >= 36 && matches[0] <= 44)
|
||||
{
|
||||
ChangeSlot((short)(matches[0] - 36));
|
||||
return true;
|
||||
}
|
||||
|
||||
var movingHelper = GetItemMovingHelper(inventory);
|
||||
movingHelper.Swap(matches[0], targetInventorySlot);
|
||||
ChangeSlot(targetHotbarSlot);
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
Why this pattern is good:
|
||||
- it reads the current snapshot first
|
||||
- it does not pretend local `Container` edits affect the server
|
||||
- it uses the item-moving helper for real inventory manipulation
|
||||
|
||||
### Drop or click items with window actions
|
||||
|
||||
Use `WindowAction(...)` when the bot needs direct inventory clicks or dropping behavior.
|
||||
|
||||
```csharp
|
||||
private void DropAllOfType(ItemType itemType)
|
||||
{
|
||||
var inventory = GetPlayerInventory();
|
||||
|
||||
foreach (int slot in inventory.SearchItem(itemType))
|
||||
WindowAction(0, slot, WindowActionType.DropItemStack);
|
||||
}
|
||||
```
|
||||
|
||||
Use this pattern carefully:
|
||||
- verify the correct inventory ID first
|
||||
- prefer reacting to `OnInventoryUpdate(...)` for larger inventory workflows
|
||||
- for crafting or chest workflows, use `GetInventories()` and `CloseInventory(...)` as needed
|
||||
|
||||
## Built-in command bot pattern
|
||||
|
||||
Only use this when the user explicitly asks for a built-in bot.
|
||||
|
||||
```csharp
|
||||
public override void Initialize()
|
||||
{
|
||||
McClient.dispatcher.Register(l => l.Literal("help")
|
||||
.Then(l => l.Literal(CommandName)
|
||||
.Executes(r => OnCommandHelp(r.Source, string.Empty))
|
||||
)
|
||||
);
|
||||
|
||||
McClient.dispatcher.Register(l => l.Literal(CommandName)
|
||||
.Then(l => l.Literal("_help")
|
||||
.Executes(r => OnCommandHelp(r.Source, string.Empty))
|
||||
.Redirect(McClient.dispatcher.GetRoot().GetChild("help").GetChild(CommandName)))
|
||||
);
|
||||
}
|
||||
|
||||
public override void OnUnload()
|
||||
{
|
||||
McClient.dispatcher.Unregister(CommandName);
|
||||
McClient.dispatcher.GetRoot().GetChild("help").RemoveChild(CommandName);
|
||||
}
|
||||
```
|
||||
|
||||
Use a built-in bot only when the user explicitly asks for compiled MCC behavior or repo wiring.
|
||||
Loading…
Add table
Add a link
Reference in a new issue