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

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

La 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.

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 runs on Java 25

crafting 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:

EstructuraTexto
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.png

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

La clase de entrada no hace nada por sí misma: crea lo compartido y llama a cada parte.

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

El manifiesto

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" }
}
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

dev.crafting.api.mod.CraftingModKotlin
interface CraftingMod {
    fun onLoad(ctx: ModContext)
    fun onReady(ctx: ModContext) {}
    fun onShutdown(ctx: ModContext) {}
}
  1. onLoad: registra contenido, eventos y comandos.
  2. Crafting crea los bloques e ítems en el juego. A partir de aquí registrar contenido lanza RegistryFrozenException.
  3. onReady: todos los mods están cargados; Crafting.runtime.isModLoaded("other_mod") dice si otro mod está presente.
  4. 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.

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 is active") }
        }
    }

    override fun onShutdown(ctx: ModContext) = ctx.logger.info("ChatGuard disabled")
}
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("&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:

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

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 runAsync no toca el mundo; vuelve al hilo del juego con runSync.