Skip to content

Lifecycle and Runtime

  1. Discover The loader scans extension JARs for `@Game` classes and resource annotations.
  2. Configure The engine binds runtime, events, commands, dialog settings, teams, and leader state.
  3. Prepare `createGameWorldSuspend`, additional worlds, settings finalization, and setup happen in order.
  4. Run Game code reacts through normalized handles while tasks and subscriptions stay scoped.
  5. Dispose End/cancel paths clear prompts, tasks, events, entities, projectiles, vehicles, and worlds.
@Game(
name = "Pillars",
displayItem = "bedrock",
description = "Outlast everyone else on your own pillar.",
minPlayers = 2,
maxPlayers = 8,
pack = "classic",
category = "survival",
)
class PillarsGame : SettingsGameTemplate()

Use resources on @Game for simple embedded resource paths, or prefer @GameResource / @GameMap for typed resource metadata.

Core lifecycle methods:

abstract fun setup(players: List<GamePlayer>)
abstract fun start()
abstract fun end(winner: GamePlayer? = null)
abstract fun getMinPlayers(): Int
abstract fun getMaxPlayers(): Int

Optional suspend hooks:

override suspend fun createGameWorldSuspend(): GameWorld? = worlds.pooled(getStartPlayers())
override suspend fun prepareAdditionalWorldsSuspend(players: List<GamePlayer>) {}
override fun preparesAdditionalWorldsAfterTeams(): Boolean = false

The engine calls these in the correct phase. You normally do not construct GameRuntime yourself.

MiniModes stores participants and tracked alive players by UUID:

initializeAlivePlayers(players, initialLives = 3)
trackAlivePlayer(player)
setPlayerLives(player, lives = 2)
consumePlayerLives(player)
eliminateTrackedPlayer(player)
aliveCount()
alivePlayers()
isParticipant(player)
isAlive(player)

Use these helpers so built-in event behavior, item interaction gating, sidebars, and end checks share one state model.

startMatchClock(timeLimitSeconds = 600)
val heartbeat = every(20L) {
if (aliveCount() <= 1) endGame(alivePlayers().firstOrNull())
}
after(100L) {
gamePlayers.send(Chat("<yellow>Five seconds passed."))
}

startGameCountdown provides a standard match-start flow with optional movement locking:

startGameCountdown(seconds = 5, lockMovement = true) {
isRunning = true
}

Override onCountdownStart, onCountdownTick, and onCountdownComplete for custom announcements.

prompt(
player = leader,
type = GameInputTypes.integer(min = 1, max = 10),
message = "<yellow>How many rounds?",
lockMovement = true,
) { respondent, rounds ->
respondent.send(Chat("<green>Rounds set to $rounds"))
}

Use endGame(winner) for simple games. Use GameOutcome when rankings, ties, scores, or special completion reasons matter:

endGame(
rankedOutcome(
placements = listOf(listOf(first), listOf(second, third)),
scores = scoreByPlayer,
reason = GameCompletionReason.TIME_LIMIT,
detail = "Most coins after 10 minutes",
)
)

performEnd sets isRunning = false, clears input state, stops the match clock, disposes runtime objects, calls your end, and clears participants. finalizeShutdown cleans game worlds.