Native API (SloangNative)¶
SloangNative is the canonical, no-boilerplate way to read and write arousal from any Papyrus script. It is a hidden script of global functions, so you call it directly — no quest casts, no ModEvent.Create/Push/Send sequences:
What depending on this actually costs
Global calls like SloangNative.Foo() are not an ESM master dependency and will not stop your plugin from loading if SLA is missing — they resolve by script name at runtime, and an absent script just logs an error and returns a default (0 / None / no-op). The only real coupling is at compile time: SloangNative.pex must be on your import path. SexLabAroused.esm is a very common requirement, so that's fine for most mods. If you want zero compile-time coupling too, use the slaSetArousalEffect / slaModArousalEffect ModEvents instead — same underlying system, purely an ergonomics trade-off.
There is no presence guard
Don't try to wrap calls in an "is SLA installed?" check — any such check would itself be a global call on this script, logging the same missing-script error when SLA is absent. If you require SexLabAroused.esm, just call the API directly. GetVersion() is for runtime version gating only (it returns 0 when the esm is present but disabled).
SloangNative ≠ OSLArousedNative
SloangNative is the canonical SLA NG API. OSLArousedNative is a separate compatibility stub that redirects calls written against the older OSL Aroused mod. They are different scripts — call SloangNative for new integrations.
Reading arousal¶
| Function | Returns |
|---|---|
float GetArousal(Actor who) |
Current arousal (unclamped float) |
int GetArousalInt(Actor who) |
Current arousal clamped to 0–100 |
float GetExposure(Actor who) |
Legacy exposure component |
Writing arousal¶
| Function | Effect |
|---|---|
float ModArousal(Actor who, float modifier) |
Adds modifier to the actor's exposure; returns the new value |
float SetArousal(Actor who, float value) |
Sets arousal toward value (0.0 resets toward the floor) |
Dynamic effects (no ModEvent boilerplate)¶
Named, time-aware contributions to arousal — the direct-call equivalents of the dynamic-effect ModEvents. Prefer the convenience wrappers below for almost everything; only drop to the low-level primitives when no wrapper fits.
Convenience wrappers (recommended)¶
One line per common shape — you never touch raw functionId/param/limit. All time arguments are in in-game hours. Each Add… call creates or refreshes the named effect (it does not stack — use ModDynamicEffect to accumulate). Amounts may be negative (a negative effect lowers arousal).
SloangNative.AddDecayingEffect(akActor, "MyMod_Tease", 50.0, 2.0) ; +50, halves every 2h
SloangNative.AddLinearEffect (akActor, "MyMod_Denial", 1.0, 2.0, 80.0) ; start 1, +2/h, cap 80
SloangNative.AddFlatEffect (akActor, "MyMod_Cursed", 15.0) ; steady +15 until cleared
SloangNative.AddDelayedEffect(akActor, "MyMod_SlowBurn",30.0, 6.0) ; nothing for 6h, then +30
SloangNative.ClearDynamicEffect(akActor, "MyMod_Tease") ; remove entirely
| Function | Effect |
|---|---|
AddFlatEffect(Actor who, string effectId, float amount) |
Constant, non-decaying named contribution |
AddDecayingEffect(Actor who, string effectId, float amount, float halveEveryHours) |
One-shot bump that halves every halveEveryHours toward 0 |
AddLinearEffect(Actor who, string effectId, float startAmount, float ratePerHour, float cap) |
Ramps by ratePerHour (negative to ramp down) until cap |
AddDelayedEffect(Actor who, string effectId, float amount, float delayHours) |
Contributes 0 for delayHours, then jumps to amount |
ClearDynamicEffect(Actor who, string effectId) |
Removes the effect entirely (safe if absent) |
bool HasDynamicEffect(Actor who, string effectId) |
True if the effect has a non-zero value |
Low-level primitives¶
Reach for these only when a wrapper doesn't fit — you manage the raw functionId/param/limit yourself (use the FuncX() accessors, not bare ints).
SloangNative.SetDynamicEffect(akActor, "MyMod_Tease", 50.0, SloangNative.FuncDecay(), 2.0 / 24.0, 0.0)
SloangNative.ModDynamicEffect(akActor, "MyMod_Tease", -20.0, 0.0)
float v = SloangNative.GetDynamicEffectValue(akActor, "MyMod_Tease")
| Function | Effect |
|---|---|
SetDynamicEffect(Actor who, string effectId, float initialValue, int functionId, float param, float limit) |
Create/replace a dynamic effect. initialValue is absolute (sets the value), and initialValue == 0 is ignored — use ClearDynamicEffect to remove |
ModDynamicEffect(Actor who, string effectId, float modifier, float limit) |
Add modifier to an effect, clamped at limit |
float GetDynamicEffectValue(Actor who, string effectId) |
Read a single dynamic effect's value |
What happens when a cap is reached¶
The cap/limit argument is always per-effect — it bounds that one named contribution, never the actor's total arousal. There is no global ceiling: capped effects still sum freely, so a total can exceed 100. (GetArousalInt clamps to 0–100, but that's a display convenience — the underlying float and every other effect are unaffected.)
What "reaching the cap" does depends on the effect kind:
- Ramping effects (
AddLinearEffect,AddDecayingEffect, or theFuncLinear()/FuncDecay()primitives) — when the value crosses the cap it is pinned to the cap and the timer is switched off. The effect stops advancing and stays parked at the cap value as a steady (function-None) contribution; it won't drift past it. A decaying effect whose cap is0is removed entirely once it lands there (same asClearDynamicEffect). ModDynamicEffect/ModArousal— the cap is a directional clamp that only prevents overshoot on this call. A positivemodifiertreats the cap as an upper bound, a negative one as a lower bound, and only the partial step up to the cap is applied. It does not pull a value that is already past the cap back toward it — if you're beyond the cap in the direction you're pushing, the clamp doesn't fire.- Sine (
FuncSine()) and delayed step (FuncDelayedStep()) never "finish" on the cap: for sine,limitis the oscillation amplitude (it keeps updating forever); for delayed step,limitis the target value it jumps to afterparam, not a ceiling.
Want a hard overall ceiling?
Cap each effect you contribute, or read GetArousal and decide in your own plugin — SLA will not clamp the sum for you.
Timed-function ID accessors¶
Prefer these over bare integers — they document intent and survive any future renumbering. See Timed function IDs for behaviour.
| Accessor | ID | Behaviour |
|---|---|---|
FuncNone() |
0 | Static value |
FuncDecay() |
1 | Halves every param game days |
FuncLinear() |
2 | Changes by param per game day |
FuncSine() |
3 | Oscillates |
FuncDelayedStep() |
4 | Jumps to limit after param days |
Reacting to updates (don't poll)¶
SLA recomputes arousal on its own scan cycle and fires the sla_UpdateComplete ModEvent once at the end of each cycle. React to that instead of polling GetArousal on a timer. There is no global-function wrapper — RegisterForModEvent must run on your script instance, so register and handle it directly (no dependency on SloangNative to receive it).
The numeric arg is the count of actors updated that cycle, not a specific actor — read whichever actor you care about (usually the player) inside the handler.
This event is infrequent — not real-time
The scan cycle defaults to 120 seconds (MCM-configurable), so sla_UpdateComplete fires only every couple of minutes. If you need the current value between cycles, just call GetArousal any time — it's a direct SKSE native call that re-sums the actor's effects on each call (not a cached value), so it always reflects the latest effect values. The event just marks the end of a scan cycle (timed effects advanced, plugins ran); it isn't what makes GetArousal current.
; register once, e.g. in OnInit / OnPlayerLoadGame
RegisterForModEvent("sla_UpdateComplete", "OnSlaUpdateComplete")
Event OnSlaUpdateComplete(string eventName, string strArg, float actorCount, Form sender)
int arousal = SloangNative.GetArousalInt(Game.GetPlayer())
; ... map arousal -> your effect ...
EndEvent
Per-actor flags & preferences¶
| Function | Notes |
|---|---|
bool IsActorNaked(Actor who) |
Uses the configured naked-detection rules |
bool IsActorExhibitionist(Actor who) / SetActorExhibitionist(Actor who, bool) |
|
bool IsArousalLocked(Actor who) / SetArousalLocked(Actor who, bool) |
Locked actors keep a fixed value |
bool IsArousalBlocked(Actor who) / SetArousalBlocked(Actor who, bool) |
Blocked actors are skipped by updates |
int GetGenderPreference(Actor who) / SetGenderPreference(Actor who, int) |
0 Male, 1 Female, 2 Both, 3 SexLab |
Orgasm tracking¶
| Function | Notes |
|---|---|
float GetDaysSinceLastOrgasm(Actor who) |
Game days since last recorded orgasm |
UpdateOrgasmDate(Actor who) |
Stamp an orgasm now (applies the default plugin's post-orgasm satisfaction handling, enjoyment 0) |
Orgasm(Actor who, float enjoyment) |
Same as UpdateOrgasmDate but enjoyment weights the climax (0.0 plain; higher deepens the post-orgasm dip) |
To register an orgasm from a script that must not compile-couple to SLA (e.g. cross-fork code, or you'd rather not import SloangNative), fire the slaOrgasm ModEvent instead — same effect as UpdateOrgasmDate, plus an enjoyment weight (0.0 = a plain climax; higher values deepen the post-orgasm satisfaction dip):
int h = ModEvent.Create("slaOrgasm")
ModEvent.PushForm(h, akActor)
ModEvent.PushFloat(h, 0.0) ; enjoyment
ModEvent.Send(h)
Registered by SLA NG 3.2.1+ only (older builds and other forks ignore it — use UpdateOrgasmDate / slaFrameworkScr.UpdateActorOrgasmDate there).
Meta¶
| Function | Notes |
|---|---|
int GetVersion() |
Packed integer version (MMmmppp) |