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

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

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

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 działa na Javie 25

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

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

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

Klasa wejściowa sama nic nie robi: tworzy to, co wspólne, i wywołuje każdą część.

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

Manifest

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

dev.crafting.api.mod.CraftingModKotlin
interface CraftingMod {
    fun onLoad(ctx: ModContext)
    fun onReady(ctx: ModContext) {}
    fun onShutdown(ctx: ModContext) {}
}
  1. onLoad: zarejestruj zawartość, zdarzenia i polecenia.
  2. Crafting tworzy bloki i przedmioty w grze. Od tej chwili rejestrowanie zawartości rzuca RegistryFrozenException.
  3. onReady: wszystkie mody są wczytane; Crafting.runtime.isModLoaded("other_mod") mówi, czy inny mod jest obecny.
  4. 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.

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 jest aktywny") }
        }
    }

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

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

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 runAsync nie dotyka świata; wróć do wątku gry przez runSync.