initial BFS movement

This commit is contained in:
Boki 2026-02-13 16:56:44 -05:00
parent 25257e1517
commit 490fb8bdba
7 changed files with 258 additions and 101 deletions

View file

@ -58,60 +58,61 @@ public class NavigationExecutor : IDisposable
_stopped = false;
Log.Information("Starting explore loop");
// Open minimap overlay (Tab)
await _game.ToggleMinimap();
await Helpers.Sleep(300);
var lastMoveTime = 0L;
var lastClickTime = 0L;
var heldKeys = new HashSet<int>(); // currently held WASD keys
var lastMoveTime = long.MinValue;
while (!_stopped)
try
{
var frameStart = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
try
while (!_stopped)
{
// 1. Capture + track every frame (~30 fps)
SetState(NavigationState.Capturing);
using var frame = _capture.CaptureFrame();
if (frame == null)
var frameStart = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
try
{
Log.Warning("Failed to capture minimap frame");
await Helpers.Sleep(_config.CaptureIntervalMs);
continue;
}
SetState(NavigationState.Processing);
var pos = _worldMap.MatchAndStitch(frame.ClassifiedMat, frame.WallMask);
if (_worldMap.LastMatchSucceeded)
_capture.CommitWallColors();
// Stuck detection: position hasn't moved enough over several frames
if (_lastPosition != null)
{
var dx = pos.X - _lastPosition.X;
var dy = pos.Y - _lastPosition.Y;
if (Math.Sqrt(dx * dx + dy * dy) < _config.StuckThreshold)
_stuckCounter++;
else
_stuckCounter = 0;
}
_lastPosition = pos;
// 2. Movement decisions at slower rate
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
if (now - lastMoveTime >= _config.MovementWaitMs)
{
lastMoveTime = now;
if (_stuckCounter >= _config.StuckFrameCount)
// 1. Capture + track every frame
SetState(NavigationState.Capturing);
using var frame = _capture.CaptureFrame();
if (frame == null)
{
SetState(NavigationState.Stuck);
Log.Information("Stuck detected, clicking random direction");
await ClickRandomDirection();
Log.Warning("Failed to capture minimap frame");
await Helpers.Sleep(_config.CaptureIntervalMs);
continue;
}
else
SetState(NavigationState.Processing);
var pos = _worldMap.MatchAndStitch(frame.ClassifiedMat, frame.WallMask);
if (_worldMap.LastMatchSucceeded)
_capture.CommitWallColors();
// Stuck detection
if (_lastPosition != null)
{
SetState(NavigationState.Planning);
var dx = pos.X - _lastPosition.X;
var dy = pos.Y - _lastPosition.Y;
if (Math.Sqrt(dx * dx + dy * dy) < _config.StuckThreshold)
_stuckCounter++;
else
_stuckCounter = 0;
}
_lastPosition = pos;
// 2. Movement decisions (faster re-evaluation when stuck)
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
var moveInterval = _stuckCounter >= _config.StuckFrameCount
? 200
: _config.MovementWaitMs;
if (now - lastMoveTime >= moveInterval)
{
lastMoveTime = now;
if (_stuckCounter >= _config.StuckFrameCount)
SetState(NavigationState.Stuck);
else
SetState(NavigationState.Planning);
// BFS finds path through explored cells to nearest frontier
var direction = _worldMap.FindNearestUnexplored(pos);
if (direction == null)
@ -122,23 +123,45 @@ public class NavigationExecutor : IDisposable
}
SetState(NavigationState.Moving);
await ClickToMove(direction.Value.dirX, direction.Value.dirY);
await UpdateWasdKeys(heldKeys, direction.Value.dirX, direction.Value.dirY);
if (_stuckCounter >= _config.StuckFrameCount)
_stuckCounter = 0; // reset after re-routing
}
// 3. Occasional combat clicks near screen center
if (now - lastClickTime >= 1000 + Rng.Next(1000))
{
lastClickTime = now;
var cx = 1280 + Rng.Next(-150, 150);
var cy = 720 + Rng.Next(-150, 150);
await _game.LeftClickAt(cx, cy);
await Helpers.Sleep(100 + Rng.Next(100));
cx = 1280 + Rng.Next(-150, 150);
cy = 720 + Rng.Next(-150, 150);
await _game.RightClickAt(cx, cy);
}
}
}
catch (Exception ex)
{
Log.Error(ex, "Error in explore loop");
SetState(NavigationState.Failed);
await Helpers.Sleep(1000);
continue;
}
catch (Exception ex)
{
Log.Error(ex, "Error in explore loop");
SetState(NavigationState.Failed);
await Helpers.Sleep(1000);
continue;
}
// Sleep remainder of frame interval
var elapsed = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - frameStart;
var sleepMs = _config.CaptureIntervalMs - (int)elapsed;
if (sleepMs > 0)
await Helpers.Sleep(sleepMs);
// Sleep remainder of frame interval
var elapsed = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - frameStart;
var sleepMs = _config.CaptureIntervalMs - (int)elapsed;
if (sleepMs > 0)
await Helpers.Sleep(sleepMs);
}
}
finally
{
// Always release held keys
foreach (var key in heldKeys)
await _game.KeyUp(key);
}
if (_state != NavigationState.Completed)
@ -147,6 +170,39 @@ public class NavigationExecutor : IDisposable
Log.Information("Explore loop ended");
}
/// <summary>
/// Convert a direction vector to WASD key holds. Releases keys no longer needed,
/// presses new ones. Supports diagonal movement (two keys at once).
/// </summary>
private async Task UpdateWasdKeys(HashSet<int> held, double dirX, double dirY)
{
var wanted = new HashSet<int>();
// Threshold for diagonal: if both components are significant, hold both keys
const double threshold = 0.3;
if (dirY < -threshold) wanted.Add(InputSender.VK.W); // up
if (dirY > threshold) wanted.Add(InputSender.VK.S); // down
if (dirX < -threshold) wanted.Add(InputSender.VK.A); // left
if (dirX > threshold) wanted.Add(InputSender.VK.D); // right
// If direction is too weak, default to W
if (wanted.Count == 0) wanted.Add(InputSender.VK.W);
// Release keys no longer wanted
foreach (var key in held.Except(wanted).ToList())
{
await _game.KeyUp(key);
held.Remove(key);
}
// Press newly wanted keys
foreach (var key in wanted.Except(held).ToList())
{
await _game.KeyDown(key);
held.Add(key);
}
}
private async Task ClickToMove(double dirX, double dirY)
{
// Player is at minimap center on screen; click offset from center
@ -169,6 +225,7 @@ public class NavigationExecutor : IDisposable
await ClickToMove(Math.Cos(angle), Math.Sin(angle));
}
public bool IsExploring => _state != NavigationState.Idle && _state != NavigationState.Completed && _state != NavigationState.Failed;
public MapPosition Position => _worldMap.Position;
public byte[] GetMapSnapshot() => _worldMap.GetMapSnapshot();
public byte[] GetViewportSnapshot(int viewSize = 400) => _worldMap.GetViewportSnapshot(_worldMap.Position, viewSize);