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:
Validation: Every action goes through a handler that can validate inputs before applying changes.
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:
public interface ICommand { }Commands are simple data containers. They hold the parameters needed to perform an action but contain no logic:
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:
A typical handler follows this pattern:
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:
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:
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:
The network provider receives the command.
It looks up the registered handler for the command's type.
It invokes
Handle()on the handler using reflection-based dispatch (inNetworkProviderBase).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:
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.
Last updated
Was this helpful?
