Посібники
Kotlin і Java
Для великих модів. Те саме API, що й у CraftScript, зі статичними типами, повноцінним IDE та будь-якою бібліотекою Java, а також кілька частин, які поки що є лише тут. Цей посібник використовує Kotlin; Java працює так само й показана наприкінці.
Що можна зробити
| Сфера | В API | У CraftScript |
|---|---|---|
| Предмети, блоки, рецепти | ContentBuilder: item, block, shaped, shapeless, smelting |
Так |
| Істоти | ContentBuilder.creature: тіло, текстура, характеристики, Behaviour, TargetRule, здобич, природна поява, приборкання, розведення, їзда, власні поведінки |
Так |
| Звуки й частинки | ContentBuilder.sound, ContentBuilder.particle |
Так |
| Генерація світу | ContentBuilder: ore, tree, patch, structure (пули, plan/build), biome, dimension, feature, terrain, biomes, carver, tag. Дивись Генерація світу і Генерація кодом |
Усе, крім feature, tag, salt і aboveBottom |
| Інтерфейс | Form і player.showForm; server.bossBar; player.setSidebar |
Так |
| Події | Crafting.events.on<E> { }, з пріоритетом і скасуванням |
Так |
| Ефекти, атрибути, спорядження | addEffect, attribute, setAttribute, equipment, setEquipment, EquipmentSlot |
Yes |
| Клавіші | ContentBuilder.key, PlayerKeyEvent |
Yes |
| Команди | Crafting.commands.command(…): підкоманди, типізовані аргументи, дозволи, псевдоніми |
Так |
| Світ і гравці | World, Block, Entity, Player, Inventory, Server |
Так |
| Завдання | Crafting.scheduler: runLater, runRepeating, runAsync, runSync |
Без фонової роботи |
| Налаштування у файлах | Типізований Config у TOML, може синхронізуватися з клієнтами |
Так (config) |
| Мережа | Crafting.network.channel(…), з власними кодеками |
Так (listen, send) |
| Збережені дані | player.data["rubies.visits"] = n, data.getInt(…); також у сутностей, блоків і світів |
Так |
| Бібліотеки Java | Будь-які | Ні |
Того, чого в API ще немає, немає в жодній мові. Не обходь це викликами класів Minecraft: мод втратить гарантію, що працюватиме й далі, коли гра зміниться.
Створи проєкт
crafting new rubies --kind kotlin
cd rubies && gradle buildAPI поки немає в публічному репозиторії Maven. Встанови його у свій локальний Maven з вихідного коду Crafting і оголоси як compileOnly: під час роботи Crafting сам надає його моду.
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 працює на Java 25crafting new генерує цей файл і проєкт, уже розділений за відповідальностями: <Name>Mod.kt, який лише з’єднує частини, content/ModContent.kt, events/Welcome.kt і commands/GemCommand.kt.
Структура
Пакети за темами, вхідний клас, який лише з’єднує частини, і один файл на частину. Посібник Як організувати мод пояснює чому; ось повний приклад мода:
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/
Вхідний клас сам нічого не робить: створює спільне й викликає кожну частину.
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()
}
}Маніфест
{
"id": "rubies",
"name": "Rubies",
"version": "1.0.0",
"kind": "mod",
"side": "both",
"entrypoints": ["com.example.rubies.RubiesMod"],
"depends": { "crafting": ">=0.1.0" }
}| Поле | Що це |
|---|---|
id |
2–64 символи: малі літери, цифри, _ або -, починається з літери. Це також простір назв для твоїх предметів і блоків. |
kind |
mod або plugin (лише сервер, з перезавантаженням; дивись нижче). |
side |
both, client або server. Клієнтський мод мовчки ігнорується на виділеному сервері. |
entrypoints |
Класи, що реалізують CraftingMod. |
depends, optional, incompatible |
Id → діапазон версій: >=1.2 <2, ^1.4, ~1.4.2, 1.20.x, [1.0,2.0). Якщо щось не так, Crafting повідомляє про всі проблеми одразу, з порадою. |
loadAfter, loadBefore |
Порядок завантаження без жорсткої залежності. |
Життєвий цикл
interface CraftingMod {
fun onLoad(ctx: ModContext)
fun onReady(ctx: ModContext) {}
fun onShutdown(ctx: ModContext) {}
}onLoad: зареєструй вміст, події й команди.- Crafting створює блоки й предмети в грі. Відтепер реєстрація вмісту викидає
RegistryFrozenException. onReady: усі моди завантажено;Crafting.runtime.isModLoaded("other_mod")скаже, чи є інший мод.onShutdown: під час виходу.
Серверні плагіни
Плагін — це CraftingMod з "kind": "plugin" і "side": "server"; crafting new chatguard --kind plugin створює вхідний клас, events/ChatFilter.kt і commands/HelloCommand.kt. Він не додає блоків чи предметів, тож гравці зі звичайним Minecraft без модів можуть заходити на сервер, де є лише плагіни. Ти вмикаєш, вимикаєш і перезавантажуєш його без перезапуску: onLoad і onReady виконуються, коли його вмикають, onShutdown — коли вимикають або перезавантажують.
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 активний") }
}
}
override fun onShutdown(ctx: ModContext) = ctx.logger.info("ChatGuard вимкнено")
}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("&cБудь ласка, спілкуйся дружньо")
}
}
}
}Усе, що реєструє плагін, прив’язане до нього. /crafting reload chatguard викликає onShutdown, прибирає його слухачів, команди й завдання, знову читає jar і викликає onLoad. У режимі розробки це відбувається автоматично, коли ти замінюєш jar.
| Мод | Плагін | |
|---|---|---|
| Тека | mods/ |
plugins/ |
| Сторона | Клієнт і/або сервер | Лише сервер |
| Нові блоки й предмети | Так | Ні |
| Перезавантаження без перезапуску | Ні | Так |
| Можуть заходити клієнти без модів | Ні, якщо він додає вміст | Так |
Java
Те саме API, та сама структура. З Java точкою входу є Crafting.INSTANCE, ти передаєш кожен параметр, що має значення за замовчуванням, а слухач повертає 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;
});
}
}Старі способи
Вони досі працюють, але позначені як застарілі й мають заміну:
| Раніше | Тепер |
|---|---|
class X : CraftingPlugin() з onEnable/onDisable |
CraftingMod з "kind": "plugin" |
Методи з @Subscribe і events.register(obj) |
events.on<E> { } (Java: events.subscribe(E.class, …)) |
@Mod("id") на класі |
Клас у entrypoints маніфесту |
EntityDamageEvent.source |
damageType (id, наприклад minecraft:fall) |
world.setBlockState(x, y, z, "…[open=true]") |
world.setBlock(x, y, z, type, mapOf("open" to true)) |
Правила
- Не імпортуй нічого з
net.minecraft. Саме це дає моду змогу працювати й далі, коли гра змінюється. - Реєструй вміст лише в
onLoad. - Завдання
runAsyncне чіпає світ; повертайся в ігровий потік черезrunSync.