API
Блоки, предметы и рецепты
Контент объявляется как список свойств. Crafting создаёт настоящие игровые объекты в нужный момент и генерирует нужные им модели, таблицы добычи, теги, рецепты и переводы. Ты не пишешь ни одного файла игровых данных.
Предметы
item "ruby" { rarity: "rare" }
item "ruby_apple" {
food: { nutrition: 8, saturation: 1.2, always_edible: true }
glint: true
}
item "ruby_pickaxe" { tool: { type: "pickaxe", tier: "diamond", speed: 9 }, tab: "tools_and_utilities" }| Свойство | Значения | По умолчанию |
|---|---|---|
stack или max_stack |
1–99 | 64 |
rarity |
common, uncommon, rare, epic (цвет названия) |
common |
glint |
мерцание зачарования | false |
durability |
число; принудительно задаёт stack: 1 |
0 |
fireproof |
не горит в лаве и огне | false |
food |
число или { nutrition, saturation, always_edible } |
— |
tool |
"pickaxe" или { type, tier, damage, speed }; type: pickaxe, axe, shovel, hoe, sword |
— |
tab |
вкладка инвентаря творческого режима (ниже) | ingredients |
texture |
путь к текстуре | item/<id> |
on_use |
fun(player, stack); возвращает true, если обрабатывает использование |
— |
armor |
{ slot, defense, toughness, knockback_resistance, texture } |
— |
on_hit |
fun(attacker, target, stack) |
— |
cooldown |
секунды | 0 |
Броня, оружие и перезарядка
Предмет с armor можно надеть. slot — это head, chest, legs или feet. defense, toughness и knockback_resistance (от 0 до 1) — значения брони из игры: у железного нагрудника 6 защиты, у алмаза 2 прочности.
item "frost_helmet" { texture: "minecraft:item/iron_helmet", armor: { slot: "head", defense: 3, texture: "frost" } }
item "frost_chestplate" { armor: { slot: "chest", defense: 8, toughness: 2 }, durability: 400 }texture — внешний вид надетой брони. frost означает textures/entity/equipment/humanoid/frost.png в моде и, для ног, textures/entity/equipment/humanoid_leggings/frost.png, с той же раскладкой, что и текстуры брони игры. Без неё броня выглядит как железная.
on_hit срабатывает, когда предметом бьют, будь то игрок или моб.
item "frost_blade" {
texture: "minecraft:item/diamond_sword"
tool: "sword"
cooldown: 3
on_hit: fun(attacker, target, stack) {
target.add_effect("minecraft:slowness", 4, 4)
target.world.spawn_particle("minecraft:snowflake", target.location.add(0, 1, 0), 20, 0.4, 0.02)
}
}cooldown (в секундах) блокирует предмет на это время после каждого использования, которое обработал его on_use, и панель быстрого доступа это показывает. Из кода: player.set_cooldown("minecraft:feather", 2) и player.has_cooldown(id). В Kotlin: armor(EquipmentSlot.HEAD, defense = 3, texture = "frost") для брони и onHit = { ctx -> … } для оружия (ctx.attacker, ctx.target, ctx.stack).
item("frost_helmet") { armor(EquipmentSlot.HEAD, defense = 3, texture = "frost") }Блоки
block "ruby_ore" {
hardness: 3
tool: "pickaxe"
tier: "iron"
loot: [
{ item: "rubies:ruby", count: [1, 2] },
{ item: "minecraft:diamond", chance: 0.1 }
]
}
block "ruby_block" { hardness: 5, light: 7, tool: "pickaxe", sound: "metal" }
block "ruby_lamp" { hardness: 0.3, light: 15, sound: "glass", transparent: true, tab: "functional_blocks" }
block "ruby_bricks" { hardness: 2, tool: "pickaxe", drops: "ruby", drop_count: [2, 4] }| Свойство | Значения | По умолчанию |
|---|---|---|
hardness |
число; -1 = неразрушимый |
1.5 |
resistance |
взрывоустойчивость | = hardness |
light |
0–15 | 0 |
light_when |
состояние-переключатель, например "lit": блок светится, только пока оно включено (см. «Состояния блока») |
всегда |
sound |
stone, wood, gravel, grass, metal, glass, wool, sand | stone |
like |
блок, по образцу которого он сделан, игровой ("minecraft:end_stone") или твой: прочность, взрывоустойчивость, звук и то, нужен ли правильный инструмент, чтобы что-то выпало, берутся у этого блока |
— |
kind |
один из видов блоков игры: лестница, дверь, сундук, посев… (см. Виды блоков) | cube |
tool, tier |
инструмент, без которого блок ничего не выбрасывает, и минимальный материал этого инструмента: wood, stone, iron, diamond, netherite | —, wood |
drops, drop_count |
один предмет: id; число или [min, max] |
сам блок, 1 |
loot |
несколько предметов: [{ item, count, chance, tool, fortune }, …]. Каждый разыгрывается отдельно; count — число или [min, max], а chance — от 0 до 1 (по умолчанию 1). tool и fortune: см. «Дроп: инструменты и удача» ниже |
— |
friction |
0.6 обычное, 0.98 лёд | 0.6 |
transparent |
сквозь него видно (стекло, листва) | false |
has_item |
также регистрировать предмет блока; без него блок ничего не выдаёт, если drops или loot не говорят, что именно |
true |
tab |
вкладка инвентаря творческого режима | building_blocks |
on_use |
fun(player, block); возвращает true, если обрабатывает использование. Игрок, который крадётся с предметом в руке, вместо этого ставит его, как у игровых блоков |
— |
Вкладки: building_blocks, colored_blocks, natural_blocks, functional_blocks, redstone_blocks, tools_and_utilities, combat, food_and_drinks, ingredients, spawn_eggs. В Kotlin они лежат в CreativeTabs.
Чтобы существо при гибели выбрасывало что-то сверх обычного, добавь это в e.drops в событии entity_death (смотри События).
Блоки не в форме куба, блоки видов игры, существа, руды, звуки и частицы объявляются так же, и у них есть свои страницы: Формы блоков, Виды блоков, Существа, Генерация мира, Звуки и частицы.
Дроп: инструменты и удача
Для предмета в loot может понадобиться инструмент, а удача может дать его больше:
block "ruby_ore" {
kind: "ore"
tool: "pickaxe"
tier: "iron"
xp: [1, 4]
loot: [
{ item: "ruby", count: [1, 2], fortune: true },
{ item: "ruby_ore", tool: "silk_touch" }
]
}
block "frost_leaves" {
kind: "leaves"
loot: [
{ item: "frost_leaves", tool: "shears" },
{ item: "frost_sapling", chance: 0.05, fortune: true }
]
}tool: "silk_touch": выпадает, только когда блок сломан с шёлковым касанием.tool: "shears": только с ножницами или шёлковым касанием, как выпадает сама листва и лозы в игре.- Когда для каких-то предметов нужен инструмент, предметы без него выпадают, только если этот инструмент не использован: руда выше выпадает сама с шёлковым касанием, а иначе выдаёт рубины.
fortune: true: удача даёт его больше, больше егоcount, как у игровых руд, или лучшийchance, как у саженцев из листвы.
В Kotlin: drop("ruby", 1..2, tool = null, fortune = true) и drop("ruby_ore", tool = DropTool.SILK_TOUCH).
block("ruby_ore") {
kind = BlockKind.ORE
xp = 1..4
drop("ruby", 1..2, tool = null, fortune = true)
drop("ruby_ore", tool = DropTool.SILK_TOUCH)
}Рецепты
recipe shaped "ruby_block" {
pattern: ["RRR", "RRR", "RRR"]
keys: { R: "ruby" }
result: "ruby_block"
}
recipe shapeless "rubies_from_block" { ingredients: ["ruby_block"], result: "ruby", count: 9 }
recipe smelting "ruby_from_ore" { input: "ruby_ore", result: "ruby", xp: 1.0 }Поведение при использовании
on_use — единственная логика, которая живёт рядом с данными. Если она перерастает пару строк, напиши функцию в lib/ и импортируй её:
import { lift } from "/lib/teleport"
item "lift_wand" { stack: 1, rarity: "uncommon", on_use: lift }fun lift(player, stack) {
player.teleport(player.location.add(0, 10, 0))
return true
}Блоки, которые реагируют
Блок может выполнять код, когда что-то происходит с ним или рядом с ним. Каждый обработчик получает блок и, где это уместно, сущность.
| Свойство | Выполняется, когда |
|---|---|
on_step: fun(block, entity) |
сущность наступает на блок. |
on_inside: fun(block, entity) |
сущность находится внутри блока. Блок должен пропускать сущности: портал, растение или неполная форма. |
on_random_tick: fun(block) |
время от времени, случайно, как рост посевов: примерно раз в 68 секунд на блок. |
on_tick: fun(block) |
прошло время, заказанное через block.schedule(ticks) (20 тиков = 1 секунда). |
on_neighbor_change: fun(block) |
изменился соседний блок: его поставили, сломали или подали на него сигнал. |
on_placed: fun(block, entity) |
кто-то поставил блок; entity — это кто. |
block "jump_pad" { on_step: fun(b, e) { e.velocity = vector(0, 1.1, 0) } }
block "blinker" {
light: 7
on_tick: fun(b) {
b.world.spawn_particle("minecraft:end_rod", b.location.add(0.5, 1.2, 0.5), 4, 0.1, 0.01)
b.schedule(20)
}
on_neighbor_change: fun(b) { b.schedule(1) }
}В Kotlin обработчики называются onStep, onInside, onRandomTick, onTick, onNeighborChange и onPlaced. Каждый получает BlockEventContext с block и, если есть, entity:
block("pad") { onStep = { ctx -> ctx.entity?.velocity = Vector(0.0, 1.1, 0.0) } }Порталы
Блок с portal переносит всякого, кто находится внутри, в другое измерение. Укажите измерение назначения или to и back, чтобы выбрать, куда он ведёт оттуда:
block "sky_gate" { portal: "gatemod:sky", light: 11 }
block "sky_gate_two_way" { portal: { to: "gatemod:sky", back: "minecraft:overworld" } }Сущность появляется на земле в тех же x и z и находит там блок портала, чтобы вернуться; если поблизости его нет, рядом с местом прибытия ставится новый. Прежде чем снова войти в портал, сущности некоторое время ждут. Блоки порталов пропускают сущности. Само измерение объявляется через dimension (см. @dim@). В Kotlin:
block("sky_gate") { portal("gatemod:sky") }Состояния блока
states даёт блоку значения, которые он помнит и может показывать:
block "lamp" {
model: "block/lamp"
states: ["facing", "lit"]
on_use: fun(p, b) { b.set_state("lit", !b.state("lit")); return true }
on_neighbor_change: fun(b) { b.set_state("lit", b.redstone_power > 0) }
}
block "crop" { states: [{ stage: 3 }, { wet: true }] }"facing": север, восток, юг или запад. Блок поворачивается лицом к игроку, который его ставит, и модель поворачивается вместе с ним."lit": одно имя означает переключатель вкл/выкл, по умолчанию выключен.{ lit: true }делает его включённым по умолчанию.{ stage: 3 }: число от 0 до указанного максимума (от 1 до 15)."facing_all": как"facing", но ещё вверх и вниз. Блок смотрит на игрока, который его ставит, а модель переворачивается для направлений вверх и вниз.{ shape: ["top", "middle", "bottom"] }: одно из нескольких имён, по умолчанию первое. Имена состоят из строчных букв, цифр и_.
Читать и менять их можно через block.state("lit") и block.set_state("lit", true). Это работает и для блоков самой игры, например facing и lit печи.
Состояние, отличное от значения по умолчанию, может выглядеть иначе. Добавьте текстуру в textures/block/<id>_<имя>.png (включённый переключатель) или <id>_<имя><n>.png (число) или <id>_<значение>.png (имя) либо модель в models/block/<id>_<имя>.json. Сначала пробуется вся комбинация (<id>_lit_stage2), затем каждое состояние отдельно. В примере lamp.json и lamp_lit.json — две модели мода, например с родителем minecraft:block/orientable и текстурами лицевой стороны, боковой и верха.
Свет по состоянию. light_when: "lit" делает так, что light блока светится, только пока этот переключатель включён, как у лампы:
block "lamp" {
light: 15
light_when: "lit"
states: ["lit"]
on_use: fun(p, b) { b.set_state("lit", !b.state("lit")); return true }
}В Kotlin: facing(), facingAll(), toggle("lit"), number("stage", 3), choice("shape", "top", "middle", "bottom"), а в коде — block.state(…) и block.setState(…):
block("lamp") {
model = "block/lamp"
facing()
toggle("lit")
number("stage", 3)
}Эффекты
Мод может добавлять собственные эффекты состояния. Они отображаются на экране игрока так же, как эффекты игры, и накладываются и проверяются так же.
effect "frozen" {
color: "#88CCFF"
kind: "harmful"
on_tick: fun(e, level) { e.damage(level) }
}color: цвет его частиц и фона значка.kind:beneficial,harmfulилиneutral.on_tick:fun(e, level), выполняется каждую секунду, пока у сущности есть эффект, с его уровнем (1 — это I).- Значок — это
textures/mob_effect/<id>.pngмода.
Наложите его через e.add_effect("mymod:frozen", 3, 1) (id, секунды, уровень) и проверьте через e.has_effect("mymod:frozen"), как с эффектами игры (см. Мир и игроки). На Kotlin:
content.effect("frozen") {
color = 0x88CCFF
kind = EffectKind.HARMFUL
onTick = { e, level -> /* … */ }
}Редстоун
block "battery" {
texture: "minecraft:block/copper_block"
redstone: true
on_use: fun(p, b) { if b.state("power") > 0 { b.set_state("power", 0) } else { b.set_state("power", 15) }; return true }
}redstone: true заставляет блок излучать сигнал такой силы, как его состояние power — число от 0 до 15, которое добавляется автоматически. b.set_state("power", 15) включает его, а провода, лампы и двери вокруг следуют за ним. block.redstone_power — мощность, которую блок получает от окружения, а on_neighbor_change выполняется, когда она меняется (так работает лампа выше).
В Kotlin: redstone = true и block.redstonePower.
block("battery") {
texture = "minecraft:block/copper_block"
redstone = true
// in a handler: ctx.block.redstonePower
}Инвентари
block "crate" { texture: "minecraft:block/barrel_side", inventory: 27 }inventory: 27 даёт блоку столько ячеек (от 9 до 54, рядами по 9). Предметы сохраняются между перезапусками, при использовании открывается экран сундука (если только его on_use не забрал клик), а при разрушении выпадает всё, что в нём лежит. От 1 до 8 ячеек экрана нет: мод сам кладёт предметы и забирает их.
В коде block.inventory (работает и для сундуков и бочек игры) предоставляет set(slot, item(…)), get(slot), count(id) и add(…). В Kotlin: inventory = 27.
block("crate") {
texture = "minecraft:block/barrel_side"
inventory = 27
}Предметы, показанные на блоке. shows_item: 14 заставляет предмет из первой ячейки блока парить над ним, вращаясь, на столько пикселей выше его низа. Его видят все игроки, и он исчезает, когда ячейка пустеет или блок ломается. Пьедестал берёт предмет из руки игрока и возвращает его:
block "pedestal" {
shape: [[2, 0, 2, 14, 12, 14]]
inventory: 1
shows_item: 14
on_use: fun(p, b) {
let inv = b.inventory
if inv.get(0) == null and p.held_item != null {
inv.set(0, p.held_item)
p.set_held_item(null)
} else if inv.get(0) != null {
p.give(inv.get(0))
inv.set(0, null)
}
return true
}
}player.set_held_item(stack) кладёт предмет в основную руку игрока или опустошает её через null. В Kotlin: showsItem = 14 и player.setHeldItem(stack).
block("pedestal") {
box(2, 0, 2, 14, 12, 14)
inventory = 1
showsItem = 14
onUse { ctx ->
val inv = ctx.block.inventory!!
val held = ctx.player.heldItem
val shown = inv[0]
if (shown == null && held != null) { inv[0] = held; ctx.player.setHeldItem(null) }
else if (shown != null) { ctx.player.give(shown); inv[0] = null }
true
}
}Текстуры
Текстуры поставляются внутри мода; никому не нужно устанавливать набор ресурсов. Имя файла — это id:
| Мод | Где |
|---|---|
| CraftScript (папка) | mods/rubies/textures/item/ruby.png, mods/rubies/textures/block/ruby_ore.png |
| Kotlin или Java | src/main/resources/assets/rubies/textures/item/ruby.png |
Если мод кладёт в эти же папки собственные модели, рецепты или таблицы добычи, Crafting использует их.
На Kotlin
package com.example.rubies.content
object Blocks {
fun register(content: ContentBuilder) {
content.block("ruby_ore") {
hardness = 3f
requiresTool = ToolType.PICKAXE
minTier = ToolTier.IRON
drop("ruby", 1..2)
drop("minecraft:diamond", chance = 0.1)
}
content.block("ruby_block") { hardness = 5f; luminance = 7; sound = SoundGroup.METAL }
}
}Id. Без пространства имён id принадлежит твоему моду ("ruby" — это rubies:ruby). Для контента игры указывай пространство имён: "minecraft:diamond". Регистрировать контент в пространстве имён minecraft нельзя.