About This File
Framework that allows you to attach REAL leashes to NPC's and either have them guide you around, or you guide them. No real limitation, you can make a huge train if you wanted.Â
Â
I'm releasing this in it's current beta state! I've yet to find any serious bugs, but I do want to gauge the community to see if this mod is worth continuing further or expanding upon.Â
Â
Â
Important Note
This is a FRAMEWORK. NPC's are expected to have AI packages with locomotion. If you try to use it on an NPC who is leaning against a wall, playing an animation, guarding the whiterun gate - this mod ~WILL NOT~ make them follow. Your mod is also expected to equip the leash.
Â
Features
- Rich Papyrus API for connecting actors together in any order you will
- Advanced physics system that takes full advantage of HAVOK
- Easy to use debug menu for testing, or just using the mod freely
- Zero package interruption, this mod has it's own built-in AI system, pathfinder, and locomotion system. Your mod's packages will never be touched!
- Full support for the player actor, other actors, etc. No animal support yet, although that wouldn't be difficult
- Contains two test leashes by @kzrp to mess around with
- Supports ANY/ALL leashes, they just need a series of bones for this mod to move around! This means that all the chain collars in devious devices are fully compatible!
Â
How to use (Without Papyrus API)
- Enough the leash to the actor (Or a leash, it can be any leash like from devious devices)
- Go into the SKSE Mod Menu using `f1`, find this mod. Open `Debug`.Â
- Select the leasher, and the leashed. Leasher MUST have that leash equipped, the mod doesn't do it for you - stop being lazy
- The parent bone is `NPC Spine2 [Spn2]` for the included leash. This can be any parent of the chain bones, even just NPC
- The `Leash Bone Match` is `Leash1` for the included leash. This is the prefix of the bones (or common name) in your leash
- Select a minimum distance, like ~200+. Don't go too low or it'll be fucked.Â
- `Persistent` means it'll persist with your save.Â
- Click `Leash actor`
- Enjoy
- (Note: Make sure the collar/leash is equipped to the actor, this mod doesn't auto equip it)
Â
For a funnier experience, go into `Settings` and enable `Forced recovery for NPCs`. This will rag doll the actor once they exceed the distance threshold. Works well!
Â
Known issues/limitations
- An actor can get stuck obviously.
- The less bones in the leash, the more fucked it'll be when stretching a long distance
Â
Requirements
- Skyrim SE/AE (Doubt VR support works, this required a lot of reverse engineering)
- SKSE Mod Menu Framework (Optional for the mod menu)
Â
Papyrus API
Scriptname LeashFramework Hidden ;/ Active leash factions supplied by Leash.esm: - LeashedFaction (form ID 0x000D6A): contains actors that currently have an active leash. - LeasherFaction (form ID 0x000D6B): contains actors that currently hold one or more active leashes. Leash lifecycle mod events: - LeashFramework_OnLeash: sent when a leash is successfully applied, replaced, or restored from a save. - LeashFramework_OnUnleash: sent when a leash is disconnected, removed by UnleashAll, or replaced. Both lifecycle events use the leashed Actor as sender and set numArg to 0.0. strArg describes why the event was sent. OnLeash uses "applied", "replaced", or "loaded". OnUnleash uses "disconnected", "unleashAll", or "replaced". Replacing a leash sends OnUnleash before OnLeash. Internal state cleanup while starting or loading a game does not send OnUnleash. Pull mod events: - LeashFramework_OnActorPulled: sent once when normal direct-locomotion pulling starts. - LeashFramework_OnActorRagdollPulled: sent once when forced ragdoll pulling starts. Register for these events with RegisterForModEvent. Both pull events use the leashed Actor as sender, leave strArg empty, and provide the holder/anchor-to-collar distance at the transition in numArg. A pull event can be sent again only after that pull mode stops and later starts again; it is not sent every frame while pulling remains active. /; ;/ Connects a leash from a holder's right hand to leash bones on another actor. holder: Actor holding the leash. The current attachment bone is NPC R Hand [RHnd]. leashed: Actor wearing the leash mesh. An actor can have only one active leash. parentBone: Exact node name to find on the leashed actor before searching its descendants. leashBoneMatch: Text found anywhere in each ordered leash bone name beneath parentBone. For example, Main_ matches hdtSSEPhysics_AutoRename_Armor_00000004 Main_01 after SMP renames the node. minLength: Distance where active leash pulling stops. Must be zero or greater. maxLength: Maximum holder-to-collar distance. Must be positive and at least minLength. persistent: When true, the leash is saved and restored until explicitly disconnected. Applying another leash to the same leashed actor replaces its current leash. Returns true when the arguments are valid and the leash is accepted. /; Bool Function ApplyLeash(Actor holder, Actor leashed, String parentBone, String leashBoneMatch, Float minLength, Float maxLength, Bool persistent) Global Native ;/ Connects a leash from either hand on a holder to leash bones on another actor and applies the closed-fist grip. rightHand: When true, uses NPC R Hand [RHnd]. When false, uses NPC L Hand [LHnd]. All other arguments and replacement behavior match ApplyLeash. Returns true when the arguments are valid and the leash is accepted. /; Bool Function ApplyLeashToHand(Actor holder, Actor leashed, String parentBone, String leashBoneMatch, Float minLength, Float maxLength, Bool persistent, Bool rightHand = True) Global Native ;/ Connects a leash from an exact bone on a holder to leash bones on another actor. holderBone: Exact node name on the holder. This attachment does not alter the holder's hand pose. offsetX, offsetY, offsetZ: Optional local-space offset from holderBone. The offset follows the bone's translation, rotation, and scale, but does not add an attachment rotation. All other arguments and replacement behavior match ApplyLeash. Returns true when the arguments are valid and the leash is accepted. /; Bool Function ApplyLeashToBone(Actor holder, Actor leashed, String holderBone, String parentBone, String leashBoneMatch, Float minLength, Float maxLength, Bool persistent, Float offsetX = 0.0, Float offsetY = 0.0, Float offsetZ = 0.0) Global Native ;/ Connects a leash mesh equipped by the holder to an exact bone on the leashed actor. The first ordered leash bone remains in its neutral animated pose on the holder. The last ordered leash bone attaches to leashedAttachmentBone. leashParentBone: Exact node name to find on the holder before searching its descendants for leash bones. leashedAttachmentBone: Exact node name on the leashed actor. attachmentOffsetX, attachmentOffsetY, attachmentOffsetZ: Optional local-space offset from leashedAttachmentBone. The offset follows the bone's translation, rotation, and scale, but does not add an attachment rotation. closedHand: Applies the closed-fist pose to the holder. 1 closes the right hand, 2 closes the left hand, and every other value leaves both hands unchanged. All other arguments and replacement behavior match ApplyLeash. Returns true when the arguments are valid and the leash is accepted. /; Bool Function ApplyHolderOwnedLeashToBone(Actor holder, Actor leashed, String leashedAttachmentBone, String leashParentBone, String leashBoneMatch, Float minLength, Float maxLength, Bool persistent, Float attachmentOffsetX = 0.0, Float attachmentOffsetY = 0.0, Float attachmentOffsetZ = 0.0, Int closedHand = 0) Global Native ;/ Connects a holderless leash from a fixed position to leash bones on an actor. anchorCell: Cell containing the world-space position. It must correspond to the supplied coordinates. x, y, z: Fixed world-space coordinates of the leash anchor. All other arguments and replacement behavior match ApplyLeash. Pull distances are measured from the fixed anchor instead of a holder. World-position leashes do not assign LeasherFaction, have no result from GetLeashHolder, and do not use holder teleport recovery. Use DisconnectLeash(None, leashed) to disconnect one. Returns true when the arguments are valid and the leash is accepted. /; Bool Function ApplyLeashAtPosition(Actor leashed, Cell anchorCell, Float x, Float y, Float z, String parentBone, String leashBoneMatch, Float minLength, Float maxLength, Bool persistent) Global Native ;/ Disconnects the leash matching the specific holder and leashed actor. holder: Actor holding the leash. Pass None for a holderless world-position leash. leashed: Actor connected as the leashed endpoint. Returns true when the matching leash was disconnected. /; Bool Function DisconnectLeash(Actor holder, Actor leashed) Global Native ;/ Disconnects every leash where actor is either the holder or the leashed actor. Returns true when at least one leash was disconnected. /; Bool Function UnleashAll(Actor actor) Global Native ;/ Returns true when actor currently has an active leash. /; Bool Function IsLeashed(Actor actor) Global Native ;/ Returns true when actor currently holds at least one active leash. /; Bool Function IsLeashHolder(Actor actor) Global Native ;/ Returns the actor holding leashed's active leash, or None when the leash is holderless or absent. /; Actor Function GetLeashHolder(Actor leashed) Global Native ;/ Returns every actor currently leashed to holder. Returns an empty array when holder has no active leashes. /; Actor[] Function GetLeashedActors(Actor holder) Global Native ;/ Returns the minimum length of leashed's active leash, or -1.0 when leashed has no active leash. /; Float Function GetMinLeashLength(Actor leashed) Global Native ;/ Returns the maximum length of leashed's active leash, or -1.0 when leashed has no active leash. /; Float Function GetMaxLeashLength(Actor leashed) Global Native ;/ Sets the minimum length of leashed's active leash. newLength must be zero or greater and cannot exceed the current maximum length. Returns false when leashed has no active leash or newLength is invalid. /; Bool Function SetMinLeashLength(Actor leashed, Float newLength) Global Native ;/ Sets the maximum length of leashed's active leash. newLength must be positive and cannot be less than the current minimum length. Returns false when leashed has no active leash or newLength is invalid. /; Bool Function SetMaxLeashLength(Actor leashed, Float newLength) Global Native
Â
Future todos:
-
Allow attaching the rope to specific bones rather than just the right hand - Add explicit API to disable walking control (For now just set minLength/maxLength to high numbers so it never triggers)
Â
Known issues:
- Actors can get stuck on objects like doors rarely
- Some chains may not converge well (Gaps)Â - increase the solve iterations in the SKSE Mod Menu
-
Actors wont follow you between load doors (Not a problem, lack of a feature)
Â
Source code:
What's New in Version 1.1.1
Released
Â
1.1.1 will unleash all your older 1.0.9 leashes upon loading.
Â
1.1.1:
- Added API/functionality to allow the leasher to have the leash equipped instead of the leashed actor
- Some stability improvements to leash physics/actor-collision
- Added lordbuttcheese's modified hand-leash (The leasher equips this instead to their shield slot)
- Corrected Skyrim's premature culling of the rope
Â
1.0.9
- Pretty significant internal code refactor/cleanup - use with caution (Backing up saves recommended just in case)
- Added procedural body animations, so the body will react to the physical pull force of the leash
- Added an option to allow camera movement while player's follow/AiMode is enabled (Patches for all of skyrim, not just this mod)
- New API events LeashFramework_OnLeash, LeashFramework_OnUnleash
- Leash anchors expanded, you can now anchor to either right/left hand, a specific world position, or specific bones in the leasher's body
- krzp: Added new magic leash - formid 32ce
Â
Due to the .esp to .esm - you'll need to unleash your actors before upgrading to 1.0.8 to avoid save issues. Make sure you also rename the Leash.esp to Leash.esm in the add-leash part of the debug menu otherwise obviously it wont add the leash since Leash.esp doesn't exist anymore.
Â
1.0.8
- Added escort teleporting (for both player/npc) between cells and if you get too far away (Lightly tested, GL!)
- Fixed some data getting baked into the save file
- Prevent SexLab actors from being yanked out of animations
- Converted to a light master (.esm) instead of an .esp
Â
1.0.7
- Fixed dangling pointer (Likely lead to many random bugs, including potential crashes. WOOPS)
Â
1.0.6
- Expanded the Papyrus API
- Leashed/Leashers will now be entered into a faction
- Cleaned up the holder's fist a bit more
- Player input is while leashed and in follow mode will allow you to catch up, or resist
Â
1.0.5
- Improved leash-on-actor collision a bunch, exposed configuration for it in the Mod Menu. It's enabled by default now too
- krzp: Unleashed items now have a simple SMP config, so they don't load as sticks if SMP is present (doesn't have any colliders, I didn't bother editing all the meshes now, may do so later)
- krzp:Â New leash model, Neck chain (id 806)
Â
1.0.4
- Allow more actor states to be ragdolled (It's safer than I thought)
- Fixed some bugs with the pathfinding
Â
1.0.3:
- Fixed following not working on Skyrim AE for certain people (Don't expect it to work on NPC's who are in an unwalkable state)
- Other minor tweaks
Â
1.0.2:
- Improved pathfinding object avoidanceÂ
- QoL updates, such as warnings and a debug toggle in the menuÂ
Â
1.0.1:
- Significantly improved the pathfinding (Actors following). I still need to implement an obstacle avoidance heuristic though since the navmesh in Skyrim isn't perfect (And dynamic objects like doors...)
- Put some more effort into the collision system to avoid collision boiling (Didn't iterate on actor collision at all yet)
- Exposed more configuration to the SKSE Mod Menu
- Fixed a bug with equipping through the mod menu
- Added a skeleton dump to the menu to help you find the proper nodes
- Freeze the rope while the game is froz