diff --git a/Glamourer/Designs/FixedDesigns.cs b/Glamourer/Designs/FixedDesigns.cs new file mode 100644 index 0000000..0a45a5c --- /dev/null +++ b/Glamourer/Designs/FixedDesigns.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Logging; +using Glamourer.FileSystem; +using Penumbra.GameData.Enums; + +namespace Glamourer.Designs +{ + public class FixedDesigns : IDisposable + { + public class FixedDesign + { + public string Name; + public Design Design; + public bool Enabled; + + public GlamourerConfig.FixedDesign ToSave() + => new() + { + Name = Name, + Path = Design.FullName(), + Enabled = Enabled, + }; + + public FixedDesign(string name, Design design, bool enabled) + { + Name = name; + Design = design; + Enabled = enabled; + } + } + + public List Data; + public Dictionary EnabledDesigns; + + public bool EnableDesign(FixedDesign design) + { + var changes = !design.Enabled; + if (EnabledDesigns.TryGetValue(design.Name, out var oldDesign)) + { + oldDesign.Enabled = false; + changes = true; + } + else + { + Glamourer.PlayerWatcher.AddPlayerToWatch(design.Name); + } + + EnabledDesigns[design.Name] = design; + design.Enabled = true; + if (Dalamud.Objects.FirstOrDefault(o => o.ObjectKind == ObjectKind.Player && o.Name.ToString() == design.Name) + is Character character) + OnPlayerChange(character); + return changes; + } + + public bool DisableDesign(FixedDesign design) + { + if (!design.Enabled) + return false; + + design.Enabled = false; + EnabledDesigns.Remove(design.Name); + Glamourer.PlayerWatcher.RemovePlayerFromWatch(design.Name); + return true; + } + + public FixedDesigns(DesignManager designs) + { + Data = new List(Glamourer.Config.FixedDesigns.Count); + EnabledDesigns = new Dictionary(Glamourer.Config.FixedDesigns.Count); + Glamourer.PlayerWatcher.PlayerChanged += OnPlayerChange; + var changes = false; + for (var i = 0; i < Glamourer.Config.FixedDesigns.Count; ++i) + { + var save = Glamourer.Config.FixedDesigns[i]; + if (designs.FileSystem.Find(save.Path, out var d) && d is Design design) + { + Data.Add(new FixedDesign(save.Name, design, save.Enabled)); + if (save.Enabled) + changes |= EnableDesign(Data.Last()); + } + else + { + PluginLog.Warning($"{save.Path} does not exist anymore, removing {save.Name} from fixed designs."); + Glamourer.Config.FixedDesigns.RemoveAt(i--); + changes = true; + } + } + + if (changes) + Glamourer.Config.Save(); + } + + private void OnPlayerChange(Character character) + { + var name = character.Name.ToString(); + if (EnabledDesigns.TryGetValue(name, out var design)) + { + PluginLog.Debug("Redrawing {CharacterName} with {DesignName}.", name, design.Design.FullName()); + design.Design.Data.Apply(character); + Glamourer.PlayerWatcher.UpdatePlayerWithoutEvent(character); + Glamourer.Penumbra.RedrawObject(character, RedrawType.WithSettings, false); + } + } + + public void Add(string name, Design design, bool enabled = false) + { + Data.Add(new FixedDesign(name, design, enabled)); + Glamourer.Config.FixedDesigns.Add(Data.Last().ToSave()); + + if (enabled) + EnableDesign(Data.Last()); + + Glamourer.Config.Save(); + } + + public void Remove(FixedDesign design) + { + var idx = Data.IndexOf(design); + if (idx < 0) + return; + + Data.RemoveAt(idx); + Glamourer.Config.FixedDesigns.RemoveAt(idx); + if (design.Enabled) + { + EnabledDesigns.Remove(design.Name); + Glamourer.PlayerWatcher.RemovePlayerFromWatch(design.Name); + } + + Glamourer.Config.Save(); + } + + public void Move(FixedDesign design, int newIdx) + { + if (newIdx < 0) + newIdx = 0; + if (newIdx >= Data.Count) + newIdx = Data.Count - 1; + + var idx = Data.IndexOf(design); + if (idx < 0 || idx == newIdx) + return; + + Data.RemoveAt(idx); + Data.Insert(newIdx, design); + Glamourer.Config.FixedDesigns.RemoveAt(idx); + Glamourer.Config.FixedDesigns.Insert(newIdx, design.ToSave()); + Glamourer.Config.Save(); + } + + public void Dispose() + { + Glamourer.Config.FixedDesigns = Data.Select(d => d.ToSave()).ToList(); + Glamourer.Config.Save(); + } + } +} diff --git a/Glamourer/Main.cs b/Glamourer/Glamourer.cs similarity index 93% rename from Glamourer/Main.cs rename to Glamourer/Glamourer.cs index 9c35ec9..73dc9c3 100644 --- a/Glamourer/Main.cs +++ b/Glamourer/Glamourer.cs @@ -24,7 +24,8 @@ namespace Glamourer public static IPlayerWatcher PlayerWatcher = null!; public static ICustomizationManager Customization = null!; private readonly Interface _interface; - public DesignManager Designs; + public readonly DesignManager Designs; + public readonly FixedDesigns FixedDesigns; public static string Version = string.Empty; @@ -39,6 +40,10 @@ namespace Glamourer Designs = new DesignManager(); Penumbra = new PenumbraAttach(Config.AttachToPenumbra); PlayerWatcher = PlayerWatchFactory.Create(Dalamud.Framework, Dalamud.ClientState, Dalamud.Objects); + FixedDesigns = new FixedDesigns(Designs); + + if (Config.ApplyFixedDesigns) + PlayerWatcher.Enable(); Dalamud.Commands.AddHandler("/glamourer", new CommandInfo(OnGlamourer) { @@ -190,6 +195,7 @@ namespace Glamourer public void Dispose() { + FixedDesigns.Dispose(); Penumbra.Dispose(); PlayerWatcher.Dispose(); _interface.Dispose(); diff --git a/Glamourer/GlamourerConfig.cs b/Glamourer/GlamourerConfig.cs index c948e8f..0e28efd 100644 --- a/Glamourer/GlamourerConfig.cs +++ b/Glamourer/GlamourerConfig.cs @@ -1,24 +1,35 @@ -using Dalamud.Configuration; +using System.Collections.Generic; +using Dalamud.Configuration; namespace Glamourer { public class GlamourerConfig : IPluginConfiguration { + public struct FixedDesign + { + public string Name; + public string Path; + public bool Enabled; + } + public int Version { get; set; } = 1; public const uint DefaultCustomizationColor = 0xFFC000C0; public const uint DefaultStateColor = 0xFF00C0C0; public const uint DefaultEquipmentColor = 0xFF00C000; - public bool FoldersFirst { get; set; } = false; - public bool ColorDesigns { get; set; } = true; - public bool ShowLocks { get; set; } = true; - public bool AttachToPenumbra { get; set; } = true; + public bool FoldersFirst { get; set; } = false; + public bool ColorDesigns { get; set; } = true; + public bool ShowLocks { get; set; } = true; + public bool AttachToPenumbra { get; set; } = true; + public bool ApplyFixedDesigns { get; set; } = true; public uint CustomizationColor { get; set; } = DefaultCustomizationColor; public uint StateColor { get; set; } = DefaultStateColor; public uint EquipmentColor { get; set; } = DefaultEquipmentColor; + public List FixedDesigns { get; set; } = new(); + public void Save() => Dalamud.PluginInterface.SavePluginConfig(this); diff --git a/Glamourer/Gui/Interface.cs b/Glamourer/Gui/Interface.cs index 6f555dc..cb4442c 100644 --- a/Glamourer/Gui/Interface.cs +++ b/Glamourer/Gui/Interface.cs @@ -88,6 +88,7 @@ namespace Glamourer.Gui DrawPlayerTab(); DrawSaves(); + DrawFixedDesignsTab(); DrawConfigTab(); } finally diff --git a/Glamourer/Gui/InterfaceConfig.cs b/Glamourer/Gui/InterfaceConfig.cs index c1a2d3a..54c020d 100644 --- a/Glamourer/Gui/InterfaceConfig.cs +++ b/Glamourer/Gui/InterfaceConfig.cs @@ -54,9 +54,7 @@ namespace Glamourer.Gui } if (ImGui.Button(buttonLabel)) - { Glamourer.Penumbra.Reattach(true); - } if (ImGui.IsItemHovered()) ImGui.SetTooltip( @@ -86,17 +84,25 @@ namespace Glamourer.Gui { cfg.AttachToPenumbra = v; if (v) - { Glamourer.Penumbra.Reattach(true); - } else - { Glamourer.Penumbra.Unattach(); - } }); ImGui.SameLine(); DrawRestorePenumbraButton(); + DrawConfigCheckMark("Apply Fixed Designs", + "Automatically apply fixed designs to characters and redraw them when anything changes.", + cfg.ApplyFixedDesigns, + v => + { + cfg.ApplyFixedDesigns = v; + if (v) + Glamourer.PlayerWatcher.Enable(); + else + Glamourer.PlayerWatcher.Disable(); + }); + ImGui.Dummy(Vector2.UnitY * ImGui.GetTextLineHeightWithSpacing() / 2); DrawColorPicker("Customization Color", "The color for designs that only apply their character customization.", diff --git a/Glamourer/Gui/InterfaceFixedDesigns.cs b/Glamourer/Gui/InterfaceFixedDesigns.cs new file mode 100644 index 0000000..24e520a --- /dev/null +++ b/Glamourer/Gui/InterfaceFixedDesigns.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Dalamud.Interface; +using Glamourer.Designs; +using Glamourer.FileSystem; +using ImGuiNET; + +namespace Glamourer.Gui +{ + internal partial class Interface + { + private const string FixDragDropLabel = "##FixDragDrop"; + + private List? _fullPathCache; + private string _newFixCharacterName = string.Empty; + private string _newFixDesignPath = string.Empty; + private Design? _newFixDesign; + private int _fixDragDropIdx = -1; + + private static unsafe bool IsDropping() + => ImGui.AcceptDragDropPayload(FixDragDropLabel).NativePtr != null; + + private void DrawFixedDesignsTab() + { + using var raii = new ImGuiRaii(); + if (!raii.Begin(() => ImGui.BeginTabItem("Fixed Designs"), ImGui.EndTabItem)) + { + _fullPathCache = null; + _newFixDesign = null; + _newFixDesignPath = string.Empty; + return; + } + + _fullPathCache ??= _plugin.FixedDesigns.Data.Select(d => d.Design.FullName()).ToList(); + + raii.Begin(() => ImGui.BeginTable("##FixedTable", 3), ImGui.EndTable); + + var buttonWidth = 23.5f * ImGuiHelpers.GlobalScale; + + + ImGui.TableSetupColumn("##DeleteColumn", ImGuiTableColumnFlags.WidthFixed, 2 * buttonWidth); + ImGui.TableSetupColumn("Character", ImGuiTableColumnFlags.WidthFixed, 200 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Design", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableHeadersRow(); + var xPos = 0f; + for (var i = 0; i < _fullPathCache.Count; ++i) + { + var path = _fullPathCache[i]; + var name = _plugin.FixedDesigns.Data[i]; + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + raii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing / 2); + raii.PushFont(UiBuilder.IconFont); + if (ImGui.Button($"{FontAwesomeIcon.Trash.ToIconChar()}##{i}")) + { + _fullPathCache.RemoveAt(i); + _plugin.FixedDesigns.Remove(name); + } + + var tmp = name.Enabled; + ImGui.SameLine(); + xPos = ImGui.GetCursorPosX(); + if (ImGui.Checkbox($"##Enabled{i}", ref tmp)) + if (tmp && _plugin.FixedDesigns.EnableDesign(name) + || !tmp && _plugin.FixedDesigns.DisableDesign(name)) + Glamourer.Config.Save(); + raii.PopStyles(); + raii.PopFonts(); + ImGui.TableNextColumn(); + ImGui.Selectable($"{name.Name}##Fix{i}"); + if (ImGui.BeginDragDropSource()) + { + _fixDragDropIdx = i; + ImGui.SetDragDropPayload("##FixDragDrop", IntPtr.Zero, 0); + ImGui.Text($"Dragging {name.Name} ({path})..."); + ImGui.EndDragDropSource(); + } + if (ImGui.BeginDragDropTarget()) + { + if (IsDropping() && _fixDragDropIdx >= 0) + { + var d = _plugin.FixedDesigns.Data[_fixDragDropIdx]; + _plugin.FixedDesigns.Move(d, i); + var p = _fullPathCache[_fixDragDropIdx]; + _fullPathCache.RemoveAt(_fixDragDropIdx); + _fullPathCache.Insert(i, p); + _fixDragDropIdx = -1; + } + ImGui.EndDragDropTarget(); + } + + ImGui.TableNextColumn(); + ImGui.Text(path); + } + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + raii.PushFont(UiBuilder.IconFont); + + ImGui.SetCursorPosX(xPos); + if (_newFixDesign == null || _newFixCharacterName == string.Empty) + { + raii.PushStyle(ImGuiStyleVar.Alpha, 0.5f); + ImGui.Button($"{FontAwesomeIcon.Plus.ToIconChar()}##NewFix"); + raii.PopStyles(); + } + else if (ImGui.Button($"{FontAwesomeIcon.Plus.ToIconChar()}##NewFix")) + { + _fullPathCache.Add(_newFixDesignPath); + _plugin.FixedDesigns.Add(_newFixCharacterName, _newFixDesign, false); + _newFixCharacterName = string.Empty; + _newFixDesignPath = string.Empty; + _newFixDesign = null; + } + + raii.PopFonts(); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); + ImGui.InputTextWithHint("##NewFix", "Enter new Character", ref _newFixCharacterName, 32); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(-1); + if (!raii.Begin(() => ImGui.BeginCombo("##NewFixPath", _newFixDesignPath), ImGui.EndCombo)) + return; + + foreach (var design in _plugin.Designs.FileSystem.Root.AllLeaves(SortMode.Lexicographical).Cast()) + { + var fullName = design.FullName(); + ImGui.SetNextItemWidth(-1); + if (!ImGui.Selectable($"{fullName}##NewFixDesign", fullName == _newFixDesignPath)) + continue; + + _newFixDesignPath = fullName; + _newFixDesign = design; + } + } + } +}