attacking fixed

This commit is contained in:
Boki 2026-02-21 16:05:00 -05:00
parent 109b1b4059
commit f914443d86
5 changed files with 96 additions and 66 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 MiB

After

Width:  |  Height:  |  Size: 7.1 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 MiB

After

Width:  |  Height:  |  Size: 5.9 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 KiB

After

Width:  |  Height:  |  Size: 331 KiB

Before After
Before After

View file

@ -481,22 +481,70 @@ public class BossRunExecutor : GameExecutor
/// <summary>
/// Wait for the boss healthbar to appear (boss spawns/becomes active).
/// </summary>
// -- Async combat: combat runs in background, checks run on main thread --
private volatile int _combatTargetX = 1280;
private volatile int _combatTargetY = 720;
private volatile int _combatJitter = 30;
/// <summary>
/// Background combat loop — calls Tick continuously until cancelled.
/// Target position is updated via volatile fields from the main thread.
/// </summary>
private async Task RunCombatLoop(CancellationToken ct)
{
try
{
while (!ct.IsCancellationRequested)
await _combat.Tick(_combatTargetX, _combatTargetY, _combatJitter);
}
catch (OperationCanceledException) { }
}
/// <summary>
/// Start background combat loop, returning the task and CTS.
/// Caller must cancel + await + ReleaseAll when done.
/// </summary>
private (Task combatTask, CancellationTokenSource cts) StartCombatLoop(int x = 1280, int y = 720, int jitter = 30)
{
_combatTargetX = x;
_combatTargetY = y;
_combatJitter = jitter;
_combat.Reset().GetAwaiter().GetResult();
var cts = new CancellationTokenSource();
var task = Task.Run(() => RunCombatLoop(cts.Token));
return (task, cts);
}
private async Task StopCombatLoop(Task combatTask, CancellationTokenSource cts)
{
cts.Cancel();
try { await combatTask; } catch { /* expected */ }
await _combat.ReleaseAll();
cts.Dispose();
}
private async Task<bool> WaitForBossSpawn(int timeoutMs = 30_000)
{
Log.Information("Waiting for boss healthbar to appear...");
var sw = Stopwatch.StartNew();
var atkX = 1280;
var atkY = 720;
var lastCheckMs = 0L;
var (combatTask, cts) = StartCombatLoop();
try
{
while (sw.ElapsedMilliseconds < timeoutMs)
{
if (_stopped) return false;
// Only check healthbar + death every ~500ms to avoid blocking combat
if (sw.ElapsedMilliseconds - lastCheckMs > 500)
// Update target from YOLO (fast, no capture)
var snapshot = _bossDetector.Latest;
if (snapshot.Bosses.Count > 0)
{
lastCheckMs = sw.ElapsedMilliseconds;
_combatTargetX = snapshot.Bosses[0].Cx;
_combatTargetY = snapshot.Bosses[0].Cy;
}
// Check death + healthbar (blocks main thread, combat continues in background)
if (await CheckDeath()) continue;
if (await IsBossAlive())
@ -506,19 +554,14 @@ public class BossRunExecutor : GameExecutor
}
}
// Attack toward YOLO-detected boss if available, otherwise last known position
var snapshot = _bossDetector.Latest;
if (snapshot.Bosses.Count > 0)
{
atkX = snapshot.Bosses[0].Cx;
atkY = snapshot.Bosses[0].Cy;
}
await _combat.Tick(atkX, atkY);
}
Log.Warning("WaitForBossSpawn timed out after {Ms}ms", timeoutMs);
return false;
}
finally
{
await StopCombatLoop(combatTask, cts);
}
}
/// <summary>
/// Wait for boss to spawn, then attack until healthbar disappears.
@ -532,87 +575,68 @@ public class BossRunExecutor : GameExecutor
const int screenCx = 1280;
const int screenCy = 720;
// Attack target — defaults to screen center, updated by YOLO
var atkX = screenCx;
var atkY = screenCy;
(double X, double Y)? lastBossWorldPos = null;
await _game.MoveMouseFast(atkX, atkY);
await Helpers.Sleep(200);
var sw = Stopwatch.StartNew();
var lastCheckMs = 0L;
await _combat.Reset();
Log.Information("Boss is alive, engaging");
var (combatTask, cts) = StartCombatLoop(screenCx, screenCy);
try
{
var sw = Stopwatch.StartNew();
while (sw.ElapsedMilliseconds < timeoutMs)
{
if (_stopped) return lastBossWorldPos;
// Update attack target from YOLO when available
// Update attack target from YOLO (fast, no capture)
var snapshot = _bossDetector.Latest;
if (snapshot.Bosses.Count > 0)
{
var boss = snapshot.Bosses[0];
atkX = boss.Cx;
atkY = boss.Cy;
_combatTargetX = boss.Cx;
_combatTargetY = boss.Cy;
// Convert boss screen offset to world position
// Scale: sqrt(72²+65²)/835 ≈ 0.116 minimap px per screen px
const double screenToWorld = 97.0 / 835.0;
var wp = _nav.WorldPosition;
var bossWorldX = wp.X + (boss.Cx - screenCx) * screenToWorld;
var bossWorldY = wp.Y + (boss.Cy - screenCy) * screenToWorld;
lastBossWorldPos = (bossWorldX, bossWorldY);
lastBossWorldPos = (
wp.X + (boss.Cx - screenCx) * screenToWorld,
wp.Y + (boss.Cy - screenCy) * screenToWorld);
}
// Re-check healthbar every ~0.5s, first miss = phase over
if (sw.ElapsedMilliseconds - lastCheckMs > 500)
{
// Check death + healthbar (combat keeps running in background)
if (await CheckDeath()) continue;
var bossAlive = await IsBossAlive();
lastCheckMs = sw.ElapsedMilliseconds;
if (!bossAlive)
if (!await IsBossAlive())
{
Log.Information("Healthbar not found, boss phase over after {Ms}ms", sw.ElapsedMilliseconds);
return lastBossWorldPos;
}
}
await _combat.Tick(atkX, atkY);
}
Log.Warning("AttackBossUntilGone timed out after {Ms}ms", timeoutMs);
return lastBossWorldPos;
}
finally
{
await _combat.ReleaseAll();
await StopCombatLoop(combatTask, cts);
}
}
private async Task AttackAtPosition(int x, int y, int durationMs)
{
await _combat.Reset();
var sw = Stopwatch.StartNew();
var (combatTask, cts) = StartCombatLoop(x, y, jitter: 20);
try
{
var sw = Stopwatch.StartNew();
while (sw.ElapsedMilliseconds < durationMs)
{
if (_stopped) return;
if (await CheckDeath()) continue;
await _combat.Tick(x, y, jitter: 20);
await Helpers.Sleep(500);
}
}
finally
{
await _combat.ReleaseAll();
await StopCombatLoop(combatTask, cts);
}
}

View file

@ -170,19 +170,25 @@ public class ScreenReader : IScreenReader
// -- Template matching --
public Task<TemplateMatchResult?> TemplateMatch(string templatePath, Region? region = null)
{
return Task.Run(() =>
{
var result = _templateMatch.Match(templatePath, region);
if (result != null)
Log.Information("Template match found: ({X},{Y}) confidence={Conf:F3}", result.X, result.Y, result.Confidence);
return Task.FromResult(result);
return result;
});
}
public Task<List<TemplateMatchResult>> TemplateMatchAll(string templatePath, Region? region = null, double threshold = 0.7, bool silent = false)
{
return Task.Run(() =>
{
var results = _templateMatch.MatchAll(templatePath, region, threshold);
if (!silent)
Log.Information("TemplateMatchAll: {Count} matches for {Template}", results.Count, Path.GetFileName(templatePath));
return Task.FromResult(results);
return results;
});
}
// -- Save --