食べ物が腐るようにする

Mod作成

Horizon+サバイバルモードの環境は、物資に乏しい序盤が最もつらく、余裕が出てくる中盤以降は緊張感にかけてきます。特に肉類は無限に手に入るため、戦前の食べ物が不要となり、発見しても有り難みがなくなってしまいます。

そこで、時間経過で肉類が腐るようにしてみました。

サンプルソースコード

FoodManagerScript.psc (papyrus)

Scriptname MyTweak:FoodManagerScript Extends Quest

Keyword Property ObjectTypeFoodRawMeat Auto Const
Form Property RottenFood_DiseasedMeat Auto Const
Container Property WorkshopWorkbench Auto Const
ActorValue Property LastCheckedGameTime Auto Const

Actor PlayerRef

Int CheckFoodInterval = 6 ; 6 hour
Float DestoryPercentage = 0.5

; OnTimerGameTime
Int TimerCheckFood = 1 Const

; OnTimer
Int TimerCheckWorkBench = 1 Const


Event OnQuestInit()
    PlayerRef = Game.GetPlayer()
    RegisterForRemoteEvent(PlayerRef, "OnItemAdded")
    RegisterForRemoteEvent(PlayerRef, "OnLocationChange")
    AddInventoryEventFilter(ObjectTypeFoodRawMeat)
    StartTimerGameTime(CheckFoodInterval, TimerCheckFood)
EndEvent


Event OnTimer(Int aiTimerID)
    if Utility.IsInMenuMode()
        StartTimer(1.0, TimerCheckWorkBench)
        return
    endif

    CheckWorkBench()
EndEvent


Event OnTimerGameTime(Int aiTimerID)
    if Utility.IsInMenuMode()
        StartTimerGameTime(0.1, TimerCheckFood)
        return
    endif

    CheckFood(PlayerRef, DestoryPercentage)
    StartTimerGameTime(CheckFoodInterval, TimerCheckFood)
EndEvent


Event Actor.OnLocationChange(Actor akActorRef, Location akOldLoc, Location akNewLoc)
    CancelTimer(TimerCheckWorkBench)
    StartTimer(10.0, TimerCheckWorkBench)
EndEvent


Event ObjectReference.OnItemAdded(ObjectReference akSender, Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)
    ;Debug.Trace(akBaseItem + " from " + akSourceContainer)

    if !(akSourceContainer as Actor)
        if Utility.RandomFloat() < DestoryPercentage
            akSender.RemoveItem(akBaseItem, aiItemCount, abSilent = true)
            akSender.AddItem(RottenFood_DiseasedMeat, aiItemCount, abSilent = true)
            Debug.Notification(akBaseItem.GetName() + "は腐っている")
        endif
    endif
EndEvent


Function CheckFood(ObjectReference akTarget, float fPercentage)
    Form[] kItemList = akTarget.GetInventoryItems()
    Int i = kItemList.Length

    while i > 0
        i -= 1

        if kItemList[i].HasKeyword(ObjectTypeFoodRawMeat)
            if Utility.RandomFloat() < fPercentage
                Int c = akTarget.GetItemCount(kItemList[i])
                akTarget.RemoveItem(kItemList[i], c, abSilent = true)
                akTarget.AddItem(RottenFood_DiseasedMeat, c, abSilent = true)
                Debug.Notification(kItemList[i].GetName() + "は腐った")
            endif
        endif
    endwhile
EndFunction


Function CheckWorkBench()
    ObjectReference kWorkBench = Game.FindClosestReferenceOfTypeFromRef(WorkshopWorkbench, PlayerRef, 5000.0)

    if !kWorkBench
        return
    endif

    float fLastCheckedGameTime = kWorkBench.GetValue(LastCheckedGameTime)
    float fCurrentGameTime = Utility.GetCurrentGameTime()
    float fPassTime = fCurrentGameTime - fLastCheckedGameTime

    Debug.Trace(kWorkBench + " last=" + fLastCheckedGameTime + " now=" + fCurrentGameTime + " pass=" + fPassTime)

    float fInterval = (1.0 / 24.0) * 6.0

    if fPassTime < fInterval
        return
    endif

    float fPercentage = 1.0 - DestoryPercentage
    fPercentage = 1.0 - Math.pow( fPercentage, Math.Floor(fPassTime / fInterval) )
    Debug.Trace("interval=" + fInterval + " percentage=" + fPercentage)

    CheckFood(kWorkBench, fPercentage)
    kWorkBench.SetValue(LastCheckedGameTime, fCurrentGameTime)
EndFunction

腐る仕組みをどのように実装するか

SkyrimではEquipment ManagerというModを作成し、厳密な消費期限を設定して実現していました。より現実感が増すという利点がありますが、処理が複雑になり負荷が増すという問題もあります。

今回は、処理を省いて手を抜くことで、できるだけ動作がシンプルになる実装を考えてみました。

肉類を入手した時に腐らせる

プレイヤーのOnItemAddedイベントを捕捉します。AddInventoryEventFilterでフィルタを使って肉類だけに限定して負荷を下げます。

Horizonでは生肉にObjectTypeFoodRawMeatというキーワードが設定されていますので、これがそのまま使えます。

OnItemAddedのakSourceContainer引数を検査して、コンテナから入手した場合に限り50%で腐らせます。これは、Actorから入手したときに腐らせないためです。

つまり、厳密な消費期限を設定するのではなくランダムに腐るようにすることで、食べ物の一覧や消費期限の保持をしなくて済むようにしています。

プレイヤーの所持品にある肉類を一定間隔で腐らせる

StartTimerGameTimeを使ってゲーム内の6時間おきに所持品を検査して、生肉を50%で腐らせます。

タイミングによっては生肉を入手してすぐに腐ることもありますが、これを何とかしようとすると複雑になってしまうため、対策はせずに手を抜きます。

ワークベンチに入っている肉類を一定間隔で腐らせる

一番近くにあるワークベンチを検索して、生肉を50%で腐らせます。

一番のポイントは、検査するタイミングです。一定間隔だと無駄なスクリプト稼働が増えるので、できるだけ簡略化しつつも十分なタイミングで検査する必要があります。

プレイヤーのOnLocationChangeイベントをきっかけに検査するようにします。拠点に進入したタイミングで1回だけ検査すれば十分です。

いきなり検査を始めるのではなく、StartTimerを使って10秒後に検査するようにしています。これは、場合によってはOnLocationChangeイベントが短時間に連発することがあるからです。

Fallout 4ではActor Valueを新規に作成できるようになりました。また、Actor ValueといいつつもObjectReferenceにも持たせることができます。厳密にはActor ValueではなくてValueになったわけです。

LastCheckedGameTimeというActor Valueを作成します。これは最後に検査したゲーム内時刻です。初期値は0にしておきます。これをワークベンチに設定します。そして、最後に検査してから6時間以上が経過していたら再検査するようにします。はじめて検出したワークベンチは0なので、必ず検査をすることになります。検査が終わったらLastCheckedGameTimeに現在時刻を設定します。

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