mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-05-21 09:45:48 +08:00
* 修改万叶的模拟战技与普攻输入操作 将高层的技能释放 / 普通攻击函数调用,替换为明确的模拟输入时序流程,以提升运行稳定性。 改动内容: AutoFightTask(自动战斗任务) 元素战技采用鼠标 / 按键按下 + 松开时序模拟(长按后松开);将原有三次普通攻击调用,改为6 次鼠标左键按下 / 松开循环,并优化了间隔延时。 AutoLeyLineOutcropTask(自动地脉之花任务) 对万叶长按元素战技(长 E)做同款重构:模拟按键按下 / 松开动作,新增对元素战技冷却区域的视觉 / OCR 识别校验以确认技能已释放;截取冷却区域数据并调用技能后置回调函数,同时沿用 6 次普攻循环;补充了所需的资源引用命名空间。 PickUpCollectHandler(拾取收集处理器) 将长 E 预设等待时长从 1.0 秒 调整为 0.8 秒。 改动说明 本次优化调校了各操作时序,并新增视觉校验机制,减少拾取、技能连招过程中技能 / 普攻输入失效、漏触发的问题。 * Update BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * 重构万叶战后长E拾取逻辑:提取公共输入时序、增强防卡键保护及OCR释放校验 - 提取公共输入时序方法 ( TaskControl.cs ) - 新增 SimulateHoldActionAsync 、 SimulateHoldElementalSkillAsync :封装了包含前摇处理、精准延时按压和后摇缓冲的长按逻辑。 - 新增 SimulateMouseLeftClickLoopAsync :封装了左键连续点击循环。 - 核心安全提升 :在上述所有涉及 KeyDown/LeftButtonDown 的方法中,全面引入了双层 try/finally 块,确保在任何异常或手动停止任务的情况下,必然触发 KeyUp/LeftButtonUp 。 - 重构自动战斗拾取 ( AutoFightTask.cs ) - 移除 picker.UseSkill(true) ,接入新的公共方法,将长 E 持续时间精准设定为 800ms 。 - 重构地脉拾取并增加状态双重校验 ( AutoLeyLineOutcropTask.cs ) - 接入公共方法,将长 E 持续时间设定为 1000ms 。 - 新增校验拦截 :在松开 E 键后,截取当前画面,通过 HSV过滤 + PaddleOCR 读取技能 CD 数字,结合 Bv.IsSkillReady 进行双重验证。若未读取到 CD 且图标依然高亮(技能未成功释放),则提前 return 跳过后续左键下落攻击动作,并 阻断 AfterUseSkill 的调用,避免污染 CD 池。 * Update TaskControl.cs --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
239 lines
9.9 KiB
C#
239 lines
9.9 KiB
C#
using System;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using BetterGenshinImpact.GameTask.AutoPathing.Model;
|
||
using Microsoft.Extensions.Logging;
|
||
using static BetterGenshinImpact.GameTask.Common.TaskControl;
|
||
using BetterGenshinImpact.GameTask.AutoFight.Model;
|
||
using BetterGenshinImpact.GameTask.AutoFight.Script;
|
||
using System.Linq;
|
||
using System.Collections.Generic;
|
||
using BetterGenshinImpact.GameTask.AutoFight.Config;
|
||
|
||
namespace BetterGenshinImpact.GameTask.AutoPathing.Handler;
|
||
|
||
/// <summary>
|
||
/// 使用万叶或琴团通过战技吸取拾取物品,优先万叶,如果没有万叶则使用琴团
|
||
/// </summary>
|
||
public class PickUpCollectHandler : IActionHandler
|
||
{
|
||
/// <summary>
|
||
/// 新增命令时,以角色名称开头(必填),“-”后定义动作(必填,用于预解析,不能单写角色名称),空格后定义参数(必填)
|
||
/// 1、"action": "pick_up_collect","action_params":为空或不填,在队伍中寻找CharacterNames中第一个找到的角色,找到就会执行PickUpActions第一个找到的相关角色命令。
|
||
/// 2、"action": "pick_up_collect","action_params":"琴",只填角色名称(或别名),会执行PickUpActions第一个找到的相关角色命令。
|
||
/// 3、"action": "pick_up_collect","action_params":"琴-短E",填了具体角色和动作,直接找PickUpActions找到该命令执行。
|
||
/// </summary>
|
||
public static readonly string[] PickUpActions =
|
||
[
|
||
"枫原万叶-长E attack(0.08),keydown(E),wait(0.8),keyup(E),attack(0.5)",
|
||
"枫原万叶-短E attack(0.08),keydown(E),wait(0.47),keyup(E),attack(0.5)",
|
||
"琴-短E wait(0.1),keydown(E),wait(0.4),moveby(1000,0),wait(0.2),moveby(1000,0),wait(0.2),moveby(1000,0),wait(0.2),moveby(1000,-3500),wait(1.8),keyup(E),wait(0.3),click(middle)",
|
||
"琴-长E wait(0.1),click(middle),keydown(E),click(middle),wait(0.4),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1)," +
|
||
"moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1)," +
|
||
"moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1)," +
|
||
"moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1)," +
|
||
"moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1)," +
|
||
"moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1)," +
|
||
"moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1)," +
|
||
"moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1)," +
|
||
"moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),moveby(1000,3500),wait(1.8),keyup(E),wait(0.3),click(middle),wait(0.3)",
|
||
];
|
||
|
||
// 预解析所有角色名
|
||
private static readonly HashSet<string> CharacterNames = new HashSet<string>(
|
||
PickUpActions
|
||
.Select(action => action.Split(' ')[0])
|
||
.Select(GetBaseCharacterName)
|
||
.Distinct()
|
||
);
|
||
|
||
public async Task RunAsync(CancellationToken ct, WaypointForTrack? waypointForTrack = null, object? config = null)
|
||
{
|
||
Logger.LogInformation("简易策略:执行 {Nhd} 动作","聚集材料");
|
||
|
||
var combatScenes = await RunnerContext.Instance.GetCombatScenes(ct);
|
||
if (combatScenes == null)
|
||
{
|
||
Logger.LogError("队伍识别未初始化成功!");
|
||
return;
|
||
}
|
||
|
||
Avatar? picker = null;
|
||
var commandsList = new List<string>();
|
||
|
||
if (waypointForTrack != null)
|
||
{
|
||
if (!string.IsNullOrEmpty(waypointForTrack.ActionParams))
|
||
{
|
||
var commands = waypointForTrack.ActionParams.Split(',');
|
||
|
||
foreach (var command in commands)
|
||
{
|
||
|
||
try
|
||
{
|
||
var alias = DefaultAutoFightConfig.AvatarAliasToStandardName(command);
|
||
commandsList.Add(!string.IsNullOrEmpty(alias) ? alias : command);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
commandsList.Add(command);
|
||
Console.WriteLine(e);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 1、ActionParams没填参数,尝试选择,如果找到,后续会执行第一个找到该角色的相关命令
|
||
foreach (var characterName in CharacterNames)
|
||
{
|
||
var pickerNull = combatScenes.SelectAvatar(characterName);
|
||
if (pickerNull is null)
|
||
{
|
||
continue;
|
||
}
|
||
commandsList.Add(characterName);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
foreach (var commands in commandsList)
|
||
{
|
||
if (CharacterNames.Contains(commands))
|
||
{
|
||
picker = combatScenes.SelectAvatar(commands);
|
||
}
|
||
else
|
||
{
|
||
var characterName = GetCharacterName(commands);
|
||
picker = combatScenes.SelectAvatar(characterName);
|
||
}
|
||
|
||
if (picker is not null)
|
||
{
|
||
picker.TrySwitch();
|
||
await picker.WaitSkillCd(ct);
|
||
}
|
||
else
|
||
{
|
||
continue;
|
||
}
|
||
|
||
PickUpMaterial(combatScenes,commands); // 开始执行动作
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行聚集材料动作
|
||
/// <param name="combatScenes"></param>
|
||
/// <param name="pickerName"></param>
|
||
/// </summary>
|
||
private void PickUpMaterial(CombatScenes combatScenes, string? pickerName = null)
|
||
{
|
||
try
|
||
{
|
||
var foundAvatar = false;
|
||
string[] actionsToUse;
|
||
var characterName = string.Empty;
|
||
|
||
if (pickerName != null)
|
||
{
|
||
actionsToUse = PickUpActions.Where(action =>
|
||
action.StartsWith(pickerName + " ", StringComparison.OrdinalIgnoreCase)).ToArray();
|
||
|
||
if (actionsToUse.Length == 0)
|
||
{
|
||
if (CharacterNames.Contains(pickerName)) //2.只填了角色名,则用基础角色名筛选,执行pickerName相关的第一个命令
|
||
{
|
||
var actions = PickUpActions.FirstOrDefault(action => action.StartsWith(pickerName, StringComparison.OrdinalIgnoreCase));
|
||
actionsToUse = actions == null ? new string[0] : new string[] {actions};
|
||
|
||
// 替换第一个空格前的字符为 pickerName
|
||
if (actionsToUse.Length > 0 && actionsToUse[0].Contains(' '))
|
||
{
|
||
string action = actionsToUse[0];
|
||
int firstSpaceIndex = action.IndexOf(' ');
|
||
actionsToUse[0] = pickerName + action.Substring(firstSpaceIndex);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Logger.LogError($"未找到角色 {pickerName} 对应的动作");
|
||
return;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 提取角色名称
|
||
characterName = GetCharacterName(pickerName);
|
||
|
||
// 3.填了具体命令,则用具体命令筛选,并将命令中的角色替换为角色名称
|
||
actionsToUse = actionsToUse
|
||
.Select(action => action.Replace(pickerName + " ", characterName + " ", StringComparison.OrdinalIgnoreCase))
|
||
.ToArray();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Logger.LogError("未找到ActionParams");
|
||
return;
|
||
}
|
||
|
||
foreach (var pickUpActionStr in actionsToUse)
|
||
{
|
||
var pickUpAction = CombatScriptParser.ParseContext(pickUpActionStr);
|
||
foreach (var command in pickUpAction.CombatCommands)
|
||
{
|
||
var avatar = combatScenes.SelectAvatar(command.Name);
|
||
if (avatar != null)
|
||
{
|
||
command.Execute(combatScenes);
|
||
foundAvatar = true;
|
||
}
|
||
}
|
||
if (foundAvatar)
|
||
{
|
||
var selectedAvatar = combatScenes.SelectAvatar(characterName);
|
||
if (selectedAvatar is not null)
|
||
{
|
||
Sleep(200);//等待CD显示
|
||
selectedAvatar.AfterUseSkill();
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 处理异常
|
||
Console.WriteLine($"PickUpCollectHandler 异常: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 直接匹配预解析的角色名
|
||
private static string GetCharacterName(string pickerName)
|
||
{
|
||
foreach (var name in CharacterNames)
|
||
{
|
||
if (pickerName.StartsWith(name))
|
||
return name;
|
||
}
|
||
|
||
return pickerName;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从完整动作名提取基础角色名
|
||
/// </summary>
|
||
private static string GetBaseCharacterName(string fullActionName)
|
||
{
|
||
// 找到第一个"-"号的位置
|
||
var dashIndex = fullActionName.IndexOf('-');
|
||
|
||
// 如果存在"-"号,则返回"-"号前的部分
|
||
return dashIndex > 0 ? fullActionName.Substring(0, dashIndex) : string.Empty;
|
||
}
|
||
|
||
}
|