diff --git a/Glamourer.GameData/Customization/CustomizeFlag.cs b/Glamourer.GameData/Customization/CustomizeFlag.cs index b26bd6a..aae9de4 100644 --- a/Glamourer.GameData/Customization/CustomizeFlag.cs +++ b/Glamourer.GameData/Customization/CustomizeFlag.cs @@ -95,4 +95,14 @@ public static class CustomizeFlagExtensions CustomizeFlag.FacePaintColor => CustomizeIndex.FacePaintColor, _ => (CustomizeIndex)byte.MaxValue, }; + + public static bool SetIfDifferent(ref this CustomizeFlag flags, CustomizeFlag flag, bool value) + { + var newValue = value ? flags | flag : flags & ~flag; + if (newValue == flags) + return false; + + flags = newValue; + return true; + } } diff --git a/Glamourer/Api/GlamourerIpc.cs b/Glamourer/Api/GlamourerIpc.cs index 0a245b6..6337895 100644 --- a/Glamourer/Api/GlamourerIpc.cs +++ b/Glamourer/Api/GlamourerIpc.cs @@ -30,7 +30,6 @@ public partial class Glamourer private readonly ObjectTable _objectTable; private readonly DalamudPluginInterface _pluginInterface; - private readonly Glamourer _glamourer; internal ICallGateProvider? ProviderGetAllCustomization; internal ICallGateProvider? ProviderGetAllCustomizationFromCharacter; @@ -44,10 +43,8 @@ public partial class Glamourer internal ICallGateProvider? ProviderRevertCharacter; internal ICallGateProvider? ProviderGetApiVersion; - public GlamourerIpc(Glamourer glamourer, ClientState clientState, ObjectTable objectTable, - DalamudPluginInterface pluginInterface) + public GlamourerIpc(ObjectTable objectTable, DalamudPluginInterface pluginInterface) { - _glamourer = glamourer; _objectTable = objectTable; _pluginInterface = pluginInterface; @@ -214,9 +211,9 @@ public partial class Glamourer if (character == null) return; - var design = Design.CreateTemporaryFromBase64(customization, true, true); - var active = _glamourer._stateManager.GetOrCreateSave(character.Address); - _glamourer._stateManager.ApplyDesign(active, design, false); + //var design = Design.CreateTemporaryFromBase64(customization, true, true); + //var active = _glamourer._stateManager.GetOrCreateSave(character.Address); + //_glamourer._stateManager.ApplyDesign(active, design, false); } private void ApplyOnlyCustomization(string customization, string characterName) @@ -235,9 +232,9 @@ public partial class Glamourer { if (character == null) return; - var design = Design.CreateTemporaryFromBase64(customization, true, false); - var active = _glamourer._stateManager.GetOrCreateSave(character.Address); - _glamourer._stateManager.ApplyDesign(active, design, false); + //var design = Design.CreateTemporaryFromBase64(customization, true, false); + //var active = _glamourer._stateManager.GetOrCreateSave(character.Address); + //_glamourer._stateManager.ApplyDesign(active, design, false); } private void ApplyOnlyEquipment(string customization, string characterName) @@ -256,9 +253,9 @@ public partial class Glamourer { if (character == null) return; - var design = Design.CreateTemporaryFromBase64(customization, false, true); - var active = _glamourer._stateManager.GetOrCreateSave(character.Address); - _glamourer._stateManager.ApplyDesign(active, design, false); + //var design = Design.CreateTemporaryFromBase64(customization, false, true); + //var active = _glamourer._stateManager.GetOrCreateSave(character.Address); + //_glamourer._stateManager.ApplyDesign(active, design, false); } private void Revert(string characterName) @@ -277,9 +274,9 @@ public partial class Glamourer if (character == null) return; - var ident = Actors.FromObject(character, true, false, false); - _glamourer._stateManager.DeleteSave(ident); - _glamourer._penumbra.RedrawObject(character.Address, RedrawType.Redraw); + //var ident = Actors.FromObject(character, true, false, false); + //_glamourer._stateManager.DeleteSave(ident); + //_glamourer._penumbra.RedrawObject(character.Address, RedrawType.Redraw); } private string? GetAllCustomization(Character? character) @@ -287,11 +284,12 @@ public partial class Glamourer if (character == null) return null; - var ident = Actors.FromObject(character, true, false, false); - if (!_glamourer._stateManager.TryGetValue(ident, out var design)) - design = new ActiveDesign(ident, character.Address); - - return design.CreateOldBase64(); + //var ident = Actors.FromObject(character, true, false, false); + //if (!_glamourer._stateManager.TryGetValue(ident, out var design)) + // design = new ActiveDesign(ident, character.Address); + // + //return design.CreateOldBase64(); + return null; } private string? GetAllCustomization(string characterName) diff --git a/Glamourer/Api/PenumbraAttach.cs b/Glamourer/Api/PenumbraAttach.cs index 34f93ce..0026689 100644 --- a/Glamourer/Api/PenumbraAttach.cs +++ b/Glamourer/Api/PenumbraAttach.cs @@ -1,6 +1,7 @@ using System; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Logging; +using Dalamud.Plugin; using Glamourer.Interop; using Penumbra.Api; using Penumbra.Api.Enums; @@ -8,11 +9,17 @@ using Penumbra.Api.Helpers; namespace Glamourer.Api; +public class CommunicatorService +{ + +} + public unsafe class PenumbraAttach : IDisposable { public const int RequiredPenumbraBreakingVersion = 4; public const int RequiredPenumbraFeatureVersion = 15; + private readonly DalamudPluginInterface _pluginInterface; private readonly EventSubscriber _tooltipSubscriber; private readonly EventSubscriber _clickSubscriber; private readonly EventSubscriber _creatingCharacterBase; @@ -25,14 +32,15 @@ public unsafe class PenumbraAttach : IDisposable private readonly EventSubscriber _disposedEvent; public bool Available { get; private set; } - public PenumbraAttach() + public PenumbraAttach(DalamudPluginInterface pi) { - _initializedEvent = Ipc.Initialized.Subscriber(Dalamud.PluginInterface, Reattach); - _disposedEvent = Ipc.Disposed.Subscriber(Dalamud.PluginInterface, Unattach); - _tooltipSubscriber = Ipc.ChangedItemTooltip.Subscriber(Dalamud.PluginInterface); - _clickSubscriber = Ipc.ChangedItemClick.Subscriber(Dalamud.PluginInterface); - _createdCharacterBase = Ipc.CreatedCharacterBase.Subscriber(Dalamud.PluginInterface); - _creatingCharacterBase = Ipc.CreatingCharacterBase.Subscriber(Dalamud.PluginInterface); + _pluginInterface = pi; + _initializedEvent = Ipc.Initialized.Subscriber(pi, Reattach); + _disposedEvent = Ipc.Disposed.Subscriber(pi, Unattach); + _tooltipSubscriber = Ipc.ChangedItemTooltip.Subscriber(pi); + _clickSubscriber = Ipc.ChangedItemClick.Subscriber(pi); + _createdCharacterBase = Ipc.CreatedCharacterBase.Subscriber(pi); + _creatingCharacterBase = Ipc.CreatingCharacterBase.Subscriber(pi); Reattach(); } @@ -88,7 +96,7 @@ public unsafe class PenumbraAttach : IDisposable { Unattach(); - var (breaking, feature) = Ipc.ApiVersions.Subscriber(Dalamud.PluginInterface).Invoke(); + var (breaking, feature) = Ipc.ApiVersions.Subscriber(_pluginInterface).Invoke(); if (breaking != RequiredPenumbraBreakingVersion || feature < RequiredPenumbraFeatureVersion) throw new Exception( $"Invalid Version {breaking}.{feature:D4}, required major Version {RequiredPenumbraBreakingVersion} with feature greater or equal to {RequiredPenumbraFeatureVersion}."); @@ -97,9 +105,9 @@ public unsafe class PenumbraAttach : IDisposable _clickSubscriber.Enable(); _creatingCharacterBase.Enable(); _createdCharacterBase.Enable(); - _drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(Dalamud.PluginInterface); - _cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(Dalamud.PluginInterface); - _redrawSubscriber = Ipc.RedrawObjectByIndex.Subscriber(Dalamud.PluginInterface); + _drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(_pluginInterface); + _cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(_pluginInterface); + _redrawSubscriber = Ipc.RedrawObjectByIndex.Subscriber(_pluginInterface); Available = true; Glamourer.Log.Debug("Glamourer attached to Penumbra."); } diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs new file mode 100644 index 0000000..e7a024c --- /dev/null +++ b/Glamourer/Configuration.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using System.IO; +using Dalamud.Configuration; +using Glamourer.Services; +using Newtonsoft.Json; +using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; + +namespace Glamourer; + +public class Configuration : IPluginConfiguration, ISavable +{ + [JsonIgnore] + private readonly SaveService _saveService; + + public class FixedDesign + { + public string Name = string.Empty; + public string Path = string.Empty; + public uint JobGroups; + 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 UseRestrictedGearProtection { get; set; } = true; + + public bool FoldersFirst { get; set; } = false; + public bool ColorDesigns { get; set; } = true; + public bool ShowLocks { 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() + => _saveService.QueueSave(this); + + public Configuration(SaveService saveService) + { + _saveService = saveService; + Load(); + } + + public void Load() + { + static void HandleDeserializationError(object? sender, ErrorEventArgs errorArgs) + { + Glamourer.Log.Error( + $"Error parsing Configuration at {errorArgs.ErrorContext.Path}, using default or migrating:\n{errorArgs.ErrorContext.Error}"); + errorArgs.ErrorContext.Handled = true; + } + + if (!File.Exists(_saveService.FileNames.ConfigFile)) + return; + + var text = File.ReadAllText(_saveService.FileNames.ConfigFile); + JsonConvert.PopulateObject(text, this, new JsonSerializerSettings + { + Error = HandleDeserializationError, + }); + } + + public string ToFilename(FilenameService fileNames) + => fileNames.ConfigFile; + + public void Save(StreamWriter writer) + { + using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented }; + var serializer = new JsonSerializer { Formatting = Formatting.Indented }; + serializer.Serialize(jWriter, this); + } +} diff --git a/Glamourer/Dalamud.cs b/Glamourer/Dalamud.cs index 65a9c5f..98a7575 100644 --- a/Glamourer/Dalamud.cs +++ b/Glamourer/Dalamud.cs @@ -7,26 +7,43 @@ using Dalamud.Game.Command; using Dalamud.Game.Gui; using Dalamud.IoC; using Dalamud.Plugin; - -// ReSharper disable AutoPropertyCanBeMadeGetOnly.Local +using Microsoft.Extensions.DependencyInjection; namespace Glamourer; -public class Dalamud +public class DalamudServices { - public static void Initialize(DalamudPluginInterface pluginInterface) - => pluginInterface.Create(); + public DalamudServices(DalamudPluginInterface pi) + { + pi.Inject(this); + } + + public void AddServices(IServiceCollection services) + { + services.AddSingleton(PluginInterface); + services.AddSingleton(Commands); + services.AddSingleton(GameData); + services.AddSingleton(ClientState); + services.AddSingleton(GameGui); + services.AddSingleton(Chat); + services.AddSingleton(Framework); + services.AddSingleton(Targets); + services.AddSingleton(Objects); + services.AddSingleton(KeyState); + services.AddSingleton(this); + services.AddSingleton(PluginInterface.UiBuilder); + } // @formatter:off - [PluginService][RequiredVersion("1.0")] public static DalamudPluginInterface PluginInterface { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public static CommandManager Commands { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public static DataManager GameData { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public static ClientState ClientState { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public static GameGui GameGui { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public static ChatGui Chat { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public static Framework Framework { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public static TargetManager Targets { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public static ObjectTable Objects { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public static KeyState KeyState { get; private set; } = null!; + [PluginService][RequiredVersion("1.0")] public DalamudPluginInterface PluginInterface { get; private set; } = null!; + [PluginService][RequiredVersion("1.0")] public CommandManager Commands { get; private set; } = null!; + [PluginService][RequiredVersion("1.0")] public DataManager GameData { get; private set; } = null!; + [PluginService][RequiredVersion("1.0")] public ClientState ClientState { get; private set; } = null!; + [PluginService][RequiredVersion("1.0")] public GameGui GameGui { get; private set; } = null!; + [PluginService][RequiredVersion("1.0")] public ChatGui Chat { get; private set; } = null!; + [PluginService][RequiredVersion("1.0")] public Framework Framework { get; private set; } = null!; + [PluginService][RequiredVersion("1.0")] public TargetManager Targets { get; private set; } = null!; + [PluginService][RequiredVersion("1.0")] public ObjectTable Objects { get; private set; } = null!; + [PluginService][RequiredVersion("1.0")] public KeyState KeyState { get; private set; } = null!; // @formatter:on } diff --git a/Glamourer/Designs/Design.Manager.cs b/Glamourer/Designs/Design.Manager.cs index ac9c529..262b9c1 100644 --- a/Glamourer/Designs/Design.Manager.cs +++ b/Glamourer/Designs/Design.Manager.cs @@ -5,6 +5,7 @@ using System.Linq; using Dalamud.Plugin; using Dalamud.Utility; using Glamourer.Customization; +using Glamourer.Services; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; @@ -21,8 +22,9 @@ public partial class Design public const string DesignFolderName = "designs"; public readonly string DesignFolder; - private readonly FrameworkManager _framework; - private readonly List _designs = new(); + private readonly ItemManager _items; + private readonly SaveService _saveService; + private readonly List _designs = new(); public enum DesignChangeType { @@ -50,9 +52,10 @@ public partial class Design public IReadOnlyList Designs => _designs; - public Manager(DalamudPluginInterface pi, FrameworkManager framework) + public Manager(DalamudPluginInterface pi, SaveService saveService, ItemManager items) { - _framework = framework; + _saveService = saveService; + _items = items; DesignFolder = SetDesignFolder(pi); DesignChange += OnChange; LoadDesigns(); @@ -105,7 +108,7 @@ public partial class Design => Path.Combine(DesignFolder, $"{design.Identifier}.json"); public void SaveDesign(Design design) - => _framework.RegisterDelayed($"{nameof(SaveDesign)}_{design.Identifier}", () => SaveDesignInternal(design)); + => _saveService.QueueSave(design); private void SaveDesignInternal(Design design) { @@ -133,7 +136,7 @@ public partial class Design { var text = File.ReadAllText(file.FullName); var data = JObject.Parse(text); - var design = LoadDesign(data, out var changes); + var design = LoadDesign(_items, data, out var changes); if (design.Identifier.ToString() != Path.GetFileNameWithoutExtension(file.Name)) invalidNames.Add((design, file.FullName)); if (_designs.Any(f => f.Identifier == design.Identifier)) @@ -177,7 +180,7 @@ public partial class Design public Design Create(string name) { - var design = new Design() + var design = new Design(_items) { CreationDate = DateTimeOffset.UtcNow, Identifier = CreateNewGuid(), @@ -297,7 +300,7 @@ public partial class Design public void ChangeEquip(Design design, EquipSlot slot, uint itemId, Lumina.Excel.GeneratedSheets.Item? item = null) { var old = design.Armor(slot); - if (design.SetArmor(slot, itemId, item)) + if (design.SetArmor(_items, slot, itemId, item)) { var n = design.Armor(slot); Glamourer.Log.Debug( @@ -309,8 +312,8 @@ public partial class Design public void ChangeWeapon(Design design, uint itemId, EquipSlot offhand, Lumina.Excel.GeneratedSheets.Item? item = null) { var (old, change, n) = offhand == EquipSlot.OffHand - ? (design.WeaponOff, design.SetOffhand(itemId, item), design.WeaponOff) - : (design.WeaponMain, design.SetMainhand(itemId, item), design.WeaponMain); + ? (design.WeaponOff, design.SetOffhand(_items, itemId, item), design.WeaponOff) + : (design.WeaponMain, design.SetMainhand(_items, itemId, item), design.WeaponMain); if (change) { Glamourer.Log.Debug( @@ -386,13 +389,13 @@ public partial class Design try { var actualName = Path.GetFileName(name); - var design = new Design() + var design = new Design(_items) { CreationDate = DateTimeOffset.UtcNow, Identifier = CreateNewGuid(), Name = actualName, }; - design.MigrateBase64(base64); + design.MigrateBase64(_items, base64); Add(design, $"Migrated old design to {design.Identifier}."); migratedFileSystemPaths.Add(design.Identifier.ToString(), name); ++successes; diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index e38babf..5ec8ee4 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -1,16 +1,17 @@ using System; +using System.IO; using System.Linq; using Glamourer.Customization; -using Glamourer.Util; +using Glamourer.Services; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OtterGui; using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; namespace Glamourer.Designs; -public partial class Design : DesignBase +public partial class Design : DesignBase, ISavable { public const int FileVersion = 1; @@ -69,7 +70,8 @@ public partial class Design : DesignBase } - private Design() + private Design(ItemManager items) + : base(items) { } public JObject JsonSerialize() @@ -142,17 +144,17 @@ public partial class Design : DesignBase return ret; } - public static Design LoadDesign(JObject json, out bool changes) + public static Design LoadDesign(ItemManager items, JObject json, out bool changes) { var version = json[nameof(FileVersion)]?.ToObject() ?? 0; return version switch { - 1 => LoadDesignV1(json, out changes), + 1 => LoadDesignV1(items, json, out changes), _ => throw new Exception("The design to be loaded has no valid Version."), }; } - private static Design LoadDesignV1(JObject json, out bool changes) + private static Design LoadDesignV1(ItemManager items, JObject json, out bool changes) { static string[] ParseTags(JObject json) { @@ -160,7 +162,7 @@ public partial class Design : DesignBase return tags.OrderBy(t => t).Distinct().ToArray(); } - var design = new Design() + var design = new Design(items) { CreationDate = json["CreationDate"]?.ToObject() ?? throw new ArgumentNullException("CreationDate"), Identifier = json["Identifier"]?.ToObject() ?? throw new ArgumentNullException("Identifier"), @@ -169,12 +171,12 @@ public partial class Design : DesignBase Tags = ParseTags(json), }; - changes = LoadEquip(json["Equipment"], design); + changes = LoadEquip(items, json["Equipment"], design); changes |= LoadCustomize(json["Customize"], design); return design; } - private static bool LoadEquip(JToken? equip, Design design) + private static bool LoadEquip(ItemManager items, JToken? equip, Design design) { if (equip == null) return true; @@ -192,7 +194,7 @@ public partial class Design : DesignBase foreach (var slot in EquipSlotExtensions.EqdpSlots) { var (id, stain, apply, applyStain) = ParseItem(slot, equip[slot.ToString()]); - changes |= !design.SetArmor(slot, id); + changes |= !design.SetArmor(items, slot, id); changes |= !design.SetStain(slot, stain); design.SetApplyEquip(slot, apply); design.SetApplyStain(slot, applyStain); @@ -205,11 +207,11 @@ public partial class Design : DesignBase } else { - var id = main["ItemId"]?.ToObject() ?? Glamourer.Items.DefaultSword.RowId; + var id = main["ItemId"]?.ToObject() ?? items.DefaultSword.RowId; var stain = (StainId)(main["Stain"]?.ToObject() ?? 0); var apply = main["Apply"]?.ToObject() ?? false; var applyStain = main["ApplyStain"]?.ToObject() ?? false; - changes |= !design.SetMainhand(id); + changes |= !design.SetMainhand(items, id); changes |= !design.SetStain(EquipSlot.MainHand, stain); design.SetApplyEquip(EquipSlot.MainHand, apply); design.SetApplyStain(EquipSlot.MainHand, applyStain); @@ -226,7 +228,7 @@ public partial class Design : DesignBase var stain = (StainId)(off["Stain"]?.ToObject() ?? 0); var apply = off["Apply"]?.ToObject() ?? false; var applyStain = off["ApplyStain"]?.ToObject() ?? false; - changes |= !design.SetOffhand(id); + changes |= !design.SetOffhand(items, id); changes |= !design.SetStain(EquipSlot.OffHand, stain); design.SetApplyEquip(EquipSlot.OffHand, apply); design.SetApplyStain(EquipSlot.OffHand, applyStain); @@ -259,14 +261,14 @@ public partial class Design : DesignBase return false; } - public void MigrateBase64(string base64) + public void MigrateBase64(ItemManager items, string base64) { var data = MigrateBase64(base64, out var applyEquip, out var applyCustomize, out var writeProtected, out var wet, out var hat, out var visor, out var weapon); - UpdateMainhand(data.MainHand); - UpdateMainhand(data.OffHand); + UpdateMainhand(items, data.MainHand); + UpdateOffhand(items, data.OffHand); foreach (var slot in EquipSlotExtensions.EqdpSlots) - UpdateArmor(slot, data.Equipment[slot], true); + UpdateArmor(items, slot, data.Equipment[slot], true); CharacterData.CustomizeData = data.CustomizeData; ApplyEquip = applyEquip; ApplyCustomize = applyCustomize; @@ -277,10 +279,10 @@ public partial class Design : DesignBase Weapon = weapon; } - public static Design CreateTemporaryFromBase64(string base64, bool customize, bool equip) + public static Design CreateTemporaryFromBase64(ItemManager items, string base64, bool customize, bool equip) { - var ret = new Design(); - ret.MigrateBase64(base64); + var ret = new Design(items); + ret.MigrateBase64(items, base64); if (!customize) ret.ApplyCustomize = 0; if (!equip) @@ -296,4 +298,20 @@ public partial class Design : DesignBase public string CreateOldBase64() => CreateOldBase64(in CharacterData, ApplyEquip, ApplyCustomize, Wetness == QuadBool.True, Hat.ForcedValue, Hat.Enabled, Visor.ForcedValue, Visor.Enabled, Weapon.ForcedValue, Weapon.Enabled, WriteProtected, 1f); + + public string ToFilename(FilenameService fileNames) + => fileNames.DesignFile(this); + + public void Save(StreamWriter writer) + { + using var j = new JsonTextWriter(writer) + { + Formatting = Formatting.Indented, + }; + var obj = JsonSerialize(); + obj.WriteTo(j); + } + + public string LogName(string fileName) + => Path.GetFileNameWithoutExtension(fileName); } diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 843f7cd..f027e49 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -1,5 +1,6 @@ using System; using Glamourer.Customization; +using Glamourer.Services; using Glamourer.Util; using OtterGui.Classes; using OtterGui; @@ -45,14 +46,14 @@ public class DesignBase public CharacterEquip Equipment() => CharacterData.Equipment; - public DesignBase() + public DesignBase(ItemManager items) { - MainHand = Glamourer.Items.DefaultSword.RowId; + MainHand = items.DefaultSword.RowId; (_, CharacterData.MainHand.Set, CharacterData.MainHand.Type, CharacterData.MainHand.Variant, MainhandName, MainhandType) = - Glamourer.Items.Resolve(MainHand, Glamourer.Items.DefaultSword); + items.Resolve(MainHand, items.DefaultSword); OffHand = ItemManager.NothingId(MainhandType.Offhand()); (_, CharacterData.OffHand.Set, CharacterData.OffHand.Type, CharacterData.OffHand.Variant, OffhandName, _) = - Glamourer.Items.Resolve(OffHand, MainhandType); + items.Resolve(OffHand, MainhandType); } public uint ModelId @@ -96,9 +97,9 @@ public class DesignBase return true; } - protected bool SetArmor(EquipSlot slot, uint itemId, Lumina.Excel.GeneratedSheets.Item? item = null) + protected bool SetArmor(ItemManager items, EquipSlot slot, uint itemId, Lumina.Excel.GeneratedSheets.Item? item = null) { - var (valid, set, variant, name) = Glamourer.Items.Resolve(slot, itemId, item); + var (valid, set, variant, name) = items.Resolve(slot, itemId, item); if (!valid) return false; @@ -108,7 +109,7 @@ public class DesignBase protected bool SetArmor(EquipSlot slot, Item item) => SetArmor(slot, item.ModelBase, item.Variant, item.Name, item.ItemId); - protected bool UpdateArmor(EquipSlot slot, CharacterArmor armor, bool force) + protected bool UpdateArmor(ItemManager items, EquipSlot slot, CharacterArmor armor, bool force) { if (!force) switch (slot) @@ -125,19 +126,19 @@ public class DesignBase case EquipSlot.LFinger when CharacterData.LFinger.Value == armor.Value: return false; } - var (valid, id, name) = Glamourer.Items.Identify(slot, armor.Set, armor.Variant); + var (valid, id, name) = items.Identify(slot, armor.Set, armor.Variant); if (!valid) return false; return SetArmor(slot, armor.Set, armor.Variant, name, id); } - protected bool SetMainhand(uint mainId, Lumina.Excel.GeneratedSheets.Item? main = null) + protected bool SetMainhand(ItemManager items, uint mainId, Lumina.Excel.GeneratedSheets.Item? main = null) { if (mainId == MainHand) return false; - var (valid, set, weapon, variant, name, type) = Glamourer.Items.Resolve(mainId, main); + var (valid, set, weapon, variant, name, type) = items.Resolve(mainId, main); if (!valid) return false; @@ -150,16 +151,16 @@ public class DesignBase CharacterData.MainHand.Type = weapon; CharacterData.MainHand.Variant = variant; if (fixOffhand) - SetOffhand(ItemManager.NothingId(type.Offhand())); + SetOffhand(items, ItemManager.NothingId(type.Offhand())); return true; } - protected bool SetOffhand(uint offId, Lumina.Excel.GeneratedSheets.Item? off = null) + protected bool SetOffhand(ItemManager items, uint offId, Lumina.Excel.GeneratedSheets.Item? off = null) { if (offId == OffHand) return false; - var (valid, set, weapon, variant, name, type) = Glamourer.Items.Resolve(offId, MainhandType, off); + var (valid, set, weapon, variant, name, type) = items.Resolve(offId, MainhandType, off); if (!valid) return false; @@ -171,12 +172,12 @@ public class DesignBase return true; } - protected bool UpdateMainhand(CharacterWeapon weapon) + protected bool UpdateMainhand(ItemManager items, CharacterWeapon weapon) { if (weapon.Value == CharacterData.MainHand.Value) return false; - var (valid, id, name, type) = Glamourer.Items.Identify(EquipSlot.MainHand, weapon.Set, weapon.Type, (byte)weapon.Variant); + var (valid, id, name, type) = items.Identify(EquipSlot.MainHand, weapon.Set, weapon.Type, (byte)weapon.Variant); if (!valid || id == MainHand) return false; @@ -190,16 +191,16 @@ public class DesignBase CharacterData.MainHand.Variant = weapon.Variant; CharacterData.MainHand.Stain = weapon.Stain; if (fixOffhand) - SetOffhand(ItemManager.NothingId(type.Offhand())); + SetOffhand(items, ItemManager.NothingId(type.Offhand())); return true; } - protected bool UpdateOffhand(CharacterWeapon weapon) + protected bool UpdateOffhand(ItemManager items, CharacterWeapon weapon) { if (weapon.Value == CharacterData.OffHand.Value) return false; - var (valid, id, name, _) = Glamourer.Items.Identify(EquipSlot.OffHand, weapon.Set, weapon.Type, (byte)weapon.Variant, MainhandType); + var (valid, id, name, _) = items.Identify(EquipSlot.OffHand, weapon.Set, weapon.Type, (byte)weapon.Variant, MainhandType); if (!valid || id == OffHand) return false; diff --git a/Glamourer/Designs/DesignFileSystem.cs b/Glamourer/Designs/DesignFileSystem.cs index a94dee7..b1f66aa 100644 --- a/Glamourer/Designs/DesignFileSystem.cs +++ b/Glamourer/Designs/DesignFileSystem.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; using Dalamud.Plugin; +using Glamourer.Services; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui.Classes; @@ -12,20 +13,20 @@ using OtterGui.Filesystem; namespace Glamourer.Designs; -public sealed class DesignFileSystem : FileSystem, IDisposable +public sealed class DesignFileSystem : FileSystem, IDisposable, ISavable { public static string GetDesignFileSystemFile(DalamudPluginInterface pi) => Path.Combine(pi.GetPluginConfigDirectory(), "sort_order.json"); - public readonly string DesignFileSystemFile; - private readonly FrameworkManager _framework; - private readonly Design.Manager _designManager; + public readonly string DesignFileSystemFile; + private readonly SaveService _saveService; + private readonly Design.Manager _designManager; - public DesignFileSystem(Design.Manager designManager, DalamudPluginInterface pi, FrameworkManager framework) + public DesignFileSystem(Design.Manager designManager, DalamudPluginInterface pi, SaveService saveService) { DesignFileSystemFile = GetDesignFileSystemFile(pi); _designManager = designManager; - _framework = framework; + _saveService = saveService; _designManager.DesignChange += OnDataChange; Changed += OnChange; Reload(); @@ -34,7 +35,7 @@ public sealed class DesignFileSystem : FileSystem, IDisposable private void Reload() { if (Load(new FileInfo(DesignFileSystemFile), _designManager.Designs, DesignToIdentifier, DesignToName)) - SaveFilesystem(); + _saveService.ImmediateSave(this); Glamourer.Log.Debug("Reloaded design filesystem."); } @@ -71,18 +72,9 @@ public sealed class DesignFileSystem : FileSystem, IDisposable private void OnChange(FileSystemChangeType type, IPath _1, IPath? _2, IPath? _3) { if (type != FileSystemChangeType.Reload) - SaveFilesystem(); + _saveService.QueueSave(this); } - private void SaveFilesystem() - { - SaveToFile(new FileInfo(DesignFileSystemFile), SaveDesign, true); - Glamourer.Log.Verbose("Saved design filesystem."); - } - - public void Save() - => _framework.RegisterDelayed(nameof(SaveFilesystem), SaveFilesystem); - private void OnDataChange(Design.Manager.DesignChangeType type, Design design, object? data) { switch (type) @@ -176,4 +168,12 @@ public sealed class DesignFileSystem : FileSystem, IDisposable Glamourer.Log.Error($"Could not migrate old folder paths to new version:\n{ex}"); } } + + public string ToFilename(FilenameService fileNames) + => fileNames.DesignFileSystem; + + public void Save(StreamWriter writer) + { + SaveToFile(writer, SaveDesign, true); + } } diff --git a/Glamourer/DrawObjectManager.cs b/Glamourer/DrawObjectManager.cs index 2bcee68..47478c7 100644 --- a/Glamourer/DrawObjectManager.cs +++ b/Glamourer/DrawObjectManager.cs @@ -3,9 +3,8 @@ using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.Api; using Glamourer.Customization; using Glamourer.Interop; +using Glamourer.Services; using Glamourer.State; -using Glamourer.Util; -using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using CustomizeData = Penumbra.GameData.Structs.CustomizeData; @@ -15,12 +14,12 @@ namespace Glamourer; public class DrawObjectManager : IDisposable { private readonly ItemManager _items; - private readonly ActorManager _actors; + private readonly ActorService _actors; private readonly ActiveDesign.Manager _manager; private readonly Interop.Interop _interop; private readonly PenumbraAttach _penumbra; - public DrawObjectManager(ItemManager items, ActorManager actors, ActiveDesign.Manager manager, Interop.Interop interop, + public DrawObjectManager(ItemManager items, ActorService actors, ActiveDesign.Manager manager, Interop.Interop interop, PenumbraAttach penumbra) { _items = items; @@ -54,7 +53,7 @@ public class DrawObjectManager : IDisposable if (gameObject.ModelId != modelId) return; - var identifier = _actors.FromObject((GameObject*)gameObjectPtr, out _, true, true, false); + var identifier = _actors.AwaitedService.FromObject((GameObject*)gameObjectPtr, out _, true, true, false); if (!identifier.IsValid || !_manager.TryGetValue(identifier, out var design)) return; @@ -75,7 +74,7 @@ public class DrawObjectManager : IDisposable foreach (var slot in EquipSlotExtensions.EquipmentSlots) { (_, equip[slot]) = - Glamourer.Items.ResolveRestrictedGear(saveEquip[slot], slot, customize.Race, customize.Gender); + _items.ResolveRestrictedGear(saveEquip[slot], slot, customize.Race, customize.Gender); } } } diff --git a/Glamourer/Fixed/FixedCondition.cs b/Glamourer/Fixed/FixedCondition.cs index e864ada..2a71299 100644 --- a/Glamourer/Fixed/FixedCondition.cs +++ b/Glamourer/Fixed/FixedCondition.cs @@ -17,15 +17,15 @@ public struct FixedCondition public bool Check(Actor actor) { - if ((_data & (_territoryFlag | _jobFlag)) == 0) - return true; - - if ((_data & _territoryFlag) != 0) - return Dalamud.ClientState.TerritoryType == (ushort)_data; - - if (actor && GameData.JobGroups(Dalamud.GameData).TryGetValue((ushort)_data, out var group) && group.Fits(actor.Job)) - return true; - + //if ((_data & (_territoryFlag | _jobFlag)) == 0) + // return true; + // + //if ((_data & _territoryFlag) != 0) + // return Dalamud.ClientState.TerritoryType == (ushort)_data; + // + //if (actor && GameData.JobGroups(Dalamud.GameData).TryGetValue((ushort)_data, out var group) && group.Fits(actor.Job)) + // return true; + // return true; } diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 3f79bd5..0179f46 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -1,29 +1,15 @@ -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using Dalamud.Game.Command; -using Dalamud.Interface.Windowing; +using System.Reflection; using Dalamud.Plugin; -using Glamourer.Api; -using Glamourer.Customization; -using Glamourer.Designs; using Glamourer.Gui; -using Glamourer.Interop; -using Glamourer.State; -using Glamourer.Util; +using Glamourer.Services; +using Microsoft.Extensions.DependencyInjection; using OtterGui.Classes; using OtterGui.Log; -using Penumbra.GameData.Actors; -using FixedDesigns = Glamourer.State.FixedDesigns; namespace Glamourer; public partial class Glamourer : IDalamudPlugin { - private const string HelpString = "[Copy|Apply|Save],[Name or PlaceHolder],"; - private const string MainCommandString = "/glamourer"; - private const string ApplyCommandString = "/glamour"; - public string Name => "Glamourer"; @@ -33,73 +19,20 @@ public partial class Glamourer : IDalamudPlugin Assembly.GetExecutingAssembly().GetCustomAttribute()?.InformationalVersion ?? "Unknown"; - public static GlamourerConfig Config = null!; - public static Logger Log = null!; - public static ActorManager Actors = null!; - public static ICustomizationManager Customization = null!; - public static RedrawManager RedrawManager = null!; - public static ItemManager Items = null!; - public readonly FixedDesigns FixedDesigns; - - private readonly Interop.Interop _interop; - private readonly PenumbraAttach _penumbra; - private readonly ObjectManager _objectManager; - private readonly Design.Manager _designManager; - private readonly ActiveDesign.Manager _stateManager; - private readonly DrawObjectManager _drawObjectManager; - private readonly DesignFileSystem _fileSystem; - private readonly FrameworkManager _framework; - private readonly WindowSystem _windowSystem = new("Glamourer"); - private readonly Interface _interface; - - //public readonly DesignManager Designs; - - //public static RevertableDesigns RevertableDesigns = new(); - public readonly GlamourerIpc Ipc; + public static readonly Logger Log = new(); + public static ChatService ChatService { get; private set; } = null!; + private readonly ServiceProvider _services; public Glamourer(DalamudPluginInterface pluginInterface) { try { - Dalamud.Initialize(pluginInterface); - Log = new Logger(); - _framework = new FrameworkManager(Dalamud.Framework, Log); - _interop = new Interop.Interop(); - _penumbra = new PenumbraAttach(); - - Items = new ItemManager(Dalamud.PluginInterface, Dalamud.GameData); - Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData); - - Backup.CreateBackup(pluginInterface.ConfigDirectory, BackupFiles(Dalamud.PluginInterface)); - Config = GlamourerConfig.Load(); - - _objectManager = new ObjectManager(Dalamud.Framework, Dalamud.ClientState, Dalamud.Objects); - Actors = new ActorManager(Dalamud.PluginInterface, Dalamud.Objects, Dalamud.ClientState, Dalamud.Framework, Dalamud.GameData, - Dalamud.GameGui, i => (short)_penumbra.CutsceneParent(i)); - - - _designManager = new Design.Manager(Dalamud.PluginInterface, _framework); - _fileSystem = new DesignFileSystem(_designManager, Dalamud.PluginInterface, _framework); - FixedDesigns = new FixedDesigns(); - _stateManager = new ActiveDesign.Manager(Actors, _objectManager, _interop, _penumbra); - - //GlamourerIpc = new GlamourerIpc(Dalamud.ClientState, Dalamud.Objects, Dalamud.PluginInterface); - RedrawManager = new RedrawManager(FixedDesigns, _stateManager); - _drawObjectManager = new DrawObjectManager(Items, Actors, _stateManager, _interop, _penumbra); - - Dalamud.Commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) - { - HelpMessage = "Open or close the Glamourer window.", - }); - Dalamud.Commands.AddHandler(ApplyCommandString, new CommandInfo(OnGlamour) - { - HelpMessage = $"Use Glamourer Functions: {HelpString}", - }); - - _interface = new Interface(Dalamud.PluginInterface, Items, _stateManager, _designManager, _fileSystem, _objectManager); - _windowSystem.AddWindow(_interface); - Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw; - Ipc = new GlamourerIpc(this, Dalamud.ClientState, Dalamud.Objects, Dalamud.PluginInterface); + _services = ServiceManager.CreateProvider(pluginInterface, Log); + ChatService = _services.GetRequiredService(); + _services.GetRequiredService(); + _services.GetRequiredService(); + _services.GetRequiredService(); + _services.GetRequiredService(); } catch { @@ -111,25 +44,9 @@ public partial class Glamourer : IDalamudPlugin public void Dispose() { - Ipc?.Dispose(); - _drawObjectManager?.Dispose(); - RedrawManager?.Dispose(); - if (_windowSystem != null) - Dalamud.PluginInterface.UiBuilder.Draw -= _windowSystem.Draw; - _interface?.Dispose(); - _fileSystem?.Dispose(); - //GlamourerIpc.Dispose(); - _interop?.Dispose(); - _penumbra?.Dispose(); - _framework?.Dispose(); - Items?.Dispose(); - Dalamud.Commands.RemoveHandler(ApplyCommandString); - Dalamud.Commands.RemoveHandler(MainCommandString); + _services?.Dispose(); } - public void OnGlamourer(string command, string arguments) - => _interface.Toggle(); - //private static GameObject? GetPlayer(string name) //{ // var lowerName = name.ToLowerInvariant(); @@ -262,25 +179,4 @@ public partial class Glamourer : IDalamudPlugin // return; //} } - - // Collect all relevant files for glamourer configuration. - private static IReadOnlyList BackupFiles(DalamudPluginInterface pi) - { - var list = new List(16) - { - pi.ConfigFile, - new(DesignFileSystem.GetDesignFileSystemFile(pi)), - }; - - var configDir = Dalamud.PluginInterface.ConfigDirectory; - if (Directory.Exists(configDir.FullName)) - { - list.Add(new FileInfo(Path.Combine(configDir.FullName, "Designs.json"))); // migration - var designDir = new DirectoryInfo(Path.Combine(configDir.FullName, Design.Manager.DesignFolderName)); - if (designDir.Exists) - list.AddRange(designDir.EnumerateFiles("*.json", SearchOption.TopDirectoryOnly)); - } - - return list; - } } diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index dae6aa5..5532cb6 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -86,6 +86,7 @@ + diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs index 47e185c..9b5fe17 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs @@ -19,8 +19,8 @@ public partial class CustomizationDrawer ImGui.SameLine(); using var group = ImRaii.Group(); DrawRaceCombo(); - var gender = Glamourer.Customization.GetName(CustomName.Gender); - var clan = Glamourer.Customization.GetName(CustomName.Clan); + var gender = _service.AwaitedService.GetName(CustomName.Gender); + var clan = _service.AwaitedService.GetName(CustomName.Clan); ImGui.TextUnformatted($"{gender} & {clan}"); } @@ -39,20 +39,20 @@ public partial class CustomizationDrawer if (!ImGuiUtil.DrawDisabledButton(icon.ToIconString(), _framedIconSize, string.Empty, icon == FontAwesomeIcon.MarsDouble, true)) return; - Changed |= _customize.ChangeGender(CharacterEquip.Null, _customize.Gender is Gender.Male ? Gender.Female : Gender.Male); + Changed |= _customize.ChangeGender(CharacterEquip.Null, _customize.Gender is Gender.Male ? Gender.Female : Gender.Male, _items, _service.AwaitedService); } private void DrawRaceCombo() { ImGui.SetNextItemWidth(_raceSelectorWidth); - using var combo = ImRaii.Combo("##subRaceCombo", _customize.ClanName()); + using var combo = ImRaii.Combo("##subRaceCombo", _customize.ClanName(_service.AwaitedService)); if (!combo) return; foreach (var subRace in Enum.GetValues().Skip(1)) // Skip Unknown { - if (ImGui.Selectable(CustomizeExtensions.ClanName(subRace, _customize.Gender), subRace == _customize.Clan)) - Changed |= _customize.ChangeRace(CharacterEquip.Null, subRace); + if (ImGui.Selectable(CustomizeExtensions.ClanName(_service.AwaitedService, subRace, _customize.Gender), subRace == _customize.Clan)) + Changed |= _customize.ChangeRace(CharacterEquip.Null, subRace, _items, _service.AwaitedService); } } } diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index 2276bb6..3d09e58 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -26,7 +26,7 @@ public partial class CustomizationDrawer custom = _set.Data(index, 0); } - var icon = Glamourer.Customization.GetIcon(custom!.Value.IconId); + var icon = _service.AwaitedService.GetIcon(custom!.Value.IconId); if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize)) ImGui.OpenPopup(IconSelectorPopup); ImGuiUtil.HoverIconTooltip(icon, _iconSize); @@ -77,13 +77,12 @@ public partial class CustomizationDrawer if (!popup) return; - var ret = false; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) .Push(ImGuiStyleVar.FrameRounding, 0); for (var i = 0; i < _currentCount; ++i) { var custom = _set.Data(_currentIndex, i, _customize.Face); - var icon = Glamourer.Customization.GetIcon(custom.IconId); + var icon = _service.AwaitedService.GetIcon(custom.IconId); using (var _ = ImRaii.Group()) { if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize)) @@ -134,8 +133,8 @@ public partial class CustomizationDrawer var enabled = _customize.Get(featureIdx) != CustomizeValue.Zero; var feature = _set.Data(featureIdx, 0, _customize.Face); var icon = featureIdx == CustomizeIndex.LegacyTattoo - ? _legacyTattoo ?? Glamourer.Customization.GetIcon(feature.IconId) - : Glamourer.Customization.GetIcon(feature.IconId); + ? _legacyTattoo ?? _service.AwaitedService.GetIcon(feature.IconId) + : _service.AwaitedService.GetIcon(feature.IconId); if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X, Vector4.Zero, enabled ? Vector4.One : _redTint)) { diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index 02fc1e5..fa90d55 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -4,6 +4,7 @@ using System.Reflection; using System.Runtime.InteropServices; using Dalamud.Plugin; using Glamourer.Customization; +using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Raii; @@ -40,8 +41,13 @@ public partial class CustomizationDrawer : IDisposable private float _comboSelectorSize; private float _raceSelectorWidth; - public CustomizationDrawer(DalamudPluginInterface pi) + private readonly CustomizationService _service; + private readonly ItemManager _items; + + public CustomizationDrawer(DalamudPluginInterface pi, CustomizationService service, ItemManager items) { + _service = service; + _items = items; _legacyTattoo = GetLegacyTattooIcon(pi); unsafe { @@ -120,7 +126,7 @@ public partial class CustomizationDrawer : IDisposable try { DrawRaceGenderSelector(); - _set = Glamourer.Customization.GetList(_customize.Clan, _customize.Gender); + _set = _service.AwaitedService.GetList(_customize.Clan, _customize.Gender); foreach (var id in _set.Order[CharaMakeParams.MenuType.Percentage]) PercentageSelector(id); diff --git a/Glamourer/Gui/Designs/DesignFileSystemSelector.cs b/Glamourer/Gui/Designs/DesignFileSystemSelector.cs index a3d4789..17c110b 100644 --- a/Glamourer/Gui/Designs/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Designs/DesignFileSystemSelector.cs @@ -1,4 +1,8 @@ -using Glamourer.Designs; +using System.Numerics; +using Dalamud.Game.ClientState.Keys; +using Dalamud.Interface; +using Glamourer.Designs; +using OtterGui; using OtterGui.FileSystem.Selector; namespace Glamourer.Gui.Designs; @@ -10,11 +14,12 @@ public sealed class DesignFileSystemSelector : FileSystemSelector _weaponCombo; - public EquipmentDrawer(ItemManager items) + public EquipmentDrawer(DataManager gameData, ItemManager items) { _items = items; _stainData = items.Stains; _stainCombo = new FilterComboColors(140, _stainData.Data.Prepend(new KeyValuePair(0, ("None", 0, false)))); - _itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(items, e)).ToArray(); + _itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e)).ToArray(); _weaponCombo = new Dictionary<(FullEquipType, EquipSlot), WeaponCombo>(FullEquipTypeExtensions.WeaponTypes.Count * 2); foreach (var type in Enum.GetValues()) { if (type.ToSlot() is EquipSlot.MainHand) - _weaponCombo.TryAdd((type, EquipSlot.MainHand), new WeaponCombo(items, type, EquipSlot.MainHand)); + _weaponCombo.TryAdd((type, EquipSlot.MainHand), new WeaponCombo(gameData, items, type, EquipSlot.MainHand)); else if (type.ToSlot() is EquipSlot.OffHand) - _weaponCombo.TryAdd((type, EquipSlot.OffHand), new WeaponCombo(items, type, EquipSlot.OffHand)); + _weaponCombo.TryAdd((type, EquipSlot.OffHand), new WeaponCombo(gameData, items, type, EquipSlot.OffHand)); var offhand = type.Offhand(); if (offhand is not FullEquipType.Unknown && !_weaponCombo.ContainsKey((offhand, EquipSlot.OffHand))) - _weaponCombo.TryAdd((offhand, EquipSlot.OffHand), new WeaponCombo(items, type, EquipSlot.OffHand)); + _weaponCombo.TryAdd((offhand, EquipSlot.OffHand), new WeaponCombo(gameData, items, type, EquipSlot.OffHand)); } - _weaponCombo.Add((FullEquipType.Unknown, EquipSlot.MainHand), new WeaponCombo(items, FullEquipType.Unknown, EquipSlot.MainHand)); + _weaponCombo.Add((FullEquipType.Unknown, EquipSlot.MainHand), new WeaponCombo(gameData, items, FullEquipType.Unknown, EquipSlot.MainHand)); } private string VerifyRestrictedGear(Item gear, EquipSlot slot, Gender gender, Race race) diff --git a/Glamourer/Gui/Equipment/ItemCombo.cs b/Glamourer/Gui/Equipment/ItemCombo.cs index 4fe4854..4601578 100644 --- a/Glamourer/Gui/Equipment/ItemCombo.cs +++ b/Glamourer/Gui/Equipment/ItemCombo.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using System.Linq; -using Glamourer.Util; +using Dalamud.Data; +using Glamourer.Designs; +using Glamourer.Services; using ImGuiNET; using Lumina.Excel.GeneratedSheets; using OtterGui; @@ -17,10 +19,10 @@ public sealed class ItemCombo : FilterComboCache public readonly string Label; private uint _currentItem; - public ItemCombo(ItemManager items, EquipSlot slot) + public ItemCombo(DataManager gameData, ItemManager items, EquipSlot slot) : base(() => GetItems(items, slot)) { - Label = GetLabel(slot); + Label = GetLabel(gameData, slot); _currentItem = ItemManager.NothingId(slot); } @@ -66,9 +68,9 @@ public sealed class ItemCombo : FilterComboCache protected override string ToString(Item obj) => obj.Name; - private static string GetLabel(EquipSlot slot) + private static string GetLabel(DataManager gameData, EquipSlot slot) { - var sheet = Dalamud.GameData.GetExcelSheet()!; + var sheet = gameData.GetExcelSheet()!; return slot switch { @@ -89,7 +91,7 @@ public sealed class ItemCombo : FilterComboCache private static IReadOnlyList GetItems(ItemManager items, EquipSlot slot) { var nothing = ItemManager.NothingItem(slot); - if (!items.Items.TryGetValue(slot.ToEquipType(), out var list)) + if (!items.ItemService.AwaitedService.TryGetValue(slot.ToEquipType(), out var list)) return new[] { nothing, diff --git a/Glamourer/Gui/Equipment/WeaponCombo.cs b/Glamourer/Gui/Equipment/WeaponCombo.cs index cc200ab..b7413ca 100644 --- a/Glamourer/Gui/Equipment/WeaponCombo.cs +++ b/Glamourer/Gui/Equipment/WeaponCombo.cs @@ -1,8 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using Dalamud.Data; using Glamourer.Designs; -using Glamourer.Util; +using Glamourer.Services; using ImGuiNET; using Lumina.Excel.GeneratedSheets; using OtterGui; @@ -18,9 +19,9 @@ public sealed class WeaponCombo : FilterComboCache public readonly string Label; private uint _currentItem; - public WeaponCombo(ItemManager items, FullEquipType type, EquipSlot offhand) + public WeaponCombo(DataManager gameData, ItemManager items, FullEquipType type, EquipSlot offhand) : base(offhand is EquipSlot.OffHand ? () => GetOff(items, type) : () => GetMain(items, type)) - => Label = GetLabel(offhand); + => Label = GetLabel(gameData, offhand); protected override void DrawList(float width, float itemHeight) { @@ -64,9 +65,9 @@ public sealed class WeaponCombo : FilterComboCache protected override string ToString(Weapon obj) => obj.Name; - private static string GetLabel(EquipSlot offhand) + private static string GetLabel(DataManager gameData, EquipSlot offhand) { - var sheet = Dalamud.GameData.GetExcelSheet()!; + var sheet = gameData.GetExcelSheet()!; return offhand is EquipSlot.OffHand ? sheet.GetRow(739)?.Text.ToString() ?? "Off Hand" : sheet.GetRow(738)?.Text.ToString() ?? "Main Hand"; @@ -77,9 +78,9 @@ public sealed class WeaponCombo : FilterComboCache var list = new List(); if (type is FullEquipType.Unknown) foreach (var t in Enum.GetValues().Where(t => t.ToSlot() == EquipSlot.MainHand)) - list.AddRange(items.Items[t].Select(w => new Weapon(w, false))); + list.AddRange(items.ItemService.AwaitedService[t].Select(w => new Weapon(w, false))); else if (type.ToSlot() is EquipSlot.MainHand) - list.AddRange(items.Items[type].Select(w => new Weapon(w, false))); + list.AddRange(items.ItemService.AwaitedService[type].Select(w => new Weapon(w, false))); list.Sort((w1, w2) => string.CompareOrdinal(w1.Name, w2.Name)); return list; } @@ -89,7 +90,7 @@ public sealed class WeaponCombo : FilterComboCache if (type.ToSlot() == EquipSlot.OffHand) { var nothing = ItemManager.NothingItem(type); - if (!items.Items.TryGetValue(type, out var list)) + if (!items.ItemService.AwaitedService.TryGetValue(type, out var list)) return new[] { nothing, @@ -97,7 +98,7 @@ public sealed class WeaponCombo : FilterComboCache return list.Select(w => new Weapon(w, true)).OrderBy(w => w.Name).Prepend(nothing).ToList(); } - else if (items.Items.TryGetValue(type, out var list)) + else if (items.ItemService.AwaitedService.TryGetValue(type, out var list)) { return list.Select(w => new Weapon(w, true)).OrderBy(w => w.Name).ToList(); } diff --git a/Glamourer/Gui/GlamourerWindowSystem.cs b/Glamourer/Gui/GlamourerWindowSystem.cs new file mode 100644 index 0000000..dbe1c8c --- /dev/null +++ b/Glamourer/Gui/GlamourerWindowSystem.cs @@ -0,0 +1,30 @@ +using System; +using Dalamud.Interface; +using Dalamud.Interface.Windowing; + +namespace Glamourer.Gui; + +public class GlamourerWindowSystem : IDisposable +{ + private readonly WindowSystem _windowSystem = new("Glamourer"); + private readonly UiBuilder _uiBuilder; + private readonly Interface _ui; + + public GlamourerWindowSystem(UiBuilder uiBuilder, Interface ui) + { + _uiBuilder = uiBuilder; + _ui = ui; + _windowSystem.AddWindow(ui); + _uiBuilder.Draw += _windowSystem.Draw; + _uiBuilder.OpenConfigUi += _ui.Toggle; + } + + public void Dispose() + { + _uiBuilder.Draw -= _windowSystem.Draw; + _uiBuilder.OpenConfigUi -= _ui.Toggle; + } + + public void Toggle() + => _ui.Toggle(); +} diff --git a/Glamourer/Gui/Interface.Actors.cs b/Glamourer/Gui/Interface.Actors.cs index 7f4590c..a63017e 100644 --- a/Glamourer/Gui/Interface.Actors.cs +++ b/Glamourer/Gui/Interface.Actors.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Numerics; +using Dalamud.Game.ClientState.Objects; using Dalamud.Interface; using Glamourer.Interop; +using Glamourer.Services; using Glamourer.State; using ImGuiNET; using OtterGui; @@ -14,25 +16,32 @@ using ImGui = ImGuiNET.ImGui; namespace Glamourer.Gui; -internal partial class Interface +public partial class Interface { private class ActorTab { private readonly Interface _main; private readonly ActiveDesign.Manager _activeDesigns; private readonly ObjectManager _objects; + private readonly TargetManager _targets; + private readonly ActorService _actors; + private readonly ItemManager _items; - public ActorTab(Interface main, ActiveDesign.Manager activeDesigns, ObjectManager objects) + public ActorTab(Interface main, ActiveDesign.Manager activeDesigns, ObjectManager objects, TargetManager targets, ActorService actors, + ItemManager items) { _main = main; _activeDesigns = activeDesigns; _objects = objects; + _targets = targets; + _actors = actors; + _items = items; } - private ActorIdentifier _identifier = ActorIdentifier.Invalid; - private ObjectManager.ActorData _currentData = ObjectManager.ActorData.Invalid; - private string _currentLabel = string.Empty; - private ActiveDesign? _currentSave; + private ActorIdentifier _identifier = ActorIdentifier.Invalid; + private ActorData _currentData = ActorData.Invalid; + private string _currentLabel = string.Empty; + private ActiveDesign? _currentSave; public void Draw() { @@ -42,7 +51,7 @@ internal partial class Interface DrawActorSelector(); if (!_objects.TryGetValue(_identifier, out _currentData)) - _currentData = ObjectManager.ActorData.Invalid; + _currentData = ActorData.Invalid; else _currentLabel = _currentData.Label; @@ -64,11 +73,12 @@ internal partial class Interface return; if (_currentData.Valid) - _currentSave.Initialize(_currentData.Objects[0]); + _currentSave.Initialize(_items, _currentData.Objects[0]); RevertButton(); if (_main._customizationDrawer.Draw(_currentSave.Customize(), _identifier.Type == IdentifierType.Special)) - _activeDesigns.ChangeCustomize(_currentSave, _main._customizationDrawer.Changed, _main._customizationDrawer.CustomizeData, false); + _activeDesigns.ChangeCustomize(_currentSave, _main._customizationDrawer.Changed, _main._customizationDrawer.CustomizeData, + false); foreach (var slot in EquipSlotExtensions.EqdpSlots) { @@ -76,7 +86,8 @@ internal partial class Interface if (_main._equipmentDrawer.DrawStain(current.Stain, slot, out var stain)) _activeDesigns.ChangeStain(_currentSave, slot, stain.RowIndex, false); ImGui.SameLine(); - if (_main._equipmentDrawer.DrawArmor(current, slot, out var armor, _currentSave.Customize().Gender, _currentSave.Customize().Race)) + if (_main._equipmentDrawer.DrawArmor(current, slot, out var armor, _currentSave.Customize().Gender, + _currentSave.Customize().Race)) _activeDesigns.ChangeEquipment(_currentSave, slot, armor, false); } @@ -95,9 +106,7 @@ internal partial class Interface } if (_main._equipmentDrawer.DrawVisor(_currentSave, out var value)) - { _activeDesigns.ChangeVisor(_currentSave, value, false); - } } private const uint RedHeaderColor = 0xFF1818C0; @@ -241,10 +250,10 @@ internal partial class Interface ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); } - private bool CheckFilter(KeyValuePair pair) + private bool CheckFilter(KeyValuePair pair) => _actorFilter.IsEmpty || pair.Value.Label.Contains(_actorFilter.Lower, StringComparison.OrdinalIgnoreCase); - private void DrawSelectable(KeyValuePair pair) + private void DrawSelectable(KeyValuePair pair) { var equal = pair.Key.Equals(_identifier); if (ImGui.Selectable(pair.Value.Label, equal) && !equal) @@ -263,13 +272,13 @@ internal partial class Interface if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth , "Select the local player character.", !_objects.Player, true)) - _identifier = _objects.Player.GetIdentifier(); + _identifier = _objects.Player.GetIdentifier(_actors.AwaitedService); ImGui.SameLine(); - Actor targetActor = Dalamud.Targets.Target?.Address; + Actor targetActor = _targets.Target?.Address; if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth, "Select the current target, if it is in the list.", _objects.IsInGPose || !targetActor, true)) - _identifier = targetActor.GetIdentifier(); + _identifier = targetActor.GetIdentifier(_actors.AwaitedService); } } } diff --git a/Glamourer/Gui/Interface.DebugDataTab.cs b/Glamourer/Gui/Interface.DebugDataTab.cs index 821bc08..a38c31f 100644 --- a/Glamourer/Gui/Interface.DebugDataTab.cs +++ b/Glamourer/Gui/Interface.DebugDataTab.cs @@ -1,5 +1,6 @@ using System; using Glamourer.Customization; +using Glamourer.Services; using Glamourer.Util; using ImGuiNET; using OtterGui; @@ -7,31 +8,34 @@ using OtterGui.Raii; namespace Glamourer.Gui; -internal partial class Interface +public partial class Interface { private class DebugDataTab { - private readonly ICustomizationManager _mg; + private readonly CustomizationService _service; - public DebugDataTab(ICustomizationManager manager) - => _mg = manager; + public DebugDataTab(CustomizationService service) + => _service = service; public void Draw() { + if (!_service.Valid) + return; + using var tab = ImRaii.TabItem("Debug"); if (!tab) return; - foreach (var clan in _mg.Clans) + foreach (var clan in _service.AwaitedService.Clans) { - foreach (var gender in _mg.Genders) - DrawCustomizationInfo(_mg.GetList(clan, gender)); + foreach (var gender in _service.AwaitedService.Genders) + DrawCustomizationInfo(_service.AwaitedService.GetList(clan, gender)); } } - public static void DrawCustomizationInfo(CustomizationSet set) + public void DrawCustomizationInfo(CustomizationSet set) { - if (!ImGui.CollapsingHeader($"{CustomizeExtensions.ClanName(set.Clan, set.Gender)} {set.Gender}")) + if (!ImGui.CollapsingHeader($"{CustomizeExtensions.ClanName(_service.AwaitedService, set.Clan, set.Gender)} {set.Gender}")) return; using var table = ImRaii.Table("data", 5); diff --git a/Glamourer/Gui/Interface.DebugStateTab.cs b/Glamourer/Gui/Interface.DebugStateTab.cs index 229b5af..eb1c31d 100644 --- a/Glamourer/Gui/Interface.DebugStateTab.cs +++ b/Glamourer/Gui/Interface.DebugStateTab.cs @@ -11,7 +11,7 @@ using Penumbra.GameData.Actors; namespace Glamourer.Gui; -internal partial class Interface +public partial class Interface { private class DebugStateTab { diff --git a/Glamourer/Gui/Interface.DesignTab.cs b/Glamourer/Gui/Interface.DesignTab.cs index 948594b..97c7172 100644 --- a/Glamourer/Gui/Interface.DesignTab.cs +++ b/Glamourer/Gui/Interface.DesignTab.cs @@ -1,34 +1,31 @@ using System; using System.Numerics; +using Dalamud.Game.ClientState.Keys; using Dalamud.Interface; using Glamourer.Customization; using Glamourer.Designs; -using Glamourer.Gui.Customization; using Glamourer.Gui.Designs; -using Glamourer.Gui.Equipment; -using Glamourer.Interop; using ImGuiNET; using OtterGui.Raii; using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; namespace Glamourer.Gui; -internal partial class Interface +public partial class Interface { private class DesignTab : IDisposable { public readonly DesignFileSystemSelector Selector; private readonly Interface _main; private readonly DesignFileSystem _fileSystem; - private readonly Design.Manager _manager; + private readonly Design.Manager _manager; - public DesignTab(Interface main, Design.Manager manager, DesignFileSystem fileSystem) + public DesignTab(Interface main, Design.Manager manager, DesignFileSystem fileSystem, KeyState keyState) { _main = main; _manager = manager; _fileSystem = fileSystem; - Selector = new DesignFileSystemSelector(manager, fileSystem); + Selector = new DesignFileSystemSelector(manager, fileSystem, keyState); } public void Dispose() diff --git a/Glamourer/Gui/Interface.SettingsTab.cs b/Glamourer/Gui/Interface.SettingsTab.cs index b129985..b1c1276 100644 --- a/Glamourer/Gui/Interface.SettingsTab.cs +++ b/Glamourer/Gui/Interface.SettingsTab.cs @@ -1,29 +1,28 @@ using System; -using Glamourer.State; using ImGuiNET; using OtterGui; using OtterGui.Raii; namespace Glamourer.Gui; -internal partial class Interface +public partial class Interface { - private static void Checkmark(string label, string tooltip, bool value, Action setter) + private void Checkmark(string label, string tooltip, bool value, Action setter) { if (ImGuiUtil.Checkbox(label, tooltip, value, setter)) - Glamourer.Config.Save(); + _config.Save(); } - private static void ChangeAndSave(T value, T currentValue, Action setter) where T : IEquatable + private void ChangeAndSave(T value, T currentValue, Action setter) where T : IEquatable { if (value.Equals(currentValue)) return; setter(value); - Glamourer.Config.Save(); + _config.Save(); } - private static void DrawColorPicker(string name, string tooltip, uint value, uint defaultValue, Action setter) + private void DrawColorPicker(string name, string tooltip, uint value, uint defaultValue, Action setter) { const ImGuiColorEditFlags flags = ImGuiColorEditFlags.AlphaPreviewHalf | ImGuiColorEditFlags.NoInputs; @@ -42,7 +41,7 @@ internal partial class Interface private static void DrawRestorePenumbraButton() { - const string buttonLabel = "Re-Register Penumbra"; + //const string buttonLabel = "Re-Register Penumbra"; // TODO //if (ImGui.Button(buttonLabel)) // Glamourer.Penumbra.Reattach(true); @@ -51,36 +50,35 @@ internal partial class Interface // "If Penumbra did not register the functions for some reason, pressing this button might help restore functionality."); } - private static void DrawSettingsTab() + private void DrawSettingsTab() { using var tab = ImRaii.TabItem("Settings"); if (!tab) return; - var cfg = Glamourer.Config; ImGui.Dummy(_spacing); - Checkmark("Folders First", "Sort Folders before all designs instead of lexicographically.", cfg.FoldersFirst, - v => cfg.FoldersFirst = v); + Checkmark("Folders First", "Sort Folders before all designs instead of lexicographically.", _config.FoldersFirst, + v => _config.FoldersFirst = v); Checkmark("Color Designs", "Color the names of designs in the selector using the colors from below for the given cases.", - cfg.ColorDesigns, - v => cfg.ColorDesigns = v); - Checkmark("Show Locks", "Write-protected Designs show a lock besides their name in the selector.", cfg.ShowLocks, - v => cfg.ShowLocks = v); + _config.ColorDesigns, + v => _config.ColorDesigns = v); + Checkmark("Show Locks", "Write-protected Designs show a lock besides their name in the selector.", _config.ShowLocks, + v => _config.ShowLocks = v); DrawRestorePenumbraButton(); Checkmark("Apply Fixed Designs", "Automatically apply fixed designs to characters and redraw them when anything changes.", - cfg.ApplyFixedDesigns, - v => { cfg.ApplyFixedDesigns = v; }); + _config.ApplyFixedDesigns, + v => { _config.ApplyFixedDesigns = v; }); ImGui.Dummy(_spacing); DrawColorPicker("Customization Color", "The color for designs that only apply their character customization.", - cfg.CustomizationColor, GlamourerConfig.DefaultCustomizationColor, c => cfg.CustomizationColor = c); + _config.CustomizationColor, Configuration.DefaultCustomizationColor, c => _config.CustomizationColor = c); DrawColorPicker("Equipment Color", "The color for designs that only apply some or all of their equipment slots and stains.", - cfg.EquipmentColor, GlamourerConfig.DefaultEquipmentColor, c => cfg.EquipmentColor = c); + _config.EquipmentColor, Configuration.DefaultEquipmentColor, c => _config.EquipmentColor = c); DrawColorPicker("State Color", "The color for designs that only apply some state modification.", - cfg.StateColor, GlamourerConfig.DefaultStateColor, c => cfg.StateColor = c); + _config.StateColor, Configuration.DefaultStateColor, c => _config.StateColor = c); } } diff --git a/Glamourer/Gui/Interface.State.cs b/Glamourer/Gui/Interface.State.cs index 24b84fa..1986407 100644 --- a/Glamourer/Gui/Interface.State.cs +++ b/Glamourer/Gui/Interface.State.cs @@ -4,7 +4,7 @@ using ImGuiNET; namespace Glamourer.Gui; -internal partial class Interface +public partial class Interface { private static Vector2 _spacing = Vector2.Zero; private static float _actorSelectorWidth; diff --git a/Glamourer/Gui/Interface.cs b/Glamourer/Gui/Interface.cs index 41fa10d..41fdbe5 100644 --- a/Glamourer/Gui/Interface.cs +++ b/Glamourer/Gui/Interface.cs @@ -1,5 +1,8 @@ using System; using System.Numerics; +using Dalamud.Data; +using Dalamud.Game.ClientState.Keys; +using Dalamud.Game.ClientState.Objects; using Dalamud.Interface.Windowing; using Dalamud.Logging; using Dalamud.Plugin; @@ -7,43 +10,43 @@ using Glamourer.Designs; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Interop; +using Glamourer.Services; using Glamourer.State; -using Glamourer.Util; using ImGuiNET; using OtterGui.Raii; namespace Glamourer.Gui; -internal partial class Interface : Window, IDisposable +public partial class Interface : Window, IDisposable { private readonly DalamudPluginInterface _pi; private readonly EquipmentDrawer _equipmentDrawer; private readonly CustomizationDrawer _customizationDrawer; - - private readonly ActorTab _actorTab; - private readonly DesignTab _designTab; - private readonly DebugStateTab _debugStateTab; - private readonly DebugDataTab _debugDataTab; + private readonly Configuration _config; + private readonly ActorTab _actorTab; + private readonly DesignTab _designTab; + private readonly DebugStateTab _debugStateTab; + private readonly DebugDataTab _debugDataTab; public Interface(DalamudPluginInterface pi, ItemManager items, ActiveDesign.Manager activeDesigns, Design.Manager manager, - DesignFileSystem fileSystem, ObjectManager objects) + DesignFileSystem fileSystem, ObjectManager objects, CustomizationService customization, Configuration config, DataManager gameData, TargetManager targets, ActorService actors, KeyState keyState) : base(GetLabel()) { - _pi = pi; - _equipmentDrawer = new EquipmentDrawer(items); - _customizationDrawer = new CustomizationDrawer(pi); - Dalamud.PluginInterface.UiBuilder.DisableGposeUiHide = true; - Dalamud.PluginInterface.UiBuilder.OpenConfigUi += Toggle; + _pi = pi; + _config = config; + _equipmentDrawer = new EquipmentDrawer(gameData, items); + _customizationDrawer = new CustomizationDrawer(pi, customization, items); + pi.UiBuilder.DisableGposeUiHide = true; SizeConstraints = new WindowSizeConstraints() { MinimumSize = new Vector2(675, 675), MaximumSize = ImGui.GetIO().DisplaySize, }; - _actorTab = new ActorTab(this, activeDesigns, objects); + _actorTab = new ActorTab(this, activeDesigns, objects, targets, actors, items); _debugStateTab = new DebugStateTab(activeDesigns); - _debugDataTab = new DebugDataTab(Glamourer.Customization); - _designTab = new DesignTab(this, manager, fileSystem); + _debugDataTab = new DebugDataTab(customization); + _designTab = new DesignTab(this, manager, fileSystem, keyState); } public override void Draw() diff --git a/Glamourer/Gui/InterfaceActorPanel.cs b/Glamourer/Gui/InterfaceActorPanel.cs index 34042bf..4eaa79c 100644 --- a/Glamourer/Gui/InterfaceActorPanel.cs +++ b/Glamourer/Gui/InterfaceActorPanel.cs @@ -1,7 +1,7 @@  namespace Glamourer.Gui; -internal partial class Interface +public partial class Interface { //private readonly CharacterSave _currentSave = new(); //private string _newDesignName = string.Empty; diff --git a/Glamourer/Interop/Actor.cs b/Glamourer/Interop/Actor.cs index 0345264..b3126fe 100644 --- a/Glamourer/Interop/Actor.cs +++ b/Glamourer/Interop/Actor.cs @@ -24,14 +24,14 @@ public unsafe partial struct Actor : IEquatable, IDesignable public static implicit operator IntPtr(Actor actor) => actor.Pointer == null ? IntPtr.Zero : (IntPtr)actor.Pointer; - public ActorIdentifier GetIdentifier() - => Glamourer.Actors.FromObject((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Pointer, out _, true, true, false); + public ActorIdentifier GetIdentifier(ActorManager actors) + => actors.FromObject((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Pointer, out _, true, true, false); - public bool Identifier(out ActorIdentifier ident) + public bool Identifier(ActorManager actors, out ActorIdentifier ident) { if (Valid) { - ident = GetIdentifier(); + ident = GetIdentifier(actors); return true; } @@ -39,9 +39,6 @@ public unsafe partial struct Actor : IEquatable, IDesignable return false; } - public Character? Character - => Pointer == null ? null : Dalamud.Objects[Pointer->GameObject.ObjectIndex] as Character; - public bool IsAvailable => Pointer->GameObject.GetIsTargetable(); diff --git a/Glamourer/Interop/ObjectManager.cs b/Glamourer/Interop/ObjectManager.cs index 4c03a72..0e67536 100644 --- a/Glamourer/Interop/ObjectManager.cs +++ b/Glamourer/Interop/ObjectManager.cs @@ -4,35 +4,50 @@ using System.Collections.Generic; using Dalamud.Game; using Dalamud.Game.ClientState; using Dalamud.Game.ClientState.Objects; +using Glamourer.Services; using Penumbra.GameData.Actors; namespace Glamourer.Interop; -public class ObjectManager : IReadOnlyDictionary +public readonly struct ActorData { - public readonly struct ActorData + public readonly List Objects; + public readonly string Label; + + public bool Valid + => Objects.Count > 0; + + public ActorData(Actor actor, string label) { - public readonly List Objects; - public readonly string Label; - - public bool Valid - => Objects.Count > 0; - - public ActorData(Actor actor, string label) - { - Objects = new List { actor }; - Label = label; - } - - public static readonly ActorData Invalid = new(false); - - private ActorData(bool _) - { - Objects = new List(0); - Label = string.Empty; - } + Objects = new List { actor }; + Label = label; } + public static readonly ActorData Invalid = new(false); + + private ActorData(bool _) + { + Objects = new List(0); + Label = string.Empty; + } +} + +public class ObjectManager : IReadOnlyDictionary +{ + private readonly Framework _framework; + private readonly ClientState _clientState; + private readonly ObjectTable _objects; + private readonly ActorService _actors; + + public ObjectManager(Framework framework, ClientState clientState, ObjectTable objects, ActorService actors) + { + _framework = framework; + _clientState = clientState; + _objects = objects; + _actors = actors; + } + + public DateTime LastUpdate { get; private set; } public bool IsInGPose { get; private set; } @@ -56,16 +71,6 @@ public class ObjectManager : IReadOnlyDictionary? JobChanged; @@ -195,14 +199,18 @@ public partial class Interop public unsafe partial class RedrawManager : IDisposable { - private readonly FixedDesigns _fixedDesigns; + private readonly ItemManager _items; + private readonly ActorService _actors; + private readonly FixedDesignManager _fixedDesignManager; private readonly ActiveDesign.Manager _stateManager; - public RedrawManager(FixedDesigns fixedDesigns, ActiveDesign.Manager stateManager) + public RedrawManager(FixedDesignManager fixedDesignManager, ActiveDesign.Manager stateManager, ItemManager items, ActorService actors) { SignatureHelper.Initialise(this); - _fixedDesigns = fixedDesigns; - _stateManager = stateManager; + _fixedDesignManager = fixedDesignManager; + _stateManager = stateManager; + _items = items; + _actors = actors; _flagSlotForUpdateHook.Enable(); _loadWeaponHook.Enable(); } @@ -221,8 +229,8 @@ public unsafe partial class RedrawManager : IDisposable return; // Check if we have a current design in use, or if not if the actor has a fixed design. - var identifier = actor.GetIdentifier(); - if (!(_stateManager.TryGetValue(identifier, out var save) || _fixedDesigns.TryGetDesign(identifier, out var save2))) + var identifier = actor.GetIdentifier(_actors.AwaitedService); + if (!(_stateManager.TryGetValue(identifier, out var save) || _fixedDesignManager.TryGetDesign(identifier, out var save2))) return; // Compare game object customize data against draw object customize data for transformations. @@ -240,7 +248,7 @@ public unsafe partial class RedrawManager : IDisposable foreach (var slot in EquipSlotExtensions.EqdpSlots) { (_, equip[slot]) = - Glamourer.Items.ResolveRestrictedGear(saveEquip[slot], slot, customize.Race, customize.Gender); + _items.ResolveRestrictedGear(saveEquip[slot], slot, customize.Race, customize.Gender); } } } diff --git a/Glamourer/Services/BackupService.cs b/Glamourer/Services/BackupService.cs new file mode 100644 index 0000000..b98de25 --- /dev/null +++ b/Glamourer/Services/BackupService.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.IO; +using OtterGui.Classes; +using OtterGui.Log; + +namespace Glamourer.Services; + +public class BackupService +{ + public BackupService(Logger logger, FilenameService fileNames) + { + var files = GlamourerFiles(fileNames); + Backup.CreateBackup(logger, new DirectoryInfo(fileNames.ConfigDirectory), files); + } + + /// Collect all relevant files for glamourer configuration. + private static IReadOnlyList GlamourerFiles(FilenameService fileNames) + { + var list = new List(16) + { + new(fileNames.ConfigFile), + new(fileNames.DesignFileSystem), + new(fileNames.MigrationDesignFile), + }; + + list.AddRange(fileNames.Designs()); + + return list; + } +} diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs new file mode 100644 index 0000000..602a897 --- /dev/null +++ b/Glamourer/Services/CommandService.cs @@ -0,0 +1,36 @@ +using System; +using Dalamud.Game.Command; +using Glamourer.Gui; + +namespace Glamourer.Services; + +public class CommandService : IDisposable +{ + private const string HelpString = "[Copy|Apply|Save],[Name or PlaceHolder],"; + private const string MainCommandString = "/glamourer"; + private const string ApplyCommandString = "/glamour"; + + private readonly CommandManager _commands; + private readonly Interface _interface; + + public CommandService(CommandManager commands, Interface ui) + { + _commands = commands; + _interface = ui; + + _commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." }); + _commands.AddHandler(ApplyCommandString, new CommandInfo(OnGlamour) { HelpMessage = $"Use Glamourer Functions: {HelpString}" }); + } + + public void Dispose() + { + _commands.RemoveHandler(MainCommandString); + _commands.RemoveHandler(ApplyCommandString); + } + + private void OnGlamourer(string command, string arguments) + => _interface.Toggle(); + + private void OnGlamour(string command, string arguments) + { } +} diff --git a/Glamourer/Services/FilenameService.cs b/Glamourer/Services/FilenameService.cs new file mode 100644 index 0000000..f4e1176 --- /dev/null +++ b/Glamourer/Services/FilenameService.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Dalamud.Plugin; +using Glamourer.Designs; + +namespace Glamourer.Services; + +public class FilenameService +{ + public readonly string ConfigDirectory; + public readonly string ConfigFile; + public readonly string DesignFileSystem; + public readonly string MigrationDesignFile; + public readonly string DesignDirectory; + + public FilenameService(DalamudPluginInterface pi) + { + ConfigDirectory = pi.ConfigDirectory.FullName; + ConfigFile = pi.ConfigFile.FullName; + DesignFileSystem = Path.Combine(ConfigDirectory, "sort_order.json"); + MigrationDesignFile = Path.Combine(ConfigDirectory, "Designs.json"); + DesignDirectory = Path.Combine(ConfigDirectory, "designs"); + } + + public IEnumerable Designs() + { + if (!Directory.Exists(DesignDirectory)) + yield break; + + foreach (var file in Directory.EnumerateFiles(DesignDirectory, "*.json", SearchOption.TopDirectoryOnly)) + yield return new FileInfo(file); + } + + public string DesignFile(Design design) + => DesignFile(design.Identifier.ToString()); + + public string DesignFile(string identifier) + => Path.Combine(DesignDirectory, $"{identifier}.json"); +} diff --git a/Glamourer/Util/ItemManager.cs b/Glamourer/Services/ItemManager.cs similarity index 82% rename from Glamourer/Util/ItemManager.cs rename to Glamourer/Services/ItemManager.cs index 001e870..65f4b13 100644 --- a/Glamourer/Util/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -6,13 +6,13 @@ using Dalamud.Plugin; using Dalamud.Utility; using Lumina.Excel; using Lumina.Excel.GeneratedSheets; -using Penumbra.GameData; +using Lumina.Text; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Race = Penumbra.GameData.Enums.Race; -namespace Glamourer.Util; +namespace Glamourer.Services; public class ItemManager : IDisposable { @@ -20,33 +20,33 @@ public class ItemManager : IDisposable public const string SmallClothesNpc = "Smallclothes (NPC)"; public const ushort SmallClothesNpcModel = 9903; - public readonly IObjectIdentifier Identifier; - public readonly ExcelSheet ItemSheet; - public readonly StainData Stains; - public readonly ItemData Items; - public readonly RestrictedGear RestrictedGear; + private readonly Configuration _config; + public readonly IdentifierService IdentifierService; + public readonly ExcelSheet ItemSheet; + public readonly StainData Stains; + public readonly ItemService ItemService; + public readonly RestrictedGear RestrictedGear; - public ItemManager(DalamudPluginInterface pi, DataManager gameData) + public ItemManager(DalamudPluginInterface pi, DataManager gameData, IdentifierService identifierService, ItemService itemService, Configuration config) { - ItemSheet = gameData.GetExcelSheet()!; - Identifier = Penumbra.GameData.GameData.GetIdentifier(pi, gameData, gameData.Language); - Stains = new StainData(pi, gameData, gameData.Language); - Items = new ItemData(pi, gameData, gameData.Language); - RestrictedGear = new RestrictedGear(pi, gameData.Language, gameData); - DefaultSword = ItemSheet.GetRow(1601)!; // Weathered Shortsword + _config = config; + ItemSheet = gameData.GetExcelSheet()!; + IdentifierService = identifierService; + Stains = new StainData(pi, gameData, gameData.Language); + ItemService = itemService; + RestrictedGear = new RestrictedGear(pi, gameData.Language, gameData); + DefaultSword = ItemSheet.GetRow(1601)!; // Weathered Shortsword } public void Dispose() { Stains.Dispose(); - Items.Dispose(); - Identifier.Dispose(); RestrictedGear.Dispose(); } public (bool, CharacterArmor) ResolveRestrictedGear(CharacterArmor armor, EquipSlot slot, Race race, Gender gender) { - if (Glamourer.Config.UseRestrictedGearProtection) + if (_config.UseRestrictedGearProtection) return RestrictedGear.ResolveRestricted(armor, slot, race, gender); return (false, armor); @@ -152,7 +152,7 @@ public class ItemManager : IDisposable case 0: return (true, NothingId(slot), Nothing); case SmallClothesNpcModel: return (true, SmallclothesId(slot), SmallClothesNpc); default: - var item = Identifier.Identify(id, variant, slot).FirstOrDefault(); + var item = IdentifierService.AwaitedService.Identify(id, variant, slot).FirstOrDefault(); return item == null ? (false, 0, string.Intern($"Unknown ({id.Value}-{variant})")) : (true, item.RowId, string.Intern(item.Name.ToDalamudString().TextValue)); @@ -166,7 +166,7 @@ public class ItemManager : IDisposable { case EquipSlot.MainHand: { - var item = Identifier.Identify(id, type, variant, slot).FirstOrDefault(); + var item = IdentifierService.AwaitedService.Identify(id, type, variant, slot).FirstOrDefault(); return item != null ? (true, item.RowId, string.Intern(item.Name.ToDalamudString().TextValue), item.ToEquipType()) : (false, 0, string.Intern($"Unknown ({id.Value}-{type.Value}-{variant})"), mainhandType); @@ -177,7 +177,7 @@ public class ItemManager : IDisposable if (id.Value == 0) return (true, NothingId(weaponType), Nothing, weaponType); - var item = Identifier.Identify(id, type, variant, slot).FirstOrDefault(); + var item = IdentifierService.AwaitedService.Identify(id, type, variant, slot).FirstOrDefault(); return item != null ? (true, item.RowId, string.Intern(item.Name.ToDalamudString().TextValue), item.ToEquipType()) : (false, 0, string.Intern($"Unknown ({id.Value}-{type.Value}-{variant})"), diff --git a/Glamourer/Services/JobService.cs b/Glamourer/Services/JobService.cs new file mode 100644 index 0000000..9b91adb --- /dev/null +++ b/Glamourer/Services/JobService.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using Dalamud.Data; + +namespace Glamourer.Services; + +public class JobService +{ + public readonly IReadOnlyDictionary Jobs; + public readonly IReadOnlyDictionary JobGroups; + + public JobService(DataManager gameData) + { + Jobs = GameData.Jobs(gameData); + JobGroups = GameData.JobGroups(gameData); + } +} diff --git a/Glamourer/Services/SaveService.cs b/Glamourer/Services/SaveService.cs new file mode 100644 index 0000000..06fb498 --- /dev/null +++ b/Glamourer/Services/SaveService.cs @@ -0,0 +1,97 @@ +using System; +using System.IO; +using System.Text; +using OtterGui.Classes; +using OtterGui.Log; + +namespace Glamourer.Services; + +/// +/// Any file type that we want to save via SaveService. +/// +public interface ISavable +{ + /// The full file name of a given object. + public string ToFilename(FilenameService fileNames); + + /// Write the objects data to the given stream writer. + public void Save(StreamWriter writer); + + /// An arbitrary message printed to Debug before saving. + public string LogName(string fileName) + => fileName; + + public string TypeName + => GetType().Name; +} + +public class SaveService +{ + private readonly Logger _log; + private readonly FrameworkManager _framework; + + public readonly FilenameService FileNames; + + public SaveService(Logger log, FrameworkManager framework, FilenameService fileNames) + { + _log = log; + _framework = framework; + FileNames = fileNames; + } + + /// Queue a save for the next framework tick. + public void QueueSave(ISavable value) + { + var file = value.ToFilename(FileNames); + _framework.RegisterDelayed(value.GetType().Name + file, () => + { + ImmediateSave(value); + }); + } + + /// Immediately trigger a save. + public void ImmediateSave(ISavable value) + { + var name = value.ToFilename(FileNames); + try + { + if (name.Length == 0) + { + throw new Exception("Invalid object returned empty filename."); + } + + _log.Debug($"Saving {value.TypeName} {value.LogName(name)}..."); + var file = new FileInfo(name); + file.Directory?.Create(); + using var s = file.Exists ? file.Open(FileMode.Truncate) : file.Open(FileMode.CreateNew); + using var w = new StreamWriter(s, Encoding.UTF8); + value.Save(w); + } + catch (Exception ex) + { + _log.Error($"Could not save {value.GetType().Name} {value.LogName(name)}:\n{ex}"); + } + } + + public void ImmediateDelete(ISavable value) + { + var name = value.ToFilename(FileNames); + try + { + if (name.Length == 0) + { + throw new Exception("Invalid object returned empty filename."); + } + + if (!File.Exists(name)) + return; + + _log.Information($"Deleting {value.GetType().Name} {value.LogName(name)}..."); + File.Delete(name); + } + catch (Exception ex) + { + _log.Error($"Could not delete {value.GetType().Name} {value.LogName(name)}:\n{ex}"); + } + } +} diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs new file mode 100644 index 0000000..e6d28bc --- /dev/null +++ b/Glamourer/Services/ServiceManager.cs @@ -0,0 +1,76 @@ +using Dalamud.Plugin; +using Glamourer.Api; +using Glamourer.Designs; +using Glamourer.Gui; +using Glamourer.Interop; +using Glamourer.State; +using Microsoft.Extensions.DependencyInjection; +using OtterGui.Classes; +using OtterGui.Log; + +namespace Glamourer.Services; + +public static class ServiceManager +{ + public static ServiceProvider CreateProvider(DalamudPluginInterface pi, Logger log) + { + var services = new ServiceCollection() + .AddSingleton(log) + .AddDalamud(pi) + .AddMeta() + .AddConfig() + .AddPenumbra() + .AddInterop() + .AddGameData() + .AddDesigns() + .AddInterface() + .AddApi(); + + return services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true }); + } + + private static IServiceCollection AddDalamud(this IServiceCollection services, DalamudPluginInterface pi) + { + new DalamudServices(pi).AddServices(services); + return services; + } + + private static IServiceCollection AddMeta(this IServiceCollection services) + => services.AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton(); + + private static IServiceCollection AddConfig(this IServiceCollection services) + => services.AddSingleton() + .AddSingleton(); + + private static IServiceCollection AddPenumbra(this IServiceCollection services) + => services.AddSingleton(); + + private static IServiceCollection AddGameData(this IServiceCollection services) + => services.AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton(); + + private static IServiceCollection AddInterop(this IServiceCollection services) + => services.AddSingleton() + .AddSingleton(); + + private static IServiceCollection AddDesigns(this IServiceCollection services) + => services.AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton(); + + private static IServiceCollection AddInterface(this IServiceCollection services) + => services.AddSingleton() + .AddSingleton(); + + private static IServiceCollection AddApi(this IServiceCollection services) + => services.AddSingleton() + .AddSingleton(); +} diff --git a/Glamourer/Services/ServiceWrapper.cs b/Glamourer/Services/ServiceWrapper.cs new file mode 100644 index 0000000..8cd16c2 --- /dev/null +++ b/Glamourer/Services/ServiceWrapper.cs @@ -0,0 +1,105 @@ +using Dalamud.Data; +using Dalamud.Game.ClientState.Objects; +using Dalamud.Game.ClientState; +using Dalamud.Game.Gui; +using Dalamud.Plugin; +using Penumbra.GameData.Actors; +using System; +using System.Threading.Tasks; +using Dalamud.Game; +using Glamourer.Api; +using Glamourer.Customization; +using Penumbra.GameData.Data; +using Penumbra.GameData; + +namespace Glamourer.Services; + +public abstract class AsyncServiceWrapper +{ + public string Name { get; } + public T? Service { get; private set; } + + public T AwaitedService + { + get + { + _task?.Wait(); + return Service!; + } + } + + public bool Valid + => Service != null && !_isDisposed; + + public event Action? FinishedCreation; + private Task? _task; + + private bool _isDisposed; + + protected AsyncServiceWrapper(string name, Func factory) + { + Name = name; + _task = Task.Run(() => + { + var service = factory(); + if (_isDisposed) + { + if (service is IDisposable d) + d.Dispose(); + } + else + { + Service = service; + Glamourer.Log.Verbose($"[{Name}] Created."); + _task = null; + } + }); + _task.ContinueWith((t, x) => + { + if (!_isDisposed) + FinishedCreation?.Invoke(); + }, null); + } + + public void Dispose() + { + if (_isDisposed) + return; + + _isDisposed = true; + _task = null; + if (Service is IDisposable d) + d.Dispose(); + Glamourer.Log.Verbose($"[{Name}] Disposed."); + } +} + +public sealed class IdentifierService : AsyncServiceWrapper +{ + public IdentifierService(DalamudPluginInterface pi, DataManager data) + : base(nameof(IdentifierService), () => Penumbra.GameData.GameData.GetIdentifier(pi, data)) + { } +} + +public sealed class ItemService : AsyncServiceWrapper +{ + public ItemService(DalamudPluginInterface pi, DataManager gameData) + : base(nameof(ItemService), () => new ItemData(pi, gameData, gameData.Language)) + { } +} + +public sealed class ActorService : AsyncServiceWrapper +{ + public ActorService(DalamudPluginInterface pi, ObjectTable objects, ClientState clientState, Framework framework, DataManager gameData, + GameGui gui, PenumbraAttach penumbra) + : base(nameof(ActorService), + () => new ActorManager(pi, objects, clientState, framework, gameData, gui, idx => (short)penumbra.CutsceneParent(idx))) + { } +} + +public sealed class CustomizationService : AsyncServiceWrapper +{ + public CustomizationService(DalamudPluginInterface pi, DataManager gameData) + : base(nameof(CustomizationService), () => CustomizationManager.Create(pi, gameData)) + { } +} \ No newline at end of file diff --git a/Glamourer/State/ActiveDesign.Manager.cs b/Glamourer/State/ActiveDesign.Manager.cs index bc49ac4..b273559 100644 --- a/Glamourer/State/ActiveDesign.Manager.cs +++ b/Glamourer/State/ActiveDesign.Manager.cs @@ -6,16 +6,14 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using FFXIVClientStructs.FFXIV.Client.Game.Object; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Api; using Glamourer.Customization; using Glamourer.Designs; +using Glamourer.Services; using Penumbra.Api.Enums; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using CustomizeData = Penumbra.GameData.Structs.CustomizeData; -using Item = Glamourer.Designs.Item; -using Lumina.Excel.GeneratedSheets; namespace Glamourer.State; @@ -23,19 +21,21 @@ public sealed partial class ActiveDesign { public partial class Manager : IReadOnlyDictionary { - private readonly ActorManager _actors; + private readonly ActorService _actors; private readonly ObjectManager _objects; private readonly Interop.Interop _interop; private readonly PenumbraAttach _penumbra; + private readonly ItemManager _items; private readonly Dictionary _characterSaves = new(); - public Manager(ActorManager actors, ObjectManager objects, Interop.Interop interop, PenumbraAttach penumbra) + public Manager(ActorService actors, ObjectManager objects, Interop.Interop interop, PenumbraAttach penumbra, ItemManager items) { _actors = actors; _objects = objects; _interop = interop; _penumbra = penumbra; + _items = items; } public IEnumerator> GetEnumerator() @@ -67,16 +67,16 @@ public sealed partial class ActiveDesign public unsafe ActiveDesign GetOrCreateSave(Actor actor) { - var id = _actors.FromObject((GameObject*)actor.Pointer, out _, false, false, false); + var id = _actors.AwaitedService.FromObject((GameObject*)actor.Pointer, out _, false, false, false); if (_characterSaves.TryGetValue(id, out var save)) { - save.Initialize(actor); + save.Initialize(_items, actor); return save; } id = id.CreatePermanent(); - save = new ActiveDesign(id, actor); - save.Initialize(actor); + save = new ActiveDesign(_items, id, actor); + save.Initialize(_items, actor); _characterSaves.Add(id, save); return save; } @@ -100,7 +100,7 @@ public sealed partial class ActiveDesign if (from.DoApplyEquip(EquipSlot.MainHand)) ChangeMainHand(to, from.MainHand, fromFixed); - + if (from.DoApplyEquip(EquipSlot.OffHand)) ChangeOffHand(to, from.OffHand, fromFixed); @@ -130,10 +130,10 @@ public sealed partial class ActiveDesign } public void ChangeMainHand(ActiveDesign design, uint itemId, bool fromFixed) - => design.SetMainhand(itemId); + => design.SetMainhand(_items, itemId); public void ChangeOffHand(ActiveDesign design, uint itemId, bool fromFixed) - => design.SetOffhand(itemId); + => design.SetOffhand(_items, itemId); public void RevertMainHand(ActiveDesign design) { } @@ -187,7 +187,7 @@ public sealed partial class ActiveDesign if (equip) { var flag = slot.ToFlag(); - design.UpdateArmor(slot, item, true); + design.UpdateArmor(_items, slot, item, true); design.ChangedEquip &= ~flag; design.FixedEquip &= ~flag; } @@ -265,7 +265,7 @@ public sealed partial class ActiveDesign if (!_objects.TryGetValue(design.Identifier, out var data)) return; - foreach (var obj in data.Objects) + foreach (var obj in data.Objects) Interop.Interop.SetVisorState(obj.DrawObject, on); } } diff --git a/Glamourer/State/ActiveDesign.cs b/Glamourer/State/ActiveDesignData.cs similarity index 84% rename from Glamourer/State/ActiveDesign.cs rename to Glamourer/State/ActiveDesignData.cs index b0764d5..4346b1a 100644 --- a/Glamourer/State/ActiveDesign.cs +++ b/Glamourer/State/ActiveDesignData.cs @@ -1,9 +1,7 @@ -using System; -using System.Linq; -using Glamourer.Customization; +using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Interop; -using Penumbra.Api.Enums; +using Glamourer.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; @@ -26,13 +24,15 @@ public sealed partial class ActiveDesign : DesignBase public bool IsVisorToggled { get; private set; } = false; public bool IsWet { get; private set; } = false; - private ActiveDesign(ActorIdentifier identifier) + private ActiveDesign(ItemManager items, ActorIdentifier identifier) + : base(items) => Identifier = identifier; - public ActiveDesign(ActorIdentifier identifier, Actor actor) + public ActiveDesign(ItemManager items, ActorIdentifier identifier, Actor actor) + : base(items) { Identifier = identifier; - Initialize(actor); + Initialize(items, actor); } //public void ApplyToActor(Actor actor) @@ -65,7 +65,7 @@ public sealed partial class ActiveDesign : DesignBase // RedrawManager.SetVisor(actor.DrawObject.Pointer, actor.VisorEnabled); //} // - public void Initialize(Actor actor) + public void Initialize(ItemManager items, Actor actor) { if (!actor) return; @@ -84,7 +84,7 @@ public sealed partial class ActiveDesign : DesignBase if (initialEquip[slot] != current) { initialEquip[slot] = current; - UpdateArmor(slot, current, true); + UpdateArmor(items, slot, current, true); SetStain(slot, current.Stain); } } @@ -92,14 +92,14 @@ public sealed partial class ActiveDesign : DesignBase if (_initialData.MainHand != actor.MainHand) { _initialData.MainHand = actor.MainHand; - UpdateMainhand(actor.MainHand); + UpdateMainhand(items, actor.MainHand); SetStain(EquipSlot.MainHand, actor.MainHand.Stain); } if (_initialData.OffHand != actor.OffHand) { _initialData.OffHand = actor.OffHand; - UpdateOffhand(actor.OffHand); + UpdateOffhand(items, actor.OffHand); SetStain(EquipSlot.OffHand, actor.OffHand.Stain); } diff --git a/Glamourer/State/FixedDesigns.cs b/Glamourer/State/FixedDesignManager.cs similarity index 86% rename from Glamourer/State/FixedDesigns.cs rename to Glamourer/State/FixedDesignManager.cs index 6327c05..1d7c6e8 100644 --- a/Glamourer/State/FixedDesigns.cs +++ b/Glamourer/State/FixedDesignManager.cs @@ -5,7 +5,7 @@ using Penumbra.GameData.Actors; namespace Glamourer.State; -public class FixedDesigns +public class FixedDesignManager { public bool TryGetDesign(ActorIdentifier actor, [NotNullWhen(true)] out Design? save) { diff --git a/Glamourer/State/GlamourerConfig.cs b/Glamourer/State/GlamourerConfig.cs deleted file mode 100644 index ceb7a84..0000000 --- a/Glamourer/State/GlamourerConfig.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Collections.Generic; -using Dalamud.Configuration; - -namespace Glamourer.State; - -public class GlamourerConfig : IPluginConfiguration -{ - public class FixedDesign - { - public string Name = string.Empty; - public string Path = string.Empty; - public uint JobGroups; - 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 UseRestrictedGearProtection { get; set; } = true; - - public bool FoldersFirst { get; set; } = false; - public bool ColorDesigns { get; set; } = true; - public bool ShowLocks { 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); - - public static GlamourerConfig Load() - { - if (Dalamud.PluginInterface.GetPluginConfig() is GlamourerConfig config) - return config; - - config = new GlamourerConfig(); - config.Save(); - return config; - } -} diff --git a/Glamourer/State/IDesign.cs b/Glamourer/State/IDesign.cs deleted file mode 100644 index d6cc18c..0000000 --- a/Glamourer/State/IDesign.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Glamourer.Designs; -using Glamourer.Interop; - -namespace Glamourer.State; - -public interface IDesign -{ - public ref CharacterData Data { get; } - - public void ApplyToActor(Actor a); -} diff --git a/Glamourer/Util/CustomizeExtensions.cs b/Glamourer/Util/CustomizeExtensions.cs index 7b32860..c24359b 100644 --- a/Glamourer/Util/CustomizeExtensions.cs +++ b/Glamourer/Util/CustomizeExtensions.cs @@ -1,5 +1,6 @@ using System; using Glamourer.Customization; +using Glamourer.Services; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -8,7 +9,7 @@ namespace Glamourer.Util; public static class CustomizeExtensions { // In languages other than english the actual clan name may depend on gender. - public static string ClanName(SubRace race, Gender gender) + public static string ClanName(ICustomizationManager customization, SubRace race, Gender gender) { if (gender == Gender.FemaleNpc) gender = Gender.Female; @@ -16,79 +17,79 @@ public static class CustomizeExtensions gender = Gender.Male; return (gender, race) switch { - (Gender.Male, SubRace.Midlander) => Glamourer.Customization.GetName(CustomName.MidlanderM), - (Gender.Male, SubRace.Highlander) => Glamourer.Customization.GetName(CustomName.HighlanderM), - (Gender.Male, SubRace.Wildwood) => Glamourer.Customization.GetName(CustomName.WildwoodM), - (Gender.Male, SubRace.Duskwight) => Glamourer.Customization.GetName(CustomName.DuskwightM), - (Gender.Male, SubRace.Plainsfolk) => Glamourer.Customization.GetName(CustomName.PlainsfolkM), - (Gender.Male, SubRace.Dunesfolk) => Glamourer.Customization.GetName(CustomName.DunesfolkM), - (Gender.Male, SubRace.SeekerOfTheSun) => Glamourer.Customization.GetName(CustomName.SeekerOfTheSunM), - (Gender.Male, SubRace.KeeperOfTheMoon) => Glamourer.Customization.GetName(CustomName.KeeperOfTheMoonM), - (Gender.Male, SubRace.Seawolf) => Glamourer.Customization.GetName(CustomName.SeawolfM), - (Gender.Male, SubRace.Hellsguard) => Glamourer.Customization.GetName(CustomName.HellsguardM), - (Gender.Male, SubRace.Raen) => Glamourer.Customization.GetName(CustomName.RaenM), - (Gender.Male, SubRace.Xaela) => Glamourer.Customization.GetName(CustomName.XaelaM), - (Gender.Male, SubRace.Helion) => Glamourer.Customization.GetName(CustomName.HelionM), - (Gender.Male, SubRace.Lost) => Glamourer.Customization.GetName(CustomName.LostM), - (Gender.Male, SubRace.Rava) => Glamourer.Customization.GetName(CustomName.RavaM), - (Gender.Male, SubRace.Veena) => Glamourer.Customization.GetName(CustomName.VeenaM), - (Gender.Female, SubRace.Midlander) => Glamourer.Customization.GetName(CustomName.MidlanderF), - (Gender.Female, SubRace.Highlander) => Glamourer.Customization.GetName(CustomName.HighlanderF), - (Gender.Female, SubRace.Wildwood) => Glamourer.Customization.GetName(CustomName.WildwoodF), - (Gender.Female, SubRace.Duskwight) => Glamourer.Customization.GetName(CustomName.DuskwightF), - (Gender.Female, SubRace.Plainsfolk) => Glamourer.Customization.GetName(CustomName.PlainsfolkF), - (Gender.Female, SubRace.Dunesfolk) => Glamourer.Customization.GetName(CustomName.DunesfolkF), - (Gender.Female, SubRace.SeekerOfTheSun) => Glamourer.Customization.GetName(CustomName.SeekerOfTheSunF), - (Gender.Female, SubRace.KeeperOfTheMoon) => Glamourer.Customization.GetName(CustomName.KeeperOfTheMoonF), - (Gender.Female, SubRace.Seawolf) => Glamourer.Customization.GetName(CustomName.SeawolfF), - (Gender.Female, SubRace.Hellsguard) => Glamourer.Customization.GetName(CustomName.HellsguardF), - (Gender.Female, SubRace.Raen) => Glamourer.Customization.GetName(CustomName.RaenF), - (Gender.Female, SubRace.Xaela) => Glamourer.Customization.GetName(CustomName.XaelaF), - (Gender.Female, SubRace.Helion) => Glamourer.Customization.GetName(CustomName.HelionM), - (Gender.Female, SubRace.Lost) => Glamourer.Customization.GetName(CustomName.LostM), - (Gender.Female, SubRace.Rava) => Glamourer.Customization.GetName(CustomName.RavaF), - (Gender.Female, SubRace.Veena) => Glamourer.Customization.GetName(CustomName.VeenaF), + (Gender.Male, SubRace.Midlander) => customization.GetName(CustomName.MidlanderM), + (Gender.Male, SubRace.Highlander) => customization.GetName(CustomName.HighlanderM), + (Gender.Male, SubRace.Wildwood) => customization.GetName(CustomName.WildwoodM), + (Gender.Male, SubRace.Duskwight) => customization.GetName(CustomName.DuskwightM), + (Gender.Male, SubRace.Plainsfolk) => customization.GetName(CustomName.PlainsfolkM), + (Gender.Male, SubRace.Dunesfolk) => customization.GetName(CustomName.DunesfolkM), + (Gender.Male, SubRace.SeekerOfTheSun) => customization.GetName(CustomName.SeekerOfTheSunM), + (Gender.Male, SubRace.KeeperOfTheMoon) => customization.GetName(CustomName.KeeperOfTheMoonM), + (Gender.Male, SubRace.Seawolf) => customization.GetName(CustomName.SeawolfM), + (Gender.Male, SubRace.Hellsguard) => customization.GetName(CustomName.HellsguardM), + (Gender.Male, SubRace.Raen) => customization.GetName(CustomName.RaenM), + (Gender.Male, SubRace.Xaela) => customization.GetName(CustomName.XaelaM), + (Gender.Male, SubRace.Helion) => customization.GetName(CustomName.HelionM), + (Gender.Male, SubRace.Lost) => customization.GetName(CustomName.LostM), + (Gender.Male, SubRace.Rava) => customization.GetName(CustomName.RavaM), + (Gender.Male, SubRace.Veena) => customization.GetName(CustomName.VeenaM), + (Gender.Female, SubRace.Midlander) => customization.GetName(CustomName.MidlanderF), + (Gender.Female, SubRace.Highlander) => customization.GetName(CustomName.HighlanderF), + (Gender.Female, SubRace.Wildwood) => customization.GetName(CustomName.WildwoodF), + (Gender.Female, SubRace.Duskwight) => customization.GetName(CustomName.DuskwightF), + (Gender.Female, SubRace.Plainsfolk) => customization.GetName(CustomName.PlainsfolkF), + (Gender.Female, SubRace.Dunesfolk) => customization.GetName(CustomName.DunesfolkF), + (Gender.Female, SubRace.SeekerOfTheSun) => customization.GetName(CustomName.SeekerOfTheSunF), + (Gender.Female, SubRace.KeeperOfTheMoon) => customization.GetName(CustomName.KeeperOfTheMoonF), + (Gender.Female, SubRace.Seawolf) => customization.GetName(CustomName.SeawolfF), + (Gender.Female, SubRace.Hellsguard) => customization.GetName(CustomName.HellsguardF), + (Gender.Female, SubRace.Raen) => customization.GetName(CustomName.RaenF), + (Gender.Female, SubRace.Xaela) => customization.GetName(CustomName.XaelaF), + (Gender.Female, SubRace.Helion) => customization.GetName(CustomName.HelionM), + (Gender.Female, SubRace.Lost) => customization.GetName(CustomName.LostM), + (Gender.Female, SubRace.Rava) => customization.GetName(CustomName.RavaF), + (Gender.Female, SubRace.Veena) => customization.GetName(CustomName.VeenaF), _ => throw new ArgumentOutOfRangeException(nameof(race), race, null), }; } - public static string ClanName(this Customize customize) - => ClanName(customize.Clan, customize.Gender); + public static string ClanName(this Customize customize, ICustomizationManager customization) + => ClanName(customization, customize.Clan, customize.Gender); // Change a gender and fix up all required customizations afterwards. - public static CustomizeFlag ChangeGender(this Customize customize, CharacterEquip equip, Gender gender) + public static CustomizeFlag ChangeGender(this Customize customize, CharacterEquip equip, Gender gender, ItemManager items, ICustomizationManager customization) { if (customize.Gender == gender) return 0; - FixRestrictedGear(customize, equip, gender, customize.Race); + FixRestrictedGear(items, customize, equip, gender, customize.Race); customize.Gender = gender; - return CustomizeFlag.Gender | FixUpAttributes(customize); + return CustomizeFlag.Gender | FixUpAttributes(customization, customize); } // Change a race and fix up all required customizations afterwards. - public static CustomizeFlag ChangeRace(this Customize customize, CharacterEquip equip, SubRace clan) + public static CustomizeFlag ChangeRace(this Customize customize, CharacterEquip equip, SubRace clan, ItemManager items, ICustomizationManager customization) { if (customize.Clan == clan) return 0; var race = clan.ToRace(); var gender = race == Race.Hrothgar ? Gender.Male : customize.Gender; // TODO Female Hrothgar - FixRestrictedGear(customize, equip, gender, race); + FixRestrictedGear(items, customize, equip, gender, race); var flags = CustomizeFlag.Race | CustomizeFlag.Clan; if (gender != customize.Gender) flags |= CustomizeFlag.Gender; customize.Gender = gender; customize.Race = race; customize.Clan = clan; - return flags | FixUpAttributes(customize); + return flags | FixUpAttributes(customization, customize); } // Go through a whole customization struct and fix up all settings that need fixing. - private static CustomizeFlag FixUpAttributes(Customize customize) + private static CustomizeFlag FixUpAttributes(ICustomizationManager customization, Customize customize) { - var set = Glamourer.Customization.GetList(customize.Clan, customize.Gender); + var set = customization.GetList(customize.Clan, customize.Gender); CustomizeFlag flags = 0; foreach (CustomizeIndex id in Enum.GetValues(typeof(CustomizeIndex))) { @@ -115,12 +116,12 @@ public static class CustomizeExtensions return flags; } - private static void FixRestrictedGear(Customize customize, CharacterEquip equip, Gender gender, Race race) + private static void FixRestrictedGear(ItemManager items, Customize customize, CharacterEquip equip, Gender gender, Race race) { if (!equip || race == customize.Race && gender == customize.Gender) return; foreach (var slot in EquipSlotExtensions.EqdpSlots) - (_, equip[slot]) = Glamourer.Items.ResolveRestrictedGear(equip[slot], slot, race, gender); + (_, equip[slot]) = items.ResolveRestrictedGear(equip[slot], slot, race, gender); } }