Oblivionいじり アレコレ

OBLIVIONのCSやMOD,改造などの情報を載せています。他にオマケ情報もあり。

Companion Share and Recruit v3.27のゼロ除算フリーズを防ぐ

自動ルーティング機能により勝手にコンパニオンがアイテムを収集してくれるのが最高に便利な CSR v3.27 ですが、アイテムを拾うかどうかの判断として「価値 / 重量」を行っているためフリーズすることがあります。

一番下に修正パッチ置いてあります。

タブー「ゼロ除算」

私は特に詳しいわけではないですが、エラーを起こすコンピュータ上での有名な現象として「ゼロ除算」というものがあります。簡潔に言うと、0で割る計算をすると必ずエラーになるのです。逆に0が割られる数の場合は関係ないです。例えば×「5 / 0」、○「0 / 5」、×「0 / 0」。

「0で割る計算は処理しない」とすればエラーは防げますが、関数の戻り値で0が返ってくるようなこともあり、全て対策するのは難しいようです。それで、Oblivionでもフリーズするのですが、なんと「割る数」に限らず「割られる数」が0でもエラーになります!

CSRではアイテムを拾うかどうかを、変数retioを使ってアイテムの「価値 / 重量」を行って判断しています。重量0や価値0のアイテムって結構あるので、それらを拾おうとすると唐突にフリーズします。落ちるのでもCTDでもなく、処理が停止して操作不能なフリーズです。

これは自分でスクリプトの該当部を修正することで防げます。というか、実は非公式パッチが既に存在しているのですが、古い CSR をもとに作られているので v3.27 では動作しません。様々な修正が盛り込まれているようですが、CSR のバージョンアップによるスクリプトの変更で部分的に利用することも厳しそうですし、そもそもバージョンアップで修正されている可能性もあります。

なので、致命的なバグであるゼロ除算だけ修正してみたいと思います。そんなに難しくないです。

スクリプトを見てみるいじってみる

まず、オリジナルのスクリプトの該当部がこちら。

if(GetWeight item != 0)                            ;重量が0でなければ
  set ratio to GetGoldValue item / GetWeight item  ;  →変数ratio = 価値 / 重量
elseif(GetGoldValue item)                          ;価値が0でなければ
  set ratio to 99                                  ;  →変数ratio = 99
else                                               ;それ以外なら(重量 = 価値 = 0)
  set ratio to 0                                   ;  →変数ratio = 0
endif

どこが問題かわかりますか? 最初の (GetWeight item != 0) で重量が0かどうかはチェックされていますが、価値は未タッチです。そしてその後「価値 / 重量」をしてしまっているため、価値が0ならフリーズしてしまうのです。

恐らく作者は「0で割るとエラーが出る」とわかっていて重量0を除外したのだと思いますが、Oblivionでは割られる数が0でもエラーなのです。常識を知っているほどハマる罠のようなものですね。

作者の想定としては、「まず重量が0でなければratioを計算し、次に重量が0で価値が0でない(価値がある)なら必ず取得し、重量も価値も0なら取得しない」としたかったのでしょう。Oblivionの謎仕様がなければ正解でした。

そこで私がバグ修正+プチ改造したのが以下です。ちなみにこれは「Toaster Says Share v3.esm」のスクリプト「aaaGenericLootActivatorScript」の41行目です。

if(GetGoldValue item > 0)                              ;価値が0でないなら(価値が0なら何もしない)
  if (GetWeight item == 0) && (GetGoldValue item > 0)  ;  →重量が0で価値が0でないなら
    set ratio to GetGoldValue item / 0.11              ;      →変数ratio = 価値 / 0.11
  else                                                 ;  →それ以外なら(重量も価値も0でないなら)
    set ratio to GetGoldValue item / GetWeight item    ;      →変数ratio = 価値 / 重量
  endif
endif

まず価値が0なら取得せず、価値があって重量が0なら重量を0.11としてratioを計算、価値も重量も0でない時だけ「価値 / 重量」をしています。除算には0が使われないようにしており、これでフリーズは避けられます。

なぜ重量が0の時に0.11として計算しているかというと、重量が0でも価値が1のアイテムが個人的に要らないからです。多分売っても0か1円なので…。プレイヤーが選べるratio的に、「ratio=10」か「ratio=20」なら価値が1のアイテムは拾いません。売る手間がかかるだけです。この辺りは好き好きで調整の余地があります。

ちなみにアイテムの価値は整数なので、最低が0、次に1、2...となってます。重量が0で価値が1のようなアイテムはほとんどないので気にする必要もないですが。

↓は非公式パッチでの処理です。

if GetWeight item == 0.0                               ;重量が0なら
  if GetGoldValue item > 0.0                           ;  →価値が0でないなら
    set ratio to 99.0                                  ;      →変数ratio = 99
  endif
elseif getenchantment item != 0 || IsPotion item == 1  ;エンチャント品かポーションなら
  set ratio to 99.0                                    ;  →変数ratio = 99
else                                                   ;それ以外なら
  set ratio to GetGoldValue item / GetWeight item      ;  →変数ratio = 価値 / 重量
endif

独自の改造として必ず拾うアイテムとしているのが、重量が0で価値が0でないアイテム、そしてエンチャント品とポーションです。

そして見るとわかる通り、重量が0の場合は例外にしているものの、価値が0の時は考慮されておらず除算しているのでフリーズします。これでも「ゼロ除算のエラーを修正」とあったのですが、実はこのパッチは2007/12/25に作られているので、当時のCSRは全くゼロ除算が考慮されておらず、そこでこの非公式パッチでは割る数だけを例外にしたのでしょう。割られる数が0でもフリーズするOblivionがイレギュラーなのだと思います。

修正パッチの配布について

TESN の CSR ページによると改造したものの再配布は許可されていないようなので、修正したesmの配布はできません。そこで自分でesmファイルを修正していただくか、私が作った該当スクリプト1つだけを書き換えるパッチespを利用してください。

CSR v3.27の修正パッチ

修正パッチはesmファイルを書き換えるものなのでロード順はどこでも大丈夫ですが、CSRのオプションespの下辺りが自然でしょう。BOSSをお使いなら、userlistに↓を加えてください。

add: CSRv3 Looting Patch.esp
after: Toaster Says Share Faction Recruitment.esp

※(2011.09.25) espを修正しました。不具合の修正に加え、重量が0で価値が0でないアイテムは全て拾うように。

if(getgoldvalue item > 0)
  if (getweight item == 0)
    set ratio to 99
  else
    set ratio to getgoldvalue item / getweight item
  endif
else
  set ratio to 0
endif

ついでに、召喚武具を装備していると思われる人物のインベントリを開いた際に、MessageBoxを消せずに操作不能になるバグを持つスクリプトも修正。

(2011.10.09) さらにespを修正しました。スクロールを拾わないところを全て拾わせるようにしたのと、「GetGoldValue」で出したエンチャント品の価値がエンチャント無しの状態の価値だったので10倍して計算することに。

if(isplayable item==0) && (isclothing item || isarmor item) ;Playableでないなら
  set loop to loop + 1                                      ;  →次のアイテムへ
  goto 10                                                   ;  ※isclothing と isarmor は必要なようです
elseif(item== gold001 || getbookisscroll item==1)           ;ゴールドかスクロールなら
  set ratio to 99                                           ;  →必ず取得
elseif(getgoldvalue item > 0)                               ;価値が0でないなら
  if(getweight item==0)                                     ;  →重量が0なら
    set ratio to 99                                         ;      →必ず取得
  elseif(getenchantment item!=0)                            ;  →エンチャント品なら
    set ratio to (getgoldvalue item / getweight item) * 10  ;      →10倍して計算
  else                                                      ;  →その他(通常の判定)
    set ratio to getgoldvalue item / getweight item         ;      →Ratio = 価値/重量
  endif
else                                                        ;価値が0なら
	set ratio to 0                                      ;  →必ず取得しない
endif

エンチャント品を10倍としているのはちょうど良さそうだったからです。大抵のエンチャント品はかなり拾いやすくなりますが、、例えば斧などはエンチャント品で10倍したとしてもも「価値:230 / 重量:30」とかになって渋いので拾いにくいです。この辺りはどのくらいエンチャント品を重視するかによります。私は価値/重量でだけ見ていて別に優遇しないのでこういう設定になっています。

あと「printc "Ratio: %n's %n = %.2f" loot item ratio」によって、誰のアイテムの価値がいくらだったか、コンソールに出力されるようになっています。邪魔だったら消してください。

戻る