实时触发 - 自动吃药(秘境中的自动吃药功能改进作为实时触发功能 (#1993)

Co-authored-by: 辉鸭蛋 <huiyadanli@gmail.com>
This commit is contained in:
xoipz
2025-08-10 13:48:25 +08:00
committed by GitHub
parent 85e8bb7dfd
commit d8ba04f97b
20 changed files with 535 additions and 5 deletions

4
.gitignore vendored
View File

@@ -29,4 +29,6 @@ node_modules/
# Rider
.idea
.trae
.trae
.claude
CLAUDE.md

View File

@@ -163,6 +163,8 @@
<None Update="GameTask\QuickSereniteaPot\Assets\1920x1080\**">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="GameTask\AutoEat\Assets\1920x1080\**">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<None Update="GameTask\QuickBuy\Assets\1920x1080\**">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>

View File

@@ -21,6 +21,7 @@ using BetterGenshinImpact.GameTask.AutoTrackPath;
using BetterGenshinImpact.GameTask.AutoArtifactSalvage;
using BetterGenshinImpact.GameTask.AutoStygianOnslaught;
using BetterGenshinImpact.GameTask.GetGridIcons;
using BetterGenshinImpact.GameTask.AutoEat;
namespace BetterGenshinImpact.Core.Config;
@@ -165,6 +166,11 @@ public partial class AllConfig : ObservableObject
/// </summary>
public AutoArtifactSalvageConfig AutoArtifactSalvageConfig { get; set; } = new();
/// <summary>
/// 自动吃药配置
/// </summary>
public AutoEatConfig AutoEatConfig { get; set; } = new();
/// <summary>
/// 截取物品图标配置
/// </summary>
@@ -247,6 +253,7 @@ public partial class AllConfig : ObservableObject
AutoDomainConfig.PropertyChanged += OnAnyPropertyChanged;
AutoStygianOnslaughtConfig.PropertyChanged += OnAnyPropertyChanged;
AutoArtifactSalvageConfig.PropertyChanged += OnAnyPropertyChanged;
AutoEatConfig.PropertyChanged += OnAnyPropertyChanged;
AutoMusicGameConfig.PropertyChanged += OnAnyPropertyChanged;
TpConfig.PropertyChanged += OnAnyPropertyChanged;
ScriptConfig.PropertyChanged += OnAnyPropertyChanged;

View File

@@ -29,6 +29,10 @@ public partial class PathingConditionConfig : ObservableObject
// 使用小道具的间隔时间(ms)
[ObservableProperty]
private int _useGadgetIntervalMs = 0;
// 启用自动吃药功能
[ObservableProperty]
private bool _autoEatEnabled = true;
public static PathingConditionConfig Default => new()
{

View File

@@ -92,6 +92,10 @@ public partial class PathingPartyConfig : ObservableObject
[ObservableProperty]
private bool _autoRunEnabled = true;
// 启用自动吃药功能
[ObservableProperty]
private bool _autoEatEnabled = true;
//在连续执行时是否隐藏
[ObservableProperty]
private bool _hideOnRepeat = false;
@@ -120,7 +124,8 @@ public partial class PathingPartyConfig : ObservableObject
return new PathingPartyConfig
{
OnlyInTeleportRecover = pathingConditionConfig.OnlyInTeleportRecover,
UseGadgetIntervalMs = pathingConditionConfig.UseGadgetIntervalMs
UseGadgetIntervalMs = pathingConditionConfig.UseGadgetIntervalMs,
AutoEatEnabled = pathingConditionConfig.AutoEatEnabled
};
}
}

View File

@@ -667,11 +667,11 @@ public class AutoDomainTask : ISoloTask
// 对局结束检测
var domainEndTask = DomainEndDetectionTask(cts);
// 自动吃药
var autoEatRecoveryHpTask = AutoEatRecoveryHpTask(cts.Token);
// var autoEatRecoveryHpTask = AutoEatRecoveryHpTask(cts.Token);
combatTask.Start();
domainEndTask.Start();
autoEatRecoveryHpTask.Start();
return Task.WhenAll(combatTask, domainEndTask, autoEatRecoveryHpTask);
// autoEatRecoveryHpTask.Start();
return Task.WhenAll(combatTask, domainEndTask);
}
private void EndFightWait()

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 934 B

View File

@@ -0,0 +1,37 @@
using BetterGenshinImpact.Core.Recognition;
using BetterGenshinImpact.Core.Recognition.OpenCv;
using BetterGenshinImpact.GameTask.Model;
using OpenCvSharp;
namespace BetterGenshinImpact.GameTask.AutoEat.Assets;
public class AutoEatAssets : BaseAssets<AutoEatAssets>
{
public RecognitionObject RecoveryIconRa;
public RecognitionObject ResurrectionIconRa;
private AutoEatAssets()
{
var s = TaskContext.Instance().SystemInfo.AssetScale;
RecoveryIconRa = new RecognitionObject
{
Name = "RecoveryIcon",
RecognitionType = RecognitionTypes.TemplateMatch,
TemplateImageMat = GameTaskManager.LoadAssetImage("AutoEat", "Recovery.png"),
Threshold = 0.8,
RegionOfInterest = new Rect((int)(1810 * s), (int)(778 * s), (int)(23 * s), (int)(23 * s)),
DrawOnWindow = false
}.InitTemplate();
ResurrectionIconRa = new RecognitionObject
{
Name = "ResurrectionIcon",
RecognitionType = RecognitionTypes.TemplateMatch,
TemplateImageMat = GameTaskManager.LoadAssetImage("AutoEat", "Resurrection.png"),
Threshold = 0.8,
RegionOfInterest = new Rect((int)(1810 * s), (int)(778 * s), (int)(18 * s), (int)(19 * s)),
DrawOnWindow = false
}.InitTemplate();
}
}

View File

@@ -0,0 +1,36 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
namespace BetterGenshinImpact.GameTask.AutoEat;
/// <summary>
/// 自动吃药配置
/// </summary>
[Serializable]
public partial class AutoEatConfig : ObservableObject
{
/// <summary>
/// 是否启用自动吃药
/// </summary>
[ObservableProperty]
private bool _enabled = true;
/// <summary>
/// 是否显示吃药通知
/// </summary>
[ObservableProperty]
private bool _showNotification = true;
/// <summary>
/// 检测间隔时间(毫秒)
/// </summary>
[ObservableProperty]
private int _checkInterval = 500;
/// <summary>
/// 吃药间隔时间(毫秒)
/// 防止频繁吃药
/// </summary>
[ObservableProperty]
private int _eatInterval = 2000;
}

View File

@@ -0,0 +1,37 @@
using BetterGenshinImpact.GameTask.Model;
namespace BetterGenshinImpact.GameTask.AutoEat;
/// <summary>
/// 自动吃药任务参数
/// </summary>
public class AutoEatParam : BaseTaskParam
{
/// <summary>
/// 是否显示通知
/// </summary>
public bool ShowNotification { get; set; }
/// <summary>
/// 检测间隔(毫秒)
/// </summary>
public int CheckInterval { get; set; }
/// <summary>
/// 吃药间隔(毫秒)
/// </summary>
public int EatInterval { get; set; }
public AutoEatParam()
{
SetDefault();
}
public void SetDefault()
{
var config = TaskContext.Instance().Config.AutoEatConfig;
ShowNotification = config.ShowNotification;
CheckInterval = config.CheckInterval;
EatInterval = config.EatInterval;
}
}

View File

@@ -0,0 +1,131 @@
using BetterGenshinImpact.Core.Recognition.OCR;
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.Core.Simulator.Extensions;
using BetterGenshinImpact.GameTask.Common.BgiVision;
using BetterGenshinImpact.GameTask.Common.Element.Assets;
using BetterGenshinImpact.GameTask.Model;
using BetterGenshinImpact.Service.Notification;
using BetterGenshinImpact.Service.Notification.Model.Enum;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
using static BetterGenshinImpact.GameTask.Common.TaskControl;
namespace BetterGenshinImpact.GameTask.AutoEat;
/// <summary>
/// 自动吃药任务
/// 检测红血自动使用便携营养袋
/// </summary>
public class AutoEatTask : BaseIndependentTask, ISoloTask
{
public string Name => "自动吃药";
private readonly AutoEatParam _taskParam;
private readonly AutoEatConfig _config;
private CancellationToken _ct;
public AutoEatTask(AutoEatParam taskParam)
{
_taskParam = taskParam;
_config = TaskContext.Instance().Config.AutoEatConfig;
}
public async Task Start(CancellationToken ct)
{
_ct = ct;
Init();
Logger.LogInformation("自动吃药任务启动");
if (!IsTakeFood())
{
Logger.LogWarning("未装备 \"{Tool}\",无法启用自动吃药功能", "便携营养袋");
return;
}
try
{
await AutoEatLoop();
}
catch (Exception e)
{
Logger.LogError(e, "自动吃药任务发生异常");
throw;
}
finally
{
Logger.LogInformation("自动吃药任务结束");
}
}
private void Init()
{
Logger.LogInformation("→ {Text} 检测间隔: {Interval}ms", "自动吃药,", _config.CheckInterval);
Logger.LogInformation("→ {Text} 吃药间隔: {Interval}ms", "自动吃药,", _config.EatInterval);
}
/// <summary>
/// 自动吃药主循环
/// </summary>
private async Task AutoEatLoop()
{
var lastEatTime = DateTime.MinValue;
while (!_ct.IsCancellationRequested)
{
try
{
// 检测当前角色是否红血
if (Bv.CurrentAvatarIsLowHp(CaptureToRectArea()))
{
var now = DateTime.Now;
// 检查是否超过吃药间隔时间,避免重复吃药
if ((now - lastEatTime).TotalMilliseconds >= _config.EatInterval)
{
// 模拟按键 "Z" 使用便携营养袋
Simulation.SendInput.SimulateAction(GIActions.QuickUseGadget);
lastEatTime = now;
Logger.LogInformation("检测到红血,自动吃药");
}
}
await Delay(_config.CheckInterval, _ct);
}
catch (OperationCanceledException)
{
// 任务被取消,正常退出
break;
}
catch (Exception e)
{
Logger.LogDebug(e, "自动吃药检测时发生异常");
await Delay(1000, _ct); // 异常时稍作等待
}
}
}
/// <summary>
/// 检测是否装备了便携营养袋
/// </summary>
private bool IsTakeFood()
{
try
{
// 获取图像
using var ra = CaptureToRectArea();
// 识别道具图标下是否是数字
var s = TaskContext.Instance().SystemInfo.AssetScale;
var countArea = ra.DeriveCrop(1800 * s, 845 * s, 40 * s, 20 * s);
var count = OcrFactory.Paddle.OcrWithoutDetector(countArea.CacheGreyMat);
return int.TryParse(count, out _);
}
catch (Exception e)
{
Logger.LogDebug(e, "检测便携营养袋时发生异常");
return false;
}
}
}

View File

@@ -0,0 +1,139 @@
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.Core.Simulator.Extensions;
using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.GameTask.Common.BgiVision;
using BetterGenshinImpact.GameTask.Model.Area;
using BetterGenshinImpact.GameTask.AutoEat.Assets;
using BetterGenshinImpact.Service.Notification;
using BetterGenshinImpact.Service.Notification.Model.Enum;
using Microsoft.Extensions.Logging;
using System;
using static BetterGenshinImpact.GameTask.Common.TaskControl;
using static Vanara.PInvoke.User32;
namespace BetterGenshinImpact.GameTask.AutoEat;
/// <summary>
/// 自动吃药触发器
/// 检测红血状态时自动使用Recovery.png检测到Resurrection.png时按z复活
/// </summary>
public class AutoEatTrigger : ITaskTrigger
{
private readonly ILogger<AutoEatTrigger> _logger = App.GetLogger<AutoEatTrigger>();
public string Name => "自动吃药";
public bool IsEnabled { get; set; }
public int Priority => 25; // 中等优先级
public bool IsExclusive => false;
private readonly AutoEatConfig _config;
private DateTime _lastRecoveryCheckTime = DateTime.MinValue;
private DateTime _lastResurrectionTime = DateTime.MinValue;
private DateTime _lastEatTime = DateTime.MinValue;
private bool _recoveryDetected = false;
public AutoEatTrigger()
{
_config = TaskContext.Instance().Config.AutoEatConfig;
}
public void Init()
{
IsEnabled = _config.Enabled;
}
public void OnCapture(CaptureContent content)
{
if (!IsEnabled)
return;
try
{
using var ra = TaskControl.CaptureToRectArea();
var now = DateTime.Now;
// 优先检测复活图标添加2秒CD
if (CheckResurrection(ra))
{
// 检查复活CD2秒
if ((now - _lastResurrectionTime).TotalSeconds >= 2)
{
// 按z键复活
Simulation.SendInput.Keyboard.KeyPress(VK.VK_Z);
_lastResurrectionTime = now;
_logger.LogInformation("检测到复活图标,自动复活");
}
return;
}
// 检测角色是否红血
if (Bv.CurrentAvatarIsLowHp(ra))
{
// 检查Recovery缓存是否过期30秒
if ((now - _lastRecoveryCheckTime).TotalSeconds >= 30)
{
_recoveryDetected = false;
_lastRecoveryCheckTime = now;
}
// 如果Recovery还在缓存期内或者重新检测到Recovery
if (_recoveryDetected || CheckRecovery(ra))
{
if (!_recoveryDetected)
{
_recoveryDetected = true;
_lastRecoveryCheckTime = now;
}
// 检查吃药间隔,防止频繁吃药
if ((now - _lastEatTime).TotalMilliseconds >= _config.EatInterval)
{
// 使用便携营养袋
Simulation.SendInput.SimulateAction(GIActions.QuickUseGadget);
_lastEatTime = now;
_logger.LogInformation("检测到红血且Recovery可用自动吃药");
}
}
}
}
catch (Exception e)
{
_logger.LogDebug(e, "自动吃药检测时发生异常");
}
}
/// <summary>
/// 检测Recovery.png图标
/// </summary>
private bool CheckRecovery(ImageRegion imageRegion)
{
try
{
var result = imageRegion.Find(AutoEatAssets.Instance.RecoveryIconRa);
return result.IsExist();
}
catch (Exception e)
{
_logger.LogDebug(e, "检测Recovery图标时发生异常");
return false;
}
}
/// <summary>
/// 检测Resurrection.png图标
/// </summary>
private bool CheckResurrection(ImageRegion imageRegion)
{
try
{
var result = imageRegion.Find(AutoEatAssets.Instance.ResurrectionIconRa);
return result.IsExist();
}
catch (Exception e)
{
_logger.LogDebug(e, "检测Resurrection图标时发生异常");
return false;
}
}
}

View File

@@ -7,6 +7,7 @@ using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Assets;
using BetterGenshinImpact.GameTask.AutoPick.Assets;
using BetterGenshinImpact.GameTask.AutoSkip.Assets;
using BetterGenshinImpact.GameTask.AutoWood.Assets;
using BetterGenshinImpact.GameTask.AutoEat.Assets;
using BetterGenshinImpact.GameTask.Common.Element.Assets;
using BetterGenshinImpact.GameTask.GameLoading;
using BetterGenshinImpact.GameTask.GameLoading.Assets;
@@ -45,6 +46,7 @@ internal class GameTaskManager
TriggerDictionary.TryAdd("AutoSkip", new AutoSkip.AutoSkipTrigger());
TriggerDictionary.TryAdd("AutoFish", new AutoFishing.AutoFishingTrigger());
TriggerDictionary.TryAdd("AutoCook", new AutoCook.AutoCookTrigger());
TriggerDictionary.TryAdd("AutoEat", new AutoEat.AutoEatTrigger());
return ConvertToTriggerList();
}
@@ -94,6 +96,10 @@ internal class GameTaskManager
triggerName = "AutoSkip";
trigger = new AutoSkip.AutoSkipTrigger();
break;
case "AutoEat":
triggerName = "AutoEat";
trigger = new AutoEat.AutoEatTrigger();
break;
}
if (triggerName == null || trigger == null)
@@ -114,6 +120,7 @@ internal class GameTaskManager
TriggerDictionary.GetValueOrDefault("QuickTeleport")?.Init();
// TriggerDictionary.GetValueOrDefault("GameLoading")?.Init();
TriggerDictionary.GetValueOrDefault("AutoCook")?.Init();
TriggerDictionary.GetValueOrDefault("AutoEat")?.Init();
// 清理画布
VisionContext.Instance().DrawContent.ClearAll();
}
@@ -134,6 +141,7 @@ internal class GameTaskManager
QuickSereniteaPotAssets.DestroyInstance();
GameLoadingAssets.DestroyInstance();
MapLazyAssets.DestroyInstance();
AutoEatAssets.DestroyInstance();
}
/// <summary>

View File

@@ -133,6 +133,9 @@ public class TaskRunner
// 清空实时任务触发器
TaskTriggerDispatcher.Instance().ClearTriggers();
// 重新加载基础触发器(包括自动吃药等实时功能)
var basicTriggers = GameTaskManager.LoadInitialTriggers();
TaskTriggerDispatcher.Instance().SetTriggers(basicTriggers);
// 激活原神窗口
var maskWindow = MaskWindow.Instance();

View File

@@ -21,6 +21,9 @@ public class NotificationEvent(string code, string msg)
public static readonly NotificationEvent DailyReward = new("daily.reward", "检查每日奖励领取状态");
public static readonly NotificationEvent JsCustom = new("js.custom", "JS自定义事件");
public static readonly NotificationEvent JsError = new("js.error", "JS运行时错误");
public static readonly NotificationEvent AutoEatStart = new("autoeat.start", "自动吃药启动");
public static readonly NotificationEvent AutoEatEnd = new("autoeat.end", "自动吃药结束");
public static readonly NotificationEvent AutoEatInfo = new("autoeat.info", "自动吃药信息");
public string Code { get; private set; } = code;
public string Msg { get; private set; } = msg;

View File

@@ -1001,6 +1001,7 @@
</Grid>
</StackPanel>
</ui:CardExpander>
<!-- 自动刷本 -->
<ui:CardExpander Margin="0,0,0,12" ContentPadding="0">

View File

@@ -643,6 +643,68 @@
</StackPanel>
</ui:CardExpander>
<!-- 自动吃药 -->
<ui:CardExpander Margin="0,0,0,12" ContentPadding="0">
<ui:CardExpander.Icon>
<ui:FontIcon Glyph="&#xf0f1;" Style="{StaticResource FaFontIconStyle}" />
</ui:CardExpander.Icon>
<ui:CardExpander.Header>
<Grid>
<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}"
TextWrapping="Wrap">
检测角色红血状态,自动使用便携营养袋回复生命值
</ui:TextBlock>
<ui:ToggleSwitch Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,24,0"
IsChecked="{Binding Config.AutoEatConfig.Enabled, Mode=TwoWay}" />
</Grid>
</ui:CardExpander.Header>
<StackPanel>
<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}"
Text="是否在吃药时显示通知消息"
TextWrapping="Wrap" />
<ui:ToggleSwitch Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,36,0"
IsChecked="{Binding Config.AutoEatConfig.ShowNotification, Mode=TwoWay}" />
</Grid>
</StackPanel>
</ui:CardExpander>
<!-- 快速传送 -->
<ui:CardExpander Margin="0,0,0,12" ContentPadding="0">
<ui:CardExpander.Icon>

View File

@@ -231,6 +231,35 @@
TextWrapping="NoWrap" />
</ui:CardControl>
<ui:CardControl Margin="0,0,0,12" >
<ui:CardControl.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<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}"
Text="检测到红血状态时自动使用便携营养袋"
TextWrapping="Wrap" />
</Grid>
</ui:CardControl.Header>
<ui:ToggleSwitch
Margin="0,0,36,0"
IsChecked="{Binding Config.PathingConditionConfig.AutoEatEnabled, Mode=TwoWay}" />
</ui:CardControl>
<ui:CardControl Margin="0,0,0,12" >
<ui:CardControl.Icon>

View File

@@ -75,6 +75,30 @@
Grid.Column="1"
IsChecked="{Binding PathingConfig.AutoPickEnabled}" />
</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}"
Text="检测到红血状态时自动使用便携营养袋"
TextWrapping="Wrap" />
<ui:ToggleSwitch Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
IsChecked="{Binding PathingConfig.AutoEatEnabled}" />
</Grid>
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />