TOP > プログラミング関係解説&調査 > 幕末編のザコ敵

幕末編のザコ敵

当ページの内容は、ある程度のプログラミングの知識を必要とする。
まず、プログラミング関係解説&調査をひととおり読んでいただきたい。
世界の合言葉は森部様の「その他メモ」に、幕末編のザコ敵(エンカウント時に固有名称がある奴や腰元など)について、以下のように説明がある。
■幕末編の名前が変わる敵

名前が違うだけかと思いきや、2体目以降は力速体知が+1ずつ強化されていくようだ。
奴×2や虚無僧×3など複数同時に出る相手は、強い方の能力値に揃えられる。
(虚無僧×3は3体とも敵データの値から4種能力値+2された状態になる)

このように面白い設定があるのだが、実際にはどのような処理がされているのだろうか。
先に結論だけざっくりと記す。

幕末編の固有名称のあるザコ敵には、本作の内部ステータスの「敵のタイプ」で「NAME」タイプと設定されている。
NAMEの敵である、奴、浪人、藩士、強藩士、外人、商人、家老、忍者(1人、2人組、4人組)、くの一、腰元、虚無僧には、それぞれに、何体遭遇したかの「遭遇数カウンター」が存在しており、撃破した数が加算されていく。
このカウンター数から、2体目以降のステータスと名前が決まっていく仕組みである。

遭遇数カウンターは、NAMEタイプの敵とエンカウントすると、その戦闘に登場する敵の数だけカウントアップされるが、逃げた場合は減り、勝利した場合はそのまま。
このため、場所を変え、同じNAMEタイプの敵にエンカウントした場合、名前とステータスは同一である。
たとえば奴だったら、エンカウントしても倒さず逃げた場合、三の丸外の見張りだろうが、建物内部の奴だろうが、本丸出入り口の見張りだろうが、1体目の名前は必ず「やたろう」なのである。

戦闘開始時の処理は大まかに以下のようになる。

  1. 敵データから、敵の種族のデータ(ステータス)をコピーする。
  2. 1体目の敵のステータスをセットしたら、「遭遇数カウンター」を+1する。
    2体目以降がいる場合、遭遇数カウンターの値+力・速・体・知をセットしてから、「遭遇数カウンター」を+1する。更に自身のデータを敵の種族データにコピーする。
  3. 遭遇数カウンターから、固有名称のデータをセットし、敵の名前を画面上ウィンドウに表示する。
  4. おぼろ丸行動開始直後、固有名称がある敵が複数いる時、種族データからすべての敵のステータスをコピーする(一番ステータスが高い敵にステータスが揃う)。

戦闘開始時の処理は他の敵とほぼ共通であろうが、幕末編にてタイプがNAMEの敵については、「遭遇数カウンター」によりステータスと名前が変わる処理が加わっているということになる。
また、敵にステータスをセットしていく作業と、敵が複数の時にステータスが一番高くセットされたキャラにステータスを合わせる作業は別に行われている。

まずは最初に行われる、敵にステータスをセットする処理について説明する。

戦闘開始時の敵のステータス設定

本作のゲームデータ部分(ROM領域・Read-Only Memory)の中で、戦闘に関係するデータは$D5:????$D6:????付近、つまりバンク$D5$D6に入っている。
(実際にはバンク$55$56のミラーリングがバンク$D5$D6で、同値が入っているのだが、実際のゲームのプログラムではだいたいバンク$D5$D6から呼び出している)
敵パーティデータは$D6:4280$D6:512F、敵データは$D5:04CB$D5:24CAに入っており、戦闘を開始すると、ここから敵の種類(最大4種類)を$7E:1A00(または$00:1A00)~に、敵番号0~E(最大15体)を$7E:1B00(または$00:1B00)~に書き出している。
バンク$00$7EはRAM(Random-Access Memory)領域であり、一回の戦闘毎に書き換えになる。

幕末編で固有名称がある敵は、本作の内部ステータスの「敵のタイプ」で「NAME」タイプと設定されている。
「NAME」タイプの敵は、それぞれ、倒した数がカウントされている。
以下アドレスがそのカウンターである。
幕末編開始時に、以下アドレスにすべて$00が入った後、幕末編プレイ中は常に以下の値が記録される。セーブデータにも記録される。
戦闘開始直後に遭遇数の分だけ増加、「逃げる」コマンド選択時に増えた分だけ減少するようになっている。
$00:0F9Eには値は入らない。

アドレス数値
$00:0F92$00~$06忍者(2人組)
$00:0F93$00~$03虚無僧
$00:0F94$00~$0A
$00:0F95$00~$0A浪人
$00:0F96$00~$07藩士
$00:0F97$00~$0A強藩士
$00:0F98$00~$02外人
$00:0F99$00~$04商人
$00:0F9A$00~$05家老
$00:0F9B$00~$08忍者(1人)
$00:0F9C$00~$04くの一
$00:0F9D$00~$09腰元
$00:0F9F$00~$08忍者(4人組)

以上のメモリアドレスを踏まえた上で、重要な部分のサブルーチンを見ていく。
例として、幕末編開始直後、奴×2との初戦闘のサブルーチンを紹介しながら説明する。
奴との戦闘は初めてなので、上の奴とのカウンター[$00:0F94]は初期値の$00の状態で戦闘に突入する。
また、奴の敵IDは$7E、敵データ32バイト分の領域は[$D5:148B][$D5:14AB]にあたる。

$C1/FB62 LDA $0040  [$00:0040]   ;Aに[$00:0040]をロード
$C1/FB65 CMP #$04                ;Aと$04を減算比較 ステータスフラグ変更のみ
$C1/FB67 BNE $0F    [$FB78]      ;ゼロフラグが立っていないとき[$FB78]分岐
$C1/FB78 LDA $D60007,x[$D6:4E4E] ;Aに[$D6:4E4E]をロード
$C1/FB7C STA $10    [$00:0310]   ;Aを[$00:0310]に書き込み
$C1/FB7E LDY #$1A00              ;Yに$1A00をロード
$C1/FB81 LDA $0001,y[$00:1A01]   ;Aに[$00:1A01](敵種類1-01 アドレス用の値)をロード
$C1/FB84 BEQ $23    [$FBA9]      ;ゼロフラグが立っているとき[$FBA9]分岐
$C1/FBA9 LDA $10    [$00:0310]   ;Aに[$00:0310]をロード
$C1/FBAB STA $0000,y[$00:1A00]   ;Aを[$00:1A00](敵種類1-00 敵ID)に書き込み
$C1/FBAE LDA #$FF                ;Aに$FFをロード
$C1/FBB0 STA $0001,y[$00:1A01]   ;Aを[$00:1A01](敵種類1-01 アドレス用の値)に書き込み
$C1/FBB3 LDA #$00                ;Aに$00をロード
$C1/FBB5 STA $000A,y[$00:1A0A]   ;Aを[$00:1A0A]に書き込み
$C1/FBB8 JSR $FC0A  [$C1:FC0A]   ;[$C1:FC0A]へ

奴×2の戦闘では、敵は奴のみなので、敵の種類は1種類である。
ここでは敵種類1とするが、敵種類1のデータは$00:1A00~に入る。
上のサブルーチンが、その処理の最初の部分にあたる。
$C1/FB78[$D6:4E4E]がロードされ、一度[$00:0310]に書き込まれた後に、$C1/FBAB[$00:1A00]に書き込まれている。
敵種類1のデータ00は敵IDであり、[$D6:4E4E]に奴の敵IDの「$7E」が格納されていたのだが、これは「奴×2」の敵パーティデータ07、敵番号0の敵IDのアドレスにあたる。
他のニーモニックは、この先で奴の敵データを呼び出すためのアドレスの計算などなので説明は割愛。
とりあえず、敵パーティデータから、奴の敵IDを呼び出した、ということがわかる。

$C1/FC0A PHX                     ;Xレジスタをスタックへプッシュ
$C1/FC0B PHY                     ;Yレジスタをスタックへプッシュ
$C1/FC0C LDA $0000,y[$00:1A00]   ;Aに[$00:1A00](敵種類1-00 敵ID)をロード
$C1/FC0F STA $211B  [$00:211B]   ;符号付16bit x 8bit 被乗数の下位バイト
$C1/FC12 LDA #$00                ;Aに$00をロード
$C1/FC14 STA $211B  [$00:211B]   ;符号付16bit x 8bit 被乗数の上位バイト
$C1/FC17 LDA #$20                ;Aに$20をロード
$C1/FC19 STA $211C  [$00:211C]   ;符号付16bit x 8bit 乗数
$C1/FC1C STA $211C  [$00:211C]   ;符号付16bit x 8bit 乗数
$C1/FC1F REP #$21                ;Aを16bit幅に変更、キャリーフラグクリア
$C1/FC21 LDA $2134  [$00:2134]   ;符号付16bit x 8bit 乗算結果下1バイト
$C1/FC24 ADC $D50010[$D5:0010]   ;A + [$D5:0010]
$C1/FC28 TAX                     ;AレジスタをXレジスタにコピー
$C1/FC29 SEP #$20                ;MフラグON Aレジスタは8bit幅
$C1/FC2B LDA #$20                ;Aに$20をロード
$C1/FC2D STA $09    [$00:0309]   ;Aを[$00:0309]に書き込み
;
;敵データ呼び出し用サブルーチン ループ開始
$C1/FC2F LDA $D50000,x[$D5:148B] ;Aに[$D5:148B]をロード
$C1/FC33 INX                     ;Xをインクリメント +1
$C1/FC34 STA $0020,y[$00:1A20]   ;Aを[$00:1A20](敵種類1-20 敵データ00 移動パターン)に書き込み
$C1/FC37 INY                     ;Yをインクリメント +1
$C1/FC38 DEC $09    [$00:0309]   ;[$00:0309](初期値$20)をデクリメント -1
$C1/FC3A BNE $F3    [$FC2F]      ;ゼロフラグが立っていないとき[$C1/FC2F]分岐
;敵データ呼び出し用サブルーチン ループここまで

$C1/FC0A$C1/FC29は、奴の敵データ開始アドレス位置である[$D5:148B]の算出であるから詳細は省く。
計算としては、敵ID×$20の答え(16ビット)に[$D5:0010]に入っている値を加算して、その数値がそのまま敵データ開始アドレスになり、バンク$D5から該当アドレスを呼び出している。
$C1/FC2Fからが、敵データ呼び出し用サブルーチンである。
その少し前の$C1/FC2B$C1/FC2Dで、[$00:0309]$20を書き込んでいるが、これがループ回数になる。
$20、つまり10進数の32であるから、ループ回数は32であり、敵データ00~31まで、すべての値を呼び出しては、[$00:1A00]~に書き込む、という作業をしている。
敵番号0なら[$00:1A00]~、敵番号1なら[$00:1A40]~である。
ループするたびに、[$00:0309]の値がデクリメント(-1)され、0まで減るとゼロフラグが立つから、$C1/FC3Aの分岐でループが停止する。

$C1/FC2F LDA $D50000,x[$D5:14AA] ;Aに[$D5:14AA]をロード
$C1/FC33 INX                     ;Xをインクリメント +1
$C1/FC34 STA $0020,y[$00:1A3F]   ;Aを[$00:1A3F](敵データ31 回避属性2)に書き込み
$C1/FC37 INY                     ;Yをインクリメント +1
$C1/FC38 DEC $09    [$00:0309]   ;[$00:0309](初期値$20)をデクリメント -1
$C1/FC3A BNE $F3    [$FC2F]      ;ゼロフラグが立っていないとき[$C1/FC2F]分岐
;ゼロフラグが立ち、ループ終了
;
$C1/FC3C PLY                     ;Yレジスタへ値をプル
$C1/FC3D PLX                     ;Xレジスタへ値をプル
$C1/FC3E RTS                     ;サブルーチン戻り

敵データ31までループが終わると、$C1/FC3Aでゼロフラグが立ってループ終了である。
敵番号0の敵データが、[$00:1A00][$00:1A3F]に格納されたことになる。
2体目以降の処理は省略する。

更に、敵番号0なら[$7E:AA00]~にデータがコピーされていく。
今回の話とは関係ない部分も含まれるが、戦闘における重要な値も含まれるため、続きもまるごと紹介しておく。

$C1/FC78 PHX                     ;Xレジスタをスタックへプッシュ
$C1/FC79 TYX                     ;YレジスタをXレジスタにコピー
$C1/FC7A LDY $0002,x[$00:1B02]   ;Yに[$00:1B02]をロード
$C1/FC7D LDA $0024,y[$00:1A24]   ;Aに[$00:1A24](敵データ04 HP)をロード
$C1/FC80 REP #$20                ;Mフラグをクリア Aレジスタは16bit幅
$C1/FC82 AND #$00FF              ;Aと$00FFで論理積(下8ビット取り出し)
$C1/FC85 BIT #$0080              ;Aと$0080で論理積(ステータスフラグのみ変更)
$C1/FC88 BEQ $07    [$FC91]      ;ゼロフラグが立っているとき[$FC91]分岐
$C1/FC8A AND #$007F              ;(ゼロフラグOFF時)$007Fと論理積
$C1/FC8D ASL A                   ;Aを算術左シフト *2
$C1/FC8E ASL A                   ;Aを算術左シフト *2
$C1/FC8F ASL A                   ;Aを算術左シフト *2
$C1/FC90 ASL A                   ;Aを算術左シフト *2
$C1/FC91 STA $0010,y[$00:1A10]   ;Aを[$00:1A11][$00:1A10]に書き込み
$C1/FC94 STA $7E8F00,x[$7E:AA00] ;Aを[$7E:AA01][$7E:AA00]に書き込み
$C1/FC98 SEP #$20                ;MフラグON Aレジスタは8bit幅

上は、敵データ04に格納された値から、敵のHPを算出するサブルーチンである。
本作の味方キャラの最大HPは999(ブリキ大王は2023だが、実際には敵データに格納されている)、敵キャラの最大HPは2023である。
一方、16進数1バイトの最大値は$FF、10進数だと255だから、1バイトだけでは最大HPが記録できない。
だが、実際には敵データ04にしかHPの値は記録されていない。
どのようにして、最大で2023になる最大HPを記録しているのかは、上の処理を見るとわかる。

敵データ00~31が[$00:1A00][$00:1A3F]に格納されたが、さっそく$C1/FC7D[$00:1A24](敵データ04)をロードしている。これが敵の最大HPの元データである。
その先からは16bitモードでの計算になる。
$C1/FC82で、[$00:1A24]$00FFで論理積を取っているので、Aは上2桁が00、下2桁が敵データ04の値そのままが入った状態になる。
更に、$0080と論理積を取って、ゼロフラグが立つかどうか判定している。
$0080と論理積をとって0になるのは、敵データ04が$80未満の時、つまり$00$7Fの時である。
この場合は$C1/FC91にジャンプしているから、Aを[$00:1A10]に書き込むのだが、16bitモードなので、Aの下2桁1バイトが[$00:1A10]に、上2桁1バイトが[$00:1A11]に入る。
といっても、ここでは上2桁は必ず$00だから、[$00:1A11]には$00が、そして[$00:1A10]に敵データ04の1バイトの値がそのまま入ることになる。
つまり、敵データ04に入っている値がそのまま敵の最大HPであり、[$00:1A10]にコピーされたことになる。
また、同値が[$7E:AA01][$7E:AA00]にも入っている。

では、敵データ04が$80以上の時はどうなるかというと、$C1/FC88でゼロフラグが立たず、$C1/FC8Aに進む。
この時はまず、$007F(%0111 1111)と論理積を取っている。
つまり、敵データ04を2進数8ビットで見た時に、下7桁だけ取り出している。
$C1/FC8D$C1/FC90で、その値に* $2を4回行っている。つまり、*$10である。
16進数で見ると、小数点を1個左に動かすことと同義である。
その次で$C1/FC91に合流しているので、Aの上1バイトが[$00:1A11]に、下1バイトが[$00:1A10]に入る。
よって、

  • 敵データ04の値が$80未満の時は、最大HP = 敵データ04
  • 敵データ04の値が$80以上の時は、最大HP = (敵データ04 & $7F) * $10

ということがわかった。
また、$80以上の時は、計算結果が1バイト分の領域では収まらなくなるので、[$00:1A10][$00:1A11]にHPを分割して格納していることになる。

例として、最終編のヘッドプラッカーのHPを計算してみる。敵データ04に入っている値は$A2で、$80以上である。
よって、
最大HP = ($A2 & $7F) * $10
= $22 * $10
= $0220
10進数544となる。
[$00:1A10]$20が、[$00:1A11]$02が格納されることになる。

また、敵データ04が$80以上の場合、上の計算からわかるとおり、* $10するため、必ず$10(10進数16)の倍数になることもわかる。
つまり、HPが$80以上(10進数128)以上の敵の最大HPは、必ず16の倍数であり、最大値は($FF & $7F) * $10 = $07F0、10進数の2032となる。
このようにして、256以上のHPを1バイトの領域におさめていることがわかる。
また、この計算式のために、本作においては最大HPを20002032などには設定できても、20012002には設定できない。
(リメイク版では最大HPが128以上の敵でも、最大HPが16の倍数以外である敵が存在しているため、根本的な計算方法を変えたか、HPの記録領域が増えたかのどちらかだと思われる)

少し話が逸れたが、今回の本題は幕末編に登場する奴の場合であり、奴の敵データ04は$28なので、最大HPはそのまま$28(10進数40)である。
これで[$00:1A11][$00:1A10][$7E:AA01][$7E:AA00]に、奴のHPが入った。
続きの処理を見ていく。

$C1/FC9A LDA $0029,y[$00:1A29]   ;Aに[$00:1A29](敵データ09)をロード
$C1/FC9D AND #$7F                ;Aと$7Fで論理積
$C1/FC9F STA $000B,y[$00:1A0B]   ;Aを[$00:1A0B]に書き込み
$C1/FCA2 STA $7E9000,x[$7E:AB00] ;Aを[$7E:AB00]に書き込み
$C1/FCA6 LDA #$08                ;Aに$08をロード
$C1/FCA8 STA $10    [$00:0310]   ;Aを[$00:0310]に書き込み
$C1/FCAA LDA #$00                ;Aに$00をロード
$C1/FCAC PHX                     ;Xレジスタをスタックへプッシュ
;
;状態異常継続時間セット用ループ処理
$C1/FCAD STA $7E8F08,x[$7E:AA08] ;$00を[$7E:AA08~AA0F](敵番号0 状態異常継続時間)に書き込み
$C1/FCB1 INX                     ;Xをインクリメント +1
$C1/FCB2 DEC $10    [$00:0310]   ;[$00:0310](初期値$08)をデクリメント -1
$C1/FCB4 BNE $F7    [$FCAD]      ;ゼロフラグが立っていないとき[$FCAD]分岐
;ループ処理ここまで

$C1/FC9Aで、敵データ09がロードされ、$C1/FC9Dでは敵データ09と$7F(10進数127)で論理積を取っている。
敵データ09は、下7ビットにレベルの値が入っているので、ここでレベルの値を取得し、[$00:1A0B]及び[$7E:AB00]に書き込んでいることがわかる。
ちなみに、敵データ09の上1ビットは、使用技を敵技IDから取得するなら0、味方技IDから取得するなら1が入っている。

そこから先は、$7E:AA08$7E:AA0F$00を入れるためのループ処理になる。
[$00:0310]$08を書き込んでいるのは、ループ回数である。
$C1/FCAD$C1/FCB4でループ処理を8回行うことになるのだが、ここでは$7E:AA08$7E:AA0Fすべてに$00を書き込んでいる。
このアドレスには、8種類の状態異常の継続時間が入る。順に首かため・足かため・腕かため・毒・マヒ・眠り・酔い・石化。
石化は時間経過で自動回復しないので、継続時間が入る$7E:AA0Fは、実質、「石化の状態異常になるかどうか」を判定するための値が入るアドレスである(技による状態異常は、計算結果で状態異常継続時間が17以上でないと状態異常発生にならない)。
ループ処理の後も更に処理が続く。

$C1/FCB6 PLX                     ;Xレジスタへ値をプル
$C1/FCB7 STA $7E8F02,x[$7E:AA02] ;[$7E:AA02]に$00を書き込み
$C1/FCBB STA $7E8F07,x[$7E:AA07] ;[$7E:AA07]に$00を書き込み
$C1/FCBF STA $7E9008,x[$7E:AB08] ;[$7E:AB08]に$00を書き込み
$C1/FCC3 STA $7E9009,x[$7E:AB09] ;[$7E:AB09]に$00を書き込み
$C1/FCC7 LDA $003C,y[$00:1A3C]   ;Aに[$00:1A3C](敵データ28)をロード
$C1/FCCA BIT #$F0                ;Aと$F0で論理積(ステータスフラグのみ変更)
$C1/FCCC BNE $06    [$FCD4]      ;ゼロフラグが立っていないとき[$FCD4]分岐
$C1/FCCE STA $7E8F02,x[$7E:AA02] ;Aを[$7E:AA02]に書き込み
$C1/FCD2 BRA $1D    [$FCF1]      ;フラグにかかわりなく常に分岐[$FCF1]

Aには$00が入ったままだが、更に[$7E:AA02][$7E:AA07][$7E:AB08][$7E:AB09]にも$00が書き込まれている。
[$7E:AA02]は敵番号0の行動異常、[$7E:AA07]は状態異常にあたる。
$C1/FCC7では敵データ28をロードしているのだが、実はすべての敵で敵データ28には$00が入っている。
このため、$C1/FCC7$C1/FCD2で、敵データ28と$F0で論理積をとってもとらなくても、必ずゼロフラグが立つ。[$FCD4]にジャンプすることは絶対にない、ということである。
そして、Aが[$7E:AA02]に書き込まれるが、これはさきほど$00を入れたばかりの敵番号0の行動異常のアドレスである。
$00を上書きしたことになり、この後は[$FCF1]にジャンプする。

さて、この意味のない$00の上書きが意味することは何だろうか。
もし敵データ28の上2桁に0以外の何かの値が入っていたら、論理積を取った時にゼロフラグが立たないので、[$C1/FCD4]に飛んだはずだ。
実際のゲームでは絶対に飛ばないはずの[$C1/FCD4]以降の処理もきちんと設定されており、処理を追いかけてみると、[$00:1A3B](敵データ27)を[$7E:AA07]の状態異常に、[$00:1A3C](敵データ28)を確率で状態異常継続時間としてセットするサブルーチンになっているようである(ここでは長くなるし話が横道にそれてしまうため、説明は雑多メモ > 戦闘開始時の状態異常に記した)。
つまり、実際のゲームでは採用されなかったが、敵データ27・敵データ28は戦闘開始時に状態異常を敵にセットするための値を入れておくアドレスだった、ということが推測できる。

とにかく、どの敵であっても必ず上のように[$FCD4]分岐が起こらずに先に進む。

$C1/FCF1 LDA $0025,y[$00:1A25]   ;Aに[$00:1A25](敵データ05)をロード
$C1/FCF4 STA $7E8F03,x[$7E:AA03] ;Aを[$7E:AA03](敵番号0 力(現在値))に書き込み
$C1/FCF8 LDA $0026,y[$00:1A26]   ;Aに[$00:1A26](敵データ06)をロード
$C1/FCFB STA $7E8F04,x[$7E:AA04] ;Aを[$7E:AA04](敵番号0 速(現在値))に書き込み
$C1/FCFF LDA $0027,y[$00:1A27]   ;Aに[$00:1A27](敵データ07)をロード
$C1/FD02 STA $7E8F05,x[$7E:AA05] ;Aを[$7E:AA05](敵番号0 体(現在値))に書き込み
$C1/FD06 LDA $0028,y[$00:1A28]   ;Aに[$00:1A28](敵データ08)をロード
$C1/FD09 STA $7E8F06,x[$7E:AA06] ;Aを[$7E:AA06](敵番号0 知(現在値))に書き込み
$C1/FD0D TXY                     ;XレジスタをYレジスタにコピー
$C1/FD0E PLX                     ;Xレジスタへ値をプル
$C1/FD0F RTS                     ;サブルーチン戻り

$C1/FCF1$C1/FD09は、敵データ05~08に入っている、敵の力・速・体・知を、それぞれ[$7E:AA03][$7E:AA06]に書き込んでいる。
これでひととおりの処理が終わりである。

奴1体目のレベル・HP・力・速・体・知がひととおり書き込まれたことになる。
レベル・HPについては前述のとおりに元の敵データから、実際のレベル・HPを計算しているものの、この時点では、敵データがそのままコピーされたのみである。

ステータス数値バンク$D5$00:1A??$7E:AA0?
レベル$02[$D5:1494][$00:1A29][$00:1A0B](=[$00:1A29] & $7F)
[$7E:AB00]
HP$28[$D5:148F][$00:1A24][$00:1A11][$00:1A10](2バイト)
[$7E:AA01][$7E:AA00](2バイト)
$10[$D5:1490][$00:1A25][$7E:AA03]
$18[$D5:1491][$00:1A26][$7E:AA04]
$28[$D5:1492][$00:1A27][$7E:AA05]
$14[$D5:1493][$00:1A28][$7E:AA06]

奴2体目だが、奴1体目と同じサブルーチンで、1体目なら$7E:AA0?~と$7E:AB0?~に入った値が、$7E:AA1?~と$7E:AB1?~、つまり+$10されたアドレスに入る。
つまり、~$C1/FD0Fまでは、奴1体目も2体目も、まったく同じステータスである。

奴1体目の続きの処理を見てみよう。

$C1/25D7 LDA $0A00  [$00:0A00]   ;Aに[$00:0A00]をロード
$C1/25DA CMP #$07                ;Aと$07で減算比較
$C1/25DC BNE $0D    [$25EB]      ;ゼロフラグが立っていないとき[$25EB]分岐
$C1/25DE LDX #$1AF0              ;Xに$1AF0をロード
$C1/25E1 JSR $E1A9  [$C1:E1A9]   ;[$C1:E1A9]へ

[$00:0A00]の値をロードし、$07と減算比較している。
[$00:0A00]には、フィールドでも戦闘でも共通して、現在プレイしているシナリオの値が格納されている。
下表の通り、幕末編なら$07である。
上の処理で、ゼロフラグが立つのは幕末編だけということである。
よってこの先の処理は、幕末編専用であり、幕末編以外では行われない。

シナリオ[$00:0A00]
中世$00
原始$01
SF$02
功夫$03
西部$04
現代$05
近未来$06
幕末$07
最終$08
$C1/25F2 LDA $002F,y[$00:1A2F]   ;Aに[$00:1A25](敵データ15)をロード
$C1/25F5 AND #$F0                ;Aと$F0で論理積
$C1/25F7 STA $11    [$00:0311]   ;Aを[$00:0311]に書き込み
$C1/25F9 BEQ $1A    [$2615]      ;ゼロフラグが立っているとき[$2615]分岐
$C1/25FB LSR A                   ;Aを論理右シフト /2
$C1/25FC LSR A                   ;Aを論理右シフト /2
$C1/25FD LSR A                   ;Aを論理右シフト /2
$C1/25FE LSR A                   ;Aを論理右シフト /2
$C1/25FF TAY                     ;AレジスタをYレジスタに転送
$C1/2600 LDA $0F90,y[$00:0F94]   ;Aに[$00:0F94]をロード
$C1/2603 STA $10    [$00:0310]   ;Aを[$00:0310]に書き込み
$C1/2605 INC A                   ;Aをインクリメント+1 $01
$C1/2606 STA $0F90,y[$00:0F94]   ;Aを[$00:0F94]に書き込み
$C1/2609 LDA $10    [$00:0310]   ;Aに[$00:0310]をロード
$C1/260B CLC                     ;キャリーフラグクリア
$C1/260C ADC $11    [$00:0311]   ;A + [$00:0311]
$C1/260E STA $7E920F,x[$7E:AD0F] ;Aを[$7E:AD0F]に書き込み
$C1/2612 JSR $2616  [$C1:2616]   ;[$C1:2616]にジャンプ

$C1/25F2でロードした敵データ15だが、敵のタイプのデータが入っている。

上位
4ビット
下位
4ビット
タイプ
00(なし)
01COLLAPSE
02BREAKDOWN
04OBJECT
08LEADER
20NAME 忍者(2人組)
30NAME 虚無僧
40NAME 奴
50NAME 浪人
60NAME 藩士
70NAME 強藩士
80NAME 外人
90NAME 商人
A0NAME 家老
B0NAME 忍者
C0NAME くの一/ザビエール
D0NAME 腰元
F0NAME 忍者(4人組)

今回調べたい、固有名称のある幕末編ザコ敵のタイプは「NAME」で、必ず上位4ビットに0以外の数値、下位4ビットが0となっている。
$C1/25F5で、敵データ15と$F0で論理積を取っているから、上4ビットを取り出している。この値は[$00:0311]に書き込まれる。
更に$C1/25F9でゼロフラグ判定をしている。
ゼロフラグが立たないのは、上4ビットに0以外の値が入っていない、タイプ「NAME」の敵だけである。
よって、この先の$C1/25FB~の処理は、NAME専用処理になる。

$C1/25FB~では、敵データ15と$F0の論理積を4回論理右シフトしている。つまり÷$10であり、敵データ15の上1桁が下1桁に移動してくる。
奴だと、敵データ15は$40なので、上の桁「4」が下の桁に移動し$04となる。
この値をYレジスタに転送して、[$0F90,y]をロードしているのだが、冒頭で紹介した、固有名称がある敵に遭遇した数のカウンターのアドレスを見ていただきたい。
敵データ15の上1桁の数値を$0F90に加算すると、遭遇数カウンターのアドレスになるのである。
つまり$C1/2606までの処理は、敵データ15から遭遇数カウンターのアドレスを計算する処理で、$C1/2606では実際に遭遇数カウンターの値をロードしている。
この値は一度[$00:0310]に書き込まれる。

なお、最終編のザビエールもなぜかタイプがNAMEに設定されているが、少し前の$C1/25D7$C1/25DCで、プレイ中のシナリオが幕末編かどうかが判定されているので、最終編でザビエールが出現した時、NAME関係のステータス処理は行われない。

アドレス数値
$00:0F92$00~$06忍者(2人組)
$00:0F93$00~$03虚無僧
$00:0F94$00~$0A
$00:0F95$00~$0A浪人
$00:0F96$00~$07藩士
$00:0F97$00~$0A強藩士
$00:0F98$00~$02外人
$00:0F99$00~$04商人
$00:0F9A$00~$05家老
$00:0F9B$00~$08忍者(1人)
$00:0F9C$00~$04くの一
$00:0F9D$00~$09腰元
$00:0F9F$00~$08忍者(4人組)

$C1/2605Aをインクリメント、+1して[$00:0F94]に書き込んだから、「奴に遭遇したカウント数を+1」したことになる。
ここは奴1体目のサブルーチンなので、[$00:0F94]には$01が入ったことになる。
インクリメントする前の値が入った[$00:0310](ここでは$00が入っている)は、[$00:0311](敵データ15と$F0の論理積、$40)と加算され、[$7E:AD0F]に書き込まれている。
つまり[$7E:AD0F]$40が入ったことになる。
[$7E:AD0F]は後に敵の固有名称の判定にも関わってくる。
この後は、[$C1:2616]にジャンプする。

$C1/2616 PHX                     ;Xレジスタをスタックへプッシュ
$C1/2617 LDA #$04                ;Aに$04をロード
$C1/2619 STA $08    [$00:0308]   ;Aを[$00:0308]に書き込み
$C1/261B LDY $0002,x[$00:1B02]   ;Yに[$00:1B02]をロード
;
;ステータス再計算ループ処理
$C1/261E LDA $7E8F03,x[$7E:AA0x] ;Aに[$7E:AA03]~[$7E:AA06]をロード
$C1/2622 CLC                     ;キャリーフラグクリア
$C1/2623 ADC $10    [$00:0310]   ;A + [$00:0310]
$C1/2625 STA $7E8F03,x[$7E:AA0x] ;Aを[$7E:AA03]~[$7E:AA06]に書き込み
$C1/2629 STA $0025,y[$00:1A2y]   ;Aを[$00:1A25]~[$00:1A28]に書き込み
$C1/262C INX                     ;Xをインクリメント +1
$C1/262D INY                     ;Yをインクリメント +1
$C1/262E DEC $08    [$00:0308]   ;[$00:0308]をデクリメント -1
$C1/2630 BNE $EC    [$261E]      ;ゼロフラグが立っていないとき[$261E]分岐
;ステータス再計算ループ処理 ここまで
;
$C1/2632 PLX                     ;Xレジスタへ値をプル
$C1/2633 RTS                     ;サブルーチン戻り

タイプがNAMEの敵のみ、力・体・速・知が入っている[$7E:AA03][$7E:AA06]及び[$00:1A25][$00:1A28]を再計算する。
ループ処理で、[$7E:AA03][$7E:AA06]及び[$00:1A25][$00:1A28][$00:0310]を加算している。
[$00:0310]には、遭遇数カウンター[$00:0F94]の、+1加算前の値が入っている。
奴1体目の場合は[$00:0310]00が入っているから、結果、ループ処理を行っても、値はまったく変化しない。

だが、奴2体目は話が変わってくる。
1体目と同じく、$C1/25F2$C1/2612$C1/2616$C1/2633の処理が行われるが、2体目の場合は奴の遭遇数カウンター[$00:0F94]$01が入った状態で処理を進める。
このため、

$C1/2600 LDA $0F90,y[$00:0F94]   ;Aに[$00:0F94]($01)をロード
$C1/2603 STA $10    [$00:0310]   ;A($01)を[$00:0310]に書き込み
$C1/2605 INC A                   ;Aをインクリメント+1 $01
$C1/2606 STA $0F90,y[$00:0F94]   ;A($02)を[$00:0F94]に書き込み
$C1/2609 LDA $10    [$00:0310]   ;Aに[$00:0310]($01)をロード
$C1/260B CLC                     ;キャリーフラグクリア
$C1/260C ADC $11    [$00:0311]   ;A($01) + [$00:0311]($40)
$C1/260E STA $7E920F,x[$7E:AD1F] ;A($41)を[$7E:AD1F]に書き込み
$C1/2612 JSR $2616  [$C1:2616]   ;[$C1:2616]にジャンプ

奴の遭遇数カウンター[$00:0F94]$02に増加する。
また、[$00:0310]$01が入る。
$C1/260Eの処理は、1体目なら[$7E:AD0F]$40が入っていたが、2体目の処理では[$7E:AD1F]$41が入ったことになる。この値は後で敵の固有名称計算に関わる。

結果、$C1/2616$C1/2633のステータス再計算が以下のようになる。

$C1/2616 PHX                     ;Xレジスタをスタックへプッシュ
$C1/2617 LDA #$04                ;Aに$04をロード
$C1/2619 STA $08    [$00:0308]   ;Aを[$00:0308]に書き込み
$C1/261B LDY $0002,x[$00:1B12]   ;Yに[$00:1B12]をロード
;
;ステータス再計算ループ処理
$C1/261E LDA $7E8F03,x[$7E:AA1x] ;Aに[$7E:AA13]~[$7E:AA16]をロード
$C1/2622 CLC                     ;キャリーフラグクリア
$C1/2623 ADC $10    [$00:0310]   ;A + [$00:0310]($01)
$C1/2625 STA $7E8F03,x[$7E:AA1x] ;Aを[$7E:AA13]~[$7E:AA16]に書き込み
$C1/2629 STA $0025,y[$00:1A2y]   ;Aを[$00:1A25]~[$00:1A28]に書き込み
$C1/262C INX                     ;Xをインクリメント +1
$C1/262D INY                     ;Yをインクリメント +1
$C1/262E DEC $08    [$00:0308]   ;[$00:0308]をデクリメント -1
$C1/2630 BNE $EC    [$261E]      ;ゼロフラグが立っていないとき[$261E]分岐
;ステータス再計算ループ処理 ここまで
;
$C1/2632 PLX                     ;Xレジスタへ値をプル
$C1/2633 RTS                     ;サブルーチン戻り

$C1/2623で、元の力・体・速・知に+1する処理が行われ、[$7E:AA13][$7E:AA16][$00:1A25][$00:1A28]に入ったことになる。
[$00:1A25][$00:1A28]は、この戦闘における敵の種類1、つまり「奴」そのもののステータスを記録してあるアドレスという点が重要である。
修正されたのは「奴2体目」のステータスだけではないのである。
ただし、この時点でも、奴1体目のステータスが入っている[$7E:AA03][$7E:AA06]の値は変わっていない。

戦闘開始時は、ここまで処理した後に、更に敵の名称を画面上ウィンドウに表示し(その処理は次ページで紹介する)、プレイヤーが行動できるようになる。つまりおぼろ丸の操作が可能になる。
その時点まで、ステータスは奴1体目と奴2体目で、以下のようにズレがあるままである。

ステータス奴1体目奴2体目
レベル$02$02
HP$28$28
$10$11
$18$19
$28$29
$14$15

敵の名前表示後の処理

プレイヤーが操作可能となり、何かしらの操作を行うと、奴1体目のステータスが奴2体目に揃えられてしまう。
この時点で、改めて敵種類1のステータス[$00:1A25][$00:1A28]を、戦闘中の全ての奴(この戦闘だと1体目と2体目)に上書きするというサブルーチンが処理されるのである。
敵種類1のステータス[$00:1A25][$00:1A28]には、奴2体目の計算値が上書きされたから、この戦闘においては、奴2体目の値を奴1体目の値に上書きする処理と言い換えることができる。
なお、この処理の前に、[$00:036C]$40が書き込まれ、[$00:036D]には[$00:036C]/$10$04が書き込まれているが、詳細は省略。
奴1体目のループ処理は以下のようになる。

$C1/2136 LDA #$04                ;Aに$04をロード
$C1/2138 STA $08    [$00:0308]   ;Aを[$00:0308]に書き込み(ループ回数)
$C1/213A LDA $0025,y[$00:1A25]   ;[$00:1A25]をロード
$C1/213D STA $20    [$00:0320]   ;[$00:0320]に書き込み
$C1/213F LDA $7E8F03,x[$7E:AA03] ;[$7E:AA03]をロード
$C1/2143 JSR $20CD  [$C1:20CD]   ;[$C1:20CD]にジャンプ
;
;ステータス計算ループ処理(n=0~3)
$C1/20CD CMP $20    [$00:0320]   ;A(=[$7E:AA03 + n])と[$00:0320](=[$00:1A25 + n])を減算比較 ステータスフラグ変更のみ
$C1/20CF BCS $0D    [$20DE]      ;キャリーフラグが立っているとき[$20DE]分岐
$C1/20D1 ADC $6D    [$00:036D]   ;A+[$00:036D](=$04)
$C1/20D3 BCS $04    [$20D9]      ;キャリーフラグが立っているとき[$20D9]分岐
$C1/20D5 CMP $20    [$00:0320]   ;Aと[$00:0320]を減算比較 ステータスフラグ変更のみ
$C1/20D7 BCC $0D    [$20E6]      ;キャリーフラグが立っていないとき[$20E6]分岐
$C1/20D9 LDA $20    [$00:0320]   ;[$00:0320]をロード
$C1/20DB CMP $20    [$00:0320]   ;Aと[$00:0320]を減算比較 ステータスフラグ変更のみ
$C1/20DD RTS                     ;サブルーチン戻り
;
$C1/2146 STA $7E8F03,x[$7E:AA03] ;Aを[$7E:AA03 + n]に書き込み
$C1/214A INX                     ;Xをインクリメント +1
$C1/214B INY                     ;Yをインクリメント +1
$C1/214C DEC $08    [$00:0308]   ;[$00:0308]をデクリメント -1
$C1/214E BNE $EA    [$213A]      ;ゼロフラグが立っていないとき[$213A]分岐
;ステータス計算ループ処理ここまで
;
$C1/2150 LDX $10    [$00:0310]   ;Aに[$00:0310]をロード
$C1/2152 RTS                     ;サブルーチン戻り

ループ処理では、[$00:1A25][$00:1A28]を一度[$00:0320]にロードし、それぞれ[$7E:AA03][$7E:AA06]と減算比較している。
[$7E:AA03][$7E:AA06]には奴1体目のステータス(力・体・速・知)が入っており、[$00:1A25][$00:1A28]には元の敵データから+1された奴2体目のステータス(力・体・速・知)が入っているため、それぞれ1ずつ奴1体目のステータスの方が低い。
このため、減算すると必ず-1になり、減算で負の値になるのでキャリーフラグは立たず、$C1/20CFの分岐先には飛ばないで、そのまま$C1/20D1に進む。
もし奴2体目だったら、減算すると0だからキャリーフラグが立ち、ループ処理がスキップされることになる。つまりステータスの再計算は行われない。

$C1/20D1~で、「奴1体目のステータス[$7E:AA03][$7E:AA06][$00:1A25][$00:1A28]に合わせる処理」が行われることになる。
この戦闘では奴が2体だから、2体目のステータスに合わせることになるが、例えば忍者4体の戦闘であったら、4体目のステータス、つまり1体目は元ステータス+3ということになる。
幕末編のNAMEの敵ではまずないが、元ステータスに何かしらの値を加算してキャリーオーバーする、つまり$FF以上になる可能性を考慮しての判定が、$C1/20D1~である。
幕末編のNAMEの敵の場合、$C1/20D1$C1/20D7の判定は分岐なしで処理されて$C1/20D9まで進むはずである。
最終的に、$C1/2146で、[$00:1A25][$00:1A28]が入った[$00:0320]が、それぞれ[$7E:AA03][$7E:AA06]に書き込まれる。
力・体・速・知すべての処理を終えてサブルーチン終了である。
ここで、奴2体とも、元々の奴の力・体・速・知に+1した値が入った。

ステータス奴1体目奴2体目
レベル$02$02
HP$28$28
$11$11
$19$19
$29$29
$15$15

結果として、奴との戦闘では、奴に遭遇した数[$00:0F94](その戦闘での数も含む)から1引いた値だけ、力・体・速・知が加算されることになる。
もし、奴との初戦が、奴×1だったら、ステータス変動は起こらない。
だが、奴との初戦が、上で示した例のように奴×2の場合、遭遇数は2なので、2 - 1 = 1だけ、力・体・速・知が加算される。この加算は、奴2体どちらにも適用される。

[$00:0F94]は幕末編プレイ中、カウントされ続けるため、奴×2を初戦で倒した後は[$00:0F94]$02が入ったままである。
その後にまた奴×2との戦闘に入ると、

  1. 奴1体目の処理で[$00:0F94]+1されて[$00:0F94] = $03になり、奴1体目にはまず、力・体・速・知に+2した値がステータスとして入る。
  2. 奴2体目の処理で[$00:0F94]+1されて[$00:0F94] = $04になり、奴2体目にはまず、力・体・速・知に+3した値がステータスとして入る。[$00:1A25][$00:1A28]も同様。
  3. おぼろ丸が行動可能になり何か行動した時点で、[$00:1A25][$00:1A28]の力・体・速・知に+3した値が奴1体目に上書きされる。

という処理が行われることにより、奴×2の2戦目では、力・体・速・知に+3されたステータスの奴2体と戦うことになる。

これがタイプ「NAME」の敵のステータスが変動していく仕組みである。
奴は10人存在するので、最終的には初期ステータス+9されることになるが、このことによりオーバーフローしないよう、NAMEの敵の元ステータス(力・体・速・知)は調整してあるようだ。
一応、上のようにキャリーオーバーしないか判定する処理も途中で入っている。
幕末編には極端なステータスが設定されている敵が何体かおり(たとえば、ひとだまは力・体・速が$FFであるし、○○の仏像も力・体・知が$FFである)、もしかすると開発中はそういった敵にもステータス+1の処理を行うつもりがあり、そのためにキャリーオーバー関係の処理をきっちり入れておいたのかもしれないが、あくまでも推測である。

次ページでは、タイプ「NAME」の固有名称の処理について説明する。



このページをシェアする

上へ