ソースコード
http://members.shaw.ca/rob.ellwood/sources.txtより勝手に和訳。
この項目はまだ書きかけです。
別翻訳: http://sourceforge.jp/projects/jslashem/docs/jsources/ja/jsources.txt
ネットハックを何[ヶ月|年]も楽しんでいるかい?何かこのゲームに付け加えるいいアイデアがあるのに、誰も話を聞いてくれないなんて悩んでないかい?Cコンパイラがあるマシンにアクセス出来るかい?プログラムを組んだことあるかい?もしこれらの質問に“はい”と答えるなら、この文書は君の役に立てるかもしれない。
'94くらいからネットハックのソースを覗きはじめたんだ。このゲームが好きだったからいくつか新しい怪物や機能をつけようと考えたんだけど、なかなかに骨の折れる作業だった。私はCの初心者というわけでは無かったんだが、100(あるいはそれ以上!)もあるソースファイルは私の手に負えるものじゃなかった - main()を探すことにすら苦労したんだ。ソースファイル中のコメントは一見さんには役立たないものばっかりだったし。
今、九ヶ月かかって、私はこのソースのおおまかな構造は捉えることができた。新しい怪物、アイテム、部屋、階層、ふるまいを加えることができるようになった。私 -おっと念のため 私は開発チームの者ではないですよ- はこの巨大な化け物みたいなソースを全部詳細に理解しているわけではないから、この文書は完璧なソースガイドではなくて新しいアイデアの実装の助けになることを目標としている。
多分この文書には間違ってる箇所もあると思う。そして、多分…じゃなくて、きっと不完全な箇所があるだろうとも。OK,バグ/コメント/付記は私に送ってくれ、変更/追記するから。
お願いだから、この文書は自己責任で使用してほしい。いらいら以外得るものが無くても、非難しないでくれ。私は善意で助けようとしただけなんだ。
この文書の全ての内容はNethack 3.1.3(この文書を書いてる時点での公式最新版)に基づいています。
さあ、君は先週ネットハックを始めて、Unixマシンのアカウントを手にして、“そうだ!いいことを思いついたぞ!この文書に従っていくだけでネットハックを改変しながらCを会得できる!”なんて思いついたなら、*やめておけ*。レオナルド・ダ・ヴィンチの生まれ変わりでもない限り無理だから。
言い換えよう。この文書を読むためには少なくとも次のものが必要だ:
まずあなたは自分でソースをコンパイルしなくてはいけない。別に全行程を理解していなくともよいけれど、公式ソースを持ってきて動かせないと。ディレクトリ最上層のREADMEと/sys/XXX/Install.XXX (XXXはあなたのOSによりけり)を参照のこと。
ちゃんとコンパイルに通せれば準備は完了だ。
恐らくは知っての通り、ウィザードモードはいくつかのコマンドを増やしてネットハックを起動するモードだ。
^E == 隠された扉や罠を見つける。 ^F == 魔法の地図の魔法を唱える。 ^G == 怪物を作る。 ^I == 背負い袋の中のアイテムを識別する。 ^O == 特別な階層の位置を知る。 ^T == 同じ階層内でテレポートする。 ^V == 違う階層へテレポートする。 ^W == 願う。 ^X == 能力を見る。
それに、いくつかの振る舞いが変わる。e.g. 怪物を作る巻物を読んだとき、怪物を特定できる。(訳注:怪物の特定以外の違いもあったんで、i.e.じゃなくてe.g.で。)このモードを使えば変更点をすぐに確かめることができる。始めるにはこうタイプするだけでいい:
nethack -u wizard -D
これでうまくいくはずだけど、次の点に注意:
ネットハックを改良していく中で、このモードをしっかり活用しなければいけないだろう。*1
新しい階層を作るのは簡単だ。実を言うと、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ファイルをいじって文法に慣れてみよう。新しい階層や分岐をつくるのは難しくなんかないだろう?
もしかしたらこの章で君のしたいことは全部実現できるかもしれない、でももっとおもしろいことがしたいなら、続きもご覧あれ。
"気をつけろ!もう戻って来れないかもしれないぞ!それでも読む?(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)}
G_UNIQ /* 一度しか生成されない */ G_NOHELL /* ゲヘナでは生成されない */ G_HELL /* ゲヘナ以外では生成されない */ G_NOGEN /* 自然生成されない */ G_NOCORPSE /* 絶対に死体を残さない */ G_SGROUP /* 通常小さな集団で生成される */ G_LGROUP /* 通常大きな集団で生成される */ G_GENO /* 虐殺可能 */ 一部訳略:その他はmonsym.h(現バージョン(3.4.3)ではmonflag.h)参照のこと
( 攻撃方法, 攻撃の種類, ダイス倍数, ダイスの面 )
攻撃方法は以下のうち一つ:
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'を使う。
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 /* 寄付をもとめたり、文無しを追っ払ったりする */
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 /* 計り知れない大きさ */
MR_FIRE /* 火に対する耐性 */ MR_COLD /* 冷気に対する耐性 */ MR_SLEEP /* 睡眠に対する耐性 */ MR_DISINT /* 分解に対する耐性 */ MR_ELEC /* 電撃に対する耐性 */ MR_POISON /* 毒に対する耐性 */ MR_ACID /* 酸に対する耐性 */ MR_STONE /* 石化に対する耐性 */
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 /* 金属食である */
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 /* 魔法のアイテムを拾う */
M3_WANTSAMUL /* 例の魔除けを欲する */ M3_WANTSBELL /* 銀のベルを欲する */ M3_WANTSBOOK /* 死者の書を欲する */ M3_WANTSCAND /* 燭台を欲する */ M3_WANTSARTI /* クエストアーティファクトを欲する */ M3_WANTSALL /* アーティファクトを欲する */ M3_WAITFORU /* あなたに気づくまでじっとする */ M3_CLOSE /* 攻撃されない限り親密でいる (話しかける) */ M3_COVETOUS /* 強欲 (イェンダーの魔法使い) */ M3_WAITMASK /* waiting... (内部用マスクのようだ) */
あなたは崇高なる開発チームの定めし以下のルールを守らなければならない。
これで完成だ。もちろん既に存在する怪物をコピーするところから始めた方が楽だよ。 たとえば、強力なケンタウロスが要るときには草原のケンタウロスをコピーしてきて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を書いておこう。
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 | -------- -------- -------- --------
がああああ! この構造体大きすぎ! そう思ってるだろうけど、気にしないで。少なくとも初めのうちは全部を相手にする必要なんてないんだから。
まず初めに次の質問に答えてみよう。『この怪物は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()を使おう。
本当に面白い怪物は自分専用のアイテムや攻撃方法を持っているだろう(そうでないならそれはただ普通の怪物の亜種でしかない。強いイモリ、弱っちいドラゴン、...)。新しいアイテムをゲームに加えたければ次の章へ飛ぼう。さあ、次は新しい攻撃や振る舞いの加え方だ。
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による攻撃は別物にすることができるんだ。
この章では、怪物用の行動を追加する方法について説明するよ。 例として、怪物が解呪の巻物を読めるようにするコードを追加してみよう。
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; }
怪物の行動を面白いものにするには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(店主用のコード)を参照するといいよ。
databseソースファイル (dat/data.base) *6 はコメント('#'で始まる行)の点在する多数の項目で構成されている。
それぞれの項目は、独立した行になっている多数のパターンで構成されているんだ。 怪物やアイテムがプレイヤーに表示される説明文(ほとんどは引用)によって説明されてて、パターンはそれの識別に使われてるんだ。
パターン一覧の最も簡単な形式は、同じテキストを表示するアイテムや怪物の名前の一覧で構成されるんだ。 例えば次のとおり:
インキュバス サキュバス 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。
最後に、上記で述べなかった点をいくつか記述しておく。
説明文は接頭字がタブ文字の複数の行から構成される(テキスト出力時にはこの接頭字は取り除かれる)。
次のASCIIマークアップ規約が適用される:
_xxx_ xxxを強調表示 [xxx] xxxをボールド表示 - (語中) ハイフン - (数字の間) 半角ダッシュ - (空白で囲まれた) 全角ダッシュ ... 省略記号 ... (行中にそれだけ) 縦の省略記号 "xxx" xxxを会話文として囲む number' 数字をフィート表示
注: これらはゲーム中においてまったく解釈はされないけど、 一貫とした形式でプレーヤーにテキストを表示するのに役立つ。
行は、右行端未調整(つまり左揃え)で、67文字より短くしなきゃならない。 パラグラフの分割は前節を短い行で示すべきだ(字下げは使うべきではない)。 パラグラフ間の空行は、引用もしくは他の特殊な目的のために使用するべきだ (詩における句の間の分離等)。 終止符(および短文を終える他の句読点)には2つの空白文字を続けるべきだ。