diff --git a/BetterGenshinImpact/Core/Recorder/GlobalKeyMouseRecord.cs b/BetterGenshinImpact/Core/Recorder/GlobalKeyMouseRecord.cs index eb2fed89..6b8c667c 100644 --- a/BetterGenshinImpact/Core/Recorder/GlobalKeyMouseRecord.cs +++ b/BetterGenshinImpact/Core/Recorder/GlobalKeyMouseRecord.cs @@ -25,7 +25,7 @@ public class GlobalKeyMouseRecord : Singleton { private readonly ILogger _logger = App.GetLogger(); - private KeyMouseRecorder? _recorder; + private KeyMouseRecorderJsonLine? _recorder; // private SharpAviRecorder _sharpAviRecorder; @@ -100,21 +100,21 @@ public class GlobalKeyMouseRecord : Singleton _ffmpegRecorder.Start(); _directInputMonitor.Start(); - _recorder = new KeyMouseRecorder(); + _recorder = new KeyMouseRecorderJsonLine(fileName); Status = KeyMouseRecorderStatus.Recording; _logger.LogInformation("录制:{Text}", "已启动"); } - public string StopRecord() + public void StopRecord() { if (Status != KeyMouseRecorderStatus.Recording) { throw new InvalidOperationException("未处于录制中状态,无法停止"); } - var macro = _recorder?.ToJsonMacro() ?? string.Empty; + // var macro = _recorder?.ToJsonMacro() ?? string.Empty; _recorder = null; _directInputMonitor?.Stop(); _directInputMonitor?.Dispose(); @@ -133,7 +133,7 @@ public class GlobalKeyMouseRecord : Singleton Status = KeyMouseRecorderStatus.Stop; - return macro; + // return macro; } public void Tick(object? sender, EventArgs e) diff --git a/BetterGenshinImpact/Core/Recorder/KeyMouseMacroPlayerJsonLine.cs b/BetterGenshinImpact/Core/Recorder/KeyMouseMacroPlayerJsonLine.cs new file mode 100644 index 00000000..6c382c93 --- /dev/null +++ b/BetterGenshinImpact/Core/Recorder/KeyMouseMacroPlayerJsonLine.cs @@ -0,0 +1,239 @@ +using BetterGenshinImpact.Core.Recorder.Model; +using BetterGenshinImpact.Core.Simulator; +using BetterGenshinImpact.GameTask; +using BetterGenshinImpact.GameTask.Common; +using BetterGenshinImpact.GameTask.Common.Map; +using BetterGenshinImpact.Helpers; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Fischless.WindowsInput; +using Vanara.PInvoke; +using Wpf.Ui.Violeta.Controls; + +namespace BetterGenshinImpact.Core.Recorder; + +public class KeyMouseMacroPlayerJsonLine +{ + public static async Task PlayMacro(string path, CancellationToken ct, bool withDelay = true) + { + if (!TaskContext.Instance().IsInitialized) + { + Toast.Warning("请先在启动页,启动截图器再使用本功能"); + return; + } + + var file = new FileInfo(path); + var infoJson = await File.ReadAllTextAsync(file.FullName, ct); + var info = JsonSerializer.Deserialize(infoJson, KeyMouseRecorder.JsonOptions) ?? throw new Exception("Failed to deserialize macro info"); + + var macroEventsLines = await File.ReadAllTextAsync(Path.Combine(file.Directory!.FullName, "macroEvents.jsonl"), ct); + List macroEvents = new(); + foreach (var line in macroEventsLines.Split("\n")) + { + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + macroEvents.Add(JsonSerializer.Deserialize(line, KeyMouseRecorder.JsonOptions) ?? throw new Exception("Failed to deserialize macro event")); + } + // MacroEvents 需要以实际时间进行排序 + macroEvents.Sort((a, b) => a.Time.CompareTo(b.Time)); + // 删除为负数的时间 + macroEvents.RemoveAll(m => m.Time < 0); + + var script = new KeyMouseScript + { + Info = info, + MacroEvents = macroEvents + }; + script.Adapt(TaskContext.Instance().SystemInfo.CaptureAreaRect, TaskContext.Instance().DpiScale); + script.Merge(); + SystemControl.ActivateWindow(); + + if (withDelay) + { + for (var i = 2; i >= 1; i--) + { + TaskControl.Logger.LogInformation("{Sec}秒后进行重放...", i); + await Task.Delay(1000, ct); + } + + TaskControl.Logger.LogInformation("开始重放"); + } + + await PlayMacro(script.MacroEvents, ct); + } + + public static async Task PlayMacro(List macroEvents, CancellationToken ct) + { + WorkingArea = PrimaryScreen.WorkingArea; + var startTime = DateTime.UtcNow; + foreach (var e in macroEvents) + { + if (ct.IsCancellationRequested) + { + return; + } + + var timeToWait = (int)(e.Time - (DateTime.UtcNow - startTime).TotalMilliseconds); + if (timeToWait < 0) + { + TaskControl.Logger.LogWarning("无法原速重放事件{Event},落后{TimeToWait}ms", e.Type.ToString(), -timeToWait); + } + else + { + await Task.Delay(timeToWait, ct); + } + + switch (e.Type) + { + case MacroEventType.KeyDown: + var vkDown = (User32.VK)e.KeyCode!; + if (InputBuilder.IsExtendedKey(vkDown)) + { + Simulation.SendInput.Keyboard.KeyDown(false, vkDown); + } + else + { + Simulation.SendInput.Keyboard.KeyDown(vkDown); + } + + break; + case MacroEventType.KeyUp: + + var vkUp = (User32.VK)e.KeyCode!; + if (InputBuilder.IsExtendedKey(vkUp)) + { + Simulation.SendInput.Keyboard.KeyUp(false, vkUp); + } + else + { + Simulation.SendInput.Keyboard.KeyUp(vkUp); + } + + break; + + case MacroEventType.MouseDown: + var buttonMouseDown = Enum.Parse(e.MouseButton!); + var xMouseDown = ToVirtualDesktopX(e.MouseX); + var yMouseDown = ToVirtualDesktopY(e.MouseY); + switch (buttonMouseDown) + { + case MouseButtons.Left: + Simulation.SendInput.Mouse.MoveMouseTo(xMouseDown, yMouseDown).LeftButtonDown(); + break; + + case MouseButtons.Right: + Simulation.SendInput.Mouse.MoveMouseTo(xMouseDown, yMouseDown).RightButtonDown(); + break; + + case MouseButtons.Middle: + Simulation.SendInput.Mouse.MoveMouseTo(xMouseDown, yMouseDown).MiddleButtonDown(); + break; + + case MouseButtons.None: + break; + + case MouseButtons.XButton1: + break; + + case MouseButtons.XButton2: + break; + + default: + throw new ArgumentOutOfRangeException(); + } + + break; + + case MacroEventType.MouseUp: + var buttonMouseUp = Enum.Parse(e.MouseButton!); + var xMouseUp = ToVirtualDesktopX(e.MouseX); + var yMouseUp = ToVirtualDesktopY(e.MouseY); + switch (buttonMouseUp) + { + case MouseButtons.Left: + Simulation.SendInput.Mouse.MoveMouseTo(xMouseUp, yMouseUp).LeftButtonUp(); + break; + + case MouseButtons.Right: + Simulation.SendInput.Mouse.MoveMouseTo(xMouseUp, yMouseUp).RightButtonUp(); + break; + + case MouseButtons.Middle: + Simulation.SendInput.Mouse.MoveMouseTo(xMouseUp, yMouseUp).MiddleButtonUp(); + break; + + case MouseButtons.None: + break; + + case MouseButtons.XButton1: + break; + + case MouseButtons.XButton2: + break; + + default: + throw new ArgumentOutOfRangeException(); + } + + break; + + case MacroEventType.MouseMoveTo: + Simulation.SendInput.Mouse.MoveMouseTo(ToVirtualDesktopX(e.MouseX), ToVirtualDesktopY(e.MouseY)); + break; + + case MacroEventType.MouseWheel: + var num = (int)(e.MouseY / 120.0); + if (num != 0) + { + // 不支持多次的场景,但是不会出现这种情况 + Simulation.SendInput.Mouse.VerticalScroll(num); + } + + break; + + case MacroEventType.MouseMoveBy: + if (e.CameraOrientation != null) + { + var cao = CameraOrientation.Compute(TaskControl.CaptureToRectArea().SrcMat); + var diff = ((int)Math.Round(cao) - (int)e.CameraOrientation + 180) % 360 - 180; + diff += diff < -180 ? 360 : 0; + //过滤一下特别大的角度偏差 + if (diff != 0 && diff < 8 && diff > -8) + { + TaskControl.Logger.LogWarning("视角重放偏差{diff}°,尝试修正", diff); + e.MouseX -= diff; + } + } + + Simulation.SendInput.Mouse.MoveMouseBy(e.MouseX, e.MouseY); + break; + + default: + throw new ArgumentOutOfRangeException(); + } + } + } + + public static Size WorkingArea; + + public static double ToVirtualDesktopX(int x) + { + return x * 65535 * 1d / WorkingArea.Width; + } + + public static double ToVirtualDesktopY(int y) + { + return y * 65535 * 1d / WorkingArea.Height; + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Core/Recorder/KeyMouseRecorderJsonLine.cs b/BetterGenshinImpact/Core/Recorder/KeyMouseRecorderJsonLine.cs index 6041d81d..89327d6d 100644 --- a/BetterGenshinImpact/Core/Recorder/KeyMouseRecorderJsonLine.cs +++ b/BetterGenshinImpact/Core/Recorder/KeyMouseRecorderJsonLine.cs @@ -14,7 +14,9 @@ using System.Text.Json.Serialization; using System.Threading.Channels; using System.Threading.Tasks; using System.Windows.Forms; +using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.Helpers; +using Microsoft.Extensions.Logging; using Vanara.PInvoke; namespace BetterGenshinImpact.Core.Recorder; @@ -40,15 +42,21 @@ public class KeyMouseRecorderJsonLine DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; - public KeyMouseRecorderJsonLine(string path) + public KeyMouseRecorderJsonLine(string folderName) { + var path = Global.Absolute($@"User\KeyMouseScript\{folderName}\"); + + DateTime startTime = DateTime.UtcNow; var bootTime = EnvironmentUtil.LastBootUpTime(); - if (bootTime != null) + if (bootTime == null) { - throw new Exception("无法获取系统启动时间"); + TaskControl.Logger.LogWarning("无法获取系统启动时间"); + } + else + { + startTime = bootTime.Value + TimeSpan.FromMilliseconds(StartTick); } - var startTime = bootTime! + TimeSpan.FromMilliseconds(StartTick); var rect = TaskContext.Instance().SystemInfo.CaptureAreaRect; Info = new KeyMouseScriptInfo { @@ -58,7 +66,7 @@ public class KeyMouseRecorderJsonLine Height = rect.Height, RecordDpi = TaskContext.Instance().DpiScale, StartTime = $"{startTime:yyyy-MM-dd HH:mm:ss:ffff}", - StartTimeUnixTimestamp = (startTime - new DateTime(1970, 1, 1)).Value.TotalNanoseconds.ToString("F0") + StartTimeUnixTimestamp = (startTime - new DateTime(1970, 1, 1)).TotalNanoseconds.ToString("F0") }; var infoJson = JsonSerializer.Serialize(Info, JsonOptions); File.WriteAllText(Path.Combine(path, "info.json"), infoJson); @@ -174,13 +182,12 @@ public class KeyMouseRecorderJsonLine public void MouseMoveBy(MouseState state, uint tick, bool save = false) { - var mEvent = new MacroEvent { Type = MacroEventType.MouseMoveBy, MouseX = state.X, MouseY = state.Y, - Time = tick - 5 + Time = tick - 5 - StartTick }; AddEvent(_mouseMoveByMacroEventsChannel, mEvent); if (save) diff --git a/BetterGenshinImpact/Core/Recorder/Model/KeyMouseScript.cs b/BetterGenshinImpact/Core/Recorder/Model/KeyMouseScript.cs index 4a451473..a68634db 100644 --- a/BetterGenshinImpact/Core/Recorder/Model/KeyMouseScript.cs +++ b/BetterGenshinImpact/Core/Recorder/Model/KeyMouseScript.cs @@ -79,7 +79,7 @@ public class KeyMouseScript break; case MacroEventType.MouseMoveBy: - if (macroEvent.Time - currentMerge.Time > mergedEventTimeMax) + if (macroEvent.Time - currentMerge.Time > 10) { mergedMacroEvents.Add(currentMerge); currentMerge = macroEvent; diff --git a/BetterGenshinImpact/Core/Video/FfmpegRecorder.cs b/BetterGenshinImpact/Core/Video/FfmpegRecorder.cs index a778f122..169ad500 100644 --- a/BetterGenshinImpact/Core/Video/FfmpegRecorder.cs +++ b/BetterGenshinImpact/Core/Video/FfmpegRecorder.cs @@ -52,7 +52,7 @@ public class FfmpegRecorder // _process.OutputDataReceived += (sender, args) => { Debug.WriteLine(args.Data); }; _process.ErrorDataReceived += (sender, args) => { - Debug.WriteLine(args.Data); + TaskControl.Logger.LogDebug(args.Data); if (string.IsNullOrEmpty(_startTime)) { if (args.Data != null && args.Data.Contains("start")) diff --git a/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs index 0e2962a5..7874565d 100644 --- a/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs @@ -107,9 +107,9 @@ public partial class KeyMouseRecordPageViewModel : ObservableObject, INavigation { try { - var macro = GlobalKeyMouseRecord.Instance.StopRecord(); + GlobalKeyMouseRecord.Instance.StopRecord(); // Genshin Copilot Macro - File.WriteAllText(Path.Combine(scriptPath, $"{fileName}/{fileName}.json"), macro); + // File.WriteAllText(Path.Combine(scriptPath, $"{fileName}/{fileName}.json"), macro); // 刷新ListView InitScriptListViewData(); IsRecording = false; @@ -125,14 +125,13 @@ public partial class KeyMouseRecordPageViewModel : ObservableObject, INavigation [RelayCommand] public async Task OnStartPlay(string path) { - var name = new FileInfo(path).Name; + var file = new FileInfo(path); + var name = file.Name; _logger.LogInformation("重放开始:{Name}", name); try { - var s = await File.ReadAllTextAsync(path); - await new TaskRunner(DispatcherTimerOperationEnum.UseSelfCaptureImage) - .RunAsync(async () => await KeyMouseMacroPlayer.PlayMacro(s, CancellationContext.Instance.Cts.Token)); + .RunAsync(async () => await KeyMouseMacroPlayerJsonLine.PlayMacro(path, CancellationContext.Instance.Cts.Token)); } catch (Exception e) {