Guías
Kotlin y Java
Para mods grandes. La misma API que CraftScript, con tipos estáticos, el IDE completo y cualquier librería de Java, más algunas partes que de momento solo existen aquí. Esta guía usa Kotlin; Java funciona igual y se muestra al final.
Qué puedes hacer
| Área | En la API | En CraftScript |
|---|---|---|
| Ítems, bloques, recetas | ContentBuilder: item, block, shaped, shapeless, smelting |
Sí |
| Criaturas | ContentBuilder.creature: cuerpo, textura, estadísticas, Behaviour, TargetRule, botín, aparición natural, doma, cría, monta, comportamientos propios |
Sí |
| Sonidos y partículas | ContentBuilder.sound, ContentBuilder.particle |
Sí |
| Generación de mundo | ContentBuilder: ore, tree, patch, structure (pools, plan/build), biome, dimension, feature, terrain, biomes, carver, tag. Ver Generación de mundo y Generación por código |
Todo menos feature, tag, salt y aboveBottom |
| Interfaz | Form y player.showForm; server.bossBar; player.setSidebar |
Sí |
| Eventos | Crafting.events.on<E> { }, con prioridad y cancelación |
Sí |
| Efectos, atributos, equipo | addEffect, attribute, setAttribute, equipment, setEquipment, EquipmentSlot |
Yes |
| Teclas | ContentBuilder.key, PlayerKeyEvent |
Yes |
| Comandos | Crafting.commands.command(…): subcomandos, argumentos tipados, permisos, alias |
Sí |
| Mundo y jugadores | World, Block, Entity, Player, Inventory, Server |
Sí |
| Tareas | Crafting.scheduler: runLater, runRepeating, runAsync, runSync |
Sin segundo plano |
| Ajustes en archivo | Config tipada en TOML, sincronizable con los clientes |
Sí (config) |
| Red | Crafting.network.channel(…), con codecs propios |
Sí (listen, send) |
| Datos guardados | player.data["rubies.visits"] = n, data.getInt(…); también en entidades, bloques y mundos |
Sí |
| Librerías de Java | Las que quieras | No |
Lo que la API todavía no tiene no existe en ningún lenguaje. No lo suplas llamando a clases de Minecraft: el mod perdería la garantía de seguir funcionando cuando el juego cambie.
Crear el proyecto
crafting new rubies --kind kotlin
cd rubies && gradle buildLa API aún no está en un repositorio Maven público. Instálala en tu Maven local desde el código de Crafting y decláralo como compileOnly: Crafting la pone a disposición del mod al ejecutar.
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 runs on Java 25crafting new genera este archivo y un proyecto ya repartido por responsabilidades: <Nombre>Mod.kt, que solo conecta las partes, content/ModContent.kt, events/Welcome.kt y commands/GemCommand.kt.
Estructura
Paquetes por tema, una clase de entrada que solo conecta las partes, y un archivo por cada parte. La guía Organizar un mod explica el porqué; aquí está el mod de ejemplo completo:
rubies/
├── build.gradle.kts
├── settings.gradle.kts
└── src/main/
├── kotlin/com/example/rubies/
│ ├── RubiesMod.kt entrada: solo conecta las partes
│ ├── RubiesConfig.kt ajustes del jugador
│ ├── content/
│ │ ├── Items.kt
│ │ ├── Blocks.kt
│ │ ├── Creatures.kt
│ │ ├── Sounds.kt
│ │ ├── Particles.kt
│ │ └── Recipes.kt
│ ├── storage/
│ │ └── Visits.kt datos guardados: las claves, en un solo sitio
│ ├── events/
│ │ ├── MiningEvents.kt
│ │ ├── CombatEvents.kt
│ │ └── WelcomeEvents.kt
│ ├── ui/
│ │ └── ShopForm.kt formularios: qué ve el jugador
│ └── 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 clase de entrada no hace nada por sí misma: crea lo compartido y llama a cada parte.
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()
}
}El manifiesto
{
"id": "rubies",
"name": "Rubies",
"version": "1.0.0",
"kind": "mod",
"side": "both",
"entrypoints": ["com.example.rubies.RubiesMod"],
"depends": { "crafting": ">=0.1.0" }
}| Campo | Qué es |
|---|---|
id |
2–64 caracteres: minúsculas, dígitos, _ o -, empezando por letra. Es también el namespace de tus ítems y bloques. |
kind |
mod o plugin (solo servidor, recargable; ver abajo). |
side |
both, client o server. Un mod de cliente se ignora sin error en un servidor dedicado. |
entrypoints |
Las clases que implementan CraftingMod. |
depends, optional, incompatible |
Id → rango de versiones: >=1.2 <2, ^1.4, ~1.4.2, 1.20.x, [1.0,2.0). Si algo falla, Crafting informa de todos los problemas a la vez, con una sugerencia. |
loadAfter, loadBefore |
Orden de carga sin dependencia obligatoria. |
Ciclo de vida
interface CraftingMod {
fun onLoad(ctx: ModContext)
fun onReady(ctx: ModContext) {}
fun onShutdown(ctx: ModContext) {}
}onLoad: registra contenido, eventos y comandos.- Crafting crea los bloques e ítems en el juego. A partir de aquí registrar contenido lanza
RegistryFrozenException. onReady: todos los mods están cargados;Crafting.runtime.isModLoaded("other_mod")dice si otro mod está presente.onShutdown: al cerrar.
Plugins de servidor
Un plugin es un CraftingMod con "kind": "plugin" y "side": "server"; crafting new chatguard --kind plugin crea la clase de entrada, events/ChatFilter.kt y commands/HelloCommand.kt. No añade bloques ni ítems, así que los jugadores con Minecraft sin mods pueden entrar a un servidor que solo tenga plugins. Se activa, desactiva y recarga sin reiniciar: onLoad y onReady corren al activarlo, onShutdown al desactivarlo o recargarlo.
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 is active") }
}
}
override fun onShutdown(ctx: ModContext) = ctx.logger.info("ChatGuard disabled")
}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("&cPlease keep it friendly")
}
}
}
}Todo lo que registra un plugin queda asociado a él. /crafting reload chatguard llama a onShutdown, retira sus listeners, comandos y tareas, vuelve a leer el jar y llama a onLoad. En modo desarrollo pasa solo al reemplazar el jar.
| Mod | Plugin | |
|---|---|---|
| Carpeta | mods/ |
plugins/ |
| Lado | Cliente y/o servidor | Solo servidor |
| Bloques e ítems nuevos | Sí | No |
| Recarga sin reiniciar | No | Sí |
| Clientes sin mods pueden entrar | No, si añade contenido | Sí |
Java
Misma API, misma estructura. Desde Java, el punto de entrada es Crafting.INSTANCE, los parámetros con valor por defecto se pasan todos, y un listener devuelve 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;
});
}
}Formas antiguas
Siguen funcionando, pero están marcadas como obsoletas y tienen sustituto:
| Antes | Ahora |
|---|---|
class X : CraftingPlugin() con onEnable/onDisable |
CraftingMod con "kind": "plugin" |
Métodos con @Subscribe y events.register(obj) |
events.on<E> { } (Java: events.subscribe(E.class, …)) |
@Mod("id") en la clase |
La clase en entrypoints del manifiesto |
EntityDamageEvent.source |
damageType (un id, como minecraft:fall) |
world.setBlockState(x, y, z, "…[open=true]") |
world.setBlock(x, y, z, type, mapOf("open" to true)) |
Reglas
- No importes nada de
net.minecraft. Es lo que permite que el mod siga funcionando cuando el juego cambia. - Registra contenido solo en
onLoad. - Una tarea de
runAsyncno toca el mundo; vuelve al hilo del juego conrunSync.