It was a warm sunny day of spring 2020... or summer. I don't care.
After hearing so many whispers about how notably difficult was NiOverride to work with, I finally gathered the courage to use it while telling myself:
Ohhh come on! How difficult could it be?
I don't really think it's harder than decompiling Flash files and rummaging all around the internet just to implement some stupid bar meter in Skyrim... and I surely did that and came out more or less unscathed, but certainly wiser.
...if only I knew...
Soooo... you've already read the basics about NiOverride texturing and found it totally easy to understand, "What's the fuss about NiOverride? It's not that hard!".
Well, it's certainly easy to understand once someone tells you what and how things are supposed to be; so I'll continue doing just that.
NiOverride.psc, read it, and get totally hyped by all the things it can do.
Then read all the functions' parameters and then start planning to cry.
Let's take a look first to:
Function AddNodeOverrideInt(ObjectReference ref, bool isFemale, string node, int key, int index, int value, bool persist) native global
By it's name, we know it adds an integer value to a Node Override Layer, whatever the hell that means.
Let us see what arguments it can take, shallllll we???
ObjectReference ref is quite easy to understand, "It should be the
Actor we want to apply the override to, right?". Yes, you are right.
bool isFemale doesn't need Captain Obvious to come rescue you.
string node is easy because now you know some of the possible values for this (feel free to send me money for teaching you that).
int key: err...
int index: uhmmm...
int value is the value you want to add to this Node Override.
bool persist requires more explanation. I'll leave that for other day.
And the problem here is that we can't know what this function is useful for if we don't know what
indexes are and which values they can take.
Keysin human language
key argument will tell all functions that use it what they will modify.
These are the possible values for that variable. They are taken from the very same NiOverride script.
Valid keys ID - TYPE - Name 0 - int - ShaderEmissiveColor 1 - float - ShaderEmissiveMultiple 2 - float - ShaderGlossiness 3 - float - ShaderSpecularStrength 4 - float - ShaderLightingEffect1 5 - float - ShaderLightingEffect2 6 - TextureSet - ShaderTextureSet 7 - int - ShaderTintColor 8 - float - ShaderAlpha 9 - string - ShaderTexture (index 0-8) 20 - float - ControllerStartStop (-1.0 for stop, anything else indicates start time) 21 - float - ControllerStartTime 22 - float - ControllerStopTime 23 - float - ControllerFrequency 24 - float - ControllerPhase
For example, if we call
AddNodeOverrideInt with a key of
7 and a value of
0xFF0000, we can set the
Actor skin color to a healthy thank-you-for-fucking-up-my-retina red.
If you try any of those, you will see they totally don't work.
Indeed it is. And I left it on purpose.
When working with Node Override Layers, you can think about them as being hidden, and they will not show up until you set a texture set to them.
You won't see changes in a Node Override Layer that hasn't a
So you need to assign a
TextureSet (from an
*.esp file) by using
AddNodeOverrideTextureSet(ref, isFemale, node, 6, myTextureSet, true)
Notice how this time we are using
key = 6.
If we go back to where we learned the possible values for all the keys, we will see that, "Using
6 for a key means NiOverride expects a
ShaderTextureSet of type
6 - TextureSet - ShaderTextureSet).
Knowing this I trust now you can get a better idea of what you are actually doing.
Say, do you understand why if you want to change a layer transparency to 50% you should do this?
AddNodeOverrideFloat(ref, isFemale, node, 8, -1, 0.5, true)
Up until now I've been using
-1 for all indexes. This means the value for an index is irrelevant for that
In other words, that particular
key doesn't care about indexes.
There's only one key that cares about indexes:
When I finally understood that fact, I suspected this was the key that solved what I was looking for: changing only the normal map for an
Actor to simulate dynamic muscle definition.
Now my problem was that I didn't know what the fuck indexes were.
So I did a little
while loop that got me all texture names in indexes from 0 to 99, or something.
int i = 0 While i < 100 Debug.Trace( i + " " + GetNodeOverrideString(ref, isFemale, "Body [Ovl0]", 9, i) ) i += 1 EndWhile
And I got mostly nothing, except that
index = 0 was the diffuse texture, while
index = 1 was the normal one.
I also got two or three more textures, but I didn't care about those; I had the index for the normal texture and that was all that mattered.
Around 1 year later I was setting up some
TextureSets for some mod or whatever in SEEdit and then I noticed this:
Then it sudenly dawned on me these were the so called inedexes!
At that time I was pantless (because, is there any other programming paradigm?), so I totally stood up and started running around the street while yelling, "EUREKA, EUREKA!"
Those elusive values for indexes were in the
*.esp files all along!
Through testings and delving into
TextureSet records, I got a more complete list of them.
|2||Environment mask / Subsurface tint|
|3||Glow / detail|
|7||Backlight mask / specular|
You can find some ideas on how these and other
keys are used by reading DM_SandowPP_TextureMngr.psc. Lines 373-387 are the ones you actually care about.
That's all for today, kiddos.
Hopefully now you are able to actually use NiOverride.
Next time we will learn about
The same way Egyptian hyeroglyphs became "easy" to understand once some poor bastard dedicated his whole life to deciphering the Rosetta Stone. ↩︎
Numbers are in RGB hexadecimal format. First pair is Red, second is Green and last is Blue.
These articles aren't for beginners, as the disclaimer at the main articles page says. If you don't know what hex numbers are, go learn it somewhere else. ↩︎
Pun intended. By living this long I've earned the right to make dad jokes. ↩︎