TypeScript Reference Plugin¶
The tsref plugin embeds live TypeScript source code directly in markdown documentation.
When you update source code, the documentation automatically reflects those changes on rebuild.
Why Use This?¶
- No manual copying - Code is extracted directly from source files
- Always up-to-date - Rebuild docs to sync with implementation
- Reduces errors - No risk of docs drifting from actual code
- Smart targeting - Finds symbols by name, no line numbers needed
- JSDoc aware - Optionally includes/excludes documentation comments
- Auto-dedent - Removes excess indentation from nested code
Quick Start¶
Reference code in any markdown file using HTML comments:
Examples:
```typescript
/**
* Base class for Lands.io mods.
*
* Provides:
* - Lifecycle hooks: onGameInit, onPlayerAdded, onGameEnd
* - Action registration: registerActions
* - Intent handling: registerIntentHandlers
* - State access: modState (in-game), persistentStorage (cross-game)
* - Config attachment: static Config property for game mechanics overrides
*/
export abstract class Mod {
/**
* Optional Config class for game mechanics overrides.
* Implement Partial<Config> to override specific methods.
* The rest will fall back to DefaultConfig.
*/
static Config?: ConfigClass;
/**
* In-game state container for the mod.
* Changes are automatically synced to clients.
* Set by the engine before any hooks are called.
*/
public modState!: ModState;
/**
* Persistent storage for cross-game data (ELO, leaderboards, etc.).
* Set by the engine before any hooks are called.
*/
public persistentStorage!: IModStorage;
/**
* Called by the engine to inject mod state.
* @internal
*/
setModState(state: ModState): void {
this.modState = state;
}
/**
* Called by the engine to inject persistent storage.
* @internal
*/
setPersistentStorage(storage: IModStorage): void {
this.persistentStorage = storage;
}
// ═══════════════════════════════════════════════════════════════════════════
// LIFECYCLE HOOKS - Override these in your mod
// ═══════════════════════════════════════════════════════════════════════════
/**
* Called only during fresh initialization (never during snapshot restore).
*
* Use this to deterministically set up a scenario:
* - Create NPC players
* - Paint initial territories
* - Override deterministic human spawn location(s)
*
* This hook runs before any bot/fake-human spawning and before gameplay starts.
*
* IMPORTANT: ScenarioSetup is init-only. Calls after this hook will throw.
*/
onScenarioSetup?(scenario: ScenarioSetup, game: Game, executor: Executor): void;
/**
* Called after game initialization, before gameplay starts.
* Use this to set up teams, initial state, etc.
*
* @param game - The game instance
* @param executor - The executor for adding executions
*/
onGameInit?(game: Game, executor: Executor): void;
/**
* Called once per engine tick.
*
* Determinism rules:
* - Do not use Date/time, Math.random, or other non-deterministic sources.
* - Drive behavior from game state + modState only.
*/
onTick?(game: Game, executor: Executor): void;
/**
* Called when a player spawns.
* Use this to assign teams, set up per-player state, etc.
*
* @param player - The player that was added
*/
onPlayerAdded?(player: Player): void;
/**
* Called when the game ends (winner determined).
* Use this for post-game processing, ELO calculations, etc.
*
* @param game - The game instance
* @param winnerIds - Array of PlayerIDs of the winners
* @param stats - Game statistics for all players
*/
onGameEnd?(game: Game, winnerIds: PlayerID[], stats: unknown): void;
/**
* Called when a player loses their last tile and is eliminated from the game.
* The eliminator is the player who conquered the final tile, or null if the
* elimination was caused by a non-combat mechanic (e.g. tile relinquish).
*/
onPlayerEliminated?(game: Game, event: PlayerEliminatedEvent): void;
/**
* Called when territory is absorbed from one player into another due to
* cutoff/encircled resolution.
*/
onTerritoryAbsorbed?(game: Game, event: TerritoryAbsorbedEvent): void;
/**
* Optional custom win condition logic.
* If provided, this completely replaces the default win check.
* Return an array of winners to end the game, or null/undefined if no winner yet.
*
* Called periodically during gameplay (every 10 ticks by default).
*
* @param game - The game instance
* @returns Array of winning players, or null if game should continue
*
* @example
* ```typescript
* // Team mode: Team wins when controlling >80% of territory
* getWinners(game) {
* const teams = this.modState.get('teams');
* const territoryByTeam = { blue: 0, red: 0 };
*
* for (const player of game.players()) {
* const team = getPlayerTeam(game, player.id());
* if (team) territoryByTeam[team] += player.numTilesOwned();
* }
*
* const totalTiles = game.numLandTiles();
*
* for (const [teamName, territory] of Object.entries(territoryByTeam)) {
* if ((territory / totalTiles) * 100 > 80) {
* // Return all players on winning team
* return teams[teamName].map(id => game.player(id));
* }
* }
*
* return null; // No winner yet
* }
* ```
*/
getWinners?(game: Game): Player[] | null;
// ═══════════════════════════════════════════════════════════════════════════
// REGISTRATION HOOKS - Override these in your mod
// ═══════════════════════════════════════════════════════════════════════════
/**
* Register custom actions or override default actions.
* Called during game initialization.
*
* @param executor - The executor for registering actions
*
* @example
* ```typescript
* registerActions(executor) {
* // Override default attack action with team-aware version
* executor.registerAction(new TeamAttackAction());
* }
* ```
*/
registerActions?(executor: Executor): void;
/**
* Register custom intent handlers for mod-specific player actions.
* Called during game initialization.
*
* @param executor - The executor for registering intent handlers
*
* @example
* ```typescript
* registerIntentHandlers(executor) {
* executor.registerModIntentHandler('my-mod', 'donate', (intent) => {
* return new DonateExecution(intent.playerID, intent.payload);
* });
* }
* ```
*/
registerIntentHandlers?(executor: Executor): void;
}
attackAmount(attacker: Player, defender: Player | TerraNullius): number {
if (attacker.type() === PlayerType.Bot) {
return attacker.troops() / 20;
}
return attacker.troops() / 5;
}
## Syntax Reference
| Syntax | What it extracts |
| ----------------------- | ------------------------------------------ |
| `file="path.ts"` | Required. Path relative to `src/` |
| `symbol="Name"` | Class, interface, type, function, or const |
| `symbol="Class.member"` | Method or property of a class |
| `docstring="false"` | Exclude JSDoc comment above symbol |
## Reference Types
### Whole File
```markdown
```typescript
/**
* Configuration types and utilities for mod development.
*
* Mods implement Partial<Config> to override specific game mechanics.
* Attach your Config class to your Mod via the static Config property.
* For lifecycle hooks (onGameInit, onPlayerAdded, etc.), use the Mod class.
*/
import type {
Config,
PartialConfig,
ConfigClass,
Player,
TerraNullius,
UnitInfo,
} from '../types/game.js';
import type { Tick, GameConfig } from '../types/schemas.js';
import {
UnitType,
Difficulty,
PlayerType,
GameType,
} from '../types/schemas.js';
// Re-export Config types for convenience
export type { Config, PartialConfig, ConfigClass };
/**
* Default game configuration used as the base.
* Mods don't need to extend this - instead implement Partial<Config>
* and attach it to your Mod class via the static Config property.
*
* @example
* ```typescript
* import { Mod, Config } from '@lands.io/mod-sdk';
*
* class MyConfig implements Partial<Config> {
* // Override only what you need - rest falls back to DefaultConfig
* attackAmount(attacker, defender) {
* return attacker.troops() / 10;
* }
* }
*
* export default class MyMod extends Mod {
* static Config = MyConfig;
* // ... lifecycle hooks
* }
* ```
*/
export class DefaultConfig implements Config {
constructor(protected _gameConfig: GameConfig) {}
// ============================================================
// Game configuration (override to customize)
// ============================================================
gameConfig(): GameConfig {
return this._gameConfig;
}
spawnImmunityDuration(): Tick {
return 5 * 10;
}
numSpawnPhaseTurns(): number {
return this._gameConfig.gameType === GameType.Singleplayer ? 70 : 150;
}
percentageTilesOwnedToWin(): number {
return 80;
}
bots(): number {
return this._gameConfig.bots;
}
numBots(): number {
return this.bots();
}
fakeHumans(): number {
return this._gameConfig.fakeHumans || 0;
}
infiniteGold(): boolean {
return this._gameConfig.infiniteGold;
}
infiniteTroops(): boolean {
return this._gameConfig.infiniteTroops;
}
// ============================================================
// Player settings (override to customize)
// ============================================================
startManpower(player: Player): number {
const maxPop = this.maxPopulation(player);
const ratio = player.type() === PlayerType.Bot ? 0.5 : 0.75;
return maxPop * ratio;
}
maxPopulation(player: Player): number {
const maxPop = this.infiniteTroops()
? 1_000_000_000
: Math.pow(player.numTilesOwned(), 0.6) * 100;
return maxPop;
}
populationIncreaseRate(player: Player): number {
const max = this.maxPopulation(player);
const toAdd = max * 0.0007;
return Math.min(player.population() + toAdd, max) - player.population();
}
goldAdditionRate(player: Player): number {
return Math.sqrt(player.workers() * player.numTilesOwned()) / 200;
}
troopAdjustmentRate(player: Player): number {
const maxDiff = this.maxPopulation(player) / 1000;
const target = player.population() * player.targetTroopRatio();
const diff = target - player.troops();
if (Math.abs(diff) < maxDiff) {
return diff;
}
const adjustment = maxDiff * Math.sign(diff);
return adjustment < 0 ? adjustment * 5 : adjustment;
}
// ============================================================
// Combat settings (override to customize)
// ============================================================
attackAmount(attacker: Player, defender: Player | TerraNullius): number {
if (attacker.type() === PlayerType.Bot) {
return attacker.troops() / 20;
}
return attacker.troops() / 5;
}
attackTilesPerTick(
attackTroops: number,
attacker: Player,
defender: Player | TerraNullius,
numAdjacentTilesWithEnemy: number,
): number {
if ('isPlayer' in defender && defender.isPlayer()) {
return (
within(((5 * attackTroops) / defender.troops()) * 2, 0.01, 0.5) *
numAdjacentTilesWithEnemy *
3
);
}
const troopFactor = Math.min(3, Math.max(1, Math.sqrt(attackTroops / 500) * 3));
return numAdjacentTilesWithEnemy * 2 * troopFactor;
}
// ============================================================
// Unit costs (override to customize)
// ============================================================
unitInfo(type: UnitType): UnitInfo {
// Default costs - override for custom pricing
const defaultInfo: UnitInfo = {
cost: () => 100_000,
territoryBound: true,
};
return defaultInfo;
}
// ============================================================
// Difficulty settings
// ============================================================
difficultyModifier(difficulty: Difficulty): number {
switch (difficulty) {
case Difficulty.Easy:
return 1;
case Difficulty.Medium:
return 3;
case Difficulty.Hard:
return 9;
case Difficulty.Impossible:
return 18;
}
}
}
// ============================================================
// Utility functions
// ============================================================
/**
* Clamp a value between min and max
*/
export function within(value: number, min: number, max: number): number {
return Math.max(min, Math.min(max, value));
}
/**
* TypeScript exhaustiveness check helper
*/
export function assertNever(x: never): never {
throw new Error(`Unexpected value: ${x}`);
}
### Class or Interface
```markdown
```typescript
/**
* Base class for Lands.io mods.
*
* Provides:
* - Lifecycle hooks: onGameInit, onPlayerAdded, onGameEnd
* - Action registration: registerActions
* - Intent handling: registerIntentHandlers
* - State access: modState (in-game), persistentStorage (cross-game)
* - Config attachment: static Config property for game mechanics overrides
*/
export abstract class Mod {
/**
* Optional Config class for game mechanics overrides.
* Implement Partial<Config> to override specific methods.
* The rest will fall back to DefaultConfig.
*/
static Config?: ConfigClass;
/**
* In-game state container for the mod.
* Changes are automatically synced to clients.
* Set by the engine before any hooks are called.
*/
public modState!: ModState;
/**
* Persistent storage for cross-game data (ELO, leaderboards, etc.).
* Set by the engine before any hooks are called.
*/
public persistentStorage!: IModStorage;
/**
* Called by the engine to inject mod state.
* @internal
*/
setModState(state: ModState): void {
this.modState = state;
}
/**
* Called by the engine to inject persistent storage.
* @internal
*/
setPersistentStorage(storage: IModStorage): void {
this.persistentStorage = storage;
}
// ═══════════════════════════════════════════════════════════════════════════
// LIFECYCLE HOOKS - Override these in your mod
// ═══════════════════════════════════════════════════════════════════════════
/**
* Called only during fresh initialization (never during snapshot restore).
*
* Use this to deterministically set up a scenario:
* - Create NPC players
* - Paint initial territories
* - Override deterministic human spawn location(s)
*
* This hook runs before any bot/fake-human spawning and before gameplay starts.
*
* IMPORTANT: ScenarioSetup is init-only. Calls after this hook will throw.
*/
onScenarioSetup?(scenario: ScenarioSetup, game: Game, executor: Executor): void;
/**
* Called after game initialization, before gameplay starts.
* Use this to set up teams, initial state, etc.
*
* @param game - The game instance
* @param executor - The executor for adding executions
*/
onGameInit?(game: Game, executor: Executor): void;
/**
* Called once per engine tick.
*
* Determinism rules:
* - Do not use Date/time, Math.random, or other non-deterministic sources.
* - Drive behavior from game state + modState only.
*/
onTick?(game: Game, executor: Executor): void;
/**
* Called when a player spawns.
* Use this to assign teams, set up per-player state, etc.
*
* @param player - The player that was added
*/
onPlayerAdded?(player: Player): void;
/**
* Called when the game ends (winner determined).
* Use this for post-game processing, ELO calculations, etc.
*
* @param game - The game instance
* @param winnerIds - Array of PlayerIDs of the winners
* @param stats - Game statistics for all players
*/
onGameEnd?(game: Game, winnerIds: PlayerID[], stats: unknown): void;
/**
* Called when a player loses their last tile and is eliminated from the game.
* The eliminator is the player who conquered the final tile, or null if the
* elimination was caused by a non-combat mechanic (e.g. tile relinquish).
*/
onPlayerEliminated?(game: Game, event: PlayerEliminatedEvent): void;
/**
* Called when territory is absorbed from one player into another due to
* cutoff/encircled resolution.
*/
onTerritoryAbsorbed?(game: Game, event: TerritoryAbsorbedEvent): void;
/**
* Optional custom win condition logic.
* If provided, this completely replaces the default win check.
* Return an array of winners to end the game, or null/undefined if no winner yet.
*
* Called periodically during gameplay (every 10 ticks by default).
*
* @param game - The game instance
* @returns Array of winning players, or null if game should continue
*
* @example
* ```typescript
* // Team mode: Team wins when controlling >80% of territory
* getWinners(game) {
* const teams = this.modState.get('teams');
* const territoryByTeam = { blue: 0, red: 0 };
*
* for (const player of game.players()) {
* const team = getPlayerTeam(game, player.id());
* if (team) territoryByTeam[team] += player.numTilesOwned();
* }
*
* const totalTiles = game.numLandTiles();
*
* for (const [teamName, territory] of Object.entries(territoryByTeam)) {
* if ((territory / totalTiles) * 100 > 80) {
* // Return all players on winning team
* return teams[teamName].map(id => game.player(id));
* }
* }
*
* return null; // No winner yet
* }
* ```
*/
getWinners?(game: Game): Player[] | null;
// ═══════════════════════════════════════════════════════════════════════════
// REGISTRATION HOOKS - Override these in your mod
// ═══════════════════════════════════════════════════════════════════════════
/**
* Register custom actions or override default actions.
* Called during game initialization.
*
* @param executor - The executor for registering actions
*
* @example
* ```typescript
* registerActions(executor) {
* // Override default attack action with team-aware version
* executor.registerAction(new TeamAttackAction());
* }
* ```
*/
registerActions?(executor: Executor): void;
/**
* Register custom intent handlers for mod-specific player actions.
* Called during game initialization.
*
* @param executor - The executor for registering intent handlers
*
* @example
* ```typescript
* registerIntentHandlers(executor) {
* executor.registerModIntentHandler('my-mod', 'donate', (intent) => {
* return new DonateExecution(intent.playerID, intent.payload);
* });
* }
* ```
*/
registerIntentHandlers?(executor: Executor): void;
}
### Class Method
```markdown
<!-- tsref: Could not find code for file="examples/team-mode/gameplay.ts" symbol="TeamMod.registerActions" -->
Function¶
Type Alias¶
Constant¶
Controlling JSDoc Comments¶
By default, JSDoc comments above symbols are included. To exclude:
```typescript
export abstract class Mod {
/**
* Optional Config class for game mechanics overrides.
* Implement Partial<Config> to override specific methods.
* The rest will fall back to DefaultConfig.
*/
static Config?: ConfigClass;
/**
* In-game state container for the mod.
* Changes are automatically synced to clients.
* Set by the engine before any hooks are called.
*/
public modState!: ModState;
/**
* Persistent storage for cross-game data (ELO, leaderboards, etc.).
* Set by the engine before any hooks are called.
*/
public persistentStorage!: IModStorage;
/**
* Called by the engine to inject mod state.
* @internal
*/
setModState(state: ModState): void {
this.modState = state;
}
/**
* Called by the engine to inject persistent storage.
* @internal
*/
setPersistentStorage(storage: IModStorage): void {
this.persistentStorage = storage;
}
// ═══════════════════════════════════════════════════════════════════════════
// LIFECYCLE HOOKS - Override these in your mod
// ═══════════════════════════════════════════════════════════════════════════
/**
* Called only during fresh initialization (never during snapshot restore).
*
* Use this to deterministically set up a scenario:
* - Create NPC players
* - Paint initial territories
* - Override deterministic human spawn location(s)
*
* This hook runs before any bot/fake-human spawning and before gameplay starts.
*
* IMPORTANT: ScenarioSetup is init-only. Calls after this hook will throw.
*/
onScenarioSetup?(scenario: ScenarioSetup, game: Game, executor: Executor): void;
/**
* Called after game initialization, before gameplay starts.
* Use this to set up teams, initial state, etc.
*
* @param game - The game instance
* @param executor - The executor for adding executions
*/
onGameInit?(game: Game, executor: Executor): void;
/**
* Called once per engine tick.
*
* Determinism rules:
* - Do not use Date/time, Math.random, or other non-deterministic sources.
* - Drive behavior from game state + modState only.
*/
onTick?(game: Game, executor: Executor): void;
/**
* Called when a player spawns.
* Use this to assign teams, set up per-player state, etc.
*
* @param player - The player that was added
*/
onPlayerAdded?(player: Player): void;
/**
* Called when the game ends (winner determined).
* Use this for post-game processing, ELO calculations, etc.
*
* @param game - The game instance
* @param winnerIds - Array of PlayerIDs of the winners
* @param stats - Game statistics for all players
*/
onGameEnd?(game: Game, winnerIds: PlayerID[], stats: unknown): void;
/**
* Called when a player loses their last tile and is eliminated from the game.
* The eliminator is the player who conquered the final tile, or null if the
* elimination was caused by a non-combat mechanic (e.g. tile relinquish).
*/
onPlayerEliminated?(game: Game, event: PlayerEliminatedEvent): void;
/**
* Called when territory is absorbed from one player into another due to
* cutoff/encircled resolution.
*/
onTerritoryAbsorbed?(game: Game, event: TerritoryAbsorbedEvent): void;
/**
* Optional custom win condition logic.
* If provided, this completely replaces the default win check.
* Return an array of winners to end the game, or null/undefined if no winner yet.
*
* Called periodically during gameplay (every 10 ticks by default).
*
* @param game - The game instance
* @returns Array of winning players, or null if game should continue
*
* @example
* ```typescript
* // Team mode: Team wins when controlling >80% of territory
* getWinners(game) {
* const teams = this.modState.get('teams');
* const territoryByTeam = { blue: 0, red: 0 };
*
* for (const player of game.players()) {
* const team = getPlayerTeam(game, player.id());
* if (team) territoryByTeam[team] += player.numTilesOwned();
* }
*
* const totalTiles = game.numLandTiles();
*
* for (const [teamName, territory] of Object.entries(territoryByTeam)) {
* if ((territory / totalTiles) * 100 > 80) {
* // Return all players on winning team
* return teams[teamName].map(id => game.player(id));
* }
* }
*
* return null; // No winner yet
* }
* ```
*/
getWinners?(game: Game): Player[] | null;
// ═══════════════════════════════════════════════════════════════════════════
// REGISTRATION HOOKS - Override these in your mod
// ═══════════════════════════════════════════════════════════════════════════
/**
* Register custom actions or override default actions.
* Called during game initialization.
*
* @param executor - The executor for registering actions
*
* @example
* ```typescript
* registerActions(executor) {
* // Override default attack action with team-aware version
* executor.registerAction(new TeamAttackAction());
* }
* ```
*/
registerActions?(executor: Executor): void;
/**
* Register custom intent handlers for mod-specific player actions.
* Called during game initialization.
*
* @param executor - The executor for registering intent handlers
*
* @example
* ```typescript
* registerIntentHandlers(executor) {
* executor.registerModIntentHandler('my-mod', 'donate', (intent) => {
* return new DonateExecution(intent.playerID, intent.payload);
* });
* }
* ```
*/
registerIntentHandlers?(executor: Executor): void;
}
## Configuration
The plugin is configured in `mkdocs.yml`:
```yaml
plugins:
- tsref:
src_base: '../src' # Source files location (relative to docs/)
default_docstring: true # Include JSDoc by default
language: typescript # Code block language
Workflow¶
- Write code with JSDoc comments in TypeScript
- Add references in markdown using
<!-- tsref: Could not find code for ... -->syntax - Serve docs with
pnpm nx run @lands.io/mod-sdk:docs:serve - Edit source code - changes appear on page reload
When you're done, build the final docs:
Examples in Practice¶
Document a Class with Description¶
# The Mod Class
The base class for all mods. Extend this to create your mod.
```typescript
/**
* Base class for Lands.io mods.
*
* Provides:
* - Lifecycle hooks: onGameInit, onPlayerAdded, onGameEnd
* - Action registration: registerActions
* - Intent handling: registerIntentHandlers
* - State access: modState (in-game), persistentStorage (cross-game)
* - Config attachment: static Config property for game mechanics overrides
*/
export abstract class Mod {
/**
* Optional Config class for game mechanics overrides.
* Implement Partial<Config> to override specific methods.
* The rest will fall back to DefaultConfig.
*/
static Config?: ConfigClass;
/**
* In-game state container for the mod.
* Changes are automatically synced to clients.
* Set by the engine before any hooks are called.
*/
public modState!: ModState;
/**
* Persistent storage for cross-game data (ELO, leaderboards, etc.).
* Set by the engine before any hooks are called.
*/
public persistentStorage!: IModStorage;
/**
* Called by the engine to inject mod state.
* @internal
*/
setModState(state: ModState): void {
this.modState = state;
}
/**
* Called by the engine to inject persistent storage.
* @internal
*/
setPersistentStorage(storage: IModStorage): void {
this.persistentStorage = storage;
}
// ═══════════════════════════════════════════════════════════════════════════
// LIFECYCLE HOOKS - Override these in your mod
// ═══════════════════════════════════════════════════════════════════════════
/**
* Called only during fresh initialization (never during snapshot restore).
*
* Use this to deterministically set up a scenario:
* - Create NPC players
* - Paint initial territories
* - Override deterministic human spawn location(s)
*
* This hook runs before any bot/fake-human spawning and before gameplay starts.
*
* IMPORTANT: ScenarioSetup is init-only. Calls after this hook will throw.
*/
onScenarioSetup?(scenario: ScenarioSetup, game: Game, executor: Executor): void;
/**
* Called after game initialization, before gameplay starts.
* Use this to set up teams, initial state, etc.
*
* @param game - The game instance
* @param executor - The executor for adding executions
*/
onGameInit?(game: Game, executor: Executor): void;
/**
* Called once per engine tick.
*
* Determinism rules:
* - Do not use Date/time, Math.random, or other non-deterministic sources.
* - Drive behavior from game state + modState only.
*/
onTick?(game: Game, executor: Executor): void;
/**
* Called when a player spawns.
* Use this to assign teams, set up per-player state, etc.
*
* @param player - The player that was added
*/
onPlayerAdded?(player: Player): void;
/**
* Called when the game ends (winner determined).
* Use this for post-game processing, ELO calculations, etc.
*
* @param game - The game instance
* @param winnerIds - Array of PlayerIDs of the winners
* @param stats - Game statistics for all players
*/
onGameEnd?(game: Game, winnerIds: PlayerID[], stats: unknown): void;
/**
* Called when a player loses their last tile and is eliminated from the game.
* The eliminator is the player who conquered the final tile, or null if the
* elimination was caused by a non-combat mechanic (e.g. tile relinquish).
*/
onPlayerEliminated?(game: Game, event: PlayerEliminatedEvent): void;
/**
* Called when territory is absorbed from one player into another due to
* cutoff/encircled resolution.
*/
onTerritoryAbsorbed?(game: Game, event: TerritoryAbsorbedEvent): void;
/**
* Optional custom win condition logic.
* If provided, this completely replaces the default win check.
* Return an array of winners to end the game, or null/undefined if no winner yet.
*
* Called periodically during gameplay (every 10 ticks by default).
*
* @param game - The game instance
* @returns Array of winning players, or null if game should continue
*
* @example
* ```typescript
* // Team mode: Team wins when controlling >80% of territory
* getWinners(game) {
* const teams = this.modState.get('teams');
* const territoryByTeam = { blue: 0, red: 0 };
*
* for (const player of game.players()) {
* const team = getPlayerTeam(game, player.id());
* if (team) territoryByTeam[team] += player.numTilesOwned();
* }
*
* const totalTiles = game.numLandTiles();
*
* for (const [teamName, territory] of Object.entries(territoryByTeam)) {
* if ((territory / totalTiles) * 100 > 80) {
* // Return all players on winning team
* return teams[teamName].map(id => game.player(id));
* }
* }
*
* return null; // No winner yet
* }
* ```
*/
getWinners?(game: Game): Player[] | null;
// ═══════════════════════════════════════════════════════════════════════════
// REGISTRATION HOOKS - Override these in your mod
// ═══════════════════════════════════════════════════════════════════════════
/**
* Register custom actions or override default actions.
* Called during game initialization.
*
* @param executor - The executor for registering actions
*
* @example
* ```typescript
* registerActions(executor) {
* // Override default attack action with team-aware version
* executor.registerAction(new TeamAttackAction());
* }
* ```
*/
registerActions?(executor: Executor): void;
/**
* Register custom intent handlers for mod-specific player actions.
* Called during game initialization.
*
* @param executor - The executor for registering intent handlers
*
* @example
* ```typescript
* registerIntentHandlers(executor) {
* executor.registerModIntentHandler('my-mod', 'donate', (intent) => {
* return new DonateExecution(intent.playerID, intent.payload);
* });
* }
* ```
*/
registerIntentHandlers?(executor: Executor): void;
}
### Document Multiple Methods
```markdown
## Lifecycle Hooks
### onGameInit
Called when the game initializes:
<!-- tsref: Could not find code for file="examples/team-mode/gameplay.ts" symbol="TeamMod.onGameInit" -->
### onPlayerAdded
Called when a player spawns:
<!-- tsref: Could not find code for file="examples/team-mode/gameplay.ts" symbol="TeamMod.onPlayerAdded" -->
Mix Live Code with Manual Examples¶
The default implementation:
```typescript
attackAmount(attacker: Player, defender: Player | TerraNullius): number {
if (attacker.type() === PlayerType.Bot) {
return attacker.troops() / 20;
}
return attacker.troops() / 5;
}
```
Override it in your config:
```typescript
attackAmount(attacker, defender) {
return attacker.troops() / 10; // Custom formula
}
```
## Troubleshooting
### Symbol Not Found
If you see `<!-- tsref: Could not find code for ... -->`:
1. Check symbol name matches exactly (case-sensitive)
2. For class members, use `ClassName.memberName` syntax
3. Verify file path is correct relative to `src/`
4. Ensure the symbol is exported (`export class/function/const`)
### Wrong Code Extracted
1. Make sure the symbol name is unique in the file
2. For methods, always use `Class.method` syntax
### Missing JSDoc
1. Check that `docstring="false"` isn't set
2. JSDoc must be directly above symbol (no blank lines between)