diff --git a/.gitignore b/.gitignore index 0b36a13e..6c21f5d7 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,7 @@ Tmp/ /packages/ node_modules/ -*.zip \ No newline at end of file +*.zip + +# Rider +.idea diff --git a/BetterGenshinImpact/GameTask/AutoWood/Assets/AutoWoodAssets.cs b/BetterGenshinImpact/GameTask/AutoWood/Assets/AutoWoodAssets.cs index cea8e05b..a7047c99 100644 --- a/BetterGenshinImpact/GameTask/AutoWood/Assets/AutoWoodAssets.cs +++ b/BetterGenshinImpact/GameTask/AutoWood/Assets/AutoWoodAssets.cs @@ -14,8 +14,14 @@ public class AutoWoodAssets : BaseAssets public RecognitionObject ConfirmRo; public RecognitionObject EnterGameRo; + // 木头数量 + public Rect WoodCountUpperRect; + private AutoWoodAssets() { + + WoodCountUpperRect = new Rect((int)(100 * AssetScale), (int)(450 * AssetScale), (int)(300 * AssetScale), (int)(250 * AssetScale)); + //「王树瑞佑」 TheBoonOfTheElderTreeRo = new RecognitionObject { diff --git a/BetterGenshinImpact/GameTask/AutoWood/AutoWoodTask.cs b/BetterGenshinImpact/GameTask/AutoWood/AutoWoodTask.cs index eb3ccca2..ee1c903e 100644 --- a/BetterGenshinImpact/GameTask/AutoWood/AutoWoodTask.cs +++ b/BetterGenshinImpact/GameTask/AutoWood/AutoWoodTask.cs @@ -10,11 +10,15 @@ using BetterGenshinImpact.View.Drawable; using BetterGenshinImpact.ViewModel.Pages; using Microsoft.Extensions.Logging; using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using static BetterGenshinImpact.GameTask.Common.TaskControl; using static Vanara.PInvoke.User32; using GC = System.GC; +using BetterGenshinImpact.Core.Recognition.OCR; +using System.Text.RegularExpressions; +using System.Collections.Concurrent; namespace BetterGenshinImpact.GameTask.AutoWood; @@ -26,6 +30,12 @@ public class AutoWoodTask private readonly AutoWoodAssets _assets; private bool _first = true; + + private bool _shouldContinue = true; + private int _nothingCount; + private bool _firstWoodOcr = true; + private readonly ConcurrentDictionary _woodTotalDict; + private readonly Dictionary _enumWoodDict; private readonly Login3rdParty _login3rdParty; @@ -36,6 +46,8 @@ public class AutoWoodTask _login3rdParty = new(); AutoWoodAssets.DestroyInstance(); _assets = AutoWoodAssets.Instance; + _woodTotalDict = new ConcurrentDictionary(); + _enumWoodDict = new Dictionary(); } public void Start(WoodTaskParam taskParam) @@ -77,8 +89,13 @@ public class AutoWoodTask SystemControl.ActivateWindow(); for (var i = 0; i < taskParam.WoodRoundNum; i++) { + if (_nothingCount >= 3) + { + Logger.LogInformation("连续{Cnt}次获取木材数量为0。已达每日上限或者当前地图中没有树木", _nothingCount); + break; + } Logger.LogInformation("第{Cnt}次伐木", i + 1); - if (taskParam.Cts.IsCancellationRequested) + if (taskParam.Cts.IsCancellationRequested || !_shouldContinue) { break; } @@ -111,6 +128,176 @@ public class AutoWoodTask } } } + + private void RecognizeWoodCount(WoodTaskParam taskParam) + { + var firstTextFound = false; + var recognizedText = ""; + var firstOcrResultList = new List(); + + // 创建一个计时器,循环识别文本,直到超时 + var stopwatch = Stopwatch.StartNew(); + while (stopwatch.ElapsedMilliseconds < 3000) // 3秒超时 + { + // OCR识别木材文本 + recognizedText = PerformOcr(taskParam, firstTextFound, firstOcrResultList); + if (firstTextFound) + { + _nothingCount = 0; + break; + } + + firstTextFound = StartRecognizedText(recognizedText); + } + stopwatch.Stop(); // 停止计时 + + ProcessRecognizedText(taskParam, recognizedText, firstOcrResultList); + } + + private string PerformOcr(WoodTaskParam taskParam, bool firstTextFound, List firstOcrResultList) + { + if (_firstWoodOcr) + { + var firstWoodCountRect = CaptureToRectArea().DeriveCrop(_assets.WoodCountUpperRect); + var recognizedText = OcrFactory.Paddle.Ocr(firstWoodCountRect.SrcGreyMat); + firstOcrResultList.Add(recognizedText); + Sleep(500, taskParam.Cts); + return recognizedText; + } + + if (firstTextFound) + { + // 多个木材时休眠下,然后重新OCR识别所有的文本。因为多个时木材文本是依次出现的,有延迟 + Sleep(200 * _woodTotalDict.Keys.Count, taskParam.Cts); + } + + var woodCountRect = CaptureToRectArea().DeriveCrop(_assets.WoodCountUpperRect); + return OcrFactory.Paddle.Ocr(woodCountRect.SrcGreyMat); + } + + private static bool StartRecognizedText(string recognizedText) + { + return !string.IsNullOrEmpty(recognizedText) && + recognizedText.Contains("获得") && + (recognizedText.Contains('×') || recognizedText.Contains('x')); + } + + private void ProcessRecognizedText(WoodTaskParam taskParam, string recognizedText, List firstOcrResultList) + { + if (_firstWoodOcr) + { + // 首次识别时,找到最长的OCR结果 + recognizedText = FindLongestOcrResult(firstOcrResultList, recognizedText); + } + + if (!string.IsNullOrEmpty(recognizedText)) + { + ParseWoodCount(taskParam, recognizedText); + } + else + { + _nothingCount++; + Logger.LogWarning("未能识别到伐木数量"); + return; + } + + CheckWoodQuantitiesAndContinue(taskParam); + } + + private void ParseWoodCount(WoodTaskParam taskParam, string text) + { + // 从识别的文本中提取木材名称和数量 + // 格式示例:"获得\n竹节×30\n杉木×20" + int index = text.IndexOf('×'); + if (index == -1) + { + index = text.IndexOf('X'); + } + + if (index != -1) + { + // 匹配模式 "名称×数量",其中名称可能包含中文或字母,数量为数字 + var matches = Regex.Matches(text, @"([^\d\n]+)[×x](\d+)"); + + // 如果OCR识别木材的种类小于等于最初保存的一样时,直接使用最初的木材数量。 + if (!_firstWoodOcr && 1 <= matches.Count && matches.Count <= _enumWoodDict.Count) + { + foreach (var entry in _enumWoodDict.Where(entry => entry.Value <= taskParam.WoodDailyMaxCount)) + { + UpdateWoodCount(entry.Key, entry.Value); + } + } + else + { + foreach (Match match in matches) + { + if (match.Success) + { + var materialName = match.Groups[1].Value.Trim(); + var quantityStr = match.Groups[2].Value.Trim(); + var quantity = int.Parse(quantityStr); + Debug.WriteLine($"首次获取木材的名称:{materialName}, 数量:{quantity}"); + UpdateWoodCount(materialName, quantity); + } + else + { + Logger.LogWarning("识别到的数量不是有效的整数:{woodText}", text); + } + } + + // 所有数据都保存一遍后,首次OCR识别结束 + _firstWoodOcr = false; + } + } + else + { + Logger.LogWarning("未能正确解析木材信息格式:{woodText}", text); + } + } + + private void UpdateWoodCount(string materialName, int quantity) + { + // 检查字典中是否已包含这种木材名称 + if (!_firstWoodOcr && !_woodTotalDict.ContainsKey(materialName)) + { + Logger.LogWarning("未知的木材名:{woodName},数量{Cnt}", materialName, quantity); + } + _woodTotalDict.AddOrUpdate( + key: materialName, + addValue: quantity, + updateValueFactory: (_, existingValue) => existingValue + quantity + ); + if (_firstWoodOcr) + { + // 保存木材单次获取的值 + _enumWoodDict.Add(materialName, quantity); + } + } + + private static string FindLongestOcrResult(List firstOcrResultList, string recognizedText) + { + foreach (var str in firstOcrResultList.Where(str => str.Length > recognizedText.Length)) + { + recognizedText = str; + } + + return recognizedText; + } + + private void CheckWoodQuantitiesAndContinue(WoodTaskParam taskParam) + { + var allMax = true; + foreach (var entry in _woodTotalDict) + { + // 打印每个条目的键(木材名称)和值(数量) + Logger.LogInformation("木材{woodName}累积获取数量:{Cnt}", entry.Key, entry.Value); + // 检查木材是否超过每日上限 + if (entry.Value < taskParam.WoodDailyMaxCount) allMax = false; + else Logger.LogInformation("木材{Name}已达到每日数量上限:{Count}", entry.Key, entry.Value); + } + + _shouldContinue = !_woodTotalDict.IsEmpty || !allMax; + } private void Felling(WoodTaskParam taskParam, bool isLast = false) { @@ -121,6 +308,9 @@ public class AutoWoodTask { return; } + + // 计算木材数量 + RecognizeWoodCount(taskParam); // 2. 按下 ESC 打开菜单 并退出游戏 PressEsc(taskParam); diff --git a/BetterGenshinImpact/GameTask/AutoWood/WoodTaskParam.cs b/BetterGenshinImpact/GameTask/AutoWood/WoodTaskParam.cs index fe46cb86..856e95e4 100644 --- a/BetterGenshinImpact/GameTask/AutoWood/WoodTaskParam.cs +++ b/BetterGenshinImpact/GameTask/AutoWood/WoodTaskParam.cs @@ -7,6 +7,7 @@ namespace BetterGenshinImpact.GameTask.AutoWood; public class WoodTaskParam : BaseTaskParam { public int WoodRoundNum { get; set; } + public int WoodDailyMaxCount { get; set; } public WoodTaskParam(CancellationTokenSource cts, int woodRoundNum) : base(cts) { @@ -15,5 +16,7 @@ public class WoodTaskParam : BaseTaskParam { WoodRoundNum = 9999; } + + WoodDailyMaxCount = 2000; } }