ダンジョンを攻略する冒険者を追加する

Modを作ろう

スカイリムは基本的にプレイヤーひとりでダンジョンを攻略します。あるいはフォロワーを引き連れて行くこともできますが、行動は常にプレイヤーの付き添いです。あるクエストを進めて行くと、仲間と一緒に団体でダンジョンを探索することがあります。これが楽しいので、普段からこういうことが出来るModを作ってみました。

やりたいこと

  • 朝になると宿屋の前に冒険者たちが集まる。
  • プレイヤーが近づくか時間が経つと、ダンジョンの近くまで移動する。
  • プレイヤーが近づくとダンジョンに入る。入ってすぐのところで待機している。
  • プレイヤーがダンジョンに入ると、奥へ進攻を始める。
  • ダンジョンの奥に到達すると、そこでしばらくうろつく。
  • 夜になると外に出て街に戻る。
  • 深夜になると寝る。

クエストを作る

CKを起動したら、まずはQuestを新規作成します。

名前はaeQuestAdventurerとしました。何でもいいのですが、aから始まる名前にすると一覧で上の方に出てくるので、CKでの作業が格段に楽になります。aaaQuestでもいいです。長くし過ぎるとゲーム中にコンソールで入力するときに面倒なので、意味がわかる範囲でそこそこの長さにしておきましょう。

IDを決めたらOKを押して閉じます。すぐにSaveでespファイルを保存してください。いつCKが落ちてもいいように、とにかく常に保存するクセをつけることをオススメします。

ファクションを作る

Factionを3つ作ります。

次のようにしました。

名前用途
aeFactionAdvDiscoveringダンジョンを侵攻中。
aeFactionAdvGoalダンジョンの侵攻がひと段落してうろついている。
aeFactionAdvSleeping寝ている。

名前は付けても付けなくてもいいです。付けておくとコンソール(MFG Fixあり)で確認するときに楽なのですが、Factionの表示が更新されないことがあるようなので、どちらでもいいです。

マーカーを作る

マーカーはObject WindowのStaticにあります。Filterにmarkerと入れると絞り込めるので探すのが楽です。

このXMarkerを使います。これは方向指定なしバージョンです。

Cell ViewからaaaMarkersを開きます。Render Windowのどこでもいいので、XMarkerをドラッグ&ドロップして、セルにマーカーを新規作成します。

するとCell Viewにマーカーが表示されますので、右クリックしてEditを選びます。

Reference Editor IDのところに名前を入れます。名前を付けるとスクリプトと紐づけるのが楽になります。今回はaeAdventurerMarkerとしました。それからRespawnsのチェックを外して、セルリセット時に初期化されないようにしました。

パッケージを作る

Packageを4つ作ります。

PackageのTemplateはSleepingだけSleepで、あとはSandboxです。

4つとも、Locationの指定をaeAdventurerMarkerにしましょう。

このようにNear Referenceを選び、CellからaaaMarkersを選んで、Refからマーカーを探します。

Conditionsは「Factionに属しているとき」とします。WanderingだけConditionsはなしです。

同じ要領で4つとも作ります。Flagsはそれぞれ相応しいものにチェックをします。待機中(Wandering)は会話ありの納刀で、進攻中(Discovering)は会話なしの抜刀がいいでしょう。

フォームリストを作る

FormListを以下のように作ります。

名前用途
aeLocationList地域を特定する。
aeInnEntranceList宿屋前を指定する。
aeLandingPointListダンジョン前の待機場所を指定する。
aeDungeonStartListダンジョンに入ってすぐの場所を指定する。
aeDungeonRouteListダンジョンの中間あたりの場所を指定する。
aeDungeonGoalListダンジョンの探索完了場所を指定する。
aeSleepPointList寝る場所を指定する。

FormListを作ったら、まずaeLocationListにLocationを入れます。

LocationはRiverwoodLocationにしました。リバーウッド周辺です。FormListのダイアログを開いておいて、Locationをドラッグ&ドロップです。

aeInnEntranceListにCell Viewからオブジェクトを選んで入れていきます。

例えば宿屋前はMQ201DelphineRiverwoodStartMarkerが使えそうです。黄色い丸で囲ってあるマーカーです。こんな感じで大抵使えるものがありますので、新たにマーカーを置く必要はありません。Render Windowでクリックしておくと、Cell Viewの方も選択状態になりますので、それをFormListにドラッグ&ドロップです。

残りのFormListは、近くにあるエンバーシャード鉱山を使います。鉱山前、入ってすぐ、中間地点、ゴールを指定します。中間地点のaeDungeonRouteListはエンバーシャード鉱山の中の橋の手前を指定します。ここはプレイヤーが橋を降ろさないと先に進めず、移動先に奥の場所を指定するとワープしてしまうからです。

寝る場所はリバーウッドの入り口にある焚火にしました。

リファレンスエイリアスを作る

Questを開き、ReferenceAliasを作ります。

Alias NameはAdventurer01としました。Fill TypeはSpecific Referenceにしておき、空のままでいいです。

フラグはOptionalにチェックを入れておきます。チェックを入れておかないと、空のReferenceAliasが存在する間はQuestをスタートできません。

Alias Package Dataに4つのPackageを置きます。Wanderingが一番下になるように置きましょう。

ここまで出来たらOKを押して閉じます。右クリックしてDuplicateを選ぶと複製されるので、Alias Nameだけをかえていきます。

これが冒険者になります。ReferenceAliasの数が冒険者の人数になりますので、好きなように調整しましょう。Adventurer01からAdventurer04まで4つ作りました。

スクリプトを書く

以下のスクリプトを書いて、Questに紐づけます。

「冒険者を追加しよう」のサンプル (papyrus)

ScriptName aeQuestAdventurerScript Extends Quest

ReferenceAlias[] Property Adventurers Auto

ObjectReference Property WIDeadBodyCleanupCellMarker Auto
ObjectReference Property aeAdventurerMarker Auto

FormList Property aeLocationList Auto
FormList Property aeInnEntranceList Auto
FormList Property aeLandingPointList Auto
FormList Property aeDungeonStartList Auto
FormList Property aeDungeonRouteList Auto
FormList Property aeDungeonGoalList Auto
FormList Property aeSleepPointList Auto

Faction Property aeFactionAdvDiscovering Auto
Faction Property aeFactionAdvGoal Auto
Faction Property aeFactionAdvSleeping Auto

Actor Property PlayerRef Auto

Int CurrentLocation = -1
Int NextLocation = -1


; 冒険者にFactionを追加する
Function AddToFaction(Faction akFaction)
    Int i = Adventurers.Length

    while i > 0
        i -= 1

        Actor kActor = Adventurers[i].GetActorReference()

        if kActor
            kActor.AddToFaction(akFaction)
            kActor.EvaluatePackage()
        endif
    endwhile
EndFunction


; 現在時刻を取得する
Float Function GetCurrentHourOfDay()
    Float fTime = Utility.GetCurrentGameTime()
    fTime -= Math.Floor(fTime)
    fTime *= 24.0

    return fTime
EndFunction


; 目標の時刻までの時間を求める
Float Function GetNextHourInterval(Float afHour, Bool abAddRandom = false)
    Float fInterval = 24.0 + afHour - GetCurrentHourOfDay()

    ; 25時間以上先になっていたら調整
    ; ただし1時間以上先にする
    ; https://www.creationkit.com/index.php?title=RegisterForSingleUpdateGameTime_-_Form
    if 25.0 0
        i -= 1

        Actor kActor = Adventurers[i].GetActorReference()

        if kActor
            kActor.RemoveFromFaction(akFaction)
            kActor.EvaluatePackage()
        endif
    endwhile
EndFunction


; 冒険者を出現させる
Function SpawnAdventurer()
    Faction kEEFactionForceEquipArmor

    if Game.GetModByName("EnemyEquipment.esp") 0
        i -= 1

        Actor kActor = Adventurers[i].GetActorReference()

        if kActor
            kActor.Delete()
            Adventurers[i].Clear()
        endif

        kActor = WIDeadBodyCleanupCellMarker.PlaceActorAtMe(kActorForm as ActorBase)
        Adventurers[i].ForceRefTo(kActor)

        if kEEFactionForceEquipArmor
            kActor.AddToFaction(kEEFactionForceEquipArmor)
        endif

        kActor.MoveTo(aeAdventurerMarker)
    endwhile
EndFunction


; 現在地域を更新する
Function UpdateLocation()
    Location kLoc = PlayerRef.GetCurrentLocation()

    if kLoc
        Int i = aeLocationList.Find(kLoc)

        if i != -1
            NextLocation = i
            Debug.Trace("AE: new location = " + kLoc)
        endif
    endif

EndFunction


Event OnInit()
    UpdateLocation()
    GoToState("WAITING")
EndEvent


State WAITING
    Event OnBeginState()
        Float fInterval = GetNextHourInterval(8.0, abAddRandom = true)
        RegisterForSingleUpdateGameTime(fInterval)
        Debug.Trace("AE: [WAITING] OnBeginState: interval = " + fInterval)
    EndEvent


    Event OnUpdateGameTime()
        Debug.Trace("AE: [WAITING] OnUpdateGameTime: next location = " + NextLocation)

        if NextLocation == -1
            RegisterForSingleUpdateGameTime( GetNextHourInterval(8.0, abAddRandom = true) )
            return
        endif

        CurrentLocation = NextLocation
        aeAdventurerMarker.MoveTo(aeInnEntranceList.GetAt(CurrentLocation) as ObjectReference)
        SpawnAdventurer()
        GoToState("READY")
    EndEvent
EndState


State READY
    Event OnBeginState()
        Debug.Trace("AE: [READY] OnBeginState")
        RegisterForSingleUpdate(30.0)
    EndEvent


    Event OnUpdate()
        Float fHour = GetCurrentHourOfDay()

        if 10.0 < fHour || PlayerRef.GetDistance(aeAdventurerMarker) < 1000.0
            GoToState("LANDING")
            return
        endif

        if 12.0 < fHour
            GoToState("WAITING")
        else
            RegisterForSingleUpdate(30.0)
        endif
    EndEvent
EndState


State LANDING
    Event OnBeginState()
        Debug.Trace("AE: [LANDING] OnBeginState")
        aeAdventurerMarker.MoveTo(aeLandingPointList.GetAt(CurrentLocation) as ObjectReference)
        RegisterForSingleUpdate(30.0)
    EndEvent


    Event OnUpdate()
        if PlayerRef.GetDistance(aeAdventurerMarker) < 1000.0
            GoToState("ENTERING")
            return
        endif

        if 15.0 < GetCurrentHourOfDay()
            GoToState("RESTING")
        else
            RegisterForSingleUpdate(30.0)
        endif
    EndEvent
EndState


State ENTERING
    Event OnBeginState()
        Debug.Trace("AE: [ENTERING] OnBeginState")
        aeAdventurerMarker.MoveTo(aeDungeonStartList.GetAt(CurrentLocation) as ObjectReference)
        AddToFaction(aeFactionAdvDiscovering)
        RegisterForSingleUpdate(30.0)
    EndEvent


    Event OnUpdate()
        if PlayerRef.GetDistance(aeAdventurerMarker) < 1000.0
            GoToState("DISCOVERING1")
            return
        endif

        if 17.0 < GetCurrentHourOfDay()
            GoToState("RESTING")
        else
            RegisterForSingleUpdate(30.0)
        endif
    EndEvent
EndState


State DISCOVERING1
    Event OnBeginState()
        Debug.Trace("AE: [DISCOVERING1] OnBeginState")
        aeAdventurerMarker.MoveTo(aeDungeonRouteList.GetAt(CurrentLocation) as ObjectReference)
        RegisterForSingleUpdate(30.0)
    EndEvent


    Event OnUpdate()
        if PlayerRef.GetDistance(aeAdventurerMarker) < 1000.0
            GoToState("DISCOVERING2")
            return
        endif

        if 19.0 < GetCurrentHourOfDay()
            GoToState("RESTING")
        else
            RegisterForSingleUpdate(30.0)
        endif
    EndEvent
EndState


State DISCOVERING2
    Event OnBeginState()
        Debug.Trace("AE: [DISCOVERING2] OnBeginState")
        aeAdventurerMarker.MoveTo(aeDungeonGoalList.GetAt(CurrentLocation) as ObjectReference)
        RegisterForSingleUpdate(30.0)
    EndEvent


    Event OnUpdate()
        if PlayerRef.GetDistance(aeAdventurerMarker) < 1000.0
            GoToState("GOAL")
            return
        endif

        if 19.0 < GetCurrentHourOfDay()
            GoToState("RESTING")
        else
            RegisterForSingleUpdate(30.0)
        endif
    EndEvent
EndState


State GOAL
    Event OnBeginState()
        Debug.Trace("AE: [GOAL] OnBeginState")
        RemoveFromFaction(aeFactionAdvDiscovering)
        AddToFaction(aeFactionAdvGoal)
        RegisterForSingleUpdate(30.0)
    EndEvent


    Event OnUpdate()
        if 19.0 < GetCurrentHourOfDay()
            GoToState("RESTING")
        else
            RegisterForSingleUpdate(30.0)
        endif
    EndEvent
EndState


State RESTING
    Event OnBeginState()
        Debug.Trace("AE: [RESTING] OnBeginState")
        aeAdventurerMarker.MoveTo(aeSleepPointList.GetAt(CurrentLocation) as ObjectReference)
        RemoveFromFaction(aeFactionAdvGoal)
        RemoveFromFaction(aeFactionAdvDiscovering)
        RegisterForSingleUpdate(30.0)
    EndEvent


    Event OnUpdate()
        Float fTime = GetCurrentHourOfDay()

        if 23.0 < fTime || fTime < 4.0
            GoToState("SLEEPING")
        elseif 4.0 < fTime && fTime < 15.0
            GoToState("WAITING")
        else
            RegisterForSingleUpdate(30.0)
        endif
    EndEvent
EndState


State SLEEPING
    Event OnBeginState()
        Debug.Trace("AE: [SLEEPING] OnBeginState")
        AddToFaction(aeFactionAdvSleeping)
        RegisterForSingleUpdate(5.0)
    EndEvent


    Event OnUpdate()
        Float fTime = GetCurrentHourOfDay()

        if 5.0 < fTime && fTime < 23.0
            GoToState("WAITING")
        else
            RegisterForSingleUpdate(5.0)
        endif
    EndEvent
EndState

プロパティも紐づけます。名前が適切であれば一発でAlias以外は入ります。

Aliasだけは配列なので手作業で入れていきます。

Addを選んだらプルダウンリストから選ぶだけなので楽でしょう。

遊び方

朝の8時までにリバーウッドに行きます。8時すぎ(8時から9時までランダム)に冒険者が現れます。見ているか近づくかすると移動を始めますので、付いていきましょう。

エンバーシャード前で少し立ち止まるので、そこで様子を見ていると中に入ります。中に入ったら一緒に戦いましょう。

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