Skip to content

Sidebars, UI, Results, and Mashup

@Suppress("unused")
private val sidebarLayout by lazy {
sidebar {
title = "<gold><bold>PILLARS"
refreshSeconds = 1
line { "<yellow>Phase: <white>$phase" }
alive { trackedAlivePlayers.size }
matchTimeLeft(limitSeconds = config.gameDurationSeconds)
perPlayer { viewer -> "<gray>You: <white>${viewer.name}" }
rows({ alivePlayers() }, { player -> "<green>${player.name}" })
}
}

Use helpers:

  • matchClock(label) to show elapsed time.
  • matchTimeLeft(label, limitSeconds) to show remaining time.
  • alive(label) to show alive count.
  • field(label, color) { value } for simple dynamic fields.
  • rows(source, row, empty) for expanding player lists.
  • trackMatchClock(limitSeconds) to tell the sidebar it depends on match clock state.

For simple sidebars, annotations are enough:

@Sidebar(title = "<gold><bold>SPLEEF", refreshSeconds = 1)
class SpleefGame : GameTemplate() {
@SidebarLine(order = 0)
fun aliveLine() = "<green>Alive: ${aliveCount()}"
@SidebarLine(order = 1, spacer = true)
val spacer = ""
@SidebarLine(order = 2, expand = true)
fun playerRows() = alivePlayers().map { "<white>${it.name}" }
}

Use the DSL when you want stronger structure and helper functions; use annotations for compact scoreboards.

MiniModes wraps common player-facing output:

player.send(Chat("<green>Ready."))
player.send(ActionBar("<yellow>Cooldown: 2s"))
player.send(Title("<gold>Victory", "<white>${player.name} wins"))
player.send(SoundEffect("entity.player.levelup"))
players.send(Chat("<aqua>Round starting."))

Messages use MiniMessage-style strings through the platform adapter.

GamePlayer.openMenu accepts a platform-neutral InventoryMenu:

player.openMenu(
InventoryMenu(
title = "<gold>Pick a Kit",
rows = 3,
items = mapOf(
11 to ItemDSL("iron_sword") { name = "<red>Fighter" },
15 to ItemDSL("bow") { name = "<aqua>Archer" },
),
) { slot ->
when (slot) {
11 -> chooseKit(player, "fighter")
15 -> chooseKit(player, "archer")
}
player.closeMenu()
}
)

Use mutable slots and close/click snapshots when the player is allowed to arrange items.

endGame(GameOutcome.winner(player))
endGame(
rankedOutcome(
placements = listOf(listOf(first), listOf(second, third)),
scores = points,
reason = GameCompletionReason.OBJECTIVE,
detail = "Highest score",
metadata = mapOf("mode" to config.mode.displayName),
)
)

Completion reasons are OBJECTIVE, ELIMINATION, TIME_LIMIT, DRAW, CANCELLED, and ERROR.

@Game(name = "Laser Tag", minPlayers = 2, maxPlayers = 16)
@MashupRunnable(
pace = MashupPace.FAST,
family = "arena-combat",
weight = 120,
cooldownRounds = 2,
)
class LaserTagGame : SettingsGameTemplate()

Paces are SLOW, CHILL, FAST, and BLITZ.