Files
better-genshin-impact/BetterGenshinImpact/GameTask/AutoPathing/PathExecutor.cs
辉鸭蛋 a06f0fcdb2 auto pathing: fix not releasing the mouse and keyboard when stopping tasks
更新日志和黑名单

移除 `PathExecutor.cs` 中的无用引用,重构 `Pathing` 方法,添加 `InitializePathing` 和 `ConvertWaypoints` 方法,提取传送点处理逻辑到 `HandleTeleportWaypoint` 方法。修改 `TpTask.cs` 中的日志格式为浮点数格式。将 `TaskRunner.cs` 中的 `FireAndForgetAsync` 方法重命名为 `RunThreadAsync`,并在 `ScriptService.cs` 中相应替换调用。为 `DpiHelper.cs` 中的 `ScaleY` 属性添加注释。更新 `pick_black_lists.json`,添加新的黑名单项 `"摆放巧像"`。在 `MapPathingViewModel.cs` 中添加 `_mapViewer` 字段,并在 `OnOpenMapViewer` 方法中使用。
2024-09-09 21:57:48 +08:00

308 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.GameTask.AutoPathing.Model;
using BetterGenshinImpact.GameTask.AutoPathing.Model.Enum;
using BetterGenshinImpact.GameTask.AutoTrackPath;
using BetterGenshinImpact.GameTask.Common.BgiVision;
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 Vanara.PInvoke;
using static BetterGenshinImpact.GameTask.Common.TaskControl;
namespace BetterGenshinImpact.GameTask.AutoPathing;
public class PathExecutor(CancellationTokenSource cts)
{
private double _dpi = 1;
public async Task Pathing(PathingTask task)
{
_dpi = TaskContext.Instance().DpiScale;
if (!task.Positions.Any())
{
Logger.LogWarning("没有路径点,寻路结束");
return;
}
InitializePathing(task);
var waypoints = ConvertWaypoints(task.Positions);
await Delay(100, cts);
Navigation.WarmUp(); // 提前加载地图特征点
try
{
foreach (var waypoint in waypoints)
{
if (waypoint.Type == WaypointType.Teleport.Code)
{
await HandleTeleportWaypoint(waypoint);
}
else
{
// Path不用走得很近Target需要接近但都需要先移动到对应位置
await MoveTo(waypoint);
if (waypoint.Type == WaypointType.Target.Code || !string.IsNullOrEmpty(waypoint.Action))
{
await MoveCloseTo(waypoint);
}
}
}
}
finally
{
// 不管咋样,松开所有按键
Simulation.SendInput.Keyboard.KeyUp(User32.VK.VK_W);
Simulation.SendInput.Mouse.RightButtonUp();
}
}
private void InitializePathing(PathingTask task)
{
task.Positions.First().Type = WaypointType.Teleport.Code;
WeakReferenceMessenger.Default.Send(new PropertyChangedMessage<object>(this,
"UpdateCurrentPathing", new object(), task));
}
private List<Waypoint> ConvertWaypoints(List<Waypoint> positions)
{
var waypoints = new List<Waypoint>();
foreach (var waypoint in positions)
{
if (waypoint.Type == WaypointType.Teleport.Code)
{
waypoints.Add(waypoint);
continue;
}
var waypointCopy = new Waypoint
{
Action = waypoint.Action,
Type = waypoint.Type,
MoveMode = waypoint.MoveMode
};
(waypointCopy.X, waypointCopy.Y) = MapCoordinate.GameToMain2048(waypoint.X, waypoint.Y);
waypoints.Add(waypointCopy);
}
return waypoints;
}
private async Task HandleTeleportWaypoint(Waypoint waypoint)
{
var (tpX, tpY) = await new TpTask(cts).Tp(waypoint.X, waypoint.Y);
var (tprX, tprY) = MapCoordinate.GameToMain2048(tpX, tpY);
EntireMap.Instance.SetPrevPosition((float)tprX, (float)tprY); // 通过上一个位置直接进行局部特征匹配
}
private async Task MoveTo(Waypoint waypoint)
{
var screen = CaptureToRectArea();
var position = Navigation.GetPosition(screen);
var targetOrientation = Navigation.GetTargetOrientation(waypoint, position);
Logger.LogInformation("粗略接近途经点,位置({x2},{y2})", $"{waypoint.X:F1}", $"{waypoint.Y:F1}");
await WaitUntilRotatedTo(targetOrientation, 5);
var startTime = DateTime.UtcNow;
var lastPositionRecord = DateTime.UtcNow;
var fastMode = false;
var prevPositions = new List<Point2f>();
// 按下w一直走
Simulation.SendInput.Keyboard.KeyDown(User32.VK.VK_W);
while (!cts.IsCancellationRequested)
{
var now = DateTime.UtcNow;
if ((now - startTime).TotalSeconds > 60)
{
Logger.LogWarning("执行超时,跳过路径点");
break;
}
screen = CaptureToRectArea();
position = Navigation.GetPosition(screen);
var distance = Navigation.GetDistance(waypoint, position);
Debug.WriteLine($"接近目标点中,距离为{distance}");
if (distance < 4)
{
Logger.LogInformation("到达路径点附近");
break;
}
if (distance > 500)
{
Logger.LogWarning("距离过远,跳过路径点");
break;
}
if ((now - lastPositionRecord).TotalMilliseconds > 1000)
{
lastPositionRecord = now;
prevPositions.Add(position);
if (prevPositions.Count > 8)
{
var delta = prevPositions[^1] - prevPositions[^8];
if (Math.Abs(delta.X) + Math.Abs(delta.Y) < 3)
{
Logger.LogWarning("疑似卡死,尝试脱离并跳过路径点");
Simulation.SendInput.Keyboard.KeyUp(User32.VK.VK_W);
await Delay(500, cts);
Simulation.SendInput.Keyboard.KeyPress(User32.VK.VK_X);
await Delay(500, cts);
Simulation.SendInput.Keyboard.KeyPress(User32.VK.VK_S);
await Delay(500, cts);
Simulation.SendInput.Keyboard.KeyPress(User32.VK.VK_A);
await Delay(500, cts);
Simulation.SendInput.Keyboard.KeyPress(User32.VK.VK_D);
await Delay(500, cts);
return;
}
}
}
// 旋转视角
targetOrientation = Navigation.GetTargetOrientation(waypoint, position);
RotateTo(targetOrientation, screen);
// 根据指定方式进行移动
if (waypoint.MoveMode == MoveModeEnum.Fly.Code)
{
var isFlying = Bv.GetMotionStatus(screen) == MotionStatus.Fly;
if (!isFlying)
{
Debug.WriteLine("未进入飞行状态,按下空格");
Simulation.SendInput.Keyboard.KeyPress(User32.VK.VK_SPACE);
await Delay(200, cts);
}
continue;
}
// if (isFlying)
// {
// Simulation.SendInput.Mouse.LeftButtonClick();
// await Delay(1000, cts);
// continue;
// }
if (waypoint.MoveMode == MoveModeEnum.Jump.Code)
{
Simulation.SendInput.Keyboard.KeyPress(User32.VK.VK_SPACE);
await Delay(200, cts);
continue;
}
// 跑步或者游泳
if (distance > 20 != fastMode)// 距离大于20时可以使用疾跑/自由泳
{
if (fastMode)
{
Simulation.SendInput.Mouse.RightButtonUp();
}
else
{
Simulation.SendInput.Mouse.RightButtonDown();
}
fastMode = !fastMode;
}
await Delay(100, cts);
}
// 抬起w键
Simulation.SendInput.Keyboard.KeyUp(User32.VK.VK_W);
}
private async Task MoveCloseTo(Waypoint waypoint)
{
var screen = CaptureToRectArea();
var position = Navigation.GetPosition(screen);
var targetOrientation = Navigation.GetTargetOrientation(waypoint, position);
Logger.LogInformation("精确接近目标点,位置({x2},{y2})", $"{waypoint.X:F1}", $"{waypoint.Y:F1}");
if (waypoint.MoveMode == MoveModeEnum.Fly.Code && waypoint.Action == ActionEnum.StopFlying.Code)
{
//下落攻击接近目的地
Logger.LogInformation("动作:下落攻击");
Simulation.SendInput.Mouse.LeftButtonClick();
await Delay(1000, cts);
}
await WaitUntilRotatedTo(targetOrientation, 2);
var wPressed = false;
var stepsTaken = 0;
while (!cts.IsCancellationRequested)
{
stepsTaken++;
if (stepsTaken > 8)
{
Logger.LogWarning("精确接近超时");
break;
}
screen = CaptureToRectArea();
position = Navigation.GetPosition(screen);
if (Navigation.GetDistance(waypoint, position) < 2)
{
Logger.LogInformation("已到达路径点");
break;
}
RotateTo(targetOrientation, screen); //不再改变视角
if (waypoint.MoveMode == MoveModeEnum.Walk.Code)
{
// 小碎步接近
Simulation.SendInput.Keyboard.KeyDown(User32.VK.VK_W).Sleep(60).KeyUp(User32.VK.VK_W);
await Delay(200, cts);
continue;
}
if (!wPressed)
{
Simulation.SendInput.Keyboard.KeyDown(User32.VK.VK_W);
}
await Delay(100, cts);
}
if (wPressed)
{
Simulation.SendInput.Keyboard.KeyUp(User32.VK.VK_W);
}
}
private int RotateTo(int targetOrientation, ImageRegion imageRegion, double controlRatio = 1)
{
var cao = CameraOrientation.Compute(imageRegion.SrcGreyMat);
var diff = (cao - targetOrientation + 180) % 360 - 180;
diff += diff < -180 ? 360 : 0;
if (diff == 0)
{
return diff;
}
// 平滑的旋转视角
if (Math.Abs(diff) > 90)
{
controlRatio = 5;
}
else if (Math.Abs(diff) > 30)
{
controlRatio = 3;
}
else if (Math.Abs(diff) > 5)
{
controlRatio = 2;
}
Simulation.SendInput.Mouse.MoveMouseBy((int)Math.Round(-controlRatio * diff * _dpi), 0);
return diff;
}
private async Task WaitUntilRotatedTo(int targetOrientation, int maxDiff)
{
int count = 0;
while (!cts.IsCancellationRequested)
{
var screen = CaptureToRectArea();
if (Math.Abs(RotateTo(targetOrientation, screen)) < maxDiff)
{
break;
}
if (count > 50)
{
break;
}
await Delay(50, cts);
count++;
}
}
}