2013/03/31

Critterの一生とPapyrusのログ(後編)

お待たせしました。Critterのお話の後編です。
さて、前回の記事では、スカイリムのいたるところにいる、ありふれた存在でありながら、セラーナちゃんなみに凶悪なスクリプトを背負って生きている「Critter」のしくみについて、書かせていただきました。
今回はそのCritterが、我が家のレイクビュー邸の周りで何やら怪しげな動きをしているようなので、いったい何が起きているのか、Papyrusのログを調査してみたいと思います。
Critterの物の怪に憑りつかれた?マイホーム……レイクビュー邸。
ちなみに前回、我が家の周辺でCritter関連のエラーが多発するようになったのは、ソルスセイム島から帰宅して以来、と書いたのですが、改めて確認してみたところ、出発前のセーブデータにも、すでに同種のエラーが出ておりました。
どうやらソルスセイム島から帰宅してCTDした折に、たまたまログを見て発見しただけで、だいぶ前からうちのデータはCritter病にやられていたようです。

…というかですね……結論から先に言ってしまいますと、我が家のCritterエラーの一つはCritterのいるところではどこでも、それこそオープニングのヘルゲンすら経由していない、まっさらなニューゲームの状態でも起きる現象だったということが分かりました。
つまり、ある意味このゲームの「仕様」みたいなもので……ログに膨大なエラーが出るのが不安なのであれば、故意に何度も「あること」を行ったりしないように気をつけながら、プレイするしかない状態です。
まあ、気をつけるといっても、気をつけようがない気もするんですけど……しかし今まではそんなこと意識しなくても、滅多にCTDなんて起きなかったのですからね。普通に遊んでる分にはきっと大丈夫なんでしょう。(と思いたい)

というわけで、今回おばちゃんが取っ組み合ったCritterのエラーは、Critterが生息する場所では誰でも気づかずに起こしてるんじゃないかと思われるエラーなので、少なくとも今のところはまだCTDを起こすような深刻なタイプのエラーではありません。
ですので、そういった異常事態の起こっている特殊なケースについては、おばちゃんはよくわかりません。
そこのところをどうかご理解の上、あくまで事例の一つとしてお読み下さい。


さて、我が家の周囲で起こるCritterのエラーの詳細なんですが、こんな感じ(パターンA)とこんな感じ(パターンB)のエラーが繰り返し発生しております。
■パターンA
[00/00/2013-00:00:00AM]error: Cannot check if the reference has a named node because it has no 3D
stack:
 [ (FF000FB9)].ObjectReference.HasNode() - "<native>" Line ?
 [ (FF001A6E)].critterMoth.GoToNewPlant() - "critterMoth.psc" Line 295
 [ (FF001A6E)].critterMoth.OnUpdate() - "critterMoth.psc" Line 147
■パターンB
[00/00/2013-00:00:00AM]error: Unable to call Delete - no native object bound to the script object, or object is of incorrect type
stack:
 [None].ObjectReference.Delete() - "<native>" Line ?
 [ (FF0011A1)].critterFish.disableAndDelete() - "Critter.psc" Line 295
 [ (FF0011A1)].critterFish.OnCellDetach() - "critterFish.psc" Line 292
ちなみにパターンAのエラーでは「critterMoth」という名称が見られるものばかりなんですが、パターンBは「critterFish」だけでなく「critterMoth」や「critterdragonfly」などの名称も見られます。
……ま、こんなエラー行だけでは、蝶や魚やトンボが何かエラー出してるなあ、というくらいのことしかわかりませんね。
ですので、このエラー吐いてる奴らが、いったいどこのSpawnerから生まれ、どの処理の過程でつまずいてるのか、スクリプト内に細かくデバッグ用のコメントを挿入して、徹底的に出所を洗ってやりたいと思います。
さあて……キミたちの親の顔をじっくり見てやんよ。

↓こんな感じで各処理ごとにコメントを吐き出させて、Critterの足跡を追ってみます。
[00/00/2013-01:18:49AM] ◎[critterspawn < (000E9090)>]OnLoad開始
[00/00/2013-01:18:49AM] ◎[critterspawn < (000E9090)>]ShouldSpawn開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (000E9090)>]OnLoad…プレイヤー圏外のため待機します
[00/00/2013-01:18:50AM] ◎[critterSpawn01 < (00047992)>]OnLoad開始
[00/00/2013-01:18:50AM] ◎[critterSpawn01 < (00047992)>]ShouldSpawn開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (00047992)>]OnLoad開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (00047992)>]ShouldSpawn開始
[00/00/2013-01:18:50AM] ◎[critterSpawn01 < (00047992)>]OnLoad…プレイヤー圏外のため待機します
[00/00/2013-01:18:50AM] ◎[critterspawn < (00047992)>]OnLoad…プレイヤー圏外のため待機します
[00/00/2013-01:18:50AM] ◎[critterspawn < (03012A55)>]OnLoad開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (03012A55)>]ShouldSpawn開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (000479EF)>]OnLoad開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (000479EF)>]ShouldSpawn開始
[00/00/2013-01:18:50AM] ◎[critterSpawn01 < (000479EF)>]OnLoad開始
[00/00/2013-01:18:50AM] ◎[critterSpawn01 < (000479EF)>]ShouldSpawn開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (03012A55)>]IsActiveTime開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (000479EF)>]IsActiveTime開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (000479EF)>]OnLoad…プレイヤー圏外のため待機します
[00/00/2013-01:18:50AM] ◎[critterSpawn01 < (000479EF)>]IsActiveTime開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (03012A55)>]OnLoad…Critter生成開始!
[00/00/2013-01:18:50AM] ◎[critterspawn < (03012A55)>]SpawnInitialCritterBatch開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (03012A55)>]SpawnCritter開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (03012A55)>]SpawnCritterAtRef開始
[00/00/2013-01:18:50AM] ◎[critterSpawn01 < (000479EF)>]OnLoad…Critter生成開始!
[00/00/2013-01:18:50AM] ◎[critterSpawn01 < (000479EF)>]SpawnInitialCritterBatch開始
[00/00/2013-01:18:50AM] ◎[critterSpawn01 < (000479EF)>]SpawnCritter開始
[00/00/2013-01:18:50AM] ◎[critterSpawn01 < (000479EF)>]SpawnCritterAtRef開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (03012A55)>]Critter[Firefly < (FF001A8C)>]を生成しました
(※以下、記事中のPapyrusのログはすべて、見やすいように抜粋して編集しています)

さて、検証した状況についてなんですが、まずソルスセイム島から直接コンソールコマンドを使ってレイクビュー邸の家の中に移動し、そこからレイクビュー邸の外(エクステリア)に出て、また家の中に入って、また外に出て…という行動を何回か繰り返したシチュエーションでデータを取っています。
なぜそんな状況で検証したのかといえば、それが最初にCTDを起こした状況だったからです。……もっともCTDを起こしたのはその最初の一回のみだったんですけどね。
その後はどんなに無茶をしても全然落ちなくなってしまったので、あえてその状況を再現する必要もないかな、と思ったのですが、まあ、せっかくなのでCTD当時と似たような状況で検証してみました。
ちなみにレイクビュー邸の家の中から外に出ると、おばちゃんの環境では必ずロード画面が発生します。(外から家の中に入る際は発生しません)
ですから家の中から外に出ると、外の世界にあるものは「OnLoad」されるわけですね。
また、家の外に出ると、初回は必ず山賊さんたちがお帰りなさいアタックをしにきます。
養子たちもこづかいをせびりにきたり、いらないものを押し付けてきたり、テスト中のパパを何かと妨害しにやってきます。
二人とも立派に空気の読めないノルドとして、日々成長しているようでとても嬉しいです。

さて、そんなテストプレイ時のログをチェックしてみたわけなんですが、まずプレイヤーがレイクビュー邸の外に出た時点で、最大25個のCritterのSpawnerが一斉に「OnLoad」イベントを開始していることを発見しました。
えっ、レイクビュー邸の周囲って、そんなにCritterっていたっけ!?とビックリしましたね。
レイクビュー邸の付近にいる生き物で、おばちゃんがとっさに思い出せるのは、パインウォッチ方面のカニさんのご夫婦が棲んでる水たまりのトンボと小魚くらいです。
あとは、家畜の囲いの側にも蝶々が飛んでたっけなあ……
そういえばレイクビュー邸の上空には鷹が二羽飛んでますが、あれは別系統のスクリプトで動いている生き物なので、今回の調査からは除外しております。
(※鷹はFxfakeCritterScriptというスクリプトがついた生き物で、虫や魚などのCritterクラスを継承している連中とはちょっと種類が違うんです)
レイクビュー邸付近は風光明媚、スカイリムの中ではかなり緑豊かな自然に恵まれた地方ですが、さすがに25箇所もの生き物たちの発生ポイントには心当たりがありません。
そんなわけで、どこにそんな大量のSpawnerが配置されているのか、まずログに出ているRefIDを頼りに、CKを使って付近をしらみつぶしに捜索してみることにしました。
すると、どうやら(-5,-15)から(-1,-19)のセルの範囲にあるSpawnerが一斉にOnLoadされたらしい、ということが分かりました。
緑のがSpawnerのOnLoadイベントの反応があった区域。
「BYOHHouse1EXterior」「BYOHHouse1EXterior02」がレイクビュー邸の外回りです。
おばちゃんはてっきり「OnLoad」されるセルの単位って、CKのCell Viewウィンドウで編集する一区画分なのかと思ってたんですが、「Interior」ではないセルの場合だと、連結してる区画を複数まとめたブロック単位になるんですね。
ちなみに「Onload」が発生したSpawnerの分布図から見るに、プレイヤーのいるマスを中心に5×5の区画がまとめて「OnLoad」イベントの発動対象となっているようです。
(上図だとグリーンの背景で塗りつぶした範囲です)
レイクビュー邸の場合、邸宅は2つの区画にまたがっていますので、家の外周をぐるっと回ると縦方向には計6マス分の区画が反応することになります。

これはもしかして、Skyrim.iniの設定の「uGridsToLoad」という奴の大きさかしら……
確か遠くの方まで遠景を描写するためにSkyrim.iniを「uGridsToLoad=7」にするという技があったかと思うんですけど、通常5のところを7に増やしたら、5×5=25が一気に7×7=49となるわけで……iniを弄ってる人は、ほぼ二倍の区画分のSpawnerが一斉にOnLoadされたりするんでしょうかねえ、
まあ、Onloadされたとしても、プレイヤーが近くに行くまではCritter自体は生成されないのでそんなに負担にはならないでしょうが……しかしデフォルトの5×5でも、おばちゃんの想像をはるかに越えたスケールです。
だって、レイクビュー邸の西棟2Fのバルコニーからちょっと顔を出しただけで、リバーウッドの大守護石近くの狩人さんキャンプ付近のサケのSpawnerまで反応してるんですよ。
そんな広範囲に存在するSpawnerが全員、プレイヤーが圏内にくるのを待ちながら黙ってループをえんえん繰り返しているのかと思うと……なんというか、ガルマルさんに熱い期待のまなざしで朝から晩までじーっと見られてるみたいで、非常に落ち着かないです。


さてそんなわけで、レイクビュー邸の外に出た時点で、多数のSpawnerが「OnLoad」イベントを開始してるわけなんですが、レイクビュー邸の建物の近くに居る限りではプレイヤーの周囲4000以内に存在していて、Critter生成のGOサインが出るSpawnerは、たった二つしかなかったりします。
一つは西棟の周辺にある皮なめしの棚の近くに配置されているSpawner(000479EF)で、こちらからは昼間は蝶類、夜間はホタルやルナ・モスといった昆虫が生成されます。
もう一つは養蜂場(03012A55)で、こちらからはハチが生成されます。
レイクビュー邸のエクステリアに存在するSpawner達。

それではここで、皮なめしの棚近くの蝶のSpawner(000479EF)に焦点をあてて、ゲーム上ではどのような感じで処理が流れているのか、ログを見てみたいと思います。
「000479EF」が含まれる行をピックアップしてみると、このSpawnerが「OnLoad」されてから順調に処理を行って、無事にCritter生成まで辿りついていることが確認できます。

皮なめしの棚近くのSpawner(000479EF)関連のログの抜粋
01:18:50AM ◎[critterspawn < (000479EF)>]OnLoad開始
01:18:50AM ◎[critterspawn < (000479EF)>]ShouldSpawn開始
01:18:50AM ◎[critterSpawn01 < (000479EF)>]OnLoad開始
01:18:50AM ◎[critterSpawn01 < (000479EF)>]ShouldSpawn開始
01:18:50AM ◎[critterspawn < (000479EF)>]IsActiveTime開始
01:18:50AM ◎[critterspawn < (000479EF)>]OnLoad…条件不適合のため生成不可、待機します...
01:18:50AM ◎[critterSpawn01 < (000479EF)>]IsActiveTime開始
01:18:50AM ◎[critterSpawn01 < (000479EF)>]OnLoad中…Critter生成開始
01:18:50AM ◎[critterSpawn01 < (000479EF)>]SpawnInitialCritterBatch開始
01:18:50AM ◎[critterSpawn01 < (000479EF)>]SpawnCritter開始
01:18:50AM ◎[critterSpawn01 < (000479EF)>]SpawnCritterAtRef開始
01:18:50AM ◎[critterSpawn01 < (000479EF)>]Critter[critterMoth < (FF001AB1)>]を生成しました
01:18:50AM ◎[critterSpawn01 < (000479EF)>]SpawnCritter開始
01:18:50AM ◎[critterSpawn01 < (000479EF)>]SpawnCritterAtRef開始
01:18:50AM ◎[critterSpawn01 < (000479EF)>]Critter[critterMoth < (FF001AE3)>]を生成しました
01:18:50AM ◎[critterSpawn01 < (000479EF)>]SpawnCritter開始
01:18:50AM ◎[critterSpawn01 < (000479EF)>]SpawnCritterAtRef開始
01:18:50AM ◎[critterSpawn01 < (000479EF)>]Critter[critterMoth < (FF000FFF)>]を生成しました
ちなみに「critterspawn」や「critterSpawn01」といったRefIDの前にあるワードは、そのオブジェクトのRefに付いているスクリプトの名称を示しています。
実は「000479EF」というSpawnerには、「critterspawn」というスクリプトと、「critterSpawn01」というスクリプトの二つのスクリプトが付いていて、同時に稼動しているんですね。
これは1つのSpawnerからタイプの違うCritterをセットで生成する場合(たとえばトンボ&魚のコンビとか)に、それぞれの特性によってプロパティの設定を変える必要があるため、同じスクリプトを2つ付けてるんです。
(※「critterSpawn01」という、お尻に数字のついているタイプはダミー用のスクリプトで、「critterspawn」のスクリプトとまったく同じ働きをしています)

この皮なめし棚近くの「000479EF」のSpawnerの場合、「critterspawn」というスクリプトの方には、ホタルやルナ・モスといった夜間に活動する虫たちの設定がセットされてまして、「critterSpawn01」の方には日中用の蝶たちの設定がセットされています。
ですから上記のログでは、夜間用のCritterを扱う[critterspawn < (000479EF)>]の方からは、活動時間外で条件不適合ということになり何も生成されていないのです。
(※テストプレイは昼間に行なっています)
critterSpawn01」スクリプトの方から生成されたCritterは、それぞれ「FF001AB1」「FF001AE3」「FF000FFF」というRefIDの3匹で、「CritterMoth」というスクリプトが付いていることから察しがつくように、青い蝶か黄色い蝶のどちらかとして生を受けています。
また、CKでSpawnerの「critterSpawn01」スクリプトのプロパティを見てみると、最大何匹まで生成するかという「iMaxCritterCount」のプロパティの値が「3」になっています。
なので今回は、前回生成されたCritterの残りはいない、という判定のもと、指定通りの3匹がすべて生成されたことがわかります。


さて、この皮なめしの棚近くのSpawnerから生まれた3匹の蝶Critterたちですが、実はこいつらが「パターンA」のタイプのエラーを出していたりします。
3匹全員のログを追うのは大変なので、ここでは最初に生まれた長男(FF001AB1)に関連する行だけを抜粋して拾ってみたいと思います。
蝶Critter(FF001AB1)関連のログの抜粋
01:18:50AM ◎[critterSpawn01 < (000479EF)>]Critter[critterMoth < (FF001AB1)>]を生成しました
01:18:50AM ◎[critterMoth < (FF001AB1)>]OnInit開始
01:18:50AM ◎[critterMoth < (FF001AB1)>]CheckStateAndStart開始
01:18:50AM ◎[critterMoth < (FF001AB1)>]SetInitialSpawnerProperties開始
01:18:50AM ◎[critterMoth < (FF001AB1)>]CheckStateAndStart開始
01:18:50AM ◎[critterMoth < (FF001AB1)>]SetSpawnerProperties開始
01:18:50AM ◎[critterMoth < (FF001AB1)>]【KickOffOnStart】OnUpdate開始
01:18:50AM ○[critterMoth < (FF001AB1)>]【KickOffOnStart】OnStart開始
01:18:50AM ○[critterMoth < (FF001AB1)>]WarpToNewPlant開始
01:18:50AM ○[critterMoth < (FF001AB1)>]PickNextPlant開始
01:18:50AM ○[critterMoth < (FF001AB1)>]PickNextPlant…対象は[ObjectReference < (03006912)>]
01:18:50AM error: Cannot check if the reference has a named node because it has no 3D
stack:
 [ (03006912)].ObjectReference.HasNode() - "<native>" Line ?
 [ (FF001AB1)].critterMoth.WarpToNewPlant() - "critterMoth.psc" Line 334
 [ (FF001AB1)].critterMoth.OnStart() - "critterMoth.psc" Line 74
 [ (FF001AB1)].critterMoth.OnUpdate() - "Critter.psc" Line 276

01:18:50AM ○[critterMoth < (FF001AB1)>]WarpToNewPlant…[ObjectReference < (03006912)>]にLandingSmall03が見つかりません
01:18:50AM error: Cannot check if the reference has a named node because it has no 3D
stack:
 [ (03006912)].ObjectReference.HasNode() - "<native>" Line ?
 [ (FF001AB1)].critterMoth.WarpToNewPlant() - "critterMoth.psc" Line 340
 [ (FF001AB1)].critterMoth.OnStart() - "critterMoth.psc" Line 74
 [ (FF001AB1)].critterMoth.OnUpdate() - "Critter.psc" Line 276

01:18:50AM ○[critterMoth < (FF001AB1)>]WarpToNewPlant…[ObjectReference < (03006912)>]にLandingSmall01が見つかりません
01:18:50AM ◎[critterMoth < (FF001AB1)>]PlaceLandingMarker開始
01:18:51AM ○[critterMoth < (FF001AB1)>]【AtPlant】OnUpdate開始
字が細かいので非常に見にくいですが……とりあえずエラーが起こるまでの過程をざっと見てみましょう。
蝶Critterが生成されると、まずは「OnInit」というイベントが起こって、親Spawnerから貰った諸々のプロパティを装着する処理に入ります。
その準備が無事に完了しますと、今度は【KickOffOnStart】というStateに移行します。
これは初回のみに移行するモードで、その名の通り、蝶Critterが親元からキックオフするスタート処理ためのモードです。
この【KickOffOnStart】のStateで蝶Critterは最初の着地点(出現地点)にワープしてから、植物と植物の間を飛び回る【AtPlant】の通常モードに移行することになります。

ログを見てみますと、【KickOffOnStart】のStateで「OnUpdate」のイベント処理が始まった後、そこで「OnStart」というイベント処理が行なわれ、続いて「WarpToNewPlant」という処理が始まったことがわかります。
…と、こう書いても何がなにやらわかりませんね。
おばちゃんも書きながら、頭がぐるぐるしてきましたよ。
しかし、ここでエラーの「stack:」の部分を改めて見てみて下さい。
エラーが起こる前までの過程をふまえながら見てみると……おや?と思いませんか。
01:18:50AM error: Cannot check if the reference has a named node because it has no 3D
stack:
 [ (03006912)].ObjectReference.HasNode() - "<native>" Line ?
 [ (FF001AB1)].critterMoth.WarpToNewPlant() - "critterMoth.psc" Line 334
 [ (FF001AB1)].critterMoth.OnStart() - "critterMoth.psc" Line 74
 [ (FF001AB1)].critterMoth.OnUpdate() - "Critter.psc" Line 276

どうやら「stack:」以下のログはエラーが起こるまでの処理の道筋をさかのぼって示してくれているようです。
つまり、蝶Critter(FF001AB1)の「OnUpdate」処理が始まって、その処理中に「OnStart」という処理が起こり、さらにその処理中に「WarpToNewPlant」という処理が行なわれて、さらにその処理の最中に「03006912」というObjectReferenceの「HasNode」というネイティブな処理を行なおうとして……そこでエラーとなったのです。
何が原因でエラーが起きたのか、ということまではわかりませんが、少なくとも「Cannot check if the reference has a named node because it has no 3D(そのリファレンスには3Dが無いのでノードがあるかどうかのチェックはできません)」というエラーが出るに至るまでの経緯だけはわかるようになっているんですね。

ちなみにエラーになってしまった「HasNode」という処理についてですが、これは「stack:」の直前の行が示す通り、「WarpToNewPlant」という処理中に行なわれているものです。
「WarpToNewPlant」という処理は、蝶Critterが最初の着地点(出現地点)にワープする処理でして、まず最初に着地点となる植物をピックアップする作業に入ります。
それが上記のログの「WarpToNewPlant開始」というコメントの直後にある「PickNextPlant」という処理です。
ログを見ると、どうやら蝶Critterはその「PickNextPlant」の処理の結果、無事に (03006912)というRefIDの対象を見つけてきたことがわかります。
先頭の2ケタが「03」ということから察するに、Hearthfire関連で配置された付近の植物のどれかでしょうかね。
蝶Critterは、この対象の植物に対し、今度は「LandingSmall」という接頭語のつくノードが、3Dデータ上に存在していないか、チェックを始めます。
前回の蝶Critterの説明では、あまりにも話がややこしくなるので割愛したのですが、実は蝶Critterが着地するポイントは、対象に選ばれた植物のデータに、着地用の特別なノードがないかどうか探して、もしそのノードがあるようでしたら、その位置に優先的に着地するようになっているのです。
NifSkopeでランディングマーカーのmeshデータを覗いてみると、
「LandingSmall」や「ApproachSmall」といった接頭辞のつくノード名が確認できます。
しかもこの着地用ノードは物によっては「LandingSmall01」から「LandingSmall03」まであるようでして、蝶の着地するポイントが均等にばらけるように、最初は01番から03番までのランダムの抽選結果から選ばれたノードを、そのノードが存在しなかった場合は次は01番を探す、といった具合に二段階に分けてチェックをかけています。
つまり、「WarpToNewPlant」の最中に「HasNode」が実行される機会は二回あるわけです。
ログを見るとわかるんですが、この蝶Critterは続けざまに二回、「HasNode」という処理を行なって、それでコケてエラー吐いてます。
エラーの後のコメントを見ると、最初にコケた「HasNode」の処理では、「LandingSmall03」のノードをチェックしようとしており、次の「HasNode」では、「LandingSmall01」のノードをチェックしようとしていたことがわかります。

ちなみに長男(FF001AB1)の蝶Critterだけでなく、次男(FF001AE3)も三男(FF000FFF)もレイクビュー邸の外周りで生成された蝶Critterはことごとく、この「HasNode」の処理で失敗してコケてます。
いったい何が原因で失敗するのかはわかりませんが……問題はこの「HasNode」という処理が、「WarpToNewPlant」の処理中だけでなく、通常モードの【AtPlant】の中でもひんぱんに呼び出されている処理だということです。
なにしろ蝶が移動しようとするたびに、着地点となる植物をピックアップして、その植物のノードをチェックするという作業をするわけですから、蝶が飛び回っている間は当然、「HasNode」しまくりなわけですよ。
3匹の蝶がそうやって対象となる植物にしょっちゅう「HasNode」しようとするので、それでパターンAタイプのエラーがばんばん多発してたというわけです。
今まではエラーの部分だけしか見えていなかったので、そこで処理が詰まってリピートしてるような印象を受けてたんですが、スクリプトの処理自体は、エラーを吐きながらもそのまま流れていってたんですね。

ところで今回のこの「HasNode」のエラーに関しては、幸いなことに、たとえエラーが起きて「HasNode」の結果が得られなくても、スクリプトの処理自体が次の過程に進んでくれているのであれば、全然問題なかったりします。
(問題ない、と言っても、あくまでおばちゃんの私見ですが)
…というのも、もともと蝶CritterのCritterMothスクリプトでは、「HasNode」で着地用のノードを取得できなかった場合における処理というものがあらかじめ用意されているからです。
着地用のノードが見つからなかった場合は、対象植物の周囲のランダムなポイントに新規で着地ポイントを作成して、そちらに飛び移るようになっています。
ですからまあ、エラーでコケても蝶は何事もなく辺りを飛び回っているものと思われます。
もちろん、「HasNode」という処理でことごとくコケてしまう根源的な原因については憂慮しなくてはいけないんですけどね。


さてお次は、パターンBのエラーの方です。
こちらのエラーはパターンAのエラーと比べると、かなり状況が深刻といいますか…そもそも最初はいったい何が起こっているのか、おばちゃんにはわけがわかりませんでした。
なぜならエラーを起こしているCritterが、いつどこで生成されたものなのか、出生の記録が一切不明だったからです。
前述した通り、レイクビュー邸の家の外周から離れずにいれば、たとえどんなに多数のSpawnerが「OnLoad」されていたとしても、皮なめし棚近くのSpawnerと養蜂場以外は、決してCritterを生成することはありません。
ですから昼間の時間帯であれば、蝶とハチ以外は存在しない筈ですし、そもそもログには他のCritterが生成されたという形跡は一切残っていないのです。
それなのに、いきなりエラーの中に姿を見せるトンボや魚達……何なんでしょう、これ。

パターンBのエラーはすべて、生成された記録がログに残っていない
突然現れた謎のCritter達が引き起こしています。
09:15:36PM error: Unable to call Delete - no native object bound to the script object, or object is of incorrect type
stack:
 [None].ObjectReference.Delete() - "<native>" Line ?
 [ (FF0017BE)].critterdragonfly.disableAndDelete() - "Critter.psc" Line 315
 [ (FF0017BE)].critterdragonfly.OnCellDetach() - "critterDragonFly.psc" Line 224
09:15:36PM error: Unable to call Delete - no native object bound to the script object, or object is of incorrect type
stack:
 [None].ObjectReference.Delete() - "<native>" Line ?
 [ (FF0017C0)].critterFish.disableAndDelete() - "Critter.psc" Line 311
 [ (FF0017C0)].critterFish.OnCellDetach() - "critterFish.psc" Line 292
この、いつ生成されたのかわからない謎のCritter達は、どうやらレイクビュー邸の外から家の中に入った時……プレイヤーがセル移動して「OnCellDetach」というイベントが発生するタイミングで怪しげな活動を始めるようです。
それまではまったく動かず、ログ上では完全に沈黙していたのに、セル移動した瞬間、わらわらとボウフラのように沸いてきて活動を始めるのです。
今までいったい、どこで何してたんですかね、こいつら。

「OnCellDetach」のタイミングで突然ログに登場する謎のCritter達……計11匹!  
[00/00/2013 - 09:15:36PM] #[critterdragonfly < (FF0017BE)>]onCellDetach開始
[00/00/2013 - 09:15:36PM] #[critterdragonfly < (FF0017B0)>]onCellDetach開始
[00/00/2013 - 09:15:36PM] ○[critterMoth < (FF0016EC)>]onCellDetach開始
[00/00/2013 - 09:15:36PM] ○[critterMoth < (FF0017C1)>]onCellDetach開始
[00/00/2013 - 09:15:36PM] ○[critterMoth < (FF0017BA)>]onCellDetach開始
[00/00/2013 - 09:15:36PM] ○[critterMoth < (FF001733)>]onCellDetach開始
[00/00/2013 - 09:15:36PM] ○[critterMoth < (FF001710)>]onCellDetach開始
[00/00/2013 - 09:15:36PM] ○[critterMoth < (FF0017B7)>]onCellDetach開始
[00/00/2013 - 09:15:36PM] ●[critterFish < (FF0017AF)>]DisableAndDelete開始
[00/00/2013 - 09:15:36PM] ●[critterFish < (FF0011A1)>]DisableAndDelete開始
[00/00/2013 - 09:15:36PM] ●[critterFish < (FF0017C0)>]DisableAndDelete開始
この11匹のうち、「critterMoth」のCritterは例の皮なめし棚のSpawnerから産まれた蝶Critterなのでは?と思ったんですが、RefIDを確認してみると、直前の「OnLoad」で生成された3匹とはIDがまったく異なります。(しかも6匹もいるし!)
あるいは、この蝶Critter達は以前レイクビュー邸に訪れた時に生成されたもので、たまたま何らかの理由でレイクビュー邸の周囲に残されていたのかしら…とか思ったのですが、よくよく考えてみると、前回に生成されたCritterが今の今まで生き残っている、ということは通常ではありえないのですよね。
しかも6匹(スポーン2回分?)も残っているなんて……明らかにおかしいです。

前編の記事でもご紹介した通り、Critterというものはプレイヤーがそのセルから出ていく際に、必ず消えゆくさだめにあります。
(※後で気づいたのですが、実はハチだけはプレイヤーがセルを離れる時も消えたりしないようです。ですからハチは以前に生成されたものが残っている…という場合があります)
「プレイヤーがセルを出て行った」という状況によって「onCellDetach」というイベントが始まり、そのイベントをキャッチすると、Critterは問答無用で自分自身を削除する「DisableAndDelete」という処理を始めるからです。
プレイヤーとの距離が4000以上離れてしまった場合も、即座に削除処理に入るようになってますが、プレイヤーが自分の居るセルからいなくなった……という状況は、さらに決定的なもので、この削除命令は絶対にさからえません。
それなのに、いったいどうしてレイクビュー邸の外回りのセルには、いつ生成されたのかわからない謎のCritter達が11匹もいたのでしょう。 しかもこいつらは、レイクビュー邸の外と家の中を何度往復しても消えないのです。
そして家の出入りをするたびに、パターンBのエラーを鬼のように吐きます。
普通だったらセル移動した瞬間に消えるはずなのに……なぜいつまでも生き残っていていられるのか。
奴らが吐いているエラーは、いったい何が原因で起きているのでしょう?


ログを改めて見てみますと、この正体不明のCritter達はイレギュラーな存在のくせに、スクリプトで定められた通り「onCellDetach」イベントのタイミングで、律儀にも「DisableAndDelete」の削除処理を開始しています。
この処理では、自分自身をまず「disable」して姿を見えなくし、それから自分が生まれた時に一緒に用意した、着地用のマーカーを削除します。
エラーを見ると、このCritter達は全員、その段階でつまづいてしまっているようです。
09:15:36PM error: Unable to call Delete - no native object bound to the script object, or object is of incorrect type
stack:
 [None].ObjectReference.Delete() - "<native>" Line ?
 [ (FF0017BE)].critterdragonfly.disableAndDelete() - "Critter.psc" Line 315
 [ (FF0017BE)].critterdragonfly.OnCellDetach() - "critterDragonFly.psc" Line 224
stackを見ると、「OnCellDetach」イベントが開始されて、「disableAndDelete」という処理が始まり、そして「何か」をDelete(削除)しようとして、エラーに「削除できません」と言われてしまっていますね。
その「何か」というのは、stack行には[None]となっていますが……おそらくは着地用のマーカーのオブジェクトではないかと思われます。
着地用のマーカーは2種類(landingMarkerとdummyMarker)あるんですが、エラーを見るとやはり、各Critterに計二回ずつ、「delete」失敗のエラーが出ているんですね。
つまり、11匹全員分だと計22回のエラーがダダーっとログに吐き出されているわけです。
それが家の出入りのたびに発生するんだから……そりゃ膨大なログになるわ。

不思議なことに、よく見てみると今回はパターンAのエラーの時のように「HasNode」という処理が行われるたびにかならずコケる、というような現象ではなかったりします。
「delete」という処理は、着地用のマーカー2種を消し去った後にも、もう一度行われる機会があるんですが、その最後の一回についてはエラーは特に出ていないのです。
その最後の削除の機会とは……Critter自身の削除の機会です。
Critterは着地用のマーカーを始末した後、お母さんに「さようなら」と挨拶してから、自分自身を削除して死にます。
でもその最後の自分の「delete」については、特に何もエラーは出ていません。
ですから、「delete」という処理自体ができなくなってしまって、それでいつまでも生き残っている…というわけでは無さそうです。
ログを見る限りでは、謎のCritter11匹は全員、自分自身のdeleteで完全に死亡していますし、自分が死んだということを親Spawnerにきっちり報告にいってます。

そういえば……この段階で、この出所不明の11匹の親が誰だかわかるんですよね。
デバッグ用のコメントに親の名前を出すように細工しましたので……これでこいつらがいつ生成されたのか、という問題はともかく、どの親Spawnrから生成されたCritterなのか、ということだけは判明します。
[00:00:36PM] #[critterdragonfly < (FF0017BE)>]死亡。親は[critterspawn < (000EA3E2)>]
[00:00:36PM] #[critterdragonfly < (FF0017B0)>]死亡。親は[critterspawn < (000EA3E2)>]
[00:00:36PM] ○[critterMoth < (FF0016EC)>]死亡。親は[critterspawn01 < (000479EF)>]
[00:00:36PM] ○[critterMoth < (FF0017C1)>]死亡。親は[critterspawn01 < (000479EF)>]
[00:00:36PM] ○[critterMoth < (FF0017BA)>]死亡。親は[critterspawn01 < (000479EF)>]
[00:00:36PM] ○[critterMoth < (FF001733)>]死亡。親は[critterspawn01 < (000479EF)>]
[00:00:36PM] ○[critterMoth < (FF001710)>]死亡。親は[critterspawn01 < (000479EF)>]
[00:00:36PM] ○[critterMoth < (FF0017B7)>]死亡。親は[critterspawn01 < (000479EF)>]
[00:00:36PM] ●[critterFish < (FF0017AF)>]死亡。親は[critterspawn01 < (000EA3E2)>]
[00:00:36PM] ●[critterFish < (FF0011A1)>]死亡。親は[critterspawn01 < (000EA3E2)>]
[00:00:36PM] ●[critterFish < (FF0017C0)>]死亡。親は[critterspawn01 < (000EA3E2)>]
RefIDを見てみると、どうやら6匹の蝶(critterMoth)たちについては、親Spawnerはやはり先程の皮なめしの棚近くのSpawner(000479EF)だったようです。
まあ、とんでもなく遠くのセルのSpawnerの子とかじゃなくて良かったですが……それにしてもいったいいつ、6匹の蝶が生成されていたというのでしょう。
そもそも、この6匹が皮なめしの棚近くのSpawnerから生成されたCritterとして生き残っていたのだとしたら、最初の「OnLoad」の時に皮なめしの棚のSpawnerから新たな蝶が3匹生成されるのはおかしいです。
だって必ず前回の生き残りはいないかどうか、という判定をした後で、Critterを生成するかどうか決めてるんですから。
つまり、こいつらは親に「死んだ」と見せかけて、こっそり生きているということになります。
いったい何なの。親に隠れて何をしようっての?

一方、トンボ(critterdragonfly)や魚(critterFish)の親は、パインウォッチ近くにある水溜りに配置されたSpawner(000EA3E2)だったようです。
こちらもレイクビュー邸の近くといえば近くですが……いつ生成されたのか謎なのは蝶Critterの場合と同様です。
馬車の停留場の少し先にある水たまり。ここにはマッドクラブの夫婦が棲んでます。
ところで……この謎のCritter11匹なんですが、ゲーム上でコンソールを使って彼らのRefIDを「Prid」してみると、ちゃんと選択できるんですよね。
どうやら奴らは実体のない幽霊というわけではなく、ObjectRefrenceとして、このセル上にちゃんと「実在」している存在のようです。
ですので、いったいこの謎のCritter達がどこにいやがるのか、コンソールで「moveto」して居場所を突き止めてみることにしました。
いつ生成されたわからない謎の魚Critter(CritterFish)たち
謎の魚のCritter3匹(FF0017AF)((FF0011A1)(FF0017C0)は、親Spawnerの配置された水たまりの中にいました。
ただ、最初は姿が見えない状態になっていまして……コンソールで「enable」と打ったら、姿が見えるようになりました。
ちなみにこの3匹以外に画面に写っている魚は、プレイヤーが至近距離に近づいたために水たまりのSpawnerから新たに生成された魚Critterです。
この新規で生成された魚Critterの方は、ピチピチと跳ねる音がしそうなくらいちょこまかと泳ぎ回っているのですが、コンソールで出現した謎の魚Critter3匹は、まったく動いておりません。(静止画だと全然わかりませんが)
まあ、元気よく泳いでいるようなら、ログにその足跡が出ていなければおかしいわけで……動いていないのが当然といえば当然なんですけどね。
トンボのCritter(critterdragonfly)の方は、こんなところにいました。
まさかこんな上空にいるとは思わなかったものだから、「moveto」でいきなり飛んだら落下死してしまいました。(おのれ…)
Spawnerのある水たまりからこんなに離れたところにいるなんて……いったいトンボCritterに何が起きたというのでしょう。
遊んでいるのではありませんよ。落下中のポーズのまま「moveto」しただけです。
このはぐれトンボのCritterも、最初は外観が見えない状態なんですが、「enable」をすることで姿を見ることができるようになります。
しかもこんなに異常事態な状況であっても、きちんとオブジェクトとしての当たり判定は持っているらしく、一瞬ではありますが、落下中のウルフリック首長を支えるほどの怪力っぷりを発揮しておりました。
おそらく、こんな変なところにさえいなければ、普通にアクティベートして採取できるのではないかと思われます。
ちなみに魚Critterの方は、アクティベートしてちゃんと素材を取ることができました。
死んだみたいに動かない状態でしたが、どうやらスクリプトはまだ生きていて、アクティベート時や攻撃時の処理といったものは正常に稼動している様子です。

さて、このように「いったい何故そこに?」という場所に隠れていた謎のCritterたちなんですが、おばちゃんはそれでもまだ、最初はそこまで大変な問題が起きていると思わずに、事態を楽観視しておりました。
この謎のCritterたちがどうしてこんな異常な状態になってしまったのかはわかりませんが……「まあ、スカイリムだったら、そんなこともあるよね」と思ったりして(笑)
このゲームを長時間やっていたら、けったいなことの十や二十は平気で起きますからね。
こんなことでいちいち目くじら立てて騒いでいたら、白髪が増えてしょうがないです。
とりあえずこの目障りな11匹については、コンソールコマンドで「MarkForDelete」して強制的に削除しちゃえばさすがに復活はしないだろうし、エラーを吐いてるCritterさえ始末してしまえば問題なかろう、と思ったのでした。
だから11匹まとめて、コンソールでえいっと削除することにしたわけですよ。

ところで、「MarkForDelete」というオブジェクトを削除するコンソールコマンドですが……あれってすぐには消えてくれない仕様ですよね……
はっきりとは確認していませんが、「MarkForDelete」とコマンドを打って、オブジェクトに削除の印(RefIdの横に[D]と出ます)をつけても、何度かロード画面を挟まないと、対象はセルデータ上から完全には消えてくれなかったりします。
外観自体はロードを挟むとすぐに見えなくなるんですが、「prid」してみると、しばらくはまだRefIDが残っているんですよね。
それで、おばちゃんは完全に謎の11匹のRefIDを消すために、レイクビュー邸の外と家の中の出入りを繰り返して、ついでに家の中でセーブ&ロードして、これでもかとばかりにしつこくロード画面を挟んで……「prid」しても「そんなIDのオブジェクトは存在しません!」と言われるまで、頑張ったのです。
はあ~これでやっと駆除できたわ~、これでもう、パターンBの方のエラーは出なくなるに違いない、と思ってゲームを中断してログを見てみたら……いったいどうなっていたと思います?

確かに、コンソールで駆除した謎のCritter11匹については……「MarkForDelete」をしたことで始末できたらしく、ログには現れなくなっておりました。
しかし、今度は別のRefIDのCritterが、またパターンBタイプのエラーを吐いてるんです。
しかも家の出入りを繰り返すたびに、3匹ずつ増えていってる……!!!!
ログ上に存在するCritterのRefID一覧
※回数はレイクビュー邸の中に入ったカウントです
  種類 1回目2回目3回目
正規で生成された
Critter
FF001AFDFF001ADFFF001AE9
FF001B01FF001AEBFF001AF0
FF001B0AFF001B00FF001B07
ハチ FF001B00FF001AE0FF001AEF
FF001B03FF00AEFFF001AE3
FF001B0BFF00B03FF001B08
FF001B11FF001B0BFF001AE3
パターンBの
エラーを起こす
Critter
トンボ FF0017BE××
FF0017B0××
FF0017AF××
FF0011A1××
FF0017C0××
FF0016EC××
FF0017C1××
FF0017BA××
FF001733××
FF001710××
FF0017B7××
 FF001AFDFF001AFD
 FF001B01FF001B01
 FF001B0AFF001B0A
  FF001ADF
  FF001AEB
  FF001B00
こんな風に表にまとめて整理してみると、あんまりたいしたことないように見えますが、エラーを吐く見知らぬRefIDをログの中に新たに発見した時は、一瞬めまいがして気が遠くなりかけました。
この状況、いったいどういうことかお分かりになりますか?
これ……よく見ると、二回目、三回目のターンで新たにエラーを出すようになった蝶Critterというのは、その直前の回の「OnLoad」の時に発生した蝶Critterたちなんです。
たとえば最初の「OnLoad」で蝶Critterは正規の生成ルートから(FF001AFD)(FF001B01)(FF001B0A)の3匹が産まれてるんですが、一回目の「OnCellDetach」のタイミングでエラーを出さずに問題なく消えているにもかかわらず、次に外に出て、再び家の中に入る「OnCellDetach」の際に、こんどはエラーを出すCritterとして復活してるんです。
(ああ、こんな説明でわかるでしょうか……うまく文章にできない……)
こいつらは3回目の出入りの時の「OnCellDetach」の時にも、相変わらずエラーを出し続けてます。つまり最初の謎Critter達と同じように、家を出入りするたびにエラーを吐きまくる個体に変化してしまったのです。
せっかく始めの11匹を退治したのに……どうして前の回に生成されて、しっかり消滅したはずの蝶Critterがまた復活して、最初の11匹みたいな物の怪になってしまうんでしょう。
ほんとに……何なんでしょうか、これは。
養子がパパに嫌がらせするためにこっそり集めているコレクションか何かでしょうか。
こんなやっかいな虫たちが、気づかないうちにどんどん増殖してしまったら……レイクビュー邸はいつの日かパンクしてしまいます。

ちなみに、謎Critter達のエラーを調べるために、単に家の出入りだけを繰り返していたプレイの時には、最初の11匹から新たに別のCritterが増える気配はありませんでした。
また、「OnLoad」の際に正規のルートで生成されるCritterは蝶の他にもハチがいるわけですが、不思議なことにこいつらは一匹も復活したりしていません。
もっとも養蜂場から産まれるハチのCritterは、ログの足跡を追ってみると、どうやら生成されても付近にワープするための植物自体が見つからないようでして、しばらくすると自動的に消滅してしまっています。
せっかくスクリプトがついているのに、Critter着地用の植物やマーカーを周囲に配置するのを忘れてしまったんでしょうかね?
Hearthfireの家はこういった設定ミス(?)が実に多いですよねえ……
まあ、そんなわけで、ハチは誕生してまもなく消えてしまってるんですが、蝶だって「OnCellDetach」のタイミングで消えているのは同じといえば同じです。
どちらも「disableAndDelete」という処理が呼ばれて、エラーも出さずにちゃんと削除されています。(※delete時にエラーを出すのは物の怪になってからです)
それなのに、なぜハチは復活しなくて、蝶だけが蘇ってしまうのか。
う~ん、どういった法則で、このような現象が起こるんでしょうかねえ……


そんなわけで、いったいどういう状況下になると、生成されたCritterが消えずにデータ上に残り、パターンBのようなエラーを吐くようになってしまうのか、いろいろ試しまくって、あれこれ調べてみました。
最初はその最終的な結論に辿りつくまでの検証の経緯を、ダラダラと長文を重ねて書いていたのですが……あまりにも長すぎて、何よりおばちゃん自身がしんどくなってきたので、やめました。(というか今でも充分長いですよね。すみません)
なので、途中のデータの詳細も書かず、いきなり結論だけを書いてしまいますが、もし以下の内容をおかしいと思ったり、疑わしいと思った場合は、ぜひご自分でもテストしてみて同じ現象が再現されるかどうか、確認してみてください。
そしてその検証の結果を、ぜひおばちゃんに教えて下さい。
おばちゃんも何度か試してはいるのですが……全部勘違い、ということもありえますので、他の方の環境でもそうなるのか、切実に知りたいです。


さて、いろいろテストした結果、おばちゃんがようやく確認できたCritterの法則は、プレイヤーがCritterのいるセルから別のセルに移動する時、移動先や移動手段によってはCritterが消えずに残る場合がある…ということでした。
通常だったら「OnCellDetach」イベントを受け取ったCritterは、自分の着地マーカーと自分自身を消し去る処理に移り、終了時にはCritterのRefIDは完全に消去されます。
しかしその挙動はどうも、プレイヤーが歩いたり馬で移動したり、とにかく地続きの隣のセルへそのまま移動する場合と、特定の場所以外の建物やダンジョンや街の中などに直接入った場合、だけのようなのです。
通常のセル移動では、Critterのすべてがそこで完全に消滅します。
ちなみにこういった通常のセル移動を行うと、なんと、その時に生成されたCritter達だけでなく、パターンBのエラーを吐く悪質なCritterまでもが、一網打尽に消滅します。
これまでにさんざん、「どうして消えてくれないの!」とレイクビュー邸の家の出入りを繰り返していたわけですが、なんのことはない、レイクビュー邸からちょろっと、近くのタロス像のあたりまでお散歩にいって、タロス様に「どうかCritter病を直してください」とお願いするだけで良かったのです。
こんな簡単なことで、あれほどしつこかった害虫たちが消えるとは……まったく、やってられません。おばちゃんの睡眠時間を返せ。
移動手段や移動先によっては、Critterの処理の残骸?がセルに残ります。
では、反対にCritterが消えずに残ってしまうパターンはどんな状況なのかといいますと……それはCritterの居るセルから、ファストトラベルで移動したり、またレイクビュー邸のような特定の場所に入ったりする場合です。
えっ、ファストトラベルするだけでCritterがエラーを吐くようになるの?とお思いでしょうが……もし良かったら、どこか蝶々やトンボや魚のいっぱいいる場所に行って、その場所からファストトラベルでどこか遠くの場所に飛んでみてください。
そしてそこでセーブ&ロードしてから、再び先ほどのCritterの居場所に戻って、そのポイントでもう一度ファストトラベルすると……その移動の際には、Critter達がパターンBのエラーを吐くようになっていると思います。(ただしハチ(Firefly)を除く)

これはあくまで推測なのですが……ファストトラベルした時や特定の場所へ入ったりする場合には、移動時に移動元のセルの状態をキャッシュのようなものに保持しているような気がするのです。
特にCritterの「onCellDetach」イベントの一連の処理は、セル移動時に何かのスクリプト処理をやってる途中だった…みたいな情報として残ってしまうんじゃないかと思うんです。
で、そのCritterの処理の残骸のようなものが、残された処理の続きを行うために、削除された自分の体を復活させているんじゃないかと思うんですよね。
実は「セーブ&ロード」を挟まなくても、再びCritterの居たセルに戻って、RefIDを「prid」してみると、すでにその時点で消えた筈のCritterが存在していることが確認できたりします。
また、着地マーカーなども削除マーク[D]付きではあるのですが、完全に削除されてはおらず、RefIDなどの情報はしっかり残っています。
それを見ると、やはりファストトラベルや特定の場所への移動は、通常のセル移動の場合とは異なる仕組みのような気がするんです。
通常のセル移動を行うと、Critterや着地マーカーの情報はいっぺんでスパーンと消えてなくなりますからね。

ちなみに「特定の場所」への移動と書きましたが、実はこの「特定の場所」の条件とは何なのか、ということは今のところおばちゃんにはまったく解明できていません。
ただ、Critterの出没するめぼしい箇所で確認してみたところ、「レイクビュー邸」の家の中だけでなく「ホニングブリューハチミツ酒醸造所」や「パインウォッチ」「マルカルス」といった建物や街の中に出入りをする際にも、Critterが消えずに残る現象を発見しました。
これらの場所に共通する特徴は何か……いろいろ考えてみたのですけれど、どうもわからないのですよねえ。
もちろんこの4箇所以外にも、こういった場所は沢山あるのだろうと思います。

ちなみに、こういったCritterが消えずに残ってしまうようなロケーションであっても、Critterをすべてアクティベートして採取してから移動すれば、こういった問題は起きません。
ようするに特定の場所へ入室する時、またはファストトラベルをする際の「OnCellDetach」イベントが起こるタイミングで、Critterが残存していなければ、Critterが消えずに残る……ということはないわけです。
そういえばレイクビュー邸の養蜂場のハチは、あれは元々、着地点となる植物が無いせいですぐに消滅してしまい、「onCellDetach」イベントを迎えることなかったわけですが、よくよくハチの「FireFly」スクリプトを見てみましたら、ハチにはなぜか「onCellDetach」そのものが設置されていませんでした。
なのでハチはセル移動時に生き残っていても、復活はしなさそうです。
ただ、もしハチに異常事態が発生して、物の怪として復活してしまった場合は……非常に困ったことになるかとは思います。
「onCellDetach」イベントで削除されるというルートがなければ、通常のセル移動で消えてくれることもないわけですからね。


では、こういった中途半端に残されたCritterの情報を含んだセルデータが、「セーブ」されてしまった時のことを考えてみたいと思います。
消えずに残ってしまったCritterはおそらく、何かの処理の途中だった…という情報として、その処理のスクリプトが付いていたObjectRefarenceの情報と共にセーブデータに記録されるのではないかと思います。
このゲームの現状を維持しようという力は非常に強力なので……特にObjectReferenceやセルのデータに紐付いているスクリプトは、何が何でも最後まで責任取らせようとするはずです。
しかしCritterが用意した着地マーカーのような副産物の情報については、マーカー自体には処理途中のスクリプトがついているわけではないので、あっさり切り捨てられている可能性が高いです。
しかも着地マーカーのRefにはしっかり削除マークがついているわけですしね。
そしてこのセーブされたデータを「ロード」すると、再びCritterは蘇って処理の続きが行われる機会を待つんだと思うんですが、この時点ではもう、お供の着地マーカー達はどこにもいなくなってしまっています。
つまり「セーブ&ロード」の後からエラーが発生するのは、着地マーカー2種が消えるためじゃないかと思うのです。
Critterが着地マーカーのRefIDを覚えていたとしても、マーカーそれ自体がデータから消えてしまっているんじゃ、削除しようがないですからね。
ちなみに「セーブ&ロード」の後に、着地マーカーのRefIDが「prid」できるか調べてみたんですが、やっぱり消えてしまっておりました。

ちなみにおばちゃんはこういったパターンBのdeleteのエラーを吐き続けるCritterを180匹ほどわざと発生させてみましたが、そのせいでCTDするようなことはありませんでした。
ですから、このようなCritterが増えてしまうことを、そこまで神経質に気にする必要はないんじゃないかと思っています。
それに無駄に増えてしまっても、その辺をちょっと歩けば消えてくれるわけですしね。

とりあえず、我が家のCritter問題については、自宅である以上、何度も出入りをせざるをえない場所なので……家の外に出た時は、Critterを一匹残らず採取することで対応したいと思います。


2013/03/24

Critterの一生とPapyrusのログ(前編)

さて今日は、スカイリムの美しい大自然を満喫するのに欠かせない、虫や魚、鳥といったCritter(生き物)たちのしくみについて、いつもの如く長文を垂れ流したいと思います。
ちなみにおばちゃんは前作オブリビオンの頃から素材採集というものが大好きで、クエストそっちのけでキノコ狩りに明け暮れていて、一年以上もプレイしていたのにメインクエストは未だクリアしていない…という体たらくでありました。
そんなわけでスカイリムにやって来て、まず一番最初に感動したのは、蝶々やトンボ、サケといった生き物が捕れる!ということでした。
花やキノコなど植物の素材を集めるだけでも楽しかった採集の楽しさを、スカイリムでは対象にCritter(生き物)を加えることで、さらに進化させた…と言えましょう。
どんなに美しい大自然の景色も、採取できる生き物がいないと物足りないです。

ああ、こういう浜辺で釣りや潮干狩りができたら、もっと楽しいのにな……
ところで皆様は、サケとスローターフィッシュの違いとか、鷹とニワトリの違いとか、Critterとそうでないものの違いって、判別がつきますでしょうか?
いや、蝶とかトンボみたいに小さい生き物ならともかく、見かけだけだとCritterなのかそうでないのか、よくわからない生き物もいるんじゃないかと、ふと思ったりして……
まあ、CKを弄る方であれば、攻撃するとコンパス上に赤い印が点灯するようなクリーチャーはCritterではなく、見かけが虫だろうが魚だろうが鳥だろうが「Actor」に分類される代物だということはよくご存知かと思います。
あのソルスセイム島のアッシュなんちゃら…というキモいカマドウマでさえも、見かけは思いっきり下等生物、というか便所コオロギにしか見えませんが、アイツもCritterではなく、人間のNPC達と分類的には同じ「Actor」の仲間です。
ちなみにあのカマドウマが、Critter扱いのちっちゃなバッタとかだったら、たとえ足がもげようがお腹がポロンと取れようが、おばちゃんは喜んで佃煮にするレシピModなぞを作ってレイロフ君にしこたま食わせていたかと思います。
どうしてベセスダ様は、アレをあんな化け物にしてしまったのでしょうかねえ。
次こそは、採集しがいのある可愛い新規Critterが追加されることを祈ります。


ちなみに「Critter」の生き物と「Actor」との違いは何かと言えば……それはまあ、いろいろありますが、なんといっても一番大きな違いは、自分で状況判断するActor用のAIを搭載しているか否かなのではないかと思います。
「Actor」は、AIパッケージというものをセットしてやれば、勝手に辺りをうろついたり、ご飯を食べたり、自分にできる行動の中から最適な行動を選択して適当に振る舞ってくれます。
また攻撃されれば自動的に相手と敵対化して、臆病なタイプなら逃走を計りますし、相手と自分の実力を見比べて勝てそうだったら挑みかかっていったり、また勝てそうになくても突っ込んでいったり……とにかく状況に応じた行動をいちいち細かいところまで指示してあげなくても、AIが勝手に考えて行動してくれます。
しかし「Critter(生き物)」はそういったActorではなく、単なるアクティベーターであるため、予め用意された自動のAIは一切搭載されていません。
それなのに何故、プレイヤーが近づくと急に身をひるがえして逃げたり、夜になったらいなくなったり、生き物みたいな行動をするのかというと……それは擬似的なAIとして組まれた「スクリプト」が付いているからです。
ホニングブリューハチミツ酒醸造所近くの橋に設置されたCritterのSpawner(生成器)

Critterは「Activator」の一種で、スイッチのようなある種の『仕掛け』です。
Critterの生き物は、上画像のようなSpawner(生成器)と呼ばれるアクティベーターを介してスクリプト処理によってゲーム上に自動で生み出されています。
Spawnerとは、スポーンするもの……Critterを産み出す親のような存在です。
(※Spawnerはいろんな種類があります。上画像にあるようなのは単なる一例です。
またハチの巣や養蜂場など、Containerタイプのオブジェクトにスクリプトが付けられて、Spawnerとして振舞っているものもあります)
ちなみにSpawnerから生成されるCritter自身もまた、アクティベーターです。
Critterの活動の全ては、このSpawnerがゲーム上に読み込まれて「OnLoad」というイベントを迎えることから始まります。
とある場所(セル)に配置されたSpawnerが「OnLoad」された時…のイメージ図。

文章だけだとイメージがつかみにくいので、ビジュアルにしてみました。
(余計わかりにくかったりして……;)
まず、プレイヤーがSpawnerの配置されたセルに侵入してくると、Spawnerのデータが「OnLoad」されるわけなんですが……実はこの「OnLoad」というのは、プレイヤーがセルに侵入するたびに発生するというわけではなくって……まあ、それについては後で書きますので、詳細は後回しにさせてください。

で、この「OnLoad」というイベントが起こるタイミングで、Spawnerは活動を始めるわけなんですが、「OnLoad」されたからと言って、SpawnerはいきなりポコポコCritterを発生させるわけではなくて、まずはいろんな状況を見てCritterを生成すべきかどうかというチェックを入念に行っております。
第一のチェック項目としては、プレイヤーとの距離……これは予め設定された距離の範囲内にプレイヤーがいるかどうかのチェックです。
この範囲は、Spawnerにくっついているスクリプトの「fMaxPlayerDistance」というプロパティの値なんですが、蝶やトンボなんかですと、だいたい4000に設定されてるようです。
4000という距離がいかほどのものなのか、数字だけでは想像はしにくいかと思いますが……そうですね、ブリーズホームの玄関を入ったとこからまっすぐ、突き当たりの食器棚のある壁までの長さがだいたい1200程度なので、距離4000はその3倍強といったところです。
おそらくその辺りの数値が、視認できるかどうか、といったラインなのでしょう。
たとえ同じセル内にいたとしても、プレイヤーから見えないような遠距離にCritterを発生させても処理の無駄ですから……一定の距離内にプレイヤーが近づいてくるまではCritterは生成されないようになっているのだと思います。

さて、プレイヤーが指定の距離圏内に居た!ということが確認できると……今度はSpawnerは発生させるCritterが活動しても良い状況かどうかのチェックをします。
たとえば蝶やトンボ、ハチといった虫は、雨や雪の降ってる時には見かけませんよね。
でも水の中にいるサケやアビシアン・ロングフィンのような魚は関係なく泳いでます。
それから活動時間……黄色い蝶や青い蝶なんかは昼間はよく見かけますが、夜になるといません。代わりに辺りが暗くなると出現するのはホタルやルナ・モスです。
そういった、Critter達の特性に合わせた出現条件(これもSpawnerのスクリプトのプロパティにセットされています)をチェックして、条件にすべて当てはまっている、という確認ができたら、そこでようやくCritterを発生させる準備に入ります。

ちなみにこういったCritterの発生条件が合わず、ダメ出しが出てしまった場合はどうなるのかというと……SpawnerはCritterを生成できる条件が整うまで、しつこくチェックを繰り返す怒涛のループ処理に入ります。
たとえばSpawnerに「Onload」イベントが起こった時点で、まだプレイヤーが指定の距離以内にはいなかったとすると……SpawnerはいったんそこでCritterを発生させることを諦めますが、再び二秒後にまたプレイヤーが圏内にいるかどうかのチェックを始めます。
Critterの活動時間や天気などについてのチェック時も同じです。
条件が合わなかった場合、一旦はそこで生成を諦めますが、二秒待機してから再びチェックをして状況が変わっていないかどうか確認します。
そんなしつこく確認しなくても……と思ったりしますが、こういう常時チェックが入るからこそプレイヤーがその場でボーっと突っ立っていても、雨が止んで晴れてくると、岩場の影から蝶々がひらひら飛び始めたり、夜になって辺りが暗くなると、茂みからポッポッとホタルが光って出てきて「ああ、もうそんな時間なんだ。早く寝床を探さなくっちゃ」…とか思うような変化に富んだ自然の風情を演出できるわけなんですよね。
もちろんこのループ処理は、永遠にハマってえんえんとチェックを繰り返すことのないように、様々なシチュエーションでループから抜け出せるように対策が施されています。
SpawnerからCritterが生成されるイメージ図(蝶の場合)
お次はSpawnerからCritterが生成される過程を見てみましょう。
ところでCritterといってもいろいろ種類がありまして、一応基本的には似たような構造ではあるのですが、生成されてからの動きはそれぞれのCritterでちょっとずつ違います。
そんなわけなので、今回は『蝶』を一例として書いていますが、これがトンボだったり鷹だったりする場合は、また違った流れになりますので、あくまでCritterというものがどのようなサイクルで動いているのか、ざっくりイメージを掴むためのもの、と思ってお読み下さい。

さて、Critterが発生する条件がすべて整ってGOサインが出ると、Spawnerはまず現在発生しているCritterの残数を調べます。
SpawnerにはCritterを何匹まで生成するか、という数値が予めプロパティ(iMaxCritterCount)によって決められているので、Critterを無数にじゃんじゃん好きなだけ発生させる、ということはありません。
また、自分が産み出した我が子のCritterについてはしっかりその数を把握していますし、子供のCritterの方も、自分が捕まったり、殺されたり、あるいは消えることになった場合は必ず親のSpawnerに「自分は死んだよ」ということを伝えています。
そういうわけで、現在生き残っているCritterの数というものは親Spawnerには分かるようになってますので、今回はあと何匹生成すればいいか、という数を決めます。
たとえば合計3匹の蝶が生成されるように設置されている場合、前回の生成時に生まれた蝶がまだ1匹生き残っていたとしたら……今回生成するのは2匹、となるわけです。

生成するCritterの数が決まったら、Spawner母さんはその数だけCritterを出産し、Critterが生きていくのに必要なCritterのクラスだのプロパティだのを我が子につけてやります。
生まれたばかりのCritterは、まず自分が動き回るために必要なlandingMarker、dummyMarkerといった着地地点用のマーカー類を準備しなくてはなりません。
そしてこの着地マーカーを使って、まず最初の移動(ワープ)を行います。

一般的な蝶の場合ですと、移動する時の着地点は「AAAMothPlantTypes」というFormListに登録された34種の植物とマーカーから候補を探すように設定されています。
蝶はよくツンドラの綿とかラベンダーの花の辺りに止まっているのを見かけるかと思いますが、あれは「AAAMothPlantTypes」というフォームリストに登録されている植物だからこそ、蝶が羽根を休める場所として着地点に採用されているわけです。
ちなみにこういった着地点は、親Spawnerから貰った「fLeashLength」というプロパティの範囲内で探すように決められています。
つまり母さんからあまり離れたところまで飛んでいったりしないように、手綱(Leash)をしっかり握られているわけですね。
その手綱の範囲内で、子供のCritterの蝶は自分が止まれる植物(あるいはマーカー)のターゲットを見つけ、初回はその地点に直接ワープします。
蝶Critterは、周囲の状況を逐一チェックしながら活動します。
うまく最初の着地点にワープした蝶Critterは、「AtPlant」という状態(State)にあります。
蝶CritterはいろんなStateを持っておりまして、状況に応じてこのStateを切り替えて活動してるんですが、植物の間をひらひら移動するサイクルを繰り返している時はこの「AtPlant」というStateの状態にあります。
「AtPlant」というStateの時は、次の着地点となる植物を見つけて、そちらに飛び移る、という動作を繰り返すのですが、その際に近くにActorがいないかサーチして、もし誰かを見つけたら、通常よりも早いスピード(逃げる時のスピード)で移動します。
また移動が完了した後も、通常なら5~10秒の間、植物に止まった状態で待機するのですが、誰かが近くにいたら、すぐさま次の植物に向かって飛びます。
(次の植物が見つかれば…の話ですが)
こういったチェックを常時行うことで、あたかも蝶が周囲の生き物を警戒して逃げ惑うような生き生きとした動きを再現しているわけです。

ちなみに蝶Critterが逐一チェックしているのは、周囲に誰かいるかどうかだけではありません。プレイヤーと自分の距離についても、逐一細かくチェックしています。
これは前述したSpawnerの時と同じで、プレイヤーがちゃんと「fMaxPlayerDistance」という距離の圏内に入っているか確認し、もし視認できないような遠くにいる場合は、即座に自分自身と関連する着地マーカー類を消去する一連の削除処理を実行するためです。
また、お天気や活動時間などの、自分が活動していても良いかどうかの状況チェックも、同じようにその都度行われています。
お天気が変わったり、活動時間外になると、Critterは「KillForTheNight」というお休みモードに移行して、自分自身と関連物を消去する削除処理に入っていきます。
この削除処理(disableAndDelete()という関数の処理)は、Critterをアクティベートして採取したり、また攻撃したり魔法を使ったりして殺したりした時にも実行されている処理です。
またプレイヤーがCritterの居るセルからプレイヤーが出ていってしまった時などにも実行されていまして、Critterが無駄にいつまでもしぶとく生き残っていたりしないように、何かイレギュラーなことがあればすぐに削除されるようになっています。
そんなわけで、Critterの一生というものは実に短くて、せっかく生まれても、大抵はプレイヤーが遠くにいってしまったりセルから出ていってしまったり、何かあればすぐに消されてしまう、はかない命だったりします。
もっとも幽霊になって、しぶとくデータに残ってる奴もいたりするようなんですが……これは一寸の虫にも五分のスクリプト…という奴なんでしょうかねえ。

それから、これはちょっと通常の蝶では見られないモードなのですが、蝶Critterには「FollowingPlayer」という面白いモードがあります。
たとえばDLC第一弾のDawnguardで、カンティクルの木の樹皮を持っていると聖蚕の蛾がたくさんプレイヤーの周りに群がってきましたよね。
おばちゃんはあの蛾にたかられた時はうひゃあ、という感じだったのですが……あれは「カンティクルの木の樹皮」を蝶Critterの好むアイテムに指定してあって、そのアイテムを持っているせいで蛾たちは「FollowingPlayer」のStateに移行してたわけです。
この「FollowingPlayer」というStateでは、いつもは人が近づくと逃げる蝶が、わらわらとプレイヤーの周りにたかってくるようになります。
これをいろんなCritterに悪用したら……と考えると、想像するだけで怖いですね。


さて、Spawnerの働きからCritterの活動内容といった概略を、かなり大雑把にまとめてみたのですが、いかがでしたでしょうか。これでも端折れるところはバッサバッサと9割方カットしたつもりだったんですが、後から読み返してみるとやはり無駄に話がクドくて読みにくいですね……ほんとお疲れ様でした。
それでも、Critterがいったいどういうタイミングで「発生」し、どういったタイミングで「消滅」していくものなのか、まずそれを知らないことには、Papyrusのログを分析することなんかできませんのでね。
そもそもおばちゃんがCritterの仕組みを調べようと思ったきっかけは、先日ソルスセイム島から我が家のレイクビュー邸に久しぶりに戻った折に、CTDしたことが発端なんです。
まあ、そのCTDは別にCritterのせいというわけじゃなくて、ゲームを再起動しなおしたら、もうCTDしなくなっていたので、たぶん何かのタイミングが悪くて落ちちゃっただけだと思うんですが、それでもPapyrusのログを見てみたら……Critterとおぼしきエラーがわんさか出ていまして。
確定CTDが起こるような致命的なエラーじゃないとはいえ、マイホームの周りでこんなわけのわからんエラーが仰山出てると、さすがに心配になりましてねえ。
それでとりあえず、Critterとはどんなしくみで動いているものなのか、わからないとログを見てもさっぱりなので……こうしてCritterとがっつり取っ組み合うことにしたんでした。

ちなみに我が家の周りに憑りついた幽霊Critterとの格闘の話は、長くなりましたので後編に回したいと思いますが、その前にSpawnerの説明の折に触れた「OnLoad」イベントについて少し補足したいと思います。
なにしろ「OnLoad」イベントはCritterの「発生」の鍵を握る重要なポイントですので、このイベントがどのタイミングで起こるものなのか、ということを把握することは大事です。
それではちょっとした実験をして、「OnLoad」イベントとはなんぞや、ということを確認してみましょう。
まずはブリーズホームの暖炉脇に蝶を一匹だけ生成する「Spawner」を配置します。
配置するのはSpawnerだけでなく、ラベンダーやランディングマーカー(CritterLandingMarker_Small)といった「AAAMothPlantTypes」のFormListに登録されている着地点用のオブジェクトもその辺に一緒に設置しておきます。
一匹の蝶が動き回るためには着地するポイントが最低でも2点は必要ですからね。
では準備がすんだら、このModを導入して、ブリーズホーム内でセーブしたデータから、ゲームを始めてみることにしたいと思います。
ゲームがロードされた直後のブリーズホーム。

ゲームをロードして室内を見てみると、配置したラベンダーはきちんと出現していますが、蝶の姿はどこにも見えません。
Spawner自体はもともと外観は無いので、見えていなくて当然なんですが……ラベンダーが反映されているということはMod自体の導入は成功しているということですよね。
ですからSpawnerも姿は見えなくてもきちんと暖炉の脇に配置されているはずです。
それなのに、いったいどうして蝶は生成されなかったのでしょう?

ちなみに、ここでブリーズホームから一旦外に出ると、おばちゃんの環境ではここで必ずロード画面が発生するわけなんですが、そのロード画面を挟んで再び室内に戻ってくると蝶がちゃんと生成されています。
壺が転がっているのは、舞い上がった蝶がぶつかったためです。
蝶の当たり判定って、パないですね(笑)
ちなみにスクリプトの各処理にデバッグ用のコメントをつけて、ブリーズホームの出入りの時の状況を書き出してみたPapyrusのログはこんな感じです。
[03/17/2013 - 00:00:09PM] Papyrus log opened (PC)
[03/17/2013 - 00:00:09PM] Update budget: 1.200000ms (Extra tasklet budget: 1.200000ms, Load screen budget: 500.000000ms)
[03/17/2013 - 00:00:09PM] Memory page: 128 (min) 512 (max) 76800 (max total)
[03/17/2013 - 00:00:17PM] VM is freezing...
[03/17/2013 - 00:00:17PM] VM is frozen
[03/17/2013 - 00:00:17PM] Reverting game...
[03/17/2013 - 00:00:20PM] Loading game...
[03/17/2013 - 00:00:20PM] VM is thawing...
[03/17/2013 - 00:00:30PM] ★Spawner[critterspawn < (02000D66)>]がonCellDetachされました
[03/17/2013 - 00:00:30PM] ★Spawner[critterspawn < (02000D66)>]がonUnloadされました
[03/17/2013 - 00:00:38PM] ★Spawner[critterspawn < (02000D66)>]がOnloadされました
[03/17/2013 - 00:00:38PM] ★Spawner[critterspawn < (02000D66)>]ShouldSpawn()を実行します
このログを見ると、面白いことに、最初に「OnLoad」はされなくても、ブリーズホームを出た瞬間に、Spawnerに「onCellDetach」と「onUnload」イベントがしっかり発生していることがわかります。
ですから外観は無いので見えないですけども、セーブデータがロードされた時点で、「Spawner」自体は確実にブリーズホームのセル内に読み込まれて存在していたわけです。
しかしセーブデータからロードしても、プレイヤーが最初からブリーズホーム内にいる状態では「OnLoad」というイベントは発生しなかった……
セーブデータから「ロード」したんですから、「OnLoad」されても良さそうなものなんですが、プレイヤーがそのセルにいる場合はどういうわけか「OnLoad」はされない……みたいなんですね。
理由はわかりませんが、そういう仕様なんだ、と解釈するしかありません。

ちなみに一度外に出てロード画面を挟んでから、再びブリーズホームのセルに侵入することで、ようやく初めての「OnLoad」状態が発生するわけですが……どうやらこの「OnLoad」というのは二回目以降も、ロード画面が発生した後、Spawnerの配置されたセルにプレイヤーが初めて侵入する時にしか発生しないようなんです。
つまり何が言いたいのかというと………次に蝶が新たに生成されるのはまた、Spawnerが「OnLoad」されるタイミングなんですが、この「OnLoad」という状態はどういう状況になったら起こるのか、と言いますと、ロード画面を発生させた後、Spawnerの配置されたセルにプレイヤーが別セルから初回侵入する時…というタイミングなんです。
ですからたとえば、ブリーズホームにいる蝶を全部獲ってしまって、再び室外に出ても、ロード画面が発生しない限りは蝶が再び生成されることはありません。
室外に出て、一度セーブして、それからそのデータを「ロード」して、ロード画面をしっかり拝んでから改めて室内に戻れば、蝶は新たに生成されています。
(セーブしなくても街の外に出るなどして強制的にロード画面さえ挟めばOKです)

今まで、蝶やハチやトンボは、植物と違って一日ぐらい経つとすぐにリスポンしているよな…とは思っていたんですが、まさか一日どころかロード画面を挟むだけで復活していたとは思いませんでしたねえ。
もっともプレイヤーがSpawnerと同じセルにいる場合は、ロード画面を発生させても「OnLoad」されることはないので、それでその法則にはなかなか気が付かなかったのでしょうが。
そう考えると、この仕様はゲームを中断した際に前後の状況を矛盾しないようにするための処置なのかもしれませんね。

Critterの一生とPapyrusのログ(後編)に続く

2013/03/10

タロス巡礼の旅(8)~ノーザンコースト・ドレモラの海賊王~

タロス巡礼の旅の続きに戻るため、久々にマイホームに帰ってまいりました。
ああ、スカイリムの空気は美味しい……ソルスセイム島では火山灰のせいか始終埃っぽくて、周りの人がみんなゲホゲホしているので、なんかこっちまで喉が痛くなってくるようで堪らなかったんですよね。
喜び勇んで駆けつけてくる娘の笑顔に癒されます。
お土産よりお小遣いをせびられてガックリきましたけども……子供ってそんなもんよね。
ただいま
ちなみに今回は、1.9βを導入してプレイしております。
アップデートの内容を見て、もしやフォロワー周りに修正が入ったのでは……と、ビクビクしていたのですが、改造しまくっている仲間達には特に不具合も無いようで安堵しました。
ちなみに1.9のアップデートの中で、おばちゃんが特筆すべきだと思ったのは、パンやスローターフィッシュの焼き身、リーキのグリルといった、重力(?)がおかしかった食品群のnifファイルの修正です。
お皿の上にこれだけのっけてもハヴォック様が暴れないなんて……(感涙)
1.9アップデート
今までは、パンはロードを挟むと直立しちゃうし、スローターフィッシュの焼き身は空中に浮いてしまうしで、配膳にはとても使えない状態だったのですが、修正後は気味が悪くなるくらいおとなしくなって、綺麗に配膳できるようになっていました。
もっとも、これだけ綺麗に盛り付けしてやったのに、レイロフ君は何が気に入らないのか、悲しそうにうなだれていましたが。
こら、好き嫌いしないで、野菜もしっかり食べなさいよ。

ソルスセイム島ではいろんな物を拾ったので、お土産として仲間達に配りました。
両手武器の得意なガルマルさんには勇者のハンマーを。
ガルマルさんにお土産
二刀流のレイロフ君には、ブラッドサイズとソウルレンダーのセットを。
レイロフ君にお土産
ヘイムスカー氏にはミラーク先輩の触手の杖を。
へっ君には先輩の杖を
この「ミラークの杖」って、遠距離から放出すると、まるでホースで水を撒いているような放物線の軌道を描くので面白いですね。
もっとも、得体の知れない海藻のような触手がブチャブチャブチャ…という、気味の悪い効果音と共に放出されるので、汚物を撒き散らしているようで気が引けるのですが。
触手に憑りつかれ、タタリ神のようになってしまった巨人さん。
タタリ神化した巨人さん
ヘイムスカー氏もこのお土産は気に入ったみたいで、魂切れを起こすまでそこら中に汚物を撒き散らしていたのですが、残念ながらこの触手は、よほど当たり判定が大きいのか、仲間に誤爆しまくるので、没収せざるをえませんでした。
なにしろこの杖を使うたびに、レイロフ君やガルマルさんと大喧嘩になるので……ミラーク先輩もこんな『自分さえ良ければ他人はどうなってもいい』みたいな武器を使ってるから、友達ができなくて住人を洗脳するしかなかったんじゃないですかねえ……


さて、前置きがだいぶ長くなりましたが、タロスお遍路の続きです。
だいぶ以前のことなどで記憶が遠くなっていますが……前回のタロス様はウィンターホールド地方のフロストフロウ灯台を越えた北の海に近い氷河地帯の最中にありました。
で、今回のタロス様なんですが、実は前回のタロス像から見えるご近所にあったりします。
次のタロス様はすぐ近く
今回の道程は、走ったらものの数十秒で着いてしまいそうな距離なので、巡礼の『旅』と呼ぶにはいささか気がひけるのですが……しかし今回は前準備として、とある人物に会っておく必要があるため、巡礼の前にウィンターホールド大学に向かわなくてはなりません。
会っておかなくてはならない人物というのは……この御方です。
ヴェレーク・セイン氏
今回のタロス様のお膝元には、このヴェレーク・セイン氏の宝箱が眠っているんですよね。
せっかくなので、このドレモラの海賊王のお宝を頂戴できるように、彼と取引をしてから、現地に向かいたいと思います。

ヴェレーク・セインという御方は、ウィンターホールド大学の地下のミッデン・ダークにある下画像のような妖しげな篭手のオブジェに、四つの指輪を装填することで封印が解かれ、出現します。(指輪はアルケイナエウムに保管されています)
インテリアに欲しい
このオブジェ、指輪用のジュエリーボックスとしてインテリアに欲しいなあ……
ちなみにこのドレモラの海賊王さんは、書物の「アビシアンの海賊王」に登場するヴェレーク・セイン船長本人のようです。
ドレモラって、規律や上下関係にこだわるお堅い軍人のようなイメージがあったんですが、こんな風にレッドガード風の装束を身に着けて、はっちゃけちゃう個体もいるんですね。

アビシアン海というのは、UESPの説明によると、タムリエル大陸の西にある、シロディールやハンマーフェル、ヴァレンウッド、サマーセット島などに面した海のようです。
その世界を股に架けた海で活躍していた著名な海賊王が、なにゆえ辺境のスカイリムのウィンターホールド大学の地下に封印されていたのかはわかりませんが、とりあえずせっかくお会いできた著名人なのですから、相手がドレモラだろうが何だろうが友好的に接して、貰えるもんは貰っておこうと思います。
名前にはその個体を縛る「言霊」の力があるようです。陰陽師な世界観ですね。
名前が大事
名前には力がある……と言われているのに、「ベーレク・サイン」という違う発音の名前を思いっきり口にしてしまうボケ気味のドヴァキンさん。
ツッコミ待ち?
てっきりツッコミ待ちなのかと思いましたが、単に訳語が統一されていないだけのようでした。こういうの、別に困るほどではないですけど……「は?」ってなりますね。

そんなわけで、ヴェレークさんからは無事に隠し財宝の在り処を教えていただきました。
何しろ『アビシアンの海賊王』という書籍にもなった有名なキャプテンが遺した、ひとつなぎ(かどうかはわかりませんが)の財宝です。胸が躍ります。
財宝の在り処を示す絵図は下画像のような感じです。
宝の地図
あんまり精密なスケッチとは言えませんが、よく見ると、タロス像らしきシルエットの部分はしっかりとマントを羽織っているのが確認できますし、また足元の蛇らしき彫像の存在も、ちゃんと見て取れます。
現場のスクリーンショットと見比べてみると、まずまずの再現度……と言えるでしょうか。
絵図通り
ちなみにこういったメモや日記のスケッチで、精密かつ描写力に秀でているのは、DLC第一弾の「Dawnguard」のスケッチですね。
特にカトリアさんが遺したドワーフの遺跡の仕掛けのスケッチなどは、あまりにもうますぎるので「絵師になった方が良かったのでは?」と思ったくらいでした。
スカイリム本編に出てくる宝の地図などのスケッチは、あんまり精密に描いてしまうとすぐに答えがわかってしまうので、わざとそうしているのでしょうが……ちょっとわかりにくい絵柄ですよね。
ヴェレーク氏と取引してから現場に行くと、宝箱が召喚されます。
宝箱出現
宝箱の周囲に山積み(という程でもないですが)になった金塊や小銭の袋に、心臓が高鳴りましたが……宝箱を開けてみた瞬間、ため息をついてしまいました。
ヴェレーク・セインの箱
海賊王は「持ちきれない程の財宝を隠した」、と言っていたのに……
なんですかこのガラクタは!
これではリークリングと同レベルです。(彼らもディベラ像とか食器とか大好きですよね)

しかし、考えてみれば……海賊王と言ったって、所詮、山賊と変わりないのですよね。
というか、日本人が『海賊』に対して、夢を見すぎなんだと思います。
アメリカ人が『忍者』に対してドリームを見すぎなのと同じように……日本人は『海賊』というジャンルに、ロマンを感じすぎているように思うんですよね。
海賊だって、山賊と同じくらい、お風呂に入っていないだろうと思われるのに。
いや、まあ……お風呂に入っているかどうかは、人格とは関係ありませんけども;

夢と人情とロマンに溢れた「海賊」ではなく、単なる山賊王の残したお宝だと思えば……宝箱の中身がしょぼくても仕方ないな……と思えてきます。
まあ、スカイリムの山賊王もとい、山賊長はドヴァキンさんが一撃で首を刎ねられるくらいの凄腕の戦士なので、かえって畏敬の念を抱かずにはいられませんが。
こちらのタロス像は目印として機能させるためか、かなり巨大です。
ここのタロス様は大きいです
ちなみにタロス像に参詣するためには、ヴェレーク・セインの箱の出現位置から、半島をぐるりと北に回ってこなくてはなりません。
プレイヤーキャラでしたら、無理矢理、岩場をジャンプしてでも登ってこれますが。
祠はタロス像の裏側にあります。
お供え物
人骨が散らばっている傍らに、錬金術師のかばんがありました。
この骨は錬金術師のものなのでしょうか。この辺はめぼしい錬金素材は採れないように思うのですが……こんな北の果てまで植物採取に来て、事故に逢ったのなら不憫です。
その他のお供え物は、レベル依存の両手斧と、片手武器のスキル本の「狙いどころ指南書」です。
やはりタロス様の近くにあるお供え物は、武芸に関する物が多いですね。

そんなわけで、今回のタロスお遍路は、ピルグリム トレンチ号の難破箇所に程近い、ノーザンコースト地区よりお送りしました。
八番札所
【アクセス】ウィンターホールドより、徒歩約一時間半強。
コンソールご利用の場合は、「coc POINorthernCoast02」→「player.moveto 000ddee9」
今回の順路
ウィンターホールドからタロス七番札所までのアクセスは前回参照です。
七番札所のタロス像からは、ほぼ一直線に北東に向かうだけです。
ドレモラの海賊王の宝箱については……中身がしょぼすぎるので、あえて取得する必要はないかもしれませんが、ディベラ像コレクターの方はぜひともお立ち寄りになり、ついでにタロス像にもご参詣下さい。

【タロス巡礼の旅】
【Next】Coming Soon...
【Prev】 << タロス巡礼の旅(7)~氷河の狭間・古代ノルドの足跡~
第一話はこちら

2013/03/03

リークリング&ストームクロークの救援

スカイリム本土に帰らなきゃ…と思いつつも、サブクエストが充実しているため、なかなかソルスセイム島から立ち去れないでいる今日この頃です。
気が付いたら、いつのまにかDragonbornのスクリプトソースが配信されていたので、リークリング達が救援に駆けつけてくる際に、ストームクローク兵の皆さんも助けに来てくれるようにクエストを改造してみました。
こんなちっこい生き物ですら族長を助けに来てくれるんだもの、
ストクロ兵なら首長のピンチに駆けつけてくるのは当然でしょう。
ところでリークリング達の声は、英語版の音声の方が断然可愛いですね。
せっかくの吹き替え音声なのに申し訳ないのですが、おばちゃんは「dlc2rieklingvoice」フォルダはすべて英語音声に戻してしまいました。

ちなみにおばちゃんは、シルスク出身?のノルド達の存在をまったく知らずに、いきなり人間の言葉を話すリークリング達に出会ってしまったので、彼らが敵対しているノルドというのは、てっきり近くのスコール村のノルドのことだと思い込んでおりました。
ですから、彼らにノルドをやっつけてくれ、と頼まれた時はかなり悩みましたよ……
でも、こんなキモ可愛い生き物たちに頼まれたら、イヤとは言えなかったです。
だって見てくださいよ、この族長……このがんばり具合、微笑ましすぎる。
お皿や熊手で一生懸命に飾り立てている玉座が、庶民用のCommonChairの椅子なのがもう……可笑しいやら、可愛いやら。
フォロワーじゃなくて、いっそ養子にしたいくらいです。
うちにはもう養子の枠は残ってないけど……養子にできるように改造しようかな。
リークリングのセリフなら、全部「ムワサスー」とか言わせておけばいいし。
助っ人に来てくれたリークリング戦士。初めて見た時は感激しました。

そんなわけで、リークリング達が助っ人に来る仕組みをCKで見てみたのですが……この一連の処理は「DLC2RieklingRescue」(リークリングの救援)というクエストで設定されているようです。
このクエスト内で設定されたPlayerのエイリアスで、プレイヤーに何らかの攻撃がヒットした際に、「DLC2RieklingRescueScript」というスクリプトのRollForRieklings()という処理が呼ばれておりまして……そこでいろんな条件を見定め、実際に救援を行うかどうかを判定しています。

その条件とは……まず、「DLC2MH02」というリークリング達の味方側に立つクエストをステージ200まで進めていること……つまり、彼らの新しい族長になっていることが第一の条件です。
それから、前回の救援から半日以上の時間が経っていること。
また、プレイヤーがソルスセイム島に居ること。
(正確には、DLC2SolstheimWorldというワールドスペース内にいること…ですが)
それらの条件が整った上、さらに「DLC2RieklingRescueChance」というGlobal値が、その都度決められる「roll」という1~100のランダムの数値よりも大きかった場合のみ、リークリングの救援が実現します。

ちなみに「DLC2RieklingRescueChance」というGlobal値は、リークリング達の救援が実行されると、一律「5」まで引き下げられてしまいます。
ですから、一度リークリング達が救援に来ると、次回以降は救援に来てくれる確率がものすごく低くなってしまうわけですが……このチャンスの確率は、プレイヤーに弓による攻撃やProjectile(罠魔法のような投射物)による攻撃がヒットすると二倍に増加するように設定されています。
なので、リークリング達がなかなか救援に来てくれない時は、ガンガン弓矢に当たりにいけばいいわけですね。
高レベルになると、弓の攻撃はめちゃ痛いですけども。
もっともこの二倍増の処理は、「DLC2RieklingRescueChance」が25以上の場合は適用されないので、救援の確率は最高でも40%まで(?)しか上がりません。
そんなわけで、リークリング達を確実に救援に呼びたい時は、コンソールで「Set DLC2RieklingRescueChance to 100」と入力してしまう方がいいかもしれません。
(もちろん、救援を呼ぶには前述のその他の条件も満たす必要がありますが)
条件をクリアし、救援が実行されると、プレイヤーの後方に助っ人が転送されます。
ちなみに、この転送の処理なんですが、助っ人が現れる瞬間を絶対に見られないように、充分に距離を取った後方に場所を指定し、しかもプレイヤーが抜刀しているかどうかを、しばらくチェックしているのに感心してしまいました。
確かに、tclでカメラを自由に動かして見てみると、助っ人がいきなり整列した直立状態でワープしてくるので不自然なんですよね。
戦ってる最中に、どこからともなくわらわらと駆けつけてくる…という演出をするためには、こんな風に小技を使うんだなあ…と感じ入りました。
リークリング&ストームクローク連合軍 VS レドラン家
このストームクローク兵の追加部隊……最初は、救援の人員を追加するために「DLC2RieklingRescue」クエストに直接エイリアスを追加しようと思ったのですが、全然、変更が適用される気配がなくて途方にくれてしまいました。
どうやら、この「DLC2RieklingRescue」のクエストは、「Start Game Enabled」と「Run Once」にチェックがついているせいか、一度Dragonbornを導入してしまったら最後、その導入時の状態がずっとセーブデータ上に生き残るみたいです。
コンソールでresetquestやstopquestなど、思いつく限りの初期化を試してみたんですが、何をどうがんばっても、変更が適用されませんでした。
仕方ないので、ストームクローク兵の救援専用の新規クエストを追加しましたが……こういう、途中からの変更が効かないようになっているクエストの改造って、どうすりゃいいんでしょうね。非常にやっかいです。
意外に強かったレドラン家。…てか、ストームクロークが弱すぎる……のか?