mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-05-17 09:26:50 +08:00
Merge pull request #914 from mfkvfhpdx/main
路径追踪支持 未知界面检查关闭,主要用来解决狗粮、或锄地捡物品,有小概率误触烹饪界面的问题。
This commit is contained in:
@@ -12,6 +12,7 @@ using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception;
|
||||
using BetterGenshinImpact.GameTask.AutoTrackPath;
|
||||
using BetterGenshinImpact.GameTask.Common.BgiVision;
|
||||
using Vanara.PInvoke;
|
||||
@@ -125,7 +126,7 @@ public class Avatar
|
||||
var tpTask = new TpTask(Ct);
|
||||
tpTask.Tp(TpTask.ReviveStatueOfTheSevenPointX, TpTask.ReviveStatueOfTheSevenPointY, true).Wait(Ct);
|
||||
|
||||
throw new Exception("检测到复苏界面,存在角色被击败,前往七天神像复活");
|
||||
throw new RetryException("检测到复苏界面,存在角色被击败,前往七天神像复活");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -144,105 +144,112 @@ public class PathExecutor
|
||||
return;
|
||||
}
|
||||
|
||||
InitializePathing(task);
|
||||
|
||||
// 转换、按传送点分割路径
|
||||
var waypointsList = ConvertWaypointsForTrack(task.Positions);
|
||||
|
||||
await Delay(100, ct);
|
||||
Navigation.WarmUp(); // 提前加载地图特征点
|
||||
|
||||
foreach (var waypoints in waypointsList)
|
||||
try
|
||||
{
|
||||
CurWaypoints = (waypointsList.FindIndex(wps => wps == waypoints), waypoints);
|
||||
InitializePathing(task);
|
||||
// 转换、按传送点分割路径
|
||||
var waypointsList = ConvertWaypointsForTrack(task.Positions);
|
||||
|
||||
await Delay(100, ct);
|
||||
Navigation.WarmUp(); // 提前加载地图特征点
|
||||
|
||||
for (var i = 0; i < RetryTimes; i++)
|
||||
foreach (var waypoints in waypointsList)
|
||||
{
|
||||
try
|
||||
CurWaypoints = (waypointsList.FindIndex(wps => wps == waypoints), waypoints);
|
||||
|
||||
|
||||
for (var i = 0; i < RetryTimes; i++)
|
||||
{
|
||||
await ResolveAnomalies(); // 异常场景处理
|
||||
foreach (var waypoint in waypoints)
|
||||
try
|
||||
{
|
||||
CurWaypoint = (waypoints.FindIndex(wps => wps == waypoint), waypoint);
|
||||
TryCloseSkipOtherOperations();
|
||||
await RecoverWhenLowHp(waypoint); // 低血量恢复
|
||||
if (waypoint.Type == WaypointType.Teleport.Code)
|
||||
await ResolveAnomalies(); // 异常场景处理
|
||||
foreach (var waypoint in waypoints)
|
||||
{
|
||||
await HandleTeleportWaypoint(waypoint);
|
||||
CurWaypoint = (waypoints.FindIndex(wps => wps == waypoint), waypoint);
|
||||
TryCloseSkipOtherOperations();
|
||||
await RecoverWhenLowHp(waypoint); // 低血量恢复
|
||||
if (waypoint.Type == WaypointType.Teleport.Code)
|
||||
{
|
||||
await HandleTeleportWaypoint(waypoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
await BeforeMoveToTarget(waypoint);
|
||||
|
||||
// Path不用走得很近,Target需要接近,但都需要先移动到对应位置
|
||||
await MoveTo(waypoint);
|
||||
|
||||
if (waypoint.Type == WaypointType.Target.Code
|
||||
// 除了 fight mining 之外的 action 都需要接近
|
||||
|| (!string.IsNullOrEmpty(waypoint.Action)
|
||||
&& waypoint.Action != ActionEnum.NahidaCollect.Code
|
||||
&& waypoint.Action != ActionEnum.Fight.Code
|
||||
&& waypoint.Action != ActionEnum.CombatScript.Code
|
||||
&& waypoint.Action != ActionEnum.Mining.Code))
|
||||
{
|
||||
await MoveCloseTo(waypoint);
|
||||
}
|
||||
|
||||
//skipOtherOperations如果重试,则跳过相关操作
|
||||
if (!string.IsNullOrEmpty(waypoint.Action) && !_skipOtherOperations)
|
||||
{
|
||||
// 执行 action
|
||||
await AfterMoveToTarget(waypoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
catch (NormalEndException normalEndException)
|
||||
{
|
||||
Logger.LogInformation(normalEndException.Message);
|
||||
if (RunnerContext.Instance.IsContinuousRunGroup)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
else
|
||||
{
|
||||
await BeforeMoveToTarget(waypoint);
|
||||
|
||||
// Path不用走得很近,Target需要接近,但都需要先移动到对应位置
|
||||
await MoveTo(waypoint);
|
||||
|
||||
if (waypoint.Type == WaypointType.Target.Code
|
||||
// 除了 fight mining 之外的 action 都需要接近
|
||||
|| (!string.IsNullOrEmpty(waypoint.Action)
|
||||
&& waypoint.Action != ActionEnum.NahidaCollect.Code
|
||||
&& waypoint.Action != ActionEnum.Fight.Code
|
||||
&& waypoint.Action != ActionEnum.CombatScript.Code
|
||||
&& waypoint.Action != ActionEnum.Mining.Code))
|
||||
{
|
||||
await MoveCloseTo(waypoint);
|
||||
}
|
||||
|
||||
//skipOtherOperations如果重试,则跳过相关操作
|
||||
if (!string.IsNullOrEmpty(waypoint.Action) && !_skipOtherOperations)
|
||||
{
|
||||
// 执行 action
|
||||
await AfterMoveToTarget(waypoint);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
catch (NormalEndException normalEndException)
|
||||
{
|
||||
Logger.LogInformation(normalEndException.Message);
|
||||
if (RunnerContext.Instance.IsContinuousRunGroup)
|
||||
catch (TaskCanceledException e)
|
||||
{
|
||||
throw;
|
||||
if (RunnerContext.Instance.IsContinuousRunGroup)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (RetryException retryException)
|
||||
{
|
||||
break;
|
||||
StartSkipOtherOperations();
|
||||
Logger.LogWarning(retryException.Message);
|
||||
}
|
||||
}
|
||||
catch (TaskCanceledException e)
|
||||
{
|
||||
if (RunnerContext.Instance.IsContinuousRunGroup)
|
||||
catch (RetryNoCountException retryException)
|
||||
{
|
||||
throw;
|
||||
//特殊情况下,重试不消耗次数
|
||||
i--;
|
||||
StartSkipOtherOperations();
|
||||
Logger.LogWarning(retryException.Message);
|
||||
}
|
||||
else
|
||||
finally
|
||||
{
|
||||
break;
|
||||
// 不管咋样,松开所有按键
|
||||
Simulation.SendInput.Keyboard.KeyUp(User32.VK.VK_W);
|
||||
Simulation.SendInput.Mouse.RightButtonUp();
|
||||
}
|
||||
}
|
||||
catch (RetryException retryException)
|
||||
{
|
||||
StartSkipOtherOperations();
|
||||
Logger.LogWarning(retryException.Message);
|
||||
}
|
||||
catch (RetryNoCountException retryException)
|
||||
{
|
||||
//特殊情况下,重试不消耗次数
|
||||
i--;
|
||||
StartSkipOtherOperations();
|
||||
Logger.LogWarning(retryException.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 不管咋样,松开所有按键
|
||||
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyUp);
|
||||
Simulation.SendInput.SimulateAction(GIActions.SprintMouse, KeyType.KeyUp);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_unknownInterfaceCheckingTask = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> SwitchPartyBefore(PathingTask task)
|
||||
@@ -289,11 +296,58 @@ public class PathExecutor
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _unknownInterfaceCheckingTask = false;
|
||||
|
||||
private void UnknownInterfaceCheckingTaskStart()
|
||||
{
|
||||
/*if (_partyConfig.Enabled && _partyConfig.CloseUnknownInterfaceCheck)
|
||||
{*/
|
||||
_unknownInterfaceCheckingTask = true;
|
||||
Task.Run(async () =>
|
||||
{
|
||||
Logger.LogInformation("开始未知界面检查");
|
||||
while (_unknownInterfaceCheckingTask && !ct.IsCancellationRequested)
|
||||
{
|
||||
ImageRegion imageRegion = TaskTriggerDispatcher.Instance().CaptureToRectArea();
|
||||
|
||||
var cookRa = imageRegion.Find(AutoSkipAssets.Instance.CookRo);
|
||||
if (cookRa.IsExist())
|
||||
{
|
||||
Logger.LogInformation("检测到烹饪界面,使用ESC关闭界面");
|
||||
Simulation.SendInput.Keyboard.KeyPress(User32.VK.VK_ESCAPE);
|
||||
}
|
||||
|
||||
var mainRa2 = imageRegion.Find(AutoSkipAssets.Instance.PageCloseMainRo);
|
||||
if (mainRa2.IsExist())
|
||||
{
|
||||
Logger.LogInformation("检测到主界面,使用ESC关闭界面");
|
||||
Simulation.SendInput.Keyboard.KeyPress(User32.VK.VK_ESCAPE);
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
if (!_unknownInterfaceCheckingTask || ct.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
await Task.Delay(1000, ct);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.LogInformation("关闭未知界面检查");
|
||||
}, ct);
|
||||
/*} */
|
||||
}
|
||||
|
||||
|
||||
private void InitializePathing(PathingTask task)
|
||||
{
|
||||
LogScreenResolution();
|
||||
WeakReferenceMessenger.Default.Send(new PropertyChangedMessage<object>(this,
|
||||
"UpdateCurrentPathing", new object(), task));
|
||||
UnknownInterfaceCheckingTaskStart();
|
||||
}
|
||||
|
||||
private void LogScreenResolution()
|
||||
|
||||
BIN
BetterGenshinImpact/GameTask/AutoSkip/Assets/1920x1080/cook.png
Normal file
BIN
BetterGenshinImpact/GameTask/AutoSkip/Assets/1920x1080/cook.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
@@ -20,7 +20,8 @@ public class AutoSkipAssets : BaseAssets<AutoSkipAssets>
|
||||
public RecognitionObject ExclamationIconRo;
|
||||
|
||||
public RecognitionObject PageCloseRo;
|
||||
|
||||
public RecognitionObject CookRo;
|
||||
public RecognitionObject PageCloseMainRo;
|
||||
public RecognitionObject CollectRo;
|
||||
public RecognitionObject ReRo;
|
||||
|
||||
@@ -117,7 +118,24 @@ public class AutoSkipAssets : BaseAssets<AutoSkipAssets>
|
||||
RegionOfInterest = new Rect(CaptureRect.Width - CaptureRect.Width / 8, 0, CaptureRect.Width / 8, CaptureRect.Height / 8),
|
||||
DrawOnWindow = true
|
||||
}.InitTemplate();
|
||||
|
||||
CookRo= new RecognitionObject
|
||||
{
|
||||
Name = "Cook",
|
||||
RecognitionType = RecognitionTypes.TemplateMatch,
|
||||
TemplateImageMat = GameTaskManager.LoadAssetImage("AutoSkip", "cook.png"),
|
||||
RegionOfInterest = new Rect(CaptureRect.Width / 15, 0, CaptureRect.Width / 14, CaptureRect.Height /14),
|
||||
DrawOnWindow = true
|
||||
}.InitTemplate();
|
||||
PageCloseMainRo= new RecognitionObject
|
||||
{
|
||||
Name = "PageCloseMain",
|
||||
RecognitionType = RecognitionTypes.TemplateMatch,
|
||||
TemplateImageMat = GameTaskManager.LoadAssetImage("AutoSkip", "page_close_main.png"),
|
||||
RegionOfInterest = new Rect(0, 0, CaptureRect.Width / 25, CaptureRect.Height / 14),
|
||||
DrawOnWindow = true
|
||||
}.InitTemplate();
|
||||
|
||||
|
||||
// 一键派遣
|
||||
CollectRo = new RecognitionObject
|
||||
{
|
||||
|
||||
@@ -152,7 +152,7 @@ public class TpTask(CancellationToken ct)
|
||||
return (clickX, clickY);
|
||||
}
|
||||
|
||||
public async Task<(double, double)> Tp(double tpX, double tpY, bool force = false)
|
||||
private async Task checkInBigMapUi()
|
||||
{
|
||||
// M 打开地图识别当前位置,中心点为当前位置
|
||||
var ra1 = CaptureToRectArea();
|
||||
@@ -174,8 +174,14 @@ public class TpTask(CancellationToken ct)
|
||||
await Delay(500, ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(double, double)> Tp(double tpX, double tpY, bool force = false)
|
||||
{
|
||||
|
||||
await checkInBigMapUi();
|
||||
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
try
|
||||
@@ -192,6 +198,7 @@ public class TpTask(CancellationToken ct)
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await checkInBigMapUi();
|
||||
Logger.LogError("传送失败,重试 {I} 次", i + 1);
|
||||
Logger.LogDebug(e, "传送失败,重试 {I} 次", i + 1);
|
||||
}
|
||||
|
||||
368
BetterGenshinImpact/GameTask/LogParse/LogParse.cs
Normal file
368
BetterGenshinImpact/GameTask/LogParse/LogParse.cs
Normal file
@@ -0,0 +1,368 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using static LogParse.LogParse.ConfigGroupEntity;
|
||||
using System.Reflection;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Vanara.PInvoke;
|
||||
|
||||
namespace LogParse
|
||||
{
|
||||
public class LogParse
|
||||
{
|
||||
|
||||
public static List<string> SafeReadAllLines(string filePath)
|
||||
{
|
||||
var lines = new List<string>();
|
||||
try
|
||||
{
|
||||
// 使用 FileStream 和 StreamReader,允许共享读取
|
||||
using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
using (var reader = new StreamReader(fileStream))
|
||||
{
|
||||
string line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
lines.Add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Console.WriteLine($"无法读取文件 {filePath}: {ex.Message}");
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
public static List<ConfigGroupEntity> ParseFile(List<(string, string)> logFiles)
|
||||
{
|
||||
List<(string, string)> logLines = new();
|
||||
foreach (var logFile in logFiles)
|
||||
{
|
||||
string[] logstrs = SafeReadAllLines(logFile.Item1).ToArray();
|
||||
foreach (var logstr in logstrs)
|
||||
{
|
||||
logLines.Add((logstr, logFile.Item2));
|
||||
}
|
||||
|
||||
}
|
||||
return Parse(logLines);
|
||||
}
|
||||
public static List<ConfigGroupEntity> Parse(List<(string, string)> logLines)
|
||||
{
|
||||
|
||||
// var logstrs = log.Item1;
|
||||
List<ConfigGroupEntity> configGroupEntities = new();
|
||||
ConfigGroupEntity configGroupEntity = null;
|
||||
ConfigTask configTask = null;
|
||||
for (int i = 0; i < logLines.Count; i++)
|
||||
{
|
||||
var logstr = logLines[i].Item1;
|
||||
var logrq = logLines[i].Item2;
|
||||
//if("配置组 \"${}\" 加载完成,共25个脚本,开始执行")
|
||||
|
||||
|
||||
// 定义正则表达式
|
||||
|
||||
var result = parseBgiLine(@"配置组 ""(.+?)"" 加载完成,共(\d+)个脚本", logstr);
|
||||
if (result.Item1)
|
||||
{
|
||||
configGroupEntity = new();
|
||||
configGroupEntity.Name = result.Item2[1];
|
||||
configGroupEntity.StartDate = parsePreDataTime(logLines, i - 1, logrq);
|
||||
configGroupEntities.Add(configGroupEntity);
|
||||
}
|
||||
if (configGroupEntity != null)
|
||||
{
|
||||
//配置组 "战斗" 执行结束
|
||||
result = parseBgiLine($"配置组 \"{configGroupEntity.Name}\" 执行结束", logstr);
|
||||
if (result.Item1)
|
||||
{
|
||||
|
||||
configGroupEntity.EndDate = parsePreDataTime(logLines, i - 1, logrq);
|
||||
configGroupEntity = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (configGroupEntity != null)
|
||||
{
|
||||
|
||||
result = parseBgiLine(@"→ 开始执行路径追踪任务: ""(.+?)""", logstr);
|
||||
if (result.Item1)
|
||||
{
|
||||
configTask = new();
|
||||
configTask.Name = result.Item2[1];
|
||||
configTask.StartDate = parsePreDataTime(logLines, i - 1, logrq);
|
||||
configGroupEntity.ConfigTaskList.Add(configTask);
|
||||
}
|
||||
|
||||
if (configTask != null)
|
||||
{
|
||||
|
||||
if (logstr.StartsWith("→ 脚本执行结束: \"" + configTask.Name + "\""))
|
||||
{
|
||||
configTask.EndDate = parsePreDataTime(logLines, i - 1, logrq);
|
||||
configTask = null;
|
||||
}
|
||||
result = parseBgiLine(@"交互或拾取:""(.+?)""", logstr);
|
||||
if (result.Item1)
|
||||
{
|
||||
configTask.addPick(result.Item2[1]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Console.WriteLine(logstr);
|
||||
}
|
||||
|
||||
//if (configGroupEntity != null)
|
||||
//{
|
||||
// configGroupEntities.Add(configGroupEntity);
|
||||
//}
|
||||
|
||||
|
||||
return configGroupEntities;
|
||||
}
|
||||
private static (bool, List<string>) parseBgiLine(string pattern, string str)
|
||||
{
|
||||
Match match = Regex.Match(str, pattern);
|
||||
if (match.Success)
|
||||
{
|
||||
return (true, match.Groups.Cast<Group>().Select(g => g.Value).ToList());
|
||||
}
|
||||
return (false, []);
|
||||
}
|
||||
private static DateTime? parsePreDataTime(List<(string, string)> list, int index, string logrq)
|
||||
{
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
(bool, List<string>) result = parseBgiLine(@"\[(\d{2}:\d{2}:\d{2})\.\d+\]", list[index].Item1);
|
||||
if (result.Item1)
|
||||
{
|
||||
|
||||
DateTime dateTime = DateTime.ParseExact(logrq + " " + result.Item2[1], "yyyy-MM-dd HH:mm:ss", null);
|
||||
return dateTime;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public class ConfigGroupEntity
|
||||
{
|
||||
|
||||
//配置组名字
|
||||
public string Name { get; set; }
|
||||
//开始日期
|
||||
public DateTime? StartDate { get; set; }
|
||||
|
||||
//结束日期
|
||||
public DateTime? EndDate { get; set; }
|
||||
//配置人物列表xxx.json
|
||||
public List<ConfigTask> ConfigTaskList { get; } = new();
|
||||
|
||||
public class ConfigTask
|
||||
{
|
||||
public string Name { get; set; }
|
||||
//开始日期
|
||||
public DateTime? StartDate { get; set; }
|
||||
|
||||
//结束日期
|
||||
public DateTime? EndDate { get; set; }
|
||||
//拾取字典
|
||||
public Dictionary<string, int> Picks { get; } = new();
|
||||
public void addPick(string val)
|
||||
{
|
||||
if (!Picks.ContainsKey(val))
|
||||
{
|
||||
Picks.Add(val, 0);
|
||||
}
|
||||
Picks[val] = Picks[val] + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
public static List<(string FileName, string Date)> GetLogFiles(string folderPath)
|
||||
{
|
||||
// 定义返回的元组列表
|
||||
var result = new List<(string FileName, string Date)>();
|
||||
|
||||
// 确认文件夹是否存在
|
||||
if (!Directory.Exists(folderPath))
|
||||
{
|
||||
Console.WriteLine("指定的文件夹不存在。");
|
||||
return result;
|
||||
}
|
||||
|
||||
// 定义文件名匹配的正则表达式
|
||||
string pattern = @"^better-genshin-impact(\d{8})\.log$";
|
||||
Regex regex = new Regex(pattern);
|
||||
|
||||
// 遍历文件夹中的所有文件
|
||||
var files = Directory.GetFiles(folderPath);
|
||||
foreach (var file in files)
|
||||
{
|
||||
string fileName = Path.GetFileName(file);
|
||||
|
||||
// 检查文件名是否匹配模式
|
||||
var match = regex.Match(fileName);
|
||||
if (match.Success)
|
||||
{
|
||||
string dateString = match.Groups[1].Value;
|
||||
|
||||
// 尝试将日期字符串格式化为 yyyy-MM-dd
|
||||
if (DateTime.TryParseExact(dateString, "yyyyMMdd", null, System.Globalization.DateTimeStyles.None, out DateTime parsedDate))
|
||||
{
|
||||
result.Add((folderPath + "\\" + fileName, parsedDate.ToString("yyyy-MM-dd")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 按日期排序
|
||||
result = result.OrderBy(r => r.Date).ToList();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string ConvertSecondsToTime(double totalSeconds)
|
||||
{
|
||||
if (totalSeconds < 0)
|
||||
throw new ArgumentException("Seconds cannot be negative.");
|
||||
|
||||
int hours = (int)(totalSeconds / 3600);
|
||||
int minutes = (int)((totalSeconds % 3600) / 60);
|
||||
double seconds = totalSeconds % 60;
|
||||
|
||||
string result = "";
|
||||
if (hours > 0)
|
||||
{
|
||||
result += $"{hours}小时";
|
||||
}
|
||||
if (minutes > 0 || hours > 0)
|
||||
{
|
||||
result += $"{minutes}分钟";
|
||||
}
|
||||
if (seconds > 0 || (hours == 0 && minutes == 0))
|
||||
{
|
||||
// 根据小数点后是否为0决定是否保留小数
|
||||
if (seconds % 1 == 0)
|
||||
{
|
||||
result += $"{(int)seconds}秒";
|
||||
}
|
||||
else
|
||||
{
|
||||
result += $"{seconds:F2}秒"; // 保留两位小数
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
public static string GenerHtmlByConfigGroupEntity(List<ConfigGroupEntity> configGroups) {
|
||||
(string name, Func<ConfigTask, string> value)[] colConfigs = [
|
||||
(name: "名称", value: task => task.Name)
|
||||
,(name: "开始日期", value: task => task.StartDate?.ToString("yyyy-MM-dd HH:mm:ss")??"")
|
||||
,(name: "结束日期", value: task => task.EndDate?.ToString("yyyy-MM-dd HH:mm:ss")??"")
|
||||
,(name: "耗时", value: task => ConvertSecondsToTime((task.EndDate - task.StartDate)?.TotalSeconds ?? 0))
|
||||
];
|
||||
return GenerHtmlByConfigGroupEntity(configGroups, "日志分析", colConfigs);
|
||||
}
|
||||
public static string GenerHtmlByConfigGroupEntity(List<ConfigGroupEntity> configGroups,string title, (string name, Func<ConfigTask, string> value)[] colConfigs)
|
||||
{
|
||||
|
||||
StringBuilder html = new StringBuilder();
|
||||
|
||||
// HTML头部
|
||||
html.AppendLine("<!DOCTYPE html>");
|
||||
html.AppendLine("<html lang=\"en\">");
|
||||
html.AppendLine("<head>");
|
||||
html.AppendLine(" <meta charset=\"UTF-8\">");
|
||||
html.AppendLine(" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">");
|
||||
html.AppendLine($" <title>{title}</title>");
|
||||
html.AppendLine(" <style>");
|
||||
html.AppendLine(" table { border-collapse: collapse; width: 100%; margin-bottom: 20px; }");
|
||||
html.AppendLine(" th, td { border: 1px solid black; padding: 8px; text-align: left; }");
|
||||
html.AppendLine(" th { background-color: #f2f2f2; }");
|
||||
html.AppendLine(" </style>");
|
||||
html.AppendLine("</head>");
|
||||
html.AppendLine("<body>");
|
||||
|
||||
|
||||
|
||||
// 遍历每个配置组生成表格
|
||||
foreach (var group in configGroups)
|
||||
{
|
||||
TimeSpan? timeDiff = group.EndDate - group.StartDate;
|
||||
double totalSeconds = timeDiff?.TotalSeconds ?? 0;
|
||||
|
||||
html.AppendLine($"<h2>配置组:{group.Name}({group.StartDate?.ToString("yyyy-MM-dd HH:mm:ss")}-{group.EndDate?.ToString("yyyy-MM-dd HH:mm:ss")}),耗时{ConvertSecondsToTime(totalSeconds)}</h2>");
|
||||
html.AppendLine("<table>");
|
||||
html.AppendLine(" <tr>");
|
||||
foreach (var item in colConfigs)
|
||||
{
|
||||
html.AppendLine($" <th>{item.name}</th>");
|
||||
}
|
||||
html.AppendLine(" </tr>");
|
||||
|
||||
// 合并所有任务的 Picks
|
||||
Dictionary<string, int> mergedPicks = new Dictionary<string, int>();
|
||||
foreach (var task in group.ConfigTaskList)
|
||||
{
|
||||
foreach (var pick in task.Picks)
|
||||
{
|
||||
if (!mergedPicks.ContainsKey(pick.Key))
|
||||
{
|
||||
mergedPicks[pick.Key] = 0;
|
||||
}
|
||||
mergedPicks[pick.Key] += pick.Value;
|
||||
}
|
||||
|
||||
// 任务行
|
||||
|
||||
timeDiff = task.EndDate - task.StartDate;
|
||||
totalSeconds = timeDiff?.TotalSeconds ?? 0;
|
||||
html.AppendLine(" <tr>");
|
||||
foreach (var item in colConfigs)
|
||||
{
|
||||
html.AppendLine($" <td>{item.value.Invoke(task)}</td>");
|
||||
}
|
||||
html.AppendLine(" </tr>");
|
||||
}
|
||||
|
||||
// 按 Value 倒序排列 Picks
|
||||
var sortedPicks = mergedPicks.OrderByDescending(p => p.Value)
|
||||
.Select(p => $"{p.Key} ({p.Value})");
|
||||
|
||||
// Picks 行
|
||||
html.AppendLine(" <tr>");
|
||||
html.AppendLine($" <td colspan=\"4\">拾取物: {string.Join(", ", sortedPicks)}</td>");
|
||||
html.AppendLine(" </tr>");
|
||||
|
||||
html.AppendLine("</table>");
|
||||
}
|
||||
|
||||
// HTML尾部
|
||||
html.AppendLine("</body>");
|
||||
html.AppendLine("</html>");
|
||||
|
||||
return html.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -211,6 +211,7 @@
|
||||
<ui:DropDownButton.Flyout>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="清空" Command="{Binding ClearTasksCommand}" />
|
||||
<MenuItem Header="日志分析" Command="{Binding OpenLogParseCommand}" />
|
||||
<!--<MenuItem Header="根据文件夹更新" Command="{Binding UpdateTasksCommand}" />-->
|
||||
</ContextMenu>
|
||||
</ui:DropDownButton.Flyout>
|
||||
|
||||
@@ -262,7 +262,7 @@
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
TextWrapping="Wrap">
|
||||
开启此项可制定战斗配置,关闭此项,则用于独立任务中的配置
|
||||
需开启上方路径追踪配置,开启此项可制定战斗配置,关闭此项,则用于独立任务中的配置
|
||||
</ui:TextBlock>
|
||||
<ui:ToggleSwitch Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
|
||||
@@ -29,6 +29,7 @@ using Wpf.Ui.Controls;
|
||||
using Wpf.Ui.Violeta.Controls;
|
||||
using StackPanel = Wpf.Ui.Controls.StackPanel;
|
||||
using System.Windows.Navigation;
|
||||
using BetterGenshinImpact.View.Controls.Webview;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using static Vanara.PInvoke.User32;
|
||||
using TextBox = Wpf.Ui.Controls.TextBox;
|
||||
@@ -57,6 +58,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
|
||||
private ScriptGroup? _selectedScriptGroup;
|
||||
|
||||
public readonly string ScriptGroupPath = Global.Absolute(@"User\ScriptGroup");
|
||||
public readonly string LogPath = Global.Absolute(@"log");
|
||||
|
||||
public void OnNavigatedFrom()
|
||||
{
|
||||
@@ -104,7 +106,112 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
|
||||
SelectedScriptGroup.Projects.Clear();
|
||||
WriteScriptGroup(SelectedScriptGroup);
|
||||
}
|
||||
[RelayCommand]
|
||||
private async Task OpenLogParse()
|
||||
{
|
||||
|
||||
|
||||
// 创建 StackPanel
|
||||
var stackPanel = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Vertical,
|
||||
Margin = new Thickness(10)
|
||||
};
|
||||
|
||||
// 创建 ComboBox
|
||||
var rangeComboBox = new ComboBox
|
||||
{
|
||||
Width = 200,
|
||||
Margin = new Thickness(0, 0, 0, 10),
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
var rangeComboBoxItems = new List<object>
|
||||
{
|
||||
new { Text = "当前配置组", Value = "CurrentConfig" },
|
||||
new { Text = "所有", Value = "All" }
|
||||
};
|
||||
rangeComboBox.DisplayMemberPath = "Text"; // 显示的文本
|
||||
rangeComboBox.SelectedValuePath = "Value"; // 绑定的值
|
||||
rangeComboBox.ItemsSource = rangeComboBoxItems;
|
||||
rangeComboBox.SelectedIndex = 0; // 默认选中第一个项
|
||||
stackPanel.Children.Add(rangeComboBox);
|
||||
|
||||
|
||||
var dayRangeComboBox = new ComboBox
|
||||
{
|
||||
Width = 200,
|
||||
Margin = new Thickness(0, 0, 0, 10),
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
// 定义范围选项数据
|
||||
var dayRangeComboBoxItems = new List<object>
|
||||
{
|
||||
new { Text = "3天", Value = "3" },
|
||||
new { Text = "7天", Value = "7" },
|
||||
new { Text = "所有", Value = "All" }
|
||||
};
|
||||
dayRangeComboBox.ItemsSource = dayRangeComboBoxItems;
|
||||
dayRangeComboBox.DisplayMemberPath = "Text"; // 显示的文本
|
||||
dayRangeComboBox.SelectedValuePath = "Value"; // 绑定的值
|
||||
dayRangeComboBox.SelectedIndex = 0;
|
||||
stackPanel.Children.Add(dayRangeComboBox);
|
||||
|
||||
|
||||
|
||||
//PrimaryButtonText
|
||||
var uiMessageBox = new Wpf.Ui.Controls.MessageBox
|
||||
{
|
||||
Title = "日志分析",
|
||||
Content = stackPanel,
|
||||
CloseButtonText = "取消",
|
||||
PrimaryButtonText = "确定",
|
||||
Owner = Application.Current.MainWindow,
|
||||
};
|
||||
Wpf.Ui.Controls.MessageBoxResult result = await uiMessageBox.ShowDialogAsync();
|
||||
|
||||
|
||||
if (result == Wpf.Ui.Controls.MessageBoxResult.Primary) {
|
||||
WebpageWindow win = new()
|
||||
{
|
||||
Title = "日志分析",
|
||||
Width = 800,
|
||||
Height = 600,
|
||||
Owner = Application.Current.MainWindow,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner
|
||||
};
|
||||
string rangeValue = ((dynamic)rangeComboBox.SelectedItem).Value;
|
||||
string dayRangeValue = ((dynamic)dayRangeComboBox.SelectedItem).Value;
|
||||
|
||||
|
||||
List<(string FileName, string Date)> fs = LogParse.LogParse.GetLogFiles(LogPath);
|
||||
if (dayRangeValue != "All") {
|
||||
int n = int.Parse(dayRangeValue);
|
||||
if (n < fs.Count)
|
||||
{
|
||||
fs = fs.GetRange(fs.Count - n, n);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var configGroupEntities = LogParse.LogParse.ParseFile(fs);
|
||||
if (rangeValue == "CurrentConfig") {
|
||||
//Toast.Success(_selectedScriptGroup.Name);
|
||||
configGroupEntities =configGroupEntities.Where(item => _selectedScriptGroup.Name == item.Name).ToList();
|
||||
}
|
||||
if (configGroupEntities.Count == 0)
|
||||
{
|
||||
Toast.Warning("未解析出日志记录!");
|
||||
}
|
||||
else {
|
||||
configGroupEntities.Reverse();
|
||||
win.NavigateToHtml(LogParse.LogParse.GenerHtmlByConfigGroupEntity(configGroupEntities));
|
||||
win.ShowDialog();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
private void UpdateTasks()
|
||||
{
|
||||
//PromptDialog.Prompt
|
||||
|
||||
Reference in New Issue
Block a user