Skip to content

State Management

Manage in-game and persistent data in your mods.

Overview

Mods have access to two state systems:

System Scope Use Cases
modState Per-game Teams, scores, cooldowns
persistentStorage Cross-game ELO, leaderboards, lifetime stats

ModState (In-Game)

Temporary state that lives for one game session.

Setting State

// Simple values
this.modState.set('gameMode', 'team-battle');

// Objects
this.modState.set('teams', { 
  blue: ['player1', 'player2'], 
  red: ['player3', 'player4'] 
});

// Player-specific state
this.modState.set(`score_${playerId}`, 100);

Getting State

const teams = this.modState.get('teams');
const playerScore = this.modState.get(`score_${playerId}`);

Client Synchronization

Automatic Sync

All modState changes are automatically synchronized to connected clients. The frontend can read this state to update the UI.

Player Color Overrides

Set custom colors for players (useful for teams):

this.modState.setPlayerColor(playerId, {
  fill: '#3b82f6',    // Blue fill
  border: '#1d4ed8'   // Darker border
});

PersistentStorage (Cross-Game)

Permanent storage that persists across game sessions.

Incrementing Values

// Increment a player's win count
await this.persistentStorage.increment('players', oderId, 'wins', 1);

// Increment ELO
await this.persistentStorage.increment('players', oderId, 'elo', 25);

Getting Ordered Data

// Get top 10 players by ELO
const leaderboard = await this.persistentStorage.getOrderedDesc(
  'players',  // collection
  'elo',      // field to sort by
  10          // limit
);

// Result: [{ oderId: '...', elo: 2100 }, ...]

Use Cases

  • Leaderboards: Track lifetime wins, kills, territory conquered
  • ELO Ratings: Matchmaking and skill tracking
  • Achievements: Unlock conditions and completion status
  • Clan Stats: Aggregate clan performance

Example: Team State Management

interface Teams {
  blue: string[];
  red: string[];
}

interface TeamStats {
  blue: { kills: number; territory: number };
  red: { kills: number; territory: number };
}

export default class TeamMod extends Mod {

  onGameInit(game: Game, executor: Executor): void {
    // Initialize team structure
    this.modState.set('teams', { blue: [], red: [] } as Teams);
    this.modState.set('stats', {
      blue: { kills: 0, territory: 0 },
      red: { kills: 0, territory: 0 }
    } as TeamStats);
  }

  onPlayerAdded(player: Player): void {
    const teams = this.modState.get('teams') as Teams;

    // Assign to smaller team
    const team = teams.blue.length <= teams.red.length ? 'blue' : 'red';
    teams[team].push(player.id());

    this.modState.set('teams', teams);

    // Set team color
    const colors = {
      blue: { fill: '#3b82f6', border: '#1d4ed8' },
      red: { fill: '#ef4444', border: '#dc2626' }
    };
    this.modState.setPlayerColor(player.id(), colors[team]);
  }

  async onGameEnd(game: Game, winnerId: string): Promise<void> {
    const teams = this.modState.get('teams') as Teams;
    const winningTeam = teams.blue.includes(winnerId) ? 'blue' : 'red';

    // Award ELO to winning team
    for (const oderId of teams[winningTeam]) {
      await this.persistentStorage.increment('players', oderId, 'elo', 25);
      await this.persistentStorage.increment('players', oderId, 'wins', 1);
    }
  }
}