Over the winter holiday I ran into a problem that resulted in the creation of a new mod and a new tool for Skyrim. The mod is called Poser Hotkeys and the tool that generates the data for the mod is PoserDataGen.
Recently, I’ve been spending most of my time in Skyrim setting up cool scenes for screenshots. I have several poser mods installed and I was finding it tedious to find a cool pose, apply it to an actor, position the actors and then find a good camera angle. I use several tools for this, but none of them seemed to play together nicely.
I have heard of some tool that allows you to assign hotkeys to iterate through poses in the MCM. That sounded great but I could never find a reliable download source, so I just created my own mod for it. You can get my mod and the data tool for the mod on the Nexus. The source code repositories are also available here and here.
If you want to know how to use them in your game, check out the Read Me or Nexus description. The rest of this post is a breakdown of what these things do, how and why.
Adding animations to Skyrim is made possible through a tool called Fores New Idles in Skyrim, or FNIS for short. If you are interested, Fore has some technical documentation available on the FNIS Nexus page.
When an artist adds animations to Skyrim, they need to supply FNIS with a text file containing a list of the animation files along with a name for each animation or pose. These names provide a means to play the animation through SendAnimationEvent in Papyrus.
Poser Hotkeys is essentially a means to organize animation events and call SendAnimationEvent when a user presses a button. In order to send the correct animation event, Poser Hotkeys has to know the event name to send. This is where PoserDataGen comes in.
PoserDataGen is a .NET tool that scans your Skyrim animations folder for FNIS definitions. These definitions are then placed into data files that Poser Hotkeys can read at run time. Being data driven allows the mod to work with any poser combination that exists on a user’s setup without the need to recompile scripts or make changes in the CreationKit.
Originally, I threw the data into XML and used FISS to read it at run time. This worked reasonably well, but there were some problems. The first is that FISS seems to be unable to handle reading a string from an empty XML attribute. For example, reading from the element “name” below:
I would expect this to return an empty string, but instead FISS sends through the XML starting with the end of the “name” element. In this example, the string returned by FISS for element “name” would be:
This is a problem for me, since there are times I am expecting elements to be empty. A second problem I was having was that I couldn’t scan a folder for XML files. This meant that I had to either create a hard coded list of XML to look for (thus limiting the poser support) or cram all of the definitions into a single, known XML file.
I opted to cram everything into a single XML file, which then created other problems: I could only handle 128 poser packs since that is the array length limit of Papyrus, whenever a user installed or removed a poser the entire XML file had to be recreated with PoserDataGen from scratch (invalidating their favorites), and the file had to be loaded by the user in MCM before the mod could be aware of data.
Beyond all that, another problem was that I was introducing an additional dependency for the user.
Since I already require PapyrusUtil for functions related to NPC packages (to stop them in place while they play an animation) I took a look at the Json library that comes with it. It turns out that JsonUtil works very well, so I changed PoserDataGen to spit out .json data files that JsonUtil can read at run time. This lets me update and save parts of the loaded .json files on the fly during run time and save things, like the currently selected pose, directly in the pose’s data file. There is also a function to get an array of all .json files in a specific directory, which lets me scan for poser data files without having to know which ones to look for ahead of time.
How it works now is that PoserDataGen loads all of the FNIS animation lists and organizes the pose data by poser and by “poser pack.” I define a poser to be an FNIS list file and a “poser pack” to be animation definitions that share a similar prefix. For example, Halo’s Poser S comes with two FNIS files which results in two poser data files. Each FNIS file has several groups of animations starting with a similar prefix. For example, all poses that are part of Halo’s Poser Module 11 start with “H11P” resulting in a “poser pack” called H11P in the data file.
I allow a user to specify their Skyrim directory in the accompanying config file. If it’s not there, I try to find it in the registry. Failing that, I try a hard-coded default path that Steam uses when it installs the game. Here is the C# for it:
// load skyrim directory in user settings
this.skyrimDirectory = Settings.Default.SkyrimDirectory;
// try default skyrim directory if none in settings
var localSkyrimDirectory = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Bethesda Softworks\Skyrim", "Installed Path", DefaultSkyrimDirectory);
this.skyrimDirectory = localSkyrimDirectory?.ToString() ?? DefaultSkyrimDirectory;
Once I find a valid animations folder, I save the Skyrim directory in the config so I don’t need to keep asking for it every time you open the app:
// save valid directory
this.textBoxSkyrimDirectory.Text = skyrimDirectory;
Settings.Default.SkyrimDirectory = skyrimDirectory;
A neat feature I added is the option to validate that animation files actually exist for each animation event before adding them to the data file. If I simply trust the FNIS file, Poser Hotkeys could send animation events to an actor that do not have a matching animation file, resulting in the actor simply doing nothing. Since the SendAnimationEvent function has no return value, Poser Hotkeys does not know if sending the event succeeded or failed. Users would be left without any information as to why they pressed a button to start a pose and nothing happened, which is obviously a bad experience for the user.
I added this because my copy of Pinup Poser includes many animation events for animation files that are absent, resulting in lots of data records that did nothing when executed. With this option turned on, PoserDataGen skips the missing animations, making them invisible to the user.
When parsing the FNIS files, I only worry about a single FNIS list file in each directory. I may change this to handle multiple files in the future, but I have never come across a poser that had more than one in a single folder, and I think FNIS enforces that only one list file can be in a given directory anyway.
// find fnis definition
var fnisFile = Directory.GetFiles(this.AnimationsDirectory, "FNIS_*_LIST.txt").FirstOrDefault();
// parse fnis file
var fnisTextData = File.ReadAllText(fnisFile);
I then go line by line to find the animation events. FNIS allows the author to define some options in between the animation event and the animation file path, so I need to check and skip the unneeded data if present. I need the animation file path to verify the file actually exists, assuming the user has selected the option:
// skip sequenced animation events
if (lineParts == "+")
// extract animation event
var animationEvent = lineParts;
var animationFile = lineParts;
if (lineParts == '-')
animationEvent = lineParts;
animationFile = lineParts;
// filter missing animation files
if (filterMissingAnimations && !File.Exists(this.AnimationsDirectory + animationFile))
All of this data is written out to .json in a structure matching what is expected by JsonUtil. The structure is unintuitive to me, though I have to assume it’s needed for the utility to function. Since Papyrus has no concept of objects, it is difficult to serialize related data in a meaningful way. JsonUtil seems to prefer to nest elements according their data type. For example, all strings are nested under an element called “string” and all integer lists under “intList.”
Something peculiar I found while using JsonUtil was that it refused to read data from keys that started with a capital letter. When JsonUtil creates data, it seems to always start with a lower case letter. Since I am not using JsonUtil to create the data files I didn’t know about this and I had a hard time discovering why I could not read from an element with the key “Name” while I could read and write other data to the file perfectly fine. Changing the key to “name” solved the problem.
As far as the Papyrus goes, the bulk of it is MCM magic to handle key assignments, drop down lists and user experience. Perhaps the most important parts are the functions that start and stop posing, so I’ll post them here.
Here is the Papyrus used to show a pose on an actor:
Function PoseShow(ObjectReference akTarget)
DisplayedPoseDescription = GetSelectedPackName() + " (Pose " + (GetSelectedPoseIndex() + 1) + ")"
DisplayedPoseAnimationEvent = GetSelectedPoseAnimationEvent()
If akTarget == Player
PlayerIsPosing = True
ActorUtil.AddPackageOverride(akTarget as Actor, PackageStop, 1)
(akTarget as Actor).EvaluatePackage()
akTarget.SetAnimationVariableInt("IsNPC", 0) ; disable head tracking
Some things to note:
- The akTarget argument passed in is determined by looking at the player’s cross-hair reference. If there is an NPC in the cross-hair the NPC is the target, otherwise the player is the target.
- For NPCs, I take an empty AI package I made called “PackageStop” and apply it with ActorUtil to freeze them in place. I also set “IsNPC” to false, which stops them from head-tracking the player
- For both the player and NPCs, I set “bHumanoidFootIKDisable” to true, which turns off the inverse-kinematics used to position the legs/feet of actors on uneven terrain.
These settings result in a pose that should be as close to the artist’s intent as possible. You shouldn’t see posed NPCs engaging in combat or breaking their backs trying to face you while talking. They also should keep their feet flat on the ground, preventing weird leg positions that could result from the inverse-kinematics.
Clearing a pose from an actor is done essentially the same way, but in reverse: restoring the old AI package and turning on head tracking and inverse-kinematics:
Function PoseStop(ObjectReference akTarget)
If akTarget == Player
PlayerIsPosing = False
ActorUtil.RemovePackageOverride(akTarget as Actor, PackageStop)
(akTarget as Actor).EvaluatePackage()
akTarget.SetAnimationVariableInt("IsNPC", 1) ; enable head tracking
Sending the “IdleForceDefaultState” animation event at the end clears the current pose from the actor, returning them to their normal idle state.
The core functionality of this mod was spun up over a weekend, but polishing it to make it user friendly took nearly two weeks. I hope I made these tools intuitive and robust, but I’m sure users will quickly show me the holes when I release it this week.