scriptname sslSystemConfig extends sslSystemLibrary

; ------------------------------------------------------- ;
; --- System Resources                                --- ;
; ------------------------------------------------------- ;

SexLabFramework property SexLab auto

int function GetVersion()
	return SexLabUtil.GetVersion()
endFunction

string function GetStringVer()
	return SexLabUtil.GetStringVer()
endFunction

bool bDebugMode
bool property DebugMode hidden
	bool function get()
		return bDebugMode
	endFunction
	function set(bool value)
		bDebugMode = value
		if bDebugMode
			; MiscUtil.PrintConsole("SexLab Debug/Development Mode Activated")
			PlayerRef.AddSpell((Game.GetFormFromFile(0x073CC, "SexLab.esm") as Spell))
			PlayerRef.AddSpell((Game.GetFormFromFile(0x5FE9B, "SexLab.esm") as Spell))
		else
			; MiscUtil.PrintConsole("SexLab Debug/Development Mode Deactivated")
			PlayerRef.RemoveSpell((Game.GetFormFromFile(0x073CC, "SexLab.esm") as Spell))
			PlayerRef.RemoveSpell((Game.GetFormFromFile(0x5FE9B, "SexLab.esm") as Spell))
		endIf
	endFunction
endProperty

bool property Enabled hidden
	bool function get()
		return SexLab.Enabled
	endFunction
endProperty

Faction property AnimatingFaction auto
Faction property GenderFaction auto
Faction property ForbiddenFaction auto
Weapon property DummyWeapon auto
Armor property NudeSuit auto
Armor property CalypsStrapon auto
form[] property Strapons auto hidden

Spell property CumVaginalOralAnalSpell auto
Spell property CumOralAnalSpell auto
Spell property CumVaginalOralSpell auto
Spell property CumVaginalAnalSpell auto
Spell property CumVaginalSpell auto
Spell property CumOralSpell auto
Spell property CumAnalSpell auto

Keyword property CumOralKeyword auto
Keyword property CumAnalKeyword auto
Keyword property CumVaginalKeyword auto
Keyword property ActorTypeNPC auto

; FormList property ValidActorList auto
; FormList property NoStripList auto
; FormList property StripList auto

Furniture property BaseMarker auto
Package property DoNothing auto

Sound property OrgasmFX auto
Sound property SquishingFX auto
Sound property SuckingFX auto
Sound property SexMixedFX auto

Static property LocationMarker auto
FormList property BedsList auto
FormList property BedRollsList auto
Message property UseBed auto
Message property CleanSystemFinish auto
Message property CheckSKSE auto
Message property CheckFNIS auto
Message property CheckSkyrim auto
Message property CheckSexLabUtil auto
Message property CheckPapyrusUtil auto
Message property CheckSkyUI auto
Message property TakeThreadControl auto

Topic property LipSync auto
VoiceType property SexLabVoiceM auto
VoiceType property SexLabVoiceF auto
FormList property SexLabVoices auto
; FormList property VoicesPlayer auto ; No longer used - v1.56
SoundCategory property AudioSFX auto
SoundCategory property AudioVoice auto

GlobalVariable property DebugVar1 auto
GlobalVariable property DebugVar2 auto
GlobalVariable property DebugVar3 auto
GlobalVariable property DebugVar4 auto
GlobalVariable property DebugVar5 auto

; ------------------------------------------------------- ;
; --- Config Properties                               --- ;
; ------------------------------------------------------- ;

; Booleans
bool property RestrictAggressive auto hidden
bool property AllowCreatures auto hidden
bool property NPCSaveVoice auto hidden
bool property UseStrapons auto hidden
bool property RedressVictim auto hidden
bool property RagdollEnd auto hidden
bool property UseMaleNudeSuit auto hidden
bool property UseFemaleNudeSuit auto hidden
bool property UndressAnimation auto hidden
bool property UseLipSync auto hidden
bool property UseExpressions auto hidden
bool property ScaleActors auto hidden
bool property UseCum auto hidden
bool property AllowFFCum auto hidden
bool property DisablePlayer auto hidden
bool property AutoTFC auto hidden
bool property AutoAdvance auto hidden
bool property ForeplayStage auto hidden
bool property OrgasmEffects auto hidden
bool property RaceAdjustments auto hidden
bool property BedRemoveStanding auto hidden

; Integers
int property AnimProfile auto hidden
int property NPCBed auto hidden

int property Backwards auto hidden
int property AdjustStage auto hidden
int property AdvanceAnimation auto hidden
int property ChangeAnimation auto hidden
int property ChangePositions auto hidden
int property AdjustChange auto hidden
int property AdjustForward auto hidden
int property AdjustSideways auto hidden
int property AdjustUpward auto hidden
int property RealignActors auto hidden
int property MoveScene auto hidden
int property RestoreOffsets auto hidden
int property RotateScene auto hidden
int property EndAnimation auto hidden
int property ToggleFreeCamera auto hidden
int property TargetActor auto hidden
; int property AutoAlign auto hidden

; Floats
float property CumTimer auto hidden
float property AutoSUCSM auto hidden
float property MaleVoiceDelay auto hidden
float property FemaleVoiceDelay auto hidden
float property VoiceVolume auto hidden
float property SFXDelay auto hidden
float property SFXVolume auto hidden

; Boolean Arrays
bool[] property StripMale auto hidden
bool[] property StripFemale auto hidden
bool[] property StripLeadInFemale auto hidden
bool[] property StripLeadInMale auto hidden
bool[] property StripVictim auto hidden
bool[] property StripAggressor auto hidden

; Float Array
float[] property StageTimer auto hidden
float[] property StageTimerLeadIn auto hidden
float[] property StageTimerAggr auto hidden

; Data
Actor property TargetRef auto hidden
Actor CrosshairRef

; 2015.07.19 / Add
Bool Property mEnableCreatureCum = True Auto Hidden
String Property mszCumColorChaurus = "$BlueCum" Auto Hidden
String Property mszCumColorFalmer = "$BlueCum" Auto Hidden
String Property mszCumColorGoblin = "$GreenCum" Auto Hidden
String Property mszCumColorTroll = "$GreenCum" Auto Hidden
String Property mszCumColorSpider = "$GreenCum" Auto Hidden
String Property mszCumColorSeeker = "$GreenCum" Auto Hidden
String Property mszCumColorGargoyle = "$BrownCum" Auto Hidden
String Property mszCumColorZombie = "$GreenCum" Auto Hidden
String Property mszCumColorDraugr = "$BrownCum" Auto Hidden
String Property mszCumColorOthers = "$RandomCum" Auto Hidden
Bool Property mEnableBleedoutRagdollEnd = True Auto Hidden
Bool Property mDisableBleedoutStop = False Auto Hidden
; 2015.07.19 / Add
; 2015.07.22 / Add
Bool Property mEnableAllAnimations = False Auto Hidden
; 2015.07.22 / Add

; ------------------------------------------------------- ;
; --- Config Accessors                                --- ;
; ------------------------------------------------------- ;

float function GetVoiceDelay(bool IsFemale = false, int Stage = 1, bool IsSilent = false)
	if IsSilent
		return 3.0 ; Return basic delay for loop
	endIf
	float VoiceDelay
	if IsFemale
		VoiceDelay = FemaleVoiceDelay
	else
		VoiceDelay = MaleVoiceDelay
	endIf
	if Stage > 1
		VoiceDelay -= (Stage * 0.8) + Utility.RandomFloat(-0.4, 0.4)
		if VoiceDelay < 0.8
			return Utility.RandomFloat(0.8, 1.3) ; Can't have delay shorter than animation update loop
		endIf
	endIf
	return VoiceDelay
endFunction

bool[] function GetStrip(bool IsFemale, bool IsLeadIn = false, bool IsAggressive = false, bool IsVictim = false)
	if IsLeadIn
		if IsFemale
			return StripLeadInFemale
		else
			return StripLeadInMale
		endIf
 	elseif IsAggressive
 		if IsVictim
 			return StripVictim
 		else
 			return StripAggressor
 		endIf
 	elseIf IsFemale
 		return StripFemale
 	else
 		return StripMale
 	endIf
endFunction

bool function UsesNudeSuit(bool IsFemale)
	return ((!IsFemale && UseMaleNudeSuit) || (IsFemale && UseFemaleNudeSuit))
endFunction

; ------------------------------------------------------- ;
; --- Strapon Functions                               --- ;
; ------------------------------------------------------- ;

form function GetStrapon()
	if Strapons.Length > 0
		return Strapons[Utility.RandomInt(0, (Strapons.Length - 1))]
	endIf
	return none
endFunction

form function WornStrapon(Actor ActorRef)
	int i = Strapons.Length
	while i
		i -= 1
		if ActorRef.IsEquipped(Strapons[i])
			return Strapons[i]
		endIf
	endWhile
	return none
endFunction

bool function HasStrapon(Actor ActorRef)
	return WornStrapon(ActorRef) != none
endFunction

form function PickStrapon(Actor ActorRef)
	form Strapon = WornStrapon(ActorRef)
	if Strapon
		return Strapon
	endIf
	return Strapons[Utility.RandomInt(0, Strapons.Length - 1)]
endFunction

form function EquipStrapon(Actor ActorRef)
	form Strapon = PickStrapon(ActorRef)
	if Strapon
		ActorRef.AddItem(Strapon, 1, true)
		ActorRef.EquipItem(Strapon, false, true)
	endIf
	return Strapon
endFunction

function UnequipStrapon(Actor ActorRef)
	int i = Strapons.Length
	while i
		i -= 1
		if ActorRef.IsEquipped(Strapons[i])
			ActorRef.UnequipItem(Strapons[i], false, true)
			ActorRef.RemoveItem(Strapons[i], 1, true)
		endIf
	endWhile
endFunction

function LoadStrapons()
	Strapons = new form[1]
	Strapons[0] = CalypsStrapon
	int i = Game.GetModCount()
	while i
		i -= 1
		string Name = Game.GetModName(i)
		if Name == "StrapOnbyaeonv1.1.esp"
			LoadStrapon("StrapOnbyaeonv1.1.esp", 0x0D65)
		elseif Name == "TG.esp"
			LoadStrapon("TG.esp", 0x0182B)
		elseif Name == "Futa equippable.esp"
			LoadStrapon("Futa equippable.esp", 0x0D66)
			LoadStrapon("Futa equippable.esp", 0x0D67)
			LoadStrapon("Futa equippable.esp", 0x01D96)
			LoadStrapon("Futa equippable.esp", 0x022FB)
			LoadStrapon("Futa equippable.esp", 0x022FC)
			LoadStrapon("Futa equippable.esp", 0x022FD)
		elseif Name == "Skyrim_Strap_Ons.esp"
			LoadStrapon("Skyrim_Strap_Ons.esp", 0x00D65)
			LoadStrapon("Skyrim_Strap_Ons.esp", 0x02859)
			LoadStrapon("Skyrim_Strap_Ons.esp", 0x0285A)
			LoadStrapon("Skyrim_Strap_Ons.esp", 0x0285B)
			LoadStrapon("Skyrim_Strap_Ons.esp", 0x0285C)
			LoadStrapon("Skyrim_Strap_Ons.esp", 0x0285D)
			LoadStrapon("Skyrim_Strap_Ons.esp", 0x0285E)
			LoadStrapon("Skyrim_Strap_Ons.esp", 0x0285F)
		elseif Name == "SOS Equipable Schlong.esp"
			LoadStrapon("SOS Equipable Schlong.esp", 0x0D62)
		endif
	endWhile
endFunction

Armor function LoadStrapon(string esp, int id)
	Armor Strapon = Game.GetFormFromFile(id, esp) as Armor
	if Strapon
		Strapons = sslUtility.PushForm(Strapon, Strapons)
	endif
	return Strapon
endFunction


; ------------------------------------------------------- ;
; --- Hotkeys                                         --- ;
; ------------------------------------------------------- ;

sslThreadController Control

event OnKeyDown(int keyCode)
	; 2015.09.07 / Add
	Int    nForward = 0
	Int    nSideway = 1
	Int    nUpward  = 2
	; 2015.09.08 / Add
	Int    nSosUp   = 3
	; 2015.09.08 / Add
	Float  nIncr    = mnAdjustValue
	Float  nDecr    = mnAdjustValue * -1
	; 2015.09.07 / Add
	; 2015.09.11 / Add
	Int    nIdx
	; 2015.09.11 / Add
	
	if !Utility.IsInMenuMode() && !UI.IsMenuOpen("Console") && !UI.IsMenuOpen("Loading Menu")
		if keyCode == ToggleFreeCamera
			ToggleFreeCamera()
		elseIf keyCode == TargetActor
			if Control
				DisableThreadControl(Control)
			else
				SetTargetActor()
			endIf
		; 2015.09.07 / Add
		elseIf (keyCode == mnHotKeyExtAnimEdit) && !gIsExtAnimEditMode
			; 2015.09.11 / Add
			If (gaClickCnt.Length != 8)
				gaClickCnt = new Int[8]
			Endif
				
			nIdx = gaClickCnt.Length
			
			While (nIdx > 0)
				nIdx -= 1
				
				gaClickCnt[nIdx] = 0
			EndWhile
			; 2015.09.11 / Add
			
			Debug.Notification("Edit mode starts.")
			gIsExtAnimEditMode = True
		elseIf (keyCode == mnHotKeyExtAnimEdit) && gIsExtAnimEditMode
			Debug.Notification("Edit mode finished.")
			ExportProfile(AnimProfile)
			gIsExtAnimEditMode = False
		elseIf (keyCode == mnHotKeyAdjustUp) && gIsExtAnimEditMode
			onClickAdjustKey(nUpward, nIncr)
		elseIf (keyCode == mnHotKeyAdjustDown) && gIsExtAnimEditMode
			onClickAdjustKey(nUpward, nDecr)
		elseIf (keyCode == mnHotKeyAdjustFwd) && gIsExtAnimEditMode
			onClickAdjustKey(nForward, nIncr)
		elseIf (keyCode == mnHotKeyAdjustBwd) && gIsExtAnimEditMode
			onClickAdjustKey(nForward, nDecr)
		elseIf (keyCode == mnHotKeyAdjustRight) && gIsExtAnimEditMode
			onClickAdjustKey(nSideway, nIncr)
		elseIf (keyCode == mnHotKeyAdjustLeft) && gIsExtAnimEditMode
			onClickAdjustKey(nSideway, nDecr)
		; 2015.09.07 / Add
		; 2015.09.08 / Add
		elseIf (keyCode == mnHotKeyAdjustSosUp) && gIsExtAnimEditMode
			onClickAdjustKey(nSosUp, nIncr)
		elseIf (keyCode == mnHotKeyAdjustSosDown) && gIsExtAnimEditMode
			onClickAdjustKey(nSosUp, nDecr)
		; 2015.09.08 / Add
		; 2015.09.12 / Add
		elseIf (keyCode == AdvanceAnimation) && gIsExtAnimEditMode
			If ExtAnimationEditor()
				goCurEdit.AdvanceStage(BackwardsPressed())
			Endif
		elseIf (keyCode == ChangeAnimation) && gIsExtAnimEditMode
			If ExtAnimationEditor()
				goCurEdit.ChangeAnimation(BackwardsPressed())
			Endif
		elseIf (keyCode == RotateScene) && gIsExtAnimEditMode
			If ExtAnimationEditor()
				goCurEdit.RotateScene(BackwardsPressed())
			Endif
		elseIf (keyCode == ChangePositions) && gIsExtAnimEditMode
			If ExtAnimationEditor()
				goCurEdit.ChangePositions(BackwardsPressed())
			Endif
		; 2015.09.12 / Add
		; 2015.09.16 / Add
		elseIf (keyCode == mnHotKeyCumBukkake) && gsplCumBukkake
			Actor actTarget =					\
				getActiveActor(					\
					enableGrabbed    = True, 	\
					enableCrosshair  = True, 	\
					enableConsole    = True,	\
					enableNearPlayer = False,	\
					disableDead      = True,	\
					onlySexActive    = False	)
			
			If actTarget
				gsplCumBukkake.Cast(actTarget, actTarget)
			Elseif TargetRef
				gsplCumBukkake.Cast(TargetRef, TargetRef)
			Endif
		; 2015.09.16 / Add
		endIf
	endIf
endEvent

; 2015.09.09 / Del
;event OnCrosshairRefChange(ObjectReference ActorRef)
;	CrosshairRef = none
;	if ActorRef
;		CrosshairRef = ActorRef as Actor
;	endIf
;endEvent
; 2015.09.09 / Del

function SetTargetActor()
	; 2015.09.09 / Add
	Actor actTarget =					\
		getActiveActor(					\
			enableGrabbed    = True, 	\
			enableCrosshair  = True, 	\
			enableConsole    = True,	\
			enableNearPlayer = False,	\
			disableDead      = True,	\
			onlySexActive    = False	)
	; 2015.09.09 / Add
	
	; 2015.09.09 / Mod / Not using CrosshairRef but getActiveActor()
;	if CrosshairRef
	if actTarget
		TargetRef = actTarget
		Debug.Notification("SexLab Target Selected: "+TargetRef.GetLeveledActorBase().GetName())
		; Give them stats if they need it
		Stats.SeedActor(TargetRef)
		; Attempt to grab control of their animation?
		sslThreadController TargetThread = ThreadSlots.GetActorController(TargetRef)
		if TargetThread && !TargetThread.HasPlayer && !ThreadSlots.GetActorController(PlayerRef) && TakeThreadControl.Show()
			GetThreadControl(TargetThread)
		endIf
	endif
endFunction

function GetThreadControl(sslThreadController TargetThread)
	if Control || !(TargetThread.GetState() == "Animating" || TargetThread.GetState() == "Advancing")
		Log("Failed to control thread "+TargetThread)
		return ; Control not available
	endIf
	; Set active controlled thread
	Control = TargetThread
	; Lock players movement
	PlayerRef.StopCombat()
	if PlayerRef.IsWeaponDrawn()
		PlayerRef.SheatheWeapon()
	endIf
	PlayerRef.SetFactionRank(AnimatingFaction, 1)
	ActorUtil.AddPackageOverride(PlayerRef, DoNothing, 100, 1)
	PlayerRef.EvaluatePackage()
	Game.SetPlayerAIDriven()
	; Give player control
	Control.AutoAdvance = false
	Control.HasPlayer   = true
	Control.EnableHotkeys()
	Control.HasPlayer   = false
	Log("Player has taken control of thread "+Control)
endFunction

function DisableThreadControl(sslThreadController TargetThread)
	if Control && Control == TargetThread
		; Unlock players movement
		PlayerRef.RemoveFromFaction(AnimatingFaction)
		ActorUtil.RemovePackageOverride(PlayerRef, DoNothing)
		PlayerRef.EvaluatePackage()
		Game.EnablePlayerControls()
		Game.SetPlayerAIDriven(false)
		; Release players thread control
		Control.DisableHotkeys()
		Control.AutoAdvance = true
		Control = none
	endIf
endfunction

function ToggleFreeCamera()
	if Game.GetCameraState() != 3
		MiscUtil.SetFreeCameraSpeed(AutoSUCSM)
	endIf
	MiscUtil.ToggleFreeCamera()
endFunction

bool function BackwardsPressed()
	return Input.GetNumKeysPressed() > 1 && (Input.IsKeyPressed(Backwards) || (Backwards == 54 && Input.IsKeyPressed(42)) || (Backwards == 42 && Input.IsKeyPressed(54)))
endFunction

bool function AdjustStagePressed()
	return Input.GetNumKeysPressed() > 1 && (Input.IsKeyPressed(AdjustStage) || (AdjustStage == 157 && Input.IsKeyPressed(29)) || (AdjustStage == 29 && Input.IsKeyPressed(157)))
endFunction

function HotkeyCallback(sslThreadController Thread, int keyCode)

	; Advance Stage
	; 2015.09.12 / Mod / Adding the condition, !gIsExtAnimEditMode
	if keyCode == AdvanceAnimation && !gIsExtAnimEditMode
		Thread.AdvanceStage(BackwardsPressed())

	; Change Animation
	; 2015.09.12 / Mod / Adding the condition, !gIsExtAnimEditMode
	elseIf keyCode == ChangeAnimation && !gIsExtAnimEditMode
		Thread.ChangeAnimation(BackwardsPressed())

	; Forward / Backward adjustments
	; 2015.09.12 / Mod / Adding the condition, !gIsExtAnimEditMode
	elseIf keyCode == AdjustForward && !gIsExtAnimEditMode
		Thread.AdjustForward(BackwardsPressed(), AdjustStagePressed())

	; Up / Down adjustments
	; 2015.09.12 / Mod / Adding the condition, !gIsExtAnimEditMode
	elseIf keyCode == AdjustUpward && !gIsExtAnimEditMode
		Thread.AdjustUpward(BackwardsPressed(), AdjustStagePressed())

	; Left / Right adjustments
	; 2015.09.12 / Mod / Adding the condition, !gIsExtAnimEditMode
	elseIf keyCode == AdjustSideways && !gIsExtAnimEditMode
		Thread.AdjustSideways(BackwardsPressed(), AdjustStagePressed())

	; Rotate Scene
	; 2015.09.12 / Mod / Adding the condition, !gIsExtAnimEditMode
	elseIf keyCode == RotateScene && !gIsExtAnimEditMode
		Thread.RotateScene(BackwardsPressed())

	; Change Adjusted Actor
	; 2015.09.12 / Mod / Adding the condition, !gIsExtAnimEditMode
	elseIf keyCode == AdjustChange && !gIsExtAnimEditMode
		Thread.AdjustChange(BackwardsPressed())

	; RePosition Actors
	; 2015.09.12 / Mod / Adding the condition, !gIsExtAnimEditMode
	elseIf keyCode == RealignActors && !gIsExtAnimEditMode
		Thread.RealignActors()

	; Auto adjust alignments
	; 2015.09.12 / Mod / Adding the condition, !gIsExtAnimEditMode
	; elseIf keyCode == AutoAlign && !gIsExtAnimEditMode
	; 	Thread.AutoAlign(BackwardsPressed())

	; Change Positions
	; 2015.09.12 / Mod / Adding the condition, !gIsExtAnimEditMode
	elseIf keyCode == ChangePositions && !gIsExtAnimEditMode
		Thread.ChangePositions(BackwardsPressed())

	; Restore animation offsets
	; 2015.09.12 / Mod / Adding the condition, !gIsExtAnimEditMode
	elseIf keyCode == RestoreOffsets && !gIsExtAnimEditMode
		Thread.RestoreOffsets()

	; Move Scene
	; 2015.09.12 / Mod / Adding the condition, !gIsExtAnimEditMode
	elseIf keyCode == MoveScene && !gIsExtAnimEditMode
		Thread.MoveScene()

	; EndAnimation
	; 2015.09.12 / Mod / Adding the condition, !gIsExtAnimEditMode
	elseIf keyCode == EndAnimation && !gIsExtAnimEditMode
		Thread.EndAnimation(true)

	endIf
endFunction

; ------------------------------------------------------- ;
; --- Animation Profiles                              --- ;
; ------------------------------------------------------- ;

function ExportProfile(int Profile = 1)
	JsonUtil.Save("../SexLab/AnimationProfile_"+Profile+".json", DebugMode)
endFunction

function ImportProfile(int Profile = 1)
	string ProfilePath = "../SexLab/AnimationProfile_"+Profile+".json"
	if !JsonUtil.Load(ProfilePath)
		JsonUtil.Save(ProfilePath)
	endIf
endfunction

function SwapToProfile(int Profile)
	if Profile != AnimProfile
		ExportProfile(AnimProfile)
		ImportProfile(Profile)
		AnimProfile = Profile
		UpdateProfile(Profile)
	endIf
endFunction

;v1.59c - altered animation profile structure
function UpdateProfile(int Profile)
	string ProfilePath = "../SexLab/AnimationProfile_"+Profile+".json"
	int i = AnimSlots.Slotted
	while i
		i -= 1
		sslBaseAnimation Anim = AnimSlots.GetBySlot(i)
		if Anim
			Anim._Update_Profile_159c(ProfilePath)
		endIf
	endWhile
	i = CreatureSlots.Slotted
	while i
		i -= 1
		sslBaseAnimation Anim = CreatureSlots.GetBySlot(i)
		if Anim
			Anim._Update_Profile_159c(ProfilePath)
		endIf
	endWhile
	JsonUtil.Save(ProfilePath)
endFunction

; ------------------------------------------------------- ;
; --- System Use                                      --- ;
; ------------------------------------------------------- ;

bool function CheckSystem()
	; Check Skyrim Version
	if (StringUtil.SubString(Debug.GetVersionNumber(), 0, 3) as float) < 1.9
		CheckSkyrim.Show()
		return false
	; Check SKSE install
	elseIf SKSE.GetScriptVersionRelease() < 46
		CheckSKSE.Show(1.71)
		return false
	; Check SkyUI install - depends on passing SKSE check passing
	elseIf Quest.GetQuest("SKI_ConfigManagerInstance") == none
		CheckSkyUI.Show(4.1)
		return false
	; Check SexLabUtil install - this should never happen if they have properly updated
	elseIf SexLabUtil.GetPluginVersion() < 15903
		CheckSexLabUtil.Show()
		return false
	; Check PapyrusUtil install - depends on passing SKSE check passing
	elseIf PapyrusUtil.GetVersion() < 28
		CheckPapyrusUtil.Show(2.8)
		return false
	; Check FNIS generation - soft fail
	elseIf !FNIS.IsGenerated()
		CheckFNIS.Show()
	endIf
	; Return result
	return true
endFunction

function ValidateTrackedActors()
	int i = StorageUtil.FormListCount(self, "TrackedActors")
	while i
		i -= 1
		Actor ActorRef = StorageUtil.FormListGet(self, "TrackedActors", i) as Actor
		if !ActorRef
			StorageUtil.FormListRemoveAt(self, "TrackedActors", i)
		endIf
	endWhile
endFunction

function ValidateTrackedFactions()
	int i = StorageUtil.FormListCount(self, "TrackedFactions")
	while i
		i -= 1
		Faction FactionRef = StorageUtil.FormListGet(self, "TrackedFactions", i) as Faction
		if !FactionRef
			StorageUtil.FormListRemoveAt(self, "TrackedFactions", i)
		endIf
	endWhile
endFunction

function CleanLists()
	int count = StorageUtil.FormListCount(self, "ValidActors")
	Log("Total Actors: "+count, "CleanLists")
	int i = StorageUtil.FormListCount(self, "ValidActors")
	while i > 0
		i -= 1
		Form FormRef = StorageUtil.FormListGet(self, "ValidActors", i)
		if !FormRef
			StorageUtil.FormListRemoveAt(self, "ValidActors", i)
			; Log("Is None", "Removing")
		else
			Actor ActorRef = FormRef as Actor
			if !ActorRef
				StorageUtil.FormListRemoveAt(self, "ValidActors", i)
				; Log(FormRef+" - Not Actor", "Removing")
			elseIf ActorRef.IsDead() || ActorRef.IsDisabled()
				StorageUtil.FormListRemove(self, "ValidActors", ActorRef)
				StorageUtil.FormListRemove(none, "SexLab.SkilledActors", ActorRef, true)
				StorageUtil.FormListRemove(self, "TrackedActors", ActorRef, true)
				StorageUtil.FloatListClear(ActorRef, "SexLabSkills")
				Stats.ClearCustomStats(ActorRef)
				; Log(FormRef + " - "+ ActorRef.GetLeveledActorBase().GetName()+" - IsDead: " + ActorRef.IsDead() + " IsDisabled: " + ActorRef.IsDisabled(), "Removing")
			endIf
		endIf
	endWhile
	Log("Actors Removed: "+(count - StorageUtil.FormListCount(self, "ValidActors")), "CleanLists")
endFunction


function Reload()
	Setup()
	; TFC Toggle key
	UnregisterForAllKeys()
	RegisterForKey(ToggleFreeCamera)
	RegisterForKey(TargetActor)
	; 2015.09.08 / Add
	RegisterForKey(mnHotKeyExtAnimEdit)
	RegisterForKey(mnHotKeyAdjustUp)
	RegisterForKey(mnHotKeyAdjustDown)
	RegisterForKey(mnHotKeyAdjustFwd)
	RegisterForKey(mnHotKeyAdjustBwd)
	RegisterForKey(mnHotKeyAdjustLeft)
	RegisterForKey(mnHotKeyAdjustRight)
	; 2015.09.08 / Add
	; 2015.09.12 / Add
	RegisterForKey(AdvanceAnimation)
	RegisterForKey(ChangeAnimation)
	RegisterForKey(ChangePositions)
	RegisterForKey(RotateScene)
	; 2015.09.12 / Add
	; 2015.09.16 / Add
	; SexLabCumBukkake "Bukkake Cum" [SPEL:0107E3BA]
	RegisterForKey(mnHotKeyCumBukkake)
	gsplCumBukkake = Game.GetFormFromFile(0x7E3BA, "SexLab.esm") as Spell
	; 2015.09.16 / Add
	; Configure SFX & Voice volumes
	AudioVoice.SetVolume(VoiceVolume)
	AudioSFX.SetVolume(SFXVolume)
	; Remove any targeted actors
	; 2015.09.09 / Del
;	RegisterForCrosshairRef()
	; 2015.09.09 / Del
	CrosshairRef = none
	TargetRef = none
	; Remove any NPC thread control player has
	DisableThreadControl(Control)
	; Validate tracked factions & actors
	ValidateTrackedActors()
	ValidateTrackedFactions()
	; Cleanup phantom slots with missing owners
	SexLab.Factory.Cleanup()
endFunction

function SetDefaults()
	SexLab = Quest.GetQuest("SexLabQuestFramework") as SexLabFramework
	PlayerRef = Game.GetPlayer()

	; Booleans
	RestrictAggressive = true
	AllowCreatures     = false
	NPCSaveVoice       = false
	UseStrapons        = true
	RedressVictim      = true
	RagdollEnd         = false
	UseMaleNudeSuit    = false
	UseFemaleNudeSuit  = false
	UndressAnimation   = false
	UseLipSync         = false
	UseExpressions     = false
	ScaleActors        = false
	UseCum             = true
	AllowFFCum         = false
	DisablePlayer      = false
	AutoTFC            = false
	AutoAdvance        = true
	ForeplayStage      = false
	OrgasmEffects      = false
	RaceAdjustments    = true
	BedRemoveStanding  = true

	; Integers
	AnimProfile        = 1
	NPCBed             = 0

	Backwards          = 54 ; Right Shift
	AdjustStage        = 157; Right Ctrl
	AdvanceAnimation   = 57 ; Space
	ChangeAnimation    = 24 ; O
	ChangePositions    = 13 ; =
	AdjustChange       = 37 ; K
	AdjustForward      = 38 ; L
	AdjustSideways     = 40 ; '
	AdjustUpward       = 39 ; ;
	RealignActors      = 26 ; [
	MoveScene          = 27 ; ]
	RestoreOffsets     = 12 ; -
	RotateScene        = 22 ; U
	ToggleFreeCamera   = 81 ; NUM 3
	EndAnimation       = 207; End
	TargetActor        = 49 ; N
	; AutoAlign          = 83 ; NUM .

	; Floats
	CumTimer           = 120.0
	AutoSUCSM          = 5.0
	MaleVoiceDelay     = 5.0
	FemaleVoiceDelay   = 4.0
	VoiceVolume        = 1.0
	SFXDelay           = 3.0
	SFXVolume          = 1.0

	; Boolean strip arrays
	StripMale = new bool[33]
	StripMale[0] = true
	StripMale[1] = true
	StripMale[2] = true
	StripMale[3] = true
	StripMale[7] = true
	StripMale[8] = true
	StripMale[9] = true
	StripMale[4] = true
	StripMale[11] = true
	StripMale[15] = true
	StripMale[16] = true
	StripMale[17] = true
	StripMale[19] = true
	StripMale[23] = true
	StripMale[24] = true
	StripMale[26] = true
	StripMale[27] = true
	StripMale[28] = true
	StripMale[29] = true
	StripMale[32] = true

	StripFemale = new bool[33]
	StripFemale[0] = true
	StripFemale[1] = true
	StripFemale[2] = true
	StripFemale[3] = true
	StripFemale[4] = true
	StripFemale[7] = true
	StripFemale[8] = true
	StripFemale[9] = true
	StripFemale[11] = true
	StripFemale[15] = true
	StripFemale[16] = true
	StripFemale[17] = true
	StripFemale[19] = true
	StripFemale[23] = true
	StripFemale[24] = true
	StripFemale[26] = true
	StripFemale[27] = true
	StripFemale[28] = true
	StripFemale[29] = true
	StripFemale[32] = true

	StripLeadInFemale = new bool[33]
	StripLeadInFemale[0] = true
	StripLeadInFemale[2] = true
	StripLeadInFemale[9] = true
	StripLeadInFemale[14] = true
	StripLeadInFemale[32] = true

	StripLeadInMale = new bool[33]
	StripLeadInMale[0] = true
	StripLeadInMale[2] = true
	StripLeadInMale[9] = true
	StripLeadInMale[14] = true
	StripLeadInMale[32] = true

	StripVictim = new bool[33]
	StripVictim[1] = true
	StripVictim[2] = true
	StripVictim[4] = true
	StripVictim[9] = true
	StripVictim[11] = true
	StripVictim[16] = true
	StripVictim[24] = true
	StripVictim[26] = true
	StripVictim[28] = true
	StripVictim[32] = true

	StripAggressor = new bool[33]
	StripAggressor[2] = true
	StripAggressor[4] = true
	StripAggressor[9] = true
	StripAggressor[16] = true
	StripAggressor[24] = true
	StripAggressor[26] = true

	; Float timer arrays
	StageTimer = new float[5]
	StageTimer[0] = 30.0
	StageTimer[1] = 20.0
	StageTimer[2] = 15.0
	StageTimer[3] = 15.0
	StageTimer[4] = 9.0

	StageTimerLeadIn = new float[5]
	StageTimerLeadIn[0] = 10.0
	StageTimerLeadIn[1] = 10.0
	StageTimerLeadIn[2] = 10.0
	StageTimerLeadIn[3] = 8.0
	StageTimerLeadIn[4] = 8.0

	StageTimerAggr = new float[5]
	StageTimerAggr[0] = 20.0
	StageTimerAggr[1] = 15.0
	StageTimerAggr[2] = 10.0
	StageTimerAggr[3] = 10.0
	StageTimerAggr[4] = 4.0

	; Config loaders
	LoadStrapons()
	Reload()

	; Cleanup dead NPCS in lists
	CleanLists()

	; Rest some player configurations
	Stats.SetSkill(PlayerRef, "Sexuality", 100)
	VoiceSlots.ForgetVoice(PlayerRef)
endFunction

function ReloadData()
	; ActorTypeNPC =            Game.GetForm(0x13794)
	; AnimatingFaction =        Game.GetFormFromFile(0xE50F, "SexLab.esm")
	; AudioSFX =                Game.GetFormFromFile(0x61428, "SexLab.esm")
	; AudioVoice =              Game.GetFormFromFile(0x61429, "SexLab.esm")
	; BaseMarker =              Game.GetFormFromFile(0x45A93 "SexLab.esm")
	; BedRollsList =            Game.GetFormFromFile(0x6198C, "SexLab.esm")
	; BedsList =                Game.GetFormFromFile(0x181B1, "SexLab.esm")
	; CalypsStrapon =           Game.GetFormFromFile(0x1A22A, "SexLab.esm")
	; CheckFNIS =               Game.GetFormFromFile(0x70C38, "SexLab.esm")
	; CheckPapyrusUtil =        Game.GetFormFromFile(0x70C3B, "SexLab.esm")
	; CheckSKSE =               Game.GetFormFromFile(0x70C39, "SexLab.esm")
	; CheckSkyrim =             Game.GetFormFromFile(0x70C3A, "SexLab.esm")
	; CheckSkyUI =              Game.GetFormFromFile(0x70C3C, "SexLab.esm")
	; CleanSystemFinish =       Game.GetFormFromFile(0x6CB9E, "SexLab.esm")
	; CumAnalKeyword =          Game.GetFormFromFile(0x, "SexLab.esm")
	; CumAnalSpell =            Game.GetFormFromFile(0x, "SexLab.esm")
	; CumOralAnalSpell =        Game.GetFormFromFile(0x, "SexLab.esm")
	; CumOralKeyword =          Game.GetFormFromFile(0x, "SexLab.esm")
	; CumOralSpell =            Game.GetFormFromFile(0x, "SexLab.esm")
	; CumVaginalAnalSpell =     Game.GetFormFromFile(0x, "SexLab.esm")
	; CumVaginalKeyword =       Game.GetFormFromFile(0x, "SexLab.esm")
	; CumVaginalOralAnalSpell = Game.GetFormFromFile(0x, "SexLab.esm")
	; CumVaginalOralSpell =     Game.GetFormFromFile(0x, "SexLab.esm")
	; CumVaginalSpell =         Game.GetFormFromFile(0x, "SexLab.esm")
	; DoNothing =               Game.GetFormFromFile(0x, "SexLab.esm")
	; DummyWeapon =             Game.GetFormFromFile(0x, "SexLab.esm")
	; ForbiddenFaction =        Game.GetFormFromFile(0x, "SexLab.esm")
	; GenderFaction =           Game.GetFormFromFile(0x, "SexLab.esm")
	; LipSync =                 Game.GetFormFromFile(0x, "SexLab.esm")
	; LocationMarker =          Game.GetFormFromFile(0x, "SexLab.esm")
	; NudeSuit =                Game.GetFormFromFile(0x, "SexLab.esm")
	; OrgasmFX =                Game.GetFormFromFile(0x, "SexLab.esm")
	; SexLabVoiceF =            Game.GetFormFromFile(0x, "SexLab.esm")
	; SexLabVoiceM =            Game.GetFormFromFile(0x, "SexLab.esm")
	; SexMixedFX =              Game.GetFormFromFile(0x, "SexLab.esm")
	; SquishingFX =             Game.GetFormFromFile(0x, "SexLab.esm")
	; SuckingFX =               Game.GetFormFromFile(0x, "SexLab.esm")
	; UseBed =                  Game.GetFormFromFile(0x, "SexLab.esm")
	; VoicesPlayer =            Game.GetFormFromFile(0x, "SexLab.esm")
endFunction


; ------------------------------------------------------- ;
; --- Pre 1.50 Config Accessors                       --- ;
; ------------------------------------------------------- ;

bool property bRestrictAggressive hidden
	bool function get()
		return RestrictAggressive
	endFunction
endProperty
bool property bAllowCreatures hidden
	bool function get()
		return AllowCreatures
	endFunction
endProperty
bool property bUseStrapons hidden
	bool function get()
		return UseStrapons
	endFunction
endProperty
bool property bRedressVictim hidden
	bool function get()
		return RedressVictim
	endFunction
endProperty
bool property bRagdollEnd hidden
	bool function get()
		return RagdollEnd
	endFunction
endProperty
bool property bUndressAnimation hidden
	bool function get()
		return UndressAnimation
	endFunction
endProperty
bool property bScaleActors hidden
	bool function get()
		return ScaleActors
	endFunction
endProperty
bool property bUseCum hidden
	bool function get()
		return UseCum
	endFunction
endProperty
bool property bAllowFFCum hidden
	bool function get()
		return AllowFFCum
	endFunction
endProperty
bool property bDisablePlayer hidden
	bool function get()
		return DisablePlayer
	endFunction
endProperty
bool property bAutoTFC hidden
	bool function get()
		return AutoTFC
	endFunction
endProperty
bool property bAutoAdvance hidden
	bool function get()
		return AutoAdvance
	endFunction
endProperty
bool property bForeplayStage hidden
	bool function get()
		return ForeplayStage
	endFunction
endProperty
bool property bOrgasmEffects hidden
	bool function get()
		return OrgasmEffects
	endFunction
endProperty

; 2015.09.07 / Add
Int   Property mnHotKeyExtAnimEdit   = 0x12 Auto Hidden
Int   Property mnHotKeyAdjustUp      = 0xC9 Auto Hidden
Int   Property mnHotKeyAdjustDown    = 0xD1 Auto Hidden
Int   Property mnHotKeyAdjustFwd     = 0xC8 Auto Hidden
Int   Property mnHotKeyAdjustBwd     = 0xD0 Auto Hidden
Int   Property mnHotKeyAdjustLeft    = 0xCB Auto Hidden
Int   Property mnHotKeyAdjustRight   = 0xCD Auto Hidden
; 2015.09.08 / Add
Int   Property mnHotKeyAdjustSosUp   = 0xC7 Auto Hidden
Int   Property mnHotKeyAdjustSosDown = 0xCF Auto Hidden
; 2015.09.08 / Add
Float Property mnAdjustValue         = 5.0  Auto Hidden
; 2015.09.16 / Add
Int   Property mnHotKeyCumBukkake    = 0x30 Auto Hidden
; 2015.09.16 / Add

Bool gIsExtAnimEditMode = False

sslThreadController goCurEdit
sslBaseAnimation    goAnimation
String              gszAdjustKey
Int                 gnPosition
; 2015.09.11 / Add
Int[]               gaClickCnt
; 2015.09.11 / Add
; 2015.09.16 / Add
Spell               gsplCumBukkake
; 2015.09.16 / Add

Bool Function isJaxonzPluginInstalled()
	Bool   isInstalled = False
	String szPlugin    = "JaxonzConsolePlugIn"
	Int    nVer
	
	nVer = SKSE.GetPluginVersion(szPlugin)
	
	If (nVer > 0)
		isInstalled = True
	Endif
	
	Return isInstalled
EndFunction

Actor Function getActiveActor(		\
	Bool enableGrabbed    = True, 	\
	Bool enableCrosshair  = True, 	\
	Bool enableConsole    = True,	\
	Bool enableNearPlayer = True,	\
	Bool disableDead      = True,	\
	Bool onlySexActive    = True	\
)
	Actor actFind    = None
	Float nMaxSearch = 170.0
	Int   nSkseVer   = SKSE.GetScriptVersionRelease()
	
	; Console selected
	If !actFind && enableConsole && (nSkseVer > 47)
		actFind = Game.GetCurrentConsoleRef() as Actor
	Elseif !actFind && enableConsole && isJaxonzPluginInstalled()
		actFind = JaxonzConsolePlugIn.GetCurrentConsoleRef() as Actor
	Endif
	
	If actFind && !actFind.GetActorBase()
		actFind = None
	Elseif actFind && actFind.IsDead() && disableDead
		actFind = None
	Elseif actFind && !actFind.HasKeyWordString("SexLabActive") && onlySexActive
		actFind = None
	Endif
	
	; Grabbed
	If !actFind && enableGrabbed
		actFind = Game.GetPlayerGrabbedRef() as Actor
	Endif
	
	If actFind && !actFind.GetActorBase()
		actFind = None
	Elseif actFind && actFind.IsDead() && disableDead
		actFind = None
	Elseif actFind && !actFind.HasKeyWordString("SexLabActive") && onlySexActive
		actFind = None
	Endif
	
	; Crosshair
	If !actFind && enableCrosshair
		actFind = Game.GetCurrentCrosshairRef() as Actor
	Endif
	
	If actFind && !actFind.GetActorBase()
		actFind = None
	Elseif actFind && actFind.IsDead() && disableDead
		actFind = None
	Elseif actFind && !actFind.HasKeyWordString("SexLabActive") && onlySexActive
		actFind = None
	Endif
	
	; Near player
	If !actFind && enableNearPlayer
		actFind = Game.FindRandomActorFromRef(PlayerRef, nMaxSearch)
	Endif
	
	If actFind && !actFind.GetActorBase()
		actFind = None
	Elseif actFind && actFind.IsDead() && disableDead
		actFind = None
	Elseif actFind && !actFind.HasKeyWordString("SexLabActive") && onlySexActive
		actFind = None
	Endif
	
	Return actFind
EndFunction

Bool function ExtAnimationEditor()
	Bool    isFound
	Bool    isAnimating
	Actor   actTarget
	Bool    isSetupOK   = False
	
	actTarget = getActiveActor(enableNearPlayer = False)
	
	If !actTarget && TargetRef
		actTarget = TargetRef
	Elseif !actTarget
		actTarget = PlayerRef
	Endif
	
	; 2015.09.15 / Mod / Config.AnimatingFaction -> AnimatingFaction
	isAnimating = actTarget.IsInFaction(AnimatingFaction)
	isFound     = (ThreadSlots.FindActorController(actTarget) >= 0)
	
	if isAnimating && isFound
		goCurEdit = ThreadSlots.GetActorController(actTarget)
		
		if (goCurEdit.GetState() == "Animating")
			goAnimation = goCurEdit.Animation
			gnPosition  = goCurEdit.Positions.Find(actTarget)
			gszAdjustKey =					\
				sslUtility.RemoveString(	\
					goCurEdit.AdjustKey,	\
					goAnimation.Key("")		)
			isSetupOK = True
		endIf
	endIf
	
	Return isSetupOK
endFunction

function onClickAdjustKey(Int nSlot, Float nValue)
	Int     nCurStage
	Float   nCurValue
	Float   nNewValue
	Bool    isSetupOK
	; 2015.09.11 / Add
	Int     nIdx
	Actor   actAdj
	String  szMsg
	; 2015.09.11 / Add
	
	; 2015.09.11 / Add
	nIdx = nSlot * 2
	
	If (nValue < 0)
		nIdx += 1
	Endif
	
	gaClickCnt[nIdx] = gaClickCnt[nIdx] + 1
	
	If (gaClickCnt[nIdx] > 1)
;		szMsg = "Spam guard ("					\
;		      + (nIdx as String)				\
;		      + ": "							\
;		      + (gaClickCnt[nIdx] as String)	\
;		      + ")."
;		Debug.Notification(szMsg)
		Return
	Endif
	; 2015.09.11 / Add
	
	isSetupOK = ExtAnimationEditor()
	
	If !isSetupOK
		Return
	Endif
	
	nCurStage = goCurEdit.Stage
	
	nCurValue =								\
		goAnimation.GetAdjustment(			\
			goAnimation.Key(gszAdjustKey),	\
			gnPosition,						\
			nCurStage,						\
			nSlot							)
	
	; 2015.09.11 / Add
	nValue *= gaClickCnt[nIdx]
	; 2015.09.11 / Add
	nNewValue = nCurValue + nValue
	
	goAnimation.SetAdjustment(			\
		goAnimation.Key(gszAdjustKey),	\
		gnPosition,						\
		nCurStage,						\
		nSlot,							\
		nNewValue						)
	
	; 2015.09.11 / Add
	gaClickCnt[nIdx] = 0
	; 2015.09.11 / Add
	
	goCurEdit.RealignActors()
	
	actAdj = goCurEdit.Positions[gnPosition]
	szMsg  = actAdj.GetLeveledActorBase().GetName()
	
	If (nSlot == 0)
		szMsg += " forwards "
	Elseif (nSlot == 1)
		szMsg += " sideways "
	Elseif (nSlot == 2)
		szMsg += " upwards "
	Elseif (nSlot == 3)
		szMsg += " sos "
	Else
		szMsg += " unknown "
	Endif
	
	If (nValue < 0)
		szMsg += (nValue as String)
	Else
		szMsg += "+"+(nValue as String)
	Endif
	
	Debug.Notification(szMsg)
endFunction
; 2015.09.07 / Add

; ------------------------------------------------------------------------------
; End of Script
; ------------------------------------------------------------------------------
