Outward
Install with App

Details

Latest version
1.1.0
Last Updated
First Uploaded
Downloads
5
Likes
0
Size
60KB
Dependants

Categories

Overview

A standalone SideLoader library for mod authors. It adds SL_UseSkill, SL_LearnSkillHidden, SL_Banish, SL_PlayVFXOnWeapon, SL_PlayVFXOnChar, SL_PlayVFXPulsed, SL_PlayTimedSound and SL_ReliableVisuals you can use from XML.

SL_UseSkill

Makes a character cast a skill they already know.

<SL_Effect xsi:type="SL_UseSkill">
  <Delay>0</Delay>
  <SyncType>OwnerSync</SyncType>
  <OverrideCategory>None</OverrideCategory>
  <SkillID>8100102</SkillID>
  <Target>Owner</Target>
  <MaxWait>3</MaxWait>
</SL_Effect>
Field Meaning
SkillID Item ID of the skill. The target must already know it
Target Owner (the caster) or AffectedCharacter
MaxWait Seconds to wait for the character to be free before giving up

It goes through Item.TryQuickSlotUse() — the same path as pressing a quickslot key — so the game handles requirements, cooldown, costs, animation and networking.

SL_LearnSkillHidden

Teaches a skill permanently without it appearing in the skill menu. For skills your mod triggers itself rather than ones the player casts.

<SL_Effect xsi:type="SL_LearnSkillHidden">
  <Delay>0</Delay>
  <SyncType>OwnerSync</SyncType>
  <OverrideCategory>None</OverrideCategory>
  <SkillID>-88002</SkillID>
</SL_Effect>

The skill is genuinely learned, castable and networked — it simply never shows in the menu. It survives saves and reloads.

Why you need this.

SL_UseSkill cannot cast a skill the character has not learned — Item.TryQuickSlotUse refuses one outright. Without a way to teach invisibly, a mod that fires its own skill must either put it in the player's menu or not work at all.

The learning is vanilla's TryUnlockSkill. Only the display lists are patched; the authoritative knowledge is left alone. So the skill is genuinely learned, castable and networked — it simply is not listed, and skill-limit mods, which count those same display lists, do not see it.

Nothing is persisted: the display lists are rebuilt from scratch every session, which is why uninstalling this library just makes the skill visible again rather than corrupting a save.

SL_Banish

Sends a character beyond the veil (inert, untouchable, untargetable and unseen) for a duration, then returns it exactly as it was.

<SL_Effect xsi:type="SL_Banish">
  <Delay>0</Delay>
  <SyncType>MasterSync</SyncType>
  <OverrideCategory>None</OverrideCategory>
  <Duration>10</Duration>
</SL_Effect>

Use MasterSync on enemies. Ends early if the character dies, restoring it as a lootable corpse.

A banished character is silent, and cannot start an attack while it is away. An attack it had already begun is not cancelled — it is held exactly as it was and resolves normally when the banish ends.

Loading a save mid-banish releases the creature immediately rather than starting the duration over, and in co-op a client that joins while a banish is running follows the host's rather than running a clock of its own.

Why you need this.

Vanilla has no equivalent. SL_Stun is cancelled by almost any state change, and SL_Petrify makes the target count as dead and revivable. Neither gives you a timed removal from the fight that returns the character untouched.

There are no toggles: Duration is the only field.

SL_PlayVFXOnWeapon

SL_PlayVFX, but able to land on weapons the vanilla hook silently refuses.

<SL_Effect xsi:type="SL_PlayVFXOnWeapon">
  <Delay>0</Delay>
  <SyncType>OwnerSync</SyncType>
  <OverrideCategory>None</OverrideCategory>
  <VFXPrefab>VFXRunicBlade</VFXPrefab>
  <HitPos>false</HitPos>
  <ParentMode>This</ParentMode>
  <DontInstantiateNew>false</DontInstantiateNew>
  <AllowSkinnedMesh>true</AllowSkinnedMesh>
  <Slot>Keep</Slot>
</SL_Effect>
Field Meaning
VFXPrefab Same list as SL_PlayVFX
AllowSkinnedMesh Let the emitters find a SkinnedMeshRenderer. Default true
Slot Keep (the prefab's own value), MainHand or OffHand

Everything else behaves exactly as SL_PlayVFX.

Why you need this.

A weapon VFX like VFXRunicBlade has no position of its own. Its particle systems each carry a VFXParticlesOnWeapon, which does not follow the hand — it takes the weapon's mesh and makes it the particle emission shape.

Two things make it give up, and both are silent. When it gives up, the particle system keeps whatever shape the prefab was authored with and the effect still plays — just in the wrong place and the wrong form, with nothing logged. That fallback looks exactly like a positioning bug, which it is not: no Position, Rotation, ParentMode or Delay can affect it, because position was never the mechanism.

This effect sets those two fields on its own copy of the VFX before the game clones it. Everything else is left to vanilla PlayVFX.

SL_PlayVFXOnChar

SL_PlayVFX, but it puts the VFX on the affected character instead of wherever the prefab expects to hang from.

<SL_Effect xsi:type="SL_PlayVFXOnChar">
  <Delay>0</Delay>
  <SyncType>OwnerSync</SyncType>
  <OverrideCategory>None</OverrideCategory>
  <VFXPrefab>VFXBoonEthereal</VFXPrefab>
  <HitPos>false</HitPos>
  <ParentMode>This</ParentMode>
  <DontInstantiateNew>false</DontInstantiateNew>
  <Offset>
    <x>0</x><y>0</y><z>0</z>
  </Offset>
  <Detach>Character</Detach>
  <DetachLifetime>5</DetachLifetime>
</SL_Effect>
Field Meaning
VFXPrefab Same list as SL_PlayVFX
Offset Added to the character's position. Zero puts it on their root, at the feet
Detach Where the VFX goes when the status ends: None, Character or World
DetachLifetime Seconds before an outliving VFX is destroyed. Ignored when Detach is None

Everything else behaves exactly as SL_PlayVFX.

Why you need this.

A status effect's transforms hang below StatusManager, which sits a metre above the character's root. A prefab carrying its own vertical offset stacks the two and plays two metres up, with the second metre inside the prefab itself. Nothing is logged and nothing errors; the effect simply plays in the wrong place.

You cannot fix it from stock XML. SL_PlayVFX has no Position field, and ParentMode offers only This and FXWorld — neither anchors to a character. Adding the vanilla VFXPositionOnChar hook does not work either: it answers the broadcast VFXSystem.Play sends and reads the character passed to it, and that character is null here, so it does nothing. This effect places the VFX itself.

Detach solves a separate problem. A VFX near the end of a status forces you to extend the status Lifespan to give it room — and that extends every other effect too. Character gives the VFX its own clock while it keeps riding the character; World leaves it where it stood, for a VFX that marks a place rather than a creature.

SL_PlayVFXPulsed

SL_PlayVFX that can blink its VFX off and on again at given times, so a long-lived effect can mark moments instead of merely existing.

<SL_Effect xsi:type="SL_PlayVFXPulsed">
  <Delay>0</Delay>
  <SyncType>OwnerSync</SyncType>
  <OverrideCategory>None</OverrideCategory>
  <VFXPrefab>HexHauntedVFX</VFXPrefab>
  <HitPos>false</HitPos>
  <ParentMode>This</ParentMode>
  <DontInstantiateNew>false</DontInstantiateNew>
  <PulseAt>
    <float>8.68</float>
    <float>15.4</float>
    <float>19.98</float>
  </PulseAt>
  <PulseLength>0.4</PulseLength>
  <HardClear>true</HardClear>
  <StopAt>0</StopAt>
</SL_Effect>
Field Meaning
PulseAt Seconds after this effect activates at which the VFX blinks off. Order does not matter
PulseLength How long each blink stays dark. Default 0.3
HardClear Destroy the particles already in the air instead of letting them fade. Default true
StopAt Seconds after activation at which the VFX stops for good. Zero means never

The first four fields are the same as SL_PlayVFX.

If PulseAt is empty in the log, the entries were dropped — each one must be a <float> element.

Why you need this.

Two separate things make a flicker impossible from stock XML:

  • Every PlayVFX instantiates its own copy of the prefab and keeps it in a private field. Two effects pointed at the same VFXPrefab therefore produce two independent VFX, and neither can stop the other. Stacking SL_PlayTimedVFX entries at intervals does not pulse one effect — it spawns several, each stopping on its own clock
  • VFXSystem.Stop() calls ParticleSystem.Stop() with no arguments, which means StopEmitting. Emission halts, but every particle already in the air lives out its startLifetime first

This effect owns one instance for its whole life and drives it from a single coroutine. HardClear uses StopEmittingAndClear to destroy the live particles rather than let them fade, which is what makes a short blink actually read as a blink.

PulseAt is relative to activation, not to the status. Effect.Delay is applied before the effect activates, so subtract it: a mark at 9.83s on an effect with <Delay>1.15</Delay> is 8.68.

SL_PlayTimedSound

SL_PlaySoundEffect that stops after a given time, instead of running until its clip ends or the status does.

<SL_Effect xsi:type="SL_PlayTimedSound">
  <Delay>0</Delay>
  <SyncType>OwnerSync</SyncType>
  <OverrideCategory>None</OverrideCategory>
  <Sounds>
    <Sounds>ENV_MagicSourceLoop</Sounds>
  </Sounds>
  <Follow>true</Follow>
  <MinPitch>1</MinPitch>
  <MaxPitch>1</MaxPitch>
  <StopAfter>10</StopAfter>
  <FadeIn>0</FadeIn>
  <FadeOut>0.15</FadeOut>
</SL_Effect>
Field Meaning
Sounds Same list as SL_PlaySoundEffect; one is chosen, as vanilla does
StopAfter Seconds after activation at which the stop begins. Zero means never
FadeIn Seconds to fade in. Zero starts at full volume
FadeOut Seconds to fade, the sound is silent at StopAfter + FadeOut. Zero cuts at StopAfter

Follow, MinPitch and MaxPitch behave exactly as SL_PlaySoundEffect.

Why you need this.

SL_PlaySoundEffect fires and forgets. The audio manager hands back a sound source and the vanilla effect discards it, so there is no handle to stop — which makes the status Lifespan the only control you have over when a sound ends. Extend a Lifespan for any other reason and every sound in the status is dragged along with it.

This plays the sound itself so it can keep that handle. Everything else was checked against the decompiled vanilla effect rather than assumed, so swapping one for the other changes nothing except that it now stops.

SL_ReliableVisuals

A marker. It does nothing on its own. Put one on a status effect and that status VFX and sounds play on every client, not only on the one that owns the affected character.

<SL_Effect xsi:type="SL_ReliableVisuals">
  <Delay>0</Delay>
  <SyncType>OwnerSync</SyncType>
  <OverrideCategory>None</OverrideCategory>
  <IncludeBlasts>false</IncludeBlasts>
</SL_Effect>
Field Meaning
IncludeBlasts Also play SL_ShootBlast. Default false

WARNING: IncludeBlasts changes gameplay, not just visuals.

A blast normally deals damage, and playing one on a client that does not own the character is a gameplay change rather than a visual. Turn this on only for a blast you have deliberately made inert — RefreshTime -1, HitOnShoot false — and are using purely for its particles.

Why you need this.

A status applied to an AI is owned by the master client, so only the master runs its effects locally. Every other client depends on this:

if (m_statuses.TryGetValue((UID)_statusUID, out status))
    status.OnReceiveActivatedEffects(_affectedCharacter, _activatedEffectsInfos);

The receiving client must already hold that exact status UID — and every application mints a new one, so an announcement that arrives before the status itself is discarded silently.

This acts when the status arrives, which happens reliably on every client, rather than when its effects are announced. Nothing is sent, so nothing can be lost: both clients hold the same XML and replay it themselves. If the announcement does arrive afterwards it is suppressed, so nothing plays twice.

Limits:

  • Conditions are evaluated locally: an effect gated on state a remote client cannot read correctly may decide differently there. Avoid EffectConditions on effects you mark
  • Timing is per-client: each client times from its own status arrival, so a long sequence can sit tens of milliseconds apart between screens. Invisible for visuals; do not build gameplay on it
  • It reports itself in the log — played N visual effects, and (N FAILED) with a warning naming the effect if any throws

Notes

  • All eight effects are verified in single-player and co-op

Contacts

Thunderstore development is made possible with ads. Please consider making an exception to your adblock.