monster_type及びmonster_race構造体によるモンスター状態処理と拡張の方向性について

https://adventar.org/calendars/5176
Roguelike Advent Calendar 2020(https://adventar.org/calendars/5176)の6日目が空いていたので、ついでくらいで記事上げさせてもらいます。

変愚蛮怒の3.0.0Alphaリリースに向けて色々進めてはいるのですが、それら手近な作業の他にも、将来的な所を見据えた話を一つしようと、それが今回のテーマです。

ソースコードにある程度触れた人には大抵周知の内容でありますが、まず基本から。今回取り上げるmonster_type及びmonster_raceはCソースコードの構造体として定義されたもので、

  • monster_type …モンスター1個体毎の状態を保存する構造体
  • monster_race …モンスターの1種を定義する構造体

という関係を取っています。少なくとも筆者個人が積極的に変愚のソースコードをいじり出す前からこの大まかな構成は変わっておらず、一方でこれからハードコード上で何らかの拡張を行っていくならば、こいつらを大掛かりにいじっていくことが必要不可欠になるでしょう。

2020/12/06現在のmonster_type構造体の構成は /src/system/monster-type-definition.h は以下の通りになっています。

typedef struct monster_type {
	MONRACE_IDX r_idx;		/*!< モンスターの実種族ID (これが0の時は死亡扱いになる) / Monster race index 0 = dead. */
	MONRACE_IDX ap_r_idx;	/*!< モンスターの外見種族ID(あやしい影、たぬき、ジュラル星人誤認などにより変化する)Monster race appearance index */
	floor_type *current_floor_ptr; /*!< 所在フロアID(現状はfloor_type構造体によるオブジェクトは1つしかないためソースコード設計上の意義以外はない)*/

	/* Sub-alignment flags for neutral monsters */
	#define SUB_ALIGN_NEUTRAL 0x0000 /*!< モンスターのサブアライメント:中立 */
	#define SUB_ALIGN_EVIL    0x0001 /*!< モンスターのサブアライメント:善 */
	#define SUB_ALIGN_GOOD    0x0002 /*!< モンスターのサブアライメント:悪 */
	BIT_FLAGS8 sub_align;	/*!< 中立属性のモンスターが召喚主のアライメントに従い一時的に立っている善悪陣営 / Sub-alignment for a neutral monster */

	POSITION fy;		/*!< 所在グリッドY座標 / Y location on map */
	POSITION fx;		/*!< 所在グリッドX座標 / X location on map */
	HIT_POINT hp;		/*!< 現在のHP / Current Hit points */
	HIT_POINT maxhp;		/*!< 現在の最大HP(衰弱効果などにより低下したものの反映) / Max Hit points */
	HIT_POINT max_maxhp;		/*!< 生成時の初期最大HP / Max Max Hit points */
	HIT_POINT dealt_damage;		/*!< これまでに蓄積して与えてきたダメージ / Sum of damages dealt by player */
	TIME_EFFECT mtimed[MAX_MTIMED];	/*!< 与えられた時限効果の残りターン / Timed status counter */
	SPEED mspeed;	        /*!< モンスターの個体加速値 / Monster "speed" */
	ACTION_ENERGY energy_need;	/*!< モンスター次ターンまでに必要な行動エネルギー / Monster "energy" */
	POSITION cdis;		/*!< 現在のプレイヤーから距離(逐一計算を避けるためのテンポラリ変数) Current dis from player */
	BIT_FLAGS8 mflag;	/*!< モンスター個体に与えられた特殊フラグ1 / Extra monster flags */
	BIT_FLAGS8 mflag2;	/*!< モンスター個体に与えられた特殊フラグ2 /   Extra monster flags */
	bool ml;		/*!< モンスターがプレイヤーにとって視認できるか(処理のためのテンポラリ変数) Monster is "visible" */
	OBJECT_IDX hold_o_idx;	/*!< モンスターが盗み処理により保持しているアイテム(object_type構造体自身がリスト構造を持つ) Object being held (if any) */
	POSITION target_y;		/*!< モンスターの攻撃目標対象Y座標 / Can attack !los player */
	POSITION target_x;		/*!< モンスターの攻撃目標対象X座標 /  Can attack !los player */
	STR_OFFSET nickname;	/*!< ペットに与えられた名前の保存先文字列オフセット Monster's Nickname */
    EXP exp; /*!< モンスターの現在所持経験値 */

	/* TODO: クローン、ペット、有効化は意義が異なるので別変数に切り離すこと。save/loadのバージョン更新が面倒そうだけど */
	BIT_FLAGS smart; /*!< モンスターのプレイヤーに対する学習状態 / Field for "smart_learn" - Some bit-flags for the "smart" field */
	MONSTER_IDX parent_m_idx; /*!< 召喚主のモンスターID */
} monster_type;

これに関連するmflag,及びmflag2に利用するビットフラグが /src/monster/monster-flag-types.h でさらに以下のように定義されています。

typedef enum monster_flags_type {
    MFLAG_VIEW = 0x01, /* Monster is in line of sight */
    MFLAG_LOS = 0x02, /* Monster is marked for project_all_los(caster_ptr, ) */
    MFLAG_XXX2 = 0x04, /* (unused) */
    MFLAG_ETF = 0x08, /* Monster is entering the field. */
    MFLAG_BORN = 0x10, /* Monster is still being born */
    MFLAG_NICE = 0x20, /* Monster is still being nice */
} monster_flags_type;

typedef enum monster_flags2_type {
    MFLAG2_KAGE = 0x01, /* Monster is kage */
    MFLAG2_NOPET = 0x02, /* Cannot make monster pet */
    MFLAG2_NOGENO = 0x04, /* Cannot genocide */
    MFLAG2_CHAMELEON = 0x08, /* Monster is chameleon */
    MFLAG2_NOFLOW = 0x10, /* Monster is in no_flow_by_smell mode */
    MFLAG2_SHOW = 0x20, /* Monster is recently memorized */
    MFLAG2_MARK = 0x40, /* Monster is currently memorized */
} monster_flags2_type;

さらに、今回内容自体は余り主眼にならないのですが /src/system/monster-race-definition.h 内の monster_race 構造体の内容が以下の通りになっています。

typedef struct monster_race {
    STR_OFFSET name; /*!< 名前データのオフセット(日本語) /  Name offset(Japanese) */
#ifdef JP
    STR_OFFSET E_name; /*!< 名前データのオフセット(英語) /  Name offset(English) */
#endif
    STR_OFFSET text; /*!< 思い出テキストのオフセット / Lore text offset */
    DICE_NUMBER hdice; /*!< HPのダイス数 / Creatures hit dice count */
    DICE_SID hside; /*!< HPのダイス面数 / Creatures hit dice sides */
    ARMOUR_CLASS ac; /*!< アーマークラス / Armour Class */
    SLEEP_DEGREE sleep; /*!< 睡眠値 / Inactive counter (base) */
    POSITION aaf; /*!< 感知範囲(1-100スクエア) / Area affect radius (1-100) */
    SPEED speed; /*!< 加速(110で+0) / Speed (normally 110) */
    EXP mexp; /*!< 殺害時基本経験値 / Exp value for kill */
    BIT_FLAGS16 extra; /*!< 未使用 /  Unused (for now) */
    RARITY freq_spell; /*!< 魔法&特殊能力仕様頻度(1/n) /  Spell frequency */
    BIT_FLAGS flags1; /* Flags 1 (general) */
    BIT_FLAGS flags2; /* Flags 2 (abilities) */
    BIT_FLAGS flags3; /* Flags 3 (race/resist) */
    BIT_FLAGS flags4; /* Flags 4 (inate/breath) */
    BIT_FLAGS flags7; /* Flags 7 (movement related abilities) */
    BIT_FLAGS flags8; /* Flags 8 (wilderness info) */
    BIT_FLAGS flags9; /* Flags 9 (drops info) */
    BIT_FLAGS flagsr; /* Flags R (resistances info) */
    BIT_FLAGS a_ability_flags1; /* Activate Ability Flags 5 (normal spells) */
    BIT_FLAGS a_ability_flags2; /* Activate Ability Flags 6 (special spells) */
    BIT_FLAGS a_ability_flags3; /* Activate Ability Flags 7 (implementing) */
    BIT_FLAGS a_ability_flags4; /* Activate Ability Flags 8 (implementing) */
    monster_blow blow[4]; /* Up to four blows per round */
    MONRACE_IDX reinforce_id[6];
    DICE_NUMBER reinforce_dd[6];
    DICE_SID reinforce_ds[6];
    ARTIFACT_IDX artifact_id[4]; /* 特定アーティファクトドロップID */
    RARITY artifact_rarity[4]; /* 特定アーティファクトレア度 */
    PERCENTAGE artifact_percent[4]; /* 特定アーティファクトドロップ率 */
    PERCENTAGE arena_ratio; /* モンスター闘技場の掛け金倍率修正値(%基準 / 0=100%) / The adjustment ratio for gambling monster */
    MONRACE_IDX next_r_idx;
    EXP next_exp;
    DEPTH level; /* Level of creature */
    RARITY rarity; /* Rarity of creature */
    TERM_COLOR d_attr; /* Default monster attribute */
    SYMBOL_CODE d_char; /* Default monster character */
    TERM_COLOR x_attr; /* Desired monster attribute */
    SYMBOL_CODE x_char; /* Desired monster character */
    MONSTER_NUMBER max_num; /* Maximum population allowed per level */
    MONSTER_NUMBER cur_num; /* Monster population on current level */
    FLOOR_IDX floor_id; /* Location of unique monster */
    MONSTER_NUMBER r_sights; /* Count sightings of this monster */
    MONSTER_NUMBER r_deaths; /* Count deaths from this monster */
    MONSTER_NUMBER r_pkills; /* Count visible monsters killed in this life */
    MONSTER_NUMBER r_akills; /* Count all monsters killed in this life */
    MONSTER_NUMBER r_tkills; /* Count monsters killed in all lives */
    byte r_wake; /* Number of times woken up (?) */
    byte r_ignore; /* Number of times ignored (?) */
#define MR1_EVOLUTION 0x01
    byte r_xtra1; /* Something */
    byte r_xtra2; /* Something (unused) */
    ITEM_NUMBER r_drop_gold; /*!< これまでに撃破時に落とした財宝の数 / Max number of gold dropped at once */
    ITEM_NUMBER r_drop_item; /*!< これまでに撃破時に落としたアイテムの数 / Max number of item dropped at once */
    byte r_cast_spell; /* Max number of other spells seen */
    byte r_blows[4]; /* Number of times each blow type was seen */
    u32b r_flags1; /* Observed racial flags */
    u32b r_flags2; /* Observed racial flags */
    u32b r_flags3; /* Observed racial flags */
    u32b r_flags4; /* Observed racial flags */
    u32b r_flags5; /* Observed racial flags */
    u32b r_flags6; /* Observed racial flags */
    u32b r_flagsr; /* Observed racial resistance flags */
} monster_race;

monrace_typeを見るに、やはり初期のヴァニラ2.7系列より派生以来から様々な付け加えがあったであろうことが伺えます。善悪のアライメントに応じて、モンスター同士が争う場合があること。それに召喚の要素が発生した際に、誰がどちらの陣営について戦うべきかが判定されていること。モンスターがSMARTならばプレイヤーに効かない攻撃については、学習して選択肢から外すためのフラグ。ペット化や抹殺などのモンスターにとって致命的な要素について、抵抗に成功した場合は免疫を与えるフラグ等々。

ただ、ある程度やり込んでいるプレイヤーならば知っての通り、そのようなシステムの付け加えのために必要だった要素を除くと、殊の外モンスターの個体要素は限られています。生成処理の結果得られるモンスターの個性は、

  • HP(FORCE_MAXだったらこれも固定)
  • スピード(特に騎兵や魔獣使いの変幻の魔公厳選で御馴染みの差異)

全くもってこの2点のみです。

これ以外の値についてはmonster_type構造体内で完結できず、都度monster_race構造体から

    monster_race *r_ptr = &r_info[m_ptr->r_idx];

と、個別の案件ごとに参照ポインタを用意して引っ張ってきています。この個別処理は単純にgrepかけただけでも100は余裕です。今はともかく、これが将来のために実によろしくない。モンスターの設定にmonrace_type以外の追加要素を入れようとした時に、この現状の実装が圧倒的に壁になります。

例えば、バリアントの大本であるAngbandのバージョン4系列には変愚のlib/editに該当するデータ系列にmonster_base.txtという定義ファイルがあり、こちらで種族共通の特徴を予め定義して「アンデッドなのに設定を漏らして特有の耐性を持っていない」といった事態を避けられる仕様になったようです。この種のテンプレートは発展させれば、例えば「巨大ショゴス」とか、「サイバー〈コーン〉の渇血悪魔」、とかD&D等TRPGで見かけるような強化テンプレートを充てるといった応用が可能になるでしょう。さらにあるいは変愚勝手版のようにランダムユニークを実装したいとか。実に思う所は色々あるのですが、それが今のままでは非常にめんどい。

例えばモンスターが火炎耐性を持っているかの判定に、monster_race以外のそれら新規の要素を都度判定する処理を、既存の全モンスターのフラグ要素個別に余さず実装しなければなりません。それらを関数にでもまとめた後、今度は上記のように各々勝手にmonster_raceで参照している所部分それぞれを置換しなければならんわけです実は似たようなことをプレイヤーの耐性関係の定義で整理し直しているのですが、3.0.0Alpha実現までにどこまでやるかの判断も含めて、実に骨を折っています。これと同程度くらいの整理をつけないと実現できそうにないんですよね。

あるいは、モンスターを生成する際にこれらの値をmonster_type側に移植して、同構造体のみで完結する実装にする、ということも考えています。元々このような仕様になっていたのも、*bandそのものが大昔の限られたメモリ環境でそれらを節約するために過ぎず、今となってはmonster_typeが数倍程度に膨れ上がろうがどうということはないはずです。

ただこれをやると今度は「モンスターボール」という仕様の現行のために、大きな支障をきたします。

モンスターボールに保管されたモンスターの個体情報はobject_type構造体内の汎用変数xtra1~xtra5内に押し込める以上には追加できない状態になってます。

これを解決するには例えば今度は、モンスターボールの仕様そのものを大掛かりに直して、例えばボールに納められたペットの情報を新たに作ったmonster_type配列に保管する、という形にする必要もあるでしょう。

こう長々と話をしましたが、結局のところこれらの改造はAngbandという先人も、変愚蛮怒勝手版という子孫も、とうにやってしまっていることな訳です。特に変愚蛮怒勝手版はモンスターボール改め「妖魔本」という形で前述の問題も、作者であるみやまさ氏一人でしっかり解決済ませてしまっているはずです。それを思うとブログでこう書いていることが愚痴にしか思えず、書いている暇があればそれこそさっさと改変してしまえという心持になり赤面の限りです。

両者のソースコードでも覗いて参考にしながら、追々やっていきます。どうぞこれからもよろしく。

明日七日目は、Infy 氏の「今年遊んだローグライクの紹介」となります。

monster_type及びmonster_race構造体によるモンスター状態処理と拡張の方向性について」への2件のフィードバック

  1. みやまさ

    お世話になっております。
    勝手版でのランダムユニークモンスターや可変パラメータモンスターの実装は下記のような感じでやっています。
    ・起動時にr_info.txtから読み込んだモンスター種族情報を、生成時に新たなパラメータを計算して上書きする
    ・セーブ/ロードのときにはパラメータを再計算するか、ランダムユニークの場合セーブファイルにランダムユニークパラメータ専用領域を作ってそこに記録しておく
    ・モンスターボール(妖魔本)には捕獲できない。モンスターによっては階層移動すると消える
    ・ほかモンスターの種類ごとに場当たり的なツギハギ多数
    恥ずかしながら違法建築の上にさらに違法建築を重ねるような騙し騙しの実装なので参考にはならないと思います。

    返信
    1. deskull 投稿作成者

      コメント返信むっちゃ遅れました。のみならずなんか色がウィンドウと同じになって表示しづらいですね。申し訳ありません。いや、なるほど、現状の変愚から拡張するにはそういう形にせざるを得ないのも仕方ないかと。
      個人的にはそこをしっかりしたいと思って、昔のバリアンドでドツボにハマって頓挫したもんですから。ちゃんと実装なすっているだけ、みやまささんのが正着としか言えませんよねえ。

      返信

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です