using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.Core.Simulator; using BetterGenshinImpact.GameTask.AutoFight.Assets; using BetterGenshinImpact.GameTask.AutoFight.Model; using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception; using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Model; using BetterGenshinImpact.GameTask.AutoPathing.Handler; using BetterGenshinImpact.GameTask.AutoPathing.Model; using BetterGenshinImpact.GameTask.AutoPathing.Model.Enum; using BetterGenshinImpact.GameTask.AutoSkip; using BetterGenshinImpact.GameTask.AutoSkip.Assets; using BetterGenshinImpact.GameTask.AutoTrackPath; using BetterGenshinImpact.GameTask.Common.BgiVision; using BetterGenshinImpact.GameTask.Common.Job; using BetterGenshinImpact.GameTask.Common.Map; using BetterGenshinImpact.GameTask.Model.Area; using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging.Messages; using Microsoft.Extensions.Logging; using OpenCvSharp; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using BetterGenshinImpact.GameTask.AutoPathing.Suspend; using BetterGenshinImpact.GameTask.Common; using Vanara.PInvoke; using static BetterGenshinImpact.GameTask.Common.TaskControl; using static BetterGenshinImpact.GameTask.SystemControl; using ActionEnum = BetterGenshinImpact.GameTask.AutoPathing.Model.Enum.ActionEnum; using BetterGenshinImpact.Core.Simulator.Extensions; using BetterGenshinImpact.GameTask; using BetterGenshinImpact.GameTask.AutoPathing; using BetterGenshinImpact.GameTask.Common.Element.Assets; using BetterGenshinImpact.GameTask.Common.Exceptions; namespace BetterGenshinImpact.GameTask.AutoPathing; public class PathExecutor { private readonly CameraRotateTask _rotateTask; private readonly TrapEscaper _trapEscaper; private readonly BlessingOfTheWelkinMoonTask _blessingOfTheWelkinMoonTask = new(); private AutoSkipTrigger? _autoSkipTrigger; private PathingPartyConfig? _partyConfig; private CancellationToken ct; private PathExecutorSuspend pathExecutorSuspend; public PathExecutor(CancellationToken ct) { _trapEscaper = new(ct); _rotateTask = new(ct); this.ct = ct; pathExecutorSuspend = new PathExecutorSuspend(this); } public PathingPartyConfig PartyConfig { get => _partyConfig ?? PathingPartyConfig.BuildDefault(); set => _partyConfig = value; } /// /// 判断是否中止地图追踪的条件 /// public Func? EndAction { get; set; } private CombatScenes? _combatScenes; // private readonly Dictionary _actionAvatarIndexMap = new(); private DateTime _elementalSkillLastUseTime = DateTime.MinValue; private DateTime _useGadgetLastUseTime = DateTime.MinValue; private const int RetryTimes = 2; private int _inTrap = 0; //记录当前相关点位数组 public (int, List) CurWaypoints { get; set; } //记录当前点位 public (int, WaypointForTrack) CurWaypoint { get; set; } //记录恢复点位数组 private (int, List) RecordWaypoints { get; set; } //记录恢复点位 private (int, WaypointForTrack) RecordWaypoint { get; set; } //跳过除走路径以外的操作 private bool _skipOtherOperations = false; //当到达恢复点位 public void TryCloseSkipOtherOperations() { // Logger.LogWarning("判断是否跳过地图追踪:" + (CurWaypoint.Item1 < RecordWaypoint.Item1)); if (RecordWaypoints == CurWaypoints && CurWaypoint.Item1 < RecordWaypoint.Item1) { return; } if (_skipOtherOperations) { Logger.LogWarning("已到达上次点位,地图追踪功能恢复"); } _skipOtherOperations = false; } //记录点位,方便后面恢复 public void StartSkipOtherOperations() { Logger.LogWarning("记录恢复点位,地图追踪将到达上次点位之前将跳过走路之外的操作"); _skipOtherOperations = true; RecordWaypoints = CurWaypoints; RecordWaypoint = CurWaypoint; } public async Task Pathing(PathingTask task) { // SuspendableDictionary; const string sdKey = "PathExecutor"; var sd = RunnerContext.Instance.SuspendableDictionary; sd.Remove(sdKey); RunnerContext.Instance.SuspendableDictionary.TryAdd(sdKey, pathExecutorSuspend); if (!task.Positions.Any()) { Logger.LogWarning("没有路径点,寻路结束"); return; } // 切换队伍 if (!await SwitchPartyBefore(task)) { return; } // 校验路径是否可以执行 if (!await ValidateGameWithTask(task)) { return; } InitializePathing(task); // 转换、按传送点分割路径 var waypointsList = ConvertWaypointsForTrack(task.Positions); await Delay(100, ct); Navigation.WarmUp(); // 提前加载地图特征点 foreach (var waypoints in waypointsList) // 按传送点分割的路径 { CurWaypoints = (waypointsList.FindIndex(wps => wps == waypoints), waypoints); for (var i = 0; i < RetryTimes; i++) { try { await ResolveAnomalies(); // 异常场景处理 foreach (var waypoint in waypoints) // 一条路径 { CurWaypoint = (waypoints.FindIndex(wps => wps == waypoint), waypoint); TryCloseSkipOtherOperations(); await RecoverWhenLowHp(waypoint); // 低血量恢复 if (waypoint.Type == WaypointType.Teleport.Code) { await HandleTeleportWaypoint(waypoint); } else { await BeforeMoveToTarget(waypoint); // Path不用走得很近,Target需要接近,但都需要先移动到对应位置 if (waypoint.Type == WaypointType.Orientation.Code) { // 方位点,只需要朝向 // 考虑到方位点大概率是作为执行action的最后一个点,所以放在此处处理,不和传送点一样单独处理 await FaceTo(waypoint); } else { await MoveTo(waypoint); } await BeforeMoveCloseToTarget(waypoint); if (IsTargetPoint(waypoint)) { await MoveCloseTo(waypoint); } //skipOtherOperations如果重试,则跳过相关操作, if ((!string.IsNullOrEmpty(waypoint.Action) && !_skipOtherOperations) || waypoint.Action == ActionEnum.CombatScript.Code) { // 执行 action await AfterMoveToTarget(waypoint); } } } break; } catch (NormalEndException normalEndException) { Logger.LogInformation(normalEndException.Message); if (RunnerContext.Instance.IsContinuousRunGroup) { throw; } else { break; } } catch (TaskCanceledException e) { if (RunnerContext.Instance.IsContinuousRunGroup) { throw; } else { break; } } catch (RetryException retryException) { StartSkipOtherOperations(); Logger.LogWarning(retryException.Message); } catch (RetryNoCountException retryException) { //特殊情况下,重试不消耗次数 i--; StartSkipOtherOperations(); Logger.LogWarning(retryException.Message); } finally { // 不管咋样,松开所有按键 Simulation.SendInput.Keyboard.KeyUp(User32.VK.VK_W); Simulation.SendInput.Mouse.RightButtonUp(); } } } } private bool IsTargetPoint(WaypointForTrack waypoint) { // 方位点不需要接近 if (waypoint.Type == WaypointType.Orientation.Code) { return false; } var action = ActionEnum.GetEnumByCode(waypoint.Action); if (action is not null && action.UseWaypointTypeEnum != ActionUseWaypointTypeEnum.Custom) { // 强制点位类型的 action,以 action 为准 return action.UseWaypointTypeEnum == ActionUseWaypointTypeEnum.Target; } // 其余情况和没有action的情况以点位类型为准 return waypoint.Type == WaypointType.Target.Code; } private async Task SwitchPartyBefore(PathingTask task) { var ra = CaptureToRectArea(); // 切换队伍前判断是否全队死亡 // 可能队伍切换失败导致的死亡 if (Bv.ClickIfInReviveModal(ra)) { await Bv.WaitForMainUi(ct); // 等待主界面加载完成 Logger.LogInformation("复苏完成"); await Delay(4000, ct); // 血量肯定不满,直接去七天神像回血 await TpStatueOfTheSeven(); } var pRaList = ra.FindMulti(AutoFightAssets.Instance.PRa); // 判断是否联机 if (pRaList.Count > 0) { Logger.LogInformation("处于联机状态下,不切换队伍"); } else { if (PartyConfig is { Enabled: false }) { // 调度器未配置的情况下,根据地图追踪条件配置切换队伍 var partyName = FilterPartyNameByConditionConfig(task); if (!await SwitchParty(partyName)) { Logger.LogError("切换队伍失败,无法执行此路径!请检查地图追踪设置!"); return false; } } else if (!string.IsNullOrEmpty(PartyConfig.PartyName)) { if (!await SwitchParty(PartyConfig.PartyName)) { Logger.LogError("切换队伍失败,无法执行此路径!请检查配置组中的地图追踪配置!"); return false; } } } return true; } private void InitializePathing(PathingTask task) { LogScreenResolution(); WeakReferenceMessenger.Default.Send(new PropertyChangedMessage(this, "UpdateCurrentPathing", new object(), task)); } private void LogScreenResolution() { var gameScreenSize = SystemControl.GetGameScreenRect(TaskContext.Instance().GameHandle); if (gameScreenSize.Width * 9 != gameScreenSize.Height * 16) { Logger.LogError("游戏窗口分辨率不是 16:9 !当前分辨率为 {Width}x{Height} , 非 16:9 分辨率的游戏无法正常使用地图追踪功能!", gameScreenSize.Width, gameScreenSize.Height); throw new Exception("游戏窗口分辨率不是 16:9 !无法使用地图追踪功能!"); } if (gameScreenSize.Width < 1920 || gameScreenSize.Height < 1080) { Logger.LogError("游戏窗口分辨率小于 1920x1080 !当前分辨率为 {Width}x{Height} , 小于 1920x1080 的分辨率的游戏地图追踪的效果非常差!", gameScreenSize.Width, gameScreenSize.Height); throw new Exception("游戏窗口分辨率小于 1920x1080 !无法使用地图追踪功能!"); } } /// /// 切换队伍 /// /// /// private async Task SwitchParty(string? partyName) { bool success = true; if (!string.IsNullOrEmpty(partyName)) { if (RunnerContext.Instance.PartyName == partyName) { return success; } bool forceTp = PartyConfig.IsVisitStatueBeforeSwitchParty; if (forceTp) // 强制传送模式 { await new TpTask(ct).TpToStatueOfTheSeven(); // fix typos success = await new SwitchPartyTask().Start(partyName, ct); } else // 优先原地切换模式 { try { success = await new SwitchPartyTask().Start(partyName, ct); } catch (PartySetupFailedException) { await new TpTask(ct).TpToStatueOfTheSeven(); success = await new SwitchPartyTask().Start(partyName, ct); } } if (success) { RunnerContext.Instance.PartyName = partyName; RunnerContext.Instance.ClearCombatScenes(); } } return success; } private static string? FilterPartyNameByConditionConfig(PathingTask task) { var pathingConditionConfig = TaskContext.Instance().Config.PathingConditionConfig; var materialName = task.GetMaterialName(); var specialActions = task.Positions .Select(p => p.Action) .Where(action => !string.IsNullOrEmpty(action)) .Distinct() .ToList(); var partyName = pathingConditionConfig.FilterPartyName(materialName, specialActions); return partyName; } /// /// 校验 /// /// /// private async Task ValidateGameWithTask(PathingTask task) { _combatScenes = await RunnerContext.Instance.GetCombatScenes(ct); if (_combatScenes == null) { return false; } // 没有强制配置的情况下,使用地图追踪内的条件配置 // 必须放在这里,因为要通过队伍识别来得到最终结果 var pathingConditionConfig = TaskContext.Instance().Config.PathingConditionConfig; if (PartyConfig is { Enabled: false }) { PartyConfig = pathingConditionConfig.BuildPartyConfigByCondition(_combatScenes); } // 校验角色是否存在 if (task.HasAction(ActionEnum.NahidaCollect.Code)) { var avatar = _combatScenes.SelectAvatar("纳西妲"); if (avatar == null) { Logger.LogError("此路径存在纳西妲收集动作,队伍中没有纳西妲角色,无法执行此路径!"); return false; } // _actionAvatarIndexMap.Add("nahida_collect", avatar.Index.ToString()); } // 把所有需要切换的角色编号记录下来 Dictionary map = new() { { ActionEnum.HydroCollect.Code, ElementalType.Hydro }, { ActionEnum.ElectroCollect.Code, ElementalType.Electro }, { ActionEnum.AnemoCollect.Code, ElementalType.Anemo } }; foreach (var (action, el) in map) { if (!ValidateElementalActionAvatarIndex(task, action, el, _combatScenes)) { return false; } } return true; } private bool ValidateElementalActionAvatarIndex(PathingTask task, string action, ElementalType el, CombatScenes combatScenes) { if (task.HasAction(action)) { foreach (var avatar in combatScenes.GetAvatars()) { if (ElementalCollectAvatarConfigs.Get(avatar.Name, el) != null) { return true; } } Logger.LogError("此路径存在 {El}元素采集 动作,队伍中没有对应元素角色:{Names},无法执行此路径!", el.ToChinese(), string.Join(",", ElementalCollectAvatarConfigs.GetAvatarNameList(el))); return false; } else { return true; } } private List> ConvertWaypointsForTrack(List positions) { // 把 X Y 转换为 MatX MatY var allList = positions.Select(waypoint => new WaypointForTrack(waypoint)).ToList(); // 按照WaypointType.Teleport.Code切割数组 var result = new List>(); var tempList = new List(); foreach (var waypoint in allList) { if (waypoint.Type == WaypointType.Teleport.Code) { if (tempList.Count > 0) { result.Add(tempList); tempList = new List(); } } tempList.Add(waypoint); } result.Add(tempList); return result; } /// /// 尝试队伍回血,如果单人回血,由于记录检查时是哪位残血,则当作行走位处理。 /// private async Task TryPartyHealing() { if (_combatScenes is null) return false; foreach (var avatar in _combatScenes.GetAvatars()) { if (avatar.Name == "白术") { if (avatar.TrySwitch()) { //1命白术能两次 Simulation.SendInput.SimulateAction(GIActions.ElementalSkill); await Delay(800, ct); Simulation.SendInput.SimulateAction(GIActions.ElementalSkill); await Delay(800, ct); await SwitchAvatar(PartyConfig.MainAvatarIndex); await Delay(4000, ct); return true; } break; } else if (avatar.Name == "希格雯") { if (avatar.TrySwitch()) { Simulation.SendInput.SimulateAction(GIActions.ElementalSkill); await Delay(11000, ct); await SwitchAvatar(PartyConfig.MainAvatarIndex); return true; } break; } else if (avatar.Name == "珊瑚宫心海") { if (avatar.TrySwitch()) { Simulation.SendInput.SimulateAction(GIActions.ElementalSkill); await Delay(500, ct); //尝试Q全队回血 Simulation.SendInput.SimulateAction(GIActions.ElementalBurst); //单人血只给行走位加血 await SwitchAvatar(PartyConfig.MainAvatarIndex); await Delay(5000, ct); return true; } } } return false; } private async Task RecoverWhenLowHp(WaypointForTrack waypoint) { if (PartyConfig.OnlyInTeleportRecover && waypoint.Type != WaypointType.Teleport.Code) { return; } using var region = CaptureToRectArea(); if (Bv.CurrentAvatarIsLowHp(region) && !(await TryPartyHealing() && Bv.CurrentAvatarIsLowHp(region))) { Logger.LogInformation("当前角色血量过低,去七天神像恢复"); await TpStatueOfTheSeven(); throw new RetryException("回血完成后重试路线"); } else if (Bv.ClickIfInReviveModal(region)) { await Bv.WaitForMainUi(ct); // 等待主界面加载完成 Logger.LogInformation("复苏完成"); await Delay(4000, ct); // 血量肯定不满,直接去七天神像回血 await TpStatueOfTheSeven(); throw new RetryException("回血完成后重试路线"); } } private async Task TpStatueOfTheSeven() { // tp 到七天神像回血 var tpTask = new TpTask(ct); await tpTask.TpToStatueOfTheSeven(); Logger.LogInformation("血量恢复完成。【设置】-【七天神像设置】可以修改回血相关配置。"); } private async Task HandleTeleportWaypoint(WaypointForTrack waypoint) { var forceTp = waypoint.Action == ActionEnum.ForceTp.Code; var (tpX, tpY) = await new TpTask(ct).Tp(waypoint.GameX, waypoint.GameY, forceTp); var (tprX, tprY) = MapCoordinate.GameToMain2048(tpX, tpY); EntireMap.Instance.SetPrevPosition((float)tprX, (float)tprY); // 通过上一个位置直接进行局部特征匹配 await Delay(500, ct); // 多等一会 } private async Task FaceTo(WaypointForTrack waypoint) { var screen = CaptureToRectArea(); var position = await GetPosition(screen); var targetOrientation = Navigation.GetTargetOrientation(waypoint, position); Logger.LogInformation("朝向点,位置({x2},{y2})", $"{waypoint.GameX:F1}", $"{waypoint.GameY:F1}"); await _rotateTask.WaitUntilRotatedTo(targetOrientation, 2); await Delay(500, ct); } public DateTime moveToStartTime; public async Task MoveTo(WaypointForTrack waypoint) { // 切人 await SwitchAvatar(PartyConfig.MainAvatarIndex); var screen = CaptureToRectArea(); var position = await GetPosition(screen); var targetOrientation = Navigation.GetTargetOrientation(waypoint, position); Logger.LogInformation("粗略接近途经点,位置({x2},{y2})", $"{waypoint.GameX:F1}", $"{waypoint.GameY:F1}"); await _rotateTask.WaitUntilRotatedTo(targetOrientation, 5); moveToStartTime = DateTime.UtcNow; var lastPositionRecord = DateTime.UtcNow; var fastMode = false; var prevPositions = new List(); var fastModeColdTime = DateTime.MinValue; var num = 0; // 按下w,一直走 Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyDown); while (!ct.IsCancellationRequested) { if (!Simulation.IsKeyDown(GIActions.MoveForward.ToActionKey().ToVK())) { Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyDown); } num++; if ((DateTime.UtcNow - moveToStartTime).TotalSeconds > 240) { Logger.LogWarning("执行超时,放弃此次追踪"); throw new RetryException("路径点执行超时,放弃整条路径"); } screen = CaptureToRectArea(); EndJudgment(screen); position = await GetPosition(screen); var distance = Navigation.GetDistance(waypoint, position); Debug.WriteLine($"接近目标点中,距离为{distance}"); if (distance < 4) { Logger.LogInformation("到达路径点附近"); break; } if (distance > 500) { if (pathExecutorSuspend.CheckAndResetSuspendPoint()) { throw new RetryNoCountException("可能暂停导致路径过远,重试一次此路线!"); } else { Logger.LogWarning("距离过远,跳过路径点"); } break; } // 非攀爬状态下,检测是否卡死(脱困触发器) if (waypoint.MoveMode != MoveModeEnum.Climb.Code) { if ((DateTime.UtcNow - lastPositionRecord).TotalMilliseconds > 1000) { lastPositionRecord = DateTime.UtcNow; prevPositions.Add(position); if (prevPositions.Count > 8) { var delta = prevPositions[^1] - prevPositions[^8]; if (Math.Abs(delta.X) + Math.Abs(delta.Y) < 3) { _inTrap++; if (_inTrap > 2) { throw new RetryException("此路线出现3次卡死,重试一次路线或放弃此路线!"); } Logger.LogWarning("疑似卡死,尝试脱离..."); //调用脱困代码,由TrapEscaper接管移动 await _trapEscaper.RotateAndMove(); await _trapEscaper.MoveTo(waypoint); Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyDown); Logger.LogInformation("卡死脱离结束"); continue; } } } } // 旋转视角 targetOrientation = Navigation.GetTargetOrientation(waypoint, position); //执行旋转 _rotateTask.RotateToApproach(targetOrientation, screen); // 根据指定方式进行移动 if (waypoint.MoveMode == MoveModeEnum.Fly.Code) { var isFlying = Bv.GetMotionStatus(screen) == MotionStatus.Fly; if (!isFlying) { Debug.WriteLine("未进入飞行状态,按下空格"); Simulation.SendInput.SimulateAction(GIActions.Jump); await Delay(200, ct); } await Delay(200, ct); continue; } if (waypoint.MoveMode == MoveModeEnum.Jump.Code) { Simulation.SendInput.SimulateAction(GIActions.Jump); await Delay(200, ct); continue; } // 只有设置为run才会一直疾跑 if (waypoint.MoveMode == MoveModeEnum.Run.Code) { if (distance > 20 != fastMode) // 距离大于20时可以使用疾跑/自由泳 { if (fastMode) { Simulation.SendInput.SimulateAction(GIActions.SprintMouse, KeyType.KeyUp); } else { Simulation.SendInput.SimulateAction(GIActions.SprintMouse, KeyType.KeyDown); } fastMode = !fastMode; } } else if (waypoint.MoveMode == MoveModeEnum.Dash.Code) { if (distance > 20) // 距离大于25时可以使用疾跑 { if (Math.Abs((fastModeColdTime - DateTime.UtcNow).TotalMilliseconds) > 1000) //冷却一会 { fastModeColdTime = DateTime.UtcNow; Simulation.SendInput.SimulateAction(GIActions.SprintMouse); } } } else if (waypoint.MoveMode != MoveModeEnum.Climb.Code) //否则自动短疾跑 { // 使用 E 技能 if (distance > 10 && !string.IsNullOrEmpty(PartyConfig.GuardianAvatarIndex) && double.TryParse(PartyConfig.GuardianElementalSkillSecondInterval, out var s)) { if (s < 1) { Logger.LogWarning("元素战技冷却时间设置太短,不执行!"); return; } var ms = s * 1000; if ((DateTime.UtcNow - _elementalSkillLastUseTime).TotalMilliseconds > ms) { // 可能刚切过人在冷却时间内 if (num <= 5 && (!string.IsNullOrEmpty(PartyConfig.MainAvatarIndex) && PartyConfig.GuardianAvatarIndex != PartyConfig.MainAvatarIndex)) { await Delay(800, ct); // 总共1s } await UseElementalSkill(); _elementalSkillLastUseTime = DateTime.UtcNow; } } // 自动疾跑 if (distance > 20 && PartyConfig.AutoRunEnabled) { if (Math.Abs((fastModeColdTime - DateTime.UtcNow).TotalMilliseconds) > 2500) //冷却时间2.5s,回复体力用 { fastModeColdTime = DateTime.UtcNow; Simulation.SendInput.SimulateAction(GIActions.SprintMouse); } } } // 使用小道具 if (PartyConfig.UseGadgetIntervalMs > 0) { if ((DateTime.UtcNow - _useGadgetLastUseTime).TotalMilliseconds > PartyConfig.UseGadgetIntervalMs) { Simulation.SendInput.SimulateAction(GIActions.QuickUseGadget); _useGadgetLastUseTime = DateTime.UtcNow; } } await Delay(100, ct); } // 抬起w键 Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyUp); } private async Task UseElementalSkill() { if (string.IsNullOrEmpty(PartyConfig.GuardianAvatarIndex)) { return; } await Delay(200, ct); // 切人 Logger.LogInformation("切换盾、回血角色,使用元素战技"); var avatar = await SwitchAvatar(PartyConfig.GuardianAvatarIndex,true); if (avatar == null) { return; } // 钟离往身后放柱子 if (avatar.Name == "钟离") { Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyUp); await Delay(50, ct); Simulation.SendInput.SimulateAction(GIActions.MoveBackward); await Delay(200, ct); } avatar.UseSkill(PartyConfig.GuardianElementalSkillLongPress); // 钟离往身后放柱子 后继续走路 if (avatar.Name == "钟离") { Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyDown); } } private async Task MoveCloseTo(WaypointForTrack waypoint) { ImageRegion screen; Point2f position; int targetOrientation; Logger.LogInformation("精确接近目标点,位置({x2},{y2})", $"{waypoint.GameX:F1}", $"{waypoint.GameY:F1}"); var stepsTaken = 0; while (!ct.IsCancellationRequested) { stepsTaken++; if (stepsTaken > 25) { Logger.LogWarning("精确接近超时"); break; } screen = CaptureToRectArea(); EndJudgment(screen); position = await GetPosition(screen); if (Navigation.GetDistance(waypoint, position) < 2) { Logger.LogInformation("已到达路径点"); break; } targetOrientation = Navigation.GetTargetOrientation(waypoint, position); await _rotateTask.WaitUntilRotatedTo(targetOrientation, 2); // 小碎步接近 Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyDown); Thread.Sleep(60); Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyUp); // Simulation.SendInput.Keyboard.KeyDown(User32.VK.VK_W).Sleep(60).KeyUp(User32.VK.VK_W); await Delay(20, ct); } Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyUp); // 到达目的地后停顿一秒 await Delay(1000, ct); } private async Task BeforeMoveCloseToTarget(WaypointForTrack waypoint) { if (waypoint.MoveMode == MoveModeEnum.Fly.Code && waypoint.Action == ActionEnum.StopFlying.Code) { await ActionFactory.GetBeforeHandler(ActionEnum.StopFlying.Code).RunAsync(ct, waypoint); } } private async Task BeforeMoveToTarget(WaypointForTrack waypoint) { if (waypoint.Action == ActionEnum.UpDownGrabLeaf.Code) { var handler = ActionFactory.GetBeforeHandler(waypoint.Action); await handler.RunAsync(ct, waypoint); await Delay(800, ct); } else if (waypoint.Action == ActionEnum.LogOutput.Code) { Logger.LogInformation(waypoint.LogInfo); } } private async Task AfterMoveToTarget(WaypointForTrack waypoint) { if (waypoint.Action == ActionEnum.NahidaCollect.Code || waypoint.Action == ActionEnum.PickAround.Code || waypoint.Action == ActionEnum.Fight.Code || waypoint.Action == ActionEnum.HydroCollect.Code || waypoint.Action == ActionEnum.ElectroCollect.Code || waypoint.Action == ActionEnum.AnemoCollect.Code || waypoint.Action == ActionEnum.PyroCollect.Code || waypoint.Action == ActionEnum.CombatScript.Code || waypoint.Action == ActionEnum.Mining.Code || waypoint.Action == ActionEnum.Fishing.Code || waypoint.Action == ActionEnum.ExitAndRelogin.Code) { var handler = ActionFactory.GetAfterHandler(waypoint.Action); //,PartyConfig await handler.RunAsync(ct, waypoint, PartyConfig); await Delay(1000, ct); } } private async Task SwitchAvatar(string index,bool needSkill = false) { if (string.IsNullOrEmpty(index)) { return null; } var avatar = _combatScenes?.SelectAvatar(int.Parse(index)); if (avatar == null) return null; if (needSkill && !avatar.IsSkillReady()) { Logger.LogInformation("角色{Name}技能未冷却,跳过。", avatar.Name); return null; } var success = avatar.TrySwitch(); if (success) { await Delay(100, ct); return avatar; } Logger.LogInformation("尝试切换角色{Name}失败!", avatar.Name); return null; } private async Task GetPosition(ImageRegion imageRegion) { var position = Navigation.GetPosition(imageRegion); if (position == new Point2f()) { if (!Bv.IsInMainUi(imageRegion)) { Logger.LogDebug("小地图位置定位失败,且当前不是主界面,进入异常处理"); await ResolveAnomalies(imageRegion); } } return position; } /** * 处理各种异常场景 * 需要保证耗时不能太高 */ private async Task ResolveAnomalies(ImageRegion? imageRegion = null) { if (imageRegion == null) { imageRegion = CaptureToRectArea(); } // 一些异常界面处理 var cookRa = imageRegion.Find(AutoSkipAssets.Instance.CookRo); var closeRa = imageRegion.Find(AutoSkipAssets.Instance.PageCloseMainRo); var closeRa2 = imageRegion.Find(ElementAssets.Instance.PageCloseWhiteRo); if (cookRa.IsExist() || closeRa.IsExist() || closeRa2.IsExist()) { // 排除大地图 if (Bv.IsInBigMapUi(imageRegion)) { return; } Logger.LogInformation("检测到其他界面,使用ESC关闭界面"); Simulation.SendInput.Keyboard.KeyPress(User32.VK.VK_ESCAPE); await Delay(1000, ct); // 等待界面关闭 } // 处理月卡 await _blessingOfTheWelkinMoonTask.Start(ct); if (PartyConfig.AutoSkipEnabled) { // 判断是否进入剧情 await AutoSkip(); } } private async Task AutoSkip() { var ra = CaptureToRectArea(); var disabledUiButtonRa = ra.Find(AutoSkipAssets.Instance.DisabledUiButtonRo); if (disabledUiButtonRa.IsExist()) { Logger.LogWarning("进入剧情,自动点击剧情直到结束"); if (_autoSkipTrigger == null) { _autoSkipTrigger = new AutoSkipTrigger(new AutoSkipConfig { Enabled = true, QuicklySkipConversationsEnabled = true, // 快速点击过剧情 ClosePopupPagedEnabled = true, ClickChatOption = "优先选择最后一个选项", }); _autoSkipTrigger.Init(); } int noDisabledUiButtonTimes = 0; while (true) { ra = CaptureToRectArea(); disabledUiButtonRa = ra.Find(AutoSkipAssets.Instance.DisabledUiButtonRo); if (disabledUiButtonRa.IsExist()) { _autoSkipTrigger.OnCapture(new CaptureContent(ra)); } else { noDisabledUiButtonTimes++; if (noDisabledUiButtonTimes > 10) { Logger.LogInformation("自动剧情结束"); break; } } await Delay(210, ct); } } } private void EndJudgment(ImageRegion ra) { if (EndAction != null && EndAction(ra)) { throw new NormalEndException("达成结束条件,结束地图追踪"); } } }