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
minPlayersis reached. - COUNTDOWN -
countdownSecondsticks down (announced for you). Late joiners still land until the lobby is full. - PLAYING - the countdown hit zero; the genre calls
onStart. Deaths flow toonDeath. - 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
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.
| Call | Does |
|---|---|
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:
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
- Manifest & config - size the lobby and type your config.
- Ports & portability - the
PlayerHandle/GameWorldsurface and the native escape hatch. - Party games - the other genre.