diff --git a/BetterGenshinImpact/Core/Simulator/PostMessageSimulator.cs b/BetterGenshinImpact/Core/Simulator/PostMessageSimulator.cs index cffcbbc8..cd0eac20 100644 --- a/BetterGenshinImpact/Core/Simulator/PostMessageSimulator.cs +++ b/BetterGenshinImpact/Core/Simulator/PostMessageSimulator.cs @@ -1,5 +1,4 @@ using System; -using System.Formats.Asn1; using System.Threading; using Vanara.PInvoke; @@ -65,6 +64,15 @@ public class PostMessageSimulator return this; } + public PostMessageSimulator LongKeyPress(User32.VK vk) + { + User32.PostMessage(_hWnd, User32.WindowMessage.WM_KEYDOWN, (nint)vk, 0x1e0001); + Thread.Sleep(1000); + User32.PostMessage(_hWnd, User32.WindowMessage.WM_CHAR, (nint)vk, 0x1e0001); + User32.PostMessage(_hWnd, User32.WindowMessage.WM_KEYUP, (nint)vk, (nint)0xc01e0001); + return this; + } + public PostMessageSimulator KeyDown(User32.VK vk) { User32.PostMessage(_hWnd, User32.WindowMessage.WM_KEYDOWN, (nint)vk, 0x1e0001); diff --git a/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs b/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs index 8edbf117..27832aa1 100644 --- a/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs +++ b/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using BetterGenshinImpact.GameTask.AutoFight.Model; using static BetterGenshinImpact.GameTask.Common.TaskControl; using static Vanara.PInvoke.User32; +using BetterGenshinImpact.GameTask.AutoFight; namespace BetterGenshinImpact.GameTask.AutoDomain; @@ -16,12 +17,12 @@ public class AutoDomainTask private AutoDomainParam _taskParam; - private readonly PostMessageSimulator _postMessage; + private readonly PostMessageSimulator _simulator; public AutoDomainTask(AutoDomainParam taskParam) { _taskParam = taskParam; - _postMessage = Simulation.PostMessage(TaskContext.Instance().GameHandle); + _simulator = AutoFightContext.Instance().Simulator; } public void Start() @@ -31,6 +32,16 @@ public class AutoDomainTask Init(); var combatScenes = new CombatScenes(); combatScenes.InitializeTeam(GetContentFromDispatcher()); + // test code + for (int i = 3 - 1; i >= 0; i--) + { + combatScenes.Avatars[0].Switch(); + combatScenes.Avatars[1].Switch(); + combatScenes.Avatars[2].Switch(); + combatScenes.Avatars[3].Switch(); + Sleep(3000); + } + // 1. 走到钥匙处启动 // WalkToStartDomain(); @@ -61,7 +72,7 @@ public class AutoDomainTask { await Task.Run(() => { - _postMessage.KeyDown(VK.VK_W); + _simulator.KeyDown(VK.VK_W); try { while (true) @@ -82,7 +93,7 @@ public class AutoDomainTask } finally { - _postMessage.KeyUp(VK.VK_W); + _simulator.KeyUp(VK.VK_W); } }); } diff --git a/BetterGenshinImpact/GameTask/AutoFight/Assets/AutoFightAssets.cs b/BetterGenshinImpact/GameTask/AutoFight/Assets/AutoFightAssets.cs index b1afeb6b..1b7b1b35 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/Assets/AutoFightAssets.cs +++ b/BetterGenshinImpact/GameTask/AutoFight/Assets/AutoFightAssets.cs @@ -1,14 +1,29 @@ using BetterGenshinImpact.Core.Recognition; +using OpenCvSharp; namespace BetterGenshinImpact.GameTask.AutoFight.Assets; public class AutoFightAssets { + public Rect TeamRect; + public Rect ERect; + public Rect QRect; public RecognitionObject WandererIconRa; + public AutoFightAssets() { var info = TaskContext.Instance().SystemInfo; + var captureRect = info.CaptureAreaRect; + var assetScale = info.AssetScale; + + TeamRect = new Rect(captureRect.Width - (int)(355 * assetScale), (int)(220 * assetScale), + (int)(355 * assetScale), (int)(465 * assetScale)); + ERect = new Rect(captureRect.Width - (int)(267 * assetScale), captureRect.Height - (int)(132 * assetScale), + (int)(77 * assetScale), (int)(77 * assetScale)); + QRect = new Rect(captureRect.Width - (int)(157 * assetScale), captureRect.Height - (int)(165 * assetScale), + (int)(110 * assetScale), (int)(110 * assetScale)); + WandererIconRa = new RecognitionObject { Name = "WandererIcon", diff --git a/BetterGenshinImpact/GameTask/AutoFight/AutoFightContext.cs b/BetterGenshinImpact/GameTask/AutoFight/AutoFightContext.cs new file mode 100644 index 00000000..01871da5 --- /dev/null +++ b/BetterGenshinImpact/GameTask/AutoFight/AutoFightContext.cs @@ -0,0 +1,48 @@ +using BetterGenshinImpact.Core.Simulator; +using BetterGenshinImpact.GameTask.AutoFight.Assets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BetterGenshinImpact.GameTask.AutoFight; + +/// +/// 自动战斗上下文 +/// 请在启动BetterGI以后再初始化 +/// +public class AutoFightContext +{ + + private static AutoFightContext? _uniqueInstance; + private static readonly object Locker = new(); + + private AutoFightContext() + { + FightAssets = new(); + Simulator = Simulation.PostMessage(TaskContext.Instance().GameHandle); + } + + public static AutoFightContext Instance() + { + if (_uniqueInstance == null) + { + lock (Locker) + { + _uniqueInstance ??= new AutoFightContext(); + } + } + return _uniqueInstance; + } + + /// + /// find资源 + /// + public readonly AutoFightAssets FightAssets; + + /// + /// 战斗专用的PostMessage模拟键鼠操作 + /// + public readonly PostMessageSimulator Simulator; +} \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs b/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs index 19decfc7..c404c8e1 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs +++ b/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs @@ -1,4 +1,11 @@ -using BetterGenshinImpact.GameTask.AutoFight.Config; +using System.Linq; +using BetterGenshinImpact.GameTask.AutoFight.Config; +using OpenCvSharp; +using System.Threading; +using Microsoft.Extensions.Logging; +using Vanara.PInvoke; +using static BetterGenshinImpact.GameTask.Common.TaskControl; +using BetterGenshinImpact.Core.Recognition.OpenCv; namespace BetterGenshinImpact.GameTask.AutoFight.Model; @@ -42,20 +49,26 @@ public class Avatar /// public double BurstCd { get; set; } - /// - /// 元素战技是否就绪 - /// - public bool IsSkillReady { get; set; } - /// /// 元素爆发是否就绪 /// public bool IsBurstReady { get; set; } - public Avatar(string name, int index) + /// + /// 名字所在矩形位置 + /// + public Rect NameRect { get; set; } + + /// + /// 名字右边的编号位置 + /// + public Rect IndexRect { get; set; } + + public Avatar(string name, int index, Rect nameRect) { Name = name; Index = index; + NameRect = nameRect; var ca = DefaultAutoFightConfig.CombatAvatarMap[name]; NameEn = ca.NameEn; @@ -64,4 +77,154 @@ public class Avatar SkillHoldCd = ca.SkillHoldCd; BurstCd = ca.BurstCd; } + + /// + /// 切换到本角色 + /// 切换cd是1秒,如果切换失败,会尝试再次切换,最多尝试5次 + /// + public void Switch() + { + for (var i = 0; i < 5; i++) + { + if (IsActive(GetContentFromDispatcher())) + { + return; + } + + AutoFightContext.Instance().Simulator.KeyPress(User32.VK.VK_1 + (byte)Index - 1); + Thread.Sleep(1050); // 比1秒多一点,给截图留出时间 + } + } + + /// + /// 是否出战状态 + /// + /// + public bool IsActive(CaptureContent content) + { + // 通过寻找右侧人物编号来判断是否出战 + if (IndexRect == Rect.Empty) + { + var assetScale = TaskContext.Instance().SystemInfo.AssetScale; + // 剪裁出队伍区域 + var teamRa = content.CaptureRectArea.Crop(AutoFightContext.Instance().FightAssets.TeamRect); + var blockX = NameRect.X + NameRect.Width * 2 - 10; + var block = teamRa.Crop(new Rect(blockX, NameRect.Y, teamRa.Width - blockX, NameRect.Height * 2)); + // 取白色区域 + var bMat = OpenCvCommonHelper.Threshold(block.SrcMat, new Scalar(255, 255, 255), new Scalar(255, 255, 255)); + // 矩形识别 + Cv2.FindContours(bMat, out var contours, out _, RetrievalModes.External, + ContourApproximationModes.ApproxSimple); + if (contours.Length > 0) + { + var boxes = contours.Select(Cv2.BoundingRect).Where(w => w.Width >= 20 * assetScale && w.Height >= 18 * assetScale).OrderByDescending(w => w.Width).ToList(); + if (boxes.Any()) + { + IndexRect = boxes.First(); + return false; + } + } + } + else + { + // 剪裁出IndexRect区域 + var teamRa = content.CaptureRectArea.Crop(AutoFightContext.Instance().FightAssets.TeamRect); + var blockX = NameRect.X + NameRect.Width * 2 - 10; + var indexBlock = teamRa.Crop(new Rect(blockX + IndexRect.X, NameRect.Y + IndexRect.Y, IndexRect.Width, IndexRect.Height)); + Cv2.ImWrite("indexBlock_" + Name + ".png", indexBlock.SrcMat); + int count = OpenCvCommonHelper.CountGrayMatColor(indexBlock.SrcGreyMat, 255); + if (count * 1.0 / (IndexRect.Width * IndexRect.Height) > 0.7) + { + return false; + } + } + + Logger.LogInformation("{Name} 当前出战", Name); + return true; + } + + /// + /// 使用元素战技 E + /// + public void UseSkill(bool hold = false) + { + var cd = GetSkillCurrentCd(GetContentFromDispatcher()); + if (cd > 0) + { + Logger.LogInformation("{Name} 元素战技仍在CD中,还剩{Cd}s,跳过此操作", Name, cd); + } + else + { + for (var i = 0; i < 10; i++) + { + if (hold) + { + AutoFightContext.Instance().Simulator.KeyPress(User32.VK.VK_E); + } + else + { + AutoFightContext.Instance().Simulator.KeyPress(User32.VK.VK_E); + } + + cd = GetSkillCurrentCd(GetContentFromDispatcher()); + if (cd > 0) + { + Logger.LogInformation(hold ? "{Name} 长按元素战技" : "{Name} 点按元素战技", Name); + // todo 把cd加入执行队列 + return; + } + + Thread.Sleep(500); + } + } + } + + /// + /// 元素战技是否正在CD中 + /// 右下 267x132 + /// 77x77 + /// + public double GetSkillCurrentCd(CaptureContent content) + { + var eRa = content.CaptureRectArea.Crop(AutoFightContext.Instance().FightAssets.ERect); + return 0; + } + + /// + /// 使用元素爆发 Q + /// + public void UseBurst() + { + var cd = GetBurstCurrentCd(GetContentFromDispatcher()); + if (cd > 0) + { + Logger.LogInformation("{Name} 元素爆发仍在CD中,还剩{Cd}s,跳过此操作", Name, cd); + } + else + { + for (var i = 0; i < 10; i++) + { + AutoFightContext.Instance().Simulator.KeyPress(User32.VK.VK_Q); + cd = GetBurstCurrentCd(GetContentFromDispatcher()); + if (cd > 0) + { + Logger.LogInformation("{Name} 释放元素爆发", Name); + // todo 把cd加入执行队列 + return; + } + + Thread.Sleep(500); + } + } + } + + /// + /// 元素爆发是否正在CD中 + /// 右下 157x165 + /// 110x110 + /// + public double GetBurstCurrentCd(CaptureContent content) + { + return 0; + } } \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/AutoFight/Model/CombatScenes.cs b/BetterGenshinImpact/GameTask/AutoFight/Model/CombatScenes.cs index 3f9aeb67..7d2638a7 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/Model/CombatScenes.cs +++ b/BetterGenshinImpact/GameTask/AutoFight/Model/CombatScenes.cs @@ -21,10 +21,6 @@ public class CombatScenes /// 当前配队 /// public Avatar[] Avatars { get; set; } = new Avatar[5]; - /// - /// find资源 - /// - private readonly AutoFightAssets _assets = new(); /// /// 通过OCR识别队伍内角色 @@ -32,11 +28,8 @@ public class CombatScenes /// 完整游戏画面的捕获截图 public void InitializeTeam(CaptureContent content) { - var captureRect = TaskContext.Instance().SystemInfo.CaptureAreaRect; - var assetScale = TaskContext.Instance().SystemInfo.AssetScale; // 剪裁出队伍区域 - var teamRa = content.CaptureRectArea.Crop(new Rect(captureRect.Width - (int)(355 * assetScale), (int)(220 * assetScale), - (int)(355 * assetScale), (int)(465 * assetScale))); + var teamRa = content.CaptureRectArea.Crop(AutoFightContext.Instance().FightAssets.TeamRect); // 识别队伍内角色 var result = OcrFactory.Paddle.OcrResult(teamRa.SrcGreyMat); @@ -45,7 +38,16 @@ public class CombatScenes private void ParseTeamOcrResult(PaddleOcrResult result, RectArea rectArea) { - List names = (from item in result.Regions where DefaultAutoFightConfig.CombatAvatarNames.Contains(item.Text) select item.Text).ToList(); + List names = new(); + List nameRects = new(); + foreach (var item in result.Regions) + { + if (DefaultAutoFightConfig.CombatAvatarNames.Contains(item.Text)) + { + names.Add(item.Text); + nameRects.Add(item.Rect.BoundingRect()); + } + } if (names.Count < 3 || names.Count > 5) { @@ -56,7 +58,7 @@ public class CombatScenes if (names.Count == 3) { // 4人以上的队伍,不支持流浪者的识别 - var wanderer = rectArea.Find(_assets.WandererIconRa); + var wanderer = rectArea.Find(AutoFightContext.Instance().FightAssets.WandererIconRa); if (wanderer.IsEmpty()) { Logger.LogWarning("识别到的队伍角色数量不正确,当前识别结果:{Text}", string.Join(",", names)); @@ -69,12 +71,14 @@ public class CombatScenes if (DefaultAutoFightConfig.CombatAvatarNames.Contains(item.Text)) { names.Add(item.Text); + nameRects.Add(item.Rect.BoundingRect()); } var rect = item.Rect.BoundingRect(); - if (rect.Y > wanderer.Y && wanderer.Y + wanderer.Height > rect.Y+ rect.Height && StringUtils.IsChinese(item.Text)) + if (rect.Y > wanderer.Y && wanderer.Y + wanderer.Height > rect.Y + rect.Height && StringUtils.IsChinese(item.Text)) { names.Add(item.Text); + nameRects.Add(item.Rect.BoundingRect()); } } @@ -84,16 +88,17 @@ public class CombatScenes } } } + Logger.LogInformation("识别到的队伍角色:{Text}", string.Join(",", names)); - Avatars = BuildAvatars(names); + Avatars = BuildAvatars(names, nameRects); } - private Avatar[] BuildAvatars(List names) + private Avatar[] BuildAvatars(List names, List nameRects) { var avatars = new Avatar[5]; for (var i = 0; i < names.Count; i++) { - avatars[i] = new Avatar(names[i], i); + avatars[i] = new Avatar(names[i], i + 1, nameRects[i]); } return avatars; diff --git a/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs b/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs index 930181d6..8a2f27e5 100644 --- a/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs +++ b/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs @@ -5,6 +5,7 @@ using BetterGenshinImpact.Core.Recognition.OpenCv; using BetterGenshinImpact.Core.Simulator; using BetterGenshinImpact.GameTask.AutoFishing.Assets; using BetterGenshinImpact.GameTask.AutoFishing.Model; +using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception; using BetterGenshinImpact.GameTask.Model; using BetterGenshinImpact.Helpers; using BetterGenshinImpact.Helpers.Extensions; @@ -17,7 +18,6 @@ using Fischless.GameCapture; using GeniusInvokationAutoToy.Utils; using Microsoft.Extensions.Logging; using OpenCvSharp; -using OpenCvSharp.Extensions; using System; using System.Collections.Generic; using System.Diagnostics; @@ -25,12 +25,10 @@ using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Threading; -using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception; using static Vanara.PInvoke.User32; using Color = System.Drawing.Color; using Pen = System.Drawing.Pen; using Point = OpenCvSharp.Point; -using System.Runtime.Intrinsics.Arm; namespace BetterGenshinImpact.GameTask.AutoFishing { diff --git a/BetterGenshinImpact/GameTask/TaskContext.cs b/BetterGenshinImpact/GameTask/TaskContext.cs index e5d9e32c..d5f58f66 100644 --- a/BetterGenshinImpact/GameTask/TaskContext.cs +++ b/BetterGenshinImpact/GameTask/TaskContext.cs @@ -1,13 +1,9 @@ -using BetterGenshinImpact.GameTask.Model; -using System; -using System.Diagnostics; -using BetterGenshinImpact.Helpers; -using Vanara.PInvoke; -using System.Windows.Interop; -using BetterGenshinImpact.Core.Config; -using BetterGenshinImpact.View; -using BetterGenshinImpact.Service; +using BetterGenshinImpact.Core.Config; +using BetterGenshinImpact.GameTask.Model; using BetterGenshinImpact.Genshin.Settings; +using BetterGenshinImpact.Helpers; +using BetterGenshinImpact.Service; +using System; namespace BetterGenshinImpact.GameTask {