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();