Skip to content

Actions

Actions define what players (and AI) can do in the game.

Overview

An Action represents an ability like attacking, building, or using a special power:

import {
  BaseAction,
  ActionContext,
  ActionTarget,
  Execution,
} from '@lands.io/mod-sdk';

class MyAction extends BaseAction {
  readonly id = 'my-action';
  readonly label = 'My Action';

  canActivate(context: ActionContext): boolean {
    return context.player.troops() > 1000;
  }

  chooseTarget(context: ActionContext): ActionTarget | null {
    // AI targeting logic
    return null;
  }

  createExecution(context: ActionContext, target: ActionTarget): Execution {
    return new MyExecution(context.player.id(), target);
  }
}

BaseAction Interface

Required Properties

abstract readonly id: string;     // Unique identifier
abstract readonly label: string;  // Display name

Required Methods

// Can this action be used right now?
abstract canActivate(context: ActionContext): boolean;

// AI: Choose a target (return null for no action)
abstract chooseTarget(context: ActionContext): ActionTarget | null;

// Create the execution that modifies game state
abstract createExecution(context: ActionContext, target: ActionTarget): Execution;

Optional Methods

// Cost to use this action (for display)
getCost?(context: ActionContext): number;

ActionContext

Provides context about the current game state:

interface ActionContext {
  game: Game; // The game instance
  player: Player; // The acting player
  tick: number; // Current game tick
}

Registering Actions

Register in registerActions:

registerActions(executor: Executor): void {
  executor.registerAction(new SpecialAttackAction());
  executor.registerAction(new DefendAction());
}

Action Hooks

Modify existing actions without replacing them:

registerActions(executor: Executor): void {
  // Block attacks on teammates
  executor.registerActionHooks
    .attack({
      isValidTarget: (ctx, target, valid) => {
        if (!valid) return false;
        return !this.isTeammate(ctx.player, target);
      }
    })
    .boat({
      isValidTarget: (ctx, target, valid) => {
        if (!valid) return false;
        return !this.isTeammate(ctx.player, target);
      }
    });
}

Example: Team Attack Action

class TeamAttackAction extends BaseAction<Player> {
  readonly id = 'team-attack';
  readonly label = 'Team Attack';

  canActivate(context: ActionContext): boolean {
    // Can attack if we have troops and teammates nearby
    return context.player.troops() > 100 && this.hasNearbyTeammates(context);
  }

  chooseTarget(context: ActionContext): Player | null {
    // Find nearest enemy
    const enemies = context.game
      .players()
      .filter((p) => !this.isTeammate(context.player, p))
      .filter((p) => p.isAlive());

    return this.findNearest(context.player, enemies);
  }

  createExecution(context: ActionContext, target: Player): Execution {
    return new TeamAttackExecution(context.player.id(), target.id());
  }
}