using System.Diagnostics; using Poe2Trade.Core; using Poe2Trade.Game; using Poe2Trade.Screen; using Serilog; namespace Poe2Trade.Bot; /// /// Manages the attack state machine (click → hold) with mana monitoring and flask usage. /// Call each combat loop iteration, between phases, /// and when done. /// public class CombatManager { private static readonly Random Rng = new(); // Orbit: cycle W→D→S→A to dodge in a small circle private static readonly int[] OrbitKeys = [InputSender.VK.W, InputSender.VK.D, InputSender.VK.S, InputSender.VK.A]; private const int OrbitStepMinMs = 60; // short tap per direction → ~10px radius private const int OrbitStepMaxMs = 120; private readonly IGameController _game; private readonly HudReader _hudReader; private readonly FlaskManager _flasks; private bool _holding; private int _manaStableCount; private readonly Stopwatch _orbitSw = Stopwatch.StartNew(); private int _orbitIndex = -1; private long _lastOrbitMs; private int _nextOrbitMs = OrbitStepMinMs; // Smoothed mouse position — lerps toward target to avoid jitter private double _smoothX = 1280; private double _smoothY = 720; private const double SmoothFactor = 0.25; // 0=no movement, 1=instant snap public bool IsHolding => _holding; public CombatManager(IGameController game, HudReader hudReader, FlaskManager flasks) { _game = game; _hudReader = hudReader; _flasks = flasks; } /// /// One combat iteration: flask check, mana-based click/hold, mouse jitter toward target. /// public async Task Tick(int x, int y, int jitter = 30) { await _flasks.Tick(); await UpdateOrbit(); // Lerp smoothed position toward target _smoothX += (x - _smoothX) * SmoothFactor; _smoothY += (y - _smoothY) * SmoothFactor; var mouseX = (int)_smoothX + Rng.Next(-jitter, jitter + 1); var mouseY = (int)_smoothY + Rng.Next(-jitter, jitter + 1); var mana = _hudReader.Current.ManaPct; if (!_holding) { if (mana >= 0.80f) _manaStableCount++; else _manaStableCount = 0; await _game.MoveMouseFast(mouseX, mouseY); _game.LeftMouseDown(); await Helpers.Sleep(Rng.Next(20, 35)); _game.LeftMouseUp(); _game.RightMouseDown(); await Helpers.Sleep(Rng.Next(20, 35)); _game.RightMouseUp(); await Helpers.Sleep(Rng.Next(50, 80)); if (_manaStableCount >= 5) { Log.Information("Mana stable at {Mana:P0}, switching to hold attack", mana); _game.LeftMouseDown(); _game.RightMouseDown(); _holding = true; } } else { await _game.MoveMouseFast(mouseX, mouseY); if (mana < 0.30f) { Log.Information("Mana dropped to {Mana:P0}, releasing to recover", mana); _game.LeftMouseUp(); _game.RightMouseUp(); _holding = false; _manaStableCount = 0; } await Helpers.Sleep(Rng.Next(80, 120)); } } /// /// Cycle WASD directions to orbit in a small circle while attacking. /// private async Task UpdateOrbit() { var now = _orbitSw.ElapsedMilliseconds; if (now - _lastOrbitMs < _nextOrbitMs) return; _lastOrbitMs = now; _nextOrbitMs = Rng.Next(OrbitStepMinMs, OrbitStepMaxMs + 1); // Release previous direction if (_orbitIndex >= 0) await _game.KeyUp(OrbitKeys[_orbitIndex]); // Occasionally skip a direction to make movement less predictable var skip = Rng.Next(0, 5) == 0 ? 2 : 1; _orbitIndex = (_orbitIndex + skip) % OrbitKeys.Length; await _game.KeyDown(OrbitKeys[_orbitIndex]); } private async Task ReleaseOrbit() { if (_orbitIndex >= 0) { await _game.KeyUp(OrbitKeys[_orbitIndex]); _orbitIndex = -1; } } /// /// Reset state for a new combat phase (releases held buttons if any). /// public async Task Reset() { if (_holding) { _game.LeftMouseUp(); _game.RightMouseUp(); } _holding = false; _manaStableCount = 0; _smoothX = 1280; _smoothY = 720; await ReleaseOrbit(); } /// /// Release any held mouse buttons and movement keys. Call in finally blocks. /// public async Task ReleaseAll() { if (_holding) { _game.LeftMouseUp(); _game.RightMouseUp(); _holding = false; } await ReleaseOrbit(); } }