From fe21f945abd28d21c427ad3b53c275db3fd6a76f Mon Sep 17 00:00:00 2001 From: Jamis Date: Mon, 22 Sep 2025 21:42:37 +0800 Subject: [PATCH 01/20] Replace wrongly recognized text (#2249) --- BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs | 6 +++++- BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs | 2 +- BetterGenshinImpact/GameTask/Model/RectArea.cs | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs b/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs index ed0e3171..1c5a9ffc 100644 --- a/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs +++ b/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs @@ -878,7 +878,11 @@ public class TpTask var list = ra.FindMulti(new RecognitionObject { RecognitionType = RecognitionTypes.Ocr, - RegionOfInterest = new Rect(ra.Width / 2, 0, ra.Width / 2, ra.Height) + RegionOfInterest = new Rect(ra.Width / 2, 0, ra.Width / 2, ra.Height), + ReplaceDictionary = new Dictionary + { + ["渊下宫"] = ["渊下宮"], + }, }); string minCountryLocalized = this.stringLocalizer.WithCultureGet(this.cultureInfo, areaName); Region? matchRect = list.OrderByDescending(r => r.Y).FirstOrDefault(r => r.Text.Contains(minCountryLocalized)); diff --git a/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs b/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs index dee3f17c..821e2506 100644 --- a/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs +++ b/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs @@ -193,7 +193,7 @@ public class ImageRegion : Region { foreach (var replaceStr in entry.Value) { - text = text.Replace(entry.Key, replaceStr); + text = text.Replace(replaceStr, entry.Key); } } diff --git a/BetterGenshinImpact/GameTask/Model/RectArea.cs b/BetterGenshinImpact/GameTask/Model/RectArea.cs index 563dd32e..1457258a 100644 --- a/BetterGenshinImpact/GameTask/Model/RectArea.cs +++ b/BetterGenshinImpact/GameTask/Model/RectArea.cs @@ -305,7 +305,7 @@ // { // foreach (var replaceStr in entry.Value) // { -// text = text.Replace(entry.Key, replaceStr); +// text = text.Replace(replaceStr, entry.Key); // } // } // From 6f2b9ae95f3b0b3721e83bb5e33b72eda85054cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Mon, 22 Sep 2025 21:56:32 +0800 Subject: [PATCH 02/20] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=B9=BD=E5=A2=83?= =?UTF-8?q?=E5=8D=B1=E6=88=98=EF=BC=8C=E5=BB=B6=E9=95=BF=E4=BC=A0=E9=80=81?= =?UTF-8?q?=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs b/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs index eeaac5f6..5e28903d 100644 --- a/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs +++ b/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs @@ -223,7 +223,7 @@ public class AutoStygianOnslaughtTask : ISoloTask await Delay(800, _ct); // 等待传送完成 - await page.Locator(ElementAssets.Instance.PaimonMenuRo).WaitFor(); + await page.Locator(ElementAssets.Instance.PaimonMenuRo).WaitFor(60000); _logger.LogInformation($"{Name}:传送完成"); await Delay(2000, _ct); From 2ecacc0eeb08a539202fb238d2fb1b8aef432bc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Tue, 23 Sep 2025 00:34:33 +0800 Subject: [PATCH 03/20] =?UTF-8?q?=E7=AD=89=E5=BE=85=E8=BF=9B=E5=85=A5?= =?UTF-8?q?=E4=B8=BB=E7=95=8C=E9=9D=A2=E5=90=8E=E6=89=A7=E8=A1=8C=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/Service/ScriptService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/BetterGenshinImpact/Service/ScriptService.cs b/BetterGenshinImpact/Service/ScriptService.cs index c8986dc0..d0c8e453 100644 --- a/BetterGenshinImpact/Service/ScriptService.cs +++ b/BetterGenshinImpact/Service/ScriptService.cs @@ -578,11 +578,12 @@ public partial class ScriptService : IScriptService { if (!homePageViewModel.TaskDispatcherEnabled || !TaskContext.Instance().IsInitialized) { + await Task.Delay(500); continue; } var content = TaskControl.CaptureToRectArea(); - if (Bv.IsInMainUi(content) || Bv.IsInAnyClosableUi(content)) + if (Bv.IsInMainUi(content) || Bv.IsInAnyClosableUi(content) || Bv.IsInDomain(content)) { return; } From 99502ed35b68677faa8e10875f7edbbb3726338d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Tue, 23 Sep 2025 00:47:57 +0800 Subject: [PATCH 04/20] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=BF=AB=E9=80=9F?= =?UTF-8?q?=E8=B4=AD=E4=B9=B0=E6=97=B6=E5=80=99=EF=BC=8C=E5=AF=B9=E6=B4=9E?= =?UTF-8?q?=E5=A4=A9=E7=9A=84=E8=AF=86=E5=88=AB=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GameTask/QuickBuy/Assets/QuickBuyAssets.cs | 5 +++-- BetterGenshinImpact/GameTask/QuickBuy/QuickBuyTask.cs | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/BetterGenshinImpact/GameTask/QuickBuy/Assets/QuickBuyAssets.cs b/BetterGenshinImpact/GameTask/QuickBuy/Assets/QuickBuyAssets.cs index a715f045..5954b66c 100644 --- a/BetterGenshinImpact/GameTask/QuickBuy/Assets/QuickBuyAssets.cs +++ b/BetterGenshinImpact/GameTask/QuickBuy/Assets/QuickBuyAssets.cs @@ -16,8 +16,9 @@ public class QuickBuyAssets : BaseAssets Name = "SereniteaPotCoin", RecognitionType = RecognitionTypes.TemplateMatch, TemplateImageMat = GameTaskManager.LoadAssetImage("QuickBuy", "SereniteaPotCoin.png"), - RegionOfInterest = new Rect((int)(1620 * AssetScale),(int)(30 * AssetScale),(int)(50 * AssetScale),(int)(40 * AssetScale)), - DrawOnWindow = false + RegionOfInterest = new Rect((int)(1610 * AssetScale),(int)(28 * AssetScale),(int)(160 * AssetScale),(int)(45 * AssetScale)), + Use3Channels = true, + DrawOnWindow = true }.InitTemplate(); } diff --git a/BetterGenshinImpact/GameTask/QuickBuy/QuickBuyTask.cs b/BetterGenshinImpact/GameTask/QuickBuy/QuickBuyTask.cs index c740165d..b38fb168 100644 --- a/BetterGenshinImpact/GameTask/QuickBuy/QuickBuyTask.cs +++ b/BetterGenshinImpact/GameTask/QuickBuy/QuickBuyTask.cs @@ -3,6 +3,7 @@ using BetterGenshinImpact.Core.Simulator; using BetterGenshinImpact.GameTask.Common; using BetterGenshinImpact.GameTask.Model.Area; using BetterGenshinImpact.GameTask.QuickBuy.Assets; +using BetterGenshinImpact.View.Drawable; using Microsoft.Extensions.Logging; using Wpf.Ui.Violeta.Controls; @@ -52,6 +53,7 @@ public class QuickBuyTask return; } + // 点击购买/兑换 右下225x60 GameCaptureRegion.GameRegionClick((size, scale) => (size.Width - 225 * scale, size.Height - 60 * scale)); TaskControl.CheckAndSleep(100); // 等待窗口弹出 @@ -78,5 +80,9 @@ public class QuickBuyTask { TaskControl.Logger.LogWarning(e.Message); } + finally + { + VisionContext.Instance().DrawContent.ClearAll(); + } } } From 94e50307a5d2403966402d7c1059d449871b9548 Mon Sep 17 00:00:00 2001 From: bhbghghbgb <113711814+bhbghghbgb@users.noreply.github.com> Date: Tue, 23 Sep 2025 00:59:07 +0800 Subject: [PATCH 05/20] =?UTF-8?q?=E9=87=8D=E6=9E=84=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8=E9=87=8D=E7=BD=AE=E6=97=B6=E9=97=B4=E5=A4=84=E7=90=86?= =?UTF-8?q?=20(#2160)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/App.xaml.cs | 4 + .../Core/Config/OneDragonFlowConfig.cs | 5 +- .../Core/Config/OtherConfig.cs | 4 + .../Config/PathingPartyTaskCycleConfig.cs | 15 ++- .../Config/TaskCompletionSkipRuleConfig.cs | 3 + .../Core/Script/Dependence/ServerTime.cs | 19 +++ .../Core/Script/EngineExtend.cs | 3 +- .../Core/Script/Group/ScriptGroupProject.cs | 9 ++ .../GameTask/AutoDomain/AutoDomainTask.cs | 4 +- .../Common/Job/BlessingOfTheWelkinMoonTask.cs | 4 +- .../Common/Job/GoToSereniteaPotTask.cs | 4 +- .../FarmingPlan/FarmingStatsRecorder.cs | 7 +- .../GameTask/LogParse/ExecutionRecord.cs | 17 ++- .../LogParse/ExecutionRecordStorage.cs | 19 ++- .../LogParse/TravelsDiaryDetailManager.cs | 24 ++-- .../Helpers/ServerTimeHelper.cs | 117 ++++++++++++++++++ BetterGenshinImpact/Service/ScriptService.cs | 2 +- .../View/Pages/CommonSettingsPage.xaml | 29 +++++ .../Pages/View/ScriptGroupConfigView.xaml | 50 ++++++++ .../Pages/CommonSettingsPageViewModel.cs | 21 +++- .../Pages/View/ScriptGroupConfigViewModel.cs | 2 +- 21 files changed, 323 insertions(+), 39 deletions(-) create mode 100644 BetterGenshinImpact/Core/Script/Dependence/ServerTime.cs create mode 100644 BetterGenshinImpact/Helpers/ServerTimeHelper.cs diff --git a/BetterGenshinImpact/App.xaml.cs b/BetterGenshinImpact/App.xaml.cs index 95abc1c7..6c755e03 100644 --- a/BetterGenshinImpact/App.xaml.cs +++ b/BetterGenshinImpact/App.xaml.cs @@ -134,6 +134,9 @@ public partial class App : Application services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + + services.AddSingleton(TimeProvider.System); + services.AddSingleton(); // Configuration //services.Configure(context.Configuration.GetSection(nameof(AppConfig))); @@ -181,6 +184,7 @@ public partial class App : Application ConsoleHelper.AllocateConsole("BetterGI Console"); RegisterEvents(); await _host.StartAsync(); + ServerTimeHelper.Initialize(_host.Services.GetRequiredService()); await UrlProtocolHelper.RegisterAsync(); } catch (Exception ex) diff --git a/BetterGenshinImpact/Core/Config/OneDragonFlowConfig.cs b/BetterGenshinImpact/Core/Config/OneDragonFlowConfig.cs index 8c381ca4..9af3c053 100644 --- a/BetterGenshinImpact/Core/Config/OneDragonFlowConfig.cs +++ b/BetterGenshinImpact/Core/Config/OneDragonFlowConfig.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using BetterGenshinImpact.GameTask; +using BetterGenshinImpact.Helpers; using CommunityToolkit.Mvvm.ComponentModel; namespace BetterGenshinImpact.Core.Config; @@ -120,7 +122,8 @@ public partial class OneDragonFlowConfig : ObservableObject { if (WeeklyDomainEnabled) { - var dayOfWeek = (DateTime.Now.Hour >= 4 ? DateTime.Today : DateTime.Today.AddDays(-1)).DayOfWeek; + var serverTime = ServerTimeHelper.GetServerTimeNow(); + var dayOfWeek = (serverTime.Hour >= 4 ? serverTime : serverTime.AddDays(-1)).DayOfWeek; return dayOfWeek switch { DayOfWeek.Monday => (MondayPartyName, MondayDomainName,SundaySelectedValue), diff --git a/BetterGenshinImpact/Core/Config/OtherConfig.cs b/BetterGenshinImpact/Core/Config/OtherConfig.cs index 9ebc64d5..491e6888 100644 --- a/BetterGenshinImpact/Core/Config/OtherConfig.cs +++ b/BetterGenshinImpact/Core/Config/OtherConfig.cs @@ -1,5 +1,6 @@ using System; using BetterGenshinImpact.Core.Recognition; +using BetterGenshinImpact.Model; using CommunityToolkit.Mvvm.ComponentModel; namespace BetterGenshinImpact.Core.Config; @@ -14,6 +15,9 @@ public partial class OtherConfig : ObservableObject //自动领取派遣任务城市 [ObservableProperty] private string _autoFetchDispatchAdventurersGuildCountry = "无"; + //服务器时区偏移量 + [ObservableProperty] + private TimeSpan _serverTimeZoneOffset = TimeSpan.FromHours(8); [ObservableProperty] private AutoRestart _autoRestartConfig = new(); //锄地规划 diff --git a/BetterGenshinImpact/Core/Config/PathingPartyTaskCycleConfig.cs b/BetterGenshinImpact/Core/Config/PathingPartyTaskCycleConfig.cs index b7ee8ec8..b2564caa 100644 --- a/BetterGenshinImpact/Core/Config/PathingPartyTaskCycleConfig.cs +++ b/BetterGenshinImpact/Core/Config/PathingPartyTaskCycleConfig.cs @@ -1,4 +1,5 @@ using System; +using BetterGenshinImpact.Helpers; using CommunityToolkit.Mvvm.ComponentModel; namespace BetterGenshinImpact.Core.Config; @@ -10,9 +11,13 @@ public partial class PathingPartyTaskCycleConfig : ObservableObject [ObservableProperty] private bool _enable = false; - //周期分界时间点 + //周期分界时间点(小时),如果负数则不启用 [ObservableProperty] private int _boundaryTime = 0; + // 分界时间是否基于服务器时间(否则基于本地时间) + [ObservableProperty] + private bool _isBoundaryTimeBasedOnServerTime = false; + //不同材料有不同的周期,按需配置,如矿石类是3、突破材料是2,或按照自已想几天执行一次配置即可 [ObservableProperty] @@ -25,7 +30,7 @@ public partial class PathingPartyTaskCycleConfig : ObservableObject private int _index = 1; - public int GetExecutionOrder(DateTime now) + public int GetExecutionOrder(DateTimeOffset now) { try { @@ -55,5 +60,11 @@ public partial class PathingPartyTaskCycleConfig : ObservableObject } } + public int GetExecutionOrder() + { + return GetExecutionOrder(IsBoundaryTimeBasedOnServerTime + ? ServerTimeHelper.GetServerTimeNow() + : DateTimeOffset.Now); + } } \ No newline at end of file diff --git a/BetterGenshinImpact/Core/Config/TaskCompletionSkipRuleConfig.cs b/BetterGenshinImpact/Core/Config/TaskCompletionSkipRuleConfig.cs index c1c78aeb..d3a1dd74 100644 --- a/BetterGenshinImpact/Core/Config/TaskCompletionSkipRuleConfig.cs +++ b/BetterGenshinImpact/Core/Config/TaskCompletionSkipRuleConfig.cs @@ -19,6 +19,9 @@ public partial class TaskCompletionSkipRuleConfig:ObservableObject //周期分界时间点,如果负数则不启用,主要适用于固定时间的刷新物品适用 [ObservableProperty] private int _boundaryTime = 4; + // 分界时间是否基于服务器时间(否则基于本地时间) + [ObservableProperty] + private bool _isBoundaryTimeBasedOnServerTime = false; //上一次执行间隔时间,出于精度考虑,这里使用秒为单位 [ObservableProperty] diff --git a/BetterGenshinImpact/Core/Script/Dependence/ServerTime.cs b/BetterGenshinImpact/Core/Script/Dependence/ServerTime.cs new file mode 100644 index 00000000..618cff0e --- /dev/null +++ b/BetterGenshinImpact/Core/Script/Dependence/ServerTime.cs @@ -0,0 +1,19 @@ +using System; +using BetterGenshinImpact.Helpers; + +namespace BetterGenshinImpact.Core.Script.Dependence; + +public static class ServerTime +{ + /// + /// 获取服务器时区偏移量 + /// + /// + /// 以毫秒为单位的偏移量 + /// 该值可直接在JavaScript中使用:`new Date(Date.now() + offset)` + /// + public static int GetServerTimeZoneOffset() + { + return (int)ServerTimeHelper.GetServerTimeOffset().TotalMilliseconds; + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Core/Script/EngineExtend.cs b/BetterGenshinImpact/Core/Script/EngineExtend.cs index 22a3f397..54b91c57 100644 --- a/BetterGenshinImpact/Core/Script/EngineExtend.cs +++ b/BetterGenshinImpact/Core/Script/EngineExtend.cs @@ -62,7 +62,8 @@ public class EngineExtend engine.AddHostObject("OpenCvSharp", new HostTypeCollection("OpenCvSharp")); - + engine.AddHostType("ServerTime", typeof(ServerTime)); + // 添加C#的类型 diff --git a/BetterGenshinImpact/Core/Script/Group/ScriptGroupProject.cs b/BetterGenshinImpact/Core/Script/Group/ScriptGroupProject.cs index 4d55a5fb..be1bf101 100644 --- a/BetterGenshinImpact/Core/Script/Group/ScriptGroupProject.cs +++ b/BetterGenshinImpact/Core/Script/Group/ScriptGroupProject.cs @@ -19,6 +19,7 @@ using BetterGenshinImpact.GameTask.AutoPathing.Model.Enum; using BetterGenshinImpact.GameTask.Common; using BetterGenshinImpact.GameTask.FarmingPlan; using BetterGenshinImpact.GameTask.LogParse; +using BetterGenshinImpact.Helpers; using Microsoft.Extensions.Logging; namespace BetterGenshinImpact.Core.Script.Group; @@ -167,6 +168,10 @@ public partial class ScriptGroupProject : ObservableObject //执行记录 ExecutionRecord executionRecord = new ExecutionRecord() { + ServerStartTime = + GroupInfo?.Config.PathingConfig.TaskCompletionSkipRuleConfig.IsBoundaryTimeBasedOnServerTime ?? false + ? ServerTimeHelper.GetServerTimeNow() + : DateTimeOffset.Now, StartTime = DateTime.Now, GroupName = GroupInfo?.Name ?? "", FolderName = FolderName, @@ -280,6 +285,10 @@ public partial class ScriptGroupProject : ObservableObject executionRecord.IsSuccessful = true; } + executionRecord.ServerEndTime = + GroupInfo?.Config.PathingConfig.TaskCompletionSkipRuleConfig.IsBoundaryTimeBasedOnServerTime ?? false + ? ServerTimeHelper.GetServerTimeNow() + : DateTimeOffset.Now; executionRecord.EndTime = DateTime.Now; ExecutionRecordStorage.SaveExecutionRecord(executionRecord); } diff --git a/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs b/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs index 293a606b..3bbec55b 100644 --- a/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs +++ b/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs @@ -390,8 +390,8 @@ public class AutoDomainTask : ISoloTask Logger.LogInformation("自动秘境:{Text}", "检测到秘境限时全开"); } - DateTime now = DateTime.Now; - if ((now.DayOfWeek == DayOfWeek.Sunday && now.Hour >= 4 || now.DayOfWeek == DayOfWeek.Monday && now.Hour < 4) || limitedFullyStringRaocrListdone != null) + var serverTime = ServerTimeHelper.GetServerTimeNow(); + if (serverTime is { DayOfWeek: DayOfWeek.Sunday, Hour: >= 4 } || serverTime is { DayOfWeek: DayOfWeek.Monday, Hour: < 4 } || limitedFullyStringRaocrListdone != null) { using var artifactArea = CaptureToRectArea().Find(fightAssets.ArtifactAreaRa); //检测是否为圣遗物副本 if (artifactArea.IsEmpty()) diff --git a/BetterGenshinImpact/GameTask/Common/Job/BlessingOfTheWelkinMoonTask.cs b/BetterGenshinImpact/GameTask/Common/Job/BlessingOfTheWelkinMoonTask.cs index c85d7546..1b58fdf7 100644 --- a/BetterGenshinImpact/GameTask/Common/Job/BlessingOfTheWelkinMoonTask.cs +++ b/BetterGenshinImpact/GameTask/Common/Job/BlessingOfTheWelkinMoonTask.cs @@ -3,6 +3,7 @@ using System; using System.Threading; using System.Threading.Tasks; using BetterGenshinImpact.GameTask.Model.Area; +using BetterGenshinImpact.Helpers; using Microsoft.Extensions.Logging; using static BetterGenshinImpact.GameTask.Common.TaskControl; @@ -15,13 +16,12 @@ public class BlessingOfTheWelkinMoonTask { public string Name => "自动点击空月祝福"; - public async Task Start(CancellationToken ct) { try { // 4点全程触发 - if (DateTime.Now.Hour == 4) + if (ServerTimeHelper.GetServerTimeNow().Hour == 4) { using var ra = CaptureToRectArea(); if (Bv.IsInBlessingOfTheWelkinMoon(ra)) diff --git a/BetterGenshinImpact/GameTask/Common/Job/GoToSereniteaPotTask.cs b/BetterGenshinImpact/GameTask/Common/Job/GoToSereniteaPotTask.cs index f483b903..b618d601 100644 --- a/BetterGenshinImpact/GameTask/Common/Job/GoToSereniteaPotTask.cs +++ b/BetterGenshinImpact/GameTask/Common/Job/GoToSereniteaPotTask.cs @@ -476,8 +476,8 @@ internal class GoToSereniteaPotTask Logger.LogInformation("领取尘歌壶奖励:{text}", "未配置购买商店物品"); return; } - DateTime now = DateTime.Now; - DayOfWeek currentDayOfWeek = now.Hour >= 4 ? now.DayOfWeek : now.AddDays(-1).DayOfWeek; + DateTimeOffset serverTime = ServerTimeHelper.GetServerTimeNow(); + DayOfWeek currentDayOfWeek = serverTime.Hour >= 4 ? serverTime.DayOfWeek : serverTime.AddDays(-1).DayOfWeek; DayOfWeek? configDayOfWeek = GetDayOfWeekFromConfig(SelectedConfig.SecretTreasureObjects.First()); if (configDayOfWeek.HasValue || SelectedConfig.SecretTreasureObjects.First() == "每天重复" && SelectedConfig.SecretTreasureObjects.Count > 1) { diff --git a/BetterGenshinImpact/GameTask/FarmingPlan/FarmingStatsRecorder.cs b/BetterGenshinImpact/GameTask/FarmingPlan/FarmingStatsRecorder.cs index 1aac9aea..42a5039a 100644 --- a/BetterGenshinImpact/GameTask/FarmingPlan/FarmingStatsRecorder.cs +++ b/BetterGenshinImpact/GameTask/FarmingPlan/FarmingStatsRecorder.cs @@ -8,6 +8,7 @@ using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.GameTask.AutoPathing.Model; using BetterGenshinImpact.GameTask.Common; using BetterGenshinImpact.GameTask.LogParse; +using BetterGenshinImpact.Helpers; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -212,8 +213,8 @@ public static class FarmingStatsRecorder public static DailyFarmingData ReadDailyFarmingData() { // 确定统计日期(以凌晨4点为分界) - DateTime now = DateTime.Now; - DateTime statsDate = CalculateStatsDate(now); + DateTimeOffset now = ServerTimeHelper.GetServerTimeNow(); + DateTimeOffset statsDate = CalculateStatsDate(now); string dateString = statsDate.ToString("yyyyMMdd"); // 确保目录存在 @@ -228,7 +229,7 @@ public static class FarmingStatsRecorder /// /// 计算统计日期(凌晨4点为分界) /// - private static DateTime CalculateStatsDate(DateTime currentTime) + private static DateTime CalculateStatsDate(DateTimeOffset currentTime) { // 如果当前时间在4点之前,则算作前一天 return currentTime.Hour < 4 ? currentTime.Date.AddDays(-1) : currentTime.Date; diff --git a/BetterGenshinImpact/GameTask/LogParse/ExecutionRecord.cs b/BetterGenshinImpact/GameTask/LogParse/ExecutionRecord.cs index b4447858..66838028 100644 --- a/BetterGenshinImpact/GameTask/LogParse/ExecutionRecord.cs +++ b/BetterGenshinImpact/GameTask/LogParse/ExecutionRecord.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.Serialization; using Newtonsoft.Json; namespace BetterGenshinImpact.GameTask.LogParse; @@ -12,13 +13,25 @@ public class ExecutionRecord [JsonProperty("type")] public string Type = string.Empty; /// - /// 执行开始时间 + /// 执行开始时间(服务器时间,包含时区信息) + /// + [JsonProperty("server_start_time")] + public DateTimeOffset? ServerStartTime { get; set; } + + /// + /// 执行开始时间的本地时间表示(用于向后兼容的统计显示) /// [JsonProperty("start_time")] public DateTime StartTime { get; set; } /// - /// 执行结束时间 + /// 执行结束时间(服务器时间,包含时区信息) + /// + [JsonProperty("server_end_time")] + public DateTimeOffset? ServerEndTime { get; set; } + + /// + /// 执行结束时间的本地时间表示(用于向后兼容的统计显示) /// [JsonProperty("end_time")] public DateTime EndTime { get; set; } diff --git a/BetterGenshinImpact/GameTask/LogParse/ExecutionRecordStorage.cs b/BetterGenshinImpact/GameTask/LogParse/ExecutionRecordStorage.cs index 83470d89..93d01099 100644 --- a/BetterGenshinImpact/GameTask/LogParse/ExecutionRecordStorage.cs +++ b/BetterGenshinImpact/GameTask/LogParse/ExecutionRecordStorage.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.Core.Script.Group; +using BetterGenshinImpact.Helpers; using Newtonsoft.Json; namespace BetterGenshinImpact.GameTask.LogParse; @@ -140,16 +141,19 @@ public class ExecutionRecordStorage /// /// 根据自定义的一天开始时间判断日期是否属于"今天" /// - /// 分界时间(小时),0-23之间 - /// 要判断的日期 - /// 如果属于"今天"则返回true,否则返回false - private static bool IsTodayByBoundary(int boundaryHour, DateTime targetDate) + /// 分界时间(小时),0-23之间,基于参数决定的时间参考系 + /// 要判断的日期(本地时间) + /// true表示使用服务器时间计算,false表示使用本地时间计算 + /// 如果目标日期在根据分界时间定义的"今天"范围内则返回true,否则返回false + private static bool IsTodayByBoundary(int boundaryHour, DateTimeOffset targetDate, bool isBoundaryTimeBasedOnServerTime) { // 验证分界时间是否有效 if (boundaryHour < 0 || boundaryHour > 23) throw new ArgumentOutOfRangeException(nameof(boundaryHour), "分界时间必须在0-23之间"); - DateTime now = DateTime.Now; + DateTimeOffset now = isBoundaryTimeBasedOnServerTime + ? ServerTimeHelper.GetServerTimeNow() + : DateTimeOffset.Now; // 计算今天的开始时间(根据分界时间) DateTime todayStart; @@ -237,7 +241,10 @@ public class ExecutionRecordStorage if (boundaryTimeEnable) { // 如果记录不在"今天",则跳过 - if (!IsTodayByBoundary(config.BoundaryTime, record.StartTime)) continue; + if (!IsTodayByBoundary(config.BoundaryTime, + record.ServerStartTime ?? + new DateTimeOffset(record.StartTime).ToOffset(ServerTimeHelper.GetServerTimeOffset()), + config.IsBoundaryTimeBasedOnServerTime)) continue; } bool isMatchFound = false; diff --git a/BetterGenshinImpact/GameTask/LogParse/TravelsDiaryDetailManager.cs b/BetterGenshinImpact/GameTask/LogParse/TravelsDiaryDetailManager.cs index ee2c40d7..ec163479 100644 --- a/BetterGenshinImpact/GameTask/LogParse/TravelsDiaryDetailManager.cs +++ b/BetterGenshinImpact/GameTask/LogParse/TravelsDiaryDetailManager.cs @@ -6,6 +6,7 @@ using System.Text.Json; using System.Threading.Tasks; using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.GameTask.Common; +using BetterGenshinImpact.Helpers; using Microsoft.Extensions.Logging; using Wpf.Ui.Violeta.Controls; @@ -67,14 +68,14 @@ public class TravelsDiaryDetailManager } private static List<(int year, int month)> GetMonthPairs() { - DateTime now = DateTime.Now; + DateTimeOffset now = ServerTimeHelper.GetServerTimeNow(); List<(int year, int month)> result = new List<(int, int)>(); if (now.Day == 1 && now.Hour < 4) { // 上个月 - DateTime lastMonth = now.AddMonths(-1); + DateTimeOffset lastMonth = now.AddMonths(-1); result.Add((lastMonth.Year, lastMonth.Month)); } @@ -90,10 +91,10 @@ public class TravelsDiaryDetailManager { //正序的 var sortedList = loadAllActionItems(gameInfo, GetMonthPairs()); - DateTime now = DateTime.Now; - DateTime today4am = now.Date.AddHours(4); + DateTimeOffset now = ServerTimeHelper.GetServerTimeNow(); + DateTimeOffset today4am = new DateTimeOffset(now.Year, now.Month, now.Day, 4, 0, 0, now.Offset); - DateTime startTime, endTime; + DateTimeOffset startTime, endTime; if (now < today4am) { @@ -231,11 +232,14 @@ public class TravelsDiaryDetailManager throw new FileNotFoundException("文件未找到", filePath); } - DateTime lastModified = File.GetLastWriteTime(filePath); - + // File.GetLastWriteTime 返回 DateTime 类型为 DateTimeKind.Local + DateTimeOffset lastModified = + new DateTimeOffset(File.GetLastWriteTime(filePath)).ToOffset(ServerTimeHelper.GetServerTimeOffset()); + // 获取当前月份的开始和结束日期 - DateTime startOfMonth = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1); - DateTime endOfMonth = startOfMonth.AddMonths(1).AddDays(-1); + DateTimeOffset now = ServerTimeHelper.GetServerTimeNow(); + DateTimeOffset startOfMonth = new DateTimeOffset(now.Year, now.Month, 1, 0, 0, 0, now.Offset); + DateTimeOffset endOfMonth = startOfMonth.AddMonths(1).AddDays(-1); // 判断文件最后修改时间是否在本月 return lastModified >= startOfMonth && lastModified <= endOfMonth; @@ -244,7 +248,7 @@ public class TravelsDiaryDetailManager static List<(int year, int month)> GetCurrentAndPreviousTwoMonths() { List<(int year, int month)> months = new List<(int year, int month)>(); - DateTime now = DateTime.Now; + DateTimeOffset now = ServerTimeHelper.GetServerTimeNow(); for (int i = 0; i < 3; i++) { diff --git a/BetterGenshinImpact/Helpers/ServerTimeHelper.cs b/BetterGenshinImpact/Helpers/ServerTimeHelper.cs new file mode 100644 index 00000000..c281fd88 --- /dev/null +++ b/BetterGenshinImpact/Helpers/ServerTimeHelper.cs @@ -0,0 +1,117 @@ +using System; +using BetterGenshinImpact.GameTask; + +namespace BetterGenshinImpact.Helpers; + +/// +/// 提供配置的服务器时区的当前时间 +/// +public interface IServerTimeProvider +{ + /// + /// 获取调整到服务器时区偏移量的当前时间 + /// + /// 表示当前服务器时间的 + DateTimeOffset GetServerTimeNow(); + + /// + /// 获取服务器时区偏移量 + /// + /// 表示服务器时区偏移量的 + TimeSpan GetServerTimeOffset(); +} + +/// +/// +/// 此实现使用 获取当前UTC时间, +/// 然后应用配置的服务器时区偏移量 +/// +public class ServerTimeProvider : IServerTimeProvider +{ + private readonly TimeProvider _timeProvider; + + /// + /// 初始化 类的新实例 + /// + /// 用于获取基准UTC时间的 + public ServerTimeProvider(TimeProvider timeProvider) + { + _timeProvider = timeProvider; + } + + /// + public DateTimeOffset GetServerTimeNow() + { + var serverOffset = GetServerTimeOffset(); + return _timeProvider.GetUtcNow().ToOffset(serverOffset); + } + + public TimeSpan GetServerTimeOffset() + { + try + { + return TaskContext.Instance().Config.OtherConfig.ServerTimeZoneOffset; + } + // throw new Exception("Config未初始化"); in TaskContext.cs + catch (Exception) + { + // 如果配置未加载,假定为北京时间用于核心开发者测试 + return TimeSpan.FromHours(8); + } + } +} + +/// +/// 提供静态外观以便轻松访问服务器时间 +/// +public static class ServerTimeHelper +{ + private static IServerTimeProvider? _serverTimeProvider; + + /// + /// 使用具体的 实现初始化静态辅助类 + /// 此方法必须在应用程序启动期间调用一次 + /// + /// 用于检索服务器时间的提供程序 + /// 为 null 时抛出 + public static void Initialize(IServerTimeProvider serverTimeProvider) + { + _serverTimeProvider = serverTimeProvider ?? throw new ArgumentNullException(nameof(serverTimeProvider)); + } + + /// + /// 获取调整到服务器时区偏移量的当前时间 + /// + /// 表示当前服务器时间的 + /// + /// 如果尚未调用 则抛出 + /// + public static DateTimeOffset GetServerTimeNow() + { + if (_serverTimeProvider is null) + { + throw new InvalidOperationException( + $"{nameof(ServerTimeHelper)} 尚未初始化。请先调用 {nameof(Initialize)}。"); + } + + return _serverTimeProvider.GetServerTimeNow(); + } + + /// + /// 获取服务器时区偏移量 + /// + /// 表示服务器时区偏移量的 + /// + /// 如果尚未调用 则抛出 + /// + public static TimeSpan GetServerTimeOffset() + { + if (_serverTimeProvider is null) + { + throw new InvalidOperationException( + $"{nameof(ServerTimeHelper)} 尚未初始化。请先调用 {nameof(Initialize)}。"); + } + + return _serverTimeProvider.GetServerTimeOffset(); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/ScriptService.cs b/BetterGenshinImpact/Service/ScriptService.cs index d0c8e453..29285ced 100644 --- a/BetterGenshinImpact/Service/ScriptService.cs +++ b/BetterGenshinImpact/Service/ScriptService.cs @@ -63,7 +63,7 @@ public partial class ScriptService : IScriptService var tcc = project.GroupInfo.Config.PathingConfig.TaskCycleConfig; if (tcc.Enable) { - int index = tcc.GetExecutionOrder(DateTime.Now); + int index = tcc.GetExecutionOrder(); if (index == -1) { _logger.LogInformation($"{project.Name}周期配置参数错误,配置将不生效,任务正常执行!"); diff --git a/BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml b/BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml index 9f7525bb..f79df523 100644 --- a/BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml +++ b/BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml @@ -1026,6 +1026,35 @@ Margin="0,0,36,0" IsChecked="{Binding Config.OtherConfig.RestoreFocusOnLostEnabled, Mode=TwoWay}" />--> + + + + + + + + + + + + + diff --git a/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml b/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml index f7c98998..2981aa3a 100644 --- a/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml +++ b/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml @@ -416,6 +416,31 @@ MinWidth="120" Text="{Binding PathingConfig.TaskCycleConfig.BoundaryTime}" /> + + + + + + + + + + + + + @@ -591,6 +616,31 @@ MinWidth="120" Text="{Binding PathingConfig.TaskCompletionSkipRuleConfig.BoundaryTime}" /> + + + + + + + + + + + + + diff --git a/BetterGenshinImpact/ViewModel/Pages/CommonSettingsPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/CommonSettingsPageViewModel.cs index 9c80c25d..d52622cb 100644 --- a/BetterGenshinImpact/ViewModel/Pages/CommonSettingsPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/CommonSettingsPageViewModel.cs @@ -50,6 +50,13 @@ public partial class CommonSettingsPageViewModel : ViewModel private string _selectedCountry = string.Empty; [ObservableProperty] private List _adventurersGuildCountry = ["无", "枫丹", "稻妻", "璃月", "蒙德"]; + + [ObservableProperty] private List> _serverTimeZones = + [ + Tuple.Create(TimeSpan.FromHours(8), "其他 UTC+08"), + Tuple.Create(TimeSpan.FromHours(1), "欧服 UTC+01"), + Tuple.Create(TimeSpan.FromHours(-5), "美服 UTC-05") + ]; public CommonSettingsPageViewModel(IConfigService configService, INavigationService navigationService, NotificationService notificationService) @@ -66,7 +73,7 @@ public partial class CommonSettingsPageViewModel : ViewModel public AllConfig Config { get; set; } public ObservableCollection CountryList { get; } = new(); public ObservableCollection Areas { get; } = new(); - + public ObservableCollection MapPathingTypes { get; } = ["SIFT", "TemplateMatch"]; [ObservableProperty] private FrozenDictionary _languageDict = @@ -106,11 +113,11 @@ public partial class CommonSettingsPageViewModel : ViewModel } } } - - public ObservableCollection PaddleOcrModelConfigs { get; } = new(Enum.GetValues(typeof(PaddleOcrModelConfig)).Cast()); - [ObservableProperty] - private PaddleOcrModelConfig _selectedPaddleOcrModelConfig; + public ObservableCollection PaddleOcrModelConfigs { get; } = + new(Enum.GetValues(typeof(PaddleOcrModelConfig)).Cast()); + + [ObservableProperty] private PaddleOcrModelConfig _selectedPaddleOcrModelConfig; [RelayCommand] public void OnQuestionButtonOnClick() @@ -127,10 +134,11 @@ public partial class CommonSettingsPageViewModel : ViewModel cookieWin.NavigateToHtml(TravelsDiaryDetailManager.generHtmlMessage()); cookieWin.Show(); } + private void InitializeMiyousheCookie() { OtherConfig.Miyoushe mcfg = TaskContext.Instance().Config.OtherConfig.MiyousheConfig; - if (mcfg.Cookie == string.Empty&& + if (mcfg.Cookie == string.Empty && mcfg.LogSyncCookie) { var config = LogParse.LoadConfig(); @@ -339,6 +347,7 @@ public partial class CommonSettingsPageViewModel : ViewModel { await App.ServiceProvider.GetRequiredService().Unload(); } + [RelayCommand] private async Task OnPaddleOcrModelConfigChanged(PaddleOcrModelConfig value) { diff --git a/BetterGenshinImpact/ViewModel/Pages/View/ScriptGroupConfigViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/View/ScriptGroupConfigViewModel.cs index 3f4b6289..cec930b6 100644 --- a/BetterGenshinImpact/ViewModel/Pages/View/ScriptGroupConfigViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/View/ScriptGroupConfigViewModel.cs @@ -75,7 +75,7 @@ public partial class ScriptGroupConfigViewModel : ObservableObject, IViewModel [RelayCommand] public void OnGetExecutionOrder() { - var index = _pathingConfig.TaskCycleConfig.GetExecutionOrder(DateTime.Now); + var index = _pathingConfig.TaskCycleConfig.GetExecutionOrder(); if (index == -1) { Toast.Error("计算失败,请检查参数!"); From eeb6bb320b2d0e42ba3a5f1746a3023a1b5165e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Tue, 23 Sep 2025 01:41:56 +0800 Subject: [PATCH 06/20] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=A0=91=E8=84=82?= =?UTF-8?q?=E4=B8=8D=E8=B6=B3=E6=83=85=E5=86=B5=E4=B8=8B=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=B9=BD=E5=A2=83=E5=8D=B1=E6=88=98=E6=97=A0=E6=B3=95=E6=AD=A3?= =?UTF-8?q?=E5=B8=B8=E9=80=80=E5=87=BA=E7=9A=84=E9=97=AE=E9=A2=98=20#2241?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AutoStygianOnslaught/AutoStygianOnslaughtTask.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs b/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs index 5e28903d..317c5bb0 100644 --- a/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs +++ b/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs @@ -535,11 +535,7 @@ public class AutoStygianOnslaughtTask : ISoloTask private async Task ExitDomain() { - Simulation.SendInput.Keyboard.KeyPress(VK.VK_ESCAPE); - await Delay(500, _ct); - Simulation.SendInput.Keyboard.KeyPress(VK.VK_ESCAPE); - await Delay(800, _ct); - Bv.ClickBlackConfirmButton(CaptureToRectArea()); + await ExitDomain(new BvPage(_ct)); } private bool PressUseResin(ImageRegion ra, string resinName) @@ -624,7 +620,7 @@ public class AutoStygianOnslaughtTask : ISoloTask await page.Locator(ElementAssets.Instance.BtnExitDoor.Value).Click(); // 等待传送完成 - await page.Locator(ElementAssets.Instance.PaimonMenuRo).WaitFor(); + await page.Locator(ElementAssets.Instance.PaimonMenuRo).WaitFor(60000); await Delay(3000, _ct); } From e779aab20b320c1326fdff2612ef747663b24f8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Tue, 23 Sep 2025 23:25:10 +0800 Subject: [PATCH 07/20] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=B9=BD=E5=A2=83=E5=8D=B1=E6=88=98=E5=81=9C=E6=AD=A2=E6=97=B6?= =?UTF-8?q?=E5=BC=B9=E5=87=BA=E9=94=99=E8=AF=AF=E6=8F=90=E7=A4=BA=E7=AA=97?= =?UTF-8?q?=E5=8F=A3=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AutoStygianOnslaughtTask.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs b/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs index 317c5bb0..29228184 100644 --- a/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs +++ b/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs @@ -67,7 +67,18 @@ public class AutoStygianOnslaughtTask : ISoloTask Init(); Notify.Event(NotificationEvent.DomainStart).Success($"{Name}启动"); - await DoDomain(); + try + { + await DoDomain(); + } + catch (TaskCanceledException) + { + // do nothing + } + catch (Exception e) + { + _logger.LogInformation(e.Message); + } await Delay(3000, ct); From dc04c224739f61ba46a07060487f1da8ee1c42c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Wed, 24 Sep 2025 01:09:40 +0800 Subject: [PATCH 08/20] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=B5=93=E7=BC=A9?= =?UTF-8?q?=E6=A0=91=E8=84=82=E8=AF=86=E5=88=AB=E8=8C=83=E5=9B=B4=EF=BC=8C?= =?UTF-8?q?=E8=A7=A3=E5=86=B3V4=E6=A8=A1=E5=9E=8B=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E5=87=86=E7=A1=AE=E8=AF=86=E5=88=AB=E6=B5=93=E7=BC=A9=E6=A0=91?= =?UTF-8?q?=E8=84=82=E4=B8=AA=E6=95=B0=E7=9A=84=E9=97=AE=E9=A2=98=20#2185?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GameTask/AutoDomain/Model/ResinStatus.cs | 3 +- .../GameTask/AutoPick/AutoPickTrigger.cs | 2 +- .../GameTask/AutoPick/TextRectExtractor.cs | 59 ++++++++++++++++--- Test/BetterGenshinImpact.UnitTest/Assets | 2 +- .../OCRTests/PaddleFixture.cs | 6 +- .../AutoDomainTests/ResinStatusTests.cs | 8 ++- 6 files changed, 63 insertions(+), 17 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoDomain/Model/ResinStatus.cs b/BetterGenshinImpact/GameTask/AutoDomain/Model/ResinStatus.cs index 54bf7bea..2f5c27da 100644 --- a/BetterGenshinImpact/GameTask/AutoDomain/Model/ResinStatus.cs +++ b/BetterGenshinImpact/GameTask/AutoDomain/Model/ResinStatus.cs @@ -6,6 +6,7 @@ using BetterGenshinImpact.Helpers; using Microsoft.Extensions.Logging; using OpenCvSharp; using System; +using BetterGenshinImpact.GameTask.AutoPick; namespace BetterGenshinImpact.GameTask.AutoDomain.Model; @@ -77,7 +78,7 @@ public class ResinStatus if (condensedResinRes.IsExist()) { // 找出 icon 的位置 + 25 ~ icon 的位置+45 就是浓缩树脂的数字,数字宽20 - var condensedResinCountRect = new Rect(crop2.X + condensedResinRes.Right + (int)(20 * assetScale), (int)(37 * assetScale), (int)(70 * assetScale), (int)(24 * assetScale)); + var condensedResinCountRect = new Rect(crop2.X + condensedResinRes.Right + (int)(20 * assetScale), crop2.Y + condensedResinRes.Y, (int)(30 * assetScale), condensedResinRes.Height); using ImageRegion countRegion = region.DeriveCrop(condensedResinCountRect); using Mat threshold = countRegion.CacheGreyMat.Threshold(180, 255, ThresholdTypes.Binary); using Mat bitwiseNot = new Mat(); diff --git a/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs b/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs index f77cfd72..443e86f7 100644 --- a/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs +++ b/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs @@ -246,7 +246,7 @@ public partial class AutoPickTrigger : ITaskTrigger else { var textMat = new Mat(content.CaptureRectArea.SrcMat, textRect); - var boundingRect = TextRectExtractor.GetTextBoundingRect(textMat, out var bin); + var boundingRect = TextRectExtractor.GetTextBoundingRect(textMat); // 如果找到有效区域 if (boundingRect.X <20 && boundingRect.Width > 5 && boundingRect.Height > 5) { diff --git a/BetterGenshinImpact/GameTask/AutoPick/TextRectExtractor.cs b/BetterGenshinImpact/GameTask/AutoPick/TextRectExtractor.cs index ac15db89..3b201af0 100644 --- a/BetterGenshinImpact/GameTask/AutoPick/TextRectExtractor.cs +++ b/BetterGenshinImpact/GameTask/AutoPick/TextRectExtractor.cs @@ -11,12 +11,17 @@ public static class TextRectExtractor /// 从图片中提取文字范围(假定文字从最左边贴边开始,向右连续) /// 结果矩形固定 x=0,y=0,h=原图高度,只计算连续文字宽度。 /// - public static Rect GetTextBoundingRect(Mat textMat, out Mat bin) + /// 文字图片 + /// 二值化阈值 + /// 二值化阈值 + /// + public static Rect GetTextBoundingRect(Mat textMat, double min = 160, double max = 255) { // 转换为灰度图 - Mat gray = new Mat(); + Mat gray; if (textMat.Channels() == 3) { + gray = new Mat(); Cv2.CvtColor(textMat, gray, ColorConversionCodes.BGR2GRAY); } else @@ -25,8 +30,8 @@ public static class TextRectExtractor } // 使用阈值160进行二值化处理 - bin = new Mat(); - Cv2.Threshold(gray, bin, 160, 255, ThresholdTypes.Binary); + using var bin = new Mat(); + Cv2.Threshold(gray, bin, min, max, ThresholdTypes.Binary); // 形态学操作:先腐蚀后膨胀,去除噪点并保持文字完整 Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(3, 3)); @@ -37,7 +42,46 @@ public static class TextRectExtractor return ProjectionRect(textMat, bin); } - private static Rect ProjectionRect(Mat textMat, Mat bin) + public static Rect GetNumberBoundingRect(Mat textMat, double min = 180, double max = 255) + { + // 转换为灰度图 + Mat gray; + if (textMat.Channels() == 3) + { + gray = new Mat(); + Cv2.CvtColor(textMat, gray, ColorConversionCodes.BGR2GRAY); + } + else + { + gray = textMat.Clone(); + } + + // 使用阈值160进行二值化处理 + using var bin = new Mat(); + Cv2.Threshold(gray, bin, min, max, ThresholdTypes.Binary); + + // 形态学操作:直接膨胀 + Cv2.Dilate(bin, bin, new Mat(), iterations: 2); + gray.Dispose(); + var rect = ProjectionRect(textMat, bin); + // 数字后面加点宽度 + if (rect != new Rect()) + { + var newWidth = rect.Width + 10; + rect.Width = newWidth > textMat.Width ? textMat.Width : newWidth; + } + + return rect; + } + + /// + /// 投影, 获取连续文字的边界矩形 + /// + /// + /// + /// 允许的最大连续空列数 + /// + public static Rect ProjectionRect(Mat textMat, Mat bin, int maxGap = 30) { // 投影:对行做 ReduceSum,得到 1 x width 的列和 using var projection = new Mat(); @@ -45,7 +89,6 @@ public static class TextRectExtractor int width = projection.Cols; projection.GetArray(out int[] colSums); - int maxGap = 30; // 允许的最大连续空列数 int gapCount = 0; int lastNonEmpty = -1; @@ -66,7 +109,7 @@ public static class TextRectExtractor } } } - + if (lastNonEmpty == -1) { // 没有检测到文字 @@ -76,4 +119,4 @@ public static class TextRectExtractor Rect boundingRect = new Rect(0, 0, lastNonEmpty, textMat.Height); return boundingRect; } -} +} \ No newline at end of file diff --git a/Test/BetterGenshinImpact.UnitTest/Assets b/Test/BetterGenshinImpact.UnitTest/Assets index 3df550ad..e6446663 160000 --- a/Test/BetterGenshinImpact.UnitTest/Assets +++ b/Test/BetterGenshinImpact.UnitTest/Assets @@ -1 +1 @@ -Subproject commit 3df550ad83bd7c2f512d2fa8795f6284c13ddd96 +Subproject commit e644666398474a60e0d8a245dcf269c5c52d9dea diff --git a/Test/BetterGenshinImpact.UnitTest/CoreTests/RecognitionTests/OCRTests/PaddleFixture.cs b/Test/BetterGenshinImpact.UnitTest/CoreTests/RecognitionTests/OCRTests/PaddleFixture.cs index fd661a16..ec61fe32 100644 --- a/Test/BetterGenshinImpact.UnitTest/CoreTests/RecognitionTests/OCRTests/PaddleFixture.cs +++ b/Test/BetterGenshinImpact.UnitTest/CoreTests/RecognitionTests/OCRTests/PaddleFixture.cs @@ -11,19 +11,19 @@ namespace BetterGenshinImpact.UnitTest.CoreTests.RecognitionTests.OCRTests public PaddleOcrService Get(string cultureInfoName = "zh-Hans", string version = "V5") { - return _paddleOcrServices.GetOrAdd(cultureInfoName, name => + return _paddleOcrServices.GetOrAdd(cultureInfoName + "_" + version, _ => { lock (_paddleOcrServices) { if (version == "V5") { return new PaddleOcrService(new BgiOnnxFactory(new FakeLogger()), - PaddleOcrService.PaddleOcrModelType.FromCultureInfo(new CultureInfo(name)) ?? PaddleOcrService.PaddleOcrModelType.V5); + PaddleOcrService.PaddleOcrModelType.FromCultureInfo(new CultureInfo(cultureInfoName)) ?? PaddleOcrService.PaddleOcrModelType.V5); } else if (version == "V4") { return new PaddleOcrService(new BgiOnnxFactory(new FakeLogger()), - PaddleOcrService.PaddleOcrModelType.FromCultureInfoV4(new CultureInfo(name)) ?? PaddleOcrService.PaddleOcrModelType.V4); + PaddleOcrService.PaddleOcrModelType.FromCultureInfoV4(new CultureInfo(cultureInfoName)) ?? PaddleOcrService.PaddleOcrModelType.V4); } else { diff --git a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoDomainTests/ResinStatusTests.cs b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoDomainTests/ResinStatusTests.cs index 06d49f58..d44f7109 100644 --- a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoDomainTests/ResinStatusTests.cs +++ b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoDomainTests/ResinStatusTests.cs @@ -21,12 +21,14 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoDomainTests this.paddle = paddle; } - [Theory] - [InlineData(@"AutoDomain\SelectRevitalization.png", 21, 0, 2, 1)] - [InlineData(@"AutoDomain\SelectRevitalizationOcrV4.png", 11, 0, 1, 149, "V4")] /// /// 测试识别四种树脂数量,数量应正确 /// + [Theory] + [InlineData(@"AutoDomain\SelectRevitalization.png", 21, 0, 2, 1)] + [InlineData(@"AutoDomain\SelectRevitalizationOcrV4.png", 11, 0, 1, 149, "V4")] + [InlineData(@"AutoDomain\SelectRevitalizationOcrV4_NSSZ1.png", 20, 1, 1, 0, "V4")] + [InlineData(@"AutoDomain\SelectRevitalizationOcrV4_NSSZ1.png", 20, 1, 1, 0)] public void RecogniseFromRegion_ResinStatusShouldBeRight(string screenshot1080p, int originalResinCount, int fragileResinCount, int condensedResinCount, int transientResinCount, string ocrVersion = "V5") { // From 547b4a4929b58f23751594b1b7d1cf93b76ef98d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Wed, 24 Sep 2025 01:15:47 +0800 Subject: [PATCH 09/20] update submodule --- Test/BetterGenshinImpact.UnitTest/Assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Test/BetterGenshinImpact.UnitTest/Assets b/Test/BetterGenshinImpact.UnitTest/Assets index e6446663..1ac6fa09 160000 --- a/Test/BetterGenshinImpact.UnitTest/Assets +++ b/Test/BetterGenshinImpact.UnitTest/Assets @@ -1 +1 @@ -Subproject commit e644666398474a60e0d8a245dcf269c5c52d9dea +Subproject commit 1ac6fa09609d843245109aa405402bcf51cb5bee From e94614a3a98b9a2b9feab98b6e1a387743f553fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Wed, 24 Sep 2025 01:36:19 +0800 Subject: [PATCH 10/20] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=BC=80=E9=97=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/GameTask/GameLoading/GameLoading.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/BetterGenshinImpact/GameTask/GameLoading/GameLoading.cs b/BetterGenshinImpact/GameTask/GameLoading/GameLoading.cs index 5af58439..923c9133 100644 --- a/BetterGenshinImpact/GameTask/GameLoading/GameLoading.cs +++ b/BetterGenshinImpact/GameTask/GameLoading/GameLoading.cs @@ -345,6 +345,7 @@ public class GameLoadingTrigger : ITaskTrigger var wmRa = content.CaptureRectArea.Find(_assets.WelkinMoonRo); if (!wmRa.IsEmpty()) { + GameCaptureRegion.GameRegion1080PPosMove(100, 100); TaskContext.Instance().PostMessageSimulator.LeftButtonClickBackground(); Debug.WriteLine("[GameLoading] Click blessing of the welkin moon"); // TaskControl.Logger.LogInformation("自动点击月卡"); @@ -355,7 +356,7 @@ public class GameLoadingTrigger : ITaskTrigger var ysRa = content.CaptureRectArea.Find(ElementAssets.Instance.PrimogemRo); if (!ysRa.IsEmpty()) { - GameCaptureRegion.GameRegion1080PPosMove(10, 10); + GameCaptureRegion.GameRegion1080PPosMove(100, 100); TaskContext.Instance().PostMessageSimulator.LeftButtonClickBackground(); Debug.WriteLine("[GameLoading] 跳过原石"); return; From 71e71c5d6ba4e401215dd6c2549f12b2b8ec19f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Wed, 24 Sep 2025 01:40:06 +0800 Subject: [PATCH 11/20] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=81=9A=E6=89=80?= =?UTF-8?q?=E7=9A=84=E6=8B=BE=E5=8F=96=E6=8E=92=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GameTask/AutoPick/AutoPickTrigger.cs | 51 +++++++++++++------ 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs b/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs index 443e86f7..b4983e77 100644 --- a/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs +++ b/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs @@ -283,22 +283,8 @@ public partial class AutoPickTrigger : ITaskTrigger { // 处理OCR识别结果,清理无效字符并确保引号配对 text = ProcessOcrText(text); - - // 唯一一个动态拾取项,特殊处理,不拾取 - if (text.Contains("长时间")) - { - return; - } - // 纳塔部落中文名特殊处理,不拾取 - if (text.Contains("我在") && (text.Contains("声望") || text.Contains("回声") || text.Contains("悬木人") || - text.Contains("流泉"))) - { - return; - } - // 挪德卡莱聚所中文名特殊处理,不拾取 - if (text.Contains("聚所") && (text.Contains("霜月") || text.Contains("叮铃") || - text.Contains("眶螂") || text.Contains("蛋卷") || text.Contains("坊"))) + if (DoNotPick(text)) { return; } @@ -336,6 +322,41 @@ public partial class AutoPickTrigger : ITaskTrigger } speedTimer.DebugPrint(); + + + } + + private bool DoNotPick(string text) + { + // 唯一一个动态拾取项,特殊处理,不拾取 + if (text.Contains("长时间")) + { + return true; + } + + // 纳塔部落中文名特殊处理,不拾取 + if (text.Contains("我在") && (text.Contains("声望") || text.Contains("回声") || text.Contains("悬木人") || + text.Contains("流泉"))) + { + return true; + } + // 挪德卡莱聚所中文名特殊处理,不拾取 + if (text.Contains("聚所")) + { + return true; + } + + if (text.Contains("霜月") && text.Contains("坊")) + { + return true; + } + + if (text.Contains("叮铃") || text.Contains("眶螂") || (text.Contains("蛋卷") && text.Contains("坊"))) + { + return true; + } + + return false; } public static Rect GetWhiteTextBoundingRect(Mat textMat) From 5681cb436ca6fefc93c870869cbf6b28f3d663e0 Mon Sep 17 00:00:00 2001 From: kaedelcb <57870068+kaedelcb@users.noreply.github.com> Date: Wed, 24 Sep 2025 01:42:33 +0800 Subject: [PATCH 12/20] =?UTF-8?q?=E4=B8=8D=E8=BF=9B=E8=A1=8C=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E6=A3=80=E6=B5=8B=E5=81=8F=E7=A7=BB=E9=80=82=E9=85=8D?= =?UTF-8?q?=20(#2256)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs | 3 +++ .../GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs b/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs index 3bbec55b..f18e35f0 100644 --- a/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs +++ b/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs @@ -40,6 +40,7 @@ using BetterGenshinImpact.GameTask.AutoDomain.Model; using BetterGenshinImpact.GameTask.Common; using Compunet.YoloSharp; using Microsoft.Extensions.DependencyInjection; +using BetterGenshinImpact.GameTask.AutoFight; namespace BetterGenshinImpact.GameTask.AutoDomain; @@ -646,6 +647,7 @@ public class AutoDomainTask : ISoloTask { try { + AutoFightTask.FightStatusFlag = true; while (!cts.Token.IsCancellationRequested) { // 通用化战斗策略 @@ -668,6 +670,7 @@ public class AutoDomainTask : ISoloTask { Logger.LogInformation("自动战斗线程结束"); Simulation.ReleaseAllKey(); + AutoFightTask.FightStatusFlag = false; } }, cts.Token); diff --git a/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs b/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs index 29228184..4b7f919c 100644 --- a/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs +++ b/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs @@ -28,6 +28,7 @@ using System.Threading; using System.Threading.Tasks; using static BetterGenshinImpact.GameTask.Common.TaskControl; using static Vanara.PInvoke.User32; +using BetterGenshinImpact.GameTask.AutoFight; namespace BetterGenshinImpact.GameTask.AutoStygianOnslaught; @@ -318,6 +319,7 @@ public class AutoStygianOnslaughtTask : ISoloTask { try { + AutoFightTask.FightStatusFlag = true; while (!cts.Token.IsCancellationRequested) { // 通用化战斗策略 @@ -341,6 +343,7 @@ public class AutoStygianOnslaughtTask : ISoloTask _logger.LogInformation("自动战斗线程结束"); Simulation.ReleaseAllKey(); Simulation.SendInput.Mouse.LeftButtonUp(); + AutoFightTask.FightStatusFlag = false; } }, cts.Token); From 808dbec396b799b8141a67fef1d63f81e816ab3d Mon Sep 17 00:00:00 2001 From: FishmanTheMurloc <162452111+FishmanTheMurloc@users.noreply.github.com> Date: Wed, 24 Sep 2025 13:39:38 +0800 Subject: [PATCH 13/20] =?UTF-8?q?=E6=9A=B4=E9=9C=B2ocr=E7=9A=84=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E4=BF=A1=E6=81=AF=E4=BE=9B=E8=B0=83=E7=94=A8=E8=80=85?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=EF=BC=8C=E5=A2=9E=E5=8A=A0=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E6=AD=A4=E9=A1=B9=E7=9A=84=E5=8D=95=E6=B5=8B?= =?UTF-8?q?=20(#2259)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core/Recognition/OCR/Paddle/Det.cs | 2 ++ .../Recognition/OCR/Paddle/PaddleOcrService.cs | 16 +++++++++++++--- .../Core/Recognition/OCR/Paddle/Rec.cs | 2 ++ .../OCRTests/PaddleOcrServiceTests.cs | 15 ++++++++++++++- .../AutoDomainTests/ResinStatusTests.cs | 2 +- 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/BetterGenshinImpact/Core/Recognition/OCR/Paddle/Det.cs b/BetterGenshinImpact/Core/Recognition/OCR/Paddle/Det.cs index aae85c04..6c2c25db 100644 --- a/BetterGenshinImpact/Core/Recognition/OCR/Paddle/Det.cs +++ b/BetterGenshinImpact/Core/Recognition/OCR/Paddle/Det.cs @@ -195,4 +195,6 @@ public class Det(BgiOnnxModel model, OcrVersionConfig config, BgiOnnxFactory bgi return score; } + + public string GetConfigName => config.Name; } \ No newline at end of file diff --git a/BetterGenshinImpact/Core/Recognition/OCR/Paddle/PaddleOcrService.cs b/BetterGenshinImpact/Core/Recognition/OCR/Paddle/PaddleOcrService.cs index 268767c8..9c70849c 100644 --- a/BetterGenshinImpact/Core/Recognition/OCR/Paddle/PaddleOcrService.cs +++ b/BetterGenshinImpact/Core/Recognition/OCR/Paddle/PaddleOcrService.cs @@ -1,7 +1,6 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; -using System.Drawing; using System.Globalization; using System.IO; using System.Linq; @@ -201,7 +200,7 @@ public class PaddleOcrService : IOcrService, IDisposable return null; } - + /// /// 中英文优先使用V4模型,其他语言使用V5模型 /// @@ -342,4 +341,15 @@ public class PaddleOcrService : IOcrService, IDisposable _localDetModel.Dispose(); _localRecModel.Dispose(); } + + /// + /// 返回(DetConfigName, RecConfigName) + /// + public (string, string) GetConfigName + { + get + { + return (this._localDetModel.GetConfigName, this._localRecModel.GetConfigName); + } + } } \ No newline at end of file diff --git a/BetterGenshinImpact/Core/Recognition/OCR/Paddle/Rec.cs b/BetterGenshinImpact/Core/Recognition/OCR/Paddle/Rec.cs index 2471c470..c167f4bf 100644 --- a/BetterGenshinImpact/Core/Recognition/OCR/Paddle/Rec.cs +++ b/BetterGenshinImpact/Core/Recognition/OCR/Paddle/Rec.cs @@ -181,4 +181,6 @@ public class Rec( } }).ToArray(); } + + public string GetConfigName => config.Name; } \ No newline at end of file diff --git a/Test/BetterGenshinImpact.UnitTest/CoreTests/RecognitionTests/OCRTests/PaddleOcrServiceTests.cs b/Test/BetterGenshinImpact.UnitTest/CoreTests/RecognitionTests/OCRTests/PaddleOcrServiceTests.cs index ba2ab8f6..5c16e311 100644 --- a/Test/BetterGenshinImpact.UnitTest/CoreTests/RecognitionTests/OCRTests/PaddleOcrServiceTests.cs +++ b/Test/BetterGenshinImpact.UnitTest/CoreTests/RecognitionTests/OCRTests/PaddleOcrServiceTests.cs @@ -1,4 +1,4 @@ -using OpenCvSharp; +using OpenCvSharp; using System.Drawing; using BetterGenshinImpact.Core.Recognition.OCR.Paddle; using OpenCvSharp.Extensions; @@ -72,5 +72,18 @@ namespace BetterGenshinImpact.UnitTest.CoreTests.RecognitionTests.OCRTests Assert.Matches(pattern, actual); } } + + [Fact] + public void PaddleOcrService_Version_ShouldBeCorrect() + { + // + var ocrV4 = this.paddle.Get("zh-Hans", "V4"); + var ocrV5 = this.paddle.Get("zh-Hans", "V5"); + // + Assert.EndsWith("V4", ocrV4.GetConfigName.Item1, StringComparison.OrdinalIgnoreCase); + Assert.EndsWith("V4", ocrV4.GetConfigName.Item2, StringComparison.OrdinalIgnoreCase); + Assert.EndsWith("V5", ocrV5.GetConfigName.Item1, StringComparison.OrdinalIgnoreCase); + Assert.EndsWith("V5", ocrV5.GetConfigName.Item2, StringComparison.OrdinalIgnoreCase); + } } } diff --git a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoDomainTests/ResinStatusTests.cs b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoDomainTests/ResinStatusTests.cs index d44f7109..2de90c67 100644 --- a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoDomainTests/ResinStatusTests.cs +++ b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoDomainTests/ResinStatusTests.cs @@ -37,7 +37,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoDomainTests FakeSystemInfo systemInfo = new FakeSystemInfo(new Vanara.PInvoke.RECT(0, 0, mat.Width, mat.Height), 1); // - var result = ResinStatus.RecogniseFromRegion(imageRegion, systemInfo, this.paddle.Get(version: ocrVersion)); // todo:System.Exception : 未找到原粹树脂图标 + var result = ResinStatus.RecogniseFromRegion(imageRegion, systemInfo, this.paddle.Get(version: ocrVersion)); // Assert.Equal(originalResinCount, result.OriginalResinCount); From 5fefcc2047e459cb7896b02d651d0c0b18cd248d Mon Sep 17 00:00:00 2001 From: huiyadanli <15783049+huiyadanli@users.noreply.github.com> Date: Wed, 24 Sep 2025 06:21:35 +0000 Subject: [PATCH 14/20] Update version to 0.51.1-alpha.1 --- BetterGenshinImpact/BetterGenshinImpact.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index aea03bad..6b975f7d 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -2,7 +2,7 @@ BetterGI - 0.51.0 + 0.51.1-alpha.1 false WinExe net8.0-windows10.0.22621.0 From a3492aaabe8b52fb1dff45135f7f03c40f64aadd Mon Sep 17 00:00:00 2001 From: Jamis Date: Thu, 25 Sep 2025 09:58:40 +0800 Subject: [PATCH 15/20] fix wrongly recognized text 2 (#2263) --- BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs b/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs index 821e2506..fe276517 100644 --- a/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs +++ b/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs @@ -417,6 +417,13 @@ public class ImageRegion : Region { var newRa = this.Derive(r.Rect.BoundingRect() + ro.RegionOfInterest.Location); newRa.Text = r.Text; + foreach (var entry in ro.ReplaceDictionary) + { + foreach (var replaceStr in entry.Value) + { + newRa.Text = newRa.Text.Replace(replaceStr, entry.Key); + } + } return newRa; }).ToList(); if (ro.DrawOnWindow && !string.IsNullOrEmpty(ro.Name)) From 17790317d9bc675cfced0a4d8121520954956e17 Mon Sep 17 00:00:00 2001 From: FishmanTheMurloc <162452111+FishmanTheMurloc@users.noreply.github.com> Date: Thu, 25 Sep 2025 15:41:00 +0800 Subject: [PATCH 16/20] =?UTF-8?q?ScreenshotUidCoverEnabled=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E7=9A=84=E9=BB=98=E8=AE=A4=E5=80=BC=E6=94=B9=E4=B8=BA?= =?UTF-8?q?true=20(#2262)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/Core/Config/CommonConfig.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BetterGenshinImpact/Core/Config/CommonConfig.cs b/BetterGenshinImpact/Core/Config/CommonConfig.cs index f29671b6..7510aa1f 100644 --- a/BetterGenshinImpact/Core/Config/CommonConfig.cs +++ b/BetterGenshinImpact/Core/Config/CommonConfig.cs @@ -1,4 +1,4 @@ -using BetterGenshinImpact.Helpers; +using BetterGenshinImpact.Helpers; using CommunityToolkit.Mvvm.ComponentModel; using System; using System.Collections.Generic; @@ -36,7 +36,7 @@ public partial class CommonConfig : ObservableObject /// UID遮盖是否启用 /// [ObservableProperty] - private bool _screenshotUidCoverEnabled; + private bool _screenshotUidCoverEnabled = true; /// /// 退出时最小化至托盘 @@ -48,7 +48,7 @@ public partial class CommonConfig : ObservableObject /// 当前主题类型(新版主题) /// [ObservableProperty] - private ThemeType _currentThemeType = OsVersionHelper.IsWindows11_22523_OrGreater? ThemeType.DarkMica : ThemeType.DarkNone; + private ThemeType _currentThemeType = OsVersionHelper.IsWindows11_22523_OrGreater ? ThemeType.DarkMica : ThemeType.DarkNone; /// /// 主题(旧版主题,兼容性保留) From 0aef3ec39a867de8037188996c33a50a79187d0f Mon Sep 17 00:00:00 2001 From: bhbghghbgb <113711814+bhbghghbgb@users.noreply.github.com> Date: Fri, 26 Sep 2025 15:18:30 +0800 Subject: [PATCH 17/20] =?UTF-8?q?feat=EF=BC=9A=E8=87=AA=E5=8A=A8=E5=9C=A3?= =?UTF-8?q?=E9=81=97=E7=89=A9=E8=BD=AC=E5=8C=96=E5=8A=9F=E8=83=BD=E5=8F=AF?= =?UTF-8?q?=E8=AF=86=E5=88=AB=E5=B9=B6=E6=A0=87=E8=AE=B0=E6=9C=AA=E6=BF=80?= =?UTF-8?q?=E6=B4=BB=E5=89=AF=E8=AF=8D=E6=9D=A1=20(#2258)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AutoArtifactSalvage/ArtifactAffix.cs | 12 +- .../AutoArtifactSalvage/ArtifactStat.cs | 6 +- .../AutoArtifactSalvageTask.cs | 173 +++++++++++------- .../AutoArtifactSalvageTaskTests.cs | 2 +- 4 files changed, 116 insertions(+), 77 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoArtifactSalvage/ArtifactAffix.cs b/BetterGenshinImpact/GameTask/AutoArtifactSalvage/ArtifactAffix.cs index 3eb436fc..d7b6887f 100644 --- a/BetterGenshinImpact/GameTask/AutoArtifactSalvage/ArtifactAffix.cs +++ b/BetterGenshinImpact/GameTask/AutoArtifactSalvage/ArtifactAffix.cs @@ -6,16 +6,16 @@ namespace BetterGenshinImpact.GameTask.AutoArtifactSalvage /// /// 圣遗物词条 /// - public class ArtifactAffix + public class ArtifactAffix(ArtifactAffixType type, float value, bool isUnactivated) { - public ArtifactAffix(ArtifactAffixType type, float value) + public ArtifactAffix(ArtifactAffixType type, float value) : this(type, value, false) { - Type = type; - Value = value; } - public ArtifactAffixType Type { get; private set; } - public float Value { get; private set; } + public ArtifactAffixType Type { get; private set; } = type; + public float Value { get; private set; } = value; + public bool IsUnactivated { get; private set; } = isUnactivated; + public static FrozenDictionary DefaultStrDic { get; } = new Dictionary() { { ArtifactAffixType.ATK, "攻击力" }, { ArtifactAffixType.ATKPercent, "攻击力" }, diff --git a/BetterGenshinImpact/GameTask/AutoArtifactSalvage/ArtifactStat.cs b/BetterGenshinImpact/GameTask/AutoArtifactSalvage/ArtifactStat.cs index 9a31e297..7c3914e4 100644 --- a/BetterGenshinImpact/GameTask/AutoArtifactSalvage/ArtifactStat.cs +++ b/BetterGenshinImpact/GameTask/AutoArtifactSalvage/ArtifactStat.cs @@ -50,7 +50,11 @@ namespace BetterGenshinImpact.GameTask.AutoArtifactSalvage sb.Append("├─").Append("MinorAffixes: ").Append('\n'); for (int i = 0; i < this.MinorAffixes.Length; i++) { - sb.Append('│').Append('\t').Append(i == this.MinorAffixes.Length - 1 ? "└─" : "├─").Append($"[{i}]: ").Append(this.MinorAffixes[i].Type).Append(", ").Append(this.MinorAffixes[i].Value).Append('\n'); + sb.Append('│').Append('\t').Append(i == this.MinorAffixes.Length - 1 ? "└─" : "├─").Append($"[{i}]: ").Append(this.MinorAffixes[i].Type).Append(", ").Append(this.MinorAffixes[i].Value); + if (this.MinorAffixes[i].IsUnactivated) { + sb.Append(", Unactivated"); + } + sb.Append('\n'); } sb.Append("└─").Append("Level: ").Append(this.Level); return sb.ToString(); diff --git a/BetterGenshinImpact/GameTask/AutoArtifactSalvage/AutoArtifactSalvageTask.cs b/BetterGenshinImpact/GameTask/AutoArtifactSalvage/AutoArtifactSalvageTask.cs index 4fb07a35..7c61a7ea 100644 --- a/BetterGenshinImpact/GameTask/AutoArtifactSalvage/AutoArtifactSalvageTask.cs +++ b/BetterGenshinImpact/GameTask/AutoArtifactSalvage/AutoArtifactSalvageTask.cs @@ -497,12 +497,16 @@ public class AutoArtifactSalvageTask : ISoloTask string mainAffixText = string.Join("\n", mainAffixOcrResult.Regions.Where(r => r.Score > 0.5).OrderBy(r => r.Rect.Center.Y).ThenBy(r => r.Rect.Center.X).Select(r => r.Text)); var mainAffixLines = mainAffixText.Split('\n'); var levelAndMinorAffixOcrResult = ocrService.OcrResult(levelAndMinorAffixRoi); - string levelAndMinorAffixText = string.Join("\n", levelAndMinorAffixOcrResult.Regions.Where(r => r.Score > 0.5) + (string Text, Rect Rect)[] levelAndMinorAffixResult = levelAndMinorAffixOcrResult.Regions.Where(r => r.Score > 0.5) .Where(r => r.Rect.BoundingRect().Left < levelAndMinorAffixRoi.Width * 0.1) // 一定是贴着左边的,排除套装效果文字也存在类似+15%的情况 - .OrderBy(r => r.Rect.Center.Y).ThenBy(r => r.Rect.Center.X).Select(r => r.Text)); - var levelAndMinorAffixLines = levelAndMinorAffixText.Split('\n'); - - allText = String.Join('\n', nameOcrResult.Text, typeOcrResult.Text, mainAffixText, levelAndMinorAffixText); + .OrderBy(r => r.Rect.Center.Y).ThenBy(r => r.Rect.Center.X).Select(r => (r.Text, r.Rect.BoundingRect())).ToArray(); + var levelAndMinorAffixLines = levelAndMinorAffixResult.Select(r => r.Text).ToArray(); + allText = string.Join('\n', new[] + { + nameOcrResult.Text, + typeOcrResult.Text, + mainAffixText + }.Concat(levelAndMinorAffixLines)); string percentStr = "%"; @@ -547,81 +551,112 @@ public class AutoArtifactSalvageTask : ISoloTask #endregion #region 副词条 - ArtifactAffix[] minorAffixes = levelAndMinorAffixLines.Select(l => + var minorAffixes = new List(); + string pattern = @"^([^+::]+)\+([\d., ]*)(%?).*$"; + pattern = pattern.Replace("%", percentStr); + foreach (var r in levelAndMinorAffixResult) { - string pattern = @"^([^+::]+)\+([\d., ]*)(%?).*$"; - pattern = pattern.Replace("%", percentStr); - Match match = Regex.Match(l, pattern); - if (match.Success) + Match match = Regex.Match(r.Text, pattern); + if (!match.Success) { - ArtifactAffixType artifactAffixType; - var dic = this.artifactAffixStrDic; - - if (match.Groups[1].Value.Contains(dic[ArtifactAffixType.ATK])) + continue; + } + ArtifactAffixType artifactAffixType; + var dic = this.artifactAffixStrDic; + if (match.Groups[1].Value.Contains(dic[ArtifactAffixType.ATK])) + { + if (String.IsNullOrEmpty(match.Groups[3].Value)) { - if (String.IsNullOrEmpty(match.Groups[3].Value)) - { - artifactAffixType = ArtifactAffixType.ATK; - } - else - { - artifactAffixType = ArtifactAffixType.ATKPercent; - } - } - else if (match.Groups[1].Value.Contains(dic[ArtifactAffixType.DEF])) - { - if (String.IsNullOrEmpty(match.Groups[3].Value)) - { - artifactAffixType = ArtifactAffixType.DEF; - } - else - { - artifactAffixType = ArtifactAffixType.DEFPercent; - } - } - else if (match.Groups[1].Value.Contains(dic[ArtifactAffixType.HP])) - { - if (String.IsNullOrEmpty(match.Groups[3].Value)) - { - artifactAffixType = ArtifactAffixType.HP; - } - else - { - artifactAffixType = ArtifactAffixType.HPPercent; - } - } - else if (match.Groups[1].Value.Contains(dic[ArtifactAffixType.CRITRate])) - { - artifactAffixType = ArtifactAffixType.CRITRate; - } - else if (match.Groups[1].Value.Contains(dic[ArtifactAffixType.CRITDMG])) - { - artifactAffixType = ArtifactAffixType.CRITDMG; - } - else if (match.Groups[1].Value.Contains(dic[ArtifactAffixType.ElementalMastery])) - { - artifactAffixType = ArtifactAffixType.ElementalMastery; - } - else if (match.Groups[1].Value.Contains(dic[ArtifactAffixType.EnergyRecharge])) - { - artifactAffixType = ArtifactAffixType.EnergyRecharge; + artifactAffixType = ArtifactAffixType.ATK; } else { - throw new Exception($"未识别的副词条:{match.Groups[1].Value}"); + artifactAffixType = ArtifactAffixType.ATKPercent; } - - if (!float.TryParse(match.Groups[2].Value, NumberStyles.Any, cultureInfo, out float value)) + } + else if (match.Groups[1].Value.Contains(dic[ArtifactAffixType.DEF])) + { + if (String.IsNullOrEmpty(match.Groups[3].Value)) { - throw new Exception($"未识别的副词条数值:{match.Groups[2].Value}"); + artifactAffixType = ArtifactAffixType.DEF; } - return new ArtifactAffix(artifactAffixType, value); + else + { + artifactAffixType = ArtifactAffixType.DEFPercent; + } + } + else if (match.Groups[1].Value.Contains(dic[ArtifactAffixType.HP])) + { + if (String.IsNullOrEmpty(match.Groups[3].Value)) + { + artifactAffixType = ArtifactAffixType.HP; + } + else + { + artifactAffixType = ArtifactAffixType.HPPercent; + } + } + else if (match.Groups[1].Value.Contains(dic[ArtifactAffixType.CRITRate])) + { + artifactAffixType = ArtifactAffixType.CRITRate; + } + else if (match.Groups[1].Value.Contains(dic[ArtifactAffixType.CRITDMG])) + { + artifactAffixType = ArtifactAffixType.CRITDMG; + } + else if (match.Groups[1].Value.Contains(dic[ArtifactAffixType.ElementalMastery])) + { + artifactAffixType = ArtifactAffixType.ElementalMastery; + } + else if (match.Groups[1].Value.Contains(dic[ArtifactAffixType.EnergyRecharge])) + { + artifactAffixType = ArtifactAffixType.EnergyRecharge; } else { - return null; + throw new Exception($"未识别的副词条:{match.Groups[1].Value}"); } - }).Where(a => a != null).Cast().ToArray(); + + if (!float.TryParse(match.Groups[2].Value, NumberStyles.Any, cultureInfo, out float affixValue)) + { + throw new Exception($"未识别的副词条数值:{match.Groups[2].Value}"); + } + + bool isUnactivated = false; + // 只有在已经成功识别至少 3 个词条后才执行额外的直方图分析。 + if (minorAffixes.Count >= 3) + { + using var lineRoi = levelAndMinorAffixRoi.SubMat(r.Rect); + using var lineHistogram = new Mat(); + Cv2.CalcHist( + images: [lineRoi], + channels: [0], + mask: null, + hist: lineHistogram, + dims: 1, + histSize: [256], + ranges: [new Rangef(0, 256)] + ); + lineHistogram.GetArray(out float[] histogramFrequencies); + // 检查背景和前景像素是否符合未激活的特征。 + const int backgroundIntensity = 222; + const int foregroundIntensity = 152; + var backgroundFrequency = histogramFrequencies[backgroundIntensity]; + var foregroundFrequency = histogramFrequencies[foregroundIntensity]; + var noiseFrequencyUpperBound = Math.Min(backgroundFrequency, foregroundFrequency); + // 检查这两个强度是否比所有其他强度更常见 + isUnactivated = backgroundFrequency > 0 && + foregroundFrequency > 0 && + backgroundFrequency > foregroundFrequency && + !histogramFrequencies + .Where((frequency, intensity) => + intensity != backgroundIntensity && + intensity != foregroundIntensity && + frequency > noiseFrequencyUpperBound) + .Any(); + } + minorAffixes.Add(new ArtifactAffix(artifactAffixType, affixValue, isUnactivated)); + } #endregion #region 等级 @@ -637,14 +672,14 @@ public class AutoArtifactSalvageTask : ISoloTask { return null; } - }).Where(l => l != null).Cast().SingleOrDefault() ?? throw new Exception($"未找到等级对应的行:\n{levelAndMinorAffixText}"); + }).Where(l => l != null).Cast().SingleOrDefault() ?? throw new Exception($"未找到等级对应的行:\n{levelAndMinorAffixLines}"); if (!int.TryParse(levelLine, out int level) || level < 0 || level > 20) { throw new Exception($"未识别的等级:{levelLine}"); } #endregion - return new ArtifactStat(name, mainAffix, minorAffixes, level); + return new ArtifactStat(name, mainAffix, minorAffixes.ToArray(), level); } public static ArtifactStatus GetArtifactStatus(Mat src) diff --git a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoArtifactSalvageTests/AutoArtifactSalvageTaskTests.cs b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoArtifactSalvageTests/AutoArtifactSalvageTaskTests.cs index bc450699..ae11fd79 100644 --- a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoArtifactSalvageTests/AutoArtifactSalvageTaskTests.cs +++ b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoArtifactSalvageTests/AutoArtifactSalvageTaskTests.cs @@ -213,7 +213,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoArtifactSalvageTests foreach (ArtifactAffix expectedArtifactAffix in expectedArtifactStat.MinorAffixes) { Assert.Contains(result.MinorAffixes, a => - a.Type == expectedArtifactAffix.Type && a.Value == expectedArtifactAffix.Value); + a.Type == expectedArtifactAffix.Type && a.Value == expectedArtifactAffix.Value && a.IsUnactivated == expectedArtifactAffix.IsUnactivated); } Assert.True(result.Level == expectedArtifactStat.Level); } From 8a7440dbbe3cb7a84dada4a47c6655814c8a2511 Mon Sep 17 00:00:00 2001 From: FishmanTheMurloc <162452111+FishmanTheMurloc@users.noreply.github.com> Date: Fri, 26 Sep 2025 23:15:18 +0800 Subject: [PATCH 18/20] =?UTF-8?q?ScriptObjectConverter.GetValue()=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=94=AF=E6=8C=81=E4=B8=80=E5=B1=82=E9=9B=86=E5=90=88?= =?UTF-8?q?=E6=B3=9B=E5=9E=8B=E5=8F=82=E6=95=B0=E8=A7=A3=E6=9E=90=EF=BC=9B?= =?UTF-8?q?CountInventoryItem=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81=E5=A4=8D?= =?UTF-8?q?=E6=95=B0=E7=89=A9=E5=93=81=E7=9A=84=E8=AE=A1=E6=95=B0=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E5=9C=A8Dispatcher=E4=B8=AD=E5=90=91JavaScript?= =?UTF-8?q?=E6=9A=B4=E9=9C=B2=E6=AD=A4=E7=A7=8D=E4=BD=BF=E7=94=A8=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=20(#2267)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core/Script/Dependence/Dispatcher.cs | 35 ++++- .../Core/Script/Project/ScriptProject.cs | 2 +- .../GameTask/Common/Job/CountInventoryItem.cs | 113 +++++++++++++-- .../Helpers/ScriptObjectConverter.cs | 130 +++++++++++++----- .../GridIconsAccuracyTestTaskTests.cs | 2 +- 5 files changed, 232 insertions(+), 50 deletions(-) diff --git a/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs b/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs index 85733596..f43a0d6b 100644 --- a/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs +++ b/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs @@ -14,6 +14,8 @@ using BetterGenshinImpact.ViewModel.Pages; using Microsoft.ClearScript; using Microsoft.Extensions.Logging; using System; +using System.Collections.Generic; +using System.Dynamic; using System.Threading; using System.Threading.Tasks; @@ -192,8 +194,8 @@ public class Dispatcher return null; case "AutoEat": { - string? foodName = soloTask.Config == null ? null : ScriptObjectConverter.GetValue((ScriptObject)soloTask.Config, "foodName", null); - FoodEffectType? foodEffectType = soloTask.Config == null ? null : (FoodEffectType?)ScriptObjectConverter.GetValue((ScriptObject)soloTask.Config, "foodEffectType", null); + string? foodName = soloTask.Config == null ? null : ScriptObjectConverter.GetValue((ScriptObject)soloTask.Config, "foodName", (string?)null); + FoodEffectType? foodEffectType = soloTask.Config == null ? null : (FoodEffectType?)ScriptObjectConverter.GetValue((ScriptObject)soloTask.Config, "foodEffectType", (int?)null); if (foodName != null && foodEffectType != null) { @@ -259,9 +261,32 @@ public class Dispatcher { throw new NullReferenceException($"{nameof(soloTask.Config)}为空"); } - GridScreenName gridScreenName = ScriptObjectConverter.GetValue((ScriptObject)soloTask.Config, "gridScreenName", null) ?? throw new Exception("gridScreenName为空或错误"); - string itemName = ScriptObjectConverter.GetValue((ScriptObject)soloTask.Config, "itemName", null) ?? throw new Exception("itemName为空"); - return await new CountInventoryItem(gridScreenName, itemName).Start(cancellationToken); + GridScreenName gridScreenName = ScriptObjectConverter.GetValue((ScriptObject)soloTask.Config, "gridScreenName", (GridScreenName?)null) ?? throw new Exception("gridScreenName为空或错误"); + string? itemName = ScriptObjectConverter.GetValue((ScriptObject)soloTask.Config, "itemName", (string?)null); + IEnumerable? itemNames = ScriptObjectConverter.GetValue((ScriptObject)soloTask.Config, "itemNames"); + if (itemName != null && itemNames != null) + { + throw new ArgumentException($"参数{nameof(itemName)}和{nameof(itemNames)}不能同时使用"); + } + if (itemName == null && itemNames == null) + { + throw new ArgumentException($"参数{nameof(itemName)}和{nameof(itemNames)}不能同时为空"); + } + var result = await new CountInventoryItem(gridScreenName, itemName, itemNames).Start(cancellationToken); + if (itemName != null) + { + return result; + } + else + { + dynamic expando = new ExpandoObject(); + var expandoDict = (IDictionary)expando; + foreach (var kvp in (Dictionary)result) + { + expandoDict[kvp.Key] = kvp.Value; + } + return expandoDict; + } } default: throw new ArgumentException($"未知的任务名称: {soloTask.Name}", nameof(soloTask.Name)); diff --git a/BetterGenshinImpact/Core/Script/Project/ScriptProject.cs b/BetterGenshinImpact/Core/Script/Project/ScriptProject.cs index 9486cac2..d8320d89 100644 --- a/BetterGenshinImpact/Core/Script/Project/ScriptProject.cs +++ b/BetterGenshinImpact/Core/Script/Project/ScriptProject.cs @@ -1,4 +1,4 @@ -using BetterGenshinImpact.Core.Config; +using BetterGenshinImpact.Core.Config; using Microsoft.ClearScript; using Microsoft.ClearScript.V8; using System; diff --git a/BetterGenshinImpact/GameTask/Common/Job/CountInventoryItem.cs b/BetterGenshinImpact/GameTask/Common/Job/CountInventoryItem.cs index bcb749ac..6dd49f51 100644 --- a/BetterGenshinImpact/GameTask/Common/Job/CountInventoryItem.cs +++ b/BetterGenshinImpact/GameTask/Common/Job/CountInventoryItem.cs @@ -10,13 +10,14 @@ using Microsoft.ML.OnnxRuntime; using OpenCvSharp; using System; using System.Collections.Generic; +using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace BetterGenshinImpact.GameTask.Common.Job { - internal class CountInventoryItem : ISoloTask + internal class CountInventoryItem : ISoloTask { public string Name => "背包数物品"; @@ -24,33 +25,74 @@ namespace BetterGenshinImpact.GameTask.Common.Job private readonly InputSimulator input = Simulation.SendInput; private CancellationToken ct; private readonly GridScreenName gridScreenName; - private readonly string itemName; + private readonly string? itemName; + private readonly IEnumerable? itemNames; - public CountInventoryItem(GridScreenName gridScreenName, string itemName) + public CountInventoryItem(GridScreenName gridScreenName, string? itemName = null, IEnumerable? itemNames = null) { this.gridScreenName = gridScreenName; + if (itemName != null && itemNames != null) + { + throw new ArgumentException($"参数{nameof(itemName)}和{nameof(itemNames)}不能同时使用"); + } + if (itemName == null && itemNames == null) + { + throw new ArgumentException($"参数{nameof(itemName)}和{nameof(itemNames)}不能同时为空"); + } + if (itemNames != null && !itemNames.Any()) + { + throw new ArgumentException($"参数{nameof(itemNames)}不能为空序列"); + } this.itemName = itemName; + this.itemNames = itemNames; } - public async Task Start(CancellationToken ct) + public async Task Start(CancellationToken ct) { this.ct = ct; - logger.LogInformation("打开背包并在{grid}寻找{name}……", this.gridScreenName, this.itemName); + if (this.itemName != null) + { + logger.LogInformation("打开背包并在{grid}寻找{name}……", this.gridScreenName, this.itemName!); + } + else + { + logger.LogInformation("打开背包并在{grid}寻找{first}等{count}类物品……", this.gridScreenName, this.itemNames!.First(), this.itemNames!.Count()); + } await new ReturnMainUiTask().Start(ct); await AutoArtifactSalvageTask.OpenInventory(this.gridScreenName, input, logger, this.ct); using InferenceSession session = GridIconsAccuracyTestTask.LoadModel(out Dictionary prototypes); - using var ra = TaskControl.CaptureToRectArea(); + object result; + if (this.itemName != null) + { + result = await FindOne(session, prototypes); + } + else + { + result = await FindMulti(session, prototypes); + } + + await new ReturnMainUiTask().Start(ct); + + return result; + } + + private async Task FindOne(InferenceSession session, Dictionary prototypes) + { GridScreen gridScreen = new GridScreen(GridParams.Templates[this.gridScreenName], logger, ct); int? count = null; await foreach (ImageRegion itemRegion in gridScreen) { using Mat icon = itemRegion.SrcMat.GetGridIcon(); var result = GridIconsAccuracyTestTask.Infer(icon, session, prototypes); + if (result.Item1 == null) + { + continue; + } string predName = result.Item1; - if (predName == this.itemName) + if (predName == this.itemName!) { string numStr = itemRegion.SrcMat.GetGridItemIconText(OcrFactory.Paddle); if (int.TryParse(numStr, out int num)) @@ -59,23 +101,70 @@ namespace BetterGenshinImpact.GameTask.Common.Job } else { - count = -2; logger.LogWarning("无法识别数量:{text}", numStr); + count = -2; } - break; } } if (count == null) { count = -1; - logger.LogInformation("没有找到{name}", this.itemName); + logger.LogInformation("没有找到{name}", this.itemName!); } - await new ReturnMainUiTask().Start(ct); - return count.Value; } + private async Task> FindMulti(InferenceSession session, Dictionary prototypes) + { + Dictionary itemsCountDic = new Dictionary(); + List notFoundItemNames = this.itemNames!.ToList(); + + GridScreen gridScreen = new GridScreen(GridParams.Templates[this.gridScreenName], logger, ct); + await foreach (ImageRegion itemRegion in gridScreen) + { + using Mat icon = itemRegion.SrcMat.GetGridIcon(); + var result = GridIconsAccuracyTestTask.Infer(icon, session, prototypes); + if (result.Item1 == null) + { + continue; + } + string predName = result.Item1; + if (this.itemNames!.Contains(predName) && !itemsCountDic!.ContainsKey(predName)) + { + int count; + string numStr = itemRegion.SrcMat.GetGridItemIconText(OcrFactory.Paddle); + if (int.TryParse(numStr, out int num)) + { + count = num; + } + else + { + logger.LogWarning("无法识别数量:{text}", numStr); + count = -2; + } + + if (!itemsCountDic!.TryAdd(predName, count)) + { + logger.LogWarning("重复的名称:{name}", predName); + } + + notFoundItemNames.RemoveAll(n => n == predName); + + if (notFoundItemNames.Count <= 0) + { + break; + } + } + } + + if (notFoundItemNames.Count > 0) + { + logger.LogInformation("没有找到{name}", String.Join(", ", notFoundItemNames)); + } + return itemsCountDic; + } + async Task ISoloTask.Start(CancellationToken ct) { await Start(ct); diff --git a/BetterGenshinImpact/Helpers/ScriptObjectConverter.cs b/BetterGenshinImpact/Helpers/ScriptObjectConverter.cs index 506ed447..1d0d3cfc 100644 --- a/BetterGenshinImpact/Helpers/ScriptObjectConverter.cs +++ b/BetterGenshinImpact/Helpers/ScriptObjectConverter.cs @@ -1,5 +1,6 @@ using Microsoft.ClearScript; using System; +using System.Collections.Generic; using System.Reflection; namespace BetterGenshinImpact.Helpers; @@ -44,41 +45,108 @@ public class ScriptObjectConverter { if (source[propertyName] is not Undefined && source[propertyName] != null) { - object value = source.GetProperty(propertyName); + object v8Value = source.GetProperty(propertyName); - Type type = typeof(T); - type = Nullable.GetUnderlyingType(type) ?? type; - - if (type.IsEnum) + if (TryMap(v8Value, out T value)) { - // 处理数字 - if (value is int intValue) - { - if (Enum.IsDefined(type, intValue)) - { - return (T)Enum.ToObject(type, intValue); - } - else - { - return defaultValue; - } - } - // 处理字符串 - else if (value is string strValue) - { - if (Enum.TryParse(type, strValue, ignoreCase: true, out object? parsedEnum)) - { - return (T)parsedEnum; - } - else - { - return defaultValue; - } - } + return value; } - - return (T)value; } return defaultValue; } + + /// + /// 适用集合的重载 + /// 如果解析失败,默认返回一个空集合; + /// 如果集合元素解析失败,将跳过该元素 + /// 仅支持一层集合,不能再是集合 + /// 避开反射,享受健康生活 + /// + /// + /// + /// + /// + public static IEnumerable GetValue(ScriptObject source, string propertyName) + { + if (source[propertyName] is not Undefined && source[propertyName] != null) + { + object v8Value = source.GetProperty(propertyName); + + return TryMap(v8Value); + } + return new T[0]; + } + + /// + /// 尝试将ClearScript的类型转换成常用类型 + /// + /// + /// + /// + /// + private static bool TryMap(object v8Value, out T value) + { + Type type = typeof(T); + type = Nullable.GetUnderlyingType(type) ?? type; + + if (type.IsEnum) + { + // 处理数字 + if (v8Value is int intValue) + { + if (Enum.IsDefined(type, intValue)) + { + value = (T)Enum.ToObject(type, intValue); + return true; + } + else + { + value = default!; + return false; + } + } + // 处理字符串 + else if (v8Value is string strValue) + { + if (Enum.TryParse(type, strValue, ignoreCase: true, out object? parsedEnum)) + { + value = (T)parsedEnum; + return true; + } + else + { + value = default!; + return false; + } + } + } + + value = (T)v8Value; + return true; + } + + /// + /// 尝试将ClearScript的V8Array类型转换成常用泛型集合类型 + /// 如果集合元素解析失败,将跳过该元素,如此始终能返回一个集合 + /// + /// + /// + /// + private static IEnumerable TryMap(object v8Value) + { + Type elementType = typeof(T); + var iList = (System.Collections.IList)v8Value; // V8Array虽然是私有类型无法获取,但其实现IList,可根据此接口操作 + + Type listType = typeof(List<>).MakeGenericType(elementType); + System.Collections.IList list = (System.Collections.IList)Activator.CreateInstance(listType)!; + foreach (var elementV8Value in iList) + { + if (TryMap(elementV8Value, out T elementValue)) + { + list.Add(elementValue); + } + } + + return (IEnumerable)list; + } } diff --git a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/GetGridIconsTests/GridIconsAccuracyTestTaskTests.cs b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/GetGridIconsTests/GridIconsAccuracyTestTaskTests.cs index 7297d158..4d84245b 100644 --- a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/GetGridIconsTests/GridIconsAccuracyTestTaskTests.cs +++ b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/GetGridIconsTests/GridIconsAccuracyTestTaskTests.cs @@ -46,7 +46,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.GetGridIconsTests var rows = GridScreen.GridEnumerator.ClusterRows(gridItems, 10); // - var result = new List<(string, int)>(); + var result = new List<(string?, int)>(); foreach (var row in rows) { foreach (Rect rect in row) From 4c5cdb0ccf995696858a512482c5ee40fded2a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Fri, 26 Sep 2025 23:29:29 +0800 Subject: [PATCH 19/20] =?UTF-8?q?=E4=BF=AE=E5=A4=8DItemsSource=3D'=201234'?= =?UTF-8?q?=20=E5=AF=BC=E8=87=B4XAML=E8=AE=BE=E8=AE=A1=E5=99=A8=E6=8A=A5?= =?UTF-8?q?=E9=94=99=E7=9A=84=E9=97=AE=E9=A2=98=20#2039?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml | 3 +-- BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml | 3 +-- .../ViewModel/Pages/TaskSettingsPageViewModel.cs | 2 ++ 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml b/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml index db677611..22ebe8ec 100644 --- a/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml +++ b/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml @@ -779,10 +779,9 @@ HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Margin="10,0,10,0" - ItemsSource=' 1234' + ItemsSource="{Binding Source={x:Static pages:TaskSettingsPageViewModel.AvatarIndexList}}" SelectedIndex="{Binding Config.AutoFightConfig.GuardianAvatar, Mode=TwoWay}" SelectedItem="{Binding Config.AutoFightConfig.GuardianAvatar, Mode=TwoWay}" /> - - BossNumList = [1, 2, 3]; + public static List AvatarIndexList = ["", "1", "2", "3", "4"]; + [ObservableProperty] private List _autoMusicLevelList = ["传说", "大师", "困难", "普通", "所有"]; From 24d1bab2d410b67bc02a43f21753f21e89938450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sat, 27 Sep 2025 00:49:47 +0800 Subject: [PATCH 20/20] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=8B=BE=E5=8F=96?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=A8=A1=E7=B3=8A=E5=8C=B9=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GameTask/AutoPick/AutoPickTrigger.cs | 41 +++++- .../View/Pages/TriggerSettingsPage.xaml | 2 +- .../Pages/TriggerSettingsPageViewModel.cs | 131 +++++++++++++++--- 3 files changed, 150 insertions(+), 24 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs b/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs index b4983e77..3bd20911 100644 --- a/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs +++ b/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; @@ -36,6 +37,11 @@ public partial class AutoPickTrigger : ITaskTrigger /// 拾取黑名单 /// private HashSet _blackList = []; + + /// + /// 拾取黑名单(模糊匹配) + /// + private List _fuzzyBlackList = []; /// /// 拾取白名单 @@ -71,6 +77,7 @@ public partial class AutoPickTrigger : ITaskTrigger { _blackList.UnionWith(userBlackList); } + _fuzzyBlackList = ReadTextList(@"User\pick_black_lists.txt"); } if (config.WhiteListEnabled) @@ -117,6 +124,26 @@ public partial class AutoPickTrigger : ITaskTrigger return []; } + + private List ReadTextList(string textFilePath) + { + try + { + var txt = Global.ReadAllTextIfExist(textFilePath); + if (!string.IsNullOrEmpty(txt)) + { + // 明确指定使用 char[] 重载版本 + return [..txt.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries)]; + } + } + catch (Exception e) + { + _logger.LogError(e, "读取拾取黑/白名单失败"); + MessageBox.Error("读取拾取黑/白名单失败,请确认修改后的拾取黑/白名单内容格式是否正确!"); + } + + return []; + } /// @@ -310,9 +337,19 @@ public partial class AutoPickTrigger : ITaskTrigger return; } - if (config.BlackListEnabled && _blackList.Contains(text)) + if (config.BlackListEnabled) { - return; + if (_blackList.Contains(text)) + { + return; + } + if (_fuzzyBlackList.Count>0) + { + if (_fuzzyBlackList.Any(item => text.Contains(item))) + { + return; + } + } } speedTimer.Record("黑名单判断"); diff --git a/BetterGenshinImpact/View/Pages/TriggerSettingsPage.xaml b/BetterGenshinImpact/View/Pages/TriggerSettingsPage.xaml index dfbf5408..be096b89 100644 --- a/BetterGenshinImpact/View/Pages/TriggerSettingsPage.xaml +++ b/BetterGenshinImpact/View/Pages/TriggerSettingsPage.xaml @@ -1,4 +1,4 @@ -