Skip to content

UI Components

Add custom buttons and interfaces to the game.

Overview

The SDK provides ModActionButton for adding custom buttons to the game's attack menu:

import { ModActionButton, ModButtonContext } from '@lands.io/mod-sdk';

class DonateButton extends ModActionButton {
  get icon() {
    return 'donate-icon.png';
  }
  get label() {
    return 'Donate';
  }
  get intentName() {
    return 'donate';
  }

  shouldShow(context: ModButtonContext): boolean {
    return context.myPlayer !== null;
  }
}

ModActionButton

Required Properties

abstract get icon(): string;        // Icon path/URL
abstract get label(): string;       // Button label
abstract get intentName(): string;  // Intent name for handler

Required Methods

// Should this button be visible?
abstract shouldShow(context: ModButtonContext): boolean;

Optional Methods

// Payload to send with the intent
getPayload(context: ModButtonContext): unknown {
  return {};
}

// Is the button disabled (visible but not clickable)?
isDisabled(context: ModButtonContext): boolean {
  return false;
}

Styling Properties

get cost(): number | undefined { return undefined; }
get fillColor(): string | undefined { return undefined; }
get strokeColor(): string | undefined { return undefined; }
get hoverFillColor(): string | undefined { return undefined; }
get hoverStrokeColor(): string | undefined { return undefined; }
get disabledFillColor(): string | undefined { return undefined; }
get disabledStrokeColor(): string | undefined { return undefined; }

ModButtonContext

Context provided to button methods:

interface ModButtonContext {
  game: Game; // Game instance
  clickedCell: { x: number; y: number } | null; // Clicked cell
  myPlayer: Player | null; // Local player
  uiState: unknown; // UI state info
}

Example: Donate Button

import { ModActionButton, ModButtonContext } from '@lands.io/mod-sdk';

export class DonateButton extends ModActionButton {
  get icon() {
    return '/assets/icons/donate.svg';
  }

  get label() {
    return 'Donate Troops';
  }

  get intentName() {
    return 'donate';
  }

  get cost() {
    return undefined; // No gold cost
  }

  get fillColor() {
    return '#3b82f6'; // Blue
  }

  get hoverFillColor() {
    return '#2563eb';
  }

  shouldShow(context: ModButtonContext): boolean {
    // Only show for alive players
    if (!context.myPlayer?.isAlive()) return false;

    // Only show when clicking on a teammate
    if (!context.clickedCell) return false;

    const clickedPlayer = context.game.playerAt(
      context.clickedCell.x,
      context.clickedCell.y
    );

    return (
      clickedPlayer !== null && this.isTeammate(context.myPlayer, clickedPlayer)
    );
  }

  isDisabled(context: ModButtonContext): boolean {
    // Disable if not enough troops
    return (context.myPlayer?.troops() ?? 0) < 100;
  }

  getPayload(context: ModButtonContext) {
    const clickedPlayer = context.game.playerAt(
      context.clickedCell!.x,
      context.clickedCell!.y
    );

    return {
      recipientId: clickedPlayer?.id(),
      percentage: 10,
    };
  }

  private isTeammate(player1: Player, player2: Player): boolean {
    // Check team membership via modState
    return false; // Implement based on your team logic
  }
}

Handling Button Intents

Register an intent handler to process button clicks:

registerIntentHandlers(executor: Executor): void {
  executor.registerModIntentHandler(executor.modId, 'donate', (intent) => {
    const { recipientId, percentage } = intent.payload;

    if (!recipientId || !percentage) {
      return new NoOpExecution();
    }

    return new DonateExecution(
      intent.playerID,
      recipientId,
      percentage,
      this.modState
    );
  });
}