Files
AetherBags/AetherBags/Monitoring/InventoryMonitor.cs
T
KnackAtNite 8db4ce6094
Debug Build and Test / Build against Latest Dalamud (push) Has been cancelled
Debug Build and Test / Build against Staging Dalamud (push) Has been cancelled
Initial commit: AetherBags + KamiToolKit for FC Gitea
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 14:46:31 -05:00

255 lines
8.4 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using AetherBags.Configuration;
using AetherBags.Inventory.Context;
using AetherBags.Inventory.Scanning;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.Inventory.InventoryEventArgTypes;
using Dalamud.Game.NativeWrapper;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
using Lumina.Text.ReadOnly;
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
namespace AetherBags.Monitoring;
public static unsafe class DragDropState
{
/// <summary>
/// Returns true if the game's drag-drop manager is currently dragging.
/// </summary>
public static bool IsDragging => AtkStage.Instance()->DragDropManager.IsDragging;
}
public class InventoryMonitor : IDisposable
{
public InventoryMonitor()
{
var bags = new[] { "Inventory", "InventoryLarge", "InventoryExpansion" };
var saddle = new[] { "InventoryBuddy" };
var retainer = new[] { "InventoryRetainer", "InventoryRetainerLarge" };
Services.AddonLifecycle.RegisterListener(AddonEvent.PostSetup, saddle, OnPostSetup);
Services.AddonLifecycle.RegisterListener(AddonEvent.PostSetup, retainer, OnPostSetup);
Services.AddonLifecycle.RegisterListener(AddonEvent.PreFinalize, saddle, OnPreFinalize);
Services.AddonLifecycle.RegisterListener(AddonEvent.PreFinalize, retainer, OnPreFinalize);
Services.AddonLifecycle.RegisterListener(AddonEvent.PreFinalize, bags, OnInventoryPreFinalize);
Services.AddonLifecycle.RegisterListener(AddonEvent.PreHide, bags, OnInventoryPreHide);
// PreRefresh Handlers
Services.AddonLifecycle.RegisterListener(AddonEvent.PreRefresh, bags, InventoryPreRefreshHandler);
// PostRequestedUpdate
Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, "Inventory", OnInventoryUpdate);
Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, "InventoryBuddy", OnSaddleBagUpdate);
Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, retainer, OnRetainerInventoryUpdate);
// Dalamud raw event for raw inventory changes (scans once per frame)
Services.GameInventory.InventoryChangedRaw += OnInventoryChangedRaw;
Services.Logger.Verbose("InventoryLifecycles initialized");
}
private void OnPreFinalize(AddonEvent type, AddonArgs args)
{
CloseInventories(args.AddonName);
}
private void OnPostSetup(AddonEvent type, AddonArgs args)
{
OpenInventories(args.AddonName);
}
private void OnInventoryPreFinalize(AddonEvent type, AddonArgs args)
{
System.AddonInventoryWindow.Close();
}
private void OnInventoryPreHide(AddonEvent type, AddonArgs args)
{
if (System.Config.General.OpenWithGameInventory)
{
System.AddonInventoryWindow.Close();
}
}
private unsafe void OpenInventories(string name)
{
GeneralSettings config = System.Config.General;
if (name.Contains("Retainer") && config.OpenRetainerWithGameInventory)
{
System.AddonRetainerWindow.Open();
if (config.HideGameRetainer)
{
var addon = RaptureAtkUnitManager.Instance()->GetAddonByName("InventoryRetainer");
if (addon != null)
{
addon->IsVisible = false;
}
addon = RaptureAtkUnitManager.Instance()->GetAddonByName("InventoryRetainerLarge");
if (addon != null)
{
addon->IsVisible = false;
}
}
}
if (name.Contains("InventoryBuddy") && config.OpenSaddleBagsWithGameInventory)
{
System.AddonSaddleBagWindow.Open();
if (config.HideGameSaddleBags)
{
var addon = RaptureAtkUnitManager.Instance()->GetAddonByName("InventoryBuddy");
if (addon != null)
{
addon->IsVisible = false;
}
}
}
}
private void CloseInventories(string name)
{
if (name.Contains("Retainer")) System.AddonRetainerWindow.Close();
if (name.Contains("InventoryBuddy")) System.AddonSaddleBagWindow.Close();
}
private static bool IsInUnsafeState()
{
if (!Services.ClientState.IsLoggedIn)
return true;
return Services.Condition.Any(ConditionFlag.BetweenAreas, ConditionFlag.BetweenAreas51);
}
/*
values[0] = OpenType
values[1] = OpenTitleId
values[2] = tab index
values[3] = InventoryAddonId | (OpenerAddonId << 16)
values[4] = focus
values[5] = title
values[6] = upper title
values[7] = can use Saddlebags (Agent InventoryBuddy IsActivatable)
*/
private void OnInventoryChangedRaw(IReadOnlyCollection<InventoryEventArgs> events)
{
bool needsRefresh = false;
foreach (var inventoryEventArgs in events)
{
if (InventoryScanner.StandardInventories.Contains((InventoryType)inventoryEventArgs.Item.ContainerType))
{
needsRefresh = true;
break;
}
}
if (needsRefresh)
{
Services.Framework.RunOnTick(() =>
{
if (IsInUnsafeState() || DragDropState.IsDragging) return;
System.LootedItemsTracker.FlushPendingChanges();
System.AddonInventoryWindow?.RefreshFromLifecycle();
System.AddonSaddleBagWindow?.RefreshFromLifecycle();
System.AddonRetainerWindow?.RefreshFromLifecycle();
});
}
}
private unsafe void InventoryPreRefreshHandler(AddonEvent type, AddonArgs args)
{
if (args is not AddonRefreshArgs refreshArgs)
return;
if (IsInUnsafeState())
return;
GeneralSettings config = System.Config.General;
Services.Logger.DebugOnly("PreRefresh event for Inventory detected");
AtkValuePtr[] atkValues = refreshArgs.AtkValueEnumerable.ToArray();
if (atkValues.Length < 7) return;
AtkValue* value5 = (AtkValue*)atkValues[5].Address;
AtkValue* value6 = (AtkValue*)atkValues[6].Address;
if (value5->Type != ValueType.ManagedString || value6->Type != ValueType.ManagedString)
return;
ReadOnlySeString title = value5->String.AsReadOnlySeString();
ReadOnlySeString upperTitle = value6->String.AsReadOnlySeString();
System.AddonInventoryWindow.SetNotification(new InventoryNotificationInfo(title, upperTitle));
if (config.HideGameInventory)
{
refreshArgs.AtkValueCount = 0;
}
if (config.OpenWithGameInventory)
{
var addon = RaptureAtkUnitManager.Instance()->GetAddonByName(args.AddonName);
bool isCurrentlyVisible = addon != null && addon->IsVisible;
if (!isCurrentlyVisible)
{
System.AddonInventoryWindow.Open();
}
}
}
private void OnInventoryUpdate(AddonEvent type, AddonArgs args)
{
if (IsInUnsafeState())
return;
if (DragDropState.IsDragging)
return;
System.LootedItemsTracker.FlushPendingChanges();
System.AddonInventoryWindow?.RefreshFromLifecycle();
}
private void OnSaddleBagUpdate(AddonEvent type, AddonArgs args)
{
if (IsInUnsafeState())
return;
if (DragDropState.IsDragging)
return;
System.LootedItemsTracker.FlushPendingChanges();
System.AddonSaddleBagWindow?.RefreshFromLifecycle();
}
private void OnRetainerInventoryUpdate(AddonEvent type, AddonArgs args)
{
if (IsInUnsafeState())
return;
if (DragDropState.IsDragging)
return;
System.LootedItemsTracker.FlushPendingChanges();
System.AddonRetainerWindow?.RefreshFromLifecycle();
}
public void Dispose()
{
Services.GameInventory.InventoryChangedRaw -= OnInventoryChangedRaw;
Services.AddonLifecycle.UnregisterListener(OnPostSetup, OnPreFinalize, OnInventoryUpdate, OnSaddleBagUpdate, OnRetainerInventoryUpdate, OnInventoryPreFinalize, OnInventoryPreHide, InventoryPreRefreshHandler);
}
}