From e575b5fb3bcc40f3643f8b86ea3f87afd8b564b3 Mon Sep 17 00:00:00 2001 From: iAmAsval Date: Tue, 1 Jun 2021 00:10:30 +0200 Subject: [PATCH] base map viewer v2 --- .github/workflows/main.yml | 1 - CUE4Parse | 2 +- FModel/MainWindow.xaml.cs | 4 +- FModel/ViewModels/ApiEndpoints/FModelApi.cs | 2 +- FModel/ViewModels/ApplicationViewModel.cs | 5 + FModel/ViewModels/CUE4ParseViewModel.cs | 50 +--- FModel/ViewModels/MapViewerViewModel.cs | 232 ++++-------------- FModel/Views/MapViewer.xaml | 92 +++++-- FModel/Views/MapViewer.xaml.cs | 17 ++ FModel/Views/Resources/Controls/Breadcrumb.cs | 7 + FModel/Views/Resources/Resources.xaml | 2 + FModel/Views/SettingsView.xaml.cs | 2 +- 12 files changed, 167 insertions(+), 249 deletions(-) create mode 100644 FModel/Views/Resources/Controls/Breadcrumb.cs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8c5302d2..b177e934 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,7 +12,6 @@ jobs: uses: actions/checkout@v2 with: submodules: 'true' - token: ${{ secrets.PAT_TOKEN }} - name: .NET 5 Setup uses: actions/setup-dotnet@v1 diff --git a/CUE4Parse b/CUE4Parse index bfc78f61..83465934 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit bfc78f61b6e0dfcf8cba8e227eae21947c72f077 +Subproject commit 83465934b69a1d4b339d05f0396ed1164ee77a6f diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index 639b9bea..6b9cb43b 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -57,8 +57,10 @@ namespace FModel private async void OnLoaded(object sender, RoutedEventArgs e) { +#if !DEBUG ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode); - +#endif + switch (UserSettings.Default.AesReload) { case EAesReload.Always: diff --git a/FModel/ViewModels/ApiEndpoints/FModelApi.cs b/FModel/ViewModels/ApiEndpoints/FModelApi.cs index acff5a6e..5ba50e5a 100644 --- a/FModel/ViewModels/ApiEndpoints/FModelApi.cs +++ b/FModel/ViewModels/ApiEndpoints/FModelApi.cs @@ -109,7 +109,7 @@ namespace FModel.ViewModels.ApiEndpoints { if (args != null) { - if (!args.IsUpdateAvailable) return; + if (new Version(args.CurrentVersion) == args.InstalledVersion) return; var messageBox = new MessageBoxModel { Text = $"FModel {args.CurrentVersion} is available. You are using version {args.InstalledVersion}. Do you want to update the application now?", diff --git a/FModel/ViewModels/ApplicationViewModel.cs b/FModel/ViewModels/ApplicationViewModel.cs index 38a2a71e..f2f3c699 100644 --- a/FModel/ViewModels/ApplicationViewModel.cs +++ b/FModel/ViewModels/ApplicationViewModel.cs @@ -105,6 +105,11 @@ namespace FModel.ViewModels UserSettings.Default.GameDirectory = gameLauncherViewModel.SelectedDetectedGame.GameDirectory; if (!bAlreadyLaunched || gameDirectory.Equals(gameLauncherViewModel.SelectedDetectedGame.GameDirectory)) return; + RestartWithWarning(); + } + + public void RestartWithWarning() + { MessageBox.Show("It looks like you just changed something.\nFModel will restart to apply your changes.", "Uh oh, a restart is needed", MessageBoxButton.OK, MessageBoxImage.Warning); Restart(); } diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index fe99eb1a..a160b863 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -539,9 +539,7 @@ namespace FModel.ViewModels { case UTexture2D texture: { - var filter = texture.GetOrDefault("Filter"); - var lodGroup = texture.GetOrDefault("LODGroup"); - SetImage(texture.Decode(), filter.IsNone ? null : filter.Text, lodGroup.IsNone ? null : lodGroup.Text); + SetImage(texture.Decode()); return true; } case UAkMediaAssetData: @@ -590,45 +588,17 @@ namespace FModel.ViewModels return false; } - private void SetImage(SKImage img, string filter = null, string lodGroup = null) + private void SetImage(SKImage img) { - const int UPSCALE_SIZE = 512; - SKData data; - - if ((filter != null && filter.EndsWith("TF_Nearest", StringComparison.Ordinal) || - lodGroup != null && lodGroup.EndsWith("TEXTUREGROUP_Pixels2D", StringComparison.Ordinal)) && - img.Width < UPSCALE_SIZE && img.Height < UPSCALE_SIZE) - { - var width = img.Width; - var heigth = img.Height; - - while (width < UPSCALE_SIZE && heigth < UPSCALE_SIZE) - { - width *= 2; - heigth *= 2; - } - - using var bitmap = SKBitmap.FromImage(img); - using var resized = bitmap.Resize(new SKImageInfo(width, heigth), SKFilterQuality.None); - data = resized.Encode(SKEncodedImageFormat.Png, 100); // maybe dispose 'img' at this point? - } - else - { - data = img.Encode(SKEncodedImageFormat.Png, 100); - } - - using (data) - { - using var stream = data.AsStream(false); - var image = new BitmapImage(); - image.BeginInit(); - image.CacheOption = BitmapCacheOption.OnLoad; - image.StreamSource = stream; - image.EndInit(); - image.Freeze(); - TabControl.SelectedTab.Image = image; - } + using var stream = img.Encode().AsStream(); + var image = new BitmapImage(); + image.BeginInit(); + image.CacheOption = BitmapCacheOption.OnLoad; + image.StreamSource = stream; + image.EndInit(); + image.Freeze(); + TabControl.SelectedTab.Image = image; if (UserSettings.Default.IsAutoSaveTextures) TabControl.SelectedTab.SaveImage(true); } diff --git a/FModel/ViewModels/MapViewerViewModel.cs b/FModel/ViewModels/MapViewerViewModel.cs index f121190a..13241050 100644 --- a/FModel/ViewModels/MapViewerViewModel.cs +++ b/FModel/ViewModels/MapViewerViewModel.cs @@ -1,20 +1,15 @@ -using System; +using System.Collections.Generic; using System.Threading.Tasks; -using System.Windows; using System.Windows.Media.Imaging; using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Assets.Exports.Material; using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse.UE4.Assets.Objects; -using CUE4Parse.UE4.Objects.Core.i18N; using CUE4Parse.UE4.Objects.Core.Math; -using CUE4Parse.UE4.Objects.GameplayTags; -using CUE4Parse.UE4.Objects.UObject; using FModel.Creator; -using FModel.Extensions; using FModel.Framework; using FModel.Services; using SkiaSharp; -using SkiaSharp.HarfBuzz; namespace FModel.ViewModels { @@ -22,27 +17,31 @@ namespace FModel.ViewModels { private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; - private bool _showCities; - public bool ShowCities + private int _mapIndex = -1; + public int MapIndex // 0 is BR, 1 is PR { - get => _showCities; - set => SetProperty(ref _showCities, value, nameof(ShowCities)); - } - - private bool _showLandmarks; - public bool ShowLandmarks - { - get => _showLandmarks; - set => SetProperty(ref _showLandmarks, value, nameof(ShowLandmarks)); - } - - private bool _showPatrolPaths; - public bool ShowPatrolPaths - { - get => _showPatrolPaths; - set => SetProperty(ref _showPatrolPaths, value, nameof(ShowPatrolPaths)); + get => _mapIndex; + set + { + switch (value) + { + case 0: + _mapRadius = 135000; + _mapImage = _brMiniMapImage; + break; + case 1: + _mapRadius = 51000; + _mapImage = _prMiniMapImage; + break; + } + + SetProperty(ref _mapIndex, value); + } } + private int _mapRadius; + private BitmapImage _brMiniMapImage; + private BitmapImage _prMiniMapImage; private BitmapImage _mapImage; public BitmapImage MapImage { @@ -50,55 +49,30 @@ namespace FModel.ViewModels set => SetProperty(ref _mapImage, value); } - private BitmapImage _citiesImage; - public BitmapImage CitiesImage - { - get => _citiesImage; - set => SetProperty(ref _citiesImage, value); - } - - private BitmapImage _landmarksImage; - public BitmapImage LandmarksImage - { - get => _landmarksImage; - set => SetProperty(ref _landmarksImage, value); - } - - private BitmapImage _patrolPathImage; - public BitmapImage PatrolPathImage - { - get => _patrolPathImage; - set => SetProperty(ref _patrolPathImage, value); - } - + private readonly List[] _bitmaps; // first bitmap is the displayed map, others are overlays of the map private readonly CUE4ParseViewModel _cue4Parse; public MapViewerViewModel(CUE4ParseViewModel cue4Parse) { + _bitmaps = new[] {new List(), new List()}; _cue4Parse = cue4Parse; } public async void Initialize() { Utils.Typefaces ??= new Typefaces(_cue4Parse); - if (MapImage == null && _mapBitmap == null) - { - await LoadMiniMap(); - } + await LoadBrMiniMap(); + await LoadPrMiniMap(); + MapIndex = 1; // don't forget br is selected by default + MapIndex = 0; // this will trigger the br map to be shown } public BitmapImage GetImageToSave() { - var ret = new SKBitmap(_mapBitmap.Width, _mapBitmap.Height, SKColorType.Rgba8888, SKAlphaType.Premul); + var ret = new SKBitmap(_bitmaps[MapIndex][0].Width, _bitmaps[MapIndex][0].Height, SKColorType.Rgba8888, SKAlphaType.Premul); using var c = new SKCanvas(ret); - c.DrawBitmap(_mapBitmap, 0, 0); - if (ShowCities) - c.DrawBitmap(_citiesBitmap, 0, 0); - if (ShowLandmarks) - c.DrawBitmap(_landmarksBitmap, 0, 0); - if (ShowPatrolPaths) - c.DrawBitmap(_patrolPathBitmap, 0, 0); + c.DrawBitmap(_bitmaps[MapIndex][0], 0, 0); return GetImageSource(ret); } @@ -114,21 +88,7 @@ namespace FModel.ViewModels { switch (propertyName) { - case nameof(ShowCities) when _citiesBitmap == null && _landmarksBitmap == null && _mapBitmap != null: - { - await LoadCities(); - break; - } - case nameof(ShowLandmarks) when _landmarksBitmap == null && _citiesBitmap == null && _mapBitmap != null: - { - await LoadCities(); - break; - } - case nameof(ShowPatrolPaths) when _patrolPathBitmap == null && _mapBitmap != null: - { - await LoadPatrolPaths(); - break; - } + } } @@ -144,16 +104,7 @@ namespace FModel.ViewModels image.Freeze(); return image; } - - private const int WorldRadius = 135000; - private SKBitmap _mapBitmap; - private SKBitmap _citiesBitmap; - private SKBitmap _landmarksBitmap; - private SKBitmap _patrolPathBitmap; - private readonly SKBitmap _pinBitmap = - SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/pin.png"))?.Stream); - private readonly SKBitmap _cityPinBitmap = - SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/city_pin.png"))?.Stream); + private readonly SKPaint _imagePaint = new() { IsAntialias = true, FilterQuality = SKFilterQuality.High, @@ -169,13 +120,14 @@ namespace FModel.ViewModels private FVector2D GetMapPosition(FVector vector) { - var nx = (vector.Y + WorldRadius) / (WorldRadius * 2) * _mapBitmap.Width; - var ny = (1 - (vector.X + WorldRadius) / (WorldRadius * 2)) * _mapBitmap.Height; + var nx = (vector.Y + _mapRadius) / (_mapRadius * 2) * _bitmaps[MapIndex][0].Width; + var ny = (1 - (vector.X + _mapRadius) / (_mapRadius * 2)) * _bitmaps[MapIndex][0].Height; return new FVector2D(nx, ny); } - - private async Task LoadMiniMap() + + private async Task LoadBrMiniMap() { + if (_bitmaps[0].Count > 0) return; await _threadWorkerView.Begin(_ => { if (!Utils.TryLoadObject("FortniteGame/Content/UI/IngameMap/UIMapManagerBR.Default__UIMapManagerBR_C", out UObject mapManager) || @@ -183,109 +135,23 @@ namespace FModel.ViewModels !mapMaterial.TryGetValue(out FStructFallback cachedExpressionData, "CachedExpressionData") || !cachedExpressionData.TryGetValue(out FStructFallback parameters, "Parameters") || !parameters.TryGetValue(out UTexture2D[] textureValues, "TextureValues")) return; - - _imagePaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.DisplayName; - _mapBitmap = Utils.GetBitmap(textureValues[0]); - MapImage = GetImageSource(_mapBitmap); + + _bitmaps[0].Add(Utils.GetBitmap(textureValues[0])); + _brMiniMapImage = GetImageSource(_bitmaps[0][0]); }); } - private async Task LoadCities() + private async Task LoadPrMiniMap() { + if (_bitmaps[1].Count > 0) return; await _threadWorkerView.Begin(_ => { - _citiesBitmap = new SKBitmap(_mapBitmap.Width, _mapBitmap.Height, SKColorType.Rgba8888, SKAlphaType.Premul); - _landmarksBitmap = new SKBitmap(_mapBitmap.Width, _mapBitmap.Height, SKColorType.Rgba8888, SKAlphaType.Premul); - using var cities = new SKCanvas(_citiesBitmap); - using var landmarks = new SKCanvas(_landmarksBitmap); - if (Utils.TryLoadObject("FortniteGame/Content/Quests/QuestIndicatorData.QuestIndicatorData", out UObject indicatorData) && - indicatorData.TryGetValue(out FStructFallback[] challengeMapPoiData, "ChallengeMapPoiData")) - { - foreach (var poiData in challengeMapPoiData) - { - if (!poiData.TryGetValue(out FSoftObjectPath discoveryQuest, "DiscoveryQuest") || - !poiData.TryGetValue(out FText text, "Text") || string.IsNullOrEmpty(text.Text) || - !poiData.TryGetValue(out FVector worldLocation, "WorldLocation") || - !poiData.TryGetValue(out FName discoverBackend, "DiscoverObjectiveBackendName") || - discoverBackend.Text.Contains("papaya", StringComparison.OrdinalIgnoreCase)) continue; - - var shaper = new CustomSKShaper(_imagePaint.Typeface); - var shapedText = shaper.Shape(text.Text, _imagePaint); - var vector = GetMapPosition(worldLocation); + if (!Utils.TryLoadObject("FortniteGame/Content/UI/IngameMap/UIMapManagerPapaya.Default__UIMapManagerPapaya_C", out UObject mapManager) || + !mapManager.TryGetValue(out UMaterial mapMaterial, "MapMaterial") || + mapMaterial.ReferencedTextures.Count < 1) return; - if (discoveryQuest.AssetPathName.Text.Contains("landmarks", StringComparison.OrdinalIgnoreCase)) - { - landmarks.DrawPoint(vector.X, vector.Y, _pathPaint); - landmarks.DrawShapedText(shaper, text.Text, vector.X - shapedText.Points[^1].X / 2, vector.Y - 12.5F, _imagePaint); - } - else - { - cities.DrawPoint(vector.X, vector.Y, _pathPaint); - cities.DrawBitmap(_cityPinBitmap, vector.X - 50, vector.Y - 90, _imagePaint); - cities.DrawShapedText(shaper, text.Text, vector.X - shapedText.Points[^1].X / 2, vector.Y - 12.5F, _imagePaint); - } - } - } - - CitiesImage = GetImageSource(_citiesBitmap); - LandmarksImage = GetImageSource(_landmarksBitmap); - }); - } - - private async Task LoadPatrolPaths() - { - await _threadWorkerView.Begin(_ => - { - _patrolPathBitmap = new SKBitmap(_mapBitmap.Width, _mapBitmap.Height, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(_patrolPathBitmap); - - if (!Utils.TryLoadObject("FortniteGame/Plugins/GameFeatures/NPCLibrary/Content/GameFeatureData.GameFeatureData", out UObject gameFeatureData) || - !gameFeatureData.TryGetValue(out FPackageIndex levelOverlayConfig, "LevelOverlayConfig") || - !Utils.TryGetPackageIndexExport(levelOverlayConfig, out UObject npcLibrary) || - !npcLibrary.TryGetValue(out FStructFallback[] overlayList, "OverlayList")) - return; - - foreach (var overlay in overlayList) - { - if (!overlay.TryGetValue(out FSoftObjectPath overlayWorld, "OverlayWorld")) - continue; - - var exports = Utils.LoadExports(overlayWorld.AssetPathName.Text.SubstringBeforeLast(".")); - foreach (var export in exports) - { - if (!(export is UObject uObject)) continue; - if (!uObject.ExportType.Equals("FortAthenaPatrolPath", StringComparison.OrdinalIgnoreCase) || - !uObject.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags") || - !uObject.TryGetValue(out FPackageIndex[] patrolPoints, "PatrolPoints")) continue; - - if (!Utils.TryGetPackageIndexExport(patrolPoints[0], out uObject) || - !uObject.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out uObject) || - !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var path = new SKPath(); - var vector = GetMapPosition(relativeLocation); - path.MoveTo(vector.X, vector.Y); - - for (var i = 1; i < patrolPoints.Length; i++) - { - if (!Utils.TryGetPackageIndexExport(patrolPoints[i], out uObject) || - !uObject.TryGetValue(out rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out uObject) || - !uObject.TryGetValue(out relativeLocation, "RelativeLocation")) continue; - - vector = GetMapPosition(relativeLocation); - path.LineTo(vector.X, vector.Y); - } - - path.Close(); - c.DrawPath(path, _pathPaint); - c.DrawBitmap(_pinBitmap, vector.X - 50, vector.Y - 90, _imagePaint); - c.DrawText(gameplayTags.GameplayTags[0].Text.SubstringAfterLast("."), vector.X, vector.Y - 12.5F, _imagePaint); - } - } - - PatrolPathImage = GetImageSource(_patrolPathBitmap); + _bitmaps[1].Add(Utils.GetBitmap(mapMaterial.ReferencedTextures[0] as UTexture2D)); + _prMiniMapImage = GetImageSource(_bitmaps[1][0]); }); } } diff --git a/FModel/Views/MapViewer.xaml b/FModel/Views/MapViewer.xaml index f21ef599..d31bee9a 100644 --- a/FModel/Views/MapViewer.xaml +++ b/FModel/Views/MapViewer.xaml @@ -5,9 +5,9 @@ xmlns:converters="clr-namespace:FModel.Views.Resources.Converters" xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI" xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI" - WindowStartupLocation="CenterScreen" IconVisibility="Collapsed" + WindowStartupLocation="CenterScreen" IconVisibility="Collapsed" SizeToContent="Width" ResizeMode="CanMinimize" Height="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={converters:RatioConverter}, ConverterParameter='0.75'}" - Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.50'}"> + MinWidth="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.50'}">