ガイド

CraftScript

Crafting の言語です。英語のキーワード、短い構文、コンパイル不要。ファイルを保存すれば Mod が再読み込みされます。10 行の Mod にも、クラスと型を使う 40 ファイルの Mod にも使えます。

今日できること

分野 方法
アイテム item "ruby" { … }:レア度、エンチャントの輝き、食料、ツール、耐久値、テクスチャ、クリエイティブタブ、使用時の動作。
ブロック block "ruby_ore" { … }:硬さ、爆発耐性、明るさ、サウンド、ツールと最低ティア、ドロップ、摩擦、透過、使用時の動作。形とモデルはブロックの形を参照してください。階段、ドア、フェンス、チェスト、看板、作物、鉱石などゲームの他の種類、座席、高さ 2 ブロックのブロックは、ブロックの種類を参照してください。ブロックは、踏まれたりティックに反応したり、ポータルになったり、ステートを持ったり、レッドストーンを使ったり、アイテムを保持したりもできます。ブロック、アイテム、レシピを参照してください。
ワールド生成 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。設定とネットワークを参照してください。
大規模な Mod 省略可能な型、継承できるクラス、列挙型、モジュール、定数。

Kotlin のみ:バックグラウンド処理、Java ライブラリ、そしてワールド生成では、コードで作るフィーチャーとタグ。

ファイル

Mod は 1 つのファイル(mods/gems.craft)か、大きくなってきたら、main.craft とそれがインポートするファイルを入れたフォルダーにするのがおすすめです。main.craft は、id、名前、バージョンを示す mod ヘッダーで始まります。

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"

残りをどうフォルダーに分けるか、そしてその理由は Mod の整理 を参照してください。

値と変数

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} を展開し、\{ で波括弧そのものを書けます。シングルクォートは展開しません。チャットの色は & で書きます:"&aGreen"。

制御フロー

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 秒経ちました") }

大規模な Mod

省略可能な型

型を書いた場所では型が強制され、書かない場所ではコードは自由なままです。何も書かなくても、チェッカーは 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, "a lot")  →  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}")
}

ファイルがどのブロックの外でも宣言したものは、すべてインポートできます:関数、クラス、列挙型、定数。複数のファイルからインポートされても、各ファイルは一度しか実行されません。

チェック、再読み込み、安全性

  • Mod の読み込み時にすべてがチェックされます。エラーがあると読み込まれず、場所が示されます。警告はログに出ます。crafting check main.craft はターミナルから同じことを、インポートをたどりながら行います。
  • 開発モード(-Dcrafting.dev=true、crafting sandbox では常にオン)では保存すると Mod が再読み込みされます。/crafting reload <id> でも同じです。新しいアイテムやブロックにはゲームの再起動が必要ですが、それ以外はすぐに変わります。
  • スクリプトから見えるのは Crafting の API だけです。ファイルを読む、接続を開く、Java のクラスを使うことはできません。
  • 各イベント、コマンド、タイマーには 2,000,000 ステップの上限があります。無限ループはサーバーを止めるのではなく、エラーで打ち切られます。
  • 実行時エラーはファイルと行とともにログに記録され、サーバーは動き続けます。

すべての関数、演算子、メンバーは CraftScript リファレンス にあります。