diff --git a/debug_loot_capture.png b/debug_loot_capture.png index a5cd149..1e7069b 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 85ba59a..56c21c2 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 41fc8a0..dcc5e79 100644 Binary files a/debug_loot_edges.png and b/debug_loot_edges.png differ diff --git a/src/Poe2Trade.Bot/BossRunExecutor.cs b/src/Poe2Trade.Bot/BossRunExecutor.cs index 5e2f538..e859ad9 100644 --- a/src/Poe2Trade.Bot/BossRunExecutor.cs +++ b/src/Poe2Trade.Bot/BossRunExecutor.cs @@ -351,12 +351,18 @@ public class BossRunExecutor : GameExecutor for (var phase = 1; phase <= 3; phase++) { if (_stopped) return; - Log.Information("=== Boss phase {Phase}/4 ===", phase); + var preWp = _nav.WorldPosition; + Log.Information("=== Boss phase {Phase}/4 === fightArea=({FX:F0},{FY:F0}) charPos=({CX:F1},{CY:F1})", + phase, fightWorldX, fightWorldY, preWp.X, preWp.Y); var lastBossPos = await AttackBossUntilGone(fightWorldX, fightWorldY); if (_stopped) return; // Update fight area to where the boss was last seen + var postWp = _nav.WorldPosition; + Log.Information("Phase {Phase} ended: charPos=({CX:F1},{CY:F1}) lastBossPos={Boss}", + phase, postWp.X, postWp.Y, + lastBossPos != null ? $"({lastBossPos.Value.X:F1},{lastBossPos.Value.Y:F1})" : "null"); if (lastBossPos != null) { fightWorldX = lastBossPos.Value.X; @@ -380,16 +386,27 @@ public class BossRunExecutor : GameExecutor // 4th fight - no well after if (_stopped) return; - Log.Information("=== Boss phase 4/4 ==="); + { + var p4wp = _nav.WorldPosition; + Log.Information("=== Boss phase 4/4 === fightArea=({FX:F0},{FY:F0}) charPos=({CX:F1},{CY:F1})", + fightWorldX, fightWorldY, p4wp.X, p4wp.Y); + } var finalBossPos = await AttackBossUntilGone(fightWorldX, fightWorldY); if (_stopped) return; // Update fight area from phase 4 if we got detections + { + var p4postWp = _nav.WorldPosition; + Log.Information("Phase 4 ended: charPos=({CX:F1},{CY:F1}) finalBossPos={Boss}", + p4postWp.X, p4postWp.Y, + finalBossPos != null ? $"({finalBossPos.Value.X:F1},{finalBossPos.Value.Y:F1})" : "null"); + } if (finalBossPos != null) { fightWorldX = finalBossPos.Value.X; fightWorldY = finalBossPos.Value.Y; } + Log.Information("Ring phase: using fightArea=({FX:F0},{FY:F0})", fightWorldX, fightWorldY); // Walk to known ring position and look for the template await WalkToWorldPosition(-440, -330); @@ -606,6 +623,19 @@ public class BossRunExecutor : GameExecutor const int screenCy = 720; const double screenToWorld = 97.0 / 835.0; (double X, double Y)? lastBossWorldPos = null; + var yoloLogCount = 0; + + // Subscribe to YOLO events for real-time chase updates + // (main loop is too slow due to template matching to effectively track boss) + void OnBossDetected(BossSnapshot snapshot) + { + if (snapshot.Bosses.Count == 0) return; + var boss = snapshot.Bosses[0]; + _combatTargetX = boss.Cx; + _combatTargetY = boss.Cy; + _combat.SetChaseTarget(boss.Cx, boss.Cy); + } + _bossDetector.BossDetected += OnBossDetected; Log.Information("Boss is alive, engaging"); var (combatTask, cts) = StartCombatLoop(screenCx, screenCy); @@ -620,39 +650,22 @@ public class BossRunExecutor : GameExecutor { if (_stopped) return lastBossWorldPos; - // Update attack target from YOLO (fast, no capture) + // Update lastBossWorldPos from latest YOLO (for phase tracking) var snapshot = _bossDetector.Latest; if (snapshot.Bosses.Count > 0) { var boss = snapshot.Bosses[0]; - _combatTargetX = boss.Cx; - _combatTargetY = boss.Cy; var wp = _nav.WorldPosition; lastBossWorldPos = ( wp.X + (boss.Cx - screenCx) * screenToWorld, wp.Y + (boss.Cy - screenCy) * screenToWorld); - // Walk toward boss to stay as close as possible - var bossDx = boss.Cx - screenCx; - var bossDy = boss.Cy - screenCy; - var bossDist = Math.Sqrt(bossDx * bossDx + bossDy * bossDy); - - if (bossDist > 50) - { - var dirX = bossDx / bossDist; - var dirY = bossDy / bossDist; - - var keys = new List(); - if (dirY < -0.3) keys.Add(InputSender.VK.W); - if (dirY > 0.3) keys.Add(InputSender.VK.S); - if (dirX < -0.3) keys.Add(InputSender.VK.A); - if (dirX > 0.3) keys.Add(InputSender.VK.D); - - foreach (var k in keys) await _game.KeyDown(k); - await Helpers.Sleep(150); - foreach (var k in keys) await _game.KeyUp(k); - } + yoloLogCount++; + if (yoloLogCount % 5 == 1) // log every 5th detection + Log.Information("YOLO boss: screen=({Sx},{Sy}) charWorld=({Cx:F1},{Cy:F1}) bossWorld=({Bx:F1},{By:F1}) conf={Conf:F2}", + boss.Cx, boss.Cy, wp.X, wp.Y, + lastBossWorldPos.Value.X, lastBossWorldPos.Value.Y, boss.Confidence); } // Check death + healthbar (combat keeps running in background) @@ -686,6 +699,8 @@ public class BossRunExecutor : GameExecutor } finally { + _bossDetector.BossDetected -= OnBossDetected; + _combat.ClearChaseTarget(); await StopCombatLoop(combatTask, cts); } } diff --git a/src/Poe2Trade.Bot/CombatManager.cs b/src/Poe2Trade.Bot/CombatManager.cs index f085215..8f00535 100644 --- a/src/Poe2Trade.Bot/CombatManager.cs +++ b/src/Poe2Trade.Bot/CombatManager.cs @@ -32,6 +32,11 @@ public class CombatManager private long _lastOrbitMs; private int _nextOrbitMs = OrbitStepMinMs; + // Chase — walks toward a screen position instead of orbiting + private volatile int _chaseX = -1; + private volatile int _chaseY = -1; + private readonly HashSet _chaseKeys = new(); + // Smoothed mouse position — lerps toward target to avoid jitter private double _smoothX = 1280; private double _smoothY = 720; @@ -39,6 +44,18 @@ public class CombatManager public bool IsHolding => _holding; + public void SetChaseTarget(int screenX, int screenY) + { + _chaseX = screenX; + _chaseY = screenY; + } + + public void ClearChaseTarget() + { + _chaseX = -1; + _chaseY = -1; + } + public CombatManager(IGameController game, HudReader hudReader, FlaskManager flasks) { _game = game; @@ -52,7 +69,16 @@ public class CombatManager public async Task Tick(int x, int y, int jitter = 30) { await _flasks.Tick(); - await UpdateOrbit(); + + if (_chaseX >= 0) + { + await UpdateChase(); + } + else + { + if (_chaseKeys.Count > 0) await ReleaseChaseKeys(); + await UpdateOrbit(); + } // Lerp smoothed position toward target _smoothX += (x - _smoothX) * SmoothFactor; @@ -106,6 +132,55 @@ public class CombatManager } } + /// + /// Walk toward chase target using held WASD keys. Replaces orbit when active. + /// + private async Task UpdateChase() + { + // Release orbit key when entering chase mode + if (_orbitIndex >= 0) + { + await _game.KeyUp(OrbitKeys[_orbitIndex]); + _orbitIndex = -1; + } + + const int screenCx = 1280, screenCy = 720; + var cx = _chaseX; + var cy = _chaseY; + var dx = cx - screenCx; + var dy = cy - screenCy; + var dist = Math.Sqrt(dx * dx + dy * dy); + + var wanted = new HashSet(); + if (dist > 100) + { + var dirX = dx / dist; + var dirY = dy / dist; + if (dirY < -0.3) wanted.Add(InputSender.VK.W); + if (dirY > 0.3) wanted.Add(InputSender.VK.S); + if (dirX < -0.3) wanted.Add(InputSender.VK.A); + if (dirX > 0.3) wanted.Add(InputSender.VK.D); + } + + foreach (var k in _chaseKeys.Except(wanted).ToList()) + { + await _game.KeyUp(k); + _chaseKeys.Remove(k); + } + foreach (var k in wanted.Except(_chaseKeys).ToList()) + { + await _game.KeyDown(k); + _chaseKeys.Add(k); + } + } + + private async Task ReleaseChaseKeys() + { + foreach (var k in _chaseKeys) + await _game.KeyUp(k); + _chaseKeys.Clear(); + } + /// /// Cycle WASD directions to orbit in a small circle while attacking. /// @@ -150,6 +225,9 @@ public class CombatManager _smoothX = 1280; _smoothY = 720; await ReleaseOrbit(); + await ReleaseChaseKeys(); + _chaseX = -1; + _chaseY = -1; } /// @@ -164,5 +242,8 @@ public class CombatManager _holding = false; } await ReleaseOrbit(); + await ReleaseChaseKeys(); + _chaseX = -1; + _chaseY = -1; } }