基礎知識
戦闘関係
世界の合言葉は森部様で紹介している、ダメージ計算式はプログラム上ではどうなっているのかを、このページで説明する。
・単発ダメージ:
(((min(max((8+技LV+自LV-敵LV),1),24)*LV差係数/8+(自能力値*係数)) *((255-敵能力値)/2)/256)*2+攻撃力) *乱数(1~1.25)
(乱数幅は厳密には単発ダメージ*(0~63)/256)
最後の「乱数」は、戦闘用乱数のことなのだが、乱数計算についての説明は後回しにして、まずは乱数以外の部分のサブルーチンを説明する。
乱数計算については次ページで解説しているので参照のこと。
また、プログラミング関係解説&調査トップページにひととおり目を通して、スーパーファミコンの仕様や65C816向けのアセンブリ言語の基本知識を頭の片隅に置いた状態で読み進めていただきたい。
まず改めて、単発ダメージの計算式を見ると、掛け算や割り算が混ざっている。
だが基本知識に記した通り、65C816には乗除算の機能がないため、代わりの方法で乗除算を行っている。
上の単発ダメージの計算式だと、「*2」は×2の命令であるASLを使えば良い。
「/2」や「/8」は、÷2の命令であるLSRを使えばよい。3回連続で使うと「÷8」が実行できる。
掛け算である「*LV差係数」は、コプロセッサを利用した処理を行っている。
更に、割り算の「/256」は、「16bitモードでメモリに入れた数値の、ひとつ上のアドレスを値を取得することで、実質÷256を実行する」という方法を使っている。詳しい説明は該当部分の解説で行う。
掛け算は、最初の数を「被乗数」、×の後の数を「乗数」としている。
つまり「被乗数」×「乗数」である。
話を戻して、単発ダメージの計算は、$C1/77CEから開始され、$C1/782Dにてアキュムレータ(以下Aとする)に単発ダメージの計算値が入る仕組みになっている。
計算の前には、使用する技についてのデータや、ダメージ計算に使うための味方と敵のステータスが読み込みされたり、事前に計算も行われるが、そのあたりは省く。
細かい説明は後で行う。
65C816では数値を扱う上で、8bitモードと16bitモードのどちらで計算しているかが重要であることも先に記したが、下のサブルーチンはAが8bitモードで計算が開始になっている。
切り替えている場所(命令)には「Aを16bit幅に変更」などと説明をしてある。
$C1/77CE LDA #$08 ;Aに$08(=8)をロード $C1/77D0 CLC ;キャリーフラグクリア $C1/77D1 ADC $7E900C;A + [$7E:900C](=技LV+自LV) $C1/77D5 BCC $02 ;キャリーフラグが立っていないなら$C1/77D9へ $C1/77D9 SEC ;キャリーフラグを立てる $C1/77DA SBC $25 ;A - [$00:0325](=敵LV) $C1/77DC BCC $08 ;キャリーフラグが立っていないなら$C1/77E6へ $C1/77DE CMP #$18 ;Aと$18(24)を減算比較 $C1/77E0 BCC $06 ;キャリーフラグが立っていないなら$C1/77E8へ ;※ここから符号付8bit × 8bit処理: $C1/77E8 STA $4202 ;Aを被乗数として[$00:4202]にセット $C1/77EB LDA $7E900B;Aに[$7E:900B](=技データ10)をロード $C1/77EF AND #$FC ;Aと$FC(=252)で論理積(技データ10からLV差係数算出) $C1/77F1 STA $4203 ;A(=LV差係数)を乗数として[$00:4203]にセット $C1/77F4 REP #$21 ;Aを16bit幅に変更、キャリーフラグクリア $C1/77F6 LDA $7E900E;Aに[$7E:900E](計算済みの「自能力値*係数」)をロード $C1/77FA AND #$00FF ;Aと$00FFで論理積(Aの上位2ビットを00に、下位2ビットはそのまま) $C1/77FD STA $20 ;Aを[$00:0320]に書き込み(「自能力値*係数」を書き込み) $C1/77FF LDA $4216 ;掛け算の結果[$00:4216]をAにロード ;※8bit × 8bit処理はここまで $C1/7802 LSR A ;論理右シフト(A/2) $C1/7803 LSR A ;論理右シフト(A/2) $C1/7804 LSR A ;論理右シフト(A/2) $C1/7805 CLC ;キャリーフラグクリア $C1/7806 ADC $20 ;A + [$00:0320](=自能力値*係数) ;※符号付16bit × 8bit $C1/7808 SEP #$20 ;MフラグON。Aは8bit幅 $C1/780A STA $211B ;Aを被乗数下位バイトとして[$00:211B]にセット $C1/780D XBA ;Aの上位バイトと下位バイトを交換 $C1/780E STA $211B ;Aを被乗数上位バイトとして[$00:211B]に書き込み $C1/7811 LDA #$FF ;Aに$FF(=255)をロード $C1/7813 SEC ;キャリーフラグを立てる $C1/7814 SBC $26 ;A(=255) - [$00:0326](=敵能力値) $C1/7816 LSR A ;論理右シフト(A/2) $C1/7817 STA $211C ; $C1/781A STA $211C ;Aを乗数として[$00:211C]にセット $C1/781D LDA $7E900F;Aに[$7E:900F]をロード(攻撃側が味方の時の攻撃力) $C1/7821 STA $22 ;Aを[$00:0322]に書き込み $C1/7823 STZ $23 ;[$00:0323]に1バイトの00を書き込み $C1/7825 REP #$21 ;Aを16bit幅に変更、キャリーフラグクリア $C1/7827 LDA $2135 ;掛け算の結果の中央部分2バイトをAにロード ;※16bit × 8bit処理はここまで $C1/782A ASL A ;算術左シフト(A*2) $C1/782B ADC $22 ;A + [$00:0322](=攻撃力) $C1/782D STA $20 ;Aを[$00:0320]に書き込み
では、部分ごとに何が行われているか説明する。
まず
min(max((8+技LV+自LV-敵LV),1),24)
の計算である。
$C1/77CE LDA #$08 ;Aに$08(=8)をロード $C1/77D0 CLC ;キャリーフラグクリア $C1/77D1 ADC $7E900C;A + [$7E:900C](=技LV+自LV) $C1/77D5 BCC $02 ;キャリーフラグが立っていないなら$C1/77D9へ $C1/77D9 SEC ;キャリーフラグを立てる $C1/77DA SBC $25 ;A - [$00:0325](=敵LV) $C1/77DC BCC $08 ;キャリーフラグが立っていないなら$C1/77E6へ $C1/77DE CMP #$18 ;Aと$18(24)を減算比較 $C1/77E0 BCC $06 ;キャリーフラグが立っていないなら$C1/77E8へ
ここは計算部分だけ抜粋してあるのだが、ここまでにメモリ[$7E:900C]に技LV+自LVの結果、[$00:0325]に敵LVが入っている。
[$7E:900C]及び、[$00:0325]については、このページの一番下で、別途説明する。
$C1/77CEで16進数の$08、10進数の8をAにロードし、$C1/77D1で「技LV+自LV」を足して、$C1/77DAで敵LVを引いた。
減算後のキャリーフラグについては、プログラミング関係解説&調査トップページ及び、テレポートの仕様簡易解説で説明したが、キャリーフラグが立つ場合は減算結果で0以下の時。
正の数(1以上)なら、キャリーフラグが立たない。
次の$C1/77DCの「キャリーフラグが立っていないなら$C1/77E6へ」という分岐が起こるが、ここが
max((8+技LV+自LV-敵LV),1)
の部分である。
上ではキャリーフラグが立った後の$C1/77E6~以降の処理は乗せていないが、$C1/77E6~で結果が1になる。
続いて、「8+技LV+自LV-敵LV」が1以上なら$C1/77DEで16進数の18(10進数の24)を引いて、同じようにキャリーフラグが立つかどうかを判定する。
min((8+技LV+自LV-敵LV),24)
の部分である。
「8+技LV+自LV-敵LV」の方が小さい場合はキャリーフラグが立って$C1/77E8へ飛び、そうでない場合はそのまま計算が続いて計算結果を16進数の18(10進数の24)とする。
続いて、「*LV差係数」の処理が行われるが、乗算なので、
乗除算 - 65C816命令表 ロード・ストア命令
にある符号付8bit × 8bitの方法で計算が行われる。
ちなみに「符号付」とは正と負の両方の数値、「符号なし」は0と正の数値のみを意味する。符号属性 - Wikipediaも参照。
※余談だが、乗除算 - 65C816命令表 ロード・ストア命令の冒頭に、乗除算は、「BGモード7の拡縮回転用マトリクスを使って計算しています」とある。
どういう意味かというと、スーパーファミコンの特徴のひとつである、画像の拡大・縮小・回転機能を使って乗除算の計算をする仕組み、ということである。
ただしこれは、符号付16bit x 8bitの、$211B~$211Cを使う時のみ。
本作で言えば、SF編キャプテンスクウェア開始時に、キャプテンスクウェアが回転しながら拡大される演出があるが、あの時に使われている。戦闘開始時に画面全体が三角形に切り抜かれつつ回転してブラックアウトする演出も同様。
つまり、これら演出の最中は拡大・縮小・回転機能を使用しているため、$211B~$211Cを使う計算を行うことはできない(やろうとするとエラーになるらしい。snes-docs-ja / 乗算を参照)。
;※ここから符号付8bit × 8bit処理: $C1/77E8 STA $4202 ;Aを被乗数として[$00:4202]にセット $C1/77EB LDA $7E900B;Aに[$7E:900B](=技データ10)をロード $C1/77EF AND #$FC ;Aと$FC(=252)で論理積(技データ10からLV差係数算出) $C1/77F1 STA $4203 ;A(=LV差係数)を乗数として[$00:4203]にセット $C1/77F4 REP #$21 ;Aを16bit幅に変更、キャリーフラグクリア $C1/77F6 LDA $7E900E;Aに[$7E:900E](計算済みの「自能力値*係数」)をロード $C1/77FA AND #$00FF ;Aと$00FFで論理積(Aの上位2ビットを00に、下位2ビットはそのまま) $C1/77FD STA $20 ;Aを[$00:0320]に書き込み(「自能力値*係数」を書き込み) $C1/77FF LDA $4216 ;掛け算の結果[$00:4216]をAにロード ;※8bit × 8bit処理はここまで
形式としては、STA $4202の時点でAに入っている数値(ここまでの計算値)と、STA $4203の時点でAに入っている数値とを掛け算して、その結果が[$00:4216]に出力されることになっている。
また、答えは16bitで出力されるため、答えをAに16bit(16進数4文字分)で出力するのなら、事前にAを16bitモードに切り替える必要がある。
STA $4203の前の、
$C1/77EB LDA $7E900B;Aに[$7E:900B](=技データ10)をロード $C1/77EF AND #$FC ;Aと$FC(=252)で論理積(技データ10からLV差係数算出)
この部分でLV差係数を計算し、Aに入れている。
技データについては、プログラミング関係解説&調査トップページでも触れたが、本作の技のデータは、それぞれの技について、25バイト分の領域を使って記録されており、便宜上25個のデータに順に0から24の番号を付けておく。
その中の11バイト目の「技データ10」に、LV差係数が格納されている。
例えば「ローキック」だと16進数で「0A」という数値であり、2進数だと「00001010」である。
2進数での上6桁がLV差係数にあたる。
ある数値から特定の場所を取り出す方法として、論理積を取る方法があることも、先に記した通りである。
2進数の上6桁を取り出すために、$C1/77EFでは、技データ10と$FC(2進数11111100)で論理積を取っている。
論理積を取ることで求めたLV差係数は、次の$C1/77F1で乗数として[$00:4203]にセットされる。
掛け算の処理に少し時間がかかるので、$C1/77F4~$C1/77FDで別処理をしている。
この後の計算には「自能力値*係数」の数値が必要だが、ここまでで[$7E:900E]に格納済みなので、$C1/77F6にてAにロードしている。
ただし、それより前のREP #$21という処理でAは16bitモードに切り替わっている。
よって、この時はAに4桁の数値が読み込まれている。
さて、$C1/77F6に書かれているLDAという命令は、Aに指定の数値をロードしろという命令である。
ここではLDA $7E900Eと書かれており、[$7E:900E](=「自能力値*係数」)というアドレスが指定されている。
8bitモードなら、[$7E:900E]の下2桁だけが読み込まれてAに収納されるのであるが、LDAを16bitモードで実行するため、指定アドレスの内容を下位バイトとして、指定アドレス+1にある内容を上位バイトとして、合計4桁で読み込む。
ここでいえば「[$7E:900E]の数値を下2桁、[$7E:900F]の数値を上2桁」として読み込むことになる。
必要なのは[$7E:900E]だけで、[$7E:900F]の数値は必要がない。
しかも、後々でAの上2桁が計算に必要になるので(後述)、上2桁に読み込まれた[$7E:900F]の数値は削除しておきたい。
そこで、Aの上位8bitに「00」を入れる処理をしたのが、$C1/77FAである。
ここではAと$00FFで論理積を取ることで、上2桁は00、下2桁に「自能力値*係数」を残している。
そして、$C1/77FDでは[$00:0320]に「上2桁は00、下2桁に自能力値*係数」を格納している。
というように掛け算処理の時間を稼いだところで、$C1/77FFで掛け算の結果[$00:4216]をAにロードし、「*LV差係数」までの計算が終了。
$C1/7802 LSR A ;論理右シフト(A/2) $C1/7803 LSR A ;論理右シフト(A/2) $C1/7804 LSR A ;論理右シフト(A/2) $C1/7805 CLC ;キャリーフラグクリア $C1/7806 ADC $20 ;A + [$00:0320](=自能力値*係数)
$C1/7802~$C1/7804では、「LSR A」という同じ処理を3回繰り返している。
先に記したとおり、これは「Aを÷2して新たにAに入れる」処理であり、3回繰り返すと÷8ということになる。
これで、
(min(max((8+技LV+自LV-敵LV),1),24)
まで計算できたことになる。
更に$C1/7806では、[$00:0320]を足し算しているが、$C1/77FDで「上2桁は00、下2桁に自能力値*係数」を[$00:0320]に格納したので、
(min(max((8+技LV+自LV-敵LV),1),24)
という足し算を行ったことになる。
残りの
*((255-敵能力値)/2)/256)*2+攻撃力
の処理は以下のようになっている。
乗算部分は、
乗除算 - 65C816命令表 ロード・ストア命令
にある符号付16bit × 8bitの処理を使っている。
16bit × 8bitとは、$12AB × $34のように、16進法の4桁の数字と、16進法の2桁の数字の掛け算のことである。
ここまで基本的に8bit(16進法で2桁)での計算をしてきたが、どうしてここで4桁の計算になるのかは後でわかる。
;※符号付16bit × 8bit $C1/7808 SEP #$20 ;MフラグON。Aは8bit幅 $C1/780A STA $211B ;Aを被乗数下位バイトとして[$00:211B]にセット $C1/780D XBA ;Aの上位バイトと下位バイトを交換 $C1/780E STA $211B ;Aを被乗数上位バイトとして[$00:211B]に書き込み $C1/7811 LDA #$FF ;Aに$FF(=255)をロード $C1/7813 SEC ;キャリーフラグを立てる $C1/7814 SBC $26 ;A(=255) - [$00:0326](=敵能力値) $C1/7816 LSR A ;論理右シフト(A/2) $C1/7817 STA $211C ; $C1/781A STA $211C ;Aを乗数として[$00:211C]にセット $C1/781D LDA $7E900F;Aに[$7E:900F]をロード(攻撃側が味方の時の攻撃力) $C1/7821 STA $22 ;Aを[$00:0322]に書き込み $C1/7823 STZ $23 ;[$00:0323]に1バイトの00を書き込み $C1/7825 REP #$21 ;Aを16bit幅に変更、キャリーフラグクリア $C1/7827 LDA $2135 ;掛け算の結果の中央部分2バイトをAにロード ;※16bit × 8bit処理はここまで
16bit × 8bitの計算では、
STA $211Bで16bitの被乗数の下2桁をセットするSTA $211Bで16bitの被乗数の上2桁をセットするSTA $211Cで8bitの乗数をセットする[$00:2134][$00:2135][$00:2136]に掛け算の結果が出力されるという手順を踏む。
掛け算の結果がアドレス3ヶ所に分けて出力されるが、これは結果が16進数で6桁まで、つまり24ビットまで出力可能であるという意味である。
まとめてアドレス2つ分をAに取得したいのなら、事前にAを16bitモードに切り替えてから[$00:2134]をロードすれば、ひとつ上の[$00:2135]も一緒に取得できる、といった処理も可能である。
16bit × 8bitの処理だが、まず、これまでの計算結果を$C1/780Aで「16bitの被乗数の下2桁」としてセットする。
次に$C1/780DでXBAという命令が入っているが、これはAに入っている値の上2桁と下2桁を交換しろという意味である。
少し話が戻るが、
$C1/77FA AND #$00FF ;Aと$00FFで論理積(Aの上位2ビットを00に、下位2ビットはそのまま)
ここで必ず、Aの上2桁は「00」になっており、以降の計算も8ビット(下2桁)でしか行っていないので、XBAを行うと、上2桁の00が下2桁に移動する。つまり下2桁は必ず「00」になる。
この状態で、$C1/780EのSTA $211Bを実行した場合、被乗数上位バイトとして[$00:211B]に書き込まれるのは「00」。
よって、16bit × 8bitの計算をするのに、16bit部分は「00??」(??がこれまでの計算結果)という数値が入ることになる。
続いて、STA $211Cで乗数をセットする。
掛け算したい値は、「(255-敵能力値)/2」である。
$C1/7811 LDA #$FF ;Aに$FF(=255)をロード $C1/7813 SEC ;キャリーフラグを立てる $C1/7814 SBC $26 ;A(=255) - [$00:0326](=敵能力値) $C1/7816 LSR A ;論理右シフト(A/2) $C1/7817 STA $211C $C1/781A STA $211C ;Aを乗数として[$00:211C]にセット
$C1/7811で16進数のFF、つまり10進数の255をロードして、$C1/7814では255から、[$00:0326]にセット済みの敵能力値を引く。
更に$C1/7816のLSRで÷2を行って、「(255-敵能力値)/2」となる。
これを$C1/7817で乗数としてセット。
(STA $211Cが2回連続な理由はよくわからないが、多分計算時間を稼いでいるのだろう)
この乗算も少し時間がかかるので、他の処理をして時間稼ぎ。
$C1/781D LDA $7E900F;Aに[$7E:900F]をロード(攻撃側が味方の時の攻撃力) $C1/7821 STA $22 ;Aを[$00:0322]に書き込み $C1/7823 STZ $23 ;[$00:0323]に1バイトの00を書き込み $C1/7825 REP #$21 ;Aを16bit幅に変更、キャリーフラグクリア $C1/7827 LDA $2135 ;掛け算の結果の中央部分2バイトをAにロード
最後に「+攻撃力」するので、その値を[$7E:900F]からAにロードする。
(攻撃力が関わらない技なら0)
その値を[$00:0322]に書き込み保存。
そして$C1/7827で、掛け算の答えをAにロードする。
だがここでロードした値は、「掛け算の結果の中央部分2バイト」である。
先に述べた通り、符号付16bit × 8bitの計算では、答えが符号付24bitで出力される。
16進数でいえば6桁分で、順に、
[$00:2134][$00:2135][$00:2136]
の3つに分割されて出力される。
$C1/7827では、答えの中央にあたる[$00:2135]をロードしている。
例:$12AB × $34 = $03CABC
(10進法なら4779 × 52 = 248508)
この場合なら、
[$00:2134] = $03
[$00:2135] = $CA
[$00:2136] = $BC
のように2桁ずつ3分割されて答えが出力される。
さて、16進数の数値で、下2桁部分は捨てて、次の2桁だけ抜き出すことが何を意味するかは、既にプログラミング関係解説&調査トップページの「掛け算と割り算」「8bitモードと16bitモードの応用」で書いた通り、
「162 = 256で割り算をすることと同義」
なのである。
つまり、「*((255-敵能力値)/2)/256」の、最後の「/256」部分は、「掛け算の結果の中央部分2バイト」を取り出すことにより行われたということになる。
(こういった処理が、アセンブリ言語のプログラミングに慣れていないと理解に難しい部分である)
長くなったが、ここまでで、
((min(max((8+技LV+自LV-敵LV),1),24)
が計算された、ということになる。
$C1/782A ASL A ;算術左シフト(A*2) $C1/782B ADC $22 ;A + [$00:0322](攻撃力) $C1/782D STA $20 ;Aを[$00:0320]に書き込み
これが最後の処理である。
ASLで*2の処理をし、$C1/782Bで[$00:0322]に格納した攻撃力を足して、計算終了である。
この値は[$00:0320]に格納されて、後で乱数計算にも使われるが、ここでは省く。
以上で、
((min(max((8+技LV+自LV-敵LV),1),24)
が計算できた。
最後に例として、アキラがクルセイダーRSに「ローキック」を正面から当てる時、実際にAがどう変化していくかを紹介しておく。
「ローキック」は技LV2、LV差係数8、自依存は力1/2、敵依存は体、攻撃力依存あり。
アキラはレベル7、力は25、攻撃力は16。
クルセイダーRSはレベル7、体30である。
(すべて10進数表記、プログラム内では16進数にして計算する必要がある)
Aを仮にxxxxから開始としておく。
$C1/77CE LDA #$08 ;A:xxxx Aに$08(=8)をロード $C1/77D0 CLC ;A:xx08 キャリーフラグクリア $C1/77D1 ADC $7E900C;A:xx08 A + [$7E:900C](=技LV2+自LV7。$08+$02+$07=$11) $C1/77D5 BCC $02 ;A:xx11 キャリーフラグが立っていないなら$C1/77D9へ $C1/77D9 SEC ;A:xx11 キャリーフラグを立てる $C1/77DA SBC $25 ;A:xx11 A - [$00:0325](=敵LV7。$11-$07=$0A) $C1/77DC BCC $08 ;A:xx0A キャリーフラグが立っていないなら$C1/77E6へ $C1/77DE CMP #$18 ;A:xx0A Aと$18(24)を減算比較($0A-$18、負の値) $C1/77E0 BCC $06 ;A:xx0A キャリーフラグが立っていないなら$C1/77E8へ ;※ここから符号付8bit × 8bit処理: $C1/77E8 STA $4202 ;A:xx0A Aを被乗数として[$00:4202]にセット($0Aをセット) $C1/77EB LDA $7E900B;A:xx0A Aに[$7E:900B](=技データ10、$0A)をロード $C1/77EF AND #$FC ;A:xx0A Aと$FC(=252)で論理積(技データ10からLV差係数算出、$0A AND $FC = $08) $C1/77F1 STA $4203 ;A:xx08 A(=LV差係数$08)を乗数として[$00:4203]にセット $C1/77F4 REP #$21 ;A:xx08 Aを16bit幅に変更、キャリーフラグクリア $C1/77F6 LDA $7E900E;A:xx08 Aに[$7E:900E](計算済みの「自能力値*係数=trunc(25/2)=12=$0C」)をロード $C1/77FA AND #$00FF ;A:xx0C Aと$00FFで論理積(Aの上位2ビットを00に、下位2ビットはそのまま) $C1/77FD STA $20 ;A:000C Aを[$00:0320]に書き込み(「自能力値*係数=$0C」を書き込み) $C1/77FF LDA $4216 ;A:000C 掛け算の結果[$00:4216]をAにロード($0A×$08 = $50) ;※8bit × 8bit処理はここまで $C1/7802 LSR A ;A:0050 論理右シフト(A/2) $50/$02 = $28 $C1/7803 LSR A ;A:0028 論理右シフト(A/2) $28/$02 = $14 $C1/7804 LSR A ;A:0014 論理右シフト(A/2) $14/$02 = $0A $C1/7805 CLC ;A:000A キャリーフラグクリア $C1/7806 ADC $20 ;A:000A A + [$00:0320](=自能力値*係数) $0A + $0C = $16 ;※符号付16bit × 8bit $C1/7808 SEP #$20 ;A:0016 MフラグON。Aは8bit幅 $C1/780A STA $211B ;A:0016 A($16)を被乗数下位バイトとして[$00:211B]にセット $C1/780D XBA ;A:0016 Aの上位バイトと下位バイトを交換 $C1/780E STA $211B ;A:1600 A($00)を被乗数上位バイトとして[$00:211B]に書き込み $C1/7811 LDA #$FF ;A:1600 Aに$FF(=255)をロード $C1/7813 SEC ;A:16FF キャリーフラグを立てる $C1/7814 SBC $26 ;A:16FF A(=255) - [$00:0326](=敵能力値 = 30 = $1E) $FF - $1E = $E1 $C1/7816 LSR A ;A:16E1 論理右シフト(A/2) $E1/$02 = $70 $C1/7817 STA $211C ; $C1/781A STA $211C ;A:1670 A($70)を乗数として[$00:211C]にセット $C1/781D LDA $7E900F;A:1670 Aに[$7E:900F]をロード(攻撃側が味方の時の攻撃力 = 16 = $10) $C1/7821 STA $22 ;A:1610 A($10)を[$00:0322]に書き込み $C1/7823 STZ $23 ;A:1610 [$00:0323]に1バイトの00を書き込み $C1/7825 REP #$21 ;A:1610 Aを16bit幅に変更、キャリーフラグクリア $C1/7827 LDA $2135 ;A:1610 掛け算の結果の中央部分2バイトをAにロード $0016 × $70 = $0009A0, 中央部分2バイトは「$09」 ;※16bit × 8bit処理はここまで $C1/782A ASL A ;A:0009 算術左シフト(A*2) $09×$02 = $12 $C1/782B ADC $22 ;A:0012 A + [$00:0322](=攻撃力$10) $12 + $10 = $22 $C1/782D STA $20 ;A:0022 A($22)を[$00:0320]に書き込み
単発ダメージ量は$22 = 34となり、これに乱数を加えた値が実際のダメージ量となる。
次ページでは、乱数の計算について説明する。
[$7E:900C]及び、[$00:0325]について上の説明では端折った、[$7E:900C](「技LV+自LV」)及び、[$00:0325](敵レベル)について説明する。
[$7E:900C](「技LV+自LV」)メモリ[$7E:900C]には「技LV+自LV」が入っている、と述べたが、その部分について説明をする。
上では$C1/77CEからのプログラム実行結果について紹介をしたが、それ以前であらかじめ、「技LV+自LV」が計算されている。
該当部分は以下になる。
$C1/DF05 LDA $7E9021[$7E:9021] ;Aに[$7E:9021](=技データ17)をロード $C1/DF09 AND #$0F ;Aと$0Fで論理積(技データ17から技LV算出) $C1/DF0B CLC ;キャリーフラグをクリア $C1/DF0C RTS ;サブルーチン終わり ; $C1/DEEF ADC $0038,y[$00:1C??] ;A + [$00:1C??](=技使用者のレベル現在値) $C1/DEF2 BCS $01 [$DEF5] ;キャリーフラグが立っている場合[$C1/DEF5]へジャンプ $C1/DEF4 RTS ;サブルーチン終わり ; $C1/D562 STA $7E900C[$7E:900C] ;Aを[$7E:900C]に書き込み
最初の[$7E:9021]を読み込みしている部分だが、これは攻撃側の「技データ17」の読み込みである。
「技データ17」には、16進数の上1桁に「技の発動時間」、下1桁に「技LV」が格納されている。
そこで、次のAND #$0Fで、Aと$0Fで論理積を取ることにより、技LVだけ算出できる。
次に、ADC $0038,yの部分だが、「$0038,y」は「$0038にYレジスタの値を加えた位置のメモリ」を意味する。
ここでのYレジスタの下2桁には、パーティメンバーによって割り振られた番号が入っている。
00なら1人目、40なら2人目、80なら3人目、C0なら4人目である。
よって、[$00:1C??]は、[$00:1C38],[$00:1C78],[$00:1CB8],[$00:1CF8]のどれかである。
ここに収納された値は、それぞれ、「1人目のレベル現在値」「2人目のレベル現在値」「3人目のレベル現在値」「4人目のレベル現在値」である。
つまりここで、Aに入っていた「技LV」と、「技使用者のレベル現在値」が足されたことになる。
(わざわざ「現在値」としたのは、戦闘中、バフやデバフでレベルが変動するためである)
なお、「ADC」で足し算をする時は、キャリーフラグが立っているかどうかで挙動が変わり、キャリーフラグが立っていると「足し算の時に、更に繰り上がり分の+1を行う」という判断がされてしまうので、$C1/DF0B CLCでは一度キャリーフラグをクリアしている。
最終的に、$C1/D562で、計算した「技LV」+「自LV」が、[$7E:900C]に書き込まれることとなり、後でダメージ計算に使われることになる。
また、$C1/DEF2では、「技LV」+「自LV」を計算した結果キャリーフラグが立つなら[$C1/DEF5]へジャンプ、という指示がある。
2桁の16進数の足し算でキャリーフラグが立つということは、16進数3桁目にまで値があふれたという意味であり、16進数だと100以上、10進数だと256以上になった時である。
「技LV」は、このゲームの仕様上、0~15(16進数で0~F)しか指定ができない。
「自LV」は、10進数で1~99、16進数なら最大で63である。
ということは最大値同士を足した場合、10進数なら15 + 99 = 114であるから、キャリーフラグが立つことはないのでは? という気もする。
だが、「自LV」についてはステータスアップ(バフ)を使うと、戦闘中には99以上に増加することもあるらしい。
どうやらこのために、キャリーフラグが立った時用の処理を別に用意しているようだ。
(何らかの理由で数値計算がバグった時用の対策も兼ねているのかもしれない。なおここでは関係ないが、レベルはデバフにより0まで落ちることもある)
また、キャリーフラグが立った場合の[$C1/DEF5]の処理は、以下のようになっている。
$C1/DEF5 A9 FF LDA #$FF ;Aに$FF(10進数255)を書き込む $C1/DEF7 60 RTS ;サブルーチン終わり
つまり、足し算結果が16進数で100以上、10進数で256以上になった場合は、頭打ちとなって16進数でFF、10進数で255に上書きされるのである。
[$00:0325](敵レベル)攻撃対象の敵の元々のレベルは、$C1/E0D0のLDA $7E9000,xで呼び出される。
敵は複数体出現することもあり、本作では最大で15体まで敵が出現するので、xには敵に順に割り振られた番号が入る。
ここでは順に1B00, 1B10, 1B20, 1B30,~,1BF0がXに入る。
1体目なら1B00なので、[$7E:AB00]に敵のレベルが格納されており、Aにロードされた後、一度STA $10でメモリ[$00:0310]に書き込まれる。
この後に、敵の状態から「敵の現在のレベル」が計算される。
その計算の過程は省くが、バフ・デバフによる変動や、攻撃される向きによる補正が計算された結果、その値は$C1/E120のSTA $7E900A,xで、該当アドレスに書き込まれる。
敵1体目の時は[$7E:AB0A]である。
この値は$C1/D998のLDA $7E900A,xで読み込まれて、$C1/D99CのSTA $25で[$00:0325]に書き込まれ、単発ダメージ量計算の際に[$00:0325]から読み出される。
敵1体目なら該当部分は以下の通り。
$C1/E0D0 LDA $7E9000,x[$7E:AB00] $C1/E0D4 STA $10 [$00:0310]
$C1/E11E LDA $10 [$00:0310] $C1/E120 STA $7E900A,x[$7E:AB0A]
$C1/D998 LDA $7E900A,x[$7E:AB0A] $C1/D99C STA $25 [$00:0325]