Tournament Step

A tournament event launches a registered TournamentConfig by its id. Register the configuration during mod initialization, then use a TournamentStep to select the event’s consequences. Opponents, bracket size, rewards, and commentary belong to the configuration.

Event Step

interface TournamentStep {
  kind: 'tournament';
  condition?: string;
  tournamentId: string;
  victory: EventStep[];
  secondPlace?: EventStep[];
  defeat: EventStep[];
  placementBranches?: TournamentPlacementBranch[];
}

interface TournamentPlacementBranch {
  placementMin: number;
  steps: EventStep[];
}

tournamentId must match a configuration registered with window.modAPI.actions.addTournamentConfig. victory, secondPlace, and defeat run according to the player’s final result. An additional placementBranches entry can provide rank-based consequences; the narrowest qualifying cutoff wins.

Registering a Tournament

The roster is a function returning authored EnemyEntity objects. Choose a realm matching those enemies. The competitors below are enemies defined elsewhere in your mod; import them before using this example.

import { TournamentConfig, TournamentStep } from 'afnm-types';

const harvestTournament: TournamentConfig = {
  id: 'myMod_harvestTournament',
  title: 'Village Harvest Tournament',
  realm: 'bodyForging',
  entrantCount: 8,
  roster: () => [villageChampion, travelingMartialArtist, youngProdigy, veteranFighter],
  participantBuffs: [],
  rewards: [
    { placementMin: 1, money: 5000, items: [{ name: 'Village Champion Medal' }] },
    { placementMin: 2, money: 2000, items: [] },
    { placementMin: 8, money: 500, items: [] },
  ],
};

window.modAPI.actions.addTournamentConfig(harvestTournament);

const harvestTournamentStep: TournamentStep = {
  kind: 'tournament',
  tournamentId: harvestTournament.id,
  victory: [{ kind: 'text', text: 'The crowd cheers for the new village champion!' }],
  secondPlace: [{ kind: 'text', text: 'You earn the respect of the village.' }],
  defeat: [{ kind: 'text', text: 'The competition gives you valuable experience.' }],
};

Register every item named in the reward ladder before the tournament runs. The ladder pays cumulatively: the champion receives the top-eight, top-two, and champion tiers. Avoid also paying those rewards in the event branches unless you intend an additional reward.

Configuration Options

Use the exported TournamentConfig from afnm-types as the authoritative interface. Its required fields are id, title, realm, roster, and participantBuffs.

FieldPurpose
entrantCountTotal bracket size, including the player. Set it explicitly for a small tournament; the default is 64.
rosterOnlyUse the saved roster without filling vacancies with anonymous fighters.
participantCharactersConditional entries for named NPCs.
participantBuffsBuffs applied to tournament participants.
guaranteedWinnerAn authored winner override.
entryFeeAmount displayed as the registration fee. Deduct it in the entry event.
rewardsCumulative placement rewards; each tier requires an items array, which may be empty.
announcerIntroConditional opening credentials and optional flavour lines.
announcerCharacterNamed NPC providing commentary.
announcerChatterReplacement announcer line pools.
crowdChatterAdditional occasion-specific crowd lines.
roundKinds'combat', 'crafting', or an array assigning the kind for each round.
randomThirdPlaceRandomly assign third and fourth places to the losing semifinalists.

Conditional Entry and Commentary

Assume arenaCompetitors contains the enemies defined by your mod:

const arenaTournament: TournamentConfig = {
  id: 'myMod_arenaOpen',
  title: 'Shen Henda Arena Open',
  realm: 'coreFormation',
  entrantCount: 8,
  roster: () => arenaCompetitors,
  participantBuffs: [],
  entryFee: 500,
  announcerIntro: {
    credentials: [
      {
        condition: 'myMod_arenaWins >= 3',
        countFlag: 'myMod_arenaWins',
        lines: ['{player} returns with {n} prior victories!'],
      },
      { condition: '1', lines: ['A new challenger enters the arena!'] },
    ],
  },
  rewards: [
    { placementMin: 1, money: 5000, items: [] },
    { placementMin: 8, items: [{ name: 'Participation Ribbon' }] },
  ],
};

window.modAPI.actions.addTournamentConfig(arenaTournament);

const arenaEntry: EventStep = {
  kind: 'choice',
  choices: [{
    text: 'Enter the tournament (500 spirit stones)',
    condition: { kind: 'money', amount: 500 },
    children: [
      { kind: 'money', amount: '-500' },
      {
        kind: 'tournament',
        tournamentId: arenaTournament.id,
        victory: [{ kind: 'flag', flag: 'myMod_arenaWins', value: 'myMod_arenaWins + 1', global: true }],
        defeat: [{ kind: 'text', text: 'You are eliminated from the tournament.' }],
      },
    ],
  }],
};

The entryFee metadata describes the fee; the money event step above performs the deduction. Keep the displayed fee, choice requirement, and deduction consistent.