Jump to content

[LE] Change Tint of Facegen Texture via Script


OregonPete

Recommended Posts

I'm creating a mod where the heroine (companion NPC) transforms into a demoness, but I'm having issues getting the tints right with NiOverride. Here's what I've done so far:

 

I was able to add texture overrides for the entire body using NiOverride.AddNodeOverrideXxx, including the head (haven't tried the hair yet), which got the tint right, but messed up everything else (for instance, she no longer had eyebrows because the head texture overrode them). Not quite sure how to add the lip, hair, and eyebrow textures seperately, but I might try that next.

 

If I just attempt to change the tint with NiOverride.AddSkinOverrideInt using the slot, I can't seem to change the head texture. The body (i.e. torso, hands, feet) isn't an issue, but I think the facegen mesh is getting in the way somehow and overriding anything I attempt to do with the head. I can't even change my NPC's hair color, not even with SKSE's SetHairColor on the actor base. It works fine if I remove the facegen mesh and texture (for instance, by renaming them), but then, of course, I get the black face bug.

 

Strangely, if I use NiOverride.AddSkinOverrideInt, I can't seem to remove the override anymore. I've tried using NiOverride.RemoveAllReferenceSkinOverrides for just that NPC, and I tried using NiOverride.RemoveSkinOverride, but no luck. Really strange. If anyone has any ideas on that, I would sure appreciate it.

 

Any ideas on how to override the facegen texture? Is there a specific slot for that? I would be grateful for any ideas.

 

To make things simpler, here's some of my code:

 

 

 

; This attempts to just change the skin tone.
; Works for body, hands, and feet, but could
; not get it to work for head or hair.
Function ApplyDemonessSkinOverrides()

	Actor _marisa = GetMarisa()

	Int _slotHead = 0x01
	Int _slotHair = 0x02
	Int _slotBody = 0x04
	Int _slotHand = 0x08
	Int _slotFeet = 0x80

	Int _colorHair = 1053202 ; HairColor12BlackTrue (16 18 18; #101212; 1,053,202)
	Int _colorBody = 12454415 ; Body Color Red (190 10 15; #BE0A0F; 12,454,415)

	AddSkinColorOverride( _marisa, _slotHair, _colorHair )
	AddSkinColorOverride( _marisa, _slotHead, _colorBody )
	AddSkinColorOverride( _marisa, _slotBody, _colorBody )
	AddSkinColorOverride( _marisa, _slotHand, _colorBody )
	AddSkinColorOverride( _marisa, _slotFeet, _colorBody )

	NiOverride.ApplySkinOverrides( _marisa )

EndFunction

Function AddSkinColorOverride( Actor Target, Int Slot, Int Color  )

	Int _idx = -1 ; Index is irrelevant for Skin Color
	Int _key = 7 ; Shader Tint Color

	Bool _female = True
	Bool _persist = True
	Bool _1stPers = False

	NiOverride.AddSkinOverrideInt( Target, _female, _1stPers, Slot, _key, _idx, Color, _persist )
	Utility.Wait( 0.01 )

EndFunction

; Clearing the tint overrides, however,
; does not work like this
Function ClearTextures()

	Actor _marisa = GetMarisa()

	NiOverride.RemoveAllReferenceNodeOverrides( _marisa )

EndFunction

; And clearing the tint overrides
; also does not work like this
Function RemoveSkinColorOverride( Actor Target, Int Slot  )

	Int _idx = -1 ; Index is irrelevant for Skin Color
	Int _key = 7 ; Shader Tint Color

	Bool _female = True
	Bool _1stPers = False

	NiOverride.RemoveSkinOverride( Target, _female, _1stPers, Slot, _key, _idx )
	Utility.Wait( 0.01 )

EndFunction


; This applies a single texture, seems to work fine
Function ApplySingleTexture( Actor _target, Int TxTo, Int Type )

	Bool _female = True

	Int _slot = 0
	Int _glow = 0
	Int _color = 16316671

	Float _alpha = 1.0

	String _type = GetTextureType( Type )

	String _node = _type + " [ovl" + _slot + "]"
	String _path = GetNodePath( TxTo, Type )

	NiOverride.AddNodeOverrideString( _target, _female, _node, 9, 0, _path, true)
	Utility.Wait( 0.01 )

	NiOverride.AddNodeOverrideInt( _target, _female, _node, 7, -1, _color, true)
	Utility.Wait( 0.01 )

	NiOverride.AddNodeOverrideInt( _target, _female, _node, 0, -1, _glow, true)
	Utility.Wait( 0.01 )

	NiOverride.AddNodeOverrideFloat( _target, _female, _node, 1, -1, 1.0, true)
	Utility.Wait( 0.01 )

	NiOverride.AddNodeOverrideFloat( _target, _female, _node, 8, -1, _alpha, true)
	Utility.Wait( 0.01 )

	NiOverride.AddNodeOverrideFloat( _target, _female, _node, 2, -1, 0.0, true)
	Utility.Wait( 0.01 )

	NiOverride.AddNodeOverrideFloat( _target, _female, _node, 3, -1, 0.0, true)
	Utility.Wait( 0.01 )

	NiOverride.ApplyNodeOverrides( _target )

	Debug.Trace("Texture Applied - " + _node + " = " + _path + ", Color: " + _color )

EndFunction

; Clearing the texture works too
Function ClearTextureType( Actor _target, Int Type )

	Bool _female = True

	Int _slot = 0

	String _type = GetTextureType( Type )
	String _node = _type + " [ovl" + _slot + "]"

    NiOverride.AddNodeOverrideString( _target, _female, _node, 9, 0, _TexturePath + "blank.dds", True )
    Utility.Wait( 0.01 )

    NiOverride.RemoveNodeOverride( _target, _female, _node, 9, 0)
    Utility.Wait( 0.01 )

    NiOverride.RemoveNodeOverride( _target, _female, _node, 7, -1)
    Utility.Wait( 0.01 )

    NiOverride.RemoveNodeOverride( _target, _female, _node, 0, -1)
    Utility.Wait( 0.01 )

    NiOverride.RemoveNodeOverride( _target, _female, _node, 8, -1)
    Utility.Wait( 0.01 )

    Debug.Trace("Texture Cleared - " + _node)

EndFunction


 

 

Link to comment
Share on other sites

Yeah, that with the race never really seems to work for me. I've tried several times with other characters, but I can't get the character to look the way I want her to. As soon as the race change is done, the character's face is completely messed up. Seems it uses some kind of defaults to reset the face. Or do you have any experience with that? She needs to change her skin tone from normal to red, and her hair color from brown to black. But otherwise her face and hair should remain the same. If I can get it to work with a race change, I'd do it, but I've never gotten it to work for me. Any support would be appreciated.

Link to comment
Share on other sites

So, I was able to solve my problem, although not quite in the way I was hoping. In the end, I wound up overriding all the body and head textures instead of just the tint of the original textures. Using murfk's SL/LL SlaveTats mod as a basis, and a lot of research on relevant forums, I was able to figure out how to make the switch. It's still not perfect, but I'm getting there. To show you the result, here are two images, before and after. I also have the relevant code below for those who are interested.

 

Is it possible to mark this thread as solved?

 

So, this is Marisa, a rather unaggressive ex-bandit and Breton mage with dark brown hair and dark blue eyes:

 

 

 

 

MarisaFollower01.jpg

 

 

 

 

But Marisa has a secret. Because she's also a black-haired, brown-eyed, Demi-Daedric princess, who can change into her hidden persona in an instant:

 

 

 

 

MarisaFollower02.jpg

 

 

 

 

And for those who are interested in how the texture, hair, and eye color swap was done, here's the relevant code:

 

 

 


Function TransformToDemoness()

	Debug.Trace( "Marisa Follower Quest - TransformToDemoness() called" )

	Actor _marisa = GetMarisa()
	ActorBase _base = _marisa.GetActorBase()

	If ( !NiOverride.HasOverlays( _marisa ) )

		NiOverride.AddOverlays( _marisa )
		Utility.Wait( 0.5 )

		If ( !NiOverride.HasOverlays( _marisa ) )

			Debug.Trace( "Marisa Follower TransformToDemoness() - Marisa is not compatible with overlays." )
			Return

		Endif

	Endif

	;ListWornItems()
	ListNumOverlays()
	ListSkinColorsPerSlot()

	ApplyTextures()

	If ( _marisa.IsOnMount() )

		_marisa.Dismount()

	EndIf

	; HairColor12BlackTrue
	ColorForm _color = Game.GetFormFromFile( 0x000F7565, "Skyrim.esm" ) as ColorForm
	Debug.Trace( "Marisa Follower Quest - TransformToDemoness() Color: " + _color )

	_base.SetHairColor( _color )
	Utility.Wait( 0.15 )

	; Required so we can call QueueNiNodeUpdate()
	Utility.SetINIBool( _UseFaceGen, False )

	Utility.Wait( 0.15 )
	_marisa.QueueNiNodeUpdate()

	; Required so we can call QueueNiNodeUpdate()
	Utility.SetINIBool( _UseFaceGen, True )

	_marisa.SetEyeTexture( EyesMaleHumanBrown )
	Utility.Wait( 0.15 )

	_marisa.AddToFaction( OPsMarisa_DemonessFaction )

	ListHeadParts()
	ListNumOverlays()

EndFunction


Function TransformToHuman()

	Debug.Trace( "Marisa Follower Quest - TransformToHuman() called" )

	ListWornItems()

	ClearTextures()

	Actor _marisa = GetMarisa()
	ActorBase _base = _marisa.GetActorBase()

	If ( _marisa.IsOnMount() )

		_marisa.Dismount()

	EndIf

	; HairColor09DarkBrown
	ColorForm _color = Game.GetFormFromFile( 0x000A0433, "Skyrim.esm" ) as ColorForm

	_base.SetHairColor( _color )
	Utility.Wait( 0.15 )

	; Required so we can call QueueNiNodeUpdate()
	Utility.SetINIBool( _UseFaceGen, False )

	_marisa.QueueNiNodeUpdate()
	Utility.Wait( 0.15 )

	; Required so we can call QueueNiNodeUpdate()
	Utility.SetINIBool( _UseFaceGen, True )

	_marisa.SetEyeTexture( EyesMaleHumanDarkBlue )
	Utility.Wait( 0.15 )

	_marisa.RemoveFromFaction( OPsMarisa_DemonessFaction )

EndFunction


Function ApplyTextures()

	Actor _marisa = GetMarisa()

	ApplyTextureSet( _marisa, 1 )
	ApplyTextureSet( _marisa, 2 )
	ApplyTextureSet( _marisa, 3 )
	ApplyTextureSet( _marisa, 4 )

	; Target, Texture, Type, Slot, Color, Alpha
	ApplySingleTexture( _marisa, _TextureLips, 2, 1, 0, 0.8, 4.0 )
	ApplySingleTexture( _marisa, _TextureBrows, 2, 2, 0, 1.0 )
	ApplySingleTexture( _marisa, _TextureNipples, 1, 2, 4194304, 0.9 ) ; Color #400000; 4194304
	ApplySingleTexture( _marisa, _TextureAreolas, 1, 1, 7340032, 0.9 ) ; Color #700000; 7340032
	;ApplySingleTexture( _marisa, _TextureNipples, 1, 2, 6291456, 0.8 ) ; Color #600000; 6291456
	;ApplySingleTexture( _marisa, _TextureAreolas, 1, 1, 9437184, 0.8 ) ; Color #900000; 9437184

EndFunction


Function ClearTextures()

	Actor _marisa = GetMarisa()

	ClearTextureSet( _marisa, 1 )
	ClearTextureSet( _marisa, 2 )
	ClearTextureSet( _marisa, 3 )
	ClearTextureSet( _marisa, 4 )

	; Target, Type, Slot
	ClearSingleTexture( _marisa, 2, 1 ) ; Lips
	ClearSingleTexture( _marisa, 2, 2 ) ; Brows
	ClearSingleTexture( _marisa, 1, 2 ) ; Nipples
	ClearSingleTexture( _marisa, 1, 1 ) ; Areolas

EndFunction


Function ApplyTextureSet( Actor Target, Int Type )

	Bool _female = True
	Bool _persist = True

	Int _idx = -1
	Int _slot = 0
	Int _glow = 0
	; Lighter Red w/o ENB
	;Int _color = 12454415 ;12454415 = Red (190 10 15, #BE0A0F); 16316671 = White (16 18 18; #101212)
	; Darker Red for ENB
	Int _color = 11471375 ;11471375 = Red (175 10 15, #AF0A0F); 16316671 = White (16 18 18; #101212) 

	Float _mult = 1.0
	Float _spec = 0.0
	Float _alpha = 1.0
	Float _gloss = 2.0

	String _type = GetTextureType( Type )
	String _path = GetTexturePath( Type )
	String _pathNormal = GetTexturePathNormal( Type )
	String _node = _type + " [ovl" + _slot + "]"

	TextureSet _set = GetTextureSetMarisa( Type )

	NiOverride.AddNodeOverrideTextureSet( Target, _female, _node, _KeyTexSet, _idx, _set, _persist )
	Utility.Wait( 0.01 )

	NiOverride.AddNodeOverrideInt( Target, _female, _node, _KeyColor, _idx, _color, _persist )
	Utility.Wait( 0.01 )

	NiOverride.AddNodeOverrideInt( Target, _female, _node, _KeyGlow, _idx, _glow, _persist )
	Utility.Wait( 0.01 )

	NiOverride.AddNodeOverrideFloat( Target, _female, _node, _KeyMult, _idx, _mult, _persist )
	Utility.Wait( 0.01 )

	NiOverride.AddNodeOverrideFloat( Target, _female, _node, _KeyAlpha, _idx, _alpha, _persist )
	Utility.Wait( 0.01 )

	NiOverride.AddNodeOverrideFloat( Target, _female, _node, _KeyGloss, _idx, _gloss, _persist )
	Utility.Wait( 0.01 )

	NiOverride.AddNodeOverrideFloat( Target, _female, _node, _KeySpec, _idx, _spec, _persist )
	Utility.Wait( 0.01 )

	Debug.Trace( "Marisa - Applied Texture Set " + _node + " = " + _set.GetName() + ", Color: " + _color )

EndFunction


Function ApplySingleTexture( Actor Target, String Texture, Int Type, Int Slot, Int Color, Float Alpha, Float Gloss = 0.0 )

	Bool _female = True
	Bool _persist = True

	Int _idx = -1
	Int _glow = 0

	Float _mult = 1.0
	Float _spec = 0.0

	String _type = GetTextureType( Type )
	String _path = _TexturePath + Texture
	String _node = _type + " [ovl" + Slot + "]"

	NiOverride.AddNodeOverrideString( Target, _female, _node, _KeyTex, 0, _path, _persist )
	Utility.Wait( 0.01 )

	NiOverride.AddNodeOverrideInt( Target, _female, _node, _KeyColor, _idx, Color, _persist )
	Utility.Wait( 0.01 )

	NiOverride.AddNodeOverrideInt( Target, _female, _node, _KeyGlow, _idx, _glow, _persist )
	Utility.Wait( 0.01 )

	NiOverride.AddNodeOverrideFloat( Target, _female, _node, _KeyMult, _idx, _mult, _persist )
	Utility.Wait( 0.01 )

	NiOverride.AddNodeOverrideFloat( Target, _female, _node, _KeyAlpha, _idx, Alpha, _persist )
	Utility.Wait( 0.01 )

	NiOverride.AddNodeOverrideFloat( Target, _female, _node, _KeyGloss, _idx, Gloss, _persist )
	Utility.Wait( 0.01 )

	NiOverride.AddNodeOverrideFloat( Target, _female, _node, _KeySpec, _idx, _spec, _persist )
	Utility.Wait( 0.01 )

	Debug.Trace( "Marisa - Applied Texture for " + _node + " = " + _path + ", Color: " + Color )

EndFunction


Function ClearTextureSet( Actor Target, Int Type )

	Bool _female = True
	Bool _persist = True

	Int _idx = -1
	Int _slot = 0

	String _type = GetTextureType( Type )
	String _node = _type + " [ovl" + _slot + "]"

	TextureSet _set = SkinBlankFemaleMarisa

	NiOverride.AddNodeOverrideTextureSet( Target, _female, _node, _KeyTexSet, _idx, _set, _persist )
    Utility.Wait( 0.01 )

    NiOverride.RemoveNodeOverride( Target, _female, _node, _KeyTexSet, _idx )
    Utility.Wait( 0.01 )

    NiOverride.RemoveNodeOverride( Target, _female, _node, _KeyColor, _idx )
    Utility.Wait( 0.01 )

    NiOverride.RemoveNodeOverride( Target, _female, _node, _KeyGlow, _idx )
    Utility.Wait( 0.01 )

    NiOverride.RemoveNodeOverride( Target, _female, _node, _keyAlpha, _idx )
    Utility.Wait( 0.01 )

    Debug.Trace( "Marisa - Cleared Texture Sets for " + _node)

EndFunction


Function ClearSingleTexture( Actor Target, Int Type, Int Slot )

	Bool _female = True
	Bool _persist = True

	Int _idx = -1

	String _type = GetTextureType( Type )
	String _node = _type + " [ovl" + Slot + "]"
	String _path = _TexturePath + "blank.dds"

    NiOverride.AddNodeOverrideString( Target, _female, _node, _KeyTex, 0, _path, _persist )
    Utility.Wait( 0.01 )

    NiOverride.RemoveNodeOverride( Target, _female, _node, _KeyTex, 0 )
    Utility.Wait( 0.01 )

    NiOverride.RemoveNodeOverride( Target, _female, _node, _KeyTexSet, _idx )
    Utility.Wait( 0.01 )

    NiOverride.RemoveNodeOverride( Target, _female, _node, _KeyColor, _idx )
    Utility.Wait( 0.01 )

    NiOverride.RemoveNodeOverride( Target, _female, _node, _KeyGlow, _idx )
    Utility.Wait( 0.01 )

    NiOverride.RemoveNodeOverride( Target, _female, _node, _keyAlpha, _idx )
    Utility.Wait( 0.01 )

    Debug.Trace( "Marisa - Cleared Texture for " + _node )

EndFunction


 

 

Link to comment
Share on other sites

Sorry, realized there are two relevant pieces of information missing in the code. The _UseFaceGen and _type strings weren't defined. Here's the code for those:

; All used textures have to be relative to "Data\\Textures\\"
String _TexturePath = "actors\\character\\marisa\\"

String _UseFaceGen = "bUseFaceGenPreprocessedHeads:General"

String Function GetTextureType( Int Type )

	String _type = ""

	; Body
	If ( Type == 1 )

		_type = "Body"

	; Face
	ElseIf ( Type == 2 )

		_type = "Face"

	; Hands
	ElseIf ( Type == 3 )

		_type = "Hands"

	; Feet
	ElseIf ( Type == 4 )

		_type = "Feet"

	EndIf

	Return _type

EndFunction


Link to comment
Share on other sites

Here you can find the script source of https://github.com/Rukan/Grimy-Skyrim-Papyrus-Source/blob/master/NiOverride.psc and I cannot see something like that:

NiOverride.AddSkinOverrideInt( Target, _female, _1stPers, Slot, _key, _idx, Color, _persist )
NiOverride.RemoveSkinOverride( Target, _female, _1stPers, Slot, _key, _idx )

Both functions are not a part of SKSE extending script "NiOverride.psc". No idea where does it come from.

Link to comment
Share on other sites

Here you can find the script source of https://github.com/Rukan/Grimy-Skyrim-Papyrus-Source/blob/master/NiOverride.psc and I cannot see something like that:

NiOverride.AddSkinOverrideInt( Target, _female, _1stPers, Slot, _key, _idx, Color, _persist )
NiOverride.RemoveSkinOverride( Target, _female, _1stPers, Slot, _key, _idx )

Both functions are not a part of SKSE extending script "NiOverride.psc". No idea where does it come from.

 

It was probably a typo when posting the initial post. In the script that they did get working, they used AddNodeOverrideInt and RemoveNodeOverride. Both of which are listed on the linked NiOverride source.

Link to comment
Share on other sites

 

 

It was probably a typo when posting the initial post. In the script that they did get working, they used AddNodeOverrideInt and RemoveNodeOverride. Both of which are listed on the linked NiOverride source.

 

 

 

@IsharaMeradin - No, no typo. In the NiOverride version I have, the SkinOverride functions are all readily available. I have my NiOverride from a Modders Package file in the Files section of the RaceMenu mod. It's in the Miscellaneous Files. It contains the latest NiOverride version, and you can create your own standalone package with it if you need it. Just need to make sure you also have the correct skee.dll and skee.ini files from RaceMenu. And, of course, the NiOverride.pex from the same RaceMenu version, otherwise they won't be compatible.

 

Not sure if you guys knew this, but the whole NiOverride thing is now being maintained by the RaceMenu team, that's maybe why you didn't realize there's a more current version available.

 

I would rather use the SkinOverride functions, because then you can override the original skin color instead of using an overlay, which has advantages. But I can't figure out which Slot Mask the FaceGen head uses, so I can't tell NiOverride which slot to color. In NifSkope, The FaceGen file uses slots 130, 143, and 230 for the head, and 131 for the hair. But I'm afraid there are no equivalent Slot Masks. Do either of you have an idea what they might be? Or where I could find that information?

Link to comment
Share on other sites

Regarding body slots, perhaps these will help

https://wiki.nexusmods.com/index.php/Skyrim_bodyparts_number

https://www.creationkit.com/index.php?title=Slot_Masks_-_Armor

 

I am unsure of what the results might be if using 30 instead of 130. The head itself isn't considered "armor" like the body. Thus I am not even sure if any functions designed for armor can affect the head.

Link to comment
Share on other sites

Regarding body slots, perhaps these will help

https://wiki.nexusmods.com/index.php/Skyrim_bodyparts_number

https://www.creationkit.com/index.php?title=Slot_Masks_-_Armor

 

I am unsure of what the results might be if using 30 instead of 130. The head itself isn't considered "armor" like the body. Thus I am not even sure if any functions designed for armor can affect the head.

 

Yes, thanks for posting the links. I've used both pages, and the NIF slots I mentioned are listed on the first page (under "Other body parts..."), but there's no equivalent on the second page. And that's my problem. Without that slot mask equivalent, there's no way to color the contents of the slot. If you just use 30 instead of 130 (i.e. 0x01 as the slot value for AddSkinOverrideInt), whatever is in that head slot will get colored. If there's nothing there, well, to be honest, not sure what would happen then. And I'm afraid there's usually nothing there, at least that's what I found when I listed the contents of all the slots. When I listed the body, hands, and feet slots (i.e. 0x04, 0x08, and 0x80), I clearly saw the textures that were being used, and those were what was being colored.

 

The AddSkinOverrideInt works wonderfully on body, hands, and feet, and it has the advantage that it's not subject to the underwater skin overlay transparency issue you observe if you add tattoos using a mod like SlaveTats. There's a workaround for that issue by changing a couple of options in the skee.ini, but that again might have other consequences. It's what I'm using now to get everything to work well together, and if I don't find a solution for the facegen slots, I'll have to add a comment when I publish the mod so users know what to do about it.

 

Do you know how to just override certain options in the skee.ini without having to override the entire INI file? If that were possible, I could offer users just that if they want it.

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...