Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions BetterGenshinImpact/GameTask/AutoFight/Assets/AutoFightAssets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ public class AutoFightAssets : BaseAssets<AutoFightAssets>
public Rect GadgetRect;

public RecognitionObject AbnormalIconRa;

// 经验图标
public RecognitionObject ExperienceRa;

private Vanara.PInvoke.RECT _gameScreenSize = SystemControl.GetGameScreenRect(TaskContext.Instance().GameHandle);

#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑添加 "required" 修饰符或声明为可为 null。
private AutoFightAssets() : base()
Expand Down Expand Up @@ -265,4 +270,30 @@ private void Initialization(ISystemInfo systemInfo)
DrawOnWindow = false
}.InitTemplate();
}

public RecognitionObject InitializeRecognitionObject(int experience)
{
var threshold = 0.9;

if (_gameScreenSize.Width > 2560)
{
threshold =0.6;
}
else if (_gameScreenSize.Width > 1920)
{
threshold =0.6;
}

ExperienceRa = new RecognitionObject
{
Name = experience.ToString(),
RecognitionType = RecognitionTypes.TemplateMatch,
TemplateImageMat = GameTaskManager.LoadAssetImage("AutoFight", "experience_" + experience + ".png"),
RegionOfInterest = new Rect((int)(CaptureRect.Width*0.145),(int)(CaptureRect.Height*0.5), (int)(CaptureRect.Width*0.02), (int)(CaptureRect.Height*0.22)),
UseMask = true,
Threshold = threshold,
DrawOnWindow = true,
}.InitTemplate();
return ExperienceRa;
}
}
3 changes: 3 additions & 0 deletions BetterGenshinImpact/GameTask/AutoFight/AutoFightConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ public partial class FightFinishDetectConfig : ObservableObject

[ObservableProperty]
private bool _swimmingEnabled = false;

[ObservableProperty]
private bool _expKazuhaPickup = false;

/// <summary>
/// 战斗超时,单位秒
Expand Down
5 changes: 3 additions & 2 deletions BetterGenshinImpact/GameTask/AutoFight/AutoFightParam.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ public AutoFightParam(string path, AutoFightConfig autoFightConfig) : base(null,
GuardianCombatSkip = autoFightConfig.GuardianCombatSkip;
GuardianAvatarHold = autoFightConfig.GuardianAvatarHold;
BurstEnabled = autoFightConfig.BurstEnabled;

CheckBeforeBurst = autoFightConfig.FinishDetectConfig.CheckBeforeBurst;
ExpKazuhaPickup = autoFightConfig.ExpKazuhaPickup;
IsFirstCheck = autoFightConfig.FinishDetectConfig.IsFirstCheck;
RotaryFactor = autoFightConfig.FinishDetectConfig.RotaryFactor;
QinDoublePickUp = autoFightConfig.QinDoublePickUp;
Expand Down Expand Up @@ -80,6 +79,7 @@ public AutoFightParam(string path, AutoFightConfig autoFightConfig) : base(null,
public bool IsFirstCheck { get; set; } = true;
public int RotaryFactor { get; set; } = 10;
public bool BurstEnabled { get; set; } = false;
public bool ExpKazuhaPickup { get; set; } = false;

public bool QinDoublePickUp { get; set; } = false;
public static bool SwimmingEnabled { get; set; } = false;
Expand Down Expand Up @@ -138,5 +138,6 @@ public void SetDefault()
GuardianAvatarHold = autoFightConfig.GuardianAvatarHold;
SwimmingEnabled = autoFightConfig.SwimmingEnabled;
QinDoublePickUp = autoFightConfig.QinDoublePickUp;
ExpKazuhaPickup = autoFightConfig.ExpKazuhaPickup;
}
}
116 changes: 113 additions & 3 deletions BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,25 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.Core.Recognition;
using static BetterGenshinImpact.GameTask.Common.TaskControl;
using BetterGenshinImpact.GameTask.Common.Job;
using OpenCvSharp;
using BetterGenshinImpact.Helpers;
using Vanara;
using Microsoft.Extensions.DependencyInjection;
using BetterGenshinImpact.GameTask.AutoPathing.Model;
using BetterGenshinImpact.GameTask.Common.Element.Assets;
using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.GameTask.AutoFight.Assets;
using BetterGenshinImpact.View.Drawable;
using BetterGenshinImpact.Core.Recognition.OpenCv;
using BetterGenshinImpact.GameTask.Common.BgiVision;
using Vanara.PInvoke;
using BetterGenshinImpact.GameTask.AutoPathing.Handler;
using BetterGenshinImpact.GameTask.AutoPick.Assets;
using BetterGenshinImpact.GameTask.AutoPathing.Model;
using BetterGenshinImpact.GameTask.AutoFight.Assets;

namespace BetterGenshinImpact.GameTask.AutoFight;

Expand All @@ -44,6 +54,8 @@ public class AutoFightTask : ISoloTask
public static bool FightStatusFlag { get; set; } = false;

private static readonly object PickLock = new object();

private static bool _isExperiencePickup = false;

// 战斗点位
public static WaypointForTrack? FightWaypoint {get; set;} = null;
Expand Down Expand Up @@ -291,6 +303,10 @@ public async Task Start(CancellationToken ct)
var guardianAvatar = string.IsNullOrWhiteSpace(_taskParam.GuardianAvatar) ? null : combatScenes.SelectAvatar(int.Parse(_taskParam.GuardianAvatar));

AutoFightSeek.RotationCount= 0; // 重置旋转次数

var avatarName = combatScenes.Avatars.Select(a => a.Name).ToArray();
var requiredAvatars = new HashSet<string> { "枫原万叶", "琴" };//后续方便添加
var findExpAvatar = requiredAvatars.Any(a => avatarName.Contains(a));

// 战斗操作
var fightTask = Task.Run(async () =>
Expand All @@ -299,6 +315,12 @@ public async Task Start(CancellationToken ct)
{
FightStatusFlag = true;

#region 基于战斗检测经验值开关万叶拾取功能同步任务

if (_taskParam.ExpKazuhaPickup && findExpAvatar) FindExp(cts2.Token);

#endregion

while (!cts2.Token.IsCancellationRequested)
Comment on lines +318 to 324
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

FindExp 调用未管理生命周期;绘制清理时机错误

  • FindExp 立即返回 CompletedTask,真正的检测在 Task.Run 内异步进行;同时 ClearAll 在外层 finally 中被过早调用,无法保证在任务结束后才清理绘制,可能导致闪烁或残留。
  • 建议让 FindExp 返回内部 Task,并将清理移入该任务的 finally;调用端可按需忽略返回值或在需要时等待其完成。

可按下方方式修改 FindExp(见对应方法处的完整 diff)。

🤖 Prompt for AI Agents
In BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs around lines 318 to
324, the call to FindExp starts a background Task internally and returns a
completed Task immediately while ClearAll is invoked from the outer finally too
early; change FindExp to return the internal Task so its lifecycle is managed by
the returned Task, move drawing cleanup (ClearAll) into the finally block inside
that internal Task, and update this call site to await or ignore the returned
Task as appropriate (e.g., if you don't need to wait, explicitly ignore the Task
to document intent).

{
// 所有战斗角色都可以被取消
Expand Down Expand Up @@ -475,13 +497,22 @@ public async Task Start(CancellationToken ct)
}, cts2.Token);

await fightTask;
if (_taskParam.BattleThresholdForLoot>=2 && countFight < _taskParam.BattleThresholdForLoot)

if (_taskParam.KazuhaPickupEnabled && _taskParam.ExpKazuhaPickup && !_isExperiencePickup)//可能刚死亡,等待经验显示
{
TaskControl.Logger.LogInformation("基于怪物经验判断:{text} 经验值显示","等待");
await Delay(1000, ct);
}

if ((_taskParam.BattleThresholdForLoot >= 2 && countFight < _taskParam.BattleThresholdForLoot) && (!_taskParam.ExpKazuhaPickup || !_isExperiencePickup))
{
Logger.LogInformation($"战斗人次({countFight})低于配置人次({_taskParam.BattleThresholdForLoot}),跳过此次拾取!");
return;
}

if(_taskParam.KazuhaPickupEnabled && _taskParam.ExpKazuhaPickup) Logger.LogInformation("基于怪物经验判断:{text} 聚集拾取", _isExperiencePickup? "执行" : "不执行");

if (_taskParam.KazuhaPickupEnabled)
if (_taskParam.KazuhaPickupEnabled && (!_taskParam.ExpKazuhaPickup || _isExperiencePickup))
{
// 队伍中存在万叶的时候使用一次长E
var picker = combatScenes.SelectAvatar("枫原万叶") ?? combatScenes.SelectAvatar("琴");
Expand Down Expand Up @@ -733,6 +764,85 @@ bool IsWhite(int r, int g, int b)
(g >= 240 && g <= 255) &&
(b >= 240 && b <= 255);
}

//基于万叶经验值判断是否拾取
private static Task FindExp(CancellationToken cts2)
{
var autoFightAssets = AutoFightAssets.Instance;

try
{
Task.Run(() =>
{
_isExperiencePickup = false;
var expLogo = false;

var experienceRas = new[]
{
autoFightAssets.InitializeRecognitionObject(60),
autoFightAssets.InitializeRecognitionObject(58),
autoFightAssets.InitializeRecognitionObject(57),
};

while (!(_isExperiencePickup || !FightStatusFlag) && !cts2.IsCancellationRequested)
{
try
{
cts2.ThrowIfCancellationRequested();

var result = NewRetry.WaitForAction(() =>
{
using (var ra = CaptureToRectArea())
{
_isExperiencePickup = experienceRas.Any(experienceRa =>
{
var isExist = ra.Find(experienceRa);
if (!isExist.IsExist())
{
return false;
}

var pixelValue1 = ra.SrcMat.At<Vec3b>(isExist.Y, isExist.X - 147); //经验值图标,在2K以上时匹配度0.6,这个经验值颜色尤为重要
expLogo = pixelValue1[0] == 253 && pixelValue1[1] == 247 && pixelValue1[2] == 172;

return expLogo;
});
}
return _isExperiencePickup;
}, cts2, 1, 100).Result;
}
catch (OperationCanceledException ex)
{
Console.WriteLine($"检测经验发生异常: {ex.Message}");
}
catch (Exception ex)
{
// Console.WriteLine($"检测怪物经验发生异常: {ex.Message}");
}

if (_isExperiencePickup) Logger.LogInformation("基于怪物经验判断:识别到 {text1} 经验值,{text2} 聚集拾取","精英","启用" );

}

cts2.ThrowIfCancellationRequested();

}, cts2);
}
catch (OperationCanceledException ex)
{
Console.WriteLine($"检测经验发生异常: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"检测怪物经验发生异常: {ex.Message}");
}
finally
{
VisionContext.Instance().DrawContent.ClearAll();
}

return Task.CompletedTask;
}
Comment on lines +768 to +845
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

FindExp:将清理放入任务 finally,避免 .Result 阻塞并加入边界检查/色差容忍

  • 将外层 finally 的 ClearAll 移入 Task.Run 的 finally,使清理与任务生命周期一致;
  • 避免 .Result 阻塞,直接 await WaitForAction;
  • 采样点加入边界保护;
  • 颜色匹配加入微小容忍,降低分辨率/抗锯齿差异带来的误判。

建议变更:

-    private static Task FindExp(CancellationToken cts2)
-    {
-        var autoFightAssets = AutoFightAssets.Instance;
-        try  
-        {
-            Task.Run(() =>
-            {
-                _isExperiencePickup = false;
-                var expLogo = false;
-                
-                var experienceRas = new[]
-                {
-                   autoFightAssets.InitializeRecognitionObject(60), 
-                   autoFightAssets.InitializeRecognitionObject(58), 
-                   autoFightAssets.InitializeRecognitionObject(57),
-                };
-                
-                while (!(_isExperiencePickup || !FightStatusFlag) && !cts2.IsCancellationRequested)
-                {
-                    try
-                    {
-                        cts2.ThrowIfCancellationRequested();
-
-                        var result = NewRetry.WaitForAction(() =>
-                        {
-                            using (var ra = CaptureToRectArea())
-                            {
-                                _isExperiencePickup = experienceRas.Any(experienceRa => 
-                                {
-                                    var isExist = ra.Find(experienceRa);
-                                    if (!isExist.IsExist())
-                                    {
-                                        return false;
-                                    }
-                
-                                    var pixelValue1 = ra.SrcMat.At<Vec3b>(isExist.Y, isExist.X - 147); //经验值图标,在2K以上时匹配度0.6,这个经验值颜色尤为重要
-                                    expLogo = pixelValue1[0] == 253 && pixelValue1[1] == 247 && pixelValue1[2] == 172;
-
-                                    return expLogo;
-                                });
-                            }
-                            return _isExperiencePickup;
-                        }, cts2, 1, 100).Result;
-                    }
-                    catch (OperationCanceledException ex)
-                    {
-                        Console.WriteLine($"检测经验发生异常: {ex.Message}");
-                    }
-                    catch (Exception ex)
-                    {
-                        // Console.WriteLine($"检测怪物经验发生异常: {ex.Message}");
-                    }
-                    
-                    if (_isExperiencePickup) Logger.LogInformation("基于怪物经验判断:识别到 {text1} 经验值,{text2} 聚集拾取","精英","启用" );
-
-                }
-                
-                cts2.ThrowIfCancellationRequested();
-                
-            }, cts2); 
-        }
-        catch (OperationCanceledException ex)
-        {
-            Console.WriteLine($"检测经验发生异常: {ex.Message}");
-        }
-        catch (Exception ex)
-        {
-            Console.WriteLine($"检测怪物经验发生异常: {ex.Message}");
-        }
-        finally
-        {
-            VisionContext.Instance().DrawContent.ClearAll();
-        }
-        
-        return Task.CompletedTask;
-    }
+    private static Task FindExp(CancellationToken token)
+    {
+        var assets = AutoFightAssets.Instance;
+        return Task.Run(async () =>
+        {
+            try
+            {
+                _isExperiencePickup = false;
+                var experienceRas = new[]
+                {
+                    assets.InitializeRecognitionObject(60),
+                    assets.InitializeRecognitionObject(58),
+                    assets.InitializeRecognitionObject(57),
+                };
+
+                while (!_isExperiencePickup && FightStatusFlag && !token.IsCancellationRequested)
+                {
+                    try
+                    {
+                        token.ThrowIfCancellationRequested();
+                        var detected = await NewRetry.WaitForAction(() =>
+                        {
+                            using var ra = CaptureToRectArea();
+                            foreach (var ro in experienceRas)
+                            {
+                                var match = ra.Find(ro);
+                                if (!match.IsExist()) continue;
+                                var sampleX = match.X - 147;
+                                var sampleY = match.Y;
+                                if (sampleX < 0 || sampleY < 0 || sampleX >= ra.SrcMat.Cols || sampleY >= ra.SrcMat.Rows)
+                                {
+                                    continue;
+                                }
+                                var p = ra.SrcMat.At<Vec3b>(sampleY, sampleX);
+                                // 颜色近似(±3)以提升鲁棒性
+                                bool near = Math.Abs(p[0] - 253) <= 3 && Math.Abs(p[1] - 247) <= 3 && Math.Abs(p[2] - 172) <= 3;
+                                if (near) return (_isExperiencePickup = true);
+                            }
+                            return _isExperiencePickup;
+                        }, token, retryTimes: 1, delayMs: 100);
+
+                        if (detected)
+                        {
+                            Logger.LogInformation("基于怪物经验判断:识别到 {text1} 经验值,{text2} 聚集拾取", "精英", "启用");
+                        }
+                    }
+                    catch (OperationCanceledException) { /* ignore */ }
+                    catch { /* ignore */ }
+                }
+            }
+            finally
+            {
+                VisionContext.Instance().DrawContent.ClearAll();
+            }
+        }, token);
+    }

如需进一步稳妥,可将 FightStatusFlag 也以 volatile/Interlocked 保护(参见上一条评论)。

🤖 Prompt for AI Agents
In BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs around lines 768 to
845: the review asks to move the cleanup into the spawned task’s finally, avoid
blocking .Result by awaiting WaitForAction, add bounds checks for the sample
coordinate (ensure X-147 >= 0 and Y within image height) and add a small
tolerance when comparing pixel RGB values to account for resolution/antialiasing
differences (e.g., compare absolute difference <= tolerance). Change the
synchronous Task-returning method to use async/await for the inner loop (await
NewRetry.WaitForAction(...)) and wrap the Task.Run delegate with try/finally
where VisionContext.Instance().DrawContent.ClearAll() is called, and ensure any
shared flags (like FightStatusFlag) are accessed in a thread-safe manner
(volatile or Interlocked) as needed.


static double FindMax(double[] numbers)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class CombatScenes : IDisposable
/// <summary>
/// 当前配队
/// </summary>
private Avatar[] Avatars { set; get; } = [];
public Avatar[] Avatars { set; get; } = [];

public int AvatarCount => Avatars.Length;

Expand Down
129 changes: 73 additions & 56 deletions BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -1019,62 +1019,79 @@
</StackPanel>
</ui:CardExpander>
</Grid>
<Separator Margin="-18,0" BorderThickness="0,1,0,0" />
<Grid Margin="16,16,16,13">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ui:TextBlock Grid.Row="0"
Grid.Column="0"
FontTypography="Body"
Text="聚集材料动作"
TextWrapping="Wrap" />
<ui:TextBlock Grid.Row="0"
Grid.Column="0"
Margin="90,0,5,0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
FontSize="10"
FontTypography="Body"
VerticalAlignment="Bottom"
Text="(琴二次拾取:首次拾取空,再次拾取)"
TextWrapping="Wrap" />

<ui:TextBlock Grid.Row="1"
Grid.Column="0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
Text="战斗结束后,如存在(万叶/琴),则执行长E聚集材料动作"
TextWrapping="Wrap" />
<ui:ToggleSwitch Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,10,0"
IsChecked="{Binding Config.AutoFightConfig.KazuhaPickupEnabled, Mode=TwoWay}" />

<ui:ToggleSwitch Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="2"
Margin="0,0,38,0"
IsEnabled="{Binding Config.AutoFightConfig.KazuhaPickupEnabled, Mode=TwoWay}"
IsChecked="{Binding Config.AutoFightConfig.QinDoublePickUp, Mode=TwoWay}" />
<ui:TextBlock Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="8"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
Margin="0,0,38,26"
FontTypography="Body"
Text="琴二次拾取"
TextWrapping="Wrap" />
</Grid>
<Separator Margin="-18,0"
<Separator Margin="-18,0" BorderThickness="0,1,0,0" />

<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ui:TextBlock Grid.Row="0"
Grid.Column="0"
FontTypography="Body"
Text="聚集材料动作"
TextWrapping="Wrap" />
<ui:TextBlock Grid.Row="0"
Grid.Column="0"
Margin="90,0,5,0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
FontSize="10"
FontTypography="Body"
VerticalAlignment="Bottom"
Text="(基于经验:检测精英经验值判断聚集 / 琴二次拾取:首次拾取空,再次拾取)"
TextWrapping="Wrap" />
<ui:TextBlock Grid.Row="1"
Grid.Column="0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
Text="战斗结束后,如存在(万叶/琴),则执行长E聚集材料动作"
TextWrapping="Wrap" />
<ui:ToggleSwitch Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,10,0"
IsChecked="{Binding Config.AutoFightConfig.KazuhaPickupEnabled, Mode=TwoWay}" />
<ui:ToggleSwitch Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="2"
Margin="0,0,10,0"
IsEnabled="{Binding Config.AutoFightConfig.KazuhaPickupEnabled, Mode=TwoWay}"
IsChecked="{Binding Config.AutoFightConfig.ExpKazuhaPickup, Mode=TwoWay}" />
<ui:TextBlock Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="8"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
Margin="0,0,10,26"
FontTypography="Body"
Text="基于经验值"
TextWrapping="Wrap" />
<ui:ToggleSwitch Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="3"
Margin="0,0,38,0"
IsEnabled="{Binding Config.AutoFightConfig.KazuhaPickupEnabled, Mode=TwoWay}"
IsChecked="{Binding Config.AutoFightConfig.QinDoublePickUp, Mode=TwoWay}" />
<ui:TextBlock Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="3"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="8"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
Margin="0,0,38,26"
FontTypography="Body"
Text="琴二次拾取"
TextWrapping="Wrap" />
</Grid>
<Separator Margin="-18,-5,-18,0"
BorderThickness="0,1,0,0" />

<Grid Margin="16,16,16,16">
Expand Down
Loading