Add experimental automation condition for gearsets.

This commit is contained in:
Ottermandias 2023-11-24 15:42:55 +01:00
parent c11bd629da
commit dd42b7ab7f
7 changed files with 185 additions and 32 deletions

View file

@ -27,6 +27,7 @@ public class AutoDesign
public Design? Design; public Design? Design;
public JobGroup Jobs; public JobGroup Jobs;
public Type ApplicationType; public Type ApplicationType;
public short GearsetIndex = -1;
public string Name(bool incognito) public string Name(bool incognito)
=> Revert ? RevertName : incognito ? Design!.Incognito : Design!.Name.Text; => Revert ? RevertName : incognito ? Design!.Incognito : Design!.Name.Text;
@ -43,10 +44,22 @@ public class AutoDesign
Design = Design, Design = Design,
ApplicationType = ApplicationType, ApplicationType = ApplicationType,
Jobs = Jobs, Jobs = Jobs,
GearsetIndex = GearsetIndex,
}; };
public unsafe bool IsActive(Actor actor) 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() public JObject Serialize()
=> new() => new()
@ -58,9 +71,12 @@ public class AutoDesign
private JObject CreateConditionObject() private JObject CreateConditionObject()
{ {
var ret = new JObject(); var ret = new JObject
if (Jobs.Id != 0) {
ret["JobGroup"] = Jobs.Id; ["Gearset"] = GearsetIndex,
["JobGroup"] = Jobs.Id,
};
return ret; return ret;
} }

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using Glamourer.Customization; using Glamourer.Customization;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Events; using Glamourer.Events;
@ -26,6 +27,7 @@ public class AutoDesignApplier : IDisposable
private readonly AutoDesignManager _manager; private readonly AutoDesignManager _manager;
private readonly StateManager _state; private readonly StateManager _state;
private readonly JobService _jobs; private readonly JobService _jobs;
private readonly EquippedGearset _equippedGearset;
private readonly ActorService _actors; private readonly ActorService _actors;
private readonly CustomizationService _customizations; private readonly CustomizationService _customizations;
private readonly CustomizeUnlockManager _customizeUnlocks; private readonly CustomizeUnlockManager _customizeUnlocks;
@ -49,7 +51,8 @@ public class AutoDesignApplier : IDisposable
public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs,
CustomizationService customizations, ActorService actors, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks, 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; _config = config;
_manager = manager; _manager = manager;
@ -64,15 +67,18 @@ public class AutoDesignApplier : IDisposable
_weapons = weapons; _weapons = weapons;
_humans = humans; _humans = humans;
_clientState = clientState; _clientState = clientState;
_equippedGearset = equippedGearset;
_jobs.JobChanged += OnJobChange; _jobs.JobChanged += OnJobChange;
_event.Subscribe(OnAutomationChange, AutomationChanged.Priority.AutoDesignApplier); _event.Subscribe(OnAutomationChange, AutomationChanged.Priority.AutoDesignApplier);
_weapons.Subscribe(OnWeaponLoading, WeaponLoading.Priority.AutoDesignApplier); _weapons.Subscribe(OnWeaponLoading, WeaponLoading.Priority.AutoDesignApplier);
_equippedGearset.Subscribe(OnEquippedGearset, EquippedGearset.Priority.AutoDesignApplier);
} }
public void Dispose() public void Dispose()
{ {
_weapons.Unsubscribe(OnWeaponLoading); _weapons.Unsubscribe(OnWeaponLoading);
_event.Unsubscribe(OnAutomationChange); _event.Unsubscribe(OnAutomationChange);
_equippedGearset.Unsubscribe(OnEquippedGearset);
_jobs.JobChanged -= OnJobChange; _jobs.JobChanged -= OnJobChange;
} }
@ -496,4 +502,38 @@ public class AutoDesignApplier : IDisposable
totalMetaFlags |= 0x08; 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;
}
} }

View file

@ -306,6 +306,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
return; return;
var design = set.Designs[which]; var design = set.Designs[which];
if (design.Jobs.Id == jobs.Id) if (design.Jobs.Id == jobs.Id)
return; return;
@ -316,6 +317,22 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
_event.Invoke(AutomationChanged.Type.ChangedConditions, set, (which, old, jobs)); _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) public void ChangeApplicationType(AutoDesignSet set, int which, AutoDesign.Type type)
{ {
if (which >= set.Designs.Count || which < 0) if (which >= set.Designs.Count || which < 0)
@ -338,10 +355,8 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
public void Save(StreamWriter writer) public void Save(StreamWriter writer)
{ {
using var j = new JsonTextWriter(writer) using var j = new JsonTextWriter(writer);
{ j.Formatting = Formatting.Indented;
Formatting = Formatting.Indented,
};
Serialize().WriteTo(j); Serialize().WriteTo(j);
} }
@ -456,13 +471,16 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
{ {
if (designIdentifier.Length == 0) 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; return null;
} }
if (!Guid.TryParse(designIdentifier, out var guid)) 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; return null;
} }
@ -471,7 +489,8 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
if (design == null) if (design == null)
{ {
Glamourer.Messager.NotificationMessage( 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; return null;
} }
} }
@ -483,24 +502,31 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
Design = design, Design = design,
ApplicationType = applicationType & AutoDesign.Type.All, 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"]; var conditions = jObj["Conditions"];
if (conditions == null) if (conditions == null)
return ret; return true;
var jobs = conditions["JobGroup"]?.ToObject<int>() ?? -1; var jobs = conditions["JobGroup"]?.ToObject<int>() ?? -1;
if (jobs >= 0) if (jobs >= 0)
{ {
if (!_jobs.JobGroups.TryGetValue((ushort)jobs, out var jobGroup)) 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); Glamourer.Messager.NotificationMessage(
return null; $"Error parsing automatically applied design for set {setName}: The job condition {jobs} does not exist.",
NotificationType.Warning);
return false;
} }
ret.Jobs = jobGroup; ret.Jobs = jobGroup;
} }
return ret; ret.GearsetIndex = conditions["Gearset"]?.ToObject<short>() ?? -1;
return true;
} }
private void Save() private void Save()

View file

@ -0,0 +1,30 @@
using System;
using OtterGui.Classes;
namespace Glamourer.Events;
/// <summary>
/// Triggered when the player equips a gear set.
/// <list type="number">
/// <item>Parameter is the name of the gear set. </item>
/// <item>Parameter is the id of the gear set. </item>
/// <item>Parameter is the id of the prior gear set. </item>
/// <item>Parameter is the id of the associated glamour. </item>
/// <item>Parameter is the job id of the associated job. </item>
/// </list>
/// </summary>
public sealed class EquippedGearset : EventWrapper<Action<string, int, int, byte, byte>, EquippedGearset.Priority>
{
public enum Priority
{
/// <seealso cref="Automation.AutoDesignApplier.OnEquippedGearset"/>
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);
}

View file

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Text; using System.Text;
@ -29,17 +30,18 @@ public class SetPanel
private readonly CustomizeUnlockManager _customizeUnlocks; private readonly CustomizeUnlockManager _customizeUnlocks;
private readonly CustomizationService _customizations; private readonly CustomizationService _customizations;
private readonly Configuration _config; private readonly Configuration _config;
private readonly RevertDesignCombo _designCombo; private readonly RevertDesignCombo _designCombo;
private readonly JobGroupCombo _jobGroupCombo; private readonly JobGroupCombo _jobGroupCombo;
private readonly IdentifierDrawer _identifierDrawer; private readonly IdentifierDrawer _identifierDrawer;
private string? _tempName; private string? _tempName;
private int _dragIndex = -1; private int _dragIndex = -1;
private Action? _endAction; 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) CustomizeUnlockManager customizeUnlocks, CustomizationService customizations, IdentifierDrawer identifierDrawer, Configuration config)
{ {
_selector = selector; _selector = selector;
@ -216,11 +218,11 @@ public class SetPanel
ImGui.TableNextColumn(); ImGui.TableNextColumn();
DrawApplicationTypeBoxes(Selection, design, idx, singleRow); DrawApplicationTypeBoxes(Selection, design, idx, singleRow);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
_jobGroupCombo.Draw(Selection, design, idx); DrawConditions(design, idx);
} }
else else
{ {
_jobGroupCombo.Draw(Selection, design, idx); DrawConditions(design, idx);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
DrawApplicationTypeBoxes(Selection, design, idx, singleRow); DrawApplicationTypeBoxes(Selection, design, idx, singleRow);
} }
@ -244,6 +246,38 @@ public class SetPanel
_endAction = null; _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) private void DrawWarnings(AutoDesign design, int idx)
{ {
if (design.Revert) if (design.Revert)

View file

@ -7,17 +7,20 @@ using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using Glamourer.Events; using Glamourer.Events;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using Penumbra.String;
namespace Glamourer.Interop; namespace Glamourer.Interop;
public unsafe class InventoryService : IDisposable 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); 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<MoveItemDelegate>((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour); _moveItemHook = interop.HookFromAddress<MoveItemDelegate>((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour);
_equipGearsetHook = _equipGearsetHook =
@ -39,7 +42,10 @@ public unsafe class InventoryService : IDisposable
private int EquipGearSetDetour(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId) 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})"); Glamourer.Log.Excessive($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})");
if (ret == 0) if (ret == 0)
{ {
@ -107,7 +113,7 @@ public unsafe class InventoryService : IDisposable
Add(EquipSlot.LFinger, ref entry->RingLeft); Add(EquipSlot.LFinger, ref entry->RingLeft);
} }
_event.Invoke(_itemList.ToArray()); _movedItemsEvent.Invoke(_itemList.ToArray());
} }
return ret; return ret;
@ -127,18 +133,18 @@ public unsafe class InventoryService : IDisposable
{ {
if (InvokeSource(sourceContainer, sourceSlot, out var source)) if (InvokeSource(sourceContainer, sourceSlot, out var source))
if (InvokeTarget(manager, targetContainer, targetSlot, out var target)) if (InvokeTarget(manager, targetContainer, targetSlot, out var target))
_event.Invoke(new[] _movedItemsEvent.Invoke(new[]
{ {
source, source,
target, target,
}); });
else else
_event.Invoke(new[] _movedItemsEvent.Invoke(new[]
{ {
source, source,
}); });
else if (InvokeTarget(manager, targetContainer, targetSlot, out var target)) else if (InvokeTarget(manager, targetContainer, targetSlot, out var target))
_event.Invoke(new[] _movedItemsEvent.Invoke(new[]
{ {
target, target,
}); });

View file

@ -73,6 +73,7 @@ public static class ServiceManager
.AddSingleton<ObjectUnlocked>() .AddSingleton<ObjectUnlocked>()
.AddSingleton<TabSelected>() .AddSingleton<TabSelected>()
.AddSingleton<MovedEquipment>() .AddSingleton<MovedEquipment>()
.AddSingleton<EquippedGearset>()
.AddSingleton<GPoseService>() .AddSingleton<GPoseService>()
.AddSingleton<PenumbraReloaded>(); .AddSingleton<PenumbraReloaded>();