diff --git a/Influx/AllaganTools/AllaganToolsIpc.cs b/Influx/AllaganTools/AllaganToolsIpc.cs index fce55e7..9c2b744 100644 --- a/Influx/AllaganTools/AllaganToolsIpc.cs +++ b/Influx/AllaganTools/AllaganToolsIpc.cs @@ -15,22 +15,27 @@ internal sealed class AllaganToolsIpc : IDisposable private readonly DalamudReflector _dalamudReflector; private readonly IFramework _framework; private readonly IPluginLog _pluginLog; - private readonly ICallGateSubscriber? _initalized; + private readonly ICallGateSubscriber? _initialized; private readonly ICallGateSubscriber? _isInitialized; + private readonly ICallGateSubscriber> _getSearchFilters; public ICharacterMonitor Characters { get; private set; } = new UnavailableCharacterMonitor(); public IInventoryMonitor Inventories { get; private set; } = new UnavailableInventoryMonitor(); + public IFilterService Filters { get; set; } = new UnavailableFilterService(); - public AllaganToolsIpc(DalamudPluginInterface pluginInterface, IChatGui chatGui, DalamudReflector dalamudReflector, IFramework framework, IPluginLog pluginLog) + public AllaganToolsIpc(DalamudPluginInterface pluginInterface, IChatGui chatGui, DalamudReflector dalamudReflector, + IFramework framework, IPluginLog pluginLog) { _chatGui = chatGui; _dalamudReflector = dalamudReflector; _framework = framework; _pluginLog = pluginLog; - _initalized = pluginInterface.GetIpcSubscriber("AllaganTools.Initialized"); + _initialized = pluginInterface.GetIpcSubscriber("AllaganTools.Initialized"); _isInitialized = pluginInterface.GetIpcSubscriber("AllaganTools.IsInitialized"); - _initalized.Subscribe(ConfigureIpc); + _initialized.Subscribe(ConfigureIpc); + _getSearchFilters = + pluginInterface.GetIpcSubscriber>("AllaganTools.GetSearchFilters"); try { @@ -58,6 +63,7 @@ internal sealed class AllaganToolsIpc : IDisposable Characters = new CharacterMonitor(pluginService.GetProperty("CharacterMonitor")!.GetValue(null)!); Inventories = new InventoryMonitor( pluginService.GetProperty("InventoryMonitor")!.GetValue(null)!); + Filters = new FilterService(pluginService.GetProperty("FilterService")!.GetValue(null)!); } else { @@ -72,6 +78,32 @@ internal sealed class AllaganToolsIpc : IDisposable }, TimeSpan.FromMilliseconds(100)); } + public Dictionary GetSearchFilters() + { + try + { + return _getSearchFilters.InvokeFunc(); + } + catch (IpcError e) + { + _pluginLog.Error(e, "Unable to retrieve allagantools filters"); + return new Dictionary(); + } + } + + public Filter? GetFilter(string keyOrName) + { + try + { + return Filters.GetFilterByKeyOrName(keyOrName); + } + catch (IpcError e) + { + _pluginLog.Error(e, $"Unable to retrieve filter items for filter '{keyOrName}'"); + return null; + } + } + public Dictionary CountCurrencies() { _pluginLog.Debug($"{Characters.GetType()}, {Inventories.GetType()}"); @@ -98,9 +130,10 @@ internal sealed class AllaganToolsIpc : IDisposable public void Dispose() { - _initalized?.Unsubscribe(ConfigureIpc); + _initialized?.Unsubscribe(ConfigureIpc); Characters = new UnavailableCharacterMonitor(); Inventories = new UnavailableInventoryMonitor(); + Filters = new UnavailableFilterService(); } private sealed class InventoryWrapper diff --git a/Influx/AllaganTools/Filter.cs b/Influx/AllaganTools/Filter.cs new file mode 100644 index 0000000..8563d4c --- /dev/null +++ b/Influx/AllaganTools/Filter.cs @@ -0,0 +1,29 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace Influx.AllaganTools; + +internal sealed class Filter +{ + private readonly object _delegate; + private readonly MethodInfo _generateFilteredList; + + public Filter(object @delegate) + { + _delegate = @delegate; + _generateFilteredList = _delegate.GetType().GetMethod("GenerateFilteredList")!; + } + + public IReadOnlyList GenerateFilteredList() + { + Task task = (Task)_generateFilteredList.Invoke(_delegate, new object?[] { null })!; + object result = task.GetType().GetProperty("Result")!.GetValue(task)!; + return ((IEnumerable)result.GetType().GetProperty("SortedItems")!.GetValue(result)!) + .Cast() + .Select(x => new SortingResult(x)) + .ToList(); + } +} diff --git a/Influx/AllaganTools/FilterService.cs b/Influx/AllaganTools/FilterService.cs new file mode 100644 index 0000000..3fee3f7 --- /dev/null +++ b/Influx/AllaganTools/FilterService.cs @@ -0,0 +1,21 @@ +using System.Reflection; + +namespace Influx.AllaganTools; + +internal sealed class FilterService : IFilterService +{ + private readonly object _delegate; + private readonly MethodInfo _getFilterByKeyOrName; + + public FilterService(object @delegate) + { + _delegate = @delegate; + _getFilterByKeyOrName = _delegate.GetType().GetMethod("GetFilterByKeyOrName")!; + } + + public Filter? GetFilterByKeyOrName(string keyOrName) + { + var f = _getFilterByKeyOrName.Invoke(_delegate, new object?[] { keyOrName }); + return f != null ? new Filter(f) : null; + } +} diff --git a/Influx/AllaganTools/IFilterService.cs b/Influx/AllaganTools/IFilterService.cs new file mode 100644 index 0000000..ae52de4 --- /dev/null +++ b/Influx/AllaganTools/IFilterService.cs @@ -0,0 +1,6 @@ +namespace Influx.AllaganTools; + +internal interface IFilterService +{ + Filter? GetFilterByKeyOrName(string keyOrName); +} diff --git a/Influx/AllaganTools/SortingResult.cs b/Influx/AllaganTools/SortingResult.cs new file mode 100644 index 0000000..ce1a4d2 --- /dev/null +++ b/Influx/AllaganTools/SortingResult.cs @@ -0,0 +1,27 @@ +namespace Influx.AllaganTools; + +using ItemFlags = FFXIVClientStructs.FFXIV.Client.Game.InventoryItem.ItemFlags; +internal sealed class SortingResult +{ + public SortingResult(object @delegate) + { + LocalContentId = (ulong)@delegate.GetType().GetProperty("SourceRetainerId")!.GetValue(@delegate)!; + Quantity = (int)@delegate.GetType().GetProperty("Quantity")!.GetValue(@delegate)!; + + var inventoryItem = @delegate.GetType().GetProperty("InventoryItem")!.GetValue(@delegate)!; + ItemId = (uint)inventoryItem.GetType().GetField("ItemId")!.GetValue(inventoryItem)!; + Flags = (ItemFlags)inventoryItem.GetType().GetField("Flags")!.GetValue(inventoryItem)!; + } + + public ulong LocalContentId { get; } + public uint ItemId { get; } + public ItemFlags Flags { get; } + public int Quantity { get; } + + public bool IsHq => Flags.HasFlag(ItemFlags.HQ); + + public override string ToString() + { + return $"{LocalContentId}, {ItemId}, {Quantity}"; + } +} diff --git a/Influx/AllaganTools/UnavailableFilterService.cs b/Influx/AllaganTools/UnavailableFilterService.cs new file mode 100644 index 0000000..ebbf3a9 --- /dev/null +++ b/Influx/AllaganTools/UnavailableFilterService.cs @@ -0,0 +1,6 @@ +namespace Influx.AllaganTools; + +internal sealed class UnavailableFilterService : IFilterService +{ + public Filter? GetFilterByKeyOrName(string keyOrName) => null; +} diff --git a/Influx/Configuration.cs b/Influx/Configuration.cs index 25ee867..8a6cf10 100644 --- a/Influx/Configuration.cs +++ b/Influx/Configuration.cs @@ -10,6 +10,7 @@ public sealed class Configuration : IPluginConfiguration public ServerConfiguration Server { get; set; } = new(); public List IncludedCharacters { get; set; } = new(); + public List IncludedInventoryFilters { get; set; } = new(); public sealed class ServerConfiguration { @@ -27,4 +28,9 @@ public sealed class Configuration : IPluginConfiguration public string? CachedWorldName { get; set; } public bool IncludeFreeCompany { get; set; } = true; } + + public sealed class FilterInfo + { + public required string Name { get; set; } + } } diff --git a/Influx/Influx.csproj b/Influx/Influx.csproj index f8ed94c..5167c1d 100644 --- a/Influx/Influx.csproj +++ b/Influx/Influx.csproj @@ -1,7 +1,7 @@ net7.0-windows - 0.8 + 0.9 11.0 enable true diff --git a/Influx/Influx/InfluxStatisticsClient.cs b/Influx/Influx/InfluxStatisticsClient.cs index db7a5d0..0c0419c 100644 --- a/Influx/Influx/InfluxStatisticsClient.cs +++ b/Influx/Influx/InfluxStatisticsClient.cs @@ -7,6 +7,7 @@ using Influx.AllaganTools; using Influx.LocalStatistics; using InfluxDB.Client; using InfluxDB.Client.Api.Domain; +using InfluxDB.Client.Core; using InfluxDB.Client.Writes; using Lumina.Excel.GeneratedSheets; using GrandCompany = FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany; @@ -22,7 +23,8 @@ internal sealed class InfluxStatisticsClient : IDisposable private readonly IPluginLog _pluginLog; private readonly IReadOnlyDictionary _classJobToArrayIndex; private readonly IReadOnlyDictionary _classJobNames; - private readonly Dictionary _expToJobs; + private readonly IReadOnlyDictionary _expToJobs; + private readonly IReadOnlyDictionary _prices; public InfluxStatisticsClient(IChatGui chatGui, Configuration configuration, IDataManager dataManager, IClientState clientState, IPluginLog pluginLog) @@ -41,6 +43,13 @@ internal sealed class InfluxStatisticsClient : IDisposable .Where(x => x.JobIndex > 0) .Where(x => x.Abbreviation.ToString() != "SMN") .ToDictionary(x => x.ExpArrayIndex, x => x.Abbreviation.ToString()); + _prices = dataManager.GetExcelSheet()! + .ToDictionary(x => x.RowId, x => new PriceInfo + { + Name = x.Name.ToString(), + Normal = x.PriceLow, + UiCategory = x.ItemUICategory.Row, + }); } public bool Enabled => _configuration.Server.Enabled && @@ -84,143 +93,16 @@ internal sealed class InfluxStatisticsClient : IDisposable { if (character.CharacterType == CharacterType.Character) { - update.LocalStats.TryGetValue(character, out LocalStats? localStats); - - bool includeFc = character.FreeCompanyId > 0 && - _configuration.IncludedCharacters.Any(x => - x.LocalContentId == character.CharacterId && x.IncludeFreeCompany); - - values.Add(PointData.Measurement("currency") - .Tag("id", character.CharacterId.ToString()) - .Tag("player_name", character.Name) - .Tag("type", character.CharacterType.ToString()) - .Tag("fc_id", includeFc ? character.FreeCompanyId.ToString() : null) - .Field("gil", localStats?.Gil ?? currencies.Gil) - .Field("mgp", localStats?.MGP ?? 0) - .Field("ventures", currencies.Ventures) - .Field("ceruleum_tanks", currencies.CeruleumTanks) - .Field("repair_kits", currencies.RepairKits) - .Timestamp(date, WritePrecision.S)); - - if (localStats != null) - { - values.Add(PointData.Measurement("grandcompany") - .Tag("id", character.CharacterId.ToString()) - .Tag("player_name", character.Name) - .Tag("type", character.CharacterType.ToString()) - .Tag("fc_id", includeFc ? character.FreeCompanyId.ToString() : null) - .Field("gc", localStats.GrandCompany) - .Field("gc_rank", localStats.GcRank) - .Field("seals", (GrandCompany)localStats.GrandCompany switch - { - GrandCompany.Maelstrom => currencies.GcSealsMaelstrom, - GrandCompany.TwinAdder => currencies.GcSealsTwinAdders, - GrandCompany.ImmortalFlames => currencies.GcSealsImmortalFlames, - _ => 0, - }) - .Field("seal_cap", localStats.GcRank switch - { - 1 => 10_000, - 2 => 15_000, - 3 => 20_000, - 4 => 25_000, - 5 => 30_000, - 6 => 35_000, - 7 => 40_000, - 8 => 45_000, - 9 => 50_000, - 10 => 80_000, - 11 => 90_000, - _ => 0, - }) - .Field("squadron_unlocked", localStats.SquadronUnlocked ? 1 : 0) - .Timestamp(date, WritePrecision.S)); - - if (localStats.ClassJobLevels.Count > 0) - { - foreach (var (expIndex, abbreviation) in _expToJobs) - { - var level = localStats.ClassJobLevels[expIndex]; - if (level > 0) - { - values.Add(PointData.Measurement("experience") - .Tag("id", character.CharacterId.ToString()) - .Tag("player_name", character.Name) - .Tag("type", character.CharacterType.ToString()) - .Tag("fc_id", includeFc ? character.FreeCompanyId.ToString() : null) - .Tag("job", abbreviation) - .Field("level", level) - .Timestamp(date, WritePrecision.S)); - } - } - } - - if (localStats.MsqCount != -1) - { - values.Add(PointData.Measurement("quests") - .Tag("id", character.CharacterId.ToString()) - .Tag("player_name", character.Name) - .Tag("msq_name", localStats.MsqName) - .Tag("fc_id", includeFc ? character.FreeCompanyId.ToString() : null) - .Field("msq_count", localStats.MsqCount) - .Field("msq_genre", localStats.MsqGenre) - .Timestamp(date, WritePrecision.S)); - } - } + values.AddRange(GenerateCharacterStats(character, currencies, update, date)); } else if (character.CharacterType == CharacterType.Retainer) { - var owner = currencyStats.Keys.First(x => x.CharacterId == character.OwnerId); - values.Add(PointData.Measurement("currency") - .Tag("id", character.CharacterId.ToString()) - .Tag("player_name", owner.Name) - .Tag("player_id", character.OwnerId.ToString()) - .Tag("type", character.CharacterType.ToString()) - .Tag("retainer_name", character.Name) - .Field("gil", currencies.Gil) - .Field("ceruleum_tanks", currencies.CeruleumTanks) - .Field("repair_kits", currencies.RepairKits) - .Timestamp(date, WritePrecision.S)); - - if (update.LocalStats.TryGetValue(owner, out var ownerStats) && character.ClassJob != 0) - { - values.Add(PointData.Measurement("retainer") - .Tag("id", character.CharacterId.ToString()) - .Tag("player_name", owner.Name) - .Tag("player_id", character.OwnerId.ToString()) - .Tag("type", character.CharacterType.ToString()) - .Tag("retainer_name", character.Name) - .Tag("class", _classJobNames[character.ClassJob]) - .Field("level", character.Level) - .Field("is_max_level", character.Level == ownerStats.MaxLevel ? 1 : 0) - .Field("can_reach_max_level", - ownerStats.ClassJobLevels.Count > 0 && - ownerStats.ClassJobLevels[_classJobToArrayIndex[character.ClassJob]] == - ownerStats.MaxLevel - ? 1 - : 0) - .Field("levels_before_cap", - ownerStats.ClassJobLevels.Count > 0 - ? ownerStats.ClassJobLevels[_classJobToArrayIndex[character.ClassJob]] - - character.Level - : 0) - .Timestamp(date, WritePrecision.S)); - } + values.AddRange(GenerateRetainerStats(character, currencies, update, date)); } else if (character.CharacterType == CharacterType.FreeCompanyChest && validFcIds.Contains(character.CharacterId)) { - update.FcStats.TryGetValue(character.CharacterId, out FcStats? fcStats); - - values.Add(PointData.Measurement("currency") - .Tag("id", character.CharacterId.ToString()) - .Tag("fc_name", character.Name) - .Tag("type", character.CharacterType.ToString()) - .Field("gil", currencies.Gil) - .Field("fccredit", fcStats?.FcCredits ?? 0) - .Field("ceruleum_tanks", currencies.CeruleumTanks) - .Field("repair_kits", currencies.RepairKits) - .Timestamp(date, WritePrecision.S)); + values.AddRange(GenerateFcStats(character, currencies, update, date)); } } @@ -263,8 +145,186 @@ internal sealed class InfluxStatisticsClient : IDisposable }); } + private IEnumerable GenerateCharacterStats(Character character, Currencies currencies, + StatisticsUpdate update, DateTime date) + { + update.LocalStats.TryGetValue(character, out LocalStats? localStats); + + bool includeFc = character.FreeCompanyId > 0 && + _configuration.IncludedCharacters.Any(x => + x.LocalContentId == character.CharacterId && x.IncludeFreeCompany); + + Func pointData = s => PointData.Measurement(s) + .Tag("id", character.CharacterId.ToString()) + .Tag("player_name", character.Name) + .Tag("type", character.CharacterType.ToString()) + .Tag("fc_id", includeFc ? character.FreeCompanyId.ToString() : null) + .Timestamp(date, WritePrecision.S); + + yield return pointData("currency") + .Field("gil", localStats?.Gil ?? currencies.Gil) + .Field("mgp", localStats?.MGP ?? 0) + .Field("ventures", currencies.Ventures) + .Field("ceruleum_tanks", currencies.CeruleumTanks) + .Field("repair_kits", currencies.RepairKits); + + if (localStats != null) + { + yield return pointData("grandcompany") + .Tag("id", character.CharacterId.ToString()) + .Tag("player_name", character.Name) + .Tag("type", character.CharacterType.ToString()) + .Tag("fc_id", includeFc ? character.FreeCompanyId.ToString() : null) + .Field("gc", localStats.GrandCompany) + .Field("gc_rank", localStats.GcRank) + .Field("seals", (GrandCompany)localStats.GrandCompany switch + { + GrandCompany.Maelstrom => currencies.GcSealsMaelstrom, + GrandCompany.TwinAdder => currencies.GcSealsTwinAdders, + GrandCompany.ImmortalFlames => currencies.GcSealsImmortalFlames, + _ => 0, + }) + .Field("seal_cap", localStats.GcRank switch + { + 1 => 10_000, + 2 => 15_000, + 3 => 20_000, + 4 => 25_000, + 5 => 30_000, + 6 => 35_000, + 7 => 40_000, + 8 => 45_000, + 9 => 50_000, + 10 => 80_000, + 11 => 90_000, + _ => 0, + }) + .Field("squadron_unlocked", localStats.SquadronUnlocked ? 1 : 0); + + if (localStats.ClassJobLevels.Count > 0) + { + foreach (var (expIndex, abbreviation) in _expToJobs) + { + var level = localStats.ClassJobLevels[expIndex]; + if (level > 0) + { + yield return pointData("experience") + .Tag("job", abbreviation) + .Field("level", level); + } + } + } + + if (localStats.MsqCount != -1) + { + yield return pointData("quests") + .Tag("msq_name", localStats.MsqName) + .Field("msq_count", localStats.MsqCount) + .Field("msq_genre", localStats.MsqGenre); + } + } + + foreach (var inventoryPoint in GenerateInventoryStats(character.CharacterId, update, pointData)) + yield return inventoryPoint; + } + + private IEnumerable GenerateRetainerStats(Character character, Currencies currencies, + StatisticsUpdate update, DateTime date) + { + var owner = update.Currencies.Keys.First(x => x.CharacterId == character.OwnerId); + + Func pointData = s => PointData.Measurement(s) + .Tag("id", character.CharacterId.ToString()) + .Tag("player_name", owner.Name) + .Tag("player_id", character.OwnerId.ToString()) + .Tag("type", character.CharacterType.ToString()) + .Tag("retainer_name", character.Name) + .Timestamp(date, WritePrecision.S); + + yield return pointData("currency") + .Field("gil", currencies.Gil) + .Field("ceruleum_tanks", currencies.CeruleumTanks) + .Field("repair_kits", currencies.RepairKits); + + if (update.LocalStats.TryGetValue(owner, out var ownerStats) && character.ClassJob != 0) + { + yield return pointData("retainer") + .Tag("class", _classJobNames[character.ClassJob]) + .Field("level", character.Level) + .Field("is_max_level", character.Level == ownerStats.MaxLevel ? 1 : 0) + .Field("can_reach_max_level", + ownerStats.ClassJobLevels.Count > 0 && + ownerStats.ClassJobLevels[_classJobToArrayIndex[character.ClassJob]] == + ownerStats.MaxLevel + ? 1 + : 0) + .Field("levels_before_cap", + ownerStats.ClassJobLevels.Count > 0 + ? ownerStats.ClassJobLevels[_classJobToArrayIndex[character.ClassJob]] - + character.Level + : 0); + } + + + foreach (var inventoryPoint in GenerateInventoryStats(character.CharacterId, update, pointData)) + yield return inventoryPoint; + } + + private IEnumerable GenerateInventoryStats(ulong localContentId, StatisticsUpdate update, + Func pointData) + { + foreach (var (filterName, items) in update.InventoryItems) + { + foreach (var item in items.Where(x => x.LocalContentId == localContentId) + .GroupBy(x => new { x.ItemId, x.IsHq })) + { + _prices.TryGetValue(item.Key.ItemId, out PriceInfo priceInfo); + _pluginLog.Information($"CH {localContentId} → {priceInfo.Name} x {item.Sum(x => x.Quantity)}"); + + bool priceHq = item.Key.IsHq || priceInfo.UiCategory == 58; // materia always uses HQ prices + + yield return pointData("items") + .Tag("filter_name", filterName) + .Tag("item_id", item.Key.ItemId.ToString()) + .Tag("item_name", priceInfo.Name) + .Tag("hq", (item.Key.IsHq ? 1 : 0).ToString()) + .Field("quantity", item.Sum(x => x.Quantity)) + .Field("total_gil", item.Sum(x => x.Quantity) * (priceHq ? priceInfo.Hq : priceInfo.Normal)); + } + } + } + + private IEnumerable GenerateFcStats(Character character, Currencies currencies, StatisticsUpdate update, + DateTime date) + { + update.FcStats.TryGetValue(character.CharacterId, out FcStats? fcStats); + + Func pointData = s => PointData.Measurement(s) + .Tag("id", character.CharacterId.ToString()) + .Tag("fc_name", character.Name) + .Tag("type", character.CharacterType.ToString()) + .Timestamp(date, WritePrecision.S); + + yield return pointData("currency") + .Field("gil", currencies.Gil) + .Field("fccredit", fcStats?.FcCredits ?? 0) + .Field("ceruleum_tanks", currencies.CeruleumTanks) + .Field("repair_kits", currencies.RepairKits); + + foreach (var inventoryPoint in GenerateInventoryStats(character.CharacterId, update, pointData)) + yield return inventoryPoint; + } + public void Dispose() { _influxClient?.Dispose(); } + + private struct PriceInfo + { + public string Name { get; init; } + public uint Normal { get; init; } + public uint Hq => Normal + (uint)Math.Ceiling((decimal)Normal / 10); + public uint UiCategory { get; set; } + } } diff --git a/Influx/InfluxPlugin.cs b/Influx/InfluxPlugin.cs index 46cb210..284a074 100644 --- a/Influx/InfluxPlugin.cs +++ b/Influx/InfluxPlugin.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.InteropServices; @@ -66,7 +67,7 @@ public class InfluxPlugin : IDalamudPlugin _windowSystem = new WindowSystem(typeof(InfluxPlugin).FullName); _statisticsWindow = new StatisticsWindow(); _windowSystem.AddWindow(_statisticsWindow); - _configurationWindow = new ConfigurationWindow(_pluginInterface, clientState, _configuration); + _configurationWindow = new ConfigurationWindow(_pluginInterface, clientState, _configuration, _allaganToolsIpc); _configurationWindow.ConfigUpdated += (_, _) => _influxStatisticsClient.UpdateClient(); _windowSystem.AddWindow(_configurationWindow); @@ -110,6 +111,18 @@ public class InfluxPlugin : IDalamudPlugin if (characters.Count == 0) return; + Dictionary> inventoryItems = + _configuration.IncludedInventoryFilters.Select(c => c.Name) + .Distinct() + .ToDictionary(c => c, c => + { + var filter = _allaganToolsIpc.GetFilter(c); + if (filter == null) + return new List(); + + return filter.GenerateFilteredList(); + }); + var update = new StatisticsUpdate { Currencies = currencies @@ -118,6 +131,7 @@ public class InfluxPlugin : IDalamudPlugin y.LocalContentId == x.Key.OwnerId || characters.Any(z => y.LocalContentId == z.CharacterId && z.FreeCompanyId == x.Key.CharacterId))) .ToDictionary(x => x.Key, x => x.Value), + InventoryItems = inventoryItems, Submarines = _submarineTrackerIpc.GetSubmarineStats(characters), LocalStats = _localStatsCalculator.GetAllCharacterStats() .Where(x => characters.Any(y => y.CharacterId == x.Key)) diff --git a/Influx/StatisticsUpdate.cs b/Influx/StatisticsUpdate.cs index b55f28d..049cc1d 100644 --- a/Influx/StatisticsUpdate.cs +++ b/Influx/StatisticsUpdate.cs @@ -8,6 +8,7 @@ namespace Influx; internal sealed class StatisticsUpdate { public required IReadOnlyDictionary Currencies { get; init; } + public required IReadOnlyDictionary> InventoryItems { get; init; } public required Dictionary> Submarines { get; init; } public required Dictionary LocalStats { get; init; } public required Dictionary FcStats { get; init; } diff --git a/Influx/Windows/ConfigurationWindow.cs b/Influx/Windows/ConfigurationWindow.cs index a589cbd..38de3a2 100644 --- a/Influx/Windows/ConfigurationWindow.cs +++ b/Influx/Windows/ConfigurationWindow.cs @@ -1,11 +1,14 @@ using System; using System.Linq; +using Dalamud.Interface; using Dalamud.Interface.Colors; +using Dalamud.Interface.Components; using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Windowing; using Dalamud.Plugin; using Dalamud.Plugin.Services; using ImGuiNET; +using Influx.AllaganTools; namespace Influx.Windows; @@ -14,14 +17,18 @@ internal sealed class ConfigurationWindow : Window private readonly DalamudPluginInterface _pluginInterface; private readonly IClientState _clientState; private readonly Configuration _configuration; + private readonly AllaganToolsIpc _allaganToolsIpc; + private string[] _filterNames = Array.Empty(); + private int _filterIndexToAdd = 0; public ConfigurationWindow(DalamudPluginInterface pluginInterface, IClientState clientState, - Configuration configuration) + Configuration configuration, AllaganToolsIpc allaganToolsIpc) : base("Configuration###InfluxConfiguration") { _pluginInterface = pluginInterface; _clientState = clientState; _configuration = configuration; + _allaganToolsIpc = allaganToolsIpc; } public event EventHandler? ConfigUpdated; @@ -33,9 +40,19 @@ internal sealed class ConfigurationWindow : Window { DrawConnectionSettings(); DrawIncludedCharacters(); + DrawAllaganToolsFilters(); } } + public override void OnOpen() + { + _filterNames = _allaganToolsIpc.GetSearchFilters() + .Select(x => x.Value) + .Order() + .ToArray(); + _filterIndexToAdd = 0; + } + private void DrawConnectionSettings() { using var tabItem = ImRaii.TabItem("Connection Settings"); @@ -165,6 +182,64 @@ internal sealed class ConfigurationWindow : Window } } + private void DrawAllaganToolsFilters() + { + using var tabItem = ImRaii.TabItem("Inventory Filters"); + if (!tabItem) + return; + + if (_configuration.IncludedInventoryFilters.Count > 0) + { + int? indexToRemove = null; + + ImGui.Text("Selected Filters:"); + ImGui.Indent(30); + foreach (var filter in _configuration.IncludedInventoryFilters) + { + if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Times, $"{filter.Name}")) + { + indexToRemove = _configuration.IncludedInventoryFilters.IndexOf(filter); + } + } + + ImGui.Unindent(30); + + if (indexToRemove != null) + { + _configuration.IncludedInventoryFilters.RemoveAt(indexToRemove.Value); + Save(); + } + } + else + { + ImGui.Text("You are not tracking any AllaganTools filters."); + } + + ImGui.Separator(); + + if (_filterNames.Length > 0) + { + ImGui.Combo("Add Search Filter", ref _filterIndexToAdd, _filterNames, _filterNames.Length); + + ImGui.BeginDisabled(_configuration.IncludedInventoryFilters.Any(x => x.Name == _filterNames[_filterIndexToAdd])); + if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Plus, "Track Filter")) + { + _configuration.IncludedInventoryFilters.Add(new Configuration.FilterInfo + { + Name = _filterNames[_filterIndexToAdd], + }); + Save(); + } + + ImGui.EndDisabled(); + } + else + { + ImGui.TextColored(ImGuiColors.DalamudRed, + "You don't have any search filters, or the AllaganTools integration doesn't work."); + } + } + private void Save(bool sendEvent = false) { _pluginInterface.SavePluginConfig(_configuration);