改了一亿个东西,修了一亿个 BUG
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
using Microsoft.Win32;
|
||||
using AnotherReplayReader.ReplayFile;
|
||||
using AnotherReplayReader.Utils;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
@@ -17,28 +20,14 @@ namespace AnotherReplayReader
|
||||
{
|
||||
public MainWindowProperties()
|
||||
{
|
||||
string userDataLeafName = null;
|
||||
string replayFolderName = null;
|
||||
try
|
||||
{
|
||||
using (var view32 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32))
|
||||
using (var ra3Key = view32.OpenSubKey(@"Software\Electronic Arts\Electronic Arts\Red Alert 3", false))
|
||||
{
|
||||
RA3Directory = ra3Key?.GetValue("Install Dir") as string;
|
||||
userDataLeafName = ra3Key?.GetValue("UserDataLeafName") as string;
|
||||
replayFolderName = ra3Key?.GetValue("ReplayFolderName") as string;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.Instance.DebugMessage += $"获取注册表项时出现错误:{e}\r\n";
|
||||
}
|
||||
|
||||
const RegistryHive hklm = RegistryHive.LocalMachine;
|
||||
RA3Directory = RegistryUtils.RetrieveInRa3(hklm, "Install Dir");
|
||||
string? userDataLeafName = RegistryUtils.RetrieveInRa3(hklm, "UserDataLeafName");
|
||||
string? replayFolderName = RegistryUtils.RetrieveInRa3(hklm, "ReplayFolderName");
|
||||
if (string.IsNullOrWhiteSpace(userDataLeafName))
|
||||
{
|
||||
userDataLeafName = "Red Alert 3";
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(replayFolderName))
|
||||
{
|
||||
replayFolderName = "Replays";
|
||||
@@ -50,23 +39,21 @@ namespace AnotherReplayReader
|
||||
ModsDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), userDataLeafName, "Mods");
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
// This method is called by the Set accessor of each property.
|
||||
// The CallerMemberName attribute that is applied to the optional propertyName
|
||||
// parameter causes the property name of the caller to be substituted as an argument.
|
||||
private void SetAndNotifyPropertyChanged<T>(ref T target, T newValue, [CallerMemberName] string propertyName = "")
|
||||
{
|
||||
var equals = Equals(target, newValue);
|
||||
target = newValue;
|
||||
if (!equals)
|
||||
if (!Equals(target, newValue))
|
||||
{
|
||||
target = newValue;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public string RA3Directory { get; }
|
||||
public string? RA3Directory { get; }
|
||||
public string RA3ReplayFolderPath { get; }
|
||||
public string RA3Exe => Path.Combine(RA3Directory, "RA3.exe");
|
||||
public string CustomMapsDirectory { get; }
|
||||
@@ -78,19 +65,19 @@ namespace AnotherReplayReader
|
||||
set => SetAndNotifyPropertyChanged(ref _replayFolderPath, value);
|
||||
}
|
||||
|
||||
public string ReplayDetails
|
||||
public string? ReplayDetails
|
||||
{
|
||||
get => _replayDetails;
|
||||
set => SetAndNotifyPropertyChanged(ref _replayDetails, value);
|
||||
}
|
||||
|
||||
public bool ReplaySelected => _currentReplay != null;
|
||||
public bool ReplaySelected => CurrentReplay != null;
|
||||
|
||||
public bool ReplayPlayable => _currentReplay?.HasFooter == true && _currentReplay?.HasCommentator == true && (RA3Directory != null) && File.Exists(RA3Exe);
|
||||
public bool ReplayPlayable => CurrentReplay?.HasFooter == true && CurrentReplay?.HasCommentator == true && (RA3Directory != null) && File.Exists(RA3Exe);
|
||||
|
||||
public bool ReplayDamaged => _currentReplay?.HasFooter == false;
|
||||
public bool ReplayDamaged => CurrentReplay?.HasFooter == false;
|
||||
|
||||
public Replay CurrentReplay
|
||||
public Replay? CurrentReplay
|
||||
{
|
||||
get => _currentReplay;
|
||||
set
|
||||
@@ -103,9 +90,9 @@ namespace AnotherReplayReader
|
||||
}
|
||||
}
|
||||
|
||||
private string _replayFolderPath;
|
||||
private string _replayDetails;
|
||||
private Replay _currentReplay;
|
||||
private string _replayFolderPath = null!;
|
||||
private string? _replayDetails;
|
||||
private Replay? _currentReplay;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -113,346 +100,236 @@ namespace AnotherReplayReader
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private readonly MainWindowProperties _properties = new MainWindowProperties();
|
||||
private readonly Cache _cache = new Cache();
|
||||
private readonly TaskQueue _taskQueue;
|
||||
private readonly MainWindowProperties _properties = new();
|
||||
private readonly Cache _cache = new();
|
||||
private readonly PlayerIdentity _playerIdentity;
|
||||
private readonly BigMinimapCache _minimapCache;
|
||||
private readonly MinimapReader _minimapReader;
|
||||
private List<Replay> _replayList = new List<Replay>();
|
||||
private List<PinyinReplayData> _pinyinList = new List<PinyinReplayData>();
|
||||
private CancellationTokenSource _loadReplaysToken;
|
||||
private readonly CancelManager _cancelLoadReplays = new();
|
||||
private readonly CancelManager _cancelFilterReplays = new();
|
||||
private readonly CancelManager _cancelDisplayReplays = new();
|
||||
private ReplayPinyinList _replayList;
|
||||
private ImmutableArray<string> _filterStrings = ImmutableArray<string>.Empty;
|
||||
private ImmutableArray<Replay> _filteredReplays = ImmutableArray<Replay>.Empty;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
DataContext = _properties;
|
||||
|
||||
var handling = new bool[1] { false };
|
||||
Application.Current.Dispatcher.UnhandledException += (sender, eventArgs) =>
|
||||
{
|
||||
if (handling == null || handling[0])
|
||||
{
|
||||
return;
|
||||
}
|
||||
handling[0] = true;
|
||||
Dispatcher.Invoke(() => MessageBox.Show($"错误:\r\n{eventArgs.Exception}"));
|
||||
};
|
||||
|
||||
_taskQueue = new(Dispatcher);
|
||||
_playerIdentity = new PlayerIdentity(_cache);
|
||||
_minimapCache = new BigMinimapCache(_cache, _properties.RA3Directory);
|
||||
_minimapReader = new MinimapReader(_minimapCache, _properties.RA3Directory, _properties.CustomMapsDirectory, _properties.ModsDirectory);
|
||||
_minimapCache = new BigMinimapCache(_properties.RA3Directory);
|
||||
_minimapReader = new MinimapReader(_minimapCache, _properties.CustomMapsDirectory, _properties.ModsDirectory);
|
||||
_replayList = new(_playerIdentity);
|
||||
|
||||
DataContext = _properties;
|
||||
InitializeComponent();
|
||||
Closing += (sender, eventArgs) => _cache.Save();
|
||||
|
||||
LoadReplays();
|
||||
Task.Run(() => AutoSaveReplays(Dispatcher, _properties.RA3ReplayFolderPath));
|
||||
Closing += (sender, eventArgs) =>
|
||||
{
|
||||
_cache.Save();
|
||||
Application.Current.Shutdown();
|
||||
};
|
||||
}
|
||||
|
||||
private static async Task AutoSaveReplays(Dispatcher dispatcher, string replayFolderPath)
|
||||
private async void OnMainWindowLoaded(object sender, EventArgs e)
|
||||
{
|
||||
const string ourPrefix = "自动保存";
|
||||
var errorMessageCount = 0;
|
||||
// filename and last write time
|
||||
var previousFiles = new Dictionary<string, DateTime>(StringComparer.OrdinalIgnoreCase);
|
||||
// filename and file size
|
||||
var lastReplays = new Dictionary<string, long>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var changed = (from fileName in Directory.GetFiles(replayFolderPath, "*.RA3Replay")
|
||||
let info = new FileInfo(fileName)
|
||||
where !info.Name.StartsWith(ourPrefix)
|
||||
where !previousFiles.ContainsKey(info.FullName) || previousFiles[info.FullName] != info.LastWriteTimeUtc
|
||||
select info).ToList();
|
||||
|
||||
foreach (var info in changed)
|
||||
{
|
||||
previousFiles[info.FullName] = info.LastWriteTimeUtc;
|
||||
}
|
||||
|
||||
var replays = changed.Select(info =>
|
||||
{
|
||||
Debug.Instance.DebugMessage += $"正在尝试检测已更改的文件:{info.FullName}\r\n";
|
||||
try
|
||||
{
|
||||
using (var stream = info.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
{
|
||||
return new Replay(info.FullName, stream);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.Instance.DebugMessage += $"自动保存录像/检测录像更改时发生错误:{e}\r\n";
|
||||
return null;
|
||||
}
|
||||
}).Where(replay => replay != null);
|
||||
|
||||
var newLastReplays = from replay in replays
|
||||
let threshold = Math.Abs((DateTime.UtcNow - replay.Date).TotalSeconds)
|
||||
let endDate = replay.Date.Add(replay.Length ?? TimeSpan.Zero)
|
||||
let endThreshold = Math.Abs((DateTime.UtcNow - endDate).TotalSeconds)
|
||||
where threshold < 40 || endThreshold < 40
|
||||
select replay;
|
||||
|
||||
var toBeChecked = newLastReplays.ToDictionary(replay => replay.Path, StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var savedLastReplay in lastReplays.Keys)
|
||||
{
|
||||
if (!toBeChecked.ContainsKey(savedLastReplay))
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var stream = File.Open(savedLastReplay, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
{
|
||||
toBeChecked.Add(savedLastReplay, new Replay(savedLastReplay, stream));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.Instance.DebugMessage += $"自动保存录像/检测录像更改时发生错误:{e}\r\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kv in toBeChecked)
|
||||
{
|
||||
Debug.Instance.DebugMessage += $"正在检测录像更改:{kv.Key}\r\n";
|
||||
var replay = kv.Value;
|
||||
if (lastReplays.TryGetValue(kv.Key, out var fileSize))
|
||||
{
|
||||
if (fileSize == replay.Size)
|
||||
{
|
||||
// skip if size is not changed
|
||||
Debug.Instance.DebugMessage += $"已跳过未更改的录像:{kv.Key}\r\n";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Debug.Instance.DebugMessage += $"将会自动保存已更改的录像:{kv.Key}\r\n";
|
||||
lastReplays[kv.Key] = replay.Size;
|
||||
|
||||
var date = replay.Date;
|
||||
|
||||
var playerString = $"{replay.NumberOfPlayingPlayers}名玩家";
|
||||
if (replay.NumberOfPlayingPlayers <= 2)
|
||||
{
|
||||
var playingPlayers = from player in replay.Players
|
||||
let faction = ModData.GetFaction(replay.Mod, player.FactionID)
|
||||
where faction.Kind != FactionKind.Observer
|
||||
select $"{player.PlayerName}({faction.Name})";
|
||||
playerString = playingPlayers.Aggregate(string.Empty, (x, y) => x + y);
|
||||
}
|
||||
|
||||
var dateString = $"{date.Year}{date.Month:D2}{date.Day:D2}_{date.Hour:D2}{date.Minute:D2}{date.Second:D2}";
|
||||
var destinationPath = Path.Combine(replayFolderPath, $"{ourPrefix}-{playerString}{dateString}.RA3Replay");
|
||||
try
|
||||
{
|
||||
File.Copy(replay.Path, destinationPath, true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception($"复制文件({replay.Path} -> {destinationPath})失败:{e.Message}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var errorString = $"自动保存录像时出现错误:\r\n{e}\r\n";
|
||||
Debug.Instance.DebugMessage += errorString;
|
||||
if (Interlocked.Increment(ref errorMessageCount) == 1)
|
||||
{
|
||||
_ = dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
MessageBox.Show(errorString);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Decrement(ref errorMessageCount);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(10 * 1000);
|
||||
}
|
||||
Debug.Initialize();
|
||||
ReplayAutoSaver.SpawnAutoSaveReplaysTask(_properties.RA3ReplayFolderPath);
|
||||
var token = _cancelLoadReplays.ResetAndGetToken(CancellationToken.None);
|
||||
await _taskQueue.Enqueue(() => LoadReplays(null, token), token);
|
||||
}
|
||||
|
||||
private async void LoadReplays(string nextSelected = null)
|
||||
private async Task LoadReplays(string? nextSelected, CancellationToken cancelToken)
|
||||
{
|
||||
const string loadingString = "正在加载录像列表,请稍候… 已加载 {0} 个录像";
|
||||
if (!IsLoaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
var filterToken = _cancelFilterReplays.ResetAndGetToken(cancelToken);
|
||||
_cancelDisplayReplays.Reset(_cancelFilterReplays.Token);
|
||||
|
||||
try
|
||||
{
|
||||
_loadReplaysToken?.Cancel();
|
||||
}
|
||||
catch (AggregateException e)
|
||||
{
|
||||
Debug.Instance.DebugMessage += $"Cancellation failed: {e}";
|
||||
}
|
||||
_loadReplaysToken?.Dispose();
|
||||
_loadReplaysToken = new CancellationTokenSource();
|
||||
var cancelToken = _loadReplaysToken.Token;
|
||||
var path = _properties.ReplayFolderPath;
|
||||
|
||||
if (_image != null)
|
||||
{
|
||||
_image.Source = null;
|
||||
}
|
||||
if (_dataGrid != null)
|
||||
_properties.CurrentReplay = null;
|
||||
_image.Source = null;
|
||||
if (_dataGrid.Items.Count > 0)
|
||||
{
|
||||
_dataGrid.ItemsSource = Array.Empty<Replay>();
|
||||
_dataGrid.Items.Refresh();
|
||||
}
|
||||
_replayList.Clear();
|
||||
_pinyinList.Clear();
|
||||
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
_replayList = new(_playerIdentity);
|
||||
|
||||
var path = _properties.ReplayFolderPath;
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
DisplayReplays("这个文件夹并不存在。", nextSelected);
|
||||
await FilterReplays("这个文件夹并不存在。", nextSelected, filterToken);
|
||||
return;
|
||||
}
|
||||
|
||||
var (newList, newPinyinList) = await Task.Run(() =>
|
||||
var result = await Task.Run(() =>
|
||||
{
|
||||
var list = new List<Replay>();
|
||||
var pinyinList = new List<PinyinReplayData>();
|
||||
var clock = new Stopwatch();
|
||||
clock.Start();
|
||||
foreach (var replayPath in Directory.EnumerateFiles(path, "*.RA3Replay"))
|
||||
{
|
||||
if (cancelToken.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
var replay = new Replay(replayPath);
|
||||
list.Add(replay);
|
||||
pinyinList.Add(replay.ToPinyin(_playerIdentity));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.Instance.DebugMessage += $"Uncaught exception when loading replay list: \r\n{exception}\r\n";
|
||||
continue;
|
||||
}
|
||||
_ = Dispatcher.Invoke(() => _properties.ReplayDetails = string.Format(loadingString, _replayList.Count));
|
||||
}
|
||||
return (list, pinyinList);
|
||||
});
|
||||
|
||||
_replayList = newList;
|
||||
_pinyinList = newPinyinList;
|
||||
DisplayReplays(string.Empty, nextSelected);
|
||||
}
|
||||
|
||||
private void DisplayReplays(string message = null, string nextSelected = null, Replay[] filtered = null)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
filtered = filtered ?? _replayList.ToArray();
|
||||
_properties.CurrentReplay = null;
|
||||
_dataGrid.ItemsSource = filtered;
|
||||
_dataGrid.Items.Refresh();
|
||||
_properties.ReplayDetails = message;
|
||||
|
||||
if (nextSelected != null)
|
||||
{
|
||||
for (var i = 0; i < _dataGrid.Items.Count; ++i)
|
||||
if (clock.ElapsedMilliseconds > 300)
|
||||
{
|
||||
if (_dataGrid.Items[i] is Replay replay && replay.Path.Equals(nextSelected, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_dataGrid.SelectedIndex = i;
|
||||
OnReplaySelectionChanged(null, null);
|
||||
break;
|
||||
}
|
||||
var text = $"正在加载录像列表,请稍候… 已加载 {list.Count} 个录像";
|
||||
Dispatcher.Invoke(() => _properties.ReplayDetails = text);
|
||||
clock.Restart();
|
||||
}
|
||||
}
|
||||
});
|
||||
return new ReplayPinyinList(list.ToImmutableArray(), _playerIdentity);
|
||||
}, cancelToken);
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
_replayList = result;
|
||||
await FilterReplays(string.Empty, nextSelected, filterToken);
|
||||
}
|
||||
|
||||
private void OnReplayFolderPathBoxTextChanged(object sender, EventArgs e)
|
||||
private async Task FilterReplays(string message, string? nextSelected, CancellationToken cancelToken)
|
||||
{
|
||||
LoadReplays();
|
||||
if (!IsLoaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
_cancelDisplayReplays.Reset(cancelToken);
|
||||
_filteredReplays = _replayList.Replays;
|
||||
|
||||
_properties.CurrentReplay = null;
|
||||
_dataGrid.SelectedItem = null;
|
||||
_image.Source = null;
|
||||
|
||||
if (_filterStrings.Any())
|
||||
{
|
||||
_properties.ReplayDetails = "正在筛选符合条件的录像…";
|
||||
if (_dataGrid.Items.Count > 0)
|
||||
{
|
||||
_dataGrid.ItemsSource = Array.Empty<Replay>();
|
||||
_dataGrid.Items.Refresh();
|
||||
}
|
||||
|
||||
var (pinyins, list) = (_filterStrings, _replayList.Pinyins);
|
||||
var result = await Task.Run(() =>
|
||||
{
|
||||
var query = from replay in list.AsParallel().WithCancellation(cancelToken)
|
||||
where pinyins.Any(pinyin => replay.MatchPinyin(pinyin))
|
||||
select replay.Replay;
|
||||
return query.ToImmutableArray();
|
||||
}, cancelToken);
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
_filteredReplays = result;
|
||||
}
|
||||
|
||||
_properties.CurrentReplay = null;
|
||||
_dataGrid.SelectedItem = null;
|
||||
_dataGrid.ItemsSource = _filteredReplays;
|
||||
_dataGrid.Items.Refresh();
|
||||
_properties.ReplayDetails = message;
|
||||
|
||||
if (nextSelected is not null)
|
||||
{
|
||||
for (var i = 0; i < _dataGrid.Items.Count; ++i)
|
||||
{
|
||||
|
||||
if (_dataGrid.Items[i] is Replay replay && replay.PathEquals(nextSelected))
|
||||
{
|
||||
_dataGrid.SelectedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnReplaySelectionChanged(object sender, EventArgs e)
|
||||
private async Task DisplayReplayDetail(Replay replay, string replayDetails, CancellationToken cancelToken)
|
||||
{
|
||||
_properties.CurrentReplay = _dataGrid.SelectedItem as Replay;
|
||||
if (_properties.CurrentReplay == null)
|
||||
if (!IsLoaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
_properties.CurrentReplay = null;
|
||||
_image.Source = null;
|
||||
_properties.ReplayDetails = replayDetails;
|
||||
|
||||
// 开始获取小地图
|
||||
var mapPath = replay.MapPath;
|
||||
var minimapTask = _minimapReader.TryReadTargaAsync(replay);
|
||||
|
||||
// 解析录像内容
|
||||
try
|
||||
{
|
||||
replay = await Task.Run(() => new Replay(replay.Path, true));
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
catch (Exception e) when (e is not OperationCanceledException)
|
||||
{
|
||||
Debug.Instance.DebugMessage += $"Uncaught exception when loading replay body: \r\n{e}\r\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// 假如小地图变了(这……),那么重新加载小地图
|
||||
if (replay.MapPath != mapPath)
|
||||
{
|
||||
minimapTask.Forget();
|
||||
minimapTask = _minimapReader.TryReadTargaAsync(replay);
|
||||
}
|
||||
var newDetails = replay.GetDetails(_playerIdentity);
|
||||
if (_properties.ReplayDetails != newDetails)
|
||||
{
|
||||
if (_replayList.Replays.FindIndex(r => r.PathEquals(replay)) is int index)
|
||||
{
|
||||
_replayList = _replayList.SetItem(index, replay.CloneHeader());
|
||||
var token = _cancelFilterReplays.ResetAndGetToken(_cancelLoadReplays.Token);
|
||||
await FilterReplays(newDetails, replay.Path, token);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_properties.CurrentReplay = replay;
|
||||
_properties.ReplayDetails = newDetails;
|
||||
|
||||
try
|
||||
{
|
||||
var newSource = await minimapTask;
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
_image.Source = newSource;
|
||||
/* _image.Width = source.Width;
|
||||
_image.Height = source.Height; */
|
||||
}
|
||||
catch (Exception e) when (e is not OperationCanceledException)
|
||||
{
|
||||
Debug.Instance.DebugMessage += $"Uncaught exception when loading minimap: \r\n{e}\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnReplayFolderPathBoxTextChanged(object sender, EventArgs e)
|
||||
{
|
||||
var token = _cancelLoadReplays.ResetAndGetToken(CancellationToken.None);
|
||||
await _taskQueue.Enqueue(() => LoadReplays(null, token), token);
|
||||
}
|
||||
|
||||
private async void OnReplaySelectionChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (_dataGrid.SelectedItem is not Replay replay)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dispatcher.Invoke(() => { _image.Source = null; });
|
||||
|
||||
string GetSizeString(double size)
|
||||
{
|
||||
if (size > 1024 * 1024)
|
||||
{
|
||||
return $"{Math.Round(size / (1024 * 1024), 2)}MB";
|
||||
}
|
||||
return $"{Math.Round(size / 1024)}KB";
|
||||
}
|
||||
|
||||
const string formatA = "文件名:{0}\n大小:{1}\n";
|
||||
const string formatB = "地图:{0}\n日期:{1}\n长度:{2}\n";
|
||||
const string formatC = "录像类别:{0}\n这个文件是{1}保存的\n";
|
||||
const string playerListTitle = "玩家列表:\n";
|
||||
var replay = _properties.CurrentReplay;
|
||||
var sizeString = GetSizeString(replay.Size);
|
||||
var lengthString = "录像已损坏,请先修复录像";
|
||||
if (replay.HasFooter)
|
||||
{
|
||||
lengthString = $"{replay.Length}";
|
||||
}
|
||||
|
||||
var replaySaver = "[无法获取保存录像的玩家]";
|
||||
try
|
||||
{
|
||||
replaySaver = replay.ReplaySaver.PlayerName;
|
||||
}
|
||||
catch { }
|
||||
|
||||
_properties.ReplayDetails = string.Format(formatA, replay.FileName, sizeString);
|
||||
_properties.ReplayDetails += string.Format(formatB, replay.MapName, replay.Date, lengthString);
|
||||
_properties.ReplayDetails += string.Format(formatC, replay.TypeString, replaySaver);
|
||||
_properties.ReplayDetails += playerListTitle;
|
||||
foreach (var player in replay.Players)
|
||||
{
|
||||
if (player == replay.Players.Last() && player.PlayerName.Equals("post Commentator"))
|
||||
{
|
||||
break;
|
||||
}
|
||||
var factionName = ModData.GetFaction(replay.Mod, player.FactionID).Name;
|
||||
var realName = replay.Type == ReplayType.Lan ? _playerIdentity.FormatRealName(player.PlayerIP) : string.Empty;
|
||||
_properties.ReplayDetails += $"{player.PlayerName + realName},{factionName}\n";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var mapPath = replay.MapPath.TrimEnd('/');
|
||||
var mapName = mapPath.Substring(mapPath.LastIndexOf('/') + 1);
|
||||
var minimapPath = $"{mapPath}/{mapName}_art.tga";
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
var source = _minimapReader.TryReadTarga(minimapPath, replay.Mod);
|
||||
_image.Source = source;
|
||||
/*_image.Width = source.Width;
|
||||
_image.Height = source.Height;*/
|
||||
});
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.Instance.DebugMessage += $"Uncaught exception when loading minimap: \r\n{exception}\r\n";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
replay.ParseBody();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.Instance.DebugMessage += $"Uncaught exception when loading replay body: \r\n{exception}\r\n";
|
||||
}
|
||||
var token = _cancelDisplayReplays.ResetAndGetToken(_cancelFilterReplays.Token);
|
||||
_properties.ReplayDetails = replay.GetDetails(_playerIdentity);
|
||||
await _taskQueue.Enqueue(() => DisplayReplayDetail(replay, _properties.ReplayDetails, token), token);
|
||||
}
|
||||
|
||||
private void OnAboutButton_Click(object sender, RoutedEventArgs e)
|
||||
@@ -477,7 +354,6 @@ namespace AnotherReplayReader
|
||||
var fileName = openFileDialog.FileName;
|
||||
var directoryName = Path.GetDirectoryName(fileName);
|
||||
_properties.ReplayFolderPath = directoryName;
|
||||
LoadReplays(fileName);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
@@ -488,7 +364,7 @@ namespace AnotherReplayReader
|
||||
|
||||
private void OnDetailsButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var detailsWindow = new APM(_properties.CurrentReplay, _playerIdentity);
|
||||
var detailsWindow = new ApmWindow(_properties.CurrentReplay!, _playerIdentity);
|
||||
detailsWindow.ShowDialog();
|
||||
}
|
||||
|
||||
@@ -499,12 +375,12 @@ namespace AnotherReplayReader
|
||||
Process.Start(_properties.RA3Exe, $" -replayGame \"{_properties.CurrentReplay}\" ");
|
||||
}
|
||||
|
||||
private void OnFixReplayButton_Click(object sender, RoutedEventArgs e)
|
||||
private async void OnFixReplayButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var replay = _properties.CurrentReplay ?? throw new InvalidOperationException("Trying to fix a null replay");
|
||||
|
||||
try
|
||||
{
|
||||
var replay = _properties.CurrentReplay;
|
||||
|
||||
var saveFileDialog = new SaveFileDialog
|
||||
{
|
||||
Filter = "红警3录像文件 (*.RA3Replay)|*.RA3Replay|所有文件 (*.*)|*.*",
|
||||
@@ -516,11 +392,9 @@ namespace AnotherReplayReader
|
||||
var result = saveFileDialog.ShowDialog(this);
|
||||
if (result == true)
|
||||
{
|
||||
using (var file = saveFileDialog.OpenFile())
|
||||
using (var writer = new BinaryWriter(file))
|
||||
{
|
||||
writer.WriteReplay(replay);
|
||||
}
|
||||
using var file = saveFileDialog.OpenFile();
|
||||
using var writer = new BinaryWriter(file);
|
||||
writer.Write(replay);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
@@ -528,42 +402,21 @@ namespace AnotherReplayReader
|
||||
MessageBox.Show($"无法修复录像:\r\n{exception}");
|
||||
}
|
||||
|
||||
LoadReplays();
|
||||
var token = _cancelLoadReplays.ResetAndGetToken(CancellationToken.None);
|
||||
await _taskQueue.Enqueue(() => LoadReplays(replay.Path, token), token);
|
||||
}
|
||||
|
||||
private async void ReplayFilterBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var pinyins = _replayFilterBox.Text.Split(',', ' ', ',')
|
||||
.Select(x => x.ToPinyin())
|
||||
.Where(x => !string.IsNullOrEmpty(x))
|
||||
.ToArray();
|
||||
if (!pinyins.Any())
|
||||
{
|
||||
DisplayReplays();
|
||||
return;
|
||||
}
|
||||
var tokenSource = _loadReplaysToken;
|
||||
var token = tokenSource.Token;
|
||||
var list = _pinyinList.ToArray();
|
||||
var result = await Task.Run(() =>
|
||||
{
|
||||
var query = from replay in list.AsParallel()
|
||||
where pinyins.Any(pinyin => replay.MatchPinyin(pinyin))
|
||||
select replay.Replay;
|
||||
return query.ToArray();
|
||||
});
|
||||
if (token.IsCancellationRequested || _loadReplaysToken != tokenSource)
|
||||
{
|
||||
return;
|
||||
}
|
||||
DisplayReplays(null, null, result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.Instance.DebugMessage += $"Exception when filtering replays: {ex}";
|
||||
}
|
||||
_filterStrings = _replayFilterBox.Text
|
||||
.Split(',', ' ', ',')
|
||||
.Select(x => x.ToPinyin())
|
||||
.Where(x => !string.IsNullOrEmpty(x))
|
||||
.ToImmutableArray()!;
|
||||
var currentReplayPath = _properties?.CurrentReplay?.Path;
|
||||
|
||||
var token = _cancelFilterReplays.ResetAndGetToken(_cancelLoadReplays.Token);
|
||||
await _taskQueue.Enqueue(() => FilterReplays(string.Empty, currentReplayPath, token), token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user