shebop Posted June 7, 2018 Posted June 7, 2018 I am experimenting with a quest based story mod that I started a while back that uses the Sexlab Animation Framework. Previously, I got frustrated with some of the issues I was having and have decided to try again. After a sex animation is done, depending on the animation, the player may be given one of three ingredients I have added to the game that are used for part of a quest. and are also usable in alchemy later on. One problem I am having is that every time I use the "player.Additem(ingredient, 1)" method, it adds two of the ingredient instead of one. Does anyone have any idea why? I am guessing it has to do with threading and the function being called twice? The problem with that theory is that I only see the "notification" messages once. This happened with oldrim and is now happening in SE as well.
Tyrant99 Posted June 7, 2018 Posted June 7, 2018 Post the script? Also, this probably belongs under tech support, not DLs...
Guest Posted June 7, 2018 Posted June 7, 2018 All depends on how your function is called. There is a possibility that it is called twice. Post your script. (Moved to tech support.)
shebop Posted June 7, 2018 Author Posted June 7, 2018 Actually I think I may have thought of what is happening but I have a question. When Sexlab plays a "scene" there can be up to five stages/positions/parts. Is each one of those parts a separate animation that triggers "AnimationEnd" when finished? If so, that would explain it. EDIT: The script is fairly complex so I will post it if necessary; but I think it has more to do with me understanding exactly what happens during a traditional set of events in Sexlab.
Guest Posted June 7, 2018 Posted June 7, 2018 No. The sequence of events is: AnimationStarting, AnimationStart, StageChanged, StageChanged, ..., StageChanged, AnimationEnding, AnimationEnd. Be aware that the actual names may be slightly different because I do not have my modding PC here.
Tyrant99 Posted June 7, 2018 Posted June 7, 2018 I've always preferred the RegisterforModEvent("HookAnimationEnd", "CheckDone") method for processing things after a SexLab scene. - Seems to be very reliable.
Guest Posted June 7, 2018 Posted June 7, 2018 54 minutes ago, Tyrant99 said: I've always preferred the RegisterforModEvent("HookAnimationEnd", "CheckDone") method for processing things after a SexLab scene. - Seems to be very reliable. In this case you do a global hook. It is not clear if the anims are played directly by the mod of the OP or not (so it should apply to all SexLab anims.)
Tyrant99 Posted June 7, 2018 Posted June 7, 2018 3 minutes ago, CPU said: In this case you do a global hook. It is not clear if the anims are played directly by the mod of the OP or not (so it should apply to all SexLab anims.) It is a bit hard to diagnose anything for sure as OP hasn't lined out the specifics of how they have things setup. Was just throwing out a shot in the dark suggestion as I've used that method often and never run into any duplication issues similar to what OP is having a problem with... But you're right, it may or may not be applicable in this case.
shebop Posted June 7, 2018 Author Posted June 7, 2018 OK, I guess I will need to share code segments then. This script is active starting with a specific quest and is used to for two other quests and then beyond. When the game loads from a save, it determines which quest is currently active and then sets some global variables accordingly. The script registers to handle end of sex events in the OnInit event. Event OnInit() ; // Register globally onto AnimationEnd, with our callback QuestAdvanceCheck whenever the script is first initialized RegisterForModEvent("AnimationEnd", "QuestAdvanceCheck") Debug.Notification("Quest Sex Monitor is Active") actionTracker.SetValueInt(0) endEvent When an act is finished, the first thing that happens is that the "QuestAdvanceCheck" method determines if the act involved the player. I do this using the following condition: event QuestAdvanceCheck(string eventName, string argString, float argNum, form sender) Debug.Notification("Event Triggered!") Debug.Notification(sender.GetName()) ; // Use the HookController() function to get thread controller that triggered this event sslThreadController controller = SexLab.HookController(argString) if controller.HasPlayer Different things happen depending on the quest stage. So the next thing that happens is it calculates a value depending on quest and stage. For most specific values, the quest will advance. If the value doesn't satisfy any specific value then a final if statement handles all other incidents. It is this section that handles whether or not the player gets an ingredient and which ingredient they get. During this portion of the script, the script determines who was involved in the act, what type of act it was, whether or not the player was the aggressor, and if the aggressor was in a particular faction that is set up at the beginning of the game (which the player may join at a certain point). Only a specific, limited number of NPCs are in this faction (some vanilla, some added by the mod); then depending on the circumstances, it may give the player a particular ingredient. Two types of sex acts can result in an ingredient (this is determined by the "tag" associated with the animation) and who is involved Regardless of the tag setup and who is involved, the basic code that gives the ingredient is nested inside an if statement that determines if the act had a particular tag, and assigns values if the right person is the "aggressor". Finally, if the setup meets the right criteria, it gives the player the ingredient. The basic format is... if animation.HasTag("ARelvantTag") // determine the values of female, male, merite (faction based), depending on who was involved and what role they had in the act ... // based on outcome; each of these variables is assigned in the above ... section if isMerite akPlayer.AddItem(meriteIngredient, 1) elseif isMale akPlayer.AddItem(maleIngredient, 1) elseif isFemale akPlayer.AddItem(femaleIngredienr, 1) endIf endIf EDIT: Out of curiosity, should QuestAdvanceCheck be a function rather than an event? EDIT2: It should also be noted that only one tag uses "if". The others use "elseif" so that only one tag applies (based on priority). In other words, tag sections are mutually exclusive.
Guest Posted June 7, 2018 Posted June 7, 2018 Point one. Do not put the init code in your OnInit, or it will fails to init in case of a game reload (in case you update the mod.) Create a function (DoInit?) and put the code inside, then just call the code from the OnInit. Then add a ReferenceAlias to the player, add a script, and in the event OnPlayerLoadGame, call the previous DoInit function. QuestAdvanceCheck can be an event. There is not much difference in Payrus between events and functions. (Functions can return a value.) You use elseIf in case they are mutually exclusive. Or a set of If in case they are not.
shebop Posted June 8, 2018 Author Posted June 8, 2018 3 hours ago, CPU said: Point one. Do not put the init code in your OnInit, or it will fails to init in case of a game reload (in case you update the mod.) Create a function (DoInit?) and put the code inside, then just call the code from the OnInit. Then add a ReferenceAlias to the player, add a script, and in the event OnPlayerLoadGame, call the previous DoInit function. QuestAdvanceCheck can be an event. There is not much difference in Payrus between events and functions. (Functions can return a value.) You use elseIf in case they are mutually exclusive. Or a set of If in case they are not. I do have a reference alias that uses a script to initialize data related to quests when the game is loaded: Scriptname SBGameLoadScript extends ReferenceAlias QuestSexTracker Property pQST Auto Event OnPlayerLoadGame() ;Debug.Notification("LoadGameCall") pQST.LoadCurrentQuestFromGlobal() endEvent The function it calls is part of the script in my previous post above: Function LoadCurrentQuestFromGlobal() int i = questTracker.GetValueInt() if i == 0 CurrentQuest = SBDBq0 elseif i == 1 CurrentQuest = SBDBq1 elseif i == 2 CurrentQuest = SBDBq2 endIf endFunction 1. As you can see it doesn't call the method in the way you suggest. So far I haven't seen a need to. I am sure you know better than me regarding how I should implement/maintain scripts like this to avoid other problems in the future but I would like to understand the reason so that I can make sure that I don't break anything in the process... 2. Right now I am more concerned with the the only thing that doesn't work properly... the fact that the QuestAdvanceCheck function seems to be called twice. There is no way that the code itself inside the function is responsible for executing twice. (I am a pretty experienced programmer... been doing C# and C++ for years... though it would help if I could step through the code to make sure it is completely error free; my only gap of knowledge are all the ins and outs of papyrus). Is this related to the OnInit code?
Tyrant99 Posted June 8, 2018 Posted June 8, 2018 My guess would be that it has something to do with the way you are calling OnInit(). You mentioned that you're using that script for multiple Quests. OnInit() will fire on Game Start regardless of whether a Quest is running, and it will fire again when the Quest that it's attached to is started. I don't believe that a GlobalVariable filter of 'which Quest is running' would stop it. I.E. Events could be running on Quests that aren't started. To test, you could put some differentiated debug notifications in your different registered events to determine exactly which events are firing (and if multiple are firing). If it is duplicate OnInit() events causing it, it might be solvable by encapsulating them within States, or else, add more logic to the Events that are firing so they have additional conditions filtering.
shebop Posted June 8, 2018 Author Posted June 8, 2018 Ok. Then a few questions just for clarification's sake. When is OnInit() called? I suppose I need to further understand this particular behavior. You mention that it occurs at the beginning of the game and again when the quest is started Why twice? I suppose that means I am registering for multiple events like you said. How come then the notification only appear once? Do notifications fail to appear if there is a flood of them? Or if they say the same thing?
Tyrant99 Posted June 8, 2018 Posted June 8, 2018 This is per Papyrus documentation for OnInit(): As for why it's done this way, I suppose you'd have to ask Bethesda... If you have a notification that's saying the same thing, the OnInit would fire simultaneously on load, so it may blend together in the way that it appears. (It actually is getting fired twice but doesn't look like it.) If you have different notifications, then I would expect that you would see both. (Unless there is still somehow some interference with how they display due to simultaneous calls).
shebop Posted June 8, 2018 Author Posted June 8, 2018 Thanks Tyrant. One more question to make sure I understand the engine. I am not sure if you are familiar with OOP and the term instances, but hopefully this will makes sense. Lets say I have a script that I add to multiple quests (as is the case here). Does each quest share the variables that are part of the script? Or is the script instanced per quest, meaning that the values of the properties are unique to that instance only? If I change a variable for a quest script for one quest, does it change for all quests or just that particular quest?
Guest Posted June 8, 2018 Posted June 8, 2018 Scripts are local instances. So each quest will have its own values for variables and properties. Functions and events are also re-entrant. So if you call a script function from another script, the local variables will be different in the two runs (also if they are run in parallel.) But global variables (the ones outside functions and events) will be shared between all calls to the same instance of the script.
Tyrant99 Posted June 8, 2018 Posted June 8, 2018 I think CPU answered it, don't know that there's much more to add... If you are using the same script for multiple Quests (or other things), they can be treated as localized instances. But, you may want to take some care with how you are filling out properties as to whether things stay localized or whether the values are shared by all the scripts. For instance: Int Property MyNumber Auto; This could be set up with a unique integer for each version of the same script, whatever Int you fill out in the Properties will be unique to that script instance and that Quest (or object). Int Property MyNumber = 5 Auto; In this case, you've changed the default Int for all versions of this script. If you went into a different Quest and set Int Property MyNumber = 6 Auto; then your previous change to 5 in the other Quest would get changed to 6. - But, you could still go into properties for that script instance and change the default value to a unique Int value. If the property was left at default value, it would not be unique. Int MyNumber = 5; If you were using a direct variable like this, say in a function or event, if you change the value for one version of the script, it will change for all. The same concept could be used for other kinds of properties. (Except for things that are fully externally defined such as GlobalVariables). So, to get a unique localized value of the 'MyNumber' Int, you would use the first method and fill out the Int property. The 2nd method would also work if you specifically changed the Int property in places where you didn't want it to = 5. The other method would result in the same 'MyNumber' Int value being used for all places where the same script was being used.
shebop Posted June 9, 2018 Author Posted June 9, 2018 12 hours ago, Tyrant99 said: I think CPU answered it, don't know that there's much more to add... For instance: Int Property MyNumber Auto; This could be set up with a unique integer for each version of the same script, whatever Int you fill out in the Properties will be unique to that script instance and that Quest (or object). Int Property MyNumber = 5 Auto; In this case, you've changed the default Int for all versions of this script. If you went into a different Quest and set Int Property MyNumber = 6 Auto; then your previous change to 5 in the other Quest would get changed to 6. - But, you could still go into properties for that script instance and change the default value to a unique Int value. If the property was left at default value, it would not be unique. Int MyNumber = 5; If you were using a direct variable like this, say in a function or event, if you change the value for one version of the script, it will change for all. The same concept could be used for other kinds of properties. (Except for things that are fully externally defined such as GlobalVariables). So, to get a unique localized value of the 'MyNumber' Int, you would use the first method and fill out the Int property. The 2nd method would also work if you specifically changed the Int property in places where you didn't want it to = 5. The other method would result in the same 'MyNumber' Int value being used for all places where the same script was being used. Thanks. That was helpful. Is this all documented somewhere that isn't full of all the beginner stuff? Like a simple reference/primer for people who already have programming experience?
Tyrant99 Posted June 9, 2018 Posted June 9, 2018 This might help you with some of the Papyrus minutiae.
shebop Posted June 9, 2018 Author Posted June 9, 2018 Hopefully the last question. I am assuming that the 'self' variable refers to the instance being extended and not the script. For example, lets say I create a script 'MyQuestScript' that extends Quest and is assigned to several quests. In this case, I am assuming that 'self' refers to the quest. If that is correct, the following code would do what is described in the comment: ; assume that ActiveQuest is a Quest property which is set dynamically based on a global variable by calling a method of MyQuestSCript when a save loads ; reference from a fragment if (ActiveQuest == self) ; will let us know that the quest the script is attached to is the active quest DoWhataver() endIf
Guest Posted June 9, 2018 Posted June 9, 2018 Correct. "Self" points to the form where the script instance is attached to. Be aware that some forms do not have "instances" (Quests, ReferenceAliases, Cells, ActorBase) while other forms have instances (ObjectReference, Actor, Armor, Weapon) And, fun fact, imagine that you have a Quest called myQuest, that has a script attached called myQuestScript. Inside the script "Self" is the Quest, but (Self as myQuestScript) is the script.
yatol Posted June 9, 2018 Posted June 9, 2018 On 6/8/2018 at 6:33 AM, Tyrant99 said: As for why it's done this way, I suppose you'd have to ask Bethesda... if you use event onload(), stuff in the onload() is run everytime the game load whatever have that thing if you use event oninit(), stuff in the oninit() is only run the first time the game load whatever have that thing saw some if doonce == true somewhere no need for those useless checks if you put the stuff you don't want redone in oninit()
Recommended Posts
Archived
This topic is now archived and is closed to further replies.