From 8dc9f21b9459ad7e5f8977c5b87bc6378d07f952 Mon Sep 17 00:00:00 2001 From: iAmAsval Date: Sat, 5 Jun 2021 03:25:32 +0200 Subject: [PATCH] back to a working map viewer --- FModel/MainWindow.xaml | 7 +- FModel/ViewModels/ApplicationViewModel.cs | 1 - FModel/ViewModels/MapViewerViewModel.cs | 273 ++++++++++++++++++---- FModel/Views/MapViewer.xaml | 32 +-- FModel/Views/MapViewer.xaml.cs | 6 - 5 files changed, 251 insertions(+), 68 deletions(-) diff --git a/FModel/MainWindow.xaml b/FModel/MainWindow.xaml index 0369c96e..3f76aa26 100644 --- a/FModel/MainWindow.xaml +++ b/FModel/MainWindow.xaml @@ -14,11 +14,8 @@ diff --git a/FModel/ViewModels/ApplicationViewModel.cs b/FModel/ViewModels/ApplicationViewModel.cs index f2f3c699..bfc7aca2 100644 --- a/FModel/ViewModels/ApplicationViewModel.cs +++ b/FModel/ViewModels/ApplicationViewModel.cs @@ -44,7 +44,6 @@ namespace FModel.ViewModels set { SetProperty(ref _status, value); - RaisePropertyChanged(nameof(TitleExtra)); IsReady = Status != EStatusKind.Loading && Status != EStatusKind.Stopping; } } diff --git a/FModel/ViewModels/MapViewerViewModel.cs b/FModel/ViewModels/MapViewerViewModel.cs index adee6a51..3dfaa8e7 100644 --- a/FModel/ViewModels/MapViewerViewModel.cs +++ b/FModel/ViewModels/MapViewerViewModel.cs @@ -1,16 +1,21 @@ -using System.Collections.Generic; -using System.Linq; +using System; +using System.Collections.Generic; using System.Threading.Tasks; 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,32 +27,42 @@ namespace FModel.ViewModels public class MapViewerViewModel : ViewModel { - private const string _FIRST_BITMAP = "mapBase"; private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; - private int _mapIndex = -1; - public int MapIndex // 0 is BR, 1 is PR + #region BINDINGS + + private bool _poisCheck; + public bool PoisCheck { - get => _mapIndex; - set - { - switch (value) - { - case 0: - _mapRadius = 135000; - _mapImage = _brMiniMapImage; - break; - case 1: - _mapRadius = 51000; - _mapImage = _prMiniMapImage; - break; - } - - SetProperty(ref _mapIndex, value); - } + get => _poisCheck; + set => SetProperty(ref _poisCheck, value, "ApolloGameplay_MapPois"); + } + + private bool _brLandmarksCheck; + public bool BrLandmarksCheck + { + get => _brLandmarksCheck; + set => SetProperty(ref _brLandmarksCheck, value, "ApolloGameplay_MapLandmarks"); + } + + private bool _patrolsPathCheck; + public bool PatrolsPathCheck + { + get => _patrolsPathCheck; + set => SetProperty(ref _patrolsPathCheck, value, "ApolloGameplay_PatrolsPath"); + } + + private bool _prLandmarksCheck; + public bool PrLandmarksCheck + { + get => _prLandmarksCheck; + set => SetProperty(ref _prLandmarksCheck, value, "PapayaGameplay_MapLandmarks"); } - private int _mapRadius; + #endregion + + #region BITMAP IMAGES + private BitmapImage _brMiniMapImage; private BitmapImage _prMiniMapImage; private BitmapImage _mapImage; @@ -56,6 +71,9 @@ namespace FModel.ViewModels get => _mapImage; set => SetProperty(ref _mapImage, value); } + + private BitmapImage _brLayerImage; + private BitmapImage _prLayerImage; private BitmapImage _layerImage; public BitmapImage LayerImage { @@ -63,6 +81,23 @@ namespace FModel.ViewModels set => SetProperty(ref _layerImage, value); } + private const int _widthHeight = 2048; + private const int _brRadius = 135000; + private const int _prRadius = 51000; + private int _mapIndex; + public int MapIndex // 0 is BR, 1 is PR + { + get => _mapIndex; + set + { + SetProperty(ref _mapIndex, value); + TriggerChange(); + } + } + + #endregion + + private const string _FIRST_BITMAP = "MapCheck"; private readonly Dictionary[] _bitmaps; // first bitmap is the displayed map, others are overlays of the map private readonly CUE4ParseViewModel _cue4Parse; @@ -79,43 +114,66 @@ namespace FModel.ViewModels public async void Initialize() { Utils.Typefaces ??= new Typefaces(_cue4Parse); + _imagePaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.DisplayName; 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 + TriggerChange(); } - public BitmapImage GetImageToSave() + public BitmapImage GetImageToSave() => GetImageSource(GetLayerBitmap(true)); + private SKBitmap GetLayerBitmap(bool withMap) { - var ret = new SKBitmap(_bitmaps[MapIndex][_FIRST_BITMAP].Layer.Width, _bitmaps[MapIndex][_FIRST_BITMAP].Layer.Height, SKColorType.Rgba8888, SKAlphaType.Premul); + var ret = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); using var c = new SKCanvas(ret); - foreach (var layer in _bitmaps[MapIndex].Values.Where(layer => layer.IsEnabled)) + foreach (var (key, value) in _bitmaps[MapIndex]) { - c.DrawBitmap(layer.Layer, 0, 0); + if (!value.IsEnabled || !withMap && key == _FIRST_BITMAP) continue; + c.DrawBitmap(value.Layer, new SKRect(0, 0, _widthHeight, _widthHeight)); } - return GetImageSource(ret); + return ret; } - public async Task GenericToggle(string key, bool enabled) + protected override bool SetProperty(ref T storage, T value, string propertyName = null) // don't delete, else nothing will update for some reason + { + var ret = base.SetProperty(ref storage, value, propertyName); + if (bool.TryParse(value.ToString(), out var b)) GenericToggle(propertyName, b); + return ret; + } + + private async void GenericToggle(string key, bool enabled) { if (_bitmaps[MapIndex].TryGetValue(key, out var layer) && layer.Layer != null) { layer.IsEnabled = enabled; } - else // load layer + else if (enabled) // load layer { switch (key) { - + case "ApolloGameplay_MapPois": + case "ApolloGameplay_MapLandmarks": + case "PapayaGameplay_MapLandmarks": + await LoadQuestIndicatorData(); + break; + case "ApolloGameplay_PatrolsPath": + await LoadPatrolsPath(); + break; } + _bitmaps[MapIndex][key].IsEnabled = true; } - } - protected override bool SetProperty(ref T storage, T value, string propertyName = null) // don't delete, else nothing will update for some reason - { - return base.SetProperty(ref storage, value, propertyName); + switch (MapIndex) + { + case 0: + _brLayerImage = GetImageSource(GetLayerBitmap(false)); + break; + case 1: + _prLayerImage = GetImageSource(GetLayerBitmap(false)); + break; + } + TriggerChange(); } private BitmapImage GetImageSource(SKBitmap bitmap) @@ -130,6 +188,24 @@ namespace FModel.ViewModels image.Freeze(); return image; } + + private void TriggerChange() + { + switch (_mapIndex) + { + case 0: + _mapImage = _brMiniMapImage; + _layerImage = _brLayerImage; + break; + case 1: + _mapImage = _prMiniMapImage; + _layerImage = _prLayerImage; + break; + } + + RaisePropertyChanged(nameof(MapImage)); + RaisePropertyChanged(nameof(LayerImage)); + } private readonly SKPaint _imagePaint = new() { @@ -140,20 +216,22 @@ namespace FModel.ViewModels private readonly SKPaint _pathPaint = new() { IsAntialias = true, FilterQuality = SKFilterQuality.High, IsStroke = true, - Style = SKPaintStyle.Stroke, StrokeWidth = 10, Color = SKColors.Red, + Style = SKPaintStyle.Stroke, StrokeWidth = 5, Color = SKColors.Red, ImageFilter = SKImageFilter.CreateDropShadow(4, 4, 8, 8, SKColors.Black) }; - private FVector2D GetMapPosition(FVector vector) + private FVector2D GetMapPosition(FVector vector, int mapRadius) { - var nx = (vector.Y + _mapRadius) / (_mapRadius * 2) * _bitmaps[MapIndex][_FIRST_BITMAP].Layer.Width; - var ny = (1 - (vector.X + _mapRadius) / (_mapRadius * 2)) * _bitmaps[MapIndex][_FIRST_BITMAP].Layer.Height; + var nx = (vector.Y + mapRadius) / (mapRadius * 2) * _widthHeight; + var ny = (1 - (vector.X + mapRadius) / (mapRadius * 2)) * _widthHeight; return new FVector2D(nx, ny); } private async Task LoadBrMiniMap() { - if (_bitmaps[0].TryGetValue(_FIRST_BITMAP, out var layer) && layer.Layer != null) return; + if (_bitmaps[0].TryGetValue(_FIRST_BITMAP, out var brMap) && brMap.Layer != null) + return; // if map already loaded + await _threadWorkerView.Begin(_ => { if (!Utils.TryLoadObject("FortniteGame/Content/UI/IngameMap/UIMapManagerBR.Default__UIMapManagerBR_C", out UObject mapManager) || @@ -169,7 +247,9 @@ namespace FModel.ViewModels private async Task LoadPrMiniMap() { - if (_bitmaps[1].TryGetValue(_FIRST_BITMAP, out var layer) && layer.Layer != null) return; + if (_bitmaps[1].TryGetValue(_FIRST_BITMAP, out var prMap) && prMap.Layer != null) + return; // if map already loaded + await _threadWorkerView.Begin(_ => { if (!Utils.TryLoadObject("FortniteGame/Content/UI/IngameMap/UIMapManagerPapaya.Default__UIMapManagerPapaya_C", out UObject mapManager) || @@ -180,5 +260,112 @@ namespace FModel.ViewModels _prMiniMapImage = GetImageSource(_bitmaps[1][_FIRST_BITMAP].Layer); }); } + + private async Task LoadQuestIndicatorData() + { + await _threadWorkerView.Begin(_ => + { + var poisBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); + var brLandmarksBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); + var prLandmarksBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); + using var pois = new SKCanvas(poisBitmap); + using var brLandmarks = new SKCanvas(brLandmarksBitmap); + using var prLandmarks = new SKCanvas(prLandmarksBitmap); + + if (Utils.TryLoadObject("FortniteGame/Content/Quests/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")) continue; + + var shaper = new CustomSKShaper(_imagePaint.Typeface); + var shapedText = shaper.Shape(text.Text, _imagePaint); + + if (discoverBackend.Text.Contains("papaya", StringComparison.OrdinalIgnoreCase)) + { + var vector = GetMapPosition(worldLocation, _prRadius); + prLandmarks.DrawPoint(vector.X, vector.Y, _pathPaint); + prLandmarks.DrawShapedText(shaper, text.Text, vector.X - shapedText.Points[^1].X / 2, vector.Y - 12.5F, _imagePaint); + } + else if (discoveryQuest.AssetPathName.Text.Contains("landmarks", StringComparison.OrdinalIgnoreCase)) + { + var vector = GetMapPosition(worldLocation, _brRadius); + brLandmarks.DrawPoint(vector.X, vector.Y, _pathPaint); + brLandmarks.DrawShapedText(shaper, text.Text, vector.X - shapedText.Points[^1].X / 2, vector.Y - 12.5F, _imagePaint); + } + else + { + var vector = GetMapPosition(worldLocation, _brRadius); + pois.DrawPoint(vector.X, vector.Y, _pathPaint); + pois.DrawShapedText(shaper, text.Text, vector.X - shapedText.Points[^1].X / 2, vector.Y - 12.5F, _imagePaint); + } + } + } + + _bitmaps[0]["ApolloGameplay_MapPois"] = new MapLayer {Layer = poisBitmap, IsEnabled = false}; + _bitmaps[0]["ApolloGameplay_MapLandmarks"] = new MapLayer {Layer = brLandmarksBitmap, IsEnabled = false}; + _bitmaps[1]["PapayaGameplay_MapLandmarks"] = new MapLayer {Layer = prLandmarksBitmap, IsEnabled = false}; + }); + } + + private async Task LoadPatrolsPath() + { + await _threadWorkerView.Begin(_ => + { + var patrolsPathBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(patrolsPathBitmap); + + 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, _brRadius); + 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, _brRadius); + path.LineTo(vector.X, vector.Y); + } + + path.Close(); + c.DrawPath(path, _pathPaint); + c.DrawText(gameplayTags.GameplayTags[0].Text.SubstringAfterLast("."), vector.X, vector.Y - 12.5F, _imagePaint); + } + } + + _bitmaps[0]["ApolloGameplay_PatrolsPath"] = new MapLayer {Layer = patrolsPathBitmap, IsEnabled = false}; + }); + } } } \ No newline at end of file diff --git a/FModel/Views/MapViewer.xaml b/FModel/Views/MapViewer.xaml index b3097de6..2f467041 100644 --- a/FModel/Views/MapViewer.xaml +++ b/FModel/Views/MapViewer.xaml @@ -1,6 +1,7 @@  - - - + + + - - - - - - - - - - + + + + + + + + + + + diff --git a/FModel/Views/MapViewer.xaml.cs b/FModel/Views/MapViewer.xaml.cs index 74507a26..16da3b4b 100644 --- a/FModel/Views/MapViewer.xaml.cs +++ b/FModel/Views/MapViewer.xaml.cs @@ -74,11 +74,5 @@ namespace FModel.Views break; } } - - private async void ToggleOnChange(object sender, RoutedEventArgs e) - { - if (sender is not CheckBox checkBox) return; - await _applicationView.MapViewer.GenericToggle(checkBox.Content.ToString(), checkBox.IsChecked ?? true); - } } } \ No newline at end of file