Jump to content

Understanding dynamic array correctly


SMB92

Recommended Posts

Thanks very much for your help so far BnF, would have still been in the stone age without ya. I'm gonna go do some serious play with it now and start getting things polished/set up.

 

@kitcat81 - thanks for all your help so far as well :) For the random spawn markers, I'd prefer they just run the spawn() function themselves, they fire on a chance variable so not all of them will spawn something. But the code you mentioned there is exactly how I want the Ambush type points to work. There will be more functions and values stored in the main quests relative to each type/system so I'm not really wasting them on just storing arrays. Or maybe it is just better to have the function in the quest, I'm not sure if there is any difference or if I'd need to code it differently to make the SpawnEnemyActors() function in the local script work properly

Link to comment
Share on other sites

  • Replies 111
  • Created
  • Last Reply

Top Posters In This Topic

 

Done everything like you mentioned and finally, it compiles (Ship It™)

 

 

 

Scriptname ASC_Random extends ObjectReference

import ASC_MainStruct

ASC_MasterRandomQuestScript Property ASC_MasterRandomQuest Auto
GlobalVariable Property ASC_Main_ModEnabled Auto Const
GlobalVariable Property ASC_Main_RandomEnabled_R1 Auto Const
GlobalVariable Property ASC_Main_Random_Chance_R1 Auto Const
GlobalVariable Property ASC_Main_Random_DisableOnBlock_R1 Auto Const
FormList Property ASC_ResetList_R1 Auto Const
Actor Property PlayerRef Auto Const
ObjectReference Property PatrolMarker Auto Const
GlobalVariable Property ASC_Main_Difficulty_R1 Auto Const
GlobalVariable Property ASC_Reroll_Main_Chance_R1 Auto Const
GlobalVariable Property ASC_Reroll_Main_R1 Auto Const
GlobalVariable Property ASC_DeleteTimer_R1 Auto Const

bool Rerolled = false
Actor[] GroupList

Function Spawn()
    int iNumSpawnTypes = (ASC_MasterRandomQuest as ASC_MasterRandomQuestScript).ActorTypes.Length ; how many types of spawning actors can we support
    int iWhoToSpawn = Utility.RandomInt(1,iNumSpawnTypes) ; changed to use the size of our array of actor types
    ActorTypeStruct spawnDetails = (ASC_MasterRandomQuest as ASC_MasterRandomQuestScript).ActorTypes[iWhoToSpawn]
    if (spawnDetails.ASC_Allowed.GetValueInt() == 1)
        SpawnEnemyActors(spawnDetails.ASC_Max_Allowed.GetValueInt(), spawnDetails.ASC_Chance.GetValueInt(), spawnDetails.LvlActorBase, spawnDetails.ASC_Allowed_Boss.GetValue() as Bool, spawnDetails.ASC_Max_Allowed_Boss.GetValueInt(), spawnDetails.ASC_Chance_Boss.GetValueInt(), spawnDetails.LvlActorBossBase)
    elseif (spawnDetails.ASC_Allowed.GetValueInt() == 0) && (spawnDetails.ASC_Reroll_Allowed.GetValueInt() == 1)
        RerollCheck(spawnDetails.ASC_Reroll_Chance.GetValueInt())
    ;else
        ; should there be something to handle a case where we're not allowed and have no reroll allowance? the way this is right now, if both were 0 then nothing would get spawned and the script stalls until the cell unloads and the spawn is called again on the new cell load.
    endif
EndFunction

Function SpawnEnemyActors(int iMaxSpawnCount, int iChance, ActorBase varBaseActor, bool bBossAllowed, int iMaxBossCount, int iBossChance, ActorBase varBossActor)
    int Difficulty = ASC_Main_Difficulty_R1.GetValueInt()
    int iSpawnCounter = iMaxSpawnCount
	int iPosition = 0
    bool bSpawnSuccess = false
    GroupList = new Actor[0] ; since we don't have 100% chance of spawning actors, we need this to be an accumulating array
    Debug.Notification("Spawning enemies") ; unless we add a function parameter for text this had to be changed to be something generic
        while (iSpawnCounter < iMaxSpawnCount)
            if (Utility.RandomInt(1,100) <= iChance)
                GroupList.Add(Self.PlaceActorAtMe(varBaseActor, Utility.RandomInt(1,Difficulty), None))
                iPosition = GroupList.Length - 1
                GroupList[iPosition].SetLinkedRef(PatrolMarker)
                Debug.Notification("Array Add Success. Actor count now: " + iPosition)
                bSpawnSuccess = True
            endif
            iSpawnCounter += 1
        endwhile
        if bBossAllowed == 1
            iSpawnCounter = 0 ; reuse var. memory efficient
            while (iSpawnCounter < iMaxBossCount)
                if (Utility.RandomInt(1,100) <= iBossChance)
                    GroupList.Add(Self.PlaceActorAtMe(varBossActor, Utility.RandomInt(1,Difficulty), None))
                    iPosition = GroupList.Length - 1
                    GroupList[iPosition].SetLinkedRef(PatrolMarker)
                    Debug.Notification("Array Add Success. Actor count now: " + iPosition)
                    bSpawnSuccess = True
                endif
                iSpawnCounter += 1
            endwhile
        endif
EndFunction

Function RerollCheck(int iRerollChance)
    if (Rerolled == false) && (Utility.RandomInt(1,100) <= iRerollChance)
        Spawn()
        Rerolled = true
        Debug.Notification("Rerolling after block")
    else
        Debug.Notification("Reroll on block denied")
    endif
EndFunction

 

 

 

EDIT: Confirming that the array is no longer a property on the marker script!

 

Just thought about your mention to move SpawnEnemyActors to the quest script. While you can do that, as it is it doesn't really matter either way. You'd need to make sure to add an argument to the function so you can pass the marker ObjectReference. Then in the SpawnEnemyActors function change the self in the uses of self.PlaceActorAtMe to the argument variable.

 

My problem with that is, I want to store the array of spawned actors on the local script, so I can clean them up or do other things with them later. As mentioned above though, if it is better to have the Spawn() function on the Quest, I could do that, but I'm not sure what changes would have to be made for the SpawnEnemyActors function on the local script so that all the parameters etc would still be passed etc, or maybe I don't have to do anything at all?

 

So visually (because I'm crap at explaining things lol) the script would look like this?

 

 

Scriptname ASC_Random extends ObjectReference

import ASC_MainStruct
import ASC_MasterRandomQuestScript

ASC_MasterRandomQuestScript Property ASC_MasterRandomQuest Auto
GlobalVariable Property ASC_Main_ModEnabled Auto Const
GlobalVariable Property ASC_Main_RandomEnabled_R1 Auto Const
GlobalVariable Property ASC_Main_Random_Chance_R1 Auto Const
GlobalVariable Property ASC_Main_Random_DisableOnBlock_R1 Auto Const
FormList Property ASC_ResetList_R1 Auto Const
Actor Property PlayerRef Auto Const
ObjectReference Property PatrolMarker Auto Const
GlobalVariable Property ASC_Main_Difficulty_R1 Auto Const
GlobalVariable Property ASC_Reroll_Main_Chance_R1 Auto Const
GlobalVariable Property ASC_Reroll_Main_R1 Auto Const
GlobalVariable Property ASC_DeleteTimer_R1 Auto Const

bool Rerolled = false
Actor[] GroupList

Event OnCellAttach()
	SpawnPointLoaded = true
	if (Self.IsDisabled() == false) && (ASC_Main_ModEnabled.GetValueInt() == 1) && (ASC_Main_RandomEnabled_R1.GetValueInt() == 1)
		Spawn()
	endif
EndEvent

Function SpawnEnemyActors(int iMaxSpawnCount, int iChance, ActorBase varBaseActor, bool bBossAllowed, int iMaxBossCount, int iBossChance, ActorBase varBossActor)
    int Difficulty = ASC_Main_Difficulty_R1.GetValueInt()
    int iSpawnCounter = iMaxSpawnCount
	int iPosition = 0
    bool bSpawnSuccess = false
    GroupList = new Actor[0] ; since we don't have 100% chance of spawning actors, we need this to be an accumulating array
    Debug.Notification("Spawning enemies") ; unless we add a function parameter for text this had to be changed to be something generic
        while (iSpawnCounter < iMaxSpawnCount)
            if (Utility.RandomInt(1,100) <= iChance)
                GroupList.Add(Self.PlaceActorAtMe(varBaseActor, Utility.RandomInt(1,Difficulty), None))
                iPosition = GroupList.Length - 1
                GroupList[iPosition].SetLinkedRef(PatrolMarker)
                Debug.Notification("Array Add Success. Actor count now: " + iPosition)
                bSpawnSuccess = True
            endif
            iSpawnCounter += 1
        endwhile
        if bBossAllowed == 1
            iSpawnCounter = 0 ; reuse var. memory efficient
            while (iSpawnCounter < iMaxBossCount)
                if (Utility.RandomInt(1,100) <= iBossChance)
                    GroupList.Add(Self.PlaceActorAtMe(varBossActor, Utility.RandomInt(1,Difficulty), None))
                    iPosition = GroupList.Length - 1
                    GroupList[iPosition].SetLinkedRef(PatrolMarker)
                    Debug.Notification("Array Add Success. Actor count now: " + iPosition)
                    bSpawnSuccess = True
                endif
                iSpawnCounter += 1
            endwhile
        endif
EndFunction

Function RerollCheck(int iRerollChance)
    if (Rerolled == false) && (Utility.RandomInt(1,100) <= iRerollChance)
        Spawn()
        Rerolled = true
        Debug.Notification("Rerolling after block")
    else
        Debug.Notification("Reroll on block denied")
    endif
EndFunction

 

 

 

And the quest like this

 

 

Scriptname ASC_MasterRandomQuestScript extends Quest

Import ASC_MainStruct

ActorTypeStruct[] Property ActorTypes Auto Const

Function Spawn()
    int iNumSpawnTypes = (ASC_MasterRandomQuest as ASC_MasterRandomQuestScript).ActorTypes.Length ; how many types of spawning actors can we support
    int iWhoToSpawn = Utility.RandomInt(1,iNumSpawnTypes) ; changed to use the size of our array of actor types
    ActorTypeStruct spawnDetails = (ASC_MasterRandomQuest as ASC_MasterRandomQuestScript).ActorTypes[iWhoToSpawn]
    if (spawnDetails.ASC_Allowed.GetValueInt() == 1)
        SpawnEnemyActors(spawnDetails.ASC_Max_Allowed.GetValueInt(), spawnDetails.ASC_Chance.GetValueInt(), spawnDetails.LvlActorBase, spawnDetails.ASC_Allowed_Boss.GetValue() as Bool, spawnDetails.ASC_Max_Allowed_Boss.GetValueInt(), spawnDetails.ASC_Chance_Boss.GetValueInt(), spawnDetails.LvlActorBossBase)
    elseif (spawnDetails.ASC_Allowed.GetValueInt() == 0) && (spawnDetails.ASC_Reroll_Allowed.GetValueInt() == 1)
        RerollCheck(spawnDetails.ASC_Reroll_Chance.GetValueInt())
    ;else
        ; should there be something to handle a case where we're not allowed and have no reroll allowance? the way this is right now, if both were 0 then nothing would get spawned and the script stalls until the cell unloads and the spawn is called again on the new cell load.
    endif
EndFunction

 

 

 

If this just works like this than that's great, but the wikis give me the impression I need to do something else for everything to work properly?

Link to comment
Share on other sites

Thanks very much for your help so far BnF, would have still been in the stone age without ya. I'm gonna go do some serious play with it now and start getting things polished/set up.

 

@kitcat81 - thanks for all your help so far as well :smile: For the random spawn markers, I'd prefer they just run the spawn() function themselves, they fire on a chance variable so not all of them will spawn something. But the code you mentioned there is exactly how I want the Ambush type points to work. There will be more functions and values stored in the main quests relative to each type/system so I'm not really wasting them on just storing arrays. Or maybe it is just better to have the function in the quest, I'm not sure if there is any difference or if I'd need to code it differently to make the SpawnEnemyActors() function in the local script work properly

Not at all! ;) You just mentioned that filling properties on every marker and changing them in case of an update can be a problem. In this case you move all the functionality and properties to the quest and the marker only has to detect the right moment and to run the spawn function that is defined in the quest. So you can change any property or function later and this won`t break anything.

Edited by kitcat81
Link to comment
Share on other sites

 

Not at all! ;) You just mentioned that filling properties on every marker and changing them in case of an update can be a problem. In this case you move all the functionality and properties to the quest and the marker only has to detect the right moment and to run the spawn function that is defined in the quest. So you can change any property or function later and this won`t break anything.

Cool, I'll have a play with that as well!

Link to comment
Share on other sites

 

 

You're gonna want to remove the line

import ASC_MasterRandomQuestScript

from the ASC_Random script. You're already importing the struct. There's no need to import the quest script. Doing so will give a duplicate array. Won't hurt anything as long as you don't try to use it inadvertently.

Link to comment
Share on other sites

 

Â

Â

You're gonna want to remove the line

import ASC_MasterRandomQuestScript
from the ASC_Random script. You're already importing the struct. There's no need to import the quest script. Doing so will give a duplicate array. Won't hurt anything as long as you don't try to use it inadvertently.

Cheers, I'll do that.

Link to comment
Share on other sites

@BnF - Just wanted to ask one thing before I go to sleep. In the Spawn() function you mentioned that there might have to be something in the else block if both the first blocks returned none/false

Function Spawn()
    int iNumSpawnTypes = (ASC_MasterRandomQuest as ASC_MasterRandomQuestScript).ActorTypesR1.Length ; how many types of spawning actors can we support
    int iWhoToSpawn = Utility.RandomInt(1,iNumSpawnTypes) ; changed to use the size of our array of actor types
    ActorTypeStruct spawnDetails = (ASC_MasterRandomQuest as ASC_MasterRandomQuestScript).ActorTypesR1[iWhoToSpawn]
    if (spawnDetails.ASC_Allowed.GetValueInt() == 1)
        SpawnEnemyActors(spawnDetails.ASC_Max_Allowed.GetValueInt(), spawnDetails.ASC_Chance.GetValueInt(), spawnDetails.LvlActorBase, spawnDetails.ASC_Allowed_Boss.GetValue() as Bool, spawnDetails.ASC_Max_Allowed_Boss.GetValueInt(), spawnDetails.ASC_Chance_Boss.GetValueInt(), spawnDetails.LvlActorBossBase)
    elseif (spawnDetails.ASC_Allowed.GetValueInt() == 0) && (spawnDetails.ASC_Reroll_Allowed.GetValueInt() == 1)   ;Reroll for another spawn once, if elected group is not allowed as per user settings
        RerollCheck(spawnDetails.ASC_Reroll_Chance.GetValueInt())
    else     
		;Not sure what to do here to stop script from stalling
    endif
EndFunction

Would you just link that to another bogus function that just does something for the sake of it (or nothing) just to complete the code? Or something specific?

 

BTW here is a more polished up version now:

 

 

Scriptname ASC_SP_Random_R1 extends ObjectReference
{Region 1 Random Type SpawnPoint Marker Script }

import ASC_MainStruct

ASC_MasterRandomQuestScript Property ASC_MasterRandomQuest Auto
GlobalVariable Property ASC_Main_ModEnabled Auto Const
GlobalVariable Property ASC_Main_RandomEnabled_R1 Auto Const
GlobalVariable Property ASC_Main_Random_Chance_R1 Auto Const
GlobalVariable Property ASC_Main_Random_DisableOnBlock_R1 Auto Const
FormList Property ASC_ResetList_R1 Auto Const
Actor Property PlayerRef Auto Const
GlobalVariable Property ASC_Main_Difficulty_R1 Auto Const
GlobalVariable Property ASC_Reroll_Main_Chance_R1 Auto Const
GlobalVariable Property ASC_Reroll_Main_R1 Auto Const

bool bRerolled = false
Actor[] GroupList
bool bSpawnPointLoaded = false

Event OnCellAttach()
	bSpawnPointLoaded = true
	if (Self.IsDisabled() == false) && (ASC_Main_ModEnabled.GetValueInt() == 1) && (ASC_Main_RandomEnabled_R1.GetValueInt() == 1)
		SPActivate()
	endif
EndEvent

Event OnCellDetach()
    bSpawnPointLoaded = false
    Utility.Wait(10)
    if (bSpawnPointLoaded == false) && (PlayerRef.IsInCombat() == false)  ;make sure we are out of loaded area and were not potentially fighting said enemies
        int iCounter = 0
        while iCounter < GroupList.Length
            GroupList[iCounter].SetLinkedRef(None)
            GroupList[iCounter].DeleteWhenAble()
            iCounter += 1
        endwhile
        GroupList.Clear()
        Debug.Notification("All members cleared from array")
	elseif (bSpawnPointLoaded == false) && (PlayerRef.IsInCombat() == true)
		while (PlayerRef.IsInCombat() == true)
		; Do nothing until combat is over, then check if we are still in the loaded area
		endwhile
		if bSpawnPointLoaded == false ;if we are not in the loaded area, start cleaning up. Event will be called again if we aren't so no worries
			int iCounter = 0
			while iCounter < GroupList.Length
				GroupList[iCounter].SetLinkedRef(None)
				GroupList[iCounter].DeleteWhenAble()
				iCounter += 1
			endwhile
		endif
    endif
EndEvent

Function SPActivate()  ;This may not be necessary but it looks neat. Main roll chance and disabling happens here
    int iChanceToSpawn = Utility.RandomInt(1,100)
    if (iChanceToSpawn <= ASC_Main_Random_Chance_R1.GetValueInt())
        Spawn()
        Self.Disable()
        ASC_ResetList_R1.AddForm(Self)
    elseif (iChanceToSpawn >= ASC_Main_Random_Chance_R1.GetValueInt()) && (ASC_Main_Random_DisableOnBlock_R1.GetValueInt() == 1)
        Self.Disable()
        ASC_ResetList_R1.AddForm(Self)
    else
        ;Nothing
    endif
EndFunction

Function Spawn()
    int iNumSpawnTypes = (ASC_MasterRandomQuest as ASC_MasterRandomQuestScript).ActorTypesR1.Length ; how many types of spawning actors can we support
    int iWhoToSpawn = Utility.RandomInt(1,iNumSpawnTypes) ; changed to use the size of our array of actor types
    ActorTypeStruct spawnDetails = (ASC_MasterRandomQuest as ASC_MasterRandomQuestScript).ActorTypesR1[iWhoToSpawn]
    if (spawnDetails.ASC_Allowed.GetValueInt() == 1)
        SpawnEnemyActors(spawnDetails.ASC_Max_Allowed.GetValueInt(), spawnDetails.ASC_Chance.GetValueInt(), spawnDetails.LvlActorBase, spawnDetails.ASC_Allowed_Boss.GetValue() as Bool, spawnDetails.ASC_Max_Allowed_Boss.GetValueInt(), spawnDetails.ASC_Chance_Boss.GetValueInt(), spawnDetails.LvlActorBossBase)
    elseif (spawnDetails.ASC_Allowed.GetValueInt() == 0) && (spawnDetails.ASC_Reroll_Allowed.GetValueInt() == 1)   ;Reroll for another spawn once, if elected group is not allowed as per user settings
        RerollCheck(spawnDetails.ASC_Reroll_Chance.GetValueInt())
    else     
		;Not sure what to do here to stop script from stalling, shouldn't get a none value though if we got this far EDIT: Duh, I have blocking settings so it could both be none
    endif
EndFunction

Function SpawnEnemyActors(int iMaxSpawnCount, int iChance, ActorBase varBaseActor, bool bBossAllowed, int iMaxBossCount, int iBossChance, ActorBase varBossActor)
    Form kMarker = Game.GetFormFromFile(0x00002ce2, "Fallout4.ESM")
	ObjectReference kPatrolMarker = Game.FindClosestReferenceOfTypeFromRef(kMarker, Self as ObjectReference, 32.0)
	int iDifficulty = ASC_Main_Difficulty_R1.GetValueInt()
    int iSpawnCounter = iMaxSpawnCount
	int iPosition = 0
    bool bSpawnSuccess = false
    GroupList = new Actor[0] ; since we don't have 100% chance of spawning actors, we need this to be an accumulating array
    Debug.Notification("Spawning enemies") ; unless we add a function parameter for text this had to be changed to be something generic
        while (iSpawnCounter < iMaxSpawnCount)
            if (Utility.RandomInt(1,100) <= iChance)
                GroupList.Add(Self.PlaceActorAtMe(varBaseActor, Utility.RandomInt(1,iDifficulty), None))
                iPosition = GroupList.Length - 1
                GroupList[iPosition].SetLinkedRef(kPatrolMarker)
                Debug.Notification("Array Add Success. Actor count now: " + iPosition)
                bSpawnSuccess = True
            endif
            iSpawnCounter += 1
        endwhile
        if bBossAllowed == 1
            iSpawnCounter = 0 ; reuse var. memory efficient
            while (iSpawnCounter < iMaxBossCount)
                if (Utility.RandomInt(1,100) <= iBossChance)
                    GroupList.Add(Self.PlaceActorAtMe(varBossActor, Utility.RandomInt(1,iDifficulty), None))
                    iPosition = GroupList.Length - 1
                    GroupList[iPosition].SetLinkedRef(kPatrolMarker)
                    Debug.Notification("Array Add Success. Actor count now: " + iPosition)
                    bSpawnSuccess = True
                endif
                iSpawnCounter += 1
            endwhile
        endif
		if (bSpawnSuccess == true) && (ASC_Reroll_Main_R1.GetValueInt() == 1)
			RerollForAnotherGroup()
		endif
EndFunction

Function RerollCheck(int iRerollChance)
    if (bRerolled == false) && (Utility.RandomInt(1,100) <= iRerollChance)
        Spawn()
        bRerolled = true
        Debug.Notification("Rerolling after block")
    else
        Debug.Notification("Reroll on block denied")
    endif
EndFunction

Function RerollForAnotherGroup()  ;this simulates spawning battles between two groups, or possibly a really large group of the same enemy. 
	if (Utility.RandomInt(1,100) <= ASC_Reroll_Main_Chance_R1.GetValueInt())
		Spawn()
		Debug.Notification("Spawning another group!")
	else
		Debug.Notification("Failed to Reroll another group!")
	endif	
EndFunction

 

 

Link to comment
Share on other sites

@BnF - Just wanted to ask one thing before I go to sleep. In the Spawn() function you mentioned that there might have to be something in the else block if both the first blocks returned none/false

Function Spawn()
    int iNumSpawnTypes = (ASC_MasterRandomQuest as ASC_MasterRandomQuestScript).ActorTypesR1.Length ; how many types of spawning actors can we support
    int iWhoToSpawn = Utility.RandomInt(1,iNumSpawnTypes) ; changed to use the size of our array of actor types
    ActorTypeStruct spawnDetails = (ASC_MasterRandomQuest as ASC_MasterRandomQuestScript).ActorTypesR1[iWhoToSpawn]
    if (spawnDetails.ASC_Allowed.GetValueInt() == 1)
        SpawnEnemyActors(spawnDetails.ASC_Max_Allowed.GetValueInt(), spawnDetails.ASC_Chance.GetValueInt(), spawnDetails.LvlActorBase, spawnDetails.ASC_Allowed_Boss.GetValue() as Bool, spawnDetails.ASC_Max_Allowed_Boss.GetValueInt(), spawnDetails.ASC_Chance_Boss.GetValueInt(), spawnDetails.LvlActorBossBase)
    elseif (spawnDetails.ASC_Allowed.GetValueInt() == 0) && (spawnDetails.ASC_Reroll_Allowed.GetValueInt() == 1)   ;Reroll for another spawn once, if elected group is not allowed as per user settings
        RerollCheck(spawnDetails.ASC_Reroll_Chance.GetValueInt())
    else     
        ;Not sure what to do here to stop script from stalling
    endif
EndFunction
Would you just link that to another bogus function that just does something for the sake of it (or nothing) just to complete the code? Or something specific?

 

BTW here is a more polished up version now:

 

 

Scriptname ASC_SP_Random_R1 extends ObjectReference
{Region 1 Random Type SpawnPoint Marker Script }

import ASC_MainStruct

ASC_MasterRandomQuestScript Property ASC_MasterRandomQuest Auto
GlobalVariable Property ASC_Main_ModEnabled Auto Const
GlobalVariable Property ASC_Main_RandomEnabled_R1 Auto Const
GlobalVariable Property ASC_Main_Random_Chance_R1 Auto Const
GlobalVariable Property ASC_Main_Random_DisableOnBlock_R1 Auto Const
FormList Property ASC_ResetList_R1 Auto Const
Actor Property PlayerRef Auto Const
GlobalVariable Property ASC_Main_Difficulty_R1 Auto Const
GlobalVariable Property ASC_Reroll_Main_Chance_R1 Auto Const
GlobalVariable Property ASC_Reroll_Main_R1 Auto Const

bool bRerolled = false
Actor[] GroupList
bool bSpawnPointLoaded = false

Event OnCellAttach()
    bSpawnPointLoaded = true
    if (Self.IsDisabled() == false) && (ASC_Main_ModEnabled.GetValueInt() == 1) && (ASC_Main_RandomEnabled_R1.GetValueInt() == 1)
        SPActivate()
    endif
EndEvent

Event OnCellDetach()
    bSpawnPointLoaded = false
    Utility.Wait(10)
    if (bSpawnPointLoaded == false) && (PlayerRef.IsInCombat() == false)  ;make sure we are out of loaded area and were not potentially fighting said enemies
        int iCounter = 0
        while iCounter < GroupList.Length
            GroupList[iCounter].SetLinkedRef(None)
            GroupList[iCounter].DeleteWhenAble()
            iCounter += 1
        endwhile
        GroupList.Clear()
        Debug.Notification("All members cleared from array")
    elseif (bSpawnPointLoaded == false) && (PlayerRef.IsInCombat() == true)
        while (PlayerRef.IsInCombat() == true)
        ; Do nothing until combat is over, then check if we are still in the loaded area
        endwhile
        if bSpawnPointLoaded == false ;if we are not in the loaded area, start cleaning up. Event will be called again if we aren't so no worries
            int iCounter = 0
            while iCounter < GroupList.Length
                GroupList[iCounter].SetLinkedRef(None)
                GroupList[iCounter].DeleteWhenAble()
                iCounter += 1
            endwhile
        endif
    endif
EndEvent

Function SPActivate()  ;This may not be necessary but it looks neat. Main roll chance and disabling happens here
    int iChanceToSpawn = Utility.RandomInt(1,100)
    if (iChanceToSpawn <= ASC_Main_Random_Chance_R1.GetValueInt())
        Spawn()
        Self.Disable()
        ASC_ResetList_R1.AddForm(Self)
    elseif (iChanceToSpawn >= ASC_Main_Random_Chance_R1.GetValueInt()) && (ASC_Main_Random_DisableOnBlock_R1.GetValueInt() == 1)
        Self.Disable()
        ASC_ResetList_R1.AddForm(Self)
    else
        ;Nothing
    endif
EndFunction

Function Spawn()
    int iNumSpawnTypes = (ASC_MasterRandomQuest as ASC_MasterRandomQuestScript).ActorTypesR1.Length ; how many types of spawning actors can we support
    int iWhoToSpawn = Utility.RandomInt(1,iNumSpawnTypes) ; changed to use the size of our array of actor types
    ActorTypeStruct spawnDetails = (ASC_MasterRandomQuest as ASC_MasterRandomQuestScript).ActorTypesR1[iWhoToSpawn]
    if (spawnDetails.ASC_Allowed.GetValueInt() == 1)
        SpawnEnemyActors(spawnDetails.ASC_Max_Allowed.GetValueInt(), spawnDetails.ASC_Chance.GetValueInt(), spawnDetails.LvlActorBase, spawnDetails.ASC_Allowed_Boss.GetValue() as Bool, spawnDetails.ASC_Max_Allowed_Boss.GetValueInt(), spawnDetails.ASC_Chance_Boss.GetValueInt(), spawnDetails.LvlActorBossBase)
    elseif (spawnDetails.ASC_Allowed.GetValueInt() == 0) && (spawnDetails.ASC_Reroll_Allowed.GetValueInt() == 1)   ;Reroll for another spawn once, if elected group is not allowed as per user settings
        RerollCheck(spawnDetails.ASC_Reroll_Chance.GetValueInt())
    else     
        ;Not sure what to do here to stop script from stalling, shouldn't get a none value though if we got this far EDIT: Duh, I have blocking settings so it could both be none
    endif
EndFunction

Function SpawnEnemyActors(int iMaxSpawnCount, int iChance, ActorBase varBaseActor, bool bBossAllowed, int iMaxBossCount, int iBossChance, ActorBase varBossActor)
    Form kMarker = Game.GetFormFromFile(0x00002ce2, "Fallout4.ESM")
    ObjectReference kPatrolMarker = Game.FindClosestReferenceOfTypeFromRef(kMarker, Self as ObjectReference, 32.0)
    int iDifficulty = ASC_Main_Difficulty_R1.GetValueInt()
    int iSpawnCounter = iMaxSpawnCount
    int iPosition = 0
    bool bSpawnSuccess = false
    GroupList = new Actor[0] ; since we don't have 100% chance of spawning actors, we need this to be an accumulating array
    Debug.Notification("Spawning enemies") ; unless we add a function parameter for text this had to be changed to be something generic
        while (iSpawnCounter < iMaxSpawnCount)
            if (Utility.RandomInt(1,100) <= iChance)
                GroupList.Add(Self.PlaceActorAtMe(varBaseActor, Utility.RandomInt(1,iDifficulty), None))
                iPosition = GroupList.Length - 1
                GroupList[iPosition].SetLinkedRef(kPatrolMarker)
                Debug.Notification("Array Add Success. Actor count now: " + iPosition)
                bSpawnSuccess = True
            endif
            iSpawnCounter += 1
        endwhile
        if bBossAllowed == 1
            iSpawnCounter = 0 ; reuse var. memory efficient
            while (iSpawnCounter < iMaxBossCount)
                if (Utility.RandomInt(1,100) <= iBossChance)
                    GroupList.Add(Self.PlaceActorAtMe(varBossActor, Utility.RandomInt(1,iDifficulty), None))
                    iPosition = GroupList.Length - 1
                    GroupList[iPosition].SetLinkedRef(kPatrolMarker)
                    Debug.Notification("Array Add Success. Actor count now: " + iPosition)
                    bSpawnSuccess = True
                endif
                iSpawnCounter += 1
            endwhile
        endif
        if (bSpawnSuccess == true) && (ASC_Reroll_Main_R1.GetValueInt() == 1)
            RerollForAnotherGroup()
        endif
EndFunction

Function RerollCheck(int iRerollChance)
    if (bRerolled == false) && (Utility.RandomInt(1,100) <= iRerollChance)
        Spawn()
        bRerolled = true
        Debug.Notification("Rerolling after block")
    else
        Debug.Notification("Reroll on block denied")
    endif
EndFunction

Function RerollForAnotherGroup()  ;this simulates spawning battles between two groups, or possibly a really large group of the same enemy. 
    if (Utility.RandomInt(1,100) <= ASC_Reroll_Main_Chance_R1.GetValueInt())
        Spawn()
        Debug.Notification("Spawning another group!")
    else
        Debug.Notification("Failed to Reroll another group!")
    endif    
EndFunction

 

I had originally commented out the else on the Spawn function because it was empty. Leaving it there empty doesn't hurt either. I just wasn't sure if there was something you wanted to do if the first 2 checks failed.

 

The changes you made to OnCellDetach are a good try. The while loop for checking if player is in combat shoud have a wait so it doesn't eat a ton of cycles and freeze up the machine.

 

        while (PlayerRef.IsInCombat() == true)
            ; Do nothing until combat is over, then check if we are still in the loaded area
            Utility.Wait(0.1)
        endwhile

You could also refactor that to put the loop in the first check and drop the check on the initial if statement. Though on a side note. Checking if they're in combat doesn't really work well here because we could have left the area and are in combat somewhere else and the script will halt. It probably won't hurt anything but just something to take note of. I should point out that the cell doesn't get unloaded until you're pretty far away. Like Sanctuary to Starlight drive in far. So the initial 10s wait probably isn't necessary. Nor the wait on combat. Unless a mod is involved the enemies won't follow that far. I think this would do what you want.

 

Event OnCellDetach()
    bSpawnPointLoaded = false
    ;Utility.Wait(10) ; Not really necessary but left for reference
    while (PlayerRef.IsInCombat()) ; also may not be necessary but reference for sanity check. never do a while loop without something to do inside. if you don't have anything to do then do a little pause before looping.
        ; Do nothing until combat is over, then check if we are still in the loaded area
        Utility.Wait(0.1)
    endwhile
    if (bSpawnPointLoaded == false) ; make sure we are still out of loaded area
        int iCounter = 0
        while iCounter < GroupList.Length
            GroupList[iCounter].SetLinkedRef(None)
            GroupList[iCounter].DeleteWhenAble()
            iCounter += 1
        endwhile
        GroupList.Clear()
        Debug.Notification("All members cleared from array")
    endif
EndEvent
Link to comment
Share on other sites

Thanks for the optimization.

 

From my testing, the cells detach fairly close (inline with ugrids settings), I could see this with the debug note when testing that before. I couldn't use OnUnload I think due to some parts of it still being persistent and never getting the event. Unless I never walked far away enough for it to get the event :P

 

I guess the idea behind the IsInCombat check and the initial 10 second timer, I have seen in WOTC some enemies being "cleaned up" when I've only been out of the cell for a few seconds. And it could be possible for the player to engage combat with another group while backing away from one group etc, and end up crossing 10 cells or so. So in my mind it was the lesser evil to have the check. The 10 seconds timer, I was thinking to make a Global for it as well, so users can decide said timer. That was the intentions anyway.

 

I added a new item to the struct as well, ASC_Ez_Lvl, and an array on the quest of all the EZ's I have made. Also because I wasn't sure about the "script stalling" a made a new function that could rearm the point if nothing actually happened

 

 

Scriptname ASC_SP_Random_R1 extends ObjectReference
{Region 1 Random Type SpawnPoint Marker Script }

import ASC_MainStruct

ASC_MasterRandomQuestScript Property ASC_MasterRandomQuest Auto
GlobalVariable Property ASC_Main_ModEnabled Auto Const
GlobalVariable Property ASC_Main_RandomEnabled_R1 Auto Const
GlobalVariable Property ASC_Main_Random_Chance_R1 Auto Const
GlobalVariable Property ASC_Main_Random_DisableOnBlock_R1 Auto Const
GlobalVariable Property ASC_Main_Random_EnableOnSpawnBlock_R1 Auto Const
GlobalVariable Property ASC_Main_Difficulty_R1 Auto Const
GlobalVariable Property ASC_Reroll_Main_Chance_R1 Auto Const
GlobalVariable Property ASC_Reroll_Main_R1 Auto Const
GlobalVariable Property ASC_CleanupTimer_R1 Auto Const
FormList Property ASC_ResetList_R1 Auto Const
Actor Property PlayerRef Auto Const

bool bRerolled = false
Actor[] GroupList
bool bSpawnPointLoaded = false

Event OnCellAttach()
	bSpawnPointLoaded = true
	if (Self.IsDisabled() == false) && (ASC_Main_ModEnabled.GetValueInt() == 1) && (ASC_Main_RandomEnabled_R1.GetValueInt() == 1)
		SPActivate()
	endif
EndEvent

Event OnCellDetach()
    bSpawnPointLoaded = false
    Utility.Wait(ASC_CleanupTimer_R1.GetValueInt())
    if (bSpawnPointLoaded == false) && (PlayerRef.IsInCombat() == false)  ;make sure we are out of loaded area and were not potentially fighting said enemies
        int iCounter = 0
        while iCounter < GroupList.Length
            GroupList[iCounter].SetLinkedRef(None)
            GroupList[iCounter].DeleteWhenAble()
            iCounter += 1
        endwhile
        GroupList.Clear()
        Debug.Notification("All members cleared from array")
	elseif (bSpawnPointLoaded == false) && (PlayerRef.IsInCombat() == true)
		while (PlayerRef.IsInCombat() == true)
		Utility.Wait(0.1)
		endwhile
		if bSpawnPointLoaded == false ;if we are not in the loaded area, start cleaning up. Event will be called again if we aren't so no worries
			int iCounter = 0
			while iCounter < GroupList.Length
				GroupList[iCounter].SetLinkedRef(None)
				GroupList[iCounter].DeleteWhenAble()
				iCounter += 1
			endwhile
		endif
    endif
EndEvent

Function SPActivate()  ;This may not be necessary but it looks neat. Main roll chance and disabling happens here
    int iChanceToSpawn = Utility.RandomInt(1,100)
    if (iChanceToSpawn <= ASC_Main_Random_Chance_R1.GetValueInt())
        Spawn()
        Self.Disable()
        ASC_ResetList_R1.AddForm(Self)
    elseif (iChanceToSpawn >= ASC_Main_Random_Chance_R1.GetValueInt()) && (ASC_Main_Random_DisableOnBlock_R1.GetValueInt() == 1)
        Self.Disable()
        ASC_ResetList_R1.AddForm(Self)
    else
        ;Nothing, exit function
    endif
EndFunction

Function Spawn()
    int iNumSpawnTypes = (ASC_MasterRandomQuest as ASC_MasterRandomQuestScript).ActorTypesR1.Length ; how many types of spawning actors can we support
    int iWhoToSpawn = Utility.RandomInt(1,iNumSpawnTypes) ; changed to use the size of our array of actor types
    ActorTypeStruct spawnDetails = (ASC_MasterRandomQuest as ASC_MasterRandomQuestScript).ActorTypesR1[iWhoToSpawn]
    if (spawnDetails.ASC_Allowed.GetValueInt() == 1)
        SpawnEnemyActors(spawnDetails.ASC_Max_Allowed.GetValueInt(), spawnDetails.ASC_Chance.GetValueInt(), spawnDetails.LvlActorBase, spawnDetails.ASC_Allowed_Boss.GetValue() as Bool, spawnDetails.ASC_Max_Allowed_Boss.GetValueInt(), spawnDetails.ASC_Chance_Boss.GetValueInt(), spawnDetails.LvlActorBossBase, spawnDetails.ASC_EZ_Lvl.GetValueInt())
    elseif (spawnDetails.ASC_Allowed.GetValueInt() == 0) && (spawnDetails.ASC_Reroll_Allowed.GetValueInt() == 1)   ;Reroll for another spawn once, if elected group is not allowed as per user settings
        RerollCheck(spawnDetails.ASC_Reroll_Chance.GetValueInt())
    elseif (spawnDetails.ASC_Allowed.GetValueInt() == 0) && (spawnDetails.ASC_Reroll_Allowed.GetValueInt() == 0)
		BlockedOnSpawn()
    endif
EndFunction

Function SpawnEnemyActors(int iMaxSpawnCount, int iChance, ActorBase varBaseActor, bool bBossAllowed, int iMaxBossCount, int iBossChance, ActorBase varBossActor, int iEzLevel)
    Form kMarker = Game.GetFormFromFile(0x00002ce2, "Fallout4.ESM")
	ObjectReference kPatrolMarker = Game.FindClosestReferenceOfTypeFromRef(kMarker, Self as ObjectReference, 32.0)
	int iDifficulty = ASC_Main_Difficulty_R1.GetValueInt()
    int iSpawnCounter = iMaxSpawnCount
	int iPosition = 0
    bool bSpawnSuccess = false
    GroupList = new Actor[0] ; since we don't have 100% chance of spawning actors, we need this to be an accumulating array
    Debug.Notification("Spawning enemies") ; unless we add a function parameter for text this had to be changed to be something generic
        while (iSpawnCounter < iMaxSpawnCount)
            if (Utility.RandomInt(1,100) <= iChance)
                GroupList.Add(Self.PlaceActorAtMe(varBaseActor, Utility.RandomInt(1,iDifficulty), None))
                iPosition = GroupList.Length - 1
                GroupList[iPosition].SetLinkedRef(kPatrolMarker)
                Debug.Notification("Array Add Success. Actor count now: " + iPosition)
                bSpawnSuccess = True
            endif
            iSpawnCounter += 1
        endwhile
        if bBossAllowed == 1
            iSpawnCounter = 0 ; reuse var. memory efficient
            while (iSpawnCounter < iMaxBossCount)
                if (Utility.RandomInt(1,100) <= iBossChance)
                    GroupList.Add(Self.PlaceActorAtMe(varBossActor, Utility.RandomInt(1,iDifficulty), (ASC_MasterRandomQuest as ASC_MasterRandomQuestScript).EzArray[iEzLevel]))
                    iPosition = GroupList.Length - 1
                    GroupList[iPosition].SetLinkedRef(kPatrolMarker)
                    Debug.Notification("Array Add Success. Actor count now: " + iPosition)
                    bSpawnSuccess = True
                endif
                iSpawnCounter += 1
            endwhile
        endif
		if (bSpawnSuccess == true) && (ASC_Reroll_Main_R1.GetValueInt() == 1)
			RerollForAnotherGroup()
		endif
EndFunction

Function RerollCheck(int iRerollChance)
    if (bRerolled == false) && (Utility.RandomInt(1,100) <= iRerollChance)
        Spawn()
        bRerolled = true
        Debug.Notification("Rerolling after block")
    else
        Debug.Notification("Reroll on block denied")
    endif
EndFunction

Function RerollForAnotherGroup()  ;this simulates spawning battles between two groups, or possibly a really large group of the same enemy. 
	if (Utility.RandomInt(1,100) <= ASC_Reroll_Main_Chance_R1.GetValueInt())
		Spawn()
		Debug.Notification("Spawning another group!")
	else
		Debug.Notification("Failed to Reroll another group!")
	endif	
EndFunction

Function BlockedOnSpawn()
	if (ASC_Main_Random_EnableOnSpawnBlock_R1.GetValueInt() == 1)
		Self.Enable()
	else
		;Nothing, exit function
	endif
Function

 

 

Link to comment
Share on other sites

Another thing that crosses my mind here, seems the OnCellAttach/detach works within the ugrids range, I have not implemented anything in the case that you happen to "pass by" a cell very quickly. I think in WOTC, a lot of CTDs happen when the player moves across a lot of cells and the script isn't keeping up, and by the time it elects a place to MoveTo the spawned actors, they're out of the loaded area (probably why I see up to 5 "could not find navmesh with MoveToNearestNavmeshLocation function before most regular crashes). I think my script is a bit faster though and from what I can see (given I don't intend on having any more than 2 points per cell mostly) it will be finished before that could happen. Just thoughts based on what I have seen anyway

 

 

On an entirely separate note, it's in the back of my head that if I set up a patrol marker network, this will be persistent. I wanted to set it up so that other modders could also use it, a bit of a convenience feature, as well as to address the problem that WOTC sees with it's patrol AI packages and the fact I don't want to modify/have too many custom ActorBases/LvlNPCs (for cross compatibility). But for shiz and giggles I was looking at how it could be possible to dynamically give each NPC a "patrol" package. Apparently the only way to do this is ForceRefTo() a reference alias and assign the package (maybe having to force with EvaluatePackage()). I wonder though, if I had a single Ref Alias on the quest, and I kept forcing NPCs in and out of it and assigning them the package, would that work for the way things are set up (possibly multiple "spawnpoints" sending NPCs to the RefAlias, will they keep package after they are out of it, how to remove the very last NPC fro mthe RefAlias when they've all been through etc etc)

 

Just reading that back to myself, I'll stay with the patrol marker network lol. Just curious how far I can "rev the engine" lol. On that note, I kinda thought of a way to make them non-persistent using an uninstall script. If my whole network is linked from one end of the CW to another, I could do some code that specifically starts at one of them, and keeps getting the next linked ref and setting it to none. Until they are all unlinked in that savegame, so users can have peace of mind. Suppose that would take some very careful placement though, but hey at least I think this way :)

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.

×
×
  • Create New...