Custom Actions¶
Add new player abilities to your mod.
Overview¶
Actions are abilities that players (and AI) can use. This guide shows how to create a "Rally" action that boosts nearby troops.
Step 1: Define the Action¶
Create actions/rally-action.ts:
import {
BaseAction,
ActionContext,
ActionTarget,
Execution,
} from '@lands.io/mod-sdk';
import { RallyExecution } from '../executions/rally';
export class RallyAction extends BaseAction<ActionTarget> {
readonly id = 'rally';
readonly label = 'Rally Troops';
// Can only rally with enough troops
canActivate(context: ActionContext): boolean {
return context.player.troops() >= 5000;
}
// AI targeting - rally when we have lots of troops
chooseTarget(context: ActionContext): ActionTarget | null {
if (context.player.troops() >= 10000) {
return { type: 'self' };
}
return null;
}
// Create the execution
createExecution(context: ActionContext, target: ActionTarget): Execution {
return new RallyExecution(context.player.id());
}
// Optional: Show cost on UI
getCost(context: ActionContext): number {
return 1000; // Costs 1000 gold
}
}
Step 2: Create the Execution¶
Create executions/rally.ts:
import { Execution, Game } from '@lands.io/mod-sdk';
export class RallyExecution extends Execution {
private static readonly BOOST_PERCENT = 10;
private static readonly GOLD_COST = 1000;
constructor(private playerId: string) {
super();
}
execute(game: Game): void {
const player = game.player(this.playerId);
if (!player || !player.isAlive()) return;
// Check gold cost
if (player.gold() < RallyExecution.GOLD_COST) return;
// Deduct gold
player.removeGold(RallyExecution.GOLD_COST);
// Boost troops by 10%
const boost = Math.floor(
player.troops() * (RallyExecution.BOOST_PERCENT / 100)
);
player.addTroops(boost);
console.log(`${player.name()} rallied troops! +${boost} troops`);
}
}
Step 3: Register the Action¶
In your mod's registerActions:
import { RallyAction } from './actions/rally-action';
export default class MyMod extends Mod {
registerActions(executor: Executor): void {
executor.registerAction(new RallyAction());
}
}
Modifying Existing Actions¶
Use action hooks to modify built-in actions:
registerActions(executor: Executor): void {
executor.registerActionHooks
// Modify attack targeting
.attack({
isValidTarget: (ctx, target, currentValid) => {
// Don't allow attacking players with < 1000 troops
if ('troops' in target && target.troops() < 1000) {
return false;
}
return currentValid;
}
})
// Modify boat action
.boat({
isValidTarget: (ctx, target, currentValid) => {
// Custom boat logic
return currentValid;
}
});
}
Action Types¶
Self-Target Actions¶
Actions that affect only the acting player:
Player-Target Actions¶
Actions that target another player:
chooseTarget(context: ActionContext): Player | null {
return context.game.players()
.filter(p => p.isAlive() && p.id() !== context.player.id())
.sort((a, b) => a.troops() - b.troops())[0] || null;
}
Tile-Target Actions¶
Actions that target a specific tile:
chooseTarget(context: ActionContext): { x: number; y: number } | null {
// Find a strategic tile
return { x: 100, y: 100 };
}
Best Practices¶
Balance
Test your actions thoroughly. Overpowered abilities ruin gameplay.
AI Support
Always implement chooseTarget so AI bots can use your actions.
Feedback
Log action results so players understand what happened.