Compare commits

..

No commits in common. "master" and "4.0.0" have entirely different histories.

290 changed files with 12282 additions and 27375 deletions

View File

@ -1,164 +0,0 @@
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
# C# files
[*.cs]
indent_size = 4
# New line preferences
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_within_query_expression_clauses = true
# Code files
[*.{cs,csx,vb,vbx}]
indent_size = 4
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_indent_labels = one_less_than_current
# avoid this. unless absolutely necessary
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion
# only use var when it's obvious what the variable type is
csharp_style_var_for_built_in_types = false:none
csharp_style_var_when_type_is_apparent = false:none
csharp_style_var_elsewhere = false:suggestion
# use language keywords instead of BCL types
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
# name all constant fields using PascalCase
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.required_modifiers = const
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# internal and private fields should be _camelCase
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
dotnet_naming_style.camel_case_underscore_style.required_prefix = _
dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
# Code style defaults
dotnet_sort_system_directives_first = true
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = false
# Expression-level preferences
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
# Expression-bodied members
csharp_style_expression_bodied_methods = false:none
csharp_style_expression_bodied_constructors = false:none
csharp_style_expression_bodied_operators = false:none
csharp_style_expression_bodied_properties = true:none
csharp_style_expression_bodied_indexers = true:none
csharp_style_expression_bodied_accessors = true:none
# Pattern matching
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
# Null checking preferences
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Space preferences
csharp_space_after_cast = true
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = do_not_ignore
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
[*.{asm,inc}]
indent_size = 8
# Visual Studio Solution Files
[*.sln]
indent_style = tab
# Visual Studio XML Project Files
[*.{csproj,vbproj,vcxproj.filters,proj,projitems,shproj}]
indent_size = 2
# XML Configuration Files
[*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct}]
indent_size = 2
[CMakeLists.txt]
indent_size = 2
# Makefiles
[Makefile]
indent_style = tab
# Batch Files
[*.{cmd,bat}]
indent_size = 2
end_of_line = crlf
# Bash Files
[*.sh]
end_of_line = lf
# Web Files
[*.{htm,html,js,jsm,ts,tsx,css,sass,scss,less,pcss,svg,vue}]
indent_size = 2
# Markdown Files
[*.md]
trim_trailing_whitespace = false
# JSON Files
[*.{json,json5,webmanifest}]
indent_size = 2

1
.github/FUNDING.yml vendored
View File

@ -1 +0,0 @@
custom: "https://fmodel.app/donate"

View File

@ -1,45 +0,0 @@
name: Bug Report
description: File a bug report
title: "Bug Title"
labels: [bug]
assignees:
- iAmAsval
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report! Keep in mind that screenshots and log files help us a lot so don't forget to provide one or both of those (drag and drop files in a text area).
Your bug report will be closed without explanation if you don't follow the following rules:
- Bad bug explanation will result in bad support and probably on a negative tone
- This template shouldn't be used to ask how to use FModel or a certain feature FModel provides
- Bug reports must always use the latest FModel with the latest available version of the game you use
- If you can't load files, it's probably because of your AES key, no need to file a report
- We absolutely do not support modding
- type: input
id: game
attributes:
label: Game
placeholder: ex. Fortnite, Valorant, ...
validations:
required: true
- type: textarea
id: error
attributes:
label: Error
description: Tell us what FModel says about the error, from the console and / or the log file
placeholder: ex. [ERR] Could not export 'EditorClientAssetRegistry.bin'
render: shell
validations:
required: true
- type: textarea
id: repro
attributes:
label: Reproduction steps
description: How do you trigger this bug? Please walk us through it step by step.
placeholder: |
1.
2.
3.
...
validations:
required: true

View File

@ -1,5 +0,0 @@
blank_issues_enabled: true
contact_links:
- name: Discord Server
url: https://fmodel.app/discord
about: Please ask and answer questions here.

View File

@ -1,22 +0,0 @@
name: Feature Request
description: Submit a new feature request
title: "Feature Title"
labels: [suggestion]
assignees:
- iAmAsval
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this feature request! Before going any further, make sure what you're about to submit doesn't already exist.
Your feature request will be closed without explanation if you don't follow the following rules:
- This template shouldn't be used to ask how to use FModel or a certain feature FModel provides
- We absolutely do not support modding
- type: textarea
id: description
attributes:
label: Description
description: Tell us what you want FModel to be able to do
placeholder: Please describe with details and how it could be done if possible...
validations:
required: true

View File

@ -1,12 +1,7 @@
name: FModel Builder
name: Artifact Generator
on:
workflow_dispatch:
inputs:
appVersion:
description: 'FModel Version And Release Tag'
required: true
default: '4.0.X.X'
jobs:
build:
@ -17,32 +12,21 @@ jobs:
uses: actions/checkout@v2
with:
submodules: 'true'
token: ${{ secrets.PAT_TOKEN }}
- name: Fetch Submodules Recursively
run: git submodule update --init --recursive
- name: .NET 8 Setup
uses: actions/setup-dotnet@v2
- name: .NET 5 Setup
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.0.x'
dotnet-version: 5.0.x
- name: .NET Restore
run: dotnet restore FModel
- name: .NET Publish
run: dotnet publish FModel -c Release --no-self-contained -r win-x64 -f net8.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false -p:AssemblyVersion=${{ github.event.inputs.appVersion }} -p:FileVersion=${{ github.event.inputs.appVersion }}
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: ZIP File
uses: papeloto/action-zip@v1
- name: EXE Upload
uses: actions/upload-artifact@v2
with:
files: ./FModel/bin/Publish/FModel.exe
dest: FModel.zip # will end up in working directory not the Publish folder
- name: GIT Release
uses: marvinpinto/action-automatic-releases@latest
with:
title: "FModel v${{ github.event.inputs.appVersion }}"
automatic_release_tag: ${{ github.event.inputs.appVersion }}
repo_token: ${{ secrets.GITHUB_TOKEN }}
prerelease: false
files: FModel.zip
name: FModel
path: D:\a\FModel\FModel\FModel\bin\Publish\

View File

@ -1,65 +0,0 @@
name: FModel QA Builder
on:
push:
branches: [ dev ]
jobs:
build:
runs-on: windows-latest
steps:
- name: GIT Checkout
uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: .NET 8 Setup
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: .NET Restore
run: dotnet restore FModel
- name: .NET Publish
run: dotnet publish "./FModel/FModel.csproj" -c Release --no-restore --no-self-contained -r win-x64 -f net8.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false
- name: ZIP File
uses: thedoctor0/zip-release@0.7.6
with:
type: zip
filename: ${{ github.sha }}.zip # will end up in working directory not the Publish folder
path: ./FModel/bin/Publish/FModel.exe
- name: Edit QA Artifact
uses: ncipollo/release-action@v1.14.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: 'FModel QA Testing'
body: 'Dev builds'
tag: 'qa'
artifacts: ${{ github.sha }}.zip
prerelease: true
allowUpdates: true
- name: Get Version
id: package_version
uses: kzrnm/get-net-sdk-project-versions-action@v2
with:
proj-path: ./FModel/FModel.csproj
- name: FModel Auth
id: fmodel_auth
uses: fjogeleit/http-request-action@v1.15.5
with:
url: "https://api.fmodel.app/v1/oauth/token"
data: '{"username": "${{ secrets.API_USERNAME }}", "password": "${{ secrets.API_PASSWORD }}"}'
- name: FModel Deploy Build
uses: fjogeleit/http-request-action@v1.15.5
with:
url: "https://api.fmodel.app/v1/infos/${{ secrets.QA_ID }}"
method: "PATCH"
bearerToken: ${{ fromJson(steps.fmodel_auth.outputs.response).accessToken }}
data: '{"version": "${{ steps.package_version.outputs.version }}-dev+${{ github.sha }}", "downloadUrl": "https://github.com/4sval/FModel/releases/download/qa/${{ github.sha }}.zip"}'

@ -1 +1 @@
Subproject commit 455b72e5e38bfe9476b5823bc642fc8ef488347f
Subproject commit 09a98cdafd273db1cdee58ded37ed4f5b3a2ff22

View File

@ -1,10 +1,10 @@
using AdonisUI.Controls;
using AdonisUI.Controls;
using Microsoft.Win32;
using Serilog;
using System;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using FModel.Framework;
@ -16,163 +16,109 @@ 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
namespace FModel
{
[DllImport("kernel32.dll")]
private static extern bool AttachConsole(int dwProcessId);
[DllImport("winbrand.dll", CharSet = CharSet.Unicode)]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
static extern string BrandingFormatString(string format);
protected override void OnStartup(StartupEventArgs e)
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App
{
#if DEBUG
AttachConsole(-1);
#endif
base.OnStartup(e);
try
protected override void OnStartup(StartupEventArgs e)
{
UserSettings.Default = JsonConvert.DeserializeObject<UserSettings>(
File.ReadAllText(UserSettings.FilePath), JsonNetSerializer.SerializerSettings);
}
catch
{
UserSettings.Default = new UserSettings();
}
base.OnStartup(e);
var createMe = false;
if (!Directory.Exists(UserSettings.Default.OutputDirectory))
{
var currentDir = Directory.GetCurrentDirectory();
var dirInfo = new DirectoryInfo(currentDir);
if (dirInfo.Attributes.HasFlag(FileAttributes.Archive))
throw new Exception("FModel cannot be run from an archive file. Please extract it and try again.");
if (dirInfo.Attributes.HasFlag(FileAttributes.ReadOnly))
throw new Exception("FModel cannot be run from a read-only directory. Please move it to a writable location.");
UserSettings.Default.OutputDirectory = Path.Combine(currentDir, "Output");
}
if (!Directory.Exists(UserSettings.Default.RawDataDirectory))
{
createMe = true;
UserSettings.Default.RawDataDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
if (!Directory.Exists(UserSettings.Default.PropertiesDirectory))
{
createMe = true;
UserSettings.Default.PropertiesDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
if (!Directory.Exists(UserSettings.Default.TextureDirectory))
{
createMe = true;
UserSettings.Default.TextureDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
if (!Directory.Exists(UserSettings.Default.AudioDirectory))
{
createMe = true;
UserSettings.Default.AudioDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
if (!Directory.Exists(UserSettings.Default.ModelDirectory))
{
createMe = true;
UserSettings.Default.ModelDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FModel"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Backups"));
if (createMe) Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Exports"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Logs"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
#if DEBUG
Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File(
Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Debug-Log-{DateTime.Now:yyyy-MM-dd}.txt"),
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [FModel] [{Level:u3}] {Message:lj}{NewLine}{Exception}").CreateLogger();
#else
Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File(
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();
#endif
Log.Information("Version {Version} ({CommitId})", Constants.APP_VERSION, Constants.APP_COMMIT_ID);
Log.Information("{OS}", GetOperatingSystemProductName());
Log.Information("{RuntimeVer}", RuntimeInformation.FrameworkDescription);
Log.Information("Culture {SysLang}", CultureInfo.CurrentCulture);
}
private void AppExit(object sender, ExitEventArgs e)
{
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[]
try
{
MessageBoxButtons.Custom("Reset Settings", EErrorKind.ResetSettings),
MessageBoxButtons.Custom("Restart", EErrorKind.Restart),
MessageBoxButtons.Custom("OK", EErrorKind.Ignore)
},
IsSoundEnabled = false
};
UserSettings.Default = JsonConvert.DeserializeObject<UserSettings>(
File.ReadAllText(UserSettings.FilePath), JsonNetSerializer.SerializerSettings);
}
catch
{
UserSettings.Default = new UserSettings();
}
MessageBox.Show(messageBox);
if (messageBox.Result == MessageBoxResult.Custom && (EErrorKind) messageBox.ButtonPressed.Id != EErrorKind.Ignore)
{
if ((EErrorKind) messageBox.ButtonPressed.Id == EErrorKind.ResetSettings)
UserSettings.Delete();
if (!Directory.Exists(UserSettings.Default.OutputDirectory))
{
UserSettings.Default.OutputDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Output");
}
ApplicationService.ApplicationView.Restart();
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);
}
e.Handled = true;
}
private string GetOperatingSystemProductName()
{
var productName = string.Empty;
try
private void AppExit(object sender, ExitEventArgs e)
{
productName = BrandingFormatString("%WINDOWS_LONG%");
}
catch
{
// ignored
Log.Information("");
Log.CloseAndFlush();
UserSettings.Save();
Environment.Exit(0);
}
if (string.IsNullOrEmpty(productName))
productName = Environment.OSVersion.VersionString;
private void OnUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
Log.Error("{Exception}", e.Exception);
return $"{productName} ({(Environment.Is64BitOperatingSystem ? "64" : "32")}-bit)";
}
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
};
public static 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;
MessageBox.Show(messageBox);
if (messageBox.Result == MessageBoxResult.Custom && (EErrorKind) messageBox.ButtonPressed.Id != EErrorKind.Ignore)
{
ApplicationService.ApplicationView.Restart();
}
e.Handled = true;
}
private string GetOperatingSystemProductName()
{
var productName = string.Empty;
try
{
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;
}
}
}
}

View File

@ -1,57 +1,63 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Numerics;
using System.Reflection;
using CUE4Parse.UE4.Objects.Core.Misc;
using FModel.Extensions;
namespace FModel;
public static class Constants
namespace FModel
{
public static readonly string APP_PATH = Path.GetFullPath(Environment.GetCommandLineArgs()[0]);
public static readonly string APP_VERSION = FileVersionInfo.GetVersionInfo(APP_PATH).FileVersion;
public static readonly string APP_COMMIT_ID = FileVersionInfo.GetVersionInfo(APP_PATH).ProductVersion.SubstringAfter('+');
public static readonly string APP_SHORT_COMMIT_ID = APP_COMMIT_ID[..7];
public const string ZERO_64_CHAR = "0000000000000000000000000000000000000000000000000000000000000000";
public static readonly FGuid ZERO_GUID = new(0U);
public const float SCALE_DOWN_RATIO = 0.01F;
public const int SAMPLES_COUNT = 4;
public const string WHITE = "#DAE5F2";
public const string GRAY = "#BBBBBB";
public const string RED = "#E06C75";
public const string GREEN = "#98C379";
public const string YELLOW = "#E5C07B";
public const string BLUE = "#528BCC";
public const string ISSUE_LINK = "https://github.com/4sval/FModel/discussions/categories/q-a";
public const string GH_REPO = "https://api.github.com/repos/4sval/FModel";
public const string GH_COMMITS_HISTORY = GH_REPO + "/commits";
public const string GH_RELEASES = GH_REPO + "/releases";
public const string DONATE_LINK = "https://fmodel.app/donate";
public const string DISCORD_LINK = "https://fmodel.app/discord";
public const string _FN_LIVE_TRIGGER = "fortnite-live.manifest";
public const string _VAL_LIVE_TRIGGER = "valorant-live.manifest";
public const string _NO_PRESET_TRIGGER = "Hand Made";
public static int PALETTE_LENGTH => COLOR_PALETTE.Length;
public static readonly Vector3[] COLOR_PALETTE =
public static class Constants
{
new (0.231f, 0.231f, 0.231f), // Dark gray
new (0.376f, 0.490f, 0.545f), // Teal
new (0.957f, 0.263f, 0.212f), // Red
new (0.196f, 0.804f, 0.196f), // Green
new (0.957f, 0.647f, 0.212f), // Orange
new (0.612f, 0.153f, 0.690f), // Purple
new (0.129f, 0.588f, 0.953f), // Blue
new (1.000f, 0.920f, 0.424f), // Yellow
new (0.824f, 0.412f, 0.118f), // Brown
new (0.612f, 0.800f, 0.922f) // Light blue
};
}
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",
};
}
}

View File

@ -1,42 +1,43 @@
using CUE4Parse.UE4.Assets.Exports;
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
namespace FModel.Creator.Bases.BB
{
public BaseBreakersIcon(UObject uObject, EIconStyle style) : base(uObject, style)
public class BaseBreakersIcon : BaseIcon
{
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 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", "UnlockPortraitGuideImage"))
Preview = Utils.GetBitmap(iconTextureAssetData);
public override void ParseForInfo()
{
if (Object.TryGetValue(out FSoftObjectPath iconTextureAssetData, "IconTextureAssetData"))
Preview = Utils.GetBitmap(iconTextureAssetData);
if (Object.TryGetValue(out FText displayName, "DisplayName", "RegionDisplayName", "ZoneName"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description", "RegionShortName", "ZoneDescription"))
Description = description.Text;
}
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description"))
Description = description.Text;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
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);
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawDescription(c);
return new[] { ret };
return SKImage.FromBitmap(ret);
}
}
}

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
@ -8,132 +8,134 @@ using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class BaseBundle : UCreator
namespace FModel.Creator.Bases.FN
{
private IList<BaseQuest> _quests;
private const int _headerHeight = 100;
public BaseBundle(UObject uObject, EIconStyle style) : base(uObject, style)
public class BaseBundle : UCreator
{
Width = 1024;
Height = _headerHeight;
Margin = 0;
}
private IList<BaseQuest> _quests;
private const int _headerHeight = 100;
public override void ParseForInfo()
{
_quests = new List<BaseQuest>();
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName"))
DisplayName = displayName.Text.ToUpperInvariant();
if (Object.TryGetValue(out FStructFallback[] quests, "QuestInfos")) // prout :)
public BaseBundle(UObject uObject, EIconStyle style) : base(uObject, style)
{
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));
}
Width = 1024;
Height = _headerHeight;
Margin = 0;
}
if (Object.TryGetValue(out FStructFallback[] completionRewards, "BundleCompletionRewards"))
public override void ParseForInfo()
{
foreach (var completionReward in completionRewards)
_quests = new List<BaseQuest>();
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text.ToUpperInvariant();
if (Object.TryGetValue(out FStructFallback[] quests, "QuestInfos")) // prout :)
{
if (!completionReward.TryGetValue(out int completionCount, "CompletionCount") ||
!completionReward.TryGetValue(out FStructFallback[] rewards, "Rewards")) continue;
foreach (var reward in rewards)
foreach (var quest in quests)
{
if (!reward.TryGetValue(out int quantity, "Quantity") ||
!reward.TryGetValue(out string templateId, "TemplateId") ||
!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition")) continue;
if (!quest.TryGetValue(out FSoftObjectPath questDefinition, "QuestDefinition")) continue;
if (!itemDefinition.AssetPathName.IsNone &&
!itemDefinition.AssetPathName.Text.Contains("/Items/Tokens/") &&
!itemDefinition.AssetPathName.Text.Contains("/Items/Quests"))
BaseQuest q;
var path = questDefinition.AssetPathName.Text;
do
{
_quests.Add(new BaseQuest(completionCount, itemDefinition, Style));
}
else if (!string.IsNullOrWhiteSpace(templateId))
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)
{
_quests.Add(new BaseQuest(completionCount, quantity, templateId, Style));
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;
}
Height += 256 * _quests.Count;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawHeader(c);
DrawDisplayName(c);
DrawQuests(c);
return new[] { 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)
public override SKImage Draw()
{
_headerPaint.TextSize -= 1;
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);
}
var shaper = new CustomSKShaper(_headerPaint.Typeface);
c.DrawShapedText(shaper, DisplayName, Width / 2f, _headerHeight / 2f + _headerPaint.TextSize / 2 - 10, _headerPaint);
}
private void DrawQuests(SKCanvas c)
{
var y = _headerHeight;
foreach (var quest in _quests)
private readonly SKPaint _headerPaint = new()
{
quest.DrawQuest(c, y);
y += quest.Height;
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;
}
}
}
}
}

View File

@ -1,10 +1,8 @@
using System.Linq;
using CUE4Parse.GameTypes.FN.Enums;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
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;
@ -13,150 +11,260 @@ using FModel.ViewModels.ApiEndpoints.Models;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class BaseCommunity : BaseIcon
namespace FModel.Creator.Bases.FN
{
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)
public class BaseCommunity : BaseIcon
{
Margin = 0;
_lowerDrawn = false;
_design = ApplicationService.ApiEndpointView.FModelApi.GetDesign(designName);
}
private readonly CommunityDesign _design;
private string _rarityName;
private string _source;
private string _season;
private bool _lowerDrawn;
public override void ParseForInfo()
{
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
if (Object.TryGetValue(out FPackageIndex series, "Series"))
public BaseCommunity(UObject uObject, EIconStyle style, string designName) : base(uObject, style)
{
_rarityName = series.Name;
}
else if (Object.TryGetValue(out FInstancedStruct[] dataList, "DataList") &&
dataList.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FPackageIndex _, "Series") == true) is { } dl)
{
_rarityName = dl.NonConstStruct?.Get<FPackageIndex>("Series").Name;
}
else if (Object.TryGetValue(out FStructFallback componentContainer, "ComponentContainer") &&
componentContainer.TryGetValue(out FPackageIndex[] components, "Components") &&
components.FirstOrDefault(c => c.Name.Contains("Series")) is { } seriesDef &&
seriesDef.TryLoad(out var seriesDefObj) && seriesDefObj is not null &&
seriesDefObj.TryGetValue(out series, "Series"))
{
_rarityName = series.Name;
}
else
{
_rarityName = Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription();
Margin = 0;
_lowerDrawn = false;
_design = ApplicationService.ApiEndpointView.FModelApi.GetDesign(designName);
}
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 SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
if (_design == null)
public override void ParseForInfo()
{
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);
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();
}
return new[] { ret };
}
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
{
if (_design == null) return;
if (_design.DrawSource)
public override SKImage Draw()
{
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();
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);
}
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);
var triggers = _design.GameplayTags.DrawCustomOnly ? new[] { "Cosmetics.UserFacingFlags." } : new[] { "Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender." };
GetUserFacingFlags(gameplayTags.GetAllGameplayTags(triggers));
}
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..];
(int chapterIdx, int seasonIdx) = GetInternalSID(int.Parse(s));
return $"C{chapterIdx} S{seasonIdx}";
}
private new void DrawBackground(SKCanvas c)
{
if (_design.Rarities.TryGetValue(_rarityName, out var rarity))
private void CheckGameplayTags(FGameplayTagContainer gameplayTags)
{
c.DrawBitmap(rarity.Background, 0, 0, ImagePaint);
c.DrawBitmap(rarity.Upper, 0, 0, ImagePaint);
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."));
}
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))
private string GetCosmeticSet(string setName, bool bShort)
{
c.DrawBitmap(rarity.Lower, 0, 0, ImagePaint);
return bShort ? setName["Cosmetics.Set.".Length..] : base.GetCosmeticSet(setName);
}
else
{
base.DrawTextBackground(c);
}
}
private new void DrawDisplayName(SKCanvas c)
{
if (string.IsNullOrEmpty(DisplayName)) return;
if (_design.Fonts.TryGetValue(nameof(DisplayName), out var font))
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;
@ -168,111 +276,30 @@ public class BaseCommunity : BaseIcon
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(text, DisplayNamePaint);
var x = font.Alignment switch
{
SKTextAlign.Center => Width / 2f,
SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f,
SKTextAlign.Right => font.X - DisplayNamePaint.MeasureText(text),
_ => font.X
};
c.DrawShapedText(shaper, DisplayName, x, font.Y, DisplayNamePaint);
c.DrawShapedText(shaper, text, x, font.Y, DisplayNamePaint);
}
else
private void DrawUserFacingFlags(SKCanvas c, bool customOnly)
{
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)
if (UserFacingFlags == null || UserFacingFlags.Length < 1) return;
if (customOnly)
{
DescriptionPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(DescriptionPaint.Typeface);
var x = font.Alignment switch
{
SKTextAlign.Center => Width / 2f,
_ => font.X
};
if (font.MaxLineCount < 2)
{
c.DrawShapedText(shaper, Description, x, font.Y, DescriptionPaint);
c.DrawBitmap(_design.GameplayTags.Custom, 0, 0, ImagePaint);
}
else
{
Utils.DrawMultilineText(c, Description, Width - Margin * 2, Margin, DescriptionPaint.TextAlign,
new SKRect(Margin, font.Y, Width - Margin, Height), DescriptionPaint, out _);
// add size to api
// draw
}
}
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 x = font.Alignment switch
{
SKTextAlign.Center => Width / 2f,
_ => font.X
};
c.DrawShapedText(shaper, text, x, font.Y, DisplayNamePaint);
}
private void DrawUserFacingFlags(SKCanvas c, bool customOnly)
{
if (UserFacingFlags == null || UserFacingFlags.Count < 1) return;
if (customOnly)
{
c.DrawBitmap(_design.GameplayTags.Custom, 0, 0, ImagePaint);
}
else
{
// add size to api
// draw
}
}
}
}

View File

@ -1,325 +1,297 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using CUE4Parse.GameTypes.FN.Enums;
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 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 Dictionary<string, 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 if (Object.TryGetValue(out FStructFallback componentContainer, "ComponentContainer")) GetSeries(componentContainer);
else GetRarity(Object.GetOrDefault("Rarity", EFortRarity.Uncommon)); // default is uncommon
if (Object.TryGetValue(out FInstancedStruct[] dataList, "DataList"))
{
GetSeries(dataList);
Preview = Utils.GetBitmap(dataList);
}
// preview
if (Preview is null)
{
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", "EntryListIcon", "SmallPreviewImage", "BundleImage", "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);
else if (Object.TryGetValue(out UMaterialInstanceConstant materialInstancePreview, "EventCalloutImage"))
Preview = Utils.GetBitmap(materialInstancePreview);
else if (Object.TryGetValue(out FStructFallback brush, "IconBrush") && brush.TryGetValue(out UTexture2D res, "ResourceObject"))
Preview = Utils.GetBitmap(res);
}
// text
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName", "BundleName", "DefaultHeaderText", "UIDisplayName", "EntryName", "EventCalloutTitle"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description", "ItemDescription", "BundleDescription", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription", "EventCalloutDescription"))
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 = Utils.GetLocalizedResource("Fort.Cosmetics", "ItemWrapShortDescription", "Wrap");
// Only works on non-cataba designs
if (Object.TryGetValue(out FStructFallback eventArrowColor, "EventArrowColor") &&
eventArrowColor.TryGetValue(out FLinearColor specifiedArrowColor, "SpecifiedColor") &&
Object.TryGetValue(out FStructFallback eventArrowShadowColor, "EventArrowShadowColor") &&
eventArrowShadowColor.TryGetValue(out FLinearColor specifiedShadowColor, "SpecifiedColor"))
{
Background = new[] { SKColor.Parse(specifiedArrowColor.Hex), SKColor.Parse(specifiedShadowColor.Hex) };
Border = new[] { SKColor.Parse(specifiedShadowColor.Hex), SKColor.Parse(specifiedArrowColor.Hex) };
}
Description = Utils.RemoveHtmlTags(Description);
}
public override void ParseForInfo()
{
ParseForReward(UserSettings.Default.CosmeticDisplayAsset);
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 SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
Draw(c);
return new[] { ret };
}
private void GetSeries(FPackageIndex s)
{
if (!Utils.TryGetPackageIndexExport(s, out UObject export)) return;
GetSeries(export);
}
private void GetSeries(FInstancedStruct[] s)
{
if (s.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FPackageIndex _, "Series") == true) is { } dl)
GetSeries(dl.NonConstStruct?.Get<FPackageIndex>("Series"));
}
private void GetSeries(FStructFallback s)
{
if (!s.TryGetValue(out FPackageIndex[] components, "Components")) return;
if (components.FirstOrDefault(c => c.Name.Contains("Series")) is not { } seriesDef ||
!seriesDef.TryLoad(out var seriesDefObj) || seriesDefObj is null ||
!seriesDefObj.TryGetValue(out UObject series, "Series")) return;
GetSeries(series);
}
protected void GetSeries(UObject uObject)
{
if (uObject is UTexture2D texture2D)
{
SeriesBackground = texture2D.Decode();
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(EFortRarity r)
{
if (!Utils.TryLoadObject("FortniteGame/Content/Balance/RarityData.RarityData", out UObject export)) return;
if (export.GetByIndex<FStructFallback>((int) r) 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 (int, int) GetInternalSID(int number)
{
static int GetSeasonsInChapter(int chapter) => chapter switch
{
1 => 10,
2 => 8,
3 => 4,
4 => 5,
_ => 10
};
var chapterIdx = 0;
var seasonIdx = 0;
while (number > 0)
{
var seasonsInChapter = GetSeasonsInChapter(++chapterIdx);
if (number > seasonsInChapter)
number -= seasonsInChapter;
else
{
seasonIdx = number;
number = 0;
}
}
return (chapterIdx, seasonIdx);
}
protected string GetCosmeticSeason(string seasonNumber)
{
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
var initial = int.Parse(s);
(int chapterIdx, int seasonIdx) = GetInternalSID(initial);
var season = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}");
var introduced = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in <SeasonText>{0}</>.");
if (initial <= 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, chapterIdx), string.Format(season, seasonIdx));
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 Dictionary<string, SKBitmap>(userFacingFlags.Count);
foreach (var flag in userFacingFlags)
{
if (flag.Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests", StringComparison.OrdinalIgnoreCase))
{
if (Object.ExportType.Equals("AthenaPetCarrierItemDefinition", StringComparison.OrdinalIgnoreCase))
UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png"))?.Stream);
else UserFacingFlags[flag] = 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(flag, 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[flag] = 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.Values.Where(flag => flag != null))
{
c.DrawBitmap(flag.Resize(size), new SKPoint(x, Margin * (int) 2.5), ImagePaint);
x += size;
}
}
}
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;
}
}
}
}

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using CUE4Parse.GameTypes.FN.Enums;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Assets.Objects;
@ -8,286 +7,313 @@ 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
namespace FModel.Creator.Bases.FN
{
private readonly IList<IconStat> _statistics;
private const int _headerHeight = 128;
private bool _screenLayer;
public BaseIconStats(UObject uObject, EIconStyle style) : base(uObject, style)
public class BaseIconStats : BaseIcon
{
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");
}
private readonly IList<IconStat> _statistics;
private const int _headerHeight = 128;
private bool _screenLayer;
public override void ParseForInfo()
{
base.ParseForInfo();
DisplayName = DisplayName.ToUpperInvariant();
if (Object.TryGetValue(out FName accoladeType, "AccoladeType") &&
accoladeType.Text.Equals("EFortAccoladeType::Medal", StringComparison.OrdinalIgnoreCase))
public BaseIconStats(UObject uObject, EIconStyle style) : base(uObject, style)
{
_screenLayer = false;
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");
}
if (Object.TryGetValue(out FGameplayTagContainer poiLocations, "POILocations") &&
Utils.TryLoadObject("FortniteGame/Content/Quests/QuestIndicatorData.QuestIndicatorData", out UObject uObject) &&
uObject.TryGetValue(out FStructFallback[] challengeMapPoiData, "ChallengeMapPoiData"))
public override void ParseForInfo()
{
foreach (var location in poiLocations)
base.ParseForInfo();
DisplayName = DisplayName.ToUpperInvariant();
if (Object.TryGetValue(out FName accoladeType, "AccoladeType") &&
accoladeType.Text.Equals("EFortAccoladeType::Medal", StringComparison.OrdinalIgnoreCase))
{
var locationName = "Unknown";
foreach (var poi in challengeMapPoiData)
_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)
{
if (!poi.TryGetValue(out FStructFallback locationTag, "LocationTag") || !locationTag.TryGetValue(out FName tagName, "TagName") ||
tagName != location.TagName || !poi.TryGetValue(out FText text, "Text")) continue;
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;
locationName = text.Text;
break;
}
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "B0C091D7409B1657423C5F97E9CF4C77", "LOCATION NAME"), locationName.ToUpper()));
}
_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 > 0)
if (Object.TryGetValue(out FStructFallback maxStackSize, "MaxStackSize"))
{
_statistics.Add(new IconStat("Max Stack", v, 15));
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));
}
}
else if (TryGetCurveTableStat(maxStackSize, out var s))
if (Object.TryGetValue(out FStructFallback xpRewardAmount, "XpRewardAmount") && TryGetCurveTableStat(xpRewardAmount, out var x))
{
_statistics.Add(new IconStat("Max Stack", s, 15));
_statistics.Add(new IconStat("XP Amount", x));
}
}
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 int bpc, "BulletsPerCartridge"))
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))
{
var multiplier = bpc != 0f ? bpc : 1;
if (weaponRowValue.TryGetValue(out float dmgPb, "DmgPB") && dmgPb != 0f)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "35D04D1B45737BEA25B69686D9E085B9", "Damage"), dmgPb * multiplier, 200));
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "BF7E3CF34A9ACFF52E95EAAD4F09F133", "Damage to Player"), dmgPb, 200));
}
if (weaponRowValue.TryGetValue(out float mdpc, "MaxDamagePerCartridge") && mdpc >= 0f)
if (weaponRowValue.TryGetValue(out int clipSize, "ClipSize") && clipSize != 0)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), mdpc, 200));
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "068239DD4327B36124498C9C5F61C038", "Magazine Size"), clipSize, 50));
}
else if (weaponRowValue.TryGetValue(out float dmgCritical, "DamageZone_Critical"))
if (weaponRowValue.TryGetValue(out float firingRate, "FiringRate") && firingRate != 0f)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), dmgPb * dmgCritical * multiplier, 200));
_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 (weaponRowValue.TryGetValue(out int clipSize, "ClipSize") && clipSize != 0)
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)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "068239DD4327B36124498C9C5F61C038", "Magazine Size"), clipSize, 50));
statValue = keys[0].KeyValue;
return true;
}
if (weaponRowValue.TryGetValue(out float firingRate, "FiringRate") && firingRate != 0f)
statValue = 0F;
return false;
}
private string GetRarityName(FName r)
{
var rarity = EFortRarity.Uncommon;
switch (r.Text)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "27B80BA44805ABD5A2D2BAB2902B250C", "Fire Rate"), firingRate, 15));
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 (weaponRowValue.TryGetValue(out float armTime, "ArmTime") && armTime != 0f)
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)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "3BFEB8BD41A677CC5F45B9A90D6EAD6F", "Arming Delay"), armTime, 125));
_informationPaint.TextSize -= 1;
}
if (weaponRowValue.TryGetValue(out float reloadTime, "ReloadTime") && reloadTime != 0f)
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))
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6EA26D1A4252034FBD869A90F9A6E49A", "Reload Time"), reloadTime, 15));
_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;
}
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, Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription()))
foreach (var stat in _statistics)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6FA2882140CB69DE32FD73A392F0585B", "Durability"), duraByRarity, 20));
stat.Draw(c, Border[0].WithAlpha(100), Width, _headerHeight, ref outY);
outY += 50;
}
}
if (!string.IsNullOrEmpty(Description))
Height += 40 + (int) _informationPaint.TextSize * Utils.SplitLines(Description, _informationPaint, Width - 20).Count;
Height += 50 * _statistics.Count;
}
public override SKBitmap[] Draw()
public class IconStat
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
private readonly string _statName;
private readonly object _value;
private readonly float _maxValue;
DrawHeader(c);
DrawDisplayName(c);
DrawStatistics(c);
return new[] { 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.TryFindCurve(rowName, out var rowValue) &&
rowValue is FSimpleCurve s && s.Keys.Length > 0)
public IconStat(string statName, object value, float maxValue = 0)
{
statValue = s.Keys[0].Value;
return true;
_statName = statName.ToUpperInvariant();
_value = value;
_maxValue = maxValue;
}
statValue = 0F;
return false;
}
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)
private readonly SKPaint _statPaint = new()
{
_informationPaint.TextSize -= 1;
}
IsAntialias = true, FilterQuality = SKFilterQuality.High,
TextSize = 25, Typeface = Utils.Typefaces.DisplayName,
Color = SKColors.White
};
var shaper = new CustomSKShaper(_informationPaint.Typeface);
c.DrawShapedText(shaper, DisplayName, _headerHeight + _headerHeight / 3 + 10, _headerHeight / 2f + _informationPaint.TextSize / 3, _informationPaint);
}
private void DrawStatistics(SKCanvas c)
{
var outY = _headerHeight + 25f;
if (!string.IsNullOrEmpty(Description))
public void Draw(SKCanvas c, SKColor sliderColor, int width, int height, ref float y)
{
_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;
}
while (_statPaint.MeasureText(_statName) > height * 2)
{
_statPaint.TextSize -= 1;
}
foreach (var stat in _statistics)
{
stat.Draw(c, Border[0].WithAlpha(100), Width, _headerHeight, ref outY);
outY += 50;
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);
}
}
}
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 - 40)
{
_statPaint.TextSize -= 1;
}
var shaper = new CustomSKShaper(_statPaint.Typeface);
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;
if (floatValue < 0)
floatValue = 0;
var sliderWidth = (sliderRight - height * 2) * (floatValue / _maxValue);
c.DrawRect(new SKRect(height * 2, y, Math.Min(height * 2 + sliderWidth, sliderRight), y + 5), _statPaint);
}
}
}

View File

@ -5,95 +5,96 @@ using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class BaseItemAccessToken : UCreator
namespace FModel.Creator.Bases.FN
{
private readonly SKBitmap _locked, _unlocked;
private string _unlockedDescription, _exportName;
private BaseIcon _icon;
public BaseItemAccessToken(UObject uObject, EIconStyle style) : base(uObject, style)
public class BaseItemAccessToken : UCreator
{
_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);
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);
}
}
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", "ItemName") && displayName.Text != "TBD")
DisplayName = displayName.Text;
else
DisplayName = _icon?.DisplayName;
Description = Object.TryGetValue(out FText description, "Description", "ItemDescription") ? description.Text : _icon?.Description;
if (Object.TryGetValue(out FText unlockDescription, "UnlockDescription")) _unlockedDescription = unlockDescription.Text;
}
public override SKBitmap[] Draw()
{
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 new[] { 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.Width / 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);
}
}
}

View File

@ -1,49 +0,0 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.FN;
public class BaseJuno : BaseIcon
{
private BaseIcon _character;
public BaseJuno(UObject uObject, EIconStyle style) : base(uObject, style)
{
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FSoftObjectPath baseCid, "BaseAthenaCharacterItemDefinition") &&
Utils.TryLoadObject(baseCid.AssetPathName.Text, out UObject cid))
{
_character = new BaseIcon(cid, Style);
_character.ParseForInfo();
if (Object.TryGetValue(out FSoftObjectPath assembledMeshSchema, "AssembledMeshSchema", "LowDetailsAssembledMeshSchema") &&
Utils.TryLoadObject(assembledMeshSchema.AssetPathName.Text, out UObject meshSchema) &&
meshSchema.TryGetValue(out FInstancedStruct[] additionalData, "AdditionalData"))
{
foreach (var data in additionalData)
{
if (data.NonConstStruct?.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "SmallPreviewImage") ?? false)
{
_character.Preview = Utils.GetBitmap(largePreview);
break;
}
}
}
}
if (Object.TryGetValue(out FSoftObjectPath baseEid, "BaseAthenaDanceItemDefinition") &&
Utils.TryLoadObject(baseEid.AssetPathName.Text, out UObject eid))
{
_character = new BaseIcon(eid, Style);
_character.ParseForInfo();
}
}
public override SKBitmap[] Draw() => _character.Draw();
}

View File

@ -1,92 +1,83 @@
using System.Linq;
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
namespace FModel.Creator.Bases.FN
{
public BaseMaterialInstance(UObject uObject, EIconStyle style) : base(uObject, style)
public class BaseMaterialInstance : BaseIcon
{
Background = new[] { SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69") };
Border = new[] { SKColor.Parse("9092AB") };
}
public override void ParseForInfo()
{
if (Object is not UMaterialInstanceConstant material) return;
texture_finding:
foreach (var textureParameter in material.TextureParameterValues) // get texture from base material
public BaseMaterialInstance(UObject uObject, EIconStyle style) : base(uObject, style)
{
if (!textureParameter.ParameterValue.TryLoad<UTexture2D>(out var texture) || Preview != null) continue;
switch (textureParameter.ParameterInfo.Name.Text)
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
{
case "SeriesTexture":
GetSeries(texture);
break;
case "TextureA":
case "TextureB":
case "OfferImage":
case "CarTexture":
Preview = Utils.GetBitmap(texture);
break;
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;
}
}
}
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
public override SKImage Draw()
{
if (material.TryGetValue(out FPackageIndex parent, "Parent"))
Utils.TryGetPackageIndexExport(parent, out material);
else return;
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
if (material == null) return;
}
if (Preview == null)
{
if (material.TryGetValue(out FPackageIndex parent, "Parent"))
Utils.TryGetPackageIndexExport(parent, out material);
goto texture_finding;
}
foreach (var vectorParameter in material.VectorParameterValues)
{
if (vectorParameter.ParameterValue == null) continue;
switch (vectorParameter.ParameterInfo.Name.Text)
switch (Style)
{
case "Background_Color_A":
Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex);
Border[0] = Background[0];
case EIconStyle.NoBackground:
DrawPreview(c);
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);
default:
DrawBackground(c);
DrawPreview(c);
break;
}
return SKImage.FromBitmap(ret);
}
}
public override SKBitmap[] Draw()
{
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 new[] { ret };
}
}
}

View File

@ -1,83 +1,85 @@
using CUE4Parse.UE4.Assets.Exports;
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
namespace FModel.Creator.Bases.FN
{
public BaseMtxOffer(UObject uObject, EIconStyle style) : base(uObject, style)
public class BaseMtxOffer : UCreator
{
Background = new[] { SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69") };
Border = new[] { SKColor.Parse("9092AB") };
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FSoftObjectPath image, "SoftDetailsImage", "SoftTileImage"))
public BaseMtxOffer(UObject uObject, EIconStyle style) : base(uObject, style)
{
Preview = Utils.GetBitmap(image);
Background = new[] {SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69")};
Border = new[] {SKColor.Parse("9092AB")};
}
if (Object.TryGetValue(out FStructFallback gradient, "Gradient") &&
gradient.TryGetValue(out FLinearColor start, "Start") &&
gradient.TryGetValue(out FLinearColor stop, "Stop"))
public override void ParseForInfo()
{
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 (Object.TryGetValue(out FStructFallback typeImage, "DetailsImage", "TileImage") &&
typeImage.TryGetValue(out FPackageIndex resource, "ResourceObject"))
{
if (detail.TryGetValue(out FText detailName, "Name"))
{
Description += $"\n- {detailName.Text.TrimEnd()}";
}
Preview = Utils.GetBitmap(resource);
}
if (detail.TryGetValue(out FText detailValue, "Value") && !string.IsNullOrEmpty(detailValue.Text))
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)
{
Description += $" ({detailValue.Text})";
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);
}
Description = Utils.RemoveHtmlTags(Description);
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
switch (Style)
public override SKImage Draw()
{
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;
}
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
return new[] { 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);
}
}
}
}

View File

@ -1,45 +0,0 @@
using System.Collections.Generic;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.FN;
public class BaseOfferDisplayData : UCreator
{
private readonly List<BaseMaterialInstance> _offerImages;
public BaseOfferDisplayData(UObject uObject, EIconStyle style) : base(uObject, style)
{
_offerImages = new List<BaseMaterialInstance>();
}
public override void ParseForInfo()
{
if (!Object.TryGetValue(out FStructFallback[] contextualPresentations, "ContextualPresentations"))
return;
for (var i = 0; i < contextualPresentations.Length; i++)
{
if (!contextualPresentations[i].TryGetValue(out FSoftObjectPath material, "Material") ||
!material.TryLoad(out UMaterialInterface presentation)) continue;
var offerImage = new BaseMaterialInstance(presentation, Style);
offerImage.ParseForInfo();
_offerImages.Add(offerImage);
}
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap[_offerImages.Count];
for (var i = 0; i < ret.Length; i++)
{
ret[i] = _offerImages[i]?.Draw()[0];
}
return ret;
}
}

View File

@ -1,4 +1,4 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
@ -6,72 +6,71 @@ using FModel.Services;
using FModel.ViewModels;
using SkiaSharp;
namespace FModel.Creator.Bases.FN;
public class BasePlaylist : UCreator
namespace FModel.Creator.Bases.FN
{
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
private SKBitmap _missionIcon;
public BasePlaylist(UObject uObject, EIconStyle style) : base(uObject, style)
public class BasePlaylist : UCreator
{
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");
}
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
private SKBitmap _missionIcon;
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 is not { HasShowcase: true } ||
!_apiEndpointView.FortniteApi.TryGetBytes(playlist.Data.Images.Showcase, out var image))
return;
Preview = Utils.GetBitmap(image).ResizeWithRatio(1024, 512);
Width = Preview.Width;
Height = Preview.Height;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
switch (Style)
public BasePlaylist(UObject uObject, EIconStyle style) : base(uObject, 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;
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");
}
return new[] { ret };
}
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);
private void DrawMissionIcon(SKCanvas c)
{
if (_missionIcon == null) return;
c.DrawBitmap(_missionIcon, new SKPoint(5, 5), ImagePaint);
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);
}
}
}
}

View File

@ -1,9 +1,7 @@
using System;
using System;
using System.Linq;
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.UObject;
@ -12,267 +10,250 @@ using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class BaseQuest : BaseIcon
namespace FModel.Creator.Bases.FN
{
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)
public class BaseQuest : BaseIcon
{
Margin = 0;
Width = 1024;
Height = 256;
DefaultPreview = Utils.GetBitmap("FortniteGame/Content/Athena/HUD/Quests/Art/T_NPC_Default.T_NPC_Default");
if (uObject != null)
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)
{
_screenLayer = uObject.ExportType.Equals("FortFeatItemDefinition", StringComparison.OrdinalIgnoreCase);
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"))
private BaseQuest(int completionCount, EIconStyle style) : this(null, style) // completion
{
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);
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);
}
else
public BaseQuest(int completionCount, FSoftObjectPath itemDefinition, EIconStyle style) : this(completionCount, style) // completion
{
if (!string.IsNullOrEmpty(ShortDescription))
_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 (string.IsNullOrEmpty(DisplayName) && !string.IsNullOrEmpty(Description))
DisplayName = Description;
if (DisplayName == Description)
Description = string.Empty;
if ((Object.TryGetValue(out FSoftObjectPath icon, "QuestGiverWidgetIcon", "NotificationIconOverride") &&
Utils.TryLoadObject(icon.AssetPathName.Text, out UObject iconObject)) ||
(Object.TryGetValue(out FSoftObjectPath tandemCharacterData, "TandemCharacterData") &&
Utils.TryLoadObject(tandemCharacterData.AssetPathName.Text, out UObject uObject) &&
uObject.TryGetValue(out FSoftObjectPath tandemIcon, "EntryListIcon", "ToastIcon") &&
Utils.TryLoadObject(tandemIcon.AssetPathName.Text, out iconObject)))
{
Preview = iconObject switch
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"))
{
UTexture2D text => Utils.GetBitmap(text),
UMaterialInstanceConstant mat => Utils.GetBitmap(mat),
_ => Preview
};
}
}
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, $"{name}:{primaryAssetName}");
Preview = Utils.GetBitmap(tandemIcon);
}
}
}
if (_reward == null)
{
FName rowName = null;
if (Object.TryGetValue(out UDataTable rewardsTable, "RewardsTable"))
rowName = new FName("Default");
else if (Object.TryGetValue(out FStructFallback[] rewardTableRows, "IndividualRewardTableRows") &&
rewardTableRows.Length > 0 && rewardTableRows[0].TryGetValue(out rowName, "RowName") &&
rewardTableRows[0].TryGetValue(out rewardsTable, "DataTable")) {}
if (Object.TryGetValue(out int objectiveCompletionCount, "ObjectiveCompletionCount"))
_count = objectiveCompletionCount;
if (rewardsTable != null && rowName != null && rewardsTable.TryGetDataTableRow(rowName.Text, StringComparison.InvariantCulture, out var row))
if (Object.TryGetValue(out FStructFallback[] objectives, "Objectives") && objectives.Length > 0)
{
if (row.TryGetValue(out FName templateId, "TemplateId") &&
row.TryGetValue(out int quantity, "Quantity"))
// 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();
}
if (_reward == null && Object.TryGetValue(out FStructFallback[] hiddenRewards, "HiddenRewards"))
public void DrawQuest(SKCanvas c, int y)
{
foreach (var hiddenReward in hiddenRewards)
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)
{
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 SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawQuest(c, 0);
return new[] { 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 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 shaper = new CustomSKShaper(_informationPaint.Typeface);
c.DrawShapedText(shaper, DisplayName, Height, y + 50, _informationPaint);
var upper = s.SubstringAfter(">").SubstringBefore("</>");
return string.Format(Utils.RemoveHtmlTags(s.Replace(upper, upper.ToUpper())), isAll ? string.Empty : completionCount).Replace(" ", " ");
}
var outY = y + 75f;
if (!string.IsNullOrWhiteSpace(Description))
private readonly SKPaint _informationPaint = new()
{
_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);
}
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Color = SKColor.Parse("#262630")
};
_informationPaint.Color = Border[0].WithAlpha(100);
c.DrawRect(new SKRect(Height, outY, Width - 150, outY + 5), _informationPaint);
if (_count > 0)
private void DrawBackground(SKCanvas c, int y)
{
_informationPaint.TextSize = 25;
_informationPaint.Color = SKColors.White;
_informationPaint.Typeface = Utils.Typefaces.BundleNumber;
c.DrawText("0 / ", new SKPoint(Width - 130, outY + 10), _informationPaint);
c.DrawRect(new SKRect(Margin, y, Width, y + Height), _informationPaint);
_informationPaint.Color = Border[0];
c.DrawText(_count.ToString(), new SKPoint(Width - 95, outY + 10), _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);
}
_reward.DrawQuest(c, new SKRect(Height, outY + 25, Width - 20, y + Height - 25));
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));
}
}
}
}

View File

@ -1,5 +1,5 @@
using System;
using System.Linq;
using System;
using System.Collections.Generic;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
@ -8,151 +8,167 @@ using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class Page
namespace FModel.Creator.Bases.FN
{
public int LevelsNeededForUnlock;
public int RewardsNeededForUnlock;
public Reward[] RewardEntryList;
}
public class BaseSeason : UCreator
{
private Reward _firstWinReward;
private Page[] _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)
public class BaseSeason : UCreator
{
Width = 1024;
Height = _headerHeight + 50;
Margin = 0;
}
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 override void ParseForInfo()
{
_bookXpSchedule = Array.Empty<Page>();
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName"))
DisplayName = displayName.Text.ToUpperInvariant();
if (Object.TryGetValue(out FStructFallback seasonFirstWinRewards, "SeasonFirstWinRewards") &&
seasonFirstWinRewards.TryGetValue(out FStructFallback[] rewards, "Rewards"))
public BaseSeason(UObject uObject, EIconStyle style) : base(uObject, style)
{
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;
}
Width = 1024;
Height = _headerHeight + 50;
Margin = 0;
}
if (Object.TryGetValue(out FPackageIndex[] additionalSeasonData, "AdditionalSeasonData"))
public override void ParseForInfo()
{
foreach (var data in additionalSeasonData)
{
if (!Utils.TryGetPackageIndexExport(data, out UObject packageIndex) ||
!packageIndex.TryGetValue(out FStructFallback[] pageList, "PageList")) continue;
_bookXpSchedule = new Dictionary<int, List<Reward>>();
var i = 0;
_bookXpSchedule = new Page[pageList.Length];
foreach (var page in pageList)
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 (!page.TryGetValue(out int levelsNeededForUnlock, "LevelsNeededForUnlock") ||
!page.TryGetValue(out int rewardsNeededForUnlock, "RewardsNeededForUnlock") ||
!page.TryGetValue(out FPackageIndex[] rewardEntryList, "RewardEntryList"))
continue;
if (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") ||
!Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue;
var p = new Page
{
LevelsNeededForUnlock = levelsNeededForUnlock,
RewardsNeededForUnlock = rewardsNeededForUnlock,
RewardEntryList = new Reward[rewardEntryList.Length]
};
for (var j = 0; j < p.RewardEntryList.Length; j++)
{
if (!Utils.TryGetPackageIndexExport(rewardEntryList[j], out packageIndex) ||
!packageIndex.TryGetValue(out FStructFallback battlePassOffer, "BattlePassOffer") ||
!battlePassOffer.TryGetValue(out FStructFallback rewardItem, "RewardItem") ||
!rewardItem.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") ||
!Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue;
p.RewardEntryList[j] = new Reward(uObject);
}
_bookXpSchedule[i++] = p;
_firstWinReward = new Reward(uObject);
break;
}
break;
}
}
Height += 100 * _bookXpSchedule.Sum(x => x.RewardEntryList.Length) / _bookXpSchedule.Length;
}
public override SKBitmap[] Draw()
{
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 new[] { 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")
};
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);
c.DrawShapedText(shaper, DisplayName, Width / 2f, _headerHeight / 2f + _headerPaint.TextSize / 2 - 10, _headerPaint);
}
private void DrawBookSchedule(SKCanvas c)
{
var x = 20;
var y = _headerHeight + 50;
foreach (var page in _bookXpSchedule)
{
foreach (var reward in page.RewardEntryList)
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"))
{
reward.DrawSeason(c, x, y, _DEFAULT_AREA_SIZE);
x += _DEFAULT_AREA_SIZE + 20;
// 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
}
y += _DEFAULT_AREA_SIZE + 20;
x = 20;
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;
}
}
}
}
}
}

View File

@ -1,26 +1,27 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports;
using SkiaSharp;
namespace FModel.Creator.Bases.FN;
public class BaseSeries : BaseIcon
namespace FModel.Creator.Bases.FN
{
public BaseSeries(UObject uObject, EIconStyle style) : base(uObject, style)
public class BaseSeries : BaseIcon
{
}
public BaseSeries(UObject uObject, EIconStyle style) : base(uObject, style)
{
}
public override void ParseForInfo()
{
GetSeries(Object);
}
public override void ParseForInfo()
{
GetSeries(Object);
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
public override SKImage Draw()
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawBackground(c);
return new []{ret};
return SKImage.FromBitmap(ret);
}
}
}

View File

@ -1,228 +0,0 @@
using System;
using System.Windows;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse.UE4.Versions;
using FModel.Framework;
using FModel.Settings;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class BaseTandem : BaseIcon
{
private string _generalDescription, _additionalDescription;
public BaseTandem(UObject uObject, EIconStyle style) : base(uObject, style)
{
DefaultPreview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/BattleRoyale/FeaturedItems/Outfit/T-AthenaSoldiers-CID-883-Athena-Commando-M-ChOneJonesy.T-AthenaSoldiers-CID-883-Athena-Commando-M-ChOneJonesy");
Margin = 0;
Width = 690;
Height = 1080;
}
public override void ParseForInfo()
{
base.ParseForInfo();
string sidePanel = string.Empty, entryList = string.Empty;
if (Object.TryGetValue(out FSoftObjectPath sidePanelIcon, "SidePanelIcon"))
sidePanel = sidePanelIcon.AssetPathName.Text;
if (Object.TryGetValue(out FSoftObjectPath entryListIcon, "EntryListIcon"))
entryList = entryListIcon.AssetPathName.Text;
// Overrides for generic "default" images Epic uses for Quest-only or unfinished NPCs
if (sidePanel.Contains("Clown") && entryList.Contains("Clown"))
Preview = null;
else if (sidePanel.Contains("Bane") && !Object.Name.Contains("Sorana"))
Preview = Utils.GetBitmap(entryList);
else if (!string.IsNullOrWhiteSpace(sidePanel) && !sidePanel.Contains("Clown"))
Preview = Utils.GetBitmap(sidePanel);
else if ((string.IsNullOrWhiteSpace(sidePanel) || sidePanel.Contains("Clown")) && !string.IsNullOrWhiteSpace(entryList))
Preview = Utils.GetBitmap(entryList);
if (Object.TryGetValue(out FText genDesc, "GeneralDescription"))
_generalDescription = genDesc.Text;
if (Object.TryGetValue(out FText addDesc, "AdditionalDescription"))
_additionalDescription = addDesc.Text;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawPreview(c);
DrawHaze(c);
// Korean is slightly smaller than other languages, so the font size is increased slightly
DrawName(c);
DrawGeneralDescription(c);
DrawAdditionalDescription(c);
return new[] { ret };
}
private readonly SKPaint _panelPaint = new() { IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = SKColor.Parse("#0045C7") };
private new void DrawBackground(SKCanvas c)
{
c.DrawBitmap(SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/npcleftside.png"))?.Stream).Resize(Width, Height), 0, 0, new SKPaint { IsAntialias = false, FilterQuality = SKFilterQuality.None, ImageFilter = SKImageFilter.CreateBlur(0, 25) });
using var rect1 = new SKPath { FillType = SKPathFillType.EvenOdd };
_panelPaint.Color = SKColor.Parse("#002A8C");
rect1.MoveTo(29, 0);
rect1.LineTo(62, Height);
rect1.LineTo(Width, Height);
rect1.LineTo(Width, 0);
rect1.LineTo(29, 0);
rect1.Close();
c.DrawPath(rect1, _panelPaint);
_panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(29, 0), new SKPoint(Width, Height),
new[] { SKColor.Parse("#002A8C") }, SKShaderTileMode.Clamp);
c.DrawPath(rect1, _panelPaint);
_panelPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(348, 196), 300, new[] { SKColor.Parse("#0049CE"), SKColor.Parse("#002A8C") }, SKShaderTileMode.Clamp);
c.DrawPath(rect1, _panelPaint);
using var rect2 = new SKPath { FillType = SKPathFillType.EvenOdd };
rect2.MoveTo(10, 0);
rect2.LineTo(30, 0);
rect2.LineTo(63, Height);
rect2.LineTo(56, Height);
rect2.LineTo(10, 0);
rect2.Close();
c.DrawPath(rect2, _panelPaint);
_panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(10, 0), new SKPoint(62, Height),
new[] { SKColor.Parse("#0045C7") }, SKShaderTileMode.Clamp);
c.DrawPath(rect2, _panelPaint);
}
private new void DrawPreview(SKCanvas c)
{
var previewToUse = Preview ?? DefaultPreview;
if (Preview == null)
{
previewToUse = DefaultPreview;
ImagePaint.BlendMode = SKBlendMode.DstOut;
ImagePaint.Color = SKColor.Parse("#00175F");
}
var x = -125;
switch (previewToUse.Width)
{
case 512 when previewToUse.Height == 1024:
previewToUse = previewToUse.ResizeWithRatio(500, 1000);
x = 100;
break;
case 512 when previewToUse.Height == 512:
case 128 when previewToUse.Height == 128:
previewToUse = previewToUse.Resize(512);
x = 125;
break;
default:
previewToUse = previewToUse.Resize(1000, 1000);
break;
}
c.DrawBitmap(previewToUse, x, 30, ImagePaint);
}
private void DrawHaze(SKCanvas c)
{
using var rect1 = new SKPath { FillType = SKPathFillType.EvenOdd };
rect1.MoveTo(29, 0);
rect1.LineTo(62, Height);
rect1.LineTo(Width, Height);
rect1.LineTo(Width, 0);
rect1.LineTo(29, 0);
rect1.Close();
_panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(343, 0), new SKPoint(343, Height),
new[] { SKColors.Transparent, SKColor.Parse("#001E70FF"), SKColor.Parse("#001E70").WithAlpha(200), SKColor.Parse("#001E70").WithAlpha(245), SKColor.Parse("#001E70") }, new[] { 0, (float) .1, (float) .65, (float) .85, 1 }, SKShaderTileMode.Clamp);
c.DrawPath(rect1, _panelPaint);
}
private void DrawName(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(DisplayName)) return;
DisplayNamePaint.TextSize = UserSettings.Default.AssetLanguage switch
{
ELanguage.Korean => 56,
_ => 42
};
DisplayNamePaint.TextScaleX = (float) 1.1;
DisplayNamePaint.Color = SKColors.White;
DisplayNamePaint.TextSkewX = (float) -.25;
DisplayNamePaint.TextAlign = SKTextAlign.Left;
var typeface = Utils.Typefaces.TandemDisplayName;
if (typeface == Utils.Typefaces.Default)
{
DisplayNamePaint.TextSize = 30;
}
DisplayNamePaint.Typeface = typeface;
var shaper = new CustomSKShaper(DisplayNamePaint.Typeface);
c.DrawShapedText(shaper, DisplayName.ToUpper(), 97, 900, DisplayNamePaint);
}
private void DrawGeneralDescription(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(_generalDescription)) return;
DescriptionPaint.TextSize = UserSettings.Default.AssetLanguage switch
{
ELanguage.Korean => 20,
_ => 17
};
DescriptionPaint.Color = SKColor.Parse("#00FFFB");
DescriptionPaint.TextAlign = SKTextAlign.Left;
var typeface = Utils.Typefaces.TandemGenDescription;
if (typeface == Utils.Typefaces.Default)
{
DescriptionPaint.TextSize = 21;
}
DescriptionPaint.Typeface = typeface;
var shaper = new CustomSKShaper(DescriptionPaint.Typeface);
c.DrawShapedText(shaper, _generalDescription.ToUpper(), 97, 930, DescriptionPaint);
}
private void DrawAdditionalDescription(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(_additionalDescription)) return;
DescriptionPaint.TextSize = UserSettings.Default.AssetLanguage switch
{
ELanguage.Korean => 22,
_ => 18
};
DescriptionPaint.Color = SKColor.Parse("#89D8FF");
DescriptionPaint.TextAlign = SKTextAlign.Left;
var typeface = Utils.Typefaces.TandemAddDescription;
if (typeface == Utils.Typefaces.Default)
{
DescriptionPaint.TextSize = 20;
}
DescriptionPaint.Typeface = typeface;
Utils.DrawMultilineText(c, _additionalDescription, Width, 0, SKTextAlign.Left,
new SKRect(97, 960, Width - 10, Height), DescriptionPaint, out _);
}
}

View File

@ -9,183 +9,185 @@ using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class BaseUserControl : UCreator
namespace FModel.Creator.Bases.FN
{
private List<Options> _optionValues = new();
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", "OptionText"))
DisplayName = optionDisplayName.Text.ToUpperInvariant();
if (Object.TryGetValue(out FText optionDescription, "OptionDescription", "OptionToolTip"))
private readonly SKPaint _displayNamePaint = new()
{
Description = optionDescription.Text;
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
};
if (string.IsNullOrWhiteSpace(Description)) return;
Height += (int) _descriptionPaint.TextSize * Utils.SplitLines(Description, _descriptionPaint, Width - Margin).Count;
Height += (int) _descriptionPaint.TextSize;
public BaseUserControl(UObject uObject, EIconStyle style) : base(uObject, style)
{
Width = 512;
Height = 128;
Margin = 32;
}
if (Object.TryGetValue(out FStructFallback[] optionValues, "OptionValues", "Options"))
public override void ParseForInfo()
{
_optionValues = new List<Options>();
foreach (var option in optionValues)
if (Object.TryGetValue(out FText optionDisplayName, "OptionDisplayName"))
DisplayName = optionDisplayName.Text.ToUpperInvariant();
if (Object.TryGetValue(out FText optionDescription, "OptionDescription"))
{
if (option.TryGetValue(out FText displayName, "DisplayName", "DisplayText"))
{
var opt = new Options { Option = displayName.Text.ToUpperInvariant() };
if (option.TryGetValue(out FLinearColor color, "Value"))
opt.Color = SKColor.Parse(color.Hex).WithAlpha(150);
Description = optionDescription.Text;
_optionValues.Add(opt);
}
else if (option.TryGetValue(out FName primaryAssetName, "PrimaryAssetName"))
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)
{
_optionValues.Add(new Options { Option = primaryAssetName.Text });
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 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)
if (Object.TryGetValue(out int iMin, "Min", "DefaultValue") &&
Object.TryGetValue(out int iMax, "Max"))
{
_optionValues.Add(new Options { Option = string.Format(format, i) });
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)
if (Object.TryGetValue(out float fMin, "Min", "DefaultValue") &&
Object.TryGetValue(out float fMax, "Max"))
{
_optionValues.Add(new Options { Option = string.Format(format, i) });
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;
}
Height += Margin;
Height += 35 * _optionValues.Count;
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawInformation(c);
return new[] { 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)
public override SKImage Draw()
{
_displayNamePaint.TextSize -= 2;
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);
}
var shaper = new CustomSKShaper(_displayNamePaint.Typeface);
c.DrawShapedText(shaper, DisplayName, Margin, Margin + _displayNamePaint.TextSize, _displayNamePaint);
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 });
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;
// 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);
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);
// 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()
public class Options
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.DisplayName, TextSize = 20,
Color = SKColor.Parse("EEFFFF"), TextAlign = SKTextAlign.Left
};
private const int _SPACE = 5;
private const int _HEIGHT = 30;
public string Option;
public SKColor Color = SKColor.Parse("55C5FC").WithAlpha(150);
private readonly SKPaint _optionPaint = new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.DisplayName, TextSize = 20,
Color = SKColor.Parse("EEFFFF"), TextAlign = SKTextAlign.Left
};
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;
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;
}
}
}
}

View File

@ -5,147 +5,149 @@ using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases.FN;
public class Reward
namespace FModel.Creator.Bases.FN
{
private string _rewardQuantity;
private BaseIcon _theReward;
public bool HasReward() => _theReward != null;
public Reward()
public class Reward
{
_rewardQuantity = "x0";
}
private string _rewardQuantity;
private BaseIcon _theReward;
public Reward(int quantity, FName primaryAssetName) : this(quantity, primaryAssetName.Text)
{
}
public bool HasReward() => _theReward != null;
public Reward(int quantity, string assetName) : this()
{
_rewardQuantity = $"x{quantity:###,###,###}".Trim();
if (assetName.Contains(':'))
public Reward()
{
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]);
_rewardQuantity = "x0";
}
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())
public Reward(int quantity, FName primaryAssetName) : this(quantity, primaryAssetName.Text)
{
c.DrawBitmap((_theReward.Preview ?? _theReward.DefaultPreview).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);
c.DrawShapedText(shaper, _rewardQuantity, rect.Left + rect.Height + 25, rect.MidY + 20, _rewardPaint);
}
else
public Reward(int quantity, string assetName) : this()
{
_rewardPaint.Color = SKColors.White;
_rewardPaint.Typeface = Utils.Typefaces.BundleNumber;
c.DrawText("No Reward", new SKPoint(rect.Left, rect.MidY + 20), _rewardPaint);
}
}
_rewardQuantity = $"x{quantity:###,###,###}".Trim();
public void DrawSeasonWin(SKCanvas c, int size)
{
if (!HasReward()) return;
c.DrawBitmap((_theReward.Preview ?? _theReward.DefaultPreview).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/Athena/UI/Frontend/Art/T_UI_BP_BattleStar_L.T_UI_BP_BattleStar_L");
// 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-XPUncommon-L.T-FNBR-XPUncommon-L");
// 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:
if (assetName.Contains(':'))
{
var path = Utils.GetFullPath($"FortniteGame/(?:Content/Athena|Content/Items|Plugins/GameFeatures)/.*?/{trigger}.uasset"); // 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))
var parts = assetName.Split(':');
if (parts[0].Equals("HomebaseBannerIcon", StringComparison.CurrentCultureIgnoreCase))
{
_theReward = new BaseIcon(d, EIconStyle.Default);
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} ({_rewardQuantity})";
_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;
}
break;
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;
}
}
}
}
}
}

View File

@ -1,287 +0,0 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Extensions;
using SkiaSharp;
namespace FModel.Creator.Bases.MV;
public class BaseFighter : UCreator
{
private float _xOffset = 1f;
private float _yOffset = 1f;
private float _zoom = 1f;
private readonly SKBitmap _pattern;
private readonly SKBitmap _perk;
private readonly SKBitmap _emote;
private readonly SKBitmap _skin;
private (SKBitmap, List<string>) _fighterType;
private readonly List<SKBitmap> _recommendedPerks;
private readonly List<SKBitmap> _availableTaunts;
private readonly List<SKBitmap> _skins;
public BaseFighter(UObject uObject, EIconStyle style) : base(uObject, style)
{
Width = 1024;
DisplayNamePaint.TextSize = 100;
DisplayNamePaint.TextAlign = SKTextAlign.Left;
DisplayNamePaint.Typeface = Utils.Typefaces.TandemDisplayName;
DescriptionPaint.TextSize = 25;
DescriptionPaint.Typeface = Utils.Typefaces.TandemGenDescription;
DefaultPreview = Utils.GetBitmap("/Game/Panda_Main/UI/PreMatch/Images/DiamondPortraits/0010_Random.0010_Random");
_pattern = Utils.GetBitmap("/Game/Panda_Main/UI/Assets/UI_Textures/halftone_jagged.halftone_jagged");
_perk = Utils.GetBitmap("/Game/Panda_Main/UI/Assets/Icons/ui_icons_perks.ui_icons_perks");
_emote = Utils.GetBitmap("/Game/Panda_Main/UI/Assets/Icons/ui_icons_emote.ui_icons_emote");
_skin = Utils.GetBitmap("/Game/Panda_Main/UI/Assets/Icons/ui_icons_skins.ui_icons_skins");
_fighterType.Item2 = new List<string>();
_recommendedPerks = new List<SKBitmap>();
_availableTaunts = new List<SKBitmap>();
_skins = new List<SKBitmap>();
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FLinearColor backgroundColor, "BackgroundColor"))
Background = new[] { SKColor.Parse(backgroundColor.Hex) };
if (Object.TryGetValue(out FSoftObjectPath portraitMaterial, "CollectionsPortraitMaterial") &&
portraitMaterial.TryLoad(out UMaterialInstanceConstant portrait))
{
_xOffset = Math.Abs(portrait.ScalarParameterValues.FirstOrDefault(x => x.ParameterInfo.Name.Text == "XOffset")?.ParameterValue ?? 1f);
_yOffset = Math.Abs(portrait.ScalarParameterValues.FirstOrDefault(x => x.ParameterInfo.Name.Text == "YOffset")?.ParameterValue / 10 ?? 1f);
_zoom = Math.Clamp(portrait.ScalarParameterValues.FirstOrDefault(x => x.ParameterInfo.Name.Text == "Zoom")?.ParameterValue ?? 1f, 0, 1);
Preview = Utils.GetBitmap(portrait);
}
else if (Object.TryGetValue(out FSoftObjectPath portraitTexture, "NewCharacterSelectPortraitTexture", "HUDPortraitTexture"))
Preview = Utils.GetBitmap(portraitTexture);
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text;
GetFighterClassInfo(Object.GetOrDefault("Class", EFighterClass.Support));
_fighterType.Item2.Add(Utils.GetLocalizedResource(Object.GetOrDefault("Type", EFighterType.Horizontal)));
if (Object.TryGetValue(out FText property, "Property"))
_fighterType.Item2.Add(property.Text);
if (Object.TryGetValue(out UScriptSet recommendedPerks, "RecommendedPerkDatas")) // PORCO DIO WB USE ARRAYS!!!!!!
{
foreach (var recommendedPerk in recommendedPerks.Properties)
{
if (recommendedPerk.GenericValue is not FPackageIndex packageIndex ||
!Utils.TryGetPackageIndexExport(packageIndex, out UObject export) ||
!export.TryGetValue(out FSoftObjectPath rewardThumbnail, "RewardThumbnail"))
continue;
_recommendedPerks.Add(Utils.GetBitmap(rewardThumbnail));
}
}
if (Object.TryGetValue(out FSoftObjectPath[] availableTaunts, "AvailableTauntData"))
{
foreach (var taunt in availableTaunts)
{
if (!Utils.TryLoadObject(taunt.AssetPathName.Text, out UObject export) ||
!export.TryGetValue(out FSoftObjectPath rewardThumbnail, "RewardThumbnail"))
continue;
_availableTaunts.Add(Utils.GetBitmap(rewardThumbnail));
}
}
if (Object.TryGetValue(out FSoftObjectPath[] skins, "Skins"))
{
foreach (var skin in skins)
{
if (!Utils.TryLoadObject(skin.AssetPathName.Text, out UObject export) ||
!export.TryGetValue(out FSoftObjectPath rewardThumbnail, "RewardThumbnail"))
continue;
_skins.Add(Utils.GetBitmap(rewardThumbnail));
}
}
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawPreview(c);
DrawDisplayName(c);
DrawFighterInfo(c);
DrawRecommendedPerks(c);
DrawAvailableTaunts(c);
DrawSkins(c);
return new[] { ret };
}
private void GetFighterClassInfo(EFighterClass clas)
{
if (!Utils.TryLoadObject("/Game/Panda_Main/UI/In-Game/Data/UICharacterClassInfo_Datatable.UICharacterClassInfo_Datatable", out UDataTable dataTable))
return;
var row = dataTable.RowMap.ElementAt((int) clas).Value;
if (!row.TryGetValue(out FText displayName, "DisplayName_5_9DB5DDFF490E1F4AD72329866F96B81D") ||
!row.TryGetValue(out FPackageIndex icon, "Icon_8_711534AD4F240D4B001AA6A471EA1895"))
return;
_fighterType.Item1 = Utils.GetBitmap(icon);
_fighterType.Item2.Add(displayName.Text);
}
private new void DrawBackground(SKCanvas c)
{
c.DrawRect(new SKRect(0, 0, Width, Height),
new SKPaint
{
IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = Background[0]
});
if (!string.IsNullOrWhiteSpace(DisplayName))
{
c.DrawText(DisplayName, -50, 125, new()
{
IsAntialias = true, FilterQuality = SKFilterQuality.High,
Typeface = Utils.Typefaces.DisplayName, TextSize = 200,
TextScaleX = .95f, TextSkewX = -0.25f, Color = SKColors.Black.WithAlpha(25)
});
}
c.DrawBitmap(_pattern, new SKRect(0, Height / 2, Width, Height), new SKPaint
{
IsAntialias = true, FilterQuality = SKFilterQuality.High, BlendMode = SKBlendMode.SoftLight
});
var path = new SKPath { FillType = SKPathFillType.EvenOdd };
path.MoveTo(0, Height);
path.LineTo(0, Height - 20);
path.LineTo(Width, Height - 60);
path.LineTo(Width, Height);
path.Close();
c.DrawPath(path, new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Color = SKColor.Parse("#141629")
});
}
private new void DrawPreview(SKCanvas c)
{
var img = (Preview ?? DefaultPreview).ResizeWithRatio(_zoom);
var x_offset = img.Width * _xOffset;
var y_offset = img.Height * -_yOffset;
c.DrawBitmap(img, new SKRect(Width + x_offset - img.Width, y_offset, Width + x_offset, img.Height + y_offset), ImagePaint);
}
private new void DrawDisplayName(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(DisplayName)) return;
c.DrawText(DisplayName.ToUpper(), 50, 100, DisplayNamePaint);
}
private void DrawFighterInfo(SKCanvas c)
{
if (_fighterType.Item1 != null)
c.DrawBitmap(_fighterType.Item1, new SKRect(50, 112.5f, 98, 160.5f), ImagePaint);
c.DrawText(string.Join(" | ", _fighterType.Item2), 98, 145, DescriptionPaint);
}
private void DrawRecommendedPerks(SKCanvas c)
{
const int x = 50;
const int y = 200;
const int size = 64;
ImagePaint.ImageFilter = null;
ImagePaint.BlendMode = SKBlendMode.SoftLight;
c.DrawBitmap(_perk, new SKRect(x, y, x + size / 2, y + size / 2), ImagePaint);
if (_recommendedPerks.Count < 1) return;
ImagePaint.BlendMode = SKBlendMode.SrcOver;
ImagePaint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 2.5f, 2.5f, SKColors.Black);
c.DrawBitmap(_recommendedPerks[1], new SKRect(161, y, 225, y + size), ImagePaint);
c.DrawBitmap(_recommendedPerks[2], new SKRect(193, y + size / 2, 257, y + size * 1.5f), ImagePaint);
c.DrawBitmap(_recommendedPerks[3], new SKRect(161, y + size, 225, y + size * 2), ImagePaint);
ImagePaint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 5, 5, SKColors.Black.WithAlpha(150));
c.DrawBitmap(_recommendedPerks[0], new SKRect(x, y, x + size * 2, y + size * 2), ImagePaint);
}
private void DrawAvailableTaunts(SKCanvas c)
{
var x = 300;
const int y = 232;
const int size = 64;
ImagePaint.ImageFilter = null;
ImagePaint.BlendMode = SKBlendMode.SoftLight;
c.DrawBitmap(_emote, new SKRect(x, y - size / 2, x + size / 2, y), ImagePaint);
if (_availableTaunts.Count < 1) return;
ImagePaint.BlendMode = SKBlendMode.SrcOver;
ImagePaint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 1.5f, 1.5f, SKColors.Black);
foreach (var taunt in _availableTaunts)
{
c.DrawBitmap(taunt, new SKRect(x, y, x + size, y + size), ImagePaint);
x += size;
}
}
private void DrawSkins(SKCanvas c)
{
var x = 50;
const int y = 333;
const int size = 128;
ImagePaint.ImageFilter = null;
ImagePaint.BlendMode = SKBlendMode.SoftLight;
c.DrawBitmap(_skin, new SKRect(x, y, x + size / 4, y + size / 4), ImagePaint);
if (_skins.Count < 1) return;
ImagePaint.BlendMode = SKBlendMode.SrcOver;
ImagePaint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 1.5f, 1.5f, SKColors.Black);
foreach (var skin in _skins)
{
c.DrawBitmap(skin, new SKRect(x, y, x + size, y + size), ImagePaint);
x += size;
}
}
}
public enum EFighterClass : byte
{
Mage = 4,
Tank = 3,
Fighter = 2,
Bruiser = 2,
Assassin = 1,
Support = 0 // Default
}
public enum EFighterType : byte
{
[Description("B980C82D40FF37FD359C74A339CE1B3A")]
Hybrid = 2,
[Description("2C55443D47164019BE73A5ABDC670F36")]
Vertical = 1,
[Description("97A60DD54AA23D4B93D5B891F729BF5C")]
Horizontal = 0 // Default
}

View File

@ -1,344 +0,0 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
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 SkiaSharp;
namespace FModel.Creator.Bases.MV;
public class BasePandaIcon : UCreator
{
private float _y_offset;
private ERewardRarity _rarity;
private string _type;
protected readonly List<(SKBitmap, string)> Pictos;
public BasePandaIcon(UObject uObject, EIconStyle style) : base(uObject, style)
{
Width = 1024;
Margin = 30;
DisplayNamePaint.TextSize = 50;
DisplayNamePaint.TextAlign = SKTextAlign.Left;
DisplayNamePaint.Color = SKColor.Parse("#191C33");
DescriptionPaint.TextSize = 25;
DefaultPreview = Utils.GetBitmap("/Game/Panda_Main/UI/PreMatch/Images/DiamondPortraits/0010_Random.0010_Random");
_y_offset = Height / 2 + DescriptionPaint.TextSize;
Pictos = new List<(SKBitmap, string)>();
}
public override void ParseForInfo()
{
var category = Object.GetOrDefault("Category", EPerkCategory.Offense);
_rarity = Object.GetOrDefault("Rarity", ERewardRarity.None);
_type = Object.ExportType;
var t = _type switch // ERewardType like
{
"StatTrackingBundleData" => EItemType.Badge,
"AnnouncerPackData" => EItemType.Announcer,
"CharacterGiftData" => EItemType.ExperiencePoints,
"ProfileIconData" => EItemType.ProfileIcon,
"RingOutVfxData" => EItemType.Ringout,
"BannerData" => EItemType.Banner,
"EmoteData" => EItemType.Sticker,
"QuestData" => EItemType.Mission,
"TauntData" => EItemType.Emote,
"SkinData" => EItemType.Variant,
"PerkData" when category == EPerkCategory.CharacterSpecific => EItemType.SignaturePerk,
"PerkData" => EItemType.Perk,
_ => EItemType.Unknown
};
if (t == EItemType.SignaturePerk) _rarity = ERewardRarity.Legendary;
Background = GetRarityBackground(_rarity);
if (Object.TryGetValue(out FSoftObjectPath rewardThumbnail, "RewardThumbnail", "DisplayTextureRef", "Texture"))
Preview = Utils.GetBitmap(rewardThumbnail);
else if (Object.TryGetValue(out FPackageIndex icon, "Icon"))
Preview = Utils.GetBitmap(icon);
if (Object.TryGetValue(out FText displayName, "DisplayName", "QuestName"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description", "QuestDescription"))
Description = Utils.RemoveHtmlTags(description.Text);
var unlockLocation = Object.GetOrDefault("UnlockLocation", EUnlockLocation.None);
if (t == EItemType.Unknown && unlockLocation == EUnlockLocation.CharacterMastery) t = EItemType.MasteryLevel;
Pictos.Add((Utils.GetBitmap("/Game/Panda_Main/UI/Assets/Icons/ui_icons_unlocked.ui_icons_unlocked"), Utils.GetLocalizedResource(unlockLocation)));
if (Object.TryGetValue(out string slug, "Slug"))
{
t = _type switch
{
"HydraSyncedDataAsset" when slug == "gold" => EItemType.Gold,
"HydraSyncedDataAsset" when slug == "gleamium" => EItemType.Gleamium,
"HydraSyncedDataAsset" when slug == "match_toasts" => EItemType.Toast,
_ => t
};
Pictos.Add((Utils.GetBitmap("/Game/Panda_Main/UI/Assets/Icons/ui_icons_link.ui_icons_link"), slug));
}
if (Object.TryGetValue(out int xpValue, "XPValue"))
DisplayName += $" (+{xpValue})";
if (Utils.TryLoadObject("/Game/Panda_Main/UI/Prototype/Foundation/Types/DT_EconomyGlossary.DT_EconomyGlossary", out UDataTable dataTable))
{
if (t != EItemType.Unknown &&
dataTable.RowMap.ElementAt((int) t).Value.TryGetValue(out FText name, "Name_14_7F75AD6047CBDEA7B252B1BD76EF84B9"))
_type = name.Text;
}
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawPreview(c);
DrawDisplayName(c);
DrawDescription(c);
DrawPictos(c);
return new[] { ret };
}
private SKColor[] GetRarityBackground(ERewardRarity rarity)
{
return rarity switch // the colors here are the base color and brighter color that the game uses for rarities from the "Rarity to Color" blueprint function
{
ERewardRarity.Common => new[]
{
SKColor.Parse(new FLinearColor(0.068478f, 0.651406f, 0.016807f, 1.000000f).Hex),
SKColor.Parse(new FLinearColor(0.081422f, 1.000000f, 0.000000f, 1.000000f).Hex)
},
ERewardRarity.Rare => new[]
{
SKColor.Parse(new FLinearColor(0.035911f, 0.394246f, 0.900000f, 1.000000f).Hex),
SKColor.Parse(new FLinearColor(0.033333f, 0.434207f, 1.000000f, 1.000000f).Hex)
},
ERewardRarity.Epic => new[]
{
SKColor.Parse(new FLinearColor(0.530391f, 0.060502f, 0.900000f, 1.000000f).Hex),
SKColor.Parse(new FLinearColor(0.579907f, 0.045833f, 1.000000f, 1.000000f).Hex)
},
ERewardRarity.Legendary => new[]
{
SKColor.Parse(new FLinearColor(1.000000f, 0.223228f, 0.002428f, 1.000000f).Hex),
SKColor.Parse(new FLinearColor(1.000000f, 0.479320f, 0.030713f, 1.000000f).Hex)
},
_ => new[]
{
SKColor.Parse(new FLinearColor(0.194618f, 0.651406f, 0.630757f, 1.000000f).Hex),
SKColor.Parse(new FLinearColor(0.273627f, 0.955208f, 0.914839f, 1.000000f).Hex)
}
};
}
private new void DrawBackground(SKCanvas c)
{
c.DrawRect(new SKRect(0, 0, Width, Height),
new SKPaint
{
IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = SKColor.Parse("#F3FCF0")
});
var has_tr = _rarity != ERewardRarity.None;
var tr = Utils.GetLocalizedResource(_rarity);
var tr_paint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
TextAlign = SKTextAlign.Right,
TextSize = 35,
Color = SKColors.White,
Typeface = Utils.Typefaces.DisplayName
};
var path = new SKPath { FillType = SKPathFillType.EvenOdd };
path.MoveTo(0, Height);
path.LineTo(14, Height);
path.LineTo(20, 20);
if (has_tr)
{
const int margin = 15;
var width = tr_paint.MeasureText(tr);
path.LineTo(Width - width - margin * 2, 15);
path.LineTo(Width - width - margin * 2.5f, 60);
path.LineTo(Width, 55);
}
else
{
path.LineTo(Width, 14);
}
path.LineTo(Width, 0);
path.LineTo(0, 0);
path.LineTo(0, Height);
path.Close();
c.DrawPath(path, new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateLinearGradient(
new SKPoint(Width / 2, Height), new SKPoint(Width, Height / 4), Background, SKShaderTileMode.Clamp)
});
if (has_tr)
{
var x = Width - 20f;
foreach (var a in tr.Select(character => character.ToString()).Reverse())
{
c.DrawText(a, x, 40, tr_paint);
x -= tr_paint.MeasureText(a) - 2;
}
}
}
private new void DrawPreview(SKCanvas c)
{
const int size = 384;
var y = Height - size - Margin * 2;
c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, y, size + Margin, y + size), ImagePaint);
}
private new void DrawDisplayName(SKCanvas c)
{
if (string.IsNullOrWhiteSpace(DisplayName))
return;
var x = 450f;
while (DisplayNamePaint.MeasureText(DisplayName) > Width - x / 1.25)
{
DisplayNamePaint.TextSize -= 1;
}
var y = Height / 2 - DisplayNamePaint.TextSize / 4;
foreach (var a in DisplayName.Select(character => character.ToString()))
{
c.DrawText(a, x, y, DisplayNamePaint);
x += DisplayNamePaint.MeasureText(a) - 4;
}
}
private new void DrawDescription(SKCanvas c)
{
const int x = 450;
DescriptionPaint.Color = Background[0];
c.DrawText(_type.ToUpper(), x, 170, DescriptionPaint);
if (string.IsNullOrWhiteSpace(Description)) return;
DescriptionPaint.Color = SKColor.Parse("#191C33");
Utils.DrawMultilineText(c, Description, Width - x, Margin, SKTextAlign.Left,
new SKRect(x, _y_offset, Width - Margin, Height - Margin), DescriptionPaint, out _y_offset);
}
private void DrawPictos(SKCanvas c)
{
if (Pictos.Count < 1) return;
const float x = 450f;
const int size = 24;
var color = SKColor.Parse("#495B6E");
var paint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
TextSize = 27,
Color = color,
Typeface = Utils.Typefaces.Default
};
ImagePaint.ColorFilter = SKColorFilter.CreateBlendMode(color, SKBlendMode.SrcIn);
foreach (var picto in Pictos)
{
c.DrawBitmap(picto.Item1, new SKRect(x, _y_offset + 10, x + size, _y_offset + 10 + size), ImagePaint);
c.DrawText(picto.Item2, x + size + 10, _y_offset + size + 6, paint);
_y_offset += size + 5;
}
}
}
public enum ERewardRarity : byte
{
[Description("0D4B15CE4FB6F2BC5E5F5FAA9E8B376C")]
None = 0, // Default
[Description("0FCDEF47485E2C3D0D477988C481D8E3")]
Common = 1,
[Description("18241CA7441AE16AAFB6EFAB499FF981")]
Rare = 2,
[Description("D999D9CB4754D1078BF9A1B34A231005")]
Epic = 3,
[Description("705AE967407D6EF8870E988A08C6900E")]
Legendary = 4
}
public enum EUnlockLocation : byte
{
[Description("0D4B15CE4FB6F2BC5E5F5FAA9E8B376C")]
None = 0, // Default
[Description("0AFBCE5F41D930D6E9B5138C8EBCFE87")]
Shop = 1,
[Description("062F178B4EE74502C9AD9D878F3D7CEA")]
AccountLevel = 2,
[Description("1AE7A5DF477B2B5F4B3CCC8DCD732884")]
CharacterMastery = 3,
[Description("0B37731C49DC9AE1EAC566950C1A329D")]
Battlepass = 4,
[Description("16F160084187479E5D471786190AE5B7")]
CharacterAffinity = 5,
[Description("E5C1E35C406C585E83B5D18A817FA0B4")]
GuildBoss = 6,
[Description("4A89F5DD432113750EF52D8B58977DCE")]
Tutorial = 7
}
public enum EPerkCategory : byte
{
Offense = 0, // Default
Defense = 1,
Utility = 2,
CharacterSpecific = 3
}
public enum EItemType
{
Unknown = -1,
Announcer,
Badge,
Banner,
BattlePassPoints,
Emote,
ExperiencePoints,
Gleamium,
Gold,
MasteryLevel,
Mission,
Perk,
PlayerLevel,
ProfileIcon,
Rested,
Ringout,
SignaturePerk,
Sticker,
Toast,
Variant
}

View File

@ -1,45 +0,0 @@
using System.Collections.Generic;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.MV;
public class BasePerkGroup : UCreator
{
private readonly List<BasePandaIcon> _perks;
public BasePerkGroup(UObject uObject, EIconStyle style) : base(uObject, style)
{
_perks = new List<BasePandaIcon>();
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out UScriptSet perks, "Perks")) // PORCO DIO WB USE ARRAYS!!!!!!
{
foreach (var perk in perks.Properties)
{
if (perk.GenericValue is not FPackageIndex packageIndex ||
!Utils.TryGetPackageIndexExport(packageIndex, out UObject export))
continue;
var icon = new BasePandaIcon(export, Style);
icon.ParseForInfo();
_perks.Add(icon);
}
}
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap[_perks.Count];
for (var i = 0; i < ret.Length; i++)
{
ret[i] = _perks[i].Draw()[0];
}
return ret;
}
}

View File

@ -1,54 +0,0 @@
using System.ComponentModel;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Extensions;
namespace FModel.Creator.Bases.MV;
public class BaseQuest : BasePandaIcon
{
public BaseQuest(UObject uObject, EIconStyle style) : base(uObject, style)
{
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FStructFallback[] questCompletionRewards, "QuestCompletionRewards") &&
questCompletionRewards.Length > 0 && questCompletionRewards[0] is { } actualReward)
{
var rewardType = actualReward.GetOrDefault("RewardType", EQuestRewardType.Inventory);
var count = actualReward.GetOrDefault("Count", 0);
Pictos.Add((Utils.GetBitmap("/Game/Panda_Main/UI/Assets/Icons/ui_icons_plus.ui_icons_plus"), count.ToString()));
base.ParseForInfo();
if (actualReward.TryGetValue(out FPackageIndex assetReward, "AssetReward") &&
Utils.TryGetPackageIndexExport(assetReward, out UObject export))
{
var item = new BasePandaIcon(export, Style);
item.ParseForInfo();
Preview = item.Preview;
}
else if (rewardType != EQuestRewardType.Inventory)
{
Preview = Utils.GetBitmap(rewardType.GetDescription());
}
}
else
{
base.ParseForInfo();
}
}
}
public enum EQuestRewardType : byte
{
Inventory = 0, // Default
[Description("/Game/Panda_Main/UI/Assets/Icons/UI_CharacterTicket.UI_CharacterTicket")]
AccountXP = 1,
[Description("/Game/Panda_Main/UI/Assets/Icons/UI_BattlepassToken.UI_BattlepassToken")]
BattlepassXP = 2
}

View File

@ -1,47 +1,48 @@
using CUE4Parse.UE4.Assets.Exports;
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
namespace FModel.Creator.Bases.SB
{
public BaseDivision(UObject uObject, EIconStyle style) : base(uObject, style)
public class BaseDivision : UCreator
{
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FPackageIndex icon, "Icon", "IconNoTier"))
public BaseDivision(UObject uObject, EIconStyle style) : base(uObject, style)
{
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"))
public override void ParseForInfo()
{
Background = new[] { SKColor.Parse(lightColor.Hex), SKColor.Parse(cardColor.Hex) };
Border = new[] { SKColor.Parse(mediumColor.Hex), SKColor.Parse(darkColor.Hex) };
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;
}
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);
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
return new[] { ret };
return SKImage.FromBitmap(ret);
}
}
}

View File

@ -1,49 +0,0 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.Core.i18N;
using SkiaSharp;
namespace FModel.Creator.Bases.SB;
public class BaseGameModeInfo : UCreator
{
private SKBitmap _icon;
public BaseGameModeInfo(UObject uObject, EIconStyle style) : base(uObject, style)
{
Width = 738;
Height = 1024;
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text;
if (Object.TryGetValue(out FText description, "Description"))
Description = description.Text;
if (Object.TryGetValue(out UMaterialInstanceConstant portrait, "Portrait"))
Preview = Utils.GetBitmap(portrait);
if (Object.TryGetValue(out UTexture2D icon, "Icon"))
_icon = Utils.GetBitmap(icon).Resize(25);
}
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
DrawIcon(c);
return new[] { ret };
}
private void DrawIcon(SKCanvas c)
{
if (_icon == null) return;
c.DrawBitmap(_icon, new SKPoint(5, 5), ImagePaint);
}
}

View File

@ -3,52 +3,53 @@ using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using SkiaSharp;
namespace FModel.Creator.Bases.SB;
public class BaseLeague : UCreator
namespace FModel.Creator.Bases.SB
{
private int _promotionXp, _xpLostPerMatch;
public BaseLeague(UObject uObject, EIconStyle style) : base(uObject, style)
public class BaseLeague : UCreator
{
_promotionXp = 0;
_xpLostPerMatch = 0;
}
private int _promotionXp, _xpLostPerMatch;
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))
public BaseLeague(UObject uObject, EIconStyle style) : base(uObject, style)
{
var d = new BaseDivision(div, Style);
d.ParseForInfo();
Preview = d.Preview;
Background = d.Background;
Border = d.Border;
_promotionXp = 0;
_xpLostPerMatch = 0;
}
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text;
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out int promotionXp, "PromotionXP"))
_promotionXp = promotionXp;
if (Object.TryGetValue(out int xpLostPerMatch, "XPLostPerMatch"))
_xpLostPerMatch = xpLostPerMatch;
public override SKBitmap[] Draw()
{
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
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;
}
DrawBackground(c);
DrawPreview(c);
DrawTextBackground(c);
DrawDisplayName(c);
if (Object.TryGetValue(out FText displayName, "DisplayName"))
DisplayName = displayName.Text;
}
DrawToBottom(c, SKTextAlign.Left, $"PromotionXP: {_promotionXp}");
DrawToBottom(c, SKTextAlign.Right, $"XPLostPerMatch: {_xpLostPerMatch}");
public override SKImage Draw()
{
using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var c = new SKCanvas(ret);
return new[] { 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);
}
}
}

View File

@ -7,90 +7,91 @@ using CUE4Parse.UE4.Objects.UObject;
using FModel.Creator.Bases.FN;
using SkiaSharp;
namespace FModel.Creator.Bases.SB;
public class BaseSpellIcon : BaseIcon
namespace FModel.Creator.Bases.SB
{
private SKBitmap _seriesBackground2;
private readonly SKPaint _overlayPaint = new()
public class BaseSpellIcon : BaseIcon
{
FilterQuality = SKFilterQuality.High,
IsAntialias = true,
Color = SKColors.Transparent.WithAlpha(75)
};
private SKBitmap _seriesBackground2;
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("GCosmeticCard") ? 1536 : 512;
Height = Object.ExportType.StartsWith("GCosmeticCard") ? 450 : 512;
}
public override void ParseForInfo()
{
if (Object.TryGetValue(out FName rarity, "Rarity"))
GetRarity(rarity);
if (Object.TryGetValue(out FSoftObjectPath preview, "IconTexture", "OfferTexture", "PortraitTexture"))
Preview = Utils.GetBitmap(preview);
else if (Object.TryGetValue(out FPackageIndex icon, "IconTexture", "OfferTexture", "PortraitTexture"))
Preview = Utils.GetBitmap(icon);
if (Object.TryGetValue(out FText displayName, "DisplayName", "Title", "Name"))
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 SKBitmap[] Draw()
{
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 new[] { 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
private readonly SKPaint _overlayPaint = new()
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Shader = SKShader.CreateRadialGradient(
new SKPoint(radi, radi), radi * 2 / 5 * 4,
Background, SKShaderTileMode.Clamp)
});
}
IsAntialias = true,
Color = SKColors.Transparent.WithAlpha(75)
};
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))
public BaseSpellIcon(UObject uObject, EIconStyle style) : base(uObject, style)
{
if (row.TryGetValue(out FLinearColor[] colors, "Colors"))
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
{
Background = new[] { SKColor.Parse(colors[0].Hex), SKColor.Parse(colors[2].Hex) };
Border = new[] { SKColor.Parse(colors[1].Hex), SKColor.Parse(colors[0].Hex) };
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)};
}
}
}
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Windows;
using CUE4Parse.UE4.Assets.Exports;
using FModel.Creator.Bases.FN;
@ -6,223 +6,225 @@ using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator.Bases;
public abstract class UCreator
namespace FModel.Creator.Bases
{
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 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 SKBitmap[] Draw();
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
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:
{
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)
});
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:
{
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)
});
DisplayNamePaint.TextAlign = SKTextAlign.Right;
x = Width - Margin * 2 - shapedText.Points[^1].X;
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 / 2f;
var y = _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE;
switch (Style)
{
case EIconStyle.Flat:
{
DisplayNamePaint.TextAlign = SKTextAlign.Right;
x = Width - Margin * 2;
break;
}
}
#if DEBUG
var halfWidth = shapedText.Width / 2f;
c.DrawLine(x - halfWidth, 0, x - halfWidth, Width, new SKPaint { Color = SKColors.Blue, IsStroke = true });
c.DrawLine(x + halfWidth, 0, x + halfWidth, 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 });
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;
c.DrawShapedText(shaper, DisplayName, x, y, DisplayNamePaint);
}
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)
protected void DrawDescription(SKCanvas c)
{
case SKTextAlign.Left:
_shortDescriptionPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.DisplayName;
var shaper = new CustomSKShaper(_shortDescriptionPaint.Typeface);
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;
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;
}
}
}
}
}

View File

@ -1,256 +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.MV;
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 "FortCreativeWeaponMeleeItemDefinition":
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 "JunoKnowledgeBundle":
case "FortBannerTokenType":
case "FortVariantTokenType":
case "FortDecoItemDefinition":
case "FortStatItemDefinition":
case "FortAmmoItemDefinition":
case "FortEmoteItemDefinition":
case "FortBadgeItemDefinition":
case "SparksMicItemDefinition":
case "FortAwardItemDefinition":
case "FortStackItemDefinition":
case "FortWorldItemDefinition":
case "SparksAuraItemDefinition":
case "SparksDrumItemDefinition":
case "SparksBassItemDefinition":
case "FortGadgetItemDefinition":
case "AthenaCharmItemDefinition":
case "FortPlaysetItemDefinition":
case "FortGiftBoxItemDefinition":
case "FortOutpostItemDefinition":
case "FortVehicleItemDefinition":
case "FortMissionItemDefinition":
case "FortAccountItemDefinition":
case "SparksGuitarItemDefinition":
case "FortCardPackItemDefinition":
case "FortDefenderItemDefinition":
case "FortCurrencyItemDefinition":
case "FortResourceItemDefinition":
case "FortBackpackItemDefinition":
case "FortEventQuestMapDataAsset":
case "FortBuildingItemDefinition":
case "FortWeaponModItemDefinition":
case "FortCodeTokenItemDefinition":
case "FortSchematicItemDefinition":
case "FortAlterableItemDefinition":
case "SparksKeyboardItemDefinition":
case "FortWorldMultiItemDefinition":
case "FortAlterationItemDefinition":
case "FortExpeditionItemDefinition":
case "FortIngredientItemDefinition":
case "FortConsumableItemDefinition":
case "StWFortAccoladeItemDefinition":
case "FortAccountBuffItemDefinition":
case "FortWeaponMeleeItemDefinition":
case "FortPlayerPerksItemDefinition":
case "FortPlaysetPropItemDefinition":
case "FortPrerollDataItemDefinition":
case "JunoRecipeBundleItemDefinition":
case "FortHomebaseNodeItemDefinition":
case "FortNeverPersistItemDefinition":
case "FortPlayerAugmentItemDefinition":
case "FortSmartBuildingItemDefinition":
case "FortGiftBoxUnlockItemDefinition":
case "FortWeaponModItemDefinitionOptic":
case "RadioContentSourceItemDefinition":
case "FortPlaysetGrenadeItemDefinition":
case "JunoWeaponCreatureItemDefinition":
case "FortEventDependentItemDefinition":
case "FortPersonalVehicleItemDefinition":
case "FortGameplayModifierItemDefinition":
case "FortHardcoreModifierItemDefinition":
case "FortWeaponModItemDefinitionMagazine":
case "FortConsumableAccountItemDefinition":
case "FortConversionControlItemDefinition":
case "FortAccountBuffCreditItemDefinition":
case "JunoBuildInstructionsItemDefinition":
case "FortCharacterCosmeticItemDefinition":
case "JunoBuildingSetAccountItemDefinition":
case "FortEventCurrencyItemDefinitionRedir":
case "FortPersistentResourceItemDefinition":
case "FortWeaponMeleeOffhandItemDefinition":
case "FortHomebaseBannerIconItemDefinition":
case "FortVehicleCosmeticsVariantTokenType":
case "JunoBuildingPropAccountItemDefinition":
case "FortCampaignHeroLoadoutItemDefinition":
case "FortConditionalResourceItemDefinition":
case "FortChallengeBundleScheduleDefinition":
case "FortWeaponMeleeDualWieldItemDefinition":
case "FortDailyRewardScheduleTokenDefinition":
case "FortCreativeWeaponRangedItemDefinition":
case "FortVehicleCosmeticsItemDefinition_Body":
case "FortVehicleCosmeticsItemDefinition_Skin":
case "FortVehicleCosmeticsItemDefinition_Wheel":
case "FortCreativeRealEstatePlotItemDefinition":
case "FortDeployableBaseCloudSaveItemDefinition":
case "FortVehicleCosmeticsItemDefinition_Booster":
case "AthenaDanceItemDefinition_AdHocSquadsJoin_C":
case "FortVehicleCosmeticsItemDefinition_DriftSmoke":
case "FortVehicleCosmeticsItemDefinition_EngineAudio":
creator = _style switch
{
EIconStyle.Cataba => new BaseCommunity(_object, _style, "Cataba"),
_ => new BaseIcon(_object, _style)
};
return true;
case "JunoAthenaCharacterItemOverrideDefinition":
case "JunoAthenaDanceItemOverrideDefinition":
creator = new BaseJuno(_object, _style);
return true;
case "FortTandemCharacterData":
creator = new BaseTandem(_object, _style);
return true;
case "FortTrapItemDefinition":
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.Contains("/MI_OfferImages/", StringComparison.OrdinalIgnoreCase) ||
_object.Owner.Name.EndsWith($"/RenderSwitch_Materials/{_object.Name}", StringComparison.OrdinalIgnoreCase) ||
_object.Owner.Name.EndsWith($"/MI_BPTile/{_object.Name}", StringComparison.OrdinalIgnoreCase)):
creator = new BaseMaterialInstance(_object, _style);
return true;
case "AthenaItemShopOfferDisplayData":
creator = new BaseOfferDisplayData(_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 "FortQuestItemDefinition_Athena":
case "FortQuestItemDefinition_Campaign":
case "AthenaDailyQuestDefinition":
case "FortUrgentQuestItemDefinition":
creator = new Bases.FN.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 "FortCreativeOption":
case "PlaylistUserOptionEnum":
case "PlaylistUserOptionBool":
case "PlaylistUserOptionString":
case "PlaylistUserOptionIntEnum":
case "PlaylistUserOptionIntRange":
case "PlaylistUserOptionColorEnum":
case "PlaylistUserOptionFloatEnum":
case "PlaylistUserOptionFloatRange":
case "PlaylistUserTintedIconIntEnum":
case "PlaylistUserOptionPrimaryAsset":
case "PlaylistUserOptionCollisionProfileEnum":
creator = new BaseUserControl(_object, _style);
return true;
// PandaGame
case "CharacterData":
creator = new BaseFighter(_object, _style);
return true;
case "PerkGroup":
creator = new BasePerkGroup(_object, _style);
return true;
case "StatTrackingBundleData":
case "HydraSyncedDataAsset":
case "AnnouncerPackData":
case "CharacterGiftData":
case "ProfileIconData":
case "RingOutVfxData":
case "BannerData":
case "EmoteData":
case "TauntData":
case "SkinData":
case "PerkData":
creator = new BasePandaIcon(_object, _style);
return true;
case "QuestData":
creator = new Bases.MV.BaseQuest(_object, _style);
return true;
default:
creator = null;
return false;
}
}
public override string ToString() => $"{_object.ExportType} | {_style}";
public void Dispose()
{
_object = null;
}
}
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;
}
}
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Windows;
using CUE4Parse.UE4.Versions;
@ -6,194 +6,219 @@ using FModel.Settings;
using FModel.ViewModels;
using SkiaSharp;
namespace FModel.Creator;
public class Typefaces
namespace FModel.Creator
{
private readonly Uri _BURBANK_BIG_CONDENSED_BOLD = new("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 _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"; // japanese fortnite
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";
// PandaGame
private const string _PANDAGAME_BASE_PATH = "/Game/Panda_Main/UI/Fonts/";
private const string _NORMS_STD_CONDENSED_EXTRABOLD_ITALIC = "Norms/TT_Norms_Std_Condensed_ExtraBold_Italic";
private const string _NORMS_PRO_EXTRABOLD_ITALIC = "Norms/TT_Norms_Pro_ExtraBold_Italic";
private const string _NORMS_STD_CONDENSED_MEDIUM = "Norms/TT_Norms_Std_Condensed_Medium";
private const string _XIANGHEHEI_SC_PRO_BLACK = "XiangHeHei_SC/MXiangHeHeiSCPro-Black";
private const string _XIANGHEHEI_SC_PRO_HEAVY = "XiangHeHei_SC/MXiangHeHeiSCPro-Heavy";
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 readonly SKTypeface TandemDisplayName;
public readonly SKTypeface TandemGenDescription;
public readonly SKTypeface TandemAddDescription;
public Typefaces(CUE4ParseViewModel viewModel)
public class Typefaces
{
_viewModel = viewModel;
var language = UserSettings.Default.AssetLanguage;
private readonly Uri _BURBANK_BIG_CONDENSED_BOLD = new Uri("pack://application:,,,/Resources/BurbankBigCondensed-Bold.ttf");
private const string _EXT = ".ufont";
Default = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream);
// 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";
switch (viewModel.Provider.InternalGameName.ToUpperInvariant())
// 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)
{
case "FORTNITEGAME":
byte[] data;
_viewModel = viewModel;
var language = UserSettings.Default.AssetLanguage;
Default = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream);
switch (viewModel.Game)
{
DisplayName = OnTheFly(_FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => string.Empty
} + _EXT);
Description = OnTheFly(_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
} + _EXT);
Bottom = OnTheFly(_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
} + _EXT, true);
BundleNumber = OnTheFly(_FORTNITE_BASE_PATH + _BURBANK_BIG_CONDENSED_BLACK + _EXT);
Bundle = OnTheFly(_FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => string.Empty
} + _EXT, true) ?? BundleNumber;
TandemDisplayName = OnTheFly(_FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK,
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => _BURBANK_BIG_REGULAR_BLACK
} + _EXT);
TandemGenDescription = OnTheFly(_FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => _BURBANK_SMALL_BLACK
} + _EXT);
TandemAddDescription = OnTheFly(_FORTNITE_BASE_PATH +
language switch
{
ELanguage.Korean => _ASIA_ERINM,
ELanguage.Japanese => _NOTO_SANS_JP_BOLD,
ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK,
ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK,
ELanguage.Chinese => _NOTO_SANS_SC_BLACK,
_ => _BURBANK_SMALL_BOLD
} + _EXT);
break;
}
case "MULTIVERSUS":
{
DisplayName = OnTheFly(_PANDAGAME_BASE_PATH + language switch
case FGame.FortniteGame:
{
ELanguage.Chinese => _XIANGHEHEI_SC_PRO_HEAVY,
_ => _NORMS_PRO_EXTRABOLD_ITALIC
} + _EXT);
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;
Description = OnTheFly(_PANDAGAME_BASE_PATH + language switch
{
ELanguage.Chinese => _XIANGHEHEI_SC_PRO_BLACK,
_ => _NORMS_STD_CONDENSED_MEDIUM
} + _EXT);
TandemDisplayName = OnTheFly(_PANDAGAME_BASE_PATH + language switch
{
ELanguage.Chinese => _XIANGHEHEI_SC_PRO_BLACK,
_ => _NORMS_STD_CONDENSED_EXTRABOLD_ITALIC
} + _EXT);
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;
TandemGenDescription = OnTheFly(_PANDAGAME_BASE_PATH + language switch
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:
{
ELanguage.Chinese => _XIANGHEHEI_SC_PRO_HEAVY,
_ => _NORMS_STD_CONDENSED_MEDIUM
} + _EXT);
break;
}
default:
{
DisplayName = Default;
Description = Default;
break;
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, bool fallback = false)
{
if (!_viewModel.Provider.TrySaveAsset(path, out var data)) return fallback ? null : Default;
var m = new MemoryStream(data) { Position = 0 };
return SKTypeface.FromStream(m);
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);
}
}
}
}

View File

@ -1,420 +1,309 @@
using System;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
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.Objects.UObject;
using CUE4Parse.UE4.Versions;
using CUE4Parse_Conversion.Textures;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse.Utils;
using CUE4Parse_Conversion.Textures;
using FModel.Framework;
using FModel.Extensions;
using FModel.Services;
using FModel.Settings;
using FModel.ViewModels;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace FModel.Creator;
public static class Utils
namespace FModel.Creator
{
private static ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private static readonly Regex _htmlRegex = new("<.*?>");
public static Typefaces Typefaces;
public static string RemoveHtmlTags(string s)
public static class Utils
{
var match = _htmlRegex.Match(s);
while (match.Success)
private static ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private static readonly Regex _htmlRegex = new("<.*?>");
public static Typefaces Typefaces;
public static string RemoveHtmlTags(string s)
{
s = s.Replace(match.Value, string.Empty);
match = match.NextMatch();
var match = _htmlRegex.Match(s);
while (match.Success)
{
s = s.Replace(match.Value, string.Empty);
match = match.NextMatch();
}
return s;
}
return s;
}
public static bool TryGetDisplayAsset(UObject uObject, out SKBitmap preview)
{
if (uObject.TryGetValue(out FSoftObjectPath sidePanelIcon, "SidePanelIcon"))
public static bool TryGetDisplayAsset(UObject uObject, out SKBitmap preview)
{
preview = GetBitmap(sidePanelIcon);
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;
}
var path = $"/Game/Catalog/MI_OfferImages/MI_{uObject.Name.Replace("Athena_Commando_", string.Empty)}";
if (!TryLoadObject(path, out UMaterialInstanceConstant material)) // non-obfuscated item definition
public static SKBitmap GetDisplayAsset(FSoftObjectPath path)
{
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
}
if (!TryLoadObject(path.AssetPathName.Text, out UObject obj)) return null;
preview = GetBitmap(material);
return preview != null;
}
public static SKBitmap GetBitmap(FPackageIndex packageIndex)
{
while (true)
{
if (!TryGetPackageIndexExport(packageIndex, out UObject export)) return null;
switch (export)
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/"))
{
case UTexture2D texture:
return GetBitmap(texture);
case UMaterialInstanceConstant material:
return GetBitmap(material);
default:
return GetBitmap(resource);
}
return null;
}
public static SKBitmap GetBitmap(FPackageIndex packageIndex)
{
while (true)
{
if (!TryGetPackageIndexExport(packageIndex, out UExport export)) return null;
switch (export)
{
if (export.TryGetValue(out FInstancedStruct[] dataList, "DataList")) return GetBitmap(dataList);
if (export.TryGetValue(out FSoftObjectPath previewImage, "LargePreviewImage", "SmallPreviewImage")) return GetBitmap(previewImage);
if (export.TryGetValue(out string largePreview, "LargePreviewImage")) return GetBitmap(largePreview);
if (export.TryGetValue(out FPackageIndex smallPreview, "SmallPreviewImage"))
case UTexture2D texture:
return GetBitmap(texture);
case UMaterialInstanceConstant material:
return GetBitmap(material);
case UObject uObject:
{
packageIndex = smallPreview;
continue;
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;
}
return null;
default:
return null;
}
}
}
}
public static SKBitmap GetBitmap(FInstancedStruct[] structs)
{
if (structs.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FSoftObjectPath p, "LargeIcon") == true && !p.AssetPathName.IsNone) is { NonConstStruct: not null } isl)
public static SKBitmap GetBitmap(UMaterialInstanceConstant material)
{
return GetBitmap(isl.NonConstStruct.Get<FSoftObjectPath>("LargeIcon"));
}
if (structs.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FSoftObjectPath p, "Icon") == true && !p.AssetPathName.IsNone) is { NonConstStruct: not null } isi)
{
return GetBitmap(isi.NonConstStruct.Get<FSoftObjectPath>("Icon"));
}
return null;
}
public static SKBitmap GetBitmap(UMaterialInstanceConstant material)
{
if (material == null) return null;
foreach (var textureParameter in material.TextureParameterValues)
{
if (!textureParameter.ParameterValue.TryLoad<UTexture2D>(out var texture)) continue;
switch (textureParameter.ParameterInfo.Name.Text)
if (material == null) return null;
foreach (var textureParameter in material.TextureParameterValues)
{
case "MainTex":
case "Texture":
case "TextureA":
case "TextureB":
case "OfferImage":
case "KeyArtTexture":
case "NPC-Portrait":
if (!(textureParameter.ParameterValue is UTexture2D texture)) continue;
switch (textureParameter.ParameterInfo.Name.Text)
{
return GetBitmap(texture);
case "TextureA":
case "TextureB":
case "OfferImage":
{
return GetBitmap(texture);
}
}
}
return null;
}
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);
return null;
}
public static SKBitmap Resize(this SKBitmap me, int size) => me.Resize(size, size);
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.Decode(UserSettings.Default.CurrentDir.TexturePlatform);
public static SKBitmap GetBitmap(byte[] data) => SKBitmap.Decode(data);
public static SKBitmap ResizeWithRatio(this SKBitmap me, double width, double height)
{
var ratioX = width / me.Width;
var ratioY = height / me.Height;
return ResizeWithRatio(me, ratioX < ratioY ? ratioX : ratioY);
}
public static SKBitmap ResizeWithRatio(this SKBitmap me, double ratio)
{
return me.Resize(Convert.ToInt32(me.Width * ratio), Convert.ToInt32(me.Height * ratio));
}
public static SKBitmap Resize(this SKBitmap me, int size) => me.Resize(size, size);
public static SKBitmap Resize(this SKBitmap me, int width, int 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 bool TryGetPackageIndexExport<T>(FPackageIndex packageIndex, out T export) where T : UObject
{
return packageIndex.TryLoad(out export);
}
// fullpath must be either without any extension or with the export objectname
public static bool TryLoadObject<T>(string fullPath, out T export) where T : UObject
{
return _applicationView.CUE4Parse.Provider.TryLoadObject(fullPath, out export);
}
public static IEnumerable<UObject> LoadExports(string packagePath)
{
return _applicationView.CUE4Parse.Provider.LoadAllObjects(packagePath);
}
public static float GetMaxFontSize(double sectorSize, SKTypeface typeface, string text, float degreeOfCertainty = 1f, float maxFont = 100f)
{
var max = maxFont;
var min = 0f;
var last = -1f;
float value;
while (true)
public static SKBitmap Resize(this SKBitmap me, int width, int height)
{
value = min + ((max - min) / 2);
using (SKFont ft = new SKFont(typeface, value))
using (SKPaint paint = new SKPaint(ft))
{
if (paint.MeasureText(text) > sectorSize)
{
last = value;
max = value;
}
else
{
min = value;
if (Math.Abs(last - value) <= degreeOfCertainty)
return last;
last = value;
}
}
var bmp = new SKBitmap(new SKImageInfo(width, height), SKBitmapAllocFlags.ZeroPixels);
using var pixmap = bmp.PeekPixels();
me.ScalePixels(pixmap, SKFilterQuality.Medium);
return bmp;
}
}
public static string GetLocalizedResource(string @namespace, string key, string defaultValue)
{
return _applicationView.CUE4Parse.Provider.GetLocalizedString(@namespace, key, defaultValue);
}
public static string GetLocalizedResource<T>(T @enum) where T : Enum
{
var resource = _applicationView.CUE4Parse.Provider.GetLocalizedString("", @enum.GetDescription(), @enum.ToString());
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(resource.ToLower());
}
public static string GetFullPath(string partialPath)
{
var regex = new Regex(partialPath, RegexOptions.IgnoreCase | RegexOptions.Compiled);
foreach (var path in _applicationView.CUE4Parse.Provider.Files.Keys)
public static bool TryGetPackageIndexExport<T>(FPackageIndex packageIndex, out T export) where T : UExport
{
if (regex.IsMatch(path))
if (packageIndex.ResolvedObject == null)
{
return path;
export = default;
return false;
}
var outerChain = new List<string>();
var current = packageIndex.ResolvedObject.Outer;
while (current != null)
{
outerChain.Add(current.Name.Text);
current = current.Outer;
}
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;
}
return string.Empty;
}
// 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 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);
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 });
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;
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.Width / 2,
SKTextAlign.Right => size - margin - shapedText.Width,
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)
{
var fontSize = GetMaxFontSize(area.Width, paint.Typeface, line);
if (paint.TextSize > fontSize) // if the text is not fitting in the line decrease the font size (CKJ languages)
{
paint.TextSize = fontSize;
lineHeight = paint.TextSize * 1.2f;
}
if (line == null) continue;
var lineText = line.Trim();
var shaper = new CustomSKShaper(paint.Typeface);
var shapedText = shaper.Shape(lineText, paint);
var x = side switch
for (var i = 0; i < maxCount; i++)
{
SKTextAlign.Center => area.MidX - shapedText.Width / 2,
SKTextAlign.Right => size - margin - shapedText.Width,
SKTextAlign.Left => area.Left,
_ => throw new NotImplementedException()
};
var line = lines[i];
if (line == null) continue;
c.DrawShapedText(shaper, lineText, x, yPos, paint);
yPos += lineHeight;
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 });
c.DrawRect(new SKRect(area.Left, area.Top - paint.TextSize, area.Right, yPos), new SKPaint {Color = SKColors.Red, IsStroke = true});
#endif
}
#region Chinese, Korean and Japanese text split
// https://github.com/YoungjaeKim/mikan.sharp/blob/master/MikanSharp/Mikan/Mikan.cs
static string joshi = @"(でなければ|について|かしら|くらい|けれど|なのか|ばかり|ながら|ことよ|こそ|こと|さえ|しか|した|たり|だけ|だに|だの|つつ|ても|てよ|でも|とも|から|など|なり|ので|のに|ほど|まで|もの|やら|より|って|で|と|な|に|ね|の|も|は|ば|へ|や|わ|を|か|が|さ|し|ぞ|て)";
static string keywords = @"(\&nbsp;|[a-zA-Z0-9]+\.[a-z]{2,}|[一-龠々〆ヵヶゝ]+|[ぁ-んゝ]+|[ァ-ヴー]+|[a-zA-Z0-9]+|[---]+)";
static string periods = @"([\.\,。、!\!\?]+)$";
static string bracketsBegin = @"([〈《「『「((\[【〔〚〖〘❮❬❪❨(<{❲❰{❴])";
static string bracketsEnd = @"([〉》」』」)\]】〕〗〙〛}>\)❩❫❭❯❱❳❵}])";
public static string[] SplitCKJText(string str)
{
var line1 = Regex.Split(str, keywords).ToList();
var line2 = line1.SelectMany((o, _) => Regex.Split(o, joshi)).ToList();
var line3 = line2.SelectMany((o, _) => Regex.Split(o, bracketsBegin)).ToList();
var line4 = line3.SelectMany((o, _) => Regex.Split(o, bracketsEnd)).ToList();
var words = line4.Where(o => !string.IsNullOrEmpty(o)).ToList();
var prevType = string.Empty;
var prevWord = string.Empty;
List<string> result = new List<string>();
words.ForEach(word =>
{
var token = Regex.IsMatch(word, periods) || Regex.IsMatch(word, joshi);
if (Regex.IsMatch(word, bracketsBegin))
{
prevType = "braketBegin";
prevWord = word;
return;
}
if (Regex.IsMatch(word, bracketsEnd))
{
result[result.Count - 1] += word;
prevType = "braketEnd";
prevWord = word;
return;
}
if (prevType == "braketBegin")
{
word = prevWord + word;
prevWord = string.Empty;
prevType = string.Empty;
}
// すでに文字が入っている上で助詞が続く場合は結合する
if (result.Count > 0 && token && prevType == string.Empty)
{
result[result.Count - 1] += word;
prevType = "keyword";
prevWord = word;
return;
}
// 単語のあとの文字がひらがななら結合する
if (result.Count > 1 && token || (prevType == "keyword" && Regex.IsMatch(word, @"[ぁ-んゝ]+")))
{
result[result.Count - 1] += word;
prevType = string.Empty;
prevWord = word;
return;
}
result.Add(word);
prevType = "keyword";
prevWord = word;
});
return result.ToArray();
}
#endregion
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 isCJK = false;
var words = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (words.Length <= 1 && UserSettings.Default.AssetLanguage is ELanguage.Japanese or ELanguage.Korean or ELanguage.Chinese or ELanguage.TraditionalChinese)
{
words = SplitCKJText(line);
isCJK = true;
}
var lineResult = new StringBuilder();
foreach (var word in words)
{
var wordWidth = paint.MeasureText(word);
var wordWithSpaceWidth = wordWidth + spaceWidth;
var wordWithSpace = isCJK ? word : 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;
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;
}
}
}

View File

@ -1,116 +1,139 @@
using System;
using System.ComponentModel;
using System.ComponentModel;
namespace FModel;
public enum EBuildKind
namespace FModel
{
Debug,
Release,
Unknown
}
public enum EBuildKind
{
Debug,
Release,
Unknown
}
public enum EErrorKind
{
Ignore,
Restart,
ResetSettings
}
public enum EErrorKind
{
Close,
Ignore,
Restart
}
public enum SettingsOut
{
ReloadLocres,
ReloadMappings
}
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 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 EAesReload
{
[Description("Always")]
Always,
[Description("Never")]
Never,
[Description("Once Per Day")]
OncePerDay
}
public enum EDiscordRpc
{
[Description("Always")]
Always,
[Description("Never")]
Never
}
public enum EDiscordRpc
{
[Description("Always")]
Always,
[Description("Never")]
Never
}
public enum ELoadingMode
{
[Description("Multiple")]
Multiple,
[Description("All")]
All,
[Description("All (New)")]
AllButNew,
[Description("All (Modified)")]
AllButModified
}
public enum EEnabledDisabled
{
[Description("Disabled")]
Disabled,
[Description("Enabled")]
Enabled
}
// public enum EUpdateMode
// {
// [Description("Stable")]
// Stable,
// [Description("Beta")]
// Beta,
// [Description("QA Testing")]
// Qa
// }
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 ECompressedAudio
{
[Description("Play the decompressed data")]
PlayDecompressed,
[Description("Play the compressed data (might not always be a valid audio data)")]
PlayCompressed
}
public enum ELoadingMode
{
[Description("Single")]
Single,
[Description("Multiple")]
Multiple,
[Description("All")]
All,
[Description("All (New)")]
AllButNew,
[Description("All (Modified)")]
AllButModified
}
public enum EIconStyle
{
[Description("Default")]
Default,
[Description("No Background")]
NoBackground,
[Description("No Text")]
NoText,
[Description("Flat")]
Flat,
[Description("Cataba")]
Cataba,
// [Description("Community")]
// CommunityMade
}
public enum EUpdateMode
{
[Description("Stable")]
Stable,
[Description("Beta")]
Beta
}
public enum EEndpointType
{
Aes,
Mapping
}
public enum ECompressedAudio
{
[Description("Play the decompressed data")]
PlayDecompressed,
[Description("Play the compressed data (might not always be a valid audio data)")]
PlayCompressed
}
[Flags]
public enum EBulkType
{
None = 0,
Auto = 1 << 0,
Properties = 1 << 1,
Textures = 1 << 2,
Meshes = 1 << 3,
Skeletons = 1 << 4,
Animations = 1 << 5
}
public enum EIconStyle
{
[Description("Default")]
Default,
[Description("No Background")]
NoBackground,
[Description("No Text")]
NoText,
[Description("Flat")]
Flat,
[Description("Cataba")]
Cataba,
// [Description("Community")]
// CommunityMade
}
}

View File

@ -4,51 +4,44 @@ using System.Xml;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;
namespace FModel.Extensions;
public static class AvalonExtensions
namespace FModel.Extensions
{
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");
private static readonly IHighlightingDefinition _changelogHighlighter = LoadHighlighter("Changelog.xshd");
private static readonly IHighlightingDefinition _verseHighlighter = LoadHighlighter("Verse.xshd");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static IHighlightingDefinition LoadHighlighter(string resourceName)
public static class AvalonExtensions
{
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);
}
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)]
public static IHighlightingDefinition HighlighterSelector(string ext)
{
switch (ext)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static IHighlightingDefinition LoadHighlighter(string resourceName)
{
case "ini":
case "csv":
return _iniHighlighter;
case "xml":
case "tps":
return _xmlHighlighter;
case "h":
case "cpp":
return _cppHighlighter;
case "changelog":
return _changelogHighlighter;
case "verse":
return _verseHighlighter;
case "bat":
case "txt":
case "pem":
case "po":
return null;
default:
return _jsonHighlighter;
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;
}
}
}
}
}

View File

@ -1,198 +0,0 @@
using SkiaSharp;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows;
namespace FModel.Extensions;
public static class ClipboardExtensions
{
public static void SetImage(byte[] pngBytes, string fileName = null)
{
Clipboard.Clear();
var data = new DataObject();
using var pngMs = new MemoryStream(pngBytes);
using var image = Image.FromStream(pngMs);
// As standard bitmap, without transparency support
data.SetData(DataFormats.Bitmap, image, true);
// As PNG. Gimp will prefer this over the other two
data.SetData("PNG", pngMs, false);
// As DIB. This is (wrongly) accepted as ARGB by many applications
using var dibMemStream = new MemoryStream(ConvertToDib(image));
data.SetData(DataFormats.Dib, dibMemStream, false);
// Optional fileName
if (!string.IsNullOrEmpty(fileName))
{
var htmlFragment = GenerateHTMLFragment($"<img src=\"{fileName}\"/>");
data.SetData(DataFormats.Html, htmlFragment);
}
// The 'copy=true' argument means the MemoryStreams can be safely disposed after the operation
Clipboard.SetDataObject(data, true);
}
public static byte[] ConvertToDib(Image image)
{
byte[] bm32bData;
var width = image.Width;
var height = image.Height;
// Ensure image is 32bppARGB by painting it on a new 32bppARGB image.
using (var bm32b = new Bitmap(width, height, PixelFormat.Format32bppPArgb))
{
using (var gr = Graphics.FromImage(bm32b))
{
gr.DrawImage(image, new Rectangle(0, 0, width, height));
}
// Bitmap format has its lines reversed.
bm32b.RotateFlip(RotateFlipType.Rotate180FlipX);
bm32bData = GetRawBytes(bm32b);
}
// BITMAPINFOHEADER struct for DIB.
const int hdrSize = 0x28;
var fullImage = new byte[hdrSize + 12 + bm32bData.Length];
//Int32 biSize;
WriteIntToByteArray(fullImage, 0x00, 4, true, hdrSize);
//Int32 biWidth;
WriteIntToByteArray(fullImage, 0x04, 4, true, (uint) width);
//Int32 biHeight;
WriteIntToByteArray(fullImage, 0x08, 4, true, (uint) height);
//Int16 biPlanes;
WriteIntToByteArray(fullImage, 0x0C, 2, true, 1);
//Int16 biBitCount;
WriteIntToByteArray(fullImage, 0x0E, 2, true, 32);
//BITMAPCOMPRESSION biCompression = BITMAPCOMPRESSION.BITFIELDS;
WriteIntToByteArray(fullImage, 0x10, 4, true, 3);
//Int32 biSizeImage;
WriteIntToByteArray(fullImage, 0x14, 4, true, (uint) bm32bData.Length);
// These are all 0. Since .net clears new arrays, don't bother writing them.
//Int32 biXPelsPerMeter = 0;
//Int32 biYPelsPerMeter = 0;
//Int32 biClrUsed = 0;
//Int32 biClrImportant = 0;
// The aforementioned "BITFIELDS": colour masks applied to the Int32 pixel value to get the R, G and B values.
WriteIntToByteArray(fullImage, hdrSize + 0, 4, true, 0x00FF0000);
WriteIntToByteArray(fullImage, hdrSize + 4, 4, true, 0x0000FF00);
WriteIntToByteArray(fullImage, hdrSize + 8, 4, true, 0x000000FF);
Unsafe.CopyBlockUnaligned(ref fullImage[hdrSize + 12], ref bm32bData[0], (uint) bm32bData.Length);
return fullImage;
}
private static byte[] ConvertToDib(byte[] pngBytes = null)
{
byte[] bm32bData;
int width, height;
using (var skBmp = SKBitmap.Decode(pngBytes))
{
width = skBmp.Width;
height = skBmp.Height;
using var rotated = new SKBitmap(new SKImageInfo(width, height, skBmp.ColorType));
using var canvas = new SKCanvas(rotated);
canvas.Scale(1, -1, 0, height / 2.0f);
canvas.DrawBitmap(skBmp, SKPoint.Empty);
canvas.Flush();
bm32bData = rotated.Bytes;
}
// BITMAPINFOHEADER struct for DIB.
const int hdrSize = 0x28;
var fullImage = new byte[hdrSize + 12 + bm32bData.Length];
//Int32 biSize;
WriteIntToByteArray(fullImage, 0x00, 4, true, hdrSize);
//Int32 biWidth;
WriteIntToByteArray(fullImage, 0x04, 4, true, (uint) width);
//Int32 biHeight;
WriteIntToByteArray(fullImage, 0x08, 4, true, (uint) height);
//Int16 biPlanes;
WriteIntToByteArray(fullImage, 0x0C, 2, true, 1);
//Int16 biBitCount;
WriteIntToByteArray(fullImage, 0x0E, 2, true, 32);
//BITMAPCOMPRESSION biCompression = BITMAPCOMPRESSION.BITFIELDS;
WriteIntToByteArray(fullImage, 0x10, 4, true, 3);
//Int32 biSizeImage;
WriteIntToByteArray(fullImage, 0x14, 4, true, (uint) bm32bData.Length);
// These are all 0. Since .net clears new arrays, don't bother writing them.
//Int32 biXPelsPerMeter = 0;
//Int32 biYPelsPerMeter = 0;
//Int32 biClrUsed = 0;
//Int32 biClrImportant = 0;
// The aforementioned "BITFIELDS": colour masks applied to the Int32 pixel value to get the R, G and B values.
WriteIntToByteArray(fullImage, hdrSize + 0, 4, true, 0x00FF0000);
WriteIntToByteArray(fullImage, hdrSize + 4, 4, true, 0x0000FF00);
WriteIntToByteArray(fullImage, hdrSize + 8, 4, true, 0x000000FF);
Unsafe.CopyBlockUnaligned(ref fullImage[hdrSize + 12], ref bm32bData[0], (uint) bm32bData.Length);
return fullImage;
}
public static unsafe byte[] GetRawBytes(Bitmap bmp)
{
var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
var bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, bmp.PixelFormat);
var bytes = (uint) (Math.Abs(bmpData.Stride) * bmp.Height);
var buffer = new byte[bytes];
fixed (byte* pBuffer = buffer)
{
Unsafe.CopyBlockUnaligned(pBuffer, bmpData.Scan0.ToPointer(), bytes);
}
bmp.UnlockBits(bmpData);
return buffer;
}
private static void WriteIntToByteArray(byte[] data, int startIndex, int bytes, bool littleEndian, uint value)
{
var lastByte = bytes - 1;
if (data.Length < startIndex + bytes)
{
throw new ArgumentOutOfRangeException(nameof(startIndex), "Data array is too small to write a " + bytes + "-byte value at offset " + startIndex + ".");
}
for (var index = 0; index < bytes; index++)
{
var offs = startIndex + (littleEndian ? index : lastByte - index);
data[offs] = (byte) (value >> 8 * index & 0xFF);
}
}
private static string GenerateHTMLFragment(string html)
{
var sb = new StringBuilder();
const string header = "Version:0.9\r\nStartHTML:<<<<<<<<<1\r\nEndHTML:<<<<<<<<<2\r\nStartFragment:<<<<<<<<<3\r\nEndFragment:<<<<<<<<<4\r\n";
const string startHTML = "<html>\r\n<body>\r\n";
const string startFragment = "<!--StartFragment-->";
const string endFragment = "<!--EndFragment-->";
const string endHTML = "\r\n</body>\r\n</html>";
sb.Append(header);
var startHTMLLength = header.Length;
var startFragmentLength = startHTMLLength + startHTML.Length + startFragment.Length;
var endFragmentLength = startFragmentLength + Encoding.UTF8.GetByteCount(html);
var endHTMLLength = endFragmentLength + endFragment.Length + endHTML.Length;
sb.Replace("<<<<<<<<<1", startHTMLLength.ToString("D10"));
sb.Replace("<<<<<<<<<2", endHTMLLength.ToString("D10"));
sb.Replace("<<<<<<<<<3", startFragmentLength.ToString("D10"));
sb.Replace("<<<<<<<<<4", endFragmentLength.ToString("D10"));
sb.Append(startHTML);
sb.Append(startFragment);
sb.Append(html);
sb.Append(endFragment);
sb.Append(endHTML);
return sb.ToString();
}
}

View File

@ -2,35 +2,36 @@
using System.Linq;
using System.Runtime.CompilerServices;
namespace FModel.Extensions;
public static class CollectionExtensions
namespace FModel.Extensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Next<T>(this IList<T> collection, T value)
public static class CollectionExtensions
{
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, 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 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, 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];
[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];
}
}
}

View File

@ -1,59 +1,84 @@
using FModel.Properties;
using System;
using System.ComponentModel;
using System.Resources;
using System.Runtime.CompilerServices;
namespace FModel.Extensions;
public static class EnumExtensions
namespace FModel.Extensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetDescription(this Enum value)
public static class EnumExtensions
{
var fi = value.GetType().GetField(value.ToString());
if (fi == null) return $"{value} ({value:D})";
[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})";
}
var attributes = (DescriptionAttribute[]) fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Length > 0) return attributes[0].Description;
[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);
var suffix = $"{value:D}";
var current = Convert.ToInt32(suffix);
var target = current & ~0xF;
if (current != target)
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());
var index = Array.IndexOf(values, value);
suffix = values.GetValue(index - (current - target))?.ToString();
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);
}
return $"{value} ({suffix})";
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T ToEnum<T>(this string value, T defaultValue) where T : struct => !Enum.TryParse(value, true, out T ret) ? defaultValue : 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);
}
}
}

View File

@ -2,29 +2,30 @@
using System.IO;
using System.Runtime.CompilerServices;
namespace FModel.Extensions;
public enum Endianness
namespace FModel.Extensions
{
LittleEndian,
BigEndian
}
public static class StreamExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ReadUInt32(this Stream s, Endianness endian = Endianness.LittleEndian)
public enum Endianness
{
var b1 = s.ReadByte();
var b2 = s.ReadByte();
var b3 = s.ReadByte();
var b4 = s.ReadByte();
LittleEndian,
BigEndian,
}
return endian switch
public static class StreamExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ReadUInt32(this Stream s, Endianness endian = Endianness.LittleEndian)
{
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")
};
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")
};
}
}
}

View File

@ -1,185 +1,134 @@
using System;
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using ICSharpCode.AvalonEdit.Document;
namespace FModel.Extensions;
public static class StringExtensions
namespace FModel.Extensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetReadableSize(double size)
public static class StringExtensions
{
if (size == 0) return "0 B";
string[] sizes = { "B", "KB", "MB", "GB", "TB" };
var order = 0;
while (size >= 1024 && order < sizes.Length - 1)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetReadableSize(double size)
{
order++;
size /= 1024;
}
if (size == 0) return "0 B";
return $"{size:# ###.##} {sizes[order]}".TrimStart();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringBefore(this string s, char delimiter)
{
var index = s.IndexOf(delimiter);
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[..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[..index];
}
[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[..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)
{
var index = s.LastIndexOf(delimiter, comparisonType);
return index == -1 ? s : s.Substring(index + delimiter.Length, s.Length - index - delimiter.Length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetNameLineNumber(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)]
public static string GetParentExportType(this TextDocument doc, int startOffset)
{
var line = doc.GetLineByOffset(startOffset);
var lineNumber = line.LineNumber - 1;
while (doc.GetText(line.Offset, line.Length) is { } content)
{
if (content.StartsWith(" \"Type\": \"", StringComparison.OrdinalIgnoreCase))
return content.Split("\"")[3];
lineNumber--;
if (lineNumber < 1) break;
line = doc.GetLineByNumber(lineNumber);
}
return string.Empty;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetKismetLineNumber(this string s, string input)
{
var match = Regex.Match(input, @"^(.+)\[(\d+)\]$");
var name = match.Groups[1].Value;
int index = int.Parse(match.Groups[2].Value);
var lineToFind = $" \"Name\": \"{name}\",";
var offset = $"\"StatementIndex\": {index}";
using var reader = new StringReader(s);
var lineNum = 0;
string line;
while ((line = reader.ReadLine()) != null)
{
lineNum++;
if (line.Equals(lineToFind, StringComparison.OrdinalIgnoreCase))
string[] sizes = {"B", "KB", "MB", "GB", "TB"};
var order = 0;
while (size >= 1024 && order < sizes.Length - 1)
{
var objectLine = lineNum;
while ((line = reader.ReadLine()) != null)
{
lineNum++;
if (line.Contains(offset, StringComparison.OrdinalIgnoreCase))
return lineNum;
}
return objectLine;
order++;
size /= 1024;
}
return $"{size:# ###.##} {sizes[order]}".TrimStart();
}
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)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string SubstringBefore(this string s, char delimiter)
{
lineNum++;
if (line.Equals(" {"))
index--;
if (index == -1)
return lineNum + 1;
var index = s.IndexOf(delimiter);
return index == -1 ? s : s[..index];
}
return 1;
[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[..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[..index];
}
[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[..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)
{
var index = s.LastIndexOf(delimiter, comparisonType);
return index == -1 ? s : s.Substring(index + delimiter.Length, s.Length - index - delimiter.Length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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;
}
}
}
}

View File

@ -2,18 +2,17 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<TargetFramework>net5.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<ApplicationIcon>FModel.ico</ApplicationIcon>
<Version>4.4.4.0</Version>
<AssemblyVersion>4.4.4.0</AssemblyVersion>
<FileVersion>4.4.4.0</FileVersion>
<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 Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">true</PublishSingleFile>
<PublishSingleFile>true</PublishSingleFile>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<StartupObject>FModel.App</StartupObject>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@ -39,18 +38,11 @@
<None Remove="Resources\fallenorder.png" />
<None Remove="Resources\FModel.ico" />
<None Remove="Resources\folder.png" />
<None Remove="Resources\label.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\square.png" />
<None Remove="Resources\square_off.png" />
<None Remove="Resources\cube.png" />
<None Remove="Resources\cube_off.png" />
<None Remove="Resources\light.png" />
<None Remove="Resources\light_off.png" />
<None Remove="Resources\pc.png" />
<None Remove="Resources\puzzle.png" />
<None Remove="Resources\roguecompany.png" />
@ -75,20 +67,18 @@
<None Remove="Resources\athena.png" />
<None Remove="Resources\Json.xshd" />
<None Remove="Resources\Ini.xshd" />
<None Remove="Resources\Verse.xshd" />
<None Remove="Resources\Xml.xshd" />
<None Remove="Resources\Cpp.xshd" />
<None Remove="Resources\Changelog.xshd" />
<None Remove="Resources\unix.png" />
<None Remove="Resources\linux.png" />
<None Remove="Resources\stateofdecay2.png" />
<None Remove="Resources\T_Placeholder_Item_Image.png" />
<None Remove="Resources\checker.png" />
<None Remove="Resources\T_ClipSize_Weapon_Stats.png" />
<None Remove="Resources\T_DamagePerBullet_Weapon_Stats.png" />
<None Remove="Resources\T_ReloadTime_Weapon_Stats.png" />
<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" />
@ -99,76 +89,36 @@
<None Remove="Resources\delete.png" />
<None Remove="Resources\edit.png" />
<None Remove="Resources\go_to_directory.png" />
<None Remove="Resources\npcleftside.png" />
<None Remove="Resources\default.frag" />
<None Remove="Resources\default.vert" />
<None Remove="Resources\grid.frag" />
<None Remove="Resources\grid.vert" />
<None Remove="Resources\skybox.frag" />
<None Remove="Resources\skybox.vert" />
<None Remove="Resources\framebuffer.frag" />
<None Remove="Resources\framebuffer.vert" />
<None Remove="Resources\outline.frag" />
<None Remove="Resources\outline.vert" />
<None Remove="Resources\picking.frag" />
<None Remove="Resources\picking.vert" />
<None Remove="Resources\light.frag" />
<None Remove="Resources\light.vert" />
<None Remove="Resources\bone.frag" />
<None Remove="Resources\bone.vert" />
<None Remove="Resources\collision.vert" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\Json.xshd" />
<EmbeddedResource Include="Resources\Ini.xshd" />
<EmbeddedResource Include="Resources\Verse.xshd" />
<EmbeddedResource Include="Resources\Xml.xshd" />
<EmbeddedResource Include="Resources\Cpp.xshd" />
<EmbeddedResource Include="Resources\Changelog.xshd" />
<EmbeddedResource Include="Resources\default.frag" />
<EmbeddedResource Include="Resources\default.vert" />
<EmbeddedResource Include="Resources\grid.frag" />
<EmbeddedResource Include="Resources\grid.vert" />
<EmbeddedResource Include="Resources\skybox.frag" />
<EmbeddedResource Include="Resources\skybox.vert" />
<EmbeddedResource Include="Resources\framebuffer.frag" />
<EmbeddedResource Include="Resources\framebuffer.vert" />
<EmbeddedResource Include="Resources\outline.frag" />
<EmbeddedResource Include="Resources\outline.vert" />
<EmbeddedResource Include="Resources\picking.frag" />
<EmbeddedResource Include="Resources\picking.vert" />
<EmbeddedResource Include="Resources\light.frag" />
<EmbeddedResource Include="Resources\light.vert" />
<EmbeddedResource Include="Resources\bone.frag" />
<EmbeddedResource Include="Resources\bone.vert" />
<EmbeddedResource Include="Resources\collision.vert" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AdonisUI" Version="1.17.1" />
<PackageReference Include="AdonisUI.ClassicTheme" Version="1.17.1" />
<PackageReference Include="Autoupdater.NET.Official" Version="1.9.2" />
<PackageReference Include="AvalonEdit" Version="6.3.0.90" />
<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.2.1.24" />
<PackageReference Include="EpicManifestParser" Version="2.3.3" />
<PackageReference Include="ImGui.NET" Version="1.91.0.1" />
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.8" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NVorbis" Version="0.10.5" />
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
<PackageReference Include="OpenTK" Version="4.8.2" />
<PackageReference Include="RestSharp" Version="112.1.0" />
<PackageReference Include="Serilog" Version="4.0.2" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.8" />
<PackageReference Include="SkiaSharp.Svg" Version="1.60.0" />
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
<PackageReference Include="EpicManifestParser" Version="1.2.0" />
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NVorbis" Version="0.10.1" />
<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" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CUE4Parse\CUE4Parse-Conversion\CUE4Parse-Conversion.csproj" />
<ProjectReference Include="..\CUE4Parse\CUE4Parse-Fortnite\CUE4Parse-Fortnite.csproj" />
<ProjectReference Include="..\CUE4Parse\CUE4Parse\CUE4Parse.csproj" />
</ItemGroup>
@ -181,7 +131,6 @@
<Resource Include="Resources\fallenorder.png" />
<Resource Include="Resources\FModel.ico" />
<Resource Include="Resources\folder.png" />
<Resource Include="Resources\label.png" />
<Resource Include="Resources\fortnite.png" />
<Resource Include="Resources\fortnitebr.png" />
<Resource Include="Resources\empty_folder.png" />
@ -189,12 +138,6 @@
<Resource Include="Resources\gear.png" />
<Resource Include="Resources\localization.png" />
<Resource Include="Resources\materialicon.png" />
<Resource Include="Resources\square.png" />
<Resource Include="Resources\square_off.png" />
<Resource Include="Resources\cube.png" />
<Resource Include="Resources\cube_off.png" />
<Resource Include="Resources\light.png" />
<Resource Include="Resources\light_off.png" />
<Resource Include="Resources\pc.png" />
<Resource Include="Resources\puzzle.png" />
<Resource Include="Resources\roguecompany.png" />
@ -219,14 +162,14 @@
<Resource Include="Resources\athena.png" />
<Resource Include="Resources\unix.png" />
<Resource Include="Resources\linux.png" />
<Resource Include="Resources\stateofdecay2.png" />
<Resource Include="Resources\T_Placeholder_Item_Image.png" />
<Resource Include="Resources\checker.png" />
<Resource Include="Resources\T_ClipSize_Weapon_Stats.png" />
<Resource Include="Resources\T_DamagePerBullet_Weapon_Stats.png" />
<Resource Include="Resources\T_ReloadTime_Weapon_Stats.png" />
<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" />
@ -237,24 +180,6 @@
<Resource Include="Resources\delete.png" />
<Resource Include="Resources\edit.png" />
<Resource Include="Resources\go_to_directory.png" />
<Resource Include="Resources\npcleftside.png" />
<Resource Include="Resources\nx.png" />
<Resource Include="Resources\ny.png" />
<Resource Include="Resources\nz.png" />
<Resource Include="Resources\px.png" />
<Resource Include="Resources\py.png" />
<Resource Include="Resources\pz.png" />
<Resource Include="Resources\pointlight.png" />
<Resource Include="Resources\spotlight.png" />
<Resource Include="Resources\link_on.png" />
<Resource Include="Resources\link_off.png" />
<Resource Include="Resources\link_has.png" />
<Resource Include="Resources\tl_play.png" />
<Resource Include="Resources\tl_pause.png" />
<Resource Include="Resources\tl_rewind.png" />
<Resource Include="Resources\tl_forward.png" />
<Resource Include="Resources\tl_previous.png" />
<Resource Include="Resources\tl_next.png" />
</ItemGroup>
<ItemGroup>

View File

@ -1,12 +1,14 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31912.275
# 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
@ -23,6 +25,10 @@ Global
{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

View File

@ -2,31 +2,32 @@
using System.Threading;
using System.Threading.Tasks.Dataflow;
namespace FModel.Framework;
public class AsyncQueue<T> : IAsyncEnumerable<T>
namespace FModel.Framework
{
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)
public class AsyncQueue<T> : IAsyncEnumerable<T>
{
await _semaphore.WaitAsync(token);
try
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)
{
while (Count > 0)
await _semaphore.WaitAsync(token);
try
{
token.ThrowIfCancellationRequested();
yield return await _buffer.ReceiveAsync(token);
while (Count > 0)
{
token.ThrowIfCancellationRequested();
yield return await _buffer.ReceiveAsync(token);
}
}
finally
{
_semaphore.Release();
}
}
finally
{
_semaphore.Release();
}
}
}
}

View File

@ -1,18 +1,19 @@
using System;
using System.Windows.Input;
namespace FModel.Framework;
public abstract class Command : ICommand
namespace FModel.Framework
{
public abstract void Execute(object parameter);
public abstract bool CanExecute(object parameter);
public void RaiseCanExecuteChanged()
public abstract class Command : ICommand
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
public abstract void Execute(object parameter);
public event EventHandler CanExecuteChanged;
public abstract bool CanExecute(object parameter);
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
public event EventHandler CanExecuteChanged;
}
}

View File

@ -4,87 +4,93 @@ using SkiaSharp;
using SkiaSharp.HarfBuzz;
using Buffer = HarfBuzzSharp.Buffer;
namespace FModel.Framework;
public class CustomSKShaper : SKShaper
namespace FModel.Framework
{
private const int _FONT_SIZE_SCALE = 512;
private readonly Font _font;
public CustomSKShaper(SKTypeface typeface) : base(typeface)
public class CustomSKShaper : SKShaper
{
using var blob = Typeface.OpenStream(out var index).ToHarfBuzzBlob();
using var face = new Face(blob, index);
face.Index = index;
face.UnitsPerEm = Typeface.UnitsPerEm;
private const int _FONT_SIZE_SCALE = 512;
private readonly Font _font;
private readonly Buffer _buffer;
_font = new Font(face);
_font.SetScale(_FONT_SIZE_SCALE, _FONT_SIZE_SCALE);
_font.SetFunctionsOpenType();
}
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++)
public CustomSKShaper(SKTypeface typeface) : base(typeface)
{
// move the cursor
xOffset += pos[i].XAdvance * textSizeX;
yOffset += pos[i].YAdvance * textSizeY;
using (var blob = Typeface.OpenStream(out var index).ToHarfBuzzBlob())
using (var face = new Face(blob, index))
{
face.Index = index;
face.UnitsPerEm = Typeface.UnitsPerEm;
codepoints[i] = info[i].Codepoint;
clusters[i] = info[i].Cluster;
points[i] = new SKPoint(xOffset + pos[i].XOffset * textSizeX, yOffset - pos[i].YOffset * textSizeY);
_font = new Font(face);
_font.SetScale(_FONT_SIZE_SCALE, _FONT_SIZE_SCALE);
_font.SetFunctionsOpenType();
}
_buffer = new Buffer();
}
return new Result(codepoints, clusters, points, points[^1].X);
}
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)
public new Result Shape(Buffer buffer, float xOffset, float yOffset, SKPaint paint)
{
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.");
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);
}
buffer.GuessSegmentProperties();
return Shape(buffer, xOffset, yOffset, paint);
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);
}
}
}
}

View File

@ -1,19 +0,0 @@
using System;
using RestSharp;
namespace FModel.Framework;
public class FRestRequest : RestRequest
{
private const int TimeoutSeconds = 5;
public FRestRequest(string url, Method method = Method.Get) : base(url, method)
{
Timeout = TimeSpan.FromSeconds(TimeoutSeconds);
}
public FRestRequest(Uri uri, Method method = Method.Get) : base(uri, method)
{
Timeout = TimeSpan.FromSeconds(TimeoutSeconds);
}
}

View File

@ -1,45 +0,0 @@
namespace FModel.Framework;
public class FStatus : ViewModel
{
private bool _isReady;
public bool IsReady
{
get => _isReady;
private set => SetProperty(ref _isReady, value);
}
private EStatusKind _kind;
public EStatusKind Kind
{
get => _kind;
private set
{
SetProperty(ref _kind, value);
IsReady = Kind != EStatusKind.Loading && Kind != EStatusKind.Stopping;
}
}
private string _label;
public string Label
{
get => _label;
private set => SetProperty(ref _label, value);
}
public FStatus()
{
SetStatus(EStatusKind.Loading);
}
public void SetStatus(EStatusKind kind, string label = "")
{
Kind = kind;
UpdateStatusLabel(label);
}
public void UpdateStatusLabel(string label, string prefix = null)
{
Label = Kind == EStatusKind.Loading ? $"{prefix ?? Kind.ToString()} {label}".Trim() : Kind.ToString();
}
}

View File

@ -4,113 +4,114 @@ using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
namespace FModel.Framework;
public class FullyObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
namespace FModel.Framework
{
/// <summary>
/// Occurs when a property is changed within an item.
/// </summary>
public event EventHandler<ItemPropertyChangedEventArgs> ItemPropertyChanged;
public FullyObservableCollection()
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(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)
public FullyObservableCollection()
{
foreach (T item in e.OldItems)
item.PropertyChanged -= ChildPropertyChanged;
}
if (e.Action is NotifyCollectionChangedAction.Add or
NotifyCollectionChangedAction.Replace)
public FullyObservableCollection(List<T> list) : base(list)
{
foreach (T item in e.NewItems)
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;
}
base.OnCollectionChanged(e);
}
private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var typedSender = (T) sender;
var i = Items.IndexOf(typedSender);
protected void OnItemPropertyChanged(ItemPropertyChangedEventArgs e)
{
ItemPropertyChanged?.Invoke(this, e);
}
if (i < 0)
throw new ArgumentException("Received property notification from item not in collection");
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 (var 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;
OnItemPropertyChanged(i, e);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
/// Provides data for the <see cref="FullyObservableCollection{T}.ItemPropertyChanged"/> event.
/// </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)
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)
{
}
}
}

View File

@ -1,49 +1,50 @@
using System.Text;
using System.Windows.Input;
namespace FModel.Framework;
public class Hotkey : ViewModel
namespace FModel.Framework
{
private Key _key;
public Key Key
public class Hotkey : ViewModel
{
get => _key;
set => SetProperty(ref _key, value);
}
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);
}
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 Hotkey(Key key, ModifierKeys modifiers = ModifierKeys.None)
{
Key = key;
Modifiers = modifiers;
}
public bool IsTriggered(Key e)
{
return e == Key && Keyboard.Modifiers.HasFlag(Modifiers);
}
public bool IsTriggered(Key e)
{
return e == Key && Keyboard.Modifiers.HasFlag(Modifiers);
}
public override string ToString()
{
var str = new StringBuilder();
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 + ");
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();
str.Append(Key);
return str.ToString();
}
}
}

View File

@ -1,608 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Forms;
using FModel.Settings;
using ImGuiNET;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework;
using ErrorCode = OpenTK.Graphics.OpenGL4.ErrorCode;
using Keys = OpenTK.Windowing.GraphicsLibraryFramework.Keys;
namespace FModel.Framework;
public class ImGuiController : IDisposable
{
private bool _frameBegun;
private int _vertexArray;
private int _vertexBuffer;
private int _vertexBufferSize;
private int _indexBuffer;
private int _indexBufferSize;
//private Texture _fontTexture;
private int _fontTexture;
private int _shader;
private int _shaderFontTextureLocation;
private int _shaderProjectionMatrixLocation;
private int _windowWidth;
private int _windowHeight;
public ImFontPtr FontNormal;
public ImFontPtr FontBold;
public ImFontPtr FontSemiBold;
private readonly Vector2 _scaleFactor = Vector2.One;
public readonly float DpiScale = GetDpiScale();
private static bool KHRDebugAvailable = false;
public ImGuiController(int width, int height)
{
_windowWidth = width;
_windowHeight = height;
int major = GL.GetInteger(GetPName.MajorVersion);
int minor = GL.GetInteger(GetPName.MinorVersion);
KHRDebugAvailable = (major == 4 && minor >= 3) || IsExtensionSupported("KHR_debug");
IntPtr context = ImGui.CreateContext();
ImGui.SetCurrentContext(context);
var io = ImGui.GetIO();
unsafe
{
var iniFileNamePtr = Marshal.StringToCoTaskMemUTF8(Path.Combine(UserSettings.Default.OutputDirectory, ".data", "imgui.ini"));
io.NativePtr->IniFilename = (byte*)iniFileNamePtr;
}
FontNormal = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeui.ttf", 16 * DpiScale);
FontBold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeuib.ttf", 16 * DpiScale);
FontSemiBold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\seguisb.ttf", 16 * DpiScale);
io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset;
io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags.DockingEnable;
io.Fonts.Flags |= ImFontAtlasFlags.NoBakedLines;
CreateDeviceResources();
SetPerFrameImGuiData(1f / 60f);
ImGui.NewFrame();
_frameBegun = true;
}
public void Bold() => PushFont(FontBold);
public void SemiBold() => PushFont(FontSemiBold);
public void PopFont()
{
ImGui.PopFont();
PushFont(FontNormal);
}
private void PushFont(ImFontPtr ptr) => ImGui.PushFont(ptr);
public void WindowResized(int width, int height)
{
_windowWidth = width;
_windowHeight = height;
}
public void DestroyDeviceObjects()
{
Dispose();
}
public void CreateDeviceResources()
{
_vertexBufferSize = 10000;
_indexBufferSize = 2000;
int prevVAO = GL.GetInteger(GetPName.VertexArrayBinding);
int prevArrayBuffer = GL.GetInteger(GetPName.ArrayBufferBinding);
_vertexArray = GL.GenVertexArray();
GL.BindVertexArray(_vertexArray);
LabelObject(ObjectLabelIdentifier.VertexArray, _vertexArray, "ImGui");
_vertexBuffer = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBuffer);
LabelObject(ObjectLabelIdentifier.Buffer, _vertexBuffer, "VBO: ImGui");
GL.BufferData(BufferTarget.ArrayBuffer, _vertexBufferSize, IntPtr.Zero, BufferUsageHint.DynamicDraw);
_indexBuffer = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ElementArrayBuffer, _indexBuffer);
LabelObject(ObjectLabelIdentifier.Buffer, _indexBuffer, "EBO: ImGui");
GL.BufferData(BufferTarget.ElementArrayBuffer, _indexBufferSize, IntPtr.Zero, BufferUsageHint.DynamicDraw);
RecreateFontDeviceTexture();
string VertexSource = @"#version 460 core
uniform mat4 projection_matrix;
layout(location = 0) in vec2 in_position;
layout(location = 1) in vec2 in_texCoord;
layout(location = 2) in vec4 in_color;
out vec4 color;
out vec2 texCoord;
void main()
{
gl_Position = projection_matrix * vec4(in_position, 0, 1);
color = in_color;
texCoord = in_texCoord;
}";
string FragmentSource = @"#version 460 core
uniform sampler2D in_fontTexture;
in vec4 color;
in vec2 texCoord;
out vec4 outputColor;
void main()
{
outputColor = color * texture(in_fontTexture, texCoord);
}";
_shader = CreateProgram("ImGui", VertexSource, FragmentSource);
_shaderProjectionMatrixLocation = GL.GetUniformLocation(_shader, "projection_matrix");
_shaderFontTextureLocation = GL.GetUniformLocation(_shader, "in_fontTexture");
int stride = Unsafe.SizeOf<ImDrawVert>();
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, stride, 0);
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, stride, 8);
GL.VertexAttribPointer(2, 4, VertexAttribPointerType.UnsignedByte, true, stride, 16);
GL.EnableVertexAttribArray(0);
GL.EnableVertexAttribArray(1);
GL.EnableVertexAttribArray(2);
GL.BindVertexArray(prevVAO);
GL.BindBuffer(BufferTarget.ArrayBuffer, prevArrayBuffer);
CheckGLError("End of ImGui setup");
}
/// <summary>
/// Recreates the device texture used to render text.
/// </summary>
public void RecreateFontDeviceTexture()
{
ImGuiIOPtr io = ImGui.GetIO();
io.Fonts.GetTexDataAsRGBA32(out IntPtr pixels, out int width, out int height, out int bytesPerPixel);
int mips = (int)Math.Floor(Math.Log(Math.Max(width, height), 2));
int prevActiveTexture = GL.GetInteger(GetPName.ActiveTexture);
GL.ActiveTexture(TextureUnit.Texture0);
int prevTexture2D = GL.GetInteger(GetPName.TextureBinding2D);
_fontTexture = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, _fontTexture);
GL.TexStorage2D(TextureTarget2d.Texture2D, mips, SizedInternalFormat.Rgba8, width, height);
LabelObject(ObjectLabelIdentifier.Texture, _fontTexture, "ImGui Text Atlas");
GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, width, height, PixelFormat.Bgra, PixelType.UnsignedByte, pixels);
GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMaxLevel, mips - 1);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
// Restore state
GL.BindTexture(TextureTarget.Texture2D, prevTexture2D);
GL.ActiveTexture((TextureUnit)prevActiveTexture);
io.Fonts.SetTexID((IntPtr)_fontTexture);
io.Fonts.ClearTexData();
}
/// <summary>
/// Renders the ImGui draw list data.
/// </summary>
public void Render()
{
if (_frameBegun)
{
_frameBegun = false;
ImGui.Render();
RenderImDrawData(ImGui.GetDrawData());
}
CheckGLError("End of frame");
}
/// <summary>
/// Updates ImGui input and IO configuration state.
/// </summary>
public void Update(GameWindow wnd, float deltaSeconds)
{
if (_frameBegun)
{
ImGui.Render();
}
SetPerFrameImGuiData(deltaSeconds);
UpdateImGuiInput(wnd);
_frameBegun = true;
ImGui.NewFrame();
}
/// <summary>
/// Sets per-frame data based on the associated window.
/// This is called by Update(float).
/// </summary>
private void SetPerFrameImGuiData(float deltaSeconds)
{
ImGuiIOPtr io = ImGui.GetIO();
// if (io.WantSaveIniSettings) ImGui.SaveIniSettingsToDisk(_iniPath);
io.DisplaySize = new Vector2(
_windowWidth / _scaleFactor.X,
_windowHeight / _scaleFactor.Y);
io.DisplayFramebufferScale = _scaleFactor;
io.DeltaTime = deltaSeconds; // DeltaTime is in seconds.
}
readonly List<char> PressedChars = new List<char>();
private void UpdateImGuiInput(GameWindow wnd)
{
ImGuiIOPtr io = ImGui.GetIO();
var mState = wnd.MouseState;
var kState = wnd.KeyboardState;
io.AddMousePosEvent(mState.X, mState.Y);
io.AddMouseButtonEvent(0, mState[MouseButton.Left]);
io.AddMouseButtonEvent(1, mState[MouseButton.Right]);
io.AddMouseButtonEvent(2, mState[MouseButton.Middle]);
io.AddMouseButtonEvent(3, mState[MouseButton.Button1]);
io.AddMouseButtonEvent(4, mState[MouseButton.Button2]);
io.AddMouseWheelEvent(mState.ScrollDelta.X, mState.ScrollDelta.Y);
foreach (Keys key in Enum.GetValues(typeof(Keys)))
{
if (key == Keys.Unknown) continue;
io.AddKeyEvent(TranslateKey(key), kState.IsKeyDown(key));
}
foreach (var c in PressedChars)
{
io.AddInputCharacter(c);
}
PressedChars.Clear();
io.KeyShift = kState.IsKeyDown(Keys.LeftShift) || kState.IsKeyDown(Keys.RightShift);
io.KeyCtrl = kState.IsKeyDown(Keys.LeftControl) || kState.IsKeyDown(Keys.RightControl);
io.KeyAlt = kState.IsKeyDown(Keys.LeftAlt) || kState.IsKeyDown(Keys.RightAlt);
io.KeySuper = kState.IsKeyDown(Keys.LeftSuper) || kState.IsKeyDown(Keys.RightSuper);
}
public void PressChar(char keyChar)
{
PressedChars.Add(keyChar);
}
private void RenderImDrawData(ImDrawDataPtr draw_data)
{
if (draw_data.CmdListsCount == 0)
{
return;
}
// Get intial state.
int prevVAO = GL.GetInteger(GetPName.VertexArrayBinding);
int prevArrayBuffer = GL.GetInteger(GetPName.ArrayBufferBinding);
int prevProgram = GL.GetInteger(GetPName.CurrentProgram);
bool prevBlendEnabled = GL.GetBoolean(GetPName.Blend);
bool prevScissorTestEnabled = GL.GetBoolean(GetPName.ScissorTest);
int prevBlendEquationRgb = GL.GetInteger(GetPName.BlendEquationRgb);
int prevBlendEquationAlpha = GL.GetInteger(GetPName.BlendEquationAlpha);
int prevBlendFuncSrcRgb = GL.GetInteger(GetPName.BlendSrcRgb);
int prevBlendFuncSrcAlpha = GL.GetInteger(GetPName.BlendSrcAlpha);
int prevBlendFuncDstRgb = GL.GetInteger(GetPName.BlendDstRgb);
int prevBlendFuncDstAlpha = GL.GetInteger(GetPName.BlendDstAlpha);
bool prevCullFaceEnabled = GL.GetBoolean(GetPName.CullFace);
bool prevDepthTestEnabled = GL.GetBoolean(GetPName.DepthTest);
int prevActiveTexture = GL.GetInteger(GetPName.ActiveTexture);
GL.ActiveTexture(TextureUnit.Texture0);
int prevTexture2D = GL.GetInteger(GetPName.TextureBinding2D);
Span<int> prevScissorBox = stackalloc int[4];
unsafe
{
fixed (int* iptr = &prevScissorBox[0])
{
GL.GetInteger(GetPName.ScissorBox, iptr);
}
}
// Bind the element buffer (thru the VAO) so that we can resize it.
GL.BindVertexArray(_vertexArray);
// Bind the vertex buffer so that we can resize it.
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBuffer);
for (int i = 0; i < draw_data.CmdListsCount; i++)
{
ImDrawListPtr cmd_list = draw_data.CmdLists[i];
int vertexSize = cmd_list.VtxBuffer.Size * Unsafe.SizeOf<ImDrawVert>();
if (vertexSize > _vertexBufferSize)
{
int newSize = (int)Math.Max(_vertexBufferSize * 1.5f, vertexSize);
GL.BufferData(BufferTarget.ArrayBuffer, newSize, IntPtr.Zero, BufferUsageHint.DynamicDraw);
_vertexBufferSize = newSize;
}
int indexSize = cmd_list.IdxBuffer.Size * sizeof(ushort);
if (indexSize > _indexBufferSize)
{
int newSize = (int)Math.Max(_indexBufferSize * 1.5f, indexSize);
GL.BufferData(BufferTarget.ElementArrayBuffer, newSize, IntPtr.Zero, BufferUsageHint.DynamicDraw);
_indexBufferSize = newSize;
}
}
// Setup orthographic projection matrix into our constant buffer
ImGuiIOPtr io = ImGui.GetIO();
var mvp = OpenTK.Mathematics.Matrix4.CreateOrthographicOffCenter(
0.0f,
io.DisplaySize.X,
io.DisplaySize.Y,
0.0f,
-1.0f,
1.0f);
GL.UseProgram(_shader);
GL.UniformMatrix4(_shaderProjectionMatrixLocation, false, ref mvp);
GL.Uniform1(_shaderFontTextureLocation, 0);
CheckGLError("Projection");
GL.BindVertexArray(_vertexArray);
CheckGLError("VAO");
draw_data.ScaleClipRects(io.DisplayFramebufferScale);
GL.Enable(EnableCap.Blend);
GL.Enable(EnableCap.ScissorTest);
GL.BlendEquation(BlendEquationMode.FuncAdd);
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
GL.Disable(EnableCap.CullFace);
GL.Disable(EnableCap.DepthTest);
// Render command lists
for (int n = 0; n < draw_data.CmdListsCount; n++)
{
ImDrawListPtr cmd_list = draw_data.CmdLists[n];
GL.BufferSubData(BufferTarget.ArrayBuffer, IntPtr.Zero, cmd_list.VtxBuffer.Size * Unsafe.SizeOf<ImDrawVert>(), cmd_list.VtxBuffer.Data);
CheckGLError($"Data Vert {n}");
GL.BufferSubData(BufferTarget.ElementArrayBuffer, IntPtr.Zero, cmd_list.IdxBuffer.Size * sizeof(ushort), cmd_list.IdxBuffer.Data);
CheckGLError($"Data Idx {n}");
for (int cmd_i = 0; cmd_i < cmd_list.CmdBuffer.Size; cmd_i++)
{
ImDrawCmdPtr pcmd = cmd_list.CmdBuffer[cmd_i];
if (pcmd.UserCallback != IntPtr.Zero)
{
throw new NotImplementedException();
}
else
{
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, (int)pcmd.TextureId);
CheckGLError("Texture");
// We do _windowHeight - (int)clip.W instead of (int)clip.Y because gl has flipped Y when it comes to these coordinates
var clip = pcmd.ClipRect;
GL.Scissor((int)clip.X, _windowHeight - (int)clip.W, (int)(clip.Z - clip.X), (int)(clip.W - clip.Y));
CheckGLError("Scissor");
if ((io.BackendFlags & ImGuiBackendFlags.RendererHasVtxOffset) != 0)
{
GL.DrawElementsBaseVertex(PrimitiveType.Triangles, (int)pcmd.ElemCount, DrawElementsType.UnsignedShort, (IntPtr)(pcmd.IdxOffset * sizeof(ushort)), unchecked((int)pcmd.VtxOffset));
}
else
{
GL.DrawElements(BeginMode.Triangles, (int)pcmd.ElemCount, DrawElementsType.UnsignedShort, (int)pcmd.IdxOffset * sizeof(ushort));
}
CheckGLError("Draw");
}
}
}
GL.Disable(EnableCap.Blend);
GL.Disable(EnableCap.ScissorTest);
// Reset state
GL.BindTexture(TextureTarget.Texture2D, prevTexture2D);
GL.ActiveTexture((TextureUnit)prevActiveTexture);
GL.UseProgram(prevProgram);
GL.BindVertexArray(prevVAO);
GL.Scissor(prevScissorBox[0], prevScissorBox[1], prevScissorBox[2], prevScissorBox[3]);
GL.BindBuffer(BufferTarget.ArrayBuffer, prevArrayBuffer);
GL.BlendEquationSeparate((BlendEquationMode)prevBlendEquationRgb, (BlendEquationMode)prevBlendEquationAlpha);
GL.BlendFuncSeparate(
(BlendingFactorSrc)prevBlendFuncSrcRgb,
(BlendingFactorDest)prevBlendFuncDstRgb,
(BlendingFactorSrc)prevBlendFuncSrcAlpha,
(BlendingFactorDest)prevBlendFuncDstAlpha);
if (prevBlendEnabled) GL.Enable(EnableCap.Blend); else GL.Disable(EnableCap.Blend);
if (prevDepthTestEnabled) GL.Enable(EnableCap.DepthTest); else GL.Disable(EnableCap.DepthTest);
if (prevCullFaceEnabled) GL.Enable(EnableCap.CullFace); else GL.Disable(EnableCap.CullFace);
if (prevScissorTestEnabled) GL.Enable(EnableCap.ScissorTest); else GL.Disable(EnableCap.ScissorTest);
}
/// <summary>
/// Frees all graphics resources used by the renderer.
/// </summary>
public void Dispose()
{
GL.DeleteVertexArray(_vertexArray);
GL.DeleteBuffer(_vertexBuffer);
GL.DeleteBuffer(_indexBuffer);
GL.DeleteTexture(_fontTexture);
GL.DeleteProgram(_shader);
}
public static void LabelObject(ObjectLabelIdentifier objLabelIdent, int glObject, string name)
{
if (KHRDebugAvailable)
GL.ObjectLabel(objLabelIdent, glObject, name.Length, name);
}
static bool IsExtensionSupported(string name)
{
int n = GL.GetInteger(GetPName.NumExtensions);
for (int i = 0; i < n; i++)
{
string extension = GL.GetString(StringNameIndexed.Extensions, i);
if (extension == name) return true;
}
return false;
}
public static int CreateProgram(string name, string vertexSource, string fragmentSoruce)
{
int program = GL.CreateProgram();
LabelObject(ObjectLabelIdentifier.Program, program, $"Program: {name}");
int vertex = CompileShader(name, ShaderType.VertexShader, vertexSource);
int fragment = CompileShader(name, ShaderType.FragmentShader, fragmentSoruce);
GL.AttachShader(program, vertex);
GL.AttachShader(program, fragment);
GL.LinkProgram(program);
GL.GetProgram(program, GetProgramParameterName.LinkStatus, out int success);
if (success == 0)
{
string info = GL.GetProgramInfoLog(program);
Debug.WriteLine($"GL.LinkProgram had info log [{name}]:\n{info}");
}
GL.DetachShader(program, vertex);
GL.DetachShader(program, fragment);
GL.DeleteShader(vertex);
GL.DeleteShader(fragment);
return program;
}
private static int CompileShader(string name, ShaderType type, string source)
{
int shader = GL.CreateShader(type);
LabelObject(ObjectLabelIdentifier.Shader, shader, $"Shader: {name}");
GL.ShaderSource(shader, source);
GL.CompileShader(shader);
GL.GetShader(shader, ShaderParameter.CompileStatus, out int success);
if (success == 0)
{
string info = GL.GetShaderInfoLog(shader);
Debug.WriteLine($"GL.CompileShader for shader '{name}' [{type}] had info log:\n{info}");
}
return shader;
}
public static void CheckGLError(string title)
{
ErrorCode error;
int i = 1;
while ((error = GL.GetError()) != ErrorCode.NoError)
{
Debug.Print($"{title} ({i++}): {error}");
}
}
public static float GetDpiScale()
{
return Math.Max((float)(Screen.PrimaryScreen.Bounds.Width / SystemParameters.PrimaryScreenWidth), (float)(Screen.PrimaryScreen.Bounds.Height / SystemParameters.PrimaryScreenHeight));
}
public static ImGuiKey TranslateKey(Keys key)
{
if (key is >= Keys.D0 and <= Keys.D9)
return key - Keys.D0 + ImGuiKey._0;
if (key is >= Keys.A and <= Keys.Z)
return key - Keys.A + ImGuiKey.A;
if (key is >= Keys.KeyPad0 and <= Keys.KeyPad9)
return key - Keys.KeyPad0 + ImGuiKey.Keypad0;
if (key is >= Keys.F1 and <= Keys.F24)
return key - Keys.F1 + ImGuiKey.F24;
return key switch
{
Keys.Tab => ImGuiKey.Tab,
Keys.Left => ImGuiKey.LeftArrow,
Keys.Right => ImGuiKey.RightArrow,
Keys.Up => ImGuiKey.UpArrow,
Keys.Down => ImGuiKey.DownArrow,
Keys.PageUp => ImGuiKey.PageUp,
Keys.PageDown => ImGuiKey.PageDown,
Keys.Home => ImGuiKey.Home,
Keys.End => ImGuiKey.End,
Keys.Insert => ImGuiKey.Insert,
Keys.Delete => ImGuiKey.Delete,
Keys.Backspace => ImGuiKey.Backspace,
Keys.Space => ImGuiKey.Space,
Keys.Enter => ImGuiKey.Enter,
Keys.Escape => ImGuiKey.Escape,
Keys.Apostrophe => ImGuiKey.Apostrophe,
Keys.Comma => ImGuiKey.Comma,
Keys.Minus => ImGuiKey.Minus,
Keys.Period => ImGuiKey.Period,
Keys.Slash => ImGuiKey.Slash,
Keys.Semicolon => ImGuiKey.Semicolon,
Keys.Equal => ImGuiKey.Equal,
Keys.LeftBracket => ImGuiKey.LeftBracket,
Keys.Backslash => ImGuiKey.Backslash,
Keys.RightBracket => ImGuiKey.RightBracket,
Keys.GraveAccent => ImGuiKey.GraveAccent,
Keys.CapsLock => ImGuiKey.CapsLock,
Keys.ScrollLock => ImGuiKey.ScrollLock,
Keys.NumLock => ImGuiKey.NumLock,
Keys.PrintScreen => ImGuiKey.PrintScreen,
Keys.Pause => ImGuiKey.Pause,
Keys.KeyPadDecimal => ImGuiKey.KeypadDecimal,
Keys.KeyPadDivide => ImGuiKey.KeypadDivide,
Keys.KeyPadMultiply => ImGuiKey.KeypadMultiply,
Keys.KeyPadSubtract => ImGuiKey.KeypadSubtract,
Keys.KeyPadAdd => ImGuiKey.KeypadAdd,
Keys.KeyPadEnter => ImGuiKey.KeypadEnter,
Keys.KeyPadEqual => ImGuiKey.KeypadEqual,
Keys.LeftShift => ImGuiKey.LeftShift,
Keys.LeftControl => ImGuiKey.LeftCtrl,
Keys.LeftAlt => ImGuiKey.LeftAlt,
Keys.LeftSuper => ImGuiKey.LeftSuper,
Keys.RightShift => ImGuiKey.RightShift,
Keys.RightControl => ImGuiKey.RightCtrl,
Keys.RightAlt => ImGuiKey.RightAlt,
Keys.RightSuper => ImGuiKey.RightSuper,
Keys.Menu => ImGuiKey.Menu,
_ => ImGuiKey.None
};
}
}

View File

@ -2,29 +2,42 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using RestSharp;
using RestSharp.Serializers;
using RestSharp.Serialization;
namespace FModel.Framework;
public class JsonNetSerializer : IRestSerializer, ISerializer, IDeserializer
namespace FModel.Framework
{
public static readonly JsonSerializerSettings SerializerSettings = new()
public class JsonNetSerializer : IRestSerializer
{
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore,
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
public static readonly JsonSerializerSettings SerializerSettings = new()
{
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore,
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
public string Serialize(Parameter parameter) => JsonConvert.SerializeObject(parameter.Value);
public string Serialize(object obj) => JsonConvert.SerializeObject(obj);
public T Deserialize<T>(RestResponse response) => JsonConvert.DeserializeObject<T>(response.Content!, SerializerSettings);
public string Serialize(object obj)
{
return JsonConvert.SerializeObject(obj);
}
public ISerializer Serializer => this;
public IDeserializer Deserializer => this;
[Obsolete]
public string Serialize(Parameter parameter)
{
return JsonConvert.SerializeObject(parameter.Value);
}
public ContentType ContentType { get; set; } = ContentType.Json;
public string[] AcceptedContentTypes => ContentType.JsonAccept;
public SupportsContentType SupportsContentType => contentType => contentType.Value.EndsWith("json", StringComparison.InvariantCultureIgnoreCase);
public T Deserialize<T>(IRestResponse response)
{
return JsonConvert.DeserializeObject<T>(response.Content, SerializerSettings);
}
public DataFormat DataFormat => DataFormat.Json;
}
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;
}
}

View File

@ -1,46 +0,0 @@
using System.Collections.Generic;
namespace FModel.Framework;
public class NavigationList<T> : List<T>
{
private int _currentIndex;
public int CurrentIndex
{
get
{
if (_currentIndex > Count - 1)
{
_currentIndex = Count - 1;
}
if (_currentIndex < 0)
{
_currentIndex = 0;
}
return _currentIndex;
}
set => _currentIndex = value;
}
public T MoveNext
{
get
{
_currentIndex++;
return this[CurrentIndex];
}
}
public T MovePrevious
{
get
{
_currentIndex--;
return this[CurrentIndex];
}
}
public T Current => this[CurrentIndex];
}

View File

@ -3,39 +3,40 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
namespace FModel.Framework;
public sealed class RangeObservableCollection<T> : ObservableCollection<T>
namespace FModel.Framework
{
private bool _suppressNotification;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
public sealed class RangeObservableCollection<T> : ObservableCollection<T>
{
if (!_suppressNotification)
base.OnCollectionChanged(e);
}
private bool _suppressNotification;
public void AddRange(IEnumerable<T> list)
{
if (list == null)
throw new ArgumentNullException(nameof(list));
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (!_suppressNotification)
base.OnCollectionChanged(e);
}
_suppressNotification = true;
public void AddRange(IEnumerable<T> list)
{
if (list == null)
throw new ArgumentNullException(nameof(list));
foreach (var item in list)
Add(item);
_suppressNotification = true;
_suppressNotification = false;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
foreach (var item in list)
Add(item);
public void SetSuppressionState(bool state)
{
_suppressNotification = state;
}
_suppressNotification = false;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void InvokeOnCollectionChanged(NotifyCollectionChangedAction changedAction = NotifyCollectionChangedAction.Reset)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(changedAction));
public void SetSuppressionState(bool state)
{
_suppressNotification = state;
}
public void InvokeOnCollectionChanged(NotifyCollectionChangedAction changedAction = NotifyCollectionChangedAction.Reset)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(changedAction));
}
}
}

View File

@ -6,70 +6,71 @@ using System.Linq;
using System.Runtime.CompilerServices;
using Newtonsoft.Json;
namespace FModel.Framework;
public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo, IDataErrorInfo
namespace FModel.Framework
{
private readonly Dictionary<string, IList<string>> _validationErrors = new();
public string this[string propertyName]
public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo, IDataErrorInfo
{
get
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 Error;
return _validationErrors.SelectMany(kvp => kvp.Value);
return _validationErrors.ContainsKey(propertyName) ? string.Join(Environment.NewLine, _validationErrors[propertyName]) : string.Empty;
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;
}
}
[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;
}
}

View File

@ -1,49 +1,50 @@
using System;
namespace FModel.Framework;
public abstract class ViewModelCommand<TContextViewModel> : Command where TContextViewModel : ViewModel
namespace FModel.Framework
{
private WeakReference _parent;
public TContextViewModel ContextViewModel
public abstract class ViewModelCommand<TContextViewModel> : Command where TContextViewModel : ViewModel
{
get
{
if (_parent is { IsAlive: true })
return (TContextViewModel) _parent.Target;
private WeakReference _parent;
return null;
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;
}
}
private set
protected ViewModelCommand(TContextViewModel contextViewModel)
{
if (ContextViewModel == value)
return;
_parent = value != null ? new WeakReference(value) : null;
ContextViewModel = contextViewModel /*?? throw new ArgumentNullException(nameof(contextViewModel))*/;
}
}
protected ViewModelCommand(TContextViewModel contextViewModel)
{
ContextViewModel = contextViewModel /*?? throw new ArgumentNullException(nameof(contextViewModel))*/;
}
public sealed override void Execute(object parameter)
{
Execute(ContextViewModel, parameter);
}
public sealed override void Execute(object parameter)
{
Execute(ContextViewModel, parameter);
}
public abstract void Execute(TContextViewModel contextViewModel, object parameter);
public abstract void Execute(TContextViewModel contextViewModel, object parameter);
public sealed override bool CanExecute(object parameter)
{
return CanExecute(ContextViewModel, parameter);
}
public sealed override bool CanExecute(object parameter)
{
return CanExecute(ContextViewModel, parameter);
}
public virtual bool CanExecute(TContextViewModel contextViewModel, object parameter)
{
return true;
public virtual bool CanExecute(TContextViewModel contextViewModel, object parameter)
{
return true;
}
}
}

View File

@ -1,114 +1,88 @@
using System;
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
namespace FModel;
public static class Helper
namespace FModel
{
public static string FixKey(string key)
public static class Helper
{
if (string.IsNullOrEmpty(key))
return string.Empty;
var keySpan = key.AsSpan().Trim();
if (keySpan.Length > sizeof(char) * (2 /* 0x */ + 32 /* FAES = 256 bit */)) // maybe strictly check for length?
return string.Empty; // bullshit key
Span<char> resultSpan = stackalloc char[keySpan.Length + 2 /* pad for 0x */];
keySpan.ToUpperInvariant(resultSpan[2..]);
if (resultSpan[2..].StartsWith("0X"))
resultSpan = resultSpan[2..];
else
resultSpan[0] = '0';
resultSpan[1] = 'x';
return new string(resultSpan);
}
public static void OpenWindow<T>(string windowName, Action action) where T : Window
{
if (!IsWindowOpen<T>(windowName))
[StructLayout(LayoutKind.Explicit)]
private struct NanUnion
{
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();
[FieldOffset(0)]
internal double DoubleValue;
[FieldOffset(0)]
internal readonly ulong UlongValue;
}
var ret = (T) GetOpenedWindow<T>(windowName);
ret.Focus();
ret.Activate();
return ret;
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;
}
}
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 ulongValue = Unsafe.As<double, ulong>(ref value);
var exp = ulongValue & 0xfff0000000000000;
var man = 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;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DegreesToRadians(float degrees)
{
const float ratio = MathF.PI / 180f;
return ratio * degrees;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float RadiansToDegrees(float radians)
{
const float ratio = 180f / MathF.PI;
return radians * ratio;
}
}
}

View File

@ -4,8 +4,6 @@
xmlns:local="clr-namespace:FModel"
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
xmlns:settings="clr-namespace:FModel.Settings"
xmlns:services="clr-namespace:FModel.Services"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI"
@ -14,18 +12,13 @@
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.75'}">
<adonisControls:AdonisWindow.Style>
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}" >
<Setter Property="Title" Value="{Binding DataContext.InitialWindowTitle, RelativeSource={RelativeSource Self}}" />
<Setter Property="Title" Value="FModel" />
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext.TitleExtra, RelativeSource={RelativeSource Self}, Converter={x:Static converters:IsNullToBoolReversedConverter.Instance}}" Value="True">
<Setter Property="Title">
<Setter.Value>
<MultiBinding StringFormat="{}{0} - {1} {2}">
<Binding Path="DataContext.InitialWindowTitle" RelativeSource="{RelativeSource Self}" />
<Binding Path="DataContext.GameDisplayName" RelativeSource="{RelativeSource Self}" />
<Binding Path="DataContext.TitleExtra" RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</Setter.Value>
</Setter>
<DataTrigger
Binding="{Binding DataContext.TitleExtra, RelativeSource={RelativeSource Self}, Converter={x:Static converters:IsNullToBoolReversedConverter.Instance}}"
Value="True">
<Setter Property="Title"
Value="{Binding DataContext.TitleExtra, RelativeSource={RelativeSource Self}, StringFormat={}FModel - {0}}" />
</DataTrigger>
</Style.Triggers>
</Style>
@ -56,7 +49,7 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="AES" Command="{Binding MenuCommand}" CommandParameter="Directory_AES" IsEnabled="{Binding Status.IsReady}">
<MenuItem Header="AES" Command="{Binding MenuCommand}" CommandParameter="Directory_AES" IsEnabled="{Binding IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
@ -65,7 +58,7 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Backup" Command="{Binding MenuCommand}" CommandParameter="Directory_Backup" IsEnabled="{Binding Status.IsReady}">
<MenuItem Header="Backup" Command="{Binding MenuCommand}" CommandParameter="Directory_Backup" IsEnabled="{Binding IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
@ -74,18 +67,9 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Archives Info" Command="{Binding MenuCommand}" CommandParameter="Directory_ArchivesInfo" IsEnabled="{Binding Status.IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource InfoIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="Packages">
<MenuItem Header="Search" IsEnabled="{Binding Status.IsReady}" InputGestureText="Ctrl+Shift+F" Click="OnSearchViewClick">
<MenuItem Header="Assets">
<MenuItem Header="Search" IsEnabled="{Binding IsReady}" InputGestureText="Ctrl+Shift+F" Click="OnSearchViewClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
@ -94,7 +78,7 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Favorite Directories" ItemsSource="{Binding CustomDirectories.Directories}" IsEnabled="{Binding Status.IsReady}">
<MenuItem Header="Directories" ItemsSource="{Binding CustomDirectories.Directories}" IsEnabled="{Binding IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
@ -104,19 +88,62 @@
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="Auto Open Sounds" IsCheckable="True" StaysOpenOnClick="True"
IsChecked="{Binding IsAutoOpenSounds, Source={x:Static settings:UserSettings.Default}}" />
</MenuItem>
<MenuItem Header="Views">
<MenuItem Header="3D Viewer" Command="{Binding MenuCommand}" CommandParameter="Views_3dViewer">
<MenuItem Header="Export Data" Command="{Binding ExportDataCommand}" CommandParameter="{Binding SelectedItems, ElementName=AssetsListName}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource MeshIcon}" />
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Property" Command="{Binding SavePropertyCommand}" CommandParameter="{Binding SelectedItems, ElementName=AssetsListName}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Texture" Command="{Binding SaveTextureCommand}" CommandParameter="{Binding SelectedItems, ElementName=AssetsListName}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Auto">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource StatusBarIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem Header="Export Data" IsCheckable="True" StaysOpenOnClick="True"
InputGestureText="{Binding AutoExportData, Source={x:Static local:Settings.UserSettings.Default}}"
IsChecked="{Binding IsAutoExportData, Source={x:Static local:Settings.UserSettings.Default}}" />
<MenuItem Header="Save Properties" IsCheckable="True" StaysOpenOnClick="True"
InputGestureText="{Binding AutoSaveProps, Source={x:Static local:Settings.UserSettings.Default}}"
IsChecked="{Binding IsAutoSaveProps, Source={x:Static local:Settings.UserSettings.Default}}" />
<MenuItem Header="Save Textures" IsCheckable="True" StaysOpenOnClick="True"
InputGestureText="{Binding AutoSaveTextures, Source={x:Static local:Settings.UserSettings.Default}}"
IsChecked="{Binding IsAutoSaveTextures, Source={x:Static local:Settings.UserSettings.Default}}" />
<!--<MenuItem Header="Save Meshes" IsCheckable="True" StaysOpenOnClick="True"
InputGestureText="{Binding AutoSaveMeshes, Source={x:Static local:Settings.UserSettings.Default}}"
IsChecked="{Binding IsAutoSaveMeshes, Source={x:Static local:Settings.UserSettings.Default}}" />-->
<MenuItem Header="Save Materials" IsCheckable="True" StaysOpenOnClick="True"
InputGestureText="{Binding AutoSaveMaterials, Source={x:Static local:Settings.UserSettings.Default}}"
IsChecked="{Binding IsAutoSaveMaterials, Source={x:Static local:Settings.UserSettings.Default}}" />
<MenuItem Header="Open Sounds" IsCheckable="True" StaysOpenOnClick="True"
InputGestureText="{Binding AutoOpenSounds, Source={x:Static local:Settings.UserSettings.Default}}"
IsChecked="{Binding IsAutoOpenSounds, Source={x:Static local:Settings.UserSettings.Default}}" />
</MenuItem>
</MenuItem>
<MenuItem Header="Views">
<MenuItem Header="Audio Player" Command="{Binding MenuCommand}" CommandParameter="Views_AudioPlayer">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
@ -126,6 +153,25 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Map Viewer" Command="{Binding MenuCommand}" CommandParameter="Views_MapViewer" IsEnabled="{Binding IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource MapIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem.Style>
<Style TargetType="MenuItem" BasedOn="{StaticResource {x:Type MenuItem}}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding CUE4Parse.Game}" Value="{x:Static local:FGame.FortniteGame}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
<MenuItem Header="Image Merger" Command="{Binding MenuCommand}" CommandParameter="Views_ImageMerger">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
@ -147,7 +193,7 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Releases" Command="{Binding MenuCommand}" CommandParameter="Help_Releases">
<MenuItem Header="Changelog" Command="{Binding MenuCommand}" CommandParameter="Help_Changelog">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
@ -196,7 +242,7 @@
<GroupBox Grid.Column="0" adonisExtensions:LayerExtension.Layer="2"
Padding="{adonisUi:Space 0}" Background="Transparent">
<TabControl x:Name="LeftTabControl" SelectionChanged="OnTabItemChange">
<TabItem Style="{StaticResource TabItemFillSpace}" Header="Archives">
<TabItem Style="{StaticResource TabItemFillSpace}" Header="Directory">
<DockPanel>
<Grid DockPanel.Dock="Top">
<Grid.RowDefinitions>
@ -211,8 +257,8 @@
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Loading Mode" VerticalAlignment="Center" />
<ComboBox Grid.Row="0" Grid.Column="2" ItemsSource="{Binding LoadingModes.Modes}" IsEnabled="{Binding Status.IsReady}"
SelectedItem="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}, Mode=TwoWay}">
<ComboBox Grid.Row="0" Grid.Column="2" ItemsSource="{Binding LoadingModes.Modes}" IsEnabled="{Binding IsReady}"
SelectedItem="{Binding LoadingMode, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
@ -220,7 +266,7 @@
</ComboBox.ItemTemplate>
</ComboBox>
<Button Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Content="Load"
Command="{Binding LoadingModes.LoadCommand}" IsEnabled="{Binding Status.IsReady}"
Command="{Binding LoadingModes.LoadCommand}" IsEnabled="{Binding IsReady}"
CommandParameter="{Binding SelectedItems, ElementName=DirectoryFilesListBox}" />
</Grid>
<Grid DockPanel.Dock="Top">
@ -231,8 +277,8 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Separator Grid.Row="0" Style="{StaticResource CustomSeparator}" Tag="GAME ARCHIVES" />
<ListBox Grid.Row="1" x:Name="DirectoryFilesListBox" Style="{StaticResource DirectoryFilesListBox}" MouseDoubleClick="OnMouseDoubleClick" />
<Separator Grid.Row="0" Style="{StaticResource CustomSeparator}" Tag="GAME DIRECTORY" />
<ListBox Grid.Row="1" x:Name="DirectoryFilesListBox" Style="{StaticResource DirectoryFilesListBox}" />
<Separator Grid.Row="2" Style="{StaticResource CustomSeparator}" Tag="INFORMATION" />
<StackPanel Grid.Row="3" Orientation="Vertical" Margin="0 0 0 5">
<Grid HorizontalAlignment="Stretch">
@ -261,182 +307,110 @@
</DockPanel>
</TabItem>
<TabItem Style="{StaticResource TabItemFillSpace}" Header="Folders">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DockPanel>
<TextBlock DockPanel.Dock="Top" TextAlignment="Center" TextWrapping="Wrap" HorizontalAlignment="Center" MaxWidth="375"
Text="Open folders to navigate through your loaded files. Badges indicate how many assets are in the folder. To better optimize things, it is recommended to use your hotkeys, to quickly switch between tabs." />
<Grid DockPanel.Dock="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal">
<Image Source="/FModel;component/Resources/label.png"
Width="16" Height="16" HorizontalAlignment="Center" Margin="0 0 3.5 0" />
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" TextTrimming="CharacterEllipsis">
<TextBlock.Text>
<MultiBinding StringFormat="{}'{0}' has {1} folders and {2} packages">
<Binding Path="SelectedItem.Header" ElementName="AssetsFolderName" FallbackValue="None" />
<Binding Path="SelectedItem.FoldersView.Count" ElementName="AssetsFolderName" FallbackValue="0" />
<Binding Path="SelectedItem.AssetsList.Assets.Count" ElementName="AssetsFolderName" FallbackValue="0" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Bring Selected Folder To View" Padding="4"
Command="{Binding MenuCommand}" CommandParameter="{Binding SelectedItem, ElementName=AssetsFolderName}">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource LocateMeIcon}" />
</Canvas>
</Viewbox>
</Button>
<!-- <Button Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Expand All (not appropriate for huge amount of folders)" Padding="4" -->
<!-- Command="{Binding MenuCommand}" CommandParameter="ToolBox_Expand_All"> -->
<!-- <Viewbox Width="16" Height="16" HorizontalAlignment="Center"> -->
<!-- <Canvas Width="24" Height="24"> -->
<!-- <Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource UnfoldIcon}" /> -->
<!-- </Canvas> -->
<!-- </Viewbox> -->
<!-- </Button> -->
<Button Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Collapse All" Padding="4"
Command="{Binding MenuCommand}" CommandParameter="ToolBox_Collapse_All">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource FoldIcon}"/>
</Canvas>
</Viewbox>
</Button>
<Separator Grid.Row="0" Style="{StaticResource CustomSeparator}" />
<TreeView Grid.Row="1" x:Name="AssetsFolderName" Style="{StaticResource AssetsFolderTreeView}" PreviewMouseDoubleClick="OnAssetsTreeMouseDoubleClick">
<TreeView.ContextMenu>
<ContextMenu>
<MenuItem Header="Extract Folder's Assets" Click="OnFolderExtractClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExtractIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Export Folder's Assets Data" Click="OnFolderExportClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Assets Properties" Click="OnFolderSaveClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="Save Directory" Click="OnSaveDirectoryClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource DirectoriesAddIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Copy Directory Path" Click="OnCopyDirectoryPathClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource CopyIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</TreeView.ContextMenu>
</TreeView>
<Separator Grid.Row="2" Style="{StaticResource CustomSeparator}" Tag="INFORMATION" />
<StackPanel Grid.Row="3" Orientation="Vertical" Margin="0 0 0 5">
<Grid HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding SelectedItem.AssetsList.Assets.Count, ElementName=AssetsFolderName, FallbackValue=0}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="Assets Count" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding SelectedItem.FoldersView.Count, ElementName=AssetsFolderName, FallbackValue=0}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="Folders Count" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding SelectedItem.Package, ElementName=AssetsFolderName, FallbackValue='None'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="2" Grid.Column="1" Text="Included In Package" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="{Binding SelectedItem.MountPoint, ElementName=AssetsFolderName, FallbackValue='/', Converter={x:Static converters:TrimRightToLeftConverter.Instance}, ConverterParameter=275}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="3" Grid.Column="1" Text="Package Mount Point" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="{Binding SelectedItem.Version, ElementName=AssetsFolderName, FallbackValue='VER_UE4_LATEST', Converter={x:Static converters:TrimRightToLeftConverter.Instance}, ConverterParameter=275}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="4" Grid.Column="1" Text="Package Version" VerticalAlignment="Center" HorizontalAlignment="Right" />
</Grid>
</StackPanel>
</Grid>
<Separator Grid.Row="1" Style="{StaticResource CustomSeparator}" Margin="0" />
<TreeView Grid.Row="2" x:Name="AssetsFolderName" Style="{StaticResource AssetsFolderTreeView}" PreviewMouseDoubleClick="OnAssetsTreeMouseDoubleClick">
<TreeView.ContextMenu>
<ContextMenu>
<!-- <MenuItem Header="Extract Folder's Packages" Click="OnFolderExtractClick"> -->
<!-- <MenuItem.Icon> -->
<!-- <Viewbox Width="16" Height="16"> -->
<!-- <Canvas Width="24" Height="24"> -->
<!-- <Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExtractIcon}" /> -->
<!-- </Canvas> -->
<!-- </Viewbox> -->
<!-- </MenuItem.Icon> -->
<!-- </MenuItem> -->
<!-- <Separator /> -->
<MenuItem Header="Export Folder's Packages Raw Data (.uasset)" Click="OnFolderExportClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Properties (.json)" Click="OnFolderSaveClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Textures" Click="OnFolderTextureClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Models" Click="OnFolderModelClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Animations" Click="OnFolderAnimationClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="Favorite Directory" Click="OnFavoriteDirectoryClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource DirectoriesAddIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Copy Directory Path" Click="OnCopyDirectoryPathClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource CopyIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</TreeView.ContextMenu>
</TreeView>
<Separator Grid.Row="3" Style="{StaticResource CustomSeparator}" Tag="INFORMATION" />
<StackPanel Grid.Row="4" Orientation="Vertical" Margin="0 0 0 5">
<Grid HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding SelectedItem.AssetsList.Assets.Count, ElementName=AssetsFolderName, FallbackValue=0}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="Packages Count" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding SelectedItem.FoldersView.Count, ElementName=AssetsFolderName, FallbackValue=0}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="Folders Count" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding SelectedItem.Archive, ElementName=AssetsFolderName, FallbackValue='None'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="2" Grid.Column="1" Text="Included In Archive" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="{Binding SelectedItem.MountPoint, ElementName=AssetsFolderName, FallbackValue='/', Converter={x:Static converters:TrimRightToLeftConverter.Instance}, ConverterParameter=275}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="3" Grid.Column="1" Text="Archive Mount Point" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="{Binding SelectedItem.Version, ElementName=AssetsFolderName, FallbackValue='VER_UE4_LATEST', Converter={x:Static converters:TrimRightToLeftConverter.Instance}, ConverterParameter=275}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="4" Grid.Column="1" Text="Archive Version" VerticalAlignment="Center" HorizontalAlignment="Right" />
</Grid>
</StackPanel>
</Grid>
</DockPanel>
</TabItem>
<TabItem Style="{StaticResource TabItemFillSpace}"
Header="{Binding SelectedItem.AssetsList.Assets.Count, FallbackValue=0, ElementName=AssetsFolderName}"
HeaderStringFormat="{}{0} Packages">
HeaderStringFormat="{}{0} Assets">
<DockPanel>
<Grid DockPanel.Dock="Top" ZIndex="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" ZIndex="1" HorizontalAlignment="Left" Margin="5 2 0 0">
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
@ -465,19 +439,12 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<controls:Breadcrumb Grid.Row="0" MaxWidth="{Binding ActualWidth, ElementName=AssetsSearchName}" HorizontalAlignment="Left" Margin="0 5 0 5"
DataContext="{Binding SelectedItem.PathAtThisPoint, ElementName=AssetsFolderName, FallbackValue='No/Directory/Detected/In/Folder'}"/>
<ListBox Grid.Row="1" x:Name="AssetsListName" Style="{StaticResource AssetsListBox}" PreviewMouseDoubleClick="OnAssetsListMouseDoubleClick" PreviewKeyDown="OnPreviewKeyDown">
<Separator Grid.Row="0" Style="{StaticResource CustomSeparator}"
Tag="{Binding SelectedItem.Header, ElementName=AssetsFolderName, FallbackValue='ASSETS LIST', Converter={x:Static converters:FolderToSeparatorTagConverter.Instance}}" />
<ListBox Grid.Row="1" x:Name="AssetsListName" Style="{StaticResource AssetsListBox}" PreviewMouseDoubleClick="OnAssetsListMouseDoubleClick">
<ListBox.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Extract in New Tab" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Extract_New_Tab" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem Header="Extract in New Tab" Command="{Binding DataContext.ExtractNewTabCommand}" CommandParameter="{Binding SelectedItems}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
@ -486,14 +453,7 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="Export Raw Data (.uasset)" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Export_Data" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem Header="Export Data" Command="{Binding DataContext.ExportDataCommand}" CommandParameter="{Binding SelectedItems}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
@ -502,13 +462,7 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Properties (.json)" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Save_Properties" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem Header="Save Properties" Command="{Binding DataContext.SavePropertyCommand}" CommandParameter="{Binding SelectedItems}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
@ -517,13 +471,7 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Texture" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Save_Textures" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem Header="Save Texture" Command="{Binding DataContext.SaveTextureCommand}" CommandParameter="{Binding SelectedItems}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
@ -532,36 +480,6 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Model" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Save_Models" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Animation" Command="{Binding DataContext.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Save_Animations" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="Copy">
<MenuItem.Icon>
@ -571,7 +489,7 @@
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem Header="Package Path" Command="{Binding DataContext.CopyCommand}">
<MenuItem Header="File Path" Command="{Binding DataContext.CopyCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="File_Path" />
@ -579,7 +497,7 @@
</MultiBinding>
</MenuItem.CommandParameter>
</MenuItem>
<MenuItem Header="Package Name" Command="{Binding DataContext.CopyCommand}">
<MenuItem Header="File Name" Command="{Binding DataContext.CopyCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="File_Name" />
@ -595,7 +513,7 @@
</MultiBinding>
</MenuItem.CommandParameter>
</MenuItem>
<MenuItem Header="Package Path w/o Extension" Command="{Binding DataContext.CopyCommand}">
<MenuItem Header="File Path w/o Extension" Command="{Binding DataContext.CopyCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="File_Path_No_Extension" />
@ -603,7 +521,7 @@
</MultiBinding>
</MenuItem.CommandParameter>
</MenuItem>
<MenuItem Header="Package Name w/o Extension" Command="{Binding DataContext.CopyCommand}">
<MenuItem Header="File Name w/o Extension" Command="{Binding DataContext.CopyCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="File_Name_No_Extension" />
@ -629,7 +547,7 @@
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding SelectedItem.Offset, ElementName=AssetsListName, FallbackValue=0, StringFormat='{}0x{0:X}'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="Offset" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding SelectedItem.Size, ElementName=AssetsListName, FallbackValue=0, Converter={x:Static converters:SizeToStringConverter.Instance}}" VerticalAlignment="Center" HorizontalAlignment="Left" />
@ -638,8 +556,8 @@
<TextBlock Grid.Row="2" Grid.Column="1" Text="Compression Method" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="{Binding SelectedItem.IsEncrypted, ElementName=AssetsListName, FallbackValue='False'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="3" Grid.Column="1" Text="Is Encrypted" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="{Binding SelectedItem.Archive, ElementName=AssetsListName, FallbackValue='None'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="4" Grid.Column="1" Text="Included In Archive" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="{Binding SelectedItem.Package, ElementName=AssetsListName, FallbackValue='None'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="4" Grid.Column="1" Text="Included In Package" VerticalAlignment="Center" HorizontalAlignment="Right" />
</Grid>
</StackPanel>
</Grid>
@ -659,17 +577,16 @@
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TabControl Grid.Row="0" x:Name="TabControlName" Style="{StaticResource GameFilesTabControl}" />
<Expander Grid.Row="1" Margin="0 5 0 5" ExpandDirection="Down"
IsExpanded="{Binding IsLoggerExpanded, Source={x:Static settings:UserSettings.Default}}">
<Expander Grid.Row="1" Margin="0 5 0 5" ExpandDirection="Down">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2" />
<ColumnDefinition Width="24" />
</Grid.ColumnDefinitions>
<controls:CustomRichTextBox Grid.Column="0" Grid.ColumnSpan="3" x:Name="LogRtbName" Style="{StaticResource CustomRichTextBox}" />
<StackPanel Grid.Column="2" Orientation="Vertical" VerticalAlignment="Bottom" Margin="-5 -5 5 5">
<Button Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Open Output Folder" Padding="0,4,0,4"
@ -701,31 +618,31 @@
<Style TargetType="{x:Type StatusBar}" BasedOn="{StaticResource {x:Type StatusBar}}">
<Style.Triggers>
<!--don't mind me, MultiDataTrigger just sucks-->
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Ready}">
<DataTrigger Binding="{Binding Status}" Value="{x:Static local:EStatusKind.Ready}">
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Completed}">
<DataTrigger Binding="{Binding Status}" Value="{x:Static local:EStatusKind.Completed}">
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Loading}">
<DataTrigger Binding="{Binding Status}" Value="{x:Static local:EStatusKind.Loading}">
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.AlertBrush}}" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Stopping}">
<DataTrigger Binding="{Binding Status}" Value="{x:Static local:EStatusKind.Stopping}">
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.AlertBrush}}" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Stopped}">
<DataTrigger Binding="{Binding Status}" Value="{x:Static local:EStatusKind.Stopped}">
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Failed}">
<DataTrigger Binding="{Binding Status}" Value="{x:Static local:EStatusKind.Failed}">
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding StatusChangeAttempted, Source={x:Static services:ApplicationService.ThreadWorkerView}}" Value="True">
<DataTrigger Binding="{Binding StatusChangeAttempted, Source={x:Static local:Services.ApplicationService.ThreadWorkerView}}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard Duration="0:0:0.8">
@ -742,7 +659,7 @@
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding OperationCancelled, Source={x:Static services:ApplicationService.ThreadWorkerView}}" Value="True">
<DataTrigger Binding="{Binding OperationCancelled, Source={x:Static local:Services.ApplicationService.ThreadWorkerView}}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard Duration="0:0:1">
@ -764,17 +681,17 @@
<TextBlock>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="{Binding Status.Label}" />
<Setter Property="Text" Value="{Binding Status}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Loading}">
<Setter Property="Text" Value="{Binding Status.Label, StringFormat='{}{0} …'}" />
<DataTrigger Binding="{Binding Status}" Value="{x:Static local:EStatusKind.Loading}">
<Setter Property="Text" Value="{Binding Status, StringFormat='{}{0} …'}" />
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding CanBeCanceled, Source={x:Static services:ApplicationService.ThreadWorkerView}}" Value="True" />
<Condition Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Loading}" />
<Condition Binding="{Binding CanBeCanceled, Source={x:Static local:Services.ApplicationService.ThreadWorkerView}}" Value="True" />
<Condition Binding="{Binding Status}" Value="{x:Static local:EStatusKind.Loading}" />
</MultiDataTrigger.Conditions>
<Setter Property="Text" Value="{Binding Status.Label, StringFormat='{}{0} … ESC to Cancel'}" />
<Setter Property="Text" Value="{Binding Status, StringFormat='{}{0} … ESC to Cancel'}" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
@ -792,22 +709,83 @@
</Viewbox>
</StatusBarItem>
<StatusBarItem Width="30" HorizontalContentAlignment="Stretch" ToolTip="Auto Export Data Enabled">
<StatusBarItem.Style>
<Style TargetType="StatusBarItem">
<Style.Triggers>
<DataTrigger Binding="{Binding IsAutoExportData, Source={x:Static local:Settings.UserSettings.Default}}" Value="False">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</StatusBarItem.Style>
<TextBlock HorizontalAlignment="Center" FontWeight="SemiBold" Text="DTA" />
</StatusBarItem>
<StatusBarItem Width="30" HorizontalContentAlignment="Stretch" ToolTip="Auto Save Properties Enabled">
<StatusBarItem.Style>
<Style TargetType="StatusBarItem">
<Style.Triggers>
<DataTrigger Binding="{Binding IsAutoSaveProps, Source={x:Static local:Settings.UserSettings.Default}}" Value="False">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</StatusBarItem.Style>
<TextBlock HorizontalAlignment="Center" FontWeight="SemiBold" Text="PRP" />
</StatusBarItem>
<StatusBarItem Width="30" HorizontalContentAlignment="Stretch" ToolTip="Auto Save Textures Enabled">
<StatusBarItem.Style>
<Style TargetType="StatusBarItem">
<Style.Triggers>
<DataTrigger Binding="{Binding IsAutoSaveTextures, Source={x:Static local:Settings.UserSettings.Default}}" Value="False">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</StatusBarItem.Style>
<TextBlock HorizontalAlignment="Center" FontWeight="SemiBold" Text="TEX" />
</StatusBarItem>
<StatusBarItem Width="30" HorizontalContentAlignment="Stretch" ToolTip="Auto Save Materials Enabled">
<StatusBarItem.Style>
<Style TargetType="StatusBarItem">
<Style.Triggers>
<DataTrigger Binding="{Binding IsAutoSaveMaterials, Source={x:Static local:Settings.UserSettings.Default}}" Value="False">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</StatusBarItem.Style>
<TextBlock HorizontalAlignment="Center" FontWeight="SemiBold" Text="MAT" />
</StatusBarItem>
<StatusBarItem Width="30" HorizontalContentAlignment="Stretch" ToolTip="Auto Save Meshes Enabled">
<StatusBarItem.Style>
<Style TargetType="StatusBarItem">
<Style.Triggers>
<DataTrigger Binding="{Binding IsAutoSaveMeshes, Source={x:Static local:Settings.UserSettings.Default}}" Value="False">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</StatusBarItem.Style>
<TextBlock HorizontalAlignment="Center" FontWeight="SemiBold" Text="MSH" />
</StatusBarItem>
<StatusBarItem Width="30" HorizontalContentAlignment="Stretch" ToolTip="Auto Open Sounds Enabled">
<StatusBarItem.Style>
<Style TargetType="StatusBarItem">
<Style.Triggers>
<DataTrigger Binding="{Binding IsAutoOpenSounds, Source={x:Static settings:UserSettings.Default}}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
<DataTrigger Binding="{Binding IsAutoOpenSounds, Source={x:Static local:Settings.UserSettings.Default}}" Value="False">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</StatusBarItem.Style>
<TextBlock HorizontalAlignment="Center" FontWeight="SemiBold" Text="SND" />
</StatusBarItem>
<StatusBarItem Margin="10 0 0 0">
<TextBlock Text="{Binding LastUpdateCheck, Source={x:Static local:Settings.UserSettings.Default}, Converter={x:Static converters:RelativeDateTimeConverter.Instance}, StringFormat=Last Refresh: {0}}" />
</StatusBarItem>
</StackPanel>
</StatusBarItem>
</StatusBar>

View File

@ -1,7 +1,6 @@
using System;
using System;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
@ -14,269 +13,225 @@ using FModel.Views;
using FModel.Views.Resources.Controls;
using ICSharpCode.AvalonEdit.Editing;
namespace FModel;
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow
namespace FModel
{
public static MainWindow YesWeCats;
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private DiscordHandler _discordHandler => DiscordService.DiscordHandler;
public MainWindow()
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow
{
CommandBindings.Add(new CommandBinding(new RoutedCommand("ReloadMappings", typeof(MainWindow), new InputGestureCollection { new KeyGesture(Key.F12) }), OnMappingsReload));
CommandBindings.Add(new CommandBinding(ApplicationCommands.Find, (_, _) => OnOpenAvalonFinder()));
public static MainWindow YesWeCats;
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private DiscordHandler _discordHandler => DiscordService.DiscordHandler;
DataContext = _applicationView;
InitializeComponent();
FLogger.Logger = LogRtbName;
YesWeCats = this;
}
private void OnClosing(object sender, CancelEventArgs e)
{
_discordHandler.Dispose();
}
private async void OnLoaded(object sender, RoutedEventArgs e)
{
var newOrUpdated = UserSettings.Default.ShowChangelog;
#if !DEBUG
ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(true);
#endif
switch (UserSettings.Default.AesReload)
public MainWindow()
{
case EAesReload.Always:
await _applicationView.CUE4Parse.RefreshAes();
break;
case EAesReload.OncePerDay when UserSettings.Default.CurrentDir.LastAesReload != DateTime.Today:
UserSettings.Default.CurrentDir.LastAesReload = DateTime.Today;
await _applicationView.CUE4Parse.RefreshAes();
break;
CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoExportData", typeof(MainWindow), new InputGestureCollection
{new KeyGesture(UserSettings.Default.AutoExportData.Key, UserSettings.Default.AutoExportData.Modifiers)}), OnAutoTriggerExecuted));
CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoSaveProps", typeof(MainWindow), new InputGestureCollection
{new KeyGesture(UserSettings.Default.AutoSaveProps.Key, UserSettings.Default.AutoSaveProps.Modifiers)}), OnAutoTriggerExecuted));
CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoSaveTextures", typeof(MainWindow), new InputGestureCollection
{new KeyGesture(UserSettings.Default.AutoSaveTextures.Key, UserSettings.Default.AutoSaveTextures.Modifiers)}), OnAutoTriggerExecuted));
CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoSaveMaterials", typeof(MainWindow), new InputGestureCollection
{new KeyGesture(UserSettings.Default.AutoSaveMaterials.Key, UserSettings.Default.AutoSaveMaterials.Modifiers)}), OnAutoTriggerExecuted));
CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoSaveMeshes", typeof(MainWindow), new InputGestureCollection
{new KeyGesture(UserSettings.Default.AutoSaveMeshes.Key, UserSettings.Default.AutoSaveMeshes.Modifiers)}), OnAutoTriggerExecuted));
CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoOpenSounds", typeof(MainWindow), new InputGestureCollection
{new KeyGesture(UserSettings.Default.AutoOpenSounds.Key, UserSettings.Default.AutoOpenSounds.Modifiers)}), OnAutoTriggerExecuted));
CommandBindings.Add(new CommandBinding(new RoutedCommand("ReloadMappings", typeof(MainWindow), new InputGestureCollection {new KeyGesture(Key.F12)}), OnMappingsReload));
CommandBindings.Add(new CommandBinding(ApplicationCommands.Find, (s, e) => OnOpenAvalonFinder()));
DataContext = _applicationView;
InitializeComponent();
FLogger.Logger = LogRtbName;
YesWeCats = this;
}
await ApplicationViewModel.InitOodle();
await ApplicationViewModel.InitZlib();
await _applicationView.CUE4Parse.Initialize();
await _applicationView.AesManager.InitAes();
await _applicationView.UpdateProvider(true);
#if !DEBUG
await _applicationView.CUE4Parse.InitInformation();
#endif
await Task.WhenAll(
_applicationView.CUE4Parse.VerifyConsoleVariables(),
_applicationView.CUE4Parse.VerifyOnDemandArchives(),
_applicationView.CUE4Parse.InitMappings(),
ApplicationViewModel.InitVgmStream(),
ApplicationViewModel.InitImGuiSettings(newOrUpdated),
Task.Run(() =>
private void OnClosing(object sender, CancelEventArgs e)
{
_applicationView.CustomDirectories.Save();
_discordHandler.Dispose();
}
private async void OnLoaded(object sender, RoutedEventArgs e)
{
ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode);
switch (UserSettings.Default.AesReload)
{
if (UserSettings.Default.DiscordRpc == EDiscordRpc.Always)
_discordHandler.Initialize(_applicationView.GameDisplayName);
})
).ConfigureAwait(false);
case EAesReload.Always:
await _applicationView.CUE4Parse.RefreshAes();
break;
case EAesReload.OncePerDay when UserSettings.Default.LastAesReload != DateTime.Today:
UserSettings.Default.LastAesReload = DateTime.Today;
await _applicationView.CUE4Parse.RefreshAes();
break;
}
#if DEBUG
// await _threadWorkerView.Begin(cancellationToken =>
// _applicationView.CUE4Parse.Extract(cancellationToken,
// "FortniteGame/Content/Athena/Apollo/Maps/UI/Apollo_Terrain_Minimap.uasset"));
// await _threadWorkerView.Begin(cancellationToken =>
// _applicationView.CUE4Parse.Extract(cancellationToken,
// "FortniteGame/Content/Environments/Helios/Props/GlacierHotel/GlacierHotel_Globe_A/Meshes/SM_GlacierHotel_Globe_A.uasset"));
#endif
}
await _applicationView.CUE4Parse.InitInformation();
await _applicationView.CUE4Parse.Initialize();
await _applicationView.AesManager.InitAes();
await _applicationView.AesManager.UpdateProvider(true);
await _applicationView.CUE4Parse.InitBenMappings();
await _applicationView.InitVgmStream();
private void OnGridSplitterDoubleClick(object sender, MouseButtonEventArgs e)
{
RootGrid.ColumnDefinitions[0].Width = GridLength.Auto;
}
private void OnWindowKeyDown(object sender, KeyEventArgs e)
{
if (e.OriginalSource is TextBox || e.OriginalSource is TextArea && Keyboard.Modifiers.HasFlag(ModifierKeys.Control))
return;
if (_threadWorkerView.CanBeCanceled && e.Key == Key.Escape)
{
_applicationView.Status.SetStatus(EStatusKind.Stopping);
_threadWorkerView.Cancel();
if (UserSettings.Default.DiscordRpc == EDiscordRpc.Always)
_discordHandler.Initialize(_applicationView.CUE4Parse.Game);
}
else if (_applicationView.Status.IsReady && e.Key == Key.F && Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
OnSearchViewClick(null, null);
else if (e.Key == Key.Left && _applicationView.CUE4Parse.TabControl.SelectedTab.HasImage)
_applicationView.CUE4Parse.TabControl.SelectedTab.GoPreviousImage();
else if (e.Key == Key.Right && _applicationView.CUE4Parse.TabControl.SelectedTab.HasImage)
_applicationView.CUE4Parse.TabControl.SelectedTab.GoNextImage();
else if (UserSettings.Default.AssetAddTab.IsTriggered(e.Key))
_applicationView.CUE4Parse.TabControl.AddTab();
else if (UserSettings.Default.AssetRemoveTab.IsTriggered(e.Key))
_applicationView.CUE4Parse.TabControl.RemoveTab();
else if (UserSettings.Default.AssetLeftTab.IsTriggered(e.Key))
_applicationView.CUE4Parse.TabControl.GoLeftTab();
else if (UserSettings.Default.AssetRightTab.IsTriggered(e.Key))
_applicationView.CUE4Parse.TabControl.GoRightTab();
else if (UserSettings.Default.DirLeftTab.IsTriggered(e.Key) && LeftTabControl.SelectedIndex > 0)
LeftTabControl.SelectedIndex--;
else if (UserSettings.Default.DirRightTab.IsTriggered(e.Key) && LeftTabControl.SelectedIndex < LeftTabControl.Items.Count - 1)
private void OnGridSplitterDoubleClick(object sender, MouseButtonEventArgs e)
{
RootGrid.ColumnDefinitions[0].Width = GridLength.Auto;
}
private void OnWindowKeyDown(object sender, KeyEventArgs e)
{
if (e.OriginalSource is TextArea or TextBox)
return;
if (_threadWorkerView.CanBeCanceled && e.Key == Key.Escape)
{
_applicationView.Status = EStatusKind.Stopping;
_threadWorkerView.Cancel();
}
else if (_applicationView.IsReady && e.Key == Key.F && Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
OnSearchViewClick(null, null);
else if (UserSettings.Default.AssetAddTab.IsTriggered(e.Key))
_applicationView.CUE4Parse.TabControl.AddTab();
else if (UserSettings.Default.AssetRemoveTab.IsTriggered(e.Key))
_applicationView.CUE4Parse.TabControl.RemoveTab();
else if (UserSettings.Default.AssetLeftTab.IsTriggered(e.Key))
_applicationView.CUE4Parse.TabControl.GoLeftTab();
else if (UserSettings.Default.AssetRightTab.IsTriggered(e.Key))
_applicationView.CUE4Parse.TabControl.GoRightTab();
else if (UserSettings.Default.DirLeftTab.IsTriggered(e.Key) && LeftTabControl.SelectedIndex > 0)
LeftTabControl.SelectedIndex--;
else if (UserSettings.Default.DirRightTab.IsTriggered(e.Key) && LeftTabControl.SelectedIndex < LeftTabControl.Items.Count - 1)
LeftTabControl.SelectedIndex++;
}
private void OnSearchViewClick(object sender, RoutedEventArgs e)
{
Helper.OpenWindow<AdonisWindow>("Search View", () => new SearchView().Show());
}
private void OnTabItemChange(object sender, SelectionChangedEventArgs e)
{
if (e.OriginalSource is not TabControl tabControl)
return;
(tabControl.SelectedItem as System.Windows.Controls.TabItem)?.Focus();
}
private async void OnMappingsReload(object sender, ExecutedRoutedEventArgs e)
{
await _applicationView.CUE4Parse.InitBenMappings();
}
private void OnAutoTriggerExecuted(object sender, ExecutedRoutedEventArgs e)
{
switch ((e.Command as RoutedCommand)?.Name)
{
case "AutoExportData":
UserSettings.Default.IsAutoExportData = !UserSettings.Default.IsAutoExportData;
break;
case "AutoSaveProps":
UserSettings.Default.IsAutoSaveProps = !UserSettings.Default.IsAutoSaveProps;
break;
case "AutoSaveTextures":
UserSettings.Default.IsAutoSaveTextures = !UserSettings.Default.IsAutoSaveTextures;
break;
case "AutoSaveMaterials":
UserSettings.Default.IsAutoSaveMaterials = !UserSettings.Default.IsAutoSaveMaterials;
break;
case "AutoSaveMeshes":
UserSettings.Default.IsAutoSaveMeshes = !UserSettings.Default.IsAutoSaveMeshes;
break;
case "AutoOpenSounds":
UserSettings.Default.IsAutoOpenSounds = !UserSettings.Default.IsAutoOpenSounds;
break;
}
}
private void OnOpenAvalonFinder()
{
_applicationView.CUE4Parse.TabControl.SelectedTab.HasSearchOpen = true;
AvalonEditor.YesWeSearch.Focus();
AvalonEditor.YesWeSearch.SelectAll();
}
private void OnAssetsTreeMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (sender is not TreeView {SelectedItem: TreeItem treeItem} || treeItem.Folders.Count > 0) return;
LeftTabControl.SelectedIndex++;
}
private void OnSearchViewClick(object sender, RoutedEventArgs e)
{
Helper.OpenWindow<AdonisWindow>("Search View", () => new SearchView().Show());
}
private void OnTabItemChange(object sender, SelectionChangedEventArgs e)
{
if (e.OriginalSource is not TabControl tabControl)
return;
(tabControl.SelectedItem as System.Windows.Controls.TabItem)?.Focus();
}
private async void OnMappingsReload(object sender, ExecutedRoutedEventArgs e)
{
await _applicationView.CUE4Parse.InitMappings(true);
}
private void OnOpenAvalonFinder()
{
_applicationView.CUE4Parse.TabControl.SelectedTab.HasSearchOpen = true;
AvalonEditor.YesWeSearch.Focus();
AvalonEditor.YesWeSearch.SelectAll();
}
private void OnAssetsTreeMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (sender is not TreeView { SelectedItem: TreeItem treeItem } || treeItem.Folders.Count > 0) return;
LeftTabControl.SelectedIndex++;
}
private async void OnAssetsListMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (sender is not ListBox listBox) return;
var selectedItems = listBox.SelectedItems.Cast<AssetItem>().ToList();
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
}
private async void OnFolderExtractClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is TreeItem folder)
{
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractFolder(cancellationToken, folder); });
}
}
private async void OnFolderExportClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is TreeItem folder)
private async void OnAssetsListMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExportFolder(cancellationToken, folder); });
FLogger.Append(ELog.Information, () =>
if (sender is not ListBox listBox) return;
var selectedItems = listBox.SelectedItems.Cast<AssetItem>().ToList();
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
}
private async void OnFolderExtractClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is TreeItem folder)
{
FLogger.Text("Successfully exported ", Constants.WHITE);
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.RawDataDirectory, true);
});
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractFolder(cancellationToken, folder); });
}
}
}
private async void OnFolderSaveClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is TreeItem folder)
private async void OnFolderExportClick(object sender, RoutedEventArgs e)
{
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.SaveFolder(cancellationToken, folder); });
FLogger.Append(ELog.Information, () =>
if (AssetsFolderName.SelectedItem is TreeItem folder)
{
FLogger.Text("Successfully saved ", Constants.WHITE);
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.PropertiesDirectory, true);
});
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExportFolder(cancellationToken, folder); });
}
}
}
private async void OnFolderTextureClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is TreeItem folder)
private async void OnFolderSaveClick(object sender, RoutedEventArgs e)
{
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.TextureFolder(cancellationToken, folder); });
FLogger.Append(ELog.Information, () =>
if (AssetsFolderName.SelectedItem is TreeItem folder)
{
FLogger.Text("Successfully saved ", Constants.WHITE);
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.TextureDirectory, true);
});
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.SaveFolder(cancellationToken, folder); });
}
}
}
private async void OnFolderModelClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is TreeItem folder)
private void OnSaveDirectoryClick(object sender, RoutedEventArgs e)
{
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ModelFolder(cancellationToken, folder); });
}
}
if (AssetsFolderName.SelectedItem is not TreeItem folder) return;
private async void OnFolderAnimationClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is TreeItem folder)
_applicationView.CustomDirectories.Add(new CustomDirectory(folder.Header, folder.PathAtThisPoint));
FLogger.AppendInformation();
FLogger.AppendText($"Successfully saved '{folder.PathAtThisPoint}' as a new custom directory", Constants.WHITE, true);
}
private void OnCopyDirectoryPathClick(object sender, RoutedEventArgs e)
{
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.AnimationFolder(cancellationToken, folder); });
if (AssetsFolderName.SelectedItem is not TreeItem folder) return;
Clipboard.SetText(folder.PathAtThisPoint);
}
}
private void OnFavoriteDirectoryClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is not TreeItem folder) return;
_applicationView.CustomDirectories.Add(new CustomDirectory(folder.Header, folder.PathAtThisPoint));
FLogger.Append(ELog.Information, () =>
FLogger.Text($"Successfully saved '{folder.PathAtThisPoint}' as a new favorite directory", Constants.WHITE, true));
}
private void OnCopyDirectoryPathClick(object sender, RoutedEventArgs e)
{
if (AssetsFolderName.SelectedItem is not TreeItem folder) return;
Clipboard.SetText(folder.PathAtThisPoint);
}
private void OnDeleteSearchClick(object sender, RoutedEventArgs e)
{
AssetsSearchName.Text = string.Empty;
AssetsListName.ScrollIntoView(AssetsListName.SelectedItem);
}
private void OnFilterTextChanged(object sender, TextChangedEventArgs e)
{
if (sender is not TextBox textBox || AssetsFolderName.SelectedItem is not TreeItem folder)
return;
var filters = textBox.Text.Trim().Split(' ');
folder.AssetsList.AssetsView.Filter = o => { return o is AssetItem assetItem && filters.All(x => assetItem.FullPath.SubstringAfterLast('/').Contains(x, StringComparison.OrdinalIgnoreCase)); };
}
private void OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (!_applicationView.Status.IsReady || sender is not ListBox listBox) return;
UserSettings.Default.LoadingMode = ELoadingMode.Multiple;
_applicationView.LoadingModes.LoadCommand.Execute(listBox.SelectedItems);
}
private async void OnPreviewKeyDown(object sender, KeyEventArgs e)
{
if (!_applicationView.Status.IsReady || sender is not ListBox listBox) return;
switch (e.Key)
private void OnDeleteSearchClick(object sender, RoutedEventArgs e)
{
case Key.Enter:
var selectedItems = listBox.SelectedItems.Cast<AssetItem>().ToList();
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
break;
AssetsSearchName.Text = string.Empty;
AssetsListName.ScrollIntoView(AssetsListName.SelectedItem);
}
private void OnFilterTextChanged(object sender, TextChangedEventArgs e)
{
if (sender is not TextBox textBox || AssetsFolderName.SelectedItem is not TreeItem folder)
return;
var filters = textBox.Text.Trim().Split(' ');
folder.AssetsList.AssetsView.Filter = o =>
{
return o is AssetItem assetItem && filters.All(x => assetItem.FullPath.SubstringAfterLast('/').Contains(x, StringComparison.OrdinalIgnoreCase));
};
}
}
}
}

View File

@ -1,39 +0,0 @@
<SyntaxDefinition name="Changelog" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008">
<RuleSet name="diff">
<Span multiline="false" foreground="#98C379">
<Begin>^\+</Begin>
<End>.*(?:\t|\s{2,})+</End>
</Span>
<Span multiline="false" foreground="#E06C75">
<Begin>^\-</Begin>
<End>.*(?:\t|\s{2,})+</End>
</Span>
<Span multiline="false" foreground="#61AFEF">
<Begin>^\~</Begin>
<End>.*(?:\t|\s{2,})+</End>
</Span>
</RuleSet>
<RuleSet name="doc" ignoreCase="false">
<Span multiline="false" foreground="#7F848E">
<Begin>.*(?:\t|\#{1}|\s{2,})+</Begin>
<End>\r\n</End>
</Span>
<Span multiline="false" underline="true">
<Begin>^[0-9]\..*</Begin>
</Span>
<Keywords underline="true">
<Word>ADDED</Word>
<Word>FIXED</Word>
<Word>REMOVED</Word>
<Word>IMPROVED</Word>
</Keywords>
</RuleSet>
<RuleSet>
<Import ruleSet="diff" />
<Import ruleSet="doc" />
</RuleSet>
</SyntaxDefinition>

View File

@ -1,25 +0,0 @@
<SyntaxDefinition name="Verse Visual Process Language" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008"
extensions=".verse">
<Color name="Constant" foreground="#C792DD" />
<Color name="Method" foreground="#FFCB6B" />
<Color name="Variable" foreground="#529EFF" />
<Color name="Tag1" foreground="#F07178" />
<Color name="Tag2" foreground="#7F848E" />
<Color name="Parameter" foreground="#89DDFF" />
<Color name="Class" foreground="#FF9D5E" />
<Color name="Type" foreground="#C3E88D" />
<Color name="Punctuation" foreground="#E5C07B" />
<RuleSet>
<Rule color="Constant">\b(?:module|class|attribute|interface|struct|external|enum|where|comparable|component|void|type|subtype)|\@\w+</Rule>
<Rule color="Method">\w+(?:\'.*\')?(?=(?:&lt;\w+&gt;)+\()</Rule>
<Rule color="Variable">\w+(?:\'.*\')?(?=(?:&lt;\w+&gt;)+\^?:)</Rule>
<Rule color="Tag1">&lt;\w+&gt;</Rule>
<Rule color="Tag2">(?:^using|\# ).*</Rule>
<Rule color="Parameter">(?!\()\w+(?=:)</Rule>
<Rule color="Class">\w+(?=(?:&lt;\w+&gt;)+\s:=)</Rule>
<Rule color="Type">(?&lt;=:)[\w\[\]\?]+</Rule>
<Rule color="Punctuation">[()*+,\-.\/:;&lt;=&gt;?[\]^`{|}~]</Rule>
</RuleSet>
</SyntaxDefinition>

View File

@ -1,11 +0,0 @@
#version 460 core
in vec3 fPos;
in vec3 fColor;
out vec4 FragColor;
void main()
{
FragColor = vec4(fColor, 1.0);
}

View File

@ -1,19 +0,0 @@
#version 460 core
layout (location = 0) in vec3 vPos;
layout (location = 1) in vec3 vColor;
uniform mat4 uView;
uniform mat4 uProjection;
uniform mat4 uInstanceMatrix;
out vec3 fPos;
out vec3 fColor;
void main()
{
gl_PointSize = 7.5f;
gl_Position = uProjection * uView * uInstanceMatrix * vec4(vPos, 1.0);
fPos = vec3(uInstanceMatrix * vec4(vPos, 1.0));
fColor = vColor;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -1,20 +0,0 @@
#version 460 core
layout (location = 0) in vec3 vPos;
uniform mat4 uView;
uniform mat4 uProjection;
uniform mat4 uInstanceMatrix;
uniform mat4 uCollisionMatrix;
uniform float uScaleDown;
out vec3 fPos;
out vec3 fColor;
void main()
{
gl_PointSize = 7.5f;
gl_Position = uProjection * uView * uInstanceMatrix * uCollisionMatrix * vec4(vPos.xzy * uScaleDown, 1.0);
fPos = vec3(uInstanceMatrix * uCollisionMatrix * vec4(vPos.xzy * uScaleDown, 1.0));
fColor = vec3(1.0);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 831 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,294 +0,0 @@
#version 460 core
#define PI 3.1415926535897932384626433832795
#define MAX_UV_COUNT 8
#define MAX_LIGHT_COUNT 100
in vec3 fPos;
in vec3 fNormal;
in vec3 fTangent;
in vec2 fTexCoords;
flat in int fTexLayer;
in vec4 fColor;
struct Texture
{
sampler2D Sampler;
vec4 Color;
};
struct Boost
{
vec3 Color;
float Exponent;
};
struct AoParams
{
sampler2D Sampler;
float AmbientOcclusion;
Boost ColorBoost;
bool HasColorBoost;
};
struct Parameters
{
Texture Diffuse[MAX_UV_COUNT];
Texture Normals[MAX_UV_COUNT];
Texture SpecularMasks[MAX_UV_COUNT];
Texture Emissive[MAX_UV_COUNT];
AoParams Ao;
bool HasAo;
vec4 EmissiveRegion;
float RoughnessMin;
float RoughnessMax;
float EmissiveMult;
};
struct BaseLight
{
vec4 Color;
vec3 Position;
float Intensity;
};
//struct PointLight
//{
// BaseLight Light;
//
// float Linear;
// float Quadratic;
//};
//
//struct SpotLight
//{
// BaseLight Light;
//
// float InnerConeAngle;
// float OuterConeAngle;
// float Attenuation;
//};
struct Light {
BaseLight Base;
float InnerConeAngle;
float OuterConeAngle;
float Attenuation;
float Linear;
float Quadratic;
int Type; // 0 Point, 1 Spot
};
uniform Parameters uParameters;
uniform Light uLights[MAX_LIGHT_COUNT];
uniform int uNumLights;
uniform int uUvCount;
uniform float uOpacity;
uniform bool uHasVertexColors;
uniform vec3 uSectionColor;
uniform bool bVertexColors[6];
uniform vec3 uViewPos;
out vec4 FragColor;
int LayerToIndex()
{
return clamp(fTexLayer, 0, uUvCount - 1);
}
vec4 SamplerToVector(sampler2D s, vec2 coords)
{
return texture(s, coords);
}
vec4 SamplerToVector(sampler2D s)
{
return SamplerToVector(s, fTexCoords);
}
vec3 ComputeNormals(int layer)
{
vec3 normal = SamplerToVector(uParameters.Normals[layer].Sampler).rgb * 2.0 - 1.0;
vec3 t = normalize(fTangent);
vec3 n = normalize(fNormal);
vec3 b = -normalize(cross(n, t));
mat3 tbn = mat3(t, b, n);
return normalize(tbn * normal);
}
vec3 schlickFresnel(vec3 fLambert, float metallic, float hDotv)
{
vec3 f0 = vec3(0.04);
f0 = mix(f0, fLambert, metallic);
return f0 + (1.0 - f0) * pow(clamp(1.0 - hDotv, 0.0, 1.0), 5);
}
float ggxDistribution(float roughness, float nDoth)
{
float alpha2 = roughness * roughness * roughness * roughness;
float d = nDoth * nDoth * (alpha2- 1.0) + 1.0;
return alpha2 / (PI * d * d);
}
float geomSmith(float roughness, float dp)
{
float k = (roughness + 1.0) * (roughness + 1.0) / 8.0;
float denom = dp * (1.0 - k) + k;
return dp / denom;
}
vec3 CalcLight(int layer, vec3 normals, vec3 position, vec3 color, float attenuation, bool global)
{
vec3 fLambert = SamplerToVector(uParameters.Diffuse[layer].Sampler).rgb * uParameters.Diffuse[layer].Color.rgb;
vec3 specular_masks = SamplerToVector(uParameters.SpecularMasks[layer].Sampler).rgb;
float cavity = specular_masks.g;
if (uParameters.HasAo)
{
cavity = SamplerToVector(uParameters.Ao.Sampler).g;
}
float roughness = mix(uParameters.RoughnessMin, uParameters.RoughnessMax, specular_masks.b);
vec3 l = normalize(uViewPos - fPos);
vec3 n = normals;
vec3 v = normalize(position - fPos);
vec3 h = normalize(v + l);
float nDotH = max(dot(n, h), 0.0);
float hDotv = max(dot(h, v), 0.0);
float nDotL = max(dot(n, l), 0.0);
float nDotV = max(dot(n, v), 0.0);
vec3 f = schlickFresnel(fLambert, specular_masks.g, hDotv);
vec3 kS = f;
vec3 kD = 1.0 - kS;
kD *= 1.0 - cavity;
vec3 specBrdfNom = ggxDistribution(roughness, nDotH) * geomSmith(roughness, nDotL) * geomSmith(roughness, nDotV) * f;
float specBrdfDenom = 4.0 * nDotV * nDotL + 0.0001;
vec3 specBrdf = specBrdfNom / specBrdfDenom;
vec3 diffuseBrdf = fLambert;
if (!global) diffuseBrdf = kD * fLambert / PI;
return (diffuseBrdf + specBrdf) * color * attenuation * nDotL;
}
vec3 CalcBaseLight(int layer, vec3 normals, BaseLight base, float attenuation, bool global)
{
return CalcLight(layer, normals, base.Position, base.Color.rgb * base.Intensity, attenuation, global);
}
vec3 CalcPointLight(int layer, vec3 normals, Light light)
{
float distanceToLight = length(light.Base.Position - fPos);
float attenuation = 1.0 / (1.0 + light.Linear * distanceToLight + light.Quadratic * pow(distanceToLight, 2));
return CalcBaseLight(layer, normals, light.Base, attenuation, true);
}
vec3 CalcSpotLight(int layer, vec3 normals, Light light)
{
vec3 v = normalize(light.Base.Position - fPos);
float inner = cos(radians(light.InnerConeAngle));
float outer = cos(radians(light.OuterConeAngle));
float distanceToLight = length(light.Base.Position - fPos);
float theta = dot(v, normalize(-vec3(0, -1, 0)));
float epsilon = inner - outer;
float attenuation = 1.0 / (1.0 + light.Attenuation * pow(distanceToLight, 2));
light.Base.Intensity *= smoothstep(0.0, 1.0, (theta - outer) / epsilon);
if(theta > outer)
{
return CalcBaseLight(layer, normals, light.Base, attenuation, true);
}
else
{
return vec3(0.0);
}
}
void main()
{
int layer = LayerToIndex();
vec3 normals = ComputeNormals(layer);
vec3 lightDir = normalize(uViewPos - fPos);
float diffuseFactor = max(dot(normals, lightDir), 0.4);
if (bVertexColors[1])
{
FragColor = vec4(diffuseFactor * uSectionColor, uOpacity);
}
else if (bVertexColors[2] && uHasVertexColors)
{
FragColor = vec4(diffuseFactor * fColor.rgb, fColor.a);
}
else if (bVertexColors[3])
{
FragColor = vec4(normals, uOpacity);
}
else if (bVertexColors[4])
{
vec4 diffuse = SamplerToVector(uParameters.Diffuse[0].Sampler);
FragColor = vec4(diffuseFactor * diffuse.rgb, diffuse.a);
}
else
{
vec4 diffuse = SamplerToVector(uParameters.Diffuse[layer].Sampler);
vec3 result = diffuseFactor * diffuse.rgb * uParameters.Diffuse[layer].Color.rgb;
if (uParameters.HasAo)
{
vec3 m = SamplerToVector(uParameters.Ao.Sampler).rgb;
if (uParameters.Ao.HasColorBoost)
{
vec3 color = uParameters.Ao.ColorBoost.Color * uParameters.Ao.ColorBoost.Exponent;
result = mix(result, result * color, m.b);
}
result *= m.r;
}
vec2 coords = fTexCoords;
if (coords.x > uParameters.EmissiveRegion.x &&
coords.y > uParameters.EmissiveRegion.y &&
coords.x < uParameters.EmissiveRegion.z &&
coords.y < uParameters.EmissiveRegion.w)
{
coords.x -= uParameters.EmissiveRegion.x;
coords.y -= uParameters.EmissiveRegion.y;
coords.x *= 1.0 / (uParameters.EmissiveRegion.z - uParameters.EmissiveRegion.x);
coords.y *= 1.0 / (uParameters.EmissiveRegion.w - uParameters.EmissiveRegion.y);
vec4 emissive = SamplerToVector(uParameters.Emissive[layer].Sampler, coords);
result += uParameters.Emissive[layer].Color.rgb * emissive.rgb * uParameters.EmissiveMult;
}
{
result += CalcLight(layer, normals, uViewPos, vec3(1.0), 1.0, false);
vec3 lights = vec3(uNumLights > 0 ? 0 : 1);
for (int i = 0; i < uNumLights; i++)
{
if (uLights[i].Type == 0)
{
lights += CalcPointLight(layer, normals, uLights[i]);
}
else if (uLights[i].Type == 1)
{
lights += CalcSpotLight(layer, normals, uLights[i]);
}
}
result *= lights; // use * to darken the scene, + to lighten it
}
result = result / (result + vec3(1.0));
FragColor = vec4(pow(result, vec3(1.0 / 2.2)), uOpacity);
}
}

View File

@ -1,99 +0,0 @@
#version 460 core
layout (location = 1) in vec3 vPos;
layout (location = 2) in vec3 vNormal;
layout (location = 3) in vec3 vTangent;
layout (location = 4) in vec2 vTexCoords;
layout (location = 5) in int vTexLayer;
layout (location = 6) in float vColor;
layout (location = 7) in vec4 vBoneInfluence;
layout (location = 8) in vec4 vBoneInfluenceExtra;
layout (location = 9) in mat4 vInstanceMatrix;
layout (location = 13) in vec3 vMorphTargetPos;
layout (location = 14) in vec3 vMorphTargetTangent;
layout(std430, binding = 1) buffer BoneMatrices
{
mat4 uFinalBonesMatrix[];
};
layout(std430, binding = 2) buffer RestBoneMatrices
{
mat4 uRestBonesMatrix[];
};
uniform mat4 uView;
uniform mat4 uProjection;
uniform float uMorphTime;
uniform bool uIsAnimated;
out vec3 fPos;
out vec3 fNormal;
out vec3 fTangent;
out vec2 fTexCoords;
flat out int fTexLayer;
out vec4 fColor;
vec4 unpackARGB(int color)
{
float a = float((color >> 24) & 0xFF);
float r = float((color >> 16) & 0xFF);
float g = float((color >> 8) & 0xFF);
float b = float((color >> 0) & 0xFF);
return vec4(r, g, b, a);
}
vec2 unpackBoneIDsAndWeights(int packedData)
{
return vec2(float((packedData >> 16) & 0xFFFF), float(packedData & 0xFFFF));
}
void main()
{
vec4 bindPos = vec4(mix(vPos, vMorphTargetPos, uMorphTime), 1.0);
vec4 bindNormal = vec4(vNormal, 1.0);
vec4 bindTangent = vec4(mix(vTangent, vMorphTargetTangent, uMorphTime), 1.0);
vec4 finalPos = vec4(0.0);
vec4 finalNormal = vec4(0.0);
vec4 finalTangent = vec4(0.0);
if (uIsAnimated)
{
vec4 boneInfluences[2];
boneInfluences[0] = vBoneInfluence;
boneInfluences[1] = vBoneInfluenceExtra;
for (int i = 0; i < 2; i++)
{
for(int j = 0 ; j < 4; j++)
{
vec2 boneInfluence = unpackBoneIDsAndWeights(int(boneInfluences[i][j]));
int boneIndex = int(boneInfluence.x);
float weight = boneInfluence.y;
mat4 boneMatrix = uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]);
mat4 inverseBoneMatrix = transpose(inverse(boneMatrix));
finalPos += boneMatrix * bindPos * weight;
finalNormal += inverseBoneMatrix * bindNormal * weight;
finalTangent += inverseBoneMatrix * bindTangent * weight;
}
}
finalPos = normalize(finalPos);
finalNormal = normalize(finalNormal);
finalTangent = normalize(finalTangent);
}
else
{
finalPos = bindPos;
finalNormal = bindNormal;
finalTangent = bindTangent;
}
gl_Position = uProjection * uView * vInstanceMatrix * finalPos;
fPos = vec3(vInstanceMatrix * finalPos);
fNormal = vec3(transpose(inverse(vInstanceMatrix)) * finalNormal);
fTangent = vec3(transpose(inverse(vInstanceMatrix)) * finalTangent);
fTexCoords = vTexCoords;
fTexLayer = vTexLayer;
fColor = unpackARGB(int(vColor)) / 255.0;
}

View File

@ -1,12 +0,0 @@
#version 460 core
in vec2 fTexCoords;
uniform sampler2D screenTexture;
out vec4 FragColor;
void main()
{
FragColor = texture(screenTexture, fTexCoords);
}

View File

@ -1,12 +0,0 @@
#version 460 core
layout (location = 0) in vec2 vPos;
layout (location = 1) in vec2 vTexCoords;
out vec2 fTexCoords;
void main()
{
gl_Position = vec4(vPos.x, vPos.y, 0.0, 1.0);
fTexCoords = vTexCoords;
}

View File

@ -1,63 +0,0 @@
#version 460 core
// --------------------- IN ---------------------
in OUT_IN_VARIABLES {
vec3 nearPoint;
vec3 farPoint;
mat4 proj;
mat4 view;
float near;
float far;
} inVar;
// --------------------- OUT --------------------
out vec4 FragColor;
// ------------------- UNIFORM ------------------
uniform vec3 uCamDir;
vec4 grid(vec3 fragPos, float scale) {
vec2 coord = fragPos.xz * scale;
vec2 derivative = fwidth(coord);
vec2 grid = abs(fract(coord - 0.5) - 0.5) / derivative;
float line = min(grid.x, grid.y);
float minimumz = min(derivative.y, 1) * 0.1;
float minimumx = min(derivative.x, 1) * 0.1;
vec4 color = vec4(0.102, 0.102, 0.129, 1.0 - min(line, 1.0));
if(abs(fragPos.x) < minimumx)
color.z = 1.0;
if(abs(fragPos.z) < minimumz)
color.x = 1.0;
return color;
}
float computeDepth(vec3 pos) {
vec4 clip_space_pos = inVar.proj * inVar.view * vec4(pos.xyz, 1.0);
float clip_space_depth = clip_space_pos.z / clip_space_pos.w;
float far = gl_DepthRange.far;
float near = gl_DepthRange.near;
float depth = (((far-near) * clip_space_depth) + near + far) / 2.0;
return depth;
}
float computeLinearDepth(vec3 pos) {
vec4 clip_space_pos = inVar.proj * inVar.view * vec4(pos.xyz, 1.0);
float clip_space_depth = (clip_space_pos.z / clip_space_pos.w) * 2.0 - 1.0;
float linearDepth = (2.0 * inVar.near * inVar.far) / (inVar.far + inVar.near - clip_space_depth * (inVar.far - inVar.near));
return linearDepth / inVar.far;
}
void main() {
float t = -inVar.nearPoint.y / (inVar.farPoint.y - inVar.nearPoint.y);
vec3 fragPos3D = inVar.nearPoint + t * (inVar.farPoint - inVar.nearPoint);
gl_FragDepth = computeDepth(fragPos3D);
float linearDepth = computeLinearDepth(fragPos3D);
float fading = max(0, (0.5 - linearDepth));
FragColor = (grid(fragPos3D, 10) + grid(fragPos3D, 1)) * float(t > 0);
FragColor.a *= fading;
}

View File

@ -1,36 +0,0 @@
#version 460 core
layout (location = 0) in vec3 vPos;
// --------------------- OUT ---------------------
out OUT_IN_VARIABLES {
vec3 nearPoint;
vec3 farPoint;
mat4 proj;
mat4 view;
float near;
float far;
} outVar;
uniform mat4 proj;
uniform mat4 view;
uniform float uNear;
uniform float uFar;
vec3 UnprojectPoint(vec2 xy, float z) {
mat4 viewInv = inverse(view);
mat4 projInv = inverse(proj);
vec4 unprojectedPoint = viewInv * projInv * vec4(xy, z, 1.0);
return unprojectedPoint.xyz / unprojectedPoint.w;
}
void main()
{
outVar.near = uNear;
outVar.far = uFar;
outVar.proj = proj;
outVar.view = view;
outVar.nearPoint = UnprojectPoint(vPos.xy, -1.0).xyz;
outVar.farPoint = UnprojectPoint(vPos.xy, 1.0).xyz;
gl_Position = vec4(vPos, 1.0f);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 517 B

View File

@ -1,16 +0,0 @@
#version 460 core
uniform sampler2D uIcon;
uniform vec4 uColor;
in vec2 fTexCoords;
out vec4 FragColor;
void main()
{
vec4 color = uColor * texture(uIcon, fTexCoords);
if (color.a < 0.1) discard;
FragColor = uColor;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,22 +0,0 @@
#version 460 core
layout (location = 0) in vec3 vPos;
layout (location = 9) in mat4 vInstanceMatrix;
uniform mat4 uView;
uniform mat4 uProjection;
out vec2 fTexCoords;
void main()
{
float scale = 0.075;
mat4 result;
result[0] = vec4(scale, 0.0, 0.0, 0.0);
result[1] = vec4(0.0, scale, 0.0, 0.0);
result[2] = vec4(0.0, 0.0, scale, 0.0);
result[3] = vInstanceMatrix[3];
gl_Position = uProjection * uView * result * vec4(inverse(mat3(uView)) * vPos, 1.0);
fTexCoords = -vPos.xy * 0.5 + 0.5; // fits the whole rectangle
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -1,8 +0,0 @@
#version 460 core
out vec4 FragColor;
void main()
{
FragColor = vec4(0.929, 0.588, 0.196, 1.0);
}

View File

@ -1,72 +0,0 @@
#version 460 core
layout (location = 1) in vec3 vPos;
layout (location = 2) in vec3 vNormal;
layout (location = 7) in vec4 vBoneInfluence;
layout (location = 8) in vec4 vBoneInfluenceExtra;
layout (location = 9) in mat4 vInstanceMatrix;
layout (location = 13) in vec3 vMorphTargetPos;
layout(std430, binding = 1) buffer BoneMatrices
{
mat4 uFinalBonesMatrix[];
};
layout(std430, binding = 2) buffer RestBoneMatrices
{
mat4 uRestBonesMatrix[];
};
uniform mat4 uView;
uniform vec3 uViewPos;
uniform mat4 uProjection;
uniform float uMorphTime;
uniform bool uIsAnimated;
vec2 unpackBoneIDsAndWeights(int packedData)
{
return vec2(float((packedData >> 16) & 0xFFFF), float(packedData & 0xFFFF));
}
vec4 calculateScale(vec4 bindPos, vec4 bindNormal)
{
vec4 worldPos = vInstanceMatrix * bindPos;
float scaleFactor = length(uViewPos - worldPos.xyz) * 0.0035;
return transpose(inverse(vInstanceMatrix)) * bindNormal * scaleFactor;
}
void main()
{
vec4 bindPos = vec4(mix(vPos, vMorphTargetPos, uMorphTime), 1.0);
vec4 bindNormal = vec4(vNormal, 1.0);
bindPos.xyz += calculateScale(bindPos, bindNormal).xyz;
vec4 finalPos = vec4(0.0);
vec4 finalNormal = vec4(0.0);
if (uIsAnimated)
{
vec4 boneInfluences[2];
boneInfluences[0] = vBoneInfluence;
boneInfluences[1] = vBoneInfluenceExtra;
for(int i = 0 ; i < 2; i++)
{
for(int j = 0; j < 4; j++)
{
vec2 boneInfluence = unpackBoneIDsAndWeights(int(boneInfluences[i][j]));
int boneIndex = int(boneInfluence.x);
float weight = boneInfluence.y;
mat4 boneMatrix = uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]);
finalPos += boneMatrix * bindPos * weight;
finalNormal += transpose(inverse(boneMatrix)) * bindNormal * weight;
}
}
}
else
{
finalPos = bindPos;
finalNormal = bindNormal;
}
gl_Position = uProjection * uView * vInstanceMatrix * finalPos;
}

View File

@ -1,13 +0,0 @@
#version 460 core
uniform uint uA;
uniform uint uB;
uniform uint uC;
uniform uint uD;
out uvec4 FragColor;
void main()
{
FragColor = uvec4(uA, uB, uC, uD);
}

View File

@ -1,53 +0,0 @@
#version 460 core
layout (location = 1) in vec3 vPos;
layout (location = 7) in vec4 vBoneInfluence;
layout (location = 8) in vec4 vBoneInfluenceExtra;
layout (location = 9) in mat4 vInstanceMatrix;
layout (location = 13) in vec3 vMorphTargetPos;
layout(std430, binding = 1) buffer BoneMatrices
{
mat4 uFinalBonesMatrix[];
};
layout(std430, binding = 2) buffer RestBoneMatrices
{
mat4 uRestBonesMatrix[];
};
uniform mat4 uView;
uniform mat4 uProjection;
uniform float uMorphTime;
uniform bool uIsAnimated;
vec2 unpackBoneIDsAndWeights(int packedData)
{
return vec2(float((packedData >> 16) & 0xFFFF), float(packedData & 0xFFFF));
}
void main()
{
vec4 bindPos = vec4(mix(vPos, vMorphTargetPos, uMorphTime), 1.0);
vec4 finalPos = vec4(0.0);
if (uIsAnimated)
{
vec4 boneInfluences[2];
boneInfluences[0] = vBoneInfluence;
boneInfluences[1] = vBoneInfluenceExtra;
for(int i = 0 ; i < 2; i++)
{
for(int j = 0; j < 4; j++)
{
vec2 boneInfluence = unpackBoneIDsAndWeights(int(boneInfluences[i][j]));
int boneIndex = int(boneInfluence.x);
float weight = boneInfluence.y;
finalPos += uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]) * bindPos * weight;
}
}
}
else finalPos = bindPos;
gl_Position = uProjection * uView * vInstanceMatrix * finalPos;
}

BIN
FModel/Resources/pin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Some files were not shown because too many files have changed in this diff Show More