Poradniki
Kotlin i Java
Do dużych modów. To samo API co w CraftScript, ze statycznymi typami, pełnym IDE i dowolną biblioteką Javy, plus kilka części, które na razie istnieją tylko tutaj. Ten poradnik używa Kotlina; Java działa tak samo i jest pokazana na końcu.
Co możesz zrobić
| Obszar | W API | W CraftScript |
|---|---|---|
| Przedmioty, bloki, receptury | ContentBuilder: item, block, shaped, shapeless, smelting |
Tak |
| Stworzenia | ContentBuilder.creature: ciało, tekstura, statystyki, Behaviour, TargetRule, łup, samoistne pojawianie się, oswajanie, hodowla, jazda, własne zachowania |
Tak |
| Dźwięki i cząsteczki | ContentBuilder.sound, ContentBuilder.particle |
Tak |
| Generowanie świata | ContentBuilder: ore, tree, patch, structure (pule, plan/build), biome, dimension, feature, terrain, biomes, carver, tag. Zobacz Generowanie świata i Generowanie kodem |
Wszystko poza feature, tag, salt i aboveBottom |
| Interfejs | Form i player.showForm; server.bossBar; player.setSidebar |
Tak |
| Zdarzenia | Crafting.events.on<E> { }, z priorytetem i anulowaniem |
Tak |
| Efekty, atrybuty, wyposażenie | addEffect, attribute, setAttribute, equipment, setEquipment, EquipmentSlot |
Yes |
| Klawisze | ContentBuilder.key, PlayerKeyEvent |
Yes |
| Polecenia | Crafting.commands.command(…): podpolecenia, typowane argumenty, uprawnienia, aliasy |
Tak |
| Świat i gracze | World, Block, Entity, Player, Inventory, Server |
Tak |
| Zadania | Crafting.scheduler: runLater, runRepeating, runAsync, runSync |
Bez pracy w tle |
| Ustawienia w plikach | Typowany Config w TOML, może synchronizować się z klientami |
Tak (config) |
| Sieć | Crafting.network.channel(…), z własnymi kodekami |
Tak (listen, send) |
| Zapisane dane | player.data["rubies.visits"] = n, data.getInt(…); także w bytach, blokach i światach |
Tak |
| Biblioteki Javy | Dowolne | Nie |
Tego, czego API jeszcze nie ma, nie ma w żadnym języku. Nie obchodź tego, wywołując klasy Minecrafta: mod straciłby gwarancję, że działa dalej, gdy gra się zmienia.
Utwórz projekt
crafting new rubies --kind kotlin
cd rubies && gradle buildAPI nie ma jeszcze w publicznym repozytorium Maven. Zainstaluj je w lokalnym Mavenie ze źródeł Craftingu i zadeklaruj jako compileOnly: Crafting dostarcza je modowi w czasie działania.
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 działa na Javie 25crafting new generuje ten plik i projekt od razu podzielony według zadań: <Name>Mod.kt, który tylko łączy części, content/ModContent.kt, events/Welcome.kt i commands/GemCommand.kt.
Struktura
Pakiety według tematu, klasa wejściowa, która tylko łączy części, i jeden plik na część. Poradnik Porządkowanie moda wyjaśnia dlaczego; oto pełny przykładowy mod:
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/
Klasa wejściowa sama nic nie robi: tworzy to, co wspólne, i wywołuje każdą część.
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()
}
}Manifest
{
"id": "rubies",
"name": "Rubies",
"version": "1.0.0",
"kind": "mod",
"side": "both",
"entrypoints": ["com.example.rubies.RubiesMod"],
"depends": { "crafting": ">=0.1.0" }
}| Pole | Co to jest |
|---|---|
id |
2–64 znaki: małe litery, cyfry, _ lub -, zaczynające się od litery. To także przestrzeń nazw twoich przedmiotów i bloków. |
kind |
mod lub plugin (tylko serwer, z przeładowaniem; zobacz niżej). |
side |
both, client lub server. Mod klienta jest po cichu pomijany na serwerze dedykowanym. |
entrypoints |
Klasy implementujące CraftingMod. |
depends, optional, incompatible |
Id → zakres wersji: >=1.2 <2, ^1.4, ~1.4.2, 1.20.x, [1.0,2.0). Jeśli coś zawiedzie, Crafting zgłasza wszystkie problemy naraz, z podpowiedzią. |
loadAfter, loadBefore |
Kolejność wczytywania bez twardej zależności. |
Cykl życia
interface CraftingMod {
fun onLoad(ctx: ModContext)
fun onReady(ctx: ModContext) {}
fun onShutdown(ctx: ModContext) {}
}onLoad: zarejestruj zawartość, zdarzenia i polecenia.- Crafting tworzy bloki i przedmioty w grze. Od tej chwili rejestrowanie zawartości rzuca
RegistryFrozenException. onReady: wszystkie mody są wczytane;Crafting.runtime.isModLoaded("other_mod")mówi, czy inny mod jest obecny.onShutdown: przy wyjściu.
Wtyczki serwera
Wtyczka to CraftingMod z "kind": "plugin" i "side": "server"; crafting new chatguard --kind plugin tworzy klasę wejściową, events/ChatFilter.kt i commands/HelloCommand.kt. Nie dodaje bloków ani przedmiotów, więc gracze z Minecraftem bez modów mogą dołączyć do serwera, który ma tylko wtyczki. Włączasz ją, wyłączasz i przeładowujesz bez restartu: onLoad i onReady uruchamiają się przy włączeniu, onShutdown przy wyłączeniu lub przeładowaniu.
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 jest aktywny") }
}
}
override fun onShutdown(ctx: ModContext) = ctx.logger.info("ChatGuard wyłączony")
}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("&cBądź miły, proszę")
}
}
}
}Wszystko, co wtyczka rejestruje, jest z nią powiązane. /crafting reload chatguard wywołuje onShutdown, usuwa jej nasłuchiwacze, polecenia i zadania, ponownie czyta jar i wywołuje onLoad. W trybie deweloperskim dzieje się to automatycznie, gdy podmienisz jar.
| Mod | Wtyczka | |
|---|---|---|
| Folder | mods/ |
plugins/ |
| Strona | Klient i/lub serwer | Tylko serwer |
| Nowe bloki i przedmioty | Tak | Nie |
| Przeładowanie bez restartu | Nie | Tak |
| Klienci bez modów mogą dołączyć | Nie, jeśli dodaje zawartość | Tak |
Java
To samo API, ta sama struktura. W Javie punktem wejścia jest Crafting.INSTANCE, przekazujesz każdy parametr, który ma wartość domyślną, a nasłuchiwacz zwraca 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;
});
}
}Stare sposoby
Nadal działają, ale są oznaczone jako przestarzałe i mają zamiennik:
| Wcześniej | Teraz |
|---|---|
class X : CraftingPlugin() z onEnable/onDisable |
CraftingMod z "kind": "plugin" |
Metody z @Subscribe i events.register(obj) |
events.on<E> { } (Java: events.subscribe(E.class, …)) |
@Mod("id") na klasie |
Klasa w entrypoints manifestu |
EntityDamageEvent.source |
damageType (id, na przykład minecraft:fall) |
world.setBlockState(x, y, z, "…[open=true]") |
world.setBlock(x, y, z, type, mapOf("open" to true)) |
Zasady
- Nie importuj niczego z
net.minecraft. To właśnie pozwala modowi działać dalej, gdy gra się zmienia. - Rejestruj zawartość tylko w
onLoad. - Zadanie
runAsyncnie dotyka świata; wróć do wątku gry przezrunSync.