エセリウムの冠の謎

「最後に付与された大立石の効果を持続させる。現在の大立石と同時に適用される。」

DLCドーンガードで追加されるアイテム、エセリウムの冠の謎に迫ります。ときどき想定外の動作をするなぁと感じていたので、中身を見てみることにしました。

大立石を記憶させる通常の使用法をご紹介した後、その機能がどうやって実装されているかということを、CKおよびスクリプトの観点から見ていきます。


1. 大立石の効果を記憶させる方法

この記事では、冠に記憶させる大立石の効果を「冠効果」、プレイヤー自身が獲得している効果を「プレイヤー効果」と表記します。冠効果は冠を装備しているときにのみ適用され、装備を外すと冠効果も外されます。

入手直後の初期状態で、確実に冠効果を記憶させる手順は以下の通りです。

  1. 冠効果として記憶させたい大立石へ行き、冠を外したまま効果を獲得する。
  2. 冠を装備し、そのまま一度も外さずに、プレイヤー効果として適用させたい大立石へ行って効果を獲得する。(ここで冠効果が記憶される。)

入手時点で、冠効果に予定している大立石をすでに獲得済みの場合は、手順2のみでOKです。これで、プレイヤー効果をずっと持続させたまま、冠効果を付け外しできるようになります。

もし冠効果の設定に何らかの不具合が起きてしまった場合には、以下の手順で正常な状態に戻すことができます。

  1. 冠効果およびプレイヤー効果以外の大立石へ行き、冠を外してから効果を獲得する。
  2. 冠を一度装備してから外す。(2回以上付け外ししても構わない。)
  3. 上述の手順で記憶させ直す。

冠を所持しているとき、大立石の効果は下図のような順番で移動していきます。

「暗黙保持効果」は次の冠効果候補となるもので、プレイヤーからは見えない形で冠のシステムに保持されています。次の2つのタイミングでプレイヤー効果によって上書きされます。

  • 冠が装備された瞬間
  • 装備中に大立石が変更された後

詳しくは後述しますが、装備中に大立石が変更されると、冠効果が暗黙保持効果で上書きされてから、暗黙保持効果がプレイヤー効果で上書きされます。

この暗黙保持効果の処理というのが独特で、冠を付け外しした回数によって動作が変わるなど、スクリプトを見なければ気付きにくい部分があります。それにより、適当に冠を付け外ししながら冠効果を入れ替えたりしていると、予想と異なる効果が適用されたままうまく外せなくなったりします。

なお、冠を装備していない状態のときは通常何も変更されません。大立石をアクティベートするときに冠を外しておけば、冠効果はそのままでプレイヤー効果だけを変更することもできます。


2. CKにおける冠の構造

2-1. アイテム構造

エセリウムの冠は、Object Window の Armor に DLC1LD_AetherialCrown (FormID: xx00575A) として定義されており、アイテムとしては単なるサークレットです。Enchanting に設定がなされていますが、これは中身のない空効果で作用は何もありません。

2-2. 管理クエスト

冠の管理はクエストを介して行われています。DLC1LD_Postquest (FormID: xx00CFFD) というクエストの中に AetherialCrown というエイリアスがあり、ここに冠が当てはめられています。

2-3. 適用される Perk

エイリアスには DLC1LD_AetherialCrownScript.pex というスクリプトがついていて、冠に関連する機能のほぼすべてを管理しています。スクリプトの内容については後述しますが、この中でイベントが設定されており、装備中だけ DLC1LD_AetherialCrownPerk (FormID: xx00D00A) という Perk が適用されるようになっています。

この Perk は、大立石をアクティベートしたことを検出するために使われています。Perk Entries に項目が1つあり、その中の Target タブに13個の大立石が登録されていて、アクティベートを検出すると冠のスクリプトに通知されるようになっています。

アクティベートを検出したい場合、通常は大立石そのものにスクリプトをつけて行うのが簡単ですが、それだと既存の大立石に変更を加えることになってしまいます。Perk による検出方法を使えば既存オブジェクトを変更しなくて済むので、他の .esp と競合しにくい作りにすることができます。DLC追加アイテムということで、この方式が採用されたものと思われます。

以上で、CKで設定されている大まかな構造は説明できました。次項から、スクリプトに焦点を当てて見ていきたいと思います。


3. 大立石に付いているスクリプト

まず、もともと大立石がどのように処理されているかを見てみます。

大立石には powerShrineScript.pex というスクリプトがついていて、アクティベートされたときに、既存のプレイヤー効果を外して新しいプレイヤー効果を適用します。超単純化して書くと以下の通りです。

EVENT onACTIVATE(OBJECTREFERENCE obj)
    removeSign()
    addSign()
endEVENT

addSign 関数は、単に各大立石ごとに設定された効果をプレイヤーに適用するものです。

その前にある removeSign 関数は、「プレイヤーが何らかの効果を獲得していれば外す」というもので、見習い、精霊、淑女、大公、恋人、魔術師、儀式、大蛇、影、駿馬、盗賊、塔、戦士 の順に判定しています。以下にその一部を単純化して書き出してみます。

FUNCTION removeSign()
    IF(game.getPlayer().hasSpell(pDoomApprenticeAbility))
        game.getPlayer().removeSpell(pDoomApprenticeAbility)
        game.getPlayer().removeSpell(pdoomApprenticeNegativeAbility)
    ELSEIF(game.getPlayer().hasSpell(pDoomAtronachAbility))
        game.getPlayer().removeSpell(pDoomAtronachAbility)
    ; 他の石碑の判定が続く
    ENDIF
endFUNCTION

この判定ではエセリウムの冠は考慮されていません。DLCがない状態でも動作するスクリプトなので当然です。注目すべき点は、条件分岐が ELSEIF で組まれていることです。上述の順番に従って判定し、プレイヤーが獲得している効果があればその1つだけを外す処理です。仮に複数のプレイヤー効果を獲得していたとしても、1回のアクティベートにつき上位の1つしか外されません。


4. 冠効果を管理しているスクリプト

4-1. 効果の管理に用いる変数

管理スクリプトの DLC1LD_AetherialCrownScript.pex のソースコードを見ると、効果に関する重要な変数を2つ持っています。両方とも Int 型の変数で、内部的には効果の種類を整数値で管理しています。

  • currentCrownAbility … 冠効果
  • currentDoomstoneAbility … 暗黙保持効果(=次の冠効果候補)

4-2. 冠効果の値を書き換えるタイミング

冠効果である currentCrownAbility は、次の1か所で暗黙保持効果によって上書きされます。

  • 装備中に大立石が変更された後(RunUpdate 関数から呼ばれる、SelectNewCrownEffect 関数)

4-3. 暗黙保持効果の値を書き換えるタイミング

暗黙保持効果である currentDoomstoneAbility は、次の2か所で現在のプレイヤー効果によって上書きされます。

  • 冠が装備された瞬間(OnEquipped イベント)
  • 装備中に大立石が変更された後(RunUpdate 関数)

2個目は冠効果の書き換えタイミングと近いですが、順番としては冠効果が上書きされてから暗黙保持効果が上書きされます。

これらの処理に関する部分を抜き出して単純化してみると、以下のようになります。

; -------------------------------------------------- 装備したとき
Event OnEquipped(Actor akActor)
    if (akActor == Game.GetPlayer())
        akActor.AddPerk(DLC1LD_AetherialCrownPerk)
        currentDoomstoneAbility = IdentifyCurrentDoomstoneEffect()

        if (currentCrownAbility != currentDoomstoneAbility)
            ApplyCrownEffect(True)
        EndIf
    EndIf
EndEvent

; -------------------------------------------------- 大立石をアクティベートしたとき
Function DoomstoneActivated()
    interactionTimestamp = Utility.GetCurrentRealTime()
    UnregisterForUpdate()
    RegisterForSingleUpdate(0.5)
EndFunction

; -------------------------------------------------- RegisterForSingleUpdate から呼び出し
Event OnUpdate()
    RunUpdate()
EndEvent

; -------------------------------------------------- 効果の適用、あるいは60秒間繰り返し判定
Function RunUpdate()
    int identifiedEffect = IdentifyCurrentDoomstoneEffect()

    if (identifiedEffect > 0)
        if (identifiedEffect != currentDoomstoneAbility)
            ApplyCrownEffect(False)
            SelectNewCrownEffect(currentDoomstoneAbility)
            ApplyCrownEffect(True)
            currentDoomstoneAbility = identifiedEffect
        ElseIf (Utility.GetCurrentRealTime() - interactionTimestamp < 60)
            UnregisterForUpdate()
            RegisterForSingleUpdate(0.5)
        EndIf
    ElseIf (Utility.GetCurrentRealTime() - interactionTimestamp < 60)
        UnregisterForUpdate()
        RegisterForSingleUpdate(0.5)
    EndIf
EndFunction

冠効果を適用したり外したりする ApplyCrownEffect 関数、および冠効果を書き換える SelectNewCrownEffect 関数は素直な処理になっており、想定通りに動作しているはずで特筆すべき点はありません。

4-4. プレイヤー効果を取得する関数

上記のスクリプトに何回か出てくる、現在のプレイヤー効果を取得する関数があります。それが IdentifyCurrentDoomstoneEffect 関数で、思った通りに効果を設定できなくなる要因になっています。

int Function IdentifyCurrentDoomstoneEffect()
    if (Game.GetPlayer().HasSpell(pDoomApprenticeAbility) && currentCrownAbility != 1 && currentDoomstoneAbility != 1)
        return 1
    ElseIf (Game.GetPlayer().HasSpell(pDoomAtronachAbility) && currentCrownAbility != 2 && currentDoomstoneAbility != 2)
        return 2
    ; 他の石碑の判定が続く
    Else
        return 0
    EndIf
EndFunction

ここでは大立石の種類ごとに条件が分岐されており、その順番は大立石のスクリプトと同様に、見習い、精霊、淑女、大公、恋人、魔術師、儀式、大蛇、影、駿馬、盗賊、塔、戦士 となります。次の条件を満たす大立石番号を返し、当てはまるものがなければ0を返します。

  1. プレイヤーがその大立石効果を獲得している。
  2. 冠効果がその大立石効果ではない。
  3. 暗黙保持効果がその大立石効果ではない。

重要なのは3つ目で、プレイヤーがその大立石効果を獲得していても、暗黙保持効果にその効果番号がすでに入っている場合には条件から外れ、結果的に0を返してしまいます。冠を装備するたびに暗黙保持効果がこの返り値で上書きされるため、プレイヤー効果の番号と0とが交互に入ることになります。暗黙保持効果の値が0だと、SelectNewCrownEffect 関数で冠効果が書き換わらないので、入るはずだった冠効果が無視されてしまいます。

この関数の仕様により、プレイヤーによる操作とスクリプトの条件分岐とのパターンがすごく複雑になっています。大立石をアクティベートするときに冠を装備しているかどうかや、アクティベートした順番、そしてその間に冠を付け外しした回数によってパターンが分かれます。これほど複雑だと、スクリプトの整合性を取るのは至難の業です。一番最初に書いた大立石の効果を記憶させる方法は、この複雑さを踏まえた上で、冠の設定をクリアして想定通りの効果を上書きするためのものでした。


5. 同時に3つ以上の効果を獲得する裏技について

5-1. 裏技の方法

エセリウムの冠を高速で付け外しするなどして、同時に3つ以上の効果を獲得する裏技があります。最後にこの話題に触れておきましょう。

例として、大守護石の3つの効果を重複させる方法を挙げます。冠効果やプレイヤー効果に大守護石の3つ以外を獲得している状態から始めてください。冠の装備状態が非常に重要で、間違えると思った通りになりません。

  1. 冠を外して戦士の石碑をアクティベートした後、冠を一度付け外しする。(冠効果をリセットするため)
  2. 冠を外したまま魔術師の石碑をアクティベートする。(プレイヤー効果:魔術師)
  3. 冠を装備してから、盗賊の石碑をアクティベートする。(プレイヤー効果:盗賊、冠効果:魔術師)
  4. 冠を高速で付け外し、冠を装備していないのに盗賊と魔術師の両方を獲得した状態にする。(プレイヤー効果:魔術師+盗賊)
  5. 冠を外したまま戦士の石碑をアクティベートすると、魔術師が外れて戦士を獲得する。(プレイヤー効果:盗賊+戦士)
  6. 冠を装備すると、3つの効果が重複する。(プレイヤー効果:盗賊+戦士、冠効果:魔術師)

5のところでは、状況によって魔術師が外れず、プレイヤー効果に 魔術師+盗賊+戦士 が入ることがあります。加えて、冠効果が魔術師以外のものに書き換わってしまって失敗することもあります。

冠効果に魔術師を入れている理由は、上述したスクリプト内の判定順で魔術師が上位にいるからです。これを13個すべての大立石に拡大して考えると、2のところで見習いの石碑をアクティベートし、3で見習い以外に行って冠効果に見習いを入れ、残る大立石について4と5を繰り返せば、理論的にはすべての効果をプレイヤー効果に重複させることが可能です。見習いは順番の最上位に当たるので、大立石をアクティベートしたときに外される用の効果として利用できます。

留意する点として、この作業をゲーム内でやるにはあまりにも手間がかかりすぎることと、もし重複した効果を外したくなっても通常の手段では困難になることが挙げられます。裏技をやるくらいなら、チート石碑MODを作って設置した方が早いし安全でしょう。

5-2. 高速での付け外しについて

冠を高速で付け外ししたときに冠効果が残る理由は、DLC1LD_AetherialCrownScript にある OnUnequipped イベント、あるいは ApplyCrownEffect 関数がうまく適用されないからだと思われます。

; -------------------------------------------------- 装備を外したとき
Event OnUnequipped(Actor akActor)
    akActor.RemovePerk(DLC1LD_AetherialCrownPerk)
    ApplyCrownEffect(False)
EndEvent

; -------------------------------------------------- 冠効果の適用/取消
Function ApplyCrownEffect(bool shouldAdd)
    if (shouldAdd)
        if (currentSpell1 != None)
            Game.GetPlayer().AddSpell(currentSpell1)
            if (currentSpell2 != None)
                Game.GetPlayer().AddSpell(currentSpell2)
            EndIf
            if (currentPerk != None)
                Game.GetPlayer().AddPerk(currentPerk)
            EndIf
        EndIf
    Else
        if (currentSpell1 != None)
            Game.GetPlayer().RemoveSpell(currentSpell1)
            if (currentSpell2 != None)
                Game.GetPlayer().RemoveSpell(currentSpell2)
            EndIf
            if (currentPerk != None)
                Game.GetPlayer().RemovePerk(currentPerk)
            EndIf
        EndIf
    EndIf
EndFunction

OnUnequipped イベントによって装備を外したことが検出されると、大立石アクティベート検出用の Perk が外された後、冠効果を外す ApplyCrownEffect(False) という関数が実行されます。

想定された動作では、冠を外した際に冠効果が同時に外されます。しかし、高速で冠を付け外ししてスクリプトの処理を短時間で重複させると、その処理がスキップされることがあります。Papyrus はもともと、同一の処理を短時間で重ね合わせるとスキップする仕様になっているようで、簡単な例としてはわざと無限ループ処理をかけてもゲームがフリーズしたり落ちたりはしませんし、ものによってはエラーログすら出ません。この仕様を利用して冠効果を外す処理をスキップさせることで、プレイヤーに効果が残ったままになります。

このとき、大立石アクティベート検出用の Perk も外れずに残ることがあります。そうすると、冠を装備していなくても大立石のアクティベートで DLC1LD_AetherialCrownScript の DoomstoneActivated 関数が実行され、冠効果と暗黙保持効果の書き換えが起こってしまいます。スクリプトのスキップが起こるかどうかは運ですから、裏技で13個全部の大立石を重複させるのはかなり大変だと思います。

以上で終わります。条件分岐の全パターンを検証したわけではないので、もしかしたら間違っているところもあるかもしれません。が、あまりにもパターンが多すぎて検証しきれませんし、正しい使い方は最初に示した通りで確立できているので、これで良しとすることにします。エセリウムの冠は、用法・用量を守って正しくお使いください。

Leave a comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

four × 2 =