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
crafting new rubies --kind kotlin
cd rubies && gradle buildL’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.
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 tourne sur Java 25crafting 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 :
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/
La classe d’entrée ne fait rien elle-même : elle crée ce qui est partagé et appelle chaque partie.
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
{
"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
interface CraftingMod {
fun onLoad(ctx: ModContext)
fun onReady(ctx: ModContext) {}
fun onShutdown(ctx: ModContext) {}
}onLoad: enregistre le contenu, les événements et les commandes.- Crafting crée les blocs et les objets dans le jeu. À partir de là, enregistrer du contenu lève
RegistryFrozenException. onReady: tous les mods sont chargés ;Crafting.runtime.isModLoaded("other_mod")t’indique si un autre mod est présent.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é.
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 est actif") }
}
}
override fun onShutdown(ctx: ModContext) = ctx.logger.info("ChatGuard désactivé")
}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 :
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;
});
}
}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
runAsyncne touche pas au monde ; reviens sur le thread du jeu avecrunSync.