attacking fixed
This commit is contained in:
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 |
Binary file not shown.
|
Before Width: | Height: | Size: 6.3 MiB After Width: | Height: | Size: 5.9 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 354 KiB After Width: | Height: | Size: 331 KiB |
|
|
@ -481,22 +481,70 @@ public class BossRunExecutor : GameExecutor
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wait for the boss healthbar to appear (boss spawns/becomes active).
|
/// Wait for the boss healthbar to appear (boss spawns/becomes active).
|
||||||
/// </summary>
|
/// </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)
|
private async Task<bool> WaitForBossSpawn(int timeoutMs = 30_000)
|
||||||
{
|
{
|
||||||
Log.Information("Waiting for boss healthbar to appear...");
|
Log.Information("Waiting for boss healthbar to appear...");
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
var atkX = 1280;
|
|
||||||
var atkY = 720;
|
|
||||||
var lastCheckMs = 0L;
|
|
||||||
|
|
||||||
while (sw.ElapsedMilliseconds < timeoutMs)
|
var (combatTask, cts) = StartCombatLoop();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (_stopped) return false;
|
while (sw.ElapsedMilliseconds < timeoutMs)
|
||||||
|
|
||||||
// Only check healthbar + death every ~500ms to avoid blocking combat
|
|
||||||
if (sw.ElapsedMilliseconds - lastCheckMs > 500)
|
|
||||||
{
|
{
|
||||||
lastCheckMs = sw.ElapsedMilliseconds;
|
if (_stopped) return false;
|
||||||
|
|
||||||
|
// Update target from YOLO (fast, no capture)
|
||||||
|
var snapshot = _bossDetector.Latest;
|
||||||
|
if (snapshot.Bosses.Count > 0)
|
||||||
|
{
|
||||||
|
_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 CheckDeath()) continue;
|
||||||
|
|
||||||
if (await IsBossAlive())
|
if (await IsBossAlive())
|
||||||
|
|
@ -506,18 +554,13 @@ public class BossRunExecutor : GameExecutor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attack toward YOLO-detected boss if available, otherwise last known position
|
Log.Warning("WaitForBossSpawn timed out after {Ms}ms", timeoutMs);
|
||||||
var snapshot = _bossDetector.Latest;
|
return false;
|
||||||
if (snapshot.Bosses.Count > 0)
|
}
|
||||||
{
|
finally
|
||||||
atkX = snapshot.Bosses[0].Cx;
|
{
|
||||||
atkY = snapshot.Bosses[0].Cy;
|
await StopCombatLoop(combatTask, cts);
|
||||||
}
|
|
||||||
await _combat.Tick(atkX, atkY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Warning("WaitForBossSpawn timed out after {Ms}ms", timeoutMs);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -532,60 +575,41 @@ public class BossRunExecutor : GameExecutor
|
||||||
|
|
||||||
const int screenCx = 1280;
|
const int screenCx = 1280;
|
||||||
const int screenCy = 720;
|
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;
|
(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");
|
Log.Information("Boss is alive, engaging");
|
||||||
|
var (combatTask, cts) = StartCombatLoop(screenCx, screenCy);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
while (sw.ElapsedMilliseconds < timeoutMs)
|
while (sw.ElapsedMilliseconds < timeoutMs)
|
||||||
{
|
{
|
||||||
if (_stopped) return lastBossWorldPos;
|
if (_stopped) return lastBossWorldPos;
|
||||||
|
|
||||||
// Update attack target from YOLO when available
|
// Update attack target from YOLO (fast, no capture)
|
||||||
var snapshot = _bossDetector.Latest;
|
var snapshot = _bossDetector.Latest;
|
||||||
if (snapshot.Bosses.Count > 0)
|
if (snapshot.Bosses.Count > 0)
|
||||||
{
|
{
|
||||||
var boss = snapshot.Bosses[0];
|
var boss = snapshot.Bosses[0];
|
||||||
atkX = boss.Cx;
|
_combatTargetX = boss.Cx;
|
||||||
atkY = boss.Cy;
|
_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;
|
const double screenToWorld = 97.0 / 835.0;
|
||||||
var wp = _nav.WorldPosition;
|
var wp = _nav.WorldPosition;
|
||||||
var bossWorldX = wp.X + (boss.Cx - screenCx) * screenToWorld;
|
lastBossWorldPos = (
|
||||||
var bossWorldY = wp.Y + (boss.Cy - screenCy) * screenToWorld;
|
wp.X + (boss.Cx - screenCx) * screenToWorld,
|
||||||
lastBossWorldPos = (bossWorldX, bossWorldY);
|
wp.Y + (boss.Cy - screenCy) * screenToWorld);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-check healthbar every ~0.5s, first miss = phase over
|
// Check death + healthbar (combat keeps running in background)
|
||||||
if (sw.ElapsedMilliseconds - lastCheckMs > 500)
|
if (await CheckDeath()) continue;
|
||||||
|
|
||||||
|
if (!await IsBossAlive())
|
||||||
{
|
{
|
||||||
if (await CheckDeath()) continue;
|
Log.Information("Healthbar not found, boss phase over after {Ms}ms", sw.ElapsedMilliseconds);
|
||||||
|
return lastBossWorldPos;
|
||||||
var bossAlive = await IsBossAlive();
|
|
||||||
lastCheckMs = sw.ElapsedMilliseconds;
|
|
||||||
|
|
||||||
if (!bossAlive)
|
|
||||||
{
|
|
||||||
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);
|
Log.Warning("AttackBossUntilGone timed out after {Ms}ms", timeoutMs);
|
||||||
|
|
@ -593,26 +617,26 @@ public class BossRunExecutor : GameExecutor
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
await _combat.ReleaseAll();
|
await StopCombatLoop(combatTask, cts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AttackAtPosition(int x, int y, int durationMs)
|
private async Task AttackAtPosition(int x, int y, int durationMs)
|
||||||
{
|
{
|
||||||
await _combat.Reset();
|
var (combatTask, cts) = StartCombatLoop(x, y, jitter: 20);
|
||||||
var sw = Stopwatch.StartNew();
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
while (sw.ElapsedMilliseconds < durationMs)
|
while (sw.ElapsedMilliseconds < durationMs)
|
||||||
{
|
{
|
||||||
if (_stopped) return;
|
if (_stopped) return;
|
||||||
if (await CheckDeath()) continue;
|
if (await CheckDeath()) continue;
|
||||||
await _combat.Tick(x, y, jitter: 20);
|
await Helpers.Sleep(500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
await _combat.ReleaseAll();
|
await StopCombatLoop(combatTask, cts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -171,18 +171,24 @@ public class ScreenReader : IScreenReader
|
||||||
|
|
||||||
public Task<TemplateMatchResult?> TemplateMatch(string templatePath, Region? region = null)
|
public Task<TemplateMatchResult?> TemplateMatch(string templatePath, Region? region = null)
|
||||||
{
|
{
|
||||||
var result = _templateMatch.Match(templatePath, region);
|
return Task.Run(() =>
|
||||||
if (result != null)
|
{
|
||||||
Log.Information("Template match found: ({X},{Y}) confidence={Conf:F3}", result.X, result.Y, result.Confidence);
|
var result = _templateMatch.Match(templatePath, region);
|
||||||
return Task.FromResult(result);
|
if (result != null)
|
||||||
|
Log.Information("Template match found: ({X},{Y}) confidence={Conf:F3}", result.X, result.Y, result.Confidence);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<List<TemplateMatchResult>> TemplateMatchAll(string templatePath, Region? region = null, double threshold = 0.7, bool silent = false)
|
public Task<List<TemplateMatchResult>> TemplateMatchAll(string templatePath, Region? region = null, double threshold = 0.7, bool silent = false)
|
||||||
{
|
{
|
||||||
var results = _templateMatch.MatchAll(templatePath, region, threshold);
|
return Task.Run(() =>
|
||||||
if (!silent)
|
{
|
||||||
Log.Information("TemplateMatchAll: {Count} matches for {Template}", results.Count, Path.GetFileName(templatePath));
|
var results = _templateMatch.MatchAll(templatePath, region, threshold);
|
||||||
return Task.FromResult(results);
|
if (!silent)
|
||||||
|
Log.Information("TemplateMatchAll: {Count} matches for {Template}", results.Count, Path.GetFileName(templatePath));
|
||||||
|
return results;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Save --
|
// -- Save --
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue