mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-03-19 08:19:48 +08:00
1352 lines
51 KiB
C#
1352 lines
51 KiB
C#
using BetterGenshinImpact.Core.Config;
|
||
using BetterGenshinImpact.Core.Recognition.OCR;
|
||
using BetterGenshinImpact.Core.Recognition.ONNX;
|
||
using BetterGenshinImpact.Core.Simulator;
|
||
using BetterGenshinImpact.Core.Simulator.Extensions;
|
||
using BetterGenshinImpact.GameTask.AutoFight.Assets;
|
||
using BetterGenshinImpact.GameTask.AutoFight.Model;
|
||
using BetterGenshinImpact.GameTask.AutoFight.Script;
|
||
using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception;
|
||
using BetterGenshinImpact.GameTask.AutoPick.Assets;
|
||
using BetterGenshinImpact.GameTask.Common.Map;
|
||
using BetterGenshinImpact.GameTask.Model.Area;
|
||
using BetterGenshinImpact.Helpers;
|
||
using BetterGenshinImpact.Service.Notification;
|
||
using BetterGenshinImpact.View.Drawable;
|
||
using Microsoft.Extensions.Logging;
|
||
using OpenCvSharp;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Diagnostics;
|
||
using System.Linq;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using BetterGenshinImpact.Core.Recognition;
|
||
using BetterGenshinImpact.GameTask.AutoTrackPath;
|
||
using BetterGenshinImpact.GameTask.Common.BgiVision;
|
||
using BetterGenshinImpact.GameTask.Common.Element.Assets;
|
||
using BetterGenshinImpact.GameTask.Common.Job;
|
||
using BetterGenshinImpact.Service.Notification.Model.Enum;
|
||
using static BetterGenshinImpact.GameTask.Common.TaskControl;
|
||
using static Vanara.PInvoke.Kernel32;
|
||
using static Vanara.PInvoke.User32;
|
||
using Microsoft.Extensions.Localization;
|
||
using System.Globalization;
|
||
using System.Text.RegularExpressions;
|
||
using BetterGenshinImpact.GameTask.AutoArtifactSalvage;
|
||
using System.Collections.ObjectModel;
|
||
using BetterGenshinImpact.Core.Script.Dependence;
|
||
using BetterGenshinImpact.GameTask.AutoDomain.Model;
|
||
using BetterGenshinImpact.GameTask.Common;
|
||
using Compunet.YoloSharp;
|
||
using Microsoft.Extensions.DependencyInjection;
|
||
using BetterGenshinImpact.GameTask.AutoFight;
|
||
|
||
namespace BetterGenshinImpact.GameTask.AutoDomain;
|
||
|
||
public class AutoDomainTask : ISoloTask
|
||
{
|
||
public string Name => "自动秘境";
|
||
|
||
private readonly AutoDomainParam _taskParam;
|
||
|
||
private readonly BgiYoloPredictor _predictor;
|
||
|
||
private readonly AutoDomainConfig _config;
|
||
|
||
private readonly CombatScriptBag _combatScriptBag;
|
||
|
||
private CancellationToken _ct;
|
||
|
||
private ObservableCollection<OneDragonFlowConfig> ConfigList = [];
|
||
|
||
private readonly string challengeCompletedLocalizedString;
|
||
private readonly string autoLeavingLocalizedString;
|
||
private readonly string skipLocalizedString;
|
||
private readonly string leyLineDisorderLocalizedString;
|
||
private readonly string clickanywheretocloseLocalizedString;
|
||
private readonly string matchingChallengeString;
|
||
private readonly string rapidformationString;
|
||
private readonly string limitedFullyString;
|
||
private readonly string limitedFullyAllString;
|
||
|
||
private List<ResinUseRecord> _resinPriorityListWhenSpecifyUse;
|
||
|
||
public AutoDomainTask(AutoDomainParam taskParam)
|
||
{
|
||
AutoFightAssets.DestroyInstance();
|
||
_taskParam = taskParam;
|
||
_predictor = App.ServiceProvider.GetRequiredService<BgiOnnxFactory>().CreateYoloPredictor(BgiOnnxModel.BgiTree);
|
||
|
||
_config = TaskContext.Instance().Config.AutoDomainConfig;
|
||
|
||
_combatScriptBag = CombatScriptParser.ReadAndParse(_taskParam.CombatStrategyPath);
|
||
|
||
_resinPriorityListWhenSpecifyUse = ResinUseRecord.BuildFromDomainParam(taskParam);
|
||
|
||
IStringLocalizer<AutoDomainTask> stringLocalizer =
|
||
App.GetService<IStringLocalizer<AutoDomainTask>>() ?? throw new NullReferenceException();
|
||
CultureInfo cultureInfo = new CultureInfo(TaskContext.Instance().Config.OtherConfig.GameCultureInfoName);
|
||
this.challengeCompletedLocalizedString = stringLocalizer.WithCultureGet(cultureInfo, "挑战达成");
|
||
this.autoLeavingLocalizedString = stringLocalizer.WithCultureGet(cultureInfo, "自动退出");
|
||
this.skipLocalizedString = stringLocalizer.WithCultureGet(cultureInfo, "跳过");
|
||
this.leyLineDisorderLocalizedString = stringLocalizer.WithCultureGet(cultureInfo, "地脉异常");
|
||
this.clickanywheretocloseLocalizedString = stringLocalizer.WithCultureGet(cultureInfo, "点击任意位置关闭");
|
||
this.matchingChallengeString = stringLocalizer.WithCultureGet(cultureInfo, "匹配挑战");
|
||
this.rapidformationString = stringLocalizer.WithCultureGet(cultureInfo, "快速编队");
|
||
this.limitedFullyString = stringLocalizer.WithCultureGet(cultureInfo, "限时全开");
|
||
this.limitedFullyAllString = stringLocalizer.WithCultureGet(cultureInfo, "限时开放");
|
||
}
|
||
|
||
private static RecognitionObject GetConfirmRa(params string[] targetText)
|
||
{
|
||
var screenArea = CaptureToRectArea();
|
||
var x = (int)(screenArea.Width * 0.5);
|
||
var y = (int)(screenArea.Height * 0.5);
|
||
var width = (int)(screenArea.Width * 0.5);
|
||
var height = (int)(screenArea.Height * 0.5);
|
||
return RecognitionObject.OcrMatch(x, y, width, height, targetText);
|
||
}
|
||
|
||
public async Task Start(CancellationToken ct)
|
||
{
|
||
_ct = ct;
|
||
|
||
Init();
|
||
Notify.Event(NotificationEvent.DomainStart).Success("自动秘境启动");
|
||
|
||
// 复活重试
|
||
for (var i = 0; i < _config.ReviveRetryCount; i++)
|
||
{
|
||
try
|
||
{
|
||
await DoDomain();
|
||
// 其他场景不重试
|
||
break;
|
||
}
|
||
catch (RetryException e)
|
||
{
|
||
// 只有选择了秘境的时候才会重试
|
||
if (!string.IsNullOrEmpty(_taskParam.DomainName))
|
||
{
|
||
var msg = e.Message;
|
||
if (msg.Contains("复活"))
|
||
{
|
||
msg = "存在角色死亡,复活后重试秘境...";
|
||
}
|
||
|
||
Logger.LogWarning("自动秘境:{Text}", msg);
|
||
await Delay(2000, ct);
|
||
Notify.Event(NotificationEvent.DomainRetry).Error(msg);
|
||
continue;
|
||
}
|
||
|
||
throw;
|
||
}
|
||
}
|
||
|
||
|
||
await Delay(2000, ct);
|
||
await Bv.WaitForMainUi(_ct, 30);
|
||
await Delay(2000, ct);
|
||
|
||
await ArtifactSalvage();
|
||
Notify.Event(NotificationEvent.DomainEnd).Success("自动秘境结束");
|
||
}
|
||
|
||
private async Task DoDomain()
|
||
{
|
||
// 传送到秘境
|
||
await TpDomain();
|
||
// 切换队伍
|
||
// await SwitchParty(_taskParam.PartyName);
|
||
|
||
// 前置进入秘境
|
||
await EnterDomain();
|
||
|
||
var combatScenes = new CombatScenes();
|
||
for (var i = 0; i < _taskParam.DomainRoundNum; i++)
|
||
{
|
||
// 0. 关闭秘境提示
|
||
Logger.LogDebug("0. 关闭秘境提示");
|
||
await CloseDomainTip();
|
||
|
||
//0.5. 初始化队伍,只执行一次
|
||
if (i == 0)
|
||
{
|
||
combatScenes = new CombatScenes().InitializeTeam(CaptureToRectArea());
|
||
}
|
||
|
||
RetryTeamInit(combatScenes); // 队伍没初始化成功则重试
|
||
|
||
// 0. 切换到第一个角色
|
||
var combatCommands = FindCombatScriptAndSwitchAvatar(combatScenes);
|
||
|
||
// 1. 走到钥匙处启动
|
||
Logger.LogInformation("自动秘境:{Text}", "1. 走到钥匙处启动");
|
||
await WalkToPressF();
|
||
|
||
// 2. 执行战斗(战斗线程、视角线程、检测战斗完成线程)
|
||
Logger.LogInformation("自动秘境:{Text}", "2. 执行战斗策略");
|
||
await StartFight(combatScenes, combatCommands);
|
||
combatScenes.AfterTask();
|
||
EndFightWait();
|
||
|
||
// 3. 寻找石化古树 并左右移动直到石化古树位于屏幕中心
|
||
Logger.LogInformation("自动秘境:{Text}", "3. 寻找石化古树");
|
||
await FindPetrifiedTree();
|
||
|
||
// 4. 走到石化古树处
|
||
Logger.LogInformation("自动秘境:{Text}", "4. 走到石化古树处");
|
||
await WalkToPressF();
|
||
|
||
// 5. 快速领取奖励并判断是否有下一轮
|
||
Logger.LogInformation("自动秘境:{Text}", "5. 领取奖励");
|
||
if (!await GettingTreasure())
|
||
{
|
||
Logger.LogInformation("体力耗尽或者设置轮次已达标,结束自动秘境");
|
||
break;
|
||
}
|
||
|
||
Notify.Event(NotificationEvent.DomainReward).Success("自动秘境奖励领取");
|
||
}
|
||
}
|
||
|
||
private void Init()
|
||
{
|
||
LogScreenResolution();
|
||
if (_config.AutoEat)
|
||
{
|
||
TaskTriggerDispatcher.Instance().AddTrigger("AutoEat", null);
|
||
}
|
||
|
||
if (_config.SpecifyResinUse)
|
||
{
|
||
Logger.LogInformation("→ {Text} 指定使用树脂", "自动秘境,");
|
||
}
|
||
else
|
||
{
|
||
Logger.LogInformation("→ {Text} 用尽所有浓缩树脂和原粹树脂后结束", "自动秘境,");
|
||
}
|
||
}
|
||
|
||
private void LogScreenResolution()
|
||
{
|
||
var gameScreenSize = SystemControl.GetGameScreenRect(TaskContext.Instance().GameHandle);
|
||
if (gameScreenSize.Width * 9 != gameScreenSize.Height * 16)
|
||
{
|
||
Logger.LogError("游戏窗口分辨率不是 16:9 !当前分辨率为 {Width}x{Height} , 非 16:9 分辨率的游戏无法正常使用自动秘境功能 !",
|
||
gameScreenSize.Width, gameScreenSize.Height);
|
||
throw new Exception("游戏窗口分辨率不是 16:9");
|
||
}
|
||
|
||
if (gameScreenSize.Width < 1920 || gameScreenSize.Height < 1080)
|
||
{
|
||
Logger.LogWarning("游戏窗口分辨率小于 1920x1080 !当前分辨率为 {Width}x{Height} , 小于 1920x1080 的分辨率的游戏可能无法正常使用自动秘境功能 !",
|
||
gameScreenSize.Width, gameScreenSize.Height);
|
||
}
|
||
}
|
||
|
||
private void RetryTeamInit(CombatScenes combatScenes)
|
||
{
|
||
if (!combatScenes.CheckTeamInitialized())
|
||
{
|
||
combatScenes.InitializeTeam(CaptureToRectArea());
|
||
if (!combatScenes.CheckTeamInitialized())
|
||
{
|
||
throw new Exception("识别队伍角色失败,请在较暗背景下重试,比如游戏时间调整成夜晚。或者直接使用强制指定当前队伍角色的功能。");
|
||
}
|
||
}
|
||
}
|
||
|
||
private async Task TpDomain()
|
||
{
|
||
// 传送到秘境
|
||
if (!string.IsNullOrEmpty(_taskParam.DomainName))
|
||
{
|
||
if (MapLazyAssets.Instance.DomainPositionMap.TryGetValue(_taskParam.DomainName, out var domainPosition))
|
||
{
|
||
Logger.LogInformation("自动秘境:传送到秘境{Text}", _taskParam.DomainName);
|
||
await new TpTask(_ct).Tp(domainPosition.X, domainPosition.Y);
|
||
await Delay(1000, _ct);
|
||
await Bv.WaitForMainUi(_ct);
|
||
|
||
var menuFound = false;
|
||
if ("芬德尼尔之顶".Equals(_taskParam.DomainName))
|
||
{
|
||
menuFound = await NewRetry.WaitForElementAppear(
|
||
AutoPickAssets.Instance.PickRo,
|
||
() => Simulation.SendInput.SimulateAction(GIActions.MoveBackward, KeyType.KeyDown),
|
||
_ct,
|
||
20,
|
||
500
|
||
);
|
||
Simulation.SendInput.SimulateAction(GIActions.MoveBackward, KeyType.KeyUp);
|
||
}
|
||
else if ("无妄引咎密宫".Equals(_taskParam.DomainName))
|
||
{
|
||
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyDown);
|
||
Thread.Sleep(500);
|
||
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyUp);
|
||
|
||
menuFound = await NewRetry.WaitForElementAppear(
|
||
AutoPickAssets.Instance.PickRo,
|
||
() => Simulation.SendInput.SimulateAction(GIActions.MoveLeft, KeyType.KeyDown),
|
||
_ct,
|
||
20,
|
||
500
|
||
);
|
||
Simulation.SendInput.SimulateAction(GIActions.MoveLeft, KeyType.KeyUp);
|
||
}
|
||
else if ("太山府".Equals(_taskParam.DomainName))
|
||
{
|
||
menuFound = await NewRetry.WaitForElementAppear(
|
||
AutoPickAssets.Instance.PickRo,
|
||
() => { },
|
||
_ct,
|
||
20,
|
||
500
|
||
);
|
||
}
|
||
else
|
||
{
|
||
menuFound = await NewRetry.WaitForElementAppear(
|
||
AutoPickAssets.Instance.PickRo,
|
||
() => Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyDown),
|
||
_ct,
|
||
20,
|
||
500
|
||
);
|
||
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyUp);
|
||
}
|
||
|
||
if (!menuFound)
|
||
{
|
||
throw new Exception("请检查是否在秘境门前");
|
||
}
|
||
|
||
var menu = await NewRetry.WaitForElementAppear(
|
||
GetConfirmRa("单人挑战"),
|
||
() => Simulation.SendInput.Keyboard.KeyPress(AutoPickAssets.Instance.PickVk),
|
||
_ct,
|
||
20,
|
||
500
|
||
);
|
||
if (!menu)
|
||
{
|
||
throw new Exception("请检查是否已进入秘境页面");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Logger.LogError("自动秘境:未找到对应的秘境{Text}的传送点", _taskParam.DomainName);
|
||
throw new Exception($"未找到对应的秘境{_taskParam.DomainName}的传送点");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 切换队伍
|
||
/// </summary>
|
||
/// <param name="partyName"></param>
|
||
/// <returns></returns>
|
||
private async Task<bool> SwitchParty(string? partyName)
|
||
{
|
||
if (!string.IsNullOrEmpty(partyName))
|
||
{
|
||
var b = await new SwitchPartyTask().Start(partyName, _ct);
|
||
await Delay(500, _ct);
|
||
return b;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
private async Task EnterDomain()
|
||
{
|
||
var fightAssets = AutoFightAssets.Instance;
|
||
|
||
var menuFound = await NewRetry.WaitForElementAppear(
|
||
GetConfirmRa("单人挑战"),
|
||
() => Simulation.SendInput.Keyboard.KeyPress(AutoPickAssets.Instance.PickVk),
|
||
_ct,
|
||
10,
|
||
1000
|
||
);
|
||
if (!menuFound)
|
||
{
|
||
Logger.LogWarning("单人挑战 按键未出现,请检查是否已进入秘境页面");
|
||
}
|
||
|
||
using var limitedFullyStringRa = CaptureToRectArea();
|
||
var limitedFullyStringRaocrList =
|
||
limitedFullyStringRa.FindMulti(RecognitionObject.Ocr(0, 0, limitedFullyStringRa.Width * 0.5,
|
||
limitedFullyStringRa.Height));
|
||
var limitedFullyStringRaocrListdone = limitedFullyStringRaocrList.LastOrDefault(t =>
|
||
Regex.IsMatch(t.Text, this.limitedFullyString) || Regex.IsMatch(t.Text, this.limitedFullyAllString));
|
||
// 检测是否为限时全开秘境
|
||
if (limitedFullyStringRaocrListdone != null)
|
||
{
|
||
Logger.LogInformation("自动秘境:{Text}", "检测到秘境限时全开");
|
||
}
|
||
|
||
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())
|
||
{
|
||
if (int.TryParse(_taskParam.SundaySelectedValue, out int sundaySelectedValue))
|
||
{
|
||
if (sundaySelectedValue > 0)
|
||
{
|
||
Logger.LogInformation(limitedFullyStringRaocrListdone != null ? "自动秘境:限时全开秘境奖励序号 {sundaySelectedValue}" : "自动秘境:周日设置了秘境奖励序号 {sundaySelectedValue}", sundaySelectedValue);
|
||
using var abnormalscreenRa = CaptureToRectArea();
|
||
GlobalMethod.MoveMouseTo(abnormalscreenRa.Width / 4, abnormalscreenRa.Height / 2); //移到左侧
|
||
for (var i = 0; i < 100; i++)
|
||
{
|
||
Simulation.SendInput.Mouse.VerticalScroll(-1);
|
||
await Delay(10, _ct);
|
||
}
|
||
|
||
await Delay(400, _ct);
|
||
|
||
using var abnormalRa = CaptureToRectArea();
|
||
var ocrList =
|
||
abnormalRa.FindMulti(RecognitionObject.Ocr(0, 0, abnormalRa.Width * 0.5,
|
||
abnormalRa.Height));
|
||
var done = ocrList.LastOrDefault(t =>
|
||
Regex.IsMatch(t.Text, this.leyLineDisorderLocalizedString));
|
||
if (done != null)
|
||
{
|
||
await Delay(300, _ct);
|
||
|
||
switch (sundaySelectedValue)
|
||
{
|
||
case 1:
|
||
GlobalMethod.Click(done.X, done.Y - abnormalRa.Height / 5);
|
||
break;
|
||
case 2:
|
||
GlobalMethod.Click(done.X, done.Y - abnormalRa.Height / 10);
|
||
break;
|
||
case 3:
|
||
GlobalMethod.Click(done.X, done.Y);
|
||
break;
|
||
default:
|
||
Logger.LogWarning("无效的 sundaySelectedValue 值: {sundaySelectedValue}",
|
||
sundaySelectedValue);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Logger.LogInformation(limitedFullyStringRaocrListdone != null ? "自动秘境:限时全开秘境未设置特定秘境奖励" : "自动秘境:周日秘境未设置特定秘境奖励");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Logger.LogWarning(_taskParam.SundaySelectedValue == "" ? "未设置秘境奖励序号" : "设置秘境奖励序号错误,请检查配置页面");
|
||
}
|
||
}
|
||
|
||
await Delay(300, _ct);
|
||
}
|
||
|
||
// 点击单人挑战确认并等待队伍界面--使用图像模版匹配的方法,也可以使用文字OCR的方法识别“单人挑战”直到消失
|
||
await NewRetry.WaitForElementAppear(
|
||
ElementAssets.Instance.PartyBtnChooseView,
|
||
async void () =>
|
||
{
|
||
using var ra = CaptureToRectArea();
|
||
var ra2 = ra.Find(fightAssets.ConfirmRa);
|
||
if (!ra2.IsEmpty())
|
||
{
|
||
ra2.Click();
|
||
ra2.Dispose();
|
||
Logger.LogInformation("自动秘境:点击 {Text}", "单人挑战");
|
||
}
|
||
|
||
using var confirmRectArea2 = ra.Find(RecognitionObject.Ocr(ra.Width * 0.263, ra.Height * 0.32,
|
||
ra.Width - ra.Width * 0.263 * 2, ra.Height - ra.Height * 0.32 - ra.Height * 0.353));
|
||
if (confirmRectArea2.IsExist() && confirmRectArea2.Text.Contains("是否仍要挑战该秘境"))
|
||
{
|
||
Logger.LogWarning("自动秘境:检测到树脂不足提示:{Text}", confirmRectArea2.Text);
|
||
throw new Exception("当前树脂不足,自动秘境停止运行。");
|
||
}
|
||
},
|
||
_ct,
|
||
10,
|
||
1000
|
||
);
|
||
|
||
// 等待队伍选择界面出现
|
||
var teamUiFound = await NewRetry.WaitForElementAppear(
|
||
ElementAssets.Instance.PartyBtnChooseView,
|
||
() => { Logger.LogInformation("自动秘境:进入 {Text}", "队伍选择界面"); },
|
||
_ct,
|
||
10,
|
||
1000
|
||
);
|
||
if (!teamUiFound)
|
||
{
|
||
Logger.LogWarning("队伍选择界面未出现,跳过切换队伍。");
|
||
}
|
||
else
|
||
{
|
||
await SwitchParty(_taskParam.PartyName);
|
||
}
|
||
|
||
// 点击开始挑战确认并等待“开始挑战”文字消失
|
||
var startFightFound = await NewRetry.WaitForElementDisappear(
|
||
GetConfirmRa("开始挑战"),
|
||
screen =>
|
||
{
|
||
screen.Find(fightAssets.ConfirmRa, ra =>
|
||
{
|
||
ra.Click();
|
||
ra.Dispose();
|
||
Logger.LogInformation("自动秘境:点击 {Text}", "开始挑战");
|
||
});
|
||
},
|
||
_ct,
|
||
10,
|
||
1000
|
||
);
|
||
if (!startFightFound)
|
||
{
|
||
Logger.LogWarning("开始挑战按钮未出现或未能点击。");
|
||
}
|
||
|
||
// 载入
|
||
await Delay(1000, _ct);
|
||
}
|
||
|
||
private async Task CloseDomainTip()
|
||
{
|
||
//先等待秘境提示出现,如果直接出现Enter也属于完成条件
|
||
var domainTipFound = await NewRetry.WaitForAction(() =>
|
||
{
|
||
using var ra = CaptureToRectArea();
|
||
|
||
var ocrList = ra.FindMulti(RecognitionObject.Ocr(0, ra.Height * 0.2, ra.Width, ra.Height * 0.6));
|
||
var ocrListLeft = ra.Find(AutoFightAssets.Instance.AbnormalIconRa);
|
||
return (ocrList.Any(t => t.Text.Contains(leyLineDisorderLocalizedString) ||
|
||
t.Text.Contains(clickanywheretocloseLocalizedString))) || ocrListLeft.IsExist();
|
||
}, _ct, 40, 500);
|
||
if (!domainTipFound)
|
||
{
|
||
Logger.LogWarning("秘境提示未出现或未能点击。");
|
||
}
|
||
|
||
//持续点击,直到左下角出现目标文字
|
||
var leftBottomFound = await NewRetry.WaitForAction(() =>
|
||
{
|
||
using var ra = CaptureToRectArea();
|
||
var ocrList = ra.FindMulti(RecognitionObject.Ocr(0, ra.Height * 0.2, ra.Width, ra.Height * 0.6));
|
||
// 查找目标文字
|
||
var done = ocrList.FirstOrDefault(t =>
|
||
Regex.IsMatch(t.Text, this.leyLineDisorderLocalizedString) ||
|
||
Regex.IsMatch(t.Text, this.clickanywheretocloseLocalizedString));
|
||
if (done != null)
|
||
{
|
||
done.Click();
|
||
done.Dispose();
|
||
Logger.LogInformation("自动秘境:点击 {Text}", done.Text);
|
||
}
|
||
|
||
// 检查左下角区域是否还存在目标文字,消失则继续,存在则结束
|
||
using var leftBottom = CaptureToRectArea();
|
||
var leftBottomOcr = leftBottom.Find(AutoFightAssets.Instance.AbnormalIconRa);
|
||
return leftBottomOcr.IsExist();
|
||
}, _ct, 20, 500);
|
||
if (!leftBottomFound)
|
||
{
|
||
//尝试随意点击一下右下角
|
||
GameCaptureRegion.GameRegion1080PPosClick(1515, 892);
|
||
Logger.LogWarning("秘境提示未出现或未能点击。");
|
||
}
|
||
|
||
await Delay(500, _ct);
|
||
}
|
||
|
||
private List<CombatCommand> FindCombatScriptAndSwitchAvatar(CombatScenes combatScenes)
|
||
{
|
||
var combatCommands = _combatScriptBag.FindCombatScript(combatScenes.GetAvatars());
|
||
var avatar = combatScenes.SelectAvatar(combatCommands[0].Name);
|
||
avatar?.SwitchWithoutCts();
|
||
Sleep(200);
|
||
return combatCommands;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 走到钥匙处启动
|
||
/// </summary>
|
||
private async Task WalkToPressF()
|
||
{
|
||
if (_ct.IsCancellationRequested)
|
||
{
|
||
return;
|
||
}
|
||
|
||
await Task.Run((Action)(() =>
|
||
{
|
||
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyDown);
|
||
Sleep(30, _ct);
|
||
// 组合键好像不能直接用 postmessage
|
||
if (!_config.WalkToF)
|
||
{
|
||
Simulation.SendInput.SimulateAction(GIActions.SprintKeyboard, KeyType.KeyDown);
|
||
}
|
||
|
||
try
|
||
{
|
||
var startTime = DateTime.Now;
|
||
while (!_ct.IsCancellationRequested)
|
||
{
|
||
using var fRectArea = Common.TaskControl.CaptureToRectArea().Find(AutoPickAssets.Instance.PickRo);
|
||
if (fRectArea.IsEmpty())
|
||
{
|
||
Sleep(100, _ct);
|
||
}
|
||
else
|
||
{
|
||
Logger.LogInformation("检测到交互键");
|
||
Simulation.SendInput.Keyboard.KeyPress(AutoPickAssets.Instance.PickVk);
|
||
break;
|
||
}
|
||
|
||
// 超时直接放弃整个秘境
|
||
if (DateTime.Now - startTime > TimeSpan.FromSeconds(60))
|
||
{
|
||
Logger.LogWarning("自动秘境:{Text}", "前往目标位置处超时,如果选择了秘境名称,将在传送后重试秘境!");
|
||
Avatar.TpForRecover(_ct, new RetryException("前往目标位置处超时,先传送到七天神像,然后重试秘境"));
|
||
}
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyUp);
|
||
Sleep(50);
|
||
if (!_config.WalkToF)
|
||
{
|
||
Simulation.SendInput.SimulateAction(GIActions.SprintKeyboard, KeyType.KeyUp);
|
||
}
|
||
}
|
||
}), _ct);
|
||
}
|
||
|
||
private Task StartFight(CombatScenes combatScenes, List<CombatCommand> combatCommands)
|
||
{
|
||
CancellationTokenSource cts = new();
|
||
_ct.Register(cts.Cancel);
|
||
combatScenes.BeforeTask(cts.Token);
|
||
// 战斗操作
|
||
var combatTask = new Task(() =>
|
||
{
|
||
try
|
||
{
|
||
AutoFightTask.FightStatusFlag = true;
|
||
while (!cts.Token.IsCancellationRequested)
|
||
{
|
||
// 通用化战斗策略
|
||
foreach (var command in combatCommands)
|
||
{
|
||
command.Execute(combatScenes);
|
||
}
|
||
}
|
||
}
|
||
catch (NormalEndException e)
|
||
{
|
||
Logger.LogInformation("战斗操作中断:{Msg}", e.Message);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Logger.LogWarning(e.Message);
|
||
throw;
|
||
}
|
||
finally
|
||
{
|
||
Logger.LogInformation("自动战斗线程结束");
|
||
Simulation.ReleaseAllKey();
|
||
AutoFightTask.FightStatusFlag = false;
|
||
}
|
||
}, cts.Token);
|
||
|
||
// 对局结束检测
|
||
var domainEndTask = DomainEndDetectionTask(cts);
|
||
// 自动吃药
|
||
// var autoEatRecoveryHpTask = AutoEatRecoveryHpTask(cts.Token);
|
||
combatTask.Start();
|
||
domainEndTask.Start();
|
||
// autoEatRecoveryHpTask.Start();
|
||
return Task.WhenAll(combatTask, domainEndTask);
|
||
}
|
||
|
||
private void EndFightWait()
|
||
{
|
||
if (_ct.IsCancellationRequested)
|
||
{
|
||
return;
|
||
}
|
||
|
||
var s = TaskContext.Instance().Config.AutoDomainConfig.FightEndDelay;
|
||
if (s > 0)
|
||
{
|
||
Logger.LogInformation("战斗结束后等待 {Second} 秒", s);
|
||
Sleep((int)(s * 1000), _ct);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 对局结束检测
|
||
/// </summary>
|
||
private Task DomainEndDetectionTask(CancellationTokenSource cts)
|
||
{
|
||
return new Task(async () =>
|
||
{
|
||
try
|
||
{
|
||
while (!_ct.IsCancellationRequested)
|
||
{
|
||
if (IsDomainEnd())
|
||
{
|
||
await cts.CancelAsync();
|
||
break;
|
||
}
|
||
|
||
await Delay(1000, cts.Token);
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
}
|
||
}, cts.Token);
|
||
}
|
||
|
||
private bool IsDomainEnd()
|
||
{
|
||
using var ra = CaptureToRectArea();
|
||
|
||
var endTipsRect = ra.DeriveCrop(AutoFightAssets.Instance.EndTipsUpperRect);
|
||
var text = OcrFactory.Paddle.Ocr(endTipsRect.SrcMat);
|
||
if (Regex.IsMatch(text, this.challengeCompletedLocalizedString))
|
||
{
|
||
Logger.LogInformation("检测到秘境结束提示(挑战达成),结束秘境");
|
||
return true;
|
||
}
|
||
|
||
endTipsRect = ra.DeriveCrop(AutoFightAssets.Instance.EndTipsRect);
|
||
text = OcrFactory.Paddle.Ocr(endTipsRect.SrcMat);
|
||
if (Regex.IsMatch(text, this.autoLeavingLocalizedString))
|
||
{
|
||
Logger.LogInformation("检测到秘境结束提示(xxx秒后自动退出),结束秘境");
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private Task AutoEatRecoveryHpTask(CancellationToken ct)
|
||
{
|
||
return new Task(async () =>
|
||
{
|
||
if (!_config.AutoEat)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (!IsTakeFood())
|
||
{
|
||
Logger.LogInformation("未装备 “{Tool}”,不启用红血自动吃药功能", "便携营养袋");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
while (!_ct.IsCancellationRequested)
|
||
{
|
||
if (Bv.CurrentAvatarIsLowHp(CaptureToRectArea()))
|
||
{
|
||
// 模拟按键 "Z"
|
||
Simulation.SendInput.SimulateAction(GIActions.QuickUseGadget);
|
||
Logger.LogInformation("检测到红血,按Z吃药");
|
||
// TODO 吃饱了会一直吃
|
||
}
|
||
|
||
await Delay(500, ct);
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Logger.LogDebug(e, "红血自动吃药检测时发生异常");
|
||
}
|
||
}, ct);
|
||
}
|
||
|
||
private bool IsTakeFood()
|
||
{
|
||
// 获取图像
|
||
using var ra = CaptureToRectArea();
|
||
// 识别道具图标下是否是数字
|
||
var s = TaskContext.Instance().SystemInfo.AssetScale;
|
||
var countArea = ra.DeriveCrop(1800 * s, 845 * s, 40 * s, 20 * s);
|
||
var count = OcrFactory.Paddle.OcrWithoutDetector(countArea.CacheGreyMat);
|
||
return int.TryParse(count, out _);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 旋转视角后寻找石化古树
|
||
/// </summary>
|
||
private Task FindPetrifiedTree()
|
||
{
|
||
CancellationTokenSource treeCts = new();
|
||
_ct.Register(treeCts.Cancel);
|
||
// 中键回正视角
|
||
Simulation.SendInput.Mouse.MiddleButtonClick();
|
||
Sleep(900, _ct);
|
||
|
||
// 左右移动直到石化古树位于屏幕中心任务
|
||
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 keyConfig = TaskContext.Instance().Config.KeyBindingsConfig;
|
||
var moveLeftKey = keyConfig.MoveLeft.ToVK();
|
||
var moveRightKey = keyConfig.MoveRight.ToVK();
|
||
var moveForwardKey = keyConfig.MoveForward.ToVK();
|
||
var captureArea = TaskContext.Instance().SystemInfo.ScaleMax1080PCaptureRect;
|
||
var middleX = captureArea.Width / 2;
|
||
var leftKeyDown = false;
|
||
var rightKeyDown = false;
|
||
var noDetectCount = 0;
|
||
var prevKey = moveLeftKey;
|
||
var backwardsAndForwardsCount = 0;
|
||
while (!_ct.IsCancellationRequested)
|
||
{
|
||
var treeRect = DetectTree(CaptureToRectArea());
|
||
if (treeRect != default)
|
||
{
|
||
var treeMiddleX = treeRect.X + treeRect.Width / 2;
|
||
if (treeRect.X + treeRect.Width < middleX && !_config.ShortMovement)
|
||
{
|
||
backwardsAndForwardsCount = 0;
|
||
// 树在左边 往左走
|
||
Debug.WriteLine($"树在左边 往左走 {treeMiddleX} {middleX}");
|
||
if (rightKeyDown)
|
||
{
|
||
// 先松开D键
|
||
Simulation.SendInput.Keyboard.KeyUp(moveRightKey);
|
||
rightKeyDown = false;
|
||
}
|
||
|
||
if (!leftKeyDown)
|
||
{
|
||
Simulation.SendInput.Keyboard.KeyDown(moveLeftKey);
|
||
leftKeyDown = true;
|
||
}
|
||
}
|
||
else if (treeRect.X > middleX && !_config.ShortMovement)
|
||
{
|
||
backwardsAndForwardsCount = 0;
|
||
// 树在右边 往右走
|
||
Debug.WriteLine($"树在右边 往右走 {treeMiddleX} {middleX}");
|
||
if (leftKeyDown)
|
||
{
|
||
// 先松开A键
|
||
Simulation.SendInput.Keyboard.KeyUp(moveLeftKey);
|
||
leftKeyDown = false;
|
||
}
|
||
|
||
if (!rightKeyDown)
|
||
{
|
||
Simulation.SendInput.Keyboard.KeyDown(moveRightKey);
|
||
rightKeyDown = true;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 树在中间 松开所有键
|
||
if (rightKeyDown)
|
||
{
|
||
Simulation.SendInput.Keyboard.KeyUp(moveRightKey);
|
||
prevKey = moveRightKey;
|
||
rightKeyDown = false;
|
||
}
|
||
|
||
if (leftKeyDown)
|
||
{
|
||
Simulation.SendInput.Keyboard.KeyUp(moveLeftKey);
|
||
prevKey = moveLeftKey;
|
||
leftKeyDown = false;
|
||
}
|
||
|
||
// 松开按键后使用小碎步移动
|
||
if (treeMiddleX < middleX)
|
||
{
|
||
if (prevKey == moveRightKey)
|
||
{
|
||
backwardsAndForwardsCount++;
|
||
}
|
||
|
||
Simulation.SendInput.Keyboard.KeyDown(moveLeftKey);
|
||
Sleep(60);
|
||
Simulation.SendInput.Keyboard.KeyUp(moveLeftKey);
|
||
prevKey = moveLeftKey;
|
||
}
|
||
else if (treeMiddleX > middleX)
|
||
{
|
||
if (prevKey == moveLeftKey)
|
||
{
|
||
backwardsAndForwardsCount++;
|
||
}
|
||
|
||
Simulation.SendInput.Keyboard.KeyDown(moveRightKey);
|
||
Sleep(60);
|
||
Simulation.SendInput.Keyboard.KeyUp(moveRightKey);
|
||
prevKey = moveRightKey;
|
||
}
|
||
else
|
||
{
|
||
Simulation.SendInput.Keyboard.KeyDown(moveForwardKey);
|
||
Sleep(60);
|
||
Simulation.SendInput.Keyboard.KeyUp(moveForwardKey);
|
||
Sleep(500, _ct);
|
||
treeCts.Cancel();
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
backwardsAndForwardsCount = 0;
|
||
// 左右巡逻
|
||
noDetectCount++;
|
||
if (noDetectCount > 40)
|
||
{
|
||
if (leftKeyDown)
|
||
{
|
||
Simulation.SendInput.Keyboard.KeyUp(moveLeftKey);
|
||
leftKeyDown = false;
|
||
}
|
||
|
||
if (!rightKeyDown)
|
||
{
|
||
Simulation.SendInput.Keyboard.KeyDown(moveRightKey);
|
||
rightKeyDown = true;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (rightKeyDown)
|
||
{
|
||
Simulation.SendInput.Keyboard.KeyUp(moveRightKey);
|
||
rightKeyDown = false;
|
||
}
|
||
|
||
if (!leftKeyDown)
|
||
{
|
||
Simulation.SendInput.Keyboard.KeyDown(moveLeftKey);
|
||
leftKeyDown = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (backwardsAndForwardsCount >= _config.LeftRightMoveTimes)
|
||
{
|
||
// 左右移动5次说明已经在树中心了
|
||
Simulation.SendInput.Keyboard.KeyDown(moveForwardKey);
|
||
Sleep(60);
|
||
Simulation.SendInput.Keyboard.KeyUp(moveForwardKey);
|
||
Sleep(500, _ct);
|
||
treeCts.Cancel();
|
||
break;
|
||
}
|
||
|
||
Sleep(60, _ct);
|
||
}
|
||
|
||
VisionContext.Instance().DrawContent.ClearAll();
|
||
});
|
||
}
|
||
|
||
private Rect DetectTree(ImageRegion region)
|
||
{
|
||
var result = _predictor.Predictor.Detect(region.CacheImage);
|
||
var list = new List<RectDrawable>();
|
||
foreach (var box in result)
|
||
{
|
||
var rect = new Rect(box.Bounds.X, box.Bounds.Y, box.Bounds.Width, box.Bounds.Height);
|
||
list.Add(region.ToRectDrawable(rect, "tree"));
|
||
}
|
||
|
||
VisionContext.Instance().DrawContent.PutOrRemoveRectList("TreeBox", list);
|
||
|
||
if (list.Count > 0)
|
||
{
|
||
var box = result[0];
|
||
return new Rect(box.Bounds.X, box.Bounds.Y, box.Bounds.Width, box.Bounds.Height);
|
||
}
|
||
|
||
return default;
|
||
}
|
||
|
||
private Task LockCameraToEastTask(CancellationTokenSource cts, Task moveAvatarTask)
|
||
{
|
||
return new Task(() =>
|
||
{
|
||
var continuousCount = 0; // 连续东方向次数
|
||
var started = false;
|
||
while (!cts.Token.IsCancellationRequested)
|
||
{
|
||
using var captureRegion = CaptureToRectArea();
|
||
var angle = CameraOrientation.Compute(captureRegion.SrcMat);
|
||
CameraOrientation.DrawDirection(captureRegion, angle);
|
||
if (angle is >= 356 or <= 4)
|
||
{
|
||
// 算作对准了
|
||
continuousCount++;
|
||
// 360 度 东方向视角
|
||
if (continuousCount > 5)
|
||
{
|
||
if (!started && moveAvatarTask.Status != TaskStatus.Running)
|
||
{
|
||
started = true;
|
||
moveAvatarTask.Start();
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
continuousCount = 0;
|
||
}
|
||
|
||
if (angle <= 180)
|
||
{
|
||
// 左移视角
|
||
var moveAngle = (int)Math.Round(angle);
|
||
if (moveAngle > 2)
|
||
{
|
||
moveAngle *= 2;
|
||
}
|
||
|
||
Simulation.SendInput.Mouse.MoveMouseBy(-moveAngle, 0);
|
||
}
|
||
else if (angle is > 180 and < 360)
|
||
{
|
||
// 右移视角
|
||
var moveAngle = 360 - (int)Math.Round(angle);
|
||
if (moveAngle > 2)
|
||
{
|
||
moveAngle *= 2;
|
||
}
|
||
|
||
Simulation.SendInput.Mouse.MoveMouseBy(moveAngle, 0);
|
||
}
|
||
|
||
Sleep(100, _ct);
|
||
}
|
||
|
||
Logger.LogInformation("锁定东方向视角线程结束");
|
||
VisionContext.Instance().DrawContent.ClearAll();
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 领取奖励
|
||
/// </summary>
|
||
private async Task<bool> GettingTreasure()
|
||
{
|
||
bool isLastTurn = false;
|
||
// 等待窗口弹出
|
||
await Delay(300, _ct);
|
||
|
||
// 1. OCR 直到确认弹出框弹出
|
||
bool chooseResinPrompt = await NewRetry.WaitForAction(() =>
|
||
{
|
||
using var ra = CaptureToRectArea();
|
||
var regionList = ra.FindMulti(RecognitionObject.Ocr(ra.Width * 0.25, ra.Height * 0.2, ra.Width * 0.5, ra.Height * 0.6));
|
||
var res = regionList.FirstOrDefault(t => t.Text.Contains("石化古树"));
|
||
if (res != null)
|
||
{
|
||
// 解决水龙王按下左键后没松开,然后后续点击按下就没反应了,界面上点一下
|
||
res.Click();
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}, _ct, 10, 500);
|
||
Debug.WriteLine("识别到选择树脂页");
|
||
await Delay(800, _ct);
|
||
|
||
// 再 OCR 一次,弹出框,确认当前是否有原粹树脂
|
||
using var ra2 = CaptureToRectArea();
|
||
var textListInPrompt = ra2.FindMulti(RecognitionObject.Ocr(ra2.Width * 0.25, ra2.Height * 0.2, ra2.Width * 0.5, ra2.Height * 0.6));
|
||
if (textListInPrompt.Any(t => t.Text.Contains("数量不足") || t.Text.Contains("补充原粹树脂")))
|
||
{
|
||
// 没有原粹树脂,直接退出秘境
|
||
Logger.LogInformation("自动秘境:原粹树脂已用尽,退出秘境");
|
||
await ExitDomain();
|
||
return false;
|
||
}
|
||
|
||
if (chooseResinPrompt)
|
||
{
|
||
using var ra3 = CaptureToRectArea();
|
||
|
||
if (!_taskParam.SpecifyResinUse)
|
||
{
|
||
// 自动刷干树脂
|
||
// 识别树脂状况
|
||
var resinStatus = ResinStatus.RecogniseFromRegion(ra3, TaskContext.Instance().SystemInfo, OcrFactory.Paddle);
|
||
resinStatus.Print(Logger);
|
||
|
||
if (resinStatus is { CondensedResinCount: <= 0, OriginalResinCount: < 20 })
|
||
{
|
||
Logger.LogWarning("树脂不足");
|
||
await ExitDomain();
|
||
return false;
|
||
}
|
||
|
||
bool resinUsed = false;
|
||
if (resinStatus.CondensedResinCount > 0)
|
||
{
|
||
(resinUsed, _) = PressUseResin(ra3, "浓缩树脂");
|
||
resinStatus.CondensedResinCount -= 1;
|
||
}
|
||
else if (resinStatus.OriginalResinCount >= 20)
|
||
{
|
||
(resinUsed, var num) = PressUseResin(ra3, "原粹树脂");
|
||
resinStatus.OriginalResinCount -= num;
|
||
}
|
||
|
||
if (!resinUsed)
|
||
{
|
||
Logger.LogWarning("自动秘境:未找到可用的树脂,可能是{Msg1} 或者 {Msg2}。", "树脂不足", "OCR 识别失败");
|
||
await ExitDomain();
|
||
return false;
|
||
}
|
||
|
||
if (resinStatus is { CondensedResinCount: <= 0, OriginalResinCount: < 20 })
|
||
{
|
||
// 没树脂了就是最后一回合了
|
||
isLastTurn = true;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 指定使用树脂
|
||
var textListInPrompt2 = ra3.FindMulti(RecognitionObject.Ocr(ra3.Width * 0.25, ra3.Height * 0.2, ra3.Width * 0.5, ra3.Height * 0.6));
|
||
// 按优先级使用
|
||
int successCount = 0;
|
||
foreach (var record in _resinPriorityListWhenSpecifyUse)
|
||
{
|
||
if (record.RemainCount > 0)
|
||
{
|
||
var (success, _) = PressUseResin(textListInPrompt2, record.Name);
|
||
if (success)
|
||
{
|
||
record.RemainCount -= 1;
|
||
Logger.LogInformation("自动秘境:{Name} 刷取 {Re}/{Max}", record.Name, record.MaxCount - record.RemainCount, record.MaxCount);
|
||
successCount++;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (_resinPriorityListWhenSpecifyUse.Sum(o => o.RemainCount) <= 0)
|
||
{
|
||
// 全部刷完
|
||
isLastTurn = true;
|
||
}
|
||
|
||
if (successCount == 0)
|
||
{
|
||
// 没有找到对应的树脂
|
||
Logger.LogWarning("自动秘境:指定树脂领取次数时,当前可用树脂选项无法满足配置。你可能设置的刷取次数过多!退出秘境。");
|
||
Logger.LogInformation("当前刷取情况:{ResinList}", string.Join(", ", _resinPriorityListWhenSpecifyUse.Select(o => $"{o.Name}({o.MaxCount - o.RemainCount}/{o.MaxCount})")));
|
||
await ExitDomain();
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 如果没有选择树脂的提示,说明只有原粹树脂
|
||
// 继续向下执行
|
||
}
|
||
|
||
Sleep(1000, _ct);
|
||
|
||
for (var i = 0; i < 30; i++)
|
||
{
|
||
using var ra = CaptureToRectArea();
|
||
// 优先点击继续
|
||
using var confirmRectArea = ra.Find(AutoFightAssets.Instance.ConfirmRa);
|
||
if (!confirmRectArea.IsEmpty())
|
||
{
|
||
if (isLastTurn)
|
||
{
|
||
// 最后一回合 退出
|
||
var exitRectArea = ra.Find(AutoFightAssets.Instance.ExitRa);
|
||
if (!exitRectArea.IsEmpty())
|
||
{
|
||
exitRectArea.Click();
|
||
return false;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (!chooseResinPrompt)
|
||
{
|
||
// TODO 前面没有弹框的情况下,意味着只有原粹树脂,要再识别一次右上角确认树脂余量,没有余量直接退出
|
||
}
|
||
|
||
// 有体力继续
|
||
confirmRectArea.Click();
|
||
await Delay(60, _ct); // 双击
|
||
confirmRectArea.Click();
|
||
|
||
if (!chooseResinPrompt)
|
||
{
|
||
// 真没树脂了还有提示兜底
|
||
await Delay(900, _ct);
|
||
var textListInNoResinPrompt = CaptureToRectArea().FindMulti(RecognitionObject.Ocr(ra2.Width * 0.25, ra2.Height * 0.2, ra2.Width * 0.5, ra2.Height * 0.6));
|
||
if (textListInNoResinPrompt.Any(t => t.Text.Contains("是否仍要") && t.Text.Contains("挑战") && t.Text.Contains("秘境")))
|
||
{
|
||
var cancelBtn = textListInNoResinPrompt.FirstOrDefault(t => t.Text.Contains("取消"));
|
||
if (cancelBtn != null)
|
||
{
|
||
cancelBtn.Click();
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
}
|
||
|
||
Sleep(300, _ct);
|
||
}
|
||
|
||
throw new NormalEndException("未检测到秘境结束,可能是背包物品已满。");
|
||
}
|
||
|
||
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());
|
||
}
|
||
|
||
public static (bool, int) PressUseResin(ImageRegion ra, string resinName)
|
||
{
|
||
var regionList = ra.FindMulti(RecognitionObject.Ocr(ra.Width * 0.25, ra.Height * 0.2, ra.Width * 0.5, ra.Height * 0.6));
|
||
return PressUseResin(regionList, resinName);
|
||
}
|
||
|
||
public static (bool, int) PressUseResin(List<Region> regionList, string resinName)
|
||
{
|
||
var resinKey = regionList.FirstOrDefault(t => t.Text.Contains(resinName));
|
||
if (resinKey != null)
|
||
{
|
||
// 找到树脂名称对应的按键,关键词为使用,是同一行的(高度相交)
|
||
var useList = regionList.Where(t => t.Text.Contains("使用")).ToList();
|
||
if (useList.Count != 0)
|
||
{
|
||
// 找到使用按键
|
||
var useKey = useList.FirstOrDefault(t => t.X > TaskContext.Instance().SystemInfo.ScaleMax1080PCaptureRect.Width / 2
|
||
&& IsHeightOverlap(t, resinKey));
|
||
if (useKey != null)
|
||
{
|
||
// 点击使用
|
||
useKey.Click();
|
||
// 解决水龙王按下左键后没松开,然后后续点击按下就没反应了。使用双击
|
||
Sleep(60);
|
||
useKey.Click();
|
||
var num = GetResinNum(resinKey, resinName);
|
||
Logger.LogInformation("自动秘境:使用 {ResinName}, 数量:{Num}", resinName, num);
|
||
return (true, num);
|
||
}
|
||
else
|
||
{
|
||
Logger.LogWarning("自动秘境:未找到 {ResinName} 的使用按键", resinName);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Logger.LogWarning("自动秘境:未找到 {ResinName} 的使用按键", resinName);
|
||
}
|
||
}
|
||
|
||
return (false, 0);
|
||
}
|
||
|
||
private static int GetResinNum(Region region, string resinName)
|
||
{
|
||
if (resinName == "原粹树脂")
|
||
{
|
||
if (region.Text.Contains("20"))
|
||
{
|
||
return 20;
|
||
}
|
||
else if (region.Text.Contains("40"))
|
||
{
|
||
return 40;
|
||
}
|
||
else
|
||
{
|
||
Logger.LogWarning("自动秘境:未识别到原粹树脂消耗体力数量,默认按20计算");
|
||
return 20;
|
||
}
|
||
}
|
||
else if (resinName == "浓缩树脂" || resinName == "脆弱树脂" || resinName == "须臾树脂")
|
||
{
|
||
return 1;
|
||
}
|
||
else
|
||
{
|
||
throw new ArgumentException("未知的树脂名称");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 判断两个区域在垂直方向上是否有重叠
|
||
/// </summary>
|
||
private static bool IsHeightOverlap(Region region1, Region region2)
|
||
{
|
||
int region1Top = region1.Y;
|
||
int region1Bottom = region1.Y + region1.Height;
|
||
int region2Top = region2.Y;
|
||
int region2Bottom = region2.Y + region2.Height;
|
||
|
||
// 检查区域是否在垂直方向上重叠
|
||
return (region1Top <= region2Bottom && region1Bottom >= region2Top);
|
||
}
|
||
|
||
private async Task ArtifactSalvage()
|
||
{
|
||
if (!_taskParam.AutoArtifactSalvage)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (!int.TryParse(_taskParam.MaxArtifactStar, out var star))
|
||
{
|
||
star = 4;
|
||
}
|
||
|
||
await new AutoArtifactSalvageTask(new AutoArtifactSalvageTaskParam(star, javaScript: null, artifactSetFilter: null, maxNumToCheck: null, recognitionFailurePolicy: null)).Start(_ct);
|
||
}
|
||
} |