feat: 自动地脉花独立战斗配置与OCR遮罩 (#2829)

This commit is contained in:
ddaodan
2026-02-26 10:13:38 +08:00
committed by GitHub
parent 9e3c8920ba
commit 01f1beba0b
6 changed files with 561 additions and 93 deletions

View File

@@ -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));
}
}

View File

@@ -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 = "";
/// <summary>
/// 英文逗号分割,强制指定队伍角色。
/// </summary>
[ObservableProperty] private string _teamNames = "";
/// <summary>
/// 检测战斗结束。
/// </summary>
[ObservableProperty] private bool _fightFinishDetectEnabled = true;
/// <summary>
/// 根据技能CD优化出招人员。
/// </summary>
[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;
}
}

View File

@@ -23,6 +23,8 @@ public class AutoLeyLineOutcropParam:BaseTaskParam<AutoLeyLineOutcropTask>
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<AutoLeyLineOutcropTask>
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<AutoLeyLineOutcropTask>
this.Country = country;
this.LeyLineOutcropType = leyLineOutcropType;
}
}
}

View File

@@ -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<bool> 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<RectDrawable>(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<bool> 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<Region?> 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);
}
}
}
}

View File

@@ -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)
{

View File

@@ -2476,6 +2476,83 @@
ItemsSource="{Binding Source={x:Static pages:TaskSettingsPageViewModel.LeyLineOutcropCountryList}}"
SelectedItem="{Binding Config.AutoLeyLineOutcropConfig.Country, Mode=TwoWay}" />
</Grid>
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ui:TextBlock Grid.Row="0"
Grid.Column="0"
FontTypography="Body"
Text="选择战斗策略"
TextWrapping="Wrap" />
<ui:TextBlock Grid.Row="1"
Grid.Column="0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
Text="用于战斗"
TextWrapping="Wrap" />
<ui:Button Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,12,0"
Content="脚本仓库"
Command="{Binding OpenLocalScriptRepoCommand}"
Icon="{ui:SymbolIcon Archive24}" />
<ui:Button Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="2"
Margin="0,0,12,0"
Command="{Binding OpenFightFolderCommand}"
Content="打开目录" />
<ComboBox Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="3"
Width="180"
Margin="0,0,36,0"
ItemsSource="{Binding AutoFightViewModel.CombatStrategyList}"
SelectedItem="{Binding Config.AutoLeyLineOutcropConfig.FightConfig.StrategyName, Mode=TwoWay}">
<b:Interaction.Triggers>
<b:EventTrigger EventName="DropDownOpened">
<b:InvokeCommandAction Command="{Binding StrategyDropDownOpenedCommand}"
CommandParameter="Combat" />
</b:EventTrigger>
</b:Interaction.Triggers>
</ComboBox>
</Grid>
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ui:TextBlock Grid.Row="0"
Grid.Column="0"
FontTypography="Body"
Text="根据技能CD优化出招人员"
TextWrapping="Wrap" />
<ui:TextBlock Grid.Row="1"
Grid.Column="0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
Text="根据填入人或人和cd来决定当此人元素战技cd未结束时跳过此人出招来优化战斗流程可填入人名或人名数字用逗号分隔多种用分号分隔例如:白术;钟离,12;如果人名则用内置cd检查或填入数字也小于0如果是人名和数字则把数字当做出招cd(秒)。"
TextWrapping="Wrap" />
<ui:TextBox Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
MinWidth="180"
MaxWidth="800"
Margin="0,0,36,0"
Text="{Binding Config.AutoLeyLineOutcropConfig.FightConfig.ActionSchedulerByCd, Mode=TwoWay}"
TextWrapping="Wrap" />
</Grid>
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@@ -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}" />
</Grid>
<Grid Margin="16">
<Grid.RowDefinitions>