SMB92 Posted May 28, 2017 Author Share Posted May 28, 2017 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 More sharing options...
SMB92 Posted May 28, 2017 Author Share Posted May 28, 2017 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 More sharing options...
kitcat81 Posted May 28, 2017 Share Posted May 28, 2017 (edited) 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 properlyNot 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 May 28, 2017 by kitcat81 Link to comment Share on other sites More sharing options...
SMB92 Posted May 28, 2017 Author Share Posted May 28, 2017 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 More sharing options...
JonathanOstrus Posted May 28, 2017 Share Posted May 28, 2017 You're gonna want to remove the line import ASC_MasterRandomQuestScriptfrom 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 More sharing options...
SMB92 Posted May 28, 2017 Author Share Posted May 28, 2017 Â Â You're gonna want to remove the line import ASC_MasterRandomQuestScriptfrom 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 More sharing options...
SMB92 Posted May 28, 2017 Author Share Posted May 28, 2017 @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 More sharing options...
JonathanOstrus Posted May 29, 2017 Share Posted May 29, 2017 @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/falseFunction 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 More sharing options...
SMB92 Posted May 29, 2017 Author Share Posted May 29, 2017 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 More sharing options...
SMB92 Posted May 29, 2017 Author Share Posted May 29, 2017 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 More sharing options...
Recommended Posts