Anleitungen

Kotlin und Java

Für große Mods. Dieselbe API wie CraftScript, mit statischen Typen, voller IDE und jeder Java-Bibliothek, dazu ein paar Teile, die es vorerst nur hier gibt. Diese Anleitung nutzt Kotlin. Java funktioniert genauso und wird am Ende gezeigt.

Was du machen kannst

Bereich In der API In CraftScript
Gegenstände, Blöcke, Rezepte ContentBuilder: item, block, shaped, shapeless, smelting Ja
Kreaturen ContentBuilder.creature: Körper, Textur, Werte, Behaviour, TargetRule, Beute, natürliches Erscheinen, Zähmen, Züchten, Reiten, eigene Verhaltensweisen Ja
Geräusche und Partikel ContentBuilder.sound, ContentBuilder.particle Ja
Weltgenerierung ContentBuilder: ore, tree, patch, structure (Pools, plan/build), biome, dimension, feature, terrain, biomes, carver, tag. Siehe Weltgenerierung und Generierung per Code Alles außer feature, tag, salt und aboveBottom
Oberfläche Form und player.showForm; server.bossBar; player.setSidebar Ja
Events Crafting.events.on<E> { }, mit Priorität und Abbrechen Ja
Effekte, Attribute, Ausrüstung addEffect, attribute, setAttribute, equipment, setEquipment, EquipmentSlot Yes
Tasten ContentBuilder.key, PlayerKeyEvent Yes
Befehle Crafting.commands.command(…): Unterbefehle, typisierte Argumente, Berechtigungen, Aliase Ja
Welt und Spieler World, Block, Entity, Player, Inventory, Server Ja
Aufgaben Crafting.scheduler: runLater, runRepeating, runAsync, runSync Keine Hintergrundarbeit
Einstellungen in Dateien Typisierte Config in TOML, kann mit Clients synchronisiert werden Ja (config)
Netzwerk Crafting.network.channel(…), mit eigenen Codecs Ja (listen, send)
Speicherdaten player.data["rubies.visits"] = n, data.getInt(…); auch an Objekten, Blöcken und Welten Ja
Java-Bibliotheken Jede, die du willst Nein

Was die API noch nicht hat, gibt es in keiner Sprache. Umgeh es nicht, indem du Minecraft-Klassen aufrufst: Die Mod würde die Garantie verlieren, dass sie weiter funktioniert, wenn sich das Spiel ändert.

Das Projekt erstellen

Terminal
crafting new rubies --kind kotlin
cd rubies && gradle build

Die API liegt noch nicht in einem öffentlichen Maven-Repository. Installiere sie aus dem Crafting-Quellcode in dein lokales Maven und deklariere sie als compileOnly: Crafting stellt sie der Mod zur Laufzeit bereit.

Terminal
cd crafting && ./gradlew :crafting-api:publishToMavenLocal
build.gradle.ktsKotlin
plugins { kotlin("jvm") version "2.4.21" }

repositories { mavenCentral(); mavenLocal() }

dependencies {
    compileOnly("dev.crafting:crafting-api:0.1.0")
}

kotlin { jvmToolchain(25) } // Minecraft 26.x läuft auf Java 25

crafting new erzeugt diese Datei und ein Projekt, das schon nach Aufgaben aufgeteilt ist: <Name>Mod.kt, die nur die Teile verbindet, content/ModContent.kt, events/Welcome.kt und commands/GemCommand.kt.

Struktur

Pakete nach Thema, eine Einstiegsklasse, die nur die Teile verbindet, und eine Datei pro Teil. Die Anleitung Eine Mod organisieren erklärt, warum. Hier ist die vollständige Beispiel-Mod:

StrukturText
rubies/
├── build.gradle.kts
├── settings.gradle.kts
└── src/main/
    ├── kotlin/com/example/rubies/
    │   ├── RubiesMod.kt         entry point: only wires the parts
    │   ├── RubiesConfig.kt      player settings
    │   ├── content/
    │   │   ├── Items.kt
    │   │   ├── Blocks.kt
    │   │   ├── Creatures.kt
    │   │   ├── Sounds.kt
    │   │   ├── Particles.kt
    │   │   └── Recipes.kt
    │   ├── storage/
    │   │   └── Visits.kt        saved data: the keys, in one place
    │   ├── events/
    │   │   ├── MiningEvents.kt
    │   │   ├── CombatEvents.kt
    │   │   └── WelcomeEvents.kt
    │   ├── ui/
    │   │   └── ShopForm.kt       forms: what the player sees
    │   └── commands/
    │       ├── RubyCommand.kt
    │       └── ShopCommand.kt
    └── resources/
        ├── crafting.mod.json
        └── assets/rubies/
            ├── sounds/ruby/chime.ogg
            └── textures/
                ├── item/ruby.png
                ├── entity/ruby_golem.png
                └── particle/ruby_spark_0.png, ruby_spark_1.png

src/main/kotlin/com/example/rubies/

Die Einstiegsklasse tut selbst nichts: Sie erstellt das Gemeinsame und ruft jeden Teil auf.

src/main/kotlin/com/example/rubies/RubiesMod.ktKotlin
package com.example.rubies

class RubiesMod : CraftingMod {
    override fun onLoad(ctx: ModContext) {
        val config = Crafting.configs.load(RubiesConfig())
        val content = ContentBuilder(ctx.metadata.id, Crafting.registries)

        Items.register(content)
        Blocks.register(content)
        Creatures.register(content)
        Sounds.register(content)
        Particles.register(content)
        Recipes.register(content)

        MiningEvents.register(config)
        CombatEvents.register(config)
        WelcomeEvents.register()
        RubyCommand.register()
        ShopCommand.register()
    }
}

Das Manifest

src/main/resources/crafting.mod.jsonJSON
{
  "id": "rubies",
  "name": "Rubies",
  "version": "1.0.0",
  "kind": "mod",
  "side": "both",
  "entrypoints": ["com.example.rubies.RubiesMod"],
  "depends": { "crafting": ">=0.1.0" }
}
Feld Was es ist
id 2–64 Zeichen: Kleinbuchstaben, Ziffern, _ oder -, beginnend mit einem Buchstaben. Es ist auch der Namensraum für deine Gegenstände und Blöcke.
kind mod oder plugin (nur Server, neu ladbar, siehe unten).
side both, client oder server. Eine Client-Mod wird auf einem dedizierten Server stillschweigend ignoriert.
entrypoints Die Klassen, die CraftingMod implementieren.
depends, optional, incompatible ID → Versionsbereich: >=1.2 <2, ^1.4, ~1.4.2, 1.20.x, [1.0,2.0). Schlägt etwas fehl, meldet Crafting alle Probleme auf einmal, mit einem Vorschlag.
loadAfter, loadBefore Ladereihenfolge ohne harte Abhängigkeit.

Lebenszyklus

dev.crafting.api.mod.CraftingModKotlin
interface CraftingMod {
    fun onLoad(ctx: ModContext)
    fun onReady(ctx: ModContext) {}
    fun onShutdown(ctx: ModContext) {}
}
  1. onLoad: Inhalte, Events und Befehle registrieren.
  2. Crafting erstellt die Blöcke und Gegenstände im Spiel. Ab hier wirft das Registrieren von Inhalten RegistryFrozenException.
  3. onReady: Alle Mods sind geladen. Crafting.runtime.isModLoaded("other_mod") sagt dir, ob eine andere Mod vorhanden ist.
  4. onShutdown: beim Beenden.

Server-Plugins

Ein Plugin ist eine CraftingMod mit "kind": "plugin" und "side": "server". crafting new chatguard --kind plugin erstellt die Einstiegsklasse, events/ChatFilter.kt und commands/HelloCommand.kt. Es fügt keine Blöcke oder Gegenstände hinzu, also können Spieler mit unverändertem Minecraft einem Server beitreten, der nur Plugins hat. Du aktivierst, deaktivierst und lädst es ohne Neustart neu: onLoad und onReady laufen beim Aktivieren, onShutdown beim Deaktivieren oder Neuladen.

Terminal
crafting new chatguard --kind plugin
src/main/kotlin/com/example/chatguard/ChatGuardPlugin.ktKotlin
package com.example.chatguard

class ChatGuardPlugin : CraftingMod {
    override fun onLoad(ctx: ModContext) {
        ChatFilter.register()
        Crafting.commands.command("guard") {
            permission = "chatguard.admin"
            executes { it.reply("ChatGuard ist aktiv") }
        }
    }

    override fun onShutdown(ctx: ModContext) = ctx.logger.info("ChatGuard deaktiviert")
}
src/main/kotlin/com/example/chatguard/events/ChatFilter.ktKotlin
package com.example.chatguard.events

object ChatFilter {
    private val blocked = listOf("spam", "scam")

    fun register() {
        Crafting.events.on<PlayerChatEvent> { e ->
            if (blocked.any { it in e.message.lowercase() }) {
                e.cancel()
                e.player.send("&cBitte bleib freundlich")
            }
        }
    }
}

Alles, was ein Plugin registriert, ist an es gebunden. /crafting reload chatguard ruft onShutdown auf, entfernt seine Listener, Befehle und Aufgaben, liest die JAR-Datei neu ein und ruft onLoad auf. Im Entwicklungsmodus passiert das automatisch, wenn du die JAR-Datei ersetzt.

Mod Plugin
Ordner mods/ plugins/
Seite Client und/oder Server Nur Server
Neue Blöcke und Gegenstände Ja Nein
Neu laden ohne Neustart Nein Ja
Unveränderte Clients können beitreten Nein, wenn sie Inhalte hinzufügt Ja

Java

Dieselbe API, dieselbe Struktur. Aus Java ist der Einstiegspunkt Crafting.INSTANCE, du übergibst jeden Parameter, der einen Standardwert hat, und ein Listener gibt Unit.INSTANCE zurück:

src/main/java/com/example/rubies/RubiesMod.javaJava
package com.example.rubies;

public final class RubiesMod implements CraftingMod {
    @Override
    public void onLoad(ModContext ctx) {
        ContentBuilder content = new ContentBuilder(ctx.getMetadata().getId(), Crafting.INSTANCE.getRegistries());
        Items.register(content);
        WelcomeEvents.register();
    }
}
src/main/java/com/example/rubies/events/WelcomeEvents.javaJava
package com.example.rubies.events;

public final class WelcomeEvents {
    public static void register() {
        Crafting.INSTANCE.getEvents().subscribe(PlayerJoinEvent.class, EventPriority.NORMAL, false, null, e -> {
            e.getPlayer().send("&aWelcome, " + e.getPlayer().getName() + "!");
            return Unit.INSTANCE;
        });
    }
}

Alte Wege

Sie funktionieren noch, sind aber als veraltet markiert und haben einen Ersatz:

Vorher Jetzt
class X : CraftingPlugin() mit onEnable/onDisable CraftingMod mit "kind": "plugin"
Methoden mit @Subscribe und events.register(obj) events.on<E> { } (Java: events.subscribe(E.class, …))
@Mod("id") an der Klasse Die Klasse in den entrypoints des Manifests
EntityDamageEvent.source damageType (eine ID wie minecraft:fall)
world.setBlockState(x, y, z, "…[open=true]") world.setBlock(x, y, z, type, mapOf("open" to true))

Regeln

  • Importiere nichts aus net.minecraft. Genau das lässt die Mod weiter funktionieren, wenn sich das Spiel ändert.
  • Registriere Inhalte nur in onLoad.
  • Eine runAsync-Aufgabe fasst die Welt nicht an. Kehr mit runSync zum Spiel-Thread zurück.