For Mod Authors¶
How to build on SLO VE without touching its scripts — and how to ship a voice pack or an SFX set that plugs into it.
Shipping a voice pack¶
A SLO VE-compatible pack is just a folder of loose WAV files. No ESP, no scripts, no sound-descriptor records, no voice aliases.
Guidelines:
- Loose PCM
.wav. BSA-packed audio can't be found by a folder scan, and lipsync needs a loose PCM wav to read the amplitude envelope from. - Name folders after the categories in the Category Reference. Matching is case- and space-insensitive, so
About To CumandAboutToCumare equivalent. - Partial packs are fine and expected. Anything you don't cover backfills through the slot's
fallbackchain to SexLab's stock moans, per category. - Author for
F1(the player's slot) as the Hentairim/IVDT convention does. Users who want your pack on a follower rename the folder toF2/F3. - Several files per category — the shuffle bag deals every file before repeating, so 5–10 clips per category sound far better than one.
If you want your pack to install its own slot rather than relying on the user renaming a folder, ship an additive overlay:
# Data\SKSE\Plugins\AudioUtil\config\MyPack.toml
[[slot]]
id = "MyPack"
sex = "female"
path = 'Sound\fx\MyPack\voice'
fallback = "F0B"
gag_slot = "F1gag"
Use a stable, unique filename prefix so your overlay sorts predictably and never collides with another mod's. Don't redefine SLO VE's slots — [[slot]] is keyed by id and a duplicate id replaces the whole slot. See Configuration Overview.
Mod events¶
The Director re-broadcasts framework-independent events. These are player-scene only and fire for scenes SLO VE has adopted.
| Event | argString |
argNum |
|---|---|---|
SLOVE_SceneStart |
SexLab thread id | — |
SLOVE_StageStart |
SexLab thread id | — |
SLOVE_Orgasm |
SexLab thread id | the orgasming actor's FormID, as a float |
SLOVE_SceneEnd |
SexLab thread id | — |
RegisterForModEvent("SLOVE_Orgasm", "OnSloveOrgasm")
Event OnSloveOrgasm(String eventName, String argString, Float argNum, Form sender)
Int threadId = argString as Int
Actor who = Game.GetFormEx(argNum as Int) as Actor
; …
EndEvent
These exist so consumers never have to touch raw SexLab events — the same events will be emitted by a future OStim backend.
StorageUtil state¶
Per-actor state, readable with PapyrusUtil:
| Key | Type | Meaning |
|---|---|---|
SLOVE_Resistance |
int | Current willpower 0–100 (default 100) |
SLOVE_BrokenPoints |
int | Game-hours-to-recover remaining; > 0 means broken |
SLOVE_ResDebt |
float | Pending forced-insertion trauma, drained on later ticks |
SLOVE_LastSexTime |
float | Game time of the actor's last scene |
SLOVE_FaceOwnsMouth_Expr |
int | 1 while SLO VE's climax/ahegao face owns this actor's mouth |
SLOVE_FaceOwnsMouth_SLS |
int | 1 while SexLab Survival's ahegao owns the player's mouth |
Int willpower = StorageUtil.GetIntValue(akActor, "SLOVE_Resistance", 100)
Bool broken = StorageUtil.GetIntValue(akActor, "SLOVE_BrokenPoints", 0) > 0
Prefer the Director's getters
GetResistance(actor) and IsBroken(actor) on the Director apply the resistance.enable gate for you; the raw keys don't.
Owning an actor's face¶
If your mod drives an actor's mouth (an expression set, an ahegao, a device animation), set SLOVE_FaceOwnsMouth_Expr to 1 on that actor while you own it. SLO VE reads the union of the two markers per line and plays that actor's voice with lipsync blocked, so its lipsync never fights your face. Clear it when you're done.
There is no standing per-actor lipsync block in AudioUtil — blocking is decided per line, which is why the marker has to stay accurate.
Reading SLO VE's config¶
Settings are readable through AudioUtil's generic TomlUtil API — no dependency on SLO VE's scripts:
Or via SLO VE's thin wrapper, which knows the path:
Int v = SLOVE_Config.GetInt("voice.pcvolume", 100)
Bool ok = SLOVE_Config.Available() ; false when the AudioUtil DLL is absent
SLOVE_Config is fail-open: with no DLL, every getter returns the caller's default.
Playing audio yourself¶
You don't need SLO VE for that — call AudioUtil directly:
Int h = AudioUtil.PlayVoice(akActor, "Orgasm") ; resolves the actor's slot
Int h = AudioUtil.PlayVoiceFromSlot("F1", "Orgasm", akActor) ; explicit slot
Int h = AudioUtil.PlaySFX("MediumClap", akActor)
String slot = AudioUtil.GetSlotForActor(akActor)
Bool has = AudioUtil.CategoryExists("F1", "Orgasm")
Pass blockLipSync = true on PlayVoice for a line that must not move the mouth.
The framework firewall¶
If you're contributing to SLO VE itself, one invariant governs the codebase: SLOVE_Director is the only script allowed to reference SexLabFramework / SexLabThread / SexlabRegistry or SLPP mod-event names. SLOVE_Voice, SLOVE_Expressions, SLOVE_SFX and SLOVE_Resistance talk only to the Director's API and the SLOVE_* events. That seam is what makes an OStim backend possible as an alternative Director with the same API surface.
Building from source¶
Mirrors papyrus\Source into dist\Scripts\Source, compiles with Pyro, and writes Release\SLO VE.zip. Pyro is auto-located from the VSCode papyrus-lang extension (override with PYRO_EXE); the game root defaults to the usual Steam path (override with SKYRIM_GAME_PATH).
Compilation needs AudioUtil's Papyrus sources on the import path — it is a hard build-time dependency, not just a runtime one. SLOVE.esp is authored externally and is not built by Pyro.