mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-04-26 22:39:47 +08:00
auto pathing: add mapper viewer
This commit is contained in:
@@ -41,6 +41,7 @@
|
||||
<PackageReference Include="Microsoft.ML.OnnxRuntime.Managed" Version="1.18.1" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2592.51" />
|
||||
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
|
||||
<PackageReference Include="OpenCvSharp4.WpfExtensions" Version="4.8.0.20230708" />
|
||||
<PackageReference Include="OpenCvSharp4.Extensions" Version="4.8.0.20230708" />
|
||||
<PackageReference Include="OpenCvSharp4.Windows" Version="4.8.0.20230708" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.122" />
|
||||
|
||||
@@ -4,6 +4,8 @@ using BetterGenshinImpact.GameTask.Model.Area;
|
||||
using OpenCvSharp;
|
||||
using System;
|
||||
using BetterGenshinImpact.GameTask.Common;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.AutoPathing;
|
||||
@@ -20,7 +22,11 @@ internal class Navigation
|
||||
internal static Point2f GetPosition(ImageRegion imageRegion)
|
||||
{
|
||||
var greyMat = new Mat(imageRegion.SrcGreyMat, new Rect(62, 19, 212, 212));
|
||||
return EntireMap.Instance.GetMiniMapPositionByFeatureMatch(greyMat);
|
||||
var p = EntireMap.Instance.GetMiniMapPositionByFeatureMatch(greyMat);
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new PropertyChangedMessage<object>(typeof(Navigation),
|
||||
"SendCurrentPosition", new object(), p));
|
||||
return p;
|
||||
}
|
||||
|
||||
internal static int GetTargetOrientation(Waypoint waypoint, Point2f position)
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
using BetterGenshinImpact.Core.Simulator;
|
||||
using BetterGenshinImpact.GameTask.AutoPathing.Model;
|
||||
using BetterGenshinImpact.GameTask.AutoPathing.Model.Enum;
|
||||
using BetterGenshinImpact.GameTask.AutoTrackPath;
|
||||
using BetterGenshinImpact.GameTask.Common;
|
||||
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;
|
||||
@@ -12,10 +15,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BetterGenshinImpact.GameTask.AutoPathing.Model.Enum;
|
||||
using BetterGenshinImpact.Helpers;
|
||||
using Vanara.PInvoke;
|
||||
using Wpf.Ui.Violeta.Controls;
|
||||
using static BetterGenshinImpact.GameTask.Common.TaskControl;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.AutoPathing;
|
||||
@@ -32,7 +32,9 @@ public class PathExecutor(CancellationTokenSource cts)
|
||||
|
||||
task.Positions.First().Type = WaypointType.Teleport.Code;
|
||||
|
||||
// 这里应该判断一下自动拾取是否处于工作状态,但好像没有什么方便的读取办法
|
||||
// 初始化查看地图
|
||||
WeakReferenceMessenger.Default.Send(new PropertyChangedMessage<object>(this,
|
||||
"UpdateCurrentPathing", new object(), task));
|
||||
|
||||
// TODO:大地图传送的时候使用游戏坐标,追踪的时候应该使用2048地图图像坐标,这里临时做转换,后续改进
|
||||
var waypoints = new List<Waypoint>();
|
||||
@@ -61,7 +63,9 @@ public class PathExecutor(CancellationTokenSource cts)
|
||||
if (waypoint.Type == WaypointType.Teleport.Code)
|
||||
{
|
||||
Logger.LogInformation("正在传送到{x},{y}", waypoint.X, waypoint.Y);
|
||||
await new TpTask(cts).Tp(waypoint.X, waypoint.Y);
|
||||
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); // 通过上一个位置直接进行局部特征匹配
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -230,6 +234,8 @@ public class PathExecutor(CancellationTokenSource cts)
|
||||
{
|
||||
Simulation.SendInput.Keyboard.KeyUp(User32.VK.VK_W);
|
||||
}
|
||||
// 不管咋样,抬起w键
|
||||
Simulation.SendInput.Keyboard.KeyUp(User32.VK.VK_W);
|
||||
}
|
||||
|
||||
private int RotateTo(int targetOrientation, ImageRegion imageRegion, int controlRatio = 1)
|
||||
@@ -241,6 +247,7 @@ public class PathExecutor(CancellationTokenSource cts)
|
||||
{
|
||||
return diff;
|
||||
}
|
||||
// 平滑的旋转视角
|
||||
if (Math.Abs(diff) > 90)
|
||||
{
|
||||
controlRatio = 5;
|
||||
|
||||
@@ -33,7 +33,7 @@ public class TpTask(CancellationTokenSource cts)
|
||||
/// </summary>
|
||||
/// <param name="tpX"></param>
|
||||
/// <param name="tpY"></param>
|
||||
public async Task Tp(double tpX, double tpY)
|
||||
public async Task<(double, double)> Tp(double tpX, double tpY)
|
||||
{
|
||||
// 获取最近的传送点位置
|
||||
var (x, y) = GetRecentlyTpPoint(tpX, tpY);
|
||||
@@ -88,6 +88,7 @@ public class TpTask(CancellationTokenSource cts)
|
||||
}
|
||||
|
||||
Logger.LogInformation("传送完成");
|
||||
return (x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -201,7 +202,7 @@ public class TpTask(CancellationTokenSource cts)
|
||||
{
|
||||
throw new RetryException("当前不在地图界面");
|
||||
}
|
||||
}, TimeSpan.FromMilliseconds(500), 5);
|
||||
}, TimeSpan.FromMilliseconds(500), 10);
|
||||
|
||||
if (rect == Rect.Empty)
|
||||
{
|
||||
|
||||
@@ -33,6 +33,11 @@ public class EntireMap : Singleton<EntireMap>
|
||||
|
||||
private int _failCnt = 0;
|
||||
|
||||
public void SetPrevPosition(float x, float y)
|
||||
{
|
||||
(_prevX, _prevY) = (x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于特征匹配获取地图位置
|
||||
/// 移动匹配
|
||||
|
||||
@@ -422,7 +422,7 @@
|
||||
</ui:CardControl>
|
||||
|
||||
<!-- 地图 -->
|
||||
<ui:CardControl Margin="0,0,0,12"
|
||||
<!--<ui:CardControl Margin="0,0,0,12"
|
||||
Icon="{ui:SymbolIcon Cursor24}"
|
||||
Visibility="{markup:Converter Value={x:Static helpers:RuntimeHelper.IsDebuggerAttached},
|
||||
Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
@@ -447,7 +447,7 @@
|
||||
<ui:Button Margin="0,0,36,0"
|
||||
Command="{Binding OpenMapViewerCommand}"
|
||||
Content="查看地图" />
|
||||
</ui:CardControl>
|
||||
</ui:CardControl>-->
|
||||
|
||||
<!--<ui:CardControl Margin="0,0,0,12" Icon="{ui:SymbolIcon Keyboard24}">
|
||||
<ui:CardControl.Header>
|
||||
|
||||
@@ -47,6 +47,9 @@
|
||||
<ui:Button Command="{Binding OpenScriptsFolderCommand}"
|
||||
Content="打开任务目录"
|
||||
Icon="{ui:SymbolIcon FolderOpen24}" />
|
||||
<ui:Button Margin="10,0,0,0"
|
||||
Command="{Binding OpenMapViewerCommand}"
|
||||
Content="查看地图" />
|
||||
</StackPanel>
|
||||
|
||||
<Separator Grid.Row="3"
|
||||
|
||||
@@ -6,16 +6,15 @@
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:windows="clr-namespace:BetterGenshinImpact.ViewModel.Windows"
|
||||
Title="地图"
|
||||
Width="1400"
|
||||
Height="1330"
|
||||
MinWidth="700"
|
||||
MinHeight="650"
|
||||
Width="512"
|
||||
Height="512"
|
||||
d:DataContext="{d:DesignInstance Type=windows:MapViewerViewModel}"
|
||||
ui:Design.Background="{DynamicResource ApplicationBackgroundBrush}"
|
||||
ui:Design.Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
ExtendsContentIntoTitleBar="True"
|
||||
FontFamily="{DynamicResource TextThemeFontFamily}"
|
||||
ResizeMode="CanMinimize"
|
||||
Topmost="True"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
WindowStyle="SingleBorderWindow"
|
||||
mc:Ignorable="d">
|
||||
@@ -26,12 +25,7 @@
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Canvas Grid.Row="1">
|
||||
<ui:Image Source="{Binding MapPath, Mode=OneWay}" Stretch="Uniform" />
|
||||
<Rectangle Canvas.Left="{Binding BigMapRect.Left}"
|
||||
Canvas.Top="{Binding BigMapRect.Top}"
|
||||
Width="{Binding BigMapRect.Width}"
|
||||
Height="{Binding BigMapRect.Height}"
|
||||
Stroke="Red" />
|
||||
<ui:Image Source="{Binding MapBitmap, Mode=OneWay}" Stretch="Uniform" />
|
||||
</Canvas>
|
||||
|
||||
|
||||
|
||||
@@ -70,10 +70,4 @@ public partial class CommonSettingsPageViewModel : ObservableObject, INavigation
|
||||
|
||||
Process.Start("explorer.exe", path);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void OnOpenMapViewer()
|
||||
{
|
||||
new MapViewer().Show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ using BetterGenshinImpact.Core.Script;
|
||||
using BetterGenshinImpact.GameTask;
|
||||
using BetterGenshinImpact.GameTask.AutoPathing;
|
||||
using BetterGenshinImpact.GameTask.Model.Enum;
|
||||
using BetterGenshinImpact.View.Windows;
|
||||
using Wpf.Ui.Controls;
|
||||
using Wpf.Ui.Violeta.Controls;
|
||||
|
||||
@@ -88,7 +89,7 @@ public partial class MapPathingViewModel : ObservableObject, INavigationAware, I
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task OnStart(PathingTask? item)
|
||||
public void OnStart(PathingTask? item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
@@ -97,4 +98,10 @@ public partial class MapPathingViewModel : ObservableObject, INavigationAware, I
|
||||
new TaskRunner(DispatcherTimerOperationEnum.UseSelfCaptureImage)
|
||||
.FireAndForget(async () => await new PathExecutor(CancellationContext.Instance.Cts).Pathing(item));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void OnOpenMapViewer()
|
||||
{
|
||||
new MapViewer().Show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,185 @@
|
||||
using BetterGenshinImpact.Core.Config;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using System.Windows;
|
||||
using OpenCvSharp;
|
||||
using OpenCvSharp.WpfExtensions;
|
||||
using System.Windows.Media.Imaging;
|
||||
using BetterGenshinImpact.Core.Config;
|
||||
using BetterGenshinImpact.GameTask.AutoPathing.Model;
|
||||
using BetterGenshinImpact.GameTask.AutoPathing.Model.Enum;
|
||||
using BetterGenshinImpact.GameTask.Common.Map;
|
||||
using BetterGenshinImpact.Helpers;
|
||||
using System.Linq;
|
||||
using BetterGenshinImpact.Core.Recognition.OpenCv;
|
||||
|
||||
namespace BetterGenshinImpact.ViewModel.Windows;
|
||||
|
||||
public partial class MapViewerViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private Rect _bigMapRect = new(0, 0, 0, 0);
|
||||
private WriteableBitmap _mapBitmap;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _mapPath = Global.Absolute(@"Assets\Map\mainMap100Block.png");
|
||||
private readonly Mat _all256Map = new(Global.Absolute(@"Assets/Map/mainMap256Block.png"));
|
||||
|
||||
private Mat _currentPathingMap = new(); // 2048级别
|
||||
|
||||
private Rect _currentPathingRect = new(); // 2048级别
|
||||
|
||||
public MapViewerViewModel()
|
||||
{
|
||||
var center = MapCoordinate.GameToMain2048(0, 0);
|
||||
_mapBitmap = ClipMat(new Point2f((float)center.x, (float)center.y)).ToWriteableBitmap();
|
||||
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<object>>(this, (sender, msg) =>
|
||||
{
|
||||
if (msg.PropertyName == "UpdateBigMapRect")
|
||||
if (msg.PropertyName == "SendCurrentPosition")
|
||||
{
|
||||
BigMapRect = (Rect)msg.NewValue;
|
||||
UIDispatcherHelper.Invoke(() =>
|
||||
{
|
||||
Debug.WriteLine("更新地图位置");
|
||||
try
|
||||
{
|
||||
MapBitmap.Lock();
|
||||
WriteableBitmapConverter.ToWriteableBitmap(ClipMat((Point2f)msg.NewValue), MapBitmap);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
MapBitmap.Unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (msg.PropertyName == "UpdateCurrentPathing")
|
||||
{
|
||||
Debug.WriteLine("更新当前追踪的路径图像");
|
||||
_currentPathingMap = GenTaskMat((PathingTask)msg.NewValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Mat ClipMat(Point2f pos)
|
||||
{
|
||||
var len = 256;
|
||||
pos = new Point2f(pos.X - _currentPathingRect.X, pos.Y - _currentPathingRect.Y);
|
||||
Rect rect = new((int)pos.X - len, (int)pos.Y - len, len * 2, len * 2);
|
||||
// 实现剪切 Mat 的逻辑
|
||||
if (_currentPathingMap.Empty())
|
||||
{
|
||||
Debug.WriteLine("_currentPathingMap 未初始化");
|
||||
return new Mat(_all256Map, new Rect(rect.X / 8, rect.Y / 8, rect.Width, rect.Height));
|
||||
}
|
||||
else
|
||||
{
|
||||
Mat clipMat = new(_currentPathingMap, rect);
|
||||
clipMat = clipMat.Clone();
|
||||
// 绘制中心点
|
||||
Cv2.Circle(clipMat, new Point(len, len), 3, new Scalar(0, 255, 0), 2);
|
||||
return clipMat;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="pathingTask"></param>
|
||||
/// <returns>2048级别的图像</returns>
|
||||
public Mat GenTaskMat(PathingTask pathingTask)
|
||||
{
|
||||
// 获取路径点位并转换为当前主要展示的地图点位 2048 级别
|
||||
var points = pathingTask.Positions;
|
||||
var mapPoints = points.Select(ConvertToMapPoint).ToList();
|
||||
var offsetRect = CalcRect(mapPoints);
|
||||
var offsetRect256 = new Rect(offsetRect.X / 8, offsetRect.Y / 8, offsetRect.Width / 8, offsetRect.Height / 8);
|
||||
|
||||
// 把 _all256Map 的局部转化为 2048 级别
|
||||
Mat taskMat = new Mat(_all256Map, offsetRect256);
|
||||
taskMat = ResizeHelper.Resize(taskMat, 2048d / 256d);
|
||||
|
||||
// 设置线条粗细
|
||||
int thickness = 2;
|
||||
|
||||
// 绘制点位和连线
|
||||
for (int i = 0; i < mapPoints.Count - 1; i++)
|
||||
{
|
||||
var startPoint = mapPoints[i] - offsetRect.TopLeft;
|
||||
var endPoint = mapPoints[i + 1] - offsetRect.TopLeft;
|
||||
|
||||
var lineColor = GetLineColor(points[i], points[i + 1]);
|
||||
var circleColor = GetCircleColor(points[i]);
|
||||
|
||||
// 绘制点
|
||||
DrawCircle(taskMat, startPoint, circleColor, thickness);
|
||||
|
||||
// 绘制线
|
||||
DrawLine(taskMat, startPoint, endPoint, lineColor, 1);
|
||||
}
|
||||
|
||||
// 绘制最后一个点
|
||||
var lastPoint = mapPoints[^1] - offsetRect.TopLeft;
|
||||
var lastCircleColor = GetCircleColor(points[^1]);
|
||||
DrawCircle(taskMat, lastPoint, lastCircleColor, thickness);
|
||||
|
||||
return taskMat;
|
||||
}
|
||||
|
||||
private Rect CalcRect(List<Point> mapPoints)
|
||||
{
|
||||
// 计算其最大外接矩形
|
||||
_currentPathingRect = Cv2.BoundingRect(mapPoints);
|
||||
// 把矩形范围扩大一半
|
||||
_currentPathingRect.X -= 512;
|
||||
_currentPathingRect.Y -= 512;
|
||||
_currentPathingRect.Width += 1024;
|
||||
_currentPathingRect.Height += 1024;
|
||||
return _currentPathingRect;
|
||||
}
|
||||
|
||||
private Point ConvertToMapPoint(Waypoint point)
|
||||
{
|
||||
var (x, y) = MapCoordinate.GameToMain2048(point.X, point.Y);
|
||||
return new Point(x, y);
|
||||
}
|
||||
|
||||
private Scalar GetLineColor(Waypoint startPoint, Waypoint endPoint)
|
||||
{
|
||||
if (endPoint.Type == WaypointType.Path.Code || startPoint.Type == WaypointType.Teleport.Code)
|
||||
{
|
||||
return new Scalar(255, 0, 0); // 蓝色
|
||||
}
|
||||
else if (endPoint.Type == WaypointType.Target.Code)
|
||||
{
|
||||
return new Scalar(0, 0, 255); // 红色
|
||||
}
|
||||
|
||||
return new Scalar(0, 0, 255); // 默认红色
|
||||
}
|
||||
|
||||
private Scalar GetCircleColor(Waypoint point)
|
||||
{
|
||||
if (point.Type == WaypointType.Path.Code || point.Type == WaypointType.Teleport.Code)
|
||||
{
|
||||
return new Scalar(255, 0, 0); // 蓝色
|
||||
}
|
||||
else if (point.Type == WaypointType.Target.Code)
|
||||
{
|
||||
return new Scalar(0, 0, 255); // 红色
|
||||
}
|
||||
|
||||
return new Scalar(0, 0, 255); // 默认红色
|
||||
}
|
||||
|
||||
private void DrawCircle(Mat mat, Point point, Scalar color, int thickness)
|
||||
{
|
||||
Cv2.Circle(mat, point, 3, color, thickness);
|
||||
}
|
||||
|
||||
private void DrawLine(Mat mat, Point startPoint, Point endPoint, Scalar color, int thickness)
|
||||
{
|
||||
Cv2.Line(mat, startPoint, endPoint, color, thickness);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user