Documentation menu

Match games

One round, last-team-standing - SkyWars, Bedwars, duels. Subclass MatchLifecycle and override the verbs.

A match is the single-round genre: a lobby fills, a countdown runs, everyone plays, someone wins, and the winner is sent home. You subclass MatchLifecycle<Config> and override the verbs - the genre owns the lifecycle around them.

The lifecycle

Every match instance moves through the same states. You never drive this loop; the genre does, and calls your verbs at the right moments.

WAITING  →  COUNTDOWN  →  PLAYING  →  ENDING
 fills      min reached    your      winner paid
 lobby      → ticks down   game      out + sent home
  • WAITING - the instance is open; matchmaking routes players in until minPlayers is reached.
  • COUNTDOWN - countdownSeconds ticks down (announced for you). Late joiners still land until the lobby is full.
  • PLAYING - the countdown hit zero; the genre calls onStart. Deaths flow to onDeath.
  • ENDING - the win condition is met; the genre pays out, calls onEnd, and transfers survivors home.

All of WAITING, COUNTDOWN, the heartbeat that keeps the instance visible to matchmaking, and the transfer-home are inherited. You only write what happens at onStart, onDeath and onEnd.

The verbs

SkyWars.java
public final class SkyWars extends MatchLifecycle<SkyWarsConfig> {

  // PLAYING begins. Teleport everyone to their spawn and start.
  public void onStart(MatchContext ctx) {
    ctx.players().forEach(p -> p.teleport(ctx.spawn(p)));
    if (ctx.config().doubleJump) ctx.players().forEach(this::enableDoubleJump);
  }

  // A player died. Eliminate them; the base checks the win
  // condition and moves the match to ENDING when it's met.
  public void onDeath(PlayerHandle p, MatchContext ctx) {
    ctx.eliminate(p);
    ctx.broadcast(p.name() + " was eliminated - " + ctx.alive().size() + " left");
  }

  // Optional: the match ended. The winner is already paid out
  // and sent home - this is just for flavor.
  public void onEnd(MatchResult result, MatchContext ctx) {
    result.winner().ifPresent(w -> ctx.broadcast(w.name() + " won!"));
  }
}

onStart is the only required verb. onDeath and onEnd have sensible no-op defaults - override them when your game has deaths or an end-of-match flourish.

MatchContext

Every verb receives a MatchContext - the handle to this instance. It bundles the players, the world, your typed config and the operations the genre exposes.

CallDoes
players()everyone in the instance (PlayerHandles)
alive() / spectators()the live and eliminated sets
spawn(player)the arena spawn assigned to that player
eliminate(player)mark out → spectator; triggers the win check
world()the instance’s GameWorld
config()your typed config object
broadcast(msg)message everyone in the instance
scoreboard()the Hud for this instance

PlayerHandle and GameWorld are the portable engine seam - teleport, sendMessage, inventory(), markers(...), and so on. See Ports & portability.

Win conditions

The default win condition is last team standing - when alive() collapses to one team (or one player in a free-for-all), the genre ends the match and the survivor is the winner. eliminate(player) is what feeds it, so for most games you never write win logic at all: call eliminate on death and you’re done.

Need a different rule - first to N points, timer expiry, a captured objective? End the match explicitly:

KingOfTheHill.java
public void onScore(PlayerHandle p, MatchContext ctx) {
  int score = ctx.points().add(p, 1);
  if (score >= ctx.config().target) ctx.win(ctx.teamOf(p));   // ends the match
}

Teams

A match can be free-for-all or team-based; the manifest’s lobby settings decide sizing, and the genre balances players into teams for you. Inside a verb, ctx.teamOf(player), ctx.team(id).members() and ctx.win(team) are how you read and resolve teams. Eliminations are team-aware: a team is out when its last member is, and the win check works on teams, not individuals.

Scoring & stats

Anything you record through the context lands in player data under your game’s namespace automatically - ctx.stats().award(p, "wins", 1) and a win/kill shows up on the player’s profile and your leaderboards without extra plumbing. The match result itself (winner, placements) is recorded for you.

Next