From 01f1beba0b026af91254c8ca132fd7a33d8af40f Mon Sep 17 00:00:00 2001 From: ddaodan <40017293+ddaodan@users.noreply.github.com> Date: Thu, 26 Feb 2026 10:13:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=87=AA=E5=8A=A8=E5=9C=B0=E8=84=89?= =?UTF-8?q?=E8=8A=B1=E7=8B=AC=E7=AB=8B=E6=88=98=E6=96=97=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E4=B8=8EOCR=E9=81=AE=E7=BD=A9=20(#2829)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AutoLeyLineOutcropConfig.cs | 33 ++ .../AutoLeyLineOutcropFightConfig.cs | 116 +++++++ .../AutoLeyLineOutcropParam.cs | 5 +- .../AutoLeyLineOutcropTask.cs | 284 ++++++++++++++++-- BetterGenshinImpact/View/MaskWindow.xaml.cs | 137 ++++----- .../View/Pages/TaskSettingsPage.xaml | 79 ++++- 6 files changed, 561 insertions(+), 93 deletions(-) create mode 100644 BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropFightConfig.cs diff --git a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropConfig.cs b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropConfig.cs index 88219585..79b3ec8f 100644 --- a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropConfig.cs +++ b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropConfig.cs @@ -1,11 +1,17 @@ using CommunityToolkit.Mvvm.ComponentModel; using System; +using System.ComponentModel; namespace BetterGenshinImpact.GameTask.AutoLeyLineOutcrop; [Serializable] public partial class AutoLeyLineOutcropConfig : ObservableObject { + public AutoLeyLineOutcropConfig() + { + AttachFightConfigEvents(_fightConfig); + } + [ObservableProperty] private string _leyLineOutcropType = "启示之花"; @@ -44,4 +50,31 @@ public partial class AutoLeyLineOutcropConfig : ObservableObject [ObservableProperty] private bool _isGoToSynthesizer = false; + + [ObservableProperty] + private AutoLeyLineOutcropFightConfig _fightConfig = new(); + + partial void OnFightConfigChanged(AutoLeyLineOutcropFightConfig value) + { + AttachFightConfigEvents(value); + OnPropertyChanged(nameof(FightConfig)); + } + + private void AttachFightConfigEvents(AutoLeyLineOutcropFightConfig? config) + { + if (config == null) + { + return; + } + + config.PropertyChanged -= OnFightConfigPropertyChanged; + config.PropertyChanged += OnFightConfigPropertyChanged; + config.FinishDetectConfig.PropertyChanged -= OnFightConfigPropertyChanged; + config.FinishDetectConfig.PropertyChanged += OnFightConfigPropertyChanged; + } + + private void OnFightConfigPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + OnPropertyChanged(nameof(FightConfig)); + } } diff --git a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropFightConfig.cs b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropFightConfig.cs new file mode 100644 index 00000000..36cb4478 --- /dev/null +++ b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropFightConfig.cs @@ -0,0 +1,116 @@ +using BetterGenshinImpact.GameTask.AutoFight; +using CommunityToolkit.Mvvm.ComponentModel; +using System; + +namespace BetterGenshinImpact.GameTask.AutoLeyLineOutcrop; + +[Serializable] +public partial class AutoLeyLineOutcropFightConfig : ObservableObject +{ + [ObservableProperty] private string _strategyName = ""; + + /// + /// 英文逗号分割,强制指定队伍角色。 + /// + [ObservableProperty] private string _teamNames = ""; + + /// + /// 检测战斗结束。 + /// + [ObservableProperty] private bool _fightFinishDetectEnabled = true; + + /// + /// 根据技能CD优化出招人员。 + /// + [ObservableProperty] private string _actionSchedulerByCd = ""; + + [Serializable] + public partial class FightFinishDetectConfig : ObservableObject + { + [ObservableProperty] private string _battleEndProgressBarColor = ""; + [ObservableProperty] private string _battleEndProgressBarColorTolerance = ""; + [ObservableProperty] private bool _fastCheckEnabled = false; + [ObservableProperty] private bool _rotateFindEnemyEnabled = false; + [ObservableProperty] private string _fastCheckParams = ""; + [ObservableProperty] private string _checkEndDelay = ""; + [ObservableProperty] private string _beforeDetectDelay = ""; + [ObservableProperty] private int _rotaryFactor = 10; + [ObservableProperty] private bool _isFirstCheck = false; + [ObservableProperty] private bool _checkBeforeBurst = false; + } + + [ObservableProperty] private FightFinishDetectConfig _finishDetectConfig = new(); + [ObservableProperty] private string _guardianAvatar = string.Empty; + [ObservableProperty] private bool _guardianCombatSkip = false; + [ObservableProperty] private bool _guardianAvatarHold = false; + [ObservableProperty] private bool _burstEnabled = false; + [ObservableProperty] private bool _swimmingEnabled = false; + [ObservableProperty] private int _timeout = 120; + + public void CopyFromAutoFightConfig(AutoFightConfig source) + { + StrategyName = source.StrategyName; + TeamNames = source.TeamNames; + FightFinishDetectEnabled = source.FightFinishDetectEnabled; + ActionSchedulerByCd = source.ActionSchedulerByCd; + GuardianAvatar = source.GuardianAvatar; + GuardianCombatSkip = source.GuardianCombatSkip; + GuardianAvatarHold = source.GuardianAvatarHold; + BurstEnabled = source.BurstEnabled; + SwimmingEnabled = source.SwimmingEnabled; + Timeout = source.Timeout; + + FinishDetectConfig.BattleEndProgressBarColor = source.FinishDetectConfig.BattleEndProgressBarColor; + FinishDetectConfig.BattleEndProgressBarColorTolerance = source.FinishDetectConfig.BattleEndProgressBarColorTolerance; + FinishDetectConfig.FastCheckEnabled = source.FinishDetectConfig.FastCheckEnabled; + FinishDetectConfig.RotateFindEnemyEnabled = source.FinishDetectConfig.RotateFindEnemyEnabled; + FinishDetectConfig.FastCheckParams = source.FinishDetectConfig.FastCheckParams; + FinishDetectConfig.CheckEndDelay = source.FinishDetectConfig.CheckEndDelay; + FinishDetectConfig.BeforeDetectDelay = source.FinishDetectConfig.BeforeDetectDelay; + FinishDetectConfig.RotaryFactor = source.FinishDetectConfig.RotaryFactor; + FinishDetectConfig.IsFirstCheck = source.FinishDetectConfig.IsFirstCheck; + FinishDetectConfig.CheckBeforeBurst = source.FinishDetectConfig.CheckBeforeBurst; + } + + public AutoFightConfig ToAutoFightConfig() + { + var config = new AutoFightConfig + { + StrategyName = StrategyName, + TeamNames = TeamNames, + FightFinishDetectEnabled = FightFinishDetectEnabled, + ActionSchedulerByCd = ActionSchedulerByCd, + GuardianAvatar = GuardianAvatar, + GuardianCombatSkip = GuardianCombatSkip, + GuardianAvatarHold = GuardianAvatarHold, + BurstEnabled = BurstEnabled, + SwimmingEnabled = SwimmingEnabled, + Timeout = Timeout, + + // 地脉花战斗不启用战斗后拾取逻辑。 + PickDropsAfterFightEnabled = false, + PickDropsAfterFightSeconds = 0, + KazuhaPickupEnabled = false, + QinDoublePickUp = false, + OnlyPickEliteDropsMode = "DisableAutoPickupForNonElite", + KazuhaPartyName = string.Empty, + BattleThresholdForLoot = null + }; + + config.FinishDetectConfig = new AutoFightConfig.FightFinishDetectConfig + { + BattleEndProgressBarColor = FinishDetectConfig.BattleEndProgressBarColor, + BattleEndProgressBarColorTolerance = FinishDetectConfig.BattleEndProgressBarColorTolerance, + FastCheckEnabled = FinishDetectConfig.FastCheckEnabled, + RotateFindEnemyEnabled = FinishDetectConfig.RotateFindEnemyEnabled, + FastCheckParams = FinishDetectConfig.FastCheckParams, + CheckEndDelay = FinishDetectConfig.CheckEndDelay, + BeforeDetectDelay = FinishDetectConfig.BeforeDetectDelay, + RotaryFactor = FinishDetectConfig.RotaryFactor, + IsFirstCheck = FinishDetectConfig.IsFirstCheck, + CheckBeforeBurst = FinishDetectConfig.CheckBeforeBurst + }; + + return config; + } +} diff --git a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropParam.cs b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropParam.cs index 8f728989..527282d6 100644 --- a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropParam.cs +++ b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropParam.cs @@ -23,6 +23,8 @@ public class AutoLeyLineOutcropParam:BaseTaskParam public string Team { get; set; } //战斗超时时间 public int Timeout { get; set; } + //地脉花独立战斗配置 + public AutoLeyLineOutcropFightConfig FightConfig { get; set; } = new(); //是否前往合成台合成浓缩树脂 public bool IsGoToSynthesizer { get; set; } //是否使用脆弱树脂 @@ -46,6 +48,7 @@ public class AutoLeyLineOutcropParam:BaseTaskParam FriendshipTeam= config.FriendshipTeam; Team= config.Team; Timeout= config.Timeout; + FightConfig = config.FightConfig ?? new AutoLeyLineOutcropFightConfig(); IsGoToSynthesizer=config.IsGoToSynthesizer; UseFragileResin= config.UseFragileResin; UseTransientResin= config.UseTransientResin; @@ -67,4 +70,4 @@ public class AutoLeyLineOutcropParam:BaseTaskParam this.Country = country; this.LeyLineOutcropType = leyLineOutcropType; } -} \ No newline at end of file +} diff --git a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs index ed8e5a16..ab73f075 100644 --- a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs +++ b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs @@ -17,6 +17,7 @@ using BetterGenshinImpact.GameTask; using BetterGenshinImpact.GameTask.Model; using BetterGenshinImpact.GameTask.Model.Area; using BetterGenshinImpact.Service.Notification; +using BetterGenshinImpact.View.Drawable; using Microsoft.Extensions.Logging; using OpenCvSharp; using System; @@ -31,6 +32,7 @@ using System.Threading.Tasks; using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception; using Vanara.PInvoke; using static BetterGenshinImpact.GameTask.Common.TaskControl; +using BetterGenshinImpact.View; namespace BetterGenshinImpact.GameTask.AutoLeyLineOutcrop; @@ -70,6 +72,13 @@ public class AutoLeyLineOutcropTask : ISoloTask private const int MaxRecheckCount = 3; private const int MaxConsecutiveFailures = 5; + private const string OcrFlowOverlayKey = "AutoLeyLineOutcrop.OcrFlow"; + private const string OcrFightOverlayKey = "AutoLeyLineOutcrop.OcrFight"; + private const int OcrOverlayRenderLeadMs = 300; + private static readonly System.Drawing.Pen OcrOverlayPen = new(System.Drawing.Color.Lime, 2); + private bool _overlayDisplayTemporarilyEnabled; + private bool _overlayDisplayOriginalValue; + private DateTime _lastMaskBringTopTime = DateTime.MinValue; public string Name => "自动地脉花"; @@ -86,6 +95,7 @@ public class AutoLeyLineOutcropTask : ISoloTask try { Initialize(); + EnsureMaskOverlayVisible(); var runTimesValue = await HandleResinExhaustionMode(); if (runTimesValue <= 0) { @@ -119,16 +129,24 @@ public class AutoLeyLineOutcropTask : ISoloTask { try { - await EnsureExitRewardPage(); - } - catch (Exception ex) - { - _logger.LogDebug(ex, "地脉花结束后尝试退出奖励界面失败"); - } + try + { + await EnsureExitRewardPage(); + } + catch (Exception ex) + { + _logger.LogDebug(ex, "地脉花结束后尝试退出奖励界面失败"); + } - if (!_marksStatus) + if (!_marksStatus) + { + await OpenCustomMarks(); + } + } + finally { - await OpenCustomMarks(); + ClearOcrOverlayKeys(); + RestoreMaskOverlayVisible(); } } } @@ -144,6 +162,8 @@ public class AutoLeyLineOutcropTask : ISoloTask private void ValidateSettings() { + _taskParam.FightConfig ??= new AutoLeyLineOutcropFightConfig(); + if (string.IsNullOrWhiteSpace(_taskParam.LeyLineOutcropType)) { throw new Exception("地脉花类型未选择"); @@ -168,6 +188,20 @@ public class AutoLeyLineOutcropTask : ISoloTask { _taskParam.Count = 1; } + + if (string.IsNullOrWhiteSpace(_taskParam.FightConfig.StrategyName) && _taskParam.Timeout > 0) + { + _taskParam.FightConfig.Timeout = _taskParam.Timeout; + } + + if (_taskParam.FightConfig.Timeout <= 0) + { + _taskParam.FightConfig.Timeout = _taskParam.Timeout > 0 ? _taskParam.Timeout : 120; + } + else + { + _taskParam.Timeout = _taskParam.FightConfig.Timeout; + } } private void LoadConfigData() @@ -635,7 +669,7 @@ public class AutoLeyLineOutcropTask : ISoloTask var lastRoute = path.Routes.Last(); var targetRoute = lastRoute.Replace("Assets/pathing/", "Assets/pathing/target/").Replace("-rerun", ""); - await ProcessLeyLineOutcrop(_taskParam.Timeout, targetRoute); + await ProcessLeyLineOutcrop(_taskParam.FightConfig.Timeout, targetRoute); var rewardSuccess = await AttemptReward(); if (!rewardSuccess) @@ -819,18 +853,24 @@ public class AutoLeyLineOutcropTask : ISoloTask await Delay(500, _ct); _logger.LogDebug("检测地脉花交互状态,重试次数: {Retries}/{MaxRetries}", retries + 1, maxRetries); using var capture = CaptureToRectArea(); + using var ocrOverlayScope = DrawOcrOverlayScope(capture, OcrFlowOverlayKey, _ocrRo2!.RegionOfInterest, _ocrRo3!.RegionOfInterest); + await WaitOcrOverlayRenderTick(); + string result1Text; + string result2Text; var result1 = FindSafe(capture, _ocrRo2!); var result2 = FindSafe(capture, _ocrRo3!); - _logger.LogDebug("OCR结果: result1='{Text1}', result2='{Text2}'", result1.Text, result2.Text); + result1Text = result1.Text; + result2Text = result2.Text; + _logger.LogDebug("OCR结果: result1='{Text1}', result2='{Text2}'", result1Text, result2Text); - if (result2.Text.Contains("之花", StringComparison.Ordinal)) + if (result2Text.Contains("之花", StringComparison.Ordinal)) { _logger.LogDebug("识别到地脉之花入口"); await SwitchToFriendshipTeamIfNeeded(); return true; } - if (result2.Text.Contains("溢口", StringComparison.Ordinal)) + if (result2Text.Contains("溢口", StringComparison.Ordinal)) { _logger.LogDebug("识别到溢口提示,尝试交互"); Simulation.SendInput.SimulateAction(GIActions.PickUpOrInteract); @@ -838,7 +878,7 @@ public class AutoLeyLineOutcropTask : ISoloTask Simulation.SendInput.SimulateAction(GIActions.PickUpOrInteract); await Delay(500, _ct); } - else if (!ContainsFightText(result1.Text)) + else if (!ContainsFightText(result1Text)) { _logger.LogDebug("未识别到战斗提示,执行路径: {Path}", targetPath); await RunPathingFile(targetPath); @@ -889,6 +929,7 @@ public class AutoLeyLineOutcropTask : ISoloTask private async Task AutoFight(int timeoutSeconds) { var fightCts = CancellationTokenSource.CreateLinkedTokenSource(_ct); + using var autoFightConfigScope = UseLeyLineAutoFightConfigScope(); // Ley line uses OCR-based finish detection; disable auto-fight finish detect. var fightTask = StartAutoFightWithoutFinishDetect(fightCts.Token); var fightResult = await RecognizeTextInRegion(timeoutSeconds * 1000); @@ -915,7 +956,7 @@ public class AutoLeyLineOutcropTask : ISoloTask private Task StartAutoFightWithoutFinishDetect(CancellationToken ct) { - var autoFightConfig = TaskContext.Instance().Config.AutoFightConfig; + var autoFightConfig = BuildLeyLineAutoFightConfig(); var strategyPath = BuildAutoFightStrategyPath(autoFightConfig); var taskParam = new AutoFightParam(strategyPath, autoFightConfig) { @@ -925,9 +966,39 @@ public class AutoLeyLineOutcropTask : ISoloTask // Avoid false finish signals for ley line fights. taskParam.FinishDetectConfig.FastCheckEnabled = false; taskParam.FinishDetectConfig.RotateFindEnemyEnabled = false; + taskParam.PickDropsAfterFightEnabled = false; + taskParam.KazuhaPickupEnabled = false; + taskParam.QinDoublePickUp = false; + taskParam.OnlyPickEliteDropsMode = "DisableAutoPickupForNonElite"; return new AutoFightTask(taskParam).Start(ct); } + private IDisposable UseLeyLineAutoFightConfigScope() + { + var allConfig = TaskContext.Instance().Config; + var original = allConfig.AutoFightConfig; + allConfig.AutoFightConfig = BuildLeyLineAutoFightConfig(); + return new AutoFightConfigScope(allConfig, original); + } + + private AutoFightConfig BuildLeyLineAutoFightConfig() + { + var globalAutoFightConfig = TaskContext.Instance().Config.AutoFightConfig; + var fightConfig = _taskParam.FightConfig; + if (string.IsNullOrWhiteSpace(fightConfig.StrategyName)) + { + fightConfig.CopyFromAutoFightConfig(globalAutoFightConfig); + } + + if (fightConfig.Timeout <= 0) + { + fightConfig.Timeout = _taskParam.Timeout > 0 ? _taskParam.Timeout : Math.Max(globalAutoFightConfig.Timeout, 1); + } + + _taskParam.Timeout = fightConfig.Timeout; + return fightConfig.ToAutoFightConfig(); + } + private static string BuildAutoFightStrategyPath(AutoFightConfig config) { var path = Global.Absolute(@"User\AutoFight\" + config.StrategyName + ".txt"); @@ -954,8 +1025,13 @@ public class AutoLeyLineOutcropTask : ISoloTask while ((DateTime.UtcNow - start).TotalMilliseconds < timeoutMs) { using var capture = CaptureToRectArea(); + using var ocrOverlayScope = DrawOcrOverlayScope(capture, OcrFightOverlayKey, _ocrRo1!.RegionOfInterest, _ocrRo2!.RegionOfInterest); + await WaitOcrOverlayRenderTick(); + string text; + bool foundText; var result = capture.Find(_ocrRo1!); - var text = result.Text; + text = result.Text; + foundText = RecognizeFightText(capture); if (successKeywords.Any(text.Contains)) { @@ -969,7 +1045,6 @@ public class AutoLeyLineOutcropTask : ISoloTask return false; } - var foundText = RecognizeFightText(capture); if (!foundText) { noTextCount++; @@ -1278,6 +1353,110 @@ public class AutoLeyLineOutcropTask : ISoloTask return false; } + private IDisposable DrawOcrOverlayScope(ImageRegion capture, string key, params Rect[] rois) + { + var drawList = new List(rois.Length); + foreach (var roi in rois) + { + var clamped = roi.ClampTo(capture.Width, capture.Height); + if (clamped.Width <= 0 || clamped.Height <= 0) + { + continue; + } + + drawList.Add(capture.ToRectDrawable(clamped, key, OcrOverlayPen)); + } + + var drawContent = VisionContext.Instance().DrawContent; + drawContent.PutOrRemoveRectList(key, drawList.Count > 0 ? drawList : null); + RefreshMaskWindowForOverlay(); + return new OcrOverlayScope(drawContent, key); + } + + private void ClearOcrOverlayKeys() + { + var drawContent = VisionContext.Instance().DrawContent; + drawContent.RemoveRect(OcrFlowOverlayKey); + drawContent.RemoveRect(OcrFightOverlayKey); + drawContent.PutOrRemoveTextList(OcrFlowOverlayKey, null); + drawContent.PutOrRemoveTextList(OcrFightOverlayKey, null); + } + + private async Task WaitOcrOverlayRenderTick() + { + await Task.Yield(); + await Task.Delay(OcrOverlayRenderLeadMs, _ct); + } + + private void EnsureMaskOverlayVisible() + { + var config = TaskContext.Instance().Config.MaskWindowConfig; + _overlayDisplayOriginalValue = config.DisplayRecognitionResultsOnMask; + if (!config.DisplayRecognitionResultsOnMask) + { + config.DisplayRecognitionResultsOnMask = true; + _overlayDisplayTemporarilyEnabled = true; + } + + var maskWindow = MaskWindow.InstanceNullable(); + if (maskWindow != null) + { + maskWindow.Invoke(() => + { + maskWindow.Topmost = true; + if (!maskWindow.IsVisible) + { + maskWindow.Show(); + } + + maskWindow.BringToTop(); + }); + } + } + + private void RestoreMaskOverlayVisible() + { + if (!_overlayDisplayTemporarilyEnabled) + { + return; + } + + TaskContext.Instance().Config.MaskWindowConfig.DisplayRecognitionResultsOnMask = _overlayDisplayOriginalValue; + _overlayDisplayTemporarilyEnabled = false; + } + + private void RefreshMaskWindowForOverlay() + { + var maskWindow = MaskWindow.InstanceNullable(); + if (maskWindow == null) + { + return; + } + + var now = DateTime.UtcNow; + var shouldBringTop = now - _lastMaskBringTopTime > TimeSpan.FromSeconds(1); + if (shouldBringTop) + { + _lastMaskBringTopTime = now; + } + + maskWindow.Invoke(() => + { + maskWindow.Topmost = true; + if (!maskWindow.IsVisible) + { + maskWindow.Show(); + } + + if (shouldBringTop) + { + maskWindow.BringToTop(); + } + + maskWindow.Refresh(); + }); + } + private async Task CheckOriginalResinEmpty() { using var capture = CaptureToRectArea(); @@ -1497,12 +1676,22 @@ public class AutoLeyLineOutcropTask : ISoloTask await Delay(1000, _ct); await FindAndClickCountry(country); - await FindAndCancelTrackingInBook(); + var trackButton = await FindAndCancelTrackingInBook(); for (var retry = 0; retry < 3; retry++) { await Delay(1000, _ct); - GameCaptureRegion.GameRegion1080PPosClick(1500, 850); + if (trackButton != null) + { + trackButton.Click(); + _logger.LogDebug("通过 OCR 结果点击追踪按钮,text={Text}", trackButton.Text); + } + else + { + GameCaptureRegion.GameRegion1080PPosClick(1500, 850); + _logger.LogDebug("未识别到追踪按钮,回退固定坐标点击"); + } + await Delay(2500, _ct); if (await CheckBigMapOpened()) @@ -1514,7 +1703,7 @@ public class AutoLeyLineOutcropTask : ISoloTask { await _returnMainUiTask.Start(_ct); await FindAndClickCountry(country); - await FindAndCancelTrackingInBook(); + trackButton = await FindAndCancelTrackingInBook(); } else { @@ -1554,13 +1743,29 @@ public class AutoLeyLineOutcropTask : ISoloTask target.Click(); } - private async Task FindAndCancelTrackingInBook() + private static bool IsTrackButtonText(string text) + { + return (text.Contains("追踪", StringComparison.Ordinal) || text.Contains("追蹤", StringComparison.Ordinal)) + && !text.Contains("停止", StringComparison.Ordinal); + } + + private async Task FindAndCancelTrackingInBook() { using var capture = CaptureToRectArea(); var list = capture.FindMulti(_ocrRoThis); + var track = list.FirstOrDefault(r => IsTrackButtonText(r.Text)); var stop = list.FirstOrDefault(r => r.Text.Contains("停止", StringComparison.Ordinal)); - stop?.Click(); - await Delay(1000, _ct); + if (stop != null) + { + stop.Click(); + await Delay(1000, _ct); + + using var refreshCapture = CaptureToRectArea(); + var refreshList = refreshCapture.FindMulti(_ocrRoThis); + track = refreshList.FirstOrDefault(r => IsTrackButtonText(r.Text)); + } + + return track; } private async Task CancelTrackingInMap() @@ -1940,6 +2145,39 @@ public class AutoLeyLineOutcropTask : ISoloTask public int FragileResinTimes { get; set; } } + private sealed class OcrOverlayScope(DrawContent drawContent, string key) : IDisposable + { + private bool _disposed; + + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + drawContent.RemoveRect(key); + drawContent.PutOrRemoveTextList(key, null); + } + } + + private sealed class AutoFightConfigScope(AllConfig allConfig, AutoFightConfig originalConfig) : IDisposable + { + private bool _disposed; + + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + allConfig.AutoFightConfig = originalConfig; + } + } + private readonly struct UseButton { public int X { get; } @@ -1958,4 +2196,4 @@ public class AutoLeyLineOutcropTask : ISoloTask GameCaptureRegion.GameRegion1080PPosClick(X, Y); } } -} \ No newline at end of file +} diff --git a/BetterGenshinImpact/View/MaskWindow.xaml.cs b/BetterGenshinImpact/View/MaskWindow.xaml.cs index 9794db2e..7e0dba17 100644 --- a/BetterGenshinImpact/View/MaskWindow.xaml.cs +++ b/BetterGenshinImpact/View/MaskWindow.xaml.cs @@ -52,7 +52,6 @@ public partial class MaskWindow : Window private MaskWindowConfig? _maskWindowConfig; private MapLabelSearchWindow? _mapLabelSearchWindow; private CancellationTokenSource? _mapLabelCategorySelectCts; - static MaskWindow() { if (Application.Current.TryFindResource("TextThemeFontFamily") is FontFamily fontFamily) @@ -463,96 +462,98 @@ public partial class MaskWindow : Window return; } - // 先有上方判断的原因是,有可能Render的时候,配置还未初始化 - if (!TaskContext.Instance().Config.MaskWindowConfig.DisplayRecognitionResultsOnMask) + var displayRecognitionResults = TaskContext.Instance().Config.MaskWindowConfig.DisplayRecognitionResultsOnMask; + if (!displayRecognitionResults) { return; } - foreach (var kv in VisionContext.Instance().DrawContent.RectList) + if (displayRecognitionResults) { - foreach (var drawable in kv.Value) + foreach (var kv in VisionContext.Instance().DrawContent.RectList) { - if (!drawable.IsEmpty) + foreach (var drawable in kv.Value) { - drawingContext.DrawRectangle(Brushes.Transparent, - new Pen(new SolidColorBrush(drawable.Pen.Color.ToWindowsColor()), drawable.Pen.Width), - drawable.Rect); + if (!drawable.IsEmpty) + { + drawingContext.DrawRectangle( + Brushes.Transparent, + new Pen(new SolidColorBrush(drawable.Pen.Color.ToWindowsColor()), drawable.Pen.Width), + drawable.Rect); + } } } - } - foreach (var kv in VisionContext.Instance().DrawContent.LineList) - { - foreach (var drawable in kv.Value) + foreach (var kv in VisionContext.Instance().DrawContent.LineList) { - drawingContext.DrawLine(new Pen(new SolidColorBrush(drawable.Pen.Color.ToWindowsColor()), drawable.Pen.Width), drawable.P1, drawable.P2); - } - } - - foreach (var kv in VisionContext.Instance().DrawContent.TextList) - { - bool isSkillCd = kv.Key == "SkillCdText"; - var systemInfo = TaskContext.Instance().SystemInfo; - // 使用不封顶的物理比例进行 UI 大小缩放 - var scaleTo1080 = systemInfo.ScaleTo1080PRatio; - - foreach (var drawable in kv.Value) - { - if (!drawable.IsEmpty) + foreach (var drawable in kv.Value) { - var pixelsPerDip = VisualTreeHelper.GetDpi(this).PixelsPerDip; - var renderPoint = new Point(drawable.Point.X / pixelsPerDip, drawable.Point.Y / pixelsPerDip); + drawingContext.DrawLine(new Pen(new SolidColorBrush(drawable.Pen.Color.ToWindowsColor()), drawable.Pen.Width), drawable.P1, drawable.P2); + } + } - if (isSkillCd) + foreach (var kv in VisionContext.Instance().DrawContent.TextList) + { + bool isSkillCd = kv.Key == "SkillCdText"; + var systemInfo = TaskContext.Instance().SystemInfo; + var scaleTo1080 = systemInfo.ScaleTo1080PRatio; + + foreach (var drawable in kv.Value) + { + if (!drawable.IsEmpty) { - // 自定义缩放 - var skillConfigScale = TaskContext.Instance().Config.SkillCdConfig.Scale; - double scaledFontSize = (26 * scaleTo1080 * skillConfigScale) / pixelsPerDip; - var mediumTypeface = new Typeface(_fgiTypeface.FontFamily, _fgiTypeface.Style, FontWeights.Medium, _fgiTypeface.Stretch); - bool isZeroCd = - double.TryParse(drawable.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out var cdValue) - && Math.Abs(cdValue) < 0.8; + var pixelsPerDip = VisualTreeHelper.GetDpi(this).PixelsPerDip; + var renderPoint = new Point(drawable.Point.X / pixelsPerDip, drawable.Point.Y / pixelsPerDip); - // 从配置读取颜色 - var skillConfig = TaskContext.Instance().Config.SkillCdConfig; - string textColorStr = isZeroCd ? skillConfig.TextReadyColor : skillConfig.TextNormalColor; - string bgColorStr = isZeroCd ? skillConfig.BackgroundReadyColor : skillConfig.BackgroundNormalColor; + if (isSkillCd) + { + var skillConfigScale = TaskContext.Instance().Config.SkillCdConfig.Scale; + double scaledFontSize = (26 * scaleTo1080 * skillConfigScale) / pixelsPerDip; + var mediumTypeface = new Typeface(_fgiTypeface.FontFamily, _fgiTypeface.Style, FontWeights.Medium, _fgiTypeface.Stretch); + bool isZeroCd = + double.TryParse(drawable.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out var cdValue) + && Math.Abs(cdValue) < 0.8; - Color textColor = ParseColor(textColorStr) ?? (isZeroCd ? Color.FromRgb(93, 204, 23) : Color.FromRgb(218, 74, 35)); - Color bgColor = ParseColor(bgColorStr) ?? Colors.White; + var skillConfig = TaskContext.Instance().Config.SkillCdConfig; + string textColorStr = isZeroCd ? skillConfig.TextReadyColor : skillConfig.TextNormalColor; + string bgColorStr = isZeroCd ? skillConfig.BackgroundReadyColor : skillConfig.BackgroundNormalColor; - Brush textBrush = new SolidColorBrush(textColor); - Brush bgBrush = new SolidColorBrush(bgColor); + Color textColor = ParseColor(textColorStr) ?? (isZeroCd ? Color.FromRgb(93, 204, 23) : Color.FromRgb(218, 74, 35)); + Color bgColor = ParseColor(bgColorStr) ?? Colors.White; - var formattedText = new FormattedText( - drawable.Text, - CultureInfo.GetCultureInfo("zh-cn"), - FlowDirection.LeftToRight, - mediumTypeface, - scaledFontSize, - textBrush, - pixelsPerDip); + Brush textBrush = new SolidColorBrush(textColor); + Brush bgBrush = new SolidColorBrush(bgColor); - double px = (6 * scaleTo1080 * skillConfigScale) / pixelsPerDip; - double py = (2 * scaleTo1080 * skillConfigScale) / pixelsPerDip; - double radius = (5 * scaleTo1080 * skillConfigScale) / pixelsPerDip; - var bgRect = new Rect(renderPoint.X - px, renderPoint.Y - py, formattedText.Width + px * 2, formattedText.Height + py * 2); - drawingContext.DrawRoundedRectangle(bgBrush, null, bgRect, radius, radius); - drawingContext.DrawText(formattedText, renderPoint); - } - else - { - double defaultFontSize = (36 * scaleTo1080) / pixelsPerDip; - drawingContext.DrawText(new FormattedText(drawable.Text, - CultureInfo.GetCultureInfo("zh-cn"), - FlowDirection.LeftToRight, - _typeface, - defaultFontSize, Brushes.Black, pixelsPerDip), renderPoint); + var formattedText = new FormattedText( + drawable.Text, + CultureInfo.GetCultureInfo("zh-cn"), + FlowDirection.LeftToRight, + mediumTypeface, + scaledFontSize, + textBrush, + pixelsPerDip); + + double px = (6 * scaleTo1080 * skillConfigScale) / pixelsPerDip; + double py = (2 * scaleTo1080 * skillConfigScale) / pixelsPerDip; + double radius = (5 * scaleTo1080 * skillConfigScale) / pixelsPerDip; + var bgRect = new Rect(renderPoint.X - px, renderPoint.Y - py, formattedText.Width + px * 2, formattedText.Height + py * 2); + drawingContext.DrawRoundedRectangle(bgBrush, null, bgRect, radius, radius); + drawingContext.DrawText(formattedText, renderPoint); + } + else + { + double defaultFontSize = (36 * scaleTo1080) / pixelsPerDip; + drawingContext.DrawText(new FormattedText(drawable.Text, + CultureInfo.GetCultureInfo("zh-cn"), + FlowDirection.LeftToRight, + _typeface, + defaultFontSize, Brushes.Black, pixelsPerDip), renderPoint); + } } } } } + } catch (Exception e) { diff --git a/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml b/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml index 88407f4f..54318dc2 100644 --- a/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml +++ b/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml @@ -2476,6 +2476,83 @@ ItemsSource="{Binding Source={x:Static pages:TaskSettingsPageViewModel.LeyLineOutcropCountryList}}" SelectedItem="{Binding Config.AutoLeyLineOutcropConfig.Country, Mode=TwoWay}" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2696,7 +2773,7 @@ Maximum="9999" Minimum="1" ValidationMode="InvalidInputOverwritten" - Value="{Binding Config.AutoLeyLineOutcropConfig.Timeout, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> + Value="{Binding Config.AutoLeyLineOutcropConfig.FightConfig.Timeout, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />