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
crafting new rubies --kind kotlin
cd rubies && gradle buildDie 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.
cd crafting && ./gradlew :crafting-api:publishToMavenLocalplugins { 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 25crafting 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:
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.pngsrc/main/kotlin/com/example/rubies/
Die Einstiegsklasse tut selbst nichts: Sie erstellt das Gemeinsame und ruft jeden Teil auf.
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
{
"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
interface CraftingMod {
fun onLoad(ctx: ModContext)
fun onReady(ctx: ModContext) {}
fun onShutdown(ctx: ModContext) {}
}onLoad: Inhalte, Events und Befehle registrieren.- Crafting erstellt die Blöcke und Gegenstände im Spiel. Ab hier wirft das Registrieren von Inhalten
RegistryFrozenException. onReady: Alle Mods sind geladen.Crafting.runtime.isModLoaded("other_mod")sagt dir, ob eine andere Mod vorhanden ist.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.
crafting new chatguard --kind pluginpackage 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")
}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:
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();
}
}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 mitrunSynczum Spiel-Thread zurück.