Guides

Kotlin et Java

Pour les gros mods. La même API que CraftScript, avec des types statiques, tout l’IDE et n’importe quelle bibliothèque Java, plus quelques parties qui n’existent qu’ici pour l’instant. Ce guide utilise Kotlin ; Java fonctionne de la même façon et est présenté à la fin.

Ce que tu peux faire

Domaine Dans l’API En CraftScript
Objets, blocs, recettes ContentBuilder : item, block, shaped, shapeless, smelting Oui
Créatures ContentBuilder.creature : corps, texture, statistiques, Behaviour, TargetRule, butin, apparition naturelle, apprivoisement, élevage, monte, comportements propres Oui
Sons et particules ContentBuilder.sound, ContentBuilder.particle Oui
Génération du monde ContentBuilder : ore, tree, patch, structure (pools, plan/build), biome, dimension, feature, terrain, biomes, carver, tag. Voir Génération du monde et Génération par code Tout sauf feature, tag, salt et aboveBottom
Interface Form et player.showForm ; server.bossBar ; player.setSidebar Oui
Événements Crafting.events.on<E> { }, avec priorité et annulation Oui
Effets, attributs, équipement addEffect, attribute, setAttribute, equipment, setEquipment, EquipmentSlot Yes
Touches ContentBuilder.key, PlayerKeyEvent Yes
Commandes Crafting.commands.command(…) : sous-commandes, arguments typés, permissions, alias Oui
Monde et joueurs World, Block, Entity, Player, Inventory, Server Oui
Tâches Crafting.scheduler : runLater, runRepeating, runAsync, runSync Pas de travail en arrière-plan
Réglages dans des fichiers Config typée en TOML, synchronisable avec les clients Oui (config)
Réseau Crafting.network.channel(…), avec des codecs personnalisés Oui (listen, send)
Données enregistrées player.data["rubies.visits"] = n, data.getInt(…) ; aussi sur les entités, les blocs et les mondes Oui
Bibliothèques Java Toutes celles que tu veux Non

Ce que l’API n’a pas encore n’existe dans aucun langage. Ne le contourne pas en appelant des classes de Minecraft : le mod perdrait la garantie de continuer à fonctionner quand le jeu change.

Créer le projet

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

L’API n’est pas encore dans un dépôt Maven public. Installe-la dans ton Maven local depuis les sources de Crafting et déclare-la en compileOnly : Crafting la fournit au mod à l’exécution.

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 tourne sur Java 25

crafting new génère ce fichier et un projet déjà découpé par responsabilité : <Name>Mod.kt, qui ne fait que relier les parties, content/ModContent.kt, events/Welcome.kt et commands/GemCommand.kt.

Structure

Des packages par thème, une classe d’entrée qui ne fait que relier les parties, et un fichier par partie. Le guide Organiser un mod explique pourquoi ; voici le mod d’exemple complet :

StructureTexte
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/

La classe d’entrée ne fait rien elle-même : elle crée ce qui est partagé et appelle chaque partie.

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()
    }
}

Le manifeste

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" }
}
Champ Ce que c’est
id De 2 à 64 caractères : lettres minuscules, chiffres, _ ou -, en commençant par une lettre. C’est aussi l’espace de noms de tes objets et de tes blocs.
kind mod ou plugin (serveur uniquement, rechargeable ; voir plus bas).
side both, client ou server. Un mod client est ignoré sans message sur un serveur dédié.
entrypoints Les classes qui implémentent CraftingMod.
depends, optional, incompatible Id → plage de versions : >=1.2 <2, ^1.4, ~1.4.2, 1.20.x, [1.0,2.0). Si quelque chose échoue, Crafting signale tous les problèmes d’un coup, avec une suggestion.
loadAfter, loadBefore Ordre de chargement sans dépendance stricte.

Cycle de vie

dev.crafting.api.mod.CraftingModKotlin
interface CraftingMod {
    fun onLoad(ctx: ModContext)
    fun onReady(ctx: ModContext) {}
    fun onShutdown(ctx: ModContext) {}
}
  1. onLoad : enregistre le contenu, les événements et les commandes.
  2. Crafting crée les blocs et les objets dans le jeu. À partir de là, enregistrer du contenu lève RegistryFrozenException.
  3. onReady : tous les mods sont chargés ; Crafting.runtime.isModLoaded("other_mod") t’indique si un autre mod est présent.
  4. onShutdown : à la fermeture.

Plugins de serveur

Un plugin est un CraftingMod avec "kind": "plugin" et "side": "server" ; crafting new chatguard --kind plugin crée la classe d’entrée, events/ChatFilter.kt et commands/HelloCommand.kt. Il n’ajoute ni blocs ni objets, donc des joueurs avec un Minecraft sans mods peuvent rejoindre un serveur qui n’a que des plugins. Tu l’actives, le désactives et le recharges sans redémarrer : onLoad et onReady s’exécutent quand il est activé, onShutdown quand il est désactivé ou rechargé.

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 est actif") }
        }
    }

    override fun onShutdown(ctx: ModContext) = ctx.logger.info("ChatGuard désactivé")
}
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("&cReste aimable, s’il te plaît")
            }
        }
    }
}

Tout ce qu’un plugin enregistre lui est rattaché. /crafting reload chatguard appelle onShutdown, retire ses écouteurs, commandes et tâches, relit le jar et appelle onLoad. En mode développement, cela se fait automatiquement quand tu remplaces le jar.

Mod Plugin
Dossier mods/ plugins/
Côté Client et/ou serveur Serveur uniquement
Nouveaux blocs et objets Oui Non
Rechargement sans redémarrage Non Oui
Les clients sans mods peuvent se connecter Non, s’il ajoute du contenu Oui

Java

Même API, même structure. Depuis Java, le point d’entrée est Crafting.INSTANCE, tu passes chaque paramètre qui a une valeur par défaut, et un écouteur renvoie Unit.INSTANCE :

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;
        });
    }
}

Anciennes méthodes

Elles fonctionnent encore, mais elles sont marquées comme dépréciées et ont un remplacement :

Avant Maintenant
class X : CraftingPlugin() avec onEnable/onDisable CraftingMod avec "kind": "plugin"
Méthodes avec @Subscribe et events.register(obj) events.on<E> { } (Java : events.subscribe(E.class, …))
@Mod("id") sur la classe La classe dans les entrypoints du manifeste
EntityDamageEvent.source damageType (un id, comme minecraft:fall)
world.setBlockState(x, y, z, "…[open=true]") world.setBlock(x, y, z, type, mapOf("open" to true))

Règles

  • N’importe rien depuis net.minecraft. C’est ce qui permet au mod de continuer à fonctionner quand le jeu change.
  • Enregistre le contenu uniquement dans onLoad.
  • Une tâche runAsync ne touche pas au monde ; reviens sur le thread du jeu avec runSync.