Jump to content

SE Compatibility Tracking (Apr 23)


Recommended Posts

Sexlab Adventures 0.72 (Oldrim version)   Works fine as far as I can tell with my SSE game with SKSE 2.0.12.  (I made sure NOT to update the latest SKSE breaking patch from Bugstheda) 

 

About to try out Apropos 2 with SSE to see how well it works. 

 

UPDATES:   Apropos 2 shows menu but doesn't work due to requiring an appropriate install of JContainers.

 

Things in the Dark (Oldrim) works pretty well with SKSE 2.0.12.   Installing Things in the Dark (Modified) will not allow you to enter Ibn as a slave if you go by the spider statute near the Helgen cave entrance - you'll only get the servant option which I fixed by uninstalling that mod, then reinstalling Things in the Dark.  I didn't crash even once in Ibn in Skyrim SE. *shocked*

 

Further testing showed that Sexlab Adventures (Oldrim) Proximity Rape will "hang" on occasion and you have to restart the rape mechanism.   Which sucks as it was one of the very few mods that can start 3 person/creature sex animations. 

Link to comment

I can confirm that Passive Weapon Enchantment Recharging has been updated to 1.5.73 SKSE64 2.0.15 

(Updated on March 18, 2019) (@Sakatraka did mention this a few messages up from mine)

 

https://www.nexusmods.com/skyrimspecialedition/mods/14491?tab=files

 

Nexus Mods has updated JContainers on their site aswell (@Sakatraka did mention this a few messages up from mine)

(Updated March 18, 2019)

https://www.nexusmods.com/skyrimspecialedition/mods/16495?tab=files

 

HDT High Heels has been updated to 1.5.73 SKSE64 2.0.15 (Updated on March 17, 2019)

http://www.9damao.com/thread-39231-1-1.html

 

Picture Proof:

 

 

 

Capture.PNG

Link to comment
18 hours ago, GothicRose30 said:

I can confirm that Passive Weapon Enchantment Recharging has been updated to 1.5.73 SKSE64 2.0.15 

(Updated on March 18, 2019) (@Sakatraka did mention this a few messages up from mine)

 

https://www.nexusmods.com/skyrimspecialedition/mods/14491?tab=files

 

Nexus Mods has updated JContainers on their site aswell (@Sakatraka did mention this a few messages up from mine)

(Updated March 18, 2019)

https://www.nexusmods.com/skyrimspecialedition/mods/16495?tab=files

 

HDT High Heels has been updated to 1.5.73 SKSE64 2.0.15 (Updated on March 17, 2019)

http://www.9damao.com/thread-39231-1-1.html

 

Picture Proof:

 

 

 

Capture.PNG

translations hdtSSEHH

 

 

hdtHighHeel_english.7z

Link to comment
8 hours ago, Gh0sTG0 said:

Hm... What to do if mod SLA Monitor Widget is marked as "convertable", but it has skyui in masterfiles? Should I install skyui, skyuiSE, both of them? What to do with editing masterfiles?

I don't know what you have but last I knew SLA Monitor Widget.esp did not have SkyUI.esp as a master.

Link to comment
11 hours ago, seiryuufang said:

SL Disparity converts easily enough, but won't connect to SkyrimSE's version of NiOverride. Simply adding Oldrim files to scripts doesn't fix either. Will probably need someone to find a way to connect Disparity's NiOverride requirement to current Skee/Racemenu.

Yes indeed, apparently I never finished adding that comment in my list...

Link to comment

SexLab Disparity::

 

Need to test more but so far I got this!!

 

https://imgur.com/a/ShosVEV

 

Modified scripts to get it working.

 

_fwb_modifiers.psc

 

Spoiler

Scriptname _fwb_modifiers extends Quest  

Int Function VersionID()
    Return 1306
EndFunction

Int version = 1306

; FOLDSTART - Properties
String Property ModName Auto

Keyword Property playerHomeKeyword Auto
Keyword Property jailKeyword Auto

Quest Property mcm Auto
; DO NOT cast this to _fwb_sexlab, do not pollute modifiers with SexLab types.
Quest Property fwb_sexlab Auto

; NiOverride version
int   Property SKEE_VERSION  = 1 AutoReadOnly
int   Property NIOVERRIDE_VERSION    = 4 AutoReadOnly
int   Property NIOVERRIDE_SCRIPT_VERSION = 6 AutoReadOnly

; XPMSE version
float Property XPMSE_VERSION         = 3.0 AutoReadOnly
float Property XPMSELIB_VERSION      = 3.0 AutoReadOnly

; Spells
Spell Property dummySpell Auto ; Deprecated, no longer used - is set to None
Spell[] Property spellDebuffs Auto
Spell[] Property spellBuffs Auto

; These have the new spells for update 13
Spell[] Property spellDebuffs_U13 Auto
Spell[] Property spellBuffs_U13 Auto

Spell Property visualEffectsSpell Auto
Spell Property animStaggerEffectSpell Auto ; The internal stagger animation needs a special spell, due to vanilla stagger being an archetype not a script.

; These replace the old property - which will not have latest data if the player does an update
Spell[] spellsDown
Spell[] spellsUp



; Factions - deprecated - no longer used - are set to None
Faction Property zadAnimatingFaction Auto
Faction Property sexlabAnimatingFaction Auto
; FOLDEND - Properties

; FOLDSTART - NiNodes
String NINODE_BREAST_LEFT = "NPC L Breast"
String NINODE_BREAST_RIGHT = "NPC R Breast"
; Used for "Torpedo fix"
String NINODE_BREAST01_LEFT = "NPC L Breast01"
String NINODE_BREAST01_RIGHT = "NPC R Breast01"
String NINODE_BELLY = "NPC Belly"
String NINODE_BUTT_LEFT = "NPC L Butt"
String NINODE_BUTT_RIGHT = "NPC R Butt"
String NINODE_SCHLONG = "NPC Genitals01 [Gen01]"
; FOLDEND - NiNodes

; FOLDSTART - Keys
String keyInUpdate = "fwb_mcm_in_update"
String startedKey = "fwb_modifiers_enabled"
String updateIntervalKey = "fwb_update_interval"
String keyEventsEnabled = "fwb_EventsEnabled"
String keyVisualEffectsEnabled = "fwb_VisualEffectsEnabled"
String keyUseSlifForNodes = "fwb_UseSlifForNodes"
String keyEnableAttackSpeed = "fwb_EnableAttackSpeedChanges"

String keySliderNames = "fwb_sliderNames_"
String keySliderValues = "fwb_sliderValues_"
String keySliderDefaults = "fwb_sliderDefaults_"
String keySliderFormats = "fwb_sliderFormats_"
String keySliderSteps = "fwb_sliderSteps_"
String keySliderMin = "fwb_sliderMinKey_"
String keySliderMax = "fwb_sliderMaxKey_"

String keyMcmModifiedFlag = "fwb_valuesChangedInMcm"
String keyEffectValues = "fbw_effectValues"
String keyCurrentEffectValues = "fbw_currentEffectValues"
String keyLimitValuesLo = "fwb_limitValuesLo"
String keyLimitValuesHi = "fwb_limitValuesHi"
String keyMasterDebuff = "fwb_masterDebuff"
String keyMasterBuff = "fwb_masterBuff"

String keyIsUpdateEnabled = "fwb_effect_update_enabled"
String keyMmeEnables = "fwb_mmeUpdateEnables"
String keySlaEnables = "fwb_slaUpdateEnables"
String keyRapeCount = "fwb_currentKnownRapeCount"
String keyRapeTime = "fwb_lastKnownRapeTime"
String keyIsmStrengths = "fwb_ISM_strengths"
String keyWntEnables = "fwb_wearTearEnables"

String keyCombatEventScale = "fwb_CombatEventScale"
String keySprintEventScale = "fwb_SprintEventScale"
String keyStumbleStamina = "fwb_stumbleStaminaLoss"

String keyTripStamina = "fwb_tripStaminaLoss"
String keyTripRapeChance = "fwb_tripRapeChance"

String keyFallStamina = "fwb_fallStaminaLoss"
String keyFallHealth = "fwb_fallHealthLoss"
String keyFallRapeChance = "fwb_fallRapeChance"
String keyUseInternalAnimations = "fwb_useInternalAnimations"

String keyRapeDuringSolo = "fwb_rapeDuringSolo"
String keyRapeStaminaLoss = "fwb_rapeStaminaLoss"
String keyRapeHealthLoss = "fwb_rapeHealthLoss"
String keyRapistsMinimum = "fwb_rapistsMinimum"
String keyRapistsMaximum = "fwb_rapistsMaximum"

; This is for use by modifiers itself - thread code doesn't know or care about it
String keyThreadEventMutex = "fwb_threadEventMutex"

String keyMorphsEnabled = "fwb_morphsEnabled"
String keyMorphMode = "fwb_morphMode"
String keyMorphMasterWeights = "fwb_morphMasterWeights"
String keyMorphMasterScales = "fwb_morphMasterScales"
String keyMorphNames = "fwb_morphNames"
String keyMorphWeights = "fwb_morphWeights"
String keyMorphOffsets = "fwb_morphOffsets"
String keyPlayerMorphValues = "fwb_playerMorphValues"
String keyMorphNodeValues = "fwb_morphNodeValues"

String keyPlayerStates = "fwb_playerStates"

String keyBreasts = "breasts"
String keyBelly = "belly"
String keyButt = "butt"
String keyBody = "body"

; FOLDEND - Keys

; FOLDSTART - Indices
Int ixDebuffMax
Int ixDebuffFrom
Int ixDebuffTo

Int ixBuffMax
Int ixBuffFrom
Int ixBuffTo

Int ixStart
Int ixEnd

; playerStates indices
Int ixBreast
Int ixBelly
Int ixButt
Int ixBody
Int ixCarryWeight
; unused indices  ixMagicka = 5 ixMagickaRate = 6 ixHealth = 7 ixHealthRate = 8 ixStamina = 9 ixStaminaRate = 10

Int mmMilk
Int mmBreastFull
Int mmPain

Int slArousal
Int slDenial
Int slRaped
Int slAddicted

Int wtAbuse
Int wtCreature
Int wtDaedric
Int wtVaginal
Int wtAnal
Int wtOral
; FOLDEND - Indices

; FOLDSTART - Variables
Bool configurationChanged
Bool playerChanged

Actor player

Float defaultUpdateInterval

Float masterDebuff
Float masterBuff

Float[] playerStates
Float[] oldPlayerStates

Bool slifPresent
Bool weaponSpeedsModified

Bool mmePresent
Int[] mmeEnabled
String[] mmePageNames
Float[] mmeStates
Float[] oldMmeStates

Bool slaPresent
Int[] slaEnabled
String[] slaPageNames
Float[] slaStates
Float[] oldSlaStates

Int sexLabRapeCount
Float greetingDistance

Float[] ismStrengths

Bool wntPresent
Int[] wntEnabled
String[] wntPageNames
Float[] wntStates
Float[] oldWntStates

Float[] breastValues
Float[] bellyValues
Float[] buttValues
Float[] bodyValues
Float[] mmeMilkValues
Float[] mmeBreastPercentValues
Float[] mmePainPercentValues
Float[] slaArousalValues
Float[] slaDenialValues
Float[] slaRapedValues
Float[] slaAddictionValues
Float[] wntAbuseValues
Float[] wntCreatureValues
Float[] wntDaedricValues
Float[] wntVaginalValues
Float[] wntAnalValues
Float[] wntOralValues

Bool morphsEnabled
String morphMode
Float[] morphMasterWeights
Float[] morphMasterScales
Float[] morphWeights
Float[] morphOffsets
Float[] morphPrescales
String[] morphNames
Float[] playerMorphValues
Float[] weightedMorphs
Float[] morphNodeValues


Float[] currentModifiers
Float[] breastModifiers
Float[] bellyModifiers
Float[] buttModifiers
Float[] bodyModifiers

Float[] mmeMilkModifiers
Float[] mmeBreastPercentModifiers
Float[] mmePainPercentModifiers

Float[] slaArousalModifiers
Float[] slaDenialModifiers
Float[] slaRapedModifiers
Float[] slaAddictionModifiers

Float[] wntAbuseModifiers
Float[] wntCreatureModifiers
Float[] wntDaedricModifiers
Float[] wntVaginalModifiers
Float[] wntAnalModifiers
Float[] wntOralModifiers

Float[] effectValues
Float[] oldEffectValues
Int[] currentEffectValues
Int[] effectTypes
Float[] effectScalesUp
Float[] effectScalesDown
String[] avNames

Float[] clampLower
Float[] clampUpper

Float sexEventCooldownSeconds = 30.0
Float sexEventCooldownExpire

Float wobbleEventCooldownSeconds = 12.0
Float wobbleEventCooldownSecondsForCombat = 6.0
Float wobbleEventCooldownExpired

; FOLDEND - Variables

; FOLDSTART - Chances
Float stumbleChance
Float bleedoutChance ; tripChance
Float fallChance
Float dropChance
Float masturbateChance
Float rapeChance

Int stumbleChance_x100
Int bleedoutChance_x100
Int fallChance_x100
Int dropChance_x100
Int masturbateChance_x100
Int rapeChance_x100

Float tripRapeChance ; bleedoutRapeChance
Float fallRapeChance
Int tripRapeChance_x100
Int fallRapeChance_x100
; FOLDEND - Chances

; FOLDSTART - Effect indices
Int exMagicka       ; 0
Int exMagickaRate   ; 1
Int exHealth        ; 2
Int exHealthRate    ; 3
Int exStamina       ; 4
Int exStaminaRate   ; 5
Int exMoveSpeed     ; 6
Int exCarryWeight   ; 7
Int exMeleeDamage   ; 8
Int exUnarmed       ; 9
Int exAttackSpeed   ; 10
Int exBowSpeed      ; 11

Int exArchery       ; 12
Int exOneHanded     ; 13
Int exTwoHanded     ; 14
Int exBlock         ; 15
Int exHeavyArmor    ; 16
Int exLightArmor    ; 17
Int exSneak         ; 18
Int exLockpicking   ; 19
Int exPickpocket    ; 20
Int exSpeech        ; 21
Int exAlteration    ; 22
Int exConjuration   ; 23
Int exDestruction   ; 24
Int exIllusion      ; 25
Int exRestoration   ; 26
Int exEnchanting    ; 27
Int exAlchemy       ; 28
Int exSmithing      ; 29
Int exVisionBlur    ; 30
Int exVisionTunnel  ; 31
Int exVisionDouble  ; 32
Int exVisionVibrant ; 33
Int exStumble       ; 34
Int exBleedout      ; 35
Int exFall          ; 36
Int exDrop          ; 37
Int exMasturbate    ; 38
Int exRape          ; 39

Int effectCount     ; 40
; FOLDEND - Effect indices


STATE Disabled

    Event OnUpdate()
        _fw_utils.Info("FWB Modifiers - OnUpdate while Disabled")
    EndEvent


    Bool Function StartMod()

        GotoState("Starting")
        
        ;Debug.Notification(ModName + " Starting Modifiers")
       
        If ResetInternal()
        
            StorageUtil.SetIntValue(None, startedKey, 1)
        
            _fw_utils.Info("FWB Modifers.StartMod starting periodic updates")
            ; Don't start fwb_sexlab here now - started only on demand.
                
            ; Update after a short delay, so player gets immediate feedback on mod start
            GotoState("Enabled")
            
            CheckForExternalMods()
            
            ; Force an MCM update on mod-start - we need this so that states are considered changed and get recomputed.
            StorageUtil.SetIntValue(None, keyMcmModifiedFlag, 1)
            
            RegisterForSingleUpdate(5.0)
            Return True
        Else
            GotoState("Disabled")
            _fw_utils.Info("FWB Modifiers.ResetInternal failed")
            Return False;
        EndIf
        
    EndFunction
    
    
    Function StopMod()
    EndFunction
    
EndState


STATE Starting

    Event OnUpdate()
        _fw_utils.Info("FWB Modifiers - OnUpdate during Startup")
    EndEvent
    
    Bool Function StartMod()
        Return False
    EndFunction
    
    Function StopMod()
    EndFunction

EndState


STATE Stopping

    Event OnUpdate()
        _fw_utils.Info("FWB Modifiers - OnUpdate during Shutdown")
    EndEvent
    
    Bool Function StartMod()
        Return False
    EndFunction

    Function StopMod()
    EndFunction
    
EndState


STATE Enabled

    Function StopMod()
        GotoState("Stopping")
        ;Debug.Notification(ModName + " Stopping Modifiers")
        
        _fw_utils.Info("FWB Modifers.StopMod stopping periodic update")
        
        If fwb_sexlab.IsRunning()
        
            _fw_utils.Info("FWB Modifers.StopMod stopping sexlab handler quest")
            fwb_sexlab.Stop()
            
        EndIf
        
        StorageUtil.SetIntValue(None, startedKey, 0)
        GotoState("Disabled")
    EndFunction
    
    
    ; TODO - move this to the MCM quest, where it belongs?
    ; Can't listen for the game load event in this quest, but we have an alias with a script that calls this.
    Function HandleGameLoad()
    
        If InDebug()
            _fw_utils.DebugSpam_SetInfo()
        EndIf
    
        _fw_utils.Info("FWB HandleGameLoad")
        
        _fwb_mcm mcmScript = mcm As _fwb_mcm
        Int mcmVersion = mcmScript.GetInstalledVersionID()
        Int mcmRelease = mcmScript.GetReleaseVersionID()
        
        _fw_utils.Info("FWB Installed Version ID: " + mcmVersion + ", current version: " + version + ", Release Version ID: " + mcmRelease)
        
        If version != mcmVersion || version != mcmRelease || mcmVersion != mcmRelease
        
            ModName = mcmScript.ModName
            _fw_utils.Info("FWB update from HandleGameLoad, version is " + version + ", should be " + mcmRelease)
            
            ; Need update
            StorageUtil.SetIntValue(None, keyInUpdate, 1)
            StopMod()
            Utility.Wait(1.0)
            (mcm As _fwb_mcm).Update()
            
            String versionName = mcmScript.GetVersionName()
            
            If StartMod()
                
                (mcm As _fwb_mcm).EnableFromUpdate()
                
                StorageUtil.UnsetIntValue(None, keyInUpdate)
                
                version = mcmRelease
                
                _fw_utils.Info("FWB update from HandleGameLoad complete at version " + version)
                Debug.Notification(ModName + " auto-updated to version " + versionName)
            Else
                _fw_utils.Info("FWB update from HandleGameLoad failed to StartMod at version " + version + " MCM at " + mcmVersion + " (" + versionName +")")
                Debug.Notification("SexLab Disparity auto-update to " + versionName + " FAILED")
            EndIf
        Else
            
            _fw_utils.Info("FWB reset soft-deps in HandleLoadGame due to no update at version " + version)
            ; If not updated, it's still possible mods removed since we were last loaded.
            slifPresent = False
            slaPresent = False
            wntPresent = False
            mmePresent = False

            CheckForExternalMods()
            
            RegisterEventListeners()
            
        EndIf
            
        ; Reset these, but not to zero, allow some time before they can happen, because load is a vulnerable period.
        wobbleEventCooldownExpired = wobbleEventCooldownSeconds
        sexEventCooldownExpire = sexEventCooldownSeconds
        
        ;Debug.Notification(ModName + " handled game load")
        
    EndFunction

    
    Event OnUpdate()
    
        ;Debug.Notification(ModName + " OnUpdate")
        _fw_utils.Info("FWB - OnUpdate")
        
        configurationChanged = ReadConfigurationValues()
        _fw_utils.Info("FWB - configurationChanged: " + configurationChanged)
        
        playerChanged = ReadPlayerState()
        _fw_utils.Info("FWB - playerChanged: " + playerChanged)
        
        If configurationChanged || playerChanged

            _fw_utils.Info("FWB - OnUpdate - update intentions, modifiers, effect values")
            UpdateIntentions()
            CombineModifiers()
            EffectValuesFromModifiers()
        
        EndIf
        
        If StorageUtil.GetIntValue(None, keyIsUpdateEnabled, 1)
        
            _fw_utils.Info("FWB - OnUpdate - update effects")
            
            ; Check the effects are OK, even if we didn't change anything.
            UpdateEffects() ; Not just visual effects, this is everything.
            
            If StorageUtil.GetIntValue(None, keyEventsEnabled)
                _fw_utils.Info("FWB - OnUpdate - trigger events")
                CheckAndTriggerEvents();
            EndIf
            
        EndIf
        
        
        ; Queue next update
        Float updateInterval = StorageUtil.GetFloatValue(None, updateIntervalKey, defaultUpdateInterval)
        RegisterForSingleUpdate(updateInterval)
    
    EndEvent
    
    
    ; You should only call this while Enabled, the empty state version always returns false
    Bool Function CheckForSLIF()
    
        If _fwb_slif.CheckForSlif()
            If !slifPresent
                slifPresent = True
                SignalConfigurationChanged()
            EndIf
        Else
            If slifPresent
                slifPresent = False
                ; Disable useSlifForNodes in configuration if SLIF vanished.
                StorageUtil.SetIntValue(None, keyUseSlifForNodes, 0)
                SignalConfigurationChanged()
            EndIf
        EndIf
        
        Return slifPresent
        
    EndFunction

    
    ; You should only call this while Enabled, the empty state version always returns false
    Bool Function CheckForSLA()
    
        _fw_utils.Info("FWB - CheckForSLA...")
        Int slaIndex = Game.GetModByName("SexLabAroused.esm")
        _fw_utils.Info("FWB - CheckForSLA - SLA index " + slaIndex)
        
        If 255 != slaIndex
            _fw_utils.Info("FWB CheckForSLA - SLA found, was " + slaPresent)
            If !slaPresent
                slaPresent = True
                SignalConfigurationChanged()
            EndIf
            UpdateSlaValues()
        Else
            _fw_utils.Info("FWB CheckForSLA - SLA not found, was " + slaPresent)
            If slaPresent
                slaPresent = False
                SignalConfigurationChanged()
            EndIf
        EndIf
        
        Return slaPresent
        
    EndFunction

    
    ; You should only call this while Enabled, the empty state version always returns false
    Bool Function CheckForMME()
    
        Int mmeIndex = Game.GetModByName("MilkModNEW.esp")
        If 255 != mmeIndex

            ;Debug.Notification(ModName + " - MME ESP is present at index " + mmeIndex)
            
            If !mmePresent
                mmePresent = True
                SignalConfigurationChanged()
            EndIf
            
            mmePageNames = new String[3]
            mmePageNames[0] = "Milk-Weight"
            mmePageNames[1] = "Breasts-Percent-Full"
            mmePageNames[2] = "Pain-Percentage"
            
            mmeStates = new Float[3]
            oldMmeStates = new Float[3]
            
            UpdateMmeValues()
            
        Else
            If mmePresent
                mmePresent = False
                SignalConfigurationChanged()
            EndIf
        EndIf
        
        Return mmePresent
        
    EndFunction

    
    ; You should only call this while Enabled, the empty state version always returns false
    Bool Function CheckForApropos2()
        ; TODO - change SLA and MME to have detection handling in the shim like this!
        If _fwb_apropos2.IsAproposPresent()
        
            If !wntPresent
                wntPresent = True
                SignalConfigurationChanged()
            EndIf

            wntStates = new Float[6]
            oldWntStates = new Float[6]
            
            wntPageNames = new String[6]
            wntPageNames[0] = "OVERALL"
            wntPageNames[1] = "CREATURE"
            wntPageNames[2] = "DAEDRIC"
            wntPageNames[3] = "VAGINAL"
            wntPageNames[4] = "ANAL"
            wntPageNames[5] = "ORAL"

            UpdateAproposValues()
            
        Else
            If wntPresent
                wntPresent = False
                SignalConfigurationChanged()
            EndIf
        EndIf
        
        Return wntPresent
        
    EndFunction
   
    
EndState


;---------------------------------------------------------------------------------------------------------------
; EMPTY state
;---------------------------------------------------------------------------------------------------------------
Bool Function StartMod()
    GotoState("Disabled")
    Return StartMod()
EndFunction

Function StopMod()
EndFunction

Event OnUpdate()
    ; Empty
EndEvent

Function Reset()
    ResetInternal()
    StorageUtil.SetIntValue(None, startedKey, 0)
    GotoState("Disabled")
EndFunction

    
Function SignalConfigurationChanged()
    StorageUtil.SetIntValue(None, keyMcmModifiedFlag, 1)
EndFunction


; Re-read all the configuration values set by the MCM, but only if called with forceRead True, or if MCM has signalled them as changed somehow.
; It only returns TRUE if it reads some values - which basically means recalculate everything.
Bool Function ReadConfigurationValues(bool forceRead = False)
    
    _fw_utils.Info("FWB - ReadConfigurationValues: forceRead: " + forceRead + ", mcmChanged: " + StorageUtil.GetIntValue(None, keyMcmModifiedFlag, 1))
    If !forceRead && 0 == StorageUtil.GetIntValue(None, keyMcmModifiedFlag, 1)
        Return False
    EndIf    
    
    _fw_utils.Info("FWB - ReadConfigurationValues - read configuration values from MCM")
    StorageUtil.SetIntValue(None, keyMcmModifiedFlag, 0)
    
   
    breastValues = StorageUtil.FloatListToArray(mcm, keySliderValues + keyBreasts)
    bellyValues = StorageUtil.FloatListToArray(mcm, keySliderValues + keyBelly)
    buttValues = StorageUtil.FloatListToArray(mcm, keySliderValues + keyButt)
    bodyValues = StorageUtil.FloatListToArray(mcm, keySliderValues + keyBody)
    
    _fw_utils.Info("FWB - ReadConfigurationValues slaPresent: " + slaPresent + ", wntPresent: " + wntPresent + ", mmePresent: " + mmePresent + ", morphs: " +  StorageUtil.GetIntValue(mcm, keyMorphsEnabled))
    
    ; While it seems counter-intuitive to zero Modifiers rather than Values here, Values aren't used if disabled, Modifiers CAN be.
    ; See CombineModifiers()
    If slaPresent
        ; Read SLA per-input enables
        slaEnabled = StorageUtil.IntListToArray(mcm, keySlaEnables)
        
        _fw_utils.Info("FWB ReadConfigurationValues - slaEnabled " + slaEnabled[0] + " " + slaEnabled[1] + " " + slaEnabled[2] + " " + slaEnabled[3])
        
        If slaEnabled[slArousal]
            slaArousalValues = StorageUtil.FloatListToArray(mcm, keySliderValues + slaPageNames[slArousal] )
        Else
            ZeroArray(slaArousalModifiers)
        EndIf
        
        If slaEnabled[slDenial]
            slaDenialValues = StorageUtil.FloatListToArray(mcm, keySliderValues + slaPageNames[slDenial] )
        Else
            ZeroArray(slaDenialModifiers)
        EndIf
        If slaEnabled[slRaped]
            slaRapedValues = StorageUtil.FloatListToArray(mcm, keySliderValues + slaPageNames[slRaped] )
        Else
            ZeroArray(slaRapedModifiers)
        EndIf
        If slaEnabled[slAddicted]
            slaAddictionValues = StorageUtil.FloatListToArray(mcm, keySliderValues + slaPageNames[slAddicted] )
        Else
            ZeroArray(slaAddictionModifiers)
        EndIf
    Else
        ZeroArray(slaArousalModifiers)
        ZeroArray(slaDenialModifiers)
        ZeroArray(slaRapedModifiers)
        ZeroArray(slaAddictionModifiers)
    EndIf
    
    If wntPresent
           ; Read APROPOS per-input enables
        wntEnabled = StorageUtil.IntListToArray(mcm, keyWntEnables)
        
        ;_fw_utils.Info("FWB ReadConfigurationValues - wntEnabled, read " + wntEnabled.Length + " enabled values")
        ;_fw_utils.Info("FWB ReadConfigurationValues - wntEnabled " + wntEnabled[0] + " " + wntEnabled[1] + " " + wntEnabled[2] + " " + wntEnabled[3]  + " " + wntEnabled[4] + " " + wntEnabled[5])
        
        If wntEnabled[wtAbuse]
            wntAbuseValues = StorageUtil.FloatListToArray(mcm, keySliderValues + wntPageNames[wtAbuse] )
        Else
            ZeroArray(wntAbuseModifiers)
        EndIf
        
        If wntEnabled[wtCreature]
            wntCreatureValues = StorageUtil.FloatListToArray(mcm, keySliderValues + wntPageNames[wtCreature] )
        Else
            ZeroArray(wntCreatureModifiers)
        EndIf
        
        If wntEnabled[wtDaedric]
            wntDaedricValues = StorageUtil.FloatListToArray(mcm, keySliderValues + wntPageNames[wtDaedric] )
        Else
            ZeroArray(wntDaedricModifiers)
        EndIf
        
        If wntEnabled[wtVaginal]
            wntVaginalValues = StorageUtil.FloatListToArray(mcm, keySliderValues + wntPageNames[wtVaginal] )
        Else
            ZeroArray(wntVaginalModifiers)
        EndIf
        
                
        If wntEnabled[wtAnal]
            wntAnalValues = StorageUtil.FloatListToArray(mcm, keySliderValues + wntPageNames[wtAnal] )
        Else
            ZeroArray(wntAnalModifiers)
        EndIf

                
        If wntEnabled[wtOral]
            wntOralValues = StorageUtil.FloatListToArray(mcm, keySliderValues + wntPageNames[wtOral] )
        Else
            ZeroArray(wntOralModifiers)
        EndIf

    Else
        ZeroArray(wntAbuseModifiers)
        ZeroArray(wntCreatureModifiers)
        ZeroArray(wntDaedricModifiers)
        ZeroArray(wntVaginalModifiers)
        ZeroArray(wntAnalModifiers)
        ZeroArray(wntOralModifiers)
    EndIf
    
    If mmePresent
        ; Read MME per-input enables
        mmeEnabled = StorageUtil.IntListToArray(mcm, keyMmeEnables)
    
        If mmeEnabled[mmMilk]
            mmeMilkValues = StorageUtil.FloatListToArray(mcm, keySliderValues + mmePageNames[mmMilk] )
        Else
            ZeroArray(mmeMilkModifiers)
        EndIf
        If mmeEnabled[mmBreastFull]
            mmeBreastPercentValues = StorageUtil.FloatListToArray(mcm, keySliderValues + mmePageNames[mmBreastFull] )
        Else
            ZeroArray(mmeBreastPercentModifiers)
        EndIf
        If mmeEnabled[mmPain]
            mmePainPercentValues = StorageUtil.FloatListToArray(mcm, keySliderValues + mmePageNames[mmPain] )
        Else
            ZeroArray(mmePainPercentModifiers)
        EndIf
    Else
        ZeroArray(mmeMilkModifiers)
        ZeroArray(mmeBreastPercentModifiers)
        ZeroArray(mmePainPercentModifiers)
    EndIf
    
    
    ; Morphs
    morphsEnabled = 0 != StorageUtil.GetIntValue(mcm, keyMorphsEnabled)
    morphMode = StorageUtil.GetStringValue(mcm, keyMorphMode)
    
    morphNames = StorageUtil.StringListToArray(mcm, keyMorphNames)
    morphMasterWeights = StorageUtil.FloatListToArray(mcm, keyMorphMasterWeights)
    morphMasterScales = StorageUtil.FloatListToArray(mcm, keyMorphMasterScales)
    morphWeights = StorageUtil.FloatListToArray(mcm, keyMorphWeights)
    morphOffsets = StorageUtil.FloatListToArray(mcm, keyMorphOffsets)
    
    tripRapeChance = StorageUtil.GetFloatValue(mcm, keyTripRapeChance)
    tripRapeChance_x100 = (tripRapeChance As Int) * 100
    
    fallRapeChance = StorageUtil.GetFloatValue(mcm, keyFallRapeChance)
    fallRapeChance_x100 = (fallRapeChance As Int) * 100
    

    clampLower = StorageUtil.FloatListToArray(mcm, keyLimitValuesLo)
    clampUpper = StorageUtil.FloatListToArray(mcm, keyLimitValuesHi)
    
    masterDebuff = StorageUtil.GetFloatValue(mcm, keyMasterDebuff, 100.0)
    masterBuff = StorageUtil.GetFloatValue(mcm, keyMasterBuff, 100.0)
    
    ; the stamina and health instant changes are read on demand, not here, as they're used rarely, same with reallyDrop (read in effect)
    
    If InDebug()
        If !breastValues.Length || !bellyValues.Length || !buttValues.Length || !bodyValues.Length || !clampLower.Length || !clampUpper.Length || !slaArousalValues.Length || !slaDenialValues.Length || !slaRapedValues.Length || !slaAddictionValues.Length
            _fw_utils.Info("FWB - ReadConfigurationValues - empty array - breasts: " + breastValues.Length \
                + ", belly: " + bellyValues.Length + ", butt: " + buttValues.Length + ", body: " + bodyValues.Length \
                + ", clampLower: " + clampLower.Length + ", clampUpper: " + clampUpper \
                + ", slaArousal: " + slaArousalValues.Length + ", slaDenial: " + slaDenialValues.Length \
                + ", slaRape: " + slaRapedValues.Length + ", slaAddicted: " + slaAddictionValues.Length)
        EndIf
    EndIf
    
    ;Debug.Notification(ModName + " configuration changed - read MCM values")
    Return True

EndFunction


; This is used to SET playerChanged, it doesn't depend on it.
Bool Function ReadPlayerState()

    _fw_utils.Info("FWB Start read player state")

    oldPlayerStates[0] = playerStates[0]
    oldPlayerStates[1] = playerStates[1]
    oldPlayerStates[2] = playerStates[2]
    oldPlayerStates[3] = playerStates[3]
    oldPlayerStates[4] = playerStates[4]

    Bool useSlif = slifPresent && 0 != StorageUtil.GetIntValue(None, keyUseSlifForNodes, 0)
    
    If !useSlif || !_fwb_slif.GetNodeValues(player, playerStates)
        ; Get actual node values instead of SLIF
        Float breastScaleL = NetImmerse.GetNodeScale(player, NINODE_BREAST_LEFT, false)
        Float breastScaleR = NetImmerse.GetNodeScale(player, NINODE_BREAST_RIGHT, false)
        Float bellyScale = NetImmerse.GetNodeScale(player, NINODE_BELLY, false)
        Float buttScaleL = NetImmerse.GetNodeScale(player, NINODE_BUTT_LEFT, false)
        Float buttScaleR = NetImmerse.GetNodeScale(player, NINODE_BUTT_RIGHT, false)
        Float bodyWeight = player.GetActorBase().GetWeight()
        
        Float breastScale = (breastScaleL + breastScaleR) * 0.5
        Float buttScale = (buttScaleL + buttScaleR) * 0.5
        
        playerStates[0] = breastScale
        playerStates[1] = bellyScale
        playerStates[2] = buttScale
        playerStates[3] = bodyWeight
    EndIf
    
    If morphsEnabled
        If !useSlif || !_fwb_slif.GetMorphValues(player, morphNames, playerMorphValues)
            GetPlayerMorphValues()
        EndIf
        
        RecalculateMorphContributions()
    EndIf

    ; Carry weight has special processing: we use the base value, but it shouldn't trigger rebuilds of "intentions".
    ; This returns FALSE even if carryWt changed - carryWt shouldn't trigger recalc of "intentions", but is still used to apply effects, regardless
    playerStates[4] = player.GetBaseActorValue(avNames[ixCarryWeight])
    
    StorageUtil.FloatListCopy(mcm, keyPlayerStates, playerStates)
    
    ; Ensures that SLA is actually present first.
    UpdateSlaValues()
    
    ; Only does anything if wntPresent
    UpdateAproposValues()

    ; This will only do anything if mmePresent is True
    UpdateMmeValues()

    _fw_utils.Info("FWB Got NEW player state")
    
    If playerStates[0] != oldPlayerStates[0] || playerStates[1] != oldPlayerStates[1] || playerStates[2] != oldPlayerStates[2] \
            || playerStates[3] != oldPlayerStates[3]
        _fw_utils.Info("FWB Player state changed")
        Return True
    EndIf
    
    
    ; For all these optional components, the ***Values array is reset on configuration change that disables them wholly or in part.
    ; Their states are left with 'junk' in and shouldn't be checked or used.
    
    if (slaEnabled[0] && oldSlaStates[0] != slaStates[0]) || (slaEnabled[1] && oldSlaStates[1] != slaStates[1]) \
            || (slaEnabled[2] && oldSlaStates[2] != slaStates[2]) || (slaEnabled[3] && oldSlaStates[3] != slaStates[3]) 
        _fw_utils.Info("FWB Player state changed due to SLA")
        Return True
    EndIf
    
    If wntPresent
        if (wntEnabled[0] && oldWntStates[0] != wntStates[0]) || (wntEnabled[1] && oldWntStates[1] != wntStates[1]) \
            || (wntEnabled[2] && oldWntStates[2] != wntStates[2]) || (wntEnabled[3] && oldWntStates[3] != wntStates[3]) \
            || (wntEnabled[4] && oldWntStates[4] != wntStates[4]) || (wntEnabled[5] && oldWntStates[5] != wntStates[5])
            _fw_utils.Info("FWB Player state changed due to APROPOS")
            Return True
        EndIf
    EndIf
    
    If mmePresent
        If (mmeEnabled[0] && oldMmeStates[0] != mmeStates[0]) || (mmeEnabled[1] && oldMmeStates[1] != mmeStates[1]) \
                || (mmeEnabled[2] && oldMmeStates[2] != mmeStates[2]) 
            _fw_utils.Info("FWB Player state changed due to MME")
            Return True
        EndIf
    EndIf
    
    _fw_utils.Info("FWB player state UNCHANGED")
    
    Return False

EndFunction


Function UpdateIntentions()
    ; We have a set of node values...
    ; And we have a number of sets of debuff/buff values that are driven by each node value.
    ; Each computes a pair of output values for a stat or skill as a modifier percentage.
    ; Need to sum these up and clamp them to derive the final intended debuff and buff values.
    
    ;Debug.Notification(ModName + " Calculate modifiers")
    
    _fw_utils.Info("FWB UpdateIntentions - calculate modifiers\n")
    
    If configurationChanged || playerStates[ixBreast] != oldPlayerStates[ixBreast]
        _fw_utils.Info("FWB update BREASTS, was " + oldPlayerStates[ixBreast] + ", now " + playerStates[ixBreast])
        CalculateNodeModifers(breastValues, playerStates[ixBreast], breastModifiers, "breasts")
    EndIf
    
    If configurationChanged || playerStates[ixBelly] != oldPlayerStates[ixBelly]
        _fw_utils.Info("FWB update BELLY, was " + oldPlayerStates[ixBelly] + ", now " + playerStates[ixBelly])
        CalculateNodeModifers(bellyValues, playerStates[ixBelly], bellyModifiers, "belly")
    EndIf
    
    If configurationChanged || playerStates[ixButt] != oldPlayerStates[ixButt]
        _fw_utils.Info("FWB update BUTT, was " + oldPlayerStates[ixButt] + ", now " + playerStates[ixButt])
        CalculateNodeModifers(buttValues, playerStates[ixButt], buttModifiers, "butt")
    EndIf
    
    If configurationChanged || playerStates[ixBody] != oldPlayerStates[ixBody]
        _fw_utils.Info("FWB update BODY, was " + oldPlayerStates[ixBody] + ", now " + playerStates[ixBody])
        CalculateNodeModifers(bodyValues, playerStates[ixBody], bodyModifiers, "body")
    EndIf

    
    If slaPresent
        _fw_utils.Info("FWB UpdateIntentions - slaPresent - arousal " + slaEnabled[0] + ", denial " + slaEnabled[1] + ", rape " + slaEnabled[2] + ", addict " + slaEnabled[3])
        
        If slaEnabled[0] && (configurationChanged || (slaStates[0] != oldSlaStates[0]))
            _fw_utils.Info("FWB update SLA AROUSAL, was " + oldSlaStates[0] + ", now " + slaStates[0])
            CalculateNodeModifers(slaArousalValues , slaStates[0], slaArousalModifiers, "sla-arousal")
        EndIf
        If slaEnabled[1] && (configurationChanged || (slaStates[1] != oldSlaStates[1]))
            _fw_utils.Info("FWB update SLA DENIAL, was " + oldSlaStates[1] + ", now " + slaStates[1])
            CalculateNodeModifers(slaDenialValues , slaStates[1], slaDenialModifiers, "sla-denial")
        EndIf
        If slaEnabled[2] && (configurationChanged || (slaStates[2] != oldSlaStates[2]))
            _fw_utils.Info("FWB update SLA RAPED, was " + oldSlaStates[2] + ", now " + slaStates[2])
            CalculateNodeModifers(slaRapedValues , slaStates[2], slaRapedModifiers, "sla-raped")
        EndIf
        If slaEnabled[3] && (configurationChanged || (slaStates[3] != oldSlaStates[3]))
            _fw_utils.Info("FWB update SLA ADDICTED, was " + oldSlaStates[3] + ", now " + slaStates[3])
            CalculateNodeModifers(slaAddictionValues , slaStates[3], slaAddictionModifiers, "sla-addicted")
        EndIf
    EndIf

    If wntPresent
        If wntEnabled[0] && (configurationChanged || (wntStates[0] != oldWntStates[0]))
            CalculateNodeModifers(wntAbuseValues, wntStates[0], wntAbuseModifiers, "wnt-abuse")
        EndIf
        If wntEnabled[1] && (configurationChanged || (wntStates[1] != oldWntStates[1]))
            CalculateNodeModifers(wntCreatureValues, wntStates[1], wntCreatureModifiers, "wnt-creature")
        EndIf
        If wntEnabled[2] && (configurationChanged || (wntStates[2] != oldWntStates[2]))
            CalculateNodeModifers(wntDaedricValues, wntStates[2], wntDaedricModifiers, "wnt-daedric")
        EndIf
        If wntEnabled[3] && (configurationChanged || (wntStates[3] != oldWntStates[3]))
            CalculateNodeModifers(wntVaginalValues, wntStates[3], wntVaginalModifiers, "wnt-vaginal")
        EndIf
        If wntEnabled[4] && (configurationChanged || (wntStates[4] != oldWntStates[4]))
            CalculateNodeModifers(wntAnalValues, wntStates[4], wntAnalModifiers, "wnt-anal")
        EndIf
        If wntEnabled[5] && (configurationChanged || (wntStates[5] != oldWntStates[5]))
            CalculateNodeModifers(wntOralValues, wntStates[5], wntOralModifiers, "wnt-oral")
        EndIf
    EndIf
    
    If mmePresent
        If mmeEnabled[0] && (configurationChanged || (mmeStates[0] != oldMmeStates[0]))
            _fw_utils.Info("FWB update MME MILK WT, was " + oldMmeStates[0] + ", now " + mmeStates[0])
            CalculateNodeModifers(mmeMilkValues , mmeStates[0], mmeMilkModifiers, "mme-milk")
        EndIf
        If mmeEnabled[1] && (configurationChanged || (mmeStates[1] != oldMmeStates[1]))
            _fw_utils.Info("FWB update MME BREAST-%, was " + oldMmeStates[1] + ", now " + mmeStates[1])
            CalculateNodeModifers(mmeBreastPercentValues , mmeStates[1], mmeBreastPercentModifiers, "mme-breast-pct")
        EndIf
        If mmeEnabled[2] && (configurationChanged || (mmeStates[2] != oldMmeStates[2]))
            _fw_utils.Info("FWB update MME PAIN, was " + oldMmeStates[2] + ", now " + mmeStates[2])
            CalculateNodeModifers(mmePainPercentValues , mmeStates[2], mmePainPercentModifiers, "mme-pain-pct")
        EndIf
    EndIf
    
EndFunction


; Calculate modifierValues[] for a given input, doing interpolation on From -> To values.
; setup contains the MCM configuration data for all the sliders for a given page.
; value is the state of the INPUT value for that page.
; modifierValues is the set of OUTPUT values for the page, and it has a 1 to 1 mapping with the sliders.
Function CalculateNodeModifers(Float[] setup, Float value, Float[] modifierValues, String debugName)

    ; Per item config is interpreted so +ve value means apply a buff, -ve value means apply a debuff, regardless of the source control value.
    ;_fw_utils.Info("FWB CalculateNodeModifers: " + debugName + " setup " + setup.Length + ", value " + value + ", modifierValues " + modifierValues.Length)

    Float debuffScale = setup[ixDebuffMax] * 0.01
    Float debuffFrom = setup[ixDebuffFrom]
    Float debuffTo = setup[ixDebuffTo]
    Float interpolated = Interpolate(debuffFrom, debuffTo, value)
    
    Float effectiveDebuff = interpolated * debuffScale
    ; Can shortcut if == 0.0 or 1.0

    ;_fw_utils.Info("FWB interpolated " + debugName + " debuff value " + interpolated + " * " + debuffScale + " => " + effectiveDebuff)
    
    Float buffScale = setup[ixBuffMax] * 0.01
    Float buffFrom = setup[ixBuffFrom]
    Float buffTo = setup[ixBuffTo]
    interpolated = Interpolate(buffFrom, buffTo, value)
    Float effectiveBuff = interpolated * buffScale
    
    ;_fw_utils.Info("FWB interpolated " + debugName + " buff value " + interpolated + " * " + debuffScale + " => " + effectiveDebuff)

    Int ii = ixStart
    While ii < ixEnd
        Float debuff = setup[ii] * effectiveDebuff
        Float buff = setup[ii+1] * effectiveBuff
        modifierValues[ii] = debuff
        modifierValues[ii+1] = buff
        ii += 2
    EndWhile
    
    If InDebug()
        _fw_utils.Info("FWB - CalculateNodeModifers - " + debugName + ", value: " + value)
        _fw_utils.Info("    >>>> " + debugName + " calculated D " + _fw_utils.FormatFloat_N1(effectiveDebuff) + ", B " + _fw_utils.FormatFloat_N1(effectiveBuff))
        ii = ixStart
        While ii < ixEnd
            _fw_utils.Info("    #### " + ii + " => " + modifierValues[ii] + " ## " + modifierValues[ii+1])
            ii += 2
        EndWhile
    EndIf
    
EndFunction 


Float Function Interpolate(Float from, Float to, Float value)

    ; If player sets zero range, then we're always clamped above or below, but we can't tell the direction (so assume ascending)
    If to == from
        If value < from
            Return 0.0
        Else
            Return 1.0
        EndIF
    EndIf

    Float p =(value - from) / (to - from)
    If p < 0.0
        p = 0.0
    ElseIf p > 1.0
        p = 1.0
    EndIf
    Return p
   
EndFunction


; Calculate the overall currentModifiers from xxxxModifiers - collapses all the different inputs down to a single set.
Function CombineModifiers()    
    
    ; Add up the new modifiers
    Int ii = ixStart
    
    If mmePresent && (mmeEnabled[0] || mmeEnabled[1] || mmeEnabled[2])
    
        ;_fw_utils.Info("FWB update modifers from " + ixStart + " to " + ixEnd + " with MME values included")
        While ii <= ixEnd
            currentModifiers[ii] = breastModifiers[ii] + bellyModifiers[ii] + buttModifiers[ii] + bodyModifiers[ii] \
                + slaArousalModifiers[ii] + slaDenialModifiers[ii] + slaRapedModifiers[ii] + slaAddictionModifiers[ii] \
                + mmeMilkModifiers[ii] + mmeBreastPercentModifiers[ii] + mmePainPercentModifiers[ii]
            
            ii += 1
        EndWhile
        
    Else ; ELSE NOT A BUG - notice that SLA is also processed above - this avoids double loop traversal.
    
        ; Always add in SLA, because it's almost certainly present... MME is a bit more situational.
        ;_fw_utils.Info("FWB update modifers from " + ixStart + " to " + ixEnd + " without MME")
        While ii <= ixEnd
            currentModifiers[ii] = breastModifiers[ii] + bellyModifiers[ii] + buttModifiers[ii] + bodyModifiers[ii] \
                + slaArousalModifiers[ii] + slaDenialModifiers[ii] + slaRapedModifiers[ii] + slaAddictionModifiers[ii]
            ii += 1
        EndWhile
    EndIf
    
    If wntPresent && (wntEnabled[0] || wntEnabled[1] || wntEnabled[2] || wntEnabled[3] || wntEnabled[4] || wntEnabled[5])
        ii = ixStart
        While ii <= ixEnd
            ; You'd think I could use += but NO, Papyrus is too broken
            currentModifiers[ii] = currentModifiers[ii] + wntAbuseModifiers[ii] + wntCreatureModifiers[ii] + wntDaedricModifiers[ii] \
                    + wntVaginalModifiers[ii] + wntAnalModifiers[ii] + wntOralModifiers[ii]
            ii += 1
        EndWhile
    EndIf
    
    If InDebug()
        ii = ixStart
        _fw_utils.Info("FWB - CombineModifiers - recalculate currentModifiers[]")
        While ii <= ixEnd
            _fw_utils.Info("    [" + ii + "]  " + currentModifiers[ii])
            ii += 1
        EndWhile
    EndIf
    
EndFunction    


; Applies master slider and clamps to limits. currentModifiers => effectValues.
Function EffectValuesFromModifiers()

    ;Debug.Notification(ModName + " Master: D " + _fw_utils.FormatFloat_N0(masterDebuff) + ", B " + _fw_utils.FormatFloat_N0(masterBuff) )

    ; Collpase the pairs, clean up index base and clamp
    ; Ultimate product of this is the effectValues array.
    Int mm = 0   ; Effect index
    Int ii = ixStart ; Intention index
    While mm < effectCount
        ;_fw_utils.Info("FWB update effect (counts: " + oldEffectValues.Length + " " + effectValues.Length + " " + currentModifiers.Length \
        ;        + " " + clampLower.Length + " " +clampUpper.Length + ") index " + mm)
        ;_fw_utils.Info("FWB from " + oldEffectValues[mm] + " to " + effectValues[mm])
        ;_fw_utils.Info("FWB clampLower / clampUpper " + clampLower[mm] + " / " + clampUpper[mm] + " input " + value)
        
        oldEffectValues[mm] = effectValues[mm]
        Float value = 0.01 * ((masterDebuff * currentModifiers[ii]) + (masterBuff * currentModifiers[ii + 1]))
        
        ; Clamping
        If value < clampLower[mm]
            value = clampLower[mm]
        ElseIf value > clampUpper[mm]
            value = clampUpper[mm]
        EndIf
         
        effectValues[mm] = value
        ;_fw_utils.Info("FWB update effect value " + mm + " clamped to " + value)

        ii += 2
        mm += 1
    EndWhile
   
    ; Store the final effectValues for debugging
    StorageUtil.FloatListCopy(mcm, keyEffectValues, effectValues)
    ;_fw_utils.Info("FWB stored effect values to " + keyEffectValues)

    If InDebug()
        _fw_utils.Info("FWB - EffectValuesFromModifiers - combine and limit final effects scale list")
        mm = 0
        While mm < effectCount
            _fw_utils.Info("    [" + mm + "]  " + effectValues[mm])
            mm += 1
        EndWhile
    EndIf

EndFunction


; Transform each effectValue into the correct domain for its final application
Function UpdateEffects()
    ; Check all effects are in place as intended.
    
    ;Debug.Notification(ModName + " Update Effects")
    ; Convert effect values into integers
    _fw_utils.Info("FWB UPDATE EFFECTS")
    
    Float updateInterval = StorageUtil.GetFloatValue(None, updateIntervalKey, defaultUpdateInterval)
    
    
    ; Force updates on carryWeight if its base value has changed
    If playerStates[ixCarryWeight] != oldPlayerStates[ixCarryWeight]
        currentEffectValues[ixCarryWeight] = 1 + (effectValues[ixCarryWeight] as Int)
    EndIf
    
    Int ii = effectValues.Length
    While ii
        ii -= 1

        Float value = effectValues[ii]
        ; We don't actually USE the int value to set the effect, it's just used to prevent updating on tiny deltas.
        Int processedEffectValue = value as Int
        
        If configurationChanged || processedEffectValue != currentEffectValues[ii]
        
            currentEffectValues[ii] = processedEffectValue
            
            Int effectType = effectTypes[ii]
            
            If 0 == effectType
            
                ; Straight percentage effect
                UpdateSingleModifier(ii, value, 0.5)
                
            ElseIf 1 == effectType

                ; This is no longer used for attack or bow speed, because they need to SetAV to work around Skyrim bugs.
                ; This is used for Magicka, Health, Stamina REGENERATION rates, because we can't be sure that some rude mod hasn't set the base value to something silly.
                If value < 0.0
                    value *= effectScalesDown[ii]
                Else
                    value *= effectScalesUp[ii]
                EndIf
                UpdateSingleModifier(ii, value, 0.01)
                
            ElseIf 2 == effectType
            
                ; Base scaled Percentage effect. e.g. carry weight = 50% * current-base = +150 weight bonus
                ; Currently, carry weight is the only item using this type
                Float baseValue = playerStates[ixCarryWeight]
                UpdateSingleModifier(ii, value * baseValue * 0.01, 0.5)
                
            ElseIf 3 == effectType
                ; Various custom effect processing steps
                ; Movement speed - currently uses vanilla percentage
                ; For chances, a limit of 1,000 means the chance is at most 1,000 * 3 / 60
                ; = 50% at interval 3 secs, or 100% at intervals of 6 secs or over
                ; TODO: replace simplistic chance per interval with a tracked random deviance from expected rate.
                If ii == exMoveSpeed
                    UpdateSingleModifier(ii, value, 0.5)
                ElseIf ii == exStumble
                    stumbleChance = -value * updateInterval / 60.0
                    stumbleChance_x100 = (stumbleChance * 100.0) as Int
                    _fw_utils.Info("FWB - recalc stumble - " + value + " => " + stumbleChance_x100)
                ElseIf ii == exBleedout
                    bleedoutChance = -value * updateInterval / 60.0
                    bleedoutChance_x100 = (bleedoutChance * 100.0) as Int
                    _fw_utils.Info("FWB - recalc trip - " + value + " => " + bleedoutChance_x100)
                ElseIf ii == exFall
                    fallChance = -value * updateInterval / 60.0
                    fallChance_x100 = (fallChance * 100.0) as Int
                    _fw_utils.Info("FWB - recalc fall - " + value + " => " + fallChance_x100)
                ElseIf ii == exDrop
                    dropChance = -value * updateInterval / 60.0
                    dropChance_x100 = (dropChance * 100.0) as Int
                    _fw_utils.Info("FWB - recalc drop - " + value + " => " + dropChance_x100)
                ElseIf ii == exMasturbate
                    masturbateChance = -value * updateInterval / 60.0
                    masturbateChance_x100 = (masturbateChance * 100.0) as Int
                    _fw_utils.Info("FWB - recalc mast - " + value + " => " + masturbateChance_x100)
                ElseIf ii == exRape
                    rapeChance = -value * updateInterval / 60.0
                    rapeChance_x100 = (rapeChance * 100.0) as Int
                    _fw_utils.Info("FWB - recalc rape - " + value + " => " + masturbateChance_x100)
                EndIf
            ElseIf 4 == effectType
                
                If ii == exVisionBlur
                    ismStrengths[0] = value
                ElseIf ii == exVisionTunnel
                    ismStrengths[1] = value
                ElseIf ii == exVisionDouble
                    ismStrengths[2] = value
                ElseIf ii == exVisionVibrant
                    ismStrengths[3] = value
                EndIf
                
                StorageUtil.FloatListCopy(mcm, keyIsmStrengths, ismStrengths)
                
                If StorageUtil.GetIntValue(None, keyVisualEffectsEnabled)
                    AddSimpleSpell(visualEffectsSpell)
                EndIf

            ElseIf 5 == effectType

                _fw_utils.Info("FWB - AV value for " + ii + " at " + value)
                If StorageUtil.GetIntValue(None, keyEnableAttackSpeed, 1)
                
                    _fw_utils.Info("FWB - UpdateEffects - attack speed changes enabled")
                    
                    If value < 0.0
                        value *= effectScalesDown[ii]
                    Else
                        value *= effectScalesUp[ii]
                    EndIf
                    value += 1.0
                    
                    _fw_utils.Info("FWB - UpdateEffects - calculated value for " + ii + " is " + value)
                    
                    If ii == exAttackSpeed
                        _fw_utils.Info("FWB - UpdateEffects - Set weapon speeds to " + value)
                        ; Set left and right weapons...
                        player.SetActorValue("WeaponSpeedMult", value)
                        player.SetActorValue("LeftWeaponSpeedMult", value)
                    ElseIf ii == exBowSpeed
                        _fw_utils.Info("FWB - UpdateEffects - Set bow speed to " + value)
                        ; Set bow speed modifier (slowdown from aiming perk)
                        player.SetActorValue("BowSpeedBonus", value)
                    EndIf
                    
                    weaponSpeedsModified = True
                    
                Else
                    _fw_utils.Info("FWB - UpdateEffects - attack speed changes disabled")
                    If weaponSpeedsModified
                    _fw_utils.Info("FWB - UpdateEffects - attack speed changes disabled - restore defaults")
                        weaponSpeedsModified = False
                        ; Restore defaults... Hopefully.
                        player.SetActorValue("WeaponSpeedMult", 1.0)
                        player.SetActorValue("LeftWeaponSpeedMult", 1.0)
                        player.SetActorValue("BowSpeedBonus", 1.0)
                    EndIf
                EndIf
            EndIf
        EndIf
    EndWhile
    
    StorageUtil.IntListCopy(mcm, keyCurrentEffectValues, currentEffectValues)
    
EndFunction


Function UpdateSingleModifier(Int ii, Float value, Float zeroThreshold)

    ;Debug.Notification(ModName + " Update " + ii + " to " + _fw_utils.FormatFloat_N1(value))
    
    ; Update the effect
    Spell debuff = spellsDown[ii]
    Spell buff = spellsUp[ii]
    
    If value > zeroThreshold ; Buff
        RemoveSpell(debuff)
        If buff != dummySpell
            RefreshScaledSpell(buff, value)
        EndIf
    ElseIf value < -zeroThreshold ; Debuff
        RemoveSpell(buff)
        If debuff != dummySpell
            RefreshScaledSpell(debuff, -value)
        EndIf
    Else
        RemoveSpell(buff)
        RemoveSpell(debuff)
    EndIf
    
EndFunction


Function ResetSpells()
    _fw_utils.Info("FWB - ResetSpells - begin")
    Int ii = spellsDown.Length
    While ii
        ii -= 1
        RemoveSpell(spellsDown[ii])
    EndWhile
    
    ii = spellsUp.Length
    While ii
        ii -= 1
        RemoveSpell(spellsUp[ii])
    EndWhile
    
    ii = currentEffectValues.Length
    While ii
        ii -= 1
        currentEffectValues[ii] = 0
        effectValues[ii] = 0.0
    EndWhile
    _fw_utils.Info("FWB - ResetSpells - end")
EndFunction

; This works with abilities
Function RefreshScaledSpell(Spell singleEffectSpell, Float magnitude)

    _fw_utils.Info("FWB - RefreshScaledSpell " + singleEffectSpell.GetName() + " at " + magnitude)
	If Player.HasSpell(singleEffectSpell)
        _fw_utils.Info("FWB - RefreshScaleSpell - spell already present, remove")
		Player.RemoveSpell(singleEffectSpell)
	EndIf

	If magnitude
        _fw_utils.Info("FWB - RefreshScaleSpell - add and set spell magnitude")
		singleEffectSpell.SetNthEffectMagnitude(0, magnitude)
		Player.AddSpell(singleEffectSpell, false)
	EndIf

EndFunction


Function RefreshFormListSpells(FormList spellList, Float magnitude)

	Int ii = spellList.GetSize()
	
	While ii
		ii -= 1
		Spell targetSpell = spellList.GetAt(ii) as Spell
        
		RefreshScaledSpell(targetSpell, magnitude)
	EndWhile
	
EndFunction

; Works with abilities
Function RemoveSpell(Spell targetSpell)
	If Player.HasSpell(targetSpell)
        _fw_utils.Info("FWB - RemoveSpell " + targetSpell.GetName())
		Player.RemoveSpell(targetSpell)
	EndIf
EndFunction


Function AddSimpleSpell(Spell targetSpell)
	If !Player.HasSpell(targetSpell)
        _fw_utils.Info("FWB - AddSimpleSpell " + targetSpell.GetName())
		Player.AddSpell(targetSpell, false)
	EndIf
EndFunction


; TODO - MCM options for random timing scales, in combat and out of combat

Function CheckAndTriggerEvents()    

    ; Only stumble, fall, or drop things when we run with scissors or sneak.
    ; Mutex blocks all events, including falls, weapons, etc.
    Int threadEventMutex = StorageUtil.GetIntValue(None, keyThreadEventMutex)
    Bool notInMenu = !Utility.IsInMenuMode() && !UI.IsMenuOpen("Dialogue Menu")
    
    _fw_utils.Info("FWB - CheckAndTriggerEvents - mutex: " + threadEventMutex + ", notInMenu: " + notInMenu)
    _fw_utils.Info("    Health  info: " + _fw_utils.DumpActorValue(player, "Health"))
    _fw_utils.Info("    Stamina info: " + _fw_utils.DumpActorValue(player, "Stamina"))
    
    If threadEventMutex <= 0 && notInMenu
    
        Float timeNow = Utility.GetCurrentRealTime()

        Bool inCombat = player.IsInCombat()

        _fw_utils.Info("FWB - CheckAndTriggerEvents - inCombat: " + inCombat + ", timeNow: " + timeNow + ", sexEventCooldownExpire: " + sexEventCooldownExpire + ", wobbleEventCooldownExpired: " + wobbleEventCooldownExpired)
        

        
        If timeNow > sexEventCooldownExpire
        
            If !inCombat && (masturbateChance_x100 > 0 || rapeChance_x100 > 0)
            
                Int maxRandom = 10000   ; TO-DO - Maybe add this via MCM later ... Supposed to be per-minute (real time).
                
                ; Consider rape, then masturbate first. If either of these happens, don't check for trips, falls, drops, etc.
                ; Let sneaking block masturbate, so the player has -some- control over it.
                Bool isSneaking = player.IsSneaking()
                _fw_utils.Info("FWB - check masturbate - sneaking " + isSneaking + " - chance " + masturbateChance_x100)
                If masturbateChance_x100 > 0 && !isSneaking
                    Int randomValue = Utility.RandomInt(0, maxRandom)
                    If randomValue < masturbateChance_x100
                        ; This is a costly check, so trying to avoid it until no alternative.
                        If !IsAnimating(player)
                            _fw_utils.Info("FWB - trigger StartMasturbate")
                            StartMasturbate(timeNow)
                        EndIf
                        
                        Return
                    EndIf
                EndIf
                
                
                _fw_utils.Info("FWB - pre-rape check - chance " + rapeChance_x100)
                If rapeChance_x100 > 0 && IsRapeAllowed()
                    Int randomValue = Utility.RandomInt(0, maxRandom)
                    _fw_utils.Info("FWB - rape random " + randomValue + " out of " + maxRandom)
                    If randomValue < rapeChance_x100
                        ; IsAnimating is a costly check, so trying to avoid it until no alternative. IsBrawlHazard isn't so cheap either.
                        If !IsBrawlHazard() && !IsAnimating(player)
                            _fw_utils.Info("FWB - trigger StartRape")
                            StartRape(timeNow)
                        Else
                            _fw_utils.Info("FWB - rape abandoned due to animating or brawl risk")
                        EndIf
                        
                        Return
                    EndIf
                EndIf
                
            EndIf
        EndIf
                    
        ; These events can happen IN combat, but the chance varies according to player preference.
        If timeNow > wobbleEventCooldownExpired
            If player.IsRunning() || player.IsSprinting() || player.IsSneaking()
            
                Float combatScale = StorageUtil.GetFloatValue(mcm, keyCombatEventScale, 4.0)
                Float sprintScale = StorageUtil.GetFloatValue(mcm, keySprintEventScale, 1.0)
                
                _fw_utils.Info("FWB - check wobble events: stumble " + stumbleChance_x100 + ", bleedout " + bleedoutChance_x100 + ", fall " + fallChance_x100 + ", combatScale " + combatScale + ", sprintScale " + sprintScale)
            
                If stumbleChance_x100 > 0 || bleedoutChance_x100 > 0 || fallChance_x100 > 0 || dropChance_x100 > 0
            
                    ; Only check if something else isn't happening (this check is quite costly)
                    If  !IsAnimating(player)
                        
                        Bool didSomething = False
                        
                        Int maxRandom = 10000 ; TO-DO - Maybe add this via MCM later ...
                        
                        If inCombat
                            maxRandom = ((maxRandom as Float) / combatScale) as Int ; chance increased to per 15 seconds - TO-DO - add scale up to MCM.
                        EndIf
                        
                        If player.IsSprinting()
                            maxRandom = ((maxRandom as Float) / sprintScale) as Int
                        EndIf
                        
                        ; Test most severe events first, they block trivial events if they occur.
                        If fallChance_x100 > 0
                            Int randomValue = Utility.RandomInt(0, maxRandom)
                            If randomValue < fallChance_x100
                                
                                _fw_utils.Info("FWB - trigger ApplyFall")
                               ApplyFall()
                                Int checkForRape = Utility.RandomInt(0, maxRandom)
                                If checkForRape <= fallRapeChance_x100
                                    _fw_utils.Info("FWB - trigger StartRape from fall with " + checkForRape + " out of " + maxRandom + " with chance " + fallRapeChance_x100)
                                    StartRape(timeNow)
                                EndIf
                                
                                didSomething = True;
                                ; drop is a certainty
                                maxRandom = 0
                            EndIf
                        EndIf

                        If bleedoutChance_x100 > 0 && !didSomething
                            Int randomValue = Utility.RandomInt(0, maxRandom)
                            If randomValue < bleedoutChance_x100
                                
                               _fw_utils.Info("FWB - trigger ApplyBleedout")
                               ApplyBleedout()
                                Int checkForRape = Utility.RandomInt(0, maxRandom)
                                If checkForRape <= tripRapeChance_x100
                                    _fw_utils.Info("FWB - trigger StartRape from bleedout with " + checkForRape + " out of " + maxRandom + " with chance " + tripRapeChance_x100)
                                    StartRape(timeNow)
                                EndIf
                              
                                didSomething = True
                                maxRandom /= 10 ; Chance of concurrent drop increased.
                            EndIf
                        EndIf
                            
                        If stumbleChance_x100 > 0 && !didSomething
                        ; Stumble
                            Int randomValue = Utility.RandomInt(0, maxRandom)
                            If randomValue < stumbleChance_x100
                                ;Debug.Notification(ModName + " Stumble")
                                
                                _fw_utils.Info("FWB - trigger ApplyStumble")
                                ApplyStumble()
                                
                                didSomething = True
                                maxRandom /= 4 ; Chance of concurrent drop increased
                            EndIf
                        EndIf
                        
                        ; Drop weapons - only when already drawn
                        If dropChance_x100 > 0 && player.IsWeaponDrawn()
                            Int randomValue = Utility.RandomInt(0, maxRandom)
                            If randomValue < dropChance_x100
                            
                                Bool dropBoth = randomValue*2 < dropChance_x100
                               _fw_utils.Info("FWB - trigger DropWeapons - both: " + dropBoth)
                               
                                DropWeapons(dropBoth) ; half the time, drop both
                                
                                If !didSomething
                                    _fw_utils.Info("FWB - trigger ApplyStumble to hint at weapon drop")
                                    ApplyStumble() ; Visual cue that something happened.
                                EndIf
                            EndIf
                        EndIf
                        
                        If didSomething
                            If inCombat
                                _fw_utils.Info("FWB - events - did SOMETHING in combat")
                                wobbleEventCooldownExpired = timeNow + wobbleEventCooldownSecondsForCombat
                            Else
                                _fw_utils.Info("FWB - events - did SOMETHING out of combat")
                                wobbleEventCooldownExpired = timeNow + wobbleEventCooldownSeconds
                            EndIf
                        EndIf

                    EndIf
                EndIf
            EndIf
        EndIf
    EndIf
    
    _fw_utils.Info("FWB - CheckAndTriggerEvents - DONE")
    _fw_utils.Info("    Health info:  " + _fw_utils.DumpActorValue(player, "Health"))
    _fw_utils.Info("    Stamina info: " + _fw_utils.DumpActorValue(player, "Stamina"))

EndFunction


; This applies a Spell, not an ability
Function ApplyStumble()

    If StorageUtil.GetIntValue(None, keyUseInternalAnimations) && animStaggerEffectSpell
        animStaggerEffectSpell.Cast(player)
    Else
        Spell stumbleSpell = spellsDown[exStumble]
        stumbleSpell.Cast(player)
    EndIf
    
    DamageActorValueByIndex(exStamina, keyStumbleStamina, false)
    
    ;Debug.Notification(ModName + " Stumble " + stumbleSpell.GetName())
EndFunction

; Also spell, not ability based.
Function ApplyBleedout()

    Spell bleedoutSpell = spellsDown[exBleedout]
    bleedoutSpell.Cast(player)
    
    DamageActorValueByIndex(exStamina, keyTripStamina, false)
    
    ;Debug.Notification(ModName + " Cast bleedout - " + bleedoutSpell.GetName())

EndFunction


; Spell based
Function ApplyFall()

    Spell fallSpell = spellsDown[exFall]
    fallSpell.Cast(player)
    
    DamageActorValueByIndex(exStamina, keyFallStamina, false)
    DamageActorValueByIndex(exHealth, keyFallHealth, true)

EndFunction

; Modifies AV directly.
Function DamageActorValueByIndex(Int index, String keyName, Bool makeSafe)

    ; Percentage isn't a percentage, it's 0.0 to 1.0 
    String valueName = avNames[index]
    Float percentageLoss = StorageUtil.GetFloatValue(mcm, keyName)
    Float currentValue = player.GetAV(valueName)
    Float loss = currentValue * percentageLoss * 0.01
    
    _fw_utils.Info("FWB - DamageActorValueByIndex [" + index + "] (" + valueName + "), key = " + keyName + ", safe = " + makeSafe + ", by " + percentageLoss + "%, currently " + currentValue + " => " + loss)
    _fw_utils.Info("    Stat info: " + _fw_utils.DumpActorValue(player, valueName))
    
    If makeSafe && (currentValue - loss) < 1.0
        _fw_utils.Info("FWB - DamageActorValueByIndex - bottomed out - skipped")
        Return
    EndIf
    
    If loss >= 1.0
        _fw_utils.Info("FWB - DamageActorValueByIndex - DO " + loss + " DAMAGE to " + valueName )
        player.DamageAV(valueName, loss)
    EndIf
    
EndFunction

; Casts a spell that does the hard work.
Function DropWeapons(Bool dropBoth)

    If dropBoth
        Spell dropWeaponsSpell = spellsDown[exDrop]
        dropWeaponsSpell.Cast(player)
    Else
        Spell dropWeaponSpell = spellsUp[exDrop]
        dropWeaponSpell.Cast(player)
    EndIf

    ; The deviously helpless weapon-drop mod event dhlp-weapondrop isn't just a notification, it's a request to DHLP to drop the weapons.
    ; It runs weapon drop code in response.
    ; As SLD can drop weapons on its own, just fine, instead of sending the event, we should be able to respond to it.
    ; This would allow FHU (or other mods) to drop weapons via SLD.
    ; Will look into it when FHU support goes in.
    ; See WD_Util (line 1243) in DHLP, and sr_inflateQuest (line 239) in FHU.
    ; I have NO CLUE why DHLP decided to base drop chance on 100 - Magicka ... won't be emulating that, or will at least be optional.

endFunction

; Deprecated - for testing only
Bool Function DropHand(Int hand)

    ; Returns true if you might need to drop the other hand.
    Bool dropAgain = True
    Int handType = player.GetEquippedItemType(hand)
    If handType
        If 9 == handType ; Spell
            UnequipSpell(hand)
        ElseIf 10 == handType ; Shield
            DropOrUnequipShield()
        ElseIf (handType >= 5 && handType < 9) || 12 == handType ; 2H
            DropOrUnequip2H()
            dropAgain = False
        Else ; 1H
            DropOrUnequip1H(hand)
        EndIf
    EndIf
    
    Return dropAgain
    
EndFunction


; Deprecated - for testing only - removes an equipped spell "weapon"
Function UnequipSpell(Int hand)
    Spell equippedSpell = player.GetEquippedSpell(hand)
    If equippedSpell
        player.UnequipSpell(equippedSpell, hand)
        Debug.Notification("You lose focus on your " + equippedSpell.GetName() + " spell")
    EndIf
EndFunction


; TODO - UnequipItem can destroy charges if game doesn't have the fix for it? Test this.
Function DropOrUnequipShield()
    Armor equippedShield = player.GetEquippedShield()
    If equippedShield
        player.UnequipItem(equippedShield, abPreventEquip = False, abSilent = True)
        Debug.Notification("You lose grip on your shield")
    EndIf
EndFunction


Function DropOrUnequip1H(Int hand)
    Weapon equipped = player.GetEquippedWeapon(0 == hand) ; true = left hand
    If equipped
        player.UnequipItem(equipped, abPreventEquip = False, abSilent = True)
        Debug.Notification("Your weapon slips from your hand")
    EndIf 
EndFunction


Function DropOrUnequip2H()
    Weapon equipped = player.GetEquippedWeapon()
    If equipped
        player.UnequipItem(equipped, abPreventEquip = False, abSilent = True)
        Debug.Notification("Your weapon slips out of your hands")
    EndIf 
EndFunction


Function StartMasturbate(Float timeNow)

    If timeNow > sexEventCooldownExpire
        If StorageUtil.GetIntValue(None, keyThreadEventMutex) <= 0
            
            ; Cooldown should allow enough time to block additional events occuring before sex starts and we're 'animating'.
            sexEventCooldownExpire = timeNow + sexEventCooldownSeconds
            _fw_utils.Info("FWB - StartMasturbate origin")
            _fwb_sexwrap.StartMasturbate(GetSexLabQuest(), player)
        Else
            _fw_utils.Info("FWB - StartMasturbate blocked by mutex")
        EndIf
    Else
        _fw_utils.Info("FWB - StartMasturbate blocked by cooldown")
    EndIf
    
EndFunction


Function StartRape(Float timeNow)

    If timeNow > sexEventCooldownExpire
        If StorageUtil.GetIntValue(None, keyThreadEventMutex) <= 0
            ; Cooldown should allow enough time to block additional events occuring before sex starts and we're 'animating'.
            sexEventCooldownExpire = timeNow + sexEventCooldownSeconds
            _fw_utils.Info("FWB - StartRape origin")
            _fwb_sexwrap.StartRape(GetSexLabQuest(), player)
        Else
            _fw_utils.Info("FWB - StartRape blocked by mutex")
        EndIf
    Else
        _fw_utils.Info("FWB - StartRape blocked by cooldown")
    EndIf
    
EndFunction


Function StartOrgy()

    Float timeNow = Utility.GetCurrentRealTime()
    
    If timeNow > sexEventCooldownExpire
        If StorageUtil.GetIntValue(None, keyThreadEventMutex) <= 0
            ; Cooldown should allow enough time to block additional events occuring before sex starts and we're 'animating'.
            sexEventCooldownExpire = timeNow + sexEventCooldownSeconds
            _fw_utils.Info("FWB - StartOrgy origin")
            _fwb_sexwrap.StartOrgy(GetSexLabQuest(), player)
        Else
            _fw_utils.Info("FWB - StartOrgy blocked by mutex")
        EndIf
    Else
        _fw_utils.Info("FWB - StartMasturbate blocked by cooldown")
    EndIf

EndFunction


Event OnSexSceneEnd(Int slotNumber, String actType)

    ; Bump out cooldown, so we get that much time before another rape - as this could easily have taken longer than cooldown...
    Float timeNow = Utility.GetCurrentRealTime()
    sexEventCooldownExpire = timeNow + sexEventCooldownSeconds

    _fw_utils.Info("FWB got OnSexSceneEnd in modifiers, for slot " + slotNumber)
    
    String actName = actType
    If "solo" == actType
        actName = "- " + StorageUtil.GetStringValue(player, "fwb_soloLastAnimationPlayed")
    ElseIf "rape" == actType
        actName = "- " + StorageUtil.GetStringValue(player, "fwb_rapeLastAnimationPlayed")
    EndIf
    
EndEvent


Quest Function GetSexLabQuest()
    
    If !fwb_sexlab.IsRunning()
        fwb_sexlab.Start()
    EndIf
    
    Return fwb_sexlab
    
EndFunction


Event OnDeviousSuspend(string eventName, string strArg, float numArg, Form sender)

    _fw_utils.Info("FWB - received dhlp-Suspend event")
    StorageUtil.AdjustIntValue(None, keyThreadEventMutex, 1)

EndEvent


Event OnDeviousResume(string eventName, string strArg, float numArg, Form sender)

    _fw_utils.Info("FWB - received dhlp-Resume event")
    StorageUtil.AdjustIntValue(None, keyThreadEventMutex, -1)

EndEvent



Bool Function IsAnimating(Actor who)
    
    If !who.Is3DLoaded() || who.IsDisabled()|| who.IsOnMount() || who.IsSwimming() || who.GetSitState() || who.GetSleepState() \
            || who.IsBleedingOut() || who.IsFlying() || who.IsInKillMove() || who.IsDead() || who.GetCurrentScene()
        Return True
    EndIf

    ; Check mutex here too, just in case it got set between earlier checks and this.
    Return _fwb_dd.InAnimatingFaction(who) || _fwb_sexwrap.InAnimatingFaction(who) || StorageUtil.GetIntValue(None, keyThreadEventMutex) > 0
    
EndFunction

Bool Function IsPlayerHome()
	Location current = player.GetCurrentLocation()
    Return current && current.HasKeyword(playerHomeKeyword)
EndFunction


Bool Function IsLocationRapeExcluded()
	Location current = player.GetCurrentLocation()
    Return current && (current.HasKeyword(playerHomeKeyword) || current.HasKeyword(jailKeyword))
EndFunction


; Check for vanilla quest states that might be ruined by running rape code, particularly the cloak, which could cause a brawl bug.
; Return True is there is a hazard.
Bool Function IsBrawlHazard()

    Quest dialogIntimidateQuest = Quest.GetQuest("DGIntimidateQuest")
    
    If dialogIntimidateQuest && dialogIntimidateQuest.IsRunning()
        Return True
    EndIf
    
    Quest companions = Quest.GetQuest("C00")
    If companions && companions.IsRunning() && 20 == companions.GetStage()
        Return True
    EndIf
    
    Quest favor17 = Quest.GetQuest("Favor17")
    If favor17 && favor17.IsRunning()
        Return True
    EndIf
    
    Quest riften19 = Quest.GetQuest("FreeformRiften19")
    If riften19 && riften19.IsRunning() && 30 == riften19.GetStage()
        Return True
    EndIf
    
    Return False

EndFunction


Bool Function IsRapeAllowed()

    Return _fwb_sexwrap.GetRapePossible() && !IsLocationRapeExcluded() && !player.IsWeaponDrawn() && !player.IsSneaking()
    
EndFunction


Function GetPlayerMorphValues()
    ; May need to push this out to another quest if it seems time consuming.
    If !_fwb_slif.GetNiMorphValues(player, morphNames, playerMorphValues)
        Int ii = playerMorphValues.Length
        While ii
            ii -= 1
            playerMorphValues[ii] = 0.0
        EndWhile
    EndIf
EndFunction

Function RecalculateMorphContributions()

    Int ii = playerMorphValues.Length
    
    While ii
        ii -= 1
        Float value = (playerMorphValues[ii] * morphPrescales[ii] + morphOffsets[ii]) + 0.5
        If value < 0.0
            value = 0.0
        ElseIf value > 1.0
            value = 1.0
        EndIf
        Float weight = morphWeights[ii]
        weightedMorphs[ii] = value * weight
        ;_fw_utils.Info("FWB - MorphContributions - " + value + " * " + weight + " => weightedMorph[" + ii + "] = " + weightedMorphs[ii])
    EndWhile
    
    Float breastMorph = 0.0
    Float breastWeightTotal
    ii = 8
    While ii
        ii -= 1
        breastMorph += weightedMorphs[ii]
        breastWeightTotal += morphWeights[ii]
    EndWhile
    
    Float bellyMorph = weightedMorphs[8] + weightedMorphs[9] + weightedMorphs[10] + weightedMorphs[11]
    Float bellyWeightTotal = morphWeights[8] + morphWeights[9] + morphWeights[10] + morphWeights[11]
    
    Float buttMorph = weightedMorphs[12] + weightedMorphs[13] + weightedMorphs[14] + weightedMorphs[15] + weightedMorphs[16]
    Float buttWeightTotal = morphWeights[12] + morphWeights[13] + morphWeights[14] + morphWeights[15] + morphWeights[16]
    
    ;_fw_utils.Info("FWB - MorphContributions - raw morph totals: breasts " + breastMorph + ", belly " + bellyMorph + ", butt " + buttMorph)
    
    morphNodeValues[0] = 1.0
    morphNodeValues[1] = 1.0
    morphNodeValues[2] = 1.0
    
    ;_fw_utils.Info("FWB - MorphContributions - pre-scaled morph totals: breasts " + breastMorph + ", belly " + bellyMorph + ", butt " + buttMorph)

    If breastWeightTotal > 0.0
        morphNodeValues[0] = (breastMorph / breastWeightTotal) * morphMasterScales[0] * 2.0
    EndIf
    
    If bellyWeightTotal > 0.0
        morphNodeValues[1] = (bellyMorph / bellyWeightTotal) * morphMasterScales[1] * 2.0
    EndIf
    
    If buttWeightTotal > 0.0
        morphNodeValues[2] = (buttMorph / buttWeightTotal) * morphMasterScales[2] * 2.0
    EndIf
    
    StorageUtil.FloatListCopy(mcm, keyPlayerMorphValues, playerMorphValues)
    StorageUtil.FloatListCopy(mcm, keyMorphNodeValues, morphNodeValues)
    
    
    If "REPLACE" == morphMode
        playerStates[0] = 0.01 * (playerStates[0] * (100.0 - morphMasterWeights[0])  +  morphNodeValues[0] * morphMasterWeights[0])
        playerStates[1] = 0.01 * (playerStates[1] * (100.0 - morphMasterWeights[1])  +  morphNodeValues[1] * morphMasterWeights[1])
        playerStates[2] = 0.01 * (playerStates[2] * (100.0 - morphMasterWeights[2])  +  morphNodeValues[2] * morphMasterWeights[2])
    Else
        Float combinedBreast = playerStates[0] * morphNodeValues[0]
        Float combinedBelly = playerStates[1] * morphNodeValues[1]
        Float combinedButt = playerStates[2] * morphNodeValues[2]
        
        playerStates[0] = 0.01 * (playerStates[0] * (100.0 - morphMasterWeights[0])  +  combinedBreast * morphMasterWeights[0])
        playerStates[1] = 0.01 * (playerStates[1] * (100.0 - morphMasterWeights[1])  +  combinedBelly  * morphMasterWeights[1])
        playerStates[2] = 0.01 * (playerStates[2] * (100.0 - morphMasterWeights[2])  +  combinedButt   * morphMasterWeights[2])
    EndIf
    
    _fw_utils.Info("FWB - MorphMode is " + morphMode)
    _fw_utils.Info("FWB - MorphContributions - morph totals: breasts " + morphNodeValues[0] + ", belly " + morphNodeValues[1] + ", butt " + morphNodeValues[2])
    _fw_utils.Info("FWB - MorphContributions - master weights: breasts " + morphMasterWeights[0] + ", belly " + morphMasterWeights[1] + ", butt " + morphMasterWeights[2])
    _fw_utils.Info("FWB - MorphContributions - final values: breasts " + playerStates[0] + ", belly " + playerStates[1] + ", butt " + playerStates[2])

EndFunction


Function ZeroArray(Float[] toZero)
    Int ii = toZero.Length
    While ii
        ii -= 1
        toZero[ii] = 0.0
    EndWhile
EndFunction


; Only call this from Enabled state, as you can see below, does nothing in empty state.
Function CheckForExternalMods()
    CheckForSlif(); Preferred pattern
    CheckForSLA() ; Weird pattern
    CheckForMME() 
    CheckForApropos2()
EndFunction

Bool Function CheckForSlif()
    slifPresent = False
    Return False
EndFunction

Bool Function CheckForSLA()
    slaPresent = False
    Return False
EndFunction

Bool Function CheckForApropos2()
    wntPresent = False
    Return False
EndFunction

Bool Function CheckForMME()
    mmePresent = False
    Return False
EndFunction

Function HandleGameLoad()
EndFunction


Function ResetSLA()
    
    _fw_utils.Info("FWB - ResetSLA")
    
    ; Set these up even if we don't have SLA; it's just safer
    slaStates = new Float[4]
    oldSlaStates = new Float[4]

    slaPageNames = new String[4]
    slaPageNames[0] = "Arousal"
    slaPageNames[1] = "Denial"
    slaPageNames[2] = "Raped"
    slaPageNames[3] = "Addiction"


    ; So this will not be considered changed on the coming update.
    sexLabRapeCount = _fwb_sla.GetVictimCount(player)

    slaStates[slRaped] = 1000.0 ; This is days since last rape, so high value means set because we don't know how long it's been.
    oldSlaStates[slRaped] = 1000.0

    StorageUtil.UnsetFloatValue(mcm, keyRapeTime)
    StorageUtil.UnsetFloatValue(mcm, keyRapeCount)
    
EndFunction


; Unfortunately - retrieving SLA arousal or time-rate is potentially very expensive, with all kind of consequent calls bound into it.
; SLA doesn't cache the current value in storage util, which was a poor decision IMHO.
; Every mod that wants to get the value triggers a recalc, and the racalcs are costly, and may do all kinds of unexpected updates.
; For this reason amongst many others, SLA needs to go away. It may be better than its predecessor, but it's still not well behaved.
; In the next release, I want to split SLA updates out into another quest, but for now, I just have to suck it up and call into SLA directly.
Function UpdateSlaValues()

    If slaPresent
    
        oldSlaStates[0] = slaStates[0]
        oldSlaStates[1] = slaStates[1]
        oldSlaStates[2] = slaStates[2]
        oldSlaStates[3] = slaStates[3]

        slaStates[slArousal] = _fwb_sla.GetArousal(player)
        
        slaStates[slDenial] = _fwb_sla.GetDaysSinceOrgasm(player)
        
        slaStates[slAddicted] = _fwb_sla.GetTimeRate(player)
        
        ; Rape value is more complex, as we have to track time. What we actually track is 'days since last known rape'
        Int newRapeCount = _fwb_sla.GetVictimCount(player)
        
        if newRapeCount != sexLabRapeCount
            sexLabRapeCount = newRapeCount
            StorageUtil.SetFloatValue(mcm, keyRapeCount, sexLabRapeCount)
            StorageUtil.SetFloatValue(mcm, keyRapeTime, Utility.GetCurrentGameTime())
            slaStates[slRaped] = 0.0
        Else
            slaStates[slRaped] = CalculateDaysSinceRape()
        EndIf
                
    EndIf
    
EndFunction


Float Function CalculateDaysSinceRape()

    Float lastRaped = StorageUtil.GetFloatValue(mcm, keyRapeTime, -1.0)
    If lastRaped < 0.0
        Return 1000.0
    EndIf
    
    Return Utility.GetCurrentGameTime() - lastRaped

EndFunction


Function UpdateAproposValues()

    if wntPresent
    
        oldWntStates[0] = wntStates[0]
        oldWntStates[1] = wntStates[1]
        oldWntStates[2] = wntStates[2]
        oldWntStates[3] = wntStates[3]
        oldWntStates[4] = wntStates[4]
        oldWntStates[5] = wntStates[5]
        
        ;_fw_utils.Info("FWB update w+t values: player " + player.GetName())
        ;_fw_utils.Info("FWB update w+t values: wntStates: " + wntStates.Length)
        ;_fw_utils.Info("FWB update w+t values: OldWntStates: " + oldWntStates.Length)
        
        Bool ok = _fwb_apropos2.GetWearAndTear(player, wntStates) ; If this fails, sets all zeros, which is fine.
        
        _fw_utils.Info("FWB update w+t values: success " + ok)
        
    EndIf
    
EndFunction
    
    
Function UpdateMmeValues()

    If mmePresent
    
        oldMmeStates[0] = mmeStates[0]
        oldMmeStates[1] = mmeStates[1]
        oldMmeStates[2] = mmeStates[2]

        mmeStates[mmMilk] = _fwb_mme.GetMilk(player)
        
        Float mmeMilkMax = _fwb_mme.GetMilkMax(player)
        If mmeMilkMax < 1.0
            mmeMilkMax = 1.0
        EndIf
        
        mmeStates[mmBreastFull] = mmeStates[mmMilk] / mmeMilkMax
        
        Float mmePain = _fwb_mme.GetPain(player)
        Float mmePainMax = _fwb_mme.GetPainMax(player)
        If mmePainMax < 1.0
            mmePainMax = 1.0
        EndIf
        
        mmeStates[mmPain] = mmePain / mmePainMax
        
    EndIf
    
EndFunction    



; FOLDSTART - XMPSE
Bool Function CheckXpmseRequirements(Actor akActor, bool isFemale)
    Return CheckXMPSE() && CheckSkeleton(akActor, isFemale) && CheckNioScripts()
EndFunction

; So we can trace and diagnose a particular missing requirement if needed.
Bool Function CheckXMPSE()
    Return XPMSELib.CheckXPMSELibVersion(XPMSELIB_VERSION)
EndFunction

Bool Function CheckSkeleton(Actor akActor, bool isFemale)
    Return XPMSELib.CheckXPMSEVersion(akActor, isFemale, XPMSE_VERSION, true)
EndFunction

Bool Function CheckNio()
    Return SKSE.GetPluginVersion("NiOverride") >= NIOVERRIDE_VERSION || SKSE.GetPluginVersion("SKEE") >= SKEE_VERSION
EndFunction

Bool Function CheckNioScripts()
    Return CheckNio() && NiOverride.GetScriptVersion() >= NIOVERRIDE_SCRIPT_VERSION
EndFunction
; FOLDEND - XMPSE


Function InitializeData()

    _fw_utils.Info("FWB - InitializeData 90 / 40")
    ; Node values, core base AVs, and carry-weight
    oldPlayerStates = new Float[5]
    playerStates = new Float[5]
    ismStrengths = new Float[4]

    currentModifiers = new Float[90]
    breastModifiers = new Float[90]
    bellyModifiers = new Float[90]
    buttModifiers = new Float[90]
    bodyModifiers = new Float[90]
    
    slaArousalModifiers = new Float[90]
    slaDenialModifiers = new Float[90]
    slaRapedModifiers = new Float[90]
    slaAddictionModifiers = new Float[90]
    
    ; We have to init values for SLA because they are ALWAYS used but aren't created unless SLA is present.
    slaArousalValues = new Float[90]
    slaDenialValues = new Float[90]
    slaRapedValues = new Float[90]
    slaAddictionValues = new Float[90]
    
    wntAbuseModifiers = new Float[90]
    wntCreatureModifiers = new Float[90]
    wntDaedricModifiers = new Float[90]
    wntVaginalModifiers = new Float[90]
    wntAnalModifiers = new Float[90]
    wntOralModifiers = new Float[90]
    
    mmeMilkModifiers = new Float[90]
    mmeBreastPercentModifiers = new Float[90]
    mmePainPercentModifiers = new Float[90]
    
    oldEffectValues = new Float[40]
    effectValues = new Float[40]
    currentEffectValues = new Int[40]
    effectTypes = new Int[40]
    effectScalesUp = new Float[40]
    effectScalesDown = new Float[40]
    avNames = new String[40]
    
    morphsEnabled = False
    morphMasterWeights = new Float[3]
    morphMasterScales = new Float[3]
    morphNodeValues = new Float[3]
    morphWeights = new Float[17]
    morphOffsets = new Float[17]
    morphPrescales = new Float[17]
    playerMorphValues = new Float[17]
    weightedMorphs = new Float[17]
    
    weaponSpeedsModified = False
    
    morphMasterScales[0] = 1.0
    morphMasterScales[1] = 1.0
    morphMasterScales[2] = 1.0
    
    Int ii = morphPrescales.Length
    While ii
        ii -= 1
        morphPrescales[ii] = 0.25
    EndWhile
    
    Float invertScale = -0.25
    
    morphPrescales[0]  = invertScale  ; Breasts INVERT -ve bigger
    morphPrescales[1]  = invertScale  ; BreastsSmall INVERT -ve bigger
    morphPrescales[6]  = invertScale  ; BreastFlatness INVERT -ve less flat
    morphPrescales[11] = invertScale  ; TummyTuck INVERT -ve bigger
    morphPrescales[12] = invertScale  ; Butt INVERT -ve bigger
    morphPrescales[13] = invertScale  ; ButtSmall INVERT -ve bigger

    
    ; 0 = direct, 1 = fixed bi-scale, 2 base scale, 3 = movement
    effectTypes[exMagicka    ] = 0
    effectTypes[exMagickaRate] = 1
    effectTypes[exHealth     ] = 0
    effectTypes[exHealthRate ] = 1
    effectTypes[exStamina    ] = 0
    effectTypes[exStaminaRate] = 1
    
    effectTypes[exMoveSpeed  ] = 3
    effectTypes[exCarryWeight] = 2
    effectTypes[exMeleeDamage] = 0 ; maybe ???
    effectTypes[exUnarmed    ] = 0 ; maybe ???
    effectTypes[exAttackSpeed] = 5
    effectTypes[exBowSpeed   ] = 5
    
    effectTypes[exVisionBlur ]   = 4
    effectTypes[exVisionTunnel]  = 4
    effectTypes[exVisionDouble]  = 4
    effectTypes[exVisionVibrant] = 4
    
    effectTypes[exStumble    ] = 3
    effectTypes[exBleedout   ] = 3
    effectTypes[exFall       ] = 3
    effectTypes[exDrop       ] = 3
    effectTypes[exMasturbate ] = 3
    effectTypes[exRape       ] = 3
    
    ; Only needed for items with type 1
    ; Allow magicka/heal/stamina to go UP to 10x normal rate
    
    Float recoveryMaxScale = 10.0 ; TO-DO - put this in the MCM
    
    effectScalesUp[exMagickaRate] = 0.03  * recoveryMaxScale  ; Magicka Rate (base 3.0)
    effectScalesDown[exMagickaRate] = 0.03   ; Magicka Rate (base 3.0)

    effectScalesUp[exHealthRate ] = 0.007 * recoveryMaxScale  ; Heal Rate (base 0.7)
    effectScalesDown[exHealthRate ] = 0.007  ; Heal Rate (base 0.7)

    effectScalesUp[exStaminaRate] = 0.05  * recoveryMaxScale  ; Stamina Rate (base 5.0)
    effectScalesDown[exStaminaRate] = 0.05   ; Stamina Rate (base 5.0)

    ; Attack and bow speed are special because we set the AV directly rather than modifying it. The SetAV code has the magic +1.0 needed to make these come out right.

    effectScalesUp[exAttackSpeed] = 0.03 ; max of 4
    effectScalesDown[exAttackSpeed] = 0.007  ; We want final value around 0.3 at -100, but we add one, so -0.7 is the target

    effectScalesUp[exBowSpeed   ] = -0.001 ; 0.1 is "best" ... slowest bow - only slows time to 90% of normal, because the perk breaks otherwise.
    effectScalesDown[exBowSpeed   ] = -0.005   ; Gives a final value of 1.5 at full debuff (-100)
    ; Bow speed determines the time scaling effect if you press the zoom in key while using a bow.
    ; The larger the number the faster time runs, which is WORSE for the player. The idea is the perk slows down time, by applying a scalar to time < 1.0
    ; This has to be a very modest buff, otherwise the second grade perk breaks. Could detect the perk, but I'm reluctant to add more overhead to the calculation.
    
    
    ; These are used for GetAV - so most aren't used at all
    avNames[0] = "Magicka"
    avNames[1] = "MagickaRate"
    avNames[2] = "Health"
    avNames[3] = "HealRate"
    avNames[4] = "Stamina"
    avNames[5] = "StaminaRate"
    avNames[6] = "SpeedMult"
    avNames[7] = "CarryWeight"
    avNames[8] = "MeleeDamage"
    avNames[9] = "UnarmedDamage"
    avNames[10] = "WeaponSpeedMult" ; As we now handle this through direct AV set, also need to update LeftWeaponSpeedMult. This is done through hacky magic :(
    avNames[11] = "BowSpeedBonus"

    avNames[12] = "Marksman"
    avNames[13] = "OneHanded"
    avNames[14] = "TwoHanded"
    avNames[15] = "Block"
    avNames[16] = "HeavyArmor"
    avNames[17] = "LightArmor"
    avNames[18] = "Sneak"
    avNames[19] = "Lockpicking"
    avNames[20] = "Pickpocket"
    avNames[21] = "Speech"
    avNames[22] = "Alteration"
    avNames[23] = "Conjuration"
    avNames[24] = "Destruction"
    avNames[25] = "Illusion"
    avNames[26] = "Restoration"
    avNames[27] = "Enchanting"
    avNames[28] = "Alchemy"
    avNames[29] = "Smithing"
    ; Not AVs
    avNames[30] = "+Vision Blur"
    avNames[31] = "+Vision Tunnel"
    avNames[32] = "+Vision Double"
    avNames[33] = "+Vision Vibrant"
    avNames[34] = "+Stagger"
    avNames[35] = "+Bleedout"
    avNames[36] = "+Fall"
    avNames[37] = "+DropWeapon"
    avNames[38] = "+Masturbate"
    avNames[39] = "+Rape"
    
    ; Limits
    clampLower = StorageUtil.FloatListToArray(mcm, keyLimitValuesLo)
    clampUpper = StorageUtil.FloatListToArray(mcm, keyLimitValuesHi)
    
    _fw_utils.Info("FWB - InitializeData complete")

EndFunction


Function BuildSpellLists()

    _fw_utils.Info("FWB BuildSpellLists")
    spellsDown = new Spell[38]
    spellsUp = new Spell[38]
    
    Int ii = spellDebuffs.Length
    While ii
        ii -= 1
        _fw_utils.Info("FWB Building spell list - copy debuff " + ii + " " + spellDebuffs[ii].GetName())
        spellsDown[ii] = spellDebuffs[ii]
        spellsUp[ii] = spellBuffs[ii]
    EndWhile
    
    ii = spellBuffs_U13.Length
    While ii
        ii -= 1
        spellsDown[exStumble + ii] = spellDebuffs_U13[ii]
        spellsUp[exStumble + ii] = spellDebuffs_U13[ii]
    EndWhile

EndFunction


Function InitializeQuasiConstants()

    _fw_utils.Info("FWB InitializeQuasiConstants")

    defaultUpdateInterval = 11.0

    ixBreast = 0
    ixBelly = 1
    ixButt = 2
    ixBody = 3

    ixCarryWeight =  4
    
        ; SLA
    slArousal = 0
    slDenial = 1
    slRaped = 2
    slAddicted = 3

    ; APROPOS
    wtAbuse = 0 ; I put this first, as it's the global measure, and you can set up W&T effects with only this one and catch everything in a lazy way.
    wtCreature = 1
    wtDaedric = 2
    wtVaginal = 3
    wtAnal = 4
    wtOral = 5

    ; MME
    mmMilk = 0
    mmBreastFull = 1
    mmPain = 2

    ; Master values
    masterDebuff = 100.0
    masterBuff = 100.0

    ; Indices
    ixDebuffMax   =  0
    ixDebuffFrom  =  2
    ixDebuffTo    =  4
    ixBuffMax     =  1
    ixBuffFrom    =  3
    ixBuffTo      =  5
    
    ixStart       = 10
    ixEnd         = 89
    
    ; Effect indices
    effectCount    = 40

    exMagicka       = 0
    exMagickaRate   = 1
    exHealth        = 2
    exHealthRate    = 3
    exStamina       = 4
    exStaminaRate   = 5
    exMoveSpeed     = 6
    exCarryWeight   = 7
    exMeleeDamage   = 8
    exUnarmed       = 9
    exAttackSpeed   = 10
    exBowSpeed      = 11
                   
    exArchery       = 12
    exOneHanded     = 13
    exTwoHanded     = 14
    exBlock         = 15
    exHeavyArmor    = 16
    exLightArmor    = 17
    exSneak         = 18
    exLockpicking   = 19
    exPickpocket    = 20
    exSpeech        = 21
    exAlteration    = 22
    exConjuration   = 23
    exDestruction   = 24
    exIllusion      = 25
    exRestoration   = 26
    exEnchanting    = 27
    exAlchemy       = 28
    exSmithing      = 29
    exVisionBlur    = 30
    exVisionTunnel  = 31
    exVisionDouble  = 32
    exVisionVibrant = 33
    exStumble       = 34
    exBleedout      = 35
    exFall          = 36
    exDrop          = 37
    exMasturbate    = 38
    exRape          = 39
    
    BuildSpellLists()

EndFunction


Bool Function ResetInternal()

    _fw_utils.Info("FWB - ResetInternal")
    InitializeQuasiConstants()

    mmePresent = False

    player = Game.GetPlayer()
    
    ReadConfigurationValues(True)
    configurationChanged = True
    
    ; Note that this check doesn't demand that the PC is female.
    ; Though the PC *should* be female, this is just checking the library and skeleton version.
    If !CheckXpmseRequirements(player, _fw_utils.IsFemale(player))
    
        _fw_utils.Info("FWB ResetInternal failed due to XMPSE requirements not met")
        Debug.MessageBox("Stale/missing/wrong XMPSE/NiOverride skeleton or libraries. Cannot start.")
        StopMod()
        Return False
        
    EndIf
    
    InitializeData()
    
    ResetSpells()
    
    ; If these aren't False, then soft-dep arrays might not get created.
    slifPresent = False
    slaPresent = False
    wntPresent = False
    mmePresent = False
    
    ; Even if we don't have SLA; it's just safer
    ; - SLA is an oddity in how it's processed, don't replicate this pattern, replicate the CheckForMME pattern.
    ResetSLA()
    ; No point checking for mod presence here, as we won't be enabled.
    
    RegisterEventListeners()
    
    Return True
    
EndFunction


Function RegisterEventListeners()

    _fw_utils.Info("FWB modifiers - Register Event Listeners")
    RegisterForModEvent("_fwb_SexSceneEnd", "OnSexSceneEnd")

    RegisterForModEvent("dhlp-Suspend", "OnDeviousSuspend")
    RegisterForModEvent("dhlp-Resume", "OnDeviousResume")

EndFunction


Bool Function InDebug()
    Return False
EndFunction

 

 

 

_fwb_dd.psc

 

Spoiler

Scriptname _fwb_dd Hidden
{Interfaces with Zad Devious Devices functions in a safe way.}

; This returns a Float for DD reasons. Version 4.2 is version 10.
Float Function GetDeviousVersion() Global

    String ddFile = "Devious Devices - Integration.esm"
    Int questId = 0x0000F624
    
    Int ddIndex = Game.GetModByName(ddFile)
    If 255 != ddIndex
        
        zadLibs ddLibs = Game.GetFormFromFile(questId, ddFile) As zadLibs
        
        If ddLibs
            Return ddLibs.GetVersion()
        EndIf
    EndIf
    
    Return -1.0
    
EndFunction


; This returns a string for display, but it may not be updated reliably?
String Function GetDeviousVersionString() Global

    String ddFile = "Devious Devices - Integration.esm"
    Int questId = 0x0000F624
    
    Int ddIndex = Game.GetModByName(ddFile)
    If 255 != ddIndex
        
        zadLibs ddLibs = Game.GetFormFromFile(questId, ddFile) As zadLibs
        
        If ddLibs
            Return ddLibs.GetVersionString()
        EndIf
    EndIf
    
    Return "NOT FOUND"
    
EndFunction

; Makes the player fall over in a device animation respectful way.
; Returns False if Trip could not be called.
Bool Function Trip(Actor who) Global

    String ddFile = "Devious Devices - Integration.esm"
    Int questId = 0x0000F624
    
    Int ddIndex = Game.GetModByName(ddFile)
    If 255 != ddIndex
        
        zadLibs ddLibs = Game.GetFormFromFile(questId, ddFile) As zadLibs
        
        If ddLibs
            If ddLibs.GetVersion() >= 7.0
                ddLibs.Trip(who)
                Return True
            EndIf
        EndIf
    EndIf
    
    Return False
    
EndFunction

; True if the actor is wearing heavy bondage items - checks the worn keyword
Bool Function HasHeavyBondage(Actor who) Global

    String ddFile = "Devious Devices - Integration.esm"
    Int keywordId = 0x0005226C
    
    Int ddIndex = Game.GetModByName(ddFile)
    If 255 != ddIndex

        Keyword heavyBondage = Game.GetFormFromFile(keywordId, ddFile) as Keyword
        _fw_utils.Info("FWB_DD - HasHeavyBondage - actor is '" + who.GetActorBase().GetName() + "' and keyword is: " + heavyBondage)
        If heavyBondage
            Return who.WornHasKeyword(heavyBondage)
        EndIf
        
    EndIf
    
    Return False
    
EndFunction


; True if the actor is wearing a blindfold.
Bool Function HasBlindfold(Actor who) Global

    String asFile = "Devious Devices - Assets.esm"
    Int deviousBlindfoldId = 0x00011B1A
    
    Int asIndex = Game.GetModByName(asFile)
    If 255 != asIndex
        Keyword blindfold = Game.GetFormFromFile(deviousBlindfoldId, asFile) as Keyword
        If blindfold
            Return who.WornHasKeyword(blindfold)
        EndIf
    EndIf
    
    Return False
    
EndFunction


; True if the actor is wearing any kind of gag.
Bool Function HasGag(Actor who) Global

    String asFile = "Devious Devices - Assets.esm"
    Int deviousGagId = 0x00007EB8
    
    Int asIndex = Game.GetModByName(asFile)
    If 255 != asIndex

        Keyword gag = Game.GetFormFromFile(deviousGagId, asFile) as Keyword
        If gag
            Return who.WornHasKeyword(gag)
        EndIf
        
    EndIf
    
    Return False
    
EndFunction


; True if the actor is wearing a non-open gag (blocks sex/food).
Bool Function HasSolidGag(Actor who) Global

    String asFile = "Devious Devices - Assets.esm"
    Int deviousGagId = 0x00007EB8
    Int permitOralId = 0x0000FAC9
    
    Int asIndex = Game.GetModByName(asFile)
    If 255 != asIndex

        Keyword gag = Game.GetFormFromFile(deviousGagId, asFile) as Keyword
        If gag
            Keyword permitOral = Game.GetFormFromFile(permitOralId, asFile) as Keyword
            If permitOral
                Return who.WornHasKeyword(gag) && !who.WornHasKeyword(permitOral)
            EndIf
        EndIf
        
    EndIf
    
    Return False
    
EndFunction


; True if the actor is wearing an open gag (doesn't block sex/food).
Bool Function HasOpenGag(Actor who) Global

    String asFile = "Devious Devices - Assets.esm"
    Int deviousGagId = 0x00007EB8
    Int permitOralId = 0x0000FAC9
    
    Int asIndex = Game.GetModByName(asFile)
    If 255 != asIndex

        Keyword gag = Game.GetFormFromFile(deviousGagId, asFile) as Keyword
        If gag
            Keyword permitOral = Game.GetFormFromFile(permitOralId, asFile) as Keyword
            If permitOral
                Return who.WornHasKeyword(gag) && who.WornHasKeyword(permitOral)
            EndIf
        EndIf
        
    EndIf
    
    Return False
    
EndFunction


; True if the actor is wearing a belt of some kind.
Bool Function HasBelt(Actor who) Global

    String asFile = "Devious Devices - Assets.esm"
    Int deviousBeltId = 0x00003330
    
    Int asIndex = Game.GetModByName(asFile)
    If 255 != asIndex

        Keyword belt = Game.GetFormFromFile(deviousBeltId, asFile) as Keyword
        If belt
            Return who.WornHasKeyword(belt)
        EndIf
        
    EndIf
    
    Return False
    
EndFunction


; True if the actor is wearing a closed belt (blocks all sex).
Bool Function HasClosedBelt(Actor who) Global

    String asFile = "Devious Devices - Assets.esm"
    Int deviousBeltId = 0x00003330
    Int permitAnalId = 0x0000FACA
    
    Int asIndex = Game.GetModByName(asFile)
    If 255 != asIndex

        Keyword belt = Game.GetFormFromFile(deviousBeltId, asFile) as Keyword
        If belt
            Keyword permitAnal = Game.GetFormFromFile(permitAnalId, asFile) as Keyword
            If permitAnal
                Return who.WornHasKeyword(belt) && !who.WornHasKeyword(permitAnal)
            EndIf
        EndIf
        
    EndIf
    
    Return False
    
EndFunction

; True if the actor is wearing an open belt (doesn't block anal).
Bool Function HasOpenBelt(Actor who) Global

    String asFile = "Devious Devices - Assets.esm"
    Int deviousBeltId = 0x00003330
    Int permitAnalId = 0x0000FACA
    
    Int asIndex = Game.GetModByName(asFile)
    If 255 != asIndex

        Keyword belt = Game.GetFormFromFile(deviousBeltId, asFile) as Keyword
        If belt
            Keyword permitAnal = Game.GetFormFromFile(permitAnalId, asFile) as Keyword
            If permitAnal
                Return who.WornHasKeyword(belt) && who.WornHasKeyword(permitAnal)
            EndIf
        EndIf
        
    EndIf
    
    Return False
    
EndFunction


; True if the actor has vaginal access (and by implication, whether it is blocked - by any means).
Bool Function HasVaginalAccess(Actor who) Global

    String asFile = "Devious Devices - Assets.esm"
    String ddFile ="Devious Devices - Integration.esm"
    Int deviousBeltId           = 0x00003330
    Int vaginalPlugId           = 0x0001DD7C
    Int inflatablePlugVaginalId = 0x0005D9CA
    Int hobbleSkirtId           = 0x0005F4BA
    Int lockableId              = 0x00003894
    Int inflationStateId        = 0x0005D9C8
    
    Int asIndex = Game.GetModByName(asFile)
    Int ddIndex = Game.GetModByName(ddFile)
    If 255 != asIndex && 255 != ddIndex
        Keyword belt = Game.GetFormFromFile(deviousBeltId, asFile) as Keyword
        Keyword plug = Game.GetFormFromFile(vaginalPlugId, asFile) as Keyword
        Keyword inflatable = Game.GetFormFromFile(inflatablePlugVaginalId, ddFile) as Keyword
        Keyword skirt = Game.GetFormFromFile(hobbleSkirtId, ddFile) as Keyword
        Keyword lockable = Game.GetFormFromFile(lockableId, asFile) as Keyword
        
        _fw_utils.Info("_fwb_dd.HasVaginalAccess: keywords: " + belt + " " + plug + " " + inflatable + " " + skirt + " " + lockable)
        
        If belt && plug && inflatable && skirt && lockable
            
            If who.WornHasKeyword(belt) \
                    || who.WornHasKeyword(skirt) \
                    || (who.WornHasKeyword(plug) && who.WornHasKeyword(lockable))
                Return False
            EndIf
            
            If who.WornHasKeyword(inflatable)
                If Game.GetPlayer() == who
                    GlobalVariable inflation = Game.GetFormFromFile(inflationStateId, ddFile) as GlobalVariable
                    If inflation
                        Return inflation.GetValueInt() <= 0
                    EndIf
                Else
                    ; This assumes that the inflatable plug is inflated for all NPCs.
                    Return False
                EndIf
            EndIf
            
        EndIf
    EndIf
    
    Return True
    
EndFunction


; True if the actor has anal access (and by implication, whether it is blocked - by any means).
Bool Function HasAnalAccess(Actor who) Global

    String asFile = "Devious Devices - Assets.esm"
    String ddFile = "Devious Devices - Integration.esm"
    Int deviousBeltId           = 0x00003330
    Int analPlugId              = 0x0001DD7D
    Int inflatablePlugAnalId    = 0x0005D9C9
    Int hobbleSkirtId           = 0x0005F4BA
    Int lockableId              = 0x00003894
    Int inflationStateId        = 0x0005D9C8
    
    Int asIndex = Game.GetModByName(asFile)
    Int ddIndex = Game.GetModByName(ddFile)
    If 255 != asIndex && 255 != ddIndex
        Keyword belt = Game.GetFormFromFile(deviousBeltId, asFile) as Keyword
        Keyword plug = Game.GetFormFromFile(analPlugId, asFile) as Keyword
        Keyword inflatable = Game.GetFormFromFile(inflatablePlugAnalId, ddFile) as Keyword
        Keyword skirt = Game.GetFormFromFile(hobbleSkirtId, ddFile) as Keyword
        Keyword lockable = Game.GetFormFromFile(lockableId, asFile) as Keyword
        
        _fw_utils.Info("_fwb_dd.HasAnalAccess: keywords: " + belt + " " + plug + " " + inflatable + " " + skirt + " " + lockable)
        
        If belt && plug && inflatable && skirt && lockable
            
            If who.WornHasKeyword(belt) \
                    || who.WornHasKeyword(skirt) \
                    || (who.WornHasKeyword(plug) && who.WornHasKeyword(lockable))
                Return False
            EndIf
            
            If who.WornHasKeyword(inflatable)
                If Game.GetPlayer() == who
                    GlobalVariable inflation = Game.GetFormFromFile(inflationStateId, ddFile) as GlobalVariable
                    If inflation
                        Return inflation.GetValueInt() <= 0
                    EndIf
                Else
                    ; This assumes that the inflatable plug is inflated for all NPCs.
                    Return False
                EndIf
            EndIf
            
        EndIf
    EndIf
    
    Return True
    
EndFunction


Bool Function InAnimatingFaction(Actor who) Global

    String ddFile ="Devious Devices - Integration.esm"
    Int factionId = 0x00029567
    
    Int ddIndex = Game.GetModByName(ddFile)
    If 255 != ddIndex
        Faction animatingFaction = Game.GetFormFromFile(factionId, ddFile) As Faction
        If animatingFaction
            Return who.IsInFaction(animatingFaction)
        EndIf
    EndIf
    
    Return False
    
EndFunction


Function SetInAnimatingFaction(Actor who, Bool inFaction) Global

    String ddFile ="Devious Devices - Integration.esm"
    Int factionId = 0x00029567
    
    Int ddIndex = Game.GetModByName(ddFile)
    If 255 != ddIndex
        Faction animatingFaction = Game.GetFormFromFile(factionId, ddFile) As Faction
        If animatingFaction
            If inFaction
                who.AddToFaction(animatingFaction)
                who.SetFactionRank(animatingFaction, 1)
            Else
                who.RemoveFromFaction(animatingFaction)
            EndIf
        EndIf
    EndIf
    
EndFunction


 

 

 

Link to comment
1 hour ago, tasairis said:

You mean the italics? No problem, just means the mod is injecting forms into (in this case) Skyrim.esm.

Thanks for keeping us up to date. I really appreciate it.

 

Has anyone tried the Umbra CC? It's the only content that has looked interesting to me so far.

Link to comment
11 hours ago, Gh0sTG0 said:

Hm... What to do if mod SLA Monitor Widget is marked as "convertable", but it has skyui in masterfiles? Should I install skyui, skyuiSE, both of them? What to do with editing masterfiles?

Most mods that require SkyUI only require it for the MCM menu, which will work perfectly fine with SkyUI SE.

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
×
×
  • 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