> For the complete documentation index, see [llms.txt](https://docs.sparkframework.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sparkframework.dev/documentation/developer-guide/core-systems/command-system.md).

# Command System

Commands represent actions that change game state. They are the standard way to perform mutations in Spark: giving items, dealing damage, accepting quests, modifying reputation, and so on.

## Why Commands?

Commands exist for two reasons:

1. **Validation**: Every action goes through a handler that can validate inputs before applying changes.
2. **Networking**: Commands are routed through the network provider. In single-player, they execute locally. In multiplayer, they can be sent to the server for authoritative execution. The calling code doesn't need to know which mode it's in.

## ICommand Interface

`ICommand` is a marker interface with no methods or properties:

```csharp
public interface ICommand { }
```

Commands are simple data containers. They hold the parameters needed to perform an action but contain no logic:

```csharp
public class GiveItemCommand : ICommand
{
    public string TargetEntityId { get; set; }
    public string ItemEntryId { get; set; }
    public int Quantity { get; set; }
}
```

## ICommandHandler Interface

The handler contains all the logic for processing a command:

```csharp
public interface ICommandHandler<T> where T : ICommand
{
    void Handle(T command);
}
```

A typical handler follows this pattern:

```csharp
public class GiveItemCommandHandler : ICommandHandler<GiveItemCommand>
{
    public void Handle(GiveItemCommand command)
    {
        // 1. Validate inputs
        if (string.IsNullOrEmpty(command.TargetEntityId))
        {
            Debug.LogWarning("GiveItemCommand: No target entity specified.");
            return;
        }

        var entry = SparkDatabaseRegistry.GetEntry<ItemEntry>(command.ItemEntryId);
        if (entry == null)
        {
            Debug.LogWarning($"GiveItemCommand: Item not found: {command.ItemEntryId}");
            return;
        }

        // 2. Find the target entity
        if (!SparkEntityRegistry.TryGetEntity(command.TargetEntityId, out SparkEntity entity))
        {
            Debug.LogWarning($"GiveItemCommand: Entity not found: {command.TargetEntityId}");
            return;
        }

        var inventory = entity.GetSparkComponent<InventoryEntity>();
        if (inventory == null)
        {
            Debug.LogWarning($"GiveItemCommand: No inventory on entity.");
            return;
        }

        // 3. Apply the state change
        inventory.AddItem(command.ItemEntryId, command.Quantity);

        // 4. Publish an event
        SparkEventBus.Publish(new ItemGivenEvent(
            command.TargetEntityId,
            command.ItemEntryId,
            command.Quantity));
    }
}
```

The four-step pattern is consistent across all Spark command handlers: validate, find, apply, publish.

## Registering Command Handlers

Register handlers in your plugin's registration method:

```csharp
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void RegisterPlugin()
{
    if (!_hasRegistered)
    {
        _instance = new MyPlugin();
        Spark.RegisterPlugin<IMyPlugin>(_instance);

        // Register command handlers
        Spark.Network?.RegisterHandler<GiveItemCommand>(
            new GiveItemCommandHandler());

        _hasRegistered = true;
    }
}
```

Each command type has exactly one handler. Registering a handler for a type that already has one will overwrite the previous handler.

## Executing Commands

Execute commands through the network provider:

```csharp
Spark.Network?.ExecuteCommand(new GiveItemCommand
{
    TargetEntityId = "player_01",
    ItemEntryId = "iron_sword",
    Quantity = 1
});
```

The `?.` null-conditional is important. If no network provider is set up, the command silently does nothing rather than throwing.

## How Execution Works

When `ExecuteCommand` is called:

1. The network provider receives the command.
2. It looks up the registered handler for the command's type.
3. It invokes `Handle()` on the handler using reflection-based dispatch (in `NetworkProviderBase`).
4. The handler runs its validation and logic.

In single-player mode, `LocalNetworkProvider` executes everything immediately and locally. In multiplayer, a custom network provider could serialize the command and send it to the server.

## LocalNetworkProvider

`LocalNetworkProvider` is the default provider. It:

* Registers automatically if no other provider is set
* Executes all commands immediately and locally
* Reports `IsServer => true` (since single-player is effectively the server)
* Registers built-in handlers like `DragDropCommandHandler`

You don't need to configure it. It just works.

## Command Design Guidelines

Keep commands as simple data containers:

```csharp
// Good: flat, serializable properties
public class HealCommand : ICommand
{
    public string TargetId { get; set; }
    public float Amount { get; set; }
    public string SourceAbilityId { get; set; }
}

// Avoid: complex objects, logic, or Unity references
public class BadCommand : ICommand
{
    public GameObject Target { get; set; }    // Not serializable
    public void Execute() { /* ... */ }       // Logic belongs in handler
}
```

Commands should be serializable for networking compatibility. Use entry IDs (strings) instead of direct object references.

## Best Practices

* One command per action. Don't bundle multiple actions into one command.
* Validate everything in the handler. Don't assume inputs are valid.
* Always publish an event after applying state changes. This keeps the rest of the framework in sync.
* Use descriptive names: `{Verb}{Noun}Command` (e.g., `AcceptQuestCommand`, `CraftItemCommand`).
* Keep commands idempotent where possible. Running the same command twice should produce predictable results.
* Log warnings for validation failures. This makes debugging much easier.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.sparkframework.dev/documentation/developer-guide/core-systems/command-system.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
