Add retained selections and filters and config for them.

This commit is contained in:
Ottermandias 2026-02-23 20:35:50 +01:00
parent baedba11f8
commit 67ee7bd507
26 changed files with 605 additions and 92 deletions

View file

@ -23,6 +23,9 @@ public sealed partial class Configuration : IPluginConfiguration, ISavable, ISer
[JsonIgnore] [JsonIgnore]
public readonly UiConfig Ui; public readonly UiConfig Ui;
[JsonIgnore]
public readonly FilterConfig Filters;
public bool AttachToPcp { get; set; } = true; public bool AttachToPcp { get; set; } = true;
public bool UseRestrictedGearProtection { get; set; } = false; public bool UseRestrictedGearProtection { get; set; } = false;
public bool OpenFoldersByDefault { get; set; } = false; public bool OpenFoldersByDefault { get; set; } = false;
@ -54,6 +57,11 @@ public sealed partial class Configuration : IPluginConfiguration, ISavable, ISer
public bool PreventRandomRepeats { get; set; } = false; public bool PreventRandomRepeats { get; set; } = false;
public string PcpFolder { get; set; } = "PCP"; public string PcpFolder { get; set; } = "PCP";
public string PcpColor { get; set; } = ""; public string PcpColor { get; set; } = "";
public bool RememberActorFilter { get; set; } = false;
public bool RememberDesignFilter { get; set; } = false;
public bool RememberNpcFilter { get; set; } = true;
public bool RememberAutomationFilter { get; set; } = false;
public bool RememberUnlocksFilters { get; set; } = true;
public RoughnessSetting RoughnessSetting { get; set; } = RoughnessSetting.AsIs; public RoughnessSetting RoughnessSetting { get; set; } = RoughnessSetting.AsIs;
@ -95,11 +103,12 @@ public sealed partial class Configuration : IPluginConfiguration, ISavable, ISer
[JsonIgnore] [JsonIgnore]
private readonly SaveService _saveService; private readonly SaveService _saveService;
public Configuration(SaveService saveService, ConfigMigrationService migrator, EphemeralConfig ephemeral, UiConfig ui) public Configuration(SaveService saveService, ConfigMigrationService migrator, EphemeralConfig ephemeral, UiConfig ui, FilterConfig filters)
{ {
_saveService = saveService; _saveService = saveService;
Ephemeral = ephemeral; Ephemeral = ephemeral;
Ui = ui; Ui = ui;
Filters = filters;
Load(migrator); Load(migrator);
} }

View file

@ -0,0 +1,201 @@
using Glamourer.Gui.Tabs.UnlocksTab;
using Glamourer.Services;
using ImSharp.Table;
using Luna;
using Luna.Generators;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Config;
public sealed partial class FilterConfig : ConfigurationFile<FilenameService>
{
private readonly DictJob _jobs;
public FilterConfig(SaveService saveService, MessageService messager, DictJob jobs)
: base(saveService, messager, TimeSpan.FromMinutes(5))
{
_jobs = jobs;
_unlocksJobFilter = _jobs.AllAvailableJobs;
Load();
}
public override int CurrentVersion
=> 1;
protected override void AddData(JsonTextWriter j)
{
WriteFilter(j, ActorFilter, "Actors");
WriteFilter(j, DesignFilter, "Designs");
WriteFilter(j, NpcFilter, "Npcs");
WriteAutomation(j);
WriteUnlocksTab(j);
}
protected override void LoadData(JObject j)
{
_actorFilter = j["Actors"]?["Filter"]?.Value<string>() ?? string.Empty;
_designFilter = j["Designs"]?["Filter"]?.Value<string>() ?? string.Empty;
_npcFilter = j["Npcs"]?["Filter"]?.Value<string>() ?? string.Empty;
_automationFilter = j["Automation"]?["Filter"]?.Value<string>() ?? string.Empty;
_automationStateFilter = j["Automation"]?["State"]?.Value<bool>();
LoadUnlocksTab(j);
}
[ConfigProperty]
private string _designFilter = string.Empty;
[ConfigProperty]
private string _actorFilter = string.Empty;
[ConfigProperty]
private string _automationFilter = string.Empty;
[ConfigProperty]
private bool? _automationStateFilter;
[ConfigProperty]
private string _npcFilter = string.Empty;
[ConfigProperty]
private YesNoColumn.YesNoFlag _unlocksFavoriteFilter = YesNoColumn.YesOrNo;
[ConfigProperty]
private UnlockCacheItem.Modded _unlocksModdedFilter = UnlockCacheItem.ModdedAll;
[ConfigProperty]
private string _unlocksNameFilter = string.Empty;
[ConfigProperty]
private string _unlocksTypeFilter = string.Empty;
[ConfigProperty]
private EquipFlag _unlocksSlotFilter = UnlockCacheItem.SlotsAll;
[ConfigProperty]
private YesNoColumn.YesNoFlag _unlocksUnlockedFilter = YesNoColumn.YesOrNo;
[ConfigProperty]
private string _unlocksItemIdFilter = string.Empty;
[ConfigProperty]
private string _unlocksModelDataFilter = string.Empty;
[ConfigProperty]
private string _unlocksLevelFilter = string.Empty;
[ConfigProperty]
private JobFlag _unlocksJobFilter;
[ConfigProperty]
private UnlockCacheItem.Dyability _unlocksDyabilityFilter = UnlockCacheItem.DyableAll;
[ConfigProperty]
private YesNoColumn.YesNoFlag _unlocksTradableFilter = YesNoColumn.YesOrNo;
[ConfigProperty]
private YesNoColumn.YesNoFlag _unlocksCrestFilter = YesNoColumn.YesOrNo;
private void WriteUnlocksTab(JsonTextWriter j)
{
var obj = new JObject();
if (UnlocksFavoriteFilter is not YesNoColumn.YesOrNo)
obj["Favorite"] = (uint)UnlocksFavoriteFilter;
if (UnlocksCrestFilter is not YesNoColumn.YesOrNo)
obj["Crest"] = (uint)UnlocksCrestFilter;
if (UnlocksTradableFilter is not YesNoColumn.YesOrNo)
obj["Tradable"] = (uint)UnlocksTradableFilter;
if (UnlocksUnlockedFilter is not YesNoColumn.YesOrNo)
obj["Unlocked"] = (uint)UnlocksUnlockedFilter;
if (UnlocksModdedFilter is not UnlockCacheItem.ModdedAll)
obj["Modded"] = (uint)UnlocksModdedFilter;
if (UnlocksDyabilityFilter is not UnlockCacheItem.DyableAll)
obj["Dyability"] = (uint)UnlocksDyabilityFilter;
if (UnlocksSlotFilter is not UnlockCacheItem.SlotsAll)
obj["Slot"] = (uint)UnlocksSlotFilter;
if (UnlocksJobFilter != _jobs.AllAvailableJobs)
obj["Job"] = (ulong)UnlocksJobFilter;
if (UnlocksLevelFilter.Length > 0)
obj["Level"] = UnlocksLevelFilter;
if (UnlocksModelDataFilter.Length > 0)
obj["ModelData"] = UnlocksModelDataFilter;
if (UnlocksItemIdFilter.Length > 0)
obj["ItemId"] = UnlocksItemIdFilter;
if (UnlocksNameFilter.Length > 0)
obj["Name"] = UnlocksNameFilter;
if (UnlocksTypeFilter.Length > 0)
obj["Type"] = UnlocksTypeFilter;
if (obj.Count <= 0)
return;
j.WritePropertyName("Unlocks");
obj.WriteTo(j);
}
private void LoadUnlocksTab(JObject j)
{
if (j["Unlocks"] is not JObject unlocks)
return;
_unlocksFavoriteFilter = unlocks["Favorite"]?.Value<uint>() is { } f ? (YesNoColumn.YesNoFlag)f : YesNoColumn.YesOrNo;
_unlocksCrestFilter = unlocks["Crest"]?.Value<uint>() is { } c ? (YesNoColumn.YesNoFlag)c : YesNoColumn.YesOrNo;
_unlocksTradableFilter = unlocks["Tradable"]?.Value<uint>() is { } t ? (YesNoColumn.YesNoFlag)t : YesNoColumn.YesOrNo;
_unlocksUnlockedFilter = unlocks["Unlocked"]?.Value<uint>() is { } u ? (YesNoColumn.YesNoFlag)u : YesNoColumn.YesOrNo;
_unlocksModdedFilter = unlocks["Modded"]?.Value<uint>() is { } m ? (UnlockCacheItem.Modded)m : UnlockCacheItem.ModdedAll;
_unlocksDyabilityFilter = unlocks["Dyability"]?.Value<uint>() is { } d ? (UnlockCacheItem.Dyability)d : UnlockCacheItem.DyableAll;
_unlocksSlotFilter = unlocks["Slot"]?.Value<uint>() is { } s ? (EquipFlag)s : UnlockCacheItem.SlotsAll;
_unlocksJobFilter = unlocks["Job"]?.Value<ulong>() is { } job ? (JobFlag)job : _jobs.AllAvailableJobs;
_unlocksLevelFilter = unlocks["Level"]?.Value<string>() ?? string.Empty;
_unlocksModelDataFilter = unlocks["ModelData"]?.Value<string>() ?? string.Empty;
_unlocksItemIdFilter = unlocks["ItemId"]?.Value<string>() ?? string.Empty;
_unlocksNameFilter = unlocks["Name"]?.Value<string>() ?? string.Empty;
_unlocksTypeFilter = unlocks["Type"]?.Value<string>() ?? string.Empty;
}
public override string ToFilePath(FilenameService fileNames)
=> fileNames.FilterFile;
private void WriteAutomation(JsonTextWriter j)
{
if (AutomationStateFilter is null && AutomationFilter.Length is 0)
return;
j.WritePropertyName("Automation");
j.WriteStartObject();
if (AutomationStateFilter is not null)
{
j.WritePropertyName("State");
j.WriteValue(AutomationStateFilter.Value);
}
if (AutomationFilter.Length > 0)
{
j.WritePropertyName("Filter");
j.WriteValue(AutomationFilter);
}
j.WriteEndObject();
}
private static void WriteFilter(JsonTextWriter j, string filter, string tabName)
{
if (filter.Length is 0)
return;
j.WritePropertyName(tabName);
j.WriteStartObject();
j.WritePropertyName("Filter");
j.WriteValue(filter);
j.WriteEndObject();
}
}

View file

@ -3,14 +3,19 @@ using Luna;
using Luna.Generators; using Luna.Generators;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Structs;
namespace Glamourer.Config; namespace Glamourer.Config;
public sealed partial class UiConfig : ConfigurationFile<FilenameService> public sealed partial class UiConfig : ConfigurationFile<FilenameService>
{ {
public UiConfig(SaveService saveService, MessageService messageService) private readonly ActorManager _actors;
: base(saveService, messageService)
public UiConfig(SaveService saveService, MessageService messageService, ActorManager actors)
: base(saveService, messageService, TimeSpan.FromMinutes(5))
{ {
_actors = actors;
Load(); Load();
} }
@ -26,6 +31,15 @@ public sealed partial class UiConfig : ConfigurationFile<FilenameService>
[ConfigProperty] [ConfigProperty]
private TwoPanelWidth _npcTabScale = new(250, ScalingMode.Absolute); private TwoPanelWidth _npcTabScale = new(250, ScalingMode.Absolute);
[ConfigProperty]
private NpcId _selectedNpc = 0;
[ConfigProperty]
private int _selectedAutomationIndex = -1;
[ConfigProperty]
private ActorIdentifier _selectedActor = ActorIdentifier.Invalid;
public override int CurrentVersion public override int CurrentVersion
=> 1; => 1;
@ -35,16 +49,36 @@ public sealed partial class UiConfig : ConfigurationFile<FilenameService>
DesignsTabScale.WriteJson(j, "DesignsTab"); DesignsTabScale.WriteJson(j, "DesignsTab");
AutomationTabScale.WriteJson(j, "AutomationTab"); AutomationTabScale.WriteJson(j, "AutomationTab");
NpcTabScale.WriteJson(j, "NpcTab"); NpcTabScale.WriteJson(j, "NpcTab");
if (_selectedNpc.Id is not 0)
{
j.WritePropertyName("SelectedNpc");
j.WriteValue(_selectedNpc);
}
if (_selectedAutomationIndex >= 0)
{
j.WritePropertyName("SelectedAutomationIndex");
j.WriteValue(_selectedAutomationIndex);
}
if (_selectedActor.IsValid)
{
j.WritePropertyName("SelectedActor");
_selectedActor.ToJson().WriteTo(j);
}
} }
protected override void LoadData(JObject j) protected override void LoadData(JObject j)
{ {
_actorsTabScale = TwoPanelWidth.ReadJson(j, "ActorsTab", new TwoPanelWidth(250, ScalingMode.Absolute)); _actorsTabScale = TwoPanelWidth.ReadJson(j, "ActorsTab", new TwoPanelWidth(250, ScalingMode.Absolute));
_designsTabScale = TwoPanelWidth.ReadJson(j, "DesignsTab", new TwoPanelWidth(0.3f, ScalingMode.Percentage)); _designsTabScale = TwoPanelWidth.ReadJson(j, "DesignsTab", new TwoPanelWidth(0.3f, ScalingMode.Percentage));
_automationTabScale = TwoPanelWidth.ReadJson(j, "AutomationTab", new TwoPanelWidth(0.3f, ScalingMode.Percentage)); _automationTabScale = TwoPanelWidth.ReadJson(j, "AutomationTab", new TwoPanelWidth(0.3f, ScalingMode.Percentage));
_npcTabScale = TwoPanelWidth.ReadJson(j, "NpcTab", new TwoPanelWidth(250, ScalingMode.Absolute)); _npcTabScale = TwoPanelWidth.ReadJson(j, "NpcTab", new TwoPanelWidth(250, ScalingMode.Absolute));
_selectedNpc = j["SelectedNpc"]?.Value<uint>() ?? 0;
_selectedAutomationIndex = j["SelectedAutomationIndex"]?.Value<int>() ?? -1;
_selectedActor = _actors.FromJson(j["SelectedActor"] as JObject);
} }
public override string ToFilePath(FilenameService fileNames) public override string ToFilePath(FilenameService fileNames)
=> fileNames.UiConfiguration; => fileNames.UiConfigurationFile;
} }

View file

@ -81,12 +81,16 @@ public sealed class DesignColors : ISavable, IReadOnlyDictionary<string, Rgba32>
public void Save(StreamWriter writer) public void Save(StreamWriter writer)
{ {
var dict = new JObject();
foreach (var (name, color) in _colors)
dict[name] = color.Color;
var jObj = new JObject var jObj = new JObject
{ {
["Version"] = 1, ["Version"] = 1,
["MissingColor"] = MissingColor.Color, ["MissingColor"] = MissingColor.Color,
["Definitions"] = JToken.FromObject(_colors), ["Definitions"] = dict,
}; };
writer.Write(jObj.ToString(Formatting.Indented)); writer.Write(jObj.ToString(Formatting.Indented));
} }

View file

@ -1,4 +1,5 @@
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Glamourer.Config;
using ImSharp; using ImSharp;
using Luna; using Luna;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
@ -23,7 +24,7 @@ public sealed class ActorFilter : TextFilterBase<ActorCacheItem>, IUiService
private FilterMethod _method = FilterMethod.Empty; private FilterMethod _method = FilterMethod.Empty;
public ActorFilter(IPlayerState playerState) public ActorFilter(IPlayerState playerState, Configuration config)
{ {
_playerState = playerState; _playerState = playerState;
FilterChanged += () => FilterChanged += () =>
@ -39,7 +40,10 @@ public sealed class ActorFilter : TextFilterBase<ActorCacheItem>, IUiService
"<w>" or "<W>" => FilterMethod.Homeworld, "<w>" or "<W>" => FilterMethod.Homeworld,
_ => FilterMethod.Text, _ => FilterMethod.Text,
}; };
config.Filters.ActorFilter = Text;
}; };
if (config.RememberActorFilter)
Set(config.Filters.ActorFilter);
} }
public override bool DrawFilter(ReadOnlySpan<byte> label, Vector2 availableRegion) public override bool DrawFilter(ReadOnlySpan<byte> label, Vector2 availableRegion)
@ -78,9 +82,21 @@ public sealed class ActorFilter : TextFilterBase<ActorCacheItem>, IUiService
Im.Text("<w>"u8, color); Im.Text("<w>"u8, color);
Im.Line.NoSpacing(); Im.Line.NoSpacing();
Im.Text(": show only players from your world."u8); Im.Text(": show only players from your world."u8);
if (Text.Length > 0)
Im.Text("\nMiddle-click to clear filters."u8);
return ret; return ret;
} }
protected override bool OnMiddleClick()
{
if (!Im.Item.MiddleClicked())
return false;
Im.Id.ClearActive();
return Clear();
}
protected override string ToFilterString(in ActorCacheItem item, int globalIndex) protected override string ToFilterString(in ActorCacheItem item, int globalIndex)
=> item.DisplayText.Utf16; => item.DisplayText.Utf16;

View file

@ -1,5 +1,6 @@
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Glamourer.Config;
using Glamourer.State; using Glamourer.State;
using ImSharp; using ImSharp;
using Luna; using Luna;
@ -9,7 +10,7 @@ using Penumbra.GameData.Interop;
namespace Glamourer.Gui.Tabs.ActorTab; namespace Glamourer.Gui.Tabs.ActorTab;
public sealed class ActorSelection(StateManager manager, ActorObjectManager objects, ICondition conditions) : IUiService public sealed class ActorSelection(StateManager manager, ActorObjectManager objects, ICondition conditions, UiConfig config) : IUiService
{ {
private static readonly StringU8 NoSelection = new("No Actor Selected"u8); private static readonly StringU8 NoSelection = new("No Actor Selected"u8);
@ -23,7 +24,8 @@ public sealed class ActorSelection(StateManager manager, ActorObjectManager obje
public void Select(ActorIdentifier identifier, ActorData data) public void Select(ActorIdentifier identifier, ActorData data)
{ {
Identifier = identifier.CreatePermanent(); Identifier = identifier.CreatePermanent();
config.SelectedActor = Identifier;
if (Identifier.IsValid) if (Identifier.IsValid)
{ {
ActorName = new StringU8(data.Label); ActorName = new StringU8(data.Label);

View file

@ -1,4 +1,5 @@
using Glamourer.Interop.Penumbra; using Glamourer.Config;
using Glamourer.Interop.Penumbra;
using ImSharp; using ImSharp;
using Luna; using Luna;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
@ -14,25 +15,58 @@ public readonly struct ActorCacheItem(ActorIdentifier identifier, ActorData data
public readonly StringU8 IncognitoText = new(identifier.Incognito(data.Label)); public readonly StringU8 IncognitoText = new(identifier.Incognito(data.Label));
} }
public sealed class ActorSelector(ActorSelection selection, ActorObjectManager objects, ActorFilter filter, PenumbraService penumbra, Config.EphemeralConfig config) : IPanel public sealed class ActorSelector(
ActorSelection selection,
ActorObjectManager objects,
ActorFilter filter,
PenumbraService penumbra,
Configuration config) : IPanel
{ {
public ReadOnlySpan<byte> Id public ReadOnlySpan<byte> Id
=> "ActorSelector"u8; => "ActorSelector"u8;
public void Draw() public unsafe void Draw()
{ {
Im.Cursor.Y += Im.Style.FramePadding.Y; Im.Cursor.Y += Im.Style.FramePadding.Y;
var cache = CacheManager.Instance.GetOrCreateCache(Im.Id.Current, () => new ActorSelectorCache(objects, filter, penumbra)); var cache = CacheManager.Instance.GetOrCreateCache(Im.Id.Current, () => new ActorSelectorCache(objects, filter, penumbra));
using var clip = new Im.ListClipper(cache.Count, Im.Style.TextHeightWithSpacing); HandleRememberedSelection();
using var clip = new Im.ListClipper(cache.Count, Im.Style.TextHeightWithSpacing);
foreach (var actor in clip.Iterate(cache)) foreach (var actor in clip.Iterate(cache))
{ {
Im.Cursor.X += Im.Style.FramePadding.X; Im.Cursor.X += Im.Style.FramePadding.X;
var selected = actor.Identifier.Equals(selection.Identifier); var selected = actor.Identifier.Equals(selection.Identifier);
if (Im.Selectable(config.IncognitoMode ? actor.IncognitoText : actor.DisplayText.Utf8, selected) && !selected) if (Im.Selectable(config.Ephemeral.IncognitoMode ? actor.IncognitoText : actor.DisplayText.Utf8, selected) && !selected)
selection.Select(actor.Identifier, actor.Data); selection.Select(actor.Identifier, actor.Data);
} }
} }
private unsafe void HandleRememberedSelection()
{
// We already have a valid selection.
if (selection.Identifier.IsValid)
return;
// We do not have a remembered selection.
if (!config.Ui.SelectedActor.IsValid)
return;
// We have no actor corresponding to the selection available to create a new state.
if (!objects.TryGetValue(config.Ui.SelectedActor, out var data))
return;
// The actor has no model yet.
if (data.Objects.First().Model is not { IsCharacterBase: true } model)
return;
// The model still has a staging area, so is not fully loaded.
if (model.AsCharacterBase->PerSlotStagingArea is not null || model.AsCharacterBase->TempData is not null)
return;
// The model should be fully loaded, so the selection can probably create the expected state.
selection.Select(config.Ui.SelectedActor, data);
}
private sealed class ActorSelectorCache : BasicFilterCache<ActorCacheItem> private sealed class ActorSelectorCache : BasicFilterCache<ActorCacheItem>
{ {
private readonly ActorObjectManager _objects; private readonly ActorObjectManager _objects;

View file

@ -1,4 +1,5 @@
using ImSharp; using Glamourer.Config;
using ImSharp;
using Luna; using Luna;
namespace Glamourer.Gui.Tabs.AutomationTab; namespace Glamourer.Gui.Tabs.AutomationTab;
@ -10,6 +11,21 @@ public sealed class AutomationFilter : TextFilterBase<AutomationCacheItem>, IUiS
protected override string ToFilterString(in AutomationCacheItem item, int globalIndex) protected override string ToFilterString(in AutomationCacheItem item, int globalIndex)
=> item.Name.Utf16; => item.Name.Utf16;
public AutomationFilter(Configuration config)
{
if (config.RememberAutomationFilter)
{
_setStateFilter = config.Filters.AutomationStateFilter;
Set(config.Filters.AutomationFilter);
}
FilterChanged += () =>
{
config.Filters.AutomationFilter = Text;
config.Filters.AutomationStateFilter = _setStateFilter;
};
}
public override bool WouldBeVisible(in AutomationCacheItem item, int globalIndex) public override bool WouldBeVisible(in AutomationCacheItem item, int globalIndex)
=> _setStateFilter switch => _setStateFilter switch
{ {
@ -54,11 +70,15 @@ public sealed class AutomationFilter : TextFilterBase<AutomationCacheItem>, IUiS
return ret; return ret;
} }
public override void Clear() public override bool Clear()
{ {
var changes = _setStateFilter is not null; var changes = _setStateFilter is not null;
_setStateFilter = null; _setStateFilter = null;
if (!Set(string.Empty) && changes) if (!SetInternal(string.Empty) && !changes)
InvokeEvent(); return false;
InvokeEvent();
return true;
} }
} }

View file

@ -1,4 +1,5 @@
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Config;
using Glamourer.Events; using Glamourer.Events;
using ImSharp; using ImSharp;
using Luna; using Luna;
@ -10,6 +11,7 @@ public sealed class AutomationSelection : IUiService, IDisposable
public static readonly StringU8 NoSelection = new("No Set Selected"u8); public static readonly StringU8 NoSelection = new("No Set Selected"u8);
private readonly AutomationChanged _automationChanged; private readonly AutomationChanged _automationChanged;
private readonly UiConfig _config;
public int DraggedDesignIndex = -1; public int DraggedDesignIndex = -1;
@ -18,10 +20,12 @@ public sealed class AutomationSelection : IUiService, IDisposable
public StringU8 Name { get; private set; } = NoSelection; public StringU8 Name { get; private set; } = NoSelection;
public StringU8 Incognito { get; private set; } = NoSelection; public StringU8 Incognito { get; private set; } = NoSelection;
public AutomationSelection(AutomationChanged automationChanged) public AutomationSelection(AutomationChanged automationChanged, UiConfig config, AutoDesignManager autoDesigns)
{ {
_automationChanged = automationChanged; _automationChanged = automationChanged;
_config = config;
_automationChanged.Subscribe(OnAutomationChanged, AutomationChanged.Priority.SetSelector); _automationChanged.Subscribe(OnAutomationChanged, AutomationChanged.Priority.SetSelector);
InitialSelect(autoDesigns);
} }
public void Dispose() public void Dispose()
@ -47,15 +51,34 @@ public sealed class AutomationSelection : IUiService, IDisposable
Set = item?.Set; Set = item?.Set;
if (Set is null) if (Set is null)
{ {
Index = -1; _config.SelectedAutomationIndex = -1;
Name = NoSelection; Index = -1;
Incognito = NoSelection; Name = NoSelection;
Incognito = NoSelection;
} }
else else
{ {
Index = item!.Value.Index; _config.SelectedAutomationIndex = item!.Value.Index;
Name = item!.Value.Name.Utf8; Index = item!.Value.Index;
Incognito = item!.Value.Incognito; Name = item!.Value.Name.Utf8;
Incognito = item!.Value.Incognito;
} }
} }
private void InitialSelect(AutoDesignManager autoDesigns)
{
if (_config.SelectedAutomationIndex < 0)
return;
if (autoDesigns.Count <= _config.SelectedAutomationIndex)
{
_config.SelectedAutomationIndex = -1;
return;
}
Set = autoDesigns[_config.SelectedAutomationIndex];
Index = _config.SelectedAutomationIndex;
Name = new StringU8(Set.Name);
Incognito = new StringU8($"Auto Design Set #{Index + 1}");
}
} }

View file

@ -1,6 +1,10 @@
using Glamourer.Config; using Glamourer.Config;
using Glamourer.Services;
using ImSharp; using ImSharp;
using InteropGenerator.Runtime;
using Luna; using Luna;
using Penumbra.GameData.Files.ShaderStructs;
using Vortice.Direct3D11.Debug;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
@ -33,9 +37,29 @@ public sealed class DebugTab(ServiceManager manager) : ITab<MainTabType>
return; return;
if (Im.Tree.Header("General"u8)) if (Im.Tree.Header("General"u8))
{
if (Im.Button("Open Config Directory"u8))
OpenFileOrFolder(manager.GetService<FilenameService>().ConfigurationDirectory);
StartTimeTracker.Draw("Timers"u8, manager.GetService<StartTimeTracker>()); StartTimeTracker.Draw("Timers"u8, manager.GetService<StartTimeTracker>());
}
foreach (var header in _headers) foreach (var header in _headers)
header.Draw(); header.Draw();
} }
private static void OpenFileOrFolder(string text)
{
try
{
var process = new ProcessStartInfo(text)
{
UseShellExecute = true,
};
Process.Start(process);
}
catch
{
// Ignored
}
}
} }

View file

@ -55,7 +55,7 @@ public sealed class PenumbraPanel(PenumbraService penumbra, PenumbraChangedItemT
table.NextColumn(); table.NextColumn();
Im.Item.SetNextWidthScaled(200); Im.Item.SetNextWidthScaled(200);
Im.Input.Scalar("##CutsceneIndex"u8, ref _gameObjectIndex); Im.Input.Scalar("##CutsceneIndex"u8, ref _gameObjectIndex);
table.DrawColumn(penumbra.Available ? $"{penumbra.CutsceneParent((ushort)_gameObjectIndex)}" : "Penumbra Unavailable"u8); table.DrawColumn(penumbra.Available ? $"{penumbra.ResolveService.CutsceneParent((ushort)_gameObjectIndex)}" : "Penumbra Unavailable"u8);
table.DrawFrameColumn("Redraw Object"u8); table.DrawFrameColumn("Redraw Object"u8);
table.NextColumn(); table.NextColumn();

View file

@ -101,7 +101,7 @@ public sealed class ModCombo(PenumbraService penumbra, DesignFileSystem fileSyst
{ {
foreach (var setting in settings.Settings) foreach (var setting in settings.Settings)
{ {
if (setting.Value.Count == 0) if (setting.Value.Count is 0)
Im.Text("<None Enabled>"u8); Im.Text("<None Enabled>"u8);
else else
foreach (var option in setting.Value) foreach (var option in setting.Value)

View file

@ -16,7 +16,7 @@ public sealed class DesignFileSystemDrawer : FileSystemDrawer<DesignFileSystemCa
public DesignFileSystemDrawer(DesignFileSystem fileSystem, DesignManager manager, DesignConverter converter, Configuration config, public DesignFileSystemDrawer(DesignFileSystem fileSystem, DesignManager manager, DesignConverter converter, Configuration config,
DesignApplier designApplier, DesignChanged designChanged, DesignColors designColors) DesignApplier designApplier, DesignChanged designChanged, DesignColors designColors)
: base(fileSystem, new DesignFilter()) : base(fileSystem, new DesignFilter(config))
{ {
Manager = manager; Manager = manager;
Config = config; Config = config;

View file

@ -1,4 +1,5 @@
using ImSharp; using Glamourer.Config;
using ImSharp;
using Luna; using Luna;
namespace Glamourer.Gui.Tabs.DesignTab; namespace Glamourer.Gui.Tabs.DesignTab;
@ -6,6 +7,13 @@ namespace Glamourer.Gui.Tabs.DesignTab;
public sealed class DesignFilter : TokenizedFilter<DesignFilterTokenType, DesignFileSystemCache.DesignData, DesignFilterToken>, public sealed class DesignFilter : TokenizedFilter<DesignFilterTokenType, DesignFileSystemCache.DesignData, DesignFilterToken>,
IFileSystemFilter<DesignFileSystemCache.DesignData>, IUiService IFileSystemFilter<DesignFileSystemCache.DesignData>, IUiService
{ {
public DesignFilter(Configuration config)
{
if (config.RememberDesignFilter)
Set(config.Filters.DesignFilter);
FilterChanged += () => config.Filters.DesignFilter = Text;
}
protected override void DrawTooltip() protected override void DrawTooltip()
{ {
if (!Im.Item.Hovered()) if (!Im.Item.Hovered())

View file

@ -1,4 +1,5 @@
using ImSharp; using Glamourer.Config;
using ImSharp;
using Luna; using Luna;
namespace Glamourer.Gui.Tabs.NpcTab; namespace Glamourer.Gui.Tabs.NpcTab;
@ -12,6 +13,13 @@ public sealed class NpcFilter : TokenizedFilter<NpcFilter.TokenType, NpcCacheIte
Color, Color,
} }
public NpcFilter(Configuration config)
{
if (config.RememberNpcFilter)
Set(config.Filters.NpcFilter);
FilterChanged += () => config.Filters.NpcFilter = Text;
}
protected override void DrawTooltip() protected override void DrawTooltip()
{ {
if (!Im.Item.Hovered()) if (!Im.Item.Hovered())

View file

@ -1,4 +1,5 @@
using Glamourer.Designs; using Glamourer.Config;
using Glamourer.Designs;
using Glamourer.GameData; using Glamourer.GameData;
using ImSharp; using ImSharp;
using Luna; using Luna;
@ -10,15 +11,18 @@ public sealed class NpcSelection : IUiService, IDisposable
private readonly LocalNpcAppearanceData _data; private readonly LocalNpcAppearanceData _data;
private readonly DesignColors _colors; private readonly DesignColors _colors;
private readonly DesignConverter _converter; private readonly DesignConverter _converter;
private readonly UiConfig _config;
public NpcSelection(LocalNpcAppearanceData data, DesignColors colors, DesignConverter converter) public NpcSelection(LocalNpcAppearanceData data, DesignColors colors, DesignConverter converter, UiConfig config, NpcCustomizeSet npcs)
{ {
_data = data; _data = data;
_colors = colors; _colors = colors;
_converter = converter; _converter = converter;
_config = config;
_data.DataChanged += OnDataChanged; _data.DataChanged += OnDataChanged;
_colors.ColorChanged += OnColorChanged; _colors.ColorChanged += OnColorChanged;
InitialSelect(npcs);
} }
private void OnColorChanged() private void OnColorChanged()
@ -81,12 +85,13 @@ public sealed class NpcSelection : IUiService, IDisposable
public void Update(in NpcCacheItem item) public void Update(in NpcCacheItem item)
{ {
Data = item.Npc; Data = item.Npc;
Name = item.Name.Utf8; Name = item.Name.Utf8;
Favorite = item.Favorite; Favorite = item.Favorite;
ColorText = item.ColorText; ColorText = item.ColorText;
ColorTextU8 = ColorText.Length > 0 ? new StringU8(ColorText) : DesignColors.AutomaticNameU8; ColorTextU8 = ColorText.Length > 0 ? new StringU8(ColorText) : DesignColors.AutomaticNameU8;
Color = item.Color; Color = item.Color;
_config.SelectedNpc = Data.Id;
} }
public void Dispose() public void Dispose()
@ -94,4 +99,20 @@ public sealed class NpcSelection : IUiService, IDisposable
_data.DataChanged -= OnDataChanged; _data.DataChanged -= OnDataChanged;
_colors.ColorChanged -= OnColorChanged; _colors.ColorChanged -= OnColorChanged;
} }
private void InitialSelect(NpcCustomizeSet npcs)
{
if (_config.SelectedNpc.Id is 0)
return;
if (!npcs.FindFirst(n => n.Id == _config.SelectedNpc.Id, out var npc))
return;
Data = npc;
Name = new StringU8(npc.Name);
ColorText = _data.GetColor(npc);
ColorTextU8 = ColorText.Length is not 0 ? new StringU8(ColorText) : DesignColors.AutomaticNameU8;
(var color, Favorite) = _data.GetData(npc);
Color = color.ToVector();
}
} }

View file

@ -300,6 +300,31 @@ public sealed class SettingsTab(
Checkbox("Debug Mode"u8, "Show the debug tab. Only useful for debugging or advanced use. Not recommended in general."u8, Checkbox("Debug Mode"u8, "Show the debug tab. Only useful for debugging or advanced use. Not recommended in general."u8,
config.DebugMode, config.DebugMode,
v => config.DebugMode = v); v => config.DebugMode = v);
Im.Dummy(Vector2.Zero);
Im.Separator();
Im.Dummy(Vector2.Zero);
Checkbox("Remember Design Filter Across Sessions"u8,
"Whether the filter in the Designs tab should remember its input and start with its list filtered identically to the last session."u8,
config.RememberDesignFilter, v => config.RememberDesignFilter = v);
Checkbox("Remember Actor Filter Across Sessions"u8,
"Whether the filter in the Actors tab should remember its input and start with its list filtered identically to the last session."u8,
config.RememberActorFilter, v => config.RememberActorFilter = v);
Checkbox("Remember Automation Filters Across Sessions"u8,
"Whether the filters in the Automation tab should remember their respective inputs and start with their list filtered identically to the last session."u8,
config.RememberAutomationFilter, v => config.RememberAutomationFilter = v);
Checkbox("Remember NPC Filter Across Sessions"u8,
"Whether the filter in the NPCs tab should remember its input and start with its list filtered identically to the last session."u8,
config.RememberNpcFilter, v => config.RememberNpcFilter = v);
Checkbox("Remember Unlocks Filters Across Sessions"u8,
"Whether the filters in the Unlocks tab should remember their respective inputs and start with its table filtered identically to the last session."u8,
config.RememberUnlocksFilters, v => config.RememberUnlocksFilters = v);
Im.Line.New(); Im.Line.New();
} }

View file

@ -16,6 +16,30 @@ public readonly struct UnlockCacheItem(in EquipItem item, in EquipItem offhand,
Two = 4, Two = 4,
} }
[Flags]
public enum Modded
{
Relevant = 1,
Ignored = 2,
None = 4,
}
public const Modded ModdedAll = Modded.Relevant | Modded.Ignored | Modded.None;
public const Dyability DyableAll = Dyability.No | Dyability.Yes | Dyability.Two;
public const EquipFlag SlotsAll = EquipFlag.Head
| EquipFlag.Body
| EquipFlag.Hands
| EquipFlag.Legs
| EquipFlag.Feet
| EquipFlag.Ears
| EquipFlag.Neck
| EquipFlag.Wrist
| EquipFlag.RFinger
| EquipFlag.Mainhand
| EquipFlag.Offhand;
public readonly EquipItem Item = item; public readonly EquipItem Item = item;
public readonly StringPair Name = new(item.Name); public readonly StringPair Name = new(item.Name);
public readonly EquipFlag Slot = item.Type.ToSlot().ToFlag(); public readonly EquipFlag Slot = item.Type.ToSlot().ToFlag();

View file

@ -27,10 +27,22 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>,
private readonly IgnoredMods _ignoredMods; private readonly IgnoredMods _ignoredMods;
public UnlockTable(JobService jobs, ItemManager items, ItemUnlockManager unlocks, PenumbraChangedItemTooltip tooltip, public UnlockTable(JobService jobs, ItemManager items, ItemUnlockManager unlocks, PenumbraChangedItemTooltip tooltip,
ObjectUnlocked unlockEvent, FavoriteManager favorites, PenumbraService penumbra, TextureService textures, IgnoredMods ignoredMods) ObjectUnlocked unlockEvent, FavoriteManager favorites, PenumbraService penumbra, TextureService textures, IgnoredMods ignoredMods,
: base(new StringU8("Unlock Table"u8), new FavoriteColumn(favorites), new ModdedColumn(), new NameColumn(textures, tooltip), Configuration config)
new SlotColumn(), new TypeColumn(), new UnlockDateColumn(), new ItemIdColumn(), new ModelDataColumn(), new JobColumn(jobs), : base(new StringU8("Unlock Table"u8),
new RequiredLevelColumn(), new DyableColumn(), new CrestColumn(), new TradableColumn()) new FavoriteColumn(config, favorites),
new ModdedColumn(config),
new NameColumn(config, textures, tooltip),
new SlotColumn(config),
new TypeColumn(config),
new UnlockDateColumn(config),
new ItemIdColumn(config),
new ModelDataColumn(config),
new JobColumn(config, jobs),
new RequiredLevelColumn(config),
new DyableColumn(config),
new CrestColumn(config),
new TradableColumn(config))
{ {
_jobs = jobs; _jobs = jobs;
_items = items; _items = items;
@ -86,12 +98,15 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>,
{ {
private readonly FavoriteManager _favorites; private readonly FavoriteManager _favorites;
public FavoriteColumn(FavoriteManager favorites) public FavoriteColumn(Configuration config, FavoriteManager favorites)
{ {
_favorites = favorites; _favorites = favorites;
Flags |= TableColumnFlags.NoResize; Flags |= TableColumnFlags.NoResize;
Label = new StringU8("F"u8); Label = new StringU8("F"u8);
FilterLabel = new StringU8("Favorite"u8); FilterLabel = new StringU8("Favorite"u8);
if (config.RememberUnlocksFilters)
Filter.LoadValue(config.Filters.UnlocksFavoriteFilter);
Filter.FilterChanged += () => config.Filters.UnlocksFavoriteFilter = Filter.FilterValue;
} }
public override float ComputeWidth(IEnumerable<UnlockCacheItem> allItems) public override float ComputeWidth(IEnumerable<UnlockCacheItem> allItems)
@ -104,23 +119,18 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>,
=> item.Favorite; => item.Favorite;
} }
private sealed class ModdedColumn : FlagColumn<ModdedColumn.Modded, UnlockCacheItem> private sealed class ModdedColumn : FlagColumn<UnlockCacheItem.Modded, UnlockCacheItem>
{ {
[Flags]
public enum Modded
{
Relevant = 1,
Ignored = 2,
None = 4,
}
private static readonly AwesomeIcon Dot = FontAwesomeIcon.Circle; private static readonly AwesomeIcon Dot = FontAwesomeIcon.Circle;
private static readonly AwesomeIcon Hollow = FontAwesomeIcon.DotCircle; private static readonly AwesomeIcon Hollow = FontAwesomeIcon.DotCircle;
public ModdedColumn() public ModdedColumn(Configuration config)
{ {
Flags |= TableColumnFlags.NoResize; Flags |= TableColumnFlags.NoResize;
Label = new StringU8("M"); Label = new StringU8("M");
if (config.RememberUnlocksFilters)
Filter.LoadValue(config.Filters.UnlocksModdedFilter);
Filter.FilterChanged += () => config.Filters.UnlocksModdedFilter = Filter.FilterValue;
} }
public override float ComputeWidth(IEnumerable<UnlockCacheItem> allItems) public override float ComputeWidth(IEnumerable<UnlockCacheItem> allItems)
@ -149,18 +159,19 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>,
} }
} }
protected override Modded GetValue(in UnlockCacheItem item, int globalIndex) protected override UnlockCacheItem.Modded GetValue(in UnlockCacheItem item, int globalIndex)
=> item.RelevantMods > 0 ? Modded.Relevant : item.Mods.Length > 0 ? Modded.Ignored : Modded.None; => item.RelevantMods > 0 ? UnlockCacheItem.Modded.Relevant :
item.Mods.Length > 0 ? UnlockCacheItem.Modded.Ignored : UnlockCacheItem.Modded.None;
protected override StringU8 DisplayString(in UnlockCacheItem item, int globalIndex) protected override StringU8 DisplayString(in UnlockCacheItem item, int globalIndex)
=> StringU8.Empty; => StringU8.Empty;
protected override IReadOnlyList<(Modded Value, StringU8 Name)> EnumData protected override IReadOnlyList<(UnlockCacheItem.Modded Value, StringU8 Name)> EnumData
=> =>
[ [
(Modded.Relevant, new StringU8("Any Relevant Mods"u8)), (UnlockCacheItem.Modded.Relevant, new StringU8("Any Relevant Mods"u8)),
(Modded.Ignored, new StringU8("Only Ignored Mods"u8)), (UnlockCacheItem.Modded.Ignored, new StringU8("Only Ignored Mods"u8)),
(Modded.None, new StringU8("Unmodded"u8)), (UnlockCacheItem.Modded.None, new StringU8("Unmodded"u8)),
]; ];
@ -179,13 +190,16 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>,
private readonly TextureService _textures; private readonly TextureService _textures;
private readonly PenumbraChangedItemTooltip _tooltip; private readonly PenumbraChangedItemTooltip _tooltip;
public NameColumn(TextureService textures, PenumbraChangedItemTooltip tooltip) public NameColumn(Configuration config, TextureService textures, PenumbraChangedItemTooltip tooltip)
{ {
_textures = textures; _textures = textures;
_tooltip = tooltip; _tooltip = tooltip;
Flags |= TableColumnFlags.NoHide | TableColumnFlags.NoReorder; Flags |= TableColumnFlags.NoHide | TableColumnFlags.NoReorder;
Label = new StringU8("Item Name..."u8); Label = new StringU8("Item Name..."u8);
UnscaledWidth = 400; UnscaledWidth = 400;
if (config.RememberUnlocksFilters)
Filter.Set(config.Filters.UnlocksNameFilter);
Filter.FilterChanged += () => config.Filters.UnlocksNameFilter = Filter.Text;
} }
public override void DrawColumn(in UnlockCacheItem item, int _) public override void DrawColumn(in UnlockCacheItem item, int _)
@ -215,8 +229,13 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>,
private sealed class TypeColumn : TextColumn<UnlockCacheItem> private sealed class TypeColumn : TextColumn<UnlockCacheItem>
{ {
public TypeColumn() public TypeColumn(Configuration config)
=> Label = new StringU8("Item Type..."u8); {
Label = new StringU8("Item Type..."u8);
if (config.RememberUnlocksFilters)
Filter.Set(config.Filters.UnlocksTypeFilter);
Filter.FilterChanged += () => config.Filters.UnlocksTypeFilter = Filter.Text;
}
public override float ComputeWidth(IEnumerable<UnlockCacheItem> _) public override float ComputeWidth(IEnumerable<UnlockCacheItem> _)
=> FullEquipType.CrossPeinHammer.ToNameU8().CalculateSize().X; => FullEquipType.CrossPeinHammer.ToNameU8().CalculateSize().X;
@ -233,10 +252,13 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>,
private sealed class SlotColumn : FlagColumn<EquipFlag, UnlockCacheItem> private sealed class SlotColumn : FlagColumn<EquipFlag, UnlockCacheItem>
{ {
public SlotColumn() public SlotColumn(Configuration config)
{ {
Flags &= ~TableColumnFlags.NoResize; Flags &= ~TableColumnFlags.NoResize;
Label = new StringU8("Equip Slot"u8); Label = new StringU8("Equip Slot"u8);
if (config.RememberUnlocksFilters)
Filter.LoadValue(config.Filters.UnlocksSlotFilter);
Filter.FilterChanged += () => config.Filters.UnlocksSlotFilter = Filter.FilterValue;
} }
public override float ComputeWidth(IEnumerable<UnlockCacheItem> _) public override float ComputeWidth(IEnumerable<UnlockCacheItem> _)
@ -269,11 +291,14 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>,
private sealed class UnlockDateColumn : YesNoColumn<UnlockCacheItem> private sealed class UnlockDateColumn : YesNoColumn<UnlockCacheItem>
{ {
public UnlockDateColumn() public UnlockDateColumn(Configuration config)
{ {
Flags &= ~TableColumnFlags.NoResize; Flags &= ~TableColumnFlags.NoResize;
Label = new StringU8("Unlocked"u8); Label = new StringU8("Unlocked"u8);
FilterLabel = Label; FilterLabel = Label;
if (config.RememberUnlocksFilters)
Filter.LoadValue(config.Filters.UnlocksUnlockedFilter);
Filter.FilterChanged += () => config.Filters.UnlocksUnlockedFilter = Filter.FilterValue;
} }
public override float ComputeWidth(IEnumerable<UnlockCacheItem> allItems) public override float ComputeWidth(IEnumerable<UnlockCacheItem> allItems)
@ -297,10 +322,13 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>,
private sealed class ItemIdColumn : NumberColumn<uint, UnlockCacheItem> private sealed class ItemIdColumn : NumberColumn<uint, UnlockCacheItem>
{ {
public ItemIdColumn() public ItemIdColumn(Configuration config)
{ {
Label = new StringU8("Item Id..."u8); Label = new StringU8("Item Id..."u8);
UnscaledWidth = 70; UnscaledWidth = 70;
if (config.RememberUnlocksFilters)
Filter.Set(config.Filters.UnlocksItemIdFilter);
Filter.FilterChanged += () => config.Filters.UnlocksItemIdFilter = Filter.Text;
} }
public override uint ToValue(in UnlockCacheItem item, int globalIndex) public override uint ToValue(in UnlockCacheItem item, int globalIndex)
@ -315,10 +343,13 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>,
private sealed class ModelDataColumn : TextColumn<UnlockCacheItem> private sealed class ModelDataColumn : TextColumn<UnlockCacheItem>
{ {
public ModelDataColumn() public ModelDataColumn(Configuration config)
{ {
Label = new StringU8("Model Data..."u8); Label = new StringU8("Model Data..."u8);
UnscaledWidth = 100; UnscaledWidth = 100;
if (config.RememberUnlocksFilters)
Filter.Set(config.Filters.UnlocksModelDataFilter);
Filter.FilterChanged += () => config.Filters.UnlocksModelDataFilter = Filter.Text;
} }
public override void DrawColumn(in UnlockCacheItem item, int globalIndex) public override void DrawColumn(in UnlockCacheItem item, int globalIndex)
@ -363,10 +394,13 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>,
private sealed class RequiredLevelColumn : NumberColumn<byte, UnlockCacheItem> private sealed class RequiredLevelColumn : NumberColumn<byte, UnlockCacheItem>
{ {
public RequiredLevelColumn() public RequiredLevelColumn(Configuration config)
{ {
Label = new StringU8("Level..."u8); Label = new StringU8("Level..."u8);
UnscaledWidth = 70; UnscaledWidth = 70;
if (config.RememberUnlocksFilters)
Filter.Set(config.Filters.UnlocksLevelFilter);
Filter.FilterChanged += () => config.Filters.UnlocksLevelFilter = Filter.Text;
} }
public override byte ToValue(in UnlockCacheItem item, int globalIndex) public override byte ToValue(in UnlockCacheItem item, int globalIndex)
@ -383,7 +417,7 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>,
{ {
private readonly JobService _jobs; private readonly JobService _jobs;
public JobColumn(JobService jobs) public JobColumn(Configuration config, JobService jobs)
: base(false) : base(false)
{ {
_jobs = jobs; _jobs = jobs;
@ -392,6 +426,9 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>,
Label = new StringU8("Jobs"u8); Label = new StringU8("Jobs"u8);
Filter = new JobFilter(this); Filter = new JobFilter(this);
UnscaledWidth = 200; UnscaledWidth = 200;
if (config.RememberUnlocksFilters)
Filter.LoadValue(config.Filters.UnlocksJobFilter);
Filter.FilterChanged += () => config.Filters.UnlocksJobFilter = Filter.FilterValue;
} }
protected override StringU8 DisplayString(in UnlockCacheItem item, int globalIndex) protected override StringU8 DisplayString(in UnlockCacheItem item, int globalIndex)
@ -444,10 +481,13 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>,
private sealed class DyableColumn : FlagColumn<UnlockCacheItem.Dyability, UnlockCacheItem> private sealed class DyableColumn : FlagColumn<UnlockCacheItem.Dyability, UnlockCacheItem>
{ {
public DyableColumn() public DyableColumn(Configuration config)
{ {
Flags &= ~TableColumnFlags.NoResize; Flags &= ~TableColumnFlags.NoResize;
Label = new StringU8("Dye"u8); Label = new StringU8("Dye"u8);
if (config.RememberUnlocksFilters)
Filter.LoadValue(config.Filters.UnlocksDyabilityFilter);
Filter.FilterChanged += () => config.Filters.UnlocksDyabilityFilter = Filter.FilterValue;
} }
public override float ComputeWidth(IEnumerable<UnlockCacheItem> _) public override float ComputeWidth(IEnumerable<UnlockCacheItem> _)
@ -487,8 +527,13 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>,
private sealed class TradableColumn : LunaStyle.YesNoColumn<UnlockCacheItem> private sealed class TradableColumn : LunaStyle.YesNoColumn<UnlockCacheItem>
{ {
public TradableColumn() public TradableColumn(Configuration config)
=> Label = new StringU8("Trade"u8); {
Label = new StringU8("Trade"u8);
if (config.RememberUnlocksFilters)
Filter.LoadValue(config.Filters.UnlocksTradableFilter);
Filter.FilterChanged += () => config.Filters.UnlocksTradableFilter = Filter.FilterValue;
}
protected override bool GetValue(in UnlockCacheItem item, int globalIndex, int triEnumIndex) protected override bool GetValue(in UnlockCacheItem item, int globalIndex, int triEnumIndex)
=> item.Tradable; => item.Tradable;
@ -496,8 +541,13 @@ public sealed class UnlockTable : TableBase<UnlockCacheItem, UnlockTable.Cache>,
private sealed class CrestColumn : LunaStyle.YesNoColumn<UnlockCacheItem> private sealed class CrestColumn : LunaStyle.YesNoColumn<UnlockCacheItem>
{ {
public CrestColumn() public CrestColumn(Configuration config)
=> Label = new StringU8("Crest"u8); {
Label = new StringU8("Crest"u8);
if (config.RememberUnlocksFilters)
Filter.LoadValue(config.Filters.UnlocksCrestFilter);
Filter.FilterChanged += () => config.Filters.UnlocksCrestFilter = Filter.FilterValue;
}
protected override bool GetValue(in UnlockCacheItem item, int globalIndex, int triEnumIndex) protected override bool GetValue(in UnlockCacheItem item, int globalIndex, int triEnumIndex)
=> item.Crest; => item.Crest;

View file

@ -35,6 +35,15 @@ public readonly record struct ModSettings(Dictionary<string, List<string>> Setti
=> new(); => new();
} }
public sealed class CutsceneResolveService : IService
{
public Func<int, int>? CheckCutsceneParent;
/// <summary> Obtain the parent of a cutscene actor if it is known. </summary>
public short CutsceneParent(ushort idx)
=> (short)(CheckCutsceneParent?.Invoke(idx) ?? -1);
}
public sealed class PenumbraService : IDisposable, IService public sealed class PenumbraService : IDisposable, IService
{ {
public const int RequiredPenumbraBreakingVersion = 5; public const int RequiredPenumbraBreakingVersion = 5;
@ -45,6 +54,8 @@ public sealed class PenumbraService : IDisposable, IService
private const int KeyManual = -6160; private const int KeyManual = -6160;
private const string NameManual = "Glamourer (Manually)"; private const string NameManual = "Glamourer (Manually)";
public readonly CutsceneResolveService ResolveService;
private readonly IDalamudPluginInterface _pluginInterface; private readonly IDalamudPluginInterface _pluginInterface;
private readonly Configuration _config; private readonly Configuration _config;
private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber; private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
@ -84,7 +95,6 @@ public sealed class PenumbraService : IDisposable, IService
private global::Penumbra.Api.IpcSubscribers.UnregisterSettingsSection? _unregisterSettingsSection; private global::Penumbra.Api.IpcSubscribers.UnregisterSettingsSection? _unregisterSettingsSection;
private IReadOnlyList<(string ModDirectory, IReadOnlyDictionary<string, object?> ChangedItems)>? _changedItems; private IReadOnlyList<(string ModDirectory, IReadOnlyDictionary<string, object?> ChangedItems)>? _changedItems;
private Func<string, (string ModDirectory, string ModName)[]>? _checkCurrentChangedItems; private Func<string, (string ModDirectory, string ModName)[]>? _checkCurrentChangedItems;
private Func<int, int>? _checkCutsceneParent;
private Func<nint, nint>? _getGameObject; private Func<nint, nint>? _getGameObject;
private readonly IDisposable _initializedEvent; private readonly IDisposable _initializedEvent;
@ -97,10 +107,12 @@ public sealed class PenumbraService : IDisposable, IService
public int CurrentMinor { get; private set; } public int CurrentMinor { get; private set; }
public DateTime AttachTime { get; private set; } public DateTime AttachTime { get; private set; }
public PenumbraService(IDalamudPluginInterface pi, PenumbraReloaded penumbraReloaded, Configuration config) public PenumbraService(IDalamudPluginInterface pi, PenumbraReloaded penumbraReloaded, CutsceneResolveService resolveService,
Configuration config)
{ {
_pluginInterface = pi; _pluginInterface = pi;
_penumbraReloaded = penumbraReloaded; _penumbraReloaded = penumbraReloaded;
ResolveService = resolveService;
_config = config; _config = config;
_initializedEvent = global::Penumbra.Api.IpcSubscribers.Initialized.Subscriber(pi, Reattach); _initializedEvent = global::Penumbra.Api.IpcSubscribers.Initialized.Subscriber(pi, Reattach);
_disposedEvent = global::Penumbra.Api.IpcSubscribers.Disposed.Subscriber(pi, Unattach); _disposedEvent = global::Penumbra.Api.IpcSubscribers.Disposed.Subscriber(pi, Unattach);
@ -192,7 +204,7 @@ public sealed class PenumbraService : IDisposable, IService
private ModSettings GetSettings(Guid collection, string modDirectory, string modName, out string source) private ModSettings GetSettings(Guid collection, string modDirectory, string modName, out string source)
{ {
if (_getCurrentSettingsWithTemp != null) if (_getCurrentSettingsWithTemp is not null)
{ {
source = string.Empty; source = string.Empty;
var (ec, tuple) = _getCurrentSettingsWithTemp!.Invoke(collection, modDirectory, modName, false, false, KeyFixed); var (ec, tuple) = _getCurrentSettingsWithTemp!.Invoke(collection, modDirectory, modName, false, false, KeyFixed);
@ -204,7 +216,7 @@ public sealed class PenumbraService : IDisposable, IService
: ModSettings.Empty; : ModSettings.Empty;
} }
if (_queryTemporaryModSettings != null) if (_queryTemporaryModSettings is not null)
{ {
var tempEc = _queryTemporaryModSettings.Invoke(collection, modDirectory, out var tempTuple, out source, 0, modName); var tempEc = _queryTemporaryModSettings.Invoke(collection, modDirectory, out var tempTuple, out source, 0, modName);
if (tempEc is PenumbraApiEc.Success && tempTuple != null) if (tempEc is PenumbraApiEc.Success && tempTuple != null)
@ -485,9 +497,6 @@ public sealed class PenumbraService : IDisposable, IService
public Actor GameObjectFromDrawObject(Model drawObject) public Actor GameObjectFromDrawObject(Model drawObject)
=> _getGameObject?.Invoke(drawObject.Address) ?? Actor.Null; => _getGameObject?.Invoke(drawObject.Address) ?? Actor.Null;
/// <summary> Obtain the parent of a cutscene actor if it is known. </summary>
public short CutsceneParent(ushort idx)
=> (short)(_checkCutsceneParent?.Invoke(idx) ?? -1);
/// <summary> Try to redraw the given actor. </summary> /// <summary> Try to redraw the given actor. </summary>
public void RedrawObject(Actor actor, RedrawType settings) public void RedrawObject(Actor actor, RedrawType settings)
@ -554,7 +563,7 @@ public sealed class PenumbraService : IDisposable, IService
_collectionByIdentifier = new global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier(_pluginInterface); _collectionByIdentifier = new global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier(_pluginInterface);
_collections = new global::Penumbra.Api.IpcSubscribers.GetCollections(_pluginInterface); _collections = new global::Penumbra.Api.IpcSubscribers.GetCollections(_pluginInterface);
_redraw = new global::Penumbra.Api.IpcSubscribers.RedrawObject(_pluginInterface); _redraw = new global::Penumbra.Api.IpcSubscribers.RedrawObject(_pluginInterface);
_checkCutsceneParent = new global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndexFunc(_pluginInterface).Invoke(); ResolveService.CheckCutsceneParent = new global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndexFunc(_pluginInterface).Invoke();
_getGameObject = new global::Penumbra.Api.IpcSubscribers.GetGameObjectFromDrawObjectFunc(_pluginInterface).Invoke(); _getGameObject = new global::Penumbra.Api.IpcSubscribers.GetGameObjectFromDrawObjectFunc(_pluginInterface).Invoke();
_objectCollection = new global::Penumbra.Api.IpcSubscribers.GetCollectionForObject(_pluginInterface); _objectCollection = new global::Penumbra.Api.IpcSubscribers.GetCollectionForObject(_pluginInterface);
_getMods = new global::Penumbra.Api.IpcSubscribers.GetModList(_pluginInterface); _getMods = new global::Penumbra.Api.IpcSubscribers.GetModList(_pluginInterface);
@ -623,7 +632,7 @@ public sealed class PenumbraService : IDisposable, IService
_collections = null; _collections = null;
_redraw = null; _redraw = null;
_getGameObject = null; _getGameObject = null;
_checkCutsceneParent = null; ResolveService.CheckCutsceneParent = null;
_objectCollection = null; _objectCollection = null;
_getMods = null; _getMods = null;
_currentCollection = null; _currentCollection = null;

View file

@ -10,7 +10,7 @@ public sealed class BackupService(Logger log, FilenameService provider) : BaseBa
var list = new List<FileInfo>(16) var list = new List<FileInfo>(16)
{ {
new(fileNames.ConfigurationFile), new(fileNames.ConfigurationFile),
new(fileNames.UiConfiguration), new(fileNames.UiConfigurationFile),
new(fileNames.MigrationDesignFileSystem), new(fileNames.MigrationDesignFileSystem),
new(fileNames.MigrationDesignFile), new(fileNames.MigrationDesignFile),
new(fileNames.AutomationFile), new(fileNames.AutomationFile),

View file

@ -18,7 +18,8 @@ public sealed class FilenameService(IDalamudPluginInterface pi) : BaseFilePathPr
public readonly string EphemeralConfigFile = Path.Combine(pi.ConfigDirectory.FullName, "ephemeral_config.json"); public readonly string EphemeralConfigFile = Path.Combine(pi.ConfigDirectory.FullName, "ephemeral_config.json");
public readonly string NpcAppearanceFile = Path.Combine(pi.ConfigDirectory.FullName, "npc_appearance_data.json"); public readonly string NpcAppearanceFile = Path.Combine(pi.ConfigDirectory.FullName, "npc_appearance_data.json");
public readonly string CollectionOverrideFile = Path.Combine(pi.ConfigDirectory.FullName, "collection_overrides.json"); public readonly string CollectionOverrideFile = Path.Combine(pi.ConfigDirectory.FullName, "collection_overrides.json");
public readonly string UiConfiguration = Path.Combine(pi.ConfigDirectory.FullName, "ui_config.json"); public readonly string UiConfigurationFile = Path.Combine(pi.ConfigDirectory.FullName, "ui_config.json");
public readonly string FilterFile = Path.Combine(pi.ConfigDirectory.FullName, "filters.json");
public readonly string FileSystemFolder = Path.Combine(pi.ConfigDirectory.FullName, "design_filesystem"); public readonly string FileSystemFolder = Path.Combine(pi.ConfigDirectory.FullName, "design_filesystem");
public readonly string FileSystemEmptyFolders = Path.Combine(pi.ConfigDirectory.FullName, "design_filesystem", "empty_folders.json"); public readonly string FileSystemEmptyFolders = Path.Combine(pi.ConfigDirectory.FullName, "design_filesystem", "empty_folders.json");
public readonly string FileSystemExpandedFolders = Path.Combine(pi.ConfigDirectory.FullName, "design_filesystem", "expanded_folders.json"); public readonly string FileSystemExpandedFolders = Path.Combine(pi.ConfigDirectory.FullName, "design_filesystem", "expanded_folders.json");

View file

@ -18,7 +18,7 @@ public static class StaticServiceManager
.AddExistingService(log) .AddExistingService(log)
.AddSingleton<MessageService>() .AddSingleton<MessageService>()
.AddSingleton<ActorObjectManager>() .AddSingleton<ActorObjectManager>()
.AddSingleton(p => new CutsceneResolver(p.GetRequiredService<PenumbraService>().CutsceneParent)) .AddSingleton(p => new CutsceneResolver(p.GetRequiredService<CutsceneResolveService>().CutsceneParent))
.AddExistingService(glamourer); .AddExistingService(glamourer);
services.AddIServices(typeof(EquipItem).Assembly); services.AddIServices(typeof(EquipItem).Assembly);
services.AddIServices(typeof(Glamourer).Assembly); services.AddIServices(typeof(Glamourer).Assembly);

2
Luna

@ -1 +1 @@
Subproject commit 4a46e8551dd4438465c013c4a01d2936e5b4d4da Subproject commit bc900c5b5959915a87f60487f5b67aa358b195be

@ -1 +1 @@
Subproject commit 9cfcaf39fef363e281f066b55811deef72908f2f Subproject commit c7c1cae55b34c59580492a8e279205844e2116e2

@ -1 +1 @@
Subproject commit f75aa967763347148401ec2cb800dcba2523f8c0 Subproject commit 2d83cda9e72c132b90bba20dd862a442a218b6c2