diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index d552960..fb99bf5 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; } = false; + public bool ShowPalettePlusImport { get; set; } = true; public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY); public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift); public ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New; diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs index 3434011..23be6c4 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -1,5 +1,6 @@ using Glamourer.Designs; using Glamourer.GameData; +using Glamourer.Interop.PalettePlus; using Glamourer.State; using ImGuiNET; using OtterGui; @@ -8,11 +9,19 @@ using OtterGui.Services; namespace Glamourer.Gui.Customization; -public class CustomizeParameterDrawer(Configuration config) : IService +public class CustomizeParameterDrawer(Configuration config, PaletteImport import) : IService { + private readonly Dictionary _lastData = []; + private string _paletteName = string.Empty; + private CustomizeParameterData _data; + private float _width; + private bool _foundPalette; + + public void Draw(DesignManager designManager, Design design) { using var _ = EnsureSize(); + DrawPaletteImport(designManager, design); foreach (var flag in CustomizeParameterExtensions.RgbFlags) DrawColorInput3(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); @@ -26,11 +35,54 @@ public class CustomizeParameterDrawer(Configuration config) : IService DrawValueInput(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); } - private ImRaii.IEndObject EnsureSize() + private void DrawPaletteImport(DesignManager manager, Design design) { - var iconSize = ImGui.GetTextLineHeight() * 2 + ImGui.GetStyle().ItemSpacing.Y + 4 * ImGui.GetStyle().FramePadding.Y; - var width = 6 * iconSize + 4 * ImGui.GetStyle().ItemInnerSpacing.X; - return ImRaii.ItemWidth(width); + if (!config.ShowPalettePlusImport) + return; + + var spacing = ImGui.GetStyle().ItemInnerSpacing.X; + + if (ImGui.InputTextWithHint("##import", "Palette Name...", ref _paletteName, 256)) + { + _data = design.DesignData.Parameters; + _foundPalette = import.TryRead(_paletteName, ref _data); + } + + ImGui.SameLine(0, spacing); + var value = true; + if (ImGui.Checkbox("Show Import", ref value)) + { + config.ShowPalettePlusImport = false; + config.Save(); + } + + ImGuiUtil.HoverTooltip("Hide the Palette+ Import bar from all designs. You can re-enable it in Glamourers interface settings."); + + var buttonWidth = new Vector2((_width - spacing) / 2, 0); + var tt = _foundPalette + ? $"Apply the imported data from the Palette+ palette [{_paletteName}] to this design." + : $"The palette [{_paletteName}] could not be imported from your Palette+ configuration."; + if (ImGuiUtil.DrawDisabledButton("Apply Import", buttonWidth, tt, !_foundPalette || design.WriteProtected())) + { + // Reload Data in case anything changed since entering text. + _data = design.DesignData.Parameters; + _foundPalette = import.TryRead(_paletteName, ref _data); + _lastData[design] = design.DesignData.Parameters; + foreach (var parameter in CustomizeParameterExtensions.AllFlags) + manager.ChangeCustomizeParameter(design, parameter, _data[parameter]); + } + + ImGui.SameLine(0, spacing); + var enabled = _lastData.TryGetValue(design, out var oldData); + tt = enabled + ? $"Revert to the last set of advanced customization parameters of [{design.Name}] before importing." + : $"You have not imported any data that could be reverted for [{design.Name}]."; + if (ImGuiUtil.DrawDisabledButton("Revert Import", buttonWidth, tt, !enabled || design.WriteProtected())) + { + _lastData.Remove(design); + foreach (var parameter in CustomizeParameterExtensions.AllFlags) + manager.ChangeCustomizeParameter(design, parameter, oldData[parameter]); + } } public void Draw(StateManager stateManager, ActorState state) @@ -145,4 +197,12 @@ public class CustomizeParameterDrawer(Configuration config) : IService => ImGui.GetIO().KeyCtrl ? ImGuiColorEditFlags.Float | ImGuiColorEditFlags.HDR | ImGuiColorEditFlags.NoOptions : ImGuiColorEditFlags.Float | ImGuiColorEditFlags.HDR; + + + private ImRaii.IEndObject EnsureSize() + { + var iconSize = ImGui.GetTextLineHeight() * 2 + ImGui.GetStyle().ItemSpacing.Y + 4 * ImGui.GetStyle().FramePadding.Y; + _width = 6 * iconSize + 4 * ImGui.GetStyle().ItemInnerSpacing.X; + return ImRaii.ItemWidth(_width); + } } diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index ea1fd24..ce80cde 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -94,7 +94,8 @@ public class SettingsTab : ITab Checkbox("Revert Manual Changes on Zone Change", "Restores the old behaviour of reverting your character to its game or automation base whenever you change the zone.", _config.RevertManualChangesOnZoneChange, v => _config.RevertManualChangesOnZoneChange = v); - Checkbox("Enable Advanced Customization Options", "Enable the display and editing of advanced customization options like arbitrary colors.", + Checkbox("Enable Advanced Customization Options", + "Enable the display and editing of advanced customization options like arbitrary colors.", _config.UseAdvancedParameters, v => _config.UseAdvancedParameters = v); ImGui.NewLine(); } @@ -171,6 +172,10 @@ public class SettingsTab : ITab Checkbox("Show Unobtained Item Warnings", "Show information whether you have unlocked all items and customizations in your automated design or not.", _config.ShowUnlockedItemWarnings, v => _config.ShowUnlockedItemWarnings = v); + if (_config.UseAdvancedParameters) + Checkbox("Show Palette+ Import Button", + "Show the import button that allows you to import Palette+ palettes onto a design in the Advanced Customization options section for designs.", + _config.ShowPalettePlusImport, v => _config.ShowPalettePlusImport = v); Checkbox("Debug Mode", "Show the debug tab. Only useful for debugging or advanced use. Not recommended in general.", _config.DebugMode, v => _config.DebugMode = v); ImGui.NewLine(); diff --git a/Glamourer/Interop/PalettePlus/PaletteImport.cs b/Glamourer/Interop/PalettePlus/PaletteImport.cs new file mode 100644 index 0000000..2bd305f --- /dev/null +++ b/Glamourer/Interop/PalettePlus/PaletteImport.cs @@ -0,0 +1,94 @@ +using Dalamud.Plugin; +using Glamourer.GameData; +using Newtonsoft.Json.Linq; +using OtterGui.Services; + +namespace Glamourer.Interop.PalettePlus; + +public class PaletteImport(DalamudPluginInterface pluginInterface) : IService +{ + private string ConfigFile + => Path.Combine(Path.GetDirectoryName(pluginInterface.GetPluginConfigDirectory())!, "PalettePlus.json"); + + public bool TryRead(string name, ref CustomizeParameterData data) + { + if (name.Length == 0) + return false; + + + var path = ConfigFile; + if (!File.Exists(path)) + return false; + + try + { + var text = File.ReadAllText(path); + var obj = JObject.Parse(text); + var palettes = obj["SavedPalettes"]; + + var target = palettes?.Children().FirstOrDefault(c => c["Name"]?.ToObject() == name); + if (target == null) + return false; + + var conditions = target["Conditions"]?.ToObject() ?? 0; + var parameters = target["ShaderParams"]; + if (parameters == null) + return false; + + var discard = 0f; + + Parse("SkinTone", ref data.SkinDiffuse.X, ref data.SkinDiffuse.Y, ref data.SkinDiffuse.Z, ref discard); + Parse("SkinGloss", ref data.SkinSpecular.X, ref data.SkinSpecular.Y, ref data.SkinSpecular.Z, ref discard); + Parse("LipColor", ref data.LipDiffuse.X, ref data.LipDiffuse.Y, ref data.LipDiffuse.Z, ref data.LipDiffuse.W); + Parse("HairColor", ref data.HairDiffuse.X, ref data.HairDiffuse.Y, ref data.HairDiffuse.Z, ref discard); + Parse("HairShine", ref data.HairSpecular.X, ref data.HairSpecular.Y, ref data.HairSpecular.Z, ref discard); + Parse("LeftEyeColor", ref data.LeftEye.X, ref data.LeftEye.Y, ref data.LeftEye.Z, ref discard); + Parse("RaceFeatureColor", ref data.FeatureColor.X, ref data.FeatureColor.Y, ref data.FeatureColor.Z, ref discard); + Parse("FacePaintColor", ref data.DecalColor.X, ref data.DecalColor.Y, ref data.DecalColor.Z, ref data.DecalColor.W); + // Highlights is flag 2. + if ((conditions & 2) == 2) + Parse("HighlightsColor", ref data.HairHighlight.X, ref data.HairHighlight.Y, ref data.HairHighlight.Z, ref discard); + // Heterochromia is flag 1 + if ((conditions & 1) == 1) + Parse("RightEyeColor", ref data.RightEye.X, ref data.RightEye.Y, ref data.RightEye.Z, ref discard); + + ParseSingle("FacePaintOffset", ref data.FacePaintUvOffset); + ParseSingle("FacePaintWidth", ref data.FacePaintUvMultiplier); + ParseSingle("MuscleTone", ref data.MuscleTone); + + return true; + + void Parse(string attribute, ref float x, ref float y, ref float z, ref float w) + { + var node = parameters![attribute]; + if (node == null) + return; + + var xVal = node["X"]?.ToObject(); + var yVal = node["Y"]?.ToObject(); + var zVal = node["Z"]?.ToObject(); + var wVal = node["W"]?.ToObject(); + if (xVal.HasValue) + x = xVal.Value > 0 ? MathF.Sqrt(xVal.Value) : -MathF.Sqrt(-xVal.Value); + if (yVal.HasValue) + y = yVal.Value > 0 ? MathF.Sqrt(yVal.Value) : -MathF.Sqrt(-yVal.Value); + if (zVal.HasValue) + z = zVal.Value > 0 ? MathF.Sqrt(zVal.Value) : -MathF.Sqrt(-zVal.Value); + if (wVal.HasValue) + w = wVal.Value; + } + + void ParseSingle(string attribute, ref float value) + { + var node = parameters![attribute]?.ToObject(); + if (node.HasValue) + value = node.Value; + } + } + catch (Exception ex) + { + Glamourer.Log.Error($"Could not read Palette+ configuration:\n{ex}"); + return false; + } + } +}