CHIMiny Cricket Posted March 14, 2021 Posted March 14, 2021 Looking for some help in optimizing a script for my mod, Fertility Adventures. It's in the spoiler a the bottom of the post. My main concern is regarding the properties "QuestStarters" and "Quests". The script detects who's pregnant from Fertility Mode. If a pregnant NPC is in the QuestStarters array they will have their own personal quest in the Quests array set appropriately. The trouble is that whenever I make a new quest and add it along with the new NPC to their respective properties, a clean save is required for anyone updating the mod. Is there a way to avoid this? Spoiler Scriptname FMA_HandlerQuestScript extends Quest _JSW_BB_Storage Property Storage Auto GlobalVariable Property PollingInterval Auto GlobalVariable Property PregnancyDuration Auto int Property QuestStartThreshold Auto Actor[] Property QuestStarters Auto Quest[] Property Quests Auto Int[] Property StageList Auto Float[] Property NextStagePercentage Auto Faction Property FMA_NonPlayerPregFaction Auto Actor Property PlayerRef Auto Faction Property FMA_GenericPregFaction Auto Faction Property FMA_AnnouncementBlockerFaction Auto Quest Property FMA_PlayerTrackingQuest Auto Quest Property FMA_GenericDialogueQuest Auto event OnInit() RegisterForSingleUpdateGameTime(PollingInterval.GetValue()) RegisterForModEvent("FertilityModeLabor", "OnFertilityModeLabor") RegisterForModEvent("FertilityModeConception", "OnFertilityModeConception") FMA_PlayerTrackingQuest.SetStage(0) FMA_GenericDialogueQuest.Start() endEvent event OnPlayerLoadGame() RegisterForSingleUpdateGameTime(PollingInterval.GetValue()) RegisterForModEvent("FertilityModeLabor", "OnFertilityModeLabor") RegisterForModEvent("FertilityModeConception", "OnFertilityModeConception") endEvent Event OnUpdateGameTime() Int n = 0 while (n < Storage.TrackedActors.Length) If (Storage.LastConception[n] > 0) int today = Utility.GetCurrentGameTime() as int int lastConception = Storage.LastConception[n] as int int pregnantDay = (today - lastConception) if (pregnantDay >= QuestStartThreshold) ; The actor is pregnant, check if she's a quest starter int starterIndex = QuestStarters.Find(Storage.TrackedActorGet(n)) if (starterIndex != -1) && ((!Quests[starterIndex].IsRunning()) || (Quests[starterIndex].GetStage() == 0)) Quests[starterIndex].SetStage(10) ;The pregnancy quest is not running or in the "pre-pregancy" stage. Start the Quest. ElseIf Quests[starterIndex].GetStage() >= 10 ;The pregnancy quest is already running. Check to see if it's time to advance to the next stage. Int CurrentStageIndex = StageList.Find(Quests[starterIndex].GetStage()) Float NextStageThreshold = (PregnancyDuration.Getvalue()*(NextStagePercentage[CurrentStageIndex])) If PregnantDay > NextStageThreshold Quests[starterIndex].SetStage(Stagelist[CurrentStageIndex + 1]) EndIf EndIf EndIf EndIf n += 1 EndWhile RegisterForSingleUpdateGameTime(PollingInterval.GetValue()) EndEvent event OnFertilityModeConception(string eventName, Form akSender, string motherName, string fatherName, int iTrackingIndex) ; akSender is the woman in question and can be cast to an Actor object ; motherName is the display name of akSender ; fatherName is the display name of the current father ; iTrackingIndex is the woman's location in the tracking arrays If QuestStarters.Find(akSender as Actor) == -1 ;The actor is doesn't have a unique tracking quest. Put her in the generic pregnancy faction. (akSender as Actor).AddtoFaction(FMA_GenericPregFaction) EndIf If (fatherName != (Game.GetPlayer().GetBaseObject().GetName())) && ((akSender as Actor) != PlayerREf) ;The player is not the mother or father, put her in the non player pregnancy faction (akSender as Actor).AddtoFaction(FMA_NonPlayerPregFaction) EndIf endEvent event OnFertilityModeLabor(string eventName, Form sender, int actorIndex) ;sender is the woman in question and can be cast to an Actor object ;actorIndex is the woman's location in the tracking arrays ;Removal of factions used to control pregnancy related dialogue. If (Sender as Actor).IsInFaction(FMA_NonPlayerPregFaction) (Sender as Actor).RemovefromFaction(FMA_NonPlayerPregFaction) EndIf If (Sender as Actor).IsInFaction(FMA_GenericPregFaction) (Sender as Actor).RemovefromFaction(FMA_GenericPregFaction) EndIf If (Sender as Actor).IsInFaction(FMA_AnnouncementBlockerFaction) (Sender as Actor).RemovefromFaction(FMA_AnnouncementBlockerFaction) EndIf endEvent
Seijin8 Posted March 15, 2021 Posted March 15, 2021 58 minutes ago, CHIMiny Cricket said: event OnPlayerLoadGame() This is not reliable. Look at SkyUI player alias scripts as a better example and have a similar script send a mod event to your mod, or better yet, call its update/maintenance function directly. Any registrations should also be in whatever normal update loop you are using. 59 minutes ago, CHIMiny Cricket said: (Sender as Actor).IsInFaction(FMA_NonPlayerPregFaction) When using scripts to add/remove factions, you can sometimes get odd results with race conditions. Might be better to check for a factionrank instead, and explicitly set factionranks, so instead of "IsInFaction()" and "RemoveFromFaction()" maybe use "GetFactionRank(fac) < 0" and "SetFactionRank(fac, -2)" These amount to the same thing, but are immune to race conditions. Note that RemoveFromFaction() being called twice results in the system sometimes reading fac rank as -2 on the first, and -1 on the second, which is to say a -1 value does count as "InFaction" for some conditions. Note that while I've been working with papyrus for quite a while now, I am by no means an expert. Best of luck.
jmf890 Posted March 15, 2021 Posted March 15, 2021 1 hour ago, Seijin8 said: This is not reliable. Look at SkyUI player alias scripts as a better example and have a similar script send a mod event to your mod, or better yet, call its update/maintenance function directly. Any registrations should also be in whatever normal update loop you are using. This is not correct. The OnPlayerLoadGame event is reliable and the only way to catch it through papyrus alone. SkyUI OnGameReload is actually OnPlayerLoadGame. The reason it's wrong in his script is because the event is sent to Actors, not Quests, and only for the player Actor. Either defining it on a reference alias pointing to the player or an ActiveMagicEffect currently in-effect on the player will work.
CHIMiny Cricket Posted March 17, 2021 Author Posted March 17, 2021 On 3/14/2021 at 7:54 PM, jmf890 said: This is not correct. The OnPlayerLoadGame event is reliable and the only way to catch it through papyrus alone. SkyUI OnGameReload is actually OnPlayerLoadGame. The reason it's wrong in his script is because the event is sent to Actors, not Quests, and only for the player Actor. Either defining it on a reference alias pointing to the player or an ActiveMagicEffect currently in-effect on the player will work. So then is this part of my script not even needed? I assumed that you had to register for events on game load. The mod seems to be working as intended the way it is right now.
jmf890 Posted March 17, 2021 Posted March 17, 2021 I NEVER said anything like that. How you managed to interpret my response as such puzzles me.
AndrewLRG Posted March 17, 2021 Posted March 17, 2021 So, if I understand correctly you are adding new entries to QuestStarters and Quests arrays, but existing save does not see them. You need to restart the main quest that contains these properties. Or you can relocate QuestStarters and Quest arrays to a different quest and restart it. Also, make your main quest check whether it needs to be updated on GameLoad. Event OnPlayerLoadGame() if NewQuest.QuestStarters.Length == 3 ; do nothing else ; updating arrays NewQuest.Stop() NewQuest.Reset() NewQuestStart() endif EndEvent Where "NewQuest" is the Quest with QuestStarters and Quests arrays. P.S. Are you absolutely sure that OnPlayerLoadGame() in your script works correctly? As far as I know this event can be called only from players ReferenceAlias.
CHIMiny Cricket Posted March 17, 2021 Author Posted March 17, 2021 3 hours ago, jmf890 said: I NEVER said anything like that. How you managed to interpret my response as such puzzles me. Don't be a dick. You said that OnPlayerLoadGame only works on actors or a magic effect pointing to the player, which would mean that this part of my script is doing nothing right now because it's attached to a quest. Since everything seems to be working for me as intended I figured that having that section of code wasn't necessary.
CHIMiny Cricket Posted March 17, 2021 Author Posted March 17, 2021 3 hours ago, AndrewLRG said: So, if I understand correctly you are adding new entries to QuestStarters and Quests arrays, but existing save does not see them. You need to restart the main quest that contains these properties. Or you can relocate QuestStarters and Quest arrays to a different quest and restart it. Also, make your main quest check whether it needs to be updated on GameLoad. Event OnPlayerLoadGame() if NewQuest.QuestStarters.Length == 3 ; do nothing else ; updating arrays NewQuest.Stop() NewQuest.Reset() NewQuestStart() endif EndEvent Where "NewQuest" is the Quest with QuestStarters and Quests arrays. P.S. Are you absolutely sure that OnPlayerLoadGame() in your script works correctly? As far as I know this event can be called only from players ReferenceAlias. I'll give this a try. What is "length == 3" doing? I assume it's checking something about the QuestStarters array? I'm not sure what's going on with OnPlayerLoadGame. As I wrote in earlier posts, everything seems to be working as intended which makes me think that it might not be needed. The only purpose I'm using it for is to register for events.
AndrewLRG Posted March 17, 2021 Posted March 17, 2021 6 minutes ago, CHIMiny Cricket said: I'll give this a try. What is "length == 3" doing? I assume it's checking something about the QuestStarters array? I'm not sure what's going on with OnPlayerLoadGame. As I wrote in earlier posts, everything seems to be working as intended which makes me think that it might not be needed. The only purpose I'm using it for is to register for events. "3" is just for example. It's the total number of entries in QuestStarters array. It should check if you added new stuff in QuestStarters and update it. I would assume that OnPlayerLoadGame is not working. You have already registered events in OnInit() and despite what www.creationkit.com says its not absolutely necessary to re-register events on each game load.
jmf890 Posted March 17, 2021 Posted March 17, 2021 59 minutes ago, AndrewLRG said: "3" is just for example. It's the total number of entries in QuestStarters array. It should check if you added new stuff in QuestStarters and update it. I would assume that OnPlayerLoadGame is not working. You have already registered events in OnInit() and despite what www.creationkit.com says its not absolutely necessary to re-register events on each game load. Yes, those articles are outdated and serialization of SKSE event registration was added in version 1.6.0. skse_whatsnew.txt: Quote - introduced co-save to hold event registrations for SKSE-added event types For mod events, that's co-save plugin id 0 and chunk id MCBR. One caveat though, the one for RegisterForNiNodeUpdate is currently broken because of a typo (serializes with id NINU, but loads with id NiNU) and requires for you to re-register at OnPlayerLoadGame each time. As for RegisterForSingleUpdateGameTime, there is a difference between not calling it at each OnPlayerLoadGame and calling it. If you call it each time, the timer will be reset to 0 after loading your save, while not calling it means the timer will resume from its previous value stored within your save.
AndrewLRG Posted March 17, 2021 Posted March 17, 2021 2 hours ago, jmf890 said: As for RegisterForSingleUpdateGameTime, there is a difference between not calling it at each OnPlayerLoadGame and calling it. If you call it each time, the timer will be reset to 0 after loading your save, while not calling it means the timer will resume from its previous value stored within your save. That's good to know. Thanks!
Shivering Bee Posted March 18, 2021 Posted March 18, 2021 Instead of putting items like your QuestStarters in an array, could you put them in a formlist? Some time ago I wrote a small vampirism overhaul mod (never released) where I placed all the mortal and vampire races in two array properties. I later found out that the arrays could not be added to for custom race support, but switching to two formlists worked. ... Okay, I just loaded up Fertility Adventures.esp in CK and looked at FMA_HandlerQuest. Here's what I learned about array properties; once you install the mod, the size of the array is written in stone. You cannot add, or remove, elements without papyrus going all wonky on you. Your two numeric arrays (StageList[] and NextStagePercentage[]) can remain as property arrays because, I assume, that you will not be changing those at any time in the future. You will have to change your other two arrays,QuestStarters[] and Quests[], to formlists because you'll be adding new NPCs to those lists as you develop this mod. That is one of the the issues (I don't know if there are others) causing your clean save requirement. KRB
Recommended Posts
Archived
This topic is now archived and is closed to further replies.