Executions¶
Executions are the core mechanism for modifying game state.
Overview¶
An Execution represents a game state change that will be applied:
import { Execution, Game } from '@lands.io/mod-sdk';
class DonateExecution extends Execution {
constructor(
private donorId: string,
private recipientId: string,
private amount: number
) {
super();
}
execute(game: Game): void {
const donor = game.player(this.donorId);
const recipient = game.player(this.recipientId);
if (donor && recipient) {
donor.removeTroops(this.amount);
recipient.addTroops(this.amount);
}
}
}
Execution Lifecycle¶
- Creation - Action or intent handler creates the execution
- Validation - Engine validates the execution is legal
- Execution -
execute()is called to modify game state - Sync - Changes are broadcast to all clients
Creating Executions¶
From Actions¶
class MyAction extends BaseAction {
createExecution(context: ActionContext, target: ActionTarget): Execution {
return new MyExecution(context.player.id(), target);
}
}
From Intent Handlers¶
registerIntentHandlers(executor: Executor): void {
executor.registerModIntentHandler(executor.modId, 'donate', (intent) => {
const { recipientId, amount } = intent.payload;
return new DonateExecution(intent.playerID, recipientId, amount);
});
}
NoOpExecution¶
Return this when you want to acknowledge an intent but do nothing:
import { NoOpExecution } from '@lands.io/mod-sdk';
executor.registerModIntentHandler(executor.modId, 'donate', (intent) => {
if (!isValidDonation(intent)) {
return new NoOpExecution(); // Valid request, but we choose to do nothing
}
return new DonateExecution(/* ... */);
});
Example: Full Donate Execution¶
import { Execution, Game, ModState } from '@lands.io/mod-sdk';
export class DonateExecution extends Execution {
private static readonly COOLDOWN_TICKS = 100;
constructor(
private donorId: string,
private recipientId: string,
private percentage: number,
private modState: ModState
) {
super();
}
execute(game: Game): void {
const donor = game.player(this.donorId);
const recipient = game.player(this.recipientId);
if (!donor || !recipient) return;
if (!donor.isAlive() || !recipient.isAlive()) return;
// Check cooldown
const cooldowns = this.modState.get('cooldowns') || {};
const lastDonation = cooldowns[this.donorId] || 0;
if (game.tick() - lastDonation < DonateExecution.COOLDOWN_TICKS) {
return;
}
// Calculate donation amount
const donationAmount = Math.floor(donor.troops() * (this.percentage / 100));
if (donationAmount <= 0) return;
// Execute the transfer
donor.removeTroops(donationAmount);
recipient.addTroops(donationAmount);
// Update cooldown
cooldowns[this.donorId] = game.tick();
this.modState.set('cooldowns', cooldowns);
}
}
Best Practices¶
Validation
Always validate inputs in execute(). Players may have died or state may have changed since the execution was created.
Idempotency
Executions should be safe to replay. Avoid side effects outside of game state.
No Async
execute() is synchronous. Use persistentStorage in lifecycle hooks for async operations.