From 4cf082aa19173ba206c5b9a22e716d9f89d9ea0d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 9 Feb 2023 18:52:53 +0100 Subject: [PATCH] . --- .../Customization/CustomizationOptions.cs | 17 +- Glamourer.GameData/Customization/Customize.cs | 22 + .../Customization/CustomizeFlag.cs | 6 +- Glamourer.GameData/Offsets.cs | 1 + Glamourer.GameData/Structs/JobGroup.cs | 4 +- Glamourer/Api/PenumbraAttach.cs | 229 +++++----- Glamourer/Designs/DesignBase.cs | 4 +- Glamourer/Designs/Structs.cs | 23 +- Glamourer/DrawObjectManager.cs | 88 ++++ Glamourer/Glamourer.cs | 49 ++- .../CustomizationDrawer.Color.cs | 15 +- .../CustomizationDrawer.GenderRace.cs | 35 +- .../Customization/CustomizationDrawer.Icon.cs | 57 ++- .../Customization/CustomizationDrawer.Main.cs | 137 ------ .../CustomizationDrawer.Multi.cs | 50 --- .../CustomizationDrawer.Simple.cs | 106 +++-- .../Gui/Customization/CustomizationDrawer.cs | 172 ++++++++ Glamourer/Gui/Equipment/EquipmentDrawer.cs | 128 +++++- Glamourer/Gui/Equipment/ItemCombo.cs | 63 +-- Glamourer/Gui/Equipment/WeaponCombo.cs | 107 +++++ Glamourer/Gui/Interface.Actors.cs | 397 +++--------------- Glamourer/Gui/Interface.DebugStateTab.cs | 12 +- Glamourer/Gui/Interface.DesignTab.cs | 22 +- Glamourer/Gui/Interface.SettingsTab.cs | 17 +- Glamourer/Gui/Interface.cs | 109 +---- Glamourer/Interop/Actor.cs | 3 + Glamourer/Interop/ObjectManager.cs | 153 ++++--- Glamourer/Interop/RedrawManager.Customize.cs | 29 -- Glamourer/Interop/RedrawManager.Equipment.cs | 12 - Glamourer/Interop/RedrawManager.Weapons.cs | 2 +- Glamourer/Interop/RedrawManager.cs | 109 ++++- Glamourer/State/ActiveDesign.Manager.cs | 211 ++++++++++ Glamourer/State/ActiveDesign.StateManager.cs | 64 --- Glamourer/State/ActiveDesign.cs | 27 +- Glamourer/State/CurrentManipulations.cs | 74 ---- Glamourer/Util/CustomizeExtensions.cs | 53 +-- Glamourer/Util/ItemManager.cs | 10 +- 37 files changed, 1362 insertions(+), 1255 deletions(-) create mode 100644 Glamourer/DrawObjectManager.cs delete mode 100644 Glamourer/Gui/Customization/CustomizationDrawer.Main.cs delete mode 100644 Glamourer/Gui/Customization/CustomizationDrawer.Multi.cs create mode 100644 Glamourer/Gui/Customization/CustomizationDrawer.cs create mode 100644 Glamourer/Gui/Equipment/WeaponCombo.cs create mode 100644 Glamourer/State/ActiveDesign.Manager.cs delete mode 100644 Glamourer/State/ActiveDesign.StateManager.cs delete mode 100644 Glamourer/State/CurrentManipulations.cs diff --git a/Glamourer.GameData/Customization/CustomizationOptions.cs b/Glamourer.GameData/Customization/CustomizationOptions.cs index e353361..8f48c3a 100644 --- a/Glamourer.GameData/Customization/CustomizationOptions.cs +++ b/Glamourer.GameData/Customization/CustomizationOptions.cs @@ -59,7 +59,7 @@ public partial class CustomizationOptions public partial class CustomizationOptions { - internal readonly bool Valid; + private readonly bool _valid; public string GetName(CustomName name) => _names[(int)name]; @@ -68,7 +68,7 @@ public partial class CustomizationOptions { var tmp = new TemporaryData(gameData, this); _icons = new IconStorage(pi, gameData, _customizationSets.Length * 50); - Valid = tmp.Valid; + _valid = tmp.Valid; SetNames(gameData, tmp); foreach (var race in Clans) { @@ -77,7 +77,6 @@ public partial class CustomizationOptions } } - // Obtain localized names of customization options and race names from the game data. private readonly string[] _names = new string[Enum.GetValues().Length]; @@ -430,10 +429,14 @@ public partial class CustomizationOptions // Hair Row from CustomizeSheet might not be set in case of unlockable hair. var hairRow = _customizeSheet.GetRow(customizeIdx); - hairList.Add(hairRow != null - ? new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon, - (ushort)hairRow.RowId) - : new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx)); + if (hairRow == null) + { + hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx)); + } + else if (_options._icons.IconExists(hairRow.Icon)) + { + hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon, (ushort)hairRow.RowId)); + } } return hairList.ToArray(); diff --git a/Glamourer.GameData/Customization/Customize.cs b/Glamourer.GameData/Customization/Customize.cs index 00ba8db..62f2e89 100644 --- a/Glamourer.GameData/Customization/Customize.cs +++ b/Glamourer.GameData/Customization/Customize.cs @@ -10,6 +10,14 @@ public unsafe struct Customize public Customize(Penumbra.GameData.Structs.CustomizeData* data) => Data = data; + public Customize(ref Penumbra.GameData.Structs.CustomizeData data) + { + fixed (Penumbra.GameData.Structs.CustomizeData* ptr = &data) + { + Data = ptr; + } + } + public Race Race { get => (Race)Data->Get(CustomizeIndex.Race).Value; @@ -96,4 +104,18 @@ public unsafe struct Customize public string WriteBase64() => Data->WriteBase64(); + + public static CustomizeFlag Compare(Customize lhs, Customize rhs) + { + CustomizeFlag ret = 0; + foreach (var idx in Enum.GetValues()) + { + var l = lhs[idx]; + var r = rhs[idx]; + if (l.Value != r.Value) + ret |= idx.ToFlag(); + } + + return ret; + } } diff --git a/Glamourer.GameData/Customization/CustomizeFlag.cs b/Glamourer.GameData/Customization/CustomizeFlag.cs index 2529678..b26bd6a 100644 --- a/Glamourer.GameData/Customization/CustomizeFlag.cs +++ b/Glamourer.GameData/Customization/CustomizeFlag.cs @@ -47,7 +47,11 @@ public enum CustomizeFlag : ulong public static class CustomizeFlagExtensions { - public const CustomizeFlag All = (CustomizeFlag)(((ulong)CustomizeFlag.FacePaintColor << 1) - 1ul); + public const CustomizeFlag All = (CustomizeFlag)(((ulong)CustomizeFlag.FacePaintColor << 1) - 1ul); + public const CustomizeFlag RedrawRequired = CustomizeFlag.Race | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.Face; + + public static bool RequiresRedraw(this CustomizeFlag flags) + => (flags & RedrawRequired) != 0; [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public static CustomizeIndex ToIndex(this CustomizeFlag flag) diff --git a/Glamourer.GameData/Offsets.cs b/Glamourer.GameData/Offsets.cs index ce3f6ae..e652860 100644 --- a/Glamourer.GameData/Offsets.cs +++ b/Glamourer.GameData/Offsets.cs @@ -31,4 +31,5 @@ public static class Sigs { public const string ChangeJob = "88 51 ?? 44 3B CA"; public const string FlagSlotForUpdate = "48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A"; + public const string ChangeCustomize = "E8 ?? ?? ?? ?? 41 0F B6 C5 66 41 89 86"; } diff --git a/Glamourer.GameData/Structs/JobGroup.cs b/Glamourer.GameData/Structs/JobGroup.cs index eaa614e..55c77a6 100644 --- a/Glamourer.GameData/Structs/JobGroup.cs +++ b/Glamourer.GameData/Structs/JobGroup.cs @@ -21,7 +21,7 @@ public readonly struct JobGroup Id = group.RowId; Name = group.Name.ToString(); - Debug.Assert(jobs.RowCount < 64); + Debug.Assert(jobs.RowCount < 64, $"Number of Jobs exceeded 63 ({jobs.RowCount})."); foreach (var job in jobs) { var abbr = job.Abbreviation.ToString(); @@ -29,7 +29,7 @@ public readonly struct JobGroup continue; var prop = group.GetType().GetProperty(abbr); - Debug.Assert(prop != null); + Debug.Assert(prop != null, $"Could not get job abbreviation {abbr} property."); if (!(bool)prop.GetValue(group)!) continue; diff --git a/Glamourer/Api/PenumbraAttach.cs b/Glamourer/Api/PenumbraAttach.cs index af21d08..34f93ce 100644 --- a/Glamourer/Api/PenumbraAttach.cs +++ b/Glamourer/Api/PenumbraAttach.cs @@ -2,13 +2,9 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Logging; using Glamourer.Interop; -using Glamourer.Structs; -using ImGuiNET; using Penumbra.Api; using Penumbra.Api.Enums; using Penumbra.Api.Helpers; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; namespace Glamourer.Api; @@ -17,33 +13,76 @@ public unsafe class PenumbraAttach : IDisposable public const int RequiredPenumbraBreakingVersion = 4; public const int RequiredPenumbraFeatureVersion = 15; - private EventSubscriber _tooltipSubscriber; - private EventSubscriber _clickSubscriber; - private ActionSubscriber _redrawSubscriber; - private FuncSubscriber _drawObjectInfo; - public EventSubscriber CreatingCharacterBase; - public EventSubscriber CreatedCharacterBase; - private FuncSubscriber _cutsceneParent; + private readonly EventSubscriber _tooltipSubscriber; + private readonly EventSubscriber _clickSubscriber; + private readonly EventSubscriber _creatingCharacterBase; + private readonly EventSubscriber _createdCharacterBase; + private ActionSubscriber _redrawSubscriber; + private FuncSubscriber _drawObjectInfo; + private FuncSubscriber _cutsceneParent; private readonly EventSubscriber _initializedEvent; private readonly EventSubscriber _disposedEvent; public bool Available { get; private set; } - public PenumbraAttach(bool attach) + public PenumbraAttach() { - _initializedEvent = Ipc.Initialized.Subscriber(Dalamud.PluginInterface, Reattach); - _disposedEvent = Ipc.Disposed.Subscriber(Dalamud.PluginInterface, Unattach); - _tooltipSubscriber = Ipc.ChangedItemTooltip.Subscriber(Dalamud.PluginInterface, PenumbraTooltip); - _clickSubscriber = Ipc.ChangedItemClick.Subscriber(Dalamud.PluginInterface, PenumbraRightClick); - CreatedCharacterBase = Ipc.CreatedCharacterBase.Subscriber(Dalamud.PluginInterface); - CreatingCharacterBase = Ipc.CreatingCharacterBase.Subscriber(Dalamud.PluginInterface); - Reattach(attach); + _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); + Reattach(); } - private void Reattach() - => Reattach(Glamourer.Config.AttachToPenumbra); + public event Action Click + { + add => _clickSubscriber.Event += value; + remove => _clickSubscriber.Event -= value; + } - public void Reattach(bool attach) + public event Action Tooltip + { + add => _tooltipSubscriber.Event += value; + remove => _tooltipSubscriber.Event -= value; + } + + + public event Action CreatingCharacterBase + { + add => _creatingCharacterBase.Event += value; + remove => _creatingCharacterBase.Event -= value; + } + + public event Action CreatedCharacterBase + { + add => _createdCharacterBase.Event += value; + remove => _createdCharacterBase.Event -= value; + } + + public Actor GameObjectFromDrawObject(IntPtr drawObject) + => Available ? _drawObjectInfo.Invoke(drawObject).Item1 : Actor.Null; + + public int CutsceneParent(int idx) + => Available ? _cutsceneParent.Invoke(idx) : -1; + + public void RedrawObject(Actor actor, RedrawType settings) + { + if (!actor || !Available) + return; + + try + { + _redrawSubscriber.Invoke(actor.Index, settings); + } + catch (Exception e) + { + PluginLog.Debug($"Failure redrawing object:\n{e}"); + } + } + + public void Reattach() { try { @@ -54,22 +93,19 @@ public unsafe class PenumbraAttach : IDisposable throw new Exception( $"Invalid Version {breaking}.{feature:D4}, required major Version {RequiredPenumbraBreakingVersion} with feature greater or equal to {RequiredPenumbraFeatureVersion}."); - if (!attach) - return; - _tooltipSubscriber.Enable(); _clickSubscriber.Enable(); - CreatingCharacterBase.Enable(); - CreatedCharacterBase.Enable(); + _creatingCharacterBase.Enable(); + _createdCharacterBase.Enable(); _drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(Dalamud.PluginInterface); _cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(Dalamud.PluginInterface); - _redrawSubscriber = Ipc.RedrawObject.Subscriber(Dalamud.PluginInterface); + _redrawSubscriber = Ipc.RedrawObjectByIndex.Subscriber(Dalamud.PluginInterface); Available = true; - PluginLog.Debug("Glamourer attached to Penumbra."); + Glamourer.Log.Debug("Glamourer attached to Penumbra."); } catch (Exception e) { - PluginLog.Debug($"Could not attach to Penumbra:\n{e}"); + Glamourer.Log.Debug($"Could not attach to Penumbra:\n{e}"); } } @@ -77,8 +113,8 @@ public unsafe class PenumbraAttach : IDisposable { _tooltipSubscriber.Disable(); _clickSubscriber.Disable(); - CreatingCharacterBase.Disable(); - CreatedCharacterBase.Disable(); + _creatingCharacterBase.Disable(); + _createdCharacterBase.Disable(); if (Available) { Available = false; @@ -91,85 +127,66 @@ public unsafe class PenumbraAttach : IDisposable Unattach(); _tooltipSubscriber.Dispose(); _clickSubscriber.Dispose(); - CreatingCharacterBase.Dispose(); - CreatedCharacterBase.Dispose(); + _creatingCharacterBase.Dispose(); + _createdCharacterBase.Dispose(); _initializedEvent.Dispose(); _disposedEvent.Dispose(); } - private static void PenumbraTooltip(ChangedItemType type, uint _) - { - if (type == ChangedItemType.Item) - ImGui.Text("Right click to apply to current Glamourer Set. [Glamourer]"); - } + //private static void PenumbraTooltip(ChangedItemType type, uint _) + //{ + // if (type == ChangedItemType.Item) + // ImGui.Text("Right click to apply to current Glamourer Set. [Glamourer]"); + //} + // + //private void PenumbraRightClick(MouseButton button, ChangedItemType type, uint id) + //{ + // if (button != MouseButton.Right || type != ChangedItemType.Item) + // return; + // + // var item = (Lumina.Excel.GeneratedSheets.Item)type.GetObject(Dalamud.GameData, id)!; + // var writeItem = new Item2(item, string.Empty); + // + // UpdateItem(_objects.GPosePlayer, writeItem); + // UpdateItem(_objects.Player, writeItem); + //} - private static void PenumbraRightClick(MouseButton button, ChangedItemType type, uint id) - { - if (button != MouseButton.Right || type != ChangedItemType.Item) - return; - - var item = (Lumina.Excel.GeneratedSheets.Item)type.GetObject(Dalamud.GameData, id)!; - var writeItem = new Item2(item, string.Empty); - - UpdateItem(ObjectManager.GPosePlayer, writeItem); - UpdateItem(ObjectManager.Player, writeItem); - } - - private static void UpdateItem(Actor actor, Item2 item2) - { - if (!actor || !actor.DrawObject) - return; - - switch (item2.EquippableTo) - { - case EquipSlot.MainHand: - { - var off = item2.HasSubModel - ? new CharacterWeapon(item2.SubModel.id, item2.SubModel.type, item2.SubModel.variant, actor.DrawObject.OffHand.Stain) - : item2.IsBothHand - ? CharacterWeapon.Empty - : actor.OffHand; - var main = new CharacterWeapon(item2.MainModel.id, item2.MainModel.type, item2.MainModel.variant, actor.DrawObject.MainHand.Stain); - Glamourer.RedrawManager.LoadWeapon(actor, main, off); - return; - } - case EquipSlot.OffHand: - { - var off = new CharacterWeapon(item2.MainModel.id, item2.MainModel.type, item2.MainModel.variant, actor.DrawObject.OffHand.Stain); - var main = actor.MainHand; - Glamourer.RedrawManager.LoadWeapon(actor, main, off); - return; - } - default: - { - var current = actor.DrawObject.Equip[item2.EquippableTo]; - var armor = new CharacterArmor(item2.MainModel.id, (byte)item2.MainModel.variant, current.Stain); - Glamourer.RedrawManager.UpdateSlot(actor.DrawObject, item2.EquippableTo, armor); - return; - } - } - } - - public Actor GameObjectFromDrawObject(IntPtr drawObject) - => Available ? _drawObjectInfo.Invoke(drawObject).Item1 : IntPtr.Zero; - - public int CutsceneParent(int idx) - => Available ? _cutsceneParent.Invoke(idx) : -1; - - public void RedrawObject(GameObject? actor, RedrawType settings) - { - if (actor == null || !Available) - return; - - try - { - _redrawSubscriber.Invoke(actor, settings); - } - catch (Exception e) - { - PluginLog.Debug($"Failure redrawing object:\n{e}"); - } - } + //private static void UpdateItem(Actor actor, Item2 item2) + //{ + // if (!actor || !actor.DrawObject) + // return; + // + // switch (item2.EquippableTo) + // { + // case EquipSlot.MainHand: + // { + // var off = item2.HasSubModel + // ? new CharacterWeapon(item2.SubModel.id, item2.SubModel.type, item2.SubModel.variant, actor.DrawObject.OffHand.Stain) + // : item2.IsBothHand + // ? CharacterWeapon.Empty + // : actor.OffHand; + // var main = new CharacterWeapon(item2.MainModel.id, item2.MainModel.type, item2.MainModel.variant, + // actor.DrawObject.MainHand.Stain); + // Glamourer.RedrawManager.LoadWeapon(actor, main, off); + // return; + // } + // case EquipSlot.OffHand: + // { + // var off = new CharacterWeapon(item2.MainModel.id, item2.MainModel.type, item2.MainModel.variant, + // actor.DrawObject.OffHand.Stain); + // var main = actor.MainHand; + // Glamourer.RedrawManager.LoadWeapon(actor, main, off); + // return; + // } + // default: + // { + // var current = actor.DrawObject.Equip[item2.EquippableTo]; + // var armor = new CharacterArmor(item2.MainModel.id, (byte)item2.MainModel.variant, current.Stain); + // Glamourer.RedrawManager.UpdateSlot(actor.DrawObject, item2.EquippableTo, armor); + // return; + // } + // } + //} // Update objects without triggering PlayerWatcher Events, // then manually redraw using Penumbra. diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index fc42c3e..e33a230 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -1,7 +1,6 @@ using System; using Glamourer.Customization; using Glamourer.Util; -using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -103,6 +102,9 @@ public class DesignBase return SetArmor(slot, set, variant, name, itemId); } + 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) { if (!force) diff --git a/Glamourer/Designs/Structs.cs b/Glamourer/Designs/Structs.cs index d9aa5ec..ac2a9ae 100644 --- a/Glamourer/Designs/Structs.cs +++ b/Glamourer/Designs/Structs.cs @@ -1,5 +1,4 @@ using Dalamud.Utility; -using Glamourer.Util; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -62,6 +61,9 @@ public readonly struct Weapon => Model.Stain; + public Weapon WithStain(StainId id) + => new(Name, ItemId, Model with { Stain = id }, Type); + public Weapon(string name, uint itemId, CharacterWeapon weapon, FullEquipType type) { Name = name; @@ -81,4 +83,23 @@ public readonly struct Weapon ? new Weapon() : new Weapon(name, itemId, weapon, offType); } + + public Weapon(Lumina.Excel.GeneratedSheets.Item item, bool offhand) + { + Name = string.Intern(item.Name.ToDalamudString().TextValue); + ItemId = item.RowId; + Type = item.ToEquipType(); + var offType = Type.Offhand(); + var model = offhand && offType == Type ? item.ModelSub : item.ModelMain; + Valid = Type.ToSlot() switch + { + EquipSlot.MainHand when !offhand => true, + EquipSlot.MainHand when offhand && offType == Type => true, + EquipSlot.OffHand when offhand => true, + _ => false, + }; + Model.Set = (SetId)model; + Model.Type = (WeaponType)(model >> 16); + Model.Variant = (byte)(model >> 32); + } } diff --git a/Glamourer/DrawObjectManager.cs b/Glamourer/DrawObjectManager.cs new file mode 100644 index 0000000..0ed68a5 --- /dev/null +++ b/Glamourer/DrawObjectManager.cs @@ -0,0 +1,88 @@ +using System; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using Glamourer.Api; +using Glamourer.Customization; +using Glamourer.Interop; +using Glamourer.State; +using Glamourer.Util; +using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; +using CustomizeData = Penumbra.GameData.Structs.CustomizeData; + +namespace Glamourer; + +public class DrawObjectManager : IDisposable +{ + private readonly ItemManager _items; + private readonly ActorManager _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, + PenumbraAttach penumbra) + { + _items = items; + _actors = actors; + _manager = manager; + _interop = interop; + _penumbra = penumbra; + + _interop.EquipUpdate += FixEquipment; + _penumbra.CreatingCharacterBase += ApplyActiveDesign; + } + + private void FixEquipment(DrawObject drawObject, EquipSlot slot, ref CharacterArmor item) + { + var customize = drawObject.Customize; + var (changed, newArmor) = _items.RestrictedGear.ResolveRestricted(item, slot, customize.Race, customize.Gender); + if (!changed) + return; + + Glamourer.Log.Verbose( + $"Invalid armor {item.Set.Value}-{item.Variant} for {customize.Gender} {customize.Race} changed to {newArmor.Set.Value}-{newArmor.Variant}."); + item = newArmor; + } + + private unsafe void ApplyActiveDesign(nint gameObjectPtr, string collectionName, nint modelIdPtr, nint customizePtr, nint equipDataPtr) + { + var gameObject = (Actor)gameObjectPtr; + ref var modelId = ref *(uint*)modelIdPtr; + // Do not apply anything if the game object model id does not correspond to the draw object model id. + // This is the case if the actor is transformed to a different creature. + if (gameObject.ModelId != modelId) + return; + + var identifier = _actors.FromObject((GameObject*)gameObjectPtr, out _, true, true); + if (!identifier.IsValid || !_manager.TryGetValue(identifier, out var design)) + return; + + // Compare game object customize data against draw object customize data for transformations. + // Apply customization if they correspond and there is customization to apply. + var gameObjectCustomize = gameObject.Customize; + var customize = new Customize((CustomizeData*)customizePtr); + if (gameObjectCustomize.Equals(customize)) + customize.Load(design.Customize()); + + // Compare game object equip data against draw object equip data for transformations. + // Apply each piece of equip that should be applied if they correspond. + var gameObjectEquip = gameObject.Equip; + var equip = new CharacterEquip((CharacterArmor*)equipDataPtr); + if (gameObjectEquip.Equals(equip)) + { + var saveEquip = design.Equipment(); + foreach (var slot in EquipSlotExtensions.EquipmentSlots) + { + (_, equip[slot]) = + Glamourer.Items.RestrictedGear.ResolveRestricted(saveEquip[slot], slot, customize.Race, customize.Gender); + } + } + } + + public void Dispose() + { + _penumbra.CreatingCharacterBase -= ApplyActiveDesign; + _interop.EquipUpdate -= FixEquipment; + } +} diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 00f1f62..03e539c 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -1,11 +1,10 @@ using System.Collections.Generic; using System.IO; -using System.Linq; using System.Reflection; -using System.Security.Cryptography; using Dalamud.Game.Command; using Dalamud.Interface.Windowing; using Dalamud.Plugin; +using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Api; using Glamourer.Customization; using Glamourer.Designs; @@ -13,7 +12,6 @@ using Glamourer.Gui; using Glamourer.Interop; using Glamourer.State; using Glamourer.Util; -using ImGuizmoNET; using OtterGui.Classes; using OtterGui.Log; using Penumbra.GameData.Actors; @@ -39,18 +37,21 @@ public class Glamourer : IDalamudPlugin public static GlamourerConfig Config = null!; public static Logger Log = null!; public static ActorManager Actors = null!; - public static PenumbraAttach Penumbra = null!; public static ICustomizationManager Customization = null!; public static RedrawManager RedrawManager = null!; public static ItemManager Items = null!; public readonly FixedDesigns FixedDesigns; - public readonly CurrentManipulations CurrentManipulations; - private readonly Design.Manager _designManager; - private readonly DesignFileSystem _fileSystem; - private readonly FrameworkManager _framework; - private readonly WindowSystem _windowSystem = new("Glamourer"); - private readonly Interface _interface; + 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; @@ -65,24 +66,28 @@ public class Glamourer : IDalamudPlugin 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(); + Config = GlamourerConfig.Load(); - Penumbra = new PenumbraAttach(Config.AttachToPenumbra); - Actors = new ActorManager(Dalamud.PluginInterface, Dalamud.Objects, Dalamud.ClientState, Dalamud.GameData, Dalamud.GameGui, - i => (short)Penumbra.CutsceneParent(i)); + _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(); - CurrentManipulations = new CurrentManipulations(); + + _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, CurrentManipulations); + RedrawManager = new RedrawManager(FixedDesigns, _stateManager); + _drawObjectManager = new DrawObjectManager(Items, Actors, _stateManager, _interop, _penumbra); Dalamud.Commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { @@ -93,7 +98,7 @@ public class Glamourer : IDalamudPlugin HelpMessage = $"Use Glamourer Functions: {HelpString}", }); - _interface = new Interface(Items, CurrentManipulations, _designManager, _fileSystem); + _interface = new Interface(Dalamud.PluginInterface, Items, _stateManager, _designManager, _fileSystem, _objectManager); _windowSystem.AddWindow(_interface); Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw; //FixedDesignManager.Flag((Human*)((Actor)Dalamud.ClientState.LocalPlayer?.Address).Pointer->GameObject.DrawObject, 0, &x); @@ -108,13 +113,15 @@ public class Glamourer : IDalamudPlugin public void Dispose() { + _drawObjectManager?.Dispose(); RedrawManager?.Dispose(); - Penumbra?.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); diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs index 96bc6fe..b68624e 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs @@ -1,11 +1,12 @@ -using System.Numerics; +using System; +using System.Numerics; using Glamourer.Customization; using ImGuiNET; using OtterGui.Raii; namespace Glamourer.Gui.Customization; -internal partial class CustomizationDrawer +public partial class CustomizationDrawer { private const string ColorPickerPopupName = "ColorPicker"; @@ -26,7 +27,6 @@ internal partial class CustomizationDrawer DataInputInt(current); ImGui.TextUnformatted(_currentOption); } - DrawColorPickerPopup(); } @@ -56,10 +56,9 @@ internal partial class CustomizationDrawer private (int, CustomizeData) GetCurrentCustomization(CustomizeIndex index) { var current = _set.DataByValue(index, _customize[index], out var custom, _customize.Face); - if (!_set.IsAvailable(index) || current >= 0) - return (current, custom!.Value); + if (_set.IsAvailable(index) && current < 0) + throw new Exception($"Read invalid customization value {_customize[index]} for {index}."); - Glamourer.Log.Warning($"Read invalid customization value {_customize[index]} for {index}."); - return (0, _set.Data(index, 0)); + return (current, custom!.Value); } -} +} \ No newline at end of file diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs index bc97122..47e185c 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs @@ -6,12 +6,12 @@ using Glamourer.Util; using ImGuiNET; using OtterGui; using OtterGui.Raii; -using Penumbra.Api.Enums; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; namespace Glamourer.Gui.Customization; -internal partial class CustomizationDrawer +public partial class CustomizationDrawer { private void DrawRaceGenderSelector() { @@ -26,21 +26,20 @@ internal partial class CustomizationDrawer private void DrawGenderSelector() { - using var font = ImRaii.PushFont(UiBuilder.IconFont); - var icon = _customize.Gender == Gender.Male ? FontAwesomeIcon.Mars : FontAwesomeIcon.Venus; - var restricted = _customize.Race == Race.Hrothgar; - if (restricted) - icon = FontAwesomeIcon.MarsDouble; + using var font = ImRaii.PushFont(UiBuilder.IconFont); + var icon = _customize.Gender switch + { + Gender.Male when _customize.Race is Race.Hrothgar => FontAwesomeIcon.MarsDouble, + Gender.Male => FontAwesomeIcon.Mars, + Gender.Female => FontAwesomeIcon.Venus, - if (!ImGuiUtil.DrawDisabledButton(icon.ToIconString(), _framedIconSize, string.Empty, restricted, true)) + _ => throw new Exception($"Gender value {_customize.Gender} is not a valid gender for a design."), + }; + + if (!ImGuiUtil.DrawDisabledButton(icon.ToIconString(), _framedIconSize, string.Empty, icon == FontAwesomeIcon.MarsDouble, true)) return; - var gender = _customize.Gender == Gender.Male ? Gender.Female : Gender.Male; - if (!_customize.ChangeGender(_equip, gender)) - return; - - foreach (var actor in _actors.Where(a => a)) - Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw); + Changed |= _customize.ChangeGender(CharacterEquip.Null, _customize.Gender is Gender.Male ? Gender.Female : Gender.Male); } private void DrawRaceCombo() @@ -52,12 +51,8 @@ internal partial class CustomizationDrawer foreach (var subRace in Enum.GetValues().Skip(1)) // Skip Unknown { - if (!ImGui.Selectable(CustomizeExtensions.ClanName(subRace, _customize.Gender), subRace == _customize.Clan) - || !_customize.ChangeRace(_equip, subRace)) - continue; - - foreach (var actor in _actors.Where(a => a && a.DrawObject)) - Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw); + if (ImGui.Selectable(CustomizeExtensions.ClanName(subRace, _customize.Gender), subRace == _customize.Clan)) + Changed |= _customize.ChangeRace(CharacterEquip.Null, subRace); } } } diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index 039b703..2276bb6 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -1,16 +1,14 @@ using System; -using System.Linq; using System.Numerics; using Glamourer.Customization; using ImGuiNET; using OtterGui; using OtterGui.Raii; -using Penumbra.Api.Enums; using Penumbra.GameData.Enums; namespace Glamourer.Gui.Customization; -internal partial class CustomizationDrawer +public partial class CustomizationDrawer { private const string IconSelectorPopup = "Style Picker"; @@ -40,22 +38,23 @@ internal partial class CustomizationDrawer FaceInputInt(current); else DataInputInt(current); + ImGui.TextUnformatted($"{label} ({custom.Value.Value})"); } DrawIconPickerPopup(); } - private void UpdateFace(CustomizeData data) + private bool UpdateFace(CustomizeData data) { // Hrothgar Hack var value = _set.Race == Race.Hrothgar ? data.Value + 4 : data.Value; if (_customize.Face == value) - return; + return false; - _customize.Face = value; - foreach (var actor in _actors) - Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw); + _customize.Face = value; + Changed |= CustomizeFlag.Face; + return true; } private void FaceInputInt(int currentIndex) @@ -78,6 +77,7 @@ internal 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) @@ -107,4 +107,45 @@ internal partial class CustomizationDrawer ImGui.SameLine(); } } + + + // Only used for facial features, so fixed ID. + private void DrawMultiIconSelector() + { + using var bigGroup = ImRaii.Group(); + DrawMultiIcons(); + ImGui.SameLine(); + using var group = ImRaii.Group(); + ImGui.Dummy(new Vector2(0, ImGui.GetTextLineHeightWithSpacing() + ImGui.GetStyle().ItemSpacing.Y / 2)); + + _currentCount = 256; + PercentageInputInt(); + + ImGui.TextUnformatted(_set.Option(CustomizeIndex.LegacyTattoo)); + } + + private void DrawMultiIcons() + { + var options = _set.Order[CharaMakeParams.MenuType.IconCheckmark]; + using var _ = ImRaii.Group(); + foreach (var (featureIdx, idx) in options.WithIndex()) + { + using var id = SetId(featureIdx); + 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); + if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X, + Vector4.Zero, enabled ? Vector4.One : _redTint)) + { + _customize.Set(featureIdx, enabled ? CustomizeValue.Zero : CustomizeValue.Max); + Changed |= _currentFlag; + } + + ImGuiUtil.HoverIconTooltip(icon, _iconSize); + if (idx % 4 != 3) + ImGui.SameLine(); + } + } } diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Main.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Main.cs deleted file mode 100644 index 679b8cb..0000000 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Main.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Reflection; -using Glamourer.Customization; -using Glamourer.Interop; -using ImGuiNET; -using OtterGui; -using OtterGui.Raii; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; - -namespace Glamourer.Gui.Customization; - -internal partial class CustomizationDrawer -{ - private static readonly Vector4 RedTint = new(0.6f, 0.3f, 0.3f, 1f); - private static readonly ImGuiScene.TextureWrap? LegacyTattoo; - - private readonly Vector2 _iconSize; - private readonly Vector2 _framedIconSize; - private readonly float _inputIntSize; - private readonly float _comboSelectorSize; - private readonly float _raceSelectorWidth; - - private Customize _customize; - private CharacterEquip _equip; - private IReadOnlyCollection _actors = Array.Empty(); - private CustomizationSet _set = null!; - - private CustomizationDrawer() - { - _iconSize = new Vector2(ImGui.GetTextLineHeightWithSpacing() * 2); - _framedIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding; - _inputIntSize = 2 * _framedIconSize.X + ImGui.GetStyle().ItemSpacing.X; - _comboSelectorSize = 4 * _framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X; - _raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X; - } - - static CustomizationDrawer() - => LegacyTattoo = GetLegacyTattooIcon(); - - public static void Dispose() - => LegacyTattoo?.Dispose(); - - private static ImGuiScene.TextureWrap? GetLegacyTattooIcon() - { - using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Glamourer.LegacyTattoo.raw"); - if (resource == null) - return null; - - var rawImage = new byte[resource.Length]; - var length = resource.Read(rawImage, 0, (int)resource.Length); - return length == resource.Length - ? Dalamud.PluginInterface.UiBuilder.LoadImageRaw(rawImage, 192, 192, 4) - : null; - } - - public static void Draw(Customize customize, CharacterEquip equip, IReadOnlyCollection actors, bool locked) - { - var d = new CustomizationDrawer() - { - _customize = customize, - _equip = equip, - _actors = actors, - }; - - - if (!ImGui.CollapsingHeader("Character Customization")) - return; - - using var disabled = ImRaii.Disabled(locked); - - d.DrawRaceGenderSelector(); - - d._set = Glamourer.Customization.GetList(customize.Clan, customize.Gender); - - foreach (var id in d._set.Order[CharaMakeParams.MenuType.Percentage]) - d.PercentageSelector(id); - - Functions.IteratePairwise(d._set.Order[CharaMakeParams.MenuType.IconSelector], d.DrawIconSelector, ImGui.SameLine); - - d.DrawMultiIconSelector(); - - foreach (var id in d._set.Order[CharaMakeParams.MenuType.ListSelector]) - d.DrawListSelector(id); - - Functions.IteratePairwise(d._set.Order[CharaMakeParams.MenuType.ColorPicker], d.DrawColorPicker, ImGui.SameLine); - - Functions.IteratePairwise(d._set.Order[CharaMakeParams.MenuType.Checkmark], d.DrawCheckbox, - () => ImGui.SameLine(d._inputIntSize + d._framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X)); - } - - public static void Draw(Customize customize, IReadOnlyCollection actors, bool locked = false) - => Draw(customize, CharacterEquip.Null, actors, locked); - - public static void Draw(Customize customize, CharacterEquip equip, bool locked = false) - => Draw(customize, equip, Array.Empty(), locked); - - public static void Draw(Customize customize, bool locked = false) - => Draw(customize, CharacterEquip.Null, Array.Empty(), locked); - - // Set state for drawing of current customization. - private CustomizeIndex _currentIndex; - private CustomizeValue _currentByte = CustomizeValue.Zero; - private int _currentCount; - private string _currentOption = string.Empty; - - // Prepare a new customization option. - private ImRaii.Id SetId(CustomizeIndex index) - { - _currentIndex = index; - _currentByte = _customize[index]; - _currentCount = _set.Count(index, _customize.Face); - _currentOption = _set.Option(index); - return ImRaii.PushId((int)index); - } - - // Update the current id with a value, - // also update actors if any. - private void UpdateValue(CustomizeValue value) - { - if (_customize[_currentIndex] == value) - return; - - _customize[_currentIndex] = value; - UpdateActors(); - } - - // Update all relevant Actors by calling the UpdateCustomize game function. - private void UpdateActors() - { - foreach (var actor in _actors) - Glamourer.RedrawManager.UpdateCustomize(actor, _customize); - } -} diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Multi.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Multi.cs deleted file mode 100644 index e23d5bd..0000000 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Multi.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Numerics; -using Glamourer.Customization; -using ImGuiNET; -using OtterGui; -using OtterGui.Raii; - -namespace Glamourer.Gui.Customization; - -internal partial class CustomizationDrawer -{ - // Only used for facial features, so fixed ID. - private void DrawMultiIconSelector() - { - using var bigGroup = ImRaii.Group(); - DrawMultiIcons(); - ImGui.SameLine(); - using var group = ImRaii.Group(); - ImGui.Dummy(new Vector2(0, ImGui.GetTextLineHeightWithSpacing() + ImGui.GetStyle().ItemSpacing.Y / 2)); - - _currentCount = 256; - PercentageInputInt(); - - ImGui.TextUnformatted(_set.Option(CustomizeIndex.LegacyTattoo)); - } - - private void DrawMultiIcons() - { - var options = _set.Order[CharaMakeParams.MenuType.IconCheckmark]; - using var _ = ImRaii.Group(); - foreach (var (featureIdx, idx) in options.WithIndex()) - { - using var id = SetId(featureIdx); - 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); - if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X, - Vector4.Zero, enabled ? Vector4.One : RedTint)) - { - _customize.Set(featureIdx, enabled ? CustomizeValue.Zero : CustomizeValue.Max); - UpdateActors(); - } - - ImGuiUtil.HoverIconTooltip(icon, _iconSize); - if (idx % 4 != 3) - ImGui.SameLine(); - } - } -} diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs index 9714463..6c5fb82 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs @@ -6,44 +6,8 @@ using OtterGui.Raii; namespace Glamourer.Gui.Customization; -internal partial class CustomizationDrawer +public partial class CustomizationDrawer { - private void DrawListSelector(CustomizeIndex index) - { - using var _ = SetId(index); - using var bigGroup = ImRaii.Group(); - - ListCombo(); - ImGui.SameLine(); - ListInputInt(); - ImGui.SameLine(); - ImGui.TextUnformatted(_currentOption); - } - - private void ListCombo() - { - ImGui.SetNextItemWidth(_comboSelectorSize * ImGui.GetIO().FontGlobalScale); - using var combo = ImRaii.Combo("##combo", $"{_currentOption} #{_currentByte.Value + 1}"); - - if (!combo) - return; - - for (var i = 0; i < _currentCount; ++i) - { - if (ImGui.Selectable($"{_currentOption} #{i + 1}##combo", i == _currentByte.Value)) - UpdateValue((CustomizeValue)i); - } - } - - private void ListInputInt() - { - var tmp = _currentByte.Value + 1; - ImGui.SetNextItemWidth(_inputIntSize); - if (ImGui.InputInt("##text", ref tmp, 1, 1) && tmp > 0 && tmp <= _currentCount) - UpdateValue((CustomizeValue)Math.Clamp(tmp - 1, 0, _currentCount - 1)); - ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]"); - } - private void PercentageSelector(CustomizeIndex index) { using var _ = SetId(index); @@ -73,26 +37,6 @@ internal partial class CustomizationDrawer ImGuiUtil.HoverTooltip($"Input Range: [0, {_currentCount - 1}]"); } - - // Draw one of the four checkboxes for single bool customization options. - private void Checkbox(string label, bool current, Action setter) - { - var tmp = current; - if (ImGui.Checkbox(label, ref tmp) && tmp != current) - { - setter(tmp); - UpdateActors(); - } - } - - // Draw a customize checkbox. - private void DrawCheckbox(CustomizeIndex idx) - { - using var id = SetId(idx); - Checkbox(_set.Option(idx), _customize.Get(idx) != CustomizeValue.Zero, - b => _customize.Set(idx, b ? CustomizeValue.Max : CustomizeValue.Zero)); - } - // Integral input for an icon- or color based item. private void DataInputInt(int currentIndex) { @@ -107,4 +51,52 @@ internal partial class CustomizationDrawer ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]"); } + + private void DrawListSelector(CustomizeIndex index) + { + using var _ = SetId(index); + using var bigGroup = ImRaii.Group(); + + ListCombo(); + ImGui.SameLine(); + ListInputInt(); + ImGui.SameLine(); + ImGui.TextUnformatted(_currentOption); + } + + private void ListCombo() + { + ImGui.SetNextItemWidth(_comboSelectorSize * ImGui.GetIO().FontGlobalScale); + using var combo = ImRaii.Combo("##combo", $"{_currentOption} #{_currentByte.Value + 1}"); + + if (!combo) + return; + + for (var i = 0; i < _currentCount; ++i) + { + if (ImGui.Selectable($"{_currentOption} #{i + 1}##combo", i == _currentByte.Value)) + UpdateValue((CustomizeValue)i); + } + } + + private void ListInputInt() + { + var tmp = _currentByte.Value + 1; + ImGui.SetNextItemWidth(_inputIntSize); + if (ImGui.InputInt("##text", ref tmp, 1, 1) && tmp > 0 && tmp <= _currentCount) + UpdateValue((CustomizeValue)Math.Clamp(tmp - 1, 0, _currentCount - 1)); + ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]"); + } + + // Draw a customize checkbox. + private void DrawCheckbox(CustomizeIndex idx) + { + using var id = SetId(idx); + var tmp = _currentByte != CustomizeValue.Zero; + if (ImGui.Checkbox(_currentOption, ref tmp)) + { + _customize.Set(idx, tmp ? CustomizeValue.Max : CustomizeValue.Zero); + Changed |= _currentFlag; + } + } } diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs new file mode 100644 index 0000000..02fc1e5 --- /dev/null +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -0,0 +1,172 @@ +using System; +using System.Numerics; +using System.Reflection; +using System.Runtime.InteropServices; +using Dalamud.Plugin; +using Glamourer.Customization; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using CustomizeData = Penumbra.GameData.Structs.CustomizeData; + +namespace Glamourer.Gui.Customization; + +public partial class CustomizationDrawer : IDisposable +{ + private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f); + private readonly ImGuiScene.TextureWrap? _legacyTattoo; + + private bool _withFlags = false; + private Exception? _terminate = null; + + private readonly unsafe CustomizeData* _data; + + private Customize _customize; + private CustomizationSet _set = null!; + + public unsafe CustomizeData CustomizeData + => *_data; + + public CustomizeFlag CurrentFlag { get; private set; } + public CustomizeFlag Changed { get; private set; } + + public bool RequiresRedraw + => Changed.RequiresRedraw(); + + private bool _locked = false; + private Vector2 _iconSize; + private Vector2 _framedIconSize; + private float _inputIntSize; + private float _comboSelectorSize; + private float _raceSelectorWidth; + + public CustomizationDrawer(DalamudPluginInterface pi) + { + _legacyTattoo = GetLegacyTattooIcon(pi); + unsafe + { + _data = (CustomizeData*)Marshal.AllocHGlobal(sizeof(CustomizeData)); + *_data = Customize.Default; + _customize = new Customize(_data); + } + } + + public void Dispose() + { + _legacyTattoo?.Dispose(); + unsafe + { + Marshal.FreeHGlobal((nint)_data); + } + } + + public bool Draw(Customize current, bool locked) + { + _withFlags = false; + CurrentFlag = CustomizeFlagExtensions.All; + Init(current, locked); + return DrawInternal(); + } + + public bool Draw(Customize current, CustomizeFlag currentFlags, bool locked) + { + _withFlags = true; + CurrentFlag = currentFlags; + Init(current, locked); + return DrawInternal(); + } + + private void Init(Customize current, bool locked) + { + UpdateSizes(); + _terminate = null; + Changed = 0; + _customize.Load(current); + _locked = locked; + } + + // Set state for drawing of current customization. + private CustomizeIndex _currentIndex; + private CustomizeFlag _currentFlag; + private CustomizeValue _currentByte = CustomizeValue.Zero; + private int _currentCount; + private string _currentOption = string.Empty; + + // Prepare a new customization option. + private ImRaii.Id SetId(CustomizeIndex index) + { + _currentIndex = index; + _currentFlag = index.ToFlag(); + _currentByte = _customize[index]; + _currentCount = _set.Count(index, _customize.Face); + _currentOption = _set.Option(index); + return ImRaii.PushId((int)index); + } + + // Update the current id with a new value. + private void UpdateValue(CustomizeValue value) + { + if (_currentByte == value) + return; + + _customize[_currentIndex] = value; + Changed |= _currentFlag; + } + + private bool DrawInternal() + { + using var disabled = ImRaii.Disabled(_locked); + + try + { + DrawRaceGenderSelector(); + _set = Glamourer.Customization.GetList(_customize.Clan, _customize.Gender); + + foreach (var id in _set.Order[CharaMakeParams.MenuType.Percentage]) + PercentageSelector(id); + + Functions.IteratePairwise(_set.Order[CharaMakeParams.MenuType.IconSelector], DrawIconSelector, ImGui.SameLine); + + DrawMultiIconSelector(); + + foreach (var id in _set.Order[CharaMakeParams.MenuType.ListSelector]) + DrawListSelector(id); + + Functions.IteratePairwise(_set.Order[CharaMakeParams.MenuType.ColorPicker], DrawColorPicker, ImGui.SameLine); + + Functions.IteratePairwise(_set.Order[CharaMakeParams.MenuType.Checkmark], DrawCheckbox, + () => ImGui.SameLine(_inputIntSize + _framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X)); + return Changed != 0; + } + catch (Exception ex) + { + _terminate = ex; + using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF4040FF); + ImGui.NewLine(); + ImGuiUtil.TextWrapped(_terminate.ToString()); + return false; + } + } + + private void UpdateSizes() + { + _iconSize = new Vector2(ImGui.GetTextLineHeightWithSpacing() * 2); + _framedIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding; + _inputIntSize = 2 * _framedIconSize.X + ImGui.GetStyle().ItemSpacing.X; + _comboSelectorSize = 4 * _framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X; + _raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X; + } + + private static ImGuiScene.TextureWrap? GetLegacyTattooIcon(DalamudPluginInterface pi) + { + using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Glamourer.LegacyTattoo.raw"); + if (resource == null) + return null; + + var rawImage = new byte[resource.Length]; + var length = resource.Read(rawImage, 0, (int)resource.Length); + return length == resource.Length + ? pi.UiBuilder.LoadImageRaw(rawImage, 192, 192, 4) + : null; + } +} diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 0e1406e..cd39d5f 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -1,7 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Dalamud.Interface; +using Dalamud.Utility; using Glamourer.Designs; using Glamourer.Util; using ImGuiNET; @@ -13,26 +15,55 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Equipment; -public partial class EquipmentDrawer +public class EquipmentDrawer { - private readonly FilterComboColors _stainCombo; - private readonly StainData _stainData; - private readonly ItemCombo[] _itemCombo; + private readonly ItemManager _items; + private readonly FilterComboColors _stainCombo; + private readonly StainData _stainData; + private readonly ItemCombo[] _itemCombo; + private readonly Dictionary<(FullEquipType, EquipSlot), WeaponCombo> _weaponCombo; public EquipmentDrawer(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(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)); + else if (type.ToSlot() is EquipSlot.OffHand) + _weaponCombo.TryAdd((type, EquipSlot.OffHand), new WeaponCombo(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.Add((FullEquipType.Unknown, EquipSlot.MainHand), new WeaponCombo(items, FullEquipType.Unknown, EquipSlot.MainHand)); } - public bool DrawArmor(DesignBase design, EquipSlot slot, out Item armor) + private string VerifyRestrictedGear(Item gear, EquipSlot slot, Gender gender, Race race) { - Debug.Assert(slot.IsEquipment() || slot.IsAccessory()); + if (slot.IsAccessory()) + return gear.Name; + + var (changed, _) = _items.RestrictedGear.ResolveRestricted(gear.Model, slot, race, gender); + if (changed) + return gear.Name + " (Restricted)"; + + return gear.Name; + } + + public bool DrawArmor(Item current, EquipSlot slot, out Item armor, Gender gender = Gender.Unknown, Race race = Race.Unknown) + { + Debug.Assert(slot.IsEquipment() || slot.IsAccessory(), $"Called {nameof(DrawArmor)} on {slot}."); var combo = _itemCombo[slot.ToIndex()]; - armor = design.Armor(slot); - var change = combo.Draw(armor.Name, armor.ItemId, 320 * ImGuiHelpers.GlobalScale); + armor = current; + var change = combo.Draw(VerifyRestrictedGear(armor, slot, gender, race), armor.ItemId, 320 * ImGuiHelpers.GlobalScale); if (armor.ModelBase.Value != 0) { ImGuiUtil.HoverTooltip("Right-click to clear."); @@ -41,6 +72,10 @@ public partial class EquipmentDrawer change = true; armor = ItemManager.NothingItem(slot); } + else if (change) + { + armor = combo.CurrentSelection.WithStain(armor.Stain); + } } else if (change) { @@ -50,14 +85,77 @@ public partial class EquipmentDrawer return change; } - public bool DrawStain(DesignBase design, EquipSlot slot, out Stain stain) + public bool DrawStain(StainId current, EquipSlot slot, out Stain stain) { - Debug.Assert(slot.IsEquipment() || slot.IsAccessory()); - var armor = design.Armor(slot); - var found = _stainData.TryGetValue(armor.Stain, out stain); - if (!_stainCombo.Draw($"##stain{slot}", stain.RgbaColor, found)) + var found = _stainData.TryGetValue(current, out stain); + if (!_stainCombo.Draw($"##stain{slot}", stain.RgbaColor, stain.Name, found)) return false; return _stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain); } + + public bool DrawMainhand(Weapon current, bool drawAll, out Weapon weapon) + { + weapon = current; + if (!_weaponCombo.TryGetValue((drawAll ? FullEquipType.Unknown : current.Type, EquipSlot.MainHand), out var combo)) + return false; + + if (!combo.Draw(weapon.Name, weapon.ItemId, 320 * ImGuiHelpers.GlobalScale)) + return false; + + weapon = combo.CurrentSelection.WithStain(current.Stain); + return true; + } + + public bool DrawOffhand(Weapon current, FullEquipType mainType, out Weapon weapon) + { + weapon = current; + var offType = mainType.Offhand(); + if (offType == FullEquipType.Unknown) + return false; + + if (!_weaponCombo.TryGetValue((offType, EquipSlot.OffHand), out var combo)) + return false; + + var change = combo.Draw(weapon.Name, weapon.ItemId, 320 * ImGuiHelpers.GlobalScale); + if (offType.ToSlot() is EquipSlot.OffHand && weapon.ModelBase.Value != 0) + { + ImGuiUtil.HoverTooltip("Right-click to clear."); + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + change = true; + weapon = ItemManager.NothingItem(offType); + } + } + else if (change) + { + weapon = combo.CurrentSelection.WithStain(current.Stain); + } + + return change; + } + + public bool DrawApply(Design design, EquipSlot slot, out bool enabled) + { + enabled = design.DoApplyEquip(slot); + var tmp = enabled; + if (!ImGuiUtil.Checkbox($"##apply{slot}", string.Empty, enabled, v => tmp = v)) + return false; + + var change = enabled != tmp; + enabled = tmp; + return change; + } + + public bool DrawApplyStain(Design design, EquipSlot slot, out bool enabled) + { + enabled = design.DoApplyStain(slot); + var tmp = enabled; + if (!ImGuiUtil.Checkbox($"##applyStain{slot}", string.Empty, enabled, v => tmp = v)) + return false; + + var change = enabled != tmp; + enabled = tmp; + return change; + } } diff --git a/Glamourer/Gui/Equipment/ItemCombo.cs b/Glamourer/Gui/Equipment/ItemCombo.cs index 21a862a..4fe4854 100644 --- a/Glamourer/Gui/Equipment/ItemCombo.cs +++ b/Glamourer/Gui/Equipment/ItemCombo.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; -using System.IO; +using System.Collections.Generic; using System.Linq; -using Dalamud.Interface; -using Glamourer.Structs; using Glamourer.Util; using ImGuiNET; using Lumina.Excel.GeneratedSheets; @@ -12,52 +8,53 @@ using OtterGui.Classes; using OtterGui.Raii; using OtterGui.Widgets; using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; using Item = Glamourer.Designs.Item; namespace Glamourer.Gui.Equipment; public sealed class ItemCombo : FilterComboCache { - public readonly string Label; - public readonly EquipSlot Slot; - - private uint _lastItemId; - private uint _previewId; + public readonly string Label; + private uint _currentItem; public ItemCombo(ItemManager items, EquipSlot slot) - : base(GetItems(items, slot)) + : base(() => GetItems(items, slot)) { Label = GetLabel(slot); - Slot = slot; - _lastItemId = ItemManager.NothingId(slot); - _previewId = _lastItemId; + _currentItem = ItemManager.NothingId(slot); } protected override void DrawList(float width, float itemHeight) { - if (_previewId != _lastItemId) - { - _lastItemId = _previewId; - CurrentSelectionIdx = Items.IndexOf(i => i.ItemId == _lastItemId); - CurrentSelection = Items[CurrentSelectionIdx]; - } base.DrawList(width, itemHeight); if (NewSelection != null && Items.Count > NewSelection.Value) CurrentSelection = Items[NewSelection.Value]; } + protected override int UpdateCurrentSelected(int currentSelected) + { + if (CurrentSelection.ItemId != _currentItem) + { + CurrentSelectionIdx = Items.IndexOf(i => i.ItemId == _currentItem); + CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : default; + return base.UpdateCurrentSelected(CurrentSelectionIdx); + } + + return currentSelected; + } + public bool Draw(string previewName, uint previewIdx, float width) { - _previewId = previewIdx; - return Draw(Label, previewName, width, ImGui.GetTextLineHeightWithSpacing()); + _currentItem = previewIdx; + return Draw(Label, previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing()); } protected override bool DrawSelectable(int globalIdx, bool selected) { - var obj = Items[globalIdx]; - var name = ToString(obj); - var ret = ImGui.Selectable(name, selected); + var obj = Items[globalIdx]; + var name = ToString(obj); + var ret = ImGui.Selectable(name, selected); + ImGui.SameLine(); using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080); ImGuiUtil.RightAlign($"({obj.ModelBase.Value}-{obj.Variant})"); return ret; @@ -89,12 +86,18 @@ public sealed class ItemCombo : FilterComboCache }; } - private static IEnumerable GetItems(ItemManager items, EquipSlot slot) + private static IReadOnlyList GetItems(ItemManager items, EquipSlot slot) { var nothing = ItemManager.NothingItem(slot); if (!items.Items.TryGetValue(slot.ToEquipType(), out var list)) - return new[] { nothing }; + return new[] + { + nothing, + }; - return list.Select(i => new Item(i)).Append(ItemManager.SmallClothesItem(slot)).OrderBy(i => i.Name).Prepend(nothing); + var enumerable = list.Select(i => new Item(i)); + if (slot.IsEquipment()) + enumerable = enumerable.Append(ItemManager.SmallClothesItem(slot)); + return enumerable.OrderBy(i => i.Name).Prepend(nothing).ToList(); } -} +} \ No newline at end of file diff --git a/Glamourer/Gui/Equipment/WeaponCombo.cs b/Glamourer/Gui/Equipment/WeaponCombo.cs new file mode 100644 index 0000000..cc200ab --- /dev/null +++ b/Glamourer/Gui/Equipment/WeaponCombo.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Glamourer.Designs; +using Glamourer.Util; +using ImGuiNET; +using Lumina.Excel.GeneratedSheets; +using OtterGui; +using OtterGui.Classes; +using OtterGui.Raii; +using OtterGui.Widgets; +using Penumbra.GameData.Enums; + +namespace Glamourer.Gui.Equipment; + +public sealed class WeaponCombo : FilterComboCache +{ + public readonly string Label; + private uint _currentItem; + + public WeaponCombo(ItemManager items, FullEquipType type, EquipSlot offhand) + : base(offhand is EquipSlot.OffHand ? () => GetOff(items, type) : () => GetMain(items, type)) + => Label = GetLabel(offhand); + + protected override void DrawList(float width, float itemHeight) + { + base.DrawList(width, itemHeight); + if (NewSelection != null && Items.Count > NewSelection.Value) + CurrentSelection = Items[NewSelection.Value]; + } + + protected override int UpdateCurrentSelected(int currentSelected) + { + if (CurrentSelection.ItemId != _currentItem) + { + CurrentSelectionIdx = Items.IndexOf(i => i.ItemId == _currentItem); + CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : default; + return base.UpdateCurrentSelected(CurrentSelectionIdx); + } + + return currentSelected; + } + + public bool Draw(string previewName, uint previewIdx, float width) + { + _currentItem = previewIdx; + return Draw(Label, previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing()); + } + + protected override bool DrawSelectable(int globalIdx, bool selected) + { + var obj = Items[globalIdx]; + var name = ToString(obj); + var ret = ImGui.Selectable(name, selected); + ImGui.SameLine(); + using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080); + ImGuiUtil.RightAlign($"({obj.ModelBase.Value}-{obj.WeaponBase.Value}-{obj.Variant})"); + return ret; + } + + protected override bool IsVisible(int globalIndex, LowerString filter) + => base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].ModelBase.ToString()); + + protected override string ToString(Weapon obj) + => obj.Name; + + private static string GetLabel(EquipSlot offhand) + { + var sheet = Dalamud.GameData.GetExcelSheet()!; + return offhand is EquipSlot.OffHand + ? sheet.GetRow(739)?.Text.ToString() ?? "Off Hand" + : sheet.GetRow(738)?.Text.ToString() ?? "Main Hand"; + } + + private static IReadOnlyList GetMain(ItemManager items, FullEquipType type) + { + 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))); + else if (type.ToSlot() is EquipSlot.MainHand) + list.AddRange(items.Items[type].Select(w => new Weapon(w, false))); + list.Sort((w1, w2) => string.CompareOrdinal(w1.Name, w2.Name)); + return list; + } + + private static IReadOnlyList GetOff(ItemManager items, FullEquipType type) + { + if (type.ToSlot() == EquipSlot.OffHand) + { + var nothing = ItemManager.NothingItem(type); + if (!items.Items.TryGetValue(type, out var list)) + return new[] + { + nothing, + }; + + return list.Select(w => new Weapon(w, true)).OrderBy(w => w.Name).Prepend(nothing).ToList(); + } + else if (items.Items.TryGetValue(type, out var list)) + { + return list.Select(w => new Weapon(w, true)).OrderBy(w => w.Name).ToList(); + } + + return Array.Empty(); + } +} diff --git a/Glamourer/Gui/Interface.Actors.cs b/Glamourer/Gui/Interface.Actors.cs index 207ebf7..5b2e1d4 100644 --- a/Glamourer/Gui/Interface.Actors.cs +++ b/Glamourer/Gui/Interface.Actors.cs @@ -1,8 +1,7 @@ using System; +using System.Collections.Generic; using System.Numerics; using Dalamud.Interface; -using Glamourer.Gui.Customization; -using Glamourer.Gui.Equipment; using Glamourer.Interop; using Glamourer.State; using ImGuiNET; @@ -20,18 +19,20 @@ internal partial class Interface private class ActorTab { private readonly Interface _main; - private readonly CurrentManipulations _manipulations; + private readonly ActiveDesign.Manager _activeDesigns; + private readonly ObjectManager _objects; - public ActorTab(Interface main, CurrentManipulations manipulations) + public ActorTab(Interface main, ActiveDesign.Manager activeDesigns, ObjectManager objects) { _main = main; - _manipulations = manipulations; + _activeDesigns = activeDesigns; + _objects = objects; } private ActorIdentifier _identifier = ActorIdentifier.Invalid; private ObjectManager.ActorData _currentData = ObjectManager.ActorData.Invalid; private string _currentLabel = string.Empty; - private ActiveDesign? _currentSave; + private ActiveDesign? _currentSave; public void Draw() { @@ -40,7 +41,7 @@ internal partial class Interface return; DrawActorSelector(); - if (!ObjectManager.Actors.TryGetValue(_identifier, out _currentData)) + if (!_objects.TryGetValue(_identifier, out _currentData)) _currentData = ObjectManager.ActorData.Invalid; else _currentLabel = _currentData.Label; @@ -66,14 +67,31 @@ internal partial class Interface _currentSave.Update(_currentData.Objects[0]); RevertButton(); - CustomizationDrawer.Draw(_currentSave.Customize(), _currentSave.Equipment(), _currentData.Objects, - _identifier.Type == IdentifierType.Special); + if (_main._customizationDrawer.Draw(_currentSave.Customize(), _identifier.Type == IdentifierType.Special)) + _activeDesigns.ChangeCustomize(_currentSave, _main._customizationDrawer.Changed, _main._customizationDrawer.CustomizeData, false); foreach (var slot in EquipSlotExtensions.EqdpSlots) { - _main._equipmentDrawer.DrawStain(_currentSave, slot, out var stain); + var current = _currentSave.Armor(slot); + if (_main._equipmentDrawer.DrawStain(current.Stain, slot, out var stain)) + _activeDesigns.ChangeStain(_currentSave, slot, stain.RowIndex, false); ImGui.SameLine(); - _main._equipmentDrawer.DrawArmor(_currentSave, slot, out var armor); + if (_main._equipmentDrawer.DrawArmor(current, slot, out var armor, _currentSave.Customize().Gender, _currentSave.Customize().Race)) + _activeDesigns.ChangeEquipment(_currentSave, slot, armor, false); + } + + var currentMain = _currentSave.WeaponMain; + if (_main._equipmentDrawer.DrawStain(currentMain.Stain, EquipSlot.MainHand, out var stainMain)) + _activeDesigns.ChangeStain(_currentSave, EquipSlot.MainHand, stainMain.RowIndex, false); + ImGui.SameLine(); + _main._equipmentDrawer.DrawMainhand(currentMain, true, out var main); + if (currentMain.Type.Offhand() != FullEquipType.Unknown) + { + var currentOff = _currentSave.WeaponOff; + if (_main._equipmentDrawer.DrawStain(currentOff.Stain, EquipSlot.OffHand, out var stainOff)) + _activeDesigns.ChangeStain(_currentSave, EquipSlot.OffHand, stainOff.RowIndex, false); + ImGui.SameLine(); + _main._equipmentDrawer.DrawOffhand(currentOff, main.Type, out var off); } } @@ -83,18 +101,14 @@ internal partial class Interface private unsafe void RevertButton() { if (ImGui.Button("Revert")) - { - _manipulations.DeleteSave(_identifier); - - //foreach (var actor in _currentData.Objects) - // _currentSave!.ApplyToActor(actor); - // - //if (_currentData.Objects.Count > 0) - // _currentSave = _manipulations.GetOrCreateSave(_currentData.Objects[0]); - // - //_currentSave!.Reset(); - } - + _activeDesigns.RevertDesign(_currentSave!); + //foreach (var actor in _currentData.Objects) + // _currentSave!.ApplyToActor(actor); + // + //if (_currentData.Objects.Count > 0) + // _currentSave = _manipulations.GetOrCreateSave(_currentData.Objects[0]); + // + //_currentSave!.Reset(); if (_currentData.Objects.Count > 0) ImGui.TextUnformatted(_currentData.Objects[0].Pointer->GameObject.DataID.ToString()); //VisorBox(); @@ -215,24 +229,24 @@ internal partial class Interface if (!child) return; - ObjectManager.Update(); + _objects.Update(); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, oldSpacing); var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight()); - var remainder = ImGuiClip.FilteredClippedDraw(ObjectManager.List, skips, CheckFilter, DrawSelectable); + var remainder = ImGuiClip.FilteredClippedDraw(_objects, skips, CheckFilter, DrawSelectable); ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); } - private bool CheckFilter((ActorIdentifier, ObjectManager.ActorData) pair) - => _actorFilter.IsEmpty || pair.Item2.Label.Contains(_actorFilter.Lower, StringComparison.OrdinalIgnoreCase); + private bool CheckFilter(KeyValuePair pair) + => _actorFilter.IsEmpty || pair.Value.Label.Contains(_actorFilter.Lower, StringComparison.OrdinalIgnoreCase); - private void DrawSelectable((ActorIdentifier, ObjectManager.ActorData) pair) + private void DrawSelectable(KeyValuePair pair) { - var equal = pair.Item1.Equals(_identifier); - if (ImGui.Selectable(pair.Item2.Label, equal) && !equal) + var equal = pair.Key.Equals(_identifier); + if (ImGui.Selectable(pair.Value.Label, equal) && !equal) { - _identifier = pair.Item1.CreatePermanent(); - _currentData = pair.Item2; - _currentSave = _currentData.Valid ? _manipulations.GetOrCreateSave(_currentData.Objects[0]) : null; + _identifier = pair.Key.CreatePermanent(); + _currentData = pair.Value; + _currentSave = _currentData.Valid ? _activeDesigns.GetOrCreateSave(_currentData.Objects[0]) : null; } } @@ -243,325 +257,14 @@ internal partial class Interface var buttonWidth = new Vector2(_actorSelectorWidth / 2, 0); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth - , "Select the local player character.", !ObjectManager.Player, true)) - _identifier = ObjectManager.Player.GetIdentifier(); + , "Select the local player character.", !_objects.Player, true)) + _identifier = _objects.Player.GetIdentifier(); ImGui.SameLine(); Actor targetActor = Dalamud.Targets.Target?.Address; if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth, - "Select the current target, if it is in the list.", ObjectManager.IsInGPose || !targetActor, true)) + "Select the current target, if it is in the list.", _objects.IsInGPose || !targetActor, true)) _identifier = targetActor.GetIdentifier(); } } } - -//internal partial class Interface -//{ -// private readonly CharacterSave _currentSave = new(); -// private string _newDesignName = string.Empty; -// private bool _keyboardFocus; -// private bool _holdShift; -// private bool _holdCtrl; -// private const string DesignNamePopupLabel = "Save Design As..."; -// private const uint RedHeaderColor = 0xFF1818C0; -// private const uint GreenHeaderColor = 0xFF18C018; -// -// private void DrawPlayerHeader() -// { -// var color = _player == null ? RedHeaderColor : GreenHeaderColor; -// var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg); -// using var c = ImRaii.PushColor(ImGuiCol.Text, color) -// .Push(ImGuiCol.Button, buttonColor) -// .Push(ImGuiCol.ButtonHovered, buttonColor) -// .Push(ImGuiCol.ButtonActive, buttonColor); -// using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) -// .Push(ImGuiStyleVar.FrameRounding, 0); -// ImGui.Button($"{_currentLabel}##playerHeader", -Vector2.UnitX * 0.0001f); -// } -// -// private static void DrawCopyClipboardButton(CharacterSave save) -// { -// ImGui.PushFont(UiBuilder.IconFont); -// if (ImGui.Button(FontAwesomeIcon.Clipboard.ToIconString())) -// ImGui.SetClipboardText(save.ToBase64()); -// ImGui.PopFont(); -// ImGuiUtil.HoverTooltip("Copy customization code to clipboard."); -// } -// -// private static unsafe void ConditionalApply(CharacterSave save, FFXIVClientStructs.FFXIV.Client.Game.Character.Character* player) -// { -// //if (ImGui.GetIO().KeyShift) -// // save.ApplyOnlyCustomizations(player); -// //else if (ImGui.GetIO().KeyCtrl) -// // save.ApplyOnlyEquipment(player); -// //else -// // save.Apply(player); -// } -// -// private static unsafe void ConditionalApply(CharacterSave save, Character player) -// => ConditionalApply(save, (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)player.Address); -// -// private static CharacterSave ConditionalCopy(CharacterSave save, bool shift, bool ctrl) -// { -// var copy = save.Copy(); -// if (shift) -// { -// copy.Load(new CharacterEquipment()); -// copy.SetHatState = false; -// copy.SetVisorState = false; -// copy.SetWeaponState = false; -// copy.WriteEquipment = CharacterEquipMask.None; -// } -// else if (ctrl) -// { -// copy.Load(CharacterCustomization.Default); -// copy.SetHatState = false; -// copy.SetVisorState = false; -// copy.SetWeaponState = false; -// copy.WriteCustomizations = false; -// } -// -// return copy; -// } -// -// private bool DrawApplyClipboardButton() -// { -// ImGui.PushFont(UiBuilder.IconFont); -// var applyButton = ImGui.Button(FontAwesomeIcon.Paste.ToIconString()) && _player != null; -// ImGui.PopFont(); -// ImGuiUtil.HoverTooltip( -// "Apply customization code from clipboard.\nHold Shift to apply only customizations.\nHold Control to apply only equipment."); -// -// if (!applyButton) -// return false; -// -// try -// { -// var text = ImGui.GetClipboardText(); -// if (!text.Any()) -// return false; -// -// var save = CharacterSave.FromString(text); -// ConditionalApply(save, _player!); -// } -// catch (Exception e) -// { -// PluginLog.Information($"{e}"); -// return false; -// } -// -// return true; -// } -// -// private void DrawSaveDesignButton() -// { -// ImGui.PushFont(UiBuilder.IconFont); -// if (ImGui.Button(FontAwesomeIcon.Save.ToIconString())) -// OpenDesignNamePopup(DesignNameUse.SaveCurrent); -// -// ImGui.PopFont(); -// ImGuiUtil.HoverTooltip("Save the current design.\nHold Shift to save only customizations.\nHold Control to save only equipment."); -// -// DrawDesignNamePopup(DesignNameUse.SaveCurrent); -// } -// -// private void DrawTargetPlayerButton() -// { -// if (ImGui.Button("Target Player")) -// Dalamud.Targets.SetTarget(_player); -// } -// -// private unsafe void DrawApplyToPlayerButton(CharacterSave save) -// { -// if (!ImGui.Button("Apply to Self")) -// return; -// -// var player = _inGPose -// ? (Character?)Dalamud.Objects[GPoseObjectId] -// : Dalamud.ClientState.LocalPlayer; -// var fallback = _inGPose ? Dalamud.ClientState.LocalPlayer : null; -// if (player == null) -// return; -// -// ConditionalApply(save, (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)player.Address); -// if (_inGPose) -// ConditionalApply(save, (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)fallback!.Address); -// Glamourer.Penumbra.UpdateCharacters(player, fallback); -// } -// -// -// private static unsafe FFXIVClientStructs.FFXIV.Client.Game.Character.Character* TransformToCustomizable( -// FFXIVClientStructs.FFXIV.Client.Game.Character.Character* actor) -// { -// if (actor == null) -// return null; -// -// if (actor->ModelCharaId == 0) -// return actor; -// -// actor->ModelCharaId = 0; -// CharacterCustomization.Default.Write(actor); -// return actor; -// } -// -// private static unsafe FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Convert(GameObject? actor) -// { -// return actor switch -// { -// null => null, -// PlayerCharacter p => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)p.Address, -// BattleChara b => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)b.Address, -// _ => actor.ObjectKind switch -// { -// ObjectKind.BattleNpc => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor.Address, -// ObjectKind.Companion => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor.Address, -// ObjectKind.Retainer => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor.Address, -// ObjectKind.EventNpc => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor.Address, -// _ => null, -// }, -// }; -// } -// -// private unsafe void DrawApplyToTargetButton(CharacterSave save) -// { -// if (!ImGui.Button("Apply to Target")) -// return; -// -// var player = TransformToCustomizable(Convert(Dalamud.Targets.Target)); -// if (player == null) -// return; -// -// var fallBackCharacter = _gPoseActors.TryGetValue(new Utf8String(player->GameObject.Name).ToString(), out var f) ? f : null; -// ConditionalApply(save, player); -// if (fallBackCharacter != null) -// ConditionalApply(save, fallBackCharacter!); -// //Glamourer.Penumbra.UpdateCharacters(player, fallBackCharacter); -// } -// -// private void DrawRevertButton() -// { -// if (!ImGuiUtil.DrawDisabledButton("Revert", Vector2.Zero, string.Empty, _player == null)) -// return; -// -// Glamourer.RevertableDesigns.Revert(_player!); -// var fallBackCharacter = _gPoseActors.TryGetValue(_player!.Name.ToString(), out var f) ? f : null; -// if (fallBackCharacter != null) -// Glamourer.RevertableDesigns.Revert(fallBackCharacter); -// Glamourer.Penumbra.UpdateCharacters(_player, fallBackCharacter); -// } -// -// private void SaveNewDesign(CharacterSave save) -// { -// try -// { -// var (folder, name) = _designs.FileSystem.CreateAllFolders(_newDesignName); -// if (!name.Any()) -// return; -// -// var newDesign = new Design(folder, name) { Data = save }; -// folder.AddChild(newDesign); -// _designs.Designs[newDesign.FullName()] = save; -// _designs.SaveToFile(); -// } -// catch (Exception e) -// { -// PluginLog.Error($"Could not save new design {_newDesignName}:\n{e}"); -// } -// } -// -// private unsafe void DrawMonsterPanel() -// { -// if (DrawApplyClipboardButton()) -// Glamourer.Penumbra.UpdateCharacters(_player!); -// -// ImGui.SameLine(); -// if (ImGui.Button("Convert to Character")) -// { -// //TransformToCustomizable(_player); -// _currentLabel = _currentLabel.Replace("(Monster)", "(NPC)"); -// Glamourer.Penumbra.UpdateCharacters(_player!); -// } -// -// if (!_inGPose) -// { -// ImGui.SameLine(); -// DrawTargetPlayerButton(); -// } -// -// var currentModel = ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)_player!.Address)->ModelCharaId; -// using var combo = ImRaii.Combo("Model Id", currentModel.ToString()); -// if (!combo) -// return; -// -// foreach (var (id, _) in _models.Skip(1)) -// { -// if (!ImGui.Selectable($"{id:D6}##models", id == currentModel) || id == currentModel) -// continue; -// -// ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)_player!.Address)->ModelCharaId = 0; -// Glamourer.Penumbra.UpdateCharacters(_player!); -// } -// } -// -// private void DrawPlayerPanel() -// { -// DrawCopyClipboardButton(_currentSave); -// ImGui.SameLine(); -// var changes = !_currentSave.WriteProtected && DrawApplyClipboardButton(); -// ImGui.SameLine(); -// DrawSaveDesignButton(); -// ImGui.SameLine(); -// DrawApplyToPlayerButton(_currentSave); -// if (!_inGPose) -// { -// ImGui.SameLine(); -// DrawApplyToTargetButton(_currentSave); -// if (_player != null && !_currentSave.WriteProtected) -// { -// ImGui.SameLine(); -// DrawTargetPlayerButton(); -// } -// } -// -// var data = _currentSave; -// if (!_currentSave.WriteProtected) -// { -// ImGui.SameLine(); -// DrawRevertButton(); -// } -// else -// { -// ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.8f); -// data = data.Copy(); -// } -// -// if (DrawCustomization(ref data.Customizations) && _player != null) -// { -// Glamourer.RevertableDesigns.Add(_player); -// _currentSave.Customizations.Write(_player.Address); -// changes = true; -// } -// -// changes |= DrawEquip(data.Equipment); -// changes |= DrawMiscellaneous(data, _player); -// -// if (_player != null && changes) -// Glamourer.Penumbra.UpdateCharacters(_player); -// if (_currentSave.WriteProtected) -// ImGui.PopStyleVar(); -// } -// -// private unsafe void DrawActorPanel() -// { -// using var group = ImRaii.Group(); -// DrawPlayerHeader(); -// using var child = ImRaii.Child("##playerData", -Vector2.One, true); -// if (!child) -// return; -// -// if (_player == null || ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)_player.Address)->ModelCharaId == 0) -// DrawPlayerPanel(); -// else -// DrawMonsterPanel(); -// } -//} diff --git a/Glamourer/Gui/Interface.DebugStateTab.cs b/Glamourer/Gui/Interface.DebugStateTab.cs index 63bd288..229b5af 100644 --- a/Glamourer/Gui/Interface.DebugStateTab.cs +++ b/Glamourer/Gui/Interface.DebugStateTab.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Numerics; -using Glamourer.Gui.Customization; -using Glamourer.Interop; using Glamourer.State; using ImGuiNET; using OtterGui; @@ -17,15 +15,15 @@ internal partial class Interface { private class DebugStateTab { - private readonly CurrentManipulations _currentManipulations; + private readonly ActiveDesign.Manager _activeDesigns; private LowerString _manipulationFilter = LowerString.Empty; private ActorIdentifier _selection = ActorIdentifier.Invalid; private ActiveDesign? _save = null; private bool _delete = false; - public DebugStateTab(CurrentManipulations currentManipulations) - => _currentManipulations = currentManipulations; + public DebugStateTab(ActiveDesign.Manager activeDesigns) + => _activeDesigns = activeDesigns; [Conditional("DEBUG")] public void Draw() @@ -43,7 +41,7 @@ internal partial class Interface if (_delete) { _delete = false; - _currentManipulations.DeleteSave(_selection); + _activeDesigns.DeleteSave(_selection); _selection = ActorIdentifier.Invalid; } } @@ -56,7 +54,7 @@ internal partial class Interface using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, oldSpacing); var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight()); - var remainder = ImGuiClip.FilteredClippedDraw(_currentManipulations, skips, CheckFilter, DrawSelectable); + var remainder = ImGuiClip.FilteredClippedDraw(_activeDesigns, skips, CheckFilter, DrawSelectable); ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); } diff --git a/Glamourer/Gui/Interface.DesignTab.cs b/Glamourer/Gui/Interface.DesignTab.cs index b70ca9d..948594b 100644 --- a/Glamourer/Gui/Interface.DesignTab.cs +++ b/Glamourer/Gui/Interface.DesignTab.cs @@ -1,6 +1,7 @@ using System; using System.Numerics; using Dalamud.Interface; +using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Gui.Customization; using Glamourer.Gui.Designs; @@ -37,9 +38,7 @@ internal partial class Interface { using var tab = ImRaii.TabItem("Designs"); if (!tab) - { return; - } Selector.Draw(GetDesignSelectorSize()); ImGui.SameLine(); @@ -55,12 +54,25 @@ internal partial class Interface if (!child || Selector.Selected == null) return; - CustomizationDrawer.Draw(Selector.Selected.Customize(), Selector.Selected.Equipment(), true); + _main._customizationDrawer.Draw(Selector.Selected.Customize(), CustomizeFlagExtensions.All, true); foreach (var slot in EquipSlotExtensions.EqdpSlots) { - _main._equipmentDrawer.DrawStain(Selector.Selected, slot, out var stain); + var current = Selector.Selected.Armor(slot); + _main._equipmentDrawer.DrawStain(current.Stain, slot, out var stain); ImGui.SameLine(); - _main._equipmentDrawer.DrawArmor(Selector.Selected, slot, out var armor); + _main._equipmentDrawer.DrawArmor(current, slot, out var armor); + } + + var currentMain = Selector.Selected.WeaponMain; + _main._equipmentDrawer.DrawStain(currentMain.Stain, EquipSlot.MainHand, out var stainMain); + ImGui.SameLine(); + _main._equipmentDrawer.DrawMainhand(currentMain, true, out var main); + if (currentMain.Type.Offhand() != FullEquipType.Unknown) + { + var currentOff = Selector.Selected.WeaponOff; + _main._equipmentDrawer.DrawStain(currentOff.Stain, EquipSlot.OffHand, out var stainOff); + ImGui.SameLine(); + _main._equipmentDrawer.DrawOffhand(currentOff, main.Type, out var off); } } } diff --git a/Glamourer/Gui/Interface.SettingsTab.cs b/Glamourer/Gui/Interface.SettingsTab.cs index 9bbd065..544564c 100644 --- a/Glamourer/Gui/Interface.SettingsTab.cs +++ b/Glamourer/Gui/Interface.SettingsTab.cs @@ -50,8 +50,9 @@ internal partial class Interface return; } - if (ImGui.Button(buttonLabel)) - Glamourer.Penumbra.Reattach(true); + // TODO + //if (ImGui.Button(buttonLabel)) + // Glamourer.Penumbra.Reattach(true); ImGuiUtil.HoverTooltip( "If Penumbra did not register the functions for some reason, pressing this button might help restore functionality."); @@ -73,18 +74,6 @@ internal partial class Interface 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); - Checkmark("Attach to Penumbra", - "Allows you to right-click items in the Changed Items tab of a mod in Penumbra to apply them to your player character.", - cfg.AttachToPenumbra, - v => - { - cfg.AttachToPenumbra = v; - if (v) - Glamourer.Penumbra.Reattach(true); - else - Glamourer.Penumbra.Unattach(); - }); - ImGui.SameLine(); DrawRestorePenumbraButton(); Checkmark("Apply Fixed Designs", diff --git a/Glamourer/Gui/Interface.cs b/Glamourer/Gui/Interface.cs index 915fe5e..41fa10d 100644 --- a/Glamourer/Gui/Interface.cs +++ b/Glamourer/Gui/Interface.cs @@ -1,32 +1,38 @@ using System; -using System.Linq; using System.Numerics; using Dalamud.Interface.Windowing; using Dalamud.Logging; +using Dalamud.Plugin; using Glamourer.Designs; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; +using Glamourer.Interop; using Glamourer.State; using Glamourer.Util; using ImGuiNET; using OtterGui.Raii; -using Penumbra.GameData.Data; namespace Glamourer.Gui; internal partial class Interface : Window, IDisposable { - private readonly EquipmentDrawer _equipmentDrawer; + 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; - public Interface(ItemManager items, CurrentManipulations manipulations, Design.Manager manager, DesignFileSystem fileSystem) + public Interface(DalamudPluginInterface pi, ItemManager items, ActiveDesign.Manager activeDesigns, Design.Manager manager, + DesignFileSystem fileSystem, ObjectManager objects) : base(GetLabel()) { + _pi = pi; _equipmentDrawer = new EquipmentDrawer(items); + _customizationDrawer = new CustomizationDrawer(pi); Dalamud.PluginInterface.UiBuilder.DisableGposeUiHide = true; Dalamud.PluginInterface.UiBuilder.OpenConfigUi += Toggle; SizeConstraints = new WindowSizeConstraints() @@ -34,8 +40,8 @@ internal partial class Interface : Window, IDisposable MinimumSize = new Vector2(675, 675), MaximumSize = ImGui.GetIO().DisplaySize, }; - _actorTab = new ActorTab(this, manipulations); - _debugStateTab = new DebugStateTab(manipulations); + _actorTab = new ActorTab(this, activeDesigns, objects); + _debugStateTab = new DebugStateTab(activeDesigns); _debugDataTab = new DebugDataTab(Glamourer.Customization); _designTab = new DesignTab(this, manager, fileSystem); } @@ -67,8 +73,8 @@ internal partial class Interface : Window, IDisposable public void Dispose() { - Dalamud.PluginInterface.UiBuilder.OpenConfigUi -= Toggle; - CustomizationDrawer.Dispose(); + _pi.UiBuilder.OpenConfigUi -= Toggle; + _customizationDrawer.Dispose(); _designTab.Dispose(); } @@ -77,90 +83,3 @@ internal partial class Interface : Window, IDisposable ? "Glamourer###GlamourerConfigWindow" : $"Glamourer v{Glamourer.Version}###GlamourerConfigWindow"; } - -//public const float SelectorWidth = 200; -//public const float MinWindowWidth = 675; -//public const int GPoseObjectId = 201; -//private const string PluginName = "Glamourer"; -//private readonly string _glamourerHeader; -// -//private readonly IReadOnlyDictionary _stains; -//private readonly IReadOnlyDictionary _models; -//private readonly IObjectIdentifier _identifier; -//private readonly Dictionary, ComboWithFilter)> _combos; -//private readonly ImGuiScene.TextureWrap? _legacyTattooIcon; -//private readonly Dictionary _equipSlotNames; -//private readonly DesignManager _designs; -//private readonly Glamourer _plugin; -// -//private bool _visible; -//private bool _inGPose; -// -//public Interface(Glamourer plugin) -//{ -// _plugin = plugin; -// _designs = plugin.Designs; -// _glamourerHeader = Glamourer.Version.Length > 0 -// ? $"{PluginName} v{Glamourer.Version}###{PluginName}Main" -// : $"{PluginName}###{PluginName}Main"; -// Dalamud.PluginInterface.UiBuilder.DisableGposeUiHide = true; -// Dalamud.PluginInterface.UiBuilder.Draw += Draw; -// Dalamud.PluginInterface.UiBuilder.OpenConfigUi += ToggleVisibility; -// -// _equipSlotNames = GetEquipSlotNames(); -// -// _stains = GameData.Stains(Dalamud.GameData); -// _models = GameData.Models(Dalamud.GameData); -// _identifier = Penumbra.GameData.GameData.GetIdentifier(Dalamud.GameData, Dalamud.ClientState.ClientLanguage); -// -// -// var stainCombo = CreateDefaultStainCombo(_stains.Values.ToArray()); -// -// var equip = GameData.ItemsBySlot(Dalamud.GameData); -// _combos = equip.ToDictionary(kvp => kvp.Key, kvp => CreateCombos(kvp.Key, kvp.Value, stainCombo)); -// _legacyTattooIcon = GetLegacyTattooIcon(); -//} -// -//public void ToggleVisibility() -// => _visible = !_visible; -// -// -//private void Draw() -//{ -// if (!_visible) -// return; -// -// ImGui.SetNextWindowSizeConstraints(Vector2.One * MinWindowWidth * ImGui.GetIO().FontGlobalScale, -// Vector2.One * 5000 * ImGui.GetIO().FontGlobalScale); -// if (!ImGui.Begin(_glamourerHeader, ref _visible)) -// { -// ImGui.End(); -// return; -// } -// -// try -// { -// using var tabBar = ImRaii.TabBar("##tabBar"); -// if (!tabBar) -// return; -// -// _inGPose = Dalamud.Objects[GPoseObjectId] != null; -// _iconSize = Vector2.One * ImGui.GetTextLineHeightWithSpacing() * 2; -// _actualIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding; -// _comboSelectorSize = 4 * _actualIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X; -// _percentageSize = _comboSelectorSize; -// _inputIntSize = 2 * _actualIconSize.X + ImGui.GetStyle().ItemSpacing.X; -// _raceSelectorWidth = _inputIntSize + _percentageSize - _actualIconSize.X; -// _itemComboWidth = 6 * _actualIconSize.X + 4 * ImGui.GetStyle().ItemSpacing.X - ColorButtonWidth + 1; -// -// DrawPlayerTab(); -// DrawSaves(); -// DrawFixedDesignsTab(); -// DrawConfigTab(); -// DrawRevertablesTab(); -// } -// finally -// { -// ImGui.End(); -// } -//} diff --git a/Glamourer/Interop/Actor.cs b/Glamourer/Interop/Actor.cs index 5276f89..2d9671e 100644 --- a/Glamourer/Interop/Actor.cs +++ b/Glamourer/Interop/Actor.cs @@ -66,6 +66,9 @@ public unsafe partial struct Actor : IEquatable, IDesignable public bool Valid => Pointer != null; + public int Index + => Pointer->GameObject.ObjectIndex; + public uint ModelId { get => (uint)Pointer->ModelCharaId; diff --git a/Glamourer/Interop/ObjectManager.cs b/Glamourer/Interop/ObjectManager.cs index a9bb668..4c03a72 100644 --- a/Glamourer/Interop/ObjectManager.cs +++ b/Glamourer/Interop/ObjectManager.cs @@ -1,22 +1,15 @@ -using System.Collections.Generic; -using System.Text; -using Dalamud.Game.ClientState.Objects.Enums; -using Lumina.Excel.GeneratedSheets; +using System; +using System.Collections; +using System.Collections.Generic; +using Dalamud.Game; +using Dalamud.Game.ClientState; +using Dalamud.Game.ClientState.Objects; using Penumbra.GameData.Actors; -using static Glamourer.Interop.Actor; namespace Glamourer.Interop; -public static class ObjectManager +public class ObjectManager : IReadOnlyDictionary { - private const int CutsceneIndex = 200; - private const int GPosePlayerIndex = 201; - private const int CharacterScreenIndex = 240; - private const int ExamineScreenIndex = 241; - private const int FittingRoomIndex = 242; - private const int DyePreviewIndex = 243; - private const int PortraitIndex = 244; - public readonly struct ActorData { public readonly List Objects; @@ -40,28 +33,22 @@ public static class ObjectManager } } - public static bool IsInGPose { get; private set; } - public static ushort World { get; private set; } + public DateTime LastUpdate { get; private set; } - public static IReadOnlyDictionary Actors - => Identifiers; + public bool IsInGPose { get; private set; } + public ushort World { get; private set; } - public static IReadOnlyList<(ActorIdentifier, ActorData)> List - => ListData; + private readonly Dictionary _identifiers = new(200); - private static readonly Dictionary Identifiers = new(200); - private static readonly List<(ActorIdentifier, ActorData)> ListData = new(Dalamud.Objects.Length); - - private static void HandleIdentifier(ActorIdentifier identifier, Actor character) + private void HandleIdentifier(ActorIdentifier identifier, Actor character) { if (!character.DrawObject || !identifier.IsValid) return; - if (!Identifiers.TryGetValue(identifier, out var data)) + if (!_identifiers.TryGetValue(identifier, out var data)) { - data = new ActorData(character, identifier.ToString()); - Identifiers[identifier] = data; - ListData.Add((identifier, data)); + data = new ActorData(character, identifier.ToString()); + _identifiers[identifier] = data; } else { @@ -69,88 +56,100 @@ public static class ObjectManager } } - public static void Update() - { - World = (ushort)(Dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0u); - Identifiers.Clear(); - ListData.Clear(); + private readonly Framework _framework; + private readonly ClientState _clientState; + private readonly ObjectTable _objects; - for (var i = 0; i < CutsceneIndex; ++i) + public ObjectManager(Framework framework, ClientState clientState, ObjectTable objects) + { + _framework = framework; + _clientState = clientState; + _objects = objects; + } + + public void Update() + { + var lastUpdate = _framework.LastUpdate; + if (lastUpdate <= LastUpdate) + return; + + LastUpdate = lastUpdate; + World = (ushort)(_clientState.LocalPlayer?.CurrentWorld.Id ?? 0u); + _identifiers.Clear(); + + for (var i = 0; i < (int)ScreenActor.CutsceneStart; ++i) { - Actor character = Dalamud.Objects.GetObjectAddress(i); + Actor character = _objects.GetObjectAddress(i); if (character.Identifier(out var identifier)) HandleIdentifier(identifier, character); } - for (var i = CutsceneIndex; i < CharacterScreenIndex; ++i) + for (var i = (int)ScreenActor.CutsceneStart; i < (int)ScreenActor.CutsceneEnd; ++i) { - Actor character = Dalamud.Objects.GetObjectAddress(i); + Actor character = _objects.GetObjectAddress(i); if (!character.Identifier(out var identifier)) break; HandleIdentifier(identifier, character); } - void AddSpecial(int idx, string label) + void AddSpecial(ScreenActor idx, string label) { - Actor actor = Dalamud.Objects.GetObjectAddress(idx); + Actor actor = _objects.GetObjectAddress((int)idx); if (actor.Identifier(out var ident)) { var data = new ActorData(actor, label); - Identifiers.Add(ident, data); - ListData.Add((ident, data)); + _identifiers.Add(ident, data); } } - AddSpecial(CharacterScreenIndex, "Character Screen Actor"); - AddSpecial(ExamineScreenIndex, "Examine Screen Actor"); - AddSpecial(FittingRoomIndex, "Fitting Room Actor"); - AddSpecial(DyePreviewIndex, "Dye Preview Actor"); - AddSpecial(PortraitIndex, "Portrait Actor"); + AddSpecial(ScreenActor.CharacterScreen, "Character Screen Actor"); + AddSpecial(ScreenActor.ExamineScreen, "Examine Screen Actor"); + AddSpecial(ScreenActor.FittingRoom, "Fitting Room Actor"); + AddSpecial(ScreenActor.DyePreview, "Dye Preview Actor"); + AddSpecial(ScreenActor.Portrait, "Portrait Actor"); + AddSpecial(ScreenActor.Card6, "Card Actor 6"); + AddSpecial(ScreenActor.Card7, "Card Actor 7"); + AddSpecial(ScreenActor.Card8, "Card Actor 8"); - for (var i = PortraitIndex + 1; i < Dalamud.Objects.Length; ++i) + for (var i = (int)ScreenActor.ScreenEnd; i < Dalamud.Objects.Length; ++i) { - Actor character = Dalamud.Objects.GetObjectAddress(i); + Actor character = _objects.GetObjectAddress(i); if (character.Identifier(out var identifier)) HandleIdentifier(identifier, character); } - - Actor gPose = Dalamud.Objects.GetObjectAddress(GPosePlayerIndex); + var gPose = GPosePlayer; IsInGPose = gPose && gPose.Utf8Name.Length > 0; } - public static Actor GPosePlayer - => Dalamud.Objects.GetObjectAddress(GPosePlayerIndex); + public Actor GPosePlayer + => _objects.GetObjectAddress((int)ScreenActor.GPosePlayer); - public static Actor Player - => Dalamud.Objects.GetObjectAddress(0); + public Actor Player + => _objects.GetObjectAddress(0); - private static unsafe string GetLabel(Actor player, string playerName, int num, bool gPose) - { - var sb = new StringBuilder(64); - sb.Append(playerName); + public IEnumerator> GetEnumerator() + => _identifiers.GetEnumerator(); - if (gPose) - { - sb.Append(" (GPose"); + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); - if (player.ObjectKind == ObjectKind.Player) - sb.Append(')'); - else - sb.Append(player.ModelId == 0 ? ", NPC)" : ", Monster)"); - } - else if (player.ObjectKind != ObjectKind.Player) - { - sb.Append(player.ModelId == 0 ? " (NPC)" : " (Monster)"); - } + public int Count + => _identifiers.Count; - if (num > 1) - { - sb.Append(" #"); - sb.Append(num); - } + public bool ContainsKey(ActorIdentifier key) + => _identifiers.ContainsKey(key); - return sb.ToString(); - } + public bool TryGetValue(ActorIdentifier key, out ActorData value) + => _identifiers.TryGetValue(key, out value); + + public ActorData this[ActorIdentifier key] + => _identifiers[key]; + + public IEnumerable Keys + => _identifiers.Keys; + + public IEnumerable Values + => _identifiers.Values; } diff --git a/Glamourer/Interop/RedrawManager.Customize.cs b/Glamourer/Interop/RedrawManager.Customize.cs index 4451078..8a58333 100644 --- a/Glamourer/Interop/RedrawManager.Customize.cs +++ b/Glamourer/Interop/RedrawManager.Customize.cs @@ -8,35 +8,6 @@ namespace Glamourer.Interop; public unsafe partial class RedrawManager { - // Update - public delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment); - - [Signature("E8 ?? ?? ?? ?? 41 0F B6 C5 66 41 89 86")] - private readonly ChangeCustomizeDelegate _changeCustomize = null!; - - - - public bool UpdateCustomize(Actor actor, Customize customize) - { - if (!actor.Valid || !actor.DrawObject.Valid) - return false; - - var d = actor.DrawObject; - if (NeedsRedraw(d.Customize, customize)) - { - Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw); - return true; - } - - return _changeCustomize(d.Pointer, (byte*)customize.Data, 1); - } - - public static bool NeedsRedraw(Customize lhs, Customize rhs) - => lhs.Race != rhs.Race - || lhs.Gender != rhs.Gender - || lhs.BodyType != rhs.BodyType - || lhs.Face != rhs.Face - || lhs.Race == Race.Hyur && lhs.Clan != rhs.Clan; public static void SetVisor(Human* data, bool on) diff --git a/Glamourer/Interop/RedrawManager.Equipment.cs b/Glamourer/Interop/RedrawManager.Equipment.cs index 1b72b62..3bf2b39 100644 --- a/Glamourer/Interop/RedrawManager.Equipment.cs +++ b/Glamourer/Interop/RedrawManager.Equipment.cs @@ -69,16 +69,4 @@ public unsafe partial class RedrawManager // //return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data); } - - private void HandleEquipUpdate(nint drawObject, EquipSlot slot, ref CharacterArmor data, bool manual) - { - var actor = Glamourer.Penumbra.GameObjectFromDrawObject(drawObject); - var identifier = actor.GetIdentifier(); - - if (!_currentManipulations.TryGetDesign(identifier, out var design)) - return; - - var flag = slot.ToFlag(); - var stainFlag = slot.ToStainFlag(); - } } diff --git a/Glamourer/Interop/RedrawManager.Weapons.cs b/Glamourer/Interop/RedrawManager.Weapons.cs index dedb8df..77d8963 100644 --- a/Glamourer/Interop/RedrawManager.Weapons.cs +++ b/Glamourer/Interop/RedrawManager.Weapons.cs @@ -45,7 +45,7 @@ public unsafe partial class RedrawManager _ => weapon, }; } - else if (redrawOnEquality == 1 && _currentManipulations.TryGetDesign(identifier, out var save2)) + else if (redrawOnEquality == 1 && _stateManager.TryGetValue(identifier, out var save2)) { PluginLog.Information($"Loaded weapon from current design for {identifier}."); //switch (slot) diff --git a/Glamourer/Interop/RedrawManager.cs b/Glamourer/Interop/RedrawManager.cs index 37d5e21..2f94af4 100644 --- a/Glamourer/Interop/RedrawManager.cs +++ b/Glamourer/Interop/RedrawManager.cs @@ -1,31 +1,104 @@ using System; +using System.Diagnostics; +using System.Linq; using Dalamud.Hooking; using Dalamud.Logging; using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Customization; using Glamourer.State; using Glamourer.Structs; -using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using CustomizeData = Penumbra.GameData.Structs.CustomizeData; namespace Glamourer.Interop; -public class DesignBaseValidator +public partial class Interop : IDisposable { - private readonly CustomizationManager _manager; - private readonly RestrictedGear _restrictedGear; - - public DesignBaseValidator(CustomizationManager manager, RestrictedGear restrictedGear) + public Interop() { - _manager = manager; - _restrictedGear = restrictedGear; + SignatureHelper.Initialise(this); + _changeJobHook.Enable(); + } + + public void Dispose() + { + _changeJobHook.Dispose(); } } +public unsafe partial class Interop +{ + private delegate ulong FlagSlotForUpdateDelegateIntern(nint drawObject, uint slot, CharacterArmor* data); + public delegate void FlagSlotForUpdateDelegate(DrawObject drawObject, EquipSlot slot, ref CharacterArmor item); -public unsafe partial class RedrawManager + // This gets called when one of the ten equip items of an existing draw object gets changed. + [Signature(Sigs.FlagSlotForUpdate, DetourName = nameof(FlagSlotForUpdateDetour))] + private readonly Hook _flagSlotForUpdateHook = null!; + + public event FlagSlotForUpdateDelegate? EquipUpdate; + + public ulong FlagSlotForUpdateInterop(DrawObject drawObject, EquipSlot slot, CharacterArmor armor) + => _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); + + public void UpdateSlot(DrawObject drawObject, EquipSlot slot, CharacterArmor data) + { + InvokeFlagSlotEvent(drawObject, slot, ref data); + FlagSlotForUpdateInterop(drawObject, slot, data); + } + + public void UpdateStain(DrawObject drawObject, EquipSlot slot, StainId stain) + { + var armor = drawObject.Equip[slot] with { Stain = stain }; + UpdateSlot(drawObject, slot, armor); + } + + private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) + { + var slot = slotIdx.ToEquipSlot(); + InvokeFlagSlotEvent(drawObject, slot, ref *data); + return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data); + } + + private void InvokeFlagSlotEvent(DrawObject drawObject, EquipSlot slot, ref CharacterArmor armor) + { + if (EquipUpdate == null) + return; + + foreach (var del in EquipUpdate.GetInvocationList().OfType()) + { + try + { + del(drawObject, slot, ref armor); + } + catch (Exception ex) + { + Glamourer.Log.Error($"Could not invoke {nameof(EquipUpdate)} Subscriber:\n{ex}"); + } + } + } +} + +public unsafe partial class Interop +{ + public delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment); + + [Signature(Sigs.ChangeCustomize)] + private readonly ChangeCustomizeDelegate _changeCustomize = null!; + + public bool UpdateCustomize(Actor actor, CustomizeData customize) + { + Debug.Assert(customize.Data != null, "Customize was invalid."); + + if (!actor.Valid || !actor.DrawObject.Valid) + return false; + + return _changeCustomize(actor.DrawObject.Pointer, customize.Data, 1); + } +} + +public partial class Interop { private delegate void ChangeJobDelegate(IntPtr data, uint job); @@ -44,27 +117,21 @@ public unsafe partial class RedrawManager public unsafe partial class RedrawManager : IDisposable { private readonly FixedDesigns _fixedDesigns; - private readonly CurrentManipulations _currentManipulations; + private readonly ActiveDesign.Manager _stateManager; - public RedrawManager(FixedDesigns fixedDesigns, CurrentManipulations currentManipulations) + public RedrawManager(FixedDesigns fixedDesigns, ActiveDesign.Manager stateManager) { SignatureHelper.Initialise(this); - Glamourer.Penumbra.CreatingCharacterBase.Event += OnCharacterRedraw; - Glamourer.Penumbra.CreatedCharacterBase.Event += OnCharacterRedrawFinished; - _fixedDesigns = fixedDesigns; - _currentManipulations = currentManipulations; + _fixedDesigns = fixedDesigns; + _stateManager = stateManager; _flagSlotForUpdateHook.Enable(); _loadWeaponHook.Enable(); - _changeJobHook.Enable(); } public void Dispose() { _flagSlotForUpdateHook.Dispose(); _loadWeaponHook.Dispose(); - _changeJobHook.Dispose(); - Glamourer.Penumbra.CreatingCharacterBase.Event -= OnCharacterRedraw; - Glamourer.Penumbra.CreatedCharacterBase.Event -= OnCharacterRedrawFinished; } private void OnCharacterRedraw(Actor actor, uint* modelId, Customize customize, CharacterEquip equip) @@ -76,7 +143,7 @@ public unsafe partial class RedrawManager : IDisposable // Check if we have a current design in use, or if not if the actor has a fixed design. var identifier = actor.GetIdentifier(); - if (!(_currentManipulations.TryGetDesign(identifier, out var save) || _fixedDesigns.TryGetDesign(identifier, out var save2))) + if (!(_stateManager.TryGetValue(identifier, out var save) || _fixedDesigns.TryGetDesign(identifier, out var save2))) return; // Compare game object customize data against draw object customize data for transformations. @@ -116,4 +183,4 @@ public unsafe partial class RedrawManager : IDisposable { //SetVisor((Human*)drawObject, true); } -} \ No newline at end of file +} diff --git a/Glamourer/State/ActiveDesign.Manager.cs b/Glamourer/State/ActiveDesign.Manager.cs new file mode 100644 index 0000000..370788b --- /dev/null +++ b/Glamourer/State/ActiveDesign.Manager.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections; +using Glamourer.Interop; +using Penumbra.GameData.Actors; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using Glamourer.Api; +using Glamourer.Customization; +using Glamourer.Designs; +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; + +public sealed partial class ActiveDesign +{ + public partial class Manager : IReadOnlyDictionary + { + private readonly ActorManager _actors; + private readonly ObjectManager _objects; + private readonly Interop.Interop _interop; + private readonly PenumbraAttach _penumbra; + + private readonly Dictionary _characterSaves = new(); + + public Manager(ActorManager actors, ObjectManager objects, Interop.Interop interop, PenumbraAttach penumbra) + { + _actors = actors; + _objects = objects; + _interop = interop; + _penumbra = penumbra; + } + + public IEnumerator> GetEnumerator() + => _characterSaves.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + public int Count + => _characterSaves.Count; + + public bool ContainsKey(ActorIdentifier key) + => _characterSaves.ContainsKey(key); + + public bool TryGetValue(ActorIdentifier key, [NotNullWhen(true)] out ActiveDesign? value) + => _characterSaves.TryGetValue(key, out value); + + public ActiveDesign this[ActorIdentifier key] + => _characterSaves[key]; + + public IEnumerable Keys + => _characterSaves.Keys; + + public IEnumerable Values + => _characterSaves.Values; + + public void DeleteSave(ActorIdentifier identifier) + => _characterSaves.Remove(identifier); + + public unsafe ActiveDesign GetOrCreateSave(Actor actor) + { + var id = _actors.FromObject((GameObject*)actor.Pointer, out _, false, false); + if (_characterSaves.TryGetValue(id, out var save)) + { + save.Update(actor); + return save; + } + + id = id.CreatePermanent(); + save = new ActiveDesign(id, actor); + save.Update(actor); + _characterSaves.Add(id, save); + return save; + } + + public void RevertDesign(ActiveDesign design) + { + RevertCustomize(design, design.ChangedCustomize); + foreach (var slot in EquipSlotExtensions.EqdpSlots) + RevertEquipment(design, slot, design.ChangedEquip.HasFlag(slot.ToFlag()), design.ChangedEquip.HasFlag(slot.ToStainFlag())); + + RevertMainHand(design); + RevertOffHand(design); + } + + public void RevertMainHand(ActiveDesign design) + { } + + public void RevertOffHand(ActiveDesign design) + { } + + public void RevertCustomize(ActiveDesign design, CustomizeFlag flags) + => ChangeCustomize(design, flags, design._initialData.CustomizeData, false); + + public void ChangeCustomize(ActiveDesign design, CustomizeFlag flags, CustomizeData newValue, bool fromFixed) + { + var customize = new Customize(ref newValue); + var anyChanges = false; + foreach (var option in Enum.GetValues()) + { + var flag = option.ToFlag(); + var apply = flags.HasFlag(flag); + anyChanges |= apply && design.SetCustomize(option, customize[option]); + if (design.GetCustomize(option).Value != design._initialData.Customize[option].Value) + design.ChangedCustomize |= flag; + else + design.ChangedCustomize &= ~flag; + + if (fromFixed) + design.FixedCustomize |= flag; + else + design.FixedCustomize &= ~flag; + } + + if (!anyChanges) + return; + + _objects.Update(); + if (!_objects.TryGetValue(design.Identifier, out var data)) + return; + + var redraw = flags.RequiresRedraw(); + foreach (var obj in data.Objects) + { + if (redraw) + _penumbra.RedrawObject(obj, RedrawType.Redraw); + else + _interop.UpdateCustomize(obj, design.CharacterData.CustomizeData); + } + } + + public void RevertEquipment(ActiveDesign design, EquipSlot slot, bool equip, bool stain) + { + var item = design._initialData.Equipment[slot]; + if (equip) + { + var flag = slot.ToFlag(); + design.UpdateArmor(slot, item, true); + design.ChangedEquip &= ~flag; + design.FixedEquip &= ~flag; + } + + if (stain) + { + var flag = slot.ToStainFlag(); + design.SetStain(slot, item.Stain); + design.ChangedEquip &= ~flag; + design.FixedEquip &= ~flag; + } + + _objects.Update(); + if (!_objects.TryGetValue(design.Identifier, out var data)) + return; + + foreach (var obj in data.Objects) + _interop.UpdateSlot(obj.DrawObject, slot, item); + } + + public void ChangeEquipment(ActiveDesign design, EquipSlot slot, Item item, bool fromFixed) + { + var flag = slot.ToFlag(); + design.SetArmor(slot, item); + var current = design.Armor(slot); + var initial = design._initialData.Equipment[slot]; + if (current.ModelBase.Value != initial.Set.Value || current.Variant != initial.Variant) + design.ChangedEquip |= flag; + else + design.ChangedEquip &= ~flag; + if (fromFixed) + design.FixedEquip |= flag; + else + design.FixedEquip &= ~flag; + + _objects.Update(); + if (!_objects.TryGetValue(design.Identifier, out var data)) + return; + + foreach (var obj in data.Objects) + _interop.UpdateSlot(obj.DrawObject, slot, item.Model); + } + + public void ChangeStain(ActiveDesign design, EquipSlot slot, StainId stain, bool fromFixed) + { + var flag = slot.ToStainFlag(); + design.SetStain(slot, stain); + var current = design.Armor(slot); + var initial = design._initialData.Equipment[slot]; + if (current.Stain.Value != initial.Stain.Value) + design.ChangedEquip |= flag; + else + design.ChangedEquip &= ~flag; + if (fromFixed) + design.FixedEquip |= flag; + else + design.FixedEquip &= ~flag; + + _objects.Update(); + if (!_objects.TryGetValue(design.Identifier, out var data)) + return; + + foreach (var obj in data.Objects) + _interop.UpdateStain(obj.DrawObject, slot, stain); + } + } +} diff --git a/Glamourer/State/ActiveDesign.StateManager.cs b/Glamourer/State/ActiveDesign.StateManager.cs deleted file mode 100644 index 4c4342b..0000000 --- a/Glamourer/State/ActiveDesign.StateManager.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Collections; -using Glamourer.Interop; -using Penumbra.GameData.Actors; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Glamourer.Designs; -using Penumbra.GameData.Structs; - -namespace Glamourer.State; - -public sealed partial class ActiveDesign -{ - public partial class Manager : IReadOnlyDictionary - { - private readonly ActorManager _actors; - - private readonly Dictionary _characterSaves = new(); - - public Manager(ActorManager actors) - => _actors = actors; - - public IEnumerator> GetEnumerator() - => _characterSaves.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() - => GetEnumerator(); - - public int Count - => _characterSaves.Count; - - public bool ContainsKey(ActorIdentifier key) - => _characterSaves.ContainsKey(key); - - public bool TryGetValue(ActorIdentifier key, [NotNullWhen(true)] out ActiveDesign? value) - => _characterSaves.TryGetValue(key, out value); - - public ActiveDesign this[ActorIdentifier key] - => _characterSaves[key]; - - public IEnumerable Keys - => _characterSaves.Keys; - - public IEnumerable Values - => _characterSaves.Values; - - public void DeleteSave(ActorIdentifier identifier) - => _characterSaves.Remove(identifier); - - public ActiveDesign GetOrCreateSave(Actor actor) - { - var id = actor.GetIdentifier(); - if (_characterSaves.TryGetValue(id, out var save)) - { - save.Update(actor); - return save; - } - - save = new ActiveDesign(); - save.Update(actor); - _characterSaves.Add(id.CreatePermanent(), save); - return save; - } - } -} diff --git a/Glamourer/State/ActiveDesign.cs b/Glamourer/State/ActiveDesign.cs index dad68cb..d26842a 100644 --- a/Glamourer/State/ActiveDesign.cs +++ b/Glamourer/State/ActiveDesign.cs @@ -2,33 +2,38 @@ using Glamourer.Designs; using Glamourer.Interop; using Penumbra.Api.Enums; +using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; namespace Glamourer.State; public sealed partial class ActiveDesign : DesignBase { + public readonly ActorIdentifier Identifier; + private CharacterData _initialData = new(); - private CustomizeFlag _changedCustomize; - private CustomizeFlag _fixedCustomize; + public CustomizeFlag ChangedCustomize { get; private set; } = 0; + public CustomizeFlag FixedCustomize { get; private set; } = 0; - private EquipFlag _changedEquip; - private EquipFlag _fixedEquip; + public EquipFlag ChangedEquip { get; private set; } = 0; + public EquipFlag FixedEquip { get; private set; } = 0; public bool IsHatVisible { get; private set; } = false; public bool IsWeaponVisible { get; private set; } = false; public bool IsVisorToggled { get; private set; } = false; public bool IsWet { get; private set; } = false; - private ActiveDesign() - { } + private ActiveDesign(ActorIdentifier identifier) + => Identifier = identifier; - public ActiveDesign(Actor actor) + private ActiveDesign(ActorIdentifier identifier, Actor actor) { + Identifier = identifier; Update(actor); } + //public void ApplyToActor(Actor actor) //{ // if (!actor) @@ -72,14 +77,14 @@ public sealed partial class ActiveDesign : DesignBase var initialEquip = _initialData.Equipment; var currentEquip = actor.Equip; - var equipment = Equipment(); foreach (var slot in EquipSlotExtensions.EqdpSlots) { var current = currentEquip[slot]; if (initialEquip[slot] != current) { initialEquip[slot] = current; - equipment[slot] = current; + UpdateArmor(slot, current, true); + SetStain(slot, current.Stain); } } @@ -87,12 +92,14 @@ public sealed partial class ActiveDesign : DesignBase { _initialData.MainHand = actor.MainHand; UpdateMainhand(actor.MainHand); + SetStain(EquipSlot.MainHand, actor.MainHand.Stain); } if (_initialData.OffHand != actor.OffHand) { _initialData.OffHand = actor.OffHand; - UpdateMainhand(actor.OffHand); + UpdateOffhand(actor.OffHand); + SetStain(EquipSlot.OffHand, actor.OffHand.Stain); } } } diff --git a/Glamourer/State/CurrentManipulations.cs b/Glamourer/State/CurrentManipulations.cs deleted file mode 100644 index cbc0e5b..0000000 --- a/Glamourer/State/CurrentManipulations.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Glamourer.Interop; -using Penumbra.GameData.Actors; - -namespace Glamourer.State; - -public class CurrentManipulations : IReadOnlyCollection> -{ - private readonly Dictionary _characterSaves = new(); - - public IEnumerator> GetEnumerator() - => _characterSaves.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() - => GetEnumerator(); - - public int Count - => _characterSaves.Count; - - public ActiveDesign GetOrCreateSave(Actor actor) - { - var id = actor.GetIdentifier(); - if (_characterSaves.TryGetValue(id, out var save)) - { - save.Update(actor); - return save; - } - - save = new ActiveDesign(actor); - _characterSaves.Add(id.CreatePermanent(), save); - return save; - } - - public void DeleteSave(ActorIdentifier identifier) - => _characterSaves.Remove(identifier); - - public bool TryGetDesign(ActorIdentifier identifier, [NotNullWhen(true)] out ActiveDesign? save) - => _characterSaves.TryGetValue(identifier, out save); - - //public CharacterArmor? ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data) - //{ - // var save = CreateSave(actor); - // (_, data) = _restrictedGear.ResolveRestricted(data, slot, save.Customize.Race, save.Customize.Gender); - // if (save.Equipment[slot] == data) - // return null; - // - // save.Equipment[slot] = data; - // return data; - //} - // - //public bool ChangeWeapon(Actor actor, CharacterWeapon main) - //{ - // var save = CreateSave(actor); - // if (save.MainHand == main) - // return false; - // - // save.MainHand = main; - // return true; - //} - // - //public bool ChangeWeapon(Actor actor, CharacterWeapon main, CharacterWeapon off) - //{ - // var save = CreateSave(actor); - // if (main == save.MainHand && off == save.OffHand) - // return false; - // - // save.MainHand = main; - // save.OffHand = off; - // return true; - //} - // -} diff --git a/Glamourer/Util/CustomizeExtensions.cs b/Glamourer/Util/CustomizeExtensions.cs index f0aafcf..baae76b 100644 --- a/Glamourer/Util/CustomizeExtensions.cs +++ b/Glamourer/Util/CustomizeExtensions.cs @@ -5,7 +5,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Util; -public static unsafe class CustomizeExtensions +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) @@ -57,58 +57,39 @@ public static unsafe class CustomizeExtensions // Change a gender and fix up all required customizations afterwards. - public static bool ChangeGender(this Customize customize, CharacterEquip equip, Gender gender) + public static CustomizeFlag ChangeGender(this Customize customize, CharacterEquip equip, Gender gender) { if (customize.Gender == gender) - return false; + return 0; FixRestrictedGear(customize, equip, gender, customize.Race); customize.Gender = gender; - FixUpAttributes(customize); - return true; + return CustomizeFlag.Gender | FixUpAttributes(customize); } // Change a race and fix up all required customizations afterwards. - public static bool ChangeRace(this Customize customize, CharacterEquip equip, SubRace clan) + public static CustomizeFlag ChangeRace(this Customize customize, CharacterEquip equip, SubRace clan) { if (customize.Clan == clan) - return false; + return 0; var race = clan.ToRace(); var gender = race == Race.Hrothgar ? Gender.Male : customize.Gender; // TODO Female Hrothgar FixRestrictedGear(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; - FixUpAttributes(customize); - return true; - } - - public static void ChangeCustomization(this Customize customize, CharacterEquip equip, Customize newCustomize) - { - FixRestrictedGear(customize, equip, newCustomize.Gender, newCustomize.Race); - customize.Load(newCustomize); - } - - public static bool ChangeCustomization(this Customize customize, CharacterEquip equip, CustomizeIndex index, CustomizeValue value) - { - switch (index) - { - case CustomizeIndex.Race: return customize.ChangeRace(equip, (SubRace)value.Value); - case CustomizeIndex.Gender: return customize.ChangeGender(equip, (Gender)value.Value); - } - - if (customize[index] == value) - return false; - - customize[index] = value; - return true; + return flags | FixUpAttributes(customize); } // Go through a whole customization struct and fix up all settings that need fixing. - private static void FixUpAttributes(Customize customize) + private static CustomizeFlag FixUpAttributes(Customize customize) { - var set = Glamourer.Customization.GetList(customize.Clan, customize.Gender); + var set = Glamourer.Customization.GetList(customize.Clan, customize.Gender); + CustomizeFlag flags = 0; foreach (CustomizeIndex id in Enum.GetValues(typeof(CustomizeIndex))) { switch (id) @@ -122,10 +103,16 @@ public static unsafe class CustomizeExtensions default: var count = set.Count(id); if (set.DataByValue(id, customize[id], out _, customize.Face) < 0) - customize[id] = count == 0 ? CustomizeValue.Zero : set.Data(id, 0).Value; + { + customize[id] = count == 0 ? CustomizeValue.Zero : set.Data(id, 0).Value; + flags |= id.ToFlag(); + } + break; } } + + return flags; } private static void FixRestrictedGear(Customize customize, CharacterEquip equip, Gender gender, Race race) diff --git a/Glamourer/Util/ItemManager.cs b/Glamourer/Util/ItemManager.cs index 27ec8f6..0d77ffa 100644 --- a/Glamourer/Util/ItemManager.cs +++ b/Glamourer/Util/ItemManager.cs @@ -56,13 +56,19 @@ public class ItemManager : IDisposable public static Designs.Item NothingItem(EquipSlot slot) { - Debug.Assert(slot.IsEquipment() || slot.IsAccessory()); + Debug.Assert(slot.IsEquipment() || slot.IsAccessory(), $"Called {nameof(NothingItem)} on {slot}."); return new Designs.Item(Nothing, NothingId(slot), CharacterArmor.Empty); } + public static Designs.Weapon NothingItem(FullEquipType type) + { + Debug.Assert(type.ToSlot() == EquipSlot.OffHand, $"Called {nameof(NothingItem)} on {type}."); + return new Designs.Weapon(Nothing, NothingId(type), CharacterWeapon.Empty, type); + } + public static Designs.Item SmallClothesItem(EquipSlot slot) { - Debug.Assert(slot.IsEquipment()); + Debug.Assert(slot.IsEquipment(), $"Called {nameof(SmallClothesItem)} on {slot}."); return new Designs.Item(SmallClothesNpc, SmallclothesId(slot), new CharacterArmor(SmallClothesNpcModel, 1, 0)); }