Jump to content

Basic papyrus problem


shebop

Recommended Posts

Posted

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.

Posted

All depends on how your function is called.

There is a possibility that it is called twice.

 

Post your script.

 

(Moved to tech support.)

Posted

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.

Posted

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.

Posted

I've always preferred the RegisterforModEvent("HookAnimationEnd", "CheckDone") method for processing things after a SexLab scene. - Seems to be very reliable. 

Posted
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.)

Posted
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.

Posted

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.

 

 

Posted

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.

 

Posted
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?

Posted

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. 

Posted

Ok.  Then a few questions just for clarification's sake.

  1. When is OnInit() called?  I suppose I need to further understand this particular behavior. 
  2. You mention that it occurs at the beginning of the game and again when the quest is started  Why twice? 
  3. 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?
Posted

This is per Papyrus documentation for OnInit():

 

image.thumb.png.0e1b2f08a244d914ed8340a7783837f7.png

 

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).

Posted

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?

Posted

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.

Posted

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.

Posted
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?

Posted

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

 

Posted

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.

Posted
On 6/8/2018 at 6:33 AM, Tyrant99 said:

 

image.thumb.png.0e1b2f08a244d914ed8340a7783837f7.png

 

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()

Archived

This topic is now archived and is closed to further replies.

  • Recently Browsing   0 members

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