using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.Core.Recognition.OCR; using BetterGenshinImpact.Core.Simulator; using BetterGenshinImpact.GameTask.AutoFight; using BetterGenshinImpact.GameTask.AutoFight.Model; using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception; using BetterGenshinImpact.GameTask.AutoPick.Assets; using BetterGenshinImpact.GameTask.Common.MiniMap; using BetterGenshinImpact.Helpers; using BetterGenshinImpact.View.Drawable; using BetterGenshinImpact.ViewModel.Pages; using Compunet.YoloV8; using Microsoft.Extensions.Logging; using OpenCvSharp; using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing.Imaging; using System.IO; using System.Threading; using System.Threading.Tasks; using static BetterGenshinImpact.GameTask.Common.TaskControl; using static Vanara.PInvoke.User32; namespace BetterGenshinImpact.GameTask.AutoDomain; public class AutoDomainTask { private readonly AutoPickAssets _autoPickAssets = new(); private readonly AutoDomainParam _taskParam; private readonly PostMessageSimulator _simulator; private readonly YoloV8 _predictor = new(Global.Absolute("Assets\\Model\\Domain\\bgi_tree.onnx")); private readonly ClickOffset _clickOffset; public AutoDomainTask(AutoDomainParam taskParam) { _taskParam = taskParam; _simulator = AutoFightContext.Instance().Simulator; var captureArea = TaskContext.Instance().SystemInfo.CaptureAreaRect; var assetScale = TaskContext.Instance().SystemInfo.AssetScale; _clickOffset = new ClickOffset(captureArea.X, captureArea.Y, assetScale); } public async void Start() { try { Init(); var combatScenes = new CombatScenes().InitializeTeam(GetContentFromDispatcher()); // 前置进入秘境 EnterDomain(); for (var i = 0; i < _taskParam.DomainRoundNum; i++) { // 0. 关闭秘境提示 CloseDomainTip(); // 队伍没初始化成功则重试 RetryTeamInit(combatScenes); // 1. 走到钥匙处启动 Logger.LogInformation("自动秘境:{Text}", "1. 走到钥匙处启动"); await WalkToPressF(); // 2. 执行战斗(战斗线程、视角线程、检测战斗完成线程) Logger.LogInformation("自动秘境:{Text}", "2. 执行战斗策略"); await StartFight(combatScenes); // 3. 寻找石化古树 并左右移动直到石化古树位于屏幕中心 Logger.LogInformation("自动秘境:{Text}", "3. 寻找石化古树"); await FindPetrifiedTree(); // 4. 走到石化古树处 Logger.LogInformation("自动秘境:{Text}", "4. 走到石化古树处"); await WalkToPressF(); // 5. 快速领取奖励并判断是否有下一轮 Logger.LogInformation("自动秘境:{Text}", "5. 领取奖励"); GettingTreasure(); } } catch (NormalEndException) { Logger.LogInformation("手动中断自动秘境"); } catch (Exception e) { Logger.LogInformation(e.Message); } finally { VisionContext.Instance().DrawContent.ClearAll(); TaskTriggerDispatcher.Instance().SetOnlyCaptureMode(false); TaskSettingsPageViewModel.SetSwitchAutoDomainButtonText(false); Logger.LogInformation("→ {Text}", "自动秘境结束"); } } private void Init() { if (_taskParam.DomainRoundNum == 9999) { Logger.LogInformation("→ {Text} 用尽所有体力后结束", "自动秘境,启动!"); } else { Logger.LogInformation("→ {Text} 设置总次数:{Cnt}", "自动秘境,启动!", _taskParam.DomainRoundNum); } SystemControl.ActivateWindow(); TaskTriggerDispatcher.Instance().SetOnlyCaptureMode(true); } private void RetryTeamInit(CombatScenes combatScenes) { if (!combatScenes.CheckTeamInitialized()) { combatScenes.InitializeTeam(GetContentFromDispatcher()); if (!combatScenes.CheckTeamInitialized()) { throw new Exception("识别队伍角色并初始化失败"); } } } private void EnterDomain() { var fightAssets = AutoFightContext.Instance().FightAssets; var fRectArea = GetContentFromDispatcher().CaptureRectArea.Find(_autoPickAssets.FRo); if (!fRectArea.IsEmpty()) { Simulation.SendInputEx.Keyboard.KeyDown(VK.VK_F); Logger.LogInformation("自动秘境:{Text}", "进入秘境"); // 秘境开门动画 5s Sleep(5000, _taskParam.Cts); } int retryTimes = 0, clickCount = 0; while (retryTimes < 10 && clickCount < 2) { retryTimes++; var confirmRectArea = GetContentFromDispatcher().CaptureRectArea.Find(fightAssets.ConfirmRa); if (!confirmRectArea.IsEmpty()) { confirmRectArea.ClickCenter(); clickCount++; } Sleep(1000, _taskParam.Cts); } // 载入动画 Sleep(3000, _taskParam.Cts); } private void CloseDomainTip() { // 2min的载入时间总够了吧 var retryTimes = 0; while (retryTimes < 120) { retryTimes++; var cactRectArea = GetContentFromDispatcher().CaptureRectArea.Find(AutoFightContext.Instance().FightAssets.ClickAnyCloseTipRa); if (!cactRectArea.IsEmpty()) { Sleep(1000, _taskParam.Cts); cactRectArea.ClickCenter(); break; } // todo 添加小地图角标位置检测 防止有人手点了 Sleep(1000, _taskParam.Cts); } Sleep(1500, _taskParam.Cts); } /// /// 走到钥匙处启动 /// private async Task WalkToPressF() { if (_taskParam.Cts.Token.IsCancellationRequested) { return; } await Task.Run(() => { _simulator.KeyDown(VK.VK_W); Sleep(20); // 组合键好像不能直接用 postmessage Simulation.SendInputEx.Keyboard.KeyDown(VK.VK_SHIFT); try { while (!_taskParam.Cts.Token.IsCancellationRequested) { var content = GetContentFromDispatcher(); var fRectArea = content.CaptureRectArea.Find(_autoPickAssets.FRo); if (fRectArea.IsEmpty()) { Sleep(100, _taskParam.Cts); } else { Logger.LogInformation("检测到交互键"); Simulation.SendInputEx.Keyboard.KeyPress(VK.VK_F); break; } } } finally { _simulator.KeyUp(VK.VK_W); Sleep(50); Simulation.SendInputEx.Keyboard.KeyUp(VK.VK_SHIFT); } }); } private Task StartFight(CombatScenes combatScenes) { CancellationTokenSource cts = new CancellationTokenSource(); _taskParam.Cts.Token.Register(cts.Cancel); combatScenes.BeforeTask(cts); // 战斗操作 var combatTask = new Task(() => { try { while (!cts.Token.IsCancellationRequested) { // 钟离 combatScenes.Avatars[2].Switch(); combatScenes.Avatars[2].Walk("s", 200); combatScenes.Avatars[2].UseSkill(hold: true); Sleep(800); combatScenes.Avatars[2].Walk("w", 200); combatScenes.Avatars[2].UseBurst(); // 4号位 Sleep(1800); combatScenes.Avatars[3].Switch(); combatScenes.Avatars[3].UseSkill(); Sleep(1500); combatScenes.Avatars[3].UseBurst(); // 夜兰 Sleep(800); combatScenes.Avatars[1].Switch(); combatScenes.Avatars[1].UseSkill(); combatScenes.Avatars[1].UseSkill(); Sleep(800); combatScenes.Avatars[1].UseSkill(); combatScenes.Avatars[1].UseSkill(); Sleep(1900); // 等待元素球 combatScenes.Avatars[1].UseBurst(); // 宵宫 Sleep(1200); combatScenes.Avatars[0].Switch(); combatScenes.Avatars[0].UseSkill(); combatScenes.Avatars[0].Attack(6000); Sleep(1000); } } catch (NormalEndException) { Logger.LogInformation("战斗操作结束"); } catch (Exception e) { Logger.LogError(e.Message); } }, cts.Token); // 视角操作 // 对局结束检测 var domainEndTask = DomainEndDetectionTask(cts); combatTask.Start(); domainEndTask.Start(); return Task.WhenAll(combatTask, domainEndTask); } /// /// 对局结束检测 /// private Task DomainEndDetectionTask(CancellationTokenSource cts) { return new Task(() => { while (!_taskParam.Cts.Token.IsCancellationRequested) { if (IsDomainEnd()) { cts.Cancel(); break; } Sleep(1000); } }); } private bool IsDomainEnd() { var content = GetContentFromDispatcher(); var endTipsRect = content.CaptureRectArea.Crop(AutoFightContext.Instance().FightAssets.EndTipsRect); var result = OcrFactory.Paddle.OcrResult(endTipsRect.SrcGreyMat); var rect = result.FindRectByText("自动退出"); if (rect == Rect.Empty) { return false; } Logger.LogInformation("检测到秘境结束提示,结束秘境"); return true; } /// /// 旋转视角后寻找石化古树 /// private Task FindPetrifiedTree() { CancellationTokenSource treeCts = new CancellationTokenSource(); _taskParam.Cts.Token.Register(treeCts.Cancel); // 中键回正视角 Simulation.SendInput.Mouse.MiddleButtonClick(); Sleep(900, _taskParam.Cts); // 左右移动直到石化古树位于屏幕中心任务 var moveAvatarTask = MoveAvatarHorizontallyTask(treeCts); // 锁定东方向视角线程 var lockCameraToEastTask = LockCameraToEastTask(treeCts, moveAvatarTask); lockCameraToEastTask.Start(); return Task.WhenAll(moveAvatarTask, lockCameraToEastTask); } private Task MoveAvatarHorizontallyTask(CancellationTokenSource treeCts) { return new Task(() => { var captureArea = TaskContext.Instance().SystemInfo.CaptureAreaRect; var middleX = captureArea.Width / 2; var leftKeyDown = false; var rightKeyDown = false; var noDetectCount = 0; while (!_taskParam.Cts.Token.IsCancellationRequested) { var treeRect = DetectTree(GetContentFromDispatcher()); if (treeRect != Rect.Empty) { var treeMiddleX = treeRect.X + treeRect.Width / 2; var distance = Math.Abs(middleX - treeMiddleX); if (treeMiddleX < middleX && distance > treeRect.Width / 2) { // 树在左边 往左走 Debug.WriteLine($"树在左边 往左走 {treeMiddleX} {middleX}"); if (rightKeyDown) { // 先松开D键 _simulator.KeyUp(VK.VK_D); rightKeyDown = false; } if (!leftKeyDown) { _simulator.KeyDown(VK.VK_A); leftKeyDown = true; } } else if (treeMiddleX > middleX && distance > treeRect.Width / 2) { // 树在右边 往右走 Debug.WriteLine($"树在右边 往右走 {treeMiddleX} {middleX}"); if (leftKeyDown) { // 先松开A键 _simulator.KeyUp(VK.VK_A); leftKeyDown = false; } if (!rightKeyDown) { _simulator.KeyDown(VK.VK_D); rightKeyDown = true; } } else { // 树在中间 松开所有键 if (rightKeyDown) { _simulator.KeyUp(VK.VK_D); rightKeyDown = false; } if (leftKeyDown) { _simulator.KeyUp(VK.VK_A); leftKeyDown = false; } treeCts.Cancel(); break; } } else { // 左右巡逻 noDetectCount++; if (noDetectCount > 40) { if (leftKeyDown) { _simulator.KeyUp(VK.VK_A); leftKeyDown = false; } if (!rightKeyDown) { _simulator.KeyDown(VK.VK_D); rightKeyDown = true; } } else { if (rightKeyDown) { _simulator.KeyUp(VK.VK_D); rightKeyDown = false; } if (!leftKeyDown) { _simulator.KeyDown(VK.VK_A); leftKeyDown = true; } } } Sleep(150); } VisionContext.Instance().DrawContent.ClearAll(); }); } private Rect DetectTree(CaptureContent content) { using var memoryStream = new MemoryStream(); content.CaptureRectArea.SrcBitmap.Save(memoryStream, ImageFormat.Bmp); memoryStream.Seek(0, SeekOrigin.Begin); var result = _predictor.Detect(memoryStream); var list = new List(); foreach (var box in result.Boxes) { var rect = new System.Windows.Rect(box.Bounds.X, box.Bounds.Y, box.Bounds.Width, box.Bounds.Height); list.Add(new RectDrawable(rect)); } VisionContext.Instance().DrawContent.PutOrRemoveRectList("TreeBox", list); if (list.Count > 0) { var box = result.Boxes[0]; return new Rect(box.Bounds.X, box.Bounds.Y, box.Bounds.Width, box.Bounds.Height); } return Rect.Empty; } private Task LockCameraToEastTask(CancellationTokenSource cts, Task moveAvatarTask) { return new Task(() => { var continuousCount = 0; // 连续东方向次数 var started = false; while (!cts.Token.IsCancellationRequested) { var angle = CameraOrientation.Compute(GetContentFromDispatcher()); if (angle is >= 356 or <= 4) { // 算作对准了 continuousCount++; } if (angle < 180) { // 左移视角 var moveAngle = angle; if (moveAngle > 2) { moveAngle *= 2; } Simulation.SendInputEx.Mouse.MoveMouseBy(-moveAngle, 0); continuousCount = 0; } else if (angle is > 180 and < 360) { // 右移视角 var moveAngle = 360 - angle; if (moveAngle > 2) { moveAngle *= 2; } Simulation.SendInputEx.Mouse.MoveMouseBy(moveAngle, 0); continuousCount = 0; } else { // 360 度 东方向视角 if (continuousCount > 5) { if (!started && moveAvatarTask.Status != TaskStatus.Running) { started = true; moveAvatarTask.Start(); } } } Sleep(100); } Logger.LogInformation("锁定东方向视角线程结束"); VisionContext.Instance().DrawContent.ClearAll(); }); } /// /// 领取奖励 /// private void GettingTreasure() { // 等待窗口弹出 Sleep(1000, _taskParam.Cts); // 优先使用浓缩树脂 GetContentFromDispatcher().CaptureRectArea.Find(AutoFightContext.Instance().FightAssets.UseCondensedResinRa, area => { area.ClickCenter(); }); Sleep(300, _taskParam.Cts); var captureArea = TaskContext.Instance().SystemInfo.CaptureAreaRect; while (true) { // 跳过领取动画 _clickOffset.ClickWithoutScale(captureArea.Width - (int)(140 * _clickOffset.AssetScale), (int)(53 * _clickOffset.AssetScale)); // 优先点击继续 var content = GetContentFromDispatcher(); var confirmRectArea = content.CaptureRectArea.Find(AutoFightContext.Instance().FightAssets.ConfirmRa); if (!confirmRectArea.IsEmpty()) { confirmRectArea.ClickCenter(); break; } var exitRectArea = content.CaptureRectArea.Find(AutoFightContext.Instance().FightAssets.ExitRa); if (!exitRectArea.IsEmpty()) { exitRectArea.ClickCenter(); break; } Sleep(300, _taskParam.Cts); } } }