work on well of souls and yolo detection
This commit is contained in:
parent
3456e0d62a
commit
40d30115bf
41 changed files with 3031 additions and 148 deletions
|
|
@ -155,6 +155,7 @@ public class InventoryManager : IInventoryManager
|
|||
|
||||
private async Task CtrlClickItems(List<PlacedItem> items, GridLayout layout, int clickDelayMs = Delays.ClickInterval)
|
||||
{
|
||||
await _game.KeyDown(Game.InputSender.VK.SHIFT);
|
||||
await _game.HoldCtrl();
|
||||
foreach (var item in items)
|
||||
{
|
||||
|
|
@ -163,6 +164,7 @@ public class InventoryManager : IInventoryManager
|
|||
await Helpers.Sleep(clickDelayMs);
|
||||
}
|
||||
await _game.ReleaseCtrl();
|
||||
await _game.KeyUp(Game.InputSender.VK.SHIFT);
|
||||
await Helpers.Sleep(Delays.PostEscape);
|
||||
}
|
||||
|
||||
|
|
@ -208,13 +210,31 @@ public class InventoryManager : IInventoryManager
|
|||
for (var attempt = 1; attempt <= maxRetries; attempt++)
|
||||
{
|
||||
Log.Information("Searching for nameplate '{Name}' (attempt {Attempt}/{Max})", name, attempt, maxRetries);
|
||||
var pos = await _screen.FindTextOnScreen(name, fuzzy: true);
|
||||
|
||||
// Move mouse to bottom-left so it doesn't cover nameplates
|
||||
_game.MoveMouseInstant(0, 1440);
|
||||
await Helpers.Sleep(100);
|
||||
|
||||
// Nameplates hidden by default — capture clean reference
|
||||
using var reference = _screen.CaptureRawBitmap();
|
||||
|
||||
// Hold Alt to show nameplates, capture, then release
|
||||
await _game.KeyDown(Game.InputSender.VK.MENU);
|
||||
await Helpers.Sleep(50);
|
||||
using var current = _screen.CaptureRawBitmap();
|
||||
await _game.KeyUp(Game.InputSender.VK.MENU);
|
||||
|
||||
// Diff OCR — only processes the bright nameplate regions
|
||||
var result = await _screen.NameplateDiffOcr(reference, current);
|
||||
var pos = FindWordInOcrResult(result, name, fuzzy: true);
|
||||
if (pos.HasValue)
|
||||
{
|
||||
Log.Information("Clicking nameplate '{Name}' at ({X},{Y})", name, pos.Value.X, pos.Value.Y);
|
||||
await _game.LeftClickAt(pos.Value.X, pos.Value.Y);
|
||||
return pos;
|
||||
}
|
||||
|
||||
Log.Debug("Nameplate '{Name}' not found in diff OCR (attempt {Attempt}), text: {Text}", name, attempt, result.Text);
|
||||
if (attempt < maxRetries)
|
||||
await Helpers.Sleep(retryDelayMs);
|
||||
}
|
||||
|
|
@ -223,6 +243,73 @@ public class InventoryManager : IInventoryManager
|
|||
return null;
|
||||
}
|
||||
|
||||
private static (int X, int Y)? FindWordInOcrResult(OcrResponse result, string needle, bool fuzzy)
|
||||
{
|
||||
var lower = needle.ToLowerInvariant();
|
||||
|
||||
// Multi-word: match against full line text
|
||||
if (lower.Contains(' '))
|
||||
{
|
||||
foreach (var line in result.Lines)
|
||||
{
|
||||
if (line.Words.Count == 0) continue;
|
||||
if (line.Text.Contains(needle, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var first = line.Words[0];
|
||||
var last = line.Words[^1];
|
||||
return ((first.X + last.X + last.Width) / 2, (first.Y + last.Y + last.Height) / 2);
|
||||
}
|
||||
if (fuzzy)
|
||||
{
|
||||
var sim = BigramSimilarity(Normalize(needle), Normalize(line.Text));
|
||||
if (sim >= 0.55)
|
||||
{
|
||||
var first = line.Words[0];
|
||||
var last = line.Words[^1];
|
||||
return ((first.X + last.X + last.Width) / 2, (first.Y + last.Y + last.Height) / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Single word
|
||||
foreach (var line in result.Lines)
|
||||
foreach (var word in line.Words)
|
||||
{
|
||||
if (word.Text.Contains(needle, StringComparison.OrdinalIgnoreCase))
|
||||
return (word.X + word.Width / 2, word.Y + word.Height / 2);
|
||||
if (fuzzy && BigramSimilarity(Normalize(needle), Normalize(word.Text)) >= 0.55)
|
||||
return (word.X + word.Width / 2, word.Y + word.Height / 2);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string Normalize(string s) =>
|
||||
new(s.ToLowerInvariant().Where(char.IsLetterOrDigit).ToArray());
|
||||
|
||||
private static double BigramSimilarity(string a, string b)
|
||||
{
|
||||
if (a.Length < 2 || b.Length < 2) return a == b ? 1 : 0;
|
||||
var bigramsA = new Dictionary<(char, char), int>();
|
||||
for (var i = 0; i < a.Length - 1; i++)
|
||||
{
|
||||
var bg = (a[i], a[i + 1]);
|
||||
bigramsA[bg] = bigramsA.GetValueOrDefault(bg) + 1;
|
||||
}
|
||||
var matches = 0;
|
||||
for (var i = 0; i < b.Length - 1; i++)
|
||||
{
|
||||
var bg = (b[i], b[i + 1]);
|
||||
if (bigramsA.TryGetValue(bg, out var count) && count > 0)
|
||||
{
|
||||
matches++;
|
||||
bigramsA[bg] = count - 1;
|
||||
}
|
||||
}
|
||||
return 2.0 * matches / (a.Length - 1 + b.Length - 1);
|
||||
}
|
||||
|
||||
public async Task<bool> WaitForAreaTransition(int timeoutMs, Func<Task>? triggerAction = null)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
|
@ -254,4 +341,64 @@ public class InventoryManager : IInventoryManager
|
|||
{
|
||||
return (Tracker.GetGrid(), Tracker.GetItems(), Tracker.FreeCells);
|
||||
}
|
||||
|
||||
public async Task ClickStashTab(StashTabInfo tab, StashTabInfo? parentFolder = null)
|
||||
{
|
||||
if (parentFolder != null)
|
||||
{
|
||||
Log.Information("Clicking folder '{Folder}' at ({X},{Y})", parentFolder.Name, parentFolder.ClickX, parentFolder.ClickY);
|
||||
await _game.LeftClickAt(parentFolder.ClickX, parentFolder.ClickY);
|
||||
await Helpers.Sleep(200);
|
||||
}
|
||||
|
||||
Log.Information("Clicking tab '{Tab}' at ({X},{Y})", tab.Name, tab.ClickX, tab.ClickY);
|
||||
await _game.LeftClickAt(tab.ClickX, tab.ClickY);
|
||||
await Helpers.Sleep(Delays.PostStashOpen);
|
||||
}
|
||||
|
||||
public async Task GrabItemsFromStash(string layoutName, int maxItems, string? templatePath = null)
|
||||
{
|
||||
Log.Information("Grabbing up to {Max} items from stash layout '{Layout}' (template={Template})",
|
||||
maxItems, layoutName, templatePath ?? "none");
|
||||
|
||||
var layout = GridLayouts.All[layoutName];
|
||||
|
||||
if (templatePath != null)
|
||||
{
|
||||
// Template matching mode: repeatedly find and click matching items
|
||||
var grabbed = 0;
|
||||
await _game.HoldCtrl();
|
||||
while (grabbed < maxItems)
|
||||
{
|
||||
var match = await _screen.TemplateMatch(templatePath, layout.Region);
|
||||
if (match == null) break;
|
||||
|
||||
Log.Information("Template match at ({X},{Y}), grabbing", match.X, match.Y);
|
||||
await _game.LeftClickAt(match.X, match.Y);
|
||||
await Helpers.Sleep(Delays.ClickInterval);
|
||||
grabbed++;
|
||||
}
|
||||
await _game.ReleaseCtrl();
|
||||
await Helpers.Sleep(Delays.PostEscape);
|
||||
Log.Information("Grabbed {Count} matching items from stash", grabbed);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Grid scan mode: grab all occupied cells
|
||||
var result = await _screen.Grid.Scan(layoutName);
|
||||
var grabbed = 0;
|
||||
await _game.HoldCtrl();
|
||||
foreach (var cell in result.Occupied)
|
||||
{
|
||||
if (grabbed >= maxItems) break;
|
||||
var center = _screen.Grid.GetCellCenter(layout, cell.Row, cell.Col);
|
||||
await _game.LeftClickAt(center.X, center.Y);
|
||||
await Helpers.Sleep(Delays.ClickInterval);
|
||||
grabbed++;
|
||||
}
|
||||
await _game.ReleaseCtrl();
|
||||
await Helpers.Sleep(Delays.PostEscape);
|
||||
Log.Information("Grabbed {Count} items from stash", grabbed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue