Added bot creation skill

This commit is contained in:
Anon 2026-03-21 17:34:34 +01:00
parent 918f1a560d
commit a1c1dbc182
6 changed files with 1030 additions and 7 deletions

View 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

View file

@ -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;
}
}
}

View file

@ -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);
}
}
}

View 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.

View 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.