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
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
これは経験則なのですが、一定条件化で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します。