From cf566932f9ae912b96a29327032eaaaa2bdb9c7f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Nov 2023 11:01:04 +0100 Subject: [PATCH 01/20] Fix some issues with NPC Equip. --- Glamourer/Designs/DesignManager.cs | 2 +- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 5 +++++ Glamourer/Gui/Equipment/ItemCombo.cs | 25 +++++++++++++++++++--- Glamourer/Services/ItemManager.cs | 6 +++--- OtterGui | 2 +- 5 files changed, 32 insertions(+), 8 deletions(-) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index b8cd9a2..7a0f500 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -345,7 +345,7 @@ public class DesignManager /// Change a non-weapon equipment piece. public void ChangeEquip(Design design, EquipSlot slot, EquipItem item) { - if (!_items.IsItemValid(slot, item.ItemId, out item)) + if (!_items.IsItemValid(slot, item.Id, out item)) return; var old = design.DesignData.Item(slot); diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 3b33f8a..ca78bea 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -263,6 +263,11 @@ public class EquipmentDrawer var change = combo.Draw(armor.Name, armor.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth); if (change) armor = combo.CurrentSelection; + else if (combo.CustomVariant.Id > 0) + { + armor = _items.Identify(slot, combo.CustomSetId, combo.CustomVariant); + change = true; + } if (!locked && armor.ModelId.Id != 0) { diff --git a/Glamourer/Gui/Equipment/ItemCombo.cs b/Glamourer/Gui/Equipment/ItemCombo.cs index 5062949..4ddbbaa 100644 --- a/Glamourer/Gui/Equipment/ItemCombo.cs +++ b/Glamourer/Gui/Equipment/ItemCombo.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Dalamud.Plugin.Services; using Glamourer.Services; @@ -22,6 +23,9 @@ public sealed class ItemCombo : FilterComboCache private ItemId _currentItem; private float _innerWidth; + public SetId CustomSetId { get; private set; } + public Variant CustomVariant { get; private set; } + public ItemCombo(IDataManager gameData, ItemManager items, EquipSlot slot, Logger log, FavoriteManager favorites) : base(() => GetItems(favorites, items, slot), log) { @@ -50,8 +54,9 @@ public sealed class ItemCombo : FilterComboCache public bool Draw(string previewName, ItemId previewIdx, float width, float innerWidth) { - _innerWidth = innerWidth; - _currentItem = previewIdx; + _innerWidth = innerWidth; + _currentItem = previewIdx; + CustomVariant = 0; return Draw($"##{Label}", previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing()); } @@ -117,4 +122,18 @@ public sealed class ItemCombo : FilterComboCache enumerable = enumerable.Append(ItemManager.SmallClothesItem(slot)); return enumerable.OrderByDescending(favorites.Contains).ThenBy(i => i.Name).Prepend(nothing).ToList(); } + + protected override void OnClosePopup() + { + // If holding control while the popup closes, try to parse the input as a full pair of set id and variant, and set a custom item for that. + if (!ImGui.GetIO().KeyCtrl) + return; + + var split = Filter.Text.Split('-', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + if (split.Length != 2 || !ushort.TryParse(split[0], out var setId) || !byte.TryParse(split[1], out var variant)) + return; + + CustomSetId = setId; + CustomVariant = variant; + } } diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 59e8228..745469e 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -66,7 +66,7 @@ public class ItemManager : IDisposable public static EquipItem SmallClothesItem(EquipSlot slot) => new(SmallClothesNpc, SmallclothesId(slot), 0, SmallClothesNpcModel, 0, 1, slot.ToEquipType(), 0, 0, 0); - public EquipItem Resolve(EquipSlot slot, ItemId itemId) + public EquipItem Resolve(EquipSlot slot, CustomItemId itemId) { slot = slot.ToSlot(); if (itemId == NothingId(slot)) @@ -74,7 +74,7 @@ public class ItemManager : IDisposable if (itemId == SmallclothesId(slot)) return SmallClothesItem(slot); - if (!ItemService.AwaitedService.TryGetValue(itemId, slot, out var item)) + if (!itemId.IsItem || !ItemService.AwaitedService.TryGetValue(itemId.Item, slot, out var item)) return EquipItem.FromId(itemId); if (item.Type.ToSlot() != slot) @@ -151,7 +151,7 @@ public class ItemManager : IDisposable /// Returns whether an item id represents a valid item for a slot and gives the item. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public bool IsItemValid(EquipSlot slot, ItemId itemId, out EquipItem item) + public bool IsItemValid(EquipSlot slot, CustomItemId itemId, out EquipItem item) { item = Resolve(slot, itemId); return item.Valid; diff --git a/OtterGui b/OtterGui index f55733a..8df162f 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit f55733a96fdc9f82c9bbf8272ca6366079aa8e32 +Subproject commit 8df162f7dc7adc8be1af3eeae80bee3c0cfa4c5c From 294cbd46534cbb1c58cebf2ea8a2d2110f0a14f2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Nov 2023 11:04:18 +0100 Subject: [PATCH 02/20] Update OtterGui. --- Glamourer/Gui/UiHelpers.cs | 2 +- OtterGui | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index eb1f1c2..7dcfc7d 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -9,8 +9,8 @@ using Glamourer.Unlocks; using ImGuiNET; using Lumina.Misc; using OtterGui; -using OtterGui.Classes; using OtterGui.Raii; +using OtterGui.Widgets; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/OtterGui b/OtterGui index 8df162f..5c4c53c 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 8df162f7dc7adc8be1af3eeae80bee3c0cfa4c5c +Subproject commit 5c4c53c1f996ebf0dfd0a51ae645a46cad4a803b From cd289247e9479b8c52429194850d9fa2cef8d633 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Nov 2023 11:58:39 +0100 Subject: [PATCH 03/20] Update OtterGui. --- OtterGui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OtterGui b/OtterGui index 5c4c53c..0c50c8d 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 5c4c53c1f996ebf0dfd0a51ae645a46cad4a803b +Subproject commit 0c50c8d038e4347e6705076f47a89f478666a301 From c11bd629dadf9a94454d90a43c70d401baf3b0c3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Nov 2023 13:45:18 +0100 Subject: [PATCH 04/20] Save off the main thread. --- Glamourer/EphemeralConfig.cs | 3 ++- OtterGui | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Glamourer/EphemeralConfig.cs b/Glamourer/EphemeralConfig.cs index 349a021..4bd57ed 100644 --- a/Glamourer/EphemeralConfig.cs +++ b/Glamourer/EphemeralConfig.cs @@ -66,7 +66,8 @@ public class EphemeralConfig : ISavable public void Save(StreamWriter writer) { - using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented }; + using var jWriter = new JsonTextWriter(writer); + jWriter.Formatting = Formatting.Indented; var serializer = new JsonSerializer { Formatting = Formatting.Indented }; serializer.Serialize(jWriter, this); } diff --git a/OtterGui b/OtterGui index 0c50c8d..3a1a3f1 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 0c50c8d038e4347e6705076f47a89f478666a301 +Subproject commit 3a1a3f1a1f2021b063617ac9b294b579a154706e From dd42b7ab7ff50f8c0ee005e83b9dd20df248cfde Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Nov 2023 15:42:55 +0100 Subject: [PATCH 05/20] Add experimental automation condition for gearsets. --- Glamourer/Automation/AutoDesign.cs | 24 ++++++++-- Glamourer/Automation/AutoDesignApplier.cs | 42 +++++++++++++++- Glamourer/Automation/AutoDesignManager.cs | 48 ++++++++++++++----- Glamourer/Events/EquippedGearset.cs | 30 ++++++++++++ Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 50 ++++++++++++++++---- Glamourer/Interop/InventoryService.cs | 22 +++++---- Glamourer/Services/ServiceManager.cs | 1 + 7 files changed, 185 insertions(+), 32 deletions(-) create mode 100644 Glamourer/Events/EquippedGearset.cs diff --git a/Glamourer/Automation/AutoDesign.cs b/Glamourer/Automation/AutoDesign.cs index 7beda18..4cde895 100644 --- a/Glamourer/Automation/AutoDesign.cs +++ b/Glamourer/Automation/AutoDesign.cs @@ -27,6 +27,7 @@ public class AutoDesign public Design? Design; public JobGroup Jobs; public Type ApplicationType; + public short GearsetIndex = -1; public string Name(bool incognito) => Revert ? RevertName : incognito ? Design!.Incognito : Design!.Name.Text; @@ -43,10 +44,22 @@ public class AutoDesign Design = Design, ApplicationType = ApplicationType, Jobs = Jobs, + GearsetIndex = GearsetIndex, }; public unsafe bool IsActive(Actor actor) - => actor.IsCharacter && Jobs.Fits(actor.AsCharacter->CharacterData.ClassJob); + { + if (!actor.IsCharacter) + return false; + + var ret = true; + if (GearsetIndex < 0) + ret &= Jobs.Fits(actor.AsCharacter->CharacterData.ClassJob); + else + ret &= AutoDesignApplier.CheckGearset(GearsetIndex); + + return ret; + } public JObject Serialize() => new() @@ -58,9 +71,12 @@ public class AutoDesign private JObject CreateConditionObject() { - var ret = new JObject(); - if (Jobs.Id != 0) - ret["JobGroup"] = Jobs.Id; + var ret = new JObject + { + ["Gearset"] = GearsetIndex, + ["JobGroup"] = Jobs.Id, + }; + return ret; } diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 92cd2b0..4b76943 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Events; @@ -26,6 +27,7 @@ public class AutoDesignApplier : IDisposable private readonly AutoDesignManager _manager; private readonly StateManager _state; private readonly JobService _jobs; + private readonly EquippedGearset _equippedGearset; private readonly ActorService _actors; private readonly CustomizationService _customizations; private readonly CustomizeUnlockManager _customizeUnlocks; @@ -49,7 +51,8 @@ public class AutoDesignApplier : IDisposable public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, CustomizationService customizations, ActorService actors, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks, - AutomationChanged @event, ObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState) + AutomationChanged @event, ObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState, + EquippedGearset equippedGearset) { _config = config; _manager = manager; @@ -64,15 +67,18 @@ public class AutoDesignApplier : IDisposable _weapons = weapons; _humans = humans; _clientState = clientState; + _equippedGearset = equippedGearset; _jobs.JobChanged += OnJobChange; _event.Subscribe(OnAutomationChange, AutomationChanged.Priority.AutoDesignApplier); _weapons.Subscribe(OnWeaponLoading, WeaponLoading.Priority.AutoDesignApplier); + _equippedGearset.Subscribe(OnEquippedGearset, EquippedGearset.Priority.AutoDesignApplier); } public void Dispose() { _weapons.Unsubscribe(OnWeaponLoading); _event.Unsubscribe(OnAutomationChange); + _equippedGearset.Unsubscribe(OnEquippedGearset); _jobs.JobChanged -= OnJobChange; } @@ -496,4 +502,38 @@ public class AutoDesignApplier : IDisposable totalMetaFlags |= 0x08; } } + + internal static int NewGearsetId = -1; + + private void OnEquippedGearset(string name, int id, int prior, byte _, byte job) + { + if (!_config.EnableAutoDesigns) + return; + + var (player, data) = _objects.PlayerData; + if (!player.IsValid) + return; + + if (!GetPlayerSet(player, out var set) || !_state.TryGetValue(player, out var state)) + return; + + var respectManual = prior == id; + NewGearsetId = id; + Reduce(data.Objects[0], state, set, respectManual, job != state.LastJob); + NewGearsetId = -1; + foreach (var actor in data.Objects) + _state.ReapplyState(actor); + } + + public static unsafe bool CheckGearset(short check) + { + if (NewGearsetId != -1) + return check == NewGearsetId; + + var module = RaptureGearsetModule.Instance(); + if (module == null) + return false; + + return check == module->CurrentGearsetIndex; + } } diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index 8140084..d3fba5c 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -306,6 +306,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos return; var design = set.Designs[which]; + if (design.Jobs.Id == jobs.Id) return; @@ -316,6 +317,22 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos _event.Invoke(AutomationChanged.Type.ChangedConditions, set, (which, old, jobs)); } + public void ChangeGearsetCondition(AutoDesignSet set, int which, short index) + { + if (which >= set.Designs.Count || which < 0) + return; + + var design = set.Designs[which]; + if (design.GearsetIndex == index) + return; + + var old = design.GearsetIndex; + design.GearsetIndex = index; + Save(); + Glamourer.Log.Debug($"Changed gearset condition from {old} to {index} for associated design {which + 1} in design set."); + _event.Invoke(AutomationChanged.Type.ChangedConditions, set, (which, old, index)); + } + public void ChangeApplicationType(AutoDesignSet set, int which, AutoDesign.Type type) { if (which >= set.Designs.Count || which < 0) @@ -338,10 +355,8 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos public void Save(StreamWriter writer) { - using var j = new JsonTextWriter(writer) - { - Formatting = Formatting.Indented, - }; + using var j = new JsonTextWriter(writer); + j.Formatting = Formatting.Indented; Serialize().WriteTo(j); } @@ -456,13 +471,16 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos { if (designIdentifier.Length == 0) { - Glamourer.Messager.NotificationMessage($"Error parsing automatically applied design for set {setName}: No design specified.", NotificationType.Warning); + Glamourer.Messager.NotificationMessage($"Error parsing automatically applied design for set {setName}: No design specified.", + NotificationType.Warning); return null; } if (!Guid.TryParse(designIdentifier, out var guid)) { - Glamourer.Messager.NotificationMessage($"Error parsing automatically applied design for set {setName}: {designIdentifier} is not a valid GUID.", NotificationType.Warning); + Glamourer.Messager.NotificationMessage( + $"Error parsing automatically applied design for set {setName}: {designIdentifier} is not a valid GUID.", + NotificationType.Warning); return null; } @@ -471,7 +489,8 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos if (design == null) { Glamourer.Messager.NotificationMessage( - $"Error parsing automatically applied design for set {setName}: The specified design {guid} does not exist.", NotificationType.Warning); + $"Error parsing automatically applied design for set {setName}: The specified design {guid} does not exist.", + NotificationType.Warning); return null; } } @@ -483,24 +502,31 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos Design = design, ApplicationType = applicationType & AutoDesign.Type.All, }; + return ParseConditions(setName, jObj, ret) ? ret : null; + } + private bool ParseConditions(string setName, JObject jObj, AutoDesign ret) + { var conditions = jObj["Conditions"]; if (conditions == null) - return ret; + return true; var jobs = conditions["JobGroup"]?.ToObject() ?? -1; if (jobs >= 0) { if (!_jobs.JobGroups.TryGetValue((ushort)jobs, out var jobGroup)) { - Glamourer.Messager.NotificationMessage($"Error parsing automatically applied design for set {setName}: The job condition {jobs} does not exist.", NotificationType.Warning); - return null; + Glamourer.Messager.NotificationMessage( + $"Error parsing automatically applied design for set {setName}: The job condition {jobs} does not exist.", + NotificationType.Warning); + return false; } ret.Jobs = jobGroup; } - return ret; + ret.GearsetIndex = conditions["Gearset"]?.ToObject() ?? -1; + return true; } private void Save() diff --git a/Glamourer/Events/EquippedGearset.cs b/Glamourer/Events/EquippedGearset.cs new file mode 100644 index 0000000..a8fafff --- /dev/null +++ b/Glamourer/Events/EquippedGearset.cs @@ -0,0 +1,30 @@ +using System; +using OtterGui.Classes; + +namespace Glamourer.Events; + +/// +/// Triggered when the player equips a gear set. +/// +/// Parameter is the name of the gear set. +/// Parameter is the id of the gear set. +/// Parameter is the id of the prior gear set. +/// Parameter is the id of the associated glamour. +/// Parameter is the job id of the associated job. +/// +/// +public sealed class EquippedGearset : EventWrapper, EquippedGearset.Priority> +{ + public enum Priority + { + /// + AutoDesignApplier = 0, + } + + public EquippedGearset() + : base(nameof(EquippedGearset)) + { } + + public void Invoke(string name, int id, int lastId, byte glamour, byte jobId) + => Invoke(this, name, id, lastId, glamour, jobId); +} diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 3a0f437..918f96a 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Text; @@ -29,17 +30,18 @@ public class SetPanel private readonly CustomizeUnlockManager _customizeUnlocks; private readonly CustomizationService _customizations; - private readonly Configuration _config; - private readonly RevertDesignCombo _designCombo; - private readonly JobGroupCombo _jobGroupCombo; - private readonly IdentifierDrawer _identifierDrawer; + private readonly Configuration _config; + private readonly RevertDesignCombo _designCombo; + private readonly JobGroupCombo _jobGroupCombo; + private readonly IdentifierDrawer _identifierDrawer; private string? _tempName; private int _dragIndex = -1; private Action? _endAction; - public SetPanel(SetSelector selector, AutoDesignManager manager, JobService jobs, ItemUnlockManager itemUnlocks, RevertDesignCombo designCombo, + public SetPanel(SetSelector selector, AutoDesignManager manager, JobService jobs, ItemUnlockManager itemUnlocks, + RevertDesignCombo designCombo, CustomizeUnlockManager customizeUnlocks, CustomizationService customizations, IdentifierDrawer identifierDrawer, Configuration config) { _selector = selector; @@ -216,11 +218,11 @@ public class SetPanel ImGui.TableNextColumn(); DrawApplicationTypeBoxes(Selection, design, idx, singleRow); ImGui.TableNextColumn(); - _jobGroupCombo.Draw(Selection, design, idx); + DrawConditions(design, idx); } else { - _jobGroupCombo.Draw(Selection, design, idx); + DrawConditions(design, idx); ImGui.TableNextColumn(); DrawApplicationTypeBoxes(Selection, design, idx, singleRow); } @@ -244,6 +246,38 @@ public class SetPanel _endAction = null; } + private int _tmpGearset = int.MaxValue; + + private void DrawConditions(AutoDesign design, int idx) + { + var usingGearset = design.GearsetIndex >= 0; + if (ImGui.Button($"{(usingGearset ? "Gearset:" : "Jobs:")}##usingGearset")) + { + usingGearset = !usingGearset; + _manager.ChangeGearsetCondition(Selection, idx, (short)(usingGearset ? 0 : -1)); + } + + ImGuiUtil.HoverTooltip("Click to switch between Job and Gearset restrictions."); + + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + if (usingGearset) + { + var set = 1 + (_tmpGearset == int.MaxValue ? design.GearsetIndex : _tmpGearset); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + if (ImGui.InputInt("##whichGearset", ref set, 0, 0)) + _tmpGearset = Math.Clamp(set, 1, 100); + if (ImGui.IsItemDeactivatedAfterEdit()) + { + _manager.ChangeGearsetCondition(Selection, idx, (short)(_tmpGearset - 1)); + _tmpGearset = int.MaxValue; + } + } + else + { + _jobGroupCombo.Draw(Selection, design, idx); + } + } + private void DrawWarnings(AutoDesign design, int idx) { if (design.Revert) diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index f2832bd..4235da4 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -7,17 +7,20 @@ using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Glamourer.Events; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using Penumbra.String; namespace Glamourer.Interop; public unsafe class InventoryService : IDisposable { - private readonly MovedEquipment _event; + private readonly MovedEquipment _movedItemsEvent; + private readonly EquippedGearset _gearsetEvent; private readonly List<(EquipSlot, uint, StainId)> _itemList = new(12); - public InventoryService(MovedEquipment @event, IGameInteropProvider interop) + public InventoryService(MovedEquipment movedItemsEvent, IGameInteropProvider interop, EquippedGearset gearsetEvent) { - _event = @event; + _movedItemsEvent = movedItemsEvent; + _gearsetEvent = gearsetEvent; _moveItemHook = interop.HookFromAddress((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour); _equipGearsetHook = @@ -39,7 +42,10 @@ public unsafe class InventoryService : IDisposable private int EquipGearSetDetour(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId) { - var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); + var prior = module->CurrentGearsetIndex; + var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); + var set = module->GetGearset(gearsetId); + _gearsetEvent.Invoke(new ByteString(set->Name).ToString(), gearsetId, prior, glamourPlateId, set->ClassJob); Glamourer.Log.Excessive($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); if (ret == 0) { @@ -107,7 +113,7 @@ public unsafe class InventoryService : IDisposable Add(EquipSlot.LFinger, ref entry->RingLeft); } - _event.Invoke(_itemList.ToArray()); + _movedItemsEvent.Invoke(_itemList.ToArray()); } return ret; @@ -127,18 +133,18 @@ public unsafe class InventoryService : IDisposable { if (InvokeSource(sourceContainer, sourceSlot, out var source)) if (InvokeTarget(manager, targetContainer, targetSlot, out var target)) - _event.Invoke(new[] + _movedItemsEvent.Invoke(new[] { source, target, }); else - _event.Invoke(new[] + _movedItemsEvent.Invoke(new[] { source, }); else if (InvokeTarget(manager, targetContainer, targetSlot, out var target)) - _event.Invoke(new[] + _movedItemsEvent.Invoke(new[] { target, }); diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 6e9cffa..092d8c2 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -73,6 +73,7 @@ public static class ServiceManager .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton(); From c79997dba8ede3ffa5b3a7d5b09949245fbc8f01 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 25 Nov 2023 18:31:10 +0100 Subject: [PATCH 06/20] Use https submodule. --- .gitmodules | 8 ++++---- OtterGui | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitmodules b/.gitmodules index f74e14d..7203d22 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,16 +1,16 @@ [submodule "OtterGui"] path = OtterGui - url = git@github.com:Ottermandias/OtterGui.git + url = https://github.com/Ottermandias/OtterGui.git branch = main [submodule "Penumbra.GameData"] path = Penumbra.GameData - url = git@github.com:Ottermandias/Penumbra.GameData.git + url = https://github.com/Ottermandias/Penumbra.GameData.git branch = main [submodule "Penumbra.String"] path = Penumbra.String - url = git@github.com:Ottermandias/Penumbra.String.git + url = https://github.com/Ottermandias/Penumbra.String.git branch = main [submodule "Penumbra.Api"] path = Penumbra.Api - url = git@github.com:Ottermandias/Penumbra.Api.git + url = https://github.com/Ottermandias/Penumbra.Api.git branch = main diff --git a/OtterGui b/OtterGui index 3a1a3f1..858b610 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 3a1a3f1a1f2021b063617ac9b294b579a154706e +Subproject commit 858b610a194ee52b4e8e89c0c64d9f2653025524 From 8412c2bc68b76e5f24fe1283d5d25661c70a47fb Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 25 Nov 2023 19:52:09 +0100 Subject: [PATCH 07/20] Add NoDocking flags to QDB. --- Glamourer/Gui/DesignQuickBar.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index e6f5eac..4346f01 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -22,8 +22,8 @@ public class DesignQuickBar : Window, IDisposable { private ImGuiWindowFlags GetFlags => _config.Ephemeral.LockDesignQuickBar - ? ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoMove - : ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoFocusOnAppearing; + ? ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoMove + : ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoFocusOnAppearing; private readonly Configuration _config; private readonly DesignCombo _designCombo; @@ -37,7 +37,7 @@ public class DesignQuickBar : Window, IDisposable public DesignQuickBar(Configuration config, DesignCombo designCombo, StateManager stateManager, IKeyState keyState, ObjectManager objects, AutoDesignApplier autoDesignApplier) - : base("Glamourer Quick Bar", ImGuiWindowFlags.NoDecoration) + : base("Glamourer Quick Bar", ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking) { _config = config; _designCombo = designCombo; From eed11bb67f46221cf1483ccae7311f8bb090c46e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 25 Nov 2023 19:52:30 +0100 Subject: [PATCH 08/20] Fix HQ Item IDs in inventory service. --- Glamourer/Interop/InventoryService.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 4235da4..6e72e91 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -70,7 +70,7 @@ public unsafe class InventoryService : IDisposable else if (item.GlamourId != 0) _itemList.Add((slot, item.GlamourId, item.Stain)); else - _itemList.Add((slot, item.ItemID, item.Stain)); + _itemList.Add((slot, FixId(item.ItemID), item.Stain)); } var plate = MirageManager.Instance()->GlamourPlatesSpan[glamourPlateId - 1]; @@ -96,7 +96,7 @@ public unsafe class InventoryService : IDisposable else if (item.GlamourId != 0) _itemList.Add((slot, item.GlamourId, item.Stain)); else - _itemList.Add((slot, item.ItemID, item.Stain)); + _itemList.Add((slot, FixId(item.ItemID), item.Stain)); } Add(EquipSlot.MainHand, ref entry->MainHand); @@ -119,6 +119,9 @@ public unsafe class InventoryService : IDisposable return ret; } + private static uint FixId(uint itemId) + => itemId % 50000; + private delegate int MoveItemDelegate(InventoryManager* manager, InventoryType sourceContainer, ushort sourceSlot, InventoryType targetContainer, ushort targetSlot, byte unk); From 60a53d4bff0a7e60041fe6f85824785e47b86bd7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 25 Nov 2023 21:05:06 +0100 Subject: [PATCH 09/20] Refactor drawing of equipment to be more sane. --- .../CustomizationDrawer.Simple.cs | 6 +- .../Gui/Customization/CustomizationDrawer.cs | 47 +- Glamourer/Gui/Equipment/EquipDrawData.cs | 49 + Glamourer/Gui/Equipment/EquipmentDrawer.cs | 916 +++++++----------- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 100 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 87 +- Glamourer/Gui/ToggleDrawData.cs | 79 ++ Glamourer/Gui/UiHelpers.cs | 13 +- OtterGui | 2 +- 9 files changed, 536 insertions(+), 763 deletions(-) create mode 100644 Glamourer/Gui/Equipment/EquipDrawData.cs create mode 100644 Glamourer/Gui/ToggleDrawData.cs diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs index 3e15cad..e9ce9e9 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs @@ -194,14 +194,14 @@ public partial class CustomizationDrawer { switch (UiHelpers.DrawMetaToggle(_currentIndex.ToDefaultName(), tmp, _currentApply, out var newValue, out var newApply, _locked)) { - case DataChange.Item: + case (true, false): _customize.Set(idx, newValue ? CustomizeValue.Max : CustomizeValue.Zero); Changed |= _currentFlag; break; - case DataChange.ApplyItem: + case (false, true): ChangeApply = newApply ? ChangeApply | _currentFlag : ChangeApply & ~_currentFlag; break; - case DataChange.Item | DataChange.ApplyItem: + case (true, true): ChangeApply = newApply ? ChangeApply | _currentFlag : ChangeApply & ~_currentFlag; _customize.Set(idx, newValue ? CustomizeValue.Max : CustomizeValue.Zero); Changed |= _currentFlag; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index 0f76b62..fa725f5 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -14,18 +14,16 @@ using CustomizeData = Penumbra.GameData.Structs.CustomizeData; namespace Glamourer.Gui.Customization; -public partial class CustomizationDrawer : IDisposable +public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizationService _service, CodeService _codes, Configuration _config) + : IDisposable { - private readonly CodeService _codes; - private readonly Configuration _config; - - private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f); - private readonly IDalamudTextureWrap? _legacyTattoo; + private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f); + private readonly IDalamudTextureWrap? _legacyTattoo = GetLegacyTattooIcon(pi); private Exception? _terminate; - private Customize _customize; - private CustomizationSet _set = null!; + private Customize _customize = Customize.Default; + private CustomizationSet _set = null!; public Customize Customize => _customize; @@ -46,21 +44,8 @@ public partial class CustomizationDrawer : IDisposable private float _raceSelectorWidth; private bool _withApply; - private readonly CustomizationService _service; - - public CustomizationDrawer(DalamudPluginInterface pi, CustomizationService service, CodeService codes, Configuration config) - { - _service = service; - _codes = codes; - _config = config; - _legacyTattoo = GetLegacyTattooIcon(pi); - _customize = Customize.Default; - } - public void Dispose() - { - _legacyTattoo?.Dispose(); - } + => _legacyTattoo?.Dispose(); public bool Draw(Customize current, bool locked, bool lockedRedraw) { @@ -125,12 +110,6 @@ public partial class CustomizationDrawer : IDisposable Changed |= _currentFlag; } - public bool DrawWetnessState(bool currentValue, out bool newValue, bool locked) - => UiHelpers.DrawCheckbox("Force Wetness", "Force the character to be wet or not.", currentValue, out newValue, locked); - - public DataChange DrawWetnessState(bool currentValue, bool currentApply, out bool newValue, out bool newApply, bool locked) - => UiHelpers.DrawMetaToggle("Force Wetness", currentValue, currentApply, out newValue, out newApply, locked); - private bool DrawInternal() { using var spacing = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _spacing); @@ -199,13 +178,13 @@ public partial class CustomizationDrawer : IDisposable private void UpdateSizes() { - _spacing = ImGui.GetStyle().ItemSpacing with { X = ImGui.GetStyle().ItemInnerSpacing.X }; - _iconSize = new Vector2(ImGui.GetTextLineHeight() * 2 + _spacing.Y + 2 * ImGui.GetStyle().FramePadding.Y); - _framedIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding; - _inputIntSize = 2 * _framedIconSize.X + 1 * _spacing.X; + _spacing = ImGui.GetStyle().ItemSpacing with { X = ImGui.GetStyle().ItemInnerSpacing.X }; + _iconSize = new Vector2(ImGui.GetTextLineHeight() * 2 + _spacing.Y + 2 * ImGui.GetStyle().FramePadding.Y); + _framedIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding; + _inputIntSize = 2 * _framedIconSize.X + 1 * _spacing.X; _inputIntSizeNoButtons = _inputIntSize - 2 * _spacing.X - 2 * ImGui.GetFrameHeight(); - _comboSelectorSize = 4 * _framedIconSize.X + 3 * _spacing.X; - _raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X; + _comboSelectorSize = 4 * _framedIconSize.X + 3 * _spacing.X; + _raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X; } private static IDalamudTextureWrap? GetLegacyTattooIcon(DalamudPluginInterface pi) diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs new file mode 100644 index 0000000..ce2ba04 --- /dev/null +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -0,0 +1,49 @@ +using System; +using Glamourer.Designs; +using Glamourer.Events; +using Glamourer.State; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Gui.Equipment; + +public ref struct EquipDrawData(EquipSlot slot, in DesignData designData) +{ + public readonly EquipSlot Slot = slot; + public bool Locked; + public bool DisplayApplication; + + public Action ItemSetter = null!; + public Action StainSetter = null!; + public Action ApplySetter = null!; + public Action ApplyStainSetter = null!; + public EquipItem CurrentItem = designData.Item(slot); + public StainId CurrentStain = designData.Stain(slot); + public bool CurrentApply; + public bool CurrentApplyStain; + + public readonly Gender CurrentGender = designData.Customize.Gender; + public readonly Race CurrentRace = designData.Customize.Race; + + public static EquipDrawData FromDesign(DesignManager manager, Design design, EquipSlot slot) + => new(slot, design.DesignData) + { + ItemSetter = i => manager.ChangeEquip(design, slot, i), + StainSetter = i => manager.ChangeStain(design, slot, i), + ApplySetter = b => manager.ChangeApplyEquip(design, slot, b), + ApplyStainSetter = b => manager.ChangeApplyStain(design, slot, b), + CurrentApply = design.DoApplyEquip(slot), + CurrentApplyStain = design.DoApplyStain(slot), + Locked = design.WriteProtected(), + DisplayApplication = true, + }; + + public static EquipDrawData FromState(StateManager manager, ActorState state, EquipSlot slot) + => new(slot, state.ModelData) + { + ItemSetter = i => manager.ChangeItem(state, slot, i, StateChanged.Source.Manual), + StainSetter = i => manager.ChangeStain(state, slot, i, StateChanged.Source.Manual), + Locked = state.IsLocked, + DisplayApplication = false, + }; +} diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index ca78bea..e2b44de 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -6,10 +6,8 @@ using System.Numerics; using Dalamud.Interface.Components; using Dalamud.Interface.Utility; using Dalamud.Plugin.Services; -using Glamourer.Designs; using Glamourer.Events; using Glamourer.Services; -using Glamourer.Structs; using Glamourer.Unlocks; using ImGuiNET; using OtterGui; @@ -76,211 +74,71 @@ public class EquipmentDrawer _requiredComboWidth = _requiredComboWidthUnscaled * ImGuiHelpers.GlobalScale; } - private bool VerifyRestrictedGear(EquipSlot slot, EquipItem gear, Gender gender, Race race) + private bool VerifyRestrictedGear(EquipDrawData data) { - if (slot.IsAccessory()) + if (data.Slot.IsAccessory()) return false; - var (changed, _) = _items.ResolveRestrictedGear(gear.Armor(), slot, race, gender); + var (changed, _) = _items.ResolveRestrictedGear(data.CurrentItem.Armor(), data.Slot, data.CurrentRace, data.CurrentGender); return changed; } - - public DataChange DrawEquip(EquipSlot slot, in DesignData designData, out EquipItem rArmor, out StainId rStain, EquipFlag? cApply, - out bool rApply, out bool rApplyStain, bool locked) - => DrawEquip(slot, designData.Item(slot), out rArmor, designData.Stain(slot), out rStain, cApply, out rApply, out rApplyStain, locked, - designData.Customize.Gender, designData.Customize.Race); - - public DataChange DrawEquip(EquipSlot slot, EquipItem cArmor, out EquipItem rArmor, StainId cStain, out StainId rStain, EquipFlag? cApply, - out bool rApply, out bool rApplyStain, bool locked, Gender gender = Gender.Unknown, Race race = Race.Unknown) + public void DrawEquip(EquipDrawData equipDrawData) { if (_config.HideApplyCheckmarks) - cApply = null; + equipDrawData.DisplayApplication = false; - using var id = ImRaii.PushId((int)slot); + using var id = ImRaii.PushId((int)equipDrawData.Slot); var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y }; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); if (_config.SmallEquip) - return DrawEquipSmall(slot, cArmor, out rArmor, cStain, out rStain, cApply, out rApply, out rApplyStain, locked, gender, race); - - if (!locked && _codes.EnabledArtisan) - return DrawEquipArtisan(slot, cArmor, out rArmor, cStain, out rStain, cApply, out rApply, out rApplyStain); - - return DrawEquipNormal(slot, cArmor, out rArmor, cStain, out rStain, cApply, out rApply, out rApplyStain, locked, gender, race); + DrawEquipSmall(equipDrawData); + else if (!equipDrawData.Locked && _codes.EnabledArtisan) + DrawEquipArtisan(equipDrawData); + else + DrawEquipNormal(equipDrawData); } - public DataChange DrawWeapons(in DesignData designData, out EquipItem rMainhand, out EquipItem rOffhand, out StainId rMainhandStain, - out StainId rOffhandStain, EquipFlag? cApply, bool allWeapons, out bool rApplyMainhand, out bool rApplyMainhandStain, - out bool rApplyOffhand, out bool rApplyOffhandStain, bool locked) - => DrawWeapons(designData.Item(EquipSlot.MainHand), out rMainhand, designData.Item(EquipSlot.OffHand), out rOffhand, - designData.Stain(EquipSlot.MainHand), out rMainhandStain, designData.Stain(EquipSlot.OffHand), out rOffhandStain, cApply, - allWeapons, out rApplyMainhand, out rApplyMainhandStain, out rApplyOffhand, out rApplyOffhandStain, locked); - - private DataChange DrawWeapons(EquipItem cMainhand, out EquipItem rMainhand, EquipItem cOffhand, out EquipItem rOffhand, - StainId cMainhandStain, out StainId rMainhandStain, StainId cOffhandStain, out StainId rOffhandStain, EquipFlag? cApply, - bool allWeapons, out bool rApplyMainhand, out bool rApplyMainhandStain, out bool rApplyOffhand, out bool rApplyOffhandStain, - bool locked) + public void DrawWeapons(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons) { - if (cMainhand.ModelId.Id == 0) - { - rOffhand = cOffhand; - rMainhand = cMainhand; - rMainhandStain = cMainhandStain; - rOffhandStain = cOffhandStain; - rApplyMainhand = false; - rApplyMainhandStain = false; - rApplyOffhand = false; - rApplyOffhandStain = false; - return DataChange.None; - } + if (mainhand.CurrentItem.ModelId.Id == 0) + return; if (_config.HideApplyCheckmarks) - cApply = null; + { + mainhand.DisplayApplication = false; + offhand.DisplayApplication = false; + } using var id = ImRaii.PushId("Weapons"); var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y }; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); if (_config.SmallEquip) - return DrawWeaponsSmall(cMainhand, out rMainhand, cOffhand, out rOffhand, cMainhandStain, out rMainhandStain, cOffhandStain, - out rOffhandStain, cApply, out rApplyMainhand, out rApplyMainhandStain, out rApplyOffhand, out rApplyOffhandStain, locked, - allWeapons); - - if (!locked && _codes.EnabledArtisan) - return DrawWeaponsArtisan(cMainhand, out rMainhand, cOffhand, out rOffhand, cMainhandStain, out rMainhandStain, cOffhandStain, - out rOffhandStain, cApply, out rApplyMainhand, out rApplyMainhandStain, out rApplyOffhand, out rApplyOffhandStain); - - return DrawWeaponsNormal(cMainhand, out rMainhand, cOffhand, out rOffhand, cMainhandStain, out rMainhandStain, cOffhandStain, - out rOffhandStain, cApply, out rApplyMainhand, out rApplyMainhandStain, out rApplyOffhand, out rApplyOffhandStain, locked, - allWeapons); + DrawWeaponsSmall(mainhand, offhand, allWeapons); + else if (!mainhand.Locked && _codes.EnabledArtisan) + DrawWeaponsArtisan(mainhand, offhand); + else + DrawWeaponsNormal(mainhand, offhand, allWeapons); } - public static bool DrawHatState(bool currentValue, out bool newValue, bool locked) - => UiHelpers.DrawCheckbox("Hat Visible", "Hide or show the characters head gear.", currentValue, out newValue, locked); - - public static DataChange DrawHatState(bool currentValue, bool currentApply, out bool newValue, out bool newApply, bool locked) - => UiHelpers.DrawMetaToggle("Hat Visible", currentValue, currentApply, out newValue, out newApply, locked); - - public static bool DrawVisorState(bool currentValue, out bool newValue, bool locked) - => UiHelpers.DrawCheckbox("Visor Toggled", "Toggle the visor state of the characters head gear.", currentValue, out newValue, locked); - - public static DataChange DrawVisorState(bool currentValue, bool currentApply, out bool newValue, out bool newApply, bool locked) - => UiHelpers.DrawMetaToggle("Visor Toggled", currentValue, currentApply, out newValue, out newApply, locked); - - public static bool DrawWeaponState(bool currentValue, out bool newValue, bool locked) - => UiHelpers.DrawCheckbox("Weapon Visible", "Hide or show the characters weapons when not drawn.", currentValue, out newValue, locked); - - public static DataChange DrawWeaponState(bool currentValue, bool currentApply, out bool newValue, out bool newApply, bool locked) - => UiHelpers.DrawMetaToggle("Weapon Visible", currentValue, currentApply, out newValue, out newApply, locked); - - private bool DrawMainhand(EquipItem current, bool drawAll, out EquipItem weapon, out string label, bool locked, bool small, bool open) + public static void DrawMetaToggle(in ToggleDrawData data) { - weapon = current; - if (!_weaponCombo.TryGetValue(drawAll ? FullEquipType.Unknown : current.Type, out var combo)) + if (data.DisplayApplication) { - label = string.Empty; - return false; + var (valueChanged, applyChanged) = UiHelpers.DrawMetaToggle(data.Label, data.CurrentValue, data.CurrentApply, out var newValue, + out var newApply, data.Locked); + if (valueChanged) + data.SetValue(newValue); + if (applyChanged) + data.SetApply(newApply); } - - label = combo.Label; - - var unknown = !_gPose.InGPose && current.Type is FullEquipType.Unknown; - var ret = false; - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing); - using (var disabled = ImRaii.Disabled(locked | unknown)) + else { - if (!locked && open) - UiHelpers.OpenCombo($"##{label}"); - if (combo.Draw(weapon.Name, weapon.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth)) - { - ret = true; - weapon = combo.CurrentSelection; - } + if (UiHelpers.DrawCheckbox(data.Label, data.Tooltip, data.CurrentValue, out var newValue, data.Locked)) + data.SetValue(newValue); } - - if (unknown && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) - ImGui.SetTooltip("The weapon type could not be identified, thus changing it to other weapons of that type is not possible."); - - return ret; - } - - private bool DrawOffhand(EquipItem mainhand, EquipItem current, out EquipItem weapon, out string label, bool locked, bool small, bool clear, - bool open) - { - weapon = current; - if (!_weaponCombo.TryGetValue(current.Type, out var combo)) - { - label = string.Empty; - return false; - } - - label = combo.Label; - locked |= !_gPose.InGPose && (current.Type is FullEquipType.Unknown || mainhand.Type is FullEquipType.Unknown); - using var disabled = ImRaii.Disabled(locked); - if (!locked && open) - UiHelpers.OpenCombo($"##{combo.Label}"); - var change = combo.Draw(weapon.Name, weapon.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth); - if (change) - weapon = combo.CurrentSelection; - - if (!locked) - { - var defaultOffhand = _items.GetDefaultOffhand(mainhand); - if (defaultOffhand.Id != weapon.Id) - { - ImGuiUtil.HoverTooltip("Right-click to set to Default."); - if (clear || ImGui.IsItemClicked(ImGuiMouseButton.Right)) - { - change = true; - weapon = defaultOffhand; - } - } - } - - return change; - } - - private bool DrawApply(EquipSlot slot, EquipFlag flags, out bool enabled, bool locked) - => UiHelpers.DrawCheckbox($"##apply{slot}", "Apply this item when applying the Design.", flags.HasFlag(slot.ToFlag()), out enabled, - locked); - - private bool DrawApplyStain(EquipSlot slot, EquipFlag flags, out bool enabled, bool locked) - => UiHelpers.DrawCheckbox($"##applyStain{slot}", "Apply this dye when applying the Design.", flags.HasFlag(slot.ToStainFlag()), - out enabled, locked); - - private bool DrawItem(EquipSlot slot, EquipItem current, out EquipItem armor, out string label, bool locked, bool small, bool clear, - bool open) - { - Debug.Assert(slot.IsEquipment() || slot.IsAccessory(), $"Called {nameof(DrawItem)} on {slot}."); - var combo = _itemCombo[slot.ToIndex()]; - label = combo.Label; - armor = current; - if (!locked && open) - UiHelpers.OpenCombo($"##{combo.Label}"); - - using var disabled = ImRaii.Disabled(locked); - var change = combo.Draw(armor.Name, armor.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth); - if (change) - armor = combo.CurrentSelection; - else if (combo.CustomVariant.Id > 0) - { - armor = _items.Identify(slot, combo.CustomSetId, combo.CustomVariant); - change = true; - } - - if (!locked && armor.ModelId.Id != 0) - { - if (clear || ImGui.IsItemClicked(ImGuiMouseButton.Right)) - { - change = true; - armor = ItemManager.NothingItem(slot); - } - - ImGuiUtil.HoverTooltip("Right-click to clear."); - } - - return change; } public bool DrawAllStain(out StainId ret, bool locked) @@ -308,50 +166,97 @@ public class EquipmentDrawer return change; } - private bool DrawStain(EquipSlot slot, StainId current, out StainId ret, bool locked, bool small) + #region Artisan + + private void DrawEquipArtisan(EquipDrawData data) { - var found = _stainData.TryGetValue(current, out var stain); - using var disabled = ImRaii.Disabled(locked); - var change = small - ? _stainCombo.Draw($"##stain{slot}", stain.RgbaColor, stain.Name, found, stain.Gloss) - : _stainCombo.Draw($"##stain{slot}", stain.RgbaColor, stain.Name, found, stain.Gloss, _comboLength); - ret = current; - if (change) - if (_stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain)) - ret = stain.RowIndex; - else if (_stainCombo.CurrentSelection.Key == Stain.None.RowIndex) - ret = Stain.None.RowIndex; + DrawStainArtisan(data); + ImGui.SameLine(); + DrawArmorArtisan(data); + if (!data.DisplayApplication) + return; - if (!locked && ret != Stain.None.RowIndex) + ImGui.SameLine(); + DrawApply(data); + ImGui.SameLine(); + DrawApplyStain(data); + } + + private void DrawWeaponsArtisan(in EquipDrawData mainhand, in EquipDrawData offhand) + { + using (var _ = ImRaii.PushId(0)) { - if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) - { - ret = Stain.None.RowIndex; - change = true; - } - - ImGuiUtil.HoverTooltip("Right-click to clear."); + DrawStainArtisan(mainhand); + ImGui.SameLine(); + DrawWeapon(mainhand); } - return change; + using (var _ = ImRaii.PushId(1)) + { + DrawStainArtisan(offhand); + ImGui.SameLine(); + DrawWeapon(offhand); + } + + return; + + void DrawWeapon(in EquipDrawData current) + { + int setId = current.CurrentItem.ModelId.Id; + int type = current.CurrentItem.WeaponType.Id; + int variant = current.CurrentItem.Variant.Id; + ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); + if (ImGui.InputInt("##setId", ref setId, 0, 0)) + { + var newSetId = (SetId)Math.Clamp(setId, 0, ushort.MaxValue); + if (newSetId.Id != current.CurrentItem.ModelId.Id) + current.ItemSetter(_items.Identify(current.Slot, newSetId, current.CurrentItem.WeaponType, current.CurrentItem.Variant)); + } + + ImGui.SameLine(); + ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); + if (ImGui.InputInt("##type", ref type, 0, 0)) + { + var newType = (WeaponType)Math.Clamp(type, 0, ushort.MaxValue); + if (newType.Id != current.CurrentItem.WeaponType.Id) + current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.ModelId, newType, current.CurrentItem.Variant)); + } + + ImGui.SameLine(); + ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); + if (ImGui.InputInt("##variant", ref variant, 0, 0)) + { + var newVariant = (Variant)Math.Clamp(variant, 0, byte.MaxValue); + if (newVariant.Id != current.CurrentItem.Variant.Id) + current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.ModelId, current.CurrentItem.WeaponType, newVariant)); + } + } + } + + /// Draw an input for stain that can set arbitrary values instead of choosing valid stains. + private static void DrawStainArtisan(EquipDrawData data) + { + int stainId = data.CurrentStain.Id; + ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); + if (!ImGui.InputInt("##stain", ref stainId, 0, 0)) + return; + + var newStainId = (StainId)Math.Clamp(stainId, 0, byte.MaxValue); + if (newStainId != data.CurrentStain.Id) + data.StainSetter(newStainId); } /// Draw an input for armor that can set arbitrary values instead of choosing items. - private bool DrawArmorArtisan(EquipSlot slot, EquipItem current, out EquipItem armor) + private void DrawArmorArtisan(EquipDrawData data) { - int setId = current.ModelId.Id; - int variant = current.Variant.Id; - var ret = false; - armor = current; + int setId = data.CurrentItem.ModelId.Id; + int variant = data.CurrentItem.Variant.Id; ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); if (ImGui.InputInt("##setId", ref setId, 0, 0)) { var newSetId = (SetId)Math.Clamp(setId, 0, ushort.MaxValue); - if (newSetId.Id != current.ModelId.Id) - { - armor = _items.Identify(slot, newSetId, current.Variant); - ret = true; - } + if (newSetId.Id != data.CurrentItem.ModelId.Id) + data.ItemSetter(_items.Identify(data.Slot, newSetId, data.CurrentItem.Variant)); } ImGui.SameLine(); @@ -359,141 +264,286 @@ public class EquipmentDrawer if (ImGui.InputInt("##variant", ref variant, 0, 0)) { var newVariant = (byte)Math.Clamp(variant, 0, byte.MaxValue); - if (newVariant != current.Variant) - { - armor = _items.Identify(slot, current.ModelId, newVariant); - ret = true; - } + if (newVariant != data.CurrentItem.Variant) + data.ItemSetter(_items.Identify(data.Slot, data.CurrentItem.ModelId, newVariant)); } - - return ret; } - /// Draw an input for stain that can set arbitrary values instead of choosing valid stains. - private bool DrawStainArtisan(EquipSlot slot, StainId current, out StainId stain) - { - int stainId = current.Id; - ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); - if (ImGui.InputInt("##stain", ref stainId, 0, 0)) - { - var newStainId = (StainId)Math.Clamp(stainId, 0, byte.MaxValue); - if (newStainId != current) - { - stain = newStainId; - return true; - } - } + #endregion - stain = current; - return false; - } + #region Small - private DataChange DrawEquipArtisan(EquipSlot slot, EquipItem cArmor, out EquipItem rArmor, StainId cStain, out StainId rStain, - EquipFlag? cApply, out bool rApply, out bool rApplyStain) + private void DrawEquipSmall(in EquipDrawData equipDrawData) { - var changes = DataChange.None; - if (DrawStainArtisan(slot, cStain, out rStain)) - changes |= DataChange.Stain; + DrawStain(equipDrawData, true); ImGui.SameLine(); - if (DrawArmorArtisan(slot, cArmor, out rArmor)) - changes |= DataChange.Item; - if (cApply.HasValue) + DrawItem(equipDrawData, out var label, true, false, false); + if (equipDrawData.DisplayApplication) { ImGui.SameLine(); - if (DrawApply(slot, cApply.Value, out rApply, false)) - changes |= DataChange.ApplyItem; + DrawApply(equipDrawData); ImGui.SameLine(); - if (DrawApplyStain(slot, cApply.Value, out rApplyStain, false)) - changes |= DataChange.ApplyStain; - } - else - { - rApply = false; - rApplyStain = false; + DrawApplyStain(equipDrawData); } - return changes; - } - - private DataChange DrawEquipSmall(EquipSlot slot, EquipItem cArmor, out EquipItem rArmor, StainId cStain, out StainId rStain, - EquipFlag? cApply, out bool rApply, out bool rApplyStain, bool locked, Gender gender, Race race) - { - var changes = DataChange.None; - if (DrawStain(slot, cStain, out rStain, locked, true)) - changes |= DataChange.Stain; - ImGui.SameLine(); - if (DrawItem(slot, cArmor, out rArmor, out var label, locked, true, false, false)) - changes |= DataChange.Item; - if (cApply.HasValue) - { - ImGui.SameLine(); - if (DrawApply(slot, cApply.Value, out rApply, false)) - changes |= DataChange.ApplyItem; - ImGui.SameLine(); - if (DrawApplyStain(slot, cApply.Value, out rApplyStain, false)) - changes |= DataChange.ApplyStain; - } - else - { - rApply = false; - rApplyStain = false; - } - - if (VerifyRestrictedGear(slot, rArmor, gender, race)) + if (VerifyRestrictedGear(equipDrawData)) label += " (Restricted)"; ImGui.SameLine(); ImGui.TextUnformatted(label); - - return changes; } - private DataChange DrawEquipNormal(EquipSlot slot, EquipItem cArmor, out EquipItem rArmor, StainId cStain, out StainId rStain, - EquipFlag? cApply, out bool rApply, out bool rApplyStain, bool locked, Gender gender, Race race) + private void DrawWeaponsSmall(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons) { - var changes = DataChange.None; - cArmor.DrawIcon(_textures, _iconSize, slot); + DrawStain(mainhand, true); + ImGui.SameLine(); + DrawMainhand(ref mainhand, ref offhand, out var mainhandLabel, allWeapons, true, false); + if (mainhand.DisplayApplication) + { + ImGui.SameLine(); + DrawApply(mainhand); + ImGui.SameLine(); + DrawApplyStain(mainhand); + } + + if (allWeapons) + mainhandLabel += $" ({mainhand.CurrentItem.Type.ToName()})"; + WeaponHelpMarker(mainhandLabel); + + if (offhand.CurrentItem.Type is FullEquipType.Unknown) + return; + + DrawStain(offhand, true); + ImGui.SameLine(); + DrawOffhand(mainhand, offhand, out var offhandLabel, true, false, false); + if (offhand.DisplayApplication) + { + ImGui.SameLine(); + DrawApply(offhand); + ImGui.SameLine(); + DrawApplyStain(offhand); + } + + WeaponHelpMarker(offhandLabel); + } + + #endregion + + #region Normal + + private void DrawEquipNormal(in EquipDrawData equipDrawData) + { + equipDrawData.CurrentItem.DrawIcon(_textures, _iconSize, equipDrawData.Slot); var right = ImGui.IsItemClicked(ImGuiMouseButton.Right); var left = ImGui.IsItemClicked(ImGuiMouseButton.Left); ImGui.SameLine(); using var group = ImRaii.Group(); - if (DrawItem(slot, cArmor, out rArmor, out var label, locked, false, right, left)) - changes |= DataChange.Item; - if (cApply.HasValue) + DrawItem(equipDrawData, out var label, false, right, left); + if (equipDrawData.DisplayApplication) { ImGui.SameLine(); - if (DrawApply(slot, cApply.Value, out rApply, locked)) - changes |= DataChange.ApplyItem; - } - else - { - rApply = true; + DrawApply(equipDrawData); } ImGui.SameLine(); ImGui.TextUnformatted(label); - if (DrawStain(slot, cStain, out rStain, locked, false)) - changes |= DataChange.Stain; - if (cApply.HasValue) + DrawStain(equipDrawData, false); + if (equipDrawData.DisplayApplication) { ImGui.SameLine(); - if (DrawApplyStain(slot, cApply.Value, out rApplyStain, locked)) - changes |= DataChange.ApplyStain; - } - else - { - rApplyStain = true; + DrawApplyStain(equipDrawData); } - if (VerifyRestrictedGear(slot, rArmor, gender, race)) + if (VerifyRestrictedGear(equipDrawData)) { ImGui.SameLine(); ImGui.TextUnformatted("(Restricted)"); } - - return changes; } + private void DrawWeaponsNormal(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons) + { + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, + ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y }); + + mainhand.CurrentItem.DrawIcon(_textures, _iconSize, EquipSlot.MainHand); + var left = ImGui.IsItemClicked(ImGuiMouseButton.Left); + ImGui.SameLine(); + using (var group = ImRaii.Group()) + { + DrawMainhand(ref mainhand, ref offhand, out var mainhandLabel, allWeapons, false, left); + if (mainhand.DisplayApplication) + { + ImGui.SameLine(); + DrawApply(mainhand); + } + + WeaponHelpMarker(mainhandLabel, allWeapons ? mainhand.CurrentItem.Type.ToName() : null); + + DrawStain(mainhand, false); + if (mainhand.DisplayApplication) + { + ImGui.SameLine(); + DrawApplyStain(mainhand); + } + } + + if (offhand.CurrentItem.Type is FullEquipType.Unknown) + return; + + offhand.CurrentItem.DrawIcon(_textures, _iconSize, EquipSlot.OffHand); + var right = ImGui.IsItemClicked(ImGuiMouseButton.Right); + left = ImGui.IsItemClicked(ImGuiMouseButton.Left); + ImGui.SameLine(); + using (var group = ImRaii.Group()) + { + DrawOffhand(mainhand, offhand, out var offhandLabel, false, right, left); + if (offhand.DisplayApplication) + { + ImGui.SameLine(); + DrawApply(offhand); + } + + WeaponHelpMarker(offhandLabel); + + DrawStain(offhand, false); + if (offhand.DisplayApplication) + { + ImGui.SameLine(); + DrawApplyStain(offhand); + } + } + } + + private void DrawStain(in EquipDrawData data, bool small) + { + var found = _stainData.TryGetValue(data.CurrentStain, out var stain); + using var disabled = ImRaii.Disabled(data.Locked); + var change = small + ? _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss) + : _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss, _comboLength); + if (change) + if (_stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain)) + data.StainSetter(stain.RowIndex); + else if (_stainCombo.CurrentSelection.Key == Stain.None.RowIndex) + data.StainSetter(Stain.None.RowIndex); + + if (!data.Locked && data.CurrentStain != Stain.None.RowIndex) + { + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + data.StainSetter(Stain.None.RowIndex); + + ImGuiUtil.HoverTooltip("Right-click to clear."); + } + } + + private void DrawItem(in EquipDrawData data, out string label, bool small, bool clear, bool open) + { + Debug.Assert(data.Slot.IsEquipment() || data.Slot.IsAccessory(), $"Called {nameof(DrawItem)} on {data.Slot}."); + + var combo = _itemCombo[data.Slot.ToIndex()]; + label = combo.Label; + if (!data.Locked && open) + UiHelpers.OpenCombo($"##{combo.Label}"); + + using var disabled = ImRaii.Disabled(data.Locked); + var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, + _requiredComboWidth); + if (change) + data.ItemSetter(combo.CurrentSelection); + else if (combo.CustomVariant.Id > 0) + data.ItemSetter(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant)); + + if (!data.Locked && data.CurrentItem.ModelId.Id != 0) + { + if (clear || ImGui.IsItemClicked(ImGuiMouseButton.Right)) + data.ItemSetter(ItemManager.NothingItem(data.Slot)); + + ImGuiUtil.HoverTooltip("Right-click to clear."); + } + } + + private void DrawMainhand(ref EquipDrawData mainhand, ref EquipDrawData offhand, out string label, bool drawAll, bool small, + bool open) + { + if (!_weaponCombo.TryGetValue(drawAll ? FullEquipType.Unknown : mainhand.CurrentItem.Type, out var combo)) + { + label = string.Empty; + return; + } + + label = combo.Label; + var unknown = !_gPose.InGPose && mainhand.CurrentItem.Type is FullEquipType.Unknown; + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing); + using (var _ = ImRaii.Disabled(mainhand.Locked | unknown)) + { + if (!mainhand.Locked && open) + UiHelpers.OpenCombo($"##{label}"); + if (combo.Draw(mainhand.CurrentItem.Name, mainhand.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, + _requiredComboWidth)) + { + mainhand.ItemSetter(combo.CurrentSelection); + if (combo.CurrentSelection.Type.ValidOffhand() != mainhand.CurrentItem.Type.ValidOffhand()) + { + offhand.CurrentItem = _items.GetDefaultOffhand(combo.CurrentSelection); + offhand.ItemSetter(offhand.CurrentItem); + } + + mainhand.CurrentItem = combo.CurrentSelection; + } + } + + if (unknown && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) + ImGui.SetTooltip("The weapon type could not be identified, thus changing it to other weapons of that type is not possible."); + } + + private void DrawOffhand(in EquipDrawData mainhand, in EquipDrawData offhand, out string label, bool small, bool clear, bool open) + { + if (!_weaponCombo.TryGetValue(offhand.CurrentItem.Type, out var combo)) + { + label = string.Empty; + return; + } + + label = combo.Label; + var locked = offhand.Locked + || !_gPose.InGPose && (offhand.CurrentItem.Type is FullEquipType.Unknown || mainhand.CurrentItem.Type is FullEquipType.Unknown); + using var disabled = ImRaii.Disabled(locked); + if (!locked && open) + UiHelpers.OpenCombo($"##{combo.Label}"); + if (combo.Draw(offhand.CurrentItem.Name, offhand.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, + _requiredComboWidth)) + offhand.ItemSetter(combo.CurrentSelection); + + if (locked) + return; + + var defaultOffhand = _items.GetDefaultOffhand(mainhand.CurrentItem); + if (defaultOffhand.Id == offhand.CurrentItem.Id) + return; + + ImGuiUtil.HoverTooltip("Right-click to set to Default."); + if (clear || ImGui.IsItemClicked(ImGuiMouseButton.Right)) + offhand.ItemSetter(defaultOffhand); + } + + private static void DrawApply(in EquipDrawData data) + { + if (UiHelpers.DrawCheckbox($"##apply{data.Slot}", "Apply this item when applying the Design.", data.CurrentApply, out var enabled, + data.Locked)) + data.ApplySetter(enabled); + } + + private static void DrawApplyStain(in EquipDrawData data) + { + if (UiHelpers.DrawCheckbox($"##applyStain{data.Slot}", "Apply this item when applying the Design.", data.CurrentApplyStain, + out var enabled, + data.Locked)) + data.ApplyStainSetter(enabled); + } + + #endregion + private static void WeaponHelpMarker(string label, string? type = null) { ImGui.SameLine(); @@ -502,261 +552,11 @@ public class EquipmentDrawer + "thus it is only allowed to change weapons to other weapons of the same type."); ImGui.SameLine(); ImGui.TextUnformatted(label); - if (type != null) - { - var pos = ImGui.GetItemRectMin(); - pos.Y += ImGui.GetFrameHeightWithSpacing(); - ImGui.GetWindowDrawList().AddText(pos, ImGui.GetColorU32(ImGuiCol.Text), $"({type})"); - } - } + if (type == null) + return; - private DataChange DrawWeaponsSmall(EquipItem cMainhand, out EquipItem rMainhand, EquipItem cOffhand, out EquipItem rOffhand, - StainId cMainhandStain, out StainId rMainhandStain, StainId cOffhandStain, out StainId rOffhandStain, EquipFlag? cApply, - out bool rApplyMainhand, out bool rApplyMainhandStain, out bool rApplyOffhand, out bool rApplyOffhandStain, bool locked, - bool allWeapons) - { - var changes = DataChange.None; - if (DrawStain(EquipSlot.MainHand, cMainhandStain, out rMainhandStain, locked, true)) - changes |= DataChange.Stain; - ImGui.SameLine(); - - rOffhand = cOffhand; - if (DrawMainhand(cMainhand, allWeapons, out rMainhand, out var mainhandLabel, locked, true, false)) - { - changes |= DataChange.Item; - if (rMainhand.Type.ValidOffhand() != cMainhand.Type.ValidOffhand()) - { - rOffhand = _items.GetDefaultOffhand(rMainhand); - changes |= DataChange.Item2; - } - } - - if (cApply.HasValue) - { - ImGui.SameLine(); - if (DrawApply(EquipSlot.MainHand, cApply.Value, out rApplyMainhand, locked)) - changes |= DataChange.ApplyItem; - ImGui.SameLine(); - if (DrawApplyStain(EquipSlot.MainHand, cApply.Value, out rApplyMainhandStain, locked)) - changes |= DataChange.ApplyStain; - } - else - { - rApplyMainhand = true; - rApplyMainhandStain = true; - } - - if (allWeapons) - mainhandLabel += $" ({cMainhand.Type.ToName()})"; - WeaponHelpMarker(mainhandLabel); - - if (rOffhand.Type is FullEquipType.Unknown) - { - rOffhandStain = cOffhandStain; - rApplyOffhand = false; - rApplyOffhandStain = false; - return changes; - } - - if (DrawStain(EquipSlot.OffHand, cOffhandStain, out rOffhandStain, locked, true)) - changes |= DataChange.Stain2; - - ImGui.SameLine(); - if (DrawOffhand(rMainhand, rOffhand, out rOffhand, out var offhandLabel, locked, true, false, false)) - changes |= DataChange.Item2; - if (cApply.HasValue) - { - ImGui.SameLine(); - if (DrawApply(EquipSlot.OffHand, cApply.Value, out rApplyOffhand, locked)) - changes |= DataChange.ApplyItem2; - ImGui.SameLine(); - if (DrawApplyStain(EquipSlot.OffHand, cApply.Value, out rApplyOffhandStain, locked)) - changes |= DataChange.ApplyStain2; - } - else - { - rApplyOffhand = true; - rApplyOffhandStain = true; - } - - WeaponHelpMarker(offhandLabel); - - return changes; - } - - private DataChange DrawWeaponsNormal(EquipItem cMainhand, out EquipItem rMainhand, EquipItem cOffhand, out EquipItem rOffhand, - StainId cMainhandStain, out StainId rMainhandStain, StainId cOffhandStain, out StainId rOffhandStain, EquipFlag? cApply, - out bool rApplyMainhand, out bool rApplyMainhandStain, out bool rApplyOffhand, out bool rApplyOffhandStain, bool locked, - bool allWeapons) - { - var changes = DataChange.None; - - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, - ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y }); - - cMainhand.DrawIcon(_textures, _iconSize, EquipSlot.MainHand); - var left = ImGui.IsItemClicked(ImGuiMouseButton.Left); - ImGui.SameLine(); - using (var group = ImRaii.Group()) - { - rOffhand = cOffhand; - if (DrawMainhand(cMainhand, allWeapons, out rMainhand, out var mainhandLabel, locked, false, left)) - { - changes |= DataChange.Item; - if (rMainhand.Type.ValidOffhand() != cMainhand.Type.ValidOffhand()) - { - rOffhand = _items.GetDefaultOffhand(rMainhand); - changes |= DataChange.Item2; - } - } - - if (cApply.HasValue) - { - ImGui.SameLine(); - if (DrawApply(EquipSlot.MainHand, cApply.Value, out rApplyMainhand, locked)) - changes |= DataChange.ApplyItem; - } - else - { - rApplyMainhand = true; - } - - WeaponHelpMarker(mainhandLabel, allWeapons ? cMainhand.Type.ToName() : null); - - if (DrawStain(EquipSlot.MainHand, cMainhandStain, out rMainhandStain, locked, false)) - changes |= DataChange.Stain; - if (cApply.HasValue) - { - ImGui.SameLine(); - if (DrawApplyStain(EquipSlot.MainHand, cApply.Value, out rApplyMainhandStain, locked)) - changes |= DataChange.ApplyStain; - } - else - { - rApplyMainhandStain = true; - } - } - - if (rOffhand.Type is FullEquipType.Unknown) - { - rOffhandStain = cOffhandStain; - rApplyOffhand = false; - rApplyOffhandStain = false; - return changes; - } - - rOffhand.DrawIcon(_textures, _iconSize, EquipSlot.OffHand); - var right = ImGui.IsItemClicked(ImGuiMouseButton.Right); - left = ImGui.IsItemClicked(ImGuiMouseButton.Left); - ImGui.SameLine(); - using (var group = ImRaii.Group()) - { - if (DrawOffhand(rMainhand, rOffhand, out rOffhand, out var offhandLabel, locked, false, right, left)) - changes |= DataChange.Item2; - if (cApply.HasValue) - { - ImGui.SameLine(); - if (DrawApply(EquipSlot.OffHand, cApply.Value, out rApplyOffhand, locked)) - changes |= DataChange.ApplyItem2; - } - else - { - rApplyOffhand = true; - } - - WeaponHelpMarker(offhandLabel); - - if (DrawStain(EquipSlot.OffHand, cOffhandStain, out rOffhandStain, locked, false)) - changes |= DataChange.Stain2; - if (cApply.HasValue) - { - ImGui.SameLine(); - if (DrawApplyStain(EquipSlot.OffHand, cApply.Value, out rApplyOffhandStain, locked)) - changes |= DataChange.ApplyStain2; - } - else - { - rApplyOffhandStain = true; - } - } - - return changes; - } - - private DataChange DrawWeaponsArtisan(EquipItem cMainhand, out EquipItem rMainhand, EquipItem cOffhand, out EquipItem rOffhand, - StainId cMainhandStain, out StainId rMainhandStain, StainId cOffhandStain, out StainId rOffhandStain, EquipFlag? cApply, - out bool rApplyMainhand, out bool rApplyMainhandStain, out bool rApplyOffhand, out bool rApplyOffhandStain) - { - rApplyMainhand = (cApply ?? 0).HasFlag(EquipFlag.Mainhand); - rApplyMainhandStain = (cApply ?? 0).HasFlag(EquipFlag.MainhandStain); - rApplyOffhand = (cApply ?? 0).HasFlag(EquipFlag.Offhand); - rApplyOffhandStain = (cApply ?? 0).HasFlag(EquipFlag.MainhandStain); - - bool DrawWeapon(EquipItem current, out EquipItem ret) - { - int setId = current.ModelId.Id; - int type = current.WeaponType.Id; - int variant = current.Variant.Id; - ret = current; - var changed = false; - - ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); - if (ImGui.InputInt("##setId", ref setId, 0, 0)) - { - var newSetId = (SetId)Math.Clamp(setId, 0, ushort.MaxValue); - if (newSetId.Id != current.ModelId.Id) - { - ret = _items.Identify(EquipSlot.MainHand, newSetId, current.WeaponType, current.Variant); - changed = true; - } - } - - ImGui.SameLine(); - ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); - if (ImGui.InputInt("##type", ref type, 0, 0)) - { - var newType = (WeaponType)Math.Clamp(type, 0, ushort.MaxValue); - if (newType.Id != current.WeaponType.Id) - { - ret = _items.Identify(EquipSlot.MainHand, current.ModelId, newType, current.Variant); - changed = true; - } - } - - ImGui.SameLine(); - ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); - if (ImGui.InputInt("##variant", ref variant, 0, 0)) - { - var newVariant = (Variant)Math.Clamp(variant, 0, byte.MaxValue); - if (newVariant.Id != current.Variant.Id) - { - ret = _items.Identify(EquipSlot.MainHand, current.ModelId, current.WeaponType, newVariant); - changed = true; - } - } - - return changed; - } - - var ret = DataChange.None; - using (var id = ImRaii.PushId(0)) - { - if (DrawStainArtisan(EquipSlot.MainHand, cMainhandStain, out rMainhandStain)) - ret |= DataChange.Stain; - ImGui.SameLine(); - if (DrawWeapon(cMainhand, out rMainhand)) - ret |= DataChange.Item; - } - - using (var id = ImRaii.PushId(1)) - { - if (DrawStainArtisan(EquipSlot.OffHand, cOffhandStain, out rOffhandStain)) - ret |= DataChange.Stain; - ImGui.SameLine(); - if (DrawWeapon(cOffhand, out rOffhand)) - ret |= DataChange.Item; - } - - return ret; + var pos = ImGui.GetItemRectMin(); + pos.Y += ImGui.GetFrameHeightWithSpacing(); + ImGui.GetWindowDrawList().AddText(pos, ImGui.GetColorU32(ImGuiCol.Text), $"({type})"); } } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index d049eec..5f54965 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -24,21 +24,11 @@ using Penumbra.GameData.Enums; namespace Glamourer.Gui.Tabs.ActorTab; -public class ActorPanel +public class ActorPanel(ActorSelector _selector, StateManager _stateManager, CustomizationDrawer _customizationDrawer, + EquipmentDrawer _equipmentDrawer, IdentifierService _identification, AutoDesignApplier _autoDesignApplier, + Configuration _config, DesignConverter _converter, ObjectManager _objects, DesignManager _designManager, ImportService _importService, + ICondition _conditions) { - private readonly ActorSelector _selector; - private readonly StateManager _stateManager; - private readonly CustomizationDrawer _customizationDrawer; - private readonly EquipmentDrawer _equipmentDrawer; - private readonly IdentifierService _identification; - private readonly AutoDesignApplier _autoDesignApplier; - private readonly Configuration _config; - private readonly DesignConverter _converter; - private readonly ObjectManager _objects; - private readonly DesignManager _designManager; - private readonly ImportService _importService; - private readonly ICondition _conditions; - private ActorIdentifier _identifier; private string _actorName = string.Empty; private Actor _actor = Actor.Null; @@ -46,29 +36,10 @@ public class ActorPanel private ActorState? _state; private bool _lockedRedraw; - public ActorPanel(ActorSelector selector, StateManager stateManager, CustomizationDrawer customizationDrawer, - EquipmentDrawer equipmentDrawer, IdentifierService identification, AutoDesignApplier autoDesignApplier, - Configuration config, DesignConverter converter, ObjectManager objects, DesignManager designManager, ImportService importService, - ICondition conditions) - { - _selector = selector; - _stateManager = stateManager; - _customizationDrawer = customizationDrawer; - _equipmentDrawer = equipmentDrawer; - _identification = identification; - _autoDesignApplier = autoDesignApplier; - _config = config; - _converter = converter; - _objects = objects; - _designManager = designManager; - _importService = importService; - _conditions = conditions; - } - private CustomizeFlag CustomizeApplicationFlags => _lockedRedraw ? CustomizeFlagExtensions.AllRelevant & ~CustomizeFlagExtensions.RedrawRequired : CustomizeFlagExtensions.AllRelevant; - public unsafe void Draw() + public void Draw() { using var group = ImRaii.Group(); (_identifier, _data) = _selector.Selection; @@ -161,8 +132,7 @@ public class ActorPanel if (_customizationDrawer.Draw(_state!.ModelData.Customize, _state.IsLocked, _lockedRedraw)) _stateManager.ChangeCustomize(_state, _customizationDrawer.Customize, _customizationDrawer.Changed, StateChanged.Source.Manual); - if (_customizationDrawer.DrawWetnessState(_state!.ModelData.IsWet(), out var newWetness, _state.IsLocked)) - _stateManager.ChangeWetness(_state, newWetness, StateChanged.Source.Manual); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.Wetness, _stateManager, _state)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); } @@ -176,62 +146,22 @@ public class ActorPanel var usedAllStain = _equipmentDrawer.DrawAllStain(out var newAllStain, _state!.IsLocked); foreach (var slot in EquipSlotExtensions.EqdpSlots) { - var changes = _equipmentDrawer.DrawEquip(slot, _state!.ModelData, out var newArmor, out var newStain, null, out _, out _, - _state.IsLocked); + var data = EquipDrawData.FromState(_stateManager, _state!, slot); + _equipmentDrawer.DrawEquip(data); if (usedAllStain) - { - changes |= DataChange.Stain; - newStain = newAllStain; - } - - switch (changes) - { - case DataChange.Item: - _stateManager.ChangeItem(_state, slot, newArmor, StateChanged.Source.Manual); - break; - case DataChange.Stain: - _stateManager.ChangeStain(_state, slot, newStain, StateChanged.Source.Manual); - break; - case DataChange.Item | DataChange.Stain: - _stateManager.ChangeEquip(_state, slot, newArmor, newStain, StateChanged.Source.Manual); - break; - } + _stateManager.ChangeStain(_state, slot, newAllStain, StateChanged.Source.Manual); } - var weaponChanges = _equipmentDrawer.DrawWeapons(_state!.ModelData, out var newMainhand, out var newOffhand, out var newMainhandStain, - out var newOffhandStain, null, GameMain.IsInGPose(), out _, out _, out _, out _, _state.IsLocked); - if (usedAllStain) - { - weaponChanges |= DataChange.Stain | DataChange.Stain2; - newMainhandStain = newAllStain; - newOffhandStain = newAllStain; - } - - if (weaponChanges.HasFlag(DataChange.Item)) - if (weaponChanges.HasFlag(DataChange.Stain)) - _stateManager.ChangeEquip(_state, EquipSlot.MainHand, newMainhand, newMainhandStain, StateChanged.Source.Manual); - else - _stateManager.ChangeItem(_state, EquipSlot.MainHand, newMainhand, StateChanged.Source.Manual); - else if (weaponChanges.HasFlag(DataChange.Stain)) - _stateManager.ChangeStain(_state, EquipSlot.MainHand, newMainhandStain, StateChanged.Source.Manual); - - if (weaponChanges.HasFlag(DataChange.Item2)) - if (weaponChanges.HasFlag(DataChange.Stain2)) - _stateManager.ChangeEquip(_state, EquipSlot.OffHand, newOffhand, newOffhandStain, StateChanged.Source.Manual); - else - _stateManager.ChangeItem(_state, EquipSlot.OffHand, newOffhand, StateChanged.Source.Manual); - else if (weaponChanges.HasFlag(DataChange.Stain2)) - _stateManager.ChangeStain(_state, EquipSlot.OffHand, newOffhandStain, StateChanged.Source.Manual); + var mainhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.MainHand); + var offhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.OffHand); + _equipmentDrawer.DrawWeapons(mainhand, offhand, GameMain.IsInGPose()); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); - if (EquipmentDrawer.DrawHatState(_state!.ModelData.IsHatVisible(), out var newHatState, _state!.IsLocked)) - _stateManager.ChangeHatState(_state, newHatState, StateChanged.Source.Manual); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.HatState, _stateManager, _state)); ImGui.SameLine(); - if (EquipmentDrawer.DrawVisorState(_state!.ModelData.IsVisorToggled(), out var newVisorState, _state!.IsLocked)) - _stateManager.ChangeVisorState(_state, newVisorState, StateChanged.Source.Manual); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.VisorState, _stateManager, _state)); ImGui.SameLine(); - if (EquipmentDrawer.DrawWeaponState(_state!.ModelData.IsWeaponVisible(), out var newWeaponState, _state!.IsLocked)) - _stateManager.ChangeWeaponState(_state, newWeaponState, StateChanged.Source.Manual); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.WeaponState, _stateManager, _state)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index bd6e197..c4cf023 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; -using System.Xml.Linq; using Dalamud.Interface; using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.Internal.Notifications; @@ -95,45 +94,15 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer var usedAllStain = _equipmentDrawer.DrawAllStain(out var newAllStain, _selector.Selected!.WriteProtected()); foreach (var slot in EquipSlotExtensions.EqdpSlots) { - var changes = _equipmentDrawer.DrawEquip(slot, _selector.Selected!.DesignData, out var newArmor, out var newStain, - _selector.Selected.ApplyEquip, out var newApply, out var newApplyStain, _selector.Selected!.WriteProtected()); - if (changes.HasFlag(DataChange.Item)) - _manager.ChangeEquip(_selector.Selected, slot, newArmor); - if (changes.HasFlag(DataChange.Stain)) - _manager.ChangeStain(_selector.Selected, slot, newStain); - else if (usedAllStain) + var data = EquipDrawData.FromDesign(_manager, _selector.Selected!, slot); + _equipmentDrawer.DrawEquip(data); + if (usedAllStain) _manager.ChangeStain(_selector.Selected, slot, newAllStain); - if (changes.HasFlag(DataChange.ApplyItem)) - _manager.ChangeApplyEquip(_selector.Selected, slot, newApply); - if (changes.HasFlag(DataChange.ApplyStain)) - _manager.ChangeApplyStain(_selector.Selected, slot, newApplyStain); } - var weaponChanges = _equipmentDrawer.DrawWeapons(_selector.Selected!.DesignData, out var newMainhand, out var newOffhand, - out var newMainhandStain, out var newOffhandStain, _selector.Selected.ApplyEquip, true, out var applyMain, out var applyMainStain, - out var applyOff, out var applyOffStain, _selector.Selected!.WriteProtected()); - - if (weaponChanges.HasFlag(DataChange.Item)) - _manager.ChangeWeapon(_selector.Selected, EquipSlot.MainHand, newMainhand); - if (weaponChanges.HasFlag(DataChange.Stain)) - _manager.ChangeStain(_selector.Selected, EquipSlot.MainHand, newMainhandStain); - else if (usedAllStain) - _manager.ChangeStain(_selector.Selected, EquipSlot.MainHand, newAllStain); - if (weaponChanges.HasFlag(DataChange.ApplyItem)) - _manager.ChangeApplyEquip(_selector.Selected, EquipSlot.MainHand, applyMain); - if (weaponChanges.HasFlag(DataChange.ApplyStain)) - _manager.ChangeApplyStain(_selector.Selected, EquipSlot.MainHand, applyMainStain); - if (weaponChanges.HasFlag(DataChange.Item2)) - _manager.ChangeWeapon(_selector.Selected, EquipSlot.OffHand, newOffhand); - if (weaponChanges.HasFlag(DataChange.Stain2)) - _manager.ChangeStain(_selector.Selected, EquipSlot.OffHand, newOffhandStain); - else if (usedAllStain) - _manager.ChangeStain(_selector.Selected, EquipSlot.OffHand, newAllStain); - if (weaponChanges.HasFlag(DataChange.ApplyItem2)) - _manager.ChangeApplyEquip(_selector.Selected, EquipSlot.OffHand, applyOff); - if (weaponChanges.HasFlag(DataChange.ApplyStain2)) - _manager.ChangeApplyStain(_selector.Selected, EquipSlot.OffHand, applyOffStain); - + var mainhand = EquipDrawData.FromDesign(_manager, _selector.Selected!, EquipSlot.MainHand); + var offhand = EquipDrawData.FromDesign(_manager, _selector.Selected!, EquipSlot.OffHand); + _equipmentDrawer.DrawWeapons(mainhand, offhand, true); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); DrawEquipmentMetaToggles(); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); @@ -141,22 +110,11 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer private void DrawEquipmentMetaToggles() { - var hatChanges = EquipmentDrawer.DrawHatState(_selector.Selected!.DesignData.IsHatVisible(), - _selector.Selected.DoApplyHatVisible(), - out var newHatState, out var newHatApply, _selector.Selected.WriteProtected()); - ApplyChanges(ActorState.MetaIndex.HatState, hatChanges, newHatState, newHatApply); - + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.HatState, _manager, _selector.Selected!)); ImGui.SameLine(); - var visorChanges = EquipmentDrawer.DrawVisorState(_selector.Selected!.DesignData.IsVisorToggled(), - _selector.Selected.DoApplyVisorToggle(), - out var newVisorState, out var newVisorApply, _selector.Selected.WriteProtected()); - ApplyChanges(ActorState.MetaIndex.VisorState, visorChanges, newVisorState, newVisorApply); - + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.VisorState, _manager, _selector.Selected!)); ImGui.SameLine(); - var weaponChanges = EquipmentDrawer.DrawWeaponState(_selector.Selected!.DesignData.IsWeaponVisible(), - _selector.Selected.DoApplyWeaponVisible(), - out var newWeaponState, out var newWeaponApply, _selector.Selected.WriteProtected()); - ApplyChanges(ActorState.MetaIndex.WeaponState, weaponChanges, newWeaponState, newWeaponApply); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.WeaponState, _manager, _selector.Selected!)); } private void DrawCustomize() @@ -178,9 +136,7 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer _manager.ChangeCustomize(_selector.Selected, idx, _customizationDrawer.Customize[idx]); } - var wetnessChanges = _customizationDrawer.DrawWetnessState(_selector.Selected!.DesignData.IsWet(), - _selector.Selected!.DoApplyWetness(), out var newWetnessState, out var newWetnessApply, _selector.Selected!.WriteProtected()); - ApplyChanges(ActorState.MetaIndex.Wetness, wetnessChanges, newWetnessState, newWetnessApply); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.Wetness, _manager, _selector.Selected!)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); } @@ -306,12 +262,14 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer _manager.ChangeCustomize(_selector.Selected!, CustomizeIndex.Gender, dat.Customize[CustomizeIndex.Gender]); foreach (var idx in CustomizationExtensions.AllBasic) _manager.ChangeCustomize(_selector.Selected!, idx, dat.Customize[idx]); - Glamourer.Messager.NotificationMessage($"Applied games .dat file {dat.Description} customizations to {_selector.Selected.Name}.", NotificationType.Success, false); + Glamourer.Messager.NotificationMessage( + $"Applied games .dat file {dat.Description} customizations to {_selector.Selected.Name}.", NotificationType.Success, false); } else if (_importService.CreateCharaTarget(out var designBase, out var name)) { _manager.ApplyDesign(_selector.Selected!, designBase); - Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_selector.Selected.Name}.", NotificationType.Success, false); + Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_selector.Selected.Name}.", + NotificationType.Success, false); } } @@ -441,23 +399,6 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer _fileDialog.Draw(); } - private void ApplyChanges(ActorState.MetaIndex index, DataChange change, bool value, bool apply) - { - switch (change) - { - case DataChange.Item: - _manager.ChangeMeta(_selector.Selected!, index, value); - break; - case DataChange.ApplyItem: - _manager.ChangeApplyMeta(_selector.Selected!, index, apply); - break; - case DataChange.Item | DataChange.ApplyItem: - _manager.ChangeApplyMeta(_selector.Selected!, index, apply); - _manager.ChangeMeta(_selector.Selected!, index, value); - break; - } - } - private static unsafe string GetUserPath() => Framework.Instance()->UserPath; } diff --git a/Glamourer/Gui/ToggleDrawData.cs b/Glamourer/Gui/ToggleDrawData.cs new file mode 100644 index 0000000..f78cf6f --- /dev/null +++ b/Glamourer/Gui/ToggleDrawData.cs @@ -0,0 +1,79 @@ +using System; +using Glamourer.Designs; +using Glamourer.Events; +using Glamourer.State; + +namespace Glamourer.Gui; + +public ref struct ToggleDrawData +{ + public bool Locked; + public bool DisplayApplication; + + public bool CurrentValue; + public bool CurrentApply; + + public Action SetValue = null!; + public Action SetApply = null!; + + public string Label = string.Empty; + public string Tooltip = string.Empty; + + public ToggleDrawData() + { } + + public static ToggleDrawData FromDesign(ActorState.MetaIndex index, DesignManager manager, Design design) + { + var (label, value, apply, setValue, setApply) = index switch + { + ActorState.MetaIndex.HatState => ("Hat Visible", design.DesignData.IsHatVisible(), design.DoApplyHatVisible(), + (Action)(b => manager.ChangeMeta(design, index, b)), (Action)(b => manager.ChangeApplyMeta(design, index, b))), + ActorState.MetaIndex.VisorState => ("Visor Toggled", design.DesignData.IsVisorToggled(), design.DoApplyVisorToggle(), + b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)), + ActorState.MetaIndex.WeaponState => ("Weapon Visible", design.DesignData.IsWeaponVisible(), design.DoApplyWeaponVisible(), + b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)), + ActorState.MetaIndex.Wetness => ("Force Wetness", design.DesignData.IsWet(), design.DoApplyWetness(), + b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)), + _ => throw new Exception("Unsupported meta index."), + }; + + return new ToggleDrawData + { + Label = label, + Tooltip = string.Empty, + Locked = design.WriteProtected(), + DisplayApplication = true, + CurrentValue = value, + CurrentApply = apply, + SetValue = setValue, + SetApply = setApply, + }; + } + + public static ToggleDrawData FromState(ActorState.MetaIndex index, StateManager manager, ActorState state) + { + var (label, tooltip, value, setValue) = index switch + { + ActorState.MetaIndex.HatState => ("Hat Visible", "Hide or show the characters head gear.", state.ModelData.IsHatVisible(), + (Action)(b => manager.ChangeHatState(state, b, StateChanged.Source.Manual))), + ActorState.MetaIndex.VisorState => ("Visor Toggled", "Toggle the visor state of the characters head gear.", + state.ModelData.IsVisorToggled(), + b => manager.ChangeVisorState(state, b, StateChanged.Source.Manual)), + ActorState.MetaIndex.WeaponState => ("Weapon Visible", "Hide or show the characters weapons when not drawn.", + state.ModelData.IsWeaponVisible(), + b => manager.ChangeWeaponState(state, b, StateChanged.Source.Manual)), + ActorState.MetaIndex.Wetness => ("Force Wetness", "Force the character to be wet or not.", state.ModelData.IsWet(), + b => manager.ChangeWetness(state, b, StateChanged.Source.Manual)), + _ => throw new Exception("Unsupported meta index."), + }; + + return new ToggleDrawData + { + Label = label, + Tooltip = tooltip, + Locked = state.IsLocked, + CurrentValue = value, + SetValue = setValue, + }; + } +} diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index 7dcfc7d..22ec758 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -71,14 +71,15 @@ public static class UiHelpers return ret; } - public static DataChange DrawMetaToggle(string label, bool currentValue, bool currentApply, out bool newValue, + public static (bool, bool) DrawMetaToggle(string label, bool currentValue, bool currentApply, out bool newValue, out bool newApply, bool locked) { var flags = (sbyte)(currentApply ? currentValue ? 1 : -1 : 0); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing); using (var disabled = ImRaii.Disabled(locked)) { - if (new TristateCheckbox(ColorId.TriStateCross.Value(), ColorId.TriStateCheck.Value(), ColorId.TriStateNeutral.Value()).Draw("##" + label, flags, out flags)) + if (new TristateCheckbox(ColorId.TriStateCross.Value(), ColorId.TriStateCheck.Value(), ColorId.TriStateNeutral.Value()).Draw( + "##" + label, flags, out flags)) { (newValue, newApply) = flags switch { @@ -99,13 +100,7 @@ public static class UiHelpers ImGui.SameLine(); ImGui.TextUnformatted(label); - return (currentApply != newApply, currentValue != newValue) switch - { - (true, true) => DataChange.ApplyItem | DataChange.Item, - (true, false) => DataChange.ApplyItem, - (false, true) => DataChange.Item, - _ => DataChange.None, - }; + return (currentValue != newValue, currentApply != newApply); } public static (EquipFlag, CustomizeFlag) ConvertKeysToFlags() diff --git a/OtterGui b/OtterGui index 858b610..2a79084 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 858b610a194ee52b4e8e89c0c64d9f2653025524 +Subproject commit 2a7908493ab0fd0e576d5fa37a3acbd920be6233 From cdefe64e4e615c179777d6c7bbf0548cfef88320 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 1 Dec 2023 14:38:51 +0100 Subject: [PATCH 10/20] Allow import of .cma files. --- Glamourer/Interop/CharaFile/CmaFile.cs | 111 +++++++++++++++++++++++++ Glamourer/Interop/ImportService.cs | 40 ++++++++- 2 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 Glamourer/Interop/CharaFile/CmaFile.cs diff --git a/Glamourer/Interop/CharaFile/CmaFile.cs b/Glamourer/Interop/CharaFile/CmaFile.cs new file mode 100644 index 0000000..15b8af1 --- /dev/null +++ b/Glamourer/Interop/CharaFile/CmaFile.cs @@ -0,0 +1,111 @@ +using System; +using Glamourer.Designs; +using Glamourer.Services; +using Newtonsoft.Json.Linq; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Interop.CharaFile; + +public sealed class CmaFile +{ + public string Name = string.Empty; + public DesignData Data = new(); + + public static CmaFile? ParseData(ItemManager items, string data, string? name = null) + { + try + { + var jObj = JObject.Parse(data); + var ret = new CmaFile(); + ret.Data.SetDefaultEquipment(items); + ParseMainHand(items, jObj, ref ret.Data); + ParseOffHand(items, jObj, ref ret.Data); + ret.Name = jObj["Description"]?.ToObject() ?? name ?? "New Design"; + ParseEquipment(items, jObj, ref ret.Data); + ParseCustomization(jObj, ref ret.Data); + return ret; + } + catch + { + return null; + } + } + + private static unsafe void ParseCustomization(JObject jObj, ref DesignData data) + { + var bytes = jObj["CharacterBytes"]?.ToObject() ?? string.Empty; + if (bytes.Length is not 26 * 3 - 1) + return; + + bytes = bytes.Replace(" ", string.Empty); + var byteData = Convert.FromHexString(bytes); + fixed (byte* ptr = byteData) + { + data.Customize.Data.Read(ptr); + } + } + + private static unsafe void ParseEquipment(ItemManager items, JObject jObj, ref DesignData data) + { + var bytes = jObj["EquipmentBytes"]?.ToObject() ?? string.Empty; + bytes = bytes.Replace(" ", string.Empty); + var byteData = Convert.FromHexString(bytes); + fixed (byte* ptr = byteData) + { + foreach (var slot in EquipSlotExtensions.EqdpSlots) + { + var idx = slot.ToIndex(); + if (idx * 4 + 3 >= byteData.Length) + continue; + var armor = ((CharacterArmor*)ptr)[idx]; + var item = items.Identify(slot, armor.Set, armor.Variant); + data.SetItem(slot, item); + data.SetStain(slot, armor.Stain); + } + + data.Customize.Data.Read(ptr); + } + } + + private static void ParseMainHand(ItemManager items, JObject jObj, ref DesignData data) + { + var mainhand = jObj["MainHand"]; + if (mainhand == null) + { + data.SetItem(EquipSlot.MainHand, items.DefaultSword); + data.SetStain(EquipSlot.MainHand, 0); + return; + } + + var set = mainhand["Item1"]?.ToObject() ?? items.DefaultSword.ModelId; + var type = mainhand["Item2"]?.ToObject() ?? items.DefaultSword.WeaponType; + var variant = mainhand["Item3"]?.ToObject() ?? items.DefaultSword.Variant; + var stain = mainhand["Item4"]?.ToObject() ?? 0; + var item = items.Identify(EquipSlot.MainHand, set, type, variant); + + data.SetItem(EquipSlot.MainHand, item.Valid ? item : items.DefaultSword); + data.SetStain(EquipSlot.MainHand, stain); + } + + private static void ParseOffHand(ItemManager items, JObject jObj, ref DesignData data) + { + var offhand = jObj["OffHand"]; + var defaultOffhand = items.GetDefaultOffhand(data.Item(EquipSlot.MainHand)); + if (offhand == null) + { + data.SetItem(EquipSlot.MainHand, defaultOffhand); + data.SetStain(EquipSlot.MainHand, defaultOffhand.ModelId.Id == 0 ? 0 : data.Stain(EquipSlot.MainHand)); + return; + } + + var set = offhand["Item1"]?.ToObject() ?? items.DefaultSword.ModelId; + var type = offhand["Item2"]?.ToObject() ?? items.DefaultSword.WeaponType; + var variant = offhand["Item3"]?.ToObject() ?? items.DefaultSword.Variant; + var stain = offhand["Item4"]?.ToObject() ?? 0; + var item = items.Identify(EquipSlot.OffHand, set, type, variant, data.MainhandType); + + data.SetItem(EquipSlot.OffHand, item.Valid ? item : defaultOffhand); + data.SetStain(EquipSlot.OffHand, defaultOffhand.ModelId.Id == 0 ? 0 : (StainId)stain); + } +} diff --git a/Glamourer/Interop/ImportService.cs b/Glamourer/Interop/ImportService.cs index 2681abb..217b5fd 100644 --- a/Glamourer/Interop/ImportService.cs +++ b/Glamourer/Interop/ImportService.cs @@ -6,7 +6,9 @@ using Dalamud.Interface.DragDrop; using Dalamud.Interface.Internal.Notifications; using Glamourer.Customization; using Glamourer.Designs; +using Glamourer.Interop.CharaFile; using Glamourer.Services; +using Glamourer.Structs; using ImGuiNET; using OtterGui.Classes; @@ -22,9 +24,9 @@ public class ImportService(CustomizationService _customizations, IDragDropManage }); public void CreateCharaSource() - => _dragDropManager.CreateImGuiSource("CharaDragger", m => m.Files.Count == 1 && m.Extensions.Contains(".chara"), m => + => _dragDropManager.CreateImGuiSource("CharaDragger", m => m.Files.Count == 1 && m.Extensions.Contains(".chara") || m.Extensions.Contains(".cma"), m => { - ImGui.TextUnformatted($"Dragging {Path.GetFileName(m.Files[0])} to import Anamnesis data for Glamourer..."); + ImGui.TextUnformatted($"Dragging {Path.GetFileName(m.Files[0])} to import Anamnesis/CMTool data for Glamourer..."); return true; }); @@ -47,8 +49,8 @@ public class ImportService(CustomizationService _customizations, IDragDropManage name = string.Empty; return false; } - - return LoadChara(files[0], out design, out name); + + return Path.GetExtension(files[0]) is ".chara" ? LoadChara(files[0], out design, out name) : LoadCma(files[0], out design, out name); } public bool LoadChara(string path, [NotNullWhen(true)] out DesignBase? design, out string name) @@ -81,6 +83,36 @@ public class ImportService(CustomizationService _customizations, IDragDropManage return true; } + public bool LoadCma(string path, [NotNullWhen(true)] out DesignBase? design, out string name) + { + if (!File.Exists(path)) + { + design = null; + name = string.Empty; + return false; + } + + try + { + var text = File.ReadAllText(path); + var file = CmaFile.ParseData(_items, text, Path.GetFileNameWithoutExtension(path)); + if (file == null) + throw new Exception(); + + name = file.Name; + design = new DesignBase(_customizations, file.Data, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not read .cma file {path}.", NotificationType.Error); + design = null; + name = string.Empty; + return false; + } + + return true; + } + public bool LoadDat(string path, out DatCharacterFile file) { if (!File.Exists(path)) From 87a645b2a3bf9229bfd957e1e0052f68ad4cfa8e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 1 Dec 2023 14:39:10 +0100 Subject: [PATCH 11/20] Try using mainhand item for vfx weapons in some cases. --- Glamourer/Interop/WeaponService.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index caea959..e395f8b 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -64,6 +64,9 @@ public unsafe class WeaponService : IDisposable // First call the regular function. if (equipSlot is not EquipSlot.Unknown) _event.Invoke(actor, equipSlot, ref tmpWeapon); + // Sage hack for weapons appearing in animations? + else if (weaponValue == actor.GetMainhand().Value) + _event.Invoke(actor, EquipSlot.MainHand, ref tmpWeapon); _loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4); if (tmpWeapon.Value != weapon.Value) From 2f1b85a02ab37d79fe26570517912fd3bc08fa37 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Nov 2023 14:02:09 +0100 Subject: [PATCH 12/20] Add Crest flags. --- Glamourer.GameData/Structs/CrestFlag.cs | 50 +++++++++++++++++++++++++ Glamourer/Designs/DesignBase.cs | 40 ++++++++++++++++---- Glamourer/Designs/DesignData.cs | 19 ++++++++++ 3 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 Glamourer.GameData/Structs/CrestFlag.cs diff --git a/Glamourer.GameData/Structs/CrestFlag.cs b/Glamourer.GameData/Structs/CrestFlag.cs new file mode 100644 index 0000000..17275f5 --- /dev/null +++ b/Glamourer.GameData/Structs/CrestFlag.cs @@ -0,0 +1,50 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using FFXIVClientStructs.FFXIV.Client.Game.InstanceContent; +using Penumbra.GameData.Enums; + +namespace Glamourer.Structs; + +[Flags] +public enum CrestFlag : ushort +{ + Head = 0x0001, + Body = 0x0002, + Hands = 0x0004, + Legs = 0x0008, + Feet = 0x0010, + Ears = 0x0020, + Neck = 0x0040, + Wrist = 0x0080, + RFinger = 0x0100, + LFinger = 0x0200, + Mainhand = 0x0400, + Offhand = 0x0800, +} + +public static class CrestExtensions +{ + public const CrestFlag All = (CrestFlag)(((ulong)EquipFlag.Offhand << 1) - 1); + public const CrestFlag AllRelevant = CrestFlag.Body; + + public static CrestFlag ToCrestFlag(this EquipSlot slot) + => slot switch + { + EquipSlot.MainHand => CrestFlag.Mainhand, + EquipSlot.OffHand => CrestFlag.Offhand, + EquipSlot.Head => CrestFlag.Head, + EquipSlot.Body => CrestFlag.Body, + EquipSlot.Hands => CrestFlag.Hands, + EquipSlot.Legs => CrestFlag.Legs, + EquipSlot.Feet => CrestFlag.Feet, + EquipSlot.Ears => CrestFlag.Ears, + EquipSlot.Neck => CrestFlag.Neck, + EquipSlot.Wrists => CrestFlag.Wrist, + EquipSlot.RFinger => CrestFlag.RFinger, + EquipSlot.LFinger => CrestFlag.LFinger, + _ => 0, + }; + + public static bool Valid(this CrestFlag crest) + => AllRelevant.HasFlag(crest); +} diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 183ca99..1e45052 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -39,7 +39,6 @@ public class DesignBase ApplyEquip = equipFlags & EquipFlagExtensions.All; _designFlags = 0; CustomizationSet = SetCustomizationSet(customize); - } internal DesignBase(DesignBase clone) @@ -83,6 +82,7 @@ public class DesignBase => _applyCustomize; internal EquipFlag ApplyEquip = EquipFlagExtensions.All; + internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant; private DesignFlags _designFlags = DesignFlags.ApplyHatVisible | DesignFlags.ApplyVisorState | DesignFlags.ApplyWeaponVisible; public bool SetCustomize(CustomizationService customizationService, Customize customize) @@ -169,6 +169,9 @@ public class DesignBase public bool DoApplyCustomize(CustomizeIndex idx) => ApplyCustomize.HasFlag(idx.ToFlag()); + public bool DoApplyCrest(EquipSlot slot) + => ApplyCrest.HasFlag(slot.ToFlag()); + internal bool SetApplyEquip(EquipSlot slot, bool value) { var newValue = value ? ApplyEquip | slot.ToFlag() : ApplyEquip & ~slot.ToFlag(); @@ -199,6 +202,16 @@ public class DesignBase return true; } + internal bool SetApplyCrest(EquipSlot slot, bool value) + { + var newValue = value ? ApplyCrest | slot.ToCrestFlag() : ApplyCrest & ~slot.ToCrestFlag(); + if (newValue == ApplyCrest) + return false; + + ApplyCrest = newValue; + return true; + } + internal FlagRestrictionResetter TemporarilyRestrictApplication(EquipFlag equipFlags, CustomizeFlag customizeFlags) => new(this, equipFlags, customizeFlags); @@ -246,13 +259,15 @@ public class DesignBase protected JObject SerializeEquipment() { - static JObject Serialize(CustomItemId id, StainId stain, bool apply, bool applyStain) + static JObject Serialize(CustomItemId id, StainId stain, bool crest, bool apply, bool applyStain, bool applyCrest) => new() { ["ItemId"] = id.Id, ["Stain"] = stain.Id, + ["Crest"] = crest, ["Apply"] = apply, ["ApplyStain"] = applyStain, + ["ApplyCrest"] = applyCrest, }; var ret = new JObject(); @@ -262,7 +277,8 @@ public class DesignBase { var item = _designData.Item(slot); var stain = _designData.Stain(slot); - ret[slot.ToString()] = Serialize(item.Id, stain, DoApplyEquip(slot), DoApplyStain(slot)); + var crest = _designData.Crest(slot); + ret[slot.ToString()] = Serialize(item.Id, stain, crest, DoApplyEquip(slot), DoApplyStain(slot), DoApplyCrest(slot)); } ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyHatVisible()).ToJObject("Show", "Apply"); @@ -345,13 +361,15 @@ public class DesignBase return; } - static (CustomItemId, StainId, bool, bool) ParseItem(EquipSlot slot, JToken? item) + static (CustomItemId, StainId, bool, bool, bool, bool) ParseItem(EquipSlot slot, JToken? item) { var id = item?["ItemId"]?.ToObject() ?? ItemManager.NothingId(slot).Id; var stain = (StainId)(item?["Stain"]?.ToObject() ?? 0); + var crest = (item?["Crest"]?.ToObject() ?? false); var apply = item?["Apply"]?.ToObject() ?? false; var applyStain = item?["ApplyStain"]?.ToObject() ?? false; - return (id, stain, apply, applyStain); + var applyCrest = item?["ApplyCrest"]?.ToObject() ?? false; + return (id, stain, crest, apply, applyStain, applyCrest); } void PrintWarning(string msg) @@ -362,21 +380,23 @@ public class DesignBase foreach (var slot in EquipSlotExtensions.EqdpSlots) { - var (id, stain, apply, applyStain) = ParseItem(slot, equip[slot.ToString()]); + var (id, stain, crest, apply, applyStain, applyCrest) = ParseItem(slot, equip[slot.ToString()]); PrintWarning(items.ValidateItem(slot, id, out var item, allowUnknown)); PrintWarning(items.ValidateStain(stain, out stain, allowUnknown)); design._designData.SetItem(slot, item); design._designData.SetStain(slot, stain); + design._designData.SetCrest(slot, crest); design.SetApplyEquip(slot, apply); design.SetApplyStain(slot, applyStain); + design.SetApplyCrest(slot, applyCrest); } { - var (id, stain, apply, applyStain) = ParseItem(EquipSlot.MainHand, equip[EquipSlot.MainHand.ToString()]); + var (id, stain, crest, apply, applyStain, applyCrest) = ParseItem(EquipSlot.MainHand, equip[EquipSlot.MainHand.ToString()]); if (id == ItemManager.NothingId(EquipSlot.MainHand)) id = items.DefaultSword.ItemId; - var (idOff, stainOff, applyOff, applyStainOff) = ParseItem(EquipSlot.OffHand, equip[EquipSlot.OffHand.ToString()]); + var (idOff, stainOff, crestOff, applyOff, applyStainOff, applyCrestOff) = ParseItem(EquipSlot.OffHand, equip[EquipSlot.OffHand.ToString()]); if (id == ItemManager.NothingId(EquipSlot.OffHand)) id = ItemManager.NothingId(FullEquipType.Shield); @@ -387,10 +407,14 @@ public class DesignBase design._designData.SetItem(EquipSlot.OffHand, off); design._designData.SetStain(EquipSlot.MainHand, stain); design._designData.SetStain(EquipSlot.OffHand, stainOff); + design._designData.SetCrest(EquipSlot.MainHand, crest); + design._designData.SetCrest(EquipSlot.OffHand, crestOff); design.SetApplyEquip(EquipSlot.MainHand, apply); design.SetApplyEquip(EquipSlot.OffHand, applyOff); design.SetApplyStain(EquipSlot.MainHand, applyStain); design.SetApplyStain(EquipSlot.OffHand, applyStainOff); + design.SetApplyCrest(EquipSlot.MainHand, applyCrest); + design.SetApplyCrest(EquipSlot.OffHand, applyCrestOff); } var metaValue = QuadBool.FromJObject(equip["Hat"], "Show", "Apply", QuadBool.NullFalse); design.SetApplyHatVisible(metaValue.Enabled); diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 4a24f59..fe2e655 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -2,6 +2,7 @@ using System.Runtime.CompilerServices; using Glamourer.Customization; using Glamourer.Services; +using Glamourer.Structs; using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -29,6 +30,7 @@ public unsafe struct DesignData private fixed byte _equipmentBytes[48]; public Customize Customize = Customize.Default; public uint ModelId; + public CrestFlag CrestVisibility; private WeaponType _secondaryMainhand; private WeaponType _secondaryOffhand; private FullEquipType _typeMainhand; @@ -59,6 +61,10 @@ public unsafe struct DesignData return index > 11 ? (StainId)0 : _equipmentBytes[4 * index + 3]; } + public readonly bool Crest(EquipSlot slot) + => CrestVisibility.HasFlag(slot.ToCrestFlag()); + + public FullEquipType MainhandType => _typeMainhand; @@ -173,6 +179,16 @@ public unsafe struct DesignData _ => false, }; + public bool SetCrest(EquipSlot slot, bool visible) + { + var newValue = visible ? CrestVisibility | slot.ToCrestFlag() : CrestVisibility & ~slot.ToCrestFlag(); + if (newValue == CrestVisibility) + return false; + + CrestVisibility = newValue; + return true; + } + public readonly bool IsWet() => (_states & 0x01) == 0x01; @@ -228,12 +244,15 @@ public unsafe struct DesignData { SetItem(slot, ItemManager.NothingItem(slot)); SetStain(slot, 0); + SetCrest(slot, false); } SetItem(EquipSlot.MainHand, items.DefaultSword); SetStain(EquipSlot.MainHand, 0); + SetCrest(EquipSlot.MainHand, false); SetItem(EquipSlot.OffHand, ItemManager.NothingItem(FullEquipType.Shield)); SetStain(EquipSlot.OffHand, 0); + SetCrest(EquipSlot.OffHand, false); } From 6f4a7661d75a0748d9a8d4d19a57f7cb0b755c0a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Nov 2023 14:05:29 +0100 Subject: [PATCH 13/20] Add crest changing in designs. --- Glamourer/Designs/DesignBase.cs | 2 +- Glamourer/Designs/DesignManager.cs | 34 ++++++++++++++++++++++++++++++ Glamourer/Events/DesignChanged.cs | 6 ++++++ Glamourer/Events/StateChanged.cs | 3 +++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 1e45052..135c858 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -170,7 +170,7 @@ public class DesignBase => ApplyCustomize.HasFlag(idx.ToFlag()); public bool DoApplyCrest(EquipSlot slot) - => ApplyCrest.HasFlag(slot.ToFlag()); + => ApplyCrest.HasFlag(slot.ToCrestFlag()); internal bool SetApplyEquip(EquipSlot slot, bool value) { diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 7a0f500..7159bac 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -446,6 +446,31 @@ public class DesignManager _event.Invoke(DesignChanged.Type.ApplyStain, design, slot); } + /// Change the crest visibility for any equipment piece. + public void ChangeCrest(Design design, EquipSlot slot, bool crest) + { + var oldCrest = design.DesignData.Crest(slot); + if (!design.GetDesignDataRef().SetCrest(slot, crest)) + return; + + design.LastEdit = DateTimeOffset.UtcNow; + _saveService.QueueSave(design); + Glamourer.Log.Debug($"Set crest visibility of {slot} equipment piece to {crest}."); + _event.Invoke(DesignChanged.Type.Crest, design, (oldCrest, crest, slot)); + } + + /// Change whether to apply a specific crest visibility. + public void ChangeApplyCrest(Design design, EquipSlot slot, bool value) + { + if (!design.SetApplyCrest(slot, value)) + return; + + design.LastEdit = DateTimeOffset.UtcNow; + _saveService.QueueSave(design); + Glamourer.Log.Debug($"Set applying of crest visibility of {slot} equipment piece to {value}."); + _event.Invoke(DesignChanged.Type.ApplyCrest, design, slot); + } + /// Change the bool value of one of the meta flags. public void ChangeMeta(Design design, ActorState.MetaIndex metaIndex, bool value) { @@ -514,6 +539,9 @@ public class DesignManager if (other.DoApplyStain(slot)) ChangeStain(design, slot, other.DesignData.Stain(slot)); + + if (other.DoApplyCrest(slot)) + ChangeCrest(design, slot, other.DesignData.Crest(slot)); } } @@ -528,6 +556,12 @@ public class DesignManager if (other.DoApplyStain(EquipSlot.OffHand)) ChangeStain(design, EquipSlot.OffHand, other.DesignData.Stain(EquipSlot.OffHand)); + + if (other.DoApplyCrest(EquipSlot.MainHand)) + ChangeCrest(design, EquipSlot.MainHand, other.DesignData.Crest(EquipSlot.MainHand)); + + if (other.DoApplyCrest(EquipSlot.OffHand)) + ChangeCrest(design, EquipSlot.OffHand, other.DesignData.Crest(EquipSlot.OffHand)); } public void UndoDesignChange(Design design) diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 55956f0..c528fde 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -62,6 +62,9 @@ public sealed class DesignChanged : EventWrapper An existing design had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. Stain, + /// An existing design had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. + Crest, + /// An existing design changed whether a specific customization is applied. Data is the type of customization [CustomizeIndex]. ApplyCustomize, @@ -71,6 +74,9 @@ public sealed class DesignChanged : EventWrapper An existing design changed whether a specific stain is applied. Data is the slot of the equipment [EquipSlot]. ApplyStain, + /// An existing design changed whether a specific crest visibility is applied. Data is the slot of the equipment [EquipSlot]. + ApplyCrest, + /// An existing design changed its write protection status. Data is the new value [bool]. WriteProtection, diff --git a/Glamourer/Events/StateChanged.cs b/Glamourer/Events/StateChanged.cs index 2c5c5c8..e02a6d9 100644 --- a/Glamourer/Events/StateChanged.cs +++ b/Glamourer/Events/StateChanged.cs @@ -37,6 +37,9 @@ public sealed class StateChanged : EventWrapper A characters saved state had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. Stain, + /// A characters saved state had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. + Crest, + /// A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] Design, From 512d0a1a5f9650feb296558d841c8660c8e89cd2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 25 Nov 2023 23:54:37 +0100 Subject: [PATCH 14/20] Add interop for Actor and Model. --- Glamourer.GameData/Structs/CrestFlag.cs | 52 +++++++++++++++---- .../DesignTab/DesignFileSystemSelector.cs | 1 + Glamourer/Gui/UiHelpers.cs | 15 ------ Glamourer/Interop/Structs/Actor.cs | 19 +++++++ Glamourer/Interop/Structs/Model.cs | 32 ++++++++++++ Glamourer/State/ActorState.cs | 6 ++- 6 files changed, 98 insertions(+), 27 deletions(-) diff --git a/Glamourer.GameData/Structs/CrestFlag.cs b/Glamourer.GameData/Structs/CrestFlag.cs index 17275f5..d39fd0a 100644 --- a/Glamourer.GameData/Structs/CrestFlag.cs +++ b/Glamourer.GameData/Structs/CrestFlag.cs @@ -1,6 +1,7 @@ using System; -using System.Diagnostics.CodeAnalysis; -using FFXIVClientStructs.FFXIV.Client.Game.InstanceContent; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; using Penumbra.GameData.Enums; namespace Glamourer.Structs; @@ -15,23 +16,37 @@ public enum CrestFlag : ushort Feet = 0x0010, Ears = 0x0020, Neck = 0x0040, - Wrist = 0x0080, + Wrists = 0x0080, RFinger = 0x0100, LFinger = 0x0200, - Mainhand = 0x0400, - Offhand = 0x0800, + MainHand = 0x0400, + OffHand = 0x0800, } public static class CrestExtensions { public const CrestFlag All = (CrestFlag)(((ulong)EquipFlag.Offhand << 1) - 1); - public const CrestFlag AllRelevant = CrestFlag.Body; + public const CrestFlag AllRelevant = CrestFlag.Head | CrestFlag.Body | CrestFlag.OffHand; + + public static readonly IReadOnlyList AllRelevantSet = Enum.GetValues().Where(f => f.ToRelevantIndex() >= 0).ToArray(); + + public static int ToIndex(this CrestFlag flag) + => BitOperations.TrailingZeroCount((uint)flag); + + public static int ToRelevantIndex(this CrestFlag flag) + => flag switch + { + CrestFlag.Head => 0, + CrestFlag.Body => 1, + CrestFlag.OffHand => 2, + _ => -1, + }; public static CrestFlag ToCrestFlag(this EquipSlot slot) => slot switch { - EquipSlot.MainHand => CrestFlag.Mainhand, - EquipSlot.OffHand => CrestFlag.Offhand, + EquipSlot.MainHand => CrestFlag.MainHand, + EquipSlot.OffHand => CrestFlag.OffHand, EquipSlot.Head => CrestFlag.Head, EquipSlot.Body => CrestFlag.Body, EquipSlot.Hands => CrestFlag.Hands, @@ -39,12 +54,27 @@ public static class CrestExtensions EquipSlot.Feet => CrestFlag.Feet, EquipSlot.Ears => CrestFlag.Ears, EquipSlot.Neck => CrestFlag.Neck, - EquipSlot.Wrists => CrestFlag.Wrist, + EquipSlot.Wrists => CrestFlag.Wrists, EquipSlot.RFinger => CrestFlag.RFinger, EquipSlot.LFinger => CrestFlag.LFinger, _ => 0, }; - public static bool Valid(this CrestFlag crest) - => AllRelevant.HasFlag(crest); + public static EquipSlot ToSlot(this CrestFlag flag) + => flag switch + { + CrestFlag.MainHand => EquipSlot.MainHand, + CrestFlag.OffHand => EquipSlot.OffHand, + CrestFlag.Head => EquipSlot.Head, + CrestFlag.Body => EquipSlot.Body, + CrestFlag.Hands => EquipSlot.Hands, + CrestFlag.Legs => EquipSlot.Legs, + CrestFlag.Feet => EquipSlot.Feet, + CrestFlag.Ears => EquipSlot.Ears, + CrestFlag.Neck => EquipSlot.Neck, + CrestFlag.Wrists => EquipSlot.Wrists, + CrestFlag.RFinger => EquipSlot.RFinger, + CrestFlag.LFinger => EquipSlot.LFinger, + _ => 0, + }; } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index b323b63..b288303 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -118,6 +118,7 @@ public sealed class DesignFileSystemSelector : FileSystemSelector Open a combo popup with another method than the combo itself. diff --git a/Glamourer/Interop/Structs/Actor.cs b/Glamourer/Interop/Structs/Actor.cs index 1d1d172..ba7f132 100644 --- a/Glamourer/Interop/Structs/Actor.cs +++ b/Glamourer/Interop/Structs/Actor.cs @@ -106,6 +106,9 @@ public readonly unsafe struct Actor : IEquatable public CharacterArmor GetArmor(EquipSlot slot) => ((CharacterArmor*)&AsCharacter->DrawData.Head)[slot.ToIndex()]; + public bool GetCrest(EquipSlot slot) + => (GetFreeCompanyCrestBitfield() & CrestMask(slot)) != 0; + public CharacterWeapon GetMainhand() => new(AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.MainHand).ModelId.Value); @@ -115,6 +118,22 @@ public readonly unsafe struct Actor : IEquatable public Customize GetCustomize() => *(Customize*)&AsCharacter->DrawData.CustomizeData; + // TODO remove this when available in ClientStructs + private byte GetFreeCompanyCrestBitfield() + => ((byte*)Address)[0x1BBB]; + + private static byte CrestMask(EquipSlot slot) + => slot switch + { + EquipSlot.OffHand => 0x01, + EquipSlot.Head => 0x02, + EquipSlot.Body => 0x04, + EquipSlot.Hands => 0x08, + EquipSlot.Legs => 0x10, + EquipSlot.Feet => 0x20, + _ => 0x00, + }; + public override string ToString() => $"0x{Address:X}"; } diff --git a/Glamourer/Interop/Structs/Model.cs b/Glamourer/Interop/Structs/Model.cs index 77bf24e..811ff81 100644 --- a/Glamourer/Interop/Structs/Model.cs +++ b/Glamourer/Interop/Structs/Model.cs @@ -91,6 +91,9 @@ public readonly unsafe struct Model : IEquatable public CharacterArmor GetArmor(EquipSlot slot) => ((CharacterArmor*)&AsHuman->Head)[slot.ToIndex()]; + public bool GetCrest(EquipSlot slot) + => IsFreeCompanyCrestVisibleOnSlot(slot); + public Customize GetCustomize() => *(Customize*)&AsHuman->Customize; @@ -195,6 +198,35 @@ public readonly unsafe struct Model : IEquatable return discriminator1 == 0 && discriminator2 != 0 ? (second, first) : (first, second); } + // TODO remove these when available in ClientStructs + private bool IsFreeCompanyCrestVisibleOnSlot(EquipSlot slot) + { + if (!IsCharacterBase) + return false; + + var index = (byte)slot.ToIndex(); + if (index >= 12) + return false; + + var characterBase = AsCharacterBase; + var getter = (delegate* unmanaged)((nint*)characterBase->VTable)[95]; + return getter(characterBase, index) != 0; + } + + public void SetFreeCompanyCrestVisibleOnSlot(EquipSlot slot, bool visible) + { + if (!IsCharacterBase) + return; + + var index = (byte)slot.ToIndex(); + if (index >= 12) + return; + + var characterBase = AsCharacterBase; + var setter = (delegate* unmanaged)((nint*)characterBase->VTable)[96]; + setter(characterBase, index, visible ? (byte)1 : (byte)0); + } + public override string ToString() => $"0x{Address:X}"; } diff --git a/Glamourer/State/ActorState.cs b/Glamourer/State/ActorState.cs index 2cb3f2a..f3372e8 100644 --- a/Glamourer/State/ActorState.cs +++ b/Glamourer/State/ActorState.cs @@ -80,7 +80,8 @@ public class ActorState /// This contains whether a change to the base data was made by the game, the user via manual input or through automatic application. private readonly StateChanged.Source[] _sources = Enumerable - .Repeat(StateChanged.Source.Game, EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5).ToArray(); + .Repeat(StateChanged.Source.Game, + EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + CrestExtensions.AllRelevantSet.Count).ToArray(); internal ActorState(ActorIdentifier identifier) => Identifier = identifier.CreatePermanent(); @@ -88,6 +89,9 @@ public class ActorState public ref StateChanged.Source this[EquipSlot slot, bool stain] => ref _sources[slot.ToIndex() + (stain ? EquipFlagExtensions.NumEquipFlags / 2 : 0)]; + public ref StateChanged.Source this[CrestFlag slot] + => ref _sources[EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + slot.ToRelevantIndex()]; + public ref StateChanged.Source this[CustomizeIndex type] => ref _sources[EquipFlagExtensions.NumEquipFlags + (int)type]; From cd0196ddb4b42c58cf6340425a7592ec5a54f3eb Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 26 Nov 2023 00:29:15 +0100 Subject: [PATCH 15/20] UI for crests. --- Glamourer.GameData/Structs/CrestFlag.cs | 18 ++++ Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 30 +++++-- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 94 ++++++++++++++------- Glamourer/Gui/ToggleDrawData.cs | 25 ++++++ 4 files changed, 132 insertions(+), 35 deletions(-) diff --git a/Glamourer.GameData/Structs/CrestFlag.cs b/Glamourer.GameData/Structs/CrestFlag.cs index d39fd0a..3ef9f1e 100644 --- a/Glamourer.GameData/Structs/CrestFlag.cs +++ b/Glamourer.GameData/Structs/CrestFlag.cs @@ -77,4 +77,22 @@ public static class CrestExtensions CrestFlag.LFinger => EquipSlot.LFinger, _ => 0, }; + + public static string ToLabel(this CrestFlag flag) + => flag switch + { + CrestFlag.Head => "Head", + CrestFlag.Body => "Chest", + CrestFlag.Hands => "Gauntlets", + CrestFlag.Legs => "Pants", + CrestFlag.Feet => "Boot", + CrestFlag.Ears => "Earrings", + CrestFlag.Neck => "Necklace", + CrestFlag.Wrists => "Bracelet", + CrestFlag.RFinger => "Right Ring", + CrestFlag.LFinger => "Left Ring", + CrestFlag.MainHand => "Weapon", + CrestFlag.OffHand => "Shield", + _ => string.Empty, + }; } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 5f54965..e4006ad 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -5,6 +5,7 @@ using Dalamud.Interface; using Dalamud.Interface.Internal.Notifications; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using Glamourer.Automation; using Glamourer.Customization; using Glamourer.Designs; @@ -157,14 +158,33 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus _equipmentDrawer.DrawWeapons(mainhand, offhand, GameMain.IsInGPose()); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.HatState, _stateManager, _state)); - ImGui.SameLine(); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.VisorState, _stateManager, _state)); - ImGui.SameLine(); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.WeaponState, _stateManager, _state)); + DrawEquipmentMetaToggles(); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); } + private void DrawEquipmentMetaToggles() + { + using (var _ = ImRaii.Group()) + { + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.HatState, _stateManager, _state!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(EquipSlot.Head, _stateManager, _state!)); + } + + ImGui.SameLine(); + using (var _ = ImRaii.Group()) + { + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.VisorState, _stateManager, _state!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(EquipSlot.Body, _stateManager, _state!)); + } + + ImGui.SameLine(); + using (var _ = ImRaii.Group()) + { + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.WeaponState, _stateManager, _state!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(EquipSlot.OffHand, _stateManager, _state!)); + } + } + private void DrawMonsterPanel() { var names = _identification.AwaitedService.ModelCharaNames(_state!.ModelData.ModelId); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index c4cf023..129b5c7 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -110,11 +110,25 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer private void DrawEquipmentMetaToggles() { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.HatState, _manager, _selector.Selected!)); + using (var _ = ImRaii.Group()) + { + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.HatState, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(EquipSlot.Head, _manager, _selector.Selected!)); + } + ImGui.SameLine(); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.VisorState, _manager, _selector.Selected!)); + using (var _ = ImRaii.Group()) + { + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.VisorState, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(EquipSlot.Body, _manager, _selector.Selected!)); + } + ImGui.SameLine(); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.WeaponState, _manager, _selector.Selected!)); + using (var _ = ImRaii.Group()) + { + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.WeaponState, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(EquipSlot.OffHand, _manager, _selector.Selected!)); + } } private void DrawCustomize() @@ -140,6 +154,50 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); } + private void DrawCustomizeApplication() + { + var set = _selector.Selected!.CustomizationSet; + var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender; + var flags = _selector.Selected!.ApplyCustomize == 0 ? 0 : (_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1; + if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3)) + { + var newFlags = flags == 3; + _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Clan, newFlags); + _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Gender, newFlags); + foreach (var index in CustomizationExtensions.AllBasic) + _manager.ChangeApplyCustomize(_selector.Selected!, index, newFlags); + } + + var applyClan = _selector.Selected!.DoApplyCustomize(CustomizeIndex.Clan); + if (ImGui.Checkbox($"Apply {CustomizeIndex.Clan.ToDefaultName()}", ref applyClan)) + _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Clan, applyClan); + + var applyGender = _selector.Selected!.DoApplyCustomize(CustomizeIndex.Gender); + if (ImGui.Checkbox($"Apply {CustomizeIndex.Gender.ToDefaultName()}", ref applyGender)) + _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Gender, applyGender); + + + foreach (var index in CustomizationExtensions.All.Where(set.IsAvailable)) + { + var apply = _selector.Selected!.DoApplyCustomize(index); + if (ImGui.Checkbox($"Apply {set.Option(index)}", ref apply)) + _manager.ChangeApplyCustomize(_selector.Selected!, index, apply); + } + } + + private void DrawCrestApplication() + { + var flags = (uint)_selector.Selected!.ApplyCrest; + var bigChange = ImGui.CheckboxFlags("Apply All Crests", ref flags, (uint)CrestExtensions.AllRelevant); + foreach (var flag in CrestExtensions.AllRelevantSet) + { + var slot = flag.ToSlot(); + var apply = bigChange ? ((CrestFlag)flags & flag) == flag : _selector.Selected!.DoApplyCrest(slot); + if (ImGui.Checkbox($"Apply {flag.ToLabel()} Crest", ref apply) || bigChange) + _manager.ChangeApplyCrest(_selector.Selected!, slot, apply); + } + } + private void DrawApplicationRules() { if (!ImGui.CollapsingHeader("Application Rules")) @@ -147,33 +205,9 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer using (var _ = ImRaii.Group()) { - var set = _selector.Selected!.CustomizationSet; - var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender; - var flags = _selector.Selected!.ApplyCustomize == 0 ? 0 : (_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1; - if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3)) - { - var newFlags = flags == 3; - _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Clan, newFlags); - _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Gender, newFlags); - foreach (var index in CustomizationExtensions.AllBasic) - _manager.ChangeApplyCustomize(_selector.Selected!, index, newFlags); - } - - var applyClan = _selector.Selected!.DoApplyCustomize(CustomizeIndex.Clan); - if (ImGui.Checkbox($"Apply {CustomizeIndex.Clan.ToDefaultName()}", ref applyClan)) - _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Clan, applyClan); - - var applyGender = _selector.Selected!.DoApplyCustomize(CustomizeIndex.Gender); - if (ImGui.Checkbox($"Apply {CustomizeIndex.Gender.ToDefaultName()}", ref applyGender)) - _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Gender, applyGender); - - - foreach (var index in CustomizationExtensions.All.Where(set.IsAvailable)) - { - var apply = _selector.Selected!.DoApplyCustomize(index); - if (ImGui.Checkbox($"Apply {set.Option(index)}", ref apply)) - _manager.ChangeApplyCustomize(_selector.Selected!, index, apply); - } + DrawCustomizeApplication(); + ImGui.NewLine(); + DrawCrestApplication(); } ImGui.SameLine(ImGui.GetContentRegionAvail().X / 2); diff --git a/Glamourer/Gui/ToggleDrawData.cs b/Glamourer/Gui/ToggleDrawData.cs index f78cf6f..edb06ae 100644 --- a/Glamourer/Gui/ToggleDrawData.cs +++ b/Glamourer/Gui/ToggleDrawData.cs @@ -2,6 +2,8 @@ using Glamourer.Designs; using Glamourer.Events; using Glamourer.State; +using Glamourer.Structs; +using Penumbra.GameData.Enums; namespace Glamourer.Gui; @@ -50,6 +52,29 @@ public ref struct ToggleDrawData }; } + public static ToggleDrawData CrestFromDesign(EquipSlot slot, DesignManager manager, Design design) + => new() + { + Label = $"{slot.ToCrestFlag().ToLabel()} Crest", + Tooltip = string.Empty, + Locked = design.WriteProtected(), + DisplayApplication = true, + CurrentValue = design.DesignData.Crest(slot), + CurrentApply = design.DoApplyCrest(slot), + SetValue = v => manager.ChangeCrest(design, slot, v), + SetApply = v => manager.ChangeApplyCrest(design, slot, v), + }; + + public static ToggleDrawData CrestFromState(EquipSlot slot, StateManager manager, ActorState state) + => new() + { + Label = $"{slot.ToCrestFlag().ToLabel()} Crest", + Tooltip = "Hide or show your free company crest on this piece of gear.", + Locked = state.IsLocked, + CurrentValue = state.ModelData.Crest(slot), // TODO + SetValue = v => { }, //manager.ChangeCrest(state, slot, v, StateChanged.Source.Manual), + }; + public static ToggleDrawData FromState(ActorState.MetaIndex index, StateManager manager, ActorState state) { var (label, tooltip, value, setValue) = index switch From 3177e6ca290cb69705de161ea09ae08cc3b2594e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 26 Nov 2023 11:43:26 +0100 Subject: [PATCH 16/20] Update OtterGui. --- OtterGui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OtterGui b/OtterGui index 2a79084..3e2d4ae 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 2a7908493ab0fd0e576d5fa37a3acbd920be6233 +Subproject commit 3e2d4ae934694918d312280d62127cf1a55b03e4 From 668d4c033facefd5e1b53538e5097420f25856f7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 1 Dec 2023 16:03:48 +0100 Subject: [PATCH 17/20] Add CrestService. --- Glamourer/Interop/ChangeCustomizeService.cs | 9 +- Glamourer/Interop/CrestService.cs | 92 +++++++++++++++++++++ Glamourer/Interop/UpdateSlotService.cs | 4 +- Glamourer/Services/ServiceManager.cs | 1 + Glamourer/State/StateListener.cs | 2 +- OtterGui | 2 +- 6 files changed, 100 insertions(+), 10 deletions(-) create mode 100644 Glamourer/Interop/CrestService.cs diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index a5a46e6..9e9a043 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -23,7 +23,7 @@ public unsafe class ChangeCustomizeService : EventWrapper _original; /// Check whether we in a manual customize update, in which case we need to not toggle certain flags. - public static readonly ThreadLocal InUpdate = new(() => false); + public static readonly InMethodChecker InUpdate = new(); public enum Priority { @@ -70,9 +70,8 @@ public unsafe class ChangeCustomizeService : EventWrapper(new Customize(*(CustomizeData*)data)); Invoke(this, (Model)human, customize); diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs new file mode 100644 index 0000000..edc61a5 --- /dev/null +++ b/Glamourer/Interop/CrestService.cs @@ -0,0 +1,92 @@ +using System; +using Dalamud.Hooking; +using Dalamud.Plugin.Services; +using Dalamud.Utility.Signatures; +using Glamourer.Interop.Structs; +using OtterGui.Classes; +using Penumbra.GameData.Enums; + +namespace Glamourer.Interop; + +/// +/// Triggered when the crest visibility is updated on a model. +/// +/// Parameter is the model with an update. +/// Parameter is the equipment slot changed. +/// Parameter is the whether the crest will be shown. +/// +/// +public sealed unsafe class CrestService : EventWrapper>, CrestService.Priority>, IDisposable +{ + public enum Priority + { + /// + StateListener = 0, + } + + public CrestService(IGameInteropProvider interop) + : base(nameof(CrestService)) + { + _humanSetFreeCompanyCrestVisibleOnSlot = + interop.HookFromAddress(_humanVTable[96], HumanSetFreeCompanyCrestVisibleOnSlotDetour); + _weaponSetFreeCompanyCrestVisibleOnSlot = + interop.HookFromAddress(_weaponVTable[96], WeaponSetFreeCompanyCrestVisibleOnSlotDetour); + _humanSetFreeCompanyCrestVisibleOnSlot.Enable(); + _weaponSetFreeCompanyCrestVisibleOnSlot.Enable(); + } + + protected override void Dispose(bool _) + { + _humanSetFreeCompanyCrestVisibleOnSlot.Dispose(); + _weaponSetFreeCompanyCrestVisibleOnSlot.Dispose(); + } + + public void Invoke(Model model, EquipSlot slot, ref bool visible) + { + var ret = new Ref(visible); + Invoke(this, model, slot, ret); + visible = ret; + } + + public void UpdateCrest(Model drawObject, EquipSlot slot, bool crest) + { + using var _ = _inUpdate.EnterMethod(); + drawObject.SetFreeCompanyCrestVisibleOnSlot(slot, crest); + } + + private readonly InMethodChecker _inUpdate = new(); + + private delegate void SetCrestDelegateIntern(nint drawObject, byte slot, byte visible); + + [Signature(global::Penumbra.GameData.Sigs.HumanVTable, ScanType = ScanType.StaticAddress)] + private readonly nint* _humanVTable = null!; + + [Signature(global::Penumbra.GameData.Sigs.WeaponVTable, ScanType = ScanType.StaticAddress)] + private readonly nint* _weaponVTable = null!; + + private readonly Hook _humanSetFreeCompanyCrestVisibleOnSlot; + private readonly Hook _weaponSetFreeCompanyCrestVisibleOnSlot; + + private void HumanSetFreeCompanyCrestVisibleOnSlotDetour(nint drawObject, byte slotIdx, byte visible) + { + var slot = ((uint)slotIdx).ToEquipSlot(); + var rVisible = visible != 0; + var inUpdate = _inUpdate.InMethod; + if (!inUpdate) + Invoke(drawObject, slot, ref rVisible); + Glamourer.Log.Excessive( + $"[Human.SetFreeCompanyCrestVisibleOnSlot] Called with 0x{drawObject:X} for slot {slot} with {rVisible} (original: {visible != 0}, in update: {inUpdate})."); + _humanSetFreeCompanyCrestVisibleOnSlot.Original(drawObject, slotIdx, rVisible ? (byte)1 : (byte)0); + } + + private void WeaponSetFreeCompanyCrestVisibleOnSlotDetour(nint drawObject, byte slotIdx, byte visible) + { + var rVisible = visible != 0; + var inUpdate = _inUpdate.InMethod; + if (!inUpdate) + Invoke(drawObject, EquipSlot.BothHand, ref rVisible); + Glamourer.Log.Excessive( + $"[Weapon.SetFreeCompanyCrestVisibleOnSlot] Called with 0x{drawObject:X} with {rVisible} (original: {visible != 0}, in update: {inUpdate})."); + _weaponSetFreeCompanyCrestVisibleOnSlot.Original(drawObject, slotIdx, rVisible ? (byte)1 : (byte)0); + } +} diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index f336f5a..f5a0ec0 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -21,9 +21,7 @@ public unsafe class UpdateSlotService : IDisposable } public void Dispose() - { - _flagSlotForUpdateHook.Dispose(); - } + => _flagSlotForUpdateHook.Dispose(); public void UpdateSlot(Model drawObject, EquipSlot slot, CharacterArmor data) { diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 092d8c2..93a9854 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -98,6 +98,7 @@ public static class ServiceManager .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton(); diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 3fdf4f7..3cef72a 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -490,7 +490,7 @@ public class StateListener : IDisposable private void OnVisorChange(Model model, Ref value) { // Skip updates when in customize update. - if (ChangeCustomizeService.InUpdate.IsValueCreated && ChangeCustomizeService.InUpdate.Value) + if (ChangeCustomizeService.InUpdate.InMethod) return; // Find appropriate actor and state. diff --git a/OtterGui b/OtterGui index 3e2d4ae..f624aca 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 3e2d4ae934694918d312280d62127cf1a55b03e4 +Subproject commit f624aca526bd1f36364d63443ed1c6e83499b8b7 From 358e33346f6c6f2f03ce076ca86a6e7a526796fe Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 1 Dec 2023 21:44:23 +0100 Subject: [PATCH 18/20] Rework some stuff, add debug tab. --- Glamourer.GameData/Structs/CrestFlag.cs | 34 ++++++++++--- Glamourer/Gui/Tabs/DebugTab.cs | 29 +++++++++-- Glamourer/Interop/CrestService.cs | 68 +++++++++++++++++++++++-- Glamourer/Interop/Structs/Actor.cs | 17 ++++--- Glamourer/Interop/Structs/Model.cs | 32 ------------ Glamourer/State/ActorState.cs | 2 +- 6 files changed, 129 insertions(+), 53 deletions(-) diff --git a/Glamourer.GameData/Structs/CrestFlag.cs b/Glamourer.GameData/Structs/CrestFlag.cs index 3ef9f1e..11a7260 100644 --- a/Glamourer.GameData/Structs/CrestFlag.cs +++ b/Glamourer.GameData/Structs/CrestFlag.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Numerics; using Penumbra.GameData.Enums; namespace Glamourer.Structs; @@ -23,17 +22,22 @@ public enum CrestFlag : ushort OffHand = 0x0800, } +public enum CrestType : byte +{ + None, + Human, + Mainhand, + Offhand, +}; + public static class CrestExtensions { public const CrestFlag All = (CrestFlag)(((ulong)EquipFlag.Offhand << 1) - 1); public const CrestFlag AllRelevant = CrestFlag.Head | CrestFlag.Body | CrestFlag.OffHand; - public static readonly IReadOnlyList AllRelevantSet = Enum.GetValues().Where(f => f.ToRelevantIndex() >= 0).ToArray(); + public static readonly IReadOnlyList AllRelevantSet = Enum.GetValues().Where(f => AllRelevant.HasFlag(f)).ToArray(); - public static int ToIndex(this CrestFlag flag) - => BitOperations.TrailingZeroCount((uint)flag); - - public static int ToRelevantIndex(this CrestFlag flag) + public static int ToInternalIndex(this CrestFlag flag) => flag switch { CrestFlag.Head => 0, @@ -42,6 +46,24 @@ public static class CrestExtensions _ => -1, }; + public static (CrestType Type, byte Index) ToIndex(this CrestFlag flag) + => flag switch + { + CrestFlag.Head => (CrestType.Human, 0), + CrestFlag.Body => (CrestType.Human, 1), + CrestFlag.Hands => (CrestType.Human, 2), + CrestFlag.Legs => (CrestType.Human, 3), + CrestFlag.Feet => (CrestType.Human, 4), + CrestFlag.Ears => (CrestType.None, 0), + CrestFlag.Neck => (CrestType.None, 0), + CrestFlag.Wrists => (CrestType.None, 0), + CrestFlag.RFinger => (CrestType.None, 0), + CrestFlag.LFinger => (CrestType.None, 0), + CrestFlag.MainHand => (CrestType.None, 0), + CrestFlag.OffHand => (CrestType.Offhand, 0), + _ => (CrestType.None, 0), + }; + public static CrestFlag ToCrestFlag(this EquipSlot slot) => slot switch { diff --git a/Glamourer/Gui/Tabs/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab.cs index 7d946fb..52385b8 100644 --- a/Glamourer/Gui/Tabs/DebugTab.cs +++ b/Glamourer/Gui/Tabs/DebugTab.cs @@ -21,6 +21,7 @@ using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.Services; using Glamourer.State; +using Glamourer.Structs; using Glamourer.Unlocks; using Glamourer.Utility; using ImGuiNET; @@ -43,6 +44,7 @@ public unsafe class DebugTab : ITab private readonly VisorService _visorService; private readonly ChangeCustomizeService _changeCustomizeService; private readonly UpdateSlotService _updateSlotService; + private readonly CrestService _crestService; private readonly WeaponService _weaponService; private readonly MetaService _metaService; private readonly InventoryService _inventoryService; @@ -50,7 +52,7 @@ public unsafe class DebugTab : ITab private readonly ObjectManager _objectManager; private readonly GlamourerIpc _ipc; private readonly CodeService _code; - private readonly ImportService _importService; + private readonly ImportService _importService; private readonly ItemManager _items; private readonly ActorService _actors; @@ -82,7 +84,7 @@ public unsafe class DebugTab : ITab PenumbraChangedItemTooltip penumbraTooltip, MetaService metaService, GlamourerIpc ipc, DalamudPluginInterface pluginInterface, AutoDesignManager autoDesignManager, JobService jobs, CodeService code, CustomizeUnlockManager customizeUnlocks, ItemUnlockManager itemUnlocks, DesignConverter designConverter, ImportService importService, InventoryService inventoryService, - HumanModelList humans, FunModule funModule) + HumanModelList humans, FunModule funModule, CrestService crestService) { _changeCustomizeService = changeCustomizeService; _visorService = visorService; @@ -107,10 +109,11 @@ public unsafe class DebugTab : ITab _customizeUnlocks = customizeUnlocks; _itemUnlocks = itemUnlocks; _designConverter = designConverter; - _importService = importService; + _importService = importService; _inventoryService = inventoryService; _humans = humans; _funModule = funModule; + _crestService = crestService; } public ReadOnlySpan Label @@ -200,6 +203,7 @@ public unsafe class DebugTab : ITab DrawWetness(actor, model); DrawEquip(actor, model); DrawCustomize(actor, model); + DrawCrests(actor, model); } private string _objectFilter = string.Empty; @@ -477,6 +481,25 @@ public unsafe class DebugTab : ITab } } + private void DrawCrests(Actor actor, Model model) + { + using var id = ImRaii.PushId("Crests"); + foreach (var crestFlag in CrestExtensions.AllRelevantSet) + { + id.Push((int)crestFlag); + var modelCrest = CrestService.GetModelCrest(actor, crestFlag); + ImGuiUtil.DrawTableColumn($"{crestFlag.ToLabel()} Crest"); + ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.GetCrest(crestFlag).ToString() : "No Character"); + ImGuiUtil.DrawTableColumn(modelCrest.ToString()); + + ImGui.TableNextColumn(); + if (model.IsHuman && ImGui.SmallButton("Toggle")) + _crestService.UpdateCrest(actor, crestFlag, !modelCrest); + + id.Pop(); + } + } + #endregion #region Penumbra diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs index edc61a5..253a4ea 100644 --- a/Glamourer/Interop/CrestService.cs +++ b/Glamourer/Interop/CrestService.cs @@ -2,7 +2,10 @@ using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Interop.Structs; +using Glamourer.Structs; using OtterGui.Classes; using Penumbra.GameData.Enums; @@ -27,6 +30,7 @@ public sealed unsafe class CrestService : EventWrapper(_humanVTable[96], HumanSetFreeCompanyCrestVisibleOnSlotDetour); _weaponSetFreeCompanyCrestVisibleOnSlot = @@ -48,10 +52,68 @@ public sealed unsafe class CrestService : EventWrapper)((nint*)model.AsCharacterBase->VTable)[95]; + return getter(model.AsHuman, index) != 0; + } + case CrestType.Offhand: + { + var model = (Model)gameObject.AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.OffHand).DrawObject; + if (!model.IsWeapon) + return false; + + var getter = (delegate* unmanaged)((nint*)model.AsCharacterBase->VTable)[95]; + return getter(model.AsWeapon, index) != 0; + } + } + + return false; + } + + public void UpdateCrest(Actor gameObject, CrestFlag slot, bool crest) + { + if (!gameObject.IsCharacter) + return; + + var (type, index) = slot.ToIndex(); + switch (type) + { + case CrestType.Human: + { + var model = gameObject.Model; + if (!model.IsHuman) + return; + + using var _ = _inUpdate.EnterMethod(); + var setter = (delegate* unmanaged)((nint*)model.AsCharacterBase->VTable)[96]; + setter(model.AsHuman, index, crest ? (byte)1 : (byte)0); + break; + } + case CrestType.Offhand: + { + var model = (Model)gameObject.AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.OffHand).DrawObject; + if (!model.IsWeapon) + return; + + using var _ = _inUpdate.EnterMethod(); + var setter = (delegate* unmanaged)((nint*)model.AsCharacterBase->VTable)[96]; + setter(model.AsWeapon, index, crest ? (byte)1 : (byte)0); + break; + } + } } private readonly InMethodChecker _inUpdate = new(); diff --git a/Glamourer/Interop/Structs/Actor.cs b/Glamourer/Interop/Structs/Actor.cs index ba7f132..0a6196b 100644 --- a/Glamourer/Interop/Structs/Actor.cs +++ b/Glamourer/Interop/Structs/Actor.cs @@ -3,6 +3,7 @@ using System; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.Customization; +using Glamourer.Structs; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.String; @@ -106,7 +107,7 @@ public readonly unsafe struct Actor : IEquatable public CharacterArmor GetArmor(EquipSlot slot) => ((CharacterArmor*)&AsCharacter->DrawData.Head)[slot.ToIndex()]; - public bool GetCrest(EquipSlot slot) + public bool GetCrest(CrestFlag slot) => (GetFreeCompanyCrestBitfield() & CrestMask(slot)) != 0; public CharacterWeapon GetMainhand() @@ -122,15 +123,15 @@ public readonly unsafe struct Actor : IEquatable private byte GetFreeCompanyCrestBitfield() => ((byte*)Address)[0x1BBB]; - private static byte CrestMask(EquipSlot slot) + private static byte CrestMask(CrestFlag slot) => slot switch { - EquipSlot.OffHand => 0x01, - EquipSlot.Head => 0x02, - EquipSlot.Body => 0x04, - EquipSlot.Hands => 0x08, - EquipSlot.Legs => 0x10, - EquipSlot.Feet => 0x20, + CrestFlag.OffHand => 0x01, + CrestFlag.Head => 0x02, + CrestFlag.Body => 0x04, + CrestFlag.Hands => 0x08, + CrestFlag.Legs => 0x10, + CrestFlag.Feet => 0x20, _ => 0x00, }; diff --git a/Glamourer/Interop/Structs/Model.cs b/Glamourer/Interop/Structs/Model.cs index 811ff81..77bf24e 100644 --- a/Glamourer/Interop/Structs/Model.cs +++ b/Glamourer/Interop/Structs/Model.cs @@ -91,9 +91,6 @@ public readonly unsafe struct Model : IEquatable public CharacterArmor GetArmor(EquipSlot slot) => ((CharacterArmor*)&AsHuman->Head)[slot.ToIndex()]; - public bool GetCrest(EquipSlot slot) - => IsFreeCompanyCrestVisibleOnSlot(slot); - public Customize GetCustomize() => *(Customize*)&AsHuman->Customize; @@ -198,35 +195,6 @@ public readonly unsafe struct Model : IEquatable return discriminator1 == 0 && discriminator2 != 0 ? (second, first) : (first, second); } - // TODO remove these when available in ClientStructs - private bool IsFreeCompanyCrestVisibleOnSlot(EquipSlot slot) - { - if (!IsCharacterBase) - return false; - - var index = (byte)slot.ToIndex(); - if (index >= 12) - return false; - - var characterBase = AsCharacterBase; - var getter = (delegate* unmanaged)((nint*)characterBase->VTable)[95]; - return getter(characterBase, index) != 0; - } - - public void SetFreeCompanyCrestVisibleOnSlot(EquipSlot slot, bool visible) - { - if (!IsCharacterBase) - return; - - var index = (byte)slot.ToIndex(); - if (index >= 12) - return; - - var characterBase = AsCharacterBase; - var setter = (delegate* unmanaged)((nint*)characterBase->VTable)[96]; - setter(characterBase, index, visible ? (byte)1 : (byte)0); - } - public override string ToString() => $"0x{Address:X}"; } diff --git a/Glamourer/State/ActorState.cs b/Glamourer/State/ActorState.cs index f3372e8..3cd7cba 100644 --- a/Glamourer/State/ActorState.cs +++ b/Glamourer/State/ActorState.cs @@ -90,7 +90,7 @@ public class ActorState => ref _sources[slot.ToIndex() + (stain ? EquipFlagExtensions.NumEquipFlags / 2 : 0)]; public ref StateChanged.Source this[CrestFlag slot] - => ref _sources[EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + slot.ToRelevantIndex()]; + => ref _sources[EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + slot.ToInternalIndex()]; public ref StateChanged.Source this[CustomizeIndex type] => ref _sources[EquipFlagExtensions.NumEquipFlags + (int)type]; From cc09cced612aded2e7e48c61f35cbb41db9bc6cf Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 2 Dec 2023 16:33:01 +0100 Subject: [PATCH 19/20] Revamp, temp state. --- Glamourer.GameData/Structs/CrestFlag.cs | 44 +++------ Glamourer/Automation/AutoDesign.cs | 21 +++-- Glamourer/Automation/AutoDesignApplier.cs | 22 ++++- Glamourer/Designs/Design.cs | 9 +- Glamourer/Designs/DesignBase.cs | 51 +++++----- Glamourer/Designs/DesignConverter.cs | 66 ++++++------- Glamourer/Designs/DesignData.cs | 14 +-- Glamourer/Designs/DesignManager.cs | 14 ++- Glamourer/Gui/DesignQuickBar.cs | 4 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 24 ++--- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 4 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 19 ++-- Glamourer/Gui/ToggleDrawData.cs | 12 +-- Glamourer/Gui/UiHelpers.cs | 10 +- Glamourer/Interop/CrestService.cs | 97 ++++++++++---------- Glamourer/Interop/Structs/Actor.cs | 18 +--- Glamourer/Interop/WeaponService.cs | 8 +- Glamourer/Services/CommandService.cs | 6 +- Glamourer/State/StateApplier.cs | 44 ++++----- Glamourer/State/StateEditor.cs | 13 +++ Glamourer/State/StateListener.cs | 68 +++++++++++++- Glamourer/State/StateManager.cs | 95 +++++++++++-------- 22 files changed, 365 insertions(+), 298 deletions(-) diff --git a/Glamourer.GameData/Structs/CrestFlag.cs b/Glamourer.GameData/Structs/CrestFlag.cs index 11a7260..61ccc7e 100644 --- a/Glamourer.GameData/Structs/CrestFlag.cs +++ b/Glamourer.GameData/Structs/CrestFlag.cs @@ -8,18 +8,18 @@ namespace Glamourer.Structs; [Flags] public enum CrestFlag : ushort { - Head = 0x0001, - Body = 0x0002, - Hands = 0x0004, - Legs = 0x0008, - Feet = 0x0010, - Ears = 0x0020, - Neck = 0x0040, - Wrists = 0x0080, - RFinger = 0x0100, - LFinger = 0x0200, - MainHand = 0x0400, - OffHand = 0x0800, + OffHand = 0x0001, + Head = 0x0002, + Body = 0x0004, + Hands = 0x0008, + Legs = 0x0010, + Feet = 0x0020, + Ears = 0x0040, + Neck = 0x0080, + Wrists = 0x0100, + RFinger = 0x0200, + LFinger = 0x0400, + MainHand = 0x0800, } public enum CrestType : byte @@ -32,7 +32,7 @@ public enum CrestType : byte public static class CrestExtensions { - public const CrestFlag All = (CrestFlag)(((ulong)EquipFlag.Offhand << 1) - 1); + public const CrestFlag All = (CrestFlag)(((ulong)EquipFlag.Mainhand << 1) - 1); public const CrestFlag AllRelevant = CrestFlag.Head | CrestFlag.Body | CrestFlag.OffHand; public static readonly IReadOnlyList AllRelevantSet = Enum.GetValues().Where(f => AllRelevant.HasFlag(f)).ToArray(); @@ -82,24 +82,6 @@ public static class CrestExtensions _ => 0, }; - public static EquipSlot ToSlot(this CrestFlag flag) - => flag switch - { - CrestFlag.MainHand => EquipSlot.MainHand, - CrestFlag.OffHand => EquipSlot.OffHand, - CrestFlag.Head => EquipSlot.Head, - CrestFlag.Body => EquipSlot.Body, - CrestFlag.Hands => EquipSlot.Hands, - CrestFlag.Legs => EquipSlot.Legs, - CrestFlag.Feet => EquipSlot.Feet, - CrestFlag.Ears => EquipSlot.Ears, - CrestFlag.Neck => EquipSlot.Neck, - CrestFlag.Wrists => EquipSlot.Wrists, - CrestFlag.RFinger => EquipSlot.RFinger, - CrestFlag.LFinger => EquipSlot.LFinger, - _ => 0, - }; - public static string ToLabel(this CrestFlag flag) => flag switch { diff --git a/Glamourer/Automation/AutoDesign.cs b/Glamourer/Automation/AutoDesign.cs index 4cde895..0a26759 100644 --- a/Glamourer/Automation/AutoDesign.cs +++ b/Glamourer/Automation/AutoDesign.cs @@ -15,13 +15,13 @@ public class AutoDesign [Flags] public enum Type : byte { - Armor = 0x01, - Customizations = 0x02, - Weapons = 0x04, - Stains = 0x08, - Accessories = 0x10, + Armor = 0x01, + Customizations = 0x02, + Weapons = 0x04, + GearCustomization = 0x08, + Accessories = 0x10, - All = Armor | Accessories | Customizations | Weapons | Stains, + All = Armor | Accessories | Customizations | Weapons | GearCustomization, } public Design? Design; @@ -80,19 +80,20 @@ public class AutoDesign return ret; } - public (EquipFlag Equip, CustomizeFlag Customize, bool ApplyHat, bool ApplyVisor, bool ApplyWeapon, bool ApplyWet) ApplyWhat() + public (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, bool ApplyHat, bool ApplyVisor, bool ApplyWeapon, bool ApplyWet) ApplyWhat() { var equipFlags = (ApplicationType.HasFlag(Type.Weapons) ? WeaponFlags : 0) | (ApplicationType.HasFlag(Type.Armor) ? ArmorFlags : 0) | (ApplicationType.HasFlag(Type.Accessories) ? AccessoryFlags : 0) - | (ApplicationType.HasFlag(Type.Stains) ? StainFlags : 0); + | (ApplicationType.HasFlag(Type.GearCustomization) ? StainFlags : 0); var customizeFlags = ApplicationType.HasFlag(Type.Customizations) ? CustomizeFlagExtensions.All : 0; + var crestFlag = ApplicationType.HasFlag(Type.GearCustomization) ? CrestExtensions.AllRelevant : 0; if (Revert) - return (equipFlags, customizeFlags, ApplicationType.HasFlag(Type.Armor), ApplicationType.HasFlag(Type.Armor), + return (equipFlags, customizeFlags, crestFlag, ApplicationType.HasFlag(Type.Armor), ApplicationType.HasFlag(Type.Armor), ApplicationType.HasFlag(Type.Weapons), ApplicationType.HasFlag(Type.Customizations)); - return (equipFlags & Design!.ApplyEquip, customizeFlags & Design.ApplyCustomize, + return (equipFlags & Design!.ApplyEquip, customizeFlags & Design.ApplyCustomize, crestFlag & Design.ApplyCrest, ApplicationType.HasFlag(Type.Armor) && Design.DoApplyHatVisible(), ApplicationType.HasFlag(Type.Armor) && Design.DoApplyVisorToggle(), ApplicationType.HasFlag(Type.Weapons) && Design.DoApplyWeaponVisible(), diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 4b76943..ddc8636 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -268,6 +268,7 @@ public class AutoDesignApplier : IDisposable { EquipFlag totalEquipFlags = 0; CustomizeFlag totalCustomizeFlags = 0; + CrestFlag totalCrestFlags = 0; byte totalMetaFlags = 0; if (set.BaseState == AutoDesignSet.Base.Game) _state.ResetStateFixed(state); @@ -291,10 +292,11 @@ public class AutoDesignApplier : IDisposable if (!data.IsHuman) continue; - var (equipFlags, customizeFlags, applyHat, applyVisor, applyWeapon, applyWet) = design.ApplyWhat(); + var (equipFlags, customizeFlags, crestFlags, applyHat, applyVisor, applyWeapon, applyWet) = design.ApplyWhat(); ReduceMeta(state, data, applyHat, applyVisor, applyWeapon, applyWet, ref totalMetaFlags, respectManual, source); ReduceCustomize(state, data, customizeFlags, ref totalCustomizeFlags, respectManual, source); ReduceEquip(state, data, equipFlags, ref totalEquipFlags, respectManual, source, fromJobChange); + ReduceCrests(state, data, crestFlags, ref totalCrestFlags, respectManual, source); } if (totalCustomizeFlags != 0) @@ -324,6 +326,24 @@ public class AutoDesignApplier : IDisposable } } + private void ReduceCrests(ActorState state, in DesignData design, CrestFlag crestFlags, ref CrestFlag totalCrestFlags, bool respectManual, + StateChanged.Source source) + { + crestFlags &= ~totalCrestFlags; + if (crestFlags == 0) + return; + + foreach (var slot in CrestExtensions.AllRelevantSet) + { + if (!crestFlags.HasFlag(slot)) + continue; + + if (!respectManual || state[slot] is not StateChanged.Source.Manual) + _state.ChangeCrest(state, slot, design.Crest(slot), source); + totalCrestFlags |= slot; + } + } + private void ReduceEquip(ActorState state, in DesignData design, EquipFlag equipFlags, ref EquipFlag totalEquipFlags, bool respectManual, StateChanged.Source source, bool fromJobChange) { diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 5c106e3..d10fe29 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -3,8 +3,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Dalamud.Interface.Internal.Notifications; -using Glamourer.Customization; -using Glamourer.Gui; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Newtonsoft.Json; @@ -28,8 +26,8 @@ public sealed class Design : DesignBase, ISavable internal Design(Design other) : base(other) { - Tags = Tags.ToArray(); - Description = Description; + Tags = other.Tags.ToArray(); + Description = other.Description; AssociatedMods = new SortedList(other.AssociatedMods); } @@ -69,8 +67,7 @@ public sealed class Design : DesignBase, ISavable ["Equipment"] = SerializeEquipment(), ["Customize"] = SerializeCustomize(), ["Mods"] = SerializeMods(), - } - ; + }; return ret; } diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 135c858..d859e8e 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.Internal.Notifications; using Glamourer.Customization; using Glamourer.Services; using Glamourer.Structs; @@ -9,6 +7,8 @@ using OtterGui.Classes; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using System; +using System.Linq; namespace Glamourer.Designs; @@ -169,8 +169,8 @@ public class DesignBase public bool DoApplyCustomize(CustomizeIndex idx) => ApplyCustomize.HasFlag(idx.ToFlag()); - public bool DoApplyCrest(EquipSlot slot) - => ApplyCrest.HasFlag(slot.ToCrestFlag()); + public bool DoApplyCrest(CrestFlag slot) + => ApplyCrest.HasFlag(slot); internal bool SetApplyEquip(EquipSlot slot, bool value) { @@ -202,9 +202,9 @@ public class DesignBase return true; } - internal bool SetApplyCrest(EquipSlot slot, bool value) + internal bool SetApplyCrest(CrestFlag slot, bool value) { - var newValue = value ? ApplyCrest | slot.ToCrestFlag() : ApplyCrest & ~slot.ToCrestFlag(); + var newValue = value ? ApplyCrest | slot : ApplyCrest & ~slot; if (newValue == ApplyCrest) return false; @@ -212,28 +212,32 @@ public class DesignBase return true; } - internal FlagRestrictionResetter TemporarilyRestrictApplication(EquipFlag equipFlags, CustomizeFlag customizeFlags) - => new(this, equipFlags, customizeFlags); + internal FlagRestrictionResetter TemporarilyRestrictApplication(EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) + => new(this, equipFlags, customizeFlags, crestFlags); internal readonly struct FlagRestrictionResetter : IDisposable { private readonly DesignBase _design; private readonly EquipFlag _oldEquipFlags; private readonly CustomizeFlag _oldCustomizeFlags; + private readonly CrestFlag _oldCrestFlags; - public FlagRestrictionResetter(DesignBase d, EquipFlag equipFlags, CustomizeFlag customizeFlags) + public FlagRestrictionResetter(DesignBase d, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) { _design = d; _oldEquipFlags = d.ApplyEquip; _oldCustomizeFlags = d.ApplyCustomizeRaw; + _oldCrestFlags = d.ApplyCrest; d.ApplyEquip &= equipFlags; d.ApplyCustomize &= customizeFlags; + d.ApplyCrest &= crestFlags; } public void Dispose() { _design.ApplyEquip = _oldEquipFlags; _design.ApplyCustomize = _oldCustomizeFlags; + _design.ApplyCrest = _oldCrestFlags; } } @@ -275,10 +279,11 @@ public class DesignBase { foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) { - var item = _designData.Item(slot); - var stain = _designData.Stain(slot); - var crest = _designData.Crest(slot); - ret[slot.ToString()] = Serialize(item.Id, stain, crest, DoApplyEquip(slot), DoApplyStain(slot), DoApplyCrest(slot)); + var item = _designData.Item(slot); + var stain = _designData.Stain(slot); + var crestSlot = slot.ToCrestFlag(); + var crest = _designData.Crest(crestSlot); + ret[slot.ToString()] = Serialize(item.Id, stain, crest, DoApplyEquip(slot), DoApplyStain(slot), DoApplyCrest(crestSlot)); } ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyHatVisible()).ToJObject("Show", "Apply"); @@ -365,7 +370,7 @@ public class DesignBase { var id = item?["ItemId"]?.ToObject() ?? ItemManager.NothingId(slot).Id; var stain = (StainId)(item?["Stain"]?.ToObject() ?? 0); - var crest = (item?["Crest"]?.ToObject() ?? false); + var crest = item?["Crest"]?.ToObject() ?? false; var apply = item?["Apply"]?.ToObject() ?? false; var applyStain = item?["ApplyStain"]?.ToObject() ?? false; var applyCrest = item?["ApplyCrest"]?.ToObject() ?? false; @@ -384,19 +389,21 @@ public class DesignBase PrintWarning(items.ValidateItem(slot, id, out var item, allowUnknown)); PrintWarning(items.ValidateStain(stain, out stain, allowUnknown)); + var crestSlot = slot.ToCrestFlag(); design._designData.SetItem(slot, item); design._designData.SetStain(slot, stain); - design._designData.SetCrest(slot, crest); + design._designData.SetCrest(crestSlot, crest); design.SetApplyEquip(slot, apply); design.SetApplyStain(slot, applyStain); - design.SetApplyCrest(slot, applyCrest); + design.SetApplyCrest(crestSlot, applyCrest); } { var (id, stain, crest, apply, applyStain, applyCrest) = ParseItem(EquipSlot.MainHand, equip[EquipSlot.MainHand.ToString()]); if (id == ItemManager.NothingId(EquipSlot.MainHand)) id = items.DefaultSword.ItemId; - var (idOff, stainOff, crestOff, applyOff, applyStainOff, applyCrestOff) = ParseItem(EquipSlot.OffHand, equip[EquipSlot.OffHand.ToString()]); + var (idOff, stainOff, crestOff, applyOff, applyStainOff, applyCrestOff) = + ParseItem(EquipSlot.OffHand, equip[EquipSlot.OffHand.ToString()]); if (id == ItemManager.NothingId(EquipSlot.OffHand)) id = ItemManager.NothingId(FullEquipType.Shield); @@ -407,14 +414,14 @@ public class DesignBase design._designData.SetItem(EquipSlot.OffHand, off); design._designData.SetStain(EquipSlot.MainHand, stain); design._designData.SetStain(EquipSlot.OffHand, stainOff); - design._designData.SetCrest(EquipSlot.MainHand, crest); - design._designData.SetCrest(EquipSlot.OffHand, crestOff); + design._designData.SetCrest(CrestFlag.MainHand, crest); + design._designData.SetCrest(CrestFlag.OffHand, crestOff); design.SetApplyEquip(EquipSlot.MainHand, apply); design.SetApplyEquip(EquipSlot.OffHand, applyOff); design.SetApplyStain(EquipSlot.MainHand, applyStain); design.SetApplyStain(EquipSlot.OffHand, applyStainOff); - design.SetApplyCrest(EquipSlot.MainHand, applyCrest); - design.SetApplyCrest(EquipSlot.OffHand, applyCrestOff); + design.SetApplyCrest(CrestFlag.MainHand, applyCrest); + design.SetApplyCrest(CrestFlag.OffHand, applyCrestOff); } var metaValue = QuadBool.FromJObject(equip["Hat"], "Show", "Apply", QuadBool.NullFalse); design.SetApplyHatVisible(metaValue.Enabled); diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index e8b1742..cd6db7e 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -13,22 +13,9 @@ using Penumbra.GameData.Enums; namespace Glamourer.Designs; -public class DesignConverter +public class DesignConverter(ItemManager _items, DesignManager _designs, CustomizationService _customize, HumanModelList _humans) { - public const byte Version = 5; - - private readonly ItemManager _items; - private readonly DesignManager _designs; - private readonly CustomizationService _customize; - private readonly HumanModelList _humans; - - public DesignConverter(ItemManager items, DesignManager designs, CustomizationService customize, HumanModelList humans) - { - _items = items; - _designs = designs; - _customize = customize; - _humans = humans; - } + public const byte Version = 6; public JObject ShareJObject(DesignBase design) => design.JsonSerialize(); @@ -36,32 +23,33 @@ public class DesignConverter public JObject ShareJObject(Design design) => design.JsonSerialize(); - public JObject ShareJObject(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags) + public JObject ShareJObject(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) { - var design = Convert(state, equipFlags, customizeFlags); + var design = Convert(state, equipFlags, customizeFlags, crestFlags); return ShareJObject(design); } public string ShareBase64(Design design) - => ShareBackwardCompatible(ShareJObject(design), design); + => ShareBase64(ShareJObject(design)); public string ShareBase64(DesignBase design) - => ShareBackwardCompatible(ShareJObject(design), design); + => ShareBase64(ShareJObject(design)); public string ShareBase64(ActorState state) - => ShareBase64(state, EquipFlagExtensions.All, CustomizeFlagExtensions.All); + => ShareBase64(state, EquipFlagExtensions.All, CustomizeFlagExtensions.All, CrestExtensions.All); - public string ShareBase64(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags) + public string ShareBase64(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) { - var design = Convert(state, equipFlags, customizeFlags); - return ShareBackwardCompatible(ShareJObject(design), design); + var design = Convert(state, equipFlags, customizeFlags, crestFlags); + return ShareBase64(ShareJObject(design)); } - public DesignBase Convert(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags) + public DesignBase Convert(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) { var design = _designs.CreateTemporary(); design.ApplyEquip = equipFlags & EquipFlagExtensions.All; - design.ApplyCustomize = customizeFlags; + design.ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; + design.ApplyCrest = crestFlags & CrestExtensions.All; design.SetApplyHatVisible(design.DoApplyEquip(EquipSlot.Head)); design.SetApplyVisorToggle(design.DoApplyEquip(EquipSlot.Head)); design.SetApplyWeaponVisible(design.DoApplyEquip(EquipSlot.MainHand) || design.DoApplyEquip(EquipSlot.OffHand)); @@ -123,6 +111,16 @@ public class DesignConverter : DesignBase.LoadDesignBase(_customize, _items, jObj2); break; } + case Version: + { + version = bytes.DecompressToString(out var decompressed); + var jObj2 = JObject.Parse(decompressed); + Debug.Assert(version == Version); + ret = jObj2["Identifier"] != null + ? Design.LoadDesign(_customize, _items, jObj2) + : DesignBase.LoadDesignBase(_customize, _items, jObj2); + break; + } default: throw new Exception($"Unknown Version {bytes[0]}."); } } @@ -138,6 +136,7 @@ public class DesignConverter if (!equip) { ret.ApplyEquip = 0; + ret.ApplyCrest = 0; ret.SetApplyHatVisible(false); ret.SetApplyWeaponVisible(false); ret.SetApplyVisorToggle(false); @@ -146,23 +145,10 @@ public class DesignConverter return ret; } - private static string ShareBase64(JObject jObj) + private static string ShareBase64(JObject jObject) { - var json = jObj.ToString(Formatting.None); + var json = jObject.ToString(Formatting.None); var compressed = json.Compress(Version); return System.Convert.ToBase64String(compressed); } - - private static string ShareBackwardCompatible(JObject jObject, DesignBase design) - { - var oldBase64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.ApplyEquip, design.ApplyCustomizeRaw, - design.DoApplyHatVisible(), design.DoApplyVisorToggle(), design.DoApplyWeaponVisible(), design.WriteProtected(), 1f); - var oldBytes = System.Convert.FromBase64String(oldBase64); - var json = jObject.ToString(Formatting.None); - var compressed = json.Compress(Version); - var bytes = new byte[oldBytes.Length + compressed.Length]; - oldBytes.CopyTo(bytes, 0); - compressed.CopyTo(bytes, oldBytes.Length); - return System.Convert.ToBase64String(bytes); - } } diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index fe2e655..4b0d53b 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -61,8 +61,8 @@ public unsafe struct DesignData return index > 11 ? (StainId)0 : _equipmentBytes[4 * index + 3]; } - public readonly bool Crest(EquipSlot slot) - => CrestVisibility.HasFlag(slot.ToCrestFlag()); + public readonly bool Crest(CrestFlag slot) + => CrestVisibility.HasFlag(slot); public FullEquipType MainhandType @@ -179,9 +179,9 @@ public unsafe struct DesignData _ => false, }; - public bool SetCrest(EquipSlot slot, bool visible) + public bool SetCrest(CrestFlag slot, bool visible) { - var newValue = visible ? CrestVisibility | slot.ToCrestFlag() : CrestVisibility & ~slot.ToCrestFlag(); + var newValue = visible ? CrestVisibility | slot : CrestVisibility & ~slot; if (newValue == CrestVisibility) return false; @@ -244,15 +244,15 @@ public unsafe struct DesignData { SetItem(slot, ItemManager.NothingItem(slot)); SetStain(slot, 0); - SetCrest(slot, false); + SetCrest(slot.ToCrestFlag(), false); } SetItem(EquipSlot.MainHand, items.DefaultSword); SetStain(EquipSlot.MainHand, 0); - SetCrest(EquipSlot.MainHand, false); + SetCrest(CrestFlag.MainHand, false); SetItem(EquipSlot.OffHand, ItemManager.NothingItem(FullEquipType.Shield)); SetStain(EquipSlot.OffHand, 0); - SetCrest(EquipSlot.OffHand, false); + SetCrest(CrestFlag.OffHand, false); } diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 7159bac..392301f 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -8,6 +8,7 @@ using Glamourer.Events; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Glamourer.State; +using Glamourer.Structs; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; @@ -447,7 +448,7 @@ public class DesignManager } /// Change the crest visibility for any equipment piece. - public void ChangeCrest(Design design, EquipSlot slot, bool crest) + public void ChangeCrest(Design design, CrestFlag slot, bool crest) { var oldCrest = design.DesignData.Crest(slot); if (!design.GetDesignDataRef().SetCrest(slot, crest)) @@ -460,7 +461,7 @@ public class DesignManager } /// Change whether to apply a specific crest visibility. - public void ChangeApplyCrest(Design design, EquipSlot slot, bool value) + public void ChangeApplyCrest(Design design, CrestFlag slot, bool value) { if (!design.SetApplyCrest(slot, value)) return; @@ -539,7 +540,10 @@ public class DesignManager if (other.DoApplyStain(slot)) ChangeStain(design, slot, other.DesignData.Stain(slot)); + } + foreach (var slot in Enum.GetValues()) + { if (other.DoApplyCrest(slot)) ChangeCrest(design, slot, other.DesignData.Crest(slot)); } @@ -556,12 +560,6 @@ public class DesignManager if (other.DoApplyStain(EquipSlot.OffHand)) ChangeStain(design, EquipSlot.OffHand, other.DesignData.Stain(EquipSlot.OffHand)); - - if (other.DoApplyCrest(EquipSlot.MainHand)) - ChangeCrest(design, EquipSlot.MainHand, other.DesignData.Crest(EquipSlot.MainHand)); - - if (other.DoApplyCrest(EquipSlot.OffHand)) - ChangeCrest(design, EquipSlot.OffHand, other.DesignData.Crest(EquipSlot.OffHand)); } public void UndoDesignChange(Design design) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 4346f01..68abd0a 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -159,8 +159,8 @@ public class DesignQuickBar : Window, IDisposable return; } - var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); - using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize); + var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); + using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest); _stateManager.ApplyDesign(design, state, StateChanged.Source.Manual); } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index e4006ad..a294b08 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -5,7 +5,6 @@ using Dalamud.Interface; using Dalamud.Interface.Internal.Notifications; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; -using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using Glamourer.Automation; using Glamourer.Customization; using Glamourer.Designs; @@ -16,6 +15,7 @@ using Glamourer.Interop; using Glamourer.Interop.Structs; using Glamourer.Services; using Glamourer.State; +using Glamourer.Structs; using ImGuiNET; using OtterGui; using OtterGui.Classes; @@ -167,21 +167,21 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus using (var _ = ImRaii.Group()) { EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.HatState, _stateManager, _state!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(EquipSlot.Head, _stateManager, _state!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Head, _stateManager, _state!)); } ImGui.SameLine(); using (var _ = ImRaii.Group()) { EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.VisorState, _stateManager, _state!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(EquipSlot.Body, _stateManager, _state!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Body, _stateManager, _state!)); } ImGui.SameLine(); using (var _ = ImRaii.Group()) { EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.WeaponState, _stateManager, _state!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(EquipSlot.OffHand, _stateManager, _state!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.OffHand, _stateManager, _state!)); } } @@ -296,8 +296,8 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus { ImGui.OpenPopup("Save as Design"); _newName = _state!.Identifier.ToName(); - var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); - _newDesign = _converter.Convert(_state, applyGear, applyCustomize); + var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); + _newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest); } private void SaveDesignDrawPopup() @@ -332,8 +332,8 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus { try { - var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); - var text = _converter.ShareBase64(_state!, applyGear, applyCustomize); + var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); + var text = _converter.ShareBase64(_state!, applyGear, applyCustomize, applyCrest); ImGui.SetClipboardText(text); } catch (Exception ex) @@ -372,9 +372,9 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus !data.Valid || id == _identifier || _state!.ModelData.ModelId != 0)) return; - var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); + var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) - _stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize), state, + _stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest), state, StateChanged.Source.Manual); } @@ -390,9 +390,9 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus !data.Valid || id == _identifier || _state!.ModelData.ModelId != 0)) return; - var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); + var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) - _stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize), state, + _stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest), state, StateChanged.Source.Manual); } } diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 918f96a..6b0e301 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -286,7 +286,7 @@ public class SetPanel var size = new Vector2(ImGui.GetFrameHeight()); size.X += ImGuiHelpers.GlobalScale; - var (equipFlags, customizeFlags, _, _, _, _) = design.ApplyWhat(); + var (equipFlags, customizeFlags, _, _, _, _, _) = design.ApplyWhat(); var sb = new StringBuilder(); foreach (var slot in EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand)) { @@ -457,7 +457,7 @@ public class SetPanel "Apply all customization changes that are enabled in this design and that are valid in a fixed design and for the given race and gender."), (AutoDesign.Type.Armor, "Apply all armor piece changes that are enabled in this design and that are valid in a fixed design."), (AutoDesign.Type.Accessories, "Apply all accessory changes that are enabled in this design and that are valid in a fixed design."), - (AutoDesign.Type.Stains, "Apply all dye changes that are enabled in this design."), + (AutoDesign.Type.GearCustomization, "Apply all dye and crest changes that are enabled in this design."), (AutoDesign.Type.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."), }; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 129b5c7..b6e5fa3 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -113,21 +113,21 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer using (var _ = ImRaii.Group()) { EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.HatState, _manager, _selector.Selected!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(EquipSlot.Head, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Head, _manager, _selector.Selected!)); } ImGui.SameLine(); using (var _ = ImRaii.Group()) { EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.VisorState, _manager, _selector.Selected!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(EquipSlot.Body, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Body, _manager, _selector.Selected!)); } ImGui.SameLine(); using (var _ = ImRaii.Group()) { EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.WeaponState, _manager, _selector.Selected!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(EquipSlot.OffHand, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, _selector.Selected!)); } } @@ -191,10 +191,9 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer var bigChange = ImGui.CheckboxFlags("Apply All Crests", ref flags, (uint)CrestExtensions.AllRelevant); foreach (var flag in CrestExtensions.AllRelevantSet) { - var slot = flag.ToSlot(); - var apply = bigChange ? ((CrestFlag)flags & flag) == flag : _selector.Selected!.DoApplyCrest(slot); + var apply = bigChange ? ((CrestFlag)flags & flag) == flag : _selector.Selected!.DoApplyCrest(flag); if (ImGui.Checkbox($"Apply {flag.ToLabel()} Crest", ref apply) || bigChange) - _manager.ChangeApplyCrest(_selector.Selected!, slot, apply); + _manager.ChangeApplyCrest(_selector.Selected!, flag, apply); } } @@ -389,8 +388,8 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); - using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize); + var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); + using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest); _state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual); } } @@ -408,8 +407,8 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); - using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize); + var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); + using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest); _state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual); } } diff --git a/Glamourer/Gui/ToggleDrawData.cs b/Glamourer/Gui/ToggleDrawData.cs index edb06ae..dda4584 100644 --- a/Glamourer/Gui/ToggleDrawData.cs +++ b/Glamourer/Gui/ToggleDrawData.cs @@ -52,10 +52,10 @@ public ref struct ToggleDrawData }; } - public static ToggleDrawData CrestFromDesign(EquipSlot slot, DesignManager manager, Design design) + public static ToggleDrawData CrestFromDesign(CrestFlag slot, DesignManager manager, Design design) => new() { - Label = $"{slot.ToCrestFlag().ToLabel()} Crest", + Label = $"{slot.ToLabel()} Crest", Tooltip = string.Empty, Locked = design.WriteProtected(), DisplayApplication = true, @@ -65,14 +65,14 @@ public ref struct ToggleDrawData SetApply = v => manager.ChangeApplyCrest(design, slot, v), }; - public static ToggleDrawData CrestFromState(EquipSlot slot, StateManager manager, ActorState state) + public static ToggleDrawData CrestFromState(CrestFlag slot, StateManager manager, ActorState state) => new() { - Label = $"{slot.ToCrestFlag().ToLabel()} Crest", + Label = $"{slot.ToLabel()} Crest", Tooltip = "Hide or show your free company crest on this piece of gear.", Locked = state.IsLocked, - CurrentValue = state.ModelData.Crest(slot), // TODO - SetValue = v => { }, //manager.ChangeCrest(state, slot, v, StateChanged.Source.Manual), + CurrentValue = state.ModelData.Crest(slot), + SetValue = v => manager.ChangeCrest(state, slot, v, StateChanged.Source.Manual), }; public static ToggleDrawData FromState(ActorState.MetaIndex index, StateManager manager, ActorState state) diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index 81aaf3c..6e83838 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -88,13 +88,13 @@ public static class UiHelpers return (currentValue != newValue, currentApply != newApply); } - public static (EquipFlag, CustomizeFlag) ConvertKeysToFlags() + public static (EquipFlag, CustomizeFlag, CrestFlag) ConvertKeysToFlags() => (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift) switch { - (false, false) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant), - (true, true) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant), - (true, false) => (EquipFlagExtensions.All, (CustomizeFlag)0), - (false, true) => ((EquipFlag)0, CustomizeFlagExtensions.AllRelevant), + (false, false) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All), + (true, true) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All), + (true, false) => (EquipFlagExtensions.All, (CustomizeFlag)0, CrestExtensions.All), + (false, true) => ((EquipFlag)0, CustomizeFlagExtensions.AllRelevant, 0), }; public static (bool, bool) ConvertKeysToBool() diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs index 253a4ea..9285ec6 100644 --- a/Glamourer/Interop/CrestService.cs +++ b/Glamourer/Interop/CrestService.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; @@ -19,11 +20,11 @@ namespace Glamourer.Interop; /// Parameter is the whether the crest will be shown. /// /// -public sealed unsafe class CrestService : EventWrapper>, CrestService.Priority>, IDisposable +public sealed unsafe class CrestService : EventWrapper>, CrestService.Priority> { public enum Priority { - /// + /// StateListener = 0, } @@ -37,19 +38,51 @@ public sealed unsafe class CrestService : EventWrapper(_weaponVTable[96], WeaponSetFreeCompanyCrestVisibleOnSlotDetour); _humanSetFreeCompanyCrestVisibleOnSlot.Enable(); _weaponSetFreeCompanyCrestVisibleOnSlot.Enable(); + _crestChangeHook.Enable(); } + public void UpdateCrests(Actor gameObject, CrestFlag flags) + { + if (!gameObject.IsCharacter) + return; + + flags &= CrestExtensions.AllRelevant; + var currentCrests = gameObject.CrestBitfield; + using var update = _inUpdate.EnterMethod(); + _crestChangeHook.Original(gameObject.AsCharacter, (byte) flags); + gameObject.CrestBitfield = currentCrests; + } + + public delegate void DrawObjectCrestUpdateDelegate(Model drawObject, CrestFlag slot, ref bool value); + + public event DrawObjectCrestUpdateDelegate? ModelCrestSetup; + protected override void Dispose(bool _) { _humanSetFreeCompanyCrestVisibleOnSlot.Dispose(); _weaponSetFreeCompanyCrestVisibleOnSlot.Dispose(); + _crestChangeHook.Dispose(); } - public void Invoke(Model model, EquipSlot slot, ref bool visible) + private delegate void CrestChangeDelegate(Character* character, byte crestFlags); + + [Signature("E8 ?? ?? ?? ?? 48 8B 55 ?? 49 8B CE E8", DetourName = nameof(CrestChangeDetour))] + private readonly Hook _crestChangeHook = null!; + + private void CrestChangeDetour(Character* character, byte crestFlags) { - var ret = new Ref(visible); - Invoke(this, model, slot, ret); - visible = ret; + var actor = (Actor)character; + foreach (var slot in CrestExtensions.AllRelevantSet) + { + var newValue = new Ref(((CrestFlag)crestFlags).HasFlag(slot)); + Invoke(this, actor, slot, newValue); + crestFlags = (byte)(newValue.Value ? crestFlags | (byte)slot : crestFlags & (byte)~slot); + } + + Glamourer.Log.Information( + $"Called CrestChange on {(ulong)character:X} with {crestFlags:X} and prior flags {((Actor)character).CrestBitfield}."); + using var _ = _inUpdate.EnterMethod(); + _crestChangeHook.Original(character, crestFlags); } public static bool GetModelCrest(Actor gameObject, CrestFlag slot) @@ -83,42 +116,9 @@ public sealed unsafe class CrestService : EventWrapper)((nint*)model.AsCharacterBase->VTable)[96]; - setter(model.AsHuman, index, crest ? (byte)1 : (byte)0); - break; - } - case CrestType.Offhand: - { - var model = (Model)gameObject.AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.OffHand).DrawObject; - if (!model.IsWeapon) - return; - - using var _ = _inUpdate.EnterMethod(); - var setter = (delegate* unmanaged)((nint*)model.AsCharacterBase->VTable)[96]; - setter(model.AsWeapon, index, crest ? (byte)1 : (byte)0); - break; - } - } - } - private readonly InMethodChecker _inUpdate = new(); - private delegate void SetCrestDelegateIntern(nint drawObject, byte slot, byte visible); + private delegate void SetCrestDelegateIntern(DrawObject* drawObject, byte slot, byte visible); [Signature(global::Penumbra.GameData.Sigs.HumanVTable, ScanType = ScanType.StaticAddress)] private readonly nint* _humanVTable = null!; @@ -129,26 +129,27 @@ public sealed unsafe class CrestService : EventWrapper _humanSetFreeCompanyCrestVisibleOnSlot; private readonly Hook _weaponSetFreeCompanyCrestVisibleOnSlot; - private void HumanSetFreeCompanyCrestVisibleOnSlotDetour(nint drawObject, byte slotIdx, byte visible) + private void HumanSetFreeCompanyCrestVisibleOnSlotDetour(DrawObject* drawObject, byte slotIdx, byte visible) { - var slot = ((uint)slotIdx).ToEquipSlot(); var rVisible = visible != 0; var inUpdate = _inUpdate.InMethod; + var slot = (CrestFlag)((ushort)CrestFlag.Head << slotIdx); if (!inUpdate) - Invoke(drawObject, slot, ref rVisible); + ModelCrestSetup?.Invoke(drawObject, slot, ref rVisible); + Glamourer.Log.Excessive( - $"[Human.SetFreeCompanyCrestVisibleOnSlot] Called with 0x{drawObject:X} for slot {slot} with {rVisible} (original: {visible != 0}, in update: {inUpdate})."); + $"[Human.SetFreeCompanyCrestVisibleOnSlot] Called with 0x{(ulong)drawObject:X} for slot {slot} with {rVisible} (original: {visible != 0}, in update: {inUpdate})."); _humanSetFreeCompanyCrestVisibleOnSlot.Original(drawObject, slotIdx, rVisible ? (byte)1 : (byte)0); } - private void WeaponSetFreeCompanyCrestVisibleOnSlotDetour(nint drawObject, byte slotIdx, byte visible) + private void WeaponSetFreeCompanyCrestVisibleOnSlotDetour(DrawObject* drawObject, byte slotIdx, byte visible) { var rVisible = visible != 0; var inUpdate = _inUpdate.InMethod; - if (!inUpdate) - Invoke(drawObject, EquipSlot.BothHand, ref rVisible); + if (!inUpdate && slotIdx == 0) + ModelCrestSetup?.Invoke(drawObject, CrestFlag.OffHand, ref rVisible); Glamourer.Log.Excessive( - $"[Weapon.SetFreeCompanyCrestVisibleOnSlot] Called with 0x{drawObject:X} with {rVisible} (original: {visible != 0}, in update: {inUpdate})."); + $"[Weapon.SetFreeCompanyCrestVisibleOnSlot] Called with 0x{(ulong)drawObject:X} with {rVisible} (original: {visible != 0}, in update: {inUpdate})."); _weaponSetFreeCompanyCrestVisibleOnSlot.Original(drawObject, slotIdx, rVisible ? (byte)1 : (byte)0); } } diff --git a/Glamourer/Interop/Structs/Actor.cs b/Glamourer/Interop/Structs/Actor.cs index 0a6196b..750527b 100644 --- a/Glamourer/Interop/Structs/Actor.cs +++ b/Glamourer/Interop/Structs/Actor.cs @@ -108,7 +108,7 @@ public readonly unsafe struct Actor : IEquatable => ((CharacterArmor*)&AsCharacter->DrawData.Head)[slot.ToIndex()]; public bool GetCrest(CrestFlag slot) - => (GetFreeCompanyCrestBitfield() & CrestMask(slot)) != 0; + => CrestBitfield.HasFlag(slot); public CharacterWeapon GetMainhand() => new(AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.MainHand).ModelId.Value); @@ -120,20 +120,8 @@ public readonly unsafe struct Actor : IEquatable => *(Customize*)&AsCharacter->DrawData.CustomizeData; // TODO remove this when available in ClientStructs - private byte GetFreeCompanyCrestBitfield() - => ((byte*)Address)[0x1BBB]; - - private static byte CrestMask(CrestFlag slot) - => slot switch - { - CrestFlag.OffHand => 0x01, - CrestFlag.Head => 0x02, - CrestFlag.Body => 0x04, - CrestFlag.Hands => 0x08, - CrestFlag.Legs => 0x10, - CrestFlag.Feet => 0x20, - _ => 0x00, - }; + internal ref CrestFlag CrestBitfield + => ref *(CrestFlag*)((byte*)Address + 0x1BBB); public override string ToString() => $"0x{Address:X}"; diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index e395f8b..7d25d82 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -5,6 +5,7 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Events; using Glamourer.Interop.Structs; +using Glamourer.Structs; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -13,6 +14,7 @@ namespace Glamourer.Interop; public unsafe class WeaponService : IDisposable { private readonly WeaponLoading _event; + private readonly CrestService _crestService; private readonly ThreadLocal _inUpdate = new(() => false); @@ -20,9 +22,10 @@ public unsafe class WeaponService : IDisposable _original; - public WeaponService(WeaponLoading @event, IGameInteropProvider interop) + public WeaponService(WeaponLoading @event, IGameInteropProvider interop, CrestService crestService) { - _event = @event; + _event = @event; + _crestService = crestService; _loadWeaponHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadWeapon, LoadWeaponDetour); _original = @@ -69,6 +72,7 @@ public unsafe class WeaponService : IDisposable _event.Invoke(actor, EquipSlot.MainHand, ref tmpWeapon); _loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4); + if (tmpWeapon.Value != weapon.Value) { if (tmpWeapon.Set.Id == 0) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 6435568..253442f 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -165,7 +165,7 @@ public class CommandService : IDisposable .AddInitialPurple("Customizations, ") .AddInitialPurple("Equipment, ") .AddInitialPurple("Accessories, ") - .AddInitialPurple("Dyes and ") + .AddInitialPurple("Dyes & Crests and ") .AddInitialPurple("Weapons, where ").AddPurple("CEADW") .AddText(" means everything should be toggled on, and no value means nothing should be toggled on.") .BuiltString); @@ -268,7 +268,7 @@ public class CommandService : IDisposable applicationFlags |= AutoDesign.Type.Accessories; break; case 'd': - applicationFlags |= AutoDesign.Type.Stains; + applicationFlags |= AutoDesign.Type.GearCustomization; break; case 'w': applicationFlags |= AutoDesign.Type.Weapons; @@ -472,7 +472,7 @@ public class CommandService : IDisposable && _stateManager.GetOrCreate(identifier, data.Objects[0], out state))) continue; - var design = _converter.Convert(state, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant); + var design = _converter.Convert(state, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All); _designManager.CreateClone(design, split[0], true); return true; } diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index a32449e..5eaf672 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -1,13 +1,12 @@ using System.Linq; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Customization; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.Services; +using Glamourer.Structs; using Penumbra.Api.Enums; -using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -17,30 +16,9 @@ namespace Glamourer.State; /// This class applies changes made to state to actual objects in the game. /// It handles applying those changes as well as redrawing the actor if necessary. /// -public class StateApplier +public class StateApplier(UpdateSlotService _updateSlot, VisorService _visor, WeaponService _weapon, ChangeCustomizeService _changeCustomize, + ItemManager _items, PenumbraService _penumbra, MetaService _metaService, ObjectManager _objects, CrestService _crests) { - private readonly PenumbraService _penumbra; - private readonly UpdateSlotService _updateSlot; - private readonly VisorService _visor; - private readonly WeaponService _weapon; - private readonly MetaService _metaService; - private readonly ChangeCustomizeService _changeCustomize; - private readonly ItemManager _items; - private readonly ObjectManager _objects; - - public StateApplier(UpdateSlotService updateSlot, VisorService visor, WeaponService weapon, ChangeCustomizeService changeCustomize, - ItemManager items, PenumbraService penumbra, MetaService metaService, ObjectManager objects) - { - _updateSlot = updateSlot; - _visor = visor; - _weapon = weapon; - _changeCustomize = changeCustomize; - _items = items; - _penumbra = penumbra; - _metaService = metaService; - _objects = objects; - } - /// Simply force a redraw regardless of conditions. public void ForceRedraw(ActorData data) { @@ -279,6 +257,22 @@ public class StateApplier return data; } + /// Change the crest state on actors. + public void ChangeCrests(ActorData data, CrestFlag flags) + { + foreach (var actor in data.Objects.Where(a => a.IsCharacter)) + _crests.UpdateCrests(actor, flags); + } + + /// + public ActorData ChangeCrests(ActorState state, bool apply) + { + var data = GetData(state); + if (apply) + ChangeCrests(data, state.ModelData.CrestVisibility); + return data; + } + private ActorData GetData(ActorState state) { _objects.Update(); diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 4bd39b4..3b9d0cb 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -4,6 +4,7 @@ using Dalamud.Plugin.Services; using Glamourer.Customization; using Glamourer.Events; using Glamourer.Services; +using Glamourer.Structs; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -197,6 +198,18 @@ public class StateEditor return true; } + /// Change the crest of an equipment piece. + public bool ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateChanged.Source source, out bool oldCrest, uint key = 0) + { + oldCrest = state.ModelData.Crest(slot); + if (!state.CanUnlock(key)) + return false; + + state.ModelData.SetCrest(slot, crest); + state[slot] = source; + return true; + } + public bool ChangeMetaState(ActorState state, ActorState.MetaIndex index, bool value, StateChanged.Source source, out bool oldValue, uint key = 0) { diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 3cef72a..ae6de2f 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -13,6 +13,7 @@ using Penumbra.GameData.Structs; using System; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; +using Glamourer.Structs; namespace Glamourer.State; @@ -42,6 +43,7 @@ public class StateListener : IDisposable private readonly MovedEquipment _movedEquipment; private readonly GPoseService _gPose; private readonly ChangeCustomizeService _changeCustomizeService; + private readonly CrestService _crestService; private readonly ICondition _condition; private ActorIdentifier _creatingIdentifier = ActorIdentifier.Invalid; @@ -52,7 +54,7 @@ public class StateListener : IDisposable SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gPose, - ChangeCustomizeService changeCustomizeService, CustomizationService customizations, ICondition condition) + ChangeCustomizeService changeCustomizeService, CustomizationService customizations, ICondition condition, CrestService crestService) { _manager = manager; _items = items; @@ -74,6 +76,7 @@ public class StateListener : IDisposable _changeCustomizeService = changeCustomizeService; _customizations = customizations; _condition = condition; + _crestService = crestService; Subscribe(); } @@ -405,6 +408,58 @@ public class StateListener : IDisposable } } + private void OnCrestChange(Actor actor, CrestFlag slot, Ref value) + { + if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + return; + + if (!actor.Identifier(_actors.AwaitedService, out var identifier) + || !_manager.TryGetValue(identifier, out var state)) + return; + + switch (UpdateBaseCrest(actor, state, slot, value.Value)) + { + case UpdateState.Change: + if (state[slot] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) + _manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), StateChanged.Source.Game); + else + value.Value = state.ModelData.Crest(slot); + break; + case UpdateState.NoChange: + case UpdateState.HatHack: + value.Value = state.ModelData.Crest(slot); + break; + case UpdateState.Transformed: break; + } + } + + private void OnModelCrestSetup(Model model, CrestFlag slot, ref bool value) + { + var actor = _penumbra.GameObjectFromDrawObject(model); + if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + return; + + if (!actor.Identifier(_actors.AwaitedService, out var identifier) + || !_manager.TryGetValue(identifier, out var state)) + return; + + value = state.ModelData.Crest(slot); + } + + private static UpdateState UpdateBaseCrest(Actor actor, ActorState state, CrestFlag slot, bool visible) + { + if (actor.IsTransformed) + return UpdateState.Transformed; + + if (state.BaseData.Crest(slot) != visible) + { + state.BaseData.SetCrest(slot, visible); + return UpdateState.Change; + } + + return UpdateState.NoChange; + } + /// Update base data for a single changed weapon slot. private unsafe UpdateState UpdateBaseData(Actor actor, ActorState state, EquipSlot slot, CharacterWeapon weapon) { @@ -616,6 +671,8 @@ public class StateListener : IDisposable _headGearVisibility.Subscribe(OnHeadGearVisibilityChange, HeadGearVisibilityChanged.Priority.StateListener); _weaponVisibility.Subscribe(OnWeaponVisibilityChange, WeaponVisibilityChanged.Priority.StateListener); _changeCustomizeService.Subscribe(OnCustomizeChange, ChangeCustomizeService.Priority.StateListener); + _crestService.Subscribe(OnCrestChange, CrestService.Priority.StateListener); + _crestService.ModelCrestSetup += OnModelCrestSetup; } private void Unsubscribe() @@ -629,6 +686,8 @@ public class StateListener : IDisposable _headGearVisibility.Unsubscribe(OnHeadGearVisibilityChange); _weaponVisibility.Unsubscribe(OnWeaponVisibilityChange); _changeCustomizeService.Unsubscribe(OnCustomizeChange); + _crestService.Unsubscribe(OnCrestChange); + _crestService.ModelCrestSetup -= OnModelCrestSetup; } private void OnCreatedCharacterBase(nint gameObject, string _, nint drawObject) @@ -639,8 +698,9 @@ public class StateListener : IDisposable if (_creatingState == null) return; - _applier.ChangeHatState(new ActorData(gameObject, _creatingIdentifier.ToName()), _creatingState.ModelData.IsHatVisible()); - _applier.ChangeWeaponState(new ActorData(gameObject, _creatingIdentifier.ToName()), _creatingState.ModelData.IsWeaponVisible()); - _applier.ChangeWetness(new ActorData(gameObject, _creatingIdentifier.ToName()), _creatingState.ModelData.IsWet()); + var data = new ActorData(gameObject, _creatingIdentifier.ToName()); + _applier.ChangeHatState(data, _creatingState.ModelData.IsHatVisible()); + _applier.ChangeWeaponState(data, _creatingState.ModelData.IsWeaponVisible()); + _applier.ChangeWetness(data, _creatingState.ModelData.IsWet()); } } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 5157c60..68c9cfc 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -10,6 +10,7 @@ using Glamourer.Events; using Glamourer.Interop; using Glamourer.Interop.Structs; using Glamourer.Services; +using Glamourer.Structs; using Penumbra.GameData.Actors; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; @@ -17,32 +18,12 @@ using Penumbra.GameData.Structs; namespace Glamourer.State; -public class StateManager : IReadOnlyDictionary +public class StateManager(ActorService _actors, ItemManager _items, StateChanged _event, StateApplier _applier, StateEditor _editor, + HumanModelList _humans, ICondition _condition, IClientState _clientState) + : IReadOnlyDictionary { - private readonly ActorService _actors; - private readonly ItemManager _items; - private readonly HumanModelList _humans; - private readonly StateChanged _event; - private readonly StateApplier _applier; - private readonly StateEditor _editor; - private readonly ICondition _condition; - private readonly IClientState _clientState; - private readonly Dictionary _states = new(); - public StateManager(ActorService actors, ItemManager items, StateChanged @event, StateApplier applier, StateEditor editor, - HumanModelList humans, ICondition condition, IClientState clientState) - { - _actors = actors; - _items = items; - _event = @event; - _applier = applier; - _editor = editor; - _humans = humans; - _condition = condition; - _clientState = clientState; - } - public IEnumerator> GetEnumerator() => _states.GetEnumerator(); @@ -83,7 +64,7 @@ public class StateManager : IReadOnlyDictionary // and the draw objects data for the model data (where possible). state = new ActorState(identifier) { - ModelData = FromActor(actor, true, false), + ModelData = FromActor(actor, true, false), BaseData = FromActor(actor, false, false), LastJob = (byte)(actor.IsCharacter ? actor.AsCharacter->CharacterData.ClassJob : 0), LastTerritory = _clientState.TerritoryType, @@ -162,6 +143,9 @@ public class StateManager : IReadOnlyDictionary // Visor state is a flag on the game object, but we can see the actual state on the draw object. ret.SetVisor(VisorService.GetVisorState(model)); + + foreach (var slot in CrestExtensions.AllRelevantSet) + ret.SetCrest(slot, CrestService.GetModelCrest(actor, slot)); } else { @@ -180,6 +164,9 @@ public class StateManager : IReadOnlyDictionary off = actor.GetOffhand(); FistWeaponHack(ref ret, ref main, ref off); ret.SetVisor(actor.AsCharacter->DrawData.IsVisorToggled); + + foreach (var slot in CrestExtensions.AllRelevantSet) + ret.SetCrest(slot, actor.GetCrest(slot)); } // Set the weapons regardless of source. @@ -206,7 +193,7 @@ public class StateManager : IReadOnlyDictionary if (mainhand.Set.Id is < 1601 or >= 1651) return; - var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Set, (Variant) offhand.Type.Id); + var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Set, (Variant)offhand.Type.Id); offhand.Set = (SetId)(mainhand.Set.Id + 50); offhand.Variant = mainhand.Variant; offhand.Type = mainhand.Type; @@ -304,6 +291,18 @@ public class StateManager : IReadOnlyDictionary _event.Invoke(StateChanged.Type.Stain, source, state, actors, (old, stain, slot)); } + /// Change the crest of an equipment piece. + public void ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateChanged.Source source, uint key = 0) + { + if (!_editor.ChangeCrest(state, slot, crest, source, out var old, key)) + return; + + var actors = _applier.ChangeCrests(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); + Glamourer.Log.Verbose( + $"Set {slot.ToLabel()} crest in state {state.Identifier.Incognito(null)} from {old} to {crest}. [Affecting {actors.ToLazyString("nothing")}.]"); + _event.Invoke(StateChanged.Type.Crest, source, state, actors, (old, crest, slot)); + } + /// Change hat visibility. public void ChangeHatState(ActorState state, bool value, StateChanged.Source source, uint key = 0) { @@ -356,19 +355,8 @@ public class StateManager : IReadOnlyDictionary public void ApplyDesign(DesignBase design, ActorState state, StateChanged.Source source, uint key = 0) { - void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain) - { - var unused = (applyPiece, applyStain) switch - { - (false, false) => false, - (true, false) => _editor.ChangeItem(state, slot, design.DesignData.Item(slot), source, out _, key), - (false, true) => _editor.ChangeStain(state, slot, design.DesignData.Stain(slot), source, out _, key), - (true, true) => _editor.ChangeEquip(state, slot, design.DesignData.Item(slot), design.DesignData.Stain(slot), source, out _, - out _, key), - }; - } - - if (!_editor.ChangeModelId(state, design.DesignData.ModelId, design.DesignData.Customize, design.GetDesignDataRef().GetEquipmentPtr(), source, + if (!_editor.ChangeModelId(state, design.DesignData.ModelId, design.DesignData.Customize, design.GetDesignDataRef().GetEquipmentPtr(), + source, out var oldModelId, key)) return; @@ -393,12 +381,28 @@ public class StateManager : IReadOnlyDictionary foreach (var slot in EquipSlotExtensions.FullSlots) HandleEquip(slot, design.DoApplyEquip(slot), design.DoApplyStain(slot)); + + foreach (var slot in CrestExtensions.AllRelevantSet.Where(design.DoApplyCrest)) + _editor.ChangeCrest(state, slot, design.DesignData.Crest(slot), source, out _, key); } var actors = ApplyAll(state, redraw, false); Glamourer.Log.Verbose( $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); _event.Invoke(StateChanged.Type.Design, state[ActorState.MetaIndex.Wetness], state, actors, design); + return; + + void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain) + { + var unused = (applyPiece, applyStain) switch + { + (false, false) => false, + (true, false) => _editor.ChangeItem(state, slot, design.DesignData.Item(slot), source, out _, key), + (false, true) => _editor.ChangeStain(state, slot, design.DesignData.Stain(slot), source, out _, key), + (true, true) => _editor.ChangeEquip(state, slot, design.DesignData.Item(slot), design.DesignData.Stain(slot), source, out _, + out _, key), + }; + } } private ActorData ApplyAll(ActorState state, bool redraw, bool withLock) @@ -430,6 +434,7 @@ public class StateManager : IReadOnlyDictionary _applier.ChangeHatState(actors, state.ModelData.IsHatVisible()); _applier.ChangeWeaponState(actors, state.ModelData.IsWeaponVisible()); _applier.ChangeVisor(actors, state.ModelData.IsVisorToggled()); + _applier.ChangeCrests(actors, state.ModelData.CrestVisibility); } return actors; @@ -453,10 +458,13 @@ public class StateManager : IReadOnlyDictionary state[slot, true] = StateChanged.Source.Game; state[slot, false] = StateChanged.Source.Game; } - + foreach (var type in Enum.GetValues()) state[type] = StateChanged.Source.Game; + foreach (var slot in CrestExtensions.AllRelevantSet) + state[slot] = StateChanged.Source.Game; + var actors = ActorData.Invalid; if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc) actors = ApplyAll(state, redraw, true); @@ -491,6 +499,15 @@ public class StateManager : IReadOnlyDictionary } } + foreach (var slot in CrestExtensions.AllRelevantSet) + { + if (state[slot] is StateChanged.Source.Fixed) + { + state[slot] = StateChanged.Source.Game; + state.ModelData.SetCrest(slot, state.BaseData.Crest(slot)); + } + } + if (state[ActorState.MetaIndex.HatState] is StateChanged.Source.Fixed) { state[ActorState.MetaIndex.HatState] = StateChanged.Source.Game; From 11ab85545f2dfdbbba2cafc834fde39873e1024a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 2 Dec 2023 17:34:28 +0100 Subject: [PATCH 20/20] Some Stuff --- Glamourer/Designs/DesignConverter.cs | 11 +---------- Glamourer/Gui/Tabs/DebugTab.cs | 12 ++++++++++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index cd6db7e..6ab6901 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -111,16 +111,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi : DesignBase.LoadDesignBase(_customize, _items, jObj2); break; } - case Version: - { - version = bytes.DecompressToString(out var decompressed); - var jObj2 = JObject.Parse(decompressed); - Debug.Assert(version == Version); - ret = jObj2["Identifier"] != null - ? Design.LoadDesign(_customize, _items, jObj2) - : DesignBase.LoadDesignBase(_customize, _items, jObj2); - break; - } + default: throw new Exception($"Unknown Version {bytes[0]}."); } } diff --git a/Glamourer/Gui/Tabs/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab.cs index 52385b8..ad1d46f 100644 --- a/Glamourer/Gui/Tabs/DebugTab.cs +++ b/Glamourer/Gui/Tabs/DebugTab.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Numerics; +using System.Runtime.CompilerServices; using System.Text; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Interface; @@ -483,21 +484,28 @@ public unsafe class DebugTab : ITab private void DrawCrests(Actor actor, Model model) { - using var id = ImRaii.PushId("Crests"); + using var id = ImRaii.PushId("Crests"); + CrestFlag whichToggle = 0; + CrestFlag totalModelFlags = 0; foreach (var crestFlag in CrestExtensions.AllRelevantSet) { id.Push((int)crestFlag); var modelCrest = CrestService.GetModelCrest(actor, crestFlag); + if (modelCrest) + totalModelFlags |= crestFlag; ImGuiUtil.DrawTableColumn($"{crestFlag.ToLabel()} Crest"); ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.GetCrest(crestFlag).ToString() : "No Character"); ImGuiUtil.DrawTableColumn(modelCrest.ToString()); ImGui.TableNextColumn(); if (model.IsHuman && ImGui.SmallButton("Toggle")) - _crestService.UpdateCrest(actor, crestFlag, !modelCrest); + whichToggle = crestFlag; id.Pop(); } + + if (whichToggle != 0) + _crestService.UpdateCrests(actor, totalModelFlags ^ whichToggle); } #endregion