Jump to content

Scripting, moveto, and AI packages


Recommended Posts

Within a mod for Fallout 4, I have a jail which as twelve jail cells to house prisoners.  These jail cells are identified with xmarkers located at the center of each cell.  These markers are labeled FO4_CellMarker01 through FO4_CellMarker12.  I have created six NPC prisoners which are located in a temporary holding cell called CoveTempHoldingCell.  The six prisoners are labeled NPC_Prisoner01 through NPC_Prisoner06.  I have created a Papyrus script that achieves the following: 1) When the player character enters a certain trigger, the six prisoners are randomly placed into the jail cells; and 2) When the player leaves the trigger, the six prisoners are returned to the temporary holding cell.  In execution, I am noticing a significant dps drop when the player enters the trigger.  Also, the npcs do not appear to be using the packages when placed by the script.  If I hand place the npcs into various jail cells, the npcs use their packages with no issue.  Any insight would be welcomed and appreciated.

================================================================

Scriptname FO4_JailCellScript extends ObjectReference

; Define properties for your NPC prisoners and cell markers
Actor Property FO4_NPC_Prisoner01 Auto
Actor Property FO4_NPC_Prisoner02 Auto
Actor Property FO4_NPC_Prisoner03 Auto
Actor Property FO4_NPC_Prisoner04 Auto
Actor Property FO4_NPC_Prisoner05 Auto
Actor Property FO4_NPC_Prisoner06 Auto
ObjectReference[] Property JailCells Auto
ObjectReference Property CoveTempMarker01 Auto
ObjectReference Property CoveTempMarker02 Auto
ObjectReference Property CoveTempMarker03 Auto
ObjectReference Property CoveTempMarker04 Auto
ObjectReference Property CoveTempMarker05 Auto
ObjectReference Property CoveTempMarker06 Auto

; Define a trigger to detect player entry and exit
Event OnTriggerEnter(ObjectReference akActionRef)
    If akActionRef == Game.GetPlayer()
        PlacePrisoners()
    EndIf
EndEvent

Event OnTriggerLeave(ObjectReference akActionRef)
    If akActionRef == Game.GetPlayer()
        ReturnPrisoners()
    EndIf
EndEvent

; Function to randomly place prisoners in the cells
Function PlacePrisoners()
    ; Initialize the prisoners array
    Actor[] prisoners = new Actor[6]
    prisoners[0] = FO4_NPC_Prisoner01
    prisoners[1] = FO4_NPC_Prisoner02
    prisoners[2] = FO4_NPC_Prisoner03
    prisoners[3] = FO4_NPC_Prisoner04
    prisoners[4] = FO4_NPC_Prisoner05
    prisoners[5] = FO4_NPC_Prisoner06

    ; Array to keep track of used cells
    Int[] usedCells = new Int[0]
    ; Debug.Notification("Placing prisoners...") ; Debug message

    While prisoners.Length > 0
        Int cellIndex = Utility.RandomInt(0, JailCells.Length - 1)
        If usedCells.Find(cellIndex) == -1
            prisoners[0].MoveTo(JailCells[cellIndex])
            prisoners[0].EvaluatePackage() ; Force NPC to evaluate AI packages
            ; Debug.Notification("FO4_JailCellScript: Moved prisoner to cell: " + cellIndex) ; Debug message

            ; Add cell index to usedCells
            Int[] newUsedCells = new Int[usedCells.Length + 1]
            Int i = 0
            While i < usedCells.Length
                newUsedCells = usedCells
                i += 1
            EndWhile
            newUsedCells = cellIndex
            usedCells = newUsedCells

            ; Remove the first prisoner from the array
            Actor[] newPrisoners = new Actor[prisoners.Length - 1]
            i = 1
            While i < prisoners.Length
                newPrisoners[i - 1] = prisoners
                i += 1
            EndWhile
            prisoners = newPrisoners
        EndIf
    EndWhile
EndFunction

; Function to return prisoners to the temporary holding cell
Function ReturnPrisoners()
    FO4_NPC_Prisoner01.MoveTo(CoveTempMarker01)
    FO4_NPC_Prisoner02.MoveTo(CoveTempMarker02)
    FO4_NPC_Prisoner03.MoveTo(CoveTempMarker03)
    FO4_NPC_Prisoner04.MoveTo(CoveTempMarker04)
    FO4_NPC_Prisoner05.MoveTo(CoveTempMarker05)
    FO4_NPC_Prisoner06.MoveTo(CoveTempMarker06)
    ; Debug.Notification("FO4_JailCellScript: Prisoners returned to holding cell.") ; Debug message

EndFunction

 

Link to comment
Share on other sites

I have found that if actors are not 3d loaded after the moveto it can affect AI packages establishing even after forcing EvaluatePackage() 

You may need to add an OnLoad() EvaluatePackage() to their collection to work around that.

Link to comment
Share on other sites

You didn't meantion what kind of AI Package the NPCs should perform (i.e. AI Procedure >>Sit) but you probably want their AnimGraph managers to be initialized (and if Furnitures are involved, theirs too). Is3DLoaded() == True doesn't necessarily mean the AnimGraph is also available, however, after checking Is3DLoaded(), Utility.Wait(0.2) is usually enough for EvaluatePackage() to work as expected. (I added Bool Function HasAnimGraphManager(ObjectReference akReference) native global to Garden of Eden SE in v19.4 if you're interested).

  • Like 1
Link to comment
Share on other sites

The six prisoner NPCs have two simple AI packages: 1) Sandbox package with a 128 radius; and 2) Sleep package.  Inside jail cell is 256 x 256 game units, and contains a chair, a bed, a toilet, and a sink.  I have hand placed a few furniture markers in each of the cells (e.g., NPCNewspaperLeanLeft, etc).  Thanks for the input SKKmods, and LarannKiar, your sage wisdom is invaluable.  

Link to comment
Share on other sites

  • 2 weeks later...

Don’t you just love the brute force approach to scripting.  🙂 Anyways, here is what I have done: 1) Set up a quest; 2) moved the majority of the scripting to the quest; 3) created three scripts two of which reside on the quest while the other one resides on the default trigger.  The AI packaging issue seems to be resolved but there is still a drop in FPS when entering the trigger.  Seems odd for only moving six npcs.  Here are the scripts for referenced.  Still taking a look see at vanilla scripts to (e.g. DLC04MQ05QuestScrip) to see if I can implement the MoveTo better.

Spoiler

Scriptname FO4_PrisonerAIQuestScript extends Quest
; Updated: 09MAR2025:1451

; Properties
ReferenceAlias[] Property PrisonerAliases Auto Mandatory
ReferenceAlias Property CoveReturnMarkerAlias Auto Mandatory
ObjectReference[] Property JailCells Auto Mandatory
ObjectReference Property FO4_PoliceOfficersEnable Auto Mandatory

int PrisonerIndex = 0
bool isReturningPrisoners = false  ; Flag to track prisoner movement state

; Function to shuffle the JailCells array manually
Function ShuffleJailCells()
    int i = JailCells.Length
    while i > 1
        i -= 1
        int j = Utility.RandomInt(0, i)
        ObjectReference temp = JailCells
        JailCells = JailCells[j]
        JailCells[j] = temp
    endwhile
EndFunction

Function PlacePrisoners()
    Debug.Trace("[PrisonerAIQuest] Placing prisoners in jail cells...")

    ShuffleJailCells()  ; Randomize cell assignment

    PrisonerIndex = 0
    isReturningPrisoners = false
    ProcessPrisoners()  ; Start the staggered process
EndFunction

Function ReturnPrisoners()
    Debug.Trace("[PrisonerAIQuest] Returning prisoners to holding area...")

    PrisonerIndex = 0
    isReturningPrisoners = true
    ProcessPrisoners()  ; Start the staggered process
EndFunction

Function ProcessPrisoners()
    while PrisonerIndex < PrisonerAliases.Length
        Actor prisoner = PrisonerAliases[PrisonerIndex].GetActorReference()
        if prisoner
            prisoner.DisableNoWait()
            if isReturningPrisoners
                prisoner.MoveTo(CoveReturnMarkerAlias.GetReference())
            else
                prisoner.MoveTo(JailCells[PrisonerIndex])
            endif
            prisoner.EnableNoWait()
            Debug.Trace("[PrisonerAIQuest] Processed prisoner " + PrisonerIndex)
        endif

        PrisonerIndex += 1
        Utility.Wait(0.5)  ; Stagger movement to prevent FPS drop
    endwhile

    ; **Ensure animation graph is ready before evaluating AI**
    WaitForAnimGraph()

    ; **AI Reset & Package Evaluation**
    EvaluatePrisonersAI()

    ; **Hard Reset AI via Quest Restart**
    ;Self.Stop()
    ;Utility.Wait(1.0)
    ;Self.Start()
EndFunction

Function WaitForAnimGraph()
    Debug.Trace("[PrisonerAIQuest] Waiting for animation graphs to initialize...")
    
    int i = 0
    while i < PrisonerAliases.Length
        Actor prisoner = PrisonerAliases.GetActorReference()
        if prisoner
            int attempts = 0
            while !prisoner.Is3DLoaded() && attempts < 10
                Debug.Trace("[PrisonerAIQuest] Waiting for 3D load on prisoner " + i)
                Utility.Wait(0.5)
                attempts += 1
            endwhile

            if prisoner.Is3DLoaded()
                Debug.Trace("[PrisonerAIQuest] Prisoner " + i + " 3D loaded. Wait a bit more to ensure anim graph is available")
                Utility.Wait(0.25)
                Debug.Trace("[PrisonerAIQuest] Animation Graph is now assumed to be available for " + Prisoner)
            else
                Debug.Trace("[PrisonerAIQuest] Warning: Prisoner " + i + " 3D failed to load!  Animation Graph may NOT be loaded")
            endif
        endif
        i += 1
    endwhile
EndFunction

Function EvaluatePrisonersAI()
    Debug.Trace("[PrisonerAIQuest] Evaluating prisoner AI packages...")

    int i = 0
    while i < PrisonerAliases.Length
        Actor prisoner = PrisonerAliases.GetActorReference()
        if prisoner
            Package currentPackage = prisoner.GetCurrentPackage()
            Debug.Trace("[PrisonerAIQuest] Prisoner " + i + " current package: " + currentPackage)

            if currentPackage == None
                Debug.Trace("[PrisonerAIQuest] Prisoner " + i + " has no active package! Resetting AI.")
                prisoner.EvaluatePackage()
            else
                ; Force AI to "wake up" by temporarily disabling and re-enabling movement
                prisoner.SetRestrained(True)
                Utility.Wait(0.5)
                prisoner.SetRestrained(False)
                prisoner.EvaluatePackage()
            endif
        else
            Debug.Trace("[PrisonerAIQuest] Warning: Prisoner alias " + i + " is empty!")
        endif
        i += 1
    endwhile
EndFunction

; This function is used to enable some extras (police officer npcs)
Function EnablePoliceOfficers()
    FO4_PoliceOfficersEnable.Enable()
EndFunction

; This function is used to disable the extras (police officer npcs)
Function DisablePoliceOfficers()
    FO4_PoliceOfficersEnable.Disable()
EndFunction

Spoiler

Scriptname PrisonerAI_DebugScript extends Quest
; 03MAR2025
; Array of prisoner aliases
ReferenceAlias[] Property PrisonerAliases Auto

Function EvaluatePrisonersAI()
    int prisonerCount = PrisonerAliases.Length
    Debug.Trace("[PrisonerAIDebug] Evaluating AI for " + prisonerCount + " prisoners.")

    int i = 0
    while i < prisonerCount
        ReferenceAlias prisonerAlias = PrisonerAliases
        if prisonerAlias
            Actor prisoner = prisonerAlias.GetActorReference()
            if prisoner
                Debug.Trace("[PrisonerAIDebug] Evaluating prisoner [" + i + "]: ")

                ; Check if 3D is loaded (important for AI reevaluation)
                if prisoner.Is3DLoaded()
                    Debug.Trace("[PrisonerAIDebug] Prisoner [" + i + "] is 3D loaded.")

                    ; Log current package
                    Package currentPackage = prisoner.GetCurrentPackage()
                    if currentPackage
                        Debug.Trace("[PrisonerAIDebug] Prisoner [" + i + "] current package: " + currentPackage)
                    else
                        Debug.Trace("[PrisonerAIDebug] Prisoner [" + i + "] has no active package.")
                    endif

                    ; Force AI reevaluation
                    prisoner.EvaluatePackage()
                    Debug.Trace("[PrisonerAIDebug] Prisoner [" + i + "] AI reevaluated.")
                else
                    Debug.Trace("[PrisonerAIDebug] Prisoner [" + i + "] is NOT 3D loaded. Skipping AI evaluation.")
                endif
            else
                Debug.Trace("[PrisonerAIDebug] Prisoner alias [" + i + "] is empty or invalid.")
            endif
        else
            Debug.Trace("[PrisonerAIDebug] PrisonerAliases[" + i + "] is null.")
        endif
        i += 1
    endwhile
EndFunction

; Automatically trigger AI evaluation when the quest starts
Event OnQuestInit()
    Debug.Trace("[PrisonerAIDebug] PrisonerAI_DebugScript initialized.")
    EvaluatePrisonersAI()
EndEvent

Spoiler

Scriptname FO4_JailCellScript extends ObjectReference
; 09MARCH2025:1508

Quest Property PrisonerAIQuest Auto Mandatory

Event OnTriggerEnter(ObjectReference akActionRef)
    If akActionRef == Game.GetPlayer()
        Debug.Trace("[JailCellTrigger] The player has entered the trigger area")
        (PrisonerAIQuest as FO4_PrisonerAIQuestScript).PlacePrisoners()
        (PrisonerAIQuest as FO4_PrisonerAIQuestScript).EnablePoliceOfficers()
    EndIf
EndEvent

Event OnTriggerLeave(ObjectReference akActionRef)
    If akActionRef == Game.GetPlayer()
        Debug.Trace("[JailCellTrigger] The player has left the trigger area")
        (PrisonerAIQuest as FO4_PrisonerAIQuestScript).DisablePoliceOfficers()
    EndIf
EndEvent

 

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...