This commit is contained in:
Boki 2026-02-21 21:48:58 -05:00
parent aee3a7f22c
commit 152c74fa15
5 changed files with 122 additions and 26 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 MiB

After

Width:  |  Height:  |  Size: 7.2 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: 338 KiB

After

Width:  |  Height:  |  Size: 332 KiB

Before After
Before After

View file

@ -351,12 +351,18 @@ public class BossRunExecutor : GameExecutor
for (var phase = 1; phase <= 3; phase++) for (var phase = 1; phase <= 3; phase++)
{ {
if (_stopped) return; 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); var lastBossPos = await AttackBossUntilGone(fightWorldX, fightWorldY);
if (_stopped) return; if (_stopped) return;
// Update fight area to where the boss was last seen // 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) if (lastBossPos != null)
{ {
fightWorldX = lastBossPos.Value.X; fightWorldX = lastBossPos.Value.X;
@ -380,16 +386,27 @@ public class BossRunExecutor : GameExecutor
// 4th fight - no well after // 4th fight - no well after
if (_stopped) return; 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); var finalBossPos = await AttackBossUntilGone(fightWorldX, fightWorldY);
if (_stopped) return; if (_stopped) return;
// Update fight area from phase 4 if we got detections // 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) if (finalBossPos != null)
{ {
fightWorldX = finalBossPos.Value.X; fightWorldX = finalBossPos.Value.X;
fightWorldY = finalBossPos.Value.Y; 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 // Walk to known ring position and look for the template
await WalkToWorldPosition(-440, -330); await WalkToWorldPosition(-440, -330);
@ -606,6 +623,19 @@ public class BossRunExecutor : GameExecutor
const int screenCy = 720; const int screenCy = 720;
const double screenToWorld = 97.0 / 835.0; const double screenToWorld = 97.0 / 835.0;
(double X, double Y)? lastBossWorldPos = null; (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"); Log.Information("Boss is alive, engaging");
var (combatTask, cts) = StartCombatLoop(screenCx, screenCy); var (combatTask, cts) = StartCombatLoop(screenCx, screenCy);
@ -620,39 +650,22 @@ public class BossRunExecutor : GameExecutor
{ {
if (_stopped) return lastBossWorldPos; if (_stopped) return lastBossWorldPos;
// Update attack target from YOLO (fast, no capture) // Update lastBossWorldPos from latest YOLO (for phase tracking)
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];
_combatTargetX = boss.Cx;
_combatTargetY = boss.Cy;
var wp = _nav.WorldPosition; var wp = _nav.WorldPosition;
lastBossWorldPos = ( lastBossWorldPos = (
wp.X + (boss.Cx - screenCx) * screenToWorld, wp.X + (boss.Cx - screenCx) * screenToWorld,
wp.Y + (boss.Cy - screenCy) * screenToWorld); wp.Y + (boss.Cy - screenCy) * screenToWorld);
// Walk toward boss to stay as close as possible yoloLogCount++;
var bossDx = boss.Cx - screenCx; if (yoloLogCount % 5 == 1) // log every 5th detection
var bossDy = boss.Cy - screenCy; Log.Information("YOLO boss: screen=({Sx},{Sy}) charWorld=({Cx:F1},{Cy:F1}) bossWorld=({Bx:F1},{By:F1}) conf={Conf:F2}",
var bossDist = Math.Sqrt(bossDx * bossDx + bossDy * bossDy); boss.Cx, boss.Cy, wp.X, wp.Y,
lastBossWorldPos.Value.X, lastBossWorldPos.Value.Y, boss.Confidence);
if (bossDist > 50)
{
var dirX = bossDx / bossDist;
var dirY = bossDy / bossDist;
var keys = new List<int>();
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);
}
} }
// Check death + healthbar (combat keeps running in background) // Check death + healthbar (combat keeps running in background)
@ -686,6 +699,8 @@ public class BossRunExecutor : GameExecutor
} }
finally finally
{ {
_bossDetector.BossDetected -= OnBossDetected;
_combat.ClearChaseTarget();
await StopCombatLoop(combatTask, cts); await StopCombatLoop(combatTask, cts);
} }
} }

View file

@ -32,6 +32,11 @@ public class CombatManager
private long _lastOrbitMs; private long _lastOrbitMs;
private int _nextOrbitMs = OrbitStepMinMs; 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<int> _chaseKeys = new();
// Smoothed mouse position — lerps toward target to avoid jitter // Smoothed mouse position — lerps toward target to avoid jitter
private double _smoothX = 1280; private double _smoothX = 1280;
private double _smoothY = 720; private double _smoothY = 720;
@ -39,6 +44,18 @@ public class CombatManager
public bool IsHolding => _holding; 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) public CombatManager(IGameController game, HudReader hudReader, FlaskManager flasks)
{ {
_game = game; _game = game;
@ -52,7 +69,16 @@ public class CombatManager
public async Task Tick(int x, int y, int jitter = 30) public async Task Tick(int x, int y, int jitter = 30)
{ {
await _flasks.Tick(); await _flasks.Tick();
await UpdateOrbit();
if (_chaseX >= 0)
{
await UpdateChase();
}
else
{
if (_chaseKeys.Count > 0) await ReleaseChaseKeys();
await UpdateOrbit();
}
// Lerp smoothed position toward target // Lerp smoothed position toward target
_smoothX += (x - _smoothX) * SmoothFactor; _smoothX += (x - _smoothX) * SmoothFactor;
@ -106,6 +132,55 @@ public class CombatManager
} }
} }
/// <summary>
/// Walk toward chase target using held WASD keys. Replaces orbit when active.
/// </summary>
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<int>();
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();
}
/// <summary> /// <summary>
/// Cycle WASD directions to orbit in a small circle while attacking. /// Cycle WASD directions to orbit in a small circle while attacking.
/// </summary> /// </summary>
@ -150,6 +225,9 @@ public class CombatManager
_smoothX = 1280; _smoothX = 1280;
_smoothY = 720; _smoothY = 720;
await ReleaseOrbit(); await ReleaseOrbit();
await ReleaseChaseKeys();
_chaseX = -1;
_chaseY = -1;
} }
/// <summary> /// <summary>
@ -164,5 +242,8 @@ public class CombatManager
_holding = false; _holding = false;
} }
await ReleaseOrbit(); await ReleaseOrbit();
await ReleaseChaseKeys();
_chaseX = -1;
_chaseY = -1;
} }
} }