モス・マザー洞窟の近くに行くとスタックエラー

環境構築

モス・マザー洞窟の近くに行くとスタックエラーが起こります。前から知っていたのですが、本腰を入れて調べてみました。

エラーの原因

Cloakスペルによるスクリプト配布処理で、Cloakスペルが常時発動型のAbilityになっていると、紐づいているConcentrateスペルでConditionにて対象を絞っていたとしても、すべてのActorに毎秒複数回という頻度でOnMagicEffectApplyイベントが発火し続けてしまうようです。

問題は大きく3つです。

  • Magic Effect側のConditionの設定に関係なく、あらゆるMagic EffectでOnMagicEffectApplyイベントが発火する。
  • 事前に絞り込むことができないためOnMagicEffectApplyイベントを受け取ってから条件判断を行う必要がある。
  • OnMagicEffectApplyイベントはオン・オフができないので、どうしても使うのであればステートを使ってイベントを受け取らない(捨てる)時間を作ることが重要。

そこで、OnMagicEffectApplyイベントは使わずに、OnUpdateイベントで対象のヘルスが8割以上になったら回復したとみなすように書き換えてみました。

USKPdunMossMotherValdrAliasScript (papyrus)

Scriptname USKPdunMossMotherValdrAliasScript extends ReferenceAlias

int Property HealedStage = 15 Auto Hidden
Keyword Property MagicRestoreHealth Auto

;When the cell loads or the quest starts, damage Valdr's health.
Event OnLoad()
    ;debug.trace("USKPdunMossMotherValdrAliasScript: OnLoad")
    if (GetOwningQuest().GetStage() < HealedStage)
        ;InjureValdr()
        GoToState("Injured")
    endIf
endEvent

Event OnInit()
    ;debug.trace("USKPdunMossMotherValdrAliasScript: OnInit")
    if( GetOwningQuest().IsRunning() )
        if (GetOwningQuest().GetStage() < HealedStage)
            ;InjureValdr()
            GoToState("Injured")
        Else
            ;Self.GetActorReference().SetRestrained(False)
            GoToState("Healed")
        EndIf
    EndIf
endEvent

;/
Event OnMagicEffectApply(ObjectReference akCaster, MagicEffect akEffect)
    debug.trace( "USKPdunMossMotherValdrAliasScript: OnMagicEffectApply " + akCaster + " " + akEffect + " " + akEffect.GetName() )
    Actor Valdr = GetActorReference()
    Actor Player = Game.GetPlayer()
    if (GetOwningQuest().GetStage() < HealedStage)
        if (akCaster == Player) && (akEffect.HasKeyword(MagicRestoreHealth))
            GetOwningQuest().SetStage(HealedStage)
            Valdr.RestoreActorValue("Health", 75)
        endif
    endif
endEvent
/;


Function InjureValdr()
    ;debug.trace("USKPdunMossMotherValdrAliasScript: InjureValdr")
    Actor Valdr = GetActorReference()
    Valdr.DamageActorValue("Health", Valdr.GetActorValue("Health") - 1)
endFunction

Auto State Injured
    Event OnBeginState()
        InjureValdr()
        RegisterForSingleUpdate(2.0)
    EndEvent

    Event OnUpdate()
        if GetOwningQuest().GetStage() < HealedStage && GetActorReference().GetActorValuePercentage("Health") < 0.8
            RegisterForSingleUpdate(2.0)
        else
            GoToState("Healed")
        endif
    EndEvent
EndState

State Healed
    Event OnBeginState()
        Self.GetActorReference().SetRestrained(False)
    EndEvent
EndState

基本的にバニラではCloakスペルがこのように使われることはないようなので、表に出ることがない不具合です。

対策

まず、OnMagicEffectApplyイベントは使わないようにします。

次に、Cloakでスクリプトを配布する際は、Cloakスペルはアビリティによる常時発動型にはしないで、定期的にCastするようにします。

CloakスペルのMagnitudeを極力小さい値にします。Magnitudeは半径を表しているので、半径が大きいほど影響範囲も大きくなります。

Concentrateスペルでスクリプトを付与するときにランダムに待機時間を入れます。ノータイムでスクリプトを付与すると、Cloakスペルの影響範囲にいるActorに一斉にスクリプトが付与されて稼働を始めます。ランダムに待機時間を入れることで、瞬間的に負荷が増大する時間を作らないようにするのです。

対策 (Papyrus Extender)

Papyrus Extenderには改良版のMagic Effect Apply Exがあります。効果の種類、効果が発動したのかどうかで受け取るイベントを絞り込めるようになっています。Registerして使うため、Unregisterすれば受け取らなくできます。

これを使って、回復魔法のキーワードを持つ魔法が発動した場合に絞り込むことで、大幅な負荷軽減が可能です。

関係のない魔法はイベントが発火しませんので関係ない魔法による無駄な処理が一切なくなります。また、発火した時点でふるい分けが済んでいますので、回復魔法かどうかを確認する処理も不要になります。

対策が必要なMod

常時発動型のCloakスペル(アビリティ)でスクリプト配布を行っているModの一覧です。

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