Руководства

CraftScript

Язык Crafting. Ключевые слова на английском, короткий синтаксис, никакой компиляции: ты сохраняешь файл, и мод перезагружается. Он подходит и для мода в десять строк, и для мода из сорока файлов с классами и типами.

Что можно сделать уже сегодня

Область Как
Предметы item "ruby" { … }: редкость, мерцание, еда, инструмент, прочность, текстура, вкладка творческого режима, действие при использовании.
Блоки block "ruby_ore" { … }: прочность, взрывоустойчивость, свет, звук, инструмент и минимальный уровень, дроп, трение, прозрачность, действие при использовании. Формы и модели: смотри Формы блоков. Лестницы, двери, заборы, сундуки, таблички, посевы, руды и другие виды блоков игры, сиденья и блоки высотой в два: смотри Виды блоков. Блоки также могут реагировать на шаги и тики, быть порталами, иметь состояния, использовать редстоун и хранить предметы: см. Блоки, предметы и рецепты.
Генерация мира ore, tree, patch, structure, biome: то, что появляется в новых чанках; terrain, biome_map, carver и структуры с plan/build для рельефа, биомов, пещер и построек, созданных кодом. Смотри Генерация мира и Генерация кодом.
Существа creature "ghost" { … }: тело игрового существа с твоей текстурой или собственной 3D-моделью, характеристиками, поведением, целями и добычей, а также естественное появление, приручение, разведение, езда, дальние атаки и анимации по ключевым кадрам. Смотри Существа.
Звуки и частицы sound "chime" { file: … }, particle "spark" { textures: … }; используются по id. Смотри Звуки и частицы.
Рецепты recipe shaped, shapeless, smelting. Модели, добыча, теги и переводы создаются за тебя.
События on join, on break(e)… все события, с приоритетом и отменой.
Броня, оружие, перезарядка armor: { slot, defense, texture }, чтобы надевать, on_hit при ударе, cooldown между использованиями. См. Блоки, предметы и рецепты.
Эффекты, атрибуты, экипировка e.add_effect(…), e.attribute(…), e.set_attribute(…), e.set_equipment(…). См. Мир и игроки.
Клавиши key "dash" { label: "Dash", default: "R" } и on key(e); события сущностей on interact_entity и on attack. См. Интерфейс и События.
Команды command pay(to: player, amount: int) requires "eco.pay", с псевдонимами, описанием и правами.
Время every 5 minutes { }, after 10 seconds { }, on tick.
Мир и игроки Блоки с состояниями, сущности, взрывы, частицы, звуки, время суток, сундуки, таблички; инвентарь, здоровье, титры, телепортация. Смотри Мир и игроки.
Интерфейс form(…) и player.show_form, server.boss_bar, player.set_sidebar. Смотри Интерфейс.
Сохранённые данные p.data.set("rubies.visits", n), p.data.get_int("rubies.visits"): у игроков, сущностей, блоков и миров; переживают перезапуск. Смотри Мир и игроки.
Настройки и сеть config("settings", { … }), listen, send, send_to_server. Смотри Настройки и сеть.
Большие моды Необязательные типы, классы с наследованием, перечисления, модули, константы.

Только в Kotlin: фоновая работа, библиотеки Java и, в генерации мира, фичи и теги, созданные кодом.

Файлы

Мод — это один файл (mods/gems.craft) или, что лучше, когда он разрастается, папка с main.craft и файлами, которые он импортирует. main.craft начинается с заголовка mod, который задаёт id, название и версию:

mods/rubies/main.craftCraftScript
mod "rubies" { name: "Rubies", version: "1.0.0", author: "you" }

// What exists
import "content/items"
import "content/ores"
import "content/world"
import "content/dimensions"
import "content/creatures"
import "content/sounds"
import "content/particles"
import "content/recipes"

// What happens
import "events/mining"
import "events/hunting"
import "events/welcome"
import "commands/realm"
import "commands/rubies"
import "commands/shop"

Как разложить остальное по папкам и почему: Как организовать мод.

Значения и переменные

lib/values.craftCraftScript
let coins = 100
const MAX_HOMES = 3                          // const: нельзя присвоить заново
let greeting = "У тебя {coins * 2} монет"
let names = ["Ana", "Ben"]
let prices = { gold: 5, ruby: 2 }

names.add("Cleo")
prices["diamond"] = 9
log(len(names), prices.gold, prices.keys())

Числа (всегда дробные), строки, true/false, null, списки и словари. В двойных кавычках подставляется {expression}; \{ записывает фигурную скобку как есть. В одинарных кавычках подстановки нет. Цвета чата пишутся через &: "&aЗелёный".

Управление потоком

lib/flow.craftCraftScript
fun describe(coins: number, banned: bool): string {
    if coins > 100 and not banned {
        return "богач"
    } else if coins == 0 {
        return "без гроша"
    }
    return "нормально"
}

fun countdown() {
    for i in 1..5 { broadcast("{6 - i}…") }    // диапазон включительно
    for p in players() { p.send("Вперёд!") }
}

fun who(target: Player?, fallback: Player): string {
    let p = target ?? fallback                  // ?? : значение, если null
    return p?.name ?? "никто"                   // ?. : безопасно при null
}

Функции

lib/rewards.craftCraftScript
fun give_rubies(p: Player, amount: number = 1) {
    p.give(item("rubies:ruby", amount))
}

fun healthy(): list<Player> {
    return players().filter(fun(p) { return p.health > 10 })
}

Параметры со значениями по умолчанию, return и анонимные функции через fun(x) { … } — так их передают в map, filter или on_use.

События

Внутри on свойства события доступны как переменные (player, message, block…), или можно дать событию имя: on chat(e). e.cancel() отменяет его, если оно отменяемое.

events/chat.craftCraftScript
const BLOCKED = ["spam", "scam"]

on chat(e) priority high {
    if BLOCKED.any(fun(w) { return e.message.contains(w) }) {
        e.cancel()
        e.player.send("&cПожалуйста, будь вежлив")
    }
}

Команды

commands/heal.craftCraftScript
command heal(target: player?, amount: number?) requires "rubies.heal" alias "h" describe "Вылечить игрока" {
    let p = target ?? player
    if p == null { return "Укажи игрока" }
    p.health = min(p.max_health, p.health + (amount ?? 20))
    return "Игрок {p.name} вылечен"
}

Внутри команды доступны sender, player (или null, если её запускает консоль) и args. То, что ты вернёшь через return, становится ответом. Если аргумента не хватает или у него неверный тип, игрок видит правильный синтаксис.

Время

events/reminders.craftCraftScript
every 5 minutes { broadcast("&6Не забудь проголосовать") }

after 10 seconds { log("сервер работает уже 10 секунд") }

Большие моды

Необязательные типы

Где ты пишешь тип, он проверяется; где не пишешь, код остаётся свободным. Даже если не писать ни одного, проверка знает типы всего API:

events/welcome.craftCraftScript
on join {
    player.helth = 20          // опечатка
    player.name = "Admin"      // свойство только для чтения
    player.give()              // не хватает аргумента
}
crafting checkТекст
welcome.craft:2: error: Player has no property 'helth'. Did you mean 'health'?
welcome.craft:3: error: Player.name is read-only
welcome.craft:4: error: give() expects 1 argument(s), got 0
lib/heal.craftCraftScript
fun heal(p: Player, amount: number = 20): bool {
    p.health = min(p.max_health, p.health + amount)
    return true
}

// heal(player, "много")  →  error: Argument 'amount' of heal() must be number, got string

Типы: number, string, bool, id, list<T>, map<T>, T? (может быть null), any, void, fun, типы API (Player, World, Block, Entity, ItemStack, Location, Inventory…), события, а также твои собственные классы и перечисления. После проверки на null проверка знает, что значение существует:

commands/home.craftCraftScript
command home(target: player?) {
    if target == null { return "Укажи игрока" }
    target.teleport(world().block_at(0, 80, 0).location)   // здесь target уже не null
}

Классы

lib/wallet.craftCraftScript
class Wallet {
    coins: number = 100

    fun pay(n: number): bool {
        if n > this.coins { return false }
        this.coins -= n
        return true
    }
}

class VipWallet extends Wallet {
    fun pay(n: number): bool {
        return super.pay(n / 2)      // VIP платят половину
    }
}

Присвоить поле, которое класс не объявляет, — ошибка (так опечатки не проскакивают). Если класс определяет init(…), это конструктор; если нет, аргументы заполняют поля по порядку. is(value, Class) проверяет тип.

Перечисления

lib/rank.craftCraftScript
enum Rank { PEASANT, KNIGHT, LORD, KING }

fun rank_for(coins: number): Rank {
    if coins >= 1000 { return Rank.LORD }
    return Rank.PEASANT
}

// Rank.LORD.name → "LORD", Rank.values() → [PEASANT, …], Rank.from("king") → KING

Модули

events/economy.craftCraftScript
import { VipWallet } from "/lib/wallet"
import { rank_for } from "/lib/rank"

let wallets = {}

on join {
    wallets[player.name] = VipWallet()
    player.send("Ранг: {rank_for(wallets[player.name].coins).name}")
}

Всё, что файл объявляет вне блоков, можно импортировать: функции, классы, перечисления, константы. Каждый файл выполняется только один раз, даже если его импортируют несколько файлов.

Проверка, перезагрузка и безопасность

  • Всё проверяется при загрузке мода: ошибка не даёт ему загрузиться и говорит, где она; предупреждение идёт в журнал. crafting check main.craft делает то же самое из терминала, проходя по импортам.
  • В режиме разработки (-Dcrafting.dev=true, всегда включён в crafting sandbox) сохранение перезагружает мод; то же делает /crafting reload <id>. Новому предмету или блоку нужен перезапуск игры; всё остальное меняется сразу.
  • Скрипт видит только API Crafting: он не может читать файлы, открывать соединения или использовать классы Java.
  • У каждого события, команды или таймера есть лимит в 2 000 000 шагов: бесконечный цикл обрывается с ошибкой, а не замораживает сервер.
  • Ошибка во время выполнения записывается в журнал с файлом и строкой, а сервер продолжает работать.

Все функции, операторы и члены: Справочник CraftScript.