From 10962cac6c2a1e53f0bc9e939e6a0b0c98ace8e8 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 14 Feb 2024 18:51:48 +0100 Subject: [PATCH] Improve advanced dye stuff. --- Glamourer/Configuration.cs | 1 + Glamourer/Designs/DesignEditor.cs | 69 ++++- Glamourer/Designs/Links/DesignMerger.cs | 11 + Glamourer/Events/DesignChanged.cs | 11 +- .../CustomizationDrawer.Color.cs | 2 +- Glamourer/Gui/DesignQuickBar.cs | 4 +- Glamourer/Gui/Materials/MaterialDrawer.cs | 244 ++++++++++++------ Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 5 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 17 +- Glamourer/Interop/Material/MaterialManager.cs | 16 +- .../Interop/Material/MaterialValueIndex.cs | 11 + .../Interop/Material/MaterialValueManager.cs | 17 +- Glamourer/State/StateApplier.cs | 2 +- Glamourer/State/StateEditor.cs | 32 ++- Glamourer/State/StateManager.cs | 11 +- OtterGui | 2 +- 16 files changed, 342 insertions(+), 113 deletions(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 1d24bf3..676cacc 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -35,6 +35,7 @@ public class Configuration : IPluginConfiguration, ISavable public bool ShowQuickBarInTabs { get; set; } = true; public bool OpenWindowAtStart { get; set; } = false; public bool UseAdvancedParameters { get; set; } = true; + public bool UseAdvancedDyes { get; set; } = false; public bool ShowRevertAdvancedParametersButton { get; set; } = true; public bool ShowPalettePlusImport { get; set; } = true; public bool UseFloatForColors { get; set; } = true; diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index 9640c71..91050ec 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -33,7 +33,7 @@ public class DesignEditor( /// public void ChangeCustomize(object data, CustomizeIndex idx, CustomizeValue value, ApplySettings _ = default) { - var design = (Design)data; + var design = (Design)data; var oldValue = design.DesignData.Customize[idx]; switch (idx) { @@ -96,7 +96,7 @@ public class DesignEditor( public void ChangeCustomizeParameter(object data, CustomizeParameterFlag flag, CustomizeParameterValue value, ApplySettings _ = default) { var design = (Design)data; - var old = design.DesignData.Parameters[flag]; + var old = design.DesignData.Parameters[flag]; if (!design.GetDesignDataRef().Parameters.Set(flag, value)) return; @@ -219,6 +219,68 @@ public class DesignEditor( DesignChanged.Invoke(DesignChanged.Type.Other, design, (metaIndex, false, value)); } + public void ChangeMaterialRevert(Design design, MaterialValueIndex index, bool revert) + { + var materials = design.GetMaterialDataRef(); + if (!materials.TryGetValue(index, out var oldValue)) + return; + + materials.AddOrUpdateValue(index, oldValue with { Revert = revert }); + Glamourer.Log.Debug($"Changed advanced dye value for {index} to {(revert ? "Revert." : "no longer Revert.")}"); + design.LastEdit = DateTimeOffset.UtcNow; + SaveService.QueueSave(design); + DesignChanged.Invoke(DesignChanged.Type.MaterialRevert, design, index); + } + + public void ChangeMaterialValue(Design design, MaterialValueIndex index, ColorRow? row) + { + var materials = design.GetMaterialDataRef(); + if (materials.TryGetValue(index, out var oldValue)) + { + if (!row.HasValue) + { + materials.RemoveValue(index); + Glamourer.Log.Debug($"Removed advanced dye value for {index}."); + } + else if (!row.Value.NearEqual(oldValue.Value)) + { + materials.UpdateValue(index, new MaterialValueDesign(row.Value, oldValue.Enabled, oldValue.Revert), out _); + Glamourer.Log.Debug($"Updated advanced dye value for {index} to new value."); + } + else + { + return; + } + } + else + { + if (!row.HasValue) + return; + if (!materials.TryAddValue(index, new MaterialValueDesign(row.Value, true, false))) + return; + + Glamourer.Log.Debug($"Added new advanced dye value for {index}."); + } + + design.LastEdit = DateTimeOffset.UtcNow; + SaveService.DelaySave(design); + DesignChanged.Invoke(DesignChanged.Type.Material, design, (oldValue, row, index)); + } + + public void ChangeApplyMaterialValue(Design design, MaterialValueIndex index, bool value) + { + var materials = design.GetMaterialDataRef(); + if (!materials.TryGetValue(index, out var oldValue) || oldValue.Enabled == value) + return; + + materials.AddOrUpdateValue(index, oldValue with { Enabled = value }); + Glamourer.Log.Debug($"Changed application of advanced dye for {index} to {value}."); + design.LastEdit = DateTimeOffset.UtcNow; + SaveService.QueueSave(design); + DesignChanged.Invoke(DesignChanged.Type.ApplyMaterial, design, index); + } + + /// public void ApplyDesign(object data, MergedDesign other, ApplySettings _ = default) => ApplyDesign(data, other.Design); @@ -262,7 +324,8 @@ public class DesignEditor( } /// Change a mainhand weapon and either fix or apply appropriate offhand and potentially gauntlets. - private bool ChangeMainhandPeriphery(DesignBase design, EquipItem currentMain, EquipItem currentOff, EquipItem newMain, out EquipItem? newOff, + private bool ChangeMainhandPeriphery(DesignBase design, EquipItem currentMain, EquipItem currentOff, EquipItem newMain, + out EquipItem? newOff, out EquipItem? newGauntlets) { newOff = null; diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index 4b29582..7aa0fdf 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -1,5 +1,6 @@ using Glamourer.Automation; using Glamourer.GameData; +using Glamourer.Interop.Material; using Glamourer.Services; using Glamourer.State; using Glamourer.Unlocks; @@ -46,6 +47,7 @@ public class DesignMerger( ReduceCrests(data, crestFlags, ret, source); ReduceParameters(data, parameterFlags, ret, source); ReduceMods(design as Design, ret, modAssociations); + ReduceMaterials(design, ret); } ApplyFixFlags(ret, fixFlags); @@ -53,6 +55,15 @@ public class DesignMerger( } + private static void ReduceMaterials(DesignBase? design, MergedDesign ret) + { + if (design == null) + return; + var materials = ret.Design.GetMaterialDataRef(); + foreach (var (key, value) in design.Materials.Where(p => p.Item2.Enabled)) + materials.TryAddValue(MaterialValueIndex.FromKey(key), value); + } + private static void ReduceMods(Design? design, MergedDesign ret, bool modAssociations) { if (design == null || !modAssociations) diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 81d56e4..70e9aa6 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -74,10 +74,16 @@ public sealed class DesignChanged() /// An existing design had a customize parameter changed. Data is the old value, the new value and the flag [(CustomizeParameterValue, CustomizeParameterValue, CustomizeParameterFlag)]. Parameter, + /// An existing design had an advanced dye row added, changed, or deleted. Data is the old value, the new value and the index [(ColorRow?, ColorRow?, MaterialValueIndex)]. + Material, + + /// An existing design had an advanced dye rows Revert state changed. Data is the index [MaterialValueIndex]. + MaterialRevert, + /// An existing design changed whether a specific customization is applied. Data is the type of customization [CustomizeIndex]. ApplyCustomize, - /// An existing design changed whether a specific equipment is applied. Data is the slot of the equipment [EquipSlot]. + /// An existing design changed whether a specific equipment piece is applied. Data is the slot of the equipment [EquipSlot]. ApplyEquip, /// An existing design changed whether a specific stain is applied. Data is the slot of the equipment [EquipSlot]. @@ -89,6 +95,9 @@ public sealed class DesignChanged() /// An existing design changed whether a specific customize parameter is applied. Data is the flag for the parameter [CustomizeParameterFlag]. ApplyParameter, + /// An existing design changed whether an advanced dye row is applied. Data is the index [MaterialValueIndex]. + ApplyMaterial, + /// An existing design changed its write protection status. Data is the new value [bool]. WriteProtection, diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs index ff6e0c5..fb50f55 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs @@ -80,7 +80,7 @@ public partial class CustomizationDrawer { var size = ImGui.GetItemRectSize(); ImGui.GetWindowDrawList() - .AddCircleFilled(ImGui.GetItemRectMin() + size / 2, size.X / 4, ImGuiUtil.ContrastColorBW(custom.Color)); + .AddCircleFilled(ImGui.GetItemRectMin() + size / 2, size.X / 4, ImGuiUtil.ContrastColorBw(custom.Color)); } if (i % 8 != 7) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index a1a748f..2521765 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -240,7 +240,7 @@ public sealed class DesignQuickBar : Window, IDisposable if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) { available |= 1; - tooltip = "Left-Click: Revert the advanced customizations of the player character to their game state."; + tooltip = "Left-Click: Revert the advanced customizations and dyes of the player character to their game state."; } if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) @@ -248,7 +248,7 @@ public sealed class DesignQuickBar : Window, IDisposable if (available != 0) tooltip += '\n'; available |= 2; - tooltip += $"Right-Click: Revert the advanced customizations of {_targetIdentifier} to their game state."; + tooltip += $"Right-Click: Revert the advanced customizations and dyes of {_targetIdentifier} to their game state."; } if (available == 0) diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index fb6a4ac..2840dc5 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -10,20 +10,135 @@ using OtterGui; using OtterGui.Services; using Penumbra.GameData.Enums; using Penumbra.GameData.Files; +using Penumbra.GameData.Gui; using Penumbra.GameData.Structs; +using Vortice.DXGI; namespace Glamourer.Gui.Materials; -public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _designManager) : IService +public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _designManager, Configuration _config) : IService { - private static readonly IReadOnlyList Types = - [ - MaterialValueIndex.DrawObjectType.Human, - MaterialValueIndex.DrawObjectType.Mainhand, - MaterialValueIndex.DrawObjectType.Offhand, - ]; + private ActorState? _state; + private EquipSlot _newSlot = EquipSlot.Head; + private int _newMaterialIdx; + private int _newRowIdx; + private MaterialValueIndex _newKey = MaterialValueIndex.Min(); - private ActorState? _state; + public void DrawDesignPanel(Design design) + { + var buttonSize = new Vector2(ImGui.GetFrameHeight()); + using (var table = ImRaii.Table("table", 5, ImGuiTableFlags.RowBg)) + { + if (!table) + return; + + + ImGui.TableSetupColumn("button", ImGuiTableColumnFlags.WidthFixed, buttonSize.X); + ImGui.TableSetupColumn("enabled", ImGuiTableColumnFlags.WidthFixed, buttonSize.X); + ImGui.TableSetupColumn("values", ImGuiTableColumnFlags.WidthFixed, ImGui.GetStyle().ItemInnerSpacing.X * 4 + 3 * buttonSize.X + 220 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("revert", ImGuiTableColumnFlags.WidthFixed, buttonSize.X + ImGui.CalcTextSize("Revertm").X); + ImGui.TableSetupColumn("slot", ImGuiTableColumnFlags.WidthStretch); + + + + for (var i = 0; i < design.Materials.Count; ++i) + { + var (idx, value) = design.Materials[i]; + var key = MaterialValueIndex.FromKey(idx); + var name = key.DrawObject switch + { + MaterialValueIndex.DrawObjectType.Human => ((uint)key.SlotIndex).ToEquipSlot().ToName(), + MaterialValueIndex.DrawObjectType.Mainhand => EquipSlot.MainHand.ToName(), + MaterialValueIndex.DrawObjectType.Offhand => EquipSlot.OffHand.ToName(), + _ => string.Empty, + }; + if (name.Length == 0) + continue; + + name = $"{name} Material #{key.MaterialIndex + 1} Row #{key.RowIndex + 1}"; + using var id = ImRaii.PushId((int)idx); + var deleteEnabled = _config.DeleteDesignModifier.IsActive(); + ImGui.TableNextColumn(); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, + $"Delete this color row.{(deleteEnabled ? string.Empty : $"\nHold {_config.DeleteDesignModifier} to delete.")}", + !deleteEnabled, true)) + { + _designManager.ChangeMaterialValue(design, key, null); + --i; + } + + ImGui.TableNextColumn(); + var enabled = value.Enabled; + if (ImGui.Checkbox("Enabled", ref enabled)) + _designManager.ChangeApplyMaterialValue(design, key, enabled); + + ImGui.TableNextColumn(); + var revert = value.Revert; + using (ImRaii.Disabled(revert)) + { + var row = value.Value; + DrawRow(design, key, row); + } + + ImGui.TableNextColumn(); + + if (ImGui.Checkbox("Revert", ref revert)) + _designManager.ChangeMaterialRevert(design, key, revert); + ImGuiUtil.HoverTooltip( + "If this is checked, Glamourer will try to revert the advanced dye row to its game state instead of applying a specific row."); + + ImGui.TableNextColumn(); + ImGui.TextUnformatted(name); + } + } + + var exists = design.GetMaterialDataRef().TryGetValue(_newKey, out _); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), buttonSize, + exists ? "The selected advanced dye row already exists." : "Add the selected advanced dye row.", exists, true)) + _designManager.ChangeMaterialValue(design, _newKey, ColorRow.Empty); + + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + if (EquipSlotCombo.Draw("##slot", "Choose a slot for an advanced dye row.", ref _newSlot)) + _newKey = _newSlot switch + { + EquipSlot.MainHand => new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Mainhand, 0, (byte)_newMaterialIdx, + (byte)_newRowIdx), + EquipSlot.OffHand => new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Offhand, 0, (byte)_newMaterialIdx, + (byte)_newRowIdx), + _ => new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Human, (byte)_newSlot.ToIndex(), (byte)_newMaterialIdx, + (byte)_newRowIdx), + }; + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + DrawMaterialIdxDrag(); + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + DrawRowIdxDrag(); + } + + private void DrawMaterialIdxDrag() + { + _newMaterialIdx += 1; + ImGui.SetNextItemWidth(ImGui.CalcTextSize("Material #000").X); + if (ImGui.DragInt("##Material", ref _newMaterialIdx, 0.01f, 1, MaterialService.MaterialsPerModel, "Material #%i")) + { + _newMaterialIdx = Math.Clamp(_newMaterialIdx, 1, MaterialService.MaterialsPerModel); + _newKey = _newKey with { MaterialIndex = (byte)(_newMaterialIdx - 1) }; + } + + _newMaterialIdx -= 1; + } + + private void DrawRowIdxDrag() + { + _newRowIdx += 1; + ImGui.SetNextItemWidth(ImGui.CalcTextSize("Row #0000").X); + if (ImGui.DragInt("##Row", ref _newRowIdx, 0.01f, 1, MtrlFile.ColorTable.NumRows, "Row #%i")) + { + _newRowIdx = Math.Clamp(_newRowIdx, 1, MtrlFile.ColorTable.NumRows); + _newKey = _newKey with { RowIndex = (byte)(_newRowIdx - 1) }; + } + + _newRowIdx -= 1; + } public void DrawActorPanel(Actor actor) { @@ -41,20 +156,21 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex()) { var item = model.GetArmor(slot).ToWeapon(0); - DrawSlotMaterials(model, slot.ToName(), item, new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Human, (byte) idx, 0, 0)); + DrawSlotMaterials(model, slot.ToName(), item, new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Human, (byte)idx, 0, 0)); } var (mainhand, offhand, mh, oh) = actor.Model.GetWeapons(actor); if (mainhand.IsWeapon && mainhand.AsCharacterBase->SlotCount > 0) - DrawSlotMaterials(mainhand, EquipSlot.MainHand.ToName(), mh, new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Mainhand, 0, 0, 0)); + DrawSlotMaterials(mainhand, EquipSlot.MainHand.ToName(), mh, + new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Mainhand, 0, 0, 0)); if (offhand.IsWeapon && offhand.AsCharacterBase->SlotCount > 0) - DrawSlotMaterials(offhand, EquipSlot.OffHand.ToName(), oh, new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Offhand, 0, 0, 0)); + DrawSlotMaterials(offhand, EquipSlot.OffHand.ToName(), oh, + new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Offhand, 0, 0, 0)); } private void DrawSlotMaterials(Model model, string name, CharacterWeapon drawData, MaterialValueIndex index) { - var drawnMaterial = 1; for (byte materialIndex = 0; materialIndex < MaterialService.MaterialsPerModel; ++materialIndex) { var texture = model.AsCharacterBase->ColorTableTextures + index.SlotIndex * MaterialService.MaterialsPerModel + materialIndex; @@ -64,11 +180,11 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de if (!DirectXTextureHelper.TryGetColorTable(*texture, out var table)) continue; - using var tree = ImRaii.TreeNode($"{name} Material #{drawnMaterial++}###{name}{materialIndex}"); + using var tree = ImRaii.TreeNode($"{name} Material #{materialIndex + 1}###{name}{materialIndex}"); if (!tree) continue; - DrawMaterial(ref table, drawData, index with { MaterialIndex = materialIndex} ); + DrawMaterial(ref table, drawData, index with { MaterialIndex = materialIndex }); } } @@ -82,6 +198,27 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de } } + private void DrawRow(Design design, MaterialValueIndex index, in ColorRow row) + { + var spacing = ImGui.GetStyle().ItemInnerSpacing; + var tmp = row; + var applied = ImGuiUtil.ColorPicker("##diffuse", "Change the diffuse value for this row.", row.Diffuse, v => tmp.Diffuse = v, "D"); + ImGui.SameLine(0, spacing.X); + applied |= ImGuiUtil.ColorPicker("##specular", "Change the specular value for this row.", row.Specular, v => tmp.Specular = v, "S"); + ImGui.SameLine(0, spacing.X); + applied |= ImGuiUtil.ColorPicker("##emissive", "Change the emissive value for this row.", row.Emissive, v => tmp.Emissive = v, "E"); + ImGui.SameLine(0, spacing.X); + ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); + applied |= ImGui.DragFloat("##Gloss", ref tmp.GlossStrength, 0.01f, 0.001f, float.MaxValue, "%.3f G"); + ImGuiUtil.HoverTooltip("Change the gloss strength for this row."); + ImGui.SameLine(0, spacing.X); + ImGui.SetNextItemWidth(120 * ImGuiHelpers.GlobalScale); + applied |= ImGui.DragFloat("##Specular Strength", ref tmp.SpecularStrength, 0.01f, float.MinValue, float.MaxValue, "%.3f SS"); + ImGuiUtil.HoverTooltip("Change the specular strength for this row."); + if (applied) + _designManager.ChangeMaterialValue(design, index, tmp); + } + private void DrawRow(ref MtrlFile.ColorTable.Row row, CharacterWeapon drawData, MaterialValueIndex index) { using var id = ImRaii.PushId(index.RowIndex); @@ -92,22 +229,33 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de value = new MaterialValueState(internalRow, internalRow, drawData, StateSource.Manual); } - var applied = ImGui.ColorEdit3("Diffuse", ref value.Model.Diffuse, ImGuiColorEditFlags.NoInputs); - ImGui.SameLine(); - applied |= ImGui.ColorEdit3("Specular", ref value.Model.Specular, ImGuiColorEditFlags.NoInputs); - ImGui.SameLine(); - applied |= ImGui.ColorEdit3("Emissive", ref value.Model.Emissive, ImGuiColorEditFlags.NoInputs); - ImGui.SameLine(); + ImGui.AlignTextToFramePadding(); + using (ImRaii.PushFont(UiBuilder.MonoFont)) + { + ImGui.TextUnformatted($"Row {index.RowIndex + 1:D2}"); + } + + ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X * 2); + var applied = ImGuiUtil.ColorPicker("##diffuse", "Change the diffuse value for this row.", value.Model.Diffuse, v => value.Model.Diffuse = v, "D"); + + var spacing = ImGui.GetStyle().ItemInnerSpacing; + ImGui.SameLine(0, spacing.X); + applied |= ImGuiUtil.ColorPicker("##specular", "Change the specular value for this row.", value.Model.Specular, v => value.Model.Specular = v, "S"); + ImGui.SameLine(0, spacing.X); + applied |= ImGuiUtil.ColorPicker("##emissive", "Change the emissive value for this row.", value.Model.Emissive, v => value.Model.Emissive = v, "E"); + ImGui.SameLine(0, spacing.X); ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - applied |= ImGui.DragFloat("Gloss", ref value.Model.GlossStrength, 0.1f); - ImGui.SameLine(); + applied |= ImGui.DragFloat("##Gloss", ref value.Model.GlossStrength, 0.01f, 0.001f, float.MaxValue, "%.3f G") && value.Model.GlossStrength > 0; + ImGuiUtil.HoverTooltip("Change the gloss strength for this row."); + ImGui.SameLine(0, spacing.X); ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - applied |= ImGui.DragFloat("Specular Strength", ref value.Model.SpecularStrength, 0.1f); + 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."); if (applied) _stateManager.ChangeMaterialValue(_state!, index, value, ApplySettings.Manual); if (changed) { - ImGui.SameLine(); + ImGui.SameLine(0, spacing.X); using (ImRaii.PushFont(UiBuilder.IconFont)) { using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FavoriteStarOn.Value()); @@ -115,52 +263,4 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de } } } - - private static readonly IReadOnlyList SlotNames = - [ - "Slot 1", - "Slot 2", - "Slot 3", - "Slot 4", - "Slot 5", - "Slot 6", - "Slot 7", - "Slot 8", - "Slot 9", - "Slot 10", - "Slot 11", - "Slot 12", - "Slot 13", - "Slot 14", - "Slot 15", - "Slot 16", - "Slot 17", - "Slot 18", - "Slot 19", - "Slot 20", - ]; - - private static readonly IReadOnlyList SlotNamesHuman = - [ - "Head", - "Body", - "Hands", - "Legs", - "Feet", - "Earrings", - "Neck", - "Wrists", - "Right Finger", - "Left Finger", - "Slot 11", - "Slot 12", - "Slot 13", - "Slot 14", - "Slot 15", - "Slot 16", - "Slot 17", - "Slot 18", - "Slot 19", - "Slot 20", - ]; } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 968b88c..e1c8356 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -117,9 +117,8 @@ public class ActorPanel( RevertButtons(); - // TODO Materials - //if (ImGui.CollapsingHeader("Material Shit")) - // _materialDrawer.DrawActorPanel(_actor); + if (_config.UseAdvancedDyes && ImGui.CollapsingHeader("Material Shit")) + _materialDrawer.DrawActorPanel(_actor); using var disabled = ImRaii.Disabled(transformationId != 0); if (_state.ModelData.IsHuman) DrawHumanPanel(); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 3cf46fe..168f994 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -7,6 +7,7 @@ using Glamourer.Designs; using Glamourer.GameData; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; +using Glamourer.Gui.Materials; using Glamourer.Interop; using Glamourer.State; using ImGuiNET; @@ -31,7 +32,8 @@ public class DesignPanel( ImportService _importService, MultiDesignPanel _multiDesignPanel, CustomizeParameterDrawer _parameterDrawer, - DesignLinkDrawer _designLinkDrawer) + DesignLinkDrawer _designLinkDrawer, + MaterialDrawer _materials) { private readonly FileDialogManager _fileDialog = new(); @@ -176,21 +178,14 @@ public class DesignPanel( private void DrawMaterialValues() { - if (!_config.UseAdvancedParameters) + if (!_config.UseAdvancedDyes) return; using var h = ImRaii.CollapsingHeader("Advanced Dyes"); if (!h) return; - foreach (var ((key, value), i) in _selector.Selected!.Materials.WithIndex()) - { - using var id = ImRaii.PushId(i); - ImGui.TextUnformatted($"{key:X16}"); - ImGui.SameLine(); - var enabled = value.Enabled; - ImGui.Checkbox("Enabled", ref enabled); - } + _materials.DrawDesignPanel(_selector.Selected!); } private void DrawCustomizeApplication() @@ -384,7 +379,7 @@ public class DesignPanel( DrawCustomize(); DrawEquipment(); DrawCustomizeParameters(); - //DrawMaterialValues(); TODO Materials + DrawMaterialValues(); _designDetails.Draw(); DrawApplicationRules(); _modAssociations.Draw(); diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index b8c33c8..38e45fe 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -18,20 +18,21 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable private readonly StateManager _stateManager; private readonly PenumbraService _penumbra; private readonly ActorManager _actors; + private readonly Configuration _config; private int _lastSlot; private readonly ThreadLocal> _deleteList = new(() => []); - public MaterialManager(PrepareColorSet prepareColorSet, StateManager stateManager, ActorManager actors, PenumbraService penumbra) + public MaterialManager(PrepareColorSet prepareColorSet, StateManager stateManager, ActorManager actors, PenumbraService penumbra, + Configuration config) { _stateManager = stateManager; _actors = actors; _penumbra = penumbra; + _config = config; _event = prepareColorSet; - - // TODO Material - //_event.Subscribe(OnPrepareColorSet, PrepareColorSet.Priority.MaterialManager); + _event.Subscribe(OnPrepareColorSet, PrepareColorSet.Priority.MaterialManager); } public void Dispose() @@ -39,6 +40,9 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable private void OnPrepareColorSet(CharacterBase* characterBase, MaterialResourceHandle* material, ref StainId stain, ref nint ret) { + if (!_config.UseAdvancedDyes) + return; + var actor = _penumbra.GameObjectFromDrawObject(characterBase); var validType = FindType(characterBase, actor, out var type); var (slotId, materialId) = FindMaterial(characterBase, material); @@ -176,7 +180,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable } /// We need to get the temporary set, variant and stain that is currently being set if it is available. - private CharacterWeapon GetTempSlot(Human* human, byte slotId) + private static CharacterWeapon GetTempSlot(Human* human, byte slotId) { if (human->ChangedEquipData == null) return ((Model)human).GetArmor(((uint)slotId).ToEquipSlot()).ToWeapon(0); @@ -188,7 +192,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable /// We need to get the temporary set, variant and stain that is currently being set if it is available. /// Weapons do not change in skeleton id without being reconstructed, so this is not changeable data. /// - private CharacterWeapon GetTempSlot(Weapon* weapon) + private static CharacterWeapon GetTempSlot(Weapon* weapon) { var changedData = *(void**)((byte*)weapon + 0x918); if (changedData == null) diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index 47ddb7a..0facba4 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -2,6 +2,7 @@ using FFXIVClientStructs.Interop; using Glamourer.Interop.Structs; using Newtonsoft.Json; +using Penumbra.GameData.Enums; using Penumbra.GameData.Files; namespace Glamourer.Interop.Material; @@ -150,6 +151,16 @@ public readonly record struct MaterialValueIndex( : this((DrawObjectType)(key >> 24), (byte)(key >> 16), (byte)(key >> 8), (byte)key) { } + public override string ToString() + => DrawObject switch + { + DrawObjectType.Human when SlotIndex < 10 => + $"{((uint)SlotIndex).ToEquipSlot().ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + DrawObjectType.Mainhand when SlotIndex == 0 => $"{EquipSlot.MainHand.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + DrawObjectType.Offhand when SlotIndex == 0 => $"{EquipSlot.OffHand.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + _ => $"{DrawObject} Slot {SlotIndex} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + }; + private class Converter : JsonConverter { public override void WriteJson(JsonWriter writer, MaterialValueIndex value, JsonSerializer serializer) diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index c735182..35b61fc 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -126,16 +126,26 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa } [JsonConverter(typeof(Converter))] -public struct MaterialValueDesign(ColorRow value, bool enabled) +public struct MaterialValueDesign(ColorRow value, bool enabled, bool revert) { public ColorRow Value = value; public bool Enabled = enabled; + public bool Revert = revert; public readonly bool Apply(ref MaterialValueState state) { if (!Enabled) return false; + if (revert) + { + if (state.Model.NearEqual(state.Game)) + return false; + + state.Model = state.Game; + return true; + } + if (state.Model.NearEqual(Value)) return false; @@ -148,6 +158,8 @@ public struct MaterialValueDesign(ColorRow value, bool enabled) public override void WriteJson(JsonWriter writer, MaterialValueDesign value, JsonSerializer serializer) { writer.WriteStartObject(); + writer.WritePropertyName("Revert"); + writer.WriteValue(value.Revert); writer.WritePropertyName("DiffuseR"); writer.WriteValue(value.Value.Diffuse.X); writer.WritePropertyName("DiffuseG"); @@ -180,6 +192,7 @@ public struct MaterialValueDesign(ColorRow value, bool enabled) JsonSerializer serializer) { var obj = JObject.Load(reader); + Set(ref existingValue.Revert, obj["Revert"]?.Value()); Set(ref existingValue.Value.Diffuse.X, obj["DiffuseR"]?.Value()); Set(ref existingValue.Value.Diffuse.Y, obj["DiffuseG"]?.Value()); Set(ref existingValue.Value.Diffuse.Z, obj["DiffuseB"]?.Value()); @@ -235,7 +248,7 @@ public struct MaterialValueState( && Game.NearEqual(rhsRow); public readonly MaterialValueDesign Convert() - => new(Model, true); + => new(Model, true, false); } public readonly struct MaterialValueManager diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 7e38726..1b9c69a 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -278,7 +278,7 @@ public class StateApplier( public unsafe void ChangeMaterialValue(ActorData data, MaterialValueIndex index, ColorRow? value, bool force) { - if (!force && !_config.UseAdvancedParameters) + if (!force && !_config.UseAdvancedDyes) return; foreach (var actor in data.Objects.Where(a => a is { IsCharacter: true, Model.IsHuman: true })) diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 3705d86..f77e75a 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -172,7 +172,8 @@ public class StateEditor( return; var actors = Applier.ChangeMaterialValue(state, index, settings.Source.RequiresChange()); - Glamourer.Log.Verbose($"Set material value in state {state.Identifier.Incognito(null)} from {oldValue} to {newValue.Game}. [Affecting {actors.ToLazyString("nothing")}.]"); + Glamourer.Log.Verbose( + $"Set material value in state {state.Identifier.Incognito(null)} from {oldValue} to {newValue.Game}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChanged.Type.MaterialValue, settings.Source, state, actors, (oldValue, newValue.Game, index)); } @@ -288,13 +289,25 @@ public class StateEditor( if (!value.Enabled) continue; - var idx = MaterialValueIndex.FromKey(key); - // TODO - //if (state.Materials.TryGetValue(idx, out var materialState)) - //{ - // if (!settings.RespectManual || materialState.Source.IsManual()) - // Editor.ChangeMaterialValue(state, idx, new MaterialValueState(materialState.Game, value.Value, materialState.DrawData)); - //} + var idx = MaterialValueIndex.FromKey(key); + var source = settings.Source.SetPending(); + if (state.Materials.TryGetValue(idx, out var materialState)) + { + if (settings.RespectManual && !materialState.Source.IsManual()) + continue; + + if (value.Revert) + Editor.ChangeMaterialValue(state, idx, default, StateSource.Game, out _, settings.Key); + else + Editor.ChangeMaterialValue(state, idx, + new MaterialValueState(materialState.Game, value.Value, materialState.DrawData, source), settings.Source, out _, + settings.Key); + } + else if (!value.Revert) + { + Editor.ChangeMaterialValue(state, idx, new MaterialValueState(ColorRow.Empty, value.Value, CharacterWeapon.Empty, source), + settings.Source, out _, settings.Key); + } } } @@ -321,7 +334,8 @@ public class StateEditor( public void ApplyDesign(object data, DesignBase design, ApplySettings settings) { var merged = settings.MergeLinks && design is Design d - ? merger.Merge(d.AllLinks, ((ActorState)data).ModelData.Customize, ((ActorState)data).BaseData, false, Config.AlwaysApplyAssociatedMods) + ? merger.Merge(d.AllLinks, ((ActorState)data).ModelData.Customize, ((ActorState)data).BaseData, false, + Config.AlwaysApplyAssociatedMods) : new MergedDesign(design); ApplyDesign(data, merged, settings with diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 32d1237..d731a66 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -4,6 +4,7 @@ using Glamourer.Designs.Links; using Glamourer.Events; using Glamourer.GameData; using Glamourer.Interop; +using Glamourer.Interop.Material; using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.Services; @@ -11,6 +12,7 @@ using Penumbra.GameData.Actors; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using System; namespace Glamourer.State; @@ -265,9 +267,16 @@ public sealed class StateManager( var actors = ActorData.Invalid; if (source is not StateSource.Game) + { actors = Applier.ChangeParameters(state, CustomizeParameterExtensions.All, true); + foreach (var (idx, mat) in state.Materials.Values) + Applier.ChangeMaterialValue(actors, MaterialValueIndex.FromKey(idx), mat.Game, true); + } + + state.Materials.Clear(); + Glamourer.Log.Verbose( - $"Reset advanced customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); + $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChanged.Type.Reset, source, state, actors, null); } diff --git a/OtterGui b/OtterGui index 1a187f7..75c5a7b 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 1a187f756f2e8823197bd43db1c3383231f5eaff +Subproject commit 75c5a7b220e7f799f85741288e3de4a20af9bcf4