boss getting close
This commit is contained in:
parent
f914443d86
commit
aee3a7f22c
19 changed files with 422 additions and 119 deletions
|
|
@ -107,6 +107,7 @@ public class BossRunExecutor : GameExecutor
|
|||
if (_stopped) break;
|
||||
|
||||
SetState(BossRunState.Looting);
|
||||
await Helpers.Sleep(1000); // wait for loot labels to render
|
||||
await Loot();
|
||||
if (_stopped) break;
|
||||
|
||||
|
|
@ -316,7 +317,7 @@ public class BossRunExecutor : GameExecutor
|
|||
Log.Information("Fight phase starting");
|
||||
|
||||
// Wait for arena to settle
|
||||
await Helpers.Sleep(4500);
|
||||
await Helpers.Sleep(3000);
|
||||
if (_stopped) return;
|
||||
|
||||
// Find and click the cathedral door
|
||||
|
|
@ -343,7 +344,8 @@ public class BossRunExecutor : GameExecutor
|
|||
const double wellWorldY = -378;
|
||||
|
||||
await WalkToWorldPosition(fightWorldX, fightWorldY, cancelWhen: IsBossAlive);
|
||||
if (_stopped) return;
|
||||
_nav.Frozen = true; // Lock canvas — position tracking only
|
||||
if (_stopped) { _nav.Frozen = false; return; }
|
||||
|
||||
// 3x fight-then-well loop
|
||||
for (var phase = 1; phase <= 3; phase++)
|
||||
|
|
@ -351,7 +353,7 @@ public class BossRunExecutor : GameExecutor
|
|||
if (_stopped) return;
|
||||
Log.Information("=== Boss phase {Phase}/4 ===", phase);
|
||||
|
||||
var lastBossPos = await AttackBossUntilGone();
|
||||
var lastBossPos = await AttackBossUntilGone(fightWorldX, fightWorldY);
|
||||
if (_stopped) return;
|
||||
|
||||
// Update fight area to where the boss was last seen
|
||||
|
|
@ -368,7 +370,7 @@ public class BossRunExecutor : GameExecutor
|
|||
// Walk to well and click the closest match to screen center
|
||||
Log.Information("Phase {Phase} done, walking to well", phase);
|
||||
await WalkToWorldPosition(wellWorldX, wellWorldY);
|
||||
await Helpers.Sleep(1000);
|
||||
await Helpers.Sleep(500);
|
||||
await ClickClosestTemplateToCenter(CathedralWellTemplate);
|
||||
await Helpers.Sleep(200);
|
||||
|
||||
|
|
@ -379,29 +381,32 @@ public class BossRunExecutor : GameExecutor
|
|||
// 4th fight - no well after
|
||||
if (_stopped) return;
|
||||
Log.Information("=== Boss phase 4/4 ===");
|
||||
var phase4BossPos = await AttackBossUntilGone();
|
||||
var finalBossPos = await AttackBossUntilGone(fightWorldX, fightWorldY);
|
||||
if (_stopped) return;
|
||||
|
||||
// Walk toward where the boss died (ring spawns there)
|
||||
var ringX = phase4BossPos?.X ?? fightWorldX;
|
||||
var ringY = phase4BossPos?.Y ?? fightWorldY;
|
||||
Log.Information("Walking to ring area ({X:F0},{Y:F0})", ringX, ringY);
|
||||
await WalkToWorldPosition(ringX, ringY);
|
||||
// Update fight area from phase 4 if we got detections
|
||||
if (finalBossPos != null)
|
||||
{
|
||||
fightWorldX = finalBossPos.Value.X;
|
||||
fightWorldY = finalBossPos.Value.Y;
|
||||
}
|
||||
|
||||
// Walk to known ring position and look for the template
|
||||
await WalkToWorldPosition(-440, -330);
|
||||
await Helpers.Sleep(1000);
|
||||
if (_stopped) return;
|
||||
|
||||
Log.Information("Looking for Return the Ring...");
|
||||
var ring = await _screen.TemplateMatch(ReturnTheRingTemplate);
|
||||
if (ring == null)
|
||||
{
|
||||
Log.Warning("Could not find Return the Ring template, retrying after 2s...");
|
||||
await Helpers.Sleep(2000);
|
||||
ring = await _screen.TemplateMatch(ReturnTheRingTemplate);
|
||||
}
|
||||
if (ring != null)
|
||||
{
|
||||
Log.Information("Found Return the Ring at ({X},{Y}), clicking", ring.X, ring.Y);
|
||||
await _game.LeftClickAt(ring.X, ring.Y);
|
||||
await Helpers.Sleep(2000);
|
||||
await Helpers.Sleep(500);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -409,27 +414,20 @@ public class BossRunExecutor : GameExecutor
|
|||
}
|
||||
if (_stopped) return;
|
||||
|
||||
// Walk up and press Q
|
||||
Log.Information("Walking up and pressing Q");
|
||||
await _game.KeyDown(InputSender.VK.W);
|
||||
await Helpers.Sleep(1500);
|
||||
await _game.KeyUp(InputSender.VK.W);
|
||||
// Walk back to fight area — fightWorldX/Y carries position from all phases
|
||||
Log.Information("Walking to fight position ({X:F0},{Y:F0})", fightWorldX, fightWorldY);
|
||||
await WalkToWorldPosition(fightWorldX, fightWorldY);
|
||||
await Helpers.Sleep(300);
|
||||
await _game.PressKey(InputSender.VK.Q);
|
||||
await Helpers.Sleep(500);
|
||||
|
||||
// Spam L+R at position for 7s
|
||||
Log.Information("Attacking at ring fight position (Q phase)");
|
||||
await AttackAtPosition(1280, 720, 7000);
|
||||
await Helpers.Sleep(300);
|
||||
await _game.PressKey(InputSender.VK.E);
|
||||
await Helpers.Sleep(300);
|
||||
Log.Information("Attacking at ring fight position");
|
||||
await AttackBossUntilGone(fightWorldX, fightWorldY);
|
||||
if (_stopped) return;
|
||||
|
||||
// Press E, spam L+R at same position for 7s
|
||||
Log.Information("Pressing E and continuing attack");
|
||||
await _game.PressKey(InputSender.VK.E);
|
||||
await Helpers.Sleep(500);
|
||||
await AttackAtPosition(1280, 720, 7000);
|
||||
|
||||
StopBossDetection();
|
||||
_nav.Frozen = false;
|
||||
Log.Information("Fight complete");
|
||||
}
|
||||
|
||||
|
|
@ -468,7 +466,8 @@ public class BossRunExecutor : GameExecutor
|
|||
if (now - _lastDeathCheckMs < 2000) return false;
|
||||
_lastDeathCheckMs = now;
|
||||
|
||||
var match = await _screen.TemplateMatch(ResurrectTemplate);
|
||||
var deathRegion = new Region(1090, 1030, 370, 60);
|
||||
var match = await _screen.TemplateMatch(ResurrectTemplate, deathRegion);
|
||||
if (match == null) return false;
|
||||
|
||||
Log.Warning("Death detected! Clicking resurrect at ({X},{Y})", match.X, match.Y);
|
||||
|
|
@ -522,6 +521,27 @@ public class BossRunExecutor : GameExecutor
|
|||
try { await combatTask; } catch { /* expected */ }
|
||||
await _combat.ReleaseAll();
|
||||
cts.Dispose();
|
||||
await WaitForStablePosition();
|
||||
}
|
||||
|
||||
private async Task WaitForStablePosition(int minConsecutive = 5, int timeoutMs = 2000)
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
var consecutive = 0;
|
||||
while (sw.ElapsedMilliseconds < timeoutMs && consecutive < minConsecutive)
|
||||
{
|
||||
await Helpers.Sleep(50);
|
||||
if (_nav.LastMatchSucceeded)
|
||||
consecutive++;
|
||||
else
|
||||
consecutive = 0;
|
||||
}
|
||||
if (consecutive >= minConsecutive)
|
||||
Log.Information("Position stabilized ({Count} consecutive matches in {Ms}ms)",
|
||||
consecutive, sw.ElapsedMilliseconds);
|
||||
else
|
||||
Log.Warning("Position stabilization timed out ({Count}/{Min} after {Ms}ms)",
|
||||
consecutive, minConsecutive, sw.ElapsedMilliseconds);
|
||||
}
|
||||
|
||||
private async Task<bool> WaitForBossSpawn(int timeoutMs = 30_000)
|
||||
|
|
@ -554,6 +574,14 @@ public class BossRunExecutor : GameExecutor
|
|||
}
|
||||
}
|
||||
|
||||
// Final death check before giving up — force check ignoring throttle
|
||||
_lastDeathCheckMs = 0;
|
||||
if (await CheckDeath())
|
||||
{
|
||||
Log.Warning("Died while waiting for boss spawn");
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.Warning("WaitForBossSpawn timed out after {Ms}ms", timeoutMs);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -567,7 +595,8 @@ public class BossRunExecutor : GameExecutor
|
|||
/// Wait for boss to spawn, then attack until healthbar disappears.
|
||||
/// Returns the last world position where YOLO spotted the boss, or null.
|
||||
/// </summary>
|
||||
private async Task<(double X, double Y)?> AttackBossUntilGone(int timeoutMs = 120_000)
|
||||
private async Task<(double X, double Y)?> AttackBossUntilGone(
|
||||
double fightAreaX = double.NaN, double fightAreaY = double.NaN, int timeoutMs = 120_000)
|
||||
{
|
||||
// Wait for boss to actually appear before attacking
|
||||
if (!await WaitForBossSpawn())
|
||||
|
|
@ -575,6 +604,7 @@ public class BossRunExecutor : GameExecutor
|
|||
|
||||
const int screenCx = 1280;
|
||||
const int screenCy = 720;
|
||||
const double screenToWorld = 97.0 / 835.0;
|
||||
(double X, double Y)? lastBossWorldPos = null;
|
||||
|
||||
Log.Information("Boss is alive, engaging");
|
||||
|
|
@ -583,6 +613,9 @@ public class BossRunExecutor : GameExecutor
|
|||
try
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
var healthbarMissCount = 0;
|
||||
const int healthbarMissThreshold = 3; // require 3 consecutive misses
|
||||
|
||||
while (sw.ElapsedMilliseconds < timeoutMs)
|
||||
{
|
||||
if (_stopped) return lastBossWorldPos;
|
||||
|
|
@ -595,19 +628,55 @@ public class BossRunExecutor : GameExecutor
|
|||
_combatTargetX = boss.Cx;
|
||||
_combatTargetY = boss.Cy;
|
||||
|
||||
const double screenToWorld = 97.0 / 835.0;
|
||||
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<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)
|
||||
if (await CheckDeath()) continue;
|
||||
if (await CheckDeath()) { healthbarMissCount = 0; continue; }
|
||||
|
||||
if (!await IsBossAlive())
|
||||
if (await IsBossAlive())
|
||||
{
|
||||
Log.Information("Healthbar not found, boss phase over after {Ms}ms", sw.ElapsedMilliseconds);
|
||||
healthbarMissCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
healthbarMissCount++;
|
||||
if (healthbarMissCount < healthbarMissThreshold)
|
||||
{
|
||||
Log.Debug("Healthbar miss {N}/{Threshold}", healthbarMissCount, healthbarMissThreshold);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Confirm: did we die or did boss phase actually end?
|
||||
_lastDeathCheckMs = 0;
|
||||
if (await CheckDeath()) { healthbarMissCount = 0; continue; }
|
||||
|
||||
Log.Information("Healthbar gone for {N} checks, boss phase over after {Ms}ms",
|
||||
healthbarMissCount, sw.ElapsedMilliseconds);
|
||||
return lastBossWorldPos;
|
||||
}
|
||||
}
|
||||
|
|
@ -640,6 +709,33 @@ public class BossRunExecutor : GameExecutor
|
|||
}
|
||||
}
|
||||
|
||||
private async Task AttackUntilBossDead(int x, int y, int timeoutMs)
|
||||
{
|
||||
var (combatTask, cts) = StartCombatLoop(x, y, jitter: 20);
|
||||
try
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
// Attack for at least 2s before checking healthbar
|
||||
await Helpers.Sleep(2000);
|
||||
while (sw.ElapsedMilliseconds < timeoutMs)
|
||||
{
|
||||
if (_stopped) return;
|
||||
if (await CheckDeath()) continue;
|
||||
if (!await IsBossAlive())
|
||||
{
|
||||
Log.Information("Boss dead, stopping attack after {Ms}ms", sw.ElapsedMilliseconds);
|
||||
return;
|
||||
}
|
||||
await Helpers.Sleep(500);
|
||||
}
|
||||
Log.Warning("AttackUntilBossDead timed out after {Ms}ms", timeoutMs);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await StopCombatLoop(combatTask, cts);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find all template matches and click the one closest to screen center.
|
||||
/// </summary>
|
||||
|
|
@ -648,24 +744,38 @@ public class BossRunExecutor : GameExecutor
|
|||
const int screenCx = 2560 / 2;
|
||||
const int screenCy = 1440 / 2;
|
||||
|
||||
var matches = await _screen.TemplateMatchAll(templatePath);
|
||||
if (matches.Count == 0)
|
||||
// Search center region only to avoid clicking distant matches
|
||||
var centerRegion = new Region(850, 50, 860, 550);
|
||||
|
||||
for (var attempt = 0; attempt < 3; attempt++)
|
||||
{
|
||||
Log.Warning("No matches found for {Template}, clicking screen center", Path.GetFileName(templatePath));
|
||||
await _game.LeftClickAt(screenCx, screenCy);
|
||||
return;
|
||||
var matches = await _screen.TemplateMatchAll(templatePath, centerRegion);
|
||||
if (matches.Count > 0)
|
||||
{
|
||||
var closest = matches.OrderBy(m =>
|
||||
{
|
||||
var dx = m.X - screenCx;
|
||||
var dy = m.Y - screenCy;
|
||||
return dx * dx + dy * dy;
|
||||
}).First();
|
||||
|
||||
Log.Information("Clicking closest match at ({X},{Y}) conf={Conf:F3} (of {Count} matches, attempt {A})",
|
||||
closest.X, closest.Y, closest.Confidence, matches.Count, attempt + 1);
|
||||
await _game.LeftClickAt(closest.X, closest.Y);
|
||||
return;
|
||||
}
|
||||
|
||||
// Nudge character a bit and retry
|
||||
Log.Warning("No center match for {Template} (attempt {A}/3), nudging", Path.GetFileName(templatePath), attempt + 1);
|
||||
var nudgeKey = attempt % 2 == 0 ? InputSender.VK.W : InputSender.VK.S;
|
||||
await _game.KeyDown(nudgeKey);
|
||||
await Helpers.Sleep(300);
|
||||
await _game.KeyUp(nudgeKey);
|
||||
await Helpers.Sleep(500);
|
||||
}
|
||||
|
||||
var closest = matches.OrderBy(m =>
|
||||
{
|
||||
var dx = m.X - screenCx;
|
||||
var dy = m.Y - screenCy;
|
||||
return dx * dx + dy * dy;
|
||||
}).First();
|
||||
|
||||
Log.Information("Clicking closest match at ({X},{Y}) conf={Conf:F3} (of {Count} matches)",
|
||||
closest.X, closest.Y, closest.Confidence, matches.Count);
|
||||
await _game.LeftClickAt(closest.X, closest.Y);
|
||||
Log.Warning("No matches found for {Template} after nudges, clicking screen center", Path.GetFileName(templatePath));
|
||||
await _game.LeftClickAt(screenCx, screenCy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -772,17 +882,10 @@ public class BossRunExecutor : GameExecutor
|
|||
await _game.KeyUp(InputSender.VK.S);
|
||||
await Helpers.Sleep(200);
|
||||
|
||||
// Press + to open portal
|
||||
// Press + to open portal, then click it at known position
|
||||
await _game.PressPlus();
|
||||
await Helpers.Sleep(800);
|
||||
|
||||
// Find "The Ardura Caravan" and click it
|
||||
var caravanPos = await _inventory.FindAndClickNameplate("The Ardura Caravan", maxRetries: 5, retryDelayMs: 1500);
|
||||
if (caravanPos == null)
|
||||
{
|
||||
Log.Error("Could not find 'The Ardura Caravan' portal");
|
||||
return false;
|
||||
}
|
||||
await Helpers.Sleep(2500);
|
||||
await _game.LeftClickAt(1280, 450);
|
||||
|
||||
// Wait for area transition to caravan
|
||||
var arrivedCaravan = await _inventory.WaitForAreaTransition(_config.TravelTimeoutMs);
|
||||
|
|
@ -816,6 +919,9 @@ public class BossRunExecutor : GameExecutor
|
|||
await _game.FocusGame();
|
||||
await Helpers.Sleep(Delays.PostFocus);
|
||||
|
||||
// Identify items at Doryani before stashing
|
||||
await _inventory.IdentifyItems();
|
||||
|
||||
// Open stash
|
||||
var stashPos = await _inventory.FindAndClickNameplate("Stash");
|
||||
if (stashPos == null)
|
||||
|
|
@ -830,18 +936,26 @@ public class BossRunExecutor : GameExecutor
|
|||
if (lootTab != null)
|
||||
await _inventory.ClickStashTab(lootTab, lootFolder);
|
||||
|
||||
var scanResult = await _screen.Grid.Scan("inventory");
|
||||
if (scanResult.Occupied.Count > 0)
|
||||
for (var pass = 0; pass < 3; pass++)
|
||||
{
|
||||
Log.Information("Depositing {Count} items to loot tab", scanResult.Occupied.Count);
|
||||
var scanResult = await _screen.Grid.Scan("inventory");
|
||||
if (scanResult.Items.Count == 0)
|
||||
{
|
||||
if (pass > 0) Log.Information("Inventory clear after {Pass} passes", pass);
|
||||
break;
|
||||
}
|
||||
|
||||
Log.Information("Depositing {Count} items to loot tab (pass {Pass})", scanResult.Items.Count, pass + 1);
|
||||
await _game.KeyDown(InputSender.VK.SHIFT);
|
||||
await _game.HoldCtrl();
|
||||
foreach (var cell in scanResult.Occupied)
|
||||
|
||||
foreach (var item in scanResult.Items)
|
||||
{
|
||||
var center = _screen.Grid.GetCellCenter(GridLayouts.Inventory, cell.Row, cell.Col);
|
||||
var center = _screen.Grid.GetCellCenter(GridLayouts.Inventory, item.Row, item.Col);
|
||||
await _game.LeftClickAt(center.X, center.Y);
|
||||
await Helpers.Sleep(Delays.ClickInterval);
|
||||
}
|
||||
|
||||
await _game.ReleaseCtrl();
|
||||
await _game.KeyUp(InputSender.VK.SHIFT);
|
||||
await Helpers.Sleep(Delays.PostEscape);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue