diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index 77f4533..f32e22b 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -27,6 +27,9 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) public readonly void SetStains(StainIds stains) => _editor.ChangeStains(_object, Slot, stains, ApplySettings.Manual); + public readonly void SetStain(int which, StainId stain) + => _editor.ChangeStains(_object, Slot, CurrentStains.With(which, stain), ApplySettings.Manual); + public readonly void SetApplyItem(bool value) { var manager = (DesignManager)_editor; diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 0d2e8dc..f2ecc08 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -3,16 +3,15 @@ using Dalamud.Interface.Utility; using Dalamud.Plugin.Services; using Glamourer.Events; using Glamourer.Gui.Materials; -using Glamourer.Interop.Material; using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; -using OtterGui; using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; using OtterGui.Text.EndObjects; using OtterGui.Widgets; +using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -33,6 +32,7 @@ public class EquipmentDrawer private readonly Configuration _config; private readonly GPoseService _gPose; private readonly AdvancedDyePopup _advancedDyes; + private readonly ItemCopyService _itemCopy; private float _requiredComboWidthUnscaled; private float _requiredComboWidth; @@ -40,13 +40,14 @@ public class EquipmentDrawer private Stain? _draggedStain; public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, TextureService textures, - Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes) + Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes, ItemCopyService itemCopy) { _items = items; _textures = textures; _config = config; _gPose = gPose; _advancedDyes = advancedDyes; + _itemCopy = itemCopy; _stainData = items.Stains; _stainCombo = new GlamourerColorCombo(DefaultWidth - 20, _stainData, favorites); _itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, Glamourer.Log, favorites)).ToArray(); @@ -184,6 +185,7 @@ public class EquipmentDrawer return change; } + #region Small private void DrawEquipSmall(in EquipDrawData equipDrawData) @@ -402,6 +404,7 @@ public class EquipmentDrawer ? _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss) : _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss, width); + _itemCopy.HandleCopyPaste(data, index); if (!change) DrawStainDragDrop(data, index, stain, found); @@ -456,6 +459,7 @@ public class EquipmentDrawer data.SetItem(combo.CurrentSelection); else if (combo.CustomVariant.Id > 0) data.SetItem(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant)); + _itemCopy.HandleCopyPaste(data); if (ResetOrClear(data.Locked, clear, data.AllowRevert, true, data.CurrentItem, data.GameItem, ItemManager.NothingItem(data.Slot), out var item)) @@ -473,6 +477,14 @@ public class EquipmentDrawer var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.Id.BonusItem, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth); + if (ImGui.IsItemHovered() && ImGui.GetIO().KeyCtrl) + { + if (ImGui.IsKeyPressed(ImGuiKey.C)) + _itemCopy.Copy(combo.CurrentSelection); + else if (ImGui.IsKeyPressed(ImGuiKey.V)) + _itemCopy.Paste(data.Slot.ToEquipType(), data.SetItem); + } + if (change) data.SetItem(combo.CurrentSelection); else if (combo.CustomVariant.Id > 0) @@ -531,8 +543,12 @@ public class EquipmentDrawer if (combo.Draw(mainhand.CurrentItem.Name, mainhand.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth)) changedItem = combo.CurrentSelection; - else if (ResetOrClear(mainhand.Locked || unknown, open, mainhand.AllowRevert, false, mainhand.CurrentItem, mainhand.GameItem, - default, out var c)) + else if (combo.CustomVariant.Id > 0 && (drawAll || ItemData.ConvertWeaponId(combo.CustomSetId) == mainhand.CurrentItem.Type)) + changedItem = _items.Identify(mainhand.Slot, combo.CustomSetId, combo.CustomWeaponId, combo.CustomVariant); + _itemCopy.HandleCopyPaste(mainhand); + + if (ResetOrClear(mainhand.Locked || unknown, open, mainhand.AllowRevert, false, mainhand.CurrentItem, mainhand.GameItem, + default, out var c)) changedItem = c; if (changedItem != null) @@ -549,7 +565,8 @@ public class EquipmentDrawer } if (unknown) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, "The weapon type could not be identified, thus changing it to other weapons of that type is not possible."u8); + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, + "The weapon type could not be identified, thus changing it to other weapons of that type is not possible."u8); } private void DrawOffhand(in EquipDrawData mainhand, in EquipDrawData offhand, out string label, bool small, bool clear, bool open) @@ -569,6 +586,9 @@ public class EquipmentDrawer if (combo.Draw(offhand.CurrentItem.Name, offhand.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth)) offhand.SetItem(combo.CurrentSelection); + else if (combo.CustomVariant.Id > 0 && ItemData.ConvertWeaponId(combo.CustomSetId) == offhand.CurrentItem.Type) + offhand.SetItem(_items.Identify(mainhand.Slot, combo.CustomSetId, combo.CustomWeaponId, combo.CustomVariant)); + _itemCopy.HandleCopyPaste(offhand); var defaultOffhand = _items.GetDefaultOffhand(mainhand.CurrentItem); if (ResetOrClear(locked, clear, offhand.AllowRevert, true, offhand.CurrentItem, offhand.GameItem, defaultOffhand, out var item)) @@ -623,6 +643,7 @@ public class EquipmentDrawer { ImUtf8.Text(label); } + if (hasAdvancedDyes) ImUtf8.HoverTooltip("This design has advanced dyes setup for this slot."u8); } diff --git a/Glamourer/Gui/Equipment/ItemCopyService.cs b/Glamourer/Gui/Equipment/ItemCopyService.cs new file mode 100644 index 0000000..e72a54b --- /dev/null +++ b/Glamourer/Gui/Equipment/ItemCopyService.cs @@ -0,0 +1,78 @@ +using Dalamud.Game.ClientState.Keys; +using Dalamud.Plugin.Services; +using Glamourer.Services; +using ImGuiNET; +using OtterGui.OtterGuiInternal.Enums; +using OtterGui.Services; +using OtterGuiInternal; +using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Gui.Equipment; + +public class ItemCopyService(ItemManager items, IKeyState keyState, DictStain stainData) : IUiService +{ + public EquipItem? Item { get; private set; } + public Stain? Stain { get; private set; } + + public void Copy(in EquipItem item) + => Item = item; + + public void Copy(in Stain stain) + => Stain = stain; + + public void Paste(int which, Action setter) + { + if (Stain is { } stain) + setter(which, stain.RowIndex); + } + + public void Paste(FullEquipType type, Action setter) + { + if (Item is not { } item) + return; + + if (type != item.Type) + { + if (type.IsBonus()) + item = items.Identify(type.ToBonus(), item.PrimaryId, item.Variant); + else if (type.IsEquipment() || type.IsAccessory()) + item = items.Identify(type.ToSlot(), item.PrimaryId, item.Variant); + else + item = items.Identify(type.ToSlot(), item.PrimaryId, item.SecondaryId, item.Variant); + } + + if (item.Valid && item.Type == type) + setter(item); + } + + public void HandleCopyPaste(in EquipDrawData data) + { + if (ImGui.GetIO().KeyCtrl) + { + if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(ImGuiMouseButton.Middle)) + Paste(data.CurrentItem.Type, data.SetItem); + } + else if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled) && ImGui.IsMouseClicked(ImGuiMouseButton.Middle)) + { + Copy(data.CurrentItem); + } + } + + public void HandleCopyPaste(in EquipDrawData data, int which) + { + if (ImGui.GetIO().KeyCtrl) + { + if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(ImGuiMouseButton.Middle)) + Paste(which, data.SetStain); + } + else if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled) + && ImGui.IsMouseClicked(ImGuiMouseButton.Middle) + && stainData.TryGetValue(data.CurrentStains[which].Id, out var stain)) + { + Copy(stain); + } + } +} diff --git a/Glamourer/Gui/Equipment/WeaponCombo.cs b/Glamourer/Gui/Equipment/WeaponCombo.cs index e96f721..6e38e7c 100644 --- a/Glamourer/Gui/Equipment/WeaponCombo.cs +++ b/Glamourer/Gui/Equipment/WeaponCombo.cs @@ -1,7 +1,6 @@ using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; -using OtterGui; using OtterGui.Classes; using OtterGui.Extensions; using OtterGui.Log; @@ -20,6 +19,10 @@ public sealed class WeaponCombo : FilterComboCache private ItemId _currentItem; private float _innerWidth; + public PrimaryId CustomSetId { get; private set; } + public SecondaryId CustomWeaponId { get; private set; } + public Variant CustomVariant { get; private set; } + public WeaponCombo(ItemManager items, FullEquipType type, Logger log, FavoriteManager favorites) : base(() => GetWeapons(favorites, items, type), MouseWheelType.Control, log) { @@ -47,8 +50,9 @@ public sealed class WeaponCombo : 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()); } @@ -75,6 +79,24 @@ public sealed class WeaponCombo : FilterComboCache return ret; } + protected override void OnClosePopup() + { + // If holding control while the popup closes, try to parse the input as a full tuple of set id, weapon 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 != 3 + || !ushort.TryParse(split[0], out var setId) + || !ushort.TryParse(split[1], out var weaponId) + || !byte.TryParse(split[2], out var variant)) + return; + + CustomSetId = setId; + CustomWeaponId = weaponId; + CustomVariant = variant; + } + protected override bool IsVisible(int globalIndex, LowerString filter) => base.IsVisible(globalIndex, filter) || Items[globalIndex].ModelString.StartsWith(filter.Lower); diff --git a/Penumbra.GameData b/Penumbra.GameData index 002260d..1019b56 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 002260d9815e571f1496c50374f5b712818e9880 +Subproject commit 1019b56de3b7ab2a6a1aefd699f9a507323e92fc