DQ3 エンカウント時の出現モンスター決定方法3

さて、前回の時点で、1番目のモンスターを決定する実装まで説明しました。

2番目以降のモンスターを決定するSRは

7F01 混成モンスター1~5用
7F58 1体のみ+混成5種をお供にするモンスター用
7F71 単一種出現モンスター1~4,夜のみモンスター用
7FAE 1体のみ出現用
7FB5 固定パーティ1,2用

の5種類です。これを順番に見ていきます。

  • SR: $067F01 2種以上混成モンスター出現種・出現数決定
067F01 STA $2000 $7E2000=A モンスターIDを1種類目にセット
067F04 JSR $80E6 SR: $0680E6 出現閾値均等化
067F07 JSR $80AF SR: $0680AF 出現種打ち止め判定
067F0A LDA #$041F A=#$041F 混成1~5,夜のみ出現が対象
067F0D JSR $8059 SR: $068059 出現モンスター決定
067F10 STA $2002 $7E2002=A モンスターIDを2種類目にセット
067F13 JSR $80AF SR: $0680AF 出現種打ち止め判定
067F16 JSL $0012D1 SR: $0012D1 乱数発生 $00-FF
067F1A CMP $F7B4 A>=$7EF7B4? 3種以上出現させるか判定
067F1D BCS #$21 if(c==on) goto $067F40
067F1F LDA #$041F A=#$041F 混成1~5,夜のみ出現が対象
067F22 JSR $8059 SR: $068059 出現モンスター決定
067F25 STA $2004 $7E2004=A モンスターIDを3種類目にセット
067F28 JSR $80AF SR: $0680AF 出現種打ち止め判定
067F2B JSL $0012D1 SR: $0012D1 乱数発生 $00-FF
067F2F CMP $F7B6 A>=$7EF7B6? 4種以上出現させるか判定
067F32 BCS #$0C if(c==on) goto $067F40
067F34 LDA #$041F A=#$041F 混成1~5,夜のみ出現が対象
067F37 JSR $8059 SR: $068059 出現モンスター決定
067F3A STA $2006 $7E2006=A モンスターIDを4種類目にセット
067F3D JSR $80AF SR: $0680AF 出現種打ち止め判定
067F40 STZ $1C $00001C=#$00 ここから出現数の決定
067F42 JSR $80FC SR: $0680FC モンスターIDがセットされている場合に出現数カウンタを全て1増やす
067F45 INC $1C $00001C++
067F47 LDA $1C A=$00001C
067F49 CMP #$0008 A>=#$0008? 各グループ出現数が8になったらストップ
067F4C BCS #$09 if(c==on) goto $067F57
067F4E LDA #$0002 A=#$0002
067F51 JSL $00133E SR: $00133E 乱数発生 00-A A(1B) 1/3の確率で出現数を増やす
067F55 BNE #$EB if(z==off) goto $067F42
067F57 RTS return

2種類目からは混成1~5+夜のみ出現の閾値は全て同じ40になるので出現確率は同じになります。また、3種類、4種類出現するかどうかは個別の閾値(1Byte)が用意されていて、乱数がそれ以下の場合のみ出現判定が行われます。さらにこのSRでは、出現数はどのグループも全て同じ数になります(実際は画面幅の制限などで削られますが)。

  • SR: $0680E6 出現閾値均等化
0680E6 LDX #$0000 X=#$0000
0680E9 LDA $F798,X A=$7EF798+X
0680EC BEQ #$06 if(z==on) goto $0680F4
0680EE LDA #$0028 A=#$0028 閾値に全て40をセット(最大6種まで)
0680F1 STA $F798,X $7EF798+X=A
0680F4 INX X++
0680F5 INX X++
0680F6 CPX #$001C X>=#$001C ?
0680F9 BCC #$EE if(c==off) goto $0680E9
0680FB RTS return
  • SR: $0680AF 出現種打ち止め判定
0680AF PHA Push A
0680B0 PHX Push X
0680B1 PHY Push Y
0680B2 LDY #$0000 Y=#$0000
0680B5 LDA $2000,Y A=$7E2000+Y
0680B8 CMP #$0000 A==#$0000?
0680BB BEQ #$1E if(z==on) goto $0680DB
0680BD PHA Push A
0680BE LDX #$0000 X=#$0000
0680C1 LDA $F7B8,X A=$7EF7B8+X モンスターIDが混成1~5に一致しているか
0680C4 CMP $01,S A==Stack($01)?
0680C6 BNE #$0B if(z==off) goto $0680D3
0680C8 LDA $F7D4,X A=$7EF7D4+X
0680CB CMP #$0001 A==#$0001? 対応した打ち止めフラグを見る(ON=1)
0680CE BEQ #$03 if(z==on) goto $0680D3
0680D0 STZ $F798,X $7EF798+X=#$00 ONになっていたら混成1~固定パーティ2までの出現閾値を0にする
0680D3 INX X++
0680D4 INX X++
0680D5 CPX #$0018 X>=#$0018 ?
0680D8 BCC #$E7 if(c==off) goto $0680C1
0680DA PLA Pull A
0680DB INY Y++
0680DC INY Y++
0680DD CPY #$0008 Y>=#$0008 ?
0680E0 BCC #$D3 if(c==off) goto $0680B5
0680E2 PLY Pull Y
0680E3 PLX Pull X
0680E4 PLA Pull A
0680E5 RTS return

ちょっとわかりにくいですが、それぞれのRAMの意味がわかっていれば理解できるはずです。打ち止めフラグがONになっている場合、閾値が全て0になるのでこれ以降何度SR: $068059出現モンスター決定を呼んでも結果は空になります(多分)。

  • SR: $0680FC モンスターIDがセットされている場合に出現数カウンタを全て1増やす
0680FC LDX #$0000 X=#$0000
0680FF LDA $2000,X A=$7E2000+X
068102 CMP #$0000 A==#$0000?
068105 BEQ #$03 if(z==on) goto $06810A
068107 INC $2008,X $7E2008+X++ モンスターIDがセットされているグループの出現数を1増やす
06810A INX X++
06810B INX X++
06810C CPX #$0008 X>=#$0008 ?
06810F BCC #$EE if(c==off) goto $0680FF
068111 RTS return
  • SR: $067F58 1体+混成5種をお供(最大8体)モンスター出現数決定
067F58 STA $2000 $7E2000=A モンスターIDを1種類目にセット
067F5B JSR $80FC SR: $0680FC モンスターIDがセットされている場合に出現数カウンタを全て1増やす
067F5E JSR $80E6 SR: $0680E6 出現閾値均等化
067F61 LDA #$001F A=#$001F 混成1~5のみが対象
067F64 JSR $8059 SR: $068059 出現モンスター決定
067F67 STA $2002 $7E2002=A 2種類目を決定
067F6A LDA #$0008 A=#$0008 出現数は8に固定
067F6D STA $200A $7E200A=A
067F70 RTS return

ロマリア周辺のさまようよろい+ギズモたくさんやムオル南部?のスカイドラゴン+しびれあげはたくさん、のような出現パターンが相当します。この場合、出現数は固定で画面幅に収まるだけ出現することになるので、お供の数はいつも同じ数だけ出てくることになります。

  • SR: $067F71 単一種出現モンスター1~4+夜のみ
067F71 STA $2000 $7E2000=A モンスターIDを1種類目にセット
067F74 TXA A=X
067F75 SEC c=on
067F76 SBC #$000C A-=#$000C
067F79 BCC #$32 -> $067FAD if(c==off) goto $067FAD
067F7B TAX X=A
067F7C LDA $F7F0,X A=$7EF7F0+X 単一種出現モンスター1~4出現数IDをセット
067F7F TAX X=A
067F80 JSL $0903EE SR: $0903EE 引数:1#$00 引数:2#$0002 引数:3#$C8B72C 引数:4#$0000 出現数最小を取得
067F8C AND #$00FF A&=#$00FF
067F8F STA $1C $00001C=A
067F91 JSL $0903EE SR: $0903EE 引数:1#$00 引数:2#$0002 引数:3#$C8B72C 引数:4#$0001 出現数最大を取得
067F9D AND #$00FF A&=#$00FF
067FA0 SEC c=on
067FA1 SBC $1C A-=$00001C
067FA3 JSL $00133E SR: $00133E 最小~最大の幅で乱数を作成
067FA7 CLC c=off
067FA8 ADC $1C A+=$00001C
067FAA STA $2008 $7E2008=A 出現数をセット
067FAD RTS return

1種類しか出現しないパターンです。$08B72Cは以下のようなデータ構造です。

$08B72C- 単一種出現数範囲(2Byte*5rec)

Byte 意味
0 出現数最小
1 出現数最大

この値を取得して、その範囲の数値をランダムで作成して出現数を決定しています。

  • SR: $067FAE 1体のみ出現用
067FAE STA $2000 $7E2000=A モンスターIDを1種類目にセット
067FB1 JSR $80FC ($0680FC) SR: $0680FC モンスターIDがセットされている場合に出現数カウンタを全て1増やす
067FB4 RTS return

これは見てのとおり。特に説明することはありません。

  • SR: $067FB5 固定パーティ出現種類・出現数決定
067FB5 TAY Y=A 固定パーティレコードIDをセット
067FB6 JSL $0903E2 SR: $0903E2 引数:1#$00 引数:2#$0005 引数:3#$C8B736 引数:4#$0002
067FC2 AND #$00FF A&=#$00FF
067FC5 STA $2000 $7E2000=A 固定パーティ1を1種類目にセット
067FC8 JSL $090566 SR: $090566 引数:1#$00 引数:2#$0005 引数:3#$C8B736 引数:4#$0000 引数:5#$00000F
067FD7 JSR $802C ($06802C) SR: $06802C 固定パーティ出現数決定
067FDA STA $2008 $7E2008=A
067FDD JSL $0903E2 SR: $0903E2 引数:1#$00 引数:2#$0005 引数:3#$C8B736 引数:4#$0003
067FE9 AND #$00FF A&=#$00FF
067FEC STA $2002 $7E2002=A 固定パーティ2を2種類目にセット
067FEF JSL $090566 SR: $090566 引数:1#$00 引数:2#$0005 引数:3#$C8B736 引数:4#$0000 引数:5#$0000F0
067FFE JSR $802C SR: $06802C 固定パーティ出現数決定
068001 STA $200A $7E200A=A
068004 JSL $0903E2 SR: $0903E2 引数:1#$00 引数:2#$0005 引数:3#$C8B736 引数:4#$0004
068010 AND #$00FF A&=#$00FF
068013 STA $2004 $7E2004=A 固定パーティ3を3種類目にセット
068016 JSL $090566 SR: $090566 引数:1#$00 引数:2#$0005 引数:3#$C8B736 引数:4#$0001 引数:5#$00000F
068025 JSR $802C SR: $06802C 固定パーティ出現数決定
068028 STA $200C $7E200C=A
06802B RTS return

これも必要な固定長データからデータを取ってきてセットするだけです。

以上でエンカウントモンスター+出現数が決定したわけですが、最後に$08ADD1- エンカウントの出現数(19Byte目)で各グループの出現数をクリップします。

  • SR: $067E19 最大出現数を見て出現モンスター数調整(画面幅考慮なし)
067E19 LDX $18 X=$000018 エンカウントIDをXにセット
067E1B JSL $0903EE SR: $0903EE 引数:1#$01 引数:2#$0014 引数:3#$C8B5DD 引数:4#$0013
067E27 AND #$00FF A&=#$00FF 最大出現数を取得
067E2A STA $1C $00001C=A
067E2C STZ $F7FA $7EF7FA=#$00 バッファを初期化
067E2F STZ $F7FC $7EF7FC=#$00
067E32 STZ $F7FE $7EF7FE=#$00
067E35 STZ $F800 $7EF800=#$00
067E38 LDX #$0000 X=#$0000
067E3B LDA $2000,X A=$7E2000+X
067E3E CMP #$0000 A==#$0000?
067E41 BNE #$03 -> $067E46 if(z==off) goto $067E46
067E43 STZ $2008,X $7E2008+X=#$00 モンスターIDが設定されていない場合は出現数に0をセット
067E46 INX X++
067E47 INX X++
067E48 CPX #$0008 X>=#$0008 ?
067E4B BCC #$EE if(c==off) goto $067E3B
067E4D LDA $1C A=$00001C
067E4F BEQ #$27 if(z==on) goto $067E78
067E51 PHA Push A
067E52 LDX #$0000 X=#$0000
067E55 LDA $2000,X A=$7E2000+X
067E58 CMP #$0000 A==#$0000?
067E5B BEQ #$0D if(z==on) goto $067E6A
067E5D LDA $2008,X A=$7E2008+X
067E60 BEQ #$08 if(z==on) goto $067E6A
067E62 DEC $2008,X $7E2008+X– カレントの出現数をデクリメントする
067E65 INC $F7FA,X $7EF7FA+X++ テンポラリの出現数をインクリメントする
067E68 DEC $1C $00001C– 最大出現数をデクリメントする
067E6A INX X++
067E6B INX X++
067E6C CPX #$0008 X>=#$0008 ?
067E6F BCC #$E4 -> $067E55 if(c==off) goto $067E55
067E71 PLA Pull A
067E72 CMP $1C A==$00001C? もともと取得した最大出現数と比較?
067E74 BEQ #$02 if(z==on) goto $067E78 同じなら終わり
067E76 BRA #$D5 goto $067E4D
067E78 LDX #$0000 X=#$0000
067E7B LDA $F7FA,X A=$7EF7FA+X
067E7E STA $2008,X $7E2008+X=A 各グループに出現数をセット
067E81 INX X++
067E82 INX X++
067E83 CPX #$0008 X>=#$0008 ?
067E86 BCC #$F3 if(c==off) goto $067E7B
067E88 RTS return

この部分ですが、もしかしたらバグなのかも知れません。出現するモンスターの総数が指定された「最大出現数」以下になるようにするプログラムだと思われるのですが、意図した動作をしていないようです(結局画面幅等で制限がかかるので表面化しにくい)。たとえば、スライム:6匹、いっかくうさぎ:6匹、最大出現数:4匹の場合、実際にデバッガ上での動作を追っていくと関連する変数の変遷は以下のようになります。

A X 7E2008 7E200A 7EF7FA 7EF7FC 1C コメント
067E52 #$00 #$06 #$06 #$00 #$00 #$04
067E62 #$00 #$05 #$06 #$00 #$00 #$04 1ループ目
067E65 #$00 #$05 #$06 #$01 #$00 #$04
067E68 #$00 #$05 #$06 #$01 #$00 #$03
067E6B #$02 #$05 #$06 #$01 #$00 #$03
067E62 #$02 #$05 #$05 #$01 #$00 #$03 2ループ目
067E65 #$02 #$05 #$05 #$01 #$01 #$03
067E68 #$02 #$05 #$05 #$01 #$01 #$02
067E6B #$04 #$05 #$05 #$01 #$01 #$02 3ループ目
067E6B #$06 #$05 #$05 #$01 #$01 #$02 4ループ目
067E71 #$04 #$08 #$05 #$05 #$01 #$01 #$02
067E72 #$04 #$08 #$05 #$05 #$01 #$01 #$02 Aと$1Cを比較して異なるので$067E4Dへ
067E4D #$02 #$08 #$05 #$05 #$01 #$01 #$02
067E52 #$00 #$05 #$05 #$01 #$01 #$02
067E62 #$00 #$04 #$05 #$01 #$01 #$02 1ループ目
067E65 #$00 #$04 #$05 #$02 #$01 #$02
067E68 #$00 #$04 #$05 #$02 #$01 #$01
067E6B #$02 #$04 #$05 #$02 #$01 #$01
067E62 #$02 #$04 #$04 #$02 #$01 #$01 2ループ目
067E65 #$02 #$04 #$04 #$02 #$02 #$01
067E68 #$02 #$04 #$04 #$02 #$02 #$00
067E6B #$04 #$04 #$04 #$02 #$02 #$00 3ループ目
067E6B #$06 #$04 #$04 #$02 #$02 #$00 4ループ目
067E71 #$02 #$08 #$04 #$04 #$02 #$02 #$00
067E72 #$02 #$08 #$04 #$04 #$02 #$02 #$00 Aと$1Cを比較して異なるので$067E4Dへ
067E4D #$00 #$08 #$04 #$04 #$02 #$02 #$00
067E52 #$00 #$04 #$04 #$02 #$02 #$00
067E62 #$00 #$03 #$04 #$02 #$02 #$00 1ループ目
067E65 #$00 #$03 #$04 #$03 #$02 #$02
067E68 #$00 #$03 #$04 #$03 #$02 #$FFFF
067E6B #$02 #$04 #$03 #$03 #$02 #$FFFF
067E62 #$02 #$03 #$03 #$03 #$02 #$FFFF 2ループ目
067E65 #$02 #$03 #$03 #$03 #$03 #$FFFF
067E68 #$02 #$03 #$03 #$03 #$03 #$FFFE
067E6B #$04 #$03 #$03 #$03 #$03 #$FFFE 3ループ目
067E6B #$06 #$03 #$03 #$03 #$03 #$FFFE 4ループ目

…以降7E2008,7E200Aが0になるまでループ、その結果7EF7FA,7EF7FCには#06がセットされ、最大出現数#$04は無視される。

となり、だらだらと書きましたが、要は設定した「最大出現数」が効きません(効くケースもあります)。これが意図した実装なのかどうか判断がつかないので「バグ」と断定するのは早計です。しかし、DQ3SFC K.Mixに関して言えば、序盤の一人旅の部分でモンスター数をコントロールできないのは具合が悪いので、以下のように修正することにします。これにより、エンカウントで設定された「最大出現数」を超えてモンスターが出現することはなくなります。$067E4D-76をSR化して外部に出し、さらに変更を加えます。

  • SR: $067E19 最大出現数を見て出現モンスター数調整(画面幅考慮なし)
067E51 JSL $06F6D3 SR: $06F6D3 モンスター出現数を出現最大値以下に調整する
067E55-77 NOP
  • SR: $0F6D3 モンスター出現数を出現最大値以下に調整する
06F6D3 LDA $1E A=$00001E $1Eを念のため退避しておく
06F6D5 PHA Push A
06F6D6 STZ $1E $00001E=#$00
06F6D8 LDX #$0000 X=#$0000
06F6DB LDA $2000,X A=$7E2000+X
06F6DE CMP #$0000 A==#$0000?
06F6E1 BEQ #$0A if(z==on) goto $06F6ED
06F6E3 LDA $2008,X A=$7E2008+X
06F6E6 BEQ #$05 if(z==on) goto $06F6ED
06F6E8 CLC c=off
06F6E9 ADC $1E A+=$00001E
06F6EB STA $1E $00001E=A 現出現総数を$1Eにセット
06F6ED INX X++
06F6EE INX X++
06F6EF CPX #$0008 X>=#$0008 ?
06F6F2 BCC #$E7 if(c==off) goto $06F6DB
06F6F4 LDX #$0000 X=#$0000
06F6F7 LDA $2000,X A=$7E2000+X
06F6FA CMP #$0000 A==#$0000?
06F6FD BEQ #$17 if(z==on) goto $06F716
06F6FF LDA $2008,X A=$7E2008+X
06F702 BEQ #$12 if(z==on) goto $06F716
06F704 DEC $2008,X $7E2008+X–
06F707 INC $F7FA,X $7EF7FA+X++
06F70A DEC $1C $00001C–
06F70C DEC $1E $00001E– $1C,$1Eをデクリメントし、どちらかが0になったら終了
06F70E LDA $1C A=$00001C
06F710 BEQ #$0D if(z==on) goto $06F71F
06F712 LDA $1E A=$00001E
06F714 BEQ #$09 if(z==on) goto $06F71F
06F716 INX X++
06F717 INX X++
06F718 CPX #$0008 X>=#$0008 ?
06F71B BCC #$DA if(c==off) goto $06F6F7
06F71D BRA #$D5 goto $06F6F4 4グループ目まで来ていたらXを0にリセット
06F71F PLA Pull A
06F720 STA $1E $00001E=A
06F722 RTL return

変更点は以下の2点です。

  • 調整前の出現総数を$1Eに保存し、1体出現数を増やすごとにデクリメントし、0になったら調整終了(調整前の出現総数が出現最大数を下回っている場合の対処)
  • $1Cの値も1体出現数を増やすごとにデクリメントする。1体ごとに$1C,$1Eの値をチェックし、どちらかが0になっていたら処理終了。1体ごとにグループを移動させ、4グループ目まできたら1グループ目に戻す。

以上でエンカウントデータにセットされている情報がどのように使用されて出現モンスターが決定されているかの説明は終わりです。何度も言いますが、このSRをコールした時点では、画面幅等を考慮した最終的に出現するモンスターは決定していません(3グループそれぞれ6体出現とか結構あるので)。ここからさらに各モンスターに設定されている最大出現数によるクリップ、各モンスターの幅や使用可能なメモリ量やハードウェア制限?により出現数、種類が削られていくことになります。具体的には、SR: $04797Fでその調整をしていますが、まだざっくりしたところまでしか解析できていません。解析できたら続きのエントリを投下します。また、この部分をこれ以上(プログラム的に)変更する予定はありません。画面幅等の条件で削られてしまうので、いくら凝っても結局削られて終わり、ということになり、効果のほどが不明だからです。この解析結果に従って改めて自分の設定したエンカウント情報を見ると、いかにいい加減だったかがわかります。このあたりも要調整でしょう。