mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-03-15 07:43:20 +08:00
feat: 自动地脉花独立战斗配置与OCR遮罩 (#2829)
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user