メインコンテンツまでスキップ

Luaスクリプティング:はじめに

このページでは、FreeMinecraftModels のプロップやカスタムアイテム向けの最初の Lua スクリプトを、空のファイルから動作するインタラクティブなスクリプトになるまで順を追って解説します。読み終える頃には、フック、context、プロップ API とアイテム API、そしてすべてのスクリプトファイルに共通する基本構造が理解できているはずです。

基礎に慣れたら、関連ページに進んでください。

実験的機能

Lua のプロップスクリプトとアイテムスクリプトは現在実験的な機能です。FreeMinecraftModels の進化にともない、フック名、ヘルパーメソッド、挙動は今後も変更される可能性があります。本番サーバーで使用する前に十分にテストしてください。

EliteMobs の Lua との関係

FreeMinecraftModels は MagmaCore の Lua ランタイムを使用しています。すでに EliteMobs 向けに Lua パワーを書いたことがあるなら、中心となる概念 -- テーブルを返すスクリプトファイル、api_version、フック、context、状態、クールダウン、スケジューリング、サンドボックス -- は馴染みのあるものに感じられるでしょう。ただし、具体的なフック名と context のメソッド名はプラグインごとに異なります。

  • EliteMobs のスクリプトはボスに対して動作し、on_boss_damaged_by_playeron_enter_combat などのフックを持ちます。
  • FMM のプロップスクリプトはプロップに対して動作し、on_right_clickon_left_clickon_zone_enter などのフックを持ちます。
  • FMM のアイテムスクリプトはカスタムアイテムに対して動作し、on_equipon_attack_entityon_consumeon_game_tick などのフックを持ちます。

このページで解説する context.worldcontext.zonescontext.schedulercontext.statecontext.log の各 API は、FreeMinecraftModels/MagmaCore 版のものです。EliteMobs の NPC スクリプトは同じ汎用 MagmaCore テーブルに加えて context.npc を使用し、EliteMobs のボスパワーはいくつかのテーブルについてボス専用のバリエーションを使用します。このページでは、FMM のプロップとアイテムに固有の内容を扱います。


プロップスクリプトとは

プロップスクリプトは、plugins/FreeMinecraftModels/scripts/ フォルダーに置く単独の .lua ファイルです。モデルファイルと同じ場所に置かれた YAML 設定ファイルから参照され、プロップがワールドにスポーンされるたびに実行されます。

プロップスクリプトが得意なこと

プロップスクリプトは、次のような場面で威力を発揮します。

  • プレイヤーのクリックに反応するインタラクティブなプロップ(扉、レバー、ボタン)
  • プレイヤーに破壊されない無敵の装飾プロップ
  • プレイヤーがエリアに出入りしたことを検知する近接トリガー
  • インタラクション時やタイマーでアニメーションを再生するプロップ
  • クリックされたり近づかれたりしたときに音を鳴らすプロップ
  • 静的な装飾を超えたロジックを必要とする、あらゆるプロップの挙動

プロップが純粋に装飾目的でインタラクションが不要なら、スクリプトは必要ありません。


アイテムスクリプトとは

アイテムスクリプトは、プロップスクリプトと同じ .lua ファイル形式、同じ scripts/ フォルダーを使用します。違いは、カスタムアイテム -- YML 設定ファイルで material: フィールドが設定されたモデル -- に紐付けられる点です。プロップスクリプトがプロップエンティティのワールドへのスポーン時に実行されるのに対し、アイテムスクリプトはプレイヤーがカスタムアイテムを装備したとき(メインハンド、オフハンド、防具スロット)に実行され、装備を外したときに停止します。

アイテムスクリプトの仕組み

  • 有効化: プレイヤーが FMM のカスタムアイテムを装備すると、スクリプトインスタンスが生成されます。スクリプトはプレイヤーごと・アイテム種別ごとで、(player, itemId) の組み合わせにつき 1 つの ScriptInstance が存在します。
  • 無効化: アイテムの装備が外されると(有効スロットから移動、ドロップ、またはプレイヤーの切断)、スクリプトインスタンスは破棄されます。
  • アイテムの識別: カスタムアイテムは fmm_item_id の PDC(PersistentDataContainer)キーで識別されます。これはプロップの model_id とは異なります。正しくタグ付けされたアイテムを入手するには、/fmm giveitem <id> または管理メニューを使用してください。
  • context: アイテムフックは context.playercontext.itemcontext.worldcontext.statecontext.schedulercontext.log、そして該当する場合は context.event を含む context を受け取ります。

アイテムスクリプトが得意なこと

アイテムスクリプトは、次のような場面で威力を発揮します。

  • 特殊能力を持つカスタム武器(氷の剣、魔法の杖)
  • 独自の右クリック/シフトクリック動作を持つ道具
  • カスタム効果を持つ消費アイテム
  • 着用中にパッシブ効果を与える防具
  • 使用回数を追跡する、あるいは耐久回数が限られたアイテム
  • バニラの仕様を超える、あらゆる手持ちアイテムの挙動

このページの対象読者

このページは、次の 3 種類の読者を想定して書かれています。

  • EliteMobs の Lua スクリプティングをすでに知っていて、FMM 固有のフックと API を学びたい人
  • Lua スクリプティングが初めてで、プロップ向けの完全かつ正確な名称リファレンスを必要としている人
  • AI にプロップスクリプトを下書きさせていて、AI が架空のものをでっち上げたかどうか判断できるだけの詳細情報を必要としている人

役に立つプロップスクリプトを書くのに、本格的な Lua 開発者になる必要はありません。実用的なプロップスクリプトのほとんどで、本当に必要なのは次のことだけです。

  • 返すテーブルに有効なフックを置く方法
  • context から値を読み取る方法
  • if ... then return end で早期に処理を打ち切る方法
  • いくつかのヘルパーメソッドを正確に呼び出す方法

簡単なLua入門

FMM のスクリプトを書くのに Lua の専門家である必要はありません。ほとんどのスクリプトが使うのはごく一部の概念だけです。変数local x = 5)、関数function foo() end)、if 判定if x then ... end)、テーブル{key = value})、そして nil(Lua における「何もない」を表す値)です。構文は軽量で、セミコロンも波かっこも不要、ブロックを閉じるのは end だけです。

例を交えた完全な解説は、MagmaCore Lua スクリプティングエンジン — 簡単なLua入門 を参照してください。この入門はすべての Nightbreak プラグインで共通なので、一度覚えればどこでも通用します。


ファイルの配置場所

スクリプトファイル

.lua ファイルは中央のスクリプトフォルダーに置きます。

plugins/
FreeMinecraftModels/
scripts/
invulnerable.lua
interactive_door.lua
proximity_sound.lua

FMM は起動時に plugins/FreeMinecraftModels/scripts/ 内のすべての .lua ファイルを検出します。

モデルの scripts: リストでは、分かりやすさのために .lua 拡張子を付けて記述してください。FMM は拡張子なしのエントリーも受け付け、内部で .lua を補完します。ただしディスク上のファイルは .lua で終わっている必要があり、名前は大文字・小文字を区別します。

モデルファイルと設定ファイル

各モデルファイルは、同じディレクトリに対となる .yml 設定ファイルを持つことができます。

plugins/
FreeMinecraftModels/
models/
torch_01.fmmodel
torch_01.yml <-- torch_01 のスクリプト設定
scripts/
invulnerable.lua <-- torch_01.yml から参照される

モデルとスクリプトを結び付けるのが、この .yml 設定です。


設定ファイルの形式

モデルファイルの隣に置く YAML 設定ファイルには、次のフィールドがあります。

isEnabled: true
voxelize: false
solidify: false
scripts:
- invulnerable.lua

カスタムアイテム(プレイヤーが手に持ったり装備したりできるモデル)の場合は、さらに material フィールドを設定し、必要に応じて nameloreenchantments も指定します。

isEnabled: true
material: DIAMOND_SWORD
name: "&bFrost Blade"
lore:
- "&7A sword forged in eternal ice"
- "&7Slows enemies on hit"
enchantments:
- "SHARPNESS,5"
- "UNBREAKING,3"
scripts:
- frost_sword.lua
フィールドデフォルト備考
isEnabledbooleantrueこのプロップ/アイテムでスクリプトを有効にするかどうか
scripts文字列のリスト[]scripts/ フォルダー内の .lua スクリプトのファイル名
voxelizebooleanfalse配置を 90 度単位の回転とブロックグリッドにスナップさせる
solidifybooleanfalseプロップの占有範囲にパケットのみのバリアブロックを配置する(voxelize が必要)
materialstring""有効な Bukkit Material 名(例:DIAMOND_SWORD)。これを設定すると、モデルはプレイヤーが手に持ったり装備したりできるカスタムアイテムとなり、アイテムスクリプティングシステムが有効になります
namestring""カスタムアイテムの表示名。& カラーコードに対応
lore文字列のリスト[]アイテムのツールチップに表示される説明行。& カラーコードに対応
enchantments文字列のリスト[]アイテムに付与するエンチャント。形式:"ENCHANTMENT_NAME,LEVEL"(例:"SHARPNESS,5"

同じプロップに複数のスクリプトを紐付けることができます。各スクリプトはそれぞれ独立したインスタンスになります。

アイテム ID とスクリプト数の制限
  • アイテム ID は、YML のファイル名から拡張子を除いたものです。たとえば frost_sword.yml のアイテム ID は frost_sword になります。これが /fmm giveitemfmm_item_id の PDC キーで使われる ID です。
  • アイテムは scripts: リストのうち1 つのスクリプトしかバインドしません。FMM はエントリーを順に確認し、最初に解決できたスクリプトを使用して、それ以降のエントリーは無視します。アイテムとは異なり、プロップは解決できたすべてのスクリプトを独立したインスタンスとして実行します。
  • スクリプトのファイル名に .lua 拡張子を書かなかった場合は自動的に補完されるため、scripts: リストでは frost_swordfrost_sword.lua は同じ意味になります。

設定ファイルの遅延生成

プロップがスポーンした時点で対となる .yml ファイルが存在しない場合、FMM は isEnabled: true と空の scripts: リストを持つデフォルト設定ファイルを自動生成します。これは非同期で行われるため、最初のスポーン時にはプロップにスクリプトは適用されません。設定が生成され、そこにスクリプトのファイル名を追記して初めて適用されます。

つまり、次のような流れになります。

  1. モデルファイルを models/ に置く
  2. プロップを一度スポーンさせる(FMM が .yml を自動生成する)
  3. 生成された .yml を編集してスクリプトのファイル名を追加する
  4. プロップを再スポーンするかリロードする(これでスクリプトが有効になる)

フックリファレンス

すべての Lua プロップスクリプトファイルはテーブルを返します。そのテーブルの各キー(api_versionpriority を除く)は、以下に挙げるフックのいずれかでなければなりません。対応するゲームイベントが発生すると、ランタイムが該当する関数を呼び出します。

フック発火タイミング備考
on_spawnプロップがワールドにスポーンしたときスクリプトがバインドされた際に一度だけ実行される
on_game_tickサーバーティックごと(50 ms)スクリプトがこのフックを定義している場合のみ有効
on_destroyプロップがワールドから削除されたとき後始末用のフック
on_left_clickプレイヤーがプロップを左クリック(殴打)したときcontext.event はダメージイベント
on_right_clickプレイヤーがプロップを右クリックしたときcontext.event はインタラクションイベント
on_zone_enterプレイヤーが監視中のゾーンに入ったときゾーンの監視設定が必要
on_zone_leaveプレイヤーが監視中のゾーンから出たときゾーンの監視設定が必要
予約済みのプロップフック

現在のスクリプトバリデーターはプロップスクリプトの on_projectile_hit を受け付けますが、現行のランタイムは投射物のヒットをプロップスクリプトにまだディスパッチしません。スクリプト付きアイテムに紐付いた投射物の挙動にはアイテムの on_projectile_hit を使用し、プラグイン側でモデル化エンティティへの投射物を処理する場合は Bukkit の ModeledEntityHitByProjectileEvent API を使用してください。


アイテムフックリファレンス

アイテムスクリプトも、プロップスクリプトと同様に api_version = 1 とフック関数を含むテーブルを返します。アイテムスクリプトで使用できるフックは次のとおりです。すべてのアイテムフックは、context.playercontext.item、そして該当する場合は context.event を含む context を受け取ります。

備考の列には、基になっている Bukkit イベントの系統を記載しています。Lua ラッパーは targetblockprojectileitem といった Bukkit 固有の生フィールドを公開しません。追加の情報が必要な場合は、context.playercontext.event.player、およびエンティティ/ワールドのヘルパークエリを使用してください。

戦闘フック

フック発火タイミング備考
on_attack_entityプレイヤーがアイテムを持った状態でエンティティを攻撃したときcontext.event はダメージイベント
on_kill_entityプレイヤーがアイテムを持った状態でエンティティを倒したときcontext.event は死亡イベント
on_take_damageアイテムを装備した状態でプレイヤーがダメージを受けたときcontext.event はダメージイベント
on_shield_blockプレイヤーが盾でダメージを防いだときcontext.event はダメージイベント
on_shoot_bowプレイヤーが弓を撃ったときcontext.event は弓の発射イベント
on_projectile_hitプレイヤーが撃った投射物が何かに命中したときcontext.event は投射物のヒットイベント
on_projectile_launchプレイヤーが投射物を発射したときcontext.event は投射物の発射イベント

インタラクションフック

フック発火タイミング備考
on_right_clickプレイヤーがアイテムを持った状態で右クリックしたときcontext.event はインタラクトイベント
on_left_clickプレイヤーがアイテムを持った状態で左クリックしたときcontext.event はインタラクトイベント
on_shift_right_clickプレイヤーがアイテムを持った状態で Shift+右クリックしたときcontext.event はインタラクトイベント
on_shift_left_clickプレイヤーがアイテムを持った状態で Shift+左クリックしたときcontext.event はインタラクトイベント
on_interact_entityプレイヤーがアイテムを持った状態でエンティティを右クリックしたときcontext.event はエンティティのインタラクトイベント

装備フック

フック発火タイミング備考
on_equipアイテムが装備されたとき(有効スロットに移動したとき)状態の初期化に適した場所
on_unequipアイテムの装備が外れたとき(有効スロットから移動したとき)後始末に適した場所
on_swap_handsプレイヤーがアイテムをメインハンドとオフハンドで持ち替えたときcontext.event は持ち替えイベント
on_dropプレイヤーがアイテムをドロップしたときcontext.event はドロップイベント

ユーティリティフック

フック発火タイミング備考
on_break_blockプレイヤーがアイテムを持った状態でブロックを破壊したときcontext.event はブロック破壊イベント
on_consumeプレイヤーがアイテムを消費したとき(食料/ポーション)context.event は消費イベント
on_item_damageアイテムが耐久値のダメージを受けたときcontext.event はアイテムダメージイベント
on_fishプレイヤーが釣り竿を使用したときcontext.event は釣りイベント
on_deathアイテムを装備した状態でプレイヤーが死亡したときcontext.event は死亡イベント

ライフサイクルフック

フック発火タイミング備考
on_game_tickアイテムを装備している間、サーバーティックごと1 秒間に 20 回実行されるため、控えめに使用すること

最小ファイル規約

すべての Lua プロップスクリプトは、テーブルを return しなければなりません。

必須およびオプションのトップレベルフィールド

フィールド必須備考
api_versionはい数値現在は 1 である必要があります
priorityいいえ数値存在すれば検証されますが、FMM は現在これによるスクリプトの並べ替えを行いません。プロップは scripts: リストの順に実行され、アイテムは最初の有効なスクリプトのみをバインドします
対応するフックキーいいえ関数フックリファレンスに記載された正確なフック名のいずれかを使用する必要があります

バリデーションルール

  • ファイルはテーブルを返さなければなりません
  • api_version は必須で、現在は 1 である必要があります。
  • priority は、存在する場合は数値でなければなりません。
  • それ以外のトップレベルキーは、すべて対応するフック名でなければなりません。
  • 各フックキーは関数を指していなければなりません。
  • 未知のトップレベルキーは拒否されます。
注記

priority は MagmaCore ベースの各ランタイム間でスクリプトの可搬性を保つのに役立ちますが、FreeMinecraftModels の現行ランタイムの実行順序は設定ファイル駆動です。プロップスクリプトは、実行させたい順序どおりにモデルの scripts: リストへ並べてください。

ヘルパー関数やローカル定数は、返すテーブルの中ではなく、最後の return よりに置いてください。


動作する最初のプロップスクリプトを一歩ずつ作る

ステップ1の前に:設定を用意する

  1. モデルファイル(例:my_prop.fmmodel)を plugins/FreeMinecraftModels/models/ に置く
  2. プロップを一度スポーンさせて .yml 設定を生成する
  3. スクリプトファイルを plugins/FreeMinecraftModels/scripts/first_test.lua として作成する
  4. plugins/FreeMinecraftModels/models/my_prop.yml を編集する:
isEnabled: true
scripts:
- first_test.lua
  1. プロップを再スポーンするか、サーバーをリロードする

ステップ1:ファイルを読み込ませる

return {
api_version = 1,

on_spawn = function(context)
end
}

これがコンソールにエラーを出さずに読み込まれれば、次のことが確認できたことになります。

  • ファイルが有効な Lua であること
  • FMM が scripts/ フォルダー内でファイルを見つけたこと
  • 設定が正しくファイルを参照していること
  • 返しているテーブルの形が正しいこと

ステップ2:プロップに目に見えることを1つさせる

return {
api_version = 1,

on_spawn = function(context)
context.log:info("Prop script loaded for: " .. (context.prop.model_id or "unknown"))
end
}

サーバーコンソールを確認してください。ログメッセージが表示されていれば、フックが発火しています。

ステップ3:プレイヤーのクリックに反応する

return {
api_version = 1,

on_right_click = function(context)
context.log:info("Prop was right-clicked!")
end
}

ゲーム内でプロップを右クリックしてください。コンソールにメッセージが表示されれば、クリックフックが機能しています。

ステップ4:ダメージをキャンセルしてプロップを無敵にする

return {
api_version = 1,

on_left_click = function(context)
if context.event then
context.event.cancel()
end
end
}

これは既成の invulnerable.lua スクリプトが使っているパターンです。ダメージイベントをキャンセルすることで、プロップの土台となっている防具立てが破壊されないようにします。

ステップ5:クリックでアニメーションを再生する

return {
api_version = 1,

on_right_click = function(context)
context.prop:play_animation("open", true, false)
end
}

これは、プロップのモデルで "open" アニメーションを、ブレンドあり・ループなしで再生します。


context とは?

すべてのフック関数は、context という 1 つの引数を受け取ります。何かが起きるたびに FMM が渡してくれる道具箱だと考えてください。プロップ、ワールド、ゾーンなどを操作するのに必要なものがすべて入っています。

context は自分で作るものではありません。FMM が生成してフックに渡します。共有 context API(context.statecontext.logcontext.cooldownscontext.schedulercontext.worldcontext.zones)の詳細は、MagmaCore Lua スクリプティングエンジンのページを参照してください。


主な context API

利用できるものの概要は次のとおりです。詳細はプロップ API を参照してください。

  • context.prop -- (プロップスクリプト専用)プロップエンティティ。model_idcurrent_locationplay_animation()stop_animation() を提供します。

  • context.item -- (アイテムスクリプト専用)カスタムアイテム。idmaterial()get_amount()set_amount()consume()get_uses()set_uses()get_name()set_name()get_lore()set_lore()get_durability()get_durability_percentage()use_durability()use_durability_percentage() を提供します。詳細はプロップ&アイテム API を参照してください。

  • context.player -- プレイヤー起点のフックにおけるプレイヤー。アイテムスクリプトではアイテムの所有者から解決され、プロップのクリックフックや汎用のゾーンフックではトリガーとなったプレイヤーから解決されます。プロップのライフサイクルフック、プロップのスケジュール済みコールバック、プレイヤーが関与しないフックでは nil になります。

  • context.event -- このフックを発生させた Bukkit イベント、またはプレイヤーアクターの薄いラッパーです。クリック、戦闘、インタラクション、汎用ゾーンの各フックで利用できます。event.playeris_cancelled を提供し、基になっている Bukkit イベントがキャンセル可能な場合は cancel() / uncancel() も提供します。targetblockprojectileitem といった Bukkit 固有のフィールドは公開しません。イベントもプレイヤーアクターも存在しないフック(on_spawnon_game_tickon_equip など)では nil になります。

  • context.state -- スクリプトインスタンスの生存期間中、値を保持する素の Lua テーブルです。context.state を参照してください。

  • context.cooldowns -- ローカルおよびグローバルのクールダウンヘルパー。通常のスクリプト単位のクールダウンには context.cooldowns:check_local("key", ticks) を使用してください。context.cooldowns を参照してください。

  • context.log -- コンソールへのログ出力。context.log を参照してください。

  • context.scheduler -- 遅延実行タスクと繰り返しタスク。context.scheduler を参照してください。

  • context.world -- ワールドの操作:パーティクル、サウンド、ブロックの問い合わせ、雷、周囲のエンティティ。context.world を参照してください。

  • context.zones -- 空間ゾーン(球、円柱、直方体)の作成と監視。context.zones を参照してください。


メソッド構文::.

Lua における :. のメソッド構文の違いについては、MagmaCore Lua スクリプティングエンジンのページを参照してください。FMM の API では両方の形式が使用できます。


コピペ用スターターテンプレート

最小の有効なプロップスクリプト

return {
api_version = 1,

on_spawn = function(context)
end
}

無敵プロップのテンプレート

return {
api_version = 1,

on_left_click = function(context)
if context.event then
context.event.cancel()
end
end
}

インタラクティブプロップのテンプレート

return {
api_version = 1,

on_spawn = function(context)
context.state.is_active = false
end,

on_right_click = function(context)
context.state.is_active = not context.state.is_active

if context.state.is_active then
context.prop:play_animation("activate", true, true)
else
context.prop:stop_animation()
end
end
}

最小の有効なアイテムスクリプト

return {
api_version = 1,

on_equip = function(context)
end
}

右クリックアクション付きアイテムのテンプレート

return {
api_version = 1,

on_right_click = function(context)
if not context.cooldowns:check_local("activate", 40) then return end

-- Your action here
context.player:send_message("&aItem activated!")
end
}

大きめのファイル構成

local ANIMATION_NAME = "idle"

local function do_something(context)
context.log:info("Doing something!")
end

return {
api_version = 1,
priority = 0,

on_spawn = function(context)
context.state.task_id = nil
end,

on_right_click = function(context)
do_something(context)
end,

on_destroy = function(context)
if context.state.task_id ~= nil then
context.scheduler:cancel(context.state.task_id)
end
end
}

最初の実践的なワークフロー

まったく新しいプロップスクリプトを作るときは、次の順序で進めてください。

  1. .lua ファイルを作成し、on_spawn が動くようにする。
  2. スクリプトのファイル名をプロップの .yml 設定に追加する。
  3. 実際に使いたいフック(例:on_right_click)に切り替える。
  4. アニメーションやエフェクトの前に、まずログメッセージを追加する。
  5. 実際の効果(アニメーション、サウンド、パーティクル)を 1 つ追加する。
  6. その後で初めて、ヘルパー、状態、スケジューラーのロジック、ゾーンを追加する。

この順序なら一度に変わるのは 1 つだけなので、デバッグが格段に楽になります。


既成スクリプト

FMM には 4 つの既成 Lua スクリプトが同梱されています。

  • invulnerable.lua -- 左クリックのダメージイベントをキャンセルし、プロップを破壊不能にします。最もシンプルで実用的なプロップスクリプトです。
  • pickupable.lua -- プロップを 3 回叩くことでプレイヤーが回収できるようにします。1 回叩くごとにプロップがダメージアニメーションを再生し、3 回目でプロップが削除され、設置用アイテムがドロップしてプレイヤーが拾えるようになります。
  • storage_double.lua -- プロップをラージチェスト(54 スロット)にします。右クリックで永続的なインベントリ GUI が開きます。開閉のアニメーションとサウンドを再生します。中身はプロップに保存され、サーバー再起動後も残ります。破壊されると中身がすべてドロップします。
  • storage_single.lua -- storage_double と同じですが、6 行ではなく 3 行(27 スロット)です。

さらに多くの例はサンプルとパターンのページで確認できます。


Luaサンドボックス

プロップスクリプトとアイテムスクリプトは、EliteMobs と同じサンドボックス化された LuaJ 環境で実行されます。サンドボックスの制限は同一です。削除されたグローバル変数と利用可能な標準ライブラリ関数の完全な一覧は、MagmaCore Lua スクリプティングエンジンのページを参照してください。


次のステップ

EliteMobs のボス用 Lua パワーも書いている場合、サンドボックス、api_version、state テーブル、クールダウンの概念、フック駆動の構造は共通しているため馴染みやすいはずですが、ボスの context では EliteMobs 固有のメソッド名が使われます。ボス API の正確な内容は EliteMobs Lua ドキュメントを参照してください。