From e8cdfa4687d14cd1decd2a9b9cebd165c7a326c4 Mon Sep 17 00:00:00 2001 From: Aigiz Iskuzhin Date: Sun, 8 Jan 2023 00:11:49 +0500 Subject: [PATCH] 2.0.3 --- CHANGELOG.md | 26 +- Ethereal.FA.Scmap/MapScenario.cs | 14 +- .../Converters/SingleOrArrayConverter.cs | 36 ++- .../Interfaces/IFafApiClient.cs | 6 + .../Models/Base/ApiUniversalResult.cs | 37 +++ .../Models/Enums/ApiDataType.cs | 16 +- .../Models/MapsVault/ApiMap.cs | 49 +--- .../Models/MapsVault/Mod.cs | 42 +++ Ethereal.FAF.UI.Client/App.xaml | 4 +- Ethereal.FAF.UI.Client/App.xaml.cs | 8 +- .../Ethereal.FAF.UI.Client.csproj | 6 + .../GameTemplateSelector.cs | 12 +- .../Infrastructure/MapGen/MapGenerator.cs | 2 +- .../Services/ServersManagement.cs | 10 +- Ethereal.FAF.UI.Client/Models/LocalMap.cs | 2 + .../Dictionaries/CardsDictionary.xaml | 71 +++++ .../Dictionaries/VaultDictionary.xaml | 68 ++++- .../{VaultViewModel.cs => DataViewModel.cs} | 21 +- .../ViewModels/GenerateMapsVM.cs | 31 +- .../ViewModels/LocalMapsVM.cs | 8 +- .../ViewModels/MapsHostingVM.cs | 72 ++--- .../ViewModels/MapsViewModel.cs | 39 ++- .../ViewModels/ModsViewModel.cs | 258 +++++++++++++++-- Ethereal.FAF.UI.Client/Views/ChatView.xaml | 29 +- Ethereal.FAF.UI.Client/Views/DataView.xaml | 174 ++++++++++++ Ethereal.FAF.UI.Client/Views/DataView.xaml.cs | 30 ++ Ethereal.FAF.UI.Client/Views/GamesView.xaml | 65 ++--- .../Views/Hosting/GenerateMapView.xaml | 31 +- .../Views/Hosting/HostGameView.xaml | 4 +- .../Views/Hosting/SelectLocalMapView.xaml | 41 ++- Ethereal.FAF.UI.Client/Views/MapsView.xaml | 145 +++++----- Ethereal.FAF.UI.Client/Views/ModsView.xaml | 268 +++++++++++++++--- Ethereal.FAF.UI.Client/Views/ModsView.xaml.cs | 14 +- .../Views/NavigationView.xaml | 29 +- Ethereal.FAF.UI.Client/Views/PlayersView.xaml | 28 +- Ethereal.FAF.UI.Client/Views/ProfileView.xaml | 105 +++++++ .../Views/ProfileView.xaml.cs | 28 ++ .../Views/SettingsView.xaml | 63 +--- Ethereal.FAF.UI.Client/Views/VaultView.xaml | 150 ---------- .../Views/VaultView.xaml.cs | 19 -- 40 files changed, 1432 insertions(+), 629 deletions(-) create mode 100644 Ethereal.FAF.API.Client/Models/MapsVault/Mod.cs create mode 100644 Ethereal.FAF.UI.Client/Resources/Dictionaries/CardsDictionary.xaml rename Ethereal.FAF.UI.Client/ViewModels/{VaultViewModel.cs => DataViewModel.cs} (86%) create mode 100644 Ethereal.FAF.UI.Client/Views/DataView.xaml create mode 100644 Ethereal.FAF.UI.Client/Views/DataView.xaml.cs create mode 100644 Ethereal.FAF.UI.Client/Views/ProfileView.xaml create mode 100644 Ethereal.FAF.UI.Client/Views/ProfileView.xaml.cs delete mode 100644 Ethereal.FAF.UI.Client/Views/VaultView.xaml delete mode 100644 Ethereal.FAF.UI.Client/Views/VaultView.xaml.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dc20f16..3ca2c832 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,28 @@ # Changelog +## [2.0.3](https://github.com/Eternal-ll/Ethereal-FAF-Client/releases/tag/2.0.3) + +** Changelog ** + +- Added search support by map name in Hosting tab +- Added Mods vault tab +- Changed list views in Players/Games/Maps/Hosting tab +- Fixed error when grouping by rating ranges in Players tab +- Fixed Changelog tab +- Preview of Profile tab +- Preview of Data tab + ## [2.0.2](https://github.com/Eternal-ll/Ethereal-FAF-Client/releases/tag/2.0.2-beta.1) (2023-01-03) **Changelog** -- [x] Support of multiple connected servers -- [x] Fixed Ice related problems -- [x] Fixed patch related problems -- [x] Added startup pages -- [x] Added vault page -- [x] Added maps vault page -- [x] Added [3.3.0 Ice Adapter](https://github.com/FAForever/java-ice-adapter/releases/tag/v3.3.0) +- Support of multiple connected servers +- Fixed Ice related problems +- Fixed patch related problems +- Added startup pages +- Added vault page +- Added maps vault page +- Added [3.3.0 Ice Adapter](https://github.com/FAForever/java-ice-adapter/releases/tag/v3.3.0) **FAQ** diff --git a/Ethereal.FA.Scmap/MapScenario.cs b/Ethereal.FA.Scmap/MapScenario.cs index 8461c72c..4d63fa7e 100644 --- a/Ethereal.FA.Scmap/MapScenario.cs +++ b/Ethereal.FA.Scmap/MapScenario.cs @@ -7,7 +7,7 @@ public static class FAUtils { public static double KmFromPixels(double pixels) => pixels switch { - 2096 => 80, + 4096 => 80, 2048 => 40, 1024 => 20, 512 => 10, @@ -29,15 +29,20 @@ public class MapScenario public double WidthInKm => FAUtils.KmFromPixels(Width); public double HeigthInKm => FAUtils.KmFromPixels(Height); + public string SizeLabelKm => $"{WidthInKm}x{WidthInKm} km"; + public string PathToMap { get; set; } public string PathToSave { get; set; } public string PathToScript { get; set; } + public string PathToScenario { get; set; } + public string PathToDirectory { get; set; } public double NoRushRadius { get; set; } public bool Starts { get; set; } public List Armies { get; set; } + public string BattleType { get; set; } /// /// @@ -47,7 +52,11 @@ public class MapScenario public static MapScenario FromFile(string file) { if (!File.Exists(file)) return null; - var scenario = new MapScenario(); + var scenario = new MapScenario() + { + PathToScenario = file, + PathToDirectory = Path.GetDirectoryName(file) + }; var test = File.ReadAllText(file); var regex = new Regex(@"STRING\( (.*) \)"); var match = regex.Match(test); @@ -83,6 +92,7 @@ public static MapScenario FromFile(string file) { if (teams[1] is LuaTable data) { + scenario.BattleType = data["name"].ToString(); if (data["armies"] is LuaTable armiesTable) { var armies = new List(); diff --git a/Ethereal.FAF.API.Client/Converters/SingleOrArrayConverter.cs b/Ethereal.FAF.API.Client/Converters/SingleOrArrayConverter.cs index ddb2375f..2141bafb 100644 --- a/Ethereal.FAF.API.Client/Converters/SingleOrArrayConverter.cs +++ b/Ethereal.FAF.API.Client/Converters/SingleOrArrayConverter.cs @@ -15,21 +15,29 @@ public SingleOrArrayConverter() : this(true) { } public override TCollection Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - switch (reader.TokenType) + try { - case JsonTokenType.Null: - return null; - case JsonTokenType.StartArray: - var list = new TCollection(); - while (reader.Read()) - { - if (reader.TokenType == JsonTokenType.EndArray) - break; - list.Add(JsonSerializer.Deserialize(ref reader, options)); - } - return list; - default: - return new TCollection { JsonSerializer.Deserialize(ref reader, options) }; + + switch (reader.TokenType) + { + case JsonTokenType.Null: + return null; + case JsonTokenType.StartArray: + var list = new TCollection(); + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + break; + list.Add(JsonSerializer.Deserialize(ref reader, options)); + } + return list; + default: + return new TCollection { JsonSerializer.Deserialize(ref reader, options) }; + } + } + catch (Exception ex) + { + return null; } } diff --git a/Ethereal.FAF.API.Client/Interfaces/IFafApiClient.cs b/Ethereal.FAF.API.Client/Interfaces/IFafApiClient.cs index 8f1c2941..6b289c2d 100644 --- a/Ethereal.FAF.API.Client/Interfaces/IFafApiClient.cs +++ b/Ethereal.FAF.API.Client/Interfaces/IFafApiClient.cs @@ -57,6 +57,12 @@ Task> GetMapsAsync( [Query(CollectionFormat.Csv)] string[] include = null, CancellationToken cancellationToken = default); + [Get("/data/mod")] + Task> GetModsAsync(string filter = null, Sorting sorting = null, Pagination pagination = null, + [AliasAs("include")] + [Query(CollectionFormat.Csv)] + string[] include = null, + CancellationToken cancellationToken = default); } public interface IFafContentClient { diff --git a/Ethereal.FAF.API.Client/Models/Base/ApiUniversalResult.cs b/Ethereal.FAF.API.Client/Models/Base/ApiUniversalResult.cs index 18285c1a..8bf4eb82 100644 --- a/Ethereal.FAF.API.Client/Models/Base/ApiUniversalResult.cs +++ b/Ethereal.FAF.API.Client/Models/Base/ApiUniversalResult.cs @@ -30,6 +30,43 @@ public virtual void ParseIncluded(out SortedDictionary entityP entityProperties = null; } } + public class ModsResult : ApiUniversalResult + { + public override void ParseIncluded() + { + var included = Included; + if (included is null || Data is null) return; + if (included.Length == 0 || Data.Length == 0) return; + foreach (var mod in Data) + { + if (mod.Relations is null) continue; + foreach (var relation in mod.Relations) + { + if (relation.Value.Data is null) continue; + if (relation.Value.Data.Count == 0) continue; + var entity = ApiUniversalTools.GetDataFromIncluded(included, relation.Value.Data[0].Type, relation.Value.Data[0].Id); + if (entity is null) continue; + + switch (relation.Value.Data[0].Type) + { + case ApiDataType.modVersion: + mod.LatestVersion = entity.CastTo(); + //if (map.LatestVersion.IsLegacyMap) + //{ + // map.LatestVersion.Attributes["hidden"] = "false"; + //} + continue; + case ApiDataType.player: + mod.Uploader = entity?.Attributes["login"]; + continue; + case ApiDataType.modReviewsSummary: + mod.ReviewsSummary = entity?.CastTo(); + continue; + } + } + } + } + } public class ApiMapsResult : ApiUniversalResult { public override void ParseIncluded() diff --git a/Ethereal.FAF.API.Client/Models/Enums/ApiDataType.cs b/Ethereal.FAF.API.Client/Models/Enums/ApiDataType.cs index 86972027..782f9b30 100644 --- a/Ethereal.FAF.API.Client/Models/Enums/ApiDataType.cs +++ b/Ethereal.FAF.API.Client/Models/Enums/ApiDataType.cs @@ -3,10 +3,14 @@ public enum ApiDataType : byte { mod, - modVersion, + modStatistics, modReviewsSummary, - + modVersion, + modVersionStatistics, + modVersionReview, + modVersionReviewsSummary, + map, mapStatistics, mapReviewsSummary, @@ -21,13 +25,17 @@ public enum ApiDataType : byte player, clanMembership, + avatar, avatarAssignment, + nameRecord, globalRating, ladder1v1Rating, - - + + accountLink, + + game, gamePlayerStats, diff --git a/Ethereal.FAF.API.Client/Models/MapsVault/ApiMap.cs b/Ethereal.FAF.API.Client/Models/MapsVault/ApiMap.cs index 45f0db1d..b767ce96 100644 --- a/Ethereal.FAF.API.Client/Models/MapsVault/ApiMap.cs +++ b/Ethereal.FAF.API.Client/Models/MapsVault/ApiMap.cs @@ -1,9 +1,13 @@ -using beta.Models.API.Enums; -using beta.Models.API.Universal; +using beta.Models.API.Universal; using System.Text.Json.Serialization; namespace beta.Models.API.MapsVault { + public interface IVaultEntity + { + public bool IsHidden { get; } + public bool HasVersion { get; } + } public class ApiMap : Base.ApiUniversalData { public string DisplayedName => Attributes["displayName"]; @@ -40,12 +44,12 @@ public class ApiMapPoolAssignment : Base.ApiUniversalData // JsonSerializer.Deserialize(parameters) : null; public MapVersionModel LatestVersion { get; set; } } - public class ApiMapModel : ApiMap + public class ApiMapModel : ApiMap, IVaultEntity { public string Author { get; set; } = "Unknown"; public string SmallPreviewUrl => LatestVersion is null ? - $"https://via.placeholder.com/468x60?text={DisplayedName}.png" : + $"https://via.placeholder.com/60x60?text={DisplayedName}.png" : LatestVersion.ThumbnailUrlLarge.Replace("faforever.ru", "content.faforever.ru"); @@ -54,40 +58,7 @@ public class ApiMapModel : ApiMap public MapVersionModel LatestVersion { get; set; } public MapVersionModel[] Versions { get; set; } - - - public bool TryGetRelationId(ApiDataType type, out int id) - { - id = -1; - var relations = Relations; - if (relations is not null && relations.Count > 0) - { - foreach (var relation in relations) - { - if (relation.Value.Data[0].Type == type) - { - id = relation.Value.Data[0].Id; - return true; - } - } - } - return false; - } - public Dictionary GetRelations() - { - var dic = new Dictionary(); - var relations = Relations; - if (relations is not null) - { - foreach (var relation in relations) - { - var data = relation.Value.Data; - if (data is null) continue; - if (data.Count == 0) continue; - dic.TryAdd(data[0].Type, data[0].Id); - } - } - return dic; - } + public bool IsHidden => HasVersion && LatestVersion.IsHidden; + public bool HasVersion => LatestVersion is not null; } } diff --git a/Ethereal.FAF.API.Client/Models/MapsVault/Mod.cs b/Ethereal.FAF.API.Client/Models/MapsVault/Mod.cs new file mode 100644 index 00000000..a565939f --- /dev/null +++ b/Ethereal.FAF.API.Client/Models/MapsVault/Mod.cs @@ -0,0 +1,42 @@ +using beta.Models.API.Universal; + +namespace beta.Models.API.MapsVault +{ + public class Mod : Base.ApiUniversalData, IVaultEntity + { + public string Author => Attributes["author"]; + public string Name => Attributes["displayName"]; + public bool IsRecommended => bool.Parse(Attributes["recommended"]); + public DateTime UpdateTime => DateTime.Parse(Attributes["updateTime"]); + public DateTime CreateTime => DateTime.Parse(Attributes["createTime"]); + public string Uploader { get; set; } + public ApiUniversalSummary ReviewsSummary { get; set; } + public ModVersion[] Versions { get; set; } + public ModVersion LatestVersion { get; set; } + + public bool IsHidden => HasVersion && LatestVersion.IsHidden; + public bool HasVersion => LatestVersion is not null; + } + public enum ModType + { + UI, + SIM, + } + public class ModVersion : Base.ApiUniversalData + { + public string Uid => Attributes["uid"]; + public ModType ModType => Enum.Parse(Attributes["type"]); + public int Version => int.Parse(Attributes["version"]); + public string Description => Attributes["description"]; + public string DownloadUrl => Attributes["downloadUrl"]; + public string Filename => Attributes["filename"]; + public bool IsHidden => bool.Parse(Attributes["hidden"]); + public bool IsRanked => bool.Parse(Attributes["ranked"]); + public string ThumbnailUrl => Attributes["thumbnailUrl"]; + public DateTime Created => DateTime.Parse(Attributes["createTime"]); + public DateTime Updated => DateTime.Parse(Attributes["updateTime"]); + + public ApiUniversalStatistics Statistics { get; set; } + public ApiUniversalSummary Summary { get; set; } + } +} diff --git a/Ethereal.FAF.UI.Client/App.xaml b/Ethereal.FAF.UI.Client/App.xaml index 68b4eb3a..19e6ca05 100644 --- a/Ethereal.FAF.UI.Client/App.xaml +++ b/Ethereal.FAF.UI.Client/App.xaml @@ -38,8 +38,8 @@ - + + + diff --git a/Ethereal.FAF.UI.Client/App.xaml.cs b/Ethereal.FAF.UI.Client/App.xaml.cs index 33b038e4..1302d588 100644 --- a/Ethereal.FAF.UI.Client/App.xaml.cs +++ b/Ethereal.FAF.UI.Client/App.xaml.cs @@ -129,9 +129,6 @@ private void ConfigureServices(HostBuilderContext context, IServiceCollection se services.AddScoped(); services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddTransient(); services.AddTransient(); @@ -141,6 +138,11 @@ private void ConfigureServices(HostBuilderContext context, IServiceCollection se services.AddScoped(); services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/Ethereal.FAF.UI.Client/Ethereal.FAF.UI.Client.csproj b/Ethereal.FAF.UI.Client/Ethereal.FAF.UI.Client.csproj index 422d7e4f..ddecc542 100644 --- a/Ethereal.FAF.UI.Client/Ethereal.FAF.UI.Client.csproj +++ b/Ethereal.FAF.UI.Client/Ethereal.FAF.UI.Client.csproj @@ -77,5 +77,11 @@ + + + Code + + + diff --git a/Ethereal.FAF.UI.Client/Infrastructure/DataTemplateSelectors/GameTemplateSelector.cs b/Ethereal.FAF.UI.Client/Infrastructure/DataTemplateSelectors/GameTemplateSelector.cs index 34dc2ea3..99e52da8 100644 --- a/Ethereal.FAF.UI.Client/Infrastructure/DataTemplateSelectors/GameTemplateSelector.cs +++ b/Ethereal.FAF.UI.Client/Infrastructure/DataTemplateSelectors/GameTemplateSelector.cs @@ -14,14 +14,12 @@ public class MapTemplateSelector : DataTemplateSelector public DataTemplate NoVersionTemplate { get; set; } public override DataTemplate SelectTemplate(object item, DependencyObject container) { - if (item is ApiMapModel map) + if (item is IVaultEntity entity) { - return - map.LatestVersion is null ? - NoVersionTemplate : - map.LatestVersion.IsHidden ? - HiddenTemplate : - OpenTemplate; + return + entity.HasVersion ? + entity.IsHidden ? HiddenTemplate : OpenTemplate : + NoVersionTemplate; } return base.SelectTemplate(item, container); } diff --git a/Ethereal.FAF.UI.Client/Infrastructure/MapGen/MapGenerator.cs b/Ethereal.FAF.UI.Client/Infrastructure/MapGen/MapGenerator.cs index 8563460a..49bca86b 100644 --- a/Ethereal.FAF.UI.Client/Infrastructure/MapGen/MapGenerator.cs +++ b/Ethereal.FAF.UI.Client/Infrastructure/MapGen/MapGenerator.cs @@ -78,7 +78,7 @@ private Process GetProcess(string path) Arguments = $"-jar \"{path}\" ", UseShellExecute = false, RedirectStandardOutput = true, - CreateNoWindow = true, + CreateNoWindow = false, } }; process.StartInfo.EnvironmentVariables.Add(LogsEnvironmentVariable, Configuration.GetMapGeneratorLoggingLocation()); diff --git a/Ethereal.FAF.UI.Client/Infrastructure/Services/ServersManagement.cs b/Ethereal.FAF.UI.Client/Infrastructure/Services/ServersManagement.cs index 1a17471d..20897070 100644 --- a/Ethereal.FAF.UI.Client/Infrastructure/Services/ServersManagement.cs +++ b/Ethereal.FAF.UI.Client/Infrastructure/Services/ServersManagement.cs @@ -39,7 +39,15 @@ public ServersManagement(IServiceProvider serviceProvider, IConfiguration config var servers = Configuration.GetSection("Servers").Get>(); - var gapforeverAddresses = Dns.GetHostEntry("gapforever.com").AddressList.Select(p => p.ToString()); + var gapforeverAddresses = Array.Empty(); + try + { + gapforeverAddresses = Dns.GetHostEntry("gapforever.com").AddressList.Select(p => p.ToString()).ToArray(); + } + catch + { + + } var client = httpClientFactory.CreateClient(); diff --git a/Ethereal.FAF.UI.Client/Models/LocalMap.cs b/Ethereal.FAF.UI.Client/Models/LocalMap.cs index fba1c1e1..87f5125e 100644 --- a/Ethereal.FAF.UI.Client/Models/LocalMap.cs +++ b/Ethereal.FAF.UI.Client/Models/LocalMap.cs @@ -1,4 +1,5 @@ using Ethereal.FA.Vault; +using System; namespace Ethereal.FAF.UI.Client.Models { @@ -7,5 +8,6 @@ public sealed class LocalMap public string FolderName { get; set; } public MapScenario Scenario { get; set; } public string Preview { get; set; } + public DateTime Downloaded { get; set; } } } diff --git a/Ethereal.FAF.UI.Client/Resources/Dictionaries/CardsDictionary.xaml b/Ethereal.FAF.UI.Client/Resources/Dictionaries/CardsDictionary.xaml new file mode 100644 index 00000000..c44b5d5a --- /dev/null +++ b/Ethereal.FAF.UI.Client/Resources/Dictionaries/CardsDictionary.xaml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Ethereal.FAF.UI.Client/Resources/Dictionaries/VaultDictionary.xaml b/Ethereal.FAF.UI.Client/Resources/Dictionaries/VaultDictionary.xaml index d1538e3a..b65d6193 100644 --- a/Ethereal.FAF.UI.Client/Resources/Dictionaries/VaultDictionary.xaml +++ b/Ethereal.FAF.UI.Client/Resources/Dictionaries/VaultDictionary.xaml @@ -4,24 +4,73 @@ xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" xmlns:model="clr-namespace:Ethereal.FAF.UI.Client.Models"> - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - + - - - - - - - @@ -1016,20 +1016,17 @@ LadderGameOne="{StaticResource LadderGameOne}" LadderGameCompact="{StaticResource LadderGameCompact}"/> - + + - - - + - --> + diff --git a/Ethereal.FAF.UI.Client/Views/Hosting/GenerateMapView.xaml b/Ethereal.FAF.UI.Client/Views/Hosting/GenerateMapView.xaml index ed218299..b6905ac5 100644 --- a/Ethereal.FAF.UI.Client/Views/Hosting/GenerateMapView.xaml +++ b/Ethereal.FAF.UI.Client/Views/Hosting/GenerateMapView.xaml @@ -23,12 +23,21 @@ - - - - + + + + + + + @@ -41,10 +50,12 @@ - - - + + + + + + + + + + + + + + + + + + + SelectedValue="{Binding ViewModel.CurrentPage}" Margin="4 0"/> + public partial class ModsView : INavigableView { - public ModsView(ModsViewModel vm) + public ModsView(ModsViewModel model) { - ViewModel = vm; + ViewModel = model; + + //Resources.Add("DownloadMapCommand", model.DownloadMapCommand); + + Initialized += MapsView_Initialized; InitializeComponent(); } + private void MapsView_Initialized(object sender, EventArgs e) + { + ViewModel.RunRequest(); + } + public ModsViewModel ViewModel { get; } private void Button_Click(object sender, System.Windows.RoutedEventArgs e) diff --git a/Ethereal.FAF.UI.Client/Views/NavigationView.xaml b/Ethereal.FAF.UI.Client/Views/NavigationView.xaml index 38e4f9df..af27f25a 100644 --- a/Ethereal.FAF.UI.Client/Views/NavigationView.xaml +++ b/Ethereal.FAF.UI.Client/Views/NavigationView.xaml @@ -45,6 +45,10 @@ --> + --> - - --> + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + +