Add associated collections.

This commit is contained in:
y2_ss 2023-08-01 11:53:53 -05:00
parent fe3ce166e3
commit d3bdb8d3d9
9 changed files with 211 additions and 14 deletions

View file

@ -4,6 +4,7 @@ using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Interop;
using Glamourer.Interop.Penumbra;
using Glamourer.Interop.Structs;
using Glamourer.Services;
using Glamourer.State;
@ -29,6 +30,7 @@ public class AutoDesignApplier : IDisposable
private readonly AutomationChanged _event;
private readonly ObjectManager _objects;
private readonly WeaponLoading _weapons;
private readonly PenumbraService _penumbra;
private ActorState? _jobChangeState;
private EquipItem _jobChangeMainhand;
@ -36,7 +38,7 @@ 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)
AutomationChanged @event, ObjectManager objects, WeaponLoading weapons, PenumbraService penumbra)
{
_config = config;
_manager = manager;
@ -49,6 +51,7 @@ public class AutoDesignApplier : IDisposable
_event = @event;
_objects = objects;
_weapons = weapons;
_penumbra = penumbra;
_jobs.JobChanged += OnJobChange;
_event.Subscribe(OnAutomationChange, AutomationChanged.Priority.AutoDesignApplier);
_weapons.Subscribe(OnWeaponLoading, WeaponLoading.Priority.AutoDesignApplier);
@ -126,7 +129,10 @@ public class AutoDesignApplier : IDisposable
{
Reduce(data.Objects[0], state, newSet, false, false);
foreach (var actor in data.Objects)
{
_penumbra.SetCollection(actor, ReduceCollections(actor, set));
_state.ReapplyState(actor);
}
}
}
else if (_objects.TryGetValueAllWorld(id, out data) || _objects.TryGetValueNonOwned(id, out data))
@ -136,6 +142,7 @@ public class AutoDesignApplier : IDisposable
var specificId = actor.GetIdentifier(_actors.AwaitedService);
if (_state.GetOrCreate(specificId, actor, out var state))
{
_penumbra.SetCollection(actor, ReduceCollections(actor, set));
Reduce(actor, state, newSet, false, false);
_state.ReapplyState(actor);
}
@ -194,6 +201,7 @@ public class AutoDesignApplier : IDisposable
var respectManual = state.LastJob == newJob.Id;
state.LastJob = actor.Job;
Reduce(actor, state, set, respectManual, true);
_penumbra.SetCollection(actor, ReduceCollections(actor, set));
_state.ReapplyState(actor);
}
@ -206,6 +214,29 @@ public class AutoDesignApplier : IDisposable
return;
Reduce(actor, state, set, false, false);
_penumbra.SetCollection(actor, ReduceCollections(actor, set));
}
public unsafe Collection ReduceCollections(Actor actor, AutoDesignSet set)
{
Collection collection = new Collection();
foreach (var design in set.Designs)
{
if (!design.IsActive(actor))
continue;
if (design.ApplicationType is 0)
continue;
if (actor.AsCharacter->CharacterData.ModelCharaId != design?.Design?.DesignData.ModelId)
continue;
if (design.Design.AssociatedCollection.IsAssociable())
{
collection = design.Design.AssociatedCollection;
}
}
return collection;
}
public bool Reduce(Actor actor, ActorIdentifier identifier, [NotNullWhen(true)] out ActorState? state)

View file

@ -41,6 +41,7 @@ public sealed class Design : DesignBase, ISavable
public string[] Tags { get; internal set; } = Array.Empty<string>();
public int Index { get; internal set; }
public SortedList<Mod, ModSettings> AssociatedMods { get; private set; } = new();
public Collection AssociatedCollection { get; internal set; } = new();
public string Incognito
=> Identifier.ToString()[..8];
@ -64,6 +65,7 @@ public sealed class Design : DesignBase, ISavable
["Equipment"] = SerializeEquipment(),
["Customize"] = SerializeCustomize(),
["Mods"] = SerializeMods(),
["Collection"] = AssociatedCollection.Name,
}
;
return ret;
@ -124,6 +126,7 @@ public sealed class Design : DesignBase, ISavable
Description = json["Description"]?.ToObject<string>() ?? string.Empty,
Tags = ParseTags(json),
LastEdit = json["LastEdit"]?.ToObject<DateTimeOffset>() ?? creationDate,
AssociatedCollection = new Collection(json["Collection"]?.ToObject<string>() ?? string.Empty),
};
if (design.LastEdit < creationDate)
design.LastEdit = creationDate;

View file

@ -251,6 +251,26 @@ public class DesignManager
_event.Invoke(DesignChanged.Type.RemovedMod, design, (mod, settings));
}
/// <summary> Change an associated collection to a design. </summary>
public void ChangeAssociatedCollection(Design design, Collection collection)
{
var oldAssociatedCollection = design.AssociatedCollection;
if (oldAssociatedCollection == collection)
return;
design.AssociatedCollection = collection;
design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design);
if (collection.IsAssociable())
{
Glamourer.Log.Debug($"Removed associated collection from design {design.Identifier}.");
} else
{
Glamourer.Log.Debug($"Set associated collection {collection.Name} to design {design.Identifier}.");
}
_event.Invoke(DesignChanged.Type.ChangedAssociatedCollection, design, oldAssociatedCollection);
}
/// <summary> Set the write protection status of a design. </summary>
public void SetWriteProtection(Design design, bool value)
{

View file

@ -46,6 +46,9 @@ public sealed class DesignChanged : EventWrapper<Action<DesignChanged.Type, Desi
/// <summary> An existing design had an existing associated mod removed. Data is the Mod and its Settings [(Mod, ModSettings)]. </summary>
RemovedMod,
/// <summary> An existing design had an associated collection changed. Data is the prior collection [string]. </summary>
ChangedAssociatedCollection,
/// <summary> An existing design had a customization changed. Data is the old value, the new value and the type [(CustomizeValue, CustomizeValue, CustomizeIndex)]. </summary>
Customize,

View file

@ -0,0 +1,45 @@
using System.Numerics;
using Dalamud.Interface;
using Dalamud.Interface.Internal.Notifications;
using Dalamud.Logging;
using Dalamud.Utility;
using Glamourer.Designs;
using Glamourer.Interop.Penumbra;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
namespace Glamourer.Gui.Tabs.DesignTab;
public class CollectionAssociationTab
{
private readonly DesignFileSystemSelector _selector;
private readonly DesignManager _manager;
private readonly CollectionCombo _collectionCombo;
public CollectionAssociationTab(PenumbraService penumbra, DesignFileSystemSelector selector, DesignManager manager)
{
_selector = selector;
_manager = manager;
_collectionCombo = new CollectionCombo(penumbra);
}
public void Draw()
{
if (!ImGui.CollapsingHeader("Collection Association"))
return;
var width = new Vector2(ImGui.GetContentRegionAvail().X, 0);
var changed = _collectionCombo.Draw("##new", !_selector.Selected!.AssociatedCollection.IsAssociable() ? "Select Collection" : _selector.Selected!.AssociatedCollection.Name, string.Empty,
width.X, ImGui.GetTextLineHeight());
var currentCollection = _collectionCombo.CurrentSelection;
if (changed)
{
if (!currentCollection.IsAssociable()) return;
_manager.ChangeAssociatedCollection(_selector.Selected!, currentCollection);
}
if (ImGui.Button($"Remove Associated Collection"))
{
_manager.ChangeAssociatedCollection(_selector.Selected!, new Collection());
}
}
}

View file

@ -0,0 +1,32 @@
using System;
using System.Numerics;
using Dalamud.Interface;
using Glamourer.Interop.Penumbra;
using ImGuiNET;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Raii;
using OtterGui.Widgets;
namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class CollectionCombo : FilterComboCache<Collection>
{
public CollectionCombo(PenumbraService penumbra)
: base(penumbra.GetAllCollections)
{
SearchByParts = false;
}
protected override bool DrawSelectable(int globalIdx, bool selected)
{
using var id = ImRaii.PushId(globalIdx);
var collection = Items[globalIdx];
bool ret;
using (var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.Text)))
{
ret = ImGui.Selectable(collection.Name, selected);
}
return ret;
}
}

View file

@ -13,6 +13,7 @@ using Glamourer.Events;
using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment;
using Glamourer.Interop;
using Glamourer.Interop.Penumbra;
using Glamourer.Services;
using Glamourer.State;
using Glamourer.Structs;
@ -37,22 +38,26 @@ public class DesignPanel
private readonly DesignConverter _converter;
private readonly DatFileService _datFileService;
private readonly FileDialogManager _fileDialog = new();
private readonly PenumbraService _penumbra;
private readonly CollectionAssociationTab _collectionAssociation;
public DesignPanel(DesignFileSystemSelector selector, CustomizationDrawer customizationDrawer, DesignManager manager, ObjectManager objects,
StateManager state, EquipmentDrawer equipmentDrawer, CustomizationService customizationService, ModAssociationsTab modAssociations,
DesignDetailTab designDetails, DesignConverter converter, DatFileService datFileService)
StateManager state, EquipmentDrawer equipmentDrawer, CustomizationService customizationService, ModAssociationsTab modAssociations, CollectionAssociationTab collectionAssociation,
DesignDetailTab designDetails, DesignConverter converter, DatFileService datFileService, PenumbraService penumbra)
{
_selector = selector;
_customizationDrawer = customizationDrawer;
_manager = manager;
_objects = objects;
_state = state;
_equipmentDrawer = equipmentDrawer;
_customizationService = customizationService;
_modAssociations = modAssociations;
_designDetails = designDetails;
_converter = converter;
_datFileService = datFileService;
_selector = selector;
_customizationDrawer = customizationDrawer;
_manager = manager;
_objects = objects;
_state = state;
_equipmentDrawer = equipmentDrawer;
_customizationService = customizationService;
_modAssociations = modAssociations;
_collectionAssociation = collectionAssociation;
_designDetails = designDetails;
_converter = converter;
_datFileService = datFileService;
_penumbra = penumbra;
}
private HeaderDrawer.Button LockButton()
@ -326,6 +331,7 @@ public class DesignPanel
_designDetails.Draw();
DrawApplicationRules();
_modAssociations.Draw();
_collectionAssociation.Draw();
}
private void DrawButtonRow()
@ -374,7 +380,10 @@ public class DesignPanel
return;
if (_state.GetOrCreate(id, data.Objects[0], out var state))
{
_state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual);
_penumbra.SetCollection(data.Objects[0], _selector.Selected!.AssociatedCollection);
}
}
private void DrawApplyToTarget()

View file

@ -5,6 +5,7 @@ using System.Linq;
using System.Text;
using Dalamud.Logging;
using Dalamud.Plugin;
using Dalamud.Utility;
using Glamourer.Events;
using Glamourer.Interop.Structs;
using Penumbra.Api;
@ -36,6 +37,17 @@ public readonly record struct ModSettings(IDictionary<string, IList<string>> Set
public static ModSettings Empty
=> new();
}
public readonly record struct Collection(string Name) : IComparable<Collection>
{
public bool IsAssociable()
{
return !Name.IsNullOrEmpty();
}
public int CompareTo(Collection other)
{
return string.Compare(Name, other.Name, StringComparison.Ordinal);
}
}
public unsafe class PenumbraService : IDisposable
{
@ -54,7 +66,9 @@ public unsafe class PenumbraService : IDisposable
private FuncSubscriber<int, (bool, bool, string)> _objectCollection;
private FuncSubscriber<IList<(string, string)>> _getMods;
private FuncSubscriber<ApiCollectionType, string> _currentCollection;
private FuncSubscriber<IList<string>> _getAllCollections;
private FuncSubscriber<string, string, string, bool, CurrentSettings> _getCurrentSettings;
private FuncSubscriber<int, string, bool, bool, (PenumbraApiEc, string)> _setCurrentCollection;
private FuncSubscriber<string, string, string, bool, PenumbraApiEc> _setMod;
private FuncSubscriber<string, string, string, int, PenumbraApiEc> _setModPriority;
private FuncSubscriber<string, string, string, string, string, PenumbraApiEc> _setModSetting;
@ -141,6 +155,25 @@ public unsafe class PenumbraService : IDisposable
}
}
public IReadOnlyList<Collection> GetAllCollections()
{
if (!Available)
return Array.Empty<Collection>();
try
{
var allCollections = _getAllCollections.Invoke();
return allCollections
.Select(c => new Collection(c))
.ToList();
}
catch (Exception ex)
{
Glamourer.Log.Error($"Error fetching collections from Penumbra:\n{ex}");
return Array.Empty<Collection>();
}
}
public string CurrentCollection
=> Available ? _currentCollection.Invoke(ApiCollectionType.Current) : "<Unavailable>";
@ -195,6 +228,24 @@ public unsafe class PenumbraService : IDisposable
return sb.AppendLine(ex.Message).ToString();
}
}
public string SetCollection(Actor actor, Collection collection)
{
if (!Available)
return "Penumbra is not available.";
var sb = new StringBuilder();
try
{
var ec = _setCurrentCollection.Invoke(actor.AsObject -> ObjectIndex, collection.Name, true, false);
if (ec.Item1 is PenumbraApiEc.CollectionMissing)
sb.AppendLine($"The collection {collection.Name}] could not be found.");
return sb.ToString();
}
catch (Exception ex)
{
return sb.AppendLine(ex.Message).ToString();
}
}
/// <summary> Obtain the name of the collection currently assigned to the player. </summary>
public string GetCurrentPlayerCollection()
@ -254,6 +305,8 @@ public unsafe class PenumbraService : IDisposable
_getMods = Ipc.GetMods.Subscriber(_pluginInterface);
_currentCollection = Ipc.GetCollectionForType.Subscriber(_pluginInterface);
_getCurrentSettings = Ipc.GetCurrentModSettings.Subscriber(_pluginInterface);
_getAllCollections = Ipc.GetCollections.Subscriber(_pluginInterface);
_setCurrentCollection = Ipc.SetCollectionForObject.Subscriber( _pluginInterface);
_setMod = Ipc.TrySetMod.Subscriber(_pluginInterface);
_setModPriority = Ipc.TrySetModPriority.Subscriber(_pluginInterface);
_setModSetting = Ipc.TrySetModSetting.Subscriber(_pluginInterface);

View file

@ -128,6 +128,7 @@ public static class ServiceManager
.AddSingleton<DesignTab>()
.AddSingleton<DesignCombo>()
.AddSingleton<ModAssociationsTab>()
.AddSingleton<CollectionAssociationTab>()
.AddSingleton<DesignDetailTab>()
.AddSingleton<UnlockTable>()
.AddSingleton<UnlockOverview>()