initial BFS movement
This commit is contained in:
parent
25257e1517
commit
490fb8bdba
7 changed files with 258 additions and 101 deletions
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue