ModernUO/website/content/development/commands-and-targeting.mdx
2026-08-06 21:34:53 -07:00

206 lines
6.7 KiB
Text

---
sidebar_position: 4
title: Commands & Targeting
---
# Commands & Targeting
## Overview
ModernUO uses a command system where all player/staff commands are prefixed with `[` by default (this is configurable). Commands are registered in static `Configure()` methods that the server discovers automatically at startup. Each command is bound to a minimum access level, so only authorized players can execute it.
---
## Registering a Command
Commands are registered by calling `CommandSystem.Register` inside a static `Configure()` method. The server calls all `Configure()` methods during initialization -- no manual wiring is needed.
```csharp
public static class MyCommands
{
public static void Configure()
{
CommandSystem.Register("MyCommand", AccessLevel.GameMaster, MyCommand_OnCommand);
}
[Usage("MyCommand <name>")]
[Description("Does something with a name")]
public static void MyCommand_OnCommand(CommandEventArgs e)
{
var from = e.Mobile;
if (e.Length < 1)
{
from.SendMessage("Usage: [MyCommand <name>");
return;
}
var name = e.GetString(0);
from.SendMessage($"Processing {name}");
}
}
```
The `[Usage]` and `[Description]` attributes provide help text that appears in the in-game help system.
---
## CommandEventArgs API
When a command handler fires, it receives a `CommandEventArgs` object with the following members:
| Member | Type | Description |
|:-------|:-----|:------------|
| `Mobile` | `Mobile` | The mobile that issued the command |
| `Command` | `string` | The command name that was typed |
| `ArgString` | `string` | The full argument string after the command name |
| `Arguments` | `string[]` | Arguments split by spaces |
| `Length` | `int` | Number of arguments (`Arguments.Length`) |
| `GetString(i)` | `string` | Get argument at index `i` as a string |
| `GetInt32(i)` | `int` | Get argument at index `i` as an integer |
| `GetBoolean(i)` | `bool` | Get argument at index `i` as a boolean |
| `GetDouble(i)` | `double` | Get argument at index `i` as a double |
---
## Access Levels
Each command requires a minimum access level. Players below that level cannot execute the command.
| Level | Value | Description |
|:------|:------|:------------|
| `Player` | 0 | Normal player (default) |
| `Counselor` | 1 | Support staff with limited powers |
| `GameMaster` | 2 | GM with full world interaction |
| `Seer` | 3 | Event coordinator with extra tools |
| `Administrator` | 4 | Server administrator |
| `Developer` | 5 | Developer with access to debug commands |
| `Owner` | 6 | Server owner with unrestricted access |
---
## Targeting System
The targeting system lets a command (or any code) ask a player to click on something in the game world. The flow is:
1. Code sets `mobile.Target = new MyTarget()`.
2. The client displays a targeting cursor.
3. The player clicks on a mobile, item, land tile, or static object.
4. The `OnTarget` method fires with what was clicked.
---
## Target Implementation
A target class inherits from `Target` and overrides `OnTarget`. The `targeted` parameter can be a `Mobile`, `Item`, `LandTarget`, or `StaticTarget` -- use a `switch` to handle each case.
```csharp
public class IdentifyTarget : Target
{
public IdentifyTarget() : base(12, false, TargetFlags.None)
{
}
protected override void OnTarget(Mobile from, object targeted)
{
switch (targeted)
{
case Mobile m:
{
from.SendMessage($"That is a mobile named {m.Name}.");
break;
}
case Item item:
{
from.SendMessage($"That is an item: {item.GetType().Name} (0x{item.ItemID:X4}).");
break;
}
case LandTarget land:
{
from.SendMessage($"That is land tile at {land.Location}.");
break;
}
case StaticTarget st:
{
from.SendMessage($"That is a static: 0x{st.ItemID:X4}.");
break;
}
}
}
}
```
The `Target` constructor takes three parameters:
- **range** -- Maximum distance the target can be from the player.
- **allowGround** -- Whether clicking the ground is valid.
- **flags** -- `TargetFlags` value controlling criminal/beneficial checks.
---
## Command + Targeting Pattern
A common pattern is for a command to initiate targeting, then the target handler performs the actual work. This cleanly separates input from logic.
```csharp
public static class HealCommands
{
public static void Configure()
{
CommandSystem.Register("Heal", AccessLevel.GameMaster, Heal_OnCommand);
}
[Usage("Heal")]
[Description("Fully heals the targeted mobile")]
public static void Heal_OnCommand(CommandEventArgs e)
{
e.Mobile.SendMessage("Who do you want to heal?");
e.Mobile.Target = new HealTarget();
}
private class HealTarget : Target
{
public HealTarget() : base(12, false, TargetFlags.Beneficial)
{
}
protected override void OnTarget(Mobile from, object targeted)
{
if (targeted is Mobile m)
{
m.Hits = m.HitsMax;
m.SendMessage("You have been fully healed.");
from.SendMessage($"You healed {m.Name}.");
}
else
{
from.SendMessage("That is not a mobile.");
}
}
}
}
```
---
## TargetFlags
Target flags tell the server what kind of action the player is performing, which affects criminal checks and other systems.
| Flag | Description |
|:-----|:------------|
| `None` | Neutral action -- no criminal or beneficial checks |
| `Harmful` | Hostile action -- triggers criminal flagging if targeting innocents |
| `Beneficial` | Helpful action -- triggers beneficial checks (healing, buffing) |
:::tip
Always set the correct flag. Using `Harmful` on a healing target (or `None` on an attack) bypasses important game mechanics like the criminal system.
:::
---
## Best Practices
- **Register in `Configure()`** -- The server discovers these methods automatically. Do not register commands in constructors or other lifecycle methods.
- **Validate argument count** -- Always check `e.Length` before accessing arguments to avoid index-out-of-range errors.
- **Use appropriate access levels** -- Do not default to `Owner`. Choose the lowest level that makes sense for the command.
- **Use `Harmful`/`Beneficial` flags correctly** -- This ensures the criminal and notoriety systems work as intended.
- **Keep target handlers focused** -- Let the command set up the target, and let the target handler do the work.