スクリプトによるCTDを回避する

Modを作ろう

CTDとはCrash To Desktopの略で、いわゆる不正終了です。ゲームをプレイ中に突然デスクトップ画面に戻ることからCTDと言われます。Skeletonが正しくない(メッシュの要求を満たしていない)とか、原因はさまざまですが、スクリプトが原因のCTDをまとめてみます。

IsHostileToActor

IsHostileToActor

引数がNoneだとCTD、ということで呼び出す前に検査します。

IsHostileToActor (papyrus)

if akOtherActorRef
    if akTarget.IsHostileToActor(akOtherActorRef)
        ; 敵対しているときの処理
    endif
endif

これはやってしまいがちと思います。

いつ初期化されるかわからない場合はこちら。

IsHostileToActor その2 (papyrus)

Actor MyVictim ; 攻撃対象のActor、スクリプト内で共有

Function MyFunction(Actor akTarget)
    Actor akOtherActorRef = MyVictim ; ローカル変数に入れてしまう

    if akOtherActorRef ; Noneではないことを確認する
        ; この時点でMyVictimはNoneになっているかもしれない
        ; でもakOtherActorRefはNoneにならないので安全
        if akTarget.IsHostileToActor(akOtherActorRef)
            ; 敵対しているときの処理
        endif
    endif
EndFunction

このようにプレイヤーをGetPlayer関数で取るのはやめた方がいいと思います。

GetPlayerでCTDする例 (papyrus)

Scriptname SampleScript extends ActiveMagicEffect

Actor MySelf
Actor PlayerRef

; 魔法が掛かったときに呼ばれる
Event OnEffectStart(Actor akTarget, Actor akCaster)
    MySelf = akTarget
    PlayerRef = Game.GetPlayer() ; PlayerRefを埋める
EndEvent

; 死んだときに呼ばれる
Event OnDeath(Actor akKiller)
    if MySelf.IsHostileToActor(PlayerRef) ; PlayerRefがNoneだったらここでCTD!
        ; 敵対NPCが死んだときにここで何かする
    endif

    Dispel()
EndEvent

対策はCKでPlayerRefを紐づけることです。これならスクリプトが稼働した時点で既にPlayerRefが埋まっています。あるいはステートを使い、PlayerRefが埋まる前にOnDeathイベントを処理しないようにする方法もあります。

PlaceActorAtMe

PlaceActorAtMe

ActorBaseを元にActorを生成する関数ですが、特定状況下でCTDします。

  • 引数に指定したActorBaseがテンポラリ(ロード順が0xFF)である
  • そのActorBaseがGCで回収済みである(整理されていて、もうゲームエンジン内に存在しない)

PlaceActorAtMe (papyrus)

ActorBase akActorBase = akTarget.GetLeveledActorBase()

if Math.LogicalAnd(akActorBase.GetFormID(), 0xFF000000) == 0xFF000000
    ; このActorBaseはテンポラリなので使うべきではない
else
    Actor kNewActor = Game.GetPlayer().PlaceActorAtMe(akActorBase)
endif

Actorを動的生成する場合は要注意です。

RemoveAllItems

RemoveAllItems

これは経験則なのですが、一定条件化でCTDすることがあるようです。

  • Actorから移動、もしくはActorへ移動
  • Actorの3Dがロードされている(Playerと同一Cellにいる)

確定でCTDするのではなく、ランダムでCTDします。対策としては以下があります。

  • RemoveAllItemsを使うのではなく、RemoveItemでひとつずつ処理する
  • Actorを別のCellに移動させてから処理する

推測になりますが、RemoveAllItemsはゲームエンジンレベルにてコンテナ内のすべてのアイテムが取り除かれるため、OnItemAdded/OnItemRemovedイベントが一斉に発火するため、そのイベントを見ているスクリプトが多いほどCTD率が上がるのではないかと思われます。Fallout 4でも同じ問題があり、スクリプトエンジンが処理できるしきい値を超えると確定でフリーズするようです。

TapKey

短時間に連続で実行すると確定でCTDします。おそらくは何を押すかでかわってきます。

スクリプトかPOVを切り替えるのはGame.ForceThirdPersonもしくはGame.ForceFirstPersonでいいのですが、Immersive First Person View環境下だとこれでは効かないため、POVキーを取得してTapKeyを使って押すことで変更できます。このPOV切り替えが連続するとCTDします。

タイトルとURLをコピーしました