mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-05-25 10:05:49 +08:00
Merge branch 'main' into d-v3
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -29,3 +29,4 @@ node_modules/
|
||||
|
||||
# Rider
|
||||
.idea
|
||||
.trae
|
||||
@@ -92,9 +92,20 @@ public partial class PathingPartyConfig : ObservableObject
|
||||
[ObservableProperty]
|
||||
private bool _autoRunEnabled = true;
|
||||
|
||||
//在连续执行时是否隐藏
|
||||
[ObservableProperty]
|
||||
private bool _hideOnRepeat = false;
|
||||
|
||||
//执行周期配置
|
||||
[ObservableProperty]
|
||||
private PathingPartyTaskCycleConfig _taskCycleConfig = new();
|
||||
|
||||
//任务完成跳过执行配置
|
||||
[ObservableProperty]
|
||||
private TaskCompletionSkipRuleConfig _taskCompletionSkipRuleConfig = new();
|
||||
//优先执行其他配置组
|
||||
[ObservableProperty]
|
||||
private PreExecutionPriorityConfig _preExecutionPriorityConfig = new();
|
||||
|
||||
//启用自动战斗配置
|
||||
[ObservableProperty]
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterGenshinImpact.Core.Config;
|
||||
|
||||
|
||||
[Serializable]
|
||||
public partial class PreExecutionPriorityConfig: ObservableObject
|
||||
{
|
||||
|
||||
// 配置是否启用
|
||||
[ObservableProperty]
|
||||
private bool _enabled = false;
|
||||
/// <summary>
|
||||
/// 需要优先检查执行的配置组名称,多个用逗号分隔。
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private string _groupNames = "";
|
||||
|
||||
/// <summary>
|
||||
/// 最大重试次数,如果优先的任务执行失败了,重试几次。
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private int _maxRetryCount = 1;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterGenshinImpact.Core.Config;
|
||||
|
||||
public partial class TaskCompletionSkipRuleConfig:ObservableObject
|
||||
{
|
||||
//启用跳过完成任务
|
||||
[ObservableProperty]
|
||||
private bool _enable = false;
|
||||
|
||||
//跳过策略
|
||||
//GroupPhysicalPathSkipPolicy: 配置组且物理路径相同跳过
|
||||
//PhysicalPathSkipPolicy: 物理路径相同跳过
|
||||
//SameNameSkipPolicy: 同类型同名跳过
|
||||
[ObservableProperty]
|
||||
private string _skipPolicy = "GroupPhysicalPathSkipPolicy";
|
||||
|
||||
|
||||
//周期分界时间点,如果负数则不启用,主要适用于固定时间的刷新物品适用
|
||||
[ObservableProperty]
|
||||
private int _boundaryTime = 4;
|
||||
|
||||
//上一次执行间隔时间,出于精度考虑,这里使用秒为单位
|
||||
[ObservableProperty]
|
||||
private int _lastRunGapSeconds = -1;
|
||||
//间隔时间计算参照,开始时间(StartTime)和结束时间(EndTime)
|
||||
[ObservableProperty]
|
||||
private string _referencePoint = "EndTime";
|
||||
|
||||
}
|
||||
@@ -269,6 +269,14 @@ public class Genshin
|
||||
return false;//释放失败状态到JS,否则失败后会退出任务。
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除当前调度器的队伍缓存
|
||||
/// </summary>
|
||||
public void ClearPartyCache()
|
||||
{
|
||||
RunnerContext.Instance.ClearCombatScenes();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -18,6 +18,7 @@ using System.Threading.Tasks;
|
||||
using BetterGenshinImpact.GameTask.AutoPathing.Model.Enum;
|
||||
using BetterGenshinImpact.GameTask.Common;
|
||||
using BetterGenshinImpact.GameTask.FarmingPlan;
|
||||
using BetterGenshinImpact.GameTask.LogParse;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BetterGenshinImpact.Core.Script.Group;
|
||||
@@ -163,6 +164,16 @@ public partial class ScriptGroupProject : ObservableObject
|
||||
|
||||
public async Task Run()
|
||||
{
|
||||
//执行记录
|
||||
ExecutionRecord executionRecord = new ExecutionRecord()
|
||||
{
|
||||
StartTime = DateTime.Now,
|
||||
GroupName = GroupInfo?.Name ?? "",
|
||||
FolderName = FolderName,
|
||||
ProjectName = Name,
|
||||
Type = Type
|
||||
};
|
||||
ExecutionRecordStorage.SaveExecutionRecord(executionRecord);
|
||||
if (Type == "Javascript")
|
||||
{
|
||||
if (Project == null)
|
||||
@@ -197,7 +208,7 @@ public partial class ScriptGroupProject : ObservableObject
|
||||
await pathingTask.Pathing(task);
|
||||
|
||||
|
||||
|
||||
executionRecord.IsSuccessful = pathingTask.SuccessEnd;
|
||||
OtherConfig.AutoRestart autoRestart = TaskContext.Instance().Config.OtherConfig.AutoRestartConfig;
|
||||
if (!pathingTask.SuccessEnd)
|
||||
{
|
||||
@@ -212,7 +223,7 @@ public partial class ScriptGroupProject : ObservableObject
|
||||
{
|
||||
var successFight = pathingTask.SuccessEnd;
|
||||
var fightCount = 0;
|
||||
|
||||
|
||||
//未走完完整路径下,才校验打架次数
|
||||
if (!successFight)
|
||||
{
|
||||
@@ -247,7 +258,7 @@ public partial class ScriptGroupProject : ObservableObject
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -263,6 +274,14 @@ public partial class ScriptGroupProject : ObservableObject
|
||||
var task = new ShellTask(ShellTaskParam.BuildFromConfig(Name, shellConfig ?? new ShellConfig()));
|
||||
await task.Start(CancellationContext.Instance.Cts.Token);
|
||||
}
|
||||
|
||||
if (Type != "Pathing")
|
||||
{
|
||||
executionRecord.IsSuccessful = true;
|
||||
}
|
||||
|
||||
executionRecord.EndTime = DateTime.Now;
|
||||
ExecutionRecordStorage.SaveExecutionRecord(executionRecord);
|
||||
}
|
||||
|
||||
partial void OnTypeChanged(string value)
|
||||
|
||||
@@ -570,9 +570,39 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
//顶层目录订阅时,不会删除其下,不在订阅中的文件夹
|
||||
List<string> newPaths = new List<string>();
|
||||
foreach (var path in paths)
|
||||
{
|
||||
//顶层节点,按库中的文件夹来
|
||||
if (path == "pathing")
|
||||
{
|
||||
var scriptPath = Path.Combine(repoPath, path);
|
||||
if (Directory.Exists(scriptPath))
|
||||
{
|
||||
// 获取该路径下的所有“仅第一层文件夹”
|
||||
string[] directories = Directory.GetDirectories(scriptPath, "*", SearchOption.TopDirectoryOnly);
|
||||
foreach (var dir in directories)
|
||||
{
|
||||
newPaths.Add("pathing"+"/"+Path.GetFileName(dir));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Toast.Warning($"未知的脚本路径:{path}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newPaths.Add(path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 拷贝文件
|
||||
foreach (var path in paths)
|
||||
foreach (var path in newPaths)
|
||||
{
|
||||
var (first, remainingPath) = GetFirstFolderAndRemainingPath(path);
|
||||
if (PathMapper.TryGetValue(first, out var userPath))
|
||||
|
||||
@@ -191,7 +191,27 @@ public class AutoFightTask : ISoloTask
|
||||
|
||||
_finishDetectConfig = new TaskFightFinishDetectConfig(_taskParam.FinishDetectConfig);
|
||||
}
|
||||
public CombatScenes GetCombatScenesWithRetry()
|
||||
{
|
||||
const int maxRetries = 5;
|
||||
var retryDelayMs = 1000; // 可选:重试间隔,单位毫秒
|
||||
|
||||
for (int attempt = 1; attempt <= maxRetries; attempt++)
|
||||
{
|
||||
var combatScenes = new CombatScenes().InitializeTeam(CaptureToRectArea());
|
||||
if (combatScenes.CheckTeamInitialized())
|
||||
{
|
||||
return combatScenes;
|
||||
}
|
||||
|
||||
if (attempt < maxRetries)
|
||||
{
|
||||
Thread.Sleep(retryDelayMs); // 可选:延迟再试
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("识别队伍角色失败(已重试 5 次)");
|
||||
}
|
||||
// 方法1:判断是否是单个数字
|
||||
|
||||
/*public int delayTime=1500;
|
||||
@@ -203,11 +223,12 @@ public class AutoFightTask : ISoloTask
|
||||
_ct = ct;
|
||||
|
||||
LogScreenResolution();
|
||||
var combatScenes = new CombatScenes().InitializeTeam(CaptureToRectArea());
|
||||
var combatScenes = GetCombatScenesWithRetry();
|
||||
/*var combatScenes = new CombatScenes().InitializeTeam(CaptureToRectArea());
|
||||
if (!combatScenes.CheckTeamInitialized())
|
||||
{
|
||||
throw new Exception("识别队伍角色失败");
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
// var actionSchedulerByCd = ParseStringToDictionary(_taskParam.ActionSchedulerByCd);
|
||||
|
||||
@@ -320,7 +320,8 @@ public class TpTask(CancellationToken ct)
|
||||
Logger.LogInformation("传送完成,返回主界面");
|
||||
return;
|
||||
}
|
||||
|
||||
//增加容错,小概率情况下碰到,前面点击传送失败
|
||||
capture.Find(_assets.TeleportButtonRo,rg=>rg.Click());
|
||||
await Delay(delayMs, ct);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.LogParse;
|
||||
|
||||
public class DailyExecutionRecord
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }=string.Empty;
|
||||
[JsonProperty("execution_records")]
|
||||
public List<ExecutionRecord> ExecutionRecords { get; set; } = new();
|
||||
}
|
||||
31
BetterGenshinImpact/GameTask/LogParse/ExecutionRecord.cs
Normal file
31
BetterGenshinImpact/GameTask/LogParse/ExecutionRecord.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.LogParse;
|
||||
|
||||
public class ExecutionRecord
|
||||
{
|
||||
[JsonProperty("guid")] public Guid Id { get; set; } = Guid.NewGuid();
|
||||
[JsonProperty("group_name")] public string GroupName { get; set; } = string.Empty;
|
||||
[JsonProperty("project_name")] public string ProjectName { get; set; } = string.Empty;
|
||||
[JsonProperty("folder_name")] public string FolderName { get; set; } = string.Empty;
|
||||
[JsonProperty("type")] public string Type = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 执行开始时间
|
||||
/// </summary>
|
||||
[JsonProperty("start_time")]
|
||||
public DateTime StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 执行结束时间
|
||||
/// </summary>
|
||||
[JsonProperty("end_time")]
|
||||
public DateTime EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否执行成功
|
||||
/// </summary>
|
||||
[JsonProperty("is_successful")]
|
||||
public bool IsSuccessful { get; set; } = false;
|
||||
}
|
||||
303
BetterGenshinImpact/GameTask/LogParse/ExecutionRecordStorage.cs
Normal file
303
BetterGenshinImpact/GameTask/LogParse/ExecutionRecordStorage.cs
Normal file
@@ -0,0 +1,303 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using BetterGenshinImpact.Core.Config;
|
||||
using BetterGenshinImpact.Core.Script.Group;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.LogParse;
|
||||
|
||||
public class ExecutionRecordStorage
|
||||
{
|
||||
private static readonly string StorageDirectory = Path.Combine(Global.Absolute(@"log"), "ExecutionRecords");
|
||||
|
||||
/// <summary>
|
||||
/// 保存执行记录到对应日期的文件中
|
||||
/// </summary>
|
||||
public static void SaveExecutionRecord(ExecutionRecord record)
|
||||
{
|
||||
// 创建存储目录
|
||||
Directory.CreateDirectory(StorageDirectory);
|
||||
|
||||
// 获取基于StartTime的日期文件名
|
||||
string dateKey = record.StartTime.ToString("yyyyMMdd");
|
||||
string fileName = $"{dateKey}.json";
|
||||
string filePath = Path.Combine(StorageDirectory, fileName);
|
||||
|
||||
// 读取或创建当天的记录
|
||||
DailyExecutionRecord dailyRecord;
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
string json = File.ReadAllText(filePath);
|
||||
dailyRecord = JsonConvert.DeserializeObject<DailyExecutionRecord>(json);
|
||||
}
|
||||
else
|
||||
{
|
||||
dailyRecord = new DailyExecutionRecord
|
||||
{
|
||||
Name = dateKey
|
||||
};
|
||||
}
|
||||
|
||||
// 更新或添加记录
|
||||
var existingIndex = dailyRecord.ExecutionRecords.FindIndex(r => r.Id == record.Id);
|
||||
if (existingIndex >= 0)
|
||||
{
|
||||
dailyRecord.ExecutionRecords[existingIndex] = record;
|
||||
}
|
||||
else
|
||||
{
|
||||
dailyRecord.ExecutionRecords.Add(record);
|
||||
}
|
||||
|
||||
// 保存更新后的文件
|
||||
string updatedJson = JsonConvert.SerializeObject(dailyRecord, Formatting.Indented);
|
||||
File.WriteAllText(filePath, updatedJson);
|
||||
}
|
||||
|
||||
public static List<DailyExecutionRecord> GetRecentExecutionRecordsByConfig(TaskCompletionSkipRuleConfig config)
|
||||
{
|
||||
|
||||
// 确定边界时间是否有效(0-23之间)
|
||||
bool boundaryTimeEnable = config.BoundaryTime >= 0 && config.BoundaryTime <= 23;
|
||||
|
||||
// 默认获取最近1天的执行记录
|
||||
int dayCount = 1;
|
||||
|
||||
// 如果边界时间有效,需要获取2天的记录(可能跨越边界)
|
||||
if (boundaryTimeEnable)
|
||||
{
|
||||
dayCount = 2;
|
||||
}
|
||||
|
||||
// 如果配置了有效的间隔秒数,根据秒数计算需要获取的天数(向上取整)
|
||||
if (config.LastRunGapSeconds >= 0)
|
||||
{
|
||||
dayCount = ConvertSecondsToDaysUp(config.LastRunGapSeconds);
|
||||
}
|
||||
|
||||
return GetRecentExecutionRecords(dayCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取最近N天的执行记录
|
||||
/// </summary>
|
||||
public static List<DailyExecutionRecord> GetRecentExecutionRecords(int days)
|
||||
{
|
||||
if (days <= 0)
|
||||
throw new ArgumentException("Days must be a positive integer", nameof(days));
|
||||
|
||||
var results = new List<DailyExecutionRecord>();
|
||||
var storageDir = new DirectoryInfo(StorageDirectory);
|
||||
|
||||
// 确保目录存在
|
||||
if (!storageDir.Exists) return results;
|
||||
|
||||
// 计算日期范围
|
||||
var endDate = DateTime.Today;
|
||||
var startDate = endDate.AddDays(-days + 1);
|
||||
|
||||
// 遍历日期范围
|
||||
for (var date = startDate; date <= endDate; date = date.AddDays(1))
|
||||
{
|
||||
string fileName = $"{date:yyyyMMdd}.json";
|
||||
string filePath = Path.Combine(StorageDirectory, fileName);
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
string json = File.ReadAllText(filePath);
|
||||
var record = JsonConvert.DeserializeObject<DailyExecutionRecord>(json);
|
||||
results.Add(record);
|
||||
}
|
||||
}
|
||||
|
||||
//实际使用中,使用倒序,反转记录列表,变为倒序
|
||||
results.Reverse();
|
||||
foreach (var dailyRecord in results)
|
||||
{
|
||||
var records = dailyRecord.ExecutionRecords;
|
||||
// 反转执行记录,变为倒序
|
||||
records.Reverse();
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将秒数转换为天数(向上取整)
|
||||
/// </summary>
|
||||
/// <param name="seconds">总秒数</param>
|
||||
/// <returns>向上取整后的天数</returns>
|
||||
private static int ConvertSecondsToDaysUp(int seconds)
|
||||
{
|
||||
if (seconds <= 0) return 0;
|
||||
|
||||
const int secondsPerDay = 86400; // 24 * 60 * 60
|
||||
double days = (double)seconds / secondsPerDay;
|
||||
return (int)Math.Ceiling(days);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据自定义的一天开始时间判断日期是否属于"今天"
|
||||
/// </summary>
|
||||
/// <param name="boundaryHour">分界时间(小时),0-23之间</param>
|
||||
/// <param name="targetDate">要判断的日期</param>
|
||||
/// <returns>如果属于"今天"则返回true,否则返回false</returns>
|
||||
private static bool IsTodayByBoundary(int boundaryHour, DateTime targetDate)
|
||||
{
|
||||
// 验证分界时间是否有效
|
||||
if (boundaryHour < 0 || boundaryHour > 23)
|
||||
throw new ArgumentOutOfRangeException(nameof(boundaryHour), "分界时间必须在0-23之间");
|
||||
|
||||
DateTime now = DateTime.Now;
|
||||
|
||||
// 计算今天的开始时间(根据分界时间)
|
||||
DateTime todayStart;
|
||||
if (now.Hour >= boundaryHour)
|
||||
{
|
||||
// 今天已经过了分界时间,今天的开始是今天的分界时间
|
||||
todayStart = new DateTime(now.Year, now.Month, now.Day, boundaryHour, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 今天还没过分界时间,今天的开始是昨天的分界时间
|
||||
todayStart = new DateTime(now.Year, now.Month, now.Day, boundaryHour, 0, 0).AddDays(-1);
|
||||
}
|
||||
|
||||
// 计算今天的结束时间(明天的开始时间)
|
||||
DateTime todayEnd = todayStart.AddDays(1);
|
||||
|
||||
// 判断目标日期是否在今天的范围内
|
||||
return targetDate >= todayStart && targetDate < todayEnd;
|
||||
}
|
||||
|
||||
public static bool IsSkipTask(ScriptGroupProject project, out string message,List<DailyExecutionRecord>? dailyRecords=null)
|
||||
{
|
||||
// 初始化消息字符串
|
||||
message = "";
|
||||
|
||||
// 获取任务完成跳过规则配置
|
||||
var config = project.GroupInfo?.Config?.PathingConfig.TaskCompletionSkipRuleConfig;
|
||||
|
||||
// 检查配置是否有效:配置不存在、未启用、边界时间无效且间隔时间无效
|
||||
if (config == null ||
|
||||
!config.Enable ||
|
||||
(config.BoundaryTime < 0 || config.BoundaryTime > 23) && config.LastRunGapSeconds < 0)
|
||||
{
|
||||
return false; // 配置无效,不执行跳过检查
|
||||
}
|
||||
|
||||
// 确定边界时间是否有效(0-23之间)
|
||||
bool boundaryTimeEnable = config.BoundaryTime >= 0 && config.BoundaryTime <= 23;
|
||||
|
||||
// 获取项目相关信息
|
||||
var groupName = project.GroupInfo?.Name ?? "";
|
||||
var folderName = project.FolderName;
|
||||
var projectName = project.Name;
|
||||
var projectType = project.Type;
|
||||
|
||||
// 获取最近指定天数的执行记录
|
||||
dailyRecords ??= GetRecentExecutionRecordsByConfig(config);
|
||||
|
||||
|
||||
// 遍历每日记录
|
||||
foreach (var dailyRecord in dailyRecords)
|
||||
{
|
||||
var records = dailyRecord.ExecutionRecords;
|
||||
|
||||
// 反转执行记录,变为倒序
|
||||
// records.Reverse();
|
||||
|
||||
// 遍历每条执行记录
|
||||
foreach (var record in records)
|
||||
{
|
||||
|
||||
// 跳过未成功的执行记录
|
||||
if (!record.IsSuccessful) continue;
|
||||
|
||||
// 跳过类型或项目名称不匹配的记录
|
||||
if (record.Type != projectType || record.ProjectName != projectName) continue;
|
||||
|
||||
var calcTime = record.EndTime;
|
||||
if (config.ReferencePoint == "StartTime")
|
||||
{
|
||||
calcTime = record.StartTime;
|
||||
}
|
||||
|
||||
// 如果配置了间隔时间,检查记录是否在时间间隔内
|
||||
if (config.LastRunGapSeconds >= 0)
|
||||
{
|
||||
double secondsSinceLastRun = (DateTime.Now - calcTime).TotalSeconds;
|
||||
|
||||
// 跳过超过配置间隔时间的记录
|
||||
if (secondsSinceLastRun > config.LastRunGapSeconds) continue;
|
||||
}
|
||||
|
||||
// 检查记录是否在"今天"(根据边界时间定义)
|
||||
if (boundaryTimeEnable)
|
||||
{
|
||||
// 如果记录不在"今天",则跳过
|
||||
if (!IsTodayByBoundary(config.BoundaryTime, record.StartTime)) continue;
|
||||
}
|
||||
|
||||
bool isMatchFound = false;
|
||||
string matchReason = "";
|
||||
|
||||
// 检查匹配策略
|
||||
if (config.SkipPolicy == "GroupPhysicalPathSkipPolicy" &&
|
||||
groupName == record.GroupName &&
|
||||
folderName == record.FolderName)
|
||||
{
|
||||
// 组和物理路径匹配策略
|
||||
matchReason = "组和物理路径匹配一致";
|
||||
isMatchFound = true;
|
||||
}
|
||||
else if (config.SkipPolicy == "PhysicalPathSkipPolicy" &&
|
||||
folderName == record.FolderName)
|
||||
{
|
||||
// 物理路径匹配策略
|
||||
matchReason = "物理路径相同";
|
||||
isMatchFound = true;
|
||||
}
|
||||
else if (config.SkipPolicy == "SameNameSkipPolicy")
|
||||
{
|
||||
// 名称匹配策略(只需要项目名称相同)
|
||||
matchReason = "名称相同";
|
||||
isMatchFound = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 未知的跳过策略
|
||||
Console.WriteLine("ExecutionRecordStorage: 未预期的跳过策略!");
|
||||
continue; // 继续检查下一条记录
|
||||
}
|
||||
|
||||
if (isMatchFound)
|
||||
{
|
||||
// 构建匹配消息
|
||||
message = $"检查出满足跳过条件: {matchReason}";
|
||||
|
||||
// 添加时间相关信息
|
||||
if (config.LastRunGapSeconds >= 0)
|
||||
{
|
||||
// 计算下次可执行时间
|
||||
DateTime nextExecutionTime = calcTime.AddSeconds(config.LastRunGapSeconds);
|
||||
message += $", 需在 {nextExecutionTime:yyyy-M-d H:mm:ss} 之后才能开始执行";
|
||||
}
|
||||
else if (boundaryTimeEnable)
|
||||
{
|
||||
message += $", 需在下一日 {config.BoundaryTime} 点后才能开始执行";
|
||||
}
|
||||
|
||||
// 添加匹配记录的ID
|
||||
message += $", 匹配记录 GUID={record.Id}";
|
||||
|
||||
return true; // 找到匹配记录,返回true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 未找到匹配记录
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using BetterGenshinImpact.Core.Recognition;
|
||||
using BetterGenshinImpact.GameTask.Model;
|
||||
using BetterGenshinImpact.Helpers;
|
||||
using OpenCvSharp;
|
||||
using System.Drawing;
|
||||
using Vanara.PInvoke;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.QucikBuy.Assets;
|
||||
|
||||
public class QuickBuyAssets : BaseAssets<QuickBuyAssets>
|
||||
{
|
||||
private readonly ILogger<QuickBuyAssets> _logger = App.GetLogger<QuickBuyAssets>();
|
||||
|
||||
public RecognitionObject SereniteaPotCoin;
|
||||
|
||||
private QuickBuyAssets()
|
||||
{
|
||||
SereniteaPotCoin = new RecognitionObject
|
||||
{
|
||||
Name = "SereniteaPotCoin",
|
||||
RecognitionType = RecognitionTypes.TemplateMatch,
|
||||
TemplateImageMat = GameTaskManager.LoadAssetImage("QucikBuy", "SereniteaPotCoin.png"),
|
||||
RegionOfInterest = new Rect((int)(1630 * AssetScale),(int)(30 * AssetScale),(int)(200 * AssetScale),(int)(40 * AssetScale)),
|
||||
DrawOnWindow = false
|
||||
}.InitTemplate();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using BetterGenshinImpact.Core.Simulator;
|
||||
using BetterGenshinImpact.GameTask.AutoPick.Assets;
|
||||
using BetterGenshinImpact.GameTask.Common;
|
||||
using BetterGenshinImpact.GameTask.Model.Area;
|
||||
using BetterGenshinImpact.GameTask.QucikBuy.Assets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using Wpf.Ui.Violeta.Controls;
|
||||
@@ -23,6 +25,30 @@ public class QuickBuyTask
|
||||
|
||||
try
|
||||
{
|
||||
ImageRegion ra = TaskControl.CaptureToRectArea();
|
||||
if (ra.Find(QuickBuyAssets.Instance.SereniteaPotCoin).IsExist())
|
||||
{
|
||||
// 尘歌壶购买逻辑
|
||||
GameCaptureRegion.GameRegionClick((size, scale) => (200 * scale, 200 * scale));
|
||||
TaskControl.CheckAndSleep(100);
|
||||
// 选中左边点
|
||||
GameCaptureRegion.GameRegion1080PPosMove(1450, 690);
|
||||
TaskControl.CheckAndSleep(100);
|
||||
Simulation.SendInput.Mouse.LeftButtonDown();
|
||||
TaskControl.CheckAndSleep(50);
|
||||
|
||||
// 向右滑动
|
||||
Simulation.SendInput.Mouse.MoveMouseBy(1000, 0);
|
||||
TaskControl.CheckAndSleep(200);
|
||||
Simulation.SendInput.Mouse.LeftButtonUp();
|
||||
TaskControl.CheckAndSleep(100);
|
||||
|
||||
GameCaptureRegion.GameRegion1080PPosClick(1600, 1020);
|
||||
TaskControl.CheckAndSleep(200); // 等待窗口消失
|
||||
GameCaptureRegion.GameRegion1080PPosClick(960, 850);
|
||||
|
||||
return;
|
||||
}
|
||||
// 点击购买/兑换 右下225x60
|
||||
GameCaptureRegion.GameRegionClick((size, scale) => (size.Width - 225 * scale, size.Height - 60 * scale));
|
||||
TaskControl.CheckAndSleep(100); // 等待窗口弹出
|
||||
|
||||
@@ -27,7 +27,11 @@ public class RunnerContext : Singleton<RunnerContext>
|
||||
/// 暂停逻辑
|
||||
/// </summary>
|
||||
public bool IsSuspend { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 优先执行配置组
|
||||
/// </summary>
|
||||
public bool IsPreExecution { get; set; } = false;
|
||||
/// <summary>
|
||||
/// 暂停实现
|
||||
/// </summary>
|
||||
@@ -113,6 +117,7 @@ public class RunnerContext : Singleton<RunnerContext>
|
||||
SuspendableDictionary.Clear();
|
||||
AutoPickTriggerStopCount = 0;
|
||||
taskProgress = null;
|
||||
IsPreExecution = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -17,6 +17,7 @@ using BetterGenshinImpact.GameTask.Common;
|
||||
using BetterGenshinImpact.GameTask.Common.BgiVision;
|
||||
using BetterGenshinImpact.GameTask.Common.Job;
|
||||
using BetterGenshinImpact.GameTask.FarmingPlan;
|
||||
using BetterGenshinImpact.GameTask.LogParse;
|
||||
using BetterGenshinImpact.GameTask.TaskProgress;
|
||||
using BetterGenshinImpact.Service.Interface;
|
||||
using BetterGenshinImpact.Service.Notification;
|
||||
@@ -100,11 +101,21 @@ public partial class ScriptService : IScriptService
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
string skipMessage;
|
||||
if (ExecutionRecordStorage.IsSkipTask(project,out skipMessage))
|
||||
{
|
||||
TaskControl.Logger.LogInformation($"{project.Name}:{skipMessage},跳过此任务!");
|
||||
return true;
|
||||
}
|
||||
return false; // 不跳过
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//优先执行的配置组,统计每个project执行次数
|
||||
private readonly Dictionary<string, int> _projectExecutionCount = new();
|
||||
|
||||
public async Task RunMulti(IEnumerable<ScriptGroupProject> projectList, string? groupName = null,TaskProgress? taskProgress = null)
|
||||
{
|
||||
groupName ??= "默认";
|
||||
@@ -116,7 +127,8 @@ public partial class ScriptService : IScriptService
|
||||
{
|
||||
scriptGroupProject.SkipFlag = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// // 针对JS 脚本,检查是否包含定时器操作
|
||||
// var jsProjects = ExtractJsProjects(list);
|
||||
@@ -128,8 +140,9 @@ public partial class ScriptService : IScriptService
|
||||
|
||||
// 没启动时候,启动截图器
|
||||
await StartGameTask();
|
||||
|
||||
if (!string.IsNullOrEmpty(groupName))
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(groupName)&&!RunnerContext.Instance.IsPreExecution)
|
||||
{
|
||||
// if (hasTimer)
|
||||
// {
|
||||
@@ -143,167 +156,278 @@ public partial class ScriptService : IScriptService
|
||||
|
||||
|
||||
bool fisrt = true;
|
||||
|
||||
|
||||
//非优先执行配置下,清空执行计数
|
||||
if (!RunnerContext.Instance.IsPreExecution)
|
||||
{
|
||||
_projectExecutionCount.Clear();
|
||||
}
|
||||
|
||||
|
||||
await new TaskRunner()
|
||||
.RunThreadAsync(async () =>
|
||||
{
|
||||
var stopwatch = new Stopwatch();
|
||||
int projectIndex = -1;
|
||||
foreach (var project in list)
|
||||
for (int x = 0; x < list.Count; x++)
|
||||
{
|
||||
projectIndex++;
|
||||
if (taskProgress != null && taskProgress.Next != null)
|
||||
var project = list[x];
|
||||
//正常情况下,只有一个真正执行的project,存在其他优先执行配置组情况下,会有多个任务。
|
||||
List<ScriptGroupProject> exeProjects = [project];
|
||||
RunnerContext.Instance.IsPreExecution = false;
|
||||
//优先执行配置组逻辑
|
||||
if (!RunnerContext.Instance.IsPreExecution &&
|
||||
(project.GroupInfo?.Config.PathingConfig.Enabled ?? false) &&
|
||||
project.GroupInfo.Config.PathingConfig.PreExecutionPriorityConfig.Enabled)
|
||||
{
|
||||
if (taskProgress.Next.Index>projectIndex)
|
||||
var preConfig = project.GroupInfo.Config.PathingConfig.PreExecutionPriorityConfig;
|
||||
var groupNames = preConfig.GroupNames;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(groupNames))
|
||||
{
|
||||
continue;
|
||||
// 解析组名集合
|
||||
var groupNameSet = groupNames
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(name => name.Trim())
|
||||
.Where(name => !string.IsNullOrWhiteSpace(name));
|
||||
|
||||
// 获取匹配的脚本组
|
||||
var scriptGroups = App.GetService<ScriptControlViewModel>().ScriptGroups
|
||||
.Where(g => groupNameSet.Contains(g.Name, StringComparer.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
// 收集需要执行的项目
|
||||
var preExecutionProjects = new List<ScriptGroupProject>();
|
||||
foreach (var group in scriptGroups)
|
||||
{
|
||||
var skipConfig = group.Config.PathingConfig.TaskCompletionSkipRuleConfig;
|
||||
var records = ExecutionRecordStorage.GetRecentExecutionRecordsByConfig(skipConfig);
|
||||
|
||||
foreach (var p in group.Projects)
|
||||
{
|
||||
// 检查是否应该跳过任务
|
||||
if (ExecutionRecordStorage.IsSkipTask(p, out _, records))
|
||||
continue;
|
||||
|
||||
// 生成项目唯一标识
|
||||
var projectKey = $"{p.Name}|{p.FolderName}|{p.GroupInfo?.Name}";
|
||||
|
||||
// 检查执行次数
|
||||
if (!_projectExecutionCount.TryGetValue(projectKey, out var count))
|
||||
{
|
||||
count = 0;
|
||||
}
|
||||
|
||||
// 检查是否超过最大重试次数
|
||||
if (count > preConfig.MaxRetryCount)
|
||||
continue;
|
||||
|
||||
//增加执行计数
|
||||
//_projectExecutionCount[projectKey] = count + 1;
|
||||
preExecutionProjects.Add(p);
|
||||
}
|
||||
}
|
||||
|
||||
// 存在优先执行的项目,则优先执行
|
||||
if (preExecutionProjects.Count > 0)
|
||||
{
|
||||
|
||||
_logger.LogInformation($"存在{preExecutionProjects.Count}个需优先执行的任务!");
|
||||
// 设置执行状态,进入优先执行任务
|
||||
RunnerContext.Instance.IsPreExecution = true;
|
||||
//重新构造需要执行的配置组
|
||||
exeProjects = preExecutionProjects.Concat(new[] { project }).ToList();
|
||||
}
|
||||
}
|
||||
taskProgress.Next = null;
|
||||
}
|
||||
|
||||
if (project is {SkipFlag:true})
|
||||
if (!RunnerContext.Instance.IsPreExecution)
|
||||
{
|
||||
continue;
|
||||
projectIndex++;
|
||||
}
|
||||
if (ShouldSkipTask(project))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//月卡检测
|
||||
await _blessingOfTheWelkinMoonTask.Start(CancellationContext.Instance.Cts.Token);
|
||||
if (project.Status != "Enabled")
|
||||
{
|
||||
_logger.LogInformation("脚本 {Name} 状态为禁用,跳过执行", project.Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (CancellationContext.Instance.Cts.IsCancellationRequested)
|
||||
{
|
||||
_logger.LogInformation("执行被取消");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (fisrt)
|
||||
{
|
||||
fisrt = false;
|
||||
Notify.Event(NotificationEvent.GroupStart).Success($"配置组{groupName}启动");
|
||||
}
|
||||
|
||||
if (taskProgress!=null)
|
||||
for (int y = 0; y < exeProjects.Count; y++)
|
||||
{
|
||||
taskProgress.CurrentScriptGroupProjectInfo = new TaskProgress.ScriptGroupProjectInfo
|
||||
var exeProject = exeProjects[y];
|
||||
//最后一个执行的project,恢复正常执行状态
|
||||
if (y == exeProjects.Count - 1)
|
||||
{
|
||||
Name = project.Name,
|
||||
FolderName = project.FolderName
|
||||
,Index = projectIndex
|
||||
,GroupName = taskProgress?.CurrentScriptGroupName ?? ""
|
||||
};
|
||||
TaskProgressManager.SaveTaskProgress(taskProgress);
|
||||
}
|
||||
for (var i = 0; i < project.RunNum; i++)
|
||||
{
|
||||
try
|
||||
RunnerContext.Instance.IsPreExecution = false;
|
||||
}
|
||||
if (!RunnerContext.Instance.IsPreExecution && taskProgress != null && taskProgress.Next != null)
|
||||
{
|
||||
TaskTriggerDispatcher.Instance().ClearTriggers();
|
||||
|
||||
|
||||
_logger.LogInformation("------------------------------");
|
||||
|
||||
stopwatch.Reset();
|
||||
stopwatch.Start();
|
||||
|
||||
await ExecuteProject(project);
|
||||
|
||||
//多次执行时及时中断
|
||||
if (ShouldSkipTask(project))
|
||||
if (taskProgress.Next.Index > projectIndex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (NormalEndException e)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (TaskCanceledException e)
|
||||
{
|
||||
_logger.LogInformation("取消执行配置组: {Msg}", e.Message);
|
||||
throw;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogDebug(e, "执行脚本时发生异常");
|
||||
_logger.LogError("执行脚本时发生异常: {Msg}", e.Message);
|
||||
if (taskProgress!=null && taskProgress.CurrentScriptGroupProjectInfo!=null )
|
||||
{
|
||||
taskProgress.CurrentScriptGroupProjectInfo.Status = 2;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
stopwatch.Stop();
|
||||
var elapsedTime = TimeSpan.FromMilliseconds(stopwatch.ElapsedMilliseconds);
|
||||
// _logger.LogDebug("→ 脚本执行结束: {Name}, 耗时: {ElapsedMilliseconds} 毫秒", project.Name, stopwatch.ElapsedMilliseconds);
|
||||
_logger.LogInformation("→ 脚本执行结束: {Name}, 耗时: {Minutes}分{Seconds:0.000}秒", project.Name,
|
||||
elapsedTime.Hours * 60 + elapsedTime.Minutes, elapsedTime.TotalSeconds % 60);
|
||||
_logger.LogInformation("------------------------------");
|
||||
|
||||
taskProgress.Next = null;
|
||||
}
|
||||
|
||||
await Task.Delay(2000);
|
||||
}
|
||||
|
||||
if (taskProgress != null)
|
||||
{
|
||||
if (taskProgress.CurrentScriptGroupProjectInfo!=null )
|
||||
if (exeProject is { SkipFlag: true })
|
||||
{
|
||||
taskProgress.CurrentScriptGroupProjectInfo.TaskEnd = true;
|
||||
taskProgress.CurrentScriptGroupProjectInfo.EndTime = DateTime.Now;
|
||||
if (taskProgress.CurrentScriptGroupProjectInfo.Status == 1)
|
||||
{
|
||||
taskProgress.ConsecutiveFailureCount = 0;
|
||||
taskProgress.LastSuccessScriptGroupProjectInfo =
|
||||
taskProgress.CurrentScriptGroupProjectInfo;
|
||||
taskProgress.LastScriptGroupName =taskProgress.CurrentScriptGroupName;
|
||||
}
|
||||
//累计连续失败次数
|
||||
if (taskProgress.CurrentScriptGroupProjectInfo.Status == 2)
|
||||
{
|
||||
taskProgress.ConsecutiveFailureCount++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
taskProgress?.History?.Add(taskProgress.CurrentScriptGroupProjectInfo);
|
||||
if (ShouldSkipTask(exeProject))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//月卡检测
|
||||
await _blessingOfTheWelkinMoonTask.Start(CancellationContext.Instance.Cts.Token);
|
||||
if (exeProject.Status != "Enabled")
|
||||
{
|
||||
_logger.LogInformation("脚本 {Name} 状态为禁用,跳过执行", exeProject.Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (CancellationContext.Instance.Cts.IsCancellationRequested)
|
||||
{
|
||||
_logger.LogInformation("执行被取消");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fisrt )
|
||||
{
|
||||
fisrt = false;
|
||||
Notify.Event(NotificationEvent.GroupStart).Success($"配置组{groupName}启动");
|
||||
}
|
||||
|
||||
if (!RunnerContext.Instance.IsPreExecution &&taskProgress != null)
|
||||
{
|
||||
taskProgress.CurrentScriptGroupProjectInfo = new TaskProgress.ScriptGroupProjectInfo
|
||||
{
|
||||
Name = exeProject.Name,
|
||||
FolderName = exeProject.FolderName, Index = projectIndex,
|
||||
GroupName = taskProgress?.CurrentScriptGroupName ?? ""
|
||||
};
|
||||
TaskProgressManager.SaveTaskProgress(taskProgress);
|
||||
}
|
||||
|
||||
//异常达到一次次数,重启bgi
|
||||
var autoconfig = TaskContext.Instance().Config.OtherConfig.AutoRestartConfig;
|
||||
if (autoconfig.Enabled && taskProgress.ConsecutiveFailureCount >= autoconfig.FailureCount)
|
||||
//优先执行的任务,需要计数
|
||||
if (RunnerContext.Instance.IsPreExecution)
|
||||
{
|
||||
_logger.LogInformation("调度器任务出现未预期的异常,自动重启bgi");
|
||||
Notify.Event(NotificationEvent.GroupEnd).Error("调度器任务出现未预期的异常,自动重启bgi");
|
||||
if (autoconfig.RestartGameTogether
|
||||
&& TaskContext.Instance().Config.GenshinStartConfig.LinkedStartEnabled
|
||||
&& TaskContext.Instance().Config.GenshinStartConfig.AutoEnterGameEnabled)
|
||||
// 生成项目唯一标识
|
||||
var projectKey = $"{exeProject.Name}|{exeProject.FolderName}|{exeProject.GroupInfo?.Name}";
|
||||
// 检查执行次数
|
||||
if (!_projectExecutionCount.TryGetValue(projectKey, out var preExecutionCount))
|
||||
{
|
||||
SystemControl.CloseGame();
|
||||
Thread.Sleep(2000);
|
||||
preExecutionCount = 0;
|
||||
}
|
||||
|
||||
SystemControl.RestartApplication(["--TaskProgress",taskProgress.Name]);
|
||||
_projectExecutionCount[projectKey] = preExecutionCount + 1;
|
||||
}
|
||||
|
||||
|
||||
for (var i = 0; i < exeProject.RunNum; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
TaskTriggerDispatcher.Instance().ClearTriggers();
|
||||
|
||||
|
||||
_logger.LogInformation("------------------------------");
|
||||
|
||||
stopwatch.Reset();
|
||||
stopwatch.Start();
|
||||
|
||||
await ExecuteProject(exeProject);
|
||||
|
||||
//多次执行时及时中断
|
||||
if (exeProject.RunNum > 1 && ShouldSkipTask(exeProject))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (NormalEndException e)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (TaskCanceledException e)
|
||||
{
|
||||
_logger.LogInformation("取消执行配置组: {Msg}", e.Message);
|
||||
throw;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogDebug(e, "执行脚本时发生异常");
|
||||
_logger.LogError("执行脚本时发生异常: {Msg}", e.Message);
|
||||
if (!RunnerContext.Instance.IsPreExecution && taskProgress != null && taskProgress.CurrentScriptGroupProjectInfo != null)
|
||||
{
|
||||
taskProgress.CurrentScriptGroupProjectInfo.Status = 2;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
stopwatch.Stop();
|
||||
var elapsedTime = TimeSpan.FromMilliseconds(stopwatch.ElapsedMilliseconds);
|
||||
// _logger.LogDebug("→ 脚本执行结束: {Name}, 耗时: {ElapsedMilliseconds} 毫秒", project.Name, stopwatch.ElapsedMilliseconds);
|
||||
_logger.LogInformation("→ 脚本执行结束: {Name}, 耗时: {Minutes}分{Seconds:0.000}秒", exeProject.Name,
|
||||
elapsedTime.Hours * 60 + elapsedTime.Minutes, elapsedTime.TotalSeconds % 60);
|
||||
_logger.LogInformation("------------------------------");
|
||||
}
|
||||
|
||||
await Task.Delay(2000);
|
||||
}
|
||||
|
||||
if (!RunnerContext.Instance.IsPreExecution && taskProgress != null)
|
||||
{
|
||||
if (taskProgress.CurrentScriptGroupProjectInfo != null)
|
||||
{
|
||||
taskProgress.CurrentScriptGroupProjectInfo.TaskEnd = true;
|
||||
taskProgress.CurrentScriptGroupProjectInfo.EndTime = DateTime.Now;
|
||||
if (taskProgress.CurrentScriptGroupProjectInfo.Status == 1)
|
||||
{
|
||||
taskProgress.ConsecutiveFailureCount = 0;
|
||||
taskProgress.LastSuccessScriptGroupProjectInfo =
|
||||
taskProgress.CurrentScriptGroupProjectInfo;
|
||||
taskProgress.LastScriptGroupName = taskProgress.CurrentScriptGroupName;
|
||||
}
|
||||
|
||||
//累计连续失败次数
|
||||
if (taskProgress.CurrentScriptGroupProjectInfo.Status == 2)
|
||||
{
|
||||
taskProgress.ConsecutiveFailureCount++;
|
||||
}
|
||||
|
||||
taskProgress?.History?.Add(taskProgress.CurrentScriptGroupProjectInfo);
|
||||
TaskProgressManager.SaveTaskProgress(taskProgress);
|
||||
}
|
||||
|
||||
//异常达到一次次数,重启bgi
|
||||
var autoconfig = TaskContext.Instance().Config.OtherConfig.AutoRestartConfig;
|
||||
if (autoconfig.Enabled && taskProgress.ConsecutiveFailureCount >= autoconfig.FailureCount)
|
||||
{
|
||||
_logger.LogInformation("调度器任务出现未预期的异常,自动重启bgi");
|
||||
Notify.Event(NotificationEvent.GroupEnd).Error("调度器任务出现未预期的异常,自动重启bgi");
|
||||
if (autoconfig.RestartGameTogether
|
||||
&& TaskContext.Instance().Config.GenshinStartConfig.LinkedStartEnabled
|
||||
&& TaskContext.Instance().Config.GenshinStartConfig.AutoEnterGameEnabled)
|
||||
{
|
||||
SystemControl.CloseGame();
|
||||
Thread.Sleep(2000);
|
||||
}
|
||||
|
||||
SystemControl.RestartApplication(["--TaskProgress", taskProgress.Name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 还原定时器
|
||||
TaskTriggerDispatcher.Instance().SetTriggers(GameTaskManager.LoadInitialTriggers());
|
||||
|
||||
if (!string.IsNullOrEmpty(groupName))
|
||||
if (!string.IsNullOrEmpty(groupName)&&!RunnerContext.Instance.IsPreExecution)
|
||||
{
|
||||
_logger.LogInformation("配置组 {Name} 执行结束", groupName);
|
||||
}
|
||||
|
||||
if (!fisrt)
|
||||
if (!fisrt&&!RunnerContext.Instance.IsPreExecution)
|
||||
{
|
||||
Notify.Event(NotificationEvent.GroupEnd).Success($"配置组{groupName}结束");
|
||||
}
|
||||
@@ -387,21 +511,25 @@ public partial class ScriptService : IScriptService
|
||||
}
|
||||
|
||||
_logger.LogInformation("→ 开始执行JS脚本: {Name}", project.Name);
|
||||
if (RunnerContext.Instance.IsPreExecution) _logger.LogInformation("此任务为优先执行任务!");
|
||||
await project.Run();
|
||||
}
|
||||
else if (project.Type == "KeyMouse")
|
||||
{
|
||||
_logger.LogInformation("→ 开始执行键鼠脚本: {Name}", project.Name);
|
||||
if (RunnerContext.Instance.IsPreExecution) _logger.LogInformation("此任务为优先执行任务!");
|
||||
await project.Run();
|
||||
}
|
||||
else if (project.Type == "Pathing")
|
||||
{
|
||||
_logger.LogInformation("→ 开始执行地图追踪任务: {Name}", project.Name);
|
||||
if (RunnerContext.Instance.IsPreExecution) _logger.LogInformation("此任务为优先执行任务!");
|
||||
await project.Run();
|
||||
}
|
||||
else if (project.Type == "Shell")
|
||||
{
|
||||
_logger.LogInformation("→ 开始执行shell: {Name}", project.Name);
|
||||
if (RunnerContext.Instance.IsPreExecution) _logger.LogInformation("此任务为优先执行任务!");
|
||||
await project.Run();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,6 +305,31 @@
|
||||
MinWidth="100"
|
||||
Text="{Binding PathingConfig.SkipDuring, Mode=TwoWay}"
|
||||
TextWrapping="NoWrap" />
|
||||
</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="不在连续任务中显示"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="点击连续执行时,不显示该配置组"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:ToggleSwitch Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
IsChecked="{Binding PathingConfig.HideOnRepeat, Mode=TwoWay}" />
|
||||
|
||||
</Grid>
|
||||
<!-- 执行周期配置 -->
|
||||
<Grid Margin="16">
|
||||
@@ -451,6 +476,246 @@
|
||||
</ui:CardExpander>
|
||||
|
||||
</Grid>
|
||||
|
||||
|
||||
<!-- 跳过执行配置 -->
|
||||
<Grid Margin="16">
|
||||
|
||||
<ui:CardExpander Margin="0,0,36,12"
|
||||
ContentPadding="0"
|
||||
IsExpanded="False">
|
||||
|
||||
<ui:CardExpander.Header>
|
||||
<Grid>
|
||||
|
||||
<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="跳过执行配置"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="跳过指定时间内完成的调度器任务(js等未抛出异常则算成功,路径追踪任务,走完最后路径才算成功)的配置"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:ToggleSwitch Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,24,0"
|
||||
IsChecked="{Binding PathingConfig.TaskCompletionSkipRuleConfig.Enable, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
</ui:CardExpander.Header>
|
||||
<StackPanel>
|
||||
<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="跳过执行策略"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="根据不同的策略来跳过任务"
|
||||
TextWrapping="Wrap" />
|
||||
<ComboBox Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
MinWidth="100"
|
||||
SelectedValuePath="Key"
|
||||
DisplayMemberPath="Value"
|
||||
ItemsSource="{Binding SkipPolicySource}"
|
||||
SelectedValue="{Binding PathingConfig.TaskCompletionSkipRuleConfig.SkipPolicy}" />
|
||||
</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="分界时间"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="分界时间,一般填0或4,如果填0则当天从0点开始算,如果填4,则当天从凌晨4点开始算,小于4点算前一天,填写小于0的数或大于23,如-1,则不启用,适用于考虑固定刷新时间,且一天内不重复执行的情况。"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBox Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
MinWidth="120"
|
||||
Text="{Binding PathingConfig.TaskCompletionSkipRuleConfig.BoundaryTime}" />
|
||||
</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="和上次运行的间隔秒数"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="和上一次运行时间间隔时间小于配置时间,则跳过执行,小于0时不启用,出于精度考虑,这里使用秒为单位。"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBox Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
MinWidth="120"
|
||||
Text="{Binding PathingConfig.TaskCompletionSkipRuleConfig.LastRunGapSeconds}" />
|
||||
</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="间隔时间计算参照"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="根据配置决定,间隔时间是根据开始时间算,还是根据结束时间算"
|
||||
TextWrapping="Wrap" />
|
||||
<ComboBox Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
MinWidth="100"
|
||||
SelectedValuePath="Key"
|
||||
DisplayMemberPath="Value"
|
||||
ItemsSource="{Binding ReferencePointSource}"
|
||||
SelectedValue="{Binding PathingConfig.TaskCompletionSkipRuleConfig.ReferencePoint}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ui:CardExpander>
|
||||
|
||||
</Grid>
|
||||
<!-- 优先执行其他配置组 -->
|
||||
<Grid Margin="16">
|
||||
|
||||
<ui:CardExpander Margin="0,0,36,12"
|
||||
ContentPadding="0"
|
||||
IsExpanded="False">
|
||||
|
||||
<ui:CardExpander.Header>
|
||||
<Grid>
|
||||
|
||||
<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="优先执行其他配置组"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="每个任务开始前,会先检查优先执行的其他配置组,是否有任务可以执行,如果可以执行,则会先执行其他配置组的任务,所配置的其他任务组,必须开启跳过执行配置,不支持套娃,才会生效。可用于卡时间、优先材料等提高收益。"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:ToggleSwitch Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,24,0"
|
||||
IsChecked="{Binding PathingConfig.PreExecutionPriorityConfig.Enabled, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
</ui:CardExpander.Header>
|
||||
<StackPanel>
|
||||
<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="配置组名称"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="配置组名,多个用逗号分隔,只有开启跳过执行配置,才会生效。"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBox Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
MinWidth="120"
|
||||
Text="{Binding PathingConfig.PreExecutionPriorityConfig.GroupNames}" />
|
||||
</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="额外执行次数"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="需要优先的任务,在此配置组执行时,最多尝试的额外次数(失败或者CD过短导致依旧满足条件),防止无限循环,另外,此计数关闭任务,继续执行会清空。"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBox Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
MinWidth="120"
|
||||
Text="{Binding PathingConfig.PreExecutionPriorityConfig.MaxRetryCount}" />
|
||||
</Grid>
|
||||
|
||||
</StackPanel>
|
||||
</ui:CardExpander>
|
||||
|
||||
</Grid>
|
||||
|
||||
</StackPanel>
|
||||
</ui:CardExpander>
|
||||
<ui:CardExpander Margin="0,0,0,12"
|
||||
|
||||
@@ -1725,6 +1725,10 @@ public partial class ScriptControlViewModel : ViewModel
|
||||
// 创建每个配置组的 CheckBox
|
||||
foreach (var scriptGroup in ScriptGroups)
|
||||
{
|
||||
if (scriptGroup.Config.PathingConfig.HideOnRepeat)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var checkBox = new CheckBox
|
||||
{
|
||||
Content = scriptGroup.Name,
|
||||
|
||||
@@ -33,6 +33,24 @@ public partial class ScriptGroupConfigViewModel : ObservableObject, IViewModel
|
||||
new KeyValuePair<string, string>("AllowAutoPickupForNonElite", "非精英允许自动拾取"),
|
||||
new KeyValuePair<string, string>("DisableAutoPickupForNonElite", "非精英关闭自动拾取")
|
||||
};
|
||||
//跳过策略
|
||||
//GroupPhysicalPathSkipPolicy: 配置组且物理路径相同跳过
|
||||
//PhysicalPathSkipPolicy: 物理路径相同跳过
|
||||
//SameNameSkipPolicy: 同类型同名跳过
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<KeyValuePair<string, string>> _skipPolicySource = new()
|
||||
{
|
||||
new KeyValuePair<string, string>("GroupPhysicalPathSkipPolicy", "配置组且物理路径相同跳过"),
|
||||
new KeyValuePair<string, string>("PhysicalPathSkipPolicy", "物理路径相同跳过"),
|
||||
new KeyValuePair<string, string>("SameNameSkipPolicy", "同类型同名跳过")
|
||||
};
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<KeyValuePair<string, string>> _referencePointSource = new()
|
||||
{
|
||||
new KeyValuePair<string, string>("StartTime", "开始时间"),
|
||||
new KeyValuePair<string, string>("EndTime", "结束时间")
|
||||
};
|
||||
public ScriptGroupConfigViewModel(AllConfig config, ScriptGroupConfig scriptGroupConfig)
|
||||
{
|
||||
ScriptGroupConfig = scriptGroupConfig;
|
||||
|
||||
@@ -42,13 +42,13 @@ BetterGI · A Better Genshin Impact experience, powered by computer vision techn
|
||||
* **[Auto Hangout](https://bettergi.com/feats/timer/skip.html#%E8%87%AA%E5%8A%A8%E9%82%80%E7%BA%A6):** Automatically selects Hangout options (requires Auto Dialogue enabled).
|
||||
* **[Quick Teleport](https://bettergi.com/feats/timer/tp.html):** Auto-clicks map teleport points and initiates teleportation.
|
||||
* **[Semi-Auto Fishing](https://bettergi.com/feats/timer/fish.html):** AI-based auto-casting, auto-hook detection, and auto-catch mechanics.
|
||||
* **[Auto Cooking](https://bettergi.com/feats/timer/cook.html):** Perfectly cooks dishes (excluding "Bountiful Year" recipes).
|
||||
* **[Auto Cooking](https://bettergi.com/feats/timer/cook.html):** Perfectly cooks dishes (excluding "Adeptus' Temptation" recipes).
|
||||
|
||||
* **Standalone Tasks**
|
||||
* **[Auto Genius Invokation TCG](https://bettergi.com/feats/task/tcg.html):** Automates PvE TCG challenges like character invites and weekly duels.
|
||||
* **[Auto Woodcutting](https://bettergi.com/feats/task/felling.html):** Uses "Woodland Encounter" (<kbd>Z</kbd>) and relogs to farm wood efficiently.
|
||||
* **[Auto Woodcutting](https://bettergi.com/feats/task/felling.html):** Uses "The Boon of the Elder Tree" (<kbd>Z</kbd>) and relogs to farm wood efficiently.
|
||||
* **[Auto Domain Runs](https://bettergi.com/feats/task/domain.html):** Fully automated domain clears, including starting, combat, and claiming rewards.
|
||||
* **[Auto Rhythm Game](https://bettergi.com/feats/task/music.html):** Completes "Rhythm of the Realm" albums for achievements.
|
||||
* **[Auto Rhythm Game](https://bettergi.com/feats/task/music.html):** Completes achievements for Repertoire of Myriad Melodies.
|
||||
* **[Full Auto Fishing](https://bettergi.com/feats/task/fish.html):** Fully automates fishing at designated spots, including day/night transitions.
|
||||
|
||||
* **Full Automation**
|
||||
@@ -127,4 +127,4 @@ Formatting: [CodeMaid.config](CodeMaid.config), [Settings.XamlStyler](Settings.X
|
||||

|
||||
|
||||
## Support
|
||||
Report issues: [GitHub Issues](https://github.com/babalae/better-genshin-impact/issues)
|
||||
Report issues: [GitHub Issues](https://github.com/babalae/better-genshin-impact/issues)
|
||||
|
||||
@@ -51,7 +51,7 @@ BetterGI · 更好的原神, 一個基於電腦視覺技術,意圖讓原神
|
||||
* [自動伐木](https://bettergi.com/feats/task/felling.html):自動 <kbd>Z</kbd> 鍵使用「王樹瑞佑」,利用上下線可以刷新木材的原理,掛機刷滿一背包的木材
|
||||
* [自動秘境](https://bettergi.com/feats/task/domain.html):全自動秘境掛機刷體力,自動循環進入秘境開啟鑰匙、戰鬥、走到古樹並領取獎勵
|
||||
* [自動音遊](https://bettergi.com/feats/task/music.html):一鍵自動完成千音雅集的專輯,快速獲取成就
|
||||
* [全自動釣魚](https://bettergi.com/feats/task/fish.html):在出現釣魚F按鈕的位置面向魚塘,然後啟動全自動釣魚,啟動後程式會自動完成釣魚,並切換白天和晚上
|
||||
* [全自動釣魚](https://bettergi.com/feats/task/fish.html):在出現釣魚 <kbd>F</kbd> 按鈕的位置面向魚塘,然後啟動全自動釣魚,啟動後程式會自動完成釣魚,並切換白天和晚上
|
||||
* 全自動
|
||||
* [一條龍](https://github.com/babalae/better-genshin-impact/issues/846):一鍵完成日常(使用歷練點),並領取獎勵
|
||||
* [自動採集/挖礦/鋤地](https://bettergi.com/feats/autos/pathing.html):透過左上角小地圖的識別,完成自動採集、挖礦、鋤地等功能
|
||||
@@ -136,5 +136,4 @@ BetterGI · 更好的原神, 一個基於電腦視覺技術,意圖讓原神
|
||||
|
||||
## 問題反饋
|
||||
|
||||
提 [Issue](https://github.com/babalae/better-genshin-impact/issues) 或 discord
|
||||
|
||||
提 [Issue](https://github.com/babalae/better-genshin-impact/issues) 或 discord
|
||||
|
||||
@@ -46,7 +46,7 @@ BetterGI · 更好的原神, 一个基于计算机视觉技术,意图让原
|
||||
* [自动伐木](https://bettergi.com/feats/task/felling.html):自动 <kbd>Z</kbd> 键使用「王树瑞佑」,利用上下线可以刷新木材的原理,挂机刷满一背包的木材
|
||||
* [自动秘境](https://bettergi.com/feats/task/domain.html):全自动秘境挂机刷体力,自动循环进入秘境开启钥匙、战斗、走到古树并领取奖励
|
||||
* [自动音游](https://bettergi.com/feats/task/music.html):一键自动完成千音雅集的专辑,快速获取成就
|
||||
* [全自动钓鱼](https://bettergi.com/feats/task/fish.html):在出现钓鱼F按钮的位置面向鱼塘,然后启动全自动钓鱼,启动后程序会自动完成钓鱼,并切换白天和晚上
|
||||
* [全自动钓鱼](https://bettergi.com/feats/task/fish.html):在出现钓鱼 <kbd>F</kbd> 按钮的位置面向鱼塘,然后启动全自动钓鱼,启动后程序会自动完成钓鱼,并切换白天和晚上
|
||||
* 全自动
|
||||
* [一条龙](https://github.com/babalae/better-genshin-impact/issues/846):一键完成日常(使用历练点),并领取奖励
|
||||
* [自动采集/挖矿/锄地](https://bettergi.com/feats/autos/pathing.html):通过左上角小地图的识别,完成自动采集、挖矿、锄地等功能
|
||||
@@ -131,4 +131,4 @@ BetterGI · 更好的原神, 一个基于计算机视觉技术,意图让原
|
||||
|
||||
## 问题反馈
|
||||
|
||||
提 [Issue](https://github.com/babalae/better-genshin-impact/issues) 或 QQ群[1053273766](https://qm.qq.com/q/qtocsOXnIQ)
|
||||
提 [Issue](https://github.com/babalae/better-genshin-impact/issues) 或 QQ群[1051494685](https://qm.qq.com/q/TPQtZlgraU)
|
||||
|
||||
Reference in New Issue
Block a user