Guias
Kotlin e Java
Para mods grandes. A mesma API do CraftScript, com tipos estáticos, a IDE completa e qualquer biblioteca Java, além de algumas partes que, por enquanto, só existem aqui. Este guia usa Kotlin; Java funciona do mesmo jeito e aparece no final.
O que dá para fazer
| Área | Na API | Em CraftScript |
|---|---|---|
| Itens, blocos, receitas | ContentBuilder: item, block, shaped, shapeless, smelting |
Sim |
| Criaturas | ContentBuilder.creature: corpo, textura, atributos, Behaviour, TargetRule, loot, surgimento natural, domesticação, criação, montaria, comportamentos próprios |
Sim |
| Sons e partículas | ContentBuilder.sound, ContentBuilder.particle |
Sim |
| Geração de mundo | ContentBuilder: ore, tree, patch, structure (pools, plan/build), biome, dimension, feature, terrain, biomes, carver, tag. Veja Geração de mundo e Geração por código |
Tudo menos feature, tag, salt e aboveBottom |
| Interface | Form e player.showForm; server.bossBar; player.setSidebar |
Sim |
| Eventos | Crafting.events.on<E> { }, com prioridade e cancelamento |
Sim |
| Efeitos, atributos, equipamento | addEffect, attribute, setAttribute, equipment, setEquipment, EquipmentSlot |
Yes |
| Teclas | ContentBuilder.key, PlayerKeyEvent |
Yes |
| Comandos | Crafting.commands.command(…): subcomandos, argumentos tipados, permissões, aliases |
Sim |
| Mundo e jogadores | World, Block, Entity, Player, Inventory, Server |
Sim |
| Tarefas | Crafting.scheduler: runLater, runRepeating, runAsync, runSync |
Sem trabalho em segundo plano |
| Ajustes em arquivo | Config tipada em TOML, pode sincronizar com os clientes |
Sim (config) |
| Rede | Crafting.network.channel(…), com codecs personalizados |
Sim (listen, send) |
| Dados salvos | player.data["rubies.visits"] = n, data.getInt(…); também em entidades, blocos e mundos |
Sim |
| Bibliotecas Java | Qualquer uma | Não |
O que a API ainda não tem não existe em nenhuma linguagem. Não contorne isso chamando classes do Minecraft: o mod perderia a garantia de continuar funcionando quando o jogo muda.
Crie o projeto
crafting new rubies --kind kotlin
cd rubies && gradle buildA API ainda não está em um repositório Maven público. Instale-a no seu Maven local a partir do código-fonte do Crafting e declare-a como compileOnly: o Crafting a fornece ao mod em tempo de execução.
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) } // o Minecraft 26.x roda no Java 25crafting new gera esse arquivo e um projeto já dividido por responsabilidade: <Name>Mod.kt, que só conecta as partes, content/ModContent.kt, events/Welcome.kt e commands/GemCommand.kt.
Estrutura
Pacotes por tema, uma classe de entrada que só conecta as partes e um arquivo por parte. O guia Organizando um mod explica por quê; aqui está o mod de exemplo completo:
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/
A classe de entrada não faz nada sozinha: cria o que é compartilhado e chama 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()
}
}O manifesto
{
"id": "rubies",
"name": "Rubies",
"version": "1.0.0",
"kind": "mod",
"side": "both",
"entrypoints": ["com.example.rubies.RubiesMod"],
"depends": { "crafting": ">=0.1.0" }
}| Campo | O que é |
|---|---|
id |
2–64 caracteres: letras minúsculas, dígitos, _ ou -, começando com uma letra. Também é o namespace dos seus itens e blocos. |
kind |
mod ou plugin (só servidor, recarregável; veja abaixo). |
side |
both, client ou server. Um mod de cliente é ignorado em silêncio em um servidor dedicado. |
entrypoints |
As classes que implementam CraftingMod. |
depends, optional, incompatible |
Id → intervalo de versões: >=1.2 <2, ^1.4, ~1.4.2, 1.20.x, [1.0,2.0). Se algo falhar, o Crafting informa todos os problemas de uma vez, com uma sugestão. |
loadAfter, loadBefore |
Ordem de carregamento sem dependência obrigatória. |
Ciclo de vida
interface CraftingMod {
fun onLoad(ctx: ModContext)
fun onReady(ctx: ModContext) {}
fun onShutdown(ctx: ModContext) {}
}onLoad: registre conteúdo, eventos e comandos.- O Crafting cria os blocos e itens no jogo. A partir daí, registrar conteúdo lança
RegistryFrozenException. onReady: todos os mods estão carregados;Crafting.runtime.isModLoaded("other_mod")diz se outro mod está presente.onShutdown: ao sair.
Plugins de servidor
Um plugin é um CraftingMod com "kind": "plugin" e "side": "server"; crafting new chatguard --kind plugin cria a classe de entrada, events/ChatFilter.kt e commands/HelloCommand.kt. Ele não adiciona blocos nem itens, então jogadores com o Minecraft sem mods podem entrar em um servidor que só tem plugins. Você o ativa, desativa e recarrega sem reiniciar: onLoad e onReady rodam quando ele é ativado, onShutdown quando é desativado ou recarregado.
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("O ChatGuard está ativo") }
}
}
override fun onShutdown(ctx: ModContext) = ctx.logger.info("ChatGuard desativado")
}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("&cPor favor, mantenha a gentileza")
}
}
}
}Tudo o que um plugin registra fica ligado a ele. /crafting reload chatguard chama onShutdown, remove seus listeners, comandos e tarefas, lê o jar de novo e chama onLoad. No modo de desenvolvimento, isso acontece automaticamente quando você substitui o jar.
| Mod | Plugin | |
|---|---|---|
| Pasta | mods/ |
plugins/ |
| Lado | Cliente e/ou servidor | Só servidor |
| Blocos e itens novos | Sim | Não |
| Recarregar sem reiniciar | Não | Sim |
| Clientes sem mods podem entrar | Não, se adicionar conteúdo | Sim |
Java
Mesma API, mesma estrutura. Em Java, o ponto de entrada é Crafting.INSTANCE, você passa todos os parâmetros que têm valor padrão, e um listener retorna 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;
});
}
}Jeitos antigos
Ainda funcionam, mas estão marcados como obsoletos e têm um substituto:
| Antes | Agora |
|---|---|
class X : CraftingPlugin() com onEnable/onDisable |
CraftingMod com "kind": "plugin" |
Métodos com @Subscribe e events.register(obj) |
events.on<E> { } (Java: events.subscribe(E.class, …)) |
@Mod("id") na classe |
A classe em entrypoints do manifesto |
EntityDamageEvent.source |
damageType (um id, como minecraft:fall) |
world.setBlockState(x, y, z, "…[open=true]") |
world.setBlock(x, y, z, type, mapOf("open" to true)) |
Regras
- Não importe nada de
net.minecraft. É isso que permite ao mod continuar funcionando quando o jogo muda. - Registre conteúdo só em
onLoad. - Uma tarefa
runAsyncnão mexe no mundo; volte para a thread do jogo comrunSync.