检测战斗结束增加旋转寻找敌人实验性功能 (#1960)

This commit is contained in:
kaedelcb
2025-07-31 01:04:05 +08:00
committed by GitHub
parent c5960fedec
commit 1d49a0bd26
6 changed files with 470 additions and 50 deletions

View File

@@ -60,6 +60,12 @@ public partial class AutoFightConfig : ObservableObject
[ObservableProperty]
private bool _fastCheckEnabled = false;
/// <summary>
/// 旋转寻找敌人位置
/// </summary>
[ObservableProperty]
private bool _rotateFindEnemyEnabled = false;
/// <summary>
/// 快速检查战斗结束的参数,可填入数字和人名,多种用分号分隔,例如:15,白术;钟离;如果是数字小于等于0则不会根据时间去检查则指定检查间隔如果是人名则该角色执行一轮操作后进行检查。同时每轮结束后检查不变。
/// </summary>

View File

@@ -18,6 +18,7 @@ public class AutoFightParam : BaseTaskParam
public string FastCheckParams = "";
public string CheckEndDelay = "";
public string BeforeDetectDelay = "";
public bool RotateFindEnemyEnabled = false;
}
public AutoFightParam(string path, AutoFightConfig autoFightConfig)
@@ -34,12 +35,17 @@ public class AutoFightParam : BaseTaskParam
FinishDetectConfig.FastCheckParams = autoFightConfig.FinishDetectConfig.FastCheckParams;
FinishDetectConfig.CheckEndDelay = autoFightConfig.FinishDetectConfig.CheckEndDelay;
FinishDetectConfig.BeforeDetectDelay = autoFightConfig.FinishDetectConfig.BeforeDetectDelay;
FinishDetectConfig.RotateFindEnemyEnabled = autoFightConfig.FinishDetectConfig.RotateFindEnemyEnabled;
KazuhaPartyName = autoFightConfig.KazuhaPartyName;
OnlyPickEliteDropsMode = autoFightConfig.OnlyPickEliteDropsMode;
BattleThresholdForLoot = autoFightConfig.BattleThresholdForLoot ?? BattleThresholdForLoot;
//下面参数固定,只取自动战斗里面的
FinishDetectConfig.BattleEndProgressBarColor = TaskContext.Instance().Config.AutoFightConfig.FinishDetectConfig.BattleEndProgressBarColor;
FinishDetectConfig.BattleEndProgressBarColorTolerance = TaskContext.Instance().Config.AutoFightConfig.FinishDetectConfig.BattleEndProgressBarColorTolerance;
}
public FightFinishDetectConfig FinishDetectConfig { get; set; } = new();
@@ -57,5 +63,4 @@ public class AutoFightParam : BaseTaskParam
public string KazuhaPartyName;
public string OnlyPickEliteDropsMode="";
}

View File

@@ -0,0 +1,365 @@
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.Core.Simulator.Extensions;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;
using static BetterGenshinImpact.GameTask.Common.TaskControl;
using OpenCvSharp;
using BetterGenshinImpact.Core.Recognition.OpenCv;
namespace BetterGenshinImpact.GameTask.AutoFight
{
public static class MoveForwardTask
{
public static async Task ExecuteAsync(Scalar scalarLower, Scalar scalarHigher, ILogger logger, CancellationToken ct)
{
await MoveForwardAsync(scalarLower, scalarHigher, logger, ct);
}
public static Task<bool?> MoveForwardAsync(Scalar scalarLower, Scalar scalarHigher, ILogger logger, CancellationToken ct)
{
var image2 = CaptureToRectArea();
Mat mask2 = OpenCvCommonHelper.Threshold(
image2.DeriveCrop(0, 0, image2.Width * 1570 / 1920, image2.Height * 970 / 1080).SrcMat,
scalarLower,
scalarHigher
);
Mat labels2 = new Mat();
Mat stats2 = new Mat();
Mat centroids2 = new Mat();
int numLabels2 = Cv2.ConnectedComponentsWithStats(mask2, labels2, stats2, centroids2, connectivity: PixelConnectivity.Connectivity4, ltype: MatType.CV_32S);
logger.LogInformation($"检测数量J {numLabels2 - 1}");
if (numLabels2 > 1)
{
// 获取第一个连通对象的统计信息标签1
Mat firstRow = stats2.Row(1); // 获取第1行标签1的数据
int[] stats;
bool success = firstRow.GetArray(out stats); // 使用 out 参数来接收数组数据
if (success)
{
int x = stats[0];
int y = stats[1];
// int width = stats[2];
int height = stats[3];
Point firstPixel = new Point(x, y);
logger.LogInformation($"找到的连通对象的第一个像素坐标: ({firstPixel.X}, {firstPixel.Y}),连通对象的高度: {height}");
if (firstPixel.X < 500 || firstPixel.X > 1200 || firstPixel.Y < 300 || firstPixel.Y > 920)
{
// 非中心区域的处理逻辑
if (firstPixel.X < 500 && firstPixel.Y < 300)
{
// 左上区域
if (height <= 6)
{
logger.LogInformation("敌人在左上,向前加向左移动");
Task.Run(() =>
{
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyDown);
Simulation.SendInput.SimulateAction(GIActions.MoveLeft, KeyType.KeyDown);
Task.Delay(1000, ct).Wait();
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyUp);
Simulation.SendInput.SimulateAction(GIActions.MoveLeft, KeyType.KeyUp);
}, ct);
}
}
else if (firstPixel.X > 1200 && firstPixel.Y < 300)
{
// 右上区域
if (height <= 6)
{
logger.LogInformation("敌人在右上,向前加向右移动");
Task.Run(() =>
{
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyDown);
Simulation.SendInput.SimulateAction(GIActions.MoveRight, KeyType.KeyDown);
Task.Delay(1000, ct).Wait();
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyUp);
Simulation.SendInput.SimulateAction(GIActions.MoveRight, KeyType.KeyUp);
}, ct);
}
}
else if (firstPixel.X < 500 && firstPixel.Y > 920)
{
// 左下区域
if (height <= 6)
{
logger.LogInformation("敌人在左下,向后加向左移动");
Task.Run(() =>
{
Simulation.SendInput.SimulateAction(GIActions.MoveBackward, KeyType.KeyDown);
Simulation.SendInput.SimulateAction(GIActions.MoveLeft, KeyType.KeyDown);
Task.Delay(1000, ct).Wait();
Simulation.SendInput.SimulateAction(GIActions.MoveBackward, KeyType.KeyUp);
Simulation.SendInput.SimulateAction(GIActions.MoveLeft, KeyType.KeyUp);
}, ct);
}
}
else if (firstPixel.X > 1200 && firstPixel.Y > 920)
{
// 右下区域
if (height <= 6)
{
logger.LogInformation("敌人在右下,向后加向右移动");
Task.Run(() =>
{
Simulation.SendInput.SimulateAction(GIActions.MoveBackward, KeyType.KeyDown);
Simulation.SendInput.SimulateAction(GIActions.MoveRight, KeyType.KeyDown);
Task.Delay(1000, ct).Wait();
Simulation.SendInput.SimulateAction(GIActions.MoveBackward, KeyType.KeyUp);
Simulation.SendInput.SimulateAction(GIActions.MoveRight, KeyType.KeyUp);
}, ct);
}
}
else if (firstPixel.Y < 300)
{
// 上方区域
if (height <= 6)
{
logger.LogInformation("敌人在上方,向前移动");
Task.Run(() =>
{
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyDown);
Task.Delay(1000, ct).Wait();
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyUp);
}, ct);
}
}
else if (firstPixel.Y > 920)
{
// 下方区域
if (height <= 6)
{
logger.LogInformation("敌人在下方,向后移动");
Task.Run(() =>
{
Simulation.SendInput.SimulateAction(GIActions.MoveBackward, KeyType.KeyDown);
Task.Delay(1000, ct).Wait();
Simulation.SendInput.SimulateAction(GIActions.MoveBackward, KeyType.KeyUp);
}, ct);
}
}
else
{
// 非上述区域且非中心区域,判断左右
if (firstPixel.X < 920 && height > 6)
{
logger.LogInformation("敌人在左侧,不移动");
}
else if (firstPixel.X > 920 && height > 6)
{
logger.LogInformation("敌人在右侧,不移动");
}
}
}
else // 中心区域
{
if (height > 6)
{
logger.LogInformation("敌人在中心且高度大于6不移动");
}
else if (firstPixel.Y < 300)
{
logger.LogInformation("敌人在上方,向前移动");
Task.Run(() =>
{
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyDown);
Task.Delay(1000, ct).Wait();
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyUp);
}, ct);
}
else if (firstPixel.Y > 920)
{
logger.LogInformation("敌人在下方,向后移动");
Task.Run(() =>
{
Simulation.SendInput.SimulateAction(GIActions.MoveBackward, KeyType.KeyDown);
Task.Delay(1000, ct).Wait();
Simulation.SendInput.SimulateAction(GIActions.MoveBackward, KeyType.KeyUp);
}, ct);
}
else
{
logger.LogInformation("敌人在中心,不移动");
}
}
}
else
{
logger.LogError("无法获取统计信息数组");
}
}
mask2.Dispose();
labels2.Dispose();
return Task.FromResult<bool?>(null);
}
}
public class AutoFightSeek
{
public static async Task<bool> SeekAndFightAsync(ILogger logger, int detectDelayTime, CancellationToken ct)
{
Scalar bloodLower = new Scalar(255, 90, 90);
int retryCount = 0;
while (retryCount < 10)
{
var image = CaptureToRectArea();
Mat mask = OpenCvCommonHelper.Threshold(image.DeriveCrop(0, 0, 1500, 900).SrcMat, bloodLower);
Mat labels = new Mat();
Mat stats = new Mat();
Mat centroids = new Mat();
int numLabels = Cv2.ConnectedComponentsWithStats(mask, labels, stats, centroids,
connectivity: PixelConnectivity.Connectivity4, ltype: MatType.CV_32S);
logger.LogInformation($"检测数量首次: {numLabels - 1}");
if (numLabels > 1)
{
logger.LogInformation("首次检测画面内疑似有怪物,继续战斗...");
// 获取第一个连通对象的统计信息标签1
Mat firstRow = stats.Row(1); // 获取第1行标签1的数据
int[] statsArray;
bool success = firstRow.GetArray(out statsArray); // 使用 out 参数来接收数组数据
int height = statsArray[3];
logger.LogInformation($"敌人血量高度:{height}");
// //如果不用旋转判断敌人,直接跳过开队伍检测,加快战斗速度
// return height > 2;//大于2预防误判
if (success)
{
if (height < 7 && height > 2)
{
logger.LogInformation("敌人血量高度小于7且大于2向前移动");
Task.Run(() =>
{
MoveForwardTask.MoveForwardAsync(bloodLower, bloodLower, logger, ct);
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyDown);
Task.Delay(1000, ct).Wait();
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyUp);
}, ct);
}
mask.Dispose();
labels.Dispose();
stats.Dispose();
centroids.Dispose();
return height > 2;
}
}
//如果不用旋转判断敌人,直接跳过开队伍检测,加快战斗速度
// else
// {
// logger.LogInformation("首次检测画面内没有怪物...");
// return true;
// }
mask.Dispose();
labels.Dispose();
stats.Dispose();
centroids.Dispose();
logger.LogInformation("画面内没有怪物,旋转寻找...");
if (retryCount <= 1)
{
Simulation.SendInput.Mouse.MoveMouseBy(image.Width / 2, -image.Height / 3);
}
else
{
Simulation.SendInput.Mouse.MoveMouseBy(image.Width / 2, 0);
}
await Task.Delay(300,ct);
image = CaptureToRectArea();
mask = OpenCvCommonHelper.Threshold(image.DeriveCrop(0, 0, 1500, 900).SrcMat, bloodLower);
labels = new Mat();
stats = new Mat();
centroids = new Mat();
numLabels = Cv2.ConnectedComponentsWithStats(mask, labels, stats, centroids,
connectivity: PixelConnectivity.Connectivity4, ltype: MatType.CV_32S);
logger.LogInformation($"检测数量第 {retryCount + 1} 次: {numLabels - 1}");
if (numLabels > 1)
{
Mat firstRow2 = stats.Row(1); // 获取第1行标签1的数据
int[] statsArray2;
bool success2 = firstRow2.GetArray(out statsArray2); // 使用 out 参数来接收数组数据
int height2 = statsArray2[3];
if (success2)
{
logger.LogInformation($"敌人血量高度:{height2}");
if (height2 < 7 && height2 > 2)
{
logger.LogInformation("画面内有找到怪物,继续战斗...");
Task.Run(() => { MoveForwardTask.MoveForwardAsync(bloodLower, bloodLower, logger, ct); }, ct);
}
mask.Dispose();
labels.Dispose();
stats.Dispose();
centroids.Dispose();
return height2 > 2;
}
}
if (retryCount == 0)
{
Simulation.SendInput.SimulateAction(GIActions.OpenPartySetupScreen);
await Delay(detectDelayTime, ct);
var ra3 = CaptureToRectArea();
var b33 = ra3.SrcMat.At<Vec3b>(50, 790); // 进度条颜色
var whiteTile3 = ra3.SrcMat.At<Vec3b>(50, 768); // 白块
Simulation.SendInput.SimulateAction(GIActions.Drop);
if (IsWhite(whiteTile3.Item2, whiteTile3.Item1, whiteTile3.Item0) &&
IsYellow(b33.Item2, b33.Item1, b33.Item0))
{
logger.LogInformation("识别到战斗结束");
Simulation.SendInput.SimulateAction(GIActions.OpenPartySetupScreen);
return true;
}
}
logger.LogInformation("画面内没有怪物,尝试重新检测...");
retryCount++;
}
return false;
}
private static bool IsYellow(int r, int g, int b)
{
//Logger.LogInformation($"IsYellow({r},{g},{b})");
// 黄色范围R高G高B低
return (r >= 200 && r <= 255) &&
(g >= 200 && g <= 255) &&
(b >= 0 && b <= 100);
}
private static bool IsWhite(int r, int g, int b)
{
//Logger.LogInformation($"IsWhite({r},{g},{b})");
// 白色范围R高G高B低
return (r >= 240 && r <= 255) &&
(g >= 240 && g <= 255) &&
(b >= 240 && b <= 255);
}
}
}

View File

@@ -18,10 +18,8 @@ using BetterGenshinImpact.GameTask.Common.Job;
using OpenCvSharp;
using BetterGenshinImpact.Helpers;
using Vanara;
using Vanara.PInvoke;
using Microsoft.Extensions.DependencyInjection;
namespace BetterGenshinImpact.GameTask.AutoFight;
public class AutoFightTask : ISoloTask
@@ -49,6 +47,7 @@ public class AutoFightTask : ISoloTask
public double CheckTime = 5;
public List<string> CheckNames = new();
public bool FastCheckEnabled;
public bool RotateFindEnemyEnabled = false;
public TaskFightFinishDetectConfig(AutoFightParam.FightFinishDetectConfig finishDetectConfig)
{
@@ -61,6 +60,7 @@ public class AutoFightTask : ISoloTask
ParseSingleOrCommaSeparated(finishDetectConfig.BattleEndProgressBarColorTolerance, (6, 6, 6));
DetectDelayTime =
(int)((double.TryParse(finishDetectConfig.BeforeDetectDelay, out var result) ? result : 0.45) * 1000);
RotateFindEnemyEnabled = finishDetectConfig.RotateFindEnemyEnabled;
}
public (int, int, int) BattleEndProgressBarColor { get; }
@@ -369,8 +369,7 @@ public class AutoFightTask : ISoloTask
timeOutFlag = true;
break;
}
// 通用化战斗策略
command.Execute(combatScenes);
//统计战斗人次
if (i == combatCommands.Count - 1 || command.Name != combatCommands[i + 1].Name)
@@ -401,13 +400,9 @@ public class AutoFightTask : ISoloTask
}
else
{
Logger.LogInformation($"延时检查为{delayTime}毫秒");
// Logger.LogInformation($"延时检查为{delayTime}毫秒");
}
/*if (i<combatCommands.Count - 1)
{
Logger.LogInformation($"{command.Name}下一个人为{combatCommands[i+1].Name}毫秒");
}*/
fightEndFlag = await CheckFightFinish(delayTime, detectDelayTime);
}
}
@@ -544,46 +539,40 @@ public class AutoFightTask : ISoloTask
Math.Abs(a.Item3 - b.Item3) < c.Item3;
}
private async Task<bool> CheckFightFinish(int delayTime = 1500, int detectDelayTime = 450)
public async Task<bool> CheckFightFinish(int delayTime = 1500, int detectDelayTime = 450)
{
// YOLO 判断血条和怪物位置
// if (HasFightFlagByYolo(CaptureToRectArea()))
// {
// _lastFightFlagTime = DateTime.Now;
// return false;
// }
//
//Random random = new Random();
//double randomFraction = random.NextDouble(); // 生成 0 到 1 之间的随机小数
//此处随机数防止固定招式下使按L正好处于招式下导致无法准确判断战斗结束
// double randomNumber = 1 + (randomFraction * (3 - 1));
// 几秒内没有检测到血条和怪物位置,则开始旋转视角重新检测
//if ((DateTime.Now - _lastFightFlagTime).TotalSeconds > randomNumber)
//{
// 旋转完毕后都没有检测到血条和怪物位置则按L键确认战斗结束
/**
Simulation.SendInput.Mouse.MiddleButtonClick();
await Delay(300, _ct);
for (var i = 0; i < 8; i++)
if (_finishDetectConfig.RotateFindEnemyEnabled)
{
Simulation.SendInput.Mouse.MoveMouseBy((int)(500 * _dpi), 0);
await Delay(800, _ct); // 等待视角稳定
if (HasFightFlagByYolo(CaptureToRectArea()))
bool? result = null;
try
{
_lastFightFlagTime = DateTime.Now;
return false;
result = await AutoFightSeek.SeekAndFightAsync(Logger, detectDelayTime, _ct);
}
catch (Exception ex)
{
Logger.LogError(ex, "SeekAndFightAsync 方法发生异常");
result = false;
}
if (result == true)
{
return true;
}
return false;
}
**/
//Simulation.SendInput.SimulateAction(GIActions.Drop);//在换队前取消爬墙状态
await Delay(delayTime, _ct);
if (!_finishDetectConfig.RotateFindEnemyEnabled)await Delay(delayTime, _ct);
Logger.LogInformation("打开编队界面检查战斗是否结束,延时{detectDelayTime}毫秒检查", detectDelayTime);
// 最终方案确认战斗结束
Simulation.SendInput.SimulateAction(GIActions.OpenPartySetupScreen);
await Delay(detectDelayTime, _ct);
var ra = CaptureToRectArea();
//判断整个界面是否有红色色块,如果有,则战继续,否则战斗结束
// 只提取橙色
var b3 = ra.SrcMat.At<Vec3b>(50, 790); //进度条颜色
var whiteTile = ra.SrcMat.At<Vec3b>(50, 768); //白块
Simulation.SendInput.SimulateAction(GIActions.Drop);
@@ -600,14 +589,14 @@ public class AutoFightTask : ISoloTask
Logger.LogInformation($"未识别到战斗结束yellow{b3.Item0},{b3.Item1},{b3.Item2}");
Logger.LogInformation($"未识别到战斗结束white{whiteTile.Item0},{whiteTile.Item1},{whiteTile.Item2}");
/**
if (!Bv.IsInMainUi(ra))
Task.Run(() =>
{
// 如果不在主界面,说明异常,直接结束战斗继续下一步(地图追踪下一步会进入异常处理)
Logger.LogInformation("当前不在主界面,直接结束战斗!");
return true;
}**/
Scalar bloodLower = new Scalar(255, 90, 90);
MoveForwardTask.MoveForwardAsync(bloodLower, bloodLower, Logger, _ct);
} ,_ct);
_lastFightFlagTime = DateTime.Now;
return false;
}

View File

@@ -569,7 +569,34 @@
</StackPanel>
</ui:CardExpander>
</Grid>
</Grid>
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ui:TextBlock Grid.Row="0"
Grid.Column="0"
FontTypography="Body"
Text="旋转寻找敌人位置(实验性功能)"
TextWrapping="Wrap" />
<ui:TextBlock Grid.Row="1"
Grid.Column="0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Text="(建议配合开启“更快检测结束战斗”使用) 在打开队伍界面检测战斗结束前,会先检测画面中如果存在敌人,再判断是否需要移动靠近敌人,并跳过检测战斗结束,如果画面内没有敌人,则进行旋转寻找敌人。"
TextWrapping="Wrap" />
<ui:ToggleSwitch Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="10,0,36,0"
IsChecked="{Binding Config.AutoFightConfig.FinishDetectConfig.RotateFindEnemyEnabled, Mode=TwoWay}" />
</Grid>
<Grid Margin="16">

View File

@@ -947,8 +947,36 @@
</StackPanel>
</ui:CardExpander>
</Grid>
</Grid>
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ui:TextBlock Grid.Row="0"
Grid.Column="0"
FontTypography="Body"
Text="旋转寻找敌人位置(实验性功能)"
TextWrapping="Wrap" />
<ui:TextBlock Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
Text="(建议配合开启“更快检测结束战斗”使用) 在打开队伍界面检测战斗结束前,会先检测画面中如果存在敌人,再判断是否需要移动靠近敌人,并跳过检测战斗结束,如果画面内没有敌人,则进行旋转寻找敌人。"
TextWrapping="Wrap" />
<ui:ToggleSwitch Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="10,0,36,0"
IsChecked="{Binding PathingConfig.AutoFightConfig.FinishDetectConfig.RotateFindEnemyEnabled, Mode=TwoWay}" />
</Grid>
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />