Scriptname zbfSexLab extends sslAnimationFactory

zbfBondageShell Property zbf Auto
zbfConfig Property Config Auto

; Framework
SexLabFramework Property SexLab Auto Hidden
Float Property Distance = -45.0 AutoReadOnly

String[] Property sAnimList Auto
zbfSexLabBaseEntry[] Property Entries Auto Hidden
zbfSexLabBaseEntry[] Property SpecialEntries Auto Hidden
zbfSexLabBaseEntry[] Property BaseSlots Auto

Sound Property Silent Auto
Sound[] Property Female01 Auto
Sound[] Property Male01 Auto

sslBaseAnimation Property TempAnimation Auto

; Keywords for override handling
Keyword Property zbfWornGag Auto

; SexLab voice slots for overrides
sslBaseVoice Property MaleGagVoice01 Auto
sslBaseVoice Property FemaleGagVoice01 Auto

; System for borrowing animation objects
sslBaseAnimation[] Property BorrowSlots Auto	; Objects that are borrowed
String[] Property BorrowModNames Auto			; Mod names that have borrowed slots, "" means slot is free

; Retrieves all base animation entries
; 
; Regular animations are animations that do not require any special gear or circumstance. These
; are safe to pick up and filter based on bindings and general circumstances (gags, chastity belts, etc)
; 
; Special entries generally require something special to make sense. They may pop up pillories, require
; crotch ropes, chastitiy belts, and so on to make sense. These should generally be hand picked or
; carefully selected (then further filtered, based on circumstances).
; 
; This division is set up to make sure that pillories and so on do not pop up randomly if new types of
; pillory animations, chaste animations, and so on are added.
; 
zbfSexLabBaseEntry[] Function GetEntries(Bool abRegularEntries = True, Bool abSpecialEntries = False)
	Int i
	Int j
	zbfSexLabBaseEntry[] filtered = New zbfSexLabBaseEntry[15] ; No dynamic allocation of required size
	j = 0
	i = Entries.Length
	While (i > 0) && (abRegularEntries)
		i -= 1
		filtered[j] = Entries[i]
		j += 1
	EndWhile
	i = SpecialEntries.Length
	While (i > 0) && (abSpecialEntries)
		i -= 1
		filtered[j] = SpecialEntries[i]
		j += 1
	EndWhile
	Return filtered
EndFunction

; Helper function to create an array of strings
; 
String[] Function StrList(String asS1 = "", String asS2 = "", String asS3 = "", String asS4 = "", String asS5 = "", String asS6 = "")
	String[] list
	If asS1 == ""
		Return list ; Just an empty list then ....
	ElseIf asS2 == ""
		list = New String[1]
		list[0] = asS1
	ElseIf asS3 == ""
		list = New String[2]
		list[0] = asS1
		list[1] = asS2
	ElseIf asS4 == ""
		list = New String[3]
		list[0] = asS1
		list[1] = asS2
		list[2] = asS3
	ElseIf asS5 == ""
		list = New String[4]
		list[0] = asS1
		list[1] = asS2
		list[2] = asS3
		list[3] = asS4
	ElseIf asS6 == ""
		list = New String[5]
		list[0] = asS1
		list[1] = asS2
		list[2] = asS3
		list[3] = asS4
		list[4] = asS5
	Else
		list = New String[6]
		list[0] = asS1
		list[1] = asS2
		list[2] = asS3
		list[3] = asS4
		list[4] = asS5
		list[5] = asS6
	EndIf
	Return list
EndFunction

; Filters a list of entries on the specified actor id.
; 
; The function returns a list of filtered entries. Typically this function is called multiple 
; times on a base list to arrive at the final filtered result.
; 
; If aiActorId is set to 0, the global list of tags (for that entry) is filtered. Otherwise the
; specified actor position is filtered.
; 
; For a walkthrough of what to filter, please see zbfSexLabEntryBase which explains this in more
; detail.
; 
Function FilterEntries(zbfSexLabBaseEntry[] akList, Int aiActorId, String[] asRequired, String[] asForbidden)
	Log("FilterEntries", "Filtering on " + i + " require: " + asRequired + ", blocked: " + asForbidden)
	Int i = akList.Length
	While i > 0
		i -= 1
		If akList[i] != None
			If !akList[i].IsValidTags(aiActorId, asRequired, asForbidden)
				akList[i] = None
			EndIf
		EndIf
	EndWhile
EndFunction

; Filters a list of entries on the actor count
; 
; The function returns a list of filtered entries. Only entries with the specified actor count range
; remain in the list after filtering.
; 
; For a walkthrough of what to filter, please see zbfSexLabEntryBase which explains this in more
; detail.
; 
Function FilterEntriesOnActorCount(zbfSexLabBaseEntry[] akList, Int aiMinActors, Int aiMaxActors)
	Log("FilterEntries", "Removing entries with less than " + (aiMinActors - 1) + " or more than " + (aiMaxActors + 1) + ".")
	Int i = akList.Length
	While i > 0
		i -= 1
		zbfSexLabBaseEntry entry = akList[i]
		If entry != None
			If (entry.NumActors < aiMinActors) || (entry.NumActors > aiMaxActors)
				akList[i] = None
			EndIf
		EndIf
	EndWhile
EndFunction

; Fetches a random entry from a list of entries.
; 
; The list of entries need not be sorted or otherwise arranged in any way. None entries are ignored
; and a random entry is provided, assuming that there exists at least one defined entry in the array.
; 
; If no entry can be provided, None is returned.
; 
zbfSexLabBaseEntry Function GetRandomEntry(zbfSexLabBaseEntry[] akEntries)
	Int iActive
	Int i
	Int selected
	
	; Count active entries
	iActive = 0
	i = akEntries.Length
	While i > 0
		i -= 1
		iActive += (akEntries[i] != None) As Int
	EndWhile
	If iActive == 0
		Return None
	EndIf

	; Select random entry
	selected = Utility.RandomInt(1, iActive)
	iActive = 0
	i = akEntries.Length
	While i > 0
		i -= 1
		iActive += (akEntries[i] != None) As Int
		If iActive == selected
			Return akEntries[i]
		EndIf
	EndWhile
	Return None
EndFunction

; Sets up a new SexLab thread with automatic filtering to play animations from.
; 
; This function will completely prepare a SexLab thread to play animations from. Strip slots, bedding, actor positions,
; actual animation and all other objects are automatically set up.
; 
; Animation is created dynamically based on actor restraints.
; 
; Returns None if no suitable animation could be set up, or if an error otherwise occured.
; Returns a sslThreadController for the new thread otherwise.
; 
sslThreadModel Function CreateThreadAuto(Actor akActor1, Actor akActor2 = None, Actor akActor3 = None, Actor akActor4 = None, String asAnimBaseId = "")
	Log("CreateThread", "Running on actors " + akActor1 + ", " + akActor2 + ", " + akActor3 + ", " + akActor4)
	Log("CreateThread", "Setting animation preference to " + asAnimBaseId)

	; Find a suitable entry to create animation from
	zbfSexLabBaseEntry[] list
	If asAnimBaseId != ""
		list = New zbfSexLabBaseEntry[1]
		list[0] = GetEntryById(asAnimBaseId)
	Else
		list = GetEntries()
	EndIf

	Int numActors = 0
	numActors += (akActor1 != None) As Int
	numActors += (akActor2 != None) As Int
	numActors += (akActor3 != None) As Int
	numActors += (akActor4 != None) As Int
	Log("CreateThread", "Number of selected actors: " + numActors)

	; Filter entries - Required and Blocked calls work equally well on "None" Actors (returning an empty list)
	FilterEntriesOnActorCount(list, numActors, numActors)
	FilterEntries(list, 1, zbf.GetRequiredSexLabTags(akActor1), zbf.GetBlockedSexLabTags(akActor1))
	FilterEntries(list, 2, zbf.GetRequiredSexLabTags(akActor2), zbf.GetBlockedSexLabTags(akActor2))
	FilterEntries(list, 3, zbf.GetRequiredSexLabTags(akActor3), zbf.GetBlockedSexLabTags(akActor3))
	FilterEntries(list, 4, zbf.GetRequiredSexLabTags(akActor4), zbf.GetBlockedSexLabTags(akActor4))

	zbfSexLabBaseEntry entry = GetRandomEntry(list)
	If entry == None
		; An error occured, no animation could be found. Return None - can't play
		Log("CreateThread", "Failed to find suitable animation under constraint (" + asAnimBaseId + ")", iWarning)
		Return None
	EndIf

	; Animation base was found - set up the animation
	sslBaseAnimation anim = NewAnimation("ZaZ Animation Pack")
	String sAnim1 = zbf.GetSexLabAnimationName(entry, zbf.GetBindTypeFromWornKeywords(akActor1))
	String sAnim2 = zbf.GetSexLabAnimationName(entry, zbf.GetBindTypeFromWornKeywords(akActor2))
	String sAnim3 = zbf.GetSexLabAnimationName(entry, zbf.GetBindTypeFromWornKeywords(akActor3))
	String sAnim4 = zbf.GetSexLabAnimationName(entry, zbf.GetBindTypeFromWornKeywords(akActor4))
	DefineAnimationFromEntry(entry.BaseId, anim, "Zap" + entry.BaseId, sAnim1, sAnim2, sAnim3, sAnim4)

	sslBaseAnimation[] Animations = New sslBaseAnimation[1]
	Animations[0] = anim

	sslThreadModel thread = SexLab.NewThread()
	AddPrepareActor(thread, akActor1)
	AddPrepareActor(thread, akActor2)
	AddPrepareActor(thread, akActor3)
	AddPrepareActor(thread, akActor4)

	thread.SetAnimations(Animations)
	thread.SetBedding(0)
	thread.DisableLeadIn(disabling = True)
	thread.DisableBedUse(disabling = True)

	Return thread
EndFunction

; Searches the registered animation base entries for the specified id.
; 
; Returns None if the Id could not be found.
; 
zbfSexLabBaseEntry Function GetEntryById(String asId)
	Int i

	i = Entries.Length
	While i > 0
		i -= 1
		If (Entries[i] != None) && (Entries[i].BaseId == asId)
			Return Entries[i]
		EndIf
	EndWhile
	i = SpecialEntries.Length
	While i > 0
		i -= 1
		If (SpecialEntries[i] != None) && (SpecialEntries[i].BaseId == asId)
			Return SpecialEntries[i]
		EndIf
	EndWhile

	Return None ; Failure
EndFunction

; Searches the list of entries for a vanilla id
; 
; Useful to apply if you want to find an animation to replace a vanilla entry with something else. Not very
; useful otherwise
; 
zbfSexLabBaseEntry Function GetEntryByVanillaId(String asId)
	Int i = Entries.Length
	While i > 0
		i -= 1
		If (Entries[i] != None) && (Entries[i].VanillaBaseId == asId)
			Return Entries[i]
		EndIf
	EndWhile
	Return None ; Failure
EndFunction

; Fetches a new animation to set up
; 
; This returns a new animation that can be sent to DefineAnimationFromEntry or similar.
; Note that sslBaseAnimations returned in this manner are supposed to be played in short
; order. Skyrim can not actually dynamically allocate animations, and the available empty
; slots can run out.
; 
; If this function fails to run correctly, None is returned.
;
; Note that an animation created in this way will automatically be returned to the pool of
; available animations after playing.
; 
sslBaseAnimation Function NewAnimation(String asModName)
	sslBaseAnimation anim = None
	Int i = BorrowModNames.Find("")
	If i != -1
		BorrowModNames[i] = asModName
		anim = BorrowSlots[i]
		anim.Initialize()
	EndIf
	Return anim
EndFunction

; Returns an animation to the animation pool.
; 
; This function will only act on animations created with NewAnimation.
; 
; Note that this function should almost never be called. Animations are automatically 
; disposed after playing. Only if the animation is not played should this function be
; called to "manually" return the animation slot to the pool.
; 
Function ReleaseAnimation(sslBaseAnimation akAnim)
	Int i = BorrowSlots.Find(akAnim)
	If (i != -1) && (BorrowModNames[i] != "")
		BorrowModNames[i] = ""
	EndIf
EndFunction

; Defines an animation given a base entry
; 
; This function can be used to set up dynamic animations to support combinations of bindings not 
; already specified in the SexLab registrations.
; 
; Animation base names need to be provided, but can be constructed from the entry if needed. See the
; BaseName and VanillaBaseName properties of zbfSexLabBaseEntry for a proper description.
; 
; Example:
; DefineAnimationFromEntry(akEntry, akAnim, "MyAnimation", "ZapWri" + akEntry.BaseName, akEntry.VanillaBaseName)
; - Will set up an animation based on akEntry in the akAnim variable. Actor 1 is bound at the wrists, and Actor 2 will
;   use the vanilla animations.
; 
; DefineAnimationFromEntry(akEntry, akAnim, "MyYokeAnimation", "ZapYoke" + akEntry.BaseName, "ZapArmb" + akEntry.BaseName)
; - Will set up an animation in the akAnim variable. Actor 1 is bound with a yoke, and Actor 2 is bound with an armbinder.
; 
; It is necessary to first filter suitable animation entries using the other support functions in this class, since not
; all animations are defined for every situation. If animation string names are provided that are not found in the animation
; repository, then these animations will fail to play, and the actor will stay in the previous animation or idle.
; 
Function DefineAnimationFromEntry(String asBaseId, sslBaseAnimation akAnim, String asName, String asAnim1, String asAnim2 = "", String asAnim3 = "", String asAnim4 = "")
	Log("DefineAnimationFromEntry", "Called on " + asBaseId + ", as " + asName + " and with bases (" + asAnim1 + ", " + asAnim2 +")")
	; Check which akEntry was called, and delegate to the appropriate function
	If asBaseId == "SkullFuck01"
		DefineSkullFuck01(akAnim, asName, asAnim1, asAnim2)
	ElseIf asBaseId == "BoobJob01"
		DefineBoobJob01(akAnim, asName, asAnim1, asAnim2)
	ElseIf asBaseId == "Missionary01"
		DefineMissionary01(akAnim, asName, asAnim1, asAnim2)
	ElseIf asBaseId == "Doggy01"
		DefineDoggy01(akAnim, asName, asAnim1, asAnim2)
	ElseIf asBaseId == "Lesbian01"
		DefineLesbian01(akAnim, asName, asAnim1, asAnim2)
	ElseIf asBaseId == "PilloryLick01"
		DefinePilloryLick01(akAnim, asName, asAnim1, asAnim2)
	ElseIf asBaseId == "PillorySex01"
		DefinePillorySex01(akAnim, asName, asAnim1, asAnim2)
	ElseIf asBaseId == "Missionary02"
		DefineMissionary02(akAnim, asName, asAnim1, asAnim2)
	ElseIf asBaseId == "MilkMachine01"
		DefineMilkMachine01(akAnim, asName, asAnim1)
	ElseIf asBaseId == "Furo01"
		DefineFuro01(akAnim, asName, asAnim1)
	ElseIf asBaseId == "Spank01"
		DefineSpank01(akAnim, asName, asAnim1, asAnim2)
	Else
		Log("DefineAnimationFromEntry", "BaseId(" + asBaseId + ") was not found in the repository.", iError)
		Return
	EndIf

	akAnim.AddTag("NoSwap") ; Custom animations should not be touched
	akAnim.Save(-1)
EndFunction


; 
; Module protected functions
; 
; Do not call these as part of the API
; 

; Dynamic linking method.
Function LinkDependency()
	If SexLab == None
		SexLab = SexLabUtil.GetAPI()
		If SexLab != None		
			PrepareFactory()
		EndIf
	EndIf
EndFunction

; Returns true if dynamic linking succeeded.
Bool Function HasSexLab()
	Return SexLab != None
EndFunction

; Returns registered SexLab version
Int Function GetVersion()
	If SexLab != None
		Return SexLabUtil.GetVersion()
	EndIf
	Return 0
EndFunction

; Returns registered SexLab version
String Function GetStringVer()
	Return SexLabUtil.GetStringVer()
EndFunction

String Function GetAnimName(Int i)
	Return sAnimList[i]
EndFunction

Int Function GetAnimCount()
	Return sAnimList.Length
EndFunction

Bool Function GetAnimRegistered(Int i)
	Return (SexLab.FindAnimationByName(sAnimList[i]) != -1)
EndFunction

Bool Function IsFullyRegistered()
	If SexLab == None
		Return False
	EndIf

	Int i = sAnimList.Length - 1
	While i >= 0
		If SexLab.FindAnimationByName(sAnimList[i]) == -1
			Return False
		EndIf
		i -= 1
	EndWhile
	Return True
EndFunction

Function RegisterForSexLab()
	Log("RegisterForSexLab", "Registering SexLab animations.")
	Log("RegisterForSexLab", "SexLab connection was not set up. Registration will do nothing.", iError, SexLab == None)
	
	; Retrieve SexLab Integration
	If SexLab != None
		RegisterAnimation("PilloryBoundSex01")
		RegisterAnimation("PilloryBoundSex02")
		RegisterAnimation("PillorySex01")
		RegisterAnimation("PillorySex02")
		RegisterAnimation("PilloryTorment01")
		RegisterAnimation("PilloryTorment02")

		RegisterAnimation("BoundDoggyStyle")
		RegisterAnimation("ArmbinderDoggyStyle")
		RegisterAnimation("BothBoundDoggyStyle")
		RegisterAnimation("BothArmbinderDoggyStyle")

		RegisterAnimation("BoundMissionary")
		RegisterAnimation("ArmbinderMissionary")

		RegisterAnimation("BoundSkullFuck")
		RegisterAnimation("ArmbinderSkullFuck")

		RegisterAnimation("BoundLesbian")
		RegisterAnimation("BothBoundLesbian")
		RegisterAnimation("ArmbinderLesbian")
		RegisterAnimation("BothArmbinderLesbian")

		RegisterAnimation("ArmbinderArrokBoobjob")
		RegisterAnimation("BoundArrokBoobjob")
	EndIf

	Log("RegisterForSexLab", "Done!")
EndFunction

Function RebuildBaseAnimations()
	Int i
	Int j

	i = BaseSlots.Length
	While i > 0
		i -= 1
		BaseSlots[i].SetDefaults()
	EndWhile

	; Distribute slots between the special and regular entries
	Entries = New zbfSexLabBaseEntry[6]
	j = 0
	i = Entries.Length
	While i > 0
		i -= 1
		Entries[i] = BaseSlots[j]
		j += 1
	EndWhile
	SpecialEntries = New zbfSexLabBaseEntry[5]
	i = SpecialEntries.Length
	While i > 0
		i -= 1
		SpecialEntries[i] = BaseSlots[j]
		j += 1
	EndWhile

	; Define all the base animations
	DefineMissionary01Base(Entries[0])
	DefineSkullfuck01Base(Entries[1])
	DefineBoobJob01Base(Entries[2])
	DefineLesbian01Base(Entries[3])
	DefineDoggy01Base(Entries[4])
	DefineMissionary02Base(Entries[5])

	DefinePilloryLick01Base(SpecialEntries[0])
	DefinePillorySex01Base(SpecialEntries[1])
	DefineMilkMachine01Base(SpecialEntries[2])
	DefineFuro01Base(SpecialEntries[3])
	DefineSpank01Base(SpecialEntries[4])

	Log("RebuildBaseAnimations", "Animation entries defined.")
	Log("RebuildBaseAnimations", "        Entries - " + Entries)
	Log("RebuildBaseAnimations", "Special entries - " + SpecialEntries)
EndFunction

Function RebuildVoices()
	DefineFemaleGagVoice01(FemaleGagVoice01)
	DefineMaleGagVoice01(MaleGagVoice01)
EndFunction

Function RegisterForSexLabEvents()
	RegisterForModEvent("HookAnimationStart", "OnAnimationStart")
	RegisterForModEvent("HookAnimationEnd", "OnAnimationEnd")
EndFunction

Function AdjustActorAlias(sslThreadController akController, Actor akActor)
	sslActorAlias actAlias = akController.ActorAlias(akActor)
	sslBaseVoice voice = actAlias.GetVoice()

	If akActor.WornHasKeyword(zbfWornGag) && !voice.HasTag("NoSwap") && Config.bOverrideSexLabSound
		ActorBase base = akActor.GetActorBase()
		If base.GetSex() == 1 ; Female
			actAlias.SetVoice(FemaleGagVoice01, ForceSilence = Config.bForceSilenceSexLabSound)
		ElseIf base.GetSex() == 0 ; Male
			actAlias.SetVoice(MaleGagVoice01, ForceSilence = Config.bForceSilenceSexLabSound)
		EndIf
	EndIf
EndFunction

Event OnAnimationStart(Int aiThreadId, Bool abHasPlayer)
	Log("OnAnimationStart", "aiThreadId == " + aiThreadId)
	sslThreadController controller = SexLab.ThreadSlots.GetController(aiThreadId)
	
	Actor[] animActors = controller.Positions
	Int i = animActors.Length
	While i > 0
		i -= 1
		If animActors[i] != None
			AdjustActorAlias(controller, animActors[i])
		EndIf
	EndWhile
EndEvent

Event OnAnimationEnd(Int aiThreadId, Bool abHasPlayer)
	Log("OnAnimationEnd", "aiThreadId == " + aiThreadId)
	sslThreadController controller = SexLab.ThreadSlots.GetController(aiThreadId)

	ReleaseAnimation(controller.Animation)
EndEvent

Function SetAnimationDefaults(sslBaseAnimation akAnim, String asName, Sound akSound, Int aiContentType)
	akAnim.Registry = "Zap" + asName
	akAnim.Name = asName
	
	akAnim.SetContent(aiContentType)
	akAnim.SoundFX = akSound

	akAnim.AddTag("ZaZ")
	akAnim.AddTag("Sex")
EndFunction

Function AppendAllIdles(sslBaseAnimation akAnim, Int aiActor, String asPre, String asPost, String[] asIdles, Float afDistance = -45.0, Int aiSos = 0, Bool abStrapOn = False)
	Int i = 0
	While i < asIdles.Length
		String sFullName = asPre + asIdles[i] + asPost
		akAnim.AddPositionStage(aiActor, sFullName, afDistance, sos = aiSos, strapon = abStrapOn, silent = false, openMouth = false)
		i += 1
	EndWhile
EndFunction

Function SetPilloryDefaults(sslBaseAnimation akAnim, String asName, Sound akSound, Int aiContentType, String[] asIdles)
	SetAnimationDefaults(akAnim, asName, akSound, aiContentType)
	
	Int B = akAnim.AddPosition(Female)
	AppendAllIdles(akAnim, B, "ZazAPPill", "B", asIdles, afDistance = 0, aiSos = 0)

	Int A = akAnim.AddPosition(Male)
	AppendAllIdles(akAnim, A, "ZazAPPill", "A", asIdles, afDistance = -45.0, aiSos = 3, abStrapOn = True)

	akAnim.AddTag("Pillory")
	akAnim.AddTag("Behind")
EndFunction

Function SetVoiceDefaults(sslBaseVoice akVoice, String asName, Bool abFemale)
	akVoice.Initialize()
	
	akVoice.Registry = "Zap" + asName
	akVoice.Name = asName
	akVoice.Gender = (abFemale as Int)
	akVoice.Enabled = False
	
	akVoice.AddTag("ZaZ")
EndFunction

Function FinalizeVoiceDefaults(sslBaseVoice akVoice)
	akVoice.AddTag("NoSwap")
	akVoice.Save(-1)
EndFunction

Function DefineFemaleGagVoice01(sslBaseVoice akVoice)
	SetVoiceDefaults(akVoice, "Female Gagged", abFemale = True)

	akVoice.Mild = Female01[0]
	akVoice.Medium = Female01[1]
	akVoice.Hot = Female01[2]

	akVoice.AddTag("Gagged")
	FinalizeVoiceDefaults(akVoice)
EndFunction

Function DefineMaleGagVoice01(sslBaseVoice akVoice)
	SetVoiceDefaults(akVoice, "Male Gagged", abFemale = False)

	akVoice.Mild = Male01[0]
	akVoice.Medium = Male01[1]
	akVoice.Hot = Male01[2]

	akVoice.AddTag("Gagged")
	FinalizeVoiceDefaults(akVoice)
EndFunction

Function DefinePillorySex01(sslBaseAnimation akAnim, String asName, String asAnim1, String asAnim2)
	SetAnimationDefaults(akAnim, asName, Squishing, Sexual)

	Int B = akAnim.AddPosition(Female, AddCum = Vaginal)
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S1")
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S2")
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S3")
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S4")
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S5")
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S6")

	Int A = akAnim.AddPosition(Male)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S1", -45.0, sos = 3, strapOn = True)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S2", -45.0, sos = 3, strapOn = True)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S3", -45.0, sos = 3, strapOn = True)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S4", -45.0, sos = 3, strapOn = True)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S5", -45.0, sos = 3, strapOn = True)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S6", -45.0, sos = 3, strapOn = True)

	akAnim.AddTag("Pillory")
	akAnim.AddTag("Behind")
	akAnim.AddTag("Vaginal")
	akAnim.AddTag("Aggressive")
EndFunction

Function PilloryBoundSex01(Int aiId)
	sslBaseAnimation anim = Create(aiId)
	DefinePillorySex01(anim, "$ZazAP_PilloryBoundSex01", "ZapPillorySex01", "ZapWriPillorySex01")
	anim.AddTag("SubSub")
	anim.Save(aiId)
EndFunction

Function DefinePilloryLick01(sslBaseAnimation akAnim, String asName, String asAnim1, String asAnim2)
	SetAnimationDefaults(akAnim, asName, Sucking, Sexual)

	Int B = akAnim.AddPosition(Female)
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S1")
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S1")
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S1")

	Int A = akAnim.AddPosition(Male)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S1", Distance, sos = 5, strapOn = False, silent = True)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S1", Distance, sos = 5, strapOn = False, silent = True)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S1", Distance, sos = 7, strapOn = False, silent = True)

	akAnim.AddTag("Pillory")
	akAnim.AddTag("Behind")
	akAnim.AddTag("Oral")
EndFunction

Function PilloryBoundSex02(Int aiId)
	sslBaseAnimation anim = Create(aiId)
	DefinePilloryLick01(anim, "$ZazAP_PilloryBoundSex02", "ZapPilloryLick01", "ZapWriPilloryLick01")
	anim.AddTag("SubSub")
	anim.Save(aiId)
EndFunction

Function PillorySex01(Int aiId)
	sslBaseAnimation anim = Create(aiId)

	String[] sIdles = New String[7]
	sIdles[0] = "SXEnter"
	sIdles[1] = "SXHold01"
	sIdles[2] = "SXHold02"
	sIdles[3] = "SXSpank01"
	sIdles[4] = "SXHip03"
	sIdles[5] = "SXEND01"
	sIdles[6] = "SXExit"

	SetPilloryDefaults(anim, "$ZazAP_PillorySex01", Squishing, Sexual, sIdles)

	anim.AddTag("Aggressive")
	anim.AddTag("Vaginal")
	anim.AddTag("DomSub")

	anim.Save(aiId)
EndFunction

Function PillorySex02(Int aiId)
	sslBaseAnimation anim = Create(aiId)
	SetAnimationDefaults(anim, "$ZazAP_PillorySex02", Squishing, Sexual)

	Int B = anim.AddPosition(Female, AddCum = Vaginal)
	anim.AddPositionStage(B, "ZazAPPillSXEnterB")
	anim.AddPositionStage(B, "ZazAPPillSXHold02B")
	anim.AddPositionStage(B, "ZazAPPillSXHip02B")
	anim.AddPositionStage(B, "ZazAPPillSXHip03B")
	anim.AddPositionStage(B, "ZazAPPillSXSpank01B")
	anim.AddPositionStage(B, "ZazAPPillSXEnterB")
	anim.AddPositionStage(B, "ZazAPPillSXExitB")

	Int A = anim.AddPosition(Male)
	anim.AddPositionStage(A, "ZazAPPillSXEnterA", Distance, sos = 3, strapOn = True, silent = False)
	anim.AddPositionStage(A, "ZazAPPillSXHold02A", Distance, sos = 3, strapOn = True, silent = False)
	anim.AddPositionStage(A, "ZazAPPillSXHip02A", Distance, sos = 3, strapOn = True, silent = False)
	anim.AddPositionStage(A, "ZazAPPillSXHip03A", Distance, sos = 3, strapOn = True, silent = False)
	anim.AddPositionStage(A, "ZazAPPillSXSpank01A", Distance, sos = 3, strapOn = True, silent = False)
	anim.AddPositionStage(A, "ZazApPillSxEND01A", Distance, sos = 3, strapOn = True, silent = False)
	anim.AddPositionStage(A, "ZazAPPillSXExitA", Distance, sos = 3, strapOn = True, silent = False)

	anim.AddTag("Pillory")
	anim.AddTag("Behind")
	anim.AddTag("Aggressive")
	anim.AddTag("Vaginal")
	anim.AddTag("DomSub")

	anim.Save(aiId)
EndFunction

Function PilloryTorment01(Int aiId)
	sslBaseAnimation anim = Create(aiId)
	SetAnimationDefaults(anim, "$ZazAP_PilloryTorment01", Silent, Sexual)

	Int B = anim.AddPosition(Female)
	anim.AddPositionStage(B, "ZazAPPillSXEnterB")
	anim.AddPositionStage(B, "ZazAPPillTorButtRub1B")
	anim.AddPositionStage(B, "ZazAPPillTorPussRub1B")
	anim.AddPositionStage(B, "ZazAPPillTorButtLick1B")
	anim.AddPositionStage(B, "ZazAPPillSXEND01B")

	Int A = anim.AddPosition(Male)
	anim.AddPositionStage(A, "ZazAPPillTorButtLick1A", Distance, strapOn = False, silent = True)
	anim.AddPositionStage(A, "ZazAPPillTorButtRub1A", Distance, strapOn = False, silent = True)
	anim.AddPositionStage(A, "ZazAPPillTorPussRub1A", Distance, strapOn = False, silent = True)
	anim.AddPositionStage(A, "ZazAPPillTorButtLick1A", Distance, strapOn = False, silent = True)
	anim.AddPositionStage(A, "ZazAPPillTorButtLick1A", Distance, strapOn = False, silent = True)

	anim.AddTag("Pillory")
	anim.AddTag("Behind")
	anim.AddTag("Oral")
	anim.AddTag("DomSub")
	anim.AddTag("Aggressive")

	anim.Save(aiId)
EndFunction

Function PilloryTorment02(Int aiId)
	sslBaseAnimation anim = Create(aiId)
	SetAnimationDefaults(anim, "$ZazAP_PilloryTorment02", Squishing, Sexual)

	Int B = anim.AddPosition(Female)
	anim.AddPositionStage(B, "ZazAPPillTorSpank1B")
	anim.AddPositionStage(B, "ZazAPPillTorSpank2B")
	anim.AddPositionStage(B, "ZazAPPillTorPusBroom1B")
	anim.AddPositionStage(B, "ZazAPPillTorButtBroom1B")
	anim.AddPositionStage(B, "ZazAPPillTorPusBroom1B")

	Int A = anim.AddPosition(Male)
	anim.AddPositionStage(A, "ZazAPPillTorSpank1A", Distance)
	anim.AddPositionStage(A, "ZazAPPillTorSpank2A", Distance)
	anim.AddPositionStage(A, "ZazAPPillTorPusBroom1A", Distance)
	anim.AddPositionStage(A, "ZazAPPillTorButtBroom1A", Distance)
	anim.AddPositionStage(A, "ZazAPPillTorPusBroom1A", Distance)

	anim.AddTag("Pillory")
	anim.AddTag("Behind")
	anim.AddTag("Anal")
	anim.AddTag("Vaginal")
	anim.AddTag("DomSub")
	anim.AddTag("Aggressive")

	anim.Save(aiId)
EndFunction

Function DefineDoggy01(sslBaseAnimation akAnim, String asName, String asAnim1, String asAnim2)
	SetAnimationDefaults(akAnim, asName, Squishing, Sexual)

	Int B = akAnim.AddPosition(Female, addCum = Anal)
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S3")
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S4")
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S3")
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S4")

	Int A = akAnim.AddPosition(Male)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S3", -100, sos = 5)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S4", -100, sos = 5)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S3", -100, sos = 7)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S4", -100, sos = 5)

	akAnim.AddTag("Doggystyle")
	akAnim.AddTag("Behind")
	akAnim.AddTag("Anal")
	akAnim.AddTag("Bound")
	akAnim.AddTag("Aggressive")
EndFunction

Function BoundDoggyStyle(Int aiId)
	sslBaseAnimation anim = Create(aiId)
	DefineDoggy01(anim, "$ZazAP_SexLabDoggyStyle", "ZapWriDoggy01", "AggrDoggyStyle")
	anim.AddTag("Wrists")
	anim.AddTag("DomSub")
	anim.Save(aiId)
EndFunction

Function BothBoundDoggyStyle(Int aiId)
	sslBaseAnimation anim = Create(aiId)
	DefineDoggy01(anim, "$ZazAP_SexLabDoggyStyleBoth", "ZapWriDoggy01", "ZapWriDoggy01")
	anim.AddTag("Wrists")
	anim.AddTag("SubSub")
	anim.Save(aiId)
EndFunction

Function ArmbinderDoggyStyle(Int aiId)
	sslBaseAnimation anim = Create(aiId)
	DefineDoggy01(anim, "$Zap_Armb_Doggy01", "ZapArmbDoggy01", "AggrDoggyStyle")
	anim.AddTag("Armbinder")
	anim.AddTag("DomSub")
	anim.Save(aiId)
EndFunction

Function BothArmbinderDoggyStyle(Int aiId)
	sslBaseAnimation anim = Create(aiId)
	DefineDoggy01(anim, "$Zap_Armb_Doggy01_SS", "ZapArmbDoggy01", "ZapArmbDoggy01")
	anim.AddTag("Armbinder")
	anim.AddTag("SubSub")
	anim.Save(aiId)
EndFunction

Function DefineMissionary01(sslBaseAnimation akAnim, String asName, String asAnim1, String asAnim2)
	SetAnimationDefaults(akAnim, asName, Squishing, Sexual)

	Int B = akAnim.AddPosition(Female, addCum = Vaginal)
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S1")
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S2")
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S3")
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S4")
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S4")

	Int A = akAnim.AddPosition(Male)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S1", -86, sos = 4)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S2", -86, sos = 4)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S3", -86, sos = 3)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S4", -86, sos = 3)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S4", -86, sos = 3)

	akAnim.AddTag("Missionary")
	akAnim.AddTag("Laying")
	akAnim.AddTag("Vaginal")
	akAnim.AddTag("Bound")
	akAnim.AddTag("Aggressive")
EndFunction

Function BoundMissionary(Int aiId)
	sslBaseAnimation anim = Create(aiId)
	DefineMissionary01(anim, "$ZazAP_SexLabRoughMissionary", "ZapWriMissionary01", "AggrMissionary")
	anim.AddTag("Wrists")
	anim.AddTag("DomSub")
	anim.Save(aiId)
EndFunction

Function ArmbinderMissionary(Int aiId)
	sslBaseAnimation anim = Create(aiId)
	DefineMissionary01(anim, "$Zap_Armb_Missionary01", "ZapArmbMissionary01", "AggrMissionary")
	anim.AddTag("DomSub")
	anim.AddTag("Armbinder")
	anim.Save(aiId)
EndFunction

Function DefineSkullFuck01(sslBaseAnimation akAnim, String asName, String asAnim1, String asAnim2)
	SetAnimationDefaults(akAnim, asName, Sucking, Sexual)

	Int B = akAnim.AddPosition(Female, addCum = Oral)
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S1", 0, silent = True, openMouth = True)
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S2", 0, silent = True, openMouth = True)
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S3", 0, silent = True, openMouth = True)
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S4", 0, silent = True, openMouth = True)
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S5", 0, silent = True, openMouth = True)
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S5", 0, silent = True, openMouth = True)

	Int A = akAnim.AddPosition(Male)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S1", 49, rotate = 180, sos = 1)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S2", 49, rotate = 180, sos = 1)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S3", 49, rotate = 180, sos = 2)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S4", 49, rotate = 180, sos = 2)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S5", 49, rotate = 180, sos = 2)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S5", 49, rotate = 180, sos = 2)

	akAnim.AddTag("AP")
	akAnim.AddTag("Straight")
	akAnim.AddTag("Blowjob")
	akAnim.AddTag("Oral")
	akAnim.AddTag("Bound")
	akAnim.AddTag("Aggressive")
EndFunction

Function BoundSkullFuck(Int aiId)
	sslBaseAnimation anim = Create(aiId)
	DefineSkullFuck01(anim, "$ZazAP_SexLabSkullFuck", "ZapWriSkullFuck01", "AP_SkullFuck")
	anim.AddTag("DomSub")
	anim.AddTag("Wrists")
	anim.Save(aiId)
EndFunction

Function ArmbinderSkullFuck(Int aiId)
	sslBaseAnimation anim = Create(aiId)
	DefineSkullFuck01(anim, "$Zap_Armb_Blowjob01", "ZapArmbSkullFuck01", "AP_SkullFuck")
	anim.AddTag("DomSub")
	anim.AddTag("Armbinder")
	anim.Save(aiId)
EndFunction

Function DefineLesbian01(sslBaseAnimation akAnim, String asName, String asAnim1, String asAnim2)
	SetAnimationDefaults(akAnim, asName, Sucking, Sexual)

	Int B = akAnim.AddPosition(Female)
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S1", 0, silent = True)
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S2", 0, silent = True)
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S3", 0, silent = True)
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S4", 0, silent = True)
	akAnim.AddPositionStage(B, asAnim1 + "_A1_S4", 0, silent = True)

	Int A = akAnim.AddPosition(Female)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S1", -110, silent = True)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S2", -105, silent = True)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S3", -100, silent = True)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S4", -100, silent = True)
	akAnim.AddPositionStage(A, asAnim2 + "_A2_S4", -100, silent = True)

	akAnim.AddTag("Arrok")
	akAnim.AddTag("Lesbian")
	akAnim.AddTag("Oral")
	akAnim.AddTag("Vaginal")
	akAnim.AddTag("Bound")
EndFunction

Function BoundLesbian(Int aiId)
	sslBaseAnimation anim = Create(aiId)
	DefineLesbian01(anim, "$ZazAP_SexLabLesbian", "ZapWriLesbian01", "Arrok_Lesbian")
	anim.AddTag("Wrists")
	anim.AddTag("DomSub")
	anim.Save(aiId)
EndFunction

Function BothBoundLesbian(Int aiId)
	sslBaseAnimation anim = Create(aiId)
	DefineLesbian01(anim, "$ZazAP_SexLabLesbianBoth", "ZapWriLesbian01", "ZapWriLesbian01")
	anim.AddTag("Wrists")
	anim.AddTag("SubSub")
	anim.Save(aiId)
EndFunction

Function ArmbinderLesbian(Int aiId)
	sslBaseAnimation anim = Create(aiId)
	DefineLesbian01(anim, "$Zap_Armb_Lesbian01", "ZapArmbLesbian01", "Arrok_Lesbian")
	anim.AddTag("Armbinder")
	anim.AddTag("SubSub")
	anim.Save(aiId)
EndFunction

Function BothArmbinderLesbian(Int aiId)
	sslBaseAnimation anim = Create(aiId)
	DefineLesbian01(anim, "$Zap_Armb_Lesbian01_SS", "ZapArmbLesbian01", "ZapArmbLesbian01")
	anim.AddTag("Armbinder")
	anim.AddTag("SubSub")
	anim.Save(aiId)
EndFunction

Function DefineBoobJob01(sslBaseAnimation akAnim, String asName, String asAnim1, String asAnim2)
	SetAnimationDefaults(akAnim, asName, Squishing, Sexual)

	Int a1 = akAnim.AddPosition(Female, addCum=Oral)
	akAnim.AddPositionStage(a1, asAnim1 + "_A1_S1", 0)
	akAnim.AddPositionStage(a1, asAnim1 + "_A1_S2", 0)
	akAnim.AddPositionStage(a1, asAnim1 + "_A1_S3", 0)
	akAnim.AddPositionStage(a1, asAnim1 + "_A1_S4", 0, silent = true, openMouth = true)

	Int a2 = akAnim.AddPosition(Male) ; 102
	akAnim.AddPositionStage(a2, asAnim2 + "_A2_S1", -119, sos = 2)
	akAnim.AddPositionStage(a2, asAnim2 + "_A2_S2", -119, sos = 3)
	akAnim.AddPositionStage(a2, asAnim2 + "_A2_S3", -119, sos = -2)
	akAnim.AddPositionStage(a2, asAnim2 + "_A2_S4", -119, sos = -2)

	akAnim.AddTag("Arrok")
	akAnim.AddTag("Dirty")
	akAnim.AddTag("Boobjob")
	akAnim.AddTag("Breast")
	akAnim.AddTag("LeadIn")
	akAnim.AddTag("Bound")
EndFunction

Function BoundArrokBoobjob(Int id)
	sslBaseAnimation anim = Create(id)
	DefineBoobJob01(anim, "$Zap_Wri_Boobjob01", "ZapWriBoobJob01", "Arrok_Boobjob")
	anim.AddTag("Wrists")
	anim.AddTag("DomSub")
	anim.Save(id)
EndFunction

Function ArmbinderArrokBoobjob(Int id)
	sslBaseAnimation anim = Create(id)
	DefineBoobJob01(anim, "$Zap_Armb_Boobjob01", "ZapArmbBoobJob01", "Arrok_Boobjob")
	anim.AddTag("Armbinder")
	anim.AddTag("DomSub")
	anim.Save(id)
EndFunction

Function DefineMissionary02(sslBaseAnimation akAnim, String asName, String asAnim1, String asAnim2)
	Log("DefineMissionary02", "With bases 1: " + asAnim1 + ", 2: " + asAnim2)

	SetAnimationDefaults(akAnim, asName, Squishing, Sexual)

	Int A1 = akAnim.AddPosition(Female, addCum = VaginalOral)
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S1")
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S2")
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S3")
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S4")
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S5")

	int A2 = akAnim.AddPosition(Male)
	akAnim.AddPositionStage(A2, asAnim2 + "_A2_S1", -105)
	akAnim.AddPositionStage(A2, asAnim2 + "_A2_S2", -105)
	akAnim.AddPositionStage(A2, asAnim2 + "_A2_S3", -105, sos = 3)
	akAnim.AddPositionStage(A2, asAnim2 + "_A2_S4", -105, sos = 7)
    akAnim.AddPositionStage(A2, asAnim2 + "_A2_S5", -105, sos = 7)

	akAnim.AddTag("Zyn")
	akAnim.AddTag("Sex")
	akAnim.AddTag("Laying")
	akAnim.AddTag("Missionary")
	akAnim.AddTag("Vaginal")
EndFunction

Function DefineMilkMachine01(sslBaseAnimation akAnim, String asName, String asAnim1)
	Log("DefineMilkMachine01", "With base " + asAnim1)

	SetAnimationDefaults(akAnim, asName, Squishing, Sexual)

	Int A1 = akAnim.AddPosition(Female)
	;akAnim.AddPositionStage(A1, asAnim1 + "_A1_S1")
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S2")
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S3", openMouth = True)
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S4", openMouth = True)
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S5", openMouth = True)
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S6", openMouth = False)
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S7")
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S8")
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S9")
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S10")
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S11")
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S12")
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S13")
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S14")

	akAnim.AddTag("Milk")
	akAnim.AddTag("Vaginal")
	akAnim.AddTag("Anal")
	akAnim.AddTag("Oral")
EndFunction

Function DefineFuro01(sslBaseAnimation akAnim, String asName, String asAnim1)
	Log("DefineFuro01", "With base " + asAnim1)

	SetAnimationDefaults(akAnim, asName, Squishing, Sexual)

	Int A1 = akAnim.AddPosition(Female)
	;akAnim.AddPositionStage(A1, asAnim1 + "_A1_S1")
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S2")
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S3")
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S4")
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S5")
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S6")

	akAnim.AddTag("Furo")
	akAnim.AddTag("Vaginal")
	akAnim.AddTag("Anal")
	akAnim.AddTag("Oral")
	akAnim.AddTag("Aggressive")
EndFunction

Function DefineSpank01(sslBaseAnimation akAnim, String asName, String asAnim1, String asAnim2)
	Log("DefineSpank01", "With bases 1: " + asAnim1 + ", 2: " + asAnim2)

	SetAnimationDefaults(akAnim, asName, Silent, Misc)

	Int A1 = akAnim.AddPosition(Female)
	;akAnim.AddPositionStage(A1, asAnim1 + "_A1_S1")
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S2", rotate = -90.0, forward = 45.0, up = -4.0)
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S3", rotate = -90.0, forward = 45.0, up = -4.0)
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S4", rotate = -90.0, forward = 45.0, up = -4.0)
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S4", rotate = -90.0, forward = 45.0, up = -4.0)
	akAnim.AddPositionStage(A1, asAnim1 + "_A1_S5", rotate = -90.0, forward = 45.0, up = -4.0)

	Int A2 = akAnim.AddPosition(Male)
	;akAnim.AddPositionStage(A2, asAnim2 + "_A2_S1", strapon = False, silent = True, sos = 0, openMouth = False)
	akAnim.AddPositionStage(A2, asAnim2 + "_A2_S2", strapon = False, silent = True, sos = 0, openMouth = False)
	akAnim.AddPositionStage(A2, asAnim2 + "_A2_S3", strapon = False, silent = True, sos = 0, openMouth = False)
	akAnim.AddPositionStage(A2, asAnim2 + "_A2_S4", strapon = False, silent = True, sos = 0, openMouth = False)
	akAnim.AddPositionStage(A2, asAnim2 + "_A2_S4", strapon = False, silent = True, sos = 0, openMouth = False)
	akAnim.AddPositionStage(A2, asAnim2 + "_A2_S5", strapon = False, silent = True, sos = 0, openMouth = False)

	akAnim.AddTag("Spank")
	akAnim.AddTag("Aggressive")
EndFunction


Function DefineMissionary01Base(zbfSexLabBaseEntry akEntry)
	Log("DefineMissionary01Base", "")

	akEntry.Actor1 = StrList("Wrists", "Armbinder", "Yoke", "Vaginal")
	akEntry.Actor2 = StrList("Vaginal")
	akEntry.BaseId = "Missionary01"
	akEntry.VanillaBaseId = "Rough Missionary"
	akEntry.BaseName = "Missionary01"
	akEntry.Name = "Missionary"
	akEntry.Registrations = StrList("BoundMissionary", "ArmbinderMissionary")
	akEntry.RegNames = StrList("$ZazAP_SexLabRoughMissionary", "$Zap_Armb_Missionary01")
	akEntry.Tags = StrList("Aggressive", "Missionary", "Laying", "Vaginal")
	akEntry.VanillaBaseName = "AggrMissionary"
EndFunction

Function DefineSkullfuck01Base(zbfSexLabBaseEntry akEntry)
	Log("DefineSkullfuck01Base", "")

	akEntry.Actor1 = StrList("Wrists", "Armbinder", "Yoke", "Oral")
	akEntry.BaseId = "SkullFuck01"
	akEntry.VanillaBaseId = "AP Skull Fuck"
	akEntry.BaseName = "SkullFuck01"
	akEntry.Name = "Blowjob"
	akEntry.Registrations = StrList("BoundSkullFuck", "ArmbinderSkullFuck")
	akEntry.RegNames = StrList("$ZazAP_SexLabSkullFuck", "$Zap_Armb_Blowjob01")
	akEntry.Tags = StrList("Aggressive", "AP", "Straight", "Blowjob", "Oral")
	akEntry.VanillaBaseName = "AP_SkullFuck"
EndFunction

Function DefineBoobJob01Base(zbfSexLabBaseEntry akEntry)
	Log("DefineBoobJob01Base", "")

	akEntry.Actor1 = StrList("Wrists", "Armbinder", "Breast")
	akEntry.Actor2 = StrList("Vaginal")
	akEntry.BaseId = "BoobJob01"
	akEntry.VanillaBaseId = "Arrok Boobjob"
	akEntry.BaseName = "BoobJob01"
	akEntry.Name = "Boob job"
	akEntry.Registrations = StrList("BoundArrokBoobjob", "ArmbinderArrokBoobjob")
	akEntry.RegNames = StrList("$Zap_Wri_Boobjob01", "$Zap_Armb_Boobjob01")
	akEntry.Tags = StrList("Arrok", "Dirty", "Boobjob", "Breast", "LeadIn")
	akEntry.VanillaBaseName = "Arrok_Boobjob"
EndFunction

Function DefineLesbian01Base(zbfSexLabBaseEntry akEntry)
	Log("DefineLesbian01Base", "")

	akEntry.Actor1 = StrList("Wrists", "Armbinder", "Oral", "Vaginal")
	akEntry.Actor2 = StrList("Wrists", "Armbinder", "Oral", "Vaginal")
	akEntry.BaseId = "Lesbian01"
	akEntry.VanillaBaseId = "Arrok Lesbian"
	akEntry.BaseName = "Lesbian01"
	akEntry.Name = "Lesbian"
	akEntry.Registrations = StrList("BoundLesbian", "BothBoundLesbian", "ArmbinderLesbian", "BothArmbinderLesbian")
	akEntry.RegNames = StrList("$ZazAP_SexLabLesbian", "$ZazAP_SexLabLesbianBoth", "$Zap_Armb_Lesbian01", "$Zap_Armb_Lesbian01_SS")
	akEntry.Tags = StrList("Arrok", "Lesbian", "Oral", "Vaginal")
	akEntry.VanillaBaseName = "Arrok_Lesbian"
EndFunction

Function DefineDoggy01Base(zbfSexLabBaseEntry akEntry)
	Log("DefineDoggy01Base", "")

	akEntry.Actor1 = StrList("Wrists", "Armbinder", "Anal")
	akEntry.Actor2 = StrList("Wrists", "Armbinder", "Vaginal")
	akEntry.BaseId = "Doggy01"
	akEntry.VanillaBaseId = "Rough Doggy Style"
	akEntry.BaseName = "Doggy01"
	akEntry.Name = "Doggy Style"
	akEntry.Registrations = StrList("BoundDoggyStyle", "BothBoundDoggyStyle", "ArmbinderDoggyStyle", "BothArmbinderDoggyStyle")
	akEntry.RegNames = StrList("$ZazAP_SexLabDoggyStyle", "$ZazAP_SexLabDoggyStyleBoth", "$Zap_Armb_Doggy01", "$Zap_Armb_Doggy01_SS")
	akEntry.Tags = StrList("Aggressive", "Doggystyle", "Behind", "Anal")
	akEntry.VanillaBaseName = "AggrDoggyStyle"
EndFunction

Function DefinePillorySex01Base(zbfSexLabBaseEntry akEntry)
	Log("DefinePillorySex01Base", "")

	akEntry.Actor1 = StrList("Vaginal")
	akEntry.Actor2 = StrList("Wrists", "Vaginal")
	akEntry.BaseId = "PillorySex01"
	akEntry.VanillaBaseId = ""
	akEntry.BaseName = "PillorySex01"
	akEntry.Name = "Pillory Sex"
	;akEntry.Registrations = StrList("BoundDoggyStyle", "BothBoundDoggyStyle", "ArmbinderDoggyStyle", "BothArmbinderDoggyStyle")
	;akEntry.RegNames = StrList("$ZazAP_SexLabDoggyStyle", "$ZazAP_SexLabDoggyStyleBoth", "$Zap_Armb_Doggy01", "$Zap_Armb_Doggy01_SS")
	akEntry.Tags = StrList("Pillory", "Behind", "Vaginal", "Aggressive")
	akEntry.VanillaBaseName = "ZapPillorySex01"
EndFunction

Function DefinePilloryLick01Base(zbfSexLabBaseEntry akEntry)
	Log("DefinePilloryLick01Base", "")

	akEntry.Actor1 = StrList("Vaginal")
	akEntry.Actor2 = StrList("Wrists", "Yoke", "Oral")
	akEntry.BaseId = "PilloryLick01"
	akEntry.VanillaBaseId = ""
	akEntry.BaseName = "PilloryLick01"
	akEntry.Name = "Pillory Lick"
	;akEntry.Registrations = StrList("BoundDoggyStyle", "BothBoundDoggyStyle", "ArmbinderDoggyStyle", "BothArmbinderDoggyStyle")
	;akEntry.RegNames = StrList("$ZazAP_SexLabDoggyStyle", "$ZazAP_SexLabDoggyStyleBoth", "$Zap_Armb_Doggy01", "$Zap_Armb_Doggy01_SS")
	akEntry.Tags = StrList("Pillory", "Behind", "Vaginal", "Oral")
	akEntry.VanillaBaseName = "ZapPilloryLick01"
EndFunction

Function DefineMissionary02Base(zbfSexLabBaseEntry akEntry)
	Log("DefineMissionary02Base", "")

	akEntry.Actor1 = StrList("Yoke", "Vaginal")
	akEntry.Actor2 = StrList("Vaginal")
	akEntry.BaseId = "Missionary02"
	akEntry.VanillaBaseId = "Zyn Missionary"
	akEntry.BaseName = "Missionary02"
	akEntry.Name = "Zyn Missionary"
	akEntry.Tags = StrList("Zyn", "Laying", "Missionary", "Vaginal")
	akEntry.VanillaBaseName = "Zyn_Missionary"
EndFunction

Function DefineMilkMachine01Base(zbfSexLabBaseEntry akEntry)
	Log("DefineMilkMachine01Base", "")
	
	akEntry.Actor1 = StrList("Vaginal", "Oral", "Anal")
	akEntry.BaseId = "MilkMachine01"
	akEntry.VanillaBaseId = ""
	akEntry.BaseName = "MilkMachine01"
	akEntry.Name = "Dwemer Milking Machine"
	akEntry.Tags = StrList("Milk", "Vaginal", "Anal", "Oral")
	akEntry.VanillaBaseName = "ZapMilkMachine01"
	akEntry.NumActors = 1
EndFunction

Function DefineFuro01Base(zbfSexLabBaseEntry akEntry)
	Log("DefineMilkMachine01Base", "")

	akEntry.Actor1 = StrList("Vaginal", "Oral", "Anal")
	akEntry.BaseId = "Furo01"
	akEntry.VanillaBaseId = ""
	akEntry.BaseName = "Furo01"
	akEntry.Name = "Furo Tub Monster"
	akEntry.Tags = StrList("Furo", "Vaginal", "Anal", "Oral", "Aggressive")
	akEntry.VanillaBaseName = "ZapFuro01"
	akEntry.NumActors = 1
EndFunction

Function DefineSpank01Base(zbfSexLabBaseEntry akEntry)
	Log("DefineSpank01Base", "")

	akEntry.Actor1 = StrList() ; Should probably support "Wrists" too at some point. Seems like it's made for that.
	akEntry.BaseId = "Spank01"
	akEntry.VanillaBaseId = ""
	akEntry.BaseName = "Spank01"
	akEntry.Name = "Spanking"
	akEntry.Tags = StrList("Spank", "Aggressive")
	akEntry.VanillaBaseName = "ZapSpank01"
EndFunction

; 
; Helper functions, just no better place to put them .... 
; 
; 

; Helper function to set up each actor on the active thread.
; 
; Disables lost of normally unsuitable SexLab effects. Automatically sets up strip slots.
; 
Function AddPrepareActor(sslThreadModel akThread, Actor akActor, Bool abIsVictim = False)
	If akActor != None
		akThread.AddActor(akActor, isVictim = abIsVictim)
		akThread.DisableRagdollEnd(akActor, disabling = True)
		akThread.SetStrip(akActor, zbf.GetSexLabStripSlots(akActor))
		akThread.DisableUndressAnimation(akActor, disabling = True)
	EndIf
EndFunction

; Function PlayAnimationHelper(sslBaseAnimation akAnim, Actor akActorA,  Actor akActorB)

; This function should not be used directly. It's just a convenience function for zbfConfig.
; 
Function PlayAnimationHelper(zbfSexLabBaseEntry akEntry, Actor akActor1, Actor akActor2, String asAnim1, String asAnim2)
	Log("PlayAnimationHelper", "Sending off control to SexLab now.")

	sslBaseAnimation[] Animations = New sslBaseAnimation[1]
	Animations[0] = NewAnimation("ZaZ Animation Pack")
	
	DefineAnimationFromEntry(akEntry.BaseId, Animations[0], "Zap" + akEntry.BaseId, asAnim1, asAnim2)

	sslThreadModel SexThread = SexLab.NewThread()
	
	AddPrepareActor(SexThread, akActor1, abIsVictim = True)
	AddPrepareActor(SexThread, akActor2)

	SexThread.SetAnimations(Animations)
	SexThread.SetBedding(0)
	SexThread.DisableLeadIn(disabling = True)
	SexThread.DisableBedUse(disabling = True)

	SexThread.StartThread()
EndFunction

Int Property iDebugLevel Auto Hidden
Int iError = 0
Int iWarning = 1
Int iInfo = 2
String sFilePrefix = "zbfSexLab"
Function Log(String asMethod, String asMessage, Int aiLevel = 2, Bool abCondition = True)
	If abCondition && (aiLevel <= iDebugLevel)
		Debug.Trace(sFilePrefix + " (" + asMethod + "): " + asMessage)
	EndIf
EndFunction
