diff --git a/FModel.sln b/FModel.sln index fb6bcab7..840c08ec 100644 --- a/FModel.sln +++ b/FModel.sln @@ -1,25 +1,29 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 -VisualStudioVersion = 16.0.28803.452 +VisualStudioVersion = 16.0.29806.167 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FModel", "FModel\FModel.csproj", "{8AAB27BD-18D7-4164-8BBC-AB534D55D30F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FModel", "FModel\FModel.csproj", "{A42F8737-D056-4FA5-BEB5-AA96E1639F8A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8AAB27BD-18D7-4164-8BBC-AB534D55D30F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8AAB27BD-18D7-4164-8BBC-AB534D55D30F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8AAB27BD-18D7-4164-8BBC-AB534D55D30F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8AAB27BD-18D7-4164-8BBC-AB534D55D30F}.Release|Any CPU.Build.0 = Release|Any CPU + {A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Debug|Any CPU.ActiveCfg = Debug|x64 + {A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Debug|x64.ActiveCfg = Debug|x64 + {A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Debug|x64.Build.0 = Debug|x64 + {A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Release|Any CPU.ActiveCfg = Release|x64 + {A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Release|x64.ActiveCfg = Release|x64 + {A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {42CA5618-EB78-4DDF-95A4-BD589CC52791} + SolutionGuid = {FE6EA91D-BBB8-4FBC-875C-25AD92EDB519} EndGlobalSection EndGlobal diff --git a/FModel/App.config b/FModel/App.config deleted file mode 100644 index c897b019..00000000 --- a/FModel/App.config +++ /dev/null @@ -1,213 +0,0 @@ - - - - -
- - - - - - - - - - - - - - - - - - Default - - - English - - - False - - - False - - - - - - 0 - - - 0 - - - 3 - - - 3 - - - {BundleName} - {Date} - - - False - - - - - - 0 - - - False - - - True - - - False - - - True - - - 21:32:43 - - - 29:161:242 - - - True - - - True - - - True - - - False - - - False - - - False - - - { - "[BR] Cosmetics": { - "Path": "/FortniteGame/Content/Athena/Items/Cosmetics/", - "isChecked": "True" - }, - "[BR] Cosmetics Variants": { - "Path": "/FortniteGame/Content/Athena/Items/CosmeticVariantTokens/", - "isChecked": "True" - }, - "[BR] Banners": { - "Path": "/FortniteGame/Content/Athena/Items/BannerToken/", - "isChecked": "True" - }, - "[BR] Challenges": { - "Path": "/FortniteGame/Content/Athena/Items/ChallengeBundles/", - "isChecked": "True" - }, - "[BR] Consumables": { - "Path": "/FortniteGame/Content/Athena/Items/Consumables/", - "isChecked": "False" - }, - "[BR] Traps": { - "Path": "/FortniteGame/Content/Athena/Items/Traps/", - "isChecked": "False" - }, - "[BR] Weapons": { - "Path": "/FortniteGame/Content/Athena/Items/Weapons/", - "isChecked": "True" - }, - "[BR] 2D Assets": { - "Path": "/FortniteGame/Content/2dAssets/", - "isChecked": "True" - }, - "[BR] Featured Images": { - "Path": "/FortniteGame/Content/UI/Foundation/Textures/BattleRoyale/FeaturedItems/", - "isChecked": "False" - }, - "[STW] Heroes": { - "Path": "/FortniteGame/Content/Heroes/", - "isChecked": "False" - }, - "[STW] Defenders": { - "Path": "/FortniteGame/Content/Items/Defenders/", - "isChecked": "False" - }, - "[STW] Schematics": { - "Path": "/FortniteGame/Content/Items/Schematics/", - "isChecked": "False" - }, - "[STW] Traps": { - "Path": "/FortniteGame/Content/Items/Traps/", - "isChecked": "False" - }, - "[STW] Weapons": { - "Path": "/FortniteGame/Content/Items/Weapons/", - "isChecked": "False" - }, - "[STW] Ingredients": { - "Path": "/FortniteGame/Content/Items/Ingredients/", - "isChecked": "False" - }, - "[STW] Persistent Resources": { - "Path": "/FortniteGame/Content/Items/PersistentResources/", - "isChecked": "False" - }, - "[STW] CardPacks": { - "Path": "/FortniteGame/Content/Items/CardPacks/", - "isChecked": "False" - }, - "Tokens": { - "Path": "/FortniteGame/Content/Items/Tokens/", - "isChecked": "False" - }, - "Icons": { - "Path": "/FortniteGame/Content/UI/Foundation/Textures/Icons/", - "isChecked": "False" - }, - "Additional Banners": { - "Path": "/FortniteGame/Content/UI/Foundation/Textures/Banner/", - "isChecked": "False" - }, - "Additional Loading Screens": { - "Path": "/FortniteGame/Content/UI/Foundation/Textures/LoadingScreens/", - "isChecked": "False" - } -} - - - - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/FModel/App.xaml b/FModel/App.xaml index f780ac10..a43c45c3 100644 --- a/FModel/App.xaml +++ b/FModel/App.xaml @@ -1,17 +1,13 @@  - - - - + + diff --git a/FModel/App.xaml.cs b/FModel/App.xaml.cs index b13666a4..81295a88 100644 --- a/FModel/App.xaml.cs +++ b/FModel/App.xaml.cs @@ -1,20 +1,80 @@ -using FModel.Methods.MessageBox; -using FModel.Methods.Utilities; +using FModel.Discord; +using FModel.Logger; +using FModel.Utils; +using FModel.ViewModels.StatusBar; +using FModel.Windows.DarkMessageBox; +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Threading; using System.Windows; +using System.Windows.Threading; namespace FModel { /// - /// Logique d'interaction pour App.xaml + /// Interaction logic for App.xaml /// public partial class App : Application { - void OnDispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) + internal static Stopwatch StartTimer { get; private set; } + static bool framerateSet = false; + + protected override void OnStartup(StartupEventArgs e) { - string errorMessage = string.Format("An unhandled exception occurred: {0}", e.Exception.Message); + StartTimer = Stopwatch.StartNew(); + + //Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US"); + + DebugHelper.Init(LogsFilePath); + DebugHelper.WriteLine("{0} {1}", "[FModel]", "––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––"); + DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Version]", Assembly.GetExecutingAssembly().GetName().Version.ToString()); + DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Build]", Globals.Build); + DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[OS]", Logger.Logger.GetOperatingSystemProductName(true)); + DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Runtime]", RuntimeInformation.FrameworkDescription); + DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Culture]", Thread.CurrentThread.CurrentUICulture); + + StatusBarVm.statusBarViewModel.Set(FModel.Properties.Resources.Initializing, FModel.Properties.Resources.Loading); + DiscordIntegration.StartClient(); + + base.OnStartup(e); + } + + public static string LogsFilePath + { + get + { + string filename = string.Format("FModel-Log-{0:yyyy-MM-dd}.txt", DateTime.Now); + + // Copy user settings from previous application version if necessary + if (FModel.Properties.Settings.Default.UpdateSettings) + FModel.Properties.Settings.Default.Upgrade(); + + Folders.LoadFolders(); + + return Path.Combine(FModel.Properties.Settings.Default.OutputPath + "\\Logs", filename); + } + } + + private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) + { + string errorMessage = string.Format(FModel.Properties.Resources.UnhandledExceptionOccured, e.Exception.Message); DebugHelper.WriteException(e.Exception, "thrown in App.xaml.cs by OnDispatcherUnhandledException"); - DarkMessageBox.Show(errorMessage, "Error", MessageBoxButton.OK, MessageBoxImage.Error); + DarkMessageBoxHelper.Show(errorMessage, "Error", MessageBoxButton.OK, MessageBoxImage.Error); e.Handled = true; } + + internal static void SetFramerate() + { + if (!framerateSet) + { + System.Windows.Media.Animation.Timeline.DesiredFrameRateProperty.OverrideMetadata( + typeof(System.Windows.Media.Animation.Timeline), + new FrameworkPropertyMetadata { DefaultValue = 10 }); + framerateSet = true; + } + } } } diff --git a/FModel/AssemblyInfo.cs b/FModel/AssemblyInfo.cs new file mode 100644 index 00000000..8b5504ec --- /dev/null +++ b/FModel/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/FModel/Commands/FModel_Commands.cs b/FModel/Commands/FModel_Commands.cs deleted file mode 100644 index 17503699..00000000 --- a/FModel/Commands/FModel_Commands.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Windows.Input; - -namespace FModel.Commands -{ - static class FModel_Commands - { - public static readonly RoutedUICommand OpenSettings = new RoutedUICommand("Open Settings Window", "OpenSettings", typeof(MainWindow)); - public static readonly RoutedUICommand OpenSearch = new RoutedUICommand("Open Search Window", "OpenSearch", typeof(MainWindow)); - public static readonly RoutedUICommand OpenOutput = new RoutedUICommand("Open Output Folder", "OpenOutput", typeof(MainWindow)); - public static readonly RoutedUICommand MergeImages = new RoutedUICommand("Merge Images", "MergeImages", typeof(MainWindow)); - } -} diff --git a/FModel/Creator/BaseBundle.cs b/FModel/Creator/BaseBundle.cs new file mode 100644 index 00000000..648f278e --- /dev/null +++ b/FModel/Creator/BaseBundle.cs @@ -0,0 +1,102 @@ +using FModel.Creator.Bundles; +using FModel.Creator.Texts; +using PakReader.Pak; +using PakReader.Parsers.Class; +using PakReader.Parsers.PropertyTagData; +using System.Collections.Generic; + +namespace FModel.Creator +{ + public class BaseBundle + { + public Header DisplayStyle; + public string DisplayName; + public string FolderName; + public string Watermark; + public int Width = 1024; + public int HeaderHeight = 261; // height is the header basically + public int AdditionalSize = 50; // must be increased depending on the number of quests to draw + public bool IsDisplayNameShifted; + public List Quests; + public List CompletionRewards; + + public BaseBundle() + { + DisplayStyle = new Header(); + DisplayName = ""; + FolderName = ""; + Watermark = Properties.Settings.Default.ChallengeBannerWatermark; + Quests = new List(); + CompletionRewards = new List(); + } + + /// + /// used for the settings + /// + public BaseBundle(string watermark) : this() + { + DisplayName = "{DisplayName}"; + FolderName = "{FolderName}"; + Watermark = watermark; + Quests.Add(new Quest { Description = "", Count = 999, Reward = null }); + AdditionalSize += 89; + } + + public BaseBundle(IUExport export, string assetFolder) : this() + { + if (export.GetExport("DisplayStyle") is StructProperty displayStyle) + DisplayStyle = new Header(displayStyle, assetFolder); + if (export.GetExport("DisplayName") is TextProperty displayName) + DisplayName = Text.GetTextPropertyBase(displayName); + + if (export.GetExport("CareerQuestBitShifts") is ArrayProperty careerQuestBitShifts) + { + foreach (SoftObjectProperty questPath in careerQuestBitShifts.Value) + { + PakPackage p = Utils.GetPropertyPakPackage(questPath.Value.AssetPathName.String); + if (p.HasExport() && !p.Equals(default)) + { + var obj = p.GetExport(); + if (obj != null) + Quests.Add(new Quest(obj)); + } + } + } + + if (export.GetExport("BundleCompletionRewards") is ArrayProperty bundleCompletionRewards) + { + foreach (StructProperty completionReward in bundleCompletionRewards.Value) + { + if (completionReward.Value is UObject reward && + reward.TryGetValue("CompletionCount", out var c) && c is IntProperty completionCount && + reward.TryGetValue("Rewards", out var r) && r is ArrayProperty rewards) + { + foreach (StructProperty rew in rewards.Value) + { + if (rew.Value is UObject re && + re.TryGetValue("Quantity", out var q) && q is IntProperty quantity && + re.TryGetValue("TemplateId", out var t) && t is StrProperty templateId && + re.TryGetValue("ItemDefinition", out var d) && d is SoftObjectProperty itemDefinition) + { + if (!itemDefinition.Value.AssetPathName.IsNone && + !itemDefinition.Value.AssetPathName.String.StartsWith("/Game/Items/Tokens/") && + !itemDefinition.Value.AssetPathName.String.StartsWith("/Game/Athena/Items/Quests")) + { + CompletionRewards.Add(new CompletionReward(completionCount, quantity, itemDefinition)); + } + else if (!string.IsNullOrEmpty(templateId.Value)) + { + CompletionRewards.Add(new CompletionReward(completionCount, quantity, templateId.Value)); + } + } + } + } + } + } + + FolderName = assetFolder; + AdditionalSize += 95 * Quests.Count; + if (CompletionRewards.Count > 0) AdditionalSize += 50 + (95 * CompletionRewards.Count); + } + } +} diff --git a/FModel/Creator/BaseIcon.cs b/FModel/Creator/BaseIcon.cs new file mode 100644 index 00000000..5c5a6bfb --- /dev/null +++ b/FModel/Creator/BaseIcon.cs @@ -0,0 +1,126 @@ +using FModel.Creator.Icons; +using FModel.Creator.Rarities; +using FModel.Creator.Stats; +using FModel.Creator.Texts; +using FModel.Utils; +using PakReader.Parsers.Class; +using PakReader.Parsers.PropertyTagData; +using SkiaSharp; +using System; +using System.Collections.Generic; +using System.Windows; + +namespace FModel.Creator +{ + public class BaseIcon + { + public SKBitmap FallbackImage; + public SKBitmap IconImage; + public SKBitmap RarityBackgroundImage; + public SKBitmap[] UserFacingFlags; + public SKColor[] RarityBackgroundColors; + public SKColor[] RarityBorderColor; + public string DisplayName; + public string Description; + public string ShortDescription; + public string CosmeticSource; + public int Size = 512; // keep it 512 (or a multiple of 512) if you don't want blurry icons + public int AdditionalSize = 0; // must be increased if there are weapon stats, hero abilities or more to draw/show + public int Margin = 2; + public List Stats; + + public BaseIcon() + { + FallbackImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png")).Stream); + IconImage = FallbackImage; + RarityBackgroundImage = null; + UserFacingFlags = null; + RarityBackgroundColors = new SKColor[2] { SKColor.Parse("5EBC36"), SKColor.Parse("305C15") }; + RarityBorderColor = new SKColor[2] { SKColor.Parse("74EF52"), SKColor.Parse("74EF52") }; + DisplayName = ""; + Description = ""; + ShortDescription = ""; + CosmeticSource = ""; + Stats = new List(); + } + + /// + /// used to get low res icons ONLY + /// + /// + /// + public BaseIcon(IUExport export, string assetName) : this() + { + if (export.GetExport("HeroDefinition", "WeaponDefinition") is ObjectProperty itemDef) + LargeSmallImage.GetPreviewImage(this, itemDef, assetName, false); + else if (export.GetExport("SmallPreviewImage", "SmallImage") is SoftObjectProperty previewImage) + LargeSmallImage.GetPreviewImage(this, previewImage); + } + + /// + /// Order: + /// 1. Rarity + /// 2. Image + /// 3. Text + /// 1. DisplayName + /// 2. Description + /// 3. Misc + /// 4. GameplayTags + /// 1. order doesn't matter + /// 2. the importance here is to get the description before gameplay tags + /// + public BaseIcon(IUExport export, string exportType, string assetName) : this() + { + // rarity + if (export.GetExport("Series") is ObjectProperty series) + Serie.GetRarity(this, series); + else if (Properties.Settings.Default.UseGameColors) // override default green + Rarity.GetInGameRarity(this, export.GetExport("Rarity")); // uncommon will be triggered by Rarity being null + else if (export.GetExport("Rarity") is EnumProperty rarity) + Rarity.GetHardCodedRarity(this, rarity); + + // image + if (Properties.Settings.Default.UseItemShopIcon && + DisplayAssetImage.GetDisplayAssetImage(this, export.GetExport("DisplayAssetPath"), assetName)) + { } // ^^^^ will return false if image not found, if so, we try to get the normal icon + else if (export.GetExport("HeroDefinition", "WeaponDefinition") is ObjectProperty itemDef) + LargeSmallImage.GetPreviewImage(this, itemDef, assetName); + else if (export.GetExport("LargePreviewImage", "SmallPreviewImage", "ItemDisplayAsset") is SoftObjectProperty previewImage) + LargeSmallImage.GetPreviewImage(this, previewImage); + else if (export.GetExport("IconBrush") is StructProperty iconBrush) // abilities + LargeSmallImage.GetPreviewImage(this, iconBrush); + + // text + if (export.GetExport("DisplayName", "DefaultHeaderText", "UIDisplayName") is TextProperty displayName) + DisplayName = Text.GetTextPropertyBase(displayName); + if (export.GetExport("Description", "DefaultBodyText") is TextProperty description) + Description = Text.GetTextPropertyBase(description); + else if (export.GetExport("Description") is ArrayProperty arrayDescription) // abilities + Description = Text.GetTextPropertyBase(arrayDescription); + if (export.GetExport("MaxStackSize") is StructProperty maxStackSize) + ShortDescription = Text.GetMaxStackSize(maxStackSize); + else if (export.GetExport("ShortDescription") is TextProperty shortDescription) + ShortDescription = Text.GetTextPropertyBase(shortDescription); + else if (exportType.Equals("AthenaItemWrapDefinition")) // if no ShortDescription it's most likely a wrap + ShortDescription = Localizations.GetLocalization("Fort.Cosmetics", "ItemWrapShortDescription", "Wrap"); + + // gameplaytags + if (export.GetExport("GameplayTags") is StructProperty gameplayTags) + GameplayTag.GetGameplayTags(this, gameplayTags, exportType); + else if (export.GetExport("cosmetic_item") is ObjectProperty cosmeticItem) // variants + CosmeticSource = cosmeticItem.Value.Resource.ObjectName.String; + + if (export.GetExport("AmmoData") is SoftObjectProperty ammoData) + Statistics.GetAmmoData(this, ammoData); + if (export.GetExport("WeaponStatHandle") is StructProperty weaponStatHandle) + Statistics.GetWeaponStats(this, weaponStatHandle); + if (export.GetExport("HeroGameplayDefinition") is ObjectProperty heroGameplayDefinition) + Statistics.GetHeroStats(this, heroGameplayDefinition); + + /* Please do not add Schematics support because it takes way too much memory */ + /* Thank the STW Dev Team for using a 5,69Mb file to get... Oh nvm, they all left */ + + AdditionalSize = 48 * Stats.Count; + } + } +} diff --git a/FModel/Creator/BaseUserOption.cs b/FModel/Creator/BaseUserOption.cs new file mode 100644 index 00000000..80eab7bc --- /dev/null +++ b/FModel/Creator/BaseUserOption.cs @@ -0,0 +1,166 @@ +using FModel.Creator.Texts; +using PakReader.Parsers.Class; +using PakReader.Parsers.Objects; +using PakReader.Parsers.PropertyTagData; +using SkiaSharp; +using System.Collections.Generic; + +namespace FModel.Creator +{ + public class Options + { + public string Option; + public SKColor Color = SKColor.Parse("55C5FC").WithAlpha(150); + } + + public class BaseUserOption + { + private readonly SKPaint descriptionPaint = new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Typeface = Text.TypeFaces.DisplayNameTypeface, + TextSize = 25, + Color = SKColor.Parse("88DBFF"), + }; + + public string OptionDisplayName; + public string OptionDescription; + public List OptionValues = new List(); + public int Width = 512; + public int Height = 128; + public int Margin = 32; + + public BaseUserOption(IUExport export) + { + if (export.GetExport("OptionDisplayName") is TextProperty optionDisplayName) + OptionDisplayName = Text.GetTextPropertyBase(optionDisplayName).ToUpperInvariant(); + if (export.GetExport("OptionDescription") is TextProperty optionDescription) + { + OptionDescription = Text.GetTextPropertyBase(optionDescription); + Height += (int)descriptionPaint.TextSize * Helper.SplitLines(OptionDescription, descriptionPaint, Width - Margin).Length; + Height += (int)descriptionPaint.TextSize; + } + + if (export.GetExport("OptionValues") is ArrayProperty optionValues) + { + OptionValues = new List(optionValues.Value.Length); + for (int i = 0; i < OptionValues.Capacity; i++) + { + if (optionValues.Value[i] is StructProperty s && s.Value is UObject option) + { + if (option.TryGetValue("DisplayName", out var v1) && v1 is TextProperty displayName) + { + var opt = new Options { Option = Text.GetTextPropertyBase(displayName).ToUpperInvariant() }; + if (option.TryGetValue("Value", out var v) && v is StructProperty value && value.Value is FLinearColor color) + opt.Color = SKColor.Parse(color.Hex).WithAlpha(150); + OptionValues.Add(opt); + } + else if (option.TryGetValue("PrimaryAssetName", out var v2) && v2 is NameProperty primaryAssetName) + OptionValues.Add(new Options { Option = primaryAssetName.Value.String }); + } + } + } + + if (export.GetExport("OptionOnText") is TextProperty optionOnText) + OptionValues.Add(new Options { Option = Text.GetTextPropertyBase(optionOnText).ToUpperInvariant() }); + if (export.GetExport("OptionOffText") is TextProperty optionOffText) + OptionValues.Add(new Options { Option = Text.GetTextPropertyBase(optionOffText).ToUpperInvariant() }); + + if (export.GetExport("Min", "DefaultValue") is IntProperty iMin && + export.GetExport("Max") is IntProperty iMax) + { + int increment = iMin.Value; + if (export.GetExport("IncrementValue") is IntProperty incrementValue) + increment = incrementValue.Value; + + for (int i = iMin.Value; i <= iMax.Value; i += increment) + { + OptionValues.Add(new Options { Option = i.ToString() }); + } + } + + if (export.GetExport("Min") is FloatProperty fMin && + export.GetExport("Max") is FloatProperty fMax) + { + float increment = fMin.Value; + if (export.GetExport("IncrementValue") is FloatProperty incrementValue) + increment = incrementValue.Value; + + for (float i = fMin.Value; i <= fMax.Value; i += increment) + { + OptionValues.Add(new Options { Option = i.ToString() }); + } + } + + Height += Margin; + Height += 35 * OptionValues.Count; + } + + public void Draw(SKCanvas c) + { + c.DrawRect(new SKRect(0, 0, Width, Height), + new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Shader = SKShader.CreateLinearGradient( + new SKPoint(Width / 2, Height), + new SKPoint(Width, Height / 4), + new SKColor[2] { SKColor.Parse("01369C"), SKColor.Parse("1273C8") }, + SKShaderTileMode.Clamp) + }); + + int textSize = 45; + SKPaint namePaint = new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Typeface = Text.TypeFaces.DisplayNameTypeface, + TextSize = textSize, + Color = SKColors.White, + TextAlign = SKTextAlign.Left + }; + + // resize if too long + while (namePaint.MeasureText(OptionDisplayName) > (Width - (Margin * 2))) + { + namePaint.TextSize = textSize -= 2; + } + int y = Margin + textSize; + c.DrawText(OptionDisplayName, Margin, y, namePaint); + y += (int)descriptionPaint.TextSize + (Margin / 2); + + // wrap if too long + Helper.DrawMultilineText(c, OptionDescription, Width, Margin, ETextSide.Left, + new SKRect(Margin, y, Width - Margin, 256), descriptionPaint, out int top); + + int height = 30; + int space = 5; + foreach (Options option in OptionValues) + { + c.DrawRect(new SKRect(Margin, top, Width - Margin, top + height), + new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Color = option.Color + }); + + int ts = 20; + c.DrawText(option.Option, Margin + (space * 2), top + (ts * 1.1f), + new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Typeface = Text.TypeFaces.DisplayNameTypeface, + TextSize = ts, + Color = SKColor.Parse("EEFFFF"), + TextAlign = SKTextAlign.Left + }); + + top += height + space; + } + } + } +} diff --git a/FModel/Creator/Bundles/CompletionReward.cs b/FModel/Creator/Bundles/CompletionReward.cs new file mode 100644 index 00000000..3f92b714 --- /dev/null +++ b/FModel/Creator/Bundles/CompletionReward.cs @@ -0,0 +1,60 @@ +using FModel.Utils; +using PakReader.Parsers.PropertyTagData; +using System; + +namespace FModel.Creator.Bundles +{ + public class CompletionReward + { + private const string _TRIGGER1 = ""; + private const string _TRIGGER2 = ""; + public string CompletionText; + public Reward Reward; + + public CompletionReward(IntProperty completionCount) + { + string all = Localizations.GetLocalization("AthenaChallengeDetailsEntry", "CompletionRewardFormat_All", "Complete all {0} challenges to earn the reward item"); + string allFormated = ReformatString(all, completionCount.Value.ToString(), true); + string any = Localizations.GetLocalization("AthenaChallengeDetailsEntry", "CompletionRewardFormat", "Complete any {0} challenges to earn the reward item"); + string anyFormated = ReformatString(any, completionCount.Value.ToString(), false); + CompletionText = completionCount.Value >= 0 ? anyFormated : allFormated; + + Reward = null; + } + + public CompletionReward(IntProperty completionCount, IntProperty quantity, SoftObjectProperty itemDefinition) : this(completionCount) + { + Reward = new Reward(quantity, itemDefinition); + } + + public CompletionReward(IntProperty completionCount, IntProperty quantity, string reward) : this(completionCount) + { + Reward = new Reward(quantity, reward); + } + + private string ReformatString(string s, string completionCount, bool isAll) + { + s = s.Replace("({0})", "{0}").Replace("{QuestNumber}", "{0}"); + + int index = s.IndexOf("|plural(", StringComparison.CurrentCultureIgnoreCase); + if (index > -1) + { + int i = s.Substring(index).IndexOf(')', StringComparison.CurrentCultureIgnoreCase); + s = s.Replace(s.Substring(index, i + 1), string.Empty).Replace("{0} {0}", "{0}"); + } + + int index1 = s.IndexOf(_TRIGGER1, StringComparison.CurrentCultureIgnoreCase); + if (index1 < 0) index1 = 0; + string partOne = s.Substring(0, index1); + + string partTemp = s.Substring(index1 + _TRIGGER1.Length); + int index2 = partTemp.IndexOf(_TRIGGER2, StringComparison.CurrentCultureIgnoreCase); + if (index2 < 0) index2 = 0; + string partUpper = partTemp.Substring(0, index2).ToUpper().Replace("{0}", isAll ? string.Empty : completionCount); + + string partTwo = partTemp.Substring(index2 + _TRIGGER2.Length); + + return string.Format("{0}{1}{2}", partOne, partUpper, partTwo).Replace(" ", " ").Replace(" ,", ","); + } + } +} diff --git a/FModel/Creator/Bundles/Header.cs b/FModel/Creator/Bundles/Header.cs new file mode 100644 index 00000000..66efc995 --- /dev/null +++ b/FModel/Creator/Bundles/Header.cs @@ -0,0 +1,106 @@ +using PakReader.Parsers.Class; +using PakReader.Parsers.Objects; +using PakReader.Parsers.PropertyTagData; +using SkiaSharp; +using System; +using System.IO; + +namespace FModel.Creator.Bundles +{ + public class Header + { + public SKColor PrimaryColor; + public SKColor SecondaryColor; + public SKColor AccentColor; + public SKBitmap DisplayImage; // 256x256 + public SKBitmap CustomBackground; // 1024x256 + + private readonly Random _random = new Random(Environment.TickCount); + private readonly string[] _randomColors = new string[255] + { + "F44336", "FFEBEE", "FFCDD2", "EF9A9A", "E57373", "EF5350", "E53935", "D32F2F", "C62828", "B71C1C", + "FF8A80", "FF5252", "FF1744", "D50000", "FCE4EC", "F8BBD0", "F48FB1", "F06292", "EC407A", "E91E63", + "D81B60", "C2185B", "AD1457", "880E4F", "FF80AB", "FF4081", "F50057", "C51162", "F3E5F5", "E1BEE7", + "CE93D8", "BA68C8", "AB47BC", "9C27B0", "8E24AA", "7B1FA2", "6A1B9A", "4A148C", "EA80FC", "E040FB", + "D500F9", "AA00FF", "EDE7F6", "D1C4E9", "B39DDB", "9575CD", "7E57C2", "673AB7", "5E35B1", "512DA8", + "4527A0", "311B92", "B388FF", "7C4DFF", "651FFF", "6200EA", "E8EAF6", "C5CAE9", "9FA8DA", "7986CB", + "5C6BC0", "3F51B5", "3949AB", "303F9F", "283593", "1A237E", "8C9EFF", "536DFE", "3D5AFE", "304FFE", + "E3F2FD", "BBDEFB", "90CAF9", "64B5F6", "42A5F5", "2196F3", "1E88E5", "1976D2", "1565C0", "0D47A1", + "82B1FF", "448AFF", "2979FF", "2962FF", "E1F5FE", "B3E5FC", "81D4FA", "4FC3F7", "29B6F6", "03A9F4", + "039BE5", "0288D1", "0277BD", "01579B", "80D8FF", "40C4FF", "00B0FF", "0091EA", "E0F7FA", "B2EBF2", + "80DEEA", "4DD0E1", "26C6DA", "00BCD4", "00ACC1", "0097A7", "00838F", "006064", "84FFFF", "18FFFF", + "00E5FF", "00B8D4", "E0F2F1", "B2DFDB", "80CBC4", "4DB6AC", "26A69A", "009688", "00897B", "00796B", + "00695C", "004D40", "A7FFEB", "64FFDA", "1DE9B6", "00BFA5", "E8F5E9", "C8E6C9", "A5D6A7", "81C784", + "66BB6A", "4CAF50", "43A047", "388E3C", "2E7D32", "1B5E20", "B9F6CA", "69F0AE", "00E676", "00C853", + "F1F8E9", "DCEDC8", "C5E1A5", "AED581", "9CCC65", "8BC34A", "7CB342", "689F38", "558B2F", "33691E", + "CCFF90", "B2FF59", "76FF03", "64DD17", "F9FBE7", "F0F4C3", "E6EE9C", "DCE775", "D4E157", "CDDC39", + "C0CA33", "AFB42B", "9E9D24", "827717", "F4FF81", "EEFF41", "C6FF00", "AEEA00", "FFFDE7", "FFF9C4", + "FFF59D", "FFF176", "FFEE58", "FFEB3B", "FDD835", "FBC02D", "F9A825", "F57F17", "FFFF8D", "FFFF00", + "FFEA00", "FFD600", "FFF8E1", "FFECB3", "FFE082", "FFD54F", "FFCA28", "FFC107", "FFB300", "FFA000", + "FF8F00", "FF6F00", "FFE57F", "FFD740", "FFC400", "FFAB00", "FFF3E0", "FFE0B2", "FFCC80", "FFB74D", + "FFA726", "FF9800", "FB8C00", "F57C00", "EF6C00", "E65100", "FFD180", "FFAB40", "FF9100", "FF6D00", + "FBE9E7", "FFCCBC", "FFAB91", "FF8A65", "FF7043", "FF5722", "F4511E", "E64A19", "D84315", "BF360C", + "FF9E80", "FF6E40", "FF3D00", "DD2C00", "EFEBE9", "D7CCC8", "BCAAA4", "A1887F", "8D6E63", "795548", + "6D4C41", "5D4037", "4E342E", "3E2723", "FAFAFA", "F5F5F5", "EEEEEE", "E0E0E0", "BDBDBD", "9E9E9E", + "757575", "616161", "424242", "212121", "ECEFF1", "CFD8DC", "B0BEC5", "90A4AE", "78909C", "607D8B", + "546E7A", "455A64", "37474F", "263238", "000000", + }; + + public Header() + { + if (Properties.Settings.Default.UseChallengeBanner) + { + SKColor mainColor = SKColor.Parse(Properties.Settings.Default.ChallengeBannerPrimaryColor); + mainColor.ToHsl(out float h, out float s, out float l); + float i = l + 20.0F > 100.0F ? 100.0F - l : 20.0F; + + PrimaryColor = mainColor; + SecondaryColor = SKColor.Parse(Properties.Settings.Default.ChallengeBannerSecondaryColor); + AccentColor = SKColor.FromHsl(h += i, s, l); + DisplayImage = null; + if (!string.IsNullOrEmpty(Properties.Settings.Default.ChallengeBannerPath)) + CustomBackground = SKBitmap.Decode(new FileInfo(Properties.Settings.Default.ChallengeBannerPath).Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); + else CustomBackground = null; + } + else + { + SKColor mainColor = SKColor.Parse(_randomColors[_random.Next(0, 255)]); + mainColor.ToHsl(out float h, out float s, out float l); + while (l > 75 || l < 10) + { + mainColor = SKColor.Parse(_randomColors[_random.Next(0, 255)]); + mainColor.ToHsl(out float _, out float _, out l); + } + float i = l + 20.0F > 100.0F ? 100.0F - l : 20.0F; + + PrimaryColor = mainColor; + SecondaryColor = SKColor.FromHsl(h, s, l += i); + AccentColor = SKColor.FromHsl(h += i, s, l); + DisplayImage = null; + CustomBackground = null; + } + } + + public Header(StructProperty displayStyle, string assetFolder) : this() + { + if (displayStyle.Value is UObject o) + { + if (!Properties.Settings.Default.UseChallengeBanner && o.TryGetValue(out var c1, "PrimaryColor", "Context_LimitedTimeColor") && c1 is StructProperty s1 && s1.Value is FLinearColor primaryColor) + PrimaryColor = SKColor.Parse(primaryColor.Hex); + if (!Properties.Settings.Default.UseChallengeBanner && o.TryGetValue(out var c2, "SecondaryColor", "Context_BaseColor") && c2 is StructProperty s2 && s2.Value is FLinearColor secondaryColor) + SecondaryColor = SKColor.Parse(secondaryColor.Hex); + if (!Properties.Settings.Default.UseChallengeBanner && o.TryGetValue("AccentColor", out var c3) && c3 is StructProperty s3 && s3.Value is FLinearColor accentColor) + { + AccentColor = SKColor.Parse(accentColor.Hex); + if (SecondaryColor.Red + SecondaryColor.Green + SecondaryColor.Blue <= 75 || assetFolder.Equals("LTM", StringComparison.CurrentCultureIgnoreCase)) // if secondary is too dark + SecondaryColor = AccentColor; // use accent and pray for accent to be ligher + } + + if (o.TryGetValue("DisplayImage", out var i) && i is SoftObjectProperty displayImage) + DisplayImage = Utils.GetSoftObjectTexture(displayImage); + if (CustomBackground == null && o.TryGetValue("CustomBackground", out var b) && b is SoftObjectProperty customBackground) + CustomBackground = Utils.GetSoftObjectTexture(customBackground); + } + } + } +} diff --git a/FModel/Creator/Bundles/HeaderStyle.cs b/FModel/Creator/Bundles/HeaderStyle.cs new file mode 100644 index 00000000..bd0534b3 --- /dev/null +++ b/FModel/Creator/Bundles/HeaderStyle.cs @@ -0,0 +1,98 @@ +using FModel.Creator.Texts; +using SkiaSharp; +using System; + +namespace FModel.Creator.Bundles +{ + static class HeaderStyle + { + public static void DrawHeaderPaint(SKCanvas c, BaseBundle icon) + { + c.DrawRect(new SKRect(0, 0, icon.Width, icon.HeaderHeight), new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Color = icon.DisplayStyle.PrimaryColor + }); + + if (icon.DisplayStyle.CustomBackground != null && icon.DisplayStyle.CustomBackground.Height != icon.DisplayStyle.CustomBackground.Width) + { + icon.IsDisplayNameShifted = false; + var bgPaint = new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High, BlendMode = SKBlendMode.Screen }; + if (Properties.Settings.Default.UseChallengeBanner) bgPaint.Color = SKColors.Transparent.WithAlpha((byte)Properties.Settings.Default.ChallengeBannerOpacity); + c.DrawBitmap(icon.DisplayStyle.CustomBackground, new SKRect(0, 0, 1024, 256), bgPaint); + } + else if (icon.DisplayStyle.DisplayImage != null) + { + icon.IsDisplayNameShifted = true; + if (icon.DisplayStyle.CustomBackground != null && icon.DisplayStyle.CustomBackground.Height == icon.DisplayStyle.CustomBackground.Width) + c.DrawBitmap(icon.DisplayStyle.CustomBackground, new SKRect(0, 0, icon.HeaderHeight, icon.HeaderHeight), + new SKPaint { + IsAntialias = true, FilterQuality = SKFilterQuality.High, BlendMode = SKBlendMode.Screen, + ImageFilter = SKImageFilter.CreateDropShadow(2.5F, 0, 20, 0, icon.DisplayStyle.SecondaryColor.WithAlpha(25), SKDropShadowImageFilterShadowMode.DrawShadowAndForeground) + }); + + c.DrawBitmap(icon.DisplayStyle.DisplayImage, new SKRect(0, 0, icon.HeaderHeight, icon.HeaderHeight), + new SKPaint { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + ImageFilter = SKImageFilter.CreateDropShadow(-2.5F, 0, 20, 0, icon.DisplayStyle.SecondaryColor.WithAlpha(50), SKDropShadowImageFilterShadowMode.DrawShadowAndForeground) + }); + } + + SKPath pathTop = new SKPath { FillType = SKPathFillType.EvenOdd }; + pathTop.MoveTo(0, icon.HeaderHeight); + pathTop.LineTo(icon.Width, icon.HeaderHeight); + pathTop.LineTo(icon.Width, icon.HeaderHeight - 19); + pathTop.LineTo(icon.Width / 2 + 7, icon.HeaderHeight - 23); + pathTop.LineTo(icon.Width / 2 + 13, icon.HeaderHeight - 7); + pathTop.LineTo(0, icon.HeaderHeight - 19); + pathTop.Close(); + c.DrawPath(pathTop, new SKPaint { + IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = icon.DisplayStyle.SecondaryColor, + ImageFilter = SKImageFilter.CreateDropShadow(-5, -5, 0, 0, icon.DisplayStyle.AccentColor.WithAlpha(75), SKDropShadowImageFilterShadowMode.DrawShadowAndForeground) + }); + + c.DrawRect(new SKRect(0, icon.HeaderHeight, icon.Width, icon.HeaderHeight + icon.AdditionalSize), new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Color = icon.DisplayStyle.PrimaryColor.WithAlpha(200) // default background is black, so i'm kinda lowering the brightness here and that's what i want + }); + } + + public static void DrawHeaderText(SKCanvas c, BaseBundle icon) + { + using SKPaint paint = new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Typeface = Text.TypeFaces.BundleDisplayNameTypeface, + TextSize = 50, + Color = SKColors.White, + TextAlign = SKTextAlign.Left, + }; + + string text = icon.DisplayName.ToUpper(); + int x = icon.IsDisplayNameShifted ? 300 : 50; + while (paint.MeasureText(text) > (icon.Width - x)) + { + paint.TextSize -= 2; + } + c.DrawText(text, x, 155, paint); + + paint.Color = SKColors.White.WithAlpha(150); + paint.TextAlign = SKTextAlign.Right; + paint.TextSize = 23; + c.DrawText(icon.Watermark + .Replace("{BundleName}", text) + .Replace("{Date}", DateTime.Now.ToString("dd/MM/yyyy")), + icon.Width - 25, icon.HeaderHeight - 40, paint); + + paint.Typeface = Text.TypeFaces.BundleDefaultTypeface; + paint.Color = icon.DisplayStyle.SecondaryColor; + paint.TextAlign = SKTextAlign.Left; + paint.TextSize = 30; + c.DrawText(icon.FolderName.ToUpper(), x, 95, paint); + } + } +} diff --git a/FModel/Creator/Bundles/Quest.cs b/FModel/Creator/Bundles/Quest.cs new file mode 100644 index 00000000..ee0847b0 --- /dev/null +++ b/FModel/Creator/Bundles/Quest.cs @@ -0,0 +1,94 @@ +using FModel.Creator.Texts; +using PakReader.Pak; +using PakReader.Parsers.Class; +using PakReader.Parsers.PropertyTagData; + +namespace FModel.Creator.Bundles +{ + public class Quest + { + public string Description; + public int Count; + public Reward Reward; + + public Quest() + { + Description = ""; + Count = 0; + Reward = null; + } + + public Quest(UObject obj) : this() + { + if (obj.TryGetValue("Description", out var d) && d is TextProperty description) + Description = Text.GetTextPropertyBase(description); + if (obj.TryGetValue("ObjectiveCompletionCount", out var o) && o is IntProperty objectiveCompletionCount) + Count = objectiveCompletionCount.Value; + + if (obj.TryGetValue("Objectives", out var v1) && v1 is ArrayProperty a1 && + a1.Value.Length > 0 && a1.Value[0] is StructProperty s && s.Value is UObject objectives) + { + if (string.IsNullOrEmpty(Description) && objectives.TryGetValue("Description", out var od) && od is TextProperty objectivesDescription) + Description = Text.GetTextPropertyBase(objectivesDescription); + + if (Count == 0 && objectives.TryGetValue("Count", out var c) && c is IntProperty count) + Count = count.Value; + } + + if (obj.TryGetValue("RewardsTable", out var v4) && v4 is ObjectProperty rewardsTable) + { + PakPackage p = Utils.GetPropertyPakPackage(rewardsTable.Value.Resource.OuterIndex.Resource.ObjectName.String); + if (p.HasExport() && !p.Equals(default)) + { + var u = p.GetExport(); + if (u != null && u.TryGetValue("Default", out var i) && i is UObject r && + r.TryGetValue("TemplateId", out var i1) && i1 is NameProperty templateId && + r.TryGetValue("Quantity", out var i2) && i2 is IntProperty quantity) + { + Reward = new Reward(quantity, templateId); + } + } + } + + if (Reward == null && obj.TryGetValue("Rewards", out var v2) && v2 is ArrayProperty rewards) + { + foreach (StructProperty reward in rewards.Value) + { + if (reward.Value is UObject r1 && + r1.TryGetValue("ItemPrimaryAssetId", out var i1) && i1 is StructProperty itemPrimaryAssetId && + r1.TryGetValue("Quantity", out var i2) && i2 is IntProperty quantity) + { + if (itemPrimaryAssetId.Value is UObject r2 && + r2.TryGetValue("PrimaryAssetType", out var t1) && t1 is StructProperty primaryAssetType && + r2.TryGetValue("PrimaryAssetName", out var t2) && t2 is NameProperty primaryAssetName) + { + if (primaryAssetType.Value is UObject r3 && r3.TryGetValue("Name", out var k) && k is NameProperty name) + { + if (!name.Value.String.Equals("Quest") && !name.Value.String.Equals("Token") && + !name.Value.String.Equals("ChallengeBundle") && !name.Value.String.Equals("GiftBox")) + { + Reward = new Reward(quantity, primaryAssetName); + break; + } + } + } + } + } + } + + if (Reward == null && obj.TryGetValue("HiddenRewards", out var v3) && v3 is ArrayProperty hiddenRewards) + { + foreach (StructProperty reward in hiddenRewards.Value) + { + if (reward.Value is UObject r1 && + r1.TryGetValue("TemplateId", out var i1) && i1 is NameProperty templateId && + r1.TryGetValue("Quantity", out var i2) && i2 is IntProperty quantity) + { + Reward = new Reward(quantity, templateId); + break; + } + } + } + } + } +} diff --git a/FModel/Creator/Bundles/QuestStyle.cs b/FModel/Creator/Bundles/QuestStyle.cs new file mode 100644 index 00000000..70d3af47 --- /dev/null +++ b/FModel/Creator/Bundles/QuestStyle.cs @@ -0,0 +1,174 @@ +using FModel.Creator.Texts; +using SkiaSharp; + +namespace FModel.Creator.Bundles +{ + static class QuestStyle + { + public static void DrawQuests(SKCanvas c, BaseBundle icon) + { + using SKPaint paint = new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + TextSize = 27, + Color = SKColors.White, + TextAlign = SKTextAlign.Left, + Typeface = Text.TypeFaces.BundleDisplayNameTypeface + }; + + int y = icon.HeaderHeight + 50; + foreach (Quest q in icon.Quests) + { + DrawQuestBackground(c, icon, y, true); + + paint.TextSize = 27; + paint.ImageFilter = null; + paint.Color = SKColors.White; + paint.TextAlign = SKTextAlign.Left; + paint.Typeface = Text.TypeFaces.BundleDisplayNameTypeface; + while (paint.MeasureText(q.Description) > icon.Width - 65 - 165) + { + paint.TextSize -= 1; + } + c.DrawText(q.Description, new SKPoint(65, y + paint.TextSize + 11), paint); + + paint.TextSize = 16; + paint.Color = SKColors.White.WithAlpha(200); + paint.Typeface = Text.TypeFaces.BundleDefaultTypeface; + c.DrawText(q.Count.ToString(), new SKPoint(93 + icon.Width - (175 * 3), y + 60), paint); + + if (q.Reward?.RewardIcon != null) + { + if (q.Reward.IsCountShifted) + { + int l = q.Reward.RewardQuantity.ToString().Length; + paint.TextSize = l >= 5 ? 30 : 35; + paint.TextAlign = SKTextAlign.Right; + paint.Color = SKColor.Parse(q.Reward.RewardFillColor); + paint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 5, 5, SKColor.Parse(q.Reward.RewardBorderColor).WithAlpha(200), SKDropShadowImageFilterShadowMode.DrawShadowAndForeground); + c.DrawText(q.Reward.RewardQuantity.ToString(), new SKPoint(icon.Width - 85, y + 47.5F), paint); + c.DrawBitmap(q.Reward.RewardIcon, new SKPoint(icon.Width - 30 - q.Reward.RewardIcon.Width, y + 12.5F), + new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High }); + } + else + c.DrawBitmap(q.Reward.RewardIcon, new SKPoint(icon.Width - 125, y + 5), + new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High }); + } + + y += 95; + } + } + + public static void DrawCompletionRewards(SKCanvas c, BaseBundle icon) + { + using SKPaint paint = new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + TextSize = 35, + Color = SKColors.White, + TextAlign = SKTextAlign.Left, + Typeface = Text.TypeFaces.BundleDisplayNameTypeface + }; + + int y = icon.HeaderHeight + (50 * 2) + (95 * icon.Quests.Count); + foreach (CompletionReward r in icon.CompletionRewards) + { + DrawQuestBackground(c, icon, y, false); + + paint.TextSize = 35; + paint.ImageFilter = null; + paint.Color = SKColors.White; + paint.TextAlign = SKTextAlign.Left; + paint.Typeface = Text.TypeFaces.BundleDisplayNameTypeface; + while (paint.MeasureText(r.CompletionText) > icon.Width - 65 - 165) + { + paint.TextSize -= 1; + } + c.DrawText(r.CompletionText, new SKPoint(65, y + paint.TextSize + 15), paint); + + if (r.Reward?.RewardIcon != null) + { + if (r.Reward.IsCountShifted) + { + int l = r.Reward.RewardQuantity.ToString().Length; + paint.TextSize = l >= 5 ? 30 : 35; + paint.TextAlign = SKTextAlign.Right; + paint.Color = SKColor.Parse(r.Reward.RewardFillColor); + paint.Typeface = Text.TypeFaces.BundleDefaultTypeface; + paint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 5, 5, SKColor.Parse(r.Reward.RewardBorderColor).WithAlpha(200), SKDropShadowImageFilterShadowMode.DrawShadowAndForeground); + c.DrawText(r.Reward.RewardQuantity.ToString(), new SKPoint(icon.Width - 85, y + 47.5F), paint); + c.DrawBitmap(r.Reward.RewardIcon, new SKPoint(icon.Width - 30 - r.Reward.RewardIcon.Width, y + 12.5F), + new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High }); + } + else + c.DrawBitmap(r.Reward.RewardIcon, new SKPoint(icon.Width - 125, y + 5), + new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High }); + } + + y += 95; + } + } + + private static void DrawQuestBackground(SKCanvas c, BaseBundle icon, int y, bool hasSlider) + { + SKColor baseColor = icon.DisplayStyle.PrimaryColor; + using SKPaint paint = new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Color = baseColor + }; + using SKPath secondaryRect = new SKPath + { + FillType = SKPathFillType.EvenOdd + }; + using SKPath selector = new SKPath + { + FillType = SKPathFillType.EvenOdd + }; + using SKPath slider = new SKPath + { + FillType = SKPathFillType.EvenOdd + }; + + c.DrawRect(new SKRect(25, y, icon.Width - 25, y + 75), paint); + + baseColor.ToHsl(out float h, out float s, out float l); + baseColor = SKColor.FromHsl(h, s, l + 5); + paint.Color = baseColor; + + secondaryRect.MoveTo(32, y + 5); + secondaryRect.LineTo(icon.Width - 155, y + 4); + secondaryRect.LineTo(icon.Width - 175, y + 68); + secondaryRect.LineTo(29, y + 71); + secondaryRect.Close(); + c.DrawPath(secondaryRect, paint); + + paint.Color = icon.DisplayStyle.SecondaryColor; + selector.MoveTo(41, y + 38); + selector.LineTo(48, y + 34); + selector.LineTo(52, y + 39); + selector.LineTo(46, y + 44); + selector.Close(); + c.DrawPath(selector, paint); + + if (hasSlider) + { + slider.MoveTo(65, y + 53); + slider.LineTo(65 + icon.Width - (175 * 3), y + 53); + slider.LineTo(65 + icon.Width - (175 * 3), y + 58); + slider.LineTo(65, y + 58); + slider.Close(); + c.DrawPath(slider, paint); + + paint.TextSize = 14; + paint.Color = SKColors.White; + paint.TextAlign = SKTextAlign.Left; + paint.Typeface = Text.TypeFaces.BundleDefaultTypeface; + c.DrawText("0 / ", new SKPoint(75 + icon.Width - (175 * 3), y + 59), paint); + } + } + } +} diff --git a/FModel/Creator/Bundles/Reward.cs b/FModel/Creator/Bundles/Reward.cs new file mode 100644 index 00000000..fd38be35 --- /dev/null +++ b/FModel/Creator/Bundles/Reward.cs @@ -0,0 +1,134 @@ +using PakReader.Pak; +using PakReader.Parsers.Class; +using PakReader.Parsers.PropertyTagData; +using SkiaSharp; +using System; + +namespace FModel.Creator.Bundles +{ + public class Reward + { + public int RewardQuantity; + public SKBitmap RewardIcon; + public string RewardFillColor; + public string RewardBorderColor; + public bool IsCountShifted; + + public Reward() + { + RewardQuantity = 0; + RewardIcon = null; + RewardFillColor = ""; + RewardBorderColor = ""; + IsCountShifted = false; + } + + public Reward(IntProperty quantity, NameProperty primaryAssetName) : this(quantity, primaryAssetName.Value.String) { } + public Reward(IntProperty quantity, string assetName) : this() + { + RewardQuantity = quantity.Value; + + if (assetName.Contains(':')) + { + string[] parts = assetName.Split(':'); + if (parts[0].Equals("HomebaseBannerIcon", StringComparison.CurrentCultureIgnoreCase)) + { + PakPackage p = Utils.GetPropertyPakPackage("/Game/Banners/BannerIcons"); + if (p.HasExport() && !p.Equals(default)) + { + var c = p.GetExport(); + if (c != null && c.TryGetCaseInsensitiveValue(parts[1], out var s) && s is UObject banner) + { + RewardIcon = new BaseIcon(banner, "BannerIcons.uasset").IconImage.Resize(64, 64); + } + } + } + else GetReward(parts[1]); + } + else GetReward(assetName); + } + + public Reward(IntProperty quantity, SoftObjectProperty itemDefinition) : this() + { + RewardQuantity = quantity.Value; + + PakPackage p = Utils.GetPropertyPakPackage(itemDefinition.Value.AssetPathName.String); + if (p.HasExport() && !p.Equals(default)) + { + var d = p.GetExport(); + if (d != null) + { + int s1 = itemDefinition.Value.AssetPathName.String.LastIndexOf('/'); + if (s1 < 0) s1 = 0; + int s2 = itemDefinition.Value.AssetPathName.String.LastIndexOf('.') - s1; + switch (itemDefinition.Value.AssetPathName.String) + { + case "/Game/Items/PersistentResources/AthenaBattleStar.AthenaBattleStar": + IsCountShifted = true; + RewardFillColor = "FFDB67"; + RewardBorderColor = "8F4A20"; + RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-FNBR-BattlePoints").Resize(48, 48); + break; + case "/Game/Items/PersistentResources/AthenaSeasonalXP.AthenaSeasonalXP": + IsCountShifted = true; + RewardFillColor = "E6FDB1"; + RewardBorderColor = "51830F"; + RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-FNBR-XPMedium").Resize(48, 48); + break; + case "/Game/Items/Currency/MtxGiveaway.MtxGiveaway": + IsCountShifted = true; + RewardFillColor = "DCE6FF"; + RewardBorderColor = "64A0AF"; + RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-Items-MTX").Resize(48, 48); + break; + default: + IsCountShifted = false; + RewardIcon = new BaseIcon(d, itemDefinition.Value.AssetPathName.String.Substring(s1, s2) + ".uasset").IconImage.Resize(64, 64); + break; + } + } + } + } + + private void GetReward(string trigger) + { + switch (trigger.ToLower()) + { + case "athenabattlestar": + IsCountShifted = true; + RewardFillColor = "FFDB67"; + RewardBorderColor = "8F4A20"; + RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-FNBR-BattlePoints").Resize(48, 48); + break; + case "athenaseasonalxp": + IsCountShifted = true; + RewardFillColor = "E6FDB1"; + RewardBorderColor = "51830F"; + RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-FNBR-XPMedium").Resize(48, 48); + break; + case "mtxgiveaway": + IsCountShifted = true; + RewardFillColor = "DCE6FF"; + RewardBorderColor = "64A0AF"; + RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-Items-MTX").Resize(48, 48); + break; + default: + { + string path = Utils.GetFullPath($"/FortniteGame/Content/Athena/.*?/{trigger}.*").Replace("FortniteGame/Content", "Game"); + PakPackage p = Utils.GetPropertyPakPackage(path); + if (p.HasExport() && !p.Equals(default)) + { + var d = p.GetExport(); + if (d != null) + { + int i = path.LastIndexOf('/'); + IsCountShifted = false; + RewardIcon = new BaseIcon(d, path.Substring(i > 0 ? i : 0) + ".uasset").IconImage.Resize(64, 64); + } + } + break; + } + } + } + } +} diff --git a/FModel/Creator/Creator.cs b/FModel/Creator/Creator.cs new file mode 100644 index 00000000..adf3f13a --- /dev/null +++ b/FModel/Creator/Creator.cs @@ -0,0 +1,180 @@ +using FModel.Creator.Bundles; +using FModel.Creator.Icons; +using FModel.Creator.Rarities; +using FModel.Creator.Stats; +using FModel.Creator.Texts; +using FModel.ViewModels.ImageBox; +using PakReader.Parsers.Class; +using SkiaSharp; +using System.IO; + +namespace FModel.Creator +{ + static class Creator + { + /// + /// we draw based on the fist export type of the asset, no need to check others it's a waste of time + /// i don't cache images because i don't wanna store a lot of SKCanvas in the memory + /// + /// true if an icon has been drawn + public static bool TryDrawIcon(string assetPath, string exportType, IUExport export) + { + var d = new DirectoryInfo(assetPath); + string assetName = d.Name; + string assetFolder = d.Parent.Name; + if (Text.TypeFaces.NeedReload(false)) + Text.TypeFaces = new Typefaces(); // when opening bundle creator settings without loading paks first + + // please respect my wave if you wanna add a new exportType + // Athena first, then Fort, thank you + switch (exportType) + { + case "AthenaConsumableEmoteItemDefinition": + case "AthenaSkyDiveContrailItemDefinition": + case "AthenaLoadingScreenItemDefinition": + case "AthenaVictoryPoseItemDefinition": + case "AthenaPetCarrierItemDefinition": + case "AthenaMusicPackItemDefinition": + case "AthenaBattleBusItemDefinition": + case "AthenaCharacterItemDefinition": + case "AthenaBackpackItemDefinition": + case "AthenaPickaxeItemDefinition": + case "AthenaGadgetItemDefinition": + case "AthenaGliderItemDefinition": + case "AthenaDailyQuestDefinition": + case "AthenaSprayItemDefinition": + case "AthenaDanceItemDefinition": + case "AthenaEmojiItemDefinition": + case "AthenaItemWrapDefinition": + case "AthenaToyItemDefinition": + case "FortHeroType": + case "FortTokenType": + case "FortAbilityKit": + case "FortWorkerType": + case "FortBannerTokenType": + case "FortVariantTokenType": + case "FortFeatItemDefinition": + case "FortStatItemDefinition": + case "FortTrapItemDefinition": + case "FortAmmoItemDefinition": + case "FortQuestItemDefinition": + case "FortBadgeItemDefinition": + case "FortAwardItemDefinition": + case "FortGadgetItemDefinition": + case "FortPlaysetItemDefinition": + case "FortGiftBoxItemDefinition": + case "FortSpyTechItemDefinition": + case "FortAccoladeItemDefinition": + case "FortCardPackItemDefinition": + case "FortDefenderItemDefinition": + case "FortCurrencyItemDefinition": + case "FortResourceItemDefinition": + case "FortSchematicItemDefinition": + case "FortIngredientItemDefinition": + case "FortWeaponMeleeItemDefinition": + case "FortContextTrapItemDefinition": + case "FortPlayerPerksItemDefinition": + case "FortPlaysetPropItemDefinition": + case "FortHomebaseNodeItemDefinition": + case "FortWeaponRangedItemDefinition": + case "FortNeverPersistItemDefinition": + case "FortPlaysetGrenadeItemDefinition": + case "FortPersonalVehicleItemDefinition": + case "FortHardcoreModifierItemDefinition": + case "FortConsumableAccountItemDefinition": + case "FortConversionControlItemDefinition": + case "FortPersistentResourceItemDefinition": + case "FortCampaignHeroLoadoutItemDefinition": + case "FortConditionalResourceItemDefinition": + case "FortChallengeBundleScheduleDefinition": + case "FortWeaponMeleeDualWieldItemDefinition": + case "FortDailyRewardScheduleTokenDefinition": + { + BaseIcon icon = new BaseIcon(export, exportType, assetName); + int height = icon.Size + icon.AdditionalSize; + using (var ret = new SKBitmap(icon.Size, height, SKColorType.Rgba8888, SKAlphaType.Opaque)) + using (var c = new SKCanvas(ret)) + { + Rarity.DrawRarity(c, icon); + LargeSmallImage.DrawPreviewImage(c, icon); + if ((EIconDesign)Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoText) + { + Text.DrawBackground(c, icon); + Text.DrawDisplayName(c, icon); + Text.DrawDescription(c, icon); + if ((EIconDesign)Properties.Settings.Default.AssetsIconDesign != EIconDesign.Mini) + { + if (!icon.ShortDescription.Equals(icon.DisplayName) && !icon.ShortDescription.Equals(icon.Description)) + Text.DrawToBottom(c, icon, ETextSide.Left, icon.ShortDescription); + Text.DrawToBottom(c, icon, ETextSide.Right, icon.CosmeticSource); + } + } + UserFacingFlag.DrawUserFacingFlags(c, icon); + + // has more things to show + if (height > icon.Size) + { + Statistics.DrawStats(c, icon); + } + + Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512 + ImageBoxVm.imageBoxViewModel.Set(ret, assetName); + } + return true; + } + case "FortItemSeriesDefinition": + { + BaseIcon icon = new BaseIcon(); + using (var ret = new SKBitmap(icon.Size, icon.Size, SKColorType.Rgba8888, SKAlphaType.Opaque)) + using (var c = new SKCanvas(ret)) + { + Serie.GetRarity(icon, export); + Rarity.DrawRarity(c, icon); + + Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512 + ImageBoxVm.imageBoxViewModel.Set(ret, assetName); + } + return true; + } + case "PlaylistUserOptionEnum": + case "PlaylistUserOptionBool": + case "PlaylistUserOptionString": + case "PlaylistUserOptionIntEnum": + case "PlaylistUserOptionIntRange": + case "PlaylistUserOptionColorEnum": + case "PlaylistUserOptionFloatEnum": + case "PlaylistUserOptionFloatRange": + case "PlaylistUserOptionPrimaryAsset": + case "PlaylistUserOptionCollisionProfileEnum": + { + BaseUserOption icon = new BaseUserOption(export); + using (var ret = new SKBitmap(icon.Width, icon.Height, SKColorType.Rgba8888, SKAlphaType.Opaque)) + using (var c = new SKCanvas(ret)) + { + icon.Draw(c); + + Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512 + ImageBoxVm.imageBoxViewModel.Set(ret, assetName); + } + return true; + } + case "FortChallengeBundleItemDefinition": + { + BaseBundle icon = new BaseBundle(export, assetFolder); + using (var ret = new SKBitmap(icon.Width, icon.HeaderHeight + icon.AdditionalSize, SKColorType.Rgba8888, SKAlphaType.Opaque)) + using (var c = new SKCanvas(ret)) + { + HeaderStyle.DrawHeaderPaint(c, icon); + HeaderStyle.DrawHeaderText(c, icon); + QuestStyle.DrawQuests(c, icon); + QuestStyle.DrawCompletionRewards(c, icon); + + ImageBoxVm.imageBoxViewModel.Set(ret, assetName); + } + return true; + } + } + return false; + } + } +} diff --git a/FModel/Creator/Icons/DisplayAssetImage.cs b/FModel/Creator/Icons/DisplayAssetImage.cs new file mode 100644 index 00000000..df232b55 --- /dev/null +++ b/FModel/Creator/Icons/DisplayAssetImage.cs @@ -0,0 +1,55 @@ +using PakReader.Pak; +using PakReader.Parsers.Class; +using PakReader.Parsers.PropertyTagData; + +namespace FModel.Creator.Icons +{ + static class DisplayAssetImage + { + public static bool GetDisplayAssetImage(BaseIcon icon, SoftObjectProperty o, string assetName) + { + string imageType = "DetailsImage"; + switch ("DA_Featured_" + assetName) + { + case "DA_Featured_Glider_ID_141_AshtonBoardwalk.uasset": + case "DA_Featured_Glider_ID_150_TechOpsBlue.uasset": + case "DA_Featured_Glider_ID_131_SpeedyMidnight.uasset": + case "DA_Featured_Pickaxe_ID_178_SpeedyMidnight.uasset": + case "DA_Featured_Glider_ID_015_Brite.uasset": + case "DA_Featured_Glider_ID_016_Tactical.uasset": + case "DA_Featured_Glider_ID_017_Assassin.uasset": + case "DA_Featured_Pickaxe_ID_027_Scavenger.uasset": + case "DA_Featured_Pickaxe_ID_028_Space.uasset": + case "DA_Featured_Pickaxe_ID_029_Assassin.uasset": + return false; + case "DA_Featured_Glider_ID_070_DarkViking.uasset": + case "DA_Featured_CID_319_Athena_Commando_F_Nautilus.uasset": + imageType = "TileImage"; + break; + } + + string path = o?.Value.AssetPathName.String; + if (string.IsNullOrEmpty(path)) + path = "/Game/Catalog/DisplayAssets/DA_Featured_" + assetName.Substring(0, assetName.LastIndexOf(".")); + + PakPackage p = Utils.GetPropertyPakPackage(path); + if (p.HasExport() && !p.Equals(default)) + { + var obj = p.GetExport(); + if (obj != null) + { + if (obj.TryGetValue(imageType, out var v1) && v1 is StructProperty s && s.Value is UObject type && + type.TryGetValue("ResourceObject", out var v2) && v2 is ObjectProperty resourceObject) + { + if (!resourceObject.Value.Resource.OuterIndex.Resource.ObjectName.String.Contains("/Game/Athena/Prototype/Textures/")) + { + icon.IconImage = Utils.GetObjectTexture(resourceObject); + return true; + } + } + } + } + return false; + } + } +} diff --git a/FModel/Creator/Icons/LargeSmallImage.cs b/FModel/Creator/Icons/LargeSmallImage.cs new file mode 100644 index 00000000..3bd18e77 --- /dev/null +++ b/FModel/Creator/Icons/LargeSmallImage.cs @@ -0,0 +1,41 @@ +using PakReader.Pak; +using PakReader.Parsers.Class; +using PakReader.Parsers.PropertyTagData; +using SkiaSharp; + +namespace FModel.Creator.Icons +{ + static class LargeSmallImage + { + public static void GetPreviewImage(BaseIcon icon, StructProperty u) + { + if (u.Value is UObject o && o.TryGetValue("ResourceObject", out var v) && v is ObjectProperty resourceObject) + icon.IconImage = Utils.GetObjectTexture(resourceObject); + } + public static void GetPreviewImage(BaseIcon icon, ObjectProperty o, string assetName) => GetPreviewImage(icon, o, assetName, true); + public static void GetPreviewImage(BaseIcon icon, ObjectProperty o, string assetName, bool hightRes) + { + string path = o.Value.Resource.OuterIndex.Resource.ObjectName.String; + if (path.Equals("/Game/Athena/Items/Weapons/WID_Harvest_Pickaxe_STWCosmetic_Tier")) + path += "_" + assetName.Substring(assetName.LastIndexOf(".") - 1, 1); + + PakPackage p = Utils.GetPropertyPakPackage(path); + if (p.HasExport() && !p.Equals(default)) + { + var obj = p.GetExport(); + if (obj != null) + { + if (hightRes && obj.TryGetValue("LargePreviewImage", out var sLarge) && sLarge is SoftObjectProperty largePreviewImage) + GetPreviewImage(icon, largePreviewImage); + else if (obj.TryGetValue("SmallPreviewImage", out var sSmall) && sSmall is SoftObjectProperty smallPreviewImage) + GetPreviewImage(icon, smallPreviewImage); + } + } + } + public static void GetPreviewImage(BaseIcon icon, SoftObjectProperty s) => icon.IconImage = Utils.GetSoftObjectTexture(s); + + public static void DrawPreviewImage(SKCanvas c, BaseIcon icon) => + c.DrawBitmap(icon.IconImage ?? icon.FallbackImage, new SKRect(icon.Margin, icon.Margin, icon.Size - icon.Margin, icon.Size - icon.Margin), + new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); + } +} diff --git a/FModel/Creator/Icons/UserFacingFlag.cs b/FModel/Creator/Icons/UserFacingFlag.cs new file mode 100644 index 00000000..a8ac086c --- /dev/null +++ b/FModel/Creator/Icons/UserFacingFlag.cs @@ -0,0 +1,72 @@ +using PakReader.Pak; +using PakReader.Parsers.Class; +using PakReader.Parsers.Objects; +using PakReader.Parsers.PropertyTagData; +using SkiaSharp; +using System; +using System.Collections.Generic; +using System.Windows; + +namespace FModel.Creator.Icons +{ + static class UserFacingFlag + { + public static void GetUserFacingFlags(List uffs, BaseIcon icon, string exportType) + { + if (uffs.Count > 0) + { + PakPackage p = Utils.GetPropertyPakPackage("/Game/Items/ItemCategories"); //PrimaryCategories - SecondaryCategories - TertiaryCategories + if (p.HasExport() && !p.Equals(default)) + { + var o = p.GetExport(); + if (o != null && o.TryGetValue("TertiaryCategories", out var tertiaryCategories) && tertiaryCategories is ArrayProperty tertiaryArray) + { + icon.UserFacingFlags = new SKBitmap[uffs.Count]; + for (int i = 0; i < uffs.Count; i++) + { + if (uffs[i].Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests")) + { + if (exportType.Equals("AthenaPetCarrierItemDefinition")) + icon.UserFacingFlags[i] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png")).Stream); + else + icon.UserFacingFlags[i] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png")).Stream); + } + else + { + foreach (StructProperty structProp in tertiaryArray.Value) + { + if (structProp.Value is UObject mainUObject && + mainUObject.TryGetValue("TagContainer", out var struc1) && struc1 is StructProperty tagContainer && tagContainer.Value is FGameplayTagContainer f && f.GameplayTags.TryGetGameplayTag(uffs[i], out var _) && + mainUObject.TryGetValue("CategoryBrush", out var struc2) && struc2 is StructProperty categoryBrush && categoryBrush.Value is UObject categoryUObject && + categoryUObject.TryGetValue("Brush_XXS", out var struc3) && struc3 is StructProperty brushXXS && brushXXS.Value is UObject brushUObject && + brushUObject.TryGetValue("ResourceObject", out var object1) && object1 is ObjectProperty resourceObject) + { + icon.UserFacingFlags[i] = Utils.GetObjectTexture(resourceObject); + break; + } + } + } + } + } + } + } + } + + public static void DrawUserFacingFlags(SKCanvas c, BaseIcon icon) + { + if (icon.UserFacingFlags != null) + { + int size = 25; + int x = icon.Margin * (int)2.5; + foreach (SKBitmap b in icon.UserFacingFlags) + { + if (b == null) + continue; + + c.DrawBitmap(b.Resize(size, size), new SKPoint(x, icon.Margin * (int)2.5), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); + x += size; + } + } + } + } +} diff --git a/FModel/Creator/Icons/Watermark.cs b/FModel/Creator/Icons/Watermark.cs new file mode 100644 index 00000000..c6b952c3 --- /dev/null +++ b/FModel/Creator/Icons/Watermark.cs @@ -0,0 +1,31 @@ +using SkiaSharp; +using System.IO; + +namespace FModel.Creator.Icons +{ + static class Watermark + { + public static void DrawWatermark(SKCanvas c) + { + if (Properties.Settings.Default.UseIconWatermark && !string.IsNullOrWhiteSpace(Properties.Settings.Default.IconWatermarkPath)) + { + using SKBitmap watermarkBase = SKBitmap.Decode(new FileInfo(Properties.Settings.Default.IconWatermarkPath).Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); + int sizeX = watermarkBase.Width * (int)Properties.Settings.Default.IconWatermarkScale / 512; + int sizeY = watermarkBase.Height * (int)Properties.Settings.Default.IconWatermarkScale / 512; + SKBitmap watermark = watermarkBase.Resize(sizeX, sizeY); + + float left = Properties.Settings.Default.IconWatermarkX; + float top = Properties.Settings.Default.IconWatermarkY; + float right = left + watermark.Width; + float bottom = top + watermark.Height; + c.DrawBitmap(watermark, new SKRect(left, top, right, bottom), + new SKPaint + { + FilterQuality = SKFilterQuality.High, + IsAntialias = true, + Color = SKColors.Transparent.WithAlpha((byte)Properties.Settings.Default.IconWatermarkOpacity) + }); + } + } + } +} diff --git a/FModel/Creator/Rarities/EFortRarity.cs b/FModel/Creator/Rarities/EFortRarity.cs new file mode 100644 index 00000000..412e7988 --- /dev/null +++ b/FModel/Creator/Rarities/EFortRarity.cs @@ -0,0 +1,15 @@ +namespace FModel.Creator.Rarities +{ + public enum EFortRarity : int + { + Common, + Uncommon, + Rare, + Epic, + Legendary, + Mythic, + Transcendent, + Unattainable, + //Impossible + } +} diff --git a/FModel/Creator/Rarities/Rarity.cs b/FModel/Creator/Rarities/Rarity.cs new file mode 100644 index 00000000..069a9897 --- /dev/null +++ b/FModel/Creator/Rarities/Rarity.cs @@ -0,0 +1,171 @@ +using PakReader.Pak; +using PakReader.Parsers.Class; +using PakReader.Parsers.Objects; +using PakReader.Parsers.PropertyTagData; +using SkiaSharp; +using System.Linq; + +namespace FModel.Creator.Rarities +{ + static class Rarity + { + public static void GetInGameRarity(BaseIcon icon, EnumProperty e) + { + PakPackage p = Utils.GetPropertyPakPackage("/Game/Balance/RarityData"); + if (p.HasExport() && !p.Equals(default)) + { + var d = p.GetExport(); + if (d != null) + { + EFortRarity rarity = EFortRarity.Uncommon; + switch (e?.Value.String) + { + case "EFortRarity::Common": + rarity = EFortRarity.Common; + break; + case "EFortRarity::Rare": + rarity = EFortRarity.Rare; + break; + case "EFortRarity::Epic": + case "EFortRarity::Quality": + rarity = EFortRarity.Epic; + break; + case "EFortRarity::Legendary": + rarity = EFortRarity.Legendary; + break; + case "EFortRarity::Mythic": + rarity = EFortRarity.Mythic; + break; + case "EFortRarity::Transcendent": + rarity = EFortRarity.Transcendent; + break; + case "EFortRarity::Unattainable": + rarity = EFortRarity.Unattainable; + break; + } + + if (d.Values.ElementAt((int)rarity) is StructProperty s && s.Value is UObject colors) + { + if (colors.TryGetValue("Color1", out var c1) && c1 is StructProperty s1 && s1.Value is FLinearColor color1 && + colors.TryGetValue("Color2", out var c2) && c2 is StructProperty s2 && s2.Value is FLinearColor color2 && + colors.TryGetValue("Color3", out var c3) && c3 is StructProperty s3 && s3.Value is FLinearColor color3) + { + icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) }; + icon.RarityBorderColor = new SKColor[2] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) }; + } + } + } + } + else GetHardCodedRarity(icon, e); + } + + public static void GetHardCodedRarity(BaseIcon icon, EnumProperty e) + { + switch (e?.Value.String) + { + case "EFortRarity::Common": + icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("6D6D6D"), SKColor.Parse("333333") }; + icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("9E9E9E"), SKColor.Parse("9E9E9E") }; + break; + case "EFortRarity::Rare": + icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("3669BB"), SKColor.Parse("133254") }; + icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("5180EE"), SKColor.Parse("5180EE") }; + break; + case "EFortRarity::Epic": + case "EFortRarity::Quality": + icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("8138C2"), SKColor.Parse("35155C") }; + icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("B251ED"), SKColor.Parse("B251ED") }; + break; + case "EFortRarity::Legendary": + icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("C06A38"), SKColor.Parse("5C2814") }; + icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("EC9650"), SKColor.Parse("EC9650") }; + break; + case "EFortRarity::Mythic": + icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("BA9C36"), SKColor.Parse("594415") }; + icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("EED951"), SKColor.Parse("EED951") }; + break; + case "EFortRarity::Transcendent": + icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("D51944"), SKColor.Parse("660522") }; + icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("FF3F58"), SKColor.Parse("FF3F58") }; + break; + } + } + + public static void DrawRarity(SKCanvas c, BaseIcon icon) + { + // border + c.DrawRect(new SKRect(0, 0, icon.Size, icon.Size), + new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Shader = SKShader.CreateLinearGradient( + new SKPoint(icon.Size / 2, icon.Size), + new SKPoint(icon.Size, icon.Size / 4), + icon.RarityBorderColor, + SKShaderTileMode.Clamp) + }); + + switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign) + { + case EIconDesign.Flat: + { + if (icon.RarityBackgroundImage != null) + c.DrawBitmap(icon.RarityBackgroundImage, new SKRect(icon.Margin, icon.Margin, icon.Size - icon.Margin, icon.Size - icon.Margin), + new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); + else + { + c.DrawRect(new SKRect(icon.Margin, icon.Margin, icon.Size - icon.Margin, icon.Size - icon.Margin), + new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Color = icon.RarityBackgroundColors[0] + }); + + var paint = new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Color = icon.RarityBackgroundColors[1].WithAlpha(75) + }; + var pathTop = new SKPath { FillType = SKPathFillType.EvenOdd }; + pathTop.MoveTo(icon.Margin, icon.Margin); + pathTop.LineTo(icon.Margin + (icon.Size / 17 * 10), icon.Margin); + pathTop.LineTo(icon.Margin, icon.Margin + (icon.Size / 17)); + pathTop.Close(); + c.DrawPath(pathTop, paint); + + var pathBottom = new SKPath { FillType = SKPathFillType.EvenOdd }; + pathBottom.MoveTo(icon.Margin, icon.Size - icon.Margin); + pathBottom.LineTo(icon.Margin, icon.Size - icon.Margin - (icon.Size / 17 * 2.5f)); + pathBottom.LineTo(icon.Size - icon.Margin, icon.Size - icon.Margin - (icon.Size / 17 * 4.5f)); + pathBottom.LineTo(icon.Size - icon.Margin, icon.Size - icon.Margin); + pathBottom.Close(); + c.DrawPath(pathBottom, paint); + } + break; + } + default: + { + if (icon.RarityBackgroundImage != null) + c.DrawBitmap(icon.RarityBackgroundImage, new SKRect(icon.Margin, icon.Margin, icon.Size - icon.Margin, icon.Size - icon.Margin), + new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); + else + c.DrawRect(new SKRect(icon.Margin, icon.Margin, icon.Size - icon.Margin, icon.Size - icon.Margin), + new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Shader = SKShader.CreateRadialGradient( + new SKPoint(icon.Size / 2, icon.Size / 2), + icon.Size / 5 * 4, + icon.RarityBackgroundColors, + SKShaderTileMode.Clamp) + }); + break; + } + } + } + } +} diff --git a/FModel/Creator/Rarities/Serie.cs b/FModel/Creator/Rarities/Serie.cs new file mode 100644 index 00000000..fd037e89 --- /dev/null +++ b/FModel/Creator/Rarities/Serie.cs @@ -0,0 +1,40 @@ +using FModel.Creator.Texts; +using PakReader.Pak; +using PakReader.Parsers.Class; +using PakReader.Parsers.Objects; +using PakReader.Parsers.PropertyTagData; +using SkiaSharp; + +namespace FModel.Creator.Rarities +{ + static class Serie + { + public static void GetRarity(BaseIcon icon, ObjectProperty o) + { + PakPackage p = Utils.GetPropertyPakPackage(o.Value.Resource.OuterIndex.Resource.ObjectName.String); + if (p.HasExport() && !p.Equals(default)) + { + var obj = p.GetExport(); + if (obj != null) + GetRarity(icon, obj); + } + } + + public static void GetRarity(BaseIcon icon, IUExport export) + { + if (export.TryGetValue("BackgroundTexture", out var t) && t is SoftObjectProperty sop) + icon.RarityBackgroundImage = Utils.GetSoftObjectTexture(sop); + + if (export.TryGetValue("Colors", out var v) && v is StructProperty s && s.Value is UObject colors) + { + if (colors.TryGetValue("Color1", out var c1) && c1 is StructProperty s1 && s1.Value is FLinearColor color1 && + colors.TryGetValue("Color2", out var c2) && c2 is StructProperty s2 && s2.Value is FLinearColor color2 && + colors.TryGetValue("Color4", out var c4) && c4 is StructProperty s4 && s4.Value is FLinearColor color4) + { + icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse(color1.Hex), SKColor.Parse(color4.Hex) }; + icon.RarityBorderColor = new SKColor[2] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) }; + } + } + } + } +} diff --git a/FModel/Creator/Stats/Statistic.cs b/FModel/Creator/Stats/Statistic.cs new file mode 100644 index 00000000..0730064e --- /dev/null +++ b/FModel/Creator/Stats/Statistic.cs @@ -0,0 +1,10 @@ +using SkiaSharp; + +namespace FModel.Creator.Stats +{ + public class Statistic + { + public SKBitmap Icon; + public string Description; + } +} diff --git a/FModel/Creator/Stats/Statistics.cs b/FModel/Creator/Stats/Statistics.cs new file mode 100644 index 00000000..3b687c35 --- /dev/null +++ b/FModel/Creator/Stats/Statistics.cs @@ -0,0 +1,203 @@ +using FModel.Creator.Texts; +using FModel.Utils; +using PakReader.Pak; +using PakReader.Parsers.Class; +using PakReader.Parsers.PropertyTagData; +using SkiaSharp; +using System; +using System.Windows; + +namespace FModel.Creator.Stats +{ + static class Statistics + { + public static void GetAmmoData(BaseIcon icon, SoftObjectProperty ammoData) + { + PakPackage p = Utils.GetPropertyPakPackage(ammoData.Value.AssetPathName.String); + if (p.HasExport() && !p.Equals(default)) + { + var obj = p.GetExport(); + if (obj != null) + { + if (obj.TryGetValue("DisplayName", out var v1) && v1 is TextProperty displayName && + obj.TryGetValue("SmallPreviewImage", out var v2) && v2 is SoftObjectProperty smallPreviewImage) + { + icon.Stats.Add(new Statistic + { + Icon = Utils.GetSoftObjectTexture(smallPreviewImage), + Description = Text.GetTextPropertyBase(displayName).ToUpper() + }); + } + } + } + } + + public static void GetWeaponStats(BaseIcon icon, StructProperty weaponStatHandle) + { + if (weaponStatHandle.Value is UObject o1 && + o1.TryGetValue("DataTable", out var c1) && c1 is ObjectProperty dataTable && + o1.TryGetValue("RowName", out var c2) && c2 is NameProperty rowName) + { + PakPackage p = Utils.GetPropertyPakPackage(dataTable.Value.Resource.OuterIndex.Resource.ObjectName.String); + if (p.HasExport() && !p.Equals(default)) + { + var table = p.GetExport(); + if (table != null) + { + if (table.TryGetValue(rowName.Value.String, out var v1) && v1 is UObject stats) + { + if (stats.TryGetValue("ReloadTime", out var s1) && s1 is FloatProperty reloadTime && reloadTime.Value != 0) + icon.Stats.Add(new Statistic + { + Icon = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_ReloadTime_Weapon_Stats.png")).Stream), + Description = $"{Localizations.GetLocalization(string.Empty, "6EA26D1A4252034FBD869A90F9A6E49A", "Reload Time")} ({Localizations.GetLocalization(string.Empty, "6BA53D764BA5CC13E821D2A807A72365", "seconds")}) : {reloadTime.Value:0.0}".ToUpper() + }); + + if (stats.TryGetValue("ClipSize", out var s2) && s2 is IntProperty clipSize && clipSize.Value != 0) + icon.Stats.Add(new Statistic + { + Icon = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_ClipSize_Weapon_Stats.png")).Stream), + Description = $"{Localizations.GetLocalization(string.Empty, "068239DD4327B36124498C9C5F61C038", "Magazine Size")} : {clipSize.Value}".ToUpper() + }); + + if (stats.TryGetValue("DmgPB", out var s3) && s3 is FloatProperty dmgPB && dmgPB.Value != 0) + icon.Stats.Add(new Statistic + { + Icon = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_DamagePerBullet_Weapon_Stats.png")).Stream), + Description = $"{Localizations.GetLocalization(string.Empty, "BF7E3CF34A9ACFF52E95EAAD4F09F133", "Damage to Player")} : {dmgPB.Value}".ToUpper() + }); + } + } + } + } + } + + public static void GetHeroStats(BaseIcon icon, ObjectProperty heroGameplayDefinition) + { + PakPackage p = Utils.GetPropertyPakPackage(heroGameplayDefinition.Value.Resource.OuterIndex.Resource.ObjectName.String); + if (p.HasExport() && !p.Equals(default)) + { + var obj = p.GetExport(); + if (obj != null) + { + if (obj.TryGetValue("HeroPerk", out var v1) && v1 is StructProperty s1 && s1.Value is UObject heroPerk) + { + GetAbilityKit(icon, heroPerk); + } + + if (obj.TryGetValue("TierAbilityKits", out var v2) && v2 is ArrayProperty tierAbilityKits) + { + foreach (StructProperty abilityKit in tierAbilityKits.Value) + { + if (abilityKit.Value is UObject kit) + { + GetAbilityKit(icon, kit); + } + } + } + } + } + } + + private static void GetAbilityKit(BaseIcon icon, UObject parent) + { + if (parent.TryGetValue("GrantedAbilityKit", out var v) && v is SoftObjectProperty grantedAbilityKit) + { + PakPackage k = Utils.GetPropertyPakPackage(grantedAbilityKit.Value.AssetPathName.String); + if (k.HasExport() && !k.Equals(default)) + { + var kit = k.GetExport(); + if (kit != null && + kit.GetExport("DisplayName") is TextProperty displayName && + kit.GetExport("IconBrush") is StructProperty brush && brush.Value is UObject iconBrush && + iconBrush.TryGetValue("ResourceObject", out var s) && s is ObjectProperty resourceObject) + { + icon.Stats.Add(new Statistic + { + Icon = Utils.GetObjectTexture(resourceObject), + Description = Text.GetTextPropertyBase(displayName).ToUpper() + }); + } + } + } + } + + public static void DrawStats(SKCanvas c, BaseIcon icon) + { + int size = 48; + int iconSize = 40; + int textSize = 25; + int y = icon.Size; + foreach (Statistic stat in icon.Stats) + { + c.DrawRect(new SKRect(0, y, icon.Size, y + size), + new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Shader = SKShader.CreateLinearGradient( + new SKPoint(icon.Size / 2, icon.Size), + new SKPoint(icon.Size, icon.Size / 4), + icon.RarityBorderColor, + SKShaderTileMode.Clamp) + }); + + + if ((EIconDesign)Properties.Settings.Default.AssetsIconDesign == EIconDesign.Flat) + { + c.DrawRect(new SKRect(icon.Margin, y, icon.Size - icon.Margin, y + size - icon.Margin), + new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Color = icon.RarityBackgroundColors[0] + }); + } + else + { + c.DrawRect(new SKRect(icon.Margin, y, icon.Size - icon.Margin, y + size - icon.Margin), + new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Shader = SKShader.CreateRadialGradient( + new SKPoint(icon.Size / 2, icon.Size / 2), + icon.Size / 5 * 4, + icon.RarityBackgroundColors, + SKShaderTileMode.Clamp) + }); + } + + c.DrawRect(new SKRect(icon.Margin, y, icon.Size - icon.Margin, y + size - icon.Margin), + new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Color = new SKColor(0, 0, 50, 75) + }); + + c.DrawBitmap(stat.Icon.Resize(iconSize, iconSize), new SKPoint(icon.Margin * (int)2.5, y + 4), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); + + var statPaint = new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Typeface = Text.TypeFaces.DisplayNameTypeface, + TextSize = textSize, + Color = SKColors.White, + TextAlign = SKTextAlign.Center, + }; + + // resize if too long + while (statPaint.MeasureText(stat.Description) > (icon.Size - (icon.Margin * 2) - iconSize)) + { + statPaint.TextSize = textSize -= 2; + } + + c.DrawText(stat.Description, icon.Size / 2, y + 32, statPaint); + + y += size; + } + } + } +} diff --git a/FModel/Creator/Texts/ETextSide.cs b/FModel/Creator/Texts/ETextSide.cs new file mode 100644 index 00000000..2d4d9aba --- /dev/null +++ b/FModel/Creator/Texts/ETextSide.cs @@ -0,0 +1,9 @@ +namespace FModel.Creator.Texts +{ + public enum ETextSide + { + Center, + Right, + Left + } +} diff --git a/FModel/Creator/Texts/GameplayTag.cs b/FModel/Creator/Texts/GameplayTag.cs new file mode 100644 index 00000000..24dbd4ed --- /dev/null +++ b/FModel/Creator/Texts/GameplayTag.cs @@ -0,0 +1,72 @@ +using FModel.Creator.Icons; +using FModel.Utils; +using PakReader.Pak; +using PakReader.Parsers.Class; +using PakReader.Parsers.Objects; +using PakReader.Parsers.PropertyTagData; + +namespace FModel.Creator.Texts +{ + static class GameplayTag + { + public static void GetGameplayTags(BaseIcon icon, StructProperty s, string exportType) + { + if (s.Value is FGameplayTagContainer g) + { + if (g.GameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source)) + icon.CosmeticSource = source.String.Substring("Cosmetics.Source.".Length); + else if(g.GameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action)) + icon.CosmeticSource = action.String.Substring("Athena.ItemAction.".Length); + + if (g.GameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set)) + icon.Description += GetCosmeticSet(set.String); + if (g.GameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season)) + icon.Description += GetCosmeticSeason(season.String); + + UserFacingFlag.GetUserFacingFlags( + g.GameplayTags.GetAllGameplayTag("Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."), + icon, exportType); + } + } + + private static string GetCosmeticSet(string setName) + { + PakPackage p = Utils.GetPropertyPakPackage("/Game/Athena/Items/Cosmetics/Metadata/CosmeticSets"); + if (p.HasExport() && !p.Equals(default)) + { + var d = p.GetExport(); + if (d != null && d.TryGetValue(setName, out var obj) && obj is UObject o) + { + if (o.TryGetValue("DisplayName", out var displayName) && displayName is TextProperty t) + { + (string n, string k, string s) = Text.GetTextPropertyBases(t); + string set = Localizations.GetLocalization(n, k, s); + string format = Localizations.GetLocalization("Fort.Cosmetics", "CosmeticItemDescription_SetMembership_NotRich", "\nPart of the {0} set."); + return string.Format(format, set); + } + } + } + return string.Empty; + } + + private static string GetCosmeticSeason(string seasonNumber) + { + string s = seasonNumber.Substring("Cosmetics.Filter.Season.".Length); + int number = int.Parse(s); + if (number == 10) + s = "X"; + + string season = Localizations.GetLocalization("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}"); + string introduced = Localizations.GetLocalization("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in {0}.") + .Replace("", string.Empty).Replace("", string.Empty); + if (number > 10) + { + string chapter = Localizations.GetLocalization("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}"); + string chapterFormat = Localizations.GetLocalization("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}"); + string d = string.Format(chapterFormat, string.Format(chapter, number / 10 + 1), string.Format(season, s[^1..])); + return string.Format(introduced, d); + } + else return string.Format(introduced, string.Format(season, s)); + } + } +} diff --git a/FModel/Creator/Texts/Helper.cs b/FModel/Creator/Texts/Helper.cs new file mode 100644 index 00000000..933dc50a --- /dev/null +++ b/FModel/Creator/Texts/Helper.cs @@ -0,0 +1,110 @@ +using SkiaSharp; +using System; +using System.Collections.Generic; +using System.Text; + +namespace FModel.Creator.Texts +{ + static class Helper + { + public class Line + { + public string Value { get; set; } + public float Width { get; set; } + } + + public static void DrawCenteredMultilineText(SKCanvas canvas, string text, int maxLineCount, BaseIcon icon, ETextSide side, SKRect area, SKPaint paint) + => DrawCenteredMultilineText(canvas, text, maxLineCount, icon.Size, icon.Margin, side, area, paint); + public static void DrawCenteredMultilineText(SKCanvas canvas, string text, int maxLineCount, int size, int margin, ETextSide side, SKRect area, SKPaint paint) + { + float lineHeight = paint.TextSize * 1.2f; + Line[] lines = SplitLines(text, paint, area.Width - margin); + + if (lines == null) + return; + if (lines.Length <= maxLineCount) + maxLineCount = lines.Length; + + float height = maxLineCount * lineHeight; + float y = area.MidY - height / 2; + for (int i = 0; i < maxLineCount; i++) + { + y += lineHeight; + float x = side switch + { + ETextSide.Center => area.MidX - lines[i].Width / 2, + ETextSide.Right => size - margin - lines[i].Width, + ETextSide.Left => margin, + _ => area.MidX - lines[i].Width / 2 + }; + canvas.DrawText(lines[i].Value.TrimEnd(), x, y, paint); + } + } + + public static void DrawMultilineText(SKCanvas canvas, string text, int size, int margin, ETextSide side, SKRect area, SKPaint paint, out int yPos) + { + float lineHeight = paint.TextSize * 1.2f; + Line[] lines = SplitLines(text, paint, area.Width - margin); + if (lines == null) + { + yPos = (int)area.Top; + return; + } + + float y = area.Top; + for (int i = 0; i < lines.Length; i++) + { + float x = side switch + { + ETextSide.Center => area.MidX - lines[i].Width / 2, + ETextSide.Right => size - margin - lines[i].Width, + ETextSide.Left => margin, + _ => area.MidX - lines[i].Width / 2 + }; + canvas.DrawText(lines[i].Value.TrimEnd(), x, y, paint); + y += lineHeight; + } + yPos = (int)area.Top + ((int)lineHeight * lines.Length); + } + + public static Line[] SplitLines(string text, SKPaint paint, float maxWidth) + { + if (string.IsNullOrEmpty(text)) + return null; + + float spaceWidth = paint.MeasureText(" "); + string[] lines = text.Split('\n', StringSplitOptions.RemoveEmptyEntries); + + List ret = new List(lines.Length); + for (int i = 0; i < lines.Length; i++) + { + if (string.IsNullOrWhiteSpace(lines[i])) + continue; + + float width = 0; + var lineResult = new StringBuilder(); + string[] words = lines[i].Split(' ', StringSplitOptions.None); + foreach (var word in words) + { + float wordWidth = paint.MeasureText(word); + float wordWithSpaceWidth = wordWidth + spaceWidth; + string wordWithSpace = word + " "; + + if (width + wordWidth > maxWidth) + { + ret.Add(new Line { Value = lineResult.ToString(), Width = width }); + lineResult = new StringBuilder(wordWithSpace); + width = wordWithSpaceWidth; + } + else + { + lineResult.Append(wordWithSpace); + width += wordWithSpaceWidth; + } + } + ret.Add(new Line { Value = lineResult.ToString(), Width = width }); + } + return ret.ToArray(); + } + } +} diff --git a/FModel/Creator/Texts/Text.cs b/FModel/Creator/Texts/Text.cs new file mode 100644 index 00000000..b2cb71b4 --- /dev/null +++ b/FModel/Creator/Texts/Text.cs @@ -0,0 +1,217 @@ +using PakReader.Pak; +using PakReader.Parsers.Class; +using PakReader.Parsers.Objects; +using PakReader.Parsers.PropertyTagData; +using SkiaSharp; +using System.Collections.Generic; + +namespace FModel.Creator.Texts +{ + static class Text + { + public static Typefaces TypeFaces = new Typefaces(); + private const int _STARTER_TEXT_POSITION = 380; + private static int _BOTTOM_TEXT_SIZE = 15; + private static int _NAME_TEXT_SIZE = 45; + + public static string GetTextPropertyBase(TextProperty t) + { + if (t.Value is FText text) + if (text.Text is FTextHistory.Base b) + return b.SourceString.Replace("", string.Empty).Replace("", string.Empty); + else if (text.Text is FTextHistory.StringTableEntry s) + { + PakPackage p = Utils.GetPropertyPakPackage(s.TableId.String); + if (p.HasExport() && !p.Equals(default)) + { + var table = p.GetExport(); + if (table != null) + { + if (table.TryGetValue("StringTable", out var v1) && v1 is FStringTable stringTable && + stringTable.KeysToMetadata.TryGetValue(stringTable.TableNamespace, out var v2) && v2 is Dictionary dico && + dico.TryGetValue(s.Key, out var ret)) + { + return ret; + } + } + } + } + + return string.Empty; + } + public static string GetTextPropertyBase(ArrayProperty a) + { + if (a.Value.Length > 0 && a.Value[0] is TextProperty t) + return GetTextPropertyBase(t); + return string.Empty; + } + + public static (string, string, string) GetTextPropertyBases(TextProperty t) + { + if (t.Value is FText text && text.Text is FTextHistory.Base b) + return (b.Namespace, b.Key, b.SourceString); + return (string.Empty, string.Empty, string.Empty); + } + + public static string GetMaxStackSize(StructProperty maxStackSize) + { + if (maxStackSize.Value is UObject o1) + { + if (o1.TryGetValue("Value", out var c) && c is FloatProperty value && value.Value != -1) // old way + return $"MaxStackSize : {value.Value}"; + else if ( + o1.TryGetValue("Curve", out var c1) && c1 is StructProperty curve && curve.Value is UObject o2 && + o2.TryGetValue("CurveTable", out var c2) && c2 is ObjectProperty curveTable && + o2.TryGetValue("RowName", out var c3) && c3 is NameProperty rowName) // new way + { + PakPackage p = Utils.GetPropertyPakPackage(curveTable.Value.Resource.OuterIndex.Resource.ObjectName.String); + if (p.HasExport() && !p.Equals(default)) + { + var table = p.GetExport(); + if (table != null) + { + if (table.TryGetValue(rowName.Value.String, out var v1) && v1 is UObject maxStackAmount && + maxStackAmount.TryGetValue("Keys", out var v2) && v2 is ArrayProperty keys && + keys.Value.Length > 0 && (keys.Value[0] as StructProperty).Value is FSimpleCurveKey amount && + amount.KeyValue != -1) + { + return $"MaxStackSize : {amount.KeyValue}"; + } + } + } + } + } + return string.Empty; + } + + public static void DrawBackground(SKCanvas c, BaseIcon icon) + { + switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign) + { + case EIconDesign.Flat: + { + var pathBottom = new SKPath { FillType = SKPathFillType.EvenOdd }; + pathBottom.MoveTo(icon.Margin, icon.Size - icon.Margin); + pathBottom.LineTo(icon.Margin, icon.Size - icon.Margin - (icon.Size / 17 * 2.5f)); + pathBottom.LineTo(icon.Size - icon.Margin, icon.Size - icon.Margin - (icon.Size / 17 * 4.5f)); + pathBottom.LineTo(icon.Size - icon.Margin, icon.Size - icon.Margin); + pathBottom.Close(); + c.DrawPath(pathBottom, new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Color = new SKColor(0, 0, 50, 75), + }); + break; + } + default: + { + c.DrawRect( + new SKRect(icon.Margin, _STARTER_TEXT_POSITION, icon.Size - icon.Margin, icon.Size - icon.Margin), + new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Color = new SKColor(0, 0, 50, 75), + }); + break; + } + } + } + + public static void DrawDisplayName(SKCanvas c, BaseIcon icon) + { + _NAME_TEXT_SIZE = 45; + string text = icon.DisplayName; + SKTextAlign side = SKTextAlign.Center; + int x = icon.Size / 2; + int y = _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE; + switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign) + { + case EIconDesign.Mini: + { + _NAME_TEXT_SIZE = 47; + text = text.ToUpper(); + break; + } + case EIconDesign.Flat: + { + _NAME_TEXT_SIZE = 47; + side = SKTextAlign.Right; + x = icon.Size - icon.Margin * 2; + break; + } + } + + SKPaint namePaint = new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Typeface = TypeFaces.DisplayNameTypeface, + TextSize = _NAME_TEXT_SIZE, + Color = SKColors.White, + TextAlign = side, + }; + + // resize if too long + while (namePaint.MeasureText(text) > (icon.Size - (icon.Margin * 2))) + { + namePaint.TextSize = _NAME_TEXT_SIZE -= 2; + } + + c.DrawText(text, x, y, namePaint); + } + + public static void DrawDescription(SKCanvas c, BaseIcon icon) + { + int maxLine = 4; + _BOTTOM_TEXT_SIZE = 15; + string text = icon.Description; + ETextSide side = ETextSide.Center; + switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign) + { + case EIconDesign.Mini: + { + maxLine = 5; + _BOTTOM_TEXT_SIZE = icon.Margin; + text = text.ToUpper(); + break; + } + case EIconDesign.Flat: + { + side = ETextSide.Right; + break; + } + } + + SKPaint descriptionPaint = new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Typeface = TypeFaces.DescriptionTypeface, + TextSize = 13, + Color = SKColors.White, + }; + + // wrap if too long + Helper.DrawCenteredMultilineText(c, text, maxLine, icon, side, + new SKRect(icon.Margin, _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE, icon.Size - icon.Margin, icon.Size - _BOTTOM_TEXT_SIZE), + descriptionPaint); + } + + public static void DrawToBottom(SKCanvas c, BaseIcon icon, ETextSide side, string text) + { + SKPaint shortDescriptionPaint = new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Typeface = side == ETextSide.Left ? TypeFaces.DisplayNameTypeface : TypeFaces.DefaultTypeface, + TextSize = 15, + Color = SKColors.White, + TextAlign = side == ETextSide.Left ? SKTextAlign.Left : SKTextAlign.Right, + }; + + c.DrawText(text, side == ETextSide.Left ? icon.Margin * 2.5f : icon.Size - (icon.Margin * 2.5f), icon.Size - (icon.Margin * 2.5f), shortDescriptionPaint); + } + } +} diff --git a/FModel/Creator/Texts/Typefaces.cs b/FModel/Creator/Texts/Typefaces.cs new file mode 100644 index 00000000..05d7444f --- /dev/null +++ b/FModel/Creator/Texts/Typefaces.cs @@ -0,0 +1,105 @@ +using FModel.Utils; +using FModel.ViewModels.DataGrid; +using SkiaSharp; +using System; +using System.Windows; + +namespace FModel.Creator.Texts +{ + public class Typefaces + { +#pragma warning disable IDE0051 + private const string _BASE_PATH = "/Game/UI/Foundation/Fonts/"; + private const string _ASIA_ERINM = "AsiaERINM"; // korean fortnite + private const string _BURBANK_BIG_CONDENSED_BLACK = "BurbankBigCondensed-Black"; + private readonly Uri _BURBANK_BIG_CONDENSED_BOLD = new Uri("pack://application:,,,/Resources/BurbankBigCondensed-Bold.ttf"); // other languages fortnite unofficial + private const string _BURBANK_BIG_REGULAR_BLACK = "BurbankBigRegular-Black"; + private const string _BURBANK_BIG_REGULAR_BOLD = "BurbankBigRegular-Bold"; // official fortnite but it's too big so i use it for russian only + 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"; // traditional chinese fortnite + private const string _NOTO_SANS_SC_REGULAR = "NotoSansSC-Regular"; + private const string _NOTO_SANS_TC_BLACK = "NotoSansTC-Black"; // simplified 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"; +#pragma warning restore IDE0051 + + public SKTypeface DefaultTypeface; // used as default font for all untranslated strings (item source, ...) + public SKTypeface BundleDefaultTypeface; // used for the last folder string + public SKTypeface DisplayNameTypeface; + public SKTypeface DescriptionTypeface; + public SKTypeface BundleDisplayNameTypeface; + + public Typefaces() + { + DefaultTypeface = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD).Stream); + + ArraySegment[] t = Utils.GetPropertyArraySegmentByte(_BASE_PATH + _BURBANK_BIG_CONDENSED_BLACK); + if (t != null && t.Length == 3) + BundleDefaultTypeface = SKTypeface.FromStream(t[2].AsStream()); + else BundleDefaultTypeface = DefaultTypeface; + + string namePath = _BASE_PATH + ( + Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _ASIA_ERINM : + Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _BURBANK_BIG_REGULAR_BOLD : + Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NIS_JYAU : + Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _NOTO_SANS_ARABIC_BLACK : + Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _NOTO_SANS_SC_BLACK : + Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_TC_BLACK : + string.Empty); + if (!namePath.Equals(_BASE_PATH)) + { + t = Utils.GetPropertyArraySegmentByte(namePath); + if (t != null && t.Length == 3) + DisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream()); + } + else DisplayNameTypeface = DefaultTypeface; + + string descriptionPath = _BASE_PATH + ( + Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _NOTO_SANS_KR_REGULAR : + Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NOTO_SANS_JP_BOLD : + Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _NOTO_SANS_ARABIC_REGULAR : + Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _NOTO_SANS_SC_REGULAR : + Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_TC_REGULAR : + _NOTO_SANS_REGULAR); + t = Utils.GetPropertyArraySegmentByte(descriptionPath); + if (t != null && t.Length == 3) + DescriptionTypeface = SKTypeface.FromStream(t[2].AsStream()); + else DescriptionTypeface = DefaultTypeface; + + string bundleNamePath = _BASE_PATH + ( + Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _ASIA_ERINM : + Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _BURBANK_BIG_REGULAR_BOLD : + Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NIS_JYAU : + Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _NOTO_SANS_ARABIC_BLACK : + Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _NOTO_SANS_SC_BLACK : + Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_TC_BLACK : + string.Empty); + if (!bundleNamePath.Equals(_BASE_PATH)) + { + t = Utils.GetPropertyArraySegmentByte(bundleNamePath); + if (t != null && t.Length == 3) + BundleDisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream()); + } + else BundleDisplayNameTypeface = BundleDefaultTypeface; + } + + public bool NeedReload(bool forceReload) => forceReload ? + DataGridVm.dataGridViewModel.Count > 0 : //reload only if at least one pak is loaded + DataGridVm.dataGridViewModel.Count > 0 && (BundleDefaultTypeface == DefaultTypeface && DisplayNameTypeface == DefaultTypeface && DescriptionTypeface == DefaultTypeface && BundleDisplayNameTypeface == BundleDefaultTypeface); + } +} diff --git a/FModel/Creator/Utils.cs b/FModel/Creator/Utils.cs new file mode 100644 index 00000000..5b18e873 --- /dev/null +++ b/FModel/Creator/Utils.cs @@ -0,0 +1,78 @@ +using FModel.Utils; +using PakReader.Pak; +using PakReader.Parsers.Class; +using PakReader.Parsers.PropertyTagData; +using SkiaSharp; +using System; + +namespace FModel.Creator +{ + static class Utils + { + public static string GetFullPath(string partialPath) + { + foreach (var fileReader in Globals.CachedPakFiles.Values) + if (fileReader.TryGetPartialKey(partialPath, out var fullPath)) + { + return fullPath; + } + return string.Empty; + } + + public static PakPackage GetPropertyPakPackage(string value) + { + string path = Strings.FixPath(value); + foreach (var fileReader in Globals.CachedPakFiles.Values) + if (fileReader.TryGetValue(path, out var entry)) + { + // kinda sad to use Globals.CachedPakFileMountPoint when the mount point is already in the path ¯\_(ツ)_/¯ + string mount = path.Substring(0, path.Length - entry.Name.Substring(0, entry.Name.LastIndexOf(".")).Length); + return Assets.GetPakPackage(entry, mount); + } + return default; + } + + public static ArraySegment[] GetPropertyArraySegmentByte(string value) + { + string path = Strings.FixPath(value); + foreach (var fileReader in Globals.CachedPakFiles.Values) + if (fileReader.TryGetValue(path, out var entry)) + { + // kinda sad to use Globals.CachedPakFileMountPoint when the mount point is already in the path ¯\_(ツ)_/¯ + string mount = path.Substring(0, path.Length - entry.Name.Substring(0, entry.Name.LastIndexOf(".")).Length); + return Assets.GetArraySegmentByte(entry, mount); + } + return default; + } + + public static SKBitmap NewZeroedBitmap(int width, int height) => new SKBitmap(new SKImageInfo(width, height), SKBitmapAllocFlags.ZeroPixels); + public static SKBitmap Resize(this SKBitmap me, int width, int height) + { + var bmp = NewZeroedBitmap(width, height); + using var pixmap = bmp.PeekPixels(); + me.ScalePixels(pixmap, SKFilterQuality.Medium); + return bmp; + } + + public static SKBitmap GetObjectTexture(ObjectProperty o) => GetTexture(o.Value.Resource.OuterIndex.Resource.ObjectName.String); + public static SKBitmap GetSoftObjectTexture(SoftObjectProperty s) => GetTexture(s.Value.AssetPathName.String); + public static SKBitmap GetTexture(string s) + { + PakPackage p = GetPropertyPakPackage(s); + if (p.HasExport() && !p.Equals(default)) + { + var i = p.GetExport(); + if (i != null) + return SKBitmap.Decode(i.Image.Encode()); + + var u = p.GetExport(); + if (u != null) + if (u.TryGetValue("TextureParameterValues", out var v) && v is ArrayProperty a) + if (a.Value.Length > 0 && a.Value[0] is StructProperty str && str.Value is UObject o) + if (o.TryGetValue("ParameterValue", out var obj) && obj is ObjectProperty parameterValue) + return GetObjectTexture(parameterValue); + } + return null; + } + } +} diff --git a/FModel/Discord/DiscordIntegration.cs b/FModel/Discord/DiscordIntegration.cs new file mode 100644 index 00000000..3e8ed8ab --- /dev/null +++ b/FModel/Discord/DiscordIntegration.cs @@ -0,0 +1,66 @@ +using DiscordRPC; +using DiscordRPC.Logging; +using FModel.Logger; +using System; +using System.Reflection; + +namespace FModel.Discord +{ + static class DiscordIntegration + { + private const string _DISCORD_APP_ID = "684489366189768767"; + + private static readonly Timestamps _baseTimestamp = new Timestamps { Start = DateTime.UtcNow }; + private static readonly Assets _assets = new Assets + { + LargeImageKey = "official_logo", + SmallImageKey = "verified", + SmallImageText = $"v{Assembly.GetExecutingAssembly().GetName().Version.ToString().Substring(0, 5)}" + }; + private static readonly DiscordRpcClient _client = new DiscordRpcClient(_DISCORD_APP_ID); + private static RichPresence _presence; + + public static void Dispose() => _client.Dispose(); + private static void Initialize() + { + _client.Logger = new ConsoleLogger() { Level = LogLevel.Warning }; + _client.OnReady += (sender, e) => + { + DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Discord RPC]", $"Ready for {e.User.Username}#{e.User.Discriminator} ({e.User.ID})"); + }; + _client.Initialize(); + } + + public static void StartClient() + { + _client.SetPresence(new RichPresence + { + Assets = _assets, + Timestamps = _baseTimestamp, + State = Properties.Resources.Idling + }); + Initialize(); + SaveCurrentPresence(); + } + + public static void Update(string detail = null, string state = null) + { + _client.SetPresence(new RichPresence + { + Assets = _assets, + Timestamps = _baseTimestamp, + Details = string.IsNullOrEmpty(detail) ? _presence.Details : detail, + State = string.IsNullOrEmpty(state) ? _presence.State : state + }); + _client.Invoke(); + } + + public static void Restore() + { + _client.SetPresence(_presence); + _client.Invoke(); + } + + public static void SaveCurrentPresence() => _presence = _client.CurrentPresence; + } +} diff --git a/FModel/Enums.cs b/FModel/Enums.cs new file mode 100644 index 00000000..2e924931 --- /dev/null +++ b/FModel/Enums.cs @@ -0,0 +1,60 @@ +namespace FModel +{ + public enum EGame + { + Unknown, + Fortnite, + Valorant + } + + public enum EFModel + { + Debug, + Release, + Unknown + } + + public enum EPakLoader + { + Single, + All, + New, + Modified, + NewModified + } + + public enum ECopy + { + Path, + PathNoExt, + File, + FileNoExt + } + + public enum ELanguage : long + { + English, + French, + German, + Italian, + Spanish, + SpanishLatin, + Arabic, + Japanese, + Korean, + Polish, + PortugueseBrazil, + Russian, + Turkish, + Chinese, + TraditionalChinese + } + + public enum EIconDesign : long + { + Default, + NoText, + Mini, + Flat + } +} diff --git a/FModel/FModel.csproj b/FModel/FModel.csproj index 00de34d5..90127e46 100644 --- a/FModel/FModel.csproj +++ b/FModel/FModel.csproj @@ -1,634 +1,236 @@ - - - + + - Debug - AnyCPU - {8AAB27BD-18D7-4164-8BBC-AB534D55D30F} WinExe - FModel - FModel - v4.7.2 - 512 - {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 4 - true - true - - - - false - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - true + netcoreapp3.1 + true + FModel.ico + FModel.App + Asval + + 3.1.0.0 + 3.1.0.0 + FModel.ico + + https://github.com/iAmAsval/FModel + + 3.1.0 + x64 - + + x64 - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 true - 7.1 + 1701;1702;NU1701 - + + x64 - pdbonly - true - bin\Release\ - TRACE - prompt - 4 true - false - 7.1 - - - Logo.ico - - - FModel.Program + 1701;1702;NU1701 + - - ..\packages\Autoupdater.NET.Official.1.5.8\lib\net40\AutoUpdater.NET.dll - - - ..\packages\HtmlAgilityPack.1.11.17\lib\Net45\HtmlAgilityPack.dll - - - ..\packages\AvalonEdit.6.0.0\lib\net45\ICSharpCode.AvalonEdit.dll - - - ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Ookii.Dialogs.Wpf.1.1.0\lib\net45\Ookii.Dialogs.Wpf.dll - - - ..\packages\RestSharp.106.6.10\lib\net452\RestSharp.dll - - - ..\packages\SkiaSharp.1.68.1\lib\net45\SkiaSharp.dll - - - - ..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll - - - - - - ..\packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll - - - - ..\packages\System.Numerics.Vectors.4.6.0-preview5.19224.8\lib\net46\System.Numerics.Vectors.dll - - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.7.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll - - - - - - - - - - - - 4.0 - - - - - - ..\packages\WriteableBitmapEx.1.6.3\lib\net40\WriteableBitmapEx.Wpf.dll - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + + + - - MSBuild:Compile - Designer - - - - AESManager.xaml - - - - - - - ColorPickerControl.xaml - - - - ColorPickerSwatch.xaml - - - ColorPickerWindow.xaml - - - ColorPickRow.xaml - - - SliderRow.xaml - - - FModel_About.xaml - - - FModel_CustomMB.xaml - - - FModel_ImagesMerger.xaml - - - FModel_SearchFiles.xaml - - - FModel_Settings.xaml - - - FModel_UpdateMode.xaml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - FindReplaceWindow.xaml - - - FindWindow.xaml - - - GiveByteWindow.xaml - - - HexBox.xaml - - - - HexEditor.xaml - - - HexViewer.xaml - - - ReplaceByteWindow.xaml - - - - - - - - - - - - - - - - - - - - - - - - - - - - FindReplaceDialog.xaml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True Resources.resx - - - - MSBuild:Compile - Designer - - - App.xaml - Code - - - FModel_Main.xaml - Code - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - MSBuild:Compile - Designer - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - MSBuild:Compile - Designer - - - Designer - MSBuild:Compile - - - MSBuild:Compile - Designer - - - Designer - MSBuild:Compile - - - MSBuild:Compile - Designer - - - - - Code - - + + True True Settings.settings - True - + + + + PublicResXFileCodeGenerator Resources.Designer.cs - - - - - - - SettingsSingleFileGenerator + + + + + PublicSettingsSingleFileGenerator Settings.Designer.cs - - - - - - - - - - - - - - - - - False - Microsoft .NET Framework 4.7.2 %28x86 et x64%29 - true - - - False - .NET Framework 3.5 SP1 - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension) - - - - - - - Ce projet fait référence à des packages NuGet qui sont manquants sur cet ordinateur. Utilisez l'option de restauration des packages NuGet pour les télécharger. Pour plus d'informations, consultez http://go.microsoft.com/fwlink/?LinkID=322105. Le fichier manquant est : {0}. - - - \ No newline at end of file diff --git a/FModel/FModel.ico b/FModel/FModel.ico new file mode 100644 index 00000000..20fb9834 Binary files /dev/null and b/FModel/FModel.ico differ diff --git a/FModel/FModel_Main.xaml b/FModel/FModel_Main.xaml deleted file mode 100644 index 33ae36c4..00000000 --- a/FModel/FModel_Main.xaml +++ /dev/null @@ -1,196 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -