支持筛选录像
This commit is contained in:
parent
3f5070df77
commit
b92d1d90ad
@ -91,6 +91,9 @@
|
||||
<PackageReference Include="ILMerge.MSBuild.Task">
|
||||
<Version>1.0.7</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NPinyin.Core">
|
||||
<Version>3.0.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="OpenSage.FileFormats.Big">
|
||||
<Version>1.0.0</Version>
|
||||
</PackageReference>
|
||||
@ -118,6 +121,7 @@
|
||||
</Compile>
|
||||
<Compile Include="MinimapReader.cs" />
|
||||
<Compile Include="ModData.cs" />
|
||||
<Compile Include="PinyinExtensions.cs" />
|
||||
<Compile Include="PlayerIdentity.cs" />
|
||||
<Compile Include="Replay.cs" />
|
||||
<Compile Include="Window1.xaml.cs">
|
||||
|
@ -43,7 +43,7 @@
|
||||
<TextBox x:Name="_replayFilterBox"
|
||||
TextChanged="ReplayFilterBox_TextChanged" />
|
||||
<TextBlock IsHitTestVisible="False"
|
||||
Text="输入录像名称或玩家名称可以筛选录像"
|
||||
Text="输入录像名称、玩家名称或地图名称等 可以筛选录像"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="10,0,0,0"
|
||||
@ -65,7 +65,9 @@
|
||||
</Grid>
|
||||
|
||||
<Button x:Name="_refreshButton" Content="刷新" Grid.Column="2" HorizontalAlignment="Left" Margin="0,11,0,0" VerticalAlignment="Top" Width="80" Click="OnReplayFolderPathBoxTextChanged"/>
|
||||
<DataGrid x:Name="_dataGrid" Grid.Row="0" Grid.Column="2" Grid.RowSpan="2" Margin="0,30,10,16" SelectionMode="Single" SelectionChanged="OnReplaySelectionChanged">
|
||||
<DataGrid x:Name="_dataGrid" Grid.Row="0" Grid.Column="2" Grid.RowSpan="2" Margin="0,30,10,16"
|
||||
SelectionMode="Single" SelectionChanged="OnReplaySelectionChanged"
|
||||
AutoGenerateColumns="False">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="文件名" Binding="{Binding Path=FileName}" Width="4*" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="玩家人数" Binding="{Binding Path=NumberOfPlayingPlayers}" Width="1.5*" IsReadOnly="True"/>
|
||||
|
@ -55,9 +55,15 @@ namespace AnotherReplayReader
|
||||
// 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 NotifyPropertyChanged<T>(T value, [CallerMemberName] string propertyName = "")
|
||||
private void SetAndNotifyPropertyChanged<T>(ref T target, T newValue, [CallerMemberName] string propertyName = "")
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
var equals = Equals(target, newValue);
|
||||
target = newValue;
|
||||
if (!equals)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public string RA3Directory { get; }
|
||||
@ -68,14 +74,14 @@ namespace AnotherReplayReader
|
||||
|
||||
public string ReplayFolderPath
|
||||
{
|
||||
get { return _replayFolderPath; }
|
||||
set { _replayFolderPath = value; NotifyPropertyChanged(_replayFolderPath); }
|
||||
get => _replayFolderPath;
|
||||
set => SetAndNotifyPropertyChanged(ref _replayFolderPath, value);
|
||||
}
|
||||
|
||||
public string ReplayDetails
|
||||
{
|
||||
get { return _replayDetails; }
|
||||
set { _replayDetails = value; NotifyPropertyChanged(_replayDetails); }
|
||||
get => _replayDetails;
|
||||
set => SetAndNotifyPropertyChanged(ref _replayDetails, value);
|
||||
}
|
||||
|
||||
public bool ReplaySelected => _currentReplay != null;
|
||||
@ -89,8 +95,7 @@ namespace AnotherReplayReader
|
||||
get => _currentReplay;
|
||||
set
|
||||
{
|
||||
_currentReplay = value;
|
||||
NotifyPropertyChanged(_currentReplay);
|
||||
SetAndNotifyPropertyChanged(ref _currentReplay, value);
|
||||
ReplayDetails = "";
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ReplayPlayable)));
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ReplaySelected)));
|
||||
@ -109,19 +114,18 @@ namespace AnotherReplayReader
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private readonly MainWindowProperties _properties = new MainWindowProperties();
|
||||
private readonly List<Replay> _replayList = new List<Replay>();
|
||||
private readonly Cache _cache = new Cache();
|
||||
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;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
DataContext = _properties;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
var handling = new bool[1] { false };
|
||||
Application.Current.Dispatcher.UnhandledException += (sender, eventArgs) =>
|
||||
{
|
||||
@ -133,11 +137,11 @@ namespace AnotherReplayReader
|
||||
Dispatcher.Invoke(() => MessageBox.Show($"错误:\r\n{eventArgs.Exception}"));
|
||||
};
|
||||
|
||||
Closing += (sender, eventArgs) => _cache.Save();
|
||||
|
||||
_playerIdentity = new PlayerIdentity(_cache);
|
||||
_minimapCache = new BigMinimapCache(_cache, _properties.RA3Directory);
|
||||
_minimapReader = new MinimapReader(_minimapCache, _properties.RA3Directory, _properties.CustomMapsDirectory, _properties.ModsDirectory);
|
||||
InitializeComponent();
|
||||
Closing += (sender, eventArgs) => _cache.Save();
|
||||
|
||||
LoadReplays();
|
||||
Task.Run(() => AutoSaveReplays(Dispatcher, _properties.RA3ReplayFolderPath));
|
||||
@ -295,8 +299,13 @@ namespace AnotherReplayReader
|
||||
{
|
||||
_image.Source = null;
|
||||
}
|
||||
_dataGrid?.Items.Clear();
|
||||
if (_dataGrid != null)
|
||||
{
|
||||
_dataGrid.ItemsSource = Array.Empty<Replay>();
|
||||
_dataGrid.Items.Refresh();
|
||||
}
|
||||
_replayList.Clear();
|
||||
_pinyinList.Clear();
|
||||
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
@ -304,9 +313,10 @@ namespace AnotherReplayReader
|
||||
return;
|
||||
}
|
||||
|
||||
var newList = await Task.Run(() =>
|
||||
var (newList, newPinyinList) = await Task.Run(() =>
|
||||
{
|
||||
var list = new List<Replay>();
|
||||
var pinyinList = new List<PinyinReplayData>();
|
||||
foreach (var replayPath in Directory.EnumerateFiles(path, "*.RA3Replay"))
|
||||
{
|
||||
if (cancelToken.IsCancellationRequested)
|
||||
@ -315,7 +325,9 @@ namespace AnotherReplayReader
|
||||
}
|
||||
try
|
||||
{
|
||||
list.Add(new Replay(replayPath));
|
||||
var replay = new Replay(replayPath);
|
||||
list.Add(replay);
|
||||
pinyinList.Add(replay.ToPinyin(_playerIdentity));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@ -324,21 +336,22 @@ namespace AnotherReplayReader
|
||||
}
|
||||
_ = Dispatcher.Invoke(() => _properties.ReplayDetails = string.Format(loadingString, _replayList.Count));
|
||||
}
|
||||
return list;
|
||||
return (list, pinyinList);
|
||||
});
|
||||
|
||||
_replayList.AddRange(newList);
|
||||
_replayList = newList;
|
||||
_pinyinList = newPinyinList;
|
||||
DisplayReplays(string.Empty, nextSelected);
|
||||
}
|
||||
|
||||
private void DisplayReplays(string message = null, string nextSelected = null)
|
||||
private void DisplayReplays(string message = null, string nextSelected = null, Replay[] filtered = null)
|
||||
{
|
||||
var filtered = _replayList;
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
filtered = filtered ?? _replayList.ToArray();
|
||||
_properties.CurrentReplay = null;
|
||||
_dataGrid.Items.Clear();
|
||||
filtered.ForEach(x => _dataGrid.Items.Add(x));
|
||||
_dataGrid.ItemsSource = filtered;
|
||||
_dataGrid.Items.Refresh();
|
||||
_properties.ReplayDetails = message;
|
||||
|
||||
if (nextSelected != null)
|
||||
@ -410,7 +423,7 @@ namespace AnotherReplayReader
|
||||
break;
|
||||
}
|
||||
var factionName = ModData.GetFaction(replay.Mod, player.FactionID).Name;
|
||||
var realName = replay.Type == ReplayType.Lan ? _playerIdentity.QueryRealName(player.PlayerIP) : string.Empty;
|
||||
var realName = replay.Type == ReplayType.Lan ? _playerIdentity.FormatRealName(player.PlayerIP) : string.Empty;
|
||||
_properties.ReplayDetails += $"{player.PlayerName + realName},{factionName}\n";
|
||||
}
|
||||
|
||||
@ -522,9 +535,39 @@ namespace AnotherReplayReader
|
||||
LoadReplays();
|
||||
}
|
||||
|
||||
private void ReplayFilterBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
|
||||
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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
76
PinyinExtensions.cs
Normal file
76
PinyinExtensions.cs
Normal file
@ -0,0 +1,76 @@
|
||||
using NPinyin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AnotherReplayReader
|
||||
{
|
||||
class PinyinReplayData
|
||||
{
|
||||
public Replay Replay { get; }
|
||||
public string Name { get; }
|
||||
public string Map { get; }
|
||||
public string Mod { get; }
|
||||
public List<string> Players { get; } = new List<string>();
|
||||
public List<string> RealNames { get; } = new List<string>();
|
||||
public List<string> Factions { get; } = new List<string>();
|
||||
|
||||
public PinyinReplayData(Replay replay, PlayerIdentity playerIdentity)
|
||||
{
|
||||
Replay = replay;
|
||||
Name = replay.FileName.ToPinyin();
|
||||
Map = replay.MapName.ToPinyin();
|
||||
Mod = replay.Mod.ModName.ToPinyin();
|
||||
foreach (var player in replay.Players)
|
||||
{
|
||||
void AddIfNotEmpty(List<string> target, string s)
|
||||
{
|
||||
s = s.ToPinyin();
|
||||
if (!string.IsNullOrEmpty(s))
|
||||
{
|
||||
target.Add(s);
|
||||
}
|
||||
}
|
||||
AddIfNotEmpty(Players, player.PlayerName);
|
||||
AddIfNotEmpty(RealNames, playerIdentity.GetRealName(player.PlayerIP));
|
||||
AddIfNotEmpty(Factions, ModData.GetFaction(replay.Mod, player.FactionID).Name);
|
||||
}
|
||||
}
|
||||
|
||||
public bool MatchPinyin(string pinyin)
|
||||
{
|
||||
return Name.ContainsIgnoreCase(pinyin)
|
||||
|| Players.FindIndex(s => s.ContainsIgnoreCase(pinyin)) != -1
|
||||
|| RealNames.FindIndex(s => s.ContainsIgnoreCase(pinyin)) != -1
|
||||
|| Map.ContainsIgnoreCase(pinyin)
|
||||
|| Mod.ContainsIgnoreCase(pinyin)
|
||||
|| Factions.FindIndex(s => s.ContainsIgnoreCase(pinyin)) != -1;
|
||||
}
|
||||
}
|
||||
|
||||
static class PinyinExtensions
|
||||
{
|
||||
public static bool ContainsIgnoreCase(this string self, string s)
|
||||
{
|
||||
return s != null && self.IndexOf(s, StringComparison.CurrentCultureIgnoreCase) != -1;
|
||||
}
|
||||
|
||||
public static string ToPinyin(this string self)
|
||||
{
|
||||
string pinyin;
|
||||
try
|
||||
{
|
||||
pinyin = Pinyin.GetPinyin(self);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return pinyin.Replace(" ", "");
|
||||
}
|
||||
|
||||
public static PinyinReplayData ToPinyin(this Replay replay, PlayerIdentity playerIdentity)
|
||||
{
|
||||
return new PinyinReplayData(replay, playerIdentity);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Web;
|
||||
using System.Web.Script.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Script.Serialization;
|
||||
|
||||
namespace AnotherReplayReader
|
||||
{
|
||||
@ -14,12 +14,12 @@ namespace AnotherReplayReader
|
||||
{
|
||||
public static string SimpleIPToString(uint ip)
|
||||
{
|
||||
return $"{ip / 256 / 256 / 256}.{(ip / 256 / 256) % 256}.{(ip / 256) % 256}.{ip % 256}";
|
||||
return $"{ip / 256 / 256 / 256}.{ip / 256 / 256 % 256}.{ip / 256 % 256}.{ip % 256}";
|
||||
}
|
||||
|
||||
public uint IP
|
||||
{
|
||||
get { return _ip; }
|
||||
get => _ip;
|
||||
set
|
||||
{
|
||||
_ip = value;
|
||||
@ -27,9 +27,20 @@ namespace AnotherReplayReader
|
||||
}
|
||||
}
|
||||
public string IPString { get; private set; }
|
||||
public string ID { get; set; }
|
||||
public string ID
|
||||
{
|
||||
get => _id;
|
||||
set
|
||||
{
|
||||
_id = value;
|
||||
_pinyin = _id.ToPinyin();
|
||||
}
|
||||
}
|
||||
public string PinyinID => _pinyin;
|
||||
|
||||
private uint _ip;
|
||||
private string _id;
|
||||
private string _pinyin;
|
||||
}
|
||||
|
||||
internal class PlayerIdentity
|
||||
@ -38,7 +49,7 @@ namespace AnotherReplayReader
|
||||
|
||||
private Cache _cache;
|
||||
private volatile IReadOnlyDictionary<uint, string> _list;
|
||||
|
||||
|
||||
public PlayerIdentity(Cache cache)
|
||||
{
|
||||
_cache = cache;
|
||||
@ -57,7 +68,7 @@ namespace AnotherReplayReader
|
||||
var data = Encoding.UTF8.GetString(bytes.Select((x, i) => (byte)(x ^ id[i % id.Length])).ToArray());
|
||||
var serializer = new JavaScriptSerializer();
|
||||
var cachedTable = serializer.Deserialize<List<IPAndPlayer>>(data);
|
||||
if(cachedTable == null)
|
||||
if (cachedTable == null)
|
||||
{
|
||||
_list = null;
|
||||
return;
|
||||
@ -90,7 +101,7 @@ namespace AnotherReplayReader
|
||||
var response = reader.ReadToEnd();
|
||||
var serializer = new JavaScriptSerializer();
|
||||
var temp = serializer.Deserialize<List<IPAndPlayer>>(response);
|
||||
if(temp == null)
|
||||
if (temp == null)
|
||||
{
|
||||
_list = null;
|
||||
return;
|
||||
@ -112,7 +123,7 @@ namespace AnotherReplayReader
|
||||
{
|
||||
var list = _list;
|
||||
|
||||
if(!IsListUsable(list))
|
||||
if (!IsListUsable(list))
|
||||
{
|
||||
return new List<IPAndPlayer>();
|
||||
}
|
||||
@ -120,7 +131,7 @@ namespace AnotherReplayReader
|
||||
return _list.Select((kv) => new IPAndPlayer { IP = kv.Key, ID = kv.Value }).OrderBy(x => x.IP).ToList();
|
||||
}
|
||||
|
||||
public string QueryRealName(uint ip)
|
||||
public string GetRealName(uint ip)
|
||||
{
|
||||
var list = _list;
|
||||
|
||||
@ -129,12 +140,26 @@ namespace AnotherReplayReader
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if(ip == 0)
|
||||
if (ip == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var name = list.TryGetValue(ip, out var realName) ? realName : IPAndPlayer.SimpleIPToString(ip);
|
||||
if (!list.TryGetValue(ip, out var name))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
public string FormatRealName(uint ip)
|
||||
{
|
||||
var name = GetRealName(ip);
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
name = IPAndPlayer.SimpleIPToString(ip);
|
||||
}
|
||||
return $"({name})";
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,6 @@ namespace AnotherReplayReader
|
||||
|
||||
public string PlayerName { get; private set; }
|
||||
public uint PlayerIP { get; private set; }
|
||||
public string PlayerRealName { get; private set; }
|
||||
public int FactionID { get; private set; }
|
||||
public int Team { get; private set; }
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using NPinyin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@ -49,9 +50,21 @@ namespace AnotherReplayReader
|
||||
|
||||
private void Display(string filter = "", string nameFilter = "")
|
||||
{
|
||||
var pinyin = nameFilter.ToPinyin();
|
||||
var newList = _identity
|
||||
.AsSortedList()
|
||||
.Where(x => x.IPString.StartsWith(filter) && x.ID.StartsWith(nameFilter, StringComparison.CurrentCultureIgnoreCase))
|
||||
.Where(x =>
|
||||
{
|
||||
if (!x.IPString.StartsWith(filter))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (x.PinyinID?.ContainsIgnoreCase(pinyin) is true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return x.ID.ContainsIgnoreCase(nameFilter);
|
||||
})
|
||||
.ToArray();
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user