tag:crieit.net,2005:https://crieit.net/tags/Lua/feed 「Lua」の記事 - Crieit Crieitでタグ「Lua」に投稿された最近の記事 2022-12-02T11:52:38+09:00 https://crieit.net/tags/Lua/feed tag:crieit.net,2005:PublicArticle/18327 2022-12-02T11:46:32+09:00 2022-12-02T11:52:38+09:00 https://crieit.net/posts/Minetest-Modding-Book-Lua Minetest Modding Book ( ͡° ͜ʖ ͡°) (Lua) <p><a target="_blank" rel="nofollow noopener" href="https://dev.minetest.net/Intro">この本についてのこと</a></p> <p>これは <a target="_blank" rel="nofollow noopener" href="https://www.minetest.net/">Minetest というゲーム(とゲームエンジン)</a>でのゲームのつくり方というか、ゲームの構造を学ぶための全くのビギナー向けの本の日本語訳です。<br /> Minetest とは簡単に言うと、ある程度までのスペックのコンピューター資源で問題なく動作するマインクラフトのようななにかです。このなにかについて、<a target="_blank" rel="nofollow noopener" href="https://qiita.com/dauuricus/items/c97f0c31f6c478558e15">作り始めた人物へのインタビュー</a>がありますので、興味があればどうぞ読んでみてください。</p> <p>この本自体は Lua 言語の知識を要しますが、プログラムについての知識が無くても学習できるように丁寧にアドバイスしていると思います。<br /> Lua 言語について補足的に学習するための参考としては、<a target="_blank" rel="nofollow noopener" href="https://qiita.com/dauuricus/items/07b576a66e2c89c7247e">こちらをご覧ください</a>。</p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/tvk2g">https://rentry.co/tvk2g</a></p> <h1 id="Minetest Modding Book"><a href="#Minetest+Modding+Book">Minetest Modding Book</a></h1> <p>Book written by <a target="_blank" rel="nofollow noopener" href="https://rubenwardy.com/">rubenwardy</a></p> <p>with editing by <a target="_blank" rel="nofollow noopener" href="http://rc.minetest.tv/">Shara</a></p> <p>License: CC-BY-SA 3.0</p> <p>[rubenwardyに対してドネーションはいかがでしょうか?]<br /> © 2014-20 | Helpful? Consider <a target="_blank" rel="nofollow noopener" href="https://rubenwardy.com/donate/">donating</a> to support my work.</p> <hr /> <h2 id="Front Cover"><a href="#Front+Cover">Front Cover</a></h2> <h1 id="Minetest Modding Book"><a href="#Minetest+Modding+Book">Minetest Modding Book</a></h1> <p>by <a target="_blank" rel="nofollow noopener" href="https://rubenwardy.com/">rubenwardy</a></p> <p>with editing by <a target="_blank" rel="nofollow noopener" href="http://rc.minetest.tv/">Shara</a></p> <h2 id="Introduction"><a href="#Introduction">Introduction</a></h2> <p>Minetest は Lua スクリプトを使用して modding サポートを提供します。この本は、基本から始めて、独自の mods を作成する方法を教えることを目的としています。各章は API の特定の部分に焦点を当てており、すぐに独自の mods を作成できるようになります。</p> <p><a target="_blank" rel="nofollow noopener" href="https://rubenwardy.com/minetest_modding_book">サイトからこの本を読む</a>こともできますし、<a target="_blank" rel="nofollow noopener" href="https://gitlab.com/rubenwardy/minetest_modding_book/-/releases">htmlファイルをダウンロード</a>して読むこともできます。</p> <h3 id="Feedback and Contributions"><a href="#Feedback+and+Contributions">Feedback and Contributions</a></h3> <p>間違いに気づきましたか、それともフィードバックを送りたいですか?ぜひ連絡してください。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://gitlab.com/rubenwardy/minetest_modding_book/-/issues">GitLabのIsuueを作成します。</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://forum.minetest.net/">フォーラムトピックに投稿します。</a></li> <li><a href="">私に連絡してください</a></li> <li>コントリビュートしたいですか?<a target="_blank" rel="nofollow noopener" href="https://gitlab.com/rubenwardy/minetest_modding_book/-/blob/master/README.md">READMEをお読みください。</a></li> </ul> <h2 id="1 - Getting Started&lt;a name=&quot;getting_started&quot;&gt;&lt;/a&gt;"><a href="#1+-+Getting+Started%26lt%3Ba+name%3D%26quot%3Bgetting_started%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">1 - Getting Started<a name="getting_started"></a></a></h2> <h3 id="Introduction"><a href="#Introduction">Introduction</a></h3> <p>mods を作るためには mod のフォルダーの基本構造を理解する必要があります。</p> <ul> <li><a href="#what-are-games-and-mods">What are Games and Mods?</a></li> <li><a href="#where-are-mods-stored">Where are mods stored?</a></li> <li><a href="#mod-directory">Mod Directory</a></li> <li><a href="#dependencies">Dependencies</a> <ul> <li><a href="#modconf">mod.conf</a></li> <li><a href="#dependstxt">depends.txt</a></li> </ul></li> <li><a href="#mod-packs">Mod Packs</a></li> <li><a href="#example">Example</a> <ul> <li><a href="#mod-folder">Mod Folder</a></li> <li><a href="#dependstxt-">depends.txt</a></li> <li><a href="#initlua">init.lua</a></li> <li><a href="#modconf-">mod.conf</a></li> </ul></li> </ul> <h3 id="What are Games and Mods?&lt;a name=&quot;what-are-games-and-mods&quot;&gt;&lt;/a&gt;"><a href="#What+are+Games+and+Mods%3F%26lt%3Ba+name%3D%26quot%3Bwhat-are-games-and-mods%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">What are Games and Mods?<a name="what-are-games-and-mods"></a></a></h3> <p>Minetest の力は、独自のボクセルグラフィックス、ボクセルアルゴリズム、または派手なネットワークコードを作成しなくても、ゲームを簡単に開発できることです。</p> <p>Minetest では、ゲームは、ゲームのコンテンツと動作を提供するために連携して機能するモジュールのコレクションです。一般に mod として知られているモジュールは、スクリプトとリソースのコレクションです。 1 つの mod だけを使用してゲームを作成することは可能ですが、ゲームの一部を他の部分とは独立して調整および交換するのが容易でないため、これが行われることはめったにありません。</p> <p>ゲームの外に Mod を配布することも可能です。その場合、それらはより伝統的な意味での <em>MOD</em> 、つまり <em>Mod</em> です。これらの mod は、ゲームの機能を調整または拡張します。</p> <p>ゲームに含まれる mod とサードパーティの mod はどちらも同じ API を使用します。</p> <p>この本の内容は Minetest API の主要部分をカバーし、ゲーム開発者と modders に適用できます。</p> <h3 id="Where are mods stored?&lt;a name=&quot;where-are-mods-stored&quot;&gt;&lt;/a&gt;"><a href="#Where+are+mods+stored%3F%26lt%3Ba+name%3D%26quot%3Bwhere-are-mods-stored%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Where are mods stored?<a name="where-are-mods-stored"></a></a></h3> <p>各 mod には、Lua コード、テクスチャ、モデル、およびサウンドが配置される独自のディレクトリがあります。 Minetest は、さまざまなロケーションを mod の存在をチェックします。これらの場所は、一般に <em>mod load paths</em> と呼ばれます。</p> <p>特定のワールド/セーブゲームについて、 3 つの mod の場所が順番にチェックされます。:</p> <ol> <li>Game mods これらは、world が実行しているゲームを形成する mods です。例:<br /> <code>minetest/games/minetest_game/mods/</code>、<code>/usr/share/minetest/games/minetest/</code></li> <li>Global mods 、ほとんどの場合 mods がインストールされる場所。疑わしい場合は、ここに配置してください。例:<br /> <code>minetest/mods/</code></li> <li>World mods 、特定のワールドに固有の mods を保存する場所。例:<br /> <code>minetest/worlds/world/worldmods/</code></li> </ol> <p>Minetest は、上記の順序で場所を確認します。以前に見つかったものと同じ名前の mod が見つかった場合、前の mod の代わりに後の mod がロードされます。これは、global mod のロケーションに同じ名前の mod を配置することで、game mod をオーバーライド(上書き)できることを意味します。</p> <p>各 mod ロードパスの実際の場所は、使用しているオペレーティングシステムと、Minetest のインストール方法によって異なります。</p> <ul> <li><strong>Windows:</strong> <ul> <li>ポータブルビルドの場合、例:あなたが .zip ファイルから、抽出したディレクトリに移動します。 <code>games</code> 、 <code>mods</code> および <code>worlds</code> ディレクトリを調べてください。</li> <li>インストールされているビルド、つまり setup.exe からの場合、<code>C:\\Minetest</code> または <code>C:\\Games\Minetest</code> を調べます。</li> </ul></li> <li><strong>GNU / Linux:</strong> <ul> <li>システム全体のインストールについては、<code>~/.minetest</code> を調べてください。これ<code>~</code>は、ユーザーのホームディレクトリを意味し、ドット( <code>.</code> )で始まるファイルとディレクトリは非表示になっていることに注意してください。</li> <li>ポータブルインストールの場合は、ビルドディレクトリを確認してください。</li> <li>Flatpakのインストールについては、 <code>~/.var/app/net.minetest.Minetest/.minetest/mods/</code> をご覧ください 。</li> </ul></li> <li><strong>mac OS</strong> <ul> <li><code>~/Library/Application Support/minetest/</code> を見てください。 <code>~</code> は、ユーザーのホームを意味することに注意してください。すなわち: <code>/Users/USERNAME/</code></li> </ul></li> </ul> <h3 id="Mod Directory&lt;a name=&quot;#mod-directory&quot;&gt;&lt;/a&gt;"><a href="#Mod+Directory%26lt%3Ba+name%3D%26quot%3B%23mod-directory%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Mod Directory<a name="#mod-directory"></a></a></h3> <p><img src="https://rubenwardy.com/minetest_modding_book/static/folder_modfolder.jpg" alt="Find the mod's directory" /></p> <p><em>mod name</em> は mod を参照するために使用されます。各 mod には一意の(他に同名が存在しない)名前を付ける必要があります。 mod 名には、文字、数字、およびアンダースコアを含めることができます。 mod の名前から、 mod の機能を理解できることが好ましく、 mod のコンポーネントを含むディレクトリは、 mod 名と同じ名前である必要があります。 mod 名が使用可能かどうかを確認するには、<a target="_blank" rel="nofollow noopener" href="https://content.minetest.net/">content.minetest.net</a>で検索してみてください。</p> <pre><code>mymod ├── init.lua (required) - Runs when the game loads. ├── mod.conf (recommended) - Contains description and dependencies. ├── textures (optional) │ └── ... any textures or images ├── sounds (optional) │ └── ... any sounds └── ... any other files or directories </code></pre> <p>ゲームのロード時に実行するには、mod で init.lua ファイルのみが必要です。</p> <p>ただし、 mod.conf が推奨されています。また、 mod の機能によっては、他のコンポーネントが必要になる場合があります。</p> <h3 id="Dependencies&lt;a name=&quot;dependencies&quot;&gt;&lt;/a&gt;"><a href="#Dependencies%26lt%3Ba+name%3D%26quot%3Bdependencies%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Dependencies<a name="dependencies"></a></a></h3> <p>依存関係(dependencies)は、ロードしようとする mod がそれ自体の前に別の mod をロードする必要がある場合に発生します。 1 つの mod で、別の mod のコード、アイテム、またはその他のリソースを使用できるようにする必要がある場合があるのです。</p> <p>依存関係には、 2 種類があります。ハード依存関係とオプション依存関係です。どちらも、最初に mod をロードする必要があります。依存している mod が利用できない場合、ハード依存関係があると mod のロードに失敗しますが、オプション依存関係の場合は、有効になる機能が少なくなる可能性があります。</p> <p>オプション依存関係は、オプションで別の mod をサポートする場合に役立ちます。ユーザーが両方の mod を同時に使用したい場合は、追加のコンテンツを有効にすることができます。</p> <p>依存関係は mod.conf にリストされている必要があります。</p> <h4 id="mod.conf &lt;a name=&quot;#modconf&quot;&gt;&lt;/a&gt;"><a href="#mod.conf+%26lt%3Ba+name%3D%26quot%3B%23modconf%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">mod.conf <a name="#modconf"></a></a></h4> <p>このファイルは、mod の名前、説明、その他の情報を含む mod メタデータに使用されます。例:</p> <pre><code>name = mymod description = Adds foo, bar, and bo. depends = modone, modtwo optional_depends = modthree </code></pre> <h4 id="depends.txt &lt;a name=&quot;#dependstxt&quot;&gt;&lt;/a&gt;"><a href="#depends.txt+%26lt%3Ba+name%3D%26quot%3B%23dependstxt%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">depends.txt <a name="#dependstxt"></a></a></h4> <p>Minetest の 0.4.x バージョンとの互換性のために、mod.conf で依存関係を指定するだけでなく、すべての依存関係をリストする depends.txt ファイルを提供する必要があります。</p> <pre><code>modone modtwo modthree? </code></pre> <p>各 mod 名は 1 行に 1 つづつあり、名前の後に疑問符が付いた mod 名はオプション依存関係です。</p> <h3 id="Mod Packs&lt;a name=&quot;#mod-packs&quot;&gt;&lt;/a&gt;"><a href="#Mod+Packs%26lt%3Ba+name%3D%26quot%3B%23mod-packs%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Mod Packs<a name="#mod-packs"></a></a></h3> <p>mod は Mod Packs にグループ化でき、複数の mod をパッケージ化して一緒に移動できます。プレーヤーに複数の Mod を提供したいが、それぞれを個別にダウンロードさせたくない場合に便利です。</p> <pre><code>modpack1 ├── modpack.lua (required) - signals that this is a mod pack ├── mod1 │ └── ... mod files └── mymod (optional) └── ... mod files </code></pre> <p>modpack は <em>geme</em> ではないことに注意してください。<em>game</em> には独自の組織構造があり、これについてはゲームの章で説明します。</p> <h3 id="Example &lt;a name=&quot;example&quot;&gt;&lt;/a&gt;"><a href="#Example+%26lt%3Ba+name%3D%26quot%3Bexample%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Example <a name="example"></a></a></h3> <p>これらすべてをまとめた例を次に示します。</p> <h4 id="Mod Folder &lt;a name=&quot;#mod-folder&quot;&gt;&lt;/a&gt;"><a href="#Mod+Folder+%26lt%3Ba+name%3D%26quot%3B%23mod-folder%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Mod Folder <a name="#mod-folder"></a></a></h4> <pre><code>mymod ├── textures │ └── mymod_node.png files ├── depends.txt ├── init.lua └── mod.conf </code></pre> <h4 id="depends.txt &lt;a name=&quot;#dependstxt-&quot;&gt;&lt;/a&gt;"><a href="#depends.txt+%26lt%3Ba+name%3D%26quot%3B%23dependstxt-%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">depends.txt <a name="#dependstxt-"></a></a></h4> <pre><code>default </code></pre> <h4 id="init.lua &lt;a name=&quot;#initlua&quot;&gt;&lt;/a&gt;"><a href="#init.lua+%26lt%3Ba+name%3D%26quot%3B%23initlua%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">init.lua <a name="#initlua"></a></a></h4> <pre><code class="lua">print("This file will be run at load time!") minetest.register_node("mymod:node", { description = "This is a node", tiles = {"mymod_node.png"}, groups = {cracky = 1} }) </code></pre> <h4 id="mod.conf &lt;a name=&quot;#modconf-&quot;&gt;&lt;/a&gt;"><a href="#mod.conf+%26lt%3Ba+name%3D%26quot%3B%23modconf-%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">mod.conf <a name="#modconf-"></a></a></h4> <pre><code>name = mymod descriptions = Adds a node depends = default </code></pre> <p>この mod の名前は「 mymod 」です。 init.lua 、 mod.conf 、 depends.txt の 3 つのテキストファイルがあります。<br /> スクリプトはメッセージを出力してからノードを登録します。これについては次の章で説明します。<br /> 単一の依存関係、 <a target="_blank" rel="nofollow noopener" href="https://content.minetest.net/metapackages/default/">default mod</a> があります。これは通常、 Minetest Game にあります。<br /> また、textures/ にはノードのテクスチャがあります。</p> <h2 id="2 - Lua Scripting &lt;a name=&quot;lua&quot;&gt;&lt;/a&gt;"><a href="#2+-+Lua+Scripting+%26lt%3Ba+name%3D%26quot%3Blua%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">2 - Lua Scripting <a name="lua"></a></a></h2> <h3 id="Introduction"><a href="#Introduction">Introduction</a></h3> <p>この章では、Lua でのスクリプト作成、必要なツールについて説明し、おそらく役立つと思われるいくつかのテクニックについて説明します。</p> <ul> <li><a href="#code-editors">Code Editors</a></li> <li><a href="#coding-in-lua">Coding in Lua</a> <ul> <li><a href="#program-flow">Program Flow</a></li> <li><a href="#variable-types">Variable Types</a></li> <li><a href="#arithmetic-operators">Arithmetic Operators</a></li> <li><a href="#selection">Selection</a></li> <li><a href="#logical-operators">Logical Operators</a></li> </ul></li> <li><a href="#programming">Programming</a></li> <li><a href="#local-and-global-scope">Local and Global Scope</a> <ul> <li><a href="#locals-should-be-used-as-much-as-possible">Locals should be used as much as possible</a></li> </ul></li> <li><a href="#including-other-lua-scripts">Including other Lua Scripts</a></li> </ul> <h3 id="Code Editors &lt;a name=&quot;#code-editors&quot;&gt;&lt;/a&gt;"><a href="#Code+Editors+%26lt%3Ba+name%3D%26quot%3B%23code-editors%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Code Editors <a name="#code-editors"></a></a></h3> <p>Lua でスクリプトを作成するには、コードのハイライト表示を備えたコードエディターで十分です。コードのハイライト表示は、意味に応じて、単語や文字にさまざまな色を付けて表示させます。これにより、間違いを見つけることができます。</p> <pre><code class="lua">function ctf.post(team,msg) if not ctf.team(team) then return false end if not ctf.team(team).log then ctf.team(team).log = {} end table.insert(ctf.team(team).log,1,msg) ctf.save() return true end </code></pre> <p>たとえば、上記のスニペットのキーワードは、 if 、then 、end 、return などでハイライト表示されています。 table.insert は、デフォルトで Lua に付属している関数です。<br /> これは、 Lua に適した一般的なエディターのリストです。もちろん、他のエディターも利用できます。</p> <ul> <li>Windows: <a target="_blank" rel="nofollow noopener" href="http://notepad-plus-plus.org/">Notepad++</a>, <a target="_blank" rel="nofollow noopener" href="http://atom.io/">Atom</a>, <a target="_blank" rel="nofollow noopener" href="https://code.visualstudio.com/">VS Code</a></li> <li>Linux: Kate, Gedit, <a target="_blank" rel="nofollow noopener" href="http://atom.io/">Atom</a>, <a target="_blank" rel="nofollow noopener" href="https://code.visualstudio.com/">VS Code</a></li> <li>OSX: <a target="_blank" rel="nofollow noopener" href="http://atom.io/">Atom</a>, <a target="_blank" rel="nofollow noopener" href="https://code.visualstudio.com/">VS Code</a></li> </ul> <h3 id="Coding in Lua &lt;a name=&quot;#coding-in-lua&quot;&gt;&lt;/a&gt;"><a href="#Coding+in+Lua+%26lt%3Ba+name%3D%26quot%3B%23coding-in-lua%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Coding in Lua <a name="#coding-in-lua"></a></a></h3> <h4 id="Program Flow &lt;a name=&quot;#program-flow&quot;&gt;&lt;/a&gt;"><a href="#Program+Flow+%26lt%3Ba+name%3D%26quot%3B%23program-flow%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Program Flow <a name="#program-flow"></a></a></h4> <p>プログラムは、次々に実行される一連のコマンドです。これらのコマンドを「ステートメント」と呼びます。プログラムフローは、これらのステートメントが実行される方法です。さまざまなタイプのフローを使用すると、コマンドのセットをスキップまたはジャンプできます。フローには主に 3 つのタイプがあります。</p> <ul> <li>シーケンス (Sequence):スキップせずに、ステートメントを次々に実行するだけです。</li> <li>選択 (Selection):条件に応じてシーケンスをスキップします。</li> <li>反復 (Iteration):繰り返し、ループします。条件が満たされるまで、同じステートメントを実行し続けます。</li> </ul> <p>では、 Lua のステートメントはどのように見えますか?</p> <pre><code class="lua">local a = 2 -- Set 'a' to 2 local b = 2 -- Set 'b' to 2 local result = a + b -- Set 'result' to a + b, which is 4 a = a + 10 print("Sum is "..result) </code></pre> <p>おっと、そこで何が起こりましたか?</p> <p>a、b、および result は <em>変数</em> です。ローカル変数は、local キーワードを使用して宣言され、初期値が与えられます。ローカルは <em>スコープ(scope)</em> と呼ばれる非常に重要な概念の一部であるため、少し説明します。</p> <p><code>=</code> は <em>アサイン</em> 、ですから <code>result = a + b</code> は「 result 」 に 「 a + b 」を割り当てます。「 result 」変数で見られるように、変数名は数学とは異なり、1文字より長くなる可能性があります。 Lua では<em>大文字と小文字が区別される</em>ことにも注意してください。 A は a とは異なる変数です。</p> <h4 id="Variable Types &lt;a name=&quot;#variable-types&quot;&gt;&lt;/a&gt;"><a href="#Variable+Types+%26lt%3Ba+name%3D%26quot%3B%23variable-types%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Variable Types <a name="#variable-types"></a></a></h4> <p>変数は次のタイプのいずれかのみであり、割り当て後にタイプを変更できます。変数が nil または nil 以外の単一の型のみであることを確認することをお勧めします。</p> <div class="table-responsive"><table> <thead> <tr> <th>タイプ</th> <th>説明</th> <th>例</th> </tr> </thead> <tbody> <tr> <td>Nil</td> <td>初期化されていません。変数は空で、値がありません</td> <td><code>local A</code>、 <code>D = nil</code></td> </tr> <tr> <td>Number</td> <td>整数または10進数。</td> <td><code>local A = 4</code></td> </tr> <tr> <td>String</td> <td>テキストの一部</td> <td><code>local D = "one two three"</code></td> </tr> <tr> <td>Boolean</td> <td>True or False</td> <td><code>local is_true = false</code>、 <code>local E = (1 == 1)</code></td> </tr> <tr> <td>Table</td> <td>Lists</td> <td>以下に説明します</td> </tr> <tr> <td>Function</td> <td>実行できます。入力が必要な場合があり、値を返す場合があります</td> <td><code>local result = func(1, 2, 3)</code></td> </tr> </tbody> </table></div> <h4 id="Arithmetic Operators &lt;a name=&quot;#arithmetic-operators&quot;&gt;&lt;/a&gt;"><a href="#Arithmetic+Operators+%26lt%3Ba+name%3D%26quot%3B%23arithmetic-operators%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Arithmetic Operators <a name="#arithmetic-operators"></a></a></h4> <p>網羅的なリストではありません。考えられるすべての演算子が含まれているわけではありません。</p> <div class="table-responsive"><table> <thead> <tr> <th>シンボル</th> <th>目的</th> <th>例</th> </tr> </thead> <tbody> <tr> <td>A + B</td> <td>加算</td> <td>2 + 2 = 4</td> </tr> <tr> <td>A-B</td> <td>減算</td> <td>2-10 = -8</td> </tr> <tr> <td>A * B</td> <td>乗算</td> <td>2 * 2 = 4</td> </tr> <tr> <td>A / B</td> <td>分割</td> <td>100/50 = 2</td> </tr> <tr> <td>A ^ B</td> <td>累乗</td> <td>2 ^ 2 = 2^2 = 4</td> </tr> <tr> <td>A .. B</td> <td>文字列を結合する</td> <td>"foo".."bar" = "foobar"</td> </tr> </tbody> </table></div> <h4 id="Selection &lt;a name=&quot;#selection&quot;&gt;&lt;/a&gt;"><a href="#Selection+%26lt%3Ba+name%3D%26quot%3B%23selection%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Selection <a name="#selection"></a></a></h4> <p>最も基本的な選択は if ステートメントです。次のようになります。</p> <pre><code class="lua">local random_number = math.random(1, 100) -- Between 1 and 100. if random_number > 50 then print("Woohoo!") else print("No!") end </code></pre> <p>この例では、1から100までの乱数が生成されます。次に、「 Woohoo! 」と出力されます。その数が50より大きい場合は、「 No! 」と出力されます。</p> <pre><code class="lua">local random_number = math.random(1, 100) -- Between 1 and 100. if random_number > 50 then print("Woohoo!") else print("No!") end </code></pre> <p>「>」から他に何が離れますか?</p> <h4 id="Logical Operators &lt;a name=&quot;#logical-operators&quot;&gt;&lt;/a&gt;"><a href="#Logical+Operators+%26lt%3Ba+name%3D%26quot%3B%23logical-operators%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Logical Operators <a name="#logical-operators"></a></a></h4> <div class="table-responsive"><table> <thead> <tr> <th>シンボル</th> <th>目的</th> <th>例</th> </tr> </thead> <tbody> <tr> <td>A == B</td> <td>等しい</td> <td>1 == 1(true)、1 == 2(false)</td> </tr> <tr> <td>A 〜 = B</td> <td>等しくない</td> <td>1〜 = 1(false)、1〜 = 2(true)</td> </tr> <tr> <td>A > B</td> <td>大なり記号</td> <td>5> 2(true)、1> 2(false)、1> 1(false)</td> </tr> <tr> <td>A < B</td> <td>未満</td> <td>1</td> </tr> <tr> <td>A >= B</td> <td>以上かイコール</td> <td>5> = 5(true)、5> = 3(true)、5> = 6(false)</td> </tr> <tr> <td>A</td> <td></td> <td></td> </tr> <tr> <td>A and B</td> <td>And(両方ともtrueでなければなりません)</td> <td>(2> 1)and(1 == 1)(true)、(2> 3)and(1 == 1)(false)</td> </tr> <tr> <td>A or B</td> <td>または。一方または両方がtureでなければなりません。</td> <td>(2> 1)or(1 == 2)(true)、(2> 4)or(1 == 3)(false)</td> </tr> <tr> <td>not A</td> <td>not true</td> <td>not(1 == 2)(true)、not(1 == 1)(false)</td> </tr> </tbody> </table></div> <p>これにはすべての可能な演算子が含まれているわけではなく、次のように演算子を組み合わせることができます。</p> <pre><code class="lua">if not A and B then print("Yay!") end </code></pre> <p>A が false で B が true の場合「 Yay! 」をプリントします 。</p> <p>論理演算子と算術演算子はまったく同じように機能します。どちらも入力を受け入れ、保存可能な値を返します。</p> <pre><code class="lua">local A = 5 local is_equal = (A == 5) if is_equal then print("Is equal!") end </code></pre> <h3 id="Programming &lt;a name=&quot;#programming&quot;&gt;&lt;/a&gt;"><a href="#Programming+%26lt%3Ba+name%3D%26quot%3B%23programming%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Programming <a name="#programming"></a></a></h3> <p>プログラミングとは、項目のリストを並べ替えるなどの問題を解決し、それをコンピューターが理解できる手順に変換するアクションです。</p> <p>プログラミングの論理的なプロセスを教えることは、この本の範囲を超えています。ただし、次の Web サイトは、これを開発するのに非常に役立ちます。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="http://www.codecademy.com/">Codecademy</a>は、「コーディング」を学ぶための最良のリソースの1つであり、インタラクティブなチュートリアル体験を提供します。</li> <li><a target="_blank" rel="nofollow noopener" href="https://scratch.mit.edu/">Scratch</a>は、プログラミングに必要な問題解決手法を学び、絶対的な基礎から始めるときに優れたリソースです。<br /> Scratch は、子供たちにプログラミング方法を教えるように設計されており、本格的なプログラミング言語ではありません。</li> </ul> <h3 id="Local and Global Scope &lt;a name=&quot;#local-and-global-scope&quot;&gt;&lt;/a&gt;"><a href="#Local+and+Global+Scope+%26lt%3Ba+name%3D%26quot%3B%23local-and-global-scope%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Local and Global Scope <a name="#local-and-global-scope"></a></a></h3> <p>変数がローカルであるかグローバルであるかによって、変数の書き込み、または読み取りが可能な場所が決まります。ローカル変数には、変数が定義されている場所からのみアクセスできます。ここではいくつかの例を示します。</p> <pre><code class="lua">-- Accessible from within this script file local one = 1 function myfunc() -- Accessible from within this function local two = one + one if two == one then -- Accessible from within this if statement local three = one + two end end </code></pre> <p>一方、グローバル変数には、スクリプトファイルのどこからでも、他の mod からもアクセスできます。</p> <pre><code class="lua">my_global_variable = "blah" function one() my_global_variable = "three" end print(my_global_variable) -- Output: "blah" one() print(my_global_variable) -- Output: "three" </code></pre> <h4 id="Locals should be used as much as possible &lt;a name=&quot;#locals-should-be-used-as-much-as-possible&quot;&gt;&lt;/a&gt;"><a href="#Locals+should+be+used+as+much+as+possible+%26lt%3Ba+name%3D%26quot%3B%23locals-should-be-used-as-much-as-possible%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Locals should be used as much as possible <a name="#locals-should-be-used-as-much-as-possible"></a></a></h4> <p>Lua はデフォルトでグローバルです(他のほとんどのプログラミング言語とは異なります)。<br /> ローカル変数は、そのように識別する必要があります(つまりローカル変数を使う場合は明示的に宣言します)。</p> <pre><code class="lua">function one() foo = "bar" end function two() print(dump(foo)) -- Output: "bar" end one() two() </code></pre> <p>dump() は、任意の変数を文字列に変換できる関数であり、プログラマーはそれが何であるかを確認できます。foo 変数は、文字列であることを示す引用符を含めて、「 bar 」として出力されます。</p> <p>これはずさんなコーディングであり、Minetest は実際にこれについて警告します:</p> <pre><code class="lua">Assignment to undeclared global 'foo' inside function at init.lua:2 </code></pre> <p>これを修正するには、「ローカル」を使用します。</p> <pre><code class="lua">function one() local foo = "bar" end function two() print(dump(foo)) -- Output: nil end one() two() </code></pre> <p><code>nil</code> は<strong>初期化されていない</strong>ことを意味することに注意してください。変数に値がまだ割り当てられていないか、存在しないか、初期化されていません(つまり、 <code>nil</code> に設定されています)。</p> <p>関数についても同じことが言えます。関数は特別なタイプの変数であり、他の mod が同じ名前の関数を持つ可能性があるため、ローカルにする必要があります。</p> <pre><code class="lua">local function foo(bar) return bar * 2 end </code></pre> <p>API テーブルを使用して、次のように他の mod が関数を呼び出せるようにする必要があります。</p> <pre><code class="lua">mymod = {} function mymod.foo(bar) return "foo" .. bar end -- In another mod, or script: mymod.foo("foobar") </code></pre> <h3 id="Including other Lua Scripts &lt;a name=&quot;#including-other-lua-scripts&quot;&gt;&lt;/a&gt;"><a href="#Including+other+Lua+Scripts+%26lt%3Ba+name%3D%26quot%3B%23including-other-lua-scripts%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Including other Lua Scripts <a name="#including-other-lua-scripts"></a></a></h3> <p>mod に他のLuaスクリプトを含めるための推奨される方法は、<em>dofile</em> を使用することです。</p> <pre><code class="lua">dofile(minetest.get_modpath("modname") .. "/script.lua") </code></pre> <p>スクリプトは値を返すことができます。これは、プライベートローカル変数を共有するのに役立ちます。</p> <pre><code class="lua">-- script.lua return "Hello world!" -- init.lua local ret = dofile(minetest.get_modpath("modname") .. "/script.lua") print(ret) -- Hello world! </code></pre> <p>後の章では、mod のコードを分割する方法について詳しく説明します。ここではシンプルなアプローチとして、 nodes.lua 、crafts.lua 、craftitems.lua などのさまざまなタイプのものに対して別々のファイルを用意します。</p> <h2 id="3 - Nodes, Items, and Crafting"><a href="#3+-+Nodes%2C+Items%2C+and+Crafting">3 - Nodes, Items, and Crafting</a></h2> <h3 id="Introduction"><a href="#Introduction">Introduction</a></h3> <p>新しいノードとクラフトアイテムの登録、およびクラフトレシピの作成は、多くの mod の基本的要件です。</p> <ul> <li><a href="#what-are-nodes-and-items">What are Nodes and Items?</a></li> <li><a href="#registering-items">Registering Items</a> <ul> <li><a href="#item-names-and-aliases">Item Names and Aliases</a></li> <li><a href="#textures">Textures</a></li> </ul></li> <li><a href="#registering-a-basic-node">Registering a basic node</a></li> <li><a href="#actions-and-callbacks">Actions and Callbacks</a> <ul> <li><a href="#onuse">on_use</a></li> </ul></li> <li><a href="#crafting">Crafting</a> <ul> <li><a href="#shaped">Shaped</a></li> <li><a href="#shapeless">Shapeless</a></li> <li><a href="#cooking-and-fuel">Cooking and Fuel</a></li> </ul></li> <li><a href="#groups">Groups</a></li> <li><a href="#tools-capabilities-and-dig-types">Tools, Capabilities, and Dig Types</a></li> </ul> <h3 id="What are Nodes and Items? &lt;a name=&quot;#what-are-nodes-and-items&quot;&gt;&lt;/a&gt;"><a href="#What+are+Nodes+and+Items%3F+%26lt%3Ba+name%3D%26quot%3B%23what-are-nodes-and-items%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">What are Nodes and Items? <a name="#what-are-nodes-and-items"></a></a></h3> <p>ノード、クラフトアイテム、ツールはすべてアイテムです。アイテムは、通常のゲームプレイでは不可能な場合でも、インベントリ内で見つけることができるものです。</p> <p>ノードは、世界に配置されているか、または発見することができるアイテムです。世界のすべての位置空間は、たった1つのノードで占められている必要があります-一見空白の位置空間は通常、エアノードです。</p> <p>クラフトアイテムは配置できず、インベントリにあるか、ワールド内でドロップされたアイテムとしてのみ発見できます。</p> <p>ツールには<a target="_blank" rel="nofollow noopener" href="https://wiki.minetest.net/Tool#Wear">着用</a>する機能があり、通常はデフォルトではない掘削する機能があります。将来的には、クラフトアイテムとツールの区別がかなり人工的なものであるため、クラフトアイテムとツールが1つのタイプのアイテムに統合される可能性があります。</p> <h3 id="Registering Items &lt;a name=&quot;#registering-items&quot;&gt;&lt;/a&gt;"><a href="#Registering+Items+%26lt%3Ba+name%3D%26quot%3B%23registering-items%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Registering Items <a name="#registering-items"></a></a></h3> <p>アイテム定義は、<em>アイテム名</em>と<em>定義テーブル</em>で構成されます。定義テーブルには、アイテムの動作に影響を与える属性が含まれています。</p> <pre><code class="lua">minetest.register_craftitem("modname:itemname", { description = "My Special Item", inventory_image = "modname_itemname.png" }) </code></pre> <h4 id="Item Names and Aliases &lt;a name=&quot;#item-names-and-aliases&quot;&gt;&lt;/a&gt;"><a href="#Item+Names+and+Aliases+%26lt%3Ba+name%3D%26quot%3B%23item-names-and-aliases%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Item Names and Aliases <a name="#item-names-and-aliases"></a></a></h4> <p>すべてのアイテムには、参照するために使用されるアイテム名があります。次の形式である必要があります。</p> <pre><code class="lua">modname:itemname </code></pre> <p>modname は、アイテムが登録されている mod の名前であり、 itemname はアイテム自体の名前です。アイテム名は、アイテムが何であるかに関連している必要があり、まだ登録できません。</p> <p>アイテムには、その名前を指す<em>エイリアス</em>を含めることもできます。<em>エイリアス</em>は、擬似的なアイテム名です。エンジンはエイリアスの出現をアイテム名であるように扱います。これには、主に 2 つの一般的な用途があります。</p> <ul> <li>削除されたアイテムの名前を別の名前に変更します。 corrective コードなしでアイテムが mod から削除された場合、world とノードインベントリに不明なノードが存在するようになる可能性があります。削除ノードを取得できる場合は、取得できないノードへのエイリアシングを回避することが重要です。</li> <li>ショートカットを追加します。<code>/giveme dirt</code>より簡単です<code>/giveme default:dirt</code>。</li> </ul> <p>エイリアスの登録は非常に簡単です。引数の順序を覚える良い方法は<code>from → to</code>、<em>from</em>がエイリアスで、<em>to</em>がターゲットであるということです。</p> <pre><code class="lua">minetest.register_alias("dirt", "default:dirt") </code></pre> <p>mod はアイテム名を直接処理する前にエイリアスを解決する必要があります。エンジンはこれを行いません。ただし、これはすごく簡単です。</p> <pre><code class="lua">itemname = minetest.registered_aliases[itemname] or itemname </code></pre> <h4 id="Textures &lt;a name=&quot;#textures&quot;&gt;&lt;/a&gt;"><a href="#Textures+%26lt%3Ba+name%3D%26quot%3B%23textures%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Textures <a name="#textures"></a></a></h4> <p>テクスチャ(のファイル)は、<code>modname_itemname.png</code> という命名形式で <code>textures/</code> フォルダに配置する必要があります。<br /> JPEG テクスチャはサポートされていますが、透明度はサポートされておらず、一般に低解像度では品質が低くなります。多くの場合、 PNG 形式を使用することをお勧めします。</p> <p>Minetest のテクスチャは通常 16x16 ピクセルです。これらは任意の解像度にすることができますが、2 のオーダー、たとえば 16 、32 、64 、または 128 にすることをお勧めします。これは、古いデバイスでは他の解像度が正しくサポートされておらず、パフォーマンスが低下する可能性があるためです。</p> <h3 id="Registering a basic node &lt;a name=&quot;#registering-a-basic-node&quot;&gt;&lt;/a&gt;"><a href="#Registering+a+basic+node+%26lt%3Ba+name%3D%26quot%3B%23registering-a-basic-node%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Registering a basic node <a name="#registering-a-basic-node"></a></a></h3> <pre><code class="lua">minetest.register_node("mymod:diamond", { description = "Alien Diamond", tiles = {"mymod_diamond.png"}, is_ground_content = true, groups = {cracky=3, stone=1} }) </code></pre> <p><code>tiles</code> プロパティは、ノードが使用するテクスチャ名のテーブルです。テクスチャが 1 つしかない場合、このテクスチャは6面すべての面で使用されます。側面ごとに異なるテクスチャを与えるには、 6 つのテクスチャの名前を次の順序で指定します。</p> <pre><code class="lua">up (+Y), down (-Y), right (+X), left (-X), back (+Z), front (-Z). (+Y, -Y, +X, -X, +Z, -Z) </code></pre> <p>3D コンピュータグラフィックスの慣例と同様に、 Minetest では +Y が上向きであることを忘れないでください。</p> <pre><code class="lua">minetest.register_node("mymod:diamond", { description = "Alien Diamond", tiles = { "mymod_diamond_up.png", -- y+ "mymod_diamond_down.png", -- y- "mymod_diamond_right.png", -- x+ "mymod_diamond_left.png", -- x- "mymod_diamond_back.png", -- z+ "mymod_diamond_front.png", -- z- }, is_ground_content = true, groups = {cracky = 3}, drop = "mymod:diamond_fragments" -- ^ Rather than dropping diamond, drop mymod:diamond_fragments }) </code></pre> <p>_ground_content 属性を使用すると、石の上に洞窟を生成できます。これは、マップの生成中に地下に配置される可能性のあるノードにとって不可欠です。エリア内の他のすべてのノードが生成された後、洞窟は world から切り取られます。</p> <h3 id="Actions and Callbacks &lt;a name=&quot;#actions-and-callbacks&quot;&gt;&lt;/a&gt;"><a href="#Actions+and+Callbacks+%26lt%3Ba+name%3D%26quot%3B%23actions-and-callbacks%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Actions and Callbacks <a name="#actions-and-callbacks"></a></a></h3> <p>Minetest は、コールバックベースの modding デザインを多用しています。コールバックをアイテム定義テーブルに配置して、さまざまな異なるユーザーイベントに応答できるようにすることができます。</p> <h4 id="on&#95;use &lt;a name=&quot;#onuse&quot;&gt;&lt;/a&gt;"><a href="#on%26%2395%3Buse+%26lt%3Ba+name%3D%26quot%3B%23onuse%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">on_use <a name="#onuse"></a></a></h4> <p>デフォルトでは、プレーヤーがアイテムを左クリックすると、 use コールバックがトリガーされます。 use コールバックがあると、アイテムがノードの掘削に使用されるのを防ぐことができます。 use コールバックの一般的な使用法の1つは、food(食品)です。</p> <pre><code class="lua">minetest.register_craftitem("mymod:mudpie", { description = "Alien Mud Pie", inventory_image = "myfood_mudpie.png", on_use = minetest.item_eat(20), }) </code></pre> <p>minetest.item_eat 関数に提供される数値は、この food (食品)が消費されたときに回復したヒットポイントの数です。プレイヤーが持っている各ハートアイコンは、 2 つのヒットポイントの価値があります。プレーヤーは通常、最大10個のハートを持つことができます。これは、 20 ヒットポイントに相当します。ヒットポイントは整数( whole number )である必要はありません。小数にすることができます。</p> <p>minetest.item_eat() は、関数を返す関数であり、on_use コールバックとして設定します。これは、上記のコードがほぼ次のようになっていることを意味します。</p> <pre><code class="lua">minetest.register_craftitem("mymod:mudpie", { description = "Alien Mud Pie", inventory_image = "myfood_mudpie.png", on_use = function(...) return minetest.do_item_eat(20, nil, ...) end, }) </code></pre> <p>関数を返すことだけで item_eat がどのように機能するかを理解することで、カスタムサウンドの再生させるなど、より複雑な動作を行うように変更することができます。</p> <h3 id="Crafting &lt;a name=&quot;#crafting&quot;&gt;&lt;/a&gt;"><a href="#Crafting+%26lt%3Ba+name%3D%26quot%3B%23crafting%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Crafting <a name="#crafting"></a></a></h3> <p><code>type</code> プロパティによって示される、利用可能ないくつかのタイプのクラフトレシピがあります。</p> <ul> <li>shaped - 材料は正しい位置になければなりません。</li> <li>shapeless - 材料がどこにあるかは関係ありません。適切な量があるだけです。</li> <li>cooking - 使用する<a target="_blank" rel="nofollow noopener" href="https://wiki.minetest.net/Furnace">かまど( furnacae )</a>のレシピ。</li> <li>fuel - かまどで燃焼できるアイテムを定義します。</li> <li>tool_repair - ツールで修復できるアイテムを定義します。</li> </ul> <p>クラフトレシピはアイテムではないため、アイテム名を使用して一意に識別しません。</p> <h4 id="Shaped &lt;a name=&quot;#shaped&quot;&gt;&lt;/a&gt;"><a href="#Shaped+%26lt%3Ba+name%3D%26quot%3B%23shaped%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Shaped <a name="#shaped"></a></a></h4> <p>Shoped したレシピは、材料が機能するためには正しい Shope またはパターンである必要があります。以下の例では、クラフトが機能するために、フラグメントは椅子のようなパターンである必要があります。</p> <pre><code class="lua">minetest.register_craft({ type = "shaped", output = "mymod:diamond_chair 99", recipe = { {"mymod:diamond_fragments", "", ""}, {"mymod:diamond_fragments", "mymod:diamond_fragments", ""}, {"mymod:diamond_fragments", "mymod:diamond_fragments", ""} } }) </code></pre> <p>注意すべき点の1つは、右側の空白の列です。これは、図形の右側に空の列が必要であることを意味します。そうでない場合、これは機能しません。この空の列が必要ない場合は、次のように空の文字列を省略できます。</p> <pre><code class="lua">minetest.register_craft({ output = "mymod:diamond_chair 99", recipe = { {"mymod:diamond_fragments", "" }, {"mymod:diamond_fragments", "mymod:diamond_fragments"}, {"mymod:diamond_fragments", "mymod:diamond_fragments"} } }) </code></pre> <p>Shaped はデフォルトのクラフトタイプであるため、タイプフィールドは実際に shaped クラフトには必要ありません。</p> <h4 id="Shapeless &lt;a name=&quot;#shapeless&quot;&gt;&lt;/a&gt;"><a href="#Shapeless+%26lt%3Ba+name%3D%26quot%3B%23shapeless%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Shapeless <a name="#shapeless"></a></a></h4> <p>Shapeless レシピは、材料がどこに配置されていても、そこにあるだけで問題にならない場合に使用されるレシピの一種です。</p> <pre><code class="lua">minetest.register_craft({ type = "shapeless", output = "mymod:diamond 3", recipe = { "mymod:diamond_fragments", "mymod:diamond_fragments", "mymod:diamond_fragments", }, }) </code></pre> <h4 id="Cooking and Fuel &lt;a name=&quot;#cooking-and-fuel&quot;&gt;&lt;/a&gt;"><a href="#Cooking+and+Fuel+%26lt%3Ba+name%3D%26quot%3B%23cooking-and-fuel%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Cooking and Fuel <a name="#cooking-and-fuel"></a></a></h4> <p>「クッキング」タイプのレシピは、<a target="_blank" rel="nofollow noopener" href="https://wiki.minetest.net/Crafting#Crafting_grid_and_output_slot">クラフティンググリッド</a>では作成されませんが、かまどでされるか、または mods で見つかる可能性あります。</p> <pre><code class="lua">minetest.register_craft({ type = "cooking", output = "mymod:diamond_fragments", recipe = "default:coalblock", cooktime = 10, }) </code></pre> <p>コードの唯一の本当の違いは、レシピがテーブル内({中括弧の間})にあるのと比較して、単一のアイテムにすぎないことです。また、アイテムの cooking にかかる時間を定義するオプションの「 cooktime 」パラメーターもあります。これが設定されていない場合、デフォルトで 3 になります。<br /> 上記のレシピは、石炭ブロックが入力スロットにあり、その下に何らかの形の燃料がある場合に機能します。10秒後にダイヤモンドの破片ができます!</p> <p>このタイプは、mods から炉やその他の調理器具で何を燃やすことができるかを定義するため、cooking タイプの付属品です。</p> <pre><code class="lua">minetest.register_craft({ type = "fuel", recipe = "mymod:diamond", burntime = 300, }) </code></pre> <p>他のレシピのような出力はありませんが、燃料として持続する時間を秒単位で定義する燃焼時間( burn time )があります。だから、ダイヤモンドは 300 秒間の燃料として良いです!</p> <h3 id="Groups &lt;a name=&quot;#groups&quot;&gt;&lt;/a&gt;"><a href="#Groups+%26lt%3Ba+name%3D%26quot%3B%23groups%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Groups <a name="#groups"></a></a></h3> <p>アイテムは多くのグループのメンバーになることができ、グループは多くのメンバーを持つことができます。グループは <code>groups</code> 定義テーブルのプロパティを使用して定義され、関連付けられた値があります。</p> <pre><code class="lua">groups = {cracky = 3, wood = 1} </code></pre> <p>グループを使用する理由はいくつかあります。まず、グループは、掘削タイプや可燃性( flammability )などのプロパティを説明するために使用されます。次に、アイテム名の代わりにグループをクラフトレシピで使用して、グループ内の任意のアイテムを使用できるようにすることができます。</p> <pre><code class="lua">minetest.register_craft({ type = "shapeless", output = "mymod:diamond_thing 3", recipe = {"group:wood", "mymod:diamond"} }) </code></pre> <h3 id="Tools, Capabilities, and Dig Types &lt;a name=&quot;#tools-capabilities-and-dig-types&quot;&gt;&lt;/a&gt;"><a href="#Tools%2C+Capabilities%2C+and+Dig+Types+%26lt%3Ba+name%3D%26quot%3B%23tools-capabilities-and-dig-types%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Tools, Capabilities, and Dig Types <a name="#tools-capabilities-and-dig-types"></a></a></h3> <p>Dig types は、さまざまなツールで掘削したときのノードの強度を定義するために使用されるグループです。関連付けられた値が高いdigタイプのグループは、ノードの切断がより簡単かつ迅速であることを意味します。複数の dig タイプを組み合わせて、複数のタイプのツールをより効率的に使用できるようにすることができます。Dig type のないノードは、どのツールでも掘削できません。</p> <div class="table-responsive"><table> <thead> <tr> <th>グループ</th> <th>最高のツール</th> <th>説明</th> </tr> </thead> <tbody> <tr> <td>crumbly</td> <td>spade</td> <td>土、砂</td> </tr> <tr> <td>cracky</td> <td>pickaxe (つるはし)</td> <td>タフな(しかしもろい)石のようなもの</td> </tr> <tr> <td>snappy</td> <td><em>どれか</em></td> <td>細かい工具を使用して切断できます。 例:葉、小植物、ワイヤー、金属板</td> </tr> <tr> <td>choppy</td> <td>axe (斧)</td> <td>鋭い力で切ることができます。例:木、木の板</td> </tr> <tr> <td>fleshy</td> <td>sword (剣)</td> <td>動物やプレイヤーのような生き物。 これは、打撃時の血液への影響を示唆している可能性があります。</td> </tr> <tr> <td>explody</td> <td>?</td> <td>特に爆発しやすい</td> </tr> <tr> <td>oddly_breakable_by_hand</td> <td><em>どれか</em></td> <td>松明など-非常に素早く掘ります</td> </tr> </tbody> </table></div> <p>すべてのツールにはツール機能があります。機能には、サポートされている Dig type タイプのリストと、掘削時間( dig times )や摩耗量など、各タイプに関連するプロパティが含まれます。ツールは、タイプごとに最大サポート硬度を持つこともできます。これにより、弱いツールが硬いノードを掘るのを防ぐことができます。ツールがすべての Dig type を機能に含めることは非常に一般的であり、あまり適切でないものは非常に非効率的な特性を持っています。プレイヤーが現在使用しているアイテムに明示的なツール機能がない場合は、代わりに現在のハンドの機能が使用されます。</p> <pre><code class="lua">minetest.register_tool("mymod:tool", { description = "My Tool", inventory_image = "mymod_tool.png", tool_capabilities = { full_punch_interval = 1.5, max_drop_level = 1, groupcaps = { crumbly = { maxlevel = 2, uses = 20, times = { [1]=1.60, [2]=1.20, [3]=0.80 } }, }, damage_groups = {fleshy=2}, }, }) </code></pre> <p>Groupcaps は、ノードを掘るためにサポートされている掘りタイプのリストです。ダメージグループは、ツールがオブジェクトにダメージを与える方法を制御するためのものです。これについては、オブジェクト、プレーヤー、エンティティの章で後述します。</p> <h2 id="4 - Creating Textures &lt;a name=&quot;creating_textures&quot;&gt;&lt;/a&gt;"><a href="#4+-+Creating+Textures+%26lt%3Ba+name%3D%26quot%3Bcreating_textures%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">4 - Creating Textures <a name="creating_textures"></a></a></h2> <h3 id="Introduction"><a href="#Introduction">Introduction</a></h3> <p>テクスチャを作成して最適化できることは、Minetest 用に開発するときに非常に役立つスキルです。ピクセルアートテクスチャの操作に関連する多くのテクニックがあり、これらのテクニックを理解すると、作成するテクスチャの品質が大幅に向上します。</p> <p>優れたピクセルアートを作成するための詳細なアプローチはこの本の範囲外であり、代わりに最も関連性のある基本的なテクニックのみを取り上げます。ピクセルアートをより詳細にカバーする、利用可能な多くの<a target="_blank" rel="nofollow noopener" href="http://www.photonstorm.com/art/tutorials-art/16x16-pixel-art-tutorial">優れたオンラインチュートリアル</a>があります。</p> <ul> <li><a href="#techniques">Techniques</a> <ul> <li><a href="#using-the-pencil">Using the Pencil</a></li> <li><a href="#tiling">Tiling</a></li> <li><a href="#transparency">Transparency</a></li> </ul></li> <li><a href="#editors">Editors</a> <ul> <li><a href="#ms-paint">MS Paint</a></li> <li><a href="#gimp">GIMP</a></li> </ul></li> </ul> <h3 id="Techniques &lt;a name=&quot;#techniques&quot;&gt;&lt;/a&gt;"><a href="#Techniques+%26lt%3Ba+name%3D%26quot%3B%23techniques%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Techniques <a name="#techniques"></a></a></h3> <h4 id="Using the Pencil &lt;a name=&quot;#using-the-pencil&quot;&gt;&lt;/a&gt;"><a href="#Using+the+Pencil+%26lt%3Ba+name%3D%26quot%3B%23using-the-pencil%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Using the Pencil <a name="#using-the-pencil"></a></a></h4> <p>鉛筆ツールは、ほとんどのエディターで使用できます。最小サイズに設定すると、画像の他の部分を変更せずに、一度に1つのピクセルを編集できます。ピクセルを1つずつ操作することで、意図しないぼかしを発生させることなく、クリアでシャープなテクスチャを作成できます。また、高レベルの精度と制御を提供します。</p> <h4 id="Tiling &lt;a name=&quot;#tiling&quot;&gt;&lt;/a&gt;"><a href="#Tiling+%26lt%3Ba+name%3D%26quot%3B%23tiling%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Tiling <a name="#tiling"></a></a></h4> <p>ノードに使用されるテクスチャは、通常、タイル状に設計する必要があります。これは、同じテクスチャを持つ複数のノードを一緒に配置すると、エッジが正しく整列することを意味します。</p> <p>エッジを正しく一致させないと、結果は見た目がはるかに悪くなります。</p> <h4 id="Transparency &lt;a name=&quot;#transparency&quot;&gt;&lt;/a&gt;"><a href="#Transparency+%26lt%3Ba+name%3D%26quot%3B%23transparency%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Transparency <a name="#transparency"></a></a></h4> <p>透明度は、ほぼすべてのクラフトアイテムとガラスなどの一部のノードのテクスチャを作成するときに重要です。すべてのエディタが透明度をサポートしているわけではないため、作成するテクスチャに適したエディタを選択してください。</p> <h3 id="Editors &lt;a name=&quot;#editors&quot;&gt;&lt;/a&gt;"><a href="#Editors+%26lt%3Ba+name%3D%26quot%3B%23editors%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Editors <a name="#editors"></a></a></h3> <h4 id="MS Paint &lt;a name=&quot;#ms-paint&quot;&gt;&lt;/a&gt;"><a href="#MS+Paint+%26lt%3Ba+name%3D%26quot%3B%23ms-paint%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">MS Paint <a name="#ms-paint"></a></a></h4> <p>MS ペイントは、基本的なテクスチャデザインに役立つシンプルなエディタです。ただし、透過性はサポートされていません。これは通常、ノードの側面のテクスチャを作成する場合は問題になりませんが、テクスチャに透明度が必要な場合は、別のエディタを選択する必要があります。</p> <h4 id="GIMP &lt;a name=&quot;#gimp&quot;&gt;&lt;/a&gt;"><a href="#GIMP+%26lt%3Ba+name%3D%26quot%3B%23gimp%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">GIMP <a name="#gimp"></a></a></h4> <p>GIMP は Minetest コミュニティで一般的に使用されています。その機能の多くがすぐには明らかにならないため、学習曲線はかなり高くなります。</p> <p>GIMP を使用する場合、鉛筆ツールはツールボックスから選択できます。</p> <p><img src="https://rubenwardy.com/minetest_modding_book/static/pixel_art_gimp_pencil.png" alt="Pencil in GIMP" /></p> <p>消しゴムツールの [ハードエッジ] チェックボックスを選択することもお勧めします。</p> <h2 id="5 - Node Drawtypes"><a href="#5+-+Node+Drawtypes">5 - Node Drawtypes</a></h2> <h2 id="Node Drawtypes &lt;a name=&quot;#node_drawtypes&quot;&gt;&lt;/a&gt;"><a href="#Node+Drawtypes+%26lt%3Ba+name%3D%26quot%3B%23node_drawtypes%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Node Drawtypes <a name="#node_drawtypes"></a></a></h2> <h2 id="Introduction"><a href="#Introduction">Introduction</a></h2> <p>ノードを描画する方法は、 <em>drawtype</em> と呼ばれます。利用可能な drawtype はたくさんあります。 drawtype の動作は、ノードタイプ定義にプロパティを提供することで制御できます。これらのプロパティは、このノードのすべてのインスタンスで修正されています。と呼ばれるものを使用して、ノードごとにいくつかのプロパティを制御することができます <code>param2</code> 。</p> <p>前の章では、ノードとアイテムの概念が紹介されましたが、ノードの完全な定義は示されていませんでした。 Minetest の世界は、位置の 3D グリッドです。各位置はノードと呼ばれ、ノードタイプ(名前)と 2 つのパラメーター( param1 と param2 )で構成されます。この関数<code>minetest.register_node</code>は、実際にはノードを登録しないという点で少し誤解を招きます。新しい <em>type</em> のノードを登録します。</p> <p>ノードパラメータは、ノードが個別にレンダリングされる方法を制御するために使用されます。 <code>param1</code> は、ノードのライティングを格納するために使用され、 <code>param2</code> の意味は、ノードタイプ定義の <code>paramtype2</code> プロパティによって異なります。</p> <ul> <li><a href="#cubic-nodes-normal-and-allfaces">Cubic Nodes: Normal and Allfaces</a></li> <li><a href="#glasslike-nodes">Glasslike Nodes</a> <ul> <li><a href="#glasslikeframed">Glasslike_Framed</a></li> </ul></li> <li><a href="#airlike-nodes">Airlike Nodes</a></li> <li><a href="#lighting-and-sunlight-propagation">Lighting and Sunlight Propagation</a></li> <li><a href="#liquid-nodes">Liquid Nodes</a></li> <li><a href="#node-boxes">Node Boxes</a> <ul> <li><a href="#wallmounted-node-boxes">Wallmounted Node Boxes</a></li> </ul></li> <li><a href="#mesh-nodes">Mesh Nodes</a></li> <li><a href="#signlike-nodes">Signlike Nodes</a></li> <li><a href="#plantlike-nodes">Plantlike Nodes</a></li> <li><a href="#firelike-nodes">Firelike Nodes</a></li> <li><a href="#more-drawtypes">More Drawtypes</a></li> </ul> <h2>Cubic Nodes: Normal and Allfaces <a name="#cubic-nodes-normal-and-allfaces"></a></h2> <p><img src="https://rubenwardy.com/minetest_modding_book/static/drawtype_normal.png" alt="Normal Drawtype" /></p> <p>Normal Drawtype</p> <p>通常の drawtype は、通常、立方体ノードをレンダリングするために使用されます。通常のノードの側がソリッド側に接している場合、その側はレンダリングされないため、パフォーマンスが大幅に向上します。</p> <p>対照的に、 allfaces drawtype は、ソリッドノードに対して上向きの場合でも内側をレンダリングします。これは、リーフノードなど、側面が部分的に透明なノードに適しています。 allfaces_optional drawtype を使用して、ユーザーが遅い描画をオプトアウトできるようにすることができます。その場合、通常のノードのように動作します。</p> <pre><code class="lua">minetest.register_node("mymod:diamond", { description = "Alien Diamond", tiles = {"mymod_diamond.png"}, groups = {cracky = 3}, }) minetest.register_node("default:leaves", { description = "Leaves", drawtype = "allfaces_optional", tiles = {"default_leaves.png"} }) </code></pre> <p>注:通常の drawtype はデフォルトの drawtype であるため、明示的に指定する必要はありません。</p> <h2>Glasslike Nodes <a name="#glasslike-nodes"></a></h2> <p>ガラス状ノードと通常ノードの違いは、ガラス状ノードを通常ノードの隣に配置しても、通常ノードの側面が非表示にならないことです。ガラスのようなノードは透明になる傾向があるため、これは便利です。通常の drawtype を使用すると、 world を透視することができます。</p> <p><img src="https://rubenwardy.com/minetest_modding_book/static/drawtype_glasslike_edges.png" alt="Glasslike's Edges" /></p> <p>Glasslike's Edges</p> <pre><code class="lua">minetest.register_node("default:obsidian_glass", { description = "Obsidian Glass", drawtype = "glasslike", tiles = {"default_obsidian_glass.png"}, paramtype = "light", is_ground_content = false, sunlight_propagates = true, sounds = default.node_sound_glass_defaults(), groups = {cracky=3,oddly_breakable_by_hand=3}, }) </code></pre> <h3 id="Glasslike&#95;Framed &lt;a name=&quot;#glasslikeframed&quot;&gt;&lt;/a&gt;"><a href="#Glasslike%26%2395%3BFramed+%26lt%3Ba+name%3D%26quot%3B%23glasslikeframed%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Glasslike_Framed <a name="#glasslikeframed"></a></a></h3> <p>ノードのエッジが個々のノードではなく、3D 効果で全体を一周します。</p> <p><img src="https://rubenwardy.com/minetest_modding_book/static/drawtype_glasslike_framed.png" alt="Glasslike_framed's Edges" /></p> <p>Glasslike_Framed's Edges</p> <p>glasslike_framed_optional drawtype を使用して、ユーザーがフレーム付きの外観に<em>オプトイン</em>できるようにすることができます。</p> <pre><code class="lua">minetest.register_node("default:glass", { description = "Glass", drawtype = "glasslike_framed", tiles = {"default_glass.png", "default_glass_detail.png"}, inventory_image = minetest.inventorycube("default_glass.png"), paramtype = "light", sunlight_propagates = true, -- Sunlight can shine through block groups = {cracky = 3, oddly_breakable_by_hand = 3}, sounds = default.node_sound_glass_defaults() }) </code></pre> <h2>Airlike Nodes <a name="#airlike-nodes"></a></h2> <p>これらのノードはレンダリングされないため、テクスチャはありません。</p> <pre><code class="lua">minetest.register_node("myair:air", { description = "MyAir (you hacker you!)", drawtype = "airlike", paramtype = "light", sunlight_propagates = true, walkable = false, -- Would make the player collide with the air node pointable = false, -- You can't select the node diggable = false, -- You can't dig the node buildable_to = true, -- Nodes can be replace this node. -- (you can place a node and remove the air node -- that used to be there) air_equivalent = true, drop = "", groups = {not_in_creative_inventory=1} }) </code></pre> <h2>Lighting and Sunlight Propagation <a name="#lighting-and-sunlight-propagation"></a></h2> <p>ノードのライティングは param1 に保存されます。ノードの側面をシェーディングする方法を理解するために、隣接ノードのライト値が使用されます。このため、ソリッドノードは光を遮断するため、光の値がありません。</p> <p>デフォルトでは、ノードタイプでは、どのノードインスタンスにもライトを保存できません。通常、ガラスや空気などの一部のノードが光を通過できることが望ましいです。これを行うには、定義する必要のある 2 つのプロパティがあります。</p> <pre><code class="lua">paramtype = "light", sunlight_propagates = true, </code></pre> <p>最初の行は、param1が実際に光レベルを保存することを意味します。<br /> 2 行目は、太陽光が値を減少させることなくこのノードを通過する必要があることを意味します。</p> <h2>Liquid Nodes <a name="#liquid-nodes"></a></h2> <p><img src="https://rubenwardy.com/minetest_modding_book/static/drawtype_liquid.png" alt="Liquid Drawtype" /></p> <p>Liquid Drawtype</p> <p>液体の種類ごとに、 2 つのノード定義が必要です。1つは液体ソース用で、もう1つは流れる液体用です。</p> <pre><code class="lua">-- Some properties have been removed as they are beyond -- the scope of this chapter. minetest.register_node("default:water_source", { drawtype = "liquid", paramtype = "light", inventory_image = minetest.inventorycube("default_water.png"), -- ^ this is required to stop the inventory image from being animated tiles = { { name = "default_water_source_animated.png", animation = { type = "vertical_frames", aspect_w = 16, aspect_h = 16, length = 2.0 } } }, special_tiles = { -- New-style water source material (mostly unused) { name = "default_water_source_animated.png", animation = {type = "vertical_frames", aspect_w = 16, aspect_h = 16, length = 2.0}, backface_culling = false, } }, -- -- Behavior -- walkable = false, -- The player falls through pointable = false, -- The player can't highlight it diggable = false, -- The player can't dig it buildable_to = true, -- Nodes can be replace this node alpha = 160, -- -- Liquid Properties -- drowning = 1, liquidtype = "source", liquid_alternative_flowing = "default:water_flowing", -- ^ when the liquid is flowing liquid_alternative_source = "default:water_source", -- ^ when the liquid is a source liquid_viscosity = WATER_VISC, -- ^ how fast liquid_range = 8, -- ^ how far post_effect_color = {a=64, r=100, g=100, b=200}, -- ^ colour of screen when the player is submerged }) </code></pre> <p>フローノードの定義は似ていますが、名前とアニメーションが異なります。完全な例については、 minetest_game のデフォルト mod の default:water_flowing を参照してください。</p> <h2>Node Boxes <a name="#node-boxes"></a></h2> <p><img src="https://rubenwardy.com/minetest_modding_book/static/drawtype_nodebox.gif" alt="Nodebox drawtype" /></p> <p>Nodebox drawtype</p> <p>ノードボックスを使用すると、立方体ではなく、必要な数の直方体で作成されたノードを作成できます。</p> <pre><code class="lua">minetest.register_node("stairs:stair_stone", { drawtype = "nodebox", paramtype = "light", node_box = { type = "fixed", fixed = { {-0.5, -0.5, -0.5, 0.5, 0, 0.5}, {-0.5, 0, 0, 0.5, 0.5, 0.5}, }, } }) </code></pre> <p>最も重要な部分はノードボックステーブルです。</p> <pre><code class="lua">{-0.5, -0.5, -0.5, 0.5, 0, 0.5}, {-0.5, 0, 0, 0.5, 0.5, 0.5} </code></pre> <p>各行は直方体であり、結合されて1つのノードになります。最初の 3 つの数字は、左下隅の-0.5から0.5までの座標であり、最後の 3 つの数字は反対側の角です。それらは X, Y, Z の形式であり、Yは上です。</p> <p><a target="_blank" rel="nofollow noopener" href="hhttps://forum.minetest.net/viewtopic.php?f=14&t=284">NodeBoxEditor</a>)を使用して、エッジをドラッグすることでノードボックスを作成できます。これは、手動で行うよりも視覚的です。</p> <h3 id="Wallmounted Node Boxes &lt;a name=&quot;#wallmounted-node-boxes&quot;&gt;&lt;/a&gt;"><a href="#Wallmounted+Node+Boxes+%26lt%3Ba+name%3D%26quot%3B%23wallmounted-node-boxes%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Wallmounted Node Boxes <a name="#wallmounted-node-boxes"></a></a></h3> <p>トーチのように床、壁、または天井に配置するときに、異なる node box が必要になる場合があります。</p> <pre><code class="lua">minetest.register_node("default:sign_wall", { drawtype = "nodebox", node_box = { type = "wallmounted", -- Ceiling wall_top = { {-0.4375, 0.4375, -0.3125, 0.4375, 0.5, 0.3125} }, -- Floor wall_bottom = { {-0.4375, -0.5, -0.3125, 0.4375, -0.4375, 0.3125} }, -- Wall wall_side = { {-0.5, -0.3125, -0.4375, -0.4375, 0.3125, 0.4375} } }, }) </code></pre> <h2>Mesh Nodes <a name="#mesh-nodes"></a></h2> <p>node box は一般的に作成が簡単ですが、直方体のみで構成できるという制限があります。node box は最適化されていません。完全に非表示になっている場合でも、内面はレンダリングされます。</p> <p>面はメッシュ上の平らな面です。内面は、 2 つの異なるノードボックスの面が重なるときに発生し、ノードボックスモデルの一部が非表示になりますが、レンダリングされたままになります。</p> <p>メッシュノードは次のように登録できます。</p> <pre><code class="lua">minetest.register_node("mymod:meshy", { drawtype = "mesh", -- Holds the texture for each "material" tiles = { "mymod_meshy.png" }, -- Path to the mesh mesh = "mymod_meshy.b3d", }) </code></pre> <p>メッシュが <code>models</code> ディレクトリで使用可能であることを確認してください。ほとんどの場合、メッシュは mod のフォルダーにあるはずですが、依存している別の mod によって提供されるメッシュを共有することは問題ありません。たとえば、より多くのタイプの家具を追加する mod は、基本的な furniture mod によって提供されるモデルを共有したい場合があります。</p> <h2>Signlike Nodes <a name="#signlike-nodes"></a></h2> <p>Signlike nodes はフラットノードであり、他のノードの側面に取り付けることができます。</p> <p>この drawtype の名前にもかかわらず、サインは実際にはサインライクを使用する傾向はありませんが、代わりに <code>nodebox</code> drawtype を使用して 3D 効果を提供します。しかし、 <code>signlike</code> drawtype は、一般的にはしごで使用されています。</p> <pre><code class="lua">minetest.register_node("default:ladder_wood", { drawtype = "signlike", tiles = {"default_ladder_wood.png"}, -- Required: store the rotation in param2 paramtype2 = "wallmounted", selection_box = { type = "wallmounted", }, }) </code></pre> <h2>Plantlike Nodes <a name="#plantlike-nodes"></a></h2> <p><img src="https://rubenwardy.com/minetest_modding_book/static/drawtype_plantlike.png" alt="Plantlike Drawtype" /></p> <p>Plantlike Drawtype</p> <p>植物のようなノードは、 X のようなパターンでタイルを描画します。</p> <pre><code class="lua">minetest.register_node("default:papyrus", { drawtype = "plantlike", -- Only one texture used tiles = {"default_papyrus.png"}, selection_box = { type = "fixed", fixed = {-6 / 16, -0.5, -6 / 16, 6 / 16, 0.5, 6 / 16}, }, }) </code></pre> <h2>Firelike Nodes <a name=""></a></h2> <p>Firelike は、壁や天井に「しがみつく」ように設計されていることを除けば、Plantlike に似ています。</p> <p><img src="https://rubenwardy.com/minetest_modding_book/static/drawtype_firelike.png" alt="Firelike nodes" /></p> <p>Firelike nodes <a name="#firelike-nodes"></a></p> <pre><code class="lua">minetest.register_node("mymod:clingere", { drawtype = "firelike", -- Only one texture used tiles = { "mymod:clinger" }, }) </code></pre> <h2>More Drawtypes <a name="#more-drawtypes"></a></h2> <p>これは包括的なリストではありません。次のような他のタイプがあります。</p> <ul> <li>Fencelike</li> <li>Plantlike rooted - 水の中の植物のため</li> <li>Raillike - カートトラックのため</li> <li>Torchlike - 2D 壁 / 床 / 天井ノード用。 Minetest Game のトーチは、実際にはメッシュノードの 2 つの異なるノード定義を使用します (default:torch and default:torch_wall) 。</li> </ul> <p>いつものように、完全なリストについては<a target="_blank" rel="nofollow noopener" href="https://translate.googleusercontent.com/translate_c?depth=1&pto=aue&rurl=translate.google.com&sl=en&sp=nmt4&tl=ja&u=https://rubenwardy.com/minetest_modding_book/lua_api.html&usg=ALkJrhjuqUgv8l1eQS2P2OwYO9vNiWBvvw#node-drawtypes"> Lua APIのドキュメント</a>をお読みください。</p> <h2 id="6 - ItemStacks and Inventories"><a href="#6+-+ItemStacks+and+Inventories">6 - ItemStacks and Inventories</a></h2> <h2 id="ItemStacks and Inventories &lt;a name=&quot;#itemstacks&quot;&gt;&lt;/a&gt;"><a href="#ItemStacks+and+Inventories+%26lt%3Ba+name%3D%26quot%3B%23itemstacks%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">ItemStacks and Inventories <a name="#itemstacks"></a></a></h2> <h2 id="Introduction"><a href="#Introduction">Introduction</a></h2> <p>この章では、プレーヤーインベントリ、ノードインベントリ、またはデタッチインベントリなどの、インベントリの使用方法と操作方法を学習します。</p> <ul> <li><a href="#what-are-itemstacks-and-inventories">What are ItemStacks and Inventories?</a></li> <li><a href="#itemstacks">ItemStacks</a></li> <li><a href="#inventory-locations">Inventory Locations</a></li> <li><a href="#lists">Lists</a> <ul> <li><a href="#size-and-width">Size and Width</a></li> <li><a href="#checking-contents">Checking Contents</a></li> </ul></li> <li><a href="#modifying-inventories-and-itemstacks">Modifying Inventories and ItemStacks</a> <ul> <li><a href="#adding-to-a-list">Adding to a List</a></li> <li><a href="#taking-items">Taking Items</a></li> <li><a href="#manipulating-stacks">Manipulating Stacks</a></li> </ul></li> <li><a href="#wear">Wear</a></li> <li><a href="#lua-tables">Lua Tables</a></li> </ul> <h2>What are ItemStacks and Inventories? <a name="#what-are-itemstacks-and-inventories"></a></h2> <p>ItemStack は、インベントリ内の単一セルの背後にあるデータです。</p> <p><em>inventory</em> は <em>inventory lists</em> コレクションです、それぞれが ItemStacks の 2D グリッドです。 <em>inventory lists</em> は、インベントリのコンテキストでは単に <em>lists</em> と呼ばれます。インベントリのポイントは、プレーヤーとノードに最大で1つのインベントリしかない場合に、複数のグリッドを許可することです。</p> <h2>ItemStacks <a name="#itemstacks"></a></h2> <p>ItemStack には、名前( name )、カウント( count )、摩耗( wear )、メタデータ( metadata )の 4 つのコンポーネントがあります。</p> <p>アイテム名は、登録済みアイテムのアイテム名、エイリアス、または不明なアイテム名の場合があります。不明なアイテムは、ユーザーが mod をアンインストールする場合、または mod がエイリアスの登録などの予防措置なしにアイテムを削除する場合によく見られます。</p> <pre><code class="lua">print(stack:get_name()) stack:set_name("default:dirt") if not stack:is_known() then print("Is an unknown item!") end </code></pre> <p>カウントは常に 0 以上になります。通常のゲームプレイでは、カウントはアイテムの最大スタックサイズを超えないようにする必要があります - <code>stack_max</code> 。ただし、管理コマンド( admin commands )とバグのある mod を使用すると、スタックが最大サイズを超える場合があります。</p> <pre><code class="lua">print(stack:get_stack_max()) </code></pre> <p>ItemStack は空にすることができ、その場合、カウントは0になります。</p> <pre><code class="lua">print(stack:get_count()) stack:set_count(10) </code></pre> <p>ItemStack は、 ItemStack 関数を使用して複数の方法で構築できます。</p> <pre><code class="lua">ItemStack() -- name="", count=0 ItemStack("default:pick_stone") -- count=1 ItemStack("default:stone 30") ItemStack({ name = "default:wood", count = 10 }) </code></pre> <p>アイテムメタデータは、アイテムに関するデータの無制限の Key-Value ストアです。 Key-Value は、名前(キーと呼ばれる)を使用してデータ(値と呼ばれる)にアクセスすることを意味します。一部のキーには特別な意味があります。たとえば、<code>description</code> スタックごとのアイテムの説明に使用されます。これについては、メタデータとストレージの章で詳しく説明します。</p> <h2>Inventory Locations <a name="#inventory-locations"></a></h2> <p>インベントリロケーションは、インベントリがどこに、どのように保存されるかです。インベントリロケーションには、プレーヤー、ノード、およびデタッチの 3 つのタイプがあります。インベントリは 1 つのロケーションに直接関連付けられています。インベントリが更新されると、すぐに更新されます。</p> <p>ノードインベントリは、チェストなどの特定のノードの位置に関連しています。ノードは<a href="#metadata">ノードメタデータ</a>に保存されているため、ロードする必要があります。</p> <pre><code class="lua">local inv = minetest.get_inventory({ type="node", pos={x=1, y=2, z=3} }) </code></pre> <p>上記は、一般に<em>InvRef</em>と呼ばれる<em>インベントリ参照</em>を取得します。ノードインベントリ参照は、ノードインベントリを操作するために使用されます。<br /> <em>参照</em>とは、データが実際にはそのオブジェクト内に格納されていないことを意味しますが、オブジェクトは代わりにデータをインプレースで直接更新します。</p> <p>ノードインベントリ参照の場所は、次のように見つけることができます。</p> <pre><code class="lua">local location = inv:get_location() </code></pre> <p>プレーヤーのインベントリは、同様に、またはプレーヤーの参照を使用して取得できます。プレイヤーはインベントリにアクセスするためにオンラインである必要があります。</p> <pre><code class="lua">local inv = minetest.get_inventory({ type="player", name="player1" }) -- or local inv = player:get_inventory() </code></pre> <p>デタッチされたインベントリは、プレーヤーまたはノードから独立しているインベントリです。切り離されたノードインベントリも、再起動しても保存されません。</p> <pre><code class="lua">local inv = minetest.get_inventory({ type="detached", name="inventory_name" }) </code></pre> <p>他のタイプのインベントリとは異なり、アクセスする前に、まずデタッチされたインベントリを作成する必要があります。</p> <pre><code class="lua">minetest.create_detached_inventory("inventory_name") </code></pre> <p>create_detached_inventory 関数は 3 つの引数を受け入れますが、最初の引数(インベントリ名)のみが必要です。 2 番目の引数は、コールバックのテーブルを取ります。これは、プレーヤーがインベントリと対話する方法を制御するために使用できます。</p> <pre><code class="lua">-- Input only detached inventory minetest.create_detached_inventory("inventory_name", { allow_move = function(inv, from_list, from_index, to_list, to_index, count, player) return count -- allow moving end, allow_put = function(inv, listname, index, stack, player) return stack:get_count() -- allow putting end, allow_take = function(inv, listname, index, stack, player) return -1 -- don't allow taking end, on_put = function(inv, listname, index, stack, player) minetest.chat_send_all(player:get_player_name() .. " gave " .. stack:to_string() .. " to the donation chest from " .. minetest.pos_to_string(player:get_pos())) end, }) </code></pre> <p>パーミッションコールバック(つまり、 <code>allow_</code> で始まるもの)は、転送するアイテムの数を返します。転送を完全に防ぐために -1 が使用されます。</p> <p>アクションコールバック( <code>on</code> で始まる)には戻り値がありません。</p> <h2>Lists <a name="#lists"></a></h2> <p>インベントリリストは、複数のグリッドを1つの場所に保存できるようにするために使用される概念です。これは、<em>メイン</em>インベントリや<em>クラフト</em>スロットなど、すべてのゲームに共通のリストが多数あるため、プレイヤーにとって特に便利です。</p> <h3 id="Size and Width &lt;a name=&quot;#size-and-width&quot;&gt;&lt;/a&gt;"><a href="#Size+and+Width+%26lt%3Ba+name%3D%26quot%3B%23size-and-width%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Size and Width <a name="#size-and-width"></a></a></h3> <p>リストには、グリッド内のセルの総数であるサイズと、エンジン内でのみ使用される幅があります。ウィンドウの背後にあるコードが使用する幅を決定するため、ウィンドウにインベントリを描画する場合、リストの幅は使用されません。</p> <pre><code class="lua">if inv:set_size("main", 32) then inv:set_width("main", 8) print("size: " .. inv:get_size("main")) print("width: " .. inv:get_width("main")) else print("Error! Invalid itemname or size to set_size()") end </code></pre> <p><code>set_size</code> ではリスト名またはサイズが無効な場合は失敗し、 false を返します。たとえば、新しいサイズが小さすぎて、ノードインベントリ内の現在のすべてのアイテムを収めることができない場合があります。</p> <h3 id="Checking Contents &lt;a name=&quot;#checking-contents&quot;&gt;&lt;/a&gt;"><a href="#Checking+Contents+%26lt%3Ba+name%3D%26quot%3B%23checking-contents%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Checking Contents <a name="#checking-contents"></a></a></h3> <p><code>is_empty</code> ではリストにアイテムが含まれているかどうかを確認するために使用できます。</p> <pre><code class="lua">if inv:is_empty("main") then print("The list is empty!") end </code></pre> <p><code>contains_item</code> ではリストに特定のアイテムが含まれているかどうかを確認するために使用できます。</p> <pre><code class="lua">if inv:contains_item("main", "default:stone") then print("I've found some stone!") end </code></pre> <h2 id="Modifying Inventories and ItemStacks"><a href="#Modifying+Inventories+and+ItemStacks">Modifying Inventories and ItemStacks</a></h2> <h3 id="Adding to a List &lt;a name=&quot;#adding-to-a-list&quot;&gt;&lt;/a&gt;"><a href="#Adding+to+a+List+%26lt%3Ba+name%3D%26quot%3B%23adding-to-a-list%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Adding to a List <a name="#adding-to-a-list"></a></a></h3> <p><code>add_item</code> ではリストにアイテムを追加します(この場合 <code>"main"</code> )。以下の例では、最大スタックサイズも尊重されます。</p> <pre><code class="lua">local stack = ItemStack("default:stone 99") local leftover = inv:add_item("main", stack) if leftover:get_count() > 0 then print("Inventory is full! " .. leftover:get_count() .. " items weren't added") end </code></pre> <h3 id="Taking Items &lt;a name=&quot;#taking-items&quot;&gt;&lt;/a&gt;"><a href="#Taking+Items+%26lt%3Ba+name%3D%26quot%3B%23taking-items%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Taking Items <a name="#taking-items"></a></a></h3> <p>リストからアイテムを削除するには:</p> <pre><code class="lua">local taken = inv:remove_item("main", stack) print("Took " .. taken:get_count()) </code></pre> <h3 id="Manipulating Stacks &lt;a name=&quot;#manipulating-stacks&quot;&gt;&lt;/a&gt;"><a href="#Manipulating+Stacks+%26lt%3Ba+name%3D%26quot%3B%23manipulating-stacks%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Manipulating Stacks <a name="#manipulating-stacks"></a></a></h3> <p>最初に取得することで、個々のスタックを変更できます :</p> <pre><code class="lua">local stack = inv:get_stack(listname, 0) </code></pre> <p>次に、プロパティを設定するか、以下を尊重するメソッドを使用して、それらを変更します <code>stack_size</code> :</p> <pre><code class="lua">local stack = ItemStack("default:stone 50") local to_add = ItemStack("default:stone 100") local leftover = stack:add_item(to_add) local taken = stack:take_item(19) print("Could not add" .. leftover:get_count() .. " of the items.") -- ^ will be 51 print("Have " .. stack:get_count() .. " items") -- ^ will be 80 -- min(50+100, stack_max) - 19 = 80 -- where stack_max = 99 </code></pre> <p><code>add_item</code> では ItemStack にアイテムを追加し、追加できなかったアイテムを返します。<br /> <code>take_item</code> ではアイテムの数まで要しますが、それより少なくなる場合があり、取得したスタックを返します。</p> <p>最後に、アイテムスタックを設定します。</p> <pre><code class="lua">inv:set_stack(listname, 0, stack) </code></pre> <h2>Wear <a name="#wear"></a></h2> <p>ツールには摩耗があります。摩耗はプログレスバーを示し、完全に摩耗するとツールが壊れます。摩耗は 65535 のうちの数です。高いほど、ツールは摩耗します。</p> <p>摩耗は <code>add_wear()</code> 、 <code>get_wear()</code> と <code>set_wear(wear)</code> を使用して操作することができます。</p> <pre><code class="lua">local stack = ItemStack("default:pick_mese") local max_uses = 10 -- This is done automatically when you use a tool that digs things -- It increases the wear of an item by one use. stack:add_wear(65535 / (max_uses - 1)) </code></pre> <p>ノードを掘るとき、ツールの摩耗の量は、掘られるノードに依存する可能性があります。したがって、 max_uses は、何を掘っているかによって異なります。</p> <h2>Lua Tables <a name="#lua-tables"></a></h2> <p>ItemStacks と Inventory は、テーブルとの間で変換できます。これは、コピーおよび一括操作に役立ちます。</p> <pre><code class="lua">-- Entire inventory local data = inv1:get_lists() inv2:set_lists(data) -- One list local listdata = inv1:get_list("main") inv2:set_list("main", listdata) </code></pre> <p><code>get_lists()</code> によって返されるリストのテーブルは次の形式になります。</p> <pre><code class="lua">{ list_one = { ItemStack, ItemStack, ItemStack, ItemStack, -- inv:get_size("list_one") elements }, list_two = { ItemStack, ItemStack, ItemStack, ItemStack, -- inv:get_size("list_two") elements } } </code></pre> <p><code>get_list()</code> は ItemStack のリストとして単一のリストを返します。</p> <p>注意すべき重要な点の1つは、上記のsetメソッドはリストのサイズを変更しないということです。これは、リストを空のテーブルに設定することでリストをクリアでき、サイズが減少しないことを意味します。</p> <pre><code class="lua">inv:set_list("main", {}) </code></pre> <h2 id="7 - Basic Map Operations"><a href="#7+-+Basic+Map+Operations">7 - Basic Map Operations</a></h2> <h3 id="Introduction"><a href="#Introduction">Introduction</a></h3> <p>この章では、マップ上で基本的なアクションを実行する方法を学習します。</p> <ul> <li><a href="#map-structure">Map Structure</a></li> <li><a href="#reading">Reading</a> <ul> <li><a href="#reading-nodes">Reading Nodes</a></li> <li><a href="#finding-nodes">Finding Nodes</a></li> </ul></li> <li><a href="#writing">Writing</a> <ul> <li><a href="#writing-nodes">Writing Nodes</a></li> <li><a href="#removing-nodes">Removing Nodes</a></li> </ul></li> <li><a href="#loading-blocks">Loading Blocks</a></li> <li><a href="#deleting-blocks">Deleting Blocks</a></li> </ul> <h3 id="Map Structure&lt;a name=&quot;#map-structure&quot;&gt;&lt;/a&gt;"><a href="#Map+Structure%26lt%3Ba+name%3D%26quot%3B%23map-structure%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Map Structure<a name="#map-structure"></a></a></h3> <p>Minetest マップは MapBlock に分割され、各 MapBlock はサイズ16の立方体です。プレイヤーがマップ内を移動すると、MapBlock が作成、ロード、およびアンロードされます。まだロードされていないマップの領域は、<em>無視できる</em>ノード、つまり通過できない選択できないプレースホルダーノードでいっぱいです。空のスペースは、通り抜けることができる目に見えないノードである<em>空気</em>ノードで満たされています。</p> <p>ロードされたマップブロックは、<em>アクティブブロック</em>と呼ばれることがよくあります。アクティブブロックは、 mods またはプレーヤーからの読み取りまたは書き込みが可能で、アクティブエンティティがあります。エンジンは、液体物理学の実行など、マップ上での操作も実行します。</p> <p>MapBlocks は、ワールドデータベースからロードするか、生成することができます。 MapBlocks は、デフォルトで最大値 31000 に設定されているマップ生成制限( <code>mapgen_limit</code> )まで生成されます。ただし、既存の MapBlock は、生成制限の範囲外でワールドデータベースからロードできます。</p> <h3 id="Reading &lt;a name=&quot;#reading&quot;&gt;&lt;/a&gt;"><a href="#Reading+%26lt%3Ba+name%3D%26quot%3B%23reading%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Reading <a name="#reading"></a></a></h3> <h4 id="Reading Nodes &lt;a name=&quot;#reading-nodes&quot;&gt;&lt;/a&gt;"><a href="#Reading+Nodes+%26lt%3Ba+name%3D%26quot%3B%23reading-nodes%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Reading Nodes <a name="#reading-nodes"></a></a></h4> <p>位置が決まったら、地図から読みだすことができます。</p> <pre><code class="lua">local node = minetest.get_node({ x = 1, y = 3, z = 4 }) print(dump(node)) --> { name=.., param1=.., param2=.. } </code></pre> <p>位置が小数の場合、それを含むノードに丸められます。この関数は常にノード情報を含むテーブルを返します。</p> <ul> <li><code>name</code> - ノード名。エリアがアンロードされるときに<em>無視</em>されます。</li> <li><code>param1</code> - ノード定義を参照してください。これは一般的に軽いでしょう。</li> <li><code>param2</code> - ノード定義を参照してください。</li> </ul> <p>ブロックが非アクティブの場合、関数は含まれているブロックをロードせず、代わりに <code>name</code> が <code>ignore</code> のテーブルを返すことに注意してください。代わりに <code>minetest.get_node_or_nil</code> を使用すると、<code>ignore</code> という名前のテーブルではなく <code>nil</code> が返されます。 ただし、それでもブロックは読み込まれません。 ブロックに実際に <code>ignore</code> が含まれている場合でも、これは <code>ignore</code> を返す可能性があります。 これは、マップ生成制限で定義されているように、マップの端( <code>mapgen_limit</code> )の近くで発生します。</p> <h4 id="Finding Nodes &lt;a name=&quot;#finding-nodes&quot;&gt;&lt;/a&gt;"><a href="#Finding+Nodes+%26lt%3Ba+name%3D%26quot%3B%23finding-nodes%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Finding Nodes <a name="#finding-nodes"></a></a></h4> <p>Minetestは、一般的なマップアクションを高速化するための多数のヘルパー関数を提供します。これらの中で最も一般的に使用されるのは、ノードを見つけるためです。</p> <p>たとえば、メセの近くでよりよく育つ特定の種類の植物を作りたいとしましょう。近くのメセノードを検索し、それに応じて成長率を調整する必要があります。</p> <pre><code class="lua">local grow_speed = 1 local node_pos = minetest.find_node_near(pos, 5, { "default:mese" }) if node_pos then minetest.chat_send_all("Node found at: " .. dump(node_pos)) grow_speed = 2 end </code></pre> <p>たとえば、近くに mese が多いほど成長率が上がるとしましょう。次に、エリア内の複数のノードを見つけることができる関数を使用する必要があります。</p> <pre><code class="lua">local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 }) local pos2 = vector.add(pos, { x = 5, y = 5, z = 5 }) local pos_list = minetest.find_nodes_in_area(pos1, pos2, { "default:mese" }) local grow_speed = 1 + #pos_list </code></pre> <p>上記のコードは、面積に基づいてチェックするのに対し <code>find_node_near</code> 、範囲に基づいてチェックするため、私たちが望むことを完全には実行しません。これを修正するには、残念ながら、手動で範囲を確認する必要があります。</p> <pre><code class="lua">local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 }) local pos2 = vector.add(pos, { x = 5, y = 5, z = 5 }) local pos_list = minetest.find_nodes_in_area(pos1, pos2, { "default:mese" }) local grow_speed = 1 for i=1, #pos_list do local delta = vector.subtract(pos_list[i], pos) if delta.x*delta.x + delta.y*delta.y <= 5*5 then grow_speed = grow_speed + 1 end end </code></pre> <p>これで、範囲内の mese ノードに基づいてコードが <code>grow_speed</code> に正しく増加します。実際の距離を取得するために位置を二乗するのではなく、位置からの距離の二乗を比較した方法に注意してください。これは、コンピューターが平方根の計算コストを高くするのを可能な限り回避する必要があるためです。</p> <p><code>find_nodes_with_meta</code> 、 <code>find_nodes_in_area_under_air</code> など、上記の 2 つの関数にはさらに多くのバリエーションがあり、これらは同様に機能し、他の状況で役立ちます。</p> <h3 id="Writing &lt;a name=&quot;#writing&quot;&gt;&lt;/a&gt;"><a href="#Writing+%26lt%3Ba+name%3D%26quot%3B%23writing%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Writing <a name="#writing"></a></a></h3> <h4 id="Writing Nodes &lt;a name=&quot;#writing-nodes&quot;&gt;&lt;/a&gt;"><a href="#Writing+Nodes+%26lt%3Ba+name%3D%26quot%3B%23writing-nodes%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Writing Nodes <a name="#writing-nodes"></a></a></h4> <p><code>set_node</code> マップへの書き込みに使用できます。 set_node を呼び出すたびに、ライティングが再計算されます。つまり、多数のノードでは set_node がかなり遅くなります。</p> <pre><code class="lua">minetest.set_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" }) local node = minetest.get_node({ x = 1, y = 3, z = 4 }) print(node.name) --> default:mese </code></pre> <p>set_node は、関連するメタデータまたはインベントリをその位置から削除します。これは、すべての状況で望ましいわけではありません。特に、 1 つの概念ノードを表すために複数のノード定義を使用している場合はそうです。<br /> この例は、ファーネスノードです。概念的には 1 つのノードと考えていますが、実際には 2 つです。</p> <p>次のように、メタデータやインベントリを削除せずにノードを設定できます。</p> <pre><code class="lua">minetest.swap_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" }) </code></pre> <h4 id="Removing Nodes &lt;a name=&quot;#removing-nodes&quot;&gt;&lt;/a&gt;"><a href="#Removing+Nodes+%26lt%3Ba+name%3D%26quot%3B%23removing-nodes%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Removing Nodes <a name="#removing-nodes"></a></a></h4> <p>ノードは常に存在する必要があります。ノードを削除するには、位置を <code>air</code> に設定します。</p> <p>次の 2 行は両方ともノードを削除し、両方とも同一です。</p> <pre><code class="lua">minetest.remove_node(pos) minetest.set_node(pos, { name = "air" }) </code></pre> <p>実際、remove_node は、名前が air の set_node を呼び出します。</p> <h3 id="Loading Blocks &lt;a name=&quot;#loading-blocks&quot;&gt;&lt;/a&gt;"><a href="#Loading+Blocks+%26lt%3Ba+name%3D%26quot%3B%23loading-blocks%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Loading Blocks <a name="#loading-blocks"></a></a></h3> <p><code>minetest.emerge_area</code> を使用してマップブロックをロードできます。 出現領域は非同期です。つまり、ブロックはすぐには読み込まれません。代わりに、それらは将来すぐにロードされ、コールバックは毎回呼び出されます。</p> <pre><code class="lua">-- Load a 20x20x20 area local halfsize = { x = 10, y = 10, z = 10 } local pos1 = vector.subtract(pos, halfsize) local pos2 = vector.add (pos, halfsize) local context = {} -- persist data between callback calls minetest.emerge_area(pos1, pos2, emerge_callback, context) </code></pre> <p>Minetest は、ブロックをロードするたびに、進行状況情報とともに <code>emerge_callback</code> を呼び出します。</p> <pre><code class="lua">local function emerge_callback(pos, action, num_calls_remaining, context) -- On first call, record number of blocks if not context.total_blocks then context.total_blocks = num_calls_remaining + 1 context.loaded_blocks = 0 end -- Increment number of blocks loaded context.loaded_blocks = context.loaded_blocks + 1 -- Send progress message if context.total_blocks == context.loaded_blocks then minetest.chat_send_all("Finished loading blocks!") end local perc = 100 * context.loaded_blocks / context.total_blocks local msg = string.format("Loading blocks %d/%d (%.2f%%)", context.loaded_blocks, context.total_blocks, perc) minetest.chat_send_all(msg) end end </code></pre> <p>これはブロックをロードする唯一の方法ではありません。LVM を使用すると、包含ブロック( the encompassed blocks )が同期的にロードされます。</p> <h3 id="Deleting Blocks &lt;a name=&quot;#deleting-blocks&quot;&gt;&lt;/a&gt;"><a href="#Deleting+Blocks+%26lt%3Ba+name%3D%26quot%3B%23deleting-blocks%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Deleting Blocks <a name="#deleting-blocks"></a></a></h3> <p>delete_blocks を使用して、マップブロックの範囲を削除できます。</p> <pre><code class="lua">-- Delete a 20x20x20 area local halfsize = { x = 10, y = 10, z = 10 } local pos1 = vector.subtract(pos, halfsize) local pos2 = vector.add (pos, halfsize) minetest.delete_area(pos1, pos2) </code></pre> <p>これにより、そのエリア内のすべてのマップブロックが<em>包括的</em>に削除されます。 これは、一部のノードがエリア境界とオーバーラップするマップブロック上にあるため、エリア外で削除されることを意味します。</p> <h3 id="Introduction"><a href="#Introduction">Introduction</a></h3> <p>特定のノードで関数を定期的に実行することは、一般的なタスクです。 Minetest は、これを行う 2 つの方法を提供します。アクティブブロック修飾子( ABM )とノードタイマーです。</p> <p>ABM は、ロードされたすべての MapBlock をスキャンして、基準に一致するノードを探します。草など、世界で頻繁に見られるノードに最適です。CPU のオーバーヘッドは高くなりますが、メモリとストレージのオーバーヘッドは低くなります。</p> <p>かまどやマシンなど、一般的でないノードやすでにメタデータを使用しているノードの場合は、代わりにノードタイマーを使用する必要があります。 ノードタイマーは、各 MapBlock で保留中のタイマーを追跡し、期限切れになったときに実行することで機能します。 つまり、タイマーは、一致するものを見つけるためにロードされたすべてのノードを検索する必要はありませんが、代わりに、保留中のタイマーを追跡するためにわずかに多くのメモリとストレージを必要とします。</p> <ul> <li><a href="#node-timers">Node Timers</a></li> <li><a href="#active-block-modifiers">Active Block Modifiers</a></li> <li><a href="#your-turn">Your Turn</a></li> </ul> <h3 id="Node Timers &lt;a name=&quot;#node-timers&quot;&gt;&lt;/a&gt;"><a href="#Node+Timers+%26lt%3Ba+name%3D%26quot%3B%23node-timers%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Node Timers <a name="#node-timers"></a></a></h3> <p>ノードタイマーは、単一のノードに直接関連付けられています。 NodeTimerRef オブジェクトを取得することで、ノードタイマーを管理できます。</p> <pre><code class="lua">local timer = minetest.get_node_timer(pos) timer:start(10.5) -- in seconds </code></pre> <p>ステータスを確認したり、タイマーを停止したりすることもできます。</p> <pre><code class="lua">if timer:is_started() then print("The timer is running, and has " .. timer:get_timeout() .. "s remaining!") print(timer:get_elapsed() .. "s has elapsed.") end timer:stop() </code></pre> <p>ノードタイマーがアップすると、ノードの定義テーブルの <code>on_timer</code> メソッドが呼び出されます。このメソッドは、ノードの位置という1つのパラメーターのみを取ります。</p> <pre><code class="lua">minetest.register_node("autodoors:door_open", { on_timer = function(pos) minetest.set_node(pos, { name = "autodoors:door" }) return false end }) </code></pre> <p><code>on_timer</code> で true を返すと、タイマーが同じ間隔で再度実行されます。</p> <p>タイマーの制限に気付いたかもしれません。最適化の理由から、ノードタイプごとに1つのタイプのタイマーのみを実行でき、ノードごとに1つのタイマーのみを実行できます。</p> <h3 id="Active Block Modifiers &lt;a name=&quot;#active-block-modifiers&quot;&gt;&lt;/a&gt;"><a href="#Active+Block+Modifiers+%26lt%3Ba+name%3D%26quot%3B%23active-block-modifiers%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Active Block Modifiers <a name="#active-block-modifiers"></a></a></h3> <p>この章では、エイリアングラスは水の近くに現れる可能性のあるタイプのグラスです。</p> <pre><code class="lua">minetest.register_node("aliens:grass", { description = "Alien Grass", light_source = 3, -- The node radiates light. Min 0, max 14 tiles = {"aliens_grass.png"}, groups = {choppy=1}, on_use = minetest.item_eat(20) }) minetest.register_abm({ nodenames = {"default:dirt_with_grass"}, neighbors = {"default:water_source", "default:water_flowing"}, interval = 10.0, -- Run every 10 seconds chance = 50, -- Select every 1 in 50 nodes action = function(pos, node, active_object_count, active_object_count_wider) local pos = {x = pos.x, y = pos.y + 1, z = pos.z} minetest.set_node(pos, {name = "aliens:grass"}) end }) </code></pre> <p>この ABM は 10 秒ごとに実行され、一致するノードごとに、 50 分の1の確率で実行されます。 ABM がノード上で実行されている場合、エイリアングラスノードがその上に配置されます。 これにより、以前にその位置にあったノードが削除されることに注意してください。 これを防ぐには、 minetest.get_node を使用して、草のためのスペースがあることを確認するチェックを含める必要があります。</p> <p>ネイバーの指定はオプションです。 複数のネイバーを指定する場合、要件を満たすために存在する必要があるのはそのうちの1つだけです。</p> <p>チャンスの指定もオプションです。 チャンスを指定しない場合、ABM は他の条件が満たされたときに常に実行されます。</p> <h3 id="Your Turn"><a href="#Your+Turn">Your Turn</a></h3> <ul> <li>midas touch: 5秒ごとに100回に1回の確率で水を金のブロックに変えます。</li> <li>Decay : 水が隣にあるとき、木を土に変えます。</li> <li>Burnin' : すべてのエアノードに火をつけます。(ヒント:「 air 」および「 fire:basic_flame 」)。警告:ゲームがクラッシュします。</li> </ul> <h2 id="9 - Storage and Metadata"><a href="#9+-+Storage+and+Metadata">9 - Storage and Metadata</a></h2> <h3 id="Introduction"><a href="#Introduction">Introduction</a></h3> <p>この章では、データを保存する方法を学習します。</p> <ul> <li><a href="#metadata">Metadata</a> <ul> <li><a href="#what-is-metadata">What is Metadata?</a></li> <li><a href="#obtaining-a-metadata-object">Obtaining a Metadata Object</a></li> <li><a href="#reading-and-writing">Reading and Writing</a></li> <li><a href="#special-keys">Special Keys</a></li> <li><a href="#storing-tables">Storing Tables</a></li> <li><a href="#private-metadata">Private Metadata</a></li> <li><a href="#lua-tables">Lua Tables</a></li> </ul></li> <li><a href="#mod-storage">Mod Storage</a></li> <li><a href="#databases">Databases</a></li> <li><a href="#deciding-which-to-use">Deciding Which to Use</a></li> <li><a href="#your-turn">Your Turn</a></li> </ul> <h3 id="Metadata &lt;a name=&quot;#metadata&quot;&gt;&lt;/a&gt;"><a href="#Metadata+%26lt%3Ba+name%3D%26quot%3B%23metadata%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Metadata <a name="#metadata"></a></a></h3> <h4 id="What is Metadata? &lt;a name=&quot;#what-is-metadata&quot;&gt;&lt;/a&gt;"><a href="#What+is+Metadata%3F+%26lt%3Ba+name%3D%26quot%3B%23what-is-metadata%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">What is Metadata? <a name="#what-is-metadata"></a></a></h4> <p>Minetest では、メタデータはカスタムデータを何かに添付するために使用される Key-Value ストアです。メタデータを使用して、ノード、プレーヤー、または ItemStack に対する情報を格納できます。</p> <p>各タイプのメタデータは、まったく同じAPIを使用します。メタデータは値を文字列として格納しますが、他のプリミティブ型を変換して格納する方法はいくつかあります。</p> <p>メタデータの一部のキーには、特別な意味がある場合があります。 たとえば、ノードメタデータの <code>infotext</code> は、十字線を使用してノードにカーソルを合わせたときに表示されるツールチップを格納するために使用されます。 他の mod との競合を避けるために、キーには標準の名前空間規則 <code>modname:keyname</code> を使用する必要があります。 例外は <code>owner</code> として保存される所有者名などの従来のデータです。</p> <p>メタデータはデータに関するデータです。ノードのタイプやスタックの数などのデータ自体はメタデータではありません。</p> <h4 id="Obtaining a Metadata Object &lt;a name=&quot;#obtaining-a-metadata-object&quot;&gt;&lt;/a&gt;"><a href="#Obtaining+a+Metadata+Object+%26lt%3Ba+name%3D%26quot%3B%23obtaining-a-metadata-object%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Obtaining a Metadata Object <a name="#obtaining-a-metadata-object"></a></a></h4> <p>ノードの位置がわかっている場合は、そのメタデータを取得できます。</p> <pre><code class="lua">local meta = minetest.get_meta({ x = 1, y = 2, z = 3 }) </code></pre> <p>Player および ItemStack メタデータは、 <code>get_meta()</code> を使用して取得されます。</p> <pre><code class="lua">local pmeta = player:get_meta() local imeta = stack:get_meta() </code></pre> <h4 id="Reading and Writing &lt;a name=&quot;#reading-and-writing&quot;&gt;&lt;/a&gt;"><a href="#Reading+and+Writing+%26lt%3Ba+name%3D%26quot%3B%23reading-and-writing%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Reading and Writing <a name="#reading-and-writing"></a></a></h4> <p>ほとんどの場合、 <code>get <type>()</code> メソッドと <code>set <type>()</code> メソッドを使用してメタの読み取りと書き込みを行います。メタデータは文字列を格納するため、文字列メソッドは直接値を設定して取得します。</p> <pre><code class="lua">print(meta:get_string("foo")) --> "" meta:set_string("foo", "bar") print(meta:get_string("foo")) --> "bar" </code></pre> <p>キーが存在しない場合、入力されたすべてのゲッターはニュートラルなデフォルト値を返します。 <code>" "</code> や <code>0</code> など。 <code>get()</code> を使用して、文字列または nil を返すことができます。</p> <p>メタデータは参照であるため、変更はすべてソースに自動的に更新されます。 ただし、 ItemStack は参照ではないため、インベントリ内の itemstack を更新する必要があります。</p> <p>型指定されていないゲッターとセッターは、文字列との間で変換されます :</p> <pre><code class="lua">print(meta:get_int("count")) --> 0 meta:set_int("count", 3) print(meta:get_int("count")) --> 3 print(meta:get_string("count")) --> "3" </code></pre> <h4 id="Special Keys &lt;a name=&quot;#special-keys&quot;&gt;&lt;/a&gt;"><a href="#Special+Keys+%26lt%3Ba+name%3D%26quot%3B%23special-keys%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Special Keys <a name="#special-keys"></a></a></h4> <p><code>infotext</code> はノードメタデータで使用され、十字線をノード上に置いたときにツールチップを表示します。 これは、ノードの所有権またはステータスを表示するときに役立ちます。</p> <p><code>description</code> は ItemStack メタデータで使用され、インベントリ内のスタックにカーソルを合わせたときに説明を上書きします。 <code>minetest.colorize()</code> でエンコードすることで色を使用できます。</p> <p><code>owner</code> は、アイテムまたはノードを所有するプレーヤーのユーザー名を保存するために使用される共通のキーです。</p> <h4 id="Storing Tables&lt;a name=&quot;#strong-tables&quot;&gt;&lt;a&gt;"><a href="#Storing+Tables%26lt%3Ba+name%3D%26quot%3B%23strong-tables%26quot%3B%26gt%3B%26lt%3Ba%26gt%3B">Storing Tables<a name="#strong-tables"><a></a></h4> <p>テーブルは、保存する前に文字列に変換する必要があります。 Minetest は、これを行うために Lua と JSON の 2 つの形式を提供します。</p> <p>Lua メソッドははるかに高速で、Lua がテーブルに使用する形式と一致する傾向がありますが、JSON はより標準的な形式であり、構造が優れており、別のプログラムと情報を交換する必要がある場合に適しています。</p> <pre><code class="lua">local data = { username = "player1", score = 1234 } meta:set_string("foo", minetest.serialize(data)) data = minetest.deserialize(minetest:get_string("foo")) </code></pre> <h4 id="Private Metadata &lt;a name=&quot;#private-metadata&quot;&gt;&lt;/a&gt;"><a href="#Private+Metadata+%26lt%3Ba+name%3D%26quot%3B%23private-metadata%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Private Metadata <a name="#private-metadata"></a></a></h4> <p>ノードメタデータのエントリはプライベートとしてマークでき、クライアントに送信されません。 プライベートとしてマークされていないエントリは、クライアントに送信されます。</p> <pre><code class="lua">meta:set_string("secret", "asd34dn") meta:mark_as_private("secret") </code></pre> <h4 id="Lua Tables &lt;a name=&quot;#lua-tables&quot;&gt;&lt;/a&gt;"><a href="#Lua+Tables+%26lt%3Ba+name%3D%26quot%3B%23lua-tables%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Lua Tables <a name="#lua-tables"></a></a></h4> <p><code>to_table</code> と <code>from_table</code> を使用して Lua テーブルとの間で変換できます :</p> <pre><code class="lua">local tmp = meta:to_table() tmp.foo = "bar" meta:from_table(tmp) </code></pre> <h3 id="Mod Storage &lt;a name=&quot;#mod-storage&quot;&gt;&lt;/a&gt;"><a href="#Mod+Storage+%26lt%3Ba+name%3D%26quot%3B%23mod-storage%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Mod Storage <a name="#mod-storage"></a></a></h3> <p>Mod ストレージは、技術的にはメタデータではありませんが、メタデータとまったく同じ API を使用します。 Mod ストレージは Mod ごとに、ロード時にのみどの Mod がそれを要求しているかを知るために取得できます。</p> <pre><code class="lua">local storage = minetest.get_mod_storage() </code></pre> <p>メタデータと同じようにストレージを操作できるようになりました :</p> <pre><code class="lua">storage:set_string("foo", "bar") </code></pre> <h3 id="Databases &lt;a name=&quot;#databases)&quot;&gt;&lt;/a&gt;"><a href="#Databases+%26lt%3Ba+name%3D%26quot%3B%23databases%29%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Databases <a name="#databases)"></a></a></h3> <p>mod がサーバーで使用される可能性が高く、大量のデータを保存する場合は、データベースの保存方法を提供することをお勧めします。 データの保存方法と使用場所を分離して、これをオプションにする必要があります。</p> <pre><code class="lua">local backend if use_database then backend = dofile(minetest.get_modpath("mymod") .. "/backend_sqlite.lua") else backend = dofile(minetest.get_modpath("mymod") .. "/backend_storage.lua") end backend.get_foo("a") backend.set_foo("a", { score = 3 }) </code></pre> <p>backend_storage.lua ファイルには、 mod ストレージの実装が含まれている必要があります :</p> <pre><code class="lua">local storage = minetest.get_mod_storage() local backend = {} function backend.set_foo(key, value) storage:set_string(key, minetest.serialize(value)) end function backend.get_foo(key, value) return minetest.deserialize(storage:get_string(key)) end return backend </code></pre> <p>backend_sqlite も同様のことを行いますが、mod ストレージの代わりに <em>Lualsqlite3</em> ライブラリを使用します。</p> <p>SQLite などのデータベースを使用するには、安全でない環境を使用する必要があります。安全でない環境とは、ユーザーが明示的にホワイトリストに登録した Mod のみが利用できるテーブルであり、悪意のある Mod が利用できる場合に悪用される可能性のある Lua API の制限の少ないコピーが含まれています。安全でない環境については、<a href="#security">セキュリティ</a> 章で詳しく説明します。</p> <pre><code class="lua">local ie = minetest.request_insecure_environment() assert(ie, "Please add mymod to secure.trusted_mods in the settings") local _sql = ie.require("lsqlite3") -- Prevent other mods from using the global sqlite3 library if sqlite3 then sqlite3 = nil end </code></pre> <p>SQL または、lsqlite3 library の使い方を説明するのはこの本の範疇を超えています。</p> <h3 id="Deciding Which to Use &lt;a name=&quot;#deciding-which-to-use&quot;&gt;&lt;/a&gt;"><a href="#Deciding+Which+to+Use+%26lt%3Ba+name%3D%26quot%3B%23deciding-which-to-use%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Deciding Which to Use <a name="#deciding-which-to-use"></a></a></h3> <p>使用するメソッドのタイプは、データの内容、フォーマット方法、およびデータの大きさによって異なります。ガイドラインとして、小さいデータは最大 10K 、中程度のデータは最大 10MB 、大きいデータはそれを超える任意のサイズです。</p> <p>ノードメタデータは、ノード関連のデータを保存する必要がある場合に適しています。中程度のデータを非公開にすると、かなり効率的に保存できます。</p> <p>アイテムのメタデータは、クライアントへの送信を回避することができないため、少量のデータ以外のものを格納するために使用しないでください。スタックが移動されるか、Lua からアクセスされるたびに、データもコピーされます。</p> <p>Mod ストレージは中程度のデータには適していますが、大きなデータの書き込みは非効率的である可能性があります。保存のたびにすべてのデータを書き出す必要がないように、大きなデータにはデータベースを使用することをお勧めします。</p> <p>安全でない環境にアクセスするために mod をホワイトリストに登録する必要があるため、データベースはサーバーでのみ実行可能です。大規模なデータセットに最適です。</p> <h3 id="Your Turn"><a href="#Your+Turn">Your Turn</a></h3> <ul> <li>5回パンチすると消えるノードを作ります。(ノード定義の <code>on_punch</code> と <code>minetest.set_node</code> を使います)</li> </ul> <h2 id="10 - Objects, Players, and Entities &lt;a name=&quot;objects&quot;&gt;&lt;/a&gt;"><a href="#10+-+Objects%2C+Players%2C+and+Entities+%26lt%3Ba+name%3D%26quot%3Bobjects%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">10 - Objects, Players, and Entities <a name="objects"></a></a></h2> <h2 id="### Introduction"><a href="#%23%23%23+Introduction">### Introduction</a></h2> <p>この章では、オブジェクトを操作する方法と、オブジェクトを定義する方法を学習します。</p> <ul> <li><a href="#what-are-objects-players-and-entities">What are Objects, Players, and Entities?</a></li> <li><a href="#position-and-velocity">Position and Velocity</a></li> <li><a href="#object-properties">Object Properties</a></li> <li><a href="#entities">Entities</a></li> <li><a href="#attachments">Attachments</a></li> <li><a href="#your-turn">Your Turn</a></li> </ul> <h3 id="What are Objects, Players, and Entities? &lt;a name=&quot;#what-are-objects-players-and-entities&quot;&gt;&lt;/a&gt;"><a href="#What+are+Objects%2C+Players%2C+and+Entities%3F+%26lt%3Ba+name%3D%26quot%3B%23what-are-objects-players-and-entities%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">What are Objects, Players, and Entities? <a name="#what-are-objects-players-and-entities"></a></a></h3> <p>プレーヤーとエンティティはどちらもオブジェクトのタイプです。オブジェクトは、ノードグリッドとは独立して移動できるものであり、速度やスケールなどのプロパティがあります。オブジェクトはアイテムではなく、独自の個別の登録システムがあります。</p> <p>プレイヤーとエンティティの間にはいくつかの違いがあります。最大のものは、プレイヤーがプレイヤーによって制御されるのに対し、エンティティは mod によって制御されることです。これは、プレイヤーの速度を mod で設定できないことを意味します。プレイヤーはクライアント側であり、エンティティはサーバー側です。もう1つの違いは、プレーヤーによってマップブロックが読み込まれるのに対し、エンティティは保存されて非アクティブになることです。</p> <p>この区別は、後で説明するように、エンティティが Lua エンティティと呼ばれるテーブルを使用して制御されるという事実によって混乱しています。</p> <h3 id="Position and Velocity &lt;a name=&quot;#position-and-velocity)&quot;&gt;&lt;/a&gt;"><a href="#Position+and+Velocity+%26lt%3Ba+name%3D%26quot%3B%23position-and-velocity%29%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Position and Velocity <a name="#position-and-velocity)"></a></a></h3> <p><code>get_pos</code>と<code>set_pos</code>は、エンティティの位置を取得および設定できるようにするために存在します。</p> <pre><code class="lua">local object = minetest.get_player_by_name("bob") local pos = object:get_pos() object:set_pos({ x = pos.x, y = pos.y + 1, z = pos.z }) </code></pre> <p><code>set_pos</code> は、アニメーションなしですぐに位置を設定します。 オブジェクトを新しい位置にスムーズにアニメーション化する場合は、 <code>move_to</code> を使用する必要があります。 残念ながら、これはエンティティに対してのみ機能します。</p> <pre><code class="lua">object:move_to({ x = pos.x, y = pos.y + 1, z = pos.z }) </code></pre> <p>エンティティを処理するときに考慮すべき重要なことは、ネットワークの遅延です。 理想的な世界では、エンティティの動きに関するメッセージは、正しい順序で、送信方法と同様の間隔ですぐに届きます。 ただし、シングルプレイヤーでない限り、これは理想的な世界ではありません。 メッセージが届くまでしばらく時間がかかります。 位置メッセージが順不同で到着する可能性があり、現在の既知の位置より古い位置に移動するポイントがないため、一部の <code>set_pos</code> 呼び出しがスキップされます。 動きの間隔が同じでない場合があり、アニメーションに使用するのが困難になります。<br /> これらすべての結果、クライアントはサーバーに対してさまざまなことを認識します。これは、注意する必要があることです。</p> <h3 id="Object Properties &lt;a name=&quot;#object-properties&quot;&gt;&lt;/a&gt;"><a href="#Object+Properties+%26lt%3Ba+name%3D%26quot%3B%23object-properties%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Object Properties <a name="#object-properties"></a></a></h3> <p>オブジェクトプロパティは、オブジェクトをレンダリングして処理する方法をクライアントに指示するために使用されます。 定義上、プロパティはエンジンが使用するためのものであるため、カスタムプロパティを定義することはできません。</p> <p>ノードとは異なり、オブジェクトは設定された外観ではなく動的な外観を持っています。プロパティを更新することで、オブジェクトの外観をいつでも変更できます。</p> <pre><code class="lua">object:set_properties({ visual = "mesh", mesh = "character.b3d", textures = {"character_texture.png"}, visual_size = {x=1, y=1}, }) </code></pre> <p>更新されたプロパティは、範囲内のすべてのプレイヤーに送信されます。これは、プレイヤーごとにスキンが異なるなど、大量のバリエーションを非常に安価に入手するのに非常に便利です。</p> <p>次のセクションに示すように、エンティティはその定義で提供される初期プロパティを持つことができます。ただし、デフォルトのプレーヤープロパティはエンジンで定義されているため、新しく参加したプレーヤーのプロパティを設定するには、 <code>on_joinplayer</code> で <code>set_properties()</code> を使用する必要があります。</p> <h3 id="Entities &lt;a name=&quot;#entities&quot;&gt;&lt;/a&gt;"><a href="#Entities+%26lt%3Ba+name%3D%26quot%3B%23entities%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Entities <a name="#entities"></a></a></h3> <p>エンティティには、アイテム定義テーブルに似た定義テーブルがあります。このテーブルには、コールバックメソッド、初期オブジェクトプロパティ、およびカスタムメンバーを含めることができます。</p> <p>ただし、エンティティは1つの非常に重要な点でアイテムとは異なります。エンティティが出現すると(つまり、ロードまたは作成されると)、メタテーブルを使用して定義テーブルから<em>継承</em>するそのエンティティの新しいテーブルが作成されます。</p> <p>この新しいテーブルは、一般に Lua エンティティテーブルと呼ばれます。</p> <p>メタテーブルは、Lua 言語の重要な部分であるため、知っておく必要のある重要な Lua 機能です。</p> <p>素人の言葉で言えば、メタテーブルを使用すると、特定の Lua 構文を使用するときにテーブルがどのように動作するかを制御できます。メタテーブルの最も一般的な使用法は、別のテーブルをプロトタイプとして使用する機能です。現在のテーブルに存在しない場合は、デフォルトで他のテーブルのプロパティとメソッドが使用されます。</p> <p>テーブル A のメンバー X にアクセスするとします。テーブル A にそのメンバーがある場合、通常どおりに返されます。ただし、テーブルにそのメンバーがなくても、メタテーブルの可能性があるテーブル B がある場合は、テーブル B がそのメンバーを持っているかどうかを確認します。</p> <pre><code class="lua">local MyEntity = { initial_properties = { hp_max = 1, physical = true, collide_with_objects = false, collisionbox = {-0.3, -0.3, -0.3, 0.3, 0.3, 0.3}, visual = "wielditem", visual_size = {x = 0.4, y = 0.4}, textures = {""}, spritediv = {x = 1, y = 1}, initial_sprite_basepos = {x = 0, y = 0}, }, message = "Default message", } function MyEntity:set_message(msg) self.message = msg end </code></pre> <p>エンティティが出現すると、そのタイプテーブルからすべてをコピーすることにより、エンティティ用のテーブルが作成されます。このテーブルは、その特定のエンティティの変数を格納するために使用できます。</p> <pre><code class="lua">local entity = object:get_luaentity() local object = entity.object print("entity is at " .. minetest.pos_to_string(object:get_pos())) </code></pre> <p>エンティティで使用できるコールバックは多数あります。完全なリストは<a target="_blank" rel="nofollow noopener" href="https://rubenwardy.com/minetest_modding_book/lua_api.html#registered-entities">lua_api.txt</a>にあります。</p> <pre><code class="lua">function MyEntity:on_step(dtime) local pos = self.object:get_pos() local pos_down = vector.subtract(pos, vector.new(0, 1, 0)) local delta if minetest.get_node(pos_down).name == "air" then delta = vector.new(0, -1, 0) elseif minetest.get_node(pos).name == "air" then delta = vector.new(0, 0, 1) else delta = vector.new(0, 1, 0) end delta = vector.multiply(delta, dtime) self.object:move_to(vector.add(pos, delta)) end function MyEntity:on_punch(hitter) minetest.chat_send_player(hitter:get_player_name(), self.message) end </code></pre> <p>ここで、このエンティティをスポーンして使用すると、エンティティが非アクティブになってから再びアクティブになると、メッセージが忘れられることに気付くでしょう。これは、メッセージが保存されていないためです。 Minetest では、エンティティテーブルにすべてを保存するのではなく、保存方法を制御できます。 Staticdata は、保存する必要のあるすべてのカスタム情報を含む文字列です。</p> <pre><code class="lua">function MyEntity:get_staticdata() return minetest.write_json({ message = self.message, }) end function MyEntity:on_activate(staticdata, dtime_s) if staticdata ~= "" and staticdata ~= nil then local data = minetest.parse_json(staticdata) or {} self:set_message(data.message) end end </code></pre> <p>Minetest は、いつでも何度でも <code>get_staticdata()</code> を呼び出すことができます。これは、 Minetest が MapBlock が非アクティブになるのを待たずに保存するためです。これにより、データが失われる可能性があります。 MapBlock は約18秒ごとに保存されるため、 <code>get_staticdata()</code> が呼び出される間隔も同様であることに注意してください。<br /> 一方、 <code>on_activate()</code> は、 MapBlock がアクティブになるか、エンティティの生成によってエンティティがアクティブになったときにのみ呼び出されます。これは、 staticdata が空である可能性があることを意味します。</p> <p>最後に、適切な名前の <code>register_entity</code> を使用して型テーブルを登録する必要があります。</p> <pre><code class="lua">minetest.register_entity("mymod:entity", MyEntity) </code></pre> <p>エンティティは、次のような mod によって生成できます。</p> <pre><code class="lua">local pos = { x = 1, y = 2, z = 3 } local obj = minetest.add_entity(pos, "mymod:entity", nil) </code></pre> <p>3 番目のパラメーターは初期静的データです。メッセージを設定するには、エンティティテーブルメソッドを使用できます。</p> <pre><code class="lua">obj:get_luaentity():set_message("hello!") </code></pre> <p><em>give</em> <a href="#privileges">privilege</a>を持つプレイヤーは、<a href="#chat.html"> chat コマンド</a>を使用してエンティティを生成できます。</p> <pre><code class="lua">/spawnentity mymod:entity </code></pre> <h3 id="Attachments &lt;a name=&quot;#attachments&quot;&gt;&lt;/a&gt;"><a href="#Attachments+%26lt%3Ba+name%3D%26quot%3B%23attachments%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Attachments <a name="#attachments"></a></a></h3> <p>アタッチされたオブジェクトは、親(アタッチされているオブジェクト)が移動すると移動します。アタッチされたオブジェクトは、親の子であると言われます。オブジェクトには無制限の数の子を含めることができますが、親は多くても 1 つです。</p> <pre><code class="lua">child:set_attach(parent, bone, position, rotation) </code></pre> <p>オブジェクトの <code>get_pos()</code> は、オブジェクトがアタッチされているかどうかに関係なく、常にオブジェクトのグローバル位置を返します。 <code>set_attach</code> は相対的な位置を取りますが、期待どおりではありません。アタッチメントの位置は、 10 倍に拡大された親の原点を基準にしています。<br /> したがって、 <code>0,5,0</code> は、親の原点の半分上のノードになります。</p> <div class="table-responsive"><table> <thead> <tr> <th>⚠ 度とラジアン</th> </tr> </thead> <tbody> <tr> <td>アタッチメントの回転は度で設定されますが、オブジェクトの回転はラジアンです。必ず正しい角度測定に変換してください。</td> </tr> </tbody> </table></div> <p>アニメーションのある 3D モデルの場合、ボーン引数を使用してエンティティをボーンにアタッチします。 3D アニメーションは、スケルトンに基づいています。モデル内のボーンのネットワークで、各ボーンに位置と回転を指定して、モデルを変更したり、腕を動かしたりできます。ボーンにアタッチすることは、キャラクターに何かを持たせたい場合に便利です。</p> <pre><code class="lua">obj:set_attach(player, "Arm_Right", -- default bone {x=0.2, y=6.5, z=3}, -- default position {x=-100, y=225, z=90}) -- default rotation </code></pre> <h3 id="Your Turn"><a href="#Your+Turn">Your Turn</a></h3> <ul> <li>ノードとエンティティを組み合わせて風車を作成します。</li> <li>選択したmobを作成します(エンティティ API のみを使用し、他の Mod は使用しません)。</li> </ul> <h2 id="11 - Privileges &lt;a name=&quot;privileges&quot;&gt;&lt;/a&gt;"><a href="#11+-+Privileges+%26lt%3Ba+name%3D%26quot%3Bprivileges%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">11 - Privileges <a name="privileges"></a></a></h2> <h3 id="Introduction"><a href="#Introduction">Introduction</a></h3> <p>特権は、しばしば略して priv と呼ばれ、プレーヤーに特定のアクションを実行する能力を与えます。サーバーの所有者は、各プレーヤーが持つ能力を制御するための特権を付与および取り消すことができます。</p> <ul> <li><a href="#when-to-use-privileges">When to use Privileges</a></li> <li><a href="#declaring-privileges">Declaring Privileges</a></li> <li><a href="#checking-for-privileges">Checking for Privileges</a></li> <li><a href="#getting-and-setting-privileges">Getting and Setting Privileges</a></li> <li><a href="#adding-privileges-to-basicprivs">Adding Privileges to basic_privs</a></li> </ul> <h3 id="When to use Privileges &lt;a name=&quot;#when-to-use-privileges&quot;&gt;&lt;/a&gt;"><a href="#When+to+use+Privileges+%26lt%3Ba+name%3D%26quot%3B%23when-to-use-privileges%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">When to use Privileges <a name="#when-to-use-privileges"></a></a></h3> <p>特権はプレイヤーに何かをする能力を与えるべきです。<strong>クラスまたはステータスを示すための特権ではありません</strong>。</p> <p><strong>Good Privileges:</strong></p> <ul> <li>interact</li> <li>shout</li> <li>noclip</li> <li>fly</li> <li>kick</li> <li>ban</li> <li>vote</li> <li>worldedit</li> <li>area_admin - admin functions of one mod is ok</li> </ul> <p><strong>Bad Privileges:</strong></p> <ul> <li>moderator</li> <li>admin</li> <li>elf</li> <li>dwarf</li> </ul> <h3 id="Declaring Privileges &lt;a name=&quot;#declaring-privileges&quot;&gt;&lt;/a&gt;"><a href="#Declaring+Privileges+%26lt%3Ba+name%3D%26quot%3B%23declaring-privileges%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Declaring Privileges <a name="#declaring-privileges"></a></a></h3> <p><code>register_privilege</code> を使用して、新しい特権を宣言します。</p> <pre><code class="lua">minetest.register_privilege("vote", { description = "Can vote on issues", give_to_singleplayer = true }) </code></pre> <p><code>give_to_singleplayer</code> は、指定されていない場合、デフォルトで true に設定されるため、上記の定義では実際には必要ありません。</p> <h3 id="Checking for Privileges &lt;a name=&quot;#checking-for-privileges&quot;&gt;&lt;/a&gt;"><a href="#Checking+for+Privileges+%26lt%3Ba+name%3D%26quot%3B%23checking-for-privileges%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Checking for Privileges <a name="#checking-for-privileges"></a></a></h3> <p>プレーヤーが必要なすべての特権を持っているかどうかをすばやく確認するには:</p> <pre><code class="lua">local has, missing = minetest.check_player_privs(player_or_name, { interact = true, vote = true }) </code></pre> <p>この例では、プレーヤーが必要なすべての特権を持っている場合、 <code>has</code> は true です。 <code>has</code> が false の場合、 <code>missing</code> には欠落している特権の Key-Value テーブルが含まれます。</p> <pre><code class="lua">local has, missing = minetest.check_player_privs(name, { interact = true, vote = true }) if has then print("Player has all privs!") else print("Player is missing privs: " .. dump(missing)) end </code></pre> <p>不足している権限を確認する必要がない場合は、 <code>check_player_privs</code> を直接 if ステートメントに入れることができます。</p> <pre><code class="lua">if not minetest.check_player_privs(name, { interact=true }) then return false, "You need interact for this!" end </code></pre> <h3 id="Getting and Setting Privileges &lt;a name=&quot;#getting-and-setting-privileges&quot;&gt;&lt;/a&gt;"><a href="#Getting+and+Setting+Privileges+%26lt%3Ba+name%3D%26quot%3B%23getting-and-setting-privileges%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Getting and Setting Privileges <a name="#getting-and-setting-privileges"></a></a></h3> <p>プレーヤーがオンラインであるかどうかに関係なく、プレーヤーの特権にアクセスまたは変更できます。</p> <pre><code class="lua">local privs = minetest.get_player_privs(name) print(dump(privs)) privs.vote = true minetest.set_player_privs(name, privs) </code></pre> <p>特権は常に Key-Value テーブルとして指定され、キーは特権名、値はブール値です。</p> <pre><code class="lua">{ fly = true, interact = true, shout = true } </code></pre> <h3 id="Adding Privileges to basic_privs &lt;a name=&quot;#adding-privileges-to-basicprivs&quot;&gt;&lt;/a&gt;"><a href="#Adding+Privileges+to+basic_privs+%26lt%3Ba+name%3D%26quot%3B%23adding-privileges-to-basicprivs%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Adding Privileges to basic_privs <a name="#adding-privileges-to-basicprivs"></a></a></h3> <p><code>basic_privs</code> 特権を持つプレーヤーは、限られた特権のセットを付与および取り消すことができます。モデレーターにこの権限を付与して、「インタラクト」や「シャウト」を付与および取り消すことができるのが一般的ですが、「ギブ」や「サーバー」など、悪用される可能性の高い自分や他のプレーヤーに権限を付与することはできません。</p> <p><code>basic_privs</code> に特権を追加し、モデレーターが他のプレーヤーに付与および取り消すことができる特権を調整するには、 <code>basic_privs</code> 設定を変更する必要があります。</p> <p>デフォルトでは、 <code>basic_privs</code> の値は次のとおりです。</p> <pre><code class="lua">basic_privs = interact, shout </code></pre> <p><code>vote</code> を追加するには、これを次のように更新します。</p> <pre><code class="lua">basic_privs = interact, shout, vote </code></pre> <p>これにより、 <code>basic_privs</code> を持つプレイヤーは、<code>vote</code> 特権を付与および取り消すことができます。</p> <h2 id="12 - Chat and Commands&lt;a name=&quot;chat&quot;&gt;&lt;/a&gt;"><a href="#12+-+Chat+and+Commands%26lt%3Ba+name%3D%26quot%3Bchat%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">12 - Chat and Commands<a name="chat"></a></a></h2> <h3 id="Introduction"><a href="#Introduction">Introduction</a></h3> <p>Mod は、メッセージの送信、メッセージの傍受、チャットコマンドの登録など、プレーヤーのチャットと対話できます。</p> <ul> <li><a href="#sending-messages-to-all-players">Sending Messages to All Players</a></li> <li><a href="#sending-messages-to-specific-players">Sending Messages to Specific Players</a></li> <li><a href="#chat-commands">Chat Commands</a></li> <li><a href="#complex-subcommands">Complex Subcommands</a></li> <li><a href="#intercepting-messages">Intercepting Messages</a></li> </ul> <h3 id="Sending Messages to All Players &lt;a name=&quot;#sending-messages-to-all-players&quot;&gt;&lt;/a&gt;"><a href="#Sending+Messages+to+All+Players+%26lt%3Ba+name%3D%26quot%3B%23sending-messages-to-all-players%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Sending Messages to All Players <a name="#sending-messages-to-all-players"></a></a></h3> <p>ゲーム内のすべてのプレーヤーにメッセージを送信するには、 chat_send_all 関数を呼び出します。</p> <pre><code>minetest.chat_send_all("This is a chat message to all players") </code></pre> <p>これがゲーム内でどのように表示されるかの例を次に示します。</p> <pre><code><player1> Look at this entrance This is a chat message to all players <player2> What about it? </code></pre> <p>メッセージは、ゲーム内のプレーヤーのチャットと区別するために別の行に表示されます。</p> <h3 id="Sending Messages to Specific Players &lt;a name=&quot;#sending-messages-to-specific-players&quot;&gt;&lt;/a&gt;"><a href="#Sending+Messages+to+Specific+Players+%26lt%3Ba+name%3D%26quot%3B%23sending-messages-to-specific-players%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Sending Messages to Specific Players <a name="#sending-messages-to-specific-players"></a></a></h3> <p>特定のプレーヤーにメッセージを送信するには、chat_send_player 関数を呼び出します。</p> <pre><code>minetest.chat_send_player("player1", "This is a chat message for player1") </code></pre> <p>このメッセージは、すべてのプレーヤーへのメッセージと同じ方法で表示されますが、指定されたプレーヤー(この場合は player1 )にのみ表示されます。</p> <h3 id="Chat Commands &lt;a name=&quot;#chat-commands&quot;&gt;&lt;/a&gt;"><a href="#Chat+Commands+%26lt%3Ba+name%3D%26quot%3B%23chat-commands%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Chat Commands <a name="#chat-commands"></a></a></h3> <p><code>/foo</code> などのチャットコマンドを登録するには、<code>register_chatcommand</code> を使用します。</p> <pre><code class="lua">minetest.register_chatcommand("foo", { privs = { interact = true, }, func = function(name, param) return true, "You said " .. param .. "!" end, }) </code></pre> <p>上記のスニペットでは、 <code>interact</code> は必須の<a href="#privileges">privilege</a>としてリストされています。つまり、 <code>interact</code> 特権を持つプレーヤーだけがコマンドを実行できます。</p> <p>チャットコマンドは最大 2 つの値を返すことができます。 1 つは成功を示すブール値で、もう 1 つはユーザーに送信するメッセージです。</p> <div class="table-responsive"><table> <thead> <tr> <th>⚠ オフラインプレイヤーはコマンドを実行できます</th> </tr> </thead> <tbody> <tr> <td>Mod はオフラインプレーヤーに代わってコマンドを実行できるため、プレーヤーオブジェクトの代わりにプレーヤー名が渡されます。たとえば、IRCブリッジを使用すると、プレーヤーはゲームに参加せずにコマンドを実行できます。</td> </tr> <tr> <td>したがって、プレーヤーがオンラインであると想定しないようにしてください。確認するにはこれをチェックします</td> </tr> <tr> <td><code>minetest.get_player_by_name</code></td> </tr> <tr> <td>プレーヤーを返します。</td> </tr> </tbody> </table></div> <h3 id="Complex Subcommands &lt;a name=&quot;#complex-subcommands&quot;&gt;&lt;/a&gt;"><a href="#Complex+Subcommands+%26lt%3Ba+name%3D%26quot%3B%23complex-subcommands%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Complex Subcommands <a name="#complex-subcommands"></a></a></h3> <p>多くの場合、次のような複雑なチャットコマンドを作成する必要があります。</p> <ul> <li><code>/msg <to> <message></code></li> <li><code>/team join <teamname></code></li> <li><code>/team leave <teamname></code></li> <li><code>/team list</code></li> </ul> <p>これは通常、<a target="_blank" rel="nofollow noopener" href="https://www.lua.org/pil/20.2.html"> Lua パターン</a>を使用して行われます。パターンは、ルールを使用してテキストからコンテンツを抽出する方法です。</p> <pre><code class="lua">local to, msg = string.match(param, "^([%a%d_-]+) (*+)$") </code></pre> <p>上記のコードは <code>/msg <to> <message></code> を実装しています。左から右に見ていきましょう。</p> <ul> <li><code>^</code> は、文字列の先頭に一致することを意味します。</li> <li><code>()</code> は一致するグループです-ここにあるものと一致するものはすべて string.match から返されます。</li> <li><code>[]</code> は、このリスト内の文字を受け入れることを意味します。</li> <li><code>%a</code> は任意の文字を受け入れることを意味し、 <code>%d</code> は任意の数字を受け入れることを意味します。</li> <li><code>[%a%d_-]</code> は、任意の文字または数字、あるいは <code>_</code> または <code>-</code> を受け入れることを意味します。</li> <li><code>+</code> は、 1 回以上前のものと一致することを意味します。</li> <li><code>*</code> は、このコンテキストの任意の文字に一致することを意味します。</li> <li><code>$</code> は、文字列の末尾に一致することを意味します。</li> </ul> <p>簡単に言えば、パターンは名前(文字/数字/-/のみの単語)、スペース、メッセージ( 1 つ以上の任意の文字)に一致します。名前とメッセージは括弧で囲まれているため、返されます。</p> <p>これが、ほとんどの Mod が複雑なチャットコマンドを実装する方法です。 Lua パターンのより良いガイドは、おそらく<a target="_blank" rel="nofollow noopener" href="http://lua-users.org/wiki/PatternsTutorial">lua-users.orgチュートリアル</a>または<a target="_blank" rel="nofollow noopener" href="https://www.lua.org/pil/20.2.html">PILドキュメント</a>でしょう。</p> <p><a href="#chat_complex">Chat Command Builder</a>と呼ばれるパターンなしで複雑なチャットコマンドを作成するために使用できる、この本の著者によって書かれたライブラリもあります。</p> <h3 id="Intercepting Messages &lt;a name=&quot;#intercepting-messages&quot;&gt;&lt;/a&gt;"><a href="#Intercepting+Messages+%26lt%3Ba+name%3D%26quot%3B%23intercepting-messages%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Intercepting Messages <a name="#intercepting-messages"></a></a></h3> <p>メッセージを傍受するには、 register_on_chat_message を使用します。</p> <pre><code class="lua">minetest.register_on_chat_message(function(name, message) print(name .. " said " .. message) return false end) </code></pre> <p>false を返すことにより、チャットメッセージをデフォルトのハンドラーで送信できるようになります。 <code>nil</code> は暗黙的に返され、 false のように扱われるため、実際には <code>return false</code> の行を削除しても、同じように機能します。</p> <div class="table-responsive"><table> <thead> <tr> <th>⚠ 特権とチャットコマンド</th> </tr> </thead> <tbody> <tr> <td>プレーヤーがこのコールバックをトリガーするために、シャウト特権は必要ありません。これは、チャットコマンドが Lua に実装されており、 / で始まるチャットメッセージであるためです。</td> </tr> </tbody> </table></div> <p>チャットコマンドである可能性があること、またはユーザーが「シャウト」を持っていない可能性があることを考慮に入れる必要があります。</p> <pre><code class="lua">minetest.register_on_chat_message(function(name, message) if message:sub(1, 1) == "/" then print(name .. " ran chat command") elseif minetest.check_player_privs(name, { shout = true }) then print(name .. " said " .. message) else print(name .. " tried to say " .. message .. " but doesn't have shout") end return false end) </code></pre> <h2 id="13 - Chat Command Builder&lt;a name=&quot;chat_complex&quot;&gt;&lt;/a&gt;"><a href="#13+-+Chat+Command+Builder%26lt%3Ba+name%3D%26quot%3Bchat_complex%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">13 - Chat Command Builder<a name="chat_complex"></a></a></h2> <h3 id="Introduction"><a href="#Introduction">Introduction</a></h3> <p>この章では、 ChatCmdBuilder を使用して、 <code>/msg <name> <message></code>、 <code>/team join <teamname></code> <code>/team Leave <teamname></code> などの複雑なチャットコマンドを作成する方法を説明します。</p> <p>ChatCmdBuilder はこの本の著者によって作成されたライブラリであり、ほとんどの modder は<a href="#complex-subcommands">チャットとコマンド</a>で概説されている方法を使用する傾向があることに注意してください。</p> <ul> <li><a href="#why-chatcmdbuilder">Why ChatCmdBuilder?</a></li> <li><a href="#routes">Routes</a></li> <li><a href="#subcommand-functions">Subcommand functions</a></li> <li><a href="#installing-chatcmdbuilder">Installing ChatCmdBuilder</a></li> <li><a href="#admin-complex-command">Admin complex command</a></li> </ul> <h3 id="Why ChatCmdBuilder? &lt;a name=&quot;#why-chatcmdbuilder&quot;&gt;&lt;/a&gt;"><a href="#Why+ChatCmdBuilder%3F+%26lt%3Ba+name%3D%26quot%3B%23why-chatcmdbuilder%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Why ChatCmdBuilder? <a name="#why-chatcmdbuilder"></a></a></h3> <p>従来、mod は Lua パターンを使用してこれらの複雑なコマンドを実装していました。</p> <pre><code class="lua">local name = string.match(param, "^join ([%a%d_-]+)") </code></pre> <p>しかし、私は Lua パターンを書くのが面倒で読めないことに気づきました。このため、私はあなたのためにこれを行うためのライブラリを作成しました。</p> <pre><code class="lua">ChatCmdBuilder.new("sethp", function(cmd) cmd:sub(":target :hp:int", function(name, target, hp) local player = minetest.get_player_by_name(target) if player then player:set_hp(hp) return true, "Killed " .. target else return false, "Unable to find " .. target end end) end, { description = "Set hp of player", privs = { kick = true -- ^ probably better to register a custom priv } }) </code></pre> <p><code>ChatCmdBuilder.new(name、setup_func、def)</code> は、 <code>name</code> という新しいチャットコマンドを作成します。次に、渡された関数( <code>setup_func</code>)を呼び出し、サブコマンドを作成します。各 <code>cmd:sub(route、func)</code> はサブコマンドです。</p> <p>サブコマンドは、入力パラメーターに対する特定の応答です。プレーヤーがチャットコマンドを実行すると、入力に一致する最初のサブコマンドが実行され、他のサブコマンドは実行されません。一致するサブコマンドがない場合、ユーザーには無効な構文が通知されます。たとえば、上記のコードスニペットでは、プレーヤーが <code>/sethp username 12</code> の形式で何かを入力すると、 cmd:sub に渡された関数が呼び出されます。 <code>/sethp 12 bleh</code> と入力すると、間違った入力メッセージが表示されます。</p> <p><code>:name :hp:int</code> はルートです。 /teleport に渡されるパラメータのフォーマットを記述します。</p> <h3 id="Routes &lt;a name=&quot;#routes&quot;&gt;&lt;/a&gt;"><a href="#Routes+%26lt%3Ba+name%3D%26quot%3B%23routes%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Routes <a name="#routes"></a></a></h3> <p>ルートは、ターミナルと変数で構成されています。ターミナルは常にそこになければなりません。たとえば、 <code>/team join :username :teamname</code>の<code>join</code> です。スペースも端子としてカウントされます。</p> <p>変数は、ユーザーの入力内容に応じて値を変更できます。たとえば、 <code>:username</code> と <code>:teamname</code> です。</p> <p>変数は <code>:name:type</code> として定義されます。 <code>name</code> はヘルプドキュメントで使用されています。 <code>type</code> は入力を照合するために使用されます。タイプが指定されていない場合、タイプは <code>word</code> です。</p> <p>有効なタイプは次のとおりです。</p> <ul> <li><code>word</code> - default. Any string without spaces.</li> <li><code>int</code> - Any integer/whole number, no decimals.</li> <li><code>number</code> - Any number, including ints and decimals.</li> <li><code>pos</code> - 1,2,3 or 1.1,2,3.4567 or (1,2,3) or 1.2, 2 ,3.2</li> <li><code>text</code> - Any string. There can only ever be one text variable, no variables or terminals can come afterwards.</li> </ul> <p><code>:name :hp:int</code> には、次の 2 つの変数があります。</p> <ul> <li><code>name</code> - タイプが指定されていないため、 <code>word</code> のタイプ。スペースを含まない任意の文字列を受け入れます。</li> <li><code>hp</code> - <code>int</code> のタイプ</li> </ul> <h3 id="Subcommand functions &lt;a name=&quot;#subcommand-functions&quot;&gt;&lt;/a &gt;"><a href="#Subcommand+functions+%26lt%3Ba+name%3D%26quot%3B%23subcommand-functions%26quot%3B%26gt%3B%26lt%3B%2Fa+%26gt%3B">Subcommand functions <a name="#subcommand-functions"></a ></a></h3> <p>最初の引数は発信者の名前です。次に、変数が順番に関数に渡されます。</p> <pre><code class="lua">cmd:sub(":target :hp:int", function(name, target, hp) -- subcommand function end) </code></pre> <h3 id="Installing ChatCmdBuilder &lt;a name=&quot;#installing-chatcmdbuilder&quot;&gt;&lt;/a&gt;"><a href="#Installing+ChatCmdBuilder+%26lt%3Ba+name%3D%26quot%3B%23installing-chatcmdbuilder%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Installing ChatCmdBuilder <a name="#installing-chatcmdbuilder"></a></a></h3> <p>ソースコードは<a target="_blank" rel="nofollow noopener" href="https://github.com/rubenwardy/ChatCmdBuilder/">Github</a>で探してダウンロードできます。</p> <p>インストールには 2 つの方法があります。</p> <ol> <li>ChatCmdBuilder を mod としてインストールし、それに依存します。</li> <li>mod のchatcmdbuilder.lua として ChatCmdBuilder に init.lua ファイルを含め、それを dofile します。</li> </ol> <h3 id="Admin complex command &lt;a name=&quot;#admin-complex-command&quot;&gt;&lt;/a&gt;"><a href="#Admin+complex+command+%26lt%3Ba+name%3D%26quot%3B%23admin-complex-command%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Admin complex command <a name="#admin-complex-command"></a></a></h3> <p>これを可能にするチャットコマンドを作成する例を次に示します。</p> <ul> <li><code>/admin kill <username>></code> - ユーザーを強制終了します</li> <li><code>/admin move <username> to <pos></code> - テレポートユーザー</li> <li><code>/admin log <username></code> - レポートログを表示</li> <li><code>/admin log <username> <message></code> - レポートログへのログ</li> </ul> <pre><code class="lua">local admin_log local function load() admin_log = {} end local function save() -- todo end load() ChatCmdBuilder.new("admin", function(cmd) cmd:sub("kill :name", function(name, target) local player = minetest.get_player_by_name(target) if player then player:set_hp(0) return true, "Killed " .. target else return false, "Unable to find " .. target end end) cmd:sub("move :name to :pos:pos", function(name, target, pos) local player = minetest.get_player_by_name(target) if player then player:setpos(pos) return true, "Moved " .. target .. " to " .. minetest.pos_to_string(pos) else return false, "Unable to find " .. target end end) cmd:sub("log :username", function(name, target) local log = admin_log[target] if log then return true, table.concat(log, "\n") else return false, "No entries for " .. target end end) cmd:sub("log :username :message", function(name, target, message) local log = admin_log[target] or {} table.insert(log, message) admin_log[target] = log save() return true, "Logged" end) end, { description = "Admin tools", privs = { kick = true, ban = true } }) </code></pre> <h2 id="14 - Player Physics &lt;a name=&quot;player_physics&quot;&gt;&lt;/a&gt;"><a href="#14+-+Player+Physics+%26lt%3Ba+name%3D%26quot%3Bplayer_physics%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">14 - Player Physics <a name="player_physics"></a></a></h2> <h3 id="Introduction"><a href="#Introduction">Introduction</a></h3> <p>プレーヤーの物理は、物理オーバーライドを使用して変更できます。物理オーバーライドは、歩行速度、ジャンプ速度、および重力定数を設定できます。物理オーバーライドは、プレーヤーごとに設定され、乗数です。たとえば、重力の値が 2 の場合、重力は 2 倍強くなります。</p> <ul> <li><a href="#basic-example">Basic Example</a></li> <li><a href="#available-overrides">Available Overrides</a> <ul> <li><a href="#old-movement-behaviour">Old Movement Behaviour</a></li> </ul></li> <li><a href="#mod-incompatibility">Mod Incompatibility</a></li> <li><a href="#your-turn">Your Turn</a></li> </ul> <h3 id="Basic Example &lt;a name=&quot;#basic-example&quot;&gt;&lt;/a&gt;"><a href="#Basic+Example+%26lt%3Ba+name%3D%26quot%3B%23basic-example%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Basic Example <a name="#basic-example"></a></a></h3> <p>呼び出し元を low G にする反重力コマンドを追加する方法の例を次に示します。</p> <pre><code class="lua">minetest.register_chatcommand("antigravity", { func = function(name, param) local player = minetest.get_player_by_name(name) player:set_physics_override({ gravity = 0.1, -- set gravity to 10% of its original value -- (0.1 * 9.81) }) end, }) </code></pre> <h3 id="Available Overrides &lt;a name=&quot;#available-overrides&quot;&gt;&lt;/a&gt;"><a href="#Available+Overrides+%26lt%3Ba+name%3D%26quot%3B%23available-overrides%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Available Overrides <a name="#available-overrides"></a></a></h3> <p><code>player:set_physics_override()</code> にはオーバーライドのテーブルが与えられます。<br /> <a href="#player-only-no-op-for-other-objects">lua_api.txt</a>によると、次のようになります。</p> <ul> <li>speed 速度: デフォルトの歩行速度値への乗数(デフォルト:1)</li> <li>jump ジャンプ: デフォルトのジャンプ値への乗数(デフォルト:1)</li> <li>gravity 重力: デフォルトの重力値への乗数(デフォルト:1)</li> <li>sneak: プレーヤーがこっそりできるかどうか(デフォルト:true)</li> </ul> <h4 id="Old Movement Behaviour &lt;a name=&quot;#old-movement-behaviour&quot;&gt;&lt;/a&gt;"><a href="#Old+Movement+Behaviour++%26lt%3Ba+name%3D%26quot%3B%23old-movement-behaviour%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Old Movement Behaviour <a name="#old-movement-behaviour"></a></a></h4> <p>0.4.16 リリースより前のプレイヤーの動きには、スニークグリッチが含まれていました。これにより、特定のノードの配置から作成された「エレベーター」をスニーク(シフトを押す)やスペースを押して上昇するなど、さまざまな動きのグリッチが可能になります。この動作は意図したものではありませんが、多くのサーバーで使用されているため、オーバーライドで保持されています。</p> <p>古い動きの動作を完全に復元するには、 2 つのオーバーライドが必要です。</p> <ul> <li>new_move: プレーヤーが新しい動きを使用するかどうか(デフォルト:true)</li> <li>sneak_glitch: プレーヤーが「スニークエレベーター」を使用できるかどうか(デフォルト:false)</li> </ul> <h3 id="Mod Incompatibility &lt;a name=&quot;#mod-incompatibility&quot;&gt;&lt;/a&gt;"><a href="#Mod+Incompatibility+%26lt%3Ba+name%3D%26quot%3B%23mod-incompatibility%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Mod Incompatibility <a name="#mod-incompatibility"></a></a></h3> <p>プレイヤーの同じ物理値をオーバーライドする mod は、互いに互換性がない傾向があることに注意してください。オーバーライドを設定すると、以前に設定されたオーバーライドが上書きされます。これは、複数のオーバーライドがプレーヤーの速度を設定した場合、最後に実行されたものだけが有効になることを意味します。</p> <h3 id="Your Turn"><a href="#Your+Turn">Your Turn</a></h3> <ul> <li><strong>Sonic</strong> <strong>ソニック</strong>: プレーヤーがゲームに参加するときに、速度乗数を高い値(少なくとも6)に設定します。</li> <li><strong>Super bounce</strong> <strong>スーパーバウンス</strong>: プレーヤーが 20 メートルジャンプできるようにジャンプ値を増やします( 1 メートルは 1 ノードです)。</li> <li><strong>Space</strong> <strong>スペース</strong>: プレイヤーが高くなるにつれて重力を減少させます。</li> </ul> <h2 id="15 - GUIs (Formspecs) &lt;a name=&quot;formspecs&quot;&gt;&lt;/a&gt;"><a href="#15+-+GUIs+%28Formspecs%29+%26lt%3Ba+name%3D%26quot%3Bformspecs%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">15 - GUIs (Formspecs) <a name="formspecs"></a></a></h2> <h3 id="Introduction"><a href="#Introduction">Introduction</a></h3> <p><img src="https://rubenwardy.com/minetest_modding_book//static/formspec_example.png" alt="Furnace Inventory" />Screenshot of furnace formspec, labelled.</p> <p>この章では、 formspec を作成してユーザーに表示する方法を学習します。 formspec は、フォームの仕様コードです。 Minetest では、フォームはプレーヤーインベントリなどのウィンドウであり、ラベル、ボタン、フィールドなどのさまざまな要素を含めることができます。</p> <p>プレーヤーに情報を提供するだけでよい場合など、ユーザー入力を取得する必要がない場合は、formes ではなく<a target="_blank" rel="nofollow noopener" href="https://rubenwardy.com/minetest_modding_book/en/players/hud.html">ヘッドアップディスプレイ(HUD)</a>の要素の使用を検討する必要があることに注意してください。予期しないウィンドウはゲームプレイを混乱させる傾向があるためです。</p> <ul> <li><a href="#real-or-legacy-coordinates">Real or Legacy Coordinates</a></li> <li>Anatomy of a Formspec <ul> <li><a href="#elements">Elements</a></li> <li><a href="#header">Header</a></li> </ul></li> <li>Guessing Game <ul> <li><a href="#padding-and-spacing">Padding and Spacing</a></li> <li><a href="#receiving-formspec-submissions">Receiving Formspec Submissions</a></li> <li><a href="#contexts">Contexts</a></li> </ul></li> <li>Formspec Sources <ul> <li><a href="#node-meta-formspecs">Node Meta Formspecs</a></li> <li><a href="#player-inventory-formspecs">Player Inventory Formspecs</a></li> <li><a href="#your-turn">Your Turn</a></li> </ul></li> </ul> <h3 id="Real or Legacy Coordinates &lt;a name=&quot;#real-or-legacy-coordinates&quot;&gt;&lt;/a&gt;"><a href="#Real+or+Legacy+Coordinates+%26lt%3Ba+name%3D%26quot%3B%23real-or-legacy-coordinates%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Real or Legacy Coordinates <a name="#real-or-legacy-coordinates"></a></a></h3> <p>Minetest の古いバージョンでは、formspecs に一貫性がありませんでした。さまざまな要素が配置される方法は、予期しない方法で変化しました。要素の配置を予測して配置するのは困難でした。 Minetest 5.1.0 には、一貫した座標系を導入することでこれを修正することを目的とした実座標と呼ばれる機能が含まれています。実座標の使用を強くお勧めします。そのため、この章ではそれらを排他的に使用します。</p> <h3 id="Anatomy of a Formspec &lt;a name=&quot;&quot;&gt;&lt;/a&gt;"><a href="#Anatomy+of+a+Formspec+%26lt%3Ba+name%3D%26quot%3B%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Anatomy of a Formspec <a name=""></a></a></h3> <h4 id="Elements &lt;a name=&quot;#elements&quot;&gt;&lt;/a&gt;"><a href="#Elements+%26lt%3Ba+name%3D%26quot%3B%23elements%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Elements <a name="#elements"></a></a></h4> <p>Formspec は、通常とは異なる形式のドメイン固有言語です。これは、次の形式のいくつかの要素で構成されています。</p> <pre><code class="lua">type[param1;param2] </code></pre> <p>要素タイプが宣言され、パラメータは角括弧で囲まれています。次のように、複数の要素を結合したり、複数の行に配置したりできます。</p> <pre><code class="lua">foo[param1]bar[param1] bo[param1] </code></pre> <p>要素は、テキストボックスやボタンなどのアイテムであるか、サイズや背景などのメタデータにすることができます。考えられるすべての要素のリストについては、<a href="#elements">lua_api.txt</a>を参照してください。</p> <h4 id="Header &lt;a name=&quot;#header)&quot;&gt;&lt;/a&gt;"><a href="#Header+%26lt%3Ba+name%3D%26quot%3B%23header%29%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Header <a name="#header)"></a></a></h4> <p>formspec のヘッダーには、最初に表示する必要のある情報が含まれています。これには、 formspec のサイズ、位置、アンカー、およびゲーム全体のテーマを適用する必要があるかどうかが含まれます。</p> <p>ヘッダーの要素は特定の順序で定義する必要があります。そうしないと、エラーが発生します。この順序は上記の段落で示され、いつものように、<a href="#sizewhfixed_size">lua_api.txt</a>に記載されています。</p> <p>サイズは formspec スロットにあります。測定単位は約 64 ピクセルですが、画面密度とクライアントのスケーリング設定によって異なります。サイズが <code>2,2</code> の formspec は次のとおりです。</p> <pre><code class="lua">formspec_version[3] size[2,2] </code></pre> <p>formspec 言語バージョンを明示的に定義した方法に注目してください。これがないと、代わりにレガシーシステムが代わりに使用されます。これにより、一貫した要素の配置やその他の新しい機能を使用できなくなります。</p> <p>位置要素とアンカー要素は、 formspec を画面に配置するために使用されます。位置は、 formspec が画面上のどこにあるかを設定し、デフォルトで中央( <code>0.5,0.5</code> )になります。アンカーは、 formspec 上の位置を設定し formspec を画面の端に揃えることができます。 formspec は、次のように画面の左側に配置できます。</p> <pre><code class="lua">formspec_version[3] size[2,2] real_coordinates[true] position[0,0.5] anchor[0,0.5] </code></pre> <p>これにより、アンカーが formspec ボックスの左中央の端に設定され、次にそのアンカーの位置が画面の左側に設定されます。</p> <h3 id="Guessing Game"><a href="#Guessing+Game">Guessing Game</a></h3> <p><img src="https://rubenwardy.com/minetest_modding_book/static/formspec_guessing.png" alt="Guessing Formspec" />The guessing game formspec.</p> <p>学ぶための最良の方法は何かを作ることですので、推測ゲームを作りましょう。原理は単純です。 mod が数字を決定し、プレーヤーが数字を推測します。次に、 mod は、推測が実際の数値よりも高いか低いかを示します。</p> <p>まず、formspec コードを作成する関数を作成しましょう。他の場所での再利用が容易になるため、これを行うことをお勧めします。</p> <pre><code class="lua">guessing = {} function guessing.get_formspec(name) -- TODO: display whether the last guess was higher or lower local text = "I'm thinking of a number... Make a guess!" local formspec = { "formspec_version[3]", "size[6,3.476]", "label[0.375,0.5;", minetest.formspec_escape(text), "]", "field[0.375,1.25;5.25,0.8;number;Number;]", "button[1.5,2.3;3,0.8;guess;Guess]" } -- table.concat is faster than string concatenation - `..` return table.concat(formspec, "") end </code></pre> <p>上記のコードでは、フィールド、ラベル、およびボタンを配置します。フィールドはテキスト入力を許可し、ボタンはフォームを送信するために使用されます。パディングと間隔を追加するために要素が慎重に配置されていることに気付くでしょう。これについては後で説明します。</p> <p>次に、プレーヤーが formspec を表示できるようにします。これを行う主な方法は、 <code>show_formspec</code> を使用することです。</p> <pre><code class="lua">function guessing.show_to(name) minetest.show_formspec(name, "guessing:game", guessing.get_formspec(name)) end minetest.register_chatcommand("game", { func = function(name) guessing.show_to(name) end, }) </code></pre> <p><code>show_formspec</code> 関数は、プレーヤー名、 formspec 名、および formspec 自体を受け入れます。 formspec 名は、有効な itemname 、つまり <code>modname:itemname</code> の形式である必要があります。</p> <h4 id="Padding and Spacing &lt;a name=&quot;#padding-and-spacing&quot;&gt;&lt;/a&gt;"><a href="#Padding+and+Spacing+%26lt%3Ba+name%3D%26quot%3B%23padding-and-spacing%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Padding and Spacing <a name="#padding-and-spacing"></a></a></h4> <p><img src="https://rubenwardy.com/minetest_modding_book/static/formspec_padding_spacing.png" alt="Padding and spacing" />The guessing game formspec.</p> <p>パディングは、 formspec のエッジとそのコンテンツの間、または関連のない要素の間のギャップであり、赤で示されています。間隔は、関連する要素間のギャップであり、青で示されています。</p> <p>「 0.375 」のパディングと「 0.25 」の間隔を持つことはかなり標準的です。</p> <h4 id="Receiving Formspec Submissions &lt;a name=&quot;#receiving-formspec-submissions&quot;&gt;&lt;/a&gt;"><a href="#Receiving+Formspec+Submissions+%26lt%3Ba+name%3D%26quot%3B%23receiving-formspec-submissions%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Receiving Formspec Submissions <a name="#receiving-formspec-submissions"></a></a></h4> <p><code>show_formspec</code> が呼び出されると、 formspec がクライアントに送信されて表示されます。 formspecs を使用するには、クライアントからサーバーに情報を返す必要があります。このためのメソッドは formspec フィールド送信と呼ばれ、 <code>show_formspec</code> の場合、その送信はグローバルコールバックを使用して受信されます。</p> <pre><code class="lua">minetest.register_on_player_receive_fields(function(player, formname, fields) if formname ~= "guessing:game" then return end if fields.guess then local pname = player:get_player_name() minetest.chat_send_all(pname .. " guessed " .. fields.number) end end) </code></pre> <p><code>minetest.register_on_player_receive_fields</code> で指定された関数は、ユーザーがフォームを送信するたびに呼び出されます。ほとんどのコールバックは、関数に指定されたフォーム名を確認し、それが正しいフォームでない場合は終了する必要があります。ただし、一部のコールバックは、複数のフォームまたはすべてのフォームで機能する必要がある場合があります。</p> <p>関数の <code>fields</code> パラメータは、ユーザーが送信した値のテーブルであり、文字列でインデックスが付けられています。名前付き要素は、送信の原因となったイベントに関連する場合にのみ、独自の名前でフィールドに表示されます。たとえば、ボタン要素は、その特定のボタンが押された場合にのみフィールドに表示されます。</p> <div class="table-responsive"><table> <thead> <tr> <th>⚠ 悪意のあるクライアントはいつでも何でも送信できます</th> </tr> </thead> <tbody> <tr> <td>formspec の送信を信頼してはいけません。悪意のあるクライアントは、 formspec を表示したことがなくても、いつでも好きなものを送信できます。これは、特権をチェックし、それらがアクションの実行を許可されていることを確認する必要があることを意味します。</td> </tr> </tbody> </table></div> <p>したがって、 formspec がクライアントに送信され、クライアントが情報を送り返します。次のステップは、何らかの方法でターゲット値を生成して記憶し、推測に基づいて formspec を更新することです。これを行う方法は、「コンテキスト」と呼ばれる概念を使用することです。</p> <h4 id="Contexts &lt;a name=&quot;#contexts&quot;&gt;&lt;/a&gt;"><a href="#Contexts+%26lt%3Ba+name%3D%26quot%3B%23contexts%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Contexts <a name="#contexts"></a></a></h4> <p>多くの場合、 minetest.show_formspec で、クライアントに送信したくない情報をコールバックに提供する必要があります。これには、チャットコマンドが呼び出された対象や、ダイアログの内容が含まれる場合があります。この場合、覚えておく必要のある目標値。</p> <p>コンテキストは、情報を格納するためのプレーヤーごとのテーブルであり、すべてのオンラインプレーヤーのコンテキストは、file-local 変数に格納されます。</p> <pre><code class="lua">local _contexts = {} local function get_context(name) local context = _contexts[name] or {} _contexts[name] = context return context end minetest.register_on_leaveplayer(function(player) _contexts[player:get_player_name()] = nil end) </code></pre> <p>次に、formspec を表示する前に、 show コードを変更してコンテキストを更新する必要があります。</p> <pre><code class="Lua">function guessing.show_to(name) local context = get_context(name) context.target = context.target or math.random(1, 10) local fs = guessing.get_formspec(name, context) minetest.show_formspec(name, "guessing:game", fs) end </code></pre> <p>また、コンテキストを使用するように formspec 生成コードを変更する必要があります。</p> <pre><code class="lua">function guessing.get_formspec(name, context) local text if not context.guess then text = "I'm thinking of a number... Make a guess!" elseif context.guess == context.target then text = "Hurray, you got it!" elseif context.guess > context.target then text = "Too high!" else text = "Too low!" end </code></pre> <p><code>get_formspec</code> はコンテキストを読み取るだけで、まったく更新しないことをお勧めします。これにより、関数が単純になり、テストも簡単になります。</p> <p>そして最後に、ハンドラーを更新して、推測でコンテキストを更新する必要があります。</p> <pre><code class="lua">if fields.guess then local name = player:get_player_name() local context = get_context(name) context.guess = tonumber(fields.number) guessing.show_to(name) end </code></pre> <h3 id="Formspec Sources &lt;a name=&quot;#node-meta-formspecs&quot;&gt;&lt;/a&gt;"><a href="#Formspec+Sources+%26lt%3Ba+name%3D%26quot%3B%23node-meta-formspecs%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Formspec Sources <a name="#node-meta-formspecs"></a></a></h3> <p>formspec をクライアントに配信する方法は3つあります。</p> <ul> <li><a href="#guessing-game">show_formspec</a>: 上記で使用した方法で、フィールドは <code>register_on_player_receive_fields</code> によって受信されます。</li> <li><a href="#node-meta-formspecs">ノードメタフォームスペック</a>: ノードのメタデータにフォームスペックが含まれており、クライアントはプレーヤーが右クリックしたときそれを<em>すぐに</em>表示します。フィールドは、 <code>on_receive_fields</code> と呼ばれるノード定義のメソッドによって受信されます。</li> <li><a href="#player-inventory-formspecs">Player Inventory Formspecs</a>: formspec はある時点でクライアントに送信され、プレーヤーが <code>i</code> を押すと表示されます。フィールドは <code>register_on_player_receive_fields</code> によって受信されます。</li> </ul> <h4 id="Node Meta Formspecs &lt;a name=&quot;#node-meta-formspecs&quot;&gt;&lt;/a&gt;"><a href="#Node+Meta+Formspecs+%26lt%3Ba+name%3D%26quot%3B%23node-meta-formspecs%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Node Meta Formspecs <a name="#node-meta-formspecs"></a></a></h4> <p><code>minetest.show_formspec</code> は formspec を表示する唯一の方法ではありません。 <a href="#node_metadata">ノードのメタデータ</a>に formspecs を追加することもできます。たとえば、これはチェストで使用され、開始時間を短縮できます。サーバーがプレーヤーにチェストのフォームスペックを送信するのを待つ必要はありません。</p> <pre><code class="lua">minetest.register_node("mymod:rightclick", { description = "Rightclick me!", tiles = {"mymod_rightclick.png"}, groups = {cracky = 1}, after_place_node = function(pos, placer) -- This function is run when the chest node is placed. -- The following code sets the formspec for chest. -- Meta is a way of storing data onto a node. local meta = minetest.get_meta(pos) meta:set_string("formspec", "formspec_version[3]" .. "size[5,5]" .. "label[1,1;This is shown on right click]" .. "field[1,2;2,1;x;x;]") end, on_receive_fields = function(pos, formname, fields, player) if fields.quit then return end print(fields.x) end }) </code></pre> <p>このように設定された Formspecs は、同じコールバックをトリガーしません。 meta formspecs のフォーム入力を受け取るには、ノードを登録するときに <code>on_receive_fields</code> エントリを含める必要があります。</p> <p>このスタイルのコールバックは、フィールドで Enter キーを押すとトリガーされます。これは、 <code>minetest.show_formspec</code> では不可能です。ただし、この種のフォームは、ノードを右クリックすることによってのみ表示できます。プログラムでトリガーすることはできません。</p> <h4 id="Player Inventory Formspecs &lt;a name=&quot;#player-inventory-formspecs&quot;&gt;&lt;/a&gt;"><a href="#Player+Inventory+Formspecs+%26lt%3Ba+name%3D%26quot%3B%23player-inventory-formspecs%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Player Inventory Formspecs <a name="#player-inventory-formspecs"></a></a></h4> <p>プレイヤーインベントリフォームスペックは、プレイヤーがiを押したときに表示されるものです。グローバルコールバックは、この formspec からイベントを受信するために使用され、フォーム名は ""`です。</p> <p>複数の mod でプレーヤーのインベントリをカスタマイズできるようにするさまざまな mod がいくつかあります。公式に推奨される mod は <a href="#sfinv">SimpleFast Inventory(sfinv)</a> で、 MinetestGame に含まれています。</p> <h4 id="Your Turn"><a href="#Your+Turn">Your Turn</a></h4> <ul> <li>推測ゲームを拡張して、各プレーヤーの最高スコアを追跡します。最高スコアは、推測の数です。</li> <li>ユーザーが formspec を開いてメッセージを残すことができる「受信ボックス」と呼ばれるノードを作成します。このノードは、配置者の名前を「所有者」としてメタに保存し、「 show_formspec 」を使用してさまざまな formspec をさまざまなプレーヤーに表示する必要があります。</li> </ul> <h2 id="16 - HUD &lt;a name=&quot;hud&quot;&gt;&lt;/a&gt;"><a href="#16+-+HUD+%26lt%3Ba+name%3D%26quot%3Bhud%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">16 - HUD <a name="hud"></a></a></h2> <h3 id="Introduction"><a href="#Introduction">Introduction</a></h3> <p>ヘッドアップディスプレイ( HUD )要素を使用すると、テキスト、画像、およびその他のグラフィック要素を表示できます。</p> <p>HUD はユーザー入力を受け入れません。そのためには、<a target="_blank" rel="nofollow noopener" href="https://rubenwardy.com/minetest_modding_book/en/players/formspecs.html">formspec</a>を使用する必要があります。</p> <ul> <li>Positioning <ul> <li><a href="#position-and-offset">Position and Offset</a></li> <li><a href="#alignment">Alignment</a></li> <li><a href="#scoreboard">Scoreboard</a></li> </ul></li> <li>Text Elements <ul> <li><a href="#parameters">Parameters</a></li> <li><a href="#our-example">Our Example</a></li> </ul></li> <li>Image Elements <ul> <li><a href="#parameters-1">Parameters</a></li> <li><a href="#scale">Scale</a></li> </ul></li> <li><a href="#changing-an-element">Changing an Element</a></li> <li><a href="#storing-ids">Storing IDs</a></li> <li><a href="#other-elements">Other Elements</a></li> </ul> <h3 id="Positioning"><a href="#Positioning">Positioning</a></h3> <h4 id="Position and Offset &lt;a name=&quot;#position-and-offset&quot;&gt;&lt;/a&gt;"><a href="#Position+and+Offset+%26lt%3Ba+name%3D%26quot%3B%23position-and-offset%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Position and Offset <a name="#position-and-offset"></a></a></h4> <p><img src="https://rubenwardy.com/minetest_modding_book//static/hud_diagram_center.svg" alt="Diagram showing a centered text element" /></p> <p>画面にはさまざまな物理的サイズと解像度があり、HUDはすべての画面タイプで適切に機能する必要があります。</p> <p>これに対する Minetest の解決策は、パーセンテージ位置とオフセットの両方を使用して要素の位置を指定することです。パーセンテージの位置は画面サイズを基準にしているため、要素を画面の中央に配置するには、画面の半分のパーセンテージの位置を指定する必要があります。 (50%, 50%) 、および (0, 0) のオフセット。</p> <p>次に、オフセットを使用して、パーセント位置を基準にして要素を移動します。</p> <h4 id="Alignment &lt;a name=&quot;#alignment&quot;&gt;&lt;/a&gt;"><a href="#Alignment+%26lt%3Ba+name%3D%26quot%3B%23alignment%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Alignment <a name="#alignment"></a></a></h4> <p>位置合わせは、位置とオフセットの結果が要素上にある場所です。たとえば、 <code>{x = -1.0, y = 0.0}</code> は、位置とオフセットの結果を要素の境界の左側に配置します。これは、テキスト要素を左、中央、または右に揃える場合に特に便利です。</p> <p><img src="https://rubenwardy.com/minetest_modding_book//static/hud_diagram_alignment.svg" alt="Diagram showing alignment" /></p> <p>上の図は、3つのウィンドウ(青)を示しています。各ウィンドウには、 1 つの HUD 要素(黄色)があり、毎回異なる配置になっています。矢印は、位置とオフセットの計算結果です。</p> <h4 id="Scoreboard &lt;a name=&quot;#scoreboard&quot;&gt;&lt;/a&gt;"><a href="#Scoreboard+%26lt%3Ba+name%3D%26quot%3B%23scoreboard%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Scoreboard <a name="#scoreboard"></a></a></h4> <p>この章では、次のようにスコアパネルを配置および更新する方法を学習します。</p> <p><img src="https://rubenwardy.com/minetest_modding_book//static/hud_final.png" alt="screenshot of the HUD we're aiming for" /></p> <p>上のスクリーンショットでは、すべての要素の位置が同じパーセンテージ (100%, 50%) ですが、オフセットが異なります。これにより、すべてをウィンドウの右側に固定できますが、サイズを変更することはできません。</p> <h3 id="Text Elements"><a href="#Text+Elements">Text Elements</a></h3> <p>プレーヤーオブジェクトのコピーを取得したら、 HUD 要素を作成できます。</p> <pre><code class="Lua">local player = minetest.get_player_by_name("username") local idx = player:hud_add({ hud_elem_type = "text", position = {x = 0.5, y = 0.5}, offset = {x = 0, y = 0}, text = "Hello world!", alignment = {x = 0, y = 0}, -- center aligned scale = {x = 100, y = 100}, -- covered later }) </code></pre> <p><code>hud_add</code> 関数は要素 ID を返します - これは後で HUD 要素を変更または削除するために使用できます。</p> <h4 id="Parameters &lt;a name=&quot;#parameters&quot;&gt;&lt;/a&gt;"><a href="#Parameters+%26lt%3Ba+name%3D%26quot%3B%23parameters%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Parameters <a name="#parameters"></a></a></h4> <p>要素のタイプは、定義テーブルの <code>hud_elem_type</code>プロパティを使用して指定されます。他のプロパティの意味は、このタイプによって異なります。</p> <p><code>scale</code> はテキストの最大境界です。これらの境界外のテキストはトリミングされます。例: <code>{x=100, y=100}</code></p> <p><code>number</code> はテキストの色であり、<a target="_blank" rel="nofollow noopener" href="http://www.colorpicker.com/">16進形式</a>です。例:<code>0xFF0000</code></p> <h4 id="Our Example &lt;a name=&quot;#our-example&quot;&gt;&lt;/a&gt;"><a href="#Our+Example+%26lt%3Ba+name%3D%26quot%3B%23our-example%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Our Example <a name="#our-example"></a></a></h4> <p>先に進んで、すべてのテキストをスコアパネルに配置しましょう。</p> <pre><code class="Lua">-- Get the dig and place count from storage, or default to 0 local meta = player:get_meta() local digs_text = "Digs: " .. meta:get_int("score:digs") local places_text = "Places: " .. meta:get_int("score:places") player:hud_add({ hud_elem_type = "text", position = {x = 1, y = 0.5}, offset = {x = -120, y = -25}, text = "Stats", alignment = 0, scale = { x = 100, y = 30}, number = 0xFFFFFF, }) player:hud_add({ hud_elem_type = "text", position = {x = 1, y = 0.5}, offset = {x = -180, y = 0}, text = digs_text, alignment = -1, scale = { x = 50, y = 10}, number = 0xFFFFFF, }) player:hud_add({ hud_elem_type = "text", position = {x = 1, y = 0.5}, offset = {x = -70, y = 0}, text = places_text, alignment = -1, scale = { x = 50, y = 10}, number = 0xFFFFFF, }) </code></pre> <p>これにより、次のようになります。</p> <p><img src="https://rubenwardy.com/minetest_modding_book//static/hud_text.png" alt="screenshot of the HUD we're aiming for" /></p> <h3 id="Image Elements"><a href="#Image+Elements">Image Elements</a></h3> <p>画像要素は、テキスト要素と非常によく似た方法で作成されます。</p> <pre><code class="Lua">player:hud_add({ hud_elem_type = "image", position = {x = 1, y = 0.5}, offset = {x = -220, y = 0}, text = "score_background.png", scale = { x = 1, y = 1}, alignment = { x = 1, y = 0 }, }) </code></pre> <p>これで、次のようになります。</p> <p><img src="https://rubenwardy.com/minetest_modding_book//static/hud_background_img.png" alt="screenshot of the HUD so far" /></p> <h4 id="Parameters &lt;a name=&quot;#parameters-1&quot;&gt;&lt;/a&gt;"><a href="#Parameters+%26lt%3Ba+name%3D%26quot%3B%23parameters-1%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Parameters <a name="#parameters-1"></a></a></h4> <p><code>text</code> フィールドは画像名を提供するために使用されます。</p> <p>座標が正の場合、それはスケールファクターであり、 1 は元の画像サイズ、2 は 2 倍のサイズというようになります。ただし、座標が負の場合は、画面サイズのパーセンテージです。たとえば、 <code>x = -100</code> は幅の 100% です。</p> <h4 id="Scale &lt;a name=&quot;#scale&quot;&gt;&lt;/a&gt;"><a href="#Scale+%26lt%3Ba+name%3D%26quot%3B%23scale%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Scale <a name="#scale"></a></a></h4> <p>スケールの例として、スコアパネルのプログレスバーを作成しましょう。</p> <pre><code class="Lua">local percent = tonumber(meta:get("score:score") or 0.2) player:hud_add({ hud_elem_type = "image", position = {x = 1, y = 0.5}, offset = {x = -215, y = 23}, text = "score_bar_empty.png", scale = { x = 1, y = 1}, alignment = { x = 1, y = 0 }, }) player:hud_add({ hud_elem_type = "image", position = {x = 1, y = 0.5}, offset = {x = -215, y = 23}, text = "score_bar_full.png", scale = { x = percent, y = 1}, alignment = { x = 1, y = 0 }, }) </code></pre> <p>これで、最初の投稿のような HUD ができました。ただし、問題が 1 つありますが、統計が変更されても更新されません。</p> <h3 id="Changing an Element &lt;a name=&quot;#changing-an-element&quot;&gt;&lt;/a&gt;"><a href="#Changing+an+Element+%26lt%3Ba+name%3D%26quot%3B%23changing-an-element%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Changing an Element <a name="#changing-an-element"></a></a></h3> <p><code>hud_add</code> メソッドによって返された ID を使用して、 ID を更新したり、後で削除したりできます。</p> <pre><code class="Lua">local idx = player:hud_add({ hud_elem_type = "text", text = "Hello world!", -- parameters removed for brevity }) player:hud_change(idx, "text", "New Text") player:hud_remove(idx) </code></pre> <p><code>hud_change</code> メソッドは、要素 ID、変更するプロパティ、および新しい値を受け取ります。上記の呼び出しにより、 <code>text</code> プロパティが「 HelloWorld 」から「 Newtext 」に変更されます。</p> <p>これは、 <code>hud_add</code> の直後に <code>hud_change</code> を実行することは、機能的には以下と同等であり、かなり非効率的な方法であることを意味します。</p> <pre><code class="Lua">local idx = player:hud_add({ hud_elem_type = "text", text = "New Text", }) </code></pre> <h3 id="Storing IDs &lt;a name=&quot;#storing-ids&quot;&gt;&lt;/a&gt;"><a href="#Storing+IDs+%26lt%3Ba+name%3D%26quot%3B%23storing-ids%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Storing IDs <a name="#storing-ids"></a></a></h3> <pre><code class="Lua">score = {} local saved_huds = {} function score.update_hud(player) local player_name = player:get_player_name() -- Get the dig and place count from storage, or default to 0 local meta = player:get_meta() local digs_text = "Digs: " .. meta:get_int("score:digs") local places_text = "Places: " .. meta:get_int("score:places") local percent = tonumber(meta:get("score:score") or 0.2) local ids = saved_huds[player_name] if ids then player:hud_change(ids["places"], "text", places_text) player:hud_change(ids["digs"], "text", digs_text) player:hud_change(ids["bar_foreground"], "scale", { x = percent, y = 1 }) else ids = {} saved_huds[player_name] = ids -- create HUD elements and set ids into `ids` end end minetest.register_on_joinplayer(score.update_hud) minetest.register_on_leaveplayer(function(player) saved_huds[player:get_player_name()] = nil end) </code></pre> <h3 id="Other Elements &lt;a name=&quot;#other-elements&quot;&gt;&lt;/a&gt;"><a href="#Other+Elements+%26lt%3Ba+name%3D%26quot%3B%23other-elements%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Other Elements <a name="#other-elements"></a></a></h3> <p>HUD 要素の完全なリストについては、<a target="_blank" rel="nofollow noopener" href="https://rubenwardy.com/minetest_modding_book/lua_api.html#hud-element-types">lua_api.txt</a>をお読みください。</p> <h2 id="17 - SFINV: Inventory Formspec &lt;a name=&quot;sfinv&quot;&gt;&lt;/a&gt;"><a href="#17+-+SFINV%3A+Inventory+Formspec+%26lt%3Ba+name%3D%26quot%3Bsfinv%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">17 - SFINV: Inventory Formspec <a name="sfinv"></a></a></h2> <h3 id="Introduction"><a href="#Introduction">Introduction</a></h3> <p>Simple Fast Inventory(SFINV)は、 Minetest Game にある mod で、プレーヤーのインベントリ <a href="#formspecs">formspec</a> を作成するために使用されます。 SFINV には、表示されているページを追加または管理できる API が付属しています。</p> <p>SFINV はデフォルトでページをタブとして表示しますが、 mod またはゲームが代わりに他の形式でページを表示することを決定する可能性があるため、ページはページと呼ばれます。たとえば、複数のページを 1 つの formspec に表示できます。</p> <ul> <li><a href="#registering-a-page">Registering a Page</a></li> <li><a href="#receiving-events">Receiving events</a></li> <li><a href="#conditionally-showing-to-players">Conditionally showing to players</a></li> <li><a href="#onenter-and-onleave-callbacks">on_enter and on_leave callbacks</a></li> <li><a href="#adding-to-an-existing-page">Adding to an existing page</a></li> </ul> <h3 id="Registering a Page &lt;a name=&quot;#registering-a-page&quot;&gt;&lt;/a&gt;"><a href="#Registering+a+Page+%26lt%3Ba+name%3D%26quot%3B%23registering-a-page%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Registering a Page <a name="#registering-a-page"></a></a></h3> <p>SFINV は、ページを作成するための適切な名前の <code>sfinv.register_page</code> 関数を提供します。ページの名前とその定義を使用して関数を呼び出すだけです。</p> <pre><code class="Lua">sfinv.register_page("mymod:hello", { title = "Hello!", get = function(self, player, context) return sfinv.make_formspec(player, context, "label[0.1,0.1;Hello world!]", true) end }) </code></pre> <p><code>make_formspec</code> 関数は、 formspec を SFINV の formspec コードで囲みます。現在「 true 」に設定されている 4 番目のパラメータは、プレーヤーのインベントリを表示するかどうかを決定します。</p> <p>物事をもっとエキサイティングにしましょう。これは、プレーヤー管理タブの formspec 生成部分のコードです。このタブでは、管理者がリストでプレーヤーを選択してボタンをクリックすることで、プレーヤーをキックまたは禁止することができます。</p> <pre><code class="Lua">sfinv.register_page("myadmin:myadmin", { title = "Tab", get = function(self, player, context) local players = {} context.myadmin_players = players -- Using an array to build a formspec is considerably faster local formspec = { "textlist[0.1,0.1;7.8,3;playerlist;" } -- Add all players to the text list, and to the players list local is_first = true for _ , player in pairs(minetest.get_connected_players()) do local player_name = player:get_player_name() players[#players + 1] = player_name if not is_first then formspec[#formspec + 1] = "," end formspec[#formspec + 1] = minetest.formspec_escape(player_name) is_first = false end formspec[#formspec + 1] = "]" -- Add buttons formspec[#formspec + 1] = "button[0.1,3.3;2,1;kick;Kick]" formspec[#formspec + 1] = "button[2.1,3.3;2,1;ban;Kick + Ban]" -- Wrap the formspec in sfinv's layout -- (ie: adds the tabs and background) return sfinv.make_formspec(player, context, table.concat(formspec, ""), false) end, }) </code></pre> <p>上記のコードについては何も新しいことはありません。すべての概念は、上記および前の章で説明されています。</p> <p><img src="https://rubenwardy.com/minetest_modding_book//static/sfinv_admin_fs.png" alt="Player Admin Page" /></p> <h3 id="Receiving events &lt;a name=&quot;#receiving-events&quot;&gt;&lt;/a&gt;"><a href="#Receiving+events+%26lt%3Ba+name%3D%26quot%3B%23receiving-events%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Receiving events <a name="#receiving-events"></a></a></h3> <p>sfinv 定義に <code>on_player_receive_fields</code> 関数を追加することで、 formspec イベントを受け取ることができます。</p> <pre><code class="Lua">on_player_receive_fields = function(self, player, context, fields) -- TODO: implement this end, </code></pre> <p><code>on_player_receive_fields</code> は <code>minetest.register_on_player_receive_fields</code> と同じように機能しますが、 <code>formname</code> の代わりに <code>context</code> が指定されている点が異なります。 SFINV は、ナビゲーションタブイベントなど、 SFINV 自体に関連するイベントを消費するため、このコールバックではそれらを受信しないことに注意してください。</p> <p>それでは、 adminmod に <code>on_player_receive_fields</code> を実装しましょう。</p> <pre><code class="Lua">on_player_receive_fields = function(self, player, context, fields) -- text list event, check event type and set index if selection changed if fields.playerlist then local event = minetest.explode_textlist_event(fields.playerlist) if event.type == "CHG" then context.myadmin_selected_idx = event.index end -- Kick button was pressed elseif fields.kick then local player_name = context.myadmin_players[context.myadmin_selected_idx] if player_name then minetest.chat_send_player(player:get_player_name(), "Kicked " .. player_name) minetest.kick_player(player_name) end -- Ban button was pressed elseif fields.ban then local player_name = context.myadmin_players[context.myadmin_selected_idx] if player_name then minetest.chat_send_player(player:get_player_name(), "Banned " .. player_name) minetest.ban_player(player_name) minetest.kick_player(player_name, "Banned") end end end, </code></pre> <p>ただし、これにはかなり大きな問題があります。誰でもプレイヤーを蹴ったり禁止したりできます!キックまたはバンの特権を持つプレイヤーにのみこれを表示する方法が必要です。幸いなことに、 SFINV を使用するとこれを実行できます。</p> <h3 id="Conditionally showing to players &lt;a name=&quot;#conditionally-showing-to-players&quot;&gt;&lt;/a&gt;"><a href="#Conditionally+showing+to+players+%26lt%3Ba+name%3D%26quot%3B%23conditionally-showing-to-players%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Conditionally showing to players <a name="#conditionally-showing-to-players"></a></a></h3> <p>ページがいつ表示されるかを制御したい場合は、ページの定義に <code>is_in_nav</code> 関数を追加できます。</p> <pre><code class="Lua">is_in_nav = function(self, player, context) local privs = minetest.get_player_privs(player:get_player_name()) return privs.kick or privs.ban end, </code></pre> <p>1つの priv のみをチェックする必要がある場合、または「 and 」を実行する場合は、 <code>get_player_privs</code> の代わりに <code>minetest.check_player_privs()</code> を使用する必要があります。</p> <p><code>is_in_nav</code> は、プレーヤーのインベントリフォームスペックが生成されたときにのみ呼び出されることに注意してください。これは、プレーヤーがゲームに参加したとき、タブを切り替えたとき、または mod が SFINV の再生成を要求したときに発生します。</p> <p>つまり、 <code>is_in_nav</code> の結果を変更する可能性のあるイベントについて、 SFINV がインベントリフォームスペックを再生成するように手動で要求する必要があります。私たちの場合、キックまたは禁止がプレーヤーに付与または取り消されるたびに、それを行う必要があります。</p> <pre><code class="Lua">local function on_grant_revoke(grantee, granter, priv) if priv ~= "kick" and priv ~= "ban" then return end local player = minetest.get_player_by_name(grantee) if not player then return end local context = sfinv.get_or_create_context(player) if context.page ~= "myadmin:myadmin" then return end sfinv.set_player_inventory_formspec(player, context) end minetest.register_on_priv_grant(on_grant_revoke) minetest.register_on_priv_revoke(on_grant_revoke) </code></pre> <h3 id="on_enter and on_leave callbacks &lt;a name=&quot;#onenter-and-onleave-callbacks&quot;&gt;&lt;/a&gt;"><a href="#on_enter+and+on_leave+callbacks+%26lt%3Ba+name%3D%26quot%3B%23onenter-and-onleave-callbacks%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">on_enter and on_leave callbacks <a name="#onenter-and-onleave-callbacks"></a></a></h3> <p>プレーヤーは、タブが選択されたときにタブに<em>入り</em>、別のタブが選択されようとしているときにタブから<em>離れます</em>。カスタムテーマを使用すると、複数のページを選択できる可能性があります。</p> <p>これらのイベントは、プレーヤーによってトリガーされない場合があることに注意してください。その時点では、プレーヤーは formspec を開いていない可能性があります。たとえば、プレーヤーがインベントリを開く前でもゲームに参加すると、ホームページに対して on_enter が呼び出されます。</p> <p>プレーヤーを混乱させる可能性があるため、ページの変更をキャンセルすることはできません。</p> <pre><code class="Lua">on_enter = function(self, player, context) end, on_leave = function(self, player, context) end, </code></pre> <h3 id="Adding to an existing page &lt;a name=&quot;#adding-to-an-existing-page&quot;&gt;&lt;/a&gt;"><a href="#Adding+to+an+existing+page+%26lt%3Ba+name%3D%26quot%3B%23adding-to-an-existing-page%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Adding to an existing page <a name="#adding-to-an-existing-page"></a></a></h3> <p>既存のページにコンテンツを追加するには、ページをオーバーライドして、返された formspec を変更する必要があります。</p> <pre><code class="Lua">local old_func = sfinv.registered_pages["sfinv:crafting"].get sfinv.override_page("sfinv:crafting", { get = function(self, player, context, ...) local ret = old_func(self, player, context, ...) if type(ret) == "table" then ret.formspec = ret.formspec .. "label[0,0;Hello]" else -- Backwards compatibility ret = ret .. "label[0,0;Hello]" end return ret end }) </code></pre> <h2 id="18 - Biomes and Decorations &lt;a name=&quot;biomesdeco&quot;&gt;&lt;/a&gt;"><a href="#18+-+Biomes+and+Decorations+%26lt%3Ba+name%3D%26quot%3Bbiomesdeco%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">18 - Biomes and Decorations <a name="biomesdeco"></a></a></h2> <h3 id="Introduction"><a href="#Introduction">Introduction</a></h3> <p>興味深く多様なゲーム内環境を作成することを目指す場合、バイオームと装飾を登録する機能は不可欠です。この章では、バイオームを登録する方法、バイオームの分布を制御する方法、およびバイオームに装飾を配置する方法について説明します。</p> <ul> <li><a href="#what-are-biomes">What are Biomes?</a></li> <li>Biome Placement <ul> <li><a href="#heat-and-humidity">Heat and Humidity</a></li> <li><a href="#visualising-boundaries-using-voronoi-diagrams">Visualising Boundaries using Voronoi Diagrams</a></li> <li><a href="#creating-a-voronoi-diagram-using-geogebra">Creating a Voronoi Diagram using Geogebra</a></li> </ul></li> <li><a href="#registering-a-biome">Registering a Biome</a></li> <li><a href="#what-are-decorations">What are Decorations?</a></li> <li><a href="#registering-a-simple-decoration">Registering a Simple Decoration</a></li> <li><a href="#registering-a-schematic-decoration">Registering a Schematic Decoration</a></li> <li><a href="#mapgen-aliases">Mapgen Aliases</a></li> </ul> <h3 id="What are Biomes? &lt;a name=&quot;#what-are-biomes&quot;&gt;&lt;/a&gt;"><a href="#What+are+Biomes%3F+%26lt%3Ba+name%3D%26quot%3B%23what-are-biomes%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">What are Biomes? <a name="#what-are-biomes"></a></a></h3> <p>Minetest バイオームは、特定のゲーム内環境です。バイオームを登録するときに、マップ生成中にバイオームに表示されるノードのタイプを判別できます。バイオーム間で異なる可能性のある最も一般的なタイプのノードには、次のものがあります。</p> <ul> <li>トップノード: これは、サーフェス上で最も一般的に見られるノードです。よく知られている例は、 MinetestGame の「 Dirt with Grass 」です。</li> <li>フィラーノード: これは、最上位ノードのすぐ下のレイヤーです。草のあるバイオームでは、それはしばしば汚れになります。</li> <li>ストーンノード: これは、地下で最もよく見られるノードです。</li> <li><p>水ノード: これは通常液体であり、水域が予想される場所に表示されるノードになります。</p></li> <li><p>Top node: This is the node most commonly found on the surface. A well-known example would be “Dirt with Grass” from Minetest Game.</p></li> <li>Filler node: This is the layer immediately beneath the top node. In biomes with grass, it will often be dirt.</li> <li>Stone node: This is the node you most commonly see underground.</li> <li>Water node: This is usually a liquid and will be the node that appears where you would expect bodies of water.</li> </ul> <p>他のタイプのノードもバイオーム間で異なる可能性があり、同じゲーム内で非常に異なる環境を作成する機会を提供します。</p> <h3 id="Biome Placement &lt;a name=&quot;&quot;&gt;&lt;/a&gt;"><a href="#Biome+Placement+%26lt%3Ba+name%3D%26quot%3B%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Biome Placement <a name=""></a></a></h3> <h4 id="Heat and Humidity &lt;a name=&quot;#heat-and-humidity&quot;&gt;&lt;/a&gt;"><a href="#Heat+and+Humidity+%26lt%3Ba+name%3D%26quot%3B%23heat-and-humidity%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Heat and Humidity <a name="#heat-and-humidity"></a></a></h4> <p>バイオームを登録するだけでは十分ではありません。また、ゲーム内のどこで発生するかを決定する必要があります。これは、各バイオームに熱と湿度の値を割り当てることによって行われます。</p> <p>これらの値について慎重に検討する必要があります。それらは、どのバイオームが互いに隣接できるかを決定します。決定が不十分だと、氷河と国境を接する暑い砂漠となることを意味するものや、避けたいと思われる他のありそうもない組み合わせが生じる可能性があります。</p> <p>ゲームでは、マップの任意のポイントでの熱と湿度の値は通常 0 から 100 の間です。値は徐々に変化し、マップ内を移動するにつれて増加または減少します。任意の時点でのバイオームは、登録されたバイオームのどれがマップ上のその位置にあるものに最も近い熱と湿度の値を持っているかによって決定されます。</p> <p>熱と湿度の変化は緩やかであるため、バイオームの環境に関する合理的な期待に基づいて、バイオームに熱と湿度の値を割り当てることをお勧めします。例えば:</p> <ul> <li>砂漠は高温多湿である可能性があります。</li> <li>雪に覆われた森は、熱が低く、湿度が中程度の場合があります。</li> <li>沼地バイオームは一般的に湿度が高いでしょう。 * 実際には、これは、多様な範囲のバイオームがある限り、互いに隣接するバイオームが論理的な進行を形成することに気付く可能性が高いことを意味します。</li> </ul> <h4 id="Visualising Boundaries using Voronoi Diagrams &lt;a name=&quot;#visualising-boundaries-using-voronoi-diagrams&quot;&gt;&lt;/a&gt;"><a href="#Visualising+Boundaries+using+Voronoi+Diagrams+%26lt%3Ba+name%3D%26quot%3B%23visualising-boundaries-using-voronoi-diagrams%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Visualising Boundaries using Voronoi Diagrams <a name="#visualising-boundaries-using-voronoi-diagrams"></a></a></h4> <p><img src="https://rubenwardy.com/minetest_modding_book/static/biomes_voronoi.png" alt="Vernoi" />Voronoi diagram, showing the closest point.By <a target="_blank" rel="nofollow noopener" href="https://en.wikipedia.org/wiki/Voronoi_diagram#/media/File:Euclidean_Voronoi_diagram.svg">Balu Ertl</a>, CC BY-SA 4.0.</p> <p>使用しているバイオーム間の関係を視覚化できれば、バイオームの熱と湿度の値を微調整するのは簡単です。これは、独自のバイオームのフルセットを作成する場合に最も重要ですが、既存のセットにバイオームを追加する場合にも役立ちます。</p> <p>どのバイオームが境界を共有するかを視覚化する最も簡単な方法は、ボロノイ図を作成することです。これを使用して、任意の位置が2次元図のどの点に最も近いかを示すことができます。</p> <p>ボロノイ図は、互いに隣接する必要のあるバイオームが存在しない場所と、互いに隣接するべきではないバイオームが存在する場所を明らかにすることができます。また、一般的なバイオームがゲーム内でどのように機能するかについての一般的な洞察を与えることもできます。図の外縁にある小さなバイオームやバイオームよりも、大きくて中央のバイオームの方が一般的です。</p> <p>これは、熱と湿度の値に基づいて各バイオームのポイントをマークすることによって行われます。ここで、x軸は熱で、y軸は湿度です。次に、ダイアグラムはエリアに分割され、特定のエリア内のすべての位置が、ダイアグラム上の他のポイントよりもそのエリア内のポイントに近くなります。</p> <p>各領域はバイオームを表します。 2つのエリアが境界を共有している場合、ゲーム内でそれらが表すバイオームを隣り合わせに配置できます。他の領域と共有される長さと比較した、2つの領域間で共有される境界の長さは、2つのバイオームが互いに隣接して見つかる可能性が高い頻度を示します。</p> <h4 id="Creating a Voronoi Diagram using Geogebra &lt;a name=&quot;#creating-a-voronoi-diagram-using-geogebra&quot;&gt;&lt;/a&gt;"><a href="#Creating+a+Voronoi+Diagram+using+Geogebra+%26lt%3Ba+name%3D%26quot%3B%23creating-a-voronoi-diagram-using-geogebra%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Creating a Voronoi Diagram using Geogebra <a name="#creating-a-voronoi-diagram-using-geogebra"></a></a></h4> <p>手で描くだけでなく、<a target="_blank" rel="nofollow noopener" href="https://www.geogebra.org/">Geogebra</a>などのプログラムを使ってボロノイ図を作成することもできます。</p> <ol> <li><p>ツールバーのポイントツール(アイコンは「 A 」の付いたポイント)を選択し、チャートをクリックしてポイントを作成します。ポイントをドラッグしたり、左側のサイドバーで明示的に位置を設定したりできます。また、物事を明確にするために、各ポイントにラベルを付ける必要があります。</p></li> <li><p>次に、左側のサイドバーの入力ボックスに次の関数を入力して、ボロノイを作成します。</p> <pre><code class="Lua">Voronoi({ A, B, C, D, E }) </code></pre> <p>各ポイントが中括弧の内側にあり、コンマで区切られている場合。あなたは今すべきです。</p></li> <li><p>やったぁ!これで、ドラッグ可能なすべてのポイントを含むボロノイ図が作成されます。</p></li> </ol> <h3 id="Registering a Biome &lt;a name=&quot;#registering-a-biome&quot;&gt;&lt;/a&gt;"><a href="#Registering+a+Biome+%26lt%3Ba+name%3D%26quot%3B%23registering-a-biome%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Registering a Biome <a name="#registering-a-biome"></a></a></h3> <p>次のコードは、grasslandsbiome という名前の単純なバイオームを登録します。</p> <pre><code class="Lua">minetest.register_biome({ name = "grasslands", node_top = "default:dirt_with_grass", depth_top = 1, node_filler = "default:dirt", depth_filler = 3, y_max = 1000, y_min = -3, heat_point = 50, humidity_point = 50, }) </code></pre> <p>このバイオームには、表面に草のノードがある1層の土と、その下に 3 層の土のノードがあります。ストーンノードを指定していないため、 <code>mapgen_stone</code> の mapgen エイリアス登録で定義されたノードがダートの下に存在します。</p> <p>バイオームを登録する際には多くのオプションがあり、これらはいつものように<a href="#biome-definition">Minetest Lua APIリファレンス</a>に記載されています。</p> <p>作成するすべてのバイオームに対してすべてのオプションを定義する必要はありませんが、特定のオプションまたは適切な mapgen エイリアスのいずれかを定義しないと、マップ生成エラーが発生する場合があります。</p> <h3 id="What are Decorations? &lt;a name=&quot;#what-are-decorations&quot;&gt;&lt;/a&gt;"><a href="#What+are+Decorations%3F+%26lt%3Ba+name%3D%26quot%3B%23what-are-decorations%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">What are Decorations? <a name="#what-are-decorations"></a></a></h3> <p>デコレーションは、 mapgen のマップに配置できるノードまたはスケマチックのいずれかです。一般的な例としては、花、低木、樹木などがあります。他のより創造的な用途には、洞窟につららや石筍をぶら下げたり、地下の結晶を形成したり、小さな建物を配置したりすることもあります。</p> <p>装飾は、特定のバイオーム、高さ、またはそれらを配置できるノードに制限できます。それらは、特定の植物、樹木、またはその他の特徴を確実に持つことによって、バイオームの環境を開発するためによく使用されます。</p> <h3 id="Registering a Simple Decoration &lt;a name=&quot;#registering-a-simple-decoration&quot;&gt;&lt;/a&gt;"><a href="#Registering+a+Simple+Decoration+%26lt%3Ba+name%3D%26quot%3B%23registering-a-simple-decoration%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Registering a Simple Decoration <a name="#registering-a-simple-decoration"></a></a></h3> <p>単純な装飾は、マップ生成中にマップ上に単一ノードの装飾を配置するために使用されます。デコレーションとして配置するノード、配置できる場所の詳細、および発生頻度を指定する必要があります。</p> <p>例:</p> <pre><code class="Lua">minetest.register_decoration({ deco_type = "simple", place_on = {"base:dirt_with_grass"}, sidelen = 16, fill_ratio = 0.1, biomes = {"grassy_plains"}, y_max = 200, y_min = 1, decoration = "plants:grass", }) </code></pre> <p>この例では、 <code>plants:grass</code> という名前のノードは、<code>base:dirt_with_grass</code> ノードの上にある grassy_plains という名前のバイオームに、高さ <code>y = 1</code>と<code>y = 200</code> の間に配置されます。</p> <p>fill_ratio 値は、装飾が表示される頻度を決定します。値を 1 まで大きくすると、多数の装飾が配置されます。代わりに、ノイズパラメータを使用して配置を決定することができます。</p> <h3 id="Registering a Schematic Decoration &lt;a name=&quot;#registering-a-schematic-decoration&quot;&gt;&lt;/a&gt;"><a href="#Registering+a+Schematic+Decoration+%26lt%3Ba+name%3D%26quot%3B%23registering-a-schematic-decoration%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Registering a Schematic Decoration <a name="#registering-a-schematic-decoration"></a></a></h3> <p>スケマチックの装飾は単純な装飾と非常に似ていますが、単一のノードの配置ではなく、スケマチックの配置が含まれます。例えば:<br /> Schematic decorations are very similar to simple decoration, but involve the placement of a schematic instead of the placement of a single node. For example:</p> <pre><code class="Lua">minetest.register_decoration({ deco_type = "schematic", place_on = {"base:desert_sand"}, sidelen = 16, fill_ratio = 0.0001, biomes = {"desert"}, y_max = 200, y_min = 1, schematic = minetest.get_modpath("plants") .. "/schematics/cactus.mts", flags = "place_center_x, place_center_z", rotation = "random", }) </code></pre> <p>この例では、cactus.mts スケマチックが砂漠のバイオームに配置されています。スケマチックへのパスを指定する必要があります。この場合、このパスは mod 内の専用のスケマチックディレクトリに保存されます。</p> <p>この例では、スケマチックの配置を中央に配置するフラグも設定し、回転はランダムに設定されています。スケマチックを装飾として配置するときのランダムな回転は、非対称のスケマチックを使用するときに、より多くのバリエーションを導入するのに役立ちます。</p> <h3 id="Mapgen Aliases &lt;a name=&quot;#mapgen-aliases&quot;&gt;&lt;/a&gt;"><a href="#Mapgen+Aliases+%26lt%3Ba+name%3D%26quot%3B%23mapgen-aliases%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Mapgen Aliases <a name="#mapgen-aliases"></a></a></h3> <p>既存のゲームにはすでに適切な mapgen エイリアスが含まれているはずなので、独自のゲームを作成する場合は、独自の mapgen エイリアスの登録を検討するだけで済みます。</p> <p>Mapgen エイリアスは、コア mapgen に情報を提供し、次の形式で登録できます。</p> <pre><code class="Lua">minetest.register_alias("mapgen_stone", "base:smoke_stone") </code></pre> <p>少なくとも、登録する必要があります。</p> <ul> <li>mapgen_stone</li> <li>mapgen_water_source</li> <li>mapgen_river_water_source</li> </ul> <p>すべてのバイオームに対して洞窟液体ノード( cave liquid nodes )を定義していない場合は、以下も登録する必要があります。</p> <ul> <li>mapgen_lava_source</li> </ul> <h2 id="19 - Lua Voxel Manipulators &lt;a name=&quot;lvm&quot;&gt;&lt;/a&gt;"><a href="#19+-+Lua+Voxel+Manipulators+%26lt%3Ba+name%3D%26quot%3Blvm%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">19 - Lua Voxel Manipulators <a name="lvm"></a></a></h2> <h3 id="Introduction"><a href="#Introduction">Introduction</a></h3> <p><a href="#environment">Basic Map Operation</a>の章で概説されている関数は、便利で使いやすいですが、広い領域では非効率的です。 <code>set_node</code> または <code>get_node</code> を呼び出すたびに、mod はエンジンと通信する必要があります。これにより、エンジンと mod の間で一定の個別のコピー操作が発生し、速度が低下し、ゲームのパフォーマンスが急速に低下します。 Lua ボクセルマニピュレーター( LVM )を使用することはより良い代替手段です。</p> <ul> <li><a href="#concepts">Concepts</a></li> <li><a href="#reading-into-the-lvm">Reading into the LVM</a></li> <li><a href="#reading-nodes">Reading Nodes</a></li> <li><a href="#writing-nodes">Writing Nodes</a></li> <li><a href="#example">Example</a></li> <li><a href="#your-turn">Your Turn</a></li> </ul> <h3 id="Concepts &lt;a name=&quot;#concepts&quot;&gt;&lt;/a&gt;"><a href="#Concepts+%26lt%3Ba+name%3D%26quot%3B%23concepts%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Concepts <a name="#concepts"></a></a></h3> <p>LVM を使用すると、マップの広い領域を mod のメモリにロードできます。その後、エンジンとの対話やコールバックを実行せずに、このデータの読み取りと書き込みを行うことができます。つまり、これらの操作は非常に高速です。完了したら、その領域をエンジンに書き戻し、照明の計算を実行できます。</p> <h3 id="Reading into the LVM &lt;a name=&quot;#reading-into-the-lvm&quot;&gt;&lt;/a&gt;"><a href="#Reading+into+the+LVM+%26lt%3Ba+name%3D%26quot%3B%23reading-into-the-lvm%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Reading into the LVM <a name="#reading-into-the-lvm"></a></a></h3> <p>LVM にロードできるのは立方体領域のみであるため、変更する必要のある最小位置と最大位置を計算する必要があります。次に、LVM を作成して読み込むことができます。例えば:</p> <pre><code class="Lua">local vm = minetest.get_voxel_manip() local emin, emax = vm:read_from_map(pos1, pos2) </code></pre> <p>パフォーマンス上の理由から、LVM は指示された正確な領域を読み取ることはほとんどありません。代わりに、より広い領域を読み取る可能性があります。より大きな領域は <code>emin</code> と <code>emax</code> で与えられ、これらは <em>emerged min pos</em> と <em>emerged max pos</em> を表しています。 LVM は、メモリからのロード、ディスクからのロード、マップジェネレーターの呼び出しなど、LVM に含まれる領域をロードします。</p> <div class="table-responsive"><table> <thead> <tr> <th>⚠ LVMとMapgen</th> </tr> </thead> <tbody> <tr> <td>グリッチを引き起こす可能性があるため、 mapgen で <code>minetest.get_voxel_manip()</code> を使用しないでください。代わりに <code>minetest.get_mapgen_object("voxelmanip")</code>を使用してください。</td> </tr> </tbody> </table></div> <h3 id="Reading Nodes &lt;a name=&quot;#reading-nodes&quot;&gt;&lt;/a&gt;"><a href="#Reading+Nodes+%26lt%3Ba+name%3D%26quot%3B%23reading-nodes%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Reading Nodes <a name="#reading-nodes"></a></a></h3> <p>特定の位置にあるノードのタイプを読み取るには、 <code>get_data()</code> を使用する必要があります。これは、各エントリが特定のノードのタイプを表すフラット配列を返します。</p> <pre><code class="Lua">local data = vm:get_data() </code></pre> <p>メソッド <code>get_light_data()</code> と <code>get_param2_data()</code> を使用して、 param2 とライティングデータを取得できます。</p> <p>上記のメソッドで指定されたフラット配列のどこにノードがあるかを調べるには、 <code>emin</code> と <code>emax</code> を使用する必要があります。計算を処理する <code>VoxelArea</code> というヘルパークラスがあります。</p> <pre><code class="Lua">local a = VoxelArea:new{ MinEdge = emin, MaxEdge = emax } -- Get node's index local idx = a:index(x, y, z) -- Read node print(data[idx]) </code></pre> <p>これを実行すると、 <code>data [vi]</code> が整数であることがわかります。これは、パフォーマンス上の理由から、エンジンが文字列を使用してノードを保存しないためです。代わりに、エンジンはコンテンツ ID と呼ばれる整数を使用します。 <code>get_content_id()</code> を使用して、特定のタイプのノードのコンテンツ ID を確認できます。例えば:</p> <pre><code class="Lua">local c_stone = minetest.get_content_id("default:stone") </code></pre> <p>次に、ノードが石であるかどうかを確認できます。</p> <pre><code class="Lua">local idx = a:index(x, y, z) if data[idx] == c_stone then print("is stone!") end </code></pre> <p>ノードタイプの ID は変更されないため、ロード時にノードタイプのコンテンツ ID を見つけて保存することをお勧めします。パフォーマンス上の理由から、 ID は必ずローカル変数に格納してください。</p> <p>LVM データ配列内のノードは逆座標の順序で格納されるため、常に <code>z, y, x</code> の順序で反復する必要があります。例えば:</p> <pre><code class="Lua">for z = min.z, max.z do for y = min.y, max.y do for x = min.x, max.x do -- vi, voxel index, is a common variable name here local vi = a:index(x, y, z) if data[vi] == c_stone then print("is stone!") end end end end </code></pre> <p>この理由は、コンピュータアーキテクチャのトピックに触れています。 RAM からの読み取りはかなりコストがかかるため、CPU には複数レベルのキャッシュがあります。プロセスが要求するデータがキャッシュにある場合、プロセスはそれを非常に迅速に取得できます。データがキャッシュにない場合、キャッシュミスが発生し、 RAM から必要なデータをフェッチします。要求されたデータを取り巻くデータもフェッチされ、キャッシュ内のデータが置き換えられます。これは、プロセスがその場所の近くのデータを再度要求する可能性が非常に高いためです。つまり、最適化の適切なルールは、データを次々に読み取る方法で反復し、<em>キャッシュスラッシング</em>を回避することです。</p> <h3 id="Writing Nodes &lt;a name=&quot;#writing-nodes&quot;&gt;&lt;/a&gt;"><a href="#Writing+Nodes+%26lt%3Ba+name%3D%26quot%3B%23writing-nodes%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Writing Nodes <a name="#writing-nodes"></a></a></h3> <p>まず、データ配列に新しいコンテンツIDを設定する必要があります。</p> <pre><code class="Lua">for z = min.z, max.z do for y = min.y, max.y do for x = min.x, max.x do local vi = a:index(x, y, z) if data[vi] == c_stone then data[vi] = c_air end end end end </code></pre> <p>LVM でノードの設定が完了したら、データ配列をエンジンにアップロードする必要があります。</p> <pre><code class="Lua">vm:set_data(data) vm:write_to_map(true) </code></pre> <p>ライティングと param2 データを設定するには、適切な名前の <code>set_light_data()</code> メソッドと <code>set_param2_data()</code> メソッドを使用します。</p> <p><code>write_to_map()</code> はブール値を取ります。これは、照明を計算する場合に当てはまります。 false を渡した場合は、後で <code>minetest.fix_light</code> を使用して照明を再計算する必要があります。</p> <h3 id="Example &lt;a name=&quot;#example&quot;&gt;&lt;/a&gt;"><a href="#Example+%26lt%3Ba+name%3D%26quot%3B%23example%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Example <a name="#example"></a></a></h3> <pre><code class="Lua">-- Get content IDs during load time, and store into a local local c_dirt = minetest.get_content_id("default:dirt") local c_grass = minetest.get_content_id("default:dirt_with_grass") local function grass_to_dirt(pos1, pos2) -- Read data into LVM local vm = minetest.get_voxel_manip() local emin, emax = vm:read_from_map(pos1, pos2) local a = VoxelArea:new{ MinEdge = emin, MaxEdge = emax } local data = vm:get_data() -- Modify data for z = pos1.z, pos2.z do for y = pos1.y, pos2.y do for x = pos1.x, pos2.x do local vi = a:index(x, y, z) if data[vi] == c_grass then data[vi] = c_dirt end end end end -- Write data vm:set_data(data) vm:write_to_map(true) end </code></pre> <h3 id="Your Turn"><a href="#Your+Turn">Your Turn</a></h3> <ul> <li><code>replace_in_area(from, to, pos1, pos2)</code> を作成します。これにより、指定された領域で <code>from</code> のすべてのインスタンスが <code>to</code> に置き換えられます。ここで、<code>from</code> と <code>to</code> はノード名です。</li> <li>すべての胸節を90°回転させる関数を作成します。</li> <li>LVM を使用して、苔むした丸石を近くの石や丸石のノードに広げる関数を作成します。あなたの実装は苔むした丸石を毎回 1 ノード分の距離以上に広げる原因になりますか?もしそうなら、どうすればこれを止めることができますか?</li> </ul> <h2 id="20 - Creating Games &lt;a name=&quot;games&quot;&gt;&lt;/a&gt;"><a href="#20+-+Creating+Games+%26lt%3Ba+name%3D%26quot%3Bgames%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">20 - Creating Games <a name="games"></a></a></h2> <h3 id="Introduction"><a href="#Introduction">Introduction</a></h3> <p>Minetest の力は、独自のボクセルグラフィックス、ボクセルアルゴリズム、または派手なネットワークコードを作成しなくても、ゲームを簡単に開発できることです。</p> <ul> <li><a href="#what-is-a-game">What is a Game?</a></li> <li><a href="#game-directory">Game Directory</a></li> <li>Inter-game Compatibility <ul> <li><a href="#api-compatibility">API Compatibility</a></li> <li><a href="#groups-and-aliases">Groups and Aliases</a></li> </ul></li> <li><a href="#your-turn">Your Turn</a></li> </ul> <h3 id="What is a Game? &lt;a name=&quot;&quot;&gt;&lt;/a&gt;"><a href="#What+is+a+Game%3F+%26lt%3Ba+name%3D%26quot%3B%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">What is a Game? <a name=""></a></a></h3> <p>ゲームは、連携してまとまりのあるゲームを作成する mod のコレクションです。優れたゲームには、一貫した基本的なテーマと方向性があります。たとえば、サバイバル要素が難しい古典的なクラフターマイナーの場合もあれば、スチームパンクな自動化の美学を備えたスペースシミュレーションゲームの場合もあります。</p> <p>ゲームデザインは複雑なトピックであり、実際には専門分野全体です。簡単に触れるだけでも、本の範囲を超えています。<br /> Game design is a complex topic and is actually a whole field of expertise. It’s beyond the scope of the book to more than briefly touch on it.</p> <h3 id="Game Directory &lt;a name=&quot;&quot;&gt;&lt;/a&gt;"><a href="#Game+Directory+%26lt%3Ba+name%3D%26quot%3B%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Game Directory <a name=""></a></a></h3> <p>ゲームの構造とロケーションは、mod を使用した後はかなり馴染みがあるように見えます。ゲームは、 <code>minetest/games/foo_game</code> などのゲームのロケーションにあります。</p> <pre><code>foo_game ├── game.conf ├── menu │ ├── header.png │ ├── background.png │ └── icon.png ├── minetest.conf ├── mods │ └── ... mods ├── README.txt └── settingtypes.txt </code></pre> <p>必要なのは mods フォルダーだけですが、 <code>game.conf</code> と <code>menu/icon.png</code> をお勧めします。</p> <h3 id="Inter-game Compatibility &lt;a name=&quot;&quot;&gt;&lt;/a&gt;"><a href="#Inter-game+Compatibility+%26lt%3Ba+name%3D%26quot%3B%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Inter-game Compatibility <a name=""></a></a></h3> <h4 id="API Compatibility &lt;a name=&quot;&quot;&gt;&lt;/a&gt;"><a href="#API+Compatibility+%26lt%3Ba+name%3D%26quot%3B%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">API Compatibility <a name=""></a></a></h4> <p>mod と別のゲームへの移植がより簡単になるため、 Minetest Game との API の互換性をできるだけ便利に保つようにすることをお勧めします。</p> <p>別のゲームとの互換性を維持するための最良の方法は、同じ名前の mod との API の互換性を維持することです。つまり、 mod が別の mod と同じ名前を使用している場合、サードパーティであっても、互換性のある API が必要です。たとえば、ゲームに「 doors 」という mod が含まれている場合、 Minetest Game の「 doors 」と同じ API が必要です。</p> <p>mod の API 互換性は、次の合計です。</p> <ul> <li>Lua API テーブル - 同じ名前を共有するグローバルテーブル内のすべての文書化/アドバタイズされた関数。たとえば、 <code>mobs.register_mob</code> です。</li> <li>登録されたノード/アイテム - アイテムの存在。</li> </ul> <p>小さな破損は、実際には内部でのみ使用されるランダムなユーティリティ関数がないなど、それほど問題ありませんが、コア機能に関連する大きな破損はとてもよくないです。</p> <p>Minetest Game の <em>default</em> のような嫌なメガ God-mod との API 互換性を維持することは困難です。その場合、ゲームに default という名前の mod を含めるべきではありません。</p> <p>API の互換性は、他のサードパーティの mod やゲームにも適用されるため、新しい mod には一意の mod 名が付いていることを確認してください。 mod 名が使用されているかどうかを確認するには、<a target="_blank" rel="nofollow noopener" href="https://content.minetest.net/">content.minetest.net</a>で mod 名を検索してください。</p> <h4 id="Groups and Aliases &lt;a name=&quot;&quot;&gt;&lt;/a&gt;"><a href="#Groups+and+Aliases+%26lt%3Ba+name%3D%26quot%3B%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Groups and Aliases <a name=""></a></a></h4> <p>グループとエイリアスはどちらも、ゲーム間の互換性を維持するのに役立つツールです。これにより、ゲームごとにアイテム名を変えることができます。石や木のような一般的なノードには、材料を示すグループが必要です。デフォルトノードから直接置換するエイリアスを提供することもお勧めします。</p> <h3 id="Your Turn"><a href="#Your+Turn">Your Turn</a></h3> <ul> <li>プレイヤーが特別なブロックを掘ることでポイントを獲得する簡単なゲームを作成します。</li> </ul> <h2 id="21 - Common Mistakes&lt;A name=&quot;common_mistakes&quot;&gt;&lt;/a&gt;"><a href="#21+-+Common+Mistakes%26lt%3BA+name%3D%26quot%3Bcommon_mistakes%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">21 - Common Mistakes<A name="common_mistakes"></a></a></h2> <h3 id="Introduction"><a href="#Introduction">Introduction</a></h3> <p>この章では、よくある間違いとその回避方法について詳しく説明します。</p> <ul> <li><a href="#never-store-objectrefs-ie-players-or-entities">Never Store ObjectRefs (ie: players or entities)</a></li> <li><a href="#dont-trust-formspec-submissions">Don’t Trust Formspec Submissions</a></li> <li><a href="#set-itemstacks-after-changing-them">Set ItemStacks After Changing Them</a></li> </ul> <h3 id="Never Store ObjectRefs (ie: players or entities) &lt;a name=&quot;#never-store-objectrefs-ie-players-or-entities&quot;&gt;&lt;/a&gt;"><a href="#Never+Store+ObjectRefs+%28ie%3A+players+or+entities%29+%26lt%3Ba+name%3D%26quot%3B%23never-store-objectrefs-ie-players-or-entities%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Never Store ObjectRefs (ie: players or entities) <a name="#never-store-objectrefs-ie-players-or-entities"></a></a></h3> <p>ObjectRef が表すオブジェクトが削除された場合(たとえば、プレーヤーがオフラインになったり、エンティティがアンロードされたりした場合)、そのオブジェクトのメソッドを呼び出すとクラッシュします。</p> <p>たとえば、こうしないでください。</p> <pre><code class="Lua">minetest.register_on_joinplayer(function(player) local function func() local pos = player:get_pos() -- BAD! -- `player` is stored then accessed later. -- If the player leaves in that second, the server *will* crash. end minetest.after(1, func) foobar[player:get_player_name()] = player -- RISKY -- It's not recommended to do this. -- Use minetest.get_connected_players() and -- minetest.get_player_by_name() instead. end) </code></pre> <p>代わりにこうしてください:</p> <pre><code class="Lua">minetest.register_on_joinplayer(function(player) local function func(name) -- Attempt to get the ref again local player = minetest.get_player_by_name(name) -- Check that the player is still online if player then -- Yay! This is fine local pos = player:get_pos() end end -- Pass the name into the function minetest.after(1, func, player:get_player_name()) end) </code></pre> <h3 id="Don’t Trust Formspec Submissions &lt;a name=&quot;#dont-trust-formspec-submissions&quot;&gt;&lt;/a&gt;"><a href="#Don%E2%80%99t+Trust+Formspec+Submissions+%26lt%3Ba+name%3D%26quot%3B%23dont-trust-formspec-submissions%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Don’t Trust Formspec Submissions <a name="#dont-trust-formspec-submissions"></a></a></h3> <p>悪意のあるクライアントは、好きなときに好きなコンテンツでフォームスペックを送信できます。</p> <p>たとえば、次のコードには、プレーヤーが自分自身にモデレーター特権を与えることができる脆弱性があります。</p> <pre><code class="Lua">local function show_formspec(name) if not minetest.check_player_privs(name, { privs = true }) then return false end minetest.show_formspec(name, "modman:modman", [[ size[3,2] field[0,0;3,1;target;Name;] button_exit[0,1;3,1;sub;Promote] ]]) return true }) minetest.register_on_player_receive_fields(function(player, formname, fields) -- BAD! Missing privilege check here! local privs = minetest.get_player_privs(fields.target) privs.kick = true privs.ban = true minetest.set_player_privs(fields.target, privs) return true end) </code></pre> <p>これを解決するために特権チェックを追加します。</p> <pre><code class="Lua">minetest.register_on_player_receive_fields(function(player, formname, fields) if not minetest.check_player_privs(name, { privs = true }) then return false end -- code end) </code></pre> <h3 id="Set ItemStacks After Changing Them &lt;a name=&quot;#set-itemstacks-after-changing-them&quot;&gt;&lt;/a&gt;"><a href="#Set+ItemStacks+After+Changing+Them+%26lt%3Ba+name%3D%26quot%3B%23set-itemstacks-after-changing-them%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Set ItemStacks After Changing Them <a name="#set-itemstacks-after-changing-them"></a></a></h3> <p>「 InvRef 」のように、「 ItemStackRef 」ではなく、API では単に「 ItemStack 」と呼ばれていることに気づきましたか?これは、 <code>ItemStack</code> が参照ではなくコピーであるためです。スタックは、インベントリ内のスタックではなく、データのコピーで機能します。つまり、スタックを変更しても、インベントリ内のそのスタックは実際には変更されません。</p> <p>たとえば、これを行わないでください。</p> <pre><code class="Lua">local inv = player:get_inventory() local stack = inv:get_stack("main", 1) stack:get_meta():set_string("description", "Partially eaten") -- BAD! Modification will be lost </code></pre> <p>代わりにこれを行ってください:</p> <pre><code class="Lua">local inv = player:get_inventory() local stack = inv:get_stack("main", 1) stack:get_meta():set_string("description", "Partially eaten") inv:set_stack("main", 1, stack) -- Correct! Item stack is set </code></pre> <p>コールバックの動作は少し複雑です。与えられた <code>ItemStack</code> を変更すると、呼び出し元とその後のコールバックでも変更されます。ただし、コールバックの呼び出し元が設定した場合にのみ、エンジンに保存されます。</p> <pre><code class="Lua">minetest.register_on_item_eat(function(hp_change, replace_with_item, itemstack, user, pointed_thing) itemstack:get_meta():set_string("description", "Partially eaten") -- Almost correct! Data will be lost if another -- callback cancels the behaviour end) </code></pre> <p>コールバックがこれをキャンセルしない場合、スタックが設定され、説明が更新されますが、コールバックがこれをキャンセルすると、更新が失われる可能性があります。</p> <p>代わりにこれを行うことをお勧めします。</p> <pre><code class="Lua">minetest.register_on_item_eat(function(hp_change, replace_with_item, itemstack, user, pointed_thing) itemstack:get_meta():set_string("description", "Partially eaten") user:get_inventory():set_stack("main", user:get_wield_index(), itemstack) -- Correct, description will always be set! end) </code></pre> <p>コールバックがキャンセルされるか、コールバックランナーがスタックを設定しない場合でも、更新は設定されます。コールバックまたはコールバックランナーがスタックを設定する場合、 set_stack の使用は重要ではありません。</p> <h2 id="22 - Automatic Error Checking &lt;a name=&quot;luacheck&quot;&gt;&lt;/a&gt;"><a href="#22+-+Automatic+Error+Checking+%26lt%3Ba+name%3D%26quot%3Bluacheck%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">22 - Automatic Error Checking <a name="luacheck"></a></a></h2> <h3 id="Introduction"><a href="#Introduction">Introduction</a></h3> <p>この章では、LuaCheck と呼ばれるツールを使用して、間違いがないか mod を自動的にスキャンする方法を学習します。このツールをエディターと組み合わせて使用すると、間違いを警告できます。</p> <ul> <li>Installing LuaCheck <ul> <li><a href="#windows">Windows</a></li> <li><a href="#linux">Linux</a></li> </ul></li> <li><a href="#running-luacheck">Running LuaCheck</a></li> <li>Configuring LuaCheck <ul> <li><a href="#troubleshooting">Troubleshooting</a></li> </ul></li> <li><a href="#using-with-editor">Using with editor</a></li> <li><a href="#checking-commits-with-travis">Checking Commits with Travis</a></li> </ul> <h3 id="Installing LuaCheck"><a href="#Installing+LuaCheck">Installing LuaCheck</a></h3> <h4 id="Windows &lt;a name=&quot;#windows&quot;&gt;&lt;/a&gt;"><a href="#Windows+%26lt%3Ba+name%3D%26quot%3B%23windows%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Windows <a name="#windows"></a></a></h4> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/mpeterv/luacheck/releases">Githubリリースページ</a>から luacheck.exe をダウンロードするだけです。</p> <h4 id="Linux &lt;a name=&quot;#linux&quot;&gt;&lt;/a&gt;"><a href="#Linux+%26lt%3Ba+name%3D%26quot%3B%23linux%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Linux <a name="#linux"></a></a></h4> <p>まず、LuaRocks をインストールする必要があります。</p> <pre><code class="console">sudo apt install luarocks </code></pre> <p>その後、LuaCheck をグローバルにインストールできます。</p> <pre><code class="console">sudo luarocks install luacheck </code></pre> <p>次のコマンドでインストールされていることを確認します。</p> <pre><code class="console">luacheck -v </code></pre> <h3 id="Running LuaCheck &lt;a name=&quot;#running-luacheck&quot;&gt;&lt;/a&gt;"><a href="#Running+LuaCheck+%26lt%3Ba+name%3D%26quot%3B%23running-luacheck%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Running LuaCheck <a name="#running-luacheck"></a></a></h3> <p>LuaCheck を初めて実行すると、多くの誤ったエラーが発生する可能性があります。これは、まだ構成する必要があるためです。</p> <p>Windows では、プロジェクトのルートフォルダーにある powershell または bash を開き、 <code>path\to\luacheck.exe .</code> を実行します。</p> <p>Linux では、プロジェクトのルートフォルダで <code>luacheck.</code> を実行します。</p> <h3 id="Configuring LuaCheck"><a href="#Configuring+LuaCheck">Configuring LuaCheck</a></h3> <p>プロジェクトのルートに .luacheckrc というファイルを作成します。これは、ゲーム、 modpack 、または mod のルートである可能性があります。</p> <p>その中に次の内容を入れてください:</p> <pre><code class="Lua">unused_args = false allow_defined_top = true globals = { "minetest", } read_globals = { string = {fields = {"split"<span>}</span><span>}</span>, table = {fields = {"copy", "getn"<span>}</span><span>}</span>, -- Builtin "vector", "ItemStack", "dump", "DIR_DELIM", "VoxelArea", "Settings", -- MTG "default", "sfinv", "creative", } </code></pre> <p>次に、 LuaCheck を実行して動作することをテストする必要があります。今回はエラーが大幅に少なくなるはずです。発生した最初のエラーから始めて、コードを変更して問題を削除するか、コードが正しい場合は構成を変更します。以下のリストを参照してください。</p> <h4 id="Troubleshooting &lt;a name=&quot;#troubleshooting&quot;&gt;&lt;/a&gt;"><a href="#Troubleshooting+%26lt%3Ba+name%3D%26quot%3B%23troubleshooting%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Troubleshooting <a name="#troubleshooting"></a></a></h4> <ul> <li>** 未定義の変数foobar ** へのアクセス - <code>foobar</code> がグローバルであることが意図されている場合は、それを <code>read_globals</code> に追加します。それ以外の場合は、不足している <code>local</code> を mod に追加します。</li> <li>** 非標準のグローバル変数 foobar ** の設定 - <code>foobar</code> がグローバルであることを意図している場合は、それを <code>globals</code> に追加します。存在する場合は、 <code>read_globals</code> から削除します。それ以外の場合は、不足している <code>local</code> を mod に追加します。</li> <li>** 読み取り専用グローバル変数foobar ** の変更 - <code>foobar</code> を<code>read_globals</code> から <code>globals</code>に移動するか、 foobar への書き込みを停止します。</li> </ul> <h3 id="Using with editor &lt;a name=&quot;#using-with-editor&quot;&gt;&lt;/a&gt;"><a href="#Using+with+editor+%26lt%3Ba+name%3D%26quot%3B%23using-with-editor%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Using with editor <a name="#using-with-editor"></a></a></h3> <p>コマンドを実行せずにエラーを表示するために、選択したエディターのプラグインを見つけてインストールすることを強くお勧めします。ほとんどのエディターは、プラグインを利用できる可能性があります。</p> <ul> <li><strong>Atom</strong> - <code>linter-luacheck</code>。</li> <li><strong>VSCode</strong> - Ctrl + P、次に貼り付けます: <code>ext install dwenegar.vscode-luacheck</code></li> <li><strong>Sublime</strong> - package-control を使用してインストール:<a target="_blank" rel="nofollow noopener" href="https://github.com/SublimeLinter/SublimeLinter">SublimeLinter</a>、<a target="_blank" rel="nofollow noopener" href="https://github.com/SublimeLinter/SublimeLinter-luacheck">SublimeLinter-luacheck</a> 。</li> </ul> <h3 id="Checking Commits with Travis &lt;a name=&quot;#checking-commits-with-travis&quot;&gt;&lt;/a&gt;"><a href="#Checking+Commits+with+Travis+%26lt%3Ba+name%3D%26quot%3B%23checking-commits-with-travis%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Checking Commits with Travis <a name="#checking-commits-with-travis"></a></a></h3> <p>プロジェクトが公開されており、 Github 上にある場合は、 TravisCI を使用できます。これは無料のサービスで、コミット時にジョブを実行してチェックします。これは、プッシュするすべてのコミットが LuaCheck に対してチェックされ、 LuaCheck が間違いを検出したかどうかに応じて、それらの横に緑色のチェックマークまたは赤い十字が表示されることを意味します。これは、プロジェクトがプルリクエストを受信した場合に特に役立ちます。コードをダウンロードしなくても、 LuaCheck の出力を確認できます。</p> <p>まず、<a target="_blank" rel="nofollow noopener" href="https://travis-ci.org/">travis-ci.org</a>にアクセスし、 Github アカウントでサインインする必要があります。次に、 Travis プロファイルでプロジェクトのリポジトリを見つけ、スイッチを切り替えて Travis を有効にします。</p> <p>次に、次の内容の .travis.yml というファイルを作成します。</p> <pre><code class="yml">language: generic sudo: false addons: apt: packages: - luarocks before_install: - luarocks install --local luacheck script: - $HOME/.luarocks/bin/luacheck . notifications: email: false </code></pre> <p>プロジェクトが mod や modpack ではなくゲームの場合は、 <code>script:</code> の後の行を次のように変更します。</p> <pre><code>- $HOME/.luarocks/bin/luacheck mods/ </code></pre> <p>次に、コミットして Github にプッシュします。 Github でプロジェクトのページに移動し、[コミット]をクリックします。行ったコミットの横にオレンジ色のディスクが表示されます。しばらくすると、 LuaCheck の結果に応じて、緑色のチェックマークまたは赤い十字のいずれかに変わるはずです。いずれの場合も、アイコンをクリックして、ビルドログと LuaCheck の出力を確認できます。</p> <h2 id="23 - Security &lt;a name=&quot;security&quot;&gt;&lt;/a&gt;"><a href="#23+-+Security+%26lt%3Ba+name%3D%26quot%3Bsecurity%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">23 - Security <a name="security"></a></a></h2> <h3 id="Introduction"><a href="#Introduction">Introduction</a></h3> <p>mod によってサーバーの所有者がデータや制御を失うことがないようにするためには、セキュリティが非常に重要です。</p> <ul> <li><a href="#core-concepts">Core Concepts</a></li> <li>Formspecs <ul> <li><a href="#never-trust-submissions">Never Trust Submissions</a></li> <li><a href="#time-of-check-isnt-time-of-use">Time of Check isn’t Time of Use</a></li> </ul></li> <li><a href="#insecure-environments">(Insecure) Environments</a></li> </ul> <h3 id="Core Concepts &lt;a name=&quot;#core-concepts&quot;&gt;&lt;/a&gt;"><a href="#Core+Concepts+%26lt%3Ba+name%3D%26quot%3B%23core-concepts%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Core Concepts <a name="#core-concepts"></a></a></h3> <p>セキュリティの最も重要な概念は、<strong>ユーザーを決して信頼しない</strong> ことです。ユーザーが送信するものはすべて悪意のあるものとして扱われる必要があります。つまり、入力した情報が有効であること、ユーザーが正しい権限を持っていること、その他の方法でそのアクションを実行できること(つまり、レンジ内または所有者)を常に確認する必要があります。</p> <p>悪意のあるアクションは必ずしもデータの変更や破壊ではありませんが、パスワードハッシュやプライベートメッセージなどの機密データにアクセスする可能性があります。サーバーが電子メールや年齢などの情報を保存している場合、これは特に悪いことです。これは検証目的で行われる場合があります。</p> <h3 id="Formspecs"><a href="#Formspecs">Formspecs</a></h3> <h4 id="Never Trust Submissions &lt;a name=&quot;#never-trust-submissions&quot;&gt;&lt;/a&gt;"><a href="#Never+Trust+Submissions+%26lt%3Ba+name%3D%26quot%3B%23never-trust-submissions%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Never Trust Submissions <a name="#never-trust-submissions"></a></a></h4> <p>すべてのユーザーは、いつでも任意の値でほぼすべてのフォームスペックを送信できます。</p> <p>mod で見つかった実際のコードは次のとおりです。</p> <pre><code class="Lua">minetest.register_on_player_receive_fields(function(player, formname, fields) for key, field in pairs(fields) do local x,y,z = string.match(key, "goto_([%d-]+)_([%d-]+)_([%d-]+)") if x and y and z then player:set_pos({ x=tonumber(x), y=tonumber(y), z=tonumber(z) }) return true end end end </code></pre> <p>問題を見つけることができますか?悪意のあるユーザーは、自分の位置の値を含む formspec を送信して、好きな場所にテレポートできるようにする可能性があります。これは、クライアントの変更を使用して自動化することもでき、特権を必要とせずに「/teleport」コマンドを本質的に複製できます。</p> <p>この種の問題の解決策は、 Formspecs の章で前述したように、<a href="#contexts">Context</a>を使用することです。</p> <h4 id="Time of Check isn’t Time of Use &lt;a name=&quot;#time-of-check-isnt-time-of-use&quot;&gt;&lt;/a&gt;"><a href="#Time+of+Check+isn%E2%80%99t+Time+of+Use+%26lt%3Ba+name%3D%26quot%3B%23time-of-check-isnt-time-of-use%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Time of Check isn’t Time of Use <a name="#time-of-check-isnt-time-of-use"></a></a></h4> <p>エンジンで禁止されている場合を除き、すべてのユーザーはいつでも任意の値で任意の formspec を送信できます。</p> <ul> <li>ユーザーが離れすぎている場合、ノード formspec の送信はブロックされます。</li> <li>5.0 以降、名前付き formspec は、まだ表示されていない場合はブロックされます。</li> </ul> <p>これは、ユーザーが最初に formspec を表示するための条件と、対応するアクションを満たしていることをハンドラーで確認する必要があることを意味します。</p> <p>handle formspec ではなく showformspec でアクセス許可をチェックすることによって引き起こされる脆弱性は、Time Of Check is not Time Of Use (TOCTOU) と呼ばれます。</p> <h3 id="(Insecure) Environments &lt;a name=&quot;#insecure-environments&quot;&gt;&lt;/a&gt;"><a href="#%28Insecure%29+Environments+%26lt%3Ba+name%3D%26quot%3B%23insecure-environments%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">(Insecure) Environments <a name="#insecure-environments"></a></a></h3> <p>Minetest を使用すると、 mod はサンドボックス化されていない環境を要求でき、 Lua API 全体にアクセスできます。</p> <p>次の脆弱性を見つけることができますか?</p> <pre><code class="Lua">local ie = minetest.request_insecure_environment() ie.os.execute(("path/to/prog %d"):format(3)) </code></pre> <p><code>string.format</code> は、グローバル共有テーブル <code>string</code> の関数です。悪意のある mod が関数をオーバーライドし、 os.execute にデータを渡す可能性があります。</p> <pre><code class="Lua">string.format = function() return "xdg-open 'http://example.com'" end </code></pre> <p>mod は、リモートユーザーにマシンの制御を与えるなど、Web サイトを開くよりもはるかに悪意のあるものを渡す可能性があります。</p> <p>安全でない環境を使用するためのいくつかのルール:</p> <ul> <li>常にローカルに保存し、関数に渡さないでください。</li> <li>上記の問題を回避するために、安全でない関数に与えられた入力を信頼できることを確認してください。これは、グローバルに再定義可能な関数を回避することを意味します。</li> </ul> <h2 id="24 - Intro to Clean Architectures &lt;a name=&quot;clean_arch&quot;&gt;&lt;/a&gt;"><a href="#24+-+Intro+to+Clean+Architectures+%26lt%3Ba+name%3D%26quot%3Bclean_arch%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">24 - Intro to Clean Architectures <a name="clean_arch"></a></a></h2> <h3 id="Introduction"><a href="#Introduction">Introduction</a></h3> <p>mod が適切なサイズに達すると、コードをクリーンでバグのない状態に保つことがますます難しくなります。これは、Lua のような動的に型付けされた言語を使用する場合に特に大きな問題です。これは、型が正しく使用されていることを確認する場合など、コンパイラーがコンパイラー時のヘルプをほとんど提供しないためです。</p> <p>この章では、コードをクリーンに保つために必要な重要な概念と、それを実現するための一般的なデザインパターンについて説明します。この章は規範的なものではなく、可能性についてのアイデアを提供することを目的としていることに注意してください。 mod を設計する良い方法は1つではなく、良い mod の設計は非常に主観的です。</p> <ul> <li><a href="#cohesion-coupling-and-separation-of-concerns">Cohesion, Coupling, and Separation of Concerns</a></li> <li><a href="#observer">Observer</a></li> <li>Model-View-Controller <ul> <li><a href="#api-view">API-View</a></li> </ul></li> <li><a href="#conclusion">Conclusion</a></li> </ul> <h3 id="Cohesion, Coupling, and Separation of Concerns &lt;a name=&quot;#cohesion-coupling-and-separation-of-concerns&quot;&gt;&lt;/a&gt;"><a href="#Cohesion%2C+Coupling%2C+and+Separation+of+Concerns+%26lt%3Ba+name%3D%26quot%3B%23cohesion-coupling-and-separation-of-concerns%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Cohesion, Coupling, and Separation of Concerns <a name="#cohesion-coupling-and-separation-of-concerns"></a></a></h3> <p>計画がなければ、プログラミングプロジェクトは徐々にスパゲッティコードに陥る傾向があります。スパゲッティコードは構造の欠如を特徴としています-すべてのコードは明確な境界なしで一緒にスローされます。これにより、最終的にプロジェクトは完全に保守不可能になり、放棄されてしまいます。</p> <p>これの反対は、相互作用する小さなプログラムまたはコードの領域のコレクションとしてプロジェクトを設計することです。</p> <blockquote> <p>すべての大きなプログラムの中には、抜け出そうとする小さなプログラムがあります。</p> <p>–C.A.R. Hoare</p> </blockquote> <p>これは、関心の分離を達成するような方法で行う必要があります。各領域は別個のものであり、個別のニーズまたは懸念に対処する必要があります。<br /> This should be done in such a way that you achieve Separation of Concerns - each area should be distinct and address a separate need or concern.</p> <p>これらのプログラム/エリアには、次の 2 つのプロパティが必要です。</p> <ul> <li><strong>高凝集性</strong> - 領域は密接に/密接に関連している必要があります。</li> <li><p><strong>低結合</strong> - 領域間の依存関係を可能な限り低く保ち、内部実装に依存しないようにします。カップリングの量が少ないことを確認することをお勧めします。これは、特定の領域の API を変更することがより実現可能になることを意味します。</p></li> <li><p><strong>High Cohesion</strong> - the area should be closely/tightly related.</p></li> <li><strong>Low Coupling</strong> - keep dependencies between areas as low as possible, and avoid relying on internal implementations. It’s a very good idea to make sure you have a low amount of coupling, as this means that changing the APIs of certain areas will be more feasible.</li> </ul> <p>これらは、mod 間の関係と、mod 内の領域間の関係の両方に当てはまることに注意してください。</p> <h3 id="Observer &lt;a name=&quot;#observer&quot;&gt;&lt;/a&gt;"><a href="#Observer+%26lt%3Ba+name%3D%26quot%3B%23observer%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Observer <a name="#observer"></a></a></h3> <p>コードのさまざまな領域を分離する簡単な方法は、 Observer パターンを使用することです。</p> <p>プレイヤーが最初に珍しい動物を殺したときにアチーブメントのロックを解除する例を見てみましょう。ナイーブなアプローチは、 mob kill 関数にアチーブメントコードを入れ、 mob 名をチェックし、一致する場合は賞のロックを解除することです。ただし、これは mobs mod をアチーブメントコードに結合させるため、悪い考えです。これを続けた場合(たとえば、mob death コードに XP を追加した場合)、多くの厄介な依存関係が発生する可能性があります。</p> <p>オブザーバーパターンを入力します。賞を気にする mymobsmod の代わりに、 mymobs mod は、コードの他の領域がイベントへの関心を登録し、イベントに関するデータを受信する方法を公開します。</p> <pre><code class="Lua">mymobs.registered_on_death = {} function mymobs.register_on_death(func) table.insert(mymobs.registered_on_death, func) end -- in mob death code for i=1, #mymobs.registered_on_death do mymobs.registered_on_death[i](entity, reason) end </code></pre> <p>次に、他のコードがその関心を登録します。</p> <pre><code class="Lua">mymobs.register_on_death(function(mob, reason) if reason.type == "punch" and reason.object and reason.object:is_player() then awards.notify_mob_kill(reason.object, mob.name) end end) </code></pre> <p>あなたは考えているかもしれません - ちょっと待って、これはひどく馴染みがあるように見えます。そして、あなたは正しい! Minetest API はオブザーバーベースであり、エンジンが何かをリッスンしていることを気にする必要がなくなります。</p> <h3 id="Model-View-Controller"><a href="#Model-View-Controller">Model-View-Controller</a></h3> <p>次の章では、コードを自動的にテストする方法について説明します。問題の1つは、ロジック(計算、実行する必要があること)を API 呼び出し( <code>minetest.*</code>、その他の mod )から可能な限り分離する方法です。</p> <p>これを行う1つの方法は、次のことを考えることです。</p> <ul> <li>あなたが持っている <strong>データ</strong>。</li> <li>このデータで実行できる <strong>アクション</strong>。</li> <li><strong>イベント</strong> (つまり、 formspec 、 punchs など)がこれらのアクションをトリガーする方法、およびこれらのアクションがエンジンで発生する方法。</li> </ul> <p>土地保護 mod の例を見てみましょう。あなたが持っているデータは、エリアと関連するメタデータです。実行できるアクションは、 <code>create</code> 、 <code>edit</code> 、または <code>delete</code> です。これらのアクションをトリガーするイベントは、チャットコマンドと formspec 受信フィールドです。これらは通常、かなりうまく分離できる 3 つの領域です。</p> <p>テストでは、トリガーされたときのアクションがデータに対して正しいことを行うことを確認できます。イベントがアクションを呼び出すことをテストする必要はありません(これには Minetest API を使用する必要があり、コードのこの領域はとにかくできるだけ小さくする必要があります)。</p> <p>Pure Lua を使用してデータ表現を作成する必要があります。このコンテキストでの「 Pure 」とは、関数が Minetest の外部で実行される可能性があることを意味します。つまり、エンジンの関数は呼び出されません。</p> <pre><code class="Lua">-- Data function land.create(name, area_name) land.lands[area_name] = { name = area_name, owner = name, -- more stuff } end function land.get_by_name(area_name) return land.lands[area_name] end </code></pre> <p>あなたの行動も pure でなければなりませんが、他の関数を呼び出すことは上記よりも受け入れられます。</p> <pre><code class="Lua">-- Controller function land.handle_create_submit(name, area_name) -- process stuff -- (ie: check for overlaps, check quotas, check permissions) land.create(name, area_name) end function land.handle_creation_request(name) -- This is a bad example, as explained later land.show_create_formspec(name) end </code></pre> <p>イベントハンドラーは Minetest API と対話する必要があります。この領域を簡単にテストすることはできないため、計算の数を最小限に抑える必要があります。</p> <pre><code class="Lua">-- View function land.show_create_formspec(name) -- Note how there's no complex calculations here! return [[ size[4,3] label[1,0;This is an example] field[0,1;3,1;area_name;] button_exit[0,2;1,1;exit;Exit] ]] end minetest.register_chatcommand("/land", { privs = { land = true }, func = function(name) land.handle_creation_request(name) end, }) minetest.register_on_player_receive_fields(function(player, formname, fields) land.handle_create_submit(player:get_player_name(), fields.area_name) end) </code></pre> <p>上記は Model-View-Controller パターンです。モデルは、最小限の機能を備えたデータのコレクションです。ビューは、イベントをリッスンしてコントローラーに渡す関数のコレクションであり、コントローラーから呼び出しを受信して、 Minetest API で何かを実行します。コントローラーは、決定とほとんどの計算が行われる場所です。</p> <p>コントローラーは MinetestAPI についての知識を持っていないはずです - Minetest 呼び出しまたはそれらに類似したビュー関数がないことに注意してください。 <code>view.hud_add(player、def)</code> のような関数は<em>持ってはいけません</em>。代わりに、ビューは、 <code>view.add_hud(info)</code> のように、コントローラーがビューに実行するように指示できるいくつかのアクションを定義します。ここで、 info は、 Minetest API にまったく関係のない値またはテーブルです。</p> <p><img src="https://rubenwardy.com/minetest_modding_book/static/mvc_diagram.svg" alt="Diagram showing a centered text element" /></p> <p>エリアの内部または外部を変更する場合に変更する必要のある量を減らすために、上記のように、各エリアが直接隣接するエリアとのみ通信することが重要です。たとえば、 formspec を変更するには、ビューを編集するだけで済みます。ビュー API を変更するには、ビューとコントローラーを変更するだけで、モデルはまったく変更できません。</p> <p>実際には、この設計は複雑さが増し、ほとんどの種類の mod に多くの利点がないため、ほとんど使用されません。代わりに、一般的に、それほど形式的で厳密ではない種類のデザイン( API-View のバリアント)が表示されます。</p> <h4 id="API-View &lt;a name=&quot;#api-view&quot;&gt;&lt;/a&gt;"><a href="#API-View+%26lt%3Ba+name%3D%26quot%3B%23api-view%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">API-View <a name="#api-view"></a></a></h4> <p>理想的な世界では、通常のビューに戻る前に、上記の 3 つの領域が完全に分離され、すべてのイベントがコントローラーに送られます。しかし、これは現実の世界ではありません。良い妥協案は、 mod を 2 つの部分に減らすことです。</p> <ul> <li><strong>API</strong> - これは上記のモデルとコントローラーでした。ここでは <code>minetest.</code> を使用しないでください。</li> <li><strong>ビュー</strong> - これも上記のビューでした。これをイベントの種類ごとに別々のファイルに構造化することをお勧めします。</li> </ul> <p>rubenwardy の<a target="_blank" rel="nofollow noopener" href="https://github.com/rubenwardy/crafting">craftingmod</a>は、おおまかにこの設計に従います。 <code>api.lua</code> は、データストレージとコントローラースタイルの計算を処理するほとんどすべての pure な Lua 関数です。 <code>gui.lua</code> は formspecs と formspec 送信のビューであり、<code>async_crafter.lua</code> はノード formspec とノードタイマーのビューとコントローラーです。</p> <p>このように mod を分離すると、<a href="#unit_testing">次の章</a>に示すように、Minetest APIを使用しないため、API 部分を非常に簡単にテストできます。そして craftingmod で見られます。</p> <h3 id="Conclusion &lt;a name=&quot;#conclusion&quot;&gt;&lt;/a&gt;"><a href="#Conclusion+%26lt%3Ba+name%3D%26quot%3B%23conclusion%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Conclusion <a name="#conclusion"></a></a></h3> <p>優れたコード設計は主観的なものであり、作成するプロジェクトに大きく依存します。原則として、凝集力を高く、結合度を低く保つようにしてください。別の言い方をすれば、関連するコードをまとめ、関連しないコードを分離し、依存関係を単純に保ちます。</p> <p><a target="_blank" rel="nofollow noopener" href="http://gameprogrammingpatterns.com/">ゲームプログラミングパターン</a>の本を読むことを強くお勧めします。<br /> <a target="_blank" rel="nofollow noopener" href="http://gameprogrammingpatterns.com/contents.html">オンラインで読む</a>から無料で入手でき、ゲームに関連する一般的なプログラミングパターンについて詳しく説明しています。</p> <h2 id="26 - Releasing a Mod &lt;a name=&quot;releasing&quot;&gt;&lt;/a&gt;"><a href="#26+-+Releasing+a+Mod+%26lt%3Ba+name%3D%26quot%3Breleasing%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">26 - Releasing a Mod <a name="releasing"></a></a></h2> <h3 id="Introduction"><a href="#Introduction">Introduction</a></h3> <p>mod をリリースまたは公開すると、他の人がそれを利用できるようになります。 mod がリリースされると、シングルプレイヤーゲームや、パブリックサーバーを含むサーバーで使用される可能性があります。</p> <ul> <li>ライセンスの選択 <ul> <li><a href="#lgpl-and-cc-by-sa">LGPLおよびCC-BY-SA</a></li> <li><a href="#cc0">CC0</a></li> <li><a href="#mit">MIT</a></li> </ul></li> <li>パッケージング <ul> <li><a href="#readmetxt">README.txt</a></li> <li><a href="#descriptiontxt">description.txt</a></li> <li><a href="#screenshotpng">screenshot.png</a></li> </ul></li> <li>アップロード <ul> <li><a href="#version-control-systems">バージョン管理システム</a></li> <li><a href="#forum-attachments">フォーラムの添付ファイル</a></li> </ul></li> <li>フォーラムトピック <ul> <li><a href="#subjec">件名</a></li> </ul></li> </ul> <h3 id="License Choices"><a href="#License+Choices">License Choices</a></h3> <p>mod のライセンスを指定する必要があります。これは、他の人にあなたの作品の使用を許可する方法を伝えるため、重要です。 mod にライセンスがない場合、パブリックサーバーで mod を変更、配布、または使用することが許可されているかどうかはわかりません。</p> <p>あなたのコードとあなたのアートは、彼らが使用するライセンスとは異なるものを必要とします。たとえば、クリエイティブコモンズライセンスはソースコードと一緒に使用するべきではありませんが、画像、テキスト、メッシュなどの芸術作品に適した選択肢となる可能性があります。</p> <p>すべてのライセンスが許可されます。ただし、デリバティブを許可しない mod は、公式の Minetest フォーラムから禁止されています。 ( mod をフォーラムで許可するには、他の開発者が mod を変更して、変更されたバージョンをリリースできる必要があります。)</p> <p>定義は国によって異なるため、<strong>パブリックドメインは有効なライセンスではないこと</strong>に注意してください。</p> <h4 id="LGPL and CC-BY-SA &lt;a name=&quot;#lgpl-and-cc-by-sa&quot;&gt;&lt;/a&gt;"><a href="#LGPL+and+CC-BY-SA+%26lt%3Ba+name%3D%26quot%3B%23lgpl-and-cc-by-sa%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">LGPL and CC-BY-SA <a name="#lgpl-and-cc-by-sa"></a></a></h4> <p>これは Minetest コミュニティで一般的なライセンスの組み合わせであり、 Minetest と Minetest Game が使用するものです。コードは <code>LGPL2.1</code> でライセンスされ、アートは <code>CC-BY-SA</code> でライセンスされます。この意味は:</p> <ul> <li>誰でも、変更されたバージョンまたは変更されていないバージョンを変更、再配布、および販売できます。</li> <li>誰かがあなたの mod を変更する場合、彼らは彼らのバージョンに同じライセンスを与える必要があります。</li> <li>著作権表示を保持する必要があります。</li> </ul> <h4 id="CC0 &lt;a name=&quot;#cc0&quot;&gt;&lt;/a&gt;"><a href="#CC0+%26lt%3Ba+name%3D%26quot%3B%23cc0%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">CC0 <a name="#cc0"></a></a></h4> <p>これらのライセンスにより、誰でも mod を使ってやりたいことができるようになります。つまり、帰属を変更、再配布、販売、または除外することができます。これらのライセンスは、コードとアートの両方に使用できます。</p> <p>WTFPL は強く推奨されておらず、このライセンスを持っている場合、人々はあなたの mod を使用しないことを選択する可能性があることに注意することが重要です。</p> <h4 id="MIT &lt;a name=&quot;#mit&quot;&gt;&lt;/a&gt;"><a href="#MIT+%26lt%3Ba+name%3D%26quot%3B%23mit%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">MIT <a name="#mit"></a></a></h4> <p>これは mod コードの一般的なライセンスです。 mod のユーザーに課せられる唯一の制限は、 mod または mod の実質的な部分のコピーに同じ著作権表示とライセンスを含める必要があることです。</p> <h3 id="Packaging"><a href="#Packaging">Packaging</a></h3> <p>リリースする前に mod に含めることをお勧めするファイルがいくつかあります。</p> <h4 id="README.txt &lt;a name=&quot;#readmetxt&quot;&gt;&lt;/a&gt;"><a href="#README.txt+%26lt%3Ba+name%3D%26quot%3B%23readmetxt%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">README.txt <a name="#readmetxt"></a></a></h4> <p>README ファイルには次のように記載する必要があります。</p> <ul> <li>mod の機能。</li> <li>ライセンスとは何ですか。</li> <li>どのような依存関係がありますか。</li> <li>mod のインストール方法。</li> <li>mod の現在のバージョン。</li> <li>オプションで、問題を報告したり、ヘルプを入手したりする場所。</li> </ul> <h4 id="description.txt &lt;a name=&quot;#descriptiontxt&quot;&gt;&lt;/a&gt;"><a href="#description.txt+%26lt%3Ba+name%3D%26quot%3B%23descriptiontxt%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">description.txt <a name="#descriptiontxt"></a></a></h4> <p>これはあなたの mod が何をするかを説明するはずです。曖昧にならずに簡潔にしてください。スペースに限りがあるコンテンツインストーラーに表示されるため、短くする必要があります。</p> <p>良い手本:</p> <pre><code>Adds soup, cakes, bakes and juices. </code></pre> <p>これは避けてください:</p> <pre><code>(BAD) The food mod for Minetest. </code></pre> <h4 id="screenshot.png &lt;a name=&quot;#screenshotpng&quot;&gt;&lt;/a&gt;"><a href="#screenshot.png+%26lt%3Ba+name%3D%26quot%3B%23screenshotpng%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">screenshot.png <a name="#screenshotpng"></a></a></h4> <p>スクリーンショットは 3:2(高さ 2 ピクセルごとに幅 3 ピクセル)で、最小サイズは 300px x 200px である必要があります。</p> <p>スクリーンショットは mod ストアに表示されます。</p> <h3 id="Uploading"><a href="#Uploading">Uploading</a></h3> <p>潜在的なユーザーがあなたの mod をダウンロードできるように、あなたはそれを公的にアクセス可能な場所にアップロードする必要があります。これを行うにはいくつかの方法がありますが、これらの要件、およびフォーラムのモデレーターによって追加される可能性のあるその他の要件を満たしている限り、自分に最適なアプローチを使用する必要があります。</p> <ul> <li><strong>安定</strong> - ホスティングウェブサイトが警告なしにシャットダウンする可能性はほとんどありません。</li> <li><strong>直接リンク</strong> - フォーラムのリンクをクリックして、別のページを表示せずにファイルをダウンロードできるはずです。</li> <li><strong>ウイルスフリー</strong> - 悪意のあるコンテンツを含む mod はフォーラムから削除されます。</li> </ul> <h4 id="Version Control Systems &lt;a name=&quot;#version-control-systems&quot;&gt;&lt;/a&gt;"><a href="#Version+Control+Systems+%26lt%3Ba+name%3D%26quot%3B%23version-control-systems%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Version Control Systems <a name="#version-control-systems"></a></a></h4> <p>次のようなバージョン管理システムを使用することをお勧めします。</p> <ul> <li>他の開発者が簡単に変更を送信できるようにします。</li> <li>ダウンロードする前にコードをプレビューできるようにします。</li> <li>ユーザーがバグレポートを送信できるようにします。</li> </ul> <p>Minetest の改造者の大多数は、コードをホストする Web サイトとして GitHub を使用していますが、別の方法も可能です。</p> <p>GitHub の使用は、最初は難しい場合があります。これについてサポートが必要な場合、 GitHub の使用方法については、以下を参照してください。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="http://git-scm.com/book/en/v1/Getting-Started">Pro Gitブック</a> - オンラインで無料で読むことができます。</li> <li><a target="_blank" rel="nofollow noopener" href="https://help.github.com/articles/getting-started-with-github-for-windows/">GitHub for Windowsアプリ</a> - Windows のグラフィカルインターフェイスを使用してコードをアップロードします。</li> </ul> <h4 id="Forum Attachments &lt;a name=&quot;#forum-attachments&quot;&gt;&lt;/a&gt;"><a href="#Forum+Attachments+%26lt%3Ba+name%3D%26quot%3B%23forum-attachments%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Forum Attachments <a name="#forum-attachments"></a></a></h4> <p>バージョン管理システムを使用する代わりに、フォーラムの添付ファイルを使用して mod を共有できます。これは、 mod のフォーラムトピック(以下で説明)を作成するときに実行できます。</p> <p>mod のファイルを1つのファイルに圧縮する必要があります。これを行う方法は、オペレーティングシステムによって異なります。これはほとんどの場合、すべてのファイルを選択した後、右クリックメニューを使用して行われます。</p> <p>フォーラムのトピックを作成するときは、「 Create a Topic トピックの作成」ページ(以下を参照)で、下部にある「 Upload Attachment 添付ファイルのアップロード」タブに移動します。 「 Browse 参照」をクリックして、 zip ファイルを選択します。コメントフィールドに mod のバージョンを入力することをお勧めします。</p> <p><img src="https://rubenwardy.com/minetest_modding_book/static/releasing_attachments.png" alt="Upload Attachment" />Upload Attachment tab.</p> <h3 id="Forum Topic"><a href="#Forum+Topic">Forum Topic</a></h3> <p>これで、フォーラムトピックを作成できます。 <a target="_blank" rel="nofollow noopener" href="https://forum.minetest.net/viewforum.php?f=9">“WIP Mods”</a>(作業中)フォーラムで作成する必要があります。<br /> mod が進行中の作業であると見なされなくなったら、<a target="_blank" rel="nofollow noopener" href="https://forum.minetest.net/viewtopic.php?f=11&t=10418">移動をリクエスト</a>に「 Mod Releases 」できます。</p> <p>フォーラムのトピックには、 README と同様のコンテンツが含まれている必要がありますが、より宣伝的で、 mod をダウンロードするためのリンクも含まれている必要があります。可能であれば、実際の mod のスクリーンショットを含めることをお勧めします。</p> <p>Minetest フォーラムでは、フォーマットに bbcode を使用しています。 superspecial という名前の mod の例を次に示します。</p> <pre><code>Adds magic, rainbows and other special things. See download attached. [b]Version:[/b] 1.1 [b]License:[/b] LGPL 2.1 or later Dependencies: default mod (found in minetest_game) Report bugs or request help on the forum topic. [h]Installation[/h] Unzip the archive, rename the folder to superspecial and place it in minetest/mods/ ( GNU/Linux: If you use a system-wide installation place it in ~/.minetest/mods/. ) ( If you only want this to be used in a single world, place the folder in worldmods/ in your world directory. ) For further information or help see: [url]https://wiki.minetest.net/Installing_Mods[/url] </code></pre> <p>上記の例を mod トピック用に変更する場合は、「 superspecial 」を mod の名前に変更することを忘れないでください。</p> <h4 id="Subject &lt;a name=&quot;#subject&quot;&gt;&lt;/a&gt;"><a href="#Subject+%26lt%3Ba+name%3D%26quot%3B%23subject%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">Subject <a name="#subject"></a></a></h4> <p>トピックの主題は、次のいずれかの形式である必要があります。</p> <ul> <li>[Mod] Mod Title [modname]</li> <li>[Mod] Mod Title [バージョン番号] [modname]</li> </ul> <p>例えば:</p> <ul> <li>[Mod] More Blox [0.1] [moreblox]</li> </ul> <h2 id="27 - Read More &lt;a name=&quot;readmore&quot;&gt;&lt;/a&gt;"><a href="#27+-+Read+More+%26lt%3Ba+name%3D%26quot%3Breadmore%26quot%3B%26gt%3B%26lt%3B%2Fa%26gt%3B">27 - Read More <a name="readmore"></a></a></h2> <h3 id="List of Resources"><a href="#List+of+Resources">List of Resources</a></h3> <p>この本を読んだら、以下を見てください。</p> <h4 id="Minetest Modding"><a href="#Minetest+Modding">Minetest Modding</a></h4> <ul> <li>Minetest の Lua API リファレンス - <a target="_blank" rel="nofollow noopener" href="https://rubenwardy.com/minetest_modding_book/lua_api.html">HTMLバージョン</a> | <a target="_blank" rel="nofollow noopener" href="https://github.com/minetest/minetest/blob/master/doc/lua_api.txt">テキストバージョン</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://dev.minetest.net/Main_Page">Developer Wiki</a>を調べてください。</li> <li><a target="_blank" rel="nofollow noopener" href="https://forum.minetest.net/viewforum.php?f=11">existing mods</a>を見てください。</li> </ul> <h4 id="Lua Programming"><a href="#Lua+Programming">Lua Programming</a></h4> <ul> <li><a target="_blank" rel="nofollow noopener" href="http://www.lua.org/pil/">Luaでのプログラミング(PIL)</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://luatut.com/crash_course.html">Luaクラッシュコース</a></li> </ul> <h4 id="3D Modelling"><a href="#3D+Modelling">3D Modelling</a></h4> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://en.wikibooks.org/wiki/Blender_3D:_Noob_to_Pro">Blender 3D: Noob to pro</a>.</li> <li><a target="_blank" rel="nofollow noopener" href="http://wiki.minetest.net/Using_Blender">Using Blender with Minetest</a>.</li> </ul> <h2 id="Lua Modding API"><a href="#Lua+Modding+API">Lua Modding API</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://rubenwardy.com/minetest_modding_book/lua_api.html">Lua Modding APIリファレンス</a></p> <h2><a target="_blank" rel="nofollow noopener" href="https://github.com/rubenwardy/minetest_modding_book/archive/examples.zip">ダウンロード例</a></h2> <p>© 2014-20 | Helpful? Consider <a target="_blank" rel="nofollow noopener" href="https://rubenwardy.com/donate/">donating</a> to support my work.</p> tomato tag:crieit.net,2005:PublicArticle/17925 2022-01-08T16:27:09+09:00 2022-01-11T21:08:15+09:00 https://crieit.net/posts/bbb085470b763b7e8bcb423f4b235d64 スキャンレーション <h1 id="scanlation について"><a href="#scanlation+%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">scanlation について</a></h1> <p>スキャンレーション( scanlation )で検索すると、その言葉の定義から、ガイドラインやチュートリアル、または役割名称や、その広報的なサイトの情報がたくさんある。</p> <p>今は活動していないスキャンレーションのグループ名と、そのプロファイルのページのデータベース (sqlite file)。</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.mediafire.com/file/qlse91vdwyletgx/scanlation-g.db/file">https://www.mediafire.com/file/qlse91vdwyletgx/scanlation-g.db/file</a><br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225786/85947479-df01-7dac-fbb4-f6d8dc21aed8.jpeg" alt="IMG_20211113_140504_811.jpg" /></p> <p><a target="_blank" rel="nofollow noopener" href="https://www-mangaupdates-com.translate.goog/aboutus.html?_x_tr_sl=en&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=nui">https://www-mangaupdates-com.translate.goog/aboutus.html?_x_tr_sl=en&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=nui</a></p> <p>過去に翻訳されたタイトルのオリジナルの和名とそれ以外の対照を作りたい場合は有効かもしれないし、そうでもないかもしれない。</p> <p>著作権がらみで問題のあるコンテンツのダウンロード情報や、データ自体は含まないと宣言されているサイトから全てのスキャンレーショングループのプロファイルページの所在を抽出してから、各プロファイルページにアップデートしてアーカイブされている翻訳済みの漫画の英語タイトルを抽出すれば、プロファイルされたスキャンレーションングループについては、全ての翻訳漫画タイトルを知れるということになる。<br /> 抽出の方法はグループのプロファイルページの所在、 プロファイルページ内にある翻訳したシリーズ名、日時という順にたどると効率が良さそう。</p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/zbtnk/png">1: タイトルを考えるのが面倒な scanlation groups counter プログラム Python link</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/nekon/png">2: a , b<br /> 2つのデータベースをマージする Lua プログラムサンプル link</a> :[ 依存関係 lsqlite3 https://github.com/LuaDist/lsqlite3/blob/master/examples/smart.lua]</p> <p>1.1.1.1<br /> <a target="_blank" rel="nofollow noopener" href="https://en.m.wikipedia.org/wiki/1.1.1.1">https://en.m.wikipedia.org/wiki/1.1.1.1</a></p> <p>スキャンレーションは海賊サイトのエコシステムの一環で、フェーズとしてはオンラインでの海賊配信の水際に位置していると考えられる。<br /> 2次創作漫画画像を作るために、漫画をスキャナーでスキャンする必要があるわけだが、漫画を入手してスキャンし、アップロード ① してダウンロードアドレスを告知する ② ということをしているというところが、ボトムで、スキャンレーションの作業のエントリーポイントにもこの行程が不可欠。<br /> スキャンしてアップロード ① する人は必ずしもスキャンレーションチームにしかいないわけではないが、アップロードされたスキャン済み画像をダウングレード ③ して海賊配信 ④ していたり、スキャン画像をクリーンアップして翻訳、タイプセットして、翻訳版の海賊配信サイトにアップロード ⑤ していたりと、それぞれに直接関係があったり無かったりするのだろうけども、スキャンレーションという枠では、ひとまとまとまり情報として関係性が見える。例えば、このグループがここ ④ へアップロード ⑤ した等。</p> <p>④ のサイト群については、正当な方法(カドカワ的に「インターネット上の海賊版対策に関する検討会議 第9回会合 平成30年10月15日 議事録[^0]」)similarweb<sup id="fnref:12"><a href="#fn:12" class="footnote-ref" role="doc-noteref">1</a></sup><sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">2</a></sup> <sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">3</a></sup>で横並びに見えるもののことを主に指すようだが、ボトムアップから見るとすると、行為とモチベーション度に着目する<sup id="fnref:10"><a href="#fn:10" class="footnote-ref" role="doc-noteref">4</a></sup>とスキャンレーションのサイドからたどると動線も把握できる。スキャンレーションのさらに二次コピーなどが ④ に回収されて公開されている場合もあるだろう。トラフィックの大きさは、配信自体の技術に左右されるので、着目される経済的損失について語られるのはトラフィックの大きな上流の方のことだと思われる。漫画 BANK は ④ に含まれる(すでにアップロードされて海賊配信されているサイトからの画像流用)。</p> <p>これは想像なのでイマジネーションは図にした方がいい。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225786/ed3db1b5-2e40-1584-b6a3-4ba59ce3704f.png" alt="oie_1I69MA5YrCfZ.png" /></p> <p><a href="https://crieit.now.sh/upload_images/a4659321b5cc94fb81afc1c7163b2a6761dd7319f33e1.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/a4659321b5cc94fb81afc1c7163b2a6761dd7319f33e1.png?mw=700" alt="image" /></a></p> <p><a target="_blank" rel="nofollow noopener" href="https://fanlore.org/wiki/Scanlation_Process#General_Process">https://fanlore.org/wiki/Scanlation_Process#General_Process</a></p> <p>参考: https://kknews.cc/comic/moz43xz.html</p> <p><a target="_blank" rel="nofollow noopener" href="https://kknews-cc.translate.goog/comic/moz43xz.html?_x_tr_sl=zh-CN&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=nui">https://kknews-cc.translate.goog/comic/moz43xz.html?_x_tr_sl=zh-CN&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=nui</a></p> <p><a target="_blank" rel="nofollow noopener" href="http://onepiece.ria10.com/Entry/3752/">http://onepiece.ria10.com/Entry/3752/</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://www.similarweb.com/ja/website/mangapanda.onl/">https://www.similarweb.com/ja/website/mangapanda.onl/</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://www2.accsjp.or.jp/thanks/thanks39.php">https://www2.accsjp.or.jp/thanks/thanks39.php</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://www.neliti.com/publications/326921/accuracy-of-english-indonesian-scanlation-of-detective-conan-manga-as-compared-t">https://www.neliti.com/publications/326921/accuracy-of-english-indonesian-scanlation-of-detective-conan-manga-as-compared-t</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/scanlation-titles/png">3: link</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/zbtnk/png">1:link</a> / <a target="_blank" rel="nofollow noopener" href="https://rentry.co/nekon/png">2:link</a> / <a target="_blank" rel="nofollow noopener" href="https://rentry.co/scanlation-titles/png">3:link</a> とプログラムにせっせと、動いてもらって 3 をポチッとしてから翌日起きて、途中で止まってると思ってたら、朝になっても昼前まで止まらない量のデータをせっせと書き込んでいた。<br /> カウンターは 106000 を越えていて、なんか無理しているなぁと思いつつ、そのわきで次のプログラムを書きはじめて、</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225786/2393c94a-9848-b7d1-d308-367fa26659d2.png" alt="oie_png (1).png" /></p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/oytah/png">4:link</a> を書いた。</p> <p>たぶんこれも明日もお昼までうごいているんではないか。</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.oreilly.co.jp/books/9784873113487/">https://www.oreilly.co.jp/books/9784873113487/</a></p> <p>漠然とした中での目的としては、翻訳された(おそらく勝手に)漫画のタイトルからオリジナルのタイトルを知りたいのと、その他の言葉でのタイトルを眺めたいので、そうするとどうやって組み上げたらいいのかなということをやってみたい。</p> <p>ということで、まずスキャンレーショングループが過去から現在まで翻訳したシリーズになった(単行本とかのことだと思われる)ものをカウントアップするために、まずグループのリストを作る。これが <a target="_blank" rel="nofollow noopener" href="https://rentry.co/zbtnk/png">1:link</a> のコード。<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/zbtnk/png">1:link</a> でできたリストのなかには、グループについてのプロファイリングされた個別のページのアドレスがあり、このプロファイリングされたページ上にグループの翻訳したシリーズがリストアップされている。<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/zbtnk/png">1:link</a> で作れるデータベースを複数足し合わせるのが <a target="_blank" rel="nofollow noopener" href="https://rentry.co/nekon/png">2:link</a> のコード。なぜ、Lua なのか、コンパクトで速いから。同じように他の言語で書いて同じような速度かもしれないが、コードを見れば、見える通りのことなのでシンプル。遅くはない。</p> <p>hy (clojure lisp 風 Python) 言語に合わせて <a target="_blank" rel="nofollow noopener" href="https://rentry.co/nekon/png">2:link</a> と同じものを Lua の Lisp 風言語 <a target="_blank" rel="nofollow noopener" href="https://github.com/bakpakin/Fennel/blob/main/reference.md">fennel</a> に直してみると</p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/fennel/png">2:link fennel</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/dauuricus/items/37b80ddab68777414753">Vim</a> だとプラグインを設定しないとちょっと難しい感じだけど、<a target="_blank" rel="nofollow noopener" href="https://kakoune.org/">kakoune</a> だとデフォルトで色分けはしてくれる。どうなってるんでしょう。<a target="_blank" rel="nofollow noopener" href="https://qiita.com/dauuricus/items/c0023fb476c5f708292e">vis editor</a> も。</p> <p>プロファイルされたページのシリーズタイトルを足し合わせて記録していくのが 3:link のコード。Python だが hy 言語。今のところ hy で書くメリットは分かりやすさ。<br /> SQLite のバインドも Python 独自のものなので、select して execute されて戻ってくるデータが空のときどうやって空のデータを比較できるのかというところが言語とバインドによって違うので、そこは注意しなくてはいけない。Lua の場合は、空の場合は素通りで、値を判定する式自体を無視するよう、そのことを分かっていたらシンプル。</p> <p>例えば、</p> <pre><code class="lua">local smt1 = "SELECT id FROM tbl_scanlation ORDER BY id DESC LIMIT 1 ;" local last1 = 0 for id in db1:urows(smt1) do last1 = id end </code></pre> <p>これは Lua バインドで sqlite3 データベースの tbl_scanlation というテーブルのデータの中の primary キー連番 id 番号の最後の int 値を取り出す式を実行して last1 に代入したい箇所だが、テーブルの中にデータが入ってなければ <code>local last1 = 0</code> で初期化している値が入る。 nil とか -1 とかにはならない。python や Ruby 、 go や nim など、またそれぞれ違うので、いろんな別の言語にて書き換える場合は、<strong>無い場合</strong>どういう値が入るのかよく調べなければいけない。SQLite3 自体では返ってくる値は決められているが、その値に対する値は、バインドによって書き換えられて処理されているようなので。</p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/scanlation-titles/png">3:link</a> で出来上がるデータベースの件数は三万件くらいだろうかと思っていたが、10 万件以上だった。SQLite の場合は扱える数はそれ以下のはずだが、無理でもできるとこまででいいのでがんばってもらう。</p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/oytah/png">4:link</a> の様子。お昼になっても終わっていない。<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225786/736d3cbd-136e-d509-7247-54a18c2061ff.png" alt="Screenshot_20211120-113635.png" /></p> <p>一週間くらい経ってる気がするが、VALID データが 96,426 。つまり、中国、韓国、日本の漫画がスキャンレーショングループによって、それだけの数は翻訳されている。<br /> sqlite3 database file<br /> <a target="_blank" rel="nofollow noopener" href="https://we.tl/t-xUEh2MPRcF">https://we.tl/t-xUEh2MPRcF</a></p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225786/27ec591c-9a79-c606-7259-195d0f4161f3.png" alt="Screenshot_20211211-143003_kindlephoto-66542057.png" /></p> <div class="footnotes" role="doc-endnotes"> <hr /> <ol> <li id="fn:12" role="doc-endnote"> <p>資料7 https://www.kantei.go.jp/jp/singi/titeki2/tyousakai/kensho_hyoka_kikaku/2018/kaizoku/dai9/gijisidai.html <a href="#fnref:12" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:8" role="doc-endnote"> <p>https://en.globes.co.il/en/article-similarwebs-controversial-route-to-wall-street-1001376912 <a href="#fnref:8" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:9" role="doc-endnote"> <p>These Chrome extensions spy on 8 million users 31 Mar 2016 https://mweissbacher.com/2016/03/31/these-chrome-extensions-spy-on-8-million-users/ <a href="#fnref:9" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:10" role="doc-endnote"> <p>https://www.reddit.com/r/scanlation/about/ <a href="#fnref:10" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> </ol> </div> tomato