ソースコード
http://members.shaw.ca/rob.ellwood/sources.txtより勝手に和訳。
この項目はまだ書きかけです。

別翻訳: http://sourceforge.jp/projects/jslashem/docs/jsources/ja/jsources.txt

1. この文書について

ネットハックを何[ヶ月|年]も楽しんでいるかい?何かこのゲームに付け加えるいいアイデアがあるのに、誰も話を聞いてくれないなんて悩んでないかい?Cコンパイラがあるマシンにアクセス出来るかい?プログラムを組んだことあるかい?もしこれらの質問に“はい”と答えるなら、この文書は君の役に立てるかもしれない。

'94くらいからネットハックのソースを覗きはじめたんだ。このゲームが好きだったからいくつか新しい怪物や機能をつけようと考えたんだけど、なかなかに骨の折れる作業だった。私はCの初心者というわけでは無かったんだが、100(あるいはそれ以上!)もあるソースファイルは私の手に負えるものじゃなかった - main()を探すことにすら苦労したんだ。ソースファイル中のコメントは一見さんには役立たないものばっかりだったし。

今、九ヶ月かかって、私はこのソースのおおまかな構造は捉えることができた。新しい怪物、アイテム、部屋、階層、ふるまいを加えることができるようになった。私 -おっと念のため 私は開発チームの者ではないですよ- はこの巨大な化け物みたいなソースを全部詳細に理解しているわけではないから、この文書は完璧なソースガイドではなくて新しいアイデアの実装の助けになることを目標としている。

多分この文書には間違ってる箇所もあると思う。そして、多分…じゃなくて、きっと不完全な箇所があるだろうとも。OK,バグ/コメント/付記は私に送ってくれ、変更/追記するから。

お願いだから、この文書は自己責任で使用してほしい。いらいら以外得るものが無くても、非難しないでくれ。私は善意で助けようとしただけなんだ。

この文書の全ての内容はNethack 3.1.3(この文書を書いてる時点での公式最新版)に基づいています。

2. よし、このゲームに変更を加えていくぞ!

さあ、君は先週ネットハックを始めて、Unixマシンのアカウントを手にして、“そうだ!いいことを思いついたぞ!この文書に従っていくだけでネットハックを改変しながらCを会得できる!”なんて思いついたなら、*やめておけ*。レオナルド・ダ・ヴィンチの生まれ変わりでもない限り無理だから。

言い換えよう。この文書を読むためには少なくとも次のものが必要だ:

  • 今あなたが使っているOSに関する基礎知識。ファイルを作ったり、編集したり、消したりが自在にできること。
  • C言語に関するある程度の知識。printf("Hello world\n") くらいじゃ足りないよ。もちろんCプリプロセッサもね。
  • Cコンパイラとそれに対する慣れ。
  • 祝福された能力獲得の薬

3. まず最初に

まずあなたは自分でソースをコンパイルしなくてはいけない。別に全行程を理解していなくともよいけれど、公式ソースを持ってきて動かせないと。ディレクトリ最上層のREADMEと/sys/XXX/Install.XXX (XXXはあなたのOSによりけり)を参照のこと。

ちゃんとコンパイルに通せれば準備は完了だ。

4. ウィザードモード

恐らくは知っての通り、ウィザードモードはいくつかのコマンドを増やしてネットハックを起動するモードだ。

		^E  ==  隠された扉や罠を見つける。
		^F  ==  魔法の地図の魔法を唱える。
		^G  ==  怪物を作る。
		^I  ==  背負い袋の中のアイテムを識別する。
		^O  ==  特別な階層の位置を知る。
		^T  ==  同じ階層内でテレポートする。
		^V  ==  違う階層へテレポートする。
		^W  ==  願う。
		^X  ==  能力を見る。

それに、いくつかの振る舞いが変わる。e.g. 怪物を作る巻物を読んだとき、怪物を特定できる。(訳注:怪物の特定以外の違いもあったんで、i.e.じゃなくてe.g.で。)このモードを使えば変更点をすぐに確かめることができる。始めるにはこうタイプするだけでいい:

               nethack -u wizard -D

これでうまくいくはずだけど、次の点に注意:

  • UNIX機を使っているなら、コンパイル時に指定したユーザIDでいなければいけない。
  • include/config.hの中の#define WIZARDがある行の辺りを見てほしい;もしかすると-uの後には別の名前が要るかもしれない。

ネットハックを改良していく中で、このモードをしっかり活用しなければいけないだろう。*1

5. 新しい階層をつくる

新しい階層を作るのは簡単だ。実を言うと、Cについて何も知らなくていい:開発チームがいろいろと用意してくれている。

ネットハックをコンパイルした際、utilディレクトリ下に'lev_comp'(MS-DOSならばlev_comp.exe)というプログラムができているはずだ。
このプログラムを使えばネットハックに新しい階層を加えることができる:階層の説明を書いたファイル(<ほげほげ>.desという名前)を与えれば、ネットハック用の階層ファイル(<ほげほげ>.lev)ができるんだ。

           mylevel.des -----------
                                  |
                               __ | __
                               \  V  /
                             ___\___/___
                             |         | _
                             |lev_comp |/
                             |         |  -----> mylevel.lev
                             -------------

lev_compには'lev_comp.6'という名前のマニュアルがついている("doc"ディレクトリの中をのぞいてみて)。もし君がUNIXマシンを使ってるなら、次のコマンドをうてば標準的なmanが見れる。

                         nroff -man lev_comp.6 | more

もしそうでないなら、うーむ、.SHとか.PPとかのゴミに慣れてもらうしかないね。(注:現在の最新版(3.4.3)では、lev_comp.txtという整形済みテキスト形式のものがある。そちらを利用しよう。)
このマニュアルには(結構わかりづらいけど).desファイルの作りかたが載っている。
例として、"dat/castle.des"ファイルの始めの部分を見てみよう。

	MAZE:"castle",random
	FLAGS: noteleport
	GEOMETRY:center,center
	MAP
	}}}}}}}}}.............................................}}}}}}}}}
	}-------}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}-------}
	}|.....|-----------------------------------------------|.....|}
	}|.....+...............................................+.....|}
	}-------------------------------+-----------------------------}
	}}}}}}|........|..........+...........|.......S.S.......|}}}}}}
	.....}|........|..........|...........|.......|.|.......|}.....
	.....}|........------------...........---------S---------}.....
	.....}|...{....+..........+.........\.S.................+......
	.....}|........------------...........---------S---------}.....
	.....}|........|..........|...........|.......|.|.......|}.....
	}}}}}}|........|..........+...........|.......S.S.......|}}}}}}
	}-------------------------------+-----------------------------}
	}|.....+...............................................+.....|}
	}|.....|-----------------------------------------------|.....|}
	}-------}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}-------}
	}}}}}}}}}.............................................}}}}}}}}}
	ENDMAP

簡単そうだろ?このファイルから城レベルができるんだ。ここに引用した部分は外観だけを示してる:迷路の間に城が描かれる(MAZEの行にも注目)。

残りの部分でドア、罠、アイテム、そして怪物をランダムに、あるいは指定した場所に配置する。(上で描いた地図にたいして相対的な位置で)

マニュアルに文法が示してあるから、思い切って全部作りかえることもできる;だけど、小さい所から変えていってどうなるか見ていこうじゃないか:城の兵士はちょっと弱っちくてもっと骨のある怪物に変えたいとしよう、例えばミノタウロスとか。 次のような行を

  MONSTER:'@',"soldier",(08,06)

こんな風に変えてみよう。

  MONSTER:'H',"minotaur",(08,06)

(ここでは特定の怪物(ミノタウロス)を選んでいるけど、こんな風に

  MONSTER:'D',RANDOM,(08,06)	# ランダムにドラゴンをつくる

すれば種類で選ぶこともできたんだ。)

新しいcastle.desを保存して、datディレクトリから次のコマンドを走らせよう。

     ../util/lev_comp castle.des      (MS-DOSを使っている時は'/'を'\'に変えること)

これで新しい'castle.lev'ファイルが'dat'ディレクトリに出来上がった。試してみるには、出来上がった'castle.lev'を実行ディレクトリ(games/nethackのような、/Makefileの始めの方で指定した場所)にコピーして、ウィザードモードでネットハックを起動しよう。城までテレポートして、さまよう目の死体と目隠しを願って見てみよう、たくさんの茶色いHがいるはずだ!

さて今度はオリジナルを作ってみる番だ。僕たちがやりたかったのは*新しい*階層を作ることであって今あるものをいじることじゃ無かったからね。castle.desから離れてnewlevel.desを作ってみよう!

まずそのために、別のコンパイラを紹介しよう:dgn_comp,ダンジョンコンパイラだ。lev_compのようにこれにもdgn_comp.6というマニュアルがついている。このコンパイラのすることはこうだ:迷宮全体(階層の集まり)の設計図をみて、ネットハックのバイナリが読めるdungeonファイルをつくる。
問題の迷宮の設計図は"dungeon.def"と呼ばれている:

	DUNGEON:        "The Dungeons of Doom" "D" (25, 5)
	ALIGNMENT:      unaligned
	%MULDGN BRANCH: "The Gnomish Mines" @ (2, 3)
	%REINCARNATION LEVEL:           "rogue" "R" @ (15, 4)
	LEVEL:          "oracle" "O" @ (5, 5)
	LEVALIGN:       neutral
	LEVEL:          "bigroom" "B" @ (10, 3) 15
	%MULDGN CHAINBRANCH:    "The Quest" "oracle" + (6, 2) portal
	%MULDGN BRANCH:         "Fort Ludios" @ (18, 4) portal
	RNDLEVEL:       "medusa" "none" @ (-5, 4) 2
	LEVALIGN:       chaotic
	LEVEL:          "castle" "none" @ (-1, 0)
	CHAINBRANCH:    "Gehennom" "castle" + (0, 0) no_down
	BRANCH:         "The Elemental Planes" @ (1, 0) no_down up

おそらく知っての通り、運命の大迷宮には特殊な階層(e.g.ビッグルーム、オラクル)と分岐がある。一番簡単なのはただ階層を加えることだ。
mylevelという名前の階層をmylevel.desに作ったとしよう:

  MAZE:"mylevel",' '

(*重要*:ファイル自体の名前はMAZEの行で指定したものと同じであることが多いが、.levファイルの名前はMAZEの行で指定したものがとられることに留意!!)

運命の大迷宮にこの階層を加えるには、例えばビッグルームの後に、

  LEVEL:	"mylevel" "none" @ (15,2)

と入れればいい。これは、mylevelという階層を地下15+-2階の深さに作り、骨ファイルを残さないこと('none'の部分)を示している。
その後、../util/dgn_comp dungeon.def を走らせて'dungeon'ファイルを得よう。それをゲームディレクトリにコピーして試してみよう(ここではウィザードモードのコマンド、Ctrl-Oが便利だろう)。
もし新しい階層を読み込むときに何らかのエラーが発生した場合、ネットハックは代わりにランダムに普通の階層をつくる。

最後に、もし新しい分岐を作りたいときには、次のように入力する:

  %MULDGN BRANCH: "newbranch" @ (18, 1)
  

その後に続けて、分岐の階層を指定していく。

  DUNGEON:        "newbranch" "S" (4,0)
  DESCRIPTION:    mazelike
  LEVEL:          "mylevel1" "none" @ (1, 0)
  LEVEL:          "mylevel2" "none" @ (2, 0)
  LEVEL:          "mylevel3" "none" @ (3, 0)
  LEVEL:          "mylevel4" "none" @ (4, 0)

さあ、早速.desファイルをいじって文法に慣れてみよう。新しい階層や分岐をつくるのは難しくなんかないだろう?
もしかしたらこの章で君のしたいことは全部実現できるかもしれない、でももっとおもしろいことがしたいなら、続きもご覧あれ。

6. 新しい怪物をつくる

     "気をつけろ!もう戻って来れないかもしれないぞ!それでも読む?(y/n)"

ようこそ、モルドールの闇部へ...苦しいかもしれないが見返りは大きい:開発チームの祝福を受けた変更は不死の力を得ようぞ! そしてなんにせよ、自分で改変したネットハックは楽しいよ。

さあ、はじめようか。

まず始めに、二つの巨大な配列があることを覚えておこう:一つは全ての怪物を定義したもの、もう一つは全てのアイテムを定義したものだ。前者は'mons'と呼ばれていて、'permonst'構造体型の要素を持ち、src下のmonst.cで*2定義されている。'permonst'型はpermonst.hで定義されていて、それを見ればこの構造体が何のためにあるのかわかるだろうけど、簡便のためにここで新しくmonsに加えるものを見てみよう。

       {"mymonster", S_HUMAN, 1, 10, 10, 0, 0, G_GENO | G_NOGEN,
       { { AT_WEAP, AD_PHYS, 1, 6 }, NO_ATTK, NO_ATTK,
         NO_ATTK, NO_ATTK, NO_ATTK },
       WT_HUMAN, 400, PL_NSIZ, MS_HUMANOID, MZ_HUMAN, 0, 0,
       M1_NEEDPICK | M1_HUMANOID | M1_OMNIVORE,
       M2_HUMAN | M2_STRONG | M2_COLLECT, 0, C(HI_DOMESTIC)}
  • name: ("mymonster") 怪物の名前
  • symbol: (S_HUMAN) 使われる文字。monsym.hに全て定義してある。
  • level: (1) このレベルに達して初めてこの怪物が生成される。
  • move rate: (10) 速さ。0 -全く動かない- から 30 -かなり素早い- まで
  • AC?: (10) ようくご存じでしょ?
  • magic resistance: (0) 0から127まで
  • alignment: (0) 属性。負の値は混沌、正の値は秩序を表す。
  • creation/geno flags: (G_GENO | G_NOGEN) 生成フラグ。以下のようなものがある。
       G_UNIQ          /* 一度しか生成されない */
       G_NOHELL        /* ゲヘナでは生成されない */
       G_HELL          /* ゲヘナ以外では生成されない */
       G_NOGEN         /* 自然生成されない */
       G_NOCORPSE      /* 絶対に死体を残さない */
       G_SGROUP        /* 通常小さな集団で生成される */
       G_LGROUP        /* 通常大きな集団で生成される */
       G_GENO          /* 虐殺可能 */
       一部訳略:その他はmonsym.h(現バージョン(3.4.3)ではmonflag.h)参照のこと
  • attack: ({ { AT_WEAP, AD_PHYS, 1, 6 }, NO_ATTK, NO_ATTK,NO_ATTK, NO_ATTK, NO_ATTK },) これは怪物の攻撃方法を定義する。六つの'attack'構造体を含み、それぞれ以下のようになる。
	  ( 攻撃方法, 攻撃の種類, ダイス倍数, ダイスの面 )

攻撃方法は以下のうち一つ:

       AT_NONE         /* 攻撃しない (ex. 酸のブロッブ) */
       AT_CLAW         /* 爪 (パンチ、叩く、 等々.) */
       AT_BITE         /* 噛みつき */
       AT_KICK         /* 蹴り */
       AT_BUTT         /* 頭突き (ex. ユニコーン) */
       AT_TUCH         /* 触る */
       AT_STNG         /* 刺す */
       AT_HUGS         /* 抱きつき さば折り? */
       AT_SPIT         /* 唾吐き - 遠距離攻撃 */
       AT_ENGL         /* 飲み込み (e.g. 紫ワーム、ほこりの渦) */
       AT_BREA         /* ブレス - 遠距離攻撃 */
       AT_EXPL         /* 爆発 - 近づいて */
       AT_GAZE         /* にらみ - 遠距離攻撃 */
       AT_TENT         /* 触手 */
       AT_WEAP         /* 武器を用いる */
       AT_MAGC         /* 魔法を用いる */

攻撃の種類は以下の一つ:

       AD_PHYS         /* 通常の物理攻撃 */
       AD_MAGM         /* 魔法の矢 */
       AD_FIRE         /* 火のダメージ */
       AD_COLD         /* 冷気ダメージ */
       AD_SLEE         /* 睡眠の矢 */
       AD_DISN         /* 分解 (死の光線) */
       AD_ELEC         /* 電撃ダメージ */
       AD_DRST         /* 力の低下 (毒) */
       AD_ACID         /* 酸のダメージ */
       AD_SPC1         /* buzz()((zap.c内にある光線の処理関数))用の拡張 */
       AD_SPC2         /* buzz()用の拡張 */
       AD_BLND         /* 盲目 (光る目) */
       AD_STUN         /* 眩暈 */
       AD_SLOW         /* 減速 */
       AD_PLYS         /* 麻痺 */
       AD_DRLI         /* レベルドレイン (ヴァンパイア等) */
       AD_DREN         /* 魔力吸い取り */
       AD_LEGS         /* 脚を傷つける (ザン) */
       AD_STON         /* 石化 (メデューサ、コカトリス等) */
       AD_STCK         /* 捕まえる (ミミック等) */
       AD_SGLD         /* 金を盗む (レプラコーン等) */
       AD_SITM         /* アイテムを盗む (ニンフ) */
       AD_SEDU         /* かどわかし、複数のアイテムを盗る(ニンフ) */
       AD_TLPT         /* 瞬間移動させる (量子物理学) */
       AD_RUST         /* 装備を錆びさせる (錆びの怪物)*/
       AD_CONF         /* 混乱させる (アンバーハルク) */
       AD_DGST         /* 消化する (トラッパー等) */
       AD_HEAL         /* 回復させる (看護婦) */
       AD_WRAP         /* 捕まえる (うなぎ等) */
       AD_WERE         /* 獣化病にする */
       AD_DRDX         /* 器用さを減らす (クアシト) */
       AD_DRCO         /* 耐久を減らす */
       AD_DRIN         /* 知性を減らす (マインドフレイアー等) */
       AD_DISE         /* 病気にする */
       AD_DCAY         /* 装備を腐らせる (茶色プリン) */
       AD_SSEX         /* 淫魔の誘惑 */
       AD_DETH         /* デス用 */
       AD_PEST         /* ペスティレンス用 */
       AD_FAMN         /* フェミン用 */
       AD_CLRC         /* 僧侶魔法 */
       AD_SPEL         /* ランダム魔法 */
       AD_RBRE         /* ランダムブレス */
       AD_SAMU         /* 魔除けを盗む (イェンダーの魔法使い) */
       AD_CURS         /* 能力を盗る (グレムリン等) */

ダイス倍数/ダイスの面はダメージをさだめる:

       1,6  ならばダメージは1から6の間
       3,7  ならばダメージは3から21の間

よって、怪物は一ターンに最大六回攻撃できる。それより少ない回数攻撃させるには、'NO_ATTACK'を使う。

  • weight: (WT_HUMAN) WT_DRAGON, WT_HUMAN,WT_ELFといった定数でもいいが、直接数を入力することもできる。
  • nutritional value: (400) 栄養価
  • extension length: (PL_NSIZ) さあここが難関。これは通常のpermonstに加えて必要な拡張部分のサイズだ。たとえば店主ならば店に関するコードが入るし、ペットならばなつき方ほかを入れる部分になる。とりあえずはこの項目は無視しよう。
  • sounds made: (MS_HUMAN). 発する音 (monflag.hより)
       MS_SILENT       /* 音を出さない */
       MS_BARK         /* 満月ならば吠える */
       MS_MEW          /* にゃー(ネコ等) */
       MS_ROAR         /* 吠える(ムーマク等) */
       MS_GROWL        /* うなる(吸魔の怪物等) */
       MS_SQEEK        /* ちゅーちゅー鳴く(ねずみ等) */
       MS_SQAWK        /* さえずる(鳥類) */
       MS_HISS         /* しゅー(蛇等) */
       MS_BUZZ         /* 羽音をたてる (殺人蜂等) */
       MS_GRUNT        /* ぶーぶー言う (あるいは独自の言語をしゃべる) */
       MS_NEIGH        /* いななく */
       MS_WAIL         /* もの悲しい音をだす(影) */
       MS_GURGLE       /* ごぼごぼ (ショウビレックス) */
       MS_BURBLE       /* ぶくぶく (ジャバウォック) */
       MS_ANIMAL       /* 動物の音をたてる */
       MS_SHRIEK       /* 叫んでほかの怪物を目覚めさせる */
       MS_BONES        /* 骨をならす (骸骨) */
       MS_LAUGH        /* ニヤニヤ、クスクス、嗤う */
       MS_MUMBLE       /* 何かを言う */
       MS_IMITATE      /* 何かをまねる (レオクロッタ) */
       MS_ORC          /* オークとしてしゃべる */
       MS_HUMANOID     /* 一般的な人型生物 */
       MS_ARREST       /* "Stop in the name of the law!" (警備員等) */
       MS_SOLDIER      /* 軍人として話す */
       MS_GUARD        /* "Please drop that gold and follow me" (金庫の番人) */
       MS_DJINNI       /* "Thank you for freeing me!" (ジン)*/
       MS_NURSE        /* "Take off your shirt, please." (看護婦)*/
       MS_SEDUCE       /* "Hello, sailor." (ニンフ) */
       MS_VAMPIRE      /* (吸血鬼等) */
       MS_BRIBE        /* 通行料を要求する */
       MS_CUSS         /* 非難したり (悪魔等) 脅したりする (イェンダーの魔法使い) */
       MS_RIDER        /* 天上界の乗り手たち */
       MS_LEADER       /* クエストリーダー */
       MS_NEMESIS      /* クエストネメシス */
       MS_GUARDIAN     /* クエストの仲間 */
       MS_SELL         /* 支払いを求めたり、万引きについて不平をもらす */
       MS_ORACLE       /* 占う */
       MS_PRIEST       /* 寄付をもとめたり、文無しを追っ払ったりする */
  • physical size: (MZ_HUMAN) 大きさ。以下のうち一つ
       MZ_TINY         0               /* < 2' */
       MZ_SMALL        1               /* 2-4' */
       MZ_MEDIUM       2               /* 4-7' */
       MZ_HUMAN        MZ_MEDIUM       /* 人の大きさ */
       MZ_LARGE        3               /* 7-12' */
       MZ_HUGE         4               /* 12-25' */
       MZ_GIGANTIC     7               /* 計り知れない大きさ */
  • resistance : (0) 食べた時得られるかもしれない特性
       MR_FIRE         /* 火に対する耐性 */
       MR_COLD         /* 冷気に対する耐性 */
       MR_SLEEP        /* 睡眠に対する耐性 */
       MR_DISINT       /* 分解に対する耐性 */
       MR_ELEC         /* 電撃に対する耐性 */
       MR_POISON       /* 毒に対する耐性 */
       MR_ACID         /* 酸に対する耐性 */
       MR_STONE        /* 石化に対する耐性 */
  • First group of flags: (M1_NEEDPICK | M1_HUMANOID | M1_OMNIVORE) 怪物のふるまいのフラグ
       M1_FLY          /* 浮かんだり飛んだりする */
       M1_SWIM         /* 水中を移動できる */
       M1_AMORPHOUS    /* ドアから染み出ることができる */
       M1_WALLWALK     /* 壁の中を動ける */
       M1_CLING        /* 天井に張りつける */
       M1_TUNNEL       /* 壁を掘り進める */
       M1_NEEDPICK     /* つるはしを持てば壁を掘り進める */
       M1_CONCEAL      /* アイテムの下に隠れられる */
       M1_HIDE         /* 隠れることができる */
       M1_AMPHIBIOUS   /* 水中でも生きることができる */
       M1_BREATHLESS   /* 呼吸しない */
       M1_NOEYES       /* 眼が無い */
       M1_NOHANDS      /* 手が無い */
       M1_NOLIMBS      /* 脚が無い */
       M1_NOHEAD       /* 頭が無い */
       M1_MINDLESS     /* 意思を持たない */
       M1_HUMANOID     /* 人型である */
       M1_ANIMAL       /* 動物型である */
       M1_SLITHY       /* 蛇型である */
       M1_UNSOLID      /* 物質的な体を持たない */
       M1_THICK_HIDE   /* 厚い皮や鱗を持つ */
       M1_OVIPAROUS    /* 卵を産める */
       M1_REGEN        /* 超回復能力を持つ */
       M1_SEE_INVIS    /* 透明視できる */
       M1_TPORT        /* 瞬間移動できる */
       M1_TPORT_CNTRL  /* 瞬間移動制御できる */
       M1_ACID         /* 酸を持つ */
       M1_POIS         /* 毒を持つ */
       M1_CARNIVORE    /* 動物食である */
       M1_HERBIVORE    /* 植物食である */
       M1_OMNIVORE     /* 雑食である */
       M1_METALLIVORE  /* 金属食である */
  • Second group of flags: (M2_HUMAN | M2_STRONG | M2_COLLECT) 怪物の特性を示す
       M2_NOPOLY       /* プレイヤーは変化できない */
       M2_UNDEAD       /* アンデッドである */
       M2_WERE         /* 獣人である */
       M2_ELF          /* エルフである */
       M2_DWARF        /* ドワーフである */
       M2_GIANT        /* 巨人である */
       M2_ORC          /* オークである */
       M2_HUMAN        /* 人である */
       M2_DEMON        /* 悪魔である */
       M2_MERC         /* 番人や兵士である */
       M2_LORD         /* その種の王である */
       M2_PRINCE       /* その種の王子である */
       M2_MINION       /* 神の僕である */
       M2_MALE         /* 常にオス/男で生成される */
       M2_FEMALE       /* 常にメス/女で生成される */
       M2_NEUTER       /* オスでもメスでもない */
       M2_PNAME        /* 名前が固有名詞である */
       M2_HOSTILE      /* 常に敵対的である */
       M2_PEACEFUL     /* 常に友好的である */
       M2_DOMESTIC     /* 餌付けしてペットにできる */
       M2_WANDER       /* ふらふら動く */
       M2_STALK        /* 別の階へも追っていく */
       M2_NASTY        /* 特にいやらしい怪物である (経験値増加) */
       M2_STRONG       /* 強い/大きい怪物である */
       M2_ROCKTHROW    /* 巨岩を投げる */
       M2_GREEDY       /* 金が好きである */
       M2_JEWELS       /* 宝石が好きである */
       M2_COLLECT      /* 武器と食べ物を拾う */
       M2_MAGIC        /* 魔法のアイテムを拾う */
  • Third group of flags: (0) 怪物の特性
       M3_WANTSAMUL    /* 例の魔除けを欲する */
       M3_WANTSBELL    /* 銀のベルを欲する */
       M3_WANTSBOOK    /* 死者の書を欲する */
       M3_WANTSCAND    /* 燭台を欲する */
       M3_WANTSARTI    /* クエストアーティファクトを欲する */
       M3_WANTSALL     /* アーティファクトを欲する */
       M3_WAITFORU     /* あなたに気づくまでじっとする */
       M3_CLOSE        /* 攻撃されない限り親密でいる (話しかける) */
       M3_COVETOUS     /* 強欲 (イェンダーの魔法使い) */
       M3_WAITMASK     /* waiting... (内部用マスクのようだ) */
  • symbol color: (C(HI_DOMESTIC)) 色。C(RED), C(BROWN), C(HI_DOMESTIC),等々 (HI_DOMESTIC は友好的な怪物の色)。

あなたは崇高なる開発チームの定めし以下のルールを守らなければならない。

  • ルール #1: 同種の怪物はmons[]の中で連続していなければならない。
  • ルール #2: 同種の怪物は強さの順に並んでいなければならない
  • ルール #3: 怪物の生成頻度は生成フラグに含む (G_GENO|4 のような形となる)。0から7の値で、0はG_NOGENと同じ意味である。
  • ルール #4: 同副種類(巨人等)の怪物はルール2に反しない限りまとめてなければならない、ただし、生成されない怪物はルール2に反しない。

これで完成だ。もちろん既に存在する怪物をコピーするところから始めた方が楽だよ。 たとえば、強力なケンタウロスが要るときには草原のケンタウロスをコピーしてきてACと攻撃部分だけ変えてしまえばいい。

さあ、ソース全体を再コンパイルしなくては。そう、*ソース全体*だ。なぜって、配列monsはそれぞれの怪物ごとに一意な数を設定して後で参照するときに使うんだ。コンパイルする時に'makedefs'って文字列を見たこと有るだろ? アレが肝心要の部分で、各怪物、アイテムごとに#defineを生成しているんだ。だからpm.hを覗いてみると、

	/* This source file is generated by 'makedefs'.  Do not edit. */
	#ifndef PM_H
	#define PM_H
	#define PM_GIANT_ANT    0
	#define PM_KILLER_BEE   1
	#define PM_SOLDIER_ANT  2
	#define PM_FIRE_ANT     3

もうどういう仕組みかわかっただろう? ソースコード内では配列のどこに何があるのかを知っていなくても、定数PM_ほげほげをつかって'ほげほげ'の部分を参照することができるんだ。 もし君がmakeあるいはndmakeを正しく設定できているならmakeがきちんと再コンパイルをしてくれる。*.desファイルも再コンパイルされることに留意すること。

さあ、ウィザードモードで起動して怪物を作る巻物を願ってみよう。それを読んでmymonsterを出してみよう。やった! オリジナルの怪物を作れたぞ!

       重要:
       適切な#ifdef,#ifndefで元コードと自分のコードとを分けておくこと。include/config.hファイルの末尾にでも必要な#define SOMETHINGを書いておこう。

7. 怪物を改良する

7.0. 'monst'構造体

6章を読み終えた君のことだ、自分の怪物が運命の大迷宮をさまよい歩いていることだろう。けれど、何か物足りない。彼のためのアイテムや防具や武器、具体的なしゃべる内容、エトセトラ、エトセトラ...どうすれば解決できるだろう? 

今こそ'monst'構造体を紹介するときだ。この構造体は具体的な怪物を定義している。体力、なつき具合、その他もろもろだ。怪物の能力を定義している'permonst'構造体とは全然違う。'monst'構造体の要素はmonst.hに定義されている。

  -nmon:現在の階層にいる全ての怪物はリストに含まれている。
  Starting with a first monster descriptor ('fmon') you can navigate through
  all of them, thanks to this pointer to the next monster:
                   --------    --------   --------   --------
           fmon -> | nmon ---> | nmon --->| nmon --->| nmon ---> NULL
                   |      |    |      |   |      |   |      |
                   | MONST|    | MONST|   | MONST|   | MONST|
                   |  #1  |    |  #2  |   |  #3  |   |  #4  |
                   --------    --------   --------   --------
data
これはpermonst構造体をさすポインタ。たとえばイモリの重さなんかを各イモリの個体ごとに保持するのなんて馬鹿らしいからね。
m_id
怪物ごとに固有の値で、生成時につけられる。
mnum
固有の怪物番号。makedefsによってつけれらる。pm.hファイルで宣言されているPM_怪物名って奴。怪物の種類ごとに固有だ。
m_lev
変動後の怪物の倒しにくさレベル. つまり,怪物のレベルは初期値のそれから変化しうる.(経験とか,薬などで…)
malign
この怪物の属性。正の値ならば殺してもいいってことだ。
mx, my
地図上でのこの怪物の場所(x,y-座標で)。
mux,muy
怪物に君がいるとおもわれている場所。透明の能力や幻影の能力で実際の場所とは違うこともある。
mtrack
Monster track???. 行動ルーチンに使われているみたいだけど,僕にはそれがなんなのかをはっきりさせられなかった.
mappearance
ミミックやあの魔法使いのための場所。あなたからどう見えるか。
m_ap_type
mappearanceが示しているもの。以下のようになっている:
M_AP_NOTHING 0
/*mappearance は使われない --怪物はそれ自身の形に見える */
M_AP_FURNITURE 1
/* 階段、扉、祭壇 等など。 */
M_AP_OBJECT 2
/* なんらかのアイテム */
M_AP_MONSTER 3
/* 怪物 */
mtame
なつき度合い. もし0以上なら怪物は手なずけられていて,君を攻撃しようとしない(手懐けられた怪物がプレイヤーに怒っているときとは異なる)。
mspec_used
怪物の能力の制限時間. このカウンタが0になるまで怪物固有の能力 -ドラゴンのブレスとか- は使わない。
female
雌か否か。 1 = yes, 0 = no.
minvis
怪物が透明かどうか。 1 = yes, 0 = no.
cham
怪物が実はカメレオンかどうか。 1 = yes, 0 = no.
mundetected
怪物が隠れていて(蛇とかのように)プレイヤーから見つかっていないか。 1 = yes, 0 = no.
mcan
怪物が無力化の杖(もしくは魔法)を受けたか。
mspeed
怪物の速度に関する状態。 2ビットで4状態を表す。
mflee
怪物が今畏れをなしているか。
mfleetim
(7 bits) 畏れが解除されるまでの時間。
mcansee/mblinded
怪物の目が見えるか / 暗闇状態の時間。
mcanmove/mfrozen
怪物が動けるか / 動けるまでの時間。
msleep
怪物が眠っているか
mstun
monster is stunned.
mconf
怪物が混乱しているか。
mpeaceful
怪物が友好的か。
mtrapped
怪物が罠にかかっているか。
mleashed
紐が使われているか。
isshk
怪物が店主かどうか。
isminion
怪物が神の御使いかどうか。
isgd
怪物が番兵かどうか。
ispriest
司祭かどうか。
iswiz
イェンダーの魔法使いかどうか。
wormno
ワームのとき、ワームが作成されるごとに数値を設定する(階層内において31匹が最大数)。
mstrategy
継続的な戦略 (とても特別な怪物に使われる)。
(訳注:mflag3に基づくこのモンスターの行動原理に使用される。眠り続けたり、移動したり、移動先位置など)
mtrapseen
かかったことのあるトラップの種類別対応表。
mlstmv
1度に2回行動するのを防ぐ(?)。
(訳注:移動したときにそのターンは攻撃行動を起こさないようにする)
mgold
この怪物がどれだけ金持ちか。
minvent
'obj'構造体へのポインタ。このアイテム群は怪物が独自に持つ'monst'構造体と同じような連鎖リストの始点である。(アイテムを全く持っていない場合はNULLになる。)
mw
この怪物が武器を装備しているならば、その武器。
misc_worn_check
ワーム制御コード用のなんらかの値 (?)。
(訳注:ワーム制御用ではなく,防具や鞍などの装備有無を管理.)
weapon_check
怪物が武器を拾ったときにチェックするフラグ。
mnamelth
怪物に付けられた名前の長さ。
mxlth
拡張部分の構造体のサイズ。怪物ごとに異なる。ほとんどがこれを持つ。
meating
怪物の食事のタイムアウト。
mextra
拡張部分の構造体へのアクセスポインタ。 例えば、店主は頻繁にこれを使用する。

がああああ! この構造体大きすぎ! そう思ってるだろうけど、気にしないで。少なくとも初めのうちは全部を相手にする必要なんてないんだから。

まず初めに次の質問に答えてみよう。『この怪物はM-FLAGで設定できなかったような特別なアイテムや行動が必要か?』 イエスならばmakemon.cを開いてm_initweap()を見たまえ。

  (NOTE to UNIX users:ctagsを使うと便利だよ
  関数を探すときは、ちょっとしたツールで驚くほど効率的になるよ:
    'ctags'コマンドでアイテム用のインデックスを作るには
    以下の行を実行する。
		ctags *.c          (srcディレクトリー配下で)
    これで'TAGS'ファイルが生成されるはずだ。 この後コマンド'vi -t m_initweap'
    を実行すればすぐにm_initweap関数の実装箇所を得られるだろう。
    また、'grep'コマンドは文字列を検索するのに向いている。 例えば、
    カメレオンのコードを探したい? 次のようにすればいい。
		grep -i chamaleon | more
  End of NOTE)

このm_initweap()関数は怪物がアイテムを得るところだ。このとてつもなく巨大なswitch文(switch(mtmp->data->mlet) )でどんなアイテムを必要とするか定めている。これはmongets()関数とともに使われているんだ*3

一番簡単な方法は、似たコードを探してコピーしてくること。既にあるアイテムを持たせるときには楽なはずだ。もし怪物の武器や防具をランダムに持たせたかったらrn2()を使おう。

本当に面白い怪物は自分専用のアイテムや攻撃方法を持っているだろう(そうでないならそれはただ普通の怪物の亜種でしかない。強いイモリ、弱っちいドラゴン、...)。新しいアイテムをゲームに加えたければ次の章へ飛ぼう。さあ、次は新しい攻撃や振る舞いの加え方だ。

7.1. 新しい攻撃タイプの追加

monattk.hファイルを編集しよう。 AT_TENTが定義されている行(3.1.3時点)の後ろに次のような行を追加しよう。

    #define AT_HELLO    16    /* こんにちわ!と言って君を殺してみる */

これで新しい攻撃タイプになる。 今度は、新しいダメージを定義してみよう。AD_FAMNの定義の後ろに次の行を追加する。

    #define AD_HELLO	   39    /* 君は挨拶される */

今度はsrcディレクトリに移動し、mhitu.cファイル*4を開く。 hitmsg()関数の中にAT_HELLO攻撃タイプに関するコードを追加する。 たとえば次のコードの直後に:

               case AT_EXPL:
               case AT_BOOM:
#if 0 /*JP*/
                       pline("%s explodes!", Monnam(mtmp));
#else
                       pline("%sは爆発した!", Monnam(mtmp));
#endif
                       break;
               case AT_MULTIPLY:
                       /* No message. */
               break;

次のようなコードを追加する:

               case AT_HELLO:
#if 0 /*JP*/
                       pline("%s says hello to you!", Monnam(mtmp));
#else
                       pline("%sはこんにちはと言った!", Monnam(mtmp));
#endif
                       break;

ここで僕たちが最もよく使う関数の一つpline()を紹介しよう。 これは画面にメッセージを一つ表示するんだ。 (詳しくは付録Aを見てくれ)

次にmattacku()関数に注目しよう。 AT_HELLO攻撃用のコードをこの中の'switch(mattk->aatyp)'に追加することになる。 最も簡単なのは'hand to hand'のケースに追加する方法だ。

今度はhitmu()関数を見てくれ。 ここにAD_HELLO用のコードを追加する。 例えば次のコードの直後に:

           case AD_FAMN:
#if 0 /*JP*/
               pline("%s reaches out, and your body shrivels.",
#else
               pline("%sは腕を伸ばした、あなたの体はしなびた。",
#endif
                       Monnam(mtmp));
               exercise(A_CON, FALSE);
               if (!is_fainted()) morehungry(rn1(40,40));
               /* plus the normal damage */
               break;

次のようなコードを追加する:

           case AD_HELLO:
               hitmsg(mtmp, mattk);
               make_confused(2,FALSE);
               break;

見てのとおりこの新しい攻撃はアンバーハルクの睨みのように混乱させる。

これでmhitu.cファイルの編集は終わりだ。 今度はmhitm.c*5を同様に処理する。 つまり、怪物の場合のAT_HELLOによる攻撃は別物にすることができるんだ。

7.2. 新しい怪物の能力の追加

この章では、怪物用の行動を追加する方法について説明するよ。 例として、怪物が解呪の巻物を読めるようにするコードを追加してみよう。

muse.cファイルを開いて#define MUSE_WAN_SPEED_MONSTER 7 の行を探して新しい能力を追加するんだ。

#define MUSE_SCR_SCARE_MONSTER 8

その次に、find_misc関数内の次のコードを探し、

       if(obj->otyp == WAN_POLYMORPH && obj->spe > 0 && !mtmp->cham
       		&& monstr[monsndx(mdat)] < 6) {
       	m.misc = obj;
       	m.has_misc = MUSE_WAN_POLYMORPH;
       }

次のコードを追加する:

       if(obj->otyp == SCR_REMOVE_CURSE) {
               m.misc =obj;
               m.has_misc = MUSE_SCR_REMOVE_CURSE;
       }

これで怪物は潜在的な行動として解呪の巻物を探せるようになる。use_misc()関数内部で実際に行動を行うコードを追加しよう。

次のコードの直後に

       case MUSE_WAN_POLYMORPH:
               mzapmsg(mtmp, otmp, TRUE);
               otmp->spe--;
#if 0
               (void) newcham(mtmp, muse_newcham_mon(), TRUE, vismon);
#else
/*JP
               (void) mon_poly(mtmp, FALSE, "%s changes!");
*/
               (void) mon_poly(mtmp, FALSE, "%sは姿を変えた!");
#endif
               if (oseen) makeknown(WAN_POLYMORPH);
               return 2;

次のコードを追加しよう。

       case MUSE_SCR_REMOVE_CURSE:
           {
               register struct obj *obj;
               mreadmsg(mtmp,otmp);
#if 0 /*JP*/
               pline("%s feels than someone is helping %s.",Monnam(mtmp),
                                mtmp->female?"her":"him");
#else
               pline("誰かが%sを助けているような気がした。",Monnam(mtmp));
#endif
               if (!otmp->cursed) {
                  for(obj=mtmp->minvent;obj;obj=obj->nobj)
                     if (otmp->blessed || obj->owornmask ||
                        (obj->otyp == LOADSTONE)) {
                           if (mtmp->mconf) blessorcurse(obj,2);
                           else uncurse(obj);
                     }
               }
               if (oseen && !objects[SCR_REMOVE_CURSE].oc_name_known
                    && !objects[SCR_REMOVE_CURSE].oc_uname)
                    docall(otmp); /* not makeknown() */
               m_useup(mtmp, otmp);
               return 2;
           }

7.3. 'mextra'構造体の追加

怪物の行動を面白いものにするにはmonst構造体への新しい変数の定義をするべきだよね。例として、新しい怪物が神に祈れるようにする追加をしよう。まず思いつくのは、怪物が2ターン連続で祈ってしまうのタイムアウトを持たせることだ。そのためには新しい要素を'struct monst'に次のように追加すればいい:

       long praytime;

だけど、これだと僕たちの怪物しか使わない4バイト割り当てられた変数を全ての怪物が持つことになる。

その代わりに新しい構造体を定義するといい。 君専用の新しいインクルードファイルでやるのが一番だよ。

       struct onlymymonster {
               long praytime;
       };

そして、monst.c内のこのモンスターの定義のextension length領域に'sizeof(struct onlymymonster)'を設定するんだ。 新たな変数を参照するには次のようにする(monはmonst構造体へのポインタ):

       ((struct onlymymonster *)&(mon)->mextra[0])->praytime

このような読みにくい記述を短縮するには#defineを使えばよい:

   #define MYMONSTER(mon)   ((struct onlymymonster *)&(mon)->mextra[0])

そうすれば次のように書けるようになる

   MYMONSTER(mon)->praytime

mextra部分の使い方の例はeshk.h(店主用のコード)を参照するといいよ。

7.4. databaseへの項目追加

databseソースファイル (dat/data.base) *6 はコメント('#'で始まる行)の点在する多数の項目で構成されている。

それぞれの項目は、独立した行になっている多数のパターンで構成されているんだ。 怪物やアイテムがプレイヤーに表示される説明文(ほとんどは引用)によって説明されてて、パターンはそれの識別に使われてるんだ。

7.4.1. パターン一覧

パターン一覧の最も簡単な形式は、同じテキストを表示するアイテムや怪物の名前の一覧で構成されるんだ。 例えば次のとおり:

	インキュバス
	サキュバス
	incubus
	succubus
		インキュバスとサキュバスは同じ種類の魔神で、それぞれ男性と女
		性である。それらと関係を持つ愚かな人間は大抵手痛い目に合う。

注:この文章で例題をあげるにあたってインデントしてある。 実際のdata.baseでは、パターンは行頭に詰めて記述し、説明文は_1_タブあけて書くこと。

全てのテキストを列挙するだけでは充分じゃない場合もたまにある。 そんなときのために、'?'と'*'の特殊文字が使用可能だ。 '?'は一文字に対応して、'*'は0文字以上に対応する。 例えば次のとおり:

	*巨人
	*giant
	giant humanoid
		巨人はいつも大地を歩いていたが,最近では彼らは非常に希な存在
		となっている.彼らのサイズと言ったら,9フィートたらずのから
		空にもそびえる20フィート以上のものまでいる.大きなものは巨大
		な岩を武器として使い,遠くからでもそれを投げつける.全ての種
		類の巨人は人間(焼いたり,煮たり,揚げたりしたもの)が大好き
		という点で共通している.彼らのテーブルマナーは伝説的だ.

この場合、"*giant"パターンは次の怪物に対応する:

	giant, stone giant, hill giant, fire giant, frost giant, storm giant.

「*巨人」パターンには次の怪物に対応する:

	巨人、岩石巨人、丘の巨人、炎の巨人、吹雪の巨人、雷の巨人

特殊文字を含むパターンに無関係なテキストがマッチしちゃう場合は、これを取り除かなきゃならない。 これを可能にするために、取り消しパターンを複数列記することができる。 取り消しパターンは他のパターンよりもパターン一覧の前の方に記述しなきゃだめだよ。 もしそれがマッチしている場合はそれがある項目自体を無視するんだ。 このパターンは'~'を先頭につけることで記述する*7。 例えば次のとおり:

	*モールド
	カビ
	~slime mold
	*mold
		モールド,カビ,糸状菌.菌類に属する多細胞有機体,綿のような単
		繊維組織網で構成された植物の体の典型.モールドの特色は単繊維で
		の胞子の発芽に帰する.ほとんどのモールドは死体寄生である.ある
		特定の種は(例えばペニシリン,青カビ)はチーズや抗生物質を作るの
		に使われる.
		[ The Concise Columbia Encyclopedia ]

"~slime mold"パターンを列記しなかったら、 プレイヤーがslime moldの情報を見ようとしたときに この項目が表示されてしまう*8

最後に、上記で述べなかった点をいくつか記述しておく。

  • パターンは空白やタブ文字で始めることができない。 これは空白で始まるものは説明文とみなされることから起こる問題である。
  • 対応するものが見つかるまでdata baseにおける記載順に項目がスキャンされる (見つかった時点でスキャンは停止する)。 これは2つ以上の項目に対応するパターンに合致するテキストがあったときに最初の項目が次の項目を隠してしまうことを意味する。
    通常はこれ(を取り消しパターンの代わりに使うの)は避けるべきではあるけど、 有益である場合もあるかもしれない。
  • テキストはパターンと比較される前に小文字に変換されるため、 全てのパターンは小文字で記述すべきである。
  • ユーザが複数形で入力した場合、このゲームはアイテムやモンスターは単数形に変換しようとする。 従って、通常はパターン一覧に単数形と複数形を両方指定する必要はない。

7.4.2. 説明文

説明文は接頭字がタブ文字の複数の行から構成される(テキスト出力時にはこの接頭字は取り除かれる)。

次のASCIIマークアップ規約が適用される:

    _xxx_                        xxxを強調表示
    [xxx]                        xxxをボールド表示
    - (語中)                     ハイフン
    - (数字の間)                 半角ダッシュ
    - (空白で囲まれた)           全角ダッシュ
    ...                          省略記号
    ... (行中にそれだけ)         縦の省略記号
    "xxx"                        xxxを会話文として囲む
    number'                      数字をフィート表示

注: これらはゲーム中においてまったく解釈はされないけど、 一貫とした形式でプレーヤーにテキストを表示するのに役立つ。

行は、右行端未調整(つまり左揃え)で、67文字より短くしなきゃならない。 パラグラフの分割は前節を短い行で示すべきだ(字下げは使うべきではない)。 パラグラフ間の空行は、引用もしくは他の特殊な目的のために使用するべきだ (詩における句の間の分離等)。 終止符(および短文を終える他の句読点)には2つの空白文字を続けるべきだ。

(未翻訳)8. 新しいアイテムの追加

8.0. 'objects'配列

8.1. 道具の追加

8.2. 杖の追加

8.3. 薬の追加

8.4. 防具/武器の追加

8.5. 指輪の追加

8.6. 魔除けの追加

8.7. 魔法書の追加

8.8. 食料の追加

(未翻訳)9. 新しい種類の部屋の作成

(未翻訳)10. 新しい種類の店の作成

(未翻訳)11. 最後に

(未翻訳)付録A リファレンス関数一覧