DQ3にAIを導入するならやってみたいのは「学習機能の実装」です。最近のドラクエのAIは「賢すぎる」というのがどうにもひっかかっていました。使用するメモリ量を抑える目的もあるのでしょうが、AIは自分のターンが回ってきたときにその場で行動を決定するのでいわゆる「後出しジャンケン」の上、初見のモンスターの情報もなぜかお見通しということもあり、正直かなり便利です。じゃあお前はどうするんだよという話になりますが、少なくともなにもしないうちから「相手の耐性・行動をすべて知っている状態」がその理由の一つである、ということで「対象のモンスターの耐性情報、行動情報を把握しているか」フラグを持つようにすれば多少はマシになるのではと思います。モンスターを倒したり、実際に呪文を使ってみて初めて情報にアクセスできる、とすればAIべったりお任せにもならないのではと思います。さすがに一人づつこの情報を持たせると領域がいくらあっても足りないのでパーティで共有するとして、1モンスターにつき、基本情報(HP、攻撃力、守備力、素早さなど)1B、行動情報1B、耐性情報2Bとすると、モンスター数256として、4B*256モンスター分*冒険の書3個分=3,072Bのセーブデータの領域が追加で必要になります。オリジナルのDQ3セーブデータサイズは8KB(=8,192B)で、そのうちヘッダ5Byte+セーブデータ本体2,719B*冒険の書3個分+フッタ5B=8,167B*1とほぼフルに使っています。まるで領域が足りないので手法としては
- セーブデータファイルのサイズはそのままにして既存領域を圧縮する
- セーブデータファイルのサイズを拡張する
のどちらかになります。既存領域は大きく分けて
- 進行フラグ、宝箱回収フラグ
- アイテム(袋の中身)情報
- キャラクター情報
で使用されているのですが、圧縮できそうなのはせいぜい袋の中身くらいでやったとしても128B程度しかサイズが浮きません(袋の中身をセーブのたびに毎回固定で並び替えればもう192B浮きますが結局焼け石に水です)。というわけで必然的にセーブデータファイルのサイズを拡張することになります。とりあえず拡張後のサイズは32KBとします。拡張した場合のデメリットは既存のデータとの互換性が問題になるわけですが、最悪互換性は切り捨てることで解決します。今までDQ3 K.Mixにおいて、バージョンアップに伴い極力古いバージョンのデータをそのまま使えるようにはしてきましたが、さすがにセーブデータのファイルサイズが異なる場合にはSFCのプログラム内部ではどうしようもないのでサポートする場合には別途外部ツールを用意するか各自で対応してもらう必要があります。対応と言っても既存のファイルの最後に24KB分0をつけてもらうだけなんですが。
セーブデータを拡張するにあたってやるべきことは大きく分けて2つ。
- ROMヘッダー情報の変更
- SRAMからRAMへのデータのコピー(ロード時)/RAMからSRAMへのデータのコピー(セーブ時)
1についてはROMヘッダーの$00FFD8(ExHiROMは$40FFD8も?)の値を変えるだけ。意味はhttp://romhack.wikia.com/wiki/SNES_headerを参照のこと。オリジナルは#$03(8KB)になっているのでこれを#$05(32KB)に変えるだけです。既存のsrmファイルを消してゲームをロードすると32KBのサイズのsrmファイルができているはずです。次に肝心の拡張された領域にプログラムからアクセスするコードを書きます。最初は1セーブデータ構造体のサイズを何も考えずに増やせばいいのかと思っていたのですが大間違いで、どうやらセーブデータは8KBずつの領域でしか扱えないようです。最初の8KBはアドレス$306000-7FFFにセットされ、次の8KBはアドレス$316000-7FFFにセットされるようです。このへんは最初は試行錯誤していたのですが、色々やってダメだった結果、aaaa氏のDQ6ルイーダ拡張パッチの中を覗いてなんとか「そうらしい」という結論に至っただけです。確実に必要なのは1,024Bですが、余裕を見て1つのセーブデータにつき1,536B確保することにします。
- SR: $0FE1F8 冒険の書記録(新SR)
0FE1F8 | JSL $C4626F | SR: $04626F | 冒険の書記録 |
---|---|---|---|
0FE1FC | JSL $CFE20A | SR: $0FE20A | 冒険の書追加部分記録 |
0FE200 | RTL | return |
- SR: $0FE241 冒険の書追加部分記録(新SR)
0FE20A | PHP | Push P Flag | |
---|---|---|---|
0FE20B | PHB | Push DB | |
0FE20C | PHA | Push A | |
0FE20D | PHX | Push X | |
0FE20E | PHY | Push Y | |
0FE20F | TAX | X=A | |
0FE210 | JSL $C9050D | SR: $09050D 引数:1#$00 引数:2#$0600 引数:3#$316000 引数:4#$4A | 構造体アクセス X番目の先頭アドレスをDP($00,第4引数)にセット |
0FE21B | JSL $CFE2D2 | SR: $0FE2D2 | 冒険の書追加部分記録(AI判断部分) |
0FE21F | JSL $CFE2E8 | SR: $0FE2E8 | 冒険の書追加部分記録(リザーブ) |
0FE223 | PLY | Pull Y | |
0FE224 | PLX | Pull X | |
0FE225 | PLA | Pull A | |
0FE226 | PLB | Pull DB | |
0FE227 | PLP | Pull P Flag | |
0FE228 | RTL | return |
- SR: $0FE2D2 冒険の書追加部分記録(AI判断部分)(新SR)
0FE2D2 | LDY #$0000 | Y=#$0000 | |
---|---|---|---|
0FE2D5 | LDX #$6600 | X=#$6600 | |
0FE2D8 | LDA $7E0000,X | A=$7E0000+X | |
0FE2DC | STA $4A | $(DP[$4A])+Y=A | |
0FE2DE | INY | Y++ | |
0FE2DF | INY | Y++ | |
0FE2E0 | INX | X++ | |
0FE2E1 | INX | X++ | |
0FE2E2 | CPY #$0400 | Y>=#$0400? | |
0FE2E5 | BCC #$F1 | if(c==off) goto $0FE2D8 | |
0FE2E7 | RTL | return |
- SR: $0FE2E8 冒険の書追加部分記録(リザーブ)(新SR)中身はほぼ空なので省略
実際に使用可能かどうかはまだわからないのですが、とりあえずRAMの7E6600-69FFをRAM上の展開先としています。セーブデータの$2000-25FFを冒険の書1の追加領域、$2600-2BFFを冒険の書2、$2C00-31FFを冒険の書3に割り当てています。読み込む場合はこの逆をやればいいだけです。一応これで読み書きはできるようになっています。どうもDQ3のセーブデータは袋以外にも1つのセーブデータ全体でチェックサムを計算しているようで、この追加領域についてもそのチェックサムの計算対象に含めるべきなのですが、とりあえず対象外とします。
これで大枠としては不可能ではないということであとは中身とウィンドウ周りを実装すれば自分のやりたいことが実現できそうです。「学習機能なんかいらん」という人については学習機能使用フラグを用意してOFFなら最初からモンスターの情報を全部丸見えにするとすれば対応できそうということで肝心の中身と「実際実装して面白くなるのか」という点が残ります。一番近いであろうDQ6のAIの解析ははっきり言って10%以下といったところで、まじめに解析して理解するか、無視して1から全部自分で作るか悩みどころです。なんにせよ一朝一夕でできるものではないので、公開するにしてもしばらく日の目を見ることはないでしょう。
*1:SFC版DQ6も全く同じ
コメント