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.
-
Create the extension project
Add a Gradle subproject, for example
extensions:goldrush-extension, and include it insettings.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"))} -
Add the game class
Create
extensions/goldrush-extension/src/main/kotlin/io/kerzinator24/miniModes/extensions/goldrush/GoldRushGame.kt:package io.kerzinator24.miniModes.extensions.goldrushimport io.kerzinator24.miniModes.api.Gameimport io.kerzinator24.miniModes.api.GameTemplateimport io.kerzinator24.miniModes.api.aliveimport io.kerzinator24.miniModes.api.sidebarimport io.kerzinator24.miniModes.events.EventsScopeimport io.kerzinator24.miniModes.item.GameModesimport io.kerzinator24.miniModes.item.ItemDSLimport io.kerzinator24.miniModes.model.GamePlayerimport io.kerzinator24.miniModes.model.GameWorldimport io.kerzinator24.miniModes.sendable.Chatimport io.kerzinator24.miniModes.sendable.Titleimport 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@onPlayerInteractval held = player.inventory.mainHand() ?: return@onPlayerInteractif (held.material != "gold_ingot") return@onPlayerInteractcancel()endGame(player)}events.onPlayerDeath {clearDrops()onPlayerDeath(event)if (aliveCount() == 1) {endGame(alivePlayers().first())}}}override fun setup(players: List<GamePlayer>) {val world = gameWorld ?: returninitializeAlivePlayers(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.ADVENTUREplayer.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 = truestartMatchClock()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() = 2override fun getMaxPlayers() = 8} -
Build the
.mmxRun:
Terminal window ./gradlew :extensions:goldrush-extension:shadowJarThe
shadowJartask produces an.mmxartifact. If you used the destination from the example, it is copied directly into the MiniModes extension folder. -
Load it on the server
Start or reload the MiniModes server. The loader scans extension artifacts, finds the
@Gameclass, registers the display item and player bounds, and makes the game available in selectors and commands.
What the code did not write
Section titled “What the code did not write”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.
Common next steps
Section titled “Common next steps”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.