Skip to content

Your First Extension

This tutorial builds a tiny elimination game called Gold Rush. Players spawn in a clean void world, receive a gold ingot, and the first player to right-click their ingot wins. It is intentionally simple so the structure is obvious.

  1. Create the extension project

    Add a Gradle subproject, for example extensions:goldrush-extension, and include it in settings.gradle.kts:

    include("extensions:goldrush-extension")

    Create extensions/goldrush-extension/build.gradle.kts:

    plugins {
    kotlin("jvm")
    id("com.gradleup.shadow")
    }
    dependencies {
    compileOnly(project(":miniModes-api"))
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    }
    tasks.shadowJar {
    archiveClassifier.set("")
    archiveBaseName.set(project.name)
    archiveVersion.set(project.version.toString())
    archiveExtension.set("mmx")
    destinationDirectory.set(
    rootProject.file("network/minimodes/plugins/MiniModes/extensions")
    )
    }
  2. Add the game class

    Create extensions/goldrush-extension/src/main/kotlin/io/kerzinator24/miniModes/extensions/goldrush/GoldRushGame.kt:

    package io.kerzinator24.miniModes.extensions.goldrush
    import io.kerzinator24.miniModes.api.Game
    import io.kerzinator24.miniModes.api.GameTemplate
    import io.kerzinator24.miniModes.api.alive
    import io.kerzinator24.miniModes.api.sidebar
    import io.kerzinator24.miniModes.events.EventsScope
    import io.kerzinator24.miniModes.item.GameModes
    import io.kerzinator24.miniModes.item.ItemDSL
    import io.kerzinator24.miniModes.model.GamePlayer
    import io.kerzinator24.miniModes.model.GameWorld
    import io.kerzinator24.miniModes.sendable.Chat
    import io.kerzinator24.miniModes.sendable.Title
    import io.kerzinator24.miniModes.sendable.send
    @Game(
    name = "Gold Rush",
    displayItem = "gold_ingot",
    description = "Right-click the gold first.",
    minPlayers = 2,
    maxPlayers = 8,
    )
    class GoldRushGame : GameTemplate() {
    @Suppress("unused")
    private val sidebarLayout by lazy {
    sidebar {
    title = "<gold><bold>GOLD RUSH"
    alive()
    line { "<yellow>Goal: <white>Right-click gold" }
    line { "<gray>Time: ${matchElapsedSeconds}s" }
    }
    }
    override suspend fun createGameWorldSuspend(): GameWorld =
    worlds.pooled(getStartPlayers())
    override fun configureEvents(events: EventsScope) {
    events.onPlayerInteract {
    if (!isRunning || !requireAlive()) return@onPlayerInteract
    val held = player.inventory.mainHand() ?: return@onPlayerInteract
    if (held.material != "gold_ingot") return@onPlayerInteract
    cancel()
    endGame(player)
    }
    events.onPlayerDeath {
    clearDrops()
    onPlayerDeath(event)
    if (aliveCount() == 1) {
    endGame(alivePlayers().first())
    }
    }
    }
    override fun setup(players: List<GamePlayer>) {
    val world = gameWorld ?: return
    initializeAlivePlayers(players)
    world.spawnPos = pos(world, 0.0, 80.0, 0.0)
    world.fill(pos(world, -6.0, 79.0, -6.0), pos(world, 6.0, 79.0, 6.0), "grass_block")
    val spawns = worlds.spawnsCircle(world.spawnPos, players.size, radius = 5.0)
    worlds.teleport(players.map { it.id }, world, spawns)
    players.forEach { player ->
    player.gameMode = GameModes.ADVENTURE
    player.inventory.clear()
    player.inventory.set(4, ItemDSL("gold_ingot") {
    name = "<gold><bold>Victory Ingot"
    lore += "<gray>Right-click to win."
    glint()
    })
    player.send(Title("<gold>Gold Rush", "<yellow>Right-click first."))
    }
    }
    override fun start() {
    isRunning = true
    startMatchClock()
    startGameCountdown(seconds = 5) {
    gamePlayers.send(Chat("<green>The rush is live!"))
    }
    }
    override fun end(winner: GamePlayer?) {
    val message = winner?.let { "<gold>${it.name} won Gold Rush!" } ?: "<yellow>Gold Rush ended."
    getPlayers().send(Chat(message))
    }
    override fun getMinPlayers() = 2
    override fun getMaxPlayers() = 8
    }
  3. Build the .mmx

    Run:

    Terminal window
    ./gradlew :extensions:goldrush-extension:shadowJar

    The shadowJar task produces an .mmx artifact. If you used the destination from the example, it is copied directly into the MiniModes extension folder.

  4. Load it on the server

    Start or reload the MiniModes server. The loader scans extension artifacts, finds the @Game class, registers the display item and player bounds, and makes the game available in selectors and commands.

The tutorial game never manually:

  • Registers itself with a command or selector.
  • Wraps native player objects.
  • Creates a Bukkit/Fabric world directly.
  • Stores event subscription handles.
  • Cancels the countdown task during shutdown.
  • Removes sidebars.
  • Cleans projectiles, entities, vehicles, or runtime caches.

Those are engine responsibilities. The game only expressed what should happen when setup, start, events, and end occur.

Use SettingsGameTemplate when your mode needs pre-game choices, presets, voting, or runtime setting edits. Use @MashupRunnable when the game should appear in Mashup rotation. Use @GameResource or @GameMap when the game needs packaged maps or datapacks instead of generated arenas.