IPCs, Improved AT support, BISBuddy support

This commit is contained in:
Zeffuro
2026-01-02 23:23:49 +01:00
parent a7bfd7ea66
commit aec704dece
26 changed files with 556 additions and 130 deletions
+3 -2
View File
@@ -42,11 +42,12 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
};
_notificationNode.AttachNode(this);
SearchInputNode = new TextInputWithHintNode
SearchInputNode = new TextInputWithButtonNode
{
Position = header.SearchPosition,
Size = header.SearchSize,
OnInputReceived = _ => RefreshCategoriesCore(autosize: false),
OnInputReceived = _ => ItemRefresh(),
OnButtonClicked = () => InventoryAddonContextMenu.OpenMain(this)
};
SearchInputNode.AttachNode(this);
+3 -2
View File
@@ -51,11 +51,12 @@ public unsafe class AddonRetainerWindow : InventoryAddonBase
var header = CalculateHeaderLayout(addon);
SearchInputNode = new TextInputWithHintNode
SearchInputNode = new TextInputWithButtonNode
{
Position = header.SearchPosition,
Size = header.SearchSize,
OnInputReceived = _ => RefreshCategoriesCore(autosize: false),
OnInputReceived = _ => ItemRefresh(),
OnButtonClicked = () => InventoryAddonContextMenu.OpenMain(this)
};
SearchInputNode.AttachNode(this);
+3 -2
View File
@@ -44,11 +44,12 @@ public unsafe class AddonSaddleBagWindow : InventoryAddonBase
var header = CalculateHeaderLayout(addon);
SearchInputNode = new TextInputWithHintNode
SearchInputNode = new TextInputWithButtonNode
{
Position = header.SearchPosition,
Size = header.SearchSize,
OnInputReceived = _ => RefreshCategoriesCore(autosize: false),
OnInputReceived = _ => ItemRefresh(),
OnButtonClicked = () => InventoryAddonContextMenu.OpenMain(this)
};
SearchInputNode.AttachNode(this);
+1
View File
@@ -6,5 +6,6 @@ public interface IInventoryWindow
void Toggle();
void Close();
void ManualRefresh();
void ItemRefresh();
void SetSearchText(string searchText);
}
+71 -10
View File
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using AetherBags.Configuration;
using AetherBags.Helpers;
using AetherBags.Inventory;
using AetherBags.Inventory.Categories;
@@ -14,6 +15,7 @@ using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit;
using KamiToolKit.Classes;
using KamiToolKit.Classes.ContextMenu;
using KamiToolKit.Nodes;
namespace AetherBags.Addons;
@@ -26,11 +28,13 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
protected DragDropNode BackgroundDropTarget = null!;
protected WrappingGridNode<InventoryCategoryNode> CategoriesNode = null!;
protected TextInputWithHintNode SearchInputNode = null!;
protected TextInputWithButtonNode SearchInputNode = null!;
protected InventoryFooterNode FooterNode = null!;
protected TextNode? SlotCounterNode { get; set; }
protected CircleButtonNode SettingsButtonNode = null!;
internal ContextMenu ContextMenu = null!;
protected virtual float MinWindowWidth => 600;
protected virtual float MaxWindowWidth => 800;
protected virtual float MinWindowHeight => 200;
@@ -54,6 +58,8 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
protected virtual bool HasPinning => true;
protected virtual bool HasSlotCounter => false;
private readonly HashSet<uint> _searchMatchScratch = new();
public void ManualRefresh()
{
if (!IsOpen) return;
@@ -73,6 +79,9 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
}
}
public string GetSearchText() => SearchInputNode?.SearchString.ExtractText() ?? string.Empty;
public virtual void SetSearchText(string searchText)
{
Services.Framework.RunOnTick(() =>
@@ -105,14 +114,52 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
if (!_isSetupComplete)
return;
var config = System.Config.General;
string searchText = SearchInputNode.SearchString.ExtractText();
bool isSearching = !string.IsNullOrWhiteSpace(searchText);
if (config.SearchMode == SearchMode.Highlight && isSearching)
{
_searchMatchScratch.Clear();
var allData = InventoryState.GetCategories(string.Empty);
for (int i = 0; i < allData.Count; i++)
{
var cat = allData[i];
for (int j = 0; j < cat.Items.Count; j++)
{
var item = cat.Items[j];
if (item.IsRegexMatch(searchText))
{
_searchMatchScratch.Add(item.Item.ItemId);
}
}
}
HighlightState.SetFilter(HighlightSource.Search, _searchMatchScratch);
}
else
{
HighlightState.ClearFilter(HighlightSource.Search);
}
if (SearchInputNode != null)
{
bool atActive = !string.IsNullOrEmpty(HighlightState.SelectedAllaganToolsFilterKey);
bool filterModeActive = System.Config.General.SearchMode == SearchMode.Filter;
SearchInputNode.HintAddColor = (atActive || filterModeActive)
? new Vector3(0.0f, 0.3f, 0.3f)
: Vector3.Zero;
}
if (HasFooter)
{
FooterNode.SlotAmountText = InventoryState.GetEmptySlotsString();
FooterNode.RefreshCurrencies();
}
string filter = SearchInputNode.SearchString.ExtractText();
var categories = InventoryState.GetCategories(filter);
string dataFilter = config.SearchMode == SearchMode.Filter ? searchText : string.Empty;
var categories = InventoryState.GetCategories(dataFilter);
float maxContentWidth = MaxWindowWidth - (ContentStartPosition.X * 2);
int maxItemsPerLine = CalculateOptimalItemsPerLine(maxContentWidth);
@@ -125,6 +172,7 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
{
node.CategorizedInventory = data;
node.ItemsPerLine = Math.Min(data.Items.Count, maxItemsPerLine);
node.RefreshNodeVisuals();
},
createNodeMethod: _ => CreateCategoryNode());
@@ -155,24 +203,27 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
protected HeaderLayout CalculateHeaderLayout(AtkUnitBase* addon)
{
var size = new Vector2(addon->Size.X / 2.0f, 28.0f);
var header = addon->WindowHeaderCollisionNode;
float headerW = header->Width;
float headerH = header->Height;
float x = header->X + (headerW - size.X) * 0.5f;
float y = header->Y + (headerH - size.Y) * 0.5f;
// Center the search bar, width is 50% of header
float searchWidth = headerW * 0.5f;
var searchSize = new Vector2(searchWidth, 28f);
float searchX = (headerW - searchWidth) * 0.5f;
float itemY = header->Y + (headerH - 28f) * 0.5f;
return new HeaderLayout
{
SearchPosition = new Vector2(x, y),
SearchSize = size,
SearchPosition = new Vector2(searchX, itemY),
SearchSize = searchSize,
HeaderWidth = headerW,
HeaderY = y,
HeaderY = itemY
};
}
protected void InitializeBackgroundDropTarget()
{
BackgroundDropTarget = new DragDropNode
@@ -344,6 +395,8 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
protected void ResizeWindow(float width, float height)
=> ResizeWindow(width, height, recalcLayout: true);
public void ItemRefresh() => RefreshCategoriesCore(false);
protected override void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
{
base.OnRequestedUpdate(addon, numberArrayData, stringArrayData);
@@ -352,6 +405,13 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
RefreshCategoriesCore(autosize: true);
}
protected override void OnSetup(AtkUnitBase* addon)
{
ContextMenu = new ContextMenu();
base.OnSetup(addon);
}
protected override void OnUpdate(AtkUnitBase* addon)
{
if (RefreshQueued)
@@ -368,6 +428,7 @@ public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
protected override void OnFinalize(AtkUnitBase* addon)
{
ContextMenu?.Dispose();
HoverSubscribed.Clear();
RefreshQueued = false;
RefreshAutosizeQueued = false;
@@ -0,0 +1,74 @@
using System;
using AetherBags. Configuration;
using AetherBags. Inventory;
using AetherBags.Inventory.Context;
using KamiToolKit.Classes. ContextMenu;
namespace AetherBags.Addons;
public static class InventoryAddonContextMenu
{
private static ContextMenuItem Separator => new()
{
Name = "---------------------------",
IsEnabled = false,
OnClick = () => { }
};
public static void OpenMain(InventoryAddonBase parent)
{
if (parent?.ContextMenu == null || System.Config == null) return;
var menu = parent.ContextMenu;
menu.Clear();
bool hasActiveAtFilter = !string.IsNullOrEmpty(HighlightState.SelectedAllaganToolsFilterKey);
string searchText = parent.GetSearchText();
if (HighlightState. IsFilterActive || hasActiveAtFilter || ! string.IsNullOrEmpty(searchText))
{
menu.AddItem("Clear All Filters", () =>
{
HighlightState.ClearAll();
parent. SetSearchText(string.Empty);
InventoryOrchestrator.RefreshAll(updateMaps: false);
});
menu.AddItem(Separator);
}
var currentMode = System. Config.General.SearchMode;
string modeLabel = currentMode == SearchMode. Filter ? "Mode: Hide Non-Matches" : "Mode: Fade Non-Matches";
menu.AddItem(modeLabel, () =>
{
System.Config.General.SearchMode = currentMode == SearchMode. Filter ? SearchMode. Highlight : SearchMode.Filter;
parent.ManualRefresh();
});
if (System.IPC.AllaganTools is { IsReady: true } && System. Config.Categories.AllaganToolsCategoriesEnabled)
{
var atFilters = System.IPC.AllaganTools.GetSearchFilters();
if (atFilters is { Count: > 0 })
{
var subMenu = new ContextMenuSubItem
{
Name = "Allagan Tools Filters...",
OnClick = () => { }
};
foreach (var (key, name) in atFilters)
{
var capturedKey = key;
bool isActive = HighlightState.SelectedAllaganToolsFilterKey == key;
subMenu.AddItem(isActive ? $"✓ {name}" : $" {name}", () =>
{
HighlightState.SelectedAllaganToolsFilterKey = isActive ? string.Empty : capturedKey;
InventoryOrchestrator.RefreshAll(updateMaps: false);
});
}
menu.AddItem(subMenu);
}
}
menu.Open();
}
}
@@ -11,7 +11,9 @@ public class CategorySettings
public bool CategoriesEnabled { get; set; } = true;
public bool GameCategoriesEnabled { get; set; } = true;
public bool UserCategoriesEnabled { get; set; } = true;
public bool BisBuddyEnabled { get; set; } = true;
public bool AllaganToolsCategoriesEnabled { get; set; } = false;
public AllaganToolsFilterMode AllaganToolsMode { get; set; } = AllaganToolsFilterMode.Highlight;
public List<UserCategoryDefinition> UserCategories { get; set; } = new();
}
@@ -78,3 +80,9 @@ public enum ToggleFilterState
Allow = 1,
Disallow = 2,
}
public enum AllaganToolsFilterMode
{
Categorize = 0,
Highlight = 1,
}
@@ -3,6 +3,7 @@ namespace AetherBags.Configuration;
public class GeneralSettings
{
public InventoryStackMode StackMode { get; set; } = InventoryStackMode.AggregateByItemId;
public SearchMode SearchMode { get; set; } = SearchMode.Highlight;
public bool DebugEnabled { get; set; } = false;
public bool CompactPackingEnabled { get; set; } = true;
public int CompactLookahead { get; set; } = 24;
@@ -23,3 +24,9 @@ public enum InventoryStackMode : byte
NaturalStacks = 0,
AggregateByItemId = 1,
}
public enum SearchMode : byte
{
Filter = 0,
Highlight = 1,
}
+9 -6
View File
@@ -2,13 +2,16 @@ namespace AetherBags.Extensions;
public static class LoggerExtensions
{
public static void DebugOnly(this object logger, string message)
extension(object logger)
{
if(System.Config.General.DebugEnabled) Services.Logger.DebugOnly(message);
}
public void DebugOnly(string message)
{
if (System.Config?.General?.DebugEnabled == true)
{
Services.Logger.DebugOnly(message);
}
}
public static void DebugOnly(this object logger, string message, params object[] args)
{
if(System.Config.General.DebugEnabled) Services.Logger.DebugOnly(message);
public void DebugOnly(string message, params object[] args) => DebugOnly(logger, string.Format(message, args));
}
}
+8 -1
View File
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using AetherBags.Inventory;
using AetherBags.Inventory.Context;
using Dalamud.Plugin.Ipc;
namespace AetherBags.IPC;
@@ -46,7 +48,6 @@ public class AllaganToolsIPC : IDisposable
_initialized.Subscribe(OnAllaganInitialized);
// Check if already initialized
try
{
IsReady = _isInitialized.InvokeFunc();
@@ -181,6 +182,12 @@ public class AllaganToolsIPC : IDisposable
}
}
public void SelectFilter(string filterKey)
{
HighlightState.SelectedAllaganToolsFilterKey = filterKey;
InventoryOrchestrator.RefreshHighlights();
}
public void Dispose()
{
_initialized?.Unsubscribe(OnAllaganInitialized);
+79
View File
@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using AetherBags.Inventory;
using AetherBags.Inventory.Context;
using Dalamud.Plugin.Ipc;
namespace AetherBags.IPC;
public class BisBuddyIPC : IDisposable
{
private ICallGateSubscriber<bool>? _isInitialized;
private ICallGateSubscriber<bool, bool>? _initialized;
private ICallGateSubscriber<List<uint>>? _getBisItems;
private ICallGateSubscriber<List<uint>, bool>? _bisItemsChanged;
public bool IsReady { get; private set; }
private static readonly Vector3 BisColor = new(0.0f, 0.3f, 0.0f);
public BisBuddyIPC()
{
try
{
_isInitialized = Services.PluginInterface.GetIpcSubscriber<bool>("BisBuddy.IsInitialized");
_initialized = Services.PluginInterface.GetIpcSubscriber<bool, bool>("BisBuddy.Initialized");
_getBisItems = Services.PluginInterface.GetIpcSubscriber<List<uint>>("BisBuddy.GetBisItems");
_bisItemsChanged = Services.PluginInterface.GetIpcSubscriber<List<uint>, bool>("BisBuddy.BisItemsChanged");
_initialized.Subscribe(OnInitialized);
_bisItemsChanged.Subscribe(UpdateHighlights);
try { IsReady = _isInitialized.InvokeFunc(); } catch { IsReady = false; }
if (IsReady) RequestUpdate();
}
catch (Exception ex)
{
Services.Logger.DebugOnly($"BisBuddy not available: {ex.Message}");
}
}
private void OnInitialized(bool ready)
{
IsReady = ready;
if (ready) RequestUpdate();
else HighlightState.ClearLabel(HighlightSource.BiSBuddy);
}
public void RequestUpdate()
{
if (!IsReady) return;
try
{
var items = _getBisItems?.InvokeFunc();
if (items != null) UpdateHighlights(items);
}
catch { IsReady = false; }
}
private void UpdateHighlights(List<uint>? itemIds)
{
if (!System.Config.Categories.BisBuddyEnabled || itemIds == null || itemIds.Count == 0)
{
HighlightState.ClearLabel(HighlightSource.BiSBuddy);
}
else
{
HighlightState.SetLabel(HighlightSource.BiSBuddy, itemIds, BisColor);
}
InventoryOrchestrator.RefreshHighlights();
}
public void Dispose()
{
_initialized?.Unsubscribe(OnInitialized);
_bisItemsChanged?.Unsubscribe(UpdateHighlights);
}
}
+4 -9
View File
@@ -6,18 +6,13 @@ 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 AllaganToolsIPC AllaganTools { get; } = new();
public WotsItIPC WotsIt { get; } = new();
public BisBuddyIPC BisBuddy { get; } = new();
public void Dispose()
{
AllaganTools.Dispose();
WotsIt.Dispose();
}
}
@@ -165,7 +165,7 @@ public static class CategoryBucketManager
bool allaganCategoriesEnabled)
{
if (!allaganCategoriesEnabled) return;
if (! System.IPC.AllaganTools.IsReady) return;
if (!System.IPC.AllaganTools.IsReady) return;
var filters = System.IPC.AllaganTools.CachedSearchFilters;
var filterItems = System.IPC.AllaganTools.CachedFilterItems;
@@ -0,0 +1,53 @@
using System.Collections.Generic;
using System.Numerics;
namespace AetherBags.Inventory.Context;
public enum HighlightSource
{
Search,
AllaganTools,
BiSBuddy,
}
public static class HighlightState
{
private static readonly Dictionary<HighlightSource, HashSet<uint>> Filters = new();
private static readonly Dictionary<HighlightSource, (HashSet<uint> ids, Vector3 color)> Labels = new();
public static string? SelectedAllaganToolsFilterKey { get; set; } = string.Empty;
public static bool IsFilterActive => Filters.Count > 0;
public static void SetFilter(HighlightSource source, IEnumerable<uint> ids)
=> Filters[source] = new HashSet<uint>(ids);
public static bool IsInActiveFilters(uint itemId)
{
if (Filters.Count == 0) return true;
foreach (var filter in Filters.Values)
if (filter.Contains(itemId)) return true;
return false;
}
public static Vector3? GetLabelColor(uint itemId)
{
foreach (var label in Labels.Values)
if (label.ids.Contains(itemId)) return label.color;
return null;
}
public static void SetLabel(HighlightSource source, IEnumerable<uint> ids, Vector3 color)
=> Labels[source] = (new HashSet<uint>(ids), color);
public static void ClearAll()
{
Filters.Clear();
Labels.Clear();
SelectedAllaganToolsFilterKey = string.Empty;
}
public static void ClearFilter(HighlightSource source) => Filters.Remove(source);
public static void ClearLabel(HighlightSource source) => Labels.Remove(source);
}
@@ -42,6 +42,17 @@ public static unsafe class InventoryOrchestrator
}
}
public static void RefreshHighlights()
{
Services.Framework.RunOnTick(() =>
{
foreach (var window in GetAllWindows())
{
window.ItemRefresh();
}
});
}
private static IEnumerable<IInventoryWindow> GetAllWindows()
{
yield return System.AddonInventoryWindow;
+44 -27
View File
@@ -82,38 +82,55 @@ public sealed class ItemInfo : IEquatable<ItemInfo>
{
get
{
uint contextId = InventoryContextState.ActiveContextId;
if (contextId == 0) return true;
bool isRetainerContext = contextId == 4;
bool isSaddlebagContext = contextId == 29;
bool isMainContext = !isRetainerContext && isSaddlebagContext == false;
if (IsMainInventory)
{
if (!isMainContext) return true;
return InventoryContextState.IsEligible(InventoryPage, Item.Slot);
}
if (Item.Container.IsRetainer)
{
// ...but the context isn't for Retainers, don't dim it.
if (!isRetainerContext)
return true;
}
// 3. If we are looking at a Saddlebag item...
if (Item.Container.IsSaddleBag)
{
if (!isSaddlebagContext)
return true;
}
if (IsSlotBlocked) return false;
if (!CheckNativeContextEligibility()) return false;
if (!HighlightState.IsInActiveFilters(Item.ItemId)) return false;
return true;
}
}
public float VisualAlpha => IsEligibleForContext ? 1.0f : 0.4f;
public Vector3 HighlightOverlayColor
{
get
{
if (!System.Config.Categories.BisBuddyEnabled)
return Vector3.Zero;
return HighlightState.GetLabelColor(Item.ItemId) ?? Vector3.Zero;
}
}
private bool CheckNativeContextEligibility()
{
uint contextId = InventoryContextState.ActiveContextId;
if (contextId == 0) return true;
bool isRetainerContext = contextId == 4;
bool isSaddlebagContext = contextId == 29;
bool isMainContext = !isRetainerContext && isSaddlebagContext == false;
if (IsMainInventory)
{
if (!isMainContext) return true;
return InventoryContextState.IsEligible(InventoryPage, Item.Slot);
}
if (Item.Container.IsRetainer)
{
if (!isRetainerContext) return true;
}
if (Item.Container.IsSaddleBag)
{
if (!isSaddlebagContext) return true;
}
return true;
}
public bool IsMainInventory => InventoryPage >= 0;
public bool IsRegexMatch(string searchTerms)
@@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.Linq;
using AetherBags.Configuration;
using AetherBags.Inventory.Categories;
using AetherBags.Inventory.Context;
using AetherBags.Inventory.Items;
using AetherBags.Inventory.Scanning;
using FFXIVClientStructs.FFXIV.Client.Game;
@@ -76,8 +77,19 @@ public abstract class InventoryStateBase
if (allaganCategoriesEnabled)
{
CategoryBucketManager.BucketByAllaganFilters(
ItemInfoByKey, BucketsByKey, ClaimedKeys, allaganCategoriesEnabled);
if (config.Categories.AllaganToolsMode == AllaganToolsFilterMode.Categorize)
{
CategoryBucketManager.BucketByAllaganFilters(ItemInfoByKey, BucketsByKey, ClaimedKeys, true);
HighlightState.ClearFilter(HighlightSource.AllaganTools);
}
else
{
UpdateAllaganHighlight(HighlightState.SelectedAllaganToolsFilterKey);
}
}
else
{
HighlightState.ClearFilter(HighlightSource.AllaganTools);
}
if (gameCategoriesEnabled)
@@ -92,6 +104,25 @@ public abstract class InventoryStateBase
}
}
private void UpdateAllaganHighlight(string? filterKey)
{
if (string.IsNullOrEmpty(filterKey) || !System.IPC.AllaganTools.IsReady)
{
HighlightState.ClearFilter(HighlightSource.AllaganTools);
return;
}
var filterItems = System.IPC.AllaganTools.GetFilterItems(filterKey);
if (filterItems != null)
{
HighlightState.SetFilter(HighlightSource.AllaganTools, filterItems.Keys);
}
else
{
HighlightState.ClearFilter(HighlightSource.AllaganTools);
}
}
public IReadOnlyList<CategorizedInventory> GetCategories(string filter = "", bool invert = false)
=> InventoryFilter.FilterCategories(AllCategories, BucketsByKey, FilteredCategories, filter, invert);
@@ -16,7 +16,7 @@ public class CategoryConfigurationNode : ConfigNode<CategoryWrapper>
{
_categoryList = new ScrollingListNode
{
AutoHideScrollbar = true,
AutoHideScrollBar = true,
};
_categoryList.FitContents = true;
_categoryList.AttachNode(this);
@@ -1,7 +1,12 @@
using System;
using System.Linq;
using System.Numerics;
using AetherBags.Configuration;
using AetherBags.Inventory;
using AetherBags.Inventory.Context;
using AetherBags.Nodes.Color;
using AetherBags.Nodes.Input;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Nodes;
@@ -71,7 +76,47 @@ public sealed class CategoryGeneralConfigurationNode : TabbedVerticalListNode
};
AddNode(userCategoriesEnabled);
bool bisBuddyReady = System.IPC.BisBuddy?.IsReady ?? false;
CheckboxNode bisBuddyEnabled = new CheckboxNode
{
Size = Size with { Y = 18 },
IsVisible = true,
String = bisBuddyReady ? "BISBuddy" : "BISBuddy (Not Available)",
IsChecked = config.BisBuddyEnabled,
TextTooltip = "Allow BISBuddy to highlight items.",
OnClick = isChecked =>
{
config.BisBuddyEnabled = isChecked;
System.IPC.BisBuddy?.RequestUpdate();
RefreshInventory();
}
};
AddNode(bisBuddyEnabled);
bool allaganReady = System.IPC.AllaganTools?.IsReady ?? false;
LabeledDropdownNode? atModeDropdown = new LabeledDropdownNode
{
Size = new Vector2(300, 20),
LabelText = "Filter Display Mode",
LabelTextFlags = TextFlags.AutoAdjustNodeSize,
IsEnabled = config.AllaganToolsCategoriesEnabled && allaganReady,
Options = Enum.GetNames(typeof(AllaganToolsFilterMode)).ToList(),
SelectedOption = config.AllaganToolsMode.ToString(),
OnOptionSelected = selected =>
{
if (Enum.TryParse<AllaganToolsFilterMode>(selected, out var parsed))
{
config.AllaganToolsMode = parsed;
if (parsed == AllaganToolsFilterMode.Categorize)
HighlightState.ClearFilter(HighlightSource.AllaganTools);
RefreshInventory();
}
}
};
_allaganToolsCheckbox = new CheckboxNode
{
Size = Size with { Y = 18 },
@@ -85,14 +130,16 @@ public sealed class CategoryGeneralConfigurationNode : TabbedVerticalListNode
OnClick = isChecked =>
{
config.AllaganToolsCategoriesEnabled = isChecked;
if (isChecked)
{
System.IPC?.AllaganTools?.RefreshFilters();
}
if (atModeDropdown != null) atModeDropdown.IsEnabled = isChecked;
if (isChecked) System.IPC?.AllaganTools?.RefreshFilters();
RefreshInventory();
}
};
AddNode(_allaganToolsCheckbox);
AddTab(1);
AddNode(atModeDropdown);
SubtractTab(1);
}
private void RefreshInventory() => InventoryOrchestrator.RefreshAll(updateMaps: true);
@@ -132,6 +132,24 @@ internal sealed class FunctionalConfigurationNode : TabbedVerticalListNode
};
AddNode(linkItemCheckBox);
var searchModeDropDown = new LabeledDropdownNode
{
Size = new Vector2(300, 20),
LabelText = "Search Mode",
LabelTextFlags = TextFlags.AutoAdjustNodeSize,
Options = Enum.GetNames(typeof(SearchMode)).ToList(),
SelectedOption = config.SearchMode.ToString(),
OnOptionSelected = selected =>
{
if (Enum.TryParse<SearchMode>(selected, out var parsed))
{
config.SearchMode = parsed;
InventoryOrchestrator.RefreshAll(updateMaps: false);
}
}
};
AddNode(searchModeDropDown);
_stackDropDown = new LabeledDropdownNode
{
Size = new Vector2(300, 20),
@@ -7,8 +7,6 @@ namespace AetherBags.Nodes.Configuration.General;
public sealed class GeneralScrollingAreaNode : ScrollingListNode
{
private readonly CheckboxNode _debugCheckboxNode = null!;
public GeneralScrollingAreaNode()
{
GeneralSettings config = System.Config.General;
@@ -0,0 +1,55 @@
using System;
using System.Numerics;
using KamiToolKit.Classes;
using KamiToolKit.Nodes;
using Lumina.Text.ReadOnly;
namespace AetherBags.Nodes.Input;
public class TextInputWithButtonNode : SimpleComponentNode {
private readonly TextInputNode _textInputNode;
private readonly CircleButtonNode _contextButton;
public Action? OnButtonClicked {
get => _contextButton.OnClick;
set => _contextButton.OnClick = value;
}
public TextInputWithButtonNode() {
_textInputNode = new TextInputNode {
PlaceholderString = "Search . . .",
};
_textInputNode.AttachNode(this);
_contextButton = new CircleButtonNode {
Icon = ButtonIcon.Filter,
Size = new Vector2(28f),
};
_contextButton.AttachNode(this);
}
public Vector3 HintAddColor {
get => _contextButton.AddColor;
set => _contextButton.AddColor = value;
}
public required Action<ReadOnlySeString>? OnInputReceived {
get => _textInputNode.OnInputReceived;
set => _textInputNode.OnInputReceived = value;
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
_contextButton.Size = new Vector2(Height, Height);
_contextButton.Position = new Vector2(Width - _contextButton.Width, 0.0f);
_textInputNode.Size = new Vector2(Width - _contextButton.Width - 5.0f, Height);
_textInputNode.Position = new Vector2(0.0f, 0.0f);
}
public ReadOnlySeString SearchString {
get => _textInputNode.SeString;
set => _textInputNode.SeString = value;
}
}
@@ -1,51 +0,0 @@
using System;
using System.Numerics;
using KamiToolKit.Nodes;
using Lumina.Text;
using Lumina.Text.ReadOnly;
namespace AetherBags.Nodes.Input;
public class TextInputWithHintNode : SimpleComponentNode {
private readonly TextInputNode _textInputNode;
private readonly ImageNode _helpNode;
public TextInputWithHintNode() {
_textInputNode = new TextInputNode {
PlaceholderString = "Search . . .",
};
_textInputNode.AttachNode(this);
_helpNode = new SimpleImageNode {
TexturePath = "ui/uld/CircleButtons.tex",
TextureCoordinates = new Vector2(112.0f, 84.0f),
TextureSize = new Vector2(28.0f, 28.0f),
TextTooltip = new SeStringBuilder()
.Append("Supports Regex Search")
.AppendNewLine()
.Append("Start input with '$' to search by description")
.ToReadOnlySeString(),
};
_helpNode.AttachNode(this);
}
public required Action<ReadOnlySeString>? OnInputReceived {
get => _textInputNode.OnInputReceived;
set => _textInputNode.OnInputReceived = value;
}
protected override void OnSizeChanged() {
base.OnSizeChanged();
_helpNode.Size = new Vector2(Height, Height);
_helpNode.Position = new Vector2(Width - _helpNode.Width - 5.0f, 0.0f);
_textInputNode.Size = new Vector2(Width - _helpNode.Width - 5.0f, Height);
_textInputNode.Position = new Vector2(0.0f, 0.0f);
}
public ReadOnlySeString SearchString {
get => _textInputNode.SeString;
set => _textInputNode.SeString = value;
}
}
@@ -14,8 +14,6 @@ using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Nodes;
// TODO: Switch back to CS version when Dalamud Updated
namespace AetherBags.Nodes.Inventory;
public class InventoryCategoryNode : SimpleComponentNode
@@ -237,17 +235,15 @@ public class InventoryCategoryNode : SimpleComponentNode
ReferenceIndex = (short)absoluteIndex
};
bool isSlotBlocked = item.Container.IsMainInventory && data.IsSlotBlocked;
float alpha = !isSlotBlocked && data.IsEligibleForContext ? 1.0f : 0.4f;
return new InventoryDragDropNode
{
Size = new Vector2(42, 46),
Alpha = alpha,
Alpha = data.VisualAlpha,
AddColor = data.HighlightOverlayColor,
IsDraggable = !data.IsSlotBlocked,
IsVisible = true,
IconId = item.IconId,
AcceptedType = DragDropType.Item,
IsDraggable = !data.IsSlotBlocked,
Payload = nodePayload,
IsClickable = true,
OnDiscard = node => OnDiscard(node, data),
@@ -269,6 +265,18 @@ public class InventoryCategoryNode : SimpleComponentNode
};
}
public void RefreshNodeVisuals()
{
foreach (var node in _itemGridNode.Nodes)
{
if (node is not InventoryDragDropNode itemNode || itemNode.ItemInfo == null) continue;
itemNode.Alpha = itemNode.ItemInfo.VisualAlpha;
itemNode.AddColor = itemNode.ItemInfo.HighlightOverlayColor;
itemNode.IsDraggable = !itemNode.ItemInfo.IsSlotBlocked;
}
}
private unsafe void OnDiscard(DragDropNode node, ItemInfo item)
{
uint addonId = RaptureAtkUnitManager.Instance()->GetAddonByNode(node)->Id;
@@ -299,7 +307,7 @@ public class InventoryCategoryNode : SimpleComponentNode
if (acceptedPayload.IsSameBaseContainer(nodePayload))
{
Services.Logger.Information("[OnPayload] Source and target are in the same base container, skipping move.");
Services.Logger.DebugOnly("[OnPayload] Source and target are in the same base container, skipping move.");
node.IconId = targetItemInfo.IconId;
node.Payload = nodePayload;
return;
+2 -1
View File
@@ -24,10 +24,11 @@ public unsafe class Plugin : IDalamudPlugin
{
pluginInterface.Create<Services>();
System.Config = Util.LoadConfigOrDefault();
BackupHelper.DoConfigBackup(pluginInterface);
KamiToolKitLibrary.Initialize(pluginInterface);
System.Config = Util.LoadConfigOrDefault();
System.IPC = new IPCService();