feat: 为自动地脉花配置新增万叶与琴的拾取选项 (#2862)

This commit is contained in:
ddaodan
2026-02-28 23:59:04 +08:00
committed by GitHub
parent dad2e8ef7d
commit 23de87d1a6
3 changed files with 248 additions and 9 deletions

View File

@@ -45,6 +45,8 @@ public partial class AutoLeyLineOutcropFightConfig : ObservableObject
[ObservableProperty] private bool _guardianAvatarHold = false;
[ObservableProperty] private bool _burstEnabled = false;
[ObservableProperty] private bool _swimmingEnabled = false;
[ObservableProperty] private bool _kazuhaPickupEnabled = true;
[ObservableProperty] private bool _qinDoublePickUp = false;
[ObservableProperty] private int _timeout = 120;
public void CopyFromAutoFightConfig(AutoFightConfig source)
@@ -58,6 +60,8 @@ public partial class AutoLeyLineOutcropFightConfig : ObservableObject
GuardianAvatarHold = source.GuardianAvatarHold;
BurstEnabled = source.BurstEnabled;
SwimmingEnabled = source.SwimmingEnabled;
KazuhaPickupEnabled = source.KazuhaPickupEnabled;
QinDoublePickUp = source.QinDoublePickUp;
Timeout = source.Timeout;
FinishDetectConfig.BattleEndProgressBarColor = source.FinishDetectConfig.BattleEndProgressBarColor;

View File

@@ -6,9 +6,13 @@ using BetterGenshinImpact.Core.Script;
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.Core.Simulator.Extensions;
using BetterGenshinImpact.GameTask.AutoPathing;
using BetterGenshinImpact.GameTask.AutoPathing.Handler;
using BetterGenshinImpact.GameTask.AutoPathing.Model;
using BetterGenshinImpact.GameTask.AutoTrackPath;
using BetterGenshinImpact.GameTask.AutoFight;
using BetterGenshinImpact.GameTask.AutoPick.Assets;
using BetterGenshinImpact.GameTask.AutoFight.Model;
using BetterGenshinImpact.GameTask.AutoFight.Script;
using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.GameTask.Common.BgiVision;
using BetterGenshinImpact.GameTask.Common.Job;
@@ -76,6 +80,7 @@ public class AutoLeyLineOutcropTask : ISoloTask
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 static readonly object PickLock = new();
private bool _overlayDisplayTemporarilyEnabled;
private bool _overlayDisplayOriginalValue;
private DateTime _lastMaskBringTopTime = DateTime.MinValue;
@@ -668,8 +673,10 @@ public class AutoLeyLineOutcropTask : ISoloTask
}
var lastRoute = path.Routes.Last();
var targetRoute = lastRoute.Replace("Assets/pathing/", "Assets/pathing/target/").Replace("-rerun", "");
await ProcessLeyLineOutcrop(_taskParam.FightConfig.Timeout, targetRoute);
var targetRoute = BuildTargetRoute(lastRoute);
var rerunRoute = BuildRerunRoute(lastRoute);
var fromTeleportStart = "teleport".Equals(path.StartNode.Type, StringComparison.OrdinalIgnoreCase);
await ProcessLeyLineOutcrop(_taskParam.FightConfig.Timeout, targetRoute, rerunRoute, fromTeleportStart);
var rewardSuccess = await AttemptReward();
if (!rewardSuccess)
@@ -680,6 +687,35 @@ public class AutoLeyLineOutcropTask : ISoloTask
_consecutiveFailureCount = 0;
}
private static string BuildTargetRoute(string routePath)
{
return routePath
.Replace("assets/pathing/", "assets/pathing/target/", StringComparison.OrdinalIgnoreCase)
.Replace("-rerun", "", StringComparison.OrdinalIgnoreCase);
}
private static string BuildRerunRoute(string routePath)
{
var rerunRoute = routePath
.Replace("assets/pathing/target/", "assets/pathing/rerun/", StringComparison.OrdinalIgnoreCase)
.Replace("assets/pathing/", "assets/pathing/rerun/", StringComparison.OrdinalIgnoreCase);
if (!rerunRoute.Contains("-rerun", StringComparison.OrdinalIgnoreCase))
{
rerunRoute = rerunRoute.Replace(".json", "-rerun.json", StringComparison.OrdinalIgnoreCase);
}
return rerunRoute;
}
private bool PathingFileExists(string routePath)
{
var workDir = Global.Absolute(@"GameTask\AutoLeyLineOutcrop");
var localPath = routePath.Replace("/", Path.DirectorySeparatorChar.ToString());
var fullPath = Path.Combine(workDir, localPath);
return File.Exists(fullPath);
}
private async Task RunPathingFile(string routePath)
{
await _returnMainUiTask.Start(_ct);
@@ -841,7 +877,7 @@ public class AutoLeyLineOutcropTask : ISoloTask
}
}
private async Task<bool> ProcessLeyLineOutcrop(int timeoutSeconds, string targetPath, int retries = 0)
private async Task<bool> ProcessLeyLineOutcrop(int timeoutSeconds, string targetPath, string rerunPath, bool fromTeleportStart, int retries = 0)
{
const int maxRetries = 3;
if (retries >= maxRetries)
@@ -880,9 +916,20 @@ public class AutoLeyLineOutcropTask : ISoloTask
}
else if (!ContainsFightText(result1Text))
{
_logger.LogDebug("未识别到战斗提示,执行路径: {Path}", targetPath);
await RunPathingFile(targetPath);
return await ProcessLeyLineOutcrop(timeoutSeconds, targetPath, retries + 1);
var recoverPath = retries == 0
? targetPath
: fromTeleportStart
? targetPath
: rerunPath;
if (!PathingFileExists(recoverPath))
{
_logger.LogWarning("纠偏路径不存在回退target路径: {Path}", recoverPath);
recoverPath = targetPath;
}
_logger.LogDebug("未识别到战斗提示,执行纠偏路径: {Path}", recoverPath);
await RunPathingFile(recoverPath);
return await ProcessLeyLineOutcrop(timeoutSeconds, targetPath, rerunPath, fromTeleportStart, retries + 1);
}
var fightResult = await AutoFight(timeoutSeconds);
@@ -891,17 +938,148 @@ public class AutoLeyLineOutcropTask : ISoloTask
await EnsureExitRewardPage();
if (await ProcessResurrect())
{
return await ProcessLeyLineOutcrop(timeoutSeconds, targetPath, retries + 1);
return await ProcessLeyLineOutcrop(timeoutSeconds, targetPath, rerunPath, fromTeleportStart, retries + 1);
}
throw new Exception("战斗失败");
}
await TryCollectDropsAfterFight();
await SwitchToFriendshipTeamIfNeeded();
await AutoNavigateToReward();
return true;
}
private async Task TryCollectDropsAfterFight()
{
if (!_taskParam.FightConfig.KazuhaPickupEnabled)
{
return;
}
try
{
var combatScenes = await RunnerContext.Instance.GetCombatScenes(_ct);
if (combatScenes == null)
{
_logger.LogWarning("战后聚集拾取:队伍识别失败,跳过");
return;
}
var kazuha = combatScenes.SelectAvatar("枫原万叶");
if (kazuha != null)
{
await TryKazuhaCollect(kazuha);
return;
}
var qin = combatScenes.SelectAvatar("琴");
if (qin != null)
{
await TryQinCollect(combatScenes, qin);
return;
}
_logger.LogDebug("战后聚集拾取:当前队伍无万叶/琴,跳过");
}
catch (Exception ex)
{
_logger.LogDebug(ex, "战后聚集拾取异常");
}
finally
{
Simulation.ReleaseAllKey();
}
}
private async Task TryKazuhaCollect(Avatar kazuha)
{
_logger.LogInformation("战后聚集拾取开始使用枫原万叶执行长E拾取");
await Delay(200, _ct);
if (!kazuha.TrySwitch(10))
{
_logger.LogWarning("战后聚集拾取:切换到万叶失败,跳过");
return;
}
_logger.LogInformation("战后聚集拾取万叶已切换等待元素战技CD");
await kazuha.WaitSkillCd(_ct);
kazuha.UseSkill(true);
await Delay(50, _ct);
Simulation.SendInput.SimulateAction(GIActions.NormalAttack);
await Delay(1500, _ct);
kazuha.AfterUseSkill();
_logger.LogInformation("战后聚集拾取万叶长E聚集动作执行完成");
}
private async Task TryQinCollect(CombatScenes combatScenes, Avatar qin)
{
_logger.LogInformation("战后聚集拾取:使用琴-长E拾取掉落物");
var find = _taskParam.FightConfig.QinDoublePickUp;
await Delay(150, _ct);
if (qin.TrySwitch(10))
{
var actionsToUse = PickUpCollectHandler.PickUpActions
.Where(action => action.StartsWith("琴-长E ", StringComparison.OrdinalIgnoreCase))
.Select(action => action.Replace("琴-长E", "琴", StringComparison.OrdinalIgnoreCase))
.ToArray();
foreach (var actionStr in actionsToUse)
{
var pickUpAction = CombatScriptParser.ParseContext(actionStr);
for (var i = 0; i < 2; i++)
{
await qin.WaitSkillCd(_ct);
foreach (var command in pickUpAction.CombatCommands)
{
command.Execute(combatScenes);
Task.Run(() =>
{
if (Monitor.TryEnter(PickLock))
{
try
{
if (find)
{
using var imagePick = CaptureToRectArea();
if (imagePick.Find(AutoPickAssets.Instance.PickRo).IsExist())
{
find = false;
}
}
}
finally
{
Monitor.Exit(PickLock);
}
}
});
}
if (!find)
{
break;
}
if (i == 0)
{
_logger.LogInformation("战后聚集拾取:尝试再次执行琴-长E拾取");
qin.AfterUseSkill();
}
else
{
break;
}
}
Simulation.ReleaseAllKey();
}
}
else
{
_logger.LogWarning("战后聚集拾取:切换到琴失败,跳过");
}
}
private Region FindSafe(ImageRegion capture, RecognitionObject ro)
{
var roi = ro.RegionOfInterest;
@@ -1370,7 +1548,7 @@ public class AutoLeyLineOutcropTask : ISoloTask
var drawContent = VisionContext.Instance().DrawContent;
drawContent.PutOrRemoveRectList(key, drawList.Count > 0 ? drawList : null);
RefreshMaskWindowForOverlay();
return new OcrOverlayScope(drawContent, key);
return new OcrOverlayScope(drawContent, key, RefreshMaskWindowForOverlay);
}
private void ClearOcrOverlayKeys()
@@ -1380,6 +1558,7 @@ public class AutoLeyLineOutcropTask : ISoloTask
drawContent.RemoveRect(OcrFightOverlayKey);
drawContent.PutOrRemoveTextList(OcrFlowOverlayKey, null);
drawContent.PutOrRemoveTextList(OcrFightOverlayKey, null);
RefreshMaskWindowForOverlay();
}
private async Task WaitOcrOverlayRenderTick()
@@ -2145,7 +2324,7 @@ public class AutoLeyLineOutcropTask : ISoloTask
public int FragileResinTimes { get; set; }
}
private sealed class OcrOverlayScope(DrawContent drawContent, string key) : IDisposable
private sealed class OcrOverlayScope(DrawContent drawContent, string key, Action refreshAction) : IDisposable
{
private bool _disposed;
@@ -2159,6 +2338,7 @@ public class AutoLeyLineOutcropTask : ISoloTask
_disposed = true;
drawContent.RemoveRect(key);
drawContent.PutOrRemoveTextList(key, null);
refreshAction();
}
}

View File

@@ -2553,6 +2553,61 @@
Text="{Binding Config.AutoLeyLineOutcropConfig.FightConfig.ActionSchedulerByCd, Mode=TwoWay}"
TextWrapping="Wrap" />
</Grid>
<Separator Margin="-18,0" BorderThickness="0,1,0,0" />
<Grid Margin="16,16,16,13">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<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="0"
Grid.Column="0"
Margin="90,0,5,0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
FontSize="10"
FontTypography="Body"
VerticalAlignment="Bottom"
Text="(二次拾取:琴首次拾取空,再次拾取。万叶即使刚使用过技能,也会强制再次拾取。)"
TextWrapping="Wrap" />
<ui:TextBlock Grid.Row="1"
Grid.Column="0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
Text="战斗结束后,如存在(万叶/琴)则执行长E聚集材料动作"
TextWrapping="Wrap" />
<ui:ToggleSwitch Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,10,0"
IsChecked="{Binding Config.AutoLeyLineOutcropConfig.FightConfig.KazuhaPickupEnabled, Mode=TwoWay}" />
<ui:ToggleSwitch Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="2"
Margin="0,0,38,0"
IsEnabled="{Binding Config.AutoLeyLineOutcropConfig.FightConfig.KazuhaPickupEnabled, Mode=TwoWay}"
IsChecked="{Binding Config.AutoLeyLineOutcropConfig.FightConfig.QinDoublePickUp, Mode=TwoWay}" />
<ui:TextBlock Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="8"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
Margin="0,0,38,26"
FontTypography="Body"
Text="二次拾取"
TextWrapping="Wrap" />
</Grid>
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />