Jump to content

[Papyrus] How to force a SexLab animation to end


Recommended Posts

Heyho,

 

For a little (personal) mod I write, I create a SexLab thread with two NPCs which I want to stop as soon as one of the included actors has LOS to the player (as if they got caught)

I already can get the included actors, I only need the proper way to end the thread.

 

Does someone know how to do it?

Link to comment
3 minutes ago, Mister X said:

Heyho,

 

For a little (personal) mod I write, I create a SexLab thread with two NPCs which I want to stop as soon as one of the included actors has LOS to the player (as if they got caught)

I already can get the included actors, I only need the proper way to end the thread.

 

Does someone know how to do it?

I see something like this in DD scripts. OnHit event on Player alias in main quest if im not wrong or on combat state change

Link to comment

SexLab MCM has an option "Stop Current Animation" on page "Rebuild and Clean". You can use the same thing it does.

 

state StopCurrentAnimations
    event OnSelectST()
        ShowMessage("$SSL_StopRunningAnimations", false)
        ThreadSlots.StopAll()
    endEvent
endState

 

We're interested in "ThreadSlots.StopAll()" specificly. :)

Edited by AndrewLRG
Link to comment
17 hours ago, TRX_Trixter said:

I see something like this in DD scripts. OnHit event on Player alias in main quest if im not wrong or on combat state change

 

Here's the problem, that the player is the one to interrupt the scene on sight, the player isn't involved in the scene. 

 

16 hours ago, AndrewLRG said:

SexLab MCM has an option "Stop Current Animation" on page "Rebuild and Clean". You can use the same thing it does.

 

state StopCurrentAnimations
    event OnSelectST()
        ShowMessage("$SSL_StopRunningAnimations", false)
        ThreadSlots.StopAll()
    endEvent
endState

 

We're interested in "ThreadSlots.StopAll()" specificly. :)

 

Here's the problem that I only want to stop the thread, that I've started, not all of them (if others are active)

Link to comment

Then you must store started thread in some value when it starts and then you will have ability to stop it. As i understand your problem. 

 

For exammple you can make some spell, cust it on target actor, then take a list of all active threads and check them in cycle if current threat "works" with target actor then stop it and finish this cycle.

Edited by TRX_Trixter
Link to comment
4 hours ago, Mister X said:

Here's the problem that I only want to stop the thread, that I've started, not all of them (if others are active)

 

In that case it might look like this:

 

sslThreadSlots Property ThreadSlot Auto

SexLabFramework Property SexLab Auto

 

Function SomeFunction()

     sslThreadController TempController = SexLab.GetController(ThreadID) ; Selecting Controller using your Thread ID

     ThreadSlot.StopThread(TempController) ; Stoping Thread with previously selected Controller

EndFunction

Link to comment

OK, so I have this setup now, which I still need to test but at least it compiles properly:

Spoiler
sslThreadModel slThread ;public variable

Function OnLoad() ;called each game start
  RegisterForModEvent("HookAnimationStart_MyHook", "SexLabStart")
EndFunction

; some code
  
Function StartScene(actor dom, actor sub, ObjectReference bed = none)
  ;some code
    
  slThread = SexLab.NewThread()
  slThread.SetHook("MyHook")
  ;Thread setup
  slThread.StartThread()
EndFunction
    
Event SexLabStart(string eventName, string strArg, float numArg, Form sender)
  While SexLab.IsActorActive(slThread.Positions[1]) ;while "male" position still going strong
    If playerref.IsDetectedBy(slThread.Positions[1]) && (playerref.HasLOS(slThread.Positions[1]) || (slThread.Positions[1]).HasLOS(playerref)) ;if player interrupts scene
      SexLab.ThreadSlots.StopThread(SexLab.GetController(slThread.tid)) ;looks weird, but is needed to get the function and the proper parameters
    EndIf
  EndWhile
EndEvent

 

 

Is there a better way to get this working?

If it helps, I'm trying to "convert" ONights to work with the SL Framework instead. Sadly, I won't publish it, the author doesn't allow it

Link to comment
  • 7 months later...

I've been trying to find a way to end SexLab scenes when a bandit aggressor victimising an NPC, is alerted, alarmed (can't figure out the difference between both terms in CK functions) or goes to combat. I've been looking at SexLab Defeat scripts as I believe it ends sex scenes if aggressor NPCs goes to combat/or alerted, but can't figure out where it's relevant.

 

Is there a simpler way to script that as opposed to depending on LOS?

Link to comment
16 hours ago, hotrack said:

I've been trying to find a way to end SexLab scenes when a bandit aggressor victimising an NPC, is alerted, alarmed (can't figure out the difference between both terms in CK functions) or goes to combat. I've been looking at SexLab Defeat scripts as I believe it ends sex scenes if aggressor NPCs goes to combat/or alerted, but can't figure out where it's relevant.

 

Is there a simpler way to script that as opposed to depending on LOS?

 

I'm not sure if I understand you correctly: any bandit should stop sex with a victim, when they enter combat, right?

That's a totally different foundation than my request. For me, my mod started the sex scene, so I know exactly when it starts and, more important, who participates

 

For you, generally spoken, I would program something like that:

1) Listen to a mod event that signals the start of a sex scene. Those normally give the thread ID and a bool whether the player is involved

2) With the given Thread ID, fetch the participating actors and see if a victim is involved

 

That was the easy part to simply get the actors, now the more complicated

3) When a victim is involved, find a way to attach a script to the aggressor (maybe by starting a new quest that gives you a cloak spell?)

4) Within the aggressor script, listen for the event OnCombatStateChanged to fetch that

5) When this event fires, the aggressor is somehow involved and you can abort the sex act and stop the quest which provided the cloak spell

 

But remember, I'm far from experienced when it comes to Skyrim modding, I'm almost certain that there will be a better way. I'm not even sure if it would work like I described

Link to comment
  • 1 year later...

Sorry for resurrecting this thread but I wanna know, is there a line that can detect whether a thread has stopped running? I started a thread, added a sound to it and I'm trying to write an if statement that says if the thread has stopped running then stop the instance of the sound. 

Link to comment
42 minutes ago, valencia said:

Sorry for resurrecting this thread but I wanna know, is there a line that can detect whether a thread has stopped running? I started a thread, added a sound to it and I'm trying to write an if statement that says if the thread has stopped running then stop the instance of the sound. 

 

As you started the thread you're able to identify it, yes?

If (!Thread.IsLocked)
    ; remove sound
EndIf

with Thread being filled with the one in question, of type sslThreadController or sslThreadModel

 

If you are using Scrab's new SL p+ v2.x you will need another function:

If (Thread.GetStatus() < 2)
  ; remove sound
EndIf

with Thread being of type SexlabThread

Link to comment
24 minutes ago, Mister X said:

 

As you started the thread you're able to identify it, yes?

If (!Thread.IsLocked)
    ; remove sound
EndIf

with Thread being filled with the one in question, of type sslThreadController or sslThreadModel

 

If you are using Scrab's new SL p+ v2.x you will need another function:

If (Thread.GetStatus() < 2)
  ; remove sound
EndIf

with Thread being of type SexlabThread

Thank you so much for replying, I've been trying to solve this shit for like 2 days. I'll just show you my code and tell you the errors that I'm facing:

 

sslBaseAnimation[] anims
	actor[] sexActors = new actor[2]
	string tags = "Foreplay,Kissing,Leito"
	bool[] emptyStrip1 = new bool[33]
	bool[] emptyStrip2 = new bool[33]
	; // Strip head
    emptyStrip1[0] = true
    emptyStrip2[0] = true
    emptyStrip1[12] = true
    emptyStrip2[12] = true
    emptyStrip1[17] = true
    emptyStrip2[17] = true	
    emptyStrip1[18] = true
    emptyStrip2[18] = true	
	
	sexActors[0] = akSpeaker
        sexActors[1] = Game.GetPlayer()	

	anims = SexLab.GetAnimationsByTags(2,tags,TagSuppress = "Zaz",RequireAll = true)
	If (anims.Length == 0)
		tags = "LeadIn,Standing,Kissing"
		anims = SexLab.GetAnimationsByTags(2,tags,TagSuppress = "Sex,Aggressive,Forced,Rape",RequireAll = true)
	EndIf	

    sslThreadModel th = SexLab.NewThread()	
	
	If th.AddActor(sexActors[0]) == -1
		Debug.Notification(sexActors[1].GetLeveledActorBase().GetName() + ": Actor 0 could not be added to Animation.")
		return
	EndIf
		
	If th.AddActor(sexActors[1]) == -1
		Debug.Notification(sexActors[1].GetLeveledActorBase().GetName() + ": Actor 1 could not be added to Animation.")
		return
	EndIf

	th.SetStrip(sexActors[0], emptyStrip1)
	th.SetStrip(sexActors[1], emptyStrip2)
	th.SetAnimations(anims)
                th.SetHook("myHook")
                RegisterForModEvent("HookAnimationEnd_AnimationEnd", "myAnimEndEventHandler")
	th.DisableBedUse(true)
	th.DisableLeadIn(true)
                th.StartThread()
utility.wait(3.0)
AshaKissingSound.Play(Asha)
soundInstanceID = AshaKissingSound.Play(Asha)

Event myAnimEndEventHandler(int tid, bool HasPlayer)
    sslThreadController th = SexLab.GetController(tid)
    Sound.StopInstance(soundInstanceID)
EndEvent


So this code plays the Leito Kissing animation just fine. This is a custom made follower and she has some custom made kissing sounds. I'm trynna insert that into this whole scene. In the sound descriptor I made it loop because different users might have different timers for the animations in sexlab. 

What I'm trying to achieve is simply stop the playback of that sound when the animation ends. 

I've looked at the documentation is sexlab, found out that I can use a hook for that called "AnimationEnd" but this code currently gives me these errors:

- mismatched input 'Event' expecting ENDFUNCTION
- missing EOF at 'EndFunction'

Link to comment

If this really is your code, there are some serious issues with it. 

What function holds all of this? Where do you call that function? Why don't you use a custom event hook?

 

Can you just upload the source file so I can have a look at it?

Link to comment
30 minutes ago, Mister X said:

If this really is your code, there are some serious issues with it. 

What function holds all of this? Where do you call that function? Why don't you use a custom event hook?

 

Can you just upload the source file so I can have a look at it?

This is all located inside the end papyrus fragment of a dialogue. The answer would lead to this fragment. It's not a script that the entire quest inherits. I've changed it now to use a faction called SexLabAnimatingFaction and see if the speaker does not have it which means the animation has stopped then the sound should stop but it did not work. Logic could be wrong here.  Here's the psc file anyway:


 

AshaKissTIF__0304711C.psc

Link to comment

OK, all in all it doesn't look bad, it was just the weird indentation that threw me off ^^

 

You got it right, that you have two options: either you create a hook and and listen to the hook event or you do interval checks.

The first option with the hook would be more performance friendly but it would be a bit of an overkill. The problem there is, you need an extra event listener, meaning another script to catch the AnimationEnd event.

 

The interval checks won't be that good practice but would be more simple. Here your only problem is the IF statement:

If akSpeaker.IsInFaction(SexLabAnimatingFaction) == false
	Sound.StopInstance(soundInstanceID)
EndIf

Why is that? Well, it starts the animation, then checks once if the anim still is running. Afterwards it just shuts down.

 

Instead, simply do it like so:

	[...]
	soundInstanceID = AshaKissingSound.Play(Asha)

	While akSpeaker.IsInFaction(SexLabAnimatingFaction)
		utility.Wait(1.0)
	EndIf
	
	Sound.StopInstance(soundInstanceID)
EndFunction

 

You want to keep the script alive WHILE the animation is still running. After it stopped, leave the loop and remove the sound.

You can use any check for the loop that tells you if the thread is running. IsInFaction() or Thread.IsLocked both should work

Link to comment

You can try to use hooks though, probably can work, too, in the same script. This then would look like that:

[...]
	th.DisableLeadIn(true)
	th.SetHook("AshaKiss")
	RegisterForModEvent("HookAnimationEnding_AshaKiss", "OnAshaKissEnding")
    th.StartThread()
	utility.wait(3.0)
	soundInstanceID = AshaKissingSound.Play(Asha)
;END CODE
EndFunction
;END FRAGMENT

Event OnAshaKissEnding(Int tid, Bool HasPlayer)
	Sound.StopInstance(soundInstanceID)
    UnRegisterForModEvent("HookAnimationEnding_AshaKiss")
EndEvent

 

Link to comment
55 minutes ago, Mister X said:

OK, all in all it doesn't look bad, it was just the weird indentation that threw me off ^^

 

You got it right, that you have two options: either you create a hook and and listen to the hook event or you do interval checks.

The first option with the hook would be more performance friendly but it would be a bit of an overkill. The problem there is, you need an extra event listener, meaning another script to catch the AnimationEnd event.

 

The interval checks won't be that good practice but would be more simple. Here your only problem is the IF statement:

If akSpeaker.IsInFaction(SexLabAnimatingFaction) == false
	Sound.StopInstance(soundInstanceID)
EndIf

Why is that? Well, it starts the animation, then checks once if the anim still is running. Afterwards it just shuts down.

 

Instead, simply do it like so:

	[...]
	soundInstanceID = AshaKissingSound.Play(Asha)

	While akSpeaker.IsInFaction(SexLabAnimatingFaction)
		utility.Wait(1.0)
	EndIf
	
	Sound.StopInstance(soundInstanceID)
EndFunction

 

You want to keep the script alive WHILE the animation is still running. After it stopped, leave the loop and remove the sound.

You can use any check for the loop that tells you if the thread is running. IsInFaction() or Thread.IsLocked both should work

It worked! Thank you so much! just had to replace the endif with endwhile and voila.

 

Durant.jpg

Link to comment
6 hours ago, Mister X said:

Glad to hear that!

 

Oops, rookie error on my end ^^

Hey this is a bit unrelated but do you know of a way to make a quest available after some time? I have like a romance quest that leads to another quest that allows sexlab scenes to happen. The idea is, when you recruit the follower and say a week in-game has passed then the romance quest would be available.

Now I brainstormed a bit, came up with an idea to create a global that saves the time when the follower is first recruited via Utility.GetCurrentGameTime(). This global's value is set in the follower script (the script that allows the follower to follow you when you say follow me I need your help), and then I had an idea to make a script in the romance quest as follows:

 

Scriptname AshaCheckRecruitement extends Quest  

GlobalVariable Property AshaRecruited Auto
GlobalVariable Property AshaRecruitTime Auto

Event OnUpdate()
    ; Check if Asha has been recruited (AshaRecruited == 1) and if 7 in-game days have passed
    if (AshaRecruited.GetValue() == 1 && IsTimeElapsed(7))
        Debug.Notification("You can now smash Asha!") ;
        StopUpdating()
    endif
EndEvent

Function IsTimeElapsed(daysToWait)
 
    Float gameTime = Utility.GetCurrentGameTime()
    
    ; Calculate the time difference between now and when Asha was recruited
    Float timeDifference = gameTime - AshaRecruitTime.GetValue() ;
    ; Convert the time difference to in-game days
    Float daysPassed = timeDifference / 24.0

    ; Check if the specified number of days have passed
    return daysPassed >= daysToWait
EndFunction


The problem here is there's no actual mechanism that makes the quest available/unavailable whether the condition was satisfied or nah. (in this case being the daysPassed being more or equal to daysToWait which is 7 days in-game. It just tells you in the log that you can smash Asha now XD. I'm not aware of any function or mechanism that makes a quest available or not. Would really appreciate it if you can help me here 

Link to comment
1 hour ago, valencia said:

Hey this is a bit unrelated but do you know of a way to make a quest available after some time? I have like a romance quest that leads to another quest that allows sexlab scenes to happen. The idea is, when you recruit the follower and say a week in-game has passed then the romance quest would be available.

 

[snip]


The problem here is there's no actual mechanism that makes the quest available/unavailable whether the condition was satisfied or nah. (in this case being the daysPassed being more or equal to daysToWait which is 7 days in-game. It just tells you in the log that you can smash Asha now XD. I'm not aware of any function or mechanism that makes a quest available or not. Would really appreciate it if you can help me here 

 

You don't really need a script for this, you can do it with just conditions in the Creation Kit (well, almost). You can start a quest from a story manager event, Basically, the game has a list of things to check every time certain things happen, such as level up or location changes. It's used for instance to start some Daedric quests only once you reach level 10. You can start your quest by making a story manager event node that starts the quest, and giving the event node a condition that compares GetCurrentTime to your global, the time of recruitment. If you add this to e.g. the Change Location Event, it'll start your quest (i.e. make it available) the first time a player changes location after the required time has elapsed. The only thing you still need to do via script is to set the global that stores when you recruited Asha.

Edited by Frayed
Link to comment
14 minutes ago, Frayed said:

 

You don't really need a script for this, you can do it with just conditions in the Creation Kit (well, almost). You can start a quest from a story manager event, Basically, the game has a list of things to check every time certain things happen, such as level up or location changes. It's used for instance to start some Daedric quests only once you reach level 10. You can start your quest by making a story manager event node that starts the quest, and giving the event node a condition that compares GetCurrentTime to your global, the time of recruitment. If you add this to e.g. the Change Location Event, it'll start your quest (i.e. make it available) the first time a player changes location after the required time has elapsed. The only thing you still need to do via script is to set the global that stores when you recruited Asha.

That is brilliant. Yes I know about the story manager event but never thought about it that way. So I assume I just need to put the event in quest data of the quest to be Change Location but the condition probably would not work since GetCurrentTime returns time as in for example 14:15 pm. I gotta compare it to GameDaysPassed global or something

Link to comment
25 minutes ago, valencia said:

That is brilliant. Yes I know about the story manager event but never thought about it that way. So I assume I just need to put the event in quest data of the quest to be Change Location but the condition probably would not work since GetCurrentTime returns time as in for example 14:15 pm. I gotta compare it to GameDaysPassed global or something

 

Yes, add a node, add the condition, set start event to the same story manager event in the quest data tab. Somehow I thought GetCurrentTime returned the total time since game start but you're right, it's just the time of day. There's a "Days Passed" field in the player stats that you can probably use: GetPCMiscStat -> Days Passed.

Edited by Frayed
Link to comment
2 hours ago, Frayed said:

 

Yes, add a node, add the condition, set start event to the same story manager event in the quest data tab. Somehow I thought GetCurrentTime returned the total time since game start but you're right, it's just the time of day. There's a "Days Passed" field in the player stats that you can probably use: GetPCMiscStat -> Days Passed.

Hmm, so Utility.GetCurrentGameTime returns date and time for example 01/01/1000 14:32 pm. And Days Passed, returns the number of days passed. I don't think these 2 can be compared. I probably need to change the Global Variable AshaRecruitTime to be something other than date and time.

UPDATE: Oh lord I'm such a colossal idiot. If I simply combine 2 conditions: GetPCMiscStat Days Passed > 7 AND GetGlobalValue AshaRecruited = 1 (which is a global variable that tracks whether Asha is recruited or not then the quest would become available after literally 7 days have passed and if Asha is still recruited -______-. Goddamn the solution was literally right there staring at me  

Edited by valencia
Link to comment
18 hours ago, valencia said:

Hmm, so Utility.GetCurrentGameTime returns date and time for example 01/01/1000 14:32 pm. And Days Passed, returns the number of days passed. I don't think these 2 can be compared. I probably need to change the Global Variable AshaRecruitTime to be something other than date and time.

UPDATE: Oh lord I'm such a colossal idiot. If I simply combine 2 conditions: GetPCMiscStat Days Passed > 7 AND GetGlobalValue AshaRecruited = 1 (which is a global variable that tracks whether Asha is recruited or not then the quest would become available after literally 7 days have passed and if Asha is still recruited -______-. Goddamn the solution was literally right there staring at me  

Just wanna leave an update here in case somebody stumbles on this in the future and has a similar goal. The above method works. BUT it works in new games and the first thing you gotta do is recruit the followers to get the full experience. This won't work if you have a save game already and more than 7 days has passed, instead when you go recruit the follower you'll see the dialogue option for that quest is available which makes no sense. 

Instead here's a better way to do it. Make a global, set its default value to be 0 and float. Next up if you're using a custom follower framework, then go to to the quest. Controller script, add 2 new properties: The GameDaysPassed and your newly created global. Set their appropriate values and then go to the script block where you recruit the follower, named something like "SetFollower". And in there you gotta put something like this:

 

  if (FollowerRecruited.GetValue() == 0 && AshaRecruitTime.GetValue() <= GameDaysPassed.GetValue())
        FollowerRecruited.SetValue(1)
        AshaRecruitTime.SetValue(GameDaysPassed.GetValue() + 3.0)
    endif

Now I made it to be 3 days cause 7 was a bit much. Next up you have to set up the condition of your quest in (quest data) to be GetGlobalValue GameDaysPassed (check use global) and then in the value insert your global. The comparison gotta be >= than your global. So GameDaysPassed is >= AshaRecruitTime. The only problem that I found with this method is that AshaRecruitTime basically resets everytime you dismiss the follower. So the cycle starts over. This is fine with me. But you may have to change that in your case. 

Link to comment
29 minutes ago, valencia said:

The only problem that I found with this method is that AshaRecruitTime basically resets everytime you dismiss the follower. So the cycle starts over. This is fine with me. But you may have to change that in your case. 

 

Quite simple solution for that: define a GlobalVariable named something like "FirstTimeRecruited" and set it to something that can't be possible in real game, I'd recommend "-1".

After this, check if the Global has a negative value before setting a new one. Like that, it only will be set once.

Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

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

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue. For more information, see our Privacy Policy & Terms of Use