Very basic IPC AT support
This commit is contained in:
@@ -8,8 +8,10 @@ namespace AetherBags.Configuration;
|
|||||||
|
|
||||||
public class CategorySettings
|
public class CategorySettings
|
||||||
{
|
{
|
||||||
|
public bool CategoriesEnabled { get; set; } = true;
|
||||||
public bool GameCategoriesEnabled { get; set; } = true;
|
public bool GameCategoriesEnabled { get; set; } = true;
|
||||||
public bool UserCategoriesEnabled { get; set; } = true;
|
public bool UserCategoriesEnabled { get; set; } = true;
|
||||||
|
public bool AllaganToolsCategoriesEnabled { get; set; } = false;
|
||||||
|
|
||||||
public List<UserCategoryDefinition> UserCategories { get; set; } = new();
|
public List<UserCategoryDefinition> UserCategories { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Dalamud.Plugin.Ipc;
|
||||||
|
|
||||||
|
namespace AetherBags.IPC;
|
||||||
|
|
||||||
|
public class AllaganToolsIPC : IDisposable
|
||||||
|
{
|
||||||
|
private ICallGateSubscriber<bool>? _isInitialized;
|
||||||
|
private ICallGateSubscriber<bool, bool>? _initialized;
|
||||||
|
private ICallGateSubscriber<string, Dictionary<uint, uint>>? _getFilterItems;
|
||||||
|
private ICallGateSubscriber<Dictionary<string, string>>? _getSearchFilters;
|
||||||
|
private ICallGateSubscriber<string, bool>? _enableUiFilter;
|
||||||
|
private ICallGateSubscriber<string, bool>? _toggleUiFilter;
|
||||||
|
|
||||||
|
public bool IsReady { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cached filter items. Key = filterKey, Value = (ItemId -> Quantity).
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, Dictionary<uint, uint>> CachedFilterItems { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cached search filters. Key -> Name.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, string> CachedSearchFilters { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Quick lookup: ItemId -> List of filter keys that contain this item.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<uint, List<string>> ItemToFilters { get; } = new();
|
||||||
|
|
||||||
|
public event Action? OnInitialized;
|
||||||
|
public event Action? OnFiltersRefreshed;
|
||||||
|
|
||||||
|
public AllaganToolsIPC()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_isInitialized = Services.PluginInterface.GetIpcSubscriber<bool>("AllaganTools.IsInitialized");
|
||||||
|
_initialized = Services.PluginInterface.GetIpcSubscriber<bool, bool>("AllaganTools.Initialized");
|
||||||
|
_getFilterItems = Services.PluginInterface.GetIpcSubscriber<string, Dictionary<uint, uint>>("AllaganTools.GetFilterItems");
|
||||||
|
_getSearchFilters = Services.PluginInterface.GetIpcSubscriber<Dictionary<string, string>>("AllaganTools.GetSearchFilters");
|
||||||
|
_enableUiFilter = Services.PluginInterface.GetIpcSubscriber<string, bool>("AllaganTools.EnableUiFilter");
|
||||||
|
_toggleUiFilter = Services.PluginInterface.GetIpcSubscriber<string, bool>("AllaganTools.ToggleUiFilter");
|
||||||
|
|
||||||
|
_initialized.Subscribe(OnAllaganInitialized);
|
||||||
|
|
||||||
|
// Check if already initialized
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsReady = _isInitialized.InvokeFunc();
|
||||||
|
if (IsReady)
|
||||||
|
{
|
||||||
|
RefreshFilters();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
IsReady = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Services.Logger.Debug($"Allagan Tools not available: {ex.Message}");
|
||||||
|
IsReady = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAllaganInitialized(bool initialized)
|
||||||
|
{
|
||||||
|
IsReady = initialized;
|
||||||
|
if (initialized)
|
||||||
|
{
|
||||||
|
Services.Logger.Information("Allagan Tools IPC connected");
|
||||||
|
RefreshFilters();
|
||||||
|
OnInitialized?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Refreshes all cached filter data from Allagan Tools.
|
||||||
|
/// Call this when you need updated filter information.
|
||||||
|
/// </summary>
|
||||||
|
public void RefreshFilters()
|
||||||
|
{
|
||||||
|
if (!IsReady) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CachedSearchFilters.Clear();
|
||||||
|
CachedFilterItems.Clear();
|
||||||
|
ItemToFilters.Clear();
|
||||||
|
|
||||||
|
var filters = _getSearchFilters?.InvokeFunc();
|
||||||
|
if (filters == null) return;
|
||||||
|
|
||||||
|
foreach (var (key, name) in filters)
|
||||||
|
{
|
||||||
|
CachedSearchFilters[key] = name;
|
||||||
|
|
||||||
|
var items = _getFilterItems?.InvokeFunc(key);
|
||||||
|
if (items != null && items.Count > 0)
|
||||||
|
{
|
||||||
|
CachedFilterItems[key] = items;
|
||||||
|
|
||||||
|
// Build reverse lookup
|
||||||
|
foreach (var itemId in items.Keys)
|
||||||
|
{
|
||||||
|
if (!ItemToFilters.TryGetValue(itemId, out var filterList))
|
||||||
|
{
|
||||||
|
filterList = new List<string>(capacity: 4);
|
||||||
|
ItemToFilters[itemId] = filterList;
|
||||||
|
}
|
||||||
|
filterList.Add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Services.Logger.Debug($"Refreshed {CachedSearchFilters.Count} Allagan Tools filters, {ItemToFilters.Count} unique items");
|
||||||
|
OnFiltersRefreshed?.Invoke();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Services.Logger.Warning($"Failed to refresh Allagan Tools filters: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if an item is in any Allagan Tools filter.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsItemInAnyFilter(uint itemId)
|
||||||
|
=> ItemToFilters.ContainsKey(itemId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all filter keys that contain this item.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<string>? GetFiltersForItem(uint itemId)
|
||||||
|
=> ItemToFilters.TryGetValue(itemId, out var list) ? list : null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets items from a specific filter. Returns ItemId -> Quantity.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<uint, uint>? GetFilterItems(string filterKey)
|
||||||
|
{
|
||||||
|
// Try cache first
|
||||||
|
if (CachedFilterItems.TryGetValue(filterKey, out var cached))
|
||||||
|
return cached;
|
||||||
|
|
||||||
|
if (!IsReady) return null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _getFilterItems?.InvokeFunc(filterKey);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Services.Logger.Warning($"GetFilterItems failed: {ex.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all available search filters. Returns Key -> Name.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, string>? GetSearchFilters()
|
||||||
|
{
|
||||||
|
if (CachedSearchFilters.Count > 0)
|
||||||
|
return CachedSearchFilters;
|
||||||
|
|
||||||
|
if (!IsReady) return null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _getSearchFilters?.InvokeFunc();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Services.Logger.Warning($"GetSearchFilters failed: {ex.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_initialized?.Unsubscribe(OnAllaganInitialized);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Dalamud.Plugin.Ipc;
|
||||||
|
|
||||||
|
namespace AetherBags.IPC;
|
||||||
|
|
||||||
|
public class IPCService : IDisposable
|
||||||
|
{
|
||||||
|
public AllaganToolsIPC AllaganTools { get; }
|
||||||
|
public WotsItIPC WotsIt { get; }
|
||||||
|
// Future: public BiSBuddyIPC BiSBuddy { get; }
|
||||||
|
|
||||||
|
public IPCService()
|
||||||
|
{
|
||||||
|
AllaganTools = new AllaganToolsIPC();
|
||||||
|
WotsIt = new WotsItIPC();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
AllaganTools.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
using System;
|
||||||
|
using Dalamud.Plugin.Ipc;
|
||||||
|
|
||||||
|
namespace AetherBags.IPC;
|
||||||
|
|
||||||
|
public class WotsItIPC : IDisposable
|
||||||
|
{
|
||||||
|
private ICallGateSubscriber<string, string, string, uint, string>? _registerWithSearch;
|
||||||
|
private ICallGateSubscriber<string, bool>? _invoke;
|
||||||
|
private ICallGateSubscriber<string, bool>? _unregisterAll;
|
||||||
|
|
||||||
|
private string? _searchGuid;
|
||||||
|
|
||||||
|
public WotsItIPC()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_registerWithSearch = Services.PluginInterface.GetIpcSubscriber<string, string, string, uint, string>("FA.RegisterWithSearch");
|
||||||
|
_unregisterAll = Services.PluginInterface.GetIpcSubscriber<string, bool>("FA.UnregisterAll");
|
||||||
|
_invoke = Services.PluginInterface.GetIpcSubscriber<string, bool>("FA.Invoke");
|
||||||
|
|
||||||
|
_invoke.Subscribe(OnInvoke);
|
||||||
|
|
||||||
|
Register();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Services.Logger.Debug($"WotsIt not available: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Register()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
UnregisterAll();
|
||||||
|
|
||||||
|
_searchGuid = _registerWithSearch?.InvokeFunc(
|
||||||
|
Services.PluginInterface.InternalName,
|
||||||
|
"AetherBags: Search Inventory",
|
||||||
|
"AetherBags Search",
|
||||||
|
66472 // Icon ID
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Services.Logger.Debug($"Failed to register with WotsIt: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInvoke(string guid)
|
||||||
|
{
|
||||||
|
if (guid == _searchGuid)
|
||||||
|
{
|
||||||
|
if (! System.AddonInventoryWindow.IsOpen)
|
||||||
|
{
|
||||||
|
System.AddonInventoryWindow.Open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool UnregisterAll()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_unregisterAll?.InvokeFunc(Services.PluginInterface.InternalName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_invoke?.Unsubscribe(OnInvoke);
|
||||||
|
UnregisterAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AetherBags.Configuration;
|
using AetherBags.Configuration;
|
||||||
using AetherBags.Inventory.Items;
|
using AetherBags.Inventory.Items;
|
||||||
|
using KamiToolKit.Classes;
|
||||||
|
|
||||||
namespace AetherBags.Inventory.Categories;
|
namespace AetherBags.Inventory.Categories;
|
||||||
|
|
||||||
@@ -18,6 +19,15 @@ public static class CategoryBucketManager
|
|||||||
public static bool IsUserCategoryKey(uint key)
|
public static bool IsUserCategoryKey(uint key)
|
||||||
=> (key & UserCategoryKeyFlag) != 0;
|
=> (key & UserCategoryKeyFlag) != 0;
|
||||||
|
|
||||||
|
private const uint AllaganFilterKeyFlag = 0x4000_0000;
|
||||||
|
|
||||||
|
public static uint MakeAllaganFilterKey(int index)
|
||||||
|
=> AllaganFilterKeyFlag | (uint)(index & 0x3FFF_FFFF);
|
||||||
|
|
||||||
|
public static bool IsAllaganFilterKey(uint key)
|
||||||
|
=> (key & AllaganFilterKeyFlag) != 0 && (key & UserCategoryKeyFlag) == 0;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resets all buckets for a new refresh cycle.
|
/// Resets all buckets for a new refresh cycle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -148,6 +158,74 @@ public static class CategoryBucketManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void BucketByAllaganFilters(
|
||||||
|
Dictionary<ulong, ItemInfo> itemInfoByKey,
|
||||||
|
Dictionary<uint, CategoryBucket> bucketsByKey,
|
||||||
|
HashSet<ulong> claimedKeys,
|
||||||
|
bool allaganCategoriesEnabled)
|
||||||
|
{
|
||||||
|
if (!allaganCategoriesEnabled) return;
|
||||||
|
if (! System.IPC.AllaganTools.IsReady) return;
|
||||||
|
|
||||||
|
var filters = System.IPC.AllaganTools.CachedSearchFilters;
|
||||||
|
var filterItems = System.IPC.AllaganTools.CachedFilterItems;
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
foreach (var (filterKey, filterName) in filters)
|
||||||
|
{
|
||||||
|
if (!filterItems. TryGetValue(filterKey, out var itemIds))
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint bucketKey = MakeAllaganFilterKey(index);
|
||||||
|
|
||||||
|
if (!bucketsByKey.TryGetValue(bucketKey, out CategoryBucket? bucket))
|
||||||
|
{
|
||||||
|
bucket = new CategoryBucket
|
||||||
|
{
|
||||||
|
Key = bucketKey,
|
||||||
|
Category = new CategoryInfo
|
||||||
|
{
|
||||||
|
Name = $"[AT] {filterName}",
|
||||||
|
Description = $"Allagan Tools filter: {filterName}",
|
||||||
|
Color = ColorHelper.GetColor(32),
|
||||||
|
},
|
||||||
|
Items = new List<ItemInfo>(capacity: 16),
|
||||||
|
FilteredItems = new List<ItemInfo>(capacity: 16),
|
||||||
|
Used = true,
|
||||||
|
};
|
||||||
|
bucketsByKey. Add(bucketKey, bucket);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bucket.Used = true;
|
||||||
|
bucket.Category.Name = $"[AT] {filterName}";
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var itemKvp in itemInfoByKey)
|
||||||
|
{
|
||||||
|
ulong itemKey = itemKvp.Key;
|
||||||
|
ItemInfo item = itemKvp.Value;
|
||||||
|
|
||||||
|
if (claimedKeys.Contains(itemKey))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (itemIds.ContainsKey(item.Item.ItemId))
|
||||||
|
{
|
||||||
|
bucket.Items.Add(item);
|
||||||
|
claimedKeys.Add(itemKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bucket.Items. Count == 0)
|
||||||
|
bucket.Used = false;
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void BucketUnclaimedToMisc(
|
public static void BucketUnclaimedToMisc(
|
||||||
Dictionary<ulong, ItemInfo> itemInfoByKey,
|
Dictionary<ulong, ItemInfo> itemInfoByKey,
|
||||||
Dictionary<uint, CategoryBucket> bucketsByKey,
|
Dictionary<uint, CategoryBucket> bucketsByKey,
|
||||||
@@ -215,9 +293,13 @@ public static class CategoryBucketManager
|
|||||||
|
|
||||||
sortedCategoryKeys.Sort((left, right) =>
|
sortedCategoryKeys.Sort((left, right) =>
|
||||||
{
|
{
|
||||||
bool leftCategory = IsUserCategoryKey(left);
|
bool leftUser = IsUserCategoryKey(left);
|
||||||
bool rightCategory = IsUserCategoryKey(right);
|
bool rightUser = IsUserCategoryKey(right);
|
||||||
if (leftCategory != rightCategory) return leftCategory ? -1 : 1;
|
bool leftAllagan = IsAllaganFilterKey(left);
|
||||||
|
bool rightAllagan = IsAllaganFilterKey(right);
|
||||||
|
if (leftUser != rightUser) return leftUser ? -1 : 1;
|
||||||
|
if (leftAllagan != rightAllagan) return leftAllagan ? -1 : 1;
|
||||||
|
|
||||||
return left.CompareTo(right);
|
return left.CompareTo(right);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using System.Linq;
|
|||||||
using AetherBags.Configuration;
|
using AetherBags.Configuration;
|
||||||
using AetherBags.Currency;
|
using AetherBags.Currency;
|
||||||
using AetherBags.Inventory.Categories;
|
using AetherBags.Inventory.Categories;
|
||||||
using AetherBags.Inventory.Context;
|
|
||||||
using AetherBags.Inventory.Items;
|
using AetherBags.Inventory.Items;
|
||||||
using AetherBags.Inventory.Scanning;
|
using AetherBags.Inventory.Scanning;
|
||||||
using Dalamud.Game.Inventory;
|
using Dalamud.Game.Inventory;
|
||||||
@@ -32,68 +31,6 @@ public static unsafe class InventoryState
|
|||||||
public static bool Contains(this IReadOnlyCollection<InventoryType> inventoryTypes, GameInventoryType type)
|
public static bool Contains(this IReadOnlyCollection<InventoryType> inventoryTypes, GameInventoryType type)
|
||||||
=> inventoryTypes.Contains((InventoryType)type);
|
=> inventoryTypes.Contains((InventoryType)type);
|
||||||
|
|
||||||
public static void RefreshFromGame()
|
|
||||||
{
|
|
||||||
FFXIVClientStructs.FFXIV.Client.Game.InventoryManager* inventoryManager = FFXIVClientStructs.FFXIV.Client.Game.InventoryManager.Instance();
|
|
||||||
if (inventoryManager == null)
|
|
||||||
{
|
|
||||||
ClearAll();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var config = System.Config;
|
|
||||||
InventoryStackMode stackMode = config.General.StackMode;
|
|
||||||
bool userCategoriesEnabled = config.Categories.UserCategoriesEnabled;
|
|
||||||
bool gameCategoriesEnabled = config.Categories.GameCategoriesEnabled;
|
|
||||||
List<UserCategoryDefinition> userCategories = config.Categories.UserCategories.Where(category => category.Enabled).ToList();
|
|
||||||
|
|
||||||
Services.Logger.DebugOnly($"RefreshFromGame StackMode={stackMode}");
|
|
||||||
|
|
||||||
AggByKey.Clear();
|
|
||||||
ItemInfoByKey.Clear();
|
|
||||||
SortedCategoryKeys.Clear();
|
|
||||||
AllCategories.Clear();
|
|
||||||
FilteredCategories.Clear();
|
|
||||||
ClaimedKeys.Clear();
|
|
||||||
|
|
||||||
InventoryScanner.ScanBags(inventoryManager, stackMode, AggByKey);
|
|
||||||
CategoryBucketManager.ResetBuckets(BucketsByKey);
|
|
||||||
InventoryScanner.BuildItemInfos(AggByKey, ItemInfoByKey);
|
|
||||||
InventoryContextState.RefreshMaps();
|
|
||||||
InventoryContextState.RefreshBlockedSlots();
|
|
||||||
|
|
||||||
if (userCategoriesEnabled && userCategories.Count > 0)
|
|
||||||
{
|
|
||||||
CategoryBucketManager.BucketByUserCategories(
|
|
||||||
ItemInfoByKey,
|
|
||||||
userCategories,
|
|
||||||
BucketsByKey,
|
|
||||||
ClaimedKeys,
|
|
||||||
UserCategoriesSortedScratch);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gameCategoriesEnabled)
|
|
||||||
{
|
|
||||||
CategoryBucketManager.BucketByGameCategories(
|
|
||||||
ItemInfoByKey,
|
|
||||||
BucketsByKey,
|
|
||||||
ClaimedKeys,
|
|
||||||
userCategoriesEnabled);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CategoryBucketManager.BucketUnclaimedToMisc(
|
|
||||||
ItemInfoByKey,
|
|
||||||
BucketsByKey,
|
|
||||||
ClaimedKeys,
|
|
||||||
userCategoriesEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
InventoryScanner.PruneStaleItemInfos(AggByKey, ItemInfoByKey, RemoveKeysScratch);
|
|
||||||
CategoryBucketManager.SortBucketsAndBuildKeyList(BucketsByKey, SortedCategoryKeys);
|
|
||||||
CategoryBucketManager.BuildCategorizedList(BucketsByKey, SortedCategoryKeys, AllCategories);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IReadOnlyList<CategorizedInventory> GetInventoryItemCategories(string filterString = "", bool invert = false)
|
public static IReadOnlyList<CategorizedInventory> GetInventoryItemCategories(string filterString = "", bool invert = false)
|
||||||
{
|
{
|
||||||
return InventoryFilter.FilterCategories(
|
return InventoryFilter.FilterCategories(
|
||||||
|
|||||||
@@ -25,14 +25,14 @@ public abstract class InventoryStateBase
|
|||||||
|
|
||||||
public virtual unsafe void RefreshFromGame()
|
public virtual unsafe void RefreshFromGame()
|
||||||
{
|
{
|
||||||
FFXIVClientStructs.FFXIV.Client.Game.InventoryManager* inventoryManager = FFXIVClientStructs.FFXIV.Client.Game.InventoryManager.Instance();
|
InventoryManager* inventoryManager = InventoryManager.Instance();
|
||||||
if (inventoryManager == null)
|
if (inventoryManager == null)
|
||||||
{
|
{
|
||||||
ClearAll();
|
ClearAll();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = AetherBags.System.Config;
|
var config = System.Config;
|
||||||
InventoryStackMode stackMode = config.General.StackMode;
|
InventoryStackMode stackMode = config.General.StackMode;
|
||||||
|
|
||||||
AggByKey.Clear();
|
AggByKey.Clear();
|
||||||
@@ -61,8 +61,10 @@ public abstract class InventoryStateBase
|
|||||||
|
|
||||||
protected virtual void ApplyCategories(SystemConfiguration config)
|
protected virtual void ApplyCategories(SystemConfiguration config)
|
||||||
{
|
{
|
||||||
bool userCategoriesEnabled = config.Categories.UserCategoriesEnabled;
|
bool categoriesEnabled = config.Categories.CategoriesEnabled;
|
||||||
bool gameCategoriesEnabled = config.Categories.GameCategoriesEnabled;
|
bool userCategoriesEnabled = config.Categories.UserCategoriesEnabled && categoriesEnabled;
|
||||||
|
bool gameCategoriesEnabled = config.Categories.GameCategoriesEnabled && categoriesEnabled;
|
||||||
|
bool allaganCategoriesEnabled = config.Categories.AllaganToolsCategoriesEnabled && categoriesEnabled;
|
||||||
var userCategories = config.Categories.UserCategories.Where(c => c.Enabled).ToList();
|
var userCategories = config.Categories.UserCategories.Where(c => c.Enabled).ToList();
|
||||||
|
|
||||||
if (userCategoriesEnabled && userCategories.Count > 0)
|
if (userCategoriesEnabled && userCategories.Count > 0)
|
||||||
@@ -71,6 +73,12 @@ public abstract class InventoryStateBase
|
|||||||
ItemInfoByKey, userCategories, BucketsByKey, ClaimedKeys, UserCategoriesSortedScratch);
|
ItemInfoByKey, userCategories, BucketsByKey, ClaimedKeys, UserCategoriesSortedScratch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (allaganCategoriesEnabled)
|
||||||
|
{
|
||||||
|
CategoryBucketManager.BucketByAllaganFilters(
|
||||||
|
ItemInfoByKey, BucketsByKey, ClaimedKeys, allaganCategoriesEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
if (gameCategoriesEnabled)
|
if (gameCategoriesEnabled)
|
||||||
{
|
{
|
||||||
CategoryBucketManager.BucketByGameCategories(
|
CategoryBucketManager.BucketByGameCategories(
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using AetherBags.Configuration;
|
||||||
|
using AetherBags.Inventory;
|
||||||
|
using AetherBags.Nodes.Color;
|
||||||
|
using KamiToolKit.Classes;
|
||||||
|
using KamiToolKit.Nodes;
|
||||||
|
|
||||||
|
namespace AetherBags.Nodes.Configuration.Category;
|
||||||
|
|
||||||
|
public sealed class CategoryGeneralConfigurationNode : TabbedVerticalListNode
|
||||||
|
{
|
||||||
|
private readonly CheckboxNode _allaganToolsCheckbox;
|
||||||
|
public CategoryGeneralConfigurationNode()
|
||||||
|
{
|
||||||
|
CategorySettings config = System.Config.Categories;
|
||||||
|
|
||||||
|
LabelTextNode titleNode = new LabelTextNode
|
||||||
|
{
|
||||||
|
Size = Size with { Y = 18 },
|
||||||
|
String = "Category Configuration",
|
||||||
|
TextColor = ColorHelper.GetColor(2),
|
||||||
|
TextOutlineColor = ColorHelper.GetColor(0),
|
||||||
|
};
|
||||||
|
AddNode(titleNode);
|
||||||
|
|
||||||
|
AddTab(1);
|
||||||
|
|
||||||
|
CheckboxNode categoriesEnabled = new CheckboxNode
|
||||||
|
{
|
||||||
|
Size = Size with { Y = 18 },
|
||||||
|
IsVisible = true,
|
||||||
|
String = "Categories Enabled",
|
||||||
|
IsChecked = config.CategoriesEnabled,
|
||||||
|
OnClick = isChecked =>
|
||||||
|
{
|
||||||
|
config.CategoriesEnabled = isChecked;
|
||||||
|
RefreshInventory();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
AddNode(categoriesEnabled);
|
||||||
|
|
||||||
|
AddTab(1);
|
||||||
|
|
||||||
|
CheckboxNode gameCategoriesEnabled = new CheckboxNode
|
||||||
|
{
|
||||||
|
Size = Size with { Y = 18 },
|
||||||
|
IsVisible = true,
|
||||||
|
String = "Game Categories",
|
||||||
|
IsChecked = config.GameCategoriesEnabled,
|
||||||
|
TextTooltip = "Use the game's built-in item categories (e.g., Arms, Tools, Armor).",
|
||||||
|
OnClick = isChecked =>
|
||||||
|
{
|
||||||
|
config.GameCategoriesEnabled = isChecked;
|
||||||
|
RefreshInventory();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
AddNode(gameCategoriesEnabled);
|
||||||
|
|
||||||
|
CheckboxNode userCategoriesEnabled = new CheckboxNode
|
||||||
|
{
|
||||||
|
Size = Size with { Y = 18 },
|
||||||
|
IsVisible = true,
|
||||||
|
String = "User Categories",
|
||||||
|
IsChecked = config.UserCategoriesEnabled,
|
||||||
|
TextTooltip = "Use your custom-defined categories.",
|
||||||
|
OnClick = isChecked =>
|
||||||
|
{
|
||||||
|
config.UserCategoriesEnabled = isChecked;
|
||||||
|
RefreshInventory();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
AddNode(userCategoriesEnabled);
|
||||||
|
|
||||||
|
bool allaganReady = System.IPC.AllaganTools?.IsReady ?? false;
|
||||||
|
_allaganToolsCheckbox = new CheckboxNode
|
||||||
|
{
|
||||||
|
Size = Size with { Y = 18 },
|
||||||
|
IsVisible = true,
|
||||||
|
String = allaganReady ? "Allagan Tools Filters" : "Allagan Tools Filters (Not Available)",
|
||||||
|
IsChecked = config.AllaganToolsCategoriesEnabled,
|
||||||
|
IsEnabled = allaganReady,
|
||||||
|
TextTooltip = allaganReady
|
||||||
|
? "Use search filters from Allagan Tools as categories. Items matching a filter will be grouped together."
|
||||||
|
: "Allagan Tools is not installed or not initialized.",
|
||||||
|
OnClick = isChecked =>
|
||||||
|
{
|
||||||
|
config.AllaganToolsCategoriesEnabled = isChecked;
|
||||||
|
if (isChecked)
|
||||||
|
{
|
||||||
|
System.IPC?.AllaganTools?.RefreshFilters();
|
||||||
|
}
|
||||||
|
RefreshInventory();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
AddNode(_allaganToolsCheckbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshInventory() => InventoryOrchestrator.RefreshAll(updateMaps: true);
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ using KamiToolKit.Nodes;
|
|||||||
|
|
||||||
namespace AetherBags.Nodes.Configuration.Category;
|
namespace AetherBags.Nodes.Configuration.Category;
|
||||||
|
|
||||||
public class CategoryScrollingAreaNode : ScrollingAreaNode<VerticalListNode>
|
public sealed class CategoryScrollingAreaNode : ScrollingAreaNode<VerticalListNode>
|
||||||
{
|
{
|
||||||
private AddonCategoryConfigurationWindow? _categoryConfigurationAddon;
|
private AddonCategoryConfigurationWindow? _categoryConfigurationAddon;
|
||||||
private readonly TextButtonNode _categoryConfigurationButtonNode;
|
private readonly TextButtonNode _categoryConfigurationButtonNode;
|
||||||
@@ -13,13 +13,15 @@ public class CategoryScrollingAreaNode : ScrollingAreaNode<VerticalListNode>
|
|||||||
{
|
{
|
||||||
InitializeCategoryAddon();
|
InitializeCategoryAddon();
|
||||||
|
|
||||||
|
ContentNode.AddNode(new CategoryGeneralConfigurationNode());
|
||||||
|
|
||||||
_categoryConfigurationButtonNode = new TextButtonNode
|
_categoryConfigurationButtonNode = new TextButtonNode
|
||||||
{
|
{
|
||||||
Size = new Vector2(300, 28),
|
Size = new Vector2(300, 28),
|
||||||
String = "Configure Categories",
|
String = "Configure Categories",
|
||||||
OnClick = () => _categoryConfigurationAddon?.Toggle(),
|
OnClick = () => _categoryConfigurationAddon?.Toggle(),
|
||||||
};
|
};
|
||||||
_categoryConfigurationButtonNode.AttachNode(this);
|
ContentNode.AddNode(_categoryConfigurationButtonNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeCategoryAddon() {
|
private void InitializeCategoryAddon() {
|
||||||
|
|||||||
@@ -55,8 +55,9 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
|||||||
{
|
{
|
||||||
Size = Size with { Y = 18 },
|
Size = Size with { Y = 18 },
|
||||||
IsVisible = true,
|
IsVisible = true,
|
||||||
String = "Color When Capped",
|
String = "Color Weekly Cap",
|
||||||
IsChecked = config.ColorWhenCapped,
|
IsChecked = config.ColorWhenCapped,
|
||||||
|
TextTooltip = "Changes the color of the currency display when you have reached the maximum amount earnable for the current week (e.g., 450/450).",
|
||||||
OnClick = isChecked =>
|
OnClick = isChecked =>
|
||||||
{
|
{
|
||||||
config.ColorWhenCapped = isChecked;
|
config.ColorWhenCapped = isChecked;
|
||||||
@@ -69,7 +70,7 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
|||||||
|
|
||||||
ColorInputRow cappedCurrencyColorNode = new ColorInputRow
|
ColorInputRow cappedCurrencyColorNode = new ColorInputRow
|
||||||
{
|
{
|
||||||
Label = "Capped Currency Color",
|
Label = "Weekly Cap Color",
|
||||||
Size = new Vector2(300, 24),
|
Size = new Vector2(300, 24),
|
||||||
CurrentColor = config.CappedColor,
|
CurrentColor = config.CappedColor,
|
||||||
DefaultColor = new CurrencySettings().CappedColor,
|
DefaultColor = new CurrencySettings().CappedColor,
|
||||||
@@ -87,8 +88,9 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
|||||||
{
|
{
|
||||||
Size = Size with { Y = 18 },
|
Size = Size with { Y = 18 },
|
||||||
IsVisible = true,
|
IsVisible = true,
|
||||||
String = "Limited Currency Color",
|
String = "Color Max Capacity",
|
||||||
IsChecked = config.ColorWhenLimited,
|
IsChecked = config.ColorWhenLimited,
|
||||||
|
TextTooltip = "Changes the color of the currency display when your total held amount has reached its maximum capacity (e.g., 2000/2000).",
|
||||||
OnClick = isChecked =>
|
OnClick = isChecked =>
|
||||||
{
|
{
|
||||||
config.ColorWhenLimited = isChecked;
|
config.ColorWhenLimited = isChecked;
|
||||||
@@ -101,7 +103,7 @@ public sealed class CurrencyGeneralConfigurationNode : TabbedVerticalListNode
|
|||||||
|
|
||||||
ColorInputRow limitCurrencyColorNode = new ColorInputRow
|
ColorInputRow limitCurrencyColorNode = new ColorInputRow
|
||||||
{
|
{
|
||||||
Label = "Color Weekly Limit",
|
Label = "Max Capacity Color",
|
||||||
Size = new Vector2(300, 24),
|
Size = new Vector2(300, 24),
|
||||||
CurrentColor = config.LimitColor,
|
CurrentColor = config.LimitColor,
|
||||||
DefaultColor = new CurrencySettings().LimitColor,
|
DefaultColor = new CurrencySettings().LimitColor,
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ using AetherBags.Helpers;
|
|||||||
using AetherBags.Hooks;
|
using AetherBags.Hooks;
|
||||||
using AetherBags.Inventory;
|
using AetherBags.Inventory;
|
||||||
using AetherBags.Inventory.State;
|
using AetherBags.Inventory.State;
|
||||||
|
using AetherBags.IPC;
|
||||||
using Dalamud.Game.Gui;
|
using Dalamud.Game.Gui;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
using KamiToolKit;
|
using KamiToolKit;
|
||||||
using NotImplementedException = System.NotImplementedException;
|
|
||||||
|
|
||||||
namespace AetherBags;
|
namespace AetherBags;
|
||||||
|
|
||||||
@@ -31,6 +31,8 @@ public unsafe class Plugin : IDalamudPlugin
|
|||||||
KamiToolKitLibrary.Initialize(pluginInterface);
|
KamiToolKitLibrary.Initialize(pluginInterface);
|
||||||
System.Config = Util.LoadConfigOrDefault();
|
System.Config = Util.LoadConfigOrDefault();
|
||||||
|
|
||||||
|
System.IPC = new IPCService();
|
||||||
|
|
||||||
System.AddonInventoryWindow = new AddonInventoryWindow
|
System.AddonInventoryWindow = new AddonInventoryWindow
|
||||||
{
|
{
|
||||||
InternalName = "AetherBags_MainBags",
|
InternalName = "AetherBags_MainBags",
|
||||||
@@ -79,6 +81,7 @@ public unsafe class Plugin : IDalamudPlugin
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
System.IPC.Dispose();
|
||||||
Util.SaveConfig(System.Config);
|
Util.SaveConfig(System.Config);
|
||||||
Services.ClientState.Login -= OnLogin;
|
Services.ClientState.Login -= OnLogin;
|
||||||
Services.ClientState.Logout -= OnLogout;
|
Services.ClientState.Logout -= OnLogout;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using AetherBags.Addons;
|
using AetherBags.Addons;
|
||||||
using AetherBags.Configuration;
|
using AetherBags.Configuration;
|
||||||
|
using AetherBags.IPC;
|
||||||
|
|
||||||
namespace AetherBags;
|
namespace AetherBags;
|
||||||
|
|
||||||
@@ -9,5 +10,6 @@ public static class System
|
|||||||
public static AddonSaddleBagWindow AddonSaddleBagWindow { get; set; } = null!;
|
public static AddonSaddleBagWindow AddonSaddleBagWindow { get; set; } = null!;
|
||||||
public static AddonRetainerWindow AddonRetainerWindow { get; set; } = null!;
|
public static AddonRetainerWindow AddonRetainerWindow { get; set; } = null!;
|
||||||
public static AddonConfigurationWindow AddonConfigurationWindow { get; set; } = null!;
|
public static AddonConfigurationWindow AddonConfigurationWindow { get; set; } = null!;
|
||||||
|
public static IPCService IPC { get; set; } = null!;
|
||||||
public static SystemConfiguration Config { get; set; } = null!;
|
public static SystemConfiguration Config { get; set; } = null!;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user