基礎知識
戦闘関係
功夫編の修行は、
「主人公の心山拳老師が、味方キャラである弟子と戦闘を行い、老師の技をくらった弟子がレベルアップするとその技を逆ラーニングする」
という、本作の中でもかなり特殊な仕様の戦闘が行われる。
老師ではなく敵側の弟子がレベルアップする際には、ステータスアップや技習得の処理も、修行専用の処理が入る。
そして、技習得については後に修行による技習得バグが発生することがあるのも、本作をプレイした方には広く知られているだろう。
レベルアップの処理は、
技習得バグについては、
現代編や幕末編のラーニングについては、
で既に解説しているので、合わせてお読みいただきたい。
まずは、修行において、「老師が弟子に使用した技のうち1種類のみ、弟子のレベルアップ時に習得」という処理がどのようになっているかを説明していく。
つまり、逆ラーニングになるが、方法自体は現代編や幕末編のラーニングと似ている。
一部のサブルーチンも共通している。
まず、老師が使った技だが、修行の戦闘を含めて、弟子と戦う場面では必ず$7E:9200~に使用した技IDが順に入っていく。
ただし、その技が弟子に命中した場合に限られ、既に使用した技は書き込まれない。
これは現代編や幕末編のラーニングでも行われている処理になる。
修行以外、つまり竹林でレイを弟子にする時の戦闘や、修行前に老師の道場で3人の弟子と初戦闘をする時にも、$7E:9200~に使用した技IDが入る。
だが、これらの戦闘では弟子は経験値を得られないので、レベルアップしない。
このため、弟子が逆ラーニングで技を覚えることはないのである。
経験値を取得できる修行の戦闘でのみ、弟子がレベルアップした時に技を覚えていくことになる。
老師が使用できる技、つまり弟子に逆ラーニングできる技と、技IDは以下の通り。
| 技名 | 技ID |
|---|---|
| 竜虎両破腕 | $8D |
| 百里道一歩脚 | $8E |
| 獅子の手 | $8F |
| 山猿拳 | $90 |
| シマリス脚 | $91 |
| 老狐の舞 | $92 |
| 不射の射 | $93 |
本作には、ひとつ前の技をXボタンで出せる機能があるが、修行時はこの機能を利用して老師の技を記録していく。
戦闘中、味方キャラが何かの技を出すと(Xボタンで出したかどうかは関係ない)、戦闘中の味方キャラデータの「ひとつ前の技」を記録するアドレス+$14に書き込むが、同じ値を$7E:9200~に書き込むことで使用した技を記録していくという仕組みである。
味方キャラ番号1であれば、「ひとつ前の技」を記録するアドレスは$00:1C14である。
ひとつ前の技を記録する仕組みは、セレクトバグの解説で紹介しているので参照していただきたい。
また、功夫編におけるシナリオ別キャラデータで、ユン・レイ・サモのデータは8・9・10番目のキャラとして登録されており、この3キャラの習得済みの技2バイト分の領域は以下の通りである。
| キャラ | アドレス |
|---|---|
| ユン・ジョウ | $00:0F0D~$00:0F0E |
| レイ・クウゴ | $00:0F4D~$00:0F4E |
| サモ・ハッカ | $00:0F8D~$00:0F8E |
では、老師が何かの技を弟子に当てて「ひとつ前の技」が[$00:1C14]に書き込まれた後の、実際の処理を見てみる。
$C1/D1A2 LDX $78 [$00:0378] ;Xに[$00:0378]をロード $C1/D1A4 LDY $0002,x[$00:1C02] ;Yに[$00:1C02]をロード $C1/D1A7 LDA $0014,x[$00:1C14] ;Aに[$00:1C14]をロード $C1/D1AA STA $6A [$00:036A] ;Aを[$00:036A]に書き込み $C1/D1AC JSR $3D7A [$C1:3D7A] ;[$C1:3D7A]へジャンプ
[$00:1C14]に書き込まれている「ひとつ前の技」のIDは、上の処理で[$00:036A]にコピーされる。
$C1/D81A PHA ;Aをスタックにプッシュ $C1/D81B LDA $0001,x[$00:1B01] ;Aに[$00:1B01](戦闘状態 敵操作状態$40)をロード $C1/D81E CMP #$40 ;Aと$40を減算比較(ステータスレジスタ変更のみ) $C1/D820 BNE $0E [$D830] ;ゼロフラグが立っていないとき[$D830]分岐 $C1/D822 REP #$20 ;Aを16bit幅に変更、Mフラグをクリア $C1/D824 LDA $7E900E,x[$7E:AB0E] ;Aに[$7E:AB0E]をロード $C1/D828 TAY ;Aレジスタの値をYレジスタに転送 $C1/D829 SEP #$20 ;MフラグON Aレジスタは8bit幅 $C1/D82B PLA ;Aレジスタに値をプル $C1/D82C STA $003B,y[$00:1C7B] ;Aを[$00:1C7B]に書き込み 味方キャラデータ3人目$3B $C1/D82F RTS ;サブルーチン戻り ; $C1/D805 CMP #$01 ;Aと$01を減算比較(ステータスレジスタ変更のみ) $C1/D807 BNE $0C [$D815] ;ゼロフラグが立っていないとき[$D815]分岐 $C1/D809 JSR $D95D [$C1:D95D] ;[$C1:D95D]へジャンプ ; $C1/D95D JSR $0D0D [$C1:0D0D] ;[$C1:0D0D]へジャンプ
以上は、$7E:9200~へ老師の技を書き込む直前の段階の処理にあたるのだが、$C1/D81B~の処理が重要である。
[$00:1B01]には、敵番号1の戦闘状態が入るのだが、弟子との戦闘では敵にあたるキャラの敵・味方状態が逆転しているので、[$00:1B01]に$40が入っている。
$C1/D81Eで、$40と減算比較しているから、弟子との戦闘の間は必ずゼロフラグが立つ。
つまりこの先は、弟子との戦闘で発生する処理になる。
$C1/0D0D LDX $78 [$00:0378] ;Xに[$00:0378]をロード $C1/0D0F LDA $0000,x[$00:1C00] ;Aに[$00:1C00](味方キャラID 老師=$08)をロード $C1/0D12 CMP #$08 ;Aと$08を減算比較(ステータスレジスタ変更のみ) $C1/0D14 BEQ $01 [$0D17] ;ゼロフラグが立っているとき[$0D17]分岐 ;ここから現代編・幕末編ラーニングと共通処理 $C1/0D17 LDX #$0000 ;Xに$0000をロード ; ;[$7E:9200]~に敵が使用した技IDを書き込むループ処理 $C1/0D1A LDA $7E9200,x[$7E:9200] ;Aに[$7E:9200]をロード $C1/0D1E BEQ $0B [$0D2B] ;ゼロフラグが立っているとき[$0D2B]分岐 $C1/0D20 CMP $6A [$00:036A] ;Aと[$00:036A](ひとつ前の技ID)を減算比較(ステータスレジスタ変更のみ) $C1/0D22 BEQ $06 [$0D2A] ;ゼロフラグが立っているとき[$0D2A]分岐 $C1/0D24 INX ;Xをインクリメント +1 $C1/0D25 CPX #$00FF ;Xと$00FFを減算比較(ステータスレジスタ変更のみ) $C1/0D28 BCC $F0 [$0D1A] ;キャリーフラグが立っていないとき[$0D1A]分岐 ;ループ処理ここまで ; $C1/0D2B LDA $6A [$00:036A] ;Aに[$00:036A]をロード $C1/0D2D STA $7E9200,x[$7E:9200] ;Aを[$7E:9200]に書き込み $C1/0D31 LDA #$00 ;Aにをロード $C1/0D33 STA $7E9201,x[$7E:9201] ;Aを[$7E:9201]に書き込み $C1/0D37 RTS ;サブルーチン戻り
$C1/0D0Fで、戦闘中の味方キャラデータ内、味方キャラ1人目の味方キャラIDが入っている[$00:1C00]をロードしている。
功夫編で老師が弟子と戦う場面では必ず、[$00:1C00]に心山拳老師のキャラIDである$08が入っているから、$C1/0D12で$08と減算比較すると、必ずゼロフラグが立つ。
ここで、「戦闘の敵が弟子のユン・レイ・サモのいずれか」かつ「操作キャラが心山拳老師」が確定となり、$C1/0D17~から、現代編・幕末編ラーニングと共通の、[$7E:9200]~に技IDを書き込むループ処理が開始になる。
違いは、書き込む技IDが、[$00:036A](=[$00:1C14]に書き込まれている「ひとつ前の技」のID)だということだけである。
戦闘中は老師が技を出すたびにこの処理が入り(老師が反撃時も含む)、[$7E:9200]~に老師が使用した技が書き込まれていく。
では、戦闘勝利時、どのような処理が行われるのか。
幕末編勝利時と似たようで少し異なる処理が入る。
サブルーチンのアドレスも、現代編や幕末編とは異なる。アドレス自体は近いので、ラーニング関連のサブルーチンがまとめてあるのだろうということがわかる。
ここでは、初期レベル3のレイに、「百里道一歩脚」「竜虎両破腕」「竜虎両破腕」の順に技を当てて勝利し、レイがレベル4にレベルアップしたという状況での処理を説明する。
「竜虎両破腕」の技IDは$8D、「百里道一歩脚」は$8Eなので、$7E:9200~には以下のように値が入った状態で勝利したことになる。
| アドレス | 値 |
|---|---|
$7E:9200 | $8E |
$7E:9201 | $8D |
$7E:9202 | $00 |
また、レイの習得済み技のアドレス[$00:0F4D][$00:0F4E]は、レベル3の初期状態(「猿手」「ネコけり」「水鳥脚」「蛇形拳」を習得済み)だと以下のように値が入っている。
| アドレス | 値(16進数) | 値(2進数) |
|---|---|---|
$00:0F4D | $0F | %0000 1111 |
$00:0F4E | $00 | %0000 0000 |
以下が該当箇所の処理になる。
(なお、この直前に、レベルアップによるHP計算処理が入るが、それは次ページで説明する)
$C1/0D5B STY $1C [$00:031C] ;Yを[$00:031C]に書き込み $C1/0D5D LDX #$0001 ;Xに$0001をロード $C1/0D60 STX $20 [$00:0320] ;X($0001)を[$00:0320]に書き込み $C1/0D62 LDA $000D,y[$00:0F4D] ;Aに[$00:0F4D](レイの習得技下1バイト)をロード $C1/0D65 STA $22 [$00:0322] ;Aを[$00:0322]に書き込み $C1/0D67 LDA $000E,y[$00:0F4E] ;Aに[$00:0F4E](レイの習得技上1バイト)をロード $C1/0D6A STA $23 [$00:0323] ;Aを[$00:0323]に書き込み $C1/0D6C LDX $0004,y[$00:0F44] ;Xに[$00:0F44]をロード $C1/0D6F LDA #$10 ;Aに$10をロード $C1/0D71 STA $08 [$00:0308] ;Aを[$00:0308]に書き込み
ここは、レイの習得済み技のアドレス[$00:0F4E][$00:0F4D]の中で、最も下位ビットの「0」の値を見つけるループ処理の前準備である。
[$00:0F4E][$00:0F4D]の値はそれぞれ、[$00:0323][$00:0322]にコピーされている。
つまり、[$00:0323] = $0F, [$00:0322] = $00である。
また、ループ回数は[$00:0308]に書き込まれた$10、つまり10進数の16が最大値になり、ループの度にデクリメントされていく。
;ループここから $C1/0D73 STX $1A [$00:031A] ;Xを[$00:031A]に書き込み $C1/0D75 LSR $23 [$00:0323] ;[$00:0323]を論理右シフト (/2) $C1/0D77 ROR $22 [$00:0322] ;[$00:0322]をキャリーフラグを含めた9bitで右ローテート $C1/0D79 BCS $19 [$0D94] ;キャリーフラグが立っているとき[$0D94]分岐 $C1/0D94 ASL $20 [$00:0320] ;[$00:0320]を算術左シフト *2 $C1/0D96 ROL $21 [$00:0321] ;[$00:0321]をキャリーフラグを含めた9bitで左ローテート $C1/0D98 LDX $1A [$00:031A] ;Xに[$00:031A]をロード $C1/0D9A INX ;Xをインクリメント +1 $C1/0D9B DEC $08 [$00:0308] ;[$00:0308]をデクリメント -1 $C1/0D9D BNE $D4 [$0D73] ;ゼロフラグが立っていないとき[$0D73]分岐 ;ループここまで
ループ処理は以上のとおり。
まず、$C1/0D75で[$00:0323]を論理右シフトしているが、ここに入っているのはレイの習得技の上1バイトだから「%0000 0000」の論理右シフトになり、結局0のままで、キャリーフラグは立たない。
次の$C1/0D77は、[$00:0322]をキャリーフラグを含めた9bitで右ローテートである。
ひとつ前の処理でキャリーフラグは立っていないから、
%0 0000 1111
を右ローテート、つまり値をひとつずつ右へ移動させる。
結局、[$00:0323]の一番下のビットと、[$00:0322]の8ビットを右ローテートなので、[$00:0323][$00:0322]の16ビット分の右ローテートとほとんど同じ意味である。少なくとも下9ビット分は同値が右ローテートになる。
こうして右ローテートを行うと、一番下のビットが1なら、「÷2の処理で余りが出てキャリーフラグが立つ」と同じことが起きて、キャリーフラグがONになる。
一番下のビットが0なら、右ローテートしてもキャリーフラグは立たない。
よって、$C1/0D79のキャリーフラグ分岐は、「一番下のビットが1の時に右ローテートした時」である。
一番下のビットが0だったらキャリーフラグが立たず、ループ処理を抜ける。
一方、一番下のビットが1ならキャリーフラグが立ち、$C1/0D94に進む。
$C1/0D94の[$00:0320]を算術左シフト(×2)だが、[$00:0320]にはループ前に$0001が書き込まれている。ここでは8bitモードなので[$00:0321]に$00、[$00:0320]に$01が入っており、[$00:0320]を算術左シフト(×2)していくと、$02, $04, $08, $10, $20, $40, $80, $100となるから、8回目のループ時にキャリーオーバーになる。
$C1/0D96は[$00:0321]をキャリーフラグを含めた9bitで左ローテートだから、8回目までのループの時は$00を左ローテートするだけで値は00のまま変化しないことになる。8回目だとキャリーフラグが立っているから、%1 0000 0000の左ローテートになる。
結局、[$00:0321][$00:0320]はどう変わっていくのかというと、8回目のループまでは、[$00:0320]の初期値%0000 0001の「1」が1つずつ左に移動していき、8回目でキャリーフラグ + %0000 0000になって、以降のループでは算術左シフト(×2)しても00のままとなる。
一方、[$00:0321]は7回目のループ時までは$00のまま変わらないが、8回目は%1 0000 0000の左ローテートで%0000 0001が入り、以降の左ローテートで「1」が1つずつ左に移動していく。
要するに、[$00:0321][$00:0320]を16ビットで見ると、ループ回数分のビットに「1」が入り、他は「0」ということである。
レイの場合、[$00:0323][$00:0322] = %0000 0000 0000 1111を4回論理右シフトした時点で%0000 0000 0000 0000だから、5回目のループの$C1/0D79でキャリーフラグが立たずにループを抜ける。
この時、[$00:0321][$00:0320]は、%0000 0000 0001 0000となっている。
要するに、[$00:0321][$00:0320]は、ループを抜けた時、レイの習得技が格納されていた[$00:0F4E][$00:0F4D]の中で、0が入っている最も下位のビット、下から5番目のビットにだけ1が立った状態になる。
なお、功夫編の弟子は、通常戦闘でレベルアップしたとしても、レベル16以上で最後の「旋牙連山拳」を習得できないから、修行開始時に習得できるのは最大で15種類の技であり、16回目のループに入った直後の$C1/0D79でキャリーフラグが立ってループを抜けるはずである。
ループの最後に[$00:0308]をデクリメントしているが、[$00:0308]の中が0になってループを抜ける$C1/0D9Dの処理ではループが終わらないはずである。
何はともあれ、5回目のループに入った直後から処理の続きを見てみよう。
$C1/0D73 STX $1A [$00:031A] ;Xを[$00:031A]に書き込み $C1/0D75 LSR $23 [$00:0323] ;[$00:0323]を論理右シフト (/2) $C1/0D77 ROR $22 [$00:0322] ;[$00:0322]をキャリーフラグを含めた9bitで右ローテート $C1/0D79 BCS $19 [$0D94] ;キャリーフラグが立っているとき[$0D94]分岐 ;ループ終了 ; $C1/0D7B LDA $D50005,x[$D5:028B] ;Aに[$D5:028B]をロード $C1/0D7F STA $6A [$00:036A] ;Aを[$00:036A]に書き込み $C1/0D81 LDX #$0000 ;Xに$0000をロード
ループ前、Xに[$0004,y](レイだと$0282)が読み込まれて、ループ中にXがインクリメントされていくのだが、ループを抜けた時点でロードされる[$D50005,x]がちょうど、該当キャラの味方キャラデータ05~以降、つまり技レベル00~技レベル16に格納された技IDになる。
上では呼び出されたのが[$D5:028B]だが、これはレイのキャラデータにおける09番目の値で、技レベル04 = 竜虎両破腕($8D)になる。
この値が一度[$00:036A]に書き込まれ、今度は[$7E:9200]~に一致する値があるかを調べるループ処理が開始になる。
Xには初期値として$0000が入る。
;[$7E:9200]~に一致する値があるか確認ループ $C1/0D84 LDA $7E9200,x ;Aに[$7E:9200+x]をロード $C1/0D88 BEQ $0A [$0D94] ;ゼロフラグが立っているとき[$0D94]分岐 $C1/0D8A CMP $6A [$00:036A] ;Aと[$00:036A]を減算比較(ステータスレジスタ変更のみ) $C1/0D8C BEQ $13 [$0DA1] ;ゼロフラグが立っているとき[$0DA1]分岐 $C1/0D8E INX ;Xをインクリメント +1 $C1/0D8F CPX #$00FF ;Xと$00FFを減算比較(ステータスレジスタ変更のみ) $C1/0D92 BCC $F0 [$0D84] ;キャリーフラグが立っていないとき[$0D84]分岐 ;ループここまで
このループ処理は、[$7E:9200]~以降と[$00:036A]に一致する値があれば、$C1/0D8Cで減算比較のゼロフラグが立ちループを抜ける、という処理になる。
また、[$7E:9200]~に$00が入っている場合は、$C1/0D88でゼロフラグが立ちループを抜ける。
Xは$0000からインクリメント(+1)されていくので、$C1/0D8FでXと$00FFを減算比較し、キャリーフラグが立たないとループを抜けるようになってはいるが、通常は$C1/0D88か$C1/0D8Cのゼロフラグでループを抜けるはずである。
老師は7種類の技を使うから、[$7E:9200]~以降、技IDで埋まるのは[$7E:9206]までで、[$7E:9207]に$00が入り、ループ回数は最大7回、8回目の頭で$C1/0D88のゼロフラグが立ちループを抜けることになる。
$C1/0D8Cでゼロフラグが立ちループを抜けるのは、レイが習得可能な技、かつ未習得で一番下位のレベルの技IDと、老師が出した技IDが入っている[$7E:9200]~が一致した時である。
この場合は老師が出した技を習得する処理のため、$C1/0DA1にジャンプする。
一方、$C1/0D88でゼロフラグが立ちループを抜けるのは、[$7E:9200]~に入っている老師が出した技とはどれも一致しなかった場合である。
この場合は何の技も習得しないことになり、$C1/0D94にジャンプする。
まず、$C1/0D88でゼロフラグが立った時の飛び先、$C1/0D94~を見てみよう。
ここに飛ぶケースは、例えばレイが「竜虎両破腕」を未習得かつ、老師がこの戦闘で「竜虎両破腕」を出さなかったので、[$7E:9200]~に一致しなかった、という場合である。
この場合、例えば、老師は「竜虎両破腕」は出していないが「百里道一歩脚」は出した、という時、次に「百里道一歩脚」用の判定をしなければならない。
つまり、『レイは「百里道一歩脚」を習得済みかどうか』を調べなければならない。
;$C1/0D88からのジャンプ先(一致なし) $C1/0D94 ASL $20 [$00:0320] ;[$00:0320]を算術左シフト *2 $C1/0D96 ROL $21 [$00:0321] ;[$00:0321]をキャリーフラグを含めた9bitで左ローテート $C1/0D98 LDX $1A [$00:031A] ;Xに[$00:031A]をロード $C1/0D9A INX ;Xをインクリメント +1 $C1/0D9B DEC $08 [$00:0308] ;[$00:0308]をデクリメント -1 $C1/0D9D BNE $D4 [$0D73] ;ゼロフラグが立っていないとき[$0D73]分岐 $C1/0D9F BRA $1A [$0DBB] ;フラグにかかわりなく常に分岐[$0DBB]
$C1/0D94~$C1/0D96は、要するに[$00:0321][$00:0320]を16ビットで見た時に左にローテートしていることになる。
つまり、レイが習得可能な技、かつ未習得で一番下位+1のレベルの技IDのチェックである。
[$00:0308]は初期値が$10で、デクリメントしていくから、レベル16の技のチェックをした後に$C1/0D9Dでゼロフラグが立つ。
逆に、レベル16以下の技のチェック時は、ゼロフラグが立たないから、[$0D73]に分岐する。
$C1/0D73は先に紹介したループ処理の頭であり、
『レイの習得済み技のアドレス[$00:0F4E][$00:0F4D]の中で、最も下位ビット+1の「0」の値を見つけるループ処理』
という処理に変わる。
こうして、「レイが習得していない技、かつ老師が戦闘で使った技が一致するかどうか」が順番にチェックされていくことになる。
修行時にレイがレベル11以上だと、老師が使う技はすべて習得済みということになるから、最終的に$C1/0D9Fでループを抜けて何もラーニングしない。
$C1/0D9Fの飛び先である[$0DBB]は、
$C1/0DBB RTS ;サブルーチン戻り
これなので、サブルーチンを抜けることになる。
一方、『レイが習得可能な技、かつ未習得で一番下位のレベルの技IDと、老師が出した技IDが入っている[$7E:9200]~が一致した時』の飛び先の$C1/0DA1以降、つまり技を覚える処理は以下の通り。
(上の分岐でもわかるが、実際には『レイが習得可能な技、かつ未習得で一番下位のレベルの技ID + nと、老師が出した技IDが入っている[$7E:9200]~が一致した時』の飛び先でもある)
;$C1/0DA1からのジャンプ先(一致あり、ラーニング処理) $C1/0DA1 REP #$20 ;Aを16bit幅に変更、Mフラグをクリア $C1/0DA3 LDY $1C [$00:031C] ;Yに[$00:031C]をロード $C1/0DA5 LDA $000D,y[$00:0F4D] ;Aに[$00:0F4E][$00:0F4D](レイの習得技2バイト)をロード $C1/0DA8 ORA $20 [$00:0320] ;Aと[$00:0321][$00:0320]で論理和 $C1/0DAA STA $000D,y[$00:0F4D] ;Aを[$00:0F4E][$00:0F4D](レイの習得技2バイト)に書き込み $C1/0DAD SEP #$20 ;MフラグON Aレジスタは8bit幅 $C1/0DAF LDA $6A [$00:036A] ;Aに[$00:036A]をロード $C1/0DB1 LDA $0070 [$00:0070] ;Aに[$00:0070]をロード $C1/0DB4 CMP #$01 ;Aと$01を減算比較(ステータスレジスタ変更のみ) $C1/0DB6 BEQ $03 [$0DBB] ;ゼロフラグが立っているとき[$0DBB]分岐 $C1/0DB8 JSR $2787 [$C1:2787] ;[$C1:2787]へジャンプ $C1/0DBB RTS ;サブルーチン戻り
ここまでの処理で、[$00:0321][$00:0320]にはこれから習得する技レベルのビットに1が立った16ビットの値が入っている。
[$00:0321][$00:0320]と、[$00:0F4E][$00:0F4D](レイの習得技2バイト)の論理和、要するに足し算をして、それを新たに[$00:0F4E][$00:0F4D](レイの習得技2バイト)とする、という処理が$C1/0DAAまでであり、これで逆ラーニング処理が終了になる。
以上からわかることとして、老師が何種類か技を使用しても、技習得処理では、
「弟子が習得可能な技、かつ未習得で一番下位のレベルの技IDから順に上位へ向かってチェック」
して、最初に見つかった未習得技をラーニングして終了となっている。
このため、一度の戦闘で逆ラーニングさせられるのは技1種類のみ、そして弟子がもっとも低いレベルで覚える技が最優先になるため、どの弟子でも、優先順位が、
竜虎両破腕>百里道一歩脚>獅子の手>山猿拳>シマリス脚>老狐の舞>不射の射
この順番になることがわかる。
次ページでは、修行時のステータスアップ処理について説明する。