講座:INT8あれば出来る!MODの作り方

0.はじめに


0.C開発版での解説です。容赦なく変更されるので慣れましょう。
MOD製作はゲームの盛大なネタバレが避けられません。覚悟して進みましょう。

既存のjsonをコピーして改変。これがMOD製作への第一歩です。
しかし、お手本のjsonを見つめているだけでは、出来ることが限られてしまいます。

このページでは、もう一歩踏み込んでMODを作る上で知っておきたいことを解説します。
少し話が高度になりますが大丈夫。コピー・改変・トライアンドエラー!です。
困ったら本スレ、質問スレへGO!


1.ツールを用意しよう

本格的にMODを作ろうと思うなら、それなりのツールを用意する必要があります。
  • テキストエディタ
jsonを編集する専用のもの…は不要です。文字コードUTF-8に対応出来るエディタを用意しましょう。
エディタはシンプルで軽いものが結局は最強です。
  • grep検索ソフト
ファイル内を検索できるなんらかのソフトが必要です。エクスプローラーではちょっと…。
いわゆるgrep検索ができないと、情報を集めるのが大変です。
  • 表計算ソフト
多数のアイテムを編集しようとするとテキストエディタでは管理がおっつかなくなります。
表計算ソフトでデータを管理しておき、ここから機械的にjsonを編集するようにしておけば設定ミスが減らせるでしょう。

このページを書いている人はサクラエディタを使っています。付属のgrep検索が非常に強力なため、MOD製作に向いています。
jsonを整形する上で、短形選択やマクロも大きな助けになってくれます。
表計算ソフトはOpenOfficeを使っていますが、あるならExcelの方が100倍いいです。

2.日本語を扱う際の注意点

Cataclysm:DDAの多言語対応は、実行時に翻訳ファイルから訳文を探すことで行われています。
翻訳ファイルはMODオリジナルで配布することは出来ず、MOD内でも原文と翻訳文を両方定義することは出来ないため、英語で書くか、日本語で書くかの二択になります。
気になるなら、言語ごとにMODを別にして配布するしかありません。(公式MODに掲載してもらい翻訳してもらう方法も)

各MODのファイルにはマルチバイト文字を含めることが出来るので、普通に日本語のテキストを書けます。
ただし、ファイルを保存する際は文字コードをUTF-8にする必要があります。
また、改行コードは原則としてLFであるべきです。メモ帳では出来ないので、対応しているエディタが必要になります。

3.docフォルダを見よう

開発者、MOD製作者向けのドキュメントがゲームのフォルダに入っていますので、チラ見しておきましょう。
docフォルダの中がそれです。

中にはReadmeレベルのことが書かれている.mdと、サンプルMODの入ったフォルダがあります。
.mdはマークダウンファイルなのでatomなどで読めるのですが、わざわざそんなものを入れる必要はありません。

中身は英語な上、開発者向けに書かれているのでチンプンですが、MOD製作を行う上で欠かせない情報が入っています。
その内の1つを見てみましょう。

JSON_FLAGS.md
+ ...
### Categories

- ```CC_WEAPON```
- ```CC_AMMO```
- ```CC_FOOD```
- ```CC_DRINK```
- ```CC_CHEM```
- ```CC_ELECTRONIC```
- ```CC_ARMOR```
- ```CC_MISC```

そうです。json中に頻繁に出てくるキーワードがまとめて書いてあるのです。(情報が古くなってることも稀によくある)
このCategoriesはアイテムの定義に指定する分類の一覧で、製作メニューの大分類を指しています。
こうした情報が分かっていると、ゲーム本体とMODのjsonの中から、サンプルにするjsonや記述を探すのが楽になります。
これをキーワードに、ゲーム内のフォルダツリーをgrep検索すればいいのです。

またdocフォルダにはサンプルMODも入っています。(情報が古くなって(ry)
サンプル数は少ないものの、ゲーム中のjsonでは実現していない変わった方法が紹介されています。

4.ソースコードをダウンロードする

ソースコードの情報が必要になることがあります。
GitHubから最新のソースコードをダウンロードしておくと役に立つでしょう。

5.MOD製作例

ではMODの製作例を通して、少し踏み込んだ内容を見ていきましょう。

今回は「モンスターを召喚するアイテム」です。

(1)modinfo.jsonを書く

まずは適当なMODからmodinfo.jsonをコピーしてMODの情報を書きます。
{
     "type": "MOD_INFO"
    ,"mod-type": "SUPPLEMENTAL"
    ,"ident": "summon_test"
    ,"name": "追加 - モンスター召喚アイテム"
    ,"author": ""
    ,"description": "モンスター召喚アイテムのテストです。"
    ,"category": "items"
    ,"dependencies": ""
    ,"path": ""
  }
 
MODの情報は世界生成の際にプレイヤーに表示されるのでわかりやすく書きましょう。
メニューの幅はせまいので文字数はかなり限られます。
「追加 -」のような表示は、翻訳チームの方が、見やすさを考慮して翻訳で付け足してくれていることが分かります。

ちなみに、「category」ですが、これは何を指定すればいいのでしょう?
MODリスト上の分類なのは想像が付きますが、指定できるものの一覧が見たいと思いませんか?
こういう時は、docフォルダを見てみましょう。

が、…書いてない…。

そこでソースをgrep検索していくと…
+ ...
const std::vector<std::pair<std::string, std::string> > &get_mod_list_categories() {
    static const std::vector<std::pair<std::string, std::string> > mod_list_categories = {
        {"items", _("ITEM ADDITION MODS")},
        {"creatures", _("CREATURE MODS")},
        {"misc_additions", _("MISC ADDITIONS")},
        {"buildings", _("BUILDINGS MODS")},
        {"vehicles", _("VEHICLE MODS")},
        {"rebalance", _("REBALANCING MODS")},
        {"magical", _("MAGICAL MODS")},
        {"item_exclude", _("ITEM EXCLUSION MODS")},
        {"monster_exclude", _("MONSTER EXCLUSION MODS")},
        {"", _("NO CATEGORY")}
    };
 
    return mod_list_categories;
  }
 
直で書いてありました…。
このように、ソースを見なければ詳細が分からないケースもたまにあります。

スッキリしたところで、今回はアイテムの追加なので"items"を採用です。

(2)お手本となるアイテムを探す

次に召喚に使うアイテムを追加します。ベースになるアイテムはマッチにします。

まずはお手本となるマッチの定義を探します。
今回は"マッチ"ですので流石にmatchで検索したらヒットしますが、英語名が思い出せないアイテムもあると思います。
そんなときは、ゲームのデバッグメニューを使うと話が早いです。

さっそく、このMODだけを入れた捨てワールド・捨てキャラを作りましょう。
そして、デバッグメニューの「アイテムを手に入れる」を選択します。
アイテムを選ぶ画面には検索機能が付いていますので、日本語名で検索できます。
検索したアイテムの説明を見てください。右上に通し番号と並んで「matches」と表示されています。
これはアイテムを固有に識別するidです。この「matches」でjsonを検索すればいいわけです。
マッチを探し出せたら、jsonの内容をコピー。
新規にMODのフォルダにjsonファイルを追加して、中身をいじります。

idを決めるときは、MOD固有の接頭辞/接尾辞を付けるようしましょう。
idがほかのアイテムと被ると衝突してエラーになるか、最悪の場合定義が混ざったり上書きされたりしてしまいます。
今だけでなく、後から増えることを考えて固有のidになるようにすべきです。
今回は「summon_test_」を接頭辞にします。

summon_test_tools.json
[
   {
     "id": "summon_test_matchbook",
     "type": "TOOL",
     "symbol": ",",
     "color": "blue",
     "name": "マッチ(モンスター召喚)",
     "description": "マッチをスるとモンスターが出てきます!",
     "price": 10,
     "material": "paper",
     "weight": 10,
     "volume": 0,
     "max_charges": 20,
     "initial_charges": 20,
     "charges_per_use": 1,
     "use_action": {
         "type": "firestarter",
         "moves_cost": 15
     }
   }
 ]
 
これでidと名前、説明書きは書けましたが、このままではただのマッチです。
モンスターを召喚する方法を調べなくてはいけません。

マッチには火を付ける機能があります。このjsonで言うと、use_action...の部分がその設定のようです。
use_action、すなわち「使った時のアクション」ですから、ここに指定できるバリエーションの中に使えるものがあるかも知れません。

(3)指定できるキーワードとその効果を調べる

この手の「キーワード」が分からない場合は、まずdocフォルダを見る、です。
マッチで使われている"firestarter"のキーワードで.mdをgrep検索してみると...

JSON_INFO.md
"use_action": {
    "type": "firestarter", // Start a fire, like with a lighter.
    "moves_cost": 15 // Number of moves it takes to start the fire.
 },
 
ありました。そして、コメントが書かれており、ライターのように火を付けられ、火を付けるのにかかる行動コストが指定できる、と分かります。

更にJSON_INFO.md内を見ていくと、
"use_action": {
    "type": "place_monster", // place a turret / manhack / whatever monster on the map
    "monster_id": "mon_manhack", // monster id, see monsters.json
    "difficulty": 4, // difficulty for programming it (manhacks have 4, turrets 6, ...)
    "hostile_msg": "It's hostile!", // (optional) message when programming the monster failed and it's hostile.
    "friendly_msg": "Good!", // (optional) message when the monster is programmed properly and it's friendly.
    "place_randomly": true, // if true: places the monster randomly around the player, if false: let the player decide where to put it (default: false)
    "skill1": "throw", // Id of a skill, higher skill level means more likely to place a friendly monster.
    "skill2": "unarmed", // Another id, just like the skill1. Both entries are optional.
    "moves": 60 // how many move points the action takes.
 },
 
モンスターを配置できそうな指定がありました。
このようにキーワードから追っていくことで未知の効果が実装されていることを見つけることができます。

このuse_actionには全部で9つのパラメータを指定することになっています。
この手のパラメータには省略可能なものが多く、他のjsonを参考にしているだけだと、省略されたパラメータに気づかないこともあります。docフォルダの.mdを見て確実に仕様を把握しておきたいところです。

今回はこのJSON_INFO.mdの記述をコピーして、先ほどのマッチ(モンスター召喚)のuse_actionに指定します。
+ ...
{
    "id": "summon_test_matchbook",
    "type": "TOOL",
    "symbol": ",",
    "color": "blue",
    "name": "マッチ(モンスター召喚)",
    "description": "マッチをスるとモンスターが出てきます!",
    "price": 10,
    "material": "paper",
    "weight": 10,
    "volume": 0,
    "max_charges": 20,
    "initial_charges": 20,
    "charges_per_use": 1,
    "use_action": {
       "type": "place_monster",
       "monster_id": "mon_zombie",
       "difficulty": 1,
       "hostile_msg": "失敗した!",
       "friendly_msg": "召喚できた!",
       "place_randomly": false,
       "moves": 60
    }
  }
 

これが書けたら実際にゲーム内のデバッグメニューで呼び出してテストします。
うまくゾンビが召喚できたら、めでたしめでたし。

(4)さらにカスタマイズできる可能性

ゾンビ君を召喚できる魔法のマッチが出来ました。しかし、ちょっと不満です。
使う度にマッチが一本減るのはいいですが、いっぺんにまとめて召喚できるようにならないでしょうか?

JSON_INFO.mdを見ても、そんな都合のいいアクションはないようです。
しかし、やる気さえあれば、どこにもない新しいアクションを用意することが出来ます。

この記事を書いている時点では次のことが可能です。
MODでの拡張性がさらに必要になったときにできることは増えていくでしょう。
  1. アイテムのuse_actionの新規実装(後述)
  2. ゲーム内の特定タイミングでの割り込み(日付変更時、スキルレベル変動時、プレイヤー作成時)

この記事ではuse_actionの新規実装について解説します。

(5)アクションを作る

アクションと言うからには、jsonのような単なるデータでは実現できません。
スクリプト言語のLUAを使います。プログラミングのスキルが少し必要になります。

MODの作成手順を追って解説します。

まず、呼び出されるLUAスクリプトのファイルをMODフォルダ内に新規作成します。
このとき、ファイル名はpreload.luaとします。
これはjsonのデータよりも先にこのスクリプトをロードさせるための決まりです。
中身は作るアクションに応じた空の関数を一つと、おまじないを1行です。
function iuse_summon_test_zombies(item, active)
    game.add_msg("まずは呼び出せるかテスト!")
end
 
game.register_iuse("IUSE_SUMMON_TEST_ZOMBIES", iuse_summon_test_zombies)
 

この関数名は何でもよいですが、例によって衝突を避けるために接頭辞を付けるなどします。
関数内には目印として、game.add_msgでメッセージを出すように仕込んでみます。
最後のおまじない1行はこの時点ではイミフですが、次で話が繋がります。

次に、先ほどのマッチを改造します。
+ ...
[
  {
    "id": "summon_test_matchbook",
    "type": "TOOL",
    "symbol": ",",
    "color": "blue",
    "name": "マッチ(モンスター召喚)",
    "description": "マッチをスるとモンスターが出てきます!",
    "price": 10,
    "material": "paper",
    "weight": 10,
    "volume": 0,
    "max_charges": 20,
    "initial_charges": 20,
    "charges_per_use": 1,
    "use_action": "IUSE_SUMMON_TEST_ZOMBIES"
  }
]
 
use_actionを"IUSE_SUMMON_TEST_ZOMBIES"の1語に変えただけです。
この状態でゲームにこのマッチを呼び出してテストすると、ログに「まずは呼び出せるかテスト!」と表示されます。
テストではありますが、オリジナルのアクションが出来たわけです。

ここで、先ほどのおまじないを見てください。
game.register_iuse("IUSE_SUMMON_TEST_ZOMBIES", iuse_summon_test_zombies)
 
これは、この魔法のマッチが使われたら、
use_actionのところに書いた「IUSE_SUMMON_TEST_ZOMBIES」タグに対応する
LUAスクリプトの関数「iuse_summon_test_zombies」が呼ばれるようにゲームさんよろしく!という意味だったわけです。

(6)LUAスクリプトで出来ることを調べる

あとはこのスクリプトをいじくれば好き勝手できるわけですが、LUAスクリプト以前に、このゲームに対して何が出来るか、ということが重要です。

先ほどのテストではgame.add_msgという関数を使っていますが、いったいこれはどこの情報なのでしょうか。
今度はこのadd_msgを頼りにgrep検索してみます。対象となるファイルには.luaを含めておきます。

class_definitions.lua
global_functions = {
    add_msg = {
        cpp_name = "add_msg_wrapper",
        args     = { "string" },
        argnames = { "message" },
        rval = nil,
        desc = "Write a message to the game's standard message window."
    },
...
 
これはあらかじめゲームが用意してくれたLUAスクリプト用の関数であることが分かります。
また、このclass_definitions.luaには、LUAスクリプト内から干渉することのできる要素の定義が書いてあります。
player = {
    parent = "Character",
    attributes = {
        blocks_left = { type = "int", writable = true },
        cash = { type = "int", writable = true },
        controlling_vehicle = { type = "bool", writable = true },
        dodges_left = { type = "int", writable = true },
...
 
例えば、playerのcashがwritableなわけですから、スクリプトでここをいじればお金持ちになれそうな感じです。

+ ...
この記事を書いている時点では以下のようなことが可能です。

  • class_definitions.luaに書かれたクラス(classes配列内のメンバー)のインスタンス化と利用
  • ゲーム内のグローバル変数「player」「map」「g(gameクラス)」を介したゲーム内へのアクセス
  • グローバル関数の使用※「register_iuse」「items_at」...etc(catalua.cpp参照)
  • グローバル関数の使用※「add_msg」「popup」...etc(catalua.cpp/class_definitions.lua参照)
(※LUA側の配列変数gameのメンバーとして提供)

class_definitions.luaは具体的な処理が書いてあるわけではなく、LUAからゲーム本体の関数をコールバックするためのC++コードの生成に使用されているだけです。そのため、ここに書かれた内容そのものはLUAスクリプトでは使いようがありません。class_definitions.lua内の記載の通りに名前を解決してくれるというだけです。
従って、LUAで何が出来るかを調べるにはゲーム本体のソースコードを読む必要があります。


今のところ、好き勝手出来るほど便利なライブラリが用意されているわけではなく、試験的な実装だったり、公式MODのための特別措置だったりしますが、それでもjsonでは実現できなかったことが出来る可能性は提示されていると言えます。

ただし、ゲーム内の情報はすべて関連しあって成立しており、それを無視して直接データをいじると不具合の元になります。
そのため、ゲーム本体のソースコードをよく読んで理解した上で、十分なテストが必要になります。

公式のMODにもいくつかLUAスクリプトで実現されている処理があるので、ゲームフォルダの中の.luaファイルを参考にしていろいろ試していきましょう。

(7)仕上げ

せっかくなので、魔法のマッチを完成させましょう。あとはLUAスクリプトで書くだけです。
doc/sample_mods/lua_manhack_iuse をお手本にして書きました。
preload.lua
+ ...
function iuse_summon_test_zombies(item, active)
 
  local mon
  for x = -1, 1 do
    for y = -1, 1 do
      local point = player:pos()
      point.x = point.x + x
      point.y = point.y + y
 
      if item.charges <= 0 then
        game.add_msg("マッチがなくなりました。")
        return
      end
      if g:is_empty(point) then
        mon = game.create_monster(mtype_id("mon_zombie"), point)
        item.charges = item.charges -1
      end
    end
  end
 
  if not(mon) then
    game.add_msg("狭くて呼び出せない!")
  end
 
end
 
game.register_iuse("IUSE_SUMMON_TEST_ZOMBIES", iuse_summon_test_zombies)
 
プレイヤーの周囲の座標をチェックして、空(通れる)ならゾンビ君を召喚。
1体召喚できたらマッチが1本減るようになっています。

が…

use_actionに"place_monster"を指定していたときは味方のゾンビを召喚できていました。
でも、このスクリプトではただ単にゾンビを沸かせているだけだから…

アーッ!

6.まとめ

  • docフォルダの中は宝の山。必ずチェックしよう。
  • 分からなくなったらgrep検索でキーワードから追跡しよう。
  • デバッグメニューを使って賢く作業しよう。
  • LUAスクリプトで更に広がる可能性。難しければ人の作ったMODをお手本にして慣れていこう。

タグ:

+ タグ編集
  • タグ:

このサイトはreCAPTCHAによって保護されており、Googleの プライバシーポリシー利用規約 が適用されます。

最終更新:2016年06月26日 16:47