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[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.
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 keys
and indexes
are and which values they can take.
Keys
in human languageThe 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.
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.
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
decipheredUp 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:
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.
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.
That's all for today, kiddos.
Hopefully now you are able to actually use NiOverride.
Next time we will learn about Skin
functions.
... ugh.
The same way Egyptian hyeroglyphs became "easy" to understand once some poor bastard dedicated his whole life to deciphering the Rosetta Stone. ↩︎
You may need to unpack RaceMenu.bsa
.
I use the awesome BSA Browser. ↩︎
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. ↩︎
You can get the TextureSet
with either a property
or Game.GetFormFromFile()
. Your call. ↩︎
Pun intended. By living this long I've earned the right to make dad jokes. ↩︎