mirror of
https://github.com/4sval/FModel.git
synced 2026-03-21 17:24:26 -05:00
FModel v4.0
This commit is contained in:
parent
d7751d6f95
commit
c403453cc3
21
.github/ISSUE_TEMPLATE/bug-report.md
vendored
21
.github/ISSUE_TEMPLATE/bug-report.md
vendored
|
|
@ -1,21 +0,0 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
1. Describe the bug
|
||||
*A clear and concise description of what the bug is.*
|
||||
2. Steps to reproduce the behavior
|
||||
3. Send logs
|
||||
*The latest log file is ABSOLUTELY MANDATORY*
|
||||
4. Add screenshots
|
||||
|
||||
**DO NOT** report issues to ask where is `<insert your favorite asset here>`.
|
||||
**DO NOT** report issues if you have AES errors.
|
||||
**DO NOT** report issues if your game is not up to date.
|
||||
|
||||
Without following the rules above, your issue will be closed without explanation.
|
||||
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
|
@ -1,17 +0,0 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement, suggestion
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
32
.github/workflows/main.yml
vendored
Normal file
32
.github/workflows/main.yml
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
name: Artifact Generator
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: GIT Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: 'true'
|
||||
token: ${{ secrets.PAT_TOKEN }}
|
||||
|
||||
- name: .NET 5 Setup
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: 5.0.x
|
||||
|
||||
- name: .NET Restore
|
||||
run: dotnet restore FModel
|
||||
|
||||
- name: .NET Publish
|
||||
run: dotnet publish FModel -c Release -f net5.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=true -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false --no-self-contained -r win-x64
|
||||
|
||||
- name: EXE Upload
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: FModel
|
||||
path: D:\a\FModel4\FModel4\FModel\bin\Publish\
|
||||
54
.gitignore
vendored
54
.gitignore
vendored
|
|
@ -4,6 +4,7 @@
|
|||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
|
|
@ -12,6 +13,9 @@
|
|||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
|
|
@ -19,13 +23,17 @@
|
|||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
.idea/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
|
|
@ -36,9 +44,10 @@ Generated\ Files/
|
|||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
|
|
@ -52,7 +61,6 @@ BenchmarkDotNet.Artifacts/
|
|||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
**/Properties/launchSettings.json
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
|
@ -60,7 +68,7 @@ StyleCopReport.xml
|
|||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
|
|
@ -77,6 +85,7 @@ StyleCopReport.xml
|
|||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
|
|
@ -119,9 +128,6 @@ _ReSharper*/
|
|||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
|
|
@ -179,6 +185,8 @@ PublishScripts/
|
|||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
|
|
@ -203,12 +211,14 @@ BundleArtifacts/
|
|||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
|
|
@ -221,7 +231,7 @@ ClientBin/
|
|||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
|
|
@ -236,7 +246,7 @@ Generated_Code/
|
|||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
#Backup*/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
|
|
@ -252,6 +262,9 @@ ServiceFabricBackup/
|
|||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
|
@ -287,12 +300,8 @@ paket-files/
|
|||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
|
|
@ -317,7 +326,7 @@ __pycache__/
|
|||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
|
|
@ -326,8 +335,17 @@ ASALocalRun/
|
|||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
.github
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
|
|
|||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "CUE4Parse"]
|
||||
path = CUE4Parse
|
||||
url = https://github.com/FabianFG/CUE4Parse
|
||||
1
CUE4Parse
Submodule
1
CUE4Parse
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 09a98cdafd273db1cdee58ded37ed4f5b3a2ff22
|
||||
25
FModel.sln
25
FModel.sln
|
|
@ -1,25 +0,0 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30804.86
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FModel", "FModel\FModel.csproj", "{A42F8737-D056-4FA5-BEB5-AA96E1639F8A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Debug|x64.Build.0 = Debug|x64
|
||||
{A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Release|x64.ActiveCfg = Release|x64
|
||||
{A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {688BAB53-42F7-45F2-AFDF-79A07771213F}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
@ -1,14 +1,19 @@
|
|||
<Application x:Class="FModel.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
|
||||
StartupUri="MainWindow.xaml" ShutdownMode="OnMainWindowClose"
|
||||
DispatcherUnhandledException="OnDispatcherUnhandledException">
|
||||
Exit="AppExit" DispatcherUnhandledException="OnUnhandledException">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/Theme/Style.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/ToastNotifications.Messages;component/Themes/Default.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/AdonisUI;component/ColorSchemes/Dark.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/AdonisUI.ClassicTheme;component/Resources.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<Color x:Key="{x:Static adonisUi:Colors.AccentColor}">#206BD4</Color>
|
||||
<Color x:Key="{x:Static adonisUi:Colors.AlertColor}">#D49220</Color>
|
||||
<Color x:Key="{x:Static adonisUi:Colors.ErrorColor}">#C22B2B</Color>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
|
|
|||
|
|
@ -1,80 +1,124 @@
|
|||
using FModel.Logger;
|
||||
using FModel.Utils;
|
||||
using FModel.ViewModels.ComboBox;
|
||||
using FModel.ViewModels.StatusBar;
|
||||
using FModel.Windows.DarkMessageBox;
|
||||
using AdonisUI.Controls;
|
||||
using Microsoft.Win32;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog.Sinks.SystemConsole.Themes;
|
||||
using MessageBox = AdonisUI.Controls.MessageBox;
|
||||
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
|
||||
using MessageBoxResult = AdonisUI.Controls.MessageBoxResult;
|
||||
|
||||
namespace FModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
public partial class App
|
||||
{
|
||||
internal static Stopwatch StartTimer { get; private set; }
|
||||
static bool framerateSet = false;
|
||||
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
StartTimer = Stopwatch.StartNew();
|
||||
|
||||
DebugHelper.Init(LogsFilePath); // get old settings too
|
||||
|
||||
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(ProgramLang.GetProgramLang());
|
||||
|
||||
DebugHelper.WriteLine("{0} {1}", "[FModel]", "––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––");
|
||||
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Version]", Assembly.GetExecutingAssembly().GetName().Version.ToString());
|
||||
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Build]", Globals.Build);
|
||||
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[OS]", Logger.Logger.GetOperatingSystemProductName(true));
|
||||
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Runtime]", RuntimeInformation.FrameworkDescription);
|
||||
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Culture]", Thread.CurrentThread.CurrentUICulture);
|
||||
|
||||
StatusBarVm.statusBarViewModel.Set(FModel.Properties.Resources.Initializing, FModel.Properties.Resources.Loading);
|
||||
|
||||
base.OnStartup(e);
|
||||
}
|
||||
|
||||
public static string LogsFilePath
|
||||
{
|
||||
get
|
||||
try
|
||||
{
|
||||
string filename = string.Format("FModel-Log-{0:yyyy-MM-dd}.txt", DateTime.Now);
|
||||
|
||||
// Copy user settings from previous application version if necessary
|
||||
if (FModel.Properties.Settings.Default.UpdateSettings)
|
||||
FModel.Properties.Settings.Default.Upgrade();
|
||||
|
||||
Folders.LoadFolders();
|
||||
|
||||
return Path.Combine(FModel.Properties.Settings.Default.OutputPath + "\\Logs", filename);
|
||||
UserSettings.Default = JsonConvert.DeserializeObject<UserSettings>(
|
||||
File.ReadAllText(UserSettings.FilePath), JsonNetSerializer.SerializerSettings);
|
||||
}
|
||||
catch
|
||||
{
|
||||
UserSettings.Default = new UserSettings();
|
||||
}
|
||||
|
||||
if (!Directory.Exists(UserSettings.Default.OutputDirectory))
|
||||
{
|
||||
UserSettings.Default.OutputDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Output");
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FModel"));
|
||||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Backups"));
|
||||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Exports"));
|
||||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Saves"));
|
||||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Textures"));
|
||||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Sounds"));
|
||||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Logs"));
|
||||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
|
||||
|
||||
Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File(
|
||||
path: Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.txt"),
|
||||
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [FModel] [{Level:u3}] {Message:lj}{NewLine}{Exception}").CreateLogger();
|
||||
|
||||
Log.Information("Version {Version}", Constants.APP_VERSION);
|
||||
Log.Information("{OS}", GetOperatingSystemProductName());
|
||||
Log.Information("{RuntimeVer}", RuntimeInformation.FrameworkDescription);
|
||||
Log.Information("Culture {SysLang}", Thread.CurrentThread.CurrentUICulture);
|
||||
}
|
||||
|
||||
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
|
||||
private void AppExit(object sender, ExitEventArgs e)
|
||||
{
|
||||
string errorMessage = string.Format(FModel.Properties.Resources.UnhandledExceptionOccured, e.Exception.Message);
|
||||
DebugHelper.WriteException(e.Exception, "thrown in App.xaml.cs by OnDispatcherUnhandledException");
|
||||
DarkMessageBoxHelper.Show(errorMessage, FModel.Properties.Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error, true);
|
||||
Log.Information("––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––");
|
||||
Log.CloseAndFlush();
|
||||
UserSettings.Save();
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
private void OnUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
|
||||
{
|
||||
Log.Error("{Exception}", e.Exception);
|
||||
|
||||
var messageBox = new MessageBoxModel
|
||||
{
|
||||
Text = $"An unhandled exception occurred: {e.Exception.Message}",
|
||||
Caption = "Fatal Error",
|
||||
Icon = MessageBoxImage.Error,
|
||||
Buttons = new[]
|
||||
{
|
||||
MessageBoxButtons.Custom("Restart", EErrorKind.Restart),
|
||||
MessageBoxButtons.Custom("OK", EErrorKind.Ignore)
|
||||
},
|
||||
IsSoundEnabled = false
|
||||
};
|
||||
|
||||
MessageBox.Show(messageBox);
|
||||
if (messageBox.Result == MessageBoxResult.Custom && (EErrorKind) messageBox.ButtonPressed.Id != EErrorKind.Ignore)
|
||||
{
|
||||
ApplicationService.ApplicationView.Restart();
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
internal static void SetFramerate()
|
||||
private string GetOperatingSystemProductName()
|
||||
{
|
||||
if (!framerateSet)
|
||||
var productName = string.Empty;
|
||||
try
|
||||
{
|
||||
System.Windows.Media.Animation.Timeline.DesiredFrameRateProperty.OverrideMetadata(
|
||||
typeof(System.Windows.Media.Animation.Timeline),
|
||||
new FrameworkPropertyMetadata { DefaultValue = 10 });
|
||||
framerateSet = true;
|
||||
productName = GetRegistryValue(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ProductName", RegistryHive.LocalMachine);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(productName))
|
||||
productName = Environment.OSVersion.VersionString;
|
||||
|
||||
return $"{productName} ({(Environment.Is64BitOperatingSystem ? "64" : "32")}-bit)";
|
||||
}
|
||||
|
||||
private string GetRegistryValue(string path, string name = null, RegistryHive root = RegistryHive.CurrentUser)
|
||||
{
|
||||
using var rk = RegistryKey.OpenBaseKey(root, RegistryView.Default).OpenSubKey(path);
|
||||
if (rk != null)
|
||||
return rk.GetValue(name, null) as string;
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
63
FModel/Constants.cs
Normal file
63
FModel/Constants.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
|
||||
namespace FModel
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
public static readonly string APP_VERSION = Assembly.GetExecutingAssembly().GetName().Version?.ToString();
|
||||
public const string ZERO_64_CHAR = "0000000000000000000000000000000000000000000000000000000000000000";
|
||||
public static readonly FGuid ZERO_GUID = new(0U);
|
||||
|
||||
public const string WHITE = "#DAE5F2";
|
||||
public const string RED = "#E06C75";
|
||||
public const string GREEN = "#98C379";
|
||||
public const string YELLOW = "#E5C07B";
|
||||
public const string BLUE = "#528BCC";
|
||||
|
||||
public const string DONATE_LINK = "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=EP9SSWG8MW4UC&source=url";
|
||||
public const string CHANGELOG_LINK = "https://github.com/iAmAsval/FModel/releases/latest";
|
||||
public const string ISSUE_LINK = "https://github.com/iAmAsval/FModel/issues/new";
|
||||
public const string DISCORD_LINK = "https://discord.gg/fdkNYYQ";
|
||||
|
||||
public const string _FN_LIVE_TRIGGER = "fortnite-live.manifest";
|
||||
public const string _VAL_LIVE_TRIGGER = "valorant-live.manifest";
|
||||
|
||||
public static string GetRandomColor()
|
||||
{
|
||||
return _randomColors[_random.Next(0, 255)];
|
||||
}
|
||||
|
||||
private static readonly Random _random = new(Environment.TickCount);
|
||||
private static readonly string[] _randomColors =
|
||||
{
|
||||
"F44336", "FFEBEE", "FFCDD2", "EF9A9A", "E57373", "EF5350", "E53935", "D32F2F", "C62828", "B71C1C",
|
||||
"FF8A80", "FF5252", "FF1744", "D50000", "FCE4EC", "F8BBD0", "F48FB1", "F06292", "EC407A", "E91E63",
|
||||
"D81B60", "C2185B", "AD1457", "880E4F", "FF80AB", "FF4081", "F50057", "C51162", "F3E5F5", "E1BEE7",
|
||||
"CE93D8", "BA68C8", "AB47BC", "9C27B0", "8E24AA", "7B1FA2", "6A1B9A", "4A148C", "EA80FC", "E040FB",
|
||||
"D500F9", "AA00FF", "EDE7F6", "D1C4E9", "B39DDB", "9575CD", "7E57C2", "673AB7", "5E35B1", "512DA8",
|
||||
"4527A0", "311B92", "B388FF", "7C4DFF", "651FFF", "6200EA", "E8EAF6", "C5CAE9", "9FA8DA", "7986CB",
|
||||
"5C6BC0", "3F51B5", "3949AB", "303F9F", "283593", "1A237E", "8C9EFF", "536DFE", "3D5AFE", "304FFE",
|
||||
"E3F2FD", "BBDEFB", "90CAF9", "64B5F6", "42A5F5", "2196F3", "1E88E5", "1976D2", "1565C0", "0D47A1",
|
||||
"82B1FF", "448AFF", "2979FF", "2962FF", "E1F5FE", "B3E5FC", "81D4FA", "4FC3F7", "29B6F6", "03A9F4",
|
||||
"039BE5", "0288D1", "0277BD", "01579B", "80D8FF", "40C4FF", "00B0FF", "0091EA", "E0F7FA", "B2EBF2",
|
||||
"80DEEA", "4DD0E1", "26C6DA", "00BCD4", "00ACC1", "0097A7", "00838F", "006064", "84FFFF", "18FFFF",
|
||||
"00E5FF", "00B8D4", "E0F2F1", "B2DFDB", "80CBC4", "4DB6AC", "26A69A", "009688", "00897B", "00796B",
|
||||
"00695C", "004D40", "A7FFEB", "64FFDA", "1DE9B6", "00BFA5", "E8F5E9", "C8E6C9", "A5D6A7", "81C784",
|
||||
"66BB6A", "4CAF50", "43A047", "388E3C", "2E7D32", "1B5E20", "B9F6CA", "69F0AE", "00E676", "00C853",
|
||||
"F1F8E9", "DCEDC8", "C5E1A5", "AED581", "9CCC65", "8BC34A", "7CB342", "689F38", "558B2F", "33691E",
|
||||
"CCFF90", "B2FF59", "76FF03", "64DD17", "F9FBE7", "F0F4C3", "E6EE9C", "DCE775", "D4E157", "CDDC39",
|
||||
"C0CA33", "AFB42B", "9E9D24", "827717", "F4FF81", "EEFF41", "C6FF00", "AEEA00", "FFFDE7", "FFF9C4",
|
||||
"FFF59D", "FFF176", "FFEE58", "FFEB3B", "FDD835", "FBC02D", "F9A825", "F57F17", "FFFF8D", "FFFF00",
|
||||
"FFEA00", "FFD600", "FFF8E1", "FFECB3", "FFE082", "FFD54F", "FFCA28", "FFC107", "FFB300", "FFA000",
|
||||
"FF8F00", "FF6F00", "FFE57F", "FFD740", "FFC400", "FFAB00", "FFF3E0", "FFE0B2", "FFCC80", "FFB74D",
|
||||
"FFA726", "FF9800", "FB8C00", "F57C00", "EF6C00", "E65100", "FFD180", "FFAB40", "FF9100", "FF6D00",
|
||||
"FBE9E7", "FFCCBC", "FFAB91", "FF8A65", "FF7043", "FF5722", "F4511E", "E64A19", "D84315", "BF360C",
|
||||
"FF9E80", "FF6E40", "FF3D00", "DD2C00", "EFEBE9", "D7CCC8", "BCAAA4", "A1887F", "8D6E63", "795548",
|
||||
"6D4C41", "5D4037", "4E342E", "3E2723", "FAFAFA", "F5F5F5", "EEEEEE", "E0E0E0", "BDBDBD", "9E9E9E",
|
||||
"757575", "616161", "424242", "212121", "ECEFF1", "CFD8DC", "B0BEC5", "90A4AE", "78909C", "607D8B",
|
||||
"546E7A", "455A64", "37474F", "263238", "000000",
|
||||
};
|
||||
}
|
||||
}
|
||||
43
FModel/Creator/Bases/BB/BaseBreakersIcon.cs
Normal file
43
FModel/Creator/Bases/BB/BaseBreakersIcon.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using FModel.Creator.Bases.FN;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.BB
|
||||
{
|
||||
public class BaseBreakersIcon : BaseIcon
|
||||
{
|
||||
public BaseBreakersIcon(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
SeriesBackground = Utils.GetBitmap("WorldExplorers/Content/UMG/Materials/t_TextGradient.t_TextGradient");
|
||||
Background = new[] {SKColor.Parse("D0D0D0"), SKColor.Parse("636363")};
|
||||
Border = new[] {SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF")};
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FSoftObjectPath iconTextureAssetData, "IconTextureAssetData"))
|
||||
Preview = Utils.GetBitmap(iconTextureAssetData);
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text;
|
||||
if (Object.TryGetValue(out FText description, "Description"))
|
||||
Description = description.Text;
|
||||
}
|
||||
|
||||
public override SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
using System;
|
||||
using System.Windows;
|
||||
using FModel.Creator.Rarities;
|
||||
using FModel.Creator.Texts;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases
|
||||
{
|
||||
public class BaseBBDefinition : IBase
|
||||
{
|
||||
public SKBitmap FallbackImage;
|
||||
public SKBitmap IconImage;
|
||||
public SKBitmap RarityBackgroundImage;
|
||||
public SKColor[] RarityBackgroundColors;
|
||||
public SKColor[] RarityBorderColor;
|
||||
public string DisplayName;
|
||||
public string Description;
|
||||
public int Width = 512;
|
||||
public int Height = 512;
|
||||
public int Margin = 2;
|
||||
|
||||
public BaseBBDefinition(string exportType)
|
||||
{
|
||||
FallbackImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png"))?.Stream);
|
||||
RarityBackgroundImage = null;
|
||||
IconImage = FallbackImage;
|
||||
RarityBackgroundColors = new[] { SKColor.Parse("D0D0D0"), SKColor.Parse("636363") };
|
||||
RarityBorderColor = new[] { SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF") };
|
||||
DisplayName = "";
|
||||
Description = "";
|
||||
}
|
||||
|
||||
public BaseBBDefinition(IUExport export, string exportType) : this(exportType)
|
||||
{
|
||||
if (export.GetExport<SoftObjectProperty>("IconTextureAssetData") is {} previewImage)
|
||||
IconImage = Utils.GetSoftObjectTexture(previewImage);
|
||||
else if (export.GetExport<ObjectProperty>("IconTextureAssetData") is {} iconTexture)
|
||||
IconImage = Utils.GetObjectTexture(iconTexture);
|
||||
|
||||
if (export.GetExport<TextProperty>("DisplayName") is {} displayName)
|
||||
DisplayName = Text.GetTextPropertyBase(displayName);
|
||||
if (export.GetExport<TextProperty>("Description") is {} description)
|
||||
Description = Text.GetTextPropertyBase(description);
|
||||
|
||||
RarityBackgroundImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/battle-breakers-item-background.png"))?.Stream);
|
||||
}
|
||||
|
||||
SKBitmap IBase.FallbackImage => FallbackImage;
|
||||
SKBitmap IBase.IconImage => IconImage;
|
||||
SKColor[] IBase.RarityBackgroundColors => RarityBackgroundColors;
|
||||
SKColor[] IBase.RarityBorderColor => RarityBorderColor;
|
||||
string IBase.DisplayName => DisplayName;
|
||||
string IBase.Description => Description;
|
||||
int IBase.Width => Width;
|
||||
int IBase.Height => Height;
|
||||
int IBase.Margin => Margin;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
using FModel.Creator.Bundles;
|
||||
using FModel.Creator.Texts;
|
||||
using System.Collections.Generic;
|
||||
using FModel.PakReader;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
|
||||
namespace FModel.Creator.Bases
|
||||
{
|
||||
public class BaseBundle
|
||||
{
|
||||
public Header DisplayStyle;
|
||||
public string DisplayName;
|
||||
public string FolderName;
|
||||
public string Watermark;
|
||||
public int Width = 1024;
|
||||
public int HeaderHeight = 261; // height is the header basically
|
||||
public int AdditionalSize = 50; // must be increased depending on the number of quests to draw
|
||||
public bool IsDisplayNameShifted;
|
||||
public List<Quest> Quests;
|
||||
public List<CompletionReward> CompletionRewards;
|
||||
|
||||
public BaseBundle()
|
||||
{
|
||||
DisplayStyle = new Header();
|
||||
DisplayName = "";
|
||||
FolderName = "";
|
||||
Watermark = Properties.Settings.Default.ChallengeBannerWatermark;
|
||||
Quests = new List<Quest>();
|
||||
CompletionRewards = new List<CompletionReward>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// used for the settings
|
||||
/// </summary>
|
||||
public BaseBundle(string watermark) : this()
|
||||
{
|
||||
DisplayName = "{DisplayName}";
|
||||
FolderName = "{FolderName}";
|
||||
Watermark = watermark;
|
||||
Quests.Add(new Quest { Description = "", Count = 999, Reward = null });
|
||||
AdditionalSize += 89;
|
||||
}
|
||||
|
||||
public BaseBundle(IUExport export, string assetFolder) : this()
|
||||
{
|
||||
if (export.GetExport<StructProperty>("DisplayStyle") is StructProperty displayStyle)
|
||||
DisplayStyle = new Header(displayStyle, assetFolder);
|
||||
if (export.GetExport<TextProperty>("DisplayName") is TextProperty displayName)
|
||||
DisplayName = Text.GetTextPropertyBase(displayName);
|
||||
|
||||
if (export.GetExport<ArrayProperty>("CareerQuestBitShifts") is ArrayProperty careerQuestBitShifts)
|
||||
{
|
||||
foreach (SoftObjectProperty questPath in careerQuestBitShifts.Value)
|
||||
{
|
||||
Package p = Utils.GetPropertyPakPackage(questPath.Value.AssetPathName.String);
|
||||
if (p.HasExport() && !p.Equals(default))
|
||||
{
|
||||
var obj = p.GetExport<UObject>();
|
||||
if (obj != null)
|
||||
Quests.Add(new Quest(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (export.GetExport<ArrayProperty>("BundleCompletionRewards") is ArrayProperty bundleCompletionRewards)
|
||||
{
|
||||
foreach (StructProperty completionReward in bundleCompletionRewards.Value)
|
||||
{
|
||||
if (completionReward.Value is UObject reward &&
|
||||
reward.TryGetValue("CompletionCount", out var c) && c is IntProperty completionCount &&
|
||||
reward.TryGetValue("Rewards", out var r) && r is ArrayProperty rewards)
|
||||
{
|
||||
foreach (StructProperty rew in rewards.Value)
|
||||
{
|
||||
if (rew.Value is UObject re &&
|
||||
re.TryGetValue("Quantity", out var q) && q is IntProperty quantity &&
|
||||
re.TryGetValue("TemplateId", out var t) && t is StrProperty templateId &&
|
||||
re.TryGetValue("ItemDefinition", out var d) && d is SoftObjectProperty itemDefinition)
|
||||
{
|
||||
if (!itemDefinition.Value.AssetPathName.IsNone &&
|
||||
!itemDefinition.Value.AssetPathName.String.Contains("/Items/Tokens/") &&
|
||||
!itemDefinition.Value.AssetPathName.String.Contains("/Items/Quests"))
|
||||
{
|
||||
CompletionRewards.Add(new CompletionReward(completionCount, quantity, itemDefinition));
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(templateId.Value))
|
||||
{
|
||||
CompletionRewards.Add(new CompletionReward(completionCount, quantity, templateId.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FolderName = assetFolder;
|
||||
AdditionalSize += 95 * Quests.Count;
|
||||
if (CompletionRewards.Count > 0) AdditionalSize += 50 + (95 * CompletionRewards.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
using FModel.Creator.Rarities;
|
||||
using FModel.Creator.Texts;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
|
||||
namespace FModel.Creator.Bases
|
||||
{
|
||||
public class BaseGCosmetic : IBase
|
||||
{
|
||||
public SKBitmap FallbackImage;
|
||||
public SKBitmap IconImage;
|
||||
public SKColor[] RarityBackgroundColors;
|
||||
public SKColor[] RarityBorderColor;
|
||||
public SKBitmap RarityBackgroundImage1;
|
||||
public SKBitmap RarityBackgroundImage2;
|
||||
public string RarityDisplayName;
|
||||
public string DisplayName;
|
||||
public string Description;
|
||||
public int Width = 512; // keep it 512 (or a multiple of 512) if you don't want blurry icons
|
||||
public int Height = 512;
|
||||
public int Margin = 2;
|
||||
|
||||
public BaseGCosmetic(string exportType)
|
||||
{
|
||||
FallbackImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png")).Stream);
|
||||
IconImage = FallbackImage;
|
||||
RarityBackgroundColors = new SKColor[2] { SKColor.Parse("FFFFFF"), SKColor.Parse("636363") };
|
||||
RarityBorderColor = new SKColor[2] { SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF") };
|
||||
RarityBackgroundImage1 = null;
|
||||
RarityBackgroundImage2 = null;
|
||||
RarityDisplayName = "";
|
||||
DisplayName = "";
|
||||
Description = "";
|
||||
Width = exportType switch
|
||||
{
|
||||
"GCosmeticCard" => 1536,
|
||||
_ => 512
|
||||
};
|
||||
Height = exportType switch
|
||||
{
|
||||
"GCosmeticCard" => 450, // Not perfect, causes images to get stretched a bit, but actually allows text to show up so it works for now. - FireMonkey
|
||||
_ => 512
|
||||
};
|
||||
}
|
||||
|
||||
public BaseGCosmetic(IUExport export, string exportType) : this(exportType)
|
||||
{
|
||||
// rarity
|
||||
EnumProperty r = export.GetExport<EnumProperty>("Rarity");
|
||||
Rarity.GetInGameRarity(this, r);
|
||||
this.RarityDisplayName = r != null ? r?.Value.String["EXRarity::".Length..] : "Common";
|
||||
|
||||
// image
|
||||
if (export.GetExport<SoftObjectProperty>("IconTexture") is SoftObjectProperty previewImage)
|
||||
this.IconImage = Utils.GetSoftObjectTexture(previewImage);
|
||||
else if (export.GetExport<ObjectProperty>("IconTexture") is ObjectProperty iconTexture)
|
||||
this.IconImage = Utils.GetObjectTexture(iconTexture);
|
||||
|
||||
// text
|
||||
if (export.GetExport<TextProperty>("DisplayName", "Title") is TextProperty displayName)
|
||||
DisplayName = Text.GetTextPropertyBase(displayName);
|
||||
if (export.GetExport<TextProperty>("Description") is TextProperty description)
|
||||
Description = Text.GetTextPropertyBase(description);
|
||||
|
||||
RarityBackgroundImage1 = Utils.GetTexture("/Game/UI/Textures/assets/HUDAccentFillBox.HUDAccentFillBox");
|
||||
RarityBackgroundImage2 = Utils.GetTexture("/Game/UI/Textures/assets/store/ItemBGStatic_UIT.ItemBGStatic_UIT");
|
||||
}
|
||||
|
||||
public void Draw(SKCanvas c)
|
||||
{
|
||||
if (this.RarityBackgroundImage1 != null)
|
||||
c.DrawBitmap(this.RarityBackgroundImage1, new SKRect(0, 0, Width, Height),
|
||||
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
|
||||
|
||||
if (this.RarityBackgroundImage2 != null)
|
||||
c.DrawBitmap(this.RarityBackgroundImage2, new SKRect(0, 0, Width, Height),
|
||||
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true, Color = SKColors.Transparent.WithAlpha(75) });
|
||||
|
||||
int x = this.Margin * (int)2.5;
|
||||
int radi = 15;
|
||||
c.DrawCircle(x + radi, x + radi, radi, new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateRadialGradient(
|
||||
new SKPoint(radi, radi),
|
||||
(radi * 2) / 5 * 4,
|
||||
this.RarityBackgroundColors,
|
||||
SKShaderTileMode.Clamp)
|
||||
});
|
||||
}
|
||||
|
||||
SKBitmap IBase.FallbackImage => FallbackImage;
|
||||
SKBitmap IBase.IconImage => IconImage;
|
||||
SKColor[] IBase.RarityBackgroundColors => RarityBackgroundColors;
|
||||
SKColor[] IBase.RarityBorderColor => RarityBorderColor;
|
||||
string IBase.DisplayName => DisplayName;
|
||||
string IBase.Description => Description;
|
||||
int IBase.Width => Width;
|
||||
int IBase.Height => Height;
|
||||
int IBase.Margin => Margin;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,164 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using FModel.Creator.Icons;
|
||||
using FModel.Creator.Rarities;
|
||||
using FModel.Creator.Stats;
|
||||
using FModel.Creator.Texts;
|
||||
using FModel.PakReader;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
using FModel.Properties;
|
||||
using FModel.Utils;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases
|
||||
{
|
||||
public class BaseIcon : IBase
|
||||
{
|
||||
public SKBitmap FallbackImage;
|
||||
public SKBitmap IconImage;
|
||||
public SKBitmap RarityBackgroundImage;
|
||||
public SKBitmap[] UserFacingFlags;
|
||||
public SKColor[] RarityBackgroundColors;
|
||||
public SKColor[] RarityBorderColor;
|
||||
public string DisplayName;
|
||||
public string Description;
|
||||
public string ShortDescription;
|
||||
public string CosmeticSource;
|
||||
public int Size = 512; // keep it 512 (or a multiple of 512) if you don't want blurry icons
|
||||
public int AdditionalSize; // must be increased if there are weapon stats, hero abilities or more to draw/show
|
||||
public int Margin = 2;
|
||||
public List<Statistic> Stats;
|
||||
|
||||
public BaseIcon()
|
||||
{
|
||||
FallbackImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png"))?.Stream);
|
||||
IconImage = FallbackImage;
|
||||
RarityBackgroundImage = null;
|
||||
UserFacingFlags = null;
|
||||
RarityBackgroundColors = new[] { SKColor.Parse("5EBC36"), SKColor.Parse("305C15") };
|
||||
RarityBorderColor = new[] { SKColor.Parse("74EF52"), SKColor.Parse("74EF52") };
|
||||
DisplayName = "";
|
||||
Description = "";
|
||||
ShortDescription = "";
|
||||
CosmeticSource = "";
|
||||
Stats = new List<Statistic>();
|
||||
}
|
||||
|
||||
public BaseIcon(IUExport export, string assetName, bool forceHR) : this()
|
||||
{
|
||||
if (export.GetExport<ObjectProperty>("Series") is { } series)
|
||||
Serie.GetRarity(this, series);
|
||||
else if (Settings.Default.UseGameColors) // override default green
|
||||
Rarity.GetInGameRarity(this, export.GetExport<EnumProperty>("Rarity")); // uncommon will be triggered by Rarity being null
|
||||
else if (export.GetExport<EnumProperty>("Rarity") is { } rarity)
|
||||
Rarity.GetHardCodedRarity(this, rarity);
|
||||
|
||||
if (export.GetExport<ObjectProperty>("HeroDefinition", "WeaponDefinition") is { } itemDef)
|
||||
LargeSmallImage.GetPreviewImage(this, itemDef, assetName, forceHR);
|
||||
else if (export.GetExport<SoftObjectProperty>(forceHR ? "LargePreviewImage" : "SmallPreviewImage", forceHR ? "ItemDisplayAsset" : "SmallImage", forceHR ? "SidePanelIcon" : "ToastIcon") is { } previewImage)
|
||||
LargeSmallImage.GetPreviewImage(this, previewImage);
|
||||
else if (export.GetExport<ObjectProperty>("access_item") is { } accessItem)
|
||||
{
|
||||
Package p = Utils.GetPropertyPakPackage(accessItem.Value.Resource.OuterIndex.Resource.ObjectName.String);
|
||||
if (p.HasExport() && !p.Equals(default))
|
||||
{
|
||||
var d = p.GetExport<UObject>();
|
||||
if (d != null)
|
||||
{
|
||||
IconImage = new BaseIcon(d, accessItem.Value.Resource.ObjectName.String + ".uasset", false).IconImage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (export.GetExport<TextProperty>("DisplayName", "DefaultHeaderText", "UIDisplayName") is { } displayName)
|
||||
DisplayName = Text.GetTextPropertyBase(displayName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Order:
|
||||
/// 1. Rarity
|
||||
/// 2. Image
|
||||
/// 3. Text
|
||||
/// 1. DisplayName
|
||||
/// 2. Description
|
||||
/// 3. Misc
|
||||
/// 4. GameplayTags
|
||||
/// 1. order doesn't matter
|
||||
/// 2. the importance here is to get the description before gameplay tags
|
||||
/// </summary>
|
||||
public BaseIcon(IUExport export, string exportType, ref string assetName) : this()
|
||||
{
|
||||
// rarity
|
||||
if (export.GetExport<ObjectProperty>("Series") is { } series)
|
||||
Serie.GetRarity(this, series);
|
||||
else if (Settings.Default.UseGameColors) // override default green
|
||||
Rarity.GetInGameRarity(this, export.GetExport<EnumProperty>("Rarity")); // uncommon will be triggered by Rarity being null
|
||||
else if (export.GetExport<EnumProperty>("Rarity") is { } rarity)
|
||||
Rarity.GetHardCodedRarity(this, rarity);
|
||||
|
||||
// image
|
||||
if (Settings.Default.UseItemShopIcon &&
|
||||
DisplayAssetImage.GetDisplayAssetImage(this, export, ref assetName))
|
||||
{ } // ^^^^ will return false if image not found, if so, we try to get the normal icon
|
||||
else if (export.GetExport<ObjectProperty>("HeroDefinition", "WeaponDefinition") is { } itemDef)
|
||||
LargeSmallImage.GetPreviewImage(this, itemDef, assetName);
|
||||
else if (export.GetExport<SoftObjectProperty>("LargePreviewImage", "SidePanelIcon", "EntryListIcon", "SmallPreviewImage", "ItemDisplayAsset") is { } previewImage)
|
||||
LargeSmallImage.GetPreviewImage(this, previewImage);
|
||||
else if (export.GetExport<ObjectProperty>("SmallPreviewImage", "ToastIcon") is { } smallPreviewImage)
|
||||
IconImage = Utils.GetObjectTexture(smallPreviewImage);
|
||||
else if (export.GetExport<StructProperty>("IconBrush") is { } iconBrush) // abilities
|
||||
LargeSmallImage.GetPreviewImage(this, iconBrush);
|
||||
|
||||
// text
|
||||
if (export.GetExport<TextProperty>("DisplayName", "DefaultHeaderText", "UIDisplayName") is { } displayName)
|
||||
DisplayName = Text.GetTextPropertyBase(displayName);
|
||||
if (export.GetExport<TextProperty>("Description", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription") is { } description)
|
||||
Description = Text.GetTextPropertyBase(description);
|
||||
else if (export.GetExport<ArrayProperty>("Description") is { } arrayDescription) // abilities
|
||||
Description = Text.GetTextPropertyBase(arrayDescription);
|
||||
if (export.GetExport<StructProperty>("MaxStackSize") is { } maxStackSize)
|
||||
ShortDescription = Text.GetMaxStackSize(maxStackSize);
|
||||
else if (export.GetExport<StructProperty>("XpRewardAmount") is { } xpRewardAmount)
|
||||
ShortDescription = Text.GetXpRewardAmount(xpRewardAmount);
|
||||
else if (export.GetExport<TextProperty>("ShortDescription", "UIDisplaySubName") is { } shortDescription)
|
||||
ShortDescription = Text.GetTextPropertyBase(shortDescription);
|
||||
else if (exportType.Equals("AthenaItemWrapDefinition")) // if no ShortDescription it's most likely a wrap
|
||||
ShortDescription = Localizations.GetLocalization("Fort.Cosmetics", "ItemWrapShortDescription", "Wrap");
|
||||
|
||||
// gameplaytags
|
||||
if (export.GetExport<StructProperty>("GameplayTags") is { } gameplayTags)
|
||||
GameplayTag.GetGameplayTags(this, gameplayTags, exportType);
|
||||
else if (export.GetExport<ObjectProperty>("cosmetic_item") is { } cosmeticItem) // variants
|
||||
CosmeticSource = cosmeticItem.Value.Resource.ObjectName.String;
|
||||
|
||||
if (Settings.Default.DrawStats && export.GetExport<SoftObjectProperty>("AmmoData") is { } ammoData)
|
||||
Statistics.GetAmmoData(this, ammoData);
|
||||
if (Settings.Default.DrawStats && export.GetExport<StructProperty>("WeaponStatHandle") is { } weaponStatHandle &&
|
||||
(exportType.Equals("FortWeaponMeleeItemDefinition") ||
|
||||
(export.GetExport<SoftObjectProperty>("StatList") is { } statList &&
|
||||
!statList.Value.AssetPathName.String.StartsWith("/Game/UI/Tooltips/NoTooltipStats"))))
|
||||
{
|
||||
Statistics.GetWeaponStats(this, weaponStatHandle);
|
||||
}
|
||||
if (Settings.Default.DrawStats && export.GetExport<ObjectProperty>("HeroGameplayDefinition") is { } heroGameplayDefinition)
|
||||
Statistics.GetHeroStats(this, heroGameplayDefinition);
|
||||
|
||||
/* Please do not add Schematics support because it takes way too much memory */
|
||||
/* Thank the STW Dev Team for using a 5,69Mb file to get... Oh nvm, they all left */
|
||||
|
||||
AdditionalSize = 48 * Stats.Count;
|
||||
}
|
||||
|
||||
SKBitmap IBase.FallbackImage => FallbackImage;
|
||||
SKBitmap IBase.IconImage => IconImage;
|
||||
SKColor[] IBase.RarityBackgroundColors => RarityBackgroundColors;
|
||||
SKColor[] IBase.RarityBorderColor => RarityBorderColor;
|
||||
string IBase.DisplayName => DisplayName;
|
||||
string IBase.Description => Description;
|
||||
int IBase.Width => Size;
|
||||
int IBase.Height => Size;
|
||||
int IBase.Margin => Margin;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
using FModel.Creator.Rarities;
|
||||
using FModel.Creator.Texts;
|
||||
using FModel.PakReader;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Bases
|
||||
{
|
||||
public class BaseItemAccess
|
||||
{
|
||||
private readonly SKPaint descriptionPaint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Text.TypeFaces.DescriptionTypeface,
|
||||
TextSize = 13,
|
||||
Color = SKColors.White,
|
||||
};
|
||||
|
||||
public BaseIcon Item;
|
||||
public string SItem;
|
||||
public SKBitmap Lock;
|
||||
public SKBitmap Unlock;
|
||||
public string DisplayName;
|
||||
public string Description;
|
||||
public string UnlockDescription;
|
||||
public int Size = 512; // keep it 512 (or a multiple of 512) if you don't want blurry icons
|
||||
|
||||
public BaseItemAccess()
|
||||
{
|
||||
Item = new BaseIcon();
|
||||
SItem = "";
|
||||
Lock = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Locks/T-Icon-Lock-128").Resize(24, 24);
|
||||
Unlock = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Locks/T-Icon-Unlocked-128").Resize(24, 24);
|
||||
DisplayName = "";
|
||||
Description = "";
|
||||
UnlockDescription = "";
|
||||
}
|
||||
|
||||
public BaseItemAccess(IUExport export) : this()
|
||||
{
|
||||
if (export.GetExport<ObjectProperty>("access_item") is ObjectProperty accessItem)
|
||||
{
|
||||
SItem = accessItem.Value.Resource.ObjectName.String;
|
||||
Package p = Utils.GetPropertyPakPackage(accessItem.Value.Resource.OuterIndex.Resource.ObjectName.String);
|
||||
if (p.HasExport() && !p.Equals(default))
|
||||
{
|
||||
var d = p.GetExport<UObject>();
|
||||
if (d != null)
|
||||
{
|
||||
Item = new BaseIcon(d, SItem + ".uasset", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (export.GetExport<TextProperty>("DisplayName") is TextProperty displayName)
|
||||
DisplayName = Text.GetTextPropertyBase(displayName);
|
||||
if (export.GetExport<TextProperty>("Description") is TextProperty description)
|
||||
Description = Text.GetTextPropertyBase(description);
|
||||
if (export.GetExport<TextProperty>("UnlockDescription") is TextProperty unlockDescription)
|
||||
UnlockDescription = Text.GetTextPropertyBase(unlockDescription);
|
||||
}
|
||||
|
||||
public void Draw(SKCanvas c)
|
||||
{
|
||||
Rarity.DrawRarity(c, Item);
|
||||
|
||||
int size = 45;
|
||||
int left = Size / 2;
|
||||
SKPaint namePaint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Text.TypeFaces.DisplayNameTypeface,
|
||||
TextSize = size,
|
||||
Color = SKColors.White,
|
||||
TextAlign = SKTextAlign.Center,
|
||||
};
|
||||
if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic)
|
||||
{
|
||||
SKShaper shaper = new SKShaper(namePaint.Typeface);
|
||||
float shapedTextWidth;
|
||||
|
||||
while (true)
|
||||
{
|
||||
SKShaper.Result shapedText = shaper.Shape(DisplayName, namePaint);
|
||||
shapedTextWidth = shapedText.Points[^1].X + namePaint.TextSize / 2f;
|
||||
|
||||
if (shapedTextWidth > (Size - (Item.Margin * 2)))
|
||||
{
|
||||
namePaint.TextSize -= 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
c.DrawShapedText(shaper, DisplayName, (Size - shapedTextWidth) / 2f, Item.Margin * 8 + size, namePaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (namePaint.MeasureText(DisplayName) > (Size - (Item.Margin * 2)))
|
||||
{
|
||||
namePaint.TextSize = size -= 2;
|
||||
}
|
||||
c.DrawText(DisplayName, left, Item.Margin * 8 + size, namePaint);
|
||||
}
|
||||
|
||||
int topBase = Item.Margin + size * 2;
|
||||
c.DrawBitmap(Lock, new SKRect(50, topBase, 50 + Lock.Width, topBase + Lock.Height),
|
||||
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
|
||||
Helper.DrawMultilineText(c, UnlockDescription, Size, Item.Margin, ETextSide.Left,
|
||||
new SKRect(70 + Lock.Width, topBase + 10, Size - 50, 256), descriptionPaint, out topBase);
|
||||
|
||||
c.DrawBitmap(Unlock, new SKRect(50, topBase, 50 + Unlock.Width, topBase + Unlock.Height),
|
||||
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
|
||||
Helper.DrawMultilineText(c, Description, Size, Item.Margin, ETextSide.Left,
|
||||
new SKRect(70 + Unlock.Width, topBase + 10, Size - 50, 256), descriptionPaint, out topBase);
|
||||
|
||||
int h = Size - Item.Margin - topBase;
|
||||
c.DrawBitmap(Item.IconImage ?? Item.FallbackImage, new SKRect(left - h / 2, topBase, left + h / 2, Size - Item.Margin),
|
||||
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
|
||||
|
||||
c.DrawText(SItem, Size - (Item.Margin * 2.5f), Size - (Item.Margin * 2.5f), new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Text.TypeFaces.BottomDefaultTypeface ?? Text.TypeFaces.DefaultTypeface,
|
||||
TextSize = 15,
|
||||
Color = SKColors.White,
|
||||
TextAlign = SKTextAlign.Right,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
using FModel.Creator.Texts;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases
|
||||
{
|
||||
public class BaseMapUIData
|
||||
{
|
||||
public SKBitmap Splash;
|
||||
public SKBitmap VLogo;
|
||||
public string DisplayName;
|
||||
public string Description;
|
||||
public string Coordinates;
|
||||
public int Width = 1920;
|
||||
public int Height = 1080;
|
||||
|
||||
public BaseMapUIData()
|
||||
{
|
||||
Splash = null;
|
||||
VLogo = null;
|
||||
DisplayName = "";
|
||||
Description = "";
|
||||
Coordinates = "";
|
||||
}
|
||||
|
||||
public BaseMapUIData(IUExport export) : this()
|
||||
{
|
||||
if (export.GetExport<TextProperty>("DisplayName") is TextProperty displayName)
|
||||
DisplayName = Text.GetTextPropertyBase(displayName) ?? "";
|
||||
if (export.GetExport<TextProperty>("Description") is TextProperty description)
|
||||
Description = Text.GetTextPropertyBase(description) ?? "";
|
||||
if (export.GetExport<TextProperty>("Coordinates") is TextProperty coordinates)
|
||||
Coordinates = Text.GetTextPropertyBase(coordinates) ?? "";
|
||||
|
||||
if (export.GetExport<ObjectProperty>("Splash") is ObjectProperty icon)
|
||||
Splash = Utils.GetObjectTexture(icon);
|
||||
|
||||
VLogo = Utils.GetTexture("/Game/UI/Shared/Icons/Valorant_logo_cutout").Resize(48, 48);
|
||||
|
||||
if (Splash != null)
|
||||
{
|
||||
Width = Splash.Width;
|
||||
Height = Splash.Height;
|
||||
}
|
||||
}
|
||||
|
||||
public void Draw(SKCanvas c)
|
||||
{
|
||||
int paddingLR = 80;
|
||||
int paddingTB = 35;
|
||||
int nameSize = 200;
|
||||
int descriptionSize = 30;
|
||||
using var namePaint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Text.TypeFaces.DisplayNameTypeface,
|
||||
TextSize = nameSize,
|
||||
TextAlign = SKTextAlign.Left,
|
||||
Color = SKColor.Parse("FFFBFA")
|
||||
};
|
||||
while (namePaint.MeasureText(DisplayName) > Width - (paddingLR * 2))
|
||||
{
|
||||
namePaint.TextSize = nameSize -= 2;
|
||||
}
|
||||
using var descriptionPaint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Text.TypeFaces.DescriptionTypeface,
|
||||
TextSize = descriptionSize,
|
||||
TextAlign = SKTextAlign.Left,
|
||||
Color = SKColor.Parse("FFFBFA")
|
||||
};
|
||||
while (descriptionPaint.MeasureText(Description) > Width - (paddingLR * 2))
|
||||
{
|
||||
descriptionPaint.TextSize = descriptionSize -= 2;
|
||||
}
|
||||
|
||||
c.DrawBitmap(Splash, new SKRect(0, 0, Width, Height), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
|
||||
c.DrawText(DisplayName.ToUpper(), paddingLR, paddingTB + namePaint.TextSize, namePaint);
|
||||
c.DrawRect(new SKRect(paddingLR + 2.5f, paddingTB + 25 + namePaint.TextSize, paddingLR + 202.5f, paddingTB + 27.5f + namePaint.TextSize), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true, Color = SKColor.Parse("5AFFFBFA") });
|
||||
c.DrawText(Description, paddingLR + 2.5f, paddingTB + 40 + namePaint.TextSize + descriptionPaint.TextSize, descriptionPaint);
|
||||
|
||||
descriptionPaint.Typeface = Text.TypeFaces.BundleDefaultTypeface;
|
||||
c.DrawText(Coordinates.ToUpper(), paddingLR, Height - paddingTB - descriptionPaint.TextSize, descriptionPaint);
|
||||
|
||||
if (VLogo != null)
|
||||
{
|
||||
c.DrawBitmap(VLogo, new SKRect(Width - VLogo.Width - paddingLR, paddingLR, Width - paddingLR, paddingLR + VLogo.Height), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
|
||||
c.DrawRect(new SKRect(Width - VLogo.Width - paddingLR, paddingLR + VLogo.Height + 5, Width - paddingLR, paddingLR + VLogo.Height + 7.5f), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true, Color = SKColor.Parse("FFFBFA") });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.Objects;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
|
||||
namespace FModel.Creator.Bases
|
||||
{
|
||||
public class BaseOffer
|
||||
{
|
||||
public SKBitmap FallbackImage;
|
||||
public SKBitmap IconImage;
|
||||
public SKColor[] RarityBackgroundColors;
|
||||
public SKColor RarityBorderColor;
|
||||
public int Size = 512;
|
||||
public int Margin = 2;
|
||||
|
||||
public BaseOffer()
|
||||
{
|
||||
FallbackImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png")).Stream);
|
||||
IconImage = FallbackImage;
|
||||
RarityBackgroundColors = new SKColor[2] { SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69") };
|
||||
RarityBorderColor = SKColor.Parse("9092AB");
|
||||
}
|
||||
|
||||
public BaseOffer(IUExport export) : this()
|
||||
{
|
||||
if (export.GetExport<StructProperty>("DetailsImage", "TileImage") is StructProperty typeImage)
|
||||
{
|
||||
if (typeImage.Value is UObject t && t.TryGetValue("ResourceObject", out var v) && v is ObjectProperty resourceObject)
|
||||
{
|
||||
IconImage = Utils.GetObjectTexture(resourceObject);
|
||||
}
|
||||
}
|
||||
|
||||
if (export.GetExport<StructProperty>("Gradient") is StructProperty gradient)
|
||||
{
|
||||
if (gradient.Value is UObject g &&
|
||||
g.TryGetValue("Start", out var s1) && s1 is StructProperty t1 && t1.Value is FLinearColor start &&
|
||||
g.TryGetValue("Stop", out var s2) && s2 is StructProperty t2 && t2.Value is FLinearColor stop)
|
||||
{
|
||||
RarityBackgroundColors = new SKColor[2] { SKColor.Parse(start.Hex), SKColor.Parse(stop.Hex) };
|
||||
}
|
||||
}
|
||||
|
||||
if (export.GetExport<StructProperty>("Background") is StructProperty background)
|
||||
{
|
||||
if (background.Value is FLinearColor b)
|
||||
{
|
||||
RarityBorderColor = SKColor.Parse(b.Hex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawBackground(SKCanvas c)
|
||||
{
|
||||
if (RarityBackgroundColors[0] == RarityBackgroundColors[1])
|
||||
RarityBackgroundColors[0] = RarityBorderColor;
|
||||
|
||||
RarityBackgroundColors[0].ToHsl(out var _, out var _, out var l1);
|
||||
RarityBackgroundColors[1].ToHsl(out var _, out var _, out var l2);
|
||||
bool reverse = l1 > l2;
|
||||
|
||||
// border
|
||||
c.DrawRect(new SKRect(0, 0, Size, Size),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Color = RarityBorderColor
|
||||
});
|
||||
|
||||
c.DrawRect(new SKRect(Margin, Margin, Size - Margin, Size - Margin),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateRadialGradient(
|
||||
new SKPoint(Size / 2, Size / 2),
|
||||
Size / 5 * 4,
|
||||
new SKColor[2] { reverse ? RarityBackgroundColors[0] : RarityBackgroundColors[1], reverse ? RarityBackgroundColors[1] : RarityBackgroundColors[0] },
|
||||
SKShaderTileMode.Clamp)
|
||||
});
|
||||
}
|
||||
|
||||
public void DrawImage(SKCanvas c)
|
||||
{
|
||||
c.DrawBitmap(IconImage ?? FallbackImage, new SKRect(Margin, Margin, Size - Margin, Size - Margin),
|
||||
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.Objects;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
|
||||
namespace FModel.Creator.Bases
|
||||
{
|
||||
public class BaseOfferMaterial
|
||||
{
|
||||
public SKBitmap FallbackImage;
|
||||
public SKBitmap IconImage;
|
||||
public SKBitmap RarityBackgroundImage;
|
||||
public SKColor[] RarityBackgroundColors;
|
||||
public SKColor RarityBorderColor;
|
||||
public int Size = 512;
|
||||
public int Margin = 2;
|
||||
|
||||
public BaseOfferMaterial()
|
||||
{
|
||||
FallbackImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png")).Stream);
|
||||
IconImage = null;
|
||||
RarityBackgroundImage = null;
|
||||
RarityBackgroundColors = new SKColor[2] { SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69") };
|
||||
RarityBorderColor = SKColor.Parse("9092AB");
|
||||
}
|
||||
|
||||
public BaseOfferMaterial(IUExport export) : this()
|
||||
{
|
||||
if (export.GetExport<ArrayProperty>("VectorParameterValues") is ArrayProperty vectorParameterValues)
|
||||
{
|
||||
foreach (StructProperty vectorParameter in vectorParameterValues.Value)
|
||||
{
|
||||
if (vectorParameter.Value is UObject parameter &&
|
||||
parameter.TryGetValue("ParameterValue", out var i) && i is StructProperty v && v.Value is FLinearColor value &&
|
||||
parameter.TryGetValue("ParameterInfo", out var i1) && i1 is StructProperty i2 && i2.Value is UObject info &&
|
||||
info.TryGetValue("Name", out var j1) && j1 is NameProperty name)
|
||||
{
|
||||
if (name.Value.String.Equals("Background_Color_A"))
|
||||
{
|
||||
RarityBackgroundColors[0] = SKColor.Parse(value.Hex);
|
||||
RarityBorderColor = RarityBackgroundColors[0];
|
||||
}
|
||||
else if (name.Value.String.Equals("Background_Color_B"))
|
||||
{
|
||||
RarityBackgroundColors[1] = SKColor.Parse(value.Hex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (export.GetExport<ArrayProperty>("TextureParameterValues") is ArrayProperty textureParameterValues)
|
||||
{
|
||||
foreach (StructProperty textureParameter in textureParameterValues.Value)
|
||||
{
|
||||
if (textureParameter.Value is UObject parameter &&
|
||||
parameter.TryGetValue("ParameterValue", out var i) && i is ObjectProperty value &&
|
||||
parameter.TryGetValue("ParameterInfo", out var i1) && i1 is StructProperty i2 && i2.Value is UObject info &&
|
||||
info.TryGetValue("Name", out var j1) && j1 is NameProperty name)
|
||||
{
|
||||
if (name.Value.String.Equals("SeriesTexture"))
|
||||
{
|
||||
RarityBackgroundImage = Utils.GetObjectTexture(value);
|
||||
}
|
||||
else if (IconImage == null && value.Value.Resource.OuterIndex.Resource != null && (name.Value.String.Equals("OfferImage") || name.Value.String.Contains("Texture")))
|
||||
{
|
||||
IconImage = Utils.GetObjectTexture(value);
|
||||
if (IconImage == null) IconImage = Utils.GetTexture($"{value.Value.Resource.OuterIndex.Resource.ObjectName.String}_1");
|
||||
if (IconImage == null) IconImage = Utils.GetTexture($"{value.Value.Resource.OuterIndex.Resource.ObjectName.String}_01");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (IconImage == null)
|
||||
IconImage = FallbackImage;
|
||||
}
|
||||
|
||||
public void DrawBackground(SKCanvas c)
|
||||
{
|
||||
if (RarityBackgroundColors[0] == RarityBackgroundColors[1])
|
||||
RarityBackgroundColors[0] = RarityBorderColor;
|
||||
|
||||
RarityBackgroundColors[0].ToHsl(out var _, out var _, out var l1);
|
||||
RarityBackgroundColors[1].ToHsl(out var _, out var _, out var l2);
|
||||
bool reverse = l1 > l2;
|
||||
|
||||
// border
|
||||
c.DrawRect(new SKRect(0, 0, Size, Size),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Color = RarityBorderColor
|
||||
});
|
||||
|
||||
c.DrawRect(new SKRect(Margin, Margin, Size - Margin, Size - Margin),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateRadialGradient(
|
||||
new SKPoint(Size / 2, Size / 2),
|
||||
Size / 5 * 4,
|
||||
new SKColor[2] { reverse ? RarityBackgroundColors[0] : RarityBackgroundColors[1], reverse ? RarityBackgroundColors[1] : RarityBackgroundColors[0] },
|
||||
SKShaderTileMode.Clamp)
|
||||
});
|
||||
}
|
||||
|
||||
public void DrawImage(SKCanvas c)
|
||||
{
|
||||
if (RarityBackgroundImage != null)
|
||||
c.DrawBitmap(RarityBackgroundImage, new SKRect(Margin, Margin, Size - Margin, Size - Margin),
|
||||
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
|
||||
|
||||
c.DrawBitmap(IconImage ?? FallbackImage, new SKRect(Margin, Margin, Size - Margin, Size - Margin),
|
||||
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
using System;
|
||||
using System.Windows;
|
||||
|
||||
using FModel.Creator.Texts;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
using FModel.Utils;
|
||||
|
||||
using Fortnite_API.Objects;
|
||||
using Fortnite_API.Objects.V1;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases
|
||||
{
|
||||
public class BasePlaylist : IBase
|
||||
{
|
||||
public SKBitmap FallbackImage { get; }
|
||||
public SKBitmap IconImage { get; }
|
||||
public SKColor[] RarityBackgroundColors { get; }
|
||||
public SKColor[] RarityBorderColor { get; }
|
||||
public string DisplayName { get; }
|
||||
public string Description { get; }
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
public int Margin { get; } = 2;
|
||||
|
||||
public BasePlaylist(IUExport export)
|
||||
{
|
||||
FallbackImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png"))?.Stream);
|
||||
IconImage = FallbackImage;
|
||||
RarityBackgroundColors = new[] { SKColor.Parse("5EBC36"), SKColor.Parse("305C15") };
|
||||
RarityBorderColor = new[] { SKColor.Parse("74EF52"), SKColor.Parse("74EF52") };
|
||||
|
||||
if (export.GetExport<TextProperty>("UIDisplayName", "DisplayName") is { } displayName)
|
||||
DisplayName = Text.GetTextPropertyBase(displayName);
|
||||
if (export.GetExport<TextProperty>("UIDescription", "Description") is { } description)
|
||||
Description = Text.GetTextPropertyBase(description);
|
||||
|
||||
Width = 1024;
|
||||
Height = 512;
|
||||
|
||||
if (export.GetExport<NameProperty>("PlaylistName") is { } playlistName && !playlistName.Value.IsNone)
|
||||
{
|
||||
ApiResponse<PlaylistV1> playlist = Endpoints.FortniteAPIClient.V1.Playlists.Get(playlistName.Value.String);
|
||||
|
||||
if (playlist.IsSuccess && playlist.Data.Images.HasShowcase)
|
||||
{
|
||||
byte[] imageBytes = Endpoints.GetRawData(playlist.Data.Images.Showcase);
|
||||
|
||||
if (imageBytes != null)
|
||||
{
|
||||
IconImage = SKBitmap.Decode(imageBytes);
|
||||
Width = IconImage.Width;
|
||||
Height = IconImage.Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,318 +0,0 @@
|
|||
using FModel.Creator.Bundles;
|
||||
using FModel.Creator.Texts;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
|
||||
namespace FModel.Creator.Bases
|
||||
{
|
||||
public class BaseSeason
|
||||
{
|
||||
public string DisplayName;
|
||||
public string FolderName;
|
||||
public string Watermark;
|
||||
public Reward FirstWinReward;
|
||||
public Dictionary<int, List<Reward>> BookXpSchedule;
|
||||
public Header DisplayStyle;
|
||||
public int Width = 1024;
|
||||
public int HeaderHeight = 261; // height is the header basically
|
||||
public int AdditionalSize = 50; // must be increased depending on the number of quests to draw
|
||||
|
||||
public BaseSeason()
|
||||
{
|
||||
DisplayName = "";
|
||||
FolderName = "";
|
||||
Watermark = Properties.Settings.Default.ChallengeBannerWatermark;
|
||||
FirstWinReward = null;
|
||||
BookXpSchedule = new Dictionary<int, List<Reward>>();
|
||||
DisplayStyle = new Header();
|
||||
}
|
||||
|
||||
public BaseSeason(IUExport export, string assetFolder) : this()
|
||||
{
|
||||
if (export.GetExport<TextProperty>("DisplayName") is TextProperty displayName)
|
||||
DisplayName = Text.GetTextPropertyBase(displayName);
|
||||
|
||||
if (export.GetExport<StructProperty>("SeasonFirstWinRewards") is StructProperty s && s.Value is UObject seasonFirstWinRewards &&
|
||||
seasonFirstWinRewards.GetExport<ArrayProperty>("Rewards") is ArrayProperty rewards)
|
||||
{
|
||||
foreach (StructProperty reward in rewards.Value)
|
||||
{
|
||||
if (reward.Value is UObject o &&
|
||||
o.GetExport<SoftObjectProperty>("ItemDefinition") is SoftObjectProperty itemDefinition &&
|
||||
o.GetExport<IntProperty>("Quantity") is IntProperty quantity)
|
||||
{
|
||||
FirstWinReward = new Reward(quantity, itemDefinition.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (export.GetExport<StructProperty>("BookXpScheduleFree") is StructProperty r2 && r2.Value is UObject bookXpScheduleFree &&
|
||||
bookXpScheduleFree.GetExport<ArrayProperty>("Levels") is ArrayProperty levels2)
|
||||
{
|
||||
for (int i = 0; i < levels2.Value.Length; i++)
|
||||
{
|
||||
BookXpSchedule[i] = new List<Reward>(); // init list for all reward index and once
|
||||
if (levels2.Value[i] is StructProperty level && level.Value is UObject l &&
|
||||
l.GetExport<ArrayProperty>("Rewards") is ArrayProperty elRewards && elRewards.Value.Length > 0)
|
||||
{
|
||||
foreach (StructProperty reward in elRewards.Value)
|
||||
{
|
||||
if (reward.Value is UObject o &&
|
||||
o.GetExport<SoftObjectProperty>("ItemDefinition") is SoftObjectProperty itemDefinition &&
|
||||
!itemDefinition.Value.AssetPathName.String.Contains("/Items/Tokens/") &&
|
||||
o.GetExport<IntProperty>("Quantity") is IntProperty quantity)
|
||||
{
|
||||
BookXpSchedule[i].Add(new Reward(quantity, itemDefinition.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (export.GetExport<StructProperty>("BookXpSchedulePaid") is StructProperty r1 && r1.Value is UObject bookXpSchedulePaid &&
|
||||
bookXpSchedulePaid.GetExport<ArrayProperty>("Levels") is ArrayProperty levels1)
|
||||
{
|
||||
for (int i = 0; i < levels1.Value.Length; i++)
|
||||
{
|
||||
if (levels1.Value[i] is StructProperty level && level.Value is UObject l &&
|
||||
l.GetExport<ArrayProperty>("Rewards") is ArrayProperty elRewards && elRewards.Value.Length > 0)
|
||||
{
|
||||
foreach (StructProperty reward in elRewards.Value)
|
||||
{
|
||||
if (reward.Value is UObject o &&
|
||||
o.GetExport<SoftObjectProperty>("ItemDefinition") is SoftObjectProperty itemDefinition &&
|
||||
o.GetExport<IntProperty>("Quantity") is IntProperty quantity)
|
||||
{
|
||||
BookXpSchedule[i].Add(new Reward(quantity, itemDefinition.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FolderName = assetFolder;
|
||||
AdditionalSize += 100 * (BookXpSchedule.Count / 10);
|
||||
}
|
||||
|
||||
public void Draw(SKCanvas c)
|
||||
{
|
||||
DrawHeaderPaint(c);
|
||||
DrawHeaderText(c);
|
||||
|
||||
using SKPaint paint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
TextSize = 15,
|
||||
Color = SKColors.White,
|
||||
TextAlign = SKTextAlign.Center,
|
||||
Typeface = Text.TypeFaces.BottomDefaultTypeface ?? Text.TypeFaces.DisplayNameTypeface
|
||||
};
|
||||
using SKPaint bg = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Color = SKColor.Parse("#0F5CAF"),
|
||||
};
|
||||
using SKPaint rarity = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Color = SKColors.White,
|
||||
};
|
||||
using SKPaint icon = new SKPaint
|
||||
{
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
IsAntialias = true
|
||||
};
|
||||
|
||||
int y = HeaderHeight + 50;
|
||||
int defaultSize = 80;
|
||||
int x = 20;
|
||||
foreach (var (index, reward) in BookXpSchedule)
|
||||
{
|
||||
if (index == 0 || reward.Count == 0)
|
||||
continue;
|
||||
|
||||
c.DrawText(index.ToString(), new SKPoint(x + (defaultSize / 2), y - 5), paint);
|
||||
|
||||
var theReward = reward[0].TheReward;
|
||||
if (theReward == null)
|
||||
continue;
|
||||
|
||||
rarity.Color = theReward.RarityBackgroundColors[0];
|
||||
c.DrawRect(new SKRect(x, y, x + defaultSize, y + defaultSize), bg);
|
||||
c.DrawBitmap(reward[0].RewardIcon, new SKPoint(x, y), icon);
|
||||
var pathBottom = new SKPath { FillType = SKPathFillType.EvenOdd };
|
||||
pathBottom.MoveTo(x, y + defaultSize);
|
||||
pathBottom.LineTo(x, y + defaultSize - (defaultSize / 25 * 2.5f));
|
||||
pathBottom.LineTo(x + defaultSize, y + defaultSize - (defaultSize / 25 * 4.5f));
|
||||
pathBottom.LineTo(x + defaultSize, y + defaultSize);
|
||||
pathBottom.Close();
|
||||
c.DrawPath(pathBottom, rarity);
|
||||
|
||||
if (index != 1 && index % 10 == 0)
|
||||
{
|
||||
y += defaultSize + 20;
|
||||
x = 20;
|
||||
}
|
||||
else
|
||||
{
|
||||
x += defaultSize + 20;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawHeaderText(SKCanvas c)
|
||||
{
|
||||
using SKPaint paint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Text.TypeFaces.BundleDisplayNameTypeface,
|
||||
TextSize = 50,
|
||||
Color = SKColors.White,
|
||||
TextAlign = SKTextAlign.Left,
|
||||
};
|
||||
|
||||
string text = DisplayName.ToUpper();
|
||||
int x = 300;
|
||||
if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic)
|
||||
{
|
||||
|
||||
SKShaper shaper = new SKShaper(paint.Typeface);
|
||||
float shapedTextWidth;
|
||||
|
||||
while (true)
|
||||
{
|
||||
SKShaper.Result shapedText = shaper.Shape(text, paint);
|
||||
shapedTextWidth = shapedText.Points[^1].X + paint.TextSize / 2f;
|
||||
|
||||
if (shapedTextWidth > Width)
|
||||
{
|
||||
paint.TextSize -= 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
//only trigger the fix if The Last char is a digit
|
||||
if (char.IsDigit(text[text.Length - 1]))
|
||||
{
|
||||
int s = text.Count(k => Char.IsDigit(k));
|
||||
float numberwidth = paint.MeasureText(text.Substring(text.Length - s));
|
||||
|
||||
//Draw Number Separately
|
||||
c.DrawShapedText(shaper, text.Substring(text.Length - s), x, 155, paint);
|
||||
|
||||
c.DrawShapedText(shaper, text.Substring(0, text.Length - s), x + numberwidth, 155, paint);
|
||||
}
|
||||
else
|
||||
{
|
||||
//feels bad man
|
||||
c.DrawShapedText(shaper, text, x, 155, paint);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
while (paint.MeasureText(text) > (Width - x))
|
||||
{
|
||||
paint.TextSize -= 2;
|
||||
}
|
||||
c.DrawText(text, x, 155, paint);
|
||||
}
|
||||
|
||||
paint.Color = SKColors.White.WithAlpha(150);
|
||||
paint.TextAlign = SKTextAlign.Right;
|
||||
paint.TextSize = 23;
|
||||
paint.Typeface = Text.TypeFaces.DefaultTypeface;
|
||||
c.DrawText(Watermark
|
||||
.Replace("{BundleName}", text)
|
||||
.Replace("{Date}", DateTime.Now.ToString("dd/MM/yyyy")),
|
||||
Width - 25, HeaderHeight - 40, paint);
|
||||
|
||||
paint.Typeface = Text.TypeFaces.BundleDefaultTypeface;
|
||||
paint.Color = DisplayStyle.SecondaryColor;
|
||||
paint.TextAlign = SKTextAlign.Left;
|
||||
paint.TextSize = 30;
|
||||
c.DrawText(FolderName.ToUpper(), x, 95, paint);
|
||||
}
|
||||
|
||||
private void DrawHeaderPaint(SKCanvas c)
|
||||
{
|
||||
c.DrawRect(new SKRect(0, 0, Width, HeaderHeight), new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Color = DisplayStyle.PrimaryColor
|
||||
});
|
||||
|
||||
if (DisplayStyle.CustomBackground != null && DisplayStyle.CustomBackground.Height != DisplayStyle.CustomBackground.Width)
|
||||
{
|
||||
var bgPaint = new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High, BlendMode = SKBlendMode.Screen };
|
||||
if (Properties.Settings.Default.UseChallengeBanner) bgPaint.Color = SKColors.Transparent.WithAlpha((byte)Properties.Settings.Default.ChallengeBannerOpacity);
|
||||
c.DrawBitmap(DisplayStyle.CustomBackground, new SKRect(0, 0, 1024, 256), bgPaint);
|
||||
}
|
||||
else if (DisplayStyle.DisplayImage != null)
|
||||
{
|
||||
if (DisplayStyle.CustomBackground != null && DisplayStyle.CustomBackground.Height == DisplayStyle.CustomBackground.Width)
|
||||
c.DrawBitmap(DisplayStyle.CustomBackground, new SKRect(0, 0, HeaderHeight, HeaderHeight),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
BlendMode = SKBlendMode.Screen,
|
||||
ImageFilter = SKImageFilter.CreateDropShadow(2.5F, 0, 20, 0, DisplayStyle.SecondaryColor.WithAlpha(25))
|
||||
});
|
||||
|
||||
c.DrawBitmap(DisplayStyle.DisplayImage, new SKRect(0, 0, HeaderHeight, HeaderHeight),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
ImageFilter = SKImageFilter.CreateDropShadow(-2.5F, 0, 20, 0, DisplayStyle.SecondaryColor.WithAlpha(50))
|
||||
});
|
||||
}
|
||||
|
||||
if (FirstWinReward != null)
|
||||
{
|
||||
c.DrawBitmap(FirstWinReward.TheReward.IconImage.Resize(HeaderHeight, HeaderHeight), new SKPoint(0, 0), new SKPaint
|
||||
{
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
IsAntialias = true
|
||||
});
|
||||
}
|
||||
|
||||
SKPath pathTop = new SKPath { FillType = SKPathFillType.EvenOdd };
|
||||
pathTop.MoveTo(0, HeaderHeight);
|
||||
pathTop.LineTo(Width, HeaderHeight);
|
||||
pathTop.LineTo(Width, HeaderHeight - 19);
|
||||
pathTop.LineTo(Width / 2 + 7, HeaderHeight - 23);
|
||||
pathTop.LineTo(Width / 2 + 13, HeaderHeight - 7);
|
||||
pathTop.LineTo(0, HeaderHeight - 19);
|
||||
pathTop.Close();
|
||||
c.DrawPath(pathTop, new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Color = DisplayStyle.SecondaryColor,
|
||||
ImageFilter = SKImageFilter.CreateDropShadow(-5, -5, 0, 0, DisplayStyle.AccentColor.WithAlpha(75))
|
||||
});
|
||||
|
||||
c.DrawRect(new SKRect(0, HeaderHeight, Width, HeaderHeight + AdditionalSize), new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Color = DisplayStyle.PrimaryColor.WithAlpha(200) // default background is black, so i'm kinda lowering the brightness here and that's what i want
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
using FModel.Creator.Stats;
|
||||
using FModel.Creator.Texts;
|
||||
using SkiaSharp;
|
||||
using System.Collections.Generic;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
|
||||
namespace FModel.Creator.Bases
|
||||
{
|
||||
public class BaseUIData
|
||||
{
|
||||
private readonly SKPaint descriptionPaint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Text.TypeFaces.DescriptionTypeface,
|
||||
TextSize = 19.5f,
|
||||
Color = SKColor.Parse("939498"),
|
||||
};
|
||||
|
||||
public SKBitmap IconImage;
|
||||
public string DisplayName;
|
||||
public string Description;
|
||||
public List<Statistic> Abilities;
|
||||
public int Width = 768; // keep it 512 (or a multiple of 512) if you don't want blurry icons
|
||||
public int AdditionalWidth = 0;
|
||||
public int Height = 96;
|
||||
public int Margin = 3;
|
||||
|
||||
public BaseUIData()
|
||||
{
|
||||
IconImage = null;
|
||||
DisplayName = "";
|
||||
Description = "";
|
||||
Abilities = new List<Statistic>();
|
||||
}
|
||||
|
||||
public BaseUIData(IUExport[] exports, int baseIndex) : this()
|
||||
{
|
||||
if (exports[baseIndex].GetExport<TextProperty>("DisplayName") is TextProperty displayName)
|
||||
DisplayName = Text.GetTextPropertyBase(displayName) ?? "";
|
||||
if (exports[baseIndex].GetExport<TextProperty>("Description") is TextProperty description)
|
||||
{
|
||||
Description = Text.GetTextPropertyBase(description) ?? "";
|
||||
if (Description.Equals(DisplayName)) Description = string.Empty;
|
||||
if (!string.IsNullOrEmpty(Description))
|
||||
{
|
||||
Height += (int)descriptionPaint.TextSize * Helper.SplitLines(Description, descriptionPaint, Width - Margin).Count;
|
||||
Height += (int)descriptionPaint.TextSize;
|
||||
}
|
||||
}
|
||||
|
||||
if (exports[baseIndex].GetExport<ObjectProperty>("StoreFeaturedImage", "FullRender", "VerticalPromoImage", "LargeIcon", "DisplayIcon2", "DisplayIcon") is ObjectProperty icon)
|
||||
{
|
||||
SKBitmap raw = Utils.GetObjectTexture(icon);
|
||||
if (raw != null)
|
||||
{
|
||||
float coef = (float)Width / (float)raw.Width;
|
||||
int sizeX = (int)(raw.Width * coef);
|
||||
int sizeY = (int)(raw.Height * coef);
|
||||
Height += sizeY;
|
||||
IconImage = raw.Resize(sizeX, sizeY);
|
||||
}
|
||||
}
|
||||
|
||||
if (exports[baseIndex].GetExport<MapProperty>("Abilities") is MapProperty abilities)
|
||||
{
|
||||
AdditionalWidth = 768;
|
||||
foreach (var (_, value) in abilities.Value)
|
||||
{
|
||||
if (value is ObjectProperty o && o.Value.Resource == null && o.Value.Index > 0)
|
||||
{
|
||||
Statistic s = new Statistic();
|
||||
if (exports[o.Value.Index - 1].GetExport<TextProperty>("DisplayName") is TextProperty aDisplayName)
|
||||
s.DisplayName = Text.GetTextPropertyBase(aDisplayName) ?? "";
|
||||
if (exports[o.Value.Index - 1].GetExport<TextProperty>("Description") is TextProperty aDescription)
|
||||
{
|
||||
s.Description = Text.GetTextPropertyBase(aDescription) ?? "";
|
||||
if (!string.IsNullOrEmpty(Description))
|
||||
{
|
||||
s.Height += (int)descriptionPaint.TextSize * Helper.SplitLines(s.Description, descriptionPaint, Width - Margin).Count;
|
||||
s.Height += (int)descriptionPaint.TextSize * 3;
|
||||
}
|
||||
}
|
||||
if (exports[o.Value.Index - 1].GetExport<ObjectProperty>("DisplayIcon") is ObjectProperty displayIcon)
|
||||
{
|
||||
SKBitmap raw = Utils.GetObjectTexture(displayIcon);
|
||||
if (raw != null) s.Icon = raw.Resize(128, 128);
|
||||
}
|
||||
Abilities.Add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Draw(SKCanvas c)
|
||||
{
|
||||
DrawCenteredTitle(c, DisplayName, 67.5f, out var textSize);
|
||||
|
||||
Helper.DrawMultilineText(c, Description, Width, Margin, ETextSide.Center,
|
||||
new SKRect(Margin, textSize + 56.25f, Width - Margin, Height - 37.5f), descriptionPaint, out var yPos);
|
||||
|
||||
if (IconImage != null)
|
||||
c.DrawBitmap(IconImage, new SKRect(0, yPos, Width, Height),
|
||||
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
|
||||
|
||||
int yaPos = 0;
|
||||
foreach (Statistic ability in Abilities)
|
||||
{
|
||||
int xToAdd = ability.Icon != null ? ability.Icon.Width : 0;
|
||||
textSize = 42.5f;
|
||||
var namePaint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Text.TypeFaces.DisplayNameTypeface,
|
||||
TextSize = textSize,
|
||||
Color = SKColors.White,
|
||||
TextAlign = SKTextAlign.Left,
|
||||
};
|
||||
|
||||
// resize if too long
|
||||
while (namePaint.MeasureText(ability.DisplayName) > Width - 128)
|
||||
{
|
||||
namePaint.TextSize = textSize -= 2;
|
||||
}
|
||||
|
||||
c.DrawText(ability.DisplayName, Width + Margin + xToAdd + 10, yaPos + Margin + textSize, namePaint);
|
||||
|
||||
Helper.DrawMultilineText(c, ability.Description, Width, Width + Margin + xToAdd + 10, ETextSide.Left,
|
||||
new SKRect(Width + Margin + xToAdd + 10, textSize + yaPos + 27.5f, Width + AdditionalWidth - Margin, Height - 27.5f), descriptionPaint, out var _);
|
||||
|
||||
if (ability.Icon != null)
|
||||
c.DrawBitmap(ability.Icon, new SKRect(Width + Margin, yaPos, Width + Margin + ability.Icon.Width, yaPos + ability.Icon.Height),
|
||||
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
|
||||
|
||||
yaPos += ability.Height + 48 + (Margin * 2);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCenteredTitle(SKCanvas c, string title, float textSize, out float outTextSize)
|
||||
{
|
||||
SKPaint namePaint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Text.TypeFaces.DisplayNameTypeface,
|
||||
TextSize = textSize,
|
||||
Color = SKColors.White,
|
||||
TextAlign = SKTextAlign.Center,
|
||||
};
|
||||
float textWidth = namePaint.MeasureText(title);
|
||||
while (textWidth > Width) // resize if too long
|
||||
{
|
||||
namePaint.TextSize = textSize -= 2;
|
||||
textWidth = namePaint.MeasureText(title);
|
||||
}
|
||||
outTextSize = textSize;
|
||||
|
||||
float x1 = (Width / 2 - (textWidth / 2)) - 20;
|
||||
float x2 = (x1 + textWidth) + 40;
|
||||
float y1 = Margin + 5;
|
||||
float y2 = Margin + namePaint.TextSize + 10;
|
||||
|
||||
c.DrawLine(new SKPoint(30, y1 + 5 + (namePaint.TextSize / 2)), new SKPoint(x1 - 30, y1 + 5 + (namePaint.TextSize / 2)), new SKPaint { Color = SKColor.Parse("E2E8E6") });
|
||||
c.DrawLine(new SKPoint(x2 + 30, y1 + 5 + (namePaint.TextSize / 2)), new SKPoint(Width - 30, y1 + 5 + (namePaint.TextSize / 2)), new SKPaint { Color = SKColor.Parse("E2E8E6") });
|
||||
|
||||
c.DrawLine(new SKPoint(x1, y1), new SKPoint(x2, y1), new SKPaint { Color = SKColor.Parse("E2E8E6") }); // top
|
||||
c.DrawLine(new SKPoint(x1, y2 + 5), new SKPoint(x2, y2 + 5), new SKPaint { Color = SKColor.Parse("E2E8E6") }); // bottom
|
||||
c.DrawLine(new SKPoint(x1, y1), new SKPoint(x1, y2 + 5), new SKPaint { Color = SKColor.Parse("E2E8E6") }); // left
|
||||
c.DrawLine(new SKPoint(x2, y1), new SKPoint(x2, y2 + 5), new SKPaint { Color = SKColor.Parse("E2E8E6") }); // right
|
||||
c.DrawRect(new SKRect(x1 + 5, y1 + 5, x2 - 5, y2), new SKPaint { Color = SKColor.Parse("949598") });
|
||||
|
||||
c.DrawText(title, Width / 2, Margin + namePaint.TextSize, namePaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,201 +0,0 @@
|
|||
using FModel.Creator.Texts;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
using System.Collections.Generic;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.Objects;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
|
||||
namespace FModel.Creator.Bases
|
||||
{
|
||||
public class Options
|
||||
{
|
||||
public string Option;
|
||||
public SKColor Color = SKColor.Parse("55C5FC").WithAlpha(150);
|
||||
}
|
||||
|
||||
public class BaseUserOption
|
||||
{
|
||||
private readonly SKPaint descriptionPaint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Text.TypeFaces.DisplayNameTypeface,
|
||||
TextSize = 25,
|
||||
Color = SKColor.Parse("88DBFF"),
|
||||
};
|
||||
|
||||
public string OptionDisplayName;
|
||||
public string OptionDescription;
|
||||
public List<Options> OptionValues = new List<Options>();
|
||||
public int Width = 512;
|
||||
public int Height = 128;
|
||||
public int Margin = 32;
|
||||
|
||||
public BaseUserOption(IUExport export)
|
||||
{
|
||||
if (export.GetExport<TextProperty>("OptionDisplayName") is TextProperty optionDisplayName)
|
||||
OptionDisplayName = Text.GetTextPropertyBase(optionDisplayName).ToUpperInvariant();
|
||||
if (export.GetExport<TextProperty>("OptionDescription") is TextProperty optionDescription)
|
||||
{
|
||||
OptionDescription = Text.GetTextPropertyBase(optionDescription);
|
||||
if (!string.IsNullOrEmpty(OptionDescription))
|
||||
{
|
||||
Height += (int)descriptionPaint.TextSize * Helper.SplitLines(OptionDescription, descriptionPaint, Width - Margin).Count;
|
||||
Height += (int)descriptionPaint.TextSize;
|
||||
}
|
||||
}
|
||||
|
||||
if (export.GetExport<ArrayProperty>("OptionValues") is ArrayProperty optionValues)
|
||||
{
|
||||
OptionValues = new List<Options>(optionValues.Value.Length);
|
||||
for (int i = 0; i < OptionValues.Capacity; i++)
|
||||
{
|
||||
if (optionValues.Value[i] is StructProperty s && s.Value is UObject option)
|
||||
{
|
||||
if (option.TryGetValue("DisplayName", out var v1) && v1 is TextProperty displayName)
|
||||
{
|
||||
var opt = new Options { Option = Text.GetTextPropertyBase(displayName).ToUpperInvariant() };
|
||||
if (option.TryGetValue("Value", out var v) && v is StructProperty value && value.Value is FLinearColor color)
|
||||
opt.Color = SKColor.Parse(color.Hex).WithAlpha(150);
|
||||
OptionValues.Add(opt);
|
||||
}
|
||||
else if (option.TryGetValue("PrimaryAssetName", out var v2) && v2 is NameProperty primaryAssetName)
|
||||
OptionValues.Add(new Options { Option = primaryAssetName.Value.String });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (export.GetExport<TextProperty>("OptionOnText") is TextProperty optionOnText)
|
||||
OptionValues.Add(new Options { Option = Text.GetTextPropertyBase(optionOnText).ToUpperInvariant() });
|
||||
if (export.GetExport<TextProperty>("OptionOffText") is TextProperty optionOffText)
|
||||
OptionValues.Add(new Options { Option = Text.GetTextPropertyBase(optionOffText).ToUpperInvariant() });
|
||||
|
||||
if (export.GetExport<IntProperty>("Min", "DefaultValue") is IntProperty iMin &&
|
||||
export.GetExport<IntProperty>("Max") is IntProperty iMax)
|
||||
{
|
||||
int increment = iMin.Value;
|
||||
if (export.GetExport<IntProperty>("IncrementValue") is IntProperty incrementValue)
|
||||
increment = incrementValue.Value;
|
||||
|
||||
for (int i = iMin.Value; i <= iMax.Value; i += increment)
|
||||
{
|
||||
OptionValues.Add(new Options { Option = i.ToString() });
|
||||
}
|
||||
}
|
||||
|
||||
if (export.GetExport<FloatProperty>("Min") is FloatProperty fMin &&
|
||||
export.GetExport<FloatProperty>("Max") is FloatProperty fMax)
|
||||
{
|
||||
float increment = fMin.Value;
|
||||
if (export.GetExport<FloatProperty>("IncrementValue") is FloatProperty incrementValue)
|
||||
increment = incrementValue.Value;
|
||||
|
||||
for (float i = fMin.Value; i <= fMax.Value; i += increment)
|
||||
{
|
||||
OptionValues.Add(new Options { Option = i.ToString() });
|
||||
}
|
||||
}
|
||||
|
||||
Height += Margin;
|
||||
Height += 35 * OptionValues.Count;
|
||||
}
|
||||
|
||||
public void Draw(SKCanvas c)
|
||||
{
|
||||
c.DrawRect(new SKRect(0, 0, Width, Height),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateLinearGradient(
|
||||
new SKPoint(Width / 2, Height),
|
||||
new SKPoint(Width, Height / 4),
|
||||
new SKColor[2] { SKColor.Parse("01369C"), SKColor.Parse("1273C8") },
|
||||
SKShaderTileMode.Clamp)
|
||||
});
|
||||
|
||||
int textSize = 45;
|
||||
SKPaint namePaint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Text.TypeFaces.DisplayNameTypeface,
|
||||
TextSize = textSize,
|
||||
Color = SKColors.White,
|
||||
TextAlign = SKTextAlign.Left
|
||||
};
|
||||
SKPaint optionPaint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Text.TypeFaces.DisplayNameTypeface,
|
||||
TextSize = 20,
|
||||
Color = SKColor.Parse("EEFFFF"),
|
||||
TextAlign = SKTextAlign.Left
|
||||
};
|
||||
|
||||
if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic)
|
||||
{
|
||||
SKShaper shaper = new SKShaper(namePaint.Typeface);
|
||||
float shapedTextWidth;
|
||||
|
||||
while (true)
|
||||
{
|
||||
SKShaper.Result shapedText = shaper.Shape(OptionDisplayName, namePaint);
|
||||
shapedTextWidth = shapedText.Points[^1].X + namePaint.TextSize / 2f;
|
||||
|
||||
if (shapedTextWidth > (Width - (Margin * 2)))
|
||||
{
|
||||
namePaint.TextSize -= 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
c.DrawShapedText(shaper, OptionDisplayName, Margin, Margin + textSize, namePaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (namePaint.MeasureText(OptionDisplayName) > (Width - (Margin * 2)))
|
||||
{
|
||||
namePaint.TextSize = textSize -= 2;
|
||||
}
|
||||
c.DrawText(OptionDisplayName, Margin, Margin + textSize, namePaint);
|
||||
}
|
||||
|
||||
int y = (Margin + textSize) + ((int)descriptionPaint.TextSize + (Margin / 2));
|
||||
Helper.DrawMultilineText(c, OptionDescription, Width, Margin, ETextSide.Left,
|
||||
new SKRect(Margin, y, Width - Margin, 256), descriptionPaint, out int top);
|
||||
|
||||
int height = 30;
|
||||
int space = 5;
|
||||
foreach (Options option in OptionValues)
|
||||
{
|
||||
c.DrawRect(new SKRect(Margin, top, Width - Margin, top + height),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Color = option.Color
|
||||
});
|
||||
|
||||
if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic)
|
||||
{
|
||||
SKShaper shaper = new SKShaper(optionPaint.Typeface);
|
||||
SKShaper.Result shapedText = shaper.Shape(option.Option, optionPaint);
|
||||
float shapedTextWidth = shapedText.Points[^1].X + optionPaint.TextSize / 2f;
|
||||
c.DrawShapedText(shaper, option.Option, Margin + (space * 2), top + (20 * 1.1f), optionPaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
c.DrawText(option.Option, Margin + (space * 2), top + (20 * 1.1f), optionPaint);
|
||||
}
|
||||
|
||||
top += height + space;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
141
FModel/Creator/Bases/FN/BaseBundle.cs
Normal file
141
FModel/Creator/Bases/FN/BaseBundle.cs
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
using System.Collections.Generic;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Objects;
|
||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using FModel.Extensions;
|
||||
using FModel.Framework;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
{
|
||||
public class BaseBundle : UCreator
|
||||
{
|
||||
private IList<BaseQuest> _quests;
|
||||
private const int _headerHeight = 100;
|
||||
|
||||
public BaseBundle(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
Width = 1024;
|
||||
Height = _headerHeight;
|
||||
Margin = 0;
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
_quests = new List<BaseQuest>();
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text.ToUpperInvariant();
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback[] quests, "QuestInfos")) // prout :)
|
||||
{
|
||||
foreach (var quest in quests)
|
||||
{
|
||||
if (!quest.TryGetValue(out FSoftObjectPath questDefinition, "QuestDefinition")) continue;
|
||||
|
||||
BaseQuest q;
|
||||
var path = questDefinition.AssetPathName.Text;
|
||||
do
|
||||
{
|
||||
if (!Utils.TryLoadObject(path, out UObject uObject)) break;
|
||||
|
||||
q = new BaseQuest(uObject, Style);
|
||||
q.ParseForInfo();
|
||||
_quests.Add(q);
|
||||
path = path.SubstringBeforeWithLast('/') + q.NextQuestName + "." + q.NextQuestName;
|
||||
} while (!string.IsNullOrEmpty(q.NextQuestName));
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback[] completionRewards, "BundleCompletionRewards"))
|
||||
{
|
||||
foreach (var completionReward in completionRewards)
|
||||
{
|
||||
if (!completionReward.TryGetValue(out int completionCount, "CompletionCount") ||
|
||||
!completionReward.TryGetValue(out FStructFallback[] rewards, "Rewards")) continue;
|
||||
|
||||
foreach (var reward in rewards)
|
||||
{
|
||||
if (!reward.TryGetValue(out int quantity, "Quantity") ||
|
||||
!reward.TryGetValue(out string templateId, "TemplateId") ||
|
||||
!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition")) continue;
|
||||
|
||||
if (!itemDefinition.AssetPathName.IsNone &&
|
||||
!itemDefinition.AssetPathName.Text.Contains("/Items/Tokens/") &&
|
||||
!itemDefinition.AssetPathName.Text.Contains("/Items/Quests"))
|
||||
{
|
||||
_quests.Add(new BaseQuest(completionCount, itemDefinition, Style));
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(templateId))
|
||||
{
|
||||
_quests.Add(new BaseQuest(completionCount, quantity, templateId, Style));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Height += 256 * _quests.Count;
|
||||
}
|
||||
|
||||
public override SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawHeader(c);
|
||||
DrawDisplayName(c);
|
||||
DrawQuests(c);
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
|
||||
private readonly SKPaint _headerPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.Bundle, TextSize = 50,
|
||||
TextAlign = SKTextAlign.Center, Color = SKColor.Parse("#262630")
|
||||
};
|
||||
|
||||
private void DrawHeader(SKCanvas c)
|
||||
{
|
||||
c.DrawRect(new SKRect(0, 0, Width, _headerHeight), _headerPaint);
|
||||
|
||||
var background = _quests.Count > 0 ? _quests[0].Background : Background;
|
||||
_headerPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
|
||||
new[] {background[0].WithAlpha(50), background[1].WithAlpha(50)}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(0, 0, Width, _headerHeight), _headerPaint);
|
||||
|
||||
_headerPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75),
|
||||
new[] {SKColors.Black.WithAlpha(25), background[1].WithAlpha(0)}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _headerPaint);
|
||||
}
|
||||
|
||||
private new void DrawDisplayName(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrEmpty(DisplayName)) return;
|
||||
|
||||
_headerPaint.Shader = null;
|
||||
_headerPaint.Color = SKColors.White;
|
||||
while (_headerPaint.MeasureText(DisplayName) > Width)
|
||||
{
|
||||
_headerPaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_headerPaint.Typeface);
|
||||
var shapedText = shaper.Shape(DisplayName, _headerPaint);
|
||||
c.DrawShapedText(shaper, DisplayName, (Width - shapedText.Points[^1].X) / 2, _headerHeight / 2 + _headerPaint.TextSize / 2 - 10, _headerPaint);
|
||||
}
|
||||
|
||||
private void DrawQuests(SKCanvas c)
|
||||
{
|
||||
var y = _headerHeight;
|
||||
foreach (var quest in _quests)
|
||||
{
|
||||
quest.DrawQuest(c, y);
|
||||
y += quest.Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
305
FModel/Creator/Bases/FN/BaseCommunity.cs
Normal file
305
FModel/Creator/Bases/FN/BaseCommunity.cs
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Objects.GameplayTags;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
using CUE4Parse_Fortnite.Enums;
|
||||
using FModel.Extensions;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels.ApiEndpoints.Models;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
{
|
||||
public class BaseCommunity : BaseIcon
|
||||
{
|
||||
private readonly CommunityDesign _design;
|
||||
private string _rarityName;
|
||||
private string _source;
|
||||
private string _season;
|
||||
private bool _lowerDrawn;
|
||||
|
||||
public BaseCommunity(UObject uObject, EIconStyle style, string designName) : base(uObject, style)
|
||||
{
|
||||
Margin = 0;
|
||||
_lowerDrawn = false;
|
||||
_design = ApplicationService.ApiEndpointView.FModelApi.GetDesign(designName);
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
ParseForReward(UserSettings.Default.CosmeticDisplayAsset == EEnabledDisabled.Enabled);
|
||||
|
||||
if (Object.TryGetValue(out FPackageIndex series, "Series") && Utils.TryGetPackageIndexExport(series, out UObject export))
|
||||
_rarityName = export.Name;
|
||||
else
|
||||
_rarityName = GetRarityName(Object.GetOrDefault<FName>("Rarity"));
|
||||
|
||||
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
|
||||
CheckGameplayTags(gameplayTags);
|
||||
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
|
||||
CosmeticSource = cosmeticItem.Name.ToUpper();
|
||||
|
||||
DisplayName = DisplayName.ToUpper();
|
||||
Description = Description.ToUpper();
|
||||
}
|
||||
|
||||
public override SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
if (_design == null)
|
||||
{
|
||||
base.Draw(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
if (_design.DrawSeason && _design.Fonts.TryGetValue("Season", out var font))
|
||||
DrawToBottom(c, font, _season);
|
||||
if (_design.DrawSource && _design.Fonts.TryGetValue("Source", out font))
|
||||
DrawToBottom(c, font, _source);
|
||||
DrawUserFacingFlags(c, _design.GameplayTags.DrawCustomOnly);
|
||||
}
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
|
||||
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
|
||||
{
|
||||
if (_design == null) return;
|
||||
if (_design.DrawSource)
|
||||
{
|
||||
if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
|
||||
_source = source.Text["Cosmetics.Source.".Length..].ToUpper();
|
||||
else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action))
|
||||
_source = action.Text["Athena.ItemAction.".Length..].ToUpper();
|
||||
}
|
||||
|
||||
if (_design.DrawSet && gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set))
|
||||
Description += GetCosmeticSet(set.Text, _design.DrawSetShort);
|
||||
if (_design.DrawSeason && gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season))
|
||||
_season = GetCosmeticSeason(season.Text, _design.DrawSeasonShort);
|
||||
|
||||
GetUserFacingFlags(gameplayTags.GetAllGameplayTags(
|
||||
"Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."));
|
||||
}
|
||||
|
||||
private string GetCosmeticSet(string setName, bool bShort)
|
||||
{
|
||||
return bShort ? setName["Cosmetics.Set.".Length..] : base.GetCosmeticSet(setName);
|
||||
}
|
||||
|
||||
private string GetCosmeticSeason(string seasonNumber, bool bShort)
|
||||
{
|
||||
if (!bShort) return base.GetCosmeticSeason(seasonNumber);
|
||||
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
|
||||
var number = int.Parse(s);
|
||||
if (number == 10)
|
||||
s = "X";
|
||||
|
||||
return number > 10 ? $"C{number / 10 + 1} S{s[^1..]}" : $"C1 S{s}";
|
||||
}
|
||||
|
||||
private string GetRarityName(FName r)
|
||||
{
|
||||
var rarity = EFortRarity.Uncommon;
|
||||
switch (r.Text)
|
||||
{
|
||||
case "EFortRarity::Common":
|
||||
case "EFortRarity::Handmade":
|
||||
rarity = EFortRarity.Common;
|
||||
break;
|
||||
case "EFortRarity::Rare":
|
||||
case "EFortRarity::Sturdy":
|
||||
rarity = EFortRarity.Rare;
|
||||
break;
|
||||
case "EFortRarity::Epic":
|
||||
case "EFortRarity::Quality":
|
||||
rarity = EFortRarity.Epic;
|
||||
break;
|
||||
case "EFortRarity::Legendary":
|
||||
case "EFortRarity::Fine":
|
||||
rarity = EFortRarity.Legendary;
|
||||
break;
|
||||
case "EFortRarity::Mythic":
|
||||
case "EFortRarity::Elegant":
|
||||
rarity = EFortRarity.Mythic;
|
||||
break;
|
||||
case "EFortRarity::Transcendent":
|
||||
case "EFortRarity::Masterwork":
|
||||
rarity = EFortRarity.Transcendent;
|
||||
break;
|
||||
case "EFortRarity::Unattainable":
|
||||
case "EFortRarity::Badass":
|
||||
rarity = EFortRarity.Unattainable;
|
||||
break;
|
||||
}
|
||||
|
||||
return rarity.GetDescription();
|
||||
}
|
||||
|
||||
private new void DrawBackground(SKCanvas c)
|
||||
{
|
||||
if (_design.Rarities.TryGetValue(_rarityName, out var rarity))
|
||||
{
|
||||
c.DrawBitmap(rarity.Background, 0, 0, ImagePaint);
|
||||
c.DrawBitmap(rarity.Upper, 0, 0, ImagePaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.DrawBackground(c);
|
||||
}
|
||||
}
|
||||
|
||||
private new void DrawTextBackground(SKCanvas c)
|
||||
{
|
||||
if (!_lowerDrawn && string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return;
|
||||
|
||||
_lowerDrawn = true;
|
||||
if (_design.Rarities.TryGetValue(_rarityName, out var rarity))
|
||||
{
|
||||
c.DrawBitmap(rarity.Lower, 0, 0, ImagePaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.DrawTextBackground(c);
|
||||
}
|
||||
}
|
||||
|
||||
private new void DrawDisplayName(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrEmpty(DisplayName)) return;
|
||||
if (_design.Fonts.TryGetValue(nameof(DisplayName), out var font))
|
||||
{
|
||||
DisplayNamePaint.TextSize = font.FontSize;
|
||||
DisplayNamePaint.TextScaleX = font.FontScale;
|
||||
DisplayNamePaint.Color = font.FontColor;
|
||||
DisplayNamePaint.TextSkewX = font.SkewValue;
|
||||
DisplayNamePaint.TextAlign = font.Alignment;
|
||||
if (font.ShadowValue > 0)
|
||||
DisplayNamePaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue));
|
||||
if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) ||
|
||||
font.Typeface.TryGetValue(ELanguage.English, out path))
|
||||
DisplayNamePaint.Typeface = Utils.Typefaces.OnTheFly(path);
|
||||
|
||||
while (DisplayNamePaint.MeasureText(DisplayName) > Width - Margin * 2)
|
||||
{
|
||||
DisplayNamePaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
|
||||
var shapedText = shaper.Shape(DisplayName, DisplayNamePaint);
|
||||
var x = font.Alignment switch
|
||||
{
|
||||
SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f,
|
||||
_ => font.X
|
||||
};
|
||||
|
||||
c.DrawShapedText(shaper, DisplayName, x, font.Y, DisplayNamePaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.DrawDisplayName(c);
|
||||
}
|
||||
}
|
||||
|
||||
private new void DrawDescription(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Description)) return;
|
||||
if (_design.Fonts.TryGetValue(nameof(Description), out var font))
|
||||
{
|
||||
DescriptionPaint.TextSize = font.FontSize;
|
||||
DescriptionPaint.TextScaleX = font.FontScale;
|
||||
DescriptionPaint.Color = font.FontColor;
|
||||
DescriptionPaint.TextSkewX = font.SkewValue;
|
||||
DescriptionPaint.TextAlign = font.Alignment;
|
||||
if (font.ShadowValue > 0)
|
||||
DescriptionPaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue));
|
||||
if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) ||
|
||||
font.Typeface.TryGetValue(ELanguage.English, out path))
|
||||
DescriptionPaint.Typeface = Utils.Typefaces.OnTheFly(path);
|
||||
|
||||
while (DescriptionPaint.MeasureText(Description) > Width - Margin * 2)
|
||||
{
|
||||
DescriptionPaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(DescriptionPaint.Typeface);
|
||||
var shapedText = shaper.Shape(Description, DescriptionPaint);
|
||||
var x = font.Alignment switch
|
||||
{
|
||||
SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f,
|
||||
_ => font.X
|
||||
};
|
||||
|
||||
if (font.MaxLineCount < 2)
|
||||
{
|
||||
c.DrawShapedText(shaper, Description, x, font.Y, DescriptionPaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
Utils.DrawMultilineText(c, Description, Width - Margin * 2, Margin, DescriptionPaint.TextAlign,
|
||||
new SKRect(Margin, font.Y, Width - Margin, Height), DescriptionPaint, out _);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
base.DrawDescription(c);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawToBottom(SKCanvas c, FontDesign font, string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text)) return;
|
||||
if (!_lowerDrawn)
|
||||
{
|
||||
_lowerDrawn = true;
|
||||
DrawTextBackground(c);
|
||||
}
|
||||
|
||||
DisplayNamePaint.TextSize = font.FontSize;
|
||||
DisplayNamePaint.TextScaleX = font.FontScale;
|
||||
DisplayNamePaint.Color = font.FontColor;
|
||||
DisplayNamePaint.TextSkewX = font.SkewValue;
|
||||
DisplayNamePaint.TextAlign = font.Alignment;
|
||||
if (font.ShadowValue > 0)
|
||||
DisplayNamePaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue));
|
||||
if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) ||
|
||||
font.Typeface.TryGetValue(ELanguage.English, out path))
|
||||
DisplayNamePaint.Typeface = Utils.Typefaces.OnTheFly(path);
|
||||
|
||||
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
|
||||
var shapedText = shaper.Shape(text, DisplayNamePaint);
|
||||
var x = font.Alignment switch
|
||||
{
|
||||
SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f,
|
||||
SKTextAlign.Right => font.X - DisplayNamePaint.MeasureText(text),
|
||||
_ => font.X
|
||||
};
|
||||
|
||||
c.DrawShapedText(shaper, text, x, font.Y, DisplayNamePaint);
|
||||
}
|
||||
|
||||
private void DrawUserFacingFlags(SKCanvas c, bool customOnly)
|
||||
{
|
||||
if (UserFacingFlags == null || UserFacingFlags.Length < 1) return;
|
||||
if (customOnly)
|
||||
{
|
||||
c.DrawBitmap(_design.GameplayTags.Custom, 0, 0, ImagePaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
// add size to api
|
||||
// draw
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
297
FModel/Creator/Bases/FN/BaseIcon.cs
Normal file
297
FModel/Creator/Bases/FN/BaseIcon.cs
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
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.GameplayTags;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using CUE4Parse_Fortnite.Enums;
|
||||
using FModel.Settings;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
{
|
||||
public class BaseIcon : UCreator
|
||||
{
|
||||
public SKBitmap SeriesBackground { get; protected set; }
|
||||
protected string ShortDescription { get; set; }
|
||||
protected string CosmeticSource { get; set; }
|
||||
protected SKBitmap[] UserFacingFlags { get; set; }
|
||||
|
||||
public BaseIcon(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
}
|
||||
|
||||
public void ParseForReward(bool isUsingDisplayAsset)
|
||||
{
|
||||
// rarity
|
||||
if (Object.TryGetValue(out FPackageIndex series, "Series")) GetSeries(series);
|
||||
else GetRarity(Object.GetOrDefault<FName>("Rarity")); // default is uncommon
|
||||
|
||||
// preview
|
||||
if (isUsingDisplayAsset && Utils.TryGetDisplayAsset(Object, out var preview))
|
||||
Preview = preview;
|
||||
else if (Object.TryGetValue(out FPackageIndex itemDefinition, "HeroDefinition", "WeaponDefinition"))
|
||||
Preview = Utils.GetBitmap(itemDefinition);
|
||||
else if (Object.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "SidePanelIcon", "EntryListIcon", "SmallPreviewImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon"))
|
||||
Preview = Utils.GetBitmap(largePreview);
|
||||
else if (Object.TryGetValue(out string s, "LargePreviewImage") && !string.IsNullOrEmpty(s))
|
||||
Preview = Utils.GetBitmap(s);
|
||||
else if (Object.TryGetValue(out FPackageIndex otherPreview, "SmallPreviewImage", "ToastIcon", "access_item"))
|
||||
Preview = Utils.GetBitmap(otherPreview);
|
||||
|
||||
// text
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName", "DefaultHeaderText", "UIDisplayName", "EntryName"))
|
||||
DisplayName = displayName.Text;
|
||||
if (Object.TryGetValue(out FText description, "Description", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription"))
|
||||
Description = description.Text;
|
||||
else if (Object.TryGetValue(out FText[] descriptions, "Description"))
|
||||
Description = string.Join('\n', descriptions.Select(x => x.Text));
|
||||
if (Object.TryGetValue(out FText shortDescription, "ShortDescription", "UIDisplaySubName"))
|
||||
ShortDescription = shortDescription.Text;
|
||||
else if (Object.ExportType.Equals("AthenaItemWrapDefinition", StringComparison.OrdinalIgnoreCase))
|
||||
ShortDescription = "Wrap";
|
||||
|
||||
Description = Utils.RemoveHtmlTags(Description);
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
ParseForReward(UserSettings.Default.CosmeticDisplayAsset == EEnabledDisabled.Enabled);
|
||||
|
||||
if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags"))
|
||||
CheckGameplayTags(gameplayTags);
|
||||
if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item"))
|
||||
CosmeticSource = cosmeticItem.Name;
|
||||
}
|
||||
|
||||
protected void Draw(SKCanvas c)
|
||||
{
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.NoBackground:
|
||||
DrawPreview(c);
|
||||
break;
|
||||
case EIconStyle.NoText:
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawUserFacingFlags(c);
|
||||
break;
|
||||
default:
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
DrawToBottom(c, SKTextAlign.Right, CosmeticSource);
|
||||
if (Description != ShortDescription)
|
||||
DrawToBottom(c, SKTextAlign.Left, ShortDescription);
|
||||
DrawUserFacingFlags(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
Draw(c);
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
|
||||
private void GetSeries(FPackageIndex s)
|
||||
{
|
||||
if (!Utils.TryGetPackageIndexExport(s, out UObject export)) return;
|
||||
|
||||
GetSeries(export);
|
||||
}
|
||||
|
||||
protected void GetSeries(UObject uObject)
|
||||
{
|
||||
if (uObject is UTexture2D texture2D)
|
||||
{
|
||||
SeriesBackground = SKBitmap.Decode(texture2D.Decode()?.Encode());
|
||||
return;
|
||||
}
|
||||
|
||||
if (uObject.TryGetValue(out FSoftObjectPath backgroundTexture, "BackgroundTexture"))
|
||||
{
|
||||
SeriesBackground = Utils.GetBitmap(backgroundTexture);
|
||||
}
|
||||
|
||||
if (uObject.TryGetValue(out FStructFallback colors, "Colors") &&
|
||||
colors.TryGetValue(out FLinearColor color1, "Color1") &&
|
||||
colors.TryGetValue(out FLinearColor color2, "Color2") &&
|
||||
colors.TryGetValue(out FLinearColor color3, "Color3"))
|
||||
{
|
||||
Background = new[] {SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex)};
|
||||
Border = new[] {SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex)};
|
||||
}
|
||||
|
||||
if (uObject.Name.Equals("PlatformSeries") &&
|
||||
uObject.TryGetValue(out FSoftObjectPath itemCardMaterial, "ItemCardMaterial") &&
|
||||
Utils.TryLoadObject(itemCardMaterial.AssetPathName.Text, out UMaterialInstanceConstant material))
|
||||
{
|
||||
foreach (var vectorParameter in material.VectorParameterValues)
|
||||
{
|
||||
if (vectorParameter.ParameterValue == null || !vectorParameter.ParameterInfo.Name.Text.Equals("ColorCircuitBackground"))
|
||||
continue;
|
||||
|
||||
Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GetRarity(FName r)
|
||||
{
|
||||
if (!Utils.TryLoadObject("FortniteGame/Content/Balance/RarityData.RarityData", out UObject export)) return;
|
||||
|
||||
var rarity = EFortRarity.Uncommon;
|
||||
switch (r.Text)
|
||||
{
|
||||
case "EFortRarity::Common":
|
||||
case "EFortRarity::Handmade":
|
||||
rarity = EFortRarity.Common;
|
||||
break;
|
||||
case "EFortRarity::Rare":
|
||||
case "EFortRarity::Sturdy":
|
||||
rarity = EFortRarity.Rare;
|
||||
break;
|
||||
case "EFortRarity::Epic":
|
||||
case "EFortRarity::Quality":
|
||||
rarity = EFortRarity.Epic;
|
||||
break;
|
||||
case "EFortRarity::Legendary":
|
||||
case "EFortRarity::Fine":
|
||||
rarity = EFortRarity.Legendary;
|
||||
break;
|
||||
case "EFortRarity::Mythic":
|
||||
case "EFortRarity::Elegant":
|
||||
rarity = EFortRarity.Mythic;
|
||||
break;
|
||||
case "EFortRarity::Transcendent":
|
||||
case "EFortRarity::Masterwork":
|
||||
rarity = EFortRarity.Transcendent;
|
||||
break;
|
||||
case "EFortRarity::Unattainable":
|
||||
case "EFortRarity::Badass":
|
||||
rarity = EFortRarity.Unattainable;
|
||||
break;
|
||||
}
|
||||
|
||||
if (export.GetByIndex<FStructFallback>((int) rarity) is { } data &&
|
||||
data.TryGetValue(out FLinearColor color1, "Color1") &&
|
||||
data.TryGetValue(out FLinearColor color2, "Color2") &&
|
||||
data.TryGetValue(out FLinearColor color3, "Color3"))
|
||||
{
|
||||
Background = new[] {SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex)};
|
||||
Border = new[] {SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex)};
|
||||
}
|
||||
}
|
||||
|
||||
protected string GetCosmeticSet(string setName)
|
||||
{
|
||||
if (!Utils.TryLoadObject("FortniteGame/Content/Athena/Items/Cosmetics/Metadata/CosmeticSets.CosmeticSets", out UDataTable cosmeticSets))
|
||||
return string.Empty;
|
||||
|
||||
if (!cosmeticSets.TryGetDataTableRow(setName, StringComparison.OrdinalIgnoreCase, out var uObject))
|
||||
return string.Empty;
|
||||
|
||||
var name = string.Empty;
|
||||
if (uObject.TryGetValue(out FText displayName, "DisplayName"))
|
||||
name = displayName.Text;
|
||||
|
||||
var format = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_SetMembership_NotRich", "\nPart of the {0} set.");
|
||||
return string.Format(format, name);
|
||||
}
|
||||
|
||||
protected string GetCosmeticSeason(string seasonNumber)
|
||||
{
|
||||
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
|
||||
var number = int.Parse(s);
|
||||
if (number == 10)
|
||||
s = "X";
|
||||
|
||||
var season = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}");
|
||||
var introduced = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in <SeasonText>{0}</>.");
|
||||
if (number <= 10) return Utils.RemoveHtmlTags(string.Format(introduced, string.Format(season, s)));
|
||||
|
||||
var chapter = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}");
|
||||
var chapterFormat = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}");
|
||||
var d = string.Format(chapterFormat, string.Format(chapter, number / 10 + 1), string.Format(season, s[^1..]));
|
||||
return Utils.RemoveHtmlTags(string.Format(introduced, d));
|
||||
}
|
||||
|
||||
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
|
||||
{
|
||||
if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
|
||||
CosmeticSource = source.Text["Cosmetics.Source.".Length..];
|
||||
else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action))
|
||||
CosmeticSource = action.Text["Athena.ItemAction.".Length..];
|
||||
|
||||
if (gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set))
|
||||
Description += GetCosmeticSet(set.Text);
|
||||
if (gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season))
|
||||
Description += GetCosmeticSeason(season.Text);
|
||||
|
||||
GetUserFacingFlags(gameplayTags.GetAllGameplayTags(
|
||||
"Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."));
|
||||
}
|
||||
|
||||
protected void GetUserFacingFlags(IList<string> userFacingFlags)
|
||||
{
|
||||
if (userFacingFlags.Count < 1 || !Utils.TryLoadObject("FortniteGame/Content/Items/ItemCategories.ItemCategories", out UObject itemCategories))
|
||||
return;
|
||||
|
||||
if (!itemCategories.TryGetValue(out FStructFallback[] tertiaryCategories, "TertiaryCategories"))
|
||||
return;
|
||||
|
||||
UserFacingFlags = new SKBitmap[userFacingFlags.Count];
|
||||
for (var i = 0; i < UserFacingFlags.Length; i++)
|
||||
{
|
||||
if (userFacingFlags[i].Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Object.ExportType.Equals("AthenaPetCarrierItemDefinition", StringComparison.OrdinalIgnoreCase))
|
||||
UserFacingFlags[i] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png"))?.Stream);
|
||||
else UserFacingFlags[i] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png"))?.Stream);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var category in tertiaryCategories)
|
||||
{
|
||||
if (category.TryGetValue(out FGameplayTagContainer tagContainer, "TagContainer") && tagContainer.TryGetGameplayTag(userFacingFlags[i], out _) &&
|
||||
category.TryGetValue(out FStructFallback categoryBrush, "CategoryBrush") && categoryBrush.TryGetValue(out FStructFallback brushXxs, "Brush_XXS") &&
|
||||
brushXxs.TryGetValue(out FPackageIndex resourceObject, "ResourceObject") && Utils.TryGetPackageIndexExport(resourceObject, out UTexture2D texture))
|
||||
{
|
||||
UserFacingFlags[i] = Utils.GetBitmap(texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawUserFacingFlags(SKCanvas c)
|
||||
{
|
||||
if (UserFacingFlags == null) return;
|
||||
|
||||
const int size = 25;
|
||||
var x = Margin * (int) 2.5;
|
||||
foreach (var flag in UserFacingFlags)
|
||||
{
|
||||
if (flag == null) continue;
|
||||
|
||||
c.DrawBitmap(flag.Resize(size), new SKPoint(x, Margin * (int) 2.5), ImagePaint);
|
||||
x += size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
319
FModel/Creator/Bases/FN/BaseIconStats.cs
Normal file
319
FModel/Creator/Bases/FN/BaseIconStats.cs
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Engine;
|
||||
using CUE4Parse.UE4.Assets.Objects;
|
||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||
using CUE4Parse.UE4.Objects.Engine.Curves;
|
||||
using CUE4Parse.UE4.Objects.GameplayTags;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using CUE4Parse_Fortnite.Enums;
|
||||
using FModel.Extensions;
|
||||
using FModel.Framework;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
{
|
||||
public class BaseIconStats : BaseIcon
|
||||
{
|
||||
private readonly IList<IconStat> _statistics;
|
||||
private const int _headerHeight = 128;
|
||||
private bool _screenLayer;
|
||||
|
||||
public BaseIconStats(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
Width = 1024;
|
||||
Height = _headerHeight;
|
||||
Margin = 0;
|
||||
_statistics = new List<IconStat>();
|
||||
_screenLayer = uObject.ExportType.Equals("FortAccoladeItemDefinition", StringComparison.OrdinalIgnoreCase);
|
||||
DefaultPreview = Utils.GetBitmap("FortniteGame/Content/Athena/HUD/Quests/Art/T_NPC_Default.T_NPC_Default");
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
base.ParseForInfo();
|
||||
DisplayName = DisplayName.ToUpperInvariant();
|
||||
|
||||
if (Object.TryGetValue(out FName accoladeType, "AccoladeType") &&
|
||||
accoladeType.Text.Equals("EFortAccoladeType::Medal", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_screenLayer = false;
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FGameplayTagContainer poiLocations, "POILocations") &&
|
||||
Utils.TryLoadObject("FortniteGame/Content/Quests/QuestIndicatorData.QuestIndicatorData", out UObject uObject) &&
|
||||
uObject.TryGetValue(out FStructFallback[] challengeMapPoiData, "ChallengeMapPoiData"))
|
||||
{
|
||||
foreach (var location in poiLocations)
|
||||
{
|
||||
var locationName = "Unknown";
|
||||
foreach (var poi in challengeMapPoiData)
|
||||
{
|
||||
if (!poi.TryGetValue(out FStructFallback locationTag, "LocationTag") || !locationTag.TryGetValue(out FName tagName, "TagName") ||
|
||||
!tagName.Text.Equals(location.Text, StringComparison.OrdinalIgnoreCase) || !poi.TryGetValue(out FText text, "Text")) continue;
|
||||
|
||||
locationName = text.Text;
|
||||
break;
|
||||
}
|
||||
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "B0C091D7409B1657423C5F97E9CF4C77", "LOCATION NAME"), locationName.ToUpper()));
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback maxStackSize, "MaxStackSize"))
|
||||
{
|
||||
if (maxStackSize.TryGetValue(out float v, "Value") && v > -1)
|
||||
{
|
||||
_statistics.Add(new IconStat("Max Stack", v));
|
||||
}
|
||||
else if (TryGetCurveTableStat(maxStackSize, out var s))
|
||||
{
|
||||
_statistics.Add(new IconStat("Max Stack", s));
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback xpRewardAmount, "XpRewardAmount") && TryGetCurveTableStat(xpRewardAmount, out var x))
|
||||
{
|
||||
_statistics.Add(new IconStat("XP Amount", x));
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback weaponStatHandle, "WeaponStatHandle") &&
|
||||
weaponStatHandle.TryGetValue(out FName weaponRowName, "RowName") &&
|
||||
weaponStatHandle.TryGetValue(out UDataTable dataTable, "DataTable") &&
|
||||
dataTable.TryGetDataTableRow(weaponRowName.Text, StringComparison.OrdinalIgnoreCase, out var weaponRowValue))
|
||||
{
|
||||
if (weaponRowValue.TryGetValue(out float dmgPb, "DmgPB") && dmgPb != 0f)
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "BF7E3CF34A9ACFF52E95EAAD4F09F133", "Damage to Player"), dmgPb, 200));
|
||||
}
|
||||
|
||||
if (weaponRowValue.TryGetValue(out int clipSize, "ClipSize") && clipSize != 0)
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "068239DD4327B36124498C9C5F61C038", "Magazine Size"), clipSize, 50));
|
||||
}
|
||||
|
||||
if (weaponRowValue.TryGetValue(out float firingRate, "FiringRate") && firingRate != 0f)
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "27B80BA44805ABD5A2D2BAB2902B250C", "Fire Rate"), firingRate, 15));
|
||||
}
|
||||
|
||||
if (weaponRowValue.TryGetValue(out float armTime, "ArmTime") && armTime != 0f)
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "3BFEB8BD41A677CC5F45B9A90D6EAD6F", "Arming Delay"), armTime, 125));
|
||||
}
|
||||
|
||||
if (weaponRowValue.TryGetValue(out float reloadTime, "ReloadTime") && reloadTime != 0f)
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6EA26D1A4252034FBD869A90F9A6E49A", "Reload Time"), reloadTime, 15));
|
||||
}
|
||||
|
||||
if ((Object.ExportType.Equals("FortContextTrapItemDefinition", StringComparison.OrdinalIgnoreCase) ||
|
||||
Object.ExportType.Equals("FortTrapItemDefinition", StringComparison.OrdinalIgnoreCase)) &&
|
||||
weaponRowValue.TryGetValue(out UDataTable durabilityTable, "Durability") &&
|
||||
weaponRowValue.TryGetValue(out FName durabilityRowName, "DurabilityRowName") &&
|
||||
durabilityTable.TryGetDataTableRow(durabilityRowName.Text, StringComparison.OrdinalIgnoreCase, out var durability) &&
|
||||
durability.TryGetValue(out int duraByRarity, GetRarityName(Object.GetOrDefault<FName>("Rarity"))))
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6FA2882140CB69DE32FD73A392F0585B", "Durability"), duraByRarity, 20));
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Description))
|
||||
Height += 40 + (int) _informationPaint.TextSize * Utils.SplitLines(Description, _informationPaint, Width - 20).Count;
|
||||
Height += 50 * _statistics.Count;
|
||||
}
|
||||
|
||||
public override SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawHeader(c);
|
||||
DrawDisplayName(c);
|
||||
DrawStatistics(c);
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
|
||||
private bool TryGetCurveTableStat(FStructFallback property, out float statValue)
|
||||
{
|
||||
if (property.TryGetValue(out FStructFallback curve, "Curve") &&
|
||||
curve.TryGetValue(out FName rowName, "RowName") &&
|
||||
curve.TryGetValue(out UCurveTable curveTable, "CurveTable") &&
|
||||
curveTable.TryGetCurveTableRow(rowName.Text, StringComparison.OrdinalIgnoreCase, out var rowValue) &&
|
||||
rowValue.TryGetValue(out FSimpleCurveKey[] keys, "Keys") && keys.Length > 0)
|
||||
{
|
||||
statValue = keys[0].KeyValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
statValue = 0F;
|
||||
return false;
|
||||
}
|
||||
|
||||
private string GetRarityName(FName r)
|
||||
{
|
||||
var rarity = EFortRarity.Uncommon;
|
||||
switch (r.Text)
|
||||
{
|
||||
case "EFortRarity::Common":
|
||||
case "EFortRarity::Handmade":
|
||||
rarity = EFortRarity.Common;
|
||||
break;
|
||||
case "EFortRarity::Rare":
|
||||
case "EFortRarity::Sturdy":
|
||||
rarity = EFortRarity.Rare;
|
||||
break;
|
||||
case "EFortRarity::Epic":
|
||||
case "EFortRarity::Quality":
|
||||
rarity = EFortRarity.Epic;
|
||||
break;
|
||||
case "EFortRarity::Legendary":
|
||||
case "EFortRarity::Fine":
|
||||
rarity = EFortRarity.Legendary;
|
||||
break;
|
||||
case "EFortRarity::Mythic":
|
||||
case "EFortRarity::Elegant":
|
||||
rarity = EFortRarity.Mythic;
|
||||
break;
|
||||
case "EFortRarity::Transcendent":
|
||||
case "EFortRarity::Masterwork":
|
||||
rarity = EFortRarity.Transcendent;
|
||||
break;
|
||||
case "EFortRarity::Unattainable":
|
||||
case "EFortRarity::Badass":
|
||||
rarity = EFortRarity.Unattainable;
|
||||
break;
|
||||
}
|
||||
|
||||
return rarity.GetDescription();
|
||||
}
|
||||
|
||||
private readonly SKPaint _informationPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Color = SKColor.Parse("#262630"), TextSize = 16,
|
||||
Typeface = Utils.Typefaces.Description
|
||||
};
|
||||
|
||||
private void DrawHeader(SKCanvas c)
|
||||
{
|
||||
c.DrawRect(new SKRect(0, 0, Width, Height), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
|
||||
new[] {Background[0].WithAlpha(180), Background[1].WithAlpha(220)}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(_headerHeight, 0, Width, _headerHeight), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
|
||||
new[] {SKColor.Parse("#262630"), SKColor.Parse("#1f1f26")}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(0, _headerHeight, Width, Height), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75),
|
||||
new[] {SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0)}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4, Background, SKShaderTileMode.Clamp);
|
||||
using var rect = new SKPath {FillType = SKPathFillType.EvenOdd};
|
||||
rect.MoveTo(0, 0);
|
||||
rect.LineTo(_headerHeight + _headerHeight / 3, 0);
|
||||
rect.LineTo(_headerHeight, _headerHeight);
|
||||
rect.LineTo(0, _headerHeight);
|
||||
rect.Close();
|
||||
c.DrawPath(rect, _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(_headerHeight / 2, _headerHeight / 2), new SKPoint(_headerHeight / 2 + 100, _headerHeight / 2),
|
||||
new[] {SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0)}, SKShaderTileMode.Clamp);
|
||||
c.DrawPath(rect, _informationPaint);
|
||||
|
||||
_informationPaint.Shader = null;
|
||||
|
||||
ImagePaint.BlendMode = _screenLayer ? SKBlendMode.Screen : Preview == null ? SKBlendMode.ColorBurn : SKBlendMode.SrcOver;
|
||||
c.DrawBitmap((Preview ?? DefaultPreview).Resize(_headerHeight), 0, 0, ImagePaint);
|
||||
}
|
||||
|
||||
private new void DrawDisplayName(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrEmpty(DisplayName)) return;
|
||||
|
||||
_informationPaint.TextSize = 50;
|
||||
_informationPaint.Color = SKColors.White;
|
||||
_informationPaint.Typeface = Utils.Typefaces.Bundle;
|
||||
while (_informationPaint.MeasureText(DisplayName) > Width - _headerHeight * 2)
|
||||
{
|
||||
_informationPaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_informationPaint.Typeface);
|
||||
shaper.Shape(DisplayName, _informationPaint);
|
||||
c.DrawShapedText(shaper, DisplayName, _headerHeight + _headerHeight / 3 + 10, _headerHeight / 2 + _informationPaint.TextSize / 3, _informationPaint);
|
||||
}
|
||||
|
||||
private void DrawStatistics(SKCanvas c)
|
||||
{
|
||||
var outY = _headerHeight + 25f;
|
||||
if (!string.IsNullOrEmpty(Description))
|
||||
{
|
||||
_informationPaint.TextSize = 16;
|
||||
_informationPaint.Color = SKColors.White.WithAlpha(175);
|
||||
_informationPaint.Typeface = Utils.Typefaces.Description;
|
||||
Utils.DrawMultilineText(c, Description, Width - 40, 0, SKTextAlign.Center,
|
||||
new SKRect(20, outY, Width - 20, Height), _informationPaint, out outY);
|
||||
outY += 25;
|
||||
}
|
||||
|
||||
foreach (var stat in _statistics)
|
||||
{
|
||||
stat.Draw(c, Border[0].WithAlpha(100), Width, _headerHeight, ref outY);
|
||||
outY += 50;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class IconStat
|
||||
{
|
||||
private readonly string _statName;
|
||||
private readonly object _value;
|
||||
private readonly float _maxValue;
|
||||
|
||||
public IconStat(string statName, object value, float maxValue = 0)
|
||||
{
|
||||
_statName = statName.ToUpperInvariant();
|
||||
_value = value;
|
||||
_maxValue = maxValue;
|
||||
}
|
||||
|
||||
private readonly SKPaint _statPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
TextSize = 25, Typeface = Utils.Typefaces.DisplayName,
|
||||
Color = SKColors.White
|
||||
};
|
||||
|
||||
public void Draw(SKCanvas c, SKColor sliderColor, int width, int height, ref float y)
|
||||
{
|
||||
while (_statPaint.MeasureText(_statName) > height * 2)
|
||||
{
|
||||
_statPaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_statPaint.Typeface);
|
||||
shaper.Shape(_statName, _statPaint);
|
||||
c.DrawShapedText(shaper, _statName, 50, y + 10, _statPaint);
|
||||
|
||||
_statPaint.TextAlign = SKTextAlign.Right;
|
||||
_statPaint.Typeface = Utils.Typefaces.BundleNumber;
|
||||
_statPaint.Color = sliderColor;
|
||||
var sliderRight = width - 100 - _statPaint.MeasureText(_value.ToString());
|
||||
c.DrawRect(new SKRect(height * 2, y, Math.Min(width - height, sliderRight), y + 5), _statPaint);
|
||||
|
||||
_statPaint.Color = SKColors.White;
|
||||
c.DrawText(_value.ToString(), new SKPoint(width - 50, y + 10), _statPaint);
|
||||
|
||||
if (_maxValue < 1 || !float.TryParse(_value.ToString(), out var floatValue)) return;
|
||||
var sliderWidth = (sliderRight - height * 2) * (floatValue / _maxValue);
|
||||
c.DrawRect(new SKRect(height * 2, y, Math.Min(height * 2 + sliderWidth, sliderRight), y + 5), _statPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
100
FModel/Creator/Bases/FN/BaseItemAccessToken.cs
Normal file
100
FModel/Creator/Bases/FN/BaseItemAccessToken.cs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using FModel.Framework;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
{
|
||||
public class BaseItemAccessToken : UCreator
|
||||
{
|
||||
private readonly SKBitmap _locked, _unlocked;
|
||||
private string _unlockedDescription, _exportName;
|
||||
private BaseIcon _icon;
|
||||
|
||||
public BaseItemAccessToken(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
_unlocked = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Locks/T-Icon-Unlocked-128.T-Icon-Unlocked-128").Resize(24);
|
||||
_locked = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Locks/T-Icon-Lock-128.T-Icon-Lock-128").Resize(24);
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FPackageIndex accessItem, "access_item") &&
|
||||
Utils.TryGetPackageIndexExport(accessItem, out UObject uObject))
|
||||
{
|
||||
_exportName = uObject.Name;
|
||||
_icon = new BaseIcon(uObject, EIconStyle.Default);
|
||||
_icon.ParseForReward(false);
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName") && displayName.Text != "TBD")
|
||||
DisplayName = displayName.Text;
|
||||
else
|
||||
DisplayName = _icon?.DisplayName;
|
||||
|
||||
Description = Object.TryGetValue(out FText description, "Description") ? description.Text : _icon?.Description;
|
||||
if (Object.TryGetValue(out FText unlockDescription, "UnlockDescription")) _unlockedDescription = unlockDescription.Text;
|
||||
}
|
||||
|
||||
public override SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.NoBackground:
|
||||
Preview = _icon.Preview;
|
||||
DrawPreview(c);
|
||||
break;
|
||||
case EIconStyle.NoText:
|
||||
Preview = _icon.Preview;
|
||||
_icon.DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
break;
|
||||
default:
|
||||
_icon.DrawBackground(c);
|
||||
DrawInformation(c);
|
||||
DrawToBottom(c, SKTextAlign.Right, _exportName);
|
||||
break;
|
||||
}
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
|
||||
private void DrawInformation(SKCanvas c)
|
||||
{
|
||||
var size = 45;
|
||||
var left = Width / 2;
|
||||
|
||||
while (DisplayNamePaint.MeasureText(DisplayName) > Width - _icon.Margin * 2)
|
||||
{
|
||||
DisplayNamePaint.TextSize = size -= 2;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
|
||||
var shapedText = shaper.Shape(DisplayName, DisplayNamePaint);
|
||||
c.DrawShapedText(shaper, DisplayName, left - shapedText.Points[^1].X / 2, _icon.Margin * 8 + size, DisplayNamePaint);
|
||||
|
||||
float topBase = _icon.Margin + size * 2;
|
||||
if (!string.IsNullOrEmpty(_unlockedDescription))
|
||||
{
|
||||
c.DrawBitmap(_locked, new SKRect(50, topBase, 50 + _locked.Width, topBase + _locked.Height), ImagePaint);
|
||||
Utils.DrawMultilineText(c, _unlockedDescription, Width, _icon.Margin, SKTextAlign.Left,
|
||||
new SKRect(70 + _locked.Width, topBase + 10, Width - 50, 256), DescriptionPaint, out topBase);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Description))
|
||||
{
|
||||
c.DrawBitmap(_unlocked, new SKRect(50, topBase, 50 + _unlocked.Width, topBase + _unlocked.Height), ImagePaint);
|
||||
Utils.DrawMultilineText(c, Description, Width, _icon.Margin, SKTextAlign.Left,
|
||||
new SKRect(70 + _unlocked.Width, topBase + 10, Width - 50, 256), DescriptionPaint, out topBase);
|
||||
}
|
||||
|
||||
var h = Width - _icon.Margin - topBase;
|
||||
c.DrawBitmap(_icon.Preview ?? _icon.DefaultPreview, new SKRect(left - h / 2, topBase, left + h / 2, Width - _icon.Margin), ImagePaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
83
FModel/Creator/Bases/FN/BaseMaterialInstance.cs
Normal file
83
FModel/Creator/Bases/FN/BaseMaterialInstance.cs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
using System.Linq;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
{
|
||||
public class BaseMaterialInstance : BaseIcon
|
||||
{
|
||||
public BaseMaterialInstance(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
Background = new[] {SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69")};
|
||||
Border = new[] {SKColor.Parse("9092AB")};
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (!(Object is UMaterialInstanceConstant material)) return;
|
||||
|
||||
foreach (var textureParameter in material.TextureParameterValues) // get texture from base material
|
||||
{
|
||||
if (!(textureParameter.ParameterValue is UTexture2D texture) || Preview != null) continue;
|
||||
switch (textureParameter.ParameterInfo.Name.Text)
|
||||
{
|
||||
case "SeriesTexture":
|
||||
GetSeries(texture);
|
||||
break;
|
||||
case "TextureA":
|
||||
case "TextureB":
|
||||
case "OfferImage":
|
||||
Preview = Utils.GetBitmap(texture);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (material.VectorParameterValues.Length == 0 || // try to get color from parent if not found here
|
||||
material.VectorParameterValues.All(x => x.ParameterInfo.Name.Text.Equals("FallOff_Color"))) // use parent if it only contains FallOff_Color
|
||||
{
|
||||
if (material.TryGetValue(out FPackageIndex parent, "Parent"))
|
||||
Utils.TryGetPackageIndexExport(parent, out material);
|
||||
else return;
|
||||
|
||||
if (material == null) return;
|
||||
}
|
||||
|
||||
foreach (var vectorParameter in material.VectorParameterValues)
|
||||
{
|
||||
if (vectorParameter.ParameterValue == null) continue;
|
||||
switch (vectorParameter.ParameterInfo.Name.Text)
|
||||
{
|
||||
case "Background_Color_A":
|
||||
Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
|
||||
Border[0] = Background[0];
|
||||
break;
|
||||
case "Background_Color_B": // Border color can be defaulted here in some case where Background_Color_A should be taken from parent but Background_Color_B from base
|
||||
Background[1] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.NoBackground:
|
||||
DrawPreview(c);
|
||||
break;
|
||||
default:
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
break;
|
||||
}
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
85
FModel/Creator/Bases/FN/BaseMtxOffer.cs
Normal file
85
FModel/Creator/Bases/FN/BaseMtxOffer.cs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Objects;
|
||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
{
|
||||
public class BaseMtxOffer : UCreator
|
||||
{
|
||||
public BaseMtxOffer(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
Background = new[] {SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69")};
|
||||
Border = new[] {SKColor.Parse("9092AB")};
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FStructFallback typeImage, "DetailsImage", "TileImage") &&
|
||||
typeImage.TryGetValue(out FPackageIndex resource, "ResourceObject"))
|
||||
{
|
||||
Preview = Utils.GetBitmap(resource);
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback gradient, "Gradient") &&
|
||||
gradient.TryGetValue(out FLinearColor start, "Start") &&
|
||||
gradient.TryGetValue(out FLinearColor stop, "Stop"))
|
||||
{
|
||||
Background = new[] {SKColor.Parse(start.Hex), SKColor.Parse(stop.Hex)};
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FLinearColor background, "Background"))
|
||||
Border = new[] {SKColor.Parse(background.Hex)};
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text;
|
||||
if (Object.TryGetValue(out FText shortDescription, "ShortDescription"))
|
||||
Description = shortDescription.Text;
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback[] details, "DetailsAttributes"))
|
||||
{
|
||||
foreach (var detail in details)
|
||||
{
|
||||
if (detail.TryGetValue(out FText detailName, "Name"))
|
||||
{
|
||||
Description += $"\n- {detailName.Text.TrimEnd()}";
|
||||
}
|
||||
|
||||
if (detail.TryGetValue(out FText detailValue, "Value") && !string.IsNullOrEmpty(detailValue.Text))
|
||||
{
|
||||
Description += $" ({detailValue.Text})";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Description = Utils.RemoveHtmlTags(Description);
|
||||
}
|
||||
|
||||
public override SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.NoBackground:
|
||||
DrawPreview(c);
|
||||
break;
|
||||
case EIconStyle.NoText:
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
break;
|
||||
default:
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
break;
|
||||
}
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
76
FModel/Creator/Bases/FN/BasePlaylist.cs
Normal file
76
FModel/Creator/Bases/FN/BasePlaylist.cs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using FModel.Services;
|
||||
using FModel.ViewModels;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
{
|
||||
public class BasePlaylist : UCreator
|
||||
{
|
||||
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
|
||||
private SKBitmap _missionIcon;
|
||||
|
||||
public BasePlaylist(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
Margin = 0;
|
||||
Width = 1024;
|
||||
Height = 512;
|
||||
Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Tiles/T_Athena_Tile_Matchmaking_Default.T_Athena_Tile_Matchmaking_Default");
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FText displayName, "UIDisplayName", "DisplayName"))
|
||||
DisplayName = displayName.Text;
|
||||
if (Object.TryGetValue(out FText description, "UIDescription", "Description"))
|
||||
Description = description.Text;
|
||||
if (Object.TryGetValue(out UTexture2D missionIcon, "MissionIcon"))
|
||||
_missionIcon = Utils.GetBitmap(missionIcon).Resize(25);
|
||||
|
||||
if (!Object.TryGetValue(out FName playlistName, "PlaylistName") || string.IsNullOrWhiteSpace(playlistName.Text))
|
||||
return;
|
||||
|
||||
var playlist = _apiEndpointView.FortniteApi.GetPlaylist(playlistName.Text);
|
||||
if (!playlist.IsSuccess || !playlist.Data.Images.HasShowcase ||
|
||||
!_apiEndpointView.FortniteApi.TryGetBytes(playlist.Data.Images.Showcase, out var image))
|
||||
return;
|
||||
|
||||
Preview = Utils.GetBitmap(image).Resize(1024, 512); // Force size to 1024x512 to prevent huge previews.
|
||||
}
|
||||
|
||||
public override SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.NoBackground:
|
||||
DrawPreview(c);
|
||||
break;
|
||||
case EIconStyle.NoText:
|
||||
DrawPreview(c);
|
||||
DrawMissionIcon(c);
|
||||
break;
|
||||
default:
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
DrawMissionIcon(c);
|
||||
break;
|
||||
}
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
|
||||
private void DrawMissionIcon(SKCanvas c)
|
||||
{
|
||||
if (_missionIcon == null) return;
|
||||
c.DrawBitmap(_missionIcon, new SKPoint(5, 5), ImagePaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
259
FModel/Creator/Bases/FN/BaseQuest.cs
Normal file
259
FModel/Creator/Bases/FN/BaseQuest.cs
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Engine;
|
||||
using CUE4Parse.UE4.Assets.Objects;
|
||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using FModel.Extensions;
|
||||
using FModel.Framework;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
{
|
||||
public class BaseQuest : BaseIcon
|
||||
{
|
||||
private int _count;
|
||||
private Reward _reward;
|
||||
private readonly bool _screenLayer;
|
||||
private readonly string[] _unauthorizedReward = {"Token", "ChallengeBundle", "GiftBox"};
|
||||
|
||||
public string NextQuestName { get; private set; }
|
||||
|
||||
public BaseQuest(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
Margin = 0;
|
||||
Width = 1024;
|
||||
Height = 256;
|
||||
DefaultPreview = Utils.GetBitmap("FortniteGame/Content/Athena/HUD/Quests/Art/T_NPC_Default.T_NPC_Default");
|
||||
if (uObject != null)
|
||||
{
|
||||
_screenLayer = uObject.ExportType.Equals("FortFeatItemDefinition", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
private BaseQuest(int completionCount, EIconStyle style) : this(null, style) // completion
|
||||
{
|
||||
var description = completionCount < 0 ?
|
||||
Utils.GetLocalizedResource("AthenaChallengeDetailsEntry", "CompletionRewardFormat_All", "Complete <text color=\"FFF\" case=\"upper\" fontface=\"black\">all {0} challenges</> to earn the reward item") :
|
||||
Utils.GetLocalizedResource("AthenaChallengeDetailsEntry", "CompletionRewardFormat", "Complete <text color=\"FFF\" case=\"upper\" fontface=\"black\">any {0} challenges</> to earn the reward item");
|
||||
|
||||
DisplayName = ReformatString(description, completionCount.ToString(), completionCount < 0);
|
||||
}
|
||||
|
||||
public BaseQuest(int completionCount, FSoftObjectPath itemDefinition, EIconStyle style) : this(completionCount, style) // completion
|
||||
{
|
||||
_reward = Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject) ? new Reward(uObject) : new Reward();
|
||||
}
|
||||
|
||||
public BaseQuest(int completionCount, int quantity, string reward, EIconStyle style) : this(completionCount, style) // completion
|
||||
{
|
||||
_reward = new Reward(quantity, reward);
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
ParseForReward(false);
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback urgentQuestData, "UrgentQuestData"))
|
||||
{
|
||||
if (urgentQuestData.TryGetValue(out FText eventTitle, "EventTitle"))
|
||||
DisplayName = eventTitle.Text;
|
||||
if (urgentQuestData.TryGetValue(out FText eventDescription, "EventDescription"))
|
||||
Description = eventDescription.Text;
|
||||
if (urgentQuestData.TryGetValue(out FPackageIndex alertIcon, "AlertIcon", "BountyPriceImage"))
|
||||
Preview = Utils.GetBitmap(alertIcon);
|
||||
}
|
||||
else
|
||||
{
|
||||
Description = ShortDescription;
|
||||
if (Object.TryGetValue(out FText completionText, "CompletionText"))
|
||||
Description += "\n" + completionText.Text;
|
||||
if (Object.TryGetValue(out FSoftObjectPath tandemCharacterData, "TandemCharacterData") &&
|
||||
Utils.TryLoadObject(tandemCharacterData.AssetPathName.Text, out UObject uObject) &&
|
||||
uObject.TryGetValue(out FSoftObjectPath tandemIcon, "SidePanelIcon", "EntryListIcon", "ToastIcon"))
|
||||
{
|
||||
Preview = Utils.GetBitmap(tandemIcon);
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out int objectiveCompletionCount, "ObjectiveCompletionCount"))
|
||||
_count = objectiveCompletionCount;
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback[] objectives, "Objectives") && objectives.Length > 0)
|
||||
{
|
||||
// actual description doesn't exist
|
||||
if (string.IsNullOrEmpty(Description) && objectives[0].TryGetValue(out FText description, "Description"))
|
||||
Description = description.Text;
|
||||
|
||||
// ObjectiveCompletionCount doesn't exist
|
||||
if (_count == 0)
|
||||
{
|
||||
if (objectives[0].TryGetValue(out int count, "Count") && count > 1)
|
||||
_count = count;
|
||||
else
|
||||
_count = objectives.Length;
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback[] rewards, "Rewards"))
|
||||
{
|
||||
foreach (var reward in rewards)
|
||||
{
|
||||
if (!reward.TryGetValue(out FStructFallback itemPrimaryAssetId, "ItemPrimaryAssetId") ||
|
||||
!reward.TryGetValue(out int quantity, "Quantity")) continue;
|
||||
|
||||
if (!itemPrimaryAssetId.TryGetValue(out FStructFallback primaryAssetType, "PrimaryAssetType") ||
|
||||
!itemPrimaryAssetId.TryGetValue(out FName primaryAssetName, "PrimaryAssetName") ||
|
||||
!primaryAssetType.TryGetValue(out FName name, "Name")) continue;
|
||||
|
||||
if (name.Text.Equals("Quest", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
NextQuestName = primaryAssetName.Text;
|
||||
}
|
||||
else if (!_unauthorizedReward.Contains(name.Text))
|
||||
{
|
||||
_reward = new Reward(quantity, primaryAssetName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_reward == null && Object.TryGetValue(out UDataTable rewardsTable, "RewardsTable"))
|
||||
{
|
||||
if (rewardsTable.TryGetDataTableRow("Default", StringComparison.InvariantCulture, out var row))
|
||||
{
|
||||
if (row.TryGetValue(out FName templateId, "TemplateId") &&
|
||||
row.TryGetValue(out int quantity, "Quantity"))
|
||||
{
|
||||
_reward = new Reward(quantity, templateId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_reward == null && Object.TryGetValue(out FStructFallback[] hiddenRewards, "HiddenRewards"))
|
||||
{
|
||||
foreach (var hiddenReward in hiddenRewards)
|
||||
{
|
||||
if (!hiddenReward.TryGetValue(out FName templateId, "TemplateId") ||
|
||||
!hiddenReward.TryGetValue(out int quantity, "Quantity")) continue;
|
||||
|
||||
_reward = new Reward(quantity, templateId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_reward ??= new Reward();
|
||||
}
|
||||
|
||||
public void DrawQuest(SKCanvas c, int y)
|
||||
{
|
||||
DrawBackground(c, y);
|
||||
DrawPreview(c, y);
|
||||
DrawTexts(c, y);
|
||||
}
|
||||
|
||||
public override SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawQuest(c, 0);
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
|
||||
private string ReformatString(string s, string completionCount, bool isAll)
|
||||
{
|
||||
s = s.Replace("({0})", "{0}").Replace("{QuestNumber}", "<text color=\"FFF\" case=\"upper\" fontface=\"black\">{0}</>");
|
||||
var index = s.IndexOf("{0}|plural(", StringComparison.OrdinalIgnoreCase);
|
||||
if (index > -1)
|
||||
{
|
||||
var p = s.Substring(index, s[index..].IndexOf(')') + 1);
|
||||
s = s.Replace(p, string.Empty);
|
||||
s = s.Insert(s.IndexOf("</>", StringComparison.OrdinalIgnoreCase), p.SubstringAfter("(").SubstringAfter("=").SubstringBefore(","));
|
||||
}
|
||||
|
||||
var upper = s.SubstringAfter(">").SubstringBefore("</>");
|
||||
return string.Format(Utils.RemoveHtmlTags(s.Replace(upper, upper.ToUpper())), isAll ? string.Empty : completionCount).Replace(" ", " ");
|
||||
}
|
||||
|
||||
private readonly SKPaint _informationPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Color = SKColor.Parse("#262630")
|
||||
};
|
||||
|
||||
private void DrawBackground(SKCanvas c, int y)
|
||||
{
|
||||
c.DrawRect(new SKRect(Margin, y, Width, y + Height), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, y + Height / 2), Width / 5 * 4,
|
||||
new[] {Background[0].WithAlpha(50), Background[1].WithAlpha(50)}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(Height / 2, y, Width, y + Height), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, y + Height), new SKPoint(Width / 2, 75),
|
||||
new[] {SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0)}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(0, y + 75, Width, y + Height), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, y + Height / 2), Width / 5 * 4, Background, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(Margin, y, Height / 2, y + Height), _informationPaint);
|
||||
|
||||
_informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Height / 2, y + Height / 2), new SKPoint(Height / 2 + 100, y + Height / 2),
|
||||
new[] {SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0)}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(Height / 2, y, Height / 2 + 100, y + Height), _informationPaint);
|
||||
}
|
||||
|
||||
private void DrawPreview(SKCanvas c, int y)
|
||||
{
|
||||
ImagePaint.BlendMode = _screenLayer ? SKBlendMode.Screen : Preview == null ? SKBlendMode.ColorBurn : SKBlendMode.SrcOver;
|
||||
c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, y, Height - Margin, y + Height), ImagePaint);
|
||||
}
|
||||
|
||||
private void DrawTexts(SKCanvas c, int y)
|
||||
{
|
||||
_informationPaint.Shader = null;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(DisplayName))
|
||||
{
|
||||
_informationPaint.TextSize = 40;
|
||||
_informationPaint.Color = SKColors.White;
|
||||
_informationPaint.Typeface = Utils.Typefaces.Bundle;
|
||||
while (_informationPaint.MeasureText(DisplayName) > Width - Height - 10)
|
||||
{
|
||||
_informationPaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_informationPaint.Typeface);
|
||||
shaper.Shape(DisplayName, _informationPaint);
|
||||
c.DrawShapedText(shaper, DisplayName, Height, y + 50, _informationPaint);
|
||||
}
|
||||
|
||||
var outY = y + 75f;
|
||||
if (!string.IsNullOrWhiteSpace(Description))
|
||||
{
|
||||
_informationPaint.TextSize = 16;
|
||||
_informationPaint.Color = SKColors.White.WithAlpha(175);
|
||||
_informationPaint.Typeface = Utils.Typefaces.Description;
|
||||
Utils.DrawMultilineText(c, Description, Width - Height, y, SKTextAlign.Left,
|
||||
new SKRect(Height, outY, Width - 10, y + Height), _informationPaint, out outY);
|
||||
}
|
||||
|
||||
_informationPaint.Color = Border[0].WithAlpha(100);
|
||||
c.DrawRect(new SKRect(Height, outY, Width - 150, outY + 5), _informationPaint);
|
||||
|
||||
if (_count > 0)
|
||||
{
|
||||
_informationPaint.TextSize = 25;
|
||||
_informationPaint.Color = SKColors.White;
|
||||
_informationPaint.Typeface = Utils.Typefaces.BundleNumber;
|
||||
c.DrawText("0 / ", new SKPoint(Width - 130, outY + 10), _informationPaint);
|
||||
|
||||
_informationPaint.Color = Border[0];
|
||||
c.DrawText(_count.ToString(), new SKPoint(Width - 95, outY + 10), _informationPaint);
|
||||
}
|
||||
|
||||
_reward.DrawQuest(c, new SKRect(Height, outY + 25, Width - 20, y + Height - 25));
|
||||
}
|
||||
}
|
||||
}
|
||||
174
FModel/Creator/Bases/FN/BaseSeason.cs
Normal file
174
FModel/Creator/Bases/FN/BaseSeason.cs
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Objects;
|
||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using FModel.Framework;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
{
|
||||
public class BaseSeason : UCreator
|
||||
{
|
||||
private Reward _firstWinReward;
|
||||
private Dictionary<int, List<Reward>> _bookXpSchedule;
|
||||
private const int _headerHeight = 150;
|
||||
// keep the list because rewards are ordered by least to most important
|
||||
// we only care about the most but we also have filters so we can't just take the last reward
|
||||
|
||||
public BaseSeason(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
Width = 1024;
|
||||
Height = _headerHeight + 50;
|
||||
Margin = 0;
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
_bookXpSchedule = new Dictionary<int, List<Reward>>();
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text.ToUpperInvariant();
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback seasonFirstWinRewards, "SeasonFirstWinRewards") &&
|
||||
seasonFirstWinRewards.TryGetValue(out FStructFallback[] rewards, "Rewards"))
|
||||
{
|
||||
foreach (var reward in rewards)
|
||||
{
|
||||
if (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") ||
|
||||
!Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue;
|
||||
|
||||
_firstWinReward = new Reward(uObject);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var freeLevels = Array.Empty<FStructFallback>();
|
||||
var paidLevels = Array.Empty<FStructFallback>();
|
||||
if (Object.TryGetValue(out FPackageIndex[] additionalSeasonData, "AdditionalSeasonData") &&
|
||||
additionalSeasonData.Length > 0 && Utils.TryGetPackageIndexExport(additionalSeasonData[0], out UObject data) &&
|
||||
data.TryGetValue(out FStructFallback battlePassXpScheduleFree, "BattlePassXpScheduleFree") &&
|
||||
battlePassXpScheduleFree.TryGetValue(out freeLevels, "Levels") &&
|
||||
data.TryGetValue(out FStructFallback battlePassXpSchedulePaid, "BattlePassXpSchedulePaid") &&
|
||||
battlePassXpSchedulePaid.TryGetValue(out paidLevels, "Levels"))
|
||||
{
|
||||
// we got them boys
|
||||
}
|
||||
else if (Object.TryGetValue(out FStructFallback bookXpScheduleFree, "BookXpScheduleFree") &&
|
||||
bookXpScheduleFree.TryGetValue(out freeLevels, "Levels") &&
|
||||
Object.TryGetValue(out FStructFallback bookXpSchedulePaid, "BookXpSchedulePaid") &&
|
||||
bookXpSchedulePaid.TryGetValue(out paidLevels, "Levels"))
|
||||
{
|
||||
// we got them boys
|
||||
}
|
||||
|
||||
for (var i = 0; i < freeLevels.Length; i++)
|
||||
{
|
||||
_bookXpSchedule[i] = new List<Reward>();
|
||||
if (!freeLevels[i].TryGetValue(out rewards, "Rewards")) continue;
|
||||
|
||||
foreach (var reward in rewards)
|
||||
{
|
||||
if (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") ||
|
||||
itemDefinition.AssetPathName.Text.Contains("/Items/Tokens/") ||
|
||||
!Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue;
|
||||
|
||||
_bookXpSchedule[i].Add(new Reward(uObject));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < paidLevels.Length; i++)
|
||||
{
|
||||
if (!paidLevels[i].TryGetValue(out rewards, "Rewards")) continue;
|
||||
|
||||
foreach (var reward in rewards)
|
||||
{
|
||||
if (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") ||
|
||||
!Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue;
|
||||
|
||||
_bookXpSchedule[i].Add(new Reward(uObject));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Height += 100 * _bookXpSchedule.Count / 10;
|
||||
}
|
||||
|
||||
public override SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawHeader(c);
|
||||
_firstWinReward?.DrawSeasonWin(c, _headerHeight);
|
||||
DrawBookSchedule(c);
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
|
||||
private const int _DEFAULT_AREA_SIZE = 80;
|
||||
private readonly SKPaint _headerPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.Bundle, TextSize = 50,
|
||||
TextAlign = SKTextAlign.Center, Color = SKColor.Parse("#262630")
|
||||
};
|
||||
private readonly SKPaint _bookPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.BundleNumber,
|
||||
Color = SKColors.White, TextAlign = SKTextAlign.Center, TextSize = 15
|
||||
};
|
||||
|
||||
public void DrawHeader(SKCanvas c)
|
||||
{
|
||||
c.DrawRect(new SKRect(0, 0, Width, Height), _headerPaint);
|
||||
|
||||
_headerPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
|
||||
new[] {SKColors.SkyBlue.WithAlpha(50), SKColors.Blue.WithAlpha(50)}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(0, 0, Width, Height), _headerPaint);
|
||||
|
||||
_headerPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75),
|
||||
new[] {SKColors.Black.WithAlpha(25), SKColors.Blue.WithAlpha(0)}, SKShaderTileMode.Clamp);
|
||||
c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _headerPaint);
|
||||
|
||||
_headerPaint.Shader = null;
|
||||
_headerPaint.Color = SKColors.White;
|
||||
while (_headerPaint.MeasureText(DisplayName) > Width)
|
||||
{
|
||||
_headerPaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_headerPaint.Typeface);
|
||||
var shapedText = shaper.Shape(DisplayName, _headerPaint);
|
||||
c.DrawShapedText(shaper, DisplayName, (Width - shapedText.Points[^1].X) / 2, _headerHeight / 2 + _headerPaint.TextSize / 2 - 10, _headerPaint);
|
||||
}
|
||||
|
||||
private void DrawBookSchedule(SKCanvas c)
|
||||
{
|
||||
var x = 20;
|
||||
var y = _headerHeight + 50;
|
||||
foreach (var (index, reward) in _bookXpSchedule)
|
||||
{
|
||||
if (index == 0 || reward.Count == 0 || !reward[0].HasReward())
|
||||
continue;
|
||||
|
||||
c.DrawText(index.ToString(), new SKPoint(x + _DEFAULT_AREA_SIZE / 2, y - 5), _bookPaint);
|
||||
reward[0].DrawSeason(c, x, y, _DEFAULT_AREA_SIZE);
|
||||
|
||||
if (index != 1 && index % 10 == 0)
|
||||
{
|
||||
y += _DEFAULT_AREA_SIZE + 20;
|
||||
x = 20;
|
||||
}
|
||||
else
|
||||
{
|
||||
x += _DEFAULT_AREA_SIZE + 20;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
FModel/Creator/Bases/FN/BaseSeries.cs
Normal file
27
FModel/Creator/Bases/FN/BaseSeries.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
{
|
||||
public class BaseSeries : BaseIcon
|
||||
{
|
||||
public BaseSeries(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
GetSeries(Object);
|
||||
}
|
||||
|
||||
public override SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawBackground(c);
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
193
FModel/Creator/Bases/FN/BaseUserControl.cs
Normal file
193
FModel/Creator/Bases/FN/BaseUserControl.cs
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Objects;
|
||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using FModel.Framework;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
{
|
||||
public class BaseUserControl : UCreator
|
||||
{
|
||||
private List<Options> _optionValues = new();
|
||||
|
||||
private readonly SKPaint _displayNamePaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.DisplayName, TextSize = 45,
|
||||
Color = SKColors.White, TextAlign = SKTextAlign.Left
|
||||
};
|
||||
private readonly SKPaint _descriptionPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.DisplayName, TextSize = 25,
|
||||
Color = SKColor.Parse("88DBFF"), TextAlign = SKTextAlign.Left
|
||||
};
|
||||
|
||||
public BaseUserControl(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
Width = 512;
|
||||
Height = 128;
|
||||
Margin = 32;
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FText optionDisplayName, "OptionDisplayName"))
|
||||
DisplayName = optionDisplayName.Text.ToUpperInvariant();
|
||||
if (Object.TryGetValue(out FText optionDescription, "OptionDescription"))
|
||||
{
|
||||
Description = optionDescription.Text;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Description)) return;
|
||||
Height += (int) _descriptionPaint.TextSize * Utils.SplitLines(Description, _descriptionPaint, Width - Margin).Count;
|
||||
Height += (int) _descriptionPaint.TextSize;
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback[] optionValues, "OptionValues"))
|
||||
{
|
||||
_optionValues = new List<Options>();
|
||||
foreach (var option in optionValues)
|
||||
{
|
||||
if (option.TryGetValue(out FText displayName, "DisplayName"))
|
||||
{
|
||||
var opt = new Options {Option = displayName.Text.ToUpperInvariant()};
|
||||
if (option.TryGetValue(out FLinearColor color, "Value"))
|
||||
opt.Color = SKColor.Parse(color.Hex).WithAlpha(150);
|
||||
|
||||
_optionValues.Add(opt);
|
||||
}
|
||||
else if (option.TryGetValue(out FName primaryAssetName, "PrimaryAssetName"))
|
||||
{
|
||||
_optionValues.Add(new Options {Option = primaryAssetName.Text});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FText optionOnText, "OptionOnText"))
|
||||
_optionValues.Add(new Options {Option = optionOnText.Text});
|
||||
if (Object.TryGetValue(out FText optionOffText, "OptionOffText"))
|
||||
_optionValues.Add(new Options {Option = optionOffText.Text});
|
||||
|
||||
if (Object.TryGetValue(out int iMin, "Min", "DefaultValue") &&
|
||||
Object.TryGetValue(out int iMax, "Max"))
|
||||
{
|
||||
var increment = iMin;
|
||||
if (Object.TryGetValue(out int incrementValue, "IncrementValue"))
|
||||
increment = incrementValue;
|
||||
|
||||
var format = "{0}";
|
||||
if (Object.TryGetValue(out FText unitName, "UnitName"))
|
||||
format = unitName.Text;
|
||||
|
||||
for (var i = iMin; i <= iMax; i += increment)
|
||||
{
|
||||
_optionValues.Add(new Options {Option = string.Format(format, i)});
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out float fMin, "Min", "DefaultValue") &&
|
||||
Object.TryGetValue(out float fMax, "Max"))
|
||||
{
|
||||
var increment = fMin;
|
||||
if (Object.TryGetValue(out float incrementValue, "IncrementValue"))
|
||||
increment = incrementValue;
|
||||
|
||||
var format = "{0}";
|
||||
if (Object.TryGetValue(out FText unitName, "UnitName"))
|
||||
format = unitName.Text;
|
||||
|
||||
for (var i = fMin; i <= fMax; i += increment)
|
||||
{
|
||||
_optionValues.Add(new Options {Option = string.Format(format, i)});
|
||||
}
|
||||
}
|
||||
|
||||
Height += Margin;
|
||||
Height += 35 * _optionValues.Count;
|
||||
}
|
||||
|
||||
public override SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawBackground(c);
|
||||
DrawInformation(c);
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
|
||||
private new void DrawBackground(SKCanvas c)
|
||||
{
|
||||
c.DrawRect(new SKRect(0, 0, Width, Height),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateLinearGradient(
|
||||
new SKPoint(Width / 2, Height),
|
||||
new SKPoint(Width, Height / 4),
|
||||
new[] {SKColor.Parse("01369C"), SKColor.Parse("1273C8")},
|
||||
SKShaderTileMode.Clamp)
|
||||
});
|
||||
}
|
||||
|
||||
private void DrawInformation(SKCanvas c)
|
||||
{
|
||||
// display name
|
||||
while (_displayNamePaint.MeasureText(DisplayName) > Width - Margin * 2)
|
||||
{
|
||||
_displayNamePaint.TextSize -= 2;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_displayNamePaint.Typeface);
|
||||
shaper.Shape(DisplayName, _displayNamePaint);
|
||||
c.DrawShapedText(shaper, DisplayName, Margin, Margin + _displayNamePaint.TextSize, _displayNamePaint);
|
||||
#if DEBUG
|
||||
c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Margin + _displayNamePaint.TextSize), new SKPaint {Color = SKColors.Blue, IsStroke = true});
|
||||
#endif
|
||||
|
||||
// description
|
||||
float y = Margin;
|
||||
if (!string.IsNullOrEmpty(DisplayName)) y += _displayNamePaint.TextSize;
|
||||
if (!string.IsNullOrEmpty(Description)) y += _descriptionPaint.TextSize + Margin / 2F;
|
||||
|
||||
Utils.DrawMultilineText(c, Description, Width, Margin, SKTextAlign.Left,
|
||||
new SKRect(Margin, y, Width - Margin, 256), _descriptionPaint, out var top);
|
||||
|
||||
// options
|
||||
foreach (var option in _optionValues)
|
||||
{
|
||||
option.Draw(c, Margin, Width, ref top);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Options
|
||||
{
|
||||
private const int _SPACE = 5;
|
||||
private const int _HEIGHT = 30;
|
||||
|
||||
private readonly SKPaint _optionPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.DisplayName, TextSize = 20,
|
||||
Color = SKColor.Parse("EEFFFF"), TextAlign = SKTextAlign.Left
|
||||
};
|
||||
|
||||
public string Option;
|
||||
public SKColor Color = SKColor.Parse("55C5FC").WithAlpha(150);
|
||||
|
||||
public void Draw(SKCanvas c, int margin, int width, ref float top)
|
||||
{
|
||||
c.DrawRect(new SKRect(margin, top, width - margin, top + _HEIGHT), new SKPaint {IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = Color});
|
||||
c.DrawText(Option, margin + _SPACE * 2, top + 20 * 1.1f, _optionPaint);
|
||||
top += _HEIGHT + _SPACE;
|
||||
}
|
||||
}
|
||||
}
|
||||
153
FModel/Creator/Bases/FN/Reward.cs
Normal file
153
FModel/Creator/Bases/FN/Reward.cs
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
using System;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using FModel.Framework;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Bases.FN
|
||||
{
|
||||
public class Reward
|
||||
{
|
||||
private string _rewardQuantity;
|
||||
private BaseIcon _theReward;
|
||||
|
||||
public bool HasReward() => _theReward != null;
|
||||
|
||||
public Reward()
|
||||
{
|
||||
_rewardQuantity = "x0";
|
||||
}
|
||||
|
||||
public Reward(int quantity, FName primaryAssetName) : this(quantity, primaryAssetName.Text)
|
||||
{
|
||||
}
|
||||
|
||||
public Reward(int quantity, string assetName) : this()
|
||||
{
|
||||
_rewardQuantity = $"x{quantity:###,###,###}".Trim();
|
||||
|
||||
if (assetName.Contains(':'))
|
||||
{
|
||||
var parts = assetName.Split(':');
|
||||
|
||||
if (parts[0].Equals("HomebaseBannerIcon", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
if (!Utils.TryLoadObject($"FortniteGame/Content/Items/BannerIcons/{parts[1]}.{parts[1]}", out UObject p))
|
||||
return;
|
||||
|
||||
_theReward = new BaseIcon(p, EIconStyle.Default);
|
||||
_theReward.ParseForReward(false);
|
||||
_theReward.Border[0] = SKColors.White;
|
||||
_rewardQuantity = _theReward.DisplayName;
|
||||
}
|
||||
else GetReward(parts[1]);
|
||||
}
|
||||
else GetReward(assetName);
|
||||
}
|
||||
|
||||
public Reward(UObject uObject)
|
||||
{
|
||||
_theReward = new BaseIcon(uObject, EIconStyle.Default);
|
||||
_theReward.ParseForReward(false);
|
||||
_theReward.Border[0] = SKColors.White;
|
||||
_rewardQuantity = _theReward.DisplayName;
|
||||
}
|
||||
|
||||
private readonly SKPaint _rewardPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High
|
||||
};
|
||||
|
||||
public void DrawQuest(SKCanvas c, SKRect rect)
|
||||
{
|
||||
_rewardPaint.TextSize = 50;
|
||||
if (HasReward())
|
||||
{
|
||||
c.DrawBitmap(_theReward.Preview.Resize((int) rect.Height), new SKPoint(rect.Left, rect.Top), _rewardPaint);
|
||||
|
||||
_rewardPaint.Color = _theReward.Border[0];
|
||||
_rewardPaint.Typeface = _rewardQuantity.StartsWith("x") ? Utils.Typefaces.BundleNumber : Utils.Typefaces.Bundle;
|
||||
_rewardPaint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 5, 5, _theReward.Background[0].WithAlpha(150));
|
||||
while (_rewardPaint.MeasureText(_rewardQuantity) > rect.Width)
|
||||
{
|
||||
_rewardPaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(_rewardPaint.Typeface);
|
||||
shaper.Shape(_rewardQuantity, _rewardPaint);
|
||||
c.DrawShapedText(shaper, _rewardQuantity, rect.Left + rect.Height + 25, rect.MidY + 20, _rewardPaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
_rewardPaint.Color = SKColors.White;
|
||||
_rewardPaint.Typeface = Utils.Typefaces.BundleNumber;
|
||||
c.DrawText("No Reward", new SKPoint(rect.Left, rect.MidY + 20), _rewardPaint);
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawSeasonWin(SKCanvas c, int size)
|
||||
{
|
||||
if (!HasReward()) return;
|
||||
c.DrawBitmap(_theReward.Preview.Resize(size), new SKPoint(0, 0), _rewardPaint);
|
||||
}
|
||||
|
||||
public void DrawSeason(SKCanvas c, int x, int y, int areaSize)
|
||||
{
|
||||
if (!HasReward()) return;
|
||||
|
||||
// area + icon
|
||||
_rewardPaint.Color = SKColor.Parse("#0F5CAF");
|
||||
c.DrawRect(new SKRect(x, y, x + areaSize, y + areaSize), _rewardPaint);
|
||||
c.DrawBitmap(_theReward.Preview.Resize(areaSize), new SKPoint(x, y), _rewardPaint);
|
||||
|
||||
// rarity color
|
||||
_rewardPaint.Color = _theReward.Background[0];
|
||||
var pathBottom = new SKPath {FillType = SKPathFillType.EvenOdd};
|
||||
pathBottom.MoveTo(x, y + areaSize);
|
||||
pathBottom.LineTo(x, y + areaSize - areaSize / 25 * 2.5f);
|
||||
pathBottom.LineTo(x + areaSize, y + areaSize - areaSize / 25 * 4.5f);
|
||||
pathBottom.LineTo(x + areaSize, y + areaSize);
|
||||
pathBottom.Close();
|
||||
c.DrawPath(pathBottom, _rewardPaint);
|
||||
}
|
||||
|
||||
private void GetReward(string trigger)
|
||||
{
|
||||
switch (trigger.ToLower())
|
||||
{
|
||||
case "athenabattlestar":
|
||||
_theReward = new BaseIcon(null, EIconStyle.Default);
|
||||
_theReward.Border[0] = SKColor.Parse("FFDB67");
|
||||
_theReward.Background[0] = SKColor.Parse("8F4A20");
|
||||
_theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-FNBR-BattlePoints.T-FNBR-BattlePoints");
|
||||
break;
|
||||
case "athenaseasonalxp":
|
||||
_theReward = new BaseIcon(null, EIconStyle.Default);
|
||||
_theReward.Border[0] = SKColor.Parse("E6FDB1");
|
||||
_theReward.Background[0] = SKColor.Parse("51830F");
|
||||
_theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-FNBR-XPMedium.T-FNBR-XPMedium");
|
||||
break;
|
||||
case "mtxgiveaway":
|
||||
_theReward = new BaseIcon(null, EIconStyle.Default);
|
||||
_theReward.Border[0] = SKColor.Parse("DCE6FF");
|
||||
_theReward.Background[0] = SKColor.Parse("64A0AF");
|
||||
_theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-Items-MTX.T-Items-MTX");
|
||||
break;
|
||||
default:
|
||||
{
|
||||
var path = Utils.GetFullPath($"FortniteGame/Content/Athena/.*?/{trigger}.*"); // path has no objectname and its needed so we push the trigger again as the objectname
|
||||
if (!string.IsNullOrWhiteSpace(path) && Utils.TryLoadObject(path.Replace("uasset", trigger), out UObject d))
|
||||
{
|
||||
_theReward = new BaseIcon(d, EIconStyle.Default);
|
||||
_theReward.ParseForReward(false);
|
||||
_theReward.Border[0] = SKColors.White;
|
||||
_rewardQuantity = _theReward.DisplayName;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases
|
||||
{
|
||||
interface IBase
|
||||
{
|
||||
SKBitmap FallbackImage { get; }
|
||||
SKBitmap IconImage { get; }
|
||||
SKColor[] RarityBackgroundColors { get; }
|
||||
SKColor[] RarityBorderColor { get; }
|
||||
string DisplayName { get; }
|
||||
string Description { get; }
|
||||
int Width { get; }
|
||||
int Height { get; }
|
||||
int Margin { get; }
|
||||
}
|
||||
}
|
||||
48
FModel/Creator/Bases/SB/BaseDivision.cs
Normal file
48
FModel/Creator/Bases/SB/BaseDivision.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.SB
|
||||
{
|
||||
public class BaseDivision : UCreator
|
||||
{
|
||||
public BaseDivision(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FPackageIndex icon, "Icon", "IconNoTier"))
|
||||
{
|
||||
Preview = Utils.GetBitmap(icon);
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FLinearColor lightColor, "UILightColor") &&
|
||||
Object.TryGetValue(out FLinearColor mediumColor, "UIMediumColor") &&
|
||||
Object.TryGetValue(out FLinearColor darkColor, "UIDarkColor") &&
|
||||
Object.TryGetValue(out FLinearColor cardColor, "UICardColor"))
|
||||
{
|
||||
Background = new[] {SKColor.Parse(lightColor.Hex), SKColor.Parse(cardColor.Hex)};
|
||||
Border = new[] {SKColor.Parse(mediumColor.Hex), SKColor.Parse(darkColor.Hex)};
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text;
|
||||
}
|
||||
|
||||
public override SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
55
FModel/Creator/Bases/SB/BaseLeague.cs
Normal file
55
FModel/Creator/Bases/SB/BaseLeague.cs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.SB
|
||||
{
|
||||
public class BaseLeague : UCreator
|
||||
{
|
||||
private int _promotionXp, _xpLostPerMatch;
|
||||
|
||||
public BaseLeague(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
_promotionXp = 0;
|
||||
_xpLostPerMatch = 0;
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out int promotionXp, "PromotionXP"))
|
||||
_promotionXp = promotionXp;
|
||||
if (Object.TryGetValue(out int xpLostPerMatch, "XPLostPerMatch"))
|
||||
_xpLostPerMatch = xpLostPerMatch;
|
||||
|
||||
if (Object.TryGetValue(out FPackageIndex division, "Division") &&
|
||||
Utils.TryGetPackageIndexExport(division, out UObject div))
|
||||
{
|
||||
var d = new BaseDivision(div, Style);
|
||||
d.ParseForInfo();
|
||||
Preview = d.Preview;
|
||||
Background = d.Background;
|
||||
Border = d.Border;
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName"))
|
||||
DisplayName = displayName.Text;
|
||||
}
|
||||
|
||||
public override SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
|
||||
DrawToBottom(c, SKTextAlign.Left, $"PromotionXP: {_promotionXp}");
|
||||
DrawToBottom(c, SKTextAlign.Right, $"XPLostPerMatch: {_xpLostPerMatch}");
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
98
FModel/Creator/Bases/SB/BaseSpellIcon.cs
Normal file
98
FModel/Creator/Bases/SB/BaseSpellIcon.cs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
using System;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Engine;
|
||||
using CUE4Parse.UE4.Objects.Core.i18N;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using FModel.Creator.Bases.FN;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.SB
|
||||
{
|
||||
public class BaseSpellIcon : BaseIcon
|
||||
{
|
||||
private SKBitmap _seriesBackground2;
|
||||
|
||||
private readonly SKPaint _overlayPaint = new()
|
||||
{
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
IsAntialias = true,
|
||||
Color = SKColors.Transparent.WithAlpha(75)
|
||||
};
|
||||
|
||||
public BaseSpellIcon(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
Background = new[] {SKColor.Parse("FFFFFF"), SKColor.Parse("636363")};
|
||||
Border = new[] {SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF")};
|
||||
Width = Object.ExportType.StartsWith("BP_Cosmetic_Card") ? 1536 : 512;
|
||||
Height = Object.ExportType.StartsWith("BP_Cosmetic_Card") ? 450 : 512;
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FName rarity, "Rarity"))
|
||||
GetRarity(rarity);
|
||||
|
||||
if (Object.TryGetValue(out FSoftObjectPath preview, "IconTexture"))
|
||||
Preview = Utils.GetBitmap(preview);
|
||||
else if (Object.TryGetValue(out FPackageIndex icon, "IconTexture"))
|
||||
Preview = Utils.GetBitmap(icon);
|
||||
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName", "Title"))
|
||||
DisplayName = displayName.Text;
|
||||
if (Object.TryGetValue(out FText description, "Description"))
|
||||
Description = description.Text;
|
||||
|
||||
SeriesBackground = Utils.GetBitmap("g3/Content/UI/Textures/assets/HUDAccentFillBox.HUDAccentFillBox");
|
||||
_seriesBackground2 = Utils.GetBitmap("g3/Content/UI/Textures/assets/store/ItemBGStatic_UIT.ItemBGStatic_UIT");
|
||||
}
|
||||
|
||||
public override SKImage Draw()
|
||||
{
|
||||
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
DrawBackgrounds(c);
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
DrawTextBackground(c);
|
||||
DrawDisplayName(c);
|
||||
DrawDescription(c);
|
||||
|
||||
return SKImage.FromBitmap(ret);
|
||||
}
|
||||
|
||||
private void DrawBackgrounds(SKCanvas c)
|
||||
{
|
||||
if (SeriesBackground != null)
|
||||
c.DrawBitmap(SeriesBackground, new SKRect(0, 0, Width, Height), ImagePaint);
|
||||
if (_seriesBackground2 != null)
|
||||
c.DrawBitmap(_seriesBackground2, new SKRect(0, 0, Width, Height), _overlayPaint);
|
||||
|
||||
var x = Margin * (int) 2.5;
|
||||
const int radi = 15;
|
||||
c.DrawCircle(x + radi, x + radi, radi, new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateRadialGradient(
|
||||
new SKPoint(radi, radi), radi * 2 / 5 * 4,
|
||||
Background, SKShaderTileMode.Clamp)
|
||||
});
|
||||
}
|
||||
|
||||
private void GetRarity(FName n)
|
||||
{
|
||||
if (!Utils.TryLoadObject("g3/Content/UI/UIKit/DT_RarityColors.DT_RarityColors", out UDataTable rarity)) return;
|
||||
|
||||
if (rarity.TryGetDataTableRow(n.Text["EXRarity::".Length..], StringComparison.Ordinal, out var row))
|
||||
{
|
||||
if (row.TryGetValue(out FLinearColor[] colors, "Colors"))
|
||||
{
|
||||
Background = new[] {SKColor.Parse(colors[0].Hex), SKColor.Parse(colors[2].Hex)};
|
||||
Border = new[] {SKColor.Parse(colors[1].Hex), SKColor.Parse(colors[0].Hex)};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
230
FModel/Creator/Bases/UCreator.cs
Normal file
230
FModel/Creator/Bases/UCreator.cs
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
using System;
|
||||
using System.Windows;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using FModel.Creator.Bases.FN;
|
||||
using FModel.Framework;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Bases
|
||||
{
|
||||
public abstract class UCreator
|
||||
{
|
||||
protected UObject Object { get; }
|
||||
protected EIconStyle Style { get; }
|
||||
public SKBitmap DefaultPreview { get; set; }
|
||||
public SKBitmap Preview { get; set; }
|
||||
public SKColor[] Background { get; protected set; }
|
||||
public SKColor[] Border { get; protected set; }
|
||||
public string DisplayName { get; protected set; }
|
||||
public string Description { get; protected set; }
|
||||
public int Margin { get; protected set; }
|
||||
public int Width { get; protected set; }
|
||||
public int Height { get; protected set; }
|
||||
|
||||
public abstract void ParseForInfo();
|
||||
public abstract SKImage Draw();
|
||||
|
||||
protected UCreator(UObject uObject, EIconStyle style)
|
||||
{
|
||||
DefaultPreview = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png"))?.Stream);
|
||||
Background = new[] {SKColor.Parse("5BFD00"), SKColor.Parse("003700")};
|
||||
Border = new[] {SKColor.Parse("1E8500"), SKColor.Parse("5BFD00")};
|
||||
DisplayName = string.Empty;
|
||||
Description = string.Empty;
|
||||
Width = 512;
|
||||
Height = 512;
|
||||
Margin = 2;
|
||||
Object = uObject;
|
||||
Style = style;
|
||||
}
|
||||
|
||||
private const int _STARTER_TEXT_POSITION = 380, _NAME_TEXT_SIZE = 45, _BOTTOM_TEXT_SIZE = 15;
|
||||
protected readonly SKPaint DisplayNamePaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.DisplayName, TextSize = _NAME_TEXT_SIZE,
|
||||
Color = SKColors.White, TextAlign = SKTextAlign.Center
|
||||
};
|
||||
protected readonly SKPaint DescriptionPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Utils.Typefaces.Description, TextSize = 13,
|
||||
Color = SKColors.White
|
||||
};
|
||||
protected readonly SKPaint ImagePaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High
|
||||
};
|
||||
private readonly SKPaint _textBackgroundPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = new SKColor(0, 0, 50, 75)
|
||||
};
|
||||
private readonly SKPaint _shortDescriptionPaint = new()
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Color = SKColors.White
|
||||
};
|
||||
|
||||
public void DrawBackground(SKCanvas c)
|
||||
{
|
||||
// reverse doesn't affect basic rarities
|
||||
if (Background[0] == Background[1]) Background[0] = Border[0];
|
||||
Background[0].ToHsl(out _, out _, out var l1);
|
||||
Background[1].ToHsl(out _, out _, out var l2);
|
||||
var reverse = l1 > l2;
|
||||
|
||||
// border
|
||||
c.DrawRect(new SKRect(0, 0, Width, Height),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateLinearGradient(
|
||||
new SKPoint(Width / 2, Height), new SKPoint(Width, Height / 4), Border, SKShaderTileMode.Clamp)
|
||||
});
|
||||
|
||||
if (this is BaseIcon {SeriesBackground: { }} baseIcon)
|
||||
c.DrawBitmap(baseIcon.SeriesBackground, new SKRect(baseIcon.Margin, baseIcon.Margin, baseIcon.Width - baseIcon.Margin,
|
||||
baseIcon.Height - baseIcon.Margin), ImagePaint);
|
||||
else
|
||||
{
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.Flat:
|
||||
{
|
||||
c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Height - Margin),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, Height), new SKPoint(Width, Height / 4),
|
||||
new[] {Background[reverse ? 0 : 1].WithAlpha(150), Border[0]}, SKShaderTileMode.Clamp)
|
||||
});
|
||||
if (string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return;
|
||||
|
||||
var pathTop = new SKPath {FillType = SKPathFillType.EvenOdd};
|
||||
pathTop.MoveTo(Margin, Margin);
|
||||
pathTop.LineTo(Margin + Width / 17 * 10, Margin);
|
||||
pathTop.LineTo(Margin, Margin + Height / 17);
|
||||
pathTop.Close();
|
||||
c.DrawPath(pathTop, new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Color = Background[1].WithAlpha(75)
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Height - Margin),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, Height / 2), Width / 5 * 4,
|
||||
new[] {Background[reverse ? 0 : 1], Background[reverse ? 1 : 0]},
|
||||
SKShaderTileMode.Clamp)
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void DrawPreview(SKCanvas c)
|
||||
=> c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, Margin, Width - Margin, Height - Margin), ImagePaint);
|
||||
|
||||
protected void DrawTextBackground(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return;
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.Flat:
|
||||
{
|
||||
var pathBottom = new SKPath {FillType = SKPathFillType.EvenOdd};
|
||||
pathBottom.MoveTo(Margin, Height - Margin);
|
||||
pathBottom.LineTo(Margin, Height - Margin - Height / 17 * 2.5f);
|
||||
pathBottom.LineTo(Width - Margin, Height - Margin - Height / 17 * 4.5f);
|
||||
pathBottom.LineTo(Width - Margin, Height - Margin);
|
||||
pathBottom.Close();
|
||||
c.DrawPath(pathBottom, _textBackgroundPaint);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
c.DrawRect(new SKRect(Margin, _STARTER_TEXT_POSITION, Width - Margin, Height - Margin), _textBackgroundPaint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void DrawDisplayName(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(DisplayName)) return;
|
||||
|
||||
while (DisplayNamePaint.MeasureText(DisplayName) > Width - Margin * 2)
|
||||
{
|
||||
DisplayNamePaint.TextSize -= 1;
|
||||
}
|
||||
|
||||
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
|
||||
var shapedText = shaper.Shape(DisplayName, DisplayNamePaint);
|
||||
var x = (Width - shapedText.Points[^1].X) / 2;
|
||||
var y = _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE;
|
||||
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.Flat:
|
||||
{
|
||||
DisplayNamePaint.TextAlign = SKTextAlign.Right;
|
||||
x = Width - Margin * 2 - shapedText.Points[^1].X;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
c.DrawLine(x, 0, x, Width, new SKPaint {Color = SKColors.Blue, IsStroke = true});
|
||||
c.DrawLine(x + shapedText.Points[^1].X, 0, x + shapedText.Points[^1].X, Width, new SKPaint {Color = SKColors.Blue, IsStroke = true});
|
||||
c.DrawRect(new SKRect(Margin, _STARTER_TEXT_POSITION, Width - Margin, y), new SKPaint {Color = SKColors.Blue, IsStroke = true});
|
||||
#endif
|
||||
|
||||
c.DrawShapedText(shaper, DisplayName, x, y, DisplayNamePaint);
|
||||
}
|
||||
|
||||
protected void DrawDescription(SKCanvas c)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Description)) return;
|
||||
|
||||
var maxLine = string.IsNullOrEmpty(DisplayName) ? 8 : 4;
|
||||
var side = SKTextAlign.Center;
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.Flat:
|
||||
side = SKTextAlign.Right;
|
||||
break;
|
||||
}
|
||||
|
||||
Utils.DrawCenteredMultilineText(c, Description, maxLine, Width, Margin, side,
|
||||
new SKRect(Margin, string.IsNullOrEmpty(DisplayName) ? _STARTER_TEXT_POSITION : _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE, Width - Margin, Height - _BOTTOM_TEXT_SIZE), DescriptionPaint);
|
||||
}
|
||||
|
||||
protected void DrawToBottom(SKCanvas c, SKTextAlign side, string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text)) return;
|
||||
|
||||
_shortDescriptionPaint.TextAlign = side;
|
||||
_shortDescriptionPaint.TextSize = Utils.Typefaces.Bottom == null ? 15 : 13;
|
||||
switch (side)
|
||||
{
|
||||
case SKTextAlign.Left:
|
||||
_shortDescriptionPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.DisplayName;
|
||||
var shaper = new CustomSKShaper(_shortDescriptionPaint.Typeface);
|
||||
shaper.Shape(text, _shortDescriptionPaint);
|
||||
|
||||
c.DrawShapedText(shaper, text, Margin * 2.5f, Width - Margin * 2.5f, _shortDescriptionPaint);
|
||||
break;
|
||||
case SKTextAlign.Right:
|
||||
_shortDescriptionPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.Default;
|
||||
c.DrawText(text, Width - Margin * 2.5f, Width - Margin * 2.5f, _shortDescriptionPaint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
using FModel.Utils;
|
||||
using System;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
|
||||
namespace FModel.Creator.Bundles
|
||||
{
|
||||
public class CompletionReward
|
||||
{
|
||||
private const string _TRIGGER1 = "<text color=\"FFF\" case=\"upper\" fontface=\"black\">";
|
||||
private const string _TRIGGER2 = "</>";
|
||||
public string CompletionText;
|
||||
public Reward Reward;
|
||||
|
||||
public CompletionReward(IntProperty completionCount)
|
||||
{
|
||||
string all = Localizations.GetLocalization("AthenaChallengeDetailsEntry", "CompletionRewardFormat_All", "Complete <text color=\"FFF\" case=\"upper\" fontface=\"black\">all {0} challenges</> to earn the reward item");
|
||||
string allFormated = ReformatString(all, completionCount.Value.ToString(), true);
|
||||
string any = Localizations.GetLocalization("AthenaChallengeDetailsEntry", "CompletionRewardFormat", "Complete <text color=\"FFF\" case=\"upper\" fontface=\"black\">any {0} challenges</> to earn the reward item");
|
||||
string anyFormated = ReformatString(any, completionCount.Value.ToString(), false);
|
||||
CompletionText = completionCount.Value >= 0 ? anyFormated : allFormated;
|
||||
|
||||
Reward = null;
|
||||
}
|
||||
|
||||
public CompletionReward(IntProperty completionCount, IntProperty quantity, SoftObjectProperty itemDefinition) : this(completionCount)
|
||||
{
|
||||
Reward = new Reward(quantity, itemDefinition);
|
||||
}
|
||||
|
||||
public CompletionReward(IntProperty completionCount, IntProperty quantity, string reward) : this(completionCount)
|
||||
{
|
||||
Reward = new Reward(quantity, reward);
|
||||
}
|
||||
|
||||
private string ReformatString(string s, string completionCount, bool isAll)
|
||||
{
|
||||
s = s.Replace("({0})", "{0}").Replace("{QuestNumber}", "<text color=\"FFF\" case=\"upper\" fontface=\"black\">{0}</>");
|
||||
|
||||
int index = s.IndexOf("|plural(", StringComparison.CurrentCultureIgnoreCase);
|
||||
if (index > -1)
|
||||
{
|
||||
int i = s.Substring(index).IndexOf(')', StringComparison.CurrentCultureIgnoreCase);
|
||||
s = s.Replace(s.Substring(index, i + 1), string.Empty).Replace("{0} {0}", "{0}");
|
||||
}
|
||||
|
||||
int index1 = s.IndexOf(_TRIGGER1, StringComparison.CurrentCultureIgnoreCase);
|
||||
if (index1 < 0) index1 = 0;
|
||||
string partOne = s.Substring(0, index1);
|
||||
|
||||
string partTemp = s.Substring(index1 + _TRIGGER1.Length);
|
||||
int index2 = partTemp.IndexOf(_TRIGGER2, StringComparison.CurrentCultureIgnoreCase);
|
||||
if (index2 < 0) index2 = 0;
|
||||
string partUpper = partTemp.Substring(0, index2).ToUpper().Replace("{0}", isAll ? string.Empty : completionCount);
|
||||
|
||||
string partTwo = partTemp.Substring(index2 + _TRIGGER2.Length);
|
||||
|
||||
return string.Format("{0}{1}{2}", partOne, partUpper, partTwo).Replace(" ", " ").Replace(" ,", ",");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
using SkiaSharp;
|
||||
using System;
|
||||
using System.IO;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.Objects;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
|
||||
namespace FModel.Creator.Bundles
|
||||
{
|
||||
public class Header
|
||||
{
|
||||
public SKColor PrimaryColor;
|
||||
public SKColor SecondaryColor;
|
||||
public SKColor AccentColor;
|
||||
public SKBitmap DisplayImage; // 256x256
|
||||
public SKBitmap CustomBackground; // 1024x256
|
||||
|
||||
private readonly Random _random = new Random(Environment.TickCount);
|
||||
private readonly string[] _randomColors = new string[255]
|
||||
{
|
||||
"F44336", "FFEBEE", "FFCDD2", "EF9A9A", "E57373", "EF5350", "E53935", "D32F2F", "C62828", "B71C1C",
|
||||
"FF8A80", "FF5252", "FF1744", "D50000", "FCE4EC", "F8BBD0", "F48FB1", "F06292", "EC407A", "E91E63",
|
||||
"D81B60", "C2185B", "AD1457", "880E4F", "FF80AB", "FF4081", "F50057", "C51162", "F3E5F5", "E1BEE7",
|
||||
"CE93D8", "BA68C8", "AB47BC", "9C27B0", "8E24AA", "7B1FA2", "6A1B9A", "4A148C", "EA80FC", "E040FB",
|
||||
"D500F9", "AA00FF", "EDE7F6", "D1C4E9", "B39DDB", "9575CD", "7E57C2", "673AB7", "5E35B1", "512DA8",
|
||||
"4527A0", "311B92", "B388FF", "7C4DFF", "651FFF", "6200EA", "E8EAF6", "C5CAE9", "9FA8DA", "7986CB",
|
||||
"5C6BC0", "3F51B5", "3949AB", "303F9F", "283593", "1A237E", "8C9EFF", "536DFE", "3D5AFE", "304FFE",
|
||||
"E3F2FD", "BBDEFB", "90CAF9", "64B5F6", "42A5F5", "2196F3", "1E88E5", "1976D2", "1565C0", "0D47A1",
|
||||
"82B1FF", "448AFF", "2979FF", "2962FF", "E1F5FE", "B3E5FC", "81D4FA", "4FC3F7", "29B6F6", "03A9F4",
|
||||
"039BE5", "0288D1", "0277BD", "01579B", "80D8FF", "40C4FF", "00B0FF", "0091EA", "E0F7FA", "B2EBF2",
|
||||
"80DEEA", "4DD0E1", "26C6DA", "00BCD4", "00ACC1", "0097A7", "00838F", "006064", "84FFFF", "18FFFF",
|
||||
"00E5FF", "00B8D4", "E0F2F1", "B2DFDB", "80CBC4", "4DB6AC", "26A69A", "009688", "00897B", "00796B",
|
||||
"00695C", "004D40", "A7FFEB", "64FFDA", "1DE9B6", "00BFA5", "E8F5E9", "C8E6C9", "A5D6A7", "81C784",
|
||||
"66BB6A", "4CAF50", "43A047", "388E3C", "2E7D32", "1B5E20", "B9F6CA", "69F0AE", "00E676", "00C853",
|
||||
"F1F8E9", "DCEDC8", "C5E1A5", "AED581", "9CCC65", "8BC34A", "7CB342", "689F38", "558B2F", "33691E",
|
||||
"CCFF90", "B2FF59", "76FF03", "64DD17", "F9FBE7", "F0F4C3", "E6EE9C", "DCE775", "D4E157", "CDDC39",
|
||||
"C0CA33", "AFB42B", "9E9D24", "827717", "F4FF81", "EEFF41", "C6FF00", "AEEA00", "FFFDE7", "FFF9C4",
|
||||
"FFF59D", "FFF176", "FFEE58", "FFEB3B", "FDD835", "FBC02D", "F9A825", "F57F17", "FFFF8D", "FFFF00",
|
||||
"FFEA00", "FFD600", "FFF8E1", "FFECB3", "FFE082", "FFD54F", "FFCA28", "FFC107", "FFB300", "FFA000",
|
||||
"FF8F00", "FF6F00", "FFE57F", "FFD740", "FFC400", "FFAB00", "FFF3E0", "FFE0B2", "FFCC80", "FFB74D",
|
||||
"FFA726", "FF9800", "FB8C00", "F57C00", "EF6C00", "E65100", "FFD180", "FFAB40", "FF9100", "FF6D00",
|
||||
"FBE9E7", "FFCCBC", "FFAB91", "FF8A65", "FF7043", "FF5722", "F4511E", "E64A19", "D84315", "BF360C",
|
||||
"FF9E80", "FF6E40", "FF3D00", "DD2C00", "EFEBE9", "D7CCC8", "BCAAA4", "A1887F", "8D6E63", "795548",
|
||||
"6D4C41", "5D4037", "4E342E", "3E2723", "FAFAFA", "F5F5F5", "EEEEEE", "E0E0E0", "BDBDBD", "9E9E9E",
|
||||
"757575", "616161", "424242", "212121", "ECEFF1", "CFD8DC", "B0BEC5", "90A4AE", "78909C", "607D8B",
|
||||
"546E7A", "455A64", "37474F", "263238", "000000",
|
||||
};
|
||||
|
||||
public Header()
|
||||
{
|
||||
if (Properties.Settings.Default.UseChallengeBanner)
|
||||
{
|
||||
SKColor mainColor = SKColor.Parse(Properties.Settings.Default.ChallengeBannerPrimaryColor);
|
||||
mainColor.ToHsl(out float h, out float s, out float l);
|
||||
float i = l + 20.0F > 100.0F ? 100.0F - l : 20.0F;
|
||||
|
||||
PrimaryColor = mainColor;
|
||||
SecondaryColor = SKColor.Parse(Properties.Settings.Default.ChallengeBannerSecondaryColor);
|
||||
AccentColor = SKColor.FromHsl(h += i, s, l);
|
||||
DisplayImage = null;
|
||||
if (!string.IsNullOrEmpty(Properties.Settings.Default.ChallengeBannerPath))
|
||||
CustomBackground = SKBitmap.Decode(new FileInfo(Properties.Settings.Default.ChallengeBannerPath).Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
|
||||
else CustomBackground = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
SKColor mainColor = SKColor.Parse(_randomColors[_random.Next(0, 255)]);
|
||||
mainColor.ToHsl(out float h, out float s, out float l);
|
||||
while (l > 75 || l < 10)
|
||||
{
|
||||
mainColor = SKColor.Parse(_randomColors[_random.Next(0, 255)]);
|
||||
mainColor.ToHsl(out float _, out float _, out l);
|
||||
}
|
||||
float i = l + 20.0F > 100.0F ? 100.0F - l : 20.0F;
|
||||
|
||||
PrimaryColor = mainColor;
|
||||
SecondaryColor = SKColor.FromHsl(h, s, l += i);
|
||||
AccentColor = SKColor.FromHsl(h += i, s, l);
|
||||
DisplayImage = null;
|
||||
CustomBackground = null;
|
||||
}
|
||||
}
|
||||
|
||||
public Header(StructProperty displayStyle, string assetFolder) : this()
|
||||
{
|
||||
if (displayStyle.Value is UObject o)
|
||||
{
|
||||
if (!Properties.Settings.Default.UseChallengeBanner && o.TryGetValue(out var c1, "PrimaryColor") && c1 is StructProperty s1 && s1.Value is FLinearColor primaryColor)
|
||||
PrimaryColor = SKColor.Parse(primaryColor.Hex);
|
||||
if (!Properties.Settings.Default.UseChallengeBanner && o.TryGetValue(out var c2, "SecondaryColor") && c2 is StructProperty s2 && s2.Value is FLinearColor secondaryColor)
|
||||
SecondaryColor = SKColor.Parse(secondaryColor.Hex);
|
||||
if (!Properties.Settings.Default.UseChallengeBanner && o.TryGetValue("AccentColor", out var c3) && c3 is StructProperty s3 && s3.Value is FLinearColor accentColor)
|
||||
{
|
||||
AccentColor = SKColor.Parse(accentColor.Hex);
|
||||
if (SecondaryColor.Red + SecondaryColor.Green + SecondaryColor.Blue <= 75 || assetFolder.Equals("LTM", StringComparison.CurrentCultureIgnoreCase)) // if secondary is too dark
|
||||
SecondaryColor = AccentColor; // use accent and pray for accent to be ligher
|
||||
}
|
||||
|
||||
if (o.TryGetValue("DisplayImage", out var i) && i is SoftObjectProperty displayImage)
|
||||
DisplayImage = Utils.GetSoftObjectTexture(displayImage);
|
||||
if (CustomBackground == null && o.TryGetValue("CustomBackground", out var b) && b is SoftObjectProperty customBackground)
|
||||
CustomBackground = Utils.GetSoftObjectTexture(customBackground);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
using FModel.Creator.Bases;
|
||||
using FModel.Creator.Texts;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace FModel.Creator.Bundles
|
||||
{
|
||||
static class HeaderStyle
|
||||
{
|
||||
public static void DrawHeaderPaint(SKCanvas c, BaseBundle icon)
|
||||
{
|
||||
c.DrawRect(new SKRect(0, 0, icon.Width, icon.HeaderHeight), new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Color = icon.DisplayStyle.PrimaryColor
|
||||
});
|
||||
|
||||
if (icon.DisplayStyle.CustomBackground != null && icon.DisplayStyle.CustomBackground.Height != icon.DisplayStyle.CustomBackground.Width)
|
||||
{
|
||||
icon.IsDisplayNameShifted = false;
|
||||
var bgPaint = new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High, BlendMode = SKBlendMode.Screen };
|
||||
if (Properties.Settings.Default.UseChallengeBanner) bgPaint.Color = SKColors.Transparent.WithAlpha((byte)Properties.Settings.Default.ChallengeBannerOpacity);
|
||||
c.DrawBitmap(icon.DisplayStyle.CustomBackground, new SKRect(0, 0, 1024, 256), bgPaint);
|
||||
}
|
||||
else if (icon.DisplayStyle.DisplayImage != null)
|
||||
{
|
||||
icon.IsDisplayNameShifted = true;
|
||||
if (icon.DisplayStyle.CustomBackground != null && icon.DisplayStyle.CustomBackground.Height == icon.DisplayStyle.CustomBackground.Width)
|
||||
c.DrawBitmap(icon.DisplayStyle.CustomBackground, new SKRect(0, 0, icon.HeaderHeight, icon.HeaderHeight),
|
||||
new SKPaint {
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High, BlendMode = SKBlendMode.Screen,
|
||||
ImageFilter = SKImageFilter.CreateDropShadow(2.5F, 0, 20, 0, icon.DisplayStyle.SecondaryColor.WithAlpha(25))
|
||||
});
|
||||
|
||||
c.DrawBitmap(icon.DisplayStyle.DisplayImage, new SKRect(0, 0, icon.HeaderHeight, icon.HeaderHeight),
|
||||
new SKPaint {
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High,
|
||||
ImageFilter = SKImageFilter.CreateDropShadow(-2.5F, 0, 20, 0, icon.DisplayStyle.SecondaryColor.WithAlpha(50))
|
||||
});
|
||||
}
|
||||
|
||||
SKPath pathTop = new SKPath { FillType = SKPathFillType.EvenOdd };
|
||||
pathTop.MoveTo(0, icon.HeaderHeight);
|
||||
pathTop.LineTo(icon.Width, icon.HeaderHeight);
|
||||
pathTop.LineTo(icon.Width, icon.HeaderHeight - 19);
|
||||
pathTop.LineTo(icon.Width / 2 + 7, icon.HeaderHeight - 23);
|
||||
pathTop.LineTo(icon.Width / 2 + 13, icon.HeaderHeight - 7);
|
||||
pathTop.LineTo(0, icon.HeaderHeight - 19);
|
||||
pathTop.Close();
|
||||
c.DrawPath(pathTop, new SKPaint {
|
||||
IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = icon.DisplayStyle.SecondaryColor,
|
||||
ImageFilter = SKImageFilter.CreateDropShadow(-5, -5, 0, 0, icon.DisplayStyle.AccentColor.WithAlpha(75))
|
||||
});
|
||||
|
||||
c.DrawRect(new SKRect(0, icon.HeaderHeight, icon.Width, icon.HeaderHeight + icon.AdditionalSize), new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Color = icon.DisplayStyle.PrimaryColor.WithAlpha(200) // default background is black, so i'm kinda lowering the brightness here and that's what i want
|
||||
});
|
||||
}
|
||||
|
||||
public static void DrawHeaderText(SKCanvas c, BaseBundle icon)
|
||||
{
|
||||
using SKPaint paint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Text.TypeFaces.BundleDisplayNameTypeface,
|
||||
TextSize = 50,
|
||||
Color = SKColors.White,
|
||||
TextAlign = SKTextAlign.Left,
|
||||
};
|
||||
|
||||
string text = icon.DisplayName.ToUpper();
|
||||
int x = icon.IsDisplayNameShifted ? 300 : 50;
|
||||
if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic)
|
||||
{
|
||||
SKShaper shaper = new SKShaper(paint.Typeface);
|
||||
float shapedTextWidth;
|
||||
|
||||
while (true)
|
||||
{
|
||||
SKShaper.Result shapedText = shaper.Shape(text, paint);
|
||||
shapedTextWidth = shapedText.Points[^1].X + paint.TextSize / 2f;
|
||||
|
||||
if (shapedTextWidth > (icon.Width - x))
|
||||
{
|
||||
paint.TextSize -= 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (char.IsDigit(text[text.Length - 1]))
|
||||
{
|
||||
int s = text.Count(k => Char.IsDigit(k));
|
||||
float numberwidth = paint.MeasureText(text.Substring(text.Length - s));
|
||||
c.DrawShapedText(shaper, text.Substring(text.Length - s), x, 155, paint);
|
||||
|
||||
c.DrawShapedText(shaper, text.Substring(0, text.Length - s), x + numberwidth, 155, paint);
|
||||
}
|
||||
else
|
||||
{
|
||||
//feels bad man
|
||||
c.DrawShapedText(shaper, text, x, 155, paint);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (paint.MeasureText(text) > (icon.Width - x))
|
||||
{
|
||||
paint.TextSize -= 2;
|
||||
}
|
||||
c.DrawText(text, x, 155, paint);
|
||||
}
|
||||
|
||||
paint.Color = SKColors.White.WithAlpha(150);
|
||||
paint.TextAlign = SKTextAlign.Right;
|
||||
paint.TextSize = 23;
|
||||
paint.Typeface = Text.TypeFaces.DefaultTypeface;
|
||||
c.DrawText(icon.Watermark
|
||||
.Replace("{BundleName}", text)
|
||||
.Replace("{Date}", DateTime.Now.ToString("dd/MM/yyyy")),
|
||||
icon.Width - 25, icon.HeaderHeight - 40, paint);
|
||||
|
||||
paint.Typeface = Text.TypeFaces.BundleDefaultTypeface;
|
||||
paint.Color = icon.DisplayStyle.SecondaryColor;
|
||||
paint.TextAlign = SKTextAlign.Left;
|
||||
paint.TextSize = 30;
|
||||
c.DrawText(icon.FolderName.ToUpper(), x, 95, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
using FModel.Creator.Texts;
|
||||
using FModel.PakReader;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
|
||||
namespace FModel.Creator.Bundles
|
||||
{
|
||||
public class Quest
|
||||
{
|
||||
public string Description;
|
||||
public int Count;
|
||||
public Reward Reward;
|
||||
|
||||
public Quest()
|
||||
{
|
||||
Description = "";
|
||||
Count = 0;
|
||||
Reward = null;
|
||||
}
|
||||
|
||||
public Quest(UObject obj) : this()
|
||||
{
|
||||
if (obj.TryGetValue("Description", out var d) && d is TextProperty description)
|
||||
Description = Text.GetTextPropertyBase(description);
|
||||
if (obj.TryGetValue("ObjectiveCompletionCount", out var o) && o is IntProperty objectiveCompletionCount)
|
||||
Count = objectiveCompletionCount.Value;
|
||||
|
||||
if (obj.TryGetValue("Objectives", out var v1) && v1 is ArrayProperty a1 &&
|
||||
a1.Value.Length > 0 && a1.Value[0] is StructProperty s && s.Value is UObject objectives)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Description) && objectives.TryGetValue("Description", out var od) && od is TextProperty objectivesDescription)
|
||||
Description = Text.GetTextPropertyBase(objectivesDescription);
|
||||
|
||||
if (Count == 0 && objectives.TryGetValue("Count", out var c) && c is IntProperty count)
|
||||
Count = count.Value;
|
||||
}
|
||||
|
||||
if (obj.TryGetValue("Rewards", out var v2) && v2 is ArrayProperty rewards)
|
||||
{
|
||||
foreach (StructProperty reward in rewards.Value)
|
||||
{
|
||||
if (reward.Value is UObject r1 &&
|
||||
r1.TryGetValue("ItemPrimaryAssetId", out var i1) && i1 is StructProperty itemPrimaryAssetId &&
|
||||
r1.TryGetValue("Quantity", out var i2) && i2 is IntProperty quantity)
|
||||
{
|
||||
if (itemPrimaryAssetId.Value is UObject r2 &&
|
||||
r2.TryGetValue("PrimaryAssetType", out var t1) && t1 is StructProperty primaryAssetType &&
|
||||
r2.TryGetValue("PrimaryAssetName", out var t2) && t2 is NameProperty primaryAssetName)
|
||||
{
|
||||
if (primaryAssetType.Value is UObject r3 && r3.TryGetValue("Name", out var k) && k is NameProperty name)
|
||||
{
|
||||
if (!name.Value.String.Equals("Quest") && !name.Value.String.Equals("Token") &&
|
||||
!name.Value.String.Equals("ChallengeBundle") && !name.Value.String.Equals("GiftBox"))
|
||||
{
|
||||
Reward = new Reward(quantity, primaryAssetName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Reward == null && obj.TryGetValue("RewardsTable", out var v4) && v4 is ObjectProperty rewardsTable && rewardsTable.Value.Resource.OuterIndex.Resource != null)
|
||||
{
|
||||
Package p = Utils.GetPropertyPakPackage(rewardsTable.Value.Resource.OuterIndex.Resource?.ObjectName.String);
|
||||
if (p != null && p.HasExport() && !p.Equals(default))
|
||||
{
|
||||
var u = p.GetExport<UDataTable>();
|
||||
if (u != null && u.TryGetValue("Default", out var i) && i is UObject r &&
|
||||
r.TryGetValue("TemplateId", out var i1) && i1 is NameProperty templateId &&
|
||||
r.TryGetValue("Quantity", out var i2) && i2 is IntProperty quantity)
|
||||
{
|
||||
Reward = new Reward(quantity, templateId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Reward == null && obj.TryGetValue("HiddenRewards", out var v3) && v3 is ArrayProperty hiddenRewards)
|
||||
{
|
||||
foreach (StructProperty reward in hiddenRewards.Value)
|
||||
{
|
||||
if (reward.Value is UObject r1 &&
|
||||
r1.TryGetValue("TemplateId", out var i1) && i1 is NameProperty templateId &&
|
||||
r1.TryGetValue("Quantity", out var i2) && i2 is IntProperty quantity)
|
||||
{
|
||||
Reward = new Reward(quantity, templateId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,241 +0,0 @@
|
|||
using FModel.Creator.Bases;
|
||||
using FModel.Creator.Texts;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace FModel.Creator.Bundles
|
||||
{
|
||||
static class QuestStyle
|
||||
{
|
||||
public static void DrawQuests(SKCanvas c, BaseBundle icon)
|
||||
{
|
||||
using SKPaint paint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
TextSize = 27,
|
||||
Color = SKColors.White,
|
||||
TextAlign = SKTextAlign.Left,
|
||||
Typeface = Text.TypeFaces.BundleDisplayNameTypeface
|
||||
};
|
||||
|
||||
int y = icon.HeaderHeight + 50;
|
||||
foreach (Quest q in icon.Quests)
|
||||
{
|
||||
DrawQuestBackground(c, icon, y, true);
|
||||
|
||||
paint.TextSize = 27;
|
||||
paint.ImageFilter = null;
|
||||
paint.Color = SKColors.White;
|
||||
paint.TextAlign = SKTextAlign.Left;
|
||||
paint.Typeface = Text.TypeFaces.BundleDisplayNameTypeface;
|
||||
if (!string.IsNullOrEmpty(q.Description))
|
||||
{
|
||||
if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic)
|
||||
{
|
||||
SKShaper shaper = new SKShaper(paint.Typeface);
|
||||
float shapedTextWidth;
|
||||
|
||||
while (true)
|
||||
{
|
||||
SKShaper.Result shapedText = shaper.Shape(q.Description, paint);
|
||||
shapedTextWidth = shapedText.Points[^1].X + paint.TextSize / 2f;
|
||||
|
||||
if (shapedTextWidth > icon.Width - 65 - 165)
|
||||
{
|
||||
paint.TextSize -= 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (char.IsDigit(q.Description[q.Description.Length - 1]))
|
||||
{
|
||||
int s = q.Description.Count(k => Char.IsDigit(k));
|
||||
c.DrawShapedText(shaper, q.Description.Substring(q.Description.Length - s), 65, y + paint.TextSize + 11, paint);
|
||||
|
||||
c.DrawShapedText(shaper, q.Description.Substring(0, q.Description.Length - s), 115, y + paint.TextSize + 11, paint);
|
||||
}
|
||||
else
|
||||
{
|
||||
c.DrawShapedText(shaper, q.Description, 65, y + paint.TextSize + 11, paint);
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (paint.MeasureText(q.Description) > icon.Width - 65 - 165)
|
||||
{
|
||||
paint.TextSize -= 1;
|
||||
}
|
||||
c.DrawText(q.Description, new SKPoint(65, y + paint.TextSize + 11), paint);
|
||||
}
|
||||
}
|
||||
|
||||
paint.TextSize = 16;
|
||||
paint.Color = SKColors.White.WithAlpha(200);
|
||||
paint.Typeface = Text.TypeFaces.BundleDefaultTypeface;
|
||||
c.DrawText(q.Count.ToString(), new SKPoint(93 + icon.Width - (175 * 3), y + 60), paint);
|
||||
|
||||
if (q.Reward?.RewardIcon != null)
|
||||
{
|
||||
if (q.Reward.IsCountShifted)
|
||||
{
|
||||
int l = q.Reward.RewardQuantity.ToString().Length;
|
||||
paint.TextSize = l >= 5 ? 30 : 35;
|
||||
paint.TextAlign = SKTextAlign.Right;
|
||||
paint.Color = SKColor.Parse(q.Reward.RewardFillColor);
|
||||
paint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 5, 5, SKColor.Parse(q.Reward.RewardBorderColor).WithAlpha(200));
|
||||
c.DrawText(q.Reward.RewardQuantity.ToString(), new SKPoint(icon.Width - 85, y + 47.5F), paint);
|
||||
c.DrawBitmap(q.Reward.RewardIcon, new SKPoint(icon.Width - 30 - q.Reward.RewardIcon.Width, y + 12.5F),
|
||||
new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High });
|
||||
}
|
||||
else
|
||||
c.DrawBitmap(q.Reward.RewardIcon, new SKPoint(icon.Width - 125, y + 5),
|
||||
new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High });
|
||||
}
|
||||
|
||||
y += 95;
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawCompletionRewards(SKCanvas c, BaseBundle icon)
|
||||
{
|
||||
using SKPaint paint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
TextSize = 35,
|
||||
Color = SKColors.White,
|
||||
TextAlign = SKTextAlign.Left,
|
||||
Typeface = Text.TypeFaces.BundleDisplayNameTypeface
|
||||
};
|
||||
|
||||
int y = icon.HeaderHeight + (50 * 2) + (95 * icon.Quests.Count);
|
||||
foreach (CompletionReward r in icon.CompletionRewards)
|
||||
{
|
||||
DrawQuestBackground(c, icon, y, false);
|
||||
|
||||
paint.TextSize = 35;
|
||||
paint.ImageFilter = null;
|
||||
paint.Color = SKColors.White;
|
||||
paint.TextAlign = SKTextAlign.Left;
|
||||
paint.Typeface = Text.TypeFaces.BundleDisplayNameTypeface;
|
||||
if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic)
|
||||
{
|
||||
SKShaper shaper = new SKShaper(paint.Typeface);
|
||||
float shapedTextWidth;
|
||||
|
||||
while (true)
|
||||
{
|
||||
SKShaper.Result shapedText = shaper.Shape(r.CompletionText, paint);
|
||||
shapedTextWidth = shapedText.Points[^1].X + paint.TextSize / 2f;
|
||||
|
||||
if (shapedTextWidth > icon.Width - 65 - 165)
|
||||
{
|
||||
paint.TextSize -= 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
c.DrawShapedText(shaper, r.CompletionText, 65, y + paint.TextSize + 15, paint);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (paint.MeasureText(r.CompletionText) > icon.Width - 65 - 165)
|
||||
{
|
||||
paint.TextSize -= 1;
|
||||
}
|
||||
c.DrawText(r.CompletionText, new SKPoint(65, y + paint.TextSize + 15), paint);
|
||||
}
|
||||
|
||||
if (r.Reward?.RewardIcon != null)
|
||||
{
|
||||
if (r.Reward.IsCountShifted)
|
||||
{
|
||||
int l = r.Reward.RewardQuantity.ToString().Length;
|
||||
paint.TextSize = l >= 5 ? 30 : 35;
|
||||
paint.TextAlign = SKTextAlign.Right;
|
||||
paint.Color = SKColor.Parse(r.Reward.RewardFillColor);
|
||||
paint.Typeface = Text.TypeFaces.BundleDefaultTypeface;
|
||||
paint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 5, 5, SKColor.Parse(r.Reward.RewardBorderColor).WithAlpha(200));
|
||||
c.DrawText(r.Reward.RewardQuantity.ToString(), new SKPoint(icon.Width - 85, y + 47.5F), paint);
|
||||
c.DrawBitmap(r.Reward.RewardIcon, new SKPoint(icon.Width - 30 - r.Reward.RewardIcon.Width, y + 12.5F),
|
||||
new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High });
|
||||
}
|
||||
else
|
||||
c.DrawBitmap(r.Reward.RewardIcon, new SKPoint(icon.Width - 125, y + 5),
|
||||
new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High });
|
||||
}
|
||||
|
||||
y += 95;
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawQuestBackground(SKCanvas c, BaseBundle icon, int y, bool hasSlider)
|
||||
{
|
||||
SKColor baseColor = icon.DisplayStyle.PrimaryColor;
|
||||
using SKPaint paint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Color = baseColor
|
||||
};
|
||||
using SKPath secondaryRect = new SKPath
|
||||
{
|
||||
FillType = SKPathFillType.EvenOdd
|
||||
};
|
||||
using SKPath selector = new SKPath
|
||||
{
|
||||
FillType = SKPathFillType.EvenOdd
|
||||
};
|
||||
using SKPath slider = new SKPath
|
||||
{
|
||||
FillType = SKPathFillType.EvenOdd
|
||||
};
|
||||
|
||||
c.DrawRect(new SKRect(25, y, icon.Width - 25, y + 75), paint);
|
||||
|
||||
baseColor.ToHsl(out float h, out float s, out float l);
|
||||
baseColor = SKColor.FromHsl(h, s, l + 5);
|
||||
paint.Color = baseColor;
|
||||
|
||||
secondaryRect.MoveTo(32, y + 5);
|
||||
secondaryRect.LineTo(icon.Width - 155, y + 4);
|
||||
secondaryRect.LineTo(icon.Width - 175, y + 68);
|
||||
secondaryRect.LineTo(29, y + 71);
|
||||
secondaryRect.Close();
|
||||
c.DrawPath(secondaryRect, paint);
|
||||
|
||||
paint.Color = icon.DisplayStyle.SecondaryColor;
|
||||
selector.MoveTo(41, y + 38);
|
||||
selector.LineTo(48, y + 34);
|
||||
selector.LineTo(52, y + 39);
|
||||
selector.LineTo(46, y + 44);
|
||||
selector.Close();
|
||||
c.DrawPath(selector, paint);
|
||||
|
||||
if (hasSlider)
|
||||
{
|
||||
slider.MoveTo(65, y + 53);
|
||||
slider.LineTo(65 + icon.Width - (175 * 3), y + 53);
|
||||
slider.LineTo(65 + icon.Width - (175 * 3), y + 58);
|
||||
slider.LineTo(65, y + 58);
|
||||
slider.Close();
|
||||
c.DrawPath(slider, paint);
|
||||
|
||||
paint.TextSize = 14;
|
||||
paint.Color = SKColors.White;
|
||||
paint.TextAlign = SKTextAlign.Left;
|
||||
paint.Typeface = Text.TypeFaces.BundleDefaultTypeface;
|
||||
c.DrawText("0 / ", new SKPoint(75 + icon.Width - (175 * 3), y + 59), paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
using FModel.Creator.Bases;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using FModel.PakReader;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.Objects;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
|
||||
namespace FModel.Creator.Bundles
|
||||
{
|
||||
public class Reward
|
||||
{
|
||||
public int RewardQuantity;
|
||||
public SKBitmap RewardIcon;
|
||||
public BaseIcon TheReward;
|
||||
public string RewardFillColor;
|
||||
public string RewardBorderColor;
|
||||
public bool IsCountShifted;
|
||||
|
||||
public Reward()
|
||||
{
|
||||
RewardQuantity = 0;
|
||||
RewardIcon = null;
|
||||
TheReward = null;
|
||||
RewardFillColor = "";
|
||||
RewardBorderColor = "";
|
||||
IsCountShifted = false;
|
||||
}
|
||||
|
||||
public Reward(IntProperty quantity, NameProperty primaryAssetName) : this(quantity, primaryAssetName.Value.String) { }
|
||||
public Reward(IntProperty quantity, string assetName) : this()
|
||||
{
|
||||
RewardQuantity = quantity.Value;
|
||||
|
||||
if (assetName.Contains(':'))
|
||||
{
|
||||
string[] parts = assetName.Split(':');
|
||||
if (parts[0].Equals("HomebaseBannerIcon", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
Package p = Utils.GetPropertyPakPackage($"/Game/Items/BannerIcons/{parts[1]}.{parts[1]}");
|
||||
if (p.HasExport() && !p.Equals(default))
|
||||
{
|
||||
if (p.GetExport<UObject>() is UObject banner)
|
||||
{
|
||||
TheReward = new BaseIcon(banner, $"{parts[1]}.uasset", false);
|
||||
RewardIcon = TheReward.IconImage.Resize(64, 64);
|
||||
}
|
||||
}
|
||||
}
|
||||
else GetReward(parts[1]);
|
||||
}
|
||||
else GetReward(assetName);
|
||||
}
|
||||
public Reward(IntProperty quantity, FSoftObjectPath itemFullPath)
|
||||
{
|
||||
RewardQuantity = quantity.Value;
|
||||
Package p = Utils.GetPropertyPakPackage(itemFullPath.AssetPathName.String);
|
||||
if (p.HasExport() && !p.Equals(default))
|
||||
{
|
||||
var d = p.GetExport<UObject>();
|
||||
if (d != null)
|
||||
{
|
||||
int i = itemFullPath.AssetPathName.String.LastIndexOf('/');
|
||||
TheReward = new BaseIcon(d, itemFullPath.AssetPathName.String[(i > 0 ? i : 0)..] + ".uasset", false);
|
||||
RewardIcon = TheReward.IconImage.Resize(80, 80);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Reward(IntProperty quantity, SoftObjectProperty itemDefinition) : this()
|
||||
{
|
||||
RewardQuantity = quantity.Value;
|
||||
|
||||
Package p = Utils.GetPropertyPakPackage(itemDefinition.Value.AssetPathName.String);
|
||||
if (p != null && p.HasExport() && !p.Equals(default))
|
||||
{
|
||||
var d = p.GetExport<UObject>();
|
||||
if (d != null)
|
||||
{
|
||||
int s1 = itemDefinition.Value.AssetPathName.String.LastIndexOf('/');
|
||||
if (s1 < 0) s1 = 0;
|
||||
int s2 = itemDefinition.Value.AssetPathName.String.LastIndexOf('.') - s1;
|
||||
switch (itemDefinition.Value.AssetPathName.String)
|
||||
{
|
||||
case "/Game/Items/PersistentResources/AthenaBattleStar.AthenaBattleStar":
|
||||
IsCountShifted = true;
|
||||
RewardFillColor = "FFDB67";
|
||||
RewardBorderColor = "8F4A20";
|
||||
RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-FNBR-BattlePoints").Resize(48, 48);
|
||||
break;
|
||||
case "/Game/Items/PersistentResources/AthenaSeasonalXP.AthenaSeasonalXP":
|
||||
IsCountShifted = true;
|
||||
RewardFillColor = "E6FDB1";
|
||||
RewardBorderColor = "51830F";
|
||||
RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-FNBR-XPMedium").Resize(48, 48);
|
||||
break;
|
||||
case "/Game/Items/Currency/MtxGiveaway.MtxGiveaway":
|
||||
IsCountShifted = true;
|
||||
RewardFillColor = "DCE6FF";
|
||||
RewardBorderColor = "64A0AF";
|
||||
RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-Items-MTX").Resize(48, 48);
|
||||
break;
|
||||
default:
|
||||
IsCountShifted = false;
|
||||
TheReward = new BaseIcon(d, itemDefinition.Value.AssetPathName.String.Substring(s1, s2) + ".uasset", false);
|
||||
RewardIcon = TheReward.IconImage.Resize(64, 64);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GetReward(string trigger)
|
||||
{
|
||||
switch (trigger.ToLower())
|
||||
{
|
||||
case "athenabattlestar":
|
||||
IsCountShifted = true;
|
||||
RewardFillColor = "FFDB67";
|
||||
RewardBorderColor = "8F4A20";
|
||||
RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-FNBR-BattlePoints").Resize(48, 48);
|
||||
break;
|
||||
case "athenaseasonalxp":
|
||||
IsCountShifted = true;
|
||||
RewardFillColor = "E6FDB1";
|
||||
RewardBorderColor = "51830F";
|
||||
RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-FNBR-XPMedium").Resize(48, 48);
|
||||
break;
|
||||
case "mtxgiveaway":
|
||||
IsCountShifted = true;
|
||||
RewardFillColor = "DCE6FF";
|
||||
RewardBorderColor = "64A0AF";
|
||||
RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-Items-MTX").Resize(48, 48);
|
||||
break;
|
||||
default:
|
||||
{
|
||||
string path = Utils.GetFullPath($"/FortniteGame/Content/Athena/.*?/{trigger}.*").Replace("FortniteGame/Content", "Game");
|
||||
Package p = Utils.GetPropertyPakPackage(path);
|
||||
if (p!= null && p.HasExport() && !p.Equals(default))
|
||||
{
|
||||
var d = p.GetExport<UObject>();
|
||||
if (d != null)
|
||||
{
|
||||
int i = path.LastIndexOf('/');
|
||||
IsCountShifted = false;
|
||||
TheReward = new BaseIcon(d, path[(i > 0 ? i : 0)..] + ".uasset", false);
|
||||
RewardIcon = TheReward.IconImage.Resize(64, 64);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,496 +0,0 @@
|
|||
using FModel.Creator.Bases;
|
||||
using FModel.Creator.Bundles;
|
||||
using FModel.Creator.Icons;
|
||||
using FModel.Creator.Rarities;
|
||||
using FModel.Creator.Stats;
|
||||
using FModel.Creator.Texts;
|
||||
using FModel.ViewModels.ImageBox;
|
||||
using SkiaSharp;
|
||||
using System.IO;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.Objects;
|
||||
|
||||
namespace FModel.Creator
|
||||
{
|
||||
static class Creator
|
||||
{
|
||||
/// <summary>
|
||||
/// i don't cache images because i don't wanna store a lot of SKCanvas in the memory
|
||||
/// </summary>
|
||||
/// <returns>true if an icon has been drawn</returns>
|
||||
public static bool TryDrawIcon(string assetPath, FName[] exportTypes, IUExport[] exports)
|
||||
{
|
||||
var d = new DirectoryInfo(assetPath);
|
||||
string assetName = d.Name;
|
||||
string assetFolder = d.Parent.Name;
|
||||
if (Text.TypeFaces.NeedReload(false))
|
||||
Text.TypeFaces = new Typefaces(); // when opening bundle creator settings without loading paks first
|
||||
|
||||
int index;
|
||||
{
|
||||
if (Globals.Game.ActualGame == EGame.Valorant || Globals.Game.ActualGame == EGame.Spellbreak)
|
||||
index = 1;
|
||||
else
|
||||
index = 0;
|
||||
}
|
||||
string exportType;
|
||||
{
|
||||
if (exportTypes.Length > index && (exportTypes[index].String == "BlueprintGeneratedClass" || exportTypes[index].String == "FortWeaponAdditionalData_AudioVisualizerData" || exportTypes[index].String == "FortWeaponAdditionalData_SingleWieldState"))
|
||||
index++;
|
||||
|
||||
exportType = exportTypes.Length > index ? exportTypes[index].String : string.Empty;
|
||||
}
|
||||
|
||||
switch (exportType)
|
||||
{
|
||||
case "AthenaConsumableEmoteItemDefinition":
|
||||
case "AthenaSkyDiveContrailItemDefinition":
|
||||
case "AthenaLoadingScreenItemDefinition":
|
||||
case "AthenaVictoryPoseItemDefinition":
|
||||
case "AthenaPetCarrierItemDefinition":
|
||||
case "AthenaMusicPackItemDefinition":
|
||||
case "AthenaBattleBusItemDefinition":
|
||||
case "AthenaCharacterItemDefinition":
|
||||
case "FortAlterationItemDefinition":
|
||||
case "AthenaBackpackItemDefinition":
|
||||
case "AthenaPickaxeItemDefinition":
|
||||
case "AthenaGadgetItemDefinition":
|
||||
case "AthenaGliderItemDefinition":
|
||||
case "AthenaDailyQuestDefinition":
|
||||
case "FortBackpackItemDefinition":
|
||||
case "AthenaSprayItemDefinition":
|
||||
case "AthenaDanceItemDefinition":
|
||||
case "AthenaEmojiItemDefinition":
|
||||
case "AthenaItemWrapDefinition":
|
||||
case "AthenaToyItemDefinition":
|
||||
case "FortHeroType":
|
||||
case "FortTokenType":
|
||||
case "FortAbilityKit":
|
||||
case "FortWorkerType":
|
||||
case "RewardGraphToken":
|
||||
case "FortBannerTokenType":
|
||||
case "FortVariantTokenType":
|
||||
case "FortDecoItemDefinition":
|
||||
case "FortFeatItemDefinition":
|
||||
case "FortStatItemDefinition":
|
||||
case "FortTrapItemDefinition":
|
||||
case "FortAmmoItemDefinition":
|
||||
case "FortTandemCharacterData":
|
||||
case "FortQuestItemDefinition":
|
||||
case "FortBadgeItemDefinition":
|
||||
case "FortAwardItemDefinition":
|
||||
case "FortGadgetItemDefinition":
|
||||
case "FortPlaysetItemDefinition":
|
||||
case "FortGiftBoxItemDefinition":
|
||||
case "FortSpyTechItemDefinition":
|
||||
case "FortOutpostItemDefinition":
|
||||
case "FortAccoladeItemDefinition":
|
||||
case "FortCardPackItemDefinition":
|
||||
case "FortDefenderItemDefinition":
|
||||
case "FortCurrencyItemDefinition":
|
||||
case "FortResourceItemDefinition":
|
||||
case "FortCodeTokenItemDefinition":
|
||||
case "FortSchematicItemDefinition":
|
||||
case "FortExpeditionItemDefinition":
|
||||
case "FortIngredientItemDefinition":
|
||||
case "FortAccountBuffItemDefinition":
|
||||
case "FortWeaponMeleeItemDefinition":
|
||||
case "FortContextTrapItemDefinition":
|
||||
case "FortPlayerPerksItemDefinition":
|
||||
case "FortPlaysetPropItemDefinition":
|
||||
case "FortHomebaseNodeItemDefinition":
|
||||
case "FortWeaponRangedItemDefinition":
|
||||
case "FortNeverPersistItemDefinition":
|
||||
case "RadioContentSourceItemDefinition":
|
||||
case "FortPlaysetGrenadeItemDefinition":
|
||||
case "FortPersonalVehicleItemDefinition":
|
||||
case "FortGameplayModifierItemDefinition":
|
||||
case "FortHardcoreModifierItemDefinition":
|
||||
case "FortConsumableAccountItemDefinition":
|
||||
case "FortConversionControlItemDefinition":
|
||||
case "FortAccountBuffCreditItemDefinition":
|
||||
case "FortEventCurrencyItemDefinitionRedir":
|
||||
case "FortPersistentResourceItemDefinition":
|
||||
case "FortHomebaseBannerIconItemDefinition":
|
||||
case "FortCampaignHeroLoadoutItemDefinition":
|
||||
case "FortConditionalResourceItemDefinition":
|
||||
case "FortChallengeBundleScheduleDefinition":
|
||||
case "FortWeaponMeleeDualWieldItemDefinition":
|
||||
case "FortDailyRewardScheduleTokenDefinition":
|
||||
case "FortCreativeRealEstatePlotItemDefinition":
|
||||
{
|
||||
BaseIcon icon = new BaseIcon(exports[index], exportType, ref assetName);
|
||||
int height = icon.Size + icon.AdditionalSize;
|
||||
using (var ret = new SKBitmap(icon.Size, height, SKColorType.Rgba8888, SKAlphaType.Premul))
|
||||
using (var c = new SKCanvas(ret))
|
||||
{
|
||||
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
|
||||
{
|
||||
Rarity.DrawRarity(c, icon);
|
||||
}
|
||||
|
||||
LargeSmallImage.DrawPreviewImage(c, icon);
|
||||
|
||||
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
|
||||
{
|
||||
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoText)
|
||||
{
|
||||
Text.DrawBackground(c, icon);
|
||||
Text.DrawDisplayName(c, icon);
|
||||
Text.DrawDescription(c, icon);
|
||||
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.Mini)
|
||||
{
|
||||
if (!icon.ShortDescription.Equals(icon.DisplayName) &&
|
||||
!icon.ShortDescription.Equals(icon.Description))
|
||||
Text.DrawToBottom(c, icon, ETextSide.Left, icon.ShortDescription);
|
||||
Text.DrawToBottom(c, icon, ETextSide.Right, icon.CosmeticSource);
|
||||
}
|
||||
}
|
||||
|
||||
UserFacingFlag.DrawUserFacingFlags(c, icon);
|
||||
|
||||
// has more things to show
|
||||
if (height > icon.Size)
|
||||
{
|
||||
Statistics.DrawStats(c, icon);
|
||||
}
|
||||
}
|
||||
|
||||
Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
|
||||
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
case "FortPlaylistAthena":
|
||||
{
|
||||
BasePlaylist icon = new BasePlaylist(exports[index]);
|
||||
using (var ret = new SKBitmap(icon.Width, icon.Height, SKColorType.Rgba8888, SKAlphaType.Premul))
|
||||
using (var c = new SKCanvas(ret))
|
||||
{
|
||||
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
|
||||
{
|
||||
Rarity.DrawRarity(c, icon);
|
||||
}
|
||||
|
||||
LargeSmallImage.DrawNotStretchedPreviewImage(c, icon);
|
||||
|
||||
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
|
||||
{
|
||||
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoText)
|
||||
{
|
||||
Text.DrawBackground(c, icon);
|
||||
Text.DrawDisplayName(c, icon);
|
||||
Text.DrawDescription(c, icon);
|
||||
}
|
||||
}
|
||||
|
||||
// Watermark.DrawWatermark(c); // boi why would you watermark something you don't own ¯\_(ツ)_/¯
|
||||
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
case "AthenaSeasonItemDefinition":
|
||||
{
|
||||
BaseSeason icon = new BaseSeason(exports[index], assetFolder);
|
||||
using (var ret = new SKBitmap(icon.Width, icon.HeaderHeight + icon.AdditionalSize,
|
||||
SKColorType.Rgba8888, SKAlphaType.Opaque))
|
||||
using (var c = new SKCanvas(ret))
|
||||
{
|
||||
icon.Draw(c);
|
||||
|
||||
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
case "FortMtxOfferData":
|
||||
{
|
||||
BaseOffer icon = new BaseOffer(exports[index]);
|
||||
using (var ret = new SKBitmap(icon.Size, icon.Size, SKColorType.Rgba8888, SKAlphaType.Premul))
|
||||
using (var c = new SKCanvas(ret))
|
||||
{
|
||||
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
|
||||
{
|
||||
icon.DrawBackground(c);
|
||||
}
|
||||
|
||||
icon.DrawImage(c);
|
||||
|
||||
Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
|
||||
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
case "MaterialInstanceConstant":
|
||||
{
|
||||
if (assetFolder.Equals("MI_OfferImages") || assetFolder.Equals("RenderSwitch_Materials"))
|
||||
{
|
||||
BaseOfferMaterial icon = new BaseOfferMaterial(exports[index]);
|
||||
using (var ret = new SKBitmap(icon.Size, icon.Size, SKColorType.Rgba8888, SKAlphaType.Premul))
|
||||
using (var c = new SKCanvas(ret))
|
||||
{
|
||||
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
|
||||
{
|
||||
icon.DrawBackground(c);
|
||||
}
|
||||
|
||||
icon.DrawImage(c);
|
||||
|
||||
Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
|
||||
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
case "FortItemSeriesDefinition":
|
||||
{
|
||||
BaseIcon icon = new BaseIcon();
|
||||
using (var ret = new SKBitmap(icon.Size, icon.Size, SKColorType.Rgba8888, SKAlphaType.Opaque))
|
||||
using (var c = new SKCanvas(ret))
|
||||
{
|
||||
Serie.GetRarity(icon, exports[index]);
|
||||
Rarity.DrawRarity(c, icon);
|
||||
|
||||
Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
|
||||
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
case "PlaylistUserOptionEnum":
|
||||
case "PlaylistUserOptionBool":
|
||||
case "PlaylistUserOptionString":
|
||||
case "PlaylistUserOptionIntEnum":
|
||||
case "PlaylistUserOptionIntRange":
|
||||
case "PlaylistUserOptionColorEnum":
|
||||
case "PlaylistUserOptionFloatEnum":
|
||||
case "PlaylistUserOptionFloatRange":
|
||||
case "PlaylistUserOptionPrimaryAsset":
|
||||
case "PlaylistUserOptionCollisionProfileEnum":
|
||||
{
|
||||
BaseUserOption icon = new BaseUserOption(exports[index]);
|
||||
using (var ret = new SKBitmap(icon.Width, icon.Height, SKColorType.Rgba8888, SKAlphaType.Opaque))
|
||||
using (var c = new SKCanvas(ret))
|
||||
{
|
||||
icon.Draw(c);
|
||||
|
||||
Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
|
||||
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
case "FortChallengeBundleItemDefinition":
|
||||
{
|
||||
BaseBundle icon = new BaseBundle(exports[index], assetFolder);
|
||||
using (var ret = new SKBitmap(icon.Width, icon.HeaderHeight + icon.AdditionalSize,
|
||||
SKColorType.Rgba8888, SKAlphaType.Opaque))
|
||||
using (var c = new SKCanvas(ret))
|
||||
{
|
||||
HeaderStyle.DrawHeaderPaint(c, icon);
|
||||
HeaderStyle.DrawHeaderText(c, icon);
|
||||
QuestStyle.DrawQuests(c, icon);
|
||||
QuestStyle.DrawCompletionRewards(c, icon);
|
||||
|
||||
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
case "FortItemAccessTokenType":
|
||||
{
|
||||
BaseItemAccess icon = new BaseItemAccess(exports[index]);
|
||||
using (var ret = new SKBitmap(icon.Size, icon.Size, SKColorType.Rgba8888, SKAlphaType.Opaque))
|
||||
using (var c = new SKCanvas(ret))
|
||||
{
|
||||
icon.Draw(c);
|
||||
|
||||
Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
|
||||
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
case "MapUIData":
|
||||
{
|
||||
BaseMapUIData icon = new BaseMapUIData(exports[index]);
|
||||
using (var ret = new SKBitmap(icon.Width, icon.Height, SKColorType.Rgba8888, SKAlphaType.Premul))
|
||||
using (var c = new SKCanvas(ret))
|
||||
{
|
||||
icon.Draw(c);
|
||||
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
case "ArmorUIData":
|
||||
case "SprayUIData":
|
||||
case "ThemeUIData":
|
||||
case "ContractUIData":
|
||||
case "CurrencyUIData":
|
||||
case "GameModeUIData":
|
||||
case "CharacterUIData":
|
||||
case "SprayLevelUIData":
|
||||
case "EquippableUIData":
|
||||
case "PlayerCardUIData":
|
||||
case "Gun_UIData_Base_C":
|
||||
case "CharacterRoleUIData":
|
||||
case "EquippableSkinUIData":
|
||||
case "EquippableCharmUIData":
|
||||
case "EquippableSkinLevelUIData":
|
||||
case "EquippableSkinChromaUIData":
|
||||
case "EquippableCharmLevelUIData":
|
||||
{
|
||||
BaseUIData icon = new BaseUIData(exports, index);
|
||||
using (var ret = new SKBitmap(icon.Width + icon.AdditionalWidth, icon.Height, SKColorType.Rgba8888,
|
||||
SKAlphaType.Premul))
|
||||
using (var c = new SKCanvas(ret))
|
||||
{
|
||||
icon.Draw(c);
|
||||
|
||||
Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
|
||||
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
//case "StreamedVideoDataAsset": // must find a way to automatically gets the right version in the url
|
||||
// {
|
||||
// if (Globals.Game.ActualGame == EGame.Valorant && exports[index].GetExport<StructProperty>("Uuid") is StructProperty s && s.Value is FGuid uuid)
|
||||
// {
|
||||
// Process.Start(new ProcessStartInfo
|
||||
// {
|
||||
// FileName = string.Format(
|
||||
// "http://valorant.dyn.riotcdn.net/x/videos/release-01.05/{0}_default_universal.mp4",
|
||||
// $"{uuid.A:x8}-{uuid.B >> 16:x4}-{uuid.B & 0xFFFF:x4}-{uuid.C >> 16:x4}-{uuid.C & 0xFFFF:x4}{uuid.D:x8}"),
|
||||
// UseShellExecute = true
|
||||
// });
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
case "GQuest":
|
||||
case "GAccolade":
|
||||
case "GCosmeticSkin":
|
||||
case "GCharacterPerk":
|
||||
case "GCosmeticTitle":
|
||||
case "GCosmeticBadge":
|
||||
case "GCosmeticEmote":
|
||||
case "GCosmeticTriumph":
|
||||
case "GCosmeticRunTrail":
|
||||
case "GCosmeticArtifact":
|
||||
case "GCosmeticDropTrail":
|
||||
{
|
||||
BaseGCosmetic icon = new BaseGCosmetic(exports[index], exportType);
|
||||
using (var ret = new SKBitmap(icon.Width, icon.Height, SKColorType.Rgba8888, SKAlphaType.Premul))
|
||||
using (var c = new SKCanvas(ret))
|
||||
{
|
||||
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign == EIconDesign.Flat)
|
||||
{
|
||||
icon.Draw(c);
|
||||
}
|
||||
else if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
|
||||
{
|
||||
Rarity.DrawRarity(c, icon);
|
||||
}
|
||||
|
||||
LargeSmallImage.DrawPreviewImage(c, icon);
|
||||
|
||||
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground &&
|
||||
(EIconDesign)Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoText)
|
||||
{
|
||||
Text.DrawBackground(c, icon);
|
||||
Text.DrawDisplayName(c, icon);
|
||||
Text.DrawDescription(c, icon);
|
||||
}
|
||||
|
||||
Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
|
||||
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
case "GCosmeticCard":
|
||||
{
|
||||
BaseGCosmetic icon = new BaseGCosmetic(exports[index], exportType);
|
||||
using (var ret = new SKBitmap(icon.Width, icon.Height, SKColorType.Rgba8888, SKAlphaType.Premul))
|
||||
using (var c = new SKCanvas(ret))
|
||||
{
|
||||
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign == EIconDesign.Flat)
|
||||
{
|
||||
icon.Draw(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
|
||||
{
|
||||
Rarity.DrawRarity(c, icon);
|
||||
}
|
||||
}
|
||||
|
||||
LargeSmallImage.DrawPreviewImage(c, icon);
|
||||
|
||||
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
|
||||
{
|
||||
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoText)
|
||||
{
|
||||
Text.DrawBackground(c, icon);
|
||||
Text.DrawDisplayName(c, icon);
|
||||
Text.DrawDescription(c, icon);
|
||||
}
|
||||
}
|
||||
|
||||
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
// Battle Breakers
|
||||
case "WExpGenericAccountItemDefinition":
|
||||
{
|
||||
BaseBBDefinition icon = new BaseBBDefinition(exports[index], exportType);
|
||||
using (var ret = new SKBitmap(icon.Width, icon.Height, SKColorType.Rgba8888, SKAlphaType.Premul))
|
||||
using (var c = new SKCanvas(ret))
|
||||
{
|
||||
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
|
||||
{
|
||||
if (icon.RarityBackgroundImage != null)
|
||||
{
|
||||
c.DrawBitmap(icon.RarityBackgroundImage, new SKRect(icon.Margin, icon.Margin, icon.Width - icon.Margin, icon.Height - icon.Margin),
|
||||
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
|
||||
}
|
||||
else
|
||||
{
|
||||
Rarity.DrawRarity(c, icon);
|
||||
}
|
||||
}
|
||||
|
||||
LargeSmallImage.DrawPreviewImage(c, icon);
|
||||
|
||||
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground)
|
||||
{
|
||||
if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoText)
|
||||
{
|
||||
Text.DrawBackground(c, icon);
|
||||
Text.DrawDisplayName(c, icon);
|
||||
Text.DrawDescription(c, icon);
|
||||
}
|
||||
}
|
||||
|
||||
Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
|
||||
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
202
FModel/Creator/CreatorPackage.cs
Normal file
202
FModel/Creator/CreatorPackage.cs
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using FModel.Creator.Bases;
|
||||
using FModel.Creator.Bases.BB;
|
||||
using FModel.Creator.Bases.FN;
|
||||
using FModel.Creator.Bases.SB;
|
||||
|
||||
namespace FModel.Creator
|
||||
{
|
||||
public class CreatorPackage : IDisposable
|
||||
{
|
||||
private UObject _object;
|
||||
private EIconStyle _style;
|
||||
|
||||
public CreatorPackage(UObject uObject, EIconStyle style)
|
||||
{
|
||||
_object = uObject;
|
||||
_style = style;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public UCreator ConstructCreator()
|
||||
{
|
||||
TryConstructCreator(out var creator);
|
||||
return creator;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryConstructCreator(out UCreator creator)
|
||||
{
|
||||
switch (_object.ExportType)
|
||||
{
|
||||
// Fortnite
|
||||
case "AthenaConsumableEmoteItemDefinition":
|
||||
case "AthenaSkyDiveContrailItemDefinition":
|
||||
case "AthenaLoadingScreenItemDefinition":
|
||||
case "AthenaVictoryPoseItemDefinition":
|
||||
case "AthenaPetCarrierItemDefinition":
|
||||
case "AthenaMusicPackItemDefinition":
|
||||
case "AthenaBattleBusItemDefinition":
|
||||
case "AthenaCharacterItemDefinition":
|
||||
case "AthenaMapMarkerItemDefinition":
|
||||
case "AthenaBackpackItemDefinition":
|
||||
case "AthenaPickaxeItemDefinition":
|
||||
case "AthenaGadgetItemDefinition":
|
||||
case "AthenaGliderItemDefinition":
|
||||
case "AthenaSprayItemDefinition":
|
||||
case "AthenaDanceItemDefinition":
|
||||
case "AthenaEmojiItemDefinition":
|
||||
case "AthenaItemWrapDefinition":
|
||||
case "AthenaToyItemDefinition":
|
||||
case "FortHeroType":
|
||||
case "FortTokenType":
|
||||
case "FortAbilityKit":
|
||||
case "FortWorkerType":
|
||||
case "RewardGraphToken":
|
||||
case "FortBannerTokenType":
|
||||
case "FortVariantTokenType":
|
||||
case "FortDecoItemDefinition":
|
||||
case "FortStatItemDefinition":
|
||||
case "FortAmmoItemDefinition":
|
||||
case "FortEmoteItemDefinition":
|
||||
case "FortBadgeItemDefinition":
|
||||
case "FortAwardItemDefinition":
|
||||
case "FortGadgetItemDefinition":
|
||||
case "FortPlaysetItemDefinition":
|
||||
case "FortGiftBoxItemDefinition":
|
||||
case "FortOutpostItemDefinition":
|
||||
case "FortVehicleItemDefinition":
|
||||
case "FortCardPackItemDefinition":
|
||||
case "FortDefenderItemDefinition":
|
||||
case "FortCurrencyItemDefinition":
|
||||
case "FortResourceItemDefinition":
|
||||
case "FortBackpackItemDefinition":
|
||||
case "FortCodeTokenItemDefinition":
|
||||
case "FortSchematicItemDefinition":
|
||||
case "FortWorldMultiItemDefinition":
|
||||
case "FortAlterationItemDefinition":
|
||||
case "FortExpeditionItemDefinition":
|
||||
case "FortIngredientItemDefinition":
|
||||
case "FortAccountBuffItemDefinition":
|
||||
case "FortWeaponMeleeItemDefinition":
|
||||
case "FortPlayerPerksItemDefinition":
|
||||
case "FortPlaysetPropItemDefinition":
|
||||
case "FortHomebaseNodeItemDefinition":
|
||||
case "FortNeverPersistItemDefinition":
|
||||
case "RadioContentSourceItemDefinition":
|
||||
case "FortPlaysetGrenadeItemDefinition":
|
||||
case "FortPersonalVehicleItemDefinition":
|
||||
case "FortGameplayModifierItemDefinition":
|
||||
case "FortHardcoreModifierItemDefinition":
|
||||
case "FortConsumableAccountItemDefinition":
|
||||
case "FortConversionControlItemDefinition":
|
||||
case "FortAccountBuffCreditItemDefinition":
|
||||
case "FortEventCurrencyItemDefinitionRedir":
|
||||
case "FortPersistentResourceItemDefinition":
|
||||
case "FortHomebaseBannerIconItemDefinition":
|
||||
case "FortCampaignHeroLoadoutItemDefinition":
|
||||
case "FortConditionalResourceItemDefinition":
|
||||
case "FortChallengeBundleScheduleDefinition":
|
||||
case "FortWeaponMeleeDualWieldItemDefinition":
|
||||
case "FortDailyRewardScheduleTokenDefinition":
|
||||
case "FortCreativeRealEstatePlotItemDefinition":
|
||||
case "AthenaDanceItemDefinition_AdHocSquadsJoin_C":
|
||||
creator = _style switch
|
||||
{
|
||||
EIconStyle.Cataba => new BaseCommunity(_object, _style, "Cataba"),
|
||||
_ => new BaseIcon(_object, _style)
|
||||
};
|
||||
return true;
|
||||
case "FortTrapItemDefinition":
|
||||
case "FortTandemCharacterData":
|
||||
case "FortSpyTechItemDefinition":
|
||||
case "FortAccoladeItemDefinition":
|
||||
case "FortContextTrapItemDefinition":
|
||||
case "FortWeaponRangedItemDefinition":
|
||||
case "Daybreak_LevelExitVehicle_PartItemDefinition_C":
|
||||
creator = new BaseIconStats(_object, _style);
|
||||
return true;
|
||||
case "FortItemSeriesDefinition":
|
||||
creator = new BaseSeries(_object, _style);
|
||||
return true;
|
||||
case "MaterialInstanceConstant"
|
||||
when _object.Owner != null &&
|
||||
(_object.Owner.Name.EndsWith($"/MI_OfferImages/{_object.Name}") ||
|
||||
_object.Owner.Name.EndsWith($"/RenderSwitch_Materials/{_object.Name}")):
|
||||
creator = new BaseMaterialInstance(_object, _style);
|
||||
return true;
|
||||
case "FortMtxOfferData":
|
||||
creator = new BaseMtxOffer(_object, _style);
|
||||
return true;
|
||||
case "FortPlaylistAthena":
|
||||
creator = new BasePlaylist(_object, _style);
|
||||
return true;
|
||||
case "FortFeatItemDefinition":
|
||||
case "FortQuestItemDefinition":
|
||||
case "AthenaDailyQuestDefinition":
|
||||
case "FortUrgentQuestItemDefinition":
|
||||
creator = new BaseQuest(_object, _style);
|
||||
return true;
|
||||
case "FortCompendiumItemDefinition":
|
||||
case "FortChallengeBundleItemDefinition":
|
||||
creator = new BaseBundle(_object, _style);
|
||||
return true;
|
||||
case "AthenaSeasonItemDefinition":
|
||||
creator = new BaseSeason(_object, _style);
|
||||
return true;
|
||||
case "FortItemAccessTokenType":
|
||||
creator = new BaseItemAccessToken(_object, _style);
|
||||
return true;
|
||||
case "PlaylistUserOptionEnum":
|
||||
case "PlaylistUserOptionBool":
|
||||
case "PlaylistUserOptionString":
|
||||
case "PlaylistUserOptionIntEnum":
|
||||
case "PlaylistUserOptionIntRange":
|
||||
case "PlaylistUserOptionColorEnum":
|
||||
case "PlaylistUserOptionFloatEnum":
|
||||
case "PlaylistUserOptionFloatRange":
|
||||
case "PlaylistUserOptionPrimaryAsset":
|
||||
case "PlaylistUserOptionCollisionProfileEnum":
|
||||
creator = new BaseUserControl(_object, _style);
|
||||
return true;
|
||||
// Battle Breakers
|
||||
case "WExpGenericAccountItemDefinition":
|
||||
creator = new BaseBreakersIcon(_object, EIconStyle.Default);
|
||||
return true;
|
||||
// Spellbreak
|
||||
case "GQuest":
|
||||
case "GAccolade":
|
||||
case "GCosmeticCard":
|
||||
case "GCosmeticSkin":
|
||||
case "GCharacterPerk":
|
||||
case "GCosmeticTitle":
|
||||
case "GCosmeticBadge":
|
||||
case "GCosmeticEmote":
|
||||
case "GCosmeticTriumph":
|
||||
case "GCosmeticRunTrail":
|
||||
case "GCosmeticArtifact":
|
||||
case "GCosmeticDropTrail":
|
||||
creator = new BaseSpellIcon(_object, EIconStyle.Default);
|
||||
return true;
|
||||
case "GLeagueTier":
|
||||
creator = new BaseLeague(_object, EIconStyle.Default);
|
||||
return true;
|
||||
case "GLeagueDivision":
|
||||
creator = new BaseDivision(_object, EIconStyle.Default);
|
||||
return true;
|
||||
default:
|
||||
creator = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => $"{_object.ExportType} | {_style}";
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_object = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
using FModel.Creator.Bases;
|
||||
using FModel.PakReader;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.Objects;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
|
||||
namespace FModel.Creator.Icons
|
||||
{
|
||||
static class DisplayAssetImage
|
||||
{
|
||||
public static bool GetDisplayAssetImage(BaseIcon icon, IUExport o, ref string assetName)
|
||||
{
|
||||
string path = string.Empty;
|
||||
bool displayExists = true;
|
||||
if (o.TryGetValue("DisplayAssetPath", out var d) && d is StructProperty da && da.Value is FSoftObjectPath daOut)
|
||||
path = daOut.AssetPathName.String;
|
||||
else
|
||||
{
|
||||
displayExists = false;
|
||||
path = "/Game/Catalog/MI_OfferImages/MI_" + assetName.Substring(0, assetName.LastIndexOf(".")).Replace("Athena_Commando_", "");
|
||||
}
|
||||
|
||||
Package p = Utils.GetPropertyPakPackage(path);
|
||||
if (!displayExists && p == default)
|
||||
p = Utils.GetPropertyPakPackage(path[..path.LastIndexOf("_")]);
|
||||
|
||||
if (p != null && p.HasExport())
|
||||
{
|
||||
var obj = p.GetExport<UObject>();
|
||||
if (obj != null)
|
||||
{
|
||||
if (obj.GetExport<ArrayProperty>("TextureParameterValues") is ArrayProperty textureParameterValues)
|
||||
{
|
||||
foreach (StructProperty textureParameter in textureParameterValues.Value)
|
||||
{
|
||||
if (textureParameter.Value is UObject parameter &&
|
||||
parameter.TryGetValue("ParameterValue", out var i) && i is ObjectProperty value &&
|
||||
parameter.TryGetValue("ParameterInfo", out var i1) && i1 is StructProperty i2 && i2.Value is UObject info &&
|
||||
info.TryGetValue("Name", out var j1) && j1 is NameProperty name)
|
||||
{
|
||||
if (name.Value.String.Equals("OfferImage") || name.Value.String.Equals("Texture"))
|
||||
{
|
||||
icon.IconImage = Utils.GetObjectTexture(value) ?? Utils.GetTexture($"{value.Value.Resource.OuterIndex.Resource.ObjectName.String}_1") ?? Utils.GetTexture($"{value.Value.Resource.OuterIndex.Resource.ObjectName.String}_01");
|
||||
assetName = "MI_" + assetName;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return GenerateOldFormat(icon, obj, ref assetName);
|
||||
}
|
||||
}
|
||||
|
||||
return GenerateOldFormat(icon, ref assetName);
|
||||
}
|
||||
|
||||
private static bool GenerateOldFormat(BaseIcon icon, ref string assetName)
|
||||
{
|
||||
var p = Utils.GetPropertyPakPackage("/Game/Catalog/DisplayAssets/DA_Featured_" + assetName.Substring(0, assetName.LastIndexOf(".")));
|
||||
if (p != null && p.HasExport())
|
||||
{
|
||||
var obj = p.GetExport<UObject>();
|
||||
if (obj != null)
|
||||
{
|
||||
return GenerateOldFormat(icon, obj, ref assetName);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private static bool GenerateOldFormat(BaseIcon icon, UObject obj, ref string assetName)
|
||||
{
|
||||
string imageType = "DetailsImage";
|
||||
switch ("DA_Featured_" + assetName)
|
||||
{
|
||||
case "DA_Featured_Glider_ID_141_AshtonBoardwalk.uasset":
|
||||
case "DA_Featured_Glider_ID_150_TechOpsBlue.uasset":
|
||||
case "DA_Featured_Glider_ID_131_SpeedyMidnight.uasset":
|
||||
case "DA_Featured_Pickaxe_ID_178_SpeedyMidnight.uasset":
|
||||
case "DA_Featured_Glider_ID_015_Brite.uasset":
|
||||
case "DA_Featured_Glider_ID_016_Tactical.uasset":
|
||||
case "DA_Featured_Glider_ID_017_Assassin.uasset":
|
||||
case "DA_Featured_Pickaxe_ID_027_Scavenger.uasset":
|
||||
case "DA_Featured_Pickaxe_ID_028_Space.uasset":
|
||||
case "DA_Featured_Pickaxe_ID_029_Assassin.uasset":
|
||||
return false;
|
||||
case "DA_Featured_Glider_ID_070_DarkViking.uasset":
|
||||
case "DA_Featured_CID_319_Athena_Commando_F_Nautilus.uasset":
|
||||
imageType = "TileImage";
|
||||
break;
|
||||
}
|
||||
|
||||
if (obj.TryGetValue(imageType, out var v1) && v1 is StructProperty s && s.Value is UObject type &&
|
||||
type.TryGetValue("ResourceObject", out var v2) && v2 is ObjectProperty resourceObject &&
|
||||
resourceObject.Value.Resource.OuterIndex.Resource != null &&
|
||||
!resourceObject.Value.Resource.OuterIndex.Resource.ObjectName.String.Contains("/Game/Athena/Prototype/Textures/"))
|
||||
{
|
||||
icon.IconImage = Utils.GetObjectTexture(resourceObject);
|
||||
assetName = "DA_Featured_" + assetName;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
using FModel.Creator.Bases;
|
||||
using FModel.PakReader;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Icons
|
||||
{
|
||||
static class LargeSmallImage
|
||||
{
|
||||
public static void GetPreviewImage(BaseIcon icon, StructProperty u)
|
||||
{
|
||||
if (u.Value is UObject o && o.TryGetValue("ResourceObject", out var v) && v is ObjectProperty resourceObject)
|
||||
icon.IconImage = Utils.GetObjectTexture(resourceObject);
|
||||
}
|
||||
public static void GetPreviewImage(BaseIcon icon, ObjectProperty o, string assetName) => GetPreviewImage(icon, o, assetName, true);
|
||||
public static void GetPreviewImage(BaseIcon icon, ObjectProperty o, string assetName, bool hightRes)
|
||||
{
|
||||
string path = o.Value.Resource?.OuterIndex.Resource?.ObjectName.String;
|
||||
if (path?.Equals("/Game/Athena/Items/Weapons/WID_Harvest_Pickaxe_STWCosmetic_Tier") == true)
|
||||
path += "_" + assetName.Substring(assetName.LastIndexOf(".") - 1, 1);
|
||||
|
||||
Package p = Utils.GetPropertyPakPackage(path);
|
||||
if (p != null && p.HasExport() && !p.Equals(default))
|
||||
{
|
||||
if (GetPreviewImage(icon, p.GetIndexedExport<UObject>(0), hightRes))
|
||||
return;
|
||||
else if (GetPreviewImage(icon, p.GetIndexedExport<UObject>(1), hightRes)) // FortniteGame/Content/Athena/Items/Cosmetics/Pickaxes/Pickaxe_ID_402_BlackKnightFemale1H.uasset
|
||||
return;
|
||||
}
|
||||
}
|
||||
public static void GetPreviewImage(BaseIcon icon, SoftObjectProperty s) => icon.IconImage = Utils.GetSoftObjectTexture(s);
|
||||
|
||||
private static bool GetPreviewImage(BaseIcon icon, UObject obj, bool hightRes)
|
||||
{
|
||||
if (obj != null)
|
||||
{
|
||||
if (hightRes && obj.TryGetValue("LargePreviewImage", out var sLarge) && sLarge is SoftObjectProperty largePreviewImage && !string.IsNullOrEmpty(largePreviewImage.Value.AssetPathName.String))
|
||||
{
|
||||
GetPreviewImage(icon, largePreviewImage);
|
||||
return true;
|
||||
}
|
||||
else if (obj.TryGetValue("SmallPreviewImage", out var sSmall1) && sSmall1 is SoftObjectProperty smallPreviewImage1 && !string.IsNullOrEmpty(smallPreviewImage1.Value.AssetPathName.String))
|
||||
{
|
||||
GetPreviewImage(icon, smallPreviewImage1);
|
||||
return true;
|
||||
}
|
||||
else if (obj.TryGetValue("SmallPreviewImage", out var sSmall2) && sSmall2 is ObjectProperty smallPreviewImage2 && !string.IsNullOrEmpty(smallPreviewImage2.Value.Resource.OuterIndex.Resource.ObjectName.String))
|
||||
{
|
||||
icon.IconImage = Utils.GetObjectTexture(smallPreviewImage2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void DrawPreviewImage(SKCanvas c, IBase icon) =>
|
||||
c.DrawBitmap(icon.IconImage ?? icon.FallbackImage, new SKRect(icon.Margin, icon.Margin, icon.Width - icon.Margin, icon.Height - icon.Margin),
|
||||
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
|
||||
|
||||
public static void DrawNotStretchedPreviewImage(SKCanvas c, IBase icon)
|
||||
{
|
||||
SKBitmap i = icon.IconImage ?? icon.FallbackImage;
|
||||
int x = i.Width < icon.Width ? ((icon.Width / 2) - (i.Width / 2)) : icon.Margin;
|
||||
c.DrawBitmap(i, new SKRect(x, icon.Margin, (x + i.Width) - (icon.Margin * 2), i.Height - icon.Margin),
|
||||
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
using FModel.Creator.Bases;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using FModel.PakReader;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.Objects;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
|
||||
namespace FModel.Creator.Icons
|
||||
{
|
||||
static class UserFacingFlag
|
||||
{
|
||||
public static void GetUserFacingFlags(List<string> uffs, BaseIcon icon, string exportType)
|
||||
{
|
||||
if (uffs.Count > 0)
|
||||
{
|
||||
Package p = Utils.GetPropertyPakPackage("/Game/Items/ItemCategories"); //PrimaryCategories - SecondaryCategories - TertiaryCategories
|
||||
if (p.HasExport() && !p.Equals(default))
|
||||
{
|
||||
var o = p.GetExport<UObject>();
|
||||
if (o != null && o.TryGetValue("TertiaryCategories", out var tertiaryCategories) && tertiaryCategories is ArrayProperty tertiaryArray)
|
||||
{
|
||||
icon.UserFacingFlags = new SKBitmap[uffs.Count];
|
||||
for (int i = 0; i < uffs.Count; i++)
|
||||
{
|
||||
if (uffs[i].Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests"))
|
||||
{
|
||||
if (exportType.Equals("AthenaPetCarrierItemDefinition"))
|
||||
icon.UserFacingFlags[i] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png")).Stream);
|
||||
else
|
||||
icon.UserFacingFlags[i] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png")).Stream);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (StructProperty structProp in tertiaryArray.Value)
|
||||
{
|
||||
if (structProp.Value is UObject mainUObject &&
|
||||
mainUObject.TryGetValue("TagContainer", out var struc1) && struc1 is StructProperty tagContainer && tagContainer.Value is FGameplayTagContainer f && f.GameplayTags.TryGetGameplayTag(uffs[i], out var _) &&
|
||||
mainUObject.TryGetValue("CategoryBrush", out var struc2) && struc2 is StructProperty categoryBrush && categoryBrush.Value is UObject categoryUObject &&
|
||||
categoryUObject.TryGetValue("Brush_XXS", out var struc3) && struc3 is StructProperty brushXXS && brushXXS.Value is UObject brushUObject &&
|
||||
brushUObject.TryGetValue("ResourceObject", out var object1) && object1 is ObjectProperty resourceObject)
|
||||
{
|
||||
icon.UserFacingFlags[i] = Utils.GetObjectTexture(resourceObject);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawUserFacingFlags(SKCanvas c, BaseIcon icon)
|
||||
{
|
||||
if (icon.UserFacingFlags != null)
|
||||
{
|
||||
int size = 25;
|
||||
int x = icon.Margin * (int)2.5;
|
||||
foreach (SKBitmap b in icon.UserFacingFlags)
|
||||
{
|
||||
if (b == null)
|
||||
continue;
|
||||
|
||||
c.DrawBitmap(b.Resize(size, size), new SKPoint(x, icon.Margin * (int)2.5), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
|
||||
x += size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
using SkiaSharp;
|
||||
using System.IO;
|
||||
|
||||
namespace FModel.Creator.Icons
|
||||
{
|
||||
static class Watermark
|
||||
{
|
||||
public static void DrawWatermark(SKCanvas c)
|
||||
{
|
||||
if (Properties.Settings.Default.UseIconWatermark && !string.IsNullOrWhiteSpace(Properties.Settings.Default.IconWatermarkPath))
|
||||
{
|
||||
using SKBitmap watermarkBase = SKBitmap.Decode(new FileInfo(Properties.Settings.Default.IconWatermarkPath).Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
|
||||
int sizeX = watermarkBase.Width * (int)Properties.Settings.Default.IconWatermarkScale / 512;
|
||||
int sizeY = watermarkBase.Height * (int)Properties.Settings.Default.IconWatermarkScale / 512;
|
||||
SKBitmap watermark = watermarkBase.Resize(sizeX, sizeY);
|
||||
|
||||
float left = Properties.Settings.Default.IconWatermarkX;
|
||||
float top = Properties.Settings.Default.IconWatermarkY;
|
||||
float right = left + watermark.Width;
|
||||
float bottom = top + watermark.Height;
|
||||
c.DrawBitmap(watermark, new SKRect(left, top, right, bottom),
|
||||
new SKPaint
|
||||
{
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
IsAntialias = true,
|
||||
Color = SKColors.Transparent.WithAlpha((byte)Properties.Settings.Default.IconWatermarkOpacity)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
namespace FModel.Creator.Rarities
|
||||
{
|
||||
public enum EFortRarity : int
|
||||
{
|
||||
Common,
|
||||
Uncommon,
|
||||
Rare,
|
||||
Epic,
|
||||
Legendary,
|
||||
Mythic,
|
||||
Transcendent,
|
||||
Unattainable,
|
||||
//Impossible
|
||||
}
|
||||
}
|
||||
|
|
@ -1,204 +0,0 @@
|
|||
using FModel.Creator.Bases;
|
||||
using SkiaSharp;
|
||||
using System.Linq;
|
||||
using FModel.PakReader;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.Objects;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
|
||||
namespace FModel.Creator.Rarities
|
||||
{
|
||||
static class Rarity
|
||||
{
|
||||
public static void GetInGameRarity(BaseIcon icon, EnumProperty e)
|
||||
{
|
||||
Package p = Utils.GetPropertyPakPackage("/Game/Balance/RarityData");
|
||||
if (p != null && p.HasExport())
|
||||
{
|
||||
var d = p.GetExport<UObject>();
|
||||
if (d != null)
|
||||
{
|
||||
EFortRarity rarity = EFortRarity.Uncommon;
|
||||
switch (e?.Value.String)
|
||||
{
|
||||
case "EFortRarity::Common":
|
||||
case "EFortRarity::Handmade":
|
||||
rarity = EFortRarity.Common;
|
||||
break;
|
||||
case "EFortRarity::Rare":
|
||||
case "EFortRarity::Sturdy":
|
||||
rarity = EFortRarity.Rare;
|
||||
break;
|
||||
case "EFortRarity::Epic":
|
||||
case "EFortRarity::Quality":
|
||||
rarity = EFortRarity.Epic;
|
||||
break;
|
||||
case "EFortRarity::Legendary":
|
||||
case "EFortRarity::Fine":
|
||||
rarity = EFortRarity.Legendary;
|
||||
break;
|
||||
case "EFortRarity::Mythic":
|
||||
case "EFortRarity::Elegant":
|
||||
rarity = EFortRarity.Mythic;
|
||||
break;
|
||||
case "EFortRarity::Transcendent":
|
||||
case "EFortRarity::Masterwork":
|
||||
rarity = EFortRarity.Transcendent;
|
||||
break;
|
||||
case "EFortRarity::Unattainable":
|
||||
case "EFortRarity::Badass":
|
||||
rarity = EFortRarity.Unattainable;
|
||||
break;
|
||||
}
|
||||
|
||||
if (d.Values.ElementAt((int)rarity) is StructProperty s && s.Value is UObject colors)
|
||||
{
|
||||
if (colors.TryGetValue("Color1", out var c1) && c1 is StructProperty s1 && s1.Value is FLinearColor color1 &&
|
||||
colors.TryGetValue("Color2", out var c2) && c2 is StructProperty s2 && s2.Value is FLinearColor color2 &&
|
||||
colors.TryGetValue("Color3", out var c3) && c3 is StructProperty s3 && s3.Value is FLinearColor color3)
|
||||
{
|
||||
icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) };
|
||||
icon.RarityBorderColor = new SKColor[2] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else GetHardCodedRarity(icon, e);
|
||||
}
|
||||
|
||||
public static void GetInGameRarity(BaseGCosmetic icon, EnumProperty e)
|
||||
{
|
||||
Package p = Utils.GetPropertyPakPackage("/Game/UI/UIKit/DT_RarityColors");
|
||||
if (p != null || p.HasExport())
|
||||
{
|
||||
var d = p.GetExport<UDataTable>();
|
||||
if (d != null)
|
||||
{
|
||||
if (e != null && d.TryGetValue(e.Value.String["EXRarity::".Length..], out object r) && r is UObject rarity &&
|
||||
rarity.GetExport<ArrayProperty>("Colors") is ArrayProperty colors &&
|
||||
colors.Value[0] is StructProperty s1 && s1.Value is FLinearColor color1 &&
|
||||
colors.Value[1] is StructProperty s2 && s2.Value is FLinearColor color2 &&
|
||||
colors.Value[2] is StructProperty s3 && s3.Value is FLinearColor color3)
|
||||
{
|
||||
icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) };
|
||||
icon.RarityBorderColor = new SKColor[2] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetHardCodedRarity(BaseIcon icon, EnumProperty e)
|
||||
{
|
||||
switch (e?.Value.String)
|
||||
{
|
||||
case "EFortRarity::Common":
|
||||
case "EFortRarity::Handmade":
|
||||
icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("6D6D6D"), SKColor.Parse("333333") };
|
||||
icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("9E9E9E"), SKColor.Parse("9E9E9E") };
|
||||
break;
|
||||
case "EFortRarity::Rare":
|
||||
case "EFortRarity::Sturdy":
|
||||
icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("3669BB"), SKColor.Parse("133254") };
|
||||
icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("5180EE"), SKColor.Parse("5180EE") };
|
||||
break;
|
||||
case "EFortRarity::Epic":
|
||||
case "EFortRarity::Quality":
|
||||
icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("8138C2"), SKColor.Parse("35155C") };
|
||||
icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("B251ED"), SKColor.Parse("B251ED") };
|
||||
break;
|
||||
case "EFortRarity::Legendary":
|
||||
case "EFortRarity::Fine":
|
||||
icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("C06A38"), SKColor.Parse("5C2814") };
|
||||
icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("EC9650"), SKColor.Parse("EC9650") };
|
||||
break;
|
||||
case "EFortRarity::Mythic":
|
||||
case "EFortRarity::Elegant":
|
||||
icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("BA9C36"), SKColor.Parse("594415") };
|
||||
icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("EED951"), SKColor.Parse("EED951") };
|
||||
break;
|
||||
case "EFortRarity::Transcendent":
|
||||
case "EFortRarity::Masterwork":
|
||||
icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("5CDCE2"), SKColor.Parse("72C5F8") };
|
||||
icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("28DAFB"), SKColor.Parse("28DAFB") };
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawRarity(SKCanvas c, IBase icon)
|
||||
{
|
||||
// border
|
||||
c.DrawRect(new SKRect(0, 0, icon.Width, icon.Height),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateLinearGradient(
|
||||
new SKPoint(icon.Width / 2, icon.Height),
|
||||
new SKPoint(icon.Width, icon.Height / 4),
|
||||
icon.RarityBorderColor,
|
||||
SKShaderTileMode.Clamp)
|
||||
});
|
||||
|
||||
switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign)
|
||||
{
|
||||
case EIconDesign.Flat:
|
||||
{
|
||||
if (icon is BaseIcon i && i.RarityBackgroundImage != null)
|
||||
c.DrawBitmap(i.RarityBackgroundImage, new SKRect(icon.Margin, icon.Margin, icon.Width - icon.Margin, icon.Height - icon.Margin),
|
||||
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
|
||||
else
|
||||
{
|
||||
c.DrawRect(new SKRect(icon.Margin, icon.Margin, icon.Width - icon.Margin, icon.Height - icon.Margin),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Color = icon.RarityBackgroundColors[0]
|
||||
});
|
||||
|
||||
var paint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Color = icon.RarityBackgroundColors[1].WithAlpha(75)
|
||||
};
|
||||
var pathTop = new SKPath { FillType = SKPathFillType.EvenOdd };
|
||||
pathTop.MoveTo(icon.Margin, icon.Margin);
|
||||
pathTop.LineTo(icon.Margin + (icon.Width / 17 * 10), icon.Margin);
|
||||
pathTop.LineTo(icon.Margin, icon.Margin + (icon.Height / 17));
|
||||
pathTop.Close();
|
||||
c.DrawPath(pathTop, paint);
|
||||
|
||||
var pathBottom = new SKPath { FillType = SKPathFillType.EvenOdd };
|
||||
pathBottom.MoveTo(icon.Margin, icon.Height - icon.Margin);
|
||||
pathBottom.LineTo(icon.Margin, icon.Height - icon.Margin - (icon.Height / 17 * 2.5f));
|
||||
pathBottom.LineTo(icon.Width - icon.Margin, icon.Height - icon.Margin - (icon.Height / 17 * 4.5f));
|
||||
pathBottom.LineTo(icon.Width - icon.Margin, icon.Height - icon.Margin);
|
||||
pathBottom.Close();
|
||||
c.DrawPath(pathBottom, paint);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (icon is BaseIcon i && i.RarityBackgroundImage != null)
|
||||
c.DrawBitmap(i.RarityBackgroundImage, new SKRect(icon.Margin, icon.Margin, icon.Width - icon.Margin, icon.Height - icon.Margin),
|
||||
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
|
||||
else
|
||||
c.DrawRect(new SKRect(icon.Margin, icon.Margin, icon.Width - icon.Margin, icon.Height - icon.Margin),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateRadialGradient(
|
||||
new SKPoint(icon.Width / 2, icon.Height / 2),
|
||||
icon.Width / 5 * 4,
|
||||
icon.RarityBackgroundColors,
|
||||
SKShaderTileMode.Clamp)
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
using FModel.Creator.Bases;
|
||||
using FModel.PakReader;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.Objects;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Rarities
|
||||
{
|
||||
static class Serie
|
||||
{
|
||||
public static void GetRarity(BaseIcon icon, ObjectProperty o)
|
||||
{
|
||||
if (o.Value.Resource != null)
|
||||
{
|
||||
Package p = Utils.GetPropertyPakPackage(o.Value.Resource.OuterIndex.Resource.ObjectName.String);
|
||||
if (p.HasExport() && !p.Equals(default))
|
||||
{
|
||||
var obj = p.GetExport<UObject>();
|
||||
if (obj != null)
|
||||
GetRarity(icon, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetRarity(BaseIcon icon, IUExport export)
|
||||
{
|
||||
if (export.TryGetValue("BackgroundTexture", out var t) && t is SoftObjectProperty sop)
|
||||
icon.RarityBackgroundImage = Utils.GetSoftObjectTexture(sop);
|
||||
|
||||
if (export.TryGetValue("Colors", out var v) && v is StructProperty s && s.Value is UObject colors)
|
||||
{
|
||||
if (colors.TryGetValue("Color1", out var c1) && c1 is StructProperty s1 && s1.Value is FLinearColor color1 &&
|
||||
colors.TryGetValue("Color2", out var c2) && c2 is StructProperty s2 && s2.Value is FLinearColor color2 &&
|
||||
colors.TryGetValue("Color4", out var c4) && c4 is StructProperty s4 && s4.Value is FLinearColor color4)
|
||||
{
|
||||
icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse(color1.Hex), SKColor.Parse(color4.Hex) };
|
||||
icon.RarityBorderColor = new SKColor[2] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Stats
|
||||
{
|
||||
public class Statistic
|
||||
{
|
||||
public SKBitmap Icon;
|
||||
public string Description;
|
||||
public string DisplayName;
|
||||
public int Height;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,231 +0,0 @@
|
|||
using FModel.Creator.Bases;
|
||||
using FModel.Creator.Texts;
|
||||
using FModel.Utils;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using FModel.PakReader;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
|
||||
namespace FModel.Creator.Stats
|
||||
{
|
||||
static class Statistics
|
||||
{
|
||||
public static void GetAmmoData(BaseIcon icon, SoftObjectProperty ammoData)
|
||||
{
|
||||
if (!ammoData.Value.AssetPathName.String.StartsWith("/Game/Athena/Items/Consumables/"))
|
||||
{
|
||||
Package p = Utils.GetPropertyPakPackage(ammoData.Value.AssetPathName.String);
|
||||
if (p.HasExport() && !p.Equals(default))
|
||||
{
|
||||
var obj = p.GetExport<UObject>();
|
||||
if (obj != null)
|
||||
{
|
||||
if (obj.TryGetValue("DisplayName", out var v1) && v1 is TextProperty displayName &&
|
||||
obj.TryGetValue("SmallPreviewImage", out var v2) && v2 is SoftObjectProperty smallPreviewImage)
|
||||
{
|
||||
icon.Stats.Add(new Statistic
|
||||
{
|
||||
Icon = Utils.GetSoftObjectTexture(smallPreviewImage),
|
||||
Description = Text.GetTextPropertyBase(displayName).ToUpper()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetWeaponStats(BaseIcon icon, StructProperty weaponStatHandle)
|
||||
{
|
||||
if (weaponStatHandle.Value is UObject o1 &&
|
||||
o1.TryGetValue("DataTable", out var c1) && c1 is ObjectProperty dataTable &&
|
||||
o1.TryGetValue("RowName", out var c2) && c2 is NameProperty rowName)
|
||||
{
|
||||
Package p = Utils.GetPropertyPakPackage(dataTable.Value.Resource.OuterIndex.Resource.ObjectName.String);
|
||||
if (p.HasExport() && !p.Equals(default))
|
||||
{
|
||||
var table = p.GetExport<UDataTable>();
|
||||
if (table != null)
|
||||
{
|
||||
if (table.TryGetValue(rowName.Value.String, out var v1) && v1 is UObject stats)
|
||||
{
|
||||
if (stats.TryGetValue("ReloadTime", out var s1) && s1 is FloatProperty reloadTime && reloadTime.Value != 0)
|
||||
icon.Stats.Add(new Statistic
|
||||
{
|
||||
Icon = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_ReloadTime_Weapon_Stats.png")).Stream),
|
||||
Description = $"{Localizations.GetLocalization(string.Empty, "6EA26D1A4252034FBD869A90F9A6E49A", "Reload Time")} ({Localizations.GetLocalization(string.Empty, "6BA53D764BA5CC13E821D2A807A72365", "seconds")}) : {reloadTime.Value:0.0}".ToUpper()
|
||||
});
|
||||
|
||||
if (stats.TryGetValue("ClipSize", out var s2) && s2 is IntProperty clipSize && clipSize.Value != 0)
|
||||
icon.Stats.Add(new Statistic
|
||||
{
|
||||
Icon = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_ClipSize_Weapon_Stats.png")).Stream),
|
||||
Description = $"{Localizations.GetLocalization(string.Empty, "068239DD4327B36124498C9C5F61C038", "Magazine Size")} : {clipSize.Value}".ToUpper()
|
||||
});
|
||||
|
||||
if (stats.TryGetValue("DmgPB", out var s3) && s3 is FloatProperty dmgPB && dmgPB.Value != 0)
|
||||
icon.Stats.Add(new Statistic
|
||||
{
|
||||
Icon = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_DamagePerBullet_Weapon_Stats.png")).Stream),
|
||||
Description = $"{Localizations.GetLocalization(string.Empty, "BF7E3CF34A9ACFF52E95EAAD4F09F133", "Damage to Player")} : {dmgPB.Value}".ToUpper()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetHeroStats(BaseIcon icon, ObjectProperty heroGameplayDefinition)
|
||||
{
|
||||
Package p = Utils.GetPropertyPakPackage(heroGameplayDefinition.Value.Resource.OuterIndex.Resource.ObjectName.String);
|
||||
if (p.HasExport() && !p.Equals(default))
|
||||
{
|
||||
var obj = p.GetExport<UObject>();
|
||||
if (obj != null)
|
||||
{
|
||||
if (obj.TryGetValue("HeroPerk", out var v1) && v1 is StructProperty s1 && s1.Value is UObject heroPerk)
|
||||
{
|
||||
GetAbilityKit(icon, heroPerk);
|
||||
}
|
||||
|
||||
if (obj.TryGetValue("TierAbilityKits", out var v2) && v2 is ArrayProperty tierAbilityKits)
|
||||
{
|
||||
foreach (StructProperty abilityKit in tierAbilityKits.Value)
|
||||
{
|
||||
if (abilityKit.Value is UObject kit)
|
||||
{
|
||||
GetAbilityKit(icon, kit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void GetAbilityKit(BaseIcon icon, UObject parent)
|
||||
{
|
||||
if (parent.TryGetValue("GrantedAbilityKit", out var v) && v is SoftObjectProperty grantedAbilityKit)
|
||||
{
|
||||
Package k = Utils.GetPropertyPakPackage(grantedAbilityKit.Value.AssetPathName.String);
|
||||
if (k.HasExport() && !k.Equals(default))
|
||||
{
|
||||
var kit = k.GetExport<UObject>();
|
||||
if (kit != null &&
|
||||
kit.GetExport<TextProperty>("DisplayName") is TextProperty displayName &&
|
||||
kit.GetExport<StructProperty>("IconBrush") is StructProperty brush && brush.Value is UObject iconBrush &&
|
||||
iconBrush.TryGetValue("ResourceObject", out var s) && s is ObjectProperty resourceObject)
|
||||
{
|
||||
icon.Stats.Add(new Statistic
|
||||
{
|
||||
Icon = Utils.GetObjectTexture(resourceObject),
|
||||
Description = Text.GetTextPropertyBase(displayName).ToUpper()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawStats(SKCanvas c, BaseIcon icon)
|
||||
{
|
||||
int size = 48;
|
||||
int iconSize = 40;
|
||||
int textSize = 25;
|
||||
int y = icon.Size;
|
||||
foreach (Statistic stat in icon.Stats)
|
||||
{
|
||||
c.DrawRect(new SKRect(0, y, icon.Size, y + size),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateLinearGradient(
|
||||
new SKPoint(icon.Size / 2, icon.Size),
|
||||
new SKPoint(icon.Size, icon.Size / 4),
|
||||
icon.RarityBorderColor,
|
||||
SKShaderTileMode.Clamp)
|
||||
});
|
||||
|
||||
|
||||
if ((EIconDesign)Properties.Settings.Default.AssetsIconDesign == EIconDesign.Flat)
|
||||
{
|
||||
c.DrawRect(new SKRect(icon.Margin, y, icon.Size - icon.Margin, y + size - icon.Margin),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Color = icon.RarityBackgroundColors[0]
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
c.DrawRect(new SKRect(icon.Margin, y, icon.Size - icon.Margin, y + size - icon.Margin),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Shader = SKShader.CreateRadialGradient(
|
||||
new SKPoint(icon.Size / 2, icon.Size / 2),
|
||||
icon.Size / 5 * 4,
|
||||
icon.RarityBackgroundColors,
|
||||
SKShaderTileMode.Clamp)
|
||||
});
|
||||
}
|
||||
|
||||
c.DrawRect(new SKRect(icon.Margin, y, icon.Size - icon.Margin, y + size - icon.Margin),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Color = new SKColor(0, 0, 50, 75)
|
||||
});
|
||||
|
||||
if (stat.Icon != null) c.DrawBitmap(stat.Icon.Resize(iconSize, iconSize), new SKPoint(icon.Margin * (int)2.5, y + 4), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
|
||||
|
||||
var statPaint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Typeface = Text.TypeFaces.DisplayNameTypeface,
|
||||
TextSize = textSize,
|
||||
Color = SKColors.White,
|
||||
TextAlign = SKTextAlign.Center,
|
||||
};
|
||||
|
||||
if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic)
|
||||
{
|
||||
SKShaper shaper = new SKShaper(statPaint.Typeface);
|
||||
float shapedTextWidth;
|
||||
|
||||
while (true)
|
||||
{
|
||||
SKShaper.Result shapedText = shaper.Shape(stat.Description, statPaint);
|
||||
shapedTextWidth = shapedText.Points[^1].X + statPaint.TextSize / 2f;
|
||||
|
||||
if (shapedTextWidth > (icon.Size - (icon.Margin * 2) - iconSize))
|
||||
{
|
||||
statPaint.TextSize -= 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
c.DrawShapedText(shaper, stat.Description, (icon.Size - shapedTextWidth) / 2f, y + 32, statPaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (statPaint.MeasureText(stat.Description) > (icon.Size - (icon.Margin * 2) - iconSize))
|
||||
{
|
||||
statPaint.TextSize = textSize -= 2;
|
||||
}
|
||||
c.DrawText(stat.Description, icon.Size / 2, y + 32, statPaint);
|
||||
}
|
||||
|
||||
y += size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
namespace FModel.Creator.Texts
|
||||
{
|
||||
public enum ETextSide
|
||||
{
|
||||
Center,
|
||||
Right,
|
||||
Left
|
||||
}
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
using FModel.Creator.Bases;
|
||||
using FModel.Creator.Icons;
|
||||
using FModel.PakReader;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.Objects;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
using FModel.Utils;
|
||||
|
||||
namespace FModel.Creator.Texts
|
||||
{
|
||||
static class GameplayTag
|
||||
{
|
||||
public static void GetGameplayTags(BaseIcon icon, StructProperty s, string exportType)
|
||||
{
|
||||
if (s.Value is FGameplayTagContainer g)
|
||||
{
|
||||
if (g.GameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source))
|
||||
icon.CosmeticSource = source.String.Substring("Cosmetics.Source.".Length);
|
||||
else if(g.GameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action))
|
||||
icon.CosmeticSource = action.String.Substring("Athena.ItemAction.".Length);
|
||||
|
||||
if (g.GameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set))
|
||||
icon.Description += GetCosmeticSet(set.String);
|
||||
if (g.GameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season))
|
||||
icon.Description += GetCosmeticSeason(season.String);
|
||||
|
||||
UserFacingFlag.GetUserFacingFlags(
|
||||
g.GameplayTags.GetAllGameplayTag("Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."),
|
||||
icon, exportType);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetCosmeticSet(string setName)
|
||||
{
|
||||
Package p = Utils.GetPropertyPakPackage("/Game/Athena/Items/Cosmetics/Metadata/CosmeticSets");
|
||||
if (p.HasExport() && !p.Equals(default))
|
||||
{
|
||||
var d = p.GetExport<UDataTable>();
|
||||
if (d != null && d.TryGetCaseInsensitiveValue(setName, out var obj) && obj is UObject o)
|
||||
{
|
||||
if (o.TryGetValue("DisplayName", out var displayName) && displayName is TextProperty t)
|
||||
{
|
||||
(string n, string k, string s) = Text.GetTextPropertyBases(t);
|
||||
string set = Localizations.GetLocalization(n, k, s);
|
||||
string format = Localizations.GetLocalization("Fort.Cosmetics", "CosmeticItemDescription_SetMembership_NotRich", "\nPart of the {0} set.");
|
||||
return string.Format(format, set);
|
||||
}
|
||||
}
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static string GetCosmeticSeason(string seasonNumber)
|
||||
{
|
||||
string s = seasonNumber.Substring("Cosmetics.Filter.Season.".Length);
|
||||
int number = int.Parse(s);
|
||||
if (number == 10)
|
||||
s = "X";
|
||||
|
||||
string season = Localizations.GetLocalization("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}");
|
||||
string introduced = Localizations.GetLocalization("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in <SeasonText>{0}</>.")
|
||||
.Replace("<SeasonText>", string.Empty).Replace("</>", string.Empty);
|
||||
if (number > 10)
|
||||
{
|
||||
string chapter = Localizations.GetLocalization("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}");
|
||||
string chapterFormat = Localizations.GetLocalization("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}");
|
||||
string d = string.Format(chapterFormat, string.Format(chapter, number / 10 + 1), string.Format(season, s[^1..]));
|
||||
return string.Format(introduced, d);
|
||||
}
|
||||
else return string.Format(introduced, string.Format(season, s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,161 +0,0 @@
|
|||
using FModel.Creator.Bases;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
using FModel.Properties;
|
||||
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Texts
|
||||
{
|
||||
static class Helper
|
||||
{
|
||||
public class Line
|
||||
{
|
||||
public string Value { get; set; }
|
||||
public float Width { get; set; }
|
||||
}
|
||||
|
||||
public static void DrawCenteredMultilineText(SKCanvas canvas, string text, int maxLineCount, IBase icon, ETextSide side, SKRect area, SKPaint paint)
|
||||
=> DrawCenteredMultilineText(canvas, text, maxLineCount, icon.Width, icon.Margin, side, area, paint);
|
||||
public static void DrawCenteredMultilineText(SKCanvas canvas, string text, int maxLineCount, int size, int margin, ETextSide side, SKRect area, SKPaint paint)
|
||||
{
|
||||
float lineHeight = paint.TextSize * 1.2f;
|
||||
List<Line> lines = SplitLines(text, paint, area.Width - margin);
|
||||
|
||||
if (lines == null)
|
||||
return;
|
||||
if (lines.Count <= maxLineCount)
|
||||
maxLineCount = lines.Count;
|
||||
|
||||
float height = maxLineCount * lineHeight;
|
||||
float y = area.MidY - height / 2;
|
||||
SKShaper shaper = (ELanguage)Settings.Default.AssetsLanguage == ELanguage.Arabic ? new SKShaper(paint.Typeface) : null;
|
||||
|
||||
for (int i = 0; i < maxLineCount; i++)
|
||||
{
|
||||
Line line = lines[i];
|
||||
|
||||
y += lineHeight;
|
||||
float x = side switch
|
||||
{
|
||||
ETextSide.Center => area.MidX - line.Width / 2,
|
||||
ETextSide.Right => size - margin - line.Width,
|
||||
ETextSide.Left => margin,
|
||||
_ => area.MidX - line.Width / 2
|
||||
};
|
||||
|
||||
string lineText = line.Value.TrimEnd();
|
||||
|
||||
if (shaper == null)
|
||||
{
|
||||
canvas.DrawText(lineText, x, y, paint);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (side == ETextSide.Center)
|
||||
{
|
||||
SKShaper.Result shapedText = shaper.Shape(lineText, paint);
|
||||
float shapedTextWidth = shapedText.Points[^1].X + paint.TextSize / 2f;
|
||||
canvas.DrawShapedText(shaper, lineText, (area.Width - shapedTextWidth) / 2f, y, paint);
|
||||
}
|
||||
else
|
||||
{
|
||||
canvas.DrawShapedText(shaper, lineText, x, y, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawMultilineText(SKCanvas canvas, string text, int size, int margin, ETextSide side, SKRect area, SKPaint paint, out int yPos)
|
||||
{
|
||||
float lineHeight = paint.TextSize * 1.2f;
|
||||
List<Line> lines = SplitLines(text, paint, area.Width);
|
||||
if (lines == null)
|
||||
{
|
||||
yPos = (int)area.Top;
|
||||
return;
|
||||
}
|
||||
|
||||
float y = area.Top;
|
||||
SKShaper shaper = (ELanguage)Settings.Default.AssetsLanguage == ELanguage.Arabic ? new SKShaper(paint.Typeface) : null;
|
||||
|
||||
for (int i = 0; i < lines.Count; i++)
|
||||
{
|
||||
var line = lines[i];
|
||||
float x = side switch
|
||||
{
|
||||
ETextSide.Center => area.MidX - line.Width / 2,
|
||||
ETextSide.Right => size - margin - line.Width,
|
||||
ETextSide.Left => area.Left,
|
||||
_ => area.MidX - line.Width / 2
|
||||
};
|
||||
|
||||
string lineText = line.Value.TrimEnd();
|
||||
|
||||
if (shaper == null)
|
||||
{
|
||||
canvas.DrawText(lineText, x, y, paint);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (side == ETextSide.Center)
|
||||
{
|
||||
SKShaper.Result shapedText = shaper.Shape(lineText, paint);
|
||||
float shapedTextWidth = shapedText.Points[^1].X + paint.TextSize / 2f;
|
||||
canvas.DrawShapedText(shaper, lineText, (area.Width - shapedTextWidth) / 2f, y, paint);
|
||||
}
|
||||
else
|
||||
{
|
||||
canvas.DrawShapedText(shaper, lineText, x, y, paint);
|
||||
}
|
||||
}
|
||||
|
||||
y += lineHeight;
|
||||
}
|
||||
yPos = (int)area.Top + ((int)lineHeight * lines.Count);
|
||||
}
|
||||
|
||||
public static List<Line> SplitLines(string text, SKPaint paint, float maxWidth)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return null;
|
||||
|
||||
float spaceWidth = paint.MeasureText(" ");
|
||||
string[] lines = text.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
List<Line> ret = new List<Line>(lines.Length);
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(lines[i]))
|
||||
continue;
|
||||
|
||||
float width = 0;
|
||||
var lineResult = new StringBuilder();
|
||||
string[] words = lines[i].Split(' ');
|
||||
foreach (var word in words)
|
||||
{
|
||||
float wordWidth = paint.MeasureText(word);
|
||||
float wordWithSpaceWidth = wordWidth + spaceWidth;
|
||||
string wordWithSpace = word + " ";
|
||||
|
||||
if (width + wordWidth > maxWidth)
|
||||
{
|
||||
ret.Add(new Line { Value = lineResult.ToString(), Width = width });
|
||||
lineResult = new StringBuilder(wordWithSpace);
|
||||
width = wordWithSpaceWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
lineResult.Append(wordWithSpace);
|
||||
width += wordWithSpaceWidth;
|
||||
}
|
||||
}
|
||||
ret.Add(new Line { Value = lineResult.ToString(), Width = width });
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,300 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using FModel.Creator.Bases;
|
||||
using FModel.PakReader;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.Objects;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator.Texts
|
||||
{
|
||||
static class Text
|
||||
{
|
||||
public static Typefaces TypeFaces = new Typefaces();
|
||||
private const int _STARTER_TEXT_POSITION = 380;
|
||||
private static int _BOTTOM_TEXT_SIZE = 15;
|
||||
private static int _NAME_TEXT_SIZE = 45;
|
||||
|
||||
public static string GetTextPropertyBase(TextProperty t)
|
||||
{
|
||||
if (t.Value is { } text)
|
||||
if (text.Text is FTextHistory.None n)
|
||||
return n.CultureInvariantString;
|
||||
else if (text.Text is FTextHistory.Base b)
|
||||
return b.SourceString.Replace("<Emphasized>", string.Empty).Replace("</>", string.Empty);
|
||||
else if (text.Text is FTextHistory.StringTableEntry s)
|
||||
{
|
||||
Package p = Utils.GetPropertyPakPackage(s.TableId.String);
|
||||
if (p.HasExport() && !p.Equals(default))
|
||||
{
|
||||
var table = p.GetExport<UStringTable>();
|
||||
if (table != null)
|
||||
{
|
||||
if (table.TryGetValue("StringTable", out var v1) && v1 is FStringTable stringTable &&
|
||||
stringTable.KeysToMetadata.TryGetValue(stringTable.TableNamespace, out var v2) && v2 is Dictionary<string, string> dico &&
|
||||
dico.TryGetValue(s.Key, out var ret))
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
public static string GetTextPropertyBase(ArrayProperty a)
|
||||
{
|
||||
if (a.Value.Length > 0 && a.Value[0] is TextProperty t)
|
||||
return GetTextPropertyBase(t);
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static (string, string, string) GetTextPropertyBases(TextProperty t)
|
||||
{
|
||||
if (t.Value is { } text && text.Text is FTextHistory.Base b)
|
||||
return (b.Namespace, b.Key, b.SourceString);
|
||||
return (string.Empty, string.Empty, string.Empty);
|
||||
}
|
||||
|
||||
public static string GetMaxStackSize(StructProperty maxStackSize)
|
||||
{
|
||||
if (maxStackSize.Value is UObject o1)
|
||||
{
|
||||
if (o1.TryGetValue("Value", out var c) && c is FloatProperty value && value.Value != -1) // old way
|
||||
return $"MaxStackSize : {value.Value}";
|
||||
|
||||
if (
|
||||
o1.TryGetValue("Curve", out var c1) && c1 is StructProperty curve && curve.Value is UObject o2 &&
|
||||
o2.TryGetValue("CurveTable", out var c2) && c2 is ObjectProperty curveTable &&
|
||||
o2.TryGetValue("RowName", out var c3) && c3 is NameProperty rowName) // new way
|
||||
{
|
||||
Package p = Utils.GetPropertyPakPackage(curveTable.Value.Resource.OuterIndex.Resource.ObjectName.String);
|
||||
if (p.HasExport() && !p.Equals(default))
|
||||
{
|
||||
var table = p.GetExport<UCurveTable>();
|
||||
if (table != null)
|
||||
{
|
||||
if (table.TryGetValue(rowName.Value.String, out var v1) && v1 is UObject maxStackAmount &&
|
||||
maxStackAmount.TryGetValue("Keys", out var v2) && v2 is ArrayProperty keys &&
|
||||
keys.Value.Length > 0 && (keys.Value[0] as StructProperty)?.Value is FSimpleCurveKey amount &&
|
||||
amount.KeyValue != -1)
|
||||
{
|
||||
return $"MaxStackSize : {amount.KeyValue}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static string GetXpRewardAmount(StructProperty xpRewardAmount)
|
||||
{
|
||||
if (xpRewardAmount.Value is UObject o1)
|
||||
{
|
||||
if (
|
||||
o1.TryGetValue("Curve", out var c1) && c1 is StructProperty curve && curve.Value is UObject o2 &&
|
||||
o2.TryGetValue("CurveTable", out var c2) && c2 is ObjectProperty curveTable &&
|
||||
o2.TryGetValue("RowName", out var c3) && c3 is NameProperty rowName) // new way
|
||||
{
|
||||
Package p = Utils.GetPropertyPakPackage(curveTable.Value.Resource.OuterIndex.Resource.ObjectName.String);
|
||||
if (p.HasExport() && !p.Equals(default))
|
||||
{
|
||||
var table = p.GetExport<UCurveTable>();
|
||||
if (table != null)
|
||||
{
|
||||
if (table.TryGetValue(rowName.Value.String, out var v1) && v1 is UObject maxStackAmount &&
|
||||
maxStackAmount.TryGetValue("Keys", out var v2) && v2 is ArrayProperty keys &&
|
||||
keys.Value.Length > 0 && (keys.Value[0] as StructProperty)?.Value is FSimpleCurveKey amount &&
|
||||
amount.KeyValue != -1)
|
||||
{
|
||||
return $"{amount.KeyValue} Xp";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static void DrawBackground(SKCanvas c, IBase icon)
|
||||
{
|
||||
switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign)
|
||||
{
|
||||
case EIconDesign.Flat:
|
||||
{
|
||||
var pathBottom = new SKPath { FillType = SKPathFillType.EvenOdd };
|
||||
pathBottom.MoveTo(icon.Margin, icon.Height - icon.Margin);
|
||||
pathBottom.LineTo(icon.Margin, icon.Height - icon.Margin - icon.Height / 17 * 2.5f);
|
||||
pathBottom.LineTo(icon.Width - icon.Margin, icon.Height - icon.Margin - icon.Height / 17 * 4.5f);
|
||||
pathBottom.LineTo(icon.Width - icon.Margin, icon.Height - icon.Margin);
|
||||
pathBottom.Close();
|
||||
c.DrawPath(pathBottom, new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Color = new SKColor(0, 0, 50, 75),
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
c.DrawRect(
|
||||
new SKRect(icon.Margin, _STARTER_TEXT_POSITION, icon.Width - icon.Margin, icon.Height - icon.Margin),
|
||||
new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Color = new SKColor(0, 0, 50, 75),
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawDisplayName(SKCanvas c, IBase icon)
|
||||
{
|
||||
_NAME_TEXT_SIZE = 45;
|
||||
string text = icon.DisplayName;
|
||||
|
||||
if (string.IsNullOrEmpty(text)) return;
|
||||
|
||||
SKTextAlign side = SKTextAlign.Center;
|
||||
int x = icon.Width / 2;
|
||||
int y = _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE;
|
||||
switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign)
|
||||
{
|
||||
case EIconDesign.Mini:
|
||||
{
|
||||
_NAME_TEXT_SIZE = 47;
|
||||
text = text.ToUpperInvariant();
|
||||
break;
|
||||
}
|
||||
case EIconDesign.Flat:
|
||||
{
|
||||
_NAME_TEXT_SIZE = 47;
|
||||
side = SKTextAlign.Right;
|
||||
x = icon.Width - icon.Margin * 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SKPaint namePaint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Typeface = TypeFaces.DisplayNameTypeface,
|
||||
TextSize = _NAME_TEXT_SIZE,
|
||||
Color = SKColors.White,
|
||||
TextAlign = side
|
||||
};
|
||||
|
||||
if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic)
|
||||
{
|
||||
SKShaper shaper = new SKShaper(namePaint.Typeface);
|
||||
float shapedTextWidth;
|
||||
|
||||
while (true)
|
||||
{
|
||||
SKShaper.Result shapedText = shaper.Shape(text, namePaint);
|
||||
shapedTextWidth = shapedText.Points[^1].X + namePaint.TextSize / 2f;
|
||||
|
||||
if (shapedTextWidth > icon.Width - icon.Margin * 2)
|
||||
{
|
||||
namePaint.TextSize = _NAME_TEXT_SIZE -= 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
c.DrawShapedText(shaper, text, side == SKTextAlign.Right ? (x - shapedTextWidth) : ((icon.Width - shapedTextWidth) / 2f), y, namePaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
// resize if too long
|
||||
while (namePaint.MeasureText(text) > icon.Width - icon.Margin * 2)
|
||||
{
|
||||
namePaint.TextSize = _NAME_TEXT_SIZE -= 1;
|
||||
}
|
||||
|
||||
c.DrawText(text, x, y, namePaint);
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawDescription(SKCanvas c, IBase icon)
|
||||
{
|
||||
_BOTTOM_TEXT_SIZE = 15;
|
||||
string text = icon.Description;
|
||||
|
||||
if (string.IsNullOrEmpty(text)) return;
|
||||
|
||||
int maxLine = 4;
|
||||
ETextSide side = ETextSide.Center;
|
||||
switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign)
|
||||
{
|
||||
case EIconDesign.Mini:
|
||||
{
|
||||
maxLine = 5;
|
||||
_BOTTOM_TEXT_SIZE = icon.Margin;
|
||||
text = text.ToUpper();
|
||||
break;
|
||||
}
|
||||
case EIconDesign.Flat:
|
||||
{
|
||||
side = ETextSide.Right;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SKPaint descriptionPaint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Typeface = TypeFaces.DescriptionTypeface,
|
||||
TextSize = 13,
|
||||
Color = SKColors.White,
|
||||
};
|
||||
|
||||
// wrap if too long
|
||||
Helper.DrawCenteredMultilineText(c, text, maxLine, icon, side,
|
||||
new SKRect(icon.Margin, _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE, icon.Width - icon.Margin, icon.Height - _BOTTOM_TEXT_SIZE),
|
||||
descriptionPaint);
|
||||
}
|
||||
|
||||
public static void DrawToBottom(SKCanvas c, BaseIcon icon, ETextSide side, string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text)) return;
|
||||
|
||||
SKPaint shortDescriptionPaint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
FilterQuality = SKFilterQuality.High,
|
||||
Typeface = side == ETextSide.Left ? TypeFaces.BottomDefaultTypeface ?? TypeFaces.DisplayNameTypeface : TypeFaces.BottomDefaultTypeface ?? TypeFaces.DefaultTypeface,
|
||||
TextSize = TypeFaces.BottomDefaultTypeface == null ? 15 : 13,
|
||||
Color = SKColors.White,
|
||||
TextAlign = side == ETextSide.Left ? SKTextAlign.Left : SKTextAlign.Right,
|
||||
};
|
||||
|
||||
if (side == ETextSide.Left)
|
||||
{
|
||||
if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic)
|
||||
{
|
||||
shortDescriptionPaint.TextSize -= 4f;
|
||||
SKShaper shaper = new SKShaper(shortDescriptionPaint.Typeface);
|
||||
c.DrawShapedText(shaper, text, icon.Margin * 2.5f, icon.Size - icon.Margin * 2.5f - shortDescriptionPaint.TextSize * .5f /* ¯\_(ツ)_/¯ */, shortDescriptionPaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
c.DrawText(text, icon.Margin * 2.5f, icon.Size - icon.Margin * 2.5f, shortDescriptionPaint);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
c.DrawText(text, icon.Size - icon.Margin * 2.5f, icon.Size - icon.Margin * 2.5f, shortDescriptionPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,243 +0,0 @@
|
|||
using FModel.Utils;
|
||||
using FModel.ViewModels.DataGrid;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace FModel.Creator.Texts
|
||||
{
|
||||
public class Typefaces
|
||||
{
|
||||
#pragma warning disable IDE0051
|
||||
private const string _FORTNITE_BASE_PATH = "/Game/UI/Foundation/Fonts/";
|
||||
private const string _ASIA_ERINM = "AsiaERINM"; // korean fortnite
|
||||
private const string _BURBANK_BIG_CONDENSED_BLACK = "BurbankBigCondensed-Black"; // russian
|
||||
private readonly Uri _BURBANK_BIG_CONDENSED_BOLD = new Uri("pack://application:,,,/Resources/BurbankBigCondensed-Bold.ttf"); // other languages fortnite unofficial
|
||||
private const string _BURBANK_BIG_REGULAR_BLACK = "BurbankBigRegular-Black";
|
||||
private const string _BURBANK_BIG_REGULAR_BOLD = "BurbankBigRegular-Bold"; // official fortnite ig
|
||||
private const string _BURBANK_SMALL_MEDIUM = "BurbankSmall-Medium";
|
||||
private const string _DROID_SANS_FORTNITE_SUBSET = "DroidSans-Fortnite-Subset";
|
||||
private const string _NIS_JYAU = "NIS_JYAU"; // japanese fortnite
|
||||
private const string _NOTO_COLOR_EMOJI = "NotoColorEmoji";
|
||||
private const string _NOTO_SANS_BOLD = "NotoSans-Bold";
|
||||
private const string _NOTO_SANS_FORTNITE_BOLD = "NotoSans-Fortnite-Bold";
|
||||
private const string _NOTO_SANS_FORTNITE_ITALIC = "NotoSans-Fortnite-Italic";
|
||||
private const string _NOTO_SANS_FORTNITE_REGULAR = "NotoSans-Fortnite-Regular";
|
||||
private const string _NOTO_SANS_ITALIC = "NotoSans-Italic";
|
||||
private const string _NOTO_SANS_REGULAR = "NotoSans-Regular";
|
||||
private const string _NOTO_SANS_ARABIC_BLACK = "NotoSansArabic-Black"; // arabic fortnite
|
||||
private const string _NOTO_SANS_ARABIC_BOLD = "NotoSansArabic-Bold";
|
||||
private const string _NOTO_SANS_ARABIC_REGULAR = "NotoSansArabic-Regular";
|
||||
private const string _NOTO_SANS_JP_BOLD = "NotoSansJP-Bold";
|
||||
private const string _NOTO_SANS_KR_REGULAR = "NotoSansKR-Regular";
|
||||
private const string _NOTO_SANS_SC_BLACK = "NotoSansSC-Black"; // simplified chinese fortnite
|
||||
private const string _NOTO_SANS_SC_REGULAR = "NotoSansSC-Regular";
|
||||
private const string _NOTO_SANS_TC_BLACK = "NotoSansTC-Black"; // traditional chinese fortnite
|
||||
private const string _NOTO_SANS_TC_REGULAR = "NotoSansTC-Regular";
|
||||
private const string _BURBANK_SMALL_BLACK = "burbanksmall-black";
|
||||
private const string _BURBANK_SMALL_BOLD = "burbanksmall-bold";
|
||||
|
||||
private const string _VALORANT_BASE_PATH = "/Game/";
|
||||
private const string _TUNGSTEN_BOLD = "Tungsten-Bold";
|
||||
private const string _DINNEXT_LTARABIC_HEAVY = "UI/Fonts/FinalFonts/LOCFonts/DIN_Next_Arabic/DINNextLTArabic-Heavy";
|
||||
private const string _TUNGSTEN_CYRILLIC = "UI/Fonts/FinalFonts/LOCFonts/LOC_Tungsten/Cyrillic_Tungsten";
|
||||
private const string _TUNGSTEN_JAPANESE = "UI/Fonts/FinalFonts/LOCFonts/LOC_Tungsten/JP_Tungsten";
|
||||
private const string _TUNGSTEN_KOREAN = "UI/Fonts/FinalFonts/LOCFonts/LOC_Tungsten/KR_Tungsten";
|
||||
private const string _TUNGSTEN_SIMPLIFIED_CHINESE = "UI/Fonts/FinalFonts/LOCFonts/LOC_Tungsten/zh-CN_Tungsten";
|
||||
private const string _TUNGSTEN_TRADITIONAL_CHINESE = "UI/Fonts/FinalFonts/LOCFonts/LOC_Tungsten/zh-TW_Tungsten";
|
||||
private const string _DINNEXT_W1G_LIGHT = "UI/Fonts/FinalFonts/DINNextW1G-Light";
|
||||
private const string _DINNEXT_LTARABIC_LIGHT = "UI/Fonts/FinalFonts/LOCFonts/DIN_Next_Arabic/DINNextLTArabic-Light";
|
||||
private const string _NOTOSANS_CJK_LIGHT = "UI/Fonts/FinalFonts/LOCFonts/CJK/NotoSansCJK-Light"; // chinese, japanese, korean
|
||||
private const string _DINNEXT_W1G_BOLD = "UI/Fonts/FinalFonts/DINNextW1G-Bold";
|
||||
|
||||
private const string _SPELLBREAK_BASE_PATH = "/Game/UI/Fonts/";
|
||||
private const string _MONTSERRAT_SEMIBOLD = "Montserrat-Semibold";
|
||||
private const string _MONTSERRAT_SEMIBOLD_ITALIC = "Montserrat-SemiBoldItalic";
|
||||
private const string _NANUM_GOTHIC = "NanumGothic";
|
||||
private const string _QUADRAT_BOLD = "Quadrat_Bold";
|
||||
private const string _SEGOE_BOLD_ITALIC = "Segoe_Bold_Italic";
|
||||
|
||||
private const string _BATTLE_BREAKERS_BASE_PATH = "/Game/UMG/Fonts/Faces/";
|
||||
private const string _HEMIHEAD426 = "HemiHead426";
|
||||
private const string _ROBOTO_BOLD = "Roboto-Bold";
|
||||
private const string _ROBOTO_BOLD_ALL_CAPS = "Roboto-BoldAllCaps";
|
||||
private const string _ROBOTO_REGULAR = "Roboto-Regular";
|
||||
private const string _NOTO_SANS_JP_REGULAR = "NotoSansJP-Regular";
|
||||
private const string _LATO_LIGHT = "Lato-Light";
|
||||
private const string _LATO_MEDIUM = "Lato-Medium";
|
||||
private const string _LATO_BLACK_ITALIC = "Lato-BlackItalic";
|
||||
private const string _LATO_BLACK = "Lato-Black";
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
public SKTypeface DefaultTypeface; // used as default font for all untranslated strings (item source, ...)
|
||||
public SKTypeface BundleDefaultTypeface; // used for the last folder string
|
||||
public SKTypeface BottomDefaultTypeface;
|
||||
public SKTypeface DisplayNameTypeface;
|
||||
public SKTypeface DescriptionTypeface;
|
||||
public SKTypeface BundleDisplayNameTypeface;
|
||||
|
||||
public Typefaces()
|
||||
{
|
||||
DefaultTypeface = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream);
|
||||
if (Globals.Game.ActualGame == EGame.Fortnite)
|
||||
{
|
||||
ArraySegment<byte>[] t = Utils.GetPropertyArraySegmentByte(_FORTNITE_BASE_PATH + _BURBANK_BIG_CONDENSED_BLACK);
|
||||
if (t != null && t.Length == 3)
|
||||
{
|
||||
if (t[0].Array != null)
|
||||
BundleDefaultTypeface = SKTypeface.FromStream(t[0].AsStream());
|
||||
if (BundleDefaultTypeface != null && t[2].Array != null)
|
||||
BundleDefaultTypeface = SKTypeface.FromStream(t[2].AsStream());
|
||||
}
|
||||
else BundleDefaultTypeface = DefaultTypeface;
|
||||
|
||||
string namePath = _FORTNITE_BASE_PATH + (
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _ASIA_ERINM :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _BURBANK_BIG_CONDENSED_BLACK :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NIS_JYAU :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _NOTO_SANS_ARABIC_BLACK :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _NOTO_SANS_TC_BLACK :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_SC_BLACK :
|
||||
string.Empty);
|
||||
if (!namePath.Equals(_FORTNITE_BASE_PATH))
|
||||
{
|
||||
t = Utils.GetPropertyArraySegmentByte(namePath);
|
||||
if (t != null && t.Length == 3)
|
||||
{
|
||||
if (t[0].Array != null)
|
||||
DisplayNameTypeface = SKTypeface.FromStream(t[0].AsStream());
|
||||
if (DisplayNameTypeface != null && t[2].Array != null)
|
||||
DisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream());
|
||||
}
|
||||
}
|
||||
else DisplayNameTypeface = DefaultTypeface;
|
||||
|
||||
string bottomPath = _FORTNITE_BASE_PATH + (
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? string.Empty :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? string.Empty :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? string.Empty :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? string.Empty :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? string.Empty :
|
||||
_BURBANK_SMALL_BOLD);
|
||||
t = Utils.GetPropertyArraySegmentByte(bottomPath);
|
||||
if (t != null && t.Length == 3)
|
||||
{
|
||||
if (t[0].Array != null)
|
||||
BottomDefaultTypeface = SKTypeface.FromStream(t[0].AsStream());
|
||||
if (BottomDefaultTypeface != null && t[2].Array != null)
|
||||
BottomDefaultTypeface = SKTypeface.FromStream(t[2].AsStream());
|
||||
}
|
||||
|
||||
string descriptionPath = _FORTNITE_BASE_PATH + (
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _NOTO_SANS_KR_REGULAR :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NOTO_SANS_JP_BOLD :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _NOTO_SANS_ARABIC_REGULAR :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _NOTO_SANS_TC_REGULAR :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_SC_REGULAR :
|
||||
_NOTO_SANS_REGULAR);
|
||||
t = Utils.GetPropertyArraySegmentByte(descriptionPath);
|
||||
if (t != null && t.Length == 3)
|
||||
{
|
||||
if (t[0].Array != null)
|
||||
DescriptionTypeface = SKTypeface.FromStream(t[0].AsStream());
|
||||
if (DescriptionTypeface != null && t[2].Array != null)
|
||||
DescriptionTypeface = SKTypeface.FromStream(t[2].AsStream());
|
||||
}
|
||||
else DescriptionTypeface = DefaultTypeface;
|
||||
|
||||
string bundleNamePath = _FORTNITE_BASE_PATH + (
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _ASIA_ERINM :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _BURBANK_BIG_CONDENSED_BLACK :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NIS_JYAU :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _NOTO_SANS_ARABIC_BLACK :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _NOTO_SANS_TC_BLACK :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_SC_BLACK :
|
||||
string.Empty);
|
||||
if (!bundleNamePath.Equals(_FORTNITE_BASE_PATH))
|
||||
{
|
||||
t = Utils.GetPropertyArraySegmentByte(bundleNamePath);
|
||||
if (t != null && t.Length == 3)
|
||||
{
|
||||
if (t[0].Array != null)
|
||||
BundleDisplayNameTypeface = SKTypeface.FromStream(t[0].AsStream());
|
||||
if (BundleDisplayNameTypeface != null && t[2].Array != null)
|
||||
BundleDisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream());
|
||||
}
|
||||
}
|
||||
else BundleDisplayNameTypeface = BundleDefaultTypeface;
|
||||
}
|
||||
else if (Globals.Game.ActualGame == EGame.Valorant)
|
||||
{
|
||||
string namePath = _VALORANT_BASE_PATH + (
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _TUNGSTEN_KOREAN :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _TUNGSTEN_CYRILLIC :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _TUNGSTEN_JAPANESE :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _DINNEXT_LTARABIC_HEAVY :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _TUNGSTEN_TRADITIONAL_CHINESE :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _TUNGSTEN_SIMPLIFIED_CHINESE :
|
||||
_TUNGSTEN_BOLD);
|
||||
ArraySegment<byte>[] t = Utils.GetPropertyArraySegmentByte(namePath);
|
||||
if (t != null && t.Length == 3 && t[2].Array != null)
|
||||
DisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream());
|
||||
else DisplayNameTypeface = DefaultTypeface;
|
||||
|
||||
string descriptionPath = _VALORANT_BASE_PATH + (
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _NOTOSANS_CJK_LIGHT :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NOTOSANS_CJK_LIGHT :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _DINNEXT_LTARABIC_LIGHT :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _NOTOSANS_CJK_LIGHT :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTOSANS_CJK_LIGHT :
|
||||
_DINNEXT_W1G_LIGHT);
|
||||
t = Utils.GetPropertyArraySegmentByte(descriptionPath);
|
||||
if (t != null && t.Length == 3 && t[2].Array != null)
|
||||
DescriptionTypeface = SKTypeface.FromStream(t[2].AsStream());
|
||||
else DescriptionTypeface = DefaultTypeface;
|
||||
|
||||
t = Utils.GetPropertyArraySegmentByte(_VALORANT_BASE_PATH + _DINNEXT_W1G_BOLD);
|
||||
if (t != null && t.Length == 3 && t[2].Array != null)
|
||||
BundleDefaultTypeface = SKTypeface.FromStream(t[2].AsStream());
|
||||
else BundleDefaultTypeface = DefaultTypeface;
|
||||
}
|
||||
else if (Globals.Game.ActualGame == EGame.Spellbreak)
|
||||
{
|
||||
ArraySegment<byte>[] t = Utils.GetPropertyArraySegmentByte(_SPELLBREAK_BASE_PATH + _QUADRAT_BOLD);
|
||||
if (t != null && t.Length == 3 && t[2].Array != null)
|
||||
DisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream());
|
||||
else DisplayNameTypeface = DefaultTypeface;
|
||||
|
||||
t = Utils.GetPropertyArraySegmentByte(_SPELLBREAK_BASE_PATH + _MONTSERRAT_SEMIBOLD);
|
||||
if (t != null && t.Length == 3 && t[2].Array != null)
|
||||
DescriptionTypeface = SKTypeface.FromStream(t[2].AsStream());
|
||||
else DescriptionTypeface = DefaultTypeface;
|
||||
}
|
||||
else if (Globals.Game.ActualGame == EGame.BattleBreakers)
|
||||
{
|
||||
string namePath = _BATTLE_BREAKERS_BASE_PATH + (
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _NOTO_SANS_KR_REGULAR :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _LATO_BLACK :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NOTO_SANS_JP_REGULAR :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_SC_REGULAR :
|
||||
_HEMIHEAD426);
|
||||
ArraySegment<byte>[] t = Utils.GetPropertyArraySegmentByte(namePath);
|
||||
if (t != null && t.Length == 3 && t[2].Array != null)
|
||||
DisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream());
|
||||
else DisplayNameTypeface = DefaultTypeface;
|
||||
|
||||
string descriptionPath = _BATTLE_BREAKERS_BASE_PATH + (
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _NOTO_SANS_KR_REGULAR :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _LATO_BLACK :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NOTO_SANS_JP_REGULAR :
|
||||
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_SC_REGULAR :
|
||||
_HEMIHEAD426);
|
||||
t = Utils.GetPropertyArraySegmentByte(descriptionPath);
|
||||
if (t != null && t.Length == 3 && t[2].Array != null)
|
||||
DescriptionTypeface = SKTypeface.FromStream(t[2].AsStream());
|
||||
else DescriptionTypeface = DefaultTypeface;
|
||||
}
|
||||
}
|
||||
|
||||
public bool NeedReload(bool forceReload) => forceReload ?
|
||||
DataGridVm.dataGridViewModel.Count > 0 : //reload only if at least one pak is loaded
|
||||
DataGridVm.dataGridViewModel.Count > 0 && (BundleDefaultTypeface == DefaultTypeface && DisplayNameTypeface == DefaultTypeface && DescriptionTypeface == DefaultTypeface && BundleDisplayNameTypeface == BundleDefaultTypeface);
|
||||
}
|
||||
}
|
||||
224
FModel/Creator/Typefaces.cs
Normal file
224
FModel/Creator/Typefaces.cs
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator
|
||||
{
|
||||
public class Typefaces
|
||||
{
|
||||
private readonly Uri _BURBANK_BIG_CONDENSED_BOLD = new Uri("pack://application:,,,/Resources/BurbankBigCondensed-Bold.ttf");
|
||||
private const string _EXT = ".ufont";
|
||||
|
||||
// FortniteGame
|
||||
private const string _FORTNITE_BASE_PATH = "/Game/UI/Foundation/Fonts/";
|
||||
private const string _ASIA_ERINM = "AsiaERINM"; // korean fortnite
|
||||
private const string _BURBANK_BIG_CONDENSED_BLACK = "BurbankBigCondensed-Black"; // russian
|
||||
private const string _BURBANK_BIG_REGULAR_BLACK = "BurbankBigRegular-Black";
|
||||
private const string _BURBANK_BIG_REGULAR_BOLD = "BurbankBigRegular-Bold"; // official fortnite ig
|
||||
private const string _BURBANK_SMALL_MEDIUM = "BurbankSmall-Medium";
|
||||
private const string _DROID_SANS_FORTNITE_SUBSET = "DroidSans-Fortnite-Subset";
|
||||
private const string _NIS_JYAU = "NIS_JYAU"; // japanese fortnite
|
||||
private const string _NOTO_COLOR_EMOJI = "NotoColorEmoji";
|
||||
private const string _NOTO_SANS_BOLD = "NotoSans-Bold";
|
||||
private const string _NOTO_SANS_FORTNITE_BOLD = "NotoSans-Fortnite-Bold";
|
||||
private const string _NOTO_SANS_FORTNITE_ITALIC = "NotoSans-Fortnite-Italic";
|
||||
private const string _NOTO_SANS_FORTNITE_REGULAR = "NotoSans-Fortnite-Regular";
|
||||
private const string _NOTO_SANS_ITALIC = "NotoSans-Italic";
|
||||
private const string _NOTO_SANS_REGULAR = "NotoSans-Regular";
|
||||
private const string _NOTO_SANS_ARABIC_BLACK = "NotoSansArabic-Black"; // arabic fortnite
|
||||
private const string _NOTO_SANS_ARABIC_BOLD = "NotoSansArabic-Bold";
|
||||
private const string _NOTO_SANS_ARABIC_REGULAR = "NotoSansArabic-Regular";
|
||||
private const string _NOTO_SANS_JP_BOLD = "NotoSansJP-Bold";
|
||||
private const string _NOTO_SANS_KR_REGULAR = "NotoSansKR-Regular";
|
||||
private const string _NOTO_SANS_SC_BLACK = "NotoSansSC-Black"; // simplified chinese fortnite
|
||||
private const string _NOTO_SANS_SC_REGULAR = "NotoSansSC-Regular";
|
||||
private const string _NOTO_SANS_TC_BLACK = "NotoSansTC-Black"; // traditional chinese fortnite
|
||||
private const string _NOTO_SANS_TC_REGULAR = "NotoSansTC-Regular";
|
||||
private const string _BURBANK_SMALL_BLACK = "burbanksmall-black";
|
||||
private const string _BURBANK_SMALL_BOLD = "burbanksmall-bold";
|
||||
|
||||
// WorldExplorers
|
||||
private const string _BATTLE_BREAKERS_BASE_PATH = "/Game/UMG/Fonts/Faces/";
|
||||
private const string _HEMIHEAD426 = "HemiHead426";
|
||||
private const string _NOTO_SANS_JP_REGULAR = "NotoSansJP-Regular";
|
||||
private const string _LATO_BLACK = "Lato-Black";
|
||||
private const string _LATO_BLACK_ITALIC = "Lato-BlackItalic";
|
||||
private const string _LATO_LIGHT = "Lato-Light";
|
||||
private const string _LATO_MEDIUM = "Lato-Medium";
|
||||
|
||||
private readonly CUE4ParseViewModel _viewModel;
|
||||
|
||||
public readonly SKTypeface Default; // used as a fallback font for all untranslated strings (item source, ...)
|
||||
public readonly SKTypeface DisplayName;
|
||||
public readonly SKTypeface Description;
|
||||
public readonly SKTypeface Bottom; // must be null for non-latin base languages
|
||||
public readonly SKTypeface Bundle;
|
||||
public readonly SKTypeface BundleNumber;
|
||||
|
||||
public Typefaces(CUE4ParseViewModel viewModel)
|
||||
{
|
||||
byte[] data;
|
||||
_viewModel = viewModel;
|
||||
var language = UserSettings.Default.AssetLanguage;
|
||||
Default = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream);
|
||||
|
||||
switch (viewModel.Game)
|
||||
{
|
||||
case FGame.FortniteGame:
|
||||
{
|
||||
var namePath = _FORTNITE_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _ASIA_ERINM,
|
||||
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
|
||||
ELanguage.Japanese => _NIS_JYAU,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||
_ => string.Empty
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(namePath + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) {Position = 0};
|
||||
DisplayName = SKTypeface.FromStream(m);
|
||||
}
|
||||
else DisplayName = Default;
|
||||
|
||||
|
||||
var descriptionPath = _FORTNITE_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _NOTO_SANS_KR_REGULAR,
|
||||
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_REGULAR,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_REGULAR,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_REGULAR,
|
||||
_ => _NOTO_SANS_REGULAR
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(descriptionPath + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) {Position = 0};
|
||||
Description = SKTypeface.FromStream(m);
|
||||
}
|
||||
else Description = Default;
|
||||
|
||||
|
||||
var bottomPath = _FORTNITE_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => string.Empty,
|
||||
ELanguage.Japanese => string.Empty,
|
||||
ELanguage.Arabic => string.Empty,
|
||||
ELanguage.TraditionalChinese => string.Empty,
|
||||
ELanguage.Chinese => string.Empty,
|
||||
_ => _BURBANK_SMALL_BOLD
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(bottomPath + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) {Position = 0};
|
||||
Bottom = SKTypeface.FromStream(m);
|
||||
}
|
||||
// else keep it null
|
||||
|
||||
|
||||
if (viewModel.Provider.TrySaveAsset(_FORTNITE_BASE_PATH + _BURBANK_BIG_CONDENSED_BLACK + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) {Position = 0};
|
||||
BundleNumber = SKTypeface.FromStream(m);
|
||||
}
|
||||
else BundleNumber = Default;
|
||||
|
||||
|
||||
var bundleNamePath = _FORTNITE_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _ASIA_ERINM,
|
||||
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
|
||||
ELanguage.Japanese => _NIS_JYAU,
|
||||
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
|
||||
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
|
||||
_ => string.Empty
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(bundleNamePath + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) {Position = 0};
|
||||
Bundle = SKTypeface.FromStream(m);
|
||||
}
|
||||
else Bundle = BundleNumber;
|
||||
|
||||
break;
|
||||
}
|
||||
case FGame.WorldExplorers:
|
||||
{
|
||||
var namePath = _BATTLE_BREAKERS_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _NOTO_SANS_KR_REGULAR,
|
||||
ELanguage.Russian => _LATO_BLACK,
|
||||
ELanguage.Japanese => _NOTO_SANS_JP_REGULAR,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_REGULAR,
|
||||
_ => _HEMIHEAD426
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(namePath + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) {Position = 0};
|
||||
DisplayName = SKTypeface.FromStream(m);
|
||||
}
|
||||
else DisplayName = Default;
|
||||
|
||||
var descriptionPath = _BATTLE_BREAKERS_BASE_PATH +
|
||||
language switch
|
||||
{
|
||||
ELanguage.Korean => _NOTO_SANS_KR_REGULAR,
|
||||
ELanguage.Russian => _LATO_BLACK,
|
||||
ELanguage.Japanese => _NOTO_SANS_JP_REGULAR,
|
||||
ELanguage.Chinese => _NOTO_SANS_SC_REGULAR,
|
||||
_ => _HEMIHEAD426
|
||||
};
|
||||
if (viewModel.Provider.TrySaveAsset(descriptionPath + _EXT, out data))
|
||||
{
|
||||
var m = new MemoryStream(data) {Position = 0};
|
||||
Description = SKTypeface.FromStream(m);
|
||||
}
|
||||
else Description = Default;
|
||||
|
||||
break;
|
||||
}
|
||||
case FGame.ShooterGame:
|
||||
break;
|
||||
case FGame.DeadByDaylight:
|
||||
break;
|
||||
case FGame.OakGame:
|
||||
break;
|
||||
case FGame.Dungeons:
|
||||
break;
|
||||
case FGame.g3:
|
||||
break;
|
||||
case FGame.StateOfDecay2:
|
||||
break;
|
||||
case FGame.Prospect:
|
||||
break;
|
||||
case FGame.Indiana:
|
||||
break;
|
||||
case FGame.RogueCompany:
|
||||
break;
|
||||
case FGame.SwGame:
|
||||
break;
|
||||
case FGame.Platform:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public SKTypeface OnTheFly(string path)
|
||||
{
|
||||
if (!_viewModel.Provider.TrySaveAsset(path, out var data)) return Default;
|
||||
var m = new MemoryStream(data) {Position = 0};
|
||||
return SKTypeface.FromStream(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,129 +1,309 @@
|
|||
using FModel.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
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.UObject;
|
||||
using CUE4Parse.Utils;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.ViewModels;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using FModel.PakReader;
|
||||
using FModel.PakReader.IO;
|
||||
using FModel.PakReader.Parsers.Class;
|
||||
using FModel.PakReader.Parsers.PropertyTagData;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
|
||||
namespace FModel.Creator
|
||||
{
|
||||
static class Utils
|
||||
public static class Utils
|
||||
{
|
||||
public static string GetFullPath(FPackageId id)
|
||||
private static ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
private static readonly Regex _htmlRegex = new("<.*?>");
|
||||
public static Typefaces Typefaces;
|
||||
|
||||
public static string RemoveHtmlTags(string s)
|
||||
{
|
||||
foreach (var ioStore in Globals.CachedIoStores.Values)
|
||||
var match = _htmlRegex.Match(s);
|
||||
while (match.Success)
|
||||
{
|
||||
if (ioStore.Chunks.TryGetValue(id.Id, out string path))
|
||||
s = s.Replace(match.Value, string.Empty);
|
||||
match = match.NextMatch();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
public static bool TryGetDisplayAsset(UObject uObject, out SKBitmap preview)
|
||||
{
|
||||
if (uObject.TryGetValue(out FSoftObjectPath displayAsset, "DisplayAssetPath"))
|
||||
{
|
||||
preview = GetDisplayAsset(displayAsset);
|
||||
return preview != null;
|
||||
}
|
||||
|
||||
if (uObject.TryGetValue(out FSoftObjectPath sidePanelIcon, "SidePanelIcon"))
|
||||
{
|
||||
preview = GetBitmap(sidePanelIcon);
|
||||
return preview != null;
|
||||
}
|
||||
|
||||
var path = $"/Game/Catalog/MI_OfferImages/MI_{uObject.Name.Replace("Athena_Commando_", string.Empty)}";
|
||||
if (!TryLoadObject(path, out UMaterialInstanceConstant material)) // non-obfuscated item definition
|
||||
{
|
||||
if (!TryLoadObject($"{path[..path.LastIndexOf('_')]}_{path.SubstringAfterLast('_').ToLower()}", out material)) // Try to get MI with lowercase obfuscation
|
||||
TryLoadObject(path[..path.LastIndexOf('_')], out material); // hopefully gets obfuscated item definition
|
||||
}
|
||||
|
||||
preview = GetBitmap(material);
|
||||
return preview != null;
|
||||
}
|
||||
|
||||
public static SKBitmap GetDisplayAsset(FSoftObjectPath path)
|
||||
{
|
||||
if (!TryLoadObject(path.AssetPathName.Text, out UObject obj)) return null;
|
||||
|
||||
if (obj.TryGetValue(out FStructFallback type, "DetailsImage") &&
|
||||
type.TryGetValue(out FPackageIndex resource, "ResourceObject") && resource.ResolvedObject?.Outer != null &&
|
||||
!resource.ResolvedObject.Outer.Name.Text.Contains("FortniteGame/Content/Athena/Prototype/Textures/"))
|
||||
{
|
||||
return GetBitmap(resource);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
public static SKBitmap GetBitmap(FPackageIndex packageIndex)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (!TryGetPackageIndexExport(packageIndex, out UExport export)) return null;
|
||||
switch (export)
|
||||
{
|
||||
return ioStore.MountPoint + path;
|
||||
case UTexture2D texture:
|
||||
return GetBitmap(texture);
|
||||
case UMaterialInstanceConstant material:
|
||||
return GetBitmap(material);
|
||||
case UObject uObject:
|
||||
{
|
||||
if (uObject.TryGetValue(out FSoftObjectPath previewImage, "LargePreviewImage", "SmallPreviewImage")) return GetBitmap(previewImage);
|
||||
if (uObject.TryGetValue(out string largePreview, "LargePreviewImage")) return GetBitmap(largePreview);
|
||||
if (uObject.TryGetValue(out FPackageIndex smallPreview, "SmallPreviewImage"))
|
||||
{
|
||||
packageIndex = smallPreview;
|
||||
continue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
public static SKBitmap GetBitmap(UMaterialInstanceConstant material)
|
||||
{
|
||||
if (material == null) return null;
|
||||
foreach (var textureParameter in material.TextureParameterValues)
|
||||
{
|
||||
if (!(textureParameter.ParameterValue is UTexture2D texture)) continue;
|
||||
switch (textureParameter.ParameterInfo.Name.Text)
|
||||
{
|
||||
case "TextureA":
|
||||
case "TextureB":
|
||||
case "OfferImage":
|
||||
{
|
||||
return GetBitmap(texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string GetFullPath(string partialPath)
|
||||
{
|
||||
foreach (var fileReader in Globals.CachedPakFiles.Values)
|
||||
if (fileReader.TryGetPartialKey(partialPath, out var fullPath))
|
||||
{
|
||||
return fullPath;
|
||||
}
|
||||
public static SKBitmap GetB64Bitmap(string b64) => SKBitmap.Decode(new MemoryStream(Convert.FromBase64String(b64)) {Position = 0});
|
||||
public static SKBitmap GetBitmap(FSoftObjectPath softObjectPath) => GetBitmap(softObjectPath.AssetPathName.Text);
|
||||
public static SKBitmap GetBitmap(string fullPath) => TryLoadObject(fullPath, out UTexture2D texture) ? GetBitmap(texture) : null;
|
||||
public static SKBitmap GetBitmap(UTexture2D texture) => texture.IsVirtual ? null : SKBitmap.Decode(texture.Decode()?.Encode());
|
||||
public static SKBitmap GetBitmap(byte[] data) => SKBitmap.Decode(data);
|
||||
|
||||
foreach (var ioStoreReader in Globals.CachedIoStores.Values)
|
||||
{
|
||||
if (ioStoreReader.TryGetPartialKey(partialPath, out var fullPath))
|
||||
{
|
||||
return fullPath;
|
||||
}
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
public static SKBitmap Resize(this SKBitmap me, int size) => me.Resize(size, size);
|
||||
|
||||
public static Package GetPropertyPakPackage(string value)
|
||||
{
|
||||
string path = Strings.FixPath(value);
|
||||
foreach (var fileReader in Globals.CachedPakFiles.Values)
|
||||
if (fileReader.TryGetCaseInsensiteveValue(path, out var entry))
|
||||
{
|
||||
// kinda sad to use Globals.CachedPakFileMountPoint when the mount point is already in the path ¯\_(ツ)_/¯
|
||||
string mount = path.Substring(0, path.Length - entry.Name.Substring(0, entry.Name.LastIndexOf('.')).Length);
|
||||
return Assets.GetPackage(entry, mount);
|
||||
}
|
||||
foreach (var ioStoreReader in Globals.CachedIoStores.Values)
|
||||
if (ioStoreReader.TryGetCaseInsensiteveValue(path, out var entry))
|
||||
{
|
||||
// kinda sad to use Globals.CachedPakFileMountPoint when the mount point is already in the path ¯\_(ツ)_/¯
|
||||
string mount = path.Substring(0, path.Length - entry.Name.Substring(0, entry.Name.LastIndexOf('.')).Length);
|
||||
return Assets.GetPackage(entry, mount);
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public static ArraySegment<byte>[] GetPropertyArraySegmentByte(string value)
|
||||
{
|
||||
string path = Strings.FixPath(value);
|
||||
foreach (var fileReader in Globals.CachedPakFiles.Values)
|
||||
if (fileReader.TryGetCaseInsensiteveValue(path, out var entry))
|
||||
{
|
||||
// kinda sad to use Globals.CachedPakFileMountPoint when the mount point is already in the path ¯\_(ツ)_/¯
|
||||
string mount = path.Substring(0, path.Length - entry.Name.Substring(0, entry.Name.LastIndexOf('.')).Length);
|
||||
return Assets.GetArraySegmentByte(entry, mount);
|
||||
}
|
||||
foreach (var ioStoreReader in Globals.CachedIoStores.Values)
|
||||
if (ioStoreReader.TryGetCaseInsensiteveValue(path, out var entry))
|
||||
{
|
||||
// kinda sad to use Globals.CachedPakFileMountPoint when the mount point is already in the path ¯\_(ツ)_/¯
|
||||
string mount = path.Substring(0, path.Length - entry.Name.Substring(0, entry.Name.LastIndexOf('.')).Length);
|
||||
return Assets.GetArraySegmentByte(entry, mount);
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static SKBitmap NewZeroedBitmap(int width, int height) => new SKBitmap(new SKImageInfo(width, height), SKBitmapAllocFlags.ZeroPixels);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static SKBitmap Resize(this SKBitmap me, int width, int height)
|
||||
{
|
||||
var bmp = NewZeroedBitmap(width, height);
|
||||
var bmp = new SKBitmap(new SKImageInfo(width, height), SKBitmapAllocFlags.ZeroPixels);
|
||||
using var pixmap = bmp.PeekPixels();
|
||||
me.ScalePixels(pixmap, SKFilterQuality.Medium);
|
||||
return bmp;
|
||||
}
|
||||
|
||||
public static SKBitmap GetObjectTexture(ObjectProperty o) => GetTexture(o.Value.Resource.OuterIndex.Resource.ObjectName.String);
|
||||
public static SKBitmap GetSoftObjectTexture(SoftObjectProperty s) => GetTexture(s.Value.AssetPathName.String);
|
||||
public static SKBitmap GetTexture(string s)
|
||||
public static bool TryGetPackageIndexExport<T>(FPackageIndex packageIndex, out T export) where T : UExport
|
||||
{
|
||||
if (s != null)
|
||||
if (packageIndex.ResolvedObject == null)
|
||||
{
|
||||
if (s.Equals("/Game/UI/Foundation/Textures/BattleRoyale/FeaturedItems/Outfit/T_UI_InspectScreen_annualPass"))
|
||||
s += "_1024";
|
||||
else if (s.Equals("/Game/UI/Foundation/Textures/BattleRoyale/BattlePass/T-BattlePass-Season14-Tile") || s.Equals("/Game/UI/Foundation/Textures/BattleRoyale/BattlePass/T-BattlePassWithLevels-Season14-Tile"))
|
||||
s += "_1";
|
||||
else if (s.Equals("/Game/UI/Textures/assets/cosmetics/skins/headshot/Skin_Headshot_WolfsBlood_UIT"))
|
||||
s = "/Game/UI/Textures/assets/cosmetics/skins/headshot/Skin_Headshot_Wolfsblood_UIT";
|
||||
else if (s.Equals("/Game/UI/Textures/assets/cosmetics/skins/headshot/Skin_Headshot_Timeweaver_UIT"))
|
||||
s = "/Game/UI/Textures/assets/cosmetics/skins/headshot/Skin_Headshot_TimeWeaver_UIT";
|
||||
export = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
var p = GetPropertyPakPackage(s);
|
||||
if (p != null && p.HasExport() && !p.Equals(default))
|
||||
var outerChain = new List<string>();
|
||||
var current = packageIndex.ResolvedObject.Outer;
|
||||
while (current != null)
|
||||
{
|
||||
var i = p.GetExport<UTexture2D>();
|
||||
if (i != null)
|
||||
return SKBitmap.Decode(i.Image.Encode());
|
||||
|
||||
var u = p.GetExport<UObject>();
|
||||
if (u != null)
|
||||
if (u.TryGetValue("TextureParameterValues", out var v) && v is ArrayProperty a)
|
||||
if (a.Value.Length > 0 && a.Value[0] is StructProperty str && str.Value is UObject o)
|
||||
if (o.TryGetValue("ParameterValue", out var obj) && obj is ObjectProperty parameterValue)
|
||||
return GetObjectTexture(parameterValue);
|
||||
outerChain.Add(current.Name.Text);
|
||||
current = current.Outer;
|
||||
}
|
||||
return null;
|
||||
|
||||
if (outerChain.Count < 1)
|
||||
{
|
||||
export = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_applicationView.CUE4Parse.Provider.TryLoadPackage(outerChain[^1], out var pkg))
|
||||
{
|
||||
export = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
export = pkg.GetExport(packageIndex.ResolvedObject.Index) as T;
|
||||
return export != null;
|
||||
}
|
||||
|
||||
// fullpath must be either without any extension or with the export objectname
|
||||
public static bool TryLoadObject<T>(string fullPath, out T export) where T : UExport
|
||||
{
|
||||
return _applicationView.CUE4Parse.Provider.TryLoadObject(fullPath, out export);
|
||||
}
|
||||
|
||||
public static IEnumerable<UExport> LoadExports(string fullPath)
|
||||
{
|
||||
return _applicationView.CUE4Parse.Provider.LoadObjectExports(fullPath);
|
||||
}
|
||||
|
||||
public static string GetLocalizedResource(string namespacee, string key, string defaultValue)
|
||||
{
|
||||
return _applicationView.CUE4Parse.Provider.GetLocalizedString(namespacee, key, defaultValue);
|
||||
}
|
||||
|
||||
public static string GetFullPath(string partialPath)
|
||||
{
|
||||
var regex = new Regex(partialPath, RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
foreach (var path in _applicationView.CUE4Parse.Provider.Files.Keys)
|
||||
{
|
||||
if (regex.IsMatch(path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static void DrawCenteredMultilineText(SKCanvas c, string text, int maxCount, int size, int margin, SKTextAlign side, SKRect area, SKPaint paint)
|
||||
{
|
||||
var lineHeight = paint.TextSize * 1.2f;
|
||||
var lines = SplitLines(text, paint, area.Width - margin);
|
||||
|
||||
#if DEBUG
|
||||
c.DrawRect(new SKRect(area.Left, area.Top, area.Right, area.Bottom), new SKPaint {Color = SKColors.Red, IsStroke = true});
|
||||
#endif
|
||||
|
||||
if (lines == null) return;
|
||||
if (lines.Count <= maxCount) maxCount = lines.Count;
|
||||
var height = maxCount * lineHeight;
|
||||
var y = area.MidY - height / 2;
|
||||
|
||||
var shaper = new CustomSKShaper(paint.Typeface);
|
||||
for (var i = 0; i < maxCount; i++)
|
||||
{
|
||||
var line = lines[i];
|
||||
if (line == null) continue;
|
||||
|
||||
var lineText = line.Trim();
|
||||
var shapedText = shaper.Shape(lineText, paint);
|
||||
|
||||
y += lineHeight;
|
||||
var x = side switch
|
||||
{
|
||||
SKTextAlign.Center => area.MidX - shapedText.Points[^1].X / 2,
|
||||
SKTextAlign.Right => size - margin - shapedText.Points[^1].X,
|
||||
SKTextAlign.Left => margin,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
c.DrawShapedText(shaper, lineText, x, y, paint);
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawMultilineText(SKCanvas c, string text, int size, int margin, SKTextAlign side, SKRect area, SKPaint paint, out float yPos)
|
||||
{
|
||||
yPos = area.Top;
|
||||
var lineHeight = paint.TextSize * 1.2f;
|
||||
var lines = SplitLines(text, paint, area.Width);
|
||||
if (lines == null) return;
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line == null) continue;
|
||||
var lineText = line.Trim();
|
||||
var shaper = new CustomSKShaper(paint.Typeface);
|
||||
var shapedText = shaper.Shape(lineText, paint);
|
||||
|
||||
var x = side switch
|
||||
{
|
||||
SKTextAlign.Center => area.MidX - shapedText.Points[^1].X / 2,
|
||||
SKTextAlign.Right => size - margin - shapedText.Points[^1].X,
|
||||
SKTextAlign.Left => area.Left,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
c.DrawShapedText(shaper, lineText, x, yPos, paint);
|
||||
yPos += lineHeight;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
c.DrawRect(new SKRect(area.Left, area.Top - paint.TextSize, area.Right, yPos), new SKPaint {Color = SKColors.Red, IsStroke = true});
|
||||
#endif
|
||||
}
|
||||
|
||||
public static List<string> SplitLines(string text, SKPaint paint, float maxWidth)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text)) return null;
|
||||
|
||||
var spaceWidth = paint.MeasureText(" ");
|
||||
var lines = text.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
var ret = new List<string>(lines.Length);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line)) continue;
|
||||
|
||||
float width = 0;
|
||||
var words = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
var lineResult = new StringBuilder();
|
||||
foreach (var word in words)
|
||||
{
|
||||
var wordWidth = paint.MeasureText(word);
|
||||
var wordWithSpaceWidth = wordWidth + spaceWidth;
|
||||
var wordWithSpace = word + " ";
|
||||
|
||||
if (width + wordWidth > maxWidth)
|
||||
{
|
||||
ret.Add(lineResult.ToString());
|
||||
lineResult = new StringBuilder(wordWithSpace);
|
||||
width = wordWithSpaceWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
lineResult.Append(wordWithSpace);
|
||||
width += wordWithSpaceWidth;
|
||||
}
|
||||
}
|
||||
|
||||
ret.Add(lineResult.ToString());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
using DiscordRPC;
|
||||
using DiscordRPC.Logging;
|
||||
using FModel.Logger;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace FModel.Discord
|
||||
{
|
||||
static class DiscordIntegration
|
||||
{
|
||||
private const string _DISCORD_APP_ID = "684489366189768767";
|
||||
|
||||
private static readonly Timestamps _baseTimestamp = new Timestamps { Start = DateTime.UtcNow };
|
||||
private static readonly Assets _assets = new Assets
|
||||
{
|
||||
LargeImageKey = "official_logo",
|
||||
SmallImageKey = "verified",
|
||||
SmallImageText = $"v{Assembly.GetExecutingAssembly().GetName().Version.ToString().Substring(0, 5)}"
|
||||
};
|
||||
private static readonly DiscordRpcClient _client = new DiscordRpcClient(_DISCORD_APP_ID);
|
||||
private static RichPresence _presence;
|
||||
|
||||
public static void Dispose() => _client.Dispose();
|
||||
public static void Deinitialize() => _client.Deinitialize();
|
||||
private static void Initialize()
|
||||
{
|
||||
_client.Logger = new ConsoleLogger() { Level = LogLevel.Warning };
|
||||
_client.OnReady += (sender, e) =>
|
||||
{
|
||||
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Discord RPC]", $"Ready for {e.User.Username}#{e.User.Discriminator} ({e.User.ID})");
|
||||
};
|
||||
_client.Initialize();
|
||||
}
|
||||
|
||||
public static void StartClient()
|
||||
{
|
||||
_client.SetPresence(new RichPresence
|
||||
{
|
||||
Assets = _assets,
|
||||
Timestamps = _baseTimestamp,
|
||||
State = string.Format(Properties.Resources.Idling, Globals.Game.GetName())
|
||||
});
|
||||
Initialize();
|
||||
SaveCurrentPresence();
|
||||
}
|
||||
|
||||
public static void Update(string detail = null, string state = null)
|
||||
{
|
||||
_client.SetPresence(new RichPresence
|
||||
{
|
||||
Assets = _assets,
|
||||
Timestamps = _baseTimestamp,
|
||||
Details = string.IsNullOrEmpty(detail) ? _presence?.Details : detail,
|
||||
State = string.IsNullOrEmpty(state) ? _presence?.State : state
|
||||
});
|
||||
_client.Invoke();
|
||||
}
|
||||
|
||||
public static void Restore()
|
||||
{
|
||||
if (_presence != null)
|
||||
{
|
||||
_client.SetPresence(_presence);
|
||||
_client.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public static void SaveCurrentPresence() => _presence = _client.CurrentPresence;
|
||||
}
|
||||
}
|
||||
179
FModel/Enums.cs
179
FModel/Enums.cs
|
|
@ -1,86 +1,139 @@
|
|||
namespace FModel
|
||||
{
|
||||
public enum EGame
|
||||
{
|
||||
Unknown,
|
||||
Fortnite,
|
||||
Valorant,
|
||||
DeadByDaylight,
|
||||
Borderlands3,
|
||||
MinecraftDungeons,
|
||||
BattleBreakers,
|
||||
Spellbreak,
|
||||
StateOfDecay2,
|
||||
TheCycle,
|
||||
TheOuterWorlds,
|
||||
RogueCompany
|
||||
}
|
||||
using System.ComponentModel;
|
||||
|
||||
public enum EFModel
|
||||
namespace FModel
|
||||
{
|
||||
public enum EBuildKind
|
||||
{
|
||||
Debug,
|
||||
Release,
|
||||
Unknown
|
||||
}
|
||||
|
||||
public enum EPakLoader
|
||||
public enum EErrorKind
|
||||
{
|
||||
Close,
|
||||
Ignore,
|
||||
Restart
|
||||
}
|
||||
|
||||
public enum SettingsOut
|
||||
{
|
||||
Restart,
|
||||
ReloadLocres,
|
||||
Nothing
|
||||
}
|
||||
|
||||
public enum EStatusKind
|
||||
{
|
||||
Ready, // ready
|
||||
Loading, // doing stuff
|
||||
Stopping, // trying to stop
|
||||
Stopped, // stopped
|
||||
Failed, // crashed
|
||||
Completed // worked
|
||||
}
|
||||
|
||||
public enum EAesReload
|
||||
{
|
||||
[Description("Always")]
|
||||
Always,
|
||||
[Description("Never")]
|
||||
Never,
|
||||
[Description("Once Per Day")]
|
||||
OncePerDay
|
||||
}
|
||||
|
||||
public enum EDiscordRpc
|
||||
{
|
||||
[Description("Always")]
|
||||
Always,
|
||||
[Description("Never")]
|
||||
Never
|
||||
}
|
||||
|
||||
public enum EEnabledDisabled
|
||||
{
|
||||
[Description("Disabled")]
|
||||
Disabled,
|
||||
[Description("Enabled")]
|
||||
Enabled
|
||||
}
|
||||
|
||||
public enum FGame
|
||||
{
|
||||
[Description("Unknown")]
|
||||
Unknown,
|
||||
[Description("Fortnite")]
|
||||
FortniteGame,
|
||||
[Description("Valorant")]
|
||||
ShooterGame,
|
||||
[Description("Dead By Daylight")]
|
||||
DeadByDaylight,
|
||||
[Description("Borderlands 3")]
|
||||
OakGame,
|
||||
[Description("Minecraft Dungeons")]
|
||||
Dungeons,
|
||||
[Description("Battle Breakers")]
|
||||
WorldExplorers,
|
||||
[Description("Spellbreak")]
|
||||
g3,
|
||||
[Description("State Of Decay 2")]
|
||||
StateOfDecay2,
|
||||
[Description("The Cycle")]
|
||||
Prospect,
|
||||
[Description("The Outer Worlds")]
|
||||
Indiana,
|
||||
[Description("Rogue Company")]
|
||||
RogueCompany,
|
||||
[Description("Star Wars: Jedi Fallen Order")]
|
||||
SwGame,
|
||||
[Description("Core")]
|
||||
Platform
|
||||
}
|
||||
|
||||
public enum ELoadingMode
|
||||
{
|
||||
[Description("Single")]
|
||||
Single,
|
||||
[Description("Multiple")]
|
||||
Multiple,
|
||||
[Description("All")]
|
||||
All,
|
||||
New,
|
||||
Modified,
|
||||
NewModified
|
||||
[Description("All (New)")]
|
||||
AllButNew,
|
||||
[Description("All (Modified)")]
|
||||
AllButModified
|
||||
}
|
||||
|
||||
public enum ECopy
|
||||
public enum EUpdateMode
|
||||
{
|
||||
Path,
|
||||
PathNoExt,
|
||||
PathNoFile,
|
||||
File,
|
||||
FileNoExt
|
||||
[Description("Stable")]
|
||||
Stable,
|
||||
[Description("Beta")]
|
||||
Beta
|
||||
}
|
||||
|
||||
public enum ELanguage : long
|
||||
public enum ECompressedAudio
|
||||
{
|
||||
English = 0,
|
||||
AustralianEnglish = 15,
|
||||
BritishEnglish = 16,
|
||||
French = 1,
|
||||
German = 2,
|
||||
Italian = 3,
|
||||
Spanish = 4,
|
||||
SpanishLatin = 5,
|
||||
SpanishMexico = 17,
|
||||
Arabic = 6,
|
||||
Japanese = 7,
|
||||
Korean = 8,
|
||||
Polish = 9,
|
||||
PortugueseBrazil = 10,
|
||||
PortuguesePortugal = 18,
|
||||
Russian = 11,
|
||||
Turkish = 12,
|
||||
Chinese = 13,
|
||||
TraditionalChinese = 14,
|
||||
Swedish = 19,
|
||||
Thai = 20,
|
||||
Indonesian = 21,
|
||||
VietnameseVietnam = 22,
|
||||
Zulu = 23
|
||||
[Description("Play the decompressed data")]
|
||||
PlayDecompressed,
|
||||
[Description("Play the compressed data (might not always be a valid audio data)")]
|
||||
PlayCompressed
|
||||
}
|
||||
|
||||
public enum EJsonType: long
|
||||
{
|
||||
Default,
|
||||
Positioned
|
||||
}
|
||||
|
||||
public enum EIconDesign : long
|
||||
public enum EIconStyle
|
||||
{
|
||||
[Description("Default")]
|
||||
Default,
|
||||
[Description("No Background")]
|
||||
NoBackground,
|
||||
[Description("No Text")]
|
||||
NoText,
|
||||
Mini,
|
||||
Flat
|
||||
[Description("Flat")]
|
||||
Flat,
|
||||
[Description("Cataba")]
|
||||
Cataba,
|
||||
// [Description("Community")]
|
||||
// CommunityMade
|
||||
}
|
||||
}
|
||||
}
|
||||
47
FModel/Extensions/AvalonExtensions.cs
Normal file
47
FModel/Extensions/AvalonExtensions.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Xml;
|
||||
using ICSharpCode.AvalonEdit.Highlighting;
|
||||
using ICSharpCode.AvalonEdit.Highlighting.Xshd;
|
||||
|
||||
namespace FModel.Extensions
|
||||
{
|
||||
public static class AvalonExtensions
|
||||
{
|
||||
private static readonly IHighlightingDefinition _jsonHighlighter = LoadHighlighter("Json.xshd");
|
||||
private static readonly IHighlightingDefinition _iniHighlighter = LoadHighlighter("Ini.xshd");
|
||||
private static readonly IHighlightingDefinition _xmlHighlighter = LoadHighlighter("Xml.xshd");
|
||||
private static readonly IHighlightingDefinition _cppHighlighter = LoadHighlighter("Cpp.xshd");
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static IHighlightingDefinition LoadHighlighter(string resourceName)
|
||||
{
|
||||
var executingAssembly = Assembly.GetExecutingAssembly();
|
||||
using var stream = executingAssembly.GetManifestResourceStream($"{executingAssembly.GetName().Name}.Resources.{resourceName}");
|
||||
using var reader = new XmlTextReader(stream);
|
||||
return HighlightingLoader.Load(reader, HighlightingManager.Instance);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static IHighlightingDefinition HighlighterSelector(string ext)
|
||||
{
|
||||
switch (ext)
|
||||
{
|
||||
case "ini":
|
||||
case "csv":
|
||||
return _iniHighlighter;
|
||||
case "xml":
|
||||
return _xmlHighlighter;
|
||||
case "h":
|
||||
case "cpp":
|
||||
return _cppHighlighter;
|
||||
case "bat":
|
||||
case "txt":
|
||||
case "po":
|
||||
return null;
|
||||
default:
|
||||
return _jsonHighlighter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
FModel/Extensions/CollectionExtensions.cs
Normal file
37
FModel/Extensions/CollectionExtensions.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace FModel.Extensions
|
||||
{
|
||||
public static class CollectionExtensions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T Next<T>(this IList<T> collection, T value)
|
||||
{
|
||||
var i = collection.IndexOf(value) + 1;
|
||||
return i >= collection.Count ? collection.First() : collection[i];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T Next<T>(this IList<T> collection, int index)
|
||||
{
|
||||
var i = index + 1;
|
||||
return i >= collection.Count ? collection.First() : collection[i];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T Previous<T>(this IList<T> collection, T value)
|
||||
{
|
||||
var i = collection.IndexOf(value) - 1;
|
||||
return i < 0 ? collection.Last() : collection[i];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T Previous<T>(this IList<T> collection, int index)
|
||||
{
|
||||
var i = index - 1;
|
||||
return i < 0 ? collection.Last() : collection[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
84
FModel/Extensions/EnumExtensions.cs
Normal file
84
FModel/Extensions/EnumExtensions.cs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
using FModel.Properties;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Resources;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace FModel.Extensions
|
||||
{
|
||||
public static class EnumExtensions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string GetDescription(this Enum value)
|
||||
{
|
||||
var fi = value.GetType().GetField(value.ToString());
|
||||
if (fi == null) return $"{value} ({value:D})";
|
||||
var attributes = (DescriptionAttribute[]) fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
|
||||
return attributes.Length > 0 ? attributes[0].Description : $"{value} ({value:D})";
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string GetLocalizedDescription(this Enum value) => value.GetLocalizedDescription(Resources.ResourceManager);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string GetLocalizedDescription(this Enum value, ResourceManager resourceManager)
|
||||
{
|
||||
var resourceName = value.GetType().Name + "_" + value;
|
||||
var description = resourceManager.GetString(resourceName);
|
||||
|
||||
if (string.IsNullOrEmpty(description))
|
||||
{
|
||||
description = value.GetDescription();
|
||||
}
|
||||
|
||||
return description;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string GetLocalizedCategory(this Enum value) => value.GetLocalizedCategory(Resources.ResourceManager);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string GetLocalizedCategory(this Enum value, ResourceManager resourceManager)
|
||||
{
|
||||
var resourceName = value.GetType().Name + "_" + value + "_Category";
|
||||
var description = resourceManager.GetString(resourceName);
|
||||
|
||||
return description;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T ToEnum<T>(this string value, T defaultValue)
|
||||
{
|
||||
if (!Enum.TryParse(typeof(T), value, true, out var ret))
|
||||
return defaultValue;
|
||||
|
||||
return (T) ret;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool HasAnyFlags<T>(this T flags, T contains) where T : Enum, IConvertible => (flags.ToInt32(null) & contains.ToInt32(null)) != 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetIndex(this Enum value)
|
||||
{
|
||||
var values = Enum.GetValues(value.GetType());
|
||||
return Array.IndexOf(values, value);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T Next<T>(this Enum value)
|
||||
{
|
||||
var values = Enum.GetValues(value.GetType());
|
||||
var i = Array.IndexOf(values, value) + 1;
|
||||
return i == values.Length ? (T) values.GetValue(0) : (T) values.GetValue(i);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T Previous<T>(this Enum value)
|
||||
{
|
||||
var values = Enum.GetValues(value.GetType());
|
||||
var i = Array.IndexOf(values, value) - 1;
|
||||
return i == -1 ? (T) values.GetValue(values.Length - 1) : (T) values.GetValue(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
FModel/Extensions/StreamExtensions.cs
Normal file
31
FModel/Extensions/StreamExtensions.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace FModel.Extensions
|
||||
{
|
||||
public enum Endianness
|
||||
{
|
||||
LittleEndian,
|
||||
BigEndian,
|
||||
}
|
||||
|
||||
public static class StreamExtensions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint ReadUInt32(this Stream s, Endianness endian = Endianness.LittleEndian)
|
||||
{
|
||||
var b1 = s.ReadByte();
|
||||
var b2 = s.ReadByte();
|
||||
var b3 = s.ReadByte();
|
||||
var b4 = s.ReadByte();
|
||||
|
||||
return endian switch
|
||||
{
|
||||
Endianness.LittleEndian => (uint) (b4 << 24 | b3 << 16 | b2 << 8 | b1),
|
||||
Endianness.BigEndian => (uint) (b1 << 24 | b2 << 16 | b3 << 8 | b4),
|
||||
_ => throw new Exception("unknown endianness")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,113 +1,90 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace FModel.Utils
|
||||
namespace FModel.Extensions
|
||||
{
|
||||
|
||||
public static class Strings
|
||||
public static class StringExtensions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string GetReadableSize(double size)
|
||||
{
|
||||
string[] sizes = { Properties.Resources.B, Properties.Resources.KB, Properties.Resources.MB, Properties.Resources.GB, Properties.Resources.TB };
|
||||
int order = 0;
|
||||
if (size == 0) return "0 B";
|
||||
|
||||
string[] sizes = {"B", "KB", "MB", "GB", "TB"};
|
||||
var order = 0;
|
||||
while (size >= 1024 && order < sizes.Length - 1)
|
||||
{
|
||||
order++;
|
||||
size /= 1024;
|
||||
}
|
||||
return string.Format("{0:# ###.##} {1}", size, sizes[order]).TrimStart();
|
||||
|
||||
return $"{size:# ###.##} {sizes[order]}".TrimStart();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string FixPath(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path) || path.Length < 5)
|
||||
return string.Empty;
|
||||
|
||||
string trigger;
|
||||
{
|
||||
if (path.Contains("/"))
|
||||
{
|
||||
string tempPath = path[1..];
|
||||
trigger = tempPath.Substring(0, tempPath.IndexOf('/'));
|
||||
}
|
||||
else
|
||||
trigger = path;
|
||||
}
|
||||
|
||||
Regex regex = new Regex(trigger);
|
||||
if (trigger.Equals("SrirachaRanchCore") || trigger.Equals("SrirachaRanchHoagie") || trigger.Equals("SrirachaRanchValet"))
|
||||
trigger = "SrirachaRanch/" + trigger;
|
||||
|
||||
string fixedPath = trigger switch
|
||||
{
|
||||
"Game" => regex.Replace(path, $"{Folders.GetGameName()}/Content", 1),
|
||||
"RegionCN" => regex.Replace(path, $"{Folders.GetGameName()}/Plugins/{trigger}/Content", 1),
|
||||
_ => regex.Replace(path, $"{Folders.GetGameName()}/Plugins/GameFeatures/{trigger}/Content", 1)
|
||||
};
|
||||
|
||||
int sep = fixedPath.LastIndexOf('.');
|
||||
return fixedPath.Substring(0, sep > 0 ? sep : fixedPath.Length);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string SubstringBefore(this string s, char delimiter)
|
||||
{
|
||||
var index = s.IndexOf(delimiter);
|
||||
return index == -1 ? s : s.Substring(0, index);
|
||||
return index == -1 ? s : s[..index];
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string SubstringBefore(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal)
|
||||
{
|
||||
var index = s.IndexOf(delimiter, comparisonType);
|
||||
return index == -1 ? s : s.Substring(0, index);
|
||||
return index == -1 ? s : s[..index];
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string SubstringAfter(this string s, char delimiter)
|
||||
{
|
||||
var index = s.IndexOf(delimiter);
|
||||
return index == -1 ? s : s.Substring(index + 1, s.Length - index - 1);
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string SubstringAfter(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal)
|
||||
{
|
||||
var index = s.IndexOf(delimiter, comparisonType);
|
||||
return index == -1 ? s : s.Substring(index + delimiter.Length, s.Length - index - delimiter.Length);
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string SubstringBeforeLast(this string s, char delimiter)
|
||||
{
|
||||
var index = s.LastIndexOf(delimiter);
|
||||
return index == -1 ? s : s.Substring(0, index);
|
||||
return index == -1 ? s : s[..index];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string SubstringBeforeWithLast(this string s, char delimiter)
|
||||
{
|
||||
var index = s.LastIndexOf(delimiter);
|
||||
return index == -1 ? s : s.Substring(0, index + 1);
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string SubstringBeforeLast(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal)
|
||||
{
|
||||
var index = s.LastIndexOf(delimiter, comparisonType);
|
||||
return index == -1 ? s : s.Substring(0, index);
|
||||
return index == -1 ? s : s[..index];
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string SubstringBeforeWithLast(this string s, char delimiter)
|
||||
{
|
||||
var index = s.LastIndexOf(delimiter);
|
||||
return index == -1 ? s : s[..(index + 1)];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string SubstringBeforeWithLast(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal)
|
||||
{
|
||||
var index = s.LastIndexOf(delimiter, comparisonType);
|
||||
return index == -1 ? s : s[..(index + 1)];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string SubstringAfterLast(this string s, char delimiter)
|
||||
{
|
||||
var index = s.LastIndexOf(delimiter);
|
||||
return index == -1 ? s : s.Substring(index + 1, s.Length - index - 1);
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static string SubstringAfterLast(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal)
|
||||
{
|
||||
|
|
@ -116,7 +93,42 @@ namespace FModel.Utils
|
|||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Contains(this string orig, string value, StringComparison comparisonType) =>
|
||||
orig.IndexOf(value, comparisonType) >= 0;
|
||||
public static int GetLineNumber(this string s, string lineToFind)
|
||||
{
|
||||
if (int.TryParse(lineToFind, out var index))
|
||||
return s.GetLineNumber(index);
|
||||
|
||||
lineToFind = $" \"Name\": \"{lineToFind}\",";
|
||||
using var reader = new StringReader(s);
|
||||
var lineNum = 0;
|
||||
string line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
lineNum++;
|
||||
if (line.Equals(lineToFind, StringComparison.OrdinalIgnoreCase))
|
||||
return lineNum;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetLineNumber(this string s, int index)
|
||||
{
|
||||
using var reader = new StringReader(s);
|
||||
var lineNum = 0;
|
||||
string line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
lineNum++;
|
||||
if (line.Equals(" {"))
|
||||
index--;
|
||||
|
||||
if (index == -1)
|
||||
return lineNum + 1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,201 +1,185 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net5.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ApplicationIcon>FModel.ico</ApplicationIcon>
|
||||
<StartupObject>FModel.App</StartupObject>
|
||||
<Authors>Asval</Authors>
|
||||
<Company></Company>
|
||||
<AssemblyVersion>3.1.2.0</AssemblyVersion>
|
||||
<FileVersion>3.1.2.0</FileVersion>
|
||||
<PackageIcon>FModel.ico</PackageIcon>
|
||||
<PackageIconUrl />
|
||||
<PackageProjectUrl>https://github.com/iAmAsval/FModel</PackageProjectUrl>
|
||||
<Description></Description>
|
||||
<Version>3.1.2</Version>
|
||||
<Platforms>x64</Platforms>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
<Version>4.0.0</Version>
|
||||
<AssemblyVersion>4.0.0.0</AssemblyVersion>
|
||||
<FileVersion>4.0.0.0</FileVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsPublishable>true</IsPublishable>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<NoWarn>1701;1702;NU1701</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<NoWarn>1701;1702;NU1701</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<NoWarn>NU1701</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="FModel.ico" />
|
||||
<None Remove="Resources\alert.ico" />
|
||||
<None Remove="Resources\api-off.ico" />
|
||||
<None Remove="Resources\api.ico" />
|
||||
<None Remove="Resources\backup-restore.png" />
|
||||
<None Remove="Resources\battlebreakers.ico" />
|
||||
<None Remove="Resources\borderlands3.ico" />
|
||||
<None Remove="Resources\BurbankBigCondensed-Bold.ttf" />
|
||||
<None Remove="Resources\check-circle.ico" />
|
||||
<None Remove="Resources\ColorPickerOne.png" />
|
||||
<None Remove="Resources\ColorPickerTwo.png" />
|
||||
<None Remove="Resources\content-copy.png" />
|
||||
<None Remove="Resources\Cpp.xshd" />
|
||||
<None Remove="Resources\delete-forever.png" />
|
||||
<None Remove="Resources\egl2.ico" />
|
||||
<None Remove="Resources\EIconDesign_Default.png" />
|
||||
<None Remove="Resources\EIconDesign_Flat.png" />
|
||||
<None Remove="Resources\EIconDesign_Mini.png" />
|
||||
<None Remove="Resources\EIconDesign_NoBackground.png" />
|
||||
<None Remove="Resources\EIconDesign_NoText.png" />
|
||||
<None Remove="Resources\file-image.ico" />
|
||||
<None Remove="Resources\file-multiple.png" />
|
||||
<None Remove="Resources\file.png" />
|
||||
<None Remove="Resources\fmodel.png" />
|
||||
<None Remove="Resources\folder-download.png" />
|
||||
<None Remove="Resources\fortnite.ico" />
|
||||
<None Remove="Resources\image-plus.png" />
|
||||
<None Remove="Resources\image-remove.png" />
|
||||
<None Remove="Resources\Ini.xshd" />
|
||||
<None Remove="Resources\Json.xshd" />
|
||||
<None Remove="Resources\lock-open-variant.ico" />
|
||||
<None Remove="Resources\minecraftdungeons.ico" />
|
||||
<None Remove="Resources\open-in-new.png" />
|
||||
<None Remove="Resources\pause.png" />
|
||||
<None Remove="Resources\pencil.png" />
|
||||
<None Remove="Resources\play.png" />
|
||||
<None Remove="Resources\playlist-plus.png" />
|
||||
<None Remove="Resources\power.png" />
|
||||
<None Remove="Resources\progress-download.png" />
|
||||
<None Remove="Resources\refresh.png" />
|
||||
<None Remove="Resources\android.png" />
|
||||
<None Remove="Resources\apple.png" />
|
||||
<None Remove="Resources\battlebreakers.png" />
|
||||
<None Remove="Resources\blueprint.png" />
|
||||
<None Remove="Resources\borderlands.png" />
|
||||
<None Remove="Resources\empty_folder.png" />
|
||||
<None Remove="Resources\engine.png" />
|
||||
<None Remove="Resources\fallenorder.png" />
|
||||
<None Remove="Resources\FModel.ico" />
|
||||
<None Remove="Resources\folder.png" />
|
||||
<None Remove="Resources\fortnite.png" />
|
||||
<None Remove="Resources\fortnitebr.png" />
|
||||
<None Remove="Resources\gear.png" />
|
||||
<None Remove="Resources\localization.png" />
|
||||
<None Remove="Resources\materialicon.png" />
|
||||
<None Remove="Resources\pc.png" />
|
||||
<None Remove="Resources\puzzle.png" />
|
||||
<None Remove="Resources\roguecompany.png" />
|
||||
<None Remove="Resources\rotate-3d.png" />
|
||||
<None Remove="Resources\share-all.png" />
|
||||
<None Remove="Resources\share.png" />
|
||||
<None Remove="Resources\sign-direction-plus.png" />
|
||||
<None Remove="Resources\sign-direction-remove.png" />
|
||||
<None Remove="Resources\spellbreak.ico" />
|
||||
<None Remove="Resources\stop.png" />
|
||||
<None Remove="Resources\sod2.ico" />
|
||||
<None Remove="Resources\T-Icon-Pets-64.png" />
|
||||
<None Remove="Resources\T-Icon-Quests-64.png" />
|
||||
<None Remove="Resources\thecycle.ico" />
|
||||
<None Remove="Resources\theouterworlds.png" />
|
||||
<None Remove="Resources\sound.png" />
|
||||
<None Remove="Resources\creative.png" />
|
||||
<None Remove="Resources\spellbreak.png" />
|
||||
<None Remove="Resources\texture.png" />
|
||||
<None Remove="Resources\thecycle.png" />
|
||||
<None Remove="Resources\ui.png" />
|
||||
<None Remove="Resources\valorant.png" />
|
||||
<None Remove="Resources\weapon.png" />
|
||||
<None Remove="Resources\windows.png" />
|
||||
<None Remove="Resources\cinematics.png" />
|
||||
<None Remove="Resources\archive.png" />
|
||||
<None Remove="Resources\archive_enabled.png" />
|
||||
<None Remove="Resources\archive_disabled.png" />
|
||||
<None Remove="Resources\unknown_asset.png" />
|
||||
<None Remove="Resources\asset.png" />
|
||||
<None Remove="Resources\asset_ini.png" />
|
||||
<None Remove="Resources\asset_psd.png" />
|
||||
<None Remove="Resources\asset_png.png" />
|
||||
<None Remove="Resources\athena.png" />
|
||||
<None Remove="Resources\Json.xshd" />
|
||||
<None Remove="Resources\Ini.xshd" />
|
||||
<None Remove="Resources\Xml.xshd" />
|
||||
<None Remove="Resources\Cpp.xshd" />
|
||||
<None Remove="Resources\unix.png" />
|
||||
<None Remove="Resources\linux.png" />
|
||||
<None Remove="Resources\T_Placeholder_Item_Image.png" />
|
||||
<None Remove="Resources\T_ClipSize_Weapon_Stats.png" />
|
||||
<None Remove="Resources\T_DamagePerBullet_Weapon_Stats.png" />
|
||||
<None Remove="Resources\T_Placeholder_Challenge_Image.png" />
|
||||
<None Remove="Resources\T_Placeholder_Item_Image.png" />
|
||||
<None Remove="Resources\T_ReloadTime_Weapon_Stats.png" />
|
||||
<None Remove="Resources\battle-breakers-item-background.png" />
|
||||
<None Remove="Resources\valorant.live.ico" />
|
||||
<None Remove="Resources\volume-minus.png" />
|
||||
<None Remove="Resources\volume-mute.png" />
|
||||
<None Remove="Resources\volume-plus.png" />
|
||||
<None Remove="Resources\wifi-strength-off.ico" />
|
||||
<None Remove="Resources\Xml.xshd" />
|
||||
<None Include="FModel.ico">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath></PackagePath>
|
||||
</None>
|
||||
<None Remove="Resources\T-Icon-Pets-64.png" />
|
||||
<None Remove="Resources\T-Icon-Quests-64.png" />
|
||||
<None Remove="Resources\city_pin.png" />
|
||||
<None Remove="Resources\pin.png" />
|
||||
<None Remove="Resources\Default.png" />
|
||||
<None Remove="Resources\NoBackground.png" />
|
||||
<None Remove="Resources\NoText.png" />
|
||||
<None Remove="Resources\Flat.png" />
|
||||
<None Remove="Resources\Cataba.png" />
|
||||
<None Remove="Resources\BurbankBigCondensed-Bold.ttf" />
|
||||
<None Remove="Resources\add_directory.png" />
|
||||
<None Remove="Resources\delete.png" />
|
||||
<None Remove="Resources\edit.png" />
|
||||
<None Remove="Resources\go_to_directory.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\Cpp.xshd" />
|
||||
<EmbeddedResource Include="Resources\Ini.xshd" />
|
||||
<EmbeddedResource Include="Resources\Json.xshd" />
|
||||
<EmbeddedResource Include="Resources\Ini.xshd" />
|
||||
<EmbeddedResource Include="Resources\Xml.xshd" />
|
||||
<None Remove="Resources\Detex.dll" />
|
||||
<EmbeddedResource Include="Resources\Detex.dll" />
|
||||
<EmbeddedResource Include="Resources\Cpp.xshd" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AdonisUI.ClassicTheme.NET5" Version="1.17.1" />
|
||||
<PackageReference Include="AdonisUI.NET5" Version="1.17.1" />
|
||||
<PackageReference Include="Autoupdater.NET.Official" Version="1.6.4" />
|
||||
<PackageReference Include="AvalonEdit" Version="6.0.1" />
|
||||
<PackageReference Include="CSCore" Version="1.2.1.2" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
|
||||
<PackageReference Include="DotNetZip" Version="1.15.0" />
|
||||
<PackageReference Include="EpicManifestParser" Version="1.2.0" />
|
||||
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.0.2" />
|
||||
<PackageReference Include="Fortnite-API-Wrapper" Version="2.2.0" />
|
||||
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.2.6" />
|
||||
<PackageReference Include="LZMA-SDK" Version="19.0.0" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.19041.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="NVorbis" Version="0.10.1" />
|
||||
<PackageReference Include="Ookii.Dialogs.Wpf" Version="1.2.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.2" />
|
||||
<PackageReference Include="Ookii.Dialogs.Wpf" Version="3.1.0" />
|
||||
<PackageReference Include="RestSharp" Version="106.11.7" />
|
||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
|
||||
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.80.2" />
|
||||
<PackageReference Include="System.Data.HashFunction.CityHash" Version="2.0.0" />
|
||||
<PackageReference Include="ToastNotifications" Version="2.5.1" />
|
||||
<PackageReference Include="ToastNotifications.Messages" Version="2.5.1" />
|
||||
<PackageReference Include="Usmap.NET" Version="1.1.1" />
|
||||
<PackageReference Include="WriteableBitmapEx" Version="1.6.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="FModel.ico" />
|
||||
<Resource Include="Resources\alert.ico" />
|
||||
<Resource Include="Resources\api-off.ico" />
|
||||
<Resource Include="Resources\api.ico" />
|
||||
<Resource Include="Resources\backup-restore.png" />
|
||||
<Resource Include="Resources\battlebreakers.ico" />
|
||||
<Resource Include="Resources\borderlands3.ico" />
|
||||
<Resource Include="Resources\BurbankBigCondensed-Bold.ttf" />
|
||||
<Resource Include="Resources\check-circle.ico" />
|
||||
<Resource Include="Resources\ColorPickerOne.png" />
|
||||
<Resource Include="Resources\ColorPickerTwo.png" />
|
||||
<Resource Include="Resources\content-copy.png" />
|
||||
<Resource Include="Resources\delete-forever.png" />
|
||||
<Resource Include="Resources\egl2.ico" />
|
||||
<Resource Include="Resources\EIconDesign_Default.png" />
|
||||
<Resource Include="Resources\EIconDesign_Flat.png" />
|
||||
<Resource Include="Resources\EIconDesign_Mini.png" />
|
||||
<Resource Include="Resources\EIconDesign_NoBackground.png" />
|
||||
<Resource Include="Resources\EIconDesign_NoText.png" />
|
||||
<Resource Include="Resources\file-image.ico" />
|
||||
<Resource Include="Resources\file-multiple.png" />
|
||||
<Resource Include="Resources\file.png" />
|
||||
<Resource Include="Resources\fmodel.png" />
|
||||
<Resource Include="Resources\folder-download.png" />
|
||||
<Resource Include="Resources\fortnite.ico" />
|
||||
<Resource Include="Resources\image-plus.png" />
|
||||
<Resource Include="Resources\image-remove.png" />
|
||||
<Resource Include="Resources\lock-open-variant.ico" />
|
||||
<Resource Include="Resources\minecraftdungeons.ico" />
|
||||
<Resource Include="Resources\open-in-new.png" />
|
||||
<Resource Include="Resources\pause.png" />
|
||||
<Resource Include="Resources\pencil.png" />
|
||||
<Resource Include="Resources\play.png" />
|
||||
<Resource Include="Resources\playlist-plus.png" />
|
||||
<Resource Include="Resources\power.png" />
|
||||
<Resource Include="Resources\progress-download.png" />
|
||||
<Resource Include="Resources\refresh.png" />
|
||||
<ProjectReference Include="..\CUE4Parse\CUE4Parse-Conversion\CUE4Parse-Conversion.csproj" />
|
||||
<ProjectReference Include="..\CUE4Parse\CUE4Parse-Fortnite\CUE4Parse-Fortnite.csproj" />
|
||||
<ProjectReference Include="..\CUE4Parse\CUE4Parse\CUE4Parse.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\android.png" />
|
||||
<Resource Include="Resources\apple.png" />
|
||||
<Resource Include="Resources\battlebreakers.png" />
|
||||
<Resource Include="Resources\blueprint.png" />
|
||||
<Resource Include="Resources\borderlands.png" />
|
||||
<Resource Include="Resources\fallenorder.png" />
|
||||
<Resource Include="Resources\FModel.ico" />
|
||||
<Resource Include="Resources\folder.png" />
|
||||
<Resource Include="Resources\fortnite.png" />
|
||||
<Resource Include="Resources\fortnitebr.png" />
|
||||
<Resource Include="Resources\empty_folder.png" />
|
||||
<Resource Include="Resources\engine.png" />
|
||||
<Resource Include="Resources\gear.png" />
|
||||
<Resource Include="Resources\localization.png" />
|
||||
<Resource Include="Resources\materialicon.png" />
|
||||
<Resource Include="Resources\pc.png" />
|
||||
<Resource Include="Resources\puzzle.png" />
|
||||
<Resource Include="Resources\roguecompany.png" />
|
||||
<Resource Include="Resources\share-all.png" />
|
||||
<Resource Include="Resources\share.png" />
|
||||
<Resource Include="Resources\sign-direction-plus.png" />
|
||||
<Resource Include="Resources\sign-direction-remove.png" />
|
||||
<Resource Include="Resources\spellbreak.ico" />
|
||||
<Resource Include="Resources\stop.png" />
|
||||
<Resource Include="Resources\sod2.ico" />
|
||||
<Resource Include="Resources\T-Icon-Pets-64.png" />
|
||||
<Resource Include="Resources\T-Icon-Quests-64.png" />
|
||||
<Resource Include="Resources\thecycle.ico" />
|
||||
<Resource Include="Resources\theouterworlds.png" />
|
||||
<Resource Include="Resources\spellbreak.png" />
|
||||
<Resource Include="Resources\sound.png" />
|
||||
<Resource Include="Resources\creative.png" />
|
||||
<Resource Include="Resources\texture.png" />
|
||||
<Resource Include="Resources\thecycle.png" />
|
||||
<Resource Include="Resources\valorant.png" />
|
||||
<Resource Include="Resources\ui.png" />
|
||||
<Resource Include="Resources\weapon.png" />
|
||||
<Resource Include="Resources\windows.png" />
|
||||
<Resource Include="Resources\cinematics.png" />
|
||||
<Resource Include="Resources\archive.png" />
|
||||
<Resource Include="Resources\archive_enabled.png" />
|
||||
<Resource Include="Resources\archive_disabled.png" />
|
||||
<Resource Include="Resources\unknown_asset.png" />
|
||||
<Resource Include="Resources\asset.png" />
|
||||
<Resource Include="Resources\asset_ini.png" />
|
||||
<Resource Include="Resources\asset_psd.png" />
|
||||
<Resource Include="Resources\asset_png.png" />
|
||||
<Resource Include="Resources\athena.png" />
|
||||
<Resource Include="Resources\unix.png" />
|
||||
<Resource Include="Resources\linux.png" />
|
||||
<Resource Include="Resources\T_Placeholder_Item_Image.png" />
|
||||
<Resource Include="Resources\T_ClipSize_Weapon_Stats.png" />
|
||||
<Resource Include="Resources\T_DamagePerBullet_Weapon_Stats.png" />
|
||||
<Resource Include="Resources\T_Placeholder_Challenge_Image.png" />
|
||||
<Resource Include="Resources\T_Placeholder_Item_Image.png" />
|
||||
<Resource Include="Resources\T_ReloadTime_Weapon_Stats.png" />
|
||||
<Resource Include="Resources\battle-breakers-item-background.png" />
|
||||
<Resource Include="Resources\valorant.live.ico" />
|
||||
<Resource Include="Resources\volume-minus.png" />
|
||||
<Resource Include="Resources\volume-mute.png" />
|
||||
<Resource Include="Resources\volume-plus.png" />
|
||||
<Resource Include="Resources\wifi-strength-off.ico" />
|
||||
<Resource Include="Resources\T-Icon-Pets-64.png" />
|
||||
<Resource Include="Resources\T-Icon-Quests-64.png" />
|
||||
<Resource Include="Resources\city_pin.png" />
|
||||
<Resource Include="Resources\pin.png" />
|
||||
<Resource Include="Resources\Default.png" />
|
||||
<Resource Include="Resources\NoBackground.png" />
|
||||
<Resource Include="Resources\NoText.png" />
|
||||
<Resource Include="Resources\Flat.png" />
|
||||
<Resource Include="Resources\Cataba.png" />
|
||||
<Resource Include="Resources\BurbankBigCondensed-Bold.ttf" />
|
||||
<Resource Include="Resources\add_directory.png" />
|
||||
<Resource Include="Resources\delete.png" />
|
||||
<Resource Include="Resources\edit.png" />
|
||||
<Resource Include="Resources\go_to_directory.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -204,11 +188,6 @@
|
|||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Properties\Settings.Designer.cs">
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -218,10 +197,4 @@
|
|||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Properties\Settings.settings">
|
||||
<Generator>PublicSettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
43
FModel/FModel.sln
Normal file
43
FModel/FModel.sln
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30804.86
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FModel", "FModel.csproj", "{B1F494EA-90A6-4C24-800E-2F724A1884CA}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse", "..\CUE4Parse\CUE4Parse\CUE4Parse.csproj", "{C4620341-BBB7-4384-AC7D-5082D3E0386E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse-Fortnite", "..\CUE4Parse\CUE4Parse-Fortnite\CUE4Parse-Fortnite.csproj", "{7765FB4C-B54D-427B-ABB6-1073687E56BD}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse-Conversion", "..\CUE4Parse\CUE4Parse-Conversion\CUE4Parse-Conversion.csproj", "{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{B1F494EA-90A6-4C24-800E-2F724A1884CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B1F494EA-90A6-4C24-800E-2F724A1884CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B1F494EA-90A6-4C24-800E-2F724A1884CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B1F494EA-90A6-4C24-800E-2F724A1884CA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C4620341-BBB7-4384-AC7D-5082D3E0386E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7765FB4C-B54D-427B-ABB6-1073687E56BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7765FB4C-B54D-427B-ABB6-1073687E56BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7765FB4C-B54D-427B-ABB6-1073687E56BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7765FB4C-B54D-427B-ABB6-1073687E56BD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {53DB7A15-4E15-4575-9402-0110BDF2794E}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
33
FModel/Framework/AsyncQueue.cs
Normal file
33
FModel/Framework/AsyncQueue.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks.Dataflow;
|
||||
|
||||
namespace FModel.Framework
|
||||
{
|
||||
public class AsyncQueue<T> : IAsyncEnumerable<T>
|
||||
{
|
||||
private readonly SemaphoreSlim _semaphore = new(1);
|
||||
private readonly BufferBlock<T> _buffer = new();
|
||||
|
||||
public int Count => _buffer.Count;
|
||||
|
||||
public void Enqueue(T item) => _buffer.Post(item);
|
||||
|
||||
public async IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken token = default)
|
||||
{
|
||||
await _semaphore.WaitAsync(token);
|
||||
try
|
||||
{
|
||||
while (Count > 0)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
yield return await _buffer.ReceiveAsync(token);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
FModel/Framework/Command.cs
Normal file
19
FModel/Framework/Command.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace FModel.Framework
|
||||
{
|
||||
public abstract class Command : ICommand
|
||||
{
|
||||
public abstract void Execute(object parameter);
|
||||
|
||||
public abstract bool CanExecute(object parameter);
|
||||
|
||||
public void RaiseCanExecuteChanged()
|
||||
{
|
||||
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public event EventHandler CanExecuteChanged;
|
||||
}
|
||||
}
|
||||
96
FModel/Framework/CustomSKShaper.cs
Normal file
96
FModel/Framework/CustomSKShaper.cs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
using System;
|
||||
using HarfBuzzSharp;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.HarfBuzz;
|
||||
using Buffer = HarfBuzzSharp.Buffer;
|
||||
|
||||
namespace FModel.Framework
|
||||
{
|
||||
public class CustomSKShaper : SKShaper
|
||||
{
|
||||
private const int _FONT_SIZE_SCALE = 512;
|
||||
private readonly Font _font;
|
||||
private readonly Buffer _buffer;
|
||||
|
||||
public CustomSKShaper(SKTypeface typeface) : base(typeface)
|
||||
{
|
||||
using (var blob = Typeface.OpenStream(out var index).ToHarfBuzzBlob())
|
||||
using (var face = new Face(blob, index))
|
||||
{
|
||||
face.Index = index;
|
||||
face.UnitsPerEm = Typeface.UnitsPerEm;
|
||||
|
||||
_font = new Font(face);
|
||||
_font.SetScale(_FONT_SIZE_SCALE, _FONT_SIZE_SCALE);
|
||||
_font.SetFunctionsOpenType();
|
||||
}
|
||||
|
||||
_buffer = new Buffer();
|
||||
}
|
||||
|
||||
public new Result Shape(Buffer buffer, float xOffset, float yOffset, SKPaint paint)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
|
||||
if (paint == null)
|
||||
throw new ArgumentNullException(nameof(paint));
|
||||
|
||||
// do the shaping
|
||||
_font.Shape(buffer);
|
||||
|
||||
// get the shaping results
|
||||
var len = buffer.Length;
|
||||
var info = buffer.GlyphInfos;
|
||||
var pos = buffer.GlyphPositions;
|
||||
|
||||
// get the sizes
|
||||
var textSizeY = paint.TextSize / _FONT_SIZE_SCALE;
|
||||
var textSizeX = textSizeY * paint.TextScaleX;
|
||||
|
||||
var points = new SKPoint[len];
|
||||
var clusters = new uint[len];
|
||||
var codepoints = new uint[len];
|
||||
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
// move the cursor
|
||||
xOffset += pos[i].XAdvance * textSizeX;
|
||||
yOffset += pos[i].YAdvance * textSizeY;
|
||||
|
||||
codepoints[i] = info[i].Codepoint;
|
||||
clusters[i] = info[i].Cluster;
|
||||
points[i] = new SKPoint(xOffset + pos[i].XOffset * textSizeX, yOffset - pos[i].YOffset * textSizeY);
|
||||
}
|
||||
|
||||
return new Result(codepoints, clusters, points);
|
||||
}
|
||||
|
||||
public new Result Shape(string text, SKPaint paint) => Shape(text, 0, 0, paint);
|
||||
|
||||
public new Result Shape(string text, float xOffset, float yOffset, SKPaint paint)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return new Result();
|
||||
|
||||
using var buffer = new Buffer();
|
||||
switch (paint.TextEncoding)
|
||||
{
|
||||
case SKTextEncoding.Utf8:
|
||||
buffer.AddUtf8(text);
|
||||
break;
|
||||
case SKTextEncoding.Utf16:
|
||||
buffer.AddUtf16(text);
|
||||
break;
|
||||
case SKTextEncoding.Utf32:
|
||||
buffer.AddUtf32(text);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException("TextEncoding of type GlyphId is not supported.");
|
||||
}
|
||||
|
||||
buffer.GuessSegmentProperties();
|
||||
return Shape(buffer, xOffset, yOffset, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
117
FModel/Framework/FullyObservableCollection.cs
Normal file
117
FModel/Framework/FullyObservableCollection.cs
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace FModel.Framework
|
||||
{
|
||||
public class FullyObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when a property is changed within an item.
|
||||
/// </summary>
|
||||
public event EventHandler<ItemPropertyChangedEventArgs> ItemPropertyChanged;
|
||||
|
||||
public FullyObservableCollection()
|
||||
{
|
||||
}
|
||||
|
||||
public FullyObservableCollection(List<T> list) : base(list)
|
||||
{
|
||||
ObserveAll();
|
||||
}
|
||||
|
||||
public FullyObservableCollection(IEnumerable<T> enumerable) : base(enumerable)
|
||||
{
|
||||
ObserveAll();
|
||||
}
|
||||
|
||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (e.Action is NotifyCollectionChangedAction.Remove or
|
||||
NotifyCollectionChangedAction.Replace)
|
||||
{
|
||||
foreach (T item in e.OldItems)
|
||||
item.PropertyChanged -= ChildPropertyChanged;
|
||||
}
|
||||
|
||||
if (e.Action is NotifyCollectionChangedAction.Add or
|
||||
NotifyCollectionChangedAction.Replace)
|
||||
{
|
||||
foreach (T item in e.NewItems)
|
||||
item.PropertyChanged += ChildPropertyChanged;
|
||||
}
|
||||
|
||||
base.OnCollectionChanged(e);
|
||||
}
|
||||
|
||||
protected void OnItemPropertyChanged(ItemPropertyChangedEventArgs e)
|
||||
{
|
||||
ItemPropertyChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected void OnItemPropertyChanged(int index, PropertyChangedEventArgs e)
|
||||
{
|
||||
OnItemPropertyChanged(new ItemPropertyChangedEventArgs(index, e));
|
||||
}
|
||||
|
||||
protected override void ClearItems()
|
||||
{
|
||||
foreach (T item in Items)
|
||||
item.PropertyChanged -= ChildPropertyChanged;
|
||||
|
||||
base.ClearItems();
|
||||
}
|
||||
|
||||
private void ObserveAll()
|
||||
{
|
||||
foreach (T item in Items)
|
||||
item.PropertyChanged += ChildPropertyChanged;
|
||||
}
|
||||
|
||||
private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
var typedSender = (T) sender;
|
||||
var i = Items.IndexOf(typedSender);
|
||||
|
||||
if (i < 0)
|
||||
throw new ArgumentException("Received property notification from item not in collection");
|
||||
|
||||
OnItemPropertyChanged(i, e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for the <see cref="FullyObservableCollection{T}.ItemPropertyChanged"/> event.
|
||||
/// </summary>
|
||||
public class ItemPropertyChangedEventArgs : PropertyChangedEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the index in the collection for which the property change has occurred.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// Index in parent collection.
|
||||
/// </value>
|
||||
public int CollectionIndex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="index">The index in the collection of changed item.</param>
|
||||
/// <param name="name">The name of the property that changed.</param>
|
||||
public ItemPropertyChangedEventArgs(int index, string name) : base(name)
|
||||
{
|
||||
CollectionIndex = index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <param name="args">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
|
||||
public ItemPropertyChangedEventArgs(int index, PropertyChangedEventArgs args) : this(index, args.PropertyName)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
50
FModel/Framework/Hotkey.cs
Normal file
50
FModel/Framework/Hotkey.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
using System.Text;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace FModel.Framework
|
||||
{
|
||||
public class Hotkey : ViewModel
|
||||
{
|
||||
private Key _key;
|
||||
public Key Key
|
||||
{
|
||||
get => _key;
|
||||
set => SetProperty(ref _key, value);
|
||||
}
|
||||
|
||||
private ModifierKeys _modifiers;
|
||||
public ModifierKeys Modifiers
|
||||
{
|
||||
get => _modifiers;
|
||||
set => SetProperty(ref _modifiers, value);
|
||||
}
|
||||
|
||||
public Hotkey(Key key, ModifierKeys modifiers = ModifierKeys.None)
|
||||
{
|
||||
Key = key;
|
||||
Modifiers = modifiers;
|
||||
}
|
||||
|
||||
public bool IsTriggered(Key e)
|
||||
{
|
||||
return e == Key && Keyboard.Modifiers.HasFlag(Modifiers);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var str = new StringBuilder();
|
||||
|
||||
if (Modifiers.HasFlag(ModifierKeys.Control))
|
||||
str.Append("Ctrl + ");
|
||||
if (Modifiers.HasFlag(ModifierKeys.Shift))
|
||||
str.Append("Shift + ");
|
||||
if (Modifiers.HasFlag(ModifierKeys.Alt))
|
||||
str.Append("Alt + ");
|
||||
if (Modifiers.HasFlag(ModifierKeys.Windows))
|
||||
str.Append("Win + ");
|
||||
|
||||
str.Append(Key);
|
||||
return str.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
43
FModel/Framework/JsonNetSerializer.cs
Normal file
43
FModel/Framework/JsonNetSerializer.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using RestSharp;
|
||||
using RestSharp.Serialization;
|
||||
|
||||
namespace FModel.Framework
|
||||
{
|
||||
public class JsonNetSerializer : IRestSerializer
|
||||
{
|
||||
public static readonly JsonSerializerSettings SerializerSettings = new()
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
MissingMemberHandling = MissingMemberHandling.Ignore,
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
};
|
||||
|
||||
public string Serialize(object obj)
|
||||
{
|
||||
return JsonConvert.SerializeObject(obj);
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public string Serialize(Parameter parameter)
|
||||
{
|
||||
return JsonConvert.SerializeObject(parameter.Value);
|
||||
}
|
||||
|
||||
public T Deserialize<T>(IRestResponse response)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(response.Content, SerializerSettings);
|
||||
}
|
||||
|
||||
public string[] SupportedContentTypes { get; } =
|
||||
{
|
||||
"application/json", "application/json; charset=UTF-8"
|
||||
};
|
||||
|
||||
public string ContentType { get; set; } = "application/json; charset=UTF-8";
|
||||
|
||||
public DataFormat DataFormat => DataFormat.Json;
|
||||
}
|
||||
}
|
||||
42
FModel/Framework/RangeObservableCollection.cs
Normal file
42
FModel/Framework/RangeObservableCollection.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace FModel.Framework
|
||||
{
|
||||
public sealed class RangeObservableCollection<T> : ObservableCollection<T>
|
||||
{
|
||||
private bool _suppressNotification;
|
||||
|
||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (!_suppressNotification)
|
||||
base.OnCollectionChanged(e);
|
||||
}
|
||||
|
||||
public void AddRange(IEnumerable<T> list)
|
||||
{
|
||||
if (list == null)
|
||||
throw new ArgumentNullException(nameof(list));
|
||||
|
||||
_suppressNotification = true;
|
||||
|
||||
foreach (var item in list)
|
||||
Add(item);
|
||||
|
||||
_suppressNotification = false;
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
public void SetSuppressionState(bool state)
|
||||
{
|
||||
_suppressNotification = state;
|
||||
}
|
||||
|
||||
public void InvokeOnCollectionChanged(NotifyCollectionChangedAction changedAction = NotifyCollectionChangedAction.Reset)
|
||||
{
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(changedAction));
|
||||
}
|
||||
}
|
||||
}
|
||||
76
FModel/Framework/ViewModel.cs
Normal file
76
FModel/Framework/ViewModel.cs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace FModel.Framework
|
||||
{
|
||||
public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo, IDataErrorInfo
|
||||
{
|
||||
private readonly Dictionary<string, IList<string>> _validationErrors = new();
|
||||
|
||||
public string this[string propertyName]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(propertyName))
|
||||
return Error;
|
||||
|
||||
return _validationErrors.ContainsKey(propertyName) ? string.Join(Environment.NewLine, _validationErrors[propertyName]) : string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore] public string Error => string.Join(Environment.NewLine, GetAllErrors());
|
||||
[JsonIgnore] public bool HasErrors => _validationErrors.Any();
|
||||
|
||||
public IEnumerable GetErrors(string propertyName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(propertyName))
|
||||
return _validationErrors.SelectMany(kvp => kvp.Value);
|
||||
|
||||
return _validationErrors.TryGetValue(propertyName, out var errors) ? errors : Enumerable.Empty<object>();
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetAllErrors()
|
||||
{
|
||||
return _validationErrors.SelectMany(kvp => kvp.Value).Where(e => !string.IsNullOrEmpty(e));
|
||||
}
|
||||
|
||||
public void AddValidationError(string propertyName, string errorMessage)
|
||||
{
|
||||
if (!_validationErrors.ContainsKey(propertyName))
|
||||
_validationErrors.Add(propertyName, new List<string>());
|
||||
|
||||
_validationErrors[propertyName].Add(errorMessage);
|
||||
}
|
||||
|
||||
public void ClearValidationErrors(string propertyName)
|
||||
{
|
||||
if (_validationErrors.ContainsKey(propertyName))
|
||||
_validationErrors.Remove(propertyName);
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
#pragma warning disable 67
|
||||
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
|
||||
#pragma warning disable 67
|
||||
|
||||
protected void RaisePropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(storage, value))
|
||||
return false;
|
||||
|
||||
storage = value;
|
||||
RaisePropertyChanged(propertyName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
50
FModel/Framework/ViewModelCommand.cs
Normal file
50
FModel/Framework/ViewModelCommand.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
|
||||
namespace FModel.Framework
|
||||
{
|
||||
public abstract class ViewModelCommand<TContextViewModel> : Command where TContextViewModel : ViewModel
|
||||
{
|
||||
private WeakReference _parent;
|
||||
|
||||
public TContextViewModel ContextViewModel
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_parent is {IsAlive: true})
|
||||
return (TContextViewModel) _parent.Target;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private set
|
||||
{
|
||||
if (ContextViewModel == value)
|
||||
return;
|
||||
|
||||
_parent = value != null ? new WeakReference(value) : null;
|
||||
}
|
||||
}
|
||||
|
||||
protected ViewModelCommand(TContextViewModel contextViewModel)
|
||||
{
|
||||
ContextViewModel = contextViewModel /*?? throw new ArgumentNullException(nameof(contextViewModel))*/;
|
||||
}
|
||||
|
||||
public sealed override void Execute(object parameter)
|
||||
{
|
||||
Execute(ContextViewModel, parameter);
|
||||
}
|
||||
|
||||
public abstract void Execute(TContextViewModel contextViewModel, object parameter);
|
||||
|
||||
public sealed override bool CanExecute(object parameter)
|
||||
{
|
||||
return CanExecute(ContextViewModel, parameter);
|
||||
}
|
||||
|
||||
public virtual bool CanExecute(TContextViewModel contextViewModel, object parameter)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using FModel.PakReader.IO;
|
||||
using FModel.PakReader.Pak;
|
||||
using FModel.PakReader.Parsers.Objects;
|
||||
using FModel.Properties;
|
||||
using ToastNotifications;
|
||||
using ToastNotifications.Lifetime;
|
||||
using ToastNotifications.Position;
|
||||
using UsmapNET.Classes;
|
||||
|
||||
namespace FModel
|
||||
{
|
||||
static class Globals
|
||||
{
|
||||
/// <summary>
|
||||
/// string is the pak file name
|
||||
/// PakFileReader is the reader where you can grab the FPakEntries, MountPoint and more
|
||||
/// </summary>
|
||||
public static readonly Dictionary<string, PakFileReader> CachedPakFiles = new Dictionary<string, PakFileReader>();
|
||||
public static readonly Dictionary<string, FFileIoStoreReader> CachedIoStores = new Dictionary<string, FFileIoStoreReader>();
|
||||
public static readonly Dictionary<string, FUnversionedType> CachedSchemas = new Dictionary<string, FUnversionedType>();
|
||||
public static Usmap Usmap = null;
|
||||
public static FIoGlobalData GlobalData = null;
|
||||
public static readonly Notifier gNotifier = new Notifier(cfg =>
|
||||
{
|
||||
cfg.LifetimeSupervisor = new TimeAndCountBasedLifetimeSupervisor(TimeSpan.FromSeconds(7), MaximumNotificationCount.FromCount(15));
|
||||
cfg.PositionProvider = new PrimaryScreenPositionProvider(Corner.BottomRight, 5, 5);
|
||||
cfg.Dispatcher = Application.Current.Dispatcher;
|
||||
});
|
||||
public static bool bSearch = false; // trigger the event to select a file thank to the search window
|
||||
public static string sSearch = string.Empty; // this will be the file name triggered
|
||||
public static FGame Game = new FGame(EGame.Unknown, EPakVersion.LATEST, 0);
|
||||
public const EFModel Build =
|
||||
#if RELEASE
|
||||
EFModel.Release;
|
||||
#elif DEBUG
|
||||
EFModel.Debug;
|
||||
#else
|
||||
EFModel.Unknown;
|
||||
#endif
|
||||
}
|
||||
|
||||
public class FGame
|
||||
{
|
||||
public EGame ActualGame;
|
||||
public EPakVersion Version;
|
||||
public int SubVersion;
|
||||
|
||||
public FGame(EGame game, EPakVersion version, int subVersion)
|
||||
{
|
||||
ActualGame = game;
|
||||
Version = version;
|
||||
SubVersion = subVersion;
|
||||
}
|
||||
|
||||
public string GetName()
|
||||
{
|
||||
return ActualGame switch
|
||||
{
|
||||
EGame.Fortnite => Resources.GameName_Fortnite,
|
||||
EGame.Valorant => Resources.GameName_Valorant,
|
||||
EGame.DeadByDaylight => Resources.GameName_DeadByDaylight,
|
||||
EGame.Borderlands3 => Resources.GameName_Borderlands3,
|
||||
EGame.MinecraftDungeons => Resources.GameName_MinecraftDungeons,
|
||||
EGame.BattleBreakers => Resources.GameName_BattleBreakers,
|
||||
EGame.Spellbreak => Resources.GameName_Spellbreak,
|
||||
EGame.StateOfDecay2 => Resources.GameName_StateofDecay2,
|
||||
EGame.TheCycle => Resources.GameName_TheCycle,
|
||||
EGame.TheOuterWorlds => Resources.GameName_TheOuterWorlds,
|
||||
EGame.RogueCompany => Resources.GameName_RogueCompany,
|
||||
EGame.Unknown => "Unknown",
|
||||
_ => "Unknown"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static class FColors
|
||||
{
|
||||
public const string Red = "#E06C75";
|
||||
public const string Orange = "#D19A66";
|
||||
public const string Yellow = "#E5C07B";
|
||||
public const string Purple = "#C678DD";
|
||||
public const string Blue = "#61AFEF";
|
||||
public const string Discord = "#8B9BD4";
|
||||
public const string Green = "#98C379";
|
||||
public const string LightGray = "#BBBBBB";
|
||||
public const string DarkGray = "#9B9B9B";
|
||||
public const string White = "#EFEFEF";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
using FModel.Logger;
|
||||
using FModel.Utils;
|
||||
using FModel.Windows.CustomNotifier;
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FModel.Grabber.Aes
|
||||
{
|
||||
static class AesData
|
||||
{
|
||||
public static async Task<BenResponse> GetData()
|
||||
{
|
||||
if (NetworkInterface.GetIsNetworkAvailable())
|
||||
{
|
||||
BenResponse data = await Endpoints.GetJsonEndpoint<BenResponse>(Endpoints.BENBOT_AES, string.Empty).ConfigureAwait(false);
|
||||
return data;
|
||||
}
|
||||
else
|
||||
{
|
||||
Globals.gNotifier.ShowCustomMessage(Properties.Resources.AES, Properties.Resources.NoInternet, "/FModel;component/Resources/wifi-strength-off.ico");
|
||||
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[AES]", "No internet");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class BenResponse
|
||||
{
|
||||
[JsonProperty("mainKey")]
|
||||
public string MainKey { get; set; }
|
||||
|
||||
[JsonProperty("dynamicKeys")]
|
||||
public Dictionary<string, string> DynamicKeys { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
using FModel.Logger;
|
||||
using FModel.ViewModels.MenuItem;
|
||||
using FModel.Windows.CustomNotifier;
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FModel.Grabber.Aes
|
||||
{
|
||||
static class AesGrabber
|
||||
{
|
||||
public static async Task<bool> Load(bool forceReload = false)
|
||||
{
|
||||
if (Globals.Game.ActualGame == EGame.Fortnite && MenuItems.pakFiles.AtLeastOnePak())
|
||||
{
|
||||
if (forceReload)
|
||||
{
|
||||
Dictionary<string, string> staticKeys = new Dictionary<string, string>();
|
||||
if (!string.IsNullOrEmpty(Properties.Settings.Default.StaticAesKeys))
|
||||
staticKeys = JsonConvert.DeserializeObject<Dictionary<string, string>>(Properties.Settings.Default.StaticAesKeys);
|
||||
|
||||
Dictionary<string, Dictionary<string, string>> oldDynamicKeys = new Dictionary<string, Dictionary<string, string>>();
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Properties.Settings.Default.DynamicAesKeys))
|
||||
oldDynamicKeys = JsonConvert.DeserializeObject<Dictionary<string, Dictionary<string, string>>>(Properties.Settings.Default.DynamicAesKeys);
|
||||
}
|
||||
catch (JsonSerializationException) { /* Needed for the transition bewteen global dynamic keys and "per game" dynamic keys */ }
|
||||
|
||||
BenResponse benResponse = await AesData.GetData().ConfigureAwait(false);
|
||||
if (benResponse != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(benResponse.MainKey))
|
||||
{
|
||||
string mainKey = $"0x{benResponse.MainKey[2..].ToUpper()}";
|
||||
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[AES]", $"BenBot Main key is {mainKey}");
|
||||
staticKeys[Globals.Game.ActualGame.ToString()] = mainKey;
|
||||
Properties.Settings.Default.StaticAesKeys = JsonConvert.SerializeObject(staticKeys, Formatting.None);
|
||||
}
|
||||
|
||||
if (oldDynamicKeys.TryGetValue(Globals.Game.ActualGame.ToString(), out var gameDict))
|
||||
{
|
||||
Dictionary<string, string> difference = benResponse.DynamicKeys
|
||||
.Where(x => !x.Key.Contains("optional") && (!gameDict.ContainsKey(x.Key) || !gameDict[x.Key].Equals(x.Value)))
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
foreach (KeyValuePair<string, string> KvP in difference)
|
||||
{
|
||||
Globals.gNotifier.ShowCustomMessage(
|
||||
Properties.Resources.PakFiles,
|
||||
string.Format(
|
||||
Properties.Resources.PakCanBeOpened,
|
||||
KvP.Key[(KvP.Key.IndexOf("Paks/") + "Paks/".Length)..]),
|
||||
"/FModel;component/Resources/lock-open-variant.ico");
|
||||
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[AES]", $"{KvP.Key} with key {KvP.Value} can be opened");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (key, value) in benResponse.DynamicKeys.ToList())
|
||||
{
|
||||
if (key.Contains("optional"))
|
||||
{
|
||||
if (!benResponse.DynamicKeys.TryGetValue(key.Replace("optional", ""), out string _))
|
||||
benResponse.DynamicKeys[key.Replace("optional", "")] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!benResponse.DynamicKeys.TryGetValue(key.Replace("-WindowsClient", "optional-WindowsClient"), out string _))
|
||||
benResponse.DynamicKeys[key.Replace("-WindowsClient", "optional-WindowsClient")] = value;
|
||||
}
|
||||
}
|
||||
|
||||
oldDynamicKeys[Globals.Game.ActualGame.ToString()] = benResponse.DynamicKeys;
|
||||
Properties.Settings.Default.DynamicAesKeys = JsonConvert.SerializeObject(oldDynamicKeys, Formatting.None);
|
||||
Properties.Settings.Default.Save();
|
||||
|
||||
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[AES]", $"BenBot Dynamic keys are {Properties.Settings.Default.DynamicAesKeys}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
using AutoUpdaterDotNET;
|
||||
using FModel.Logger;
|
||||
using FModel.Utils;
|
||||
using FModel.Windows.CustomNotifier;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FModel.Grabber.Cdn
|
||||
{
|
||||
static class CdnData
|
||||
{
|
||||
public static readonly bool bInternet = NetworkInterface.GetIsNetworkAvailable();
|
||||
|
||||
public static async Task<CdnResponse> GetData()
|
||||
{
|
||||
if (bInternet)
|
||||
return await Endpoints.GetJsonEndpoint<CdnResponse>(Endpoints.FMODEL_JSON).ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
Globals.gNotifier.ShowCustomMessage("CDN", Properties.Resources.NoInternet, "/FModel;component/Resources/wifi-strength-off.ico");
|
||||
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[CDN]", "No internet");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CdnResponse
|
||||
{
|
||||
public Dictionary<string, Backup[]> Backups { get; set; }
|
||||
public Dictionary<string, GlobalMessage[]> GlobalMessages { get; set; }
|
||||
public FUpdater Updater { get; set; }
|
||||
}
|
||||
public class Backup
|
||||
{
|
||||
public string Header { get; set; }
|
||||
public string DownloadUrl { get; set; }
|
||||
public double Size { get; set; }
|
||||
}
|
||||
public class GlobalMessage
|
||||
{
|
||||
public string Message { get; set; }
|
||||
public string Color { get; set; }
|
||||
public bool NewLine { get; set; }
|
||||
}
|
||||
public class FUpdater
|
||||
{
|
||||
public string Version { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string Changelog { get; set; }
|
||||
public Mandatory Mandatory { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
using FModel.Logger;
|
||||
using FModel.Utils;
|
||||
using FModel.ViewModels.MenuItem;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace FModel.Grabber.Cdn
|
||||
{
|
||||
static class CdnDataGrabber
|
||||
{
|
||||
private static CdnResponse _data = null; // avoid multiple requests on same endpoint
|
||||
|
||||
public static async Task DoCDNStuff()
|
||||
{
|
||||
await PopulateBackups().ConfigureAwait(false); // step by step
|
||||
await ShowGMessages().ConfigureAwait(false); // step by step
|
||||
}
|
||||
|
||||
public static async Task PopulateBackups()
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(delegate
|
||||
{
|
||||
// Backup PAKs Menu Item
|
||||
MenuItems.backupFiles.Add(new BackupMenuItemViewModel
|
||||
{
|
||||
Header = Properties.Resources.BackupPaks,
|
||||
Icon = new Image { Source = new BitmapImage(new Uri("Resources/backup-restore.png", UriKind.Relative)) }
|
||||
});
|
||||
MenuItems.backupFiles.Add(new Separator { });
|
||||
});
|
||||
|
||||
List<BackupMenuItemViewModel> backupsInfos = await GetBackups().ConfigureAwait(false);
|
||||
if (backupsInfos.Any())
|
||||
{
|
||||
foreach (BackupMenuItemViewModel b in backupsInfos)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(delegate
|
||||
{
|
||||
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[CDN]", $"{b.Header} is available to download");
|
||||
MenuItems.backupFiles.Add(b);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<FUpdater> GetUpdateData()
|
||||
{
|
||||
if (_data == null)
|
||||
_data = await CdnData.GetData().ConfigureAwait(false);
|
||||
|
||||
if (_data != null)
|
||||
{
|
||||
return _data.Updater;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static async Task<List<BackupMenuItemViewModel>> GetBackups()
|
||||
{
|
||||
if (_data == null)
|
||||
_data = await CdnData.GetData().ConfigureAwait(false);
|
||||
|
||||
if (_data != null && _data.Backups.TryGetValue(Globals.Game.ActualGame.ToString(), out var backups))
|
||||
{
|
||||
return JsonConvert.DeserializeObject<List<BackupMenuItemViewModel>>(JsonConvert.SerializeObject(backups));
|
||||
}
|
||||
return new List<BackupMenuItemViewModel>();
|
||||
}
|
||||
|
||||
public static async Task ShowGMessages()
|
||||
{
|
||||
Dictionary<string, GlobalMessage[]> globalMessages = await GetGlobalMessages().ConfigureAwait(false);
|
||||
if (globalMessages.Any())
|
||||
{
|
||||
string version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
if (globalMessages.ContainsKey(version))
|
||||
if (!string.IsNullOrEmpty(globalMessages[version][0].Message))
|
||||
foreach (GlobalMessage gm in globalMessages[version])
|
||||
FConsole.AppendText(gm.Message, gm.Color, gm.NewLine);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<Dictionary<string, GlobalMessage[]>> GetGlobalMessages()
|
||||
{
|
||||
if (_data == null && CdnData.bInternet) // if data is still null after getting backups, that means there's no internet, do not try again
|
||||
_data = await CdnData.GetData().ConfigureAwait(false); // ^ will also avoid showing "No internet" notifier twice
|
||||
|
||||
if (_data != null)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<Dictionary<string, GlobalMessage[]>>(JsonConvert.SerializeObject(_data.GlobalMessages));
|
||||
}
|
||||
return new Dictionary<string, GlobalMessage[]>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
using EpicManifestParser.Objects;
|
||||
|
||||
using FModel.Utils;
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FModel.Grabber.Manifests
|
||||
{
|
||||
static class ManifestGrabber
|
||||
{
|
||||
public static async Task<ManifestInfo> TryGetLatestManifestInfo()
|
||||
{
|
||||
if (IsExpired())
|
||||
{
|
||||
OAuth auth = await Endpoints.GetOAuthInfo().ConfigureAwait(false);
|
||||
|
||||
if (auth != null)
|
||||
{
|
||||
Properties.Settings.Default.AccessToken = auth.AccessToken;
|
||||
Properties.Settings.Default.LauncherExpiration = DateTimeOffset.Now.AddSeconds(Convert.ToDouble(auth.ExpiresIn)).ToUnixTimeMilliseconds();
|
||||
Properties.Settings.Default.Save();
|
||||
}
|
||||
}
|
||||
|
||||
string ret = await Endpoints.GetStringEndpoint("https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/v2/platform/Windows/namespace/fn/catalogItem/4fe75bbc5a674f4f9b356b5c90567da5/app/Fortnite/label/Live", Properties.Settings.Default.AccessToken).ConfigureAwait(false);
|
||||
return string.IsNullOrEmpty(ret) ? null : new ManifestInfo(ret);
|
||||
}
|
||||
|
||||
private static bool IsExpired()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Properties.Settings.Default.AccessToken)) return true;
|
||||
|
||||
long currentTime = DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
||||
return currentTime - 60000 >= Properties.Settings.Default.LauncherExpiration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,780 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FModel.PakReader;
|
||||
using FModel.Utils;
|
||||
|
||||
using Ionic.Zlib;
|
||||
|
||||
namespace FModel.Grabber.Manifests
|
||||
{
|
||||
public class ValorantAPIManifestV1
|
||||
{
|
||||
private const string _url = "https://fmodel.fortnite-api.com/valorant/v1/manifest";
|
||||
|
||||
private readonly HttpClient _client;
|
||||
private readonly DirectoryInfo _chunkDirectory;
|
||||
|
||||
public readonly ulong Id;
|
||||
public readonly Dictionary<ulong, ValorantChunkV1> Chunks;
|
||||
public readonly ValorantPakV1[] Paks;
|
||||
|
||||
public ValorantAPIManifestV1(byte[] data, DirectoryInfo directoryInfo) : this(new MemoryStream(data, false), directoryInfo) { }
|
||||
public ValorantAPIManifestV1(Stream stream, DirectoryInfo directoryInfo) : this(new BinaryReader(stream), directoryInfo) { }
|
||||
public ValorantAPIManifestV1(BinaryReader reader, DirectoryInfo directoryInfo)
|
||||
{
|
||||
using (reader)
|
||||
{
|
||||
Id = reader.ReadUInt64();
|
||||
var chunks = reader.ReadInt32();
|
||||
Chunks = new Dictionary<ulong, ValorantChunkV1>(chunks);
|
||||
|
||||
for (var i = 0; i < chunks; i++)
|
||||
{
|
||||
var chunk = new ValorantChunkV1(reader);
|
||||
Chunks.Add(chunk.Id, chunk);
|
||||
}
|
||||
|
||||
Paks = reader.ReadTArray(() => new ValorantPakV1(reader));
|
||||
}
|
||||
|
||||
_client = new HttpClient(new HttpClientHandler
|
||||
{
|
||||
UseProxy = false,
|
||||
UseCookies = false,
|
||||
AutomaticDecompression = DecompressionMethods.All,
|
||||
CheckCertificateRevocationList = false,
|
||||
PreAuthenticate = false,
|
||||
MaxConnectionsPerServer = 1337
|
||||
});
|
||||
_chunkDirectory = directoryInfo;
|
||||
}
|
||||
|
||||
public Stream GetPakStream(int index)
|
||||
{
|
||||
return new ValorantPakV1Stream(this, index);
|
||||
}
|
||||
|
||||
public async Task PrefetchChunk(ValorantChunkV1 chunk, CancellationToken cancellationToken)
|
||||
{
|
||||
var chunkPath = Path.Combine(_chunkDirectory.FullName, $"{chunk.Id}.valchunk");
|
||||
|
||||
if (File.Exists(chunkPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, chunk.Url);
|
||||
using var response = await _client.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
var chunkBytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
|
||||
await using var fs = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
await fs.WriteAsync(chunkBytes, 0, chunkBytes.Length, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
#if DEBUG
|
||||
else
|
||||
{
|
||||
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
Debugger.Break();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public async Task<byte[]> GetChunkBytes(ValorantChunkV1 chunk, CancellationToken cancellationToken)
|
||||
{
|
||||
var chunkPath = Path.Combine(_chunkDirectory.FullName, $"{chunk.Id}.valchunk");
|
||||
byte[] chunkBytes;
|
||||
|
||||
if (File.Exists(chunkPath))
|
||||
{
|
||||
chunkBytes = new byte[chunk.Size];
|
||||
await using var fs = new FileStream(chunkPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
await fs.ReadAsync(chunkBytes, 0, chunkBytes.Length, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, chunk.Url);
|
||||
using var response = await _client.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
chunkBytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
|
||||
await using var fs = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
await fs.WriteAsync(chunkBytes, 0, chunkBytes.Length, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if DEBUG
|
||||
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
Debugger.Break();
|
||||
#endif
|
||||
chunkBytes = null;
|
||||
}
|
||||
}
|
||||
|
||||
return chunkBytes;
|
||||
}
|
||||
|
||||
public static async Task<ValorantAPIManifestV1> DownloadAndParse(DirectoryInfo directoryInfo)
|
||||
{
|
||||
using var client = new HttpClient(new HttpClientHandler
|
||||
{
|
||||
UseProxy = false,
|
||||
UseCookies = false,
|
||||
AutomaticDecompression = DecompressionMethods.All,
|
||||
CheckCertificateRevocationList = false,
|
||||
PreAuthenticate = false
|
||||
});
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, _url);
|
||||
|
||||
try
|
||||
{
|
||||
using var response = await client.SendAsync(request).ConfigureAwait(false);
|
||||
|
||||
if (response.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var responseStream = await response.Content.ReadAsStreamAsync();
|
||||
return new ValorantAPIManifestV1(responseStream, directoryInfo);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ValorantAPIManifestV2
|
||||
{
|
||||
private const string _url = "https://fmodel.fortnite-api.com/valorant/v2/manifest";
|
||||
|
||||
private readonly HttpClient _client;
|
||||
private readonly DirectoryInfo _chunkDirectory;
|
||||
|
||||
public readonly ValorantAPIManifestHeaderV2 Header;
|
||||
public readonly ValorantChunkV2[] Chunks;
|
||||
public readonly ValorantPakV2[] Paks;
|
||||
|
||||
public ValorantAPIManifestV2(byte[] data, DirectoryInfo directoryInfo) : this(new MemoryStream(data, false), directoryInfo) { }
|
||||
public ValorantAPIManifestV2(Stream stream, DirectoryInfo directoryInfo) : this(new BinaryReader(stream), directoryInfo) { }
|
||||
public ValorantAPIManifestV2(BinaryReader reader, DirectoryInfo directoryInfo)
|
||||
{
|
||||
using (reader)
|
||||
{
|
||||
Header = new ValorantAPIManifestHeaderV2(reader);
|
||||
|
||||
var compressedBuffer = reader.ReadBytes((int)Header.CompressedSize);
|
||||
var uncompressedBuffer = ZlibStream.UncompressBuffer(compressedBuffer);
|
||||
|
||||
if (uncompressedBuffer.Length != Header.UncompressedSize)
|
||||
{
|
||||
throw new FileLoadException("invalid decompressed manifest body");
|
||||
}
|
||||
|
||||
using var bodyMs = new MemoryStream(uncompressedBuffer, false);
|
||||
using var bodyReader = new BinaryReader(bodyMs);
|
||||
|
||||
Chunks = new ValorantChunkV2[Header.ChunkCount];
|
||||
|
||||
for (var i = 0u; i < Header.ChunkCount; i++)
|
||||
{
|
||||
Chunks[i] = new ValorantChunkV2(bodyReader);
|
||||
}
|
||||
|
||||
Paks = new ValorantPakV2[Header.PakCount];
|
||||
|
||||
for (var i = 0u; i < Header.PakCount; i++)
|
||||
{
|
||||
Paks[i] = new ValorantPakV2(bodyReader);
|
||||
}
|
||||
}
|
||||
|
||||
_client = new HttpClient(new HttpClientHandler
|
||||
{
|
||||
UseProxy = false,
|
||||
UseCookies = false,
|
||||
AutomaticDecompression = DecompressionMethods.All,
|
||||
CheckCertificateRevocationList = false,
|
||||
PreAuthenticate = false,
|
||||
MaxConnectionsPerServer = 1337,
|
||||
UseDefaultCredentials = false,
|
||||
AllowAutoRedirect = false
|
||||
});
|
||||
_chunkDirectory = directoryInfo;
|
||||
}
|
||||
|
||||
public Stream GetPakStream(int index)
|
||||
{
|
||||
return new ValorantPakV2Stream(this, index);
|
||||
}
|
||||
|
||||
public async Task PrefetchChunk(ValorantChunkV2 chunk, CancellationToken cancellationToken)
|
||||
{
|
||||
var chunkPath = Path.Combine(_chunkDirectory.FullName, $"{chunk.Id}.valchunk");
|
||||
|
||||
if (File.Exists(chunkPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, chunk.Url);
|
||||
using var response = await _client.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
var chunkBytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
|
||||
await using var fs = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
await fs.WriteAsync(chunkBytes, 0, chunkBytes.Length, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
#if DEBUG
|
||||
else
|
||||
{
|
||||
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
Debugger.Break();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public async Task<byte[]> GetChunkBytes(ValorantChunkV2 chunk, CancellationToken cancellationToken)
|
||||
{
|
||||
var chunkPath = Path.Combine(_chunkDirectory.FullName, $"{chunk.Id}.valchunk");
|
||||
byte[] chunkBytes;
|
||||
|
||||
if (File.Exists(chunkPath))
|
||||
{
|
||||
chunkBytes = new byte[chunk.Size];
|
||||
await using var fs = new FileStream(chunkPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
await fs.ReadAsync(chunkBytes, 0, chunkBytes.Length, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, chunk.Url);
|
||||
using var response = await _client.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
chunkBytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
|
||||
await using var fs = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
await fs.WriteAsync(chunkBytes, 0, chunkBytes.Length, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if DEBUG
|
||||
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
Debugger.Break();
|
||||
#endif
|
||||
chunkBytes = null;
|
||||
}
|
||||
}
|
||||
|
||||
return chunkBytes;
|
||||
}
|
||||
|
||||
public static async Task<ValorantAPIManifestV2> DownloadAndParse(DirectoryInfo directoryInfo)
|
||||
{
|
||||
using var client = new HttpClient(new HttpClientHandler
|
||||
{
|
||||
UseProxy = false,
|
||||
UseCookies = false,
|
||||
AutomaticDecompression = DecompressionMethods.All,
|
||||
CheckCertificateRevocationList = false,
|
||||
PreAuthenticate = false
|
||||
});
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, _url);
|
||||
|
||||
try
|
||||
{
|
||||
using var response = await client.SendAsync(request).ConfigureAwait(false);
|
||||
|
||||
if (response.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var responseStream = await response.Content.ReadAsStreamAsync();
|
||||
return new ValorantAPIManifestV2(responseStream, directoryInfo);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct ValorantAPIManifestHeaderV2
|
||||
{
|
||||
public const uint MAGIC = 0xC3D088F7u;
|
||||
|
||||
public readonly uint Magic;
|
||||
public readonly uint HeaderSize;
|
||||
public readonly ulong ManifestId;
|
||||
public readonly uint UncompressedSize;
|
||||
public readonly uint CompressedSize;
|
||||
public readonly uint ChunkCount;
|
||||
public readonly uint PakCount;
|
||||
public readonly string GameVersion;
|
||||
|
||||
public ValorantAPIManifestHeaderV2(BinaryReader reader)
|
||||
{
|
||||
Magic = reader.ReadUInt32();
|
||||
|
||||
if (Magic != MAGIC)
|
||||
{
|
||||
throw new FileLoadException("invalid manifest magic");
|
||||
}
|
||||
|
||||
HeaderSize = reader.ReadUInt32();
|
||||
ManifestId = reader.ReadUInt64();
|
||||
UncompressedSize = reader.ReadUInt32();
|
||||
CompressedSize = reader.ReadUInt32();
|
||||
ChunkCount = reader.ReadUInt32();
|
||||
PakCount = reader.ReadUInt32();
|
||||
|
||||
var gameVersionLength = (int)reader.ReadByte();
|
||||
if (gameVersionLength == 0)
|
||||
{
|
||||
GameVersion = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var gameVersionBuffer = reader.ReadBytes(gameVersionLength);
|
||||
GameVersion = Encoding.ASCII.GetString(gameVersionBuffer);
|
||||
}
|
||||
|
||||
reader.BaseStream.Position = HeaderSize;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct ValorantChunkV1
|
||||
{
|
||||
private const string _baseUrl = "https://fmodel.fortnite-api.com/valorant/v1/chunks/";
|
||||
|
||||
public readonly ulong Id;
|
||||
public readonly uint Size;
|
||||
public string Url => _baseUrl + Id;
|
||||
|
||||
public ValorantChunkV1(BinaryReader reader)
|
||||
{
|
||||
Id = reader.ReadUInt64();
|
||||
Size = reader.ReadUInt32();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Id:X8} | {Strings.GetReadableSize(Size)}";
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct ValorantChunkV2
|
||||
{
|
||||
private const string _baseUrl = "https://fmodel.fortnite-api.com/valorant/v2/chunks/";
|
||||
|
||||
public readonly ulong Id;
|
||||
public readonly uint Size;
|
||||
public string Url => $"{_baseUrl}{Id}";
|
||||
|
||||
public ValorantChunkV2(BinaryReader reader)
|
||||
{
|
||||
Id = reader.ReadUInt64();
|
||||
Size = reader.ReadUInt32();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Id:X8} | {Strings.GetReadableSize(Size)}";
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct ValorantPakV1
|
||||
{
|
||||
public readonly ulong Id;
|
||||
public readonly uint Size;
|
||||
public readonly string Name;
|
||||
public readonly ulong[] ChunkIds;
|
||||
|
||||
public ValorantPakV1(BinaryReader reader)
|
||||
{
|
||||
Id = reader.ReadUInt64();
|
||||
Size = reader.ReadUInt32();
|
||||
var nameLength = reader.ReadInt32();
|
||||
var nameBytes = reader.ReadBytes(nameLength);
|
||||
Name = Encoding.ASCII.GetString(nameBytes);
|
||||
ChunkIds = new ulong[reader.ReadInt32()];
|
||||
|
||||
for (var i = 0; i < ChunkIds.Length; i++)
|
||||
{
|
||||
ChunkIds[i] = reader.ReadUInt64();
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name} | {Strings.GetReadableSize(Size)}";
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct ValorantPakV2
|
||||
{
|
||||
public readonly ulong Id;
|
||||
public readonly uint Size;
|
||||
public readonly uint[] ChunkIndices;
|
||||
public readonly string Name;
|
||||
|
||||
public ValorantPakV2(BinaryReader reader)
|
||||
{
|
||||
Id = reader.ReadUInt64();
|
||||
Size = reader.ReadUInt32();
|
||||
|
||||
var chunkIndicesLength = reader.ReadUInt32();
|
||||
ChunkIndices = new uint[chunkIndicesLength];
|
||||
for (uint i = 0; i < chunkIndicesLength; i++)
|
||||
{
|
||||
ChunkIndices[i] = reader.ReadUInt32();
|
||||
}
|
||||
|
||||
var nameLength = (int)reader.ReadByte();
|
||||
var nameBytes = reader.ReadBytes(nameLength);
|
||||
Name = Encoding.ASCII.GetString(nameBytes);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name} | {Strings.GetReadableSize(Size)}";
|
||||
}
|
||||
}
|
||||
|
||||
public class ValorantPakV1Stream : Stream
|
||||
{
|
||||
public override bool CanRead { get; } = true;
|
||||
public override bool CanSeek { get; } = true;
|
||||
public override bool CanWrite { get; } = false;
|
||||
public override long Length { get; }
|
||||
|
||||
private long _position;
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => _position;
|
||||
set
|
||||
{
|
||||
if (value >= Length || value < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
_position = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string FileName { get; }
|
||||
private readonly ValorantAPIManifestV1 _manifest;
|
||||
private readonly ValorantChunkV1[] _chunks;
|
||||
|
||||
public ValorantPakV1Stream(ValorantAPIManifestV1 manifest, int pakIndex)
|
||||
{
|
||||
_manifest = manifest;
|
||||
var pak = manifest.Paks[pakIndex];
|
||||
FileName = pak.Name;
|
||||
Length = pak.Size;
|
||||
_chunks = new ValorantChunkV1[pak.ChunkIds.Length];
|
||||
|
||||
for (var i = 0; i < _chunks.Length; i++)
|
||||
{
|
||||
_chunks[i] = manifest.Chunks[pak.ChunkIds[i]];
|
||||
}
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task PrefetchAsync(int i, uint startPos, long count, CancellationToken cancellationToken, int concurrentDownloads = 4)
|
||||
{
|
||||
var tasks = new List<Task>();
|
||||
var sem = new SemaphoreSlim(concurrentDownloads);
|
||||
|
||||
while (count > 0)
|
||||
{
|
||||
await sem.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
var chunk = _chunks[i++];
|
||||
tasks.Add(PrefetchChunkAsync(chunk));
|
||||
|
||||
if (i == _chunks.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
count -= chunk.Size - startPos;
|
||||
startPos = 0u;
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
sem.Dispose();
|
||||
|
||||
async Task PrefetchChunkAsync(ValorantChunkV1 chunk)
|
||||
{
|
||||
await _manifest.PrefetchChunk(chunk, cancellationToken).ConfigureAwait(false);
|
||||
sem.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
var (i, startPos) = GetChunkIndex(_position);
|
||||
|
||||
if (i == -1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
await PrefetchAsync(i, startPos, count, cancellationToken).ConfigureAwait(false);
|
||||
var bytesRead = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var chunk = _chunks[i];
|
||||
var chunkData = await _manifest.GetChunkBytes(chunk, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var chunkBytes = chunk.Size - startPos;
|
||||
var bytesLeft = count - bytesRead;
|
||||
|
||||
if (bytesLeft <= chunkBytes)
|
||||
{
|
||||
Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], (uint)bytesLeft);
|
||||
bytesRead += bytesLeft;
|
||||
break;
|
||||
}
|
||||
|
||||
Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], chunkBytes);
|
||||
bytesRead += (int)chunkBytes;
|
||||
startPos = 0u;
|
||||
|
||||
if (++i == _chunks.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_position += bytesRead;
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
private (int Index, uint ChunkPos) GetChunkIndex(long position)
|
||||
{
|
||||
for (var i = 0; i < _chunks.Length; i++)
|
||||
{
|
||||
var size = _chunks[i].Size;
|
||||
|
||||
if (position < size)
|
||||
{
|
||||
return (i, (uint)position);
|
||||
}
|
||||
|
||||
position -= size;
|
||||
}
|
||||
|
||||
return (-1, 0u);
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
Position = origin switch
|
||||
{
|
||||
SeekOrigin.Begin => offset,
|
||||
SeekOrigin.Current => offset + _position,
|
||||
SeekOrigin.End => Length + offset,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
return _position;
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class ValorantPakV2Stream : Stream
|
||||
{
|
||||
public override bool CanRead { get; } = true;
|
||||
public override bool CanSeek { get; } = true;
|
||||
public override bool CanWrite { get; } = false;
|
||||
public override long Length { get; }
|
||||
|
||||
private long _position;
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => _position;
|
||||
set
|
||||
{
|
||||
if (value >= Length || value < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
_position = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string FileName { get; }
|
||||
private readonly ValorantAPIManifestV2 _manifest;
|
||||
private readonly ValorantChunkV2[] _chunks;
|
||||
|
||||
public ValorantPakV2Stream(ValorantAPIManifestV2 manifest, int pakIndex)
|
||||
{
|
||||
_manifest = manifest;
|
||||
var pak = manifest.Paks[pakIndex];
|
||||
FileName = pak.Name;
|
||||
Length = pak.Size;
|
||||
|
||||
_chunks = new ValorantChunkV2[pak.ChunkIndices.Length];
|
||||
for (var i = 0; i < pak.ChunkIndices.Length; i++)
|
||||
{
|
||||
_chunks[i] = manifest.Chunks[pak.ChunkIndices[i]];
|
||||
}
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task PrefetchAsync(int i, uint startPos, long count, CancellationToken cancellationToken, int concurrentDownloads = 4)
|
||||
{
|
||||
var tasks = new List<Task>();
|
||||
var sem = new SemaphoreSlim(concurrentDownloads);
|
||||
|
||||
while (count > 0)
|
||||
{
|
||||
await sem.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
var chunk = _chunks[i++];
|
||||
tasks.Add(PrefetchChunkAsync(chunk));
|
||||
|
||||
if (i == _chunks.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
count -= chunk.Size - startPos;
|
||||
startPos = 0u;
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
sem.Dispose();
|
||||
|
||||
async Task PrefetchChunkAsync(ValorantChunkV2 chunk)
|
||||
{
|
||||
await _manifest.PrefetchChunk(chunk, cancellationToken).ConfigureAwait(false);
|
||||
sem.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
var (i, startPos) = GetChunkIndex(_position);
|
||||
|
||||
if (i == -1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
await PrefetchAsync(i, startPos, count, cancellationToken).ConfigureAwait(false);
|
||||
var bytesRead = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var chunk = _chunks[i];
|
||||
var chunkData = await _manifest.GetChunkBytes(chunk, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var chunkBytes = chunk.Size - startPos;
|
||||
var bytesLeft = count - bytesRead;
|
||||
|
||||
if (bytesLeft <= chunkBytes)
|
||||
{
|
||||
Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], (uint)bytesLeft);
|
||||
bytesRead += bytesLeft;
|
||||
break;
|
||||
}
|
||||
|
||||
Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], chunkBytes);
|
||||
bytesRead += (int)chunkBytes;
|
||||
startPos = 0u;
|
||||
|
||||
if (++i == _chunks.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_position += bytesRead;
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
private (int Index, uint ChunkPos) GetChunkIndex(long position)
|
||||
{
|
||||
for (var i = 0; i < _chunks.Length; i++)
|
||||
{
|
||||
var size = _chunks[i].Size;
|
||||
|
||||
if (position < size)
|
||||
{
|
||||
return (i, (uint)position);
|
||||
}
|
||||
|
||||
position -= size;
|
||||
}
|
||||
|
||||
return (-1, 0u);
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
Position = origin switch
|
||||
{
|
||||
SeekOrigin.Begin => offset,
|
||||
SeekOrigin.Current => offset + _position,
|
||||
SeekOrigin.End => Length + offset,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
return _position;
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
using FModel.Logger;
|
||||
using FModel.Utils;
|
||||
using FModel.Windows.CustomNotifier;
|
||||
using Newtonsoft.Json;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FModel.Grabber.Mappings
|
||||
{
|
||||
static class MappingsData
|
||||
{
|
||||
public static async Task<Mapping[]> GetData()
|
||||
{
|
||||
if (NetworkInterface.GetIsNetworkAvailable())
|
||||
{
|
||||
Mapping[] data = await Endpoints.GetJsonEndpoint<Mapping[]>(Endpoints.BENBOT_MAPPINGS, string.Empty).ConfigureAwait(false);
|
||||
return data;
|
||||
}
|
||||
else
|
||||
{
|
||||
Globals.gNotifier.ShowCustomMessage("Mappings", Properties.Resources.NoInternet, "/FModel;component/Resources/wifi-strength-off.ico");
|
||||
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Mappings]", "No internet");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Mapping
|
||||
{
|
||||
[JsonProperty("url")]
|
||||
public string Url { get; set; }
|
||||
[JsonProperty("fileName")]
|
||||
public string FileName { get; set; }
|
||||
[JsonProperty("hash")]
|
||||
public string Hash { get; set; }
|
||||
[JsonProperty("length")]
|
||||
public string Length { get; set; }
|
||||
[JsonProperty("uploaded")]
|
||||
public string Uploaded { get; set; }
|
||||
[JsonProperty("meta")]
|
||||
public Metadata Meta { get; set; }
|
||||
}
|
||||
public class Metadata
|
||||
{
|
||||
[JsonProperty("version")]
|
||||
public string Version { get; set; }
|
||||
[JsonProperty("compressionMethod")]
|
||||
public string CompressionMethod { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
using FModel.Utils;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using UsmapNET.Classes;
|
||||
|
||||
namespace FModel.Grabber.Mappings
|
||||
{
|
||||
static class MappingsGrabber
|
||||
{
|
||||
public static async Task<bool> Load(bool forceReload = false)
|
||||
{
|
||||
if (Globals.Game.ActualGame == EGame.Fortnite)
|
||||
{
|
||||
Mapping[] benMappings = await MappingsData.GetData().ConfigureAwait(false);
|
||||
if (benMappings != null)
|
||||
{
|
||||
foreach (Mapping mapping in benMappings)
|
||||
{
|
||||
if (mapping.Meta.CompressionMethod == "Brotli")
|
||||
{
|
||||
DirectoryInfo chunksDir = Directory.CreateDirectory(Path.Combine(Properties.Settings.Default.OutputPath, "PakChunks"));
|
||||
string mappingPath = Path.Combine(chunksDir.FullName, mapping.FileName);
|
||||
|
||||
byte[] mappingsData;
|
||||
if (!forceReload && File.Exists(mappingPath))
|
||||
{
|
||||
mappingsData = await File.ReadAllBytesAsync(mappingPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
mappingsData = await Endpoints.GetRawDataAsync(new Uri(mapping.Url)).ConfigureAwait(false);
|
||||
await File.WriteAllBytesAsync(mappingPath, mappingsData).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
FConsole.AppendText($"Mappings pulled from {mapping.FileName}", FColors.Yellow, true);
|
||||
Globals.Usmap = new Usmap(mappingsData);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var latestUsmaps = new DirectoryInfo(Path.Combine(Properties.Settings.Default.OutputPath, "PakChunks")).GetFiles("*_br.usmap");
|
||||
if (Globals.Usmap == null && latestUsmaps.Length > 0)
|
||||
{
|
||||
var latestUsmapInfo = latestUsmaps.OrderBy(f => f.LastWriteTime).Last();
|
||||
byte[] mappingsData = await File.ReadAllBytesAsync(latestUsmapInfo.FullName);
|
||||
FConsole.AppendText($"Mappings pulled from {latestUsmapInfo.Name}", FColors.Yellow, true);
|
||||
Globals.Usmap = new Usmap(mappingsData);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FModel.Grabber.Paks
|
||||
{
|
||||
public class InstallsJson
|
||||
{
|
||||
[JsonProperty("associated_client")]
|
||||
public Dictionary<string, string> AssociatedClient;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
namespace FModel.Grabber.Paks
|
||||
{
|
||||
public class LauncherDat
|
||||
{
|
||||
public InstallationList[] InstallationList;
|
||||
}
|
||||
|
||||
public class InstallationList
|
||||
{
|
||||
public string InstallLocation;
|
||||
public string AppName;
|
||||
public string AppVersion;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
namespace FModel.Grabber.Paks
|
||||
{
|
||||
public class LauncherSettings
|
||||
{
|
||||
public string productLibraryDir;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,289 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media.Imaging;
|
||||
using EpicManifestParser.Objects;
|
||||
using FModel.Grabber.Manifests;
|
||||
using FModel.Logger;
|
||||
using FModel.PakReader.IO;
|
||||
using FModel.PakReader.Pak;
|
||||
using FModel.Utils;
|
||||
using FModel.ViewModels.MenuItem;
|
||||
using FModel.Windows.Launcher;
|
||||
|
||||
namespace FModel.Grabber.Paks
|
||||
{
|
||||
static class PaksGrabber
|
||||
{
|
||||
private static readonly Regex _pakFileRegex = new Regex(@"FortniteGame(/|\\)Content(/|\\)Paks(/|\\)(pakchunk(?:0|10.*|\w+)-WindowsClient|global)\.(pak|ucas)$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
|
||||
public static async Task PopulateMenu()
|
||||
{
|
||||
await Application.Current.Dispatcher.InvokeAsync(delegate
|
||||
{
|
||||
PopulateBase();
|
||||
});
|
||||
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(Properties.Settings.Default.PakPath))
|
||||
{
|
||||
await Application.Current.Dispatcher.InvokeAsync(delegate
|
||||
{
|
||||
var launcher = new FLauncher();
|
||||
bool? result = launcher.ShowDialog();
|
||||
if (result.HasValue && result.Value)
|
||||
{
|
||||
Properties.Settings.Default.PakPath = launcher.Path;
|
||||
Properties.Settings.Default.Save();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add Pak Files
|
||||
if (Properties.Settings.Default.PakPath.EndsWith("-fn.manifest"))
|
||||
{
|
||||
ManifestInfo manifestInfo = await ManifestGrabber.TryGetLatestManifestInfo().ConfigureAwait(false);
|
||||
|
||||
if (manifestInfo == null)
|
||||
{
|
||||
throw new Exception("Failed to load latest manifest.");
|
||||
}
|
||||
|
||||
DirectoryInfo chunksDir = Directory.CreateDirectory(Path.Combine(Properties.Settings.Default.OutputPath, "PakChunks"));
|
||||
string manifestPath = Path.Combine(chunksDir.FullName, manifestInfo.Filename);
|
||||
byte[] manifestData;
|
||||
|
||||
if (File.Exists(manifestPath))
|
||||
{
|
||||
manifestData = await File.ReadAllBytesAsync(manifestPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
manifestData = await manifestInfo.DownloadManifestDataAsync().ConfigureAwait(false);
|
||||
await File.WriteAllBytesAsync(manifestPath, manifestData).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Manifest manifest = new Manifest(manifestData, new ManifestOptions
|
||||
{
|
||||
ChunkBaseUri = new Uri("http://epicgames-download1.akamaized.net/Builds/Fortnite/CloudDir/ChunksV3/", UriKind.Absolute),
|
||||
ChunkCacheDirectory = Directory.CreateDirectory(Path.Combine(Properties.Settings.Default.OutputPath, "PakChunks"))
|
||||
});
|
||||
int pakFiles = 0;
|
||||
|
||||
foreach (FileManifest fileManifest in manifest.FileManifests)
|
||||
{
|
||||
if (!_pakFileRegex.IsMatch(fileManifest.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var pakStream = fileManifest.GetStream();
|
||||
if (pakStream.Length == 365) continue;
|
||||
|
||||
var pakFileName = fileManifest.Name.Replace('/', '\\');
|
||||
if (pakFileName.EndsWith(".pak"))
|
||||
{
|
||||
PakFileReader pakFile = new PakFileReader(pakFileName, pakStream);
|
||||
if (pakFiles++ == 0)
|
||||
{
|
||||
// define the current game thank to the pak path
|
||||
Folders.SetGameName(pakFileName);
|
||||
|
||||
Globals.Game.Version = pakFile.Info.Version;
|
||||
Globals.Game.SubVersion = pakFile.Info.SubVersion;
|
||||
}
|
||||
|
||||
await Application.Current.Dispatcher.InvokeAsync(delegate
|
||||
{
|
||||
MenuItems.pakFiles.Add(new PakMenuItemViewModel
|
||||
{
|
||||
PakFile = pakFile,
|
||||
IsEnabled = false
|
||||
});
|
||||
});
|
||||
}
|
||||
else if (pakFileName.EndsWith(".ucas"))
|
||||
{
|
||||
var utocStream = manifest.FileManifests.FirstOrDefault(x => x.Name.Equals(fileManifest.Name.Replace(".ucas", ".utoc")));
|
||||
var ioStore = new FFileIoStoreReader(pakFileName.SubstringAfterLast('\\'), pakFileName.SubstringBeforeLast('\\'), utocStream.GetStream(), pakStream);
|
||||
await Application.Current.Dispatcher.InvokeAsync(delegate
|
||||
{
|
||||
MenuItems.pakFiles.Add(new PakMenuItemViewModel
|
||||
{
|
||||
IoStore = ioStore,
|
||||
IsEnabled = false
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
FConsole.AppendText($"Fortnite-Manifest version: {manifest.Version}-{manifest.CL}", FColors.Yellow, true);
|
||||
}
|
||||
else if (Properties.Settings.Default.PakPath.EndsWith("-val.manifest"))
|
||||
{
|
||||
//var manifest = await ValorantAPIManifestV1.DownloadAndParse(Directory.CreateDirectory(Path.Combine(Properties.Settings.Default.OutputPath, "PakChunks"))).ConfigureAwait(false);
|
||||
var manifest = await ValorantAPIManifestV2.DownloadAndParse(Directory.CreateDirectory(Path.Combine(Properties.Settings.Default.OutputPath, "PakChunks"))).ConfigureAwait(false);
|
||||
|
||||
if (manifest == null)
|
||||
{
|
||||
throw new Exception("Failed to load latest manifest.");
|
||||
}
|
||||
|
||||
for (var i = 0; i < manifest.Paks.Length; i++)
|
||||
{
|
||||
var pak = manifest.Paks[i];
|
||||
var pakFileName = @$"ShooterGame\Content\Paks\{pak.Name}";
|
||||
var pakFile = new PakFileReader(pakFileName, manifest.GetPakStream(i));
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
Folders.SetGame(EGame.Valorant);
|
||||
Globals.Game.Version = pakFile.Info.Version;
|
||||
Globals.Game.SubVersion = pakFile.Info.SubVersion;
|
||||
}
|
||||
|
||||
await Application.Current.Dispatcher.InvokeAsync(delegate
|
||||
{
|
||||
MenuItems.pakFiles.Add(new PakMenuItemViewModel
|
||||
{
|
||||
PakFile = pakFile,
|
||||
IsEnabled = false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
FConsole.AppendText($"Valorant-Manifest version: {manifest.Header.GameVersion ?? "Unknown"}", FColors.Yellow, true);
|
||||
}
|
||||
else if (Directory.Exists(Properties.Settings.Default.PakPath))
|
||||
{
|
||||
// define the current game thank to the pak path
|
||||
Folders.SetGameName(Properties.Settings.Default.PakPath);
|
||||
|
||||
// paks
|
||||
string[] paks = Directory.GetFiles(Properties.Settings.Default.PakPath);
|
||||
for (int i = 0; i < paks.Length; i++)
|
||||
{
|
||||
var pakInfo = new FileInfo(paks[i]);
|
||||
if (pakInfo.Length == 365)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Utils.Paks.IsFileReadLocked(pakInfo))
|
||||
{
|
||||
if (paks[i].EndsWith(".pak"))
|
||||
{
|
||||
PakFileReader pakFile = new PakFileReader(paks[i]);
|
||||
DebugHelper.WriteLine("{0} {1} {2} {3}", "[FModel]", "[PAK]", "[Registering]", $"{pakFile.FileName} with GUID {pakFile.Info.EncryptionKeyGuid.Hex}");
|
||||
if (i == 0)
|
||||
{
|
||||
Globals.Game.Version = pakFile.Info.Version;
|
||||
Globals.Game.SubVersion = pakFile.Info.SubVersion;
|
||||
}
|
||||
|
||||
await Application.Current.Dispatcher.InvokeAsync(delegate
|
||||
{
|
||||
MenuItems.pakFiles.Add(new PakMenuItemViewModel
|
||||
{
|
||||
PakFile = pakFile,
|
||||
IsEnabled = false
|
||||
});
|
||||
});
|
||||
}
|
||||
else if (paks[i].EndsWith(".ucas"))
|
||||
{
|
||||
var utoc = paks[i].Replace(".ucas", ".utoc");
|
||||
if (!Utils.Paks.IsFileReadLocked(new FileInfo(utoc)))
|
||||
{
|
||||
var utocStream = File.OpenRead(utoc);
|
||||
var ucasStream = File.OpenRead(paks[i]);
|
||||
var ioStore = new FFileIoStoreReader(paks[i].SubstringAfterLast('\\'), paks[i].SubstringBeforeLast('\\'), utocStream, ucasStream);
|
||||
DebugHelper.WriteLine("{0} {1} {2} {3}", "[FModel]", "[IO Store]", "[Registering]", $"{ioStore.FileName} with GUID {ioStore.TocResource.Header.EncryptionKeyGuid.Hex}");
|
||||
|
||||
await Application.Current.Dispatcher.InvokeAsync(delegate
|
||||
{
|
||||
MenuItems.pakFiles.Add(new PakMenuItemViewModel
|
||||
{
|
||||
IoStore = ioStore,
|
||||
IsEnabled = false
|
||||
});
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
FConsole.AppendText(string.Format(Properties.Resources.PakFileLocked, Path.GetFileNameWithoutExtension(utoc)), FColors.Red, true);
|
||||
DebugHelper.WriteLine("{0} {1} {2} {3}", "[FModel]", "[IO Store]", "[Locked]", utoc);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FConsole.AppendText(string.Format(Properties.Resources.PakFileLocked, Path.GetFileNameWithoutExtension(paks[i])), FColors.Red, true);
|
||||
DebugHelper.WriteLine("{0} {1} {2} {3}", "[FModel]", "[PAK]", "[Locked]", paks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void PopulateBase()
|
||||
{
|
||||
// Loading Mode
|
||||
PakMenuItemViewModel parent = new PakMenuItemViewModel
|
||||
{
|
||||
Header = $"{Properties.Resources.LoadingMode} 🠞 {Properties.Resources.Default}",
|
||||
Icon = new Image { Source = new BitmapImage(new Uri("Resources/progress-download.png", UriKind.Relative)) }
|
||||
};
|
||||
parent.Childrens = new ObservableCollection<PakMenuItemViewModel>
|
||||
{
|
||||
new PakMenuItemViewModel // Default Mode
|
||||
{
|
||||
Header = Properties.Resources.Default,
|
||||
Parent = parent,
|
||||
IsCheckable = true,
|
||||
IsChecked = true,
|
||||
StaysOpenOnClick = true
|
||||
},
|
||||
new PakMenuItemViewModel
|
||||
{
|
||||
Header = Properties.Resources.NewFiles,
|
||||
Parent = parent,
|
||||
IsCheckable = true,
|
||||
StaysOpenOnClick = true
|
||||
},
|
||||
new PakMenuItemViewModel
|
||||
{
|
||||
Header = Properties.Resources.ModifiedFiles,
|
||||
Parent = parent,
|
||||
IsCheckable = true,
|
||||
StaysOpenOnClick = true
|
||||
},
|
||||
new PakMenuItemViewModel
|
||||
{
|
||||
Header = Properties.Resources.NewModifiedFiles,
|
||||
Parent = parent,
|
||||
IsCheckable = true,
|
||||
StaysOpenOnClick = true
|
||||
}
|
||||
};
|
||||
MenuItems.pakFiles.Add(parent);
|
||||
|
||||
// Load All
|
||||
MenuItems.pakFiles.Add(new PakMenuItemViewModel
|
||||
{
|
||||
Header = Properties.Resources.LoadAll,
|
||||
Icon = new Image { Source = new BitmapImage(new Uri("Resources/folder-download.png", UriKind.Relative)) },
|
||||
IsEnabled = false
|
||||
});
|
||||
|
||||
// Separator
|
||||
MenuItems.pakFiles.Add(new Separator { });
|
||||
}
|
||||
}
|
||||
}
|
||||
88
FModel/Helper.cs
Normal file
88
FModel/Helper.cs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
|
||||
namespace FModel
|
||||
{
|
||||
public static class Helper
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
private struct NanUnion
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
internal double DoubleValue;
|
||||
[FieldOffset(0)]
|
||||
internal readonly ulong UlongValue;
|
||||
}
|
||||
|
||||
public static void OpenWindow<T>(string windowName, Action action) where T : Window
|
||||
{
|
||||
if (!IsWindowOpen<T>(windowName))
|
||||
{
|
||||
action();
|
||||
}
|
||||
else
|
||||
{
|
||||
var w = GetOpenedWindow<T>(windowName);
|
||||
if (windowName == "Search View") w.WindowState = WindowState.Normal;
|
||||
w.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
public static T GetWindow<T>(string windowName, Action action) where T : Window
|
||||
{
|
||||
if (!IsWindowOpen<T>(windowName))
|
||||
{
|
||||
action();
|
||||
}
|
||||
|
||||
var ret = (T) GetOpenedWindow<T>(windowName);
|
||||
ret.Focus();
|
||||
ret.Activate();
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void CloseWindow<T>(string windowName) where T : Window
|
||||
{
|
||||
if (!IsWindowOpen<T>(windowName)) return;
|
||||
GetOpenedWindow<T>(windowName).Close();
|
||||
}
|
||||
|
||||
private static bool IsWindowOpen<T>(string name = "") where T : Window
|
||||
{
|
||||
return string.IsNullOrEmpty(name)
|
||||
? Application.Current.Windows.OfType<T>().Any()
|
||||
: Application.Current.Windows.OfType<T>().Any(w => w.Title.Equals(name));
|
||||
}
|
||||
|
||||
private static Window GetOpenedWindow<T>(string name) where T : Window
|
||||
{
|
||||
return Application.Current.Windows.OfType<T>().FirstOrDefault(w => w.Title.Equals(name));
|
||||
}
|
||||
|
||||
public static bool IsNaN(double value)
|
||||
{
|
||||
var t = new NanUnion {DoubleValue = value};
|
||||
var exp = t.UlongValue & 0xfff0000000000000;
|
||||
var man = t.UlongValue & 0x000fffffffffffff;
|
||||
return exp is 0x7ff0000000000000 or 0xfff0000000000000 && man != 0;
|
||||
}
|
||||
|
||||
public static bool AreVirtuallyEqual(double d1, double d2)
|
||||
{
|
||||
if (double.IsPositiveInfinity(d1))
|
||||
return double.IsPositiveInfinity(d2);
|
||||
|
||||
if (double.IsNegativeInfinity(d1))
|
||||
return double.IsNegativeInfinity(d2);
|
||||
|
||||
if (IsNaN(d1))
|
||||
return IsNaN(d2);
|
||||
|
||||
var n = d1 - d2;
|
||||
var d = (Math.Abs(d1) + Math.Abs(d2) + 10) * 1.0e-15;
|
||||
return -d < n && d > n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
using System;
|
||||
using System.Configuration;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace FModel.Logger
|
||||
{
|
||||
static class DebugHelper
|
||||
{
|
||||
public static Logger Logger { get; private set; }
|
||||
|
||||
public static void Init(string logFilePath) => Logger = new Logger(logFilePath);
|
||||
|
||||
public static void WriteLine(string message = "")
|
||||
{
|
||||
if (Logger != null)
|
||||
Logger.WriteLine(message);
|
||||
else
|
||||
Debug.WriteLine(message);
|
||||
}
|
||||
|
||||
public static void WriteLine(string format, params object[] args) => WriteLine(string.Format(format, args));
|
||||
|
||||
public static void WriteException(string exception, string message = "Exception")
|
||||
{
|
||||
if (Logger != null)
|
||||
Logger.WriteException(exception, message);
|
||||
else
|
||||
Debug.WriteLine(exception);
|
||||
}
|
||||
|
||||
public static void WriteException(Exception exception, string message = "Exception") => WriteException(exception.ToString(), message);
|
||||
|
||||
public static void WriteUserSettings()
|
||||
{
|
||||
foreach (SettingsProperty currentProperty in Properties.Settings.Default.Properties)
|
||||
{
|
||||
WriteLine("{0} {1} {2} {3}", "[FModel]", "[User Settings]", $"[{currentProperty.Name}]", Properties.Settings.Default[currentProperty.Name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,178 +0,0 @@
|
|||
using FModel.Windows.DarkMessageBox;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace FModel.Logger
|
||||
{
|
||||
class Logger
|
||||
{
|
||||
public delegate void MessageAddedEventHandler(string message);
|
||||
|
||||
public event MessageAddedEventHandler MessageAdded;
|
||||
|
||||
public string MessageFormat { get; set; } = "{0:yyyy-MM-dd HH:mm:ss.fff} - {1}";
|
||||
public bool AsyncWrite { get; set; } = true;
|
||||
public bool DebugWrite { get; set; } = Globals.Build == EFModel.Debug;
|
||||
public bool StringWrite { get; set; } = true;
|
||||
public bool FileWrite { get; set; } = false;
|
||||
public string LogFilePath { get; private set; }
|
||||
|
||||
private readonly object loggerLock = new object();
|
||||
private readonly ConcurrentQueue<string> messageQueue = new ConcurrentQueue<string>();
|
||||
private readonly StringBuilder sbMessages = new StringBuilder();
|
||||
|
||||
public Logger()
|
||||
{
|
||||
}
|
||||
|
||||
public Logger(string logFilePath)
|
||||
{
|
||||
FileWrite = true;
|
||||
LogFilePath = logFilePath;
|
||||
CreateDirectoryFromFilePath(LogFilePath);
|
||||
}
|
||||
|
||||
protected void OnMessageAdded(string message) => MessageAdded?.Invoke(message);
|
||||
|
||||
private void ProcessMessageQueue()
|
||||
{
|
||||
lock (loggerLock)
|
||||
{
|
||||
while (messageQueue.TryDequeue(out string message))
|
||||
{
|
||||
if (DebugWrite)
|
||||
Debug.Write(message);
|
||||
|
||||
if (StringWrite && sbMessages != null)
|
||||
sbMessages.Append(message);
|
||||
|
||||
if (FileWrite && !string.IsNullOrEmpty(LogFilePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.AppendAllText(LogFilePath, message, Encoding.UTF8);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine(e);
|
||||
}
|
||||
}
|
||||
|
||||
OnMessageAdded(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(string message)
|
||||
{
|
||||
if (message != null)
|
||||
{
|
||||
message = string.Format(MessageFormat, DateTime.Now, message);
|
||||
messageQueue.Enqueue(message);
|
||||
|
||||
if (AsyncWrite)
|
||||
Task.Run(() => ProcessMessageQueue());
|
||||
else
|
||||
ProcessMessageQueue();
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(string format, params object[] args) => Write(string.Format(format, args));
|
||||
public void WriteLine(string message) => Write(message + Environment.NewLine);
|
||||
public void WriteLine(string format, params object[] args) => WriteLine(string.Format(format, args));
|
||||
public void WriteException(string exception, string message = "Exception") => WriteLine($"{message}:{Environment.NewLine}{exception}");
|
||||
public void WriteException(Exception exception, string message = "Exception") => WriteException(exception.ToString(), message);
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
lock (loggerLock)
|
||||
{
|
||||
if (sbMessages != null)
|
||||
sbMessages.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
lock (loggerLock)
|
||||
{
|
||||
if (sbMessages != null && sbMessages.Length > 0)
|
||||
return sbMessages.ToString();
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public static void CreateDirectoryFromFilePath(string filePath)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
string directoryPath = Path.GetDirectoryName(filePath);
|
||||
CreateDirectoryFromDirectoryPath(directoryPath);
|
||||
}
|
||||
}
|
||||
|
||||
public static void CreateDirectoryFromDirectoryPath(string directoryPath)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(directoryPath) && !Directory.Exists(directoryPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(directoryPath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugHelper.WriteException(e);
|
||||
DarkMessageBoxHelper.Show(Properties.Resources.CouldNotCreateDirectory + "\r\n\r\n" + e, "FModel - " + Properties.Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetOperatingSystemProductName(bool includeBit = false)
|
||||
{
|
||||
string productName = null;
|
||||
|
||||
try
|
||||
{
|
||||
productName = GetRegistryValue(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ProductName", RegistryHive.LocalMachine);
|
||||
}
|
||||
catch
|
||||
{
|
||||
//hello world :)
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(productName))
|
||||
productName = Environment.OSVersion.VersionString;
|
||||
|
||||
if (includeBit)
|
||||
{
|
||||
string bit;
|
||||
if (Environment.Is64BitOperatingSystem)
|
||||
bit = "64";
|
||||
else
|
||||
bit = "32";
|
||||
|
||||
productName = $"{productName} ({bit}-bit)";
|
||||
}
|
||||
|
||||
return productName;
|
||||
}
|
||||
|
||||
public static string GetRegistryValue(string path, string name = null, RegistryHive root = RegistryHive.CurrentUser)
|
||||
{
|
||||
using (RegistryKey rk = RegistryKey.OpenBaseKey(root, RegistryView.Default).OpenSubKey(path))
|
||||
{
|
||||
if (rk != null)
|
||||
return rk.GetValue(name, null) as string;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user