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 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;
}

View file

@ -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;
}
}

View file

@ -306,6 +306,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
return;
var design = set.Designs[which];
if (design.Jobs.Id == jobs.Id)
return;
@ -316,6 +317,22 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, 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<AutoDesignSet>, 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<AutoDesignSet>, 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<AutoDesignSet>, 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<AutoDesignSet>, 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<int>() ?? -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<short>() ?? -1;
return true;
}
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.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)

View file

@ -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<MoveItemDelegate>((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,
});

View file

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