FModel/FModel/ViewModels/MapViewerViewModel.cs
2021-09-19 22:35:04 +02:00

1012 lines
48 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
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.UObject;
using FModel.Creator;
using FModel.Extensions;
using FModel.Framework;
using FModel.Services;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.ViewModels
{
public class MapLayer
{
public SKBitmap Layer;
public bool IsEnabled;
}
public enum EWaypointType
{
Parkour,
TimeTrials
}
public class MapViewerViewModel : ViewModel
{
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private DiscordHandler _discordHandler => DiscordService.DiscordHandler;
#region BINDINGS
private bool _brPois;
public bool BrPois
{
get => _brPois;
set => SetProperty(ref _brPois, value, "ApolloGameplay_MapPois");
}
private bool _brLandmarks;
public bool BrLandmarks
{
get => _brLandmarks;
set => SetProperty(ref _brLandmarks, value, "ApolloGameplay_MapLandmarks");
}
private bool _brTagsLocation;
public bool BrTagsLocation
{
get => _brTagsLocation;
set => SetProperty(ref _brTagsLocation, value, "ApolloGameplay_TagsLocation");
}
private bool _brPatrolsPath;
public bool BrPatrolsPath
{
get => _brPatrolsPath;
set => SetProperty(ref _brPatrolsPath, value, "ApolloGameplay_PatrolsPath");
}
private bool _brUpgradeBenches;
public bool BrUpgradeBenches
{
get => _brUpgradeBenches;
set => SetProperty(ref _brUpgradeBenches, value, "ApolloGameplay_UpgradeBenches");
}
private bool _brPhonebooths;
public bool BrPhonebooths
{
get => _brPhonebooths;
set => SetProperty(ref _brPhonebooths, value, "ApolloGameplay_Phonebooths");
}
private bool _brVendingMachines;
public bool BrVendingMachines
{
get => _brVendingMachines;
set => SetProperty(ref _brVendingMachines, value, "ApolloGameplay_VendingMachines");
}
private bool _brFireflies;
public bool BrFireflies
{
get => _brFireflies;
set => SetProperty(ref _brFireflies, value, "ApolloGameplay_Fireflies");
}
private bool _brCorruptionZones;
public bool BrCorruptionZones
{
get => _brCorruptionZones;
set => SetProperty(ref _brCorruptionZones, value, "ApolloGameplay_CorruptionZones");
}
private bool _brCubeMovements;
public bool BrCubeMovements
{
get => _brCubeMovements;
set => SetProperty(ref _brCubeMovements, value, "ApolloGameplay_CubeMovements");
}
private bool _prLandmarks;
public bool PrLandmarks
{
get => _prLandmarks;
set => SetProperty(ref _prLandmarks, value, "PapayaGameplay_MapLandmarks");
}
private bool _prCannonball;
public bool PrCannonball
{
get => _prCannonball;
set => SetProperty(ref _prCannonball, value, "PapayaGameplay_CannonballGame");
}
private bool _prSkydive;
public bool PrSkydive
{
get => _prSkydive;
set => SetProperty(ref _prSkydive, value, "PapayaGameplay_SkydiveGame");
}
private bool _prShootingTargets;
public bool PrShootingTargets
{
get => _prShootingTargets;
set => SetProperty(ref _prShootingTargets, value, "PapayaGameplay_ShootingTargets");
}
private bool _prParkour;
public bool PrParkour
{
get => _prParkour;
set => SetProperty(ref _prParkour, value, "PapayaGameplay_ParkourGame");
}
private bool _prTimeTrials;
public bool PrTimeTrials
{
get => _prTimeTrials;
set => SetProperty(ref _prTimeTrials, value, "PapayaGameplay_TimeTrials");
}
private bool _prVendingMachines;
public bool PrVendingMachines
{
get => _prVendingMachines;
set => SetProperty(ref _prVendingMachines, value, "PapayaGameplay_VendingMachines");
}
private bool _prMusicBlocks;
public bool PrMusicBlocks
{
get => _prMusicBlocks;
set => SetProperty(ref _prMusicBlocks, value, "PapayaGameplay_MusicBlocks");
}
#endregion
#region BITMAP IMAGES
private BitmapImage _brMiniMapImage;
private BitmapImage _prMiniMapImage;
private BitmapImage _mapImage;
public BitmapImage MapImage
{
get => _mapImage;
set => SetProperty(ref _mapImage, value);
}
private BitmapImage _brLayerImage;
private BitmapImage _prLayerImage;
private BitmapImage _layerImage;
public BitmapImage LayerImage
{
get => _layerImage;
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<string, MapLayer>[] _bitmaps; // first bitmap is the displayed map, others are overlays of the map
private readonly CUE4ParseViewModel _cue4Parse;
public MapViewerViewModel(CUE4ParseViewModel cue4Parse)
{
_bitmaps = new[]
{
new Dictionary<string, MapLayer>(),
new Dictionary<string, MapLayer>()
};
_cue4Parse = cue4Parse;
}
public async void Initialize()
{
Utils.Typefaces ??= new Typefaces(_cue4Parse);
_textPaint.Typeface = _fillPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.DisplayName;
await LoadBrMiniMap();
await LoadPrMiniMap();
TriggerChange();
}
public BitmapImage GetImageToSave() => GetImageSource(GetLayerBitmap(true));
private SKBitmap GetLayerBitmap(bool withMap)
{
var ret = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
foreach (var (key, value) in _bitmaps[MapIndex])
{
if (!value.IsEnabled || !withMap && key == _FIRST_BITMAP)
continue;
SKPaint p = null;
if (key == "ApolloGameplay_CorruptionZones")
p = new SKPaint { BlendMode = SKBlendMode.Color };
c.DrawBitmap(value.Layer, new SKRect(0, 0, _widthHeight, _widthHeight), p);
}
return ret;
}
protected override bool SetProperty<T>(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 if (enabled) // load layer
{
switch (key)
{
case "ApolloGameplay_MapPois":
case "ApolloGameplay_MapLandmarks":
case "PapayaGameplay_MapLandmarks":
await LoadQuestIndicatorData();
break;
case "ApolloGameplay_TagsLocation":
await LoadTagsLocation();
break;
case "ApolloGameplay_PatrolsPath":
await LoadPatrolsPath();
break;
case "ApolloGameplay_UpgradeBenches":
await LoadUpgradeBenches();
break;
case "ApolloGameplay_Phonebooths":
await LoadPhonebooths();
break;
case "ApolloGameplay_VendingMachines":
await LoadBrVendingMachines();
break;
case "ApolloGameplay_Fireflies":
await LoadFireflies();
break;
case "ApolloGameplay_CorruptionZones":
await LoadCorruptionZones();
break;
case "ApolloGameplay_CubeMovements":
await LoadCubeMovements();
break;
case "PapayaGameplay_CannonballGame":
await LoadCannonballGame();
break;
case "PapayaGameplay_SkydiveGame":
await LoadSkydiveGame();
break;
case "PapayaGameplay_ShootingTargets":
await LoadShootingTargets();
break;
case "PapayaGameplay_ParkourGame":
await LoadWaypoint(EWaypointType.Parkour);
break;
case "PapayaGameplay_TimeTrials":
await LoadWaypoint(EWaypointType.TimeTrials);
break;
case "PapayaGameplay_VendingMachines":
await LoadPrVendingMachines();
break;
case "PapayaGameplay_MusicBlocks":
await LoadMusicBlocks();
break;
}
_bitmaps[MapIndex][key].IsEnabled = true;
}
switch (MapIndex)
{
case 0:
_brLayerImage = GetImageSource(GetLayerBitmap(false));
break;
case 1:
_prLayerImage = GetImageSource(GetLayerBitmap(false));
break;
}
TriggerChange();
}
private BitmapImage GetImageSource(SKBitmap bitmap)
{
if (bitmap == null) return null;
using var stream = SKImage.FromBitmap(bitmap).Encode().AsStream();
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = stream;
image.EndInit();
image.Freeze();
return image;
}
private void TriggerChange()
{
var layerCount = _bitmaps[_mapIndex].Count(x => x.Value.IsEnabled);
var layerString = $"{layerCount} Layer{(layerCount > 1 ? "s" : "")}";
switch (_mapIndex)
{
case 0:
_discordHandler.UpdateButDontSavePresence(null, $"Map Viewer: Battle Royale ({layerString})");
_mapImage = _brMiniMapImage;
_layerImage = _brLayerImage;
break;
case 1:
_discordHandler.UpdateButDontSavePresence(null, $"Map Viewer: Party Royale ({layerString})");
_mapImage = _prMiniMapImage;
_layerImage = _prLayerImage;
break;
}
RaisePropertyChanged(nameof(MapImage));
RaisePropertyChanged(nameof(LayerImage));
}
private readonly SKPaint _textPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Color = SKColors.White, TextAlign = SKTextAlign.Center, TextSize = 26
};
private readonly SKPaint _fillPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
IsStroke = true, Color = SKColors.Black, TextSize = 26,
TextAlign = SKTextAlign.Center
};
private readonly SKPaint _pathPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High, IsStroke = true,
Style = SKPaintStyle.Stroke, StrokeWidth = 5, Color = SKColors.Red,
ImageFilter = SKImageFilter.CreateDropShadow(4, 4, 8, 8, SKColors.Black)
};
private FVector2D GetMapPosition(FVector vector, int mapRadius)
{
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 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) ||
!mapManager.TryGetValue(out UObject mapMaterial, "MapMaterial") ||
!mapMaterial.TryGetValue(out FStructFallback cachedExpressionData, "CachedExpressionData") ||
!cachedExpressionData.TryGetValue(out FStructFallback parameters, "Parameters") ||
!parameters.TryGetValue(out UTexture2D[] textureValues, "TextureValues")) return;
_bitmaps[0][_FIRST_BITMAP] = new MapLayer{Layer = Utils.GetBitmap(textureValues[0]), IsEnabled = true};
_brMiniMapImage = GetImageSource(_bitmaps[0][_FIRST_BITMAP].Layer);
});
}
private async Task LoadPrMiniMap()
{
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) ||
!mapManager.TryGetValue(out UMaterial mapMaterial, "MapMaterial") ||
mapMaterial.ReferencedTextures.Count < 1) return;
_bitmaps[1][_FIRST_BITMAP] = new MapLayer{Layer = Utils.GetBitmap(mapMaterial.ReferencedTextures[0] as UTexture2D), IsEnabled = true};
_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(_textPaint.Typeface);
var shapedText = shaper.Shape(text.Text, _textPaint);
if (discoverBackend.Text.Contains("papaya", StringComparison.OrdinalIgnoreCase))
{
_fillPaint.StrokeWidth = 5;
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, _fillPaint);
prLandmarks.DrawShapedText(shaper, text.Text, vector.X - shapedText.Points[^1].X / 2, vector.Y - 12.5F, _textPaint);
}
else if (discoveryQuest.AssetPathName.Text.Contains("landmarks", StringComparison.OrdinalIgnoreCase))
{
_fillPaint.StrokeWidth = 5;
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, _fillPaint);
brLandmarks.DrawShapedText(shaper, text.Text, vector.X - shapedText.Points[^1].X / 2, vector.Y - 12.5F, _textPaint);
}
else
{
_fillPaint.StrokeWidth = 10;
var vector = GetMapPosition(worldLocation, _brRadius);
pois.DrawShapedText(shaper, text.Text.ToUpperInvariant(), vector.X - shapedText.Points[^1].X / 2, vector.Y, _fillPaint);
pois.DrawShapedText(shaper, text.Text.ToUpperInvariant(), vector.X - shapedText.Points[^1].X / 2, vector.Y, _textPaint);
}
}
}
_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(_ =>
{
_fillPaint.StrokeWidth = 5;
var patrolsPathBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(patrolsPathBitmap);
var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Apollo_Terrain_NPCLibrary_Overlay_S18");
foreach (var export in exports)
{
if (!export.ExportType.Equals("FortAthenaPatrolPath", StringComparison.OrdinalIgnoreCase) ||
!export.TryGetValue(out FPackageIndex[] patrolPoints, "PatrolPoints")) continue;
if (!Utils.TryGetPackageIndexExport(patrolPoints[0], out UObject 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);
var displayName = export.Name["FortAthenaPatrolPath_Tandem_S18_".Length..];
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);
}
c.DrawPath(path, _pathPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
}
_bitmaps[0]["ApolloGameplay_PatrolsPath"] = new MapLayer {Layer = patrolsPathBitmap, IsEnabled = false};
});
}
private async Task LoadCannonballGame()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
var cannonballBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(cannonballBitmap);
var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_CannonballGame");
foreach (var export in exports)
{
if (!export.ExportType.Equals("BP_CannonballGame_Target_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("CannonballGame_VehicleSpawner_C", StringComparison.OrdinalIgnoreCase)) continue;
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var displayName = Utils.GetLocalizedResource("", "D998BEF44F051E0885C6C58565934BEA", "Cannonball");
var vector = GetMapPosition(relativeLocation, _prRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
}
_bitmaps[1]["PapayaGameplay_CannonballGame"] = new MapLayer {Layer = cannonballBitmap, IsEnabled = false};
});
}
private async Task LoadSkydiveGame()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
var skydiveBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(skydiveBitmap);
var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_SkydiveGame");
foreach (var export in exports)
{
if (!export.ExportType.Equals("BP_Waypoint_Papaya_Skydive_Start_C", StringComparison.OrdinalIgnoreCase)) continue;
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!export.TryGetValue(out FText minigameActivityName, "MinigameActivityName") ||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var vector = GetMapPosition(relativeLocation, _prRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
c.DrawText(minigameActivityName.Text, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(minigameActivityName.Text, vector.X, vector.Y - 12.5F, _textPaint);
}
_bitmaps[1]["PapayaGameplay_SkydiveGame"] = new MapLayer {Layer = skydiveBitmap, IsEnabled = false};
});
}
private async Task LoadShootingTargets()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
var shootingTargetsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(shootingTargetsBitmap);
var bDone = false;
var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_ShootingTargets");
foreach (var export in exports)
{
if (!export.ExportType.Equals("PapayaShootingTarget_C", StringComparison.OrdinalIgnoreCase)) continue;
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var vector = GetMapPosition(relativeLocation, _prRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
if (bDone) continue;
bDone = true;
c.DrawText("Shooting Target", vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText("Shooting Target", vector.X, vector.Y - 12.5F, _textPaint);
}
_bitmaps[1]["PapayaGameplay_ShootingTargets"] = new MapLayer {Layer = shootingTargetsBitmap, IsEnabled = false};
});
}
private async Task LoadWaypoint(EWaypointType type)
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
var waypointBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(waypointBitmap);
string file;
string name;
switch (type)
{
case EWaypointType.Parkour:
file = "PapayaGameplay_ParkourGame";
name = "Parkour";
break;
case EWaypointType.TimeTrials:
file = "PapayaGameplay_TimeTrials";
name = "Time Trials";
break;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
var path = new SKPath();
var exports = Utils.LoadExports($"/PapayaGameplay/LevelOverlays/{file}");
foreach (var export in exports)
{
if (!export.ExportType.Equals("BP_Waypoint_Parent_Papaya_C", StringComparison.OrdinalIgnoreCase)) continue;
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject root) ||
!root.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var vector = GetMapPosition(relativeLocation, _prRadius);
if (path.IsEmpty || export.TryGetValue(out bool startsTrial, "StartsTrial") && startsTrial)
{
path.MoveTo(vector.X, vector.Y);
c.DrawText(name, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(name, vector.X, vector.Y - 12.5F, _textPaint);
}
else if (export.TryGetValue(out bool endsTrial, "EndsTrial") && endsTrial)
{
path.LineTo(vector.X, vector.Y);
c.DrawPath(path, _pathPaint);
path = new SKPath();
}
else path.LineTo(vector.X, vector.Y);
}
_bitmaps[1][file] = new MapLayer {Layer = waypointBitmap, IsEnabled = false};
});
}
private async Task LoadPrVendingMachines()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
var set = new HashSet<string>();
var timeTrialsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(timeTrialsBitmap);
var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_VendingMachines");
foreach (var export in exports)
{
if (!export.ExportType.Equals("B_Papaya_VendingMachine_Boat_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_BoogieBomb_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_Burger_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_CrashPad_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_FishingPole_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_Grappler_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_Jetpack_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_PaintGrenade_Blue_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_PaintGrenade_Red_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_PaintLauncher_Blue_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_PaintLauncher_Red_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_PlungerBow_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_Quad_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Papaya_VendingMachine_Tomato_C", StringComparison.OrdinalIgnoreCase)) continue;
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject root) ||
!root.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var name = export.ExportType.SubstringAfter("B_Papaya_VendingMachine_").SubstringBeforeLast("_C");
var vector = GetMapPosition(relativeLocation, _prRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
if (!set.Add(name)) continue;
c.DrawText(name, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(name, vector.X, vector.Y - 12.5F, _textPaint);
}
_bitmaps[1]["PapayaGameplay_VendingMachines"] = new MapLayer {Layer = timeTrialsBitmap, IsEnabled = false};
});
}
private async Task LoadMusicBlocks()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
var shootingTargetsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(shootingTargetsBitmap);
var bDone = false;
var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_MusicBlocks");
foreach (var export in exports)
{
if (!export.ExportType.Equals("MusicBlock_Piano3_Papaya_C", StringComparison.OrdinalIgnoreCase)) continue;
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var vector = GetMapPosition(relativeLocation, _prRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
if (bDone) continue;
bDone = true;
c.DrawText("Music Blocks", vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText("Music Blocks", vector.X, vector.Y - 12.5F, _textPaint);
}
_bitmaps[1]["PapayaGameplay_MusicBlocks"] = new MapLayer {Layer = shootingTargetsBitmap, IsEnabled = false};
});
}
private async Task LoadUpgradeBenches()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
var upgradeBenchesBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(upgradeBenchesBitmap);
var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Apollo_Terrain_NPCLibrary_Stations_UpgradeBenches");
foreach (var export in exports)
{
if (!export.ExportType.Equals("B_Athena_Spawner_UpgradeStation_C", StringComparison.OrdinalIgnoreCase)) continue;
var displayName = export.Name["B_Athena_Spawner_".Length..];
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var vector = GetMapPosition(relativeLocation, _brRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
}
_bitmaps[0]["ApolloGameplay_UpgradeBenches"] = new MapLayer {Layer = upgradeBenchesBitmap, IsEnabled = false};
});
}
private async Task LoadPhonebooths()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
var phoneboothsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(phoneboothsBitmap);
var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Apollo_Terrain_NPCLibrary_Stations_Phonebooths");
foreach (var export in exports)
{
if (!export.ExportType.Equals("B_Athena_Spawner_Payphone_C", StringComparison.OrdinalIgnoreCase)) continue;
var displayName = export.Name["B_Athena_Spawner_".Length..];
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var vector = GetMapPosition(relativeLocation, _brRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
}
_bitmaps[0]["ApolloGameplay_Phonebooths"] = new MapLayer {Layer = phoneboothsBitmap, IsEnabled = false};
});
}
private async Task LoadBrVendingMachines()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
var vendingMachinesBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(vendingMachinesBitmap);
var exports = Utils.LoadExports("FortniteGame/Content/Athena/Apollo/Maps/Special/ItemCollections/Apollo_Item_VendingMachines");
foreach (var export in exports)
{
if (!export.ExportType.Equals("B_Athena_Spawner_VendingMachine_MendingOnly_C", StringComparison.OrdinalIgnoreCase) &&
!export.ExportType.Equals("B_Athena_Spawner_VendingMachine_Random_C", StringComparison.OrdinalIgnoreCase)) continue;
var displayName = $"{(export.ExportType.Contains("Mending") ? "MM" : "WOM")}_{export.Name["B_Athena_Spawner_VendingMachine_Random".Length..]}";
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var vector = GetMapPosition(relativeLocation, _brRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
}
_bitmaps[0]["ApolloGameplay_VendingMachines"] = new MapLayer {Layer = vendingMachinesBitmap, IsEnabled = false};
});
}
private async Task LoadFireflies()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
var firefliesBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(firefliesBitmap);
var exports = Utils.LoadExports("FortniteGame/Content/Athena/Apollo/Maps/Special/ItemCollections/Apollo_Item_Fireflies");
foreach (var export in exports)
{
if (!export.ExportType.Equals("BP_BGACSpawner_Fireflies_C", StringComparison.OrdinalIgnoreCase)) continue;
var displayName = $"FF_{export.Name["BP_BGACSpawnerFireFlies".Length..]}";
if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) ||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
var vector = GetMapPosition(relativeLocation, _brRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
}
_bitmaps[0]["ApolloGameplay_Fireflies"] = new MapLayer {Layer = firefliesBitmap, IsEnabled = false};
});
}
private async Task LoadTagsLocation()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
if (!Utils.TryLoadObject("FortniteGame/Content/Quests/QuestTagToLocationDataRows.QuestTagToLocationDataRows", out UDataTable locationData))
return;
var tagsLocationBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(tagsLocationBitmap);
foreach (var (key, uObject) in locationData.RowMap)
{
if (key.Text.StartsWith("Athena.Location.POI", StringComparison.OrdinalIgnoreCase) ||
key.Text.StartsWith("Athena.Location.Unnamed", StringComparison.OrdinalIgnoreCase) ||
key.Text.Contains(".Tandem.", StringComparison.OrdinalIgnoreCase) ||
!uObject.TryGetValue(out FVector worldLocation, "WorldLocation")) continue;
var parts = key.Text.Split('.');
var displayName = parts[^2];
if (!int.TryParse(parts[^1], out var _))
displayName += " " + parts[^1];
var vector = GetMapPosition(worldLocation, _brRadius);
c.DrawPoint(vector.X, vector.Y, _pathPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint);
c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint);
}
_bitmaps[0]["ApolloGameplay_TagsLocation"] = new MapLayer {Layer = tagsLocationBitmap, IsEnabled = false};
});
}
private async Task LoadCorruptionZones()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
if (!Utils.TryLoadObject("FortniteGame/Content/Athena/Apollo/Environments/Landscape/Materials/Corruption/T_InitialCorruptionAreas.T_InitialCorruptionAreas", out UTexture2D corruption))
return;
var overlay = Utils.GetBitmap(corruption);
var width = overlay.Width;
var height = overlay.Height;
var rotatedBitmap = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(rotatedBitmap);
c.Clear();
c.Translate(0, width);
c.RotateDegrees(-90);
c.DrawRect(0, 0, width, height, new SKPaint
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateCompose(SKShader.CreateSweepGradient(new SKPoint(width / 2f, height / 2f),new [] {
SKColor.Parse("#352176"), SKColor.Parse("#fd78fa"), SKColor.Parse("#f0b843"), SKColor.Parse("#e54a21")
}, null), SKShader.CreatePerlinNoiseTurbulence(0.05f, 0.05f, 4, 0), SKBlendMode.SrcOver)
});
c.DrawBitmap(overlay, 0, 0, new SKPaint { BlendMode = SKBlendMode.Darken });
rotatedBitmap.ClearToTransparent();
_bitmaps[0]["ApolloGameplay_CorruptionZones"] = new MapLayer {Layer = rotatedBitmap.Resize(_widthHeight, _widthHeight), IsEnabled = false};
});
}
/// <summary>
/// FortniteGame/Plugins/GameFeatures/CorruptionGameplay/Content/CorruptionGameplay_LevelOverlay.uasset
/// too lazy to filters
/// </summary>
private async Task LoadCubeMovements()
{
await _threadWorkerView.Begin(_ =>
{
_fillPaint.StrokeWidth = 5;
var cubeMovementsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(cubeMovementsBitmap);
if (!Utils.TryLoadObject("/CorruptionGameplay/Levels/CorruptionGameplay_ApolloTerrain_Overlay.BP_CubeMovementGradient_2", out UObject overlay) ||
!overlay.TryGetValue(out FSoftObjectPath[] cubeMovementStaticPaths, "cubeMovementStaticPaths") || cubeMovementStaticPaths.Length < 1)
return;
var oldColor = _pathPaint.Color;
_pathPaint.Color = SKColors.Purple;
foreach (var cubeMovementStaticPath in cubeMovementStaticPaths)
{
var objectPath = cubeMovementStaticPath.AssetPathName.Text.SubstringBeforeLast(".");
var objectName = cubeMovementStaticPath.SubPathString.SubstringAfterLast(".");
if (!Utils.TryLoadObject($"{objectPath}.{objectName}", out UObject staticPath))
return;
DrawCubeMovements(c, staticPath, true);
}
if (Utils.TryLoadObject("/CorruptionGameplay/Levels/CubeMovement/Apollo_CM_Gold_Overlay.CM_Spline_Gold", out UObject goldPath))
{
_pathPaint.Color = SKColors.Gold;
DrawCubeMovements(c, goldPath, false);
}
_pathPaint.Color = oldColor;
_bitmaps[0]["ApolloGameplay_CubeMovements"] = new MapLayer {Layer = cubeMovementsBitmap, IsEnabled = false};
});
}
private void DrawCubeMovements(SKCanvas c, UObject staticPath, bool fixLocation)
{
if (!staticPath.TryGetValue(out FStructFallback[] pathTravelers, "PathTravelers") || pathTravelers.Length < 1 ||
!pathTravelers[0].TryGetValue(out FPackageIndex[] generatedSplinesArray, "GeneratedSplinesArray") || generatedSplinesArray.Length < 1)
return;
UObject uObject;
var parentRelativeLocation = new FVector();
if (fixLocation)
{
if (!pathTravelers[0].TryGetValue(out FPackageIndex pathTraveler, "PathTraveler") ||
!Utils.TryGetPackageIndexExport(pathTraveler, out uObject) ||
!uObject.TryGetValue(out FPackageIndex rootComponent, "RootComponent") ||
!Utils.TryGetPackageIndexExport(rootComponent, out uObject) ||
!uObject.TryGetValue(out parentRelativeLocation, "RelativeLocation"))
return;
}
var bDone = false;
var path = new SKPath();
foreach (var generatedSpline in generatedSplinesArray)
{
if (!Utils.TryGetPackageIndexExport(generatedSpline, out uObject) ||
!uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue;
if (!uObject.TryGetValue(out FStructFallback splineCurves, "SplineCurves") ||
!splineCurves.TryGetValue(out FStructFallback positions, "Position") ||
!positions.TryGetValue(out FStructFallback[] positionPoints, "Points")) continue;
foreach (var positionPoint in positionPoints)
{
if (!positionPoint.TryGetValue(out FVector point, "OutVal")) continue;
var vector = GetMapPosition(parentRelativeLocation + relativeLocation + point, _brRadius);
if (!bDone)
{
path.MoveTo(vector.X, vector.Y);
bDone = true;
}
else
{
path.LineTo(vector.X, vector.Y);
}
}
}
c.DrawPath(path, _pathPaint);
}
}
}