diff --git a/debug_loot_capture.png b/debug_loot_capture.png index ce062e7..cc5699e 100644 Binary files a/debug_loot_capture.png and b/debug_loot_capture.png differ diff --git a/debug_loot_detected.png b/debug_loot_detected.png index e96616c..ebf52d3 100644 Binary files a/debug_loot_detected.png and b/debug_loot_detected.png differ diff --git a/debug_loot_edges.png b/debug_loot_edges.png index 25d92de..9c976ce 100644 Binary files a/debug_loot_edges.png and b/debug_loot_edges.png differ diff --git a/src/Poe2Trade.Bot/KulemakExecutor.cs b/src/Poe2Trade.Bot/KulemakExecutor.cs index d80c99e..8990b62 100644 --- a/src/Poe2Trade.Bot/KulemakExecutor.cs +++ b/src/Poe2Trade.Bot/KulemakExecutor.cs @@ -129,37 +129,15 @@ public class KulemakExecutor : MappingExecutor } await Sleep(Delays.PostStashOpen); - // Scan inventory now that stash (and inventory panel) is open - var scanResult = await _inventory.SnapshotInventory(); - - if (scanResult.Occupied.Count > 0) + var (lootTab, lootFolder) = ResolveTabPath(_config.Kulemak.LootTabPath); + if (lootTab == null) { - var (lootTab, lootFolder) = ResolveTabPath(_config.Kulemak.LootTabPath); - if (lootTab != null) - { - await _inventory.ClickStashTab(lootTab, lootFolder); - - Log.Information("Depositing {Count} inventory items to loot tab", scanResult.Occupied.Count); - await _game.KeyDown(InputSender.VK.SHIFT); - await _game.HoldCtrl(); - foreach (var cell in scanResult.Occupied) - { - var center = _screen.Grid.GetCellCenter(GridLayouts.Inventory, cell.Row, cell.Col); - await _game.LeftClickAt(center.X, center.Y); - await Sleep(Delays.ClickInterval); - } - await _game.ReleaseCtrl(); - await _game.KeyUp(InputSender.VK.SHIFT); - await Sleep(Delays.PostEscape); - } - else - { - Log.Warning("Loot tab path not configured or not found, skipping deposit"); - } + Log.Warning("Loot tab path not configured or not found, skipping deposit"); } else { - Log.Information("Inventory empty, skipping loot deposit"); + await _inventory.ClickStashTab(lootTab, lootFolder); + await _inventory.DepositAllToOpenStash(); } if (invTab != null) @@ -465,30 +443,7 @@ public class KulemakExecutor : MappingExecutor if (lootTab != null) await _inventory.ClickStashTab(lootTab, lootFolder); - for (var pass = 0; pass < 3; pass++) - { - var scanResult = await _inventory.SnapshotInventory(); - if (scanResult.Items.Count == 0) - { - if (pass > 0) Log.Information("Inventory clear after {Pass} passes", pass); - break; - } - - Log.Information("Depositing {Count} items to loot tab (pass {Pass})", scanResult.Items.Count, pass + 1); - await _game.KeyDown(InputSender.VK.SHIFT); - await _game.HoldCtrl(); - - foreach (var item in scanResult.Items) - { - var center = _screen.Grid.GetCellCenter(GridLayouts.Inventory, item.Row, item.Col); - await _game.LeftClickAt(center.X, center.Y); - await Sleep(Delays.ClickInterval); - } - - await _game.ReleaseCtrl(); - await _game.KeyUp(InputSender.VK.SHIFT); - await Sleep(Delays.PostEscape); - } + await _inventory.DepositAllToOpenStash(); // Grab 1 invitation for the next run while stash is still open if (grabInvitation) diff --git a/src/Poe2Trade.Inventory/IInventoryManager.cs b/src/Poe2Trade.Inventory/IInventoryManager.cs index cca23e4..0cf6d9c 100644 --- a/src/Poe2Trade.Inventory/IInventoryManager.cs +++ b/src/Poe2Trade.Inventory/IInventoryManager.cs @@ -19,6 +19,7 @@ public interface IInventoryManager Task WaitForAreaTransition(int timeoutMs, Func? triggerAction = null); Task<(int X, int Y)?> FindAndClickNameplate(string name, int maxRetries = 3, int retryDelayMs = 1000, System.Drawing.Rectangle? scanRegion = null, string? savePath = null); Task DepositItemsToStash(List items); + Task DepositAllToOpenStash(); Task SalvageItems(List items); Task IdentifyItems(); (bool[,] Grid, List Items, int Free) GetInventoryState(); diff --git a/src/Poe2Trade.Inventory/InventoryManager.cs b/src/Poe2Trade.Inventory/InventoryManager.cs index bb67f0a..2d3e1d7 100644 --- a/src/Poe2Trade.Inventory/InventoryManager.cs +++ b/src/Poe2Trade.Inventory/InventoryManager.cs @@ -130,14 +130,73 @@ public class InventoryManager : IInventoryManager } await Helpers.Sleep(Delays.PostStashOpen); - Log.Information("Depositing {Count} items to stash", items.Count); - await CtrlClickItems(items, GridLayouts.Inventory); - - await SnapshotInventory(); + await DepositAllToOpenStash(); await _game.PressEscape(); await Helpers.Sleep(Delays.PostEscape); - Log.Information("Items deposited to stash"); + Log.Information("Deposit complete"); + } + + /// + /// Deposit one item at a time while stash is already open. + /// Scans → clicks first occupied cell → rescans → repeats. + /// Cells that remain occupied after clicking are marked as false positives and skipped. + /// + public async Task DepositAllToOpenStash() + { + await _game.KeyDown(Game.InputSender.VK.SHIFT); + await _game.HoldCtrl(); + + var falsePositives = new HashSet<(int Row, int Col)>(); + (int Row, int Col)? lastClicked = null; + + for (var i = 0; i < 60; i++) + { + if (lastClicked == null) + { + _game.MoveMouseInstant(1700, 700); + await Helpers.Sleep(50); + } + var scan = await _screen.Grid.Scan("inventory"); + LastScreenshot = await _screen.CaptureRegion(GridLayouts.Inventory.Region); + Updated?.Invoke(); + + // If we clicked a cell last iteration, check if it's still there (= false positive) + if (lastClicked.HasValue && + scan.Occupied.Any(c => c.Row == lastClicked.Value.Row && c.Col == lastClicked.Value.Col)) + { + Log.Debug("Cell ({Row},{Col}) still occupied after click — false positive", + lastClicked.Value.Row, lastClicked.Value.Col); + falsePositives.Add(lastClicked.Value); + } + lastClicked = null; + + var candidates = scan.Occupied + .Where(c => !falsePositives.Contains((c.Row, c.Col))) + .ToList(); + + if (candidates.Count == 0) + { + Log.Information(scan.Occupied.Count > 0 + ? $"All {scan.Occupied.Count} remaining occupied cells are false positives, done" + : "Inventory empty"); + break; + } + + var cell = candidates[0]; + var center = _screen.Grid.GetCellCenter(GridLayouts.Inventory, cell.Row, cell.Col); + Log.Information("Depositing cell ({Row},{Col}) — {Real} candidates / {Total} detected", + cell.Row, cell.Col, candidates.Count, scan.Occupied.Count); + _game.MoveMouseInstant(center.X, center.Y); + await Helpers.RandomDelay(50, 100); + _game.LeftMouseDown(); + _game.LeftMouseUp(); + lastClicked = (cell.Row, cell.Col); + await Helpers.RandomDelay(50, 100); + } + + await _game.ReleaseCtrl(); + await _game.KeyUp(Game.InputSender.VK.SHIFT); } public async Task SalvageItems(List items) @@ -237,10 +296,7 @@ public class InventoryManager : IInventoryManager } await ScanInventory(PostAction.Stash); - - var allItems = Tracker.GetItems(); - if (allItems.Count > 0) - await DepositItemsToStash(allItems); + await DepositItemsToStash(Tracker.GetItems()); Tracker.Clear(); Log.Information("Inventory processing complete");