Update advanced dyes.

This commit is contained in:
Ottermandias 2024-08-07 17:55:23 +02:00
parent 61cb46a298
commit a1b455d9a5
7 changed files with 157 additions and 48 deletions

View file

@ -1,6 +1,7 @@
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using FFXIVClientStructs.Interop; using FFXIVClientStructs.Interop;
using Glamourer.Designs; using Glamourer.Designs;
@ -27,6 +28,7 @@ public sealed unsafe class AdvancedDyePopup(
private MaterialValueIndex? _drawIndex; private MaterialValueIndex? _drawIndex;
private ActorState _state = null!; private ActorState _state = null!;
private Actor _actor; private Actor _actor;
private ColorRow.Mode _mode;
private byte _selectedMaterial = byte.MaxValue; private byte _selectedMaterial = byte.MaxValue;
private bool _anyChanged; private bool _anyChanged;
private bool _forceFocus; private bool _forceFocus;
@ -96,7 +98,7 @@ public sealed unsafe class AdvancedDyePopup(
return (path, gamePath); return (path, gamePath);
} }
private void DrawTabBar(ReadOnlySpan<Pointer<Texture>> textures, ref bool firstAvailable) private void DrawTabBar(ReadOnlySpan<Pointer<Texture>> textures, ReadOnlySpan<Pointer<Material>> materials, ref bool firstAvailable)
{ {
using var bar = ImRaii.TabBar("tabs"); using var bar = ImRaii.TabBar("tabs");
if (!bar) if (!bar)
@ -106,7 +108,8 @@ public sealed unsafe class AdvancedDyePopup(
for (byte i = 0; i < MaterialService.MaterialsPerModel; ++i) for (byte i = 0; i < MaterialService.MaterialsPerModel; ++i)
{ {
var index = _drawIndex!.Value with { MaterialIndex = i }; var index = _drawIndex!.Value with { MaterialIndex = i };
var available = index.TryGetTexture(textures, out var texture) && directX.TryGetColorTable(*texture, out table); var available = index.TryGetTexture(textures, materials, out var texture, out _mode)
&& directX.TryGetColorTable(*texture, out table);
if (index == preview.LastValueIndex with { RowIndex = 0 }) if (index == preview.LastValueIndex with { RowIndex = 0 })
@ -163,16 +166,16 @@ public sealed unsafe class AdvancedDyePopup(
} }
} }
private void DrawContent(ReadOnlySpan<Pointer<Texture>> textures) private void DrawContent(ReadOnlySpan<Pointer<Texture>> textures, ReadOnlySpan<Pointer<Material>> materials)
{ {
var firstAvailable = true; var firstAvailable = true;
DrawTabBar(textures, ref firstAvailable); DrawTabBar(textures, materials, ref firstAvailable);
if (firstAvailable) if (firstAvailable)
ImGui.TextUnformatted("No Editable Materials available."); ImGui.TextUnformatted("No Editable Materials available.");
} }
private void DrawWindow(ReadOnlySpan<Pointer<Texture>> textures) private void DrawWindow(ReadOnlySpan<Pointer<Texture>> textures, ReadOnlySpan<Pointer<Material>> materials)
{ {
var flags = ImGuiWindowFlags.NoFocusOnAppearing var flags = ImGuiWindowFlags.NoFocusOnAppearing
| ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoCollapse
@ -208,7 +211,7 @@ public sealed unsafe class AdvancedDyePopup(
try try
{ {
if (window) if (window)
DrawContent(textures); DrawContent(textures, materials);
} }
finally finally
{ {
@ -223,8 +226,8 @@ public sealed unsafe class AdvancedDyePopup(
if (!ShouldBeDrawn()) if (!ShouldBeDrawn())
return; return;
if (_drawIndex!.Value.TryGetTextures(actor, out var textures)) if (_drawIndex!.Value.TryGetTextures(actor, out var textures, out var materials))
DrawWindow(textures); DrawWindow(textures, materials);
} }
private void DrawTable(MaterialValueIndex materialIndex, ColorTable.Table table) private void DrawTable(MaterialValueIndex materialIndex, ColorTable.Table table)
@ -340,15 +343,30 @@ public sealed unsafe class AdvancedDyePopup(
v => value.Model.Emissive = v, "E"); v => value.Model.Emissive = v, "E");
ImGui.SameLine(0, spacing.X); ImGui.SameLine(0, spacing.X);
if (_mode is not ColorRow.Mode.Dawntrail)
{
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
applied |= ImGui.DragFloat("##Gloss", ref value.Model.GlossStrength, 0.01f, 0.001f, float.MaxValue, "%.3f G") applied |= ImGui.DragFloat("##Gloss", ref value.Model.GlossStrength, 0.01f, 0.001f, float.MaxValue, "%.3f G")
&& value.Model.GlossStrength > 0; && value.Model.GlossStrength > 0;
ImGuiUtil.HoverTooltip("Change the gloss strength for this row."); ImGuiUtil.HoverTooltip("Change the gloss strength for this row.");
}
else
{
ImGui.Dummy(new Vector2(100 * ImGuiHelpers.GlobalScale, 0));
}
ImGui.SameLine(0, spacing.X); ImGui.SameLine(0, spacing.X);
if (_mode is not ColorRow.Mode.Dawntrail)
{
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
applied |= ImGui.DragFloat("##Specular Strength", ref value.Model.SpecularStrength, 0.01f, float.MinValue, float.MaxValue, "%.3f%% SS"); applied |= ImGui.DragFloat("##Specular Strength", ref value.Model.SpecularStrength, 0.01f, float.MinValue, float.MaxValue,
"%.3f%% SS");
ImGuiUtil.HoverTooltip("Change the specular strength for this row."); ImGuiUtil.HoverTooltip("Change the specular strength for this row.");
}
else
{
ImGui.Dummy(new Vector2(100 * ImGuiHelpers.GlobalScale, 0));
}
ImGui.SameLine(0, spacing.X); ImGui.SameLine(0, spacing.X);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), buttonSize, "Export this row to your clipboard.", false, if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), buttonSize, "Export this row to your clipboard.", false,

View file

@ -1,5 +1,6 @@
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using FFXIVClientStructs.Havok.Animation.Rig;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Glamourer.State; using Glamourer.State;
@ -67,7 +68,8 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
MaterialValueIndex.DrawObjectType.Human => GetTempSlot((Human*)characterBase, slotId), MaterialValueIndex.DrawObjectType.Human => GetTempSlot((Human*)characterBase, slotId),
_ => GetTempSlot((Weapon*)characterBase), _ => GetTempSlot((Weapon*)characterBase),
}; };
UpdateMaterialValues(state, values, drawData, ref baseColorSet); var mode = PrepareColorSet.GetMode(material);
UpdateMaterialValues(state, values, drawData, ref baseColorSet, mode);
if (MaterialService.GenerateNewColorTable(baseColorSet, out var texture)) if (MaterialService.GenerateNewColorTable(baseColorSet, out var texture))
ret = (nint)texture; ret = (nint)texture;
@ -75,7 +77,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
/// <summary> Update and apply the glamourer state of an actor according to the application sources when updated by the game. </summary> /// <summary> Update and apply the glamourer state of an actor according to the application sources when updated by the game. </summary>
private void UpdateMaterialValues(ActorState state, ReadOnlySpan<(uint Key, MaterialValueState Value)> values, CharacterWeapon drawData, private void UpdateMaterialValues(ActorState state, ReadOnlySpan<(uint Key, MaterialValueState Value)> values, CharacterWeapon drawData,
ref ColorTable.Table colorTable) ref ColorTable.Table colorTable, ColorRow.Mode mode)
{ {
var deleteList = _deleteList.Value!; var deleteList = _deleteList.Value!;
deleteList.Clear(); deleteList.Clear();
@ -86,17 +88,17 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
ref var row = ref colorTable[idx.RowIndex]; ref var row = ref colorTable[idx.RowIndex];
var newGame = new ColorRow(row); var newGame = new ColorRow(row);
if (materialValue.EqualGame(newGame, drawData)) if (materialValue.EqualGame(newGame, drawData))
materialValue.Model.Apply(ref row); materialValue.Model.Apply(ref row, mode);
else else
switch (materialValue.Source) switch (materialValue.Source)
{ {
case StateSource.Pending: case StateSource.Pending:
materialValue.Model.Apply(ref row); materialValue.Model.Apply(ref row, mode);
state.Materials.UpdateValue(idx, new MaterialValueState(newGame, materialValue.Model, drawData, StateSource.Manual), state.Materials.UpdateValue(idx, new MaterialValueState(newGame, materialValue.Model, drawData, StateSource.Manual),
out _); out _);
break; break;
case StateSource.IpcPending: case StateSource.IpcPending:
materialValue.Model.Apply(ref row); materialValue.Model.Apply(ref row, mode);
state.Materials.UpdateValue(idx, new MaterialValueState(newGame, materialValue.Model, drawData, StateSource.IpcManual), state.Materials.UpdateValue(idx, new MaterialValueState(newGame, materialValue.Model, drawData, StateSource.IpcManual),
out _); out _);
break; break;
@ -106,7 +108,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
break; break;
case StateSource.Fixed: case StateSource.Fixed:
case StateSource.IpcFixed: case StateSource.IpcFixed:
materialValue.Model.Apply(ref row); materialValue.Model.Apply(ref row, mode);
state.Materials.UpdateValue(idx, new MaterialValueState(newGame, materialValue.Model, drawData, materialValue.Source), state.Materials.UpdateValue(idx, new MaterialValueState(newGame, materialValue.Model, drawData, materialValue.Source),
out _); out _);
break; break;

View file

@ -1,9 +1,11 @@
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using FFXIVClientStructs.Interop; using FFXIVClientStructs.Interop;
using Newtonsoft.Json; using Newtonsoft.Json;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Files.MaterialStructs;
using Penumbra.GameData.Interop; using Penumbra.GameData.Interop;
using CsMaterial = FFXIVClientStructs.FFXIV.Client.Graphics.Render.Material;
namespace Glamourer.Interop.Material; namespace Glamourer.Interop.Material;
@ -75,6 +77,23 @@ public readonly record struct MaterialValueIndex(
return model.IsCharacterBase; return model.IsCharacterBase;
} }
public unsafe bool TryGetTextures(Actor actor, out ReadOnlySpan<Pointer<Texture>> textures, out ReadOnlySpan<Pointer<CsMaterial>> materials)
{
if (!TryGetModel(actor, out var model)
|| SlotIndex >= model.AsCharacterBase->SlotCount
|| model.AsCharacterBase->ColorTableTexturesSpan.Length < (SlotIndex + 1) * MaterialService.MaterialsPerModel)
{
textures = [];
materials = [];
return false;
}
var from = SlotIndex * MaterialService.MaterialsPerModel;
textures = model.AsCharacterBase->ColorTableTexturesSpan.Slice(from, MaterialService.MaterialsPerModel);
materials = model.AsCharacterBase->MaterialsSpan.Slice(from, MaterialService.MaterialsPerModel);
return true;
}
public unsafe bool TryGetTextures(Actor actor, out ReadOnlySpan<Pointer<Texture>> textures) public unsafe bool TryGetTextures(Actor actor, out ReadOnlySpan<Pointer<Texture>> textures)
{ {
if (!TryGetModel(actor, out var model) if (!TryGetModel(actor, out var model)
@ -85,11 +104,12 @@ public readonly record struct MaterialValueIndex(
return false; return false;
} }
textures = model.AsCharacterBase->ColorTableTexturesSpan.Slice(SlotIndex * MaterialService.MaterialsPerModel, var from = SlotIndex * MaterialService.MaterialsPerModel;
MaterialService.MaterialsPerModel); textures = model.AsCharacterBase->ColorTableTexturesSpan.Slice(from, MaterialService.MaterialsPerModel);
return true; return true;
} }
public unsafe bool TryGetTexture(Actor actor, out Texture** texture) public unsafe bool TryGetTexture(Actor actor, out Texture** texture)
{ {
if (TryGetTextures(actor, out var textures)) if (TryGetTextures(actor, out var textures))
@ -99,6 +119,38 @@ public readonly record struct MaterialValueIndex(
return false; return false;
} }
public unsafe bool TryGetTexture(Actor actor, out Texture** texture, out ColorRow.Mode mode)
{
if (TryGetTextures(actor, out var textures, out var materials))
return TryGetTexture(textures, materials, out texture, out mode);
mode = ColorRow.Mode.Dawntrail;
texture = null;
return false;
}
public unsafe bool TryGetTexture(ReadOnlySpan<Pointer<Texture>> textures, ReadOnlySpan<Pointer<CsMaterial>> materials,
out Texture** texture, out ColorRow.Mode mode)
{
mode = MaterialIndex >= materials.Length
? ColorRow.Mode.Dawntrail
: PrepareColorSet.GetMode((MaterialResourceHandle*)materials[MaterialIndex].Value);
if (MaterialIndex >= textures.Length || textures[MaterialIndex].Value == null)
{
texture = null;
return false;
}
fixed (Pointer<Texture>* ptr = textures)
{
texture = (Texture**)ptr + MaterialIndex;
}
return true;
}
public unsafe bool TryGetTexture(ReadOnlySpan<Pointer<Texture>> textures, out Texture** texture) public unsafe bool TryGetTexture(ReadOnlySpan<Pointer<Texture>> textures, out Texture** texture)
{ {
if (MaterialIndex >= textures.Length || textures[MaterialIndex].Value == null) if (MaterialIndex >= textures.Length || textures[MaterialIndex].Value == null)
@ -115,6 +167,7 @@ public readonly record struct MaterialValueIndex(
return true; return true;
} }
public static MaterialValueIndex FromKey(uint key) public static MaterialValueIndex FromKey(uint key)
=> new(key); => new(key);

View file

@ -13,6 +13,12 @@ namespace Glamourer.Interop.Material;
/// <summary> Values are not squared. </summary> /// <summary> Values are not squared. </summary>
public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, float specularStrength, float glossStrength) public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, float specularStrength, float glossStrength)
{ {
public enum Mode
{
Legacy,
Dawntrail,
}
public static readonly ColorRow Empty = new(Vector3.Zero, Vector3.Zero, Vector3.Zero, 0, 0); public static readonly ColorRow Empty = new(Vector3.Zero, Vector3.Zero, Vector3.Zero, 0, 0);
public Vector3 Diffuse = diffuse; public Vector3 Diffuse = diffuse;
@ -22,8 +28,9 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa
public float GlossStrength = glossStrength; public float GlossStrength = glossStrength;
public ColorRow(in ColorTableRow row) public ColorRow(in ColorTableRow row)
: this(Root((Vector3)row.DiffuseColor), Root((Vector3)row.SpecularColor), Root((Vector3)row.EmissiveColor), (float)row.SheenRate, : this(Root((Vector3)row.DiffuseColor), Root((Vector3)row.SpecularColor), Root((Vector3)row.EmissiveColor),
(float)row.Metalness) (float)row.LegacySpecularStrength(),
(float)row.LegacyGloss())
{ } { }
public readonly bool NearEqual(in ColorRow rhs) public readonly bool NearEqual(in ColorRow rhs)
@ -45,7 +52,7 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa
private static float Root(float value) private static float Root(float value)
=> value < 0 ? MathF.Sqrt(-value) : MathF.Sqrt(value); => value < 0 ? MathF.Sqrt(-value) : MathF.Sqrt(value);
public readonly bool Apply(ref ColorTableRow row) public readonly bool Apply(ref ColorTableRow row, Mode mode)
{ {
var ret = false; var ret = false;
var d = Square(Diffuse); var d = Square(Diffuse);
@ -69,22 +76,40 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa
ret = true; ret = true;
} }
if (!((float)row.SheenRate).NearEqual(SpecularStrength)) if (mode is Mode.Legacy)
{ {
row.SheenRate = (Half)SpecularStrength; if (!((float)row.LegacySpecularStrength()).NearEqual(SpecularStrength))
{
row.LegacySpecularStrengthWrite() = (Half)SpecularStrength;
ret = true; ret = true;
} }
if (!((float)row.Metalness).NearEqual(GlossStrength)) if (!((float)row.LegacyGloss()).NearEqual(GlossStrength))
{ {
row.Metalness = (Half)GlossStrength; row.LegacyGlossWrite() = (Half)GlossStrength;
ret = true; ret = true;
} }
}
return ret; return ret;
} }
} }
internal static class ColorTableRowExtensions
{
internal static Half LegacySpecularStrength(this in ColorTableRow row)
=> row[7];
internal static Half LegacyGloss(this in ColorTableRow row)
=> row[3];
internal static ref Half LegacySpecularStrengthWrite(this ref ColorTableRow row)
=> ref row[7];
internal static ref Half LegacyGlossWrite(this ref ColorTableRow row)
=> ref row[3];
}
[JsonConverter(typeof(Converter))] [JsonConverter(typeof(Converter))]
public struct MaterialValueDesign(ColorRow value, bool enabled, bool revert) public struct MaterialValueDesign(ColorRow value, bool enabled, bool revert)
{ {

View file

@ -91,11 +91,12 @@ public sealed unsafe class PrepareColorSet
} }
/// <summary> Assumes the actor is valid. </summary> /// <summary> Assumes the actor is valid. </summary>
public static bool TryGetColorTable(Actor actor, MaterialValueIndex index, out ColorTable.Table table) public static bool TryGetColorTable(Actor actor, MaterialValueIndex index, out ColorTable.Table table, out ColorRow.Mode mode)
{ {
var idx = index.SlotIndex * MaterialService.MaterialsPerModel + index.MaterialIndex; var idx = index.SlotIndex * MaterialService.MaterialsPerModel + index.MaterialIndex;
if (!index.TryGetModel(actor, out var model)) if (!index.TryGetModel(actor, out var model))
{ {
mode = ColorRow.Mode.Dawntrail;
table = default; table = default;
return false; return false;
} }
@ -103,10 +104,12 @@ public sealed unsafe class PrepareColorSet
var handle = (MaterialResourceHandle*)model.AsCharacterBase->Materials[idx]; var handle = (MaterialResourceHandle*)model.AsCharacterBase->Materials[idx];
if (handle == null) if (handle == null)
{ {
mode = ColorRow.Mode.Dawntrail;
table = default; table = default;
return false; return false;
} }
mode = GetMode(handle);
return TryGetColorTable(handle, GetStains(), out table); return TryGetColorTable(handle, GetStains(), out table);
StainIds GetStains() StainIds GetStains()
@ -126,6 +129,14 @@ public sealed unsafe class PrepareColorSet
} }
} }
/// <summary> Get the shader mode of the material. </summary>
public static ColorRow.Mode GetMode(MaterialResourceHandle* handle)
=> handle == null
? ColorRow.Mode.Dawntrail
: handle->ShpkNameSpan.SequenceEqual("characterlegacy.shpk"u8)
? ColorRow.Mode.Legacy
: ColorRow.Mode.Dawntrail;
/// <summary> Get the correct dye table for a material. </summary> /// <summary> Get the correct dye table for a material. </summary>
private static bool GetDyeTable(MaterialResourceHandle* material, out ushort* ptr) private static bool GetDyeTable(MaterialResourceHandle* material, out ushort* ptr)
{ {

View file

@ -75,7 +75,7 @@ public class StateApplier(
} }
} }
/// <inheritdoc cref="ChangeCustomize(ActorData, in CustomizeArray, ActorState?)"/> /// <inheritdoc cref="ChangeCustomize(ActorData,in CustomizeArray,ActorState?)"/>
public ActorData ChangeCustomize(ActorState state, bool apply) public ActorData ChangeCustomize(ActorState state, bool apply)
{ {
var data = GetData(state); var data = GetData(state);
@ -177,7 +177,7 @@ public class StateApplier(
} }
} }
/// <inheritdoc cref="ChangeStain(ActorData,EquipSlot,StainId)"/> /// <inheritdoc cref="ChangeStain(ActorData,EquipSlot,StainIds)"/>
public ActorData ChangeStain(ActorState state, EquipSlot slot, bool apply) public ActorData ChangeStain(ActorState state, EquipSlot slot, bool apply)
{ {
var data = GetData(state); var data = GetData(state);
@ -197,7 +197,7 @@ public class StateApplier(
ChangeOffhand(data, item, stains); ChangeOffhand(data, item, stains);
} }
/// <inheritdoc cref="ChangeWeapon(ActorData,EquipSlot,EquipItem,StainId)"/> /// <inheritdoc cref="ChangeWeapon(ActorData,EquipSlot,EquipItem,StainIds)"/>
public ActorData ChangeWeapon(ActorState state, EquipSlot slot, bool apply, bool onlyGPose) public ActorData ChangeWeapon(ActorState state, EquipSlot slot, bool apply, bool onlyGPose)
{ {
var data = GetData(state); var data = GetData(state);
@ -229,7 +229,7 @@ public class StateApplier(
} }
/// <summary> Change a meta state. </summary> /// <summary> Change a meta state. </summary>
public unsafe void ChangeMetaState(ActorData data, MetaIndex index, bool value) public void ChangeMetaState(ActorData data, MetaIndex index, bool value)
{ {
switch (index) switch (index)
{ {
@ -311,15 +311,15 @@ public class StateApplier(
foreach (var actor in data.Objects.Where(a => a is { IsCharacter: true, Model.IsHuman: true })) foreach (var actor in data.Objects.Where(a => a is { IsCharacter: true, Model.IsHuman: true }))
{ {
if (!index.TryGetTexture(actor, out var texture)) if (!index.TryGetTexture(actor, out var texture, out var mode))
continue; continue;
if (!_directX.TryGetColorTable(*texture, out var table)) if (!_directX.TryGetColorTable(*texture, out var table))
continue; continue;
if (value.HasValue) if (value.HasValue)
value.Value.Apply(ref table[index.RowIndex]); value.Value.Apply(ref table[index.RowIndex], mode);
else if (PrepareColorSet.TryGetColorTable(actor, index, out var baseTable)) else if (PrepareColorSet.TryGetColorTable(actor, index, out var baseTable, out _))
table[index.RowIndex] = baseTable[index.RowIndex]; table[index.RowIndex] = baseTable[index.RowIndex];
else else
continue; continue;
@ -353,11 +353,11 @@ public class StateApplier(
if (!mainKey.TryGetTexture(actor, out var texture)) if (!mainKey.TryGetTexture(actor, out var texture))
continue; continue;
if (!PrepareColorSet.TryGetColorTable(actor, mainKey, out var table)) if (!PrepareColorSet.TryGetColorTable(actor, mainKey, out var table, out var mode))
continue; continue;
foreach (var (key, value) in values) foreach (var (key, value) in values)
value.Model.Apply(ref table[key.RowIndex]); value.Model.Apply(ref table[key.RowIndex], mode);
_directX.ReplaceColorTable(texture, table); _directX.ReplaceColorTable(texture, table);
} }

@ -1 +1 @@
Subproject commit 51bab6dd1bd7d98cc468e8122f410e1c79e3c92d Subproject commit d9486ae54b5a4b61cf74f79ed27daa659eb1ce5b