Files
better-genshin-impact/BetterGenshinImpact/GameTask/AutoPathing/PathExecutor.cs
辉鸭蛋 1fd5320df2 auto pathing: update
- 移除了不必要的 `using` 语句,添加了新的 `using` 语句。
- 将 `Waypoints` 属性重命名为 `Positions`。
- 更新了 `BuildFromFilePath` 方法中的 `JsonOptions` 引用。
- 将 `Waypoint` 类移到单独的文件中,并添加了相关属性和注释。
- 添加了 `SaveToFile` 方法,用于将 `PathingTask` 对象保存到文件。
- 更新了 `PathExecutor` 中的 `TaskControl` 引用,简化了代码。
- 更新了 `PathRecorder` 的属性和方法,使用私有字段 `_pathingTask`。
- 更新了 `MapPathingPage.xaml` 的命令绑定。
- 更新了 `HotKeyPageViewModel` 和 `MapPathingViewModel` 的逻辑。
- 添加了新的 `PathingTaskConfig` 类,用于配置路径任务。
2024-09-08 02:23:45 +08:00

284 lines
10 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.AutoTrackPath;
using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.GameTask.Common.BgiVision;
using BetterGenshinImpact.GameTask.Common.Map;
using BetterGenshinImpact.GameTask.Model.Area;
using Microsoft.Extensions.Logging;
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BetterGenshinImpact.GameTask.AutoPathing.Model.Enum;
using Vanara.PInvoke;
using Wpf.Ui.Violeta.Controls;
using static BetterGenshinImpact.GameTask.Common.TaskControl;
namespace BetterGenshinImpact.GameTask.AutoPathing;
public class PathExecutor(CancellationTokenSource cts)
{
public async Task Pathing(PathingTask task)
{
if (!TaskContext.Instance().IsInitialized)
{
Toast.Warning("请先在启动页,启动截图器再使用本功能");
return;
}
SystemControl.ActivateWindow();
if (task.Positions.Count == 0)
{
Logger.LogWarning("没有路径点,寻路结束");
return;
}
task.Positions.First().Type = WaypointType.Teleport.Code;
// 这里应该判断一下自动拾取是否处于工作状态,但好像没有什么方便的读取办法
// TODO:大地图传送的时候使用游戏坐标追踪的时候应该使用2048地图图像坐标这里临时做转换后续改进
var waypoints = new List<Waypoint>();
foreach (var waypoint in task.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);
}
foreach (var waypoint in waypoints)
{
if (waypoint.Type == WaypointType.Teleport.Code)
{
Logger.LogInformation("正在传送到{x},{y}", waypoint.X, waypoint.Y);
await new TpTask(cts).Tp(waypoint.X, waypoint.Y);
continue;
}
// waypoint.WaypointType == WaypointType.Path 或者 WaypointType.Target
// Path不用走得很近Target需要接近但都需要先移动到对应位置
await MoveTo(waypoint);
if (waypoint.Type == WaypointType.Target.Code)
{
await MoveCloseTo(waypoint);
}
}
}
private async Task MoveTo(Waypoint waypoint)
{
var screen = CaptureToRectArea();
var position = Navigation.GetPosition(screen);
var targetOrientation = Navigation.GetTargetOrientation(waypoint, position);
Logger.LogInformation("粗略接近路径点,当前位置({x1},{y1}),目标位置({x2},{y2})", position.X, position.Y, waypoint.X, waypoint.Y);
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 (true)
{
var now = DateTime.UtcNow;
if ((now - startTime).TotalSeconds > 30)
{
Logger.LogWarning("执行超时,跳过路径点");
break;
}
screen = CaptureToRectArea();
position = Navigation.GetPosition(screen);
var distance = Navigation.GetDistance(waypoint, position);
Logger.LogInformation("接近目标点中,距离为{distance}", 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)
{
TaskControl.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;
}
}
}
// 旋转视角
var isFlying = Bv.GetMotionStatus(screen) == MotionStatus.Fly;
targetOrientation = Navigation.GetTargetOrientation(waypoint, position);
RotateTo(targetOrientation, screen);
// 根据指定方式进行移动
if (waypoint.MoveMode == MoveModeEnum.Fly.Code)
{
// TODO:一直起跳直到打开风之翼
if (!isFlying)
{
Simulation.SendInput.Keyboard.KeyPress(User32.VK.VK_SPACE);
await Task.Delay(300);
}
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(1000, 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("精确接近路径点,当前位置({x1},{y1}),目标位置({x2},{y2})", position.X, position.Y, waypoint.X, waypoint.Y);
var isFlying = Bv.GetMotionStatus(screen) == MotionStatus.Fly;
if (waypoint.MoveMode == MoveModeEnum.Fly.Code && waypoint.Action == ActionEnum.StopFlying.Code && isFlying)
{
//下落攻击接近目的地
Simulation.SendInput.Mouse.LeftButtonClick();
await Task.Delay(1000);
}
await WaitUntilRotatedTo(targetOrientation, 2);
var wPressed = false;
var stepsTaken = 0;
while (true)
{
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.KeyPress(User32.VK.VK_W);
await Task.Delay(500);
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, int 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(-controlRatio * diff, 0);
return diff;
}
private async Task WaitUntilRotatedTo(int targetOrientation, int maxDiff)
{
int count = 0;
while (true)
{
var screen = CaptureToRectArea();
if (Math.Abs(RotateTo(targetOrientation, screen)) < maxDiff)
{
break;
}
if (count > 50)
{
break;
}
await Delay(50, cts);
count++;
}
}
}