Keys, indexes and other evil creatures

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...

Node Override Layers in practice

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[1]; so I'll continue doing just that.

Go open NiOverride.psc[2], 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.

... a lot.

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???

And the problem here is that we can't know what this function is useful for if we don't know what keys and indexes are and which values they can take.

Keys in human language

The 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[3], we can set the Actor skin color to a healthy thank-you-for-fucking-up-my-retina red.

Use

AddNodeOverrideInt(ref, isFemale, "Body [Ovl5]", 0, -1, 0x00FF00, false)
AddNodeOverrideInt(ref, isFemale, "Hands [Ovl2]", 0, -1, 0x00FF00, false)
AddNodeOverrideInt(ref, isFemale, "Feet [Ovl2]", 0, -1, 0x00FF00, false)
AddNodeOverrideInt(ref, isFemale, "Face [Ovl2]", 0, -1, 0x00FF00, false)

to bring momentary world peace.

If you try any of those, you will see they totally don't work.

It's a trap!

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.

Warning

You won't see changes in a Node Override Layer that hasn't a TextureSet assigned.

So you need to assign a TextureSet (from an *.esp file)[4] by using AddNodeOverrideTextureSet.

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 TextureSet" (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)

Indexes deciphered

Up until now I've been using -1 for all indexes. This means the value for an index is irrelevant for that key.
In other words, that particular key doesn't care about indexes.

Note

There's only one key that cares about indexes: 9.

When I finally understood that fact, I suspected this was the key[5] 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:

Oh...

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!"

All my neighborgs can attest I actually did this; but if they say I did something else, they are lying.

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.

Index Texture
0 Diffuse
1 Normal
2 Environment mask / Subsurface tint
3 Glow / detail
4 Height
5 Environment
6 Multilayer
7 Backlight mask / specular
8 ???

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.

Wrapping up

That's all for today, kiddos.
Hopefully now you are able to actually use NiOverride.

Next time we will learn about Skin functions.
... ugh.


  1. The same way Egyptian hyeroglyphs became "easy" to understand once some poor bastard dedicated his whole life to deciphering the Rosetta Stone. ↩︎

  2. You may need to unpack RaceMenu.bsa.
    I use the awesome BSA Browser. ↩︎

  3. 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. ↩︎

  4. You can get the TextureSet with either a property or Game.GetFormFromFile(). Your call. ↩︎

  5. Pun intended. By living this long I've earned the right to make dad jokes. ↩︎